From ee466f287273654c2ebe28a08e0352b67d29006e Mon Sep 17 00:00:00 2001
From: celogeek <65178+celogeek@users.noreply.github.com>
Date: Sat, 27 Apr 2024 17:55:58 +0200
Subject: [PATCH 01/11] update deps

---
 go.mod |  8 ++++----
 go.sum | 14 ++++++++------
 2 files changed, 12 insertions(+), 10 deletions(-)

diff --git a/go.mod b/go.mod
index c758477..893f94f 100644
--- a/go.mod
+++ b/go.mod
@@ -22,8 +22,8 @@ require (
 	github.com/hashicorp/go-version v1.6.0 // indirect
 	github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
 	github.com/rivo/uniseg v0.4.7 // indirect
-	github.com/stretchr/testify v1.8.4 // indirect
-	golang.org/x/net v0.21.0 // indirect
-	golang.org/x/sys v0.17.0 // indirect
-	golang.org/x/term v0.17.0 // indirect
+	github.com/stretchr/testify v1.9.0 // indirect
+	golang.org/x/net v0.24.0 // indirect
+	golang.org/x/sys v0.19.0 // indirect
+	golang.org/x/term v0.19.0 // indirect
 )
diff --git a/go.sum b/go.sum
index d2e2dde..e443d1d 100644
--- a/go.sum
+++ b/go.sum
@@ -35,19 +35,21 @@ github.com/schollz/progressbar/v3 v3.14.2 h1:EducH6uNLIWsr560zSV1KrTeUb/wZGAHqyM
 github.com/schollz/progressbar/v3 v3.14.2/go.mod h1:aQAZQnhF4JGFtRJiw/eobaXpsqpVQAftEQ+hLGXaRc4=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
-github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
-github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e h1:IWllFTiDjjLIf2oeKxpIUmtiDV5sn71VgeQgg6vcE7k=
 github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e/go.mod h1:d7u6HkTYKSv5m6MCKkOQlHwaShTMl3HjqSGW3XtVhXM=
 golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
 golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
-golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
-golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
+golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
+golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
 golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U=
+golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
+golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
+golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
+golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

From c75c58dede4ee90a41be818159a0af644e1017c5 Mon Sep 17 00:00:00 2001
From: celogeek <65178+celogeek@users.noreply.github.com>
Date: Sat, 27 Apr 2024 17:57:49 +0200
Subject: [PATCH 02/11] add crop limit option

---
 internal/converter/converter.go               |  1 +
 .../converter/options/converter_options.go    |  7 ++-
 .../epub_image_filters_autocrop.go            | 14 +++---
 .../imageprocessor/epub_image_processor.go    |  4 +-
 internal/epub/options/epub_options.go         |  1 +
 main.go                                       | 43 +++++++++++++------
 6 files changed, 47 insertions(+), 23 deletions(-)

diff --git a/internal/converter/converter.go b/internal/converter/converter.go
index 83c0b07..c45d791 100644
--- a/internal/converter/converter.go
+++ b/internal/converter/converter.go
@@ -113,6 +113,7 @@ func (c *Converter) InitParse() {
 	c.AddIntParam(&c.Options.CropRatioUp, "crop-ratio-up", c.Options.CropRatioUp, "Crop ratio up: ratio of pixels allow to be non blank while cutting on the top.")
 	c.AddIntParam(&c.Options.CropRatioRight, "crop-ratio-right", c.Options.CropRatioRight, "Crop ratio right: ratio of pixels allow to be non blank while cutting on the right.")
 	c.AddIntParam(&c.Options.CropRatioBottom, "crop-ratio-bottom", c.Options.CropRatioBottom, "Crop ratio bottom: ratio of pixels allow to be non blank while cutting on the bottom.")
+	c.AddIntParam(&c.Options.CropLimit, "crop-limit", c.Options.CropLimit, "Crop limit: maximum number of pixel to crop on all side. 0 mean unlimited.")
 	c.AddIntParam(&c.Options.Brightness, "brightness", c.Options.Brightness, "Brightness readjustment: between -100 and 100, > 0 lighter, < 0 darker")
 	c.AddIntParam(&c.Options.Contrast, "contrast", c.Options.Contrast, "Contrast readjustment: between -100 and 100, > 0 more contrast, < 0 less contrast")
 	c.AddBoolParam(&c.Options.AutoContrast, "autocontrast", c.Options.AutoContrast, "Improve contrast automatically")
diff --git a/internal/converter/options/converter_options.go b/internal/converter/options/converter_options.go
index fc7daaf..9e18f17 100644
--- a/internal/converter/options/converter_options.go
+++ b/internal/converter/options/converter_options.go
@@ -8,8 +8,9 @@ import (
 	"path/filepath"
 	"strings"
 
-	"github.com/celogeek/go-comic-converter/v2/internal/converter/profiles"
 	"gopkg.in/yaml.v3"
+
+	"github.com/celogeek/go-comic-converter/v2/internal/converter/profiles"
 )
 
 type Options struct {
@@ -29,6 +30,7 @@ type Options struct {
 	CropRatioUp                int     `yaml:"crop_ratio_up"`
 	CropRatioRight             int     `yaml:"crop_ratio_right"`
 	CropRatioBottom            int     `yaml:"crop_ratio_bottom"`
+	CropLimit                  int     `yaml:"crop_limit"`
 	Brightness                 int     `yaml:"brightness"`
 	Contrast                   int     `yaml:"contrast"`
 	AutoContrast               bool    `yaml:"auto_contrast"`
@@ -161,6 +163,7 @@ func (o *Options) MarshalJSON() ([]byte, error) {
 			"up":     o.CropRatioUp,
 			"bottom": o.CropRatioBottom,
 		}
+		out["crop_limit"] = o.CropLimit
 	}
 	if o.Brightness != 0 {
 		out["brightness"] = o.Brightness
@@ -265,7 +268,7 @@ func (o *Options) ShowConfig() string {
 		{"Grayscale", o.Grayscale, true},
 		{"Grayscale mode", grayscaleMode, o.Grayscale},
 		{"Crop", o.Crop, true},
-		{"Crop ratio", fmt.Sprintf("%d Left - %d Up - %d Right - %d Bottom", o.CropRatioLeft, o.CropRatioUp, o.CropRatioRight, o.CropRatioBottom), o.Crop},
+		{"Crop ratio", fmt.Sprintf("%d Left - %d Up - %d Right - %d Bottom - %d Limit", o.CropRatioLeft, o.CropRatioUp, o.CropRatioRight, o.CropRatioBottom, o.CropLimit), o.Crop},
 		{"Brightness", o.Brightness, o.Brightness != 0},
 		{"Contrast", o.Contrast, o.Contrast != 0},
 		{"Auto contrast", o.AutoContrast, true},
diff --git a/internal/epub/imagefilters/epub_image_filters_autocrop.go b/internal/epub/imagefilters/epub_image_filters_autocrop.go
index e3929fb..8d48de2 100644
--- a/internal/epub/imagefilters/epub_image_filters_autocrop.go
+++ b/internal/epub/imagefilters/epub_image_filters_autocrop.go
@@ -8,9 +8,9 @@ import (
 )
 
 // AutoCrop Lookup for margin and crop
-func AutoCrop(img image.Image, bounds image.Rectangle, cutRatioLeft, cutRatioUp, cutRatioRight, cutRatioBottom int) gift.Filter {
+func AutoCrop(img image.Image, bounds image.Rectangle, cutRatioLeft, cutRatioUp, cutRatioRight, cutRatioBottom int, limit int) gift.Filter {
 	return gift.Crop(
-		findMargin(img, bounds, cutRatioOptions{cutRatioLeft, cutRatioUp, cutRatioRight, cutRatioBottom}),
+		findMargin(img, bounds, cutRatioOptions{cutRatioLeft, cutRatioUp, cutRatioRight, cutRatioBottom}, limit),
 	)
 }
 
@@ -25,11 +25,11 @@ type cutRatioOptions struct {
 	Left, Up, Right, Bottom int
 }
 
-func findMargin(img image.Image, bounds image.Rectangle, cutRatio cutRatioOptions) image.Rectangle {
+func findMargin(img image.Image, bounds image.Rectangle, cutRatio cutRatioOptions, limit int) image.Rectangle {
 	imgArea := bounds
 
 LEFT:
-	for x := imgArea.Min.X; x < imgArea.Max.X; x++ {
+	for x, maxCut := imgArea.Min.X, limit; x < imgArea.Max.X && (limit == 0 || maxCut > 0); x, maxCut = x+1, maxCut-1 {
 		allowNonBlank := imgArea.Dy() * cutRatio.Left / 100
 		for y := imgArea.Min.Y; y < imgArea.Max.Y; y++ {
 			if !colorIsBlank(img.At(x, y)) {
@@ -43,7 +43,7 @@ LEFT:
 	}
 
 UP:
-	for y := imgArea.Min.Y; y < imgArea.Max.Y; y++ {
+	for y, maxCut := imgArea.Min.Y, limit; y < imgArea.Max.Y && (limit == 0 || maxCut > 0); y, maxCut = y+1, maxCut-1 {
 		allowNonBlank := imgArea.Dx() * cutRatio.Up / 100
 		for x := imgArea.Min.X; x < imgArea.Max.X; x++ {
 			if !colorIsBlank(img.At(x, y)) {
@@ -57,7 +57,7 @@ UP:
 	}
 
 RIGHT:
-	for x := imgArea.Max.X - 1; x >= imgArea.Min.X; x-- {
+	for x, maxCut := imgArea.Max.X-1, limit; x >= imgArea.Min.X && (limit == 0 || maxCut > 0); x, maxCut = x-1, maxCut-1 {
 		allowNonBlank := imgArea.Dy() * cutRatio.Right / 100
 		for y := imgArea.Min.Y; y < imgArea.Max.Y; y++ {
 			if !colorIsBlank(img.At(x, y)) {
@@ -71,7 +71,7 @@ RIGHT:
 	}
 
 BOTTOM:
-	for y := imgArea.Max.Y - 1; y >= imgArea.Min.Y; y-- {
+	for y, maxCut := imgArea.Max.Y-1, limit; y >= imgArea.Min.Y && (limit == 0 || maxCut > 0); y, maxCut = y-1, maxCut-1 {
 		allowNonBlank := imgArea.Dx() * cutRatio.Bottom / 100
 		for x := imgArea.Min.X; x < imgArea.Max.X; x++ {
 			if !colorIsBlank(img.At(x, y)) {
diff --git a/internal/epub/imageprocessor/epub_image_processor.go b/internal/epub/imageprocessor/epub_image_processor.go
index cfa2385..30db08e 100644
--- a/internal/epub/imageprocessor/epub_image_processor.go
+++ b/internal/epub/imageprocessor/epub_image_processor.go
@@ -9,12 +9,13 @@ import (
 	"os"
 	"sync"
 
+	"github.com/disintegration/gift"
+
 	epubimage "github.com/celogeek/go-comic-converter/v2/internal/epub/image"
 	epubimagefilters "github.com/celogeek/go-comic-converter/v2/internal/epub/imagefilters"
 	epuboptions "github.com/celogeek/go-comic-converter/v2/internal/epub/options"
 	epubprogress "github.com/celogeek/go-comic-converter/v2/internal/epub/progress"
 	epubzip "github.com/celogeek/go-comic-converter/v2/internal/epub/zip"
-	"github.com/disintegration/gift"
 )
 
 type EPUBImageProcessor struct {
@@ -189,6 +190,7 @@ func (e *EPUBImageProcessor) transformImage(input *task, part int, right bool) *
 			e.Image.Crop.Up,
 			e.Image.Crop.Right,
 			e.Image.Crop.Bottom,
+			e.Image.Crop.Limit,
 		)
 
 		// detect if blank image
diff --git a/internal/epub/options/epub_options.go b/internal/epub/options/epub_options.go
index 17665dc..041ae43 100644
--- a/internal/epub/options/epub_options.go
+++ b/internal/epub/options/epub_options.go
@@ -6,6 +6,7 @@ import "fmt"
 type Crop struct {
 	Enabled                 bool
 	Left, Up, Right, Bottom int
+	Limit                   int
 }
 
 type Color struct {
diff --git a/main.go b/main.go
index 7ae381d..8be2cd6 100644
--- a/main.go
+++ b/main.go
@@ -13,10 +13,11 @@ import (
 	"os"
 	"runtime/debug"
 
+	"github.com/tcnksm/go-latest"
+
 	"github.com/celogeek/go-comic-converter/v2/internal/converter"
 	"github.com/celogeek/go-comic-converter/v2/internal/epub"
 	epuboptions "github.com/celogeek/go-comic-converter/v2/internal/epub/options"
-	"github.com/tcnksm/go-latest"
 )
 
 func main() {
@@ -122,18 +123,34 @@ $ go install github.com/celogeek/go-comic-converter/v%d@%s
 		Quiet:                      cmd.Options.Quiet,
 		Json:                       cmd.Options.Json,
 		Image: &epuboptions.Image{
-			Crop:                   &epuboptions.Crop{Enabled: cmd.Options.Crop, Left: cmd.Options.CropRatioLeft, Up: cmd.Options.CropRatioUp, Right: cmd.Options.CropRatioRight, Bottom: cmd.Options.CropRatioBottom},
-			Quality:                cmd.Options.Quality,
-			Brightness:             cmd.Options.Brightness,
-			Contrast:               cmd.Options.Contrast,
-			AutoContrast:           cmd.Options.AutoContrast,
-			AutoRotate:             cmd.Options.AutoRotate,
-			AutoSplitDoublePage:    cmd.Options.AutoSplitDoublePage,
-			KeepDoublePageIfSplit:  cmd.Options.KeepDoublePageIfSplit,
-			NoBlankImage:           cmd.Options.NoBlankImage,
-			Manga:                  cmd.Options.Manga,
-			HasCover:               cmd.Options.HasCover,
-			View:                   &epuboptions.View{Width: profile.Width, Height: profile.Height, AspectRatio: cmd.Options.AspectRatio, PortraitOnly: cmd.Options.PortraitOnly, Color: epuboptions.Color{Foreground: cmd.Options.ForegroundColor, Background: cmd.Options.BackgroundColor}},
+			Crop: &epuboptions.Crop{
+				Enabled: cmd.Options.Crop,
+				Left:    cmd.Options.CropRatioLeft,
+				Up:      cmd.Options.CropRatioUp,
+				Right:   cmd.Options.CropRatioRight,
+				Bottom:  cmd.Options.CropRatioBottom,
+				Limit:   cmd.Options.CropLimit,
+			},
+			Quality:               cmd.Options.Quality,
+			Brightness:            cmd.Options.Brightness,
+			Contrast:              cmd.Options.Contrast,
+			AutoContrast:          cmd.Options.AutoContrast,
+			AutoRotate:            cmd.Options.AutoRotate,
+			AutoSplitDoublePage:   cmd.Options.AutoSplitDoublePage,
+			KeepDoublePageIfSplit: cmd.Options.KeepDoublePageIfSplit,
+			NoBlankImage:          cmd.Options.NoBlankImage,
+			Manga:                 cmd.Options.Manga,
+			HasCover:              cmd.Options.HasCover,
+			View: &epuboptions.View{
+				Width:        profile.Width,
+				Height:       profile.Height,
+				AspectRatio:  cmd.Options.AspectRatio,
+				PortraitOnly: cmd.Options.PortraitOnly,
+				Color: epuboptions.Color{
+					Foreground: cmd.Options.ForegroundColor,
+					Background: cmd.Options.BackgroundColor,
+				},
+			},
 			GrayScale:              cmd.Options.Grayscale,
 			GrayScaleMode:          cmd.Options.GrayscaleMode,
 			Resize:                 !cmd.Options.NoResize,

From 9133493e60afffaf81cef66017c4c69547c6c773 Mon Sep 17 00:00:00 2001
From: celogeek <65178+celogeek@users.noreply.github.com>
Date: Sun, 28 Apr 2024 17:40:37 +0200
Subject: [PATCH 03/11] use percentage for cropping

---
 internal/converter/converter.go                        |  2 +-
 internal/converter/options/converter_options.go        |  2 +-
 .../epub/imagefilters/epub_image_filters_autocrop.go   | 10 ++++++----
 3 files changed, 8 insertions(+), 6 deletions(-)

diff --git a/internal/converter/converter.go b/internal/converter/converter.go
index c45d791..0a2be5f 100644
--- a/internal/converter/converter.go
+++ b/internal/converter/converter.go
@@ -113,7 +113,7 @@ func (c *Converter) InitParse() {
 	c.AddIntParam(&c.Options.CropRatioUp, "crop-ratio-up", c.Options.CropRatioUp, "Crop ratio up: ratio of pixels allow to be non blank while cutting on the top.")
 	c.AddIntParam(&c.Options.CropRatioRight, "crop-ratio-right", c.Options.CropRatioRight, "Crop ratio right: ratio of pixels allow to be non blank while cutting on the right.")
 	c.AddIntParam(&c.Options.CropRatioBottom, "crop-ratio-bottom", c.Options.CropRatioBottom, "Crop ratio bottom: ratio of pixels allow to be non blank while cutting on the bottom.")
-	c.AddIntParam(&c.Options.CropLimit, "crop-limit", c.Options.CropLimit, "Crop limit: maximum number of pixel to crop on all side. 0 mean unlimited.")
+	c.AddIntParam(&c.Options.CropLimit, "crop-limit", c.Options.CropLimit, "Crop limit: maximum number of cropping in percentage allowed. 0 mean unlimited.")
 	c.AddIntParam(&c.Options.Brightness, "brightness", c.Options.Brightness, "Brightness readjustment: between -100 and 100, > 0 lighter, < 0 darker")
 	c.AddIntParam(&c.Options.Contrast, "contrast", c.Options.Contrast, "Contrast readjustment: between -100 and 100, > 0 more contrast, < 0 less contrast")
 	c.AddBoolParam(&c.Options.AutoContrast, "autocontrast", c.Options.AutoContrast, "Improve contrast automatically")
diff --git a/internal/converter/options/converter_options.go b/internal/converter/options/converter_options.go
index 9e18f17..e4d8772 100644
--- a/internal/converter/options/converter_options.go
+++ b/internal/converter/options/converter_options.go
@@ -268,7 +268,7 @@ func (o *Options) ShowConfig() string {
 		{"Grayscale", o.Grayscale, true},
 		{"Grayscale mode", grayscaleMode, o.Grayscale},
 		{"Crop", o.Crop, true},
-		{"Crop ratio", fmt.Sprintf("%d Left - %d Up - %d Right - %d Bottom - %d Limit", o.CropRatioLeft, o.CropRatioUp, o.CropRatioRight, o.CropRatioBottom, o.CropLimit), o.Crop},
+		{"Crop ratio", fmt.Sprintf("%d Left - %d Up - %d Right - %d Bottom - %d%% Limit", o.CropRatioLeft, o.CropRatioUp, o.CropRatioRight, o.CropRatioBottom, o.CropLimit), o.Crop},
 		{"Brightness", o.Brightness, o.Brightness != 0},
 		{"Contrast", o.Contrast, o.Contrast != 0},
 		{"Auto contrast", o.AutoContrast, true},
diff --git a/internal/epub/imagefilters/epub_image_filters_autocrop.go b/internal/epub/imagefilters/epub_image_filters_autocrop.go
index 8d48de2..53abf0a 100644
--- a/internal/epub/imagefilters/epub_image_filters_autocrop.go
+++ b/internal/epub/imagefilters/epub_image_filters_autocrop.go
@@ -28,8 +28,10 @@ type cutRatioOptions struct {
 func findMargin(img image.Image, bounds image.Rectangle, cutRatio cutRatioOptions, limit int) image.Rectangle {
 	imgArea := bounds
 
+	maxCutX, maxCutY := imgArea.Dx()*limit/100, imgArea.Dy()*limit/100
+
 LEFT:
-	for x, maxCut := imgArea.Min.X, limit; x < imgArea.Max.X && (limit == 0 || maxCut > 0); x, maxCut = x+1, maxCut-1 {
+	for x, maxCut := imgArea.Min.X, maxCutX; x < imgArea.Max.X && (maxCutX == 0 || maxCut > 0); x, maxCut = x+1, maxCut-1 {
 		allowNonBlank := imgArea.Dy() * cutRatio.Left / 100
 		for y := imgArea.Min.Y; y < imgArea.Max.Y; y++ {
 			if !colorIsBlank(img.At(x, y)) {
@@ -43,7 +45,7 @@ LEFT:
 	}
 
 UP:
-	for y, maxCut := imgArea.Min.Y, limit; y < imgArea.Max.Y && (limit == 0 || maxCut > 0); y, maxCut = y+1, maxCut-1 {
+	for y, maxCut := imgArea.Min.Y, maxCutY; y < imgArea.Max.Y && (maxCutY == 0 || maxCut > 0); y, maxCut = y+1, maxCut-1 {
 		allowNonBlank := imgArea.Dx() * cutRatio.Up / 100
 		for x := imgArea.Min.X; x < imgArea.Max.X; x++ {
 			if !colorIsBlank(img.At(x, y)) {
@@ -57,7 +59,7 @@ UP:
 	}
 
 RIGHT:
-	for x, maxCut := imgArea.Max.X-1, limit; x >= imgArea.Min.X && (limit == 0 || maxCut > 0); x, maxCut = x-1, maxCut-1 {
+	for x, maxCut := imgArea.Max.X-1, maxCutX; x >= imgArea.Min.X && (maxCutX == 0 || maxCut > 0); x, maxCut = x-1, maxCut-1 {
 		allowNonBlank := imgArea.Dy() * cutRatio.Right / 100
 		for y := imgArea.Min.Y; y < imgArea.Max.Y; y++ {
 			if !colorIsBlank(img.At(x, y)) {
@@ -71,7 +73,7 @@ RIGHT:
 	}
 
 BOTTOM:
-	for y, maxCut := imgArea.Max.Y-1, limit; y >= imgArea.Min.Y && (limit == 0 || maxCut > 0); y, maxCut = y-1, maxCut-1 {
+	for y, maxCut := imgArea.Max.Y-1, maxCutY; y >= imgArea.Min.Y && (maxCutY == 0 || maxCut > 0); y, maxCut = y-1, maxCut-1 {
 		allowNonBlank := imgArea.Dx() * cutRatio.Bottom / 100
 		for x := imgArea.Min.X; x < imgArea.Max.X; x++ {
 			if !colorIsBlank(img.At(x, y)) {

From c500755a4e77f365efca8a957ab96312854b3884 Mon Sep 17 00:00:00 2001
From: celogeek <65178+celogeek@users.noreply.github.com>
Date: Sun, 28 Apr 2024 17:45:02 +0200
Subject: [PATCH 04/11] add limit to crop limit

---
 internal/converter/converter.go                 | 5 +++++
 internal/converter/options/converter_options.go | 2 +-
 2 files changed, 6 insertions(+), 1 deletion(-)

diff --git a/internal/converter/converter.go b/internal/converter/converter.go
index 0a2be5f..39f6730 100644
--- a/internal/converter/converter.go
+++ b/internal/converter/converter.go
@@ -389,6 +389,11 @@ func (c *Converter) Validate() error {
 		return errors.New("grayscale mode should be 0, 1 or 2")
 	}
 
+	// crop
+	if c.Options.CropLimit < 0 || c.Options.CropLimit > 100 {
+		return errors.New("crop limit should be between 0 and 100")
+	}
+
 	return nil
 }
 
diff --git a/internal/converter/options/converter_options.go b/internal/converter/options/converter_options.go
index e4d8772..432033b 100644
--- a/internal/converter/options/converter_options.go
+++ b/internal/converter/options/converter_options.go
@@ -268,7 +268,7 @@ func (o *Options) ShowConfig() string {
 		{"Grayscale", o.Grayscale, true},
 		{"Grayscale mode", grayscaleMode, o.Grayscale},
 		{"Crop", o.Crop, true},
-		{"Crop ratio", fmt.Sprintf("%d Left - %d Up - %d Right - %d Bottom - %d%% Limit", o.CropRatioLeft, o.CropRatioUp, o.CropRatioRight, o.CropRatioBottom, o.CropLimit), o.Crop},
+		{"Crop ratio", fmt.Sprintf("%d Left - %d Up - %d Right - %d Bottom - Limit %d%%", o.CropRatioLeft, o.CropRatioUp, o.CropRatioRight, o.CropRatioBottom, o.CropLimit), o.Crop},
 		{"Brightness", o.Brightness, o.Brightness != 0},
 		{"Contrast", o.Contrast, o.Contrast != 0},
 		{"Auto contrast", o.AutoContrast, true},

From 0dde6e02a48123a194b4d8a84ad17a9a63bc7045 Mon Sep 17 00:00:00 2001
From: celogeek <65178+celogeek@users.noreply.github.com>
Date: Sun, 28 Apr 2024 19:28:35 +0200
Subject: [PATCH 05/11] add skip crop if limit reached

---
 internal/converter/converter.go               |  1 +
 .../converter/options/converter_options.go    | 40 ++++++++++---------
 .../epub_image_filters_autocrop.go            | 28 +++++++++----
 .../imageprocessor/epub_image_processor.go    |  1 +
 internal/epub/options/epub_options.go         |  1 +
 main.go                                       | 13 +++---
 6 files changed, 52 insertions(+), 32 deletions(-)

diff --git a/internal/converter/converter.go b/internal/converter/converter.go
index 39f6730..69db9e0 100644
--- a/internal/converter/converter.go
+++ b/internal/converter/converter.go
@@ -114,6 +114,7 @@ func (c *Converter) InitParse() {
 	c.AddIntParam(&c.Options.CropRatioRight, "crop-ratio-right", c.Options.CropRatioRight, "Crop ratio right: ratio of pixels allow to be non blank while cutting on the right.")
 	c.AddIntParam(&c.Options.CropRatioBottom, "crop-ratio-bottom", c.Options.CropRatioBottom, "Crop ratio bottom: ratio of pixels allow to be non blank while cutting on the bottom.")
 	c.AddIntParam(&c.Options.CropLimit, "crop-limit", c.Options.CropLimit, "Crop limit: maximum number of cropping in percentage allowed. 0 mean unlimited.")
+	c.AddBoolParam(&c.Options.CropSkipIfLimitReached, "crop-skip-if-limit-reached", c.Options.CropSkipIfLimitReached, "Crop skip if limit reached.")
 	c.AddIntParam(&c.Options.Brightness, "brightness", c.Options.Brightness, "Brightness readjustment: between -100 and 100, > 0 lighter, < 0 darker")
 	c.AddIntParam(&c.Options.Contrast, "contrast", c.Options.Contrast, "Contrast readjustment: between -100 and 100, > 0 more contrast, < 0 less contrast")
 	c.AddBoolParam(&c.Options.AutoContrast, "autocontrast", c.Options.AutoContrast, "Improve contrast automatically")
diff --git a/internal/converter/options/converter_options.go b/internal/converter/options/converter_options.go
index 432033b..cfd89ec 100644
--- a/internal/converter/options/converter_options.go
+++ b/internal/converter/options/converter_options.go
@@ -31,6 +31,7 @@ type Options struct {
 	CropRatioRight             int     `yaml:"crop_ratio_right"`
 	CropRatioBottom            int     `yaml:"crop_ratio_bottom"`
 	CropLimit                  int     `yaml:"crop_limit"`
+	CropSkipIfLimitReached     bool    `yaml:"crop_skip_if_limit_reached"`
 	Brightness                 int     `yaml:"brightness"`
 	Contrast                   int     `yaml:"contrast"`
 	AutoContrast               bool    `yaml:"auto_contrast"`
@@ -81,23 +82,25 @@ type Options struct {
 // New Initialize default options.
 func New() *Options {
 	return &Options{
-		Profile:               "SR",
-		Quality:               85,
-		Grayscale:             true,
-		Crop:                  true,
-		CropRatioLeft:         1,
-		CropRatioUp:           1,
-		CropRatioRight:        1,
-		CropRatioBottom:       3,
-		NoBlankImage:          true,
-		HasCover:              true,
-		KeepDoublePageIfSplit: true,
-		SortPathMode:          1,
-		ForegroundColor:       "000",
-		BackgroundColor:       "FFF",
-		Format:                "jpeg",
-		TitlePage:             1,
-		profiles:              profiles.New(),
+		Profile:                "SR",
+		Quality:                85,
+		Grayscale:              true,
+		Crop:                   true,
+		CropRatioLeft:          1,
+		CropRatioUp:            1,
+		CropRatioRight:         1,
+		CropRatioBottom:        3,
+		CropLimit:              10,
+		CropSkipIfLimitReached: true,
+		NoBlankImage:           true,
+		HasCover:               true,
+		KeepDoublePageIfSplit:  true,
+		SortPathMode:           1,
+		ForegroundColor:        "000",
+		BackgroundColor:        "FFF",
+		Format:                 "jpeg",
+		TitlePage:              1,
+		profiles:               profiles.New(),
 	}
 }
 
@@ -164,6 +167,7 @@ func (o *Options) MarshalJSON() ([]byte, error) {
 			"bottom": o.CropRatioBottom,
 		}
 		out["crop_limit"] = o.CropLimit
+		out["crop_skip_if_limit_reached"] = o.CropSkipIfLimitReached
 	}
 	if o.Brightness != 0 {
 		out["brightness"] = o.Brightness
@@ -268,7 +272,7 @@ func (o *Options) ShowConfig() string {
 		{"Grayscale", o.Grayscale, true},
 		{"Grayscale mode", grayscaleMode, o.Grayscale},
 		{"Crop", o.Crop, true},
-		{"Crop ratio", fmt.Sprintf("%d Left - %d Up - %d Right - %d Bottom - Limit %d%%", o.CropRatioLeft, o.CropRatioUp, o.CropRatioRight, o.CropRatioBottom, o.CropLimit), o.Crop},
+		{"Crop ratio", fmt.Sprintf("%d Left - %d Up - %d Right - %d Bottom - Limit %d%% - Skip %v", o.CropRatioLeft, o.CropRatioUp, o.CropRatioRight, o.CropRatioBottom, o.CropLimit, o.CropSkipIfLimitReached), o.Crop},
 		{"Brightness", o.Brightness, o.Brightness != 0},
 		{"Contrast", o.Contrast, o.Contrast != 0},
 		{"Auto contrast", o.AutoContrast, true},
diff --git a/internal/epub/imagefilters/epub_image_filters_autocrop.go b/internal/epub/imagefilters/epub_image_filters_autocrop.go
index 53abf0a..aa51b85 100644
--- a/internal/epub/imagefilters/epub_image_filters_autocrop.go
+++ b/internal/epub/imagefilters/epub_image_filters_autocrop.go
@@ -8,9 +8,9 @@ import (
 )
 
 // AutoCrop Lookup for margin and crop
-func AutoCrop(img image.Image, bounds image.Rectangle, cutRatioLeft, cutRatioUp, cutRatioRight, cutRatioBottom int, limit int) gift.Filter {
+func AutoCrop(img image.Image, bounds image.Rectangle, cutRatioLeft, cutRatioUp, cutRatioRight, cutRatioBottom int, limit int, skipIfLimitReached bool) gift.Filter {
 	return gift.Crop(
-		findMargin(img, bounds, cutRatioOptions{cutRatioLeft, cutRatioUp, cutRatioRight, cutRatioBottom}, limit),
+		findMargin(img, bounds, cutRatioOptions{cutRatioLeft, cutRatioUp, cutRatioRight, cutRatioBottom}, limit, skipIfLimitReached),
 	)
 }
 
@@ -25,13 +25,13 @@ type cutRatioOptions struct {
 	Left, Up, Right, Bottom int
 }
 
-func findMargin(img image.Image, bounds image.Rectangle, cutRatio cutRatioOptions, limit int) image.Rectangle {
+func findMargin(img image.Image, bounds image.Rectangle, cutRatio cutRatioOptions, limit int, skipIfLimitReached bool) image.Rectangle {
 	imgArea := bounds
 
-	maxCutX, maxCutY := imgArea.Dx()*limit/100, imgArea.Dy()*limit/100
+	maxCropX, maxCropY := imgArea.Dx()*limit/100, imgArea.Dy()*limit/100
 
 LEFT:
-	for x, maxCut := imgArea.Min.X, maxCutX; x < imgArea.Max.X && (maxCutX == 0 || maxCut > 0); x, maxCut = x+1, maxCut-1 {
+	for x, maxCrop := imgArea.Min.X, maxCropX; x < imgArea.Max.X && (limit == 0 || maxCrop > 0); x, maxCrop = x+1, maxCrop-1 {
 		allowNonBlank := imgArea.Dy() * cutRatio.Left / 100
 		for y := imgArea.Min.Y; y < imgArea.Max.Y; y++ {
 			if !colorIsBlank(img.At(x, y)) {
@@ -42,10 +42,13 @@ LEFT:
 			}
 		}
 		imgArea.Min.X++
+		if limit > 0 && maxCrop == 1 && skipIfLimitReached {
+			return bounds
+		}
 	}
 
 UP:
-	for y, maxCut := imgArea.Min.Y, maxCutY; y < imgArea.Max.Y && (maxCutY == 0 || maxCut > 0); y, maxCut = y+1, maxCut-1 {
+	for y, maxCrop := imgArea.Min.Y, maxCropY; y < imgArea.Max.Y && (limit == 0 || maxCrop > 0); y, maxCrop = y+1, maxCrop-1 {
 		allowNonBlank := imgArea.Dx() * cutRatio.Up / 100
 		for x := imgArea.Min.X; x < imgArea.Max.X; x++ {
 			if !colorIsBlank(img.At(x, y)) {
@@ -56,10 +59,13 @@ UP:
 			}
 		}
 		imgArea.Min.Y++
+		if limit > 0 && maxCrop == 1 && skipIfLimitReached {
+			return bounds
+		}
 	}
 
 RIGHT:
-	for x, maxCut := imgArea.Max.X-1, maxCutX; x >= imgArea.Min.X && (maxCutX == 0 || maxCut > 0); x, maxCut = x-1, maxCut-1 {
+	for x, maxCrop := imgArea.Max.X-1, maxCropX; x >= imgArea.Min.X && (limit == 0 || maxCrop > 0); x, maxCrop = x-1, maxCrop-1 {
 		allowNonBlank := imgArea.Dy() * cutRatio.Right / 100
 		for y := imgArea.Min.Y; y < imgArea.Max.Y; y++ {
 			if !colorIsBlank(img.At(x, y)) {
@@ -70,10 +76,13 @@ RIGHT:
 			}
 		}
 		imgArea.Max.X--
+		if limit > 0 && maxCrop == 1 && skipIfLimitReached {
+			return bounds
+		}
 	}
 
 BOTTOM:
-	for y, maxCut := imgArea.Max.Y-1, maxCutY; y >= imgArea.Min.Y && (maxCutY == 0 || maxCut > 0); y, maxCut = y-1, maxCut-1 {
+	for y, maxCrop := imgArea.Max.Y-1, maxCropY; y >= imgArea.Min.Y && (limit == 0 || maxCrop > 0); y, maxCrop = y-1, maxCrop-1 {
 		allowNonBlank := imgArea.Dx() * cutRatio.Bottom / 100
 		for x := imgArea.Min.X; x < imgArea.Max.X; x++ {
 			if !colorIsBlank(img.At(x, y)) {
@@ -84,6 +93,9 @@ BOTTOM:
 			}
 		}
 		imgArea.Max.Y--
+		if limit > 0 && maxCrop == 1 && skipIfLimitReached {
+			return bounds
+		}
 	}
 
 	return imgArea
diff --git a/internal/epub/imageprocessor/epub_image_processor.go b/internal/epub/imageprocessor/epub_image_processor.go
index 30db08e..a152c2b 100644
--- a/internal/epub/imageprocessor/epub_image_processor.go
+++ b/internal/epub/imageprocessor/epub_image_processor.go
@@ -191,6 +191,7 @@ func (e *EPUBImageProcessor) transformImage(input *task, part int, right bool) *
 			e.Image.Crop.Right,
 			e.Image.Crop.Bottom,
 			e.Image.Crop.Limit,
+			e.Image.Crop.SkipIfLimitReached,
 		)
 
 		// detect if blank image
diff --git a/internal/epub/options/epub_options.go b/internal/epub/options/epub_options.go
index 041ae43..6715ee8 100644
--- a/internal/epub/options/epub_options.go
+++ b/internal/epub/options/epub_options.go
@@ -7,6 +7,7 @@ type Crop struct {
 	Enabled                 bool
 	Left, Up, Right, Bottom int
 	Limit                   int
+	SkipIfLimitReached      bool
 }
 
 type Color struct {
diff --git a/main.go b/main.go
index 8be2cd6..ea6cab9 100644
--- a/main.go
+++ b/main.go
@@ -124,12 +124,13 @@ $ go install github.com/celogeek/go-comic-converter/v%d@%s
 		Json:                       cmd.Options.Json,
 		Image: &epuboptions.Image{
 			Crop: &epuboptions.Crop{
-				Enabled: cmd.Options.Crop,
-				Left:    cmd.Options.CropRatioLeft,
-				Up:      cmd.Options.CropRatioUp,
-				Right:   cmd.Options.CropRatioRight,
-				Bottom:  cmd.Options.CropRatioBottom,
-				Limit:   cmd.Options.CropLimit,
+				Enabled:            cmd.Options.Crop,
+				Left:               cmd.Options.CropRatioLeft,
+				Up:                 cmd.Options.CropRatioUp,
+				Right:              cmd.Options.CropRatioRight,
+				Bottom:             cmd.Options.CropRatioBottom,
+				Limit:              cmd.Options.CropLimit,
+				SkipIfLimitReached: cmd.Options.CropSkipIfLimitReached,
 			},
 			Quality:               cmd.Options.Quality,
 			Brightness:            cmd.Options.Brightness,

From f4501753c54792fd76ce88437537f6a0c11ca025 Mon Sep 17 00:00:00 2001
From: celogeek <65178+celogeek@users.noreply.github.com>
Date: Sun, 28 Apr 2024 19:36:49 +0200
Subject: [PATCH 06/11] factor stderr printing

---
 internal/converter/converter.go               | 14 ++++++-------
 internal/epub/epub.go                         | 19 +++++++++---------
 .../imageprocessor/epub_image_processor.go    |  5 +++--
 .../epub_image_processor_loader.go            | 10 ++++++----
 internal/epub/progress/epub_progress.go       |  4 +++-
 internal/utils/utils.go                       | 14 +++++++++++++
 main.go                                       | 20 +++++++++----------
 7 files changed, 52 insertions(+), 34 deletions(-)
 create mode 100644 internal/utils/utils.go

diff --git a/internal/converter/converter.go b/internal/converter/converter.go
index 69db9e0..2c2aac5 100644
--- a/internal/converter/converter.go
+++ b/internal/converter/converter.go
@@ -19,6 +19,7 @@ import (
 	"time"
 
 	"github.com/celogeek/go-comic-converter/v2/internal/converter/options"
+	"github.com/celogeek/go-comic-converter/v2/internal/utils"
 )
 
 type Converter struct {
@@ -44,17 +45,17 @@ func New() *Converter {
 	var cmdOutput strings.Builder
 	cmd.SetOutput(&cmdOutput)
 	cmd.Usage = func() {
-		fmt.Fprintf(os.Stderr, "Usage of %s:\n", filepath.Base(os.Args[0]))
+		utils.Printf("Usage of %s:\n", filepath.Base(os.Args[0]))
 		for _, o := range conv.order {
 			switch v := o.(type) {
 			case converterOrderSection:
-				fmt.Fprintf(os.Stderr, "\n%s:\n", o.Value())
+				utils.Printf("\n%s:\n", o.Value())
 			case converterOrderName:
-				fmt.Fprintln(os.Stderr, conv.Usage(v.isString, cmd.Lookup(v.Value())))
+				utils.Println(conv.Usage(v.isString, cmd.Lookup(v.Value())))
 			}
 		}
 		if cmdOutput.Len() > 0 {
-			fmt.Fprintf(os.Stderr, "\nError: %s", cmdOutput.String())
+			utils.Printf("\nError: %s", cmdOutput.String())
 		}
 	}
 
@@ -401,7 +402,7 @@ func (c *Converter) Validate() error {
 // Fatal Helper to show usage, err and exit 1
 func (c *Converter) Fatal(err error) {
 	c.Cmd.Usage()
-	fmt.Fprintf(os.Stderr, "\nError: %s\n", err)
+	utils.Printf("\nError: %s\n", err)
 	os.Exit(1)
 }
 
@@ -420,8 +421,7 @@ func (c *Converter) Stats() {
 			},
 		})
 	} else {
-		fmt.Fprintf(
-			os.Stderr,
+		utils.Printf(
 			"Completed in %s, Memory usage %d Mb\n",
 			elapse,
 			mem.Sys/1024/1024,
diff --git a/internal/epub/epub.go b/internal/epub/epub.go
index ff91ba9..b047476 100644
--- a/internal/epub/epub.go
+++ b/internal/epub/epub.go
@@ -5,7 +5,6 @@ import (
 	"archive/zip"
 	"fmt"
 	"math"
-	"os"
 	"path/filepath"
 	"regexp"
 	"sort"
@@ -13,6 +12,8 @@ import (
 	"text/template"
 	"time"
 
+	"github.com/gofrs/uuid"
+
 	epubimage "github.com/celogeek/go-comic-converter/v2/internal/epub/image"
 	epubimageprocessor "github.com/celogeek/go-comic-converter/v2/internal/epub/imageprocessor"
 	epuboptions "github.com/celogeek/go-comic-converter/v2/internal/epub/options"
@@ -20,7 +21,7 @@ import (
 	epubtemplates "github.com/celogeek/go-comic-converter/v2/internal/epub/templates"
 	epubtree "github.com/celogeek/go-comic-converter/v2/internal/epub/tree"
 	epubzip "github.com/celogeek/go-comic-converter/v2/internal/epub/zip"
-	"github.com/gofrs/uuid"
+	"github.com/celogeek/go-comic-converter/v2/internal/utils"
 )
 
 type EPub struct {
@@ -418,12 +419,12 @@ func (e *EPub) Write() error {
 
 	if e.Dry {
 		p := epubParts[0]
-		fmt.Fprintf(os.Stderr, "TOC:\n  - %s\n%s\n", e.Title, e.getTree(p.Images, true))
+		utils.Printf("TOC:\n  - %s\n%s\n", e.Title, e.getTree(p.Images, true))
 		if e.DryVerbose {
 			if e.Image.HasCover {
-				fmt.Fprintf(os.Stderr, "Cover:\n%s\n", e.getTree([]*epubimage.Image{p.Cover}, false))
+				utils.Printf("Cover:\n%s\n", e.getTree([]*epubimage.Image{p.Cover}, false))
 			}
-			fmt.Fprintf(os.Stderr, "Files:\n%s\n", e.getTree(p.Images, false))
+			utils.Printf("Files:\n%s\n", e.getTree(p.Images, false))
 		}
 		return nil
 	}
@@ -469,7 +470,7 @@ func (e *EPub) Write() error {
 	}
 	bar.Close()
 	if !e.Json {
-		fmt.Fprintln(os.Stderr)
+		utils.Println()
 	}
 
 	// display corrupted images
@@ -477,17 +478,17 @@ func (e *EPub) Write() error {
 	for pId, part := range epubParts {
 		if pId == 0 && e.Image.HasCover && part.Cover.Error != nil {
 			hasError = true
-			fmt.Fprintf(os.Stderr, "Error on image %s: %v\n", filepath.Join(part.Cover.Path, part.Cover.Name), part.Cover.Error)
+			utils.Printf("Error on image %s: %v\n", filepath.Join(part.Cover.Path, part.Cover.Name), part.Cover.Error)
 		}
 		for _, img := range part.Images {
 			if img.Part == 0 && img.Error != nil {
 				hasError = true
-				fmt.Fprintf(os.Stderr, "Error on image %s: %v\n", filepath.Join(img.Path, img.Name), img.Error)
+				utils.Printf("Error on image %s: %v\n", filepath.Join(img.Path, img.Name), img.Error)
 			}
 		}
 	}
 	if hasError {
-		fmt.Fprintln(os.Stderr)
+		utils.Println()
 	}
 
 	return nil
diff --git a/internal/epub/imageprocessor/epub_image_processor.go b/internal/epub/imageprocessor/epub_image_processor.go
index a152c2b..2a60eb0 100644
--- a/internal/epub/imageprocessor/epub_image_processor.go
+++ b/internal/epub/imageprocessor/epub_image_processor.go
@@ -16,6 +16,7 @@ import (
 	epuboptions "github.com/celogeek/go-comic-converter/v2/internal/epub/options"
 	epubprogress "github.com/celogeek/go-comic-converter/v2/internal/epub/progress"
 	epubzip "github.com/celogeek/go-comic-converter/v2/internal/epub/zip"
+	"github.com/celogeek/go-comic-converter/v2/internal/utils"
 )
 
 type EPUBImageProcessor struct {
@@ -84,7 +85,7 @@ func (e *EPUBImageProcessor) Load() (images []*epubimage.Image, err error) {
 					e.Options.Image.AutoSplitDoublePage && !e.Options.Image.KeepDoublePageIfSplit) {
 					if err = imgStorage.Add(img.EPUBImgPath(), img.Raw, e.Image.Quality); err != nil {
 						bar.Close()
-						fmt.Fprintf(os.Stderr, "error with %s: %s", input.Name, err)
+						utils.Printf("error with %s: %s", input.Name, err)
 						os.Exit(1)
 					}
 					// do not keep raw image except for cover
@@ -105,7 +106,7 @@ func (e *EPUBImageProcessor) Load() (images []*epubimage.Image, err error) {
 					img = e.transformImage(input, i+1, b)
 					if err = imgStorage.Add(img.EPUBImgPath(), img.Raw, e.Image.Quality); err != nil {
 						bar.Close()
-						fmt.Fprintf(os.Stderr, "error with %s: %s", input.Name, err)
+						utils.Printf("error with %s: %s", input.Name, err)
 						os.Exit(1)
 					}
 					img.Raw = nil
diff --git a/internal/epub/imageprocessor/epub_image_processor_loader.go b/internal/epub/imageprocessor/epub_image_processor_loader.go
index c3bc4a0..50a08df 100644
--- a/internal/epub/imageprocessor/epub_image_processor_loader.go
+++ b/internal/epub/imageprocessor/epub_image_processor_loader.go
@@ -20,12 +20,14 @@ import (
 	"golang.org/x/image/font/gofont/gomonobold"
 	_ "golang.org/x/image/webp"
 
-	"github.com/celogeek/go-comic-converter/v2/internal/sortpath"
 	"github.com/fogleman/gg"
 	"github.com/golang/freetype/truetype"
 	"github.com/nwaples/rardecode/v2"
 	pdfimage "github.com/raff/pdfreader/image"
 	"github.com/raff/pdfreader/pdfread"
+
+	"github.com/celogeek/go-comic-converter/v2/internal/sortpath"
+	"github.com/celogeek/go-comic-converter/v2/internal/utils"
 )
 
 type task struct {
@@ -315,7 +317,7 @@ func (e *EPUBImageProcessor) loadCbr() (totalImages int, output chan *task, err
 		if isSolid && !e.Dry {
 			r, rerr := rardecode.OpenReader(e.Input)
 			if rerr != nil {
-				fmt.Fprintf(os.Stderr, "\nerror processing image %s: %s\n", e.Input, rerr)
+				utils.Printf("\nerror processing image %s: %s\n", e.Input, rerr)
 				os.Exit(1)
 			}
 			defer r.Close()
@@ -325,14 +327,14 @@ func (e *EPUBImageProcessor) loadCbr() (totalImages int, output chan *task, err
 					if rerr == io.EOF {
 						break
 					}
-					fmt.Fprintf(os.Stderr, "\nerror processing image %s: %s\n", f.Name, rerr)
+					utils.Printf("\nerror processing image %s: %s\n", f.Name, rerr)
 					os.Exit(1)
 				}
 				if i, ok := indexedNames[f.Name]; ok {
 					var b bytes.Buffer
 					_, rerr = io.Copy(&b, r)
 					if rerr != nil {
-						fmt.Fprintf(os.Stderr, "\nerror processing image %s: %s\n", f.Name, rerr)
+						utils.Printf("\nerror processing image %s: %s\n", f.Name, rerr)
 						os.Exit(1)
 					}
 					jobs <- &job{i, f.Name, func() (io.ReadCloser, error) {
diff --git a/internal/epub/progress/epub_progress.go b/internal/epub/progress/epub_progress.go
index 84d5f5c..ab3d2f8 100644
--- a/internal/epub/progress/epub_progress.go
+++ b/internal/epub/progress/epub_progress.go
@@ -7,6 +7,8 @@ import (
 	"time"
 
 	"github.com/schollz/progressbar/v3"
+
+	"github.com/celogeek/go-comic-converter/v2/internal/utils"
 )
 
 type Options struct {
@@ -38,7 +40,7 @@ func New(o Options) EpubProgress {
 		progressbar.OptionSetWriter(os.Stderr),
 		progressbar.OptionThrottle(65*time.Millisecond),
 		progressbar.OptionOnCompletion(func() {
-			fmt.Fprint(os.Stderr, "\n")
+			utils.Println()
 		}),
 		progressbar.OptionSetDescription(fmt.Sprintf(fmtDesc, o.CurrentJob, o.TotalJob, o.Description)),
 		progressbar.OptionSetWidth(60),
diff --git a/internal/utils/utils.go b/internal/utils/utils.go
new file mode 100644
index 0000000..5a55a37
--- /dev/null
+++ b/internal/utils/utils.go
@@ -0,0 +1,14 @@
+package utils
+
+import (
+	"fmt"
+	"os"
+)
+
+func Printf(format string, a ...interface{}) {
+	_, _ = fmt.Fprintf(os.Stderr, format, a...)
+}
+
+func Println(a ...interface{}) {
+	_, _ = fmt.Fprintln(os.Stderr, a...)
+}
diff --git a/main.go b/main.go
index ea6cab9..346fd8e 100644
--- a/main.go
+++ b/main.go
@@ -9,7 +9,6 @@ package main
 
 import (
 	"encoding/json"
-	"fmt"
 	"os"
 	"runtime/debug"
 
@@ -18,6 +17,7 @@ import (
 	"github.com/celogeek/go-comic-converter/v2/internal/converter"
 	"github.com/celogeek/go-comic-converter/v2/internal/epub"
 	epuboptions "github.com/celogeek/go-comic-converter/v2/internal/epub/options"
+	"github.com/celogeek/go-comic-converter/v2/internal/utils"
 )
 
 func main() {
@@ -31,7 +31,7 @@ func main() {
 	if cmd.Options.Version {
 		bi, ok := debug.ReadBuildInfo()
 		if !ok {
-			fmt.Fprintln(os.Stderr, "failed to fetch current version")
+			utils.Println("failed to fetch current version")
 			os.Exit(1)
 		}
 
@@ -41,12 +41,12 @@ func main() {
 		}
 		v, err := githubTag.Fetch()
 		if err != nil || len(v.Versions) < 1 {
-			fmt.Fprintln(os.Stderr, "failed to fetch the latest version")
+			utils.Println("failed to fetch the latest version")
 			os.Exit(1)
 		}
 		latestVersion := v.Versions[0]
 
-		fmt.Fprintf(os.Stderr, `go-comic-converter
+		utils.Printf(`go-comic-converter
   Path             : %s
   Sum              : %s
   Version          : %s
@@ -67,8 +67,7 @@ $ go install github.com/celogeek/go-comic-converter/v%d@%s
 
 	if cmd.Options.Save {
 		cmd.Options.SaveConfig()
-		fmt.Fprintf(
-			os.Stderr,
+		utils.Printf(
 			"%s%s\n\nSaving to %s\n",
 			cmd.Options.Header(),
 			cmd.Options.ShowConfig(),
@@ -78,14 +77,13 @@ $ go install github.com/celogeek/go-comic-converter/v%d@%s
 	}
 
 	if cmd.Options.Show {
-		fmt.Fprintln(os.Stderr, cmd.Options.Header(), cmd.Options.ShowConfig())
+		utils.Println(cmd.Options.Header(), cmd.Options.ShowConfig())
 		return
 	}
 
 	if cmd.Options.Reset {
 		cmd.Options.ResetConfig()
-		fmt.Fprintf(
-			os.Stderr,
+		utils.Printf(
 			"%s%s\n\nReset default to %s\n",
 			cmd.Options.Header(),
 			cmd.Options.ShowConfig(),
@@ -103,7 +101,7 @@ $ go install github.com/celogeek/go-comic-converter/v%d@%s
 			"type": "options", "data": cmd.Options,
 		})
 	} else {
-		fmt.Fprintln(os.Stderr, cmd.Options)
+		utils.Println(cmd.Options)
 	}
 
 	profile := cmd.Options.GetProfile()
@@ -159,7 +157,7 @@ $ go install github.com/celogeek/go-comic-converter/v%d@%s
 			AppleBookCompatibility: cmd.Options.AppleBookCompatibility,
 		},
 	}).Write(); err != nil {
-		fmt.Fprintf(os.Stderr, "Error: %v\n", err)
+		utils.Printf("Error: %v\n", err)
 		os.Exit(1)
 	}
 	if !cmd.Options.Dry {

From 8233d067eff7aa72edf86d563c6f2dbe4eeced70 Mon Sep 17 00:00:00 2001
From: celogeek <65178+celogeek@users.noreply.github.com>
Date: Sun, 28 Apr 2024 21:35:36 +0200
Subject: [PATCH 07/11] handle errors

---
 internal/converter/converter.go                    |  4 ++--
 internal/converter/options/converter_options.go    | 12 +++++++++---
 internal/epub/epub.go                              | 12 +++++++-----
 .../imagefilters/epub_image_filters_cover_title.go |  2 +-
 .../epub/imageprocessor/epub_image_processor.go    | 12 ++++++------
 .../imageprocessor/epub_image_processor_loader.go  | 14 ++++++++------
 internal/epub/zip/epub_zip_storage_image.go        |  2 +-
 main.go                                            | 10 +++++++---
 8 files changed, 41 insertions(+), 27 deletions(-)

diff --git a/internal/converter/converter.go b/internal/converter/converter.go
index 2c2aac5..3909416 100644
--- a/internal/converter/converter.go
+++ b/internal/converter/converter.go
@@ -228,7 +228,7 @@ func (c *Converter) isZeroValue(f *flag.Flag, value string) (ok bool, err error)
 
 // Parse all parameters
 func (c *Converter) Parse() {
-	c.Cmd.Parse(os.Args[1:])
+	_ = c.Cmd.Parse(os.Args[1:])
 	if c.Options.Help {
 		c.Cmd.Usage()
 		os.Exit(0)
@@ -413,7 +413,7 @@ func (c *Converter) Stats() {
 	runtime.ReadMemStats(&mem)
 
 	if c.Options.Json {
-		json.NewEncoder(os.Stdout).Encode(map[string]any{
+		_ = json.NewEncoder(os.Stdout).Encode(map[string]any{
 			"type": "stats",
 			"data": map[string]any{
 				"elapse_ms":       elapse.Milliseconds(),
diff --git a/internal/converter/options/converter_options.go b/internal/converter/options/converter_options.go
index cfd89ec..d67a6bd 100644
--- a/internal/converter/options/converter_options.go
+++ b/internal/converter/options/converter_options.go
@@ -202,7 +202,9 @@ func (o *Options) LoadConfig() error {
 	if err != nil {
 		return nil
 	}
-	defer f.Close()
+	defer func(f *os.File) {
+		_ = f.Close()
+	}(f)
 	err = yaml.NewDecoder(f).Decode(o)
 	if err != nil && err.Error() != "EOF" {
 		return err
@@ -302,7 +304,9 @@ func (o *Options) ShowConfig() string {
 
 // ResetConfig reset all settings to default value
 func (o *Options) ResetConfig() error {
-	New().SaveConfig()
+	if err := New().SaveConfig(); err != nil {
+		return err
+	}
 	return o.LoadConfig()
 }
 
@@ -312,7 +316,9 @@ func (o *Options) SaveConfig() error {
 	if err != nil {
 		return err
 	}
-	defer f.Close()
+	defer func(f *os.File) {
+		_ = f.Close()
+	}(f)
 	return yaml.NewEncoder(f).Encode(o)
 }
 
diff --git a/internal/epub/epub.go b/internal/epub/epub.go
index b047476..2d2ef14 100644
--- a/internal/epub/epub.go
+++ b/internal/epub/epub.go
@@ -339,7 +339,9 @@ func (e *EPub) writePart(path string, currentPart, totalParts int, part *epubPar
 	if err != nil {
 		return err
 	}
-	defer wz.Close()
+	defer func(wz *epubzip.EPUBZip) {
+		_ = wz.Close()
+	}(wz)
 
 	title := e.Title
 	if totalParts > 1 {
@@ -429,8 +431,8 @@ func (e *EPub) Write() error {
 		return nil
 	}
 	defer func() {
-		imgStorage.Close()
-		imgStorage.Remove()
+		_ = imgStorage.Close()
+		_ = imgStorage.Remove()
 	}()
 
 	totalParts := len(epubParts)
@@ -466,9 +468,9 @@ func (e *EPub) Write() error {
 			return err
 		}
 
-		bar.Add(1)
+		_ = bar.Add(1)
 	}
-	bar.Close()
+	_ = bar.Close()
 	if !e.Json {
 		utils.Println()
 	}
diff --git a/internal/epub/imagefilters/epub_image_filters_cover_title.go b/internal/epub/imagefilters/epub_image_filters_cover_title.go
index 678b8e0..7692e91 100644
--- a/internal/epub/imagefilters/epub_image_filters_cover_title.go
+++ b/internal/epub/imagefilters/epub_image_filters_cover_title.go
@@ -94,5 +94,5 @@ func (p *coverTitle) Draw(dst draw.Image, src image.Image, _ *gift.Options) {
 		textLeft = textArea.Min.X
 	}
 	textTop := textArea.Min.Y + textArea.Dy()/2 + textHeight/4
-	c.DrawString(p.title, freetype.Pt(textLeft, textTop))
+	_, _ = c.DrawString(p.title, freetype.Pt(textLeft, textTop))
 }
diff --git a/internal/epub/imageprocessor/epub_image_processor.go b/internal/epub/imageprocessor/epub_image_processor.go
index 2a60eb0..12c4943 100644
--- a/internal/epub/imageprocessor/epub_image_processor.go
+++ b/internal/epub/imageprocessor/epub_image_processor.go
@@ -64,7 +64,7 @@ func (e *EPUBImageProcessor) Load() (images []*epubimage.Image, err error) {
 
 	imgStorage, err := epubzip.NewStorageImageWriter(e.ImgStorage(), e.Image.Format)
 	if err != nil {
-		bar.Close()
+		_ = bar.Close()
 		return nil, err
 	}
 
@@ -84,7 +84,7 @@ func (e *EPUBImageProcessor) Load() (images []*epubimage.Image, err error) {
 				if !(img.DoublePage && input.Id > 0 &&
 					e.Options.Image.AutoSplitDoublePage && !e.Options.Image.KeepDoublePageIfSplit) {
 					if err = imgStorage.Add(img.EPUBImgPath(), img.Raw, e.Image.Quality); err != nil {
-						bar.Close()
+						_ = bar.Close()
 						utils.Printf("error with %s: %s", input.Name, err)
 						os.Exit(1)
 					}
@@ -105,7 +105,7 @@ func (e *EPUBImageProcessor) Load() (images []*epubimage.Image, err error) {
 				for i, b := range []bool{e.Image.Manga, !e.Image.Manga} {
 					img = e.transformImage(input, i+1, b)
 					if err = imgStorage.Add(img.EPUBImgPath(), img.Raw, e.Image.Quality); err != nil {
-						bar.Close()
+						_ = bar.Close()
 						utils.Printf("error with %s: %s", input.Name, err)
 						os.Exit(1)
 					}
@@ -118,20 +118,20 @@ func (e *EPUBImageProcessor) Load() (images []*epubimage.Image, err error) {
 
 	go func() {
 		wg.Wait()
-		imgStorage.Close()
+		_ = imgStorage.Close()
 		close(imageOutput)
 	}()
 
 	for img := range imageOutput {
 		if img.Part == 0 {
-			bar.Add(1)
+			_ = bar.Add(1)
 		}
 		if e.Image.NoBlankImage && img.IsBlank {
 			continue
 		}
 		images = append(images, img)
 	}
-	bar.Close()
+	_ = bar.Close()
 
 	if len(images) == 0 {
 		return nil, errNoImagesFound
diff --git a/internal/epub/imageprocessor/epub_image_processor_loader.go b/internal/epub/imageprocessor/epub_image_processor_loader.go
index 50a08df..da610f0 100644
--- a/internal/epub/imageprocessor/epub_image_processor_loader.go
+++ b/internal/epub/imageprocessor/epub_image_processor_loader.go
@@ -155,7 +155,7 @@ func (e *EPUBImageProcessor) loadDir() (totalImages int, output chan *task, err
 					f, err = os.Open(job.Path)
 					if err == nil {
 						img, _, err = image.Decode(f)
-						f.Close()
+						_ = f.Close()
 					}
 				}
 
@@ -205,7 +205,7 @@ func (e *EPUBImageProcessor) loadCbz() (totalImages int, output chan *task, err
 	totalImages = len(images)
 
 	if totalImages == 0 {
-		r.Close()
+		_ = r.Close()
 		err = errNoImagesFound
 		return
 	}
@@ -248,7 +248,7 @@ func (e *EPUBImageProcessor) loadCbz() (totalImages int, output chan *task, err
 					if err == nil {
 						img, _, err = image.Decode(f)
 					}
-					f.Close()
+					_ = f.Close()
 				}
 
 				p, fn := filepath.Split(filepath.Clean(job.F.Name))
@@ -269,7 +269,7 @@ func (e *EPUBImageProcessor) loadCbz() (totalImages int, output chan *task, err
 	go func() {
 		wg.Wait()
 		close(output)
-		r.Close()
+		_ = r.Close()
 	}()
 	return
 }
@@ -320,7 +320,9 @@ func (e *EPUBImageProcessor) loadCbr() (totalImages int, output chan *task, err
 				utils.Printf("\nerror processing image %s: %s\n", e.Input, rerr)
 				os.Exit(1)
 			}
-			defer r.Close()
+			defer func(r *rardecode.ReadCloser) {
+				_ = r.Close()
+			}(r)
 			for {
 				f, rerr := r.Next()
 				if rerr != nil {
@@ -367,7 +369,7 @@ func (e *EPUBImageProcessor) loadCbr() (totalImages int, output chan *task, err
 					if err == nil {
 						img, _, err = image.Decode(f)
 					}
-					f.Close()
+					_ = f.Close()
 				}
 
 				p, fn := filepath.Split(filepath.Clean(job.Name))
diff --git a/internal/epub/zip/epub_zip_storage_image.go b/internal/epub/zip/epub_zip_storage_image.go
index 50c3c7f..dd918cd 100644
--- a/internal/epub/zip/epub_zip_storage_image.go
+++ b/internal/epub/zip/epub_zip_storage_image.go
@@ -25,7 +25,7 @@ func NewStorageImageWriter(filename string, format string) (*StorageImageWriter,
 
 func (e *StorageImageWriter) Close() error {
 	if err := e.fz.Close(); err != nil {
-		e.fh.Close()
+		_ = e.fh.Close()
 		return err
 	}
 	return e.fh.Close()
diff --git a/main.go b/main.go
index 346fd8e..0181f87 100644
--- a/main.go
+++ b/main.go
@@ -66,7 +66,9 @@ $ go install github.com/celogeek/go-comic-converter/v%d@%s
 	}
 
 	if cmd.Options.Save {
-		cmd.Options.SaveConfig()
+		if err := cmd.Options.SaveConfig(); err != nil {
+			cmd.Fatal(err)
+		}
 		utils.Printf(
 			"%s%s\n\nSaving to %s\n",
 			cmd.Options.Header(),
@@ -82,7 +84,9 @@ $ go install github.com/celogeek/go-comic-converter/v%d@%s
 	}
 
 	if cmd.Options.Reset {
-		cmd.Options.ResetConfig()
+		if err := cmd.Options.ResetConfig(); err != nil {
+			cmd.Fatal(err)
+		}
 		utils.Printf(
 			"%s%s\n\nReset default to %s\n",
 			cmd.Options.Header(),
@@ -97,7 +101,7 @@ $ go install github.com/celogeek/go-comic-converter/v%d@%s
 	}
 
 	if cmd.Options.Json {
-		json.NewEncoder(os.Stdout).Encode(map[string]any{
+		_ = json.NewEncoder(os.Stdout).Encode(map[string]any{
 			"type": "options", "data": cmd.Options,
 		})
 	} else {

From 79e20ca622ccdc78c4268ca2eb17e108425f264d Mon Sep 17 00:00:00 2001
From: celogeek <65178+celogeek@users.noreply.github.com>
Date: Sun, 28 Apr 2024 21:35:48 +0200
Subject: [PATCH 08/11] skip deprecated

---
 internal/epub/zip/epub_zip.go       | 1 +
 internal/epub/zip/epub_zip_image.go | 1 +
 2 files changed, 2 insertions(+)

diff --git a/internal/epub/zip/epub_zip.go b/internal/epub/zip/epub_zip.go
index 28e0890..5c32b33 100644
--- a/internal/epub/zip/epub_zip.go
+++ b/internal/epub/zip/epub_zip.go
@@ -39,6 +39,7 @@ func (e *EPUBZip) Close() error {
 // This will be valid with epubcheck tools.
 func (e *EPUBZip) WriteMagic() error {
 	t := time.Now().UTC()
+	//goland:noinspection GoDeprecation
 	fh := &zip.FileHeader{
 		Name:               "mimetype",
 		Method:             zip.Store,
diff --git a/internal/epub/zip/epub_zip_image.go b/internal/epub/zip/epub_zip_image.go
index ad1f0b4..060cb86 100644
--- a/internal/epub/zip/epub_zip_image.go
+++ b/internal/epub/zip/epub_zip_image.go
@@ -52,6 +52,7 @@ func CompressImage(filename string, format string, img image.Image, quality int)
 	}
 
 	t := time.Now()
+	//goland:noinspection GoDeprecation
 	return &ZipImage{
 		&zip.FileHeader{
 			Name:               filename,

From 5968cbdd0983d096d50293a0a4d877b9204d1d22 Mon Sep 17 00:00:00 2001
From: celogeek <65178+celogeek@users.noreply.github.com>
Date: Sun, 28 Apr 2024 21:36:08 +0200
Subject: [PATCH 09/11] sort import

---
 internal/epub/templates/epub_templates_content.go | 1 +
 internal/epub/templates/epub_templates_toc.go     | 1 +
 2 files changed, 2 insertions(+)

diff --git a/internal/epub/templates/epub_templates_content.go b/internal/epub/templates/epub_templates_content.go
index 861bf7d..7b3a662 100644
--- a/internal/epub/templates/epub_templates_content.go
+++ b/internal/epub/templates/epub_templates_content.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 
 	"github.com/beevik/etree"
+
 	epubimage "github.com/celogeek/go-comic-converter/v2/internal/epub/image"
 	epuboptions "github.com/celogeek/go-comic-converter/v2/internal/epub/options"
 )
diff --git a/internal/epub/templates/epub_templates_toc.go b/internal/epub/templates/epub_templates_toc.go
index f751fca..9df2766 100644
--- a/internal/epub/templates/epub_templates_toc.go
+++ b/internal/epub/templates/epub_templates_toc.go
@@ -5,6 +5,7 @@ import (
 	"strings"
 
 	"github.com/beevik/etree"
+
 	epubimage "github.com/celogeek/go-comic-converter/v2/internal/epub/image"
 )
 

From cc7a97ad6f5b6fee2a03a4bfa070b3a22463bc29 Mon Sep 17 00:00:00 2001
From: celogeek <65178+celogeek@users.noreply.github.com>
Date: Sun, 28 Apr 2024 21:36:26 +0200
Subject: [PATCH 10/11] handle missing profile

---
 internal/converter/options/converter_options.go | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/internal/converter/options/converter_options.go b/internal/converter/options/converter_options.go
index d67a6bd..934159c 100644
--- a/internal/converter/options/converter_options.go
+++ b/internal/converter/options/converter_options.go
@@ -241,7 +241,11 @@ func (o *Options) ShowConfig() string {
 	if o.AspectRatio > 0 {
 		aspectRatio = fmt.Sprintf("1:%.02f", o.AspectRatio)
 	} else if o.AspectRatio < 0 {
-		aspectRatio = fmt.Sprintf("1:%0.2f (device)", float64(profile.Height)/float64(profile.Width))
+		if profile != nil {
+			aspectRatio = fmt.Sprintf("1:%0.2f (device)", float64(profile.Height)/float64(profile.Width))
+		} else {
+			aspectRatio = "1:?? (device)"
+		}
 	}
 
 	titlePage := ""

From 7b304149fd03d318c4fcf93ac876ecec1917695c Mon Sep 17 00:00:00 2001
From: celogeek <65178+celogeek@users.noreply.github.com>
Date: Sun, 28 Apr 2024 21:36:40 +0200
Subject: [PATCH 11/11] write string directly

---
 internal/converter/converter.go | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/internal/converter/converter.go b/internal/converter/converter.go
index 3909416..29fd6a0 100644
--- a/internal/converter/converter.go
+++ b/internal/converter/converter.go
@@ -165,7 +165,7 @@ func (c *Converter) InitParse() {
 // Usage Customize version of FlagSet.PrintDefaults
 func (c *Converter) Usage(isString bool, f *flag.Flag) string {
 	var b strings.Builder
-	fmt.Fprintf(&b, "  -%s", f.Name) // Two spaces before -; see next two comments.
+	b.WriteString("  -" + f.Name)
 	name, usage := flag.UnquoteUsage(f)
 	if len(name) > 0 {
 		b.WriteString(" ")
@@ -177,9 +177,9 @@ func (c *Converter) Usage(isString bool, f *flag.Flag) string {
 		c.isZeroValueErrs = append(c.isZeroValueErrs, err)
 	} else if !isZero {
 		if isString {
-			fmt.Fprintf(&b, " (default %q)", f.DefValue)
+			b.WriteString(fmt.Sprintf(" (default %q)", f.DefValue))
 		} else {
-			fmt.Fprintf(&b, " (default %v)", f.DefValue)
+			b.WriteString(fmt.Sprintf(" (default %v)", f.DefValue))
 		}
 	}