Three corrections from the first real session. Routed via a manager, the box was prefilled with the DX's own address — the one thing that must not go on that envelope. It now starts empty and the QRZ fetch fills in the MANAGER's; switching the routing recomputes the box unless the operator has typed in it, because a hand-checked address is not the app's to replace. A fetch that came back with no street was overwriting a reviewed address with a bare country — the cty.dat fallback dressed as an answer. It now refuses to touch the box and says nothing was found. And the prefill no longer stacks the same town three times (address + QTH + country all carrying it). The output is ONE PDF for the whole session, each page at its own label size, written to the temp dir and opened straight in the viewer — no save dialog: the file is a print run, not a document to keep.
91 lines
3.2 KiB
Go
91 lines
3.2 KiB
Go
package main
|
||
|
||
// The label PRINT path: pick the paper-QSL queue, review each address, choose
|
||
// the routing, and export PDFs whose pages are the exact label size — one PDF
|
||
// per label kind, because a roll printer holds one stock at a time and a file
|
||
// mixing 29 mm addresses with 62 mm QSO labels could not be printed at all.
|
||
//
|
||
// The pages arrive from the frontend already rasterised: the designer's canvas
|
||
// renderer draws them at the stock's dpi, so what was previewed is — pixel for
|
||
// pixel — what lands in the PDF. Go only carries them to disk.
|
||
|
||
import (
|
||
"encoding/base64"
|
||
"fmt"
|
||
"os"
|
||
"os/exec"
|
||
"path/filepath"
|
||
"strings"
|
||
"time"
|
||
|
||
"hamlog/internal/applog"
|
||
"hamlog/internal/pdf"
|
||
"hamlog/internal/qso"
|
||
)
|
||
|
||
// LabelPaperQueue returns the contacts whose paper QSL is REQUESTED or QUEUED
|
||
// (ADIF qsl_sent R/Q) — the natural worklist for a labelling session. The
|
||
// frontend groups them by callsign.
|
||
func (a *App) LabelPaperQueue() ([]qso.QSO, error) {
|
||
if a.qso == nil {
|
||
return nil, fmt.Errorf("db not initialized")
|
||
}
|
||
return a.qso.List(a.ctx, qso.ListFilter{
|
||
QSLSentIn: []string{"R", "Q"},
|
||
Limit: 10_000,
|
||
})
|
||
}
|
||
|
||
// LabelPDFPage is one page of the session's output: a rasterised label and its
|
||
// physical size. Sizes vary WITHIN one document — the operator asked for a
|
||
// single PDF holding QSO labels, addresses and return labels together, and PDF
|
||
// pages each carry their own MediaBox, so a 90×29 page can follow a 100×62 one.
|
||
type LabelPDFPage struct {
|
||
PNG string `json:"png"` // base64, data-URL prefix tolerated
|
||
WMm float64 `json:"w_mm"`
|
||
HMm float64 `json:"h_mm"`
|
||
}
|
||
|
||
// LabelOpenPDF writes the session's labels to ONE temporary PDF and opens it in
|
||
// the system viewer, from which the operator prints. No save dialog by choice:
|
||
// the file is a print run, not a document to keep — anyone who wants to keep it
|
||
// saves from the viewer.
|
||
func (a *App) LabelOpenPDF(pages []LabelPDFPage) (string, error) {
|
||
if len(pages) == 0 {
|
||
return "", fmt.Errorf("nothing to print")
|
||
}
|
||
var doc pdf.Doc
|
||
for i, pg := range pages {
|
||
if pg.WMm < 5 || pg.HMm < 5 || pg.WMm > 400 || pg.HMm > 400 {
|
||
return "", fmt.Errorf("page %d: label size out of range", i+1)
|
||
}
|
||
p := pg.PNG
|
||
if idx := strings.Index(p, ","); idx >= 0 && strings.Contains(p[:idx], "base64") {
|
||
p = p[idx+1:]
|
||
}
|
||
raw, err := base64.StdEncoding.DecodeString(p)
|
||
if err != nil {
|
||
return "", fmt.Errorf("page %d: %w", i+1, err)
|
||
}
|
||
if err := doc.AddImagePage(raw, pg.WMm, pg.HMm); err != nil {
|
||
return "", fmt.Errorf("page %d: %w", i+1, err)
|
||
}
|
||
}
|
||
out, err := doc.Bytes()
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
// A timestamped name in the temp dir: two sessions in one evening must not
|
||
// fight over the file, least of all while a viewer holds the first one open.
|
||
path := filepath.Join(os.TempDir(), fmt.Sprintf("opslog-labels-%s.pdf", time.Now().Format("20060102-150405")))
|
||
if err := os.WriteFile(path, out, 0o644); err != nil {
|
||
return "", err
|
||
}
|
||
applog.Printf("labels: wrote %d page(s) to %s", len(pages), path)
|
||
if err := exec.Command("rundll32", "url.dll,FileProtocolHandler", path).Start(); err != nil {
|
||
applog.Printf("labels: could not open the PDF viewer: %v", err)
|
||
return "", fmt.Errorf("the PDF was written to %s but no viewer opened: %w", path, err)
|
||
}
|
||
return path, nil
|
||
}
|