diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5b3029c..7e5ad7f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -14,6 +14,7 @@ include(GNUInstallDirs)
find_package(PkgConfig REQUIRED)
pkg_check_modules(LIBUSB REQUIRED IMPORTED_TARGET libusb-1.0)
find_package(PNG REQUIRED)
+pkg_check_modules(LIBRSVG IMPORTED_TARGET librsvg-2.0)
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/include/libptouch_version.h.in"
@@ -25,9 +26,11 @@ set(LIBPTOUCH_SOURCES
src/lib/libptouch_core.c
src/lib/libptouch_usb.c
src/lib/libptouch_layout.c
+ src/lib/libptouch_media_info.c
src/lib/libptouch_print.c
src/lib/libptouch_status.c
src/lib/libptouch_png.c
+ src/lib/libptouch_svg.c
)
add_library(ptouch STATIC ${LIBPTOUCH_SOURCES})
@@ -52,6 +55,12 @@ target_include_directories(ptouch_shared PRIVATE
)
target_link_libraries(ptouch PRIVATE PkgConfig::LIBUSB PNG::PNG)
target_link_libraries(ptouch_shared PRIVATE PkgConfig::LIBUSB PNG::PNG)
+if(LIBRSVG_FOUND)
+ target_link_libraries(ptouch PRIVATE PkgConfig::LIBRSVG)
+ target_link_libraries(ptouch_shared PRIVATE PkgConfig::LIBRSVG)
+ target_compile_definitions(ptouch PRIVATE LIBPTOUCH_HAS_RSVG=1)
+ target_compile_definitions(ptouch_shared PRIVATE LIBPTOUCH_HAS_RSVG=1)
+endif()
if(NOT MSVC)
target_compile_options(ptouch PRIVATE -Wall -Wextra -Wpedantic)
target_compile_options(ptouch_shared PRIVATE -Wall -Wextra -Wpedantic)
diff --git a/README.md b/README.md
index 3d94eb2..9ed9e45 100644
--- a/README.md
+++ b/README.md
@@ -11,15 +11,15 @@ Brother P-touch シリーズ向けのラベル印刷用 **C コアライブラ
| パス | 内容 |
|------|------|
| `include/libptouch.h` | 公開 API |
-| `src/lib/libptouch_*.c` | ライブラリ本体(core / usb / print / status / png) |
+| `src/lib/libptouch_*.c` | ライブラリ本体(core / usb / print / status / png / svg) |
| `src/cli/main.c` | `ptouch-print` エントリ |
-| `samples/` | 試験用サンプル画像の置き場(PNG 等) |
+| `samples/` | 試験用サンプル画像の置き場(PNG/SVG 等) |
| `ruby/` | Ruby FFI gem(`libptouch`)・コマンド `ptouch-print-png`(PNG のみ)— `ruby/README.md` |
| `reference/` | 仕様・参考資料(例: ラスター PDF) |
## ビルド
-依存: **CMake 3.16+**、**libusb-1.0**、**libpng**(開発パッケージ例: `libusb-1.0-0-dev`、`libpng-dev`)。
+依存: **CMake 3.16+**、**libusb-1.0**、**libpng**、**librsvg-2.0**(開発パッケージ例: `libusb-1.0-0-dev`、`libpng-dev`、`librsvg2-dev`)。
```bash
cmake -S . -B build
@@ -38,7 +38,9 @@ cmake --build build
## CLI の使い方(雛形)
-**PNG**(拡張子 `.png` または PNG シグネチャ)の場合は幅・高さは画像から取得します。任意で `-t`(0–255)で二値化しきい値を指定できます。
+**PNG**(拡張子 `.png` または PNG シグネチャ)の場合は幅・高さは画像から取得します。
+**SVG**(拡張子 `.svg`)の場合は現在装着中テープの印字可能ドット幅に合わせて自動拡大・縮小します(USB 接続が必要)。
+任意で `-t`(0–255)で二値化しきい値を指定できます。
**1bit packed ラスター**(行優先、行あたり `ceil(width/8)` バイト × 行数)の場合は `-f` に加え `-w` / `-H` が必須です。
@@ -48,6 +50,9 @@ cmake --build build
# PNG — 検証のみ(USB 不要)
./build/ptouch-print -n -f label.png
+# SVG — 幅を現在テープにフィットさせて検証(USB 必要)
+./build/ptouch-print -n -f label.svg
+
# PNG — しきい値を指定
./build/ptouch-print -n -f label.png -t 160
@@ -59,6 +64,7 @@ cmake --build build
# USB 接続時
./build/ptouch-print -f label.png
+./build/ptouch-print -f label.svg
./build/ptouch-print -f sample.raster -w 128 -H 64
# PT-P750W / PT-P710BT(`lsusb` の PID に合わせる)
@@ -74,8 +80,10 @@ cmake --build build
- `libptouch_create` / `libptouch_destroy` — コンテキスト
- `libptouch_open_usb` / `libptouch_open_usb_vid_pid` / `libptouch_close` — USB(libusb・VID/PID)
- `libptouch_get_status` / `libptouch_status_fprint` — ステータス情報リクエスト(ESC i S)の応答
+- `libptouch_get_current_media_info` — 現在テープ幅(mm)・印字可能幅(dots)・DPI・最小余白(mm)を取得
- `libptouch_check_raster` — ラスターバッファの検証のみ
- `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 のコマンド列、内部で転置)
- `libptouch_strerror` / `libptouch_last_error` — エラー情報
diff --git a/include/libptouch.h b/include/libptouch.h
index 3677942..0b32912 100644
--- a/include/libptouch.h
+++ b/include/libptouch.h
@@ -73,6 +73,19 @@ typedef struct {
uint8_t margin_mm; /**< 余白など(機種・仕様に合わせて使用) */
} 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 feed_dpi; /**< 送り方向 DPI(ESC i d の基準) */
+ double tape_width_mm; /**< 装着テープ幅(mm)。例: 3.5, 6, 9, 12, 18, 24, 36 */
+ uint16_t printable_dots; /**< 現在テープで印字可能な幅(ドット) */
+ uint16_t left_margin_dots; /**< 左余白(ドット) */
+ uint16_t right_margin_dots; /**< 右余白(ドット) */
+ uint16_t min_feed_dots; /**< 仕様上の最小送り量(ドット)。現在は 14 */
+ double min_feed_mm; /**< 最小送り量(mm) */
+} libptouch_media_info_t;
+
/**
* バッファサイズとパラメータの整合性のみ検査(USB 不要)。--dry-run 用。
*/
@@ -80,6 +93,13 @@ libptouch_err_t libptouch_check_raster(libptouch_ctx *ctx,
const uint8_t *data, size_t data_len,
const libptouch_raster_params_t *params);
+/**
+ * 現在装着テープの情報を取得する(USB 接続済み必須)。
+ * ステータスと機種プロファイルからテープ幅・印字可能幅・最小余白量を返す。
+ */
+libptouch_err_t libptouch_get_current_media_info(libptouch_ctx *ctx,
+ libptouch_media_info_t *out_info);
+
/**
* 1 ビット packed ラスターを USB で印刷(各機種のラスター PDF 準拠)。
* 印字前にステータスでテープ幅を読み、印刷可能ドット内に画像を中央配置する。
@@ -101,6 +121,10 @@ typedef struct {
uint8_t threshold; /**< 輝度がこれ未満なら黒(1)、以上なら白(0) */
} libptouch_png_options_t;
+typedef struct {
+ uint8_t threshold; /**< 輝度がこれ未満なら黒(1)、以上なら白(0) */
+} libptouch_svg_options_t;
+
/**
* PNG ファイルを読み、1bit packed ラスターに変換する(libpng)。
* @param out_raster malloc 済みバッファのポインタを返す。不要時は @ref libptouch_free_raster で解放。
@@ -110,6 +134,17 @@ libptouch_err_t libptouch_png_file_to_raster(libptouch_ctx *ctx, const char *pat
uint8_t **out_raster, size_t *out_raster_bytes,
libptouch_raster_params_t *out_params);
+/**
+ * SVG ファイルを現在装着中テープの印字可能幅に合わせて 1bit packed ラスターへ変換する
+ * (librsvg + cairo)。
+ * @note 現在テープ幅を取得するため、事前に @ref libptouch_open_usb などで USB 接続が必要。
+ * @param out_raster malloc 済みバッファのポインタを返す。不要時は @ref libptouch_free_raster で解放。
+ */
+libptouch_err_t libptouch_svg_file_to_raster_fit_current_tape(
+ libptouch_ctx *ctx, const char *path,
+ const libptouch_svg_options_t *options, uint8_t **out_raster,
+ size_t *out_raster_bytes, libptouch_raster_params_t *out_params);
+
void libptouch_free_raster(uint8_t *raster);
/** ステータス情報リクエスト(ESC i S)の応答バイト数(cv_ptp900_jpn_raster_102.pdf) */
diff --git a/ruby/README.md b/ruby/README.md
index 03cee0e..3b6da2f 100644
--- a/ruby/README.md
+++ b/ruby/README.md
@@ -31,9 +31,10 @@ export LIBPTOUCH_LIB=/usr/local/lib/libptouch.so
(`cmake --install` で共有ライブラリをインストールした場合は、通常は `libptouch` 名でローダが解決します。)
-## コマンド `ptouch-print-png`(PNG のみ)
+## コマンド `ptouch-print-png`(PNG/SVG)
-C の `ptouch-print` と同様の流れですが、**PNG 入力のみ**(`-w`/`-H` や 1bit ラスターは扱いません)。`gem install` 後は PATH に `ptouch-print-png` が入ります。
+C の `ptouch-print` と同様の流れで、**PNG/SVG 入力**(`-w`/`-H` や 1bit ラスターは扱いません)を扱います。`gem install` 後は PATH に `ptouch-print-png` が入ります。
+SVG は現在装着テープの印字可能幅に合わせて自動拡大・縮小します(USB 接続が必要)。
オプションは C 側に合わせ、**`-p` / `--pid`** で USB 製品 ID(16 進可)を指定できます。省略時は PT-P900W(`Libptouch::USB_PID_PTP900W` = `0x2085`)。例: PT-P750W `0x2062`、PT-P710BT `0x20af`(`libptouch.h` / `Libptouch` 定数と同じ)。
@@ -42,6 +43,7 @@ C の `ptouch-print` と同様の流れですが、**PNG 入力のみ**(`-w`/`
```bash
bundle exec ruby -I lib exe/ptouch-print-png --help
bundle exec ruby -I lib exe/ptouch-print-png -n -f ../samples/your.png
+bundle exec ruby -I lib exe/ptouch-print-png -n -f ../samples/your.svg
bundle exec ruby -I lib exe/ptouch-print-png --status -p 0x2062
bundle exec ruby -I lib exe/ptouch-print-png -f ../samples/your.png -p 0x20af
```
@@ -76,12 +78,23 @@ ctx.print_raster(data, width_dots: w, height_dots: h, margin_mm: 0)
ctx.dispose
```
+SVG から現在テープ幅に合わせてラスターへ:
+
+```ruby
+ctx = Libptouch::Context.new
+ctx.open_usb
+data, w, h = ctx.svg_file_to_raster_fit_current_tape("/path/to/label.svg")
+ctx.print_raster(data, width_dots: w, height_dots: h, margin_mm: 0)
+ctx.dispose
+```
+
**解像度:** P750W / P710BT はラスター幅方向が **180 dpi**、P900W 系は **360 dpi**(C の `libptouch_print` / 各機種ラスター PDF に合わせて画像を用意してください)。
## API の範囲
-- 実行ファイル `ptouch-print-png` … PNG のみ(`-f`, `-t`, `-p`, `-n`, `-S`, `-V`, `-h`)。ステータスは JSON(`status_bytes` を `parse_status` したもの、`raw_bytes` 除く)
+- 実行ファイル `ptouch-print-png` … PNG/SVG(`-f`, `-t`, `-p`, `-n`, `-S`, `-V`, `-h`)。ステータスは JSON(`status_bytes` を `parse_status` したもの、`raw_bytes` 除く)
- `Libptouch::Context` … `open_usb` / `open_usb_vid_pid` / `close` / `dispose`
-- `check_raster` / `print_raster` / `png_file_to_raster` / `status_bytes` / `status_hash`
+- `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` などを含む
- `Libptouch.parse_status(raw)` … 32 バイトを Hash に展開(機種・テープ幅・**テープ種類**・色・**状態(status_kind)**・エラービット・`raw_hex` など)
- C の `libptouch_status_fprint`(`FILE *`)は FFI からはバインドしていません。テキスト出力の代わりに `parse_status` / `status_hash` を使ってください。
diff --git a/ruby/lib/libptouch/binding.rb b/ruby/lib/libptouch/binding.rb
index ada383e..e922153 100644
--- a/ruby/lib/libptouch/binding.rb
+++ b/ruby/lib/libptouch/binding.rb
@@ -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
diff --git a/ruby/lib/libptouch/cli/png_print.rb b/ruby/lib/libptouch/cli/png_print.rb
index 6fd69c2..2f2fc64 100644
--- a/ruby/lib/libptouch/cli/png_print.rb
+++ b/ruby/lib/libptouch/cli/png_print.rb
@@ -7,7 +7,7 @@ require "libptouch"
module Libptouch
module Cli
- # PNG のみを扱う ptouch-print 相当の CLI(1bit ラスター経路なし)。
+ # PNG/SVG を扱う ptouch-print 相当の CLI(1bit ラスター経路なし)。
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,
- "二値化しきい値 0–255(既定 #{Libptouch::PNG_DEFAULT_THRESHOLD})") do |v|
+ "二値化しきい値 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|
@@ -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,
- "二値化しきい値 0–255(既定 #{Libptouch::PNG_DEFAULT_THRESHOLD})") do |v|
+ "二値化しきい値 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|
@@ -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
diff --git a/ruby/lib/libptouch/context.rb b/ruby/lib/libptouch/context.rb
index b37769c..463d611 100644
--- a/ruby/lib/libptouch/context.rb
+++ b/ruby/lib/libptouch/context.rb
@@ -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
diff --git a/samples/sample.svg b/samples/sample.svg
new file mode 100644
index 0000000..ef968e6
--- /dev/null
+++ b/samples/sample.svg
@@ -0,0 +1,40 @@
+
+
+
+
diff --git a/src/cli/main.c b/src/cli/main.c
index 430f157..db99c12 100644
--- a/src/cli/main.c
+++ b/src/cli/main.c
@@ -21,14 +21,15 @@ static void usage(const char *argv0)
" -f, --file PATH 入力ファイル\n"
" -w, --width DOTS 1bit ラスター時: 幅(ドット)\n"
" -H, --height DOTS 1bit ラスター時: 高さ(ドット)\n"
- " -t, --threshold N PNG 二値化しきい値 0–255(既定 %u、PNG のみ)\n"
+ " -t, --threshold N 二値化しきい値 0–255(既定 %u、PNG/SVG)\n"
" -n, --dry-run 読み込みと check_raster のみ(USB なし)\n"
" -p, --pid HEX USB 製品 ID(既定: P900W の 0x2085)。例: P750W 0x2062、P710BT 0x20af\n"
" -S, --status USB 接続プリンタのステータス(テープ種・幅・色等)を表示して終了\n"
" -V, --version バージョンを表示して終了\n"
" -h, --help このヘルプ\n"
"\n"
- "PNG の場合は幅・高さはファイルから取得(-w/-H 不要)。\n"
+ "PNG は幅・高さを画像から取得(-w/-H 不要)。\n"
+ "SVG は現在テープの印字可能幅に合わせて自動拡大・縮小(USB 必須)。\n"
"1bit packed ラスター(行優先)の場合は -f -w -H が必須。\n"
"--status のときは -f は不要(他オプションは無視されます)。\n",
argv0, (unsigned)LIBPTOUCH_PNG_DEFAULT_THRESHOLD);
@@ -54,6 +55,14 @@ static int input_is_png(const char *path)
return memcmp(sig, png_magic, sizeof(png_magic)) == 0;
}
+static int input_is_svg(const char *path)
+{
+ const char *dot = strrchr(path, '.');
+ if (dot && strcasecmp(dot, ".svg") == 0)
+ return 1;
+ return 0;
+}
+
static int read_file(const char *path, uint8_t **out, size_t *out_len)
{
FILE *fp = fopen(path, "rb");
@@ -201,20 +210,21 @@ int main(int argc, char **argv)
}
int png = input_is_png(file);
- if (png) {
+ int svg = input_is_svg(file);
+ if (png || svg) {
if (width != 0 || height != 0)
fprintf(stderr,
- "warning: -w/-H ignored for PNG (using image size)\n");
+ "warning: -w/-H ignored for image inputs\n");
} else {
if (width == 0 || height == 0) {
fprintf(stderr,
- "1bit raster requires -w and -H (or use a PNG file)\n");
+ "1bit raster requires -w and -H (or use a PNG/SVG file)\n");
usage(argv[0]);
return 2;
}
if (has_threshold)
fprintf(stderr,
- "warning: -t applies to PNG only (ignored)\n");
+ "warning: -t applies to PNG/SVG only (ignored)\n");
}
libptouch_ctx *ctx = libptouch_create();
@@ -227,6 +237,7 @@ int main(int argc, char **argv)
size_t data_len = 0;
libptouch_raster_params_t params = { 0, 0, 0 };
libptouch_err_t e;
+ int usb_opened = 0;
if (png) {
libptouch_png_options_t opt = { .threshold = (uint8_t)threshold };
@@ -239,6 +250,28 @@ int main(int argc, char **argv)
libptouch_destroy(ctx);
return 1;
}
+ } else if (svg) {
+ e = usb_pid_arg != 0
+ ? libptouch_open_usb_vid_pid(ctx, LIBPTOUCH_USB_VID_BROTHER,
+ (uint16_t)usb_pid_arg)
+ : libptouch_open_usb(ctx);
+ if (e != LIBPTOUCH_OK) {
+ fprintf(stderr, "open_usb: %s\n", libptouch_strerror(ctx));
+ libptouch_destroy(ctx);
+ return 1;
+ }
+ usb_opened = 1;
+ libptouch_svg_options_t opt = { .threshold = (uint8_t)threshold };
+ e = libptouch_svg_file_to_raster_fit_current_tape(
+ ctx, file, has_threshold ? &opt : NULL, &data, &data_len,
+ ¶ms);
+ if (e != LIBPTOUCH_OK) {
+ fprintf(stderr, "svg_file_to_raster_fit_current_tape: %s\n",
+ libptouch_strerror(ctx));
+ libptouch_close(ctx);
+ libptouch_destroy(ctx);
+ return 1;
+ }
} else {
if (read_file(file, &data, &data_len) != 0) {
libptouch_destroy(ctx);
@@ -254,7 +287,7 @@ int main(int argc, char **argv)
fprintf(stderr, "check_raster: %s\n",
libptouch_strerror(ctx));
libptouch_destroy(ctx);
- if (png)
+ if (png || svg)
libptouch_free_raster(data);
else
free(data);
@@ -262,29 +295,36 @@ int main(int argc, char **argv)
}
if (dry_run) {
- printf("dry-run OK: %zu bytes, %ux%u dots\n", data_len,
+ printf("dry-run OK: %zu bytes, src %ux%u dots (print lengthxwidth %ux%u)\n",
+ data_len,
+ (unsigned)params.width_dots,
+ (unsigned)params.height_dots,
(unsigned)params.width_dots,
(unsigned)params.height_dots);
+ if (usb_opened)
+ libptouch_close(ctx);
libptouch_destroy(ctx);
- if (png)
+ if (png || svg)
libptouch_free_raster(data);
else
free(data);
return 0;
}
- e = usb_pid_arg != 0
- ? libptouch_open_usb_vid_pid(ctx, LIBPTOUCH_USB_VID_BROTHER,
- (uint16_t)usb_pid_arg)
- : libptouch_open_usb(ctx);
- if (e != LIBPTOUCH_OK) {
- fprintf(stderr, "open_usb: %s\n", libptouch_strerror(ctx));
- libptouch_destroy(ctx);
- if (png)
- libptouch_free_raster(data);
- else
- free(data);
- return 1;
+ 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) {
+ fprintf(stderr, "open_usb: %s\n", libptouch_strerror(ctx));
+ libptouch_destroy(ctx);
+ if (png || svg)
+ libptouch_free_raster(data);
+ else
+ free(data);
+ return 1;
+ }
}
e = libptouch_print_raster(ctx, data, data_len, ¶ms);
@@ -293,7 +333,7 @@ int main(int argc, char **argv)
libptouch_strerror(ctx));
libptouch_close(ctx);
libptouch_destroy(ctx);
- if (png)
+ if (png || svg)
libptouch_free_raster(data);
else
free(data);
@@ -302,7 +342,7 @@ int main(int argc, char **argv)
libptouch_close(ctx);
libptouch_destroy(ctx);
- if (png)
+ if (png || svg)
libptouch_free_raster(data);
else
free(data);
diff --git a/src/lib/libptouch_internal.h b/src/lib/libptouch_internal.h
index ec4f454..4c27026 100644
--- a/src/lib/libptouch_internal.h
+++ b/src/lib/libptouch_internal.h
@@ -25,13 +25,18 @@ struct libptouch_ctx {
uint16_t usb_pid; /* 0 if USB not open; used to pick 128- vs 560-dot raster */
};
+/* ctx に最終エラー(コードとメッセージ)を記録する。公開 API のエラー返却前に使う。 */
void ptouch_set_error(libptouch_ctx *ctx, libptouch_err_t code, const char *msg);
+/* libusb のエラー番号を人が読めるメッセージにして ptouch_set_error に渡す。 */
void ptouch_set_error_usb(libptouch_ctx *ctx, int libusb_err, const char *what);
+/* bulk OUT 転送。成功時 0、失敗時 libusb の負のエラーコード。 */
int ptouch_bulk_out(libusb_device_handle *h, uint8_t ep, const uint8_t *data,
int len, unsigned int timeout_ms);
+/* bulk IN で len バイトを揃えるまで読む。成功時 0、失敗時 libusb の負のエラーコード。 */
int ptouch_bulk_in_exact(libusb_device_handle *h, uint8_t ep, uint8_t *buf,
int len, unsigned int timeout_ms);
+/* ctx の bulk OUT エンドポイントへ buf を送る。印刷ジョブのチャンク送信用。 */
libptouch_err_t ptouch_bulk_send_job(libptouch_ctx *ctx, const uint8_t *buf,
size_t len, const char *what);
diff --git a/src/lib/libptouch_media_info.c b/src/lib/libptouch_media_info.c
new file mode 100644
index 0000000..ffac354
--- /dev/null
+++ b/src/lib/libptouch_media_info.c
@@ -0,0 +1,100 @@
+/*
+ * libptouch — current tape/media information helper
+ *
+ * Author: knb
+ * Email: knb@artif.org
+ */
+
+#include "libptouch_internal.h"
+#include "libptouch_layout.h"
+
+#include
+
+static double tape_width_mm_from_code(uint8_t media_w)
+{
+ switch (media_w) {
+ case 0x04:
+ return 3.5;
+ case 0x06:
+ return 6.0;
+ case 0x09:
+ return 9.0;
+ case 0x0C:
+ return 12.0;
+ case 0x12:
+ return 18.0;
+ case 0x18:
+ return 24.0;
+ case 0x24:
+ return 36.0;
+ case 0x15:
+ return 21.0; /* FLe width code */
+ default:
+ return 0.0;
+ }
+}
+
+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)
+{
+ if (!ctx || !out_info) {
+ if (ctx)
+ ptouch_set_error(ctx, LIBPTOUCH_ERR_ARG, "null argument");
+ return LIBPTOUCH_ERR_ARG;
+ }
+
+ memset(out_info, 0, sizeof(*out_info));
+
+ uint8_t st[LIBPTOUCH_STATUS_LENGTH];
+ libptouch_err_t v = libptouch_get_status(ctx, st);
+ if (v != LIBPTOUCH_OK)
+ return v;
+
+ const ptouch_printer_profile_t *prof =
+ ptouch_layout_resolve_profile(ctx->usb_pid, st[4]);
+ if (!prof) {
+ ptouch_set_error(ctx, LIBPTOUCH_ERR_UNSUPPORTED,
+ "no layout profile for this printer");
+ return LIBPTOUCH_ERR_UNSUPPORTED;
+ }
+
+ uint16_t left_dots = 0, print_dots = 0, right_dots = 0;
+ v = ptouch_layout_from_status(prof, st[11], st[10], &left_dots,
+ &print_dots, &right_dots);
+ if (v != LIBPTOUCH_OK) {
+ ptouch_set_error(ctx, LIBPTOUCH_ERR_UNSUPPORTED,
+ "tape width/layout not supported for this media");
+ return LIBPTOUCH_ERR_UNSUPPORTED;
+ }
+
+ double tape_mm = tape_width_mm_from_code(st[10]);
+ if (tape_mm <= 0.0) {
+ ptouch_set_error(ctx, LIBPTOUCH_ERR_UNSUPPORTED,
+ "unknown tape width code");
+ return LIBPTOUCH_ERR_UNSUPPORTED;
+ }
+
+ 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->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;
+
+ ptouch_set_error(ctx, LIBPTOUCH_OK, "");
+ return LIBPTOUCH_OK;
+}
diff --git a/src/lib/libptouch_svg.c b/src/lib/libptouch_svg.c
new file mode 100644
index 0000000..e92446c
--- /dev/null
+++ b/src/lib/libptouch_svg.c
@@ -0,0 +1,211 @@
+/*
+ * libptouch — SVG -> 1bit packed raster (librsvg + cairo)
+ *
+ * Author: knb
+ * Email: knb@artif.org
+ */
+
+#include "libptouch_internal.h"
+#include "libptouch_layout.h"
+
+#ifdef LIBPTOUCH_HAS_RSVG
+#include
+#include
+#endif
+
+#include
+#include
+#include
+
+libptouch_err_t libptouch_svg_file_to_raster_fit_current_tape(
+ libptouch_ctx *ctx, const char *path,
+ const libptouch_svg_options_t *options, uint8_t **out_raster,
+ size_t *out_raster_bytes, libptouch_raster_params_t *out_params)
+{
+#ifndef LIBPTOUCH_HAS_RSVG
+ (void)path;
+ (void)options;
+ (void)out_raster;
+ (void)out_raster_bytes;
+ (void)out_params;
+ if (ctx) {
+ ptouch_set_error(ctx, LIBPTOUCH_ERR_UNSUPPORTED,
+ "SVG support is not built in (missing librsvg-2.0)");
+ }
+ return LIBPTOUCH_ERR_UNSUPPORTED;
+#else
+ if (!ctx || !path || !out_raster || !out_raster_bytes || !out_params) {
+ if (ctx)
+ ptouch_set_error(ctx, LIBPTOUCH_ERR_ARG, "null argument");
+ return LIBPTOUCH_ERR_ARG;
+ }
+ if (!ctx->usb_open || !ctx->bulk_out_ep) {
+ ptouch_set_error(ctx, LIBPTOUCH_ERR_IO,
+ "not connected (open USB before SVG conversion)");
+ return LIBPTOUCH_ERR_IO;
+ }
+
+ uint8_t thr = LIBPTOUCH_PNG_DEFAULT_THRESHOLD;
+ if (options)
+ thr = options->threshold;
+
+ *out_raster = NULL;
+ *out_raster_bytes = 0;
+ out_params->width_dots = 0;
+ out_params->height_dots = 0;
+ out_params->margin_mm = 0;
+
+ uint8_t st[LIBPTOUCH_STATUS_LENGTH];
+ libptouch_err_t v = libptouch_get_status(ctx, st);
+ if (v != LIBPTOUCH_OK)
+ return v;
+
+ const ptouch_printer_profile_t *prof =
+ ptouch_layout_resolve_profile(ctx->usb_pid, st[4]);
+ if (!prof) {
+ ptouch_set_error(ctx, LIBPTOUCH_ERR_UNSUPPORTED,
+ "no layout profile for this printer");
+ return LIBPTOUCH_ERR_UNSUPPORTED;
+ }
+
+ uint16_t left_dots = 0, print_dots = 0, right_dots = 0;
+ v = ptouch_layout_from_status(prof, st[11], st[10], &left_dots,
+ &print_dots, &right_dots);
+ if (v != LIBPTOUCH_OK) {
+ ptouch_set_error(ctx, LIBPTOUCH_ERR_UNSUPPORTED,
+ "tape width/layout not supported for this media");
+ return LIBPTOUCH_ERR_UNSUPPORTED;
+ }
+ (void)left_dots;
+ (void)right_dots;
+
+ GError *gerr = NULL;
+ RsvgHandle *handle = rsvg_handle_new_from_file(path, &gerr);
+ if (!handle) {
+ const char *msg = (gerr && gerr->message) ? gerr->message :
+ "failed to load SVG";
+ ptouch_set_error(ctx, LIBPTOUCH_ERR_IMAGE, msg);
+ if (gerr)
+ g_error_free(gerr);
+ return LIBPTOUCH_ERR_IMAGE;
+ }
+
+ double svg_w = 0.0;
+ double svg_h = 0.0;
+ if (!rsvg_handle_get_intrinsic_size_in_pixels(handle, &svg_w, &svg_h) ||
+ svg_w <= 0.0 || svg_h <= 0.0) {
+ g_object_unref(handle);
+ ptouch_set_error(ctx, LIBPTOUCH_ERR_IMAGE,
+ "invalid SVG dimensions");
+ return LIBPTOUCH_ERR_IMAGE;
+ }
+
+ /*
+ * libptouch_print_raster() transposes the incoming raster before packing
+ * lines, so printable tape width maps to the source raster height.
+ */
+ double scale = (double)print_dots / svg_h;
+ if (scale <= 0.0 || !isfinite(scale)) {
+ g_object_unref(handle);
+ ptouch_set_error(ctx, LIBPTOUCH_ERR_IMAGE, "invalid SVG scale");
+ return LIBPTOUCH_ERR_IMAGE;
+ }
+
+ uint32_t out_h = (uint32_t)print_dots;
+ uint32_t out_w = (uint32_t)ceil(svg_w * scale);
+ if (out_h == 0)
+ out_h = 1;
+ if (out_h > 100000u) {
+ g_object_unref(handle);
+ ptouch_set_error(ctx, LIBPTOUCH_ERR_IMAGE, "scaled SVG too tall");
+ return LIBPTOUCH_ERR_IMAGE;
+ }
+
+ cairo_surface_t *surface = cairo_image_surface_create(
+ CAIRO_FORMAT_ARGB32, (int)out_w, (int)out_h);
+ if (cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) {
+ cairo_surface_destroy(surface);
+ g_object_unref(handle);
+ ptouch_set_error(ctx, LIBPTOUCH_ERR_NOMEM,
+ "cairo_image_surface_create failed");
+ return LIBPTOUCH_ERR_NOMEM;
+ }
+
+ cairo_t *cr = cairo_create(surface);
+ if (cairo_status(cr) != CAIRO_STATUS_SUCCESS) {
+ cairo_destroy(cr);
+ cairo_surface_destroy(surface);
+ g_object_unref(handle);
+ ptouch_set_error(ctx, LIBPTOUCH_ERR_NOMEM, "cairo_create failed");
+ return LIBPTOUCH_ERR_NOMEM;
+ }
+
+ cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
+ cairo_paint(cr);
+ RsvgRectangle viewport = { 0.0, 0.0, (double)out_w, (double)out_h };
+ GError *render_err = NULL;
+ if (!rsvg_handle_render_document(handle, cr, &viewport, &render_err)) {
+ cairo_destroy(cr);
+ cairo_surface_destroy(surface);
+ g_object_unref(handle);
+ const char *msg = (render_err && render_err->message) ?
+ render_err->message :
+ "SVG render failed";
+ ptouch_set_error(ctx, LIBPTOUCH_ERR_IMAGE, msg);
+ if (render_err)
+ g_error_free(render_err);
+ return LIBPTOUCH_ERR_IMAGE;
+ }
+ cairo_destroy(cr);
+ g_object_unref(handle);
+
+ cairo_surface_flush(surface);
+ int stride = cairo_image_surface_get_stride(surface);
+ const uint8_t *pix = cairo_image_surface_get_data(surface);
+
+ size_t row_bytes = ((size_t)out_w + 7u) / 8u;
+ if (out_h > SIZE_MAX / row_bytes) {
+ cairo_surface_destroy(surface);
+ ptouch_set_error(ctx, LIBPTOUCH_ERR_IMAGE, "raster size overflow");
+ return LIBPTOUCH_ERR_IMAGE;
+ }
+ size_t total = row_bytes * (size_t)out_h;
+ uint8_t *out = (uint8_t *)calloc(1, total);
+ if (!out) {
+ cairo_surface_destroy(surface);
+ ptouch_set_error(ctx, LIBPTOUCH_ERR_NOMEM, "calloc raster failed");
+ return LIBPTOUCH_ERR_NOMEM;
+ }
+
+ for (uint32_t y = 0; y < out_h; y++) {
+ const uint32_t *src_row =
+ (const uint32_t *)(pix + (size_t)y * (size_t)stride);
+ uint8_t *dst_row = out + (size_t)y * row_bytes;
+ for (uint32_t x = 0; x < out_w; x++) {
+ uint32_t p = src_row[x];
+ unsigned a = (p >> 24) & 0xFFu;
+ if (a < 128u)
+ continue;
+ unsigned r = (p >> 16) & 0xFFu;
+ unsigned g = (p >> 8) & 0xFFu;
+ unsigned b = p & 0xFFu;
+ unsigned yv = (77u * r + 150u * g + 29u * b) >> 8;
+ if (yv >= (unsigned)thr)
+ continue;
+ size_t byte = (size_t)x / 8u;
+ size_t bit = (size_t)x % 8u;
+ dst_row[byte] |= (uint8_t)(1u << (7u - bit));
+ }
+ }
+
+ cairo_surface_destroy(surface);
+
+ out_params->width_dots = out_w;
+ out_params->height_dots = out_h;
+ out_params->margin_mm = 0;
+ *out_raster = out;
+ *out_raster_bytes = total;
+ ptouch_set_error(ctx, LIBPTOUCH_OK, "");
+ return LIBPTOUCH_OK;
+#endif
+}