From c0811df4bb7b61fa4189c6f405715a46f45dfe0b Mon Sep 17 00:00:00 2001 From: Moritz Martinius Date: Sat, 23 Sep 2023 13:29:54 +0200 Subject: [PATCH] Moved graphics classes into own namespace, added unit testing with gtest --- .gitignore | 5 +- .vscode/launch.json | 2 +- meson.build | 62 ++++++++++++++---- src/Bitmap.hpp | 16 ----- src/P700Printer.cpp | 35 ++++++++++- src/P700Printer.hpp | 13 +++- src/PtouchPrint.cpp | 4 ++ src/Usb.cpp | 101 ------------------------------ src/graphics/Bitmap.cpp | 55 ++++++++++++++++ src/graphics/Bitmap.hpp | 37 +++++++++++ src/graphics/Image.cpp | 33 ++++++++++ src/graphics/Image.hpp | 17 +++++ src/interface/IPrinterDriver.hpp | 4 +- subprojects/gtest.wrap | 16 +++++ tests/.gitkeep | 0 tests/bitmap_test/bitmap_test.cpp | 47 ++++++++++++++ tests/image_test/image_test.cpp | 9 +++ tests/meson.build | 14 +++++ 18 files changed, 334 insertions(+), 136 deletions(-) delete mode 100644 src/Bitmap.hpp delete mode 100644 src/Usb.cpp create mode 100644 src/graphics/Bitmap.cpp create mode 100644 src/graphics/Bitmap.hpp create mode 100644 src/graphics/Image.cpp create mode 100644 src/graphics/Image.hpp create mode 100644 subprojects/gtest.wrap delete mode 100644 tests/.gitkeep create mode 100644 tests/bitmap_test/bitmap_test.cpp create mode 100644 tests/image_test/image_test.cpp create mode 100644 tests/meson.build diff --git a/.gitignore b/.gitignore index 7b016fe..b215aae 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ builddir/ +subprojects/* +!subprojects/*.wrap .cache/ .vscode/* !.vscode/c_cpp_properties.json !.vscode/settings.json -!.vscode/launch.json \ No newline at end of file +!.vscode/launch.json +ptouch-print/ \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index 53ce6ff..33a2921 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -5,7 +5,7 @@ "version": "0.2.0", "configurations": [ { - "name": "(gdb) Starten", + "name": "ptprnt_debug", "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/builddir/ptprnt", diff --git a/meson.build b/meson.build index 1298279..98d36ca 100644 --- a/meson.build +++ b/meson.build @@ -4,23 +4,59 @@ project('ptprnt', 'cpp', default_options : ['c_std=c11', 'cpp_std=c++17'] ) -usbdep = dependency('libusb-1.0') -logdep = dependency('spdlog') +usb_dep = dependency('libusb-1.0') +log_dep = dependency('spdlog') +pangocairo_dep = dependency('pangocairo') incdir = include_directories('src') -srcs = [ - 'src/main.cpp', - 'src/PtouchPrint.cpp', - 'src/P700Printer.cpp', - 'src/libusbwrap/UsbDeviceFactory.cpp', - 'src/libusbwrap/UsbDevice.cpp' +ptprnt_hpps = [ + 'src/libusbwrap/interface/IUsbDeviceFactory.hpp', + 'src/libusbwrap/interface/IUsbDevice.hpp', + 'src/libusbwrap/UsbDeviceFactory.hpp', + 'src/libusbwrap/LibUsbTypes.hpp', + 'src/libusbwrap/UsbDevice.hpp', + 'src/interface/IPrinterDriver.hpp', + 'src/interface/IPrinterTypes.hpp', + 'src/P700Printer.hpp', + 'src/PtouchPrint.hpp', + 'src/graphics/Bitmap.hpp', + 'src/graphics/Image.hpp', ] -executable( +ptprnt_srcs = [ + 'src/PtouchPrint.cpp', + 'src/P700Printer.cpp', + 'src/graphics/Image.cpp', + 'src/graphics/Bitmap.cpp', + 'src/libusbwrap/UsbDeviceFactory.cpp', + 'src/libusbwrap/UsbDevice.cpp', +] + +ptprnt_lib = library('ptprnt', + include_directories: incdir, + install: true, + dependencies: [usb_dep, log_dep, pangocairo_dep], + sources: [ptprnt_hpps, ptprnt_srcs]) + +ptprnt_dep = declare_dependency(include_directories: incdir, + link_with: ptprnt_lib) + +ptprnt_debug_exe = executable( 'ptprnt', - srcs, - include_directories : incdir, - dependencies : [usbdep, logdep], + 'src/main.cpp', + install: true, + dependencies : [usb_dep, log_dep, ptprnt_dep], cpp_args : ['-DPROJ_VERSION="'+meson.project_version()+'"'] -) \ No newline at end of file +) + + + +#cmake = import('cmake') +gtest_proj = subproject('gtest') +gtest_dep = gtest_proj.get_variable('gtest_main_dep') +if not gtest_dep.found() + error('MESON_SKIP_TEST: gtest not installed.') +endif + +subdir('tests') \ No newline at end of file diff --git a/src/Bitmap.hpp b/src/Bitmap.hpp deleted file mode 100644 index 9a960ad..0000000 --- a/src/Bitmap.hpp +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#include - -#include - -namespace ptprnt { - -struct Bitmap { - uint16_t width; - uint16_t height; - uint16_t bitsPerPixel; - std::vector data; -}; - -} // namespace ptprnt \ No newline at end of file diff --git a/src/P700Printer.cpp b/src/P700Printer.cpp index 4e5cba0..13941fa 100644 --- a/src/P700Printer.cpp +++ b/src/P700Printer.cpp @@ -2,6 +2,7 @@ #include +#include #include #include #include @@ -9,6 +10,7 @@ #include #include +#include "graphics/Image.hpp" #include "libusb.h" #include "libusbwrap/LibUsbTypes.hpp" #include "spdlog/fmt/bin_to_hex.h" @@ -100,11 +102,35 @@ bool P700Printer::detachUsbDevice() { return true; } -bool P700Printer::printBitmap(const Bitmap& bitmap) { +bool P700Printer::printBitmap(const graphics::Bitmap& bitmap) { + auto img = graphics::Image(); + //uint8_t* imgRef = img.getRaw(); + std::array line; + + send(commands["rasterstart"]); + std::vector buf(64); + buf[0] = 0x47; + for (int i = 0; i < 8; i++) { + if (i % 2) { + line.fill(0xffffffff); + } else { + line.fill(0x00000000); + } + + buf[1] = (uint8_t)(8 + 1); + buf[2] = 0; + buf[3] = (uint8_t)(8 - 1); + memcpy(buf.data() + 4, line.data(), line.size()); + send(buf); + } + send(commands["eject"]); return true; } bool P700Printer::printText(const std::string& text, uint16_t fontSize) { + send(commands["lf"]); + send(commands["ff"]); + send(commands["eject"]); return true; } @@ -121,4 +147,11 @@ bool P700Printer::send(std::vector& data) { return true; } + +bool P700Printer::init() { + std::vector cmd(102); + cmd[100] = 0x1b; /* ESC */ + cmd[101] = 0x40; /* @ */ + return send(cmd); +} } // namespace ptprnt::printer \ No newline at end of file diff --git a/src/P700Printer.hpp b/src/P700Printer.hpp index 66222dc..34f09f8 100644 --- a/src/P700Printer.hpp +++ b/src/P700Printer.hpp @@ -1,6 +1,8 @@ #include +#include #include +#include #include "interface/IPrinterDriver.hpp" #include "interface/IPrinterTypes.hpp" @@ -29,11 +31,12 @@ class P700Printer : public ::ptprnt::IPrinterDriver { const PrinterStatus getPrinterStatus() override; bool attachUsbDevice(std::shared_ptr usbHndl) override; bool detachUsbDevice() override; - bool printBitmap(const Bitmap& bitmap) override; + bool printBitmap(const graphics::Bitmap& bitmap) override; bool printText(const std::string& text, uint16_t fontSize) override; private: bool send(std::vector& data); + bool init(); std::shared_ptr mUsbHndl{nullptr}; @@ -42,6 +45,14 @@ class P700Printer : public ::ptprnt::IPrinterDriver { .version = "v1.0", .vid = 0x04f9, .pid = 0x2061}; + std::map> commands{ + {"rasterstart", {0x1b, 0x69, 0x52, 0x01}}, + {"info", {0x1b, 0x69, 0x7a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {"packbitson", {0x02}}, + {"lf", {0x5a}}, + {"ff", {0x0c}}, + {"eject", {0x1a}}, + }; }; } // namespace ptprnt::printer \ No newline at end of file diff --git a/src/PtouchPrint.cpp b/src/PtouchPrint.cpp index a69a103..a2c8859 100644 --- a/src/PtouchPrint.cpp +++ b/src/PtouchPrint.cpp @@ -3,6 +3,7 @@ #include #include "P700Printer.hpp" +#include "graphics/Bitmap.hpp" #include "libusbwrap/UsbDeviceFactory.hpp" PtouchPrint::PtouchPrint() {} @@ -35,6 +36,9 @@ void PtouchPrint::run() { printer->attachUsbDevice(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); } unsigned int PtouchPrint::getCompatiblePrinters() { diff --git a/src/Usb.cpp b/src/Usb.cpp deleted file mode 100644 index af4fff1..0000000 --- a/src/Usb.cpp +++ /dev/null @@ -1,101 +0,0 @@ -#include "Usb.hpp" - -#include - -#include -#include -#include -#include - -#include "UsbTypes.hpp" -#include "libusb.h" - -namespace ptprnt::driver { - -Usb::Usb() { - spdlog::debug("Usb constructing"); - libusb_init(NULL); -} - -Usb::~Usb() { - spdlog::debug("Usb destructing"); -} - -std::optional> Usb::getDevices() { - libusb_device* libUsbDev; - struct libusb_device_descriptor libUsbDesc; - int ret, i = 0; - - mDevices.clear(); - - if (0 == (ret = libusb_get_device_list(NULL, &mLibUsbDevs))) { - spdlog::error("Could not find any USB devices: {}", libusb_error_name(ret)); - return std::nullopt; - } - - while ((libUsbDev = mLibUsbDevs[i++]) != NULL) { - if ((ret = libusb_get_device_descriptor(libUsbDev, &libUsbDesc)) < 0) { - spdlog::error("failed to get device"); - return std::nullopt; - } - - UsbDevice newDev{.vendorId = libUsbDesc.idVendor, - .productId = libUsbDesc.idProduct, - .device = libUsbDev, - .hndl = nullptr}; // handle is only available after we opened the dev - - mDevices.push_back(newDev); - } - - return mDevices; -} - -std::optional Usb::open(UsbDevice dev) { - libusb_device_handle* libUsbHandle = nullptr; - int ret = 0; - - if ((ret = libusb_open(dev.device, &libUsbHandle) != 0)) { - spdlog::error("Could not open device {0:04X}:{1:04X}: {2}", dev.vendorId, dev.productId, - libusb_error_name(ret)); - return std::nullopt; - } - - // detach the device from the kernel if it is active - if ((ret = libusb_kernel_driver_active(libUsbHandle, 0)) == 1) { - if ((ret = libusb_detach_kernel_driver(libUsbHandle, 0)) != 0) { - spdlog::error( - "Could not detach kernel driver for device {0:04X}:{1:04X}: " - "{2}", - dev.vendorId, dev.productId, libusb_error_name(ret)); - return std::nullopt; - } - } - - // claim interface - if ((ret = libusb_claim_interface(libUsbHandle, 0)) != 0) { - spdlog::error( - "Could not claim interface {0:04X}:{1:04X}: " - "{2}", - dev.vendorId, dev.productId, libusb_error_name(ret)); - } - - dev.hndl = libUsbHandle; - - return dev; -} - -bool Usb::close(UsbDevice dev) { - int ret = 0; - if (0 != (ret = libusb_release_interface(dev.hndl, 0))) { - spdlog::error( - "Could not close USB device {0:04X}:{1:04X}: " - "{2}", - dev.vendorId, dev.productId, libusb_error_name(ret)); - } - - spdlog::debug("test"); - libusb_close(dev.hndl); - return true; -} - -} // namespace ptprnt::driver \ No newline at end of file diff --git a/src/graphics/Bitmap.cpp b/src/graphics/Bitmap.cpp new file mode 100644 index 0000000..c9ae74c --- /dev/null +++ b/src/graphics/Bitmap.cpp @@ -0,0 +1,55 @@ +#include "Bitmap.hpp" + +#include + +#include +#include +#include + +namespace ptprnt::graphics { +template +Bitmap::Bitmap(uint16_t width, uint16_t height) + : mWidth{width}, mHeight{height}, mPixels(width * height) {} + +template +uint16_t Bitmap::getWidth() { + return mWidth; +} + +template +uint16_t Bitmap::getHeight() { + return mHeight; +} + +template +std::optional> Bitmap::getLine(uint16_t line) { + if (line >= mHeight) { + // out of bound + return std::nullopt; + } + + auto lineStart = mPixels.begin() + (line * mWidth); + auto lineEnd = mPixels.begin() + ((line + 1) * mWidth); + return std::vector(lineStart, lineEnd); +} + +template +std::optional> Bitmap::getRow(uint16_t row) { + if (row >= mWidth) { + // out of bound + return std::nullopt; + } + + // first pixel is always beginning of the row + std::vector rowPixels(mHeight); + auto it = mPixels.begin() + row; + + for (auto& rowElement : rowPixels) { + rowElement = *it; + it += mWidth; + } + + return rowPixels; +} + +} // namespace ptprnt::graphics \ No newline at end of file diff --git a/src/graphics/Bitmap.hpp b/src/graphics/Bitmap.hpp new file mode 100644 index 0000000..422a880 --- /dev/null +++ b/src/graphics/Bitmap.hpp @@ -0,0 +1,37 @@ +#pragma once + +#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 + +template +class Bitmap { + public: + Bitmap(uint16_t width, uint16_t height); + ~Bitmap() = default; + + uint16_t getWidth(); + uint16_t getHeight(); + std::optional> getLine(uint16_t line); + std::optional> getRow(uint16_t row); + + private: + uint16_t mWidth; + uint16_t mHeight; + std::vector mPixels; +}; + +// force compiler to generate class for type ALPHA8 and RGBX8 +template class Bitmap; +template class Bitmap; + +} // namespace ptprnt::graphics \ No newline at end of file diff --git a/src/graphics/Image.cpp b/src/graphics/Image.cpp new file mode 100644 index 0000000..44170dc --- /dev/null +++ b/src/graphics/Image.cpp @@ -0,0 +1,33 @@ +#include "Image.hpp" + +#include "pango/pango-font.h" + +namespace ptprnt::graphics { +Image::Image() { + mSurface = cairo_image_surface_create(CAIRO_FORMAT_A1, 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); + + mLayout = pango_cairo_create_layout(cr); + pango_layout_set_font_description(mLayout, mFontDescription); + pango_layout_set_text(mLayout, "Hello, world", -1); + + cairo_set_source_rgb(cr, 0.0, 0.0, 0.0); + cairo_move_to(cr, 10.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* Image::getRaw() { + return cairo_image_surface_get_data(mSurface); +} + +Image::~Image() { + g_object_unref(mLayout); + pango_font_description_free(mFontDescription); +} +} // namespace ptprnt::graphics \ No newline at end of file diff --git a/src/graphics/Image.hpp b/src/graphics/Image.hpp new file mode 100644 index 0000000..be3a051 --- /dev/null +++ b/src/graphics/Image.hpp @@ -0,0 +1,17 @@ +#include + +#include + +namespace ptprnt::graphics { +class Image { + public: + Image(); + ~Image(); + uint8_t* getRaw(); + + private: + PangoLayout* mLayout; + PangoFontDescription* mFontDescription; + cairo_surface_t* mSurface; +}; +} // namespace ptprnt::graphics \ No newline at end of file diff --git a/src/interface/IPrinterDriver.hpp b/src/interface/IPrinterDriver.hpp index 33838de..0fcede6 100644 --- a/src/interface/IPrinterDriver.hpp +++ b/src/interface/IPrinterDriver.hpp @@ -3,7 +3,7 @@ #include #include -#include "Bitmap.hpp" +#include "graphics/Bitmap.hpp" #include "interface/IPrinterTypes.hpp" #include "libusbwrap/interface/IUsbDevice.hpp" @@ -20,7 +20,7 @@ class IPrinterDriver { virtual const PrinterStatus getPrinterStatus() = 0; virtual bool attachUsbDevice(std::shared_ptr usbHndl) = 0; virtual bool detachUsbDevice() = 0; - virtual bool printBitmap(const Bitmap& bitmap) = 0; + virtual bool printBitmap(const graphics::Bitmap& bitmap) = 0; virtual bool printText(const std::string& text, uint16_t fontSize) = 0; }; diff --git a/subprojects/gtest.wrap b/subprojects/gtest.wrap new file mode 100644 index 0000000..2b9a32d --- /dev/null +++ b/subprojects/gtest.wrap @@ -0,0 +1,16 @@ +[wrap-file] +directory = googletest-1.14.0 +source_url = https://github.com/google/googletest/archive/refs/tags/v1.14.0.tar.gz +source_filename = gtest-1.14.0.tar.gz +source_hash = 8ad598c73ad796e0d8280b082cebd82a630d73e73cd3c70057938a6501bba5d7 +patch_filename = gtest_1.14.0-1_patch.zip +patch_url = https://wrapdb.mesonbuild.com/v2/gtest_1.14.0-1/get_patch +patch_hash = 2e693c7d3f9370a7aa6dac802bada0874d3198ad4cfdf75647b818f691182b50 +source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/gtest_1.14.0-1/gtest-1.14.0.tar.gz +wrapdb_version = 1.14.0-1 + +[provide] +gtest = gtest_dep +gtest_main = gtest_main_dep +gmock = gmock_dep +gmock_main = gmock_main_dep \ No newline at end of file diff --git a/tests/.gitkeep b/tests/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/tests/bitmap_test/bitmap_test.cpp b/tests/bitmap_test/bitmap_test.cpp new file mode 100644 index 0000000..9480ab2 --- /dev/null +++ b/tests/bitmap_test/bitmap_test.cpp @@ -0,0 +1,47 @@ +#include "graphics/Bitmap.hpp" + +#include + +#include + +TEST(basic_test, Bitmap_createBitmapWithCertainSize_yieldsSpecifiedSize) { + auto bm = ptprnt::graphics::Bitmap(16, 8); + auto width = bm.getWidth(); + auto height = bm.getHeight(); + ASSERT_EQ(width, 16); + ASSERT_EQ(height, 8); +} + +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); +} + +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(); + ASSERT_EQ(16, lineSize); +} + +TEST(basic_test, Bitmap_getBitmapRowOutsideOfImage_yieldsNullopt) { + auto bm = ptprnt::graphics::Bitmap(16, 8); + // row 16 is out of bounds, count begins with 0 + auto outOfBoundsRow = bm.getRow(16); + ASSERT_EQ(std::nullopt, outOfBoundsRow); +} + +TEST(basic_test, Bitmap_getBitmapRowInsideOfImage_yieldsValidRowSize) { + auto bm = ptprnt::graphics::Bitmap(16, 8); + auto row = bm.getRow(15); + if (!row) { + FAIL() << "Returned Row is invalid"; + } + auto rowSize = row->size(); + ASSERT_EQ(8, rowSize); +} diff --git a/tests/image_test/image_test.cpp b/tests/image_test/image_test.cpp new file mode 100644 index 0000000..24c1082 --- /dev/null +++ b/tests/image_test/image_test.cpp @@ -0,0 +1,9 @@ +#include "graphics/Image.hpp" + +#include + +#include + +TEST(basic_test, Image_smokeTest_succeeds) { + auto im = ptprnt::graphics::Image(); +} diff --git a/tests/meson.build b/tests/meson.build new file mode 100644 index 0000000..43857e3 --- /dev/null +++ b/tests/meson.build @@ -0,0 +1,14 @@ +tests = [['bitmap_test', 'bitmap_test_exe', ['bitmap_test/bitmap_test.cpp']], + ['image_test', 'image_test_exe', ['image_test/image_test.cpp']] + ] + +foreach test : tests + test(test.get(0), + executable(test.get(1), + sources: test.get(2), + include_directories: incdir, + link_with:[ptprnt_lib], + dependencies: [gtest_dep, pangocairo_dep] + ) + ) +endforeach \ No newline at end of file