/* 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 . */ #include "P700Printer.hpp" #include #include #include #include #include #include #include #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 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 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& 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 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 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 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 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& 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(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 cmd(102); cmd[100] = 0x1b; /* ESC */ cmd[101] = 0x40; /* @ */ return send(cmd); } } // namespace ptprnt::printer