Compare commits
12 Commits
33465c7299
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| e10a430f9e | |||
| bfd6adda42 | |||
| 3f2eb464aa | |||
| f1779b94f0 | |||
| d0e5846012 | |||
| d2fd6cc1f9 | |||
| 779a50747d | |||
| 29072dc20c | |||
| 42a785f086 | |||
| e92273a747 | |||
| 094f183994 | |||
| 32ab12f661 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,4 +1,5 @@
|
|||||||
build/
|
build/
|
||||||
|
tmp/
|
||||||
|
|
||||||
ruby/*.gem
|
ruby/*.gem
|
||||||
ruby/.rubocop_cache/
|
ruby/.rubocop_cache/
|
||||||
|
|||||||
19
CHANGELOG.md
Normal file
19
CHANGELOG.md
Normal 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.
|
||||||
@@ -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)
|
||||||
@@ -25,8 +25,11 @@ 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_layout.c
|
||||||
|
src/lib/libptouch_family_config.c
|
||||||
src/lib/libptouch_media_info.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
|
||||||
@@ -73,6 +76,21 @@ 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"
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/include"
|
||||||
|
"${CMAKE_CURRENT_BINARY_DIR}/include"
|
||||||
|
)
|
||||||
|
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}")
|
||||||
@@ -81,3 +99,6 @@ install(FILES
|
|||||||
"${CMAKE_CURRENT_SOURCE_DIR}/include/libptouch.h"
|
"${CMAKE_CURRENT_SOURCE_DIR}/include/libptouch.h"
|
||||||
"${CMAKE_CURRENT_BINARY_DIR}/include/libptouch_version.h"
|
"${CMAKE_CURRENT_BINARY_DIR}/include/libptouch_version.h"
|
||||||
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}")
|
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}")
|
||||||
|
install(FILES
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/config/printer_families.json"
|
||||||
|
DESTINATION "${CMAKE_INSTALL_DATADIR}/ptouch_label")
|
||||||
|
|||||||
41
README.md
41
README.md
@@ -1,21 +1,25 @@
|
|||||||
# 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**(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`。
|
対象機種: **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 / svg) |
|
| `include/libptouch.h` | 公開 API |
|
||||||
| `src/cli/main.c` | `ptouch-print` エントリ |
|
| `src/lib/libptouch_*.c` | ライブラリ本体(core / usb / print / status / png / svg) |
|
||||||
| `samples/` | 試験用サンプル画像の置き場(PNG/SVG 等) |
|
| `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) |
|
||||||
|
|
||||||
|
|
||||||
## ビルド
|
## ビルド
|
||||||
|
|
||||||
@@ -24,6 +28,7 @@ Brother P-touch シリーズ向けのラベル印刷用 **C コアライブラ
|
|||||||
```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/` 以下):
|
||||||
@@ -40,7 +45,10 @@ cmake --build build
|
|||||||
|
|
||||||
**PNG**(拡張子 `.png` または PNG シグネチャ)の場合は幅・高さは画像から取得します。
|
**PNG**(拡張子 `.png` または PNG シグネチャ)の場合は幅・高さは画像から取得します。
|
||||||
**SVG**(拡張子 `.svg`)の場合は現在装着中テープの印字可能ドット幅に合わせて自動拡大・縮小します(USB 接続が必要)。
|
**SVG**(拡張子 `.svg`)の場合は現在装着中テープの印字可能ドット幅に合わせて自動拡大・縮小します(USB 接続が必要)。
|
||||||
|
`--trim-right[=DOTS]` で右側空白列を削減できます(DOTS 省略時は左余白ドット、取得失敗時は 0)。
|
||||||
任意で `-t`(0–255)で二値化しきい値を指定できます。
|
任意で `-t`(0–255)で二値化しきい値を指定できます。
|
||||||
|
`--cut BITS` でカット系フラグを指定できます(`[auto-cut][half-cut][chain-print]`、例 `010`)。
|
||||||
|
指定なしの既定は `011`(オートカットしない / ハーフカットする / つなげて印刷する)です。
|
||||||
|
|
||||||
**1bit packed ラスター**(行優先、行あたり `ceil(width/8)` バイト × 行数)の場合は `-f` に加え `-w` / `-H` が必須です。
|
**1bit packed ラスター**(行優先、行あたり `ceil(width/8)` バイト × 行数)の場合は `-f` に加え `-w` / `-H` が必須です。
|
||||||
|
|
||||||
@@ -49,6 +57,8 @@ 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 必要)
|
# SVG — 幅を現在テープにフィットさせて検証(USB 必要)
|
||||||
./build/ptouch-print -n -f label.svg
|
./build/ptouch-print -n -f label.svg
|
||||||
@@ -56,6 +66,12 @@ cmake --build build
|
|||||||
# PNG — しきい値を指定
|
# PNG — しきい値を指定
|
||||||
./build/ptouch-print -n -f label.png -t 160
|
./build/ptouch-print -n -f label.png -t 160
|
||||||
|
|
||||||
|
# カットフラグを指定(auto=0, half=1, chain=0)
|
||||||
|
./build/ptouch-print -n -f label.png --cut 010
|
||||||
|
|
||||||
|
# デバッグ: USB に送る印字データをファイル保存
|
||||||
|
./build/ptouch-print -f label.png --debug-dump print_job.bin
|
||||||
|
|
||||||
# 1bit ラスター — 検証のみ
|
# 1bit ラスター — 検証のみ
|
||||||
./build/ptouch-print -n -f sample.raster -w 128 -H 64
|
./build/ptouch-print -n -f sample.raster -w 128 -H 64
|
||||||
|
|
||||||
@@ -82,6 +98,7 @@ cmake --build build
|
|||||||
- `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_get_current_media_info` — 現在テープ幅(mm)・印字可能幅(dots)・DPI・最小余白(mm)を取得
|
||||||
- `libptouch_check_raster` — ラスターバッファの検証のみ
|
- `libptouch_check_raster` — ラスターバッファの検証のみ
|
||||||
|
- `libptouch_set_debug_dump_path` — USB bulk OUT へ送った印字データをファイル保存(デバッグ用)
|
||||||
- `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_svg_file_to_raster_fit_current_tape` — SVG を現在テープ幅に合わせて 1bit ラスターへ変換(librsvg + cairo、USB 必須)
|
||||||
- `libptouch_print_raster` — ラスター印刷(USB・PDF のコマンド列、内部で転置)
|
- `libptouch_print_raster` — ラスター印刷(USB・PDF のコマンド列、内部で転置)
|
||||||
@@ -93,7 +110,7 @@ cmake --build build
|
|||||||
|
|
||||||
- 接続は **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` の定数参照)。
|
- 接続は **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` の定数参照)。
|
||||||
- PT-P750W / PT-P710BT ではラスター幅方向は **180 dpi**(P900W は 360 dpi)。PNG から印刷する場合は解像度に合わせて画像を用意してください。
|
- PT-P750W / PT-P710BT ではラスター幅方向は **180 dpi**(P900W は 360 dpi)。PNG から印刷する場合は解像度に合わせて画像を用意してください。
|
||||||
- ラスターコマンドの詳細は **`reference/` の PDF** および Brother 公開資料に沿って `src/lib/libptouch_*.c` に実装してください。
|
- ラスターコマンドの詳細は `**reference/` の PDF** および Brother 公開資料に沿って `src/lib/libptouch_*.c` に実装してください。
|
||||||
|
|
||||||
### Ubuntu で sudo なしで USB を開く(udev)
|
### Ubuntu で sudo なしで USB を開く(udev)
|
||||||
|
|
||||||
@@ -113,3 +130,7 @@ sudo usermod -aG plugdev "$USER"
|
|||||||
## ライセンス
|
## ライセンス
|
||||||
|
|
||||||
[MIT License](LICENSE)(`LICENSE` ファイルを参照)。
|
[MIT License](LICENSE)(`LICENSE` ファイルを参照)。
|
||||||
|
|
||||||
|
## 変更履歴
|
||||||
|
|
||||||
|
リリース間の変更点は `CHANGELOG.md` を参照してください。
|
||||||
18
config/printer_families.json
Normal file
18
config/printer_families.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"families": {
|
||||||
|
"p700": {
|
||||||
|
"comment": "PT-P750W / PT-P710BT(cv_ptp750w_710bt_jpn_raster_102.pdf 系)",
|
||||||
|
"print_dpi": 180,
|
||||||
|
"margin_feed_dpi": 180,
|
||||||
|
"margin_feed_max_dots": 900,
|
||||||
|
"send_esc_ia_cut_each": false
|
||||||
|
},
|
||||||
|
"p900": {
|
||||||
|
"comment": "PT-P900 / P900W / P950NW / P910BT(cv_ptp900_jpn_raster_102.pdf 系)",
|
||||||
|
"print_dpi": 360,
|
||||||
|
"margin_feed_dpi": 360,
|
||||||
|
"margin_feed_max_dots": 1800,
|
||||||
|
"send_esc_ia_cut_each": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -39,6 +39,18 @@ typedef enum {
|
|||||||
LIBPTOUCH_ERR_IMAGE = 7,
|
LIBPTOUCH_ERR_IMAGE = 7,
|
||||||
} libptouch_err_t;
|
} libptouch_err_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* リファレンス上の系統(PT-P750W/P710BT = P700、PT-P900 系 = P900)。
|
||||||
|
* DPI の値で分岐する代わりに、この系統を使う。
|
||||||
|
*/
|
||||||
|
typedef enum {
|
||||||
|
LIBPTOUCH_FAMILY_UNKNOWN = 0,
|
||||||
|
/** PT-P750W / PT-P710BT(cv_ptp750w_710bt_jpn_raster_102.pdf 系) */
|
||||||
|
LIBPTOUCH_FAMILY_P700 = 1,
|
||||||
|
/** PT-P900 / P900W / P950NW / P910BT 等(cv_ptp900_jpn_raster_102.pdf 系) */
|
||||||
|
LIBPTOUCH_FAMILY_P900 = 2,
|
||||||
|
} libptouch_printer_family_t;
|
||||||
|
|
||||||
/** 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
|
||||||
@@ -49,6 +61,11 @@ typedef enum {
|
|||||||
libptouch_ctx *libptouch_create(void);
|
libptouch_ctx *libptouch_create(void);
|
||||||
void libptouch_destroy(libptouch_ctx *ctx);
|
void libptouch_destroy(libptouch_ctx *ctx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系統の短い英語ラベル("p700" / "p900" / "unknown")。UI やログ用。
|
||||||
|
*/
|
||||||
|
const char *libptouch_printer_family_label(libptouch_printer_family_t family);
|
||||||
|
|
||||||
/** 人が読める英語メッセージ(スレッド非安全: ctx ごと) */
|
/** 人が読める英語メッセージ(スレッド非安全: ctx ごと) */
|
||||||
const char *libptouch_strerror(const libptouch_ctx *ctx);
|
const char *libptouch_strerror(const libptouch_ctx *ctx);
|
||||||
libptouch_err_t libptouch_last_error(const libptouch_ctx *ctx);
|
libptouch_err_t libptouch_last_error(const libptouch_ctx *ctx);
|
||||||
@@ -67,16 +84,39 @@ libptouch_err_t libptouch_open_usb(libptouch_ctx *ctx);
|
|||||||
|
|
||||||
void libptouch_close(libptouch_ctx *ctx);
|
void libptouch_close(libptouch_ctx *ctx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* デバッグ用: USB bulk OUT へ実際に送ったバイト列を path に保存する。
|
||||||
|
* 各 @ref libptouch_print_raster ごとにファイルを上書きし直す(ジョブ先頭のチャンクで wb)。
|
||||||
|
* path が NULL または空文字で無効化。転送が成功したチャンクのみ追記する。
|
||||||
|
*/
|
||||||
|
void libptouch_set_debug_dump_path(libptouch_ctx *ctx, const char *path);
|
||||||
|
|
||||||
|
/** @ref libptouch_raster_params_t.flags 用: ESC i M bit6 でオートカット ON */
|
||||||
|
#define LIBPTOUCH_RASTER_FLAG_AUTO_CUT 0x01u
|
||||||
|
/** @ref libptouch_raster_params_t.flags 用: ESC i K bit2 でハーフカット ON */
|
||||||
|
#define LIBPTOUCH_RASTER_FLAG_HALF_CUT 0x02u
|
||||||
|
/** @ref libptouch_raster_params_t.flags 用: ChainPrint する(ESC i K bit3=0) */
|
||||||
|
#define LIBPTOUCH_RASTER_FLAG_CHAIN_PRINT 0x04u
|
||||||
|
/** 既定: 011(オートカットしない・ハーフカットする・つなげて印刷する) */
|
||||||
|
#define LIBPTOUCH_RASTER_FLAGS_DEFAULT \
|
||||||
|
(LIBPTOUCH_RASTER_FLAG_HALF_CUT | LIBPTOUCH_RASTER_FLAG_CHAIN_PRINT)
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint32_t width_dots; /**< ラスター幅(ドット) */
|
uint32_t width_dots; /**< ラスター幅(ドット) */
|
||||||
uint32_t height_dots; /**< ラスター高さ(ドット・走査方向は実装と機種に依存) */
|
uint32_t height_dots; /**< ラスター高さ(ドット・走査方向は実装と機種に依存) */
|
||||||
uint8_t margin_mm; /**< 余白など(機種・仕様に合わせて使用) */
|
uint8_t margin_mm; /**< 余白など(機種・仕様に合わせて使用) */
|
||||||
|
/**
|
||||||
|
* LIBPTOUCH_RASTER_FLAG_*。未使用ビットは 0。
|
||||||
|
* 既定は @ref LIBPTOUCH_RASTER_FLAGS_DEFAULT(011: オートカットしない・ハーフカットする・つなげて印刷する)。
|
||||||
|
*/
|
||||||
|
uint8_t flags;
|
||||||
|
uint8_t _reserved[2]; /**< 将来用。0 にすること */
|
||||||
} libptouch_raster_params_t;
|
} libptouch_raster_params_t;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint8_t media_width_code; /**< status[10] */
|
uint8_t media_width_code; /**< status[10] */
|
||||||
uint8_t media_kind_code; /**< status[11] */
|
uint8_t media_kind_code; /**< status[11] */
|
||||||
double print_dpi; /**< 印字幅方向 DPI(現状 180 or 360) */
|
double print_dpi; /**< 印字幅方向 DPI(系統既定または設定ファイル上書き) */
|
||||||
double feed_dpi; /**< 送り方向 DPI(ESC i d の基準) */
|
double feed_dpi; /**< 送り方向 DPI(ESC i d の基準) */
|
||||||
double tape_width_mm; /**< 装着テープ幅(mm)。例: 3.5, 6, 9, 12, 18, 24, 36 */
|
double tape_width_mm; /**< 装着テープ幅(mm)。例: 3.5, 6, 9, 12, 18, 24, 36 */
|
||||||
uint16_t printable_dots; /**< 現在テープで印字可能な幅(ドット) */
|
uint16_t printable_dots; /**< 現在テープで印字可能な幅(ドット) */
|
||||||
@@ -84,6 +124,8 @@ typedef struct {
|
|||||||
uint16_t right_margin_dots; /**< 右余白(ドット) */
|
uint16_t right_margin_dots; /**< 右余白(ドット) */
|
||||||
uint16_t min_feed_dots; /**< 仕様上の最小送り量(ドット)。現在は 14 */
|
uint16_t min_feed_dots; /**< 仕様上の最小送り量(ドット)。現在は 14 */
|
||||||
double min_feed_mm; /**< 最小送り量(mm) */
|
double min_feed_mm; /**< 最小送り量(mm) */
|
||||||
|
/** @ref libptouch_printer_family_t(系統不明時は @ref LIBPTOUCH_FAMILY_UNKNOWN) */
|
||||||
|
uint32_t printer_family;
|
||||||
} libptouch_media_info_t;
|
} libptouch_media_info_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -100,12 +142,24 @@ libptouch_err_t libptouch_check_raster(libptouch_ctx *ctx,
|
|||||||
libptouch_err_t libptouch_get_current_media_info(libptouch_ctx *ctx,
|
libptouch_err_t libptouch_get_current_media_info(libptouch_ctx *ctx,
|
||||||
libptouch_media_info_t *out_info);
|
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 準拠)。
|
* 1 ビット packed ラスターを USB で印刷(各機種のラスター PDF 準拠)。
|
||||||
* 印字前にステータスでテープ幅を読み、印刷可能ドット内に画像を中央配置する。
|
* 印字前にステータスでテープ幅を読み、印刷可能ドット内に画像を中央配置する。
|
||||||
* width_dots は装着テープの印刷可能幅以下であること。PT-P900 系は幅 360dpi、
|
* width_dots は装着テープの印刷可能幅以下であること。系統は @ref libptouch_get_current_media_info
|
||||||
* PT-P750W / PT-P710BT は幅 180dpi(cv_ptp750w_710bt_jpn_raster_102.pdf)のドット列を想定。
|
* の printer_family で判別できる(P900 系は主に 360dpi 相当ドット列、P700 系は 180dpi 相当)。
|
||||||
* @param margin_mm 余白(フィード)量。0 のとき PDF の最小 1mm(14 ドット)相当を送る。
|
* @param margin_mm 余白(フィード)量。0 のとき PDF の最小 1mm(14 ドット)相当を送る。
|
||||||
|
* @param params->flags bit0=オートカット、bit1=ハーフカット、bit2=チェーンプリントする。
|
||||||
|
* 指定なしは @ref LIBPTOUCH_RASTER_FLAGS_DEFAULT(011)。
|
||||||
* 印刷時は内部でドット列を転置する(テープ幅方向とバッファの縦横の対応)。
|
* 印刷時は内部でドット列を転置する(テープ幅方向とバッファの縦横の対応)。
|
||||||
* @param data 1 行あたり width_dots ビットを ceil(width_dots/8) バイトで並べた連続領域
|
* @param data 1 行あたり width_dots ビットを ceil(width_dots/8) バイトで並べた連続領域
|
||||||
* @param data_len 期待値: height * row_bytes, row_bytes = (width_dots + 7) / 8
|
* @param data_len 期待値: height * row_bytes, row_bytes = (width_dots + 7) / 8
|
||||||
|
|||||||
44
reference/note.adoc
Normal file
44
reference/note.adoc
Normal 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 を維持)。
|
||||||
14
reference/ptp_raster_ref.adoc
Normal file
14
reference/ptp_raster_ref.adoc
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
= P-Toouch Raster コマンド リファレンスメモ
|
||||||
|
|
||||||
|
== 印字データ
|
||||||
|
|
||||||
|
印刷データは大きく分けて、初期化コマンド、制御コード、ラスターデータ、印字指令から構成される。
|
||||||
|
ジョブが複数ページからなる場合には、制御コード~ラスターデータを繰り返します。
|
||||||
|
|
||||||
|
=== 初期化コマンド
|
||||||
|
|
||||||
|
印刷開始時に一度送信
|
||||||
|
|
||||||
|
1. 無効指令: NULL(0x00) × 200バイト
|
||||||
|
2. 初期化: ESC @ (0x1B 0x40)
|
||||||
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
PATH
|
PATH
|
||||||
remote: .
|
remote: .
|
||||||
specs:
|
specs:
|
||||||
libptouch (1.0.0)
|
libptouch (1.0.1)
|
||||||
|
barby
|
||||||
ffi (~> 1.15)
|
ffi (~> 1.15)
|
||||||
|
rexml
|
||||||
|
rqrcode
|
||||||
|
|
||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
ast (2.4.3)
|
ast (2.4.3)
|
||||||
|
barby (0.7.0)
|
||||||
|
chunky_png (1.4.0)
|
||||||
ffi (1.17.4)
|
ffi (1.17.4)
|
||||||
ffi (1.17.4-aarch64-linux-gnu)
|
ffi (1.17.4-aarch64-linux-gnu)
|
||||||
ffi (1.17.4-aarch64-linux-musl)
|
ffi (1.17.4-aarch64-linux-musl)
|
||||||
@@ -30,6 +35,11 @@ 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)
|
||||||
|
rqrcode (3.2.0)
|
||||||
|
chunky_png (~> 1.0)
|
||||||
|
rqrcode_core (~> 2.0)
|
||||||
|
rqrcode_core (2.1.0)
|
||||||
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,10 +74,12 @@ 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
|
||||||
|
barby (0.7.0) sha256=8dbc7c0c320e596135d97929c0df77f969e9f9c955a157cf6749c05b44dae213
|
||||||
|
chunky_png (1.4.0) sha256=89d5b31b55c0cf4da3cf89a2b4ebc3178d8abe8cbaf116a1dba95668502fdcfe
|
||||||
ffi (1.17.4) sha256=bcd1642e06f0d16fc9e09ac6d49c3a7298b9789bcb58127302f934e437d60acf
|
ffi (1.17.4) sha256=bcd1642e06f0d16fc9e09ac6d49c3a7298b9789bcb58127302f934e437d60acf
|
||||||
ffi (1.17.4-aarch64-linux-gnu) sha256=b208f06f91ffd8f5e1193da3cae3d2ccfc27fc36fba577baf698d26d91c080df
|
ffi (1.17.4-aarch64-linux-gnu) sha256=b208f06f91ffd8f5e1193da3cae3d2ccfc27fc36fba577baf698d26d91c080df
|
||||||
ffi (1.17.4-aarch64-linux-musl) sha256=9286b7a615f2676245283aef0a0a3b475ae3aae2bb5448baace630bb77b91f39
|
ffi (1.17.4-aarch64-linux-musl) sha256=9286b7a615f2676245283aef0a0a3b475ae3aae2bb5448baace630bb77b91f39
|
||||||
@@ -81,7 +93,7 @@ CHECKSUMS
|
|||||||
ffi (1.17.4-x86_64-linux-musl) sha256=3fdf9888483de005f8ef8d1cf2d3b20d86626af206cbf780f6a6a12439a9c49e
|
ffi (1.17.4-x86_64-linux-musl) sha256=3fdf9888483de005f8ef8d1cf2d3b20d86626af206cbf780f6a6a12439a9c49e
|
||||||
json (2.19.3) sha256=289b0bb53052a1fa8c34ab33cc750b659ba14a5c45f3fcf4b18762dc67c78646
|
json (2.19.3) sha256=289b0bb53052a1fa8c34ab33cc750b659ba14a5c45f3fcf4b18762dc67c78646
|
||||||
language_server-protocol (3.17.0.5) sha256=fd1e39a51a28bf3eec959379985a72e296e9f9acfce46f6a79d31ca8760803cc
|
language_server-protocol (3.17.0.5) sha256=fd1e39a51a28bf3eec959379985a72e296e9f9acfce46f6a79d31ca8760803cc
|
||||||
libptouch (1.0.0)
|
libptouch (1.0.1)
|
||||||
lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87
|
lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87
|
||||||
parallel (2.0.1) sha256=337782d3e39f4121e67563bf91dd8ece67f48923d90698614773a0ec9a5b2c7d
|
parallel (2.0.1) sha256=337782d3e39f4121e67563bf91dd8ece67f48923d90698614773a0ec9a5b2c7d
|
||||||
parser (3.3.11.1) sha256=d17ace7aabe3e72c3cc94043714be27cc6f852f104d81aa284c2281aecc65d54
|
parser (3.3.11.1) sha256=d17ace7aabe3e72c3cc94043714be27cc6f852f104d81aa284c2281aecc65d54
|
||||||
@@ -89,6 +101,9 @@ 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
|
||||||
|
rqrcode (3.2.0) sha256=64c1494ca6bb67d731330f38b50e3fd09eeab4f5dcd04b608e21218d1d0b9542
|
||||||
|
rqrcode_core (2.1.0) sha256=f303b85df89c1b8fc5ee8dc19808c9dc4330e6329b660d99d4a8cbb36ca13051
|
||||||
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
|
||||||
|
|||||||
@@ -5,12 +5,10 @@
|
|||||||
## 前提
|
## 前提
|
||||||
|
|
||||||
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,21 +29,33 @@ export LIBPTOUCH_LIB=/usr/local/lib/libptouch.so
|
|||||||
|
|
||||||
(`cmake --install` で共有ライブラリをインストールした場合は、通常は `libptouch` 名でローダが解決します。)
|
(`cmake --install` で共有ライブラリをインストールした場合は、通常は `libptouch` 名でローダが解決します。)
|
||||||
|
|
||||||
## コマンド `ptouch-print-png`(PNG/SVG)
|
## コマンド `ptouch-label`(PNG/SVG)
|
||||||
|
|
||||||
C の `ptouch-print` と同様の流れで、**PNG/SVG 入力**(`-w`/`-H` や 1bit ラスターは扱いません)を扱います。`gem install` 後は PATH に `ptouch-print-png` が入ります。
|
C の `ptouch-print` と同様の流れで、**PNG/SVG 入力**(`-w`/`-H` や 1bit ラスターは扱いません)を扱います。`gem install` 後は PATH に `ptouch-label` が入ります。
|
||||||
SVG は現在装着テープの印字可能幅に合わせて自動拡大・縮小します(USB 接続が必要)。
|
SVG は現在装着テープの印字可能幅に合わせて自動拡大・縮小します(USB 接続が必要)。
|
||||||
|
後方互換のため `ptouch-print-png` も引き続き使えます。
|
||||||
|
|
||||||
オプションは C 側に合わせ、**`-p` / `--pid`** で USB 製品 ID(16 進可)を指定できます。省略時は PT-P900W(`Libptouch::USB_PID_PTP900W` = `0x2085`)。例: PT-P750W `0x2062`、PT-P710BT `0x20af`(`libptouch.h` / `Libptouch` 定数と同じ)。
|
オプションは C 側に合わせ、`**-p` / `--pid`** で USB 製品 ID(16 進可)を指定できます。省略時は PT-P900W(`Libptouch::USB_PID_PTP900W` = `0x2085`)。例: PT-P750W `0x2062`、PT-P710BT `0x20af`(`libptouch.h` / `Libptouch` 定数と同じ)。
|
||||||
|
また、`--template`(SVG)と `--data`(JSON/YAML)を使うと `data-field` 属性をキーにした差込印刷が可能です。
|
||||||
|
`data-kb-placeholder="qr"` / `data-kb-placeholder="barcode"` を付けた SVG 要素(`x/y/width/height` 必須)には、対応する `data-field` 値から QR / Code128 バーコードを生成して差し込みできます。
|
||||||
|
`--trim-right[=DOTS]` を付けると、libptouch 側の共通処理でラベル右側の空白ドット列を削減します。`DOTS` 省略時は左余白ドット数を使い、取得失敗時は `0` にフォールバックします。
|
||||||
|
`--cut BITS` は 3bit(`[auto-cut][half-cut][chain-print]`)で、例 `010` のように指定します。既定は `011` です。
|
||||||
|
`--debug-dump PATH` を付けると USB へ送る印字データをファイル保存します(1 印刷ごとに上書き)。
|
||||||
|
|
||||||
開発ツリーからそのまま試す例:
|
開発ツリーからそのまま試す例:
|
||||||
|
|
||||||
```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-print-png -n -f ../samples/your.svg
|
bundle exec ruby -I lib exe/ptouch-label -n -f ../samples/your.svg
|
||||||
bundle exec ruby -I lib exe/ptouch-print-png --status -p 0x2062
|
bundle exec ruby -I lib exe/ptouch-label -n -f ../samples/your.svg --trim-right
|
||||||
bundle exec ruby -I lib exe/ptouch-print-png -f ../samples/your.png -p 0x20af
|
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 -f ../samples/your.png --cut 010
|
||||||
|
bundle exec ruby -I lib exe/ptouch-label -f ../samples/your.png --debug-dump print_job.bin
|
||||||
|
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
|
||||||
```
|
```
|
||||||
|
|
||||||
## 使用例
|
## 使用例
|
||||||
@@ -92,9 +102,10 @@ ctx.dispose
|
|||||||
|
|
||||||
## API の範囲
|
## API の範囲
|
||||||
|
|
||||||
- 実行ファイル `ptouch-print-png` … PNG/SVG(`-f`, `-t`, `-p`, `-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]`, `--cut BITS`, `--debug-dump PATH`)。`-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` / `svg_file_to_raster_fit_current_tape` / `status_bytes` / `status_hash` / `current_media_info`
|
- `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` / `min_feed_mm` などを含む
|
- `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
6
ruby/exe/ptouch-label
Executable file
@@ -0,0 +1,6 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "libptouch/cli/label_print"
|
||||||
|
|
||||||
|
exit Libptouch::Cli::LabelPrint.run(ARGV)
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -27,4 +27,14 @@ module Libptouch
|
|||||||
|
|
||||||
STATUS_LENGTH = 32
|
STATUS_LENGTH = 32
|
||||||
PNG_DEFAULT_THRESHOLD = 128
|
PNG_DEFAULT_THRESHOLD = 128
|
||||||
|
|
||||||
|
# libptouch_printer_family_t(C API と同じ値)
|
||||||
|
FAMILY_UNKNOWN = 0
|
||||||
|
FAMILY_P700 = 1
|
||||||
|
FAMILY_P900 = 2
|
||||||
|
|
||||||
|
RASTER_FLAG_AUTO_CUT = 0x01
|
||||||
|
RASTER_FLAG_HALF_CUT = 0x02
|
||||||
|
RASTER_FLAG_CHAIN_PRINT = 0x04
|
||||||
|
RASTER_FLAGS_DEFAULT = RASTER_FLAG_HALF_CUT | RASTER_FLAG_CHAIN_PRINT
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -25,7 +25,9 @@ module Libptouch
|
|||||||
layout :width_dots, :uint32,
|
layout :width_dots, :uint32,
|
||||||
:height_dots, :uint32,
|
:height_dots, :uint32,
|
||||||
:margin_mm, :uint8,
|
:margin_mm, :uint8,
|
||||||
:_pad, [:uint8, 3]
|
:flags, :uint8,
|
||||||
|
:reserved0, :uint8,
|
||||||
|
:reserved1, :uint8
|
||||||
end
|
end
|
||||||
|
|
||||||
class MediaInfo < FFI::Struct
|
class MediaInfo < FFI::Struct
|
||||||
@@ -39,7 +41,8 @@ module Libptouch
|
|||||||
:left_margin_dots, :uint16,
|
:left_margin_dots, :uint16,
|
||||||
:right_margin_dots, :uint16,
|
:right_margin_dots, :uint16,
|
||||||
:min_feed_dots, :uint16,
|
:min_feed_dots, :uint16,
|
||||||
:min_feed_mm, :double
|
:min_feed_mm, :double,
|
||||||
|
:printer_family, :uint32
|
||||||
end
|
end
|
||||||
|
|
||||||
class PngOptions < FFI::Struct
|
class PngOptions < FFI::Struct
|
||||||
@@ -51,6 +54,8 @@ module Libptouch
|
|||||||
end
|
end
|
||||||
|
|
||||||
attach_function :libptouch_create, [], :pointer
|
attach_function :libptouch_create, [], :pointer
|
||||||
|
attach_function :libptouch_set_debug_dump_path, %i[pointer string], :void
|
||||||
|
attach_function :libptouch_printer_family_label, [:uint32], :string
|
||||||
attach_function :libptouch_destroy, [:pointer], :void
|
attach_function :libptouch_destroy, [:pointer], :void
|
||||||
attach_function :libptouch_strerror, [:pointer], :string
|
attach_function :libptouch_strerror, [:pointer], :string
|
||||||
attach_function :libptouch_last_error, [:pointer], :int
|
attach_function :libptouch_last_error, [:pointer], :int
|
||||||
@@ -59,6 +64,8 @@ module Libptouch
|
|||||||
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_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
|
||||||
|
|||||||
589
ruby/lib/libptouch/cli/label_print.rb
Normal file
589
ruby/lib/libptouch/cli/label_print.rb
Normal file
@@ -0,0 +1,589 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "json"
|
||||||
|
require "optparse"
|
||||||
|
require "rqrcode"
|
||||||
|
require "rexml/document"
|
||||||
|
require "tempfile"
|
||||||
|
require "yaml"
|
||||||
|
require "barby"
|
||||||
|
require "barby/barcode/code_128"
|
||||||
|
require "barby/outputter/svg_outputter"
|
||||||
|
|
||||||
|
require "libptouch"
|
||||||
|
|
||||||
|
module Libptouch
|
||||||
|
module Cli
|
||||||
|
# PNG/SVG を扱う ptouch-print 相当の CLI(1bit ラスター経路なし)。
|
||||||
|
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)
|
||||||
|
return nil if opts_hash[:cut_invalid]
|
||||||
|
|
||||||
|
opts_hash.delete(:usb_pid_invalid)
|
||||||
|
opts_hash.delete(:cut_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,
|
||||||
|
cut_flags: Libptouch::RASTER_FLAGS_DEFAULT,
|
||||||
|
debug_dump: nil
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def pid_option_description
|
||||||
|
p900 = Libptouch::USB_PID_PTP900W
|
||||||
|
p750 = Libptouch::USB_PID_PTP750W
|
||||||
|
p710 = Libptouch::USB_PID_PTP710BT
|
||||||
|
"USB 製品 ID(16 進可)。既定 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 apply_cut_option(opts_hash, bits)
|
||||||
|
unless bits.is_a?(String) && bits.match?(/\A[01]{3}\z/)
|
||||||
|
warn "--cut must be 3 bits (e.g. 010: auto/half/chain)"
|
||||||
|
opts_hash[:cut_invalid] = true
|
||||||
|
return
|
||||||
|
end
|
||||||
|
flags = 0
|
||||||
|
flags |= Libptouch::RASTER_FLAG_AUTO_CUT if bits[0] == "1"
|
||||||
|
flags |= Libptouch::RASTER_FLAG_HALF_CUT if bits[1] == "1"
|
||||||
|
flags |= Libptouch::RASTER_FLAG_CHAIN_PRINT if bits[2] == "1"
|
||||||
|
opts_hash[:cut_flags] = flags
|
||||||
|
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,
|
||||||
|
"しきい値 0–255(既定 #{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("--cut BITS", "3bit: [auto][half][chain]。既定 011") do |v|
|
||||||
|
apply_cut_option(opts_hash, v)
|
||||||
|
end
|
||||||
|
p.on("--debug-dump PATH", "デバッグ: 印字バイト列を PATH に保存(1 印刷ごとに上書き)") do |v|
|
||||||
|
opts_hash[:debug_dump] = 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,
|
||||||
|
"しきい値 0–255(既定 #{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("--cut BITS", "3bit: [auto][half][chain]。既定 011") do |v|
|
||||||
|
apply_cut_option(opts_hash, v)
|
||||||
|
end
|
||||||
|
parser.on("--debug-dump PATH", "印字バイト列を PATH に保存") do |v|
|
||||||
|
opts_hash[:debug_dump] = 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
|
||||||
|
replace_code_placeholders(doc, data)
|
||||||
|
out = +""
|
||||||
|
formatter = REXML::Formatters::Default.new
|
||||||
|
formatter.write(doc, out)
|
||||||
|
out
|
||||||
|
end
|
||||||
|
|
||||||
|
def replace_code_placeholders(doc, data)
|
||||||
|
doc.elements.each("//*[@data-field][@data-kb-placeholder]") do |el|
|
||||||
|
key = el.attributes["data-field"].to_s
|
||||||
|
next unless data.key?(key)
|
||||||
|
|
||||||
|
kind = el.attributes["data-kb-placeholder"].to_s.downcase
|
||||||
|
next unless %w[qr barcode].include?(kind)
|
||||||
|
|
||||||
|
x, y, width, height = read_box(el)
|
||||||
|
next if width <= 0 || height <= 0
|
||||||
|
|
||||||
|
raw_value = data[key].to_s
|
||||||
|
next if raw_value.empty?
|
||||||
|
|
||||||
|
replacement = build_code_svg_element(
|
||||||
|
kind: kind,
|
||||||
|
value: raw_value,
|
||||||
|
x: x,
|
||||||
|
y: y,
|
||||||
|
width: width,
|
||||||
|
height: height
|
||||||
|
)
|
||||||
|
el.parent&.insert_after(el, replacement)
|
||||||
|
el.parent&.delete(el)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def read_box(el)
|
||||||
|
x = el.attributes["x"].to_s.to_f
|
||||||
|
y = el.attributes["y"].to_s.to_f
|
||||||
|
width = el.attributes["width"].to_s.to_f
|
||||||
|
height = el.attributes["height"].to_s.to_f
|
||||||
|
[x, y, width, height]
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_code_svg_element(kind:, value:, x:, y:, width:, height:)
|
||||||
|
svg_fragment =
|
||||||
|
if kind == "qr"
|
||||||
|
render_qr_svg(value)
|
||||||
|
else
|
||||||
|
render_barcode_svg(value)
|
||||||
|
end
|
||||||
|
fragment_doc = REXML::Document.new(strip_xml_declaration(svg_fragment))
|
||||||
|
svg_root = fragment_doc.root
|
||||||
|
svg_root = wrap_non_svg_root(svg_root) unless svg_root&.name == "svg"
|
||||||
|
|
||||||
|
if kind == "qr"
|
||||||
|
apply_qr_svg_box(svg_root, x: x, y: y, width: width, height: height)
|
||||||
|
else
|
||||||
|
apply_barcode_svg_box(svg_root, x: x, y: y, height: height)
|
||||||
|
end
|
||||||
|
svg_root
|
||||||
|
end
|
||||||
|
|
||||||
|
def apply_qr_svg_box(svg_root, x:, y:, width:, height:)
|
||||||
|
nat_w, nat_h = svg_natural_dimensions(svg_root)
|
||||||
|
if nat_w <= 0 || nat_h <= 0
|
||||||
|
nat_w = parse_length_attr(svg_root.attributes["width"])
|
||||||
|
nat_h = parse_length_attr(svg_root.attributes["height"])
|
||||||
|
end
|
||||||
|
raise ArgumentError, "QR SVG has no usable dimensions" if nat_w <= 0 || nat_h <= 0
|
||||||
|
|
||||||
|
svg_root.attributes["viewBox"] = "0 0 #{nat_w} #{nat_h}"
|
||||||
|
svg_root.attributes["x"] = x.to_s
|
||||||
|
svg_root.attributes["y"] = y.to_s
|
||||||
|
svg_root.attributes["width"] = width.to_s
|
||||||
|
svg_root.attributes["height"] = height.to_s
|
||||||
|
svg_root.attributes["preserveAspectRatio"] = "xMidYMid meet"
|
||||||
|
end
|
||||||
|
|
||||||
|
def apply_barcode_svg_box(svg_root, x:, y:, height:)
|
||||||
|
nat_w, nat_h = svg_natural_dimensions(svg_root)
|
||||||
|
if nat_w <= 0 || nat_h <= 0
|
||||||
|
nat_w = parse_length_attr(svg_root.attributes["width"])
|
||||||
|
nat_h = parse_length_attr(svg_root.attributes["height"])
|
||||||
|
end
|
||||||
|
raise ArgumentError, "barcode SVG has no usable dimensions" if nat_w <= 0 || nat_h <= 0
|
||||||
|
|
||||||
|
scale = height / nat_h
|
||||||
|
out_w = nat_w * scale
|
||||||
|
out_h = nat_h * scale
|
||||||
|
|
||||||
|
svg_root.attributes["viewBox"] = "0 0 #{nat_w} #{nat_h}"
|
||||||
|
svg_root.attributes["x"] = x.to_s
|
||||||
|
svg_root.attributes["y"] = y.to_s
|
||||||
|
svg_root.attributes["width"] = out_w.to_s
|
||||||
|
svg_root.attributes["height"] = out_h.to_s
|
||||||
|
svg_root.attributes["preserveAspectRatio"] = "xMidYMid meet"
|
||||||
|
end
|
||||||
|
|
||||||
|
def strip_xml_declaration(s)
|
||||||
|
s.to_s.sub(/\A<\?xml[^>]*\?>\s*/m, "")
|
||||||
|
end
|
||||||
|
|
||||||
|
def svg_natural_dimensions(svg_root)
|
||||||
|
vb = svg_root.attributes["viewBox"].to_s.strip
|
||||||
|
if (m = /\A\s*([-\d.eE+]+)\s+([-\d.eE+]+)\s+([-\d.eE+]+)\s+([-\d.eE+]+)\s*\z/.match(vb))
|
||||||
|
return [m[3].to_f, m[4].to_f]
|
||||||
|
end
|
||||||
|
|
||||||
|
[parse_length_attr(svg_root.attributes["width"]),
|
||||||
|
parse_length_attr(svg_root.attributes["height"])]
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse_length_attr(str)
|
||||||
|
s = str.to_s.strip
|
||||||
|
return 0.0 if s.empty?
|
||||||
|
|
||||||
|
s = s.delete_suffix("px").strip
|
||||||
|
Float(s)
|
||||||
|
rescue ArgumentError
|
||||||
|
0.0
|
||||||
|
end
|
||||||
|
|
||||||
|
def render_qr_svg(value)
|
||||||
|
qrcode = RQRCode::QRCode.new(value)
|
||||||
|
qrcode.as_svg(
|
||||||
|
standalone: true,
|
||||||
|
use_path: true,
|
||||||
|
module_size: 1,
|
||||||
|
offset: 0,
|
||||||
|
color: "000",
|
||||||
|
shape_rendering: "crispEdges"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def render_barcode_svg(value)
|
||||||
|
barcode = Barby::Code128B.new(value)
|
||||||
|
Barby::SvgOutputter.new(barcode).to_svg(
|
||||||
|
xdim: 2,
|
||||||
|
height: 64,
|
||||||
|
margin: 0
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def wrap_non_svg_root(root)
|
||||||
|
svg = REXML::Element.new("svg")
|
||||||
|
svg.add_namespace("http://www.w3.org/2000/svg")
|
||||||
|
svg.add_element(root)
|
||||||
|
svg
|
||||||
|
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
|
||||||
|
ctx.debug_dump_path = opts[:debug_dump] if opts[:debug_dump]
|
||||||
|
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,
|
||||||
|
cut_flags: opts[:cut_flags]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
ctx.check_raster(data, width_dots: width, height_dots: height,
|
||||||
|
cut_flags: opts[:cut_flags])
|
||||||
|
|
||||||
|
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,
|
||||||
|
cut_flags: opts[:cut_flags])
|
||||||
|
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
|
||||||
@@ -1,251 +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/SVG を扱う ptouch-print 相当の CLI(1bit ラスター経路なし)。
|
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(opts)
|
|
||||||
end
|
|
||||||
|
|
||||||
return usage_error("-f is required (or use --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 usb_pid_option_ok?(opts_hash)
|
|
||||||
|
|
||||||
opts_hash.delete(:usb_pid_invalid)
|
|
||||||
opts_hash
|
|
||||||
end
|
|
||||||
|
|
||||||
def default_cli_opts
|
|
||||||
{
|
|
||||||
file: nil,
|
|
||||||
threshold: nil,
|
|
||||||
usb_pid: nil,
|
|
||||||
usb_pid_invalid: false,
|
|
||||||
dry_run: 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 製品 ID(16 進可)。既定 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 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("-t", "--threshold N", Integer,
|
|
||||||
"二値化しきい値 0–255(既定 #{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("-S", "--status", "USB プリンタのステータスを 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-print-png [options]
|
|
||||||
|
|
||||||
PNG/SVG 対応。幅・高さは画像から取得します(-w/-H はありません)。
|
|
||||||
SVG は現在テープ幅に合わせて自動拡大・縮小します(USB 接続必須)。
|
|
||||||
--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
|
|
||||||
opts_hash = default_cli_opts
|
|
||||||
p = OptionParser.new do |parser|
|
|
||||||
parser.banner = "ptouch-print-png [options]"
|
|
||||||
parser.on("-f", "--file PATH", "入力 PNG/SVG ファイル") { |v| opts_hash[:file] = v }
|
|
||||||
parser.on("-t", "--threshold N", Integer,
|
|
||||||
"二値化しきい値 0–255(既定 #{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("-S", "--status", "USB プリンタのステータスを 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_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_print(path, opts, kind)
|
|
||||||
ctx = nil
|
|
||||||
begin
|
|
||||||
ctx = Libptouch::Context.new
|
|
||||||
threshold = opts[:threshold]
|
|
||||||
data, width, height = if kind == :svg
|
|
||||||
open_usb_for_opts(ctx, opts)
|
|
||||||
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
|
|
||||||
|
|
||||||
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
|
|
||||||
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
|
||||||
|
|||||||
@@ -36,6 +36,11 @@ module Libptouch
|
|||||||
self
|
self
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def debug_dump_path=(path)
|
||||||
|
Binding.libptouch_set_debug_dump_path(@native, path.nil? || path.to_s.empty? ? nil : path.to_s)
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
def close
|
def close
|
||||||
Binding.libptouch_close(@native) if @native && !@native.null?
|
Binding.libptouch_close(@native) if @native && !@native.null?
|
||||||
self
|
self
|
||||||
@@ -47,11 +52,14 @@ module Libptouch
|
|||||||
@native = nil
|
@native = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_raster(data, width_dots:, height_dots:, margin_mm: 0)
|
def check_raster(data, width_dots:, height_dots:, margin_mm: 0, cut_flags: RASTER_FLAGS_DEFAULT)
|
||||||
params = Binding::RasterParams.new
|
params = Binding::RasterParams.new
|
||||||
params[:width_dots] = width_dots
|
params[:width_dots] = width_dots
|
||||||
params[:height_dots] = height_dots
|
params[:height_dots] = height_dots
|
||||||
params[:margin_mm] = margin_mm
|
params[:margin_mm] = margin_mm
|
||||||
|
params[:flags] = cut_flags
|
||||||
|
params[:reserved0] = 0
|
||||||
|
params[:reserved1] = 0
|
||||||
buf = FFI::MemoryPointer.new(:uint8, data.bytesize)
|
buf = FFI::MemoryPointer.new(:uint8, data.bytesize)
|
||||||
buf.put_bytes(0, data)
|
buf.put_bytes(0, data)
|
||||||
raise_on_error(Binding.libptouch_check_raster(@native, buf, data.bytesize,
|
raise_on_error(Binding.libptouch_check_raster(@native, buf, data.bytesize,
|
||||||
@@ -59,11 +67,14 @@ module Libptouch
|
|||||||
self
|
self
|
||||||
end
|
end
|
||||||
|
|
||||||
def print_raster(data, width_dots:, height_dots:, margin_mm: 0)
|
def print_raster(data, width_dots:, height_dots:, margin_mm: 0, cut_flags: RASTER_FLAGS_DEFAULT)
|
||||||
params = Binding::RasterParams.new
|
params = Binding::RasterParams.new
|
||||||
params[:width_dots] = width_dots
|
params[:width_dots] = width_dots
|
||||||
params[:height_dots] = height_dots
|
params[:height_dots] = height_dots
|
||||||
params[:margin_mm] = margin_mm
|
params[:margin_mm] = margin_mm
|
||||||
|
params[:flags] = cut_flags
|
||||||
|
params[:reserved0] = 0
|
||||||
|
params[:reserved1] = 0
|
||||||
buf = FFI::MemoryPointer.new(:uint8, data.bytesize)
|
buf = FFI::MemoryPointer.new(:uint8, data.bytesize)
|
||||||
buf.put_bytes(0, data)
|
buf.put_bytes(0, data)
|
||||||
raise_on_error(Binding.libptouch_print_raster(@native, buf, data.bytesize,
|
raise_on_error(Binding.libptouch_print_raster(@native, buf, data.bytesize,
|
||||||
@@ -115,6 +126,33 @@ module Libptouch
|
|||||||
[bytes, out_params[:width_dots], out_params[:height_dots]]
|
[bytes, out_params[:width_dots], out_params[:height_dots]]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def trim_right_blank_columns(data, width_dots:, height_dots:, margin_mm: 0,
|
||||||
|
right_padding_dots: 0, cut_flags: RASTER_FLAGS_DEFAULT)
|
||||||
|
in_params = Binding::RasterParams.new
|
||||||
|
in_params[:width_dots] = width_dots
|
||||||
|
in_params[:height_dots] = height_dots
|
||||||
|
in_params[:margin_mm] = margin_mm
|
||||||
|
in_params[:flags] = cut_flags
|
||||||
|
in_params[:reserved0] = 0
|
||||||
|
in_params[:reserved1] = 0
|
||||||
|
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)
|
||||||
|
Binding.libptouch_free_raster(raw)
|
||||||
|
[bytes, out_params[:width_dots], out_params[:height_dots]]
|
||||||
|
end
|
||||||
|
|
||||||
def status_bytes
|
def status_bytes
|
||||||
buf = FFI::MemoryPointer.new(:uint8, STATUS_LENGTH)
|
buf = FFI::MemoryPointer.new(:uint8, STATUS_LENGTH)
|
||||||
raise_on_error(Binding.libptouch_get_status(@native, buf))
|
raise_on_error(Binding.libptouch_get_status(@native, buf))
|
||||||
@@ -128,17 +166,20 @@ module Libptouch
|
|||||||
def current_media_info
|
def current_media_info
|
||||||
info = Binding::MediaInfo.new
|
info = Binding::MediaInfo.new
|
||||||
raise_on_error(Binding.libptouch_get_current_media_info(@native, info.pointer))
|
raise_on_error(Binding.libptouch_get_current_media_info(@native, info.pointer))
|
||||||
|
fam = info[:printer_family]
|
||||||
{
|
{
|
||||||
media_width_code: info[:media_width_code],
|
media_width_code: info[:media_width_code],
|
||||||
media_kind_code: info[:media_kind_code],
|
media_kind_code: info[:media_kind_code],
|
||||||
print_dpi: info[:print_dpi],
|
print_dpi: info[:print_dpi],
|
||||||
feed_dpi: info[:feed_dpi],
|
feed_dpi: info[:feed_dpi],
|
||||||
tape_width_mm: info[:tape_width_mm],
|
tape_width_mm: info[:tape_width_mm],
|
||||||
printable_dots: info[:printable_dots],
|
printable_height_dots: info[:printable_dots],
|
||||||
left_margin_dots: info[:left_margin_dots],
|
left_margin_dots: info[:left_margin_dots],
|
||||||
right_margin_dots: info[:right_margin_dots],
|
right_margin_dots: info[:right_margin_dots],
|
||||||
min_feed_dots: info[:min_feed_dots],
|
min_feed_dots: info[:min_feed_dots],
|
||||||
min_feed_mm: info[:min_feed_mm]
|
min_feed_mm: info[:min_feed_mm],
|
||||||
|
printer_family: fam,
|
||||||
|
printer_family_label: Binding.libptouch_printer_family_label(fam)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -25,8 +25,11 @@ 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 "barby"
|
||||||
|
spec.add_dependency "rqrcode"
|
||||||
|
spec.add_dependency "rexml"
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,9 +1,17 @@
|
|||||||
# 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
|
||||||
|
ruby -I ruby/lib ruby/exe/ptouch-label -n --template samples/code.svg --data samples/code.yml
|
||||||
|
```
|
||||||
|
|||||||
BIN
samples/aBw70.png
Normal file
BIN
samples/aBw70.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.2 KiB |
7
samples/code.svg
Normal file
7
samples/code.svg
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 36" width="100mm" height="36mm">
|
||||||
|
<rect x="0.25" y="0.25" width="99.75" height="35.75" fill="#ffffff" stroke="#000000" stroke-width="0.25"/>
|
||||||
|
<text x="2" y="7" font-size="4.5" fill="#000000" font-family="sans-serif" data-field="itemName">サンプル備品</text>
|
||||||
|
<text x="2" y="14" font-size="3.2" fill="#000000" font-family="sans-serif" data-field="model">KB-001</text>
|
||||||
|
<rect x="78" y="3" width="18.25" height="18.25" fill="none" stroke="#000000" stroke-width="0.25" stroke-dasharray="1 0.8" data-field="qrText" data-kb-placeholder="qr"/>
|
||||||
|
<rect x="8" y="24" width="72.25" height="8.25" fill="none" stroke="#000000" stroke-width="0.25" stroke-dasharray="1 0.6" data-field="barcodeText" data-kb-placeholder="barcode"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 781 B |
4
samples/code.yml
Normal file
4
samples/code.yml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
itemName: "サンプル備品 001"
|
||||||
|
model: "KB-002"
|
||||||
|
qrText: "https://wwww.artif.org"
|
||||||
|
barcodeText: "KB001"
|
||||||
5
samples/merge_data.json
Normal file
5
samples/merge_data.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"title": "出荷ラベル",
|
||||||
|
"left": "品番 ABC-123",
|
||||||
|
"right": "数量 24"
|
||||||
|
}
|
||||||
3
samples/merge_data.yml
Normal file
3
samples/merge_data.yml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
title: "出荷ラベル"
|
||||||
|
left: "品番 ABC-123"
|
||||||
|
right: "数量 24"
|
||||||
9
samples/merge_template.svg
Normal file
9
samples/merge_template.svg
Normal 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 |
225
src/cli/main.c
225
src/cli/main.c
@@ -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,15 +22,20 @@ 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 二値化しきい値 0–255(既定 %u、PNG/SVG)\n"
|
" -t, --threshold N しきい値 0–255(既定 %u、PNG/SVG)\n"
|
||||||
|
" --trim-right[=DOTS] 右側空白を削減(省略時は左余白、失敗時 0)\n"
|
||||||
" -n, --dry-run 読み込みと check_raster のみ(USB なし)\n"
|
" -n, --dry-run 読み込みと check_raster のみ(USB なし)\n"
|
||||||
|
" -v, --verbose 印刷前情報と印刷後ステータスを標準出力\n"
|
||||||
" -p, --pid HEX USB 製品 ID(既定: P900W の 0x2085)。例: P750W 0x2062、P710BT 0x20af\n"
|
" -p, --pid HEX USB 製品 ID(既定: P900W の 0x2085)。例: P750W 0x2062、P710BT 0x20af\n"
|
||||||
" -S, --status USB 接続プリンタのステータス(テープ種・幅・色等)を表示して終了\n"
|
" --cut BITS 3bit 指定: [auto-cut][half-cut][chain-print] (例: 010)\n"
|
||||||
|
" 既定: 011(オートカットしない、ハーフカットする、つなげて印刷する)\n"
|
||||||
|
" --debug-dump PATH デバッグ: USB に送った印字データをファイルへ保存(1 印刷ごとに上書き)\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"
|
"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);
|
||||||
@@ -100,6 +106,97 @@ static int read_file(const char *path, uint8_t **out, size_t *out_len)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int parse_cut_bits(const char *s, uint8_t *out_flags)
|
||||||
|
{
|
||||||
|
if (!s || strlen(s) != 3u)
|
||||||
|
return -1;
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
if (s[i] != '0' && s[i] != '1')
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
uint8_t flags = 0u;
|
||||||
|
if (s[0] == '1')
|
||||||
|
flags |= LIBPTOUCH_RASTER_FLAG_AUTO_CUT;
|
||||||
|
if (s[1] == '1')
|
||||||
|
flags |= LIBPTOUCH_RASTER_FLAG_HALF_CUT;
|
||||||
|
if (s[2] == '1')
|
||||||
|
flags |= LIBPTOUCH_RASTER_FLAG_CHAIN_PRINT;
|
||||||
|
*out_flags = flags;
|
||||||
|
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);
|
||||||
|
printf("raster flags: 0x%02X (auto-cut %s, half-cut %s, chain-print %s)\n",
|
||||||
|
(unsigned)params->flags,
|
||||||
|
(params->flags & LIBPTOUCH_RASTER_FLAG_AUTO_CUT) ? "on" : "off",
|
||||||
|
(params->flags & LIBPTOUCH_RASTER_FLAG_HALF_CUT) ? "on" : "off",
|
||||||
|
(params->flags & LIBPTOUCH_RASTER_FLAG_CHAIN_PRINT) ? "on" : "off");
|
||||||
|
|
||||||
|
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("printer family: %s (%u)\n",
|
||||||
|
libptouch_printer_family_label(
|
||||||
|
(libptouch_printer_family_t)mi.printer_family),
|
||||||
|
(unsigned)mi.printer_family);
|
||||||
|
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;
|
||||||
@@ -107,23 +204,33 @@ int main(int argc, char **argv)
|
|||||||
unsigned threshold = LIBPTOUCH_PNG_DEFAULT_THRESHOLD;
|
unsigned threshold = LIBPTOUCH_PNG_DEFAULT_THRESHOLD;
|
||||||
unsigned usb_pid_arg = 0;
|
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;
|
||||||
|
uint8_t cut_flags = LIBPTOUCH_RASTER_FLAGS_DEFAULT;
|
||||||
|
const char *debug_dump_path = NULL;
|
||||||
static struct option longopts[] = {
|
static struct option longopts[] = {
|
||||||
{ "width", required_argument, NULL, 'w' },
|
{ "width", required_argument, NULL, 'w' },
|
||||||
{ "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' },
|
{ "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' },
|
||||||
|
{ "cut", required_argument, NULL, 1000 },
|
||||||
|
{ "debug-dump", required_argument, NULL, 1001 },
|
||||||
{ NULL, 0, NULL, 0 },
|
{ NULL, 0, NULL, 0 },
|
||||||
};
|
};
|
||||||
|
|
||||||
int c;
|
int c;
|
||||||
while ((c = getopt_long(argc, argv, "w:H:f:t:p: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':
|
||||||
@@ -146,6 +253,23 @@ 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':
|
case 'p':
|
||||||
usb_pid_arg = (unsigned)strtoul(optarg, NULL, 0);
|
usb_pid_arg = (unsigned)strtoul(optarg, NULL, 0);
|
||||||
if (usb_pid_arg == 0u || usb_pid_arg > 0xFFFFu) {
|
if (usb_pid_arg == 0u || usb_pid_arg > 0xFFFFu) {
|
||||||
@@ -153,6 +277,16 @@ int main(int argc, char **argv)
|
|||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 1000:
|
||||||
|
if (parse_cut_bits(optarg, &cut_flags) != 0) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"--cut must be 3 bits (e.g. 010: auto/half/chain)\n");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 1001:
|
||||||
|
debug_dump_path = optarg;
|
||||||
|
break;
|
||||||
case 'S':
|
case 'S':
|
||||||
want_status = 1;
|
want_status = 1;
|
||||||
break;
|
break;
|
||||||
@@ -189,6 +323,10 @@ int main(int argc, char **argv)
|
|||||||
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) {
|
||||||
@@ -232,12 +370,15 @@ int main(int argc, char **argv)
|
|||||||
fprintf(stderr, "libptouch_create failed\n");
|
fprintf(stderr, "libptouch_create failed\n");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
if (debug_dump_path)
|
||||||
|
libptouch_set_debug_dump_path(ctx, debug_dump_path);
|
||||||
|
|
||||||
uint8_t *data = NULL;
|
uint8_t *data = NULL;
|
||||||
size_t data_len = 0;
|
size_t data_len = 0;
|
||||||
libptouch_raster_params_t params = { 0, 0, 0 };
|
libptouch_raster_params_t params = { 0 };
|
||||||
libptouch_err_t e;
|
libptouch_err_t e;
|
||||||
int usb_opened = 0;
|
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 };
|
||||||
@@ -250,6 +391,7 @@ int main(int argc, char **argv)
|
|||||||
libptouch_destroy(ctx);
|
libptouch_destroy(ctx);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
data_from_lib = 1;
|
||||||
} else if (svg) {
|
} else if (svg) {
|
||||||
e = usb_pid_arg != 0
|
e = usb_pid_arg != 0
|
||||||
? libptouch_open_usb_vid_pid(ctx, LIBPTOUCH_USB_VID_BROTHER,
|
? libptouch_open_usb_vid_pid(ctx, LIBPTOUCH_USB_VID_BROTHER,
|
||||||
@@ -272,6 +414,7 @@ int main(int argc, char **argv)
|
|||||||
libptouch_destroy(ctx);
|
libptouch_destroy(ctx);
|
||||||
return 1;
|
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);
|
||||||
@@ -282,12 +425,64 @@ 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 };
|
||||||
|
e = libptouch_trim_right_blank_columns(
|
||||||
|
ctx, data, data_len, ¶ms, 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
params.flags = cut_flags;
|
||||||
|
params._reserved[0] = 0u;
|
||||||
|
params._reserved[1] = 0u;
|
||||||
|
|
||||||
e = libptouch_check_raster(ctx, data, data_len, ¶ms);
|
e = libptouch_check_raster(ctx, data, data_len, ¶ms);
|
||||||
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 || svg)
|
if (data_from_lib)
|
||||||
libptouch_free_raster(data);
|
libptouch_free_raster(data);
|
||||||
else
|
else
|
||||||
free(data);
|
free(data);
|
||||||
@@ -304,7 +499,7 @@ int main(int argc, char **argv)
|
|||||||
if (usb_opened)
|
if (usb_opened)
|
||||||
libptouch_close(ctx);
|
libptouch_close(ctx);
|
||||||
libptouch_destroy(ctx);
|
libptouch_destroy(ctx);
|
||||||
if (png || svg)
|
if (data_from_lib)
|
||||||
libptouch_free_raster(data);
|
libptouch_free_raster(data);
|
||||||
else
|
else
|
||||||
free(data);
|
free(data);
|
||||||
@@ -319,7 +514,7 @@ int main(int argc, char **argv)
|
|||||||
if (e != LIBPTOUCH_OK) {
|
if (e != LIBPTOUCH_OK) {
|
||||||
fprintf(stderr, "open_usb: %s\n", libptouch_strerror(ctx));
|
fprintf(stderr, "open_usb: %s\n", libptouch_strerror(ctx));
|
||||||
libptouch_destroy(ctx);
|
libptouch_destroy(ctx);
|
||||||
if (png || svg)
|
if (data_from_lib)
|
||||||
libptouch_free_raster(data);
|
libptouch_free_raster(data);
|
||||||
else
|
else
|
||||||
free(data);
|
free(data);
|
||||||
@@ -327,22 +522,30 @@ int main(int argc, char **argv)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (verbose)
|
||||||
|
verbose_print_pre_print_info(ctx, ¶ms, data_len);
|
||||||
|
|
||||||
e = libptouch_print_raster(ctx, data, data_len, ¶ms);
|
e = libptouch_print_raster(ctx, data, data_len, ¶ms);
|
||||||
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 || svg)
|
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 || svg)
|
if (data_from_lib)
|
||||||
libptouch_free_raster(data);
|
libptouch_free_raster(data);
|
||||||
else
|
else
|
||||||
free(data);
|
free(data);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "libptouch_internal.h"
|
#include "libptouch_internal.h"
|
||||||
|
#include "libptouch_family_config.h"
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
@@ -33,6 +34,8 @@ void ptouch_set_error_usb(libptouch_ctx *ctx, int libusb_err, const char *what)
|
|||||||
|
|
||||||
libptouch_ctx *libptouch_create(void)
|
libptouch_ctx *libptouch_create(void)
|
||||||
{
|
{
|
||||||
|
ptouch_family_config_init_once();
|
||||||
|
|
||||||
libptouch_ctx *ctx = calloc(1, sizeof(*ctx));
|
libptouch_ctx *ctx = calloc(1, sizeof(*ctx));
|
||||||
if (!ctx)
|
if (!ctx)
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -52,9 +55,29 @@ void libptouch_destroy(libptouch_ctx *ctx)
|
|||||||
if (!ctx)
|
if (!ctx)
|
||||||
return;
|
return;
|
||||||
libptouch_close(ctx);
|
libptouch_close(ctx);
|
||||||
|
free(ctx->debug_dump_path);
|
||||||
free(ctx);
|
free(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void libptouch_set_debug_dump_path(libptouch_ctx *ctx, const char *path)
|
||||||
|
{
|
||||||
|
if (!ctx)
|
||||||
|
return;
|
||||||
|
free(ctx->debug_dump_path);
|
||||||
|
ctx->debug_dump_path = NULL;
|
||||||
|
ctx->debug_dump_truncate_next = 0;
|
||||||
|
if (!path || !path[0])
|
||||||
|
return;
|
||||||
|
ctx->debug_dump_path = strdup(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ptouch_debug_dump_begin_print_job(libptouch_ctx *ctx)
|
||||||
|
{
|
||||||
|
if (!ctx)
|
||||||
|
return;
|
||||||
|
ctx->debug_dump_truncate_next = 1;
|
||||||
|
}
|
||||||
|
|
||||||
const char *libptouch_strerror(const libptouch_ctx *ctx)
|
const char *libptouch_strerror(const libptouch_ctx *ctx)
|
||||||
{
|
{
|
||||||
if (!ctx)
|
if (!ctx)
|
||||||
|
|||||||
302
src/lib/libptouch_family_config.c
Normal file
302
src/lib/libptouch_family_config.c
Normal file
@@ -0,0 +1,302 @@
|
|||||||
|
/*
|
||||||
|
* libptouch — optional JSON overrides for P700/P900 family parameters
|
||||||
|
*
|
||||||
|
* 検索順: 環境変数 LIBPTOUCH_CONFIG、カレントの printer_families.json、
|
||||||
|
* config/printer_families.json(リポジトリ配置用)
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "libptouch_family_config.h"
|
||||||
|
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
enum {
|
||||||
|
OV_PRINT_DPI = 1 << 0,
|
||||||
|
OV_MARGIN_FEED_DPI = 1 << 1,
|
||||||
|
OV_MARGIN_MAX = 1 << 2,
|
||||||
|
OV_ESC_IA = 1 << 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
unsigned mask;
|
||||||
|
double print_dpi;
|
||||||
|
double margin_feed_dpi;
|
||||||
|
unsigned margin_feed_max_dots;
|
||||||
|
int send_esc_ia;
|
||||||
|
} family_override_t;
|
||||||
|
|
||||||
|
static family_override_t g_p700;
|
||||||
|
static family_override_t g_p900;
|
||||||
|
static int g_loaded;
|
||||||
|
|
||||||
|
static const char *skip_ws(const char *p)
|
||||||
|
{
|
||||||
|
while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')
|
||||||
|
p++;
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int read_file_all(const char *path, char **out, size_t *out_len)
|
||||||
|
{
|
||||||
|
FILE *fp = fopen(path, "rb");
|
||||||
|
if (!fp)
|
||||||
|
return -1;
|
||||||
|
if (fseek(fp, 0, SEEK_END) != 0) {
|
||||||
|
fclose(fp);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
long sz = ftell(fp);
|
||||||
|
if (sz < 0 || sz > 65536) {
|
||||||
|
fclose(fp);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
rewind(fp);
|
||||||
|
char *buf = (char *)malloc((size_t)sz + 1u);
|
||||||
|
if (!buf) {
|
||||||
|
fclose(fp);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
size_t n = fread(buf, 1, (size_t)sz, fp);
|
||||||
|
fclose(fp);
|
||||||
|
buf[n] = '\0';
|
||||||
|
*out = buf;
|
||||||
|
*out_len = n;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *find_quoted_key(const char *json, const char *key)
|
||||||
|
{
|
||||||
|
char pat[48];
|
||||||
|
snprintf(pat, sizeof(pat), "\"%s\"", key);
|
||||||
|
return strstr(json, pat);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int extract_object_after_key(const char *json, const char *key,
|
||||||
|
char *out, size_t out_sz)
|
||||||
|
{
|
||||||
|
const char *p = find_quoted_key(json, key);
|
||||||
|
if (!p)
|
||||||
|
return -1;
|
||||||
|
p += strlen(key) + 2u;
|
||||||
|
p = skip_ws(p);
|
||||||
|
if (*p != ':')
|
||||||
|
return -1;
|
||||||
|
p++;
|
||||||
|
p = skip_ws(p);
|
||||||
|
if (*p != '{')
|
||||||
|
return -1;
|
||||||
|
int depth = 0;
|
||||||
|
const char *start = p;
|
||||||
|
for (; *p; p++) {
|
||||||
|
if (*p == '{')
|
||||||
|
depth++;
|
||||||
|
else if (*p == '}') {
|
||||||
|
depth--;
|
||||||
|
if (depth == 0) {
|
||||||
|
size_t n = (size_t)(p - start + 1u);
|
||||||
|
if (n >= out_sz)
|
||||||
|
return -1;
|
||||||
|
memcpy(out, start, n);
|
||||||
|
out[n] = '\0';
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int parse_bool_val(const char *s, int *out)
|
||||||
|
{
|
||||||
|
s = skip_ws(s);
|
||||||
|
if (strncmp(s, "true", 4) == 0) {
|
||||||
|
*out = 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (strncmp(s, "false", 5) == 0) {
|
||||||
|
*out = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int parse_field_double(const char *obj, const char *key, double *dst)
|
||||||
|
{
|
||||||
|
const char *p = find_quoted_key(obj, key);
|
||||||
|
if (!p)
|
||||||
|
return -1;
|
||||||
|
p += strlen(key) + 2u;
|
||||||
|
p = skip_ws(p);
|
||||||
|
if (*p != ':')
|
||||||
|
return -1;
|
||||||
|
p++;
|
||||||
|
p = skip_ws(p);
|
||||||
|
char *end = NULL;
|
||||||
|
double v = strtod(p, &end);
|
||||||
|
if (end == p)
|
||||||
|
return -1;
|
||||||
|
*dst = v;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int parse_field_uint(const char *obj, const char *key, unsigned *dst)
|
||||||
|
{
|
||||||
|
const char *p = find_quoted_key(obj, key);
|
||||||
|
if (!p)
|
||||||
|
return -1;
|
||||||
|
p += strlen(key) + 2u;
|
||||||
|
p = skip_ws(p);
|
||||||
|
if (*p != ':')
|
||||||
|
return -1;
|
||||||
|
p++;
|
||||||
|
p = skip_ws(p);
|
||||||
|
char *end = NULL;
|
||||||
|
unsigned long v = strtoul(p, &end, 10);
|
||||||
|
if (end == p)
|
||||||
|
return -1;
|
||||||
|
*dst = (unsigned)v;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int parse_field_bool(const char *obj, const char *key, int *dst)
|
||||||
|
{
|
||||||
|
const char *p = find_quoted_key(obj, key);
|
||||||
|
if (!p)
|
||||||
|
return -1;
|
||||||
|
p += strlen(key) + 2u;
|
||||||
|
p = skip_ws(p);
|
||||||
|
if (*p != ':')
|
||||||
|
return -1;
|
||||||
|
p++;
|
||||||
|
p = skip_ws(p);
|
||||||
|
return parse_bool_val(p, dst);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void apply_family_block(const char *block, family_override_t *o)
|
||||||
|
{
|
||||||
|
double d;
|
||||||
|
unsigned u;
|
||||||
|
int b;
|
||||||
|
|
||||||
|
if (parse_field_double(block, "print_dpi", &d) == 0) {
|
||||||
|
o->print_dpi = d;
|
||||||
|
o->mask |= OV_PRINT_DPI;
|
||||||
|
}
|
||||||
|
if (parse_field_double(block, "margin_feed_dpi", &d) == 0) {
|
||||||
|
o->margin_feed_dpi = d;
|
||||||
|
o->mask |= OV_MARGIN_FEED_DPI;
|
||||||
|
}
|
||||||
|
if (parse_field_uint(block, "margin_feed_max_dots", &u) == 0) {
|
||||||
|
o->margin_feed_max_dots = u;
|
||||||
|
o->mask |= OV_MARGIN_MAX;
|
||||||
|
}
|
||||||
|
if (parse_field_bool(block, "send_esc_ia_cut_each", &b) == 0) {
|
||||||
|
o->send_esc_ia = b;
|
||||||
|
o->mask |= OV_ESC_IA;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void parse_config_json(const char *json)
|
||||||
|
{
|
||||||
|
char inner[4096];
|
||||||
|
const char *families = json;
|
||||||
|
|
||||||
|
if (extract_object_after_key(json, "families", inner, sizeof(inner)) == 0)
|
||||||
|
families = inner;
|
||||||
|
|
||||||
|
char block[2048];
|
||||||
|
if (extract_object_after_key(families, "p700", block, sizeof(block)) == 0)
|
||||||
|
apply_family_block(block, &g_p700);
|
||||||
|
if (extract_object_after_key(families, "p900", block, sizeof(block)) == 0)
|
||||||
|
apply_family_block(block, &g_p900);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void try_load_path(const char *path)
|
||||||
|
{
|
||||||
|
char *buf = NULL;
|
||||||
|
size_t len = 0;
|
||||||
|
if (read_file_all(path, &buf, &len) != 0)
|
||||||
|
return;
|
||||||
|
parse_config_json(buf);
|
||||||
|
free(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ptouch_family_config_init_once(void)
|
||||||
|
{
|
||||||
|
if (g_loaded)
|
||||||
|
return;
|
||||||
|
g_loaded = 1;
|
||||||
|
memset(&g_p700, 0, sizeof(g_p700));
|
||||||
|
memset(&g_p900, 0, sizeof(g_p900));
|
||||||
|
|
||||||
|
const char *env = getenv("LIBPTOUCH_CONFIG");
|
||||||
|
if (env && env[0])
|
||||||
|
try_load_path(env);
|
||||||
|
try_load_path("printer_families.json");
|
||||||
|
try_load_path("config/printer_families.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
double ptouch_effective_print_dpi(const ptouch_printer_profile_t *prof)
|
||||||
|
{
|
||||||
|
if (!prof)
|
||||||
|
return 0.0;
|
||||||
|
if (prof->family == LIBPTOUCH_FAMILY_P700 &&
|
||||||
|
(g_p700.mask & OV_PRINT_DPI))
|
||||||
|
return g_p700.print_dpi;
|
||||||
|
if (prof->family == LIBPTOUCH_FAMILY_P900 &&
|
||||||
|
(g_p900.mask & OV_PRINT_DPI))
|
||||||
|
return g_p900.print_dpi;
|
||||||
|
return prof->print_dpi;
|
||||||
|
}
|
||||||
|
|
||||||
|
double ptouch_effective_margin_feed_dpi(const ptouch_printer_profile_t *prof)
|
||||||
|
{
|
||||||
|
if (!prof)
|
||||||
|
return 0.0;
|
||||||
|
if (prof->family == LIBPTOUCH_FAMILY_P700 &&
|
||||||
|
(g_p700.mask & OV_MARGIN_FEED_DPI))
|
||||||
|
return g_p700.margin_feed_dpi;
|
||||||
|
if (prof->family == LIBPTOUCH_FAMILY_P900 &&
|
||||||
|
(g_p900.mask & OV_MARGIN_FEED_DPI))
|
||||||
|
return g_p900.margin_feed_dpi;
|
||||||
|
return prof->margin_feed_dpi;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned ptouch_effective_margin_feed_max_dots(const ptouch_printer_profile_t *prof)
|
||||||
|
{
|
||||||
|
if (!prof)
|
||||||
|
return 0u;
|
||||||
|
if (prof->family == LIBPTOUCH_FAMILY_P700 &&
|
||||||
|
(g_p700.mask & OV_MARGIN_MAX))
|
||||||
|
return g_p700.margin_feed_max_dots;
|
||||||
|
if (prof->family == LIBPTOUCH_FAMILY_P900 &&
|
||||||
|
(g_p900.mask & OV_MARGIN_MAX))
|
||||||
|
return g_p900.margin_feed_max_dots;
|
||||||
|
return prof->margin_feed_max_dots;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ptouch_effective_send_esc_ia_cut_each(const ptouch_printer_profile_t *prof)
|
||||||
|
{
|
||||||
|
if (!prof)
|
||||||
|
return 0;
|
||||||
|
if (prof->family == LIBPTOUCH_FAMILY_P700 &&
|
||||||
|
(g_p700.mask & OV_ESC_IA))
|
||||||
|
return g_p700.send_esc_ia;
|
||||||
|
if (prof->family == LIBPTOUCH_FAMILY_P900 &&
|
||||||
|
(g_p900.mask & OV_ESC_IA))
|
||||||
|
return g_p900.send_esc_ia;
|
||||||
|
return (int)prof->send_esc_ia_cut_each;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *libptouch_printer_family_label(libptouch_printer_family_t family)
|
||||||
|
{
|
||||||
|
switch (family) {
|
||||||
|
case LIBPTOUCH_FAMILY_P700:
|
||||||
|
return "p700";
|
||||||
|
case LIBPTOUCH_FAMILY_P900:
|
||||||
|
return "p900";
|
||||||
|
default:
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
19
src/lib/libptouch_family_config.h
Normal file
19
src/lib/libptouch_family_config.h
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* libptouch — printer family overrides (JSON config, optional)
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef LIBPTOUCH_FAMILY_CONFIG_H
|
||||||
|
#define LIBPTOUCH_FAMILY_CONFIG_H
|
||||||
|
|
||||||
|
#include "libptouch_layout.h"
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
void ptouch_family_config_init_once(void);
|
||||||
|
|
||||||
|
double ptouch_effective_print_dpi(const ptouch_printer_profile_t *prof);
|
||||||
|
double ptouch_effective_margin_feed_dpi(const ptouch_printer_profile_t *prof);
|
||||||
|
unsigned ptouch_effective_margin_feed_max_dots(const ptouch_printer_profile_t *prof);
|
||||||
|
int ptouch_effective_send_esc_ia_cut_each(const ptouch_printer_profile_t *prof);
|
||||||
|
|
||||||
|
#endif /* LIBPTOUCH_FAMILY_CONFIG_H */
|
||||||
@@ -23,8 +23,13 @@ struct libptouch_ctx {
|
|||||||
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 */
|
uint16_t usb_pid; /* 0 if USB not open; used to pick 128- vs 560-dot raster */
|
||||||
|
char *debug_dump_path; /* strdup; NULL = 印字データのファイル保存なし */
|
||||||
|
int debug_dump_truncate_next; /* 次の bulk 書き込みでファイルを切り詰め(ジョブ先頭) */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** 1 回の libptouch_print_raster ジョブの先頭で呼ぶ(最初の bulk 前) */
|
||||||
|
void ptouch_debug_dump_begin_print_job(libptouch_ctx *ctx);
|
||||||
|
|
||||||
/* ctx に最終エラー(コードとメッセージ)を記録する。公開 API のエラー返却前に使う。 */
|
/* 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 に渡す。 */
|
/* libusb のエラー番号を人が読めるメッセージにして ptouch_set_error に渡す。 */
|
||||||
|
|||||||
@@ -61,9 +61,12 @@ static const ptouch_printer_profile_t ptouch_layout_profiles[] = {
|
|||||||
.usb_pids = pids_128pin,
|
.usb_pids = pids_128pin,
|
||||||
.model_codes = models_128pin,
|
.model_codes = models_128pin,
|
||||||
.is_default = 0,
|
.is_default = 0,
|
||||||
|
.family = LIBPTOUCH_FAMILY_P700,
|
||||||
.head_width_dots = 128u,
|
.head_width_dots = 128u,
|
||||||
|
.print_dpi = 180.0,
|
||||||
.margin_feed_dpi = 180.0,
|
.margin_feed_dpi = 180.0,
|
||||||
.margin_feed_max_dots = 900u,
|
.margin_feed_max_dots = 900u,
|
||||||
|
.send_esc_ia_cut_each = 0u,
|
||||||
.tze = layout_tze_128,
|
.tze = layout_tze_128,
|
||||||
.tze_count = sizeof(layout_tze_128) / sizeof(layout_tze_128[0]),
|
.tze_count = sizeof(layout_tze_128) / sizeof(layout_tze_128[0]),
|
||||||
.hs = layout_hs_128,
|
.hs = layout_hs_128,
|
||||||
@@ -74,9 +77,12 @@ static const ptouch_printer_profile_t ptouch_layout_profiles[] = {
|
|||||||
.usb_pids = NULL,
|
.usb_pids = NULL,
|
||||||
.model_codes = NULL,
|
.model_codes = NULL,
|
||||||
.is_default = 1,
|
.is_default = 1,
|
||||||
|
.family = LIBPTOUCH_FAMILY_P900,
|
||||||
.head_width_dots = 560u,
|
.head_width_dots = 560u,
|
||||||
|
.print_dpi = 360.0,
|
||||||
.margin_feed_dpi = 360.0,
|
.margin_feed_dpi = 360.0,
|
||||||
.margin_feed_max_dots = 1800u,
|
.margin_feed_max_dots = 1800u,
|
||||||
|
.send_esc_ia_cut_each = 1u,
|
||||||
.tze = layout_tze_560,
|
.tze = layout_tze_560,
|
||||||
.tze_count = sizeof(layout_tze_560) / sizeof(layout_tze_560[0]),
|
.tze_count = sizeof(layout_tze_560) / sizeof(layout_tze_560[0]),
|
||||||
.hs = layout_hs_560,
|
.hs = layout_hs_560,
|
||||||
|
|||||||
@@ -29,9 +29,13 @@ typedef struct ptouch_printer_profile {
|
|||||||
const uint16_t *usb_pids; /* 0-terminated; NULL = do not match on PID */
|
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] */
|
const uint8_t *model_codes; /* 0-terminated; NULL = do not match on status[4] */
|
||||||
int is_default; /* 1 = fallback when no other profile matches */
|
int is_default; /* 1 = fallback when no other profile matches */
|
||||||
|
libptouch_printer_family_t family; /**< P700 / P900 系統(コマンド差の条件分岐の基準) */
|
||||||
unsigned head_width_dots; /* 128 or 560; full raster width for pack/GF payload */
|
unsigned head_width_dots; /* 128 or 560; full raster width for pack/GF payload */
|
||||||
|
double print_dpi; /* 印字幅方向 DPI(PDF・ラスター解像度の目安) */
|
||||||
double margin_feed_dpi; /* ESC i d feed dots per inch (PDF) */
|
double margin_feed_dpi; /* ESC i d feed dots per inch (PDF) */
|
||||||
unsigned margin_feed_max_dots;
|
unsigned margin_feed_max_dots;
|
||||||
|
/** ESC i A(各 n 枚でカット)を送るか(P700 系では送らない) */
|
||||||
|
unsigned char send_esc_ia_cut_each;
|
||||||
const ptouch_layout_row_t *tze;
|
const ptouch_layout_row_t *tze;
|
||||||
size_t tze_count;
|
size_t tze_count;
|
||||||
const ptouch_layout_row_t *hs;
|
const ptouch_layout_row_t *hs;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "libptouch_internal.h"
|
#include "libptouch_internal.h"
|
||||||
|
#include "libptouch_family_config.h"
|
||||||
#include "libptouch_layout.h"
|
#include "libptouch_layout.h"
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
@@ -34,15 +35,6 @@ static double tape_width_mm_from_code(uint8_t media_w)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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_err_t libptouch_get_current_media_info(libptouch_ctx *ctx,
|
||||||
libptouch_media_info_t *out_info)
|
libptouch_media_info_t *out_info)
|
||||||
{
|
{
|
||||||
@@ -53,6 +45,7 @@ libptouch_err_t libptouch_get_current_media_info(libptouch_ctx *ctx,
|
|||||||
}
|
}
|
||||||
|
|
||||||
memset(out_info, 0, sizeof(*out_info));
|
memset(out_info, 0, sizeof(*out_info));
|
||||||
|
ptouch_family_config_init_once();
|
||||||
|
|
||||||
uint8_t st[LIBPTOUCH_STATUS_LENGTH];
|
uint8_t st[LIBPTOUCH_STATUS_LENGTH];
|
||||||
libptouch_err_t v = libptouch_get_status(ctx, st);
|
libptouch_err_t v = libptouch_get_status(ctx, st);
|
||||||
@@ -85,15 +78,16 @@ libptouch_err_t libptouch_get_current_media_info(libptouch_ctx *ctx,
|
|||||||
|
|
||||||
out_info->media_width_code = st[10];
|
out_info->media_width_code = st[10];
|
||||||
out_info->media_kind_code = st[11];
|
out_info->media_kind_code = st[11];
|
||||||
out_info->print_dpi = print_dpi_from_profile(prof);
|
out_info->print_dpi = ptouch_effective_print_dpi(prof);
|
||||||
out_info->feed_dpi = prof->margin_feed_dpi;
|
out_info->feed_dpi = ptouch_effective_margin_feed_dpi(prof);
|
||||||
out_info->tape_width_mm = tape_mm;
|
out_info->tape_width_mm = tape_mm;
|
||||||
out_info->printable_dots = print_dots;
|
out_info->printable_dots = print_dots;
|
||||||
out_info->left_margin_dots = left_dots;
|
out_info->left_margin_dots = left_dots;
|
||||||
out_info->right_margin_dots = right_dots;
|
out_info->right_margin_dots = right_dots;
|
||||||
out_info->min_feed_dots = 14u;
|
out_info->min_feed_dots = 14u;
|
||||||
out_info->min_feed_mm =
|
out_info->min_feed_mm = ((double)out_info->min_feed_dots * 25.4) /
|
||||||
((double)out_info->min_feed_dots * 25.4) / prof->margin_feed_dpi;
|
ptouch_effective_margin_feed_dpi(prof);
|
||||||
|
out_info->printer_family = (uint32_t)prof->family;
|
||||||
|
|
||||||
ptouch_set_error(ctx, LIBPTOUCH_OK, "");
|
ptouch_set_error(ctx, LIBPTOUCH_OK, "");
|
||||||
return LIBPTOUCH_OK;
|
return LIBPTOUCH_OK;
|
||||||
|
|||||||
@@ -36,9 +36,8 @@ libptouch_err_t libptouch_png_file_to_raster(libptouch_ctx *ctx, const char *pat
|
|||||||
|
|
||||||
*out_raster = NULL;
|
*out_raster = NULL;
|
||||||
*out_raster_bytes = 0;
|
*out_raster_bytes = 0;
|
||||||
out_params->width_dots = 0;
|
memset(out_params, 0, sizeof(*out_params));
|
||||||
out_params->height_dots = 0;
|
out_params->flags = LIBPTOUCH_RASTER_FLAGS_DEFAULT;
|
||||||
out_params->margin_mm = 0;
|
|
||||||
|
|
||||||
FILE *fp = fopen(path, "rb");
|
FILE *fp = fopen(path, "rb");
|
||||||
if (!fp) {
|
if (!fp) {
|
||||||
@@ -157,6 +156,7 @@ libptouch_err_t libptouch_png_file_to_raster(libptouch_ctx *ctx, const char *pat
|
|||||||
out_params->width_dots = (uint32_t)width;
|
out_params->width_dots = (uint32_t)width;
|
||||||
out_params->height_dots = (uint32_t)height;
|
out_params->height_dots = (uint32_t)height;
|
||||||
out_params->margin_mm = 0;
|
out_params->margin_mm = 0;
|
||||||
|
out_params->flags = LIBPTOUCH_RASTER_FLAGS_DEFAULT;
|
||||||
*out_raster = out;
|
*out_raster = out;
|
||||||
*out_raster_bytes = total;
|
*out_raster_bytes = total;
|
||||||
ptouch_set_error(ctx, LIBPTOUCH_OK, "");
|
ptouch_set_error(ctx, LIBPTOUCH_OK, "");
|
||||||
|
|||||||
@@ -6,7 +6,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "libptouch_internal.h"
|
#include "libptouch_internal.h"
|
||||||
|
#include "libptouch_family_config.h"
|
||||||
#include "libptouch_layout.h"
|
#include "libptouch_layout.h"
|
||||||
|
#include "libptouch_protocol.h"
|
||||||
|
|
||||||
#include <libusb.h>
|
#include <libusb.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
@@ -75,6 +77,8 @@ libptouch_err_t libptouch_print_raster(libptouch_ctx *ctx,
|
|||||||
return LIBPTOUCH_ERR_IO;
|
return LIBPTOUCH_ERR_IO;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ptouch_family_config_init_once();
|
||||||
|
|
||||||
uint8_t st[LIBPTOUCH_STATUS_LENGTH];
|
uint8_t st[LIBPTOUCH_STATUS_LENGTH];
|
||||||
v = libptouch_get_status(ctx, st);
|
v = libptouch_get_status(ctx, st);
|
||||||
if (v != LIBPTOUCH_OK)
|
if (v != LIBPTOUCH_OK)
|
||||||
@@ -102,7 +106,7 @@ libptouch_err_t libptouch_print_raster(libptouch_ctx *ctx,
|
|||||||
(void)right_dots;
|
(void)right_dots;
|
||||||
|
|
||||||
unsigned head_dots = prof->head_width_dots;
|
unsigned head_dots = prof->head_width_dots;
|
||||||
size_t line_payload = (size_t)((head_dots + 7u) / 8u);
|
size_t line_payload = ptouch_line_payload_bytes(head_dots);
|
||||||
size_t gf_packet = 3u + line_payload;
|
size_t gf_packet = 3u + line_payload;
|
||||||
|
|
||||||
uint32_t wd = params->width_dots;
|
uint32_t wd = params->width_dots;
|
||||||
@@ -125,8 +129,9 @@ libptouch_err_t libptouch_print_raster(libptouch_ctx *ctx,
|
|||||||
return LIBPTOUCH_ERR_ARG;
|
return LIBPTOUCH_ERR_ARG;
|
||||||
}
|
}
|
||||||
|
|
||||||
double margin_dpi = prof->margin_feed_dpi;
|
double margin_dpi = ptouch_effective_margin_feed_dpi(prof);
|
||||||
unsigned margin_max = prof->margin_feed_max_dots;
|
unsigned margin_max =
|
||||||
|
ptouch_effective_margin_feed_max_dots(prof);
|
||||||
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 * margin_dpi /
|
margin_dots = (unsigned)((double)params->margin_mm * margin_dpi /
|
||||||
@@ -139,21 +144,6 @@ libptouch_err_t libptouch_print_raster(libptouch_ctx *ctx,
|
|||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
@@ -165,18 +155,31 @@ 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, prof, 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 };
|
/* ESC i M: bit6 オートカット(reference/印字データ.md)。既定オフ。 */
|
||||||
|
uint8_t esc_im_n1 =
|
||||||
|
(params->flags & LIBPTOUCH_RASTER_FLAG_AUTO_CUT) ? 0x40u : 0x00u;
|
||||||
|
uint8_t esc_im[] = { 0x1B, 0x69, 0x4D, esc_im_n1 };
|
||||||
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") — P700 系では送らない(通信エラーになり得る)。
|
||||||
static const uint8_t esc_ik[] = { 0x1B, 0x69, 0x4B, 0x0C };
|
* 既定は系統フラグ、printer_families.json で上書き可。
|
||||||
|
*/
|
||||||
|
if (ptouch_effective_send_esc_ia_cut_each(prof)) {
|
||||||
|
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_n1 =
|
||||||
|
ptouch_esc_ik_extended_mode(ctx->usb_pid, params->flags);
|
||||||
|
uint8_t esc_ik[] = { 0x1B, 0x69, 0x4B, esc_ik_n1 };
|
||||||
memcpy(head + pos, esc_ik, sizeof(esc_ik));
|
memcpy(head + pos, esc_ik, sizeof(esc_ik));
|
||||||
pos += sizeof(esc_ik);
|
pos += sizeof(esc_ik);
|
||||||
|
|
||||||
@@ -192,6 +195,7 @@ libptouch_err_t libptouch_print_raster(libptouch_ctx *ctx,
|
|||||||
memcpy(head + pos, mode_m, sizeof(mode_m));
|
memcpy(head + pos, mode_m, sizeof(mode_m));
|
||||||
pos += sizeof(mode_m);
|
pos += sizeof(mode_m);
|
||||||
|
|
||||||
|
ptouch_debug_dump_begin_print_job(ctx);
|
||||||
v = ptouch_bulk_send_job(ctx, head, pos, "print preamble");
|
v = ptouch_bulk_send_job(ctx, head, pos, "print preamble");
|
||||||
if (v != LIBPTOUCH_OK) {
|
if (v != LIBPTOUCH_OK) {
|
||||||
free(transposed);
|
free(transposed);
|
||||||
@@ -199,7 +203,6 @@ 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;
|
||||||
static const uint8_t g_hdr[] = { 0x47, 0x46, 0x00 };
|
|
||||||
|
|
||||||
uint8_t *gbuf = (uint8_t *)malloc(gf_packet);
|
uint8_t *gbuf = (uint8_t *)malloc(gf_packet);
|
||||||
if (!gbuf) {
|
if (!gbuf) {
|
||||||
@@ -213,7 +216,7 @@ libptouch_err_t libptouch_print_raster(libptouch_ctx *ctx,
|
|||||||
const uint8_t *row = src + (size_t)y * row_b;
|
const uint8_t *row = src + (size_t)y * row_b;
|
||||||
pack_line(gbuf + 3, line_payload, head_dots, row, wd, left_dots,
|
pack_line(gbuf + 3, line_payload, head_dots, row, wd, left_dots,
|
||||||
print_dots);
|
print_dots);
|
||||||
memcpy(gbuf, g_hdr, sizeof(g_hdr));
|
ptouch_fill_gf_header(gbuf, line_payload);
|
||||||
v = ptouch_bulk_send_job(ctx, gbuf, gf_packet, "raster line");
|
v = ptouch_bulk_send_job(ctx, gbuf, gf_packet, "raster line");
|
||||||
if (v != LIBPTOUCH_OK) {
|
if (v != LIBPTOUCH_OK) {
|
||||||
free(gbuf);
|
free(gbuf);
|
||||||
|
|||||||
57
src/lib/libptouch_protocol.c
Normal file
57
src/lib/libptouch_protocol.c
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
#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], const ptouch_printer_profile_t *prof,
|
||||||
|
uint8_t media_width, uint32_t raster_lines)
|
||||||
|
{
|
||||||
|
uint8_t page_byte = 0x00u;
|
||||||
|
if (prof && prof->family == LIBPTOUCH_FAMILY_P900)
|
||||||
|
page_byte = 0x02u; /* 単一ラスター = 1 ページのみ → 最終ページ */
|
||||||
|
|
||||||
|
out[0] = 0x1Bu;
|
||||||
|
out[1] = 0x69u;
|
||||||
|
out[2] = 0x7Au;
|
||||||
|
/* PI flags: enable media/width/length with quality bit for broad compatibility. */
|
||||||
|
out[3] = 0x84u;
|
||||||
|
/* Use status media-kind byte directly (e.g., 0x01 laminated, 0x03 non-laminate). */
|
||||||
|
out[4] = 0x00u;
|
||||||
|
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] = page_byte;
|
||||||
|
out[12] = 0x00u; /* fixed */
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t ptouch_esc_ik_extended_mode(uint16_t usb_pid, uint8_t raster_flags)
|
||||||
|
{
|
||||||
|
uint8_t n1 = 0x00u;
|
||||||
|
int want_half = (raster_flags & LIBPTOUCH_RASTER_FLAG_HALF_CUT) != 0;
|
||||||
|
int want_chain = (raster_flags & LIBPTOUCH_RASTER_FLAG_CHAIN_PRINT) != 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* reference/印字データ.md:
|
||||||
|
* - bit2: ハーフカット
|
||||||
|
* - bit3: ChainPrint しない
|
||||||
|
*/
|
||||||
|
if (usb_pid == LIBPTOUCH_USB_PID_PTP710BT)
|
||||||
|
want_half = 0; /* PT-P710BT はハーフカット非対応 */
|
||||||
|
if (want_half)
|
||||||
|
n1 |= 0x04u;
|
||||||
|
if (!want_chain)
|
||||||
|
n1 |= 0x08u;
|
||||||
|
return n1;
|
||||||
|
}
|
||||||
20
src/lib/libptouch_protocol.h
Normal file
20
src/lib/libptouch_protocol.h
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#ifndef LIBPTOUCH_PROTOCOL_H
|
||||||
|
#define LIBPTOUCH_PROTOCOL_H
|
||||||
|
|
||||||
|
#include "libptouch_layout.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);
|
||||||
|
/**
|
||||||
|
* ESC i z (13 バイト)。out[11] は prof の系統から決める(P900 の単一ページ印字では 02h、
|
||||||
|
* P700 は 00h)。prof が NULL のときは 00h。
|
||||||
|
*/
|
||||||
|
void ptouch_fill_esc_iz(uint8_t out[13], const ptouch_printer_profile_t *prof,
|
||||||
|
uint8_t media_width, uint32_t raster_lines);
|
||||||
|
/** ESC i K の n1: flags(オート/ハーフ/チェーン)と機種制約から算出。 */
|
||||||
|
uint8_t ptouch_esc_ik_extended_mode(uint16_t usb_pid, uint8_t raster_flags);
|
||||||
|
|
||||||
|
#endif /* LIBPTOUCH_PROTOCOL_H */
|
||||||
@@ -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) {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
libptouch_err_t libptouch_svg_file_to_raster_fit_current_tape(
|
libptouch_err_t libptouch_svg_file_to_raster_fit_current_tape(
|
||||||
libptouch_ctx *ctx, const char *path,
|
libptouch_ctx *ctx, const char *path,
|
||||||
@@ -51,9 +52,8 @@ libptouch_err_t libptouch_svg_file_to_raster_fit_current_tape(
|
|||||||
|
|
||||||
*out_raster = NULL;
|
*out_raster = NULL;
|
||||||
*out_raster_bytes = 0;
|
*out_raster_bytes = 0;
|
||||||
out_params->width_dots = 0;
|
memset(out_params, 0, sizeof(*out_params));
|
||||||
out_params->height_dots = 0;
|
out_params->flags = LIBPTOUCH_RASTER_FLAGS_DEFAULT;
|
||||||
out_params->margin_mm = 0;
|
|
||||||
|
|
||||||
uint8_t st[LIBPTOUCH_STATUS_LENGTH];
|
uint8_t st[LIBPTOUCH_STATUS_LENGTH];
|
||||||
libptouch_err_t v = libptouch_get_status(ctx, st);
|
libptouch_err_t v = libptouch_get_status(ctx, st);
|
||||||
@@ -203,6 +203,7 @@ libptouch_err_t libptouch_svg_file_to_raster_fit_current_tape(
|
|||||||
out_params->width_dots = out_w;
|
out_params->width_dots = out_w;
|
||||||
out_params->height_dots = out_h;
|
out_params->height_dots = out_h;
|
||||||
out_params->margin_mm = 0;
|
out_params->margin_mm = 0;
|
||||||
|
out_params->flags = LIBPTOUCH_RASTER_FLAGS_DEFAULT;
|
||||||
*out_raster = out;
|
*out_raster = out;
|
||||||
*out_raster_bytes = total;
|
*out_raster_bytes = total;
|
||||||
ptouch_set_error(ctx, LIBPTOUCH_OK, "");
|
ptouch_set_error(ctx, LIBPTOUCH_OK, "");
|
||||||
|
|||||||
87
src/lib/libptouch_trim.c
Normal file
87
src/lib/libptouch_trim.c
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
out_params->flags = in_params->flags;
|
||||||
|
out_params->_reserved[0] = in_params->_reserved[0];
|
||||||
|
out_params->_reserved[1] = in_params->_reserved[1];
|
||||||
|
ptouch_set_error(ctx, LIBPTOUCH_OK, "");
|
||||||
|
return LIBPTOUCH_OK;
|
||||||
|
}
|
||||||
@@ -194,5 +194,14 @@ libptouch_err_t ptouch_bulk_send_job(libptouch_ctx *ctx, const uint8_t *buf,
|
|||||||
ptouch_set_error(ctx, LIBPTOUCH_ERR_USB, msg);
|
ptouch_set_error(ctx, LIBPTOUCH_ERR_USB, msg);
|
||||||
return LIBPTOUCH_ERR_USB;
|
return LIBPTOUCH_ERR_USB;
|
||||||
}
|
}
|
||||||
|
if (ctx->debug_dump_path && ctx->debug_dump_path[0]) {
|
||||||
|
FILE *fp = fopen(ctx->debug_dump_path,
|
||||||
|
ctx->debug_dump_truncate_next ? "wb" : "ab");
|
||||||
|
if (fp) {
|
||||||
|
(void)fwrite(buf, 1, len, fp);
|
||||||
|
(void)fclose(fp);
|
||||||
|
}
|
||||||
|
ctx->debug_dump_truncate_next = 0;
|
||||||
|
}
|
||||||
return LIBPTOUCH_OK;
|
return LIBPTOUCH_OK;
|
||||||
}
|
}
|
||||||
|
|||||||
77
tests/protocol_regression_test.c
Normal file
77
tests/protocol_regression_test.c
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
#include "libptouch_protocol.h"
|
||||||
|
|
||||||
|
#include "libptouch.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
static const ptouch_printer_profile_t prof_fixture_p700 = {
|
||||||
|
.family = LIBPTOUCH_FAMILY_P700,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const ptouch_printer_profile_t prof_fixture_p900 = {
|
||||||
|
.family = LIBPTOUCH_FAMILY_P900,
|
||||||
|
};
|
||||||
|
|
||||||
|
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, &prof_fixture_p700, 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], 0x84);
|
||||||
|
fail |= expect_int("iz_media_kind_fixed", iz[4], 0x00);
|
||||||
|
fail |= expect_int("iz_media_width", iz[5], 0x0C);
|
||||||
|
fail |= expect_int("iz_lines_lsb", iz[7], 70);
|
||||||
|
fail |= expect_int("iz_page_control_p700", iz[11], 0x00);
|
||||||
|
fail |= expect_int("iz_last_fixed", iz[12], 0x00);
|
||||||
|
|
||||||
|
ptouch_fill_esc_iz(iz, &prof_fixture_p900, 0x0Cu, 70u);
|
||||||
|
fail |= expect_int("iz_page_control_p900_single", iz[11], 0x02);
|
||||||
|
|
||||||
|
/* ESC i K: bit2=ハーフカット、bit3=ChainPrintしない */
|
||||||
|
fail |= expect_int(
|
||||||
|
"esc_ik_p750w_half_chain",
|
||||||
|
(int)ptouch_esc_ik_extended_mode(
|
||||||
|
LIBPTOUCH_USB_PID_PTP750W, LIBPTOUCH_RASTER_FLAGS_DEFAULT),
|
||||||
|
0x04);
|
||||||
|
fail |= expect_int(
|
||||||
|
"esc_ik_p710bt_no_half",
|
||||||
|
(int)ptouch_esc_ik_extended_mode(
|
||||||
|
LIBPTOUCH_USB_PID_PTP710BT, LIBPTOUCH_RASTER_FLAGS_DEFAULT),
|
||||||
|
0x00);
|
||||||
|
fail |= expect_int(
|
||||||
|
"esc_ik_p900w_auto_half_no_chain",
|
||||||
|
(int)ptouch_esc_ik_extended_mode(
|
||||||
|
LIBPTOUCH_USB_PID_PTP900W,
|
||||||
|
LIBPTOUCH_RASTER_FLAG_AUTO_CUT |
|
||||||
|
LIBPTOUCH_RASTER_FLAG_HALF_CUT),
|
||||||
|
0x0C);
|
||||||
|
|
||||||
|
return fail ? EXIT_FAILURE : EXIT_SUCCESS;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user