Compare commits

..

5 Commits

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

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

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

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

View File

@@ -14,6 +14,7 @@ include(GNUInstallDirs)
find_package(PkgConfig REQUIRED) find_package(PkgConfig REQUIRED)
pkg_check_modules(LIBUSB REQUIRED IMPORTED_TARGET libusb-1.0) pkg_check_modules(LIBUSB REQUIRED IMPORTED_TARGET libusb-1.0)
find_package(PNG REQUIRED) find_package(PNG REQUIRED)
pkg_check_modules(LIBRSVG IMPORTED_TARGET librsvg-2.0)
configure_file( configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/include/libptouch_version.h.in" "${CMAKE_CURRENT_SOURCE_DIR}/include/libptouch_version.h.in"
@@ -24,9 +25,12 @@ 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_layout.c
src/lib/libptouch_media_info.c
src/lib/libptouch_print.c src/lib/libptouch_print.c
src/lib/libptouch_status.c src/lib/libptouch_status.c
src/lib/libptouch_png.c src/lib/libptouch_png.c
src/lib/libptouch_svg.c
) )
add_library(ptouch STATIC ${LIBPTOUCH_SOURCES}) add_library(ptouch STATIC ${LIBPTOUCH_SOURCES})
@@ -51,6 +55,12 @@ target_include_directories(ptouch_shared PRIVATE
) )
target_link_libraries(ptouch PRIVATE PkgConfig::LIBUSB PNG::PNG) target_link_libraries(ptouch PRIVATE PkgConfig::LIBUSB PNG::PNG)
target_link_libraries(ptouch_shared PRIVATE PkgConfig::LIBUSB PNG::PNG) target_link_libraries(ptouch_shared PRIVATE PkgConfig::LIBUSB PNG::PNG)
if(LIBRSVG_FOUND)
target_link_libraries(ptouch PRIVATE PkgConfig::LIBRSVG)
target_link_libraries(ptouch_shared PRIVATE PkgConfig::LIBRSVG)
target_compile_definitions(ptouch PRIVATE LIBPTOUCH_HAS_RSVG=1)
target_compile_definitions(ptouch_shared PRIVATE LIBPTOUCH_HAS_RSVG=1)
endif()
if(NOT MSVC) if(NOT MSVC)
target_compile_options(ptouch PRIVATE -Wall -Wextra -Wpedantic) target_compile_options(ptouch PRIVATE -Wall -Wextra -Wpedantic)
target_compile_options(ptouch_shared PRIVATE -Wall -Wextra -Wpedantic) target_compile_options(ptouch_shared PRIVATE -Wall -Wextra -Wpedantic)

View File

@@ -4,22 +4,22 @@
Brother P-touch シリーズ向けのラベル印刷用 **C コアライブラリlibptouch** と、動作確認用 **CLI`ptouch-print`** のリポジトリです。 Brother P-touch シリーズ向けのラベル印刷用 **C コアライブラリlibptouch** と、動作確認用 **CLI`ptouch-print`** のリポジトリです。
保有機種: **PT-P900W**USB・ラスターコマンド 対象機種: **PT-P900W**560 ドットヘッド)、**PT-P750W** / **PT-P710BT**128 ドットヘッド・USB`libptouch_open_usb_vid_pid` に各機種の VID/PID を渡すP900W 既定は `libptouch_open_usb`。P750/P710 のラスター仕様は `reference/cv_ptp750w_710bt_jpn_raster_102.pdf`
## レイアウト ## レイアウト
| パス | 内容 | | パス | 内容 |
|------|------| |------|------|
| `include/libptouch.h` | 公開 API | | `include/libptouch.h` | 公開 API |
| `src/lib/libptouch_*.c` | ライブラリ本体core / usb / print / status / png | | `src/lib/libptouch_*.c` | ライブラリ本体core / usb / print / status / png / svg |
| `src/cli/main.c` | `ptouch-print` エントリ | | `src/cli/main.c` | `ptouch-print` エントリ |
| `samples/` | 試験用サンプル画像の置き場PNG 等) | | `samples/` | 試験用サンプル画像の置き場PNG/SVG 等) |
| `ruby/` | Ruby FFI gem`libptouch`)・コマンド `ptouch-print-png`PNG のみ)— `ruby/README.md` | | `ruby/` | Ruby FFI gem`libptouch`)・コマンド `ptouch-print-png`PNG のみ)— `ruby/README.md` |
| `reference/` | 仕様・参考資料(例: ラスター PDF | | `reference/` | 仕様・参考資料(例: ラスター PDF |
## ビルド ## ビルド
依存: **CMake 3.16+**、**libusb-1.0**、**libpng**(開発パッケージ例: `libusb-1.0-0-dev``libpng-dev`)。 依存: **CMake 3.16+**、**libusb-1.0**、**libpng**、**librsvg-2.0**(開発パッケージ例: `libusb-1.0-0-dev``libpng-dev``librsvg2-dev`)。
```bash ```bash
cmake -S . -B build cmake -S . -B build
@@ -38,7 +38,9 @@ cmake --build build
## CLI の使い方(雛形) ## CLI の使い方(雛形)
**PNG**(拡張子 `.png` または PNG シグネチャ)の場合は幅・高さは画像から取得します。任意で `-t`0255で二値化しきい値を指定できます。 **PNG**(拡張子 `.png` または PNG シグネチャ)の場合は幅・高さは画像から取得します。
**SVG**(拡張子 `.svg`の場合は現在装着中テープの印字可能ドット幅に合わせて自動拡大・縮小しますUSB 接続が必要)。
任意で `-t`0255で二値化しきい値を指定できます。
**1bit packed ラスター**(行優先、行あたり `ceil(width/8)` バイト × 行数)の場合は `-f` に加え `-w` / `-H` が必須です。 **1bit packed ラスター**(行優先、行あたり `ceil(width/8)` バイト × 行数)の場合は `-f` に加え `-w` / `-H` が必須です。
@@ -48,6 +50,9 @@ cmake --build build
# PNG — 検証のみUSB 不要) # PNG — 検証のみUSB 不要)
./build/ptouch-print -n -f label.png ./build/ptouch-print -n -f label.png
# SVG — 幅を現在テープにフィットさせて検証USB 必要)
./build/ptouch-print -n -f label.svg
# PNG — しきい値を指定 # PNG — しきい値を指定
./build/ptouch-print -n -f label.png -t 160 ./build/ptouch-print -n -f label.png -t 160
@@ -59,7 +64,12 @@ cmake --build build
# USB 接続時 # USB 接続時
./build/ptouch-print -f label.png ./build/ptouch-print -f label.png
./build/ptouch-print -f label.svg
./build/ptouch-print -f sample.raster -w 128 -H 64 ./build/ptouch-print -f sample.raster -w 128 -H 64
# PT-P750W / PT-P710BT`lsusb` の PID に合わせる)
./build/ptouch-print --status -p 0x2062
./build/ptouch-print -f label.png -p 0x20af
``` ```
`-n``--dry-run`)では読み込みと `libptouch_check_raster` まで実行します。 `-n``--dry-run`)では読み込みと `libptouch_check_raster` まで実行します。
@@ -70,8 +80,10 @@ cmake --build build
- `libptouch_create` / `libptouch_destroy` — コンテキスト - `libptouch_create` / `libptouch_destroy` — コンテキスト
- `libptouch_open_usb` / `libptouch_open_usb_vid_pid` / `libptouch_close` — USBlibusb・VID/PID - `libptouch_open_usb` / `libptouch_open_usb_vid_pid` / `libptouch_close` — USBlibusb・VID/PID
- `libptouch_get_status` / `libptouch_status_fprint` — ステータス情報リクエストESC i Sの応答 - `libptouch_get_status` / `libptouch_status_fprint` — ステータス情報リクエストESC i Sの応答
- `libptouch_get_current_media_info` — 現在テープ幅(mm)・印字可能幅(dots)・DPI・最小余白(mm)を取得
- `libptouch_check_raster` — ラスターバッファの検証のみ - `libptouch_check_raster` — ラスターバッファの検証のみ
- `libptouch_png_file_to_raster` / `libptouch_free_raster` — PNG を 1bit ラスターに変換libpng - `libptouch_png_file_to_raster` / `libptouch_free_raster` — PNG を 1bit ラスターに変換libpng
- `libptouch_svg_file_to_raster_fit_current_tape` — SVG を現在テープ幅に合わせて 1bit ラスターへ変換librsvg + cairo、USB 必須)
- `libptouch_print_raster` — ラスター印刷USB・PDF のコマンド列、内部で転置) - `libptouch_print_raster` — ラスター印刷USB・PDF のコマンド列、内部で転置)
- `libptouch_strerror` / `libptouch_last_error` — エラー情報 - `libptouch_strerror` / `libptouch_last_error` — エラー情報
@@ -79,12 +91,13 @@ cmake --build build
## PT-P900W / Linux でのメモ ## PT-P900W / Linux でのメモ
- 接続は **libusb-1.0** のみ。機種ごとに **VID/PID**`lsusb` 等)を調べ、`libptouch_open_usb_vid_pid` に渡すか、既定の PT-P900W なら `libptouch_open_usb` を使います。 - 接続は **libusb-1.0** のみ。機種ごとに **VID/PID**`lsusb` 等)を調べ、`libptouch_open_usb_vid_pid` に渡すか、既定の PT-P900W なら `libptouch_open_usb` を使います。PT-P750W は **04f9:2062**、PT-P710BT は **04f9:20af**`include/libptouch.h` の定数参照)。
- 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
既定の **04f9:2085**PT-P900W向けルールを `udev/99-ptouch-label-brother.rules` に置いています。 **04f9:2085**PT-P900W、**04f9:2062**PT-P750W、**04f9:20af**PT-P710BT向けルールを `udev/99-ptouch-label-brother.rules` に置いています。
```bash ```bash
sudo cp udev/99-ptouch-label-brother.rules /etc/udev/rules.d/ sudo cp udev/99-ptouch-label-brother.rules /etc/udev/rules.d/

View File

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

View File

@@ -1,6 +1,6 @@
# libptouchRuby gem # libptouchRuby gem
[ptouch_label](../) の **libptouch** を [ffi](https://github.com/ffi/ffi) 経由で使うための Gem です。 [ptouch_label](../) の **libptouch** を [ffi](https://github.com/ffi/ffi) 経由で使うための Gem です。C ライブラリと同様、**PT-P900W**(既定 USB PID、**PT-P750W** / **PT-P710BT** などは `open_usb_vid_pid` または CLI の `-p` で選びます。
## 前提 ## 前提
@@ -31,15 +31,21 @@ export LIBPTOUCH_LIB=/usr/local/lib/libptouch.so
`cmake --install` で共有ライブラリをインストールした場合は、通常は `libptouch` 名でローダが解決します。) `cmake --install` で共有ライブラリをインストールした場合は、通常は `libptouch` 名でローダが解決します。)
## コマンド `ptouch-print-png`PNG のみ ## コマンド `ptouch-print-png`PNG/SVG
C の `ptouch-print` と同様の流れですが、**PNG 入力のみ**`-w`/`-H` や 1bit ラスターは扱いません)。`gem install` 後は PATH に `ptouch-print-png` が入ります。 C の `ptouch-print` と同様の流れで、**PNG/SVG 入力**`-w`/`-H` や 1bit ラスターは扱いません)を扱います。`gem install` 後は PATH に `ptouch-print-png` が入ります。
SVG は現在装着テープの印字可能幅に合わせて自動拡大・縮小しますUSB 接続が必要)。
オプションは C 側に合わせ、**`-p` / `--pid`** で USB 製品 ID16 進可)を指定できます。省略時は PT-P900W`Libptouch::USB_PID_PTP900W` = `0x2085`)。例: PT-P750W `0x2062`、PT-P710BT `0x20af``libptouch.h` / `Libptouch` 定数と同じ)。
開発ツリーからそのまま試す例: 開発ツリーからそのまま試す例:
```bash ```bash
bundle exec ruby -I lib exe/ptouch-print-png --help bundle exec ruby -I lib exe/ptouch-print-png --help
bundle exec ruby -I lib exe/ptouch-print-png -n -f ../samples/your.png bundle exec ruby -I lib exe/ptouch-print-png -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-print-png --status -p 0x2062
bundle exec ruby -I lib exe/ptouch-print-png -f ../samples/your.png -p 0x20af
``` ```
## 使用例 ## 使用例
@@ -49,8 +55,11 @@ require "libptouch"
Libptouch::Context.new.tap do |ctx| Libptouch::Context.new.tap do |ctx|
ctx.open_usb ctx.open_usb
# PT-P750W など別 PID のとき:
# ctx.open_usb_vid_pid(Libptouch::USB_VID_BROTHER, Libptouch::USB_PID_PTP750W)
p ctx.status_bytes.bytesize # => 32 p ctx.status_bytes.bytesize # => 32
p ctx.status_hash[:tape_kind] # => {:code=>..., :label=>"ラミネートテープ"} など p ctx.status_hash[:tape_kind] # => {:code=>..., :label=>"ラミネートテープ"} など
p ctx.status_hash[:model] # => {:code=>..., :name=>"PT-P750W"} など(対応機種のみ名前あり)
p ctx.status_hash[:status_kind] # => 状態(ステータス種類) p ctx.status_hash[:status_kind] # => 状態(ステータス種類)
ensure ensure
ctx.dispose ctx.dispose
@@ -64,15 +73,28 @@ PNG からラスターへ:
```ruby ```ruby
ctx = Libptouch::Context.new ctx = Libptouch::Context.new
data, w, h = ctx.png_file_to_raster("/path/to/label.png") data, w, h = ctx.png_file_to_raster("/path/to/label.png")
ctx.open_usb ctx.open_usb_vid_pid(Libptouch::USB_VID_BROTHER, Libptouch::USB_PID_PTP750W)
ctx.print_raster(data, width_dots: w, height_dots: h, margin_mm: 0) ctx.print_raster(data, width_dots: w, height_dots: h, margin_mm: 0)
ctx.dispose ctx.dispose
``` ```
SVG から現在テープ幅に合わせてラスターへ:
```ruby
ctx = Libptouch::Context.new
ctx.open_usb
data, w, h = ctx.svg_file_to_raster_fit_current_tape("/path/to/label.svg")
ctx.print_raster(data, width_dots: w, height_dots: h, margin_mm: 0)
ctx.dispose
```
**解像度:** P750W / P710BT はラスター幅方向が **180 dpi**、P900W 系は **360 dpi**C の `libptouch_print` / 各機種ラスター PDF に合わせて画像を用意してください)。
## API の範囲 ## API の範囲
- 実行ファイル `ptouch-print-png` … PNG のみ`-f`, `-t`, `-n`, `-S`, `-V`, `-h`)。ステータスは JSON`status_bytes` を `parse_status` したもの、`raw_bytes` 除く) - 実行ファイル `ptouch-print-png` … PNG/SVG`-f`, `-t`, `-p`, `-n`, `-S`, `-V`, `-h`)。ステータスは JSON`status_bytes` を `parse_status` したもの、`raw_bytes` 除く)
- `Libptouch::Context` … `open_usb` / `open_usb_vid_pid` / `close` / `dispose` - `Libptouch::Context` … `open_usb` / `open_usb_vid_pid` / `close` / `dispose`
- `check_raster` / `print_raster` / `png_file_to_raster` / `status_bytes` / `status_hash` - `check_raster` / `print_raster` / `png_file_to_raster` / `svg_file_to_raster_fit_current_tape` / `status_bytes` / `status_hash` / `current_media_info`
- `current_media_info` には `print_dpi` / `feed_dpi` / `tape_width_mm` / `min_feed_mm` などを含む
- `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` を使ってください。

View File

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

View File

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

View File

@@ -7,7 +7,7 @@ require "libptouch"
module Libptouch module Libptouch
module Cli module Cli
# PNG のみを扱う ptouch-print 相当の CLI1bit ラスター経路なし)。 # PNG/SVG を扱う ptouch-print 相当の CLI1bit ラスター経路なし)。
module PngPrint module PngPrint
module_function module_function
@@ -20,15 +20,16 @@ module Libptouch
if opts[:status] if opts[:status]
warn_unused_file_options(opts) warn_unused_file_options(opts)
return run_status return run_status(opts)
end end
return usage_error("-f is required (or use --status)") if opts[:file].to_s.empty? return usage_error("-f is required (or use --status)") if opts[:file].to_s.empty?
path = opts[:file] path = opts[:file]
return usage_error("not a PNG file: #{path}") unless png_file?(path) kind = image_kind(path)
return usage_error("not a PNG/SVG file: #{path}") if kind.nil?
run_print(path, opts) run_print(path, opts, kind)
end end
def png_file?(path) def png_file?(path)
@@ -43,43 +44,98 @@ module Libptouch
false false
end 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) def parse(argv)
o = { 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, file: nil,
threshold: nil, threshold: nil,
usb_pid: nil,
usb_pid_invalid: false,
dry_run: false, dry_run: false,
status: false, status: false,
version: false, version: false,
help: false help: false
} }
parser = OptionParser.new do |p| end
def pid_option_description
p900 = Libptouch::USB_PID_PTP900W
p750 = Libptouch::USB_PID_PTP750W
p710 = Libptouch::USB_PID_PTP710BT
"USB 製品 ID16 進可)。既定 P900W 0x#{p900.to_s(16)}; " \
"P750W 0x#{p750.to_s(16)}; P710BT 0x#{p710.to_s(16)}"
end
def apply_usb_pid_option(opts_hash, pid_str)
opts_hash[:usb_pid] = Integer(pid_str, 0)
rescue ArgumentError
warn "invalid --pid: #{pid_str.inspect}"
opts_hash[:usb_pid_invalid] = true
end
def threshold_option_ok?(opts_hash)
return true if opts_hash[:threshold].nil? || (0..255).cover?(opts_hash[:threshold])
warn "-t must be 0..255"
false
end
def 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.banner = usage_banner
p.separator "" p.separator ""
p.on("-f", "--file PATH", "入力 PNG ファイル") { |v| o[:file] = v } p.on("-f", "--file PATH", "入力 PNG/SVG ファイル") { |v| opts_hash[:file] = v }
p.on("-t", "--threshold N", Integer, p.on("-t", "--threshold N", Integer,
"二値化しきい値 0255既定 #{Libptouch::PNG_DEFAULT_THRESHOLD}") do |v| "二値化しきい値 0255既定 #{Libptouch::PNG_DEFAULT_THRESHOLD}、PNG/SVG") do |v|
o[:threshold] = v opts_hash[:threshold] = v
end end
p.on("-n", "--dry-run", "読み込みと検証のみUSB なし)") { o[:dry_run] = true } p.on("-p", "--pid PID", pid_option_description) do |v|
p.on("-S", "--status", "USB プリンタのステータスを JSON で表示して終了") { o[:status] = true } apply_usb_pid_option(opts_hash, v)
p.on("-V", "--version", "バージョンを表示して終了") { o[:version] = true } end
p.on("-h", "--help", "このヘルプ") { o[:help] = true } 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
parser.parse!(argv)
unless o[:threshold].nil? || (0..255).cover?(o[:threshold])
warn "-t must be 0..255"
return nil
end
o
end end
def usage_banner def usage_banner
<<~BANNER <<~BANNER
Usage: ptouch-print-png [options] Usage: ptouch-print-png [options]
PNG -w/-H PNG/SVG -w/-H
SVG USB
--status -f --status -f
BANNER BANNER
end end
@@ -103,34 +159,40 @@ module Libptouch
end end
def parser_help_text def parser_help_text
o = { opts_hash = default_cli_opts
file: nil,
threshold: nil,
dry_run: false,
status: false,
version: false,
help: false
}
p = OptionParser.new do |parser| p = OptionParser.new do |parser|
parser.banner = "ptouch-print-png [options]" parser.banner = "ptouch-print-png [options]"
parser.on("-f", "--file PATH", "入力 PNG ファイル") { |v| o[:file] = v } parser.on("-f", "--file PATH", "入力 PNG/SVG ファイル") { |v| opts_hash[:file] = v }
parser.on("-t", "--threshold N", Integer, parser.on("-t", "--threshold N", Integer,
"二値化しきい値 0255既定 #{Libptouch::PNG_DEFAULT_THRESHOLD}") do |v| "二値化しきい値 0255既定 #{Libptouch::PNG_DEFAULT_THRESHOLD}、PNG/SVG") do |v|
o[:threshold] = v opts_hash[:threshold] = v
end end
parser.on("-n", "--dry-run", "読み込みと検証のみUSB なし)") { o[:dry_run] = true } parser.on("-p", "--pid PID", pid_option_description) do |v|
parser.on("-S", "--status", "USB プリンタのステータスを JSON で表示して終了") { o[:status] = true } apply_usb_pid_option(opts_hash, v)
parser.on("-V", "--version", "バージョンを表示して終了") { o[:version] = true } end
parser.on("-h", "--help", "このヘルプ") { o[:help] = true } 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 end
p.help p.help
end end
def run_status 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 ctx = nil
begin begin
ctx = Libptouch::Context.new ctx = Libptouch::Context.new
ctx.open_usb open_usb_for_opts(ctx, opts)
h = Libptouch.parse_status(ctx.status_bytes) h = Libptouch.parse_status(ctx.status_bytes)
h.delete(:raw_bytes) h.delete(:raw_bytes)
puts JSON.pretty_generate(h) puts JSON.pretty_generate(h)
@@ -143,12 +205,19 @@ module Libptouch
end end
end end
def run_print(path, opts) def run_print(path, opts, kind)
ctx = nil ctx = nil
begin begin
ctx = Libptouch::Context.new ctx = Libptouch::Context.new
threshold = opts[:threshold] threshold = opts[:threshold]
data, width, height = if threshold.nil? 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) ctx.png_file_to_raster(path)
else else
ctx.png_file_to_raster(path, threshold: threshold) ctx.png_file_to_raster(path, threshold: threshold)
@@ -157,11 +226,11 @@ module Libptouch
ctx.check_raster(data, width_dots: width, height_dots: height) ctx.check_raster(data, width_dots: width, height_dots: height)
if opts[:dry_run] if opts[:dry_run]
puts "dry-run OK: #{data.bytesize} bytes, #{width}x#{height} dots" puts "dry-run OK: #{data.bytesize} bytes, src #{width}x#{height} dots (print lengthxwidth #{width}x#{height})"
return 0 return 0
end end
ctx.open_usb open_usb_for_opts(ctx, opts) if kind == :png
ctx.print_raster(data, width_dots: width, height_dots: height) ctx.print_raster(data, width_dots: width, height_dots: height)
0 0
rescue Libptouch::Error => e rescue Libptouch::Error => e

View File

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

View File

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

View File

@@ -13,6 +13,7 @@ Gem::Specification.new do |spec|
spec.summary = "FFI bindings for libptouch (Brother P-touch USB raster printing)" spec.summary = "FFI bindings for libptouch (Brother P-touch USB raster printing)"
spec.description = [ spec.description = [
"Ruby wrapper around the ptouch_label C library libptouch.", "Ruby wrapper around the ptouch_label C library libptouch.",
"Supports PT-P900W, PT-P750W, PT-P710BT and related open_usb_vid_pid / --pid.",
"Requires libptouch shared library (libusb, libpng)." "Requires libptouch shared library (libusb, libpng)."
].join(" ") ].join(" ")
spec.license = "MIT" spec.license = "MIT"

40
samples/sample.svg Normal file
View File

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

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -21,13 +21,15 @@ static void usage(const char *argv0)
" -f, --file PATH 入力ファイル\n" " -f, --file PATH 入力ファイル\n"
" -w, --width DOTS 1bit ラスター時: 幅(ドット)\n" " -w, --width DOTS 1bit ラスター時: 幅(ドット)\n"
" -H, --height DOTS 1bit ラスター時: 高さ(ドット)\n" " -H, --height DOTS 1bit ラスター時: 高さ(ドット)\n"
" -t, --threshold N PNG 二値化しきい値 0255既定 %u、PNG のみ\n" " -t, --threshold N 二値化しきい値 0255既定 %u、PNG/SVG\n"
" -n, --dry-run 読み込みと check_raster のみUSB なし)\n" " -n, --dry-run 読み込みと check_raster のみUSB なし)\n"
" -p, --pid HEX USB 製品 ID既定: P900W の 0x2085。例: P750W 0x2062、P710BT 0x20af\n"
" -S, --status USB 接続プリンタのステータス(テープ種・幅・色等)を表示して終了\n" " -S, --status USB 接続プリンタのステータス(テープ種・幅・色等)を表示して終了\n"
" -V, --version バージョンを表示して終了\n" " -V, --version バージョンを表示して終了\n"
" -h, --help このヘルプ\n" " -h, --help このヘルプ\n"
"\n" "\n"
"PNG の場合は幅・高さはファイルから取得(-w/-H 不要)。\n" "PNG は幅・高さを画像から取得(-w/-H 不要)。\n"
"SVG は現在テープの印字可能幅に合わせて自動拡大・縮小USB 必須)。\n"
"1bit packed ラスター(行優先)の場合は -f -w -H が必須。\n" "1bit packed ラスター(行優先)の場合は -f -w -H が必須。\n"
"--status のときは -f は不要(他オプションは無視されます)。\n", "--status のときは -f は不要(他オプションは無視されます)。\n",
argv0, (unsigned)LIBPTOUCH_PNG_DEFAULT_THRESHOLD); argv0, (unsigned)LIBPTOUCH_PNG_DEFAULT_THRESHOLD);
@@ -53,6 +55,14 @@ static int input_is_png(const char *path)
return memcmp(sig, png_magic, sizeof(png_magic)) == 0; return memcmp(sig, png_magic, sizeof(png_magic)) == 0;
} }
static int input_is_svg(const char *path)
{
const char *dot = strrchr(path, '.');
if (dot && strcasecmp(dot, ".svg") == 0)
return 1;
return 0;
}
static int read_file(const char *path, uint8_t **out, size_t *out_len) static int read_file(const char *path, uint8_t **out, size_t *out_len)
{ {
FILE *fp = fopen(path, "rb"); FILE *fp = fopen(path, "rb");
@@ -95,6 +105,7 @@ int main(int argc, char **argv)
const char *file = NULL; const char *file = NULL;
unsigned width = 0, height = 0; unsigned width = 0, height = 0;
unsigned threshold = LIBPTOUCH_PNG_DEFAULT_THRESHOLD; unsigned threshold = LIBPTOUCH_PNG_DEFAULT_THRESHOLD;
unsigned usb_pid_arg = 0;
int dry_run = 0; int dry_run = 0;
int has_threshold = 0; int has_threshold = 0;
int want_status = 0; int want_status = 0;
@@ -104,6 +115,7 @@ int main(int argc, char **argv)
{ "file", required_argument, NULL, 'f' }, { "file", required_argument, NULL, 'f' },
{ "threshold", required_argument, NULL, 't' }, { "threshold", required_argument, NULL, 't' },
{ "dry-run", no_argument, NULL, 'n' }, { "dry-run", no_argument, NULL, 'n' },
{ "pid", required_argument, NULL, 'p' },
{ "status", no_argument, NULL, 'S' }, { "status", no_argument, NULL, 'S' },
{ "version", no_argument, NULL, 'V' }, { "version", no_argument, NULL, 'V' },
{ "help", no_argument, NULL, 'h' }, { "help", no_argument, NULL, 'h' },
@@ -111,7 +123,7 @@ int main(int argc, char **argv)
}; };
int c; int c;
while ((c = getopt_long(argc, argv, "w:H:f:t:nhSV", longopts, NULL)) != while ((c = getopt_long(argc, argv, "w:H:f:t:p:nhSV", longopts, NULL)) !=
-1) { -1) {
switch (c) { switch (c) {
case 'w': case 'w':
@@ -134,6 +146,13 @@ int main(int argc, char **argv)
case 'n': case 'n':
dry_run = 1; dry_run = 1;
break; break;
case 'p':
usb_pid_arg = (unsigned)strtoul(optarg, NULL, 0);
if (usb_pid_arg == 0u || usb_pid_arg > 0xFFFFu) {
fprintf(stderr, "-p/--pid must be 1..0xFFFF\n");
return 2;
}
break;
case 'S': case 'S':
want_status = 1; want_status = 1;
break; break;
@@ -159,7 +178,12 @@ int main(int argc, char **argv)
fprintf(stderr, "libptouch_create failed\n"); fprintf(stderr, "libptouch_create failed\n");
return 1; return 1;
} }
libptouch_err_t se = libptouch_open_usb(sctx); libptouch_err_t se =
usb_pid_arg != 0
? libptouch_open_usb_vid_pid(
sctx, LIBPTOUCH_USB_VID_BROTHER,
(uint16_t)usb_pid_arg)
: libptouch_open_usb(sctx);
if (se != LIBPTOUCH_OK) { if (se != LIBPTOUCH_OK) {
fprintf(stderr, "open_usb: %s\n", libptouch_strerror(sctx)); fprintf(stderr, "open_usb: %s\n", libptouch_strerror(sctx));
libptouch_destroy(sctx); libptouch_destroy(sctx);
@@ -186,20 +210,21 @@ int main(int argc, char **argv)
} }
int png = input_is_png(file); int png = input_is_png(file);
if (png) { int svg = input_is_svg(file);
if (png || svg) {
if (width != 0 || height != 0) if (width != 0 || height != 0)
fprintf(stderr, fprintf(stderr,
"warning: -w/-H ignored for PNG (using image size)\n"); "warning: -w/-H ignored for image inputs\n");
} else { } else {
if (width == 0 || height == 0) { if (width == 0 || height == 0) {
fprintf(stderr, fprintf(stderr,
"1bit raster requires -w and -H (or use a PNG file)\n"); "1bit raster requires -w and -H (or use a PNG/SVG file)\n");
usage(argv[0]); usage(argv[0]);
return 2; return 2;
} }
if (has_threshold) if (has_threshold)
fprintf(stderr, fprintf(stderr,
"warning: -t applies to PNG only (ignored)\n"); "warning: -t applies to PNG/SVG only (ignored)\n");
} }
libptouch_ctx *ctx = libptouch_create(); libptouch_ctx *ctx = libptouch_create();
@@ -212,6 +237,7 @@ int main(int argc, char **argv)
size_t data_len = 0; size_t data_len = 0;
libptouch_raster_params_t params = { 0, 0, 0 }; libptouch_raster_params_t params = { 0, 0, 0 };
libptouch_err_t e; libptouch_err_t e;
int usb_opened = 0;
if (png) { if (png) {
libptouch_png_options_t opt = { .threshold = (uint8_t)threshold }; libptouch_png_options_t opt = { .threshold = (uint8_t)threshold };
@@ -224,6 +250,28 @@ int main(int argc, char **argv)
libptouch_destroy(ctx); libptouch_destroy(ctx);
return 1; return 1;
} }
} else if (svg) {
e = usb_pid_arg != 0
? libptouch_open_usb_vid_pid(ctx, LIBPTOUCH_USB_VID_BROTHER,
(uint16_t)usb_pid_arg)
: libptouch_open_usb(ctx);
if (e != LIBPTOUCH_OK) {
fprintf(stderr, "open_usb: %s\n", libptouch_strerror(ctx));
libptouch_destroy(ctx);
return 1;
}
usb_opened = 1;
libptouch_svg_options_t opt = { .threshold = (uint8_t)threshold };
e = libptouch_svg_file_to_raster_fit_current_tape(
ctx, file, has_threshold ? &opt : NULL, &data, &data_len,
&params);
if (e != LIBPTOUCH_OK) {
fprintf(stderr, "svg_file_to_raster_fit_current_tape: %s\n",
libptouch_strerror(ctx));
libptouch_close(ctx);
libptouch_destroy(ctx);
return 1;
}
} else { } else {
if (read_file(file, &data, &data_len) != 0) { if (read_file(file, &data, &data_len) != 0) {
libptouch_destroy(ctx); libptouch_destroy(ctx);
@@ -239,7 +287,7 @@ int main(int argc, char **argv)
fprintf(stderr, "check_raster: %s\n", fprintf(stderr, "check_raster: %s\n",
libptouch_strerror(ctx)); libptouch_strerror(ctx));
libptouch_destroy(ctx); libptouch_destroy(ctx);
if (png) if (png || svg)
libptouch_free_raster(data); libptouch_free_raster(data);
else else
free(data); free(data);
@@ -247,26 +295,36 @@ int main(int argc, char **argv)
} }
if (dry_run) { if (dry_run) {
printf("dry-run OK: %zu bytes, %ux%u dots\n", data_len, printf("dry-run OK: %zu bytes, src %ux%u dots (print lengthxwidth %ux%u)\n",
data_len,
(unsigned)params.width_dots,
(unsigned)params.height_dots,
(unsigned)params.width_dots, (unsigned)params.width_dots,
(unsigned)params.height_dots); (unsigned)params.height_dots);
if (usb_opened)
libptouch_close(ctx);
libptouch_destroy(ctx); libptouch_destroy(ctx);
if (png) if (png || svg)
libptouch_free_raster(data); libptouch_free_raster(data);
else else
free(data); free(data);
return 0; return 0;
} }
e = libptouch_open_usb(ctx); if (!usb_opened) {
if (e != LIBPTOUCH_OK) { e = usb_pid_arg != 0
fprintf(stderr, "open_usb: %s\n", libptouch_strerror(ctx)); ? libptouch_open_usb_vid_pid(ctx, LIBPTOUCH_USB_VID_BROTHER,
libptouch_destroy(ctx); (uint16_t)usb_pid_arg)
if (png) : libptouch_open_usb(ctx);
libptouch_free_raster(data); if (e != LIBPTOUCH_OK) {
else fprintf(stderr, "open_usb: %s\n", libptouch_strerror(ctx));
free(data); libptouch_destroy(ctx);
return 1; if (png || svg)
libptouch_free_raster(data);
else
free(data);
return 1;
}
} }
e = libptouch_print_raster(ctx, data, data_len, &params); e = libptouch_print_raster(ctx, data, data_len, &params);
@@ -275,7 +333,7 @@ int main(int argc, char **argv)
libptouch_strerror(ctx)); libptouch_strerror(ctx));
libptouch_close(ctx); libptouch_close(ctx);
libptouch_destroy(ctx); libptouch_destroy(ctx);
if (png) if (png || svg)
libptouch_free_raster(data); libptouch_free_raster(data);
else else
free(data); free(data);
@@ -284,7 +342,7 @@ int main(int argc, char **argv)
libptouch_close(ctx); libptouch_close(ctx);
libptouch_destroy(ctx); libptouch_destroy(ctx);
if (png) if (png || svg)
libptouch_free_raster(data); libptouch_free_raster(data);
else else
free(data); free(data);

View File

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

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

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

View File

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

View File

@@ -0,0 +1,100 @@
/*
* libptouch — current tape/media information helper
*
* Author: knb
* Email: knb@artif.org
*/
#include "libptouch_internal.h"
#include "libptouch_layout.h"
#include <string.h>
static double tape_width_mm_from_code(uint8_t media_w)
{
switch (media_w) {
case 0x04:
return 3.5;
case 0x06:
return 6.0;
case 0x09:
return 9.0;
case 0x0C:
return 12.0;
case 0x12:
return 18.0;
case 0x18:
return 24.0;
case 0x24:
return 36.0;
case 0x15:
return 21.0; /* FLe width code */
default:
return 0.0;
}
}
static double print_dpi_from_profile(const ptouch_printer_profile_t *prof)
{
if (!prof)
return 0.0;
if (prof->head_width_dots <= 128u)
return 180.0;
return 360.0;
}
libptouch_err_t libptouch_get_current_media_info(libptouch_ctx *ctx,
libptouch_media_info_t *out_info)
{
if (!ctx || !out_info) {
if (ctx)
ptouch_set_error(ctx, LIBPTOUCH_ERR_ARG, "null argument");
return LIBPTOUCH_ERR_ARG;
}
memset(out_info, 0, sizeof(*out_info));
uint8_t st[LIBPTOUCH_STATUS_LENGTH];
libptouch_err_t v = libptouch_get_status(ctx, st);
if (v != LIBPTOUCH_OK)
return v;
const ptouch_printer_profile_t *prof =
ptouch_layout_resolve_profile(ctx->usb_pid, st[4]);
if (!prof) {
ptouch_set_error(ctx, LIBPTOUCH_ERR_UNSUPPORTED,
"no layout profile for this printer");
return LIBPTOUCH_ERR_UNSUPPORTED;
}
uint16_t left_dots = 0, print_dots = 0, right_dots = 0;
v = ptouch_layout_from_status(prof, st[11], st[10], &left_dots,
&print_dots, &right_dots);
if (v != LIBPTOUCH_OK) {
ptouch_set_error(ctx, LIBPTOUCH_ERR_UNSUPPORTED,
"tape width/layout not supported for this media");
return LIBPTOUCH_ERR_UNSUPPORTED;
}
double tape_mm = tape_width_mm_from_code(st[10]);
if (tape_mm <= 0.0) {
ptouch_set_error(ctx, LIBPTOUCH_ERR_UNSUPPORTED,
"unknown tape width code");
return LIBPTOUCH_ERR_UNSUPPORTED;
}
out_info->media_width_code = st[10];
out_info->media_kind_code = st[11];
out_info->print_dpi = print_dpi_from_profile(prof);
out_info->feed_dpi = prof->margin_feed_dpi;
out_info->tape_width_mm = tape_mm;
out_info->printable_dots = print_dots;
out_info->left_margin_dots = left_dots;
out_info->right_margin_dots = right_dots;
out_info->min_feed_dots = 14u;
out_info->min_feed_mm =
((double)out_info->min_feed_dots * 25.4) / prof->margin_feed_dpi;
ptouch_set_error(ctx, LIBPTOUCH_OK, "");
return LIBPTOUCH_OK;
}

View File

@@ -6,96 +6,18 @@
*/ */
#include "libptouch_internal.h" #include "libptouch_internal.h"
#include "libptouch_layout.h"
#include <libusb.h> #include <libusb.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
/* cv_ptp900_jpn_raster_102.pdf: テープ幅から印刷可能ドット */ static void pack_line(uint8_t *line, size_t line_bytes, unsigned head_dots,
static libptouch_err_t layout_from_status(uint8_t media_kind, uint8_t media_wbyte, const uint8_t *row, uint32_t width_dots, uint16_t left_dots,
uint16_t *left, uint16_t *print_dots, uint16_t print_dots)
uint16_t *right)
{ {
if (media_kind == 0x11u || media_kind == 0x17u) { memset(line, 0, line_bytes);
switch (media_wbyte) {
case 0x06:
*left = 244;
*print_dots = 56;
*right = 260;
return LIBPTOUCH_OK;
case 0x09:
*left = 224;
*print_dots = 96;
*right = 240;
return LIBPTOUCH_OK;
case 0x0C:
*left = 206;
*print_dots = 132;
*right = 222;
return LIBPTOUCH_OK;
case 0x12:
*left = 166;
*print_dots = 212;
*right = 182;
return LIBPTOUCH_OK;
case 0x18:
*left = 144;
*print_dots = 256;
*right = 160;
return LIBPTOUCH_OK;
default:
break;
}
}
switch (media_wbyte) {
case 0x04:
*left = 248;
*print_dots = 48;
*right = 264;
return LIBPTOUCH_OK;
case 0x06:
*left = 240;
*print_dots = 64;
*right = 256;
return LIBPTOUCH_OK;
case 0x09:
*left = 219;
*print_dots = 106;
*right = 235;
return LIBPTOUCH_OK;
case 0x0C:
*left = 197;
*print_dots = 150;
*right = 213;
return LIBPTOUCH_OK;
case 0x12:
*left = 155;
*print_dots = 234;
*right = 171;
return LIBPTOUCH_OK;
case 0x18:
*left = 112;
*print_dots = 320;
*right = 128;
return LIBPTOUCH_OK;
case 0x24:
*left = 45;
*print_dots = 454;
*right = 61;
return LIBPTOUCH_OK;
default:
break;
}
(void)media_kind;
return LIBPTOUCH_ERR_UNSUPPORTED;
}
/* cv_ptp900_jpn_raster_102.pdf: 2.3.5 ラスターライン(全 560 ドット = 70 バイト) */
static void pack_line_560(uint8_t line[70], const uint8_t *row, uint32_t width_dots,
uint16_t left_dots, uint16_t print_dots)
{
memset(line, 0, 70u);
if (width_dots > (uint32_t)print_dots) if (width_dots > (uint32_t)print_dots)
return; return;
uint32_t start = (uint32_t)left_dots + uint32_t start = (uint32_t)left_dots +
@@ -106,7 +28,7 @@ static void pack_line_560(uint8_t line[70], const uint8_t *row, uint32_t width_d
if (((row[ubyte] >> ubit) & 1u) == 0) if (((row[ubyte] >> ubit) & 1u) == 0)
continue; continue;
uint32_t dot = start + x; uint32_t dot = start + x;
if (dot >= 560u) if (dot >= (uint32_t)head_dots)
break; break;
uint32_t b = dot / 8u; uint32_t b = dot / 8u;
uint32_t bi = 7u - (dot % 8u); uint32_t bi = 7u - (dot % 8u);
@@ -158,11 +80,19 @@ libptouch_err_t libptouch_print_raster(libptouch_ctx *ctx,
if (v != LIBPTOUCH_OK) if (v != LIBPTOUCH_OK)
return v; return v;
const ptouch_printer_profile_t *prof =
ptouch_layout_resolve_profile(ctx->usb_pid, st[4]);
if (!prof) {
ptouch_set_error(ctx, LIBPTOUCH_ERR_UNSUPPORTED,
"no layout profile for this printer");
return LIBPTOUCH_ERR_UNSUPPORTED;
}
uint8_t media_kind = st[11]; uint8_t media_kind = st[11];
uint8_t media_w = st[10]; uint8_t media_w = st[10];
uint16_t left_dots, print_dots, right_dots; uint16_t left_dots, print_dots, right_dots;
v = layout_from_status(media_kind, media_w, &left_dots, &print_dots, v = ptouch_layout_from_status(prof, media_kind, media_w, &left_dots,
&right_dots); &print_dots, &right_dots);
if (v != LIBPTOUCH_OK) { if (v != LIBPTOUCH_OK) {
ptouch_set_error(ctx, LIBPTOUCH_ERR_UNSUPPORTED, ptouch_set_error(ctx, LIBPTOUCH_ERR_UNSUPPORTED,
"tape width/layout not supported for this media " "tape width/layout not supported for this media "
@@ -171,6 +101,10 @@ libptouch_err_t libptouch_print_raster(libptouch_ctx *ctx,
} }
(void)right_dots; (void)right_dots;
unsigned head_dots = prof->head_width_dots;
size_t line_payload = (size_t)((head_dots + 7u) / 8u);
size_t gf_packet = 3u + line_payload;
uint32_t wd = params->width_dots; uint32_t wd = params->width_dots;
uint32_t ht = params->height_dots; uint32_t ht = params->height_dots;
uint8_t *transposed = transpose_raster_alloc(data, wd, ht, &wd, &ht); uint8_t *transposed = transpose_raster_alloc(data, wd, ht, &wd, &ht);
@@ -191,15 +125,17 @@ libptouch_err_t libptouch_print_raster(libptouch_ctx *ctx,
return LIBPTOUCH_ERR_ARG; return LIBPTOUCH_ERR_ARG;
} }
double margin_dpi = prof->margin_feed_dpi;
unsigned margin_max = prof->margin_feed_max_dots;
unsigned margin_dots = 14u; unsigned margin_dots = 14u;
if (params->margin_mm > 0) { if (params->margin_mm > 0) {
margin_dots = (unsigned)((double)params->margin_mm * 360.0 / margin_dots = (unsigned)((double)params->margin_mm * margin_dpi /
25.4 + 25.4 +
0.5); 0.5);
if (margin_dots < 14u) if (margin_dots < 14u)
margin_dots = 14u; margin_dots = 14u;
if (margin_dots > 1800u) if (margin_dots > margin_max)
margin_dots = 1800u; margin_dots = margin_max;
} }
uint32_t lines = ht; uint32_t lines = ht;
@@ -263,19 +199,29 @@ libptouch_err_t libptouch_print_raster(libptouch_ctx *ctx,
} }
size_t row_b = ((size_t)wd + 7u) / 8u; size_t row_b = ((size_t)wd + 7u) / 8u;
uint8_t gbuf[73];
static const uint8_t g_hdr[] = { 0x47, 0x46, 0x00 }; static const uint8_t g_hdr[] = { 0x47, 0x46, 0x00 };
uint8_t *gbuf = (uint8_t *)malloc(gf_packet);
if (!gbuf) {
ptouch_set_error(ctx, LIBPTOUCH_ERR_NOMEM,
"raster line buffer");
free(transposed);
return LIBPTOUCH_ERR_NOMEM;
}
for (uint32_t y = 0; y < lines; y++) { for (uint32_t y = 0; y < lines; y++) {
const uint8_t *row = src + (size_t)y * row_b; const uint8_t *row = src + (size_t)y * row_b;
pack_line_560(gbuf + 3, row, wd, left_dots, print_dots); pack_line(gbuf + 3, line_payload, head_dots, row, wd, left_dots,
print_dots);
memcpy(gbuf, g_hdr, sizeof(g_hdr)); memcpy(gbuf, g_hdr, sizeof(g_hdr));
v = ptouch_bulk_send_job(ctx, gbuf, sizeof(gbuf), "raster line"); v = ptouch_bulk_send_job(ctx, gbuf, gf_packet, "raster line");
if (v != LIBPTOUCH_OK) { if (v != LIBPTOUCH_OK) {
free(gbuf);
free(transposed); free(transposed);
return v; return v;
} }
} }
free(gbuf);
static const uint8_t print_end[] = { 0x1A }; static const uint8_t print_end[] = { 0x1A };
v = ptouch_bulk_send_job(ctx, print_end, sizeof(print_end), "print end"); v = ptouch_bulk_send_job(ctx, print_end, sizeof(print_end), "print end");

View File

@@ -75,6 +75,12 @@ static void fprint_model(FILE *fp, uint8_t code)
case 0x78: case 0x78:
name = "PT-P910BT"; name = "PT-P910BT";
break; break;
case 0x68:
name = "PT-P750W";
break;
case 0x76:
name = "PT-P710BT";
break;
default: default:
break; break;
} }

211
src/lib/libptouch_svg.c Normal file
View File

@@ -0,0 +1,211 @@
/*
* libptouch — SVG -> 1bit packed raster (librsvg + cairo)
*
* Author: knb
* Email: knb@artif.org
*/
#include "libptouch_internal.h"
#include "libptouch_layout.h"
#ifdef LIBPTOUCH_HAS_RSVG
#include <cairo.h>
#include <librsvg/rsvg.h>
#endif
#include <math.h>
#include <stdint.h>
#include <stdlib.h>
libptouch_err_t libptouch_svg_file_to_raster_fit_current_tape(
libptouch_ctx *ctx, const char *path,
const libptouch_svg_options_t *options, uint8_t **out_raster,
size_t *out_raster_bytes, libptouch_raster_params_t *out_params)
{
#ifndef LIBPTOUCH_HAS_RSVG
(void)path;
(void)options;
(void)out_raster;
(void)out_raster_bytes;
(void)out_params;
if (ctx) {
ptouch_set_error(ctx, LIBPTOUCH_ERR_UNSUPPORTED,
"SVG support is not built in (missing librsvg-2.0)");
}
return LIBPTOUCH_ERR_UNSUPPORTED;
#else
if (!ctx || !path || !out_raster || !out_raster_bytes || !out_params) {
if (ctx)
ptouch_set_error(ctx, LIBPTOUCH_ERR_ARG, "null argument");
return LIBPTOUCH_ERR_ARG;
}
if (!ctx->usb_open || !ctx->bulk_out_ep) {
ptouch_set_error(ctx, LIBPTOUCH_ERR_IO,
"not connected (open USB before SVG conversion)");
return LIBPTOUCH_ERR_IO;
}
uint8_t thr = LIBPTOUCH_PNG_DEFAULT_THRESHOLD;
if (options)
thr = options->threshold;
*out_raster = NULL;
*out_raster_bytes = 0;
out_params->width_dots = 0;
out_params->height_dots = 0;
out_params->margin_mm = 0;
uint8_t st[LIBPTOUCH_STATUS_LENGTH];
libptouch_err_t v = libptouch_get_status(ctx, st);
if (v != LIBPTOUCH_OK)
return v;
const ptouch_printer_profile_t *prof =
ptouch_layout_resolve_profile(ctx->usb_pid, st[4]);
if (!prof) {
ptouch_set_error(ctx, LIBPTOUCH_ERR_UNSUPPORTED,
"no layout profile for this printer");
return LIBPTOUCH_ERR_UNSUPPORTED;
}
uint16_t left_dots = 0, print_dots = 0, right_dots = 0;
v = ptouch_layout_from_status(prof, st[11], st[10], &left_dots,
&print_dots, &right_dots);
if (v != LIBPTOUCH_OK) {
ptouch_set_error(ctx, LIBPTOUCH_ERR_UNSUPPORTED,
"tape width/layout not supported for this media");
return LIBPTOUCH_ERR_UNSUPPORTED;
}
(void)left_dots;
(void)right_dots;
GError *gerr = NULL;
RsvgHandle *handle = rsvg_handle_new_from_file(path, &gerr);
if (!handle) {
const char *msg = (gerr && gerr->message) ? gerr->message :
"failed to load SVG";
ptouch_set_error(ctx, LIBPTOUCH_ERR_IMAGE, msg);
if (gerr)
g_error_free(gerr);
return LIBPTOUCH_ERR_IMAGE;
}
double svg_w = 0.0;
double svg_h = 0.0;
if (!rsvg_handle_get_intrinsic_size_in_pixels(handle, &svg_w, &svg_h) ||
svg_w <= 0.0 || svg_h <= 0.0) {
g_object_unref(handle);
ptouch_set_error(ctx, LIBPTOUCH_ERR_IMAGE,
"invalid SVG dimensions");
return LIBPTOUCH_ERR_IMAGE;
}
/*
* libptouch_print_raster() transposes the incoming raster before packing
* lines, so printable tape width maps to the source raster height.
*/
double scale = (double)print_dots / svg_h;
if (scale <= 0.0 || !isfinite(scale)) {
g_object_unref(handle);
ptouch_set_error(ctx, LIBPTOUCH_ERR_IMAGE, "invalid SVG scale");
return LIBPTOUCH_ERR_IMAGE;
}
uint32_t out_h = (uint32_t)print_dots;
uint32_t out_w = (uint32_t)ceil(svg_w * scale);
if (out_h == 0)
out_h = 1;
if (out_h > 100000u) {
g_object_unref(handle);
ptouch_set_error(ctx, LIBPTOUCH_ERR_IMAGE, "scaled SVG too tall");
return LIBPTOUCH_ERR_IMAGE;
}
cairo_surface_t *surface = cairo_image_surface_create(
CAIRO_FORMAT_ARGB32, (int)out_w, (int)out_h);
if (cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) {
cairo_surface_destroy(surface);
g_object_unref(handle);
ptouch_set_error(ctx, LIBPTOUCH_ERR_NOMEM,
"cairo_image_surface_create failed");
return LIBPTOUCH_ERR_NOMEM;
}
cairo_t *cr = cairo_create(surface);
if (cairo_status(cr) != CAIRO_STATUS_SUCCESS) {
cairo_destroy(cr);
cairo_surface_destroy(surface);
g_object_unref(handle);
ptouch_set_error(ctx, LIBPTOUCH_ERR_NOMEM, "cairo_create failed");
return LIBPTOUCH_ERR_NOMEM;
}
cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
cairo_paint(cr);
RsvgRectangle viewport = { 0.0, 0.0, (double)out_w, (double)out_h };
GError *render_err = NULL;
if (!rsvg_handle_render_document(handle, cr, &viewport, &render_err)) {
cairo_destroy(cr);
cairo_surface_destroy(surface);
g_object_unref(handle);
const char *msg = (render_err && render_err->message) ?
render_err->message :
"SVG render failed";
ptouch_set_error(ctx, LIBPTOUCH_ERR_IMAGE, msg);
if (render_err)
g_error_free(render_err);
return LIBPTOUCH_ERR_IMAGE;
}
cairo_destroy(cr);
g_object_unref(handle);
cairo_surface_flush(surface);
int stride = cairo_image_surface_get_stride(surface);
const uint8_t *pix = cairo_image_surface_get_data(surface);
size_t row_bytes = ((size_t)out_w + 7u) / 8u;
if (out_h > SIZE_MAX / row_bytes) {
cairo_surface_destroy(surface);
ptouch_set_error(ctx, LIBPTOUCH_ERR_IMAGE, "raster size overflow");
return LIBPTOUCH_ERR_IMAGE;
}
size_t total = row_bytes * (size_t)out_h;
uint8_t *out = (uint8_t *)calloc(1, total);
if (!out) {
cairo_surface_destroy(surface);
ptouch_set_error(ctx, LIBPTOUCH_ERR_NOMEM, "calloc raster failed");
return LIBPTOUCH_ERR_NOMEM;
}
for (uint32_t y = 0; y < out_h; y++) {
const uint32_t *src_row =
(const uint32_t *)(pix + (size_t)y * (size_t)stride);
uint8_t *dst_row = out + (size_t)y * row_bytes;
for (uint32_t x = 0; x < out_w; x++) {
uint32_t p = src_row[x];
unsigned a = (p >> 24) & 0xFFu;
if (a < 128u)
continue;
unsigned r = (p >> 16) & 0xFFu;
unsigned g = (p >> 8) & 0xFFu;
unsigned b = p & 0xFFu;
unsigned yv = (77u * r + 150u * g + 29u * b) >> 8;
if (yv >= (unsigned)thr)
continue;
size_t byte = (size_t)x / 8u;
size_t bit = (size_t)x % 8u;
dst_row[byte] |= (uint8_t)(1u << (7u - bit));
}
}
cairo_surface_destroy(surface);
out_params->width_dots = out_w;
out_params->height_dots = out_h;
out_params->margin_mm = 0;
*out_raster = out;
*out_raster_bytes = total;
ptouch_set_error(ctx, LIBPTOUCH_OK, "");
return LIBPTOUCH_OK;
#endif
}

View File

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

View File

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