Implements macOS v0: verae-print stamp + install-macos PDF menu items.
Saves {name}.pdf, {name}-verae-timestamp-{RECEIPTID}.pdf, and .verae.json.
Channels: verae (api.veraetime.net JWT) and zapier (zap.verae.ai xVerae- + attachPdf).
103 lines
2.8 KiB
Go
103 lines
2.8 KiB
Go
package pdfcert
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// Input for a simple one-page Verae registration certificate PDF.
|
|
type Input struct {
|
|
DocumentName string
|
|
SHA256 string
|
|
ReceiptID string
|
|
CreatedAt string
|
|
Merkle string
|
|
VerifyURL string
|
|
Channel string
|
|
}
|
|
|
|
// Build returns a minimal PDF (no external deps) describing the stamp.
|
|
func Build(in Input) ([]byte, error) {
|
|
created := in.CreatedAt
|
|
if created == "" {
|
|
created = time.Now().UTC().Format(time.RFC3339)
|
|
}
|
|
local := ""
|
|
if t, err := time.Parse(time.RFC3339, created); err == nil {
|
|
local = t.Local().Format(time.RFC1123)
|
|
}
|
|
verify := in.VerifyURL
|
|
if verify == "" && in.SHA256 != "" {
|
|
verify = fmt.Sprintf("https://mcp.verae.ai/register.html?sha256=%s&receipt=%s", in.SHA256, in.ReceiptID)
|
|
}
|
|
|
|
lines := []string{
|
|
"Verae Timestamp Registration",
|
|
"",
|
|
"Document: " + sanitize(in.DocumentName),
|
|
"SHA-256: " + in.SHA256,
|
|
"Receipt ID: " + in.ReceiptID,
|
|
"UTC: " + created,
|
|
}
|
|
if local != "" {
|
|
lines = append(lines, "Local: "+local)
|
|
}
|
|
if in.Merkle != "" {
|
|
lines = append(lines, "Merkle/block: "+in.Merkle)
|
|
}
|
|
if in.Channel != "" {
|
|
lines = append(lines, "Channel: "+in.Channel)
|
|
}
|
|
lines = append(lines, "Verify: "+verify)
|
|
lines = append(lines, "", "Digests only — document bytes were not uploaded.")
|
|
|
|
return buildTextPDF(lines)
|
|
}
|
|
|
|
func sanitize(s string) string {
|
|
s = strings.ReplaceAll(s, "(", "\\(")
|
|
s = strings.ReplaceAll(s, ")", "\\)")
|
|
s = strings.ReplaceAll(s, "\\", "\\\\")
|
|
return s
|
|
}
|
|
|
|
// Minimal PDF 1.4 with Helvetica text lines.
|
|
func buildTextPDF(lines []string) ([]byte, error) {
|
|
var content strings.Builder
|
|
content.WriteString("BT /F1 12 Tf 50 750 Td 14 TL\n")
|
|
for i, line := range lines {
|
|
if i > 0 {
|
|
content.WriteString("T*\n")
|
|
}
|
|
content.WriteString("(")
|
|
content.WriteString(sanitize(line))
|
|
content.WriteString(") Tj\n")
|
|
}
|
|
content.WriteString("ET\n")
|
|
stream := content.String()
|
|
|
|
objects := []string{
|
|
"<< /Type /Catalog /Pages 2 0 R >>",
|
|
"<< /Type /Pages /Kids [3 0 R] /Count 1 >>",
|
|
"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents 4 0 R /Resources << /Font << /F1 5 0 R >> >> >>",
|
|
fmt.Sprintf("<< /Length %d >>\nstream\n%s\nendstream", len(stream), stream),
|
|
"<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>",
|
|
}
|
|
|
|
var body strings.Builder
|
|
body.WriteString("%PDF-1.4\n")
|
|
offsets := make([]int, len(objects)+1)
|
|
for i, obj := range objects {
|
|
offsets[i+1] = body.Len()
|
|
fmt.Fprintf(&body, "%d 0 obj\n%s\nendobj\n", i+1, obj)
|
|
}
|
|
xrefStart := body.Len()
|
|
fmt.Fprintf(&body, "xref\n0 %d\n", len(objects)+1)
|
|
body.WriteString("0000000000 65535 f \n")
|
|
for i := 1; i <= len(objects); i++ {
|
|
fmt.Fprintf(&body, "%010d 00000 n \n", offsets[i])
|
|
}
|
|
fmt.Fprintf(&body, "trailer << /Size %d /Root 1 0 R >>\nstartxref\n%d\n%%%%EOF\n", len(objects)+1, xrefStart)
|
|
return []byte(body.String()), nil
|
|
}
|