87 lines
2.5 KiB
Go
87 lines
2.5 KiB
Go
package model
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"time"
|
|
)
|
|
|
|
// ItemSpec holds mask parameters (from template CLI).
|
|
type ItemSpec struct {
|
|
// SizeMM is legacy: square product when width_mm/height_mm are unset.
|
|
SizeMM float64 `json:"size_mm,omitempty"`
|
|
WidthMM float64 `json:"width_mm,omitempty"`
|
|
HeightMM float64 `json:"height_mm,omitempty"`
|
|
CornerRadiusMM float64 `json:"corner_radius_mm,omitempty"`
|
|
BleedMM float64 `json:"bleed_mm"`
|
|
MarginMM float64 `json:"margin_mm"`
|
|
PaddingMM float64 `json:"padding_mm"`
|
|
}
|
|
|
|
// ViewportWidth returns the SVG viewport width used for layout packing.
|
|
func (s ItemSpec) ViewportWidth() float64 {
|
|
if s.WidthMM > 0 {
|
|
return s.WidthMM
|
|
}
|
|
return s.SizeMM
|
|
}
|
|
|
|
// ViewportHeight returns the SVG viewport height used for layout packing.
|
|
func (s ItemSpec) ViewportHeight() float64 {
|
|
if s.HeightMM > 0 {
|
|
return s.HeightMM
|
|
}
|
|
return s.SizeMM
|
|
}
|
|
|
|
// markLengthMM is crop-mark extension outside the product (must match svgtemplate).
|
|
const markLengthMM = 6
|
|
|
|
// TrimOffsetMM is bleed + margin + crop marks drawn outside the product rect.
|
|
func (s ItemSpec) TrimOffsetMM() float64 {
|
|
return s.BleedMM + s.MarginMM + markLengthMM
|
|
}
|
|
|
|
// CanvasWidthMM is the full item SVG width including trim and crop marks.
|
|
func (s ItemSpec) CanvasWidthMM() float64 {
|
|
return s.ViewportWidth() + 2*s.TrimOffsetMM()
|
|
}
|
|
|
|
// CanvasHeightMM is the full item SVG height including trim and crop marks.
|
|
func (s ItemSpec) CanvasHeightMM() float64 {
|
|
return s.ViewportHeight() + 2*s.TrimOffsetMM()
|
|
}
|
|
|
|
// Normalize fills width/height from legacy size_mm and validates dimensions.
|
|
func (s *ItemSpec) Normalize() error {
|
|
if s.WidthMM <= 0 && s.HeightMM <= 0 && s.SizeMM > 0 {
|
|
s.WidthMM = s.SizeMM
|
|
s.HeightMM = s.SizeMM
|
|
}
|
|
if s.WidthMM <= 0 || s.HeightMM <= 0 {
|
|
return fmt.Errorf("width_mm and height_mm must be positive")
|
|
}
|
|
if s.CornerRadiusMM < 0 {
|
|
return fmt.Errorf("corner_radius_mm must be non-negative")
|
|
}
|
|
maxR := math.Min(s.WidthMM, s.HeightMM) / 2
|
|
if s.CornerRadiusMM > maxR {
|
|
return fmt.Errorf("corner_radius_mm must be at most %.2f", maxR)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Item is a printable product type (SVG mask) placed on a plate.
|
|
type Item struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name,omitempty"`
|
|
Spec ItemSpec `json:"spec"`
|
|
SVGTemplate string `json:"svg_template"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
}
|
|
|
|
// MetaFilename returns the sidecar metadata path for an SVG basename.
|
|
func MetaFilename(svgBasename string) string {
|
|
return svgBasename + ".meta.json"
|
|
}
|