2016-07-19 23:16:25 +00:00
|
|
|
/*
|
|
|
|
* This file is subject to the terms and conditions defined in
|
2016-07-29 17:23:39 +00:00
|
|
|
* file 'LICENSE.md', which is part of this source code package.
|
2016-07-19 23:16:25 +00:00
|
|
|
*/
|
|
|
|
|
2016-09-08 17:53:45 +00:00
|
|
|
package model
|
2016-07-19 23:16:25 +00:00
|
|
|
|
|
|
|
import (
|
2018-06-18 15:26:29 +00:00
|
|
|
"io"
|
2016-07-19 23:16:25 +00:00
|
|
|
"testing"
|
2016-09-09 15:02:52 +00:00
|
|
|
|
2019-05-16 23:08:40 +03:00
|
|
|
"github.com/unidoc/unipdf/v3/common"
|
2019-05-16 23:44:51 +03:00
|
|
|
"github.com/unidoc/unipdf/v3/core"
|
2016-07-19 23:16:25 +00:00
|
|
|
)
|
|
|
|
|
2018-07-17 23:53:28 +00:00
|
|
|
// ParseIndObjSeries loads a series of indirect objects until it runs into an error or EOF.
|
2018-06-18 15:26:29 +00:00
|
|
|
// Fully loads the objects and traverses resolving references to *PdfIndirectObjects.
|
2018-07-17 23:53:28 +00:00
|
|
|
// For use in testing where a series of indirect objects can be defined sequentially.
|
2018-06-18 15:26:29 +00:00
|
|
|
func (r *PdfReader) ParseIndObjSeries() error {
|
|
|
|
for {
|
|
|
|
obj, err := r.parser.ParseIndirectObject()
|
|
|
|
if err != nil {
|
|
|
|
if err != io.EOF {
|
|
|
|
common.Log.Debug("Error parsing indirect object: %v", err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
switch t := obj.(type) {
|
2019-02-09 11:59:12 +02:00
|
|
|
case *core.PdfObjectStream:
|
2018-06-18 15:26:29 +00:00
|
|
|
r.parser.ObjCache[int(t.ObjectNumber)] = t
|
2019-02-09 11:59:12 +02:00
|
|
|
case *core.PdfIndirectObject:
|
2018-06-18 15:26:29 +00:00
|
|
|
r.parser.ObjCache[int(t.ObjectNumber)] = t
|
|
|
|
default:
|
|
|
|
common.Log.Debug("Incorrect type for ind obj: %T", obj)
|
|
|
|
return ErrTypeCheck
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Traverse the objects, resolving references to instances to PdfIndirectObject pointers.
|
|
|
|
for _, obj := range r.parser.ObjCache {
|
|
|
|
err := r.traverseObjectData(obj)
|
|
|
|
if err != nil {
|
|
|
|
common.Log.Debug("ERROR: Unable to traverse(%s)", err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
2016-09-09 15:02:52 +00:00
|
|
|
}
|
|
|
|
|
2016-07-19 23:16:25 +00:00
|
|
|
// Test PDF date parsing from string.
|
|
|
|
func TestDateParse(t *testing.T) {
|
|
|
|
// Case 1. Test everything.
|
|
|
|
str := "D:20080313232937+01'00'"
|
|
|
|
date, err := NewPdfDate(str)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("Fail: %s", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if date.year != 2008 {
|
|
|
|
t.Errorf("Year != 2008")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if date.month != 3 {
|
|
|
|
t.Errorf("month != 3")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if date.day != 13 {
|
|
|
|
t.Errorf("Day != 13")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if date.hour != 23 {
|
|
|
|
t.Errorf("Hour != 23 (%d)", date.hour)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if date.minute != 29 {
|
|
|
|
t.Errorf("Minute != 29 (%d)", date.minute)
|
|
|
|
}
|
|
|
|
if date.second != 37 {
|
|
|
|
t.Errorf("Second != 37 (%d)", date.second)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if date.utOffsetSign != '+' {
|
|
|
|
t.Errorf("Invalid offset sign")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if date.utOffsetHours != 1 {
|
|
|
|
t.Errorf("Invalid offset hours")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if date.utOffsetMins != 0 {
|
|
|
|
t.Errorf("Invalid offset minutes")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-04-14 20:34:55 +02:00
|
|
|
dateFromTime, err := NewPdfDateFromTime(date.ToGoTime())
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("Fail: %s", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if dateFromTime.ToPdfObject().String() != date.ToPdfObject().String() {
|
|
|
|
t.Errorf("Convert to and from time failed")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-07-19 23:16:25 +00:00
|
|
|
// Case 2: Negative sign.
|
|
|
|
str = "D:20150811050933-07'00'"
|
|
|
|
date, err = NewPdfDate(str)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("Fail: %s", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if date.utOffsetSign != '-' {
|
|
|
|
t.Errorf("Invalid offset sign")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if date.utOffsetHours != 7 {
|
|
|
|
t.Errorf("Invalid offset hours")
|
|
|
|
return
|
|
|
|
}
|
2019-04-14 20:34:55 +02:00
|
|
|
dateFromTime, err = NewPdfDateFromTime(date.ToGoTime())
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("Fail: %s", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if dateFromTime.ToPdfObject().String() != date.ToPdfObject().String() {
|
|
|
|
t.Errorf("Convert to and from time failed")
|
|
|
|
return
|
|
|
|
}
|
2016-07-19 23:16:25 +00:00
|
|
|
|
|
|
|
// Case 3. Offset minutes.
|
|
|
|
str = "D:20110807220047+09'30'"
|
|
|
|
date, err = NewPdfDate(str)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("Fail: %s", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if date.utOffsetMins != 30 {
|
|
|
|
t.Errorf("Offset mins != 30")
|
|
|
|
return
|
|
|
|
}
|
2019-04-14 20:34:55 +02:00
|
|
|
dateFromTime, err = NewPdfDateFromTime(date.ToGoTime())
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("Fail: %s", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if dateFromTime.ToPdfObject().String() != date.ToPdfObject().String() {
|
|
|
|
t.Errorf("Convert to and from time failed")
|
|
|
|
return
|
|
|
|
}
|
2016-08-16 17:57:23 +00:00
|
|
|
|
|
|
|
// Case 4. Another test from failed file.
|
|
|
|
// Minutes not specified at end (assume is 0).
|
|
|
|
str = "D:20061023115457-04'"
|
|
|
|
date, err = NewPdfDate(str)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("Fail: %s", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if date.year != 2006 {
|
|
|
|
t.Errorf("Year != 2006")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if date.month != 10 {
|
|
|
|
t.Errorf("month != 10")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if date.day != 23 {
|
|
|
|
t.Errorf("Day != 23")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if date.hour != 11 {
|
|
|
|
t.Errorf("Hour != 11 (%d)", date.hour)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if date.minute != 54 {
|
|
|
|
t.Errorf("Minute != 29 (%d)", date.minute)
|
|
|
|
}
|
|
|
|
if date.second != 57 {
|
|
|
|
t.Errorf("Second != 37 (%d)", date.second)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if date.utOffsetSign != '-' {
|
|
|
|
t.Errorf("Invalid offset sign")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if date.utOffsetHours != 4 {
|
|
|
|
t.Errorf("Invalid offset hours")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if date.utOffsetMins != 0 {
|
|
|
|
t.Errorf("Invalid offset minutes")
|
|
|
|
return
|
|
|
|
}
|
2019-04-14 20:34:55 +02:00
|
|
|
dateFromTime, err = NewPdfDateFromTime(date.ToGoTime())
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("Fail: %s", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if dateFromTime.ToPdfObject().String() != date.ToPdfObject().String() {
|
|
|
|
t.Errorf("Convert to and from time failed")
|
|
|
|
return
|
|
|
|
}
|
2016-08-19 11:34:55 +00:00
|
|
|
|
|
|
|
// Case 5: Missing some more parameters.
|
|
|
|
// Seems that many implementations consider some stuff optional...
|
|
|
|
// Not following the standard, but we need to handle it.
|
|
|
|
// D:20050823042205
|
|
|
|
str = "D:20050823042205"
|
|
|
|
date, err = NewPdfDate(str)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("Fail: %s", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if date.year != 2005 {
|
|
|
|
t.Errorf("Year != 2005")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if date.month != 8 {
|
|
|
|
t.Errorf("month != 8")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if date.day != 23 {
|
|
|
|
t.Errorf("Day != 23")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if date.hour != 04 {
|
|
|
|
t.Errorf("Hour != 11 (%d)", date.hour)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if date.minute != 22 {
|
|
|
|
t.Errorf("Minute != 29 (%d)", date.minute)
|
|
|
|
}
|
|
|
|
if date.second != 05 {
|
|
|
|
t.Errorf("Second != 37 (%d)", date.second)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if date.utOffsetHours != 0 {
|
|
|
|
t.Errorf("Invalid offset hours")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if date.utOffsetMins != 0 {
|
|
|
|
t.Errorf("Invalid offset minutes")
|
|
|
|
return
|
|
|
|
}
|
2019-04-14 20:34:55 +02:00
|
|
|
dateFromTime, err = NewPdfDateFromTime(date.ToGoTime())
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("Fail: %s", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if dateFromTime.ToPdfObject().String() != date.ToPdfObject().String() {
|
|
|
|
t.Errorf("Convert to and from time failed")
|
|
|
|
return
|
|
|
|
}
|
2016-07-19 23:16:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Test parsing and building the date.
|
|
|
|
func TestPdfDateBuild(t *testing.T) {
|
|
|
|
// Case 1. Test everything.
|
|
|
|
dateStr1 := "D:20080313232937+01'00'"
|
|
|
|
date, err := NewPdfDate(dateStr1)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("Fail: %s", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
obj := date.ToPdfObject()
|
2019-02-09 11:59:12 +02:00
|
|
|
strObj, ok := obj.(*core.PdfObjectString)
|
2016-07-19 23:16:25 +00:00
|
|
|
if !ok {
|
|
|
|
t.Errorf("Date PDF object should be a string")
|
|
|
|
return
|
|
|
|
}
|
2018-07-14 02:25:29 +00:00
|
|
|
if strObj.Str() != dateStr1 {
|
2016-07-19 23:16:25 +00:00
|
|
|
t.Errorf("Built date string does not match original (%s)", strObj)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test page loading.
|
|
|
|
func TestPdfPage1(t *testing.T) {
|
|
|
|
rawText := `
|
|
|
|
9 0 obj
|
|
|
|
<<
|
|
|
|
/Type /Page
|
|
|
|
/Parent 3 0 R
|
|
|
|
/MediaBox [0 0 612 459]
|
|
|
|
/Contents 13 0 R
|
|
|
|
/Resources <<
|
|
|
|
/ProcSet 11 0 R
|
|
|
|
/ExtGState <<
|
|
|
|
/GS0 << /BM /Normal >>
|
|
|
|
>>
|
|
|
|
/XObject <</Im0 12 0 R>>
|
|
|
|
>>
|
|
|
|
>>
|
|
|
|
endobj
|
|
|
|
`
|
2019-02-09 11:59:12 +02:00
|
|
|
parser := core.NewParserFromString(rawText)
|
2016-07-19 23:16:25 +00:00
|
|
|
|
2016-09-09 15:02:52 +00:00
|
|
|
obj, err := parser.ParseIndirectObject()
|
2016-07-19 23:16:25 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Errorf("Failed to parse indirect obj (%s)", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-02-09 11:59:12 +02:00
|
|
|
pageObj, ok := obj.(*core.PdfIndirectObject)
|
2016-07-19 23:16:25 +00:00
|
|
|
if !ok {
|
|
|
|
t.Errorf("Invalid page object type != dictionary (%q)", obj)
|
|
|
|
return
|
|
|
|
}
|
2019-02-09 11:59:12 +02:00
|
|
|
pageDict, ok := pageObj.PdfObject.(*core.PdfObjectDictionary)
|
2016-07-19 23:16:25 +00:00
|
|
|
if !ok {
|
|
|
|
t.Errorf("Page object != dictionary")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-08-16 16:37:46 +00:00
|
|
|
// Bit of a hacky way to do this. PDF reader is needed if need to resolve external references,
|
|
|
|
// but none in this case, so can just use a dummy instance.
|
|
|
|
dummyPdfReader := PdfReader{}
|
|
|
|
page, err := dummyPdfReader.newPdfPageFromDict(pageDict)
|
2016-07-19 23:16:25 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Errorf("Unable to load page (%s)", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if page.MediaBox.Llx != 0 || page.MediaBox.Lly != 0 {
|
|
|
|
t.Errorf("llx, lly != 0,0")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if page.MediaBox.Urx != 612 || page.MediaBox.Ury != 459 {
|
|
|
|
t.Errorf("urx, ury!= 612 (%f), 459 (%f)", page.MediaBox.Urx, page.MediaBox.Ury)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test rectangle parsing and loading.
|
|
|
|
func TestRect(t *testing.T) {
|
|
|
|
rawText := `<< /MediaBox [0 0 613.644043 802.772034] >>`
|
|
|
|
|
2016-09-09 15:02:52 +00:00
|
|
|
//parser := PdfParser{}
|
|
|
|
//parser.reader = makeReaderForText(rawText)
|
2019-02-09 11:59:12 +02:00
|
|
|
parser := core.NewParserFromString(rawText)
|
2016-07-19 23:16:25 +00:00
|
|
|
|
2016-09-09 15:02:52 +00:00
|
|
|
dict, err := parser.ParseDict()
|
2016-07-19 23:16:25 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Errorf("Failed to parse dict obj (%s)", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-07-08 21:04:13 +00:00
|
|
|
obj := dict.Get("MediaBox")
|
2019-02-09 11:59:12 +02:00
|
|
|
arr, ok := obj.(*core.PdfObjectArray)
|
2016-07-19 23:16:25 +00:00
|
|
|
if !ok {
|
|
|
|
t.Errorf("Type != Array")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
rect, err := NewPdfRectangle(*arr)
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("Failed to create rectangle (%s)", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if rect.Llx != 0 {
|
|
|
|
t.Errorf("rect.llx != 0 (%f)", rect.Llx)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if rect.Lly != 0 {
|
|
|
|
t.Errorf("rect.lly != 0 (%f)", rect.Lly)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if rect.Urx != 613.644043 {
|
|
|
|
t.Errorf("rect.urx != 613.644043 (%f)", rect.Urx)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if rect.Ury != 802.772034 {
|
|
|
|
t.Errorf("rect.urx != 802.772034 (%f)", rect.Ury)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|