735 lines
17 KiB
Go
735 lines
17 KiB
Go
/*
|
|
Copyright 2018 The pdfcpu Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package pdfcpu
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/pdfcpu/pdfcpu/pkg/log"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
const (
|
|
|
|
// ObjectStreamMaxObjects limits the number of objects within an object stream written.
|
|
ObjectStreamMaxObjects = 100
|
|
)
|
|
|
|
func writeCommentLine(w *WriteContext, comment string) (int, error) {
|
|
return w.WriteString(fmt.Sprintf("%%%s%s", comment, w.Eol))
|
|
}
|
|
|
|
func writeHeader(w *WriteContext, v Version) error {
|
|
|
|
i, err := writeCommentLine(w, "PDF-"+v.String())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
j, err := writeCommentLine(w, "\xe2\xe3\xcf\xD3")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
w.Offset += int64(i + j)
|
|
|
|
return nil
|
|
}
|
|
|
|
func writeTrailer(w *WriteContext) (int, error) {
|
|
return w.WriteString("%%EOF")
|
|
}
|
|
|
|
func writeObjectHeader(w *WriteContext, objNumber, genNumber int) (int, error) {
|
|
return w.WriteString(fmt.Sprintf("%d %d obj%s", objNumber, genNumber, w.Eol))
|
|
}
|
|
|
|
func writeObjectTrailer(w *WriteContext) (int, error) {
|
|
return w.WriteString(fmt.Sprintf("%sendobj%s", w.Eol, w.Eol))
|
|
}
|
|
|
|
func startObjectStream(ctx *Context) error {
|
|
|
|
// See 7.5.7 Object streams
|
|
// When new object streams and compressed objects are created, they shall always be assigned new object numbers.
|
|
|
|
log.Write.Println("startObjectStream begin")
|
|
|
|
objStreamDict := NewObjectStreamDict()
|
|
|
|
objNr, err := ctx.InsertObject(*objStreamDict)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ctx.Write.CurrentObjStream = &objNr
|
|
|
|
log.Write.Printf("startObjectStream end: %d\n", objNr)
|
|
|
|
return nil
|
|
}
|
|
|
|
func stopObjectStream(ctx *Context) error {
|
|
|
|
log.Write.Println("stopObjectStream begin")
|
|
|
|
xRefTable := ctx.XRefTable
|
|
|
|
if !ctx.Write.WriteToObjectStream {
|
|
return errors.Errorf("stopObjectStream: Not writing to object stream.")
|
|
}
|
|
|
|
if ctx.Write.CurrentObjStream == nil {
|
|
ctx.Write.WriteToObjectStream = false
|
|
log.Write.Println("stopObjectStream end (no content)")
|
|
return nil
|
|
}
|
|
|
|
entry, _ := xRefTable.FindTableEntry(*ctx.Write.CurrentObjStream, 0)
|
|
osd, _ := (entry.Object).(ObjectStreamDict)
|
|
|
|
// When we are ready to write: append prolog and content
|
|
osd.Finalize()
|
|
|
|
// Encode objStreamDict.Content -> objStreamDict.Raw
|
|
// and wipe (decoded) content to free up memory.
|
|
if err := osd.StreamDict.Encode(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Release memory.
|
|
osd.Content = nil
|
|
|
|
osd.StreamDict.Insert("First", Integer(osd.FirstObjOffset))
|
|
osd.StreamDict.Insert("N", Integer(osd.ObjCount))
|
|
|
|
// for each objStream execute at the end right before xRefStreamDict gets written.
|
|
log.Write.Printf("stopObjectStream: objStreamDict: %s\n", osd)
|
|
|
|
if err := writeStreamDictObject(ctx, *ctx.Write.CurrentObjStream, 0, osd.StreamDict); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Release memory.
|
|
osd.Raw = nil
|
|
|
|
ctx.Write.CurrentObjStream = nil
|
|
ctx.Write.WriteToObjectStream = false
|
|
|
|
log.Write.Println("stopObjectStream end")
|
|
|
|
return nil
|
|
}
|
|
|
|
func writeToObjectStream(ctx *Context, objNumber, genNumber int) (ok bool, err error) {
|
|
|
|
log.Write.Printf("addToObjectStream begin, obj#:%d gen#:%d\n", objNumber, genNumber)
|
|
|
|
w := ctx.Write
|
|
|
|
if ctx.WriteXRefStream && // object streams assume an xRefStream to be generated.
|
|
ctx.WriteObjectStream && // signal for compression into object stream is on.
|
|
ctx.Write.WriteToObjectStream && // currently writing to object stream.
|
|
genNumber == 0 {
|
|
|
|
if w.CurrentObjStream == nil {
|
|
// Create new objects stream on first write.
|
|
err = startObjectStream(ctx)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
}
|
|
|
|
objStrEntry, _ := ctx.FindTableEntry(*ctx.Write.CurrentObjStream, 0)
|
|
objStreamDict, _ := (objStrEntry.Object).(ObjectStreamDict)
|
|
|
|
// Get next free index in object stream.
|
|
i := objStreamDict.ObjCount
|
|
|
|
// Locate the xref table entry for the object to be added to this object stream.
|
|
entry, _ := ctx.FindTableEntry(objNumber, genNumber)
|
|
|
|
// Turn entry into a compressed entry located in object stream at index i.
|
|
entry.Compressed = true
|
|
entry.ObjectStream = ctx.Write.CurrentObjStream // !
|
|
entry.ObjectStreamInd = &i
|
|
w.SetWriteOffset(objNumber) // for a compressed obj this is supposed to be a fake offset. value does not matter.
|
|
|
|
// Append to prolog & content
|
|
err = objStreamDict.AddObject(objNumber, entry)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
objStrEntry.Object = objStreamDict
|
|
|
|
log.Write.Printf("writeObject end, obj#%d written to objectStream #%d\n", objNumber, *ctx.Write.CurrentObjStream)
|
|
|
|
if objStreamDict.ObjCount == ObjectStreamMaxObjects {
|
|
err = stopObjectStream(ctx)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
w.WriteToObjectStream = true
|
|
}
|
|
|
|
ok = true
|
|
|
|
}
|
|
|
|
log.Write.Printf("addToObjectStream end, obj#:%d gen#:%d\n", objNumber, genNumber)
|
|
|
|
return ok, nil
|
|
}
|
|
|
|
func writeObject(ctx *Context, objNumber, genNumber int, s string) error {
|
|
|
|
log.Write.Printf("writeObject begin, obj#:%d gen#:%d <%s>\n", objNumber, genNumber, s)
|
|
|
|
w := ctx.Write
|
|
|
|
// Cleanup entry (necessary for split command)
|
|
// TODO This is not the right place to check for an existing obj since we maybe writing NULL.
|
|
entry, ok := ctx.FindTableEntry(objNumber, genNumber)
|
|
if ok {
|
|
entry.Compressed = false
|
|
}
|
|
|
|
// Set write-offset for this object.
|
|
w.SetWriteOffset(objNumber)
|
|
|
|
written, err := writeObjectHeader(w, objNumber, genNumber)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Note: Lines that are not part of stream object data are limited to no more than 255 characters.
|
|
i, err := w.WriteString(s)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
j, err := writeObjectTrailer(w)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Write-offset for next object.
|
|
w.Offset += int64(written + i + j)
|
|
|
|
log.Write.Printf("writeObject end, %d bytes written\n", written+i+j)
|
|
|
|
return nil
|
|
}
|
|
|
|
func writePDFNullObject(ctx *Context, objNumber, genNumber int) error {
|
|
|
|
return writeObject(ctx, objNumber, genNumber, "null")
|
|
}
|
|
|
|
func writeBooleanObject(ctx *Context, objNumber, genNumber int, boolean Boolean) error {
|
|
|
|
ok, err := writeToObjectStream(ctx, objNumber, genNumber)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if ok {
|
|
return nil
|
|
}
|
|
|
|
return writeObject(ctx, objNumber, genNumber, boolean.PDFString())
|
|
}
|
|
|
|
func writeNameObject(ctx *Context, objNumber, genNumber int, name Name) error {
|
|
|
|
ok, err := writeToObjectStream(ctx, objNumber, genNumber)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if ok {
|
|
return nil
|
|
}
|
|
|
|
return writeObject(ctx, objNumber, genNumber, name.PDFString())
|
|
}
|
|
|
|
func writeStringLiteralObject(ctx *Context, objNumber, genNumber int, stringLiteral StringLiteral) error {
|
|
|
|
ok, err := writeToObjectStream(ctx, objNumber, genNumber)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if ok {
|
|
return nil
|
|
}
|
|
|
|
sl := stringLiteral
|
|
|
|
if ctx.EncKey != nil {
|
|
s1, err := encryptString(stringLiteral.Value(), objNumber, genNumber, ctx.EncKey, ctx.AES4Strings, ctx.E.R)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
sl = StringLiteral(*s1)
|
|
}
|
|
|
|
return writeObject(ctx, objNumber, genNumber, sl.PDFString())
|
|
}
|
|
|
|
func writeHexLiteralObject(ctx *Context, objNumber, genNumber int, hexLiteral HexLiteral) error {
|
|
|
|
ok, err := writeToObjectStream(ctx, objNumber, genNumber)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if ok {
|
|
return nil
|
|
}
|
|
|
|
hl := hexLiteral
|
|
|
|
if ctx.EncKey != nil {
|
|
s1, err := encryptString(hexLiteral.Value(), objNumber, genNumber, ctx.EncKey, ctx.AES4Strings, ctx.E.R)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
hl = HexLiteral(*s1)
|
|
}
|
|
|
|
return writeObject(ctx, objNumber, genNumber, hl.PDFString())
|
|
}
|
|
|
|
func writeIntegerObject(ctx *Context, objNumber, genNumber int, integer Integer) error {
|
|
|
|
ok, err := writeToObjectStream(ctx, objNumber, genNumber)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if ok {
|
|
return nil
|
|
}
|
|
|
|
return writeObject(ctx, objNumber, genNumber, integer.PDFString())
|
|
}
|
|
|
|
func writeFloatObject(ctx *Context, objNumber, genNumber int, float Float) error {
|
|
|
|
ok, err := writeToObjectStream(ctx, objNumber, genNumber)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if ok {
|
|
return nil
|
|
}
|
|
|
|
return writeObject(ctx, objNumber, genNumber, float.PDFString())
|
|
}
|
|
|
|
func writeDictObject(ctx *Context, objNumber, genNumber int, d Dict) error {
|
|
|
|
ok, err := writeToObjectStream(ctx, objNumber, genNumber)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if ok {
|
|
return nil
|
|
}
|
|
|
|
if ctx.EncKey != nil {
|
|
_, err := encryptDeepObject(d, objNumber, genNumber, ctx.EncKey, ctx.AES4Strings, ctx.E.R)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return writeObject(ctx, objNumber, genNumber, d.PDFString())
|
|
}
|
|
|
|
func writeArrayObject(ctx *Context, objNumber, genNumber int, a Array) error {
|
|
|
|
ok, err := writeToObjectStream(ctx, objNumber, genNumber)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if ok {
|
|
return nil
|
|
}
|
|
|
|
if ctx.EncKey != nil {
|
|
_, err := encryptDeepObject(a, objNumber, genNumber, ctx.EncKey, ctx.AES4Strings, ctx.E.R)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return writeObject(ctx, objNumber, genNumber, a.PDFString())
|
|
}
|
|
|
|
func writeStream(w *WriteContext, sd StreamDict) (int64, error) {
|
|
|
|
b, err := w.WriteString(fmt.Sprintf("%sstream%s", w.Eol, w.Eol))
|
|
if err != nil {
|
|
return 0, errors.Wrapf(err, "writeStream: failed to write raw content")
|
|
}
|
|
|
|
c, err := w.Write(sd.Raw)
|
|
if err != nil {
|
|
return 0, errors.Wrapf(err, "writeStream: failed to write raw content")
|
|
}
|
|
if int64(c) != *sd.StreamLength {
|
|
return 0, errors.Errorf("writeStream: failed to write raw content: %d bytes written - streamlength:%d", c, *sd.StreamLength)
|
|
}
|
|
|
|
e, err := w.WriteString(fmt.Sprintf("%sendstream", w.Eol))
|
|
if err != nil {
|
|
return 0, errors.Wrapf(err, "writeStream: failed to write raw content")
|
|
}
|
|
|
|
written := int64(b+e) + *sd.StreamLength
|
|
|
|
return written, nil
|
|
}
|
|
|
|
func handleIndirectLength(ctx *Context, ir *IndirectRef) error {
|
|
|
|
objNr := int(ir.ObjectNumber)
|
|
genNr := int(ir.GenerationNumber)
|
|
|
|
if ctx.Write.HasWriteOffset(objNr) {
|
|
log.Write.Printf("*** handleIndirectLength: object #%d already written offset=%d ***\n", objNr, ctx.Write.Offset)
|
|
} else {
|
|
length, err := ctx.DereferenceInteger(*ir)
|
|
if err != nil || length == nil {
|
|
return err
|
|
}
|
|
err = writeIntegerObject(ctx, objNr, genNr, *length)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func writeStreamDictObject(ctx *Context, objNumber, genNumber int, sd StreamDict) error {
|
|
|
|
log.Write.Printf("writeStreamDictObject begin: object #%d\n%v", objNumber, sd)
|
|
|
|
var inObjStream bool
|
|
|
|
if ctx.Write.WriteToObjectStream == true {
|
|
inObjStream = true
|
|
ctx.Write.WriteToObjectStream = false
|
|
}
|
|
|
|
// Sometimes a streamDicts length is a reference.
|
|
if ir := sd.IndirectRefEntry("Length"); ir != nil {
|
|
err := handleIndirectLength(ctx, ir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
var err error
|
|
|
|
// Unless the "Identity" crypt filter is used we have to encrypt.
|
|
isXRefStreamDict := sd.Type() != nil && *sd.Type() == "XRef"
|
|
if ctx.EncKey != nil &&
|
|
!isXRefStreamDict &&
|
|
!(len(sd.FilterPipeline) == 1 && sd.FilterPipeline[0].Name == "Crypt") {
|
|
|
|
sd.Raw, err = encryptStream(sd.Raw, objNumber, genNumber, ctx.EncKey, ctx.AES4Streams, ctx.E.R)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
l := int64(len(sd.Raw))
|
|
sd.StreamLength = &l
|
|
sd.Update("Length", Integer(l))
|
|
}
|
|
|
|
ctx.Write.SetWriteOffset(objNumber)
|
|
|
|
h, err := writeObjectHeader(ctx.Write, objNumber, genNumber)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Note: Lines that are not part of stream object data are limited to no more than 255 characters.
|
|
pdfString := sd.PDFString()
|
|
_, err = ctx.Write.WriteString(pdfString)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
b, err := writeStream(ctx.Write, sd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
t, err := writeObjectTrailer(ctx.Write)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
written := b + int64(h+len(pdfString)+t)
|
|
|
|
ctx.Write.Offset += written
|
|
ctx.Write.BinaryTotalSize += *sd.StreamLength
|
|
|
|
if inObjStream {
|
|
ctx.Write.WriteToObjectStream = true
|
|
}
|
|
|
|
log.Write.Printf("writeStreamDictObject end: object #%d written=%d\n", objNumber, written)
|
|
|
|
return nil
|
|
}
|
|
|
|
func writeDirectObject(ctx *Context, o Object) error {
|
|
|
|
switch o := o.(type) {
|
|
|
|
case Dict:
|
|
for k, v := range o {
|
|
if ctx.writingPages && (k == "Dest" || k == "D") {
|
|
ctx.dest = true
|
|
}
|
|
_, _, err := writeDeepObject(ctx, v)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ctx.dest = false
|
|
}
|
|
log.Write.Printf("writeDirectObject: end offset=%d\n", ctx.Write.Offset)
|
|
|
|
case Array:
|
|
for i, v := range o {
|
|
if ctx.dest && i == 0 {
|
|
continue
|
|
}
|
|
_, _, err := writeDeepObject(ctx, v)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
log.Write.Printf("writeDirectObject: end offset=%d\n", ctx.Write.Offset)
|
|
|
|
default:
|
|
log.Write.Printf("writeDirectObject: end, direct obj - nothing written: offset=%d\n%v\n", ctx.Write.Offset, o)
|
|
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func writeNullObject(ctx *Context, objNumber, genNumber int) error {
|
|
|
|
// An indirect reference to nil is a corner case.
|
|
// Still, it is an object that will be written.
|
|
err := writePDFNullObject(ctx, objNumber, genNumber)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Ensure no entry in free list.
|
|
return ctx.UndeleteObject(objNumber)
|
|
}
|
|
|
|
func writeDeepDict(ctx *Context, d Dict, objNr, genNr int) error {
|
|
|
|
err := writeDictObject(ctx, objNr, genNr, d)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for k, v := range d {
|
|
if ctx.writingPages && (k == "Dest" || k == "D") {
|
|
ctx.dest = true
|
|
}
|
|
_, _, err = writeDeepObject(ctx, v)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ctx.dest = false
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func writeDeepStreamDict(ctx *Context, sd *StreamDict, objNr, genNr int) error {
|
|
|
|
if ctx.EncKey != nil {
|
|
_, err := encryptDeepObject(*sd, objNr, genNr, ctx.EncKey, ctx.AES4Strings, ctx.E.R)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
err := writeStreamDictObject(ctx, objNr, genNr, *sd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, v := range sd.Dict {
|
|
_, _, err = writeDeepObject(ctx, v)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func writeDeepArray(ctx *Context, a Array, objNr, genNr int) error {
|
|
|
|
err := writeArrayObject(ctx, objNr, genNr, a)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for i, v := range a {
|
|
if ctx.dest && i == 0 {
|
|
continue
|
|
}
|
|
_, _, err = writeDeepObject(ctx, v)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func writeIndirectObject(ctx *Context, ir IndirectRef) (Object, error) {
|
|
|
|
objNr := int(ir.ObjectNumber)
|
|
genNr := int(ir.GenerationNumber)
|
|
|
|
if ctx.Write.HasWriteOffset(objNr) {
|
|
log.Write.Printf("writeIndirectObject end: object #%d already written.\n", objNr)
|
|
return nil, nil
|
|
}
|
|
|
|
o, err := ctx.Dereference(ir)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "writeIndirectObject: unable to dereference indirect object #%d", objNr)
|
|
}
|
|
|
|
log.Write.Printf("writeIndirectObject: object #%d gets writeoffset: %d\n", objNr, ctx.Write.Offset)
|
|
|
|
if o == nil {
|
|
|
|
err = writeNullObject(ctx, objNr, genNr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
log.Write.Printf("writeIndirectObject: end, obj#%d resolved to nil, offset=%d\n", objNr, ctx.Write.Offset)
|
|
return nil, nil
|
|
}
|
|
|
|
switch o := o.(type) {
|
|
|
|
case Dict:
|
|
err = writeDeepDict(ctx, o, objNr, genNr)
|
|
|
|
case StreamDict:
|
|
err = writeDeepStreamDict(ctx, &o, objNr, genNr)
|
|
|
|
case Array:
|
|
err = writeDeepArray(ctx, o, objNr, genNr)
|
|
|
|
case Integer:
|
|
err = writeIntegerObject(ctx, objNr, genNr, o)
|
|
|
|
case Float:
|
|
err = writeFloatObject(ctx, objNr, genNr, o)
|
|
|
|
case StringLiteral:
|
|
err = writeStringLiteralObject(ctx, objNr, genNr, o)
|
|
|
|
case HexLiteral:
|
|
err = writeHexLiteralObject(ctx, objNr, genNr, o)
|
|
|
|
case Boolean:
|
|
err = writeBooleanObject(ctx, objNr, genNr, o)
|
|
|
|
case Name:
|
|
err = writeNameObject(ctx, objNr, genNr, o)
|
|
|
|
default:
|
|
return nil, errors.Errorf("writeIndirectObject: undefined PDF object #%d %T\n", objNr, o)
|
|
|
|
}
|
|
|
|
return nil, err
|
|
}
|
|
|
|
func writeDeepObject(ctx *Context, objIn Object) (objOut Object, written bool, err error) {
|
|
|
|
log.Write.Printf("writeDeepObject: begin offset=%d\n%s\n", ctx.Write.Offset, objIn)
|
|
|
|
ir, ok := objIn.(IndirectRef)
|
|
if !ok {
|
|
return objIn, written, writeDirectObject(ctx, objIn)
|
|
}
|
|
|
|
objOut, err = writeIndirectObject(ctx, ir)
|
|
if err == nil {
|
|
written = true
|
|
log.Write.Printf("writeDeepObject: end offset=%d\n", ctx.Write.Offset)
|
|
}
|
|
|
|
return objOut, written, err
|
|
}
|
|
|
|
func writeEntry(ctx *Context, d Dict, dictName, entryName string) (Object, error) {
|
|
|
|
o, found := d.Find(entryName)
|
|
if !found || o == nil {
|
|
log.Write.Printf("writeEntry end: entry %s is nil\n", entryName)
|
|
return nil, nil
|
|
}
|
|
|
|
log.Write.Printf("writeEntry begin: dict=%s entry=%s offset=%d\n", dictName, entryName, ctx.Write.Offset)
|
|
|
|
o, _, err := writeDeepObject(ctx, o)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if o == nil {
|
|
log.Write.Printf("writeEntry end: dict=%s entry=%s resolved to nil, offset=%d\n", dictName, entryName, ctx.Write.Offset)
|
|
return nil, nil
|
|
}
|
|
|
|
log.Write.Printf("writeEntry end: dict=%s entry=%s offset=%d\n", dictName, entryName, ctx.Write.Offset)
|
|
|
|
return o, nil
|
|
}
|