167 lines
5.9 KiB
Go
167 lines
5.9 KiB
Go
package sign
|
||
|
||
import (
|
||
"bytes"
|
||
"fmt"
|
||
"io"
|
||
"strconv"
|
||
|
||
"github.com/digitorus/pdf"
|
||
)
|
||
|
||
func (context *SignContext) createCatalog() ([]byte, error) {
|
||
var catalog_buffer bytes.Buffer
|
||
|
||
// Start the catalog object
|
||
catalog_buffer.WriteString("<<\n")
|
||
catalog_buffer.WriteString(" /Type /Catalog\n")
|
||
|
||
// (Optional; PDF 1.4) The version of the PDF specification to which
|
||
// the document conforms (for example, 1.4) if later than the version
|
||
// specified in the file’s header (see 7.5.2, "File header"). If the header
|
||
// specifies a later version, or if this entry is absent, the document
|
||
// shall conform to the version specified in the header. This entry
|
||
// enables a PDF processor to update the version using an incremental
|
||
// update; see 7.5.6, "Incremental updates".
|
||
// The value of this entry shall be a name object, not a number, and
|
||
// therefore shall be preceded by a SOLIDUS (2Fh) character (/) when
|
||
// written in the PDF file (for example, /1.4).
|
||
//
|
||
// If an incremental upgrade requires a version that is higher than specified by the document.
|
||
// Ensure PDF version is at least 1.5 to support SigFlags in acroFormDict (1.4) and UF in the fileSpecDict (1.5)
|
||
if v, err := strconv.ParseFloat(context.PDFReader.PDFVersion, 64); err == nil && v < 1.5 {
|
||
catalog_buffer.WriteString(" /Version /1.5\n")
|
||
}
|
||
|
||
// Retrieve the root, its pointer and set the root string
|
||
root := context.PDFReader.Trailer().Key("Root")
|
||
rootPtr := root.GetPtr()
|
||
context.CatalogData.RootString = strconv.Itoa(int(rootPtr.GetID())) + " " + strconv.Itoa(int(rootPtr.GetGen())) + " R"
|
||
|
||
// Copy over existing catalog entries except for type and AcroForum
|
||
for _, key := range root.Keys() {
|
||
if key != "Type" && key != "AcroForm" {
|
||
_, _ = fmt.Fprintf(&catalog_buffer, " /%s ", key)
|
||
context.serializeCatalogEntry(&catalog_buffer, rootPtr.GetID(), root.Key(key))
|
||
catalog_buffer.WriteString("\n")
|
||
}
|
||
}
|
||
|
||
// Start the AcroForm dictionary with /NeedAppearances
|
||
catalog_buffer.WriteString(" /AcroForm <<\n")
|
||
catalog_buffer.WriteString(" /Fields [")
|
||
|
||
// Add existing signatures to the AcroForm dictionary
|
||
for i, sig := range context.existingSignatures {
|
||
if i > 0 {
|
||
catalog_buffer.WriteString(" ")
|
||
}
|
||
catalog_buffer.WriteString(strconv.Itoa(int(sig.objectId)) + " 0 R")
|
||
}
|
||
|
||
// Add the visual signature field to the AcroForm dictionary
|
||
if len(context.existingSignatures) > 0 {
|
||
catalog_buffer.WriteString(" ")
|
||
}
|
||
catalog_buffer.WriteString(strconv.Itoa(int(context.VisualSignData.objectId)) + " 0 R")
|
||
|
||
catalog_buffer.WriteString("]\n") // close Fields array
|
||
|
||
// (Optional; deprecated in PDF 2.0) A flag specifying whether
|
||
// to construct appearance streams and appearance
|
||
// dictionaries for all widget annotations in the document (see
|
||
// 12.7.4.3, "Variable text"). Default value: false. A PDF writer
|
||
// shall include this key, with a value of true, if it has not
|
||
// provided appearance streams for all visible widget
|
||
// annotations present in the document.
|
||
// if context.SignData.Visible {
|
||
// catalog_buffer.WriteString(" /NeedAppearances true")
|
||
// } else {
|
||
// catalog_buffer.WriteString(" /NeedAppearances false")
|
||
// }
|
||
|
||
// Signature flags (Table 225)
|
||
//
|
||
// Bit position 1: SignaturesExist
|
||
// If set, the document contains at least one signature field. This
|
||
// flag allows an interactive PDF processor to enable user
|
||
// interface items (such as menu items or push-buttons) related to
|
||
// signature processing without having to scan the entire
|
||
// document for the presence of signature fields.
|
||
//
|
||
// Bit position 2: AppendOnly
|
||
// If set, the document contains signatures that may be invalidated
|
||
// if the PDF file is saved (written) in a way that alters its previous
|
||
// contents, as opposed to an incremental update. Merely updating
|
||
// the PDF file by appending new information to the end of the
|
||
// previous version is safe (see H.7, "Updating example").
|
||
// Interactive PDF processors may use this flag to inform a user
|
||
// requesting a full save that signatures will be invalidated and
|
||
// require explicit confirmation before continuing with the
|
||
// operation.
|
||
//
|
||
// Set SigFlags and Permissions based on Signature Type
|
||
switch context.SignData.Signature.CertType {
|
||
case CertificationSignature, ApprovalSignature, TimeStampSignature:
|
||
catalog_buffer.WriteString(" /SigFlags 3\n")
|
||
case UsageRightsSignature:
|
||
catalog_buffer.WriteString(" /SigFlags 1\n")
|
||
}
|
||
|
||
// Finalize the AcroForm and Catalog object
|
||
catalog_buffer.WriteString(" >>\n") // Close AcroForm
|
||
catalog_buffer.WriteString(">>\n") // Close Catalog
|
||
|
||
return catalog_buffer.Bytes(), nil
|
||
}
|
||
|
||
// serializeCatalogEntry takes a pdf.Value and serializes it to the given writer.
|
||
func (context *SignContext) serializeCatalogEntry(w io.Writer, rootObjId uint32, value pdf.Value) {
|
||
if ptr := value.GetPtr(); ptr.GetID() != rootObjId {
|
||
// Indirect object
|
||
_, _ = fmt.Fprintf(w, "%d %d R", ptr.GetID(), ptr.GetGen())
|
||
return
|
||
}
|
||
|
||
// Direct object
|
||
switch value.Kind() {
|
||
case pdf.String:
|
||
_, _ = fmt.Fprintf(w, "(%s)", value.RawString())
|
||
case pdf.Null:
|
||
_, _ = fmt.Fprint(w, "null")
|
||
case pdf.Bool:
|
||
if value.Bool() {
|
||
_, _ = fmt.Fprint(w, "true")
|
||
} else {
|
||
_, _ = fmt.Fprint(w, "false")
|
||
}
|
||
case pdf.Integer:
|
||
_, _ = fmt.Fprintf(w, "%d", value.Int64())
|
||
case pdf.Real:
|
||
_, _ = fmt.Fprintf(w, "%f", value.Float64())
|
||
case pdf.Name:
|
||
_, _ = fmt.Fprintf(w, "/%s", value.Name())
|
||
case pdf.Dict:
|
||
_, _ = fmt.Fprint(w, "<<")
|
||
for idx, key := range value.Keys() {
|
||
if idx > 0 {
|
||
_, _ = fmt.Fprint(w, " ") // Space between items
|
||
}
|
||
_, _ = fmt.Fprintf(w, "/%s ", key)
|
||
context.serializeCatalogEntry(w, rootObjId, value.Key(key))
|
||
}
|
||
_, _ = fmt.Fprint(w, ">>")
|
||
case pdf.Array:
|
||
_, _ = fmt.Fprint(w, "[")
|
||
for idx := range value.Len() {
|
||
if idx > 0 {
|
||
_, _ = fmt.Fprint(w, " ") // Space between items
|
||
}
|
||
context.serializeCatalogEntry(w, rootObjId, value.Index(idx))
|
||
}
|
||
_, _ = fmt.Fprint(w, "]")
|
||
case pdf.Stream:
|
||
panic("stream cannot be a direct object")
|
||
}
|
||
}
|