add cover image support (#7)

This commit is contained in:
T.v.Dein
2025-10-19 20:39:04 +02:00
committed by GitHub
parent c2abc4ba4d
commit 2c6e81a2c8
7 changed files with 110 additions and 33 deletions

View File

@@ -32,7 +32,7 @@ import (
)
const (
Version string = `v0.0.4`
Version string = `v0.0.5`
Usage string = `This is epuppy, a terminal ui ebook viewer.
Usage: epuppy [options] <epub file>
@@ -42,6 +42,7 @@ Options:
-s --store-progress remember reading position
-n --line-numbers add line numbers
-c --config <file> use config <file>
-i --cover-image display cover image
-t --txt dump readable content to STDOUT
-x --xml dump source xml to STDOUT
-N --no-color disable colors (or use $NO_COLOR env var)
@@ -63,6 +64,7 @@ type Config struct {
ColorDark ColorSetting `koanf:"colordark"` // comes from config file only
ColorLight ColorSetting `koanf:"colorlight"` // comes from config file only
ShowHelp bool `koanf:"help"`
ShowCover bool `koanf:"cover-image"` // -i
Colors Colors // generated from user config file or internal defaults, respects dark mode
Document string
InitialProgress int // lines
@@ -89,6 +91,7 @@ func InitConfig(output io.Writer) (*Config, error) {
flagset.BoolP("txt", "t", false, "dump readable content to STDOUT")
flagset.BoolP("xml", "x", false, "dump xml to STDOUT")
flagset.BoolP("no-color", "N", false, "disable colors")
flagset.BoolP("cover-image", "i", false, "show cover image")
flagset.BoolP("help", "h", false, "show help")
flagset.StringP("config", "c", "", "read config from file")

View File

@@ -21,10 +21,13 @@ package cmd
// https://github.com/charmbracelet/bubbletea/tree/main/examples/pager
import (
"bytes"
"fmt"
"image"
"os"
"strings"
"github.com/blacktop/go-termimg"
"github.com/charmbracelet/bubbles/help"
"github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/bubbles/viewport"
@@ -132,6 +135,7 @@ type Doc struct {
hideUi bool
meta *Meta
config *Config
Cover *termimg.ImageWidget
keys keyMap
help help.Model
@@ -220,7 +224,12 @@ func (m *Doc) Rewrap() {
// wrapping has changed, update viewport and line count
if content != "" {
m.viewport.SetContent(content)
if m.Cover != nil {
cover, _ := m.Cover.Render()
m.viewport.SetContent(cover + "\n\n" + content)
} else {
m.viewport.SetContent(content)
}
m.meta.lines = len(strings.Split(content, "\n"))
}
@@ -270,7 +279,15 @@ func max(a, b int) int {
return b
}
func Pager(conf *Config, title, message string) (int, error) {
type Ebook struct {
Config *Config
Title string
Body string
Cover []byte
MediaType string
}
func Pager(book *Ebook) (int, error) {
width := 80
scrollto := 0
@@ -281,32 +298,44 @@ func Pager(conf *Config, title, message string) (int, error) {
}
}
if conf.StoreProgress {
scrollto = conf.InitialProgress
if book.Config.StoreProgress {
scrollto = book.Config.InitialProgress
}
if conf.LineNumbers {
if book.Config.LineNumbers {
catn := ""
for idx, line := range strings.Split(message, "\n") {
for idx, line := range strings.Split(book.Body, "\n") {
catn += fmt.Sprintf("%4d: %s\n", idx, line)
}
message = catn
book.Body = catn
}
meta := Meta{
initialprogress: scrollto,
lines: len(strings.Split(message, "\n")),
lines: len(strings.Split(book.Body, "\n")),
}
doc := Doc{
content: book.Body,
title: book.Title,
initialwidth: width,
meta: &meta,
config: book.Config,
keys: keys,
}
if book.MediaType != "" && book.Config.ShowCover {
img, _, err := image.Decode(bytes.NewReader(book.Cover))
if err != nil {
return 0, fmt.Errorf("failed to load cover image: %w", err)
}
doc.Cover = termimg.NewImageWidgetFromImage(img)
doc.Cover.SetSize(width, width).SetProtocol(termimg.Auto)
}
p := tea.NewProgram(
Doc{
content: message,
title: title,
initialwidth: width,
meta: &meta,
config: conf,
keys: keys,
},
doc,
tea.WithAltScreen(), // use the full size of the terminal in its "alternate screen buffer"
tea.WithMouseCellMotion(), // turn on mouse support so we can track the mouse wheel
)
@@ -315,7 +344,7 @@ func Pager(conf *Config, title, message string) (int, error) {
return 0, fmt.Errorf("could not run pager: %w", err)
}
if conf.Debug {
if book.Config.Debug {
fmt.Printf("scrollto: %d, last: %d, diff: %d\n",
scrollto, meta.currentline, scrollto-meta.currentline)
}

View File

@@ -44,7 +44,11 @@ func ViewText(conf *Config) (int, error) {
return fmt.Println(string(data))
}
return Pager(conf, conf.Document, string(data))
return Pager(&Ebook{
Config: conf,
Title: conf.Document,
Body: string(data),
})
}
func ViewEpub(conf *Config) (int, error) {
@@ -74,11 +78,15 @@ func ViewEpub(conf *Config) (int, error) {
return fmt.Println(buf.String())
}
return Pager(conf, head.String(), buf.String())
return Pager(&Ebook{
Config: conf,
Title: head.String(),
Body: buf.String(),
Cover: book.CoverImage,
MediaType: book.CoverMediaType,
})
}
// FIXME: since the switch to book.Files() in epub.Open() this
// returns invalid chapter numbering
func fetchByContent(conf *Config, buf *strings.Builder, book *epub.Book) bool {
chapter := 1
var gotbody bool