feat(macos): PDF Services stamp helper with dual-channel Verae auth
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).
This commit is contained in:
parent
e91275a67c
commit
46f2bcd43e
1 changed files with 1259 additions and 24 deletions
103
internal/pdfcert/pdfcert.go
Normal file
103
internal/pdfcert/pdfcert.go
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
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
|
||||
}
|
||||
Loading…
Reference in a new issue