Kurt Jungs Auslieferungszustand
This commit is contained in:
commit
5afea81825
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -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
Normal file
29
Makefile
Normal file
|
@ -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
Normal file
282
README.md
Normal file
|
@ -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
Normal file
157
attachments.go
Normal file
|
@ -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
Normal file
146
compare.go
Normal file
|
@ -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
Normal file
302
contrib/barcode/barcode.go
Normal file
|
@ -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
Normal file
222
contrib/barcode/barcode_test.go
Normal file
|
@ -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
Normal file
68
contrib/ghostscript/ghostscript.go
Normal file
|
@ -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
Normal file
143
contrib/gofpdi/gofpdi.go
Normal file
|
@ -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
Normal file
76
contrib/gofpdi/gofpdi_test.go
Normal file
|
@ -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
Normal file
43
contrib/httpimg/httpimg.go
Normal file
|
@ -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
Normal file
23
contrib/httpimg/httpimg_test.go
Normal file
|
@ -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
Normal file
83
contrib/tiff/tiff.go
Normal file
|
@ -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.
|
||||