pridany zdrojovy kod, plne generovane AI Codex (OpenAI)
This commit is contained in:
23
go.mod
Normal file
23
go.mod
Normal file
@ -0,0 +1,23 @@
|
||||
module pdftrim
|
||||
|
||||
go 1.23.0
|
||||
|
||||
toolchain go1.23.2
|
||||
|
||||
require (
|
||||
github.com/pdfcpu/pdfcpu v0.10.2
|
||||
rsc.io/pdf v0.1.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/hhrutter/lzw v1.0.0 // indirect
|
||||
github.com/hhrutter/pkcs7 v0.2.0 // indirect
|
||||
github.com/hhrutter/tiff v1.0.2 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
golang.org/x/crypto v0.37.0 // indirect
|
||||
golang.org/x/image v0.26.0 // indirect
|
||||
golang.org/x/text v0.24.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
)
|
||||
27
go.sum
Normal file
27
go.sum
Normal file
@ -0,0 +1,27 @@
|
||||
github.com/hhrutter/lzw v1.0.0 h1:laL89Llp86W3rRs83LvKbwYRx6INE8gDn0XNb1oXtm0=
|
||||
github.com/hhrutter/lzw v1.0.0/go.mod h1:2HC6DJSn/n6iAZfgM3Pg+cP1KxeWc3ezG8bBqW5+WEo=
|
||||
github.com/hhrutter/pkcs7 v0.2.0 h1:i4HN2XMbGQpZRnKBLsUwO3dSckzgX142TNqY/KfXg+I=
|
||||
github.com/hhrutter/pkcs7 v0.2.0/go.mod h1:aEzKz0+ZAlz7YaEMY47jDHL14hVWD6iXt0AgqgAvWgE=
|
||||
github.com/hhrutter/tiff v1.0.2 h1:7H3FQQpKu/i5WaSChoD1nnJbGx4MxU5TlNqqpxw55z8=
|
||||
github.com/hhrutter/tiff v1.0.2/go.mod h1:pcOeuK5loFUE7Y/WnzGw20YxUdnqjY1P0Jlcieb/cCw=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/pdfcpu/pdfcpu v0.10.2 h1:DB2dWuoq0eF0QwHjgyLirYKLTCzFOoZdmmIUSu72aL0=
|
||||
github.com/pdfcpu/pdfcpu v0.10.2/go.mod h1:Q2Z3sqdRqHTdIq1mPAUl8nfAoim8p3c1ASOaQ10mCpE=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||
golang.org/x/image v0.26.0 h1:4XjIFEZWQmCZi6Wv8BoxsDhRU3RVnLX04dToTDAEPlY=
|
||||
golang.org/x/image v0.26.0/go.mod h1:lcxbMFAovzpnJxzXS3nyL83K27tmqtKzIJpctK8YO5c=
|
||||
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
||||
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
||||
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=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
690
main.go
Normal file
690
main.go
Normal file
@ -0,0 +1,690 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/pdfcpu/pdfcpu/pkg/api"
|
||||
"github.com/pdfcpu/pdfcpu/pkg/pdfcpu/model"
|
||||
"github.com/pdfcpu/pdfcpu/pkg/pdfcpu/types"
|
||||
"rsc.io/pdf"
|
||||
)
|
||||
|
||||
const (
|
||||
paddingPoints = 6.0
|
||||
minSidePoints = 1.0
|
||||
)
|
||||
|
||||
type rect struct {
|
||||
llx float64
|
||||
lly float64
|
||||
urx float64
|
||||
ury float64
|
||||
}
|
||||
|
||||
type pageCrop struct {
|
||||
crop rect
|
||||
apply bool
|
||||
}
|
||||
|
||||
type matrix [3][3]float64
|
||||
|
||||
var identity = matrix{{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
fmt.Fprintf(os.Stderr, "Pouzitie: %s subor.pdf\n", filepath.Base(os.Args[0]))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
input := os.Args[1]
|
||||
if strings.ToLower(filepath.Ext(input)) != ".pdf" {
|
||||
fmt.Fprintln(os.Stderr, "Chyba: prvy argument musi byt PDF subor.")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(input); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Chyba: subor %q neexistuje alebo sa neda citat (%v).\n", input, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
output := trimmedPath(input)
|
||||
if err := trimPDF(input, output); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Chyba: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Printf("Hotovo: %s\n", output)
|
||||
}
|
||||
|
||||
func trimPDF(input, output string) error {
|
||||
reader, err := pdf.Open(input)
|
||||
if err != nil {
|
||||
return fmt.Errorf("nepodarilo sa otvorit PDF: %w", err)
|
||||
}
|
||||
|
||||
pageCount := reader.NumPage()
|
||||
if pageCount == 0 {
|
||||
return errors.New("PDF nema ziadne strany")
|
||||
}
|
||||
|
||||
plan := make([]pageCrop, pageCount)
|
||||
for i := 1; i <= pageCount; i++ {
|
||||
p := reader.Page(i)
|
||||
if p.V.IsNull() {
|
||||
continue
|
||||
}
|
||||
|
||||
pageBox, err := effectivePageBox(p.V)
|
||||
if err != nil {
|
||||
return fmt.Errorf("strana %d: %w", i, err)
|
||||
}
|
||||
|
||||
contentBox, found := detectContentBox(p)
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
|
||||
contentBox = contentBox.expand(paddingPoints).clamp(pageBox)
|
||||
if contentBox.width() < minSidePoints || contentBox.height() < minSidePoints {
|
||||
continue
|
||||
}
|
||||
|
||||
if nearlyEqualRect(contentBox, pageBox, 0.01) {
|
||||
continue
|
||||
}
|
||||
|
||||
plan[i-1] = pageCrop{crop: contentBox, apply: true}
|
||||
}
|
||||
|
||||
if err := copyFile(input, output); err != nil {
|
||||
return fmt.Errorf("nepodarilo sa pripravit vystupny subor: %w", err)
|
||||
}
|
||||
|
||||
for i, item := range plan {
|
||||
if !item.apply {
|
||||
continue
|
||||
}
|
||||
|
||||
boxDef := item.crop.boxDef()
|
||||
cropBox, err := model.ParseBox(boxDef, types.POINTS)
|
||||
if err != nil {
|
||||
return fmt.Errorf("strana %d: neplatny crop box %s: %w", i+1, boxDef, err)
|
||||
}
|
||||
|
||||
pageSel := []string{strconv.Itoa(i + 1)}
|
||||
if err := api.CropFile(output, output, pageSel, cropBox, nil); err != nil {
|
||||
return fmt.Errorf("strana %d: crop zlyhal: %w", i+1, err)
|
||||
}
|
||||
|
||||
pageBounds, err := api.PageBoundaries("media:"+boxDef, types.POINTS)
|
||||
if err != nil {
|
||||
return fmt.Errorf("strana %d: nepodarilo sa pripravit media box: %w", i+1, err)
|
||||
}
|
||||
if err := api.AddBoxesFile(output, output, pageSel, pageBounds, nil); err != nil {
|
||||
return fmt.Errorf("strana %d: nastavenie media box zlyhalo: %w", i+1, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func detectContentBox(p pdf.Page) (rect, bool) {
|
||||
var box rect
|
||||
found := false
|
||||
|
||||
add := func(r rect) {
|
||||
r = r.normalize()
|
||||
if !r.valid() {
|
||||
return
|
||||
}
|
||||
if !found {
|
||||
box = r
|
||||
found = true
|
||||
return
|
||||
}
|
||||
box = box.union(r)
|
||||
}
|
||||
|
||||
if tb, ok := detectTextBox(p); ok {
|
||||
add(tb)
|
||||
}
|
||||
|
||||
if gb, ok := detectGraphicsBox(p); ok {
|
||||
add(gb)
|
||||
}
|
||||
|
||||
return box, found
|
||||
}
|
||||
|
||||
type textState struct {
|
||||
Tc float64
|
||||
Tw float64
|
||||
Th float64
|
||||
Tl float64
|
||||
Tf pdf.Font
|
||||
Tfs float64
|
||||
Trise float64
|
||||
Tm matrix
|
||||
Tlm matrix
|
||||
CTM matrix
|
||||
}
|
||||
|
||||
func detectTextBox(p pdf.Page) (rect, bool) {
|
||||
var box rect
|
||||
found := false
|
||||
|
||||
add := func(r rect) {
|
||||
r = r.normalize()
|
||||
if !r.valid() {
|
||||
return
|
||||
}
|
||||
if !found {
|
||||
box = r
|
||||
found = true
|
||||
return
|
||||
}
|
||||
box = box.union(r)
|
||||
}
|
||||
|
||||
g := textState{
|
||||
Th: 1,
|
||||
CTM: identity,
|
||||
Tm: identity,
|
||||
Tlm: identity,
|
||||
}
|
||||
|
||||
var enc pdf.TextEncoding
|
||||
var gstack []textState
|
||||
|
||||
showText := func(raw string) {
|
||||
decoded := raw
|
||||
if enc != nil {
|
||||
decoded = enc.Decode(raw)
|
||||
}
|
||||
|
||||
rawBytes := []byte(raw)
|
||||
i := 0
|
||||
|
||||
for _, ch := range decoded {
|
||||
trm := matrix{{g.Tfs * g.Th, 0, 0}, {0, g.Tfs, 0}, {0, g.Trise, 1}}.mul(g.Tm).mul(g.CTM)
|
||||
|
||||
w0 := 500.0
|
||||
if i < len(rawBytes) && !g.Tf.V.IsNull() {
|
||||
w0 = g.Tf.Width(int(rawBytes[i]))
|
||||
}
|
||||
if i < len(rawBytes) {
|
||||
i++
|
||||
}
|
||||
|
||||
charWidth := math.Abs(w0 / 1000 * trm[0][0])
|
||||
h := math.Abs(g.Tfs)
|
||||
if h <= 0 {
|
||||
h = 8
|
||||
}
|
||||
|
||||
if !unicodeIsSpace(ch) {
|
||||
add(rect{
|
||||
llx: trm[2][0],
|
||||
lly: trm[2][1] - 0.30*h,
|
||||
urx: trm[2][0] + math.Max(charWidth, 0.2*h),
|
||||
ury: trm[2][1] + 0.90*h,
|
||||
})
|
||||
}
|
||||
|
||||
tx := w0/1000*g.Tfs + g.Tc
|
||||
if ch == ' ' {
|
||||
tx += g.Tw
|
||||
}
|
||||
tx *= g.Th
|
||||
|
||||
g.Tm = matrix{{1, 0, 0}, {0, 1, 0}, {tx, 0, 1}}.mul(g.Tm)
|
||||
}
|
||||
}
|
||||
|
||||
if !interpretPageContent(p, func(stk *pdf.Stack, op string) {
|
||||
args := popArgs(stk)
|
||||
|
||||
switch op {
|
||||
case "q":
|
||||
gstack = append(gstack, g)
|
||||
case "Q":
|
||||
if len(gstack) == 0 {
|
||||
return
|
||||
}
|
||||
g = gstack[len(gstack)-1]
|
||||
gstack = gstack[:len(gstack)-1]
|
||||
case "cm":
|
||||
m, ok := matrixFromArgs(args)
|
||||
if ok {
|
||||
g.CTM = m.mul(g.CTM)
|
||||
}
|
||||
case "BT":
|
||||
g.Tm = identity
|
||||
g.Tlm = identity
|
||||
case "T*":
|
||||
x := matrix{{1, 0, 0}, {0, 1, 0}, {0, -g.Tl, 1}}
|
||||
g.Tlm = x.mul(g.Tlm)
|
||||
g.Tm = g.Tlm
|
||||
case "Tc":
|
||||
if len(args) == 1 {
|
||||
g.Tc = args[0].Float64()
|
||||
}
|
||||
case "TD":
|
||||
if len(args) != 2 {
|
||||
return
|
||||
}
|
||||
g.Tl = -args[1].Float64()
|
||||
fallthrough
|
||||
case "Td":
|
||||
if len(args) != 2 {
|
||||
return
|
||||
}
|
||||
tx := args[0].Float64()
|
||||
ty := args[1].Float64()
|
||||
x := matrix{{1, 0, 0}, {0, 1, 0}, {tx, ty, 1}}
|
||||
g.Tlm = x.mul(g.Tlm)
|
||||
g.Tm = g.Tlm
|
||||
case "Tf":
|
||||
if len(args) != 2 {
|
||||
return
|
||||
}
|
||||
g.Tf = p.Font(args[0].Name())
|
||||
g.Tfs = args[1].Float64()
|
||||
enc = nil
|
||||
if !g.Tf.V.IsNull() {
|
||||
enc = g.Tf.Encoder()
|
||||
}
|
||||
case "\"":
|
||||
if len(args) != 3 {
|
||||
return
|
||||
}
|
||||
g.Tw = args[0].Float64()
|
||||
g.Tc = args[1].Float64()
|
||||
showText(args[2].RawString())
|
||||
case "'":
|
||||
if len(args) != 1 {
|
||||
return
|
||||
}
|
||||
x := matrix{{1, 0, 0}, {0, 1, 0}, {0, -g.Tl, 1}}
|
||||
g.Tlm = x.mul(g.Tlm)
|
||||
g.Tm = g.Tlm
|
||||
showText(args[0].RawString())
|
||||
case "Tj":
|
||||
if len(args) == 1 {
|
||||
showText(args[0].RawString())
|
||||
}
|
||||
case "TJ":
|
||||
if len(args) != 1 {
|
||||
return
|
||||
}
|
||||
v := args[0]
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
x := v.Index(i)
|
||||
if x.Kind() == pdf.String {
|
||||
showText(x.RawString())
|
||||
continue
|
||||
}
|
||||
tx := -x.Float64() / 1000 * g.Tfs * g.Th
|
||||
g.Tm = matrix{{1, 0, 0}, {0, 1, 0}, {tx, 0, 1}}.mul(g.Tm)
|
||||
}
|
||||
case "TL":
|
||||
if len(args) == 1 {
|
||||
g.Tl = args[0].Float64()
|
||||
}
|
||||
case "Tm":
|
||||
m, ok := matrixFromArgs(args)
|
||||
if ok {
|
||||
g.Tm = m
|
||||
g.Tlm = m
|
||||
}
|
||||
case "Ts":
|
||||
if len(args) == 1 {
|
||||
g.Trise = args[0].Float64()
|
||||
}
|
||||
case "Tw":
|
||||
if len(args) == 1 {
|
||||
g.Tw = args[0].Float64()
|
||||
}
|
||||
case "Tz":
|
||||
if len(args) == 1 {
|
||||
g.Th = args[0].Float64() / 100
|
||||
}
|
||||
}
|
||||
}) {
|
||||
return rect{}, false
|
||||
}
|
||||
|
||||
return box, found
|
||||
}
|
||||
|
||||
func detectGraphicsBox(p pdf.Page) (rect, bool) {
|
||||
var box rect
|
||||
found := false
|
||||
|
||||
add := func(r rect) {
|
||||
r = r.normalize()
|
||||
if !r.valid() {
|
||||
return
|
||||
}
|
||||
if !found {
|
||||
box = r
|
||||
found = true
|
||||
return
|
||||
}
|
||||
box = box.union(r)
|
||||
}
|
||||
|
||||
xObjects := p.Resources().Key("XObject")
|
||||
|
||||
ctm := identity
|
||||
var ctmStack []matrix
|
||||
|
||||
if !interpretPageContent(p, func(stk *pdf.Stack, op string) {
|
||||
args := popArgs(stk)
|
||||
|
||||
switch op {
|
||||
case "q":
|
||||
ctmStack = append(ctmStack, ctm)
|
||||
|
||||
case "Q":
|
||||
if len(ctmStack) == 0 {
|
||||
return
|
||||
}
|
||||
ctm = ctmStack[len(ctmStack)-1]
|
||||
ctmStack = ctmStack[:len(ctmStack)-1]
|
||||
|
||||
case "cm":
|
||||
m, ok := matrixFromArgs(args)
|
||||
if ok {
|
||||
ctm = m.mul(ctm)
|
||||
}
|
||||
|
||||
case "re":
|
||||
if len(args) != 4 {
|
||||
return
|
||||
}
|
||||
x := args[0].Float64()
|
||||
y := args[1].Float64()
|
||||
w := args[2].Float64()
|
||||
h := args[3].Float64()
|
||||
add(transformedRect(ctm, x, y, x+w, y+h))
|
||||
|
||||
case "Do":
|
||||
if len(args) != 1 {
|
||||
return
|
||||
}
|
||||
|
||||
name := args[0].Name()
|
||||
if name == "" || xObjects.IsNull() {
|
||||
return
|
||||
}
|
||||
|
||||
xobj := xObjects.Key(name)
|
||||
subtype := xobj.Key("Subtype").Name()
|
||||
if subtype == "Image" || subtype == "Form" {
|
||||
add(transformedRect(ctm, 0, 0, 1, 1))
|
||||
}
|
||||
}
|
||||
}) {
|
||||
return rect{}, false
|
||||
}
|
||||
|
||||
return box, found
|
||||
}
|
||||
|
||||
func interpretPageContent(p pdf.Page, fn func(stk *pdf.Stack, op string)) bool {
|
||||
contents := p.V.Key("Contents")
|
||||
if contents.IsNull() {
|
||||
return false
|
||||
}
|
||||
|
||||
ok := false
|
||||
run := func(stream pdf.Value) {
|
||||
if safeInterpretStream(stream, fn) {
|
||||
ok = true
|
||||
}
|
||||
}
|
||||
|
||||
switch contents.Kind() {
|
||||
case pdf.Stream:
|
||||
run(contents)
|
||||
case pdf.Array:
|
||||
for i := 0; i < contents.Len(); i++ {
|
||||
stream := contents.Index(i)
|
||||
if stream.IsNull() || stream.Kind() != pdf.Stream {
|
||||
continue
|
||||
}
|
||||
run(stream)
|
||||
}
|
||||
}
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
func safeInterpretStream(stream pdf.Value, fn func(stk *pdf.Stack, op string)) (ok bool) {
|
||||
if stream.IsNull() || stream.Kind() != pdf.Stream {
|
||||
return false
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if recover() != nil {
|
||||
ok = false
|
||||
}
|
||||
}()
|
||||
|
||||
pdf.Interpret(stream, fn)
|
||||
return true
|
||||
}
|
||||
|
||||
func unicodeIsSpace(r rune) bool {
|
||||
if r == ' ' || r == '\t' || r == '\n' || r == '\r' {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func popArgs(stk *pdf.Stack) []pdf.Value {
|
||||
n := stk.Len()
|
||||
if n == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
args := make([]pdf.Value, n)
|
||||
for i := n - 1; i >= 0; i-- {
|
||||
args[i] = stk.Pop()
|
||||
}
|
||||
|
||||
return args
|
||||
}
|
||||
|
||||
func matrixFromArgs(args []pdf.Value) (matrix, bool) {
|
||||
if len(args) != 6 {
|
||||
return matrix{}, false
|
||||
}
|
||||
|
||||
var m matrix
|
||||
for i := 0; i < 6; i++ {
|
||||
m[i/2][i%2] = args[i].Float64()
|
||||
}
|
||||
m[2][2] = 1
|
||||
|
||||
return m, true
|
||||
}
|
||||
|
||||
func transformedRect(m matrix, x0, y0, x1, y1 float64) rect {
|
||||
p1x, p1y := transformPoint(m, x0, y0)
|
||||
p2x, p2y := transformPoint(m, x1, y0)
|
||||
p3x, p3y := transformPoint(m, x0, y1)
|
||||
p4x, p4y := transformPoint(m, x1, y1)
|
||||
|
||||
minX := math.Min(math.Min(p1x, p2x), math.Min(p3x, p4x))
|
||||
minY := math.Min(math.Min(p1y, p2y), math.Min(p3y, p4y))
|
||||
maxX := math.Max(math.Max(p1x, p2x), math.Max(p3x, p4x))
|
||||
maxY := math.Max(math.Max(p1y, p2y), math.Max(p3y, p4y))
|
||||
|
||||
return rect{llx: minX, lly: minY, urx: maxX, ury: maxY}
|
||||
}
|
||||
|
||||
func transformPoint(m matrix, x, y float64) (float64, float64) {
|
||||
px := x*m[0][0] + y*m[1][0] + m[2][0]
|
||||
py := x*m[0][1] + y*m[1][1] + m[2][1]
|
||||
return px, py
|
||||
}
|
||||
|
||||
func effectivePageBox(page pdf.Value) (rect, error) {
|
||||
if r, ok := inheritedRect(page, "CropBox"); ok {
|
||||
return r, nil
|
||||
}
|
||||
if r, ok := inheritedRect(page, "MediaBox"); ok {
|
||||
return r, nil
|
||||
}
|
||||
return rect{}, errors.New("chybajuci MediaBox/CropBox")
|
||||
}
|
||||
|
||||
func inheritedRect(v pdf.Value, key string) (rect, bool) {
|
||||
for cur := v; !cur.IsNull(); cur = cur.Key("Parent") {
|
||||
candidate := cur.Key(key)
|
||||
r, err := rectFromArray(candidate)
|
||||
if err == nil {
|
||||
return r, true
|
||||
}
|
||||
}
|
||||
return rect{}, false
|
||||
}
|
||||
|
||||
func rectFromArray(v pdf.Value) (rect, error) {
|
||||
if v.IsNull() || v.Len() != 4 {
|
||||
return rect{}, errors.New("neplatny rectangle")
|
||||
}
|
||||
|
||||
r := rect{
|
||||
llx: v.Index(0).Float64(),
|
||||
lly: v.Index(1).Float64(),
|
||||
urx: v.Index(2).Float64(),
|
||||
ury: v.Index(3).Float64(),
|
||||
}.normalize()
|
||||
|
||||
if !r.valid() {
|
||||
return rect{}, errors.New("neplatny rectangle")
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func copyFile(src, dst string) error {
|
||||
in, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer in.Close()
|
||||
|
||||
out, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = out.ReadFrom(in)
|
||||
if closeErr := out.Close(); closeErr != nil && err == nil {
|
||||
err = closeErr
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
_ = os.Remove(dst)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func trimmedPath(input string) string {
|
||||
ext := filepath.Ext(input)
|
||||
base := strings.TrimSuffix(input, ext)
|
||||
return base + "-trimmed.pdf"
|
||||
}
|
||||
|
||||
func nearlyEqualRect(a, b rect, eps float64) bool {
|
||||
return math.Abs(a.llx-b.llx) <= eps &&
|
||||
math.Abs(a.lly-b.lly) <= eps &&
|
||||
math.Abs(a.urx-b.urx) <= eps &&
|
||||
math.Abs(a.ury-b.ury) <= eps
|
||||
}
|
||||
|
||||
func (m matrix) mul(other matrix) matrix {
|
||||
var out matrix
|
||||
for i := 0; i < 3; i++ {
|
||||
for j := 0; j < 3; j++ {
|
||||
for k := 0; k < 3; k++ {
|
||||
out[i][j] += m[i][k] * other[k][j]
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (r rect) normalize() rect {
|
||||
if r.llx > r.urx {
|
||||
r.llx, r.urx = r.urx, r.llx
|
||||
}
|
||||
if r.lly > r.ury {
|
||||
r.lly, r.ury = r.ury, r.lly
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (r rect) valid() bool {
|
||||
return !(math.IsNaN(r.llx) || math.IsNaN(r.lly) || math.IsNaN(r.urx) || math.IsNaN(r.ury) ||
|
||||
math.IsInf(r.llx, 0) || math.IsInf(r.lly, 0) || math.IsInf(r.urx, 0) || math.IsInf(r.ury, 0))
|
||||
}
|
||||
|
||||
func (r rect) width() float64 {
|
||||
return r.urx - r.llx
|
||||
}
|
||||
|
||||
func (r rect) height() float64 {
|
||||
return r.ury - r.lly
|
||||
}
|
||||
|
||||
func (r rect) union(other rect) rect {
|
||||
return rect{
|
||||
llx: math.Min(r.llx, other.llx),
|
||||
lly: math.Min(r.lly, other.lly),
|
||||
urx: math.Max(r.urx, other.urx),
|
||||
ury: math.Max(r.ury, other.ury),
|
||||
}
|
||||
}
|
||||
|
||||
func (r rect) expand(padding float64) rect {
|
||||
return rect{
|
||||
llx: r.llx - padding,
|
||||
lly: r.lly - padding,
|
||||
urx: r.urx + padding,
|
||||
ury: r.ury + padding,
|
||||
}
|
||||
}
|
||||
|
||||
func (r rect) clamp(limit rect) rect {
|
||||
return rect{
|
||||
llx: clamp(r.llx, limit.llx, limit.urx),
|
||||
lly: clamp(r.lly, limit.lly, limit.ury),
|
||||
urx: clamp(r.urx, limit.llx, limit.urx),
|
||||
ury: clamp(r.ury, limit.lly, limit.ury),
|
||||
}
|
||||
}
|
||||
|
||||
func (r rect) boxDef() string {
|
||||
return fmt.Sprintf("[%.4f %.4f %.4f %.4f]", r.llx, r.lly, r.urx, r.ury)
|
||||
}
|
||||
|
||||
func clamp(v, minV, maxV float64) float64 {
|
||||
if v < minV {
|
||||
return minV
|
||||
}
|
||||
if v > maxV {
|
||||
return maxV
|
||||
}
|
||||
return v
|
||||
}
|
||||
Reference in New Issue
Block a user