From 604dcece4f3f2012fb1b47dbc89010b2195f7795 Mon Sep 17 00:00:00 2001 From: Moritz Martinius Date: Sun, 28 Apr 2024 17:37:09 +0200 Subject: [PATCH] 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/interface/IPrinterDriver.hpp | 5 -- src/interface/IPrinterTypes.hpp | 23 -------- 7 files changed, 183 insertions(+), 110 deletions(-) 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/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