unipdf/model/resources.go
2019-05-16 20:44:51 +00:00

483 lines
14 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 model
import (
"errors"
"fmt"
"github.com/unidoc/unipdf/v3/common"
"github.com/unidoc/unipdf/v3/core"
)
// PdfPageResources is a Page resources model.
// Implements PdfModel.
type PdfPageResources struct {
ExtGState core.PdfObject
ColorSpace core.PdfObject
Pattern core.PdfObject
Shading core.PdfObject
XObject core.PdfObject
Font core.PdfObject
ProcSet core.PdfObject
Properties core.PdfObject
// Primitive resource container.
primitive *core.PdfObjectDictionary
// Loaded objects.
colorspace *PdfPageResourcesColorspaces
}
// NewPdfPageResources returns a new PdfPageResources object.
func NewPdfPageResources() *PdfPageResources {
r := &PdfPageResources{}
r.primitive = core.MakeDict()
return r
}
// NewPdfPageResourcesFromDict creates and returns a new PdfPageResources object
// from the input dictionary.
func NewPdfPageResourcesFromDict(dict *core.PdfObjectDictionary) (*PdfPageResources, error) {
r := NewPdfPageResources()
if obj := dict.Get("ExtGState"); obj != nil {
r.ExtGState = obj
}
if obj := dict.Get("ColorSpace"); obj != nil && !core.IsNullObject(obj) {
r.ColorSpace = obj
}
if obj := dict.Get("Pattern"); obj != nil {
r.Pattern = obj
}
if obj := dict.Get("Shading"); obj != nil {
r.Shading = obj
}
if obj := dict.Get("XObject"); obj != nil {
r.XObject = obj
}
if obj := dict.Get("Font"); obj != nil {
r.Font = obj
}
if obj := dict.Get("ProcSet"); obj != nil {
r.ProcSet = obj
}
if obj := dict.Get("Properties"); obj != nil {
r.Properties = obj
}
return r, nil
}
// GetColorspaces loads PdfPageResourcesColorspaces from `r.ColorSpace` and returns an error if there
// is a problem loading. Once loaded, the same object is returned on multiple calls.
func (r *PdfPageResources) GetColorspaces() (*PdfPageResourcesColorspaces, error) {
if r.colorspace != nil {
return r.colorspace, nil
}
if r.ColorSpace == nil {
return nil, nil
}
colorspaces, err := newPdfPageResourcesColorspacesFromPdfObject(r.ColorSpace)
if err != nil {
return nil, err
}
r.colorspace = colorspaces
return r.colorspace, nil
}
// SetColorSpace sets `r` colorspace object to `colorspace`.
func (r *PdfPageResources) SetColorSpace(colorspace *PdfPageResourcesColorspaces) {
r.colorspace = colorspace
}
// GetContainingPdfObject returns the container of the resources object (indirect object).
func (r *PdfPageResources) GetContainingPdfObject() core.PdfObject {
return r.primitive
}
// ToPdfObject returns the PDF representation of the page resources.
func (r *PdfPageResources) ToPdfObject() core.PdfObject {
d := r.primitive
d.SetIfNotNil("ExtGState", r.ExtGState)
if r.colorspace != nil {
r.ColorSpace = r.colorspace.ToPdfObject()
}
d.SetIfNotNil("ColorSpace", r.ColorSpace)
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
}
// AddExtGState 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 core.PdfObjectName, gsDict core.PdfObject) error {
if r.ExtGState == nil {
r.ExtGState = core.MakeDict()
}
obj := r.ExtGState
dict, ok := core.TraceToDirectObject(obj).(*core.PdfObjectDictionary)
if !ok {
common.Log.Debug("ExtGState type error (got %T/%T)", obj, core.TraceToDirectObject(obj))
return core.ErrTypeError
}
dict.Set(gsName, gsDict)
return nil
}
// GetExtGState gets the ExtGState specified by keyName. Returns a bool
// indicating whether it was found or not.
func (r *PdfPageResources) GetExtGState(keyName core.PdfObjectName) (core.PdfObject, bool) {
if r.ExtGState == nil {
return nil, false
}
dict, ok := core.TraceToDirectObject(r.ExtGState).(*core.PdfObjectDictionary)
if !ok {
common.Log.Debug("ERROR: Invalid ExtGState entry - not a dict (got %T)", r.ExtGState)
return nil, false
}
if obj := dict.Get(keyName); obj != nil {
return obj, true
}
return nil, false
}
// HasExtGState checks whether a font is defined by the specified keyName.
func (r *PdfPageResources) HasExtGState(keyName core.PdfObjectName) bool {
_, has := r.GetFontByName(keyName)
return has
}
// GetShadingByName gets the shading specified by keyName. Returns nil if not existing.
// The bool flag indicated whether it was found or not.
func (r *PdfPageResources) GetShadingByName(keyName core.PdfObjectName) (*PdfShading, bool) {
if r.Shading == nil {
return nil, false
}
shadingDict, ok := core.TraceToDirectObject(r.Shading).(*core.PdfObjectDictionary)
if !ok {
common.Log.Debug("ERROR: Invalid Shading entry - not a dict (got %T)", r.Shading)
return nil, false
}
if obj := shadingDict.Get(keyName); obj != nil {
shading, err := newPdfShadingFromPdfObject(obj)
if err != nil {
common.Log.Debug("ERROR: failed to load pdf shading: %v", err)
return nil, false
}
return shading, true
}
return nil, false
}
// SetShadingByName sets a shading resource specified by keyName.
func (r *PdfPageResources) SetShadingByName(keyName core.PdfObjectName, shadingObj core.PdfObject) error {
if r.Shading == nil {
r.Shading = core.MakeDict()
}
shadingDict, has := r.Shading.(*core.PdfObjectDictionary)
if !has {
return core.ErrTypeError
}
shadingDict.Set(keyName, shadingObj)
return nil
}
// GetPatternByName gets the pattern specified by keyName. Returns nil if not existing.
// The bool flag indicated whether it was found or not.
func (r *PdfPageResources) GetPatternByName(keyName core.PdfObjectName) (*PdfPattern, bool) {
if r.Pattern == nil {
return nil, false
}
patternDict, ok := core.TraceToDirectObject(r.Pattern).(*core.PdfObjectDictionary)
if !ok {
common.Log.Debug("ERROR: Invalid Pattern entry - not a dict (got %T)", r.Pattern)
return nil, false
}
if obj := patternDict.Get(keyName); obj != nil {
pattern, err := newPdfPatternFromPdfObject(obj)
if err != nil {
common.Log.Debug("ERROR: failed to load pdf pattern: %v", err)
return nil, false
}
return pattern, true
}
return nil, false
}
// SetPatternByName sets a pattern resource specified by keyName.
func (r *PdfPageResources) SetPatternByName(keyName core.PdfObjectName, pattern core.PdfObject) error {
if r.Pattern == nil {
r.Pattern = core.MakeDict()
}
patternDict, has := r.Pattern.(*core.PdfObjectDictionary)
if !has {
return core.ErrTypeError
}
patternDict.Set(keyName, pattern)
return nil
}
// GetFontByName gets 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 core.PdfObjectName) (core.PdfObject, bool) {
if r.Font == nil {
return nil, false
}
fontDict, has := core.TraceToDirectObject(r.Font).(*core.PdfObjectDictionary)
if !has {
common.Log.Debug("ERROR: Font not a dictionary! (got %T)", core.TraceToDirectObject(r.Font))
return nil, false
}
if obj := fontDict.Get(keyName); obj != nil {
return obj, true
}
return nil, false
}
// HasFontByName checks whether a font is defined by the specified keyName.
func (r *PdfPageResources) HasFontByName(keyName core.PdfObjectName) bool {
_, has := r.GetFontByName(keyName)
return has
}
// SetFontByName sets the font specified by keyName to the given object.
func (r *PdfPageResources) SetFontByName(keyName core.PdfObjectName, obj core.PdfObject) error {
if r.Font == nil {
// Create if not existing.
r.Font = core.MakeDict()
}
fontDict, has := core.TraceToDirectObject(r.Font).(*core.PdfObjectDictionary)
if !has {
common.Log.Debug("ERROR: Font not a dictionary! (got %T)", core.TraceToDirectObject(r.Font))
return core.ErrTypeError
}
fontDict.Set(keyName, obj)
return nil
}
// GetColorspaceByName returns the colorspace with the specified name from the page resources.
func (r *PdfPageResources) GetColorspaceByName(keyName core.PdfObjectName) (PdfColorspace, bool) {
colorspace, err := r.GetColorspaces()
if err != nil {
common.Log.Debug("ERROR getting colorsprace: %v", err)
return nil, false
}
if colorspace == nil {
return nil, false
}
cs, has := colorspace.Colorspaces[string(keyName)]
if !has {
return nil, false
}
return cs, true
}
// HasColorspaceByName checks if the colorspace with the specified name exists in the page resources.
func (r *PdfPageResources) HasColorspaceByName(keyName core.PdfObjectName) bool {
colorspace, err := r.GetColorspaces()
if err != nil {
common.Log.Debug("ERROR getting colorsprace: %v", err)
return false
}
if colorspace == nil {
return false
}
_, has := colorspace.Colorspaces[string(keyName)]
return has
}
// SetColorspaceByName adds the provided colorspace to the page resources.
func (r *PdfPageResources) SetColorspaceByName(keyName core.PdfObjectName, cs PdfColorspace) error {
colorspace, err := r.GetColorspaces()
if err != nil {
common.Log.Debug("ERROR getting colorsprace: %v", err)
return err
}
if colorspace == nil {
colorspace = NewPdfPageResourcesColorspaces()
r.SetColorSpace(colorspace)
}
colorspace.Set(keyName, cs)
return nil
}
// HasXObjectByName checks if an XObject with a specified keyName is defined.
func (r *PdfPageResources) HasXObjectByName(keyName core.PdfObjectName) bool {
obj, _ := r.GetXObjectByName(keyName)
if obj != nil {
return true
}
return false
}
// GenerateXObjectName generates an unused XObject name that can be used for
// adding new XObjects. Uses format XObj1, XObj2, ...
func (r *PdfPageResources) GenerateXObjectName() core.PdfObjectName {
num := 1
for {
name := core.MakeName(fmt.Sprintf("XObj%d", num))
if !r.HasXObjectByName(*name) {
return *name
}
num++
}
// Not reached.
}
// XObjectType represents the type of an XObject.
type XObjectType int
// XObject types.
const (
XObjectTypeUndefined XObjectType = iota
XObjectTypeImage XObjectType = iota
XObjectTypeForm XObjectType = iota
XObjectTypePS XObjectType = iota
XObjectTypeUnknown XObjectType = iota
)
// GetXObjectByName returns the XObject with the specified keyName and the object type.
func (r *PdfPageResources) GetXObjectByName(keyName core.PdfObjectName) (*core.PdfObjectStream, XObjectType) {
if r.XObject == nil {
return nil, XObjectTypeUndefined
}
xresDict, has := core.TraceToDirectObject(r.XObject).(*core.PdfObjectDictionary)
if !has {
common.Log.Debug("ERROR: XObject not a dictionary! (got %T)", core.TraceToDirectObject(r.XObject))
return nil, XObjectTypeUndefined
}
if obj := xresDict.Get(keyName); obj != nil {
stream, ok := core.GetStream(obj)
if !ok {
common.Log.Debug("XObject not pointing to a stream %T", obj)
return nil, XObjectTypeUndefined
}
dict := stream.PdfObjectDictionary
name, ok := core.TraceToDirectObject(dict.Get("Subtype")).(*core.PdfObjectName)
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
}
}
// SetXObjectByName adds the XObject from the passed in stream to the page resources.
// The added XObject is identified by the specified name.
func (r *PdfPageResources) SetXObjectByName(keyName core.PdfObjectName, stream *core.PdfObjectStream) error {
if r.XObject == nil {
r.XObject = core.MakeDict()
}
obj := core.TraceToDirectObject(r.XObject)
xresDict, has := obj.(*core.PdfObjectDictionary)
if !has {
common.Log.Debug("Invalid XObject, got %T/%T", r.XObject, obj)
return errors.New("type check error")
}
xresDict.Set(keyName, stream)
return nil
}
// GetXObjectImageByName returns the XObjectImage with the specified name from the
// page resources, if it exists.
func (r *PdfPageResources) GetXObjectImageByName(keyName core.PdfObjectName) (*XObjectImage, error) {
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
}
// SetXObjectImageByName adds the provided XObjectImage to the page resources.
// The added XObjectImage is identified by the specified name.
func (r *PdfPageResources) SetXObjectImageByName(keyName core.PdfObjectName, ximg *XObjectImage) error {
stream := ximg.ToPdfObject().(*core.PdfObjectStream)
err := r.SetXObjectByName(keyName, stream)
return err
}
// GetXObjectFormByName returns the XObjectForm with the specified name from the
// page resources, if it exists.
func (r *PdfPageResources) GetXObjectFormByName(keyName core.PdfObjectName) (*XObjectForm, error) {
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
}
// SetXObjectFormByName adds the provided XObjectForm to the page resources.
// The added XObjectForm is identified by the specified name.
func (r *PdfPageResources) SetXObjectFormByName(keyName core.PdfObjectName, xform *XObjectForm) error {
stream := xform.ToPdfObject().(*core.PdfObjectStream)
err := r.SetXObjectByName(keyName, stream)
return err
}