The printing session in three steps. The worklist is the paper queue (ADIF qsl_sent R/Q), grouped by callsign since several QSOs of one station share a card. Each recipient is reviewed before anything prints: routing first — via manager when qsl_via says so, direct when an address is known, bureau otherwise — then the address itself, editable and fetchable from QRZ (the MANAGER's address when routing says via). What is printed is the reviewed text verbatim, not a re-resolution that could differ from what was checked. One PDF per label kind, never one file for all: 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. Pages are rasterised by the designer's own renderer at the stock's dpi and carried into the PDF untouched — internal/pdf is a hand-written image-page writer (DeviceGray + flate: monochrome, lossless, no cgo) because that is the entire need. Bureau stations get no address label (no envelope); return labels are printed one per envelope. Finishing offers the log update: QSL_SENT=Y, the chosen date, via B or D per routing — through the same BulkUpdateQSL the paper view uses.
98 lines
3.1 KiB
Go
98 lines
3.1 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"
|
||
"strings"
|
||
|
||
"hamlog/internal/applog"
|
||
"hamlog/internal/pdf"
|
||
"hamlog/internal/qso"
|
||
|
||
wruntime "github.com/wailsapp/wails/v2/pkg/runtime"
|
||
)
|
||
|
||
// 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,
|
||
})
|
||
}
|
||
|
||
// LabelExportPDF writes one PDF of label pages and opens it in the system
|
||
// viewer, from which the operator prints. pages are base64 PNGs (data-URL
|
||
// prefix tolerated), all of the same wMm×hMm stock.
|
||
//
|
||
// Returns the chosen path ("" if the operator cancelled the dialog — not an
|
||
// error, they changed their mind).
|
||
func (a *App) LabelExportPDF(defaultName string, wMm, hMm float64, pages []string) (string, error) {
|
||
if len(pages) == 0 {
|
||
return "", fmt.Errorf("nothing to print")
|
||
}
|
||
if wMm < 5 || hMm < 5 || wMm > 400 || hMm > 400 {
|
||
return "", fmt.Errorf("label size out of range")
|
||
}
|
||
var doc pdf.Doc
|
||
for i, p := range pages {
|
||
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, wMm, hMm); err != nil {
|
||
return "", fmt.Errorf("page %d: %w", i+1, err)
|
||
}
|
||
}
|
||
out, err := doc.Bytes()
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
name := strings.TrimSpace(defaultName)
|
||
if name == "" {
|
||
name = "labels.pdf"
|
||
}
|
||
if !strings.HasSuffix(strings.ToLower(name), ".pdf") {
|
||
name += ".pdf"
|
||
}
|
||
path, err := wruntime.SaveFileDialog(a.ctx, wruntime.SaveDialogOptions{
|
||
DefaultFilename: name,
|
||
Title: "Save label PDF",
|
||
Filters: []wruntime.FileFilter{{DisplayName: "PDF", Pattern: "*.pdf"}},
|
||
})
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
if path == "" {
|
||
return "", nil // cancelled
|
||
}
|
||
if err := os.WriteFile(path, out, 0o644); err != nil {
|
||
return "", err
|
||
}
|
||
applog.Printf("labels: wrote %d page(s) (%.0f×%.0f mm) to %s", len(pages), wMm, hMm, path)
|
||
// Opened in the default PDF viewer — printing happens there, by design: the
|
||
// operator asked for a file they can check and print with their own tool.
|
||
if err := exec.Command("rundll32", "url.dll,FileProtocolHandler", path).Start(); err != nil {
|
||
applog.Printf("labels: could not open the PDF viewer: %v", err)
|
||
}
|
||
return path, nil
|
||
}
|