diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..be1630e --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +build/ + +*.o +*.a +ptouch-print +*.so +*.dylib diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..78a6e0c --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,47 @@ +# ptouch_label — build (libptouch, ptouch-print) +# +# Author: knb +# Email: knb@artif.org + +cmake_minimum_required(VERSION 3.16) +project(ptouch_label VERSION 1.0.0 LANGUAGES C) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) + +include(GNUInstallDirs) + +find_package(PkgConfig REQUIRED) +pkg_check_modules(LIBUSB REQUIRED IMPORTED_TARGET libusb-1.0) +find_package(PNG REQUIRED) + +configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/include/libptouch_version.h.in" + "${CMAKE_CURRENT_BINARY_DIR}/include/libptouch_version.h" + @ONLY +) + +add_library(ptouch STATIC src/libptouch.c) +target_include_directories(ptouch PUBLIC + "$" + "$" + "$" +) +target_link_libraries(ptouch PRIVATE PkgConfig::LIBUSB PNG::PNG) +if(NOT MSVC) + target_compile_options(ptouch PRIVATE -Wall -Wextra -Wpedantic) +endif() + +add_executable(ptouch-print src/cli/main.c) +target_include_directories(ptouch-print PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include") +target_link_libraries(ptouch-print PRIVATE ptouch PkgConfig::LIBUSB) +if(NOT MSVC) + target_compile_options(ptouch-print PRIVATE -Wall -Wextra -Wpedantic) +endif() + +install(TARGETS ptouch ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}") +install(TARGETS ptouch-print RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}") +install(FILES + "${CMAKE_CURRENT_SOURCE_DIR}/include/libptouch.h" + "${CMAKE_CURRENT_BINARY_DIR}/include/libptouch_version.h" + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e7d4932 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 ptouch_label contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index e69de29..d2f554e 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,96 @@ +# ptouch_label + +**バージョン 1.0.0**(初回リリース) + +Brother P-touch シリーズ向けのラベル印刷用 **C コアライブラリ(libptouch)** と、動作確認用 **CLI(`ptouch-print`)** のリポジトリです。 + +保有機種: **PT-P900W**(USB・ラスターコマンド)。 + +## レイアウト + +| パス | 内容 | +|------|------| +| `include/libptouch.h` | 公開 API | +| `src/libptouch.c` | ライブラリ本体(スタブ) | +| `src/cli/main.c` | `ptouch-print` エントリ | +| `samples/` | 試験用サンプル画像の置き場(PNG 等) | +| `reference/` | 仕様・参考資料(例: ラスター PDF) | + +## ビルド + +依存: **CMake 3.16+**、**libusb-1.0**、**libpng**(開発パッケージ例: `libusb-1.0-0-dev`、`libpng-dev`)。 + +```bash +cmake -S . -B build +cmake --build build +``` + +成果物(`build/` 以下): + +- `libptouch.a` — 静的ライブラリ +- `ptouch-print` — CLI + +## CLI の使い方(雛形) + +**PNG**(拡張子 `.png` または PNG シグネチャ)の場合は幅・高さは画像から取得します。任意で `-t`(0–255)で二値化しきい値を指定できます。 + +**1bit packed ラスター**(行優先、行あたり `ceil(width/8)` バイト × 行数)の場合は `-f` に加え `-w` / `-H` が必須です。 + +印刷時、`libptouch_print_raster` は内部でラスターを**転置**してから送ります(`-T` / `-M` などのオプションはありません。鏡像のみの変換は行いません)。 + +```bash +# PNG — 検証のみ(USB 不要) +./build/ptouch-print -n -f label.png + +# PNG — しきい値を指定 +./build/ptouch-print -n -f label.png -t 160 + +# 1bit ラスター — 検証のみ +./build/ptouch-print -n -f sample.raster -w 128 -H 64 + +# プリンタステータス(テープ幅・種類・色・エラービット等、PDF の 32 バイト応答) +./build/ptouch-print --status + +# USB 接続時 +./build/ptouch-print -f label.png +./build/ptouch-print -f sample.raster -w 128 -H 64 +``` + +`-n`(`--dry-run`)では読み込みと `libptouch_check_raster` まで実行します。 + +## libptouch API(概要) + +- バージョン — `LIBPTOUCH_VERSION_MAJOR` / `MINOR` / `PATCH` / `LIBPTOUCH_VERSION_STRING`(`libptouch_version.h`、CMake の `project(VERSION …)` と同期) +- `libptouch_create` / `libptouch_destroy` — コンテキスト +- `libptouch_open_usb` / `libptouch_open_usb_vid_pid` / `libptouch_close` — USB(libusb・VID/PID) +- `libptouch_get_status` / `libptouch_status_fprint` — ステータス情報リクエスト(ESC i S)の応答 +- `libptouch_check_raster` — ラスターバッファの検証のみ +- `libptouch_png_file_to_raster` / `libptouch_free_raster` — PNG を 1bit ラスターに変換(libpng) +- `libptouch_print_raster` — ラスター印刷(USB・PDF のコマンド列、内部で転置) +- `libptouch_strerror` / `libptouch_last_error` — エラー情報 + +ラスター形式は `include/libptouch.h` のコメントに合わせてください。 + +## PT-P900W / Linux でのメモ + +- 接続は **libusb-1.0** のみ。機種ごとに **VID/PID**(`lsusb` 等)を調べ、`libptouch_open_usb_vid_pid` に渡すか、既定の PT-P900W なら `libptouch_open_usb` を使います。 +- ラスターコマンドの詳細は **`reference/` の PDF** および Brother 公開資料に沿って `src/libptouch.c` に実装してください。 + +### Ubuntu で sudo なしで USB を開く(udev) + +既定の **04f9:2085**(PT-P900W)向けルールを `udev/99-ptouch-label-brother.rules` に置いています。 + +```bash +sudo cp udev/99-ptouch-label-brother.rules /etc/udev/rules.d/ +sudo udevadm control --reload-rules +sudo udevadm trigger +sudo usermod -aG plugdev "$USER" +``` + +**再ログイン**するか、端末で `newgrp plugdev` のあと試すか、USB を**抜き差し**してください。`groups` に `plugdev` が含まれていれば、`./build/ptouch-print --status` などを sudo なしで実行できます。 + +別の Brother 機種(別 PID)のときは、`lsusb` の ID に合わせて同ファイルに行を追加してください。 + +## ライセンス + +[MIT License](LICENSE)(`LICENSE` ファイルを参照)。 diff --git a/aB.png b/aB.png new file mode 100644 index 0000000..d32ac9f Binary files /dev/null and b/aB.png differ diff --git a/include/libptouch.h b/include/libptouch.h new file mode 100644 index 0000000..7e82748 --- /dev/null +++ b/include/libptouch.h @@ -0,0 +1,128 @@ +/* + * libptouch.h — public C API + * + * Author: knb + * Email: knb@artif.org + */ + +/** + * libptouch — Brother P-touch ラスター印刷 (USB) 用 C API + * + * 対象例: PT-P900W(ラスターコマンド)。実装は src/libptouch.c を参照。 + */ + +#ifndef LIBPTOUCH_H +#define LIBPTOUCH_H + +#include "libptouch_version.h" + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** 不透明コンテキスト(接続状態・最終エラー文字列を保持) */ +typedef struct libptouch_ctx libptouch_ctx; + +typedef enum { + LIBPTOUCH_OK = 0, + LIBPTOUCH_ERR_NOMEM = 1, + LIBPTOUCH_ERR_ARG = 2, + LIBPTOUCH_ERR_USB = 3, + LIBPTOUCH_ERR_IO = 4, + LIBPTOUCH_ERR_UNSUPPORTED = 5, + LIBPTOUCH_ERR_NOT_FOUND = 6, + LIBPTOUCH_ERR_IMAGE = 7, +} libptouch_err_t; + +/** lsusb 例: PT-P900W — Brother Industries, Ltd (04f9:2085) */ +#define LIBPTOUCH_USB_VID_BROTHER 0x04f9u +#define LIBPTOUCH_USB_PID_PTP900W 0x2085u + +libptouch_ctx *libptouch_create(void); +void libptouch_destroy(libptouch_ctx *ctx); + +/** 人が読める英語メッセージ(スレッド非安全: ctx ごと) */ +const char *libptouch_strerror(const libptouch_ctx *ctx); +libptouch_err_t libptouch_last_error(const libptouch_ctx *ctx); + +/** + * USB でプリンタを開く(VID/PID で先頭の一致デバイスを開く。libusb のみ)。 + */ +libptouch_err_t libptouch_open_usb_vid_pid(libptouch_ctx *ctx, uint16_t vid, + uint16_t pid); + +/** + * 既定機種(@ref LIBPTOUCH_USB_VID_BROTHER / @ref LIBPTOUCH_USB_PID_PTP900W)を開く。 + * 別機種は @ref libptouch_open_usb_vid_pid に機種ごとの VID/PID を渡す。 + */ +libptouch_err_t libptouch_open_usb(libptouch_ctx *ctx); + +void libptouch_close(libptouch_ctx *ctx); + +typedef struct { + uint32_t width_dots; /**< ラスター幅(ドット) */ + uint32_t height_dots; /**< ラスター高さ(ドット・走査方向は実装と機種に依存) */ + uint8_t margin_mm; /**< 余白など(機種・仕様に合わせて使用) */ +} libptouch_raster_params_t; + +/** + * バッファサイズとパラメータの整合性のみ検査(USB 不要)。--dry-run 用。 + */ +libptouch_err_t libptouch_check_raster(libptouch_ctx *ctx, + const uint8_t *data, size_t data_len, + const libptouch_raster_params_t *params); + +/** + * 1 ビット packed ラスターを USB で印刷(cv_ptp900_jpn_raster_102.pdf 準拠)。 + * 印字前にステータスでテープ幅を読み、印刷可能ドット内に画像を中央配置する。 + * width_dots は装着テープの印刷可能幅以下であること。360dpi 相当のドット列を想定。 + * @param margin_mm 余白(フィード)量。0 のとき PDF の最小 1mm(14 ドット)相当を送る。 + * 印刷時は内部でドット列を転置する(テープ幅方向とバッファの縦横の対応)。 + * @param data 1 行あたり width_dots ビットを ceil(width_dots/8) バイトで並べた連続領域 + * @param data_len 期待値: height * row_bytes, row_bytes = (width_dots + 7) / 8 + */ +libptouch_err_t libptouch_print_raster(libptouch_ctx *ctx, + const uint8_t *data, size_t data_len, + const libptouch_raster_params_t *params); + +/** 二値化の既定しきい値(輝度 0–255、これ未満を黒ドット) */ +#define LIBPTOUCH_PNG_DEFAULT_THRESHOLD 128u + +typedef struct { + uint8_t threshold; /**< 輝度がこれ未満なら黒(1)、以上なら白(0) */ +} libptouch_png_options_t; + +/** + * PNG ファイルを読み、1bit packed ラスターに変換する(libpng)。 + * @param out_raster malloc 済みバッファのポインタを返す。不要時は @ref libptouch_free_raster で解放。 + */ +libptouch_err_t libptouch_png_file_to_raster(libptouch_ctx *ctx, const char *path, + const libptouch_png_options_t *options, + uint8_t **out_raster, size_t *out_raster_bytes, + libptouch_raster_params_t *out_params); + +void libptouch_free_raster(uint8_t *raster); + +/** ステータス情報リクエスト(ESC i S)の応答バイト数(cv_ptp900_jpn_raster_102.pdf) */ +#define LIBPTOUCH_STATUS_LENGTH 32u + +/** + * プリンタからステータスを読む(USB オープン済みであること)。 + * 送信: 初期化 ESC @ のあと ESC i S。応答は @ref LIBPTOUCH_STATUS_LENGTH バイト固定。 + */ +libptouch_err_t libptouch_get_status(libptouch_ctx *ctx, + uint8_t *status /* length @ref LIBPTOUCH_STATUS_LENGTH */); + +/** ステータス 32 バイトを人が読める形で出力(テープ幅・種類・色など) */ +void libptouch_status_fprint(FILE *fp, + const uint8_t *status /* length @ref LIBPTOUCH_STATUS_LENGTH */); + +#ifdef __cplusplus +} +#endif + +#endif /* LIBPTOUCH_H */ diff --git a/include/libptouch_version.h.in b/include/libptouch_version.h.in new file mode 100644 index 0000000..59009a0 --- /dev/null +++ b/include/libptouch_version.h.in @@ -0,0 +1,17 @@ +/* + * libptouch_version.h — generated from this template by CMake + * + * Author: knb + * Email: knb@artif.org + */ + +#ifndef LIBPTOUCH_VERSION_H +#define LIBPTOUCH_VERSION_H + +/** リリースバージョン(CMake project(VERSION …) と同期) */ +#define LIBPTOUCH_VERSION_MAJOR @PROJECT_VERSION_MAJOR@ +#define LIBPTOUCH_VERSION_MINOR @PROJECT_VERSION_MINOR@ +#define LIBPTOUCH_VERSION_PATCH @PROJECT_VERSION_PATCH@ +#define LIBPTOUCH_VERSION_STRING "@PROJECT_VERSION@" + +#endif diff --git a/reference/cv_ptp900_jpn_raster_102.pdf b/reference/cv_ptp900_jpn_raster_102.pdf new file mode 100644 index 0000000..e44b468 Binary files /dev/null and b/reference/cv_ptp900_jpn_raster_102.pdf differ diff --git a/samples/README.md b/samples/README.md new file mode 100644 index 0000000..22635c0 --- /dev/null +++ b/samples/README.md @@ -0,0 +1,9 @@ +# samples + +試験・デモ用のサンプル画像(PNG など)を置くディレクトリです。リポジトリに含める場合はライセンス・著作権に注意してください。 + +例(ドライラン): + +```bash +./build/ptouch-print -n -f samples/your.png +``` diff --git a/src/cli/main.c b/src/cli/main.c new file mode 100644 index 0000000..af11dbe --- /dev/null +++ b/src/cli/main.c @@ -0,0 +1,292 @@ +/* + * ptouch-print — CLI for libptouch + * + * Author: knb + * Email: knb@artif.org + */ + +#include "libptouch.h" + +#include +#include +#include +#include +#include +#include + +static void usage(const char *argv0) +{ + fprintf(stderr, + "Usage: %s [options]\n" + " -f, --file PATH 入力ファイル\n" + " -w, --width DOTS 1bit ラスター時: 幅(ドット)\n" + " -H, --height DOTS 1bit ラスター時: 高さ(ドット)\n" + " -t, --threshold N PNG 二値化しきい値 0–255(既定 %u、PNG のみ)\n" + " -n, --dry-run 読み込みと check_raster のみ(USB なし)\n" + " -S, --status USB 接続プリンタのステータス(テープ種・幅・色等)を表示して終了\n" + " -V, --version バージョンを表示して終了\n" + " -h, --help このヘルプ\n" + "\n" + "PNG の場合は幅・高さはファイルから取得(-w/-H 不要)。\n" + "1bit packed ラスター(行優先)の場合は -f -w -H が必須。\n" + "--status のときは -f は不要(他オプションは無視されます)。\n", + argv0, (unsigned)LIBPTOUCH_PNG_DEFAULT_THRESHOLD); +} + +/** パスが .png で終わる、または先頭 8 バイトが PNG シグネチャ */ +static int input_is_png(const char *path) +{ + const char *dot = strrchr(path, '.'); + if (dot && strcasecmp(dot, ".png") == 0) + return 1; + + FILE *fp = fopen(path, "rb"); + if (!fp) + return 0; + unsigned char sig[8]; + size_t n = fread(sig, 1, sizeof(sig), fp); + fclose(fp); + if (n != sizeof(sig)) + return 0; + static const unsigned char png_magic[8] = { 137, 80, 78, 71, + 13, 10, 26, 10 }; + return memcmp(sig, png_magic, sizeof(png_magic)) == 0; +} + +static int read_file(const char *path, uint8_t **out, size_t *out_len) +{ + FILE *fp = fopen(path, "rb"); + if (!fp) { + fprintf(stderr, "open %s: %s\n", path, strerror(errno)); + return -1; + } + if (fseek(fp, 0, SEEK_END) != 0) { + fprintf(stderr, "fseek: %s\n", strerror(errno)); + fclose(fp); + return -1; + } + long sz = ftell(fp); + if (sz < 0) { + fprintf(stderr, "ftell: %s\n", strerror(errno)); + fclose(fp); + return -1; + } + rewind(fp); + uint8_t *buf = malloc((size_t)sz); + if (!buf) { + fprintf(stderr, "malloc failed\n"); + fclose(fp); + return -1; + } + size_t n = fread(buf, 1, (size_t)sz, fp); + fclose(fp); + if (n != (size_t)sz) { + fprintf(stderr, "short read\n"); + free(buf); + return -1; + } + *out = buf; + *out_len = n; + return 0; +} + +int main(int argc, char **argv) +{ + const char *file = NULL; + unsigned width = 0, height = 0; + unsigned threshold = LIBPTOUCH_PNG_DEFAULT_THRESHOLD; + int dry_run = 0; + int has_threshold = 0; + int want_status = 0; + static struct option longopts[] = { + { "width", required_argument, NULL, 'w' }, + { "height", required_argument, NULL, 'H' }, + { "file", required_argument, NULL, 'f' }, + { "threshold", required_argument, NULL, 't' }, + { "dry-run", no_argument, NULL, 'n' }, + { "status", no_argument, NULL, 'S' }, + { "version", no_argument, NULL, 'V' }, + { "help", no_argument, NULL, 'h' }, + { NULL, 0, NULL, 0 }, + }; + + int c; + while ((c = getopt_long(argc, argv, "w:H:f:t:nhSV", longopts, NULL)) != + -1) { + switch (c) { + case 'w': + width = (unsigned)strtoul(optarg, NULL, 10); + break; + case 'H': + height = (unsigned)strtoul(optarg, NULL, 10); + break; + case 'f': + file = optarg; + break; + case 't': + threshold = (unsigned)strtoul(optarg, NULL, 10); + if (threshold > 255u) { + fprintf(stderr, "-t must be 0..255\n"); + return 2; + } + has_threshold = 1; + break; + case 'n': + dry_run = 1; + break; + case 'S': + want_status = 1; + break; + case 'V': + printf("ptouch-print %s\n", LIBPTOUCH_VERSION_STRING); + return 0; + case 'h': + usage(argv[0]); + return 0; + default: + usage(argv[0]); + return 2; + } + } + + if (want_status) { + if (file || width || height || dry_run || has_threshold) + fprintf(stderr, + "warning: options other than --status are ignored\n"); + + libptouch_ctx *sctx = libptouch_create(); + if (!sctx) { + fprintf(stderr, "libptouch_create failed\n"); + return 1; + } + libptouch_err_t se = libptouch_open_usb(sctx); + if (se != LIBPTOUCH_OK) { + fprintf(stderr, "open_usb: %s\n", libptouch_strerror(sctx)); + libptouch_destroy(sctx); + return 1; + } + uint8_t st[LIBPTOUCH_STATUS_LENGTH]; + se = libptouch_get_status(sctx, st); + if (se != LIBPTOUCH_OK) { + fprintf(stderr, "get_status: %s\n", libptouch_strerror(sctx)); + libptouch_close(sctx); + libptouch_destroy(sctx); + return 1; + } + libptouch_status_fprint(stdout, st); + libptouch_close(sctx); + libptouch_destroy(sctx); + return 0; + } + + if (!file) { + fprintf(stderr, "-f is required (or use --status)\n"); + usage(argv[0]); + return 2; + } + + int png = input_is_png(file); + if (png) { + if (width != 0 || height != 0) + fprintf(stderr, + "warning: -w/-H ignored for PNG (using image size)\n"); + } else { + if (width == 0 || height == 0) { + fprintf(stderr, + "1bit raster requires -w and -H (or use a PNG file)\n"); + usage(argv[0]); + return 2; + } + if (has_threshold) + fprintf(stderr, + "warning: -t applies to PNG only (ignored)\n"); + } + + libptouch_ctx *ctx = libptouch_create(); + if (!ctx) { + fprintf(stderr, "libptouch_create failed\n"); + return 1; + } + + uint8_t *data = NULL; + size_t data_len = 0; + libptouch_raster_params_t params = { 0, 0, 0 }; + libptouch_err_t e; + + if (png) { + libptouch_png_options_t opt = { .threshold = (uint8_t)threshold }; + e = libptouch_png_file_to_raster(ctx, file, + has_threshold ? &opt : NULL, + &data, &data_len, ¶ms); + if (e != LIBPTOUCH_OK) { + fprintf(stderr, "png_file_to_raster: %s\n", + libptouch_strerror(ctx)); + libptouch_destroy(ctx); + return 1; + } + } else { + if (read_file(file, &data, &data_len) != 0) { + libptouch_destroy(ctx); + return 1; + } + params.width_dots = width; + params.height_dots = height; + params.margin_mm = 0; + } + + e = libptouch_check_raster(ctx, data, data_len, ¶ms); + if (e != LIBPTOUCH_OK) { + fprintf(stderr, "check_raster: %s\n", + libptouch_strerror(ctx)); + libptouch_destroy(ctx); + if (png) + libptouch_free_raster(data); + else + free(data); + return 1; + } + + if (dry_run) { + printf("dry-run OK: %zu bytes, %ux%u dots\n", data_len, + (unsigned)params.width_dots, + (unsigned)params.height_dots); + libptouch_destroy(ctx); + if (png) + libptouch_free_raster(data); + else + free(data); + return 0; + } + + e = 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; + } + + e = libptouch_print_raster(ctx, data, data_len, ¶ms); + if (e != LIBPTOUCH_OK) { + fprintf(stderr, "print_raster: %s\n", + libptouch_strerror(ctx)); + libptouch_close(ctx); + libptouch_destroy(ctx); + if (png) + libptouch_free_raster(data); + else + free(data); + return 1; + } + + libptouch_close(ctx); + libptouch_destroy(ctx); + if (png) + libptouch_free_raster(data); + else + free(data); + return 0; +} diff --git a/src/libptouch.c b/src/libptouch.c new file mode 100644 index 0000000..bf0434b --- /dev/null +++ b/src/libptouch.c @@ -0,0 +1,1164 @@ +/* + * libptouch — Brother P-touch raster printing (USB) + * + * Author: knb + * Email: knb@artif.org + */ + +#include "libptouch.h" + +#include +#include +#include +#include + +#include +#include + +struct libptouch_ctx { + libptouch_err_t last_code; + char last_msg[256]; + int usb_open; + struct libusb_context *usb_ctx; + struct libusb_device_handle *usb_handle; + int claimed_interface; /* -1 if none */ + uint8_t bulk_out_ep; + uint8_t bulk_in_ep; +}; + +static void set_error(libptouch_ctx *ctx, libptouch_err_t code, const char *msg) +{ + if (!ctx) + return; + ctx->last_code = code; + if (msg) { + snprintf(ctx->last_msg, sizeof(ctx->last_msg), "%s", msg); + } else { + ctx->last_msg[0] = '\0'; + } +} + +static void set_error_usb(libptouch_ctx *ctx, int libusb_err, const char *what) +{ + char buf[256]; + snprintf(buf, sizeof(buf), "%s: %s", what, + libusb_strerror((enum libusb_error)libusb_err)); + set_error(ctx, LIBPTOUCH_ERR_USB, buf); +} + +/** + * バルク IN/OUT を両方持つインタフェースを探して claim する。 + * USB Printer クラス (bInterfaceClass==7) を優先する。先頭のバルクだけを取ると + * 別機能のインタフェースを掴み、bulk IN が I/O エラーになる機種がある。 + * 成功時 0、失敗時 -1。エンドポイントは *out_ep / *in_ep に格納。 + */ +static int claim_interface_with_bulk(libusb_device_handle *h, int *out_iface, + uint8_t *out_ep, uint8_t *in_ep) +{ + libusb_device *dev = libusb_get_device(h); + struct libusb_config_descriptor *cfg = NULL; + int r = libusb_get_active_config_descriptor(dev, &cfg); + if (r != 0 || !cfg) + return -1; + + for (int pass = 0; pass < 2; pass++) { + for (int i = 0; i < cfg->bNumInterfaces; i++) { + const struct libusb_interface_descriptor *alt = + &cfg->interface[i].altsetting[0]; + if (pass == 0 && + alt->bInterfaceClass != LIBUSB_CLASS_PRINTER) + continue; + + uint8_t ep_out = 0, ep_in = 0; + for (uint8_t k = 0; k < alt->bNumEndpoints; k++) { + const struct libusb_endpoint_descriptor *ep = + &alt->endpoint[k]; + if ((ep->bmAttributes & + LIBUSB_TRANSFER_TYPE_MASK) == + LIBUSB_TRANSFER_TYPE_BULK) { + if (ep->bEndpointAddress & + LIBUSB_ENDPOINT_IN) + ep_in = ep->bEndpointAddress; + else + ep_out = ep->bEndpointAddress; + } + } + if (!ep_out || !ep_in) + continue; + r = libusb_claim_interface(h, i); + if (r == 0) { + *out_iface = i; + *out_ep = ep_out; + *in_ep = ep_in; + libusb_free_config_descriptor(cfg); + return 0; + } + } + } + libusb_free_config_descriptor(cfg); + return -1; +} + +libptouch_ctx *libptouch_create(void) +{ + libptouch_ctx *ctx = calloc(1, sizeof(*ctx)); + if (!ctx) + return NULL; + ctx->last_code = LIBPTOUCH_OK; + ctx->last_msg[0] = '\0'; + ctx->usb_open = 0; + ctx->usb_ctx = NULL; + ctx->usb_handle = NULL; + ctx->claimed_interface = -1; + ctx->bulk_out_ep = 0; + ctx->bulk_in_ep = 0; + return ctx; +} + +void libptouch_destroy(libptouch_ctx *ctx) +{ + if (!ctx) + return; + libptouch_close(ctx); + free(ctx); +} + +const char *libptouch_strerror(const libptouch_ctx *ctx) +{ + if (!ctx) + return "(null ctx)"; + return ctx->last_msg[0] ? ctx->last_msg : "ok"; +} + +libptouch_err_t libptouch_last_error(const libptouch_ctx *ctx) +{ + if (!ctx) + return LIBPTOUCH_ERR_ARG; + return ctx->last_code; +} + +libptouch_err_t libptouch_open_usb_vid_pid(libptouch_ctx *ctx, uint16_t vid, + uint16_t pid) +{ + if (!ctx) { + return LIBPTOUCH_ERR_ARG; + } + if (ctx->usb_handle) { + set_error(ctx, LIBPTOUCH_ERR_ARG, "USB already open"); + return LIBPTOUCH_ERR_ARG; + } + + int r = libusb_init(&ctx->usb_ctx); + if (r != LIBUSB_SUCCESS) { + set_error_usb(ctx, r, "libusb_init"); + ctx->usb_ctx = NULL; + return LIBPTOUCH_ERR_USB; + } + + libusb_device_handle *h = + libusb_open_device_with_vid_pid(ctx->usb_ctx, vid, pid); + if (!h) { + libusb_exit(ctx->usb_ctx); + ctx->usb_ctx = NULL; + char buf[160]; + snprintf(buf, sizeof(buf), + "no USB device 0x%04x:0x%04x (check cable, power, udev)", + (unsigned)vid, (unsigned)pid); + set_error(ctx, LIBPTOUCH_ERR_NOT_FOUND, buf); + return LIBPTOUCH_ERR_NOT_FOUND; + } + ctx->usb_handle = h; + +#if defined(LIBUSB_API_VERSION) && (LIBUSB_API_VERSION >= 0x01000106) + (void)libusb_set_auto_detach_kernel_driver(h, 1); +#endif + + if (claim_interface_with_bulk(h, &ctx->claimed_interface, + &ctx->bulk_out_ep, &ctx->bulk_in_ep) != 0) { + set_error(ctx, LIBPTOUCH_ERR_USB, + "no bulk IN/OUT interface (permissions or driver?)"); + libusb_close(h); + ctx->usb_handle = NULL; + libusb_exit(ctx->usb_ctx); + ctx->usb_ctx = NULL; + return LIBPTOUCH_ERR_USB; + } + + (void)libusb_clear_halt(h, ctx->bulk_out_ep); + (void)libusb_clear_halt(h, ctx->bulk_in_ep); + + ctx->usb_open = 1; + set_error(ctx, LIBPTOUCH_OK, ""); + return LIBPTOUCH_OK; +} + +libptouch_err_t libptouch_open_usb(libptouch_ctx *ctx) +{ + return libptouch_open_usb_vid_pid(ctx, LIBPTOUCH_USB_VID_BROTHER, + LIBPTOUCH_USB_PID_PTP900W); +} + +void libptouch_close(libptouch_ctx *ctx) +{ + if (!ctx) + return; + if (ctx->usb_handle) { + if (ctx->claimed_interface >= 0) { + (void)libusb_release_interface(ctx->usb_handle, + ctx->claimed_interface); + ctx->claimed_interface = -1; + } + libusb_close(ctx->usb_handle); + ctx->usb_handle = NULL; + } + if (ctx->usb_ctx) { + libusb_exit(ctx->usb_ctx); + ctx->usb_ctx = NULL; + } + ctx->bulk_out_ep = 0; + ctx->bulk_in_ep = 0; + ctx->usb_open = 0; +} + +libptouch_err_t libptouch_check_raster(libptouch_ctx *ctx, + const uint8_t *data, size_t data_len, + const libptouch_raster_params_t *params) +{ + if (!ctx || !params) { + if (ctx) + set_error(ctx, LIBPTOUCH_ERR_ARG, "null argument"); + return LIBPTOUCH_ERR_ARG; + } + if (!data && data_len > 0) { + set_error(ctx, LIBPTOUCH_ERR_ARG, "data is null but len > 0"); + return LIBPTOUCH_ERR_ARG; + } + + size_t row = (params->width_dots + 7u) / 8u; + size_t expected = (size_t)params->height_dots * row; + if (data_len != expected) { + char buf[160]; + snprintf(buf, sizeof(buf), + "data_len mismatch: got %zu, expected %zu (w=%u h=%u)", + data_len, expected, params->width_dots, + params->height_dots); + set_error(ctx, LIBPTOUCH_ERR_ARG, buf); + return LIBPTOUCH_ERR_ARG; + } + + set_error(ctx, LIBPTOUCH_OK, ""); + return LIBPTOUCH_OK; +} + +/* cv_ptp900_jpn_raster_102.pdf: 2.3.5 ラスターライン(全 560 ドット = 70 バイト) */ +static libptouch_err_t layout_from_status(uint8_t media_kind, uint8_t media_wbyte, + uint16_t *left, uint16_t *print_dots, + uint16_t *right) +{ + /* ヒートシュリンク (HS) — 種類バイト 11h / 17h */ + if (media_kind == 0x11u || media_kind == 0x17u) { + switch (media_wbyte) { + case 0x06: + *left = 244; + *print_dots = 56; + *right = 260; + return LIBPTOUCH_OK; + case 0x09: + *left = 224; + *print_dots = 96; + *right = 240; + return LIBPTOUCH_OK; + case 0x0C: + *left = 206; + *print_dots = 132; + *right = 222; + return LIBPTOUCH_OK; + case 0x12: + *left = 166; + *print_dots = 212; + *right = 182; + return LIBPTOUCH_OK; + case 0x18: + *left = 144; + *print_dots = 256; + *right = 160; + return LIBPTOUCH_OK; + default: + break; + } + } + /* TZe 系(ラミネート / ノンラミ等)および幅バイトのみ一致する一般テープ */ + switch (media_wbyte) { + case 0x04: + *left = 248; + *print_dots = 48; + *right = 264; + return LIBPTOUCH_OK; + case 0x06: + *left = 240; + *print_dots = 64; + *right = 256; + return LIBPTOUCH_OK; + case 0x09: + *left = 219; + *print_dots = 106; + *right = 235; + return LIBPTOUCH_OK; + case 0x0C: + *left = 197; + *print_dots = 150; + *right = 213; + return LIBPTOUCH_OK; + case 0x12: + *left = 155; + *print_dots = 234; + *right = 171; + return LIBPTOUCH_OK; + case 0x18: + *left = 112; + *print_dots = 320; + *right = 128; + return LIBPTOUCH_OK; + case 0x24: + *left = 45; + *print_dots = 454; + *right = 61; + return LIBPTOUCH_OK; + default: + break; + } + (void)media_kind; + return LIBPTOUCH_ERR_UNSUPPORTED; +} + +static void pack_line_560(uint8_t line[70], const uint8_t *row, uint32_t width_dots, + uint16_t left_dots, uint16_t print_dots) +{ + memset(line, 0, 70u); + 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 >= 560u) + break; + uint32_t b = dot / 8u; + uint32_t bi = 7u - (dot % 8u); + line[b] |= (uint8_t)(1u << bi); + } +} + +/** W×H の行優先 packed を転置し H×W のバッファを新規確保(印刷向き用) */ +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; +} + +static libptouch_err_t bulk_send_job(libptouch_ctx *ctx, const uint8_t *buf, + size_t len, const char *what) +{ + int tr = 0; + int r = libusb_bulk_transfer(ctx->usb_handle, ctx->bulk_out_ep, + (unsigned char *)buf, (int)len, &tr, + 120000); + if (r != 0) { + set_error_usb(ctx, r, what); + return LIBPTOUCH_ERR_USB; + } + if ((size_t)tr != len) { + char msg[96]; + snprintf(msg, sizeof(msg), "%s: short write %d/%zu", what, tr, + len); + set_error(ctx, LIBPTOUCH_ERR_USB, msg); + return LIBPTOUCH_ERR_USB; + } + return LIBPTOUCH_OK; +} + +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) { + 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; + + uint8_t media_kind = st[11]; + uint8_t media_w = st[10]; + uint16_t left_dots, print_dots, right_dots; + v = layout_from_status(media_kind, media_w, &left_dots, &print_dots, + &right_dots); + if (v != LIBPTOUCH_OK) { + 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; + + 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) { + 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); + set_error(ctx, LIBPTOUCH_ERR_ARG, buf); + free(transposed); + return LIBPTOUCH_ERR_ARG; + } + + unsigned margin_dots = 14u; + if (params->margin_mm > 0) { + margin_dots = (unsigned)((double)params->margin_mm * 360.0 / + 25.4 + + 0.5); + if (margin_dots < 14u) + margin_dots = 14u; + if (margin_dots > 1800u) + margin_dots = 1800u; + } + + uint32_t lines = ht; + uint8_t n5 = (uint8_t)(lines & 0xFFu); + uint8_t n6 = (uint8_t)((lines >> 8) & 0xFFu); + uint8_t n7 = (uint8_t)((lines >> 16) & 0xFFu); + uint8_t n8 = (uint8_t)((lines >> 24) & 0xFFu); + + uint8_t n2_paper = 0x09u; + if (media_kind == 0x03u) + n2_paper = 0x00u; + else if (media_kind == 0x11u) + n2_paper = 0x11u; + else if (media_kind == 0x17u) + n2_paper = 0x17u; + else if (media_kind == 0x13u) + n2_paper = 0x13u; + + 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[] = { 0x1B, 0x69, 0x7A, 0x0Eu, n2_paper, media_w, + 0x00u, n5, n6, n7, n8, 0x02u, 0x00u }; + 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); + static const uint8_t esc_ia[] = { 0x1B, 0x69, 0x41, 0x01 }; + memcpy(head + pos, esc_ia, sizeof(esc_ia)); + pos += sizeof(esc_ia); + static const uint8_t esc_ik[] = { 0x1B, 0x69, 0x4B, 0x0C }; + 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 = 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[73]; + static const uint8_t g_hdr[] = { 0x47, 0x46, 0x00 }; + + for (uint32_t y = 0; y < lines; y++) { + const uint8_t *row = src + (size_t)y * row_b; + pack_line_560(gbuf + 3, row, wd, left_dots, print_dots); + memcpy(gbuf, g_hdr, sizeof(g_hdr)); + v = bulk_send_job(ctx, gbuf, sizeof(gbuf), "raster line"); + if (v != LIBPTOUCH_OK) { + free(transposed); + return v; + } + } + + static const uint8_t print_end[] = { 0x1A }; + v = bulk_send_job(ctx, print_end, sizeof(print_end), "print end"); + if (v != LIBPTOUCH_OK) { + free(transposed); + return v; + } + + /* 印字完了ステータスを 1 パケットだけ読み捨て(あれば) */ + 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); + set_error(ctx, LIBPTOUCH_OK, ""); + return LIBPTOUCH_OK; +} + +/* --- PNG → 1bit packed raster (libpng) --- */ + +void libptouch_free_raster(uint8_t *raster) +{ + free(raster); +} + +libptouch_err_t libptouch_png_file_to_raster(libptouch_ctx *ctx, const char *path, + const libptouch_png_options_t *options, + uint8_t **out_raster, size_t *out_raster_bytes, + libptouch_raster_params_t *out_params) +{ + if (!ctx || !path || !out_raster || !out_raster_bytes || !out_params) { + if (ctx) + set_error(ctx, LIBPTOUCH_ERR_ARG, "null argument"); + return LIBPTOUCH_ERR_ARG; + } + + 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; + + FILE *fp = fopen(path, "rb"); + if (!fp) { + char buf[192]; + snprintf(buf, sizeof(buf), "open %s: %s", path, strerror(errno)); + set_error(ctx, LIBPTOUCH_ERR_IO, buf); + return LIBPTOUCH_ERR_IO; + } + + unsigned char sig[8]; + if (fread(sig, 1, 8, fp) != 8) { + fclose(fp); + set_error(ctx, LIBPTOUCH_ERR_IMAGE, "short read (not a PNG?)"); + return LIBPTOUCH_ERR_IMAGE; + } + if (png_sig_cmp(sig, 0, 8) != 0) { + fclose(fp); + set_error(ctx, LIBPTOUCH_ERR_IMAGE, "not a PNG file"); + return LIBPTOUCH_ERR_IMAGE; + } + + png_structp png_ptr = + png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png_ptr) { + fclose(fp); + set_error(ctx, LIBPTOUCH_ERR_NOMEM, "png_create_read_struct failed"); + return LIBPTOUCH_ERR_NOMEM; + } + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + png_destroy_read_struct(&png_ptr, NULL, NULL); + fclose(fp); + set_error(ctx, LIBPTOUCH_ERR_NOMEM, "png_create_info_struct failed"); + return LIBPTOUCH_ERR_NOMEM; + } + + if (setjmp(png_jmpbuf(png_ptr))) { + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + fclose(fp); + set_error(ctx, LIBPTOUCH_ERR_IMAGE, "PNG decode error"); + return LIBPTOUCH_ERR_IMAGE; + } + + png_init_io(png_ptr, fp); + png_set_sig_bytes(png_ptr, 8); + + const int transforms = (int)(PNG_TRANSFORM_EXPAND | PNG_TRANSFORM_STRIP_16 | + PNG_TRANSFORM_GRAY_TO_RGB); + png_read_png(png_ptr, info_ptr, transforms, NULL); + + png_uint_32 width = png_get_image_width(png_ptr, info_ptr); + png_uint_32 height = png_get_image_height(png_ptr, info_ptr); + int channels = (int)png_get_channels(png_ptr, info_ptr); + png_bytepp rows = png_get_rows(png_ptr, info_ptr); + + if (width == 0 || height == 0 || !rows) { + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + fclose(fp); + set_error(ctx, LIBPTOUCH_ERR_IMAGE, "invalid PNG dimensions"); + return LIBPTOUCH_ERR_IMAGE; + } + if (width > 100000u || height > 100000u) { + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + fclose(fp); + set_error(ctx, LIBPTOUCH_ERR_IMAGE, "PNG dimensions too large"); + return LIBPTOUCH_ERR_IMAGE; + } + if (channels != 3 && channels != 4) { + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + fclose(fp); + set_error(ctx, LIBPTOUCH_ERR_IMAGE, "unsupported PNG channel count"); + return LIBPTOUCH_ERR_IMAGE; + } + + size_t row_bytes = ((size_t)width + 7u) / 8u; + if (height > SIZE_MAX / row_bytes) { + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + fclose(fp); + set_error(ctx, LIBPTOUCH_ERR_IMAGE, "raster size overflow"); + return LIBPTOUCH_ERR_IMAGE; + } + size_t total = row_bytes * (size_t)height; + uint8_t *out = (uint8_t *)calloc(1, total); + if (!out) { + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + fclose(fp); + set_error(ctx, LIBPTOUCH_ERR_NOMEM, "calloc raster failed"); + return LIBPTOUCH_ERR_NOMEM; + } + + for (png_uint_32 y = 0; y < height; y++) { + const png_byte *row = rows[y]; + uint8_t *dst_row = out + (size_t)y * row_bytes; + for (png_uint_32 x = 0; x < width; x++) { + size_t o = (size_t)x * (size_t)channels; + unsigned r = row[o + 0]; + unsigned g = row[o + 1]; + unsigned b = row[o + 2]; + unsigned a = channels == 4 ? row[o + 3] : 255u; + /* 透明に近いピクセルは白(非印刷) */ + if (a < 128u) { + continue; + } + /* Y = 0.299 R + 0.587 G + 0.114 B(整数近似) */ + unsigned yv = (77u * r + 150u * g + 29u * b) >> 8; + int black = (yv < (unsigned)thr) ? 1 : 0; + if (!black) + continue; + size_t bit = (size_t)x % 8u; + size_t byte = (size_t)x / 8u; + dst_row[byte] |= (uint8_t)(1u << (7u - bit)); + } + } + + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + fclose(fp); + + out_params->width_dots = (uint32_t)width; + out_params->height_dots = (uint32_t)height; + out_params->margin_mm = 0; + *out_raster = out; + *out_raster_bytes = total; + set_error(ctx, LIBPTOUCH_OK, ""); + return LIBPTOUCH_OK; +} + +/* --- ステータス (ESC i S, 32 バイト) --- */ + +static int bulk_out(libusb_device_handle *h, uint8_t ep, const uint8_t *data, + int len, unsigned int timeout_ms) +{ + int tr = 0; + int r = libusb_bulk_transfer(h, ep, (unsigned char *)data, len, &tr, + (int)timeout_ms); + if (r != 0) + return r; + if (tr != len) + return LIBUSB_ERROR_IO; + return 0; +} + +static int bulk_in_exact(libusb_device_handle *h, uint8_t ep, uint8_t *buf, + int len, unsigned int timeout_ms) +{ + int got = 0; + while (got < len) { + int tr = 0; + int r = libusb_bulk_transfer(h, ep, buf + got, len - got, &tr, + (int)timeout_ms); + if (r != 0) + return r; + if (tr == 0) + return LIBUSB_ERROR_IO; + got += tr; + } + return 0; +} + +libptouch_err_t libptouch_get_status(libptouch_ctx *ctx, uint8_t *status) +{ + if (!ctx || !status) { + if (ctx) + set_error(ctx, LIBPTOUCH_ERR_ARG, "null argument"); + return LIBPTOUCH_ERR_ARG; + } + if (!ctx->usb_handle || !ctx->bulk_out_ep || !ctx->bulk_in_ep) { + set_error(ctx, LIBPTOUCH_ERR_IO, + "USB not open or bulk endpoints missing"); + return LIBPTOUCH_ERR_IO; + } + + libusb_device_handle *h = ctx->usb_handle; + static const uint8_t init[] = { 0x1B, 0x40 }; + static const uint8_t req[] = { 0x1B, 0x69, 0x53 }; + + int r = LIBUSB_ERROR_OTHER; + for (int attempt = 0; attempt < 2; attempt++) { + if (attempt > 0) { + (void)libusb_clear_halt(h, ctx->bulk_in_ep); + (void)libusb_clear_halt(h, ctx->bulk_out_ep); + } + r = bulk_out(h, ctx->bulk_out_ep, init, 2, 5000u); + if (r != 0) { + set_error_usb(ctx, r, "bulk OUT ESC @"); + return LIBPTOUCH_ERR_USB; + } + r = bulk_out(h, ctx->bulk_out_ep, req, 3, 5000u); + if (r != 0) { + set_error_usb(ctx, r, "bulk OUT ESC i S"); + return LIBPTOUCH_ERR_USB; + } + r = bulk_in_exact(h, ctx->bulk_in_ep, status, + (int)LIBPTOUCH_STATUS_LENGTH, 5000u); + if (r == 0) + break; + if (r != LIBUSB_ERROR_IO && r != LIBUSB_ERROR_PIPE) + break; + } + if (r != 0) { + set_error_usb(ctx, r, "bulk IN status"); + return LIBPTOUCH_ERR_USB; + } + + set_error(ctx, LIBPTOUCH_OK, ""); + return LIBPTOUCH_OK; +} + +static void fprint_model(FILE *fp, uint8_t code) +{ + const char *name = "unknown"; + switch (code) { + case 0x6F: + name = "PT-P900W"; + break; + case 0x70: + name = "PT-P950NW"; + break; + case 0x71: + name = "PT-P900"; + break; + case 0x78: + name = "PT-P910BT"; + break; + default: + break; + } + fprintf(fp, "機種コード: 0x%02X ('%c') — %s\n", (unsigned)code, + (code >= 32 && code < 127) ? (char)code : '?', name); +} + +static void fprint_media_width(FILE *fp, uint8_t w, uint8_t len_byte) +{ + const char *desc = NULL; + switch (w) { + case 0x00: + desc = "テープなし / 未装着"; + break; + case 0x04: + desc = "3.5 mm"; + break; + case 0x06: + desc = "6 mm"; + break; + case 0x09: + desc = "9 mm"; + break; + case 0x0C: + desc = "12 mm"; + break; + case 0x12: + desc = "18 mm"; + break; + case 0x18: + desc = "24 mm"; + break; + case 0x24: + desc = "36 mm"; + break; + case 0x15: + desc = "FLe 21 mm 幅(長さはメディア長バイト参照)"; + break; + default: + break; + } + if (desc) + fprintf(fp, "メディア幅: 0x%02X — %s\n", (unsigned)w, desc); + else + fprintf(fp, "メディア幅: 0x%02X\n", (unsigned)w); + if (w == 0x15 && len_byte != 0) + fprintf(fp, "メディア長: 0x%02X (%u mm 相当の表記参照)\n", + (unsigned)len_byte, (unsigned)len_byte); +} + +static void fprint_media_kind(FILE *fp, uint8_t k) +{ + const char *desc = NULL; + switch (k) { + case 0x00: + desc = "テープなし"; + break; + case 0x01: + desc = "ラミネートテープ"; + break; + case 0x03: + desc = "ノンラミネートテープ"; + break; + case 0x04: + desc = "ファブリックテープ"; + break; + case 0x11: + desc = "ヒートシュリンクチューブ (HS 2:1)"; + break; + case 0x13: + desc = "FLe テープ"; + break; + case 0x14: + desc = "フレキシブルIDテープ"; + break; + case 0x15: + desc = "サテンテープ"; + break; + case 0x17: + desc = "ヒートシュリンクチューブ (HS 3:1)"; + break; + case 0xFF: + desc = "非対応テープ"; + break; + default: + break; + } + if (desc) + fprintf(fp, "テープ種類: 0x%02X — %s\n", (unsigned)k, desc); + else + fprintf(fp, "テープ種類: 0x%02X\n", (unsigned)k); +} + +static void fprint_battery(FILE *fp, uint8_t b) +{ + const char *desc = NULL; + switch (b) { + case 0x00: + desc = "フル"; + break; + case 0x01: + desc = "ハーフ"; + break; + case 0x02: + desc = "ロー"; + break; + case 0x03: + desc = "要充電"; + break; + case 0x04: + desc = "AC アダプター使用中"; + break; + case 0xFF: + desc = "不明"; + break; + default: + break; + } + if (desc) + fprintf(fp, "電池残量: 0x%02X — %s\n", (unsigned)b, desc); + else + fprintf(fp, "電池残量: 0x%02X (PT-P910BT 等は別表参照)\n", + (unsigned)b); +} + +static void fprint_tape_color(FILE *fp, uint8_t c) +{ + const char *desc = NULL; + switch (c) { + case 0x01: + desc = "白 (White)"; + break; + case 0x02: + desc = "その他 (Other)"; + break; + case 0x03: + desc = "透明 (Clear)"; + break; + case 0x04: + desc = "赤 (Red)"; + break; + case 0x05: + desc = "青 (Blue)"; + break; + case 0x06: + desc = "黄 (Yellow)"; + break; + case 0x07: + desc = "緑 (Green)"; + break; + case 0x08: + desc = "黒 (Black)"; + break; + case 0x09: + desc = "透明(文字白)"; + break; + case 0x20: + desc = "白(マット) (Matte White)"; + break; + case 0x21: + desc = "透明(マット) (Matte Clear)"; + break; + case 0x22: + desc = "銀(マット) (Matte Silver)"; + break; + case 0x23: + desc = "金(サテン) (Satin Gold)"; + break; + case 0x24: + desc = "銀(サテン) (Satin Silver)"; + break; + case 0x30: + desc = "青(D)"; + break; + case 0x31: + desc = "赤(D)"; + break; + case 0x40: + desc = "オレンジ(蛍光)"; + break; + case 0x41: + desc = "黄(蛍光)"; + break; + case 0x50: + desc = "ピンク(S)"; + break; + case 0x51: + desc = "グレー(S)"; + break; + case 0x52: + desc = "グリーン(S)"; + break; + case 0x60: + desc = "イエロー(F)"; + break; + case 0x61: + desc = "ピンク(F)"; + break; + case 0x62: + desc = "ブルー(F)"; + break; + case 0x70: + desc = "白(チューブ)"; + break; + case 0x90: + desc = "白(フレキ)"; + break; + case 0x91: + desc = "黄(フレキ)"; + break; + case 0xF0: + desc = "クリーニング"; + break; + case 0xF1: + desc = "ステンシル"; + break; + case 0xFF: + desc = "非対応"; + break; + default: + break; + } + if (desc) + fprintf(fp, "テープ色: 0x%02X — %s\n", (unsigned)c, desc); + else + fprintf(fp, "テープ色: 0x%02X\n", (unsigned)c); +} + +static void fprint_status_kind(FILE *fp, uint8_t s) +{ + const char *desc = NULL; + switch (s) { + case 0x00: + desc = "印刷終了"; + break; + case 0x01: + desc = "エラー発生"; + break; + case 0x02: + desc = "IF モード終了"; + break; + case 0x03: + desc = "パワーオフ(未使用扱い)"; + break; + case 0x04: + desc = "通知"; + break; + case 0x05: + desc = "フェーズ変更"; + break; + default: + break; + } + if (desc) + fprintf(fp, "ステータス種類: 0x%02X — %s\n", (unsigned)s, desc); + else + fprintf(fp, "ステータス種類: 0x%02X\n", (unsigned)s); +} + +void libptouch_status_fprint(FILE *fp, const uint8_t *status) +{ + if (!fp || !status) + return; + + fprintf(fp, "=== P-touch ステータス (32 バイト) ===\n"); + if (status[0] != 0x80u || status[1] != 0x20u) + fprintf(fp, + "※ 先頭マーク異常: [0]=0x%02X [1]=0x%02X (通常 80 20)\n", + (unsigned)status[0], (unsigned)status[1]); + + fprintf(fp, "ヘッダ: 0x%02X 0x%02X\n", (unsigned)status[0], + (unsigned)status[1]); + fprintf(fp, "Brother コード: %c (0x%02X)\n", + (status[2] >= 32 && status[2] < 127) ? (char)status[2] : '?', + (unsigned)status[2]); + fprint_model(fp, status[4]); + fprintf(fp, "国別コード: %c\n", + (status[5] >= 32 && status[5] < 127) ? (char)status[5] : '?'); + + fprint_battery(fp, status[6]); + + if (status[7] != 0) { + fprintf(fp, "拡張エラー: 0x%02X", (unsigned)status[7]); + switch (status[7]) { + case 0x10: + fprintf(fp, " — FLE のテープエンド"); + break; + case 0x1D: + fprintf(fp, " — 高解像度/ドラフト印刷エラー"); + break; + case 0x1E: + fprintf(fp, " — アダプター抜き挿しエラー"); + break; + case 0x21: + fprintf(fp, " — 非対応メディアエラー"); + break; + default: + break; + } + fprintf(fp, "\n"); + } else { + fprintf(fp, "拡張エラー: なし (0x00)\n"); + } + + fprintf(fp, "エラー情報1: 0x%02X\n", (unsigned)status[8]); + if (status[8] & 0x01) + fprintf(fp, " - メディア無し\n"); + if (status[8] & 0x02) + fprintf(fp, " - メディア終了\n"); + if (status[8] & 0x04) + fprintf(fp, " - カッタージャム\n"); + if (status[8] & 0x08) + fprintf(fp, " - バッテリー弱\n"); + if (status[8] & 0x40) + fprintf(fp, " - 高圧アダプター\n"); + + fprintf(fp, "エラー情報2: 0x%02X\n", (unsigned)status[9]); + if (status[9] & 0x01) + fprintf(fp, " - メディア交換(メディア違い)\n"); + if (status[9] & 0x04) + fprintf(fp, " - 通信エラー\n"); + if (status[9] & 0x08) + fprintf(fp, " - 通信バッファーフル\n"); + if (status[9] & 0x10) + fprintf(fp, " - カバーオープン\n"); + if (status[9] & 0x20) + fprintf(fp, " - 高温エラー\n"); + if (status[9] & 0x40) + fprintf(fp, " - 先端検出エラー\n"); + if (status[9] & 0x80) + fprintf(fp, " - システムエラー\n"); + + fprint_media_width(fp, status[10], status[17]); + fprint_media_kind(fp, status[11]); + fprintf(fp, "色数: 0x%02X フォント/日本語フォント: 0x%02X / 0x%02X\n", + (unsigned)status[12], (unsigned)status[13], + (unsigned)status[14]); + fprintf(fp, "モード: 0x%02X 濃度: 0x%02X\n", (unsigned)status[15], + (unsigned)status[16]); + + fprint_status_kind(fp, status[18]); + fprintf(fp, "フェーズ種類: 0x%02X フェーズ番号: %02X %02X\n", + (unsigned)status[19], (unsigned)status[20], + (unsigned)status[21]); + fprintf(fp, "通知番号: 0x%02X\n", (unsigned)status[22]); + fprintf(fp, "拡張部バイト数: 0x%02X\n", (unsigned)status[23]); + + fprint_tape_color(fp, status[24]); + fprintf(fp, "文字色: 0x%02X\n", (unsigned)status[25]); + + fprintf(fp, "生データ: "); + for (unsigned i = 0; i < LIBPTOUCH_STATUS_LENGTH; i++) + fprintf(fp, "%02X%s", (unsigned)status[i], + i + 1 == LIBPTOUCH_STATUS_LENGTH ? "\n" : " "); +} diff --git a/udev/99-ptouch-label-brother.rules b/udev/99-ptouch-label-brother.rules new file mode 100644 index 0000000..3390324 --- /dev/null +++ b/udev/99-ptouch-label-brother.rules @@ -0,0 +1,16 @@ +# Brother P-touch — libusb でユーザから開けるようにする (Ubuntu 等) +# PT-P900W: lsusb で ID 04f9:2085 +# +# 設置: +# sudo cp udev/99-ptouch-label-brother.rules /etc/udev/rules.d/ +# sudo udevadm control --reload-rules && sudo udevadm trigger +# sudo usermod -aG plugdev "$USER" +# 再ログインするか USB を抜き差しする +# +# TAG+=uaccess … systemd-logind がアクティブセッションに ACL を付与(デスクトップ向け) +# GROUP=plugdev … 従来どおり plugdev に所属していれば読み書き可 + +SUBSYSTEM=="usb", ATTR{idVendor}=="04f9", ATTR{idProduct}=="2085", MODE="0660", GROUP="plugdev", TAG+="uaccess" + +# 他機種を使う場合は lsusb の ID を足す例(コメントを外して複製): +# SUBSYSTEM=="usb", ATTR{idVendor}=="04f9", ATTR{idProduct}=="2086", MODE="0660", GROUP="plugdev", TAG+="uaccess"