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" }