4 Commits

Author SHA1 Message Date
knb
e10a430f9e add cut bitmask option and debug dump support
Replace auto-cut toggle with --cut bit flags (default 011), wire flags through C/Ruby APIs, and document the new cut/debug-dump behavior in both READMEs.

Made-with: Cursor
2026-04-20 04:22:27 +09:00
knb
bfd6adda42 libptouch: P700/P900 families, optional JSON config, ESC i z and print sequencing
- Add printer_family and profile flags; load overrides from printer_families.json
- Set ESC i z page byte from profile; pass prof into ptouch_fill_esc_iz
- End print with 0x1A only (drop FF prefix); extend Ruby FFI and CLI media info
- Add reference/ptp_raster_ref.adoc; install config under share/ptouch_label

Made-with: Cursor
2026-04-19 12:20:21 +09:00
knb
3f2eb464aa docs(samples): add QR/barcode merge template and CLI example
Add code.svg/code.yml placeholders for data-kb-placeholder qr/barcode.
Extend samples README with ptouch-label command using these files.

Made-with: Cursor
2026-04-19 10:07:27 +09:00
knb
f1779b94f0 feat(ruby): QR/Barcode merge placeholders in SVG templates
Add data-kb-placeholder qr/barcode support with rqrcode and barby.
Scale QR to fit placeholder box (viewBox + meet) and barcode by height
with natural width. Declare barby and rqrcode runtime dependencies.

Made-with: Cursor
2026-04-19 10:06:50 +09:00
32 changed files with 881 additions and 81 deletions

1
.gitignore vendored
View File

@@ -1,4 +1,5 @@
build/
tmp/
ruby/*.gem
ruby/.rubocop_cache/

View File

@@ -27,6 +27,7 @@ set(LIBPTOUCH_SOURCES
src/lib/libptouch_usb.c
src/lib/libptouch_protocol.c
src/lib/libptouch_layout.c
src/lib/libptouch_family_config.c
src/lib/libptouch_media_info.c
src/lib/libptouch_trim.c
src/lib/libptouch_print.c
@@ -82,6 +83,8 @@ add_executable(ptouch-protocol-regression-test
)
target_include_directories(ptouch-protocol-regression-test PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}/src/lib"
"${CMAKE_CURRENT_SOURCE_DIR}/include"
"${CMAKE_CURRENT_BINARY_DIR}/include"
)
if(NOT MSVC)
target_compile_options(ptouch-protocol-regression-test PRIVATE -Wall -Wextra -Wpedantic)
@@ -96,3 +99,6 @@ install(FILES
"${CMAKE_CURRENT_SOURCE_DIR}/include/libptouch.h"
"${CMAKE_CURRENT_BINARY_DIR}/include/libptouch_version.h"
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}")
install(FILES
"${CMAKE_CURRENT_SOURCE_DIR}/config/printer_families.json"
DESTINATION "${CMAKE_INSTALL_DATADIR}/ptouch_label")

View File

@@ -47,6 +47,8 @@ ctest --test-dir build --output-on-failure
**SVG**(拡張子 `.svg`の場合は現在装着中テープの印字可能ドット幅に合わせて自動拡大・縮小しますUSB 接続が必要)。
`--trim-right[=DOTS]` で右側空白列を削減できますDOTS 省略時は左余白ドット、取得失敗時は 0
任意で `-t`0255で二値化しきい値を指定できます。
`--cut BITS` でカット系フラグを指定できます(`[auto-cut][half-cut][chain-print]`、例 `010`)。
指定なしの既定は `011`(オートカットしない / ハーフカットする / つなげて印刷する)です。
**1bit packed ラスター**(行優先、行あたり `ceil(width/8)` バイト × 行数)の場合は `-f` に加え `-w` / `-H` が必須です。
@@ -64,6 +66,12 @@ ctest --test-dir build --output-on-failure
# PNG — しきい値を指定
./build/ptouch-print -n -f label.png -t 160
# カットフラグを指定auto=0, half=1, chain=0
./build/ptouch-print -n -f label.png --cut 010
# デバッグ: USB に送る印字データをファイル保存
./build/ptouch-print -f label.png --debug-dump print_job.bin
# 1bit ラスター — 検証のみ
./build/ptouch-print -n -f sample.raster -w 128 -H 64
@@ -90,6 +98,7 @@ ctest --test-dir build --output-on-failure
- `libptouch_get_status` / `libptouch_status_fprint` — ステータス情報リクエストESC i Sの応答
- `libptouch_get_current_media_info` — 現在テープ幅(mm)・印字可能幅(dots)・DPI・最小余白(mm)を取得
- `libptouch_check_raster` — ラスターバッファの検証のみ
- `libptouch_set_debug_dump_path` — USB bulk OUT へ送った印字データをファイル保存(デバッグ用)
- `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 のコマンド列、内部で転置)

View File

@@ -0,0 +1,18 @@
{
"families": {
"p700": {
"comment": "PT-P750W / PT-P710BTcv_ptp750w_710bt_jpn_raster_102.pdf 系)",
"print_dpi": 180,
"margin_feed_dpi": 180,
"margin_feed_max_dots": 900,
"send_esc_ia_cut_each": false
},
"p900": {
"comment": "PT-P900 / P900W / P950NW / P910BTcv_ptp900_jpn_raster_102.pdf 系)",
"print_dpi": 360,
"margin_feed_dpi": 360,
"margin_feed_max_dots": 1800,
"send_esc_ia_cut_each": true
}
}
}

View File

@@ -39,6 +39,18 @@ typedef enum {
LIBPTOUCH_ERR_IMAGE = 7,
} libptouch_err_t;
/**
* リファレンス上の系統PT-P750W/P710BT = P700、PT-P900 系 = P900
* DPI の値で分岐する代わりに、この系統を使う。
*/
typedef enum {
LIBPTOUCH_FAMILY_UNKNOWN = 0,
/** PT-P750W / PT-P710BTcv_ptp750w_710bt_jpn_raster_102.pdf 系) */
LIBPTOUCH_FAMILY_P700 = 1,
/** PT-P900 / P900W / P950NW / P910BT 等cv_ptp900_jpn_raster_102.pdf 系) */
LIBPTOUCH_FAMILY_P900 = 2,
} libptouch_printer_family_t;
/** lsusb 例: PT-P900W — Brother Industries, Ltd (04f9:2085) */
#define LIBPTOUCH_USB_VID_BROTHER 0x04f9u
#define LIBPTOUCH_USB_PID_PTP900W 0x2085u
@@ -49,6 +61,11 @@ typedef enum {
libptouch_ctx *libptouch_create(void);
void libptouch_destroy(libptouch_ctx *ctx);
/**
* 系統の短い英語ラベル("p700" / "p900" / "unknown"。UI やログ用。
*/
const char *libptouch_printer_family_label(libptouch_printer_family_t family);
/** 人が読める英語メッセージ(スレッド非安全: ctx ごと) */
const char *libptouch_strerror(const libptouch_ctx *ctx);
libptouch_err_t libptouch_last_error(const libptouch_ctx *ctx);
@@ -67,16 +84,39 @@ libptouch_err_t libptouch_open_usb(libptouch_ctx *ctx);
void libptouch_close(libptouch_ctx *ctx);
/**
* デバッグ用: USB bulk OUT へ実際に送ったバイト列を path に保存する。
* 各 @ref libptouch_print_raster ごとにファイルを上書きし直す(ジョブ先頭のチャンクで wb
* path が NULL または空文字で無効化。転送が成功したチャンクのみ追記する。
*/
void libptouch_set_debug_dump_path(libptouch_ctx *ctx, const char *path);
/** @ref libptouch_raster_params_t.flags 用: ESC i M bit6 でオートカット ON */
#define LIBPTOUCH_RASTER_FLAG_AUTO_CUT 0x01u
/** @ref libptouch_raster_params_t.flags 用: ESC i K bit2 でハーフカット ON */
#define LIBPTOUCH_RASTER_FLAG_HALF_CUT 0x02u
/** @ref libptouch_raster_params_t.flags 用: ChainPrint するESC i K bit3=0 */
#define LIBPTOUCH_RASTER_FLAG_CHAIN_PRINT 0x04u
/** 既定: 011オートカットしない・ハーフカットする・つなげて印刷する */
#define LIBPTOUCH_RASTER_FLAGS_DEFAULT \
(LIBPTOUCH_RASTER_FLAG_HALF_CUT | LIBPTOUCH_RASTER_FLAG_CHAIN_PRINT)
typedef struct {
uint32_t width_dots; /**< ラスター幅(ドット) */
uint32_t height_dots; /**< ラスター高さ(ドット・走査方向は実装と機種に依存) */
uint8_t margin_mm; /**< 余白など(機種・仕様に合わせて使用) */
/**
* LIBPTOUCH_RASTER_FLAG_*。未使用ビットは 0。
* 既定は @ref LIBPTOUCH_RASTER_FLAGS_DEFAULT011: オートカットしない・ハーフカットする・つなげて印刷する)。
*/
uint8_t flags;
uint8_t _reserved[2]; /**< 将来用。0 にすること */
} 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 print_dpi; /**< 印字幅方向 DPI系統既定または設定ファイル上書き */
double feed_dpi; /**< 送り方向 DPIESC i d の基準) */
double tape_width_mm; /**< 装着テープ幅mm。例: 3.5, 6, 9, 12, 18, 24, 36 */
uint16_t printable_dots; /**< 現在テープで印字可能な幅(ドット) */
@@ -84,6 +124,8 @@ typedef struct {
uint16_t right_margin_dots; /**< 右余白(ドット) */
uint16_t min_feed_dots; /**< 仕様上の最小送り量(ドット)。現在は 14 */
double min_feed_mm; /**< 最小送り量mm */
/** @ref libptouch_printer_family_t系統不明時は @ref LIBPTOUCH_FAMILY_UNKNOWN */
uint32_t printer_family;
} libptouch_media_info_t;
/**
@@ -113,9 +155,11 @@ libptouch_err_t libptouch_trim_right_blank_columns(
/**
* 1 ビット packed ラスターを USB で印刷(各機種のラスター PDF 準拠)。
* 印字前にステータスでテープ幅を読み、印刷可能ドット内に画像を中央配置する。
* width_dots は装着テープの印刷可能幅以下であること。PT-P900 系は幅 360dpi、
* PT-P750W / PT-P710BT は幅 180dpicv_ptp750w_710bt_jpn_raster_102.pdfのドット列を想定
* width_dots は装着テープの印刷可能幅以下であること。系統は @ref libptouch_get_current_media_info
* の printer_family で判別できるP900 系は主に 360dpi 相当ドット列、P700 系は 180dpi 相当)
* @param margin_mm 余白フィード量。0 のとき PDF の最小 1mm14 ドット)相当を送る。
* @param params->flags bit0=オートカット、bit1=ハーフカット、bit2=チェーンプリントする。
* 指定なしは @ref LIBPTOUCH_RASTER_FLAGS_DEFAULT011
* 印刷時は内部でドット列を転置する(テープ幅方向とバッファの縦横の対応)。
* @param data 1 行あたり width_dots ビットを ceil(width_dots/8) バイトで並べた連続領域
* @param data_len 期待値: height * row_bytes, row_bytes = (width_dots + 7) / 8

View File

@@ -0,0 +1,14 @@
= P-Toouch Raster コマンド リファレンスメモ
== 印字データ
印刷データは大きく分けて、初期化コマンド、制御コード、ラスターデータ、印字指令から構成される。
ジョブが複数ページからなる場合には、制御コード~ラスターデータを繰り返します。
=== 初期化コマンド
印刷開始時に一度送信
1. 無効指令: NULL(0x00) × 200バイト
2. 初期化: ESC @ (0x1B 0x40)

View File

@@ -1,14 +1,18 @@
PATH
remote: .
specs:
libptouch (1.0.0)
libptouch (1.0.1)
barby
ffi (~> 1.15)
rexml
rqrcode
GEM
remote: https://rubygems.org/
specs:
ast (2.4.3)
barby (0.7.0)
chunky_png (1.4.0)
ffi (1.17.4)
ffi (1.17.4-aarch64-linux-gnu)
ffi (1.17.4-aarch64-linux-musl)
@@ -32,6 +36,10 @@ GEM
rainbow (3.1.1)
regexp_parser (2.12.0)
rexml (3.4.4)
rqrcode (3.2.0)
chunky_png (~> 1.0)
rqrcode_core (~> 2.0)
rqrcode_core (2.1.0)
rubocop (1.86.1)
json (~> 2.3)
language_server-protocol (~> 3.17.0.2)
@@ -70,6 +78,8 @@ DEPENDENCIES
CHECKSUMS
ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383
barby (0.7.0) sha256=8dbc7c0c320e596135d97929c0df77f969e9f9c955a157cf6749c05b44dae213
chunky_png (1.4.0) sha256=89d5b31b55c0cf4da3cf89a2b4ebc3178d8abe8cbaf116a1dba95668502fdcfe
ffi (1.17.4) sha256=bcd1642e06f0d16fc9e09ac6d49c3a7298b9789bcb58127302f934e437d60acf
ffi (1.17.4-aarch64-linux-gnu) sha256=b208f06f91ffd8f5e1193da3cae3d2ccfc27fc36fba577baf698d26d91c080df
ffi (1.17.4-aarch64-linux-musl) sha256=9286b7a615f2676245283aef0a0a3b475ae3aae2bb5448baace630bb77b91f39
@@ -83,7 +93,7 @@ CHECKSUMS
ffi (1.17.4-x86_64-linux-musl) sha256=3fdf9888483de005f8ef8d1cf2d3b20d86626af206cbf780f6a6a12439a9c49e
json (2.19.3) sha256=289b0bb53052a1fa8c34ab33cc750b659ba14a5c45f3fcf4b18762dc67c78646
language_server-protocol (3.17.0.5) sha256=fd1e39a51a28bf3eec959379985a72e296e9f9acfce46f6a79d31ca8760803cc
libptouch (1.0.0)
libptouch (1.0.1)
lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87
parallel (2.0.1) sha256=337782d3e39f4121e67563bf91dd8ece67f48923d90698614773a0ec9a5b2c7d
parser (3.3.11.1) sha256=d17ace7aabe3e72c3cc94043714be27cc6f852f104d81aa284c2281aecc65d54
@@ -92,6 +102,8 @@ CHECKSUMS
rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a
regexp_parser (2.12.0) sha256=35a916a1d63190ab5c9009457136ae5f3c0c7512d60291d0d1378ba18ce08ebb
rexml (3.4.4) sha256=19e0a2c3425dfbf2d4fc1189747bdb2f849b6c5e74180401b15734bc97b5d142
rqrcode (3.2.0) sha256=64c1494ca6bb67d731330f38b50e3fd09eeab4f5dcd04b608e21218d1d0b9542
rqrcode_core (2.1.0) sha256=f303b85df89c1b8fc5ee8dc19808c9dc4330e6329b660d99d4a8cbb36ca13051
rubocop (1.86.1) sha256=44415f3f01d01a21e01132248d2fd0867572475b566ca188a0a42133a08d4531
rubocop-ast (1.49.1) sha256=4412f3ee70f6fe4546cc489548e0f6fcf76cafcfa80fa03af67098ffed755035
ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33

View File

@@ -37,7 +37,10 @@ SVG は現在装着テープの印字可能幅に合わせて自動拡大・縮
オプションは C 側に合わせ、`**-p` / `--pid`** で USB 製品 ID16 進可)を指定できます。省略時は PT-P900W`Libptouch::USB_PID_PTP900W` = `0x2085`)。例: PT-P750W `0x2062`、PT-P710BT `0x20af``libptouch.h` / `Libptouch` 定数と同じ)。
また、`--template`SVGと `--data`JSON/YAMLを使うと `data-field` 属性をキーにした差込印刷が可能です。
`data-kb-placeholder="qr"` / `data-kb-placeholder="barcode"` を付けた SVG 要素(`x/y/width/height` 必須)には、対応する `data-field` 値から QR / Code128 バーコードを生成して差し込みできます。
`--trim-right[=DOTS]` を付けると、libptouch 側の共通処理でラベル右側の空白ドット列を削減します。`DOTS` 省略時は左余白ドット数を使い、取得失敗時は `0` にフォールバックします。
`--cut BITS` は 3bit`[auto-cut][half-cut][chain-print]`)で、例 `010` のように指定します。既定は `011` です。
`--debug-dump PATH` を付けると USB へ送る印字データをファイル保存します1 印刷ごとに上書き)。
開発ツリーからそのまま試す例:
@@ -47,6 +50,8 @@ bundle exec ruby -I lib exe/ptouch-label -n -f ../samples/your.png
bundle exec ruby -I lib exe/ptouch-label -n -f ../samples/your.svg
bundle exec ruby -I lib exe/ptouch-label -n -f ../samples/your.svg --trim-right
bundle exec ruby -I lib exe/ptouch-label -n -f ../samples/your.svg --trim-right=20
bundle exec ruby -I lib exe/ptouch-label -n -f ../samples/your.png --cut 010
bundle exec ruby -I lib exe/ptouch-label -f ../samples/your.png --debug-dump print_job.bin
bundle exec ruby -I lib exe/ptouch-label -n --template ../samples/your_template.svg --data ../samples/your_data.yml
bundle exec ruby -I lib exe/ptouch-label --media-info
bundle exec ruby -I lib exe/ptouch-label --status -p 0x2062
@@ -97,7 +102,7 @@ ctx.dispose
## API の範囲
- 実行ファイル `ptouch-label`(互換: `ptouch-print-png`)… PNG/SVG`-f`, `-t`, `-p`, `-n`, `-M`, `-S`, `-V`, `-h`, `--template`, `--data`, `--trim-right[=DOTS]`)。`-M` は現在テープ情報(幅 mm・DPI 等)を JSON 出力、`-S` はステータス JSON 出力、`--trim-right` は右側空白列を削減
- 実行ファイル `ptouch-label`(互換: `ptouch-print-png`)… PNG/SVG`-f`, `-t`, `-p`, `-n`, `-M`, `-S`, `-V`, `-h`, `--template`, `--data`, `--trim-right[=DOTS]`, `--cut BITS`, `--debug-dump PATH`)。`-M` は現在テープ情報(幅 mm・DPI 等)を JSON 出力、`-S` はステータス JSON 出力、`--trim-right` は右側空白列を削減
- `Libptouch::Context` … `open_usb` / `open_usb_vid_pid` / `close` / `dispose`
- `check_raster` / `print_raster` / `png_file_to_raster` / `svg_file_to_raster_fit_current_tape` / `status_bytes` / `status_hash` / `current_media_info`
- `current_media_info` には `print_dpi` / `feed_dpi` / `tape_width_mm` / `printable_height_dots` / `min_feed_mm` などを含む(テープ幅方向は `printable_height_dots`

View File

@@ -27,4 +27,14 @@ module Libptouch
STATUS_LENGTH = 32
PNG_DEFAULT_THRESHOLD = 128
# libptouch_printer_family_tC API と同じ値)
FAMILY_UNKNOWN = 0
FAMILY_P700 = 1
FAMILY_P900 = 2
RASTER_FLAG_AUTO_CUT = 0x01
RASTER_FLAG_HALF_CUT = 0x02
RASTER_FLAG_CHAIN_PRINT = 0x04
RASTER_FLAGS_DEFAULT = RASTER_FLAG_HALF_CUT | RASTER_FLAG_CHAIN_PRINT
end

View File

@@ -25,7 +25,9 @@ module Libptouch
layout :width_dots, :uint32,
:height_dots, :uint32,
:margin_mm, :uint8,
:_pad, [:uint8, 3]
:flags, :uint8,
:reserved0, :uint8,
:reserved1, :uint8
end
class MediaInfo < FFI::Struct
@@ -39,7 +41,8 @@ module Libptouch
:left_margin_dots, :uint16,
:right_margin_dots, :uint16,
:min_feed_dots, :uint16,
:min_feed_mm, :double
:min_feed_mm, :double,
:printer_family, :uint32
end
class PngOptions < FFI::Struct
@@ -51,6 +54,8 @@ module Libptouch
end
attach_function :libptouch_create, [], :pointer
attach_function :libptouch_set_debug_dump_path, %i[pointer string], :void
attach_function :libptouch_printer_family_label, [:uint32], :string
attach_function :libptouch_destroy, [:pointer], :void
attach_function :libptouch_strerror, [:pointer], :string
attach_function :libptouch_last_error, [:pointer], :int

View File

@@ -2,9 +2,13 @@
require "json"
require "optparse"
require "rqrcode"
require "rexml/document"
require "tempfile"
require "yaml"
require "barby"
require "barby/barcode/code_128"
require "barby/outputter/svg_outputter"
require "libptouch"
@@ -72,8 +76,10 @@ module Libptouch
return nil unless threshold_option_ok?(opts_hash)
return nil unless trim_right_option_ok?(opts_hash)
return nil unless usb_pid_option_ok?(opts_hash)
return nil if opts_hash[:cut_invalid]
opts_hash.delete(:usb_pid_invalid)
opts_hash.delete(:cut_invalid)
opts_hash
end
@@ -90,7 +96,9 @@ module Libptouch
media_info: false,
status: false,
version: false,
help: false
help: false,
cut_flags: Libptouch::RASTER_FLAGS_DEFAULT,
debug_dump: nil
}
end
@@ -134,6 +142,19 @@ module Libptouch
false
end
def apply_cut_option(opts_hash, bits)
unless bits.is_a?(String) && bits.match?(/\A[01]{3}\z/)
warn "--cut must be 3 bits (e.g. 010: auto/half/chain)"
opts_hash[:cut_invalid] = true
return
end
flags = 0
flags |= Libptouch::RASTER_FLAG_AUTO_CUT if bits[0] == "1"
flags |= Libptouch::RASTER_FLAG_HALF_CUT if bits[1] == "1"
flags |= Libptouch::RASTER_FLAG_CHAIN_PRINT if bits[2] == "1"
opts_hash[:cut_flags] = flags
end
def build_cli_parser(opts_hash)
OptionParser.new do |p|
p.banner = usage_banner
@@ -148,6 +169,12 @@ module Libptouch
p.on("-p", "--pid PID", pid_option_description) do |v|
apply_usb_pid_option(opts_hash, v)
end
p.on("--cut BITS", "3bit: [auto][half][chain]。既定 011") do |v|
apply_cut_option(opts_hash, v)
end
p.on("--debug-dump PATH", "デバッグ: 印字バイト列を PATH に保存1 印刷ごとに上書き)") do |v|
opts_hash[:debug_dump] = v
end
p.on("-n", "--dry-run", "読み込みと検証のみUSB なし)") { opts_hash[:dry_run] = true }
p.on("--trim-right[=DOTS]", Integer,
"右側空白を削減。DOTS省略時は左余白失敗時 0") do |v|
@@ -208,6 +235,12 @@ module Libptouch
parser.on("-p", "--pid PID", pid_option_description) do |v|
apply_usb_pid_option(opts_hash, v)
end
parser.on("--cut BITS", "3bit: [auto][half][chain]。既定 011") do |v|
apply_cut_option(opts_hash, v)
end
parser.on("--debug-dump PATH", "印字バイト列を PATH に保存") do |v|
opts_hash[:debug_dump] = v
end
parser.on("-n", "--dry-run", "読み込みと検証のみUSB なし)") { opts_hash[:dry_run] = true }
parser.on("--trim-right[=DOTS]", Integer,
"右側空白を削減。DOTS省略時は左余白失敗時 0") do |v|
@@ -289,12 +322,155 @@ module Libptouch
replace_text_element_content(el, data[key].to_s)
end
replace_code_placeholders(doc, data)
out = +""
formatter = REXML::Formatters::Default.new
formatter.write(doc, out)
out
end
def replace_code_placeholders(doc, data)
doc.elements.each("//*[@data-field][@data-kb-placeholder]") do |el|
key = el.attributes["data-field"].to_s
next unless data.key?(key)
kind = el.attributes["data-kb-placeholder"].to_s.downcase
next unless %w[qr barcode].include?(kind)
x, y, width, height = read_box(el)
next if width <= 0 || height <= 0
raw_value = data[key].to_s
next if raw_value.empty?
replacement = build_code_svg_element(
kind: kind,
value: raw_value,
x: x,
y: y,
width: width,
height: height
)
el.parent&.insert_after(el, replacement)
el.parent&.delete(el)
end
end
def read_box(el)
x = el.attributes["x"].to_s.to_f
y = el.attributes["y"].to_s.to_f
width = el.attributes["width"].to_s.to_f
height = el.attributes["height"].to_s.to_f
[x, y, width, height]
end
def build_code_svg_element(kind:, value:, x:, y:, width:, height:)
svg_fragment =
if kind == "qr"
render_qr_svg(value)
else
render_barcode_svg(value)
end
fragment_doc = REXML::Document.new(strip_xml_declaration(svg_fragment))
svg_root = fragment_doc.root
svg_root = wrap_non_svg_root(svg_root) unless svg_root&.name == "svg"
if kind == "qr"
apply_qr_svg_box(svg_root, x: x, y: y, width: width, height: height)
else
apply_barcode_svg_box(svg_root, x: x, y: y, height: height)
end
svg_root
end
def apply_qr_svg_box(svg_root, x:, y:, width:, height:)
nat_w, nat_h = svg_natural_dimensions(svg_root)
if nat_w <= 0 || nat_h <= 0
nat_w = parse_length_attr(svg_root.attributes["width"])
nat_h = parse_length_attr(svg_root.attributes["height"])
end
raise ArgumentError, "QR SVG has no usable dimensions" if nat_w <= 0 || nat_h <= 0
svg_root.attributes["viewBox"] = "0 0 #{nat_w} #{nat_h}"
svg_root.attributes["x"] = x.to_s
svg_root.attributes["y"] = y.to_s
svg_root.attributes["width"] = width.to_s
svg_root.attributes["height"] = height.to_s
svg_root.attributes["preserveAspectRatio"] = "xMidYMid meet"
end
def apply_barcode_svg_box(svg_root, x:, y:, height:)
nat_w, nat_h = svg_natural_dimensions(svg_root)
if nat_w <= 0 || nat_h <= 0
nat_w = parse_length_attr(svg_root.attributes["width"])
nat_h = parse_length_attr(svg_root.attributes["height"])
end
raise ArgumentError, "barcode SVG has no usable dimensions" if nat_w <= 0 || nat_h <= 0
scale = height / nat_h
out_w = nat_w * scale
out_h = nat_h * scale
svg_root.attributes["viewBox"] = "0 0 #{nat_w} #{nat_h}"
svg_root.attributes["x"] = x.to_s
svg_root.attributes["y"] = y.to_s
svg_root.attributes["width"] = out_w.to_s
svg_root.attributes["height"] = out_h.to_s
svg_root.attributes["preserveAspectRatio"] = "xMidYMid meet"
end
def strip_xml_declaration(s)
s.to_s.sub(/\A<\?xml[^>]*\?>\s*/m, "")
end
def svg_natural_dimensions(svg_root)
vb = svg_root.attributes["viewBox"].to_s.strip
if (m = /\A\s*([-\d.eE+]+)\s+([-\d.eE+]+)\s+([-\d.eE+]+)\s+([-\d.eE+]+)\s*\z/.match(vb))
return [m[3].to_f, m[4].to_f]
end
[parse_length_attr(svg_root.attributes["width"]),
parse_length_attr(svg_root.attributes["height"])]
end
def parse_length_attr(str)
s = str.to_s.strip
return 0.0 if s.empty?
s = s.delete_suffix("px").strip
Float(s)
rescue ArgumentError
0.0
end
def render_qr_svg(value)
qrcode = RQRCode::QRCode.new(value)
qrcode.as_svg(
standalone: true,
use_path: true,
module_size: 1,
offset: 0,
color: "000",
shape_rendering: "crispEdges"
)
end
def render_barcode_svg(value)
barcode = Barby::Code128B.new(value)
Barby::SvgOutputter.new(barcode).to_svg(
xdim: 2,
height: 64,
margin: 0
)
end
def wrap_non_svg_root(root)
svg = REXML::Element.new("svg")
svg.add_namespace("http://www.w3.org/2000/svg")
svg.add_element(root)
svg
end
def replace_text_element_content(text_element, value)
# Remove all child nodes first so mixed content (<tspan>, text nodes, etc.)
# gets replaced consistently by merge data.
@@ -339,6 +515,7 @@ module Libptouch
ctx = nil
begin
ctx = Libptouch::Context.new
ctx.debug_dump_path = opts[:debug_dump] if opts[:debug_dump]
usb_opened = false
threshold = opts[:threshold]
data, width, height = if kind == :svg
@@ -361,11 +538,13 @@ module Libptouch
data,
width_dots: width,
height_dots: height,
right_padding_dots: trim_pad
right_padding_dots: trim_pad,
cut_flags: opts[:cut_flags]
)
end
ctx.check_raster(data, width_dots: width, height_dots: height)
ctx.check_raster(data, width_dots: width, height_dots: height,
cut_flags: opts[:cut_flags])
if opts[:dry_run]
puts "dry-run OK: #{data.bytesize} bytes, src #{width}x#{height} dots (print lengthxwidth #{width}x#{height})"
@@ -373,7 +552,8 @@ module Libptouch
end
open_usb_for_opts(ctx, opts) if kind == :png && !usb_opened
ctx.print_raster(data, width_dots: width, height_dots: height)
ctx.print_raster(data, width_dots: width, height_dots: height,
cut_flags: opts[:cut_flags])
0
rescue Libptouch::Error => e
warn e.message

View File

@@ -36,6 +36,11 @@ module Libptouch
self
end
def debug_dump_path=(path)
Binding.libptouch_set_debug_dump_path(@native, path.nil? || path.to_s.empty? ? nil : path.to_s)
self
end
def close
Binding.libptouch_close(@native) if @native && !@native.null?
self
@@ -47,11 +52,14 @@ module Libptouch
@native = nil
end
def check_raster(data, width_dots:, height_dots:, margin_mm: 0)
def check_raster(data, width_dots:, height_dots:, margin_mm: 0, cut_flags: RASTER_FLAGS_DEFAULT)
params = Binding::RasterParams.new
params[:width_dots] = width_dots
params[:height_dots] = height_dots
params[:margin_mm] = margin_mm
params[:flags] = cut_flags
params[:reserved0] = 0
params[:reserved1] = 0
buf = FFI::MemoryPointer.new(:uint8, data.bytesize)
buf.put_bytes(0, data)
raise_on_error(Binding.libptouch_check_raster(@native, buf, data.bytesize,
@@ -59,11 +67,14 @@ module Libptouch
self
end
def print_raster(data, width_dots:, height_dots:, margin_mm: 0)
def print_raster(data, width_dots:, height_dots:, margin_mm: 0, cut_flags: RASTER_FLAGS_DEFAULT)
params = Binding::RasterParams.new
params[:width_dots] = width_dots
params[:height_dots] = height_dots
params[:margin_mm] = margin_mm
params[:flags] = cut_flags
params[:reserved0] = 0
params[:reserved1] = 0
buf = FFI::MemoryPointer.new(:uint8, data.bytesize)
buf.put_bytes(0, data)
raise_on_error(Binding.libptouch_print_raster(@native, buf, data.bytesize,
@@ -115,11 +126,15 @@ module Libptouch
[bytes, out_params[:width_dots], out_params[:height_dots]]
end
def trim_right_blank_columns(data, width_dots:, height_dots:, margin_mm: 0, right_padding_dots: 0)
def trim_right_blank_columns(data, width_dots:, height_dots:, margin_mm: 0,
right_padding_dots: 0, cut_flags: RASTER_FLAGS_DEFAULT)
in_params = Binding::RasterParams.new
in_params[:width_dots] = width_dots
in_params[:height_dots] = height_dots
in_params[:margin_mm] = margin_mm
in_params[:flags] = cut_flags
in_params[:reserved0] = 0
in_params[:reserved1] = 0
in_buf = FFI::MemoryPointer.new(:uint8, data.bytesize)
in_buf.put_bytes(0, data)
out_pp = FFI::MemoryPointer.new(:pointer)
@@ -151,6 +166,7 @@ module Libptouch
def current_media_info
info = Binding::MediaInfo.new
raise_on_error(Binding.libptouch_get_current_media_info(@native, info.pointer))
fam = info[:printer_family]
{
media_width_code: info[:media_width_code],
media_kind_code: info[:media_kind_code],
@@ -161,7 +177,9 @@ module Libptouch
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]
min_feed_mm: info[:min_feed_mm],
printer_family: fam,
printer_family_label: Binding.libptouch_printer_family_label(fam)
}
end
end

View File

@@ -29,5 +29,7 @@ Gem::Specification.new do |spec|
spec.require_paths = ["lib"]
spec.add_dependency "ffi", "~> 1.15"
spec.add_dependency "barby"
spec.add_dependency "rqrcode"
spec.add_dependency "rexml"
end

View File

@@ -13,4 +13,5 @@
```bash
ruby -I ruby/lib ruby/exe/ptouch-label -n --template samples/merge_template.svg --data samples/merge_data.yml
ruby -I ruby/lib ruby/exe/ptouch-label -n --template samples/merge_template.svg --data samples/merge_data.json
ruby -I ruby/lib ruby/exe/ptouch-label -n --template samples/code.svg --data samples/code.yml
```

7
samples/code.svg Normal file
View File

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 36" width="100mm" height="36mm">
<rect x="0.25" y="0.25" width="99.75" height="35.75" fill="#ffffff" stroke="#000000" stroke-width="0.25"/>
<text x="2" y="7" font-size="4.5" fill="#000000" font-family="sans-serif" data-field="itemName">サンプル備品</text>
<text x="2" y="14" font-size="3.2" fill="#000000" font-family="sans-serif" data-field="model">KB-001</text>
<rect x="78" y="3" width="18.25" height="18.25" fill="none" stroke="#000000" stroke-width="0.25" stroke-dasharray="1 0.8" data-field="qrText" data-kb-placeholder="qr"/>
<rect x="8" y="24" width="72.25" height="8.25" fill="none" stroke="#000000" stroke-width="0.25" stroke-dasharray="1 0.6" data-field="barcodeText" data-kb-placeholder="barcode"/>
</svg>

After

Width:  |  Height:  |  Size: 781 B

4
samples/code.yml Normal file
View File

@@ -0,0 +1,4 @@
itemName: "サンプル備品 001"
model: "KB-002"
qrText: "https://wwww.artif.org"
barcodeText: "KB001"

View File

@@ -27,6 +27,9 @@ static void usage(const char *argv0)
" -n, --dry-run 読み込みと check_raster のみUSB なし)\n"
" -v, --verbose 印刷前情報と印刷後ステータスを標準出力\n"
" -p, --pid HEX USB 製品 ID既定: P900W の 0x2085。例: P750W 0x2062、P710BT 0x20af\n"
" --cut BITS 3bit 指定: [auto-cut][half-cut][chain-print] (例: 010)\n"
" 既定: 011オートカットしない、ハーフカットする、つなげて印刷する\n"
" --debug-dump PATH デバッグ: USB に送った印字データをファイルへ保存1 印刷ごとに上書き)\n"
" -S, --status ステータスを表示して終了\n"
" -V, --version バージョンを表示して終了\n"
" -h, --help このヘルプ\n"
@@ -103,6 +106,25 @@ static int read_file(const char *path, uint8_t **out, size_t *out_len)
return 0;
}
static int parse_cut_bits(const char *s, uint8_t *out_flags)
{
if (!s || strlen(s) != 3u)
return -1;
for (int i = 0; i < 3; i++) {
if (s[i] != '0' && s[i] != '1')
return -1;
}
uint8_t flags = 0u;
if (s[0] == '1')
flags |= LIBPTOUCH_RASTER_FLAG_AUTO_CUT;
if (s[1] == '1')
flags |= LIBPTOUCH_RASTER_FLAG_HALF_CUT;
if (s[2] == '1')
flags |= LIBPTOUCH_RASTER_FLAG_CHAIN_PRINT;
*out_flags = flags;
return 0;
}
static void verbose_print_pre_print_info(libptouch_ctx *ctx,
const libptouch_raster_params_t *params,
size_t data_len)
@@ -115,6 +137,11 @@ static void verbose_print_pre_print_info(libptouch_ctx *ctx,
printf("raster size: %ux%u dots (length x width)\n",
(unsigned)params->width_dots, (unsigned)params->height_dots);
printf("margin_mm: %u\n", (unsigned)params->margin_mm);
printf("raster flags: 0x%02X (auto-cut %s, half-cut %s, chain-print %s)\n",
(unsigned)params->flags,
(params->flags & LIBPTOUCH_RASTER_FLAG_AUTO_CUT) ? "on" : "off",
(params->flags & LIBPTOUCH_RASTER_FLAG_HALF_CUT) ? "on" : "off",
(params->flags & LIBPTOUCH_RASTER_FLAG_CHAIN_PRINT) ? "on" : "off");
libptouch_media_info_t mi;
if (libptouch_get_current_media_info(ctx, &mi) == LIBPTOUCH_OK) {
@@ -124,6 +151,10 @@ static void verbose_print_pre_print_info(libptouch_ctx *ctx,
printf("printable dots: %u\n", (unsigned)mi.printable_dots);
printf("left/right margins: %u/%u dots\n",
(unsigned)mi.left_margin_dots, (unsigned)mi.right_margin_dots);
printf("printer family: %s (%u)\n",
libptouch_printer_family_label(
(libptouch_printer_family_t)mi.printer_family),
(unsigned)mi.printer_family);
printf("print/feed dpi: %.1f/%.1f\n", mi.print_dpi, mi.feed_dpi);
} else {
printf("media info: unavailable (%s)\n", libptouch_strerror(ctx));
@@ -179,6 +210,8 @@ int main(int argc, char **argv)
unsigned trim_right_dots = 0;
int has_threshold = 0;
int want_status = 0;
uint8_t cut_flags = LIBPTOUCH_RASTER_FLAGS_DEFAULT;
const char *debug_dump_path = NULL;
static struct option longopts[] = {
{ "width", required_argument, NULL, 'w' },
{ "height", required_argument, NULL, 'H' },
@@ -191,6 +224,8 @@ int main(int argc, char **argv)
{ "status", no_argument, NULL, 'S' },
{ "version", no_argument, NULL, 'V' },
{ "help", no_argument, NULL, 'h' },
{ "cut", required_argument, NULL, 1000 },
{ "debug-dump", required_argument, NULL, 1001 },
{ NULL, 0, NULL, 0 },
};
@@ -242,6 +277,16 @@ int main(int argc, char **argv)
return 2;
}
break;
case 1000:
if (parse_cut_bits(optarg, &cut_flags) != 0) {
fprintf(stderr,
"--cut must be 3 bits (e.g. 010: auto/half/chain)\n");
return 2;
}
break;
case 1001:
debug_dump_path = optarg;
break;
case 'S':
want_status = 1;
break;
@@ -325,10 +370,12 @@ int main(int argc, char **argv)
fprintf(stderr, "libptouch_create failed\n");
return 1;
}
if (debug_dump_path)
libptouch_set_debug_dump_path(ctx, debug_dump_path);
uint8_t *data = NULL;
size_t data_len = 0;
libptouch_raster_params_t params = { 0, 0, 0 };
libptouch_raster_params_t params = { 0 };
libptouch_err_t e;
int usb_opened = 0;
int data_from_lib = 0;
@@ -402,7 +449,7 @@ int main(int argc, char **argv)
uint8_t *trimmed = NULL;
size_t trimmed_len = 0;
libptouch_raster_params_t trimmed_params = { 0, 0, 0 };
libptouch_raster_params_t trimmed_params = { 0 };
e = libptouch_trim_right_blank_columns(
ctx, data, data_len, &params, pad, &trimmed, &trimmed_len,
&trimmed_params);
@@ -426,6 +473,10 @@ int main(int argc, char **argv)
data_from_lib = 1;
}
params.flags = cut_flags;
params._reserved[0] = 0u;
params._reserved[1] = 0u;
e = libptouch_check_raster(ctx, data, data_len, &params);
if (e != LIBPTOUCH_OK) {
fprintf(stderr, "check_raster: %s\n",

View File

@@ -6,6 +6,7 @@
*/
#include "libptouch_internal.h"
#include "libptouch_family_config.h"
#include <stdio.h>
#include <stdlib.h>
@@ -33,6 +34,8 @@ void ptouch_set_error_usb(libptouch_ctx *ctx, int libusb_err, const char *what)
libptouch_ctx *libptouch_create(void)
{
ptouch_family_config_init_once();
libptouch_ctx *ctx = calloc(1, sizeof(*ctx));
if (!ctx)
return NULL;
@@ -52,9 +55,29 @@ void libptouch_destroy(libptouch_ctx *ctx)
if (!ctx)
return;
libptouch_close(ctx);
free(ctx->debug_dump_path);
free(ctx);
}
void libptouch_set_debug_dump_path(libptouch_ctx *ctx, const char *path)
{
if (!ctx)
return;
free(ctx->debug_dump_path);
ctx->debug_dump_path = NULL;
ctx->debug_dump_truncate_next = 0;
if (!path || !path[0])
return;
ctx->debug_dump_path = strdup(path);
}
void ptouch_debug_dump_begin_print_job(libptouch_ctx *ctx)
{
if (!ctx)
return;
ctx->debug_dump_truncate_next = 1;
}
const char *libptouch_strerror(const libptouch_ctx *ctx)
{
if (!ctx)

View File

@@ -0,0 +1,302 @@
/*
* libptouch — optional JSON overrides for P700/P900 family parameters
*
* 検索順: 環境変数 LIBPTOUCH_CONFIG、カレントの printer_families.json、
* config/printer_families.jsonリポジトリ配置用
*/
#include "libptouch_family_config.h"
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
enum {
OV_PRINT_DPI = 1 << 0,
OV_MARGIN_FEED_DPI = 1 << 1,
OV_MARGIN_MAX = 1 << 2,
OV_ESC_IA = 1 << 3,
};
typedef struct {
unsigned mask;
double print_dpi;
double margin_feed_dpi;
unsigned margin_feed_max_dots;
int send_esc_ia;
} family_override_t;
static family_override_t g_p700;
static family_override_t g_p900;
static int g_loaded;
static const char *skip_ws(const char *p)
{
while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')
p++;
return p;
}
static int read_file_all(const char *path, char **out, size_t *out_len)
{
FILE *fp = fopen(path, "rb");
if (!fp)
return -1;
if (fseek(fp, 0, SEEK_END) != 0) {
fclose(fp);
return -1;
}
long sz = ftell(fp);
if (sz < 0 || sz > 65536) {
fclose(fp);
return -1;
}
rewind(fp);
char *buf = (char *)malloc((size_t)sz + 1u);
if (!buf) {
fclose(fp);
return -1;
}
size_t n = fread(buf, 1, (size_t)sz, fp);
fclose(fp);
buf[n] = '\0';
*out = buf;
*out_len = n;
return 0;
}
static const char *find_quoted_key(const char *json, const char *key)
{
char pat[48];
snprintf(pat, sizeof(pat), "\"%s\"", key);
return strstr(json, pat);
}
static int extract_object_after_key(const char *json, const char *key,
char *out, size_t out_sz)
{
const char *p = find_quoted_key(json, key);
if (!p)
return -1;
p += strlen(key) + 2u;
p = skip_ws(p);
if (*p != ':')
return -1;
p++;
p = skip_ws(p);
if (*p != '{')
return -1;
int depth = 0;
const char *start = p;
for (; *p; p++) {
if (*p == '{')
depth++;
else if (*p == '}') {
depth--;
if (depth == 0) {
size_t n = (size_t)(p - start + 1u);
if (n >= out_sz)
return -1;
memcpy(out, start, n);
out[n] = '\0';
return 0;
}
}
}
return -1;
}
static int parse_bool_val(const char *s, int *out)
{
s = skip_ws(s);
if (strncmp(s, "true", 4) == 0) {
*out = 1;
return 0;
}
if (strncmp(s, "false", 5) == 0) {
*out = 0;
return 0;
}
return -1;
}
static int parse_field_double(const char *obj, const char *key, double *dst)
{
const char *p = find_quoted_key(obj, key);
if (!p)
return -1;
p += strlen(key) + 2u;
p = skip_ws(p);
if (*p != ':')
return -1;
p++;
p = skip_ws(p);
char *end = NULL;
double v = strtod(p, &end);
if (end == p)
return -1;
*dst = v;
return 0;
}
static int parse_field_uint(const char *obj, const char *key, unsigned *dst)
{
const char *p = find_quoted_key(obj, key);
if (!p)
return -1;
p += strlen(key) + 2u;
p = skip_ws(p);
if (*p != ':')
return -1;
p++;
p = skip_ws(p);
char *end = NULL;
unsigned long v = strtoul(p, &end, 10);
if (end == p)
return -1;
*dst = (unsigned)v;
return 0;
}
static int parse_field_bool(const char *obj, const char *key, int *dst)
{
const char *p = find_quoted_key(obj, key);
if (!p)
return -1;
p += strlen(key) + 2u;
p = skip_ws(p);
if (*p != ':')
return -1;
p++;
p = skip_ws(p);
return parse_bool_val(p, dst);
}
static void apply_family_block(const char *block, family_override_t *o)
{
double d;
unsigned u;
int b;
if (parse_field_double(block, "print_dpi", &d) == 0) {
o->print_dpi = d;
o->mask |= OV_PRINT_DPI;
}
if (parse_field_double(block, "margin_feed_dpi", &d) == 0) {
o->margin_feed_dpi = d;
o->mask |= OV_MARGIN_FEED_DPI;
}
if (parse_field_uint(block, "margin_feed_max_dots", &u) == 0) {
o->margin_feed_max_dots = u;
o->mask |= OV_MARGIN_MAX;
}
if (parse_field_bool(block, "send_esc_ia_cut_each", &b) == 0) {
o->send_esc_ia = b;
o->mask |= OV_ESC_IA;
}
}
static void parse_config_json(const char *json)
{
char inner[4096];
const char *families = json;
if (extract_object_after_key(json, "families", inner, sizeof(inner)) == 0)
families = inner;
char block[2048];
if (extract_object_after_key(families, "p700", block, sizeof(block)) == 0)
apply_family_block(block, &g_p700);
if (extract_object_after_key(families, "p900", block, sizeof(block)) == 0)
apply_family_block(block, &g_p900);
}
static void try_load_path(const char *path)
{
char *buf = NULL;
size_t len = 0;
if (read_file_all(path, &buf, &len) != 0)
return;
parse_config_json(buf);
free(buf);
}
void ptouch_family_config_init_once(void)
{
if (g_loaded)
return;
g_loaded = 1;
memset(&g_p700, 0, sizeof(g_p700));
memset(&g_p900, 0, sizeof(g_p900));
const char *env = getenv("LIBPTOUCH_CONFIG");
if (env && env[0])
try_load_path(env);
try_load_path("printer_families.json");
try_load_path("config/printer_families.json");
}
double ptouch_effective_print_dpi(const ptouch_printer_profile_t *prof)
{
if (!prof)
return 0.0;
if (prof->family == LIBPTOUCH_FAMILY_P700 &&
(g_p700.mask & OV_PRINT_DPI))
return g_p700.print_dpi;
if (prof->family == LIBPTOUCH_FAMILY_P900 &&
(g_p900.mask & OV_PRINT_DPI))
return g_p900.print_dpi;
return prof->print_dpi;
}
double ptouch_effective_margin_feed_dpi(const ptouch_printer_profile_t *prof)
{
if (!prof)
return 0.0;
if (prof->family == LIBPTOUCH_FAMILY_P700 &&
(g_p700.mask & OV_MARGIN_FEED_DPI))
return g_p700.margin_feed_dpi;
if (prof->family == LIBPTOUCH_FAMILY_P900 &&
(g_p900.mask & OV_MARGIN_FEED_DPI))
return g_p900.margin_feed_dpi;
return prof->margin_feed_dpi;
}
unsigned ptouch_effective_margin_feed_max_dots(const ptouch_printer_profile_t *prof)
{
if (!prof)
return 0u;
if (prof->family == LIBPTOUCH_FAMILY_P700 &&
(g_p700.mask & OV_MARGIN_MAX))
return g_p700.margin_feed_max_dots;
if (prof->family == LIBPTOUCH_FAMILY_P900 &&
(g_p900.mask & OV_MARGIN_MAX))
return g_p900.margin_feed_max_dots;
return prof->margin_feed_max_dots;
}
int ptouch_effective_send_esc_ia_cut_each(const ptouch_printer_profile_t *prof)
{
if (!prof)
return 0;
if (prof->family == LIBPTOUCH_FAMILY_P700 &&
(g_p700.mask & OV_ESC_IA))
return g_p700.send_esc_ia;
if (prof->family == LIBPTOUCH_FAMILY_P900 &&
(g_p900.mask & OV_ESC_IA))
return g_p900.send_esc_ia;
return (int)prof->send_esc_ia_cut_each;
}
const char *libptouch_printer_family_label(libptouch_printer_family_t family)
{
switch (family) {
case LIBPTOUCH_FAMILY_P700:
return "p700";
case LIBPTOUCH_FAMILY_P900:
return "p900";
default:
return "unknown";
}
}

View File

@@ -0,0 +1,19 @@
/*
* libptouch — printer family overrides (JSON config, optional)
*/
#ifndef LIBPTOUCH_FAMILY_CONFIG_H
#define LIBPTOUCH_FAMILY_CONFIG_H
#include "libptouch_layout.h"
#include <stddef.h>
void ptouch_family_config_init_once(void);
double ptouch_effective_print_dpi(const ptouch_printer_profile_t *prof);
double ptouch_effective_margin_feed_dpi(const ptouch_printer_profile_t *prof);
unsigned ptouch_effective_margin_feed_max_dots(const ptouch_printer_profile_t *prof);
int ptouch_effective_send_esc_ia_cut_each(const ptouch_printer_profile_t *prof);
#endif /* LIBPTOUCH_FAMILY_CONFIG_H */

View File

@@ -23,8 +23,13 @@ struct libptouch_ctx {
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 */
char *debug_dump_path; /* strdup; NULL = 印字データのファイル保存なし */
int debug_dump_truncate_next; /* 次の bulk 書き込みでファイルを切り詰め(ジョブ先頭) */
};
/** 1 回の libptouch_print_raster ジョブの先頭で呼ぶ(最初の bulk 前) */
void ptouch_debug_dump_begin_print_job(libptouch_ctx *ctx);
/* ctx に最終エラー(コードとメッセージ)を記録する。公開 API のエラー返却前に使う。 */
void ptouch_set_error(libptouch_ctx *ctx, libptouch_err_t code, const char *msg);
/* libusb のエラー番号を人が読めるメッセージにして ptouch_set_error に渡す。 */

View File

@@ -61,9 +61,12 @@ static const ptouch_printer_profile_t ptouch_layout_profiles[] = {
.usb_pids = pids_128pin,
.model_codes = models_128pin,
.is_default = 0,
.family = LIBPTOUCH_FAMILY_P700,
.head_width_dots = 128u,
.print_dpi = 180.0,
.margin_feed_dpi = 180.0,
.margin_feed_max_dots = 900u,
.send_esc_ia_cut_each = 0u,
.tze = layout_tze_128,
.tze_count = sizeof(layout_tze_128) / sizeof(layout_tze_128[0]),
.hs = layout_hs_128,
@@ -74,9 +77,12 @@ static const ptouch_printer_profile_t ptouch_layout_profiles[] = {
.usb_pids = NULL,
.model_codes = NULL,
.is_default = 1,
.family = LIBPTOUCH_FAMILY_P900,
.head_width_dots = 560u,
.print_dpi = 360.0,
.margin_feed_dpi = 360.0,
.margin_feed_max_dots = 1800u,
.send_esc_ia_cut_each = 1u,
.tze = layout_tze_560,
.tze_count = sizeof(layout_tze_560) / sizeof(layout_tze_560[0]),
.hs = layout_hs_560,

View File

@@ -29,9 +29,13 @@ 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 */
libptouch_printer_family_t family; /**< P700 / P900 系統(コマンド差の条件分岐の基準) */
unsigned head_width_dots; /* 128 or 560; full raster width for pack/GF payload */
double print_dpi; /* 印字幅方向 DPIPDF・ラスター解像度の目安 */
double margin_feed_dpi; /* ESC i d feed dots per inch (PDF) */
unsigned margin_feed_max_dots;
/** ESC i A各 n 枚でカットを送るかP700 系では送らない) */
unsigned char send_esc_ia_cut_each;
const ptouch_layout_row_t *tze;
size_t tze_count;
const ptouch_layout_row_t *hs;

View File

@@ -6,6 +6,7 @@
*/
#include "libptouch_internal.h"
#include "libptouch_family_config.h"
#include "libptouch_layout.h"
#include <string.h>
@@ -34,15 +35,6 @@ static double tape_width_mm_from_code(uint8_t media_w)
}
}
static double print_dpi_from_profile(const ptouch_printer_profile_t *prof)
{
if (!prof)
return 0.0;
if (prof->head_width_dots <= 128u)
return 180.0;
return 360.0;
}
libptouch_err_t libptouch_get_current_media_info(libptouch_ctx *ctx,
libptouch_media_info_t *out_info)
{
@@ -53,6 +45,7 @@ libptouch_err_t libptouch_get_current_media_info(libptouch_ctx *ctx,
}
memset(out_info, 0, sizeof(*out_info));
ptouch_family_config_init_once();
uint8_t st[LIBPTOUCH_STATUS_LENGTH];
libptouch_err_t v = libptouch_get_status(ctx, st);
@@ -85,15 +78,16 @@ libptouch_err_t libptouch_get_current_media_info(libptouch_ctx *ctx,
out_info->media_width_code = st[10];
out_info->media_kind_code = st[11];
out_info->print_dpi = print_dpi_from_profile(prof);
out_info->feed_dpi = prof->margin_feed_dpi;
out_info->print_dpi = ptouch_effective_print_dpi(prof);
out_info->feed_dpi = ptouch_effective_margin_feed_dpi(prof);
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;
out_info->min_feed_mm = ((double)out_info->min_feed_dots * 25.4) /
ptouch_effective_margin_feed_dpi(prof);
out_info->printer_family = (uint32_t)prof->family;
ptouch_set_error(ctx, LIBPTOUCH_OK, "");
return LIBPTOUCH_OK;

View File

@@ -36,9 +36,8 @@ libptouch_err_t libptouch_png_file_to_raster(libptouch_ctx *ctx, const char *pat
*out_raster = NULL;
*out_raster_bytes = 0;
out_params->width_dots = 0;
out_params->height_dots = 0;
out_params->margin_mm = 0;
memset(out_params, 0, sizeof(*out_params));
out_params->flags = LIBPTOUCH_RASTER_FLAGS_DEFAULT;
FILE *fp = fopen(path, "rb");
if (!fp) {
@@ -157,6 +156,7 @@ libptouch_err_t libptouch_png_file_to_raster(libptouch_ctx *ctx, const char *pat
out_params->width_dots = (uint32_t)width;
out_params->height_dots = (uint32_t)height;
out_params->margin_mm = 0;
out_params->flags = LIBPTOUCH_RASTER_FLAGS_DEFAULT;
*out_raster = out;
*out_raster_bytes = total;
ptouch_set_error(ctx, LIBPTOUCH_OK, "");

View File

@@ -6,6 +6,7 @@
*/
#include "libptouch_internal.h"
#include "libptouch_family_config.h"
#include "libptouch_layout.h"
#include "libptouch_protocol.h"
@@ -76,6 +77,8 @@ libptouch_err_t libptouch_print_raster(libptouch_ctx *ctx,
return LIBPTOUCH_ERR_IO;
}
ptouch_family_config_init_once();
uint8_t st[LIBPTOUCH_STATUS_LENGTH];
v = libptouch_get_status(ctx, st);
if (v != LIBPTOUCH_OK)
@@ -126,8 +129,9 @@ 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;
double margin_dpi = ptouch_effective_margin_feed_dpi(prof);
unsigned margin_max =
ptouch_effective_margin_feed_max_dots(prof);
unsigned margin_dots = 14u;
if (params->margin_mm > 0) {
margin_dots = (unsigned)((double)params->margin_mm * margin_dpi /
@@ -152,26 +156,30 @@ libptouch_err_t libptouch_print_raster(libptouch_ctx *ctx,
pos += sizeof(raster_mode);
uint8_t esc_iz[13];
ptouch_fill_esc_iz(esc_iz, media_kind, media_w, lines);
ptouch_fill_esc_iz(esc_iz, prof, media_w, lines);
memcpy(head + pos, esc_iz, sizeof(esc_iz));
pos += sizeof(esc_iz);
static const uint8_t esc_im[] = { 0x1B, 0x69, 0x4D, 0x40 };
/* ESC i M: bit6 オートカットreference/印字データ.md。既定オフ。 */
uint8_t esc_im_n1 =
(params->flags & LIBPTOUCH_RASTER_FLAG_AUTO_CUT) ? 0x40u : 0x00u;
uint8_t esc_im[] = { 0x1B, 0x69, 0x4D, esc_im_n1 };
memcpy(head + pos, esc_im, sizeof(esc_im));
pos += sizeof(esc_im);
/*
* ESC i A ("cut each n labels") is not supported on PT-P710BT class
* devices. Sending it can trigger a communication error (red LED blink).
* Keep it only for 560-dot family.
* ESC i A ("cut each n labels") — P700 系では送らない(通信エラーになり得る)。
* 既定は系統フラグ、printer_families.json で上書き可。
*/
if (prof->head_width_dots > 128u) {
if (ptouch_effective_send_esc_ia_cut_each(prof)) {
static const uint8_t esc_ia[] = { 0x1B, 0x69, 0x41, 0x01 };
memcpy(head + pos, esc_ia, sizeof(esc_ia));
pos += sizeof(esc_ia);
}
uint8_t esc_ik[] = { 0x1B, 0x69, 0x4B, ptouch_esc_ik_value(head_dots) };
uint8_t esc_ik_n1 =
ptouch_esc_ik_extended_mode(ctx->usb_pid, params->flags);
uint8_t esc_ik[] = { 0x1B, 0x69, 0x4B, esc_ik_n1 };
memcpy(head + pos, esc_ik, sizeof(esc_ik));
pos += sizeof(esc_ik);
@@ -187,6 +195,7 @@ libptouch_err_t libptouch_print_raster(libptouch_ctx *ctx,
memcpy(head + pos, mode_m, sizeof(mode_m));
pos += sizeof(mode_m);
ptouch_debug_dump_begin_print_job(ctx);
v = ptouch_bulk_send_job(ctx, head, pos, "print preamble");
if (v != LIBPTOUCH_OK) {
free(transposed);
@@ -217,21 +226,6 @@ libptouch_err_t libptouch_print_raster(libptouch_ctx *ctx,
}
free(gbuf);
/*
* Some 560-dot family devices are sensitive to end-of-page sequencing.
* Send FF (0x0C) then Control-Z (0x1A) on that family to explicitly
* terminate page and final feed/cut.
*/
if (head_dots > 128u) {
static const uint8_t page_end[] = { 0x0C };
v = ptouch_bulk_send_job(ctx, page_end, sizeof(page_end),
"print page end");
if (v != LIBPTOUCH_OK) {
free(transposed);
return v;
}
}
static const uint8_t print_end[] = { 0x1A };
v = ptouch_bulk_send_job(ctx, print_end, sizeof(print_end), "print end");
if (v != LIBPTOUCH_OK) {

View File

@@ -12,28 +12,46 @@ void ptouch_fill_gf_header(uint8_t out[3], size_t line_payload_bytes)
out[2] = (uint8_t)((line_payload_bytes >> 8) & 0xFFu);
}
void ptouch_fill_esc_iz(uint8_t out[13], uint8_t media_kind, uint8_t media_width,
uint32_t raster_lines)
void ptouch_fill_esc_iz(uint8_t out[13], const ptouch_printer_profile_t *prof,
uint8_t media_width, uint32_t raster_lines)
{
uint8_t page_byte = 0x00u;
if (prof && prof->family == LIBPTOUCH_FAMILY_P900)
page_byte = 0x02u; /* 単一ラスター = 1 ページのみ → 最終ページ */
out[0] = 0x1Bu;
out[1] = 0x69u;
out[2] = 0x7Au;
/* PI flags: enable media/width/length with quality bit for broad compatibility. */
out[3] = 0x8Eu;
out[3] = 0x84u;
/* Use status media-kind byte directly (e.g., 0x01 laminated, 0x03 non-laminate). */
out[4] = media_kind;
out[4] = 0x00u;
out[5] = media_width;
out[6] = 0x00u;
out[7] = (uint8_t)(raster_lines & 0xFFu);
out[8] = (uint8_t)((raster_lines >> 8) & 0xFFu);
out[9] = (uint8_t)((raster_lines >> 16) & 0xFFu);
out[10] = (uint8_t)((raster_lines >> 24) & 0xFFu);
out[11] = 0x00u; /* first page */
out[11] = page_byte;
out[12] = 0x00u; /* fixed */
}
uint8_t ptouch_esc_ik_value(unsigned head_dots)
uint8_t ptouch_esc_ik_extended_mode(uint16_t usb_pid, uint8_t raster_flags)
{
/* Preserve legacy 560-dot behaviour (0x0C), 128-dot uses 0x08. */
return head_dots > 128u ? 0x0Cu : 0x08u;
uint8_t n1 = 0x00u;
int want_half = (raster_flags & LIBPTOUCH_RASTER_FLAG_HALF_CUT) != 0;
int want_chain = (raster_flags & LIBPTOUCH_RASTER_FLAG_CHAIN_PRINT) != 0;
/*
* reference/印字データ.md:
* - bit2: ハーフカット
* - bit3: ChainPrint しない
*/
if (usb_pid == LIBPTOUCH_USB_PID_PTP710BT)
want_half = 0; /* PT-P710BT はハーフカット非対応 */
if (want_half)
n1 |= 0x04u;
if (!want_chain)
n1 |= 0x08u;
return n1;
}

View File

@@ -1,13 +1,20 @@
#ifndef LIBPTOUCH_PROTOCOL_H
#define LIBPTOUCH_PROTOCOL_H
#include "libptouch_layout.h"
#include <stddef.h>
#include <stdint.h>
size_t ptouch_line_payload_bytes(unsigned head_dots);
void ptouch_fill_gf_header(uint8_t out[3], size_t line_payload_bytes);
void ptouch_fill_esc_iz(uint8_t out[13], uint8_t media_kind, uint8_t media_width,
uint32_t raster_lines);
uint8_t ptouch_esc_ik_value(unsigned head_dots);
/**
* ESC i z (13 バイト)。out[11] は prof の系統から決めるP900 の単一ページ印字では 02h、
* P700 は 00h。prof が NULL のときは 00h。
*/
void ptouch_fill_esc_iz(uint8_t out[13], const ptouch_printer_profile_t *prof,
uint8_t media_width, uint32_t raster_lines);
/** ESC i K の n1: flags(オート/ハーフ/チェーン)と機種制約から算出。 */
uint8_t ptouch_esc_ik_extended_mode(uint16_t usb_pid, uint8_t raster_flags);
#endif /* LIBPTOUCH_PROTOCOL_H */

View File

@@ -16,6 +16,7 @@
#include <math.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
libptouch_err_t libptouch_svg_file_to_raster_fit_current_tape(
libptouch_ctx *ctx, const char *path,
@@ -51,9 +52,8 @@ libptouch_err_t libptouch_svg_file_to_raster_fit_current_tape(
*out_raster = NULL;
*out_raster_bytes = 0;
out_params->width_dots = 0;
out_params->height_dots = 0;
out_params->margin_mm = 0;
memset(out_params, 0, sizeof(*out_params));
out_params->flags = LIBPTOUCH_RASTER_FLAGS_DEFAULT;
uint8_t st[LIBPTOUCH_STATUS_LENGTH];
libptouch_err_t v = libptouch_get_status(ctx, st);
@@ -203,6 +203,7 @@ libptouch_err_t libptouch_svg_file_to_raster_fit_current_tape(
out_params->width_dots = out_w;
out_params->height_dots = out_h;
out_params->margin_mm = 0;
out_params->flags = LIBPTOUCH_RASTER_FLAGS_DEFAULT;
*out_raster = out;
*out_raster_bytes = total;
ptouch_set_error(ctx, LIBPTOUCH_OK, "");

View File

@@ -79,6 +79,9 @@ libptouch_err_t libptouch_trim_right_blank_columns(
out_params->width_dots = new_width;
out_params->height_dots = height;
out_params->margin_mm = in_params->margin_mm;
out_params->flags = in_params->flags;
out_params->_reserved[0] = in_params->_reserved[0];
out_params->_reserved[1] = in_params->_reserved[1];
ptouch_set_error(ctx, LIBPTOUCH_OK, "");
return LIBPTOUCH_OK;
}

View File

@@ -194,5 +194,14 @@ libptouch_err_t ptouch_bulk_send_job(libptouch_ctx *ctx, const uint8_t *buf,
ptouch_set_error(ctx, LIBPTOUCH_ERR_USB, msg);
return LIBPTOUCH_ERR_USB;
}
if (ctx->debug_dump_path && ctx->debug_dump_path[0]) {
FILE *fp = fopen(ctx->debug_dump_path,
ctx->debug_dump_truncate_next ? "wb" : "ab");
if (fp) {
(void)fwrite(buf, 1, len, fp);
(void)fclose(fp);
}
ctx->debug_dump_truncate_next = 0;
}
return LIBPTOUCH_OK;
}

View File

@@ -1,8 +1,18 @@
#include "libptouch_protocol.h"
#include "libptouch.h"
#include <stdio.h>
#include <stdlib.h>
static const ptouch_printer_profile_t prof_fixture_p700 = {
.family = LIBPTOUCH_FAMILY_P700,
};
static const ptouch_printer_profile_t prof_fixture_p900 = {
.family = LIBPTOUCH_FAMILY_P900,
};
static int expect_int(const char *name, int got, int want)
{
if (got == want)
@@ -30,20 +40,38 @@ int main(void)
fail |= expect_int("gf70_n2", gf[2], 0x00);
uint8_t iz[13];
ptouch_fill_esc_iz(iz, 0x01u, 0x0Cu, 70u);
ptouch_fill_esc_iz(iz, &prof_fixture_p700, 0x0Cu, 70u);
fail |= expect_int("iz_cmd_0", iz[0], 0x1B);
fail |= expect_int("iz_cmd_1", iz[1], 0x69);
fail |= expect_int("iz_cmd_2", iz[2], 0x7A);
fail |= expect_int("iz_n1_flags", iz[3], 0x8E);
fail |= expect_int("iz_media_kind_passthrough", iz[4], 0x01);
fail |= expect_int("iz_n1_flags", iz[3], 0x84);
fail |= expect_int("iz_media_kind_fixed", iz[4], 0x00);
fail |= expect_int("iz_media_width", iz[5], 0x0C);
fail |= expect_int("iz_lines_lsb", iz[7], 70);
fail |= expect_int("iz_page_index", iz[11], 0x00);
fail |= expect_int("iz_page_control_p700", iz[11], 0x00);
fail |= expect_int("iz_last_fixed", iz[12], 0x00);
/* ESC i K mode byte: 128-dot vs 560-dot families */
fail |= expect_int("esc_ik_128", ptouch_esc_ik_value(128u), 0x08);
fail |= expect_int("esc_ik_560", ptouch_esc_ik_value(560u), 0x0C);
ptouch_fill_esc_iz(iz, &prof_fixture_p900, 0x0Cu, 70u);
fail |= expect_int("iz_page_control_p900_single", iz[11], 0x02);
/* ESC i K: bit2=ハーフカット、bit3=ChainPrintしない */
fail |= expect_int(
"esc_ik_p750w_half_chain",
(int)ptouch_esc_ik_extended_mode(
LIBPTOUCH_USB_PID_PTP750W, LIBPTOUCH_RASTER_FLAGS_DEFAULT),
0x04);
fail |= expect_int(
"esc_ik_p710bt_no_half",
(int)ptouch_esc_ik_extended_mode(
LIBPTOUCH_USB_PID_PTP710BT, LIBPTOUCH_RASTER_FLAGS_DEFAULT),
0x00);
fail |= expect_int(
"esc_ik_p900w_auto_half_no_chain",
(int)ptouch_esc_ik_extended_mode(
LIBPTOUCH_USB_PID_PTP900W,
LIBPTOUCH_RASTER_FLAG_AUTO_CUT |
LIBPTOUCH_RASTER_FLAG_HALF_CUT),
0x0C);
return fail ? EXIT_FAILURE : EXIT_SUCCESS;
}