All checks were successful
Build ptprnt / build (push) Successful in 3m41s
Goal of this PR is to have some basic labels generated with pangocairo - size of the canvas should be matching the input text and grow/shrink accordingly - basic formatting options like fontsize and align should be working Reviewed-on: moritz/ptouch-prnt#8
239 lines
7.4 KiB
C++
239 lines
7.4 KiB
C++
/*
|
||
ptrnt - print labels on linux
|
||
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
|
||
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 <https://www.gnu.org/licenses/>.
|
||
|
||
*/
|
||
|
||
#include "P700Printer.hpp"
|
||
|
||
#include <spdlog/spdlog.h>
|
||
|
||
#include <cassert>
|
||
#include <cstdint>
|
||
#include <iterator>
|
||
#include <memory>
|
||
#include <thread>
|
||
#include <vector>
|
||
|
||
#include "../graphics/Bitmap.hpp"
|
||
#include "../graphics/Monochrome.hpp"
|
||
#include "../libusbwrap/LibUsbTypes.hpp"
|
||
#include "spdlog/fmt/bin_to_hex.h"
|
||
|
||
namespace ptprnt::printer {
|
||
|
||
const PrinterInfo P700Printer::mInfo = {.driverName = "P700",
|
||
.name = "Brother P-touch P700",
|
||
.version = "v1.0",
|
||
.usbId{0x04f9, 0x2061},
|
||
.pixelLines = 128};
|
||
|
||
P700Printer::~P700Printer() {
|
||
P700Printer::detachUsbDevice();
|
||
if (mUsbHndl) {
|
||
mUsbHndl->close();
|
||
}
|
||
}
|
||
|
||
const std::string_view P700Printer::getDriverName() {
|
||
return mInfo.driverName;
|
||
}
|
||
|
||
const std::string_view P700Printer::getName() {
|
||
return mInfo.name;
|
||
}
|
||
|
||
const std::string_view P700Printer::getVersion() {
|
||
return mInfo.version;
|
||
}
|
||
|
||
const PrinterInfo P700Printer::getPrinterInfo() {
|
||
return mInfo;
|
||
}
|
||
|
||
const PrinterStatus P700Printer::getPrinterStatus() {
|
||
using namespace std::chrono_literals;
|
||
send(p700::commands::GET_STATUS);
|
||
|
||
int tx = 0;
|
||
int tries = 0;
|
||
std::vector<uint8_t> recvBuf(32);
|
||
while (tries++ < MAX_TRIES_GET_STATUS) {
|
||
std::this_thread::sleep_for(100ms);
|
||
mUsbHndl->bulkTransfer(p700::commands::PRINTER_INFO[0], recvBuf, &tx, 0);
|
||
}
|
||
|
||
return PrinterStatus{.tapeWidthMm = recvBuf[10]};
|
||
}
|
||
|
||
const libusbwrap::usbId P700Printer::getUsbId() {
|
||
return mInfo.usbId;
|
||
}
|
||
|
||
bool P700Printer::attachUsbDevice(std::shared_ptr<libusbwrap::IUsbDevice> usbHndl) {
|
||
if (!usbHndl->open()) {
|
||
spdlog::error("Unable to open USB device: {}", usbHndl->getLastErrorString());
|
||
return false;
|
||
}
|
||
|
||
if (!usbHndl->detachKernelDriver(0)) {
|
||
|
||
spdlog::error("Device is already in use or couldn't be detached from kernel: {}",
|
||
usbHndl->getLastErrorString());
|
||
return false;
|
||
}
|
||
|
||
if (!usbHndl->claimInterface(0)) {
|
||
spdlog::error("Could not claim interface 0: {}", usbHndl->getLastErrorString());
|
||
return false;
|
||
}
|
||
|
||
mUsbHndl = std::move(usbHndl);
|
||
return true;
|
||
}
|
||
|
||
bool P700Printer::detachUsbDevice() {
|
||
if (!mUsbHndl) {
|
||
spdlog::warn("No device to detach...");
|
||
return true;
|
||
}
|
||
if (!mUsbHndl->releaseInterface(0)) {
|
||
spdlog::error("Could not release interface 0: {}", mUsbHndl->getLastErrorString());
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
bool P700Printer::printBitmap(const graphics::Bitmap<graphics::ALPHA8>& 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.get();
|
||
|
||
return printMonochromeData(monoData);
|
||
}
|
||
|
||
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<uint8_t> initCmd(128, 0x00);
|
||
initCmd[126] = p700::commands::INITIALIZE[0]; // ESC
|
||
initCmd[127] = p700::commands::INITIALIZE[1]; // @
|
||
send(initCmd);
|
||
|
||
// Status is already queried in getPrinterStatus()
|
||
send(p700::commands::PRINT_MODE);
|
||
send(p700::commands::AUTO_STATUS);
|
||
send(p700::commands::MODE_SETTING);
|
||
|
||
// 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<uint8_t> rowData;
|
||
|
||
// 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 && (col + bit) < data.width; bit++) {
|
||
if (data.getBit(col + bit, row)) {
|
||
byte |= (1 << (7 - bit));
|
||
}
|
||
}
|
||
rowData.push_back(byte);
|
||
}
|
||
|
||
// Build raster line command: G + length_high + 0x00 + length_low + data
|
||
std::vector<uint8_t> buf;
|
||
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 raster line {} to printer", row);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// Send print finalization commands
|
||
send(p700::commands::EJECT);
|
||
|
||
return true;
|
||
}
|
||
|
||
bool P700Printer::printLabel(std::unique_ptr<graphics::ILabel> label) {
|
||
// Convert label directly to MonochromeData
|
||
// getRaw() returns data in Cairo surface coordinates matching getWidth() × getHeight()
|
||
auto pixels = label->getRaw();
|
||
|
||
// 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 was {}x{}, after transform to portrait: {}x{}", label->getWidth(), label->getHeight(),
|
||
monoData.width, monoData.height);
|
||
|
||
return printMonochromeData(monoData);
|
||
}
|
||
|
||
bool P700Printer::print() {
|
||
send(p700::commands::LF);
|
||
send(p700::commands::FF);
|
||
send(p700::commands::EJECT);
|
||
return true;
|
||
}
|
||
|
||
bool P700Printer::send(const std::vector<uint8_t>& data) {
|
||
|
||
if (mUsbHndl == nullptr || data.size() > 128) {
|
||
spdlog::error("Invalid device handle or invalid 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)) {
|
||
spdlog::error("Error writing command to Printer: {}", mUsbHndl->getLastErrorString());
|
||
return false;
|
||
}
|
||
|
||
assert(tx > 0);
|
||
if (static_cast<std::uint32_t>(tx) != data.size()) {
|
||
spdlog::error("Could not transfer all data via USB bulk transfer. Only sent {} of {} bytes", tx, data.size());
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
#endif
|
||
}
|
||
|
||
bool P700Printer::init() {
|
||
std::vector<uint8_t> cmd(102);
|
||
cmd[100] = 0x1b; /* ESC */
|
||
cmd[101] = 0x40; /* @ */
|
||
return send(cmd);
|
||
}
|
||
} // namespace ptprnt::printer
|