ptouch-label/ptouch-print のオプション整理と trim-right 対応を反映

CLIヘルプ文言を簡潔化しつつ、右余白トリム機能と関連API・ドキュメント更新をまとめて取り込み、PNG/SVG/テンプレート経路での利用体験を揃える。

Made-with: Cursor
This commit is contained in:
knb
2026-04-16 19:09:37 +09:00
parent 32ab12f661
commit 094f183994
13 changed files with 272 additions and 24 deletions

View File

@@ -27,6 +27,7 @@ set(LIBPTOUCH_SOURCES
src/lib/libptouch_usb.c
src/lib/libptouch_layout.c
src/lib/libptouch_media_info.c
src/lib/libptouch_trim.c
src/lib/libptouch_print.c
src/lib/libptouch_status.c
src/lib/libptouch_png.c

View File

@@ -40,6 +40,7 @@ cmake --build build
**PNG**(拡張子 `.png` または PNG シグネチャ)の場合は幅・高さは画像から取得します。
**SVG**(拡張子 `.svg`の場合は現在装着中テープの印字可能ドット幅に合わせて自動拡大・縮小しますUSB 接続が必要)。
`--trim-right[=DOTS]` で右側空白列を削減できますDOTS 省略時は左余白ドット、取得失敗時は 0
任意で `-t`0255で二値化しきい値を指定できます。
**1bit packed ラスター**(行優先、行あたり `ceil(width/8)` バイト × 行数)の場合は `-f` に加え `-w` / `-H` が必須です。
@@ -49,6 +50,8 @@ cmake --build build
```bash
# PNG — 検証のみUSB 不要)
./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 必要)
./build/ptouch-print -n -f label.svg

View File

@@ -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_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 準拠)。
* 印字前にステータスでテープ幅を読み、印刷可能ドット内に画像を中央配置する。

View File

@@ -5,5 +5,5 @@ source "https://rubygems.org"
gemspec
group :development do
gem "rubocop", "~> 1.69", require: false
gem "rubocop", "~> 1.86", require: false
end

View File

@@ -3,6 +3,7 @@ PATH
specs:
libptouch (1.0.0)
ffi (~> 1.15)
rexml
GEM
remote: https://rubygems.org/
@@ -30,6 +31,7 @@ GEM
racc (1.8.1)
rainbow (3.1.1)
regexp_parser (2.12.0)
rexml (3.4.4)
rubocop (1.86.1)
json (~> 2.3)
language_server-protocol (~> 3.17.0.2)
@@ -64,7 +66,7 @@ PLATFORMS
DEPENDENCIES
libptouch!
rubocop (~> 1.69)
rubocop (~> 1.86)
CHECKSUMS
ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383
@@ -89,6 +91,7 @@ CHECKSUMS
racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f
rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a
regexp_parser (2.12.0) sha256=35a916a1d63190ab5c9009457136ae5f3c0c7512d60291d0d1378ba18ce08ebb
rexml (3.4.4) sha256=19e0a2c3425dfbf2d4fc1189747bdb2f849b6c5e74180401b15734bc97b5d142
rubocop (1.86.1) sha256=44415f3f01d01a21e01132248d2fd0867572475b566ca188a0a42133a08d4531
rubocop-ast (1.49.1) sha256=4412f3ee70f6fe4546cc489548e0f6fcf76cafcfa80fa03af67098ffed755035
ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33

View File

@@ -39,6 +39,7 @@ 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` 属性をキーにした差込印刷が可能です。
`--trim-right[=DOTS]` を付けると、libptouch 側の共通処理でラベル右側の空白ドット列を削減します。`DOTS` 省略時は左余白ドット数を使い、取得失敗時は `0` にフォールバックします。
開発ツリーからそのまま試す例:
@@ -46,6 +47,8 @@ SVG は現在装着テープの印字可能幅に合わせて自動拡大・縮
bundle exec ruby -I lib exe/ptouch-label --help
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 --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
@@ -96,7 +99,7 @@ ctx.dispose
## API の範囲
- 実行ファイル `ptouch-label`(互換: `ptouch-print-png`)… PNG/SVG`-f`, `-t`, `-p`, `-n`, `-M`, `-S`, `-V`, `-h`, `--template`, `--data`)。`-M` は現在テープ情報(幅 mm・DPI 等)を JSON 出力、`-S` はステータス JSON 出力
- 実行ファイル `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`
- `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`

0
ruby/exe/ptouch-label Normal file → Executable file
View File

View File

@@ -59,6 +59,8 @@ module Libptouch
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_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_png_file_to_raster,
%i[pointer string pointer pointer pointer pointer], :int

View File

@@ -70,6 +70,7 @@ module Libptouch
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)
@@ -85,6 +86,7 @@ module Libptouch
usb_pid: nil,
usb_pid_invalid: false,
dry_run: false,
trim_right: nil,
media_info: false,
status: false,
version: false,
@@ -114,6 +116,15 @@ module Libptouch
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?
@@ -131,17 +142,21 @@ module Libptouch
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,
"二値化しきい値 0255既定 #{Libptouch::PNG_DEFAULT_THRESHOLD}、PNG/SVG") 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|
apply_usb_pid_option(opts_hash, v)
end
p.on("-n", "--dry-run", "読み込みと検証のみUSB なし)") { opts_hash[:dry_run] = true }
p.on("-M", "--media-info", "現在テープの幅(mm)・DPI・最小余白(mm)を JSON で表示して終了") do
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", "USB プリンタのステータスを JSON で表示して終了") do
p.on("-S", "--status", "ステータスを JSON で表示して終了") do
opts_hash[:status] = true
end
p.on("-V", "--version", "バージョンを表示して終了") { opts_hash[:version] = true }
@@ -153,15 +168,16 @@ module Libptouch
<<~BANNER
Usage: ptouch-label [options]
PNG/SVG -w/-H
--template/--data SVG
SVG USB
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[:threshold].nil?
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
@@ -186,17 +202,21 @@ module Libptouch
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,
"二値化しきい値 0255既定 #{Libptouch::PNG_DEFAULT_THRESHOLD}、PNG/SVG") 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|
apply_usb_pid_option(opts_hash, v)
end
parser.on("-n", "--dry-run", "読み込みと検証のみUSB なし)") { opts_hash[:dry_run] = true }
parser.on("-M", "--media-info", "現在テープの幅(mm)・DPI・最小余白(mm)を JSON で表示して終了") do
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", "USB プリンタのステータスを JSON で表示して終了") do
parser.on("-S", "--status", "ステータスを JSON で表示して終了") do
opts_hash[:status] = true
end
parser.on("-V", "--version", "バージョンを表示して終了") { opts_hash[:version] = true }
@@ -319,9 +339,11 @@ module Libptouch
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
@@ -333,6 +355,16 @@ module Libptouch
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]
@@ -340,7 +372,7 @@ module Libptouch
return 0
end
open_usb_for_opts(ctx, opts) if kind == :png
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
@@ -351,6 +383,22 @@ module Libptouch
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)"

View File

@@ -115,6 +115,29 @@ 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)
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
buf = FFI::MemoryPointer.new(:uint8, STATUS_LENGTH)
raise_on_error(Binding.libptouch_get_status(@native, buf))

View File

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

View File

@@ -21,15 +21,16 @@ static void usage(const char *argv0)
" -f, --file PATH 入力ファイル\n"
" -w, --width DOTS 1bit ラスター時: 幅(ドット)\n"
" -H, --height DOTS 1bit ラスター時: 高さ(ドット)\n"
" -t, --threshold N 二値化しきい値 0255既定 %u、PNG/SVG\n"
" -t, --threshold N しきい値 0255既定 %u、PNG/SVG\n"
" --trim-right[=DOTS] 右側空白を削減(省略時は左余白、失敗時 0\n"
" -n, --dry-run 読み込みと check_raster のみUSB なし)\n"
" -p, --pid HEX USB 製品 ID既定: P900W の 0x2085。例: P750W 0x2062、P710BT 0x20af\n"
" -S, --status USB 接続プリンタのステータス(テープ種・幅・色等)を表示して終了\n"
" -S, --status ステータスを表示して終了\n"
" -V, --version バージョンを表示して終了\n"
" -h, --help このヘルプ\n"
"\n"
"PNG は幅・高さを画像から取得-w/-H 不要)。\n"
"SVG は現在テープの印字可能幅に合わせて自動拡大・縮小USB 必須)。\n"
"PNG は画像サイズを使用-w/-H 不要)。\n"
"SVG は現在テープ幅に自動フィットUSB 必須)。\n"
"1bit packed ラスター(行優先)の場合は -f -w -H が必須。\n"
"--status のときは -f は不要(他オプションは無視されます)。\n",
argv0, (unsigned)LIBPTOUCH_PNG_DEFAULT_THRESHOLD);
@@ -107,6 +108,9 @@ int main(int argc, char **argv)
unsigned threshold = LIBPTOUCH_PNG_DEFAULT_THRESHOLD;
unsigned usb_pid_arg = 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 want_status = 0;
static struct option longopts[] = {
@@ -114,6 +118,7 @@ int main(int argc, char **argv)
{ "height", required_argument, NULL, 'H' },
{ "file", required_argument, NULL, 'f' },
{ "threshold", required_argument, NULL, 't' },
{ "trim-right", optional_argument, NULL, 'r' },
{ "dry-run", no_argument, NULL, 'n' },
{ "pid", required_argument, NULL, 'p' },
{ "status", no_argument, NULL, 'S' },
@@ -123,7 +128,7 @@ int main(int argc, char **argv)
};
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) {
switch (c) {
case 'w':
@@ -146,6 +151,20 @@ int main(int argc, char **argv)
case 'n':
dry_run = 1;
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':
usb_pid_arg = (unsigned)strtoul(optarg, NULL, 0);
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_err_t e;
int usb_opened = 0;
int data_from_lib = 0;
if (png) {
libptouch_png_options_t opt = { .threshold = (uint8_t)threshold };
@@ -250,6 +270,7 @@ int main(int argc, char **argv)
libptouch_destroy(ctx);
return 1;
}
data_from_lib = 1;
} else if (svg) {
e = usb_pid_arg != 0
? libptouch_open_usb_vid_pid(ctx, LIBPTOUCH_USB_VID_BROTHER,
@@ -272,6 +293,7 @@ int main(int argc, char **argv)
libptouch_destroy(ctx);
return 1;
}
data_from_lib = 1;
} else {
if (read_file(file, &data, &data_len) != 0) {
libptouch_destroy(ctx);
@@ -282,12 +304,60 @@ int main(int argc, char **argv)
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, &params, 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, &params);
if (e != LIBPTOUCH_OK) {
fprintf(stderr, "check_raster: %s\n",
libptouch_strerror(ctx));
libptouch_destroy(ctx);
if (png || svg)
if (data_from_lib)
libptouch_free_raster(data);
else
free(data);
@@ -304,7 +374,7 @@ int main(int argc, char **argv)
if (usb_opened)
libptouch_close(ctx);
libptouch_destroy(ctx);
if (png || svg)
if (data_from_lib)
libptouch_free_raster(data);
else
free(data);
@@ -319,7 +389,7 @@ int main(int argc, char **argv)
if (e != LIBPTOUCH_OK) {
fprintf(stderr, "open_usb: %s\n", libptouch_strerror(ctx));
libptouch_destroy(ctx);
if (png || svg)
if (data_from_lib)
libptouch_free_raster(data);
else
free(data);
@@ -333,7 +403,7 @@ int main(int argc, char **argv)
libptouch_strerror(ctx));
libptouch_close(ctx);
libptouch_destroy(ctx);
if (png || svg)
if (data_from_lib)
libptouch_free_raster(data);
else
free(data);
@@ -342,7 +412,7 @@ int main(int argc, char **argv)
libptouch_close(ctx);
libptouch_destroy(ctx);
if (png || svg)
if (data_from_lib)
libptouch_free_raster(data);
else
free(data);

84
src/lib/libptouch_trim.c Normal file
View 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;
}