// Copyright (c) 2015 Jelmer Snoeck (Gmail: jelmer.snoeck) // // Permission to use, copy, modify, and distribute this software for any purpose // with or without fee is hereby granted, provided that the above copyright notice // and this permission notice appear in all copies. // // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND // FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM // LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR // OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. // Package barcode provides helper methods for adding barcodes of different // types to your pdf document. It relies on the github.com/boombuler/barcode // package for the barcode creation. package barcode import ( "bytes" "errors" "image/jpeg" "io" "strconv" "sync" "github.com/boombuler/barcode" "github.com/boombuler/barcode/aztec" "github.com/boombuler/barcode/codabar" "github.com/boombuler/barcode/code128" "github.com/boombuler/barcode/code39" "github.com/boombuler/barcode/datamatrix" "github.com/boombuler/barcode/ean" "github.com/boombuler/barcode/qr" "github.com/boombuler/barcode/twooffive" "github.com/jung-kurt/gofpdf" "github.com/ruudk/golang-pdf417" ) // barcodes represents the barcodes that have been registered through this // package. They will later be used to be scaled and put on the page. // RubenN: made this a struct with a mutex to prevent race condition var barcodes struct { sync.Mutex cache map[string]barcode.Barcode } // barcodePdf is a partial PDF implementation that only implements a subset of // functions that are required to add the barcode to the PDF. type barcodePdf interface { GetConversionRatio() float64 GetImageInfo(imageStr string) *gofpdf.ImageInfoType Image(imageNameStr string, x, y, w, h float64, flow bool, tp string, link int, linkStr string) RegisterImageReader(imgName, tp string, r io.Reader) *gofpdf.ImageInfoType SetError(err error) } // printBarcode internally prints the scaled or unscaled barcode to the PDF. Used by both // Barcode() and BarcodeUnscalable(). func printBarcode(pdf barcodePdf, code string, x, y float64, w, h *float64, flow bool) { barcodes.Lock() unscaled, ok := barcodes.cache[code] barcodes.Unlock() if !ok { err := errors.New("Barcode not found") pdf.SetError(err) return } bname := uniqueBarcodeName(code, x, y) info := pdf.GetImageInfo(bname) scaleToWidth := unscaled.Bounds().Dx() scaleToHeight := unscaled.Bounds().Dy() if info == nil { bcode, err := barcode.Scale( unscaled, scaleToWidth, scaleToHeight, ) if err != nil { pdf.SetError(err) return } err = registerScaledBarcode(pdf, bname, bcode) if err != nil { pdf.SetError(err) return } } scaleToWidthF := float64(scaleToWidth) scaleToHeightF := float64(scaleToHeight) if w != nil { scaleToWidthF = *w } if h != nil { scaleToHeightF = *h } pdf.Image(bname, x, y, scaleToWidthF, scaleToHeightF, flow, "jpg", 0, "") } // BarcodeUnscalable puts a registered barcode in the current page. // // Its arguments work in the same way as that of Barcode(). However, it allows for an unscaled // barcode in the width and/or height dimensions. This can be useful if you want to prevent // side effects of upscaling. func BarcodeUnscalable(pdf barcodePdf, code string, x, y float64, w, h *float64, flow bool) { printBarcode(pdf, code, x, y, w, h, flow) } // Barcode puts a registered barcode in the current page. // // The size should be specified in the units used to create the PDF document. // If width or height are left unspecfied, the barcode is not scaled in the unspecified dimensions. // // Positioning with x, y and flow is inherited from Fpdf.Image(). func Barcode(pdf barcodePdf, code string, x, y, w, h float64, flow bool) { printBarcode(pdf, code, x, y, &w, &h, flow) } // GetUnscaledBarcodeDimensions returns the width and height of the // unscaled barcode associated with the given code. func GetUnscaledBarcodeDimensions(pdf barcodePdf, code string) (w, h float64) { barcodes.Lock() unscaled, ok := barcodes.cache[code] barcodes.Unlock() if !ok { err := errors.New("Barcode not found") pdf.SetError(err) return } return convertFrom96Dpi(pdf, float64(unscaled.Bounds().Dx())), convertFrom96Dpi(pdf, float64(unscaled.Bounds().Dy())) } // Register registers a barcode but does not put it on the page. Use Barcode() // with the same code to put the barcode on the PDF page. func Register(bcode barcode.Barcode) string { barcodes.Lock() if len(barcodes.cache) == 0 { barcodes.cache = make(map[string]barcode.Barcode) } key := barcodeKey(bcode) barcodes.cache[key] = bcode barcodes.Unlock() return key } // RegisterAztec registers a barcode of type Aztec to the PDF, but not to // the page. Use Barcode() with the return value to put the barcode on the page. // code is the string to be encoded. minECCPercent is the error correction percentage. 33 is the default. // userSpecifiedLayers can be a value between -4 and 32 inclusive. func RegisterAztec(pdf barcodePdf, code string, minECCPercent int, userSpecifiedLayers int) string { bcode, err := aztec.Encode([]byte(code), minECCPercent, userSpecifiedLayers) return registerBarcode(pdf, bcode, err) } // RegisterCodabar registers a barcode of type Codabar to the PDF, but not to // the page. Use Barcode() with the return value to put the barcode on the page. func RegisterCodabar(pdf barcodePdf, code string) string { bcode, err := codabar.Encode(code) return registerBarcode(pdf, bcode, err) } // RegisterCode128 registers a barcode of type Code128 to the PDF, but not to // the page. Use Barcode() with the return value to put the barcode on the page. func RegisterCode128(pdf barcodePdf, code string) string { bcode, err := code128.Encode(code) return registerBarcode(pdf, bcode, err) } // RegisterCode39 registers a barcode of type Code39 to the PDF, but not to // the page. Use Barcode() with the return value to put the barcode on the page. // // includeChecksum and fullASCIIMode are inherited from code39.Encode(). func RegisterCode39(pdf barcodePdf, code string, includeChecksum, fullASCIIMode bool) string { bcode, err := code39.Encode(code, includeChecksum, fullASCIIMode) return registerBarcode(pdf, bcode, err) } // RegisterDataMatrix registers a barcode of type DataMatrix to the PDF, but not // to the page. Use Barcode() with the return value to put the barcode on the // page. func RegisterDataMatrix(pdf barcodePdf, code string) string { bcode, err := datamatrix.Encode(code) return registerBarcode(pdf, bcode, err) } // RegisterPdf417 registers a barcode of type Pdf417 to the PDF, but not to the // page. code is the string to be encoded. columns specifies the number of // barcode columns; this should be a value between 1 and 30 inclusive. // securityLevel specifies an error correction level between zero and 8 // inclusive. Barcodes for use with FedEx must set columns to 10 and // securityLevel to 5. Use Barcode() with the return value to put the barcode // on the page. func RegisterPdf417(pdf barcodePdf, code string, columns int, securityLevel int) string { bcode := pdf417.Encode(code, columns, securityLevel) return registerBarcode(pdf, bcode, nil) } // RegisterEAN registers a barcode of type EAN to the PDF, but not to the page. // It will automatically detect if the barcode is EAN8 or EAN13. Use Barcode() // with the return value to put the barcode on the page. func RegisterEAN(pdf barcodePdf, code string) string { bcode, err := ean.Encode(code) return registerBarcode(pdf, bcode, err) } // RegisterQR registers a barcode of type QR to the PDF, but not to the page. // Use Barcode() with the return value to put the barcode on the page. // // The ErrorCorrectionLevel and Encoding mode are inherited from qr.Encode(). func RegisterQR(pdf barcodePdf, code string, ecl qr.ErrorCorrectionLevel, mode qr.Encoding) string { bcode, err := qr.Encode(code, ecl, mode) return registerBarcode(pdf, bcode, err) } // RegisterTwoOfFive registers a barcode of type TwoOfFive to the PDF, but not // to the page. Use Barcode() with the return value to put the barcode on the // page. // // The interleaved bool is inherited from twooffive.Encode(). func RegisterTwoOfFive(pdf barcodePdf, code string, interleaved bool) string { bcode, err := twooffive.Encode(code, interleaved) return registerBarcode(pdf, bcode, err) } // registerBarcode registers a barcode internally using the Register() function. // In case of an error generating the barcode it will not be registered and will // set an error on the PDF. It will return a unique key for the barcode type and // content that can be used to put the barcode on the page. func registerBarcode(pdf barcodePdf, bcode barcode.Barcode, err error) string { if err != nil { pdf.SetError(err) return "" } return Register(bcode) } // uniqueBarcodeName makes sure every barcode has a unique name for its // dimensions. Scaling a barcode image results in quality loss, which could be // a problem for barcode readers. func uniqueBarcodeName(code string, x, y float64) string { xStr := strconv.FormatFloat(x, 'E', -1, 64) yStr := strconv.FormatFloat(y, 'E', -1, 64) return "barcode-" + code + "-" + xStr + yStr } // barcodeKey combines the code type and code value into a unique identifier for // a barcode type. This is so that we can store several barcodes with the same // code but different type in the barcodes map. func barcodeKey(bcode barcode.Barcode) string { return bcode.Metadata().CodeKind + bcode.Content() } // registerScaledBarcode registers a barcode with its exact dimensions to the // PDF but does not put it on the page. Use Fpdf.Image() with the same code to // add the barcode to the page. func registerScaledBarcode(pdf barcodePdf, code string, bcode barcode.Barcode) error { buf := new(bytes.Buffer) err := jpeg.Encode(buf, bcode, nil) if err != nil { return err } reader := bytes.NewReader(buf.Bytes()) pdf.RegisterImageReader(code, "jpg", reader) return nil } // convertTo96DPI converts the given value, which is based on a 72 DPI value // like the rest of the PDF document, to a 96 DPI value that is required for // an Image. // // Doing this through the Fpdf.Image() function would mean that it uses a 72 DPI // value and stretches it to a 96 DPI value. This results in quality loss which // could be problematic for barcode scanners. func convertTo96Dpi(pdf barcodePdf, value float64) float64 { return value * pdf.GetConversionRatio() / 72 * 96 } // convertFrom96Dpi converts the given value, which is based on a 96 DPI value // required for an Image, to a 72 DPI value like the rest of the PDF document. func convertFrom96Dpi(pdf barcodePdf, value float64) float64 { return value / pdf.GetConversionRatio() * 72 / 96 }