diff options
author | Scrotadamus <scrotadamus@insiberia.net> | 2025-03-07 17:09:07 +0100 |
---|---|---|
committer | Scrotadamus <scrotadamus@insiberia.net> | 2025-03-07 17:11:10 +0100 |
commit | 30417cde4c7353a6c8e960f6be84a6c76c512e60 (patch) | |
tree | dec1d0da29db0cbb8226dccc13b94c5f1c8fe2f9 | |
parent | 7e25e46a37d39ba546889c7e5056257ba2364c2e (diff) |
Feature -- minimal pdf browser
added minimal pdf browser, still experimental (like everything here lol!)
Changes to be committed:
new file: cmd/browse.go
modified: document/document.go
modified: go-poppler/page.go
-rw-r--r-- | cmd/browse.go | 310 | ||||
-rw-r--r-- | document/document.go | 18 | ||||
-rw-r--r-- | go-poppler/page.go | 21 |
3 files changed, 339 insertions, 10 deletions
diff --git a/cmd/browse.go b/cmd/browse.go new file mode 100644 index 0000000..a6abac0 --- /dev/null +++ b/cmd/browse.go @@ -0,0 +1,310 @@ +/* +Copyright © 2025 NAME HERE <EMAIL ADDRESS> +*/ +package cmd + +import ( + "fmt" + "os" + + "github.com/gdamore/tcell/v2" + "github.com/rivo/tview" + "github.com/scrotadamus/ghligh/document" + "github.com/spf13/cobra" +) + +type browsableDoc struct { + doc *document.GhlighDoc + currentPage int + + title string + author string + nPages int +} + +func (d *browsableDoc) getCurrentPage() int { + return d.currentPage +} +func (d *browsableDoc) setCurrentPage(i int) { + switch { + case i >= d.nPages: + d.currentPage = d.nPages - 1 + case i < 0: + d.currentPage = 0 + default: + d.currentPage = i + } +} + +func openBrowsableDoc(path string) (*browsableDoc, error) { + // open and init document + doc, err := document.Open(path) + if err != nil { + return nil, err + } + + b := &browsableDoc{ + doc: doc, + currentPage: 0, + title: doc.Info().Title, + author: doc.Info().Author, + nPages: doc.GetNPages(), + } + return b, nil +} + +func (d *browsableDoc) header() string { + return fmt.Sprintf("[yellow]%s - %s ~ Page %d of %d[-]", + d.title, + d.author, + d.currentPage+1, + d.nPages, + ) +} + +func (d *browsableDoc) status(accumulator int) string { + + accumulated := "" + if accumulator != 0 { + accumulated = fmt.Sprintf("[%d] --", accumulator) + } + + // TODO + // return fmt.Sprintf("%s %d", accumulated, percetuale_cursore) + return fmt.Sprintf("%s page %d of %d", + accumulated, + d.currentPage+1, + d.nPages, + ) + +} + +func (d *browsableDoc) getCurrentPageText() string { + text, _ := d.doc.GetPageText(d.currentPage) + return text +} + +type Browser struct { + app *tview.Application + pageContent *tview.TextView + header *tview.TextView + status *tview.TextView + layout *tview.Flex + + docs []*browsableDoc + currDoc int + + accumulator int +} + +func newBrowser(paths []string) *Browser { + app := tview.NewApplication() + header := tview.NewTextView(). + SetDynamicColors(true). + SetRegions(true). + SetWordWrap(true). + SetChangedFunc(func() { + app.Draw() + }) + pageContent := tview.NewTextView(). + SetDynamicColors(true). + SetRegions(true). + SetWordWrap(true). + SetChangedFunc(func() { + app.Draw() + }) + status := tview.NewTextView(). + SetDynamicColors(true). + SetRegions(true). + SetWordWrap(true). + SetChangedFunc(func() { + app.Draw() + }) + layout := tview.NewFlex().SetDirection(tview.FlexRow). + AddItem(header, 1, 0, false). // TODO Fixed size header (1 line) + AddItem(pageContent, 0, 1, true). // TODO Content takes all remaining space + AddItem(status, 1, 0, false) // TODO Fixed size footer (1 line) + + var docs []*browsableDoc + for _, path := range paths { + doc, err := openBrowsableDoc(path) + if err != nil { + fmt.Fprintf(os.Stderr, "%s", err) + } + docs = append(docs, doc) + } + + return &Browser{ + app: app, + header: header, + pageContent: pageContent, + status: status, + layout: layout, + docs: docs, + } + +} + +func (b *Browser) currentDoc() *browsableDoc { + return b.docs[b.currDoc] +} + +func (b *Browser) updateStatus() { + b.status.SetText(b.currentDoc().status(b.accumulator)) +} + +func (b *Browser) updateHeader() { + b.header.SetText(b.currentDoc().header()) +} + +func (b *Browser) updateContent() { + text := b.currentDoc().getCurrentPageText() + + b.pageContent. + SetTextAlign(tview.AlignLeft). + SetText(text). + SetBorder(true) +} + +func (b *Browser) updatePage(newPage int) { + // lock mutex here + b.currentDoc().setCurrentPage(newPage) + b.updateHeader() + b.updateContent() + // unlock mutex here + + b.updateStatus() +} +func (b *Browser) Run() { + b.updatePage(0) + b.app.SetInputCapture(b.handle) + if err := b.app.SetRoot(b.layout, true).Run(); err != nil { + panic(err) + } +} + +// if acc is equal to 0 return 1, return acc otherwise +func newAcc(acc int) int { + if acc == 0 { + return 1 + } + return acc +} + +func (b *Browser) nextDoc(acc int) { + acc = newAcc(acc) + // todo implement + +} +func (b *Browser) prevDoc(acc int) { + acc = newAcc(acc) + // todo implement + +} +func (b *Browser) nextPage(acc int) { + b.resetAccumulator() + acc = newAcc(acc) + currentPage := b.currentDoc().getCurrentPage() + b.updatePage(currentPage + acc) +} +func (b *Browser) prevPage(acc int) { + b.resetAccumulator() + acc = newAcc(acc) + currentPage := b.currentDoc().getCurrentPage() + b.updatePage(currentPage - acc) + +} +func (b *Browser) scrollUp(acc int) { + b.resetAccumulator() + acc = newAcc(acc) + b.pageContent.ScrollTo(acc, 0) + b.updateStatus() + +} +func (b *Browser) scrollDown(acc int) { + b.resetAccumulator() + acc = newAcc(acc) + b.pageContent.ScrollTo(acc, 0) + b.updateStatus() +} + +func (b *Browser) resetAccumulator() { + b.accumulator = 0 +} +func (b *Browser) handle(event *tcell.EventKey) *tcell.EventKey { + switch event.Key() { + case tcell.KeyEscape: + b.accumulator = 0 + b.updateStatus() + case tcell.KeyRune: + switch event.Rune() { + case 'Q': + b.app.Stop() + return event + //TODO case 'q': QUIT CURRENT DOCUMENT + // b.app.Stop() + // return event + case 'N': + b.nextDoc(b.accumulator) + case 'P': + b.prevDoc(b.accumulator) + case 'n': + b.nextPage(b.accumulator) + case 'p': + b.prevPage(b.accumulator) + case 'j': + b.scrollUp(b.accumulator) + case 'k': + b.scrollDown(b.accumulator) + default: + if event.Rune() >= '0' && event.Rune() <= '9' { + b.accumulator = b.accumulator*10 + int(event.Rune()-'0') + b.updateStatus() + } + } + } + return event +} + +// browseCmd represents the browse command +var browseCmd = &cobra.Command{ + Use: "browse", + Short: "browse pdf file", + Long: `A longer description that spans multiple lines and likely contains examples +and usage of using your command. For example: + +Cobra is a CLI library for Go that empowers applications. +This application is a tool to generate the needed files +to quickly create a Cobra application.`, + Run: func(cmd *cobra.Command, args []string) { + + var docs []*document.GhlighDoc + for _, arg := range args { + doc, err := document.Open(arg) + if err != nil { + fmt.Fprintf(os.Stderr, "could not open %s: %v\n", arg, err) + continue + } + docs = append(docs, doc) + } + + // if more then one file show menu + browser := newBrowser(args) + browser.Run() + + }, +} + +func init() { + rootCmd.AddCommand(browseCmd) + + // Here you will define your flags and configuration settings. + + // Cobra supports Persistent Flags which will work for this command + // and all subcommands, e.g.: + // browseCmd.PersistentFlags().String("foo", "", "A help for foo") + + // Cobra supports local flags which will only run when this command + // is called directly, e.g.: + // browseCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} diff --git a/document/document.go b/document/document.go index a036eee..cd84875 100644 --- a/document/document.go +++ b/document/document.go @@ -134,6 +134,24 @@ func integrityCheck(tizio *GhlighDoc, caio *GhlighDoc) { } +func (d *GhlighDoc) GetNPages() int { + return d.doc.GetNPages() +} + +func (d *GhlighDoc) GetPageText(i int) (string, error) { + nPages := d.doc.GetNPages() + + if i < 0 || i > nPages { + return "", fmt.Errorf("error page %d out of range %d", i, nPages) + } + + p := d.doc.GetPage(i) + defer p.Close() + + text := p.Text() + return text, nil +} + func (d *GhlighDoc) Save() (bool, error) { d.mu.Lock() defer d.mu.Unlock() diff --git a/go-poppler/page.go b/go-poppler/page.go index efd0706..ffb408c 100644 --- a/go-poppler/page.go +++ b/go-poppler/page.go @@ -5,13 +5,16 @@ package poppler // #include <glib.h> // #include <cairo.h> import "C" -import "unsafe" -import "github.com/ungerik/go-cairo" +import ( + "unsafe" + + "github.com/ungerik/go-cairo" +) //import "fmt" type Page struct { - p *C.struct__PopplerPage + p *C.struct__PopplerPage openedAnnots []*Annot } @@ -145,28 +148,28 @@ func (p *Page) Close() { // Converts a page into SVG and saves to file. // Inspired by https://github.com/dawbarton/pdf2svg -func (p *Page) ConvertToSVG(filename string){ +func (p *Page) ConvertToSVG(filename string) { width, height := p.Size() // Open the SVG file - surface := cairo.NewSVGSurface( filename, width, height, cairo.SVG_VERSION_1_2 ) + surface := cairo.NewSVGSurface(filename, width, height, cairo.SVG_VERSION_1_2) // TODO Can be improved by using cairo_svg_surface_create_for_stream() instead of // cairo_svg_surface_create() for stream processing instead of file processing. // However, this needs to be changed in github.com/ungerik/go-cairo/surface.go // Get cairo context pointer - _, drawcontext := surface.Native() + _, drawcontext := surface.Native() // Render the PDF file into the SVG file - C.poppler_page_render_for_printing(p.p, (*C.cairo_t)(unsafe.Pointer(drawcontext)) ); + C.poppler_page_render_for_printing(p.p, (*C.cairo_t)(unsafe.Pointer(drawcontext))) // Close the SVG file surface.ShowPage() surface.Destroy() } -func (p *Page) closeAnnotMappings(){ +func (p *Page) closeAnnotMappings() { for i := 0; i < len(p.openedAnnots); i++ { p.openedAnnots[i].Close() } @@ -186,7 +189,6 @@ func (p *Page) GetAnnots() (Annots []*Annot) { for annotGlist != nil { popplerAnnot := (*C.PopplerAnnotMapping)(annotGlist.data) - annot := &Annot{ am: popplerAnnot, } @@ -197,7 +199,6 @@ func (p *Page) GetAnnots() (Annots []*Annot) { annots = append(annots, annot) p.openedAnnots = append(p.openedAnnots, annot) - annotGlist = annotGlist.next } |