SVG印刷対応とメディア情報APIを追加

SVG入力を現在テープ幅に自動フィットして印刷できるようにし、アプリ側が余白計算できるようにテープ幅・DPI・最小送り量を取得するAPIを追加する。

Made-with: Cursor
This commit is contained in:
knb
2026-04-14 18:29:24 +09:00
parent f26a1186a3
commit 2ed0bfc0be
10 changed files with 539 additions and 43 deletions

View File

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

View File

@@ -7,7 +7,7 @@ require "libptouch"
module Libptouch
module Cli
# PNG のみを扱う ptouch-print 相当の CLI1bit ラスター経路なし)。
# PNG/SVG を扱う ptouch-print 相当の CLI1bit ラスター経路なし)。
module PngPrint
module_function
@@ -26,9 +26,10 @@ module Libptouch
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)
kind = image_kind(path)
return usage_error("not a PNG/SVG file: #{path}") if kind.nil?
run_print(path, opts)
run_print(path, opts, kind)
end
def png_file?(path)
@@ -43,6 +44,17 @@ module Libptouch
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)
@@ -101,9 +113,9 @@ module Libptouch
OptionParser.new do |p|
p.banner = usage_banner
p.separator ""
p.on("-f", "--file PATH", "入力 PNG ファイル") { |v| opts_hash[:file] = v }
p.on("-f", "--file PATH", "入力 PNG/SVG ファイル") { |v| opts_hash[:file] = v }
p.on("-t", "--threshold N", Integer,
"二値化しきい値 0255既定 #{Libptouch::PNG_DEFAULT_THRESHOLD}") do |v|
"二値化しきい値 0255既定 #{Libptouch::PNG_DEFAULT_THRESHOLD}、PNG/SVG") do |v|
opts_hash[:threshold] = v
end
p.on("-p", "--pid PID", pid_option_description) do |v|
@@ -122,7 +134,8 @@ module Libptouch
<<~BANNER
Usage: ptouch-print-png [options]
PNG -w/-H
PNG/SVG -w/-H
SVG USB
--status -f
BANNER
end
@@ -149,9 +162,9 @@ module Libptouch
opts_hash = default_cli_opts
p = OptionParser.new do |parser|
parser.banner = "ptouch-print-png [options]"
parser.on("-f", "--file PATH", "入力 PNG ファイル") { |v| opts_hash[:file] = v }
parser.on("-f", "--file PATH", "入力 PNG/SVG ファイル") { |v| opts_hash[:file] = v }
parser.on("-t", "--threshold N", Integer,
"二値化しきい値 0255既定 #{Libptouch::PNG_DEFAULT_THRESHOLD}") do |v|
"二値化しきい値 0255既定 #{Libptouch::PNG_DEFAULT_THRESHOLD}、PNG/SVG") do |v|
opts_hash[:threshold] = v
end
parser.on("-p", "--pid PID", pid_option_description) do |v|
@@ -192,12 +205,19 @@ module Libptouch
end
end
def run_print(path, opts)
def run_print(path, opts, kind)
ctx = nil
begin
ctx = Libptouch::Context.new
threshold = opts[:threshold]
data, width, height = if threshold.nil?
data, width, height = if kind == :svg
open_usb_for_opts(ctx, opts)
if threshold.nil?
ctx.svg_file_to_raster_fit_current_tape(path)
else
ctx.svg_file_to_raster_fit_current_tape(path, threshold: threshold)
end
elsif threshold.nil?
ctx.png_file_to_raster(path)
else
ctx.png_file_to_raster(path, threshold: threshold)
@@ -206,11 +226,11 @@ module Libptouch
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"
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)
open_usb_for_opts(ctx, opts) if kind == :png
ctx.print_raster(data, width_dots: width, height_dots: height)
0
rescue Libptouch::Error => e

View File

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