1package barcode
2
3import (
4	"errors"
5	"fmt"
6	"image"
7	"image/color"
8	"math"
9)
10
11type wrapFunc func(x, y int) color.Color
12
13type scaledBarcode struct {
14	wrapped     Barcode
15	wrapperFunc wrapFunc
16	rect        image.Rectangle
17}
18
19type intCSscaledBC struct {
20	scaledBarcode
21}
22
23func (bc *scaledBarcode) Content() string {
24	return bc.wrapped.Content()
25}
26
27func (bc *scaledBarcode) Metadata() Metadata {
28	return bc.wrapped.Metadata()
29}
30
31func (bc *scaledBarcode) ColorModel() color.Model {
32	return bc.wrapped.ColorModel()
33}
34
35func (bc *scaledBarcode) Bounds() image.Rectangle {
36	return bc.rect
37}
38
39func (bc *scaledBarcode) At(x, y int) color.Color {
40	return bc.wrapperFunc(x, y)
41}
42
43func (bc *intCSscaledBC) CheckSum() int {
44	if cs, ok := bc.wrapped.(BarcodeIntCS); ok {
45		return cs.CheckSum()
46	}
47	return 0
48}
49
50// Scale returns a resized barcode with the given width and height.
51func Scale(bc Barcode, width, height int) (Barcode, error) {
52	switch bc.Metadata().Dimensions {
53	case 1:
54		return scale1DCode(bc, width, height)
55	case 2:
56		return scale2DCode(bc, width, height)
57	}
58
59	return nil, errors.New("unsupported barcode format")
60}
61
62func newScaledBC(wrapped Barcode, wrapperFunc wrapFunc, rect image.Rectangle) Barcode {
63	result := &scaledBarcode{
64		wrapped:     wrapped,
65		wrapperFunc: wrapperFunc,
66		rect:        rect,
67	}
68
69	if _, ok := wrapped.(BarcodeIntCS); ok {
70		return &intCSscaledBC{*result}
71	}
72	return result
73}
74
75func scale2DCode(bc Barcode, width, height int) (Barcode, error) {
76	orgBounds := bc.Bounds()
77	orgWidth := orgBounds.Max.X - orgBounds.Min.X
78	orgHeight := orgBounds.Max.Y - orgBounds.Min.Y
79
80	factor := int(math.Min(float64(width)/float64(orgWidth), float64(height)/float64(orgHeight)))
81	if factor <= 0 {
82		return nil, fmt.Errorf("can not scale barcode to an image smaller than %dx%d", orgWidth, orgHeight)
83	}
84
85	offsetX := (width - (orgWidth * factor)) / 2
86	offsetY := (height - (orgHeight * factor)) / 2
87
88	wrap := func(x, y int) color.Color {
89		if x < offsetX || y < offsetY {
90			return color.White
91		}
92		x = (x - offsetX) / factor
93		y = (y - offsetY) / factor
94		if x >= orgWidth || y >= orgHeight {
95			return color.White
96		}
97		return bc.At(x, y)
98	}
99
100	return newScaledBC(
101		bc,
102		wrap,
103		image.Rect(0, 0, width, height),
104	), nil
105}
106
107func scale1DCode(bc Barcode, width, height int) (Barcode, error) {
108	orgBounds := bc.Bounds()
109	orgWidth := orgBounds.Max.X - orgBounds.Min.X
110	factor := int(float64(width) / float64(orgWidth))
111
112	if factor <= 0 {
113		return nil, fmt.Errorf("can not scale barcode to an image smaller than %dx1", orgWidth)
114	}
115	offsetX := (width - (orgWidth * factor)) / 2
116
117	wrap := func(x, y int) color.Color {
118		if x < offsetX {
119			return color.White
120		}
121		x = (x - offsetX) / factor
122
123		if x >= orgWidth {
124			return color.White
125		}
126		return bc.At(x, 0)
127	}
128
129	return newScaledBC(
130		bc,
131		wrap,
132		image.Rect(0, 0, width, height),
133	), nil
134}
135