Align raster protocol bytes and print-end sequencing by printer family, add safer status polling/retry behavior, and document the changes with regression coverage to prevent protocol regressions. Made-with: Cursor
252 lines
6.8 KiB
C
252 lines
6.8 KiB
C
/*
|
|
* libptouch — raster layout, transpose, P-touch print job (ESC/P bulk)
|
|
*
|
|
* Author: knb
|
|
* Email: knb@artif.org
|
|
*/
|
|
|
|
#include "libptouch_internal.h"
|
|
#include "libptouch_layout.h"
|
|
#include "libptouch_protocol.h"
|
|
|
|
#include <libusb.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
static void pack_line(uint8_t *line, size_t line_bytes, unsigned head_dots,
|
|
const uint8_t *row, uint32_t width_dots, uint16_t left_dots,
|
|
uint16_t print_dots)
|
|
{
|
|
memset(line, 0, line_bytes);
|
|
if (width_dots > (uint32_t)print_dots)
|
|
return;
|
|
uint32_t start = (uint32_t)left_dots +
|
|
((uint32_t)print_dots - width_dots) / 2u;
|
|
for (uint32_t x = 0; x < width_dots; x++) {
|
|
uint32_t ubyte = x / 8u;
|
|
uint32_t ubit = 7u - (x % 8u);
|
|
if (((row[ubyte] >> ubit) & 1u) == 0)
|
|
continue;
|
|
uint32_t dot = start + x;
|
|
if (dot >= (uint32_t)head_dots)
|
|
break;
|
|
uint32_t b = dot / 8u;
|
|
uint32_t bi = 7u - (dot % 8u);
|
|
line[b] |= (uint8_t)(1u << bi);
|
|
}
|
|
}
|
|
|
|
static uint8_t *transpose_raster_alloc(const uint8_t *in, uint32_t W, uint32_t H,
|
|
uint32_t *outW, uint32_t *outH)
|
|
{
|
|
*outW = H;
|
|
*outH = W;
|
|
size_t old_rb = (W + 7u) / 8u;
|
|
size_t new_rb = (H + 7u) / 8u;
|
|
uint8_t *out = (uint8_t *)calloc(new_rb * W, 1);
|
|
if (!out)
|
|
return NULL;
|
|
for (uint32_t y = 0; y < H; y++) {
|
|
for (uint32_t x = 0; x < W; x++) {
|
|
size_t ob = (size_t)y * old_rb + x / 8u;
|
|
int bit = (int)((in[ob] >> (7u - x % 8u)) & 1u);
|
|
if (!bit)
|
|
continue;
|
|
uint32_t nx = y;
|
|
uint32_t ny = x;
|
|
size_t nb = (size_t)ny * new_rb + nx / 8u;
|
|
out[nb] |= (uint8_t)(1u << (7u - nx % 8u));
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
libptouch_err_t libptouch_print_raster(libptouch_ctx *ctx,
|
|
const uint8_t *data, size_t data_len,
|
|
const libptouch_raster_params_t *params)
|
|
{
|
|
libptouch_err_t v =
|
|
libptouch_check_raster(ctx, data, data_len, params);
|
|
if (v != LIBPTOUCH_OK)
|
|
return v;
|
|
|
|
if (!ctx->usb_open || !ctx->bulk_out_ep) {
|
|
ptouch_set_error(ctx, LIBPTOUCH_ERR_IO, "not connected");
|
|
return LIBPTOUCH_ERR_IO;
|
|
}
|
|
|
|
uint8_t st[LIBPTOUCH_STATUS_LENGTH];
|
|
v = libptouch_get_status(ctx, st);
|
|
if (v != LIBPTOUCH_OK)
|
|
return v;
|
|
|
|
const ptouch_printer_profile_t *prof =
|
|
ptouch_layout_resolve_profile(ctx->usb_pid, st[4]);
|
|
if (!prof) {
|
|
ptouch_set_error(ctx, LIBPTOUCH_ERR_UNSUPPORTED,
|
|
"no layout profile for this printer");
|
|
return LIBPTOUCH_ERR_UNSUPPORTED;
|
|
}
|
|
|
|
uint8_t media_kind = st[11];
|
|
uint8_t media_w = st[10];
|
|
uint16_t left_dots, print_dots, right_dots;
|
|
v = ptouch_layout_from_status(prof, media_kind, media_w, &left_dots,
|
|
&print_dots, &right_dots);
|
|
if (v != LIBPTOUCH_OK) {
|
|
ptouch_set_error(ctx, LIBPTOUCH_ERR_UNSUPPORTED,
|
|
"tape width/layout not supported for this media "
|
|
"(check status media bytes)");
|
|
return LIBPTOUCH_ERR_UNSUPPORTED;
|
|
}
|
|
(void)right_dots;
|
|
|
|
unsigned head_dots = prof->head_width_dots;
|
|
size_t line_payload = ptouch_line_payload_bytes(head_dots);
|
|
size_t gf_packet = 3u + line_payload;
|
|
|
|
uint32_t wd = params->width_dots;
|
|
uint32_t ht = params->height_dots;
|
|
uint8_t *transposed = transpose_raster_alloc(data, wd, ht, &wd, &ht);
|
|
if (!transposed) {
|
|
ptouch_set_error(ctx, LIBPTOUCH_ERR_NOMEM,
|
|
"transpose raster: out of memory");
|
|
return LIBPTOUCH_ERR_NOMEM;
|
|
}
|
|
const uint8_t *src = transposed;
|
|
|
|
if (wd > (uint32_t)print_dots) {
|
|
char buf[160];
|
|
snprintf(buf, sizeof(buf),
|
|
"image width %u dots > printable %u for loaded tape",
|
|
(unsigned)wd, (unsigned)print_dots);
|
|
ptouch_set_error(ctx, LIBPTOUCH_ERR_ARG, buf);
|
|
free(transposed);
|
|
return LIBPTOUCH_ERR_ARG;
|
|
}
|
|
|
|
double margin_dpi = prof->margin_feed_dpi;
|
|
unsigned margin_max = prof->margin_feed_max_dots;
|
|
unsigned margin_dots = 14u;
|
|
if (params->margin_mm > 0) {
|
|
margin_dots = (unsigned)((double)params->margin_mm * margin_dpi /
|
|
25.4 +
|
|
0.5);
|
|
if (margin_dots < 14u)
|
|
margin_dots = 14u;
|
|
if (margin_dots > margin_max)
|
|
margin_dots = margin_max;
|
|
}
|
|
|
|
uint32_t lines = ht;
|
|
uint8_t head[256];
|
|
size_t pos = 0;
|
|
memset(head + pos, 0, 200);
|
|
pos += 200u;
|
|
static const uint8_t esc_at[] = { 0x1B, 0x40 };
|
|
memcpy(head + pos, esc_at, sizeof(esc_at));
|
|
pos += sizeof(esc_at);
|
|
static const uint8_t raster_mode[] = { 0x1B, 0x69, 0x61, 0x01 };
|
|
memcpy(head + pos, raster_mode, sizeof(raster_mode));
|
|
pos += sizeof(raster_mode);
|
|
|
|
uint8_t esc_iz[13];
|
|
ptouch_fill_esc_iz(esc_iz, media_kind, media_w, lines);
|
|
memcpy(head + pos, esc_iz, sizeof(esc_iz));
|
|
pos += sizeof(esc_iz);
|
|
|
|
static const uint8_t esc_im[] = { 0x1B, 0x69, 0x4D, 0x40 };
|
|
memcpy(head + pos, esc_im, sizeof(esc_im));
|
|
pos += sizeof(esc_im);
|
|
|
|
/*
|
|
* ESC i A ("cut each n labels") is not supported on PT-P710BT class
|
|
* devices. Sending it can trigger a communication error (red LED blink).
|
|
* Keep it only for 560-dot family.
|
|
*/
|
|
if (prof->head_width_dots > 128u) {
|
|
static const uint8_t esc_ia[] = { 0x1B, 0x69, 0x41, 0x01 };
|
|
memcpy(head + pos, esc_ia, sizeof(esc_ia));
|
|
pos += sizeof(esc_ia);
|
|
}
|
|
|
|
uint8_t esc_ik[] = { 0x1B, 0x69, 0x4B, ptouch_esc_ik_value(head_dots) };
|
|
memcpy(head + pos, esc_ik, sizeof(esc_ik));
|
|
pos += sizeof(esc_ik);
|
|
|
|
uint8_t esc_id[] = {
|
|
0x1B, 0x69, 0x64,
|
|
(uint8_t)(margin_dots & 0xFFu),
|
|
(uint8_t)((margin_dots >> 8) & 0xFFu)
|
|
};
|
|
memcpy(head + pos, esc_id, sizeof(esc_id));
|
|
pos += sizeof(esc_id);
|
|
|
|
static const uint8_t mode_m[] = { 0x4D, 0x00 };
|
|
memcpy(head + pos, mode_m, sizeof(mode_m));
|
|
pos += sizeof(mode_m);
|
|
|
|
v = ptouch_bulk_send_job(ctx, head, pos, "print preamble");
|
|
if (v != LIBPTOUCH_OK) {
|
|
free(transposed);
|
|
return v;
|
|
}
|
|
|
|
size_t row_b = ((size_t)wd + 7u) / 8u;
|
|
|
|
uint8_t *gbuf = (uint8_t *)malloc(gf_packet);
|
|
if (!gbuf) {
|
|
ptouch_set_error(ctx, LIBPTOUCH_ERR_NOMEM,
|
|
"raster line buffer");
|
|
free(transposed);
|
|
return LIBPTOUCH_ERR_NOMEM;
|
|
}
|
|
|
|
for (uint32_t y = 0; y < lines; y++) {
|
|
const uint8_t *row = src + (size_t)y * row_b;
|
|
pack_line(gbuf + 3, line_payload, head_dots, row, wd, left_dots,
|
|
print_dots);
|
|
ptouch_fill_gf_header(gbuf, line_payload);
|
|
v = ptouch_bulk_send_job(ctx, gbuf, gf_packet, "raster line");
|
|
if (v != LIBPTOUCH_OK) {
|
|
free(gbuf);
|
|
free(transposed);
|
|
return v;
|
|
}
|
|
}
|
|
free(gbuf);
|
|
|
|
/*
|
|
* Some 560-dot family devices are sensitive to end-of-page sequencing.
|
|
* Send FF (0x0C) then Control-Z (0x1A) on that family to explicitly
|
|
* terminate page and final feed/cut.
|
|
*/
|
|
if (head_dots > 128u) {
|
|
static const uint8_t page_end[] = { 0x0C };
|
|
v = ptouch_bulk_send_job(ctx, page_end, sizeof(page_end),
|
|
"print page end");
|
|
if (v != LIBPTOUCH_OK) {
|
|
free(transposed);
|
|
return v;
|
|
}
|
|
}
|
|
|
|
static const uint8_t print_end[] = { 0x1A };
|
|
v = ptouch_bulk_send_job(ctx, print_end, sizeof(print_end), "print end");
|
|
if (v != LIBPTOUCH_OK) {
|
|
free(transposed);
|
|
return v;
|
|
}
|
|
|
|
uint8_t sink[64];
|
|
int tr = 0;
|
|
(void)libusb_bulk_transfer(ctx->usb_handle, ctx->bulk_in_ep, sink,
|
|
(int)sizeof(sink), &tr, 3000);
|
|
(void)tr;
|
|
|
|
free(transposed);
|
|
ptouch_set_error(ctx, LIBPTOUCH_OK, "");
|
|
return LIBPTOUCH_OK;
|
|
}
|