package platepdf import ( "bytes" "encoding/base64" "fmt" "printer.backend/internal/model" ) const renderDPI = 300 // BuildCompositeSVG assembles a plate-sized SVG by embedding each item as a PNG // rendered by rsvg-convert (identical to the standalone item file). func BuildCompositeSVG(plate model.Plate, spec model.ItemSpec, itemSVG []byte, preview model.LayoutPreview) ([]byte, error) { slotSVGs := make([][]byte, len(preview.Positions)) for i := range preview.Positions { slotSVGs[i] = itemSVG } return BuildCompositeSVGSlots(plate, spec, slotSVGs, preview) } // BuildCompositeSVGSlots places one rendered item PNG per layout position. // len(slotSVGs) must equal len(preview.Positions). func BuildCompositeSVGSlots(plate model.Plate, spec model.ItemSpec, slotSVGs [][]byte, preview model.LayoutPreview) ([]byte, error) { if len(slotSVGs) != len(preview.Positions) { return nil, fmt.Errorf("slot svg count %d does not match positions %d", len(slotSVGs), len(preview.Positions)) } canvasW := preview.CanvasWidthMM canvasH := preview.CanvasHeightMM if canvasW <= 0 || canvasH <= 0 { canvasW = spec.CanvasWidthMM() canvasH = spec.CanvasHeightMM() } slotPNGs := make([]string, len(slotSVGs)) for i, svg := range slotSVGs { itemPNG, err := svgToPNGViaRsvg(svg, renderDPI) if err != nil { return nil, fmt.Errorf("render item slot %d: %w", i, err) } slotPNGs[i] = base64.StdEncoding.EncodeToString(itemPNG) } var b bytes.Buffer fmt.Fprintf(&b, ` `, plate.WidthMM, plate.HeightMM, plate.WidthMM, plate.HeightMM, plate.WidthMM, plate.HeightMM, preview.PrintableXMM, preview.PrintableYMM, preview.PrintableWMM, preview.PrintableHMM, ) for i, pos := range preview.Positions { fmt.Fprintf(&b, ``, pos.XMM, pos.YMM, canvasW, canvasH, slotPNGs[i], ) } b.WriteString("\n") return b.Bytes(), nil }