13 Commits

Author SHA1 Message Date
knb
d0e5846012 Bump project version metadata to 1.0.1.
Synchronize CMake, README, and Ruby gem version strings with the released v1.0.1 tag to keep build and packaging metadata consistent.

Made-with: Cursor
2026-04-17 05:21:39 +09:00
knb
d2fd6cc1f9 Fix print completion flow for PT-P710BT and PT-P900W.
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
2026-04-17 05:17:22 +09:00
knb
779a50747d Bluetooth 対応状況追記 2026-04-17 03:53:13 +09:00
knb
29072dc20c Update docs and add sample image for label workflows.
Capture design notes for merge-print and PNG fit behavior, clean up Ruby README formatting, and include a new sample PNG for print testing.

Made-with: Cursor
2026-04-17 03:25:39 +09:00
knb
42a785f086 Fix PT-P710BT raster protocol handling and add regression coverage.
Align print command payload generation with model-specific protocol requirements, add verbose CLI diagnostics and robust status retries, and introduce protocol regression tests to prevent future GF/ESC i z regressions.

Made-with: Cursor
2026-04-17 03:24:00 +09:00
knb
e92273a747 Merge branch 'cursor/ruby-label-merge-cli' 2026-04-16 19:09:43 +09:00
knb
094f183994 ptouch-label/ptouch-print のオプション整理と trim-right 対応を反映
CLIヘルプ文言を簡潔化しつつ、右余白トリム機能と関連API・ドキュメント更新をまとめて取り込み、PNG/SVG/テンプレート経路での利用体験を揃える。

Made-with: Cursor
2026-04-16 19:09:37 +09:00
knb
32ab12f661 ptouch-label CLIを追加して差込印刷を拡張
コマンド名を機能に合わせて整理し、SVGテンプレート+JSON/YAMLの差込印刷とメディア情報取得を使いやすくする。

Made-with: Cursor
2026-04-16 14:49:08 +09:00
knb
33465c7299 Merge branch 'feature/svg-print-fit-tape-width' 2026-04-16 13:55:57 +09:00
knb
d683bf983a internalヘッダに関数説明コメントを追加
libptouch_internal.h の内部関数宣言に用途と戻り値の簡潔な説明を付け、実装読解時の確認コストを下げる。

Made-with: Cursor
2026-04-16 13:54:36 +09:00
knb
28a0c73f6a SVGサンプルファイルを追加
SVG印刷機能の動作確認に使えるサンプル画像を同梱し、dry-runや実機テストをすぐ試せるようにする。

Made-with: Cursor
2026-04-14 18:30:05 +09:00
knb
2ed0bfc0be SVG印刷対応とメディア情報APIを追加
SVG入力を現在テープ幅に自動フィットして印刷できるようにし、アプリ側が余白計算できるようにテープ幅・DPI・最小送り量を取得するAPIを追加する。

Made-with: Cursor
2026-04-14 18:29:24 +09:00
knb
f26a1186a3 libptouch の更新に追従 2026-04-13 11:39:31 +09:00
38 changed files with 1860 additions and 365 deletions

19
CHANGELOG.md Normal file
View File

@@ -0,0 +1,19 @@
# Changelog
## Unreleased
- Fix PT-P710BT/PT-P900W print completion flow and protocol bytes.
- Correct `G` raster transfer header length (`n1/n2`) from per-model payload size.
- Fix `ESC i z` page index (`n9`) and adjust print-information flags/media kind handling.
- Keep `ESC i A` disabled on 128-dot family and split `ESC i K` by head width.
- Use `0x0C -> 0x1A` end sequence on 560-dot family to complete feed/cut reliably.
- Improve status handling around print completion.
- Retry status reads with backoff and safer command ordering.
- Add verbose post-print short polling until print-end status is observed.
- Add small wait before `--status` command retrieval.
- Add protocol regression safeguards.
- Introduce `libptouch_protocol` helpers for shared command-byte generation.
- Add `protocol_regression_test` and wire it into CTest.
- Update documentation.
- Add `ctest` step to build instructions.
- Link to changelog from README.

View File

@@ -4,7 +4,7 @@
# Email: knb@artif.org # Email: knb@artif.org
cmake_minimum_required(VERSION 3.16) cmake_minimum_required(VERSION 3.16)
project(ptouch_label VERSION 1.0.0 LANGUAGES C) project(ptouch_label VERSION 1.0.1 LANGUAGES C)
set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON) set(CMAKE_C_STANDARD_REQUIRED ON)
@@ -14,6 +14,7 @@ include(GNUInstallDirs)
find_package(PkgConfig REQUIRED) find_package(PkgConfig REQUIRED)
pkg_check_modules(LIBUSB REQUIRED IMPORTED_TARGET libusb-1.0) pkg_check_modules(LIBUSB REQUIRED IMPORTED_TARGET libusb-1.0)
find_package(PNG REQUIRED) find_package(PNG REQUIRED)
pkg_check_modules(LIBRSVG IMPORTED_TARGET librsvg-2.0)
configure_file( configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/include/libptouch_version.h.in" "${CMAKE_CURRENT_SOURCE_DIR}/include/libptouch_version.h.in"
@@ -24,9 +25,14 @@ configure_file(
set(LIBPTOUCH_SOURCES set(LIBPTOUCH_SOURCES
src/lib/libptouch_core.c src/lib/libptouch_core.c
src/lib/libptouch_usb.c src/lib/libptouch_usb.c
src/lib/libptouch_protocol.c
src/lib/libptouch_layout.c
src/lib/libptouch_media_info.c
src/lib/libptouch_trim.c
src/lib/libptouch_print.c src/lib/libptouch_print.c
src/lib/libptouch_status.c src/lib/libptouch_status.c
src/lib/libptouch_png.c src/lib/libptouch_png.c
src/lib/libptouch_svg.c
) )
add_library(ptouch STATIC ${LIBPTOUCH_SOURCES}) add_library(ptouch STATIC ${LIBPTOUCH_SOURCES})
@@ -51,6 +57,12 @@ target_include_directories(ptouch_shared PRIVATE
) )
target_link_libraries(ptouch PRIVATE PkgConfig::LIBUSB PNG::PNG) target_link_libraries(ptouch PRIVATE PkgConfig::LIBUSB PNG::PNG)
target_link_libraries(ptouch_shared PRIVATE PkgConfig::LIBUSB PNG::PNG) target_link_libraries(ptouch_shared PRIVATE PkgConfig::LIBUSB PNG::PNG)
if(LIBRSVG_FOUND)
target_link_libraries(ptouch PRIVATE PkgConfig::LIBRSVG)
target_link_libraries(ptouch_shared PRIVATE PkgConfig::LIBRSVG)
target_compile_definitions(ptouch PRIVATE LIBPTOUCH_HAS_RSVG=1)
target_compile_definitions(ptouch_shared PRIVATE LIBPTOUCH_HAS_RSVG=1)
endif()
if(NOT MSVC) if(NOT MSVC)
target_compile_options(ptouch PRIVATE -Wall -Wextra -Wpedantic) target_compile_options(ptouch PRIVATE -Wall -Wextra -Wpedantic)
target_compile_options(ptouch_shared PRIVATE -Wall -Wextra -Wpedantic) target_compile_options(ptouch_shared PRIVATE -Wall -Wextra -Wpedantic)
@@ -63,6 +75,19 @@ if(NOT MSVC)
target_compile_options(ptouch-print PRIVATE -Wall -Wextra -Wpedantic) target_compile_options(ptouch-print PRIVATE -Wall -Wextra -Wpedantic)
endif() endif()
enable_testing()
add_executable(ptouch-protocol-regression-test
tests/protocol_regression_test.c
src/lib/libptouch_protocol.c
)
target_include_directories(ptouch-protocol-regression-test PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}/src/lib"
)
if(NOT MSVC)
target_compile_options(ptouch-protocol-regression-test PRIVATE -Wall -Wextra -Wpedantic)
endif()
add_test(NAME protocol_regression_test COMMAND ptouch-protocol-regression-test)
install(TARGETS ptouch ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}") install(TARGETS ptouch ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}")
install(TARGETS ptouch_shared LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" install(TARGETS ptouch_shared LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}") RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}")

View File

@@ -1,29 +1,34 @@
# ptouch_label # ptouch_label
**バージョン 1.0.0**(初回リリース) **バージョン 1.0.1**
Brother P-touch シリーズ向けのラベル印刷用 **C コアライブラリlibptouch** と、動作確認用 **CLI`ptouch-print`** のリポジトリです。 Brother P-touch シリーズ向けのラベル印刷用 **C コアライブラリlibptouch** と、動作確認用 **CLI`ptouch-print`** のリポジトリです。
保有機種: **PT-P900W**USB・ラスターコマンド 対象機種: **PT-P900W**560 ドットヘッド)、**PT-P750W** / **PT-P710BT**128 ドットヘッド・USB`libptouch_open_usb_vid_pid` に各機種の VID/PID を渡すP900W 既定は `libptouch_open_usb`。P750/P710 のラスター仕様は `reference/cv_ptp750w_710bt_jpn_raster_102.pdf`
現状は USB only / 将来 Bluetooth 対応予定。
## レイアウト ## レイアウト
| パス | 内容 |
|------|------| | パス | 内容 |
| `include/libptouch.h` | 公開 API | | ----------------------- | --------------------------------------------------------------------------- |
| `src/lib/libptouch_*.c` | ライブラリ本体core / usb / print / status / png | | `include/libptouch.h` | 公開 API |
| `src/cli/main.c` | `ptouch-print` エントリ | | `src/lib/libptouch_*.c` | ライブラリ本体core / usb / print / status / png / svg |
| `samples/` | 試験用サンプル画像の置き場PNG 等) | | `src/cli/main.c` | `ptouch-print` エントリ |
| `ruby/` | Ruby FFI gem`libptouch`)・コマンド `ptouch-print-png`PNG のみ)— `ruby/README.md` | | `samples/` | 試験用サンプル画像の置き場PNG/SVG 等) |
| `reference/` | 仕様・参考資料(例: ラスター PDF | | `ruby/` | Ruby FFI gem`libptouch`)・コマンド `ptouch-print-png`PNG のみ)— `ruby/README.md` |
| `reference/` | 仕様・参考資料(例: ラスター PDF |
## ビルド ## ビルド
依存: **CMake 3.16+**、**libusb-1.0**、**libpng**(開発パッケージ例: `libusb-1.0-0-dev``libpng-dev`)。 依存: **CMake 3.16+**、**libusb-1.0**、**libpng**、**librsvg-2.0**(開発パッケージ例: `libusb-1.0-0-dev``libpng-dev``librsvg2-dev`)。
```bash ```bash
cmake -S . -B build cmake -S . -B build
cmake --build build cmake --build build
ctest --test-dir build --output-on-failure
``` ```
成果物(`build/` 以下): 成果物(`build/` 以下):
@@ -38,7 +43,10 @@ cmake --build build
## CLI の使い方(雛形) ## CLI の使い方(雛形)
**PNG**(拡張子 `.png` または PNG シグネチャ)の場合は幅・高さは画像から取得します。任意で `-t`0255で二値化しきい値を指定できます。 **PNG**(拡張子 `.png` または PNG シグネチャ)の場合は幅・高さは画像から取得します。
**SVG**(拡張子 `.svg`の場合は現在装着中テープの印字可能ドット幅に合わせて自動拡大・縮小しますUSB 接続が必要)。
`--trim-right[=DOTS]` で右側空白列を削減できますDOTS 省略時は左余白ドット、取得失敗時は 0
任意で `-t`0255で二値化しきい値を指定できます。
**1bit packed ラスター**(行優先、行あたり `ceil(width/8)` バイト × 行数)の場合は `-f` に加え `-w` / `-H` が必須です。 **1bit packed ラスター**(行優先、行あたり `ceil(width/8)` バイト × 行数)の場合は `-f` に加え `-w` / `-H` が必須です。
@@ -47,6 +55,11 @@ cmake --build build
```bash ```bash
# PNG — 検証のみUSB 不要) # PNG — 検証のみUSB 不要)
./build/ptouch-print -n -f label.png ./build/ptouch-print -n -f label.png
./build/ptouch-print -n -f label.png --trim-right
./build/ptouch-print -n -f label.png --trim-right=20
# SVG — 幅を現在テープにフィットさせて検証USB 必要)
./build/ptouch-print -n -f label.svg
# PNG — しきい値を指定 # PNG — しきい値を指定
./build/ptouch-print -n -f label.png -t 160 ./build/ptouch-print -n -f label.png -t 160
@@ -59,7 +72,12 @@ cmake --build build
# USB 接続時 # USB 接続時
./build/ptouch-print -f label.png ./build/ptouch-print -f label.png
./build/ptouch-print -f label.svg
./build/ptouch-print -f sample.raster -w 128 -H 64 ./build/ptouch-print -f sample.raster -w 128 -H 64
# PT-P750W / PT-P710BT`lsusb` の PID に合わせる)
./build/ptouch-print --status -p 0x2062
./build/ptouch-print -f label.png -p 0x20af
``` ```
`-n``--dry-run`)では読み込みと `libptouch_check_raster` まで実行します。 `-n``--dry-run`)では読み込みと `libptouch_check_raster` まで実行します。
@@ -70,8 +88,10 @@ cmake --build build
- `libptouch_create` / `libptouch_destroy` — コンテキスト - `libptouch_create` / `libptouch_destroy` — コンテキスト
- `libptouch_open_usb` / `libptouch_open_usb_vid_pid` / `libptouch_close` — USBlibusb・VID/PID - `libptouch_open_usb` / `libptouch_open_usb_vid_pid` / `libptouch_close` — USBlibusb・VID/PID
- `libptouch_get_status` / `libptouch_status_fprint` — ステータス情報リクエストESC i Sの応答 - `libptouch_get_status` / `libptouch_status_fprint` — ステータス情報リクエストESC i Sの応答
- `libptouch_get_current_media_info` — 現在テープ幅(mm)・印字可能幅(dots)・DPI・最小余白(mm)を取得
- `libptouch_check_raster` — ラスターバッファの検証のみ - `libptouch_check_raster` — ラスターバッファの検証のみ
- `libptouch_png_file_to_raster` / `libptouch_free_raster` — PNG を 1bit ラスターに変換libpng - `libptouch_png_file_to_raster` / `libptouch_free_raster` — PNG を 1bit ラスターに変換libpng
- `libptouch_svg_file_to_raster_fit_current_tape` — SVG を現在テープ幅に合わせて 1bit ラスターへ変換librsvg + cairo、USB 必須)
- `libptouch_print_raster` — ラスター印刷USB・PDF のコマンド列、内部で転置) - `libptouch_print_raster` — ラスター印刷USB・PDF のコマンド列、内部で転置)
- `libptouch_strerror` / `libptouch_last_error` — エラー情報 - `libptouch_strerror` / `libptouch_last_error` — エラー情報
@@ -79,12 +99,13 @@ cmake --build build
## PT-P900W / Linux でのメモ ## PT-P900W / Linux でのメモ
- 接続は **libusb-1.0** のみ。機種ごとに **VID/PID**`lsusb` 等)を調べ、`libptouch_open_usb_vid_pid` に渡すか、既定の PT-P900W なら `libptouch_open_usb` を使います。 - 接続は **libusb-1.0** のみ。機種ごとに **VID/PID**`lsusb` 等)を調べ、`libptouch_open_usb_vid_pid` に渡すか、既定の PT-P900W なら `libptouch_open_usb` を使います。PT-P750W は **04f9:2062**、PT-P710BT は **04f9:20af**`include/libptouch.h` の定数参照)。
- ラスターコマンドの詳細は **`reference/` の PDF** および Brother 公開資料に沿って `src/lib/libptouch_*.c` に実装してください。 - PT-P750W / PT-P710BT ではラスター幅方向は **180 dpi**P900W は 360 dpi。PNG から印刷する場合は解像度に合わせて画像を用意してください。
- ラスターコマンドの詳細は `**reference/` の PDF** および Brother 公開資料に沿って `src/lib/libptouch_*.c` に実装してください。
### Ubuntu で sudo なしで USB を開くudev ### Ubuntu で sudo なしで USB を開くudev
既定の **04f9:2085**PT-P900W向けルールを `udev/99-ptouch-label-brother.rules` に置いています。 **04f9:2085**PT-P900W、**04f9:2062**PT-P750W、**04f9:20af**PT-P710BT向けルールを `udev/99-ptouch-label-brother.rules` に置いています。
```bash ```bash
sudo cp udev/99-ptouch-label-brother.rules /etc/udev/rules.d/ sudo cp udev/99-ptouch-label-brother.rules /etc/udev/rules.d/
@@ -100,3 +121,7 @@ sudo usermod -aG plugdev "$USER"
## ライセンス ## ライセンス
[MIT License](LICENSE)`LICENSE` ファイルを参照)。 [MIT License](LICENSE)`LICENSE` ファイルを参照)。
## 変更履歴
リリース間の変更点は `CHANGELOG.md` を参照してください。

View File

@@ -8,7 +8,8 @@
/** /**
* libptouch — Brother P-touch ラスター印刷 (USB) 用 C API * libptouch — Brother P-touch ラスター印刷 (USB) 用 C API
* *
* 対象例: PT-P900Wラスターコマンド)。実装は src/lib/libptouch_*.c を参照 * 対象例: PT-P900W560 ドットヘッド、PT-P750W / PT-P710BT128 ドットヘッド)
* 実装は src/lib/libptouch_*.c を参照。
*/ */
#ifndef LIBPTOUCH_H #ifndef LIBPTOUCH_H
@@ -41,6 +42,9 @@ typedef enum {
/** lsusb 例: PT-P900W — Brother Industries, Ltd (04f9:2085) */ /** lsusb 例: PT-P900W — Brother Industries, Ltd (04f9:2085) */
#define LIBPTOUCH_USB_VID_BROTHER 0x04f9u #define LIBPTOUCH_USB_VID_BROTHER 0x04f9u
#define LIBPTOUCH_USB_PID_PTP900W 0x2085u #define LIBPTOUCH_USB_PID_PTP900W 0x2085u
/** PT-P750W / PT-P710BTcv_ptp750w_710bt_jpn_raster_102.pdf Appendix A */
#define LIBPTOUCH_USB_PID_PTP750W 0x2062u
#define LIBPTOUCH_USB_PID_PTP710BT 0x20afu
libptouch_ctx *libptouch_create(void); libptouch_ctx *libptouch_create(void);
void libptouch_destroy(libptouch_ctx *ctx); void libptouch_destroy(libptouch_ctx *ctx);
@@ -69,6 +73,19 @@ typedef struct {
uint8_t margin_mm; /**< 余白など(機種・仕様に合わせて使用) */ uint8_t margin_mm; /**< 余白など(機種・仕様に合わせて使用) */
} libptouch_raster_params_t; } libptouch_raster_params_t;
typedef struct {
uint8_t media_width_code; /**< status[10] */
uint8_t media_kind_code; /**< status[11] */
double print_dpi; /**< 印字幅方向 DPI現状 180 or 360 */
double feed_dpi; /**< 送り方向 DPIESC i d の基準) */
double tape_width_mm; /**< 装着テープ幅mm。例: 3.5, 6, 9, 12, 18, 24, 36 */
uint16_t printable_dots; /**< 現在テープで印字可能な幅(ドット) */
uint16_t left_margin_dots; /**< 左余白(ドット) */
uint16_t right_margin_dots; /**< 右余白(ドット) */
uint16_t min_feed_dots; /**< 仕様上の最小送り量(ドット)。現在は 14 */
double min_feed_mm; /**< 最小送り量mm */
} libptouch_media_info_t;
/** /**
* バッファサイズとパラメータの整合性のみ検査USB 不要)。--dry-run 用。 * バッファサイズとパラメータの整合性のみ検査USB 不要)。--dry-run 用。
*/ */
@@ -77,9 +94,27 @@ libptouch_err_t libptouch_check_raster(libptouch_ctx *ctx,
const libptouch_raster_params_t *params); const libptouch_raster_params_t *params);
/** /**
* 1 ビット packed ラスターを USB で印刷cv_ptp900_jpn_raster_102.pdf 準拠)。 * 現在装着テープの情報を取得するUSB 接続済み必須)。
* ステータスと機種プロファイルからテープ幅・印字可能幅・最小余白量を返す。
*/
libptouch_err_t libptouch_get_current_media_info(libptouch_ctx *ctx,
libptouch_media_info_t *out_info);
/**
* 1bit packed ラスターの右側空白列を削減するUSB 不要)。
* 入力全体が白の場合は元サイズを維持して返す。
*/
libptouch_err_t libptouch_trim_right_blank_columns(
libptouch_ctx *ctx, const uint8_t *data, size_t data_len,
const libptouch_raster_params_t *in_params, uint16_t right_padding_dots,
uint8_t **out_raster,
size_t *out_raster_bytes, libptouch_raster_params_t *out_params);
/**
* 1 ビット packed ラスターを USB で印刷(各機種のラスター PDF 準拠)。
* 印字前にステータスでテープ幅を読み、印刷可能ドット内に画像を中央配置する。 * 印字前にステータスでテープ幅を読み、印刷可能ドット内に画像を中央配置する。
* width_dots は装着テープの印刷可能幅以下であること。360dpi 相当のドット列を想定。 * width_dots は装着テープの印刷可能幅以下であること。PT-P900 系は幅 360dpi、
* PT-P750W / PT-P710BT は幅 180dpicv_ptp750w_710bt_jpn_raster_102.pdfのドット列を想定。
* @param margin_mm 余白フィード量。0 のとき PDF の最小 1mm14 ドット)相当を送る。 * @param margin_mm 余白フィード量。0 のとき PDF の最小 1mm14 ドット)相当を送る。
* 印刷時は内部でドット列を転置する(テープ幅方向とバッファの縦横の対応)。 * 印刷時は内部でドット列を転置する(テープ幅方向とバッファの縦横の対応)。
* @param data 1 行あたり width_dots ビットを ceil(width_dots/8) バイトで並べた連続領域 * @param data 1 行あたり width_dots ビットを ceil(width_dots/8) バイトで並べた連続領域
@@ -96,6 +131,10 @@ typedef struct {
uint8_t threshold; /**< 輝度がこれ未満なら黒1、以上なら白0 */ uint8_t threshold; /**< 輝度がこれ未満なら黒1、以上なら白0 */
} libptouch_png_options_t; } libptouch_png_options_t;
typedef struct {
uint8_t threshold; /**< 輝度がこれ未満なら黒1、以上なら白0 */
} libptouch_svg_options_t;
/** /**
* PNG ファイルを読み、1bit packed ラスターに変換するlibpng * PNG ファイルを読み、1bit packed ラスターに変換するlibpng
* @param out_raster malloc 済みバッファのポインタを返す。不要時は @ref libptouch_free_raster で解放。 * @param out_raster malloc 済みバッファのポインタを返す。不要時は @ref libptouch_free_raster で解放。
@@ -105,6 +144,17 @@ libptouch_err_t libptouch_png_file_to_raster(libptouch_ctx *ctx, const char *pat
uint8_t **out_raster, size_t *out_raster_bytes, uint8_t **out_raster, size_t *out_raster_bytes,
libptouch_raster_params_t *out_params); libptouch_raster_params_t *out_params);
/**
* SVG ファイルを現在装着中テープの印字可能幅に合わせて 1bit packed ラスターへ変換する
* librsvg + cairo
* @note 現在テープ幅を取得するため、事前に @ref libptouch_open_usb などで USB 接続が必要。
* @param out_raster malloc 済みバッファのポインタを返す。不要時は @ref libptouch_free_raster で解放。
*/
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);
void libptouch_free_raster(uint8_t *raster); void libptouch_free_raster(uint8_t *raster);
/** ステータス情報リクエストESC i Sの応答バイト数cv_ptp900_jpn_raster_102.pdf */ /** ステータス情報リクエストESC i Sの応答バイト数cv_ptp900_jpn_raster_102.pdf */

44
reference/note.adoc Normal file
View File

@@ -0,0 +1,44 @@
= pTouch Label ノート
== 差込印刷機能追加(案)
SVG ファイルを template として、JSON/YAML ファイルで受け取った内容を差込印刷する。
SVG template の text element の data-field attribute をキーにして 印字テキストを JSON/YAML ファイルのデータで置き換える
----
<svg xmlns="http://www.w3.org/2000/svg" width="400" height="400" viewBox="0 0 124 124" fill="none">
<rect width="124" height="124" rx="24" fill="#FFFFFFFF"/>
<text fill="#000000" x="5" y="60" data-field="label" font-size="40">
Label
</text>
</svg>
----
という SVG ファイルと
^^^^
label: "ラベル"
----
という YAML ファイルを受け取ったら、
SVG の text の中身のテキストを"ラベル" に置き換えるという感じです。
== PNG 自動拡大・縮小(後日実装メモ)
PNG でも、現在テープ幅printable_dotsに合わせた自動拡大・縮小を可能にしたい。
既定動作は現状維持(自動拡大・縮小 OFFとし、オプションで ON/OFF を切り替える。
実装方針(案):
- lib 側に「現在テープ幅へ raster をフィットする」共通 API を追加する。
- PNG/SVG とも最終的に raster になるため、fit 処理は共通化する。
- CLI には `--fit-current-tape`(仮)を追加し、明示時のみ有効化する。
- まずは `contain` 相当(縦横比維持で収める)を実装する。
検討ポイント:
- 1bit 画像の縮小品質(単純 nearest だと潰れやすい)。
- 既存の length x width 表示/内部座標との整合。
- 既存ユーザー互換性(デフォルト OFF を維持)。

View File

@@ -5,5 +5,5 @@ source "https://rubygems.org"
gemspec gemspec
group :development do group :development do
gem "rubocop", "~> 1.69", require: false gem "rubocop", "~> 1.86", require: false
end end

View File

@@ -3,6 +3,7 @@ PATH
specs: specs:
libptouch (1.0.0) libptouch (1.0.0)
ffi (~> 1.15) ffi (~> 1.15)
rexml
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
@@ -30,6 +31,7 @@ GEM
racc (1.8.1) racc (1.8.1)
rainbow (3.1.1) rainbow (3.1.1)
regexp_parser (2.12.0) regexp_parser (2.12.0)
rexml (3.4.4)
rubocop (1.86.1) rubocop (1.86.1)
json (~> 2.3) json (~> 2.3)
language_server-protocol (~> 3.17.0.2) language_server-protocol (~> 3.17.0.2)
@@ -64,7 +66,7 @@ PLATFORMS
DEPENDENCIES DEPENDENCIES
libptouch! libptouch!
rubocop (~> 1.69) rubocop (~> 1.86)
CHECKSUMS CHECKSUMS
ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383 ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383
@@ -89,6 +91,7 @@ CHECKSUMS
racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f
rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a
regexp_parser (2.12.0) sha256=35a916a1d63190ab5c9009457136ae5f3c0c7512d60291d0d1378ba18ce08ebb regexp_parser (2.12.0) sha256=35a916a1d63190ab5c9009457136ae5f3c0c7512d60291d0d1378ba18ce08ebb
rexml (3.4.4) sha256=19e0a2c3425dfbf2d4fc1189747bdb2f849b6c5e74180401b15734bc97b5d142
rubocop (1.86.1) sha256=44415f3f01d01a21e01132248d2fd0867572475b566ca188a0a42133a08d4531 rubocop (1.86.1) sha256=44415f3f01d01a21e01132248d2fd0867572475b566ca188a0a42133a08d4531
rubocop-ast (1.49.1) sha256=4412f3ee70f6fe4546cc489548e0f6fcf76cafcfa80fa03af67098ffed755035 rubocop-ast (1.49.1) sha256=4412f3ee70f6fe4546cc489548e0f6fcf76cafcfa80fa03af67098ffed755035
ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33 ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33

View File

@@ -1,16 +1,14 @@
# libptouchRuby gem # libptouchRuby gem
[ptouch_label](../) の **libptouch** を [ffi](https://github.com/ffi/ffi) 経由で使うための Gem です。 [ptouch_label](../) の **libptouch** を [ffi](https://github.com/ffi/ffi) 経由で使うための Gem です。C ライブラリと同様、**PT-P900W**(既定 USB PID、**PT-P750W** / **PT-P710BT** などは `open_usb_vid_pid` または CLI の `-p` で選びます。
## 前提 ## 前提
1. リポジトリルートで共有ライブラリをビルドする(`libptouch.so``build/` に生成されます)。 1. リポジトリルートで共有ライブラリをビルドする(`libptouch.so``build/` に生成されます)。
```bash
```bash
cmake -S .. -B ../build cmake -S .. -B ../build
cmake --build ../build cmake --build ../build
``` ```
2. Ruby 3.0 以上と `ffi` gem。 2. Ruby 3.0 以上と `ffi` gem。
## インストール(開発時) ## インストール(開発時)
@@ -20,7 +18,7 @@ cd ruby
bundle install # または gem install ffi bundle install # または gem install ffi
bundle exec rubocop # 任意: スタイルチェック(.rubocop.yml bundle exec rubocop # 任意: スタイルチェック(.rubocop.yml
gem build libptouch.gemspec gem build libptouch.gemspec
gem install ./libptouch-1.0.0.gem gem install ./libptouch-1.0.1.gem
``` ```
ビルド済みの `../build/libptouch.so` を自動で読みに行きます。別のパスにある場合は環境変数で指定できます。 ビルド済みの `../build/libptouch.so` を自動で読みに行きます。別のパスにある場合は環境変数で指定できます。
@@ -31,15 +29,28 @@ export LIBPTOUCH_LIB=/usr/local/lib/libptouch.so
`cmake --install` で共有ライブラリをインストールした場合は、通常は `libptouch` 名でローダが解決します。) `cmake --install` で共有ライブラリをインストールした場合は、通常は `libptouch` 名でローダが解決します。)
## コマンド `ptouch-print-png`PNG のみ ## コマンド `ptouch-label`PNG/SVG
C の `ptouch-print` と同様の流れですが、**PNG 入力のみ**`-w`/`-H` や 1bit ラスターは扱いません)。`gem install` 後は PATH に `ptouch-print-png` が入ります。 C の `ptouch-print` と同様の流れで、**PNG/SVG 入力**`-w`/`-H` や 1bit ラスターは扱いません)を扱います。`gem install` 後は PATH に `ptouch-label` が入ります。
SVG は現在装着テープの印字可能幅に合わせて自動拡大・縮小しますUSB 接続が必要)。
後方互換のため `ptouch-print-png` も引き続き使えます。
オプションは C 側に合わせ、`**-p` / `--pid`** で USB 製品 ID16 進可)を指定できます。省略時は PT-P900W`Libptouch::USB_PID_PTP900W` = `0x2085`)。例: PT-P750W `0x2062`、PT-P710BT `0x20af``libptouch.h` / `Libptouch` 定数と同じ)。
また、`--template`SVGと `--data`JSON/YAMLを使うと `data-field` 属性をキーにした差込印刷が可能です。
`--trim-right[=DOTS]` を付けると、libptouch 側の共通処理でラベル右側の空白ドット列を削減します。`DOTS` 省略時は左余白ドット数を使い、取得失敗時は `0` にフォールバックします。
開発ツリーからそのまま試す例: 開発ツリーからそのまま試す例:
```bash ```bash
bundle exec ruby -I lib exe/ptouch-print-png --help bundle exec ruby -I lib exe/ptouch-label --help
bundle exec ruby -I lib exe/ptouch-print-png -n -f ../samples/your.png bundle exec ruby -I lib exe/ptouch-label -n -f ../samples/your.png
bundle exec ruby -I lib exe/ptouch-label -n -f ../samples/your.svg
bundle exec ruby -I lib exe/ptouch-label -n -f ../samples/your.svg --trim-right
bundle exec ruby -I lib exe/ptouch-label -n -f ../samples/your.svg --trim-right=20
bundle exec ruby -I lib exe/ptouch-label -n --template ../samples/your_template.svg --data ../samples/your_data.yml
bundle exec ruby -I lib exe/ptouch-label --media-info
bundle exec ruby -I lib exe/ptouch-label --status -p 0x2062
bundle exec ruby -I lib exe/ptouch-label -f ../samples/your.png -p 0x20af
``` ```
## 使用例 ## 使用例
@@ -49,8 +60,11 @@ require "libptouch"
Libptouch::Context.new.tap do |ctx| Libptouch::Context.new.tap do |ctx|
ctx.open_usb ctx.open_usb
# PT-P750W など別 PID のとき:
# ctx.open_usb_vid_pid(Libptouch::USB_VID_BROTHER, Libptouch::USB_PID_PTP750W)
p ctx.status_bytes.bytesize # => 32 p ctx.status_bytes.bytesize # => 32
p ctx.status_hash[:tape_kind] # => {:code=>..., :label=>"ラミネートテープ"} など p ctx.status_hash[:tape_kind] # => {:code=>..., :label=>"ラミネートテープ"} など
p ctx.status_hash[:model] # => {:code=>..., :name=>"PT-P750W"} など(対応機種のみ名前あり)
p ctx.status_hash[:status_kind] # => 状態(ステータス種類) p ctx.status_hash[:status_kind] # => 状態(ステータス種類)
ensure ensure
ctx.dispose ctx.dispose
@@ -64,15 +78,29 @@ PNG からラスターへ:
```ruby ```ruby
ctx = Libptouch::Context.new ctx = Libptouch::Context.new
data, w, h = ctx.png_file_to_raster("/path/to/label.png") data, w, h = ctx.png_file_to_raster("/path/to/label.png")
ctx.open_usb ctx.open_usb_vid_pid(Libptouch::USB_VID_BROTHER, Libptouch::USB_PID_PTP750W)
ctx.print_raster(data, width_dots: w, height_dots: h, margin_mm: 0) ctx.print_raster(data, width_dots: w, height_dots: h, margin_mm: 0)
ctx.dispose ctx.dispose
``` ```
SVG から現在テープ幅に合わせてラスターへ:
```ruby
ctx = Libptouch::Context.new
ctx.open_usb
data, w, h = ctx.svg_file_to_raster_fit_current_tape("/path/to/label.svg")
ctx.print_raster(data, width_dots: w, height_dots: h, margin_mm: 0)
ctx.dispose
```
**解像度:** P750W / P710BT はラスター幅方向が **180 dpi**、P900W 系は **360 dpi**C の `libptouch_print` / 各機種ラスター PDF に合わせて画像を用意してください)。
## API の範囲 ## API の範囲
- 実行ファイル `ptouch-print-png` … PNG のみ`-f`, `-t`, `-n`, `-S`, `-V`, `-h`)。ステータスは JSON`status_bytes` を `parse_status` したもの、`raw_bytes` 除く) - 実行ファイル `ptouch-label`(互換: `ptouch-print-png`… PNG/SVG`-f`, `-t`, `-p`, `-n`, `-M`, `-S`, `-V`, `-h`, `--template`, `--data`, `--trim-right[=DOTS]`)。`-M` は現在テープ情報(幅 mm・DPI 等)を JSON 出力、`-S` はステータス JSON 出力、`--trim-right` は右側空白列を削減
- `Libptouch::Context` … `open_usb` / `open_usb_vid_pid` / `close` / `dispose` - `Libptouch::Context` … `open_usb` / `open_usb_vid_pid` / `close` / `dispose`
- `check_raster` / `print_raster` / `png_file_to_raster` / `status_bytes` / `status_hash` - `check_raster` / `print_raster` / `png_file_to_raster` / `svg_file_to_raster_fit_current_tape` / `status_bytes` / `status_hash` / `current_media_info`
- `current_media_info` には `print_dpi` / `feed_dpi` / `tape_width_mm` / `printable_height_dots` / `min_feed_mm` などを含む(テープ幅方向は `printable_height_dots`
- `Libptouch.parse_status(raw)` … 32 バイトを Hash に展開(機種・テープ幅・**テープ種類**・色・**状態status_kind**・エラービット・`raw_hex` など) - `Libptouch.parse_status(raw)` … 32 バイトを Hash に展開(機種・テープ幅・**テープ種類**・色・**状態status_kind**・エラービット・`raw_hex` など)
- C の `libptouch_status_fprint``FILE *`)は FFI からはバインドしていません。テキスト出力の代わりに `parse_status` / `status_hash` を使ってください。 - C の `libptouch_status_fprint``FILE` *)は FFI からはバインドしていません。テキスト出力の代わりに `parse_status` / `status_hash` を使ってください。

6
ruby/exe/ptouch-label Executable file
View File

@@ -0,0 +1,6 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
require "libptouch/cli/label_print"
exit Libptouch::Cli::LabelPrint.run(ARGV)

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
# frozen_string_literal: true # frozen_string_literal: true
require "libptouch/cli/png_print" require "libptouch/cli/label_print"
exit Libptouch::Cli::PngPrint.run(ARGV) exit Libptouch::Cli::LabelPrint.run(ARGV)

View File

@@ -22,6 +22,8 @@ module Libptouch
USB_VID_BROTHER = 0x04f9 USB_VID_BROTHER = 0x04f9
USB_PID_PTP900W = 0x2085 USB_PID_PTP900W = 0x2085
USB_PID_PTP750W = 0x2062
USB_PID_PTP710BT = 0x20af
STATUS_LENGTH = 32 STATUS_LENGTH = 32
PNG_DEFAULT_THRESHOLD = 128 PNG_DEFAULT_THRESHOLD = 128

View File

@@ -28,10 +28,28 @@ module Libptouch
:_pad, [:uint8, 3] :_pad, [:uint8, 3]
end end
class MediaInfo < FFI::Struct
layout :media_width_code, :uint8,
:media_kind_code, :uint8,
:_pad0, [:uint8, 6],
:print_dpi, :double,
:feed_dpi, :double,
:tape_width_mm, :double,
:printable_dots, :uint16,
:left_margin_dots, :uint16,
:right_margin_dots, :uint16,
:min_feed_dots, :uint16,
:min_feed_mm, :double
end
class PngOptions < FFI::Struct class PngOptions < FFI::Struct
layout :threshold, :uint8 layout :threshold, :uint8
end end
class SvgOptions < FFI::Struct
layout :threshold, :uint8
end
attach_function :libptouch_create, [], :pointer attach_function :libptouch_create, [], :pointer
attach_function :libptouch_destroy, [:pointer], :void attach_function :libptouch_destroy, [:pointer], :void
attach_function :libptouch_strerror, [:pointer], :string attach_function :libptouch_strerror, [:pointer], :string
@@ -40,9 +58,14 @@ module Libptouch
attach_function :libptouch_open_usb_vid_pid, %i[pointer uint16 uint16], :int attach_function :libptouch_open_usb_vid_pid, %i[pointer uint16 uint16], :int
attach_function :libptouch_close, [:pointer], :void attach_function :libptouch_close, [:pointer], :void
attach_function :libptouch_check_raster, %i[pointer pointer size_t pointer], :int attach_function :libptouch_check_raster, %i[pointer pointer size_t pointer], :int
attach_function :libptouch_get_current_media_info, %i[pointer pointer], :int
attach_function :libptouch_trim_right_blank_columns,
%i[pointer pointer size_t pointer uint16 pointer pointer pointer], :int
attach_function :libptouch_print_raster, %i[pointer pointer size_t pointer], :int attach_function :libptouch_print_raster, %i[pointer pointer size_t pointer], :int
attach_function :libptouch_png_file_to_raster, attach_function :libptouch_png_file_to_raster,
%i[pointer string pointer pointer pointer pointer], :int %i[pointer string pointer pointer pointer pointer], :int
attach_function :libptouch_svg_file_to_raster_fit_current_tape,
%i[pointer string pointer pointer pointer pointer], :int
attach_function :libptouch_free_raster, [:pointer], :void attach_function :libptouch_free_raster, [:pointer], :void
attach_function :libptouch_get_status, %i[pointer pointer], :int attach_function :libptouch_get_status, %i[pointer pointer], :int
end end

View File

@@ -0,0 +1,409 @@
# frozen_string_literal: true
require "json"
require "optparse"
require "rexml/document"
require "tempfile"
require "yaml"
require "libptouch"
module Libptouch
module Cli
# PNG/SVG を扱う ptouch-print 相当の CLI1bit ラスター経路なし)。
module LabelPrint
module_function
def run(argv)
opts = parse(argv)
return 2 if opts.nil?
return run_version if opts[:version]
return run_help if opts[:help]
return run_media_info(opts) if opts[:media_info]
if opts[:status]
warn_unused_file_options(opts)
return run_status(opts)
end
if opts[:template] || opts[:data]
return usage_error("--template and --data must be used together") if opts[:template].to_s.empty? || opts[:data].to_s.empty?
return usage_error("-f and --template/--data cannot be used together") unless opts[:file].to_s.empty?
return run_template_print(opts)
end
return usage_error("-f is required (or use --template/--data, or --status)") if opts[:file].to_s.empty?
path = opts[:file]
kind = image_kind(path)
return usage_error("not a PNG/SVG file: #{path}") if kind.nil?
run_print(path, opts, kind)
end
def png_file?(path)
return true if path.downcase.end_with?(".png")
File.open(path, "rb") do |f|
sig = f.read(8)
sig == "\x89PNG\r\n\x1a\n".b
end
rescue Errno::ENOENT, Errno::EACCES => e
warn "open #{path}: #{e.message}"
false
end
def svg_file?(path)
path.downcase.end_with?(".svg")
end
def image_kind(path)
return :png if png_file?(path)
return :svg if svg_file?(path)
nil
end
def parse(argv)
opts_hash = default_cli_opts
build_cli_parser(opts_hash).parse!(argv)
return nil unless threshold_option_ok?(opts_hash)
return nil unless trim_right_option_ok?(opts_hash)
return nil unless usb_pid_option_ok?(opts_hash)
opts_hash.delete(:usb_pid_invalid)
opts_hash
end
def default_cli_opts
{
file: nil,
template: nil,
data: nil,
threshold: nil,
usb_pid: nil,
usb_pid_invalid: false,
dry_run: false,
trim_right: nil,
media_info: false,
status: false,
version: false,
help: false
}
end
def pid_option_description
p900 = Libptouch::USB_PID_PTP900W
p750 = Libptouch::USB_PID_PTP750W
p710 = Libptouch::USB_PID_PTP710BT
"USB 製品 ID16 進可)。既定 P900W 0x#{p900.to_s(16)}; " \
"P750W 0x#{p750.to_s(16)}; P710BT 0x#{p710.to_s(16)}"
end
def apply_usb_pid_option(opts_hash, pid_str)
opts_hash[:usb_pid] = Integer(pid_str, 0)
rescue ArgumentError
warn "invalid --pid: #{pid_str.inspect}"
opts_hash[:usb_pid_invalid] = true
end
def threshold_option_ok?(opts_hash)
return true if opts_hash[:threshold].nil? || (0..255).cover?(opts_hash[:threshold])
warn "-t must be 0..255"
false
end
def trim_right_option_ok?(opts_hash)
v = opts_hash[:trim_right]
return true if v.nil? || v == :auto
return true if v.is_a?(Integer) && v >= 0
warn "--trim-right must be omitted, or >= 0"
false
end
def usb_pid_option_ok?(opts_hash)
return false if opts_hash[:usb_pid_invalid]
return true if opts_hash[:usb_pid].nil?
return true if opts_hash[:usb_pid].between?(1, 0xFFFF)
warn "-p/--pid must be 1..0xFFFF"
false
end
def build_cli_parser(opts_hash)
OptionParser.new do |p|
p.banner = usage_banner
p.separator ""
p.on("-f", "--file PATH", "入力 PNG/SVG ファイル") { |v| opts_hash[:file] = v }
p.on("--template PATH", "差込用 SVG テンプレート") { |v| opts_hash[:template] = v }
p.on("--data PATH", "差込データ JSON/YAML ファイル") { |v| opts_hash[:data] = v }
p.on("-t", "--threshold N", Integer,
"しきい値 0255既定 #{Libptouch::PNG_DEFAULT_THRESHOLD}、PNG/SVG") do |v|
opts_hash[:threshold] = v
end
p.on("-p", "--pid PID", pid_option_description) do |v|
apply_usb_pid_option(opts_hash, v)
end
p.on("-n", "--dry-run", "読み込みと検証のみUSB なし)") { opts_hash[:dry_run] = true }
p.on("--trim-right[=DOTS]", Integer,
"右側空白を削減。DOTS省略時は左余白失敗時 0") do |v|
opts_hash[:trim_right] = v.nil? ? :auto : v
end
p.on("-M", "--media-info", "現在テープ情報(幅/DPI/余白)を JSON で表示して終了") do
opts_hash[:media_info] = true
end
p.on("-S", "--status", "ステータスを JSON で表示して終了") do
opts_hash[:status] = true
end
p.on("-V", "--version", "バージョンを表示して終了") { opts_hash[:version] = true }
p.on("-h", "--help", "このヘルプ") { opts_hash[:help] = true }
end
end
def usage_banner
<<~BANNER
Usage: ptouch-label [options]
PNG/SVG 使-w/-H
--template/--data SVG
SVG USB
--trim-right[=DOTS]
--status / --media-info -f
BANNER
end
def warn_unused_file_options(opts)
return unless opts[:file] || opts[:template] || opts[:data] || opts[:dry_run] || !opts[:trim_right].nil? || !opts[:threshold].nil?
warn "warning: options other than --status are ignored"
end
def run_version
puts "ptouch-label #{Libptouch::VERSION}"
0
end
def run_help
puts usage_banner
puts ""
puts parser_help_text
0
end
def parser_help_text
opts_hash = default_cli_opts
p = OptionParser.new do |parser|
parser.banner = "ptouch-label [options]"
parser.on("-f", "--file PATH", "入力 PNG/SVG ファイル") { |v| opts_hash[:file] = v }
parser.on("--template PATH", "差込用 SVG テンプレート") { |v| opts_hash[:template] = v }
parser.on("--data PATH", "差込データ JSON/YAML ファイル") { |v| opts_hash[:data] = v }
parser.on("-t", "--threshold N", Integer,
"しきい値 0255既定 #{Libptouch::PNG_DEFAULT_THRESHOLD}、PNG/SVG") do |v|
opts_hash[:threshold] = v
end
parser.on("-p", "--pid PID", pid_option_description) do |v|
apply_usb_pid_option(opts_hash, v)
end
parser.on("-n", "--dry-run", "読み込みと検証のみUSB なし)") { opts_hash[:dry_run] = true }
parser.on("--trim-right[=DOTS]", Integer,
"右側空白を削減。DOTS省略時は左余白失敗時 0") do |v|
opts_hash[:trim_right] = v.nil? ? :auto : v
end
parser.on("-M", "--media-info", "現在テープ情報(幅/DPI/余白)を JSON で表示して終了") do
opts_hash[:media_info] = true
end
parser.on("-S", "--status", "ステータスを JSON で表示して終了") do
opts_hash[:status] = true
end
parser.on("-V", "--version", "バージョンを表示して終了") { opts_hash[:version] = true }
parser.on("-h", "--help", "このヘルプ") { opts_hash[:help] = true }
end
p.help
end
def open_usb_for_opts(ctx, opts)
if opts[:usb_pid]
ctx.open_usb_vid_pid(Libptouch::USB_VID_BROTHER, opts[:usb_pid])
else
ctx.open_usb
end
end
def run_template_print(opts)
svg_text = render_svg_template(opts[:template], load_merge_data(opts[:data]))
Tempfile.create(["ptouch-merge-", ".svg"]) do |tmp|
tmp.binmode
tmp.write(svg_text)
tmp.flush
run_print(tmp.path, opts, :svg)
end
rescue REXML::ParseException => e
warn "template parse error: #{e.message}"
1
rescue Errno::ENOENT, Errno::EACCES => e
warn e.message
1
rescue JSON::ParserError, Psych::SyntaxError => e
warn "data parse error: #{e.message}"
1
rescue ArgumentError => e
warn "data error: #{e.message}"
1
end
def load_merge_data(path)
text = File.read(path, encoding: "UTF-8")
ext = File.extname(path).downcase
parsed = if ext == ".json"
JSON.parse(text)
else
YAML.safe_load(text, permitted_classes: [], aliases: false)
end
unless parsed.is_a?(Hash)
raise ArgumentError, "data must be a key-value object/hash"
end
parsed.transform_keys(&:to_s)
end
def render_svg_template(path, data)
xml = File.read(path, encoding: "UTF-8")
doc = REXML::Document.new(xml)
doc.elements.each("//text[@data-field]") do |el|
# If descendants also have data-field (e.g. tspan placeholders),
# keep node structure and let element-level replacements handle them.
next if el.elements[".//*[@data-field]"]
key = el.attributes["data-field"].to_s
next unless data.key?(key)
replace_text_element_content(el, data[key].to_s)
end
doc.elements.each("//tspan[@data-field]") do |el|
key = el.attributes["data-field"].to_s
next unless data.key?(key)
replace_text_element_content(el, data[key].to_s)
end
out = +""
formatter = REXML::Formatters::Default.new
formatter.write(doc, out)
out
end
def replace_text_element_content(text_element, value)
# Remove all child nodes first so mixed content (<tspan>, text nodes, etc.)
# gets replaced consistently by merge data.
text_element.children.to_a.each { |child| text_element.delete(child) }
text_element.add(REXML::Text.new(value, true))
end
def run_status(opts)
ctx = nil
begin
ctx = Libptouch::Context.new
open_usb_for_opts(ctx, opts)
h = Libptouch.parse_status(ctx.status_bytes)
h.delete(:raw_bytes)
puts JSON.pretty_generate(h)
0
rescue Libptouch::Error => e
warn "get_status: #{e.message}"
1
ensure
ctx&.dispose
end
end
def run_media_info(opts)
warn_unused_file_options(opts)
ctx = nil
begin
ctx = Libptouch::Context.new
open_usb_for_opts(ctx, opts)
puts JSON.pretty_generate(ctx.current_media_info)
0
rescue Libptouch::Error => e
warn "media_info: #{e.message}"
1
ensure
ctx&.dispose
end
end
def run_print(path, opts, kind)
ctx = nil
begin
ctx = Libptouch::Context.new
usb_opened = false
threshold = opts[:threshold]
data, width, height = if kind == :svg
open_usb_for_opts(ctx, opts)
usb_opened = true
if threshold.nil?
ctx.svg_file_to_raster_fit_current_tape(path)
else
ctx.svg_file_to_raster_fit_current_tape(path, threshold: threshold)
end
elsif threshold.nil?
ctx.png_file_to_raster(path)
else
ctx.png_file_to_raster(path, threshold: threshold)
end
unless opts[:trim_right].nil?
trim_pad, usb_opened = resolve_trim_right_pad_dots(ctx, opts, usb_opened)
data, width, height = ctx.trim_right_blank_columns(
data,
width_dots: width,
height_dots: height,
right_padding_dots: trim_pad
)
end
ctx.check_raster(data, width_dots: width, height_dots: height)
if opts[:dry_run]
puts "dry-run OK: #{data.bytesize} bytes, src #{width}x#{height} dots (print lengthxwidth #{width}x#{height})"
return 0
end
open_usb_for_opts(ctx, opts) if kind == :png && !usb_opened
ctx.print_raster(data, width_dots: width, height_dots: height)
0
rescue Libptouch::Error => e
warn e.message
1
ensure
ctx&.dispose
end
end
def resolve_trim_right_pad_dots(ctx, opts, usb_opened)
trim = opts[:trim_right]
return [trim, usb_opened] if trim.is_a?(Integer)
begin
unless usb_opened
open_usb_for_opts(ctx, opts)
usb_opened = true
end
info = ctx.current_media_info
[Integer(info[:left_margin_dots] || 0), usb_opened]
rescue Libptouch::Error
[0, usb_opened]
end
end
def usage_error(msg)
warn msg
warn "(try ptouch-label --help)"
2
end
end
end
end

View File

@@ -1,182 +1,8 @@
# frozen_string_literal: true # Backward-compatibility shim.
require "libptouch/cli/label_print"
require "json"
require "optparse"
require "libptouch"
module Libptouch module Libptouch
module Cli module Cli
# PNG のみを扱う ptouch-print 相当の CLI1bit ラスター経路なし)。 PngPrint = LabelPrint
module PngPrint
module_function
def run(argv)
opts = parse(argv)
return 2 if opts.nil?
return run_version if opts[:version]
return run_help if opts[:help]
if opts[:status]
warn_unused_file_options(opts)
return run_status
end
return usage_error("-f is required (or use --status)") if opts[:file].to_s.empty?
path = opts[:file]
return usage_error("not a PNG file: #{path}") unless png_file?(path)
run_print(path, opts)
end
def png_file?(path)
return true if path.downcase.end_with?(".png")
File.open(path, "rb") do |f|
sig = f.read(8)
sig == "\x89PNG\r\n\x1a\n".b
end
rescue Errno::ENOENT, Errno::EACCES => e
warn "open #{path}: #{e.message}"
false
end
def parse(argv)
o = {
file: nil,
threshold: nil,
dry_run: false,
status: false,
version: false,
help: false
}
parser = OptionParser.new do |p|
p.banner = usage_banner
p.separator ""
p.on("-f", "--file PATH", "入力 PNG ファイル") { |v| o[:file] = v }
p.on("-t", "--threshold N", Integer,
"二値化しきい値 0255既定 #{Libptouch::PNG_DEFAULT_THRESHOLD}") do |v|
o[:threshold] = v
end
p.on("-n", "--dry-run", "読み込みと検証のみUSB なし)") { o[:dry_run] = true }
p.on("-S", "--status", "USB プリンタのステータスを JSON で表示して終了") { o[:status] = true }
p.on("-V", "--version", "バージョンを表示して終了") { o[:version] = true }
p.on("-h", "--help", "このヘルプ") { o[:help] = true }
end
parser.parse!(argv)
unless o[:threshold].nil? || (0..255).cover?(o[:threshold])
warn "-t must be 0..255"
return nil
end
o
end
def usage_banner
<<~BANNER
Usage: ptouch-print-png [options]
PNG -w/-H
--status -f
BANNER
end
def warn_unused_file_options(opts)
return unless opts[:file] || opts[:dry_run] || !opts[:threshold].nil?
warn "warning: options other than --status are ignored"
end
def run_version
puts "ptouch-print-png #{Libptouch::VERSION}"
0
end
def run_help
puts usage_banner
puts ""
puts parser_help_text
0
end
def parser_help_text
o = {
file: nil,
threshold: nil,
dry_run: false,
status: false,
version: false,
help: false
}
p = OptionParser.new do |parser|
parser.banner = "ptouch-print-png [options]"
parser.on("-f", "--file PATH", "入力 PNG ファイル") { |v| o[:file] = v }
parser.on("-t", "--threshold N", Integer,
"二値化しきい値 0255既定 #{Libptouch::PNG_DEFAULT_THRESHOLD}") do |v|
o[:threshold] = v
end
parser.on("-n", "--dry-run", "読み込みと検証のみUSB なし)") { o[:dry_run] = true }
parser.on("-S", "--status", "USB プリンタのステータスを JSON で表示して終了") { o[:status] = true }
parser.on("-V", "--version", "バージョンを表示して終了") { o[:version] = true }
parser.on("-h", "--help", "このヘルプ") { o[:help] = true }
end
p.help
end
def run_status
ctx = nil
begin
ctx = Libptouch::Context.new
ctx.open_usb
h = Libptouch.parse_status(ctx.status_bytes)
h.delete(:raw_bytes)
puts JSON.pretty_generate(h)
0
rescue Libptouch::Error => e
warn "get_status: #{e.message}"
1
ensure
ctx&.dispose
end
end
def run_print(path, opts)
ctx = nil
begin
ctx = Libptouch::Context.new
threshold = opts[:threshold]
data, width, height = if threshold.nil?
ctx.png_file_to_raster(path)
else
ctx.png_file_to_raster(path, threshold: threshold)
end
ctx.check_raster(data, width_dots: width, height_dots: height)
if opts[:dry_run]
puts "dry-run OK: #{data.bytesize} bytes, #{width}x#{height} dots"
return 0
end
ctx.open_usb
ctx.print_raster(data, width_dots: width, height_dots: height)
0
rescue Libptouch::Error => e
warn e.message
1
ensure
ctx&.dispose
end
end
def usage_error(msg)
warn msg
warn "(try ptouch-print-png --help)"
2
end
end
end end
end end

View File

@@ -87,7 +87,52 @@ module Libptouch
raw = out_pp.read_pointer raw = out_pp.read_pointer
raise Libptouch::Error.new(OK, "null raster from PNG") if raw.null? raise Libptouch::Error.new(OK, "null raster from PNG") if raw.null?
len = out_len.read_size_t len = out_len.get(:size_t, 0)
bytes = raw.read_bytes(len)
Binding.libptouch_free_raster(raw)
[bytes, out_params[:width_dots], out_params[:height_dots]]
end
def svg_file_to_raster_fit_current_tape(path, threshold: nil)
opt_ptr = nil
unless threshold.nil?
o = Binding::SvgOptions.new
o[:threshold] = threshold
opt_ptr = o.pointer
end
out_pp = FFI::MemoryPointer.new(:pointer)
out_len = FFI::MemoryPointer.new(:size_t)
out_params = Binding::RasterParams.new
raise_on_error(Binding.libptouch_svg_file_to_raster_fit_current_tape(
@native, path, opt_ptr, out_pp, out_len, out_params.pointer
))
raw = out_pp.read_pointer
raise Libptouch::Error.new(OK, "null raster from SVG") if raw.null?
len = out_len.get(:size_t, 0)
bytes = raw.read_bytes(len)
Binding.libptouch_free_raster(raw)
[bytes, out_params[:width_dots], out_params[:height_dots]]
end
def trim_right_blank_columns(data, width_dots:, height_dots:, margin_mm: 0, right_padding_dots: 0)
in_params = Binding::RasterParams.new
in_params[:width_dots] = width_dots
in_params[:height_dots] = height_dots
in_params[:margin_mm] = margin_mm
in_buf = FFI::MemoryPointer.new(:uint8, data.bytesize)
in_buf.put_bytes(0, data)
out_pp = FFI::MemoryPointer.new(:pointer)
out_len = FFI::MemoryPointer.new(:size_t)
out_params = Binding::RasterParams.new
raise_on_error(Binding.libptouch_trim_right_blank_columns(
@native, in_buf, data.bytesize, in_params.pointer,
right_padding_dots, out_pp, out_len, out_params.pointer
))
raw = out_pp.read_pointer
raise Libptouch::Error.new(OK, "null raster from trim_right_blank_columns") if raw.null?
len = out_len.get(:size_t, 0)
bytes = raw.read_bytes(len) bytes = raw.read_bytes(len)
Binding.libptouch_free_raster(raw) Binding.libptouch_free_raster(raw)
[bytes, out_params[:width_dots], out_params[:height_dots]] [bytes, out_params[:width_dots], out_params[:height_dots]]
@@ -102,5 +147,22 @@ module Libptouch
def status_hash def status_hash
Libptouch.parse_status(status_bytes) Libptouch.parse_status(status_bytes)
end end
def current_media_info
info = Binding::MediaInfo.new
raise_on_error(Binding.libptouch_get_current_media_info(@native, info.pointer))
{
media_width_code: info[:media_width_code],
media_kind_code: info[:media_kind_code],
print_dpi: info[:print_dpi],
feed_dpi: info[:feed_dpi],
tape_width_mm: info[:tape_width_mm],
printable_height_dots: info[:printable_dots],
left_margin_dots: info[:left_margin_dots],
right_margin_dots: info[:right_margin_dots],
min_feed_dots: info[:min_feed_dots],
min_feed_mm: info[:min_feed_mm]
}
end
end end
end end

View File

@@ -104,7 +104,9 @@ module Libptouch
0x6F => "PT-P900W", 0x6F => "PT-P900W",
0x70 => "PT-P950NW", 0x70 => "PT-P950NW",
0x71 => "PT-P900", 0x71 => "PT-P900",
0x78 => "PT-P910BT" 0x78 => "PT-P910BT",
0x68 => "PT-P750W",
0x76 => "PT-P710BT"
}.freeze }.freeze
MEDIA_WIDTH = { MEDIA_WIDTH = {

View File

@@ -1,5 +1,5 @@
# frozen_string_literal: true # frozen_string_literal: true
module Libptouch module Libptouch
VERSION = "1.0.0" VERSION = "1.0.1"
end end

View File

@@ -13,6 +13,7 @@ Gem::Specification.new do |spec|
spec.summary = "FFI bindings for libptouch (Brother P-touch USB raster printing)" spec.summary = "FFI bindings for libptouch (Brother P-touch USB raster printing)"
spec.description = [ spec.description = [
"Ruby wrapper around the ptouch_label C library libptouch.", "Ruby wrapper around the ptouch_label C library libptouch.",
"Supports PT-P900W, PT-P750W, PT-P710BT and related open_usb_vid_pid / --pid.",
"Requires libptouch shared library (libusb, libpng)." "Requires libptouch shared library (libusb, libpng)."
].join(" ") ].join(" ")
spec.license = "MIT" spec.license = "MIT"
@@ -24,8 +25,9 @@ Gem::Specification.new do |spec|
spec.files = Dir.chdir(__dir__) { Dir["lib/**/*.rb", "exe/*", "README.md"] } spec.files = Dir.chdir(__dir__) { Dir["lib/**/*.rb", "exe/*", "README.md"] }
spec.bindir = "exe" spec.bindir = "exe"
spec.executables = ["ptouch-print-png"] spec.executables = ["ptouch-label", "ptouch-print-png"]
spec.require_paths = ["lib"] spec.require_paths = ["lib"]
spec.add_dependency "ffi", "~> 1.15" spec.add_dependency "ffi", "~> 1.15"
spec.add_dependency "rexml"
end end

View File

@@ -1,9 +1,16 @@
# samples # samples
試験・デモ用のサンプル画像PNG など)を置くディレクトリです。リポジトリに含める場合はライセンス・著作権に注意してください。 試験・デモ用のサンプル画像PNG/SVGや差込印刷データJSON/YAML)を置くディレクトリです。リポジトリに含める場合はライセンス・著作権に注意してください。
例(ドライラン): 例(ドライラン):
```bash ```bash
./build/ptouch-print -n -f samples/your.png ./build/ptouch-print -n -f samples/your.png
``` ```
差込印刷の例Ruby CLI:
```bash
ruby -I ruby/lib ruby/exe/ptouch-label -n --template samples/merge_template.svg --data samples/merge_data.yml
ruby -I ruby/lib ruby/exe/ptouch-label -n --template samples/merge_template.svg --data samples/merge_data.json
```

BIN
samples/aBw70.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

5
samples/merge_data.json Normal file
View File

@@ -0,0 +1,5 @@
{
"title": "出荷ラベル",
"left": "品番 ABC-123",
"right": "数量 24"
}

3
samples/merge_data.yml Normal file
View File

@@ -0,0 +1,3 @@
title: "出荷ラベル"
left: "品番 ABC-123"
right: "数量 24"

View File

@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" width="720" height="220" viewBox="0 0 720 220">
<rect width="720" height="220" fill="#FFFFFF" />
<text x="24" y="86" data-field="title" font-size="56" fill="#000000">TITLE</text>
<text x="24" y="160" font-size="36" fill="#000000">
<tspan data-field="left">LEFT</tspan>
<tspan dx="20">/</tspan>
<tspan dx="20" data-field="right">RIGHT</tspan>
</text>
</svg>

After

Width:  |  Height:  |  Size: 417 B

40
samples/sample.svg Normal file
View File

@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="84.467712mm"
height="27.798302mm"
viewBox="0 0 84.467714 27.798302"
version="1.1"
id="svg5"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs2" />
<g
id="layer1"
transform="matrix(1.7339801,0,0,1.6120341,-33.843459,-17.498256)">
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:3.79808px;font-family:'Noto Sans CJK JP';-inkscape-font-specification:'Noto Sans CJK JP, Medium';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#000000;stroke-width:0.383995;stroke-dasharray:none"
x="20.38328"
y="14.980056"
id="text234"><tspan
id="tspan232"
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:3.79808px;font-family:'Noto Sans CJK JP';-inkscape-font-specification:'Noto Sans CJK JP, Medium';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.383995;stroke-dasharray:none"
x="20.38328"
y="14.980056">印刷出来るかな?</tspan><tspan
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:3.79808px;font-family:'Noto Sans CJK JP';-inkscape-font-specification:'Noto Sans CJK JP, Medium';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.383995;stroke-dasharray:none"
x="20.38328"
y="18.948805"
id="tspan236">取り敢えず何か書いてみる</tspan><tspan
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:3.79808px;font-family:'Noto Sans CJK JP';-inkscape-font-specification:'Noto Sans CJK JP, Medium';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.383995;stroke-dasharray:none"
x="20.38328"
y="22.917555"
id="tspan238">4行くらい入りそう</tspan><tspan
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:3.79808px;font-family:'Noto Sans CJK JP';-inkscape-font-specification:'Noto Sans CJK JP, Medium';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.383995;stroke-dasharray:none"
x="20.38328"
y="26.886305"
id="tspan240">123456</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -13,6 +13,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <strings.h> #include <strings.h>
#include <unistd.h>
static void usage(const char *argv0) static void usage(const char *argv0)
{ {
@@ -21,13 +22,17 @@ static void usage(const char *argv0)
" -f, --file PATH 入力ファイル\n" " -f, --file PATH 入力ファイル\n"
" -w, --width DOTS 1bit ラスター時: 幅(ドット)\n" " -w, --width DOTS 1bit ラスター時: 幅(ドット)\n"
" -H, --height DOTS 1bit ラスター時: 高さ(ドット)\n" " -H, --height DOTS 1bit ラスター時: 高さ(ドット)\n"
" -t, --threshold N PNG 二値化しきい値 0255既定 %u、PNG のみ\n" " -t, --threshold N しきい値 0255既定 %u、PNG/SVG\n"
" --trim-right[=DOTS] 右側空白を削減(省略時は左余白、失敗時 0\n"
" -n, --dry-run 読み込みと check_raster のみUSB なし)\n" " -n, --dry-run 読み込みと check_raster のみUSB なし)\n"
" -S, --status USB 接続プリンタのステータス(テープ種・幅・色等)を表示して終了\n" " -v, --verbose 印刷前情報と印刷後ステータスを標準出力\n"
" -p, --pid HEX USB 製品 ID既定: P900W の 0x2085。例: P750W 0x2062、P710BT 0x20af\n"
" -S, --status ステータスを表示して終了\n"
" -V, --version バージョンを表示して終了\n" " -V, --version バージョンを表示して終了\n"
" -h, --help このヘルプ\n" " -h, --help このヘルプ\n"
"\n" "\n"
"PNG の場合は幅・高さはファイルから取得-w/-H 不要)。\n" "PNG は画像サイズを使用-w/-H 不要)。\n"
"SVG は現在テープ幅に自動フィットUSB 必須)。\n"
"1bit packed ラスター(行優先)の場合は -f -w -H が必須。\n" "1bit packed ラスター(行優先)の場合は -f -w -H が必須。\n"
"--status のときは -f は不要(他オプションは無視されます)。\n", "--status のときは -f は不要(他オプションは無視されます)。\n",
argv0, (unsigned)LIBPTOUCH_PNG_DEFAULT_THRESHOLD); argv0, (unsigned)LIBPTOUCH_PNG_DEFAULT_THRESHOLD);
@@ -53,6 +58,14 @@ static int input_is_png(const char *path)
return memcmp(sig, png_magic, sizeof(png_magic)) == 0; 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) static int read_file(const char *path, uint8_t **out, size_t *out_len)
{ {
FILE *fp = fopen(path, "rb"); FILE *fp = fopen(path, "rb");
@@ -90,12 +103,80 @@ static int read_file(const char *path, uint8_t **out, size_t *out_len)
return 0; return 0;
} }
static void verbose_print_pre_print_info(libptouch_ctx *ctx,
const libptouch_raster_params_t *params,
size_t data_len)
{
if (!ctx || !params)
return;
printf("=== verbose: pre-print info ===\n");
printf("raster bytes: %zu\n", data_len);
printf("raster size: %ux%u dots (length x width)\n",
(unsigned)params->width_dots, (unsigned)params->height_dots);
printf("margin_mm: %u\n", (unsigned)params->margin_mm);
libptouch_media_info_t mi;
if (libptouch_get_current_media_info(ctx, &mi) == LIBPTOUCH_OK) {
printf("media width code: 0x%02X\n", (unsigned)mi.media_width_code);
printf("media kind code: 0x%02X\n", (unsigned)mi.media_kind_code);
printf("tape width: %.1f mm\n", mi.tape_width_mm);
printf("printable dots: %u\n", (unsigned)mi.printable_dots);
printf("left/right margins: %u/%u dots\n",
(unsigned)mi.left_margin_dots, (unsigned)mi.right_margin_dots);
printf("print/feed dpi: %.1f/%.1f\n", mi.print_dpi, mi.feed_dpi);
} else {
printf("media info: unavailable (%s)\n", libptouch_strerror(ctx));
}
uint8_t st[LIBPTOUCH_STATUS_LENGTH];
if (libptouch_get_status(ctx, st) == LIBPTOUCH_OK) {
libptouch_status_fprint(stdout, st);
} else {
printf("status: unavailable (%s)\n", libptouch_strerror(ctx));
}
}
static void verbose_print_post_print_status(libptouch_ctx *ctx, const char *label)
{
if (!ctx)
return;
printf("=== verbose: post-print status (%s) ===\n", label);
/*
* 印刷直後は phase change (status[18] != 0x00) が続くことがあるため、
* 短時間ポーリングで最終状態まで追跡する。
*/
const int max_polls = 12; /* ~2.4s */
const useconds_t poll_interval_us = 200000;
for (int i = 0; i < max_polls; i++) {
uint8_t st[LIBPTOUCH_STATUS_LENGTH];
if (libptouch_get_status(ctx, st) != LIBPTOUCH_OK) {
printf("status poll %d/%d: unavailable (%s)\n", i + 1,
max_polls, libptouch_strerror(ctx));
} else {
printf("status poll %d/%d:\n", i + 1, max_polls);
libptouch_status_fprint(stdout, st);
if (st[18] == 0x00u) {
printf("post-print polling settled: print end.\n");
return;
}
}
usleep(poll_interval_us);
}
printf("post-print polling timeout: phase did not settle.\n");
}
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
const char *file = NULL; const char *file = NULL;
unsigned width = 0, height = 0; unsigned width = 0, height = 0;
unsigned threshold = LIBPTOUCH_PNG_DEFAULT_THRESHOLD; unsigned threshold = LIBPTOUCH_PNG_DEFAULT_THRESHOLD;
unsigned usb_pid_arg = 0;
int dry_run = 0; int dry_run = 0;
int verbose = 0;
int trim_right_enabled = 0;
int trim_right_auto = 0;
unsigned trim_right_dots = 0;
int has_threshold = 0; int has_threshold = 0;
int want_status = 0; int want_status = 0;
static struct option longopts[] = { static struct option longopts[] = {
@@ -103,7 +184,10 @@ int main(int argc, char **argv)
{ "height", required_argument, NULL, 'H' }, { "height", required_argument, NULL, 'H' },
{ "file", required_argument, NULL, 'f' }, { "file", required_argument, NULL, 'f' },
{ "threshold", required_argument, NULL, 't' }, { "threshold", required_argument, NULL, 't' },
{ "trim-right", optional_argument, NULL, 'r' },
{ "dry-run", no_argument, NULL, 'n' }, { "dry-run", no_argument, NULL, 'n' },
{ "verbose", no_argument, NULL, 'v' },
{ "pid", required_argument, NULL, 'p' },
{ "status", no_argument, NULL, 'S' }, { "status", no_argument, NULL, 'S' },
{ "version", no_argument, NULL, 'V' }, { "version", no_argument, NULL, 'V' },
{ "help", no_argument, NULL, 'h' }, { "help", no_argument, NULL, 'h' },
@@ -111,7 +195,7 @@ int main(int argc, char **argv)
}; };
int c; int c;
while ((c = getopt_long(argc, argv, "w:H:f:t:nhSV", longopts, NULL)) != while ((c = getopt_long(argc, argv, "w:H:f:t:r::p:nvhSV", longopts, NULL)) !=
-1) { -1) {
switch (c) { switch (c) {
case 'w': case 'w':
@@ -134,6 +218,30 @@ int main(int argc, char **argv)
case 'n': case 'n':
dry_run = 1; dry_run = 1;
break; break;
case 'v':
verbose = 1;
break;
case 'r':
trim_right_enabled = 1;
if (!optarg) {
trim_right_auto = 1;
trim_right_dots = 0;
break;
}
trim_right_auto = 0;
trim_right_dots = (unsigned)strtoul(optarg, NULL, 10);
if (trim_right_dots > 0xFFFFu) {
fprintf(stderr, "--trim-right must be 0..65535\n");
return 2;
}
break;
case 'p':
usb_pid_arg = (unsigned)strtoul(optarg, NULL, 0);
if (usb_pid_arg == 0u || usb_pid_arg > 0xFFFFu) {
fprintf(stderr, "-p/--pid must be 1..0xFFFF\n");
return 2;
}
break;
case 'S': case 'S':
want_status = 1; want_status = 1;
break; break;
@@ -159,12 +267,21 @@ int main(int argc, char **argv)
fprintf(stderr, "libptouch_create failed\n"); fprintf(stderr, "libptouch_create failed\n");
return 1; return 1;
} }
libptouch_err_t se = libptouch_open_usb(sctx); libptouch_err_t se =
usb_pid_arg != 0
? libptouch_open_usb_vid_pid(
sctx, LIBPTOUCH_USB_VID_BROTHER,
(uint16_t)usb_pid_arg)
: libptouch_open_usb(sctx);
if (se != LIBPTOUCH_OK) { if (se != LIBPTOUCH_OK) {
fprintf(stderr, "open_usb: %s\n", libptouch_strerror(sctx)); fprintf(stderr, "open_usb: %s\n", libptouch_strerror(sctx));
libptouch_destroy(sctx); libptouch_destroy(sctx);
return 1; return 1;
} }
/* 印刷直後などは内部処理中で最初のステータス応答が不安定なことがあるため、
* 少し待ってからステータスを取りに行く。 */
usleep(300000); /* 300ms */
uint8_t st[LIBPTOUCH_STATUS_LENGTH]; uint8_t st[LIBPTOUCH_STATUS_LENGTH];
se = libptouch_get_status(sctx, st); se = libptouch_get_status(sctx, st);
if (se != LIBPTOUCH_OK) { if (se != LIBPTOUCH_OK) {
@@ -186,20 +303,21 @@ int main(int argc, char **argv)
} }
int png = input_is_png(file); int png = input_is_png(file);
if (png) { int svg = input_is_svg(file);
if (png || svg) {
if (width != 0 || height != 0) if (width != 0 || height != 0)
fprintf(stderr, fprintf(stderr,
"warning: -w/-H ignored for PNG (using image size)\n"); "warning: -w/-H ignored for image inputs\n");
} else { } else {
if (width == 0 || height == 0) { if (width == 0 || height == 0) {
fprintf(stderr, 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]); usage(argv[0]);
return 2; return 2;
} }
if (has_threshold) if (has_threshold)
fprintf(stderr, fprintf(stderr,
"warning: -t applies to PNG only (ignored)\n"); "warning: -t applies to PNG/SVG only (ignored)\n");
} }
libptouch_ctx *ctx = libptouch_create(); libptouch_ctx *ctx = libptouch_create();
@@ -212,6 +330,8 @@ int main(int argc, char **argv)
size_t data_len = 0; size_t data_len = 0;
libptouch_raster_params_t params = { 0, 0, 0 }; libptouch_raster_params_t params = { 0, 0, 0 };
libptouch_err_t e; libptouch_err_t e;
int usb_opened = 0;
int data_from_lib = 0;
if (png) { if (png) {
libptouch_png_options_t opt = { .threshold = (uint8_t)threshold }; libptouch_png_options_t opt = { .threshold = (uint8_t)threshold };
@@ -224,6 +344,30 @@ int main(int argc, char **argv)
libptouch_destroy(ctx); libptouch_destroy(ctx);
return 1; return 1;
} }
data_from_lib = 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,
&params);
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;
}
data_from_lib = 1;
} else { } else {
if (read_file(file, &data, &data_len) != 0) { if (read_file(file, &data, &data_len) != 0) {
libptouch_destroy(ctx); libptouch_destroy(ctx);
@@ -234,12 +378,60 @@ int main(int argc, char **argv)
params.margin_mm = 0; params.margin_mm = 0;
} }
if (trim_right_enabled) {
uint16_t pad = (uint16_t)trim_right_dots;
if (trim_right_auto) {
pad = 0u;
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)
usb_opened = 1;
}
if (usb_opened) {
libptouch_media_info_t mi;
if (libptouch_get_current_media_info(ctx, &mi) ==
LIBPTOUCH_OK) {
pad = mi.left_margin_dots;
}
}
}
uint8_t *trimmed = NULL;
size_t trimmed_len = 0;
libptouch_raster_params_t trimmed_params = { 0, 0, 0 };
e = libptouch_trim_right_blank_columns(
ctx, data, data_len, &params, pad, &trimmed, &trimmed_len,
&trimmed_params);
if (e != LIBPTOUCH_OK) {
fprintf(stderr, "trim_right_blank_columns: %s\n",
libptouch_strerror(ctx));
libptouch_destroy(ctx);
if (data_from_lib)
libptouch_free_raster(data);
else
free(data);
return 1;
}
if (data_from_lib)
libptouch_free_raster(data);
else
free(data);
data = trimmed;
data_len = trimmed_len;
params = trimmed_params;
data_from_lib = 1;
}
e = libptouch_check_raster(ctx, data, data_len, &params); e = libptouch_check_raster(ctx, data, data_len, &params);
if (e != LIBPTOUCH_OK) { if (e != LIBPTOUCH_OK) {
fprintf(stderr, "check_raster: %s\n", fprintf(stderr, "check_raster: %s\n",
libptouch_strerror(ctx)); libptouch_strerror(ctx));
libptouch_destroy(ctx); libptouch_destroy(ctx);
if (png) if (data_from_lib)
libptouch_free_raster(data); libptouch_free_raster(data);
else else
free(data); free(data);
@@ -247,44 +439,62 @@ int main(int argc, char **argv)
} }
if (dry_run) { 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.width_dots,
(unsigned)params.height_dots); (unsigned)params.height_dots);
if (usb_opened)
libptouch_close(ctx);
libptouch_destroy(ctx); libptouch_destroy(ctx);
if (png) if (data_from_lib)
libptouch_free_raster(data); libptouch_free_raster(data);
else else
free(data); free(data);
return 0; return 0;
} }
e = libptouch_open_usb(ctx); if (!usb_opened) {
if (e != LIBPTOUCH_OK) { e = usb_pid_arg != 0
fprintf(stderr, "open_usb: %s\n", libptouch_strerror(ctx)); ? libptouch_open_usb_vid_pid(ctx, LIBPTOUCH_USB_VID_BROTHER,
libptouch_destroy(ctx); (uint16_t)usb_pid_arg)
if (png) : libptouch_open_usb(ctx);
libptouch_free_raster(data); if (e != LIBPTOUCH_OK) {
else fprintf(stderr, "open_usb: %s\n", libptouch_strerror(ctx));
free(data); libptouch_destroy(ctx);
return 1; if (data_from_lib)
libptouch_free_raster(data);
else
free(data);
return 1;
}
} }
if (verbose)
verbose_print_pre_print_info(ctx, &params, data_len);
e = libptouch_print_raster(ctx, data, data_len, &params); e = libptouch_print_raster(ctx, data, data_len, &params);
if (e != LIBPTOUCH_OK) { if (e != LIBPTOUCH_OK) {
if (verbose)
verbose_print_post_print_status(ctx, "print failed");
fprintf(stderr, "print_raster: %s\n", fprintf(stderr, "print_raster: %s\n",
libptouch_strerror(ctx)); libptouch_strerror(ctx));
libptouch_close(ctx); libptouch_close(ctx);
libptouch_destroy(ctx); libptouch_destroy(ctx);
if (png) if (data_from_lib)
libptouch_free_raster(data); libptouch_free_raster(data);
else else
free(data); free(data);
return 1; return 1;
} }
if (verbose)
verbose_print_post_print_status(ctx, "print success");
libptouch_close(ctx); libptouch_close(ctx);
libptouch_destroy(ctx); libptouch_destroy(ctx);
if (png) if (data_from_lib)
libptouch_free_raster(data); libptouch_free_raster(data);
else else
free(data); free(data);

View File

@@ -22,15 +22,21 @@ struct libptouch_ctx {
int claimed_interface; /* -1 if none */ int claimed_interface; /* -1 if none */
uint8_t bulk_out_ep; uint8_t bulk_out_ep;
uint8_t bulk_in_ep; uint8_t bulk_in_ep;
uint16_t usb_pid; /* 0 if USB not open; used to pick 128- vs 560-dot raster */
}; };
/* ctx に最終エラー(コードとメッセージ)を記録する。公開 API のエラー返却前に使う。 */
void ptouch_set_error(libptouch_ctx *ctx, libptouch_err_t code, const char *msg); void ptouch_set_error(libptouch_ctx *ctx, libptouch_err_t code, const char *msg);
/* libusb のエラー番号を人が読めるメッセージにして ptouch_set_error に渡す。 */
void ptouch_set_error_usb(libptouch_ctx *ctx, int libusb_err, const char *what); void ptouch_set_error_usb(libptouch_ctx *ctx, int libusb_err, const char *what);
/* bulk OUT 転送。成功時 0、失敗時 libusb の負のエラーコード。 */
int ptouch_bulk_out(libusb_device_handle *h, uint8_t ep, const uint8_t *data, int ptouch_bulk_out(libusb_device_handle *h, uint8_t ep, const uint8_t *data,
int len, unsigned int timeout_ms); int len, unsigned int timeout_ms);
/* bulk IN で len バイトを揃えるまで読む。成功時 0、失敗時 libusb の負のエラーコード。 */
int ptouch_bulk_in_exact(libusb_device_handle *h, uint8_t ep, uint8_t *buf, int ptouch_bulk_in_exact(libusb_device_handle *h, uint8_t ep, uint8_t *buf,
int len, unsigned int timeout_ms); int len, unsigned int timeout_ms);
/* ctx の bulk OUT エンドポイントへ buf を送る。印刷ジョブのチャンク送信用。 */
libptouch_err_t ptouch_bulk_send_job(libptouch_ctx *ctx, const uint8_t *buf, libptouch_err_t ptouch_bulk_send_job(libptouch_ctx *ctx, const uint8_t *buf,
size_t len, const char *what); size_t len, const char *what);

159
src/lib/libptouch_layout.c Normal file
View File

@@ -0,0 +1,159 @@
/*
* libptouch — layout tables and profile resolution (Brother raster PDFs)
*
* Author: knb
* Email: knb@artif.org
*/
#include "libptouch_layout.h"
/* --- cv_ptp900_jpn_raster_102.pdf: TZe / その他HS 以外で共通レイアウトを使う帯) --- */
static const ptouch_layout_row_t layout_tze_560[] = {
{ 0x04, 248, 48, 264 },
{ 0x06, 240, 64, 256 },
{ 0x09, 219, 106, 235 },
{ 0x0C, 197, 150, 213 },
{ 0x12, 155, 234, 171 },
{ 0x18, 112, 320, 128 },
{ 0x24, 45, 454, 61 },
};
static const ptouch_layout_row_t layout_hs_560[] = {
{ 0x06, 244, 56, 260 },
{ 0x09, 224, 96, 240 },
{ 0x0C, 206, 132, 222 },
{ 0x12, 166, 212, 182 },
{ 0x18, 144, 256, 160 },
};
/* --- cv_ptp750w_710bt_jpn_raster_102.pdf --- */
static const ptouch_layout_row_t layout_tze_128[] = {
{ 0x04, 52, 24, 52 },
{ 0x06, 48, 32, 48 },
{ 0x09, 39, 50, 39 },
{ 0x0C, 29, 70, 29 },
{ 0x12, 8, 112, 8 },
{ 0x18, 0, 128, 0 },
};
static const ptouch_layout_row_t layout_hs_128[] = {
{ 0x06, 50, 28, 50 },
{ 0x09, 40, 48, 40 },
{ 0x0C, 31, 66, 31 },
{ 0x12, 11, 106, 11 },
{ 0x18, 0, 128, 0 },
};
static const uint16_t pids_128pin[] = {
LIBPTOUCH_USB_PID_PTP750W,
LIBPTOUCH_USB_PID_PTP710BT,
0,
};
static const uint8_t models_128pin[] = {
0x68, /* PT-P750W */
0x76, /* PT-P710BT */
0,
};
static const ptouch_printer_profile_t ptouch_layout_profiles[] = {
{
.usb_pids = pids_128pin,
.model_codes = models_128pin,
.is_default = 0,
.head_width_dots = 128u,
.margin_feed_dpi = 180.0,
.margin_feed_max_dots = 900u,
.tze = layout_tze_128,
.tze_count = sizeof(layout_tze_128) / sizeof(layout_tze_128[0]),
.hs = layout_hs_128,
.hs_count = sizeof(layout_hs_128) / sizeof(layout_hs_128[0]),
.doc_ref = "cv_ptp750w_710bt_jpn_raster_102.pdf",
},
{
.usb_pids = NULL,
.model_codes = NULL,
.is_default = 1,
.head_width_dots = 560u,
.margin_feed_dpi = 360.0,
.margin_feed_max_dots = 1800u,
.tze = layout_tze_560,
.tze_count = sizeof(layout_tze_560) / sizeof(layout_tze_560[0]),
.hs = layout_hs_560,
.hs_count = sizeof(layout_hs_560) / sizeof(layout_hs_560[0]),
.doc_ref = "cv_ptp900_jpn_raster_102.pdf (560-dot family)",
},
};
static int pid_in_list(const uint16_t *list, uint16_t pid)
{
if (!list)
return 0;
for (; *list; list++) {
if (*list == pid)
return 1;
}
return 0;
}
static int model_in_list(const uint8_t *list, uint8_t code)
{
if (!list)
return 0;
for (; *list; list++) {
if (*list == code)
return 1;
}
return 0;
}
const ptouch_printer_profile_t *ptouch_layout_resolve_profile(uint16_t usb_pid,
uint8_t status_model_byte)
{
const ptouch_printer_profile_t *fallback = NULL;
for (size_t i = 0;
i < sizeof(ptouch_layout_profiles) / sizeof(ptouch_layout_profiles[0]);
i++) {
const ptouch_printer_profile_t *p = &ptouch_layout_profiles[i];
if (p->is_default) {
fallback = p;
continue;
}
if (pid_in_list(p->usb_pids, usb_pid) ||
model_in_list(p->model_codes, status_model_byte))
return p;
}
return fallback;
}
static libptouch_err_t layout_lookup_table(const ptouch_layout_row_t *table, size_t n,
uint8_t media_wbyte, uint16_t *left,
uint16_t *print_dots, uint16_t *right)
{
for (size_t i = 0; i < n; i++) {
if (table[i].media_wbyte != media_wbyte)
continue;
*left = table[i].left_dots;
*print_dots = table[i].print_dots;
*right = table[i].right_dots;
return LIBPTOUCH_OK;
}
return LIBPTOUCH_ERR_UNSUPPORTED;
}
libptouch_err_t ptouch_layout_from_status(const ptouch_printer_profile_t *prof,
uint8_t media_kind, uint8_t media_wbyte,
uint16_t *left_dots, uint16_t *print_dots,
uint16_t *right_dots)
{
if (!prof || !left_dots || !print_dots || !right_dots)
return LIBPTOUCH_ERR_ARG;
int is_hs = (media_kind == 0x11u || media_kind == 0x17u);
const ptouch_layout_row_t *table = is_hs ? prof->hs : prof->tze;
size_t n = is_hs ? prof->hs_count : prof->tze_count;
return layout_lookup_table(table, n, media_wbyte, left_dots, print_dots,
right_dots);
}

View File

@@ -0,0 +1,50 @@
/*
* libptouch — tape width → printable dots (data tables per printer family)
*
* Author: knb
* Email: knb@artif.org
*/
#ifndef LIBPTOUCH_LAYOUT_H
#define LIBPTOUCH_LAYOUT_H
#include "libptouch.h"
#include <stddef.h>
#include <stdint.h>
/** One row: status byte st[10] (media width code) when it matches this tape width slot */
typedef struct ptouch_layout_row {
uint8_t media_wbyte;
uint16_t left_dots;
uint16_t print_dots;
uint16_t right_dots;
} ptouch_layout_row_t;
/**
* Printer family: USB PID / status model byte select a profile; tables supply layouts.
* New models: add a row to @ref ptouch_layout_profiles or add TZe/HS rows to existing tables.
*/
typedef struct ptouch_printer_profile {
const uint16_t *usb_pids; /* 0-terminated; NULL = do not match on PID */
const uint8_t *model_codes; /* 0-terminated; NULL = do not match on status[4] */
int is_default; /* 1 = fallback when no other profile matches */
unsigned head_width_dots; /* 128 or 560; full raster width for pack/GF payload */
double margin_feed_dpi; /* ESC i d feed dots per inch (PDF) */
unsigned margin_feed_max_dots;
const ptouch_layout_row_t *tze;
size_t tze_count;
const ptouch_layout_row_t *hs;
size_t hs_count;
const char *doc_ref;
} ptouch_printer_profile_t;
const ptouch_printer_profile_t *ptouch_layout_resolve_profile(uint16_t usb_pid,
uint8_t status_model_byte);
libptouch_err_t ptouch_layout_from_status(const ptouch_printer_profile_t *prof,
uint8_t media_kind, uint8_t media_wbyte,
uint16_t *left_dots, uint16_t *print_dots,
uint16_t *right_dots);
#endif /* LIBPTOUCH_LAYOUT_H */

View 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;
}

View File

@@ -6,96 +6,19 @@
*/ */
#include "libptouch_internal.h" #include "libptouch_internal.h"
#include "libptouch_layout.h"
#include "libptouch_protocol.h"
#include <libusb.h> #include <libusb.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
/* cv_ptp900_jpn_raster_102.pdf: テープ幅から印刷可能ドット */ static void pack_line(uint8_t *line, size_t line_bytes, unsigned head_dots,
static libptouch_err_t layout_from_status(uint8_t media_kind, uint8_t media_wbyte, const uint8_t *row, uint32_t width_dots, uint16_t left_dots,
uint16_t *left, uint16_t *print_dots, uint16_t print_dots)
uint16_t *right)
{ {
if (media_kind == 0x11u || media_kind == 0x17u) { memset(line, 0, line_bytes);
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;
}
}
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;
}
/* cv_ptp900_jpn_raster_102.pdf: 2.3.5 ラスターライン(全 560 ドット = 70 バイト) */
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) if (width_dots > (uint32_t)print_dots)
return; return;
uint32_t start = (uint32_t)left_dots + uint32_t start = (uint32_t)left_dots +
@@ -106,7 +29,7 @@ static void pack_line_560(uint8_t line[70], const uint8_t *row, uint32_t width_d
if (((row[ubyte] >> ubit) & 1u) == 0) if (((row[ubyte] >> ubit) & 1u) == 0)
continue; continue;
uint32_t dot = start + x; uint32_t dot = start + x;
if (dot >= 560u) if (dot >= (uint32_t)head_dots)
break; break;
uint32_t b = dot / 8u; uint32_t b = dot / 8u;
uint32_t bi = 7u - (dot % 8u); uint32_t bi = 7u - (dot % 8u);
@@ -158,11 +81,19 @@ libptouch_err_t libptouch_print_raster(libptouch_ctx *ctx,
if (v != LIBPTOUCH_OK) if (v != LIBPTOUCH_OK)
return v; 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_kind = st[11];
uint8_t media_w = st[10]; uint8_t media_w = st[10];
uint16_t left_dots, print_dots, right_dots; uint16_t left_dots, print_dots, right_dots;
v = layout_from_status(media_kind, media_w, &left_dots, &print_dots, v = ptouch_layout_from_status(prof, media_kind, media_w, &left_dots,
&right_dots); &print_dots, &right_dots);
if (v != LIBPTOUCH_OK) { if (v != LIBPTOUCH_OK) {
ptouch_set_error(ctx, LIBPTOUCH_ERR_UNSUPPORTED, ptouch_set_error(ctx, LIBPTOUCH_ERR_UNSUPPORTED,
"tape width/layout not supported for this media " "tape width/layout not supported for this media "
@@ -171,6 +102,10 @@ libptouch_err_t libptouch_print_raster(libptouch_ctx *ctx,
} }
(void)right_dots; (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 wd = params->width_dots;
uint32_t ht = params->height_dots; uint32_t ht = params->height_dots;
uint8_t *transposed = transpose_raster_alloc(data, wd, ht, &wd, &ht); uint8_t *transposed = transpose_raster_alloc(data, wd, ht, &wd, &ht);
@@ -191,33 +126,20 @@ libptouch_err_t libptouch_print_raster(libptouch_ctx *ctx,
return LIBPTOUCH_ERR_ARG; return LIBPTOUCH_ERR_ARG;
} }
double margin_dpi = prof->margin_feed_dpi;
unsigned margin_max = prof->margin_feed_max_dots;
unsigned margin_dots = 14u; unsigned margin_dots = 14u;
if (params->margin_mm > 0) { if (params->margin_mm > 0) {
margin_dots = (unsigned)((double)params->margin_mm * 360.0 / margin_dots = (unsigned)((double)params->margin_mm * margin_dpi /
25.4 + 25.4 +
0.5); 0.5);
if (margin_dots < 14u) if (margin_dots < 14u)
margin_dots = 14u; margin_dots = 14u;
if (margin_dots > 1800u) if (margin_dots > margin_max)
margin_dots = 1800u; margin_dots = margin_max;
} }
uint32_t lines = ht; 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]; uint8_t head[256];
size_t pos = 0; size_t pos = 0;
memset(head + pos, 0, 200); memset(head + pos, 0, 200);
@@ -229,18 +151,27 @@ libptouch_err_t libptouch_print_raster(libptouch_ctx *ctx,
memcpy(head + pos, raster_mode, sizeof(raster_mode)); memcpy(head + pos, raster_mode, sizeof(raster_mode));
pos += sizeof(raster_mode); pos += sizeof(raster_mode);
uint8_t esc_iz[] = { 0x1B, 0x69, 0x7A, 0x0Eu, n2_paper, media_w, uint8_t esc_iz[13];
0x00u, n5, n6, n7, n8, 0x02u, 0x00u }; ptouch_fill_esc_iz(esc_iz, media_kind, media_w, lines);
memcpy(head + pos, esc_iz, sizeof(esc_iz)); memcpy(head + pos, esc_iz, sizeof(esc_iz));
pos += sizeof(esc_iz); pos += sizeof(esc_iz);
static const uint8_t esc_im[] = { 0x1B, 0x69, 0x4D, 0x40 }; static const uint8_t esc_im[] = { 0x1B, 0x69, 0x4D, 0x40 };
memcpy(head + pos, esc_im, sizeof(esc_im)); memcpy(head + pos, esc_im, sizeof(esc_im));
pos += 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); * ESC i A ("cut each n labels") is not supported on PT-P710BT class
static const uint8_t esc_ik[] = { 0x1B, 0x69, 0x4B, 0x0C }; * 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)); memcpy(head + pos, esc_ik, sizeof(esc_ik));
pos += sizeof(esc_ik); pos += sizeof(esc_ik);
@@ -263,14 +194,38 @@ libptouch_err_t libptouch_print_raster(libptouch_ctx *ctx,
} }
size_t row_b = ((size_t)wd + 7u) / 8u; size_t row_b = ((size_t)wd + 7u) / 8u;
uint8_t gbuf[73];
static const uint8_t g_hdr[] = { 0x47, 0x46, 0x00 }; 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++) { for (uint32_t y = 0; y < lines; y++) {
const uint8_t *row = src + (size_t)y * row_b; const uint8_t *row = src + (size_t)y * row_b;
pack_line_560(gbuf + 3, row, wd, left_dots, print_dots); pack_line(gbuf + 3, line_payload, head_dots, row, wd, left_dots,
memcpy(gbuf, g_hdr, sizeof(g_hdr)); print_dots);
v = ptouch_bulk_send_job(ctx, gbuf, sizeof(gbuf), "raster line"); 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) { if (v != LIBPTOUCH_OK) {
free(transposed); free(transposed);
return v; return v;

View File

@@ -0,0 +1,39 @@
#include "libptouch_protocol.h"
size_t ptouch_line_payload_bytes(unsigned head_dots)
{
return (size_t)((head_dots + 7u) / 8u);
}
void ptouch_fill_gf_header(uint8_t out[3], size_t line_payload_bytes)
{
out[0] = 0x47u;
out[1] = (uint8_t)(line_payload_bytes & 0xFFu);
out[2] = (uint8_t)((line_payload_bytes >> 8) & 0xFFu);
}
void ptouch_fill_esc_iz(uint8_t out[13], uint8_t media_kind, uint8_t media_width,
uint32_t raster_lines)
{
out[0] = 0x1Bu;
out[1] = 0x69u;
out[2] = 0x7Au;
/* PI flags: enable media/width/length with quality bit for broad compatibility. */
out[3] = 0x8Eu;
/* Use status media-kind byte directly (e.g., 0x01 laminated, 0x03 non-laminate). */
out[4] = media_kind;
out[5] = media_width;
out[6] = 0x00u;
out[7] = (uint8_t)(raster_lines & 0xFFu);
out[8] = (uint8_t)((raster_lines >> 8) & 0xFFu);
out[9] = (uint8_t)((raster_lines >> 16) & 0xFFu);
out[10] = (uint8_t)((raster_lines >> 24) & 0xFFu);
out[11] = 0x00u; /* first page */
out[12] = 0x00u; /* fixed */
}
uint8_t ptouch_esc_ik_value(unsigned head_dots)
{
/* Preserve legacy 560-dot behaviour (0x0C), 128-dot uses 0x08. */
return head_dots > 128u ? 0x0Cu : 0x08u;
}

View File

@@ -0,0 +1,13 @@
#ifndef LIBPTOUCH_PROTOCOL_H
#define LIBPTOUCH_PROTOCOL_H
#include <stddef.h>
#include <stdint.h>
size_t ptouch_line_payload_bytes(unsigned head_dots);
void ptouch_fill_gf_header(uint8_t out[3], size_t line_payload_bytes);
void ptouch_fill_esc_iz(uint8_t out[13], uint8_t media_kind, uint8_t media_width,
uint32_t raster_lines);
uint8_t ptouch_esc_ik_value(unsigned head_dots);
#endif /* LIBPTOUCH_PROTOCOL_H */

View File

@@ -9,6 +9,7 @@
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <unistd.h>
libptouch_err_t libptouch_get_status(libptouch_ctx *ctx, uint8_t *status) libptouch_err_t libptouch_get_status(libptouch_ctx *ctx, uint8_t *status)
{ {
@@ -28,18 +29,34 @@ libptouch_err_t libptouch_get_status(libptouch_ctx *ctx, uint8_t *status)
static const uint8_t req[] = { 0x1B, 0x69, 0x53 }; static const uint8_t req[] = { 0x1B, 0x69, 0x53 };
int r = LIBUSB_ERROR_OTHER; int r = LIBUSB_ERROR_OTHER;
for (int attempt = 0; attempt < 2; attempt++) { for (int attempt = 0; attempt < 5; attempt++) {
/* First status read after reconnect can fail on some P-touch units. */
(void)libusb_clear_halt(h, ctx->bulk_in_ep);
(void)libusb_clear_halt(h, ctx->bulk_out_ep);
if (attempt > 0) { if (attempt > 0) {
(void)libusb_clear_halt(h, ctx->bulk_in_ep); usleep(120000); /* 120ms backoff */
(void)libusb_clear_halt(h, ctx->bulk_out_ep);
} }
r = ptouch_bulk_out(h, ctx->bulk_out_ep, init, 2, 5000u); /*
if (r != 0) { * Avoid unconditional ESC @ here:
ptouch_set_error_usb(ctx, r, "bulk OUT ESC @"); * some models can be in phase-change right after print end, and
return LIBPTOUCH_ERR_USB; * re-initializing there may disturb cutter/feed completion.
* Try plain ESC i S first, use ESC @ only on retry paths.
*/
if (attempt > 0) {
r = ptouch_bulk_out(h, ctx->bulk_out_ep, init, 2, 5000u);
if (r != 0) {
if (r == LIBUSB_ERROR_IO || r == LIBUSB_ERROR_PIPE ||
r == LIBUSB_ERROR_TIMEOUT)
continue;
ptouch_set_error_usb(ctx, r, "bulk OUT ESC @");
return LIBPTOUCH_ERR_USB;
}
} }
r = ptouch_bulk_out(h, ctx->bulk_out_ep, req, 3, 5000u); r = ptouch_bulk_out(h, ctx->bulk_out_ep, req, 3, 5000u);
if (r != 0) { if (r != 0) {
if (r == LIBUSB_ERROR_IO || r == LIBUSB_ERROR_PIPE ||
r == LIBUSB_ERROR_TIMEOUT)
continue;
ptouch_set_error_usb(ctx, r, "bulk OUT ESC i S"); ptouch_set_error_usb(ctx, r, "bulk OUT ESC i S");
return LIBPTOUCH_ERR_USB; return LIBPTOUCH_ERR_USB;
} }
@@ -47,7 +64,8 @@ libptouch_err_t libptouch_get_status(libptouch_ctx *ctx, uint8_t *status)
(int)LIBPTOUCH_STATUS_LENGTH, 5000u); (int)LIBPTOUCH_STATUS_LENGTH, 5000u);
if (r == 0) if (r == 0)
break; break;
if (r != LIBUSB_ERROR_IO && r != LIBUSB_ERROR_PIPE) if (r != LIBUSB_ERROR_IO && r != LIBUSB_ERROR_PIPE &&
r != LIBUSB_ERROR_TIMEOUT)
break; break;
} }
if (r != 0) { if (r != 0) {
@@ -75,6 +93,12 @@ static void fprint_model(FILE *fp, uint8_t code)
case 0x78: case 0x78:
name = "PT-P910BT"; name = "PT-P910BT";
break; break;
case 0x68:
name = "PT-P750W";
break;
case 0x76:
name = "PT-P710BT";
break;
default: default:
break; break;
} }

211
src/lib/libptouch_svg.c Normal file
View 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
}

84
src/lib/libptouch_trim.c Normal file
View File

@@ -0,0 +1,84 @@
/*
* libptouch — raster right blank trim
*
* Author: knb
* Email: knb@artif.org
*/
#include "libptouch_internal.h"
#include <stdlib.h>
#include <string.h>
libptouch_err_t libptouch_trim_right_blank_columns(
libptouch_ctx *ctx, const uint8_t *data, size_t data_len,
const libptouch_raster_params_t *in_params, uint16_t right_padding_dots,
uint8_t **out_raster, size_t *out_raster_bytes,
libptouch_raster_params_t *out_params)
{
if (!ctx || !data || !in_params || !out_raster || !out_raster_bytes ||
!out_params) {
if (ctx)
ptouch_set_error(ctx, LIBPTOUCH_ERR_ARG, "null argument");
return LIBPTOUCH_ERR_ARG;
}
libptouch_err_t v = libptouch_check_raster(ctx, data, data_len, in_params);
if (v != LIBPTOUCH_OK)
return v;
uint32_t width = in_params->width_dots;
uint32_t height = in_params->height_dots;
size_t row_bytes = ((size_t)width + 7u) / 8u;
int32_t rightmost = -1;
for (uint32_t y = 0; y < height; y++) {
const uint8_t *row = data + (size_t)y * row_bytes;
for (int32_t x = (int32_t)width - 1; x >= 0; x--) {
uint8_t b = row[(size_t)x / 8u];
uint8_t bit = (uint8_t)(7u - ((uint32_t)x % 8u));
if (((b >> bit) & 1u) == 0)
continue;
if (x > rightmost)
rightmost = x;
break;
}
}
uint32_t new_width = width;
if (rightmost >= 0) {
uint32_t trimmed = (uint32_t)(rightmost + 1);
uint32_t padded = trimmed + (uint32_t)right_padding_dots;
if (padded > width)
padded = width;
if (padded < width)
new_width = padded;
}
size_t new_row_bytes = ((size_t)new_width + 7u) / 8u;
size_t total = new_row_bytes * (size_t)height;
uint8_t *out = (uint8_t *)calloc(1u, total);
if (!out) {
ptouch_set_error(ctx, LIBPTOUCH_ERR_NOMEM, "calloc raster failed");
return LIBPTOUCH_ERR_NOMEM;
}
uint8_t last_mask = 0xFFu;
if ((new_width % 8u) != 0u)
last_mask = (uint8_t)((0xFFu << (8u - (new_width % 8u))) & 0xFFu);
for (uint32_t y = 0; y < height; y++) {
const uint8_t *src = data + (size_t)y * row_bytes;
uint8_t *dst = out + (size_t)y * new_row_bytes;
memcpy(dst, src, new_row_bytes);
dst[new_row_bytes - 1u] &= last_mask;
}
*out_raster = out;
*out_raster_bytes = total;
out_params->width_dots = new_width;
out_params->height_dots = height;
out_params->margin_mm = in_params->margin_mm;
ptouch_set_error(ctx, LIBPTOUCH_OK, "");
return LIBPTOUCH_OK;
}

View File

@@ -111,6 +111,7 @@ libptouch_err_t libptouch_open_usb_vid_pid(libptouch_ctx *ctx, uint16_t vid,
(void)libusb_clear_halt(h, ctx->bulk_out_ep); (void)libusb_clear_halt(h, ctx->bulk_out_ep);
(void)libusb_clear_halt(h, ctx->bulk_in_ep); (void)libusb_clear_halt(h, ctx->bulk_in_ep);
ctx->usb_pid = pid;
ctx->usb_open = 1; ctx->usb_open = 1;
ptouch_set_error(ctx, LIBPTOUCH_OK, ""); ptouch_set_error(ctx, LIBPTOUCH_OK, "");
return LIBPTOUCH_OK; return LIBPTOUCH_OK;
@@ -141,6 +142,7 @@ void libptouch_close(libptouch_ctx *ctx)
} }
ctx->bulk_out_ep = 0; ctx->bulk_out_ep = 0;
ctx->bulk_in_ep = 0; ctx->bulk_in_ep = 0;
ctx->usb_pid = 0;
ctx->usb_open = 0; ctx->usb_open = 0;
} }

View File

@@ -0,0 +1,49 @@
#include "libptouch_protocol.h"
#include <stdio.h>
#include <stdlib.h>
static int expect_int(const char *name, int got, int want)
{
if (got == want)
return 0;
fprintf(stderr, "%s mismatch: got=%d want=%d\n", name, got, want);
return 1;
}
int main(void)
{
int fail = 0;
fail |= expect_int("line_payload_128", (int)ptouch_line_payload_bytes(128u), 16);
fail |= expect_int("line_payload_560", (int)ptouch_line_payload_bytes(560u), 70);
uint8_t gf[3];
ptouch_fill_gf_header(gf, 16u);
fail |= expect_int("gf16_cmd", gf[0], 0x47);
fail |= expect_int("gf16_n1", gf[1], 0x10);
fail |= expect_int("gf16_n2", gf[2], 0x00);
ptouch_fill_gf_header(gf, 70u);
fail |= expect_int("gf70_cmd", gf[0], 0x47);
fail |= expect_int("gf70_n1", gf[1], 0x46);
fail |= expect_int("gf70_n2", gf[2], 0x00);
uint8_t iz[13];
ptouch_fill_esc_iz(iz, 0x01u, 0x0Cu, 70u);
fail |= expect_int("iz_cmd_0", iz[0], 0x1B);
fail |= expect_int("iz_cmd_1", iz[1], 0x69);
fail |= expect_int("iz_cmd_2", iz[2], 0x7A);
fail |= expect_int("iz_n1_flags", iz[3], 0x8E);
fail |= expect_int("iz_media_kind_passthrough", iz[4], 0x01);
fail |= expect_int("iz_media_width", iz[5], 0x0C);
fail |= expect_int("iz_lines_lsb", iz[7], 70);
fail |= expect_int("iz_page_index", iz[11], 0x00);
fail |= expect_int("iz_last_fixed", iz[12], 0x00);
/* ESC i K mode byte: 128-dot vs 560-dot families */
fail |= expect_int("esc_ik_128", ptouch_esc_ik_value(128u), 0x08);
fail |= expect_int("esc_ik_560", ptouch_esc_ik_value(560u), 0x0C);
return fail ? EXIT_FAILURE : EXIT_SUCCESS;
}

View File

@@ -1,5 +1,6 @@
# Brother P-touch — libusb でユーザから開けるようにする (Ubuntu 等) # Brother P-touch — libusb でユーザから開けるようにする (Ubuntu 等)
# PT-P900W: lsusb で ID 04f9:2085 # PT-P900W: lsusb で ID 04f9:2085
# PT-P750W: 04f9:2062 PT-P710BT: 04f9:20afBrother ラスター PDF Appendix A
# #
# 設置: # 設置:
# sudo cp udev/99-ptouch-label-brother.rules /etc/udev/rules.d/ # sudo cp udev/99-ptouch-label-brother.rules /etc/udev/rules.d/
@@ -11,6 +12,8 @@
# GROUP=plugdev … 従来どおり plugdev に所属していれば読み書き可 # GROUP=plugdev … 従来どおり plugdev に所属していれば読み書き可
SUBSYSTEM=="usb", ATTR{idVendor}=="04f9", ATTR{idProduct}=="2085", MODE="0660", GROUP="plugdev", TAG+="uaccess" SUBSYSTEM=="usb", ATTR{idVendor}=="04f9", ATTR{idProduct}=="2085", MODE="0660", GROUP="plugdev", TAG+="uaccess"
SUBSYSTEM=="usb", ATTR{idVendor}=="04f9", ATTR{idProduct}=="2062", MODE="0660", GROUP="plugdev", TAG+="uaccess"
SUBSYSTEM=="usb", ATTR{idVendor}=="04f9", ATTR{idProduct}=="20af", MODE="0660", GROUP="plugdev", TAG+="uaccess"
# 他機種を使う場合は lsusb の ID を足す例(コメントを外して複製): # 他機種を使う場合は lsusb の ID を足す例(コメントを外して複製):
# SUBSYSTEM=="usb", ATTR{idVendor}=="04f9", ATTR{idProduct}=="2086", MODE="0660", GROUP="plugdev", TAG+="uaccess" # SUBSYSTEM=="usb", ATTR{idVendor}=="04f9", ATTR{idProduct}=="2086", MODE="0660", GROUP="plugdev", TAG+="uaccess"