125 lines
3.2 KiB
Go
125 lines
3.2 KiB
Go
package printjob
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
|
|
"printer.backend/internal/itemimage"
|
|
"printer.backend/internal/layout"
|
|
"printer.backend/internal/model"
|
|
"printer.backend/internal/platepdf"
|
|
"printer.backend/internal/store"
|
|
)
|
|
|
|
// RenderPDF builds a plate PDF with customer images placed in each item mask.
|
|
func RenderPDF(
|
|
job model.PrintJob,
|
|
configs *store.ConfigurationStore,
|
|
plates *store.PlateStore,
|
|
items *store.ItemStore,
|
|
orders *store.OrderStore,
|
|
) ([]byte, model.PrintJobSummary, error) {
|
|
cfg, err := configs.Get(job.ConfigurationID)
|
|
if err != nil {
|
|
return nil, model.PrintJobSummary{}, fmt.Errorf("configuration: %w", err)
|
|
}
|
|
|
|
plate, err := findPlate(plates, cfg.PlateID)
|
|
if err != nil {
|
|
return nil, model.PrintJobSummary{}, err
|
|
}
|
|
item, err := items.Get(cfg.ItemID)
|
|
if err != nil {
|
|
return nil, model.PrintJobSummary{}, fmt.Errorf("item: %w", err)
|
|
}
|
|
itemSVGPath, err := items.SVGPath(cfg.ItemID)
|
|
if err != nil {
|
|
return nil, model.PrintJobSummary{}, err
|
|
}
|
|
itemSVG, err := os.ReadFile(itemSVGPath)
|
|
if err != nil {
|
|
return nil, model.PrintJobSummary{}, fmt.Errorf("read item svg: %w", err)
|
|
}
|
|
|
|
orderList, err := loadOrders(orders, job.OrderIDs)
|
|
if err != nil {
|
|
return nil, model.PrintJobSummary{}, err
|
|
}
|
|
|
|
preview := layout.Pack(plate, item.Spec, cfg.SpacingMM)
|
|
preview.PlateID = cfg.PlateID
|
|
preview.ItemID = cfg.ItemID
|
|
summary := Summary(orderList, preview)
|
|
|
|
slotSVGs, err := buildSlotSVGs(itemSVG, item.Spec, preview, summary.Assignments, orders)
|
|
if err != nil {
|
|
return nil, summary, err
|
|
}
|
|
|
|
pdf, err := platepdf.GenerateWithSlots(plate, item.Spec, slotSVGs, preview)
|
|
if err != nil {
|
|
return nil, summary, err
|
|
}
|
|
return pdf, summary, nil
|
|
}
|
|
|
|
func buildSlotSVGs(
|
|
templateSVG []byte,
|
|
spec model.ItemSpec,
|
|
preview model.LayoutPreview,
|
|
assignments []model.PrintJobSlotAssignment,
|
|
orders *store.OrderStore,
|
|
) ([][]byte, error) {
|
|
slotSVGs := make([][]byte, len(preview.Positions))
|
|
for i := range slotSVGs {
|
|
slotSVGs[i] = templateSVG
|
|
}
|
|
for _, a := range assignments {
|
|
if a.Slot < 0 || a.Slot >= len(slotSVGs) {
|
|
return nil, fmt.Errorf("slot %d out of range", a.Slot)
|
|
}
|
|
path, img, err := orders.ImagePath(a.Image.OrderID, a.Image.ImageID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("slot %d: %w", a.Slot, err)
|
|
}
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("slot %d: read image: %w", a.Slot, err)
|
|
}
|
|
embedded, err := itemimage.Embed(templateSVG, spec, data, img.ContentType)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("slot %d: %w", a.Slot, err)
|
|
}
|
|
slotSVGs[a.Slot] = embedded
|
|
}
|
|
return slotSVGs, nil
|
|
}
|
|
|
|
func loadOrders(orders *store.OrderStore, ids []string) ([]model.Order, error) {
|
|
if len(ids) == 0 {
|
|
return nil, fmt.Errorf("at least one order_id is required")
|
|
}
|
|
out := make([]model.Order, 0, len(ids))
|
|
for _, id := range ids {
|
|
o, err := orders.Get(id)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("order %s: %w", id, err)
|
|
}
|
|
out = append(out, o)
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func findPlate(plates *store.PlateStore, id string) (model.Plate, error) {
|
|
list, err := plates.List()
|
|
if err != nil {
|
|
return model.Plate{}, err
|
|
}
|
|
for _, p := range list {
|
|
if p.ID == id {
|
|
return p, nil
|
|
}
|
|
}
|
|
return model.Plate{}, fmt.Errorf("plate not found: %s", id)
|
|
}
|