Compare commits

...

4 Commits

Author SHA1 Message Date
2650b926f8
add grayscale mode
it will apply different formula to convert image into grayscale.

it works only if the source is not already in grayscale.

0 = normal
1 = average
2 = luminance
2023-05-05 12:24:12 +02:00
a54e093844
title page settings
add option to include or not title page
0 = never
1 = always
2 = when the epub is splitted
2023-05-05 12:08:48 +02:00
0de6cc9acd
cover improvement
include part in cover if totalParts > 1
cleaner cover in grayscale
2023-05-05 11:39:44 +02:00
4a1ae516ea
move title generation into method 2023-05-05 09:03:26 +02:00
9 changed files with 279 additions and 91 deletions

@ -108,6 +108,7 @@ func (c *Converter) InitParse() {
c.AddStringParam(&c.Options.Profile, "profile", c.Options.Profile, fmt.Sprintf("Profile to use: \n%s", c.Options.AvailableProfiles()))
c.AddIntParam(&c.Options.Quality, "quality", c.Options.Quality, "Quality of the image")
c.AddBoolParam(&c.Options.Grayscale, "grayscale", c.Options.Grayscale, "Grayscale image. Ideal for eInk devices.")
c.AddIntParam(&c.Options.GrayscaleMode, "grayscale-mode", c.Options.GrayscaleMode, "Grayscale Mode\n0 = normal\n1 = average\n2 = luminance")
c.AddBoolParam(&c.Options.Crop, "crop", c.Options.Crop, "Crop images")
c.AddIntParam(&c.Options.CropRatioLeft, "crop-ratio-left", c.Options.CropRatioLeft, "Crop ratio left: ratio of pixels allow to be non blank while cutting on the left.")
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.")
@ -129,6 +130,7 @@ func (c *Converter) InitParse() {
c.AddStringParam(&c.Options.Format, "format", c.Options.Format, "Format of output images: jpeg (lossy), png (lossless)")
c.AddFloatParam(&c.Options.AspectRatio, "aspect-ratio", c.Options.AspectRatio, "Aspect ratio (height/width) of the output\n -1 = same as device\n 0 = same as source\n1.6 = amazon advice for kindle")
c.AddBoolParam(&c.Options.PortraitOnly, "portrait-only", c.Options.PortraitOnly, "Portrait only: force orientation to portrait only.")
c.AddIntParam(&c.Options.TitlePage, "titlepage", c.Options.TitlePage, "Title page\n0 = never\n1 = always\n2 = only if epub is splitted")
c.AddSection("Default config")
c.AddBoolParam(&c.Options.Show, "show", false, "Show your default parameters")
@ -361,7 +363,17 @@ func (c *Converter) Validate() error {
// Aspect Ratio
if c.Options.AspectRatio < 0 && c.Options.AspectRatio != -1 {
return errors.New("aspect ratio should be: -1, 0, > 0")
return errors.New("aspect ratio should be -1, 0 or > 0")
}
// Title Page
if c.Options.TitlePage < 0 || c.Options.TitlePage > 2 {
return errors.New("title page should be 0, 1 or 2")
}
// Grayscale Mode
if c.Options.GrayscaleMode < 0 || c.Options.GrayscaleMode > 2 {
return errors.New("grayscale mode should be 0, 1 or 2")
}
return nil

@ -24,6 +24,7 @@ type Options struct {
Profile string `yaml:"profile"`
Quality int `yaml:"quality"`
Grayscale bool `yaml:"grayscale"`
GrayscaleMode int `yaml:"grayscale_mode"` // 0 = normal, 1 = average, 2 = luminance
Crop bool `yaml:"crop"`
CropRatioLeft int `yaml:"crop_ratio_left"`
CropRatioUp int `yaml:"crop_ratio_up"`
@ -45,6 +46,7 @@ type Options struct {
Format string `yaml:"format"`
AspectRatio float64 `yaml:"aspect_ratio"`
PortraitOnly bool `yaml:"portrait_only"`
TitlePage int `yaml:"title_page"`
// Default Config
Show bool `yaml:"-"`
@ -87,6 +89,7 @@ func New() *Options {
ForegroundColor: "000",
BackgroundColor: "FFF",
Format: "jpeg",
TitlePage: 1,
profiles: profiles.New(),
}
}
@ -167,6 +170,24 @@ func (o *Options) ShowConfig() string {
aspectRatio = fmt.Sprintf("1:%0.2f (device)", float64(profile.Height)/float64(profile.Width))
}
titlePage := ""
switch o.TitlePage {
case 0:
titlePage = "never"
case 1:
titlePage = "always"
case 2:
titlePage = "when epub is splitted"
}
grayscaleMode := "normal"
switch o.GrayscaleMode {
case 1:
grayscaleMode = "average"
case 2:
grayscaleMode = "luminance"
}
var b strings.Builder
for _, v := range []struct {
Key string
@ -177,6 +198,7 @@ func (o *Options) ShowConfig() string {
{"Format", o.Format, true},
{"Quality", o.Quality, o.Format == "jpeg"},
{"Grayscale", o.Grayscale, true},
{"Grayscale Mode", grayscaleMode, o.Grayscale},
{"Crop", o.Crop, true},
{"CropRatio", fmt.Sprintf("%d Left - %d Up - %d Right - %d Bottom", o.CropRatioLeft, o.CropRatioUp, o.CropRatioRight, o.CropRatioBottom), o.Crop},
{"Brightness", o.Brightness, o.Brightness != 0},
@ -194,6 +216,7 @@ func (o *Options) ShowConfig() string {
{"Resize", !o.NoResize, true},
{"Aspect Ratio", aspectRatio, true},
{"Portrait Only", o.PortraitOnly, true},
{"Title Page", titlePage, true},
} {
if v.Condition {
b.WriteString(fmt.Sprintf("\n %-26s: %v", v.Key, v.Value))

@ -99,6 +99,105 @@ func (e *ePub) writeBlank(wz *epubzip.EPUBZip, img *epubimage.Image) error {
)
}
// write title image
func (e *ePub) writeCoverImage(wz *epubzip.EPUBZip, img *epubimage.Image, part, totalParts int) error {
title := "Cover"
text := ""
if totalParts > 1 {
text = fmt.Sprintf("%d / %d", part, totalParts)
title = fmt.Sprintf("%s %s", title, text)
}
if err := wz.WriteContent(
"OEBPS/Text/cover.xhtml",
[]byte(e.render(epubtemplates.Text, map[string]any{
"Title": title,
"ViewPort": fmt.Sprintf("width=%d,height=%d", e.Image.View.Width, e.Image.View.Height),
"ImagePath": fmt.Sprintf("Images/cover.%s", e.Image.Format),
"ImageStyle": img.ImgStyle(e.Image.View.Width, e.Image.View.Height, ""),
})),
); err != nil {
return err
}
coverTitle, err := e.imageProcessor.CoverTitleData(&epubimageprocessor.CoverTitleDataOptions{
Src: img.Raw,
Name: "cover",
Text: text,
Align: "bottom",
PctWidth: 50,
PctMargin: 50,
MaxFontSize: 96,
BorderSize: 8,
})
if err != nil {
return err
}
if err := wz.WriteRaw(coverTitle); err != nil {
return err
}
return nil
}
// write title image
func (e *ePub) writeTitleImage(wz *epubzip.EPUBZip, img *epubimage.Image, title string) error {
titleAlign := ""
if !e.Image.View.PortraitOnly {
if e.Image.Manga {
titleAlign = "right:0"
} else {
titleAlign = "left:0"
}
}
if !e.Image.View.PortraitOnly {
if err := wz.WriteContent(
"OEBPS/Text/space_title.xhtml",
[]byte(e.render(epubtemplates.Blank, map[string]any{
"Title": "Blank Page Title",
"ViewPort": fmt.Sprintf("width=%d,height=%d", e.Image.View.Width, e.Image.View.Height),
})),
); err != nil {
return err
}
}
if err := wz.WriteContent(
"OEBPS/Text/title.xhtml",
[]byte(e.render(epubtemplates.Text, map[string]any{
"Title": title,
"ViewPort": fmt.Sprintf("width=%d,height=%d", e.Image.View.Width, e.Image.View.Height),
"ImagePath": fmt.Sprintf("Images/title.%s", e.Image.Format),
"ImageStyle": img.ImgStyle(e.Image.View.Width, e.Image.View.Height, titleAlign),
})),
); err != nil {
return err
}
coverTitle, err := e.imageProcessor.CoverTitleData(&epubimageprocessor.CoverTitleDataOptions{
Src: img.Raw,
Name: "title",
Text: title,
Align: "center",
PctWidth: 100,
PctMargin: 100,
MaxFontSize: 64,
BorderSize: 4,
})
if err != nil {
return err
}
if err := wz.WriteRaw(coverTitle); err != nil {
return err
}
return nil
}
// extract image and split it into part
func (e *ePub) getParts() (parts []*epubPart, imgStorage *epubzip.EPUBZipStorageImageReader, err error) {
images, err := e.imageProcessor.Load()
@ -137,11 +236,8 @@ func (e *ePub) getParts() (parts []*epubPart, imgStorage *epubzip.EPUBZipStorage
// compute size of the EPUB part and try to be as close as possible of the target
maxSize := uint64(e.LimitMb * 1024 * 1024)
xhtmlSize := uint64(1024)
// descriptor files + title
baseSize := uint64(16*1024) + imgStorage.Size(cover.EPUBImgPath())
if e.Image.HasCover {
baseSize += imgStorage.Size(cover.EPUBImgPath())
}
// descriptor files + title + cover
baseSize := uint64(16*1024) + imgStorage.Size(cover.EPUBImgPath())*2
currentSize := baseSize
currentImages := make([]*epubimage.Image, 0)
@ -156,9 +252,6 @@ func (e *ePub) getParts() (parts []*epubPart, imgStorage *epubzip.EPUBZipStorage
})
part += 1
currentSize = baseSize
if !e.Image.HasCover {
currentSize += imgStorage.Size(cover.EPUBImgPath())
}
currentImages = make([]*epubimage.Image, 0)
}
currentSize += imgSize
@ -279,6 +372,7 @@ func (e *ePub) Write() error {
})
e.computeViewPort(epubParts)
hasTitlePage := e.TitlePage == 1 || (e.TitlePage == 2 && totalParts > 1)
for i, part := range epubParts {
ext := filepath.Ext(e.Output)
suffix := ""
@ -299,19 +393,13 @@ func (e *ePub) Write() error {
if totalParts > 1 {
title = fmt.Sprintf("%s [%d/%d]", title, i+1, totalParts)
}
titleAlign := ""
if !e.Image.View.PortraitOnly {
titleAlign = "left:0"
if e.Image.Manga {
titleAlign = "right:0"
}
}
content := []zipContent{
{"META-INF/container.xml", epubtemplates.Container},
{"META-INF/com.apple.ibooks.display-options.xml", epubtemplates.AppleBooks},
{"OEBPS/content.opf", epubtemplates.Content(&epubtemplates.ContentOptions{
Title: title,
HasTitlePage: hasTitlePage,
UID: e.UID,
Author: e.Author,
Publisher: e.Publisher,
@ -322,25 +410,10 @@ func (e *ePub) Write() error {
Current: i + 1,
Total: totalParts,
})},
{"OEBPS/toc.xhtml", epubtemplates.Toc(title, e.StripFirstDirectoryFromToc, part.Images)},
{"OEBPS/toc.xhtml", epubtemplates.Toc(title, hasTitlePage, e.StripFirstDirectoryFromToc, part.Images)},
{"OEBPS/Text/style.css", e.render(epubtemplates.Style, map[string]any{
"View": e.Image.View,
})},
{"OEBPS/Text/title.xhtml", e.render(epubtemplates.Text, map[string]any{
"Title": title,
"ViewPort": fmt.Sprintf("width=%d,height=%d", e.Image.View.Width, e.Image.View.Height),
"ImagePath": fmt.Sprintf("Images/title.%s", e.Image.Format),
"ImageStyle": part.Cover.ImgStyle(e.Image.View.Width, e.Image.View.Height, titleAlign),
})},
}
if !e.Image.View.PortraitOnly {
content = append(content, zipContent{
"OEBPS/Text/space_title.xhtml", e.render(epubtemplates.Blank, map[string]any{
"Title": "Blank Page Title",
"ViewPort": fmt.Sprintf("width=%d,height=%d", e.Image.View.Width, e.Image.View.Height),
}),
})
}
if err = wz.WriteMagic(); err != nil {
@ -351,19 +424,13 @@ func (e *ePub) Write() error {
return err
}
}
coverTitle, err := e.imageProcessor.CoverTitleData(part.Cover.Raw, title)
if err != nil {
if err = e.writeCoverImage(wz, part.Cover, i+1, totalParts); err != nil {
return err
}
if err := wz.WriteRaw(coverTitle); err != nil {
return err
}
// Cover exist or part > 1
// If no cover, part 2 and more will include the image as a cover
if e.Image.HasCover || i > 0 {
if err := e.writeImage(wz, part.Cover, imgStorage.Get(part.Cover.EPUBImgPath())); err != nil {
if hasTitlePage {
if err = e.writeTitleImage(wz, part.Cover, title); err != nil {
return err
}
}

@ -12,12 +12,17 @@ import (
)
// Create a title with the cover image
func CoverTitle(title string) gift.Filter {
return &coverTitle{title}
func CoverTitle(title string, align string, pctWidth int, pctMargin int, maxFontSize int, borderSize int) gift.Filter {
return &coverTitle{title, align, pctWidth, pctMargin, maxFontSize, borderSize}
}
type coverTitle struct {
title string
title string
align string
pctWidth int
pctMargin int
maxFontSize int
borderSize int
}
// size is the same as source
@ -28,28 +33,36 @@ func (p *coverTitle) Bounds(srcBounds image.Rectangle) (dstBounds image.Rectangl
// blur the src image, and create a box with the title in the middle
func (p *coverTitle) Draw(dst draw.Image, src image.Image, options *gift.Options) {
draw.Draw(dst, dst.Bounds(), src, src.Bounds().Min, draw.Src)
if p.title == "" {
return
}
srcWidth, srcHeight := src.Bounds().Dx(), src.Bounds().Dy()
// Calculate size of title
f, _ := truetype.Parse(gomonobold.TTF)
borderSize := 4
var fontSize, textWidth, textHeight int
for fontSize = 64; fontSize >= 12; fontSize -= 1 {
for fontSize = p.maxFontSize; fontSize >= 12; fontSize -= 1 {
face := truetype.NewFace(f, &truetype.Options{Size: float64(fontSize), DPI: 72})
textWidth = font.MeasureString(face, p.title).Ceil()
textHeight = face.Metrics().Ascent.Ceil() + face.Metrics().Descent.Ceil()
if textWidth+2*borderSize < srcWidth && 3*textHeight+2*borderSize < srcHeight {
if textWidth+2*p.borderSize < srcWidth*p.pctWidth/100 && 3*textHeight+2*p.borderSize < srcHeight {
break
}
}
// Draw rectangle in the middle of the image
textPosStart := srcHeight/2 - textHeight/2
textPosEnd := srcHeight/2 + textHeight/2
marginSize := fontSize
borderArea := image.Rect(0, textPosStart-borderSize-marginSize, srcWidth, textPosEnd+borderSize+marginSize)
textArea := image.Rect(borderSize, textPosStart-marginSize, srcWidth-borderSize, textPosEnd+marginSize)
marginSize := fontSize * p.pctMargin / 100
var textPosStart, textPosEnd int
if p.align == "bottom" {
textPosStart = srcHeight - textHeight - p.borderSize - marginSize
textPosEnd = srcHeight - p.borderSize - marginSize
} else {
textPosStart = srcHeight/2 - textHeight/2
textPosEnd = srcHeight/2 + textHeight/2
}
borderArea := image.Rect((srcWidth-(srcWidth*p.pctWidth/100))/2, textPosStart-p.borderSize-marginSize, (srcWidth+(srcWidth*p.pctWidth/100))/2, textPosEnd+p.borderSize+marginSize)
textArea := image.Rect(borderArea.Bounds().Min.X+p.borderSize, textPosStart-marginSize, borderArea.Bounds().Max.X-p.borderSize, textPosEnd+marginSize)
draw.Draw(
dst,
@ -76,9 +89,10 @@ func (p *coverTitle) Draw(dst draw.Image, src image.Image, options *gift.Options
c.SetDst(dst)
c.SetSrc(image.Black)
textLeft := srcWidth/2 - textWidth/2
if textLeft < borderSize {
textLeft = borderSize
textLeft := textArea.Min.X + textArea.Dx()/2 - textWidth/2
if textLeft < textArea.Min.X {
textLeft = textArea.Min.X
}
c.DrawString(p.title, freetype.Pt(textLeft, srcHeight/2+textHeight/4))
textTop := textArea.Min.Y + textArea.Dy()/2 + textHeight/4
c.DrawString(p.title, freetype.Pt(textLeft, textTop))
}

@ -6,6 +6,7 @@ package epubimageprocessor
import (
"fmt"
"image"
"image/color"
"image/draw"
"os"
"sync"
@ -168,7 +169,7 @@ func (e *EPUBImageProcessor) createImage(src image.Image, r image.Rectangle) dra
// transform image into 1 or 3 images
// only doublepage with autosplit has 3 versions
func (e *EPUBImageProcessor) transformImage(src image.Image, srcId int) []image.Image {
var filters, splitFilter []gift.Filter
var filters, splitFilters []gift.Filter
var images []image.Image
// Lookup for margin if crop is enable or if we want to remove blank image
@ -188,7 +189,7 @@ func (e *EPUBImageProcessor) transformImage(src image.Image, srcId int) []image.
// crop is enable or if blank image with noblankimage options
if e.Image.Crop.Enabled || (e.Image.NoBlankImage && isBlank) {
filters = append(filters, f)
splitFilter = append(splitFilter, f)
splitFilters = append(splitFilters, f)
}
}
@ -199,13 +200,13 @@ func (e *EPUBImageProcessor) transformImage(src image.Image, srcId int) []image.
if e.Image.Contrast != 0 {
f := gift.Contrast(float32(e.Image.Contrast))
filters = append(filters, f)
splitFilter = append(splitFilter, f)
splitFilters = append(splitFilters, f)
}
if e.Image.Brightness != 0 {
f := gift.Brightness(float32(e.Image.Brightness))
filters = append(filters, f)
splitFilter = append(splitFilter, f)
splitFilters = append(splitFilters, f)
}
if e.Image.Resize {
@ -213,6 +214,26 @@ func (e *EPUBImageProcessor) transformImage(src image.Image, srcId int) []image.
filters = append(filters, f)
}
if e.Image.GrayScale {
var f gift.Filter
switch e.Image.GrayScaleMode {
case 1: // average
f = gift.ColorFunc(func(r0, g0, b0, a0 float32) (r float32, g float32, b float32, a float32) {
y := (r0 + g0 + b0) / 3
return y, y, y, a0
})
case 2: // luminance
f = gift.ColorFunc(func(r0, g0, b0, a0 float32) (r float32, g float32, b float32, a float32) {
y := 0.2126*r0 + 0.7152*g0 + 0.0722*b0
return y, y, y, a0
})
default:
f = gift.Grayscale()
}
filters = append(filters, f)
splitFilters = append(splitFilters, f)
}
filters = append(filters, epubimagefilters.Pixel())
// convert
@ -240,7 +261,7 @@ func (e *EPUBImageProcessor) transformImage(src image.Image, srcId int) []image.
// convert double page
for _, b := range []bool{e.Image.Manga, !e.Image.Manga} {
g := gift.New(splitFilter...)
g := gift.New(splitFilters...)
g.Add(epubimagefilters.CropSplitDoublePage(b))
if e.Image.Resize {
g.Add(gift.ResizeToFit(e.Image.View.Width, e.Image.View.Height, gift.LanczosResampling))
@ -253,15 +274,52 @@ func (e *EPUBImageProcessor) transformImage(src image.Image, srcId int) []image.
return images
}
type CoverTitleDataOptions struct {
Src image.Image
Name string
Text string
Align string
PctWidth int
PctMargin int
MaxFontSize int
BorderSize int
}
func (e *EPUBImageProcessor) Cover16LevelOfGray(bounds image.Rectangle) draw.Image {
return image.NewPaletted(bounds, color.Palette{
color.Gray{0x00},
color.Gray{0x11},
color.Gray{0x22},
color.Gray{0x33},
color.Gray{0x44},
color.Gray{0x55},
color.Gray{0x66},
color.Gray{0x77},
color.Gray{0x88},
color.Gray{0x99},
color.Gray{0xAA},
color.Gray{0xBB},
color.Gray{0xCC},
color.Gray{0xDD},
color.Gray{0xEE},
color.Gray{0xFF},
})
}
// create a title page with the cover
func (e *EPUBImageProcessor) CoverTitleData(src image.Image, title string) (*epubzip.ZipImage, error) {
func (e *EPUBImageProcessor) CoverTitleData(o *CoverTitleDataOptions) (*epubzip.ZipImage, error) {
// Create a blur version of the cover
g := gift.New(epubimagefilters.CoverTitle(title))
dst := e.createImage(src, g.Bounds(src.Bounds()))
g.Draw(dst, src)
g := gift.New(epubimagefilters.CoverTitle(o.Text, o.Align, o.PctWidth, o.PctMargin, o.MaxFontSize, o.BorderSize))
var dst draw.Image
if o.Name == "cover" && e.Image.GrayScale {
dst = e.Cover16LevelOfGray(o.Src.Bounds())
} else {
dst = e.createImage(o.Src, g.Bounds(o.Src.Bounds()))
}
g.Draw(dst, o.Src)
return epubzip.CompressImage(
fmt.Sprintf("OEBPS/Images/title.%s", e.Image.Format),
fmt.Sprintf("OEBPS/Images/%s.%s", o.Name, e.Image.Format),
e.Image.Format,
dst,
e.Image.Quality,

@ -33,6 +33,7 @@ type Image struct {
HasCover bool
View *View
GrayScale bool
GrayScaleMode int
Resize bool
Format string
}
@ -41,6 +42,7 @@ type Options struct {
Input string
Output string
Title string
TitlePage int
Author string
LimitMb int
StripFirstDirectoryFromToc bool

@ -10,6 +10,7 @@ import (
type ContentOptions struct {
Title string
HasTitlePage bool
UID string
Author string
Publisher string
@ -124,9 +125,7 @@ func getMeta(o *ContentOptions) []tag {
metas = append(metas, tag{"meta", tagAttrs{"name": "primary-writing-mode", "content": "horizontal-lr"}, ""})
}
if o.Cover != nil {
metas = append(metas, tag{"meta", tagAttrs{"name": "cover", "content": o.Cover.ImgKey()}, ""})
}
metas = append(metas, tag{"meta", tagAttrs{"name": "cover", "content": "img_cover"}, ""})
if o.Total > 1 {
metas = append(
@ -158,16 +157,19 @@ func getManifest(o *ContentOptions) []tag {
items := []tag{
{"item", tagAttrs{"id": "toc", "href": "toc.xhtml", "properties": "nav", "media-type": "application/xhtml+xml"}, ""},
{"item", tagAttrs{"id": "css", "href": "Text/style.css", "media-type": "text/css"}, ""},
{"item", tagAttrs{"id": "page_title", "href": "Text/title.xhtml", "media-type": "application/xhtml+xml"}, ""},
{"item", tagAttrs{"id": "img_title", "href": fmt.Sprintf("Images/title.%s", o.ImageOptions.Format), "media-type": fmt.Sprintf("image/%s", o.ImageOptions.Format)}, ""},
{"item", tagAttrs{"id": "page_cover", "href": "Text/cover.xhtml", "media-type": "application/xhtml+xml"}, ""},
{"item", tagAttrs{"id": "img_cover", "href": fmt.Sprintf("Images/cover.%s", o.ImageOptions.Format), "media-type": fmt.Sprintf("image/%s", o.ImageOptions.Format)}, ""},
}
if !o.ImageOptions.View.PortraitOnly {
items = append(items, tag{"item", tagAttrs{"id": "space_title", "href": "Text/space_title.xhtml", "media-type": "application/xhtml+xml"}, ""})
}
if o.HasTitlePage {
items = append(items,
tag{"item", tagAttrs{"id": "page_title", "href": "Text/title.xhtml", "media-type": "application/xhtml+xml"}, ""},
tag{"item", tagAttrs{"id": "img_title", "href": fmt.Sprintf("Images/title.%s", o.ImageOptions.Format), "media-type": fmt.Sprintf("image/%s", o.ImageOptions.Format)}, ""},
)
if o.ImageOptions.HasCover || o.Current > 1 {
addTag(o.Cover, false)
if !o.ImageOptions.View.PortraitOnly {
items = append(items, tag{"item", tagAttrs{"id": "space_title", "href": "Text/space_title.xhtml", "media-type": "application/xhtml+xml"}, ""})
}
}
lastImage := o.Images[len(o.Images)-1]
@ -202,9 +204,12 @@ func getSpineAuto(o *ContentOptions) []tag {
return fmt.Sprintf("%s layout-blank", getSpread(false))
}
spine := []tag{
{"itemref", tagAttrs{"idref": "space_title", "properties": getSpreadBlank()}, ""},
{"itemref", tagAttrs{"idref": "page_title", "properties": getSpread(false)}, ""},
spine := []tag{}
if o.HasTitlePage {
spine = append(spine,
tag{"itemref", tagAttrs{"idref": "space_title", "properties": getSpreadBlank()}, ""},
tag{"itemref", tagAttrs{"idref": "page_title", "properties": getSpread(false)}, ""},
)
}
for _, img := range o.Images {
if img.DoublePage && o.ImageOptions.Manga == isOnTheRight {
@ -234,8 +239,11 @@ func getSpineAuto(o *ContentOptions) []tag {
}
func getSpinePortrait(o *ContentOptions) []tag {
spine := []tag{
{"itemref", tagAttrs{"idref": "page_title"}, ""},
spine := []tag{}
if o.HasTitlePage {
spine = append(spine,
tag{"itemref", tagAttrs{"idref": "page_title"}, ""},
)
}
for _, img := range o.Images {
spine = append(spine, tag{
@ -249,10 +257,8 @@ func getSpinePortrait(o *ContentOptions) []tag {
// guide part of the content
func getGuide(o *ContentOptions) []tag {
guide := []tag{}
if o.Cover != nil {
guide = append(guide, tag{"reference", tagAttrs{"type": "cover", "title": "cover", "href": o.Cover.PagePath()}, ""})
return []tag{
{"reference", tagAttrs{"type": "cover", "title": "cover", "href": "Text/cover.xhtml"}, ""},
{"reference", tagAttrs{"type": "text", "title": "content", "href": o.Images[0].PagePath()}, ""},
}
guide = append(guide, tag{"reference", tagAttrs{"type": "text", "title": "content", "href": o.Images[0].PagePath()}, ""})
return guide
}

@ -9,7 +9,7 @@ import (
)
// create toc
func Toc(title string, stripFirstDirectoryFromToc bool, images []*epubimage.Image) string {
func Toc(title string, hasTitle bool, stripFirstDirectoryFromToc bool, images []*epubimage.Image) string {
doc := etree.NewDocument()
doc.CreateProcInst("xml", `version="1.0" encoding="UTF-8"`)
doc.CreateDirective("DOCTYPE html")
@ -55,7 +55,11 @@ func Toc(title string, stripFirstDirectoryFromToc bool, images []*epubimage.Imag
beginning := etree.NewElement("li")
beginningLink := beginning.CreateElement("a")
beginningLink.CreateAttr("href", "Text/title.xhtml")
if hasTitle {
beginningLink.CreateAttr("href", "Text/title.xhtml")
} else {
beginningLink.CreateAttr("href", images[0].PagePath())
}
beginningLink.CreateText(title)
ol.InsertChildAt(0, beginning)

@ -105,6 +105,7 @@ $ go install github.com/celogeek/go-comic-converter/v%d@%s
Output: cmd.Options.Output,
LimitMb: cmd.Options.LimitMb,
Title: cmd.Options.Title,
TitlePage: cmd.Options.TitlePage,
Author: cmd.Options.Author,
StripFirstDirectoryFromToc: cmd.Options.StripFirstDirectoryFromToc,
SortPathMode: cmd.Options.SortPathMode,
@ -113,8 +114,9 @@ $ go install github.com/celogeek/go-comic-converter/v%d@%s
DryVerbose: cmd.Options.DryVerbose,
Quiet: cmd.Options.Quiet,
Image: &epuboptions.Image{
Quality: cmd.Options.Quality,
GrayScale: cmd.Options.Grayscale,
Quality: cmd.Options.Quality,
GrayScale: cmd.Options.Grayscale,
GrayScaleMode: cmd.Options.GrayscaleMode,
Crop: &epuboptions.Crop{
Enabled: cmd.Options.Crop,
Left: cmd.Options.CropRatioLeft,