# frozen_string_literal: true require "json" require "optparse" require "libptouch" module Libptouch module Cli # PNG のみを扱う ptouch-print 相当の CLI(1bit ラスター経路なし)。 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 end return usage_error("-f is required (or use --status)") if opts[:file].to_s.empty? path = opts[:file] return usage_error("not a PNG file: #{path}") unless png_file?(path) run_print(path, opts) 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 parse(argv) o = { file: nil, threshold: nil, dry_run: false, status: false, version: false, help: false } parser = OptionParser.new do |p| p.banner = usage_banner p.separator "" p.on("-f", "--file PATH", "入力 PNG ファイル") { |v| o[:file] = v } p.on("-t", "--threshold N", Integer, "二値化しきい値 0–255(既定 #{Libptouch::PNG_DEFAULT_THRESHOLD})") do |v| o[:threshold] = v end p.on("-n", "--dry-run", "読み込みと検証のみ(USB なし)") { o[:dry_run] = true } p.on("-S", "--status", "USB プリンタのステータスを JSON で表示して終了") { o[:status] = true } p.on("-V", "--version", "バージョンを表示して終了") { o[:version] = true } p.on("-h", "--help", "このヘルプ") { o[:help] = true } end parser.parse!(argv) unless o[:threshold].nil? || (0..255).cover?(o[:threshold]) warn "-t must be 0..255" return nil end o end def usage_banner <<~BANNER Usage: ptouch-print-png [options] PNG のみ対応。幅・高さは画像から取得します(-w/-H はありません)。 --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 o = { file: nil, threshold: nil, dry_run: false, status: false, version: false, help: false } p = OptionParser.new do |parser| parser.banner = "ptouch-print-png [options]" parser.on("-f", "--file PATH", "入力 PNG ファイル") { |v| o[:file] = v } parser.on("-t", "--threshold N", Integer, "二値化しきい値 0–255(既定 #{Libptouch::PNG_DEFAULT_THRESHOLD})") do |v| o[:threshold] = v end parser.on("-n", "--dry-run", "読み込みと検証のみ(USB なし)") { o[:dry_run] = true } parser.on("-S", "--status", "USB プリンタのステータスを JSON で表示して終了") { o[:status] = true } parser.on("-V", "--version", "バージョンを表示して終了") { o[:version] = true } parser.on("-h", "--help", "このヘルプ") { o[:help] = true } end p.help end def run_status ctx = nil begin ctx = Libptouch::Context.new ctx.open_usb 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) ctx = nil begin ctx = Libptouch::Context.new threshold = opts[:threshold] data, width, height = if 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, #{width}x#{height} dots" return 0 end ctx.open_usb 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