2016-07-19 23:16:25 +00:00
|
|
|
/*
|
|
|
|
* 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.
|
2016-07-19 23:16:25 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
//
|
|
|
|
// Allow higher level manipulation of PDF files and pages.
|
|
|
|
// This can be continously 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.
|
|
|
|
//
|
|
|
|
|
2016-09-08 17:53:45 +00:00
|
|
|
package model
|
2016-07-19 23:16:25 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"regexp"
|
|
|
|
"strconv"
|
2016-08-19 11:34:55 +00:00
|
|
|
|
|
|
|
"github.com/unidoc/unidoc/common"
|
2016-09-08 17:53:45 +00:00
|
|
|
. "github.com/unidoc/unidoc/pdf/core"
|
2016-07-19 23:16:25 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type PdfRectangle struct {
|
|
|
|
Llx float64 // Lower left corner (ll).
|
|
|
|
Lly float64
|
|
|
|
Urx float64 // Upper right corner (ur).
|
|
|
|
Ury float64
|
|
|
|
}
|
|
|
|
|
|
|
|
func getNumberAsFloat(obj PdfObject) (float64, error) {
|
|
|
|
if fObj, ok := obj.(*PdfObjectFloat); ok {
|
|
|
|
return float64(*fObj), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if iObj, ok := obj.(*PdfObjectInteger); ok {
|
|
|
|
return float64(*iObj), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0, errors.New("Not a number")
|
|
|
|
}
|
|
|
|
|
2016-08-19 11:34:55 +00:00
|
|
|
// Cases where expecting an integer, but some implementations actually
|
|
|
|
// store the number in a floating point format.
|
|
|
|
func getNumberAsInt64(obj PdfObject) (int64, error) {
|
|
|
|
if iObj, ok := obj.(*PdfObjectInteger); ok {
|
|
|
|
return int64(*iObj), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if fObj, ok := obj.(*PdfObjectFloat); ok {
|
|
|
|
common.Log.Debug("Number expected as integer was stored as float (type casting used)")
|
|
|
|
return int64(*fObj), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0, errors.New("Not a number")
|
|
|
|
}
|
|
|
|
|
2016-08-15 01:31:35 +00:00
|
|
|
func getNumberAsFloatOrNull(obj PdfObject) (*float64, error) {
|
|
|
|
if fObj, ok := obj.(*PdfObjectFloat); ok {
|
|
|
|
num := float64(*fObj)
|
|
|
|
return &num, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if iObj, ok := obj.(*PdfObjectInteger); ok {
|
|
|
|
num := float64(*iObj)
|
|
|
|
return &num, nil
|
|
|
|
}
|
|
|
|
if _, ok := obj.(*PdfObjectNull); ok {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, errors.New("Not a number")
|
|
|
|
}
|
|
|
|
|
2016-07-19 23:16:25 +00:00
|
|
|
// Create a PDF rectangle object based on an input array of 4 integers.
|
|
|
|
// Defining the lower left (LL) and upper right (UR) corners with
|
|
|
|
// floating point numbers.
|
|
|
|
func NewPdfRectangle(arr PdfObjectArray) (*PdfRectangle, error) {
|
|
|
|
rect := PdfRectangle{}
|
|
|
|
if len(arr) != 4 {
|
|
|
|
return nil, errors.New("Invalid rectangle array, len != 4")
|
|
|
|
}
|
|
|
|
|
|
|
|
var err error
|
|
|
|
rect.Llx, err = getNumberAsFloat(arr[0])
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
rect.Lly, err = getNumberAsFloat(arr[1])
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
rect.Urx, err = getNumberAsFloat(arr[2])
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
rect.Ury, err = getNumberAsFloat(arr[3])
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &rect, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Convert to a PDF object.
|
|
|
|
func (rect *PdfRectangle) ToPdfObject() PdfObject {
|
|
|
|
arr := PdfObjectArray{}
|
2016-07-25 14:06:37 +00:00
|
|
|
arr = append(arr, MakeFloat(rect.Llx))
|
|
|
|
arr = append(arr, MakeFloat(rect.Lly))
|
|
|
|
arr = append(arr, MakeFloat(rect.Urx))
|
|
|
|
arr = append(arr, MakeFloat(rect.Ury))
|
2016-07-19 23:16:25 +00:00
|
|
|
return &arr
|
|
|
|
}
|
|
|
|
|
|
|
|
// A date is a PDF string of the form:
|
|
|
|
// (D:YYYYMMDDHHmmSSOHH'mm)
|
|
|
|
type PdfDate struct {
|
|
|
|
year int64 // YYYY
|
|
|
|
month int64 // MM (01-12)
|
|
|
|
day int64 // DD (01-31)
|
|
|
|
hour int64 // HH (00-23)
|
|
|
|
minute int64 // mm (00-59)
|
|
|
|
second int64 // SS (00-59)
|
|
|
|
utOffsetSign byte // O ('+' / '-' / 'Z')
|
|
|
|
utOffsetHours int64 // HH' (00-23 followed by ')
|
|
|
|
utOffsetMins int64 // mm (00-59)
|
|
|
|
}
|
|
|
|
|
2016-08-19 11:34:55 +00:00
|
|
|
var reDate = regexp.MustCompile(`\s*D\s*:\s*(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})([+-Z])?(\d{2})?'?(\d{2})?`)
|
2016-07-19 23:16:25 +00:00
|
|
|
|
|
|
|
// Make a new PdfDate object from a PDF date string (see 7.9.4 Dates).
|
|
|
|
// format: "D: YYYYMMDDHHmmSSOHH'mm"
|
|
|
|
func NewPdfDate(dateStr string) (PdfDate, error) {
|
|
|
|
d := PdfDate{}
|
|
|
|
|
|
|
|
matches := reDate.FindAllStringSubmatch(dateStr, 1)
|
|
|
|
if len(matches) < 1 {
|
2016-08-16 17:57:23 +00:00
|
|
|
return d, fmt.Errorf("Invalid date string (%s)", dateStr)
|
2016-07-19 23:16:25 +00:00
|
|
|
}
|
|
|
|
if len(matches[0]) != 10 {
|
|
|
|
return d, errors.New("Invalid regexp group match length != 10")
|
|
|
|
}
|
|
|
|
|
|
|
|
// No need to handle err from ParseInt, as pre-validated via regexp.
|
|
|
|
d.year, _ = strconv.ParseInt(matches[0][1], 10, 32)
|
|
|
|
d.month, _ = strconv.ParseInt(matches[0][2], 10, 32)
|
|
|
|
d.day, _ = strconv.ParseInt(matches[0][3], 10, 32)
|
|
|
|
d.hour, _ = strconv.ParseInt(matches[0][4], 10, 32)
|
|
|
|
d.minute, _ = strconv.ParseInt(matches[0][5], 10, 32)
|
|
|
|
d.second, _ = strconv.ParseInt(matches[0][6], 10, 32)
|
2016-08-19 11:34:55 +00:00
|
|
|
// Some poor implementations do not include the offset.
|
|
|
|
if len(matches[0][7]) > 0 {
|
|
|
|
d.utOffsetSign = matches[0][7][0]
|
|
|
|
} else {
|
|
|
|
d.utOffsetSign = '+'
|
|
|
|
}
|
|
|
|
if len(matches[0][8]) > 0 {
|
|
|
|
d.utOffsetHours, _ = strconv.ParseInt(matches[0][8], 10, 32)
|
|
|
|
} else {
|
|
|
|
d.utOffsetHours = 0
|
|
|
|
}
|
|
|
|
if len(matches[0][9]) > 0 {
|
|
|
|
d.utOffsetMins, _ = strconv.ParseInt(matches[0][9], 10, 32)
|
|
|
|
} else {
|
|
|
|
d.utOffsetMins = 0
|
|
|
|
}
|
2016-07-19 23:16:25 +00:00
|
|
|
|
|
|
|
return d, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Convert to a PDF string object.
|
|
|
|
func (date *PdfDate) ToPdfObject() PdfObject {
|
|
|
|
str := fmt.Sprintf("D:%.4d%.2d%.2d%.2d%.2d%.2d%c%.2d'%.2d'",
|
|
|
|
date.year, date.month, date.day, date.hour, date.minute, date.second,
|
|
|
|
date.utOffsetSign, date.utOffsetHours, date.utOffsetMins)
|
|
|
|
pdfStr := PdfObjectString(str)
|
|
|
|
return &pdfStr
|
|
|
|
}
|
|
|
|
|
2016-08-15 01:31:35 +00:00
|
|
|
type PdfPageTreeNode struct {
|
|
|
|
Parent *PdfPageTreeNode
|
|
|
|
Kids *PdfPageTreeNode
|
|
|
|
Count *int64
|
|
|
|
}
|
|
|
|
|
2016-07-19 23:16:25 +00:00
|
|
|
// PDF page object (7.7.3.3 - Table 30).
|
|
|
|
type PdfPage struct {
|
|
|
|
Parent PdfObject
|
|
|
|
LastModified *PdfDate
|
2016-07-25 14:06:37 +00:00
|
|
|
Resources *PdfPageResources
|
2016-07-19 23:16:25 +00:00
|
|
|
CropBox *PdfRectangle
|
|
|
|
MediaBox *PdfRectangle
|
|
|
|
BleedBox *PdfRectangle
|
|
|
|
TrimBox *PdfRectangle
|
|
|
|
ArtBox *PdfRectangle
|
|
|
|
BoxColorInfo PdfObject
|
|
|
|
Contents PdfObject
|
|
|
|
Rotate *int64
|
|
|
|
Group PdfObject
|
|
|
|
Thumb PdfObject
|
|
|
|
B PdfObject
|
|
|
|
Dur PdfObject
|
|
|
|
Trans PdfObject
|
|
|
|
Annots PdfObject
|
|
|
|
AA PdfObject
|
|
|
|
Metadata PdfObject
|
|
|
|
PieceInfo PdfObject
|
|
|
|
StructParents PdfObject
|
|
|
|
ID PdfObject
|
|
|
|
PZ PdfObject
|
|
|
|
SeparationInfo PdfObject
|
|
|
|
Tabs PdfObject
|
|
|
|
TemplateInstantiated PdfObject
|
|
|
|
PresSteps PdfObject
|
|
|
|
UserUnit PdfObject
|
|
|
|
VP PdfObject
|
2016-08-15 01:31:35 +00:00
|
|
|
pageDict *PdfObjectDictionary
|
2016-09-07 17:56:45 +00:00
|
|
|
//Annotations
|
|
|
|
Annotations []*PdfAnnotation
|
2016-08-15 01:31:35 +00:00
|
|
|
}
|
|
|
|
|
2016-08-20 10:15:48 +00:00
|
|
|
func NewPdfPage() *PdfPage {
|
|
|
|
page := PdfPage{}
|
|
|
|
page.pageDict = &PdfObjectDictionary{}
|
|
|
|
return &page
|
|
|
|
}
|
|
|
|
|
2016-07-19 23:16:25 +00:00
|
|
|
// Build a PdfPage based on the underlying dictionary.
|
2016-08-18 09:43:44 +00:00
|
|
|
// Used in loading existing PDF files.
|
2016-08-16 09:36:24 +00:00
|
|
|
func (reader *PdfReader) newPdfPageFromDict(p *PdfObjectDictionary) (*PdfPage, error) {
|
2016-08-20 10:15:48 +00:00
|
|
|
page := NewPdfPage()
|
2016-08-15 01:31:35 +00:00
|
|
|
|
|
|
|
d := *p
|
2016-07-19 23:16:25 +00:00
|
|
|
|
2016-08-15 01:31:35 +00:00
|
|
|
pType, ok := d["Type"].(*PdfObjectName)
|
2016-07-19 23:16:25 +00:00
|
|
|
if !ok {
|
|
|
|
return nil, errors.New("Missing/Invalid Page dictionary Type")
|
|
|
|
}
|
|
|
|
if *pType != "Page" {
|
|
|
|
return nil, errors.New("Page dictionary Type != Page")
|
|
|
|
}
|
|
|
|
|
2016-08-15 01:31:35 +00:00
|
|
|
if obj, isDefined := d["Parent"]; isDefined {
|
2016-07-19 23:16:25 +00:00
|
|
|
page.Parent = obj
|
|
|
|
}
|
|
|
|
|
2016-08-15 01:31:35 +00:00
|
|
|
if obj, isDefined := d["LastModified"]; isDefined {
|
2016-07-19 23:16:25 +00:00
|
|
|
strObj, ok := obj.(*PdfObjectString)
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.New("Page dictionary LastModified != string")
|
|
|
|
}
|
|
|
|
lastmod, err := NewPdfDate(string(*strObj))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
page.LastModified = &lastmod
|
|
|
|
}
|
|
|
|
|
2016-08-15 01:31:35 +00:00
|
|
|
if obj, isDefined := d["Resources"]; isDefined {
|
2016-08-16 09:36:24 +00:00
|
|
|
obj, err := reader.traceToObject(obj)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
dict, ok := TraceToDirectObject(obj).(*PdfObjectDictionary)
|
2016-07-25 14:06:37 +00:00
|
|
|
if !ok {
|
2016-08-16 09:36:24 +00: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
|
|
|
|
}
|
2016-07-19 23:16:25 +00:00
|
|
|
}
|
|
|
|
|
2016-08-15 01:31:35 +00:00
|
|
|
if obj, isDefined := d["MediaBox"]; isDefined {
|
2016-07-19 23:16:25 +00:00
|
|
|
boxArr, ok := obj.(*PdfObjectArray)
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.New("Page MediaBox not an array")
|
|
|
|
}
|
|
|
|
var err error
|
|
|
|
page.MediaBox, err = NewPdfRectangle(*boxArr)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
2016-08-15 01:31:35 +00:00
|
|
|
if obj, isDefined := d["CropBox"]; isDefined {
|
2016-07-19 23:16:25 +00:00
|
|
|
boxArr, ok := obj.(*PdfObjectArray)
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.New("Page CropBox not an array")
|
|
|
|
}
|
|
|
|
var err error
|
|
|
|
page.CropBox, err = NewPdfRectangle(*boxArr)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
2016-08-15 01:31:35 +00:00
|
|
|
if obj, isDefined := d["BleedBox"]; isDefined {
|
2016-07-19 23:16:25 +00:00
|
|
|
boxArr, ok := obj.(*PdfObjectArray)
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.New("Page BleedBox not an array")
|
|
|
|
}
|
|
|
|
var err error
|
|
|
|
page.BleedBox, err = NewPdfRectangle(*boxArr)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
2016-08-15 01:31:35 +00:00
|
|
|
if obj, isDefined := d["TrimBox"]; isDefined {
|
2016-07-19 23:16:25 +00:00
|
|
|
boxArr, ok := obj.(*PdfObjectArray)
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.New("Page TrimBox not an array")
|
|
|
|
}
|
|
|
|
var err error
|
|
|
|
page.TrimBox, err = NewPdfRectangle(*boxArr)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
2016-08-15 01:31:35 +00:00
|
|
|
if obj, isDefined := d["ArtBox"]; isDefined {
|
2016-07-19 23:16:25 +00:00
|
|
|
boxArr, ok := obj.(*PdfObjectArray)
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.New("Page ArtBox not an array")
|
|
|
|
}
|
|
|
|
var err error
|
|
|
|
page.ArtBox, err = NewPdfRectangle(*boxArr)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
2016-08-15 01:31:35 +00:00
|
|
|
if obj, isDefined := d["BoxColorInfo"]; isDefined {
|
2016-07-19 23:16:25 +00:00
|
|
|
page.BoxColorInfo = obj
|
|
|
|
}
|
2016-08-15 01:31:35 +00:00
|
|
|
if obj, isDefined := d["Contents"]; isDefined {
|
2016-07-19 23:16:25 +00:00
|
|
|
page.Contents = obj
|
|
|
|
}
|
2016-08-15 01:31:35 +00:00
|
|
|
if obj, isDefined := d["Rotate"]; isDefined {
|
2016-07-19 23:16:25 +00:00
|
|
|
iObj, ok := obj.(*PdfObjectInteger)
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.New("Invalid Page Rotate object")
|
|
|
|
}
|
|
|
|
iVal := int64(*iObj)
|
|
|
|
page.Rotate = &iVal
|
|
|
|
}
|
2016-08-15 01:31:35 +00:00
|
|
|
if obj, isDefined := d["Group"]; isDefined {
|
2016-07-19 23:16:25 +00:00
|
|
|
page.Group = obj
|
|
|
|
}
|
2016-08-15 01:31:35 +00:00
|
|
|
if obj, isDefined := d["Thumb"]; isDefined {
|
2016-07-19 23:16:25 +00:00
|
|
|
page.Thumb = obj
|
|
|
|
}
|
2016-08-15 01:31:35 +00:00
|
|
|
if obj, isDefined := d["B"]; isDefined {
|
2016-07-19 23:16:25 +00:00
|
|
|
page.B = obj
|
|
|
|
}
|
2016-08-15 01:31:35 +00:00
|
|
|
if obj, isDefined := d["Dur"]; isDefined {
|
2016-07-19 23:16:25 +00:00
|
|
|
page.Dur = obj
|
|
|
|
}
|
2016-08-15 01:31:35 +00:00
|
|
|
if obj, isDefined := d["Trans"]; isDefined {
|
2016-07-19 23:16:25 +00:00
|
|
|
page.Trans = obj
|
|
|
|
}
|
2016-08-15 01:31:35 +00:00
|
|
|
if obj, isDefined := d["Annots"]; isDefined {
|
2016-07-19 23:16:25 +00:00
|
|
|
page.Annots = obj
|
|
|
|
}
|
2016-08-15 01:31:35 +00:00
|
|
|
if obj, isDefined := d["AA"]; isDefined {
|
2016-07-19 23:16:25 +00:00
|
|
|
page.AA = obj
|
|
|
|
}
|
2016-08-15 01:31:35 +00:00
|
|
|
if obj, isDefined := d["Metadata"]; isDefined {
|
2016-07-19 23:16:25 +00:00
|
|
|
page.Metadata = obj
|
|
|
|
}
|
2016-08-15 01:31:35 +00:00
|
|
|
if obj, isDefined := d["PieceInfo"]; isDefined {
|
2016-07-19 23:16:25 +00:00
|
|
|
page.PieceInfo = obj
|
|
|
|
}
|
2016-08-15 01:31:35 +00:00
|
|
|
if obj, isDefined := d["StructParents"]; isDefined {
|
2016-07-19 23:16:25 +00:00
|
|
|
page.StructParents = obj
|
|
|
|
}
|
2016-08-15 01:31:35 +00:00
|
|
|
if obj, isDefined := d["ID"]; isDefined {
|
2016-07-19 23:16:25 +00:00
|
|
|
page.ID = obj
|
|
|
|
}
|
2016-08-15 01:31:35 +00:00
|
|
|
if obj, isDefined := d["PZ"]; isDefined {
|
2016-07-19 23:16:25 +00:00
|
|
|
page.PZ = obj
|
|
|
|
}
|
2016-08-15 01:31:35 +00:00
|
|
|
if obj, isDefined := d["SeparationInfo"]; isDefined {
|
2016-07-19 23:16:25 +00:00
|
|
|
page.SeparationInfo = obj
|
|
|
|
}
|
2016-08-15 01:31:35 +00:00
|
|
|
if obj, isDefined := d["Tabs"]; isDefined {
|
2016-07-19 23:16:25 +00:00
|
|
|
page.Tabs = obj
|
|
|
|
}
|
2016-08-15 01:31:35 +00:00
|
|
|
if obj, isDefined := d["TemplateInstantiated"]; isDefined {
|
2016-07-19 23:16:25 +00:00
|
|
|
page.TemplateInstantiated = obj
|
|
|
|
}
|
2016-08-15 01:31:35 +00:00
|
|
|
if obj, isDefined := d["PresSteps"]; isDefined {
|
2016-07-19 23:16:25 +00:00
|
|
|
page.PresSteps = obj
|
|
|
|
}
|
2016-08-15 01:31:35 +00:00
|
|
|
if obj, isDefined := d["UserUnit"]; isDefined {
|
2016-07-19 23:16:25 +00:00
|
|
|
page.UserUnit = obj
|
|
|
|
}
|
2016-08-15 01:31:35 +00:00
|
|
|
if obj, isDefined := d["VP"]; isDefined {
|
2016-07-19 23:16:25 +00:00
|
|
|
page.VP = obj
|
|
|
|
}
|
|
|
|
|
2016-09-07 17:56:45 +00:00
|
|
|
var err error
|
|
|
|
page.Annotations, err = reader.LoadAnnotations(&d)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
fmt.Printf("Annotations: %+v\n", page.Annotations)
|
|
|
|
for i, annot := range page.Annotations {
|
|
|
|
fmt.Printf("%d : %+v (%T)\n", i+1, annot, annot.GetContext())
|
|
|
|
fmt.Printf("- %+v\n", annot.GetContext())
|
|
|
|
}
|
|
|
|
|
2016-08-20 10:15:48 +00:00
|
|
|
return page, nil
|
2016-07-19 23:16:25 +00:00
|
|
|
}
|
|
|
|
|
2016-09-07 17:56:45 +00:00
|
|
|
func (reader *PdfReader) LoadAnnotations(d *PdfObjectDictionary) ([]*PdfAnnotation, error) {
|
|
|
|
annotsObj, hasAnnots := (*d)["Annots"]
|
|
|
|
if !hasAnnots {
|
|
|
|
fmt.Printf("Page does not have annotations!\n")
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var err error
|
|
|
|
annotsObj, err = reader.traceToObject(annotsObj)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
annotsArr, ok := TraceToDirectObject(annotsObj).(*PdfObjectArray)
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("Annots not an array")
|
|
|
|
}
|
|
|
|
|
|
|
|
annotations := []*PdfAnnotation{}
|
|
|
|
for _, obj := range *annotsArr {
|
|
|
|
obj, err = reader.traceToObject(obj)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
dict, ok := TraceToDirectObject(obj).(*PdfObjectDictionary)
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("Annotation not a dictionary")
|
|
|
|
}
|
|
|
|
annot, err := reader.newPdfAnnotationFromDict(dict)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
annotations = append(annotations, annot)
|
|
|
|
}
|
|
|
|
|
|
|
|
return annotations, nil
|
|
|
|
}
|
|
|
|
|
2016-07-19 23:16:25 +00:00
|
|
|
// Get the inheritable media box value, either from the page
|
|
|
|
// or a higher up page/pages struct.
|
|
|
|
func (this *PdfPage) GetMediaBox() (*PdfRectangle, error) {
|
|
|
|
if this.MediaBox != nil {
|
|
|
|
return this.MediaBox, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
node := this.Parent
|
|
|
|
for node != nil {
|
|
|
|
dictObj, ok := node.(*PdfIndirectObject)
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.New("Invalid parent object")
|
|
|
|
}
|
|
|
|
|
|
|
|
dict, ok := dictObj.PdfObject.(*PdfObjectDictionary)
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.New("Invalid parent objects dictionary")
|
|
|
|
}
|
|
|
|
|
|
|
|
if obj, hasMediaBox := (*dict)["MediaBox"]; hasMediaBox {
|
|
|
|
arr, ok := obj.(*PdfObjectArray)
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.New("Invalid media box")
|
|
|
|
}
|
|
|
|
rect, err := NewPdfRectangle(*arr)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return rect, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
node = (*dict)["Parent"]
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, errors.New("Media box not defined")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Convert the Page to a PDF object dictionary.
|
|
|
|
func (this *PdfPage) GetPageDict() *PdfObjectDictionary {
|
2016-08-15 01:31:35 +00:00
|
|
|
p := this.pageDict
|
2016-07-25 14:06:37 +00:00
|
|
|
(*p)["Type"] = MakeName("Page")
|
2016-07-19 23:16:25 +00:00
|
|
|
(*p)["Parent"] = this.Parent
|
|
|
|
|
|
|
|
if this.LastModified != nil {
|
|
|
|
p.Set("LastModified", this.LastModified.ToPdfObject())
|
|
|
|
}
|
2016-07-25 14:06:37 +00:00
|
|
|
if this.Resources != nil {
|
|
|
|
p.Set("Resources", this.Resources.ToPdfObject())
|
|
|
|
}
|
2016-07-19 23:16:25 +00:00
|
|
|
if this.CropBox != nil {
|
|
|
|
p.Set("CropBox", this.CropBox.ToPdfObject())
|
|
|
|
}
|
|
|
|
if this.MediaBox != nil {
|
|
|
|
p.Set("MediaBox", this.MediaBox.ToPdfObject())
|
|
|
|
}
|
|
|
|
if this.BleedBox != nil {
|
|
|
|
p.Set("BleedBox", this.BleedBox.ToPdfObject())
|
|
|
|
}
|
|
|
|
if this.TrimBox != nil {
|
|
|
|
p.Set("TrimBox", this.TrimBox.ToPdfObject())
|
|
|
|
}
|
|
|
|
if this.ArtBox != nil {
|
|
|
|
p.Set("ArtBox", this.ArtBox.ToPdfObject())
|
|
|
|
}
|
|
|
|
p.SetIfNotNil("BoxColorInfo", this.BoxColorInfo)
|
|
|
|
p.SetIfNotNil("Contents", this.Contents)
|
|
|
|
|
|
|
|
if this.Rotate != nil {
|
2016-07-25 14:06:37 +00:00
|
|
|
p.Set("Rotate", MakeInteger(*this.Rotate))
|
2016-07-19 23:16:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
p.SetIfNotNil("Group", this.Group)
|
|
|
|
p.SetIfNotNil("Thumb", this.Thumb)
|
|
|
|
p.SetIfNotNil("B", this.B)
|
|
|
|
p.SetIfNotNil("Dur", this.Dur)
|
|
|
|
p.SetIfNotNil("Trans", this.Trans)
|
2016-09-07 17:56:45 +00:00
|
|
|
p.SetIfNotNil("Annots2", this.Annots)
|
2016-07-19 23:16:25 +00:00
|
|
|
p.SetIfNotNil("AA", this.AA)
|
|
|
|
p.SetIfNotNil("Metadata", this.Metadata)
|
|
|
|
p.SetIfNotNil("PieceInfo", this.PieceInfo)
|
|
|
|
p.SetIfNotNil("StructParents", this.StructParents)
|
|
|
|
p.SetIfNotNil("ID", this.ID)
|
|
|
|
p.SetIfNotNil("PZ", this.PZ)
|
|
|
|
p.SetIfNotNil("SeparationInfo", this.SeparationInfo)
|
|
|
|
p.SetIfNotNil("Tabs", this.Tabs)
|
|
|
|
p.SetIfNotNil("TemplateInstantiated", this.TemplateInstantiated)
|
|
|
|
p.SetIfNotNil("PresSteps", this.PresSteps)
|
|
|
|
p.SetIfNotNil("UserUnit", this.UserUnit)
|
|
|
|
p.SetIfNotNil("VP", this.VP)
|
|
|
|
|
2016-09-07 17:56:45 +00:00
|
|
|
fmt.Printf("Writing annotations\n")
|
|
|
|
if this.Annotations != nil {
|
|
|
|
fmt.Printf("...\n")
|
|
|
|
arr := PdfObjectArray{}
|
|
|
|
for i, annot := range this.Annotations {
|
|
|
|
fmt.Printf("Annotation %d\n", i+1)
|
|
|
|
arr = append(arr, annot.GetContext().ToPdfObject())
|
|
|
|
}
|
|
|
|
p.SetIfNotNil("Annots", &arr)
|
|
|
|
}
|
|
|
|
|
2016-07-19 23:16:25 +00:00
|
|
|
return p
|
|
|
|
}
|
2016-07-25 14:06:37 +00:00
|
|
|
|
|
|
|
// Get the page object as an indirect objects. Wraps the Page
|
|
|
|
// dictionary into an indirect object.
|
|
|
|
func (this *PdfPage) GetPageAsIndirectObject() *PdfIndirectObject {
|
|
|
|
dict := this.GetPageDict()
|
|
|
|
iobj := PdfIndirectObject{}
|
|
|
|
iobj.PdfObject = dict
|
|
|
|
return &iobj
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add an image to the XObject resources.
|
|
|
|
func (this *PdfPage) AddImageResource(name PdfObjectName, ximg *XObjectImage) error {
|
|
|
|
if this.Resources == nil {
|
|
|
|
this.Resources = &PdfPageResources{}
|
|
|
|
}
|
|
|
|
var xresDict *PdfObjectDictionary
|
|
|
|
if this.Resources.XObject == nil {
|
|
|
|
xresDict = &PdfObjectDictionary{}
|
|
|
|
this.Resources.XObject = xresDict
|
|
|
|
} else {
|
|
|
|
var ok bool
|
|
|
|
xresDict, ok = (this.Resources.XObject).(*PdfObjectDictionary)
|
|
|
|
if !ok {
|
|
|
|
return errors.New("Invalid xres dict type")
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
// Make a stream object container.
|
|
|
|
(*xresDict)[name] = ximg.ToPdfObject()
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add a graphics state to the XObject resources.
|
|
|
|
func (this *PdfPage) AddExtGState(name PdfObjectName, egs *PdfObjectDictionary) {
|
|
|
|
if this.Resources == nil {
|
|
|
|
this.Resources = &PdfPageResources{}
|
|
|
|
}
|
|
|
|
|
|
|
|
if this.Resources.ExtGState == nil {
|
|
|
|
this.Resources.ExtGState = &PdfObjectDictionary{}
|
|
|
|
}
|
|
|
|
|
|
|
|
egsDict := this.Resources.ExtGState.(*PdfObjectDictionary)
|
|
|
|
(*egsDict)[name] = egs
|
|
|
|
}
|
|
|
|
|
2016-07-30 00:27:21 +00:00
|
|
|
// Add a font dictionary to the Font resources.
|
|
|
|
func (this *PdfPage) AddFont(name PdfObjectName, font *PdfObjectDictionary) {
|
|
|
|
if this.Resources == nil {
|
|
|
|
this.Resources = &PdfPageResources{}
|
|
|
|
}
|
|
|
|
|
|
|
|
if this.Resources.Font == nil {
|
|
|
|
this.Resources.Font = &PdfObjectDictionary{}
|
|
|
|
}
|
|
|
|
|
|
|
|
fontDict := this.Resources.Font.(*PdfObjectDictionary)
|
|
|
|
(*fontDict)[name] = font
|
|
|
|
}
|
|
|
|
|
|
|
|
type WatermarkImageOptions struct {
|
|
|
|
Alpha float64
|
2016-07-30 00:34:35 +00:00
|
|
|
FitToWidth bool
|
2016-07-30 00:27:21 +00:00
|
|
|
PreserveAspectRatio bool
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add a watermark to the page.
|
|
|
|
func (this *PdfPage) AddWatermarkImage(ximg *XObjectImage, opt WatermarkImageOptions) error {
|
|
|
|
bbox, err := this.GetMediaBox()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
pWidth := bbox.Urx - bbox.Llx
|
|
|
|
pHeight := bbox.Ury - bbox.Lly
|
|
|
|
|
2016-07-30 00:34:35 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
imgName := PdfObjectName("Imw0")
|
|
|
|
this.AddImageResource(imgName, ximg)
|
|
|
|
|
|
|
|
gs0 := PdfObjectDictionary{}
|
|
|
|
gs0["BM"] = MakeName("Normal")
|
|
|
|
gs0["CA"] = MakeFloat(opt.Alpha)
|
|
|
|
gs0["ca"] = MakeFloat(opt.Alpha)
|
|
|
|
this.AddExtGState("GS0", &gs0)
|
|
|
|
|
|
|
|
contentStr := fmt.Sprintf("q\n"+
|
|
|
|
"/GS0 gs\n"+
|
2016-07-30 00:34:35 +00:00
|
|
|
"%.0f 0 0 %.0f %.4f %.4f cm\n"+
|
2016-07-30 00:27:21 +00:00
|
|
|
"/%s Do\n"+
|
2016-07-30 00:34:35 +00:00
|
|
|
"Q", wWidth, wHeight, xOffset, yOffset, imgName)
|
2016-07-30 00:27:21 +00:00
|
|
|
this.AddContentStreamByString(contentStr)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-07-25 14:06:37 +00:00
|
|
|
// Add content stream by string. Puts the content string into a stream
|
|
|
|
// object and points the content stream towards it.
|
|
|
|
func (this *PdfPage) AddContentStreamByString(contentStr string) {
|
|
|
|
stream := PdfObjectStream{}
|
|
|
|
|
|
|
|
sDict := PdfObjectDictionary{}
|
|
|
|
stream.PdfObjectDictionary = &sDict
|
|
|
|
|
|
|
|
sDict["Length"] = MakeInteger(int64(len(contentStr)))
|
|
|
|
stream.Stream = []byte(contentStr)
|
|
|
|
|
2016-07-30 00:27:21 +00:00
|
|
|
if this.Contents == nil {
|
|
|
|
// If not set, place it directly.
|
|
|
|
this.Contents = &stream
|
|
|
|
} else if contArray, isArray := this.Contents.(*PdfObjectArray); isArray {
|
|
|
|
// If an array of content streams, append it.
|
|
|
|
*contArray = append(*contArray, &stream)
|
|
|
|
} else {
|
|
|
|
// Only 1 element in place. Wrap inside a new array and add the new one.
|
|
|
|
contArray := PdfObjectArray{}
|
|
|
|
contArray = append(contArray, this.Contents)
|
|
|
|
contArray = append(contArray, &stream)
|
|
|
|
this.Contents = &contArray
|
|
|
|
}
|
|
|
|
|
2016-07-25 14:06:37 +00:00
|
|
|
}
|
|
|
|
|
2016-08-22 08:46:18 +00:00
|
|
|
func getContentStreamAsString(cstreamObj PdfObject) (string, error) {
|
|
|
|
if cstream, ok := TraceToDirectObject(cstreamObj).(*PdfObjectString); ok {
|
|
|
|
return string(*cstream), nil
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if cstream, ok := TraceToDirectObject(cstreamObj).(*PdfObjectStream); ok {
|
2016-09-08 17:53:45 +00:00
|
|
|
buf, err := DecodeStream(cstream)
|
2016-08-22 08:46:18 +00:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
return string(buf), nil
|
|
|
|
}
|
|
|
|
return "", fmt.Errorf("Invalid content stream object holder (%T)", cstreamObj)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get Content Stream as an array of strings.
|
|
|
|
func (this *PdfPage) GetContentStreams() ([]string, error) {
|
|
|
|
if this.Contents == nil {
|
|
|
|
return nil, nil
|
|
|
|
} else if contArray, isArray := this.Contents.(*PdfObjectArray); isArray {
|
|
|
|
// If an array of content streams, append it.
|
|
|
|
cstreams := []string{}
|
|
|
|
for _, cstreamObj := range *contArray {
|
|
|
|
cstreamStr, err := getContentStreamAsString(cstreamObj)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
cstreams = append(cstreams, cstreamStr)
|
|
|
|
}
|
|
|
|
return cstreams, nil
|
|
|
|
} else {
|
|
|
|
// Only 1 element in place. Wrap inside a new array and add the new one.
|
|
|
|
cstreamStr, err := getContentStreamAsString(this.Contents)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
cstreams := []string{cstreamStr}
|
|
|
|
return cstreams, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-25 14:06:37 +00:00
|
|
|
// Page resources.
|
|
|
|
type PdfPageResources struct {
|
|
|
|
ExtGState PdfObject
|
|
|
|
ColorSpace PdfObject
|
|
|
|
Pattern PdfObject
|
|
|
|
Shading PdfObject
|
|
|
|
XObject PdfObject
|
|
|
|
Font PdfObject
|
|
|
|
ProcSet PdfObject
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewPdfPageResourcesFromDict(dict *PdfObjectDictionary) (*PdfPageResources, error) {
|
|
|
|
r := PdfPageResources{}
|
|
|
|
|
|
|
|
if obj, isDefined := (*dict)["ExtGState"]; isDefined {
|
|
|
|
r.ExtGState = obj
|
|
|
|
}
|
|
|
|
if obj, isDefined := (*dict)["ColorSpace"]; isDefined {
|
|
|
|
r.ColorSpace = obj
|
|
|
|
}
|
|
|
|
if obj, isDefined := (*dict)["Pattern"]; isDefined {
|
|
|
|
r.Pattern = obj
|
|
|
|
}
|
|
|
|
if obj, isDefined := (*dict)["Shading"]; isDefined {
|
|
|
|
r.Shading = obj
|
|
|
|
}
|
|
|
|
if obj, isDefined := (*dict)["XObject"]; isDefined {
|
|
|
|
r.XObject = obj
|
|
|
|
}
|
|
|
|
if obj, isDefined := (*dict)["Font"]; isDefined {
|
|
|
|
r.Font = obj
|
|
|
|
}
|
|
|
|
if obj, isDefined := (*dict)["ProcSet"]; isDefined {
|
|
|
|
r.ProcSet = obj
|
|
|
|
}
|
|
|
|
|
|
|
|
return &r, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *PdfPageResources) ToPdfObject() PdfObject {
|
|
|
|
d := &PdfObjectDictionary{}
|
|
|
|
d.SetIfNotNil("ExtGState", r.ExtGState)
|
|
|
|
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)
|
|
|
|
return d
|
|
|
|
}
|
|
|
|
|
|
|
|
// Image XObject (Table 89 in 8.9.5.1).
|
|
|
|
type XObjectImage struct {
|
|
|
|
Width *int64
|
|
|
|
Height *int64
|
|
|
|
ColorSpace PdfObject
|
|
|
|
BitsPerComponent *int64
|
|
|
|
Intent PdfObject
|
|
|
|
ImageMask PdfObject
|
|
|
|
Mask PdfObject
|
|
|
|
Decode PdfObject
|
|
|
|
Interpolate PdfObject
|
|
|
|
Alternatives PdfObject
|
|
|
|
SMask PdfObject
|
|
|
|
SMaskInData PdfObject
|
|
|
|
Name PdfObject
|
|
|
|
StructParent PdfObject
|
|
|
|
ID PdfObject
|
|
|
|
OPI PdfObject
|
|
|
|
Metadata PdfObject
|
|
|
|
OC PdfObject
|
|
|
|
Stream []byte
|
|
|
|
}
|
|
|
|
|
|
|
|
// Creates a new XObject Image from an image object with default
|
|
|
|
// options.
|
|
|
|
func NewXObjectImage(name PdfObjectName, img *Image) (*XObjectImage, error) {
|
|
|
|
xobj := XObjectImage{}
|
|
|
|
|
|
|
|
xobj.Name = &name
|
|
|
|
xobj.Stream = img.Data.Bytes()
|
|
|
|
|
|
|
|
// Width and height.
|
|
|
|
imWidth := img.Width
|
|
|
|
imHeight := img.Height
|
|
|
|
xobj.Width = &imWidth
|
|
|
|
xobj.Height = &imHeight
|
|
|
|
|
|
|
|
// Bits.
|
|
|
|
bitDepth := int64(8)
|
|
|
|
xobj.BitsPerComponent = &bitDepth
|
|
|
|
|
|
|
|
xobj.ColorSpace = MakeName("DeviceRGB")
|
|
|
|
|
|
|
|
return &xobj, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Build the image xobject from a stream object.
|
|
|
|
func NewXObjectImageFromStream(stream PdfObjectStream) (*XObjectImage, error) {
|
|
|
|
img := XObjectImage{}
|
|
|
|
|
|
|
|
dict := *(stream.PdfObjectDictionary)
|
|
|
|
|
|
|
|
if obj, isDefined := dict["Width"]; isDefined {
|
|
|
|
iObj, ok := obj.(*PdfObjectInteger)
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.New("Invalid image width object")
|
|
|
|
}
|
|
|
|
iVal := int64(*iObj)
|
|
|
|
img.Width = &iVal
|
|
|
|
}
|
|
|
|
|
|
|
|
if obj, isDefined := dict["Height"]; isDefined {
|
|
|
|
iObj, ok := obj.(*PdfObjectInteger)
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.New("Invalid image height object")
|
|
|
|
}
|
|
|
|
iVal := int64(*iObj)
|
|
|
|
img.Height = &iVal
|
|
|
|
}
|
|
|
|
|
|
|
|
if obj, isDefined := dict["ColorSpace"]; isDefined {
|
|
|
|
img.ColorSpace = obj
|
|
|
|
}
|
|
|
|
|
|
|
|
if obj, isDefined := dict["BitsPerComponent"]; isDefined {
|
|
|
|
iObj, ok := obj.(*PdfObjectInteger)
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.New("Invalid image height object")
|
|
|
|
}
|
|
|
|
iVal := int64(*iObj)
|
|
|
|
img.BitsPerComponent = &iVal
|
|
|
|
}
|
|
|
|
|
|
|
|
if obj, isDefined := dict["Intent"]; isDefined {
|
|
|
|
img.Intent = obj
|
|
|
|
}
|
|
|
|
if obj, isDefined := dict["ImageMask"]; isDefined {
|
|
|
|
img.ImageMask = obj
|
|
|
|
}
|
|
|
|
if obj, isDefined := dict["Mask"]; isDefined {
|
|
|
|
img.Mask = obj
|
|
|
|
}
|
|
|
|
if obj, isDefined := dict["Decode"]; isDefined {
|
|
|
|
img.Decode = obj
|
|
|
|
}
|
|
|
|
if obj, isDefined := dict["Interpolate"]; isDefined {
|
|
|
|
img.Interpolate = obj
|
|
|
|
}
|
|
|
|
if obj, isDefined := dict["Alternatives"]; isDefined {
|
|
|
|
img.Alternatives = obj
|
|
|
|
}
|
|
|
|
if obj, isDefined := dict["SMask"]; isDefined {
|
|
|
|
img.SMask = obj
|
|
|
|
}
|
|
|
|
if obj, isDefined := dict["SMaskInData"]; isDefined {
|
|
|
|
img.SMaskInData = obj
|
|
|
|
}
|
|
|
|
if obj, isDefined := dict["Name"]; isDefined {
|
|
|
|
img.Name = obj
|
|
|
|
}
|
|
|
|
if obj, isDefined := dict["StructParent"]; isDefined {
|
|
|
|
img.StructParent = obj
|
|
|
|
}
|
|
|
|
if obj, isDefined := dict["ID"]; isDefined {
|
|
|
|
img.ID = obj
|
|
|
|
}
|
|
|
|
if obj, isDefined := dict["OPI"]; isDefined {
|
|
|
|
img.OPI = obj
|
|
|
|
}
|
|
|
|
if obj, isDefined := dict["Metadata"]; isDefined {
|
|
|
|
img.Metadata = obj
|
|
|
|
}
|
|
|
|
if obj, isDefined := dict["OC"]; isDefined {
|
|
|
|
img.OC = obj
|
|
|
|
}
|
|
|
|
|
|
|
|
img.Stream = stream.Stream
|
|
|
|
|
|
|
|
return &img, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return a stream object.
|
|
|
|
func (ximg *XObjectImage) ToPdfObject() PdfObject {
|
|
|
|
stream := PdfObjectStream{}
|
|
|
|
stream.Stream = ximg.Stream
|
|
|
|
|
|
|
|
dict := PdfObjectDictionary{}
|
|
|
|
stream.PdfObjectDictionary = &dict
|
|
|
|
|
|
|
|
// XXX/FIXME: Continue defining these.
|
|
|
|
dict["Type"] = MakeName("XObject")
|
|
|
|
dict["Subtype"] = MakeName("Image")
|
|
|
|
dict["Width"] = MakeInteger(*(ximg.Width))
|
|
|
|
dict["Height"] = MakeInteger(*(ximg.Height))
|
|
|
|
dict["Filter"] = MakeName("DCTDecode")
|
|
|
|
|
|
|
|
if ximg.BitsPerComponent != nil {
|
|
|
|
dict["BitsPerComponent"] = MakeInteger(*(ximg.BitsPerComponent))
|
|
|
|
}
|
|
|
|
|
|
|
|
dict.SetIfNotNil("ColorSpace", ximg.ColorSpace)
|
|
|
|
dict.SetIfNotNil("Intent", ximg.Intent)
|
|
|
|
dict.SetIfNotNil("ImageMask", ximg.ImageMask)
|
|
|
|
dict.SetIfNotNil("Mask", ximg.Mask)
|
|
|
|
dict.SetIfNotNil("Decode", ximg.Decode)
|
|
|
|
dict.SetIfNotNil("Interpolate", ximg.Interpolate)
|
|
|
|
dict.SetIfNotNil("Alternatives", ximg.Alternatives)
|
|
|
|
dict.SetIfNotNil("SMask", ximg.SMask)
|
|
|
|
dict.SetIfNotNil("SMaskInData", ximg.SMaskInData)
|
|
|
|
dict.SetIfNotNil("Name", ximg.Name)
|
|
|
|
dict.SetIfNotNil("StructParent", ximg.StructParent)
|
|
|
|
dict.SetIfNotNil("ID", ximg.ID)
|
|
|
|
dict.SetIfNotNil("OPI", ximg.OPI)
|
|
|
|
dict.SetIfNotNil("Metadata", ximg.Metadata)
|
|
|
|
dict.SetIfNotNil("OC", ximg.OC)
|
|
|
|
|
|
|
|
dict["Length"] = MakeInteger(int64(len(ximg.Stream)))
|
|
|
|
stream.Stream = ximg.Stream
|
|
|
|
|
|
|
|
return &stream
|
|
|
|
}
|