Merge branch 'cursor/ruby-label-merge-cli'
This commit is contained in:
@@ -27,6 +27,7 @@ set(LIBPTOUCH_SOURCES
|
|||||||
src/lib/libptouch_usb.c
|
src/lib/libptouch_usb.c
|
||||||
src/lib/libptouch_layout.c
|
src/lib/libptouch_layout.c
|
||||||
src/lib/libptouch_media_info.c
|
src/lib/libptouch_media_info.c
|
||||||
|
src/lib/libptouch_trim.c
|
||||||
src/lib/libptouch_print.c
|
src/lib/libptouch_print.c
|
||||||
src/lib/libptouch_status.c
|
src/lib/libptouch_status.c
|
||||||
src/lib/libptouch_png.c
|
src/lib/libptouch_png.c
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ cmake --build build
|
|||||||
|
|
||||||
**PNG**(拡張子 `.png` または PNG シグネチャ)の場合は幅・高さは画像から取得します。
|
**PNG**(拡張子 `.png` または PNG シグネチャ)の場合は幅・高さは画像から取得します。
|
||||||
**SVG**(拡張子 `.svg`)の場合は現在装着中テープの印字可能ドット幅に合わせて自動拡大・縮小します(USB 接続が必要)。
|
**SVG**(拡張子 `.svg`)の場合は現在装着中テープの印字可能ドット幅に合わせて自動拡大・縮小します(USB 接続が必要)。
|
||||||
|
`--trim-right[=DOTS]` で右側空白列を削減できます(DOTS 省略時は左余白ドット、取得失敗時は 0)。
|
||||||
任意で `-t`(0–255)で二値化しきい値を指定できます。
|
任意で `-t`(0–255)で二値化しきい値を指定できます。
|
||||||
|
|
||||||
**1bit packed ラスター**(行優先、行あたり `ceil(width/8)` バイト × 行数)の場合は `-f` に加え `-w` / `-H` が必須です。
|
**1bit packed ラスター**(行優先、行あたり `ceil(width/8)` バイト × 行数)の場合は `-f` に加え `-w` / `-H` が必須です。
|
||||||
@@ -49,6 +50,8 @@ cmake --build build
|
|||||||
```bash
|
```bash
|
||||||
# PNG — 検証のみ(USB 不要)
|
# PNG — 検証のみ(USB 不要)
|
||||||
./build/ptouch-print -n -f label.png
|
./build/ptouch-print -n -f label.png
|
||||||
|
./build/ptouch-print -n -f label.png --trim-right
|
||||||
|
./build/ptouch-print -n -f label.png --trim-right=20
|
||||||
|
|
||||||
# SVG — 幅を現在テープにフィットさせて検証(USB 必要)
|
# SVG — 幅を現在テープにフィットさせて検証(USB 必要)
|
||||||
./build/ptouch-print -n -f label.svg
|
./build/ptouch-print -n -f label.svg
|
||||||
|
|||||||
@@ -100,6 +100,16 @@ libptouch_err_t libptouch_check_raster(libptouch_ctx *ctx,
|
|||||||
libptouch_err_t libptouch_get_current_media_info(libptouch_ctx *ctx,
|
libptouch_err_t libptouch_get_current_media_info(libptouch_ctx *ctx,
|
||||||
libptouch_media_info_t *out_info);
|
libptouch_media_info_t *out_info);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1bit packed ラスターの右側空白列を削減する(USB 不要)。
|
||||||
|
* 入力全体が白の場合は元サイズを維持して返す。
|
||||||
|
*/
|
||||||
|
libptouch_err_t libptouch_trim_right_blank_columns(
|
||||||
|
libptouch_ctx *ctx, const uint8_t *data, size_t data_len,
|
||||||
|
const libptouch_raster_params_t *in_params, uint16_t right_padding_dots,
|
||||||
|
uint8_t **out_raster,
|
||||||
|
size_t *out_raster_bytes, libptouch_raster_params_t *out_params);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 1 ビット packed ラスターを USB で印刷(各機種のラスター PDF 準拠)。
|
* 1 ビット packed ラスターを USB で印刷(各機種のラスター PDF 準拠)。
|
||||||
* 印字前にステータスでテープ幅を読み、印刷可能ドット内に画像を中央配置する。
|
* 印字前にステータスでテープ幅を読み、印刷可能ドット内に画像を中央配置する。
|
||||||
|
|||||||
0
reference/note.adoc
Normal file
0
reference/note.adoc
Normal file
@@ -5,5 +5,5 @@ source "https://rubygems.org"
|
|||||||
gemspec
|
gemspec
|
||||||
|
|
||||||
group :development do
|
group :development do
|
||||||
gem "rubocop", "~> 1.69", require: false
|
gem "rubocop", "~> 1.86", require: false
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ PATH
|
|||||||
specs:
|
specs:
|
||||||
libptouch (1.0.0)
|
libptouch (1.0.0)
|
||||||
ffi (~> 1.15)
|
ffi (~> 1.15)
|
||||||
|
rexml
|
||||||
|
|
||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
@@ -30,6 +31,7 @@ GEM
|
|||||||
racc (1.8.1)
|
racc (1.8.1)
|
||||||
rainbow (3.1.1)
|
rainbow (3.1.1)
|
||||||
regexp_parser (2.12.0)
|
regexp_parser (2.12.0)
|
||||||
|
rexml (3.4.4)
|
||||||
rubocop (1.86.1)
|
rubocop (1.86.1)
|
||||||
json (~> 2.3)
|
json (~> 2.3)
|
||||||
language_server-protocol (~> 3.17.0.2)
|
language_server-protocol (~> 3.17.0.2)
|
||||||
@@ -64,7 +66,7 @@ PLATFORMS
|
|||||||
|
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
libptouch!
|
libptouch!
|
||||||
rubocop (~> 1.69)
|
rubocop (~> 1.86)
|
||||||
|
|
||||||
CHECKSUMS
|
CHECKSUMS
|
||||||
ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383
|
ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383
|
||||||
@@ -89,6 +91,7 @@ CHECKSUMS
|
|||||||
racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f
|
racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f
|
||||||
rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a
|
rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a
|
||||||
regexp_parser (2.12.0) sha256=35a916a1d63190ab5c9009457136ae5f3c0c7512d60291d0d1378ba18ce08ebb
|
regexp_parser (2.12.0) sha256=35a916a1d63190ab5c9009457136ae5f3c0c7512d60291d0d1378ba18ce08ebb
|
||||||
|
rexml (3.4.4) sha256=19e0a2c3425dfbf2d4fc1189747bdb2f849b6c5e74180401b15734bc97b5d142
|
||||||
rubocop (1.86.1) sha256=44415f3f01d01a21e01132248d2fd0867572475b566ca188a0a42133a08d4531
|
rubocop (1.86.1) sha256=44415f3f01d01a21e01132248d2fd0867572475b566ca188a0a42133a08d4531
|
||||||
rubocop-ast (1.49.1) sha256=4412f3ee70f6fe4546cc489548e0f6fcf76cafcfa80fa03af67098ffed755035
|
rubocop-ast (1.49.1) sha256=4412f3ee70f6fe4546cc489548e0f6fcf76cafcfa80fa03af67098ffed755035
|
||||||
ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33
|
ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33
|
||||||
|
|||||||
@@ -31,21 +31,28 @@ export LIBPTOUCH_LIB=/usr/local/lib/libptouch.so
|
|||||||
|
|
||||||
(`cmake --install` で共有ライブラリをインストールした場合は、通常は `libptouch` 名でローダが解決します。)
|
(`cmake --install` で共有ライブラリをインストールした場合は、通常は `libptouch` 名でローダが解決します。)
|
||||||
|
|
||||||
## コマンド `ptouch-print-png`(PNG/SVG)
|
## コマンド `ptouch-label`(PNG/SVG)
|
||||||
|
|
||||||
C の `ptouch-print` と同様の流れで、**PNG/SVG 入力**(`-w`/`-H` や 1bit ラスターは扱いません)を扱います。`gem install` 後は PATH に `ptouch-print-png` が入ります。
|
C の `ptouch-print` と同様の流れで、**PNG/SVG 入力**(`-w`/`-H` や 1bit ラスターは扱いません)を扱います。`gem install` 後は PATH に `ptouch-label` が入ります。
|
||||||
SVG は現在装着テープの印字可能幅に合わせて自動拡大・縮小します(USB 接続が必要)。
|
SVG は現在装着テープの印字可能幅に合わせて自動拡大・縮小します(USB 接続が必要)。
|
||||||
|
後方互換のため `ptouch-print-png` も引き続き使えます。
|
||||||
|
|
||||||
オプションは C 側に合わせ、**`-p` / `--pid`** で USB 製品 ID(16 進可)を指定できます。省略時は PT-P900W(`Libptouch::USB_PID_PTP900W` = `0x2085`)。例: PT-P750W `0x2062`、PT-P710BT `0x20af`(`libptouch.h` / `Libptouch` 定数と同じ)。
|
オプションは C 側に合わせ、**`-p` / `--pid`** で USB 製品 ID(16 進可)を指定できます。省略時は PT-P900W(`Libptouch::USB_PID_PTP900W` = `0x2085`)。例: PT-P750W `0x2062`、PT-P710BT `0x20af`(`libptouch.h` / `Libptouch` 定数と同じ)。
|
||||||
|
また、`--template`(SVG)と `--data`(JSON/YAML)を使うと `data-field` 属性をキーにした差込印刷が可能です。
|
||||||
|
`--trim-right[=DOTS]` を付けると、libptouch 側の共通処理でラベル右側の空白ドット列を削減します。`DOTS` 省略時は左余白ドット数を使い、取得失敗時は `0` にフォールバックします。
|
||||||
|
|
||||||
開発ツリーからそのまま試す例:
|
開発ツリーからそのまま試す例:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bundle exec ruby -I lib exe/ptouch-print-png --help
|
bundle exec ruby -I lib exe/ptouch-label --help
|
||||||
bundle exec ruby -I lib exe/ptouch-print-png -n -f ../samples/your.png
|
bundle exec ruby -I lib exe/ptouch-label -n -f ../samples/your.png
|
||||||
bundle exec ruby -I lib exe/ptouch-print-png -n -f ../samples/your.svg
|
bundle exec ruby -I lib exe/ptouch-label -n -f ../samples/your.svg
|
||||||
bundle exec ruby -I lib exe/ptouch-print-png --status -p 0x2062
|
bundle exec ruby -I lib exe/ptouch-label -n -f ../samples/your.svg --trim-right
|
||||||
bundle exec ruby -I lib exe/ptouch-print-png -f ../samples/your.png -p 0x20af
|
bundle exec ruby -I lib exe/ptouch-label -n -f ../samples/your.svg --trim-right=20
|
||||||
|
bundle exec ruby -I lib exe/ptouch-label -n --template ../samples/your_template.svg --data ../samples/your_data.yml
|
||||||
|
bundle exec ruby -I lib exe/ptouch-label --media-info
|
||||||
|
bundle exec ruby -I lib exe/ptouch-label --status -p 0x2062
|
||||||
|
bundle exec ruby -I lib exe/ptouch-label -f ../samples/your.png -p 0x20af
|
||||||
```
|
```
|
||||||
|
|
||||||
## 使用例
|
## 使用例
|
||||||
@@ -92,9 +99,9 @@ ctx.dispose
|
|||||||
|
|
||||||
## API の範囲
|
## API の範囲
|
||||||
|
|
||||||
- 実行ファイル `ptouch-print-png` … PNG/SVG(`-f`, `-t`, `-p`, `-n`, `-S`, `-V`, `-h`)。ステータスは JSON(`status_bytes` を `parse_status` したもの、`raw_bytes` 除く)
|
- 実行ファイル `ptouch-label`(互換: `ptouch-print-png`)… PNG/SVG(`-f`, `-t`, `-p`, `-n`, `-M`, `-S`, `-V`, `-h`, `--template`, `--data`, `--trim-right[=DOTS]`)。`-M` は現在テープ情報(幅 mm・DPI 等)を JSON 出力、`-S` はステータス JSON 出力、`--trim-right` は右側空白列を削減
|
||||||
- `Libptouch::Context` … `open_usb` / `open_usb_vid_pid` / `close` / `dispose`
|
- `Libptouch::Context` … `open_usb` / `open_usb_vid_pid` / `close` / `dispose`
|
||||||
- `check_raster` / `print_raster` / `png_file_to_raster` / `svg_file_to_raster_fit_current_tape` / `status_bytes` / `status_hash` / `current_media_info`
|
- `check_raster` / `print_raster` / `png_file_to_raster` / `svg_file_to_raster_fit_current_tape` / `status_bytes` / `status_hash` / `current_media_info`
|
||||||
- `current_media_info` には `print_dpi` / `feed_dpi` / `tape_width_mm` / `min_feed_mm` などを含む
|
- `current_media_info` には `print_dpi` / `feed_dpi` / `tape_width_mm` / `printable_height_dots` / `min_feed_mm` などを含む(テープ幅方向は `printable_height_dots`)
|
||||||
- `Libptouch.parse_status(raw)` … 32 バイトを Hash に展開(機種・テープ幅・**テープ種類**・色・**状態(status_kind)**・エラービット・`raw_hex` など)
|
- `Libptouch.parse_status(raw)` … 32 バイトを Hash に展開(機種・テープ幅・**テープ種類**・色・**状態(status_kind)**・エラービット・`raw_hex` など)
|
||||||
- C の `libptouch_status_fprint`(`FILE *`)は FFI からはバインドしていません。テキスト出力の代わりに `parse_status` / `status_hash` を使ってください。
|
- C の `libptouch_status_fprint`(`FILE *`)は FFI からはバインドしていません。テキスト出力の代わりに `parse_status` / `status_hash` を使ってください。
|
||||||
|
|||||||
6
ruby/exe/ptouch-label
Executable file
6
ruby/exe/ptouch-label
Executable file
@@ -0,0 +1,6 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "libptouch/cli/label_print"
|
||||||
|
|
||||||
|
exit Libptouch::Cli::LabelPrint.run(ARGV)
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env ruby
|
#!/usr/bin/env ruby
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require "libptouch/cli/png_print"
|
require "libptouch/cli/label_print"
|
||||||
|
|
||||||
exit Libptouch::Cli::PngPrint.run(ARGV)
|
exit Libptouch::Cli::LabelPrint.run(ARGV)
|
||||||
|
|||||||
@@ -59,6 +59,8 @@ module Libptouch
|
|||||||
attach_function :libptouch_close, [:pointer], :void
|
attach_function :libptouch_close, [:pointer], :void
|
||||||
attach_function :libptouch_check_raster, %i[pointer pointer size_t pointer], :int
|
attach_function :libptouch_check_raster, %i[pointer pointer size_t pointer], :int
|
||||||
attach_function :libptouch_get_current_media_info, %i[pointer pointer], :int
|
attach_function :libptouch_get_current_media_info, %i[pointer pointer], :int
|
||||||
|
attach_function :libptouch_trim_right_blank_columns,
|
||||||
|
%i[pointer pointer size_t pointer uint16 pointer pointer pointer], :int
|
||||||
attach_function :libptouch_print_raster, %i[pointer pointer size_t pointer], :int
|
attach_function :libptouch_print_raster, %i[pointer pointer size_t pointer], :int
|
||||||
attach_function :libptouch_png_file_to_raster,
|
attach_function :libptouch_png_file_to_raster,
|
||||||
%i[pointer string pointer pointer pointer pointer], :int
|
%i[pointer string pointer pointer pointer pointer], :int
|
||||||
|
|||||||
409
ruby/lib/libptouch/cli/label_print.rb
Normal file
409
ruby/lib/libptouch/cli/label_print.rb
Normal file
@@ -0,0 +1,409 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "json"
|
||||||
|
require "optparse"
|
||||||
|
require "rexml/document"
|
||||||
|
require "tempfile"
|
||||||
|
require "yaml"
|
||||||
|
|
||||||
|
require "libptouch"
|
||||||
|
|
||||||
|
module Libptouch
|
||||||
|
module Cli
|
||||||
|
# PNG/SVG を扱う ptouch-print 相当の CLI(1bit ラスター経路なし)。
|
||||||
|
module LabelPrint
|
||||||
|
module_function
|
||||||
|
|
||||||
|
def run(argv)
|
||||||
|
opts = parse(argv)
|
||||||
|
return 2 if opts.nil?
|
||||||
|
|
||||||
|
return run_version if opts[:version]
|
||||||
|
return run_help if opts[:help]
|
||||||
|
|
||||||
|
return run_media_info(opts) if opts[:media_info]
|
||||||
|
|
||||||
|
if opts[:status]
|
||||||
|
warn_unused_file_options(opts)
|
||||||
|
return run_status(opts)
|
||||||
|
end
|
||||||
|
|
||||||
|
if opts[:template] || opts[:data]
|
||||||
|
return usage_error("--template and --data must be used together") if opts[:template].to_s.empty? || opts[:data].to_s.empty?
|
||||||
|
return usage_error("-f and --template/--data cannot be used together") unless opts[:file].to_s.empty?
|
||||||
|
return run_template_print(opts)
|
||||||
|
end
|
||||||
|
|
||||||
|
return usage_error("-f is required (or use --template/--data, or --status)") if opts[:file].to_s.empty?
|
||||||
|
|
||||||
|
path = opts[:file]
|
||||||
|
kind = image_kind(path)
|
||||||
|
return usage_error("not a PNG/SVG file: #{path}") if kind.nil?
|
||||||
|
|
||||||
|
run_print(path, opts, kind)
|
||||||
|
end
|
||||||
|
|
||||||
|
def png_file?(path)
|
||||||
|
return true if path.downcase.end_with?(".png")
|
||||||
|
|
||||||
|
File.open(path, "rb") do |f|
|
||||||
|
sig = f.read(8)
|
||||||
|
sig == "\x89PNG\r\n\x1a\n".b
|
||||||
|
end
|
||||||
|
rescue Errno::ENOENT, Errno::EACCES => e
|
||||||
|
warn "open #{path}: #{e.message}"
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
def svg_file?(path)
|
||||||
|
path.downcase.end_with?(".svg")
|
||||||
|
end
|
||||||
|
|
||||||
|
def image_kind(path)
|
||||||
|
return :png if png_file?(path)
|
||||||
|
return :svg if svg_file?(path)
|
||||||
|
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse(argv)
|
||||||
|
opts_hash = default_cli_opts
|
||||||
|
build_cli_parser(opts_hash).parse!(argv)
|
||||||
|
return nil unless threshold_option_ok?(opts_hash)
|
||||||
|
return nil unless trim_right_option_ok?(opts_hash)
|
||||||
|
return nil unless usb_pid_option_ok?(opts_hash)
|
||||||
|
|
||||||
|
opts_hash.delete(:usb_pid_invalid)
|
||||||
|
opts_hash
|
||||||
|
end
|
||||||
|
|
||||||
|
def default_cli_opts
|
||||||
|
{
|
||||||
|
file: nil,
|
||||||
|
template: nil,
|
||||||
|
data: nil,
|
||||||
|
threshold: nil,
|
||||||
|
usb_pid: nil,
|
||||||
|
usb_pid_invalid: false,
|
||||||
|
dry_run: false,
|
||||||
|
trim_right: nil,
|
||||||
|
media_info: false,
|
||||||
|
status: false,
|
||||||
|
version: false,
|
||||||
|
help: false
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def pid_option_description
|
||||||
|
p900 = Libptouch::USB_PID_PTP900W
|
||||||
|
p750 = Libptouch::USB_PID_PTP750W
|
||||||
|
p710 = Libptouch::USB_PID_PTP710BT
|
||||||
|
"USB 製品 ID(16 進可)。既定 P900W 0x#{p900.to_s(16)}; " \
|
||||||
|
"P750W 0x#{p750.to_s(16)}; P710BT 0x#{p710.to_s(16)}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def apply_usb_pid_option(opts_hash, pid_str)
|
||||||
|
opts_hash[:usb_pid] = Integer(pid_str, 0)
|
||||||
|
rescue ArgumentError
|
||||||
|
warn "invalid --pid: #{pid_str.inspect}"
|
||||||
|
opts_hash[:usb_pid_invalid] = true
|
||||||
|
end
|
||||||
|
|
||||||
|
def threshold_option_ok?(opts_hash)
|
||||||
|
return true if opts_hash[:threshold].nil? || (0..255).cover?(opts_hash[:threshold])
|
||||||
|
|
||||||
|
warn "-t must be 0..255"
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
def trim_right_option_ok?(opts_hash)
|
||||||
|
v = opts_hash[:trim_right]
|
||||||
|
return true if v.nil? || v == :auto
|
||||||
|
return true if v.is_a?(Integer) && v >= 0
|
||||||
|
|
||||||
|
warn "--trim-right must be omitted, or >= 0"
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
def usb_pid_option_ok?(opts_hash)
|
||||||
|
return false if opts_hash[:usb_pid_invalid]
|
||||||
|
return true if opts_hash[:usb_pid].nil?
|
||||||
|
return true if opts_hash[:usb_pid].between?(1, 0xFFFF)
|
||||||
|
|
||||||
|
warn "-p/--pid must be 1..0xFFFF"
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_cli_parser(opts_hash)
|
||||||
|
OptionParser.new do |p|
|
||||||
|
p.banner = usage_banner
|
||||||
|
p.separator ""
|
||||||
|
p.on("-f", "--file PATH", "入力 PNG/SVG ファイル") { |v| opts_hash[:file] = v }
|
||||||
|
p.on("--template PATH", "差込用 SVG テンプレート") { |v| opts_hash[:template] = v }
|
||||||
|
p.on("--data PATH", "差込データ JSON/YAML ファイル") { |v| opts_hash[:data] = v }
|
||||||
|
p.on("-t", "--threshold N", Integer,
|
||||||
|
"しきい値 0–255(既定 #{Libptouch::PNG_DEFAULT_THRESHOLD}、PNG/SVG)") do |v|
|
||||||
|
opts_hash[:threshold] = v
|
||||||
|
end
|
||||||
|
p.on("-p", "--pid PID", pid_option_description) do |v|
|
||||||
|
apply_usb_pid_option(opts_hash, v)
|
||||||
|
end
|
||||||
|
p.on("-n", "--dry-run", "読み込みと検証のみ(USB なし)") { opts_hash[:dry_run] = true }
|
||||||
|
p.on("--trim-right[=DOTS]", Integer,
|
||||||
|
"右側空白を削減。DOTS省略時は左余白(失敗時 0)") do |v|
|
||||||
|
opts_hash[:trim_right] = v.nil? ? :auto : v
|
||||||
|
end
|
||||||
|
p.on("-M", "--media-info", "現在テープ情報(幅/DPI/余白)を JSON で表示して終了") do
|
||||||
|
opts_hash[:media_info] = true
|
||||||
|
end
|
||||||
|
p.on("-S", "--status", "ステータスを JSON で表示して終了") do
|
||||||
|
opts_hash[:status] = true
|
||||||
|
end
|
||||||
|
p.on("-V", "--version", "バージョンを表示して終了") { opts_hash[:version] = true }
|
||||||
|
p.on("-h", "--help", "このヘルプ") { opts_hash[:help] = true }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def usage_banner
|
||||||
|
<<~BANNER
|
||||||
|
Usage: ptouch-label [options]
|
||||||
|
|
||||||
|
PNG/SVG 対応。画像サイズを使用します(-w/-H はありません)。
|
||||||
|
--template/--data で SVG 差込印刷ができます。
|
||||||
|
SVG は現在テープ幅に自動フィットします(USB 必須)。
|
||||||
|
--trim-right[=DOTS] で右側空白を削減します(省略時は左余白基準)。
|
||||||
|
--status / --media-info のときは -f は不要です。
|
||||||
|
BANNER
|
||||||
|
end
|
||||||
|
|
||||||
|
def warn_unused_file_options(opts)
|
||||||
|
return unless opts[:file] || opts[:template] || opts[:data] || opts[:dry_run] || !opts[:trim_right].nil? || !opts[:threshold].nil?
|
||||||
|
|
||||||
|
warn "warning: options other than --status are ignored"
|
||||||
|
end
|
||||||
|
|
||||||
|
def run_version
|
||||||
|
puts "ptouch-label #{Libptouch::VERSION}"
|
||||||
|
0
|
||||||
|
end
|
||||||
|
|
||||||
|
def run_help
|
||||||
|
puts usage_banner
|
||||||
|
puts ""
|
||||||
|
puts parser_help_text
|
||||||
|
0
|
||||||
|
end
|
||||||
|
|
||||||
|
def parser_help_text
|
||||||
|
opts_hash = default_cli_opts
|
||||||
|
p = OptionParser.new do |parser|
|
||||||
|
parser.banner = "ptouch-label [options]"
|
||||||
|
parser.on("-f", "--file PATH", "入力 PNG/SVG ファイル") { |v| opts_hash[:file] = v }
|
||||||
|
parser.on("--template PATH", "差込用 SVG テンプレート") { |v| opts_hash[:template] = v }
|
||||||
|
parser.on("--data PATH", "差込データ JSON/YAML ファイル") { |v| opts_hash[:data] = v }
|
||||||
|
parser.on("-t", "--threshold N", Integer,
|
||||||
|
"しきい値 0–255(既定 #{Libptouch::PNG_DEFAULT_THRESHOLD}、PNG/SVG)") do |v|
|
||||||
|
opts_hash[:threshold] = v
|
||||||
|
end
|
||||||
|
parser.on("-p", "--pid PID", pid_option_description) do |v|
|
||||||
|
apply_usb_pid_option(opts_hash, v)
|
||||||
|
end
|
||||||
|
parser.on("-n", "--dry-run", "読み込みと検証のみ(USB なし)") { opts_hash[:dry_run] = true }
|
||||||
|
parser.on("--trim-right[=DOTS]", Integer,
|
||||||
|
"右側空白を削減。DOTS省略時は左余白(失敗時 0)") do |v|
|
||||||
|
opts_hash[:trim_right] = v.nil? ? :auto : v
|
||||||
|
end
|
||||||
|
parser.on("-M", "--media-info", "現在テープ情報(幅/DPI/余白)を JSON で表示して終了") do
|
||||||
|
opts_hash[:media_info] = true
|
||||||
|
end
|
||||||
|
parser.on("-S", "--status", "ステータスを JSON で表示して終了") do
|
||||||
|
opts_hash[:status] = true
|
||||||
|
end
|
||||||
|
parser.on("-V", "--version", "バージョンを表示して終了") { opts_hash[:version] = true }
|
||||||
|
parser.on("-h", "--help", "このヘルプ") { opts_hash[:help] = true }
|
||||||
|
end
|
||||||
|
p.help
|
||||||
|
end
|
||||||
|
|
||||||
|
def open_usb_for_opts(ctx, opts)
|
||||||
|
if opts[:usb_pid]
|
||||||
|
ctx.open_usb_vid_pid(Libptouch::USB_VID_BROTHER, opts[:usb_pid])
|
||||||
|
else
|
||||||
|
ctx.open_usb
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def run_template_print(opts)
|
||||||
|
svg_text = render_svg_template(opts[:template], load_merge_data(opts[:data]))
|
||||||
|
Tempfile.create(["ptouch-merge-", ".svg"]) do |tmp|
|
||||||
|
tmp.binmode
|
||||||
|
tmp.write(svg_text)
|
||||||
|
tmp.flush
|
||||||
|
run_print(tmp.path, opts, :svg)
|
||||||
|
end
|
||||||
|
rescue REXML::ParseException => e
|
||||||
|
warn "template parse error: #{e.message}"
|
||||||
|
1
|
||||||
|
rescue Errno::ENOENT, Errno::EACCES => e
|
||||||
|
warn e.message
|
||||||
|
1
|
||||||
|
rescue JSON::ParserError, Psych::SyntaxError => e
|
||||||
|
warn "data parse error: #{e.message}"
|
||||||
|
1
|
||||||
|
rescue ArgumentError => e
|
||||||
|
warn "data error: #{e.message}"
|
||||||
|
1
|
||||||
|
end
|
||||||
|
|
||||||
|
def load_merge_data(path)
|
||||||
|
text = File.read(path, encoding: "UTF-8")
|
||||||
|
ext = File.extname(path).downcase
|
||||||
|
parsed = if ext == ".json"
|
||||||
|
JSON.parse(text)
|
||||||
|
else
|
||||||
|
YAML.safe_load(text, permitted_classes: [], aliases: false)
|
||||||
|
end
|
||||||
|
unless parsed.is_a?(Hash)
|
||||||
|
raise ArgumentError, "data must be a key-value object/hash"
|
||||||
|
end
|
||||||
|
|
||||||
|
parsed.transform_keys(&:to_s)
|
||||||
|
end
|
||||||
|
|
||||||
|
def render_svg_template(path, data)
|
||||||
|
xml = File.read(path, encoding: "UTF-8")
|
||||||
|
doc = REXML::Document.new(xml)
|
||||||
|
doc.elements.each("//text[@data-field]") do |el|
|
||||||
|
# If descendants also have data-field (e.g. tspan placeholders),
|
||||||
|
# keep node structure and let element-level replacements handle them.
|
||||||
|
next if el.elements[".//*[@data-field]"]
|
||||||
|
|
||||||
|
key = el.attributes["data-field"].to_s
|
||||||
|
next unless data.key?(key)
|
||||||
|
|
||||||
|
replace_text_element_content(el, data[key].to_s)
|
||||||
|
end
|
||||||
|
doc.elements.each("//tspan[@data-field]") do |el|
|
||||||
|
key = el.attributes["data-field"].to_s
|
||||||
|
next unless data.key?(key)
|
||||||
|
|
||||||
|
replace_text_element_content(el, data[key].to_s)
|
||||||
|
end
|
||||||
|
out = +""
|
||||||
|
formatter = REXML::Formatters::Default.new
|
||||||
|
formatter.write(doc, out)
|
||||||
|
out
|
||||||
|
end
|
||||||
|
|
||||||
|
def replace_text_element_content(text_element, value)
|
||||||
|
# Remove all child nodes first so mixed content (<tspan>, text nodes, etc.)
|
||||||
|
# gets replaced consistently by merge data.
|
||||||
|
text_element.children.to_a.each { |child| text_element.delete(child) }
|
||||||
|
text_element.add(REXML::Text.new(value, true))
|
||||||
|
end
|
||||||
|
|
||||||
|
def run_status(opts)
|
||||||
|
ctx = nil
|
||||||
|
begin
|
||||||
|
ctx = Libptouch::Context.new
|
||||||
|
open_usb_for_opts(ctx, opts)
|
||||||
|
h = Libptouch.parse_status(ctx.status_bytes)
|
||||||
|
h.delete(:raw_bytes)
|
||||||
|
puts JSON.pretty_generate(h)
|
||||||
|
0
|
||||||
|
rescue Libptouch::Error => e
|
||||||
|
warn "get_status: #{e.message}"
|
||||||
|
1
|
||||||
|
ensure
|
||||||
|
ctx&.dispose
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def run_media_info(opts)
|
||||||
|
warn_unused_file_options(opts)
|
||||||
|
ctx = nil
|
||||||
|
begin
|
||||||
|
ctx = Libptouch::Context.new
|
||||||
|
open_usb_for_opts(ctx, opts)
|
||||||
|
puts JSON.pretty_generate(ctx.current_media_info)
|
||||||
|
0
|
||||||
|
rescue Libptouch::Error => e
|
||||||
|
warn "media_info: #{e.message}"
|
||||||
|
1
|
||||||
|
ensure
|
||||||
|
ctx&.dispose
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def run_print(path, opts, kind)
|
||||||
|
ctx = nil
|
||||||
|
begin
|
||||||
|
ctx = Libptouch::Context.new
|
||||||
|
usb_opened = false
|
||||||
|
threshold = opts[:threshold]
|
||||||
|
data, width, height = if kind == :svg
|
||||||
|
open_usb_for_opts(ctx, opts)
|
||||||
|
usb_opened = true
|
||||||
|
if threshold.nil?
|
||||||
|
ctx.svg_file_to_raster_fit_current_tape(path)
|
||||||
|
else
|
||||||
|
ctx.svg_file_to_raster_fit_current_tape(path, threshold: threshold)
|
||||||
|
end
|
||||||
|
elsif threshold.nil?
|
||||||
|
ctx.png_file_to_raster(path)
|
||||||
|
else
|
||||||
|
ctx.png_file_to_raster(path, threshold: threshold)
|
||||||
|
end
|
||||||
|
|
||||||
|
unless opts[:trim_right].nil?
|
||||||
|
trim_pad, usb_opened = resolve_trim_right_pad_dots(ctx, opts, usb_opened)
|
||||||
|
data, width, height = ctx.trim_right_blank_columns(
|
||||||
|
data,
|
||||||
|
width_dots: width,
|
||||||
|
height_dots: height,
|
||||||
|
right_padding_dots: trim_pad
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
ctx.check_raster(data, width_dots: width, height_dots: height)
|
||||||
|
|
||||||
|
if opts[:dry_run]
|
||||||
|
puts "dry-run OK: #{data.bytesize} bytes, src #{width}x#{height} dots (print lengthxwidth #{width}x#{height})"
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
open_usb_for_opts(ctx, opts) if kind == :png && !usb_opened
|
||||||
|
ctx.print_raster(data, width_dots: width, height_dots: height)
|
||||||
|
0
|
||||||
|
rescue Libptouch::Error => e
|
||||||
|
warn e.message
|
||||||
|
1
|
||||||
|
ensure
|
||||||
|
ctx&.dispose
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def resolve_trim_right_pad_dots(ctx, opts, usb_opened)
|
||||||
|
trim = opts[:trim_right]
|
||||||
|
return [trim, usb_opened] if trim.is_a?(Integer)
|
||||||
|
|
||||||
|
begin
|
||||||
|
unless usb_opened
|
||||||
|
open_usb_for_opts(ctx, opts)
|
||||||
|
usb_opened = true
|
||||||
|
end
|
||||||
|
info = ctx.current_media_info
|
||||||
|
[Integer(info[:left_margin_dots] || 0), usb_opened]
|
||||||
|
rescue Libptouch::Error
|
||||||
|
[0, usb_opened]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def usage_error(msg)
|
||||||
|
warn msg
|
||||||
|
warn "(try ptouch-label --help)"
|
||||||
|
2
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,251 +1,8 @@
|
|||||||
# frozen_string_literal: true
|
# Backward-compatibility shim.
|
||||||
|
require "libptouch/cli/label_print"
|
||||||
require "json"
|
|
||||||
require "optparse"
|
|
||||||
|
|
||||||
require "libptouch"
|
|
||||||
|
|
||||||
module Libptouch
|
module Libptouch
|
||||||
module Cli
|
module Cli
|
||||||
# PNG/SVG を扱う ptouch-print 相当の CLI(1bit ラスター経路なし)。
|
PngPrint = LabelPrint
|
||||||
module PngPrint
|
|
||||||
module_function
|
|
||||||
|
|
||||||
def run(argv)
|
|
||||||
opts = parse(argv)
|
|
||||||
return 2 if opts.nil?
|
|
||||||
|
|
||||||
return run_version if opts[:version]
|
|
||||||
return run_help if opts[:help]
|
|
||||||
|
|
||||||
if opts[:status]
|
|
||||||
warn_unused_file_options(opts)
|
|
||||||
return run_status(opts)
|
|
||||||
end
|
|
||||||
|
|
||||||
return usage_error("-f is required (or use --status)") if opts[:file].to_s.empty?
|
|
||||||
|
|
||||||
path = opts[:file]
|
|
||||||
kind = image_kind(path)
|
|
||||||
return usage_error("not a PNG/SVG file: #{path}") if kind.nil?
|
|
||||||
|
|
||||||
run_print(path, opts, kind)
|
|
||||||
end
|
|
||||||
|
|
||||||
def png_file?(path)
|
|
||||||
return true if path.downcase.end_with?(".png")
|
|
||||||
|
|
||||||
File.open(path, "rb") do |f|
|
|
||||||
sig = f.read(8)
|
|
||||||
sig == "\x89PNG\r\n\x1a\n".b
|
|
||||||
end
|
|
||||||
rescue Errno::ENOENT, Errno::EACCES => e
|
|
||||||
warn "open #{path}: #{e.message}"
|
|
||||||
false
|
|
||||||
end
|
|
||||||
|
|
||||||
def svg_file?(path)
|
|
||||||
path.downcase.end_with?(".svg")
|
|
||||||
end
|
|
||||||
|
|
||||||
def image_kind(path)
|
|
||||||
return :png if png_file?(path)
|
|
||||||
return :svg if svg_file?(path)
|
|
||||||
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse(argv)
|
|
||||||
opts_hash = default_cli_opts
|
|
||||||
build_cli_parser(opts_hash).parse!(argv)
|
|
||||||
return nil unless threshold_option_ok?(opts_hash)
|
|
||||||
return nil unless usb_pid_option_ok?(opts_hash)
|
|
||||||
|
|
||||||
opts_hash.delete(:usb_pid_invalid)
|
|
||||||
opts_hash
|
|
||||||
end
|
|
||||||
|
|
||||||
def default_cli_opts
|
|
||||||
{
|
|
||||||
file: nil,
|
|
||||||
threshold: nil,
|
|
||||||
usb_pid: nil,
|
|
||||||
usb_pid_invalid: false,
|
|
||||||
dry_run: false,
|
|
||||||
status: false,
|
|
||||||
version: false,
|
|
||||||
help: false
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def pid_option_description
|
|
||||||
p900 = Libptouch::USB_PID_PTP900W
|
|
||||||
p750 = Libptouch::USB_PID_PTP750W
|
|
||||||
p710 = Libptouch::USB_PID_PTP710BT
|
|
||||||
"USB 製品 ID(16 進可)。既定 P900W 0x#{p900.to_s(16)}; " \
|
|
||||||
"P750W 0x#{p750.to_s(16)}; P710BT 0x#{p710.to_s(16)}"
|
|
||||||
end
|
|
||||||
|
|
||||||
def apply_usb_pid_option(opts_hash, pid_str)
|
|
||||||
opts_hash[:usb_pid] = Integer(pid_str, 0)
|
|
||||||
rescue ArgumentError
|
|
||||||
warn "invalid --pid: #{pid_str.inspect}"
|
|
||||||
opts_hash[:usb_pid_invalid] = true
|
|
||||||
end
|
|
||||||
|
|
||||||
def threshold_option_ok?(opts_hash)
|
|
||||||
return true if opts_hash[:threshold].nil? || (0..255).cover?(opts_hash[:threshold])
|
|
||||||
|
|
||||||
warn "-t must be 0..255"
|
|
||||||
false
|
|
||||||
end
|
|
||||||
|
|
||||||
def usb_pid_option_ok?(opts_hash)
|
|
||||||
return false if opts_hash[:usb_pid_invalid]
|
|
||||||
return true if opts_hash[:usb_pid].nil?
|
|
||||||
return true if opts_hash[:usb_pid].between?(1, 0xFFFF)
|
|
||||||
|
|
||||||
warn "-p/--pid must be 1..0xFFFF"
|
|
||||||
false
|
|
||||||
end
|
|
||||||
|
|
||||||
def build_cli_parser(opts_hash)
|
|
||||||
OptionParser.new do |p|
|
|
||||||
p.banner = usage_banner
|
|
||||||
p.separator ""
|
|
||||||
p.on("-f", "--file PATH", "入力 PNG/SVG ファイル") { |v| opts_hash[:file] = v }
|
|
||||||
p.on("-t", "--threshold N", Integer,
|
|
||||||
"二値化しきい値 0–255(既定 #{Libptouch::PNG_DEFAULT_THRESHOLD}、PNG/SVG)") do |v|
|
|
||||||
opts_hash[:threshold] = v
|
|
||||||
end
|
|
||||||
p.on("-p", "--pid PID", pid_option_description) do |v|
|
|
||||||
apply_usb_pid_option(opts_hash, v)
|
|
||||||
end
|
|
||||||
p.on("-n", "--dry-run", "読み込みと検証のみ(USB なし)") { opts_hash[:dry_run] = true }
|
|
||||||
p.on("-S", "--status", "USB プリンタのステータスを JSON で表示して終了") do
|
|
||||||
opts_hash[:status] = true
|
|
||||||
end
|
|
||||||
p.on("-V", "--version", "バージョンを表示して終了") { opts_hash[:version] = true }
|
|
||||||
p.on("-h", "--help", "このヘルプ") { opts_hash[:help] = true }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def usage_banner
|
|
||||||
<<~BANNER
|
|
||||||
Usage: ptouch-print-png [options]
|
|
||||||
|
|
||||||
PNG/SVG 対応。幅・高さは画像から取得します(-w/-H はありません)。
|
|
||||||
SVG は現在テープ幅に合わせて自動拡大・縮小します(USB 接続必須)。
|
|
||||||
--status のときは -f は不要です。
|
|
||||||
BANNER
|
|
||||||
end
|
|
||||||
|
|
||||||
def warn_unused_file_options(opts)
|
|
||||||
return unless opts[:file] || opts[:dry_run] || !opts[:threshold].nil?
|
|
||||||
|
|
||||||
warn "warning: options other than --status are ignored"
|
|
||||||
end
|
|
||||||
|
|
||||||
def run_version
|
|
||||||
puts "ptouch-print-png #{Libptouch::VERSION}"
|
|
||||||
0
|
|
||||||
end
|
|
||||||
|
|
||||||
def run_help
|
|
||||||
puts usage_banner
|
|
||||||
puts ""
|
|
||||||
puts parser_help_text
|
|
||||||
0
|
|
||||||
end
|
|
||||||
|
|
||||||
def parser_help_text
|
|
||||||
opts_hash = default_cli_opts
|
|
||||||
p = OptionParser.new do |parser|
|
|
||||||
parser.banner = "ptouch-print-png [options]"
|
|
||||||
parser.on("-f", "--file PATH", "入力 PNG/SVG ファイル") { |v| opts_hash[:file] = v }
|
|
||||||
parser.on("-t", "--threshold N", Integer,
|
|
||||||
"二値化しきい値 0–255(既定 #{Libptouch::PNG_DEFAULT_THRESHOLD}、PNG/SVG)") do |v|
|
|
||||||
opts_hash[:threshold] = v
|
|
||||||
end
|
|
||||||
parser.on("-p", "--pid PID", pid_option_description) do |v|
|
|
||||||
apply_usb_pid_option(opts_hash, v)
|
|
||||||
end
|
|
||||||
parser.on("-n", "--dry-run", "読み込みと検証のみ(USB なし)") { opts_hash[:dry_run] = true }
|
|
||||||
parser.on("-S", "--status", "USB プリンタのステータスを JSON で表示して終了") do
|
|
||||||
opts_hash[:status] = true
|
|
||||||
end
|
|
||||||
parser.on("-V", "--version", "バージョンを表示して終了") { opts_hash[:version] = true }
|
|
||||||
parser.on("-h", "--help", "このヘルプ") { opts_hash[:help] = true }
|
|
||||||
end
|
|
||||||
p.help
|
|
||||||
end
|
|
||||||
|
|
||||||
def open_usb_for_opts(ctx, opts)
|
|
||||||
if opts[:usb_pid]
|
|
||||||
ctx.open_usb_vid_pid(Libptouch::USB_VID_BROTHER, opts[:usb_pid])
|
|
||||||
else
|
|
||||||
ctx.open_usb
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def run_status(opts)
|
|
||||||
ctx = nil
|
|
||||||
begin
|
|
||||||
ctx = Libptouch::Context.new
|
|
||||||
open_usb_for_opts(ctx, opts)
|
|
||||||
h = Libptouch.parse_status(ctx.status_bytes)
|
|
||||||
h.delete(:raw_bytes)
|
|
||||||
puts JSON.pretty_generate(h)
|
|
||||||
0
|
|
||||||
rescue Libptouch::Error => e
|
|
||||||
warn "get_status: #{e.message}"
|
|
||||||
1
|
|
||||||
ensure
|
|
||||||
ctx&.dispose
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def run_print(path, opts, kind)
|
|
||||||
ctx = nil
|
|
||||||
begin
|
|
||||||
ctx = Libptouch::Context.new
|
|
||||||
threshold = opts[:threshold]
|
|
||||||
data, width, height = if kind == :svg
|
|
||||||
open_usb_for_opts(ctx, opts)
|
|
||||||
if threshold.nil?
|
|
||||||
ctx.svg_file_to_raster_fit_current_tape(path)
|
|
||||||
else
|
|
||||||
ctx.svg_file_to_raster_fit_current_tape(path, threshold: threshold)
|
|
||||||
end
|
|
||||||
elsif threshold.nil?
|
|
||||||
ctx.png_file_to_raster(path)
|
|
||||||
else
|
|
||||||
ctx.png_file_to_raster(path, threshold: threshold)
|
|
||||||
end
|
|
||||||
|
|
||||||
ctx.check_raster(data, width_dots: width, height_dots: height)
|
|
||||||
|
|
||||||
if opts[:dry_run]
|
|
||||||
puts "dry-run OK: #{data.bytesize} bytes, src #{width}x#{height} dots (print lengthxwidth #{width}x#{height})"
|
|
||||||
return 0
|
|
||||||
end
|
|
||||||
|
|
||||||
open_usb_for_opts(ctx, opts) if kind == :png
|
|
||||||
ctx.print_raster(data, width_dots: width, height_dots: height)
|
|
||||||
0
|
|
||||||
rescue Libptouch::Error => e
|
|
||||||
warn e.message
|
|
||||||
1
|
|
||||||
ensure
|
|
||||||
ctx&.dispose
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def usage_error(msg)
|
|
||||||
warn msg
|
|
||||||
warn "(try ptouch-print-png --help)"
|
|
||||||
2
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -115,6 +115,29 @@ module Libptouch
|
|||||||
[bytes, out_params[:width_dots], out_params[:height_dots]]
|
[bytes, out_params[:width_dots], out_params[:height_dots]]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def trim_right_blank_columns(data, width_dots:, height_dots:, margin_mm: 0, right_padding_dots: 0)
|
||||||
|
in_params = Binding::RasterParams.new
|
||||||
|
in_params[:width_dots] = width_dots
|
||||||
|
in_params[:height_dots] = height_dots
|
||||||
|
in_params[:margin_mm] = margin_mm
|
||||||
|
in_buf = FFI::MemoryPointer.new(:uint8, data.bytesize)
|
||||||
|
in_buf.put_bytes(0, data)
|
||||||
|
out_pp = FFI::MemoryPointer.new(:pointer)
|
||||||
|
out_len = FFI::MemoryPointer.new(:size_t)
|
||||||
|
out_params = Binding::RasterParams.new
|
||||||
|
raise_on_error(Binding.libptouch_trim_right_blank_columns(
|
||||||
|
@native, in_buf, data.bytesize, in_params.pointer,
|
||||||
|
right_padding_dots, out_pp, out_len, out_params.pointer
|
||||||
|
))
|
||||||
|
raw = out_pp.read_pointer
|
||||||
|
raise Libptouch::Error.new(OK, "null raster from trim_right_blank_columns") if raw.null?
|
||||||
|
|
||||||
|
len = out_len.get(:size_t, 0)
|
||||||
|
bytes = raw.read_bytes(len)
|
||||||
|
Binding.libptouch_free_raster(raw)
|
||||||
|
[bytes, out_params[:width_dots], out_params[:height_dots]]
|
||||||
|
end
|
||||||
|
|
||||||
def status_bytes
|
def status_bytes
|
||||||
buf = FFI::MemoryPointer.new(:uint8, STATUS_LENGTH)
|
buf = FFI::MemoryPointer.new(:uint8, STATUS_LENGTH)
|
||||||
raise_on_error(Binding.libptouch_get_status(@native, buf))
|
raise_on_error(Binding.libptouch_get_status(@native, buf))
|
||||||
@@ -134,7 +157,7 @@ module Libptouch
|
|||||||
print_dpi: info[:print_dpi],
|
print_dpi: info[:print_dpi],
|
||||||
feed_dpi: info[:feed_dpi],
|
feed_dpi: info[:feed_dpi],
|
||||||
tape_width_mm: info[:tape_width_mm],
|
tape_width_mm: info[:tape_width_mm],
|
||||||
printable_dots: info[:printable_dots],
|
printable_height_dots: info[:printable_dots],
|
||||||
left_margin_dots: info[:left_margin_dots],
|
left_margin_dots: info[:left_margin_dots],
|
||||||
right_margin_dots: info[:right_margin_dots],
|
right_margin_dots: info[:right_margin_dots],
|
||||||
min_feed_dots: info[:min_feed_dots],
|
min_feed_dots: info[:min_feed_dots],
|
||||||
|
|||||||
@@ -25,8 +25,9 @@ Gem::Specification.new do |spec|
|
|||||||
|
|
||||||
spec.files = Dir.chdir(__dir__) { Dir["lib/**/*.rb", "exe/*", "README.md"] }
|
spec.files = Dir.chdir(__dir__) { Dir["lib/**/*.rb", "exe/*", "README.md"] }
|
||||||
spec.bindir = "exe"
|
spec.bindir = "exe"
|
||||||
spec.executables = ["ptouch-print-png"]
|
spec.executables = ["ptouch-label", "ptouch-print-png"]
|
||||||
spec.require_paths = ["lib"]
|
spec.require_paths = ["lib"]
|
||||||
|
|
||||||
spec.add_dependency "ffi", "~> 1.15"
|
spec.add_dependency "ffi", "~> 1.15"
|
||||||
|
spec.add_dependency "rexml"
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,9 +1,16 @@
|
|||||||
# samples
|
# samples
|
||||||
|
|
||||||
試験・デモ用のサンプル画像(PNG など)を置くディレクトリです。リポジトリに含める場合はライセンス・著作権に注意してください。
|
試験・デモ用のサンプル画像(PNG/SVG)や差込印刷データ(JSON/YAML)を置くディレクトリです。リポジトリに含める場合はライセンス・著作権に注意してください。
|
||||||
|
|
||||||
例(ドライラン):
|
例(ドライラン):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./build/ptouch-print -n -f samples/your.png
|
./build/ptouch-print -n -f samples/your.png
|
||||||
```
|
```
|
||||||
|
|
||||||
|
差込印刷の例(Ruby CLI):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ruby -I ruby/lib ruby/exe/ptouch-label -n --template samples/merge_template.svg --data samples/merge_data.yml
|
||||||
|
ruby -I ruby/lib ruby/exe/ptouch-label -n --template samples/merge_template.svg --data samples/merge_data.json
|
||||||
|
```
|
||||||
|
|||||||
5
samples/merge_data.json
Normal file
5
samples/merge_data.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"title": "出荷ラベル",
|
||||||
|
"left": "品番 ABC-123",
|
||||||
|
"right": "数量 24"
|
||||||
|
}
|
||||||
3
samples/merge_data.yml
Normal file
3
samples/merge_data.yml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
title: "出荷ラベル"
|
||||||
|
left: "品番 ABC-123"
|
||||||
|
right: "数量 24"
|
||||||
9
samples/merge_template.svg
Normal file
9
samples/merge_template.svg
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="720" height="220" viewBox="0 0 720 220">
|
||||||
|
<rect width="720" height="220" fill="#FFFFFF" />
|
||||||
|
<text x="24" y="86" data-field="title" font-size="56" fill="#000000">TITLE</text>
|
||||||
|
<text x="24" y="160" font-size="36" fill="#000000">
|
||||||
|
<tspan data-field="left">LEFT</tspan>
|
||||||
|
<tspan dx="20">/</tspan>
|
||||||
|
<tspan dx="20" data-field="right">RIGHT</tspan>
|
||||||
|
</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 417 B |
@@ -21,15 +21,16 @@ static void usage(const char *argv0)
|
|||||||
" -f, --file PATH 入力ファイル\n"
|
" -f, --file PATH 入力ファイル\n"
|
||||||
" -w, --width DOTS 1bit ラスター時: 幅(ドット)\n"
|
" -w, --width DOTS 1bit ラスター時: 幅(ドット)\n"
|
||||||
" -H, --height DOTS 1bit ラスター時: 高さ(ドット)\n"
|
" -H, --height DOTS 1bit ラスター時: 高さ(ドット)\n"
|
||||||
" -t, --threshold N 二値化しきい値 0–255(既定 %u、PNG/SVG)\n"
|
" -t, --threshold N しきい値 0–255(既定 %u、PNG/SVG)\n"
|
||||||
|
" --trim-right[=DOTS] 右側空白を削減(省略時は左余白、失敗時 0)\n"
|
||||||
" -n, --dry-run 読み込みと check_raster のみ(USB なし)\n"
|
" -n, --dry-run 読み込みと check_raster のみ(USB なし)\n"
|
||||||
" -p, --pid HEX USB 製品 ID(既定: P900W の 0x2085)。例: P750W 0x2062、P710BT 0x20af\n"
|
" -p, --pid HEX USB 製品 ID(既定: P900W の 0x2085)。例: P750W 0x2062、P710BT 0x20af\n"
|
||||||
" -S, --status USB 接続プリンタのステータス(テープ種・幅・色等)を表示して終了\n"
|
" -S, --status ステータスを表示して終了\n"
|
||||||
" -V, --version バージョンを表示して終了\n"
|
" -V, --version バージョンを表示して終了\n"
|
||||||
" -h, --help このヘルプ\n"
|
" -h, --help このヘルプ\n"
|
||||||
"\n"
|
"\n"
|
||||||
"PNG は幅・高さを画像から取得(-w/-H 不要)。\n"
|
"PNG は画像サイズを使用(-w/-H 不要)。\n"
|
||||||
"SVG は現在テープの印字可能幅に合わせて自動拡大・縮小(USB 必須)。\n"
|
"SVG は現在テープ幅に自動フィット(USB 必須)。\n"
|
||||||
"1bit packed ラスター(行優先)の場合は -f -w -H が必須。\n"
|
"1bit packed ラスター(行優先)の場合は -f -w -H が必須。\n"
|
||||||
"--status のときは -f は不要(他オプションは無視されます)。\n",
|
"--status のときは -f は不要(他オプションは無視されます)。\n",
|
||||||
argv0, (unsigned)LIBPTOUCH_PNG_DEFAULT_THRESHOLD);
|
argv0, (unsigned)LIBPTOUCH_PNG_DEFAULT_THRESHOLD);
|
||||||
@@ -107,6 +108,9 @@ int main(int argc, char **argv)
|
|||||||
unsigned threshold = LIBPTOUCH_PNG_DEFAULT_THRESHOLD;
|
unsigned threshold = LIBPTOUCH_PNG_DEFAULT_THRESHOLD;
|
||||||
unsigned usb_pid_arg = 0;
|
unsigned usb_pid_arg = 0;
|
||||||
int dry_run = 0;
|
int dry_run = 0;
|
||||||
|
int trim_right_enabled = 0;
|
||||||
|
int trim_right_auto = 0;
|
||||||
|
unsigned trim_right_dots = 0;
|
||||||
int has_threshold = 0;
|
int has_threshold = 0;
|
||||||
int want_status = 0;
|
int want_status = 0;
|
||||||
static struct option longopts[] = {
|
static struct option longopts[] = {
|
||||||
@@ -114,6 +118,7 @@ int main(int argc, char **argv)
|
|||||||
{ "height", required_argument, NULL, 'H' },
|
{ "height", required_argument, NULL, 'H' },
|
||||||
{ "file", required_argument, NULL, 'f' },
|
{ "file", required_argument, NULL, 'f' },
|
||||||
{ "threshold", required_argument, NULL, 't' },
|
{ "threshold", required_argument, NULL, 't' },
|
||||||
|
{ "trim-right", optional_argument, NULL, 'r' },
|
||||||
{ "dry-run", no_argument, NULL, 'n' },
|
{ "dry-run", no_argument, NULL, 'n' },
|
||||||
{ "pid", required_argument, NULL, 'p' },
|
{ "pid", required_argument, NULL, 'p' },
|
||||||
{ "status", no_argument, NULL, 'S' },
|
{ "status", no_argument, NULL, 'S' },
|
||||||
@@ -123,7 +128,7 @@ int main(int argc, char **argv)
|
|||||||
};
|
};
|
||||||
|
|
||||||
int c;
|
int c;
|
||||||
while ((c = getopt_long(argc, argv, "w:H:f:t:p:nhSV", longopts, NULL)) !=
|
while ((c = getopt_long(argc, argv, "w:H:f:t:r::p:nhSV", longopts, NULL)) !=
|
||||||
-1) {
|
-1) {
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case 'w':
|
case 'w':
|
||||||
@@ -146,6 +151,20 @@ int main(int argc, char **argv)
|
|||||||
case 'n':
|
case 'n':
|
||||||
dry_run = 1;
|
dry_run = 1;
|
||||||
break;
|
break;
|
||||||
|
case 'r':
|
||||||
|
trim_right_enabled = 1;
|
||||||
|
if (!optarg) {
|
||||||
|
trim_right_auto = 1;
|
||||||
|
trim_right_dots = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
trim_right_auto = 0;
|
||||||
|
trim_right_dots = (unsigned)strtoul(optarg, NULL, 10);
|
||||||
|
if (trim_right_dots > 0xFFFFu) {
|
||||||
|
fprintf(stderr, "--trim-right must be 0..65535\n");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
break;
|
||||||
case 'p':
|
case 'p':
|
||||||
usb_pid_arg = (unsigned)strtoul(optarg, NULL, 0);
|
usb_pid_arg = (unsigned)strtoul(optarg, NULL, 0);
|
||||||
if (usb_pid_arg == 0u || usb_pid_arg > 0xFFFFu) {
|
if (usb_pid_arg == 0u || usb_pid_arg > 0xFFFFu) {
|
||||||
@@ -238,6 +257,7 @@ int main(int argc, char **argv)
|
|||||||
libptouch_raster_params_t params = { 0, 0, 0 };
|
libptouch_raster_params_t params = { 0, 0, 0 };
|
||||||
libptouch_err_t e;
|
libptouch_err_t e;
|
||||||
int usb_opened = 0;
|
int usb_opened = 0;
|
||||||
|
int data_from_lib = 0;
|
||||||
|
|
||||||
if (png) {
|
if (png) {
|
||||||
libptouch_png_options_t opt = { .threshold = (uint8_t)threshold };
|
libptouch_png_options_t opt = { .threshold = (uint8_t)threshold };
|
||||||
@@ -250,6 +270,7 @@ int main(int argc, char **argv)
|
|||||||
libptouch_destroy(ctx);
|
libptouch_destroy(ctx);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
data_from_lib = 1;
|
||||||
} else if (svg) {
|
} else if (svg) {
|
||||||
e = usb_pid_arg != 0
|
e = usb_pid_arg != 0
|
||||||
? libptouch_open_usb_vid_pid(ctx, LIBPTOUCH_USB_VID_BROTHER,
|
? libptouch_open_usb_vid_pid(ctx, LIBPTOUCH_USB_VID_BROTHER,
|
||||||
@@ -272,6 +293,7 @@ int main(int argc, char **argv)
|
|||||||
libptouch_destroy(ctx);
|
libptouch_destroy(ctx);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
data_from_lib = 1;
|
||||||
} else {
|
} else {
|
||||||
if (read_file(file, &data, &data_len) != 0) {
|
if (read_file(file, &data, &data_len) != 0) {
|
||||||
libptouch_destroy(ctx);
|
libptouch_destroy(ctx);
|
||||||
@@ -282,12 +304,60 @@ int main(int argc, char **argv)
|
|||||||
params.margin_mm = 0;
|
params.margin_mm = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (trim_right_enabled) {
|
||||||
|
uint16_t pad = (uint16_t)trim_right_dots;
|
||||||
|
if (trim_right_auto) {
|
||||||
|
pad = 0u;
|
||||||
|
if (!usb_opened) {
|
||||||
|
e = usb_pid_arg != 0
|
||||||
|
? libptouch_open_usb_vid_pid(
|
||||||
|
ctx, LIBPTOUCH_USB_VID_BROTHER,
|
||||||
|
(uint16_t)usb_pid_arg)
|
||||||
|
: libptouch_open_usb(ctx);
|
||||||
|
if (e == LIBPTOUCH_OK)
|
||||||
|
usb_opened = 1;
|
||||||
|
}
|
||||||
|
if (usb_opened) {
|
||||||
|
libptouch_media_info_t mi;
|
||||||
|
if (libptouch_get_current_media_info(ctx, &mi) ==
|
||||||
|
LIBPTOUCH_OK) {
|
||||||
|
pad = mi.left_margin_dots;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t *trimmed = NULL;
|
||||||
|
size_t trimmed_len = 0;
|
||||||
|
libptouch_raster_params_t trimmed_params = { 0, 0, 0 };
|
||||||
|
e = libptouch_trim_right_blank_columns(
|
||||||
|
ctx, data, data_len, ¶ms, pad, &trimmed, &trimmed_len,
|
||||||
|
&trimmed_params);
|
||||||
|
if (e != LIBPTOUCH_OK) {
|
||||||
|
fprintf(stderr, "trim_right_blank_columns: %s\n",
|
||||||
|
libptouch_strerror(ctx));
|
||||||
|
libptouch_destroy(ctx);
|
||||||
|
if (data_from_lib)
|
||||||
|
libptouch_free_raster(data);
|
||||||
|
else
|
||||||
|
free(data);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (data_from_lib)
|
||||||
|
libptouch_free_raster(data);
|
||||||
|
else
|
||||||
|
free(data);
|
||||||
|
data = trimmed;
|
||||||
|
data_len = trimmed_len;
|
||||||
|
params = trimmed_params;
|
||||||
|
data_from_lib = 1;
|
||||||
|
}
|
||||||
|
|
||||||
e = libptouch_check_raster(ctx, data, data_len, ¶ms);
|
e = libptouch_check_raster(ctx, data, data_len, ¶ms);
|
||||||
if (e != LIBPTOUCH_OK) {
|
if (e != LIBPTOUCH_OK) {
|
||||||
fprintf(stderr, "check_raster: %s\n",
|
fprintf(stderr, "check_raster: %s\n",
|
||||||
libptouch_strerror(ctx));
|
libptouch_strerror(ctx));
|
||||||
libptouch_destroy(ctx);
|
libptouch_destroy(ctx);
|
||||||
if (png || svg)
|
if (data_from_lib)
|
||||||
libptouch_free_raster(data);
|
libptouch_free_raster(data);
|
||||||
else
|
else
|
||||||
free(data);
|
free(data);
|
||||||
@@ -304,7 +374,7 @@ int main(int argc, char **argv)
|
|||||||
if (usb_opened)
|
if (usb_opened)
|
||||||
libptouch_close(ctx);
|
libptouch_close(ctx);
|
||||||
libptouch_destroy(ctx);
|
libptouch_destroy(ctx);
|
||||||
if (png || svg)
|
if (data_from_lib)
|
||||||
libptouch_free_raster(data);
|
libptouch_free_raster(data);
|
||||||
else
|
else
|
||||||
free(data);
|
free(data);
|
||||||
@@ -319,7 +389,7 @@ int main(int argc, char **argv)
|
|||||||
if (e != LIBPTOUCH_OK) {
|
if (e != LIBPTOUCH_OK) {
|
||||||
fprintf(stderr, "open_usb: %s\n", libptouch_strerror(ctx));
|
fprintf(stderr, "open_usb: %s\n", libptouch_strerror(ctx));
|
||||||
libptouch_destroy(ctx);
|
libptouch_destroy(ctx);
|
||||||
if (png || svg)
|
if (data_from_lib)
|
||||||
libptouch_free_raster(data);
|
libptouch_free_raster(data);
|
||||||
else
|
else
|
||||||
free(data);
|
free(data);
|
||||||
@@ -333,7 +403,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 || svg)
|
if (data_from_lib)
|
||||||
libptouch_free_raster(data);
|
libptouch_free_raster(data);
|
||||||
else
|
else
|
||||||
free(data);
|
free(data);
|
||||||
@@ -342,7 +412,7 @@ int main(int argc, char **argv)
|
|||||||
|
|
||||||
libptouch_close(ctx);
|
libptouch_close(ctx);
|
||||||
libptouch_destroy(ctx);
|
libptouch_destroy(ctx);
|
||||||
if (png || svg)
|
if (data_from_lib)
|
||||||
libptouch_free_raster(data);
|
libptouch_free_raster(data);
|
||||||
else
|
else
|
||||||
free(data);
|
free(data);
|
||||||
|
|||||||
84
src/lib/libptouch_trim.c
Normal file
84
src/lib/libptouch_trim.c
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
* libptouch — raster right blank trim
|
||||||
|
*
|
||||||
|
* Author: knb
|
||||||
|
* Email: knb@artif.org
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "libptouch_internal.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
libptouch_err_t libptouch_trim_right_blank_columns(
|
||||||
|
libptouch_ctx *ctx, const uint8_t *data, size_t data_len,
|
||||||
|
const libptouch_raster_params_t *in_params, uint16_t right_padding_dots,
|
||||||
|
uint8_t **out_raster, size_t *out_raster_bytes,
|
||||||
|
libptouch_raster_params_t *out_params)
|
||||||
|
{
|
||||||
|
if (!ctx || !data || !in_params || !out_raster || !out_raster_bytes ||
|
||||||
|
!out_params) {
|
||||||
|
if (ctx)
|
||||||
|
ptouch_set_error(ctx, LIBPTOUCH_ERR_ARG, "null argument");
|
||||||
|
return LIBPTOUCH_ERR_ARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
libptouch_err_t v = libptouch_check_raster(ctx, data, data_len, in_params);
|
||||||
|
if (v != LIBPTOUCH_OK)
|
||||||
|
return v;
|
||||||
|
|
||||||
|
uint32_t width = in_params->width_dots;
|
||||||
|
uint32_t height = in_params->height_dots;
|
||||||
|
size_t row_bytes = ((size_t)width + 7u) / 8u;
|
||||||
|
int32_t rightmost = -1;
|
||||||
|
|
||||||
|
for (uint32_t y = 0; y < height; y++) {
|
||||||
|
const uint8_t *row = data + (size_t)y * row_bytes;
|
||||||
|
for (int32_t x = (int32_t)width - 1; x >= 0; x--) {
|
||||||
|
uint8_t b = row[(size_t)x / 8u];
|
||||||
|
uint8_t bit = (uint8_t)(7u - ((uint32_t)x % 8u));
|
||||||
|
if (((b >> bit) & 1u) == 0)
|
||||||
|
continue;
|
||||||
|
if (x > rightmost)
|
||||||
|
rightmost = x;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t new_width = width;
|
||||||
|
if (rightmost >= 0) {
|
||||||
|
uint32_t trimmed = (uint32_t)(rightmost + 1);
|
||||||
|
uint32_t padded = trimmed + (uint32_t)right_padding_dots;
|
||||||
|
if (padded > width)
|
||||||
|
padded = width;
|
||||||
|
if (padded < width)
|
||||||
|
new_width = padded;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t new_row_bytes = ((size_t)new_width + 7u) / 8u;
|
||||||
|
size_t total = new_row_bytes * (size_t)height;
|
||||||
|
uint8_t *out = (uint8_t *)calloc(1u, total);
|
||||||
|
if (!out) {
|
||||||
|
ptouch_set_error(ctx, LIBPTOUCH_ERR_NOMEM, "calloc raster failed");
|
||||||
|
return LIBPTOUCH_ERR_NOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t last_mask = 0xFFu;
|
||||||
|
if ((new_width % 8u) != 0u)
|
||||||
|
last_mask = (uint8_t)((0xFFu << (8u - (new_width % 8u))) & 0xFFu);
|
||||||
|
|
||||||
|
for (uint32_t y = 0; y < height; y++) {
|
||||||
|
const uint8_t *src = data + (size_t)y * row_bytes;
|
||||||
|
uint8_t *dst = out + (size_t)y * new_row_bytes;
|
||||||
|
memcpy(dst, src, new_row_bytes);
|
||||||
|
dst[new_row_bytes - 1u] &= last_mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
*out_raster = out;
|
||||||
|
*out_raster_bytes = total;
|
||||||
|
out_params->width_dots = new_width;
|
||||||
|
out_params->height_dots = height;
|
||||||
|
out_params->margin_mm = in_params->margin_mm;
|
||||||
|
ptouch_set_error(ctx, LIBPTOUCH_OK, "");
|
||||||
|
return LIBPTOUCH_OK;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user