2017-04-19 12:05:20 +00:00
|
|
|
/*
|
|
|
|
* 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"
|
2018-08-07 16:12:25 +00:00
|
|
|
"fmt"
|
2017-04-19 12:05:20 +00:00
|
|
|
|
|
|
|
"github.com/unidoc/unidoc/common"
|
|
|
|
. "github.com/unidoc/unidoc/pdf/core"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Page resources model.
|
|
|
|
// Implements PdfModel.
|
|
|
|
type PdfPageResources struct {
|
2017-05-24 21:43:32 +00:00
|
|
|
ExtGState PdfObject
|
2017-04-19 12:05:20 +00:00
|
|
|
ColorSpace *PdfPageResourcesColorspaces
|
|
|
|
Pattern PdfObject
|
|
|
|
Shading PdfObject
|
|
|
|
XObject PdfObject
|
|
|
|
Font PdfObject
|
|
|
|
ProcSet PdfObject
|
|
|
|
Properties PdfObject
|
2018-10-02 19:50:18 +00:00
|
|
|
// Primitive resource container.
|
2017-04-19 12:05:20 +00:00
|
|
|
primitive *PdfObjectDictionary
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewPdfPageResources() *PdfPageResources {
|
|
|
|
r := &PdfPageResources{}
|
2017-07-08 21:04:13 +00:00
|
|
|
r.primitive = MakeDict()
|
2017-04-19 12:05:20 +00:00
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewPdfPageResourcesFromDict(dict *PdfObjectDictionary) (*PdfPageResources, error) {
|
|
|
|
r := NewPdfPageResources()
|
|
|
|
|
2017-07-08 21:04:13 +00:00
|
|
|
if obj := dict.Get("ExtGState"); obj != nil {
|
2017-04-19 12:05:20 +00:00
|
|
|
r.ExtGState = obj
|
|
|
|
}
|
2018-07-25 12:00:49 +10:00
|
|
|
if obj := dict.Get("ColorSpace"); obj != nil && !IsNullObject(obj) {
|
2017-04-19 12:05:20 +00:00
|
|
|
colorspaces, err := newPdfPageResourcesColorspacesFromPdfObject(obj)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
r.ColorSpace = colorspaces
|
|
|
|
}
|
2017-07-08 21:04:13 +00:00
|
|
|
if obj := dict.Get("Pattern"); obj != nil {
|
2017-04-19 12:05:20 +00:00
|
|
|
r.Pattern = obj
|
|
|
|
}
|
2017-07-08 21:04:13 +00:00
|
|
|
if obj := dict.Get("Shading"); obj != nil {
|
2017-04-19 12:05:20 +00:00
|
|
|
r.Shading = obj
|
|
|
|
}
|
2017-07-08 21:04:13 +00:00
|
|
|
if obj := dict.Get("XObject"); obj != nil {
|
2017-04-19 12:05:20 +00:00
|
|
|
r.XObject = obj
|
|
|
|
}
|
2017-07-08 21:04:13 +00:00
|
|
|
if obj := dict.Get("Font"); obj != nil {
|
2017-04-19 12:05:20 +00:00
|
|
|
r.Font = obj
|
|
|
|
}
|
2017-07-08 21:04:13 +00:00
|
|
|
if obj := dict.Get("ProcSet"); obj != nil {
|
2017-04-19 12:05:20 +00:00
|
|
|
r.ProcSet = obj
|
|
|
|
}
|
2017-07-08 21:04:13 +00:00
|
|
|
if obj := dict.Get("Properties"); obj != nil {
|
2017-04-19 12:05:20 +00:00
|
|
|
r.Properties = obj
|
|
|
|
}
|
|
|
|
|
|
|
|
return r, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *PdfPageResources) GetContainingPdfObject() PdfObject {
|
|
|
|
return r.primitive
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *PdfPageResources) ToPdfObject() PdfObject {
|
|
|
|
d := r.primitive
|
|
|
|
d.SetIfNotNil("ExtGState", r.ExtGState)
|
|
|
|
if r.ColorSpace != nil {
|
|
|
|
d.SetIfNotNil("ColorSpace", r.ColorSpace.ToPdfObject())
|
|
|
|
}
|
|
|
|
d.SetIfNotNil("Pattern", r.Pattern)
|
|
|
|
d.SetIfNotNil("Shading", r.Shading)
|
|
|
|
d.SetIfNotNil("XObject", r.XObject)
|
|
|
|
d.SetIfNotNil("Font", r.Font)
|
|
|
|
d.SetIfNotNil("ProcSet", r.ProcSet)
|
|
|
|
d.SetIfNotNil("Properties", r.Properties)
|
|
|
|
|
|
|
|
return d
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add External Graphics State (GState). The gsDict can be specified either directly as a dictionary or an indirect
|
|
|
|
// object containing a dictionary.
|
|
|
|
func (r *PdfPageResources) AddExtGState(gsName PdfObjectName, gsDict PdfObject) error {
|
|
|
|
if r.ExtGState == nil {
|
2017-07-08 21:04:13 +00:00
|
|
|
r.ExtGState = MakeDict()
|
2017-04-19 12:05:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
obj := r.ExtGState
|
|
|
|
dict, ok := TraceToDirectObject(obj).(*PdfObjectDictionary)
|
|
|
|
if !ok {
|
|
|
|
common.Log.Debug("ExtGState type error (got %T/%T)", obj, TraceToDirectObject(obj))
|
|
|
|
return ErrTypeError
|
|
|
|
}
|
|
|
|
|
2017-07-08 21:04:13 +00:00
|
|
|
dict.Set(gsName, gsDict)
|
2017-04-19 12:05:20 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-07-01 22:08:51 +00:00
|
|
|
// Get the ExtGState specified by keyName. Returns a bool indicating whether it was found or not.
|
|
|
|
func (r *PdfPageResources) GetExtGState(keyName PdfObjectName) (PdfObject, bool) {
|
|
|
|
if r.ExtGState == nil {
|
|
|
|
return nil, false
|
|
|
|
}
|
|
|
|
|
|
|
|
dict, ok := TraceToDirectObject(r.ExtGState).(*PdfObjectDictionary)
|
|
|
|
if !ok {
|
|
|
|
common.Log.Debug("ERROR: Invalid ExtGState entry - not a dict (got %T)", r.ExtGState)
|
|
|
|
return nil, false
|
|
|
|
}
|
|
|
|
|
2017-07-08 21:04:13 +00:00
|
|
|
if obj := dict.Get(keyName); obj != nil {
|
2017-07-01 22:08:51 +00:00
|
|
|
return obj, true
|
|
|
|
} else {
|
|
|
|
return nil, false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check whether a font is defined by the specified keyName.
|
|
|
|
func (r *PdfPageResources) HasExtGState(keyName PdfObjectName) bool {
|
|
|
|
_, has := r.GetFontByName(keyName)
|
|
|
|
return has
|
|
|
|
}
|
|
|
|
|
2017-04-19 12:05:20 +00:00
|
|
|
// Get the shading specified by keyName. Returns nil if not existing. The bool flag indicated whether it was found
|
|
|
|
// or not.
|
2017-06-28 15:15:44 +00:00
|
|
|
func (r *PdfPageResources) GetShadingByName(keyName PdfObjectName) (*PdfShading, bool) {
|
2017-04-19 12:05:20 +00:00
|
|
|
if r.Shading == nil {
|
|
|
|
return nil, false
|
|
|
|
}
|
|
|
|
|
2017-08-04 17:47:55 +10:00
|
|
|
shadingDict, ok := TraceToDirectObject(r.Shading).(*PdfObjectDictionary)
|
2017-04-19 12:05:20 +00:00
|
|
|
if !ok {
|
|
|
|
common.Log.Debug("ERROR: Invalid Shading entry - not a dict (got %T)", r.Shading)
|
|
|
|
return nil, false
|
|
|
|
}
|
|
|
|
|
2017-07-08 21:04:13 +00:00
|
|
|
if obj := shadingDict.Get(keyName); obj != nil {
|
2017-04-19 12:05:20 +00:00
|
|
|
shading, err := newPdfShadingFromPdfObject(obj)
|
|
|
|
if err != nil {
|
|
|
|
common.Log.Debug("ERROR: failed to load pdf shading: %v", err)
|
|
|
|
return nil, false
|
|
|
|
}
|
|
|
|
return shading, true
|
|
|
|
} else {
|
|
|
|
return nil, false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set a shading resource specified by keyName.
|
2017-06-28 15:15:44 +00:00
|
|
|
func (r *PdfPageResources) SetShadingByName(keyName PdfObjectName, shadingObj PdfObject) error {
|
2017-04-19 12:05:20 +00:00
|
|
|
if r.Shading == nil {
|
2017-07-08 21:04:13 +00:00
|
|
|
r.Shading = MakeDict()
|
2017-04-19 12:05:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
shadingDict, has := r.Shading.(*PdfObjectDictionary)
|
|
|
|
if !has {
|
|
|
|
return ErrTypeError
|
|
|
|
}
|
|
|
|
|
2017-07-08 21:04:13 +00:00
|
|
|
shadingDict.Set(keyName, shadingObj)
|
2017-04-19 12:05:20 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the pattern specified by keyName. Returns nil if not existing. The bool flag indicated whether it was found
|
|
|
|
// or not.
|
2017-06-28 15:15:44 +00:00
|
|
|
func (r *PdfPageResources) GetPatternByName(keyName PdfObjectName) (*PdfPattern, bool) {
|
2017-04-19 12:05:20 +00:00
|
|
|
if r.Pattern == nil {
|
|
|
|
return nil, false
|
|
|
|
}
|
|
|
|
|
2017-08-04 17:47:55 +10:00
|
|
|
patternDict, ok := TraceToDirectObject(r.Pattern).(*PdfObjectDictionary)
|
2017-04-19 12:05:20 +00:00
|
|
|
if !ok {
|
|
|
|
common.Log.Debug("ERROR: Invalid Pattern entry - not a dict (got %T)", r.Pattern)
|
|
|
|
return nil, false
|
|
|
|
}
|
|
|
|
|
2017-07-08 21:04:13 +00:00
|
|
|
if obj := patternDict.Get(keyName); obj != nil {
|
2017-04-19 12:05:20 +00:00
|
|
|
pattern, err := newPdfPatternFromPdfObject(obj)
|
|
|
|
if err != nil {
|
|
|
|
common.Log.Debug("ERROR: failed to load pdf pattern: %v", err)
|
|
|
|
return nil, false
|
|
|
|
}
|
|
|
|
|
|
|
|
return pattern, true
|
|
|
|
} else {
|
|
|
|
return nil, false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set a pattern resource specified by keyName.
|
2017-06-28 15:15:44 +00:00
|
|
|
func (r *PdfPageResources) SetPatternByName(keyName PdfObjectName, pattern PdfObject) error {
|
2017-04-19 12:05:20 +00:00
|
|
|
if r.Pattern == nil {
|
2017-07-08 21:04:13 +00:00
|
|
|
r.Pattern = MakeDict()
|
2017-04-19 12:05:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
patternDict, has := r.Pattern.(*PdfObjectDictionary)
|
|
|
|
if !has {
|
|
|
|
return ErrTypeError
|
|
|
|
}
|
|
|
|
|
2017-07-08 21:04:13 +00:00
|
|
|
patternDict.Set(keyName, pattern)
|
2017-06-28 15:15:44 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the font specified by keyName. Returns the PdfObject which the entry refers to.
|
|
|
|
// Returns a bool value indicating whether or not the entry was found.
|
|
|
|
func (r *PdfPageResources) GetFontByName(keyName PdfObjectName) (PdfObject, bool) {
|
|
|
|
if r.Font == nil {
|
|
|
|
return nil, false
|
|
|
|
}
|
|
|
|
|
|
|
|
fontDict, has := TraceToDirectObject(r.Font).(*PdfObjectDictionary)
|
|
|
|
if !has {
|
|
|
|
common.Log.Debug("ERROR: Font not a dictionary! (got %T)", TraceToDirectObject(r.Font))
|
|
|
|
return nil, false
|
|
|
|
}
|
|
|
|
|
2017-07-08 21:04:13 +00:00
|
|
|
if obj := fontDict.Get(keyName); obj != nil {
|
2017-06-28 15:15:44 +00:00
|
|
|
return obj, true
|
|
|
|
} else {
|
|
|
|
return nil, false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check whether a font is defined by the specified keyName.
|
|
|
|
func (r *PdfPageResources) HasFontByName(keyName PdfObjectName) bool {
|
|
|
|
_, has := r.GetFontByName(keyName)
|
|
|
|
return has
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set the font specified by keyName to the given object.
|
|
|
|
func (r *PdfPageResources) SetFontByName(keyName PdfObjectName, obj PdfObject) error {
|
|
|
|
if r.Font == nil {
|
|
|
|
// Create if not existing.
|
2017-07-08 21:04:13 +00:00
|
|
|
r.Font = MakeDict()
|
2017-06-28 15:15:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fontDict, has := TraceToDirectObject(r.Font).(*PdfObjectDictionary)
|
|
|
|
if !has {
|
|
|
|
common.Log.Debug("ERROR: Font not a dictionary! (got %T)", TraceToDirectObject(r.Font))
|
|
|
|
return ErrTypeError
|
|
|
|
}
|
|
|
|
|
|
|
|
fontDict.Set(keyName, obj)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *PdfPageResources) GetColorspaceByName(keyName PdfObjectName) (PdfColorspace, bool) {
|
|
|
|
if r.ColorSpace == nil {
|
|
|
|
return nil, false
|
|
|
|
}
|
|
|
|
|
|
|
|
cs, has := r.ColorSpace.Colorspaces[string(keyName)]
|
|
|
|
if !has {
|
|
|
|
return nil, false
|
|
|
|
}
|
|
|
|
|
|
|
|
return cs, true
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *PdfPageResources) HasColorspaceByName(keyName PdfObjectName) bool {
|
|
|
|
if r.ColorSpace == nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
_, has := r.ColorSpace.Colorspaces[string(keyName)]
|
|
|
|
return has
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *PdfPageResources) SetColorspaceByName(keyName PdfObjectName, cs PdfColorspace) error {
|
|
|
|
if r.ColorSpace == nil {
|
|
|
|
r.ColorSpace = NewPdfPageResourcesColorspaces()
|
|
|
|
}
|
|
|
|
|
|
|
|
r.ColorSpace.Set(keyName, cs)
|
2017-04-19 12:05:20 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if an XObject with a specified keyName is defined.
|
2017-06-28 15:15:44 +00:00
|
|
|
func (r *PdfPageResources) HasXObjectByName(keyName PdfObjectName) bool {
|
2017-04-19 12:05:20 +00:00
|
|
|
obj, _ := r.GetXObjectByName(keyName)
|
|
|
|
if obj != nil {
|
|
|
|
return true
|
|
|
|
} else {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-07 16:12:25 +00:00
|
|
|
// GenerateXObjectName generates an unused XObject name that can be used for adding new XObjects.
|
|
|
|
// Uses format XObj1, XObj2, ...
|
|
|
|
func (r *PdfPageResources) GenerateXObjectName() PdfObjectName {
|
|
|
|
num := 1
|
|
|
|
for {
|
|
|
|
name := MakeName(fmt.Sprintf("XObj%d", num))
|
|
|
|
if !r.HasXObjectByName(*name) {
|
|
|
|
return *name
|
|
|
|
}
|
|
|
|
num++
|
|
|
|
}
|
|
|
|
// Not reached.
|
|
|
|
}
|
|
|
|
|
2017-04-19 12:05:20 +00:00
|
|
|
type XObjectType int
|
|
|
|
|
|
|
|
const (
|
|
|
|
XObjectTypeUndefined XObjectType = iota
|
|
|
|
XObjectTypeImage XObjectType = iota
|
|
|
|
XObjectTypeForm XObjectType = iota
|
|
|
|
XObjectTypePS XObjectType = iota
|
|
|
|
XObjectTypeUnknown XObjectType = iota
|
|
|
|
)
|
|
|
|
|
|
|
|
// Returns the XObject with the specified keyName and the object type.
|
2017-06-28 15:15:44 +00:00
|
|
|
func (r *PdfPageResources) GetXObjectByName(keyName PdfObjectName) (*PdfObjectStream, XObjectType) {
|
2017-04-19 12:05:20 +00:00
|
|
|
if r.XObject == nil {
|
|
|
|
return nil, XObjectTypeUndefined
|
|
|
|
}
|
|
|
|
|
|
|
|
xresDict, has := TraceToDirectObject(r.XObject).(*PdfObjectDictionary)
|
|
|
|
if !has {
|
|
|
|
common.Log.Debug("ERROR: XObject not a dictionary! (got %T)", TraceToDirectObject(r.XObject))
|
|
|
|
return nil, XObjectTypeUndefined
|
|
|
|
}
|
|
|
|
|
2017-07-08 21:04:13 +00:00
|
|
|
if obj := xresDict.Get(keyName); obj != nil {
|
2017-04-19 12:05:20 +00:00
|
|
|
stream, ok := obj.(*PdfObjectStream)
|
|
|
|
if !ok {
|
|
|
|
common.Log.Debug("XObject not pointing to a stream %T", obj)
|
|
|
|
return nil, XObjectTypeUndefined
|
|
|
|
}
|
|
|
|
dict := stream.PdfObjectDictionary
|
|
|
|
|
2017-08-04 17:47:55 +10:00
|
|
|
name, ok := TraceToDirectObject(dict.Get("Subtype")).(*PdfObjectName)
|
2017-04-19 12:05:20 +00:00
|
|
|
if !ok {
|
|
|
|
common.Log.Debug("XObject Subtype not a Name, dict: %s", dict.String())
|
|
|
|
return nil, XObjectTypeUndefined
|
|
|
|
}
|
|
|
|
|
|
|
|
if *name == "Image" {
|
|
|
|
return stream, XObjectTypeImage
|
|
|
|
} else if *name == "Form" {
|
|
|
|
return stream, XObjectTypeForm
|
|
|
|
} else if *name == "PS" {
|
|
|
|
return stream, XObjectTypePS
|
|
|
|
} else {
|
|
|
|
common.Log.Debug("XObject Subtype not known (%s)", *name)
|
|
|
|
return nil, XObjectTypeUndefined
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return nil, XObjectTypeUndefined
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-28 15:15:44 +00:00
|
|
|
func (r *PdfPageResources) SetXObjectByName(keyName PdfObjectName, stream *PdfObjectStream) error {
|
2017-04-19 12:05:20 +00:00
|
|
|
if r.XObject == nil {
|
2017-07-08 21:04:13 +00:00
|
|
|
r.XObject = MakeDict()
|
2017-04-19 12:05:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
obj := TraceToDirectObject(r.XObject)
|
|
|
|
xresDict, has := obj.(*PdfObjectDictionary)
|
|
|
|
if !has {
|
|
|
|
common.Log.Debug("Invalid XObject, got %T/%T", r.XObject, obj)
|
|
|
|
return errors.New("Type check error")
|
|
|
|
}
|
|
|
|
|
2017-07-08 21:04:13 +00:00
|
|
|
xresDict.Set(keyName, stream)
|
2017-04-19 12:05:20 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-06-28 15:15:44 +00:00
|
|
|
func (r *PdfPageResources) GetXObjectImageByName(keyName PdfObjectName) (*XObjectImage, error) {
|
2017-04-19 12:05:20 +00:00
|
|
|
stream, xtype := r.GetXObjectByName(keyName)
|
|
|
|
if stream == nil {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
if xtype != XObjectTypeImage {
|
|
|
|
return nil, errors.New("Not an image")
|
|
|
|
}
|
|
|
|
|
|
|
|
ximg, err := NewXObjectImageFromStream(stream)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return ximg, nil
|
|
|
|
}
|
|
|
|
|
2017-06-28 15:15:44 +00:00
|
|
|
func (r *PdfPageResources) SetXObjectImageByName(keyName PdfObjectName, ximg *XObjectImage) error {
|
2017-04-19 12:05:20 +00:00
|
|
|
stream := ximg.ToPdfObject().(*PdfObjectStream)
|
2017-06-28 15:15:44 +00:00
|
|
|
err := r.SetXObjectByName(keyName, stream)
|
2017-04-19 12:05:20 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-06-28 15:15:44 +00:00
|
|
|
func (r *PdfPageResources) GetXObjectFormByName(keyName PdfObjectName) (*XObjectForm, error) {
|
2017-04-19 12:05:20 +00:00
|
|
|
stream, xtype := r.GetXObjectByName(keyName)
|
|
|
|
if stream == nil {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
if xtype != XObjectTypeForm {
|
|
|
|
return nil, errors.New("Not a form")
|
|
|
|
}
|
|
|
|
|
|
|
|
xform, err := NewXObjectFormFromStream(stream)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return xform, nil
|
|
|
|
}
|
|
|
|
|
2017-06-28 15:15:44 +00:00
|
|
|
func (r *PdfPageResources) SetXObjectFormByName(keyName PdfObjectName, xform *XObjectForm) error {
|
2017-04-19 12:05:20 +00:00
|
|
|
stream := xform.ToPdfObject().(*PdfObjectStream)
|
2017-06-28 15:15:44 +00:00
|
|
|
err := r.SetXObjectByName(keyName, stream)
|
2017-04-19 12:05:20 +00:00
|
|
|
return err
|
|
|
|
}
|