github.com/phpdave11/gofpdf@v1.4.2/contrib/barcode/barcode.go (about)

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