mirror of
https://github.com/unidoc/unipdf.git
synced 2025-04-26 13:48:55 +08:00
483 lines
14 KiB
Go
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
|
|
}
|