barcode.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. // Copyright (c) 2015 Jelmer Snoeck (Gmail: jelmer.snoeck)
  2. //
  3. // Permission to use, copy, modify, and distribute this software for any purpose
  4. // with or without fee is hereby granted, provided that the above copyright notice
  5. // and this permission notice appear in all copies.
  6. //
  7. // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
  8. // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
  9. // FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
  10. // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
  11. // LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
  12. // OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
  13. // PERFORMANCE OF THIS SOFTWARE.
  14. // Package barcode provides helper methods for adding barcodes of different
  15. // types to your pdf document. It relies on the github.com/boombuler/barcode
  16. // package for the barcode creation.
  17. package barcode
  18. import (
  19. "bytes"
  20. "errors"
  21. "image/jpeg"
  22. "io"
  23. "strconv"
  24. "sync"
  25. "github.com/boombuler/barcode"
  26. "github.com/boombuler/barcode/aztec"
  27. "github.com/boombuler/barcode/codabar"
  28. "github.com/boombuler/barcode/code128"
  29. "github.com/boombuler/barcode/code39"
  30. "github.com/boombuler/barcode/datamatrix"
  31. "github.com/boombuler/barcode/ean"
  32. "github.com/boombuler/barcode/qr"
  33. "github.com/boombuler/barcode/twooffive"
  34. "github.com/jung-kurt/gofpdf"
  35. "github.com/ruudk/golang-pdf417"
  36. )
  37. // barcodes represents the barcodes that have been registered through this
  38. // package. They will later be used to be scaled and put on the page.
  39. // RubenN: made this a struct with a mutex to prevent race condition
  40. var barcodes struct {
  41. sync.Mutex
  42. cache map[string]barcode.Barcode
  43. }
  44. // barcodePdf is a partial PDF implementation that only implements a subset of
  45. // functions that are required to add the barcode to the PDF.
  46. type barcodePdf interface {
  47. GetConversionRatio() float64
  48. GetImageInfo(imageStr string) *gofpdf.ImageInfoType
  49. Image(imageNameStr string, x, y, w, h float64, flow bool, tp string, link int, linkStr string)
  50. RegisterImageReader(imgName, tp string, r io.Reader) *gofpdf.ImageInfoType
  51. SetError(err error)
  52. }
  53. // printBarcode internally prints the scaled or unscaled barcode to the PDF. Used by both
  54. // Barcode() and BarcodeUnscalable().
  55. func printBarcode(pdf barcodePdf, code string, x, y float64, w, h *float64, flow bool) {
  56. barcodes.Lock()
  57. unscaled, ok := barcodes.cache[code]
  58. barcodes.Unlock()
  59. if !ok {
  60. err := errors.New("Barcode not found")
  61. pdf.SetError(err)
  62. return
  63. }
  64. bname := uniqueBarcodeName(code, x, y)
  65. info := pdf.GetImageInfo(bname)
  66. scaleToWidth := unscaled.Bounds().Dx()
  67. scaleToHeight := unscaled.Bounds().Dy()
  68. if info == nil {
  69. bcode, err := barcode.Scale(
  70. unscaled,
  71. scaleToWidth,
  72. scaleToHeight,
  73. )
  74. if err != nil {
  75. pdf.SetError(err)
  76. return
  77. }
  78. err = registerScaledBarcode(pdf, bname, bcode)
  79. if err != nil {
  80. pdf.SetError(err)
  81. return
  82. }
  83. }
  84. scaleToWidthF := float64(scaleToWidth)
  85. scaleToHeightF := float64(scaleToHeight)
  86. if w != nil {
  87. scaleToWidthF = *w
  88. }
  89. if h != nil {
  90. scaleToHeightF = *h
  91. }
  92. pdf.Image(bname, x, y, scaleToWidthF, scaleToHeightF, flow, "jpg", 0, "")
  93. }
  94. // BarcodeUnscalable puts a registered barcode in the current page.
  95. //
  96. // Its arguments work in the same way as that of Barcode(). However, it allows for an unscaled
  97. // barcode in the width and/or height dimensions. This can be useful if you want to prevent
  98. // side effects of upscaling.
  99. func BarcodeUnscalable(pdf barcodePdf, code string, x, y float64, w, h *float64, flow bool) {
  100. printBarcode(pdf, code, x, y, w, h, flow)
  101. }
  102. // Barcode puts a registered barcode in the current page.
  103. //
  104. // The size should be specified in the units used to create the PDF document.
  105. // If width or height are left unspecfied, the barcode is not scaled in the unspecified dimensions.
  106. //
  107. // Positioning with x, y and flow is inherited from Fpdf.Image().
  108. func Barcode(pdf barcodePdf, code string, x, y, w, h float64, flow bool) {
  109. printBarcode(pdf, code, x, y, &w, &h, flow)
  110. }
  111. // GetUnscaledBarcodeDimensions returns the width and height of the
  112. // unscaled barcode associated with the given code.
  113. func GetUnscaledBarcodeDimensions(pdf barcodePdf, code string) (w, h float64) {
  114. barcodes.Lock()
  115. unscaled, ok := barcodes.cache[code]
  116. barcodes.Unlock()
  117. if !ok {
  118. err := errors.New("Barcode not found")
  119. pdf.SetError(err)
  120. return
  121. }
  122. return convertFrom96Dpi(pdf, float64(unscaled.Bounds().Dx())),
  123. convertFrom96Dpi(pdf, float64(unscaled.Bounds().Dy()))
  124. }
  125. // Register registers a barcode but does not put it on the page. Use Barcode()
  126. // with the same code to put the barcode on the PDF page.
  127. func Register(bcode barcode.Barcode) string {
  128. barcodes.Lock()
  129. if len(barcodes.cache) == 0 {
  130. barcodes.cache = make(map[string]barcode.Barcode)
  131. }
  132. key := barcodeKey(bcode)
  133. barcodes.cache[key] = bcode
  134. barcodes.Unlock()
  135. return key
  136. }
  137. // RegisterAztec registers a barcode of type Aztec to the PDF, but not to
  138. // the page. Use Barcode() with the return value to put the barcode on the page.
  139. // code is the string to be encoded. minECCPercent is the error correction percentage. 33 is the default.
  140. // userSpecifiedLayers can be a value between -4 and 32 inclusive.
  141. func RegisterAztec(pdf barcodePdf, code string, minECCPercent int, userSpecifiedLayers int) string {
  142. bcode, err := aztec.Encode([]byte(code), minECCPercent, userSpecifiedLayers)
  143. return registerBarcode(pdf, bcode, err)
  144. }
  145. // RegisterCodabar registers a barcode of type Codabar to the PDF, but not to
  146. // the page. Use Barcode() with the return value to put the barcode on the page.
  147. func RegisterCodabar(pdf barcodePdf, code string) string {
  148. bcode, err := codabar.Encode(code)
  149. return registerBarcode(pdf, bcode, err)
  150. }
  151. // RegisterCode128 registers a barcode of type Code128 to the PDF, but not to
  152. // the page. Use Barcode() with the return value to put the barcode on the page.
  153. func RegisterCode128(pdf barcodePdf, code string) string {
  154. bcode, err := code128.Encode(code)
  155. return registerBarcode(pdf, bcode, err)
  156. }
  157. // RegisterCode39 registers a barcode of type Code39 to the PDF, but not to
  158. // the page. Use Barcode() with the return value to put the barcode on the page.
  159. //
  160. // includeChecksum and fullASCIIMode are inherited from code39.Encode().
  161. func RegisterCode39(pdf barcodePdf, code string, includeChecksum, fullASCIIMode bool) string {
  162. bcode, err := code39.Encode(code, includeChecksum, fullASCIIMode)
  163. return registerBarcode(pdf, bcode, err)
  164. }
  165. // RegisterDataMatrix registers a barcode of type DataMatrix to the PDF, but not
  166. // to the page. Use Barcode() with the return value to put the barcode on the
  167. // page.
  168. func RegisterDataMatrix(pdf barcodePdf, code string) string {
  169. bcode, err := datamatrix.Encode(code)
  170. return registerBarcode(pdf, bcode, err)
  171. }
  172. // RegisterPdf417 registers a barcode of type Pdf417 to the PDF, but not to the
  173. // page. code is the string to be encoded. columns specifies the number of
  174. // barcode columns; this should be a value between 1 and 30 inclusive.
  175. // securityLevel specifies an error correction level between zero and 8
  176. // inclusive. Barcodes for use with FedEx must set columns to 10 and
  177. // securityLevel to 5. Use Barcode() with the return value to put the barcode
  178. // on the page.
  179. func RegisterPdf417(pdf barcodePdf, code string, columns int, securityLevel int) string {
  180. bcode := pdf417.Encode(code, columns, securityLevel)
  181. return registerBarcode(pdf, bcode, nil)
  182. }
  183. // RegisterEAN registers a barcode of type EAN to the PDF, but not to the page.
  184. // It will automatically detect if the barcode is EAN8 or EAN13. Use Barcode()
  185. // with the return value to put the barcode on the page.
  186. func RegisterEAN(pdf barcodePdf, code string) string {
  187. bcode, err := ean.Encode(code)
  188. return registerBarcode(pdf, bcode, err)
  189. }
  190. // RegisterQR registers a barcode of type QR to the PDF, but not to the page.
  191. // Use Barcode() with the return value to put the barcode on the page.
  192. //
  193. // The ErrorCorrectionLevel and Encoding mode are inherited from qr.Encode().
  194. func RegisterQR(pdf barcodePdf, code string, ecl qr.ErrorCorrectionLevel, mode qr.Encoding) string {
  195. bcode, err := qr.Encode(code, ecl, mode)
  196. return registerBarcode(pdf, bcode, err)
  197. }
  198. // RegisterTwoOfFive registers a barcode of type TwoOfFive to the PDF, but not
  199. // to the page. Use Barcode() with the return value to put the barcode on the
  200. // page.
  201. //
  202. // The interleaved bool is inherited from twooffive.Encode().
  203. func RegisterTwoOfFive(pdf barcodePdf, code string, interleaved bool) string {
  204. bcode, err := twooffive.Encode(code, interleaved)
  205. return registerBarcode(pdf, bcode, err)
  206. }
  207. // registerBarcode registers a barcode internally using the Register() function.
  208. // In case of an error generating the barcode it will not be registered and will
  209. // set an error on the PDF. It will return a unique key for the barcode type and
  210. // content that can be used to put the barcode on the page.
  211. func registerBarcode(pdf barcodePdf, bcode barcode.Barcode, err error) string {
  212. if err != nil {
  213. pdf.SetError(err)
  214. return ""
  215. }
  216. return Register(bcode)
  217. }
  218. // uniqueBarcodeName makes sure every barcode has a unique name for its
  219. // dimensions. Scaling a barcode image results in quality loss, which could be
  220. // a problem for barcode readers.
  221. func uniqueBarcodeName(code string, x, y float64) string {
  222. xStr := strconv.FormatFloat(x, 'E', -1, 64)
  223. yStr := strconv.FormatFloat(y, 'E', -1, 64)
  224. return "barcode-" + code + "-" + xStr + yStr
  225. }
  226. // barcodeKey combines the code type and code value into a unique identifier for
  227. // a barcode type. This is so that we can store several barcodes with the same
  228. // code but different type in the barcodes map.
  229. func barcodeKey(bcode barcode.Barcode) string {
  230. return bcode.Metadata().CodeKind + bcode.Content()
  231. }
  232. // registerScaledBarcode registers a barcode with its exact dimensions to the
  233. // PDF but does not put it on the page. Use Fpdf.Image() with the same code to
  234. // add the barcode to the page.
  235. func registerScaledBarcode(pdf barcodePdf, code string, bcode barcode.Barcode) error {
  236. buf := new(bytes.Buffer)
  237. err := jpeg.Encode(buf, bcode, nil)
  238. if err != nil {
  239. return err
  240. }
  241. reader := bytes.NewReader(buf.Bytes())
  242. pdf.RegisterImageReader(code, "jpg", reader)
  243. return nil
  244. }
  245. // convertTo96DPI converts the given value, which is based on a 72 DPI value
  246. // like the rest of the PDF document, to a 96 DPI value that is required for
  247. // an Image.
  248. //
  249. // Doing this through the Fpdf.Image() function would mean that it uses a 72 DPI
  250. // value and stretches it to a 96 DPI value. This results in quality loss which
  251. // could be problematic for barcode scanners.
  252. func convertTo96Dpi(pdf barcodePdf, value float64) float64 {
  253. return value * pdf.GetConversionRatio() / 72 * 96
  254. }
  255. // convertFrom96Dpi converts the given value, which is based on a 96 DPI value
  256. // required for an Image, to a 72 DPI value like the rest of the PDF document.
  257. func convertFrom96Dpi(pdf barcodePdf, value float64) float64 {
  258. return value / pdf.GetConversionRatio() * 72 / 96
  259. }