mirror of
https://github.com/unidoc/unipdf.git
synced 2025-04-26 13:48:55 +08:00
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:
parent
fbed2a74d9
commit
b1c851b7b0
@ -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.
|
||||
|
@ -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
1019
model/action.go
Normal file
File diff suppressed because it is too large
Load Diff
569
model/action_test.go
Normal file
569
model/action_test.go
Normal 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)
|
||||
}
|
@ -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
178
model/file.go
Normal 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
98
model/file_test.go
Normal 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")
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user