diff --git a/CMakeLists.txt b/CMakeLists.txt index a971ca4..5b3029c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,6 +24,7 @@ configure_file( set(LIBPTOUCH_SOURCES src/lib/libptouch_core.c src/lib/libptouch_usb.c + src/lib/libptouch_layout.c src/lib/libptouch_print.c src/lib/libptouch_status.c src/lib/libptouch_png.c diff --git a/README.md b/README.md index f869569..3d94eb2 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ 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`。 ## レイアウト @@ -60,6 +60,10 @@ cmake --build build # USB 接続時 ./build/ptouch-print -f label.png ./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` まで実行します。 @@ -79,12 +83,13 @@ cmake --build build ## 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` に実装してください。 ### 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 sudo cp udev/99-ptouch-label-brother.rules /etc/udev/rules.d/ diff --git a/include/libptouch.h b/include/libptouch.h index 2929d4f..3677942 100644 --- a/include/libptouch.h +++ b/include/libptouch.h @@ -8,7 +8,8 @@ /** * libptouch — Brother P-touch ラスター印刷 (USB) 用 C API * - * 対象例: PT-P900W(ラスターコマンド)。実装は src/lib/libptouch_*.c を参照。 + * 対象例: PT-P900W(560 ドットヘッド)、PT-P750W / PT-P710BT(128 ドットヘッド)。 + * 実装は src/lib/libptouch_*.c を参照。 */ #ifndef LIBPTOUCH_H @@ -41,6 +42,9 @@ typedef enum { /** lsusb 例: PT-P900W — Brother Industries, Ltd (04f9:2085) */ #define LIBPTOUCH_USB_VID_BROTHER 0x04f9u #define LIBPTOUCH_USB_PID_PTP900W 0x2085u +/** PT-P750W / PT-P710BT(cv_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); void libptouch_destroy(libptouch_ctx *ctx); @@ -77,9 +81,10 @@ libptouch_err_t libptouch_check_raster(libptouch_ctx *ctx, const libptouch_raster_params_t *params); /** - * 1 ビット packed ラスターを USB で印刷(cv_ptp900_jpn_raster_102.pdf 準拠)。 + * 1 ビット packed ラスターを USB で印刷(各機種のラスター PDF 準拠)。 * 印字前にステータスでテープ幅を読み、印刷可能ドット内に画像を中央配置する。 - * width_dots は装着テープの印刷可能幅以下であること。360dpi 相当のドット列を想定。 + * width_dots は装着テープの印刷可能幅以下であること。PT-P900 系は幅 360dpi、 + * PT-P750W / PT-P710BT は幅 180dpi(cv_ptp750w_710bt_jpn_raster_102.pdf)のドット列を想定。 * @param margin_mm 余白(フィード)量。0 のとき PDF の最小 1mm(14 ドット)相当を送る。 * 印刷時は内部でドット列を転置する(テープ幅方向とバッファの縦横の対応)。 * @param data 1 行あたり width_dots ビットを ceil(width_dots/8) バイトで並べた連続領域 diff --git a/ruby/README.md b/ruby/README.md index e0fb52d..03cee0e 100644 --- a/ruby/README.md +++ b/ruby/README.md @@ -1,6 +1,6 @@ # libptouch(Ruby 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` で選びます。 ## 前提 @@ -35,11 +35,15 @@ export LIBPTOUCH_LIB=/usr/local/lib/libptouch.so C の `ptouch-print` と同様の流れですが、**PNG 入力のみ**(`-w`/`-H` や 1bit ラスターは扱いません)。`gem install` 後は PATH に `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` 定数と同じ)。 + 開発ツリーからそのまま試す例: ```bash 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 --status -p 0x2062 +bundle exec ruby -I lib exe/ptouch-print-png -f ../samples/your.png -p 0x20af ``` ## 使用例 @@ -49,8 +53,11 @@ require "libptouch" Libptouch::Context.new.tap do |ctx| 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_hash[:tape_kind] # => {:code=>..., :label=>"ラミネートテープ"} など + p ctx.status_hash[:model] # => {:code=>..., :name=>"PT-P750W"} など(対応機種のみ名前あり) p ctx.status_hash[:status_kind] # => 状態(ステータス種類) ensure ctx.dispose @@ -64,14 +71,16 @@ PNG からラスターへ: ```ruby ctx = Libptouch::Context.new 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.dispose ``` +**解像度:** P750W / P710BT はラスター幅方向が **180 dpi**、P900W 系は **360 dpi**(C の `libptouch_print` / 各機種ラスター PDF に合わせて画像を用意してください)。 + ## API の範囲 -- 実行ファイル `ptouch-print-png` … PNG のみ(`-f`, `-t`, `-n`, `-S`, `-V`, `-h`)。ステータスは JSON(`status_bytes` を `parse_status` したもの、`raw_bytes` 除く) +- 実行ファイル `ptouch-print-png` … PNG のみ(`-f`, `-t`, `-p`, `-n`, `-S`, `-V`, `-h`)。ステータスは JSON(`status_bytes` を `parse_status` したもの、`raw_bytes` 除く) - `Libptouch::Context` … `open_usb` / `open_usb_vid_pid` / `close` / `dispose` - `check_raster` / `print_raster` / `png_file_to_raster` / `status_bytes` / `status_hash` - `Libptouch.parse_status(raw)` … 32 バイトを Hash に展開(機種・テープ幅・**テープ種類**・色・**状態(status_kind)**・エラービット・`raw_hex` など) diff --git a/ruby/lib/libptouch.rb b/ruby/lib/libptouch.rb index 98535fc..563c17a 100644 --- a/ruby/lib/libptouch.rb +++ b/ruby/lib/libptouch.rb @@ -22,6 +22,8 @@ module Libptouch USB_VID_BROTHER = 0x04f9 USB_PID_PTP900W = 0x2085 + USB_PID_PTP750W = 0x2062 + USB_PID_PTP710BT = 0x20af STATUS_LENGTH = 32 PNG_DEFAULT_THRESHOLD = 128 diff --git a/ruby/lib/libptouch/cli/png_print.rb b/ruby/lib/libptouch/cli/png_print.rb index fabd0a0..6fd69c2 100644 --- a/ruby/lib/libptouch/cli/png_print.rb +++ b/ruby/lib/libptouch/cli/png_print.rb @@ -20,7 +20,7 @@ module Libptouch if opts[:status] warn_unused_file_options(opts) - return run_status + return run_status(opts) end return usage_error("-f is required (or use --status)") if opts[:file].to_s.empty? @@ -44,35 +44,78 @@ module Libptouch end 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, threshold: nil, + usb_pid: nil, + usb_pid_invalid: false, dry_run: false, status: false, version: 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 製品 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 ファイル") { |v| o[:file] = v } + p.on("-f", "--file PATH", "入力 PNG ファイル") { |v| opts_hash[:file] = v } p.on("-t", "--threshold N", Integer, "二値化しきい値 0–255(既定 #{Libptouch::PNG_DEFAULT_THRESHOLD})") do |v| - o[:threshold] = v + opts_hash[:threshold] = v end - p.on("-n", "--dry-run", "読み込みと検証のみ(USB なし)") { o[:dry_run] = true } - p.on("-S", "--status", "USB プリンタのステータスを JSON で表示して終了") { o[:status] = true } - p.on("-V", "--version", "バージョンを表示して終了") { o[:version] = true } - p.on("-h", "--help", "このヘルプ") { o[:help] = true } + 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 - parser.parse!(argv) - - unless o[:threshold].nil? || (0..255).cover?(o[:threshold]) - warn "-t must be 0..255" - return nil - end - - o end def usage_banner @@ -103,34 +146,40 @@ module Libptouch end def parser_help_text - o = { - file: nil, - threshold: nil, - dry_run: false, - status: false, - version: false, - help: false - } + opts_hash = default_cli_opts p = OptionParser.new do |parser| parser.banner = "ptouch-print-png [options]" - parser.on("-f", "--file PATH", "入力 PNG ファイル") { |v| o[:file] = v } + parser.on("-f", "--file PATH", "入力 PNG ファイル") { |v| opts_hash[:file] = v } parser.on("-t", "--threshold N", Integer, "二値化しきい値 0–255(既定 #{Libptouch::PNG_DEFAULT_THRESHOLD})") do |v| - o[:threshold] = v + opts_hash[:threshold] = v end - parser.on("-n", "--dry-run", "読み込みと検証のみ(USB なし)") { o[:dry_run] = true } - parser.on("-S", "--status", "USB プリンタのステータスを JSON で表示して終了") { o[:status] = true } - parser.on("-V", "--version", "バージョンを表示して終了") { o[:version] = true } - parser.on("-h", "--help", "このヘルプ") { o[:help] = true } + 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 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 begin ctx = Libptouch::Context.new - ctx.open_usb + open_usb_for_opts(ctx, opts) h = Libptouch.parse_status(ctx.status_bytes) h.delete(:raw_bytes) puts JSON.pretty_generate(h) @@ -161,7 +210,7 @@ module Libptouch return 0 end - ctx.open_usb + open_usb_for_opts(ctx, opts) ctx.print_raster(data, width_dots: width, height_dots: height) 0 rescue Libptouch::Error => e diff --git a/ruby/lib/libptouch/context.rb b/ruby/lib/libptouch/context.rb index a7cf298..b37769c 100644 --- a/ruby/lib/libptouch/context.rb +++ b/ruby/lib/libptouch/context.rb @@ -87,7 +87,7 @@ module Libptouch raw = out_pp.read_pointer 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]] diff --git a/ruby/lib/libptouch/status_hash.rb b/ruby/lib/libptouch/status_hash.rb index c005c82..7b3934b 100644 --- a/ruby/lib/libptouch/status_hash.rb +++ b/ruby/lib/libptouch/status_hash.rb @@ -104,7 +104,9 @@ module Libptouch 0x6F => "PT-P900W", 0x70 => "PT-P950NW", 0x71 => "PT-P900", - 0x78 => "PT-P910BT" + 0x78 => "PT-P910BT", + 0x68 => "PT-P750W", + 0x76 => "PT-P710BT" }.freeze MEDIA_WIDTH = { diff --git a/ruby/libptouch.gemspec b/ruby/libptouch.gemspec index 3f39b92..2296f1a 100644 --- a/ruby/libptouch.gemspec +++ b/ruby/libptouch.gemspec @@ -13,6 +13,7 @@ Gem::Specification.new do |spec| spec.summary = "FFI bindings for libptouch (Brother P-touch USB raster printing)" spec.description = [ "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)." ].join(" ") spec.license = "MIT" diff --git a/src/cli/main.c b/src/cli/main.c index af11dbe..430f157 100644 --- a/src/cli/main.c +++ b/src/cli/main.c @@ -23,6 +23,7 @@ static void usage(const char *argv0) " -H, --height DOTS 1bit ラスター時: 高さ(ドット)\n" " -t, --threshold N PNG 二値化しきい値 0–255(既定 %u、PNG のみ)\n" " -n, --dry-run 読み込みと check_raster のみ(USB なし)\n" + " -p, --pid HEX USB 製品 ID(既定: P900W の 0x2085)。例: P750W 0x2062、P710BT 0x20af\n" " -S, --status USB 接続プリンタのステータス(テープ種・幅・色等)を表示して終了\n" " -V, --version バージョンを表示して終了\n" " -h, --help このヘルプ\n" @@ -95,6 +96,7 @@ int main(int argc, char **argv) const char *file = NULL; unsigned width = 0, height = 0; unsigned threshold = LIBPTOUCH_PNG_DEFAULT_THRESHOLD; + unsigned usb_pid_arg = 0; int dry_run = 0; int has_threshold = 0; int want_status = 0; @@ -104,6 +106,7 @@ int main(int argc, char **argv) { "file", required_argument, NULL, 'f' }, { "threshold", required_argument, NULL, 't' }, { "dry-run", no_argument, NULL, 'n' }, + { "pid", required_argument, NULL, 'p' }, { "status", no_argument, NULL, 'S' }, { "version", no_argument, NULL, 'V' }, { "help", no_argument, NULL, 'h' }, @@ -111,7 +114,7 @@ int main(int argc, char **argv) }; 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) { switch (c) { case 'w': @@ -134,6 +137,13 @@ int main(int argc, char **argv) case 'n': dry_run = 1; 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': want_status = 1; break; @@ -159,7 +169,12 @@ int main(int argc, char **argv) fprintf(stderr, "libptouch_create failed\n"); 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) { fprintf(stderr, "open_usb: %s\n", libptouch_strerror(sctx)); libptouch_destroy(sctx); @@ -258,7 +273,10 @@ int main(int argc, char **argv) return 0; } - e = libptouch_open_usb(ctx); + 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); diff --git a/src/lib/libptouch_internal.h b/src/lib/libptouch_internal.h index 0d07b12..ec4f454 100644 --- a/src/lib/libptouch_internal.h +++ b/src/lib/libptouch_internal.h @@ -22,6 +22,7 @@ struct libptouch_ctx { int claimed_interface; /* -1 if none */ uint8_t bulk_out_ep; uint8_t bulk_in_ep; + uint16_t usb_pid; /* 0 if USB not open; used to pick 128- vs 560-dot raster */ }; void ptouch_set_error(libptouch_ctx *ctx, libptouch_err_t code, const char *msg); diff --git a/src/lib/libptouch_layout.c b/src/lib/libptouch_layout.c new file mode 100644 index 0000000..bc8fd85 --- /dev/null +++ b/src/lib/libptouch_layout.c @@ -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); +} diff --git a/src/lib/libptouch_layout.h b/src/lib/libptouch_layout.h new file mode 100644 index 0000000..35d87fb --- /dev/null +++ b/src/lib/libptouch_layout.h @@ -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 +#include + +/** 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 */ diff --git a/src/lib/libptouch_print.c b/src/lib/libptouch_print.c index ae46479..2e7e2f0 100644 --- a/src/lib/libptouch_print.c +++ b/src/lib/libptouch_print.c @@ -6,96 +6,18 @@ */ #include "libptouch_internal.h" +#include "libptouch_layout.h" #include #include #include #include -/* cv_ptp900_jpn_raster_102.pdf: テープ幅から印刷可能ドット */ -static libptouch_err_t layout_from_status(uint8_t media_kind, uint8_t media_wbyte, - uint16_t *left, uint16_t *print_dots, - uint16_t *right) +static void pack_line(uint8_t *line, size_t line_bytes, unsigned head_dots, + const uint8_t *row, uint32_t width_dots, uint16_t left_dots, + uint16_t print_dots) { - if (media_kind == 0x11u || media_kind == 0x17u) { - switch (media_wbyte) { - case 0x06: - *left = 244; - *print_dots = 56; - *right = 260; - return LIBPTOUCH_OK; - case 0x09: - *left = 224; - *print_dots = 96; - *right = 240; - return LIBPTOUCH_OK; - case 0x0C: - *left = 206; - *print_dots = 132; - *right = 222; - return LIBPTOUCH_OK; - case 0x12: - *left = 166; - *print_dots = 212; - *right = 182; - return LIBPTOUCH_OK; - case 0x18: - *left = 144; - *print_dots = 256; - *right = 160; - return LIBPTOUCH_OK; - default: - break; - } - } - 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); + memset(line, 0, line_bytes); if (width_dots > (uint32_t)print_dots) return; 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) continue; uint32_t dot = start + x; - if (dot >= 560u) + if (dot >= (uint32_t)head_dots) break; uint32_t b = 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) 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_w = st[10]; uint16_t left_dots, print_dots, right_dots; - v = layout_from_status(media_kind, media_w, &left_dots, &print_dots, - &right_dots); + v = ptouch_layout_from_status(prof, media_kind, media_w, &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 " @@ -171,6 +101,10 @@ libptouch_err_t libptouch_print_raster(libptouch_ctx *ctx, } (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 ht = params->height_dots; 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; } + double margin_dpi = prof->margin_feed_dpi; + unsigned margin_max = prof->margin_feed_max_dots; unsigned margin_dots = 14u; 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 + 0.5); if (margin_dots < 14u) margin_dots = 14u; - if (margin_dots > 1800u) - margin_dots = 1800u; + if (margin_dots > margin_max) + margin_dots = margin_max; } 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; - uint8_t gbuf[73]; static const uint8_t g_hdr[] = { 0x47, 0x46, 0x00 }; + uint8_t *gbuf = (uint8_t *)malloc(gf_packet); + if (!gbuf) { + ptouch_set_error(ctx, LIBPTOUCH_ERR_NOMEM, + "raster line buffer"); + free(transposed); + return LIBPTOUCH_ERR_NOMEM; + } + for (uint32_t y = 0; y < lines; y++) { 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)); - 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) { + free(gbuf); free(transposed); return v; } } + free(gbuf); static const uint8_t print_end[] = { 0x1A }; v = ptouch_bulk_send_job(ctx, print_end, sizeof(print_end), "print end"); diff --git a/src/lib/libptouch_status.c b/src/lib/libptouch_status.c index d41c377..a0cd3fd 100644 --- a/src/lib/libptouch_status.c +++ b/src/lib/libptouch_status.c @@ -75,6 +75,12 @@ static void fprint_model(FILE *fp, uint8_t code) case 0x78: name = "PT-P910BT"; break; + case 0x68: + name = "PT-P750W"; + break; + case 0x76: + name = "PT-P710BT"; + break; default: break; } diff --git a/src/lib/libptouch_usb.c b/src/lib/libptouch_usb.c index 9d1d6f4..f3d43bd 100644 --- a/src/lib/libptouch_usb.c +++ b/src/lib/libptouch_usb.c @@ -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_in_ep); + ctx->usb_pid = pid; ctx->usb_open = 1; ptouch_set_error(ctx, LIBPTOUCH_OK, ""); return LIBPTOUCH_OK; @@ -141,6 +142,7 @@ void libptouch_close(libptouch_ctx *ctx) } ctx->bulk_out_ep = 0; ctx->bulk_in_ep = 0; + ctx->usb_pid = 0; ctx->usb_open = 0; } diff --git a/udev/99-ptouch-label-brother.rules b/udev/99-ptouch-label-brother.rules index 3390324..b53f5dd 100644 --- a/udev/99-ptouch-label-brother.rules +++ b/udev/99-ptouch-label-brother.rules @@ -1,5 +1,6 @@ # Brother P-touch — libusb でユーザから開けるようにする (Ubuntu 等) # PT-P900W: lsusb で ID 04f9:2085 +# PT-P750W: 04f9:2062 PT-P710BT: 04f9:20af(Brother ラスター PDF Appendix A) # # 設置: # sudo cp udev/99-ptouch-label-brother.rules /etc/udev/rules.d/ @@ -11,6 +12,8 @@ # GROUP=plugdev … 従来どおり plugdev に所属していれば読み書き可 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 を足す例(コメントを外して複製): # SUBSYSTEM=="usb", ATTR{idVendor}=="04f9", ATTR{idProduct}=="2086", MODE="0660", GROUP="plugdev", TAG+="uaccess"