Becoded action support (#161)

* initial commit to add action support
* Add support for all actions as defined in the specs
* Implement filespec for actions
* Add url filespec test
* update file test
* Implement remarks + add tests
This commit is contained in:
Gunnsteinn Hall 2019-08-30 08:50:30 +00:00 committed by GitHub
parent fbed2a74d9
commit b1c851b7b0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 1921 additions and 6 deletions

View File

@ -291,10 +291,11 @@ func (c *Creator) initContext() {
}
// NewPage adds a new Page to the Creator and sets as the active Page.
func (c *Creator) NewPage() {
func (c *Creator) NewPage() *model.PdfPage {
page := c.newPage()
c.pages = append(c.pages, page)
c.context.Page++
return page
}
// AddPage adds the specified page to the creator.

View File

@ -44,10 +44,9 @@ func newExternalLinkAnnotation(url string) *model.PdfAnnotation {
annotation.BS = bs.ToPdfObject()
// Set link destination.
action := core.MakeDict()
action.Set(core.PdfObjectName("S"), core.MakeName("URI"))
action.Set(core.PdfObjectName("URI"), core.MakeString(url))
annotation.A = action
action := model.NewPdfActionURI()
action.URI = core.MakeString(url)
annotation.SetAction(action.PdfAction)
return annotation.PdfAnnotation
}

1019
model/action.go Normal file

File diff suppressed because it is too large Load Diff

569
model/action_test.go Normal file
View File

@ -0,0 +1,569 @@
/*
* This file is subject to the terms and conditions defined in
* file 'LICENSE.md', which is part of this source code package.
*/
package model
import (
"strings"
"testing"
"github.com/stretchr/testify/require"
"github.com/unidoc/unipdf/v3/core"
)
// testAction loads an action object from object number 1 loaded from `rawText` PDF content and checks that
// it matches the `actionType`. Then it applies testFunc() on the action which does action-specific checks.
// Lastly the serialized output is checked against the input PDF object.
func testAction(t *testing.T, rawText string, actionType PdfActionType, testFunc func(t *testing.T, action *PdfAction)) {
// Read raw text
r := NewReaderForText(rawText)
err := r.ParseIndObjSeries()
require.NoError(t, err)
// Load the field from object number 1 as all actions in these tests are defined in object 1
obj, err := r.parser.LookupByNumber(1)
require.NoError(t, err)
ind, ok := obj.(*core.PdfIndirectObject)
require.True(t, ok)
// Parse action
action, err := r.newPdfActionFromIndirectObject(ind)
require.NoError(t, err)
// Check if raw text can be parsed to the expected action objects
// The object should be of type action + the actionType should match the expected action
require.Equal(t, "Action", action.Type.String())
require.Equal(t, string(actionType), action.S.String())
// Verify some action specific fields
testFunc(t, action)
// Check if object can be serialized to the expected text
outDict, ok := core.GetDict(action.context.ToPdfObject())
if !ok {
t.Fatalf("error")
}
require.Containsf(
t,
strings.Replace(rawText, "\n", "", -1),
outDict.WriteString(),
"generated output doesn't match the expected output - %s",
outDict.WriteString())
}
func TestPdfActionGoTo(t *testing.T) {
rawText := `
1 0 obj
<</Type /Action
/S /GoTo
/D (name)
>>
endobj
`
testAction(t, rawText, ActionTypeGoTo, func(t *testing.T, action *PdfAction) {
contextAction, ok := action.context.(*PdfActionGoTo)
require.True(t, ok)
require.Equal(t, "name", contextAction.D.String())
})
}
func TestPdfActionGoToR(t *testing.T) {
rawText := `
1 0 obj
<</Type /Action
/S /GoToR
/F <</Type /Filespec
/F (someFile.pdf)
>>
/D (name)
/NewWindow true
>>
endobj
`
testAction(t, rawText, ActionTypeGoToR, func(t *testing.T, action *PdfAction) {
contextAction, ok := action.context.(*PdfActionGoToR)
require.True(t, ok)
require.Equal(t, "name", contextAction.D.String())
require.Equal(t, "true", contextAction.NewWindow.String())
require.IsType(t, &PdfFilespec{}, contextAction.F)
})
}
func TestPdfActionGoToE(t *testing.T) {
rawText := `
1 0 obj
<</Type /Action
/S /GoToE
/F <</Type /Filespec
/F (someFile.pdf)
>>
/D (name)
/NewWindow true
/T <</R /C
/N (Embedded document)>>
>>
endobj
`
testAction(t, rawText, ActionTypeGoToE, func(t *testing.T, action *PdfAction) {
contextAction, ok := action.context.(*PdfActionGoToE)
require.True(t, ok)
require.Equal(t, "name", contextAction.D.String())
require.Equal(t, "true", contextAction.NewWindow.String())
require.IsType(t, &PdfFilespec{}, contextAction.F)
})
}
func TestPdfActionLaunch(t *testing.T) {
rawText := `
1 0 obj
<</Type /Action
/S /Launch
/F <</Type /Filespec
/F (someFile.pdf)
>>
/NewWindow true
>>
endobj
`
testAction(t, rawText, ActionTypeLaunch, func(t *testing.T, action *PdfAction) {
contextAction, ok := action.context.(*PdfActionLaunch)
require.True(t, ok)
require.Equal(t, "true", contextAction.NewWindow.String())
require.IsType(t, &PdfFilespec{}, contextAction.F)
})
}
func TestPdfActionThread(t *testing.T) {
rawText := `
1 0 obj
<</Type /Action
/S /Thread
/D 4
/B 5
>>
endobj
`
testAction(t, rawText, ActionTypeThread, func(t *testing.T, action *PdfAction) {
contextAction, ok := action.context.(*PdfActionThread)
require.True(t, ok)
require.Equal(t, "4", contextAction.D.String())
require.Equal(t, "5", contextAction.B.String())
})
}
func TestPdfActionURI(t *testing.T) {
rawText := `
1 0 obj
<</Type /Action
/S /URI
/URI (https://unidoc.io/)
/IsMap true
>>
endobj
`
testAction(t, rawText, ActionTypeURI, func(t *testing.T, action *PdfAction) {
contextAction, ok := action.context.(*PdfActionURI)
require.True(t, ok)
require.Equal(t, "https://unidoc.io/", contextAction.URI.String())
require.Equal(t, "true", contextAction.IsMap.String())
})
}
func TestPdfActionSound(t *testing.T) {
rawText := `
1 0 obj
<</Type /Action
/S /Sound
/Sound 2 0 R
/Volume 0.5
/Synchronous true
/Repeat true
/Mix true
>>
endobj
2 0 obj
<<
/B 16
/C 2
/E /Signed
/Filter /FlateDecode
/Length 12
/R 44100
/Type /Sound
>>
stream
abcdefghijkl
endstream
endobj
`
testAction(t, rawText, ActionTypeSound, func(t *testing.T, action *PdfAction) {
contextAction, ok := action.context.(*PdfActionSound)
require.True(t, ok)
require.Equal(t, "Object stream 2: Dict(\"B\": 16, \"C\": 2, \"E\": Signed, \"Filter\": FlateDecode, \"Length\": 12, \"R\": 44100, \"Type\": Sound, )", contextAction.Sound.String())
require.Equal(t, "0.500000", contextAction.Volume.String())
require.Equal(t, "true", contextAction.Synchronous.String())
require.Equal(t, "true", contextAction.Repeat.String())
require.Equal(t, "true", contextAction.Mix.String())
})
}
func TestPdfActionMovie(t *testing.T) {
rawText := `
1 0 obj
<</Type /Action
/S /Movie
/Annotation <</Foo (bar)>>
/T (Title of the movie)
/Operation /Stop
>>
endobj
`
testAction(t, rawText, ActionTypeMovie, func(t *testing.T, action *PdfAction) {
contextAction, ok := action.context.(*PdfActionMovie)
require.True(t, ok)
require.Equal(t, "Dict(\"Foo\": bar, )", contextAction.Annotation.String())
require.Equal(t, "Title of the movie", contextAction.T.String())
require.Equal(t, "Stop", contextAction.Operation.String())
})
}
func TestPdfActionHide(t *testing.T) {
rawText := `
1 0 obj
<</Type /Action
/S /Hide
/T (Field)
/H false
>>
endobj
`
testAction(t, rawText, ActionTypeHide, func(t *testing.T, action *PdfAction) {
contextAction, ok := action.context.(*PdfActionHide)
require.True(t, ok)
require.Equal(t, "Field", contextAction.T.String())
require.Equal(t, "false", contextAction.H.String())
})
}
func TestPdfActionNamed(t *testing.T) {
rawText := `
1 0 obj
<</Type /Action
/S /Named
/N /NextPage
>>
endobj
`
testAction(t, rawText, ActionTypeNamed, func(t *testing.T, action *PdfAction) {
contextAction, ok := action.context.(*PdfActionNamed)
require.True(t, ok)
require.Equal(t, "NextPage", contextAction.N.String())
})
}
func TestPdfActionSubmitForm(t *testing.T) {
rawText := `
1 0 obj
<</Type /Action
/S /SubmitForm
/F <</Type /Filespec
/F (someFile.pdf)
>>
/Fields [(Address) (By) (Date) (Email) (TelNum) (Title)]
/Flags 2
>>
endobj
`
testAction(t, rawText, ActionTypeSubmitForm, func(t *testing.T, action *PdfAction) {
contextAction, ok := action.context.(*PdfActionSubmitForm)
require.True(t, ok)
require.Equal(t, "[Address, By, Date, Email, TelNum, Title]", contextAction.Fields.String())
require.Equal(t, "2", contextAction.Flags.String())
})
}
func TestPdfActionResetForm(t *testing.T) {
rawText := `
1 0 obj
<</Type /Action
/S /ResetForm
/Fields [(Address) (By) (Date) (Email) (TelNum) (Title)]
/Flags 2
>>
endobj
`
testAction(t, rawText, ActionTypeResetForm, func(t *testing.T, action *PdfAction) {
contextAction, ok := action.context.(*PdfActionResetForm)
require.True(t, ok)
require.Equal(t, "[Address, By, Date, Email, TelNum, Title]", contextAction.Fields.String())
require.Equal(t, "2", contextAction.Flags.String())
})
}
func TestPdfActionImportData(t *testing.T) {
rawText := `
1 0 obj
<</Type /Action
/S /ImportData
/F <</Type /Filespec
/F (someFile.pdf)
>>
>>
endobj
`
testAction(t, rawText, ActionTypeImportData, func(t *testing.T, action *PdfAction) {
contextAction, ok := action.context.(*PdfActionImportData)
require.True(t, ok)
require.IsType(t, &PdfFilespec{}, contextAction.F)
})
}
func TestPdfActionSetOCGState(t *testing.T) {
rawText := `
1 0 obj
<</Type /Action
/S /SetOCGState
/State [/Off <</OffFoo (Bar)>> /Toggle <</ToggleFoo (Bar)>> /ON <</OnFoo (Bar)>>]
/PreserveRB false
>>
endobj
`
testAction(t, rawText, ActionTypeSetOCGState, func(t *testing.T, action *PdfAction) {
contextAction, ok := action.context.(*PdfActionSetOCGState)
require.True(t, ok)
require.Equal(t, "[Off, Dict(\"OffFoo\": Bar, ), Toggle, Dict(\"ToggleFoo\": Bar, ), ON, Dict(\"OnFoo\": Bar, )]", contextAction.State.String())
require.Equal(t, "false", contextAction.PreserveRB.String())
})
}
func TestPdfActionRendition(t *testing.T) {
rawText := `
1 0 obj
<</Type /Action
/S /Rendition
/R <</R 1>>
/AN <</AN 2>>
/OP 4
/JS (javascript)
>>
endobj
`
testAction(t, rawText, ActionTypeRendition, func(t *testing.T, action *PdfAction) {
contextAction, ok := action.context.(*PdfActionRendition)
require.True(t, ok)
require.Equal(t, "Dict(\"R\": 1, )", contextAction.R.String())
require.Equal(t, "Dict(\"AN\": 2, )", contextAction.AN.String())
require.Equal(t, "4", contextAction.OP.String())
require.Equal(t, "javascript", contextAction.JS.String())
})
}
func TestPdfActionTrans(t *testing.T) {
rawText := `
1 0 obj
<</Type /Action
/S /Trans
/Trans <</X 123/Y 456>>
>>
endobj
`
testAction(t, rawText, ActionTypeTrans, func(t *testing.T, action *PdfAction) {
contextAction, ok := action.context.(*PdfActionTrans)
require.True(t, ok)
require.Equal(t, "Dict(\"X\": 123, \"Y\": 456, )", contextAction.Trans.String())
})
}
func TestPdfActionGoto3DView(t *testing.T) {
rawText := `
1 0 obj
<</Type /Action
/S /GoTo3DView
/TA <<
/X 123
/Y 456
>>
/V <</Name (fake)>>
>>
endobj
`
testAction(t, rawText, ActionTypeGoTo3DView, func(t *testing.T, action *PdfAction) {
contextAction, ok := action.context.(*PdfActionGoTo3DView)
require.True(t, ok)
require.Equal(t, "Dict(\"X\": 123, \"Y\": 456, )", contextAction.TA.String())
require.Equal(t, "Dict(\"Name\": fake, )", contextAction.V.String())
})
}
func TestPdfActionJavaScript(t *testing.T) {
rawText := `
1 0 obj
<</Type /Action
/S /JavaScript
/JS (alert\("test"\))
>>
endobj
`
testAction(t, rawText, ActionTypeJavaScript, func(t *testing.T, action *PdfAction) {
contextAction, ok := action.context.(*PdfActionJavaScript)
require.True(t, ok)
require.Equal(t, "alert(\"test\")", contextAction.JS.String())
})
}
func TestNewPdfAction(t *testing.T) {
action := NewPdfAction()
require.IsType(t, &PdfAction{}, action)
require.IsType(t, &core.PdfIndirectObject{}, action.container)
}
func TestNewPdfActionGoTo(t *testing.T) {
action := NewPdfActionGoTo()
require.IsType(t, &PdfActionGoTo{}, action)
require.IsType(t, &PdfAction{}, action.PdfAction)
require.Equal(t, action, action.PdfAction.context)
}
func TestNewPdfActionGoToR(t *testing.T) {
action := NewPdfActionGoToR()
require.IsType(t, &PdfActionGoToR{}, action)
require.IsType(t, &PdfAction{}, action.PdfAction)
require.Equal(t, action, action.PdfAction.context)
}
func TestNewPdfActionGoToE(t *testing.T) {
action := NewPdfActionGoToE()
require.IsType(t, &PdfActionGoToE{}, action)
require.IsType(t, &PdfAction{}, action.PdfAction)
require.Equal(t, action, action.PdfAction.context)
}
func TestNewPdfActionLaunch(t *testing.T) {
action := NewPdfActionLaunch()
require.IsType(t, &PdfActionLaunch{}, action)
require.IsType(t, &PdfAction{}, action.PdfAction)
require.Equal(t, action, action.PdfAction.context)
}
func TestNewPdfActionThread(t *testing.T) {
action := NewPdfActionThread()
require.IsType(t, &PdfActionThread{}, action)
require.IsType(t, &PdfAction{}, action.PdfAction)
require.Equal(t, action, action.PdfAction.context)
}
func TestNewPdfActionURI(t *testing.T) {
action := NewPdfActionURI()
require.IsType(t, &PdfActionURI{}, action)
require.IsType(t, &PdfAction{}, action.PdfAction)
require.Equal(t, action, action.PdfAction.context)
}
func TestNewPdfActionSound(t *testing.T) {
action := NewPdfActionSound()
require.IsType(t, &PdfActionSound{}, action)
require.IsType(t, &PdfAction{}, action.PdfAction)
require.Equal(t, action, action.PdfAction.context)
}
func TestNewPdfActionMovie(t *testing.T) {
action := NewPdfActionMovie()
require.IsType(t, &PdfActionMovie{}, action)
require.IsType(t, &PdfAction{}, action.PdfAction)
require.Equal(t, action, action.PdfAction.context)
}
func TestNewPdfActionHide(t *testing.T) {
action := NewPdfActionHide()
require.IsType(t, &PdfActionHide{}, action)
require.IsType(t, &PdfAction{}, action.PdfAction)
require.Equal(t, action, action.PdfAction.context)
}
func TestNewPdfActionNamed(t *testing.T) {
action := NewPdfActionNamed()
require.IsType(t, &PdfActionNamed{}, action)
require.IsType(t, &PdfAction{}, action.PdfAction)
require.Equal(t, action, action.PdfAction.context)
}
func TestNewPdfActionSubmitForm(t *testing.T) {
action := NewPdfActionSubmitForm()
require.IsType(t, &PdfActionSubmitForm{}, action)
require.IsType(t, &PdfAction{}, action.PdfAction)
require.Equal(t, action, action.PdfAction.context)
}
func TestNewPdfActionResetForm(t *testing.T) {
action := NewPdfActionResetForm()
require.IsType(t, &PdfActionResetForm{}, action)
require.IsType(t, &PdfAction{}, action.PdfAction)
require.Equal(t, action, action.PdfAction.context)
}
func TestNewPdfActionImportData(t *testing.T) {
action := NewPdfActionImportData()
require.IsType(t, &PdfActionImportData{}, action)
require.IsType(t, &PdfAction{}, action.PdfAction)
require.Equal(t, action, action.PdfAction.context)
}
func TestNewPdfActionSetOCGState(t *testing.T) {
action := NewPdfActionSetOCGState()
require.IsType(t, &PdfActionSetOCGState{}, action)
require.IsType(t, &PdfAction{}, action.PdfAction)
require.Equal(t, action, action.PdfAction.context)
}
func TestNewPdfActionRendition(t *testing.T) {
action := NewPdfActionRendition()
require.IsType(t, &PdfActionRendition{}, action)
require.IsType(t, &PdfAction{}, action.PdfAction)
require.Equal(t, action, action.PdfAction.context)
}
func TestNewPdfActionTrans(t *testing.T) {
action := NewPdfActionTrans()
require.IsType(t, &PdfActionTrans{}, action)
require.IsType(t, &PdfAction{}, action.PdfAction)
require.Equal(t, action, action.PdfAction.context)
}
func TestNewPdfActionGoTo3DView(t *testing.T) {
action := NewPdfActionGoTo3DView()
require.IsType(t, &PdfActionGoTo3DView{}, action)
require.IsType(t, &PdfAction{}, action.PdfAction)
require.Equal(t, action, action.PdfAction.context)
}
func TestNewPdfActionJavaScript(t *testing.T) {
action := NewPdfActionJavaScript()
require.IsType(t, &PdfActionJavaScript{}, action)
require.IsType(t, &PdfAction{}, action.PdfAction)
require.Equal(t, action, action.PdfAction.context)
}

View File

@ -97,6 +97,38 @@ type PdfAnnotationLink struct {
PA core.PdfObject
QuadPoints core.PdfObject
BS core.PdfObject
action *PdfAction
reader *PdfReader
}
// GetAction returns the PDF action for the annotation link.
func (a *PdfAnnotationLink) GetAction() (*PdfAction, error) {
if a.action != nil {
return a.action, nil
}
if a.A == nil {
return nil, nil
}
if a.reader == nil {
return nil, nil
}
action, err := a.reader.loadAction(a.A)
if err != nil {
return nil, err
}
a.action = action
return a.action, nil
}
// SetAction sets the PDF action for the annotation link.
func (a *PdfAnnotationLink) SetAction(action *PdfAction) {
a.action = action
if action == nil {
a.A = nil
}
}
// PdfAnnotationFreeText represents FreeText annotations.
@ -1054,6 +1086,19 @@ func (r *PdfReader) newPdfAnnotationLinkFromDict(d *core.PdfObjectDictionary) (*
return &annot, nil
}
func (r *PdfReader) loadAction(obj core.PdfObject) (*PdfAction, error) {
if indObj, isIndirect := core.GetIndirect(obj); isIndirect {
actionObj, err := r.newPdfActionFromIndirectObject(indObj)
if err != nil {
return nil, err
}
return actionObj, nil
} else if !core.IsNullObject(obj) {
return nil, errors.New("action should point to an indirect object")
}
return nil, nil
}
func (r *PdfReader) newPdfAnnotationFreeTextFromDict(d *core.PdfObjectDictionary) (*PdfAnnotationFreeText, error) {
annot := PdfAnnotationFreeText{}
@ -1497,7 +1542,13 @@ func (link *PdfAnnotationLink) ToPdfObject() core.PdfObject {
d := container.PdfObject.(*core.PdfObjectDictionary)
d.SetIfNotNil("Subtype", core.MakeName("Link"))
d.SetIfNotNil("A", link.A)
if link.action != nil && link.action.context != nil {
d.Set("A", link.action.context.ToPdfObject())
} else if link.A != nil {
d.Set("A", link.A)
}
d.SetIfNotNil("Dest", link.Dest)
d.SetIfNotNil("H", link.H)
d.SetIfNotNil("PA", link.PA)

178
model/file.go Normal file
View File

@ -0,0 +1,178 @@
/*
* This file is subject to the terms and conditions defined in
* file 'LICENSE.md', which is part of this source code package.
*/
package model
import (
"errors"
"github.com/unidoc/unipdf/v3/common"
"github.com/unidoc/unipdf/v3/core"
)
// (Section 7.11.3 p. 102).
// See Table 44 - Entries in a file specification dictionary
/*
* A PDF file can refer to the contents of another file by using a file specification (PDF 1.1), which shall take either
* of two forms:
* A simple file specification shall give just the name of the target file in a standard format, independent of the
* naming conventions of any particular file system. It shall take the form of either a string or a dictionary
* A full file specification shall include information related to one or more specific file systems. It shall only be
* represented as a dictionary.
*
* A file specification shall refer to a file external to the PDF file or to a file embedded within the referring PDF file,
* allowing its contents to be stored or transmitted along with the PDF file. The file shall be considered to be
* external to the PDF file in either case.
* A file specification could describe a URL-based file system and will follow the rules of Internet RFC 1808, Relative Uniform Resource Locators
*/
// PdfFilespec represents a file specification which can either refer to an external or embedded file.
type PdfFilespec struct {
Type core.PdfObject
FS core.PdfObject
F core.PdfObject // A file specification string
UF core.PdfObject // A Unicode text string that provides file specification
DOS core.PdfObject // A file specification string representing a DOS file name. OBSOLETE
Mac core.PdfObject // A file specification string representing a Mac OS file name. OBSOLETE
Unix core.PdfObject // A file specification string representing a UNIX file name. OBSOLETE
ID core.PdfObject // An array of two byte strings constituting a file identifier
V core.PdfObject // A flag indicating whether the file referenced by the file specification is volatile (changes frequently with time).
EF core.PdfObject // A dictionary containing a subset of the keys F, UF, DOS, Mac, and Unix, corresponding to the entries by those names in the file specification dictionary
RF core.PdfObject
Desc core.PdfObject // Descriptive text associated with the file specification
CI core.PdfObject // A collection item dictionary, which shall be used to create the user interface for portable collections
container core.PdfObject
}
// GetContainingPdfObject implements interface PdfModel.
func (f *PdfFilespec) GetContainingPdfObject() core.PdfObject {
return f.container
}
func (f *PdfFilespec) getDict() *core.PdfObjectDictionary {
if indObj, is := f.container.(*core.PdfIndirectObject); is {
dict, ok := indObj.PdfObject.(*core.PdfObjectDictionary)
if !ok {
return nil
}
return dict
} else if dictObj, isDict := f.container.(*core.PdfObjectDictionary); isDict {
return dictObj
} else {
common.Log.Debug("Trying to access Filespec dictionary of invalid object type (%T)", f.container)
return nil
}
}
// ToPdfObject implements interface PdfModel.
func (f *PdfFilespec) ToPdfObject() core.PdfObject {
d := f.getDict()
d.Clear()
d.Set("Type", core.MakeName("Filespec"))
d.SetIfNotNil("FS", f.FS)
d.SetIfNotNil("F", f.F)
d.SetIfNotNil("UF", f.UF)
d.SetIfNotNil("DOS", f.DOS)
d.SetIfNotNil("Mac", f.Mac)
d.SetIfNotNil("Unix", f.Unix)
d.SetIfNotNil("ID", f.ID)
d.SetIfNotNil("V", f.V)
d.SetIfNotNil("EF", f.EF)
d.SetIfNotNil("RF", f.RF)
d.SetIfNotNil("Desc", f.Desc)
d.SetIfNotNil("CI", f.CI)
return f.container
}
// NewPdfFilespecFromObj creates and returns a new PdfFilespec object.
func NewPdfFilespecFromObj(obj core.PdfObject) (*PdfFilespec, error) {
fs := &PdfFilespec{}
var dict *core.PdfObjectDictionary
if indObj, isInd := core.GetIndirect(obj); isInd {
fs.container = indObj
d, ok := core.GetDict(indObj.PdfObject)
if !ok {
common.Log.Debug("Object not a dictionary type")
return nil, core.ErrTypeError
}
dict = d
} else if d, isDict := core.GetDict(obj); isDict {
fs.container = d
dict = d
} else {
common.Log.Debug("Object type unexpected (%T)", obj)
return nil, core.ErrTypeError
}
if dict == nil {
common.Log.Debug("Dictionary missing")
return nil, errors.New("dict missing")
}
if obj := dict.Get("Type"); obj != nil {
str, ok := obj.(*core.PdfObjectName)
if !ok {
common.Log.Trace("Incompatibility! Invalid type of Type (%T) - should be Name", obj)
} else {
if *str != "Filespec" {
// Log a debug message.
// Not returning an error on this.
common.Log.Trace("Unsuspected Type != Filespec (%s)", *str)
}
}
}
if obj := dict.Get("FS"); obj != nil {
fs.FS = obj
}
if obj := dict.Get("F"); obj != nil {
fs.F = obj
}
if obj := dict.Get("UF"); obj != nil {
fs.UF = obj
}
if obj := dict.Get("DOS"); obj != nil {
fs.DOS = obj
}
if obj := dict.Get("Mac"); obj != nil {
fs.Mac = obj
}
if obj := dict.Get("Unix"); obj != nil {
fs.Unix = obj
}
if obj := dict.Get("ID"); obj != nil {
fs.ID = obj
}
if obj := dict.Get("V"); obj != nil {
fs.V = obj
}
if obj := dict.Get("EF"); obj != nil {
fs.EF = obj
}
if obj := dict.Get("RF"); obj != nil {
fs.RF = obj
}
if obj := dict.Get("Desc"); obj != nil {
fs.Desc = obj
}
if obj := dict.Get("CI"); obj != nil {
fs.CI = obj
}
return fs, nil
}
// NewPdfFilespec returns an initialized generic PDF filespec model.
func NewPdfFilespec() *PdfFilespec {
action := &PdfFilespec{}
action.container = core.MakeIndirectObject(core.MakeDict())
return action
}

98
model/file_test.go Normal file
View File

@ -0,0 +1,98 @@
/*
* This file is subject to the terms and conditions defined in
* file 'LICENSE.md', which is part of this source code package.
*/
package model
import (
"github.com/stretchr/testify/require"
"github.com/unidoc/unipdf/v3/core"
"strings"
"testing"
)
func TestUrlFileSpec(t *testing.T) {
rawText := `
1 0 obj
<</Type /Filespec
/FS /URL
/F (ftp://www.beatles.com/Movies/AbbeyRoad.mov)
>>
endobj
`
r := NewReaderForText(rawText)
err := r.ParseIndObjSeries()
require.NoError(t, err)
// Load the field from object number 1.
obj, err := r.parser.LookupByNumber(1)
require.NoError(t, err)
ind, ok := obj.(*core.PdfIndirectObject)
require.True(t, ok)
fileSpec, err := NewPdfFilespecFromObj(ind)
require.NoError(t, err)
require.Equal(t, "URL", fileSpec.FS.String())
require.Equal(t, "ftp://www.beatles.com/Movies/AbbeyRoad.mov", fileSpec.F.String())
outDict, ok := core.GetDict(fileSpec.ToPdfObject())
if !ok {
t.Fatalf("error")
}
contains := strings.Contains(
strings.Replace(rawText, "\n", "", -1),
outDict.WriteString())
require.True(t, contains, "generated output doesn't match the expected output")
}
func TestPdfFileSpec(t *testing.T) {
rawText := `
1 0 obj
<</Type /Filespec
/F (VideoIssue1.mov)
/UF (VideoIssue2.mov)
/DOS (VIDEOISSUE.MOV)
/Mac (VideoIssue3.mov)
/Unix (VideoIssue4.mov)
>>
endobj
`
r := NewReaderForText(rawText)
err := r.ParseIndObjSeries()
require.NoError(t, err)
// Load the field from object number 1.
obj, err := r.parser.LookupByNumber(1)
require.NoError(t, err)
ind, ok := obj.(*core.PdfIndirectObject)
require.True(t, ok)
fileSpec, err := NewPdfFilespecFromObj(ind)
require.NoError(t, err)
require.Equal(t, "VideoIssue1.mov", fileSpec.F.String())
require.Equal(t, "VideoIssue2.mov", fileSpec.UF.String())
require.Equal(t, "VIDEOISSUE.MOV", fileSpec.DOS.String())
require.Equal(t, "VideoIssue3.mov", fileSpec.Mac.String())
require.Equal(t, "VideoIssue4.mov", fileSpec.Unix.String())
outDict, ok := core.GetDict(fileSpec.ToPdfObject())
if !ok {
t.Fatalf("error")
}
contains := strings.Contains(
strings.Replace(rawText, "\n", "", -1),
outDict.WriteString())
require.True(t, contains, "generated output doesn't match the expected output")
}