1st version

This commit is contained in:
knb
2026-04-12 08:52:54 +09:00
parent 2d8879f45f
commit d649b1b014
12 changed files with 1797 additions and 0 deletions

7
.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
build/
*.o
*.a
ptouch-print
*.so
*.dylib

47
CMakeLists.txt Normal file
View File

@@ -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
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
"$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/include>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
)
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}")

21
LICENSE Normal file
View File

@@ -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.

View File

@@ -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`0255で二値化しきい値を指定できます。
**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` — USBlibusb・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` ファイルを参照)。

BIN
aB.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

128
include/libptouch.h Normal file
View File

@@ -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 <stddef.h>
#include <stdint.h>
#include <stdio.h>
#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 の最小 1mm14 ドット)相当を送る。
* 印刷時は内部でドット列を転置する(テープ幅方向とバッファの縦横の対応)。
* @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);
/** 二値化の既定しきい値(輝度 0255、これ未満を黒ドット */
#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 */

View File

@@ -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

Binary file not shown.

9
samples/README.md Normal file
View File

@@ -0,0 +1,9 @@
# samples
試験・デモ用のサンプル画像PNG など)を置くディレクトリです。リポジトリに含める場合はライセンス・著作権に注意してください。
例(ドライラン):
```bash
./build/ptouch-print -n -f samples/your.png
```

292
src/cli/main.c Normal file
View File

@@ -0,0 +1,292 @@
/*
* ptouch-print — CLI for libptouch
*
* Author: knb
* Email: knb@artif.org
*/
#include "libptouch.h"
#include <errno.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
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 二値化しきい値 0255既定 %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, &params);
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, &params);
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, &params);
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;
}

1164
src/libptouch.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -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"