Browse Source

Kurt Jungs Auslieferungszustand

master
Klaus Wendel 2 years ago
commit
5afea81825
  1. 21
      LICENSE
  2. 29
      Makefile
  3. 282
      README.md
  4. 157
      attachments.go
  5. 146
      compare.go
  6. 302
      contrib/barcode/barcode.go
  7. 222
      contrib/barcode/barcode_test.go
  8. 68
      contrib/ghostscript/ghostscript.go
  9. 143
      contrib/gofpdi/gofpdi.go
  10. 76
      contrib/gofpdi/gofpdi_test.go
  11. 43
      contrib/httpimg/httpimg.go
  12. 23
      contrib/httpimg/httpimg_test.go
  13. 83
      contrib/tiff/tiff.go
  14. 23
      contrib/tiff/tiff_test.go
  15. 738
      def.go
  16. 291
      doc.go
  17. 288
      doc/document.md
  18. 13
      doc/go.awk
  19. 68
      doc/html.txt
  20. 559
      embedded.go
  21. 474
      font.go
  22. 272
      font/CalligrapherRegular.afm
  23. BIN
      font/CalligrapherRegular.pfb
  24. BIN
      font/DejaVuSansCondensed-Bold.ttf
  25. BIN
      font/DejaVuSansCondensed-BoldOblique.ttf
  26. BIN
      font/DejaVuSansCondensed-Oblique.ttf
  27. 1
      font/DejaVuSansCondensed.json
  28. BIN
      font/DejaVuSansCondensed.ttf
  29. 1
      font/calligra.json
  30. BIN
      font/calligra.ttf
  31. BIN
      font/calligra.z
  32. 1
      font/courier.json
  33. 1
      font/courierb.json
  34. 1
      font/courierbi.json
  35. 1
      font/courieri.json
  36. 251
      font/cp1250.map
  37. 255
      font/cp1251.map
  38. 251
      font/cp1252.map
  39. 239
      font/cp1253.map
  40. 249
      font/cp1254.map
  41. 233
      font/cp1255.map
  42. 244
      font/cp1257.map
  43. 247
      font/cp1258.map
  44. 225
      font/cp874.map
  45. 1
      font/helvetica.json
  46. 1
      font/helvetica_1251.json
  47. BIN
      font/helvetica_1251.z
  48. 1
      font/helvetica_1253.json
  49. BIN
      font/helvetica_1253.z
  50. 1
      font/helveticab.json
  51. 1
      font/helveticabi.json
  52. 1
      font/helveticai.json
  53. 256
      font/iso-8859-1.map
  54. 248
      font/iso-8859-11.map
  55. 256
      font/iso-8859-15.map
  56. 256
      font/iso-8859-16.map
  57. 256
      font/iso-8859-2.map
  58. 256
      font/iso-8859-4.map
  59. 256
      font/iso-8859-5.map
  60. 250
      font/iso-8859-7.map
  61. 256
      font/iso-8859-9.map
  62. 256
      font/koi8-r.map
  63. 256
      font/koi8-u.map
  64. 1
      font/times.json
  65. 1
      font/timesb.json
  66. 1
      font/timesbi.json
  67. 1
      font/timesi.json
  68. 1
      font/zapfdingbats.json
  69. 4998
      fpdf.go
  70. 2924
      fpdf_test.go
  71. 213
      fpdftrans.go
  72. 12
      go.mod
  73. 20
      go.sum
  74. 446
      grid.go
  75. 220
      htmlbasic.go
  76. BIN
      image/doc.png
  77. 20
      image/doc.svg
  78. BIN
      image/fpdf.png
  79. BIN
      image/gofpdf.png
  80. BIN
      image/golang-gopher.png
  81. BIN
      image/golang-gopher.tiff
  82. BIN
      image/logo-gray.png
  83. BIN
      image/logo-progressive.jpg
  84. BIN
      image/logo-rgb.png
  85. BIN
      image/logo.gif
  86. BIN
      image/logo.jpg
  87. BIN
      image/logo.png
  88. BIN
      image/logo_gofpdf.jpg
  89. BIN
      image/mit.png
  90. 20
      image/mit.svg
  91. 73
      image/signature.svg
  92. BIN
      image/sweden.png
  93. 143
      internal/example/example.go
  94. 31
      internal/example/example_test.go
  95. 24
      internal/files/bin/Makefile
  96. 28
      internal/files/bin/bin.go
  97. 1691
      internal/files/files.go
  98. 82
      label.go
  99. 121
      layer.go
  100. 59
      list/list.go

21
LICENSE

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 Kurt Jung and contributors acknowledged in the documentation
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

29
Makefile

@ -0,0 +1,29 @@
all : documentation
documentation : doc/index.html doc.go README.md
cov : all
go test -v -coverprofile=coverage && go tool cover -html=coverage -o=coverage.html
check :
golint .
go vet -all .
gofmt -s -l .
goreportcard-cli -v | grep -v cyclomatic
README.md : doc/document.md
pandoc --read=markdown --write=gfm < $< > $@
doc/index.html : doc/document.md doc/html.txt
pandoc --read=markdown --write=html --template=doc/html.txt \
--metadata pagetitle="GoFPDF Document Generator" < $< > $@
doc.go : doc/document.md doc/go.awk
pandoc --read=markdown --write=plain $< | awk --assign=package_name=gofpdf --file=doc/go.awk > $@
gofmt -s -w $@
build :
go build -v
clean :
rm -f coverage.html coverage doc/index.html doc.go README.md

282
README.md

@ -0,0 +1,282 @@
# GoFPDF document generator
[![No Maintenance
Intended](http://unmaintained.tech/badge.svg)](http://unmaintained.tech/)
[![MIT
licensed](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/jung-kurt/gofpdf/master/LICENSE)
[![Report](https://goreportcard.com/badge/github.com/jung-kurt/gofpdf)](https://goreportcard.com/report/github.com/jung-kurt/gofpdf)
[![GoDoc](https://img.shields.io/badge/godoc-GoFPDF-blue.svg)](https://godoc.org/github.com/jung-kurt/gofpdf)
![](https://github.com/jung-kurt/gofpdf/raw/master/image/logo_gofpdf.jpg?raw=true)
Package gofpdf implements a PDF document generator with high level
support for text, drawing and images.
## Features
- UTF-8 support
- Choice of measurement unit, page format and margins
- Page header and footer management
- Automatic page breaks, line breaks, and text justification
- Inclusion of JPEG, PNG, GIF, TIFF and basic path-only SVG images
- Colors, gradients and alpha channel transparency
- Outline bookmarks
- Internal and external links
- TrueType, Type1 and encoding support
- Page compression
- Lines, Bézier curves, arcs, and ellipses
- Rotation, scaling, skewing, translation, and mirroring
- Clipping
- Document protection
- Layers
- Templates
- Barcodes
- Charting facility
- Import PDFs as templates
gofpdf has no dependencies other than the Go standard library. All tests
pass on Linux, Mac and Windows platforms.
gofpdf supports UTF-8 TrueType fonts and “right-to-left” languages. Note
that Chinese, Japanese, and Korean characters may not be included in
many general purpose fonts. For these languages, a specialized font (for
example,
[NotoSansSC](https://github.com/jsntn/webfonts/blob/master/NotoSansSC-Regular.ttf)
for simplified Chinese) can be used.
Also, support is provided to automatically translate UTF-8 runes to code
page encodings for languages that have fewer than 256 glyphs.
## We Are Closed
This repository will not be maintained, at least for some unknown
duration. But it is hoped that gofpdf has a bright future in the open
source world. Due to Go’s promise of compatibility, gofpdf should
continue to function without modification for a longer time than would
be the case with many other languages.
Forks should be based on the [last viable
commit](https://github.com/jung-kurt/gofpdf/commit/603f56990463f011cb1dbb64ef7f872c1adc009a).
Tools such as
[active-forks](https://techgaun.github.io/active-forks/index.html#jung-kurt/gofpdf)
can be used to select a fork that looks promising for your needs. If a
particular fork looks like it has taken the lead in attracting
followers, this README will be updated to point people in that
direction.
The efforts of all contributors to this project have been deeply
appreciated. Best wishes to all of you.
## Installation
To install the package on your system, run
``` shell
go get github.com/jung-kurt/gofpdf
```
Later, to receive updates, run
``` shell
go get -u -v github.com/jung-kurt/gofpdf/...
```
## Quick Start
The following Go code generates a simple PDF file.
``` go
pdf := gofpdf.New("P", "mm", "A4", "")
pdf.AddPage()
pdf.SetFont("Arial", "B", 16)
pdf.Cell(40, 10, "Hello, world")
err := pdf.OutputFileAndClose("hello.pdf")
```
See the functions in the
[fpdf\_test.go](https://github.com/jung-kurt/gofpdf/blob/master/fpdf_test.go)
file (shown as examples in this documentation) for more advanced PDF
examples.
## Errors
If an error occurs in an Fpdf method, an internal error field is set.
After this occurs, Fpdf method calls typically return without performing
any operations and the error state is retained. This error management
scheme facilitates PDF generation since individual method calls do not
need to be examined for failure; it is generally sufficient to wait
until after `Output()` is called. For the same reason, if an error
occurs in the calling application during PDF generation, it may be
desirable for the application to transfer the error to the Fpdf instance
by calling the `SetError()` method or the `SetErrorf()` method. At any
time during the life cycle of the Fpdf instance, the error state can be
determined with a call to `Ok()` or `Err()`. The error itself can be
retrieved with a call to `Error()`.
## Conversion Notes
This package is a relatively straightforward translation from the
original [FPDF](http://www.fpdf.org/) library written in PHP (despite
the caveat in the introduction to [Effective
Go](https://golang.org/doc/effective_go.html)). The API names have been
retained even though the Go idiom would suggest otherwise (for example,
`pdf.GetX()` is used rather than simply `pdf.X()`). The similarity of
the two libraries makes the original FPDF website a good source of
information. It includes a forum and FAQ.
However, some internal changes have been made. Page content is built up
using buffers (of type bytes.Buffer) rather than repeated string
concatenation. Errors are handled as explained above rather than
panicking. Output is generated through an interface of type io.Writer or
io.WriteCloser. A number of the original PHP methods behave differently
based on the type of the arguments that are passed to them; in these
cases additional methods have been exported to provide similar
functionality. Font definition files are produced in JSON rather than
PHP.
## Example PDFs
A side effect of running `go test ./...` is the production of a number
of example PDFs. These can be found in the gofpdf/pdf directory after
the tests complete.
Please note that these examples run in the context of a test. In order
run an example as a standalone application, you’ll need to examine
[fpdf\_test.go](https://github.com/jung-kurt/gofpdf/blob/master/fpdf_test.go)
for some helper routines, for example `exampleFilename()` and
`summary()`.
Example PDFs can be compared with reference copies in order to verify
that they have been generated as expected. This comparison will be
performed if a PDF with the same name as the example PDF is placed in
the gofpdf/pdf/reference directory and if the third argument to
`ComparePDFFiles()` in internal/example/example.go is true. (By default
it is false.) The routine that summarizes an example will look for this
file and, if found, will call `ComparePDFFiles()` to check the example
PDF for equality with its reference PDF. If differences exist between
the two files they will be printed to standard output and the test will
fail. If the reference file is missing, the comparison is considered to
succeed. In order to successfully compare two PDFs, the placement of
internal resources must be consistent and the internal creation
timestamps must be the same. To do this, the methods `SetCatalogSort()`
and `SetCreationDate()` need to be called for both files. This is done
automatically for all examples.
## Nonstandard Fonts
Nothing special is required to use the standard PDF fonts (courier,
helvetica, times, zapfdingbats) in your documents other than calling
`SetFont()`.
You should use `AddUTF8Font()` or `AddUTF8FontFromBytes()` to add a
TrueType UTF-8 encoded font. Use `RTL()` and `LTR()` methods switch
between “right-to-left” and “left-to-right” mode.
In order to use a different non-UTF-8 TrueType or Type1 font, you will
need to generate a font definition file and, if the font will be
embedded into PDFs, a compressed version of the font file. This is done
by calling the MakeFont function or using the included makefont command
line utility. To create the utility, cd into the makefont subdirectory
and run “go build”. This will produce a standalone executable named
makefont. Select the appropriate encoding file from the font
subdirectory and run the command as in the following example.
``` shell
./makefont --embed --enc=../font/cp1252.map --dst=../font ../font/calligra.ttf
```
In your PDF generation code, call `AddFont()` to load the font and, as
with the standard fonts, SetFont() to begin using it. Most examples,
including the package example, demonstrate this method. Good sources of
free, open-source fonts include [Google
Fonts](https://fonts.google.com/) and [DejaVu
Fonts](http://dejavu-fonts.org/).
## Related Packages
The [draw2d](https://github.com/llgcode/draw2d) package is a two
dimensional vector graphics library that can generate output in
different forms. It uses gofpdf for its document production mode.
## Contributing Changes
gofpdf is a global community effort and you are invited to make it even
better. If you have implemented a new feature or corrected a problem,
please consider contributing your change to the project. A contribution
that does not directly pertain to the core functionality of gofpdf
should be placed in its own directory directly beneath the `contrib`
directory.
Here are guidelines for making submissions. Your change should
- be compatible with the MIT License
- be properly documented
- be formatted with `go fmt`
- include an example in
[fpdf\_test.go](https://github.com/jung-kurt/gofpdf/blob/master/fpdf_test.go)
if appropriate
- conform to the standards of [golint](https://github.com/golang/lint)
and [go vet](https://golang.org/cmd/vet/), that is, `golint .` and
`go vet .` should not generate any warnings
- not diminish [test coverage](https://blog.golang.org/cover)
[Pull requests](https://help.github.com/articles/using-pull-requests/)
are the preferred means of accepting your changes.
## License
gofpdf is released under the MIT License. It is copyrighted by Kurt Jung
and the contributors acknowledged below.
## Acknowledgments
This package’s code and documentation are closely derived from the
[FPDF](http://www.fpdf.org/) library created by Olivier Plathey, and a
number of font and image resources are copied directly from it. Bruno
Michel has provided valuable assistance with the code. Drawing support
is adapted from the FPDF geometric figures script by David Hernández
Sanz. Transparency support is adapted from the FPDF transparency script
by Martin Hall-May. Support for gradients and clipping is adapted from
FPDF scripts by Andreas Würmser. Support for outline bookmarks is
adapted from Olivier Plathey by Manuel Cornes. Layer support is adapted
from Olivier Plathey. Support for transformations is adapted from the
FPDF transformation script by Moritz Wagner and Andreas Würmser. PDF
protection is adapted from the work of Klemen Vodopivec for the FPDF
product. Lawrence Kesteloot provided code to allow an image’s extent to
be determined prior to placement. Support for vertical alignment within
a cell was provided by Stefan Schroeder. Ivan Daniluk generalized the
font and image loading code to use the Reader interface while
maintaining backward compatibility. Anthony Starks provided code for the
Polygon function. Robert Lillack provided the Beziergon function and
corrected some naming issues with the internal curve function. Claudio
Felber provided implementations for dashed line drawing and generalized
font loading. Stani Michiels provided support for multi-segment path
drawing with smooth line joins, line join styles, enhanced fill modes,
and has helped greatly with package presentation and tests. Templating
is adapted by Marcus Downing from the FPDF\_Tpl library created by Jan
Slabon and Setasign. Jelmer Snoeck contributed packages that generate a
variety of barcodes and help with registering images on the web. Jelmer
Snoek and Guillermo Pascual augmented the basic HTML functionality with
aligned text. Kent Quirk implemented backwards-compatible support for
reading DPI from images that support it, and for setting DPI manually
and then having it properly taken into account when calculating image
size. Paulo Coutinho provided support for static embedded fonts. Dan
Meyers added support for embedded JavaScript. David Fish added a generic
alias-replacement function to enable, among other things, table of
contents functionality. Andy Bakun identified and corrected a problem in
which the internal catalogs were not sorted stably. Paul Montag added
encoding and decoding functionality for templates, including images that
are embedded in templates; this allows templates to be stored
independently of gofpdf. Paul also added support for page boxes used in
printing PDF documents. Wojciech Matusiak added supported for word
spacing. Artem Korotkiy added support of UTF-8 fonts. Dave Barnes added
support for imported objects and templates. Brigham Thompson added
support for rounded rectangles. Joe Westcott added underline
functionality and optimized image storage. Benoit KUGLER contributed
support for rectangles with corners of unequal radius, modification
times, and for file attachments and annotations.
## Roadmap
- Remove all legacy code page font support; use UTF-8 exclusively
- Improve test coverage as reported by the coverage tool.

157
attachments.go

@ -0,0 +1,157 @@
package gofpdf
import (
"crypto/md5"
"encoding/hex"
"fmt"
"strings"
)
// Attachment defines a content to be included in the pdf, in one
// of the following ways :
// - associated with the document as a whole : see SetAttachments()
// - accessible via a link localized on a page : see AddAttachmentAnnotation()
type Attachment struct {
Content []byte
// Filename is the displayed name of the attachment
Filename string
// Description is only displayed when using AddAttachmentAnnotation(),
// and might be modified by the pdf reader.
Description string
objectNumber int // filled when content is included
}
// return the hex encoded checksum of `data`
func checksum(data []byte) string {
tmp := md5.Sum(data)
sl := make([]byte, len(tmp))
for i, v := range tmp {
sl[i] = v
}
return hex.EncodeToString(sl)
}
// Writes a compressed file like object as ``/EmbeddedFile``. Compressing is
// done with deflate. Includes length, compressed length and MD5 checksum.
func (f *Fpdf) writeCompressedFileObject(content []byte) {
lenUncompressed := len(content)
sum := checksum(content)
compressed := sliceCompress(content)
lenCompressed := len(compressed)
f.newobj()
f.outf("<< /Type /EmbeddedFile /Length %d /Filter /FlateDecode /Params << /CheckSum <%s> /Size %d >> >>\n",
lenCompressed, sum, lenUncompressed)
f.putstream(compressed)
f.out("endobj")
}
// Embed includes the content of `a`, and update its internal reference.
func (f *Fpdf) embed(a *Attachment) {
if a.objectNumber != 0 { // already embedded (objectNumber start at 2)
return
}
oldState := f.state
f.state = 1 // we write file content in the main buffer
f.writeCompressedFileObject(a.Content)
streamID := f.n
f.newobj()
f.outf("<< /Type /Filespec /F () /UF %s /EF << /F %d 0 R >> /Desc %s\n>>",
f.textstring(utf8toutf16(a.Filename)),
streamID,
f.textstring(utf8toutf16(a.Description)))
f.out("endobj")
a.objectNumber = f.n
f.state = oldState
}
// SetAttachments writes attachments as embedded files (document attachment).
// These attachments are global, see AddAttachmentAnnotation() for a link
// anchored in a page. Note that only the last call of SetAttachments is
// useful, previous calls are discarded. Be aware that not all PDF readers
// support document attachments. See the SetAttachment example for a
// demonstration of this method.
func (f *Fpdf) SetAttachments(as []Attachment) {
f.attachments = as
}
// embed current attachments. store object numbers
// for later use by getEmbeddedFiles()
func (f *Fpdf) putAttachments() {
for i, a := range f.attachments {
f.embed(&a)
f.attachments[i] = a
}
}
// return /EmbeddedFiles tree name catalog entry.
func (f Fpdf) getEmbeddedFiles() string {
names := make([]string, len(f.attachments))
for i, as := range f.attachments {
names[i] = fmt.Sprintf("(Attachement%d) %d 0 R ", i+1, as.objectNumber)
}
nameTree := fmt.Sprintf("<< /Names [\n %s \n] >>", strings.Join(names, "\n"))
return nameTree
}
// ---------------------------------- Annotations ----------------------------------
type annotationAttach struct {
*Attachment
x, y, w, h float64 // fpdf coordinates (y diff and scaling done)
}
// AddAttachmentAnnotation puts a link on the current page, on the rectangle
// defined by `x`, `y`, `w`, `h`. This link points towards the content defined
// in `a`, which is embedded in the document. Note than no drawing is done by
// this method : a method like `Cell()` or `Rect()` should be called to
// indicate to the reader that there is a link here. Requiring a pointer to an
// Attachment avoids useless copies in the resulting pdf: attachment pointing
// to the same data will have their content only be included once, and be
// shared amongst all links. Be aware that not all PDF readers support
// annotated attachments. See the AddAttachmentAnnotation example for a
// demonstration of this method.
func (f *Fpdf) AddAttachmentAnnotation(a *Attachment, x, y, w, h float64) {
if a == nil {
return
}
f.pageAttachments[f.page] = append(f.pageAttachments[f.page], annotationAttach{
Attachment: a,
x: x * f.k, y: f.hPt - y*f.k, w: w * f.k, h: h * f.k,
})
}
// embed current annotations attachments. store object numbers
// for later use by putAttachmentAnnotationLinks(), which is
// called for each page.
func (f *Fpdf) putAnnotationsAttachments() {
// avoid duplication
m := map[*Attachment]bool{}
for _, l := range f.pageAttachments {
for _, an := range l {
if m[an.Attachment] { // already embedded
continue
}
f.embed(an.Attachment)
}
}
}
func (f *Fpdf) putAttachmentAnnotationLinks(out *fmtBuffer, page int) {
for _, an := range f.pageAttachments[page] {
x1, y1, x2, y2 := an.x, an.y, an.x+an.w, an.y-an.h
as := fmt.Sprintf("<< /Type /XObject /Subtype /Form /BBox [%.2f %.2f %.2f %.2f] /Length 0 >>",
x1, y1, x2, y2)
as += "\nstream\nendstream"
out.printf("<< /Type /Annot /Subtype /FileAttachment /Rect [%.2f %.2f %.2f %.2f] /Border [0 0 0]\n",
x1, y1, x2, y2)
out.printf("/Contents %s ", f.textstring(utf8toutf16(an.Description)))
out.printf("/T %s ", f.textstring(utf8toutf16(an.Filename)))
out.printf("/AP << /N %s>>", as)
out.printf("/FS %d 0 R >>\n", an.objectNumber)
}
}

146
compare.go

@ -0,0 +1,146 @@
/*
* Copyright (c) 2015 Kurt Jung (Gmail: kurt.w.jung)
*
* 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 gofpdf
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"sort"
)
type sortType struct {
length int
less func(int, int) bool
swap func(int, int)
}
func (s *sortType) Len() int {
return s.length
}
func (s *sortType) Less(i, j int) bool {
return s.less(i, j)
}
func (s *sortType) Swap(i, j int) {
s.swap(i, j)
}
func gensort(Len int, Less func(int, int) bool, Swap func(int, int)) {
sort.Sort(&sortType{length: Len, less: Less, swap: Swap})
}
func writeBytes(leadStr string, startPos int, sl []byte) {
var pos, max int
var b byte
fmt.Printf("%s %07x", leadStr, startPos)
max = len(sl)
for pos < max {
fmt.Printf(" ")
for k := 0; k < 8; k++ {
if pos < max {
fmt.Printf(" %02x", sl[pos])
} else {
fmt.Printf(" ")
}
pos++
}
}
fmt.Printf(" |")
pos = 0
for pos < max {
b = sl[pos]
if b < 32 || b >= 128 {
b = '.'
}
fmt.Printf("%c", b)
pos++
}
fmt.Printf("|\n")
}
func checkBytes(pos int, sl1, sl2 []byte, printDiff bool) (eq bool) {
eq = bytes.Equal(sl1, sl2)
if !eq && printDiff {
writeBytes("<", pos, sl1)
writeBytes(">", pos, sl2)
}
return
}
// CompareBytes compares the bytes referred to by sl1 with those referred to by
// sl2. Nil is returned if the buffers are equal, otherwise an error.
func CompareBytes(sl1, sl2 []byte, printDiff bool) (err error) {
var posStart, posEnd, len1, len2, length int
var diffs bool
len1 = len(sl1)
len2 = len(sl2)
length = len1
if length > len2 {
length = len2
}
for posStart < length-1 {
posEnd = posStart + 16
if posEnd > length {
posEnd = length
}
if !checkBytes(posStart, sl1[posStart:posEnd], sl2[posStart:posEnd], printDiff) {
diffs = true
}
posStart = posEnd
}
if diffs {
err = fmt.Errorf("documents are different")
}
return
}
// ComparePDFs reads and compares the full contents of the two specified
// readers byte-for-byte. Nil is returned if the buffers are equal, otherwise
// an error.
func ComparePDFs(rdr1, rdr2 io.Reader, printDiff bool) (err error) {
var b1, b2 *bytes.Buffer
_, err = b1.ReadFrom(rdr1)
if err == nil {
_, err = b2.ReadFrom(rdr2)
if err == nil {
err = CompareBytes(b1.Bytes(), b2.Bytes(), printDiff)
}
}
return
}
// ComparePDFFiles reads and compares the full contents of the two specified
// files byte-for-byte. Nil is returned if the file contents are equal, or if
// the second file is missing, otherwise an error.
func ComparePDFFiles(file1Str, file2Str string, printDiff bool) (err error) {
var sl1, sl2 []byte
sl1, err = ioutil.ReadFile(file1Str)
if err == nil {
sl2, err = ioutil.ReadFile(file2Str)
if err == nil {
err = CompareBytes(sl1, sl2, printDiff)
} else {
// Second file is missing; treat this as success
err = nil
}
}
return
}

302
contrib/barcode/barcode.go

@ -0,0 +1,302 @@
// 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
}

222
contrib/barcode/barcode_test.go

@ -0,0 +1,222 @@
package barcode_test
import (
"testing"
"github.com/boombuler/barcode/code128"
"github.com/boombuler/barcode/qr"
"github.com/jung-kurt/gofpdf"
"github.com/jung-kurt/gofpdf/contrib/barcode"
"github.com/jung-kurt/gofpdf/internal/example"
)
func createPdf() (pdf *gofpdf.Fpdf) {
pdf = gofpdf.New("L", "mm", "A4", "")
pdf.SetFont("Helvetica", "", 12)
pdf.SetFillColor(200, 200, 220)
pdf.AddPage()
return
}
func ExampleRegister() {
pdf := createPdf()
fileStr := example.Filename("contrib_barcode_Register")
bcode, err := code128.Encode("gofpdf")
if err == nil {
key := barcode.Register(bcode)
var width float64 = 100
var height float64 = 10.0
barcode.BarcodeUnscalable(pdf, key, 15, 15, &width, &height, false)
}
err = pdf.OutputFileAndClose(fileStr)
example.Summary(err, fileStr)
// Output:
// Successfully generated ../../pdf/contrib_barcode_Register.pdf
}
func ExampleRegisterCodabar() {
pdf := createPdf()
key := barcode.RegisterCode128(pdf, "codabar")
var width float64 = 100
var height float64 = 10
barcode.BarcodeUnscalable(pdf, key, 15, 15, &width, &height, false)
fileStr := example.Filename("contrib_barcode_RegisterCodabar")
err := pdf.OutputFileAndClose(fileStr)
example.Summary(err, fileStr)
// Output:
// Successfully generated ../../pdf/contrib_barcode_RegisterCodabar.pdf
}
func ExampleRegisterAztec() {
pdf := createPdf()
key := barcode.RegisterAztec(pdf, "aztec", 33, 0)
barcode.Barcode(pdf, key, 15, 15, 100, 100, false)
fileStr := example.Filename("contrib_barcode_RegisterAztec")
err := pdf.OutputFileAndClose(fileStr)
example.Summary(err, fileStr)
// Output:
// Successfully generated ../../pdf/contrib_barcode_RegisterAztec.pdf
}
func ExampleRegisterCode128() {
pdf := createPdf()
key := barcode.RegisterCode128(pdf, "code128")
barcode.Barcode(pdf, key, 15, 15, 100, 10, false)
fileStr := example.Filename("contrib_barcode_RegisterCode128")
err := pdf.OutputFileAndClose(fileStr)
example.Summary(err, fileStr)
// Output:
// Successfully generated ../../pdf/contrib_barcode_RegisterCode128.pdf
}
func ExampleRegisterCode39() {
pdf := createPdf()
key := barcode.RegisterCode39(pdf, "CODE39", false, true)
barcode.Barcode(pdf, key, 15, 15, 100, 10, false)
fileStr := example.Filename("contrib_barcode_RegisterCode39")
err := pdf.OutputFileAndClose(fileStr)
example.Summary(err, fileStr)
// Output:
// Successfully generated ../../pdf/contrib_barcode_RegisterCode39.pdf
}
func ExampleRegisterDataMatrix() {
pdf := createPdf()
key := barcode.RegisterDataMatrix(pdf, "datamatrix")
barcode.Barcode(pdf, key, 15, 15, 20, 20, false)
fileStr := example.Filename("contrib_barcode_RegisterDataMatrix")
err := pdf.OutputFileAndClose(fileStr)
example.Summary(err, fileStr)
// Output:
// Successfully generated ../../pdf/contrib_barcode_RegisterDataMatrix.pdf
}
func ExampleRegisterEAN() {
pdf := createPdf()
key := barcode.RegisterEAN(pdf, "96385074")
barcode.Barcode(pdf, key, 15, 15, 100, 10, false)
fileStr := example.Filename("contrib_barcode_RegisterEAN")
err := pdf.OutputFileAndClose(fileStr)
example.Summary(err, fileStr)
// Output:
// Successfully generated ../../pdf/contrib_barcode_RegisterEAN.pdf
}
func ExampleRegisterQR() {
pdf := createPdf()
key := barcode.RegisterQR(pdf, "qrcode", qr.H, qr.Unicode)
barcode.Barcode(pdf, key, 15, 15, 100, 10, false)
fileStr := example.Filename("contrib_barcode_RegisterQR")
err := pdf.OutputFileAndClose(fileStr)
example.Summary(err, fileStr)
// Output:
// Successfully generated ../../pdf/contrib_barcode_RegisterQR.pdf
}
func ExampleRegisterTwoOfFive() {
pdf := createPdf()
key := barcode.RegisterTwoOfFive(pdf, "1234567895", true)
barcode.Barcode(pdf, key, 15, 15, 100, 10, false)
fileStr := example.Filename("contrib_barcode_RegisterTwoOfFive")
err := pdf.OutputFileAndClose(fileStr)
example.Summary(err, fileStr)
// Output:
// Successfully generated ../../pdf/contrib_barcode_RegisterTwoOfFive.pdf
}
func ExampleRegisterPdf417() {
pdf := createPdf()
key := barcode.RegisterPdf417(pdf, "1234567895", 10, 5)
barcode.Barcode(pdf, key, 15, 15, 100, 10, false)
fileStr := example.Filename("contrib_barcode_RegisterPdf417")
err := pdf.OutputFileAndClose(fileStr)
example.Summary(err, fileStr)
// Output:
// Successfully generated ../../pdf/contrib_barcode_RegisterPdf417.pdf
}
// TestRegisterCode128 ensures that no panic arises when an invalid barcode is registered.
func TestRegisterCode128(t *testing.T) {
pdf := createPdf()
barcode.RegisterCode128(pdf, "Invalid character: é")
}
// TestBarcodeUnscalable shows that the barcode may be scaled or not by providing optional heights and widths.
func TestBarcodeUnscalable(t *testing.T) {
pdf := createPdf()
key := barcode.RegisterCode128(pdf, "code128")
var width float64 = 100
var height float64 = 10
barcode.BarcodeUnscalable(pdf, key, 15, 15, &width, &height, false)
barcode.BarcodeUnscalable(pdf, key, 15, 35, nil, &height, false)
barcode.BarcodeUnscalable(pdf, key, 15, 55, &width, nil, false)
barcode.BarcodeUnscalable(pdf, key, 15, 75, nil, nil, false)
fileStr := example.Filename("contrib_barcode_Barcode")
err := pdf.OutputFileAndClose(fileStr)
example.Summary(err, fileStr)
// Output:
// Successfully generated ../../pdf/contrib_barcode_Barcode.pdf
}
// TestGetUnscaledBarcodeDimensions shows that the width and height returned by the function match that of the barcode
func TestGetUnscaledBarcodeDimensions(t *testing.T) {
pdf := createPdf()
key := barcode.RegisterQR(pdf, "qrcode", qr.H, qr.Unicode)
barcode.BarcodeUnscalable(pdf, key, 15, 15, nil, nil, false)
w, h := barcode.GetUnscaledBarcodeDimensions(pdf, key)
pdf.SetDrawColor(255, 0, 0)
pdf.Line(15, 15, 15+w, 15+h)
fileStr := example.Filename("contrib_barcode_GetBarcodeDimensions")
err := pdf.OutputFileAndClose(fileStr)
example.Summary(err, fileStr)
// Output:
// Successfully generated ../../pdf/contrib_barcode_GetBarcodeDimensions.pdf
}
// TestBarcodeNonIntegerScalingFactors shows that the barcode may be scaled to non-integer sizes
func TestBarcodeNonIntegerScalingFactors(t *testing.T) {
pdf := gofpdf.New("L", "in", "A4", "")
pdf.SetFont("Helvetica", "", 12)
pdf.SetFillColor(200, 200, 220)
pdf.AddPage()
key := barcode.RegisterQR(pdf, "qrcode", qr.H, qr.Unicode)
var scale float64 = 1.5
barcode.BarcodeUnscalable(pdf, key, 0.5, 0.5, &scale, &scale, false)
pdf.SetDrawColor(255, 0, 0)
pdf.Line(0.5, 0.5, 0.5+scale, 0.5+scale)
fileStr := example.Filename("contrib_barcode_BarcodeScaling")
err := pdf.OutputFileAndClose(fileStr)
example.Summary(err, fileStr)
// Output:
// Successfully generated ../../pdf/contrib_barcode_BarcodeScaling.pdf
}

68
contrib/ghostscript/ghostscript.go

@ -0,0 +1,68 @@
package main
// This command demonstrates the use of ghotscript to reduce the size
// of generated PDFs. This is based on a comment made by farkerhaiku:
// https://github.com/jung-kurt/gofpdf/issues/57#issuecomment-185843315
import (
"fmt"
"os"
"os/exec"
"github.com/jung-kurt/gofpdf"
)
func report(fileStr string, err error) {
if err == nil {
var info os.FileInfo
info, err = os.Stat(fileStr)
if err == nil {
fmt.Printf("%s: OK, size %d\n", fileStr, info.Size())
} else {
fmt.Printf("%s: bad stat\n", fileStr)
}
} else {
fmt.Printf("%s: %s\n", fileStr, err)
}
}
func newPdf() (pdf *gofpdf.Fpdf) {
pdf = gofpdf.New("P", "mm", "A4", "../../font")
pdf.SetCompression(false)
pdf.AddFont("Calligrapher", "", "calligra.json")
pdf.AddPage()
pdf.SetFont("Calligrapher", "", 35)
pdf.Cell(0, 10, "Enjoy new fonts with FPDF!")
return
}
func full(name string) {
report(name, newPdf().OutputFileAndClose(name))
}
func min(name string) {
cmd := exec.Command("gs", "-sDEVICE=pdfwrite",
"-dCompatibilityLevel=1.4",
"-dPDFSETTINGS=/screen", "-dNOPAUSE", "-dQUIET",
"-dBATCH", "-sOutputFile="+name, "-")
inPipe, err := cmd.StdinPipe()
if err == nil {
errChan := make(chan error, 1)
go func() {
errChan <- cmd.Start()
}()
err = newPdf().Output(inPipe)
if err == nil {
report(name, <-errChan)
} else {
report(name, err)
}
} else {
report(name, err)
}
}
func main() {
full("full.pdf")
min("min.pdf")
}

143
contrib/gofpdi/gofpdi.go

@ -0,0 +1,143 @@
/*
Package gofpdi wraps the gofpdi PDF library to import existing PDFs as templates. See github.com/phpdave11/gofpdi
for further information and examples.
Users should call NewImporter() to obtain their own Importer instance to work with.
To retain backwards compatibility, the package offers a default Importer that may be used via global functions. Note
however that use of the default Importer is not thread safe.
*/
package gofpdi
import (
realgofpdi "github.com/phpdave11/gofpdi"
"io"
)
// gofpdiPdf is a partial interface that only implements the functions we need
// from the PDF generator to put the imported PDF templates on the PDF.
type gofpdiPdf interface {
ImportObjects(objs map[string][]byte)
ImportObjPos(objs map[string]map[int]string)
ImportTemplates(tpls map[string]string)
UseImportedTemplate(tplName string, x float64, y float64, w float64, h float64)
SetError(err error)
}
// Importer wraps an Importer from the gofpdi library.
type Importer struct {
fpdi *realgofpdi.Importer
}
// NewImporter creates a new Importer wrapping functionality from the gofpdi library.
func NewImporter() *Importer {
return &Importer{
fpdi: realgofpdi.NewImporter(),
}
}
// ImportPage imports a page of a PDF file with the specified box (/MediaBox,
// /TrimBox, /ArtBox, /CropBox, or /BleedBox). Returns a template id that can
// be used with UseImportedTemplate to draw the template onto the page.
func (i *Importer) ImportPage(f gofpdiPdf, sourceFile string, pageno int, box string) int {
// Set source file for fpdi
i.fpdi.SetSourceFile(sourceFile)
// return template id
return i.getTemplateID(f, pageno, box)
}
// ImportPageFromStream imports a page of a PDF with the specified box
// (/MediaBox, TrimBox, /ArtBox, /CropBox, or /BleedBox). Returns a template id
// that can be used with UseImportedTemplate to draw the template onto the
// page.
func (i *Importer) ImportPageFromStream(f gofpdiPdf, rs *io.ReadSeeker, pageno int, box string) int {
// Set source stream for fpdi
i.fpdi.SetSourceStream(rs)
// return template id
return i.getTemplateID(f, pageno, box)
}
func (i *Importer) getTemplateID(f gofpdiPdf, pageno int, box string) int {
// Import page
tpl := i.fpdi.ImportPage(pageno, box)
// Import objects into current pdf document
// Unordered means that the objects will be returned with a sha1 hash instead of an integer
// The objects themselves may have references to other hashes which will be replaced in ImportObjects()
tplObjIDs := i.fpdi.PutFormXobjectsUnordered()
// Set template names and ids (hashes) in gofpdf
f.ImportTemplates(tplObjIDs)
// Get a map[string]string of the imported objects.
// The map keys will be the ID of each object.
imported := i.fpdi.GetImportedObjectsUnordered()
// Import gofpdi objects into gofpdf
f.ImportObjects(imported)
// Get a map[string]map[int]string of the object hashes and their positions within each object,
// to be replaced with object ids (integers).
importedObjPos := i.fpdi.GetImportedObjHashPos()
// Import gofpdi object hashes and their positions into gopdf
f.ImportObjPos(importedObjPos)
return tpl
}
// UseImportedTemplate draws the template onto the page at x,y. If w is 0, the
// template will be scaled to fit based on h. If h is 0, the template will be
// scaled to fit based on w.
func (i *Importer) UseImportedTemplate(f gofpdiPdf, tplid int, x float64, y float64, w float64, h float64) {
// Get values from fpdi
tplName, scaleX, scaleY, tX, tY := i.fpdi.UseTemplate(tplid, x, y, w, h)
f.UseImportedTemplate(tplName, scaleX, scaleY, tX, tY)
}
// GetPageSizes returns page dimensions for all pages of the imported pdf.
// Result consists of map[<page number>]map[<box>]map[<dimension>]<value>.
// <page number>: page number, note that page numbers start at 1
// <box>: box identifier, e.g. "/MediaBox"
// <dimension>: dimension string, either "w" or "h"
func (i *Importer) GetPageSizes() map[int]map[string]map[string]float64 {
return i.fpdi.GetPageSizes()
}
// Default Importer used by global functions
var fpdi = NewImporter()
// ImportPage imports a page of a PDF file with the specified box (/MediaBox,
// /TrimBox, /ArtBox, /CropBox, or /BleedBox). Returns a template id that can
// be used with UseImportedTemplate to draw the template onto the page.
// Note: This uses the default Importer. Call NewImporter() to obtain a custom Importer.
func ImportPage(f gofpdiPdf, sourceFile string, pageno int, box string) int {
return fpdi.ImportPage(f, sourceFile, pageno, box)
}
// ImportPageFromStream imports a page of a PDF with the specified box
// (/MediaBox, TrimBox, /ArtBox, /CropBox, or /BleedBox). Returns a template id
// that can be used with UseImportedTemplate to draw the template onto the
// page.
// Note: This uses the default Importer. Call NewImporter() to obtain a custom Importer.
func ImportPageFromStream(f gofpdiPdf, rs *io.ReadSeeker, pageno int, box string) int {
return fpdi.ImportPageFromStream(f, rs, pageno, box)
}
// UseImportedTemplate draws the template onto the page at x,y. If w is 0, the
// template will be scaled to fit based on h. If h is 0, the template will be
// scaled to fit based on w.
// Note: This uses the default Importer. Call NewImporter() to obtain a custom Importer.
func UseImportedTemplate(f gofpdiPdf, tplid int, x float64, y float64, w float64, h float64) {
fpdi.UseImportedTemplate(f, tplid, x, y, w, h)
}
// GetPageSizes returns page dimensions for all pages of the imported pdf.
// Result consists of map[<page number>]map[<box>]map[<dimension>]<value>.
// <page number>: page number, note that page numbers start at 1
// <box>: box identifier, e.g. "/MediaBox"
// <dimension>: dimension string, either "w" or "h"
// Note: This uses the default Importer. Call NewImporter() to obtain a custom Importer.
func GetPageSizes() map[int]map[string]map[string]float64 {
return fpdi.GetPageSizes()
}

76
contrib/gofpdi/gofpdi_test.go

@ -0,0 +1,76 @@
package gofpdi
import (
"bytes"
"github.com/jung-kurt/gofpdf"
"github.com/jung-kurt/gofpdf/internal/example"
"io"
"sync"
"testing"
)
func ExampleNewImporter() {
// create new pdf
pdf := gofpdf.New("P", "pt", "A4", "")
// for testing purposes, get an arbitrary template pdf as stream
rs, _ := getTemplatePdf()
// create a new Importer instance
imp := NewImporter()
// import first page and determine page sizes
tpl := imp.ImportPageFromStream(pdf, &rs, 1, "/MediaBox")
pageSizes := imp.GetPageSizes()
nrPages := len(imp.GetPageSizes())
// add all pages from template pdf
for i := 1; i <= nrPages; i++ {
pdf.AddPage()
if i > 1 {
tpl = imp.ImportPageFromStream(pdf, &rs, i, "/MediaBox")
}
imp.UseImportedTemplate(pdf, tpl, 0, 0, pageSizes[i]["/MediaBox"]["w"], pageSizes[i]["/MediaBox"]["h"])
}
// output
fileStr := example.Filename("contrib_gofpdi_Importer")
err := pdf.OutputFileAndClose(fileStr)
example.Summary(err, fileStr)
// Output:
// Successfully generated ../../pdf/contrib_gofpdi_Importer.pdf
}
func TestGofpdiConcurrent(t *testing.T) {
wg := sync.WaitGroup{}
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
defer wg.Done()
pdf := gofpdf.New("P", "mm", "A4", "")
pdf.AddPage()
rs, _ := getTemplatePdf()
imp := NewImporter()
tpl := imp.ImportPageFromStream(pdf, &rs, 1, "/MediaBox")
imp.UseImportedTemplate(pdf, tpl, 0, 0, 210.0, 297.0)
// write to bytes buffer
buf := bytes.Buffer{}
if err := pdf.Output(&buf); err != nil {
t.Fail()
}
}()
}
wg.Wait()
}
func getTemplatePdf() (io.ReadSeeker, error) {
tpdf := gofpdf.New("P", "pt", "A4", "")
tpdf.AddPage()
tpdf.SetFont("Arial", "", 12)
tpdf.Text(20, 20, "Example Page 1")
tpdf.AddPage()
tpdf.Text(20, 20, "Example Page 2")
tbuf := bytes.Buffer{}
err := tpdf.Output(&tbuf)
return bytes.NewReader(tbuf.Bytes()), err
}

43
contrib/httpimg/httpimg.go

@ -0,0 +1,43 @@
package httpimg
import (
"io"
"net/http"
"github.com/jung-kurt/gofpdf"
)
// httpimgPdf is a partial interface that only implements the functions we need
// from the PDF generator to put the HTTP images on the PDF.
type httpimgPdf interface {
GetImageInfo(imageStr string) *gofpdf.ImageInfoType
ImageTypeFromMime(mimeStr string) string
RegisterImageReader(imgName, tp string, r io.Reader) *gofpdf.ImageInfoType
SetError(err error)
}
// Register registers a HTTP image. Downloading the image from the provided URL
// and adding it to the PDF but not adding it to the page. Use Image() with the
// same URL to add the image to the page.
func Register(f httpimgPdf, urlStr, tp string) (info *gofpdf.ImageInfoType) {
info = f.GetImageInfo(urlStr)
if info != nil {
return
}
resp, err := http.Get(urlStr)
if err != nil {
f.SetError(err)
return
}
defer resp.Body.Close()
if tp == "" {
tp = f.ImageTypeFromMime(resp.Header["Content-Type"][0])
}
return f.RegisterImageReader(urlStr, tp, resp.Body)
}

23
contrib/httpimg/httpimg_test.go

@ -0,0 +1,23 @@
package httpimg_test
import (
"github.com/jung-kurt/gofpdf"
"github.com/jung-kurt/gofpdf/contrib/httpimg"
"github.com/jung-kurt/gofpdf/internal/example"
)
func ExampleRegister() {
pdf := gofpdf.New("L", "mm", "A4", "")
pdf.SetFont("Helvetica", "", 12)
pdf.SetFillColor(200, 200, 220)
pdf.AddPage()
url := "https://github.com/jung-kurt/gofpdf/raw/master/image/logo_gofpdf.jpg?raw=true"
httpimg.Register(pdf, url, "")
pdf.Image(url, 15, 15, 267, 0, false, "", 0, "")
fileStr := example.Filename("contrib_httpimg_Register")
err := pdf.OutputFileAndClose(fileStr)
example.Summary(err, fileStr)
// Output:
// Successfully generated ../../pdf/contrib_httpimg_Register.pdf
}

83
contrib/tiff/tiff.go

@ -0,0 +1,83 @@
/*
* Copyright (c) 2016 Kurt Jung (Gmail: kurt.w.jung)
*
* 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 tiff allows standard (LZW-compressed) TIFF images to be used in
// documents generated with gofpdf.
package tiff
import (
"bytes"
"fmt"
"image"
"image/png"
"io"
"os"
"github.com/jung-kurt/gofpdf"
"golang.org/x/image/tiff"
)
// RegisterReader registers a TIFF image, adding it to the PDF file but not
// adding it to the page. imgName specifies the name that will be used in the
// call to Image() that actually places the image in the document. options
// specifies various image properties; in this case, the ImageType property
// should be set to "tiff". The TIFF image is a reader from the reader
// specified by r.
func RegisterReader(fpdf *gofpdf.Fpdf, imgName string, options gofpdf.ImageOptions, r io.Reader) (info *gofpdf.ImageInfoType) {
var err error
var img image.Image
var buf bytes.Buffer
if fpdf.Ok() {
if options.ImageType == "tiff" || options.ImageType == "tif" {
img, err = tiff.Decode(r)
if err == nil {
err = png.Encode(&buf, img)
if err == nil {
options.ImageType = "png"
info = fpdf.RegisterImageOptionsReader(imgName, options, &buf)
}
}
} else {
err = fmt.Errorf("expecting \"tiff\" or \"tif\" as image type, got \"%s\"", options.ImageType)
}
if err != nil {
fpdf.SetError(err)
}
}
return
}
// RegisterFile registers a TIFF image, adding it to the PDF file but not
// adding it to the page. imgName specifies the name that will be used in the
// call to Image() that actually places the image in the document. options
// specifies various image properties; in this case, the ImageType property
// should be set to "tiff". The TIFF image is read from the file specified by
// tiffFileStr.
func RegisterFile(fpdf *gofpdf.Fpdf, imgName string, options gofpdf.ImageOptions, tiffFileStr string) (info *gofpdf.ImageInfoType) {
var f *os.File