unipdf/pdf/model/page.go

946 lines
24 KiB
Go
Raw Normal View History

/*
* This file is subject to the terms and conditions defined in
2016-07-29 17:23:39 +00:00
* file 'LICENSE.md', which is part of this source code package.
*/
//
// Allow higher level manipulation of PDF files and pages.
// This can be continuously expanded to support more and more features.
// Generic handling can be done by defining elements as PdfObject which
// can later be replaced and fully defined.
//
package model
import (
"bytes"
"errors"
2016-12-13 21:42:27 +00:00
"fmt"
"strings"
"github.com/unidoc/unidoc/common"
2019-02-09 12:36:12 +02:00
"github.com/unidoc/unidoc/pdf/core"
)
// PdfPage represents a page in a PDF document. (7.7.3.3 - Table 30).
type PdfPage struct {
2019-02-09 12:36:12 +02:00
Parent core.PdfObject
LastModified *PdfDate
Resources *PdfPageResources
CropBox *PdfRectangle
MediaBox *PdfRectangle
BleedBox *PdfRectangle
TrimBox *PdfRectangle
ArtBox *PdfRectangle
2019-02-09 12:36:12 +02:00
BoxColorInfo core.PdfObject
Contents core.PdfObject
Rotate *int64
2019-02-09 12:36:12 +02:00
Group core.PdfObject
Thumb core.PdfObject
B core.PdfObject
Dur core.PdfObject
Trans core.PdfObject
AA core.PdfObject
Metadata core.PdfObject
PieceInfo core.PdfObject
StructParents core.PdfObject
ID core.PdfObject
PZ core.PdfObject
SeparationInfo core.PdfObject
Tabs core.PdfObject
TemplateInstantiated core.PdfObject
PresSteps core.PdfObject
UserUnit core.PdfObject
VP core.PdfObject
2016-09-07 17:56:45 +00:00
Annotations []*PdfAnnotation
// Primitive container.
2019-02-09 12:36:12 +02:00
pageDict *core.PdfObjectDictionary
primitive *core.PdfIndirectObject
}
// NewPdfPage returns a new PDF page.
func NewPdfPage() *PdfPage {
page := PdfPage{}
2019-02-09 12:36:12 +02:00
page.pageDict = core.MakeDict()
2019-02-09 12:36:12 +02:00
container := core.PdfIndirectObject{}
container.PdfObject = page.pageDict
page.primitive = &container
return &page
}
2019-02-09 12:36:12 +02:00
func (p *PdfPage) setContainer(container *core.PdfIndirectObject) {
container.PdfObject = p.pageDict
p.primitive = container
}
// Duplicate creates a duplicate page based on the current one and returns it.
func (p *PdfPage) Duplicate() *PdfPage {
var dup PdfPage
dup = *p
2019-02-09 12:36:12 +02:00
dup.pageDict = core.MakeDict()
dup.primitive = core.MakeIndirectObject(dup.pageDict)
return &dup
}
// Build a PdfPage based on the underlying dictionary.
// Used in loading existing PDF files.
// Note that a new container is created (indirect object).
2019-02-09 12:36:12 +02:00
func (r *PdfReader) newPdfPageFromDict(p *core.PdfObjectDictionary) (*PdfPage, error) {
page := NewPdfPage()
2018-12-09 21:37:27 +02:00
page.pageDict = p // TODO
d := *p
2019-02-09 12:36:12 +02:00
pType, ok := d.Get("Type").(*core.PdfObjectName)
if !ok {
2018-12-09 18:45:53 +02:00
return nil, errors.New("missing/invalid Page dictionary Type")
}
if *pType != "Page" {
2018-12-08 19:16:52 +02:00
return nil, errors.New("page dictionary Type != Page")
}
if obj := d.Get("Parent"); obj != nil {
page.Parent = obj
}
if obj := d.Get("LastModified"); obj != nil {
var err error
obj, err = r.traceToObject(obj)
if err != nil {
return nil, err
}
2019-02-09 12:36:12 +02:00
strObj, ok := core.TraceToDirectObject(obj).(*core.PdfObjectString)
if !ok {
2018-12-08 19:16:52 +02:00
return nil, errors.New("page dictionary LastModified != string")
}
lastmod, err := NewPdfDate(strObj.Str())
if err != nil {
return nil, err
}
page.LastModified = &lastmod
}
if obj := d.Get("Resources"); obj != nil {
var err error
obj, err = r.traceToObject(obj)
2016-08-16 09:36:24 +00:00
if err != nil {
return nil, err
}
2019-02-09 12:36:12 +02:00
dict, ok := core.TraceToDirectObject(obj).(*core.PdfObjectDictionary)
2016-07-25 14:06:37 +00:00
if !ok {
2018-12-08 19:16:52 +02:00
return nil, fmt.Errorf("invalid resource dictionary (%T)", obj)
2016-07-25 14:06:37 +00:00
}
page.Resources, err = NewPdfPageResourcesFromDict(dict)
if err != nil {
return nil, err
}
} else {
// If Resources not explicitly defined, look up the tree (Parent objects) using
// the getResources() function. Resources should always be accessible.
resources, err := page.getResources()
if err != nil {
return nil, err
}
if resources == nil {
resources = NewPdfPageResources()
}
page.Resources = resources
}
if obj := d.Get("MediaBox"); obj != nil {
var err error
obj, err = r.traceToObject(obj)
if err != nil {
return nil, err
}
2019-02-09 12:36:12 +02:00
boxArr, ok := core.TraceToDirectObject(obj).(*core.PdfObjectArray)
if !ok {
2018-12-08 19:16:52 +02:00
return nil, errors.New("page MediaBox not an array")
}
page.MediaBox, err = NewPdfRectangle(*boxArr)
if err != nil {
return nil, err
}
}
if obj := d.Get("CropBox"); obj != nil {
var err error
obj, err = r.traceToObject(obj)
if err != nil {
return nil, err
}
2019-02-09 12:36:12 +02:00
boxArr, ok := core.TraceToDirectObject(obj).(*core.PdfObjectArray)
if !ok {
2018-12-08 19:16:52 +02:00
return nil, errors.New("page CropBox not an array")
}
page.CropBox, err = NewPdfRectangle(*boxArr)
if err != nil {
return nil, err
}
}
if obj := d.Get("BleedBox"); obj != nil {
var err error
obj, err = r.traceToObject(obj)
if err != nil {
return nil, err
}
2019-02-09 12:36:12 +02:00
boxArr, ok := core.TraceToDirectObject(obj).(*core.PdfObjectArray)
if !ok {
2018-12-08 19:16:52 +02:00
return nil, errors.New("page BleedBox not an array")
}
page.BleedBox, err = NewPdfRectangle(*boxArr)
if err != nil {
return nil, err
}
}
if obj := d.Get("TrimBox"); obj != nil {
var err error
obj, err = r.traceToObject(obj)
if err != nil {
return nil, err
}
2019-02-09 12:36:12 +02:00
boxArr, ok := core.TraceToDirectObject(obj).(*core.PdfObjectArray)
if !ok {
2018-12-08 19:16:52 +02:00
return nil, errors.New("page TrimBox not an array")
}
page.TrimBox, err = NewPdfRectangle(*boxArr)
if err != nil {
return nil, err
}
}
if obj := d.Get("ArtBox"); obj != nil {
var err error
obj, err = r.traceToObject(obj)
if err != nil {
return nil, err
}
2019-02-09 12:36:12 +02:00
boxArr, ok := core.TraceToDirectObject(obj).(*core.PdfObjectArray)
if !ok {
2018-12-08 19:16:52 +02:00
return nil, errors.New("page ArtBox not an array")
}
page.ArtBox, err = NewPdfRectangle(*boxArr)
if err != nil {
return nil, err
}
}
if obj := d.Get("BoxColorInfo"); obj != nil {
page.BoxColorInfo = obj
}
if obj := d.Get("Contents"); obj != nil {
page.Contents = obj
}
if obj := d.Get("Rotate"); obj != nil {
var err error
obj, err = r.traceToObject(obj)
if err != nil {
return nil, err
}
2019-02-09 12:36:12 +02:00
iObj, ok := core.TraceToDirectObject(obj).(*core.PdfObjectInteger)
if !ok {
2018-12-08 19:16:52 +02:00
return nil, errors.New("invalid Page Rotate object")
}
iVal := int64(*iObj)
page.Rotate = &iVal
}
if obj := d.Get("Group"); obj != nil {
page.Group = obj
}
if obj := d.Get("Thumb"); obj != nil {
page.Thumb = obj
}
if obj := d.Get("B"); obj != nil {
page.B = obj
}
if obj := d.Get("Dur"); obj != nil {
page.Dur = obj
}
if obj := d.Get("Trans"); obj != nil {
page.Trans = obj
}
//if obj := d.Get("Annots"); obj != nil {
// page.Annots = obj
//}
if obj := d.Get("AA"); obj != nil {
page.AA = obj
}
if obj := d.Get("Metadata"); obj != nil {
page.Metadata = obj
}
if obj := d.Get("PieceInfo"); obj != nil {
page.PieceInfo = obj
}
if obj := d.Get("StructParents"); obj != nil {
page.StructParents = obj
}
if obj := d.Get("ID"); obj != nil {
page.ID = obj
}
if obj := d.Get("PZ"); obj != nil {
page.PZ = obj
}
if obj := d.Get("SeparationInfo"); obj != nil {
page.SeparationInfo = obj
}
if obj := d.Get("Tabs"); obj != nil {
page.Tabs = obj
}
if obj := d.Get("TemplateInstantiated"); obj != nil {
page.TemplateInstantiated = obj
}
if obj := d.Get("PresSteps"); obj != nil {
page.PresSteps = obj
}
if obj := d.Get("UserUnit"); obj != nil {
page.UserUnit = obj
}
if obj := d.Get("VP"); obj != nil {
page.VP = obj
}
2016-09-07 17:56:45 +00:00
var err error
page.Annotations, err = r.LoadAnnotations(&d)
2016-09-07 17:56:45 +00:00
if err != nil {
return nil, err
}
return page, nil
}
// LoadAnnotations loads and returns the PDF annotations from the input dictionary.
2019-02-09 12:36:12 +02:00
func (r *PdfReader) LoadAnnotations(d *core.PdfObjectDictionary) ([]*PdfAnnotation, error) {
annotsObj := d.Get("Annots")
if annotsObj == nil {
2016-09-07 17:56:45 +00:00
return nil, nil
}
var err error
annotsObj, err = r.traceToObject(annotsObj)
2016-09-07 17:56:45 +00:00
if err != nil {
return nil, err
}
2019-02-09 12:36:12 +02:00
annotsArr, ok := core.TraceToDirectObject(annotsObj).(*core.PdfObjectArray)
2016-09-07 17:56:45 +00:00
if !ok {
return nil, fmt.Errorf("Annots not an array")
}
var annotations []*PdfAnnotation
for _, obj := range annotsArr.Elements() {
obj, err = r.traceToObject(obj)
2016-09-07 17:56:45 +00:00
if err != nil {
return nil, err
}
// Technically all annotation dictionaries should be inside indirect objects.
// In reality, sometimes the annotation dictionary is inline within the Annots array.
2019-02-09 12:36:12 +02:00
if _, isNull := obj.(*core.PdfObjectNull); isNull {
// Can safely ignore.
continue
2016-09-07 17:56:45 +00:00
}
2019-02-09 12:36:12 +02:00
annotDict, isDict := obj.(*core.PdfObjectDictionary)
indirectObj, isIndirect := obj.(*core.PdfIndirectObject)
if isDict {
// Create a container; indirect object; around the dictionary.
2019-02-09 12:36:12 +02:00
indirectObj = &core.PdfIndirectObject{}
indirectObj.PdfObject = annotDict
} else {
if !isIndirect {
2018-12-08 19:16:52 +02:00
return nil, fmt.Errorf("annotation not in an indirect object")
}
}
annot, err := r.newPdfAnnotationFromIndirectObject(indirectObj)
2016-09-07 17:56:45 +00:00
if err != nil {
return nil, err
}
if annot != nil {
annotations = append(annotations, annot)
}
2016-09-07 17:56:45 +00:00
}
return annotations, nil
}
// GetMediaBox gets the inheritable media box value, either from the page
// or a higher up page/pages struct.
func (p *PdfPage) GetMediaBox() (*PdfRectangle, error) {
if p.MediaBox != nil {
return p.MediaBox, nil
}
node := p.Parent
for node != nil {
2019-02-09 12:36:12 +02:00
dictObj, ok := node.(*core.PdfIndirectObject)
if !ok {
2018-12-08 19:16:52 +02:00
return nil, errors.New("invalid parent object")
}
2019-02-09 12:36:12 +02:00
dict, ok := dictObj.PdfObject.(*core.PdfObjectDictionary)
if !ok {
2018-12-08 19:16:52 +02:00
return nil, errors.New("invalid parent objects dictionary")
}
if obj := dict.Get("MediaBox"); obj != nil {
2019-02-09 12:36:12 +02:00
arr, ok := obj.(*core.PdfObjectArray)
if !ok {
2018-12-08 19:16:52 +02:00
return nil, errors.New("invalid media box")
}
rect, err := NewPdfRectangle(*arr)
if err != nil {
return nil, err
}
return rect, nil
}
node = dict.Get("Parent")
}
2018-12-08 19:16:52 +02:00
return nil, errors.New("media box not defined")
}
// Get the inheritable resources, either from the page or or a higher up page/pages struct.
func (p *PdfPage) getResources() (*PdfPageResources, error) {
if p.Resources != nil {
return p.Resources, nil
}
node := p.Parent
for node != nil {
2019-02-09 12:36:12 +02:00
dictObj, ok := node.(*core.PdfIndirectObject)
if !ok {
2018-12-08 19:16:52 +02:00
return nil, errors.New("invalid parent object")
}
2019-02-09 12:36:12 +02:00
dict, ok := dictObj.PdfObject.(*core.PdfObjectDictionary)
if !ok {
2018-12-08 19:16:52 +02:00
return nil, errors.New("invalid parent objects dictionary")
}
if obj := dict.Get("Resources"); obj != nil {
2019-02-09 12:36:12 +02:00
prDict, ok := core.TraceToDirectObject(obj).(*core.PdfObjectDictionary)
if !ok {
return nil, errors.New("invalid resource dict")
}
resources, err := NewPdfPageResourcesFromDict(prDict)
if err != nil {
return nil, err
}
return resources, nil
}
// Keep moving up the tree...
node = dict.Get("Parent")
}
// No resources defined...
return nil, nil
}
// GetPageDict converts the Page to a PDF object dictionary.
2019-02-09 12:36:12 +02:00
func (p *PdfPage) GetPageDict() *core.PdfObjectDictionary {
d := p.pageDict
d.Clear()
2019-02-09 12:36:12 +02:00
d.Set("Type", core.MakeName("Page"))
d.Set("Parent", p.Parent)
if p.LastModified != nil {
d.Set("LastModified", p.LastModified.ToPdfObject())
}
if p.Resources != nil {
d.Set("Resources", p.Resources.ToPdfObject())
}
if p.CropBox != nil {
d.Set("CropBox", p.CropBox.ToPdfObject())
}
if p.MediaBox != nil {
d.Set("MediaBox", p.MediaBox.ToPdfObject())
}
if p.BleedBox != nil {
d.Set("BleedBox", p.BleedBox.ToPdfObject())
}
if p.TrimBox != nil {
d.Set("TrimBox", p.TrimBox.ToPdfObject())
}
if p.ArtBox != nil {
d.Set("ArtBox", p.ArtBox.ToPdfObject())
}
d.SetIfNotNil("BoxColorInfo", p.BoxColorInfo)
d.SetIfNotNil("Contents", p.Contents)
if p.Rotate != nil {
2019-02-09 12:36:12 +02:00
d.Set("Rotate", core.MakeInteger(*p.Rotate))
}
d.SetIfNotNil("Group", p.Group)
d.SetIfNotNil("Thumb", p.Thumb)
d.SetIfNotNil("B", p.B)
d.SetIfNotNil("Dur", p.Dur)
d.SetIfNotNil("Trans", p.Trans)
d.SetIfNotNil("AA", p.AA)
d.SetIfNotNil("Metadata", p.Metadata)
d.SetIfNotNil("PieceInfo", p.PieceInfo)
d.SetIfNotNil("StructParents", p.StructParents)
d.SetIfNotNil("ID", p.ID)
d.SetIfNotNil("PZ", p.PZ)
d.SetIfNotNil("SeparationInfo", p.SeparationInfo)
d.SetIfNotNil("Tabs", p.Tabs)
d.SetIfNotNil("TemplateInstantiated", p.TemplateInstantiated)
d.SetIfNotNil("PresSteps", p.PresSteps)
d.SetIfNotNil("UserUnit", p.UserUnit)
d.SetIfNotNil("VP", p.VP)
if p.Annotations != nil {
2019-02-09 12:36:12 +02:00
arr := core.MakeArray()
for _, annot := range p.Annotations {
if subannot := annot.GetContext(); subannot != nil {
arr.Append(subannot.ToPdfObject())
} else {
// Generic annotation dict (without subtype).
arr.Append(annot.ToPdfObject())
}
2016-09-07 17:56:45 +00:00
}
d.Set("Annots", arr)
2016-09-07 17:56:45 +00:00
}
return d
}
2016-07-25 14:06:37 +00:00
// GetPageAsIndirectObject returns the page as a dictionary within an PdfIndirectObject.
2019-02-09 12:36:12 +02:00
func (p *PdfPage) GetPageAsIndirectObject() *core.PdfIndirectObject {
return p.primitive
2016-07-25 14:06:37 +00:00
}
// GetContainingPdfObject returns the page as a dictionary within an PdfIndirectObject.
2019-02-09 12:36:12 +02:00
func (p *PdfPage) GetContainingPdfObject() core.PdfObject {
return p.primitive
}
// ToPdfObject converts the PdfPage to a dictionary within an indirect object container.
2019-02-09 12:36:12 +02:00
func (p *PdfPage) ToPdfObject() core.PdfObject {
container := p.primitive
p.GetPageDict() // update.
return container
}
// AddImageResource adds an image to the XObject resources.
2019-02-09 12:36:12 +02:00
func (p *PdfPage) AddImageResource(name core.PdfObjectName, ximg *XObjectImage) error {
var xresDict *core.PdfObjectDictionary
if p.Resources.XObject == nil {
2019-02-09 12:36:12 +02:00
xresDict = core.MakeDict()
p.Resources.XObject = xresDict
2016-07-25 14:06:37 +00:00
} else {
var ok bool
2019-02-09 12:36:12 +02:00
xresDict, ok = (p.Resources.XObject).(*core.PdfObjectDictionary)
2016-07-25 14:06:37 +00:00
if !ok {
2018-12-08 19:16:52 +02:00
return errors.New("invalid xres dict type")
2016-07-25 14:06:37 +00:00
}
}
// Make a stream object container.
xresDict.Set(name, ximg.ToPdfObject())
2016-07-25 14:06:37 +00:00
return nil
}
// HasXObjectByName checks if has XObject resource by name.
2019-02-09 12:36:12 +02:00
func (p *PdfPage) HasXObjectByName(name core.PdfObjectName) bool {
xresDict, has := p.Resources.XObject.(*core.PdfObjectDictionary)
if !has {
return false
}
if obj := xresDict.Get(name); obj != nil {
return true
}
return false
}
// GetXObjectByName gets XObject by name.
2019-02-09 12:36:12 +02:00
func (p *PdfPage) GetXObjectByName(name core.PdfObjectName) (core.PdfObject, bool) {
xresDict, has := p.Resources.XObject.(*core.PdfObjectDictionary)
if !has {
return nil, false
}
if obj := xresDict.Get(name); obj != nil {
return obj, true
}
return nil, false
}
// HasFontByName checks if has font resource by name.
2019-02-09 12:36:12 +02:00
func (p *PdfPage) HasFontByName(name core.PdfObjectName) bool {
fontDict, has := p.Resources.Font.(*core.PdfObjectDictionary)
if !has {
return false
}
if obj := fontDict.Get(name); obj != nil {
return true
}
return false
}
// HasExtGState checks if ExtGState name is available.
2019-02-09 12:36:12 +02:00
func (p *PdfPage) HasExtGState(name core.PdfObjectName) bool {
if p.Resources == nil {
2017-05-28 15:14:32 +00:00
return false
}
if p.Resources.ExtGState == nil {
2017-05-28 15:14:32 +00:00
return false
}
2019-02-09 12:36:12 +02:00
egsDict, ok := core.TraceToDirectObject(p.Resources.ExtGState).(*core.PdfObjectDictionary)
if !ok {
2019-02-09 12:36:12 +02:00
common.Log.Debug("Expected ExtGState dictionary is not a dictionary: %v", core.TraceToDirectObject(p.Resources.ExtGState))
return false
}
// Update the dictionary.
obj := egsDict.Get(name)
has := obj != nil
return has
}
// AddExtGState adds a graphics state to the XObject resources.
2019-02-09 12:36:12 +02:00
func (p *PdfPage) AddExtGState(name core.PdfObjectName, egs *core.PdfObjectDictionary) error {
if p.Resources == nil {
//p.Resources = &PdfPageResources{}
p.Resources = NewPdfPageResources()
2016-07-25 14:06:37 +00:00
}
if p.Resources.ExtGState == nil {
2019-02-09 12:36:12 +02:00
p.Resources.ExtGState = core.MakeDict()
2016-07-25 14:06:37 +00:00
}
2019-02-09 12:36:12 +02:00
egsDict, ok := core.TraceToDirectObject(p.Resources.ExtGState).(*core.PdfObjectDictionary)
if !ok {
2019-02-09 12:36:12 +02:00
common.Log.Debug("Expected ExtGState dictionary is not a dictionary: %v", core.TraceToDirectObject(p.Resources.ExtGState))
2018-12-08 19:16:52 +02:00
return errors.New("type check error")
}
egsDict.Set(name, egs)
return nil
2016-07-25 14:06:37 +00:00
}
// AddFont adds a font dictionary to the Font resources.
2019-02-09 12:36:12 +02:00
func (p *PdfPage) AddFont(name core.PdfObjectName, font core.PdfObject) error {
if p.Resources == nil {
p.Resources = NewPdfPageResources()
2016-07-30 00:27:21 +00:00
}
if p.Resources.Font == nil {
2019-02-09 12:36:12 +02:00
p.Resources.Font = core.MakeDict()
2016-07-30 00:27:21 +00:00
}
2019-02-09 12:36:12 +02:00
fontDict, ok := core.TraceToDirectObject(p.Resources.Font).(*core.PdfObjectDictionary)
if !ok {
2019-02-09 12:36:12 +02:00
common.Log.Debug("Expected font dictionary is not a dictionary: %v", core.TraceToDirectObject(p.Resources.Font))
2018-12-08 19:16:52 +02:00
return errors.New("type check error")
}
// Update the dictionary.
fontDict.Set(name, font)
return nil
2016-07-30 00:27:21 +00:00
}
// WatermarkImageOptions contains options for configuring the watermark process.
2016-07-30 00:27:21 +00:00
type WatermarkImageOptions struct {
Alpha float64
FitToWidth bool
2016-07-30 00:27:21 +00:00
PreserveAspectRatio bool
}
// AddWatermarkImage adds a watermark to the page.
func (p *PdfPage) AddWatermarkImage(ximg *XObjectImage, opt WatermarkImageOptions) error {
// Page dimensions.
bbox, err := p.GetMediaBox()
2016-07-30 00:27:21 +00:00
if err != nil {
return err
}
pWidth := bbox.Urx - bbox.Llx
pHeight := bbox.Ury - bbox.Lly
wWidth := float64(*ximg.Width)
xOffset := (float64(pWidth) - float64(wWidth)) / 2
if opt.FitToWidth {
wWidth = pWidth
xOffset = 0
}
2016-07-30 00:27:21 +00:00
wHeight := pHeight
yOffset := float64(0)
if opt.PreserveAspectRatio {
wHeight = wWidth * float64(*ximg.Height) / float64(*ximg.Width)
yOffset = (pHeight - wHeight) / 2
}
if p.Resources == nil {
p.Resources = NewPdfPageResources()
}
// Find available image name for this page.
i := 0
2019-02-09 12:36:12 +02:00
imgName := core.PdfObjectName(fmt.Sprintf("Imw%d", i))
for p.Resources.HasXObjectByName(imgName) {
i++
2019-02-09 12:36:12 +02:00
imgName = core.PdfObjectName(fmt.Sprintf("Imw%d", i))
}
err = p.AddImageResource(imgName, ximg)
if err != nil {
return err
}
2016-07-30 00:27:21 +00:00
i = 0
2019-02-09 12:36:12 +02:00
gsName := core.PdfObjectName(fmt.Sprintf("GS%d", i))
for p.HasExtGState(gsName) {
i++
2019-02-09 12:36:12 +02:00
gsName = core.PdfObjectName(fmt.Sprintf("GS%d", i))
}
2019-02-09 12:36:12 +02:00
gs0 := core.MakeDict()
gs0.Set("BM", core.MakeName("Normal"))
gs0.Set("CA", core.MakeFloat(opt.Alpha))
gs0.Set("ca", core.MakeFloat(opt.Alpha))
err = p.AddExtGState(gsName, gs0)
if err != nil {
return err
}
2016-07-30 00:27:21 +00:00
contentStr := fmt.Sprintf("q\n"+
"/%s gs\n"+
"%.0f 0 0 %.0f %.4f %.4f cm\n"+
2016-07-30 00:27:21 +00:00
"/%s Do\n"+
"Q", gsName, wWidth, wHeight, xOffset, yOffset, imgName)
p.AddContentStreamByString(contentStr)
2016-07-30 00:27:21 +00:00
return nil
}
2019-02-09 12:36:12 +02:00
// AddContentStreamByString adds content stream by string. Puts the content
// string into a stream object and points the content stream towards it.
func (p *PdfPage) AddContentStreamByString(contentStr string) error {
2019-02-09 12:36:12 +02:00
stream, err := core.MakeStream([]byte(contentStr), core.NewFlateEncoder())
if err != nil {
return err
}
2016-07-25 14:06:37 +00:00
if p.Contents == nil {
2016-07-30 00:27:21 +00:00
// If not set, place it directly.
p.Contents = stream
2019-02-09 12:36:12 +02:00
} else if contArray, isArray := core.TraceToDirectObject(p.Contents).(*core.PdfObjectArray); isArray {
2016-07-30 00:27:21 +00:00
// If an array of content streams, append it.
contArray.Append(stream)
2016-07-30 00:27:21 +00:00
} else {
// Only 1 element in place. Wrap inside a new array and add the new one.
2019-02-09 12:36:12 +02:00
contArray := core.MakeArray(p.Contents, stream)
p.Contents = contArray
2016-07-30 00:27:21 +00:00
}
return nil
}
// AppendContentStream adds content stream by string. Appends to the last
// contentstream instance if many.
func (p *PdfPage) AppendContentStream(contentStr string) error {
cstreams, err := p.GetContentStreams()
if err != nil {
return err
}
if len(cstreams) == 0 {
cstreams = []string{contentStr}
2019-02-09 12:36:12 +02:00
return p.SetContentStreams(cstreams, core.NewFlateEncoder())
}
var buf bytes.Buffer
buf.WriteString(cstreams[len(cstreams)-1])
buf.WriteString("\n")
buf.WriteString(contentStr)
cstreams[len(cstreams)-1] = buf.String()
2019-02-09 12:36:12 +02:00
return p.SetContentStreams(cstreams, core.NewFlateEncoder())
}
2019-02-09 12:36:12 +02:00
// SetContentStreams sets the content streams based on a string array. Will make
// 1 object stream for each string and reference from the page Contents.
// Each stream will be encoded using the encoding specified by the StreamEncoder,
// if empty, will use identity encoding (raw data).
func (p *PdfPage) SetContentStreams(cStreams []string, encoder core.StreamEncoder) error {
if len(cStreams) == 0 {
p.Contents = nil
return nil
}
// If encoding is not set, use default raw encoder.
if encoder == nil {
2019-02-09 12:36:12 +02:00
encoder = core.NewRawEncoder()
}
2019-02-09 12:36:12 +02:00
var streamObjs []*core.PdfObjectStream
for _, cStream := range cStreams {
2019-02-09 12:36:12 +02:00
stream := &core.PdfObjectStream{}
// Make a new stream dict based on the encoding parameters.
sDict := encoder.MakeStreamDict()
encoded, err := encoder.EncodeBytes([]byte(cStream))
if err != nil {
return err
}
2016-07-30 00:27:21 +00:00
2019-02-09 12:36:12 +02:00
sDict.Set("Length", core.MakeInteger(int64(len(encoded))))
stream.PdfObjectDictionary = sDict
stream.Stream = []byte(encoded)
streamObjs = append(streamObjs, stream)
}
// Set the page contents.
// Point directly to the object stream if only one, or embed in an array.
if len(streamObjs) == 1 {
p.Contents = streamObjs[0]
} else {
2019-02-09 12:36:12 +02:00
contArray := core.MakeArray()
for _, streamObj := range streamObjs {
contArray.Append(streamObj)
}
p.Contents = contArray
}
return nil
2016-07-25 14:06:37 +00:00
}
2019-02-09 12:36:12 +02:00
func getContentStreamAsString(cstreamObj core.PdfObject) (string, error) {
if cstream, ok := core.TraceToDirectObject(cstreamObj).(*core.PdfObjectString); ok {
return cstream.Str(), nil
2016-08-22 08:46:18 +00:00
}
2019-02-09 12:36:12 +02:00
if cstream, ok := core.TraceToDirectObject(cstreamObj).(*core.PdfObjectStream); ok {
buf, err := core.DecodeStream(cstream)
2016-08-22 08:46:18 +00:00
if err != nil {
return "", err
}
return string(buf), nil
}
2019-02-09 12:36:12 +02:00
return "", fmt.Errorf("invalid content stream object holder (%T)", core.TraceToDirectObject(cstreamObj))
2016-08-22 08:46:18 +00:00
}
// GetContentStreams returns the content stream as an array of strings.
func (p *PdfPage) GetContentStreams() ([]string, error) {
if p.Contents == nil {
2016-08-22 08:46:18 +00:00
return nil, nil
}
2019-02-09 12:36:12 +02:00
contents := core.TraceToDirectObject(p.Contents)
var cStreamObjs []core.PdfObject
if contArray, ok := contents.(*core.PdfObjectArray); ok {
cStreamObjs = contArray.Elements()
2016-08-22 08:46:18 +00:00
} else {
cStreamObjs = []core.PdfObject{contents}
}
var cStreams []string
for _, cStreamObj := range cStreamObjs {
cStreamStr, err := getContentStreamAsString(cStreamObj)
2016-08-22 08:46:18 +00:00
if err != nil {
return nil, err
}
cStreams = append(cStreams, cStreamStr)
2016-08-22 08:46:18 +00:00
}
return cStreams, nil
2016-08-22 08:46:18 +00:00
}
// GetAllContentStreams gets all the content streams for a page as one string.
func (p *PdfPage) GetAllContentStreams() (string, error) {
cstreams, err := p.GetContentStreams()
if err != nil {
return "", err
}
return strings.Join(cstreams, " "), nil
}
// PdfPageResourcesColorspaces contains the colorspace in the PdfPageResources.
// Needs to have matching name and colorspace map entry. The Names define the order.
type PdfPageResourcesColorspaces struct {
Names []string
Colorspaces map[string]PdfColorspace
2019-02-09 12:36:12 +02:00
container *core.PdfIndirectObject
}
// NewPdfPageResourcesColorspaces returns a new PdfPageResourcesColorspaces object.
func NewPdfPageResourcesColorspaces() *PdfPageResourcesColorspaces {
colorspaces := &PdfPageResourcesColorspaces{}
colorspaces.Names = []string{}
colorspaces.Colorspaces = map[string]PdfColorspace{}
2019-02-09 12:36:12 +02:00
colorspaces.container = &core.PdfIndirectObject{}
return colorspaces
}
2019-02-09 12:36:12 +02:00
// Set sets the colorspace corresponding to key. Add to Names if not set.
func (rcs *PdfPageResourcesColorspaces) Set(key core.PdfObjectName, val PdfColorspace) {
if _, has := rcs.Colorspaces[string(key)]; !has {
rcs.Names = append(rcs.Names, string(key))
}
rcs.Colorspaces[string(key)] = val
}
2019-02-09 12:36:12 +02:00
func newPdfPageResourcesColorspacesFromPdfObject(obj core.PdfObject) (*PdfPageResourcesColorspaces, error) {
colorspaces := &PdfPageResourcesColorspaces{}
2019-02-09 12:36:12 +02:00
if indObj, isIndirect := obj.(*core.PdfIndirectObject); isIndirect {
colorspaces.container = indObj
obj = indObj.PdfObject
}
2019-02-09 12:36:12 +02:00
dict, ok := obj.(*core.PdfObjectDictionary)
if !ok {
return nil, errors.New("CS attribute type error")
}
colorspaces.Names = []string{}
colorspaces.Colorspaces = map[string]PdfColorspace{}
for _, csName := range dict.Keys() {
csObj := dict.Get(csName)
colorspaces.Names = append(colorspaces.Names, string(csName))
cs, err := NewPdfColorspaceFromPdfObject(csObj)
if err != nil {
return nil, err
}
colorspaces.Colorspaces[string(csName)] = cs
}
return colorspaces, nil
}
// ToPdfObject returns the PDF representation of the colorspace.
2019-02-09 12:36:12 +02:00
func (rcs *PdfPageResourcesColorspaces) ToPdfObject() core.PdfObject {
dict := core.MakeDict()
for _, csName := range rcs.Names {
2019-02-09 12:36:12 +02:00
dict.Set(core.PdfObjectName(csName), rcs.Colorspaces[csName].ToPdfObject())
}
if rcs.container != nil {
rcs.container.PdfObject = dict
return rcs.container
}
return dict
}