From cf8492a71413071efe6fa4038bf6287c170c6920 Mon Sep 17 00:00:00 2001 From: Moritz Martinius Date: Sun, 12 Nov 2023 12:26:19 +0100 Subject: [PATCH 01/28] A somewhat working state, but still struggeling with pangocairo --- src/PtouchPrint.cpp | 1 + src/graphics/Image.cpp | 56 +++++++++++++++++++++++++++++++++++++++--- src/graphics/Image.hpp | 15 +++++++++++ 3 files changed, 68 insertions(+), 4 deletions(-) diff --git a/src/PtouchPrint.cpp b/src/PtouchPrint.cpp index ee08748..e933166 100644 --- a/src/PtouchPrint.cpp +++ b/src/PtouchPrint.cpp @@ -39,6 +39,7 @@ #include "PrinterDriverFactory.hpp" #include "PtouchPrint.hpp" #include "graphics/Bitmap.hpp" +#include "graphics/Image.hpp" #include "libusbwrap/UsbDeviceFactory.hpp" namespace ptprnt { diff --git a/src/graphics/Image.cpp b/src/graphics/Image.cpp index c28e8c0..d1ca4f7 100644 --- a/src/graphics/Image.cpp +++ b/src/graphics/Image.cpp @@ -19,13 +19,23 @@ #include "Image.hpp" +#include +#include + +#include +#include // remove me #include +#include "cairo.h" +#include "pango/pango-context.h" #include "pango/pango-font.h" +#include "pango/pango-layout.h" +#include "pango/pango-types.h" +#include "pango/pangocairo.h" namespace ptprnt::graphics { Image::Image() { - mSurface = cairo_image_surface_create(CAIRO_FORMAT_A8, 512, 128); + /*mSurface = cairo_image_surface_create(CAIRO_FORMAT_A8, 512, 128); cairo_t* cr = cairo_create(mSurface); mFontDescription = pango_font_description_new(); pango_font_description_set_family(mFontDescription, "sans"); @@ -42,7 +52,7 @@ Image::Image() { cairo_move_to(cr, 0.0, 94.0); pango_cairo_show_layout_line(cr, pango_layout_get_line(mLayout, 0)); - cairo_surface_write_to_png(mSurface, "hello.png"); + cairo_surface_write_to_png(mSurface, "hello.png");*/ } uint8_t* Image::getRaw() { @@ -50,8 +60,46 @@ uint8_t* Image::getRaw() { return cairo_image_surface_get_data(mSurface); } +uint8_t Image::getNumLines(std::string_view strv) { + return std::count(strv.begin(), strv.end(), '\n'); +} + +void Image::createLabel(const std::string& str) { + // create Pango layout first, to get the render dimensions + mSurface = cairo_image_surface_create(CAIRO_FORMAT_A8, 512, 128); + cairo_t* cr = cairo_create(mSurface); + //auto pangoCtx{pango_context_new()}; + auto pangoLyt{pango_cairo_create_layout(cr)}; + auto pangoFontDesc{pango_font_description_from_string("FreeSans 32")}; + + pango_layout_set_single_paragraph_mode(pangoLyt, true); + pango_layout_set_height(pangoLyt, getNumLines(str) * -1); + pango_layout_set_alignment(pangoLyt, PANGO_ALIGN_CENTER); + pango_layout_set_font_description(pangoLyt, pangoFontDesc); + pango_layout_set_text(pangoLyt, str.c_str(), static_cast(str.length())); + + // Debug only + GError* gerr{nullptr}; + pango_layout_write_to_file(pangoLyt, PANGO_LAYOUT_SERIALIZE_DEFAULT, "hello.txt", &gerr); + + // create Cairo surface from p ango layout + int* width{}; + int* height{}; // TODO: change me, fixed for current printer (128px) + PangoRectangle* rect{}; + + pango_cairo_show_layout(cr, pangoLyt); + pango_layout_get_extents(pangoLyt, rect, rect); + std::cerr << "Width is: " << rect->width << " height is:" << rect->height << std::endl; + + //auto cairoSfc{cairo_image_surface_create(CAIRO_FORMAT_A8, *width, *height)}; + + cairo_set_source_rgb(cr, 0.0, 0.0, 0.0); + + // Debug only + cairo_surface_write_to_png(mSurface, "hellow.png"); +} + Image::~Image() { - g_object_unref(mLayout); - pango_font_description_free(mFontDescription); + spdlog::info("Image dtor..."); } } // namespace ptprnt::graphics \ No newline at end of file diff --git a/src/graphics/Image.hpp b/src/graphics/Image.hpp index 2302787..86f2ca5 100644 --- a/src/graphics/Image.hpp +++ b/src/graphics/Image.hpp @@ -22,15 +22,30 @@ #include #include +#include namespace ptprnt::graphics { + +constexpr const char* DEFAULT_FONT_FAMILY = "sans"; +constexpr const uint8_t DEFAULT_FONT_SIZE = 32; + class Image { public: Image(); ~Image(); + + Image(const Image&) = delete; + Image& operator=(const Image&) = delete; + Image(Image&&) = delete; + Image& operator=(Image&&) = delete; + uint8_t* getRaw(); + void createLabel(const std::string& labelText); private: + // methods + uint8_t getNumLines(std::string_view str); + // members PangoLayout* mLayout; PangoFontDescription* mFontDescription; cairo_surface_t* mSurface; -- 2.49.1 From 79477baecd7e2fd5a0fb8451d2db2e54eb26eea1 Mon Sep 17 00:00:00 2001 From: Moritz Martinius Date: Tue, 21 Nov 2023 20:44:20 +0100 Subject: [PATCH 02/28] Width is no automatically chosen based on image width --- src/graphics/Image.cpp | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/graphics/Image.cpp b/src/graphics/Image.cpp index d1ca4f7..56f3a5c 100644 --- a/src/graphics/Image.cpp +++ b/src/graphics/Image.cpp @@ -29,6 +29,7 @@ #include "cairo.h" #include "pango/pango-context.h" #include "pango/pango-font.h" +#include "pango/pango-fontmap.h" #include "pango/pango-layout.h" #include "pango/pango-types.h" #include "pango/pangocairo.h" @@ -65,38 +66,38 @@ uint8_t Image::getNumLines(std::string_view strv) { } void Image::createLabel(const std::string& str) { + // create Pango layout first, to get the render dimensions - mSurface = cairo_image_surface_create(CAIRO_FORMAT_A8, 512, 128); - cairo_t* cr = cairo_create(mSurface); - //auto pangoCtx{pango_context_new()}; - auto pangoLyt{pango_cairo_create_layout(cr)}; - auto pangoFontDesc{pango_font_description_from_string("FreeSans 32")}; + auto pangoCtx{pango_font_map_create_context(pango_cairo_font_map_get_default())}; + auto pangoLyt{pango_layout_new(pangoCtx)}; + auto pangoFontDesc{pango_font_description_from_string("Noto sans 32")}; pango_layout_set_single_paragraph_mode(pangoLyt, true); pango_layout_set_height(pangoLyt, getNumLines(str) * -1); pango_layout_set_alignment(pangoLyt, PANGO_ALIGN_CENTER); pango_layout_set_font_description(pangoLyt, pangoFontDesc); - pango_layout_set_text(pangoLyt, str.c_str(), static_cast(str.length())); + pango_context_load_font(pangoCtx, pangoFontDesc); + pango_layout_set_height(pangoLyt, 128 * PANGO_SCALE) + pango_layout_set_text(pangoLyt, str.c_str(), static_cast(str.length())); // Debug only GError* gerr{nullptr}; pango_layout_write_to_file(pangoLyt, PANGO_LAYOUT_SERIALIZE_DEFAULT, "hello.txt", &gerr); - // create Cairo surface from p ango layout - int* width{}; - int* height{}; // TODO: change me, fixed for current printer (128px) - PangoRectangle* rect{}; + int width, height; + pango_layout_get_size(pangoLyt, &width, &height); + + spdlog::debug("Layout width: {}, height: {}", width / PANGO_SCALE, height / PANGO_SCALE); + + auto surf = cairo_image_surface_create(CAIRO_FORMAT_A8, width / PANGO_SCALE, 128); + cairo_t* cr = cairo_create(surf); pango_cairo_show_layout(cr, pangoLyt); - pango_layout_get_extents(pangoLyt, rect, rect); - std::cerr << "Width is: " << rect->width << " height is:" << rect->height << std::endl; - - //auto cairoSfc{cairo_image_surface_create(CAIRO_FORMAT_A8, *width, *height)}; cairo_set_source_rgb(cr, 0.0, 0.0, 0.0); // Debug only - cairo_surface_write_to_png(mSurface, "hellow.png"); + cairo_surface_write_to_png(surf, "hellow.png"); } Image::~Image() { -- 2.49.1 From 4a59b508394fcb9f8a391e50fd238f6102ae4f43 Mon Sep 17 00:00:00 2001 From: Moritz Martinius Date: Tue, 21 Nov 2023 21:05:48 +0100 Subject: [PATCH 03/28] Fix typo --- src/graphics/Image.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/graphics/Image.cpp b/src/graphics/Image.cpp index 56f3a5c..fe608f9 100644 --- a/src/graphics/Image.cpp +++ b/src/graphics/Image.cpp @@ -77,14 +77,13 @@ void Image::createLabel(const std::string& str) { pango_layout_set_alignment(pangoLyt, PANGO_ALIGN_CENTER); pango_layout_set_font_description(pangoLyt, pangoFontDesc); pango_context_load_font(pangoCtx, pangoFontDesc); - pango_layout_set_height(pangoLyt, 128 * PANGO_SCALE) - pango_layout_set_text(pangoLyt, str.c_str(), static_cast(str.length())); + pango_layout_set_text(pangoLyt, str.c_str(), static_cast(str.length())); // Debug only GError* gerr{nullptr}; pango_layout_write_to_file(pangoLyt, PANGO_LAYOUT_SERIALIZE_DEFAULT, "hello.txt", &gerr); - int width, height; + int width = 0, height = 0; pango_layout_get_size(pangoLyt, &width, &height); -- 2.49.1 From 5f5c0f0f97b9cf05983cd0f56664720b4446bb3c Mon Sep 17 00:00:00 2001 From: Moritz Martinius Date: Tue, 21 Nov 2023 21:14:27 +0100 Subject: [PATCH 04/28] Remove debug output as build pipeline does not have pango >=v1.5.0 --- src/graphics/Image.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/graphics/Image.cpp b/src/graphics/Image.cpp index fe608f9..f9f1425 100644 --- a/src/graphics/Image.cpp +++ b/src/graphics/Image.cpp @@ -79,10 +79,6 @@ void Image::createLabel(const std::string& str) { pango_context_load_font(pangoCtx, pangoFontDesc); pango_layout_set_text(pangoLyt, str.c_str(), static_cast(str.length())); - // Debug only - GError* gerr{nullptr}; - pango_layout_write_to_file(pangoLyt, PANGO_LAYOUT_SERIALIZE_DEFAULT, "hello.txt", &gerr); - int width = 0, height = 0; pango_layout_get_size(pangoLyt, &width, &height); -- 2.49.1 From 28308dccad288950a81b4bbc93755433eaa34e54 Mon Sep 17 00:00:00 2001 From: Moritz Martinius Date: Sun, 3 Dec 2023 11:13:15 +0100 Subject: [PATCH 05/28] Rename Image class to Label --- src/P700Printer.cpp | 6 +++--- src/PtouchPrint.cpp | 16 +++++++++++++--- src/graphics/{Image.cpp => Label.cpp} | 16 ++++++++-------- src/graphics/{Image.hpp => Label.hpp} | 14 +++++++------- src/graphics/hello.png | 6 ++++++ src/graphics/hello.txt | 7 +++++++ src/graphics/ptprnt.log | 0 src/hello.txt | 7 +++++++ src/meson.build | 4 ++-- src/ptprnt.log | 0 .../image_test.cpp => label_test/label_test.cpp} | 6 +++--- tests/meson.build | 8 +++++--- 12 files changed, 61 insertions(+), 29 deletions(-) rename src/graphics/{Image.cpp => Label.cpp} (90%) rename src/graphics/{Image.hpp => Label.hpp} (85%) create mode 100644 src/graphics/hello.png create mode 100644 src/graphics/hello.txt create mode 100644 src/graphics/ptprnt.log create mode 100644 src/hello.txt create mode 100644 src/ptprnt.log rename tests/{image_test/image_test.cpp => label_test/label_test.cpp} (86%) diff --git a/src/P700Printer.cpp b/src/P700Printer.cpp index d022084..2b25419 100644 --- a/src/P700Printer.cpp +++ b/src/P700Printer.cpp @@ -30,7 +30,7 @@ #include #include "graphics/Bitmap.hpp" -#include "graphics/Image.hpp" +#include "graphics/Label.hpp" #include "graphics/Monochrome.hpp" #include "libusb.h" #include "libusbwrap/LibUsbTypes.hpp" @@ -125,7 +125,7 @@ bool P700Printer::detachUsbDevice() { bool P700Printer::printBitmap(const graphics::Bitmap& bitmap) { auto bm = graphics::Bitmap(512, 128); { - auto img = graphics::Image(); + auto img = graphics::Label(); bm.setPixels(std::vector(img.getRaw(), img.getRaw() + 512 * 128)); } @@ -203,7 +203,7 @@ bool P700Printer::send(std::vector& data) { } #else tx = data.size(); - spdlog::debug("USB raw data(len {}): {}", data.size(), spdlog::to_hex(data)); + SPDLOG_DEBUG("USB raw data(len {}): {}", data.size(), spdlog::to_hex(data)); #endif if (tx != static_cast(data.size())) { diff --git a/src/PtouchPrint.cpp b/src/PtouchPrint.cpp index e933166..92ddb9f 100644 --- a/src/PtouchPrint.cpp +++ b/src/PtouchPrint.cpp @@ -39,7 +39,7 @@ #include "PrinterDriverFactory.hpp" #include "PtouchPrint.hpp" #include "graphics/Bitmap.hpp" -#include "graphics/Image.hpp" +#include "graphics/Label.hpp" #include "libusbwrap/UsbDeviceFactory.hpp" namespace ptprnt { @@ -59,7 +59,7 @@ int PtouchPrint::init(int argc, char** argv) { if (mVerboseFlag) { setupLogger(spdlog::level::debug); } else { - setupLogger(spdlog::level::err); + setupLogger(spdlog::level::warn); } if (!mUsbDeviceFactory.init()) { @@ -98,6 +98,11 @@ int PtouchPrint::run() { //printer->printBitmap(bm); //printer->printText("wurst", 1); + if (0 == mCommands.size()) { + spdlog::warn("No command specified, nothing to do..."); + return 0; + } + for (auto& cmd : mCommands) { switch (cmd.first) { case CliCmdType::Text: @@ -152,7 +157,12 @@ std::vector> PtouchPrint::getCompatiblePrinters( void PtouchPrint::setupLogger(spdlog::level::level_enum lvl) { auto consoleSink = std::make_shared(); consoleSink->set_level(lvl); - consoleSink->set_pattern("%^%L:%$ %v"); + if (spdlog::level::level_enum::debug == lvl || spdlog::level::level_enum::trace == lvl) { + // This will enable file and line number for debug and trace macros + consoleSink->set_pattern("%^%L:%$ %v (%s:%#)"); + } else { + consoleSink->set_pattern("%^%L:%$ %v"); + } auto fileSink = std::make_shared("ptprnt.log", true); fileSink->set_level(spdlog::level::trace); diff --git a/src/graphics/Image.cpp b/src/graphics/Label.cpp similarity index 90% rename from src/graphics/Image.cpp rename to src/graphics/Label.cpp index f9f1425..8409755 100644 --- a/src/graphics/Image.cpp +++ b/src/graphics/Label.cpp @@ -17,7 +17,7 @@ */ -#include "Image.hpp" +#include "graphics/Label.hpp" #include #include @@ -35,7 +35,7 @@ #include "pango/pangocairo.h" namespace ptprnt::graphics { -Image::Image() { +Label::Label() { /*mSurface = cairo_image_surface_create(CAIRO_FORMAT_A8, 512, 128); cairo_t* cr = cairo_create(mSurface); mFontDescription = pango_font_description_new(); @@ -56,16 +56,16 @@ Image::Image() { cairo_surface_write_to_png(mSurface, "hello.png");*/ } -uint8_t* Image::getRaw() { +uint8_t* Label::getRaw() { cairo_surface_flush(mSurface); return cairo_image_surface_get_data(mSurface); } -uint8_t Image::getNumLines(std::string_view strv) { +uint8_t Label::getNumLines(std::string_view strv) { return std::count(strv.begin(), strv.end(), '\n'); } -void Image::createLabel(const std::string& str) { +void Label::createLabel(const std::string& str) { // create Pango layout first, to get the render dimensions auto pangoCtx{pango_font_map_create_context(pango_cairo_font_map_get_default())}; @@ -83,7 +83,7 @@ void Image::createLabel(const std::string& str) { pango_layout_get_size(pangoLyt, &width, &height); - spdlog::debug("Layout width: {}, height: {}", width / PANGO_SCALE, height / PANGO_SCALE); + SPDLOG_DEBUG("Layout width: {}, height: {}", width / PANGO_SCALE, height / PANGO_SCALE); auto surf = cairo_image_surface_create(CAIRO_FORMAT_A8, width / PANGO_SCALE, 128); cairo_t* cr = cairo_create(surf); @@ -95,7 +95,7 @@ void Image::createLabel(const std::string& str) { cairo_surface_write_to_png(surf, "hellow.png"); } -Image::~Image() { - spdlog::info("Image dtor..."); +Label::~Label() { + SPDLOG_DEBUG("Image dtor..."); } } // namespace ptprnt::graphics \ No newline at end of file diff --git a/src/graphics/Image.hpp b/src/graphics/Label.hpp similarity index 85% rename from src/graphics/Image.hpp rename to src/graphics/Label.hpp index 86f2ca5..db880de 100644 --- a/src/graphics/Image.hpp +++ b/src/graphics/Label.hpp @@ -29,15 +29,15 @@ namespace ptprnt::graphics { constexpr const char* DEFAULT_FONT_FAMILY = "sans"; constexpr const uint8_t DEFAULT_FONT_SIZE = 32; -class Image { +class Label { public: - Image(); - ~Image(); + Label(); + ~Label(); - Image(const Image&) = delete; - Image& operator=(const Image&) = delete; - Image(Image&&) = delete; - Image& operator=(Image&&) = delete; + Label(const Label&) = delete; + Label& operator=(const Label&) = delete; + Label(Label&&) = delete; + Label& operator=(Label&&) = delete; uint8_t* getRaw(); void createLabel(const std::string& labelText); diff --git a/src/graphics/hello.png b/src/graphics/hello.png new file mode 100644 index 0000000..8aed0fc --- /dev/null +++ b/src/graphics/hello.png @@ -0,0 +1,6 @@ +{ + "text" : "Hello, world :)!", + "font" : "Liberation Sans", + "single-paragraph" : true, + "height" : 0 +} diff --git a/src/graphics/hello.txt b/src/graphics/hello.txt new file mode 100644 index 0000000..a5916b9 --- /dev/null +++ b/src/graphics/hello.txt @@ -0,0 +1,7 @@ +{ + "text" : " Hello", + "font" : "FreeSans 32", + "single-paragraph" : true, + "alignment" : "center", + "height" : 0 +} diff --git a/src/graphics/ptprnt.log b/src/graphics/ptprnt.log new file mode 100644 index 0000000..e69de29 diff --git a/src/hello.txt b/src/hello.txt new file mode 100644 index 0000000..a5916b9 --- /dev/null +++ b/src/hello.txt @@ -0,0 +1,7 @@ +{ + "text" : " Hello", + "font" : "FreeSans 32", + "single-paragraph" : true, + "alignment" : "center", + "height" : 0 +} diff --git a/src/meson.build b/src/meson.build index 9061231..ee66606 100644 --- a/src/meson.build +++ b/src/meson.build @@ -10,7 +10,7 @@ ptprnt_hpps = files ( 'PtouchPrint.hpp', 'PrinterDriverFactory.hpp', 'graphics/Bitmap.hpp', - 'graphics/Image.hpp', + 'graphics/Label.hpp', 'graphics/Monochrome.hpp' ) @@ -18,7 +18,7 @@ ptprnt_srcs = files ( 'PtouchPrint.cpp', 'PrinterDriverFactory.cpp', 'P700Printer.cpp', - 'graphics/Image.cpp', + 'graphics/Label.cpp', 'graphics/Bitmap.cpp', 'graphics/Monochrome.cpp', 'libusbwrap/UsbDeviceFactory.cpp', diff --git a/src/ptprnt.log b/src/ptprnt.log new file mode 100644 index 0000000..e69de29 diff --git a/tests/image_test/image_test.cpp b/tests/label_test/label_test.cpp similarity index 86% rename from tests/image_test/image_test.cpp rename to tests/label_test/label_test.cpp index 46d0f08..a4f1dfd 100644 --- a/tests/image_test/image_test.cpp +++ b/tests/label_test/label_test.cpp @@ -17,10 +17,10 @@ */ -#include "graphics/Image.hpp" +#include "graphics/Label.hpp" #include -TEST(basic_test, Image_smokeTest_succeeds) { - auto im = ptprnt::graphics::Image(); +TEST(basic_test, Label_smokeTest_succeeds) { + auto im = ptprnt::graphics::Label(); } diff --git a/tests/meson.build b/tests/meson.build index 1d45232..4d59e27 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -5,9 +5,9 @@ tests = [ ['../src/graphics/Bitmap.cpp', 'bitmap_test/bitmap_test.cpp'], ], [ - 'image_test', - 'image_test_exe', - ['../src/graphics/Image.cpp', 'image_test/image_test.cpp'], + 'label_test', + 'label_test_exe', + ['../src/graphics/Label.cpp', 'label_test/label_test.cpp'], ], [ 'monochrome_test', @@ -36,3 +36,5 @@ foreach test : tests ), ) endforeach + + -- 2.49.1 From 09a2e621d67122fc0040429ec6e4967c77e6b193 Mon Sep 17 00:00:00 2001 From: Moritz Martinius Date: Sun, 3 Dec 2023 13:20:30 +0100 Subject: [PATCH 06/28] Some side tracking fixing undefined behaviour and memory vulnurabilities --- meson.build | 2 +- src/PtouchPrint.cpp | 2 +- src/graphics/Monochrome.cpp | 7 +-- src/libusbwrap/UsbDevice.cpp | 13 +++--- src/libusbwrap/UsbDevice.hpp | 23 +++++++--- src/libusbwrap/UsbDeviceFactory.cpp | 43 +++++++++++-------- src/libusbwrap/UsbDeviceFactory.hpp | 17 +++++--- .../interface/IUsbDeviceFactory.hpp | 4 +- 8 files changed, 69 insertions(+), 42 deletions(-) diff --git a/meson.build b/meson.build index fdc0c4a..d523b8d 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ project('ptprnt', 'cpp', version: 'v0.1.0-'+run_command('git', 'rev-parse', '--short', 'HEAD', check: true).stdout().strip(), license: 'GPLv3', - default_options : ['c_std=c11', 'cpp_std=c++17'] + default_options : ['c_std=c11', 'cpp_std=c++17', 'b_sanitize=address,undefined', 'b_lto=true', 'b_lto_mode=thin', 'b_thinlto_cache=true'] ) usb_dep = dependency('libusb-1.0') diff --git a/src/PtouchPrint.cpp b/src/PtouchPrint.cpp index 92ddb9f..2fae215 100644 --- a/src/PtouchPrint.cpp +++ b/src/PtouchPrint.cpp @@ -91,7 +91,7 @@ int PtouchPrint::run() { "Found more than one device of the same printer on bus. Currently not supported"); return -1; } - printer->attachUsbDevice(devices[0]); + printer->attachUsbDevice(std::move(devices[0])); auto status = printer->getPrinterStatus(); spdlog::info("Detected tape width is {}mm", status.tapeWidthMm); auto bm = ptprnt::graphics::Bitmap(512, 128); diff --git a/src/graphics/Monochrome.cpp b/src/graphics/Monochrome.cpp index 596ccbc..b14b3fd 100644 --- a/src/graphics/Monochrome.cpp +++ b/src/graphics/Monochrome.cpp @@ -22,6 +22,7 @@ #include #include +#include #include namespace ptprnt::graphics { @@ -41,9 +42,9 @@ std::vector Monochrome::get() { unsigned int outIndex = 0; - for (unsigned int byteNum = 0; byteNum < mPixels.size(); byteNum += 8) { - for (unsigned int bitNo = 0; bitNo < 8; bitNo++) { - if (mPixels[byteNum + bitNo] > mThreshhold) { + for (unsigned int byteNo = 0; byteNo < mPixels.size(); byteNo += 8) { + for (unsigned int bitNo = 0; bitNo <= 7 && (byteNo + bitNo < mPixels.size()); bitNo++) { + if (mPixels[byteNo + bitNo] > mThreshhold) { outPixels[outIndex] |= (1 << (7 - bitNo)); } else { outPixels[outIndex] &= ~(1 << (7 - bitNo)); diff --git a/src/libusbwrap/UsbDevice.cpp b/src/libusbwrap/UsbDevice.cpp index dc073d8..1c74b41 100644 --- a/src/libusbwrap/UsbDevice.cpp +++ b/src/libusbwrap/UsbDevice.cpp @@ -27,12 +27,13 @@ #include "libusbwrap/interface/IUsbDevice.hpp" namespace libusbwrap { -UsbDevice::UsbDevice(libusb_context* ctx, libusb_device* dev) : mLibusbCtx(ctx), mLibusbDev(dev) { +UsbDevice::UsbDevice(libusb_context* ctx, usbDevice_ptr dev) + : mLibusbCtx(ctx), mLibusbDev(std::move(dev)) { if (mLibusbCtx == nullptr || mLibusbDev == nullptr) { throw std::invalid_argument("ctx or device are nullptr"); } - libusb_get_device_descriptor(dev, &mLibusbDevDesc); + libusb_get_device_descriptor(mLibusbDev.get(), &mLibusbDevDesc); } UsbDevice::~UsbDevice() { @@ -44,7 +45,7 @@ UsbDevice::~UsbDevice() { } bool UsbDevice::open() { - int openStatus = libusb_open(mLibusbDev, &mLibusbDevHandle); + int openStatus = libusb_open(mLibusbDev.get(), &mLibusbDevHandle); if (openStatus != 0) { mLastError = static_cast(openStatus); return false; @@ -109,15 +110,15 @@ const usbId UsbDevice::getUsbId() { } const device::Speed UsbDevice::getSpeed() { - return static_cast(libusb_get_device_speed(mLibusbDev)); + return static_cast(libusb_get_device_speed(mLibusbDev.get())); } const uint8_t UsbDevice::getBusNumber() { - return libusb_get_bus_number(mLibusbDev); + return libusb_get_bus_number(mLibusbDev.get()); } const uint8_t UsbDevice::getPortNumber() { - return libusb_get_port_number(mLibusbDev); + return libusb_get_port_number(mLibusbDev.get()); } const Error UsbDevice::getLastError() { diff --git a/src/libusbwrap/UsbDevice.hpp b/src/libusbwrap/UsbDevice.hpp index 712e3a7..02d0b25 100644 --- a/src/libusbwrap/UsbDevice.hpp +++ b/src/libusbwrap/UsbDevice.hpp @@ -27,14 +27,27 @@ #include "libusbwrap/interface/IUsbDevice.hpp" namespace libusbwrap { + +struct usbDevice_deleter { + void operator()(libusb_device* dev_ptr) const { + if (nullptr != dev_ptr) { + libusb_unref_device(dev_ptr); + } + } +}; + +using usbDevice_ptr = std::unique_ptr; + class UsbDevice : public IUsbDevice { public: - explicit UsbDevice(libusb_context* ctx, libusb_device* dev); + explicit UsbDevice(libusb_context* ctx, usbDevice_ptr dev); ~UsbDevice() override; // delete copy ctor and assignment - UsbDevice(const UsbDevice&) = delete; - UsbDevice& operator=(UsbDevice&) = delete; + UsbDevice(const UsbDevice&) = delete; + UsbDevice& operator=(const UsbDevice&) = delete; + UsbDevice(UsbDevice&&) = delete; + UsbDevice& operator=(UsbDevice&&) = delete; bool open() override; void close() override; @@ -58,9 +71,9 @@ class UsbDevice : public IUsbDevice { private: libusb_context* mLibusbCtx{nullptr}; - libusb_device* mLibusbDev{nullptr}; + usbDevice_ptr mLibusbDev{nullptr}; libusb_device_handle* mLibusbDevHandle{nullptr}; - libusb_device_descriptor mLibusbDevDesc; + libusb_device_descriptor mLibusbDevDesc{0}; std::atomic mIsOpen = false; Error mLastError = Error::SUCCESS; }; diff --git a/src/libusbwrap/UsbDeviceFactory.cpp b/src/libusbwrap/UsbDeviceFactory.cpp index 7ad09e9..f475fff 100644 --- a/src/libusbwrap/UsbDeviceFactory.cpp +++ b/src/libusbwrap/UsbDeviceFactory.cpp @@ -20,9 +20,11 @@ #include "libusbwrap/UsbDeviceFactory.hpp" #include +#include #include #include +#include #include "libusb.h" #include "libusbwrap/UsbDevice.hpp" @@ -30,50 +32,53 @@ namespace libusbwrap { -UsbDeviceFactory::~UsbDeviceFactory() { - if (mDeviceListInitialized) { - libusb_free_device_list(mLibusbDeviceList, 1); - } -} +UsbDeviceFactory::~UsbDeviceFactory() = default; -std::vector> UsbDeviceFactory::findAllDevices() { +std::vector> UsbDeviceFactory::findAllDevices() { refreshDeviceList(); return buildMaskedDeviceVector(0x0, 0x0, 0x0, 0x0); } -std::vector> UsbDeviceFactory::findDevices(uint16_t vid, uint16_t pid) { +std::vector> UsbDeviceFactory::findDevices(uint16_t vid, uint16_t pid) { refreshDeviceList(); - return buildMaskedDeviceVector(0xffff, 0xffff, vid, pid); + return buildMaskedDeviceVector(LIBUSB_BITMASK_ALL, LIBUSB_BITMASK_ALL, vid, pid); } -int UsbDeviceFactory::refreshDeviceList() { - int ret = libusb_get_device_list(mLibusbCtx, &mLibusbDeviceList); +ssize_t UsbDeviceFactory::refreshDeviceList() { + libusb_device** list{nullptr}; + + ssize_t ret = libusb_get_device_list(mLibusbCtx, &list); + mLibusbDeviceList.clear(); if (ret < 0) { spdlog::error("Error enumarating USB devices"); } else if (ret == 0) { spdlog::warn("No USB devices found"); } - mDeviceListInitialized = true; + + for (ssize_t i = 0; i < ret; i++) { + mLibusbDeviceList.emplace_back(list[i]); + } + + libusb_free_device_list(list, false); return ret; } -std::vector> UsbDeviceFactory::buildMaskedDeviceVector(uint16_t vidMask, +std::vector> UsbDeviceFactory::buildMaskedDeviceVector(uint16_t vidMask, uint16_t pidMask, uint16_t vid, uint16_t pid) { - std::vector> matchedDevices; + std::vector> matchedDevices; // see libusb/examples/listdevs.c - int i = 0; - libusb_device* currDev = nullptr; - while ((currDev = mLibusbDeviceList[i++]) != nullptr) { - struct libusb_device_descriptor currDevDesc; - int ret = libusb_get_device_descriptor(currDev, &currDevDesc); + for (auto& currDev : mLibusbDeviceList) { + struct libusb_device_descriptor currDevDesc {}; + + int ret = libusb_get_device_descriptor(currDev.get(), &currDevDesc); if (ret < 0) { continue; } if (((currDevDesc.idVendor & vidMask) == vid) && ((currDevDesc.idProduct & pidMask) == pid)) { - matchedDevices.push_back(std::make_shared(mLibusbCtx, currDev)); + matchedDevices.push_back(std::make_unique(mLibusbCtx, std::move(currDev))); } } return matchedDevices; diff --git a/src/libusbwrap/UsbDeviceFactory.hpp b/src/libusbwrap/UsbDeviceFactory.hpp index e2f4d70..558a077 100644 --- a/src/libusbwrap/UsbDeviceFactory.hpp +++ b/src/libusbwrap/UsbDeviceFactory.hpp @@ -19,9 +19,16 @@ #pragma once +#include + +#include "libusb.h" +#include "libusbwrap/UsbDevice.hpp" #include "libusbwrap/interface/IUsbDeviceFactory.hpp" namespace libusbwrap { + +constexpr const uint16_t LIBUSB_BITMASK_ALL = 0xffff; + class UsbDeviceFactory : public IUsbDeviceFactory { public: UsbDeviceFactory() = default; @@ -39,7 +46,7 @@ class UsbDeviceFactory : public IUsbDeviceFactory { * * @return std::vector> Vector of all detected USB devices */ - std::vector> findAllDevices() override; + std::vector> findAllDevices() override; /** * @brief Gets all devices with certain vid/pid combination. * If only one device of certain type is connected, vector is usually only one element @@ -48,17 +55,17 @@ class UsbDeviceFactory : public IUsbDeviceFactory { * @param pid ProductId of the devices to find * @return std::vector> Vector of detected USB devices based on vid/pid */ - std::vector> findDevices(uint16_t vid, uint16_t pid) override; + std::vector> findDevices(uint16_t vid, uint16_t pid) override; private: // methods - int refreshDeviceList(); - std::vector> buildMaskedDeviceVector(uint16_t vidMask, + ssize_t refreshDeviceList(); + std::vector> buildMaskedDeviceVector(uint16_t vidMask, uint16_t pidMask, uint16_t vid, uint16_t pid); // members libusb_context* mLibusbCtx{nullptr}; - libusb_device** mLibusbDeviceList{}; + std::vector mLibusbDeviceList{}; bool mDeviceListInitialized = false; }; } // namespace libusbwrap \ No newline at end of file diff --git a/src/libusbwrap/interface/IUsbDeviceFactory.hpp b/src/libusbwrap/interface/IUsbDeviceFactory.hpp index a31cdc7..a148e44 100644 --- a/src/libusbwrap/interface/IUsbDeviceFactory.hpp +++ b/src/libusbwrap/interface/IUsbDeviceFactory.hpp @@ -10,7 +10,7 @@ namespace libusbwrap { class IUsbDeviceFactory { public: virtual ~IUsbDeviceFactory() = default; - virtual std::vector> findAllDevices() = 0; - virtual std::vector> findDevices(uint16_t vid, uint16_t pid) = 0; + virtual std::vector> findAllDevices() = 0; + virtual std::vector> findDevices(uint16_t vid, uint16_t pid) = 0; }; } // namespace libusbwrap \ No newline at end of file -- 2.49.1 From 1163ae57459eb94dc44cfa1ee70a193ca62bd215 Mon Sep 17 00:00:00 2001 From: Moritz Martinius Date: Sun, 3 Dec 2023 13:23:58 +0100 Subject: [PATCH 07/28] Removing files that shouldn't have been staged --- .vscode/settings.json | 3 +-- src/graphics/hello.png | 6 ------ src/graphics/hello.txt | 7 ------- src/graphics/ptprnt.log | 0 4 files changed, 1 insertion(+), 15 deletions(-) delete mode 100644 src/graphics/hello.png delete mode 100644 src/graphics/hello.txt delete mode 100644 src/graphics/ptprnt.log diff --git a/.vscode/settings.json b/.vscode/settings.json index d512f3b..c15385a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -85,6 +85,5 @@ }, "clang-tidy.buildPath": "builddir/", "clangd.onConfigChanged": "restart", - "C_Cpp.default.compileCommands": "/home/moritz/src/ptouch-prnt/builddir/compile_commands.json", - "C_Cpp.default.configurationProvider": "mesonbuild.mesonbuild" + "C_Cpp.default.compileCommands": "./builddir/compile_commands.json" } diff --git a/src/graphics/hello.png b/src/graphics/hello.png deleted file mode 100644 index 8aed0fc..0000000 --- a/src/graphics/hello.png +++ /dev/null @@ -1,6 +0,0 @@ -{ - "text" : "Hello, world :)!", - "font" : "Liberation Sans", - "single-paragraph" : true, - "height" : 0 -} diff --git a/src/graphics/hello.txt b/src/graphics/hello.txt deleted file mode 100644 index a5916b9..0000000 --- a/src/graphics/hello.txt +++ /dev/null @@ -1,7 +0,0 @@ -{ - "text" : " Hello", - "font" : "FreeSans 32", - "single-paragraph" : true, - "alignment" : "center", - "height" : 0 -} diff --git a/src/graphics/ptprnt.log b/src/graphics/ptprnt.log deleted file mode 100644 index e69de29..0000000 -- 2.49.1 From 5a38600e2ae29b89945732f5b38144b4afa0b30a Mon Sep 17 00:00:00 2001 From: Moritz Martinius Date: Sun, 3 Dec 2023 22:08:59 +0100 Subject: [PATCH 08/28] Got it almost working... --- .vscode/settings.json | 3 +- generate_coverage.sh | 8 ++- meson.build | 2 +- src/P700Printer.cpp | 21 ++++---- src/P700Printer.hpp | 1 + src/PtouchPrint.cpp | 3 -- src/graphics/Bitmap.cpp | 14 ++--- src/graphics/Bitmap.hpp | 16 ++++-- src/graphics/Label.cpp | 84 +++++++++++++++-------------- src/graphics/Label.hpp | 21 ++++++-- src/graphics/Monochrome.cpp | 19 ++++++- src/graphics/Monochrome.hpp | 3 +- src/interface/IPrinterTypes.hpp | 1 + src/libusbwrap/UsbDeviceFactory.cpp | 6 ++- 14 files changed, 128 insertions(+), 74 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index c15385a..d24ff8e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -85,5 +85,6 @@ }, "clang-tidy.buildPath": "builddir/", "clangd.onConfigChanged": "restart", - "C_Cpp.default.compileCommands": "./builddir/compile_commands.json" + "C_Cpp.default.compileCommands": "/home/moritz/Projekte/ptouch-prnt/builddir/compile_commands.json", + "gcovViewer.buildDirectories": ["/home/moritz/Projekte/ptouch-prnt/builddir"] } diff --git a/generate_coverage.sh b/generate_coverage.sh index 32b1ef4..0494499 100755 --- a/generate_coverage.sh +++ b/generate_coverage.sh @@ -2,6 +2,7 @@ SCRIPT_PATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" HTML_COV_PATH="coverageReport/html" +XML_COV_PATH="coverageReport/xml" HTML_START_FILE="index.html" echo "Generating Coverage report for ptouch-prnt" @@ -12,10 +13,15 @@ ninja -C builddir test mkdir -p ${HTML_COV_PATH} gcovr --html --html-details --html-syntax-highlighting --filter src --output ${HTML_COV_PATH}/${HTML_START_FILE} +mkdir -p ${XML_COV_PATH} +gcovr --xml-pretty --filter src --output ${XML_COV_PATH}/cov.xml + if [ $? ] then echo "Coverage report successful generated!" echo "Open: file://${SCRIPT_PATH}/${HTML_COV_PATH}/${HTML_START_FILE}" else echo "Error generating coverage report!" -fi \ No newline at end of file +fi + +rm *.gcov \ No newline at end of file diff --git a/meson.build b/meson.build index d523b8d..2ad425a 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ project('ptprnt', 'cpp', version: 'v0.1.0-'+run_command('git', 'rev-parse', '--short', 'HEAD', check: true).stdout().strip(), license: 'GPLv3', - default_options : ['c_std=c11', 'cpp_std=c++17', 'b_sanitize=address,undefined', 'b_lto=true', 'b_lto_mode=thin', 'b_thinlto_cache=true'] + default_options : ['c_std=c11', 'cpp_std=c++17', 'b_sanitize=none', 'b_lto=true', 'b_lto_mode=thin', 'b_thinlto_cache=true'] ) usb_dep = dependency('libusb-1.0') diff --git a/src/P700Printer.cpp b/src/P700Printer.cpp index 2b25419..d9a3451 100644 --- a/src/P700Printer.cpp +++ b/src/P700Printer.cpp @@ -123,21 +123,23 @@ bool P700Printer::detachUsbDevice() { } bool P700Printer::printBitmap(const graphics::Bitmap& bitmap) { - auto bm = graphics::Bitmap(512, 128); - { - auto img = graphics::Label(); - bm.setPixels(std::vector(img.getRaw(), img.getRaw() + 512 * 128)); +#ifdef DRYRUN + SPDLOG_DEBUG("DRYRUN enabled"); + for (unsigned int lineNo = 0; lineNo < bitmap.getHeight(); lineNo++) { + auto line = bitmap.getLine(lineNo); + auto monoLine = graphics::Monochrome(*line); + monoLine.visualize(); } +#endif send(commands["rasterstart"]); - std::vector rastercmd(4); rastercmd[0] = 0x47; rastercmd[1] = 0x00; // size +1 rastercmd[2] = 0x00; rastercmd[3] = 0x00; // size -1 - for (unsigned int i = 0; i < bm.getWidth(); i++) { - auto bmcol = bm.getCol(i); + for (unsigned int i = 0; i < bitmap.getWidth(); i++) { + auto bmcol = bitmap.getCol(i); if (!bmcol) { spdlog::error("Out of bounds bitmap access"); break; @@ -152,6 +154,7 @@ bool P700Printer::printBitmap(const graphics::Bitmap& bitmap) buf[1] = col.size() + 1; buf[3] = col.size() - 1; if (!send(buf)) { + spdlog::error("Error sending buffer to printer"); break; }; } @@ -203,11 +206,11 @@ bool P700Printer::send(std::vector& data) { } #else tx = data.size(); - SPDLOG_DEBUG("USB raw data(len {}): {}", data.size(), spdlog::to_hex(data)); + spdlog::info("USB raw data(len {}): {}", data.size(), spdlog::to_hex(data)); #endif if (tx != static_cast(data.size())) { - spdlog::error("Could not transfer all data via USB bulk transfer. Only send {} of {} bytes", + spdlog::error("Could not transfer all data via USB bulk transfer. Only sent {} of {} bytes", tx, data.size()); return false; } diff --git a/src/P700Printer.hpp b/src/P700Printer.hpp index ae72a3c..2d82448 100644 --- a/src/P700Printer.hpp +++ b/src/P700Printer.hpp @@ -17,6 +17,7 @@ */ +#include #include #include diff --git a/src/PtouchPrint.cpp b/src/PtouchPrint.cpp index 2fae215..28369c7 100644 --- a/src/PtouchPrint.cpp +++ b/src/PtouchPrint.cpp @@ -94,9 +94,6 @@ int PtouchPrint::run() { printer->attachUsbDevice(std::move(devices[0])); auto status = printer->getPrinterStatus(); spdlog::info("Detected tape width is {}mm", status.tapeWidthMm); - auto bm = ptprnt::graphics::Bitmap(512, 128); - //printer->printBitmap(bm); - //printer->printText("wurst", 1); if (0 == mCommands.size()) { spdlog::warn("No command specified, nothing to do..."); diff --git a/src/graphics/Bitmap.cpp b/src/graphics/Bitmap.cpp index bffb917..b31f045 100644 --- a/src/graphics/Bitmap.cpp +++ b/src/graphics/Bitmap.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include #include @@ -32,19 +33,20 @@ Bitmap::Bitmap(uint16_t width, uint16_t height) : mWidth{width}, mHeight{height}, mPixels(width * height) {} template -uint16_t Bitmap::getWidth() { +[[nodiscard]] uint16_t Bitmap::getWidth() const { return mWidth; } template -uint16_t Bitmap::getHeight() { +[[nodiscard]] uint16_t Bitmap::getHeight() const { return mHeight; } template bool Bitmap::setPixels(const std::vector& pixels) { if (pixels.size() != mPixels.size()) { - spdlog::error("Invalid pixel buffer size."); + spdlog::error("Invalid pixel buffer size (got {} vs. {} bitmap size).", pixels.size(), + mPixels.size()); return false; } @@ -53,12 +55,12 @@ bool Bitmap::setPixels(const std::vector& pixels) { } template -std::vector Bitmap::getPixelsCpy() { +[[nodiscard]] std::vector Bitmap::getPixelsCpy() const { return mPixels; } template -std::optional> Bitmap::getLine(uint16_t line) { +[[nodiscard]] std::optional> Bitmap::getLine(uint16_t line) const { if (line >= mHeight) { // out of bound return std::nullopt; @@ -70,7 +72,7 @@ std::optional> Bitmap::getLine(uint16_t line) { } template -std::optional> Bitmap::getCol(uint16_t col) { +[[nodiscard]] std::optional> Bitmap::getCol(uint16_t col) const { if (col >= mWidth) { // out of bound return std::nullopt; diff --git a/src/graphics/Bitmap.hpp b/src/graphics/Bitmap.hpp index 16325e8..1a2a84a 100644 --- a/src/graphics/Bitmap.hpp +++ b/src/graphics/Bitmap.hpp @@ -39,12 +39,18 @@ class Bitmap { Bitmap(uint16_t width, uint16_t height); ~Bitmap() = default; - uint16_t getWidth(); - uint16_t getHeight(); + Bitmap(const Bitmap&) = default; + Bitmap& operator=(const Bitmap&) = default; + Bitmap(Bitmap&&) = default; + Bitmap& operator=(Bitmap&&) = default; + + [[nodiscard]] uint16_t getWidth() const; + [[nodiscard]] uint16_t getHeight() const; bool setPixels(const std::vector& pixels); - std::vector getPixelsCpy(); - std::optional> getLine(uint16_t line); - std::optional> getCol(uint16_t col); + [[nodiscard]] std::vector getPixelsCpy() const; + [[nodiscard]] std::optional> getLine(uint16_t line) const; + [[nodiscard]] std::optional> getCol(uint16_t col) const; + void visualize() const; private: uint16_t mWidth; diff --git a/src/graphics/Label.cpp b/src/graphics/Label.cpp index 8409755..1c5791e 100644 --- a/src/graphics/Label.cpp +++ b/src/graphics/Label.cpp @@ -22,9 +22,11 @@ #include #include +#include #include #include // remove me #include +#include #include "cairo.h" #include "pango/pango-context.h" @@ -35,67 +37,69 @@ #include "pango/pangocairo.h" namespace ptprnt::graphics { -Label::Label() { - /*mSurface = cairo_image_surface_create(CAIRO_FORMAT_A8, 512, 128); - cairo_t* cr = cairo_create(mSurface); - mFontDescription = pango_font_description_new(); - pango_font_description_set_family(mFontDescription, "sans"); - pango_font_description_set_weight(mFontDescription, PANGO_WEIGHT_SEMIBOLD); - pango_font_description_set_size(mFontDescription, 60 * PANGO_SCALE); +Label::Label() + : mPangoCtx(pango_font_map_create_context(pango_cairo_font_map_get_default())), + mPangoLyt(pango_layout_new(mPangoCtx)), + mPangoFontDesc(pango_font_description_from_string("Noto sans 32")) {} - std::string printThis("Mist 💩"); +std::vector Label::getRaw() { + assert(mSurface != nullptr); + size_t len = mPrinterHeight * mLayoutWidth; - mLayout = pango_cairo_create_layout(cr); - pango_layout_set_font_description(mLayout, mFontDescription); - pango_layout_set_text(mLayout, printThis.c_str(), -1); - - cairo_set_source_rgb(cr, 0.0, 0.0, 0.0); - cairo_move_to(cr, 0.0, 94.0); - pango_cairo_show_layout_line(cr, pango_layout_get_line(mLayout, 0)); - - cairo_surface_write_to_png(mSurface, "hello.png");*/ -} - -uint8_t* Label::getRaw() { cairo_surface_flush(mSurface); - return cairo_image_surface_get_data(mSurface); + auto data = cairo_image_surface_get_data(mSurface); + return {data, data + len}; } uint8_t Label::getNumLines(std::string_view strv) { return std::count(strv.begin(), strv.end(), '\n'); } -void Label::createLabel(const std::string& str) { +int Label::getLayoutHeight() { + return mLayoutHeight; +} - // create Pango layout first, to get the render dimensions - auto pangoCtx{pango_font_map_create_context(pango_cairo_font_map_get_default())}; - auto pangoLyt{pango_layout_new(pangoCtx)}; - auto pangoFontDesc{pango_font_description_from_string("Noto sans 32")}; +int Label::getLayoutWidth() { + return mLayoutWidth; +} - pango_layout_set_single_paragraph_mode(pangoLyt, true); - pango_layout_set_height(pangoLyt, getNumLines(str) * -1); - pango_layout_set_alignment(pangoLyt, PANGO_ALIGN_CENTER); - pango_layout_set_font_description(pangoLyt, pangoFontDesc); - pango_context_load_font(pangoCtx, pangoFontDesc); - pango_layout_set_text(pangoLyt, str.c_str(), static_cast(str.length())); +void Label::create(const std::string& labelText, const uint16_t heightPixel) { + mPrinterHeight = heightPixel; + pango_layout_set_single_paragraph_mode(mPangoLyt, true); + pango_layout_set_height(mPangoLyt, getNumLines(labelText) * -1); + pango_layout_set_alignment(mPangoLyt, PANGO_ALIGN_CENTER); + pango_layout_set_font_description(mPangoLyt, mPangoFontDesc); + pango_context_load_font(mPangoCtx, mPangoFontDesc); + pango_layout_set_text(mPangoLyt, labelText.c_str(), static_cast(labelText.length())); - int width = 0, height = 0; + pango_layout_get_size(mPangoLyt, &mLayoutWidth, &mLayoutHeight); - pango_layout_get_size(pangoLyt, &width, &height); + mLayoutWidth /= PANGO_SCALE; + mLayoutHeight /= PANGO_SCALE; - SPDLOG_DEBUG("Layout width: {}, height: {}", width / PANGO_SCALE, height / PANGO_SCALE); + SPDLOG_DEBUG("Layout width: {}, height: {}", mLayoutWidth, mLayoutHeight); - auto surf = cairo_image_surface_create(CAIRO_FORMAT_A8, width / PANGO_SCALE, 128); - cairo_t* cr = cairo_create(surf); - pango_cairo_show_layout(cr, pangoLyt); + mSurface = cairo_image_surface_create(CAIRO_FORMAT_A8, mLayoutWidth, mPrinterHeight); + cairo_t* cr = cairo_create(mSurface); + pango_cairo_show_layout(cr, mPangoLyt); cairo_set_source_rgb(cr, 0.0, 0.0, 0.0); + cairo_surface_flush(mSurface); + cairo_destroy(cr); +} - // Debug only - cairo_surface_write_to_png(surf, "hellow.png"); +void Label::writeToPng(const std::string& file) { + if (mSurface) { + cairo_surface_flush(mSurface); + cairo_surface_write_to_png(mSurface, file.c_str()); + } } Label::~Label() { SPDLOG_DEBUG("Image dtor..."); + pango_font_description_free(mPangoFontDesc); + g_object_unref(mPangoCtx); + g_object_unref(mPangoLyt); + cairo_surface_destroy(mSurface); } } // namespace ptprnt::graphics \ No newline at end of file diff --git a/src/graphics/Label.hpp b/src/graphics/Label.hpp index db880de..ff6927a 100644 --- a/src/graphics/Label.hpp +++ b/src/graphics/Label.hpp @@ -23,6 +23,11 @@ #include #include +#include + +#include "cairo.h" +#include "pango/pango-font.h" +#include "pango/pango-types.h" namespace ptprnt::graphics { @@ -39,15 +44,21 @@ class Label { Label(Label&&) = delete; Label& operator=(Label&&) = delete; - uint8_t* getRaw(); - void createLabel(const std::string& labelText); + std::vector getRaw(); + void create(const std::string& labelText, const uint16_t heightPixel); + void writeToPng(const std::string& file); + int getLayoutWidth(); + int getLayoutHeight(); private: // methods uint8_t getNumLines(std::string_view str); // members - PangoLayout* mLayout; - PangoFontDescription* mFontDescription; - cairo_surface_t* mSurface; + PangoContext* mPangoCtx{nullptr}; + PangoLayout* mPangoLyt{nullptr}; + PangoFontDescription* mPangoFontDesc{nullptr}; + cairo_surface_t* mSurface{nullptr}; + int mLayoutWidth = 0, mLayoutHeight = 0; + int mPrinterHeight = 0; }; } // namespace ptprnt::graphics \ No newline at end of file diff --git a/src/graphics/Monochrome.cpp b/src/graphics/Monochrome.cpp index b14b3fd..2c1a892 100644 --- a/src/graphics/Monochrome.cpp +++ b/src/graphics/Monochrome.cpp @@ -21,7 +21,8 @@ #include -#include +#include +#include #include #include @@ -57,4 +58,20 @@ std::vector Monochrome::get() { } return outPixels; } + +void Monochrome::visualize() { + auto mono = get(); + for (unsigned char pix : mono) { + std::cout << ((pix & (1 << 7)) == 0 ? "." : "x"); + std::cout << ((pix & (1 << 6)) == 0 ? "." : "x"); + std::cout << ((pix & (1 << 5)) == 0 ? "." : "x"); + std::cout << ((pix & (1 << 4)) == 0 ? "." : "x"); + std::cout << ((pix & (1 << 3)) == 0 ? "." : "x"); + std::cout << ((pix & (1 << 2)) == 0 ? "." : "x"); + std::cout << ((pix & (1 << 1)) == 0 ? "." : "x"); + std::cout << ((pix & (1 << 0)) == 0 ? "." : "x"); + } + std::cout << std::endl; +} + } // namespace ptprnt::graphics \ No newline at end of file diff --git a/src/graphics/Monochrome.hpp b/src/graphics/Monochrome.hpp index b029de9..9b7db86 100644 --- a/src/graphics/Monochrome.hpp +++ b/src/graphics/Monochrome.hpp @@ -31,11 +31,12 @@ class Monochrome { void setThreshold(uint8_t); void invert(bool shouldInvert); + void visualize(); std::vector get(); private: const std::vector& mPixels; - uint8_t mThreshhold = 127; + uint8_t mThreshhold = UINT8_MAX / 2; bool mShouldInvert = false; }; } // namespace ptprnt::graphics \ No newline at end of file diff --git a/src/interface/IPrinterTypes.hpp b/src/interface/IPrinterTypes.hpp index 38fd954..1cce413 100644 --- a/src/interface/IPrinterTypes.hpp +++ b/src/interface/IPrinterTypes.hpp @@ -32,6 +32,7 @@ struct PrinterInfo { std::string_view name = ""; std::string_view version = ""; libusbwrap::usbId usbId{0x00, 0x00}; + uint16_t pixelLines = 0; }; struct PrinterStatus { diff --git a/src/libusbwrap/UsbDeviceFactory.cpp b/src/libusbwrap/UsbDeviceFactory.cpp index f475fff..20991ba 100644 --- a/src/libusbwrap/UsbDeviceFactory.cpp +++ b/src/libusbwrap/UsbDeviceFactory.cpp @@ -32,7 +32,11 @@ namespace libusbwrap { -UsbDeviceFactory::~UsbDeviceFactory() = default; +UsbDeviceFactory::~UsbDeviceFactory() { + if (mLibusbCtx) { + libusb_exit(mLibusbCtx); + } +}; std::vector> UsbDeviceFactory::findAllDevices() { refreshDeviceList(); -- 2.49.1 From 6857de7b1f200567296b044793a825fc5fc6bc9c Mon Sep 17 00:00:00 2001 From: Moritz Martinius Date: Sat, 20 Apr 2024 12:03:20 +0200 Subject: [PATCH 09/28] commands restructured to be const vectors --- .clang-format | 2 +- .vscode/settings.json | 7 +++++-- meson.build | 2 +- src/P700Printer.cpp | 22 ++++++++++------------ src/P700Printer.hpp | 24 ++++++++++++++++++++---- src/PtouchPrint.cpp | 17 ++++++----------- src/interface/IPrinterDriver.hpp | 1 - src/interface/IPrinterTypes.hpp | 3 +++ 8 files changed, 46 insertions(+), 32 deletions(-) diff --git a/.clang-format b/.clang-format index a043404..08a6e94 100644 --- a/.clang-format +++ b/.clang-format @@ -41,7 +41,7 @@ BreakBeforeBinaryOperators: None BreakBeforeTernaryOperators: true BreakConstructorInitializers: BeforeColon BreakInheritanceList: BeforeColon -ColumnLimit: 100 +ColumnLimit: 120 CompactNamespaces: false ContinuationIndentWidth: 4 Cpp11BracedListStyle: true diff --git a/.vscode/settings.json b/.vscode/settings.json index d24ff8e..eef27fa 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -85,6 +85,9 @@ }, "clang-tidy.buildPath": "builddir/", "clangd.onConfigChanged": "restart", - "C_Cpp.default.compileCommands": "/home/moritz/Projekte/ptouch-prnt/builddir/compile_commands.json", - "gcovViewer.buildDirectories": ["/home/moritz/Projekte/ptouch-prnt/builddir"] + "C_Cpp.default.compileCommands": "/home/moritz/src/ptouch-prnt/builddir/compile_commands.json", + "gcovViewer.buildDirectories": [ + "/home/moritz/Projekte/ptouch-prnt/builddir" + ], + "C_Cpp.default.configurationProvider": "mesonbuild.mesonbuild" } diff --git a/meson.build b/meson.build index 2ad425a..2c23677 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ project('ptprnt', 'cpp', version: 'v0.1.0-'+run_command('git', 'rev-parse', '--short', 'HEAD', check: true).stdout().strip(), license: 'GPLv3', - default_options : ['c_std=c11', 'cpp_std=c++17', 'b_sanitize=none', 'b_lto=true', 'b_lto_mode=thin', 'b_thinlto_cache=true'] + default_options : ['c_std=c11', 'cpp_std=c++20', 'b_sanitize=none', 'b_lto=true', 'b_lto_mode=thin', 'b_thinlto_cache=true'] ) usb_dep = dependency('libusb-1.0') diff --git a/src/P700Printer.cpp b/src/P700Printer.cpp index d9a3451..0ab0688 100644 --- a/src/P700Printer.cpp +++ b/src/P700Printer.cpp @@ -37,7 +37,7 @@ #include "spdlog/fmt/bin_to_hex.h" // as long as DRYRUN is defined, no data is actually send to the printer, we need to save some tape ;) -//#define DRYRUN +#define DRYRUN namespace ptprnt::printer { @@ -71,15 +71,14 @@ const PrinterInfo P700Printer::getPrinterInfo() { const PrinterStatus P700Printer::getPrinterStatus() { using namespace std::chrono_literals; - std::vector getStatusCmd({0x1b, 0x69, 0x53}); // status info request - send(getStatusCmd); + send(p700::commands::GET_STATUS); int tx = 0; int tries = 0; std::vector recvBuf(32); while (tries++ < MAX_TRIES_GET_STATUS) { std::this_thread::sleep_for(100ms); - mUsbHndl->bulkTransfer(commands["printerinfo"][0], recvBuf, &tx, 0); + mUsbHndl->bulkTransfer(p700::commands::INFO[0], recvBuf, &tx, 0); } return PrinterStatus{.tapeWidthMm = recvBuf[10]}; @@ -132,7 +131,7 @@ bool P700Printer::printBitmap(const graphics::Bitmap& bitmap) } #endif - send(commands["rasterstart"]); + send(p700::commands::RASTER_START); std::vector rastercmd(4); rastercmd[0] = 0x47; rastercmd[1] = 0x00; // size +1 @@ -159,7 +158,7 @@ bool P700Printer::printBitmap(const graphics::Bitmap& bitmap) }; } - send(commands["eject"]); + send(p700::commands::EJECT); return true; } @@ -184,13 +183,13 @@ bool P700Printer::setVAlign(VAlignPosition vpos) { } bool P700Printer::print() { - send(commands["lf"]); - send(commands["ff"]); - send(commands["eject"]); + send(p700::commands::LF); + send(p700::commands::FF); + send(p700::commands::EJECT); return true; } -bool P700Printer::send(std::vector& data) { +bool P700Printer::send(const std::vector& data) { if (mUsbHndl == nullptr || data.size() > 128) { spdlog::error("Invalid device handle or invalid data."); @@ -210,8 +209,7 @@ bool P700Printer::send(std::vector& data) { #endif if (tx != static_cast(data.size())) { - spdlog::error("Could not transfer all data via USB bulk transfer. Only sent {} of {} bytes", - tx, data.size()); + spdlog::error("Could not transfer all data via USB bulk transfer. Only sent {} of {} bytes", tx, data.size()); return false; } diff --git a/src/P700Printer.hpp b/src/P700Printer.hpp index 2d82448..9c1d06e 100644 --- a/src/P700Printer.hpp +++ b/src/P700Printer.hpp @@ -32,13 +32,23 @@ #pragma once namespace ptprnt::printer { +namespace p700::commands { +const cmd_T GET_STATUS{0x1b, 0x69, 0x53}; +const cmd_T RASTER_START{0x1b, 0x69, 0x7a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; +const cmd_T INFO{0x1b, 0x69, 0x7a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; +const cmd_T PACKBITSON{0x02}; +const cmd_T LF{0x5a}; +const cmd_T FF{0x0c}; +const cmd_T EJECT{0x1a}; +const cmd_T PRINTER_INFO{0x81}; +} // namespace p700::commands constexpr uint8_t MAX_TRIES_GET_STATUS = 10; class P700Printer : public ::ptprnt::IPrinterDriver { public: P700Printer() = default; - ~P700Printer(); + ~P700Printer() override; // delete copy ctor and assignment P700Printer(const P700Printer&) = default; @@ -67,14 +77,20 @@ class P700Printer : public ::ptprnt::IPrinterDriver { bool print() override; private: - bool send(std::vector& data); + bool send(const std::vector& data); bool init(); std::shared_ptr mUsbHndl{nullptr}; + + static constexpr PrinterInfo mInfo{.driverName = "P700", + .name = "Brother P-touch P700", + .version = "v1.0", + .vid = 0x04f9, + .pid = 0x2061, + .pixelLines = 128}; std::map> commands{ {"rasterstart", - {0x1b, 0x69, 0x61, - 0x01}}, // unique for P700, other printers have the 2 byte set to 0x52 instead of 0x61 + {0x1b, 0x69, 0x61, 0x01}}, // unique for P700, other printers have the 2 byte set to 0x52 instead of 0x61 {"info", {0x1b, 0x69, 0x7a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, {"packbitson", {0x02}}, {"lf", {0x5a}}, diff --git a/src/PtouchPrint.cpp b/src/PtouchPrint.cpp index 28369c7..e9d8fa2 100644 --- a/src/PtouchPrint.cpp +++ b/src/PtouchPrint.cpp @@ -75,8 +75,7 @@ int PtouchPrint::run() { mDetectedPrinters = getCompatiblePrinters(); auto numFoundPrinters = mDetectedPrinters.size(); if (numFoundPrinters == 0) { - spdlog::error( - "No compatible printers found, please make sure that they are turned on and connected"); + spdlog::error("No compatible printers found, please make sure that they are turned on and connected"); return -1; } else if (numFoundPrinters > 1) { spdlog::warn("Found more than one compatible printer. Currently not supported."); @@ -85,10 +84,9 @@ int PtouchPrint::run() { auto 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"); + spdlog::warn("Found more than one device of the same printer on bus. Currently not supported"); return -1; } printer->attachUsbDevice(std::move(devices[0])); @@ -115,12 +113,10 @@ int PtouchPrint::run() { printer->setFontSize(static_cast(std::atoi(cmd.second.c_str()))); break; case CliCmdType::HAlign:; - spdlog::debug("[Not implemented] Setting text horizontal alignment to {}", - cmd.second); + spdlog::debug("[Not implemented] Setting text horizontal alignment to {}", cmd.second); break; case CliCmdType::VAlign:; - spdlog::debug("[Not implemented] Setting text vertical alignment to {}", - cmd.second); + spdlog::debug("[Not implemented] Setting text vertical alignment to {}", cmd.second); break; case CliCmdType::None:; [[fallthrough]]; @@ -209,7 +205,6 @@ void PtouchPrint::setupCliParser() { ->each([this](std::string halign) { mCommands.emplace_back(CliCmdType::HAlign, halign); }); // Image options - mApp.add_option("-i,--image", "Image to print. Excludes all text printing ") - ->group("Image printing"); + mApp.add_option("-i,--image", "Image to print. Excludes all text printing ")->group("Image printing"); } } // namespace ptprnt \ No newline at end of file diff --git a/src/interface/IPrinterDriver.hpp b/src/interface/IPrinterDriver.hpp index 3b461bc..9655887 100644 --- a/src/interface/IPrinterDriver.hpp +++ b/src/interface/IPrinterDriver.hpp @@ -28,7 +28,6 @@ #include "libusbwrap/interface/IUsbDevice.hpp" namespace ptprnt { - class IPrinterDriver { public: virtual ~IPrinterDriver() = default; diff --git a/src/interface/IPrinterTypes.hpp b/src/interface/IPrinterTypes.hpp index 1cce413..75d9c31 100644 --- a/src/interface/IPrinterTypes.hpp +++ b/src/interface/IPrinterTypes.hpp @@ -22,6 +22,7 @@ #include #include #include +#include #include "libusbwrap/LibUsbTypes.hpp" @@ -40,6 +41,8 @@ struct PrinterStatus { unsigned int tapeWidthMm = 0.0; }; +using cmd_T = std::vector; + enum class HAlignPosition { UNKNOWN = 0, LEFT = 1, -- 2.49.1 From a47a3189d3fb7c48d36fb6fee45294f95be678c4 Mon Sep 17 00:00:00 2001 From: Moritz Martinius Date: Sat, 20 Apr 2024 13:53:12 +0200 Subject: [PATCH 10/28] Fix issues after rebase --- src/P700Printer.cpp | 10 +++------- src/P700Printer.hpp | 18 ------------------ src/PtouchPrint.cpp | 6 +++--- src/libusbwrap/UsbDevice.cpp | 10 ++++------ src/libusbwrap/UsbDevice.hpp | 5 ++--- src/libusbwrap/interface/IUsbDevice.hpp | 9 ++++----- 6 files changed, 16 insertions(+), 42 deletions(-) diff --git a/src/P700Printer.cpp b/src/P700Printer.cpp index 0ab0688..7efa210 100644 --- a/src/P700Printer.cpp +++ b/src/P700Printer.cpp @@ -19,32 +19,28 @@ #include "P700Printer.hpp" -#include #include #include -#include #include -#include #include #include #include "graphics/Bitmap.hpp" -#include "graphics/Label.hpp" #include "graphics/Monochrome.hpp" -#include "libusb.h" #include "libusbwrap/LibUsbTypes.hpp" #include "spdlog/fmt/bin_to_hex.h" // as long as DRYRUN is defined, no data is actually send to the printer, we need to save some tape ;) -#define DRYRUN +//#define DRYRUN namespace ptprnt::printer { const PrinterInfo P700Printer::mInfo = {.driverName = "P700", .name = "Brother P-touch P700", .version = "v1.0", - .usbId{0x04f9, 0x2061}}; + .usbId{0x04f9, 0x2061}, + .pixelLines = 128}; P700Printer::~P700Printer() { detachUsbDevice(); diff --git a/src/P700Printer.hpp b/src/P700Printer.hpp index 9c1d06e..20b2d66 100644 --- a/src/P700Printer.hpp +++ b/src/P700Printer.hpp @@ -20,7 +20,6 @@ #include #include -#include #include #include @@ -81,22 +80,5 @@ class P700Printer : public ::ptprnt::IPrinterDriver { bool init(); std::shared_ptr mUsbHndl{nullptr}; - - static constexpr PrinterInfo mInfo{.driverName = "P700", - .name = "Brother P-touch P700", - .version = "v1.0", - .vid = 0x04f9, - .pid = 0x2061, - .pixelLines = 128}; - std::map> commands{ - {"rasterstart", - {0x1b, 0x69, 0x61, 0x01}}, // unique for P700, other printers have the 2 byte set to 0x52 instead of 0x61 - {"info", {0x1b, 0x69, 0x7a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, - {"packbitson", {0x02}}, - {"lf", {0x5a}}, - {"ff", {0x0c}}, - {"eject", {0x1a}}, - {"printerinfo", {0x81}}}; }; - } // namespace ptprnt::printer \ No newline at end of file diff --git a/src/PtouchPrint.cpp b/src/PtouchPrint.cpp index e9d8fa2..0e55db4 100644 --- a/src/PtouchPrint.cpp +++ b/src/PtouchPrint.cpp @@ -125,10 +125,10 @@ int PtouchPrint::run() { break; } } - if (!printer->print()) { + /*if (!printer->print()) { spdlog::error("An error occured while printing"); return -1; - } + }*/ return 0; } @@ -138,7 +138,7 @@ std::vector> PtouchPrint::getCompatiblePrinters( auto driverFactory = std::make_unique(); std::vector> foundPrinterDrivers{}; - for (auto usbDev : usbDevs) { + for (auto& usbDev : usbDevs) { auto driver = driverFactory->create(usbDev->getUsbId()); if (driver != nullptr) { foundPrinterDrivers.push_back(driver); diff --git a/src/libusbwrap/UsbDevice.cpp b/src/libusbwrap/UsbDevice.cpp index 1c74b41..c3c632f 100644 --- a/src/libusbwrap/UsbDevice.cpp +++ b/src/libusbwrap/UsbDevice.cpp @@ -27,8 +27,7 @@ #include "libusbwrap/interface/IUsbDevice.hpp" namespace libusbwrap { -UsbDevice::UsbDevice(libusb_context* ctx, usbDevice_ptr dev) - : mLibusbCtx(ctx), mLibusbDev(std::move(dev)) { +UsbDevice::UsbDevice(libusb_context* ctx, usbDevice_ptr dev) : mLibusbCtx(ctx), mLibusbDev(std::move(dev)) { if (mLibusbCtx == nullptr || mLibusbDev == nullptr) { throw std::invalid_argument("ctx or device are nullptr"); } @@ -91,13 +90,12 @@ bool UsbDevice::releaseInterface(int interfaceNo) { return true; } -bool UsbDevice::bulkTransfer(uint8_t endpoint, std::vector& data, int* tx, - unsigned int timeout) { +bool UsbDevice::bulkTransfer(uint8_t endpoint, const std::vector& data, int* tx, unsigned int timeout) { // TODO: implement error handling for incomplete transactions (tx length != data length) int bulkTransferStatus = 0; - bulkTransferStatus = - libusb_bulk_transfer(mLibusbDevHandle, endpoint, data.data(), data.size(), tx, timeout); + bulkTransferStatus = libusb_bulk_transfer(mLibusbDevHandle, endpoint, const_cast(data.data()), + data.size(), tx, timeout); if (bulkTransferStatus != 0) { mLastError = static_cast(bulkTransferStatus); return false; diff --git a/src/libusbwrap/UsbDevice.hpp b/src/libusbwrap/UsbDevice.hpp index 02d0b25..8daa52a 100644 --- a/src/libusbwrap/UsbDevice.hpp +++ b/src/libusbwrap/UsbDevice.hpp @@ -20,7 +20,7 @@ #pragma once #include -#include +#include #include "libusb.h" #include "libusbwrap/LibUsbTypes.hpp" @@ -56,8 +56,7 @@ class UsbDevice : public IUsbDevice { bool detachKernelDriver(int interfaceNo) override; bool claimInterface(int interfaceNo) override; bool releaseInterface(int interfaceNo) override; - bool bulkTransfer(uint8_t endpoint, std::vector& data, int* tx, - unsigned int timeout) override; + bool bulkTransfer(uint8_t endpoint, const std::vector& data, int* tx, unsigned int timeout) override; // getters const usbId getUsbId() override; diff --git a/src/libusbwrap/interface/IUsbDevice.hpp b/src/libusbwrap/interface/IUsbDevice.hpp index e547beb..335e2ca 100644 --- a/src/libusbwrap/interface/IUsbDevice.hpp +++ b/src/libusbwrap/interface/IUsbDevice.hpp @@ -29,11 +29,10 @@ class IUsbDevice { virtual void close() = 0; // libusb wrappers - virtual bool detachKernelDriver(int interfaceNo) = 0; - virtual bool claimInterface(int interfaceNo) = 0; - virtual bool releaseInterface(int interfaceNo) = 0; - virtual bool bulkTransfer(uint8_t endpoint, std::vector& data, int* tx, - unsigned int timeout) = 0; + virtual bool detachKernelDriver(int interfaceNo) = 0; + virtual bool claimInterface(int interfaceNo) = 0; + virtual bool releaseInterface(int interfaceNo) = 0; + virtual bool bulkTransfer(uint8_t endpoint, const std::vector& data, int* tx, unsigned int timeout) = 0; // getters virtual const usbId getUsbId() = 0; -- 2.49.1 From d98399949c5695ffecf09488eaa9e6e3b572c056 Mon Sep 17 00:00:00 2001 From: Moritz Martinius Date: Sat, 20 Apr 2024 14:01:52 +0200 Subject: [PATCH 11/28] Fix printer info issue --- src/P700Printer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/P700Printer.cpp b/src/P700Printer.cpp index 7efa210..265fb92 100644 --- a/src/P700Printer.cpp +++ b/src/P700Printer.cpp @@ -74,7 +74,7 @@ const PrinterStatus P700Printer::getPrinterStatus() { std::vector recvBuf(32); while (tries++ < MAX_TRIES_GET_STATUS) { std::this_thread::sleep_for(100ms); - mUsbHndl->bulkTransfer(p700::commands::INFO[0], recvBuf, &tx, 0); + mUsbHndl->bulkTransfer(p700::commands::PRINTER_INFO[0], recvBuf, &tx, 0); } return PrinterStatus{.tapeWidthMm = recvBuf[10]}; -- 2.49.1 From 37ee7c10f11218fb63244b507aab6e3b599f12ef Mon Sep 17 00:00:00 2001 From: Moritz Martinius Date: Sat, 20 Apr 2024 14:03:51 +0200 Subject: [PATCH 12/28] c++ standard needs to be c++2a as c++20 is not possible with CI --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 2c23677..eff703f 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ project('ptprnt', 'cpp', version: 'v0.1.0-'+run_command('git', 'rev-parse', '--short', 'HEAD', check: true).stdout().strip(), license: 'GPLv3', - default_options : ['c_std=c11', 'cpp_std=c++20', 'b_sanitize=none', 'b_lto=true', 'b_lto_mode=thin', 'b_thinlto_cache=true'] + default_options : ['c_std=c11', 'cpp_std=c++2a', 'b_sanitize=none', 'b_lto=true', 'b_lto_mode=thin', 'b_thinlto_cache=true'] ) usb_dep = dependency('libusb-1.0') -- 2.49.1 From bb7ab6239daa0ccd958131bb6e9290a5efd9b777 Mon Sep 17 00:00:00 2001 From: Moritz Martinius Date: Sun, 28 Apr 2024 17:37:09 +0200 Subject: [PATCH 13/28] Implement basic layouting --- src/P700Printer.cpp | 26 +------- src/P700Printer.hpp | 8 +-- src/PtouchPrint.cpp | 96 +++++++++++++++++++++--------- src/graphics/Label.cpp | 99 ++++++++++++++++++++++++++----- src/graphics/Label.hpp | 36 +++++++---- src/graphics/interface/ILabel.hpp | 78 ++++++++++++++++++++++++ src/interface/IPrinterDriver.hpp | 5 -- src/interface/IPrinterTypes.hpp | 23 ------- 8 files changed, 261 insertions(+), 110 deletions(-) create mode 100644 src/graphics/interface/ILabel.hpp diff --git a/src/P700Printer.cpp b/src/P700Printer.cpp index 265fb92..7a0ac2d 100644 --- a/src/P700Printer.cpp +++ b/src/P700Printer.cpp @@ -32,7 +32,7 @@ #include "spdlog/fmt/bin_to_hex.h" // as long as DRYRUN is defined, no data is actually send to the printer, we need to save some tape ;) -//#define DRYRUN +#define DRYRUN namespace ptprnt::printer { @@ -119,7 +119,7 @@ bool P700Printer::detachUsbDevice() { bool P700Printer::printBitmap(const graphics::Bitmap& bitmap) { #ifdef DRYRUN - SPDLOG_DEBUG("DRYRUN enabled"); + spdlog::debug("DRYRUN enabled"); for (unsigned int lineNo = 0; lineNo < bitmap.getHeight(); lineNo++) { auto line = bitmap.getLine(lineNo); auto monoLine = graphics::Monochrome(*line); @@ -158,26 +158,6 @@ bool P700Printer::printBitmap(const graphics::Bitmap& bitmap) return true; } -bool P700Printer::setText(const std::string& text) { - return true; -}; - -bool P700Printer::setFont(const std::string& text) { - return true; -}; - -bool P700Printer::setFontSize(uint8_t fontSize) { - return true; -}; - -bool P700Printer::setHAlign(HAlignPosition hpos) { - return true; -}; - -bool P700Printer::setVAlign(VAlignPosition vpos) { - return true; -} - bool P700Printer::print() { send(p700::commands::LF); send(p700::commands::FF); @@ -201,7 +181,7 @@ bool P700Printer::send(const std::vector& data) { } #else tx = data.size(); - spdlog::info("USB raw data(len {}): {}", data.size(), spdlog::to_hex(data)); + spdlog::trace("USB raw data(len {}): {}", data.size(), spdlog::to_hex(data)); #endif if (tx != static_cast(data.size())) { diff --git a/src/P700Printer.hpp b/src/P700Printer.hpp index 20b2d66..4b638dc 100644 --- a/src/P700Printer.hpp +++ b/src/P700Printer.hpp @@ -44,6 +44,9 @@ const cmd_T PRINTER_INFO{0x81}; constexpr uint8_t MAX_TRIES_GET_STATUS = 10; +// TODO: +// Remove Text-layout specific parts, add them to label + class P700Printer : public ::ptprnt::IPrinterDriver { public: P700Printer() = default; @@ -67,11 +70,6 @@ class P700Printer : public ::ptprnt::IPrinterDriver { [[nodiscard]] const PrinterStatus getPrinterStatus() override; bool attachUsbDevice(std::shared_ptr usbHndl) override; bool detachUsbDevice() override; - bool setText(const std::string& text) override; - bool setFont(const std::string& text) override; - bool setFontSize(uint8_t fontSize) override; - bool setHAlign(HAlignPosition hpos) override; - bool setVAlign(VAlignPosition vpos) override; bool printBitmap(const graphics::Bitmap& bitmap) override; bool print() override; diff --git a/src/PtouchPrint.cpp b/src/PtouchPrint.cpp index 0e55db4..abfda98 100644 --- a/src/PtouchPrint.cpp +++ b/src/PtouchPrint.cpp @@ -16,12 +16,10 @@ along with this program. If not, see . */ -#include -#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_TRACE -#define SPDLOG_DEBUG_ON -#define SPDLOG_TRACE_ON +#include "PtouchPrint.hpp" #include +#include #include #include #include @@ -31,15 +29,16 @@ #include #include +#include #include #include +#include #include #include "CLI/Option.hpp" #include "PrinterDriverFactory.hpp" -#include "PtouchPrint.hpp" -#include "graphics/Bitmap.hpp" #include "graphics/Label.hpp" +#include "graphics/interface/ILabel.hpp" #include "libusbwrap/UsbDeviceFactory.hpp" namespace ptprnt { @@ -98,37 +97,64 @@ int PtouchPrint::run() { return 0; } - for (auto& cmd : mCommands) { - switch (cmd.first) { + auto label = graphics::Label(); + std::string labelText{}; + + for (const auto& [cmd, value] : mCommands) { + switch (cmd) { case CliCmdType::Text: - spdlog::debug("Setting text to {}", cmd.second); - printer->setText(cmd.second); + if (labelText.empty()) { + labelText = value; + } else { + labelText = labelText + '\n' + value; + } break; - case CliCmdType::Font:; - spdlog::debug("Setting font to {}", cmd.second); - printer->setFont(cmd.second); + case CliCmdType::Font: + spdlog::debug("Setting font to {}", value); + label.setFontFamily(value); break; - case CliCmdType::FontSize:; - spdlog::debug("Setting font size to {}", cmd.second); - printer->setFontSize(static_cast(std::atoi(cmd.second.c_str()))); + case CliCmdType::FontSize: + spdlog::debug("Setting font size to {}", std::stod(value)); + label.setFontSize(std::stod(value)); break; - case CliCmdType::HAlign:; - spdlog::debug("[Not implemented] Setting text horizontal alignment to {}", cmd.second); + case CliCmdType::HAlign: + spdlog::debug("Setting text horizontal alignment to {}", value); + { + auto hPos = HALignPositionMap.find(value); + if (hPos == HALignPositionMap.end()) { + spdlog::warn("Invalid horizontal alignment specified!"); + label.setHAlign(HAlignPosition::UNKNOWN); + } else { + label.setHAlign(hPos->second); + } + } break; - case CliCmdType::VAlign:; - spdlog::debug("[Not implemented] Setting text vertical alignment to {}", cmd.second); + case CliCmdType::VAlign: + spdlog::debug("Setting text vertical alignment to {}", value); + { + auto vPos = VALignPositionMap.find(value); + if (vPos == VALignPositionMap.end()) { + spdlog::warn("Invalid verical alignment specified!"); + label.setVAlign(VAlignPosition::UNKNOWN); + } else { + label.setVAlign(vPos->second); + } + } break; - case CliCmdType::None:; + case CliCmdType::None: [[fallthrough]]; default: spdlog::warn("This command is currently not supported."); break; } } - /*if (!printer->print()) { + if (!printer->print()) { spdlog::error("An error occured while printing"); return -1; - }*/ + } + + label.create(labelText, 128); + label.writeToPng("./testlabel.png"); return 0; } @@ -185,23 +211,39 @@ void PtouchPrint::setupCliParser() { ->each([this](std::string text) { mCommands.emplace_back(CliCmdType::Text, text); }); mApp.add_option("-f,--font", "Font used for the following text occurences") ->group("Text printing ") - ->multi_option_policy(CLI::MultiOptionPolicy::TakeAll) + ->multi_option_policy(CLI::MultiOptionPolicy::TakeFirst) ->trigger_on_parse() ->each([this](std::string font) { mCommands.emplace_back(CliCmdType::Font, font); }); mApp.add_option("-s,--fontsize", "Font size of the following text occurences") ->group("Text printing ") - ->multi_option_policy(CLI::MultiOptionPolicy::TakeAll) + ->multi_option_policy(CLI::MultiOptionPolicy::TakeFirst) ->trigger_on_parse() ->each([this](std::string size) { mCommands.emplace_back(CliCmdType::FontSize, size); }); mApp.add_option("--valign", "Vertical alignment of the following text occurences") ->group("Text printing ") - ->multi_option_policy(CLI::MultiOptionPolicy::TakeAll) + ->multi_option_policy(CLI::MultiOptionPolicy::TakeFirst) ->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); }); + if (validValignOptions.find(in) == validValignOptions.end()) { + return {""}; + } + return in; + }) ->each([this](std::string valign) { mCommands.emplace_back(CliCmdType::VAlign, valign); }); mApp.add_option("--halign", "Vertical alignment of the following text occurences") ->group("Text printing ") - ->multi_option_policy(CLI::MultiOptionPolicy::TakeAll) + ->multi_option_policy(CLI::MultiOptionPolicy::TakeFirst) ->trigger_on_parse() + ->transform([](std::string in) -> std::string { + std::unordered_set validValignOptions{"left", "center", "right", "justify"}; + std::transform(in.begin(), in.end(), in.begin(), [](unsigned char c) { return std::tolower(c); }); + if (validValignOptions.find(in) == validValignOptions.end()) { + return {""}; + } + return in; + }) ->each([this](std::string halign) { mCommands.emplace_back(CliCmdType::HAlign, halign); }); // Image options diff --git a/src/graphics/Label.cpp b/src/graphics/Label.cpp index 1c5791e..a19d104 100644 --- a/src/graphics/Label.cpp +++ b/src/graphics/Label.cpp @@ -24,23 +24,23 @@ #include #include -#include // remove me +#include #include #include #include "cairo.h" -#include "pango/pango-context.h" +#include "graphics/interface/ILabel.hpp" #include "pango/pango-font.h" -#include "pango/pango-fontmap.h" #include "pango/pango-layout.h" #include "pango/pango-types.h" #include "pango/pangocairo.h" namespace ptprnt::graphics { Label::Label() - : mPangoCtx(pango_font_map_create_context(pango_cairo_font_map_get_default())), + : mCairoCtx(cairo_create(mSurface)), + mPangoCtx(pango_cairo_create_context(mCairoCtx)), mPangoLyt(pango_layout_new(mPangoCtx)), - mPangoFontDesc(pango_font_description_from_string("Noto sans 32")) {} + mFontMap(pango_cairo_font_map_new()) {} std::vector Label::getRaw() { assert(mSurface != nullptr); @@ -63,29 +63,78 @@ int Label::getLayoutWidth() { return mLayoutWidth; } -void Label::create(const std::string& labelText, const uint16_t heightPixel) { +bool Label::create(PrintableText printableText, const uint16_t heightPixel) { + setFontFamily(printableText.fontFamily); + setFontSize(printableText.fontSize); + + return create(printableText.text, heightPixel); +} + +bool Label::create(const std::string& labelText, const uint16_t heightPixel) { mPrinterHeight = heightPixel; - pango_layout_set_single_paragraph_mode(mPangoLyt, true); + + // TODO: we need to create a custom fontconfig here so that Noto Emoji does not load the systems default + // fontconfig here. For this, we need to create a PangoFcFontMap and a custom FcConfig + // see: https://docs.gtk.org/PangoFc/method.FontMap.set_config.html + // see: https://gist.github.com/CallumDev/7c66b3f9cf7a876ef75f + PangoFontDescription* regularFont = pango_font_description_new(); + pango_font_description_set_size(regularFont, static_cast(mFontSize * PANGO_SCALE)); + pango_font_description_set_family(regularFont, mFontFamily.c_str()); + //pango_layout_set_single_paragraph_mode(mPangoLyt, true); // this will force a single line in the label width width -1 pango_layout_set_height(mPangoLyt, getNumLines(labelText) * -1); - pango_layout_set_alignment(mPangoLyt, PANGO_ALIGN_CENTER); - pango_layout_set_font_description(mPangoLyt, mPangoFontDesc); - pango_context_load_font(mPangoCtx, mPangoFontDesc); + pango_layout_set_font_description(mPangoLyt, regularFont); pango_layout_set_text(mPangoLyt, labelText.c_str(), static_cast(labelText.length())); - pango_layout_get_size(mPangoLyt, &mLayoutWidth, &mLayoutHeight); + // Set horizontal alignment + switch (mHAlign) { + case HAlignPosition::LEFT: + pango_layout_set_alignment(mPangoLyt, PANGO_ALIGN_LEFT); + break; + case HAlignPosition::RIGHT: + pango_layout_set_alignment(mPangoLyt, PANGO_ALIGN_RIGHT); + break; + case HAlignPosition::JUSTIFY: + pango_layout_set_alignment(mPangoLyt, PANGO_ALIGN_LEFT); // not sure if needed + pango_layout_set_justify(mPangoLyt, true); + pango_layout_set_justify_last_line(mPangoLyt, true); + break; + case HAlignPosition::CENTER: + [[fallthrough]]; + default: + pango_layout_set_alignment(mPangoLyt, PANGO_ALIGN_CENTER); + break; + } + // calculate label size for Cairo surface creation + pango_layout_get_size(mPangoLyt, &mLayoutWidth, &mLayoutHeight); mLayoutWidth /= PANGO_SCALE; mLayoutHeight /= PANGO_SCALE; - SPDLOG_DEBUG("Layout width: {}, height: {}", mLayoutWidth, mLayoutHeight); - + spdlog::debug("Layout width: {}, height: {}", mLayoutWidth, mLayoutHeight); mSurface = cairo_image_surface_create(CAIRO_FORMAT_A8, mLayoutWidth, mPrinterHeight); cairo_t* cr = cairo_create(mSurface); + + // Adjust Cairo cursor position to respect the vertical alignment + switch (mVAlign) { + case VAlignPosition::TOP: + break; + case VAlignPosition::BOTTOM: + cairo_move_to(cr, 0.0, mPrinterHeight - mLayoutHeight); + break; + case VAlignPosition::MIDDLE: + cairo_move_to(cr, 0.0, (mPrinterHeight - mLayoutHeight) / 2); + [[fallthrough]]; + default: + break; + } + + // Finally show the layout on the Cairo surface pango_cairo_show_layout(cr, mPangoLyt); cairo_set_source_rgb(cr, 0.0, 0.0, 0.0); cairo_surface_flush(mSurface); cairo_destroy(cr); + return true; } void Label::writeToPng(const std::string& file) { @@ -95,11 +144,31 @@ void Label::writeToPng(const std::string& file) { } } +void Label::setFontSize(const double fontSize) { + mFontSize = fontSize; +} + +void Label::setFontFamily(const std::string& fontFamily) { + mFontFamily = fontFamily; +} + +void Label::setHAlign(HAlignPosition hAlign) { + mHAlign = hAlign; +} + +void Label::setVAlign(VAlignPosition vAlign) { + mVAlign = vAlign; +} + +void Label::setText(const std::string& text) { + mText = text; +} + Label::~Label() { - SPDLOG_DEBUG("Image dtor..."); - pango_font_description_free(mPangoFontDesc); + spdlog::debug("Image dtor..."); g_object_unref(mPangoCtx); g_object_unref(mPangoLyt); + g_object_unref(mFontMap); cairo_surface_destroy(mSurface); } } // namespace ptprnt::graphics \ No newline at end of file diff --git a/src/graphics/Label.hpp b/src/graphics/Label.hpp index ff6927a..1ec934f 100644 --- a/src/graphics/Label.hpp +++ b/src/graphics/Label.hpp @@ -26,15 +26,12 @@ #include #include "cairo.h" -#include "pango/pango-font.h" +#include "graphics/interface/ILabel.hpp" #include "pango/pango-types.h" namespace ptprnt::graphics { -constexpr const char* DEFAULT_FONT_FAMILY = "sans"; -constexpr const uint8_t DEFAULT_FONT_SIZE = 32; - -class Label { +class Label : public ILabel { public: Label(); ~Label(); @@ -44,20 +41,35 @@ class Label { Label(Label&&) = delete; Label& operator=(Label&&) = delete; - std::vector getRaw(); - void create(const std::string& labelText, const uint16_t heightPixel); + bool create(PrintableText printableText, const uint16_t heightPixel) override; + bool create(const std::string& labelText, const uint16_t heightPixel) override; void writeToPng(const std::string& file); - int getLayoutWidth(); - int getLayoutHeight(); + [[nodiscard]] int getLayoutWidth() override; + [[nodiscard]] int getLayoutHeight() override; + [[nodiscard]] std::vector getRaw() override; + void setFontSize(const double fontSize) override; + void setFontFamily(const std::string& fontFamily) override; + + void setText(const std::string& text) override; + void setHAlign(HAlignPosition hpos) override; + void setVAlign(VAlignPosition vpos) override; private: // methods - uint8_t getNumLines(std::string_view str); + [[nodiscard]] uint8_t getNumLines(std::string_view str); + [[nodiscard]] PangoFontMap* createCustomFontMap(); // members + // TODO: convert raw pointers here into std::unique_ptr with custom deleters, calling g_object_unref() + cairo_surface_t* mSurface{nullptr}; + cairo_t* mCairoCtx{nullptr}; PangoContext* mPangoCtx{nullptr}; PangoLayout* mPangoLyt{nullptr}; - PangoFontDescription* mPangoFontDesc{nullptr}; - cairo_surface_t* mSurface{nullptr}; + PangoFontMap* mFontMap{nullptr}; + double mFontSize{DEFAULT_FONT_SIZE}; + std::string mFontFamily{DEFAULT_FONT_FAMILY}; + HAlignPosition mHAlign = HAlignPosition::LEFT; + VAlignPosition mVAlign = VAlignPosition::MIDDLE; + std::string mText{""}; int mLayoutWidth = 0, mLayoutHeight = 0; int mPrinterHeight = 0; }; diff --git a/src/graphics/interface/ILabel.hpp b/src/graphics/interface/ILabel.hpp new file mode 100644 index 0000000..9987ef1 --- /dev/null +++ b/src/graphics/interface/ILabel.hpp @@ -0,0 +1,78 @@ +/* + ptrnt - print labels on linux + Copyright (C) 2023 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 . + + */ + +#pragma once + +#include +#include +#include +#include + +constexpr const char* DEFAULT_FONT_FAMILY = "sans"; +constexpr const double DEFAULT_FONT_SIZE = 32.0; + +enum class HAlignPosition { + UNKNOWN = 0, + LEFT = 1, + CENTER = 2, + RIGHT = 3, + JUSTIFY = 4, +}; +const std::map HALignPositionMap{{"", HAlignPosition::UNKNOWN}, + {"left", HAlignPosition::LEFT}, + {"center", HAlignPosition::CENTER}, + {"right", HAlignPosition::RIGHT}, + {"justify", HAlignPosition::JUSTIFY}}; + +enum class VAlignPosition { + UNKNOWN = 0, + TOP = 1, + MIDDLE = 2, + BOTTOM = 3, +}; + +const std::map VALignPositionMap{{"", VAlignPosition::UNKNOWN}, + {"top", VAlignPosition::TOP}, + {"middle", VAlignPosition::MIDDLE}, + {"bottom", VAlignPosition::BOTTOM}}; + +struct PrintableText { + std::string text{""}; + std::string fontFamily{DEFAULT_FONT_FAMILY}; + double fontSize{DEFAULT_FONT_SIZE}; + HAlignPosition hAlign{HAlignPosition::LEFT}; + VAlignPosition vAlign{VAlignPosition::MIDDLE}; +}; + +class ILabel { + public: + virtual ~ILabel() = default; + + virtual bool create(PrintableText printableText, const uint16_t heightPixel) = 0; + virtual bool create(const std::string& labelText, const uint16_t heightPixel) = 0; + virtual std::vector getRaw() = 0; + virtual int getLayoutWidth() = 0; + virtual int getLayoutHeight() = 0; + + virtual void setText(const std::string& text) = 0; + virtual void setFontSize(const double fontSize) = 0; + virtual void setFontFamily(const std::string& fontFamily) = 0; + virtual void setHAlign(HAlignPosition hpos) = 0; + virtual void setVAlign(VAlignPosition vpos) = 0; +}; \ No newline at end of file diff --git a/src/interface/IPrinterDriver.hpp b/src/interface/IPrinterDriver.hpp index 9655887..147b9ef 100644 --- a/src/interface/IPrinterDriver.hpp +++ b/src/interface/IPrinterDriver.hpp @@ -39,11 +39,6 @@ class IPrinterDriver { [[nodiscard]] virtual const PrinterStatus getPrinterStatus() = 0; virtual bool attachUsbDevice(std::shared_ptr usbHndl) = 0; virtual bool detachUsbDevice() = 0; - virtual bool setText(const std::string& text) = 0; - virtual bool setFont(const std::string& text) = 0; - virtual bool setFontSize(uint8_t fontSize) = 0; - virtual bool setHAlign(HAlignPosition hpos) = 0; - virtual bool setVAlign(VAlignPosition vpos) = 0; virtual bool printBitmap(const graphics::Bitmap& bitmap) = 0; virtual bool print() = 0; }; diff --git a/src/interface/IPrinterTypes.hpp b/src/interface/IPrinterTypes.hpp index 75d9c31..d1b3d70 100644 --- a/src/interface/IPrinterTypes.hpp +++ b/src/interface/IPrinterTypes.hpp @@ -43,27 +43,4 @@ struct PrinterStatus { using cmd_T = std::vector; -enum class HAlignPosition { - UNKNOWN = 0, - LEFT = 1, - CENTER = 2, - RIGHT = 3, - JUSTIFY = 4, -}; - -enum class VAlignPosition { - UNKNOWN = 0, - TOP = 1, - MIDDLE = 2, - BOTTOM = 3, -}; - -struct PrintableText { - std::string text{""}; - std::string font{"Noto"}; - uint8_t fontSize{0}; - HAlignPosition hAlign{HAlignPosition::LEFT}; - VAlignPosition vAlign{VAlignPosition::MIDDLE}; -}; - } // namespace ptprnt \ No newline at end of file -- 2.49.1 From 59ef4189c440e24690bab91eacb75ed7ef4c4de4 Mon Sep 17 00:00:00 2001 From: Moritz Martinius Date: Sun, 28 Apr 2024 20:02:07 +0200 Subject: [PATCH 14/28] Implement Label printing interface for PrinterDriver --- src/P700Printer.cpp | 12 +++++++++++- src/P700Printer.hpp | 1 + src/PtouchPrint.cpp | 23 ++++++++++++----------- src/graphics/Label.cpp | 21 ++++++++++++++------- src/graphics/Label.hpp | 10 ++++++---- src/graphics/interface/ILabel.hpp | 16 ++++++++++------ src/interface/IPrinterDriver.hpp | 3 ++- tests/label_test/label_test.cpp | 2 +- 8 files changed, 57 insertions(+), 31 deletions(-) diff --git a/src/P700Printer.cpp b/src/P700Printer.cpp index 7a0ac2d..fa43a2b 100644 --- a/src/P700Printer.cpp +++ b/src/P700Printer.cpp @@ -23,6 +23,7 @@ #include #include +#include #include #include @@ -32,7 +33,7 @@ #include "spdlog/fmt/bin_to_hex.h" // as long as DRYRUN is defined, no data is actually send to the printer, we need to save some tape ;) -#define DRYRUN +//#define DRYRUN namespace ptprnt::printer { @@ -158,6 +159,15 @@ bool P700Printer::printBitmap(const graphics::Bitmap& bitmap) return true; } +bool P700Printer::printLabel(std::unique_ptr label) { + // not quite sure if I should stack allocate Bitmap, but data is held on the heap anyway (std::vector). + auto bm = graphics::Bitmap(label->getWidth(), label->getHeight()); + spdlog::debug("Label has {}x{}px size", label->getWidth(), label->getHeight()); + bm.setPixels(label->getRaw()); + printBitmap(bm); + return true; +} + bool P700Printer::print() { send(p700::commands::LF); send(p700::commands::FF); diff --git a/src/P700Printer.hpp b/src/P700Printer.hpp index 4b638dc..5c96c3b 100644 --- a/src/P700Printer.hpp +++ b/src/P700Printer.hpp @@ -71,6 +71,7 @@ class P700Printer : public ::ptprnt::IPrinterDriver { bool attachUsbDevice(std::shared_ptr usbHndl) override; bool detachUsbDevice() override; bool printBitmap(const graphics::Bitmap& bitmap) override; + bool printLabel(const std::unique_ptr label) override; bool print() override; private: diff --git a/src/PtouchPrint.cpp b/src/PtouchPrint.cpp index abfda98..9a5e0d4 100644 --- a/src/PtouchPrint.cpp +++ b/src/PtouchPrint.cpp @@ -97,9 +97,9 @@ int PtouchPrint::run() { return 0; } - auto label = graphics::Label(); + auto label = std::make_unique(printer->getPrinterInfo().pixelLines); std::string labelText{}; - + // TODO: refactor for (const auto& [cmd, value] : mCommands) { switch (cmd) { case CliCmdType::Text: @@ -111,11 +111,11 @@ int PtouchPrint::run() { break; case CliCmdType::Font: spdlog::debug("Setting font to {}", value); - label.setFontFamily(value); + label->setFontFamily(value); break; case CliCmdType::FontSize: spdlog::debug("Setting font size to {}", std::stod(value)); - label.setFontSize(std::stod(value)); + label->setFontSize(std::stod(value)); break; case CliCmdType::HAlign: spdlog::debug("Setting text horizontal alignment to {}", value); @@ -123,9 +123,9 @@ int PtouchPrint::run() { auto hPos = HALignPositionMap.find(value); if (hPos == HALignPositionMap.end()) { spdlog::warn("Invalid horizontal alignment specified!"); - label.setHAlign(HAlignPosition::UNKNOWN); + label->setHAlign(HAlignPosition::UNKNOWN); } else { - label.setHAlign(hPos->second); + label->setHAlign(hPos->second); } } break; @@ -135,9 +135,9 @@ int PtouchPrint::run() { auto vPos = VALignPositionMap.find(value); if (vPos == VALignPositionMap.end()) { spdlog::warn("Invalid verical alignment specified!"); - label.setVAlign(VAlignPosition::UNKNOWN); + label->setVAlign(VAlignPosition::UNKNOWN); } else { - label.setVAlign(vPos->second); + label->setVAlign(vPos->second); } } break; @@ -148,13 +148,13 @@ int PtouchPrint::run() { break; } } - if (!printer->print()) { + label->create(labelText); + label->writeToPng("./testlabel.png"); + if (!printer->printLabel(std::move(label))) { spdlog::error("An error occured while printing"); return -1; } - label.create(labelText, 128); - label.writeToPng("./testlabel.png"); return 0; } @@ -192,6 +192,7 @@ void PtouchPrint::setupLogger(spdlog::level::level_enum lvl) { spdlog::set_default_logger(logger); } +// TODO: CLI parsing should be a seperate class/file void PtouchPrint::setupCliParser() { auto printVersion = [this](std::size_t) { fmt::print("ptprnt version: {}\n", mVersionString); diff --git a/src/graphics/Label.cpp b/src/graphics/Label.cpp index a19d104..fefedbb 100644 --- a/src/graphics/Label.cpp +++ b/src/graphics/Label.cpp @@ -36,11 +36,12 @@ #include "pango/pangocairo.h" namespace ptprnt::graphics { -Label::Label() +Label::Label(const uint16_t heightPixel) : mCairoCtx(cairo_create(mSurface)), mPangoCtx(pango_cairo_create_context(mCairoCtx)), mPangoLyt(pango_layout_new(mPangoCtx)), - mFontMap(pango_cairo_font_map_new()) {} + mFontMap(pango_cairo_font_map_new()), + mPrinterHeight(heightPixel) {} std::vector Label::getRaw() { assert(mSurface != nullptr); @@ -55,6 +56,14 @@ uint8_t Label::getNumLines(std::string_view strv) { return std::count(strv.begin(), strv.end(), '\n'); } +int Label::getWidth() { + return mPrinterHeight; +} + +int Label::getHeight() { + return getLayoutWidth(); +} + int Label::getLayoutHeight() { return mLayoutHeight; } @@ -63,16 +72,14 @@ int Label::getLayoutWidth() { return mLayoutWidth; } -bool Label::create(PrintableText printableText, const uint16_t heightPixel) { +bool Label::create(PrintableText printableText) { setFontFamily(printableText.fontFamily); setFontSize(printableText.fontSize); - return create(printableText.text, heightPixel); + return create(printableText.text); } -bool Label::create(const std::string& labelText, const uint16_t heightPixel) { - mPrinterHeight = heightPixel; - +bool Label::create(const std::string& labelText) { // TODO: we need to create a custom fontconfig here so that Noto Emoji does not load the systems default // fontconfig here. For this, we need to create a PangoFcFontMap and a custom FcConfig // see: https://docs.gtk.org/PangoFc/method.FontMap.set_config.html diff --git a/src/graphics/Label.hpp b/src/graphics/Label.hpp index 1ec934f..3b77c1b 100644 --- a/src/graphics/Label.hpp +++ b/src/graphics/Label.hpp @@ -33,17 +33,19 @@ namespace ptprnt::graphics { class Label : public ILabel { public: - Label(); - ~Label(); + Label(const uint16_t heightPixel); + ~Label() override; Label(const Label&) = delete; Label& operator=(const Label&) = delete; Label(Label&&) = delete; Label& operator=(Label&&) = delete; - bool create(PrintableText printableText, const uint16_t heightPixel) override; - bool create(const std::string& labelText, const uint16_t heightPixel) override; + bool create(PrintableText printableText) override; + bool create(const std::string& labelText) override; void writeToPng(const std::string& file); + [[nodiscard]] int getWidth() override; + [[nodiscard]] int getHeight() override; [[nodiscard]] int getLayoutWidth() override; [[nodiscard]] int getLayoutHeight() override; [[nodiscard]] std::vector getRaw() override; diff --git a/src/graphics/interface/ILabel.hpp b/src/graphics/interface/ILabel.hpp index 9987ef1..46acd05 100644 --- a/src/graphics/interface/ILabel.hpp +++ b/src/graphics/interface/ILabel.hpp @@ -60,19 +60,23 @@ struct PrintableText { VAlignPosition vAlign{VAlignPosition::MIDDLE}; }; +namespace ptprnt::graphics { class ILabel { public: virtual ~ILabel() = default; - virtual bool create(PrintableText printableText, const uint16_t heightPixel) = 0; - virtual bool create(const std::string& labelText, const uint16_t heightPixel) = 0; - virtual std::vector getRaw() = 0; - virtual int getLayoutWidth() = 0; - virtual int getLayoutHeight() = 0; + virtual bool create(PrintableText printableText) = 0; + virtual bool create(const std::string& labelText) = 0; + virtual std::vector getRaw() = 0; + virtual int getWidth() = 0; + virtual int getHeight() = 0; + virtual int getLayoutWidth() = 0; + virtual int getLayoutHeight() = 0; virtual void setText(const std::string& text) = 0; virtual void setFontSize(const double fontSize) = 0; virtual void setFontFamily(const std::string& fontFamily) = 0; virtual void setHAlign(HAlignPosition hpos) = 0; virtual void setVAlign(VAlignPosition vpos) = 0; -}; \ No newline at end of file +}; +} // namespace ptprnt::graphics \ No newline at end of file diff --git a/src/interface/IPrinterDriver.hpp b/src/interface/IPrinterDriver.hpp index 147b9ef..f199bc8 100644 --- a/src/interface/IPrinterDriver.hpp +++ b/src/interface/IPrinterDriver.hpp @@ -19,11 +19,11 @@ #pragma once -#include #include #include #include "graphics/Bitmap.hpp" +#include "graphics/interface/ILabel.hpp" #include "interface/IPrinterTypes.hpp" #include "libusbwrap/interface/IUsbDevice.hpp" @@ -40,6 +40,7 @@ class IPrinterDriver { virtual bool attachUsbDevice(std::shared_ptr usbHndl) = 0; virtual bool detachUsbDevice() = 0; virtual bool printBitmap(const graphics::Bitmap& bitmap) = 0; + virtual bool printLabel(const std::unique_ptr label) = 0; virtual bool print() = 0; }; diff --git a/tests/label_test/label_test.cpp b/tests/label_test/label_test.cpp index a4f1dfd..08e3ce7 100644 --- a/tests/label_test/label_test.cpp +++ b/tests/label_test/label_test.cpp @@ -22,5 +22,5 @@ #include TEST(basic_test, Label_smokeTest_succeeds) { - auto im = ptprnt::graphics::Label(); + auto im = ptprnt::graphics::Label(4711); } -- 2.49.1 From f702ec5473862ecf05eba02fd8cd6ba48585c704 Mon Sep 17 00:00:00 2001 From: Moritz Martinius Date: Sun, 28 Apr 2024 20:44:13 +0200 Subject: [PATCH 15/28] Small Todos for the next session, some clean up --- src/P700Printer.cpp | 2 +- src/PtouchPrint.cpp | 3 ++- src/graphics/Bitmap.cpp | 9 +++------ src/graphics/Label.cpp | 4 +++- src/graphics/Monochrome.cpp | 3 --- 5 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/P700Printer.cpp b/src/P700Printer.cpp index fa43a2b..fec1b7a 100644 --- a/src/P700Printer.cpp +++ b/src/P700Printer.cpp @@ -33,7 +33,7 @@ #include "spdlog/fmt/bin_to_hex.h" // as long as DRYRUN is defined, no data is actually send to the printer, we need to save some tape ;) -//#define DRYRUN +#define DRYRUN namespace ptprnt::printer { diff --git a/src/PtouchPrint.cpp b/src/PtouchPrint.cpp index 9a5e0d4..5ac960e 100644 --- a/src/PtouchPrint.cpp +++ b/src/PtouchPrint.cpp @@ -178,7 +178,8 @@ void PtouchPrint::setupLogger(spdlog::level::level_enum lvl) { consoleSink->set_level(lvl); if (spdlog::level::level_enum::debug == lvl || spdlog::level::level_enum::trace == lvl) { // This will enable file and line number for debug and trace macros - consoleSink->set_pattern("%^%L:%$ %v (%s:%#)"); + // TODO: line number and functions only work with macros + consoleSink->set_pattern("%^%L:%$ %v"); } else { consoleSink->set_pattern("%^%L:%$ %v"); } diff --git a/src/graphics/Bitmap.cpp b/src/graphics/Bitmap.cpp index b31f045..9254d8d 100644 --- a/src/graphics/Bitmap.cpp +++ b/src/graphics/Bitmap.cpp @@ -19,18 +19,15 @@ #include "Bitmap.hpp" -#include #include -#include #include #include #include namespace ptprnt::graphics { template -Bitmap::Bitmap(uint16_t width, uint16_t height) - : mWidth{width}, mHeight{height}, mPixels(width * height) {} +Bitmap::Bitmap(uint16_t width, uint16_t height) : mWidth{width}, mHeight{height}, mPixels(width * height) {} template [[nodiscard]] uint16_t Bitmap::getWidth() const { @@ -45,8 +42,7 @@ template template bool Bitmap::setPixels(const std::vector& pixels) { if (pixels.size() != mPixels.size()) { - spdlog::error("Invalid pixel buffer size (got {} vs. {} bitmap size).", pixels.size(), - mPixels.size()); + spdlog::error("Invalid pixel buffer size (got {} vs. {} bitmap size).", pixels.size(), mPixels.size()); return false; } @@ -71,6 +67,7 @@ template return std::vector(lineStart, lineEnd); } +// TODO: I guess this is borked template [[nodiscard]] std::optional> Bitmap::getCol(uint16_t col) const { if (col >= mWidth) { diff --git a/src/graphics/Label.cpp b/src/graphics/Label.cpp index fefedbb..070c895 100644 --- a/src/graphics/Label.cpp +++ b/src/graphics/Label.cpp @@ -118,7 +118,9 @@ bool Label::create(const std::string& labelText) { mLayoutHeight /= PANGO_SCALE; spdlog::debug("Layout width: {}, height: {}", mLayoutWidth, mLayoutHeight); - mSurface = cairo_image_surface_create(CAIRO_FORMAT_A8, mLayoutWidth, mPrinterHeight); + auto alignedWidth = mLayoutWidth + (8 - (mLayoutWidth % 8)); + + mSurface = cairo_image_surface_create(CAIRO_FORMAT_A8, alignedWidth, mPrinterHeight); cairo_t* cr = cairo_create(mSurface); // Adjust Cairo cursor position to respect the vertical alignment diff --git a/src/graphics/Monochrome.cpp b/src/graphics/Monochrome.cpp index 2c1a892..496d549 100644 --- a/src/graphics/Monochrome.cpp +++ b/src/graphics/Monochrome.cpp @@ -21,8 +21,6 @@ #include -#include -#include #include #include @@ -40,7 +38,6 @@ void Monochrome::invert(bool shouldInvert) { std::vector Monochrome::get() { std::vector outPixels( (static_cast((mPixels.size() / 8)) + (std::floor(mPixels.size() % 8 + 0.9)))); - unsigned int outIndex = 0; for (unsigned int byteNo = 0; byteNo < mPixels.size(); byteNo += 8) { -- 2.49.1 From 77c6b7bc7bca710384ef5ab0a874253ee66909cc Mon Sep 17 00:00:00 2001 From: Moritz Martinius Date: Sun, 28 Apr 2024 20:49:10 +0200 Subject: [PATCH 16/28] Fix for the old dependency code in the CI --- src/graphics/Label.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/graphics/Label.cpp b/src/graphics/Label.cpp index 070c895..6f9f584 100644 --- a/src/graphics/Label.cpp +++ b/src/graphics/Label.cpp @@ -103,7 +103,10 @@ bool Label::create(const std::string& labelText) { case HAlignPosition::JUSTIFY: pango_layout_set_alignment(mPangoLyt, PANGO_ALIGN_LEFT); // not sure if needed pango_layout_set_justify(mPangoLyt, true); +// only enabled in pango 1.50 and greater +#if PANGO_VERSION_MAJOR >= 1 && PANGO_VERSION_MINOR >= 50 pango_layout_set_justify_last_line(mPangoLyt, true); +#endif break; case HAlignPosition::CENTER: [[fallthrough]]; -- 2.49.1 From 3dc5da6fc8087c47f58baad2756ea7b0a4904927 Mon Sep 17 00:00:00 2001 From: Moritz Martinius Date: Thu, 11 Sep 2025 10:02:43 +0200 Subject: [PATCH 17/28] Fix Monochrome class with new data structure, unit tests. There is work to be done still --- .clang-tidy | 1 - .vscode/c_cpp_properties.json | 2 +- .vscode/launch.json | 2 +- .vscode/settings.json | 6 - meson.build | 29 +++- src/P700Printer.cpp | 76 +++++---- src/P700Printer.hpp | 1 + src/PtouchPrint.cpp | 4 +- src/graphics/Bitmap.cpp | 40 ++--- src/graphics/Bitmap.hpp | 16 +- src/graphics/Monochrome.cpp | 199 ++++++++++++++++++++-- src/graphics/Monochrome.hpp | 43 ++++- src/hello.txt | 7 - src/interface/IPrinterDriver.hpp | 2 + tests/bitmap_test/bitmap_test.cpp | 23 +-- tests/monochrome_test/monochrome_test.cpp | 116 +++++++++++-- 16 files changed, 436 insertions(+), 131 deletions(-) delete mode 100644 src/hello.txt diff --git a/.clang-tidy b/.clang-tidy index 63d1f0d..6b7a80a 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -2,7 +2,6 @@ Checks: "clang-diagnostic-*,clang-analyzer-*,cppcoreguidelines-*,modernize-*,-modernize-use-trailing-return-type" WarningsAsErrors: true HeaderFilterRegex: "" -AnalyzeTemporaryDtors: false FormatStyle: google CheckOptions: - key: cert-dcl16-c.NewSuffixes diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 84aeb11..eb6a4b8 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -4,7 +4,7 @@ "name": "Linux", "compilerPath": "/usr/bin/clang", "cStandard": "c11", - "cppStandard": "c++17", + "cppStandard": "c++20", "compileCommands": "${workspaceFolder}/builddir/compile_commands.json", "browse": { "path": ["${workspaceFolder}"] diff --git a/.vscode/launch.json b/.vscode/launch.json index 5ccd544..a958035 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -14,7 +14,7 @@ "cwd": "${fileDirname}", "environment": [], "externalConsole": false, - "MIMode": "gdb", + "MIMode": "lldb", "setupCommands": [ { "description": "Automatische Strukturierung und Einrückung für \"gdb\" aktivieren", diff --git a/.vscode/settings.json b/.vscode/settings.json index eef27fa..bf2c87e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -83,11 +83,5 @@ "charconv": "cpp", "*.ipp": "cpp" }, - "clang-tidy.buildPath": "builddir/", "clangd.onConfigChanged": "restart", - "C_Cpp.default.compileCommands": "/home/moritz/src/ptouch-prnt/builddir/compile_commands.json", - "gcovViewer.buildDirectories": [ - "/home/moritz/Projekte/ptouch-prnt/builddir" - ], - "C_Cpp.default.configurationProvider": "mesonbuild.mesonbuild" } diff --git a/meson.build b/meson.build index eff703f..4c11044 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,22 @@ -project('ptprnt', 'cpp', - version: 'v0.1.0-'+run_command('git', 'rev-parse', '--short', 'HEAD', check: true).stdout().strip(), +project( + 'ptprnt', + 'cpp', + version: 'v0.1.0-' + run_command( + 'git', + 'rev-parse', + '--short', + 'HEAD', + check: true, + ).stdout().strip(), license: 'GPLv3', - default_options : ['c_std=c11', 'cpp_std=c++2a', 'b_sanitize=none', 'b_lto=true', 'b_lto_mode=thin', 'b_thinlto_cache=true'] + default_options: [ + 'c_std=c11', + 'cpp_std=c++20', + 'b_sanitize=none', + 'b_lto=true', + 'b_lto_mode=thin', + 'b_thinlto_cache=true', + ], ) usb_dep = dependency('libusb-1.0') @@ -21,13 +36,13 @@ incdir = include_directories('src') subdir('src') ptprnt_exe = executable( - 'ptprnt', + 'ptprnt', 'src/main.cpp', install: true, - dependencies : [usb_dep, log_dep, fmt_dep, pangocairo_dep, cli11_dep], + dependencies: [usb_dep, log_dep, fmt_dep, pangocairo_dep, cli11_dep], include_directories: incdir, sources: [ptprnt_srcs], - cpp_args : ['-DPROJ_VERSION="'+meson.project_version()+'"'], + cpp_args: ['-DPROJ_VERSION="' + meson.project_version() + '"'], ) @@ -40,4 +55,4 @@ if not gtest_dep.found() error('MESON_SKIP_TEST: gtest not installed.') endif -subdir('tests') \ No newline at end of file +subdir('tests') diff --git a/src/P700Printer.cpp b/src/P700Printer.cpp index fec1b7a..dd80361 100644 --- a/src/P700Printer.cpp +++ b/src/P700Printer.cpp @@ -1,4 +1,4 @@ -/* +/* ptrnt - print labels on linux Copyright (C) 2023 Moritz Martinius @@ -44,7 +44,7 @@ const PrinterInfo P700Printer::mInfo = {.driverName = "P700", .pixelLines = 128}; P700Printer::~P700Printer() { - detachUsbDevice(); + P700Printer::detachUsbDevice(); if (mUsbHndl) { mUsbHndl->close(); } @@ -92,6 +92,7 @@ bool P700Printer::attachUsbDevice(std::shared_ptr usbHnd } if (!usbHndl->detachKernelDriver(0)) { + spdlog::error("Device is already in use or couldn't be detached from kernel: {}", usbHndl->getLastErrorString()); return false; @@ -119,53 +120,66 @@ bool P700Printer::detachUsbDevice() { } bool P700Printer::printBitmap(const graphics::Bitmap& bitmap) { + // Convert bitmap to MonochromeData and delegate to printMonochromeData + auto pixels = bitmap.getPixelsCpy(); + auto mono = graphics::Monochrome(pixels, bitmap.getWidth(), bitmap.getHeight()); + auto monoData = mono.getMonochromeData(); + + return printMonochromeData(monoData); +} + +bool P700Printer::printMonochromeData(const graphics::MonochromeData& data) { #ifdef DRYRUN spdlog::debug("DRYRUN enabled"); - for (unsigned int lineNo = 0; lineNo < bitmap.getHeight(); lineNo++) { - auto line = bitmap.getLine(lineNo); - auto monoLine = graphics::Monochrome(*line); - monoLine.visualize(); - } + data.visualize(); #endif - + send(p700::commands::RASTER_START); std::vector rastercmd(4); rastercmd[0] = 0x47; rastercmd[1] = 0x00; // size +1 rastercmd[2] = 0x00; rastercmd[3] = 0x00; // size -1 - for (unsigned int i = 0; i < bitmap.getWidth(); i++) { - auto bmcol = bitmap.getCol(i); - if (!bmcol) { - spdlog::error("Out of bounds bitmap access"); - break; + + // Process data column by column for the printer + for (uint32_t col = 0; col < data.width; col++) { + std::vector columnData; + + // Extract column data bit by bit + for (uint32_t row = 0; row < data.height; row += 8) { + uint8_t byte = 0; + for (int bit = 0; bit < 8 && (row + bit) < data.height; bit++) { + if (data.getBit(col, row + bit)) { + byte |= (1 << (7 - bit)); + } + } + columnData.push_back(byte); } - auto monocol = graphics::Monochrome(*bmcol); - auto col = monocol.get(); - - std::vector buf(0); + + std::vector buf; buf.insert(buf.begin(), rastercmd.begin(), rastercmd.end()); - buf.insert(std::next(buf.begin(), 4), col.begin(), col.end()); - - buf[1] = col.size() + 1; - buf[3] = col.size() - 1; + buf.insert(std::next(buf.begin(), 4), columnData.begin(), columnData.end()); + buf[1] = columnData.size() + 1; + buf[3] = columnData.size() - 1; + if (!send(buf)) { spdlog::error("Error sending buffer to printer"); break; - }; + } } - + send(p700::commands::EJECT); return true; } bool P700Printer::printLabel(std::unique_ptr label) { - // not quite sure if I should stack allocate Bitmap, but data is held on the heap anyway (std::vector). - auto bm = graphics::Bitmap(label->getWidth(), label->getHeight()); + // Convert label directly to MonochromeData + auto pixels = label->getRaw(); + auto mono = graphics::Monochrome(pixels, label->getWidth(), label->getHeight()); + auto monoData = mono.getMonochromeData(); + spdlog::debug("Label has {}x{}px size", label->getWidth(), label->getHeight()); - bm.setPixels(label->getRaw()); - printBitmap(bm); - return true; + return printMonochromeData(monoData); } bool P700Printer::print() { @@ -182,7 +196,7 @@ bool P700Printer::send(const std::vector& data) { return false; } - int tx = 0; + size_t tx = 0; #ifndef DRYRUN if (!mUsbHndl->bulkTransfer(0x02, data, &tx, 0)) { @@ -194,7 +208,7 @@ bool P700Printer::send(const std::vector& data) { spdlog::trace("USB raw data(len {}): {}", data.size(), spdlog::to_hex(data)); #endif - if (tx != static_cast(data.size())) { + if (tx != data.size()) { spdlog::error("Could not transfer all data via USB bulk transfer. Only sent {} of {} bytes", tx, data.size()); return false; } @@ -208,4 +222,4 @@ bool P700Printer::init() { cmd[101] = 0x40; /* @ */ return send(cmd); } -} // namespace ptprnt::printer \ No newline at end of file +} // namespace ptprnt::printer diff --git a/src/P700Printer.hpp b/src/P700Printer.hpp index 5c96c3b..1309db6 100644 --- a/src/P700Printer.hpp +++ b/src/P700Printer.hpp @@ -71,6 +71,7 @@ class P700Printer : public ::ptprnt::IPrinterDriver { bool attachUsbDevice(std::shared_ptr usbHndl) override; bool detachUsbDevice() override; bool printBitmap(const graphics::Bitmap& bitmap) override; + bool printMonochromeData(const graphics::MonochromeData& data) override; bool printLabel(const std::unique_ptr label) override; bool print() override; diff --git a/src/PtouchPrint.cpp b/src/PtouchPrint.cpp index 5ac960e..4641b98 100644 --- a/src/PtouchPrint.cpp +++ b/src/PtouchPrint.cpp @@ -150,10 +150,10 @@ int PtouchPrint::run() { } label->create(labelText); label->writeToPng("./testlabel.png"); - if (!printer->printLabel(std::move(label))) { + /*if (!printer->printLabel(std::move(label))) { spdlog::error("An error occured while printing"); return -1; - } + }*/ return 0; } diff --git a/src/graphics/Bitmap.cpp b/src/graphics/Bitmap.cpp index 9254d8d..1bbcb13 100644 --- a/src/graphics/Bitmap.cpp +++ b/src/graphics/Bitmap.cpp @@ -21,8 +21,8 @@ #include -#include -#include +#include +#include #include namespace ptprnt::graphics { @@ -56,35 +56,29 @@ template } template -[[nodiscard]] std::optional> Bitmap::getLine(uint16_t line) const { - if (line >= mHeight) { - // out of bound - return std::nullopt; +[[nodiscard]] std::vector Bitmap::getLine(const uint16_t lineNo) const { + if (lineNo >= mHeight) { + throw(std::out_of_range("Line is out of range!")); } - - auto lineStart = mPixels.begin() + (line * mWidth); - auto lineEnd = mPixels.begin() + ((line + 1) * mWidth); + auto lineStart = mPixels.begin() + (lineNo * mWidth); + auto lineEnd = mPixels.begin() + ((lineNo + 1) * mWidth); return std::vector(lineStart, lineEnd); } // TODO: I guess this is borked template -[[nodiscard]] std::optional> Bitmap::getCol(uint16_t col) const { - if (col >= mWidth) { - // out of bound - return std::nullopt; +[[nodiscard]] std::vector Bitmap::getCol(const uint16_t colNo) const { + if (colNo >= mWidth) { + throw(std::out_of_range("Col is out of range!")); } - // first pixel is always beginning of the col - std::vector colPixels(mHeight); - auto it = std::next(mPixels.begin(), col); - - for (auto& colElement : colPixels) { - colElement = *it; - std::advance(it, mWidth); + std::vector col{}; + col.reserve(mHeight); + for (size_t i{0}; i <= mPixels.size(); i++) { + if (i % mWidth == colNo) { + col.push_back(mPixels[i]); + } } - - return colPixels; + return col; } - } // namespace ptprnt::graphics \ No newline at end of file diff --git a/src/graphics/Bitmap.hpp b/src/graphics/Bitmap.hpp index 1a2a84a..11a2b46 100644 --- a/src/graphics/Bitmap.hpp +++ b/src/graphics/Bitmap.hpp @@ -19,19 +19,17 @@ #pragma once -#include - #include #include -#include +#include #include namespace ptprnt::graphics { -typedef uint8_t ALPHA8; // Alpha only, 8 bit per pixel -typedef uint32_t RGBX8; // RGB, least significant byte unused, 8 bit per channel -typedef uint32_t RGBA8; // RGB, least significant byte alpha, 8 bit per channel -typedef uint32_t ARGB8; // RGB, most significant byte alpha, 8 bit per channel +using ALPHA8 = std::uint8_t; // Alpha only, 8 bit per pixel +using RGBX8 = std::uint32_t; // RGB, least significant byte unused, 8 bit per channel +using RGBA8 = std::uint32_t; // RGB, least significant byte alpha, 8 bit per channel +using ARGB8 = std::uint32_t; // RGB, most significant byte alpha, 8 bit per channel template class Bitmap { @@ -48,8 +46,8 @@ class Bitmap { [[nodiscard]] uint16_t getHeight() const; bool setPixels(const std::vector& pixels); [[nodiscard]] std::vector getPixelsCpy() const; - [[nodiscard]] std::optional> getLine(uint16_t line) const; - [[nodiscard]] std::optional> getCol(uint16_t col) const; + [[nodiscard]] std::vector getLine(uint16_t line) const; + [[nodiscard]] std::vector getCol(uint16_t col) const; void visualize() const; private: diff --git a/src/graphics/Monochrome.cpp b/src/graphics/Monochrome.cpp index 496d549..9de0f2c 100644 --- a/src/graphics/Monochrome.cpp +++ b/src/graphics/Monochrome.cpp @@ -25,7 +25,11 @@ #include namespace ptprnt::graphics { -Monochrome::Monochrome(const std::vector& grayscale) : mPixels(std::move(grayscale)) {} +Monochrome::Monochrome(const std::vector& grayscale, uint32_t width, uint32_t height) + : mPixels(grayscale), mWidth(width), mHeight(height) {} + +Monochrome::Monochrome(const std::span grayscale, uint32_t width, uint32_t height) + : mPixels(grayscale.begin(), grayscale.end()), mWidth(width), mHeight(height) {} void Monochrome::setThreshold(uint8_t threshhold) { mThreshhold = threshhold; @@ -36,23 +40,30 @@ void Monochrome::invert(bool shouldInvert) { } std::vector Monochrome::get() { - std::vector outPixels( - (static_cast((mPixels.size() / 8)) + (std::floor(mPixels.size() % 8 + 0.9)))); - unsigned int outIndex = 0; + // Calculate output size for packed format: (width + 7) / 8 bytes per row + uint32_t stride = (mWidth + 7) / 8; + size_t outputSize = stride * mHeight; + std::vector outPixels(outputSize, 0); - for (unsigned int byteNo = 0; byteNo < mPixels.size(); byteNo += 8) { - for (unsigned int bitNo = 0; bitNo <= 7 && (byteNo + bitNo < mPixels.size()); bitNo++) { - if (mPixels[byteNo + bitNo] > mThreshhold) { - outPixels[outIndex] |= (1 << (7 - bitNo)); - } else { - outPixels[outIndex] &= ~(1 << (7 - bitNo)); + // Pack pixels row by row for correct 2D layout + for (uint32_t y = 0; y < mHeight; ++y) { + for (uint32_t x = 0; x < mWidth; ++x) { + size_t pixelIndex = y * mWidth + x; // Row-major index in input + size_t byteIndex = y * stride + x / 8; // Byte index in packed output + size_t bitIndex = 7 - (x % 8); // MSB first + + // Convert grayscale pixel to bit based on threshold + bool pixelOn = mPixels[pixelIndex] > mThreshhold; + if (mShouldInvert) { + pixelOn = !pixelOn; + } + + if (pixelOn) { + outPixels[byteIndex] |= (1 << bitIndex); } } - if (mShouldInvert) { - outPixels[outIndex] = ~outPixels[outIndex]; - } - outIndex++; } + return outPixels; } @@ -71,4 +82,164 @@ void Monochrome::visualize() { std::cout << std::endl; } +MonochromeData Monochrome::getMonochromeData() { + auto processedBytes = get(); + // Calculate stride based on packed monochrome data (1 bit per pixel, 8 pixels per byte) + auto stride = static_cast((mWidth + 7) / 8); + return {std::move(processedBytes), stride, Orientation::LANDSCAPE, mWidth, mHeight}; +} + +// MonochromeData transformation methods implementation +void MonochromeData::transformTo(Orientation targetOrientation) { + if (orientation == targetOrientation) { + return; // No transformation needed + } + + auto rotatedData = createRotatedData(targetOrientation); + bytes = std::move(rotatedData); + + // Update dimensions and stride based on rotation + switch (targetOrientation) { + case Orientation::PORTRAIT: + case Orientation::PORTRAIT_FLIPPED: + // Swap width and height for portrait orientations + std::swap(width, height); + stride = (width + 7) / 8; // Recalculate stride for new width + break; + case Orientation::LANDSCAPE: + case Orientation::LANDSCAPE_FLIPPED: + // Keep original stride calculation + stride = (width + 7) / 8; + break; + } + + orientation = targetOrientation; +} + +bool MonochromeData::getBit(uint32_t x, uint32_t y) const { + if (x >= width || y >= height) { + return false; + } + + uint32_t byteIndex = y * stride + x / 8; + uint32_t bitIndex = 7 - (x % 8); // MSB first + + if (byteIndex >= bytes.size()) { + return false; + } + + return (bytes[byteIndex] >> bitIndex) & 1; +} + +void MonochromeData::setBit(uint32_t x, uint32_t y, bool value) { + if (x >= width || y >= height) { + return; + } + + uint32_t byteIndex = y * stride + x / 8; + uint32_t bitIndex = 7 - (x % 8); // MSB first + + if (byteIndex >= bytes.size()) { + return; + } + + if (value) { + bytes[byteIndex] |= (1 << bitIndex); + } else { + bytes[byteIndex] &= ~(1 << bitIndex); + } +} + +std::vector MonochromeData::createRotatedData(Orientation targetOrientation) const { + uint32_t newWidth, newHeight; + + // Determine new dimensions + switch (targetOrientation) { + case Orientation::PORTRAIT: + case Orientation::PORTRAIT_FLIPPED: + newWidth = height; + newHeight = width; + break; + case Orientation::LANDSCAPE: + case Orientation::LANDSCAPE_FLIPPED: + default: + newWidth = width; + newHeight = height; + break; + } + + uint32_t newStride = (newWidth + 7) / 8; + std::vector newBytes(newStride * newHeight, 0); + + // Create a temporary MonochromeData for the new image + MonochromeData tempData; + tempData.bytes = std::move(newBytes); + tempData.stride = newStride; + tempData.width = newWidth; + tempData.height = newHeight; + tempData.orientation = targetOrientation; + + // Copy pixels with appropriate transformation + for (uint32_t y = 0; y < height; ++y) { + for (uint32_t x = 0; x < width; ++x) { + bool pixel = getBit(x, y); + uint32_t newX, newY; + + switch (targetOrientation) { + case Orientation::LANDSCAPE: + newX = x; + newY = y; + break; + case Orientation::PORTRAIT: // 90 degrees clockwise + newX = height - 1 - y; + newY = x; + break; + case Orientation::LANDSCAPE_FLIPPED: // 180 degrees + newX = width - 1 - x; + newY = height - 1 - y; + break; + case Orientation::PORTRAIT_FLIPPED: // 270 degrees clockwise + newX = y; + newY = width - 1 - x; + break; + } + + tempData.setBit(newX, newY, pixel); + } + } + + return std::move(tempData.bytes); +} + +void MonochromeData::visualize() const { + std::cout << "MonochromeData visualization (" << width << "x" << height << ", orientation: "; + + switch (orientation) { + case Orientation::LANDSCAPE: + std::cout << "LANDSCAPE"; + break; + case Orientation::PORTRAIT: + std::cout << "PORTRAIT"; + break; + case Orientation::LANDSCAPE_FLIPPED: + std::cout << "LANDSCAPE_FLIPPED"; + break; + case Orientation::PORTRAIT_FLIPPED: + std::cout << "PORTRAIT_FLIPPED"; + break; + } + + std::cout << "):" << std::endl; + + // Print the image row by row + for (uint32_t y = 0; y < height; ++y) { + for (uint32_t x = 0; x < width; ++x) { + bool pixel = getBit(x, y); + std::cout << (pixel ? "█" : "."); + } + std::cout << std::endl; + } + std::cout << std::endl; +} + } // namespace ptprnt::graphics \ No newline at end of file diff --git a/src/graphics/Monochrome.hpp b/src/graphics/Monochrome.hpp index 9b7db86..f87f69a 100644 --- a/src/graphics/Monochrome.hpp +++ b/src/graphics/Monochrome.hpp @@ -20,22 +20,61 @@ #pragma once #include +#include +#include #include "graphics/Bitmap.hpp" namespace ptprnt::graphics { + +enum class Orientation { + LANDSCAPE = 0, // 0 degrees + PORTRAIT = 1, // 90 degrees clockwise + LANDSCAPE_FLIPPED = 2, // 180 degrees + PORTRAIT_FLIPPED = 3 // 270 degrees clockwise (90 counter-clockwise) +}; + +struct MonochromeData { + std::vector bytes; + uint32_t stride; + Orientation orientation; + uint32_t width; // Width in pixels + uint32_t height; // Height in pixels + + MonochromeData() : stride(0), orientation(Orientation::LANDSCAPE), width(0), height(0) {} + + MonochromeData(std::vector data, uint32_t stride_bytes, Orientation orient = Orientation::LANDSCAPE, + uint32_t w = 0, uint32_t h = 0) + : bytes(std::move(data)), stride(stride_bytes), orientation(orient), width(w), height(h) {} + + // Transform the image data to the target orientation + void transformTo(Orientation targetOrientation); + + // Visualize the monochrome data on stdout + void visualize() const; + + // Helper methods for orientation transformations + [[nodiscard]] bool getBit(uint32_t x, uint32_t y) const; + void setBit(uint32_t x, uint32_t y, bool value); + [[nodiscard]] std::vector createRotatedData(Orientation targetOrientation) const; +}; + class Monochrome { public: - Monochrome(const std::vector& grayscale); + Monochrome(const std::vector& grayscale, uint32_t width, uint32_t height); + Monochrome(const std::span grayscale, uint32_t width, uint32_t height); ~Monochrome() = default; void setThreshold(uint8_t); void invert(bool shouldInvert); void visualize(); std::vector get(); + MonochromeData getMonochromeData(); private: - const std::vector& mPixels; + std::vector mPixels; + uint32_t mWidth; + uint32_t mHeight; uint8_t mThreshhold = UINT8_MAX / 2; bool mShouldInvert = false; }; diff --git a/src/hello.txt b/src/hello.txt deleted file mode 100644 index a5916b9..0000000 --- a/src/hello.txt +++ /dev/null @@ -1,7 +0,0 @@ -{ - "text" : " Hello", - "font" : "FreeSans 32", - "single-paragraph" : true, - "alignment" : "center", - "height" : 0 -} diff --git a/src/interface/IPrinterDriver.hpp b/src/interface/IPrinterDriver.hpp index f199bc8..7a60462 100644 --- a/src/interface/IPrinterDriver.hpp +++ b/src/interface/IPrinterDriver.hpp @@ -23,6 +23,7 @@ #include #include "graphics/Bitmap.hpp" +#include "graphics/Monochrome.hpp" #include "graphics/interface/ILabel.hpp" #include "interface/IPrinterTypes.hpp" #include "libusbwrap/interface/IUsbDevice.hpp" @@ -40,6 +41,7 @@ class IPrinterDriver { virtual bool attachUsbDevice(std::shared_ptr usbHndl) = 0; virtual bool detachUsbDevice() = 0; virtual bool printBitmap(const graphics::Bitmap& bitmap) = 0; + virtual bool printMonochromeData(const graphics::MonochromeData& data) = 0; virtual bool printLabel(const std::unique_ptr label) = 0; virtual bool print() = 0; }; diff --git a/tests/bitmap_test/bitmap_test.cpp b/tests/bitmap_test/bitmap_test.cpp index 3185b0e..4e71d4f 100644 --- a/tests/bitmap_test/bitmap_test.cpp +++ b/tests/bitmap_test/bitmap_test.cpp @@ -22,7 +22,6 @@ #include #include -#include #include TEST(basic_test, Bitmap_createBitmapWithCertainSize_yieldsSpecifiedSize) { @@ -36,34 +35,28 @@ TEST(basic_test, Bitmap_createBitmapWithCertainSize_yieldsSpecifiedSize) { TEST(basic_test, Bitmap_getBitmapLineOutsideOfImage_yieldsNullopt) { auto bm = ptprnt::graphics::Bitmap(16, 8); // line 8 is out of bounds, count begins with 0 - auto outOfBoundsLine = bm.getLine(8); - ASSERT_EQ(std::nullopt, outOfBoundsLine); + EXPECT_ANY_THROW(auto outOfBoundsLine = bm.getLine(8)); } TEST(basic_test, Bitmap_getBitmapLineInsideOfImage_yieldsValidLineSize) { - auto bm = ptprnt::graphics::Bitmap(16, 8); - auto line = bm.getLine(7); - if (!line) { - FAIL() << "Returned line is invalid"; - } - auto lineSize = line->size(); + auto bm = ptprnt::graphics::Bitmap(16, 8); + auto line = bm.getLine(7); + auto lineSize = line.size(); ASSERT_EQ(16, lineSize); } TEST(basic_test, Bitmap_getBitmapColOutsideOfImage_yieldsNullopt) { auto bm = ptprnt::graphics::Bitmap(16, 8); // col 16 is out of bounds, count begins with 0 - auto outOfBoundsCol = bm.getCol(16); - ASSERT_EQ(std::nullopt, outOfBoundsCol); + + EXPECT_ANY_THROW(auto outOfBoundsCol = bm.getCol(16)); } TEST(basic_test, Bitmap_getBitmapColInsideOfImage_yieldsValidColSize) { auto bm = ptprnt::graphics::Bitmap(16, 8); auto col = bm.getCol(15); - if (!col) { - FAIL() << "Returned Col is invalid"; - } - auto colSize = col->size(); + + auto colSize = col.size(); ASSERT_EQ(8, colSize); } diff --git a/tests/monochrome_test/monochrome_test.cpp b/tests/monochrome_test/monochrome_test.cpp index 43b8342..93a153f 100644 --- a/tests/monochrome_test/monochrome_test.cpp +++ b/tests/monochrome_test/monochrome_test.cpp @@ -22,22 +22,22 @@ #include TEST(basic_test, Monochrome_convertGrayscale_yieldsMonochrome) { - const std::vector pixels({0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00}); + const std::vector pixels( + {0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00}); const std::vector expected({0b10101010, 0b10101010}); - auto mono = ptprnt::graphics::Monochrome(pixels); + auto mono = ptprnt::graphics::Monochrome(pixels, 16, 1); auto out = mono.get(); EXPECT_EQ(out, expected); } TEST(basic_test, Monochrome_convertInvertedGrayscale_yieldsInvertedMonochrome) { - const std::vector pixels({0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00}); + const std::vector pixels( + {0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00}); const std::vector expected({0b01010101, 0b01010101}); - auto mono = ptprnt::graphics::Monochrome(pixels); + auto mono = ptprnt::graphics::Monochrome(pixels, 16, 1); mono.invert(true); auto out = mono.get(); @@ -45,11 +45,11 @@ TEST(basic_test, Monochrome_convertInvertedGrayscale_yieldsInvertedMonochrome) { } TEST(basic_test, Monochrome_convertWithCustomThreshhold_yieldsMonochromeRespectingThreshhold) { - const std::vector pixels({0x0F, 0x11, 0x0F, 0x11, 0x0F, 0x11, 0x0F, 0x11, 0x0F, 0x11, - 0x0F, 0x11, 0x0F, 0x11, 0x0F, 0x11}); + const std::vector pixels( + {0x0F, 0x11, 0x0F, 0x11, 0x0F, 0x11, 0x0F, 0x11, 0x0F, 0x11, 0x0F, 0x11, 0x0F, 0x11, 0x0F, 0x11}); const std::vector expected({0b01010101, 0b01010101}); - auto mono = ptprnt::graphics::Monochrome(pixels); + auto mono = ptprnt::graphics::Monochrome(pixels, 16, 1); mono.setThreshold(16); auto out = mono.get(); @@ -60,12 +60,104 @@ TEST(basic_test, Monochrome_convertNonAlignedPixels_spillsOverIntoNewByte) { // TODO: We need to find to access the vector without the possiblity of out-of-bounds access // Ideas: constexpr? compile time check? GTEST_SKIP() << "Skipping this test, as ASAN will halt as this is an out-of-bounds access"; - const std::vector pixels({0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, - 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF}); + const std::vector pixels( + {0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF}); const std::vector expected({0b10101010, 0b10101010, 0b10000000}); - auto mono = ptprnt::graphics::Monochrome(pixels); + auto mono = ptprnt::graphics::Monochrome(pixels, 17, 1); auto out = mono.get(); EXPECT_EQ(out, expected); +} + +TEST(MonochromeData_test, MonochromeData_getMonochromeData_returnsStructWithCorrectData) { + const std::vector pixels({0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00}); + + auto mono = ptprnt::graphics::Monochrome(pixels, 8, 1); + auto monoData = mono.getMonochromeData(); + + EXPECT_EQ(monoData.bytes.size(), 1); + EXPECT_EQ(monoData.bytes[0], 0b10101010); + EXPECT_EQ(monoData.width, 8); + EXPECT_EQ(monoData.height, 1); + EXPECT_EQ(monoData.stride, 1); + EXPECT_EQ(monoData.orientation, ptprnt::graphics::Orientation::LANDSCAPE); +} + +TEST(MonochromeData_test, MonochromeData2x2_transformToPortrait_rotatesCorrectly) { + // Create a 2x2 image with a specific pattern + // Pixels are laid out row-major: row0_col0, row0_col1, row1_col0, ... + const std::vector pixels({0xFF, 0x00, 0x00, 0xFF}); + + auto mono = ptprnt::graphics::Monochrome(pixels, 2, 2); + auto monoData = mono.getMonochromeData(); + + monoData.transformTo(ptprnt::graphics::Orientation::PORTRAIT); + + // After 90° clockwise rotation: + // Original: █ . -> Rotated: . █ + // . █ █ . + EXPECT_EQ(monoData.width, 2); + EXPECT_EQ(monoData.height, 2); + EXPECT_EQ(monoData.orientation, ptprnt::graphics::Orientation::PORTRAIT); + + // check pixel data ...................................... x,y = value + EXPECT_EQ(monoData.getBit(0, 0), false); // 0,0 = white + EXPECT_EQ(monoData.getBit(1, 0), true); // 0,1 = black + EXPECT_EQ(monoData.getBit(0, 1), true); // 1,0 = black + EXPECT_EQ(monoData.getBit(1, 1), false); // 1,1 = white +} + +TEST(MonochromeData_test, MonochromeData3x2_transformToPortrait_rotatesCorrectly) { + // Create a 2x3 image with a specific pattern + // Pixels are laid out row-major: row0_col0, row0_col1, row0_col2, row1_col0, ... + const std::vector pixels({0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF}); + + auto mono = ptprnt::graphics::Monochrome(pixels, 3, 2); + auto monoData = mono.getMonochromeData(); + + monoData.transformTo(ptprnt::graphics::Orientation::PORTRAIT); + + // After 90° clockwise rotation: + // Original: █ . . -> Rotated: █ █ + // █ . █ . . + // █ . + EXPECT_EQ(monoData.width, 2); + EXPECT_EQ(monoData.height, 3); + EXPECT_EQ(monoData.orientation, ptprnt::graphics::Orientation::PORTRAIT); + + // check pixel data ...................................... x,y = value + EXPECT_EQ(monoData.getBit(0, 0), true); // 1,1 = black + EXPECT_EQ(monoData.getBit(1, 0), true); // 1,2 = black + EXPECT_EQ(monoData.getBit(0, 1), false); // 2,1 = white + EXPECT_EQ(monoData.getBit(1, 1), false); // 2,2 = white + EXPECT_EQ(monoData.getBit(0, 2), true); // 3,1 = black + EXPECT_EQ(monoData.getBit(1, 2), false); // 3,2 = white +} + +TEST(MonochromeData_test, MonochromeData3x2_transformToPortrait_rotatesCorrectlyCounterclockwise) { + // Create a 2x3 image with a specific pattern + // Pixels are laid out row-major: row0_col0, row0_col1, row0_col2, row1_col0, ... + const std::vector pixels({0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF}); + + auto mono = ptprnt::graphics::Monochrome(pixels, 3, 2); + auto monoData = mono.getMonochromeData(); + + monoData.transformTo(ptprnt::graphics::Orientation::PORTRAIT_FLIPPED); + + // After 90° anti-clockwise rotation: + // Original: █ . . -> Rotated: . █ + // █ . █ . . + // █ █ + EXPECT_EQ(monoData.width, 2); + EXPECT_EQ(monoData.height, 3); + EXPECT_EQ(monoData.orientation, ptprnt::graphics::Orientation::PORTRAIT_FLIPPED); + + // check pixel data ...................................... x,y = value + EXPECT_EQ(monoData.getBit(0, 0), false); // 1,1 = white + EXPECT_EQ(monoData.getBit(1, 0), true); // 1,2 = black + EXPECT_EQ(monoData.getBit(0, 1), false); // 2,1 = white + EXPECT_EQ(monoData.getBit(1, 1), false); // 2,2 = white + EXPECT_EQ(monoData.getBit(0, 2), true); // 3,1 = black + EXPECT_EQ(monoData.getBit(1, 2), true); // 3,2 = black } \ No newline at end of file -- 2.49.1 From 0b8ff28a60be064605d114d9467a3a8886047013 Mon Sep 17 00:00:00 2001 From: Moritz Martinius Date: Sat, 11 Oct 2025 12:29:43 +0200 Subject: [PATCH 18/28] Start refactoring printers into own directory --- .vscode/launch.json | 11 +- src/PrinterDriverFactory.cpp | 2 +- src/PtouchPrint.cpp | 9 +- src/graphics/Label.cpp | 137 +++++++++++++--------- src/graphics/Label.hpp | 38 ++++-- src/graphics/Monochrome.cpp | 120 ++++++++++--------- src/graphics/Monochrome.hpp | 67 +++++++---- src/meson.build | 4 +- src/{ => printers}/P700Printer.cpp | 36 +++--- src/{ => printers}/P700Printer.hpp | 8 +- tests/monochrome_test/monochrome_test.cpp | 16 +-- 11 files changed, 271 insertions(+), 177 deletions(-) rename src/{ => printers}/P700Printer.cpp (92%) rename src/{ => printers}/P700Printer.hpp (94%) diff --git a/.vscode/launch.json b/.vscode/launch.json index a958035..50664cf 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,12 +9,15 @@ "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/builddir/ptprnt", - "args": ["-t Hello"], + "args": [ + "-t i" + ], "stopAtEntry": false, - "cwd": "${fileDirname}", + "cwd": "${workspaceFolder}", "environment": [], "externalConsole": false, - "MIMode": "lldb", + "MIMode": "gdb", + "miDebuggerPath": "/usr/bin/gdb", "setupCommands": [ { "description": "Automatische Strukturierung und Einrückung für \"gdb\" aktivieren", @@ -24,4 +27,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/src/PrinterDriverFactory.cpp b/src/PrinterDriverFactory.cpp index ce34d68..908de40 100644 --- a/src/PrinterDriverFactory.cpp +++ b/src/PrinterDriverFactory.cpp @@ -4,7 +4,7 @@ #include -#include "P700Printer.hpp" +#include "printers/P700Printer.hpp" #include "libusbwrap/LibUsbTypes.hpp" namespace ptprnt { diff --git a/src/PtouchPrint.cpp b/src/PtouchPrint.cpp index 4641b98..034c893 100644 --- a/src/PtouchPrint.cpp +++ b/src/PtouchPrint.cpp @@ -88,7 +88,10 @@ int PtouchPrint::run() { spdlog::warn("Found more than one device of the same printer on bus. Currently not supported"); return -1; } - printer->attachUsbDevice(std::move(devices[0])); + if (!printer->attachUsbDevice(std::move(devices[0]))) { + spdlog::error("Failed to attach USB device to printer"); + return -1; + } auto status = printer->getPrinterStatus(); spdlog::info("Detected tape width is {}mm", status.tapeWidthMm); @@ -150,10 +153,10 @@ int PtouchPrint::run() { } label->create(labelText); label->writeToPng("./testlabel.png"); - /*if (!printer->printLabel(std::move(label))) { + if (!printer->printLabel(std::move(label))) { spdlog::error("An error occured while printing"); return -1; - }*/ + } return 0; } diff --git a/src/graphics/Label.cpp b/src/graphics/Label.cpp index 6f9f584..72a853e 100644 --- a/src/graphics/Label.cpp +++ b/src/graphics/Label.cpp @@ -37,18 +37,24 @@ namespace ptprnt::graphics { Label::Label(const uint16_t heightPixel) - : mCairoCtx(cairo_create(mSurface)), - mPangoCtx(pango_cairo_create_context(mCairoCtx)), - mPangoLyt(pango_layout_new(mPangoCtx)), - mFontMap(pango_cairo_font_map_new()), - mPrinterHeight(heightPixel) {} + : mPrinterHeight(heightPixel) { + // Initialize resources in correct order with RAII + mFontMap.reset(pango_cairo_font_map_new()); +} std::vector Label::getRaw() { assert(mSurface != nullptr); - size_t len = mPrinterHeight * mLayoutWidth; + auto* surface = mSurface.get(); + size_t len = cairo_image_surface_get_height(surface) * cairo_image_surface_get_stride(surface); - cairo_surface_flush(mSurface); - auto data = cairo_image_surface_get_data(mSurface); + 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)); + 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}; } @@ -72,6 +78,35 @@ int Label::getLayoutWidth() { return mLayoutWidth; } +void Label::configureLayout(PangoLayout* layout, const std::string& text, PangoFontDescription* fontDesc) { + pango_layout_set_font_description(layout, fontDesc); + pango_layout_set_text(layout, text.c_str(), static_cast(text.length())); + pango_layout_set_height(layout, getNumLines(text) * -1); +} + +void Label::applyHorizontalAlignment(PangoLayout* layout) { + switch (mHAlign) { + case HAlignPosition::LEFT: + pango_layout_set_alignment(layout, PANGO_ALIGN_LEFT); + break; + case HAlignPosition::RIGHT: + pango_layout_set_alignment(layout, PANGO_ALIGN_RIGHT); + break; + case HAlignPosition::JUSTIFY: + pango_layout_set_alignment(layout, PANGO_ALIGN_LEFT); + pango_layout_set_justify(layout, true); +#if PANGO_VERSION_MAJOR >= 1 && PANGO_VERSION_MINOR >= 50 + pango_layout_set_justify_last_line(layout, true); +#endif + break; + case HAlignPosition::CENTER: + [[fallthrough]]; + default: + pango_layout_set_alignment(layout, PANGO_ALIGN_CENTER); + break; + } +} + bool Label::create(PrintableText printableText) { setFontFamily(printableText.fontFamily); setFontSize(printableText.fontSize); @@ -80,79 +115,78 @@ bool Label::create(PrintableText printableText) { } bool Label::create(const std::string& labelText) { - // TODO: we need to create a custom fontconfig here so that Noto Emoji does not load the systems default - // fontconfig here. For this, we need to create a PangoFcFontMap and a custom FcConfig + // TODO: we need to create a custom font config here so that Noto Emoji does not load the systems default + // font config here. For this, we need to create a PangoFcFontMap and a custom FcConfig // see: https://docs.gtk.org/PangoFc/method.FontMap.set_config.html // see: https://gist.github.com/CallumDev/7c66b3f9cf7a876ef75f + + // Create a temporary surface for layout size calculations + auto* tempSurface = cairo_image_surface_create(CAIRO_FORMAT_A8, 1, 1); + auto* tempCr = cairo_create(tempSurface); + auto* tempPangoCtx = pango_cairo_create_context(tempCr); + auto* tempPangoLyt = pango_layout_new(tempPangoCtx); + PangoFontDescription* regularFont = pango_font_description_new(); pango_font_description_set_size(regularFont, static_cast(mFontSize * PANGO_SCALE)); pango_font_description_set_family(regularFont, mFontFamily.c_str()); - //pango_layout_set_single_paragraph_mode(mPangoLyt, true); // this will force a single line in the label width width -1 - pango_layout_set_height(mPangoLyt, getNumLines(labelText) * -1); - pango_layout_set_font_description(mPangoLyt, regularFont); - pango_layout_set_text(mPangoLyt, labelText.c_str(), static_cast(labelText.length())); - // Set horizontal alignment - switch (mHAlign) { - case HAlignPosition::LEFT: - pango_layout_set_alignment(mPangoLyt, PANGO_ALIGN_LEFT); - break; - case HAlignPosition::RIGHT: - pango_layout_set_alignment(mPangoLyt, PANGO_ALIGN_RIGHT); - break; - case HAlignPosition::JUSTIFY: - pango_layout_set_alignment(mPangoLyt, PANGO_ALIGN_LEFT); // not sure if needed - pango_layout_set_justify(mPangoLyt, true); -// only enabled in pango 1.50 and greater -#if PANGO_VERSION_MAJOR >= 1 && PANGO_VERSION_MINOR >= 50 - pango_layout_set_justify_last_line(mPangoLyt, true); -#endif - break; - case HAlignPosition::CENTER: - [[fallthrough]]; - default: - pango_layout_set_alignment(mPangoLyt, PANGO_ALIGN_CENTER); - break; - } + // Configure temporary layout for size calculation + configureLayout(tempPangoLyt, labelText, regularFont); + applyHorizontalAlignment(tempPangoLyt); - // calculate label size for Cairo surface creation - pango_layout_get_size(mPangoLyt, &mLayoutWidth, &mLayoutHeight); + // Calculate label size from temporary layout + pango_layout_get_size(tempPangoLyt, &mLayoutWidth, &mLayoutHeight); mLayoutWidth /= PANGO_SCALE; mLayoutHeight /= PANGO_SCALE; spdlog::debug("Layout width: {}, height: {}", mLayoutWidth, mLayoutHeight); - auto alignedWidth = mLayoutWidth + (8 - (mLayoutWidth % 8)); + //auto alignedWidth = mLayoutWidth + (8 - (mLayoutWidth % 8)); + //spdlog::debug("Aligned Layout width: {}, height: {}", alignedWidth, mLayoutHeight); - mSurface = cairo_image_surface_create(CAIRO_FORMAT_A8, alignedWidth, mPrinterHeight); - cairo_t* cr = cairo_create(mSurface); + // Clean up temporary resources + g_object_unref(tempPangoLyt); + g_object_unref(tempPangoCtx); + cairo_destroy(tempCr); + cairo_surface_destroy(tempSurface); + + // Now create the final surface and Pango context for actual rendering + mSurface.reset(cairo_image_surface_create(CAIRO_FORMAT_A8, mLayoutWidth, mPrinterHeight)); + cairo_t* cr = cairo_create(mSurface.get()); + mCairoCtx.reset(cr); + mPangoCtx.reset(pango_cairo_create_context(cr)); + mPangoLyt.reset(pango_layout_new(mPangoCtx.get())); + + // Configure final layout with same settings + configureLayout(mPangoLyt.get(), labelText, regularFont); + applyHorizontalAlignment(mPangoLyt.get()); // Adjust Cairo cursor position to respect the vertical alignment switch (mVAlign) { case VAlignPosition::TOP: break; case VAlignPosition::BOTTOM: - cairo_move_to(cr, 0.0, mPrinterHeight - mLayoutHeight); + cairo_move_to(mCairoCtx.get(), 0.0, mPrinterHeight - mLayoutHeight); break; case VAlignPosition::MIDDLE: - cairo_move_to(cr, 0.0, (mPrinterHeight - mLayoutHeight) / 2); + cairo_move_to(mCairoCtx.get(), 0.0, (mPrinterHeight - mLayoutHeight) / 2); [[fallthrough]]; default: break; } // Finally show the layout on the Cairo surface - pango_cairo_show_layout(cr, mPangoLyt); + pango_cairo_show_layout(mCairoCtx.get(), mPangoLyt.get()); - cairo_set_source_rgb(cr, 0.0, 0.0, 0.0); - cairo_surface_flush(mSurface); - cairo_destroy(cr); + cairo_set_source_rgb(mCairoCtx.get(), 0.0, 0.0, 0.0); + cairo_surface_flush(mSurface.get()); + // mCairoCtx smart pointer will handle cleanup return true; } void Label::writeToPng(const std::string& file) { if (mSurface) { - cairo_surface_flush(mSurface); - cairo_surface_write_to_png(mSurface, file.c_str()); + cairo_surface_flush(mSurface.get()); + cairo_surface_write_to_png(mSurface.get(), file.c_str()); } } @@ -178,9 +212,6 @@ void Label::setText(const std::string& text) { Label::~Label() { spdlog::debug("Image dtor..."); - g_object_unref(mPangoCtx); - g_object_unref(mPangoLyt); - g_object_unref(mFontMap); - cairo_surface_destroy(mSurface); + // RAII smart pointers handle cleanup automatically } } // namespace ptprnt::graphics \ No newline at end of file diff --git a/src/graphics/Label.hpp b/src/graphics/Label.hpp index 3b77c1b..2dc6bc1 100644 --- a/src/graphics/Label.hpp +++ b/src/graphics/Label.hpp @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -31,6 +32,28 @@ namespace ptprnt::graphics { +// Custom deleters for Cairo/Pango resources +struct CairoSurfaceDeleter { + void operator()(cairo_surface_t* surface) const { + if (surface) + cairo_surface_destroy(surface); + } +}; + +struct CairoDeleter { + void operator()(cairo_t* cr) const { + if (cr) + cairo_destroy(cr); + } +}; + +struct GObjectDeleter { + void operator()(gpointer obj) const { + if (obj) + g_object_unref(obj); + } +}; + class Label : public ILabel { public: Label(const uint16_t heightPixel); @@ -60,13 +83,14 @@ class Label : public ILabel { // methods [[nodiscard]] uint8_t getNumLines(std::string_view str); [[nodiscard]] PangoFontMap* createCustomFontMap(); - // members - // TODO: convert raw pointers here into std::unique_ptr with custom deleters, calling g_object_unref() - cairo_surface_t* mSurface{nullptr}; - cairo_t* mCairoCtx{nullptr}; - PangoContext* mPangoCtx{nullptr}; - PangoLayout* mPangoLyt{nullptr}; - PangoFontMap* mFontMap{nullptr}; + void configureLayout(PangoLayout* layout, const std::string& text, PangoFontDescription* fontDesc); + void applyHorizontalAlignment(PangoLayout* layout); + + std::unique_ptr mSurface{nullptr}; + std::unique_ptr mCairoCtx{nullptr}; + std::unique_ptr mPangoCtx{nullptr}; + std::unique_ptr mPangoLyt{nullptr}; + std::unique_ptr mFontMap{nullptr}; double mFontSize{DEFAULT_FONT_SIZE}; std::string mFontFamily{DEFAULT_FONT_FAMILY}; HAlignPosition mHAlign = HAlignPosition::LEFT; diff --git a/src/graphics/Monochrome.cpp b/src/graphics/Monochrome.cpp index 9de0f2c..333e114 100644 --- a/src/graphics/Monochrome.cpp +++ b/src/graphics/Monochrome.cpp @@ -25,71 +25,85 @@ #include namespace ptprnt::graphics { -Monochrome::Monochrome(const std::vector& grayscale, uint32_t width, uint32_t height) - : mPixels(grayscale), mWidth(width), mHeight(height) {} +// Constructor from grayscale data +MonochromeData::MonochromeData(const std::vector& grayscale, uint32_t width, uint32_t height, + Orientation orient) + : stride(0), + orientation(orient), + width(width), + height(height), + mPixels(grayscale), + mIsProcessed(false) {} -Monochrome::Monochrome(const std::span grayscale, uint32_t width, uint32_t height) - : mPixels(grayscale.begin(), grayscale.end()), mWidth(width), mHeight(height) {} +MonochromeData::MonochromeData(const std::span grayscale, uint32_t width, uint32_t height, + Orientation orient) + : stride(0), + orientation(orient), + width(width), + height(height), + mPixels(grayscale.begin(), grayscale.end()), + mIsProcessed(false) {} -void Monochrome::setThreshold(uint8_t threshhold) { - mThreshhold = threshhold; +void MonochromeData::setThreshold(uint8_t threshold) { + mThreshold = threshold; + mIsProcessed = false; // Mark as needing reprocessing } -void Monochrome::invert(bool shouldInvert) { +void MonochromeData::invert(bool shouldInvert) { mShouldInvert = shouldInvert; + mIsProcessed = false; // Mark as needing reprocessing } -std::vector Monochrome::get() { - // Calculate output size for packed format: (width + 7) / 8 bytes per row - uint32_t stride = (mWidth + 7) / 8; - size_t outputSize = stride * mHeight; - std::vector outPixels(outputSize, 0); +MonochromeData MonochromeData::get() { + if (!mIsProcessed) { + processGrayscaleToMonochrome(); + mIsProcessed = true; + } - // Pack pixels row by row for correct 2D layout - for (uint32_t y = 0; y < mHeight; ++y) { - for (uint32_t x = 0; x < mWidth; ++x) { - size_t pixelIndex = y * mWidth + x; // Row-major index in input - size_t byteIndex = y * stride + x / 8; // Byte index in packed output - size_t bitIndex = 7 - (x % 8); // MSB first + // Return a copy of the processed data + MonochromeData result; + result.bytes = bytes; + result.stride = stride; + result.orientation = orientation; + result.width = width; + result.height = height; + result.mIsProcessed = true; + return result; +} - // Convert grayscale pixel to bit based on threshold - bool pixelOn = mPixels[pixelIndex] > mThreshhold; - if (mShouldInvert) { - pixelOn = !pixelOn; - } +void MonochromeData::processGrayscaleToMonochrome() { + // Calculate stride based on packed monochrome data (1 bit per pixel, 8 pixels per byte) + stride = static_cast((width + 7) / 8); - if (pixelOn) { - outPixels[byteIndex] |= (1 << bitIndex); + // Create the monochrome byte array + bytes.clear(); + bytes.resize(stride * height, 0); + + // Convert grayscale to monochrome + for (uint32_t y = 0; y < height; ++y) { + for (uint32_t x = 0; x < width; ++x) { + uint32_t pixelIndex = y * width + x; + if (pixelIndex < mPixels.size()) { + uint8_t pixelValue = mPixels[pixelIndex]; + + // Apply threshold + bool isSet = pixelValue >= mThreshold; + + // Apply inversion if needed + if (mShouldInvert) { + isSet = !isSet; + } + + // Set the bit in the monochrome data + if (isSet) { + setBit(x, y, true); + } } } } - - return outPixels; } -void Monochrome::visualize() { - auto mono = get(); - for (unsigned char pix : mono) { - std::cout << ((pix & (1 << 7)) == 0 ? "." : "x"); - std::cout << ((pix & (1 << 6)) == 0 ? "." : "x"); - std::cout << ((pix & (1 << 5)) == 0 ? "." : "x"); - std::cout << ((pix & (1 << 4)) == 0 ? "." : "x"); - std::cout << ((pix & (1 << 3)) == 0 ? "." : "x"); - std::cout << ((pix & (1 << 2)) == 0 ? "." : "x"); - std::cout << ((pix & (1 << 1)) == 0 ? "." : "x"); - std::cout << ((pix & (1 << 0)) == 0 ? "." : "x"); - } - std::cout << std::endl; -} - -MonochromeData Monochrome::getMonochromeData() { - auto processedBytes = get(); - // Calculate stride based on packed monochrome data (1 bit per pixel, 8 pixels per byte) - auto stride = static_cast((mWidth + 7) / 8); - return {std::move(processedBytes), stride, Orientation::LANDSCAPE, mWidth, mHeight}; -} - -// MonochromeData transformation methods implementation +// Transformation methods implementation void MonochromeData::transformTo(Orientation targetOrientation) { if (orientation == targetOrientation) { return; // No transformation needed @@ -151,7 +165,7 @@ void MonochromeData::setBit(uint32_t x, uint32_t y, bool value) { } std::vector MonochromeData::createRotatedData(Orientation targetOrientation) const { - uint32_t newWidth, newHeight; + uint32_t newWidth = 0, newHeight = 0; // Determine new dimensions switch (targetOrientation) { @@ -182,8 +196,8 @@ std::vector MonochromeData::createRotatedData(Orientation targetOrienta // Copy pixels with appropriate transformation for (uint32_t y = 0; y < height; ++y) { for (uint32_t x = 0; x < width; ++x) { - bool pixel = getBit(x, y); - uint32_t newX, newY; + bool pixel = getBit(x, y); + uint32_t newX = 0, newY = 0; switch (targetOrientation) { case Orientation::LANDSCAPE: diff --git a/src/graphics/Monochrome.hpp b/src/graphics/Monochrome.hpp index f87f69a..958deb7 100644 --- a/src/graphics/Monochrome.hpp +++ b/src/graphics/Monochrome.hpp @@ -34,19 +34,38 @@ enum class Orientation { PORTRAIT_FLIPPED = 3 // 270 degrees clockwise (90 counter-clockwise) }; -struct MonochromeData { - std::vector bytes; - uint32_t stride; - Orientation orientation; - uint32_t width; // Width in pixels - uint32_t height; // Height in pixels - +class MonochromeData { + public: + // Constructors MonochromeData() : stride(0), orientation(Orientation::LANDSCAPE), width(0), height(0) {} MonochromeData(std::vector data, uint32_t stride_bytes, Orientation orient = Orientation::LANDSCAPE, uint32_t w = 0, uint32_t h = 0) : bytes(std::move(data)), stride(stride_bytes), orientation(orient), width(w), height(h) {} + // Constructor from grayscale data (replaces old Monochrome class) + MonochromeData(const std::vector& grayscale, uint32_t width, uint32_t height, + Orientation orient = Orientation::LANDSCAPE); + MonochromeData(const std::span grayscale, uint32_t width, uint32_t height, + Orientation orient = Orientation::LANDSCAPE); + + ~MonochromeData() = default; + + // Copy constructor and assignment + MonochromeData(const MonochromeData&) = default; + MonochromeData& operator=(const MonochromeData&) = default; + + // Move constructor and assignment + MonochromeData(MonochromeData&&) = default; + MonochromeData& operator=(MonochromeData&&) = default; + + // Configuration methods + void setThreshold(uint8_t threshold); + void invert(bool shouldInvert); + + // Get processed monochrome data + MonochromeData get(); + // Transform the image data to the target orientation void transformTo(Orientation targetOrientation); @@ -57,25 +76,25 @@ struct MonochromeData { [[nodiscard]] bool getBit(uint32_t x, uint32_t y) const; void setBit(uint32_t x, uint32_t y, bool value); [[nodiscard]] std::vector createRotatedData(Orientation targetOrientation) const; -}; -class Monochrome { - public: - Monochrome(const std::vector& grayscale, uint32_t width, uint32_t height); - Monochrome(const std::span grayscale, uint32_t width, uint32_t height); - ~Monochrome() = default; - - void setThreshold(uint8_t); - void invert(bool shouldInvert); - void visualize(); - std::vector get(); - MonochromeData getMonochromeData(); + // Public member access for backward compatibility + std::vector bytes; + uint32_t stride; + Orientation orientation; + uint32_t width; // Width in pixels + uint32_t height; // Height in pixels private: - std::vector mPixels; - uint32_t mWidth; - uint32_t mHeight; - uint8_t mThreshhold = UINT8_MAX / 2; - bool mShouldInvert = false; + // Processing parameters (for old Monochrome class compatibility) + std::vector mPixels; // Original grayscale pixels + uint8_t mThreshold = UINT8_MAX / 2; + bool mShouldInvert = false; + bool mIsProcessed = false; // Flag to indicate if conversion has been done + + // Helper method to convert grayscale to monochrome + void processGrayscaleToMonochrome(); }; + +// For backward compatibility, create a type alias +using Monochrome = MonochromeData; } // namespace ptprnt::graphics \ No newline at end of file diff --git a/src/meson.build b/src/meson.build index ee66606..2d4fa05 100644 --- a/src/meson.build +++ b/src/meson.build @@ -6,7 +6,7 @@ ptprnt_hpps = files ( 'libusbwrap/UsbDevice.hpp', 'interface/IPrinterDriver.hpp', 'interface/IPrinterTypes.hpp', - 'P700Printer.hpp', + 'printers/P700Printer.hpp', 'PtouchPrint.hpp', 'PrinterDriverFactory.hpp', 'graphics/Bitmap.hpp', @@ -17,7 +17,7 @@ ptprnt_hpps = files ( ptprnt_srcs = files ( 'PtouchPrint.cpp', 'PrinterDriverFactory.cpp', - 'P700Printer.cpp', + 'printers/P700Printer.cpp', 'graphics/Label.cpp', 'graphics/Bitmap.cpp', 'graphics/Monochrome.cpp', diff --git a/src/P700Printer.cpp b/src/printers/P700Printer.cpp similarity index 92% rename from src/P700Printer.cpp rename to src/printers/P700Printer.cpp index dd80361..3c900ad 100644 --- a/src/P700Printer.cpp +++ b/src/printers/P700Printer.cpp @@ -27,9 +27,9 @@ #include #include -#include "graphics/Bitmap.hpp" -#include "graphics/Monochrome.hpp" -#include "libusbwrap/LibUsbTypes.hpp" +#include "../graphics/Bitmap.hpp" +#include "../graphics/Monochrome.hpp" +#include "../libusbwrap/LibUsbTypes.hpp" #include "spdlog/fmt/bin_to_hex.h" // as long as DRYRUN is defined, no data is actually send to the printer, we need to save some tape ;) @@ -121,30 +121,29 @@ bool P700Printer::detachUsbDevice() { bool P700Printer::printBitmap(const graphics::Bitmap& bitmap) { // Convert bitmap to MonochromeData and delegate to printMonochromeData - auto pixels = bitmap.getPixelsCpy(); - auto mono = graphics::Monochrome(pixels, bitmap.getWidth(), bitmap.getHeight()); - auto monoData = mono.getMonochromeData(); - + auto pixels = bitmap.getPixelsCpy(); + auto mono = graphics::Monochrome(pixels, bitmap.getWidth(), bitmap.getHeight()); + auto monoData = mono.get(); + return printMonochromeData(monoData); } bool P700Printer::printMonochromeData(const graphics::MonochromeData& data) { #ifdef DRYRUN - spdlog::debug("DRYRUN enabled"); - data.visualize(); + spdlog::debug("DRYRUN enabled, printing nothing"); #endif - + send(p700::commands::RASTER_START); std::vector rastercmd(4); rastercmd[0] = 0x47; rastercmd[1] = 0x00; // size +1 rastercmd[2] = 0x00; rastercmd[3] = 0x00; // size -1 - + // Process data column by column for the printer for (uint32_t col = 0; col < data.width; col++) { std::vector columnData; - + // Extract column data bit by bit for (uint32_t row = 0; row < data.height; row += 8) { uint8_t byte = 0; @@ -155,19 +154,19 @@ bool P700Printer::printMonochromeData(const graphics::MonochromeData& data) { } columnData.push_back(byte); } - + std::vector buf; buf.insert(buf.begin(), rastercmd.begin(), rastercmd.end()); buf.insert(std::next(buf.begin(), 4), columnData.begin(), columnData.end()); buf[1] = columnData.size() + 1; buf[3] = columnData.size() - 1; - + if (!send(buf)) { spdlog::error("Error sending buffer to printer"); break; } } - + send(p700::commands::EJECT); return true; } @@ -175,9 +174,10 @@ bool P700Printer::printMonochromeData(const graphics::MonochromeData& data) { bool P700Printer::printLabel(std::unique_ptr label) { // Convert label directly to MonochromeData auto pixels = label->getRaw(); - auto mono = graphics::Monochrome(pixels, label->getWidth(), label->getHeight()); - auto monoData = mono.getMonochromeData(); - + auto mono = graphics::Monochrome(pixels, label->getWidth(), label->getHeight(), graphics::Orientation::PORTRAIT); + auto monoData = mono.get(); + monoData.visualize(); + spdlog::debug("Label has {}x{}px size", label->getWidth(), label->getHeight()); return printMonochromeData(monoData); } diff --git a/src/P700Printer.hpp b/src/printers/P700Printer.hpp similarity index 94% rename from src/P700Printer.hpp rename to src/printers/P700Printer.hpp index 1309db6..5ebb6b4 100644 --- a/src/P700Printer.hpp +++ b/src/printers/P700Printer.hpp @@ -23,10 +23,10 @@ #include #include -#include "interface/IPrinterDriver.hpp" -#include "interface/IPrinterTypes.hpp" -#include "libusbwrap/LibUsbTypes.hpp" -#include "libusbwrap/interface/IUsbDevice.hpp" +#include "../interface/IPrinterDriver.hpp" +#include "../interface/IPrinterTypes.hpp" +#include "../libusbwrap/LibUsbTypes.hpp" +#include "../libusbwrap/interface/IUsbDevice.hpp" #pragma once diff --git a/tests/monochrome_test/monochrome_test.cpp b/tests/monochrome_test/monochrome_test.cpp index 93a153f..2b5decf 100644 --- a/tests/monochrome_test/monochrome_test.cpp +++ b/tests/monochrome_test/monochrome_test.cpp @@ -29,7 +29,7 @@ TEST(basic_test, Monochrome_convertGrayscale_yieldsMonochrome) { auto mono = ptprnt::graphics::Monochrome(pixels, 16, 1); auto out = mono.get(); - EXPECT_EQ(out, expected); + EXPECT_EQ(out.bytes, expected); } TEST(basic_test, Monochrome_convertInvertedGrayscale_yieldsInvertedMonochrome) { @@ -41,7 +41,7 @@ TEST(basic_test, Monochrome_convertInvertedGrayscale_yieldsInvertedMonochrome) { mono.invert(true); auto out = mono.get(); - EXPECT_EQ(out, expected); + EXPECT_EQ(out.bytes, expected); } TEST(basic_test, Monochrome_convertWithCustomThreshhold_yieldsMonochromeRespectingThreshhold) { @@ -53,7 +53,7 @@ TEST(basic_test, Monochrome_convertWithCustomThreshhold_yieldsMonochromeRespecti mono.setThreshold(16); auto out = mono.get(); - EXPECT_EQ(out, expected); + EXPECT_EQ(out.bytes, expected); } TEST(basic_test, Monochrome_convertNonAlignedPixels_spillsOverIntoNewByte) { @@ -67,14 +67,14 @@ TEST(basic_test, Monochrome_convertNonAlignedPixels_spillsOverIntoNewByte) { auto mono = ptprnt::graphics::Monochrome(pixels, 17, 1); auto out = mono.get(); - EXPECT_EQ(out, expected); + EXPECT_EQ(out.bytes, expected); } TEST(MonochromeData_test, MonochromeData_getMonochromeData_returnsStructWithCorrectData) { const std::vector pixels({0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00}); auto mono = ptprnt::graphics::Monochrome(pixels, 8, 1); - auto monoData = mono.getMonochromeData(); + auto monoData = mono.get(); EXPECT_EQ(monoData.bytes.size(), 1); EXPECT_EQ(monoData.bytes[0], 0b10101010); @@ -90,7 +90,7 @@ TEST(MonochromeData_test, MonochromeData2x2_transformToPortrait_rotatesCorrectly const std::vector pixels({0xFF, 0x00, 0x00, 0xFF}); auto mono = ptprnt::graphics::Monochrome(pixels, 2, 2); - auto monoData = mono.getMonochromeData(); + auto monoData = mono.get(); monoData.transformTo(ptprnt::graphics::Orientation::PORTRAIT); @@ -114,7 +114,7 @@ TEST(MonochromeData_test, MonochromeData3x2_transformToPortrait_rotatesCorrectly const std::vector pixels({0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF}); auto mono = ptprnt::graphics::Monochrome(pixels, 3, 2); - auto monoData = mono.getMonochromeData(); + auto monoData = mono.get(); monoData.transformTo(ptprnt::graphics::Orientation::PORTRAIT); @@ -141,7 +141,7 @@ TEST(MonochromeData_test, MonochromeData3x2_transformToPortrait_rotatesCorrectly const std::vector pixels({0xFF, 0x00, 0x00, 0xFF, 0x00, 0xFF}); auto mono = ptprnt::graphics::Monochrome(pixels, 3, 2); - auto monoData = mono.getMonochromeData(); + auto monoData = mono.get(); monoData.transformTo(ptprnt::graphics::Orientation::PORTRAIT_FLIPPED); -- 2.49.1 From 6e3a5bd12f48614ec034d25edcc7c3903e96d7d8 Mon Sep 17 00:00:00 2001 From: Moritz Martinius Date: Sat, 11 Oct 2025 13:00:26 +0200 Subject: [PATCH 19/28] Add fake printer for testing --- src/PrinterDriverFactory.cpp | 30 ++++++ src/PrinterDriverFactory.hpp | 26 +++++ src/PtouchPrint.cpp | 104 +++++++++++++++---- src/PtouchPrint.hpp | 4 +- src/meson.build | 2 + src/printers/FakePrinter.cpp | 188 +++++++++++++++++++++++++++++++++++ src/printers/FakePrinter.hpp | 94 ++++++++++++++++++ 7 files changed, 429 insertions(+), 19 deletions(-) create mode 100644 src/printers/FakePrinter.cpp create mode 100644 src/printers/FakePrinter.hpp diff --git a/src/PrinterDriverFactory.cpp b/src/PrinterDriverFactory.cpp index 908de40..0b197b9 100644 --- a/src/PrinterDriverFactory.cpp +++ b/src/PrinterDriverFactory.cpp @@ -5,6 +5,7 @@ #include #include "printers/P700Printer.hpp" +#include "printers/FakePrinter.hpp" #include "libusbwrap/LibUsbTypes.hpp" namespace ptprnt { @@ -20,4 +21,33 @@ std::shared_ptr PrinterDriverFactory::create(libusbwrap::usbId i return nullptr; } +std::shared_ptr PrinterDriverFactory::createFakePrinter() { + spdlog::info("Creating FakePrinter (virtual test printer)"); + return std::make_shared(); +} + +std::shared_ptr PrinterDriverFactory::createByName(const std::string& driverName) { + // Convert to lowercase for case-insensitive comparison + std::string nameLower = driverName; + std::transform(nameLower.begin(), nameLower.end(), nameLower.begin(), + [](unsigned char c) { return std::tolower(c); }); + + if (nameLower == "p700" || nameLower == "p700printer") { + spdlog::info("Creating P700 printer driver by name"); + return std::make_shared(); + } else if (nameLower == "fakeprinter" || nameLower == "fake") { + return createFakePrinter(); + } + + spdlog::warn("Unknown printer driver name: {}", driverName); + return nullptr; +} + +std::vector PrinterDriverFactory::listAllDrivers() const { + return { + std::string(printer::P700Printer::mInfo.driverName), + std::string(printer::FakePrinter::mInfo.driverName) + }; +} + } // namespace ptprnt \ No newline at end of file diff --git a/src/PrinterDriverFactory.hpp b/src/PrinterDriverFactory.hpp index 2d75586..b7f675f 100644 --- a/src/PrinterDriverFactory.hpp +++ b/src/PrinterDriverFactory.hpp @@ -1,4 +1,6 @@ #include +#include +#include #include "interface/IPrinterDriver.hpp" #include "libusbwrap/LibUsbTypes.hpp" @@ -14,8 +16,32 @@ class PrinterDriverFactory { PrinterDriverFactory(PrinterDriverFactory&&) = delete; PrinterDriverFactory& operator=(PrinterDriverFactory&&) = delete; + /** + * @brief Create a printer driver based on USB ID + * @param id USB vendor and product ID + * @return Printer driver instance or nullptr if no match + */ std::shared_ptr create(libusbwrap::usbId id); + /** + * @brief Create a virtual FakePrinter for testing without hardware + * @return FakePrinter instance + */ + std::shared_ptr createFakePrinter(); + + /** + * @brief Create a printer driver by name + * @param driverName Name of the driver (from PrinterInfo.driverName) + * @return Printer driver instance or nullptr if no match + */ + std::shared_ptr createByName(const std::string& driverName); + + /** + * @brief Get list of all available printer driver names + * @return Vector of driver names + */ + std::vector listAllDrivers() const; + private: }; diff --git a/src/PtouchPrint.cpp b/src/PtouchPrint.cpp index 034c893..9a9d0ca 100644 --- a/src/PtouchPrint.cpp +++ b/src/PtouchPrint.cpp @@ -71,27 +71,87 @@ int PtouchPrint::init(int argc, char** argv) { int PtouchPrint::run() { spdlog::info("ptprnt version {}", mVersionString); SPDLOG_TRACE("testing trace"); - mDetectedPrinters = getCompatiblePrinters(); - auto numFoundPrinters = mDetectedPrinters.size(); - if (numFoundPrinters == 0) { - spdlog::error("No compatible printers found, please make sure that they are turned on and connected"); - return -1; - } else if (numFoundPrinters > 1) { - spdlog::warn("Found more than one compatible printer. Currently not supported."); - return -1; + + // Handle --list-all-drivers flag + if (mListDriversFlag) { + 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 0; } - auto printer = mDetectedPrinters[0]; - const auto printerUsbId = printer->getUsbId(); - 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"); - return -1; - } - if (!printer->attachUsbDevice(std::move(devices[0]))) { - spdlog::error("Failed to attach USB device to printer"); - return -1; + // Determine which printer to use + std::shared_ptr printer = nullptr; + + if (mPrinterSelection != "auto") { + // Explicit printer selection by name + auto driverFactory = std::make_unique(); + printer = driverFactory->createByName(mPrinterSelection); + + if (!printer) { + spdlog::error("Failed to create printer driver '{}'", mPrinterSelection); + spdlog::info("Use --list-all-drivers to see available drivers"); + return -1; + } + + spdlog::info("Using explicitly selected printer: {}", mPrinterSelection); + + // FakePrinter doesn't need USB device attachment + if (mPrinterSelection == "FakePrinter" || mPrinterSelection == "fake") { + printer->attachUsbDevice(nullptr); + } else { + // Real printer needs USB device + const auto printerUsbId = printer->getUsbId(); + 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); + return -1; + } + + if (devices.size() > 1) { + spdlog::warn("Found more than one device of the same printer on bus. Using first one."); + } + + if (!printer->attachUsbDevice(std::move(devices[0]))) { + spdlog::error("Failed to attach USB device to printer"); + return -1; + } + } + } else { + // Auto-detect printer from USB devices + mDetectedPrinters = getCompatiblePrinters(); + auto numFoundPrinters = mDetectedPrinters.size(); + + if (numFoundPrinters == 0) { + spdlog::error("No compatible printers found, please make sure that they are turned on and connected"); + spdlog::info("Tip: Use -p FakePrinter for testing without hardware"); + return -1; + } else if (numFoundPrinters > 1) { + spdlog::warn("Found more than one compatible printer. Use -p to select explicitly."); + return -1; + } + + printer = mDetectedPrinters[0]; + const auto printerUsbId = printer->getUsbId(); + 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"); + return -1; + } + + if (!printer->attachUsbDevice(std::move(devices[0]))) { + spdlog::error("Failed to attach USB device to printer"); + return -1; + } } + auto status = printer->getPrinterStatus(); spdlog::info("Detected tape width is {}mm", status.tapeWidthMm); @@ -206,6 +266,14 @@ void PtouchPrint::setupCliParser() { mApp.add_flag("-v,--verbose", mVerboseFlag, "Enable verbose output"); mApp.add_flag("-V,--version", printVersion, "Prints the ptprnt's version"); + // Printer selection + mApp.add_option("-p,--printer", mPrinterSelection, + "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"); + // Text printing options mApp.add_option("-t,--text", "Text to print (can be used multple times, use formatting options before to " diff --git a/src/PtouchPrint.hpp b/src/PtouchPrint.hpp index 75b04b3..fe66c0a 100644 --- a/src/PtouchPrint.hpp +++ b/src/PtouchPrint.hpp @@ -58,7 +58,9 @@ class PtouchPrint { std::vector mCommands{}; std::string mVersionString = ""; - // CLI flags + // CLI flags and options bool mVerboseFlag = false; + std::string mPrinterSelection = "auto"; + bool mListDriversFlag = false; }; } // namespace ptprnt \ No newline at end of file diff --git a/src/meson.build b/src/meson.build index 2d4fa05..7f184df 100644 --- a/src/meson.build +++ b/src/meson.build @@ -7,6 +7,7 @@ ptprnt_hpps = files ( 'interface/IPrinterDriver.hpp', 'interface/IPrinterTypes.hpp', 'printers/P700Printer.hpp', + 'printers/FakePrinter.hpp', 'PtouchPrint.hpp', 'PrinterDriverFactory.hpp', 'graphics/Bitmap.hpp', @@ -18,6 +19,7 @@ ptprnt_srcs = files ( 'PtouchPrint.cpp', 'PrinterDriverFactory.cpp', 'printers/P700Printer.cpp', + 'printers/FakePrinter.cpp', 'graphics/Label.cpp', 'graphics/Bitmap.cpp', 'graphics/Monochrome.cpp', diff --git a/src/printers/FakePrinter.cpp b/src/printers/FakePrinter.cpp new file mode 100644 index 0000000..9a96b83 --- /dev/null +++ b/src/printers/FakePrinter.cpp @@ -0,0 +1,188 @@ +/* + ptrnt - print labels on linux + Copyright (C) 2023 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 "FakePrinter.hpp" + +#include + +#include +#include +#include + +#include "../graphics/Monochrome.hpp" + +namespace ptprnt::printer { + +const PrinterInfo FakePrinter::mInfo = { + .driverName = "FakePrinter", + .name = "Virtual Test Printer", + .version = "v1.0", + .usbId{0x0000, 0x0000}, // No USB ID - virtual printer created explicitly + .pixelLines = 128 +}; + +const std::string_view FakePrinter::getDriverName() { + return mInfo.driverName; +} + +const std::string_view FakePrinter::getName() { + return mInfo.name; +} + +const std::string_view FakePrinter::getVersion() { + return mInfo.version; +} + +const PrinterInfo FakePrinter::getPrinterInfo() { + return mInfo; +} + +const PrinterStatus FakePrinter::getPrinterStatus() { + return mStatus; +} + +const libusbwrap::usbId FakePrinter::getUsbId() { + return mInfo.usbId; +} + +bool FakePrinter::attachUsbDevice(std::shared_ptr usbHndl) { + // FakePrinter doesn't need a real USB device + mHasAttachedDevice = true; + spdlog::debug("FakePrinter: Simulated USB device attachment"); + return true; +} + +bool FakePrinter::detachUsbDevice() { + mHasAttachedDevice = false; + spdlog::debug("FakePrinter: Simulated USB device detachment"); + return true; +} + +bool FakePrinter::printBitmap(const graphics::Bitmap& bitmap) { + // Convert bitmap to MonochromeData and delegate + auto pixels = bitmap.getPixelsCpy(); + auto mono = graphics::Monochrome(pixels, bitmap.getWidth(), bitmap.getHeight()); + auto monoData = mono.get(); + + return printMonochromeData(monoData); +} + +bool FakePrinter::printMonochromeData(const graphics::MonochromeData& data) { + spdlog::debug("FakePrinter: Simulating printing of {}x{} bitmap", data.width, data.height); + + // Simulate the printing process by reconstructing the bitmap + auto printed = simulatePrinting(data); + mLastPrint = std::make_unique>(std::move(printed)); + + spdlog::info("FakePrinter: Successfully 'printed' label ({}x{} pixels)", + mLastPrint->getWidth(), mLastPrint->getHeight()); + + return true; +} + +bool FakePrinter::printLabel(const std::unique_ptr label) { + // Convert label directly to MonochromeData + auto pixels = label->getRaw(); + auto mono = graphics::Monochrome(pixels, label->getWidth(), label->getHeight(), graphics::Orientation::PORTRAIT); + auto monoData = mono.get(); + + spdlog::debug("FakePrinter: Label has {}x{} pixel size", label->getWidth(), label->getHeight()); + + return printMonochromeData(monoData); +} + +bool FakePrinter::print() { + spdlog::debug("FakePrinter: Print command (no-op for virtual printer)"); + return true; +} + +graphics::Bitmap FakePrinter::simulatePrinting(const graphics::MonochromeData& data) { + spdlog::debug("FakePrinter: Simulating column-by-column printing like real hardware"); + + // Create output bitmap with same dimensions + graphics::Bitmap result(data.width, data.height); + std::vector pixels(data.width * data.height, 0); + + // Simulate printer behavior: process column by column + // This mimics how label printers physically print one vertical line at a time + for (uint32_t col = 0; col < data.width; col++) { + spdlog::trace("FakePrinter: Processing column {}/{}", col + 1, data.width); + + // Extract column data bit by bit (simulating what would be sent to printer) + std::vector columnBytes; + + for (uint32_t row = 0; row < data.height; row += 8) { + uint8_t byte = 0; + + // Pack 8 vertical pixels into one byte (printer data format) + for (int bit = 0; bit < 8 && (row + bit) < data.height; bit++) { + if (data.getBit(col, row + bit)) { + byte |= (1 << (7 - bit)); + } + } + + columnBytes.push_back(byte); + } + + // Now "print" this column by unpacking the bytes back to pixels + // This simulates the printer head physically printing this column + for (size_t byteIdx = 0; byteIdx < columnBytes.size(); byteIdx++) { + uint8_t byte = columnBytes[byteIdx]; + uint32_t baseRow = byteIdx * 8; + + for (int bit = 0; bit < 8 && (baseRow + bit) < data.height; bit++) { + bool pixelOn = (byte & (1 << (7 - bit))) != 0; + uint32_t row = baseRow + bit; + + // Write to output bitmap + size_t pixelIdx = row * data.width + col; + pixels[pixelIdx] = pixelOn ? 255 : 0; // 255 = black, 0 = white + } + } + } + + // Set the pixels in the result bitmap + result.setPixels(pixels); + + spdlog::debug("FakePrinter: Simulation complete, reconstructed {}x{} bitmap", + result.getWidth(), result.getHeight()); + + return result; +} + +const graphics::Bitmap& FakePrinter::getLastPrint() const { + if (!mLastPrint) { + throw std::runtime_error("FakePrinter: No print data available"); + } + return *mLastPrint; +} + +bool FakePrinter::saveLastPrintToPng(const std::string& filename) const { + if (!mLastPrint || mLastPrint->getWidth() == 0 || mLastPrint->getHeight() == 0) { + spdlog::error("FakePrinter: No print data available to save"); + 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 true; +} + +} // namespace ptprnt::printer diff --git a/src/printers/FakePrinter.hpp b/src/printers/FakePrinter.hpp new file mode 100644 index 0000000..2b63b31 --- /dev/null +++ b/src/printers/FakePrinter.hpp @@ -0,0 +1,94 @@ +/* + ptrnt - print labels on linux + Copyright (C) 2023 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 . + + */ + +#pragma once + +#include +#include +#include + +#include "../interface/IPrinterDriver.hpp" +#include "../interface/IPrinterTypes.hpp" +#include "../libusbwrap/LibUsbTypes.hpp" +#include "../libusbwrap/interface/IUsbDevice.hpp" +#include "../graphics/Bitmap.hpp" + +namespace ptprnt::printer { + +/** + * @brief Virtual printer driver for testing without hardware + * + * FakePrinter simulates a real label printer by processing bitmap data + * column by column and reconstructing it into a new bitmap, mimicking + * the physical printing process of label printers. + */ +class FakePrinter : public ::ptprnt::IPrinterDriver { + public: + FakePrinter() = default; + ~FakePrinter() override = default; + + FakePrinter(const FakePrinter&) = delete; + FakePrinter& operator=(const FakePrinter&) = delete; + FakePrinter(FakePrinter&&) = default; + FakePrinter& operator=(FakePrinter&&) = default; + + // Printer info - static to be accessed without instantiation + static const PrinterInfo mInfo; + + // IPrinterDriver interface + [[nodiscard]] const std::string_view getDriverName() override; + [[nodiscard]] const std::string_view getName() override; + [[nodiscard]] const libusbwrap::usbId getUsbId() override; + [[nodiscard]] const std::string_view getVersion() override; + [[nodiscard]] const PrinterInfo getPrinterInfo() override; + [[nodiscard]] const PrinterStatus getPrinterStatus() override; + bool attachUsbDevice(std::shared_ptr usbHndl) override; + bool detachUsbDevice() override; + bool printBitmap(const graphics::Bitmap& bitmap) override; + bool printMonochromeData(const graphics::MonochromeData& data) override; + bool printLabel(const std::unique_ptr label) override; + bool print() override; + + /** + * @brief Get the last printed bitmap + * @return The reconstructed bitmap from simulated printing + */ + [[nodiscard]] const graphics::Bitmap& getLastPrint() const; + + /** + * @brief Save the last print to a PNG file + * @param filename Path to save the PNG + * @return true if successful + */ + bool saveLastPrintToPng(const std::string& filename) const; + + private: + /** + * @brief Simulate printing by reconstructing bitmap column by column + * @param data Monochrome data to "print" + * @return Reconstructed bitmap + */ + graphics::Bitmap simulatePrinting(const graphics::MonochromeData& data); + + std::unique_ptr> mLastPrint; + bool mHasAttachedDevice = false; + PrinterStatus mStatus{.tapeWidthMm = 12}; // Default to 12mm tape +}; + +} // namespace ptprnt::printer -- 2.49.1 From 59b3b34edc21fd4d30729ed3cdbe9a40b55232c5 Mon Sep 17 00:00:00 2001 From: Moritz Martinius Date: Sat, 11 Oct 2025 17:04:55 +0200 Subject: [PATCH 20/28] 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); } -- 2.49.1 From 6a593f2a408a8a217e9ed523e41b1cdeb6e93397 Mon Sep 17 00:00:00 2001 From: Moritz Martinius Date: Sat, 11 Oct 2025 17:30:27 +0200 Subject: [PATCH 21/28] Remove unused interface methods --- src/graphics/Label.cpp | 10 +--------- src/graphics/Label.hpp | 2 -- src/graphics/interface/ILabel.hpp | 2 -- src/printers/P700Printer.hpp | 8 ++++---- 4 files changed, 5 insertions(+), 17 deletions(-) diff --git a/src/graphics/Label.cpp b/src/graphics/Label.cpp index d978eeb..8ab09c3 100644 --- a/src/graphics/Label.cpp +++ b/src/graphics/Label.cpp @@ -90,14 +90,6 @@ int Label::getHeight() { return mPrinterHeight; } -int Label::getLayoutHeight() { - return mLayoutHeight; -} - -int Label::getLayoutWidth() { - return mLayoutWidth; -} - void Label::configureLayout(PangoLayout* layout, const std::string& text, PangoFontDescription* fontDesc) { pango_layout_set_font_description(layout, fontDesc); pango_layout_set_text(layout, text.c_str(), static_cast(text.length())); @@ -189,7 +181,7 @@ bool Label::create(const std::string& labelText) { break; case VAlignPosition::MIDDLE: cairo_move_to(mCairoCtx.get(), 0.0, (mPrinterHeight - mLayoutHeight) / 2); - [[fallthrough]]; + break; default: break; } diff --git a/src/graphics/Label.hpp b/src/graphics/Label.hpp index 2dc6bc1..f7625ab 100644 --- a/src/graphics/Label.hpp +++ b/src/graphics/Label.hpp @@ -69,8 +69,6 @@ class Label : public ILabel { void writeToPng(const std::string& file); [[nodiscard]] int getWidth() override; [[nodiscard]] int getHeight() override; - [[nodiscard]] int getLayoutWidth() override; - [[nodiscard]] int getLayoutHeight() override; [[nodiscard]] std::vector getRaw() override; void setFontSize(const double fontSize) override; void setFontFamily(const std::string& fontFamily) override; diff --git a/src/graphics/interface/ILabel.hpp b/src/graphics/interface/ILabel.hpp index 46acd05..d63c6e6 100644 --- a/src/graphics/interface/ILabel.hpp +++ b/src/graphics/interface/ILabel.hpp @@ -70,8 +70,6 @@ class ILabel { virtual std::vector getRaw() = 0; virtual int getWidth() = 0; virtual int getHeight() = 0; - virtual int getLayoutWidth() = 0; - virtual int getLayoutHeight() = 0; virtual void setText(const std::string& text) = 0; virtual void setFontSize(const double fontSize) = 0; diff --git a/src/printers/P700Printer.hpp b/src/printers/P700Printer.hpp index 5ebb6b4..1309db6 100644 --- a/src/printers/P700Printer.hpp +++ b/src/printers/P700Printer.hpp @@ -23,10 +23,10 @@ #include #include -#include "../interface/IPrinterDriver.hpp" -#include "../interface/IPrinterTypes.hpp" -#include "../libusbwrap/LibUsbTypes.hpp" -#include "../libusbwrap/interface/IUsbDevice.hpp" +#include "interface/IPrinterDriver.hpp" +#include "interface/IPrinterTypes.hpp" +#include "libusbwrap/LibUsbTypes.hpp" +#include "libusbwrap/interface/IUsbDevice.hpp" #pragma once -- 2.49.1 From 8658e5c9fd0af6ebef70ba8f2312ab531ccf1555 Mon Sep 17 00:00:00 2001 From: Moritz Martinius Date: Sat, 11 Oct 2025 17:38:47 +0200 Subject: [PATCH 22/28] Fix merge --- src/printers/FakePrinter.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/printers/FakePrinter.cpp b/src/printers/FakePrinter.cpp index d6f7a91..4183aa2 100644 --- a/src/printers/FakePrinter.cpp +++ b/src/printers/FakePrinter.cpp @@ -159,7 +159,6 @@ graphics::Bitmap FakePrinter::simulatePrinting(const graphics: } // Now "print" this column by unpacking the bytes back to pixels - // This simulates the printer head physically printing this column for (size_t byteIdx = 0; byteIdx < columnBytes.size(); byteIdx++) { uint8_t byte = columnBytes[byteIdx]; uint32_t baseRow = byteIdx * 8; -- 2.49.1 From bf7ff27b8d1bbb3e25742fd6a8ea41f26e866b51 Mon Sep 17 00:00:00 2001 From: Moritz Martinius Date: Sat, 11 Oct 2025 18:37:27 +0200 Subject: [PATCH 23/28] cleanup --- src/PrinterDriverFactory.cpp | 13 +++++-------- src/PtouchPrint.cpp | 1 - src/libusbwrap/UsbDeviceFactory.cpp | 12 +++++------- src/printers/P700Printer.cpp | 18 ++++-------------- 4 files changed, 14 insertions(+), 30 deletions(-) diff --git a/src/PrinterDriverFactory.cpp b/src/PrinterDriverFactory.cpp index 0b197b9..55dbdd8 100644 --- a/src/PrinterDriverFactory.cpp +++ b/src/PrinterDriverFactory.cpp @@ -1,12 +1,13 @@ #include "PrinterDriverFactory.hpp" +#include #include #include -#include "printers/P700Printer.hpp" -#include "printers/FakePrinter.hpp" #include "libusbwrap/LibUsbTypes.hpp" +#include "printers/FakePrinter.hpp" +#include "printers/P700Printer.hpp" namespace ptprnt { @@ -29,8 +30,7 @@ std::shared_ptr PrinterDriverFactory::createFakePrinter() { std::shared_ptr PrinterDriverFactory::createByName(const std::string& driverName) { // Convert to lowercase for case-insensitive comparison std::string nameLower = driverName; - std::transform(nameLower.begin(), nameLower.end(), nameLower.begin(), - [](unsigned char c) { return std::tolower(c); }); + std::ranges::transform(nameLower, nameLower.begin(), [](unsigned char c) { return std::tolower(c); }); if (nameLower == "p700" || nameLower == "p700printer") { spdlog::info("Creating P700 printer driver by name"); @@ -44,10 +44,7 @@ std::shared_ptr PrinterDriverFactory::createByName(const std::st } std::vector PrinterDriverFactory::listAllDrivers() const { - return { - std::string(printer::P700Printer::mInfo.driverName), - std::string(printer::FakePrinter::mInfo.driverName) - }; + return {std::string(printer::P700Printer::mInfo.driverName), std::string(printer::FakePrinter::mInfo.driverName)}; } } // namespace ptprnt \ No newline at end of file diff --git a/src/PtouchPrint.cpp b/src/PtouchPrint.cpp index b79ec29..71cdbbf 100644 --- a/src/PtouchPrint.cpp +++ b/src/PtouchPrint.cpp @@ -70,7 +70,6 @@ int PtouchPrint::init(int argc, char** argv) { int PtouchPrint::run() { spdlog::info("ptprnt version {}", mVersionString); - SPDLOG_TRACE("testing trace"); // Handle --list-all-drivers flag if (mListDriversFlag) { diff --git a/src/libusbwrap/UsbDeviceFactory.cpp b/src/libusbwrap/UsbDeviceFactory.cpp index 20991ba..e5aead0 100644 --- a/src/libusbwrap/UsbDeviceFactory.cpp +++ b/src/libusbwrap/UsbDeviceFactory.cpp @@ -67,21 +67,19 @@ ssize_t UsbDeviceFactory::refreshDeviceList() { return ret; } -std::vector> UsbDeviceFactory::buildMaskedDeviceVector(uint16_t vidMask, - uint16_t pidMask, - uint16_t vid, - uint16_t pid) { +std::vector> UsbDeviceFactory::buildMaskedDeviceVector(uint16_t vidMask, uint16_t pidMask, + uint16_t vid, uint16_t pid) { std::vector> matchedDevices; // see libusb/examples/listdevs.c for (auto& currDev : mLibusbDeviceList) { - struct libusb_device_descriptor currDevDesc {}; + struct libusb_device_descriptor currDevDesc{}; int ret = libusb_get_device_descriptor(currDev.get(), &currDevDesc); + spdlog::trace("Detected Device {:04x}:{:04x} ", currDevDesc.idVendor, currDevDesc.idProduct); if (ret < 0) { continue; } - if (((currDevDesc.idVendor & vidMask) == vid) && - ((currDevDesc.idProduct & pidMask) == pid)) { + if (((currDevDesc.idVendor & vidMask) == vid) && ((currDevDesc.idProduct & pidMask) == pid)) { matchedDevices.push_back(std::make_unique(mLibusbCtx, std::move(currDev))); } } diff --git a/src/printers/P700Printer.cpp b/src/printers/P700Printer.cpp index 8d5ff1a..601bcce 100644 --- a/src/printers/P700Printer.cpp +++ b/src/printers/P700Printer.cpp @@ -21,6 +21,7 @@ #include +#include #include #include #include @@ -32,9 +33,6 @@ #include "../libusbwrap/LibUsbTypes.hpp" #include "spdlog/fmt/bin_to_hex.h" -// as long as DRYRUN is defined, no data is actually send to the printer, we need to save some tape ;) -#define DRYRUN - namespace ptprnt::printer { const PrinterInfo P700Printer::mInfo = {.driverName = "P700", @@ -129,9 +127,6 @@ bool P700Printer::printBitmap(const graphics::Bitmap& bitmap) } bool P700Printer::printMonochromeData(const graphics::MonochromeData& data) { -#ifdef DRYRUN - spdlog::debug("DRYRUN enabled, printing nothing"); -#endif send(p700::commands::RASTER_START); std::vector rastercmd(4); @@ -184,7 +179,6 @@ bool P700Printer::printLabel(std::unique_ptr label) { monoData.transformTo(graphics::Orientation::PORTRAIT); spdlog::debug("Label surface is {}x{}, transformed to portrait", label->getWidth(), label->getHeight()); - monoData.visualize(); return printMonochromeData(monoData); } @@ -203,19 +197,15 @@ bool P700Printer::send(const std::vector& data) { return false; } - size_t tx = 0; + int tx = 0; -#ifndef DRYRUN if (!mUsbHndl->bulkTransfer(0x02, data, &tx, 0)) { spdlog::error("Error writing command to Printer: {}", mUsbHndl->getLastErrorString()); return false; } -#else - tx = data.size(); - spdlog::trace("USB raw data(len {}): {}", data.size(), spdlog::to_hex(data)); -#endif - if (tx != data.size()) { + assert(tx > 0); + if (static_cast(tx) != data.size()) { spdlog::error("Could not transfer all data via USB bulk transfer. Only sent {} of {} bytes", tx, data.size()); return false; } -- 2.49.1 From 652e687fb061ada22cec5aa74530af435ec36d3d Mon Sep 17 00:00:00 2001 From: Moritz Martinius Date: Sun, 12 Oct 2025 12:56:09 +0200 Subject: [PATCH 24/28] Add a trace mode for usb tracing --- meson.build | 11 +++++++- src/PtouchPrint.cpp | 6 +++- src/PtouchPrint.hpp | 1 + src/printers/P700Printer.cpp | 53 ++++++++++++++++++++---------------- src/printers/P700Printer.hpp | 6 +++- 5 files changed, 51 insertions(+), 26 deletions(-) diff --git a/meson.build b/meson.build index 4c11044..358f0fb 100644 --- a/meson.build +++ b/meson.build @@ -35,6 +35,15 @@ incdir = include_directories('src') subdir('src') +# Build arguments +cpp_args = ['-DPROJ_VERSION="' + meson.project_version() + '"'] + +# USB trace mode option (for debugging without sending to hardware) +if get_option('usb_trace_only') + cpp_args += ['-DUSB_TRACE_ONLY'] + message('USB_TRACE_ONLY enabled: USB data will be logged but not sent to device') +endif + ptprnt_exe = executable( 'ptprnt', 'src/main.cpp', @@ -42,7 +51,7 @@ ptprnt_exe = executable( dependencies: [usb_dep, log_dep, fmt_dep, pangocairo_dep, cli11_dep], include_directories: incdir, sources: [ptprnt_srcs], - cpp_args: ['-DPROJ_VERSION="' + meson.project_version() + '"'], + cpp_args: cpp_args, ) diff --git a/src/PtouchPrint.cpp b/src/PtouchPrint.cpp index 71cdbbf..d0d352e 100644 --- a/src/PtouchPrint.cpp +++ b/src/PtouchPrint.cpp @@ -55,7 +55,10 @@ int PtouchPrint::init(int argc, char** argv) { return -1; } - if (mVerboseFlag) { + // Set log level based on flags + if (mTraceFlag) { + setupLogger(spdlog::level::trace); + } else if (mVerboseFlag) { setupLogger(spdlog::level::debug); } else { setupLogger(spdlog::level::warn); @@ -263,6 +266,7 @@ void PtouchPrint::setupCliParser() { // General options mApp.add_flag("-v,--verbose", mVerboseFlag, "Enable verbose output"); + mApp.add_flag("--trace", mTraceFlag, "Enable trace output (shows USB communication)"); mApp.add_flag("-V,--version", printVersion, "Prints the ptprnt's version"); // Printer selection diff --git a/src/PtouchPrint.hpp b/src/PtouchPrint.hpp index fe66c0a..22b5c8f 100644 --- a/src/PtouchPrint.hpp +++ b/src/PtouchPrint.hpp @@ -60,6 +60,7 @@ class PtouchPrint { // CLI flags and options bool mVerboseFlag = false; + bool mTraceFlag = false; std::string mPrinterSelection = "auto"; bool mListDriversFlag = false; }; diff --git a/src/printers/P700Printer.cpp b/src/printers/P700Printer.cpp index 601bcce..0cb7f71 100644 --- a/src/printers/P700Printer.cpp +++ b/src/printers/P700Printer.cpp @@ -127,42 +127,42 @@ bool P700Printer::printBitmap(const graphics::Bitmap& bitmap) } bool P700Printer::printMonochromeData(const graphics::MonochromeData& data) { + // Send initialization sequence + send(p700::commands::INITIALIZE); + // Status is already queried in getPrinterStatus() + send(p700::commands::PRINT_MODE); + send(p700::commands::AUTO_STATUS); + send(p700::commands::MODE_SETTING); - send(p700::commands::RASTER_START); - std::vector rastercmd(4); - rastercmd[0] = 0x47; - rastercmd[1] = 0x00; // size +1 - rastercmd[2] = 0x00; - rastercmd[3] = 0x00; // size -1 + // Send raster data row by row + for (uint32_t row = 0; row < data.height; row++) { + std::vector rowData; - // Process data column by column for the printer - for (uint32_t col = 0; col < data.width; col++) { - std::vector columnData; - - // Extract column data bit by bit - for (uint32_t row = 0; row < data.height; row += 8) { + // Extract row data byte by byte + for (uint32_t col = 0; col < data.width; col += 8) { uint8_t byte = 0; - for (int bit = 0; bit < 8 && (row + bit) < data.height; bit++) { - if (data.getBit(col, row + bit)) { + for (int bit = 0; bit < 8 && (col + bit) < data.width; bit++) { + if (data.getBit(col + bit, row)) { byte |= (1 << (7 - bit)); } } - columnData.push_back(byte); + rowData.push_back(byte); } + // Build raster line command: G + length_high + 0x00 + length_low + data std::vector buf; - buf.insert(buf.begin(), rastercmd.begin(), rastercmd.end()); - buf.insert(std::next(buf.begin(), 4), columnData.begin(), columnData.end()); - buf[1] = columnData.size() + 1; - buf[3] = columnData.size() - 1; + buf.push_back(0x47); // 'G' command + buf.push_back((rowData.size() + 1) & 0xFF); // length + 1 (low byte) + buf.push_back(0x00); // high byte (always 0 for our data size) + buf.push_back((rowData.size() - 1) & 0xFF); // length - 1 + buf.insert(buf.end(), rowData.begin(), rowData.end()); if (!send(buf)) { - spdlog::error("Error sending buffer to printer"); - break; + spdlog::error("Error sending raster line {} to printer", row); + return false; } } - send(p700::commands::EJECT); return true; } @@ -178,7 +178,8 @@ bool P700Printer::printLabel(std::unique_ptr label) { // Transform to portrait orientation for printing monoData.transformTo(graphics::Orientation::PORTRAIT); - spdlog::debug("Label surface is {}x{}, transformed to portrait", label->getWidth(), label->getHeight()); + spdlog::debug("Label surface was {}x{}, after transform to portrait: {}x{}", label->getWidth(), label->getHeight(), + monoData.width, monoData.height); return printMonochromeData(monoData); } @@ -197,6 +198,11 @@ bool P700Printer::send(const std::vector& data) { return false; } + spdlog::trace("USB Tx → 0x02 {:03d} bytes: {:Xn}", data.size(), spdlog::to_hex(data)); +#ifdef USB_TRACE_ONLY + // Trace mode: Log the data that would be sent without actually sending it + return true; +#else int tx = 0; if (!mUsbHndl->bulkTransfer(0x02, data, &tx, 0)) { @@ -211,6 +217,7 @@ bool P700Printer::send(const std::vector& data) { } return true; +#endif } bool P700Printer::init() { diff --git a/src/printers/P700Printer.hpp b/src/printers/P700Printer.hpp index 1309db6..43882e6 100644 --- a/src/printers/P700Printer.hpp +++ b/src/printers/P700Printer.hpp @@ -32,7 +32,11 @@ namespace ptprnt::printer { namespace p700::commands { -const cmd_T GET_STATUS{0x1b, 0x69, 0x53}; +const cmd_T INITIALIZE{0x1b, 0x40}; // ESC @ - Initialize +const cmd_T GET_STATUS{0x1b, 0x69, 0x53}; // ESC i S - Status query +const cmd_T PRINT_MODE{0x4d, 0x02}; // M 0x02 - Print mode +const cmd_T AUTO_STATUS{0x1b, 0x69, 0x61, 0x01}; // ESC i a - Auto status +const cmd_T MODE_SETTING{0x1b, 0x69, 0x4d, 0x40}; // ESC i M @ - Advanced mode const cmd_T RASTER_START{0x1b, 0x69, 0x7a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; const cmd_T INFO{0x1b, 0x69, 0x7a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; const cmd_T PACKBITSON{0x02}; -- 2.49.1 From ae22feed4f81c1cd7742c405a4bdcbb6bdba0982 Mon Sep 17 00:00:00 2001 From: Moritz Martinius Date: Sun, 12 Oct 2025 12:56:29 +0200 Subject: [PATCH 25/28] Add meson options --- meson_options.txt | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 meson_options.txt diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 0000000..9cc746f --- /dev/null +++ b/meson_options.txt @@ -0,0 +1,4 @@ +option('usb_trace_only', + type: 'boolean', + value: false, + description: 'Enable USB trace mode: log USB data without sending to device (saves label tape during debugging)') -- 2.49.1 From 58287202d89456279fbf8c37923e71329a03ed19 Mon Sep 17 00:00:00 2001 From: Moritz Martinius Date: Sun, 12 Oct 2025 21:20:17 +0200 Subject: [PATCH 26/28] printing but flipped --- src/printers/P700Printer.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/printers/P700Printer.cpp b/src/printers/P700Printer.cpp index 0cb7f71..3d996e8 100644 --- a/src/printers/P700Printer.cpp +++ b/src/printers/P700Printer.cpp @@ -128,7 +128,12 @@ bool P700Printer::printBitmap(const graphics::Bitmap& bitmap) bool P700Printer::printMonochromeData(const graphics::MonochromeData& data) { // Send initialization sequence - send(p700::commands::INITIALIZE); + // The INITIALIZE command needs to be sent as a 128-byte packet with ESC @ at the end + std::vector initCmd(128, 0x00); + initCmd[126] = 0x1b; // ESC + initCmd[127] = 0x40; // @ + send(initCmd); + // Status is already queried in getPrinterStatus() send(p700::commands::PRINT_MODE); send(p700::commands::AUTO_STATUS); @@ -163,6 +168,9 @@ bool P700Printer::printMonochromeData(const graphics::MonochromeData& data) { } } + // Send print finalization commands + send(p700::commands::EJECT); + return true; } -- 2.49.1 From 5f673b7d57c5fcb421be8cdae7984809df7a8d48 Mon Sep 17 00:00:00 2001 From: Moritz Martinius Date: Sun, 12 Oct 2025 21:46:24 +0200 Subject: [PATCH 27/28] Printing not flipped --- src/printers/P700Printer.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/printers/P700Printer.cpp b/src/printers/P700Printer.cpp index 3d996e8..23e75f0 100644 --- a/src/printers/P700Printer.cpp +++ b/src/printers/P700Printer.cpp @@ -130,8 +130,8 @@ bool P700Printer::printMonochromeData(const graphics::MonochromeData& data) { // Send initialization sequence // The INITIALIZE command needs to be sent as a 128-byte packet with ESC @ at the end std::vector initCmd(128, 0x00); - initCmd[126] = 0x1b; // ESC - initCmd[127] = 0x40; // @ + initCmd[126] = p700::commands::INITIALIZE[0]; // ESC + initCmd[127] = p700::commands::INITIALIZE[1]; // @ send(initCmd); // Status is already queried in getPrinterStatus() @@ -139,8 +139,9 @@ bool P700Printer::printMonochromeData(const graphics::MonochromeData& data) { send(p700::commands::AUTO_STATUS); send(p700::commands::MODE_SETTING); - // Send raster data row by row - for (uint32_t row = 0; row < data.height; row++) { + // Send raster data row by row in reverse order (bottom to top) + // The printer feeds tape as it prints, so first row sent appears at the end + for (int row = data.height - 1; row >= 0; row--) { std::vector rowData; // Extract row data byte by byte -- 2.49.1 From 7437d79393c7a8bfe928028f74b5bcb3c4983980 Mon Sep 17 00:00:00 2001 From: Moritz Martinius Date: Sun, 12 Oct 2025 22:04:29 +0200 Subject: [PATCH 28/28] Update copyright --- src/PrinterDriverFactory.cpp | 19 +++ src/PrinterDriverFactory.hpp | 24 +++- src/PtouchPrint.cpp | 2 +- src/PtouchPrint.hpp | 2 +- src/graphics/Bitmap.cpp | 2 +- src/graphics/Bitmap.hpp | 2 +- src/graphics/Label.cpp | 2 +- src/graphics/Label.hpp | 2 +- src/graphics/Monochrome.cpp | 2 +- src/graphics/Monochrome.hpp | 2 +- src/graphics/interface/ILabel.hpp | 2 +- src/interface/IPrinterDriver.hpp | 2 +- src/interface/IPrinterTypes.hpp | 2 +- src/libusbwrap/LibUsbTypes.hpp | 2 +- src/libusbwrap/UsbDevice.cpp | 2 +- src/libusbwrap/UsbDevice.hpp | 2 +- src/libusbwrap/UsbDeviceFactory.cpp | 2 +- src/libusbwrap/interface/IUsbDevice.hpp | 19 +++ .../interface/IUsbDeviceFactory.hpp | 23 +++- src/main.cpp | 2 +- src/printers/FakePrinter.cpp | 2 +- src/printers/FakePrinter.hpp | 2 +- src/printers/P700Printer.cpp | 2 +- src/printers/P700Printer.hpp | 2 +- update_copyright.sh | 115 ++++++++++++++++++ 25 files changed, 216 insertions(+), 24 deletions(-) create mode 100755 update_copyright.sh diff --git a/src/PrinterDriverFactory.cpp b/src/PrinterDriverFactory.cpp index 55dbdd8..29cd109 100644 --- a/src/PrinterDriverFactory.cpp +++ b/src/PrinterDriverFactory.cpp @@ -1,3 +1,22 @@ +/* + ptrnt - print labels on linux + Copyright (C) 2024-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 "PrinterDriverFactory.hpp" #include diff --git a/src/PrinterDriverFactory.hpp b/src/PrinterDriverFactory.hpp index b7f675f..a20cd7b 100644 --- a/src/PrinterDriverFactory.hpp +++ b/src/PrinterDriverFactory.hpp @@ -1,6 +1,26 @@ +/* + ptrnt - print labels on linux + Copyright (C) 2024-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 -#include #include +#include + #include "interface/IPrinterDriver.hpp" #include "libusbwrap/LibUsbTypes.hpp" @@ -45,4 +65,4 @@ class PrinterDriverFactory { private: }; -} \ No newline at end of file +} // namespace ptprnt \ No newline at end of file diff --git a/src/PtouchPrint.cpp b/src/PtouchPrint.cpp index d0d352e..f2137f2 100644 --- a/src/PtouchPrint.cpp +++ b/src/PtouchPrint.cpp @@ -1,6 +1,6 @@ /* ptrnt - print labels on linux - Copyright (C) 2023 Moritz Martinius + 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 diff --git a/src/PtouchPrint.hpp b/src/PtouchPrint.hpp index 22b5c8f..911eea8 100644 --- a/src/PtouchPrint.hpp +++ b/src/PtouchPrint.hpp @@ -1,6 +1,6 @@ /* ptrnt - print labels on linux - Copyright (C) 2023 Moritz Martinius + 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 diff --git a/src/graphics/Bitmap.cpp b/src/graphics/Bitmap.cpp index 1bbcb13..9b29204 100644 --- a/src/graphics/Bitmap.cpp +++ b/src/graphics/Bitmap.cpp @@ -1,6 +1,6 @@ /* ptrnt - print labels on linux - Copyright (C) 2023 Moritz Martinius + 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 diff --git a/src/graphics/Bitmap.hpp b/src/graphics/Bitmap.hpp index 11a2b46..09fefc5 100644 --- a/src/graphics/Bitmap.hpp +++ b/src/graphics/Bitmap.hpp @@ -1,6 +1,6 @@ /* ptrnt - print labels on linux - Copyright (C) 2023 Moritz Martinius + 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 diff --git a/src/graphics/Label.cpp b/src/graphics/Label.cpp index 8ab09c3..c4d112f 100644 --- a/src/graphics/Label.cpp +++ b/src/graphics/Label.cpp @@ -1,6 +1,6 @@ /* ptrnt - print labels on linux - Copyright (C) 2023 Moritz Martinius + 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 diff --git a/src/graphics/Label.hpp b/src/graphics/Label.hpp index f7625ab..cee53e3 100644 --- a/src/graphics/Label.hpp +++ b/src/graphics/Label.hpp @@ -1,6 +1,6 @@ /* ptrnt - print labels on linux - Copyright (C) 2023 Moritz Martinius + 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 diff --git a/src/graphics/Monochrome.cpp b/src/graphics/Monochrome.cpp index 333e114..07f1100 100644 --- a/src/graphics/Monochrome.cpp +++ b/src/graphics/Monochrome.cpp @@ -1,6 +1,6 @@ /* ptrnt - print labels on linux - Copyright (C) 2023 Moritz Martinius + 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 diff --git a/src/graphics/Monochrome.hpp b/src/graphics/Monochrome.hpp index 958deb7..ec6b3c0 100644 --- a/src/graphics/Monochrome.hpp +++ b/src/graphics/Monochrome.hpp @@ -1,6 +1,6 @@ /* ptrnt - print labels on linux - Copyright (C) 2023 Moritz Martinius + 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 diff --git a/src/graphics/interface/ILabel.hpp b/src/graphics/interface/ILabel.hpp index d63c6e6..5c5c059 100644 --- a/src/graphics/interface/ILabel.hpp +++ b/src/graphics/interface/ILabel.hpp @@ -1,6 +1,6 @@ /* ptrnt - print labels on linux - Copyright (C) 2023 Moritz Martinius + Copyright (C) 2024-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 diff --git a/src/interface/IPrinterDriver.hpp b/src/interface/IPrinterDriver.hpp index 7a60462..48106fb 100644 --- a/src/interface/IPrinterDriver.hpp +++ b/src/interface/IPrinterDriver.hpp @@ -1,6 +1,6 @@ /* ptrnt - print labels on linux - Copyright (C) 2023 Moritz Martinius + 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 diff --git a/src/interface/IPrinterTypes.hpp b/src/interface/IPrinterTypes.hpp index d1b3d70..908a482 100644 --- a/src/interface/IPrinterTypes.hpp +++ b/src/interface/IPrinterTypes.hpp @@ -1,6 +1,6 @@ /* ptrnt - print labels on linux - Copyright (C) 2023 Moritz Martinius + Copyright (C) 2023-2024 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 diff --git a/src/libusbwrap/LibUsbTypes.hpp b/src/libusbwrap/LibUsbTypes.hpp index 054210f..f11c79b 100644 --- a/src/libusbwrap/LibUsbTypes.hpp +++ b/src/libusbwrap/LibUsbTypes.hpp @@ -1,6 +1,6 @@ /* ptrnt - print labels on linux - Copyright (C) 2023 Moritz Martinius + Copyright (C) 2023-2024 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 diff --git a/src/libusbwrap/UsbDevice.cpp b/src/libusbwrap/UsbDevice.cpp index c3c632f..dd22aee 100644 --- a/src/libusbwrap/UsbDevice.cpp +++ b/src/libusbwrap/UsbDevice.cpp @@ -1,6 +1,6 @@ /* ptrnt - print labels on linux - Copyright (C) 2023 Moritz Martinius + Copyright (C) 2023-2024 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 diff --git a/src/libusbwrap/UsbDevice.hpp b/src/libusbwrap/UsbDevice.hpp index 8daa52a..475b1a3 100644 --- a/src/libusbwrap/UsbDevice.hpp +++ b/src/libusbwrap/UsbDevice.hpp @@ -1,6 +1,6 @@ /* ptrnt - print labels on linux - Copyright (C) 2023 Moritz Martinius + Copyright (C) 2023-2024 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 diff --git a/src/libusbwrap/UsbDeviceFactory.cpp b/src/libusbwrap/UsbDeviceFactory.cpp index e5aead0..5ad5349 100644 --- a/src/libusbwrap/UsbDeviceFactory.cpp +++ b/src/libusbwrap/UsbDeviceFactory.cpp @@ -1,6 +1,6 @@ /* ptrnt - print labels on linux - Copyright (C) 2023 Moritz Martinius + 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 diff --git a/src/libusbwrap/interface/IUsbDevice.hpp b/src/libusbwrap/interface/IUsbDevice.hpp index 335e2ca..fe80de7 100644 --- a/src/libusbwrap/interface/IUsbDevice.hpp +++ b/src/libusbwrap/interface/IUsbDevice.hpp @@ -1,3 +1,22 @@ +/* + ptrnt - print labels on linux + Copyright (C) 2023-2024 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 . + + */ + #pragma once #include diff --git a/src/libusbwrap/interface/IUsbDeviceFactory.hpp b/src/libusbwrap/interface/IUsbDeviceFactory.hpp index a148e44..725edad 100644 --- a/src/libusbwrap/interface/IUsbDeviceFactory.hpp +++ b/src/libusbwrap/interface/IUsbDeviceFactory.hpp @@ -1,3 +1,22 @@ +/* + ptrnt - print labels on linux + Copyright (C) 2023 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 . + + */ + #pragma once #include @@ -9,8 +28,8 @@ namespace libusbwrap { class IUsbDeviceFactory { public: - virtual ~IUsbDeviceFactory() = default; - virtual std::vector> findAllDevices() = 0; + virtual ~IUsbDeviceFactory() = default; + virtual std::vector> findAllDevices() = 0; virtual std::vector> findDevices(uint16_t vid, uint16_t pid) = 0; }; } // namespace libusbwrap \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index ed35bcc..19a160a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,6 +1,6 @@ /* ptrnt - print labels on linux - Copyright (C) 2023 Moritz Martinius + Copyright (C) 2022-2023 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 diff --git a/src/printers/FakePrinter.cpp b/src/printers/FakePrinter.cpp index 4183aa2..caf3e67 100644 --- a/src/printers/FakePrinter.cpp +++ b/src/printers/FakePrinter.cpp @@ -1,6 +1,6 @@ /* ptrnt - print labels on linux - Copyright (C) 2023 Moritz Martinius + Copyright (C) 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 diff --git a/src/printers/FakePrinter.hpp b/src/printers/FakePrinter.hpp index 09baafa..12cd1ff 100644 --- a/src/printers/FakePrinter.hpp +++ b/src/printers/FakePrinter.hpp @@ -1,6 +1,6 @@ /* ptrnt - print labels on linux - Copyright (C) 2023 Moritz Martinius + Copyright (C) 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 diff --git a/src/printers/P700Printer.cpp b/src/printers/P700Printer.cpp index 23e75f0..b3f8e22 100644 --- a/src/printers/P700Printer.cpp +++ b/src/printers/P700Printer.cpp @@ -1,6 +1,6 @@ /* ptrnt - print labels on linux - Copyright (C) 2023 Moritz Martinius + Copyright (C) 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 diff --git a/src/printers/P700Printer.hpp b/src/printers/P700Printer.hpp index 43882e6..e14f500 100644 --- a/src/printers/P700Printer.hpp +++ b/src/printers/P700Printer.hpp @@ -1,6 +1,6 @@ /* ptrnt - print labels on linux - Copyright (C) 2023 Moritz Martinius + Copyright (C) 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 diff --git a/update_copyright.sh b/update_copyright.sh new file mode 100755 index 0000000..0823e8d --- /dev/null +++ b/update_copyright.sh @@ -0,0 +1,115 @@ +#!/bin/bash + +# update_copyright.sh +# Updates copyright year ranges in a source file based on git history +# +# Usage: ./update_copyright.sh [--dry-run] +# +# Examples: +# # Update a single file +# ./update_copyright.sh src/main.cpp +# +# # Dry-run on a single file +# ./update_copyright.sh --dry-run src/main.cpp +# +# # Update all C++ files using find -exec +# find src \( -name "*.cpp" -o -name "*.hpp" \) -exec ./update_copyright.sh {} \; +# +# # Dry-run on all C++ files +# find src \( -name "*.cpp" -o -name "*.hpp" \) -exec ./update_copyright.sh --dry-run {} \; + +set -e + +# Check for dry-run flag +DRY_RUN=false +FILE="" + +if [ "$1" = "--dry-run" ]; then + DRY_RUN=true + FILE="$2" +elif [ "$1" = "--help" ] || [ "$1" = "-h" ]; then + grep "^#" "$0" | sed 's/^# \?//' + exit 0 +else + FILE="$1" +fi + +# Check if file argument provided +if [ -z "$FILE" ]; then + echo "Error: No file specified" + echo "Usage: $0 [--dry-run] " + echo "Run '$0 --help' for more information" + exit 1 +fi + +# Check if file exists +if [ ! -f "$FILE" ]; then + echo "Error: File not found: $FILE" + exit 1 +fi + +# Get the repository root +REPO_ROOT=$(git rev-parse --show-toplevel) +cd "$REPO_ROOT" + +# Copyright holder name +COPYRIGHT_HOLDER="Moritz Martinius" + +# Function to get first and last commit years for a file +get_years_for_file() { + local file="$1" + + # Get the year of the first commit that touched this file + first_year=$(git log --follow --format=%ad --date=format:%Y --reverse "$file" 2>/dev/null | head -1) + + # Get the year of the last commit that touched this file + last_year=$(git log --follow --format=%ad --date=format:%Y -1 "$file" 2>/dev/null) + + # If file is not in git, use current year + if [ -z "$first_year" ]; then + first_year=$(date +%Y) + last_year=$(date +%Y) + fi + + echo "$first_year $last_year" +} + +# Get years from git history +read -r first_year last_year <<< "$(get_years_for_file "$FILE")" + +# Determine the copyright year string +if [ "$first_year" = "$last_year" ]; then + year_string="$first_year" +else + year_string="$first_year-$last_year" +fi + +# Check if file has a copyright notice +if ! grep -q "Copyright (C)" "$FILE"; then + echo "No copyright notice found in $FILE, skipping" + exit 0 +fi + +if [ "$DRY_RUN" = true ]; then + # Just show what would be changed + current_year=$(grep "Copyright (C)" "$FILE" | sed -n 's/.*Copyright (C) \([0-9]\{4\}\(-[0-9]\{4\}\)\?\).*/\1/p' | head -1) + if [ "$current_year" != "$year_string" ]; then + echo "Would update $FILE: $current_year → $year_string" + else + echo "No change needed for $FILE (already $year_string)" + fi +else + # Update the copyright line + # Matches patterns like "Copyright (C) 2023 Moritz Martinius" or "Copyright (C) 2023-2024 Moritz Martinius" + # Handle variable whitespace between year and name + + # Get current year from file for comparison + current_year=$(grep "Copyright (C)" "$FILE" | sed -n 's/.*Copyright (C) \([0-9]\{4\}\(-[0-9]\{4\}\)\?\).*/\1/p' | head -1) + + if [ "$current_year" = "$year_string" ]; then + echo "No changes needed for $FILE (already $year_string)" + else + sed -i "s/Copyright (C) [0-9]\{4\}\(-[0-9]\{4\}\)\? \+$COPYRIGHT_HOLDER/Copyright (C) $year_string $COPYRIGHT_HOLDER/" "$FILE" + echo "✓ Updated $FILE: $current_year → $year_string" + fi +fi -- 2.49.1