mirror of
https://github.com/unidoc/unipdf.git
synced 2025-04-26 13:48:55 +08:00
213 lines
4.8 KiB
Go
213 lines
4.8 KiB
Go
/*
|
|
* This file is subject to the terms and conditions defined in
|
|
* file 'LICENSE.md', which is part of this source code package.
|
|
*/
|
|
|
|
package optimize_test
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"testing"
|
|
|
|
"github.com/unidoc/unipdf/v3/core"
|
|
"github.com/unidoc/unipdf/v3/model/optimize"
|
|
)
|
|
|
|
// parseIndirectObjects parses a sequence of indirect/stream objects sequentially from a `rawpdf` text.
|
|
func parseIndirectObjects(rawpdf string) ([]core.PdfObject, error) {
|
|
p := core.NewParserFromString(rawpdf)
|
|
var indirects []core.PdfObject
|
|
for {
|
|
obj, err := p.ParseIndirectObject()
|
|
if err != nil {
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
indirects = append(indirects, obj)
|
|
}
|
|
|
|
return indirects, nil
|
|
}
|
|
|
|
// debugObjects prints objects in a readable fashion, convenient when debugging.
|
|
func debugObjects(objects []core.PdfObject) string {
|
|
var buf bytes.Buffer
|
|
|
|
for _, obj := range objects {
|
|
switch t := obj.(type) {
|
|
case *core.PdfIndirectObject:
|
|
buf.WriteString(fmt.Sprintf("%d 0 obj\n", t.ObjectNumber))
|
|
buf.WriteString(fmt.Sprintf(" %s\n", t.PdfObject.String()))
|
|
}
|
|
}
|
|
return buf.String()
|
|
}
|
|
|
|
func TestOptimizeIdenticalIndirects1(t *testing.T) {
|
|
rawpdf := `
|
|
1 0 obj
|
|
<<
|
|
/Name (1234)
|
|
>>
|
|
endobj
|
|
2 0 obj
|
|
<< /Name (1234) >>
|
|
endobj
|
|
`
|
|
objects, err := parseIndirectObjects(rawpdf)
|
|
if err != nil {
|
|
t.Fatalf("Error: %v", err)
|
|
}
|
|
|
|
if len(objects) != 2 {
|
|
t.Fatalf("len(objects) != 2 (%d)", len(objects))
|
|
}
|
|
|
|
// Combine duplicate direct objects - Expect unchanged results.
|
|
{
|
|
opt := optimize.CombineDuplicateDirectObjects{}
|
|
optObjects, err := opt.Optimize(objects)
|
|
if err != nil {
|
|
t.Fatalf("Error: %v", err)
|
|
}
|
|
if len(optObjects) != 2 {
|
|
t.Fatalf("len(optObjects1) != 2 (%d)", len(optObjects))
|
|
}
|
|
}
|
|
|
|
// Combine indirect objects should go from 2 to 1.
|
|
{
|
|
opt := optimize.CombineIdenticalIndirectObjects{}
|
|
optObjects, err := opt.Optimize(objects)
|
|
if err != nil {
|
|
t.Fatalf("Error: %v", err)
|
|
}
|
|
if len(optObjects) != 1 {
|
|
t.Fatalf("len(optObjects1) != 1 (%d)", len(optObjects))
|
|
}
|
|
}
|
|
}
|
|
|
|
// More complex case, where has a reference, where as the other does not.
|
|
// Expecting this NOT to work as we don't currently support this case.
|
|
// TODO: Add support for this.
|
|
func TestOptimizeIdenticalIndirectsUnsupported1(t *testing.T) {
|
|
rawpdf := `
|
|
1 0 obj
|
|
(1234)
|
|
endobj
|
|
2 0 obj
|
|
<<
|
|
/Name (1234)
|
|
>>
|
|
endobj
|
|
3 0 obj
|
|
<< /Name 1 0 R >>
|
|
endobj
|
|
`
|
|
objects, err := parseIndirectObjects(rawpdf)
|
|
if err != nil {
|
|
t.Fatalf("Error: %v", err)
|
|
}
|
|
|
|
if len(objects) != 3 {
|
|
t.Fatalf("len(objects) != 2 (%d)", len(objects))
|
|
}
|
|
|
|
// Combine duplicate direct objects - Expect unchanged results.
|
|
{
|
|
opt := optimize.CombineDuplicateDirectObjects{}
|
|
optObjects, err := opt.Optimize(objects)
|
|
if err != nil {
|
|
t.Fatalf("Error: %v", err)
|
|
}
|
|
if len(optObjects) != 3 {
|
|
t.Fatalf("len(optObjects1) != 2 (%d)", len(optObjects))
|
|
}
|
|
}
|
|
|
|
// Combine indirect objects should go from 3 to 2.
|
|
{
|
|
opt := optimize.CombineIdenticalIndirectObjects{}
|
|
optObjects, err := opt.Optimize(objects)
|
|
if err != nil {
|
|
t.Fatalf("Error: %v", err)
|
|
}
|
|
if len(optObjects) != 3 { // TODO: Add support. IF IDEAL: would be 2.
|
|
t.Fatalf("len(optObjects1) != 2 (%d)", len(optObjects))
|
|
}
|
|
}
|
|
}
|
|
|
|
// Showcases problem with sequence of CombineDuplicateDirectObjects followed by CombineIdenticalIndirectObjects
|
|
// if object numbers are not updated between steps (due to non-unique object numbering and reference strings).
|
|
func TestOptimizationSequence1(t *testing.T) {
|
|
rawpdf := `
|
|
1 0 obj
|
|
<<
|
|
/Inner << /Color (red) >>
|
|
>>
|
|
endobj
|
|
2 0 obj
|
|
<<
|
|
/Inner << /Color (red) >>
|
|
/Other (abc)
|
|
>>
|
|
endobj
|
|
3 0 obj
|
|
<<
|
|
/Inner << /Color (blue) >>
|
|
/Other (abc)
|
|
>>
|
|
endobj
|
|
4 0 obj
|
|
<<
|
|
/Inner << /Color (blue) >>
|
|
>>
|
|
endobj
|
|
`
|
|
objects, err := parseIndirectObjects(rawpdf)
|
|
if err != nil {
|
|
t.Fatalf("Error: %v", err)
|
|
}
|
|
if len(objects) != 4 {
|
|
t.Fatalf("len(objects) != 4 (%d)", len(objects))
|
|
}
|
|
debugstr1 := debugObjects(objects)
|
|
|
|
// 1. Combine duplicate direct objects.
|
|
// Expect that 2 new indirect objects will be added, as two of the inner dictionaries are identical.
|
|
opt := optimize.CombineDuplicateDirectObjects{}
|
|
optObjects, err := opt.Optimize(objects)
|
|
if err != nil {
|
|
t.Fatalf("Error: %v", err)
|
|
}
|
|
if len(optObjects) != 6 {
|
|
t.Fatalf("len(optObjects) != 6 (%d)", len(optObjects))
|
|
}
|
|
debugstr2 := debugObjects(optObjects)
|
|
|
|
// 2. Combine indirect objects.
|
|
// Should not make any difference here unless there was a problem.
|
|
opt2 := optimize.CombineIdenticalIndirectObjects{}
|
|
optObjects, err = opt2.Optimize(optObjects)
|
|
if err != nil {
|
|
t.Fatalf("Error: %v", err)
|
|
}
|
|
debugstr3 := debugObjects(optObjects)
|
|
fmt.Println("==Original")
|
|
fmt.Println(debugstr1)
|
|
fmt.Println("==After CombineDuplicateDirectObjects")
|
|
fmt.Println(debugstr2)
|
|
fmt.Println("==After CombineIdenticalIndirectObjects")
|
|
fmt.Println(debugstr3)
|
|
if len(optObjects) != 6 {
|
|
t.Fatalf("len(optObjects) != 6 (%d)", len(optObjects))
|
|
}
|
|
}
|