Initial orientator
This commit is contained in:
51
internal/rasterize/pdf.go
Normal file
51
internal/rasterize/pdf.go
Normal file
@@ -0,0 +1,51 @@
|
||||
// Package rasterize converts PDF pages to in-memory images using MuPDF.
|
||||
package rasterize
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
|
||||
"github.com/gen2brain/go-fitz"
|
||||
)
|
||||
|
||||
// DPI used when rasterizing pages for the orientation classifier.
|
||||
// 150 mirrors the Ghostscript -r150 flag used in the original Python pipeline.
|
||||
const DPI = 150
|
||||
|
||||
type Page struct {
|
||||
Index int
|
||||
Img image.Image
|
||||
}
|
||||
|
||||
// PDF wraps an open MuPDF document.
|
||||
type PDF struct {
|
||||
doc *fitz.Document
|
||||
}
|
||||
|
||||
func Open(data []byte) (*PDF, error) {
|
||||
doc, err := fitz.NewFromMemory(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fitz open: %w", err)
|
||||
}
|
||||
return &PDF{doc: doc}, nil
|
||||
}
|
||||
|
||||
func (p *PDF) Close() error {
|
||||
if p == nil || p.doc == nil {
|
||||
return nil
|
||||
}
|
||||
return p.doc.Close()
|
||||
}
|
||||
|
||||
func (p *PDF) NumPages() int {
|
||||
return p.doc.NumPage()
|
||||
}
|
||||
|
||||
// RenderPage rasterizes a single page at the configured DPI.
|
||||
func (p *PDF) RenderPage(idx int) (image.Image, error) {
|
||||
img, err := p.doc.ImageDPI(idx, DPI)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("render page %d: %w", idx, err)
|
||||
}
|
||||
return img, nil
|
||||
}
|
||||
51
internal/rasterize/rotate.go
Normal file
51
internal/rasterize/rotate.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package rasterize
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/pdfcpu/pdfcpu/pkg/api"
|
||||
"github.com/pdfcpu/pdfcpu/pkg/pdfcpu/model"
|
||||
)
|
||||
|
||||
// RotatePDFPages applies clockwise rotations (degrees, multiples of 90) to each page.
|
||||
// pageRotations is 1-indexed slice aligned with input pages: pageRotations[0] = page 1.
|
||||
func RotatePDFPages(in []byte, pageRotations []int) ([]byte, error) {
|
||||
conf := model.NewDefaultConfiguration()
|
||||
conf.ValidationMode = model.ValidationRelaxed
|
||||
|
||||
// pdfcpu's Rotate API takes a single rotation and a page selection. We group
|
||||
// pages by rotation value to minimize passes.
|
||||
groups := map[int][]int{}
|
||||
for i, r := range pageRotations {
|
||||
r = ((r % 360) + 360) % 360
|
||||
if r == 0 {
|
||||
continue
|
||||
}
|
||||
groups[r] = append(groups[r], i+1)
|
||||
}
|
||||
if len(groups) == 0 {
|
||||
out := make([]byte, len(in))
|
||||
copy(out, in)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
current := bytes.NewReader(in)
|
||||
var buf bytes.Buffer
|
||||
for rot, pages := range groups {
|
||||
buf.Reset()
|
||||
selectedPages := make([]string, len(pages))
|
||||
for i, p := range pages {
|
||||
selectedPages[i] = fmt.Sprintf("%d", p)
|
||||
}
|
||||
if err := api.Rotate(current, &buf, rot, selectedPages, conf); err != nil {
|
||||
return nil, fmt.Errorf("rotate %d°: %w", rot, err)
|
||||
}
|
||||
current = bytes.NewReader(append([]byte(nil), buf.Bytes()...))
|
||||
}
|
||||
out := make([]byte, current.Size())
|
||||
if _, err := current.ReadAt(out, 0); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
Reference in New Issue
Block a user