SVG印刷対応とメディア情報APIを追加
SVG入力を現在テープ幅に自動フィットして印刷できるようにし、アプリ側が余白計算できるようにテープ幅・DPI・最小送り量を取得するAPIを追加する。 Made-with: Cursor
This commit is contained in:
@@ -21,14 +21,15 @@ static void usage(const char *argv0)
|
||||
" -f, --file PATH 入力ファイル\n"
|
||||
" -w, --width DOTS 1bit ラスター時: 幅(ドット)\n"
|
||||
" -H, --height DOTS 1bit ラスター時: 高さ(ドット)\n"
|
||||
" -t, --threshold N PNG 二値化しきい値 0–255(既定 %u、PNG のみ)\n"
|
||||
" -t, --threshold N 二値化しきい値 0–255(既定 %u、PNG/SVG)\n"
|
||||
" -n, --dry-run 読み込みと check_raster のみ(USB なし)\n"
|
||||
" -p, --pid HEX USB 製品 ID(既定: P900W の 0x2085)。例: P750W 0x2062、P710BT 0x20af\n"
|
||||
" -S, --status USB 接続プリンタのステータス(テープ種・幅・色等)を表示して終了\n"
|
||||
" -V, --version バージョンを表示して終了\n"
|
||||
" -h, --help このヘルプ\n"
|
||||
"\n"
|
||||
"PNG の場合は幅・高さはファイルから取得(-w/-H 不要)。\n"
|
||||
"PNG は幅・高さを画像から取得(-w/-H 不要)。\n"
|
||||
"SVG は現在テープの印字可能幅に合わせて自動拡大・縮小(USB 必須)。\n"
|
||||
"1bit packed ラスター(行優先)の場合は -f -w -H が必須。\n"
|
||||
"--status のときは -f は不要(他オプションは無視されます)。\n",
|
||||
argv0, (unsigned)LIBPTOUCH_PNG_DEFAULT_THRESHOLD);
|
||||
@@ -54,6 +55,14 @@ static int input_is_png(const char *path)
|
||||
return memcmp(sig, png_magic, sizeof(png_magic)) == 0;
|
||||
}
|
||||
|
||||
static int input_is_svg(const char *path)
|
||||
{
|
||||
const char *dot = strrchr(path, '.');
|
||||
if (dot && strcasecmp(dot, ".svg") == 0)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int read_file(const char *path, uint8_t **out, size_t *out_len)
|
||||
{
|
||||
FILE *fp = fopen(path, "rb");
|
||||
@@ -201,20 +210,21 @@ int main(int argc, char **argv)
|
||||
}
|
||||
|
||||
int png = input_is_png(file);
|
||||
if (png) {
|
||||
int svg = input_is_svg(file);
|
||||
if (png || svg) {
|
||||
if (width != 0 || height != 0)
|
||||
fprintf(stderr,
|
||||
"warning: -w/-H ignored for PNG (using image size)\n");
|
||||
"warning: -w/-H ignored for image inputs\n");
|
||||
} else {
|
||||
if (width == 0 || height == 0) {
|
||||
fprintf(stderr,
|
||||
"1bit raster requires -w and -H (or use a PNG file)\n");
|
||||
"1bit raster requires -w and -H (or use a PNG/SVG file)\n");
|
||||
usage(argv[0]);
|
||||
return 2;
|
||||
}
|
||||
if (has_threshold)
|
||||
fprintf(stderr,
|
||||
"warning: -t applies to PNG only (ignored)\n");
|
||||
"warning: -t applies to PNG/SVG only (ignored)\n");
|
||||
}
|
||||
|
||||
libptouch_ctx *ctx = libptouch_create();
|
||||
@@ -227,6 +237,7 @@ int main(int argc, char **argv)
|
||||
size_t data_len = 0;
|
||||
libptouch_raster_params_t params = { 0, 0, 0 };
|
||||
libptouch_err_t e;
|
||||
int usb_opened = 0;
|
||||
|
||||
if (png) {
|
||||
libptouch_png_options_t opt = { .threshold = (uint8_t)threshold };
|
||||
@@ -239,6 +250,28 @@ int main(int argc, char **argv)
|
||||
libptouch_destroy(ctx);
|
||||
return 1;
|
||||
}
|
||||
} else if (svg) {
|
||||
e = usb_pid_arg != 0
|
||||
? libptouch_open_usb_vid_pid(ctx, LIBPTOUCH_USB_VID_BROTHER,
|
||||
(uint16_t)usb_pid_arg)
|
||||
: libptouch_open_usb(ctx);
|
||||
if (e != LIBPTOUCH_OK) {
|
||||
fprintf(stderr, "open_usb: %s\n", libptouch_strerror(ctx));
|
||||
libptouch_destroy(ctx);
|
||||
return 1;
|
||||
}
|
||||
usb_opened = 1;
|
||||
libptouch_svg_options_t opt = { .threshold = (uint8_t)threshold };
|
||||
e = libptouch_svg_file_to_raster_fit_current_tape(
|
||||
ctx, file, has_threshold ? &opt : NULL, &data, &data_len,
|
||||
¶ms);
|
||||
if (e != LIBPTOUCH_OK) {
|
||||
fprintf(stderr, "svg_file_to_raster_fit_current_tape: %s\n",
|
||||
libptouch_strerror(ctx));
|
||||
libptouch_close(ctx);
|
||||
libptouch_destroy(ctx);
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
if (read_file(file, &data, &data_len) != 0) {
|
||||
libptouch_destroy(ctx);
|
||||
@@ -254,7 +287,7 @@ int main(int argc, char **argv)
|
||||
fprintf(stderr, "check_raster: %s\n",
|
||||
libptouch_strerror(ctx));
|
||||
libptouch_destroy(ctx);
|
||||
if (png)
|
||||
if (png || svg)
|
||||
libptouch_free_raster(data);
|
||||
else
|
||||
free(data);
|
||||
@@ -262,29 +295,36 @@ int main(int argc, char **argv)
|
||||
}
|
||||
|
||||
if (dry_run) {
|
||||
printf("dry-run OK: %zu bytes, %ux%u dots\n", data_len,
|
||||
printf("dry-run OK: %zu bytes, src %ux%u dots (print lengthxwidth %ux%u)\n",
|
||||
data_len,
|
||||
(unsigned)params.width_dots,
|
||||
(unsigned)params.height_dots,
|
||||
(unsigned)params.width_dots,
|
||||
(unsigned)params.height_dots);
|
||||
if (usb_opened)
|
||||
libptouch_close(ctx);
|
||||
libptouch_destroy(ctx);
|
||||
if (png)
|
||||
if (png || svg)
|
||||
libptouch_free_raster(data);
|
||||
else
|
||||
free(data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
e = usb_pid_arg != 0
|
||||
? libptouch_open_usb_vid_pid(ctx, LIBPTOUCH_USB_VID_BROTHER,
|
||||
(uint16_t)usb_pid_arg)
|
||||
: libptouch_open_usb(ctx);
|
||||
if (e != LIBPTOUCH_OK) {
|
||||
fprintf(stderr, "open_usb: %s\n", libptouch_strerror(ctx));
|
||||
libptouch_destroy(ctx);
|
||||
if (png)
|
||||
libptouch_free_raster(data);
|
||||
else
|
||||
free(data);
|
||||
return 1;
|
||||
if (!usb_opened) {
|
||||
e = usb_pid_arg != 0
|
||||
? libptouch_open_usb_vid_pid(ctx, LIBPTOUCH_USB_VID_BROTHER,
|
||||
(uint16_t)usb_pid_arg)
|
||||
: libptouch_open_usb(ctx);
|
||||
if (e != LIBPTOUCH_OK) {
|
||||
fprintf(stderr, "open_usb: %s\n", libptouch_strerror(ctx));
|
||||
libptouch_destroy(ctx);
|
||||
if (png || svg)
|
||||
libptouch_free_raster(data);
|
||||
else
|
||||
free(data);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
e = libptouch_print_raster(ctx, data, data_len, ¶ms);
|
||||
@@ -293,7 +333,7 @@ int main(int argc, char **argv)
|
||||
libptouch_strerror(ctx));
|
||||
libptouch_close(ctx);
|
||||
libptouch_destroy(ctx);
|
||||
if (png)
|
||||
if (png || svg)
|
||||
libptouch_free_raster(data);
|
||||
else
|
||||
free(data);
|
||||
@@ -302,7 +342,7 @@ int main(int argc, char **argv)
|
||||
|
||||
libptouch_close(ctx);
|
||||
libptouch_destroy(ctx);
|
||||
if (png)
|
||||
if (png || svg)
|
||||
libptouch_free_raster(data);
|
||||
else
|
||||
free(data);
|
||||
|
||||
100
src/lib/libptouch_media_info.c
Normal file
100
src/lib/libptouch_media_info.c
Normal file
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* libptouch — current tape/media information helper
|
||||
*
|
||||
* Author: knb
|
||||
* Email: knb@artif.org
|
||||
*/
|
||||
|
||||
#include "libptouch_internal.h"
|
||||
#include "libptouch_layout.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
static double tape_width_mm_from_code(uint8_t media_w)
|
||||
{
|
||||
switch (media_w) {
|
||||
case 0x04:
|
||||
return 3.5;
|
||||
case 0x06:
|
||||
return 6.0;
|
||||
case 0x09:
|
||||
return 9.0;
|
||||
case 0x0C:
|
||||
return 12.0;
|
||||
case 0x12:
|
||||
return 18.0;
|
||||
case 0x18:
|
||||
return 24.0;
|
||||
case 0x24:
|
||||
return 36.0;
|
||||
case 0x15:
|
||||
return 21.0; /* FLe width code */
|
||||
default:
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
static double print_dpi_from_profile(const ptouch_printer_profile_t *prof)
|
||||
{
|
||||
if (!prof)
|
||||
return 0.0;
|
||||
if (prof->head_width_dots <= 128u)
|
||||
return 180.0;
|
||||
return 360.0;
|
||||
}
|
||||
|
||||
libptouch_err_t libptouch_get_current_media_info(libptouch_ctx *ctx,
|
||||
libptouch_media_info_t *out_info)
|
||||
{
|
||||
if (!ctx || !out_info) {
|
||||
if (ctx)
|
||||
ptouch_set_error(ctx, LIBPTOUCH_ERR_ARG, "null argument");
|
||||
return LIBPTOUCH_ERR_ARG;
|
||||
}
|
||||
|
||||
memset(out_info, 0, sizeof(*out_info));
|
||||
|
||||
uint8_t st[LIBPTOUCH_STATUS_LENGTH];
|
||||
libptouch_err_t 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;
|
||||
}
|
||||
|
||||
uint16_t left_dots = 0, print_dots = 0, right_dots = 0;
|
||||
v = ptouch_layout_from_status(prof, st[11], st[10], &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");
|
||||
return LIBPTOUCH_ERR_UNSUPPORTED;
|
||||
}
|
||||
|
||||
double tape_mm = tape_width_mm_from_code(st[10]);
|
||||
if (tape_mm <= 0.0) {
|
||||
ptouch_set_error(ctx, LIBPTOUCH_ERR_UNSUPPORTED,
|
||||
"unknown tape width code");
|
||||
return LIBPTOUCH_ERR_UNSUPPORTED;
|
||||
}
|
||||
|
||||
out_info->media_width_code = st[10];
|
||||
out_info->media_kind_code = st[11];
|
||||
out_info->print_dpi = print_dpi_from_profile(prof);
|
||||
out_info->feed_dpi = prof->margin_feed_dpi;
|
||||
out_info->tape_width_mm = tape_mm;
|
||||
out_info->printable_dots = print_dots;
|
||||
out_info->left_margin_dots = left_dots;
|
||||
out_info->right_margin_dots = right_dots;
|
||||
out_info->min_feed_dots = 14u;
|
||||
out_info->min_feed_mm =
|
||||
((double)out_info->min_feed_dots * 25.4) / prof->margin_feed_dpi;
|
||||
|
||||
ptouch_set_error(ctx, LIBPTOUCH_OK, "");
|
||||
return LIBPTOUCH_OK;
|
||||
}
|
||||
211
src/lib/libptouch_svg.c
Normal file
211
src/lib/libptouch_svg.c
Normal file
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
* libptouch — SVG -> 1bit packed raster (librsvg + cairo)
|
||||
*
|
||||
* Author: knb
|
||||
* Email: knb@artif.org
|
||||
*/
|
||||
|
||||
#include "libptouch_internal.h"
|
||||
#include "libptouch_layout.h"
|
||||
|
||||
#ifdef LIBPTOUCH_HAS_RSVG
|
||||
#include <cairo.h>
|
||||
#include <librsvg/rsvg.h>
|
||||
#endif
|
||||
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
libptouch_err_t libptouch_svg_file_to_raster_fit_current_tape(
|
||||
libptouch_ctx *ctx, const char *path,
|
||||
const libptouch_svg_options_t *options, uint8_t **out_raster,
|
||||
size_t *out_raster_bytes, libptouch_raster_params_t *out_params)
|
||||
{
|
||||
#ifndef LIBPTOUCH_HAS_RSVG
|
||||
(void)path;
|
||||
(void)options;
|
||||
(void)out_raster;
|
||||
(void)out_raster_bytes;
|
||||
(void)out_params;
|
||||
if (ctx) {
|
||||
ptouch_set_error(ctx, LIBPTOUCH_ERR_UNSUPPORTED,
|
||||
"SVG support is not built in (missing librsvg-2.0)");
|
||||
}
|
||||
return LIBPTOUCH_ERR_UNSUPPORTED;
|
||||
#else
|
||||
if (!ctx || !path || !out_raster || !out_raster_bytes || !out_params) {
|
||||
if (ctx)
|
||||
ptouch_set_error(ctx, LIBPTOUCH_ERR_ARG, "null argument");
|
||||
return LIBPTOUCH_ERR_ARG;
|
||||
}
|
||||
if (!ctx->usb_open || !ctx->bulk_out_ep) {
|
||||
ptouch_set_error(ctx, LIBPTOUCH_ERR_IO,
|
||||
"not connected (open USB before SVG conversion)");
|
||||
return LIBPTOUCH_ERR_IO;
|
||||
}
|
||||
|
||||
uint8_t thr = LIBPTOUCH_PNG_DEFAULT_THRESHOLD;
|
||||
if (options)
|
||||
thr = options->threshold;
|
||||
|
||||
*out_raster = NULL;
|
||||
*out_raster_bytes = 0;
|
||||
out_params->width_dots = 0;
|
||||
out_params->height_dots = 0;
|
||||
out_params->margin_mm = 0;
|
||||
|
||||
uint8_t st[LIBPTOUCH_STATUS_LENGTH];
|
||||
libptouch_err_t 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;
|
||||
}
|
||||
|
||||
uint16_t left_dots = 0, print_dots = 0, right_dots = 0;
|
||||
v = ptouch_layout_from_status(prof, st[11], st[10], &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");
|
||||
return LIBPTOUCH_ERR_UNSUPPORTED;
|
||||
}
|
||||
(void)left_dots;
|
||||
(void)right_dots;
|
||||
|
||||
GError *gerr = NULL;
|
||||
RsvgHandle *handle = rsvg_handle_new_from_file(path, &gerr);
|
||||
if (!handle) {
|
||||
const char *msg = (gerr && gerr->message) ? gerr->message :
|
||||
"failed to load SVG";
|
||||
ptouch_set_error(ctx, LIBPTOUCH_ERR_IMAGE, msg);
|
||||
if (gerr)
|
||||
g_error_free(gerr);
|
||||
return LIBPTOUCH_ERR_IMAGE;
|
||||
}
|
||||
|
||||
double svg_w = 0.0;
|
||||
double svg_h = 0.0;
|
||||
if (!rsvg_handle_get_intrinsic_size_in_pixels(handle, &svg_w, &svg_h) ||
|
||||
svg_w <= 0.0 || svg_h <= 0.0) {
|
||||
g_object_unref(handle);
|
||||
ptouch_set_error(ctx, LIBPTOUCH_ERR_IMAGE,
|
||||
"invalid SVG dimensions");
|
||||
return LIBPTOUCH_ERR_IMAGE;
|
||||
}
|
||||
|
||||
/*
|
||||
* libptouch_print_raster() transposes the incoming raster before packing
|
||||
* lines, so printable tape width maps to the source raster height.
|
||||
*/
|
||||
double scale = (double)print_dots / svg_h;
|
||||
if (scale <= 0.0 || !isfinite(scale)) {
|
||||
g_object_unref(handle);
|
||||
ptouch_set_error(ctx, LIBPTOUCH_ERR_IMAGE, "invalid SVG scale");
|
||||
return LIBPTOUCH_ERR_IMAGE;
|
||||
}
|
||||
|
||||
uint32_t out_h = (uint32_t)print_dots;
|
||||
uint32_t out_w = (uint32_t)ceil(svg_w * scale);
|
||||
if (out_h == 0)
|
||||
out_h = 1;
|
||||
if (out_h > 100000u) {
|
||||
g_object_unref(handle);
|
||||
ptouch_set_error(ctx, LIBPTOUCH_ERR_IMAGE, "scaled SVG too tall");
|
||||
return LIBPTOUCH_ERR_IMAGE;
|
||||
}
|
||||
|
||||
cairo_surface_t *surface = cairo_image_surface_create(
|
||||
CAIRO_FORMAT_ARGB32, (int)out_w, (int)out_h);
|
||||
if (cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) {
|
||||
cairo_surface_destroy(surface);
|
||||
g_object_unref(handle);
|
||||
ptouch_set_error(ctx, LIBPTOUCH_ERR_NOMEM,
|
||||
"cairo_image_surface_create failed");
|
||||
return LIBPTOUCH_ERR_NOMEM;
|
||||
}
|
||||
|
||||
cairo_t *cr = cairo_create(surface);
|
||||
if (cairo_status(cr) != CAIRO_STATUS_SUCCESS) {
|
||||
cairo_destroy(cr);
|
||||
cairo_surface_destroy(surface);
|
||||
g_object_unref(handle);
|
||||
ptouch_set_error(ctx, LIBPTOUCH_ERR_NOMEM, "cairo_create failed");
|
||||
return LIBPTOUCH_ERR_NOMEM;
|
||||
}
|
||||
|
||||
cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
|
||||
cairo_paint(cr);
|
||||
RsvgRectangle viewport = { 0.0, 0.0, (double)out_w, (double)out_h };
|
||||
GError *render_err = NULL;
|
||||
if (!rsvg_handle_render_document(handle, cr, &viewport, &render_err)) {
|
||||
cairo_destroy(cr);
|
||||
cairo_surface_destroy(surface);
|
||||
g_object_unref(handle);
|
||||
const char *msg = (render_err && render_err->message) ?
|
||||
render_err->message :
|
||||
"SVG render failed";
|
||||
ptouch_set_error(ctx, LIBPTOUCH_ERR_IMAGE, msg);
|
||||
if (render_err)
|
||||
g_error_free(render_err);
|
||||
return LIBPTOUCH_ERR_IMAGE;
|
||||
}
|
||||
cairo_destroy(cr);
|
||||
g_object_unref(handle);
|
||||
|
||||
cairo_surface_flush(surface);
|
||||
int stride = cairo_image_surface_get_stride(surface);
|
||||
const uint8_t *pix = cairo_image_surface_get_data(surface);
|
||||
|
||||
size_t row_bytes = ((size_t)out_w + 7u) / 8u;
|
||||
if (out_h > SIZE_MAX / row_bytes) {
|
||||
cairo_surface_destroy(surface);
|
||||
ptouch_set_error(ctx, LIBPTOUCH_ERR_IMAGE, "raster size overflow");
|
||||
return LIBPTOUCH_ERR_IMAGE;
|
||||
}
|
||||
size_t total = row_bytes * (size_t)out_h;
|
||||
uint8_t *out = (uint8_t *)calloc(1, total);
|
||||
if (!out) {
|
||||
cairo_surface_destroy(surface);
|
||||
ptouch_set_error(ctx, LIBPTOUCH_ERR_NOMEM, "calloc raster failed");
|
||||
return LIBPTOUCH_ERR_NOMEM;
|
||||
}
|
||||
|
||||
for (uint32_t y = 0; y < out_h; y++) {
|
||||
const uint32_t *src_row =
|
||||
(const uint32_t *)(pix + (size_t)y * (size_t)stride);
|
||||
uint8_t *dst_row = out + (size_t)y * row_bytes;
|
||||
for (uint32_t x = 0; x < out_w; x++) {
|
||||
uint32_t p = src_row[x];
|
||||
unsigned a = (p >> 24) & 0xFFu;
|
||||
if (a < 128u)
|
||||
continue;
|
||||
unsigned r = (p >> 16) & 0xFFu;
|
||||
unsigned g = (p >> 8) & 0xFFu;
|
||||
unsigned b = p & 0xFFu;
|
||||
unsigned yv = (77u * r + 150u * g + 29u * b) >> 8;
|
||||
if (yv >= (unsigned)thr)
|
||||
continue;
|
||||
size_t byte = (size_t)x / 8u;
|
||||
size_t bit = (size_t)x % 8u;
|
||||
dst_row[byte] |= (uint8_t)(1u << (7u - bit));
|
||||
}
|
||||
}
|
||||
|
||||
cairo_surface_destroy(surface);
|
||||
|
||||
out_params->width_dots = out_w;
|
||||
out_params->height_dots = out_h;
|
||||
out_params->margin_mm = 0;
|
||||
*out_raster = out;
|
||||
*out_raster_bytes = total;
|
||||
ptouch_set_error(ctx, LIBPTOUCH_OK, "");
|
||||
return LIBPTOUCH_OK;
|
||||
#endif
|
||||
}
|
||||
Reference in New Issue
Block a user