Parcourir la source

Enterprise: add dependencies for upcoming features (#18793)

* Enterprise: add dependencies for upcoming features

See enterprise issues
Oleg Gaidarenko il y a 6 ans
Parent
commit
1a4be4af8c
46 fichiers modifiés avec 13594 ajouts et 1 suppressions
  1. 3 1
      go.mod
  2. 9 0
      go.sum
  3. 5 0
      pkg/extensions/main.go
  4. 1 0
      vendor/github.com/jung-kurt/gofpdf/.gitattribute
  5. 25 0
      vendor/github.com/jung-kurt/gofpdf/.gitignore
  6. 21 0
      vendor/github.com/jung-kurt/gofpdf/LICENSE
  7. 29 0
      vendor/github.com/jung-kurt/gofpdf/Makefile
  8. 256 0
      vendor/github.com/jung-kurt/gofpdf/README.md
  9. 146 0
      vendor/github.com/jung-kurt/gofpdf/compare.go
  10. 734 0
      vendor/github.com/jung-kurt/gofpdf/def.go
  11. 268 0
      vendor/github.com/jung-kurt/gofpdf/doc.go
  12. 559 0
      vendor/github.com/jung-kurt/gofpdf/embedded.go
  13. 474 0
      vendor/github.com/jung-kurt/gofpdf/font.go
  14. 4860 0
      vendor/github.com/jung-kurt/gofpdf/fpdf.go
  15. 213 0
      vendor/github.com/jung-kurt/gofpdf/fpdftrans.go
  16. 12 0
      vendor/github.com/jung-kurt/gofpdf/go.mod
  17. 18 0
      vendor/github.com/jung-kurt/gofpdf/go.sum
  18. 446 0
      vendor/github.com/jung-kurt/gofpdf/grid.go
  19. 220 0
      vendor/github.com/jung-kurt/gofpdf/htmlbasic.go
  20. 82 0
      vendor/github.com/jung-kurt/gofpdf/label.go
  21. 121 0
      vendor/github.com/jung-kurt/gofpdf/layer.go
  22. 213 0
      vendor/github.com/jung-kurt/gofpdf/png.go
  23. 114 0
      vendor/github.com/jung-kurt/gofpdf/protect.go
  24. 53 0
      vendor/github.com/jung-kurt/gofpdf/splittext.go
  25. 184 0
      vendor/github.com/jung-kurt/gofpdf/spotcolor.go
  26. 35 0
      vendor/github.com/jung-kurt/gofpdf/subwrite.go
  27. 246 0
      vendor/github.com/jung-kurt/gofpdf/svgbasic.go
  28. 85 0
      vendor/github.com/jung-kurt/gofpdf/svgwrite.go
  29. 273 0
      vendor/github.com/jung-kurt/gofpdf/template.go
  30. 299 0
      vendor/github.com/jung-kurt/gofpdf/template_impl.go
  31. 374 0
      vendor/github.com/jung-kurt/gofpdf/ttfparser.go
  32. 1153 0
      vendor/github.com/jung-kurt/gofpdf/utf8fontfile.go
  33. 454 0
      vendor/github.com/jung-kurt/gofpdf/util.go
  34. 22 0
      vendor/github.com/robfig/cron/v3/.gitignore
  35. 21 0
      vendor/github.com/robfig/cron/v3/LICENSE
  36. 125 0
      vendor/github.com/robfig/cron/v3/README.md
  37. 92 0
      vendor/github.com/robfig/cron/v3/chain.go
  38. 27 0
      vendor/github.com/robfig/cron/v3/constantdelay.go
  39. 350 0
      vendor/github.com/robfig/cron/v3/cron.go
  40. 212 0
      vendor/github.com/robfig/cron/v3/doc.go
  41. 3 0
      vendor/github.com/robfig/cron/v3/go.mod
  42. 86 0
      vendor/github.com/robfig/cron/v3/logger.go
  43. 45 0
      vendor/github.com/robfig/cron/v3/option.go
  44. 434 0
      vendor/github.com/robfig/cron/v3/parser.go
  45. 188 0
      vendor/github.com/robfig/cron/v3/spec.go
  46. 4 0
      vendor/modules.txt

+ 3 - 1
go.mod

@@ -40,7 +40,9 @@ require (
 	github.com/hashicorp/go-plugin v0.0.0-20190220160451-3f118e8ee104
 	github.com/hashicorp/go-version v1.1.0
 	github.com/inconshreveable/log15 v0.0.0-20180818164646-67afb5ed74ec
+	github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af
 	github.com/jonboulle/clockwork v0.1.0 // indirect
+	github.com/jung-kurt/gofpdf v1.10.1
 	github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 // indirect
 	github.com/klauspost/compress v1.4.1 // indirect
 	github.com/klauspost/cpuid v1.2.0 // indirect
@@ -52,12 +54,12 @@ require (
 	github.com/onsi/gomega v1.5.0 // indirect
 	github.com/opentracing/opentracing-go v1.1.0
 	github.com/patrickmn/go-cache v2.1.0+incompatible
-	github.com/pkg/errors v0.8.1
 	github.com/prometheus/client_golang v0.9.2
 	github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90
 	github.com/prometheus/common v0.2.0
 	github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be // indirect
 	github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967
+	github.com/robfig/cron/v3 v3.0.0
 	github.com/russellhaering/goxmldsig v0.0.0-20180430223755-7acd5e4a6ef7 // indirect
 	github.com/sergi/go-diff v1.0.0 // indirect
 	github.com/smartystreets/assertions v0.0.0-20190401211740-f487f9de1cd3 // indirect

+ 9 - 0
go.sum

@@ -16,6 +16,7 @@ github.com/benbjohnson/clock v0.0.0-20161215174838-7dc76406b6d3 h1:wOysYcIdqv3Wn
 github.com/benbjohnson/clock v0.0.0-20161215174838-7dc76406b6d3/go.mod h1:UMqtWQTnOe4byzwe7Zhwh8f8s+36uszN51sJrSIZlTE=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
+github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
 github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737 h1:rRISKWyXfVxvoa702s91Zl5oREZTrR3yv+tXrrX7G/g=
 github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
 github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
@@ -117,6 +118,9 @@ github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVY
 github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
 github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
+github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
+github.com/jung-kurt/gofpdf v1.10.1 h1:mbprTswkr0n86clAmJ4NGCFC4fdGySCAshWzrYb3xbE=
+github.com/jung-kurt/gofpdf v1.10.1/go.mod h1:s/VXv+TdctEOx2wCEguezYaR7f0OwUAd6H9VGfRkcSs=
 github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 h1:uC1QfSlInpQF+M0ao65imhwqKnz3Q2z/d8PWZRMQvDM=
 github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
 github.com/klauspost/compress v1.4.1 h1:8VMb5+0wMgdBykOV96DwNwKFQ+WTI4pzYURP99CcB9E=
@@ -158,6 +162,7 @@ github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsq
 github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
 github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
 github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
+github.com/phpdave11/gofpdi v1.0.7/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -179,8 +184,11 @@ github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be h1:ta7tUOvsPHV
 github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be/go.mod h1:MIDFMn7db1kT65GmV94GzpX9Qdi7N/pQlwb+AN8wh+Q=
 github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967 h1:x7xEyJDP7Hv3LVgvWhzioQqbC/KtuUhTigKlH/8ehhE=
 github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
+github.com/robfig/cron/v3 v3.0.0 h1:kQ6Cb7aHOHTSzNVNEhmp8EcWKLb4CbiMW9h9VyIhO4E=
+github.com/robfig/cron/v3 v3.0.0/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
 github.com/russellhaering/goxmldsig v0.0.0-20180430223755-7acd5e4a6ef7 h1:J4AOUcOh/t1XbQcJfkEqhzgvMJ2tDxdCVvmHxW5QXao=
 github.com/russellhaering/goxmldsig v0.0.0-20180430223755-7acd5e4a6ef7/go.mod h1:Oz4y6ImuOQZxynhbSXk7btjEfNBtGlj2dcaOvXl2FSM=
+github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w=
 github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
 github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
 github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
@@ -227,6 +235,7 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90Pveol
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a h1:Igim7XhdOpBnWPuYJ70XcNpq8q3BCACtVgNfoJxOV7g=
 golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
+golang.org/x/image v0.0.0-20190507092727-e4e5bf290fec/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=

+ 5 - 0
pkg/extensions/main.go

@@ -1,9 +1,14 @@
 package extensions
 
 import (
+	// Upgrade ldapsync from cron to cron.v3 and
+	// remove the cron (v1) dependency
+
 	_ "github.com/crewjam/saml"
 	_ "github.com/gobwas/glob"
+	_ "github.com/jung-kurt/gofpdf"
 	_ "github.com/robfig/cron"
+	_ "github.com/robfig/cron/v3"
 	_ "github.com/stretchr/testify/require"
 	_ "gopkg.in/square/go-jose.v2"
 )

+ 1 - 0
vendor/github.com/jung-kurt/gofpdf/.gitattribute

@@ -0,0 +1 @@
+*.pdf binary

+ 25 - 0
vendor/github.com/jung-kurt/gofpdf/.gitignore

@@ -0,0 +1,25 @@
+*.0
+coverage
+font/CalligrapherRegular.json
+font/CalligrapherRegular.z
+font/Ubuntu-*
+internal/files/bin/bin
+look
+makefont/makefont
+open
+**/*.out
+pdf/*.pdf
+pdf.txt
+private
+*.sublime*
+*.swp
+**/*.test
+.idea/
+doc/body.html
+doc/body.md
+doc/index.html
+doc/index.html.ok
+coverage.html
+
+# macOS
+.DS_Store

+ 21 - 0
vendor/github.com/jung-kurt/gofpdf/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 - 0
vendor/github.com/jung-kurt/gofpdf/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

+ 256 - 0
vendor/github.com/jung-kurt/gofpdf/README.md

@@ -0,0 +1,256 @@
+# GoFPDF document generator
+
+[![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.
+
+## 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.
+
+## Roadmap
+
+  - Improve test coverage as reported by the coverage tool.

+ 146 - 0
vendor/github.com/jung-kurt/gofpdf/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
+}

+ 734 - 0
vendor/github.com/jung-kurt/gofpdf/def.go

@@ -0,0 +1,734 @@
+/*
+ * Copyright (c) 2013-2014 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"
+	"crypto/sha1"
+	"encoding/gob"
+	"encoding/json"
+	"fmt"
+	"io"
+	"time"
+)
+
+// Version of FPDF from which this package is derived
+const (
+	cnFpdfVersion = "1.7"
+)
+
+type blendModeType struct {
+	strokeStr, fillStr, modeStr string
+	objNum                      int
+}
+
+type gradientType struct {
+	tp                int // 2: linear, 3: radial
+	clr1Str, clr2Str  string
+	x1, y1, x2, y2, r float64
+	objNum            int
+}
+
+const (
+	// OrientationPortrait represents the portrait orientation.
+	OrientationPortrait = "portrait"
+
+	// OrientationLandscape represents the landscape orientation.
+	OrientationLandscape = "landscape"
+)
+
+const (
+	// UnitPoint represents the size unit point
+	UnitPoint = "pt"
+	// UnitMillimeter represents the size unit millimeter
+	UnitMillimeter = "mm"
+	// UnitCentimeter represents the size unit centimeter
+	UnitCentimeter = "cm"
+	// UnitInch represents the size unit inch
+	UnitInch = "inch"
+)
+
+const (
+	// PageSizeA3 represents DIN/ISO A3 page size
+	PageSizeA3 = "A3"
+	// PageSizeA4 represents DIN/ISO A4 page size
+	PageSizeA4 = "A4"
+	// PageSizeA5 represents DIN/ISO A5 page size
+	PageSizeA5 = "A5"
+	// PageSizeLetter represents US Letter page size
+	PageSizeLetter = "Letter"
+	// PageSizeLegal represents US Legal page size
+	PageSizeLegal = "Legal"
+)
+
+const (
+	// BorderNone set no border
+	BorderNone = ""
+	// BorderFull sets a full border
+	BorderFull = "1"
+	// BorderLeft sets the border on the left side
+	BorderLeft = "L"
+	// BorderTop sets the border at the top
+	BorderTop = "T"
+	// BorderRight sets the border on the right side
+	BorderRight = "R"
+	// BorderBottom sets the border on the bottom
+	BorderBottom = "B"
+)
+
+const (
+	// LineBreakNone disables linebreak
+	LineBreakNone = 0
+	// LineBreakNormal enables normal linebreak
+	LineBreakNormal = 1
+	// LineBreakBelow enables linebreak below
+	LineBreakBelow = 2
+)
+
+const (
+	// AlignLeft left aligns the cell
+	AlignLeft = "L"
+	// AlignRight right aligns the cell
+	AlignRight = "R"
+	// AlignCenter centers the cell
+	AlignCenter = "C"
+	// AlignTop aligns the cell to the top
+	AlignTop = "T"
+	// AlignBottom aligns the cell to the bottom
+	AlignBottom = "B"
+	// AlignMiddle aligns the cell to the middle
+	AlignMiddle = "M"
+	// AlignBaseline aligns the cell to the baseline
+	AlignBaseline = "B"
+)
+
+type colorMode int
+
+const (
+	colorModeRGB colorMode = iota
+	colorModeSpot
+	colorModeCMYK
+)
+
+type colorType struct {
+	r, g, b    float64
+	ir, ig, ib int
+	mode       colorMode
+	spotStr    string // name of current spot color
+	gray       bool
+	str        string
+}
+
+// SpotColorType specifies a named spot color value
+type spotColorType struct {
+	id, objID int
+	val       cmykColorType
+}
+
+// CMYKColorType specifies an ink-based CMYK color value
+type cmykColorType struct {
+	c, m, y, k byte // 0% to 100%
+}
+
+// SizeType fields Wd and Ht specify the horizontal and vertical extents of a
+// document element such as a page.
+type SizeType struct {
+	Wd, Ht float64
+}
+
+// PointType fields X and Y specify the horizontal and vertical coordinates of
+// a point, typically used in drawing.
+type PointType struct {
+	X, Y float64
+}
+
+// XY returns the X and Y components of the receiver point.
+func (p PointType) XY() (float64, float64) {
+	return p.X, p.Y
+}
+
+// ImageInfoType contains size, color and other information about an image.
+// Changes to this structure should be reflected in its GobEncode and GobDecode
+// methods.
+type ImageInfoType struct {
+	data  []byte
+	smask []byte
+	n     int
+	w     float64
+	h     float64
+	cs    string
+	pal   []byte
+	bpc   int
+	f     string
+	dp    string
+	trns  []int
+	scale float64 // document scaling factor
+	dpi   float64
+	i     string
+}
+
+func generateImageID(info *ImageInfoType) (string, error) {
+	b, err := info.GobEncode()
+	return fmt.Sprintf("%x", sha1.Sum(b)), err
+}
+
+// GobEncode encodes the receiving image to a byte slice.
+func (info *ImageInfoType) GobEncode() (buf []byte, err error) {
+	fields := []interface{}{info.data, info.smask, info.n, info.w, info.h, info.cs,
+		info.pal, info.bpc, info.f, info.dp, info.trns, info.scale, info.dpi}
+	w := new(bytes.Buffer)
+	encoder := gob.NewEncoder(w)
+	for j := 0; j < len(fields) && err == nil; j++ {
+		err = encoder.Encode(fields[j])
+	}
+	if err == nil {
+		buf = w.Bytes()
+	}
+	return
+}
+
+// GobDecode decodes the specified byte buffer (generated by GobEncode) into
+// the receiving image.
+func (info *ImageInfoType) GobDecode(buf []byte) (err error) {
+	fields := []interface{}{&info.data, &info.smask, &info.n, &info.w, &info.h,
+		&info.cs, &info.pal, &info.bpc, &info.f, &info.dp, &info.trns, &info.scale, &info.dpi}
+	r := bytes.NewBuffer(buf)
+	decoder := gob.NewDecoder(r)
+	for j := 0; j < len(fields) && err == nil; j++ {
+		err = decoder.Decode(fields[j])
+	}
+
+	info.i, err = generateImageID(info)
+	return
+}
+
+// PointConvert returns the value of pt, expressed in points (1/72 inch), as a
+// value expressed in the unit of measure specified in New(). Since font
+// management in Fpdf uses points, this method can help with line height
+// calculations and other methods that require user units.
+func (f *Fpdf) PointConvert(pt float64) (u float64) {
+	return pt / f.k
+}
+
+// PointToUnitConvert is an alias for PointConvert.
+func (f *Fpdf) PointToUnitConvert(pt float64) (u float64) {
+	return pt / f.k
+}
+
+// UnitToPointConvert returns the value of u, expressed in the unit of measure
+// specified in New(), as a value expressed in points (1/72 inch). Since font
+// management in Fpdf uses points, this method can help with setting font sizes
+// based on the sizes of other non-font page elements.
+func (f *Fpdf) UnitToPointConvert(u float64) (pt float64) {
+	return u * f.k
+}
+
+// Extent returns the width and height of the image in the units of the Fpdf
+// object.
+func (info *ImageInfoType) Extent() (wd, ht float64) {
+	return info.Width(), info.Height()
+}
+
+// Width returns the width of the image in the units of the Fpdf object.
+func (info *ImageInfoType) Width() float64 {
+	return info.w / (info.scale * info.dpi / 72)
+}
+
+// Height returns the height of the image in the units of the Fpdf object.
+func (info *ImageInfoType) Height() float64 {
+	return info.h / (info.scale * info.dpi / 72)
+}
+
+// SetDpi sets the dots per inch for an image. PNG images MAY have their dpi
+// set automatically, if the image specifies it. DPI information is not
+// currently available automatically for JPG and GIF images, so if it's
+// important to you, you can set it here. It defaults to 72 dpi.
+func (info *ImageInfoType) SetDpi(dpi float64) {
+	info.dpi = dpi
+}
+
+type fontFileType struct {
+	length1, length2 int64
+	n                int
+	embedded         bool
+	content          []byte
+	fontType         string
+}
+
+type linkType struct {
+	x, y, wd, ht float64
+	link         int    // Auto-generated internal link ID or...
+	linkStr      string // ...application-provided external link string
+}
+
+type intLinkType struct {
+	page int
+	y    float64
+}
+
+// outlineType is used for a sidebar outline of bookmarks
+type outlineType struct {
+	text                                   string
+	level, parent, first, last, next, prev int
+	y                                      float64
+	p                                      int
+}
+
+// InitType is used with NewCustom() to customize an Fpdf instance.
+// OrientationStr, UnitStr, SizeStr and FontDirStr correspond to the arguments
+// accepted by New(). If the Wd and Ht fields of Size are each greater than
+// zero, Size will be used to set the default page size rather than SizeStr. Wd
+// and Ht are specified in the units of measure indicated by UnitStr.
+type InitType struct {
+	OrientationStr string
+	UnitStr        string
+	SizeStr        string
+	Size           SizeType
+	FontDirStr     string
+}
+
+// FontLoader is used to read fonts (JSON font specification and zlib compressed font binaries)
+// from arbitrary locations (e.g. files, zip files, embedded font resources).
+//
+// Open provides an io.Reader for the specified font file (.json or .z). The file name
+// never includes a path. Open returns an error if the specified file cannot be opened.
+type FontLoader interface {
+	Open(name string) (io.Reader, error)
+}
+
+// Pdf defines the interface used for various methods. It is implemented by the
+// main FPDF instance as well as templates.
+type Pdf interface {
+	AddFont(familyStr, styleStr, fileStr string)
+	AddFontFromBytes(familyStr, styleStr string, jsonFileBytes, zFileBytes []byte)
+	AddFontFromReader(familyStr, styleStr string, r io.Reader)
+	AddLayer(name string, visible bool) (layerID int)
+	AddLink() int
+	AddPage()
+	AddPageFormat(orientationStr string, size SizeType)
+	AddSpotColor(nameStr string, c, m, y, k byte)
+	AliasNbPages(aliasStr string)
+	ArcTo(x, y, rx, ry, degRotate, degStart, degEnd float64)
+	Arc(x, y, rx, ry, degRotate, degStart, degEnd float64, styleStr string)
+	BeginLayer(id int)
+	Beziergon(points []PointType, styleStr string)
+	Bookmark(txtStr string, level int, y float64)
+	CellFormat(w, h float64, txtStr, borderStr string, ln int, alignStr string, fill bool, link int, linkStr string)
+	Cellf(w, h float64, fmtStr string, args ...interface{})
+	Cell(w, h float64, txtStr string)
+	Circle(x, y, r float64, styleStr string)
+	ClearError()
+	ClipCircle(x, y, r float64, outline bool)
+	ClipEllipse(x, y, rx, ry float64, outline bool)
+	ClipEnd()
+	ClipPolygon(points []PointType, outline bool)
+	ClipRect(x, y, w, h float64, outline bool)
+	ClipRoundedRect(x, y, w, h, r float64, outline bool)
+	ClipText(x, y float64, txtStr string, outline bool)
+	Close()
+	ClosePath()
+	CreateTemplateCustom(corner PointType, size SizeType, fn func(*Tpl)) Template
+	CreateTemplate(fn func(*Tpl)) Template
+	CurveBezierCubicTo(cx0, cy0, cx1, cy1, x, y float64)
+	CurveBezierCubic(x0, y0, cx0, cy0, cx1, cy1, x1, y1 float64, styleStr string)
+	CurveCubic(x0, y0, cx0, cy0, x1, y1, cx1, cy1 float64, styleStr string)
+	CurveTo(cx, cy, x, y float64)
+	Curve(x0, y0, cx, cy, x1, y1 float64, styleStr string)
+	DrawPath(styleStr string)
+	Ellipse(x, y, rx, ry, degRotate float64, styleStr string)
+	EndLayer()
+	Err() bool
+	Error() error
+	GetAlpha() (alpha float64, blendModeStr string)
+	GetAutoPageBreak() (auto bool, margin float64)
+	GetCellMargin() float64
+	GetConversionRatio() float64
+	GetDrawColor() (int, int, int)
+	GetDrawSpotColor() (name string, c, m, y, k byte)
+	GetFillColor() (int, int, int)
+	GetFillSpotColor() (name string, c, m, y, k byte)
+	GetFontDesc(familyStr, styleStr string) FontDescType
+	GetFontSize() (ptSize, unitSize float64)
+	GetImageInfo(imageStr string) (info *ImageInfoType)
+	GetLineWidth() float64
+	GetMargins() (left, top, right, bottom float64)
+	GetPageSizeStr(sizeStr string) (size SizeType)
+	GetPageSize() (width, height float64)
+	GetStringWidth(s string) float64
+	GetTextColor() (int, int, int)
+	GetTextSpotColor() (name string, c, m, y, k byte)
+	GetX() float64
+	GetXY() (float64, float64)
+	GetY() float64
+	HTMLBasicNew() (html HTMLBasicType)
+	Image(imageNameStr string, x, y, w, h float64, flow bool, tp string, link int, linkStr string)
+	ImageOptions(imageNameStr string, x, y, w, h float64, flow bool, options ImageOptions, link int, linkStr string)
+	ImageTypeFromMime(mimeStr string) (tp string)
+	LinearGradient(x, y, w, h float64, r1, g1, b1, r2, g2, b2 int, x1, y1, x2, y2 float64)
+	LineTo(x, y float64)
+	Line(x1, y1, x2, y2 float64)
+	LinkString(x, y, w, h float64, linkStr string)
+	Link(x, y, w, h float64, link int)
+	Ln(h float64)
+	MoveTo(x, y float64)
+	MultiCell(w, h float64, txtStr, borderStr, alignStr string, fill bool)
+	Ok() bool
+	OpenLayerPane()
+	OutputAndClose(w io.WriteCloser) error
+	OutputFileAndClose(fileStr string) error
+	Output(w io.Writer) error
+	PageCount() int
+	PageNo() int
+	PageSize(pageNum int) (wd, ht float64, unitStr string)
+	PointConvert(pt float64) (u float64)
+	PointToUnitConvert(pt float64) (u float64)
+	Polygon(points []PointType, styleStr string)
+	RadialGradient(x, y, w, h float64, r1, g1, b1, r2, g2, b2 int, x1, y1, x2, y2, r float64)
+	RawWriteBuf(r io.Reader)
+	RawWriteStr(str string)
+	Rect(x, y, w, h float64, styleStr string)
+	RegisterAlias(alias, replacement string)
+	RegisterImage(fileStr, tp string) (info *ImageInfoType)
+	RegisterImageOptions(fileStr string, options ImageOptions) (info *ImageInfoType)
+	RegisterImageOptionsReader(imgName string, options ImageOptions, r io.Reader) (info *ImageInfoType)
+	RegisterImageReader(imgName, tp string, r io.Reader) (info *ImageInfoType)
+	SetAcceptPageBreakFunc(fnc func() bool)
+	SetAlpha(alpha float64, blendModeStr string)
+	SetAuthor(authorStr string, isUTF8 bool)
+	SetAutoPageBreak(auto bool, margin float64)
+	SetCatalogSort(flag bool)
+	SetCellMargin(margin float64)
+	SetCompression(compress bool)
+	SetCreationDate(tm time.Time)
+	SetCreator(creatorStr string, isUTF8 bool)
+	SetDashPattern(dashArray []float64, dashPhase float64)
+	SetDisplayMode(zoomStr, layoutStr string)
+	SetDrawColor(r, g, b int)
+	SetDrawSpotColor(nameStr string, tint byte)
+	SetError(err error)
+	SetErrorf(fmtStr string, args ...interface{})
+	SetFillColor(r, g, b int)
+	SetFillSpotColor(nameStr string, tint byte)
+	SetFont(familyStr, styleStr string, size float64)
+	SetFontLoader(loader FontLoader)
+	SetFontLocation(fontDirStr string)
+	SetFontSize(size float64)
+	SetFontStyle(styleStr string)
+	SetFontUnitSize(size float64)
+	SetFooterFunc(fnc func())
+	SetFooterFuncLpi(fnc func(lastPage bool))
+	SetHeaderFunc(fnc func())
+	SetHeaderFuncMode(fnc func(), homeMode bool)
+	SetHomeXY()
+	SetJavascript(script string)
+	SetKeywords(keywordsStr string, isUTF8 bool)
+	SetLeftMargin(margin float64)
+	SetLineCapStyle(styleStr string)
+	SetLineJoinStyle(styleStr string)
+	SetLineWidth(width float64)
+	SetLink(link int, y float64, page int)
+	SetMargins(left, top, right float64)
+	SetPageBoxRec(t string, pb PageBox)
+	SetPageBox(t string, x, y, wd, ht float64)
+	SetPage(pageNum int)
+	SetProtection(actionFlag byte, userPassStr, ownerPassStr string)
+	SetRightMargin(margin float64)
+	SetSubject(subjectStr string, isUTF8 bool)
+	SetTextColor(r, g, b int)
+	SetTextSpotColor(nameStr string, tint byte)
+	SetTitle(titleStr string, isUTF8 bool)
+	SetTopMargin(margin float64)
+	SetUnderlineThickness(thickness float64)
+	SetXmpMetadata(xmpStream []byte)
+	SetX(x float64)
+	SetXY(x, y float64)
+	SetY(y float64)
+	SplitLines(txt []byte, w float64) [][]byte
+	String() string
+	SVGBasicWrite(sb *SVGBasicType, scale float64)
+	Text(x, y float64, txtStr string)
+	TransformBegin()
+	TransformEnd()
+	TransformMirrorHorizontal(x float64)
+	TransformMirrorLine(angle, x, y float64)
+	TransformMirrorPoint(x, y float64)
+	TransformMirrorVertical(y float64)
+	TransformRotate(angle, x, y float64)
+	TransformScale(scaleWd, scaleHt, x, y float64)
+	TransformScaleX(scaleWd, x, y float64)
+	TransformScaleXY(s, x, y float64)
+	TransformScaleY(scaleHt, x, y float64)
+	TransformSkew(angleX, angleY, x, y float64)
+	TransformSkewX(angleX, x, y float64)
+	TransformSkewY(angleY, x, y float64)
+	Transform(tm TransformMatrix)
+	TransformTranslate(tx, ty float64)
+	TransformTranslateX(tx float64)
+	TransformTranslateY(ty float64)
+	UnicodeTranslatorFromDescriptor(cpStr string) (rep func(string) string)
+	UnitToPointConvert(u float64) (pt float64)
+	UseTemplateScaled(t Template, corner PointType, size SizeType)
+	UseTemplate(t Template)
+	WriteAligned(width, lineHeight float64, textStr, alignStr string)
+	Writef(h float64, fmtStr string, args ...interface{})
+	Write(h float64, txtStr string)
+	WriteLinkID(h float64, displayStr string, linkID int)
+	WriteLinkString(h float64, displayStr, targetStr string)
+}
+
+// PageBox defines the coordinates and extent of the various page box types
+type PageBox struct {
+	SizeType
+	PointType
+}
+
+// Fpdf is the principal structure for creating a single PDF document
+type Fpdf struct {
+	isCurrentUTF8    bool                       // is current font used in utf-8 mode
+	isRTL            bool                       // is is right to left mode enabled
+	page             int                        // current page number
+	n                int                        // current object number
+	offsets          []int                      // array of object offsets
+	templates        map[string]Template        // templates used in this document
+	templateObjects  map[string]int             // template object IDs within this document
+	importedObjs     map[string][]byte          // imported template objects (gofpdi)
+	importedObjPos   map[string]map[int]string  // imported template objects hashes and their positions (gofpdi)
+	importedTplObjs  map[string]string          // imported template names and IDs (hashed) (gofpdi)
+	importedTplIDs   map[string]int             // imported template ids hash to object id int (gofpdi)
+	buffer           fmtBuffer                  // buffer holding in-memory PDF
+	pages            []*bytes.Buffer            // slice[page] of page content; 1-based
+	state            int                        // current document state
+	compress         bool                       // compression flag
+	k                float64                    // scale factor (number of points in user unit)
+	defOrientation   string                     // default orientation
+	curOrientation   string                     // current orientation
+	stdPageSizes     map[string]SizeType        // standard page sizes
+	defPageSize      SizeType                   // default page size
+	defPageBoxes     map[string]PageBox         // default page size
+	curPageSize      SizeType                   // current page size
+	pageSizes        map[int]SizeType           // used for pages with non default sizes or orientations
+	pageBoxes        map[int]map[string]PageBox // used to define the crop, trim, bleed and art boxes
+	unitStr          string                     // unit of measure for all rendered objects except fonts
+	wPt, hPt         float64                    // dimensions of current page in points
+	w, h             float64                    // dimensions of current page in user unit
+	lMargin          float64                    // left margin
+	tMargin          float64                    // top margin
+	rMargin          float64                    // right margin
+	bMargin          float64                    // page break margin
+	cMargin          float64                    // cell margin
+	x, y             float64                    // current position in user unit
+	lasth            float64                    // height of last printed cell
+	lineWidth        float64                    // line width in user unit
+	fontpath         string                     // path containing fonts
+	fontLoader       FontLoader                 // used to load font files from arbitrary locations
+	coreFonts        map[string]bool            // array of core font names
+	fonts            map[string]fontDefType     // array of used fonts
+	fontFiles        map[string]fontFileType    // array of font files
+	diffs            []string                   // array of encoding differences
+	fontFamily       string                     // current font family
+	fontStyle        string                     // current font style
+	underline        bool                       // underlining flag
+	currentFont      fontDefType                // current font info
+	fontSizePt       float64                    // current font size in points
+	fontSize         float64                    // current font size in user unit
+	ws               float64                    // word spacing
+	images           map[string]*ImageInfoType  // array of used images
+	aliasMap         map[string]string          // map of alias->replacement
+	pageLinks        [][]linkType               // pageLinks[page][link], both 1-based
+	links            []intLinkType              // array of internal links
+	outlines         []outlineType              // array of outlines
+	outlineRoot      int                        // root of outlines
+	autoPageBreak    bool                       // automatic page breaking
+	acceptPageBreak  func() bool                // returns true to accept page break
+	pageBreakTrigger float64                    // threshold used to trigger page breaks
+	inHeader         bool                       // flag set when processing header
+	headerFnc        func()                     // function provided by app and called to write header
+	headerHomeMode   bool                       // set position to home after headerFnc is called
+	inFooter         bool                       // flag set when processing footer
+	footerFnc        func()                     // function provided by app and called to write footer
+	footerFncLpi     func(bool)                 // function provided by app and called to write footer with last page flag
+	zoomMode         string                     // zoom display mode
+	layoutMode       string                     // layout display mode
+	xmp              []byte                     // XMP metadata
+	producer         string                     // producer
+	title            string                     // title
+	subject          string                     // subject
+	author           string                     // author
+	keywords         string                     // keywords
+	creator          string                     // creator
+	creationDate     time.Time                  // override for dcoument CreationDate value
+	aliasNbPagesStr  string                     // alias for total number of pages
+	pdfVersion       string                     // PDF version number
+	fontDirStr       string                     // location of font definition files
+	capStyle         int                        // line cap style: butt 0, round 1, square 2
+	joinStyle        int                        // line segment join style: miter 0, round 1, bevel 2
+	dashArray        []float64                  // dash array
+	dashPhase        float64                    // dash phase
+	blendList        []blendModeType            // slice[idx] of alpha transparency modes, 1-based
+	blendMap         map[string]int             // map into blendList
+	blendMode        string                     // current blend mode
+	alpha            float64                    // current transpacency
+	gradientList     []gradientType             // slice[idx] of gradient records
+	clipNest         int                        // Number of active clipping contexts
+	transformNest    int                        // Number of active transformation contexts
+	err              error                      // Set if error occurs during life cycle of instance
+	protect          protectType                // document protection structure
+	layer            layerRecType               // manages optional layers in document
+	catalogSort      bool                       // sort resource catalogs in document
+	nJs              int                        // JavaScript object number
+	javascript       *string                    // JavaScript code to include in the PDF
+	colorFlag        bool                       // indicates whether fill and text colors are different
+	color            struct {
+		// Composite values of colors
+		draw, fill, text colorType
+	}
+	spotColorMap           map[string]spotColorType // Map of named ink-based colors
+	userUnderlineThickness float64                  // A custom user underline thickness multiplier.
+}
+
+type encType struct {
+	uv   int
+	name string
+}
+
+type encListType [256]encType
+
+type fontBoxType struct {
+	Xmin, Ymin, Xmax, Ymax int
+}
+
+// Font flags for FontDescType.Flags as defined in the pdf specification.
+const (
+	// FontFlagFixedPitch is set if all glyphs have the same width (as
+	// opposed to proportional or variable-pitch fonts, which have
+	// different widths).
+	FontFlagFixedPitch = 1 << 0
+	// FontFlagSerif is set if glyphs have serifs, which are short
+	// strokes drawn at an angle on the top and bottom of glyph stems.
+	// (Sans serif fonts do not have serifs.)
+	FontFlagSerif = 1 << 1
+	// FontFlagSymbolic is set if font contains glyphs outside the
+	// Adobe standard Latin character set. This flag and the
+	// Nonsymbolic flag shall not both be set or both be clear.
+	FontFlagSymbolic = 1 << 2
+	// FontFlagScript is set if glyphs resemble cursive handwriting.
+	FontFlagScript = 1 << 3
+	// FontFlagNonsymbolic is set if font uses the Adobe standard
+	// Latin character set or a subset of it.
+	FontFlagNonsymbolic = 1 << 5
+	// FontFlagItalic is set if glyphs have dominant vertical strokes
+	// that are slanted.
+	FontFlagItalic = 1 << 6
+	// FontFlagAllCap is set if font contains no lowercase letters;
+	// typically used for display purposes, such as for titles or
+	// headlines.
+	FontFlagAllCap = 1 << 16
+	// SmallCap is set if font contains both uppercase and lowercase
+	// letters. The uppercase letters are similar to those in the
+	// regular version of the same typeface family. The glyphs for the
+	// lowercase letters have the same shapes as the corresponding
+	// uppercase letters, but they are sized and their proportions
+	// adjusted so that they have the same size and stroke weight as
+	// lowercase glyphs in the same typeface family.
+	SmallCap = 1 << 18
+	// ForceBold determines whether bold glyphs shall be painted with
+	// extra pixels even at very small text sizes by a conforming
+	// reader. If the ForceBold flag is set, features of bold glyphs
+	// may be thickened at small text sizes.
+	ForceBold = 1 << 18
+)
+
+// FontDescType (font descriptor) specifies metrics and other
+// attributes of a font, as distinct from the metrics of individual
+// glyphs (as defined in the pdf specification).
+type FontDescType struct {
+	// The maximum height above the baseline reached by glyphs in this
+	// font (for example for "S"). The height of glyphs for accented
+	// characters shall be excluded.
+	Ascent int
+	// The maximum depth below the baseline reached by glyphs in this
+	// font. The value shall be a negative number.
+	Descent int
+	// The vertical coordinate of the top of flat capital letters,
+	// measured from the baseline (for example "H").
+	CapHeight int
+	// A collection of flags defining various characteristics of the
+	// font. (See the FontFlag* constants.)
+	Flags int
+	// A rectangle, expressed in the glyph coordinate system, that
+	// shall specify the font bounding box. This should be the smallest
+	// rectangle enclosing the shape that would result if all of the
+	// glyphs of the font were placed with their origins coincident
+	// and then filled.
+	FontBBox fontBoxType
+	// The angle, expressed in degrees counterclockwise from the
+	// vertical, of the dominant vertical strokes of the font. (The
+	// 9-o’clock position is 90 degrees, and the 3-o’clock position
+	// is –90 degrees.) The value shall be negative for fonts that
+	// slope to the right, as almost all italic fonts do.
+	ItalicAngle int
+	// The thickness, measured horizontally, of the dominant vertical
+	// stems of glyphs in the font.
+	StemV int
+	// The width to use for character codes whose widths are not
+	// specified in a font dictionary’s Widths array. This shall have
+	// a predictable effect only if all such codes map to glyphs whose
+	// actual widths are the same as the value of the MissingWidth
+	// entry. (Default value: 0.)
+	MissingWidth int
+}
+
+type fontDefType struct {
+	Tp           string        // "Core", "TrueType", ...
+	Name         string        // "Courier-Bold", ...
+	Desc         FontDescType  // Font descriptor
+	Up           int           // Underline position
+	Ut           int           // Underline thickness
+	Cw           []int         // Character width by ordinal
+	Enc          string        // "cp1252", ...
+	Diff         string        // Differences from reference encoding
+	File         string        // "Redressed.z"
+	Size1, Size2 int           // Type1 values
+	OriginalSize int           // Size of uncompressed font file
+	N            int           // Set by font loader
+	DiffN        int           // Position of diff in app array, set by font loader
+	i            string        // 1-based position in font list, set by font loader, not this program
+	utf8File     *utf8FontFile // UTF-8 font
+	usedRunes    map[int]int   // Array of used runes
+}
+
+// generateFontID generates a font Id from the font definition
+func generateFontID(fdt fontDefType) (string, error) {
+	// file can be different if generated in different instance
+	fdt.File = ""
+	b, err := json.Marshal(&fdt)
+	return fmt.Sprintf("%x", sha1.Sum(b)), err
+}
+
+type fontInfoType struct {
+	Data               []byte
+	File               string
+	OriginalSize       int
+	FontName           string
+	Bold               bool
+	IsFixedPitch       bool
+	UnderlineThickness int
+	UnderlinePosition  int
+	Widths             []int
+	Size1, Size2       uint32
+	Desc               FontDescType
+}

+ 268 - 0
vendor/github.com/jung-kurt/gofpdf/doc.go

@@ -0,0 +1,268 @@
+/*
+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 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.
+
+
+Installation
+
+To install the package on your system, run
+
+    go get github.com/jung-kurt/gofpdf
+
+Later, to receive updates, run
+
+    go get -u -v github.com/jung-kurt/gofpdf/...
+
+
+Quick Start
+
+The following Go code generates a simple PDF file.
+
+    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 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 library written in PHP (despite the caveat in the
+introduction to Effective Go). 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 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.
+
+    ./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 and DejaVu Fonts.
+
+
+Related Packages
+
+The 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 if appropriate
+
+-   conform to the standards of golint and go vet, that is, golint . and
+go vet . should not generate any warnings
+
+-   not diminish test coverage
+
+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
+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.
+
+
+Roadmap
+
+
+-   Improve test coverage as reported by the coverage tool.
+*/
+package gofpdf

+ 559 - 0
vendor/github.com/jung-kurt/gofpdf/embedded.go

@@ -0,0 +1,559 @@
+/*
+ * Copyright (c) 2014 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
+
+// Embedded standard fonts
+
+import (
+	"strings"
+)
+
+var embeddedFontList = map[string]string{
+	"courierBI":    `{"Tp":"Core","Name":"Courier-BoldOblique","Up":-100,"Ut":50,"I":256,"Cw":[600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600]}`,
+	"courierB":     `{"Tp":"Core","Name":"Courier-Bold","Up":-100,"Ut":50,"I":256,"Cw":[600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600]}`,
+	"courierI":     `{"Tp":"Core","Name":"Courier-Oblique","Up":-100,"Ut":50,"I":256,"Cw":[600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600]}`,
+	"courier":      `{"Tp":"Core","Name":"Courier","Up":-100,"Ut":50,"I":256,"Cw":[600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600,600]}`,
+	"helveticaBI":  `{"Tp":"Core","Name":"Helvetica-BoldOblique","Up":-100,"Ut":50,"Cw":[278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,333,474,556,556,889,722,238,333,333,389,584,278,333,278,278,556,556,556,556,556,556,556,556,556,556,333,333,584,584,584,611,975,722,722,722,722,667,611,778,722,278,556,722,611,833,722,778,667,778,722,667,611,722,667,944,667,667,611,333,278,333,584,556,333,556,611,556,611,556,333,611,611,278,278,556,278,889,611,611,611,611,389,556,333,611,556,778,556,556,500,389,280,389,584,350,556,350,278,556,500,1000,556,556,333,1000,667,333,1000,350,611,350,350,278,278,500,500,350,556,1000,333,1000,556,333,944,350,500,667,278,333,556,556,556,556,280,556,333,737,370,556,584,333,737,333,400,584,333,333,333,611,556,278,333,333,365,556,834,834,834,611,722,722,722,722,722,722,1000,722,667,667,667,667,278,278,278,278,722,722,778,778,778,778,778,584,778,722,722,722,722,667,667,611,556,556,556,556,556,556,889,556,556,556,556,556,278,278,278,278,611,611,611,611,611,611,611,584,611,611,611,611,611,556,611,556]}`,
+	"helveticaB":   `{"Tp":"Core","Name":"Helvetica-Bold","Up":-100,"Ut":50,"Cw":[278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,333,474,556,556,889,722,238,333,333,389,584,278,333,278,278,556,556,556,556,556,556,556,556,556,556,333,333,584,584,584,611,975,722,722,722,722,667,611,778,722,278,556,722,611,833,722,778,667,778,722,667,611,722,667,944,667,667,611,333,278,333,584,556,333,556,611,556,611,556,333,611,611,278,278,556,278,889,611,611,611,611,389,556,333,611,556,778,556,556,500,389,280,389,584,350,556,350,278,556,500,1000,556,556,333,1000,667,333,1000,350,611,350,350,278,278,500,500,350,556,1000,333,1000,556,333,944,350,500,667,278,333,556,556,556,556,280,556,333,737,370,556,584,333,737,333,400,584,333,333,333,611,556,278,333,333,365,556,834,834,834,611,722,722,722,722,722,722,1000,722,667,667,667,667,278,278,278,278,722,722,778,778,778,778,778,584,778,722,722,722,722,667,667,611,556,556,556,556,556,556,889,556,556,556,556,556,278,278,278,278,611,611,611,611,611,611,611,584,611,611,611,611,611,556,611,556]}`,
+	"helveticaI":   `{"Tp":"Core","Name":"Helvetica-Oblique","Up":-100,"Ut":50,"Cw":[278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,355,556,556,889,667,191,333,333,389,584,278,333,278,278,556,556,556,556,556,556,556,556,556,556,278,278,584,584,584,556,1015,667,667,722,722,667,611,778,722,278,500,667,556,833,722,778,667,778,722,667,611,722,667,944,667,667,611,278,278,278,469,556,333,556,556,500,556,556,278,556,556,222,222,500,222,833,556,556,556,556,333,500,278,556,500,722,500,500,500,334,260,334,584,350,556,350,222,556,333,1000,556,556,333,1000,667,333,1000,350,611,350,350,222,222,333,333,350,556,1000,333,1000,500,333,944,350,500,667,278,333,556,556,556,556,260,556,333,737,370,556,584,333,737,333,400,584,333,333,333,556,537,278,333,333,365,556,834,834,834,611,667,667,667,667,667,667,1000,722,667,667,667,667,278,278,278,278,722,722,778,778,778,778,778,584,778,722,722,722,722,667,667,611,556,556,556,556,556,556,889,500,556,556,556,556,278,278,278,278,556,556,556,556,556,556,556,584,611,556,556,556,556,500,556,500]}`,
+	"helvetica":    `{"Tp":"Core","Name":"Helvetica","Up":-100,"Ut":50,"Cw":[278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,355,556,556,889,667,191,333,333,389,584,278,333,278,278,556,556,556,556,556,556,556,556,556,556,278,278,584,584,584,556,1015,667,667,722,722,667,611,778,722,278,500,667,556,833,722,778,667,778,722,667,611,722,667,944,667,667,611,278,278,278,469,556,333,556,556,500,556,556,278,556,556,222,222,500,222,833,556,556,556,556,333,500,278,556,500,722,500,500,500,334,260,334,584,350,556,350,222,556,333,1000,556,556,333,1000,667,333,1000,350,611,350,350,222,222,333,333,350,556,1000,333,1000,500,333,944,350,500,667,278,333,556,556,556,556,260,556,333,737,370,556,584,333,737,333,400,584,333,333,333,556,537,278,333,333,365,556,834,834,834,611,667,667,667,667,667,667,1000,722,667,667,667,667,278,278,278,278,722,722,778,778,778,778,778,584,778,722,722,722,722,667,667,611,556,556,556,556,556,556,889,500,556,556,556,556,278,278,278,278,556,556,556,556,556,556,556,584,611,556,556,556,556,500,556,500]}`,
+	"timesBI":      `{"Tp":"Core","Name":"Times-BoldItalic","Up":-100,"Ut":50,"Cw":[250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,389,555,500,500,833,778,278,333,333,500,570,250,333,250,278,500,500,500,500,500,500,500,500,500,500,333,333,570,570,570,500,832,667,667,667,722,667,667,722,778,389,500,667,611,889,722,722,611,722,667,556,611,722,667,889,667,611,611,333,278,333,570,500,333,500,500,444,500,444,333,500,556,278,278,500,278,778,556,500,500,500,389,389,278,556,444,667,500,444,389,348,220,348,570,350,500,350,333,500,500,1000,500,500,333,1000,556,333,944,350,611,350,350,333,333,500,500,350,500,1000,333,1000,389,333,722,350,389,611,250,389,500,500,500,500,220,500,333,747,266,500,606,333,747,333,400,570,300,300,333,576,500,250,333,300,300,500,750,750,750,500,667,667,667,667,667,667,944,667,667,667,667,667,389,389,389,389,722,722,722,722,722,722,722,570,722,722,722,722,722,611,611,500,500,500,500,500,500,500,722,444,444,444,444,444,278,278,278,278,500,556,500,500,500,500,500,570,500,556,556,556,556,444,500,444]}`,
+	"timesB":       `{"Tp":"Core","Name":"Times-Bold","Up":-100,"Ut":50,"Cw":[250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,333,555,500,500,1000,833,278,333,333,500,570,250,333,250,278,500,500,500,500,500,500,500,500,500,500,333,333,570,570,570,500,930,722,667,722,722,667,611,778,778,389,500,778,667,944,722,778,611,778,722,556,667,722,722,1000,722,722,667,333,278,333,581,500,333,500,556,444,556,444,333,500,556,278,333,556,278,833,556,500,556,556,444,389,333,556,500,722,500,500,444,394,220,394,520,350,500,350,333,500,500,1000,500,500,333,1000,556,333,1000,350,667,350,350,333,333,500,500,350,500,1000,333,1000,389,333,722,350,444,722,250,333,500,500,500,500,220,500,333,747,300,500,570,333,747,333,400,570,300,300,333,556,540,250,333,300,330,500,750,750,750,500,722,722,722,722,722,722,1000,722,667,667,667,667,389,389,389,389,722,722,778,778,778,778,778,570,778,722,722,722,722,722,611,556,500,500,500,500,500,500,722,444,444,444,444,444,278,278,278,278,500,556,500,500,500,500,500,570,500,556,556,556,556,500,556,500]}`,
+	"timesI":       `{"Tp":"Core","Name":"Times-Italic","Up":-100,"Ut":50,"Cw":[250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,333,420,500,500,833,778,214,333,333,500,675,250,333,250,278,500,500,500,500,500,500,500,500,500,500,333,333,675,675,675,500,920,611,611,667,722,611,611,722,722,333,444,667,556,833,667,722,611,722,611,500,556,722,611,833,611,556,556,389,278,389,422,500,333,500,500,444,500,444,278,500,500,278,278,444,278,722,500,500,500,500,389,389,278,500,444,667,444,444,389,400,275,400,541,350,500,350,333,500,556,889,500,500,333,1000,500,333,944,350,556,350,350,333,333,556,556,350,500,889,333,980,389,333,667,350,389,556,250,389,500,500,500,500,275,500,333,760,276,500,675,333,760,333,400,675,300,300,333,500,523,250,333,300,310,500,750,750,750,500,611,611,611,611,611,611,889,667,611,611,611,611,333,333,333,333,722,667,722,722,722,722,722,675,722,722,722,722,722,556,611,500,500,500,500,500,500,500,667,444,444,444,444,444,278,278,278,278,500,500,500,500,500,500,500,675,500,500,500,500,500,444,500,444]}`,
+	"times":        `{"Tp":"Core","Name":"Times-Roman","Up":-100,"Ut":50,"Cw":[250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,250,333,408,500,500,833,778,180,333,333,500,564,250,333,250,278,500,500,500,500,500,500,500,500,500,500,278,278,564,564,564,444,921,722,667,667,722,611,556,722,722,333,389,722,611,889,722,722,556,722,667,556,611,722,722,944,722,722,611,333,278,333,469,500,333,444,500,444,500,444,333,500,500,278,278,500,278,778,500,500,500,500,333,389,278,500,500,722,500,500,444,480,200,480,541,350,500,350,333,500,444,1000,500,500,333,1000,556,333,889,350,611,350,350,333,333,444,444,350,500,1000,333,980,389,333,722,350,444,722,250,333,500,500,500,500,200,500,333,760,276,500,564,333,760,333,400,564,300,300,333,500,453,250,333,300,310,500,750,750,750,444,722,722,722,722,722,722,889,667,611,611,611,611,333,333,333,333,722,722,722,722,722,722,722,564,722,722,722,722,722,722,556,500,444,444,444,444,444,444,667,444,444,444,444,444,278,278,278,278,500,500,500,500,500,500,500,564,500,500,500,500,500,500,500,500]}`,
+	"zapfdingbats": `{"Tp":"Core","Name":"ZapfDingbats","Up":-100,"Ut":50,"Cw":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,278,974,961,974,980,719,789,790,791,690,960,939,549,855,911,933,911,945,974,755,846,762,761,571,677,763,760,759,754,494,552,537,577,692,786,788,788,790,793,794,816,823,789,841,823,833,816,831,923,744,723,749,790,792,695,776,768,792,759,707,708,682,701,826,815,789,789,707,687,696,689,786,787,713,791,785,791,873,761,762,762,759,759,892,892,788,784,438,138,277,415,392,392,668,668,0,390,390,317,317,276,276,509,509,410,410,234,234,334,334,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,732,544,544,910,667,760,760,776,595,694,626,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,788,894,838,1016,458,748,924,748,918,927,928,928,834,873,828,924,924,917,930,931,463,883,836,836,867,867,696,696,874,0,874,760,946,771,865,771,888,967,888,831,873,927,970,918,0]}`,
+}
+
+func (f *Fpdf) coreFontReader(familyStr, styleStr string) (r *strings.Reader) {
+	key := familyStr + styleStr
+	str, ok := embeddedFontList[key]
+	if ok {
+		r = strings.NewReader(str)
+	} else {
+		f.SetErrorf("could not locate \"%s\" among embedded core font definition files", key)
+	}
+	return
+}
+
+var embeddedMapList = map[string]string{
+	"cp1250": `
+!00 U+0000 .notdef
+!01 U+0001 .notdef
+!02 U+0002 .notdef
+!03 U+0003 .notdef
+!04 U+0004 .notdef
+!05 U+0005 .notdef
+!06 U+0006 .notdef
+!07 U+0007 .notdef
+!08 U+0008 .notdef
+!09 U+0009 .notdef
+!0A U+000A .notdef
+!0B U+000B .notdef
+!0C U+000C .notdef
+!0D U+000D .notdef
+!0E U+000E .notdef
+!0F U+000F .notdef
+!10 U+0010 .notdef
+!11 U+0011 .notdef
+!12 U+0012 .notdef
+!13 U+0013 .notdef
+!14 U+0014 .notdef
+!15 U+0015 .notdef
+!16 U+0016 .notdef
+!17 U+0017 .notdef
+!18 U+0018 .notdef
+!19 U+0019 .notdef
+!1A U+001A .notdef
+!1B U+001B .notdef
+!1C U+001C .notdef
+!1D U+001D .notdef
+!1E U+001E .notdef
+!1F U+001F .notdef
+!20 U+0020 space
+!21 U+0021 exclam
+!22 U+0022 quotedbl
+!23 U+0023 numbersign
+!24 U+0024 dollar
+!25 U+0025 percent
+!26 U+0026 ampersand
+!27 U+0027 quotesingle
+!28 U+0028 parenleft
+!29 U+0029 parenright
+!2A U+002A asterisk
+!2B U+002B plus
+!2C U+002C comma
+!2D U+002D hyphen
+!2E U+002E period
+!2F U+002F slash
+!30 U+0030 zero
+!31 U+0031 one
+!32 U+0032 two
+!33 U+0033 three
+!34 U+0034 four
+!35 U+0035 five
+!36 U+0036 six
+!37 U+0037 seven
+!38 U+0038 eight
+!39 U+0039 nine
+!3A U+003A colon
+!3B U+003B semicolon
+!3C U+003C less
+!3D U+003D equal
+!3E U+003E greater
+!3F U+003F question
+!40 U+0040 at
+!41 U+0041 A
+!42 U+0042 B
+!43 U+0043 C
+!44 U+0044 D
+!45 U+0045 E
+!46 U+0046 F
+!47 U+0047 G
+!48 U+0048 H
+!49 U+0049 I
+!4A U+004A J
+!4B U+004B K
+!4C U+004C L
+!4D U+004D M
+!4E U+004E N
+!4F U+004F O
+!50 U+0050 P
+!51 U+0051 Q
+!52 U+0052 R
+!53 U+0053 S
+!54 U+0054 T
+!55 U+0055 U
+!56 U+0056 V
+!57 U+0057 W
+!58 U+0058 X
+!59 U+0059 Y
+!5A U+005A Z
+!5B U+005B bracketleft
+!5C U+005C backslash
+!5D U+005D bracketright
+!5E U+005E asciicircum
+!5F U+005F underscore
+!60 U+0060 grave
+!61 U+0061 a
+!62 U+0062 b
+!63 U+0063 c
+!64 U+0064 d
+!65 U+0065 e
+!66 U+0066 f
+!67 U+0067 g
+!68 U+0068 h
+!69 U+0069 i
+!6A U+006A j
+!6B U+006B k
+!6C U+006C l
+!6D U+006D m
+!6E U+006E n
+!6F U+006F o
+!70 U+0070 p
+!71 U+0071 q
+!72 U+0072 r
+!73 U+0073 s
+!74 U+0074 t
+!75 U+0075 u
+!76 U+0076 v
+!77 U+0077 w
+!78 U+0078 x
+!79 U+0079 y
+!7A U+007A z
+!7B U+007B braceleft
+!7C U+007C bar
+!7D U+007D braceright
+!7E U+007E asciitilde
+!7F U+007F .notdef
+!80 U+20AC Euro
+!82 U+201A quotesinglbase
+!84 U+201E quotedblbase
+!85 U+2026 ellipsis
+!86 U+2020 dagger
+!87 U+2021 daggerdbl
+!89 U+2030 perthousand
+!8A U+0160 Scaron
+!8B U+2039 guilsinglleft
+!8C U+015A Sacute
+!8D U+0164 Tcaron
+!8E U+017D Zcaron
+!8F U+0179 Zacute
+!91 U+2018 quoteleft
+!92 U+2019 quoteright
+!93 U+201C quotedblleft
+!94 U+201D quotedblright
+!95 U+2022 bullet
+!96 U+2013 endash
+!97 U+2014 emdash
+!99 U+2122 trademark
+!9A U+0161 scaron
+!9B U+203A guilsinglright
+!9C U+015B sacute
+!9D U+0165 tcaron
+!9E U+017E zcaron
+!9F U+017A zacute
+!A0 U+00A0 space
+!A1 U+02C7 caron
+!A2 U+02D8 breve
+!A3 U+0141 Lslash
+!A4 U+00A4 currency
+!A5 U+0104 Aogonek
+!A6 U+00A6 brokenbar
+!A7 U+00A7 section
+!A8 U+00A8 dieresis
+!A9 U+00A9 copyright
+!AA U+015E Scedilla
+!AB U+00AB guillemotleft
+!AC U+00AC logicalnot
+!AD U+00AD hyphen
+!AE U+00AE registered
+!AF U+017B Zdotaccent
+!B0 U+00B0 degree
+!B1 U+00B1 plusminus
+!B2 U+02DB ogonek
+!B3 U+0142 lslash
+!B4 U+00B4 acute
+!B5 U+00B5 mu
+!B6 U+00B6 paragraph
+!B7 U+00B7 periodcentered
+!B8 U+00B8 cedilla
+!B9 U+0105 aogonek
+!BA U+015F scedilla
+!BB U+00BB guillemotright
+!BC U+013D Lcaron
+!BD U+02DD hungarumlaut
+!BE U+013E lcaron
+!BF U+017C zdotaccent
+!C0 U+0154 Racute
+!C1 U+00C1 Aacute
+!C2 U+00C2 Acircumflex
+!C3 U+0102 Abreve
+!C4 U+00C4 Adieresis
+!C5 U+0139 Lacute
+!C6 U+0106 Cacute
+!C7 U+00C7 Ccedilla
+!C8 U+010C Ccaron
+!C9 U+00C9 Eacute
+!CA U+0118 Eogonek
+!CB U+00CB Edieresis
+!CC U+011A Ecaron
+!CD U+00CD Iacute
+!CE U+00CE Icircumflex
+!CF U+010E Dcaron
+!D0 U+0110 Dcroat
+!D1 U+0143 Nacute
+!D2 U+0147 Ncaron
+!D3 U+00D3 Oacute
+!D4 U+00D4 Ocircumflex
+!D5 U+0150 Ohungarumlaut
+!D6 U+00D6 Odieresis
+!D7 U+00D7 multiply
+!D8 U+0158 Rcaron
+!D9 U+016E Uring
+!DA U+00DA Uacute
+!DB U+0170 Uhungarumlaut
+!DC U+00DC Udieresis
+!DD U+00DD Yacute
+!DE U+0162 Tcommaaccent
+!DF U+00DF germandbls
+!E0 U+0155 racute
+!E1 U+00E1 aacute
+!E2 U+00E2 acircumflex
+!E3 U+0103 abreve
+!E4 U+00E4 adieresis
+!E5 U+013A lacute
+!E6 U+0107 cacute
+!E7 U+00E7 ccedilla
+!E8 U+010D ccaron
+!E9 U+00E9 eacute
+!EA U+0119 eogonek
+!EB U+00EB edieresis
+!EC U+011B ecaron
+!ED U+00ED iacute
+!EE U+00EE icircumflex
+!EF U+010F dcaron
+!F0 U+0111 dcroat
+!F1 U+0144 nacute
+!F2 U+0148 ncaron
+!F3 U+00F3 oacute
+!F4 U+00F4 ocircumflex
+!F5 U+0151 ohungarumlaut
+!F6 U+00F6 odieresis
+!F7 U+00F7 divide
+!F8 U+0159 rcaron
+!F9 U+016F uring
+!FA U+00FA uacute
+!FB U+0171 uhungarumlaut
+!FC U+00FC udieresis
+!FD U+00FD yacute
+!FE U+0163 tcommaaccent
+!FF U+02D9 dotaccent
+	`,
+	"cp1252": `
+!00 U+0000 .notdef
+!01 U+0001 .notdef
+!02 U+0002 .notdef
+!03 U+0003 .notdef
+!04 U+0004 .notdef
+!05 U+0005 .notdef
+!06 U+0006 .notdef
+!07 U+0007 .notdef
+!08 U+0008 .notdef
+!09 U+0009 .notdef
+!0A U+000A .notdef
+!0B U+000B .notdef
+!0C U+000C .notdef
+!0D U+000D .notdef
+!0E U+000E .notdef
+!0F U+000F .notdef
+!10 U+0010 .notdef
+!11 U+0011 .notdef
+!12 U+0012 .notdef
+!13 U+0013 .notdef
+!14 U+0014 .notdef
+!15 U+0015 .notdef
+!16 U+0016 .notdef
+!17 U+0017 .notdef
+!18 U+0018 .notdef
+!19 U+0019 .notdef
+!1A U+001A .notdef
+!1B U+001B .notdef
+!1C U+001C .notdef
+!1D U+001D .notdef
+!1E U+001E .notdef
+!1F U+001F .notdef
+!20 U+0020 space
+!21 U+0021 exclam
+!22 U+0022 quotedbl
+!23 U+0023 numbersign
+!24 U+0024 dollar
+!25 U+0025 percent
+!26 U+0026 ampersand
+!27 U+0027 quotesingle
+!28 U+0028 parenleft
+!29 U+0029 parenright
+!2A U+002A asterisk
+!2B U+002B plus
+!2C U+002C comma
+!2D U+002D hyphen
+!2E U+002E period
+!2F U+002F slash
+!30 U+0030 zero
+!31 U+0031 one
+!32 U+0032 two
+!33 U+0033 three
+!34 U+0034 four
+!35 U+0035 five
+!36 U+0036 six
+!37 U+0037 seven
+!38 U+0038 eight
+!39 U+0039 nine
+!3A U+003A colon
+!3B U+003B semicolon
+!3C U+003C less
+!3D U+003D equal
+!3E U+003E greater
+!3F U+003F question
+!40 U+0040 at
+!41 U+0041 A
+!42 U+0042 B
+!43 U+0043 C
+!44 U+0044 D
+!45 U+0045 E
+!46 U+0046 F
+!47 U+0047 G
+!48 U+0048 H
+!49 U+0049 I
+!4A U+004A J
+!4B U+004B K
+!4C U+004C L
+!4D U+004D M
+!4E U+004E N
+!4F U+004F O
+!50 U+0050 P
+!51 U+0051 Q
+!52 U+0052 R
+!53 U+0053 S
+!54 U+0054 T
+!55 U+0055 U
+!56 U+0056 V
+!57 U+0057 W
+!58 U+0058 X
+!59 U+0059 Y
+!5A U+005A Z
+!5B U+005B bracketleft
+!5C U+005C backslash
+!5D U+005D bracketright
+!5E U+005E asciicircum
+!5F U+005F underscore
+!60 U+0060 grave
+!61 U+0061 a
+!62 U+0062 b
+!63 U+0063 c
+!64 U+0064 d
+!65 U+0065 e
+!66 U+0066 f
+!67 U+0067 g
+!68 U+0068 h
+!69 U+0069 i
+!6A U+006A j
+!6B U+006B k
+!6C U+006C l
+!6D U+006D m
+!6E U+006E n
+!6F U+006F o
+!70 U+0070 p
+!71 U+0071 q
+!72 U+0072 r
+!73 U+0073 s
+!74 U+0074 t
+!75 U+0075 u
+!76 U+0076 v
+!77 U+0077 w
+!78 U+0078 x
+!79 U+0079 y
+!7A U+007A z
+!7B U+007B braceleft
+!7C U+007C bar
+!7D U+007D braceright
+!7E U+007E asciitilde
+!7F U+007F .notdef
+!80 U+20AC Euro
+!82 U+201A quotesinglbase
+!83 U+0192 florin
+!84 U+201E quotedblbase
+!85 U+2026 ellipsis
+!86 U+2020 dagger
+!87 U+2021 daggerdbl
+!88 U+02C6 circumflex
+!89 U+2030 perthousand
+!8A U+0160 Scaron
+!8B U+2039 guilsinglleft
+!8C U+0152 OE
+!8E U+017D Zcaron
+!91 U+2018 quoteleft
+!92 U+2019 quoteright
+!93 U+201C quotedblleft
+!94 U+201D quotedblright
+!95 U+2022 bullet
+!96 U+2013 endash
+!97 U+2014 emdash
+!98 U+02DC tilde
+!99 U+2122 trademark
+!9A U+0161 scaron
+!9B U+203A guilsinglright
+!9C U+0153 oe
+!9E U+017E zcaron
+!9F U+0178 Ydieresis
+!A0 U+00A0 space
+!A1 U+00A1 exclamdown
+!A2 U+00A2 cent
+!A3 U+00A3 sterling
+!A4 U+00A4 currency
+!A5 U+00A5 yen
+!A6 U+00A6 brokenbar
+!A7 U+00A7 section
+!A8 U+00A8 dieresis
+!A9 U+00A9 copyright
+!AA U+00AA ordfeminine
+!AB U+00AB guillemotleft
+!AC U+00AC logicalnot
+!AD U+00AD hyphen
+!AE U+00AE registered
+!AF U+00AF macron
+!B0 U+00B0 degree
+!B1 U+00B1 plusminus
+!B2 U+00B2 twosuperior
+!B3 U+00B3 threesuperior
+!B4 U+00B4 acute
+!B5 U+00B5 mu
+!B6 U+00B6 paragraph
+!B7 U+00B7 periodcentered
+!B8 U+00B8 cedilla
+!B9 U+00B9 onesuperior
+!BA U+00BA ordmasculine
+!BB U+00BB guillemotright
+!BC U+00BC onequarter
+!BD U+00BD onehalf
+!BE U+00BE threequarters
+!BF U+00BF questiondown
+!C0 U+00C0 Agrave
+!C1 U+00C1 Aacute
+!C2 U+00C2 Acircumflex
+!C3 U+00C3 Atilde
+!C4 U+00C4 Adieresis
+!C5 U+00C5 Aring
+!C6 U+00C6 AE
+!C7 U+00C7 Ccedilla
+!C8 U+00C8 Egrave
+!C9 U+00C9 Eacute
+!CA U+00CA Ecircumflex
+!CB U+00CB Edieresis
+!CC U+00CC Igrave
+!CD U+00CD Iacute
+!CE U+00CE Icircumflex
+!CF U+00CF Idieresis
+!D0 U+00D0 Eth
+!D1 U+00D1 Ntilde
+!D2 U+00D2 Ograve
+!D3 U+00D3 Oacute
+!D4 U+00D4 Ocircumflex
+!D5 U+00D5 Otilde
+!D6 U+00D6 Odieresis
+!D7 U+00D7 multiply
+!D8 U+00D8 Oslash
+!D9 U+00D9 Ugrave
+!DA U+00DA Uacute
+!DB U+00DB Ucircumflex
+!DC U+00DC Udieresis
+!DD U+00DD Yacute
+!DE U+00DE Thorn
+!DF U+00DF germandbls
+!E0 U+00E0 agrave
+!E1 U+00E1 aacute
+!E2 U+00E2 acircumflex
+!E3 U+00E3 atilde
+!E4 U+00E4 adieresis
+!E5 U+00E5 aring
+!E6 U+00E6 ae
+!E7 U+00E7 ccedilla
+!E8 U+00E8 egrave
+!E9 U+00E9 eacute
+!EA U+00EA ecircumflex
+!EB U+00EB edieresis
+!EC U+00EC igrave
+!ED U+00ED iacute
+!EE U+00EE icircumflex
+!EF U+00EF idieresis
+!F0 U+00F0 eth
+!F1 U+00F1 ntilde
+!F2 U+00F2 ograve
+!F3 U+00F3 oacute
+!F4 U+00F4 ocircumflex
+!F5 U+00F5 otilde
+!F6 U+00F6 odieresis
+!F7 U+00F7 divide
+!F8 U+00F8 oslash
+!F9 U+00F9 ugrave
+!FA U+00FA uacute
+!FB U+00FB ucircumflex
+!FC U+00FC udieresis
+!FD U+00FD yacute
+!FE U+00FE thorn
+!FF U+00FF ydieresis
+	`,
+}

+ 474 - 0
vendor/github.com/jung-kurt/gofpdf/font.go

@@ -0,0 +1,474 @@
+/*
+ * Copyright (c) 2013 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
+
+// Utility to generate font definition files
+
+// Version: 1.2
+// Date:    2011-06-18
+// Author:  Olivier PLATHEY
+// Port to Go: Kurt Jung, 2013-07-15
+
+import (
+	"bufio"
+	"compress/zlib"
+	"encoding/binary"
+	"encoding/json"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strconv"
+	"strings"
+)
+
+func baseNoExt(fileStr string) string {
+	str := filepath.Base(fileStr)
+	extLen := len(filepath.Ext(str))
+	if extLen > 0 {
+		str = str[:len(str)-extLen]
+	}
+	return str
+}
+
+func loadMap(encodingFileStr string) (encList encListType, err error) {
+	// printf("Encoding file string [%s]\n", encodingFileStr)
+	var f *os.File
+	// f, err = os.Open(encodingFilepath(encodingFileStr))
+	f, err = os.Open(encodingFileStr)
+	if err == nil {
+		defer f.Close()
+		for j := range encList {
+			encList[j].uv = -1
+			encList[j].name = ".notdef"
+		}
+		scanner := bufio.NewScanner(f)
+		var enc encType
+		var pos int
+		for scanner.Scan() {
+			// "!3F U+003F question"
+			_, err = fmt.Sscanf(scanner.Text(), "!%x U+%x %s", &pos, &enc.uv, &enc.name)
+			if err == nil {
+				if pos < 256 {
+					encList[pos] = enc
+				} else {
+					err = fmt.Errorf("map position 0x%2X exceeds 0xFF", pos)
+					return
+				}
+			} else {
+				return
+			}
+		}
+		if err = scanner.Err(); err != nil {
+			return
+		}
+	}
+	return
+}
+
+// getInfoFromTrueType returns information from a TrueType font
+func getInfoFromTrueType(fileStr string, msgWriter io.Writer, embed bool, encList encListType) (info fontInfoType, err error) {
+	info.Widths = make([]int, 256)
+	var ttf TtfType
+	ttf, err = TtfParse(fileStr)
+	if err != nil {
+		return
+	}
+	if embed {
+		if !ttf.Embeddable {
+			err = fmt.Errorf("font license does not allow embedding")
+			return
+		}
+		info.Data, err = ioutil.ReadFile(fileStr)
+		if err != nil {
+			return
+		}
+		info.OriginalSize = len(info.Data)
+	}
+	k := 1000.0 / float64(ttf.UnitsPerEm)
+	info.FontName = ttf.PostScriptName
+	info.Bold = ttf.Bold
+	info.Desc.ItalicAngle = int(ttf.ItalicAngle)
+	info.IsFixedPitch = ttf.IsFixedPitch
+	info.Desc.Ascent = round(k * float64(ttf.TypoAscender))
+	info.Desc.Descent = round(k * float64(ttf.TypoDescender))
+	info.UnderlineThickness = round(k * float64(ttf.UnderlineThickness))
+	info.UnderlinePosition = round(k * float64(ttf.UnderlinePosition))
+	info.Desc.FontBBox = fontBoxType{
+		round(k * float64(ttf.Xmin)),
+		round(k * float64(ttf.Ymin)),
+		round(k * float64(ttf.Xmax)),
+		round(k * float64(ttf.Ymax)),
+	}
+	// printf("FontBBox\n")
+	// dump(info.Desc.FontBBox)
+	info.Desc.CapHeight = round(k * float64(ttf.CapHeight))
+	info.Desc.MissingWidth = round(k * float64(ttf.Widths[0]))
+	var wd int
+	for j := 0; j < len(info.Widths); j++ {
+		wd = info.Desc.MissingWidth
+		if encList[j].name != ".notdef" {
+			uv := encList[j].uv
+			pos, ok := ttf.Chars[uint16(uv)]
+			if ok {
+				wd = round(k * float64(ttf.Widths[pos]))
+			} else {
+				fmt.Fprintf(msgWriter, "Character %s is missing\n", encList[j].name)
+			}
+		}
+		info.Widths[j] = wd
+	}
+	// printf("getInfoFromTrueType/FontBBox\n")
+	// dump(info.Desc.FontBBox)
+	return
+}
+
+type segmentType struct {
+	marker uint8
+	tp     uint8
+	size   uint32
+	data   []byte
+}
+
+func segmentRead(r io.Reader) (s segmentType, err error) {
+	if err = binary.Read(r, binary.LittleEndian, &s.marker); err != nil {
+		return
+	}
+	if s.marker != 128 {
+		err = fmt.Errorf("font file is not a valid binary Type1")
+		return
+	}
+	if err = binary.Read(r, binary.LittleEndian, &s.tp); err != nil {
+		return
+	}
+	if err = binary.Read(r, binary.LittleEndian, &s.size); err != nil {
+		return
+	}
+	s.data = make([]byte, s.size)
+	_, err = r.Read(s.data)
+	return
+}
+
+// -rw-r--r-- 1 root root  9532 2010-04-22 11:27 /usr/share/fonts/type1/mathml/Symbol.afm
+// -rw-r--r-- 1 root root 37744 2010-04-22 11:27 /usr/share/fonts/type1/mathml/Symbol.pfb
+
+// getInfoFromType1 return information from a Type1 font
+func getInfoFromType1(fileStr string, msgWriter io.Writer, embed bool, encList encListType) (info fontInfoType, err error) {
+	info.Widths = make([]int, 256)
+	if embed {
+		var f *os.File
+		f, err = os.Open(fileStr)
+		if err != nil {
+			return
+		}
+		defer f.Close()
+		// Read first segment
+		var s1, s2 segmentType
+		s1, err = segmentRead(f)
+		if err != nil {
+			return
+		}
+		s2, err = segmentRead(f)
+		if err != nil {
+			return
+		}
+		info.Data = s1.data
+		info.Data = append(info.Data, s2.data...)
+		info.Size1 = s1.size
+		info.Size2 = s2.size
+	}
+	afmFileStr := fileStr[0:len(fileStr)-3] + "afm"
+	size, ok := fileSize(afmFileStr)
+	if !ok {
+		err = fmt.Errorf("font file (ATM) %s not found", afmFileStr)
+		return
+	} else if size == 0 {
+		err = fmt.Errorf("font file (AFM) %s empty or not readable", afmFileStr)
+		return
+	}
+	var f *os.File
+	f, err = os.Open(afmFileStr)
+	if err != nil {
+		return
+	}
+	defer f.Close()
+	scanner := bufio.NewScanner(f)
+	var fields []string
+	var wd int
+	var wt, name string
+	wdMap := make(map[string]int)
+	for scanner.Scan() {
+		fields = strings.Fields(strings.TrimSpace(scanner.Text()))
+		// Comment Generated by FontForge 20080203
+		// FontName Symbol
+		// C 32 ; WX 250 ; N space ; B 0 0 0 0 ;
+		if len(fields) >= 2 {
+			switch fields[0] {
+			case "C":
+				if wd, err = strconv.Atoi(fields[4]); err == nil {
+					name = fields[7]
+					wdMap[name] = wd
+				}
+			case "FontName":
+				info.FontName = fields[1]
+			case "Weight":
+				wt = strings.ToLower(fields[1])
+			case "ItalicAngle":
+				info.Desc.ItalicAngle, err = strconv.Atoi(fields[1])
+			case "Ascender":
+				info.Desc.Ascent, err = strconv.Atoi(fields[1])
+			case "Descender":
+				info.Desc.Descent, err = strconv.Atoi(fields[1])
+			case "UnderlineThickness":
+				info.UnderlineThickness, err = strconv.Atoi(fields[1])
+			case "UnderlinePosition":
+				info.UnderlinePosition, err = strconv.Atoi(fields[1])
+			case "IsFixedPitch":
+				info.IsFixedPitch = fields[1] == "true"
+			case "FontBBox":
+				if info.Desc.FontBBox.Xmin, err = strconv.Atoi(fields[1]); err == nil {
+					if info.Desc.FontBBox.Ymin, err = strconv.Atoi(fields[2]); err == nil {
+						if info.Desc.FontBBox.Xmax, err = strconv.Atoi(fields[3]); err == nil {
+							info.Desc.FontBBox.Ymax, err = strconv.Atoi(fields[4])
+						}
+					}
+				}
+			case "CapHeight":
+				info.Desc.CapHeight, err = strconv.Atoi(fields[1])
+			case "StdVW":
+				info.Desc.StemV, err = strconv.Atoi(fields[1])
+			}
+		}
+		if err != nil {
+			return
+		}
+	}
+	if err = scanner.Err(); err != nil {
+		return
+	}
+	if info.FontName == "" {
+		err = fmt.Errorf("the field FontName missing in AFM file %s", afmFileStr)
+		return
+	}
+	info.Bold = wt == "bold" || wt == "black"
+	var missingWd int
+	missingWd, ok = wdMap[".notdef"]
+	if ok {
+		info.Desc.MissingWidth = missingWd
+	}
+	for j := 0; j < len(info.Widths); j++ {
+		info.Widths[j] = info.Desc.MissingWidth
+	}
+	for j := 0; j < len(info.Widths); j++ {
+		name = encList[j].name
+		if name != ".notdef" {
+			wd, ok = wdMap[name]
+			if ok {
+				info.Widths[j] = wd
+			} else {
+				fmt.Fprintf(msgWriter, "Character %s is missing\n", name)
+			}
+		}
+	}
+	// printf("getInfoFromType1/FontBBox\n")
+	// dump(info.Desc.FontBBox)
+	return
+}
+
+func makeFontDescriptor(info *fontInfoType) {
+	if info.Desc.CapHeight == 0 {
+		info.Desc.CapHeight = info.Desc.Ascent
+	}
+	info.Desc.Flags = 1 << 5
+	if info.IsFixedPitch {
+		info.Desc.Flags |= 1
+	}
+	if info.Desc.ItalicAngle != 0 {
+		info.Desc.Flags |= 1 << 6
+	}
+	if info.Desc.StemV == 0 {
+		if info.Bold {
+			info.Desc.StemV = 120
+		} else {
+			info.Desc.StemV = 70
+		}
+	}
+	// printf("makeFontDescriptor/FontBBox\n")
+	// dump(info.Desc.FontBBox)
+}
+
+// makeFontEncoding builds differences from reference encoding
+func makeFontEncoding(encList encListType, refEncFileStr string) (diffStr string, err error) {
+	var refList encListType
+	if refList, err = loadMap(refEncFileStr); err != nil {
+		return
+	}
+	var buf fmtBuffer
+	last := 0
+	for j := 32; j < 256; j++ {
+		if encList[j].name != refList[j].name {
+			if j != last+1 {
+				buf.printf("%d ", j)
+			}
+			last = j
+			buf.printf("/%s ", encList[j].name)
+		}
+	}
+	diffStr = strings.TrimSpace(buf.String())
+	return
+}
+
+func makeDefinitionFile(fileStr, tpStr, encodingFileStr string, embed bool, encList encListType, info fontInfoType) error {
+	var err error
+	var def fontDefType
+	def.Tp = tpStr
+	def.Name = info.FontName
+	makeFontDescriptor(&info)
+	def.Desc = info.Desc
+	// printf("makeDefinitionFile/FontBBox\n")
+	// dump(def.Desc.FontBBox)
+	def.Up = info.UnderlinePosition
+	def.Ut = info.UnderlineThickness
+	def.Cw = info.Widths
+	def.Enc = baseNoExt(encodingFileStr)
+	// fmt.Printf("encodingFileStr [%s], def.Enc [%s]\n", encodingFileStr, def.Enc)
+	// fmt.Printf("reference [%s]\n", filepath.Join(filepath.Dir(encodingFileStr), "cp1252.map"))
+	def.Diff, err = makeFontEncoding(encList, filepath.Join(filepath.Dir(encodingFileStr), "cp1252.map"))
+	if err != nil {
+		return err
+	}
+	def.File = info.File
+	def.Size1 = int(info.Size1)
+	def.Size2 = int(info.Size2)
+	def.OriginalSize = info.OriginalSize
+	// printf("Font definition file [%s]\n", fileStr)
+	var buf []byte
+	buf, err = json.Marshal(def)
+	if err != nil {
+		return err
+	}
+	var f *os.File
+	f, err = os.Create(fileStr)
+	if err != nil {
+		return err
+	}
+	defer f.Close()
+	_, err = f.Write(buf)
+	if err != nil {
+		return err
+	}
+	err = f.Close()
+	if err != nil {
+		return err
+	}
+
+	return err
+}
+
+// MakeFont generates a font definition file in JSON format. A definition file
+// of this type is required to use non-core fonts in the PDF documents that
+// gofpdf generates. See the makefont utility in the gofpdf package for a
+// command line interface to this function.
+//
+// fontFileStr is the name of the TrueType file (extension .ttf), OpenType file
+// (extension .otf) or binary Type1 file (extension .pfb) from which to
+// generate a definition file. If an OpenType file is specified, it must be one
+// that is based on TrueType outlines, not PostScript outlines; this cannot be
+// determined from the file extension alone. If a Type1 file is specified, a
+// metric file with the same pathname except with the extension .afm must be
+// present.
+//
+// encodingFileStr is the name of the encoding file that corresponds to the
+// font.
+//
+// dstDirStr is the name of the directory in which to save the definition file
+// and, if embed is true, the compressed font file.
+//
+// msgWriter is the writer that is called to display messages throughout the
+// process. Use nil to turn off messages.
+//
+// embed is true if the font is to be embedded in the PDF files.
+func MakeFont(fontFileStr, encodingFileStr, dstDirStr string, msgWriter io.Writer, embed bool) error {
+	if msgWriter == nil {
+		msgWriter = ioutil.Discard
+	}
+	if !fileExist(fontFileStr) {
+		return fmt.Errorf("font file not found: %s", fontFileStr)
+	}
+	extStr := strings.ToLower(fontFileStr[len(fontFileStr)-3:])
+	// printf("Font file extension [%s]\n", extStr)
+	var tpStr string
+	switch extStr {
+	case "ttf":
+		fallthrough
+	case "otf":
+		tpStr = "TrueType"
+	case "pfb":
+		tpStr = "Type1"
+	default:
+		return fmt.Errorf("unrecognized font file extension: %s", extStr)
+	}
+
+	var info fontInfoType
+	encList, err := loadMap(encodingFileStr)
+	if err != nil {
+		return err
+	}
+	// printf("Encoding table\n")
+	// dump(encList)
+	if tpStr == "TrueType" {
+		info, err = getInfoFromTrueType(fontFileStr, msgWriter, embed, encList)
+		if err != nil {
+			return err
+		}
+	} else {
+		info, err = getInfoFromType1(fontFileStr, msgWriter, embed, encList)
+		if err != nil {
+			return err
+		}
+	}
+	baseStr := baseNoExt(fontFileStr)
+	// fmt.Printf("Base [%s]\n", baseStr)
+	if embed {
+		var f *os.File
+		info.File = baseStr + ".z"
+		zFileStr := filepath.Join(dstDirStr, info.File)
+		f, err = os.Create(zFileStr)
+		if err != nil {
+			return err
+		}
+		defer f.Close()
+		cmp := zlib.NewWriter(f)
+		_, err = cmp.Write(info.Data)
+		if err != nil {
+			return err
+		}
+		err = cmp.Close()
+		if err != nil {
+			return err
+		}
+		fmt.Fprintf(msgWriter, "Font file compressed: %s\n", zFileStr)
+	}
+	defFileStr := filepath.Join(dstDirStr, baseStr+".json")
+	err = makeDefinitionFile(defFileStr, tpStr, encodingFileStr, embed, encList, info)
+	if err != nil {
+		return err
+	}
+	fmt.Fprintf(msgWriter, "Font definition file successfully generated: %s\n", defFileStr)
+	return nil
+}

+ 4860 - 0
vendor/github.com/jung-kurt/gofpdf/fpdf.go

@@ -0,0 +1,4860 @@
+/*
+ * Copyright (c) 2013-2014 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
+
+// Version: 1.7
+// Date:    2011-06-18
+// Author:  Olivier PLATHEY
+// Port to Go: Kurt Jung, 2013-07-15
+
+import (
+	"bytes"
+	"encoding/binary"
+	"encoding/json"
+	"fmt"
+	"image"
+	"image/color"
+	"image/gif"
+	"image/jpeg"
+	"image/png"
+	"io"
+	"io/ioutil"
+	"math"
+	"os"
+	"path"
+	"sort"
+	"strconv"
+	"strings"
+	"time"
+)
+
+var gl struct {
+	catalogSort  bool
+	noCompress   bool // Initial zero value indicates compression
+	creationDate time.Time
+}
+
+type fmtBuffer struct {
+	bytes.Buffer
+}
+
+func (b *fmtBuffer) printf(fmtStr string, args ...interface{}) {
+	b.Buffer.WriteString(fmt.Sprintf(fmtStr, args...))
+}
+
+func fpdfNew(orientationStr, unitStr, sizeStr, fontDirStr string, size SizeType) (f *Fpdf) {
+	f = new(Fpdf)
+	if orientationStr == "" {
+		orientationStr = "p"
+	} else {
+		orientationStr = strings.ToLower(orientationStr)
+	}
+	if unitStr == "" {
+		unitStr = "mm"
+	}
+	if sizeStr == "" {
+		sizeStr = "A4"
+	}
+	if fontDirStr == "" {
+		fontDirStr = "."
+	}
+	f.page = 0
+	f.n = 2
+	f.pages = make([]*bytes.Buffer, 0, 8)
+	f.pages = append(f.pages, bytes.NewBufferString("")) // pages[0] is unused (1-based)
+	f.pageSizes = make(map[int]SizeType)
+	f.pageBoxes = make(map[int]map[string]PageBox)
+	f.defPageBoxes = make(map[string]PageBox)
+	f.state = 0
+	f.fonts = make(map[string]fontDefType)
+	f.fontFiles = make(map[string]fontFileType)
+	f.diffs = make([]string, 0, 8)
+	f.templates = make(map[string]Template)
+	f.templateObjects = make(map[string]int)
+	f.importedObjs = make(map[string][]byte, 0)
+	f.importedObjPos = make(map[string]map[int]string, 0)
+	f.importedTplObjs = make(map[string]string)
+	f.importedTplIDs = make(map[string]int, 0)
+	f.images = make(map[string]*ImageInfoType)
+	f.pageLinks = make([][]linkType, 0, 8)
+	f.pageLinks = append(f.pageLinks, make([]linkType, 0, 0)) // pageLinks[0] is unused (1-based)
+	f.links = make([]intLinkType, 0, 8)
+	f.links = append(f.links, intLinkType{}) // links[0] is unused (1-based)
+	f.aliasMap = make(map[string]string)
+	f.inHeader = false
+	f.inFooter = false
+	f.lasth = 0
+	f.fontFamily = ""
+	f.fontStyle = ""
+	f.SetFontSize(12)
+	f.underline = false
+	f.setDrawColor(0, 0, 0)
+	f.setFillColor(0, 0, 0)
+	f.setTextColor(0, 0, 0)
+	f.colorFlag = false
+	f.ws = 0
+	f.fontpath = fontDirStr
+	// Core fonts
+	f.coreFonts = map[string]bool{
+		"courier":      true,
+		"helvetica":    true,
+		"times":        true,
+		"symbol":       true,
+		"zapfdingbats": true,
+	}
+	// Scale factor
+	switch unitStr {
+	case "pt", "point":
+		f.k = 1.0
+	case "mm":
+		f.k = 72.0 / 25.4
+	case "cm":
+		f.k = 72.0 / 2.54
+	case "in", "inch":
+		f.k = 72.0
+	default:
+		f.err = fmt.Errorf("incorrect unit %s", unitStr)
+		return
+	}
+	f.unitStr = unitStr
+	// Page sizes
+	f.stdPageSizes = make(map[string]SizeType)
+	f.stdPageSizes["a3"] = SizeType{841.89, 1190.55}
+	f.stdPageSizes["a4"] = SizeType{595.28, 841.89}
+	f.stdPageSizes["a5"] = SizeType{420.94, 595.28}
+	f.stdPageSizes["a6"] = SizeType{297.64, 420.94}
+	f.stdPageSizes["a2"] = SizeType{1190.55, 1683.78}
+	f.stdPageSizes["a1"] = SizeType{1683.78, 2383.94}
+	f.stdPageSizes["letter"] = SizeType{612, 792}
+	f.stdPageSizes["legal"] = SizeType{612, 1008}
+	f.stdPageSizes["tabloid"] = SizeType{792, 1224}
+	if size.Wd > 0 && size.Ht > 0 {
+		f.defPageSize = size
+	} else {
+		f.defPageSize = f.getpagesizestr(sizeStr)
+		if f.err != nil {
+			return
+		}
+	}
+	f.curPageSize = f.defPageSize
+	// Page orientation
+	switch orientationStr {
+	case "p", "portrait":
+		f.defOrientation = "P"
+		f.w = f.defPageSize.Wd
+		f.h = f.defPageSize.Ht
+		// dbg("Assign h: %8.2f", f.h)
+	case "l", "landscape":
+		f.defOrientation = "L"
+		f.w = f.defPageSize.Ht
+		f.h = f.defPageSize.Wd
+	default:
+		f.err = fmt.Errorf("incorrect orientation: %s", orientationStr)
+		return
+	}
+	f.curOrientation = f.defOrientation
+	f.wPt = f.w * f.k
+	f.hPt = f.h * f.k
+	// Page margins (1 cm)
+	margin := 28.35 / f.k
+	f.SetMargins(margin, margin, margin)
+	// Interior cell margin (1 mm)
+	f.cMargin = margin / 10
+	// Line width (0.2 mm)
+	f.lineWidth = 0.567 / f.k
+	// 	Automatic page break
+	f.SetAutoPageBreak(true, 2*margin)
+	// Default display mode
+	f.SetDisplayMode("default", "default")
+	if f.err != nil {
+		return
+	}
+	f.acceptPageBreak = func() bool {
+		return f.autoPageBreak
+	}
+	// Enable compression
+	f.SetCompression(!gl.noCompress)
+	f.spotColorMap = make(map[string]spotColorType)
+	f.blendList = make([]blendModeType, 0, 8)
+	f.blendList = append(f.blendList, blendModeType{}) // blendList[0] is unused (1-based)
+	f.blendMap = make(map[string]int)
+	f.blendMode = "Normal"
+	f.alpha = 1
+	f.gradientList = make([]gradientType, 0, 8)
+	f.gradientList = append(f.gradientList, gradientType{}) // gradientList[0] is unused
+	// Set default PDF version number
+	f.pdfVersion = "1.3"
+	f.SetProducer("FPDF "+cnFpdfVersion, true)
+	f.layerInit()
+	f.catalogSort = gl.catalogSort
+	f.creationDate = gl.creationDate
+	f.userUnderlineThickness = 1
+	return
+}
+
+// NewCustom returns a pointer to a new Fpdf instance. Its methods are
+// subsequently called to produce a single PDF document. NewCustom() is an
+// alternative to New() that provides additional customization. The PageSize()
+// example demonstrates this method.
+func NewCustom(init *InitType) (f *Fpdf) {
+	return fpdfNew(init.OrientationStr, init.UnitStr, init.SizeStr, init.FontDirStr, init.Size)
+}
+
+// New returns a pointer to a new Fpdf instance. Its methods are subsequently
+// called to produce a single PDF document.
+//
+// orientationStr specifies the default page orientation. For portrait mode,
+// specify "P" or "Portrait". For landscape mode, specify "L" or "Landscape".
+// An empty string will be replaced with "P".
+//
+// unitStr specifies the unit of length used in size parameters for elements
+// other than fonts, which are always measured in points. Specify "pt" for
+// point, "mm" for millimeter, "cm" for centimeter, or "in" for inch. An empty
+// string will be replaced with "mm".
+//
+// sizeStr specifies the page size. Acceptable values are "A3", "A4", "A5",
+// "Letter", "Legal", or "Tabloid". An empty string will be replaced with "A4".
+//
+// fontDirStr specifies the file system location in which font resources will
+// be found. An empty string is replaced with ".". This argument only needs to
+// reference an actual directory if a font other than one of the core
+// fonts is used. The core fonts are "courier", "helvetica" (also called
+// "arial"), "times", and "zapfdingbats" (also called "symbol").
+func New(orientationStr, unitStr, sizeStr, fontDirStr string) (f *Fpdf) {
+	return fpdfNew(orientationStr, unitStr, sizeStr, fontDirStr, SizeType{0, 0})
+}
+
+// Ok returns true if no processing errors have occurred.
+func (f *Fpdf) Ok() bool {
+	return f.err == nil
+}
+
+// Err returns true if a processing error has occurred.
+func (f *Fpdf) Err() bool {
+	return f.err != nil
+}
+
+// ClearError unsets the internal Fpdf error. This method should be used with
+// care, as an internal error condition usually indicates an unrecoverable
+// problem with the generation of a document. It is intended to deal with cases
+// in which an error is used to select an alternate form of the document.
+func (f *Fpdf) ClearError() {
+	f.err = nil
+}
+
+// SetErrorf sets the internal Fpdf error with formatted text to halt PDF
+// generation; this may facilitate error handling by application. If an error
+// condition is already set, this call is ignored.
+//
+// See the documentation for printing in the standard fmt package for details
+// about fmtStr and args.
+func (f *Fpdf) SetErrorf(fmtStr string, args ...interface{}) {
+	if f.err == nil {
+		f.err = fmt.Errorf(fmtStr, args...)
+	}
+}
+
+// String satisfies the fmt.Stringer interface and summarizes the Fpdf
+// instance.
+func (f *Fpdf) String() string {
+	return "Fpdf " + cnFpdfVersion
+}
+
+// SetError sets an error to halt PDF generation. This may facilitate error
+// handling by application. See also Ok(), Err() and Error().
+func (f *Fpdf) SetError(err error) {
+	if f.err == nil && err != nil {
+		f.err = err
+	}
+}
+
+// Error returns the internal Fpdf error; this will be nil if no error has occurred.
+func (f *Fpdf) Error() error {
+	return f.err
+}
+
+// GetPageSize returns the current page's width and height. This is the paper's
+// size. To compute the size of the area being used, subtract the margins (see
+// GetMargins()).
+func (f *Fpdf) GetPageSize() (width, height float64) {
+	width = f.w
+	height = f.h
+	return
+}
+
+// GetMargins returns the left, top, right, and bottom margins. The first three
+// are set with the SetMargins() method. The bottom margin is set with the
+// SetAutoPageBreak() method.
+func (f *Fpdf) GetMargins() (left, top, right, bottom float64) {
+	left = f.lMargin
+	top = f.tMargin
+	right = f.rMargin
+	bottom = f.bMargin
+	return
+}
+
+// SetMargins defines the left, top and right margins. By default, they equal 1
+// cm. Call this method to change them. If the value of the right margin is
+// less than zero, it is set to the same as the left margin.
+func (f *Fpdf) SetMargins(left, top, right float64) {
+	f.lMargin = left
+	f.tMargin = top
+	if right < 0 {
+		right = left
+	}
+	f.rMargin = right
+}
+
+// SetLeftMargin defines the left margin. The method can be called before
+// creating the first page. If the current abscissa gets out of page, it is
+// brought back to the margin.
+func (f *Fpdf) SetLeftMargin(margin float64) {
+	f.lMargin = margin
+	if f.page > 0 && f.x < margin {
+		f.x = margin
+	}
+}
+
+// GetCellMargin returns the cell margin. This is the amount of space before
+// and after the text within a cell that's left blank, and is in units passed
+// to New(). It defaults to 1mm.
+func (f *Fpdf) GetCellMargin() float64 {
+	return f.cMargin
+}
+
+// SetCellMargin sets the cell margin. This is the amount of space before and
+// after the text within a cell that's left blank, and is in units passed to
+// New().
+func (f *Fpdf) SetCellMargin(margin float64) {
+	f.cMargin = margin
+}
+
+// SetPageBoxRec sets the page box for the current page, and any following
+// pages. Allowable types are trim, trimbox, crop, cropbox, bleed, bleedbox,
+// art and artbox box types are case insensitive. See SetPageBox() for a method
+// that specifies the coordinates and extent of the page box individually.
+func (f *Fpdf) SetPageBoxRec(t string, pb PageBox) {
+	switch strings.ToLower(t) {
+	case "trim":
+		fallthrough
+	case "trimbox":
+		t = "TrimBox"
+	case "crop":
+		fallthrough
+	case "cropbox":
+		t = "CropBox"
+	case "bleed":
+		fallthrough
+	case "bleedbox":
+		t = "BleedBox"
+	case "art":
+		fallthrough
+	case "artbox":
+		t = "ArtBox"
+	default:
+		f.err = fmt.Errorf("%s is not a valid page box type", t)
+		return
+	}
+
+	pb.X = pb.X * f.k
+	pb.Y = pb.Y * f.k
+	pb.Wd = (pb.Wd * f.k) + pb.X
+	pb.Ht = (pb.Ht * f.k) + pb.Y
+
+	if f.page > 0 {
+		f.pageBoxes[f.page][t] = pb
+	}
+
+	// always override. page defaults are supplied in addPage function
+	f.defPageBoxes[t] = pb
+}
+
+// SetPageBox sets the page box for the current page, and any following pages.
+// Allowable types are trim, trimbox, crop, cropbox, bleed, bleedbox, art and
+// artbox box types are case insensitive.
+func (f *Fpdf) SetPageBox(t string, x, y, wd, ht float64) {
+	f.SetPageBoxRec(t, PageBox{SizeType{Wd: wd, Ht: ht}, PointType{X: x, Y: y}})
+}
+
+// SetPage sets the current page to that of a valid page in the PDF document.
+// pageNum is one-based. The SetPage() example demonstrates this method.
+func (f *Fpdf) SetPage(pageNum int) {
+	if (pageNum > 0) && (pageNum < len(f.pages)) {
+		f.page = pageNum
+	}
+}
+
+// PageCount returns the number of pages currently in the document. Since page
+// numbers in gofpdf are one-based, the page count is the same as the page
+// number of the current last page.
+func (f *Fpdf) PageCount() int {
+	return len(f.pages) - 1
+}
+
+// SetFontLocation sets the location in the file system of the font and font
+// definition files.
+func (f *Fpdf) SetFontLocation(fontDirStr string) {
+	f.fontpath = fontDirStr
+}
+
+// SetFontLoader sets a loader used to read font files (.json and .z) from an
+// arbitrary source. If a font loader has been specified, it is used to load
+// the named font resources when AddFont() is called. If this operation fails,
+// an attempt is made to load the resources from the configured font directory
+// (see SetFontLocation()).
+func (f *Fpdf) SetFontLoader(loader FontLoader) {
+	f.fontLoader = loader
+}
+
+// SetHeaderFuncMode sets the function that lets the application render the
+// page header. See SetHeaderFunc() for more details. The value for homeMode
+// should be set to true to have the current position set to the left and top
+// margin after the header function is called.
+func (f *Fpdf) SetHeaderFuncMode(fnc func(), homeMode bool) {
+	f.headerFnc = fnc
+	f.headerHomeMode = homeMode
+}
+
+// SetHeaderFunc sets the function that lets the application render the page
+// header. The specified function is automatically called by AddPage() and
+// should not be called directly by the application. The implementation in Fpdf
+// is empty, so you have to provide an appropriate function if you want page
+// headers. fnc will typically be a closure that has access to the Fpdf
+// instance and other document generation variables.
+//
+// A header is a convenient place to put background content that repeats on
+// each page such as a watermark. When this is done, remember to reset the X
+// and Y values so the normal content begins where expected. Including a
+// watermark on each page is demonstrated in the example for TransformRotate.
+//
+// This method is demonstrated in the example for AddPage().
+func (f *Fpdf) SetHeaderFunc(fnc func()) {
+	f.headerFnc = fnc
+}
+
+// SetFooterFunc sets the function that lets the application render the page
+// footer. The specified function is automatically called by AddPage() and
+// Close() and should not be called directly by the application. The
+// implementation in Fpdf is empty, so you have to provide an appropriate
+// function if you want page footers. fnc will typically be a closure that has
+// access to the Fpdf instance and other document generation variables. See
+// SetFooterFuncLpi for a similar function that passes a last page indicator.
+//
+// This method is demonstrated in the example for AddPage().
+func (f *Fpdf) SetFooterFunc(fnc func()) {
+	f.footerFnc = fnc
+	f.footerFncLpi = nil
+}
+
+// SetFooterFuncLpi sets the function that lets the application render the page
+// footer. The specified function is automatically called by AddPage() and
+// Close() and should not be called directly by the application. It is passed a
+// boolean that is true if the last page of the document is being rendered. The
+// implementation in Fpdf is empty, so you have to provide an appropriate
+// function if you want page footers. fnc will typically be a closure that has
+// access to the Fpdf instance and other document generation variables.
+func (f *Fpdf) SetFooterFuncLpi(fnc func(lastPage bool)) {
+	f.footerFncLpi = fnc
+	f.footerFnc = nil
+}
+
+// SetTopMargin defines the top margin. The method can be called before
+// creating the first page.
+func (f *Fpdf) SetTopMargin(margin float64) {
+	f.tMargin = margin
+}
+
+// SetRightMargin defines the right margin. The method can be called before
+// creating the first page.
+func (f *Fpdf) SetRightMargin(margin float64) {
+	f.rMargin = margin
+}
+
+// GetAutoPageBreak returns true if automatic pages breaks are enabled, false
+// otherwise. This is followed by the triggering limit from the bottom of the
+// page. This value applies only if automatic page breaks are enabled.
+func (f *Fpdf) GetAutoPageBreak() (auto bool, margin float64) {
+	auto = f.autoPageBreak
+	margin = f.bMargin
+	return
+}
+
+// SetAutoPageBreak enables or disables the automatic page breaking mode. When
+// enabling, the second parameter is the distance from the bottom of the page
+// that defines the triggering limit. By default, the mode is on and the margin
+// is 2 cm.
+func (f *Fpdf) SetAutoPageBreak(auto bool, margin float64) {
+	f.autoPageBreak = auto
+	f.bMargin = margin
+	f.pageBreakTrigger = f.h - margin
+}
+
+// SetDisplayMode sets advisory display directives for the document viewer.
+// Pages can be displayed entirely on screen, occupy the full width of the
+// window, use real size, be scaled by a specific zooming factor or use viewer
+// default (configured in the Preferences menu of Adobe Reader). The page
+// layout can be specified so that pages are displayed individually or in
+// pairs.
+//
+// zoomStr can be "fullpage" to display the entire page on screen, "fullwidth"
+// to use maximum width of window, "real" to use real size (equivalent to 100%
+// zoom) or "default" to use viewer default mode.
+//
+// layoutStr can be "single" (or "SinglePage") to display one page at once,
+// "continuous" (or "OneColumn") to display pages continuously, "two" (or
+// "TwoColumnLeft") to display two pages on two columns with odd-numbered pages
+// on the left, or "TwoColumnRight" to display two pages on two columns with
+// odd-numbered pages on the right, or "TwoPageLeft" to display pages two at a
+// time with odd-numbered pages on the left, or "TwoPageRight" to display pages
+// two at a time with odd-numbered pages on the right, or "default" to use
+// viewer default mode.
+func (f *Fpdf) SetDisplayMode(zoomStr, layoutStr string) {
+	if f.err != nil {
+		return
+	}
+	if layoutStr == "" {
+		layoutStr = "default"
+	}
+	switch zoomStr {
+	case "fullpage", "fullwidth", "real", "default":
+		f.zoomMode = zoomStr
+	default:
+		f.err = fmt.Errorf("incorrect zoom display mode: %s", zoomStr)
+		return
+	}
+	switch layoutStr {
+	case "single", "continuous", "two", "default", "SinglePage", "OneColumn",
+		"TwoColumnLeft", "TwoColumnRight", "TwoPageLeft", "TwoPageRight":
+		f.layoutMode = layoutStr
+	default:
+		f.err = fmt.Errorf("incorrect layout display mode: %s", layoutStr)
+		return
+	}
+}
+
+// SetDefaultCompression controls the default setting of the internal
+// compression flag. See SetCompression() for more details. Compression is on
+// by default.
+func SetDefaultCompression(compress bool) {
+	gl.noCompress = !compress
+}
+
+// SetCompression activates or deactivates page compression with zlib. When
+// activated, the internal representation of each page is compressed, which
+// leads to a compression ratio of about 2 for the resulting document.
+// Compression is on by default.
+func (f *Fpdf) SetCompression(compress bool) {
+	f.compress = compress
+}
+
+// SetProducer defines the producer of the document. isUTF8 indicates if the string
+// is encoded in ISO-8859-1 (false) or UTF-8 (true).
+func (f *Fpdf) SetProducer(producerStr string, isUTF8 bool) {
+	if isUTF8 {
+		producerStr = utf8toutf16(producerStr)
+	}
+	f.producer = producerStr
+}
+
+// SetTitle defines the title of the document. isUTF8 indicates if the string
+// is encoded in ISO-8859-1 (false) or UTF-8 (true).
+func (f *Fpdf) SetTitle(titleStr string, isUTF8 bool) {
+	if isUTF8 {
+		titleStr = utf8toutf16(titleStr)
+	}
+	f.title = titleStr
+}
+
+// SetSubject defines the subject of the document. isUTF8 indicates if the
+// string is encoded in ISO-8859-1 (false) or UTF-8 (true).
+func (f *Fpdf) SetSubject(subjectStr string, isUTF8 bool) {
+	if isUTF8 {
+		subjectStr = utf8toutf16(subjectStr)
+	}
+	f.subject = subjectStr
+}
+
+// SetAuthor defines the author of the document. isUTF8 indicates if the string
+// is encoded in ISO-8859-1 (false) or UTF-8 (true).
+func (f *Fpdf) SetAuthor(authorStr string, isUTF8 bool) {
+	if isUTF8 {
+		authorStr = utf8toutf16(authorStr)
+	}
+	f.author = authorStr
+}
+
+// SetKeywords defines the keywords of the document. keywordStr is a
+// space-delimited string, for example "invoice August". isUTF8 indicates if
+// the string is encoded
+func (f *Fpdf) SetKeywords(keywordsStr string, isUTF8 bool) {
+	if isUTF8 {
+		keywordsStr = utf8toutf16(keywordsStr)
+	}
+	f.keywords = keywordsStr
+}
+
+// SetCreator defines the creator of the document. isUTF8 indicates if the
+// string is encoded in ISO-8859-1 (false) or UTF-8 (true).
+func (f *Fpdf) SetCreator(creatorStr string, isUTF8 bool) {
+	if isUTF8 {
+		creatorStr = utf8toutf16(creatorStr)
+	}
+	f.creator = creatorStr
+}
+
+// SetXmpMetadata defines XMP metadata that will be embedded with the document.
+func (f *Fpdf) SetXmpMetadata(xmpStream []byte) {
+	f.xmp = xmpStream
+}
+
+// AliasNbPages defines an alias for the total number of pages. It will be
+// substituted as the document is closed. An empty string is replaced with the
+// string "{nb}".
+//
+// See the example for AddPage() for a demonstration of this method.
+func (f *Fpdf) AliasNbPages(aliasStr string) {
+	if aliasStr == "" {
+		aliasStr = "{nb}"
+	}
+	f.aliasNbPagesStr = aliasStr
+}
+
+// RTL enables right-to-left mode
+func (f *Fpdf) RTL() {
+	f.isRTL = true
+}
+
+// LTR disables right-to-left mode
+func (f *Fpdf) LTR() {
+	f.isRTL = false
+}
+
+// open begins a document
+func (f *Fpdf) open() {
+	f.state = 1
+}
+
+// Close terminates the PDF document. It is not necessary to call this method
+// explicitly because Output(), OutputAndClose() and OutputFileAndClose() do it
+// automatically. If the document contains no page, AddPage() is called to
+// prevent the generation of an invalid document.
+func (f *Fpdf) Close() {
+	if f.err == nil {
+		if f.clipNest > 0 {
+			f.err = fmt.Errorf("clip procedure must be explicitly ended")
+		} else if f.transformNest > 0 {
+			f.err = fmt.Errorf("transformation procedure must be explicitly ended")
+		}
+	}
+	if f.err != nil {
+		return
+	}
+	if f.state == 3 {
+		return
+	}
+	if f.page == 0 {
+		f.AddPage()
+		if f.err != nil {
+			return
+		}
+	}
+	// Page footer
+	f.inFooter = true
+	if f.footerFnc != nil {
+		f.footerFnc()
+	} else if f.footerFncLpi != nil {
+		f.footerFncLpi(true)
+	}
+	f.inFooter = false
+
+	// Close page
+	f.endpage()
+	// Close document
+	f.enddoc()
+	return
+}
+
+// PageSize returns the width and height of the specified page in the units
+// established in New(). These return values are followed by the unit of
+// measure itself. If pageNum is zero or otherwise out of bounds, it returns
+// the default page size, that is, the size of the page that would be added by
+// AddPage().
+func (f *Fpdf) PageSize(pageNum int) (wd, ht float64, unitStr string) {
+	sz, ok := f.pageSizes[pageNum]
+	if ok {
+		sz.Wd, sz.Ht = sz.Wd/f.k, sz.Ht/f.k
+	} else {
+		sz = f.defPageSize // user units
+	}
+	return sz.Wd, sz.Ht, f.unitStr
+}
+
+// AddPageFormat adds a new page with non-default orientation or size. See
+// AddPage() for more details.
+//
+// See New() for a description of orientationStr.
+//
+// size specifies the size of the new page in the units established in New().
+//
+// The PageSize() example demonstrates this method.
+func (f *Fpdf) AddPageFormat(orientationStr string, size SizeType) {
+	if f.err != nil {
+		return
+	}
+	if f.page != len(f.pages)-1 {
+		f.page = len(f.pages) - 1
+	}
+	if f.state == 0 {
+		f.open()
+	}
+	familyStr := f.fontFamily
+	style := f.fontStyle
+	if f.underline {
+		style += "U"
+	}
+	fontsize := f.fontSizePt
+	lw := f.lineWidth
+	dc := f.color.draw
+	fc := f.color.fill
+	tc := f.color.text
+	cf := f.colorFlag
+
+	if f.page > 0 {
+		f.inFooter = true
+		// Page footer avoid double call on footer.
+		if f.footerFnc != nil {
+			f.footerFnc()
+
+		} else if f.footerFncLpi != nil {
+			f.footerFncLpi(false) // not last page.
+		}
+		f.inFooter = false
+		// Close page
+		f.endpage()
+	}
+	// Start new page
+	f.beginpage(orientationStr, size)
+	// 	Set line cap style to current value
+	// f.out("2 J")
+	f.outf("%d J", f.capStyle)
+	// 	Set line join style to current value
+	f.outf("%d j", f.joinStyle)
+	// Set line width
+	f.lineWidth = lw
+	f.outf("%.2f w", lw*f.k)
+	// Set dash pattern
+	if len(f.dashArray) > 0 {
+		f.outputDashPattern()
+	}
+	// 	Set font
+	if familyStr != "" {
+		f.SetFont(familyStr, style, fontsize)
+		if f.err != nil {
+			return
+		}
+	}
+	// 	Set colors
+	f.color.draw = dc
+	if dc.str != "0 G" {
+		f.out(dc.str)
+	}
+	f.color.fill = fc
+	if fc.str != "0 g" {
+		f.out(fc.str)
+	}
+	f.color.text = tc
+	f.colorFlag = cf
+	// 	Page header
+	if f.headerFnc != nil {
+		f.inHeader = true
+		f.headerFnc()
+		f.inHeader = false
+		if f.headerHomeMode {
+			f.SetHomeXY()
+		}
+	}
+	// 	Restore line width
+	if f.lineWidth != lw {
+		f.lineWidth = lw
+		f.outf("%.2f w", lw*f.k)
+	}
+	// Restore font
+	if familyStr != "" {
+		f.SetFont(familyStr, style, fontsize)
+		if f.err != nil {
+			return
+		}
+	}
+	// Restore colors
+	if f.color.draw.str != dc.str {
+		f.color.draw = dc
+		f.out(dc.str)
+	}
+	if f.color.fill.str != fc.str {
+		f.color.fill = fc
+		f.out(fc.str)
+	}
+	f.color.text = tc
+	f.colorFlag = cf
+	return
+}
+
+// AddPage adds a new page to the document. If a page is already present, the
+// Footer() method is called first to output the footer. Then the page is
+// added, the current position set to the top-left corner according to the left
+// and top margins, and Header() is called to display the header.
+//
+// The font which was set before calling is automatically restored. There is no
+// need to call SetFont() again if you want to continue with the same font. The
+// same is true for colors and line width.
+//
+// The origin of the coordinate system is at the top-left corner and increasing
+// ordinates go downwards.
+//
+// See AddPageFormat() for a version of this method that allows the page size
+// and orientation to be different than the default.
+func (f *Fpdf) AddPage() {
+	if f.err != nil {
+		return
+	}
+	// dbg("AddPage")
+	f.AddPageFormat(f.defOrientation, f.defPageSize)
+	return
+}
+
+// PageNo returns the current page number.
+//
+// See the example for AddPage() for a demonstration of this method.
+func (f *Fpdf) PageNo() int {
+	return f.page
+}
+
+func colorComp(v int) (int, float64) {
+	if v < 0 {
+		v = 0
+	} else if v > 255 {
+		v = 255
+	}
+	return v, float64(v) / 255.0
+}
+
+func rgbColorValue(r, g, b int, grayStr, fullStr string) (clr colorType) {
+	clr.ir, clr.r = colorComp(r)
+	clr.ig, clr.g = colorComp(g)
+	clr.ib, clr.b = colorComp(b)
+	clr.mode = colorModeRGB
+	clr.gray = clr.ir == clr.ig && clr.r == clr.b
+	if len(grayStr) > 0 {
+		if clr.gray {
+			clr.str = sprintf("%.3f %s", clr.r, grayStr)
+		} else {
+			clr.str = sprintf("%.3f %.3f %.3f %s", clr.r, clr.g, clr.b, fullStr)
+		}
+	} else {
+		clr.str = sprintf("%.3f %.3f %.3f", clr.r, clr.g, clr.b)
+	}
+	return
+}
+
+// SetDrawColor defines the color used for all drawing operations (lines,
+// rectangles and cell borders). It is expressed in RGB components (0 - 255).
+// The method can be called before the first page is created. The value is
+// retained from page to page.
+func (f *Fpdf) SetDrawColor(r, g, b int) {
+	f.setDrawColor(r, g, b)
+}
+
+func (f *Fpdf) setDrawColor(r, g, b int) {
+	f.color.draw = rgbColorValue(r, g, b, "G", "RG")
+	if f.page > 0 {
+		f.out(f.color.draw.str)
+	}
+}
+
+// GetDrawColor returns the most recently set draw color as RGB components (0 -
+// 255). This will not be the current value if a draw color of some other type
+// (for example, spot) has been more recently set.
+func (f *Fpdf) GetDrawColor() (int, int, int) {
+	return f.color.draw.ir, f.color.draw.ig, f.color.draw.ib
+}
+
+// SetFillColor defines the color used for all filling operations (filled
+// rectangles and cell backgrounds). It is expressed in RGB components (0
+// -255). The method can be called before the first page is created and the
+// value is retained from page to page.
+func (f *Fpdf) SetFillColor(r, g, b int) {
+	f.setFillColor(r, g, b)
+}
+
+func (f *Fpdf) setFillColor(r, g, b int) {
+	f.color.fill = rgbColorValue(r, g, b, "g", "rg")
+	f.colorFlag = f.color.fill.str != f.color.text.str
+	if f.page > 0 {
+		f.out(f.color.fill.str)
+	}
+}
+
+// GetFillColor returns the most recently set fill color as RGB components (0 -
+// 255). This will not be the current value if a fill color of some other type
+// (for example, spot) has been more recently set.
+func (f *Fpdf) GetFillColor() (int, int, int) {
+	return f.color.fill.ir, f.color.fill.ig, f.color.fill.ib
+}
+
+// SetTextColor defines the color used for text. It is expressed in RGB
+// components (0 - 255). The method can be called before the first page is
+// created. The value is retained from page to page.
+func (f *Fpdf) SetTextColor(r, g, b int) {
+	f.setTextColor(r, g, b)
+}
+
+func (f *Fpdf) setTextColor(r, g, b int) {
+	f.color.text = rgbColorValue(r, g, b, "g", "rg")
+	f.colorFlag = f.color.fill.str != f.color.text.str
+}
+
+// GetTextColor returns the most recently set text color as RGB components (0 -
+// 255). This will not be the current value if a text color of some other type
+// (for example, spot) has been more recently set.
+func (f *Fpdf) GetTextColor() (int, int, int) {
+	return f.color.text.ir, f.color.text.ig, f.color.text.ib
+}
+
+// GetStringWidth returns the length of a string in user units. A font must be
+// currently selected.
+func (f *Fpdf) GetStringWidth(s string) float64 {
+	if f.err != nil {
+		return 0
+	}
+	w := f.GetStringSymbolWidth(s)
+	return float64(w) * f.fontSize / 1000
+}
+
+// GetStringSymbolWidth returns the length of a string in glyf units. A font must be
+// currently selected.
+func (f *Fpdf) GetStringSymbolWidth(s string) int {
+	if f.err != nil {
+		return 0
+	}
+	w := 0
+	if f.isCurrentUTF8 {
+		unicode := []rune(s)
+		for _, char := range unicode {
+			intChar := int(char)
+			if len(f.currentFont.Cw) >= intChar && f.currentFont.Cw[intChar] > 0 {
+				if f.currentFont.Cw[intChar] != 65535 {
+					w += f.currentFont.Cw[intChar]
+				}
+			} else if f.currentFont.Desc.MissingWidth != 0 {
+				w += f.currentFont.Desc.MissingWidth
+			} else {
+				w += 500
+			}
+		}
+	} else {
+		for _, ch := range []byte(s) {
+			if ch == 0 {
+				break
+			}
+			w += f.currentFont.Cw[ch]
+		}
+	}
+	return w
+}
+
+// SetLineWidth defines the line width. By default, the value equals 0.2 mm.
+// The method can be called before the first page is created. The value is
+// retained from page to page.
+func (f *Fpdf) SetLineWidth(width float64) {
+	f.setLineWidth(width)
+}
+
+func (f *Fpdf) setLineWidth(width float64) {
+	f.lineWidth = width
+	if f.page > 0 {
+		f.outf("%.2f w", width*f.k)
+	}
+}
+
+// GetLineWidth returns the current line thickness.
+func (f *Fpdf) GetLineWidth() float64 {
+	return f.lineWidth
+}
+
+// SetLineCapStyle defines the line cap style. styleStr should be "butt",
+// "round" or "square". A square style projects from the end of the line. The
+// method can be called before the first page is created. The value is
+// retained from page to page.
+func (f *Fpdf) SetLineCapStyle(styleStr string) {
+	var capStyle int
+	switch styleStr {
+	case "round":
+		capStyle = 1
+	case "square":
+		capStyle = 2
+	default:
+		capStyle = 0
+	}
+	f.capStyle = capStyle
+	if f.page > 0 {
+		f.outf("%d J", f.capStyle)
+	}
+}
+
+// SetLineJoinStyle defines the line cap style. styleStr should be "miter",
+// "round" or "bevel". The method can be called before the first page
+// is created. The value is retained from page to page.
+func (f *Fpdf) SetLineJoinStyle(styleStr string) {
+	var joinStyle int
+	switch styleStr {
+	case "round":
+		joinStyle = 1
+	case "bevel":
+		joinStyle = 2
+	default:
+		joinStyle = 0
+	}
+	f.joinStyle = joinStyle
+	if f.page > 0 {
+		f.outf("%d j", f.joinStyle)
+	}
+}
+
+// SetDashPattern sets the dash pattern that is used to draw lines. The
+// dashArray elements are numbers that specify the lengths, in units
+// established in New(), of alternating dashes and gaps. The dash phase
+// specifies the distance into the dash pattern at which to start the dash. The
+// dash pattern is retained from page to page. Call this method with an empty
+// array to restore solid line drawing.
+//
+// The Beziergon() example demonstrates this method.
+func (f *Fpdf) SetDashPattern(dashArray []float64, dashPhase float64) {
+	scaled := make([]float64, len(dashArray))
+	for i, value := range dashArray {
+		scaled[i] = value * f.k
+	}
+	dashPhase *= f.k
+
+	f.dashArray = scaled
+	f.dashPhase = dashPhase
+	if f.page > 0 {
+		f.outputDashPattern()
+	}
+
+}
+
+func (f *Fpdf) outputDashPattern() {
+	var buf bytes.Buffer
+	buf.WriteByte('[')
+	for i, value := range f.dashArray {
+		if i > 0 {
+			buf.WriteByte(' ')
+		}
+		buf.WriteString(strconv.FormatFloat(value, 'f', 2, 64))
+	}
+	buf.WriteString("] ")
+	buf.WriteString(strconv.FormatFloat(f.dashPhase, 'f', 2, 64))
+	buf.WriteString(" d")
+	f.outbuf(&buf)
+}
+
+// Line draws a line between points (x1, y1) and (x2, y2) using the current
+// draw color, line width and cap style.
+func (f *Fpdf) Line(x1, y1, x2, y2 float64) {
+	f.outf("%.2f %.2f m %.2f %.2f l S", x1*f.k, (f.h-y1)*f.k, x2*f.k, (f.h-y2)*f.k)
+}
+
+// fillDrawOp corrects path painting operators
+func fillDrawOp(styleStr string) (opStr string) {
+	switch strings.ToUpper(styleStr) {
+	case "", "D":
+		// Stroke the path.
+		opStr = "S"
+	case "F":
+		// fill the path, using the nonzero winding number rule
+		opStr = "f"
+	case "F*":
+		// fill the path, using the even-odd rule
+		opStr = "f*"
+	case "FD", "DF":
+		// fill and then stroke the path, using the nonzero winding number rule
+		opStr = "B"
+	case "FD*", "DF*":
+		// fill and then stroke the path, using the even-odd rule
+		opStr = "B*"
+	default:
+		opStr = styleStr
+	}
+	return
+}
+
+// Rect outputs a rectangle of width w and height h with the upper left corner
+// positioned at point (x, y).
+//
+// It can be drawn (border only), filled (with no border) or both. styleStr can
+// be "F" for filled, "D" for outlined only, or "DF" or "FD" for outlined and
+// filled. An empty string will be replaced with "D". Drawing uses the current
+// draw color and line width centered on the rectangle's perimeter. Filling
+// uses the current fill color.
+func (f *Fpdf) Rect(x, y, w, h float64, styleStr string) {
+	f.outf("%.2f %.2f %.2f %.2f re %s", x*f.k, (f.h-y)*f.k, w*f.k, -h*f.k, fillDrawOp(styleStr))
+}
+
+// RoundedRect outputs a rectangle of width w and height h with the upper left
+// corner positioned at point (x, y). It can be drawn (border only), filled
+// (with no border) or both. styleStr can be "F" for filled, "D" for outlined
+// only, or "DF" or "FD" for outlined and filled. An empty string will be
+// replaced with "D". Drawing uses the current draw color and line width
+// centered on the rectangle's perimeter. Filling uses the current fill color.
+// The rounded corners of the rectangle are specified by radius r. corners is a
+// string that includes "1" to round the upper left corner, "2" to round the
+// upper right corner, "3" to round the lower right corner, and "4" to round
+// the lower left corner. The RoundedRect example demonstrates this method.
+func (f *Fpdf) RoundedRect(x, y, w, h, r float64, corners string, stylestr string) {
+	// This routine was adapted by Brigham Thompson from a script by Christophe Prugnaud
+	k := f.k
+	hp := f.h
+	myArc := r * (4.0 / 3.0) * (math.Sqrt2 - 1.0)
+	f.outf("q %.5f %.5f m", (x+r)*k, (hp-y)*k)
+	xc := x + w - r
+	yc := y + r
+	f.outf("%.5f %.5f l", xc*k, (hp-y)*k)
+	if strings.Contains(corners, "2") == false {
+		f.outf("%.5f %.5f l", (x+w)*k, (hp-y)*k)
+	} else {
+		f.clipArc(xc+myArc, yc-r, xc+r, yc-myArc, xc+r, yc)
+	}
+	xc = x + w - r
+	yc = y + h - r
+	f.outf("%.5f %.5f l", (x+w)*k, (hp-yc)*k)
+	if strings.Contains(corners, "3") == false {
+		f.outf("%.5f %.5f l", (x+w)*k, (hp-(y+h))*k)
+	} else {
+		f.clipArc(xc+r, yc+myArc, xc+myArc, yc+r, xc, yc+r)
+	}
+	xc = x + r
+	yc = y + h - r
+	f.outf("%.5f %.5f l", xc*k, (hp-(y+h))*k)
+	if strings.Contains(corners, "4") == false {
+		f.outf("%.5f %.5f l", x*k, (hp-(y+h))*k)
+	} else {
+		f.clipArc(xc-myArc, yc+r, xc-r, yc+myArc, xc-r, yc)
+	}
+	xc = x + r
+	yc = y + r
+	f.outf("%.5f %.5f l", x*k, (hp-yc)*k)
+	if strings.Contains(corners, "1") == false {
+		f.outf("%.5f %.5f l", x*k, (hp-y)*k)
+		f.outf("%.5f %.5f l", (x+r)*k, (hp-y)*k)
+	} else {
+		f.clipArc(xc-r, yc-myArc, xc-myArc, yc-r, xc, yc-r)
+	}
+	f.out(fillDrawOp(stylestr))
+}
+
+// Circle draws a circle centered on point (x, y) with radius r.
+//
+// styleStr can be "F" for filled, "D" for outlined only, or "DF" or "FD" for
+// outlined and filled. An empty string will be replaced with "D". Drawing uses
+// the current draw color and line width centered on the circle's perimeter.
+// Filling uses the current fill color.
+func (f *Fpdf) Circle(x, y, r float64, styleStr string) {
+	f.Ellipse(x, y, r, r, 0, styleStr)
+}
+
+// Ellipse draws an ellipse centered at point (x, y). rx and ry specify its
+// horizontal and vertical radii.
+//
+// degRotate specifies the counter-clockwise angle in degrees that the ellipse
+// will be rotated.
+//
+// styleStr can be "F" for filled, "D" for outlined only, or "DF" or "FD" for
+// outlined and filled. An empty string will be replaced with "D". Drawing uses
+// the current draw color and line width centered on the ellipse's perimeter.
+// Filling uses the current fill color.
+//
+// The Circle() example demonstrates this method.
+func (f *Fpdf) Ellipse(x, y, rx, ry, degRotate float64, styleStr string) {
+	f.arc(x, y, rx, ry, degRotate, 0, 360, styleStr, false)
+}
+
+// Polygon draws a closed figure defined by a series of vertices specified by
+// points. The x and y fields of the points use the units established in New().
+// The last point in the slice will be implicitly joined to the first to close
+// the polygon.
+//
+// styleStr can be "F" for filled, "D" for outlined only, or "DF" or "FD" for
+// outlined and filled. An empty string will be replaced with "D". Drawing uses
+// the current draw color and line width centered on the ellipse's perimeter.
+// Filling uses the current fill color.
+func (f *Fpdf) Polygon(points []PointType, styleStr string) {
+	if len(points) > 2 {
+		for j, pt := range points {
+			if j == 0 {
+				f.point(pt.X, pt.Y)
+			} else {
+				f.outf("%.5f %.5f l ", pt.X*f.k, (f.h-pt.Y)*f.k)
+			}
+		}
+		f.outf("%.5f %.5f l ", points[0].X*f.k, (f.h-points[0].Y)*f.k)
+		f.DrawPath(styleStr)
+	}
+}
+
+// Beziergon draws a closed figure defined by a series of cubic Bézier curve
+// segments. The first point in the slice defines the starting point of the
+// figure. Each three following points p1, p2, p3 represent a curve segment to
+// the point p3 using p1 and p2 as the Bézier control points.
+//
+// The x and y fields of the points use the units established in New().
+//
+// styleStr can be "F" for filled, "D" for outlined only, or "DF" or "FD" for
+// outlined and filled. An empty string will be replaced with "D". Drawing uses
+// the current draw color and line width centered on the ellipse's perimeter.
+// Filling uses the current fill color.
+func (f *Fpdf) Beziergon(points []PointType, styleStr string) {
+
+	// Thanks, Robert Lillack, for contributing this function.
+
+	if len(points) < 4 {
+		return
+	}
+	f.point(points[0].XY())
+
+	points = points[1:]
+	for len(points) >= 3 {
+		cx0, cy0 := points[0].XY()
+		cx1, cy1 := points[1].XY()
+		x1, y1 := points[2].XY()
+		f.curve(cx0, cy0, cx1, cy1, x1, y1)
+		points = points[3:]
+	}
+
+	f.DrawPath(styleStr)
+}
+
+// point outputs current point
+func (f *Fpdf) point(x, y float64) {
+	f.outf("%.2f %.2f m", x*f.k, (f.h-y)*f.k)
+}
+
+// curve outputs a single cubic Bézier curve segment from current point
+func (f *Fpdf) curve(cx0, cy0, cx1, cy1, x, y float64) {
+	// Thanks, Robert Lillack, for straightening this out
+	f.outf("%.5f %.5f %.5f %.5f %.5f %.5f c", cx0*f.k, (f.h-cy0)*f.k, cx1*f.k,
+		(f.h-cy1)*f.k, x*f.k, (f.h-y)*f.k)
+}
+
+// Curve draws a single-segment quadratic Bézier curve. The curve starts at
+// the point (x0, y0) and ends at the point (x1, y1). The control point (cx,
+// cy) specifies the curvature. At the start point, the curve is tangent to the
+// straight line between the start point and the control point. At the end
+// point, the curve is tangent to the straight line between the end point and
+// the control point.
+//
+// styleStr can be "F" for filled, "D" for outlined only, or "DF" or "FD" for
+// outlined and filled. An empty string will be replaced with "D". Drawing uses
+// the current draw color, line width, and cap style centered on the curve's
+// path. Filling uses the current fill color.
+//
+// The Circle() example demonstrates this method.
+func (f *Fpdf) Curve(x0, y0, cx, cy, x1, y1 float64, styleStr string) {
+	f.point(x0, y0)
+	f.outf("%.5f %.5f %.5f %.5f v %s", cx*f.k, (f.h-cy)*f.k, x1*f.k, (f.h-y1)*f.k,
+		fillDrawOp(styleStr))
+}
+
+// CurveCubic draws a single-segment cubic Bézier curve. This routine performs
+// the same function as CurveBezierCubic() but has a nonstandard argument order.
+// It is retained to preserve backward compatibility.
+func (f *Fpdf) CurveCubic(x0, y0, cx0, cy0, x1, y1, cx1, cy1 float64, styleStr string) {
+	// f.point(x0, y0)
+	// f.outf("%.5f %.5f %.5f %.5f %.5f %.5f c %s", cx0*f.k, (f.h-cy0)*f.k,
+	// cx1*f.k, (f.h-cy1)*f.k, x1*f.k, (f.h-y1)*f.k, fillDrawOp(styleStr))
+	f.CurveBezierCubic(x0, y0, cx0, cy0, cx1, cy1, x1, y1, styleStr)
+}
+
+// CurveBezierCubic draws a single-segment cubic Bézier curve. The curve starts at
+// the point (x0, y0) and ends at the point (x1, y1). The control points (cx0,
+// cy0) and (cx1, cy1) specify the curvature. At the start point, the curve is
+// tangent to the straight line between the start point and the control point
+// (cx0, cy0). At the end point, the curve is tangent to the straight line
+// between the end point and the control point (cx1, cy1).
+//
+// styleStr can be "F" for filled, "D" for outlined only, or "DF" or "FD" for
+// outlined and filled. An empty string will be replaced with "D". Drawing uses
+// the current draw color, line width, and cap style centered on the curve's
+// path. Filling uses the current fill color.
+//
+// This routine performs the same function as CurveCubic() but uses standard
+// argument order.
+//
+// The Circle() example demonstrates this method.
+func (f *Fpdf) CurveBezierCubic(x0, y0, cx0, cy0, cx1, cy1, x1, y1 float64, styleStr string) {
+	f.point(x0, y0)
+	f.outf("%.5f %.5f %.5f %.5f %.5f %.5f c %s", cx0*f.k, (f.h-cy0)*f.k,
+		cx1*f.k, (f.h-cy1)*f.k, x1*f.k, (f.h-y1)*f.k, fillDrawOp(styleStr))
+}
+
+// Arc draws an elliptical arc centered at point (x, y). rx and ry specify its
+// horizontal and vertical radii.
+//
+// degRotate specifies the angle that the arc will be rotated. degStart and
+// degEnd specify the starting and ending angle of the arc. All angles are
+// specified in degrees and measured counter-clockwise from the 3 o'clock
+// position.
+//
+// styleStr can be "F" for filled, "D" for outlined only, or "DF" or "FD" for
+// outlined and filled. An empty string will be replaced with "D". Drawing uses
+// the current draw color, line width, and cap style centered on the arc's
+// path. Filling uses the current fill color.
+//
+// The Circle() example demonstrates this method.
+func (f *Fpdf) Arc(x, y, rx, ry, degRotate, degStart, degEnd float64, styleStr string) {
+	f.arc(x, y, rx, ry, degRotate, degStart, degEnd, styleStr, false)
+}
+
+// GetAlpha returns the alpha blending channel, which consists of the
+// alpha transparency value and the blend mode. See SetAlpha for more
+// details.
+func (f *Fpdf) GetAlpha() (alpha float64, blendModeStr string) {
+	return f.alpha, f.blendMode
+}
+
+// SetAlpha sets the alpha blending channel. The blending effect applies to
+// text, drawings and images.
+//
+// alpha must be a value between 0.0 (fully transparent) to 1.0 (fully opaque).
+// Values outside of this range result in an error.
+//
+// blendModeStr must be one of "Normal", "Multiply", "Screen", "Overlay",
+// "Darken", "Lighten", "ColorDodge", "ColorBurn","HardLight", "SoftLight",
+// "Difference", "Exclusion", "Hue", "Saturation", "Color", or "Luminosity". An
+// empty string is replaced with "Normal".
+//
+// To reset normal rendering after applying a blending mode, call this method
+// with alpha set to 1.0 and blendModeStr set to "Normal".
+func (f *Fpdf) SetAlpha(alpha float64, blendModeStr string) {
+	if f.err != nil {
+		return
+	}
+	var bl blendModeType
+	switch blendModeStr {
+	case "Normal", "Multiply", "Screen", "Overlay",
+		"Darken", "Lighten", "ColorDodge", "ColorBurn", "HardLight", "SoftLight",
+		"Difference", "Exclusion", "Hue", "Saturation", "Color", "Luminosity":
+		bl.modeStr = blendModeStr
+	case "":
+		bl.modeStr = "Normal"
+	default:
+		f.err = fmt.Errorf("unrecognized blend mode \"%s\"", blendModeStr)
+		return
+	}
+	if alpha < 0.0 || alpha > 1.0 {
+		f.err = fmt.Errorf("alpha value (0.0 - 1.0) is out of range: %.3f", alpha)
+		return
+	}
+	f.alpha = alpha
+	f.blendMode = blendModeStr
+	alphaStr := sprintf("%.3f", alpha)
+	keyStr := sprintf("%s %s", alphaStr, blendModeStr)
+	pos, ok := f.blendMap[keyStr]
+	if !ok {
+		pos = len(f.blendList) // at least 1
+		f.blendList = append(f.blendList, blendModeType{alphaStr, alphaStr, blendModeStr, 0})
+		f.blendMap[keyStr] = pos
+	}
+	f.outf("/GS%d gs", pos)
+}
+
+func (f *Fpdf) gradientClipStart(x, y, w, h float64) {
+	// Save current graphic state and set clipping area
+	f.outf("q %.2f %.2f %.2f %.2f re W n", x*f.k, (f.h-y)*f.k, w*f.k, -h*f.k)
+	// Set up transformation matrix for gradient
+	f.outf("%.5f 0 0 %.5f %.5f %.5f cm", w*f.k, h*f.k, x*f.k, (f.h-(y+h))*f.k)
+}
+
+func (f *Fpdf) gradientClipEnd() {
+	// Restore previous graphic state
+	f.out("Q")
+}
+
+func (f *Fpdf) gradient(tp, r1, g1, b1, r2, g2, b2 int, x1, y1, x2, y2, r float64) {
+	pos := len(f.gradientList)
+	clr1 := rgbColorValue(r1, g1, b1, "", "")
+	clr2 := rgbColorValue(r2, g2, b2, "", "")
+	f.gradientList = append(f.gradientList, gradientType{tp, clr1.str, clr2.str,
+		x1, y1, x2, y2, r, 0})
+	f.outf("/Sh%d sh", pos)
+}
+
+// LinearGradient draws a rectangular area with a blending of one color to
+// another. The rectangle is of width w and height h. Its upper left corner is
+// positioned at point (x, y).
+//
+// Each color is specified with three component values, one each for red, green
+// and blue. The values range from 0 to 255. The first color is specified by
+// (r1, g1, b1) and the second color by (r2, g2, b2).
+//
+// The blending is controlled with a gradient vector that uses normalized
+// coordinates in which the lower left corner is position (0, 0) and the upper
+// right corner is (1, 1). The vector's origin and destination are specified by
+// the points (x1, y1) and (x2, y2). In a linear gradient, blending occurs
+// perpendicularly to the vector. The vector does not necessarily need to be
+// anchored on the rectangle edge. Color 1 is used up to the origin of the
+// vector and color 2 is used beyond the vector's end point. Between the points
+// the colors are gradually blended.
+func (f *Fpdf) LinearGradient(x, y, w, h float64, r1, g1, b1, r2, g2, b2 int, x1, y1, x2, y2 float64) {
+	f.gradientClipStart(x, y, w, h)
+	f.gradient(2, r1, g1, b1, r2, g2, b2, x1, y1, x2, y2, 0)
+	f.gradientClipEnd()
+}
+
+// RadialGradient draws a rectangular area with a blending of one color to
+// another. The rectangle is of width w and height h. Its upper left corner is
+// positioned at point (x, y).
+//
+// Each color is specified with three component values, one each for red, green
+// and blue. The values range from 0 to 255. The first color is specified by
+// (r1, g1, b1) and the second color by (r2, g2, b2).
+//
+// The blending is controlled with a point and a circle, both specified with
+// normalized coordinates in which the lower left corner of the rendered
+// rectangle is position (0, 0) and the upper right corner is (1, 1). Color 1
+// begins at the origin point specified by (x1, y1). Color 2 begins at the
+// circle specified by the center point (x2, y2) and radius r. Colors are
+// gradually blended from the origin to the circle. The origin and the circle's
+// center do not necessarily have to coincide, but the origin must be within
+// the circle to avoid rendering problems.
+//
+// The LinearGradient() example demonstrates this method.
+func (f *Fpdf) RadialGradient(x, y, w, h float64, r1, g1, b1, r2, g2, b2 int, x1, y1, x2, y2, r float64) {
+	f.gradientClipStart(x, y, w, h)
+	f.gradient(3, r1, g1, b1, r2, g2, b2, x1, y1, x2, y2, r)
+	f.gradientClipEnd()
+}
+
+// ClipRect begins a rectangular clipping operation. The rectangle is of width
+// w and height h. Its upper left corner is positioned at point (x, y). outline
+// is true to draw a border with the current draw color and line width centered
+// on the rectangle's perimeter. Only the outer half of the border will be
+// shown. After calling this method, all rendering operations (for example,
+// Image(), LinearGradient(), etc) will be clipped by the specified rectangle.
+// Call ClipEnd() to restore unclipped operations.
+//
+// This ClipText() example demonstrates this method.
+func (f *Fpdf) ClipRect(x, y, w, h float64, outline bool) {
+	f.clipNest++
+	f.outf("q %.2f %.2f %.2f %.2f re W %s", x*f.k, (f.h-y)*f.k, w*f.k, -h*f.k, strIf(outline, "S", "n"))
+}
+
+// ClipText begins a clipping operation in which rendering is confined to the
+// character string specified by txtStr. The origin (x, y) is on the left of
+// the first character at the baseline. The current font is used. outline is
+// true to draw a border with the current draw color and line width centered on
+// the perimeters of the text characters. Only the outer half of the border
+// will be shown. After calling this method, all rendering operations (for
+// example, Image(), LinearGradient(), etc) will be clipped. Call ClipEnd() to
+// restore unclipped operations.
+func (f *Fpdf) ClipText(x, y float64, txtStr string, outline bool) {
+	f.clipNest++
+	f.outf("q BT %.5f %.5f Td %d Tr (%s) Tj ET", x*f.k, (f.h-y)*f.k, intIf(outline, 5, 7), f.escape(txtStr))
+}
+
+func (f *Fpdf) clipArc(x1, y1, x2, y2, x3, y3 float64) {
+	h := f.h
+	f.outf("%.5f %.5f %.5f %.5f %.5f %.5f c ", x1*f.k, (h-y1)*f.k,
+		x2*f.k, (h-y2)*f.k, x3*f.k, (h-y3)*f.k)
+}
+
+// ClipRoundedRect begins a rectangular clipping operation. The rectangle is of
+// width w and height h. Its upper left corner is positioned at point (x, y).
+// The rounded corners of the rectangle are specified by radius r. outline is
+// true to draw a border with the current draw color and line width centered on
+// the rectangle's perimeter. Only the outer half of the border will be shown.
+// After calling this method, all rendering operations (for example, Image(),
+// LinearGradient(), etc) will be clipped by the specified rectangle. Call
+// ClipEnd() to restore unclipped operations.
+//
+// This ClipText() example demonstrates this method.
+func (f *Fpdf) ClipRoundedRect(x, y, w, h, r float64, outline bool) {
+	f.clipNest++
+	k := f.k
+	hp := f.h
+	myArc := (4.0 / 3.0) * (math.Sqrt2 - 1.0)
+	f.outf("q %.5f %.5f m", (x+r)*k, (hp-y)*k)
+	xc := x + w - r
+	yc := y + r
+	f.outf("%.5f %.5f l", xc*k, (hp-y)*k)
+	f.clipArc(xc+r*myArc, yc-r, xc+r, yc-r*myArc, xc+r, yc)
+	xc = x + w - r
+	yc = y + h - r
+	f.outf("%.5f %.5f l", (x+w)*k, (hp-yc)*k)
+	f.clipArc(xc+r, yc+r*myArc, xc+r*myArc, yc+r, xc, yc+r)
+	xc = x + r
+	yc = y + h - r
+	f.outf("%.5f %.5f l", xc*k, (hp-(y+h))*k)
+	f.clipArc(xc-r*myArc, yc+r, xc-r, yc+r*myArc, xc-r, yc)
+	xc = x + r
+	yc = y + r
+	f.outf("%.5f %.5f l", x*k, (hp-yc)*k)
+	f.clipArc(xc-r, yc-r*myArc, xc-r*myArc, yc-r, xc, yc-r)
+	f.outf(" W %s", strIf(outline, "S", "n"))
+}
+
+// ClipEllipse begins an elliptical clipping operation. The ellipse is centered
+// at (x, y). Its horizontal and vertical radii are specified by rx and ry.
+// outline is true to draw a border with the current draw color and line width
+// centered on the ellipse's perimeter. Only the outer half of the border will
+// be shown. After calling this method, all rendering operations (for example,
+// Image(), LinearGradient(), etc) will be clipped by the specified ellipse.
+// Call ClipEnd() to restore unclipped operations.
+//
+// This ClipText() example demonstrates this method.
+func (f *Fpdf) ClipEllipse(x, y, rx, ry float64, outline bool) {
+	f.clipNest++
+	lx := (4.0 / 3.0) * rx * (math.Sqrt2 - 1)
+	ly := (4.0 / 3.0) * ry * (math.Sqrt2 - 1)
+	k := f.k
+	h := f.h
+	f.outf("q %.5f %.5f m %.5f %.5f %.5f %.5f %.5f %.5f c",
+		(x+rx)*k, (h-y)*k,
+		(x+rx)*k, (h-(y-ly))*k,
+		(x+lx)*k, (h-(y-ry))*k,
+		x*k, (h-(y-ry))*k)
+	f.outf("%.5f %.5f %.5f %.5f %.5f %.5f c",
+		(x-lx)*k, (h-(y-ry))*k,
+		(x-rx)*k, (h-(y-ly))*k,
+		(x-rx)*k, (h-y)*k)
+	f.outf("%.5f %.5f %.5f %.5f %.5f %.5f c",
+		(x-rx)*k, (h-(y+ly))*k,
+		(x-lx)*k, (h-(y+ry))*k,
+		x*k, (h-(y+ry))*k)
+	f.outf("%.5f %.5f %.5f %.5f %.5f %.5f c W %s",
+		(x+lx)*k, (h-(y+ry))*k,
+		(x+rx)*k, (h-(y+ly))*k,
+		(x+rx)*k, (h-y)*k,
+		strIf(outline, "S", "n"))
+}
+
+// ClipCircle begins a circular clipping operation. The circle is centered at
+// (x, y) and has radius r. outline is true to draw a border with the current
+// draw color and line width centered on the circle's perimeter. Only the outer
+// half of the border will be shown. After calling this method, all rendering
+// operations (for example, Image(), LinearGradient(), etc) will be clipped by
+// the specified circle. Call ClipEnd() to restore unclipped operations.
+//
+// The ClipText() example demonstrates this method.
+func (f *Fpdf) ClipCircle(x, y, r float64, outline bool) {
+	f.ClipEllipse(x, y, r, r, outline)
+}
+
+// ClipPolygon begins a clipping operation within a polygon. The figure is
+// defined by a series of vertices specified by points. The x and y fields of
+// the points use the units established in New(). The last point in the slice
+// will be implicitly joined to the first to close the polygon. outline is true
+// to draw a border with the current draw color and line width centered on the
+// polygon's perimeter. Only the outer half of the border will be shown. After
+// calling this method, all rendering operations (for example, Image(),
+// LinearGradient(), etc) will be clipped by the specified polygon. Call
+// ClipEnd() to restore unclipped operations.
+//
+// The ClipText() example demonstrates this method.
+func (f *Fpdf) ClipPolygon(points []PointType, outline bool) {
+	f.clipNest++
+	var s fmtBuffer
+	h := f.h
+	k := f.k
+	s.printf("q ")
+	for j, pt := range points {
+		s.printf("%.5f %.5f %s ", pt.X*k, (h-pt.Y)*k, strIf(j == 0, "m", "l"))
+	}
+	s.printf("h W %s", strIf(outline, "S", "n"))
+	f.out(s.String())
+}
+
+// ClipEnd ends a clipping operation that was started with a call to
+// ClipRect(), ClipRoundedRect(), ClipText(), ClipEllipse(), ClipCircle() or
+// ClipPolygon(). Clipping operations can be nested. The document cannot be
+// successfully output while a clipping operation is active.
+//
+// The ClipText() example demonstrates this method.
+func (f *Fpdf) ClipEnd() {
+	if f.err == nil {
+		if f.clipNest > 0 {
+			f.clipNest--
+			f.out("Q")
+		} else {
+			f.err = fmt.Errorf("error attempting to end clip operation out of sequence")
+		}
+	}
+}
+
+// AddFont imports a TrueType, OpenType or Type1 font and makes it available.
+// It is necessary to generate a font definition file first with the makefont
+// utility. It is not necessary to call this function for the core PDF fonts
+// (courier, helvetica, times, zapfdingbats).
+//
+// The JSON definition file (and the font file itself when embedding) must be
+// present in the font directory. If it is not found, the error "Could not
+// include font definition file" is set.
+//
+// family specifies the font family. The name can be chosen arbitrarily. If it
+// is a standard family name, it will override the corresponding font. This
+// string is used to subsequently set the font with the SetFont method.
+//
+// style specifies the font style. Acceptable values are (case insensitive) the
+// empty string for regular style, "B" for bold, "I" for italic, or "BI" or
+// "IB" for bold and italic combined.
+//
+// fileStr specifies the base name with ".json" extension of the font
+// definition file to be added. The file will be loaded from the font directory
+// specified in the call to New() or SetFontLocation().
+func (f *Fpdf) AddFont(familyStr, styleStr, fileStr string) {
+	f.addFont(familyStr, styleStr, fileStr, false)
+}
+
+// AddUTF8Font imports a TrueType font with utf-8 symbols and makes it available.
+// It is necessary to generate a font definition file first with the makefont
+// utility. It is not necessary to call this function for the core PDF fonts
+// (courier, helvetica, times, zapfdingbats).
+//
+// The JSON definition file (and the font file itself when embedding) must be
+// present in the font directory. If it is not found, the error "Could not
+// include font definition file" is set.
+//
+// family specifies the font family. The name can be chosen arbitrarily. If it
+// is a standard family name, it will override the corresponding font. This
+// string is used to subsequently set the font with the SetFont method.
+//
+// style specifies the font style. Acceptable values are (case insensitive) the
+// empty string for regular style, "B" for bold, "I" for italic, or "BI" or
+// "IB" for bold and italic combined.
+//
+// fileStr specifies the base name with ".json" extension of the font
+// definition file to be added. The file will be loaded from the font directory
+// specified in the call to New() or SetFontLocation().
+func (f *Fpdf) AddUTF8Font(familyStr, styleStr, fileStr string) {
+	f.addFont(familyStr, styleStr, fileStr, true)
+}
+
+func (f *Fpdf) addFont(familyStr, styleStr, fileStr string, isUTF8 bool) {
+	if fileStr == "" {
+		if isUTF8 {
+			fileStr = strings.Replace(familyStr, " ", "", -1) + strings.ToLower(styleStr) + ".ttf"
+		} else {
+			fileStr = strings.Replace(familyStr, " ", "", -1) + strings.ToLower(styleStr) + ".json"
+		}
+	}
+	if isUTF8 {
+		fontKey := getFontKey(familyStr, styleStr)
+		_, ok := f.fonts[fontKey]
+		if ok {
+			return
+		}
+		var ttfStat os.FileInfo
+		var err error
+		fileStr = path.Join(f.fontpath, fileStr)
+		ttfStat, err = os.Stat(fileStr)
+		if err != nil {
+			f.SetError(err)
+			return
+		}
+		originalSize := ttfStat.Size()
+		Type := "UTF8"
+		var utf8Bytes []byte
+		utf8Bytes, err = ioutil.ReadFile(fileStr)
+		if err != nil {
+			f.SetError(err)
+			return
+		}
+		reader := fileReader{readerPosition: 0, array: utf8Bytes}
+		utf8File := newUTF8Font(&reader)
+		err = utf8File.parseFile()
+		if err != nil {
+			f.SetError(err)
+			return
+		}
+
+		desc := FontDescType{
+			Ascent:       int(utf8File.Ascent),
+			Descent:      int(utf8File.Descent),
+			CapHeight:    utf8File.CapHeight,
+			Flags:        utf8File.Flags,
+			FontBBox:     utf8File.Bbox,
+			ItalicAngle:  utf8File.ItalicAngle,
+			StemV:        utf8File.StemV,
+			MissingWidth: round(utf8File.DefaultWidth),
+		}
+
+		var sbarr map[int]int
+		if f.aliasNbPagesStr == "" {
+			sbarr = makeSubsetRange(57)
+		} else {
+			sbarr = makeSubsetRange(32)
+		}
+		def := fontDefType{
+			Tp:        Type,
+			Name:      fontKey,
+			Desc:      desc,
+			Up:        int(round(utf8File.UnderlinePosition)),
+			Ut:        round(utf8File.UnderlineThickness),
+			Cw:        utf8File.CharWidths,
+			usedRunes: sbarr,
+			File:      fileStr,
+			utf8File:  utf8File,
+		}
+		def.i, _ = generateFontID(def)
+		f.fonts[fontKey] = def
+		f.fontFiles[fontKey] = fontFileType{
+			length1:  originalSize,
+			fontType: "UTF8",
+		}
+		f.fontFiles[fileStr] = fontFileType{
+			fontType: "UTF8",
+		}
+	} else {
+		if f.fontLoader != nil {
+			reader, err := f.fontLoader.Open(fileStr)
+			if err == nil {
+				f.AddFontFromReader(familyStr, styleStr, reader)
+				if closer, ok := reader.(io.Closer); ok {
+					closer.Close()
+				}
+				return
+			}
+		}
+
+		fileStr = path.Join(f.fontpath, fileStr)
+		file, err := os.Open(fileStr)
+		if err != nil {
+			f.err = err
+			return
+		}
+		defer file.Close()
+
+		f.AddFontFromReader(familyStr, styleStr, file)
+	}
+}
+
+func makeSubsetRange(end int) map[int]int {
+	answer := make(map[int]int)
+	for i := 0; i < end; i++ {
+		answer[i] = 0
+	}
+	return answer
+}
+
+// AddFontFromBytes imports a TrueType, OpenType or Type1 font from static
+// bytes within the executable and makes it available for use in the generated
+// document.
+//
+// family specifies the font family. The name can be chosen arbitrarily. If it
+// is a standard family name, it will override the corresponding font. This
+// string is used to subsequently set the font with the SetFont method.
+//
+// style specifies the font style. Acceptable values are (case insensitive) the
+// empty string for regular style, "B" for bold, "I" for italic, or "BI" or
+// "IB" for bold and italic combined.
+//
+// jsonFileBytes contain all bytes of JSON file.
+//
+// zFileBytes contain all bytes of Z file.
+func (f *Fpdf) AddFontFromBytes(familyStr, styleStr string, jsonFileBytes, zFileBytes []byte) {
+	f.addFontFromBytes(familyStr, styleStr, jsonFileBytes, zFileBytes, nil)
+}
+
+// AddUTF8FontFromBytes  imports a TrueType font with utf-8 symbols from static
+// bytes within the executable and makes it available for use in the generated
+// document.
+//
+// family specifies the font family. The name can be chosen arbitrarily. If it
+// is a standard family name, it will override the corresponding font. This
+// string is used to subsequently set the font with the SetFont method.
+//
+// style specifies the font style. Acceptable values are (case insensitive) the
+// empty string for regular style, "B" for bold, "I" for italic, or "BI" or
+// "IB" for bold and italic combined.
+//
+// jsonFileBytes contain all bytes of JSON file.
+//
+// zFileBytes contain all bytes of Z file.
+func (f *Fpdf) AddUTF8FontFromBytes(familyStr, styleStr string, utf8Bytes []byte) {
+	f.addFontFromBytes(familyStr, styleStr, nil, nil, utf8Bytes)
+}
+
+func (f *Fpdf) addFontFromBytes(familyStr, styleStr string, jsonFileBytes, zFileBytes, utf8Bytes []byte) {
+	if f.err != nil {
+		return
+	}
+
+	// load font key
+	var ok bool
+	fontkey := getFontKey(familyStr, styleStr)
+	_, ok = f.fonts[fontkey]
+
+	if ok {
+		return
+	}
+
+	if utf8Bytes != nil {
+
+		// if styleStr == "IB" {
+		// 	styleStr = "BI"
+		// }
+
+		Type := "UTF8"
+		reader := fileReader{readerPosition: 0, array: utf8Bytes}
+
+		utf8File := newUTF8Font(&reader)
+
+		err := utf8File.parseFile()
+		if err != nil {
+			fmt.Printf("get metrics Error: %e\n", err)
+			return
+		}
+		desc := FontDescType{
+			Ascent:       int(utf8File.Ascent),
+			Descent:      int(utf8File.Descent),
+			CapHeight:    utf8File.CapHeight,
+			Flags:        utf8File.Flags,
+			FontBBox:     utf8File.Bbox,
+			ItalicAngle:  utf8File.ItalicAngle,
+			StemV:        utf8File.StemV,
+			MissingWidth: round(utf8File.DefaultWidth),
+		}
+
+		var sbarr map[int]int
+		if f.aliasNbPagesStr == "" {
+			sbarr = makeSubsetRange(57)
+		} else {
+			sbarr = makeSubsetRange(32)
+		}
+		def := fontDefType{
+			Tp:        Type,
+			Name:      fontkey,
+			Desc:      desc,
+			Up:        int(round(utf8File.UnderlinePosition)),
+			Ut:        round(utf8File.UnderlineThickness),
+			Cw:        utf8File.CharWidths,
+			utf8File:  utf8File,
+			usedRunes: sbarr,
+		}
+		def.i, _ = generateFontID(def)
+		f.fonts[fontkey] = def
+	} else {
+		// load font definitions
+		var info fontDefType
+		err := json.Unmarshal(jsonFileBytes, &info)
+
+		if err != nil {
+			f.err = err
+		}
+
+		if f.err != nil {
+			return
+		}
+
+		if info.i, err = generateFontID(info); err != nil {
+			f.err = err
+			return
+		}
+
+		// search existing encodings
+		if len(info.Diff) > 0 {
+			n := -1
+
+			for j, str := range f.diffs {
+				if str == info.Diff {
+					n = j + 1
+					break
+				}
+			}
+
+			if n < 0 {
+				f.diffs = append(f.diffs, info.Diff)
+				n = len(f.diffs)
+			}
+
+			info.DiffN = n
+		}
+
+		// embed font
+		if len(info.File) > 0 {
+			if info.Tp == "TrueType" {
+				f.fontFiles[info.File] = fontFileType{
+					length1:  int64(info.OriginalSize),
+					embedded: true,
+					content:  zFileBytes,
+				}
+			} else {
+				f.fontFiles[info.File] = fontFileType{
+					length1:  int64(info.Size1),
+					length2:  int64(info.Size2),
+					embedded: true,
+					content:  zFileBytes,
+				}
+			}
+		}
+
+		f.fonts[fontkey] = info
+	}
+}
+
+// getFontKey is used by AddFontFromReader and GetFontDesc
+func getFontKey(familyStr, styleStr string) string {
+	familyStr = strings.ToLower(familyStr)
+	styleStr = strings.ToUpper(styleStr)
+	if styleStr == "IB" {
+		styleStr = "BI"
+	}
+	return familyStr + styleStr
+}
+
+// AddFontFromReader imports a TrueType, OpenType or Type1 font and makes it
+// available using a reader that satisifies the io.Reader interface. See
+// AddFont for details about familyStr and styleStr.
+func (f *Fpdf) AddFontFromReader(familyStr, styleStr string, r io.Reader) {
+	if f.err != nil {
+		return
+	}
+	// dbg("Adding family [%s], style [%s]", familyStr, styleStr)
+	var ok bool
+	fontkey := getFontKey(familyStr, styleStr)
+	_, ok = f.fonts[fontkey]
+	if ok {
+		return
+	}
+	var info fontDefType
+	info = f.loadfont(r)
+	if f.err != nil {
+		return
+	}
+	if len(info.Diff) > 0 {
+		// Search existing encodings
+		n := -1
+		for j, str := range f.diffs {
+			if str == info.Diff {
+				n = j + 1
+				break
+			}
+		}
+		if n < 0 {
+			f.diffs = append(f.diffs, info.Diff)
+			n = len(f.diffs)
+		}
+		info.DiffN = n
+	}
+	// dbg("font [%s], type [%s]", info.File, info.Tp)
+	if len(info.File) > 0 {
+		// Embedded font
+		if info.Tp == "TrueType" {
+			f.fontFiles[info.File] = fontFileType{length1: int64(info.OriginalSize)}
+		} else {
+			f.fontFiles[info.File] = fontFileType{length1: int64(info.Size1), length2: int64(info.Size2)}
+		}
+	}
+	f.fonts[fontkey] = info
+	return
+}
+
+// GetFontDesc returns the font descriptor, which can be used for
+// example to find the baseline of a font. If familyStr is empty
+// current font descriptor will be returned.
+// See FontDescType for documentation about the font descriptor.
+// See AddFont for details about familyStr and styleStr.
+func (f *Fpdf) GetFontDesc(familyStr, styleStr string) FontDescType {
+	if familyStr == "" {
+		return f.currentFont.Desc
+	}
+	return f.fonts[getFontKey(familyStr, styleStr)].Desc
+}
+
+// SetFont sets the font used to print character strings. It is mandatory to
+// call this method at least once before printing text or the resulting
+// document will not be valid.
+//
+// The font can be either a standard one or a font added via the AddFont()
+// method or AddFontFromReader() method. Standard fonts use the Windows
+// encoding cp1252 (Western Europe).
+//
+// The method can be called before the first page is created and the font is
+// kept from page to page. If you just wish to change the current font size, it
+// is simpler to call SetFontSize().
+//
+// Note: the font definition file must be accessible. An error is set if the
+// file cannot be read.
+//
+// familyStr specifies the font family. It can be either a name defined by
+// AddFont(), AddFontFromReader() or one of the standard families (case
+// insensitive): "Courier" for fixed-width, "Helvetica" or "Arial" for sans
+// serif, "Times" for serif, "Symbol" or "ZapfDingbats" for symbolic.
+//
+// styleStr can be "B" (bold), "I" (italic), "U" (underscore) or any
+// combination. The default value (specified with an empty string) is regular.
+// Bold and italic styles do not apply to Symbol and ZapfDingbats.
+//
+// size is the font size measured in points. The default value is the current
+// size. If no size has been specified since the beginning of the document, the
+// value taken is 12.
+func (f *Fpdf) SetFont(familyStr, styleStr string, size float64) {
+	// dbg("SetFont x %.2f, lMargin %.2f", f.x, f.lMargin)
+
+	if f.err != nil {
+		return
+	}
+	// dbg("SetFont")
+	var ok bool
+	if familyStr == "" {
+		familyStr = f.fontFamily
+	} else {
+		familyStr = strings.ToLower(familyStr)
+	}
+	styleStr = strings.ToUpper(styleStr)
+	f.underline = strings.Contains(styleStr, "U")
+	if f.underline {
+		styleStr = strings.Replace(styleStr, "U", "", -1)
+	}
+	if styleStr == "IB" {
+		styleStr = "BI"
+	}
+	if size == 0.0 {
+		size = f.fontSizePt
+	}
+
+	// Test if font is already loaded
+	fontKey := familyStr + styleStr
+	_, ok = f.fonts[fontKey]
+	if !ok {
+		// Test if one of the core fonts
+		if familyStr == "arial" {
+			familyStr = "helvetica"
+		}
+		_, ok = f.coreFonts[familyStr]
+		if ok {
+			if familyStr == "symbol" {
+				familyStr = "zapfdingbats"
+			}
+			if familyStr == "zapfdingbats" {
+				styleStr = ""
+			}
+			fontKey = familyStr + styleStr
+			_, ok = f.fonts[fontKey]
+			if !ok {
+				rdr := f.coreFontReader(familyStr, styleStr)
+				if f.err == nil {
+					f.AddFontFromReader(familyStr, styleStr, rdr)
+				}
+				if f.err != nil {
+					return
+				}
+			}
+		} else {
+			f.err = fmt.Errorf("undefined font: %s %s", familyStr, styleStr)
+			return
+		}
+	}
+	// Select it
+	f.fontFamily = familyStr
+	f.fontStyle = styleStr
+	f.fontSizePt = size
+	f.fontSize = size / f.k
+	f.currentFont = f.fonts[fontKey]
+	if f.currentFont.Tp == "UTF8" {
+		f.isCurrentUTF8 = true
+	} else {
+		f.isCurrentUTF8 = false
+	}
+	if f.page > 0 {
+		f.outf("BT /F%s %.2f Tf ET", f.currentFont.i, f.fontSizePt)
+	}
+	return
+}
+
+// SetFontStyle sets the style of the current font. See also SetFont()
+func (f *Fpdf) SetFontStyle(styleStr string) {
+	f.SetFont(f.fontFamily, styleStr, f.fontSizePt)
+}
+
+// SetFontSize defines the size of the current font. Size is specified in
+// points (1/ 72 inch). See also SetFontUnitSize().
+func (f *Fpdf) SetFontSize(size float64) {
+	f.fontSizePt = size
+	f.fontSize = size / f.k
+	if f.page > 0 {
+		f.outf("BT /F%s %.2f Tf ET", f.currentFont.i, f.fontSizePt)
+	}
+}
+
+// SetFontUnitSize defines the size of the current font. Size is specified in
+// the unit of measure specified in New(). See also SetFontSize().
+func (f *Fpdf) SetFontUnitSize(size float64) {
+	f.fontSizePt = size * f.k
+	f.fontSize = size
+	if f.page > 0 {
+		f.outf("BT /F%s %.2f Tf ET", f.currentFont.i, f.fontSizePt)
+	}
+}
+
+// GetFontSize returns the size of the current font in points followed by the
+// size in the unit of measure specified in New(). The second value can be used
+// as a line height value in drawing operations.
+func (f *Fpdf) GetFontSize() (ptSize, unitSize float64) {
+	return f.fontSizePt, f.fontSize
+}
+
+// AddLink creates a new internal link and returns its identifier. An internal
+// link is a clickable area which directs to another place within the document.
+// The identifier can then be passed to Cell(), Write(), Image() or Link(). The
+// destination is defined with SetLink().
+func (f *Fpdf) AddLink() int {
+	f.links = append(f.links, intLinkType{})
+	return len(f.links) - 1
+}
+
+// SetLink defines the page and position a link points to. See AddLink().
+func (f *Fpdf) SetLink(link int, y float64, page int) {
+	if y == -1 {
+		y = f.y
+	}
+	if page == -1 {
+		page = f.page
+	}
+	f.links[link] = intLinkType{page, y}
+}
+
+// newLink adds a new clickable link on current page
+func (f *Fpdf) newLink(x, y, w, h float64, link int, linkStr string) {
+	// linkList, ok := f.pageLinks[f.page]
+	// if !ok {
+	// linkList = make([]linkType, 0, 8)
+	// f.pageLinks[f.page] = linkList
+	// }
+	f.pageLinks[f.page] = append(f.pageLinks[f.page],
+		linkType{x * f.k, f.hPt - y*f.k, w * f.k, h * f.k, link, linkStr})
+}
+
+// Link puts a link on a rectangular area of the page. Text or image links are
+// generally put via Cell(), Write() or Image(), but this method can be useful
+// for instance to define a clickable area inside an image. link is the value
+// returned by AddLink().
+func (f *Fpdf) Link(x, y, w, h float64, link int) {
+	f.newLink(x, y, w, h, link, "")
+}
+
+// LinkString puts a link on a rectangular area of the page. Text or image
+// links are generally put via Cell(), Write() or Image(), but this method can
+// be useful for instance to define a clickable area inside an image. linkStr
+// is the target URL.
+func (f *Fpdf) LinkString(x, y, w, h float64, linkStr string) {
+	f.newLink(x, y, w, h, 0, linkStr)
+}
+
+// Bookmark sets a bookmark that will be displayed in a sidebar outline. txtStr
+// is the title of the bookmark. level specifies the level of the bookmark in
+// the outline; 0 is the top level, 1 is just below, and so on. y specifies the
+// vertical position of the bookmark destination in the current page; -1
+// indicates the current position.
+func (f *Fpdf) Bookmark(txtStr string, level int, y float64) {
+	if y == -1 {
+		y = f.y
+	}
+	f.outlines = append(f.outlines, outlineType{text: txtStr, level: level, y: y, p: f.PageNo(), prev: -1, last: -1, next: -1, first: -1})
+}
+
+// Text prints a character string. The origin (x, y) is on the left of the
+// first character at the baseline. This method permits a string to be placed
+// precisely on the page, but it is usually easier to use Cell(), MultiCell()
+// or Write() which are the standard methods to print text.
+func (f *Fpdf) Text(x, y float64, txtStr string) {
+	var txt2 string
+	if f.isCurrentUTF8 {
+		if f.isRTL {
+			txtStr = reverseText(txtStr)
+			x -= f.GetStringWidth(txtStr)
+		}
+		txt2 = f.escape(utf8toutf16(txtStr, false))
+		for _, uni := range []rune(txtStr) {
+			f.currentFont.usedRunes[int(uni)] = int(uni)
+		}
+	} else {
+		txt2 = f.escape(txtStr)
+	}
+	s := sprintf("BT %.2f %.2f Td (%s) Tj ET", x*f.k, (f.h-y)*f.k, txt2)
+	if f.underline && txtStr != "" {
+		s += " " + f.dounderline(x, y, txtStr)
+	}
+	if f.colorFlag {
+		s = sprintf("q %s %s Q", f.color.text.str, s)
+	}
+	f.out(s)
+}
+
+// SetWordSpacing sets spacing between words of following text. See the
+// WriteAligned() example for a demonstration of its use.
+func (f *Fpdf) SetWordSpacing(space float64) {
+	f.out(sprintf("%.5f Tw", space*f.k))
+}
+
+// SetAcceptPageBreakFunc allows the application to control where page breaks
+// occur.
+//
+// fnc is an application function (typically a closure) that is called by the
+// library whenever a page break condition is met. The break is issued if true
+// is returned. The default implementation returns a value according to the
+// mode selected by SetAutoPageBreak. The function provided should not be
+// called by the application.
+//
+// See the example for SetLeftMargin() to see how this function can be used to
+// manage multiple columns.
+func (f *Fpdf) SetAcceptPageBreakFunc(fnc func() bool) {
+	f.acceptPageBreak = fnc
+}
+
+// CellFormat prints a rectangular cell with optional borders, background color
+// and character string. The upper-left corner of the cell corresponds to the
+// current position. The text can be aligned or centered. After the call, the
+// current position moves to the right or to the next line. It is possible to
+// put a link on the text.
+//
+// An error will be returned if a call to SetFont() has not already taken
+// place before this method is called.
+//
+// If automatic page breaking is enabled and the cell goes beyond the limit, a
+// page break is done before outputting.
+//
+// w and h specify the width and height of the cell. If w is 0, the cell
+// extends up to the right margin. Specifying 0 for h will result in no output,
+// but the current position will be advanced by w.
+//
+// txtStr specifies the text to display.
+//
+// borderStr specifies how the cell border will be drawn. An empty string
+// indicates no border, "1" indicates a full border, and one or more of "L",
+// "T", "R" and "B" indicate the left, top, right and bottom sides of the
+// border.
+//
+// ln indicates where the current position should go after the call. Possible
+// values are 0 (to the right), 1 (to the beginning of the next line), and 2
+// (below). Putting 1 is equivalent to putting 0 and calling Ln() just after.
+//
+// alignStr specifies how the text is to be positioned within the cell.
+// Horizontal alignment is controlled by including "L", "C" or "R" (left,
+// center, right) in alignStr. Vertical alignment is controlled by including
+// "T", "M", "B" or "A" (top, middle, bottom, baseline) in alignStr. The default
+// alignment is left middle.
+//
+// fill is true to paint the cell background or false to leave it transparent.
+//
+// link is the identifier returned by AddLink() or 0 for no internal link.
+//
+// linkStr is a target URL or empty for no external link. A non--zero value for
+// link takes precedence over linkStr.
+func (f *Fpdf) CellFormat(w, h float64, txtStr, borderStr string, ln int,
+	alignStr string, fill bool, link int, linkStr string) {
+	// dbg("CellFormat. h = %.2f, borderStr = %s", h, borderStr)
+	if f.err != nil {
+		return
+	}
+
+	if f.currentFont.Name == "" {
+		f.err = fmt.Errorf("font has not been set; unable to render text")
+		return
+	}
+
+	borderStr = strings.ToUpper(borderStr)
+	k := f.k
+	if f.y+h > f.pageBreakTrigger && !f.inHeader && !f.inFooter && f.acceptPageBreak() {
+		// Automatic page break
+		x := f.x
+		ws := f.ws
+		// dbg("auto page break, x %.2f, ws %.2f", x, ws)
+		if ws > 0 {
+			f.ws = 0
+			f.out("0 Tw")
+		}
+		f.AddPageFormat(f.curOrientation, f.curPageSize)
+		if f.err != nil {
+			return
+		}
+		f.x = x
+		if ws > 0 {
+			f.ws = ws
+			f.outf("%.3f Tw", ws*k)
+		}
+	}
+	if w == 0 {
+		w = f.w - f.rMargin - f.x
+	}
+	var s fmtBuffer
+	if fill || borderStr == "1" {
+		var op string
+		if fill {
+			if borderStr == "1" {
+				op = "B"
+				// dbg("border is '1', fill")
+			} else {
+				op = "f"
+				// dbg("border is empty, fill")
+			}
+		} else {
+			// dbg("border is '1', no fill")
+			op = "S"
+		}
+		/// dbg("(CellFormat) f.x %.2f f.k %.2f", f.x, f.k)
+		s.printf("%.2f %.2f %.2f %.2f re %s ", f.x*k, (f.h-f.y)*k, w*k, -h*k, op)
+	}
+	if len(borderStr) > 0 && borderStr != "1" {
+		// fmt.Printf("border is '%s', no fill\n", borderStr)
+		x := f.x
+		y := f.y
+		left := x * k
+		top := (f.h - y) * k
+		right := (x + w) * k
+		bottom := (f.h - (y + h)) * k
+		if strings.Contains(borderStr, "L") {
+			s.printf("%.2f %.2f m %.2f %.2f l S ", left, top, left, bottom)
+		}
+		if strings.Contains(borderStr, "T") {
+			s.printf("%.2f %.2f m %.2f %.2f l S ", left, top, right, top)
+		}
+		if strings.Contains(borderStr, "R") {
+			s.printf("%.2f %.2f m %.2f %.2f l S ", right, top, right, bottom)
+		}
+		if strings.Contains(borderStr, "B") {
+			s.printf("%.2f %.2f m %.2f %.2f l S ", left, bottom, right, bottom)
+		}
+	}
+	if len(txtStr) > 0 {
+		var dx, dy float64
+		// Horizontal alignment
+		switch {
+		case strings.Contains(alignStr, "R"):
+			dx = w - f.cMargin - f.GetStringWidth(txtStr)
+		case strings.Contains(alignStr, "C"):
+			dx = (w - f.GetStringWidth(txtStr)) / 2
+		default:
+			dx = f.cMargin
+		}
+
+		// Vertical alignment
+		switch {
+		case strings.Contains(alignStr, "T"):
+			dy = (f.fontSize - h) / 2.0
+		case strings.Contains(alignStr, "B"):
+			dy = (h - f.fontSize) / 2.0
+		case strings.Contains(alignStr, "A"):
+			var descent float64
+			d := f.currentFont.Desc
+			if d.Descent == 0 {
+				// not defined (standard font?), use average of 19%
+				descent = -0.19 * f.fontSize
+			} else {
+				descent = float64(d.Descent) * f.fontSize / float64(d.Ascent-d.Descent)
+			}
+			dy = (h-f.fontSize)/2.0 - descent
+		default:
+			dy = 0
+		}
+		if f.colorFlag {
+			s.printf("q %s ", f.color.text.str)
+		}
+		//If multibyte, Tw has no effect - do word spacing using an adjustment before each space
+		if (f.ws != 0 || alignStr == "J") && f.isCurrentUTF8 { // && f.ws != 0
+			if f.isRTL {
+				txtStr = reverseText(txtStr)
+			}
+			wmax := int(math.Ceil((w - 2*f.cMargin) * 1000 / f.fontSize))
+			for _, uni := range []rune(txtStr) {
+				f.currentFont.usedRunes[int(uni)] = int(uni)
+			}
+			space := f.escape(utf8toutf16(" ", false))
+			strSize := f.GetStringSymbolWidth(txtStr)
+			s.printf("BT 0 Tw %.2f %.2f Td [", (f.x+dx)*k, (f.h-(f.y+.5*h+.3*f.fontSize))*k)
+			t := strings.Split(txtStr, " ")
+			shift := float64((wmax - strSize)) / float64(len(t)-1)
+			numt := len(t)
+			for i := 0; i < numt; i++ {
+				tx := t[i]
+				tx = "(" + f.escape(utf8toutf16(tx, false)) + ")"
+				s.printf("%s ", tx)
+				if (i + 1) < numt {
+					s.printf("%.3f(%s) ", -shift, space)
+				}
+			}
+			s.printf("] TJ ET")
+		} else {
+			var txt2 string
+			if f.isCurrentUTF8 {
+				if f.isRTL {
+					txtStr = reverseText(txtStr)
+				}
+				txt2 = f.escape(utf8toutf16(txtStr, false))
+				for _, uni := range []rune(txtStr) {
+					f.currentFont.usedRunes[int(uni)] = int(uni)
+				}
+			} else {
+
+				txt2 = strings.Replace(txtStr, "\\", "\\\\", -1)
+				txt2 = strings.Replace(txt2, "(", "\\(", -1)
+				txt2 = strings.Replace(txt2, ")", "\\)", -1)
+			}
+			bt := (f.x + dx) * k
+			td := (f.h - (f.y + dy + .5*h + .3*f.fontSize)) * k
+			s.printf("BT %.2f %.2f Td (%s)Tj ET", bt, td, txt2)
+			//BT %.2F %.2F Td (%s) Tj ET',(f.x+dx)*k,(f.h-(f.y+.5*h+.3*f.FontSize))*k,txt2);
+		}
+
+		if f.underline {
+			s.printf(" %s", f.dounderline(f.x+dx, f.y+dy+.5*h+.3*f.fontSize, txtStr))
+		}
+		if f.colorFlag {
+			s.printf(" Q")
+		}
+		if link > 0 || len(linkStr) > 0 {
+			f.newLink(f.x+dx, f.y+dy+.5*h-.5*f.fontSize, f.GetStringWidth(txtStr), f.fontSize, link, linkStr)
+		}
+	}
+	str := s.String()
+	if len(str) > 0 {
+		f.out(str)
+	}
+	f.lasth = h
+	if ln > 0 {
+		// Go to next line
+		f.y += h
+		if ln == 1 {
+			f.x = f.lMargin
+		}
+	} else {
+		f.x += w
+	}
+	return
+}
+
+// Revert string to use in RTL languages
+func reverseText(text string) string {
+	oldText := []rune(text)
+	newText := make([]rune, len(oldText))
+	length := len(oldText) - 1
+	for i, r := range oldText {
+		newText[length-i] = r
+	}
+	return string(newText)
+}
+
+// Cell is a simpler version of CellFormat with no fill, border, links or
+// special alignment.
+func (f *Fpdf) Cell(w, h float64, txtStr string) {
+	f.CellFormat(w, h, txtStr, "", 0, "L", false, 0, "")
+}
+
+// Cellf is a simpler printf-style version of CellFormat with no fill, border,
+// links or special alignment. See documentation for the fmt package for
+// details on fmtStr and args.
+func (f *Fpdf) Cellf(w, h float64, fmtStr string, args ...interface{}) {
+	f.CellFormat(w, h, sprintf(fmtStr, args...), "", 0, "L", false, 0, "")
+}
+
+// SplitLines splits text into several lines using the current font. Each line
+// has its length limited to a maximum width given by w. This function can be
+// used to determine the total height of wrapped text for vertical placement
+// purposes.
+//
+// This method is useful for codepage-based fonts only. For UTF-8 encoded text,
+// use SplitText().
+//
+// You can use MultiCell if you want to print a text on several lines in a
+// simple way.
+func (f *Fpdf) SplitLines(txt []byte, w float64) [][]byte {
+	// Function contributed by Bruno Michel
+	lines := [][]byte{}
+	cw := f.currentFont.Cw
+	wmax := int(math.Ceil((w - 2*f.cMargin) * 1000 / f.fontSize))
+	s := bytes.Replace(txt, []byte("\r"), []byte{}, -1)
+	nb := len(s)
+	for nb > 0 && s[nb-1] == '\n' {
+		nb--
+	}
+	s = s[0:nb]
+	sep := -1
+	i := 0
+	j := 0
+	l := 0
+	for i < nb {
+		c := s[i]
+		l += cw[c]
+		if c == ' ' || c == '\t' || c == '\n' {
+			sep = i
+		}
+		if c == '\n' || l > wmax {
+			if sep == -1 {
+				if i == j {
+					i++
+				}
+				sep = i
+			} else {
+				i = sep + 1
+			}
+			lines = append(lines, s[j:sep])
+			sep = -1
+			j = i
+			l = 0
+		} else {
+			i++
+		}
+	}
+	if i != j {
+		lines = append(lines, s[j:i])
+	}
+	return lines
+}
+
+// MultiCell supports printing text with line breaks. They can be automatic (as
+// soon as the text reaches the right border of the cell) or explicit (via the
+// \n character). As many cells as necessary are output, one below the other.
+//
+// Text can be aligned, centered or justified. The cell block can be framed and
+// the background painted. See CellFormat() for more details.
+//
+// The current position after calling MultiCell() is the beginning of the next
+// line, equivalent to calling CellFormat with ln equal to 1.
+//
+// w is the width of the cells. A value of zero indicates cells that reach to
+// the right margin.
+//
+// h indicates the line height of each cell in the unit of measure specified in New().
+func (f *Fpdf) MultiCell(w, h float64, txtStr, borderStr, alignStr string, fill bool) {
+	if f.err != nil {
+		return
+	}
+	// dbg("MultiCell")
+	if alignStr == "" {
+		alignStr = "J"
+	}
+	cw := f.currentFont.Cw
+	if w == 0 {
+		w = f.w - f.rMargin - f.x
+	}
+	wmax := int(math.Ceil((w - 2*f.cMargin) * 1000 / f.fontSize))
+	s := strings.Replace(txtStr, "\r", "", -1)
+	srune := []rune(s)
+
+	// remove extra line breaks
+	var nb int
+	if f.isCurrentUTF8 {
+		nb = len(srune)
+		for nb > 0 && srune[nb-1] == '\n' {
+			nb--
+		}
+		srune = srune[0:nb]
+	} else {
+		nb = len(s)
+		bytes2 := []byte(s)
+		for nb > 0 && bytes2[nb-1] == '\n' {
+			nb--
+		}
+		s = s[0:nb]
+	}
+	// dbg("[%s]\n", s)
+	var b, b2 string
+	b = "0"
+	if len(borderStr) > 0 {
+		if borderStr == "1" {
+			borderStr = "LTRB"
+			b = "LRT"
+			b2 = "LR"
+		} else {
+			b2 = ""
+			if strings.Contains(borderStr, "L") {
+				b2 += "L"
+			}
+			if strings.Contains(borderStr, "R") {
+				b2 += "R"
+			}
+			if strings.Contains(borderStr, "T") {
+				b = b2 + "T"
+			} else {
+				b = b2
+			}
+		}
+	}
+	sep := -1
+	i := 0
+	j := 0
+	l := 0
+	ls := 0
+	ns := 0
+	nl := 1
+	for i < nb {
+		// Get next character
+		var c rune
+		if f.isCurrentUTF8 {
+			c = srune[i]
+		} else {
+			c = rune(s[i])
+		}
+		if c == '\n' {
+			// Explicit line break
+			if f.ws > 0 {
+				f.ws = 0
+				f.out("0 Tw")
+			}
+
+			if f.isCurrentUTF8 {
+				newAlignStr := alignStr
+				if newAlignStr == "J" {
+					if f.isRTL {
+						newAlignStr = "R"
+					} else {
+						newAlignStr = "L"
+					}
+				}
+				f.CellFormat(w, h, string(srune[j:i]), b, 2, newAlignStr, fill, 0, "")
+			} else {
+				f.CellFormat(w, h, s[j:i], b, 2, alignStr, fill, 0, "")
+			}
+			i++
+			sep = -1
+			j = i
+			l = 0
+			ns = 0
+			nl++
+			if len(borderStr) > 0 && nl == 2 {
+				b = b2
+			}
+			continue
+		}
+		if c == ' ' || isChinese(c) {
+			sep = i
+			ls = l
+			ns++
+		}
+		if cw[int(c)] == 0 { //Marker width 0 used for missing symbols
+			l += f.currentFont.Desc.MissingWidth
+		} else if cw[int(c)] != 65535 { //Marker width 65535 used for zero width symbols
+			l += cw[int(c)]
+		}
+		if l > wmax {
+			// Automatic line break
+			if sep == -1 {
+				if i == j {
+					i++
+				}
+				if f.ws > 0 {
+					f.ws = 0
+					f.out("0 Tw")
+				}
+				if f.isCurrentUTF8 {
+					f.CellFormat(w, h, string(srune[j:i]), b, 2, alignStr, fill, 0, "")
+				} else {
+					f.CellFormat(w, h, s[j:i], b, 2, alignStr, fill, 0, "")
+				}
+			} else {
+				if alignStr == "J" {
+					if ns > 1 {
+						f.ws = float64((wmax-ls)/1000) * f.fontSize / float64(ns-1)
+					} else {
+						f.ws = 0
+					}
+					f.outf("%.3f Tw", f.ws*f.k)
+				}
+				if f.isCurrentUTF8 {
+					f.CellFormat(w, h, string(srune[j:sep]), b, 2, alignStr, fill, 0, "")
+				} else {
+					f.CellFormat(w, h, s[j:sep], b, 2, alignStr, fill, 0, "")
+				}
+				i = sep + 1
+			}
+			sep = -1
+			j = i
+			l = 0
+			ns = 0
+			nl++
+			if len(borderStr) > 0 && nl == 2 {
+				b = b2
+			}
+		} else {
+			i++
+		}
+	}
+	// Last chunk
+	if f.ws > 0 {
+		f.ws = 0
+		f.out("0 Tw")
+	}
+	if len(borderStr) > 0 && strings.Contains(borderStr, "B") {
+		b += "B"
+	}
+	if f.isCurrentUTF8 {
+		if alignStr == "J" {
+			if f.isRTL {
+				alignStr = "R"
+			} else {
+				alignStr = ""
+			}
+		}
+		f.CellFormat(w, h, string(srune[j:i]), b, 2, alignStr, fill, 0, "")
+	} else {
+		f.CellFormat(w, h, s[j:i], b, 2, alignStr, fill, 0, "")
+	}
+	f.x = f.lMargin
+}
+
+// write outputs text in flowing mode
+func (f *Fpdf) write(h float64, txtStr string, link int, linkStr string) {
+	// dbg("Write")
+	cw := f.currentFont.Cw
+	w := f.w - f.rMargin - f.x
+	wmax := (w - 2*f.cMargin) * 1000 / f.fontSize
+	s := strings.Replace(txtStr, "\r", "", -1)
+	var nb int
+	if f.isCurrentUTF8 {
+		nb = len([]rune(s))
+		if nb == 1 && s == " " {
+			f.x += f.GetStringWidth(s)
+			return
+		}
+	} else {
+		nb = len(s)
+	}
+	sep := -1
+	i := 0
+	j := 0
+	l := 0.0
+	nl := 1
+	for i < nb {
+		// Get next character
+		var c rune
+		if f.isCurrentUTF8 {
+			c = []rune(s)[i]
+		} else {
+			c = rune(byte(s[i]))
+		}
+		if c == '\n' {
+			// Explicit line break
+			if f.isCurrentUTF8 {
+				f.CellFormat(w, h, string([]rune(s)[j:i]), "", 2, "", false, link, linkStr)
+			} else {
+				f.CellFormat(w, h, s[j:i], "", 2, "", false, link, linkStr)
+			}
+			i++
+			sep = -1
+			j = i
+			l = 0.0
+			if nl == 1 {
+				f.x = f.lMargin
+				w = f.w - f.rMargin - f.x
+				wmax = (w - 2*f.cMargin) * 1000 / f.fontSize
+			}
+			nl++
+			continue
+		}
+		if c == ' ' {
+			sep = i
+		}
+		l += float64(cw[int(c)])
+		if l > wmax {
+			// Automatic line break
+			if sep == -1 {
+				if f.x > f.lMargin {
+					// Move to next line
+					f.x = f.lMargin
+					f.y += h
+					w = f.w - f.rMargin - f.x
+					wmax = (w - 2*f.cMargin) * 1000 / f.fontSize
+					i++
+					nl++
+					continue
+				}
+				if i == j {
+					i++
+				}
+				if f.isCurrentUTF8 {
+					f.CellFormat(w, h, string([]rune(s)[j:i]), "", 2, "", false, link, linkStr)
+				} else {
+					f.CellFormat(w, h, s[j:i], "", 2, "", false, link, linkStr)
+				}
+			} else {
+				if f.isCurrentUTF8 {
+					f.CellFormat(w, h, string([]rune(s)[j:sep]), "", 2, "", false, link, linkStr)
+				} else {
+					f.CellFormat(w, h, s[j:sep], "", 2, "", false, link, linkStr)
+				}
+				i = sep + 1
+			}
+			sep = -1
+			j = i
+			l = 0.0
+			if nl == 1 {
+				f.x = f.lMargin
+				w = f.w - f.rMargin - f.x
+				wmax = (w - 2*f.cMargin) * 1000 / f.fontSize
+			}
+			nl++
+		} else {
+			i++
+		}
+	}
+	// Last chunk
+	if i != j {
+		if f.isCurrentUTF8 {
+			f.CellFormat(l/1000*f.fontSize, h, string([]rune(s)[j:]), "", 0, "", false, link, linkStr)
+		} else {
+			f.CellFormat(l/1000*f.fontSize, h, s[j:], "", 0, "", false, link, linkStr)
+		}
+	}
+}
+
+// Write prints text from the current position. When the right margin is
+// reached (or the \n character is met) a line break occurs and text continues
+// from the left margin. Upon method exit, the current position is left just at
+// the end of the text.
+//
+// It is possible to put a link on the text.
+//
+// h indicates the line height in the unit of measure specified in New().
+func (f *Fpdf) Write(h float64, txtStr string) {
+	f.write(h, txtStr, 0, "")
+}
+
+// Writef is like Write but uses printf-style formatting. See the documentation
+// for package fmt for more details on fmtStr and args.
+func (f *Fpdf) Writef(h float64, fmtStr string, args ...interface{}) {
+	f.write(h, sprintf(fmtStr, args...), 0, "")
+}
+
+// WriteLinkString writes text that when clicked launches an external URL. See
+// Write() for argument details.
+func (f *Fpdf) WriteLinkString(h float64, displayStr, targetStr string) {
+	f.write(h, displayStr, 0, targetStr)
+}
+
+// WriteLinkID writes text that when clicked jumps to another location in the
+// PDF. linkID is an identifier returned by AddLink(). See Write() for argument
+// details.
+func (f *Fpdf) WriteLinkID(h float64, displayStr string, linkID int) {
+	f.write(h, displayStr, linkID, "")
+}
+
+// WriteAligned is an implementation of Write that makes it possible to align
+// text.
+//
+// width indicates the width of the box the text will be drawn in. This is in
+// the unit of measure specified in New(). If it is set to 0, the bounding box
+//of the page will be taken (pageWidth - leftMargin - rightMargin).
+//
+// lineHeight indicates the line height in the unit of measure specified in
+// New().
+//
+// alignStr sees to horizontal alignment of the given textStr. The options are
+// "L", "C" and "R" (Left, Center, Right). The default is "L".
+func (f *Fpdf) WriteAligned(width, lineHeight float64, textStr, alignStr string) {
+	lMargin, _, rMargin, _ := f.GetMargins()
+
+	pageWidth, _ := f.GetPageSize()
+	if width == 0 {
+		width = pageWidth - (lMargin + rMargin)
+	}
+
+	var lines []string
+
+	if f.isCurrentUTF8 {
+		lines = f.SplitText(textStr, width)
+	} else {
+		for _, line := range f.SplitLines([]byte(textStr), width) {
+			lines = append(lines, string(line))
+		}
+	}
+
+	for _, lineBt := range lines {
+		lineStr := string(lineBt)
+		lineWidth := f.GetStringWidth(lineStr)
+
+		switch alignStr {
+		case "C":
+			f.SetLeftMargin(lMargin + ((width - lineWidth) / 2))
+			f.Write(lineHeight, lineStr)
+			f.SetLeftMargin(lMargin)
+		case "R":
+			f.SetLeftMargin(lMargin + (width - lineWidth) - 2.01*f.cMargin)
+			f.Write(lineHeight, lineStr)
+			f.SetLeftMargin(lMargin)
+		default:
+			f.SetRightMargin(pageWidth - lMargin - width)
+			f.Write(lineHeight, lineStr)
+			f.SetRightMargin(rMargin)
+		}
+	}
+}
+
+// Ln performs a line break. The current abscissa goes back to the left margin
+// and the ordinate increases by the amount passed in parameter. A negative
+// value of h indicates the height of the last printed cell.
+//
+// This method is demonstrated in the example for MultiCell.
+func (f *Fpdf) Ln(h float64) {
+	f.x = f.lMargin
+	if h < 0 {
+		f.y += f.lasth
+	} else {
+		f.y += h
+	}
+}
+
+// ImageTypeFromMime returns the image type used in various image-related
+// functions (for example, Image()) that is associated with the specified MIME
+// type. For example, "jpg" is returned if mimeStr is "image/jpeg". An error is
+// set if the specified MIME type is not supported.
+func (f *Fpdf) ImageTypeFromMime(mimeStr string) (tp string) {
+	switch mimeStr {
+	case "image/png":
+		tp = "png"
+	case "image/jpg":
+		tp = "jpg"
+	case "image/jpeg":
+		tp = "jpg"
+	case "image/gif":
+		tp = "gif"
+	default:
+		f.SetErrorf("unsupported image type: %s", mimeStr)
+	}
+	return
+}
+
+func (f *Fpdf) imageOut(info *ImageInfoType, x, y, w, h float64, allowNegativeX, flow bool, link int, linkStr string) {
+	// Automatic width and height calculation if needed
+	if w == 0 && h == 0 {
+		// Put image at 96 dpi
+		w = -96
+		h = -96
+	}
+	if w == -1 {
+		// Set image width to whatever value for dpi we read
+		// from the image or that was set manually
+		w = -info.dpi
+	}
+	if h == -1 {
+		// Set image height to whatever value for dpi we read
+		// from the image or that was set manually
+		h = -info.dpi
+	}
+	if w < 0 {
+		w = -info.w * 72.0 / w / f.k
+	}
+	if h < 0 {
+		h = -info.h * 72.0 / h / f.k
+	}
+	if w == 0 {
+		w = h * info.w / info.h
+	}
+	if h == 0 {
+		h = w * info.h / info.w
+	}
+	// Flowing mode
+	if flow {
+		if f.y+h > f.pageBreakTrigger && !f.inHeader && !f.inFooter && f.acceptPageBreak() {
+			// Automatic page break
+			x2 := f.x
+			f.AddPageFormat(f.curOrientation, f.curPageSize)
+			if f.err != nil {
+				return
+			}
+			f.x = x2
+		}
+		y = f.y
+		f.y += h
+	}
+	if !allowNegativeX {
+		if x < 0 {
+			x = f.x
+		}
+	}
+	// dbg("h %.2f", h)
+	// q 85.04 0 0 NaN 28.35 NaN cm /I2 Do Q
+	f.outf("q %.5f 0 0 %.5f %.5f %.5f cm /I%s Do Q", w*f.k, h*f.k, x*f.k, (f.h-(y+h))*f.k, info.i)
+	if link > 0 || len(linkStr) > 0 {
+		f.newLink(x, y, w, h, link, linkStr)
+	}
+}
+
+// Image puts a JPEG, PNG or GIF image in the current page.
+//
+// Deprecated in favor of ImageOptions -- see that function for
+// details on the behavior of arguments
+func (f *Fpdf) Image(imageNameStr string, x, y, w, h float64, flow bool, tp string, link int, linkStr string) {
+	options := ImageOptions{
+		ReadDpi:   false,
+		ImageType: tp,
+	}
+	f.ImageOptions(imageNameStr, x, y, w, h, flow, options, link, linkStr)
+}
+
+// ImageOptions puts a JPEG, PNG or GIF image in the current page. The size it
+// will take on the page can be specified in different ways. If both w and h
+// are 0, the image is rendered at 96 dpi. If either w or h is zero, it will be
+// calculated from the other dimension so that the aspect ratio is maintained.
+// If w and/or h are -1, the dpi for that dimension will be read from the
+// ImageInfoType object. PNG files can contain dpi information, and if present,
+// this information will be populated in the ImageInfoType object and used in
+// Width, Height, and Extent calculations. Otherwise, the SetDpi function can
+// be used to change the dpi from the default of 72.
+//
+// If w and h are any other negative value, their absolute values
+// indicate their dpi extents.
+//
+// Supported JPEG formats are 24 bit, 32 bit and gray scale. Supported PNG
+// formats are 24 bit, indexed color, and 8 bit indexed gray scale. If a GIF
+// image is animated, only the first frame is rendered. Transparency is
+// supported. It is possible to put a link on the image.
+//
+// imageNameStr may be the name of an image as registered with a call to either
+// RegisterImageReader() or RegisterImage(). In the first case, the image is
+// loaded using an io.Reader. This is generally useful when the image is
+// obtained from some other means than as a disk-based file. In the second
+// case, the image is loaded as a file. Alternatively, imageNameStr may
+// directly specify a sufficiently qualified filename.
+//
+// However the image is loaded, if it is used more than once only one copy is
+// embedded in the file.
+//
+// If x is negative, the current abscissa is used.
+//
+// If flow is true, the current y value is advanced after placing the image and
+// a page break may be made if necessary.
+//
+// If link refers to an internal page anchor (that is, it is non-zero; see
+// AddLink()), the image will be a clickable internal link. Otherwise, if
+// linkStr specifies a URL, the image will be a clickable external link.
+func (f *Fpdf) ImageOptions(imageNameStr string, x, y, w, h float64, flow bool, options ImageOptions, link int, linkStr string) {
+	if f.err != nil {
+		return
+	}
+	info := f.RegisterImageOptions(imageNameStr, options)
+	if f.err != nil {
+		return
+	}
+	f.imageOut(info, x, y, w, h, options.AllowNegativePosition, flow, link, linkStr)
+	return
+}
+
+// RegisterImageReader registers an image, reading it from Reader r, adding it
+// to the PDF file but not adding it to the page.
+//
+// This function is now deprecated in favor of RegisterImageOptionsReader
+func (f *Fpdf) RegisterImageReader(imgName, tp string, r io.Reader) (info *ImageInfoType) {
+	options := ImageOptions{
+		ReadDpi:   false,
+		ImageType: tp,
+	}
+	return f.RegisterImageOptionsReader(imgName, options, r)
+}
+
+// ImageOptions provides a place to hang any options we want to use while
+// parsing an image.
+//
+// ImageType's possible values are (case insensitive):
+// "JPG", "JPEG", "PNG" and "GIF". If empty, the type is inferred from
+// the file extension.
+//
+// ReadDpi defines whether to attempt to automatically read the image
+// dpi information from the image file. Normally, this should be set
+// to true (understanding that not all images will have this info
+// available). However, for backwards compatibility with previous
+// versions of the API, it defaults to false.
+//
+// AllowNegativePosition can be set to true in order to prevent the default
+// coercion of negative x values to the current x position.
+type ImageOptions struct {
+	ImageType             string
+	ReadDpi               bool
+	AllowNegativePosition bool
+}
+
+// RegisterImageOptionsReader registers an image, reading it from Reader r, adding it
+// to the PDF file but not adding it to the page. Use Image() with the same
+// name to add the image to the page. Note that tp should be specified in this
+// case.
+//
+// See Image() for restrictions on the image and the options parameters.
+func (f *Fpdf) RegisterImageOptionsReader(imgName string, options ImageOptions, r io.Reader) (info *ImageInfoType) {
+	// Thanks, Ivan Daniluk, for generalizing this code to use the Reader interface.
+	if f.err != nil {
+		return
+	}
+	info, ok := f.images[imgName]
+	if ok {
+		return
+	}
+
+	// First use of this image, get info
+	if options.ImageType == "" {
+		f.err = fmt.Errorf("image type should be specified if reading from custom reader")
+		return
+	}
+	options.ImageType = strings.ToLower(options.ImageType)
+	if options.ImageType == "jpeg" {
+		options.ImageType = "jpg"
+	}
+	switch options.ImageType {
+	case "jpg":
+		info = f.parsejpg(r)
+	case "png":
+		info = f.parsepng(r, options.ReadDpi)
+	case "gif":
+		info = f.parsegif(r)
+	default:
+		f.err = fmt.Errorf("unsupported image type: %s", options.ImageType)
+	}
+	if f.err != nil {
+		return
+	}
+
+	if info.i, f.err = generateImageID(info); f.err != nil {
+		return
+	}
+	f.images[imgName] = info
+
+	return
+}
+
+// RegisterImage registers an image, adding it to the PDF file but not adding
+// it to the page. Use Image() with the same filename to add the image to the
+// page. Note that Image() calls this function, so this function is only
+// necessary if you need information about the image before placing it.
+//
+// This function is now deprecated in favor of RegisterImageOptions.
+// See Image() for restrictions on the image and the "tp" parameters.
+func (f *Fpdf) RegisterImage(fileStr, tp string) (info *ImageInfoType) {
+	options := ImageOptions{
+		ReadDpi:   false,
+		ImageType: tp,
+	}
+	return f.RegisterImageOptions(fileStr, options)
+}
+
+// RegisterImageOptions registers an image, adding it to the PDF file but not
+// adding it to the page. Use Image() with the same filename to add the image
+// to the page. Note that Image() calls this function, so this function is only
+// necessary if you need information about the image before placing it. See
+// Image() for restrictions on the image and the "tp" parameters.
+func (f *Fpdf) RegisterImageOptions(fileStr string, options ImageOptions) (info *ImageInfoType) {
+	info, ok := f.images[fileStr]
+	if ok {
+		return
+	}
+
+	file, err := os.Open(fileStr)
+	if err != nil {
+		f.err = err
+		return
+	}
+	defer file.Close()
+
+	// First use of this image, get info
+	if options.ImageType == "" {
+		pos := strings.LastIndex(fileStr, ".")
+		if pos < 0 {
+			f.err = fmt.Errorf("image file has no extension and no type was specified: %s", fileStr)
+			return
+		}
+		options.ImageType = fileStr[pos+1:]
+	}
+
+	return f.RegisterImageOptionsReader(fileStr, options, file)
+}
+
+// GetImageInfo returns information about the registered image specified by
+// imageStr. If the image has not been registered, nil is returned. The
+// internal error is not modified by this method.
+func (f *Fpdf) GetImageInfo(imageStr string) (info *ImageInfoType) {
+	return f.images[imageStr]
+}
+
+// ImportObjects imports objects from gofpdi into current document
+func (f *Fpdf) ImportObjects(objs map[string][]byte) {
+	for k, v := range objs {
+		f.importedObjs[k] = v
+	}
+}
+
+// ImportObjPos imports object hash positions from gofpdi
+func (f *Fpdf) ImportObjPos(objPos map[string]map[int]string) {
+	for k, v := range objPos {
+		f.importedObjPos[k] = v
+	}
+}
+
+// putImportedTemplates writes the imported template objects to the PDF
+func (f *Fpdf) putImportedTemplates() {
+	nOffset := f.n + 1
+
+	// keep track of list of sha1 hashes (to be replaced with integers)
+	objsIDHash := make([]string, len(f.importedObjs))
+
+	// actual object data with new id
+	objsIDData := make([][]byte, len(f.importedObjs))
+
+	// Populate hash slice and data slice
+	i := 0
+	for k, v := range f.importedObjs {
+		objsIDHash[i] = k
+		objsIDData[i] = v
+
+		i++
+	}
+
+	// Populate a lookup table to get an object id from a hash
+	hashToObjID := make(map[string]int, len(f.importedObjs))
+	for i = 0; i < len(objsIDHash); i++ {
+		hashToObjID[objsIDHash[i]] = i + nOffset
+	}
+
+	// Now, replace hashes inside data with %040d object id
+	for i = 0; i < len(objsIDData); i++ {
+		// get hash
+		hash := objsIDHash[i]
+
+		for pos, h := range f.importedObjPos[hash] {
+			// Convert object id into a 40 character string padded with spaces
+			objIDPadded := fmt.Sprintf("%40s", fmt.Sprintf("%d", hashToObjID[h]))
+
+			// Convert objIDPadded into []byte
+			objIDBytes := []byte(objIDPadded)
+
+			// Replace sha1 hash with object id padded
+			for j := pos; j < pos+40; j++ {
+				objsIDData[i][j] = objIDBytes[j-pos]
+			}
+		}
+
+		// Save objsIDHash so that procset dictionary has the correct object ids
+		f.importedTplIDs[hash] = i + nOffset
+	}
+
+	// Now, put objects
+	for i = 0; i < len(objsIDData); i++ {
+		f.newobj()
+		f.out(string(objsIDData[i]))
+	}
+}
+
+// UseImportedTemplate uses imported template from gofpdi. It draws imported
+// PDF page onto page.
+func (f *Fpdf) UseImportedTemplate(tplName string, scaleX float64, scaleY float64, tX float64, tY float64) {
+	f.outf("q 0 J 1 w 0 j 0 G 0 g q %.4F 0 0 %.4F %.4F %.4F cm %s Do Q Q\n", scaleX*f.k, scaleY*f.k, tX*f.k, (tY+f.h)*f.k, tplName)
+}
+
+// ImportTemplates imports gofpdi template names into importedTplObjs for
+// inclusion in the procset dictionary
+func (f *Fpdf) ImportTemplates(tpls map[string]string) {
+	for tplName, tplID := range tpls {
+		f.importedTplObjs[tplName] = tplID
+	}
+}
+
+// GetConversionRatio returns the conversion ratio based on the unit given when
+// creating the PDF.
+func (f *Fpdf) GetConversionRatio() float64 {
+	return f.k
+}
+
+// GetXY returns the abscissa and ordinate of the current position.
+//
+// Note: the value returned for the abscissa will be affected by the current
+// cell margin. To account for this, you may need to either add the value
+// returned by GetCellMargin() to it or call SetCellMargin(0) to remove the
+// cell margin.
+func (f *Fpdf) GetXY() (float64, float64) {
+	return f.x, f.y
+}
+
+// GetX returns the abscissa of the current position.
+//
+// Note: the value returned will be affected by the current cell margin. To
+// account for this, you may need to either add the value returned by
+// GetCellMargin() to it or call SetCellMargin(0) to remove the cell margin.
+func (f *Fpdf) GetX() float64 {
+	return f.x
+}
+
+// SetX defines the abscissa of the current position. If the passed value is
+// negative, it is relative to the right of the page.
+func (f *Fpdf) SetX(x float64) {
+	if x >= 0 {
+		f.x = x
+	} else {
+		f.x = f.w + x
+	}
+}
+
+// GetY returns the ordinate of the current position.
+func (f *Fpdf) GetY() float64 {
+	return f.y
+}
+
+// SetY moves the current abscissa back to the left margin and sets the
+// ordinate. If the passed value is negative, it is relative to the bottom of
+// the page.
+func (f *Fpdf) SetY(y float64) {
+	// dbg("SetY x %.2f, lMargin %.2f", f.x, f.lMargin)
+	f.x = f.lMargin
+	if y >= 0 {
+		f.y = y
+	} else {
+		f.y = f.h + y
+	}
+}
+
+// SetHomeXY is a convenience method that sets the current position to the left
+// and top margins.
+func (f *Fpdf) SetHomeXY() {
+	f.SetY(f.tMargin)
+	f.SetX(f.lMargin)
+}
+
+// SetXY defines the abscissa and ordinate of the current position. If the
+// passed values are negative, they are relative respectively to the right and
+// bottom of the page.
+func (f *Fpdf) SetXY(x, y float64) {
+	f.SetY(y)
+	f.SetX(x)
+}
+
+// SetProtection applies certain constraints on the finished PDF document.
+//
+// actionFlag is a bitflag that controls various document operations.
+// CnProtectPrint allows the document to be printed. CnProtectModify allows a
+// document to be modified by a PDF editor. CnProtectCopy allows text and
+// images to be copied into the system clipboard. CnProtectAnnotForms allows
+// annotations and forms to be added by a PDF editor. These values can be
+// combined by or-ing them together, for example,
+// CnProtectCopy|CnProtectModify. This flag is advisory; not all PDF readers
+// implement the constraints that this argument attempts to control.
+//
+// userPassStr specifies the password that will need to be provided to view the
+// contents of the PDF. The permissions specified by actionFlag will apply.
+//
+// ownerPassStr specifies the password that will need to be provided to gain
+// full access to the document regardless of the actionFlag value. An empty
+// string for this argument will be replaced with a random value, effectively
+// prohibiting full access to the document.
+func (f *Fpdf) SetProtection(actionFlag byte, userPassStr, ownerPassStr string) {
+	if f.err != nil {
+		return
+	}
+	f.protect.setProtection(actionFlag, userPassStr, ownerPassStr)
+}
+
+// OutputAndClose sends the PDF document to the writer specified by w. This
+// method will close both f and w, even if an error is detected and no document
+// is produced.
+func (f *Fpdf) OutputAndClose(w io.WriteCloser) error {
+	f.Output(w)
+	w.Close()
+	return f.err
+}
+
+// OutputFileAndClose creates or truncates the file specified by fileStr and
+// writes the PDF document to it. This method will close f and the newly
+// written file, even if an error is detected and no document is produced.
+//
+// Most examples demonstrate the use of this method.
+func (f *Fpdf) OutputFileAndClose(fileStr string) error {
+	if f.err == nil {
+		pdfFile, err := os.Create(fileStr)
+		if err == nil {
+			f.Output(pdfFile)
+			pdfFile.Close()
+		} else {
+			f.err = err
+		}
+	}
+	return f.err
+}
+
+// Output sends the PDF document to the writer specified by w. No output will
+// take place if an error has occurred in the document generation process. w
+// remains open after this function returns. After returning, f is in a closed
+// state and its methods should not be called.
+func (f *Fpdf) Output(w io.Writer) error {
+	if f.err != nil {
+		return f.err
+	}
+	// dbg("Output")
+	if f.state < 3 {
+		f.Close()
+	}
+	_, err := f.buffer.WriteTo(w)
+	if err != nil {
+		f.err = err
+	}
+	return f.err
+}
+
+func (f *Fpdf) getpagesizestr(sizeStr string) (size SizeType) {
+	if f.err != nil {
+		return
+	}
+	sizeStr = strings.ToLower(sizeStr)
+	// dbg("Size [%s]", sizeStr)
+	var ok bool
+	size, ok = f.stdPageSizes[sizeStr]
+	if ok {
+		// dbg("found %s", sizeStr)
+		size.Wd /= f.k
+		size.Ht /= f.k
+
+	} else {
+		f.err = fmt.Errorf("unknown page size %s", sizeStr)
+	}
+	return
+}
+
+// GetPageSizeStr returns the SizeType for the given sizeStr (that is A4, A3, etc..)
+func (f *Fpdf) GetPageSizeStr(sizeStr string) (size SizeType) {
+	return f.getpagesizestr(sizeStr)
+}
+
+func (f *Fpdf) _getpagesize(size SizeType) SizeType {
+	if size.Wd > size.Ht {
+		size.Wd, size.Ht = size.Ht, size.Wd
+	}
+	return size
+}
+
+func (f *Fpdf) beginpage(orientationStr string, size SizeType) {
+	if f.err != nil {
+		return
+	}
+	f.page++
+	// add the default page boxes, if any exist, to the page
+	f.pageBoxes[f.page] = make(map[string]PageBox)
+	for box, pb := range f.defPageBoxes {
+		f.pageBoxes[f.page][box] = pb
+	}
+	f.pages = append(f.pages, bytes.NewBufferString(""))
+	f.pageLinks = append(f.pageLinks, make([]linkType, 0, 0))
+	f.state = 2
+	f.x = f.lMargin
+	f.y = f.tMargin
+	f.fontFamily = ""
+	// Check page size and orientation
+	if orientationStr == "" {
+		orientationStr = f.defOrientation
+	} else {
+		orientationStr = strings.ToUpper(orientationStr[0:1])
+	}
+	if orientationStr != f.curOrientation || size.Wd != f.curPageSize.Wd || size.Ht != f.curPageSize.Ht {
+		// New size or orientation
+		if orientationStr == "P" {
+			f.w = size.Wd
+			f.h = size.Ht
+		} else {
+			f.w = size.Ht
+			f.h = size.Wd
+		}
+		f.wPt = f.w * f.k
+		f.hPt = f.h * f.k
+		f.pageBreakTrigger = f.h - f.bMargin
+		f.curOrientation = orientationStr
+		f.curPageSize = size
+	}
+	if orientationStr != f.defOrientation || size.Wd != f.defPageSize.Wd || size.Ht != f.defPageSize.Ht {
+		f.pageSizes[f.page] = SizeType{f.wPt, f.hPt}
+	}
+	return
+}
+
+func (f *Fpdf) endpage() {
+	f.EndLayer()
+	f.state = 1
+}
+
+// Load a font definition file from the given Reader
+func (f *Fpdf) loadfont(r io.Reader) (def fontDefType) {
+	if f.err != nil {
+		return
+	}
+	// dbg("Loading font [%s]", fontStr)
+	var buf bytes.Buffer
+	_, err := buf.ReadFrom(r)
+	if err != nil {
+		f.err = err
+		return
+	}
+	err = json.Unmarshal(buf.Bytes(), &def)
+	if err != nil {
+		f.err = err
+		return
+	}
+
+	if def.i, err = generateFontID(def); err != nil {
+		f.err = err
+	}
+	// dump(def)
+	return
+}
+
+// Escape special characters in strings
+func (f *Fpdf) escape(s string) string {
+	s = strings.Replace(s, "\\", "\\\\", -1)
+	s = strings.Replace(s, "(", "\\(", -1)
+	s = strings.Replace(s, ")", "\\)", -1)
+	s = strings.Replace(s, "\r", "\\r", -1)
+	return s
+}
+
+// textstring formats a text string
+func (f *Fpdf) textstring(s string) string {
+	if f.protect.encrypted {
+		b := []byte(s)
+		f.protect.rc4(uint32(f.n), &b)
+		s = string(b)
+	}
+	return "(" + f.escape(s) + ")"
+}
+
+func blankCount(str string) (count int) {
+	l := len(str)
+	for j := 0; j < l; j++ {
+		if byte(' ') == str[j] {
+			count++
+		}
+	}
+	return
+}
+
+// SetUnderlineThickness accepts a multiplier for adjusting the text underline
+// thickness, defaulting to 1. See SetUnderlineThickness example.
+func (f *Fpdf) SetUnderlineThickness(thickness float64) {
+	f.userUnderlineThickness = thickness
+}
+
+// Underline text
+func (f *Fpdf) dounderline(x, y float64, txt string) string {
+	up := float64(f.currentFont.Up)
+	ut := float64(f.currentFont.Ut) * f.userUnderlineThickness
+	w := f.GetStringWidth(txt) + f.ws*float64(blankCount(txt))
+	return sprintf("%.2f %.2f %.2f %.2f re f", x*f.k,
+		(f.h-(y-up/1000*f.fontSize))*f.k, w*f.k, -ut/1000*f.fontSizePt)
+}
+
+func bufEqual(buf []byte, str string) bool {
+	return string(buf[0:len(str)]) == str
+}
+
+func be16(buf []byte) int {
+	return 256*int(buf[0]) + int(buf[1])
+}
+
+func (f *Fpdf) newImageInfo() *ImageInfoType {
+	// default dpi to 72 unless told otherwise
+	return &ImageInfoType{scale: f.k, dpi: 72}
+}
+
+// parsejpg extracts info from io.Reader with JPEG data
+// Thank you, Bruno Michel, for providing this code.
+func (f *Fpdf) parsejpg(r io.Reader) (info *ImageInfoType) {
+	info = f.newImageInfo()
+	var (
+		data bytes.Buffer
+		err  error
+	)
+	_, err = data.ReadFrom(r)
+	if err != nil {
+		f.err = err
+		return
+	}
+	info.data = data.Bytes()
+
+	config, err := jpeg.DecodeConfig(bytes.NewReader(info.data))
+	if err != nil {
+		f.err = err
+		return
+	}
+	info.w = float64(config.Width)
+	info.h = float64(config.Height)
+	info.f = "DCTDecode"
+	info.bpc = 8
+	switch config.ColorModel {
+	case color.GrayModel:
+		info.cs = "DeviceGray"
+	case color.YCbCrModel:
+		info.cs = "DeviceRGB"
+	case color.CMYKModel:
+		info.cs = "DeviceCMYK"
+	default:
+		f.err = fmt.Errorf("image JPEG buffer has unsupported color space (%v)", config.ColorModel)
+		return
+	}
+	return
+}
+
+// parsepng extracts info from a PNG data
+func (f *Fpdf) parsepng(r io.Reader, readdpi bool) (info *ImageInfoType) {
+	buf, err := bufferFromReader(r)
+	if err != nil {
+		f.err = err
+		return
+	}
+	return f.parsepngstream(buf, readdpi)
+}
+
+func (f *Fpdf) readBeInt32(r io.Reader) (val int32) {
+	err := binary.Read(r, binary.BigEndian, &val)
+	if err != nil && err != io.EOF {
+		f.err = err
+	}
+	return
+}
+
+func (f *Fpdf) readByte(r io.Reader) (val byte) {
+	err := binary.Read(r, binary.BigEndian, &val)
+	if err != nil {
+		f.err = err
+	}
+	return
+}
+
+// parsegif extracts info from a GIF data (via PNG conversion)
+func (f *Fpdf) parsegif(r io.Reader) (info *ImageInfoType) {
+	data, err := bufferFromReader(r)
+	if err != nil {
+		f.err = err
+		return
+	}
+	var img image.Image
+	img, err = gif.Decode(data)
+	if err != nil {
+		f.err = err
+		return
+	}
+	pngBuf := new(bytes.Buffer)
+	err = png.Encode(pngBuf, img)
+	if err != nil {
+		f.err = err
+		return
+	}
+	return f.parsepngstream(pngBuf, false)
+}
+
+// newobj begins a new object
+func (f *Fpdf) newobj() {
+	// dbg("newobj")
+	f.n++
+	for j := len(f.offsets); j <= f.n; j++ {
+		f.offsets = append(f.offsets, 0)
+	}
+	f.offsets[f.n] = f.buffer.Len()
+	f.outf("%d 0 obj", f.n)
+}
+
+func (f *Fpdf) putstream(b []byte) {
+	// dbg("putstream")
+	if f.protect.encrypted {
+		f.protect.rc4(uint32(f.n), &b)
+	}
+	f.out("stream")
+	f.out(string(b))
+	f.out("endstream")
+}
+
+// out; Add a line to the document
+func (f *Fpdf) out(s string) {
+	if f.state == 2 {
+		f.pages[f.page].WriteString(s)
+		f.pages[f.page].WriteString("\n")
+	} else {
+		f.buffer.WriteString(s)
+		f.buffer.WriteString("\n")
+	}
+}
+
+// outbuf adds a buffered line to the document
+func (f *Fpdf) outbuf(r io.Reader) {
+	if f.state == 2 {
+		f.pages[f.page].ReadFrom(r)
+		f.pages[f.page].WriteString("\n")
+	} else {
+		f.buffer.ReadFrom(r)
+		f.buffer.WriteString("\n")
+	}
+}
+
+// RawWriteStr writes a string directly to the PDF generation buffer. This is a
+// low-level function that is not required for normal PDF construction. An
+// understanding of the PDF specification is needed to use this method
+// correctly.
+func (f *Fpdf) RawWriteStr(str string) {
+	f.out(str)
+}
+
+// RawWriteBuf writes the contents of the specified buffer directly to the PDF
+// generation buffer. This is a low-level function that is not required for
+// normal PDF construction. An understanding of the PDF specification is needed
+// to use this method correctly.
+func (f *Fpdf) RawWriteBuf(r io.Reader) {
+	f.outbuf(r)
+}
+
+// outf adds a formatted line to the document
+func (f *Fpdf) outf(fmtStr string, args ...interface{}) {
+	f.out(sprintf(fmtStr, args...))
+}
+
+// SetDefaultCatalogSort sets the default value of the catalog sort flag that
+// will be used when initializing a new Fpdf instance. See SetCatalogSort() for
+// more details.
+func SetDefaultCatalogSort(flag bool) {
+	gl.catalogSort = flag
+}
+
+// SetCatalogSort sets a flag that will be used, if true, to consistently order
+// the document's internal resource catalogs. This method is typically only
+// used for test purposes to facilitate PDF comparison.
+func (f *Fpdf) SetCatalogSort(flag bool) {
+	f.catalogSort = flag
+}
+
+// SetDefaultCreationDate sets the default value of the document creation date
+// that will be used when initializing a new Fpdf instance. See
+// SetCreationDate() for more details.
+func SetDefaultCreationDate(tm time.Time) {
+	gl.creationDate = tm
+}
+
+// SetCreationDate fixes the document's internal CreationDate value. By
+// default, the time when the document is generated is used for this value.
+// This method is typically only used for testing purposes to facilitate PDF
+// comparison. Specify a zero-value time to revert to the default behavior.
+func (f *Fpdf) SetCreationDate(tm time.Time) {
+	f.creationDate = tm
+}
+
+// SetJavascript adds Adobe JavaScript to the document.
+func (f *Fpdf) SetJavascript(script string) {
+	f.javascript = &script
+}
+
+// RegisterAlias adds an (alias, replacement) pair to the document so we can
+// replace all occurrences of that alias after writing but before the
+// document is closed.
+func (f *Fpdf) RegisterAlias(alias, replacement string) {
+	f.aliasMap[alias] = replacement
+}
+
+func (f *Fpdf) replaceAliases() {
+	for alias, replacement := range f.aliasMap {
+		for n := 1; n <= f.page; n++ {
+			s := f.pages[n].String()
+			if strings.Contains(s, alias) {
+				s = strings.Replace(s, alias, replacement, -1)
+				f.pages[n].Truncate(0)
+				f.pages[n].WriteString(s)
+			}
+		}
+	}
+}
+
+func (f *Fpdf) putpages() {
+	var wPt, hPt float64
+	var pageSize SizeType
+	// var linkList []linkType
+	var ok bool
+	nb := f.page
+	if len(f.aliasNbPagesStr) > 0 {
+		// Replace number of pages
+		alias := utf8toutf16(f.aliasNbPagesStr, false)
+		r := utf8toutf16(sprintf("%d", nb), false)
+		f.RegisterAlias(alias, r)
+		f.RegisterAlias(f.aliasNbPagesStr, sprintf("%d", nb))
+	}
+	f.replaceAliases()
+	if f.defOrientation == "P" {
+		wPt = f.defPageSize.Wd * f.k
+		hPt = f.defPageSize.Ht * f.k
+	} else {
+		wPt = f.defPageSize.Ht * f.k
+		hPt = f.defPageSize.Wd * f.k
+	}
+	for n := 1; n <= nb; n++ {
+		// Page
+		f.newobj()
+		f.out("<</Type /Page")
+		f.out("/Parent 1 0 R")
+		pageSize, ok = f.pageSizes[n]
+		if ok {
+			f.outf("/MediaBox [0 0 %.2f %.2f]", pageSize.Wd, pageSize.Ht)
+		}
+		for t, pb := range f.pageBoxes[n] {
+			f.outf("/%s [%.2f %.2f %.2f %.2f]", t, pb.X, pb.Y, pb.Wd, pb.Ht)
+		}
+		f.out("/Resources 2 0 R")
+		// Links
+		if len(f.pageLinks[n]) > 0 {
+			var annots fmtBuffer
+			annots.printf("/Annots [")
+			for _, pl := range f.pageLinks[n] {
+				annots.printf("<</Type /Annot /Subtype /Link /Rect [%.2f %.2f %.2f %.2f] /Border [0 0 0] ",
+					pl.x, pl.y, pl.x+pl.wd, pl.y-pl.ht)
+				if pl.link == 0 {
+					annots.printf("/A <</S /URI /URI %s>>>>", f.textstring(pl.linkStr))
+				} else {
+					l := f.links[pl.link]
+					var sz SizeType
+					var h float64
+					sz, ok = f.pageSizes[l.page]
+					if ok {
+						h = sz.Ht
+					} else {
+						h = hPt
+					}
+					// dbg("h [%.2f], l.y [%.2f] f.k [%.2f]\n", h, l.y, f.k)
+					annots.printf("/Dest [%d 0 R /XYZ 0 %.2f null]>>", 1+2*l.page, h-l.y*f.k)
+				}
+			}
+			annots.printf("]")
+			f.out(annots.String())
+		}
+		if f.pdfVersion > "1.3" {
+			f.out("/Group <</Type /Group /S /Transparency /CS /DeviceRGB>>")
+		}
+		f.outf("/Contents %d 0 R>>", f.n+1)
+		f.out("endobj")
+		// Page content
+		f.newobj()
+		if f.compress {
+			data := sliceCompress(f.pages[n].Bytes())
+			f.outf("<</Filter /FlateDecode /Length %d>>", len(data))
+			f.putstream(data)
+		} else {
+			f.outf("<</Length %d>>", f.pages[n].Len())
+			f.putstream(f.pages[n].Bytes())
+		}
+		f.out("endobj")
+	}
+	// Pages root
+	f.offsets[1] = f.buffer.Len()
+	f.out("1 0 obj")
+	f.out("<</Type /Pages")
+	var kids fmtBuffer
+	kids.printf("/Kids [")
+	for i := 0; i < nb; i++ {
+		kids.printf("%d 0 R ", 3+2*i)
+	}
+	kids.printf("]")
+	f.out(kids.String())
+	f.outf("/Count %d", nb)
+	f.outf("/MediaBox [0 0 %.2f %.2f]", wPt, hPt)
+	f.out(">>")
+	f.out("endobj")
+}
+
+func (f *Fpdf) putfonts() {
+	if f.err != nil {
+		return
+	}
+	nf := f.n
+	for _, diff := range f.diffs {
+		// Encodings
+		f.newobj()
+		f.outf("<</Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences [%s]>>", diff)
+		f.out("endobj")
+	}
+	{
+		var fileList []string
+		var info fontFileType
+		var file string
+		for file = range f.fontFiles {
+			fileList = append(fileList, file)
+		}
+		if f.catalogSort {
+			sort.SliceStable(fileList, func(i, j int) bool { return fileList[i] < fileList[j] })
+		}
+		for _, file = range fileList {
+			info = f.fontFiles[file]
+			if info.fontType != "UTF8" {
+				f.newobj()
+				info.n = f.n
+				f.fontFiles[file] = info
+
+				var font []byte
+
+				if info.embedded {
+					font = info.content
+				} else {
+					var err error
+					font, err = f.loadFontFile(file)
+					if err != nil {
+						f.err = err
+						return
+					}
+				}
+				compressed := file[len(file)-2:] == ".z"
+				if !compressed && info.length2 > 0 {
+					buf := font[6:info.length1]
+					buf = append(buf, font[6+info.length1+6:info.length2]...)
+					font = buf
+				}
+				f.outf("<</Length %d", len(font))
+				if compressed {
+					f.out("/Filter /FlateDecode")
+				}
+				f.outf("/Length1 %d", info.length1)
+				if info.length2 > 0 {
+					f.outf("/Length2 %d /Length3 0", info.length2)
+				}
+				f.out(">>")
+				f.putstream(font)
+				f.out("endobj")
+			}
+		}
+	}
+	{
+		var keyList []string
+		var font fontDefType
+		var key string
+		for key = range f.fonts {
+			keyList = append(keyList, key)
+		}
+		if f.catalogSort {
+			sort.SliceStable(keyList, func(i, j int) bool { return keyList[i] < keyList[j] })
+		}
+		for _, key = range keyList {
+			font = f.fonts[key]
+			// Font objects
+			font.N = f.n + 1
+			f.fonts[key] = font
+			tp := font.Tp
+			name := font.Name
+			switch tp {
+			case "Core":
+				// Core font
+				f.newobj()
+				f.out("<</Type /Font")
+				f.outf("/BaseFont /%s", name)
+				f.out("/Subtype /Type1")
+				if name != "Symbol" && name != "ZapfDingbats" {
+					f.out("/Encoding /WinAnsiEncoding")
+				}
+				f.out(">>")
+				f.out("endobj")
+			case "Type1":
+				fallthrough
+			case "TrueType":
+				// Additional Type1 or TrueType/OpenType font
+				f.newobj()
+				f.out("<</Type /Font")
+				f.outf("/BaseFont /%s", name)
+				f.outf("/Subtype /%s", tp)
+				f.out("/FirstChar 32 /LastChar 255")
+				f.outf("/Widths %d 0 R", f.n+1)
+				f.outf("/FontDescriptor %d 0 R", f.n+2)
+				if font.DiffN > 0 {
+					f.outf("/Encoding %d 0 R", nf+font.DiffN)
+				} else {
+					f.out("/Encoding /WinAnsiEncoding")
+				}
+				f.out(">>")
+				f.out("endobj")
+				// Widths
+				f.newobj()
+				var s fmtBuffer
+				s.WriteString("[")
+				for j := 32; j < 256; j++ {
+					s.printf("%d ", font.Cw[j])
+				}
+				s.WriteString("]")
+				f.out(s.String())
+				f.out("endobj")
+				// Descriptor
+				f.newobj()
+				s.Truncate(0)
+				s.printf("<</Type /FontDescriptor /FontName /%s ", name)
+				s.printf("/Ascent %d ", font.Desc.Ascent)
+				s.printf("/Descent %d ", font.Desc.Descent)
+				s.printf("/CapHeight %d ", font.Desc.CapHeight)
+				s.printf("/Flags %d ", font.Desc.Flags)
+				s.printf("/FontBBox [%d %d %d %d] ", font.Desc.FontBBox.Xmin, font.Desc.FontBBox.Ymin,
+					font.Desc.FontBBox.Xmax, font.Desc.FontBBox.Ymax)
+				s.printf("/ItalicAngle %d ", font.Desc.ItalicAngle)
+				s.printf("/StemV %d ", font.Desc.StemV)
+				s.printf("/MissingWidth %d ", font.Desc.MissingWidth)
+				var suffix string
+				if tp != "Type1" {
+					suffix = "2"
+				}
+				s.printf("/FontFile%s %d 0 R>>", suffix, f.fontFiles[font.File].n)
+				f.out(s.String())
+				f.out("endobj")
+			case "UTF8":
+				fontName := "utf8" + font.Name
+				usedRunes := font.usedRunes
+				delete(usedRunes, 0)
+				utf8FontStream := font.utf8File.GenerateСutFont(usedRunes)
+				utf8FontSize := len(utf8FontStream)
+				compressedFontStream := sliceCompress(utf8FontStream)
+				CodeSignDictionary := font.utf8File.CodeSymbolDictionary
+				delete(CodeSignDictionary, 0)
+
+				f.newobj()
+				f.out(fmt.Sprintf("<</Type /Font\n/Subtype /Type0\n/BaseFont /%s\n/Encoding /Identity-H\n/DescendantFonts [%d 0 R]\n/ToUnicode %d 0 R>>\n"+"endobj", fontName, f.n+1, f.n+2))
+
+				f.newobj()
+				f.out("<</Type /Font\n/Subtype /CIDFontType2\n/BaseFont /" + fontName + "\n" +
+					"/CIDSystemInfo " + strconv.Itoa(f.n+2) + " 0 R\n/FontDescriptor " + strconv.Itoa(f.n+3) + " 0 R")
+				if font.Desc.MissingWidth != 0 {
+					f.out("/DW " + strconv.Itoa(font.Desc.MissingWidth) + "")
+				}
+				f.generateCIDFontMap(&font, font.utf8File.LastRune)
+				f.out("/CIDToGIDMap " + strconv.Itoa(f.n+4) + " 0 R>>")
+				f.out("endobj")
+
+				f.newobj()
+				f.out("<</Length " + strconv.Itoa(len(toUnicode)) + ">>")
+				f.putstream([]byte(toUnicode))
+				f.out("endobj")
+
+				// CIDInfo
+				f.newobj()
+				f.out("<</Registry (Adobe)\n/Ordering (UCS)\n/Supplement 0>>")
+				f.out("endobj")
+
+				// Font descriptor
+				f.newobj()
+				var s fmtBuffer
+				s.printf("<</Type /FontDescriptor /FontName /%s\n /Ascent %d", fontName, font.Desc.Ascent)
+				s.printf(" /Descent %d", font.Desc.Descent)
+				s.printf(" /CapHeight %d", font.Desc.CapHeight)
+				v := font.Desc.Flags
+				v = v | 4
+				v = v &^ 32
+				s.printf(" /Flags %d", v)
+				s.printf("/FontBBox [%d %d %d %d] ", font.Desc.FontBBox.Xmin, font.Desc.FontBBox.Ymin,
+					font.Desc.FontBBox.Xmax, font.Desc.FontBBox.Ymax)
+				s.printf(" /ItalicAngle %d", font.Desc.ItalicAngle)
+				s.printf(" /StemV %d", font.Desc.StemV)
+				s.printf(" /MissingWidth %d", font.Desc.MissingWidth)
+				s.printf("/FontFile2 %d 0 R", f.n+2)
+				s.printf(">>")
+				f.out(s.String())
+				f.out("endobj")
+
+				// Embed CIDToGIDMap
+				cidToGidMap := make([]byte, 256*256*2)
+
+				for cc, glyph := range CodeSignDictionary {
+					cidToGidMap[cc*2] = byte(glyph >> 8)
+					cidToGidMap[cc*2+1] = byte(glyph & 0xFF)
+				}
+
+				cidToGidMap = sliceCompress(cidToGidMap)
+				f.newobj()
+				f.out("<</Length " + strconv.Itoa(len(cidToGidMap)) + "/Filter /FlateDecode>>")
+				f.putstream(cidToGidMap)
+				f.out("endobj")
+
+				//Font file
+				f.newobj()
+				f.out("<</Length " + strconv.Itoa(len(compressedFontStream)))
+				f.out("/Filter /FlateDecode")
+				f.out("/Length1 " + strconv.Itoa(utf8FontSize))
+				f.out(">>")
+				f.putstream(compressedFontStream)
+				f.out("endobj")
+			default:
+				f.err = fmt.Errorf("unsupported font type: %s", tp)
+				return
+			}
+		}
+	}
+	return
+}
+
+func (f *Fpdf) generateCIDFontMap(font *fontDefType, LastRune int) {
+	rangeID := 0
+	cidArray := make(map[int]*untypedKeyMap)
+	cidArrayKeys := make([]int, 0)
+	prevCid := -2
+	prevWidth := -1
+	interval := false
+	startCid := 1
+	cwLen := LastRune + 1
+
+	// for each character
+	for cid := startCid; cid < cwLen; cid++ {
+		if font.Cw[cid] == 0x00 {
+			continue
+		}
+		width := font.Cw[cid]
+		if width == 65535 {
+			width = 0
+		}
+		if numb, OK := font.usedRunes[cid]; cid > 255 && (!OK || numb == 0) {
+			continue
+		}
+
+		if cid == prevCid+1 {
+			if width == prevWidth {
+
+				if width == cidArray[rangeID].get(0) {
+					cidArray[rangeID].put(nil, width)
+				} else {
+					cidArray[rangeID].pop()
+					rangeID = prevCid
+					r := untypedKeyMap{
+						valueSet: make([]int, 0),
+						keySet:   make([]interface{}, 0),
+					}
+					cidArray[rangeID] = &r
+					cidArrayKeys = append(cidArrayKeys, rangeID)
+					cidArray[rangeID].put(nil, prevWidth)
+					cidArray[rangeID].put(nil, width)
+				}
+				interval = true
+				cidArray[rangeID].put("interval", 1)
+			} else {
+				if interval {
+					// new range
+					rangeID = cid
+					r := untypedKeyMap{
+						valueSet: make([]int, 0),
+						keySet:   make([]interface{}, 0),
+					}
+					cidArray[rangeID] = &r
+					cidArrayKeys = append(cidArrayKeys, rangeID)
+					cidArray[rangeID].put(nil, width)
+				} else {
+					cidArray[rangeID].put(nil, width)
+				}
+				interval = false
+			}
+		} else {
+			rangeID = cid
+			r := untypedKeyMap{
+				valueSet: make([]int, 0),
+				keySet:   make([]interface{}, 0),
+			}
+			cidArray[rangeID] = &r
+			cidArrayKeys = append(cidArrayKeys, rangeID)
+			cidArray[rangeID].put(nil, width)
+			interval = false
+		}
+		prevCid = cid
+		prevWidth = width
+
+	}
+	previousKey := -1
+	nextKey := -1
+	isInterval := false
+	for g := 0; g < len(cidArrayKeys); {
+		key := cidArrayKeys[g]
+		ws := *cidArray[key]
+		cws := len(ws.keySet)
+		if (key == nextKey) && (!isInterval) && (ws.getIndex("interval") < 0 || cws < 4) {
+			if cidArray[key].getIndex("interval") >= 0 {
+				cidArray[key].delete("interval")
+			}
+			cidArray[previousKey] = arrayMerge(cidArray[previousKey], cidArray[key])
+			cidArrayKeys = remove(cidArrayKeys, key)
+		} else {
+			g++
+			previousKey = key
+		}
+		nextKey = key + cws
+		// ui := ws.getIndex("interval")
+		// ui = ui + 1
+		if ws.getIndex("interval") >= 0 {
+			if cws > 3 {
+				isInterval = true
+			} else {
+				isInterval = false
+			}
+			cidArray[key].delete("interval")
+			nextKey--
+		} else {
+			isInterval = false
+		}
+	}
+	var w fmtBuffer
+	for _, k := range cidArrayKeys {
+		ws := cidArray[k]
+		if len(arrayCountValues(ws.valueSet)) == 1 {
+			w.printf(" %d %d %d", k, k+len(ws.valueSet)-1, ws.get(0))
+		} else {
+			w.printf(" %d [ %s ]\n", k, implode(" ", ws.valueSet))
+		}
+	}
+	f.out("/W [" + w.String() + " ]")
+}
+
+func implode(sep string, arr []int) string {
+	var s fmtBuffer
+	for i := 0; i < len(arr)-1; i++ {
+		s.printf("%v", arr[i])
+		s.printf(sep)
+	}
+	if len(arr) > 0 {
+		s.printf("%v", arr[len(arr)-1])
+	}
+	return s.String()
+}
+
+func arrayCountValues(mp []int) map[int]int {
+	answer := make(map[int]int)
+	for _, v := range mp {
+		answer[v] = answer[v] + 1
+	}
+	return answer
+}
+
+func (f *Fpdf) loadFontFile(name string) ([]byte, error) {
+	if f.fontLoader != nil {
+		reader, err := f.fontLoader.Open(name)
+		if err == nil {
+			data, err := ioutil.ReadAll(reader)
+			if closer, ok := reader.(io.Closer); ok {
+				closer.Close()
+			}
+			return data, err
+		}
+	}
+	return ioutil.ReadFile(path.Join(f.fontpath, name))
+}
+
+func (f *Fpdf) putimages() {
+	var keyList []string
+	var key string
+	for key = range f.images {
+		keyList = append(keyList, key)
+	}
+	if f.catalogSort {
+		sort.SliceStable(keyList, func(i, j int) bool { return f.images[keyList[i]].w < f.images[keyList[j]].w })
+	}
+	for _, key = range keyList {
+		f.putimage(f.images[key])
+	}
+}
+
+func (f *Fpdf) putimage(info *ImageInfoType) {
+	f.newobj()
+	info.n = f.n
+	f.out("<</Type /XObject")
+	f.out("/Subtype /Image")
+	f.outf("/Width %d", int(info.w))
+	f.outf("/Height %d", int(info.h))
+	if info.cs == "Indexed" {
+		f.outf("/ColorSpace [/Indexed /DeviceRGB %d %d 0 R]", len(info.pal)/3-1, f.n+1)
+	} else {
+		f.outf("/ColorSpace /%s", info.cs)
+		if info.cs == "DeviceCMYK" {
+			f.out("/Decode [1 0 1 0 1 0 1 0]")
+		}
+	}
+	f.outf("/BitsPerComponent %d", info.bpc)
+	if len(info.f) > 0 {
+		f.outf("/Filter /%s", info.f)
+	}
+	if len(info.dp) > 0 {
+		f.outf("/DecodeParms <<%s>>", info.dp)
+	}
+	if len(info.trns) > 0 {
+		var trns fmtBuffer
+		for _, v := range info.trns {
+			trns.printf("%d %d ", v, v)
+		}
+		f.outf("/Mask [%s]", trns.String())
+	}
+	if info.smask != nil {
+		f.outf("/SMask %d 0 R", f.n+1)
+	}
+	f.outf("/Length %d>>", len(info.data))
+	f.putstream(info.data)
+	f.out("endobj")
+	// 	Soft mask
+	if len(info.smask) > 0 {
+		smask := &ImageInfoType{
+			w:     info.w,
+			h:     info.h,
+			cs:    "DeviceGray",
+			bpc:   8,
+			f:     info.f,
+			dp:    sprintf("/Predictor 15 /Colors 1 /BitsPerComponent 8 /Columns %d", int(info.w)),
+			data:  info.smask,
+			scale: f.k,
+		}
+		f.putimage(smask)
+	}
+	// 	Palette
+	if info.cs == "Indexed" {
+		f.newobj()
+		if f.compress {
+			pal := sliceCompress(info.pal)
+			f.outf("<</Filter /FlateDecode /Length %d>>", len(pal))
+			f.putstream(pal)
+		} else {
+			f.outf("<</Length %d>>", len(info.pal))
+			f.putstream(info.pal)
+		}
+		f.out("endobj")
+	}
+}
+
+func (f *Fpdf) putxobjectdict() {
+	{
+		var image *ImageInfoType
+		var key string
+		var keyList []string
+		for key = range f.images {
+			keyList = append(keyList, key)
+		}
+		if f.catalogSort {
+			sort.SliceStable(keyList, func(i, j int) bool { return f.images[keyList[i]].i < f.images[keyList[j]].i })
+		}
+		for _, key = range keyList {
+			image = f.images[key]
+			f.outf("/I%s %d 0 R", image.i, image.n)
+		}
+	}
+	{
+		var keyList []string
+		var key string
+		var tpl Template
+		keyList = templateKeyList(f.templates, f.catalogSort)
+		for _, key = range keyList {
+			tpl = f.templates[key]
+			// for _, tpl := range f.templates {
+			id := tpl.ID()
+			if objID, ok := f.templateObjects[id]; ok {
+				f.outf("/TPL%s %d 0 R", id, objID)
+			}
+		}
+	}
+	{
+		for tplName, objID := range f.importedTplObjs {
+			// here replace obj id hash with n
+			f.outf("%s %d 0 R", tplName, f.importedTplIDs[objID])
+		}
+	}
+}
+
+func (f *Fpdf) putresourcedict() {
+	f.out("/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]")
+	f.out("/Font <<")
+	{
+		var keyList []string
+		var font fontDefType
+		var key string
+		for key = range f.fonts {
+			keyList = append(keyList, key)
+		}
+		if f.catalogSort {
+			sort.SliceStable(keyList, func(i, j int) bool { return f.fonts[keyList[i]].i < f.fonts[keyList[j]].i })
+		}
+		for _, key = range keyList {
+			font = f.fonts[key]
+			f.outf("/F%s %d 0 R", font.i, font.N)
+		}
+	}
+	f.out(">>")
+	f.out("/XObject <<")
+	f.putxobjectdict()
+	f.out(">>")
+	count := len(f.blendList)
+	if count > 1 {
+		f.out("/ExtGState <<")
+		for j := 1; j < count; j++ {
+			f.outf("/GS%d %d 0 R", j, f.blendList[j].objNum)
+		}
+		f.out(">>")
+	}
+	count = len(f.gradientList)
+	if count > 1 {
+		f.out("/Shading <<")
+		for j := 1; j < count; j++ {
+			f.outf("/Sh%d %d 0 R", j, f.gradientList[j].objNum)
+		}
+		f.out(">>")
+	}
+	// Layers
+	f.layerPutResourceDict()
+	f.spotColorPutResourceDict()
+}
+
+func (f *Fpdf) putBlendModes() {
+	count := len(f.blendList)
+	for j := 1; j < count; j++ {
+		bl := f.blendList[j]
+		f.newobj()
+		f.blendList[j].objNum = f.n
+		f.outf("<</Type /ExtGState /ca %s /CA %s /BM /%s>>",
+			bl.fillStr, bl.strokeStr, bl.modeStr)
+		f.out("endobj")
+	}
+}
+
+func (f *Fpdf) putGradients() {
+	count := len(f.gradientList)
+	for j := 1; j < count; j++ {
+		var f1 int
+		gr := f.gradientList[j]
+		if gr.tp == 2 || gr.tp == 3 {
+			f.newobj()
+			f.outf("<</FunctionType 2 /Domain [0.0 1.0] /C0 [%s] /C1 [%s] /N 1>>", gr.clr1Str, gr.clr2Str)
+			f.out("endobj")
+			f1 = f.n
+		}
+		f.newobj()
+		f.outf("<</ShadingType %d /ColorSpace /DeviceRGB", gr.tp)
+		if gr.tp == 2 {
+			f.outf("/Coords [%.5f %.5f %.5f %.5f] /Function %d 0 R /Extend [true true]>>",
+				gr.x1, gr.y1, gr.x2, gr.y2, f1)
+		} else if gr.tp == 3 {
+			f.outf("/Coords [%.5f %.5f 0 %.5f %.5f %.5f] /Function %d 0 R /Extend [true true]>>",
+				gr.x1, gr.y1, gr.x2, gr.y2, gr.r, f1)
+		}
+		f.out("endobj")
+		f.gradientList[j].objNum = f.n
+	}
+}
+
+func (f *Fpdf) putjavascript() {
+	if f.javascript == nil {
+		return
+	}
+
+	f.newobj()
+	f.nJs = f.n
+	f.out("<<")
+	f.outf("/Names [(EmbeddedJS) %d 0 R]", f.n+1)
+	f.out(">>")
+	f.out("endobj")
+	f.newobj()
+	f.out("<<")
+	f.out("/S /JavaScript")
+	f.outf("/JS %s", f.textstring(*f.javascript))
+	f.out(">>")
+	f.out("endobj")
+}
+
+func (f *Fpdf) putresources() {
+	if f.err != nil {
+		return
+	}
+	f.layerPutLayers()
+	f.putBlendModes()
+	f.putGradients()
+	f.putSpotColors()
+	f.putfonts()
+	if f.err != nil {
+		return
+	}
+	f.putimages()
+	f.putTemplates()
+	f.putImportedTemplates() // gofpdi
+	// 	Resource dictionary
+	f.offsets[2] = f.buffer.Len()
+	f.out("2 0 obj")
+	f.out("<<")
+	f.putresourcedict()
+	f.out(">>")
+	f.out("endobj")
+	f.putjavascript()
+	if f.protect.encrypted {
+		f.newobj()
+		f.protect.objNum = f.n
+		f.out("<<")
+		f.out("/Filter /Standard")
+		f.out("/V 1")
+		f.out("/R 2")
+		f.outf("/O (%s)", f.escape(string(f.protect.oValue)))
+		f.outf("/U (%s)", f.escape(string(f.protect.uValue)))
+		f.outf("/P %d", f.protect.pValue)
+		f.out(">>")
+		f.out("endobj")
+	}
+	return
+}
+
+func (f *Fpdf) putinfo() {
+	var tm time.Time
+	if len(f.producer) > 0 {
+		f.outf("/Producer %s", f.textstring(f.producer))
+	}
+	if len(f.title) > 0 {
+		f.outf("/Title %s", f.textstring(f.title))
+	}
+	if len(f.subject) > 0 {
+		f.outf("/Subject %s", f.textstring(f.subject))
+	}
+	if len(f.author) > 0 {
+		f.outf("/Author %s", f.textstring(f.author))
+	}
+	if len(f.keywords) > 0 {
+		f.outf("/Keywords %s", f.textstring(f.keywords))
+	}
+	if len(f.creator) > 0 {
+		f.outf("/Creator %s", f.textstring(f.creator))
+	}
+	if f.creationDate.IsZero() {
+		tm = time.Now()
+	} else {
+		tm = f.creationDate
+	}
+	f.outf("/CreationDate %s", f.textstring("D:"+tm.Format("20060102150405")))
+}
+
+func (f *Fpdf) putcatalog() {
+	f.out("/Type /Catalog")
+	f.out("/Pages 1 0 R")
+	switch f.zoomMode {
+	case "fullpage":
+		f.out("/OpenAction [3 0 R /Fit]")
+	case "fullwidth":
+		f.out("/OpenAction [3 0 R /FitH null]")
+	case "real":
+		f.out("/OpenAction [3 0 R /XYZ null null 1]")
+	}
+	// } 	else if !is_string($this->zoomMode))
+	// 		$this->out('/OpenAction [3 0 R /XYZ null null '.sprintf('%.2f',$this->zoomMode/100).']');
+	switch f.layoutMode {
+	case "single", "SinglePage":
+		f.out("/PageLayout /SinglePage")
+	case "continuous", "OneColumn":
+		f.out("/PageLayout /OneColumn")
+	case "two", "TwoColumnLeft":
+		f.out("/PageLayout /TwoColumnLeft")
+	case "TwoColumnRight":
+		f.out("/PageLayout /TwoColumnRight")
+	case "TwoPageLeft", "TwoPageRight":
+		if f.pdfVersion < "1.5" {
+			f.pdfVersion = "1.5"
+		}
+		f.out("/PageLayout /" + f.layoutMode)
+	}
+	// Bookmarks
+	if len(f.outlines) > 0 {
+		f.outf("/Outlines %d 0 R", f.outlineRoot)
+		f.out("/PageMode /UseOutlines")
+	}
+	// Layers
+	f.layerPutCatalog()
+	// JavaScript
+	if f.javascript != nil {
+		f.outf("/Names <</JavaScript %d 0 R>>", f.nJs)
+	}
+}
+
+func (f *Fpdf) putheader() {
+	if len(f.blendMap) > 0 && f.pdfVersion < "1.4" {
+		f.pdfVersion = "1.4"
+	}
+	f.outf("%%PDF-%s", f.pdfVersion)
+}
+
+func (f *Fpdf) puttrailer() {
+	f.outf("/Size %d", f.n+1)
+	f.outf("/Root %d 0 R", f.n)
+	f.outf("/Info %d 0 R", f.n-1)
+	if f.protect.encrypted {
+		f.outf("/Encrypt %d 0 R", f.protect.objNum)
+		f.out("/ID [()()]")
+	}
+}
+
+func (f *Fpdf) putxmp() {
+	if len(f.xmp) == 0 {
+		return
+	}
+	f.newobj()
+	f.outf("<< /Type /Metadata /Subtype /XML /Length %d >>", len(f.xmp))
+	f.putstream(f.xmp)
+	f.out("endobj")
+}
+
+func (f *Fpdf) putbookmarks() {
+	nb := len(f.outlines)
+	if nb > 0 {
+		lru := make(map[int]int)
+		level := 0
+		for i, o := range f.outlines {
+			if o.level > 0 {
+				parent := lru[o.level-1]
+				f.outlines[i].parent = parent
+				f.outlines[parent].last = i
+				if o.level > level {
+					f.outlines[parent].first = i
+				}
+			} else {
+				f.outlines[i].parent = nb
+			}
+			if o.level <= level && i > 0 {
+				prev := lru[o.level]
+				f.outlines[prev].next = i
+				f.outlines[i].prev = prev
+			}
+			lru[o.level] = i
+			level = o.level
+		}
+		n := f.n + 1
+		for _, o := range f.outlines {
+			f.newobj()
+			f.outf("<</Title %s", f.textstring(o.text))
+			f.outf("/Parent %d 0 R", n+o.parent)
+			if o.prev != -1 {
+				f.outf("/Prev %d 0 R", n+o.prev)
+			}
+			if o.next != -1 {
+				f.outf("/Next %d 0 R", n+o.next)
+			}
+			if o.first != -1 {
+				f.outf("/First %d 0 R", n+o.first)
+			}
+			if o.last != -1 {
+				f.outf("/Last %d 0 R", n+o.last)
+			}
+			f.outf("/Dest [%d 0 R /XYZ 0 %.2f null]", 1+2*o.p, (f.h-o.y)*f.k)
+			f.out("/Count 0>>")
+			f.out("endobj")
+		}
+		f.newobj()
+		f.outlineRoot = f.n
+		f.outf("<</Type /Outlines /First %d 0 R", n)
+		f.outf("/Last %d 0 R>>", n+lru[0])
+		f.out("endobj")
+	}
+}
+
+func (f *Fpdf) enddoc() {
+	if f.err != nil {
+		return
+	}
+	f.layerEndDoc()
+	f.putheader()
+	f.putpages()
+	f.putresources()
+	if f.err != nil {
+		return
+	}
+	// Bookmarks
+	f.putbookmarks()
+	// Metadata
+	f.putxmp()
+	// 	Info
+	f.newobj()
+	f.out("<<")
+	f.putinfo()
+	f.out(">>")
+	f.out("endobj")
+	// 	Catalog
+	f.newobj()
+	f.out("<<")
+	f.putcatalog()
+	f.out(">>")
+	f.out("endobj")
+	// Cross-ref
+	o := f.buffer.Len()
+	f.out("xref")
+	f.outf("0 %d", f.n+1)
+	f.out("0000000000 65535 f ")
+	for j := 1; j <= f.n; j++ {
+		f.outf("%010d 00000 n ", f.offsets[j])
+	}
+	// Trailer
+	f.out("trailer")
+	f.out("<<")
+	f.puttrailer()
+	f.out(">>")
+	f.out("startxref")
+	f.outf("%d", o)
+	f.out("%%EOF")
+	f.state = 3
+	return
+}
+
+// Path Drawing
+
+// MoveTo moves the stylus to (x, y) without drawing the path from the
+// previous point. Paths must start with a MoveTo to set the original
+// stylus location or the result is undefined.
+//
+// Create a "path" by moving a virtual stylus around the page (with
+// MoveTo, LineTo, CurveTo, CurveBezierCubicTo, ArcTo & ClosePath)
+// then draw it or  fill it in (with DrawPath). The main advantage of
+// using the path drawing routines rather than multiple Fpdf.Line is
+// that PDF creates nice line joins at the angles, rather than just
+// overlaying the lines.
+func (f *Fpdf) MoveTo(x, y float64) {
+	f.point(x, y)
+	f.x, f.y = x, y
+}
+
+// LineTo creates a line from the current stylus location to (x, y), which
+// becomes the new stylus location. Note that this only creates the line in
+// the path; it does not actually draw the line on the page.
+//
+// The MoveTo() example demonstrates this method.
+func (f *Fpdf) LineTo(x, y float64) {
+	f.outf("%.2f %.2f l", x*f.k, (f.h-y)*f.k)
+	f.x, f.y = x, y
+}
+
+// CurveTo creates a single-segment quadratic Bézier curve. The curve starts at
+// the current stylus location and ends at the point (x, y). The control point
+// (cx, cy) specifies the curvature. At the start point, the curve is tangent
+// to the straight line between the current stylus location and the control
+// point. At the end point, the curve is tangent to the straight line between
+// the end point and the control point.
+//
+// The MoveTo() example demonstrates this method.
+func (f *Fpdf) CurveTo(cx, cy, x, y float64) {
+	f.outf("%.5f %.5f %.5f %.5f v", cx*f.k, (f.h-cy)*f.k, x*f.k, (f.h-y)*f.k)
+	f.x, f.y = x, y
+}
+
+// CurveBezierCubicTo creates a single-segment cubic Bézier curve. The curve
+// starts at the current stylus location and ends at the point (x, y). The
+// control points (cx0, cy0) and (cx1, cy1) specify the curvature. At the
+// current stylus, the curve is tangent to the straight line between the
+// current stylus location and the control point (cx0, cy0). At the end point,
+// the curve is tangent to the straight line between the end point and the
+// control point (cx1, cy1).
+//
+// The MoveTo() example demonstrates this method.
+func (f *Fpdf) CurveBezierCubicTo(cx0, cy0, cx1, cy1, x, y float64) {
+	f.curve(cx0, cy0, cx1, cy1, x, y)
+	f.x, f.y = x, y
+}
+
+// ClosePath creates a line from the current location to the last MoveTo point
+// (if not the same) and mark the path as closed so the first and last lines
+// join nicely.
+//
+// The MoveTo() example demonstrates this method.
+func (f *Fpdf) ClosePath() {
+	f.outf("h")
+}
+
+// DrawPath actually draws the path on the page.
+//
+// styleStr can be "F" for filled, "D" for outlined only, or "DF" or "FD" for
+// outlined and filled. An empty string will be replaced with "D".
+// Path-painting operators as defined in the PDF specification are also
+// allowed: "S" (Stroke the path), "s" (Close and stroke the path),
+// "f" (fill the path, using the nonzero winding number), "f*"
+// (Fill the path, using the even-odd rule), "B" (Fill and then stroke
+// the path, using the nonzero winding number rule), "B*" (Fill and
+// then stroke the path, using the even-odd rule), "b" (Close, fill,
+// and then stroke the path, using the nonzero winding number rule) and
+// "b*" (Close, fill, and then stroke the path, using the even-odd
+// rule).
+// Drawing uses the current draw color, line width, and cap style
+// centered on the
+// path. Filling uses the current fill color.
+//
+// The MoveTo() example demonstrates this method.
+func (f *Fpdf) DrawPath(styleStr string) {
+	f.outf(fillDrawOp(styleStr))
+}
+
+// ArcTo draws an elliptical arc centered at point (x, y). rx and ry specify its
+// horizontal and vertical radii. If the start of the arc is not at
+// the current position, a connecting line will be drawn.
+//
+// degRotate specifies the angle that the arc will be rotated. degStart and
+// degEnd specify the starting and ending angle of the arc. All angles are
+// specified in degrees and measured counter-clockwise from the 3 o'clock
+// position.
+//
+// styleStr can be "F" for filled, "D" for outlined only, or "DF" or "FD" for
+// outlined and filled. An empty string will be replaced with "D". Drawing uses
+// the current draw color, line width, and cap style centered on the arc's
+// path. Filling uses the current fill color.
+//
+// The MoveTo() example demonstrates this method.
+func (f *Fpdf) ArcTo(x, y, rx, ry, degRotate, degStart, degEnd float64) {
+	f.arc(x, y, rx, ry, degRotate, degStart, degEnd, "", true)
+}
+
+func (f *Fpdf) arc(x, y, rx, ry, degRotate, degStart, degEnd float64,
+	styleStr string, path bool) {
+	x *= f.k
+	y = (f.h - y) * f.k
+	rx *= f.k
+	ry *= f.k
+	segments := int(degEnd-degStart) / 60
+	if segments < 2 {
+		segments = 2
+	}
+	angleStart := degStart * math.Pi / 180
+	angleEnd := degEnd * math.Pi / 180
+	angleTotal := angleEnd - angleStart
+	dt := angleTotal / float64(segments)
+	dtm := dt / 3
+	if degRotate != 0 {
+		a := -degRotate * math.Pi / 180
+		f.outf("q %.5f %.5f %.5f %.5f %.5f %.5f cm",
+			math.Cos(a), -1*math.Sin(a),
+			math.Sin(a), math.Cos(a), x, y)
+		x = 0
+		y = 0
+	}
+	t := angleStart
+	a0 := x + rx*math.Cos(t)
+	b0 := y + ry*math.Sin(t)
+	c0 := -rx * math.Sin(t)
+	d0 := ry * math.Cos(t)
+	sx := a0 / f.k // start point of arc
+	sy := f.h - (b0 / f.k)
+	if path {
+		if f.x != sx || f.y != sy {
+			// Draw connecting line to start point
+			f.LineTo(sx, sy)
+		}
+	} else {
+		f.point(sx, sy)
+	}
+	for j := 1; j <= segments; j++ {
+		// Draw this bit of the total curve
+		t = (float64(j) * dt) + angleStart
+		a1 := x + rx*math.Cos(t)
+		b1 := y + ry*math.Sin(t)
+		c1 := -rx * math.Sin(t)
+		d1 := ry * math.Cos(t)
+		f.curve((a0+(c0*dtm))/f.k,
+			f.h-((b0+(d0*dtm))/f.k),
+			(a1-(c1*dtm))/f.k,
+			f.h-((b1-(d1*dtm))/f.k),
+			a1/f.k,
+			f.h-(b1/f.k))
+		a0 = a1
+		b0 = b1
+		c0 = c1
+		d0 = d1
+		if path {
+			f.x = a1 / f.k
+			f.y = f.h - (b1 / f.k)
+		}
+	}
+	if !path {
+		f.out(fillDrawOp(styleStr))
+	}
+	if degRotate != 0 {
+		f.out("Q")
+	}
+}

+ 213 - 0
vendor/github.com/jung-kurt/gofpdf/fpdftrans.go

@@ -0,0 +1,213 @@
+package gofpdf
+
+import (
+	"fmt"
+	"math"
+)
+
+// Routines in this file are translated from the work of Moritz Wagner and
+// Andreas Würmser.
+
+// TransformMatrix is used for generalized transformations of text, drawings
+// and images.
+type TransformMatrix struct {
+	A, B, C, D, E, F float64
+}
+
+// TransformBegin sets up a transformation context for subsequent text,
+// drawings and images. The typical usage is to immediately follow a call to
+// this method with a call to one or more of the transformation methods such as
+// TransformScale(), TransformSkew(), etc. This is followed by text, drawing or
+// image output and finally a call to TransformEnd(). All transformation
+// contexts must be properly ended prior to outputting the document.
+func (f *Fpdf) TransformBegin() {
+	f.transformNest++
+	f.out("q")
+}
+
+// TransformScaleX scales the width of the following text, drawings and images.
+// scaleWd is the percentage scaling factor. (x, y) is center of scaling.
+//
+// The TransformBegin() example demonstrates this method.
+func (f *Fpdf) TransformScaleX(scaleWd, x, y float64) {
+	f.TransformScale(scaleWd, 100, x, y)
+}
+
+// TransformScaleY scales the height of the following text, drawings and
+// images. scaleHt is the percentage scaling factor. (x, y) is center of
+// scaling.
+//
+// The TransformBegin() example demonstrates this method.
+func (f *Fpdf) TransformScaleY(scaleHt, x, y float64) {
+	f.TransformScale(100, scaleHt, x, y)
+}
+
+// TransformScaleXY uniformly scales the width and height of the following
+// text, drawings and images. s is the percentage scaling factor for both width
+// and height. (x, y) is center of scaling.
+//
+// The TransformBegin() example demonstrates this method.
+func (f *Fpdf) TransformScaleXY(s, x, y float64) {
+	f.TransformScale(s, s, x, y)
+}
+
+// TransformScale generally scales the following text, drawings and images.
+// scaleWd and scaleHt are the percentage scaling factors for width and height.
+// (x, y) is center of scaling.
+//
+// The TransformBegin() example demonstrates this method.
+func (f *Fpdf) TransformScale(scaleWd, scaleHt, x, y float64) {
+	if scaleWd == 0 || scaleHt == 0 {
+		f.err = fmt.Errorf("scale factor cannot be zero")
+		return
+	}
+	y = (f.h - y) * f.k
+	x *= f.k
+	scaleWd /= 100
+	scaleHt /= 100
+	f.Transform(TransformMatrix{scaleWd, 0, 0,
+		scaleHt, x * (1 - scaleWd), y * (1 - scaleHt)})
+}
+
+// TransformMirrorHorizontal horizontally mirrors the following text, drawings
+// and images. x is the axis of reflection.
+//
+// The TransformBegin() example demonstrates this method.
+func (f *Fpdf) TransformMirrorHorizontal(x float64) {
+	f.TransformScale(-100, 100, x, f.y)
+}
+
+// TransformMirrorVertical vertically mirrors the following text, drawings and
+// images. y is the axis of reflection.
+//
+// The TransformBegin() example demonstrates this method.
+func (f *Fpdf) TransformMirrorVertical(y float64) {
+	f.TransformScale(100, -100, f.x, y)
+}
+
+// TransformMirrorPoint symmetrically mirrors the following text, drawings and
+// images on the point specified by (x, y).
+//
+// The TransformBegin() example demonstrates this method.
+func (f *Fpdf) TransformMirrorPoint(x, y float64) {
+	f.TransformScale(-100, -100, x, y)
+}
+
+// TransformMirrorLine symmetrically mirrors the following text, drawings and
+// images on the line defined by angle and the point (x, y). angles is
+// specified in degrees and measured counter-clockwise from the 3 o'clock
+// position.
+//
+// The TransformBegin() example demonstrates this method.
+func (f *Fpdf) TransformMirrorLine(angle, x, y float64) {
+	f.TransformScale(-100, 100, x, y)
+	f.TransformRotate(-2*(angle-90), x, y)
+}
+
+// TransformTranslateX moves the following text, drawings and images
+// horizontally by the amount specified by tx.
+//
+// The TransformBegin() example demonstrates this method.
+func (f *Fpdf) TransformTranslateX(tx float64) {
+	f.TransformTranslate(tx, 0)
+}
+
+// TransformTranslateY moves the following text, drawings and images vertically
+// by the amount specified by ty.
+//
+// The TransformBegin() example demonstrates this method.
+func (f *Fpdf) TransformTranslateY(ty float64) {
+	f.TransformTranslate(0, ty)
+}
+
+// TransformTranslate moves the following text, drawings and images
+// horizontally and vertically by the amounts specified by tx and ty.
+//
+// The TransformBegin() example demonstrates this method.
+func (f *Fpdf) TransformTranslate(tx, ty float64) {
+	f.Transform(TransformMatrix{1, 0, 0, 1, tx * f.k, -ty * f.k})
+}
+
+// TransformRotate rotates the following text, drawings and images around the
+// center point (x, y). angle is specified in degrees and measured
+// counter-clockwise from the 3 o'clock position.
+//
+// The TransformBegin() example demonstrates this method.
+func (f *Fpdf) TransformRotate(angle, x, y float64) {
+	y = (f.h - y) * f.k
+	x *= f.k
+	angle = angle * math.Pi / 180
+	var tm TransformMatrix
+	tm.A = math.Cos(angle)
+	tm.B = math.Sin(angle)
+	tm.C = -tm.B
+	tm.D = tm.A
+	tm.E = x + tm.B*y - tm.A*x
+	tm.F = y - tm.A*y - tm.B*x
+	f.Transform(tm)
+}
+
+// TransformSkewX horizontally skews the following text, drawings and images
+// keeping the point (x, y) stationary. angleX ranges from -90 degrees (skew to
+// the left) to 90 degrees (skew to the right).
+//
+// The TransformBegin() example demonstrates this method.
+func (f *Fpdf) TransformSkewX(angleX, x, y float64) {
+	f.TransformSkew(angleX, 0, x, y)
+}
+
+// TransformSkewY vertically skews the following text, drawings and images
+// keeping the point (x, y) stationary. angleY ranges from -90 degrees (skew to
+// the bottom) to 90 degrees (skew to the top).
+//
+// The TransformBegin() example demonstrates this method.
+func (f *Fpdf) TransformSkewY(angleY, x, y float64) {
+	f.TransformSkew(0, angleY, x, y)
+}
+
+// TransformSkew generally skews the following text, drawings and images
+// keeping the point (x, y) stationary. angleX ranges from -90 degrees (skew to
+// the left) to 90 degrees (skew to the right). angleY ranges from -90 degrees
+// (skew to the bottom) to 90 degrees (skew to the top).
+//
+// The TransformBegin() example demonstrates this method.
+func (f *Fpdf) TransformSkew(angleX, angleY, x, y float64) {
+	if angleX <= -90 || angleX >= 90 || angleY <= -90 || angleY >= 90 {
+		f.err = fmt.Errorf("skew values must be between -90° and 90°")
+		return
+	}
+	x *= f.k
+	y = (f.h - y) * f.k
+	var tm TransformMatrix
+	tm.A = 1
+	tm.B = math.Tan(angleY * math.Pi / 180)
+	tm.C = math.Tan(angleX * math.Pi / 180)
+	tm.D = 1
+	tm.E = -tm.C * y
+	tm.F = -tm.B * x
+	f.Transform(tm)
+}
+
+// Transform generally transforms the following text, drawings and images
+// according to the specified matrix. It is typically easier to use the various
+// methods such as TransformRotate() and TransformMirrorVertical() instead.
+func (f *Fpdf) Transform(tm TransformMatrix) {
+	if f.transformNest > 0 {
+		f.outf("%.5f %.5f %.5f %.5f %.5f %.5f cm",
+			tm.A, tm.B, tm.C, tm.D, tm.E, tm.F)
+	} else if f.err == nil {
+		f.err = fmt.Errorf("transformation context is not active")
+	}
+}
+
+// TransformEnd applies a transformation that was begun with a call to TransformBegin().
+//
+// The TransformBegin() example demonstrates this method.
+func (f *Fpdf) TransformEnd() {
+	if f.transformNest > 0 {
+		f.transformNest--
+		f.out("Q")
+	} else {
+		f.err = fmt.Errorf("error attempting to end transformation operation out of sequence")
+	}
+}

+ 12 - 0
vendor/github.com/jung-kurt/gofpdf/go.mod

@@ -0,0 +1,12 @@
+module github.com/jung-kurt/gofpdf
+
+go 1.12
+
+require (
+	github.com/boombuler/barcode v1.0.0
+	github.com/phpdave11/gofpdi v1.0.7
+	github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58
+	golang.org/x/image v0.0.0-20190507092727-e4e5bf290fec
+)
+
+replace github.com/jung-kurt/gopdf => ./

+ 18 - 0
vendor/github.com/jung-kurt/gofpdf/go.sum

@@ -0,0 +1,18 @@
+github.com/boombuler/barcode v1.0.0 h1:s1TvRnXwL2xJRaccrdcBQMZxq6X7DvsMogtmJeHDdrc=
+github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
+github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
+github.com/phpdave11/gofpdi v1.0.7 h1:k2oy4yhkQopCK+qW8KjCla0iU2RpDow+QUDmH9DDt44=
+github.com/phpdave11/gofpdi v1.0.7/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
+github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58 h1:nlG4Wa5+minh3S9LVFtNoY+GVRiudA2e3EVfcCi3RCA=
+github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w=
+github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+golang.org/x/image v0.0.0-20190507092727-e4e5bf290fec h1:arXJwtMuk5vqI1NHX0UTnNw977rYk5Sl4jQqHj+hun4=
+golang.org/x/image v0.0.0-20190507092727-e4e5bf290fec/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

+ 446 - 0
vendor/github.com/jung-kurt/gofpdf/grid.go

@@ -0,0 +1,446 @@
+package gofpdf
+
+import (
+	"math"
+	"strconv"
+)
+
+func unused(args ...interface{}) {
+}
+
+// RGBType holds fields for red, green and blue color components (0..255)
+type RGBType struct {
+	R, G, B int
+}
+
+// RGBAType holds fields for red, green and blue color components (0..255) and
+// an alpha transparency value (0..1)
+type RGBAType struct {
+	R, G, B int
+	Alpha   float64
+}
+
+// StateType holds various commonly used drawing values for convenient
+// retrieval (StateGet()) and restore (Put) methods.
+type StateType struct {
+	clrDraw, clrText, clrFill RGBType
+	lineWd                    float64
+	fontSize                  float64
+	alpha                     float64
+	blendStr                  string
+	cellMargin                float64
+}
+
+// StateGet returns a variable that contains common state values.
+func StateGet(pdf *Fpdf) (st StateType) {
+	st.clrDraw.R, st.clrDraw.G, st.clrDraw.B = pdf.GetDrawColor()
+	st.clrFill.R, st.clrFill.G, st.clrFill.B = pdf.GetFillColor()
+	st.clrText.R, st.clrText.G, st.clrText.B = pdf.GetTextColor()
+	st.lineWd = pdf.GetLineWidth()
+	_, st.fontSize = pdf.GetFontSize()
+	st.alpha, st.blendStr = pdf.GetAlpha()
+	st.cellMargin = pdf.GetCellMargin()
+	return
+}
+
+// Put sets the common state values contained in the state structure
+// specified by st.
+func (st StateType) Put(pdf *Fpdf) {
+	pdf.SetDrawColor(st.clrDraw.R, st.clrDraw.G, st.clrDraw.B)
+	pdf.SetFillColor(st.clrFill.R, st.clrFill.G, st.clrFill.B)
+	pdf.SetTextColor(st.clrText.R, st.clrText.G, st.clrText.B)
+	pdf.SetLineWidth(st.lineWd)
+	pdf.SetFontUnitSize(st.fontSize)
+	pdf.SetAlpha(st.alpha, st.blendStr)
+	pdf.SetCellMargin(st.cellMargin)
+}
+
+// TickFormatFncType defines a callback for label drawing.
+type TickFormatFncType func(val float64, precision int) string
+
+// defaultFormatter returns the string form of val with precision decimal places.
+func defaultFormatter(val float64, precision int) string {
+	return strconv.FormatFloat(val, 'f', precision, 64)
+}
+
+// GridType assists with the generation of graphs. It allows the application to
+// work with logical data coordinates rather than page coordinates and assists
+// with the drawing of a background grid.
+type GridType struct {
+	// Chart coordinates in page units
+	x, y, w, h float64
+	// X, Y, Wd, Ht float64
+	// Slopes and intercepts scale data points to graph coordinates linearly
+	xm, xb, ym, yb float64
+	// Tickmarks
+	xTicks, yTicks []float64
+	// Labels are inside of graph boundary
+	XLabelIn, YLabelIn bool
+	// Labels on X-axis should be rotated
+	XLabelRotate bool
+	// Formatters; use nil to eliminate labels
+	XTickStr, YTickStr TickFormatFncType
+	// Subdivisions between tickmarks
+	XDiv, YDiv int
+	// Formatting precision
+	xPrecision, yPrecision int
+	// Line and label colors
+	ClrText, ClrMain, ClrSub RGBAType
+	// Line thickness
+	WdMain, WdSub float64
+	// Label height in points
+	TextSize float64
+}
+
+// linear returns the slope and y-intercept of the straight line joining the
+// two specified points. For scaling purposes, associate the arguments as
+// follows: x1: observed low value, y1: desired low value, x2: observed high
+// value, y2: desired high value.
+func linear(x1, y1, x2, y2 float64) (slope, intercept float64) {
+	if x2 != x1 {
+		slope = (y2 - y1) / (x2 - x1)
+		intercept = y2 - x2*slope
+	}
+	return
+}
+
+// linearTickmark returns the slope and intercept that will linearly map data
+// values (the range of which is specified by the tickmark slice tm) to page
+// values (the range of which is specified by lo and hi).
+func linearTickmark(tm []float64, lo, hi float64) (slope, intercept float64) {
+	ln := len(tm)
+	if ln > 0 {
+		slope, intercept = linear(tm[0], lo, tm[ln-1], hi)
+	}
+	return
+}
+
+// NewGrid returns a variable of type GridType that is initialized to draw on a
+// rectangle of width w and height h with the upper left corner positioned at
+// point (x, y). The coordinates are in page units, that is, the same as those
+// specified in New().
+//
+// The returned variable is initialized with a very simple default tickmark
+// layout that ranges from 0 to 1 in both axes. Prior to calling Grid(), the
+// application may establish a more suitable tickmark layout by calling the
+// methods TickmarksContainX() and TickmarksContainY(). These methods bound the
+// data range with appropriate boundaries and divisions. Alternatively, if the
+// exact extent and divisions of the tickmark layout are known, the methods
+// TickmarksExtentX() and TickmarksExtentY may be called instead.
+func NewGrid(x, y, w, h float64) (grid GridType) {
+	grid.x = x
+	grid.y = y
+	grid.w = w
+	grid.h = h
+	grid.TextSize = 7 // Points
+	grid.TickmarksExtentX(0, 1, 1)
+	grid.TickmarksExtentY(0, 1, 1)
+	grid.XLabelIn = false
+	grid.YLabelIn = false
+	grid.XLabelRotate = false
+	grid.XDiv = 10
+	grid.YDiv = 10
+	grid.ClrText = RGBAType{R: 0, G: 0, B: 0, Alpha: 1}
+	grid.ClrMain = RGBAType{R: 128, G: 160, B: 128, Alpha: 1}
+	grid.ClrSub = RGBAType{R: 192, G: 224, B: 192, Alpha: 1}
+	grid.WdMain = 0.1
+	grid.WdSub = 0.1
+	grid.YTickStr = defaultFormatter
+	grid.XTickStr = defaultFormatter
+	return
+}
+
+// WdAbs returns the absolute value of dataWd, specified in logical data units,
+// that has been converted to the unit of measure specified in New().
+func (g GridType) WdAbs(dataWd float64) float64 {
+	return math.Abs(g.xm * dataWd)
+}
+
+// Wd converts dataWd, specified in logical data units, to the unit of measure
+// specified in New().
+func (g GridType) Wd(dataWd float64) float64 {
+	return g.xm * dataWd
+}
+
+// XY converts dataX and dataY, specified in logical data units, to the X and Y
+// position on the current page.
+func (g GridType) XY(dataX, dataY float64) (x, y float64) {
+	return g.xm*dataX + g.xb, g.ym*dataY + g.yb
+}
+
+// Pos returns the point, in page units, indicated by the relative positions
+// xRel and yRel. These are values between 0 and 1. xRel specifies the relative
+// position between the grid's left and right edges. yRel specifies the
+// relative position between the grid's bottom and top edges.
+func (g GridType) Pos(xRel, yRel float64) (x, y float64) {
+	x = g.w*xRel + g.x
+	y = g.h*(1-yRel) + g.y
+	return
+}
+
+// X converts dataX, specified in logical data units, to the X position on the
+// current page.
+func (g GridType) X(dataX float64) float64 {
+	return g.xm*dataX + g.xb
+}
+
+// HtAbs returns the absolute value of dataHt, specified in logical data units,
+// that has been converted to the unit of measure specified in New().
+func (g GridType) HtAbs(dataHt float64) float64 {
+	return math.Abs(g.ym * dataHt)
+}
+
+// Ht converts dataHt, specified in logical data units, to the unit of measure
+// specified in New().
+func (g GridType) Ht(dataHt float64) float64 {
+	return g.ym * dataHt
+}
+
+// Y converts dataY, specified in logical data units, to the Y position on the
+// current page.
+func (g GridType) Y(dataY float64) float64 {
+	return g.ym*dataY + g.yb
+}
+
+// XRange returns the minimum and maximum values for the current tickmark
+// sequence. These correspond to the data values of the graph's left and right
+// edges.
+func (g GridType) XRange() (min, max float64) {
+	min = g.xTicks[0]
+	max = g.xTicks[len(g.xTicks)-1]
+	return
+}
+
+// YRange returns the minimum and maximum values for the current tickmark
+// sequence. These correspond to the data values of the graph's bottom and top
+// edges.
+func (g GridType) YRange() (min, max float64) {
+	min = g.yTicks[0]
+	max = g.yTicks[len(g.yTicks)-1]
+	return
+}
+
+// TickmarksContainX sets the tickmarks to be shown by Grid() in the horizontal
+// dimension. The argument min and max specify the minimum and maximum values
+// to be contained within the grid. The tickmark values that are generated are
+// suitable for general purpose graphs.
+//
+// See TickmarkExtentX() for an alternative to this method to be used when the
+// exact values of the tickmarks are to be set by the application.
+func (g *GridType) TickmarksContainX(min, max float64) {
+	g.xTicks, g.xPrecision = Tickmarks(min, max)
+	g.xm, g.xb = linearTickmark(g.xTicks, g.x, g.x+g.w)
+}
+
+// TickmarksContainY sets the tickmarks to be shown by Grid() in the vertical
+// dimension. The argument min and max specify the minimum and maximum values
+// to be contained within the grid. The tickmark values that are generated are
+// suitable for general purpose graphs.
+//
+// See TickmarkExtentY() for an alternative to this method to be used when the
+// exact values of the tickmarks are to be set by the application.
+func (g *GridType) TickmarksContainY(min, max float64) {
+	g.yTicks, g.yPrecision = Tickmarks(min, max)
+	g.ym, g.yb = linearTickmark(g.yTicks, g.y+g.h, g.y)
+}
+
+func extent(min, div float64, count int) (tm []float64, precision int) {
+	tm = make([]float64, count+1)
+	for j := 0; j <= count; j++ {
+		tm[j] = min
+		min += div
+	}
+	precision = TickmarkPrecision(div)
+	return
+}
+
+// TickmarksExtentX sets the tickmarks to be shown by Grid() in the horizontal
+// dimension. count specifies number of major tickmark subdivisions to be
+// graphed. min specifies the leftmost data value. div specifies, in data
+// units, the extent of each major tickmark subdivision.
+//
+// See TickmarkContainX() for an alternative to this method to be used when
+// viewer-friendly tickmarks are to be determined automatically.
+func (g *GridType) TickmarksExtentX(min, div float64, count int) {
+	g.xTicks, g.xPrecision = extent(min, div, count)
+	g.xm, g.xb = linearTickmark(g.xTicks, g.x, g.x+g.w)
+}
+
+// TickmarksExtentY sets the tickmarks to be shown by Grid() in the vertical
+// dimension. count specifies number of major tickmark subdivisions to be
+// graphed. min specifies the bottommost data value. div specifies, in data
+// units, the extent of each major tickmark subdivision.
+//
+// See TickmarkContainY() for an alternative to this method to be used when
+// viewer-friendly tickmarks are to be determined automatically.
+func (g *GridType) TickmarksExtentY(min, div float64, count int) {
+	g.yTicks, g.yPrecision = extent(min, div, count)
+	g.ym, g.yb = linearTickmark(g.yTicks, g.y+g.h, g.y)
+}
+
+// func (g *GridType) SetXExtent(dataLf, paperLf, dataRt, paperRt float64) {
+// 	g.xm, g.xb = linear(dataLf, paperLf, dataRt, paperRt)
+// }
+
+// func (g *GridType) SetYExtent(dataTp, paperTp, dataBt, paperBt float64) {
+// 	g.ym, g.yb = linear(dataTp, paperTp, dataBt, paperBt)
+// }
+
+func lineAttr(pdf *Fpdf, clr RGBAType, lineWd float64) {
+	pdf.SetLineWidth(lineWd)
+	pdf.SetAlpha(clr.Alpha, "Normal")
+	pdf.SetDrawColor(clr.R, clr.G, clr.B)
+}
+
+// Grid generates a graph-paperlike set of grid lines on the current page.
+func (g GridType) Grid(pdf *Fpdf) {
+	var st StateType
+	var yLen, xLen int
+	var textSz, halfTextSz, yMin, yMax, xMin, xMax, yDiv, xDiv float64
+	var str string
+	var strOfs, strWd, tp, bt, lf, rt, drawX, drawY float64
+
+	xLen = len(g.xTicks)
+	yLen = len(g.yTicks)
+	if xLen > 1 && yLen > 1 {
+
+		st = StateGet(pdf)
+
+		line := func(x1, y1, x2, y2 float64, heavy bool) {
+			if heavy {
+				lineAttr(pdf, g.ClrMain, g.WdMain)
+			} else {
+				lineAttr(pdf, g.ClrSub, g.WdSub)
+			}
+			pdf.Line(x1, y1, x2, y2)
+		}
+
+		textSz = pdf.PointToUnitConvert(g.TextSize)
+		halfTextSz = textSz / 2
+
+		pdf.SetAutoPageBreak(false, 0)
+		pdf.SetFontUnitSize(textSz)
+		strOfs = pdf.GetStringWidth("0")
+		pdf.SetFillColor(255, 255, 255)
+		pdf.SetCellMargin(0)
+
+		xMin = g.xTicks[0]
+		xMax = g.xTicks[xLen-1]
+
+		yMin = g.yTicks[0]
+		yMax = g.yTicks[yLen-1]
+
+		lf = g.X(xMin)
+		rt = g.X(xMax)
+		bt = g.Y(yMin)
+		tp = g.Y(yMax)
+
+		// Verticals along X axis
+		xDiv = g.xTicks[1] - g.xTicks[0]
+		if g.XDiv > 0 {
+			xDiv = xDiv / float64(g.XDiv)
+		}
+		xDiv = g.Wd(xDiv)
+		for j, x := range g.xTicks {
+			drawX = g.X(x)
+			line(drawX, tp, drawX, bt, true)
+			if j < xLen-1 {
+				for k := 1; k < g.XDiv; k++ {
+					drawX += xDiv
+					line(drawX, tp, drawX, bt, false)
+				}
+			}
+		}
+
+		// Horizontals along Y axis
+		yDiv = g.yTicks[1] - g.yTicks[0]
+		if g.YDiv > 0 {
+			yDiv = yDiv / float64(g.YDiv)
+		}
+		yDiv = g.Ht(yDiv)
+		for j, y := range g.yTicks {
+			drawY = g.Y(y)
+			line(lf, drawY, rt, drawY, true)
+			if j < yLen-1 {
+				for k := 1; k < g.YDiv; k++ {
+					drawY += yDiv
+					line(lf, drawY, rt, drawY, false)
+				}
+			}
+		}
+
+		// X labels
+		if g.XTickStr != nil {
+			drawY = bt
+			for _, x := range g.xTicks {
+				str = g.XTickStr(x, g.xPrecision)
+				strWd = pdf.GetStringWidth(str)
+				drawX = g.X(x)
+				if g.XLabelRotate {
+					pdf.TransformBegin()
+					pdf.TransformRotate(90, drawX, drawY)
+					if g.XLabelIn {
+						pdf.SetXY(drawX+strOfs, drawY-halfTextSz)
+					} else {
+						pdf.SetXY(drawX-strOfs-strWd, drawY-halfTextSz)
+					}
+					pdf.CellFormat(strWd, textSz, str, "", 0, "L", true, 0, "")
+					pdf.TransformEnd()
+				} else {
+					drawX -= strWd / 2.0
+					if g.XLabelIn {
+						pdf.SetXY(drawX, drawY-textSz-strOfs)
+					} else {
+						pdf.SetXY(drawX, drawY+strOfs)
+					}
+					pdf.CellFormat(strWd, textSz, str, "", 0, "L", true, 0, "")
+				}
+			}
+		}
+
+		// Y labels
+		if g.YTickStr != nil {
+			drawX = lf
+			for _, y := range g.yTicks {
+				// str = strconv.FormatFloat(y, 'f', g.yPrecision, 64)
+				str = g.YTickStr(y, g.yPrecision)
+				strWd = pdf.GetStringWidth(str)
+				if g.YLabelIn {
+					pdf.SetXY(drawX+strOfs, g.Y(y)-halfTextSz)
+				} else {
+					pdf.SetXY(lf-strOfs-strWd, g.Y(y)-halfTextSz)
+				}
+				pdf.CellFormat(strWd, textSz, str, "", 0, "L", true, 0, "")
+			}
+		}
+
+		// Restore drawing attributes
+		st.Put(pdf)
+
+	}
+
+}
+
+// Plot plots a series of count line segments from xMin to xMax. It repeatedly
+// calls fnc(x) to retrieve the y value associate with x. The currently
+// selected line drawing attributes are used.
+func (g GridType) Plot(pdf *Fpdf, xMin, xMax float64, count int, fnc func(x float64) (y float64)) {
+	if count > 0 {
+		var x, delta, drawX0, drawY0, drawX1, drawY1 float64
+		delta = (xMax - xMin) / float64(count)
+		x = xMin
+		for j := 0; j <= count; j++ {
+			if j == 0 {
+				drawX1 = g.X(x)
+				drawY1 = g.Y(fnc(x))
+			} else {
+				pdf.Line(drawX0, drawY0, drawX1, drawY1)
+			}
+			x += delta
+			drawX0 = drawX1
+			drawY0 = drawY1
+			drawX1 = g.X(x)
+			drawY1 = g.Y(fnc(x))
+		}
+	}
+}

+ 220 - 0
vendor/github.com/jung-kurt/gofpdf/htmlbasic.go

@@ -0,0 +1,220 @@
+/*
+ * Copyright (c) 2014 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 (
+	"regexp"
+	"strings"
+)
+
+// HTMLBasicSegmentType defines a segment of literal text in which the current
+// attributes do not vary, or an open tag or a close tag.
+type HTMLBasicSegmentType struct {
+	Cat  byte              // 'O' open tag, 'C' close tag, 'T' text
+	Str  string            // Literal text unchanged, tags are lower case
+	Attr map[string]string // Attribute keys are lower case
+}
+
+// HTMLBasicTokenize returns a list of HTML tags and literal elements. This is
+// done with regular expressions, so the result is only marginally better than
+// useless.
+func HTMLBasicTokenize(htmlStr string) (list []HTMLBasicSegmentType) {
+	// This routine is adapted from http://www.fpdf.org/
+	list = make([]HTMLBasicSegmentType, 0, 16)
+	htmlStr = strings.Replace(htmlStr, "\n", " ", -1)
+	htmlStr = strings.Replace(htmlStr, "\r", "", -1)
+	tagRe, _ := regexp.Compile(`(?U)<.*>`)
+	attrRe, _ := regexp.Compile(`([^=]+)=["']?([^"']+)`)
+	capList := tagRe.FindAllStringIndex(htmlStr, -1)
+	if capList != nil {
+		var seg HTMLBasicSegmentType
+		var parts []string
+		pos := 0
+		for _, cap := range capList {
+			if pos < cap[0] {
+				seg.Cat = 'T'
+				seg.Str = htmlStr[pos:cap[0]]
+				seg.Attr = nil
+				list = append(list, seg)
+			}
+			if htmlStr[cap[0]+1] == '/' {
+				seg.Cat = 'C'
+				seg.Str = strings.ToLower(htmlStr[cap[0]+2 : cap[1]-1])
+				seg.Attr = nil
+				list = append(list, seg)
+			} else {
+				// Extract attributes
+				parts = strings.Split(htmlStr[cap[0]+1:cap[1]-1], " ")
+				if len(parts) > 0 {
+					for j, part := range parts {
+						if j == 0 {
+							seg.Cat = 'O'
+							seg.Str = strings.ToLower(parts[0])
+							seg.Attr = make(map[string]string)
+						} else {
+							attrList := attrRe.FindAllStringSubmatch(part, -1)
+							if attrList != nil {
+								for _, attr := range attrList {
+									seg.Attr[strings.ToLower(attr[1])] = attr[2]
+								}
+							}
+						}
+					}
+					list = append(list, seg)
+				}
+			}
+			pos = cap[1]
+		}
+		if len(htmlStr) > pos {
+			seg.Cat = 'T'
+			seg.Str = htmlStr[pos:]
+			seg.Attr = nil
+			list = append(list, seg)
+		}
+	} else {
+		list = append(list, HTMLBasicSegmentType{Cat: 'T', Str: htmlStr, Attr: nil})
+	}
+	return
+}
+
+// HTMLBasicType is used for rendering a very basic subset of HTML. It supports
+// only hyperlinks and bold, italic and underscore attributes. In the Link
+// structure, the ClrR, ClrG and ClrB fields (0 through 255) define the color
+// of hyperlinks. The Bold, Italic and Underscore values define the hyperlink
+// style.
+type HTMLBasicType struct {
+	pdf  *Fpdf
+	Link struct {
+		ClrR, ClrG, ClrB         int
+		Bold, Italic, Underscore bool
+	}
+}
+
+// HTMLBasicNew returns an instance that facilitates writing basic HTML in the
+// specified PDF file.
+func (f *Fpdf) HTMLBasicNew() (html HTMLBasicType) {
+	html.pdf = f
+	html.Link.ClrR, html.Link.ClrG, html.Link.ClrB = 0, 0, 128
+	html.Link.Bold, html.Link.Italic, html.Link.Underscore = false, false, true
+	return
+}
+
+// Write prints text from the current position using the currently selected
+// font. See HTMLBasicNew() to create a receiver that is associated with the
+// PDF document instance. The text can be encoded with a basic subset of HTML
+// that includes hyperlinks and tags for italic (I), bold (B), underscore
+// (U) and center (CENTER) attributes. When the right margin is reached a line
+// break occurs and text continues from the left margin. Upon method exit, the
+// current position is left at the end of the text.
+//
+// lineHt indicates the line height in the unit of measure specified in New().
+func (html *HTMLBasicType) Write(lineHt float64, htmlStr string) {
+	var boldLvl, italicLvl, underscoreLvl, linkBold, linkItalic, linkUnderscore int
+	var textR, textG, textB = html.pdf.GetTextColor()
+	var hrefStr string
+	if html.Link.Bold {
+		linkBold = 1
+	}
+	if html.Link.Italic {
+		linkItalic = 1
+	}
+	if html.Link.Underscore {
+		linkUnderscore = 1
+	}
+	setStyle := func(boldAdj, italicAdj, underscoreAdj int) {
+		styleStr := ""
+		boldLvl += boldAdj
+		if boldLvl > 0 {
+			styleStr += "B"
+		}
+		italicLvl += italicAdj
+		if italicLvl > 0 {
+			styleStr += "I"
+		}
+		underscoreLvl += underscoreAdj
+		if underscoreLvl > 0 {
+			styleStr += "U"
+		}
+		html.pdf.SetFont("", styleStr, 0)
+	}
+	putLink := func(urlStr, txtStr string) {
+		// Put a hyperlink
+		html.pdf.SetTextColor(html.Link.ClrR, html.Link.ClrG, html.Link.ClrB)
+		setStyle(linkBold, linkItalic, linkUnderscore)
+		html.pdf.WriteLinkString(lineHt, txtStr, urlStr)
+		setStyle(-linkBold, -linkItalic, -linkUnderscore)
+		html.pdf.SetTextColor(textR, textG, textB)
+	}
+	list := HTMLBasicTokenize(htmlStr)
+	var ok bool
+	alignStr := "L"
+	for _, el := range list {
+		switch el.Cat {
+		case 'T':
+			if len(hrefStr) > 0 {
+				putLink(hrefStr, el.Str)
+				hrefStr = ""
+			} else {
+				if alignStr == "C" || alignStr == "R" {
+					html.pdf.WriteAligned(0, lineHt, el.Str, alignStr)
+				} else {
+					html.pdf.Write(lineHt, el.Str)
+				}
+			}
+		case 'O':
+			switch el.Str {
+			case "b":
+				setStyle(1, 0, 0)
+			case "i":
+				setStyle(0, 1, 0)
+			case "u":
+				setStyle(0, 0, 1)
+			case "br":
+				html.pdf.Ln(lineHt)
+			case "center":
+				html.pdf.Ln(lineHt)
+				alignStr = "C"
+			case "right":
+				html.pdf.Ln(lineHt)
+				alignStr = "R"
+			case "left":
+				html.pdf.Ln(lineHt)
+				alignStr = "L"
+			case "a":
+				hrefStr, ok = el.Attr["href"]
+				if !ok {
+					hrefStr = ""
+				}
+			}
+		case 'C':
+			switch el.Str {
+			case "b":
+				setStyle(-1, 0, 0)
+			case "i":
+				setStyle(0, -1, 0)
+			case "u":
+				setStyle(0, 0, -1)
+			case "center":
+				html.pdf.Ln(lineHt)
+				alignStr = "L"
+			case "right":
+				html.pdf.Ln(lineHt)
+				alignStr = "L"
+			}
+		}
+	}
+}

+ 82 - 0
vendor/github.com/jung-kurt/gofpdf/label.go

@@ -0,0 +1,82 @@
+package gofpdf
+
+// Adapted from Nice Numbers for Graph Labels by Paul Heckbert from "Graphics
+// Gems", Academic Press, 1990
+
+// Paul Heckbert	2 Dec 88
+
+// https://github.com/erich666/GraphicsGems
+
+// LICENSE
+
+// This code repository predates the concept of Open Source, and predates most
+// licenses along such lines. As such, the official license truly is:
+
+// EULA: The Graphics Gems code is copyright-protected. In other words, you
+// cannot claim the text of the code as your own and resell it. Using the code
+// is permitted in any program, product, or library, non-commercial or
+// commercial. Giving credit is not required, though is a nice gesture. The
+// code comes as-is, and if there are any flaws or problems with any Gems code,
+// nobody involved with Gems - authors, editors, publishers, or webmasters -
+// are to be held responsible. Basically, don't be a jerk, and remember that
+// anything free comes with no guarantee.
+
+import (
+	"math"
+)
+
+// niceNum returns a "nice" number approximately equal to x. The number is
+// rounded if round is true, converted to its ceiling otherwise.
+func niceNum(val float64, round bool) float64 {
+	var nf float64
+
+	exp := int(math.Floor(math.Log10(val)))
+	f := val / math.Pow10(exp)
+	if round {
+		switch {
+		case f < 1.5:
+			nf = 1
+		case f < 3.0:
+			nf = 2
+		case f < 7.0:
+			nf = 5
+		default:
+			nf = 10
+		}
+	} else {
+		switch {
+		case f <= 1:
+			nf = 1
+		case f <= 2.0:
+			nf = 2
+		case f <= 5.0:
+			nf = 5
+		default:
+			nf = 10
+		}
+	}
+	return nf * math.Pow10(exp)
+}
+
+// TickmarkPrecision returns an appropriate precision value for label
+// formatting.
+func TickmarkPrecision(div float64) int {
+	return int(math.Max(-math.Floor(math.Log10(div)), 0))
+}
+
+// Tickmarks returns a slice of tickmarks appropriate for a chart axis and an
+// appropriate precision for formatting purposes. The values min and max will
+// be contained within the tickmark range.
+func Tickmarks(min, max float64) (list []float64, precision int) {
+	if max > min {
+		spread := niceNum(max-min, false)
+		d := niceNum((spread / 4), true)
+		graphMin := math.Floor(min/d) * d
+		graphMax := math.Ceil(max/d) * d
+		precision = TickmarkPrecision(d)
+		for x := graphMin; x < graphMax+0.5*d; x += d {
+			list = append(list, x)
+		}
+	}
+	return
+}

+ 121 - 0
vendor/github.com/jung-kurt/gofpdf/layer.go

@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2014 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
+
+// Routines in this file are translated from
+// http://www.fpdf.org/en/script/script97.php
+
+type layerType struct {
+	name    string
+	visible bool
+	objNum  int // object number
+}
+
+type layerRecType struct {
+	list          []layerType
+	currentLayer  int
+	openLayerPane bool
+}
+
+func (f *Fpdf) layerInit() {
+	f.layer.list = make([]layerType, 0)
+	f.layer.currentLayer = -1
+	f.layer.openLayerPane = false
+}
+
+// AddLayer defines a layer that can be shown or hidden when the document is
+// displayed. name specifies the layer name that the document reader will
+// display in the layer list. visible specifies whether the layer will be
+// initially visible. The return value is an integer ID that is used in a call
+// to BeginLayer().
+func (f *Fpdf) AddLayer(name string, visible bool) (layerID int) {
+	layerID = len(f.layer.list)
+	f.layer.list = append(f.layer.list, layerType{name: name, visible: visible})
+	return
+}
+
+// BeginLayer is called to begin adding content to the specified layer. All
+// content added to the page between a call to BeginLayer and a call to
+// EndLayer is added to the layer specified by id. See AddLayer for more
+// details.
+func (f *Fpdf) BeginLayer(id int) {
+	f.EndLayer()
+	if id >= 0 && id < len(f.layer.list) {
+		f.outf("/OC /OC%d BDC", id)
+		f.layer.currentLayer = id
+	}
+}
+
+// EndLayer is called to stop adding content to the currently active layer. See
+// BeginLayer for more details.
+func (f *Fpdf) EndLayer() {
+	if f.layer.currentLayer >= 0 {
+		f.out("EMC")
+		f.layer.currentLayer = -1
+	}
+}
+
+// OpenLayerPane advises the document reader to open the layer pane when the
+// document is initially displayed.
+func (f *Fpdf) OpenLayerPane() {
+	f.layer.openLayerPane = true
+}
+
+func (f *Fpdf) layerEndDoc() {
+	if len(f.layer.list) > 0 {
+		if f.pdfVersion < "1.5" {
+			f.pdfVersion = "1.5"
+		}
+	}
+}
+
+func (f *Fpdf) layerPutLayers() {
+	for j, l := range f.layer.list {
+		f.newobj()
+		f.layer.list[j].objNum = f.n
+		f.outf("<</Type /OCG /Name %s>>", f.textstring(utf8toutf16(l.name)))
+		f.out("endobj")
+	}
+}
+
+func (f *Fpdf) layerPutResourceDict() {
+	if len(f.layer.list) > 0 {
+		f.out("/Properties <<")
+		for j, layer := range f.layer.list {
+			f.outf("/OC%d %d 0 R", j, layer.objNum)
+		}
+		f.out(">>")
+	}
+
+}
+
+func (f *Fpdf) layerPutCatalog() {
+	if len(f.layer.list) > 0 {
+		onStr := ""
+		offStr := ""
+		for _, layer := range f.layer.list {
+			onStr += sprintf("%d 0 R ", layer.objNum)
+			if !layer.visible {
+				offStr += sprintf("%d 0 R ", layer.objNum)
+			}
+		}
+		f.outf("/OCProperties <</OCGs [%s] /D <</OFF [%s] /Order [%s]>>>>", onStr, offStr, onStr)
+		if f.layer.openLayerPane {
+			f.out("/PageMode /UseOC")
+		}
+	}
+}

+ 213 - 0
vendor/github.com/jung-kurt/gofpdf/png.go

@@ -0,0 +1,213 @@
+/*
+ * Copyright (c) 2013-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 gofpdf
+
+import (
+	"bytes"
+	"fmt"
+	"strings"
+)
+
+func (f *Fpdf) pngColorSpace(ct byte) (colspace string, colorVal int) {
+	colorVal = 1
+	switch ct {
+	case 0, 4:
+		colspace = "DeviceGray"
+	case 2, 6:
+		colspace = "DeviceRGB"
+		colorVal = 3
+	case 3:
+		colspace = "Indexed"
+	default:
+		f.err = fmt.Errorf("unknown color type in PNG buffer: %d", ct)
+	}
+	return
+}
+
+func (f *Fpdf) parsepngstream(buf *bytes.Buffer, readdpi bool) (info *ImageInfoType) {
+	info = f.newImageInfo()
+	// 	Check signature
+	if string(buf.Next(8)) != "\x89PNG\x0d\x0a\x1a\x0a" {
+		f.err = fmt.Errorf("not a PNG buffer")
+		return
+	}
+	// Read header chunk
+	_ = buf.Next(4)
+	if string(buf.Next(4)) != "IHDR" {
+		f.err = fmt.Errorf("incorrect PNG buffer")
+		return
+	}
+	w := f.readBeInt32(buf)
+	h := f.readBeInt32(buf)
+	bpc := f.readByte(buf)
+	if bpc > 8 {
+		f.err = fmt.Errorf("16-bit depth not supported in PNG file")
+	}
+	ct := f.readByte(buf)
+	var colspace string
+	var colorVal int
+	colspace, colorVal = f.pngColorSpace(ct)
+	if f.err != nil {
+		return
+	}
+	if f.readByte(buf) != 0 {
+		f.err = fmt.Errorf("'unknown compression method in PNG buffer")
+		return
+	}
+	if f.readByte(buf) != 0 {
+		f.err = fmt.Errorf("'unknown filter method in PNG buffer")
+		return
+	}
+	if f.readByte(buf) != 0 {
+		f.err = fmt.Errorf("interlacing not supported in PNG buffer")
+		return
+	}
+	_ = buf.Next(4)
+	dp := sprintf("/Predictor 15 /Colors %d /BitsPerComponent %d /Columns %d", colorVal, bpc, w)
+	// Scan chunks looking for palette, transparency and image data
+	pal := make([]byte, 0, 32)
+	var trns []int
+	data := make([]byte, 0, 32)
+	loop := true
+	for loop {
+		n := int(f.readBeInt32(buf))
+		// dbg("Loop [%d]", n)
+		switch string(buf.Next(4)) {
+		case "PLTE":
+			// dbg("PLTE")
+			// Read palette
+			pal = buf.Next(n)
+			_ = buf.Next(4)
+		case "tRNS":
+			// dbg("tRNS")
+			// Read transparency info
+			t := buf.Next(n)
+			switch ct {
+			case 0:
+				trns = []int{int(t[1])} // ord(substr($t,1,1)));
+			case 2:
+				trns = []int{int(t[1]), int(t[3]), int(t[5])} // array(ord(substr($t,1,1)), ord(substr($t,3,1)), ord(substr($t,5,1)));
+			default:
+				pos := strings.Index(string(t), "\x00")
+				if pos >= 0 {
+					trns = []int{pos} // array($pos);
+				}
+			}
+			_ = buf.Next(4)
+		case "IDAT":
+			// dbg("IDAT")
+			// Read image data block
+			data = append(data, buf.Next(n)...)
+			_ = buf.Next(4)
+		case "IEND":
+			// dbg("IEND")
+			loop = false
+		case "pHYs":
+			// dbg("pHYs")
+			// png files theoretically support different x/y dpi
+			// but we ignore files like this
+			// but if they're the same then we can stamp our info
+			// object with it
+			x := int(f.readBeInt32(buf))
+			y := int(f.readBeInt32(buf))
+			units := buf.Next(1)[0]
+			// fmt.Printf("got a pHYs block, x=%d, y=%d, u=%d, readdpi=%t\n",
+			// x, y, int(units), readdpi)
+			// only modify the info block if the user wants us to
+			if x == y && readdpi {
+				switch units {
+				// if units is 1 then measurement is px/meter
+				case 1:
+					info.dpi = float64(x) / 39.3701 // inches per meter
+				default:
+					info.dpi = float64(x)
+				}
+			}
+			_ = buf.Next(4)
+		default:
+			// dbg("default")
+			_ = buf.Next(n + 4)
+		}
+		if loop {
+			loop = n > 0
+		}
+	}
+	if colspace == "Indexed" && len(pal) == 0 {
+		f.err = fmt.Errorf("missing palette in PNG buffer")
+	}
+	info.w = float64(w)
+	info.h = float64(h)
+	info.cs = colspace
+	info.bpc = int(bpc)
+	info.f = "FlateDecode"
+	info.dp = dp
+	info.pal = pal
+	info.trns = trns
+	// dbg("ct [%d]", ct)
+	if ct >= 4 {
+		// Separate alpha and color channels
+		var err error
+		data, err = sliceUncompress(data)
+		if err != nil {
+			f.err = err
+			return
+		}
+		var color, alpha bytes.Buffer
+		if ct == 4 {
+			// Gray image
+			width := int(w)
+			height := int(h)
+			length := 2 * width
+			var pos, elPos int
+			for i := 0; i < height; i++ {
+				pos = (1 + length) * i
+				color.WriteByte(data[pos])
+				alpha.WriteByte(data[pos])
+				elPos = pos + 1
+				for k := 0; k < width; k++ {
+					color.WriteByte(data[elPos])
+					alpha.WriteByte(data[elPos+1])
+					elPos += 2
+				}
+			}
+		} else {
+			// RGB image
+			width := int(w)
+			height := int(h)
+			length := 4 * width
+			var pos, elPos int
+			for i := 0; i < height; i++ {
+				pos = (1 + length) * i
+				color.WriteByte(data[pos])
+				alpha.WriteByte(data[pos])
+				elPos = pos + 1
+				for k := 0; k < width; k++ {
+					color.Write(data[elPos : elPos+3])
+					alpha.WriteByte(data[elPos+3])
+					elPos += 4
+				}
+			}
+		}
+		data = sliceCompress(color.Bytes())
+		info.smask = sliceCompress(alpha.Bytes())
+		if f.pdfVersion < "1.4" {
+			f.pdfVersion = "1.4"
+		}
+	}
+	info.data = data
+	return
+}

+ 114 - 0
vendor/github.com/jung-kurt/gofpdf/protect.go

@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2013-2014 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.
+ */
+
+// PDF protection is adapted from the work of Klemen VODOPIVEC for the fpdf
+// product.
+
+package gofpdf
+
+import (
+	"crypto/md5"
+	"crypto/rc4"
+	"encoding/binary"
+	"math/rand"
+)
+
+// Advisory bitflag constants that control document activities
+const (
+	CnProtectPrint      = 4
+	CnProtectModify     = 8
+	CnProtectCopy       = 16
+	CnProtectAnnotForms = 32
+)
+
+type protectType struct {
+	encrypted     bool
+	uValue        []byte
+	oValue        []byte
+	pValue        int
+	padding       []byte
+	encryptionKey []byte
+	objNum        int
+	rc4cipher     *rc4.Cipher
+	rc4n          uint32 // Object number associated with rc4 cipher
+}
+
+func (p *protectType) rc4(n uint32, buf *[]byte) {
+	if p.rc4cipher == nil || p.rc4n != n {
+		p.rc4cipher, _ = rc4.NewCipher(p.objectKey(n))
+		p.rc4n = n
+	}
+	p.rc4cipher.XORKeyStream(*buf, *buf)
+}
+
+func (p *protectType) objectKey(n uint32) []byte {
+	var nbuf, b []byte
+	nbuf = make([]byte, 8, 8)
+	binary.LittleEndian.PutUint32(nbuf, n)
+	b = append(b, p.encryptionKey...)
+	b = append(b, nbuf[0], nbuf[1], nbuf[2], 0, 0)
+	s := md5.Sum(b)
+	return s[0:10]
+}
+
+func oValueGen(userPass, ownerPass []byte) (v []byte) {
+	var c *rc4.Cipher
+	tmp := md5.Sum(ownerPass)
+	c, _ = rc4.NewCipher(tmp[0:5])
+	size := len(userPass)
+	v = make([]byte, size, size)
+	c.XORKeyStream(v, userPass)
+	return
+}
+
+func (p *protectType) uValueGen() (v []byte) {
+	var c *rc4.Cipher
+	c, _ = rc4.NewCipher(p.encryptionKey)
+	size := len(p.padding)
+	v = make([]byte, size, size)
+	c.XORKeyStream(v, p.padding)
+	return
+}
+
+func (p *protectType) setProtection(privFlag byte, userPassStr, ownerPassStr string) {
+	privFlag = 192 | (privFlag & (CnProtectCopy | CnProtectModify | CnProtectPrint | CnProtectAnnotForms))
+	p.padding = []byte{
+		0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41,
+		0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08,
+		0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80,
+		0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A,
+	}
+	userPass := []byte(userPassStr)
+	var ownerPass []byte
+	if ownerPassStr == "" {
+		ownerPass = make([]byte, 8, 8)
+		binary.LittleEndian.PutUint64(ownerPass, uint64(rand.Int63()))
+	} else {
+		ownerPass = []byte(ownerPassStr)
+	}
+	userPass = append(userPass, p.padding...)[0:32]
+	ownerPass = append(ownerPass, p.padding...)[0:32]
+	p.encrypted = true
+	p.oValue = oValueGen(userPass, ownerPass)
+	var buf []byte
+	buf = append(buf, userPass...)
+	buf = append(buf, p.oValue...)
+	buf = append(buf, privFlag, 0xff, 0xff, 0xff)
+	sum := md5.Sum(buf)
+	p.encryptionKey = sum[0:5]
+	p.uValue = p.uValueGen()
+	p.pValue = -(int(privFlag^255) + 1)
+}

+ 53 - 0
vendor/github.com/jung-kurt/gofpdf/splittext.go

@@ -0,0 +1,53 @@
+package gofpdf
+
+import (
+	"math"
+	//	"strings"
+	"unicode"
+)
+
+// SplitText splits UTF-8 encoded text into several lines using the current
+// font. Each line has its length limited to a maximum width given by w. This
+// function can be used to determine the total height of wrapped text for
+// vertical placement purposes.
+func (f *Fpdf) SplitText(txt string, w float64) (lines []string) {
+	cw := f.currentFont.Cw
+	wmax := int(math.Ceil((w - 2*f.cMargin) * 1000 / f.fontSize))
+	s := []rune(txt) // Return slice of UTF-8 runes
+	nb := len(s)
+	for nb > 0 && s[nb-1] == '\n' {
+		nb--
+	}
+	s = s[0:nb]
+	sep := -1
+	i := 0
+	j := 0
+	l := 0
+	for i < nb {
+		c := s[i]
+		l += cw[c]
+		if unicode.IsSpace(c) || isChinese(c) {
+			sep = i
+		}
+		if c == '\n' || l > wmax {
+			if sep == -1 {
+				if i == j {
+					i++
+				}
+				sep = i
+			} else {
+				i = sep + 1
+			}
+			lines = append(lines, string(s[j:sep]))
+			sep = -1
+			j = i
+			l = 0
+		} else {
+			i++
+		}
+	}
+	if i != j {
+		lines = append(lines, string(s[j:i]))
+	}
+	return lines
+}

+ 184 - 0
vendor/github.com/jung-kurt/gofpdf/spotcolor.go

@@ -0,0 +1,184 @@
+// Copyright (c) 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.
+
+// Adapted from http://www.fpdf.org/en/script/script89.php by Olivier PLATHEY
+
+package gofpdf
+
+import (
+	"fmt"
+	"strings"
+)
+
+func byteBound(v byte) byte {
+	if v > 100 {
+		return 100
+	}
+	return v
+}
+
+// AddSpotColor adds an ink-based CMYK color to the gofpdf instance and
+// associates it with the specified name. The individual components specify
+// percentages ranging from 0 to 100. Values above this are quietly capped to
+// 100. An error occurs if the specified name is already associated with a
+// color.
+func (f *Fpdf) AddSpotColor(nameStr string, c, m, y, k byte) {
+	if f.err == nil {
+		_, ok := f.spotColorMap[nameStr]
+		if !ok {
+			id := len(f.spotColorMap) + 1
+			f.spotColorMap[nameStr] = spotColorType{
+				id: id,
+				val: cmykColorType{
+					c: byteBound(c),
+					m: byteBound(m),
+					y: byteBound(y),
+					k: byteBound(k),
+				},
+			}
+		} else {
+			f.err = fmt.Errorf("name \"%s\" is already associated with a spot color", nameStr)
+		}
+	}
+}
+
+func (f *Fpdf) getSpotColor(nameStr string) (clr spotColorType, ok bool) {
+	if f.err == nil {
+		clr, ok = f.spotColorMap[nameStr]
+		if !ok {
+			f.err = fmt.Errorf("spot color name \"%s\" is not registered", nameStr)
+		}
+	}
+	return
+}
+
+// SetDrawSpotColor sets the current draw color to the spot color associated
+// with nameStr. An error occurs if the name is not associated with a color.
+// The value for tint ranges from 0 (no intensity) to 100 (full intensity). It
+// is quietly bounded to this range.
+func (f *Fpdf) SetDrawSpotColor(nameStr string, tint byte) {
+	var clr spotColorType
+	var ok bool
+
+	clr, ok = f.getSpotColor(nameStr)
+	if ok {
+		f.color.draw.mode = colorModeSpot
+		f.color.draw.spotStr = nameStr
+		f.color.draw.str = sprintf("/CS%d CS %.3f SCN", clr.id, float64(byteBound(tint))/100)
+		if f.page > 0 {
+			f.out(f.color.draw.str)
+		}
+	}
+}
+
+// SetFillSpotColor sets the current fill color to the spot color associated
+// with nameStr. An error occurs if the name is not associated with a color.
+// The value for tint ranges from 0 (no intensity) to 100 (full intensity). It
+// is quietly bounded to this range.
+func (f *Fpdf) SetFillSpotColor(nameStr string, tint byte) {
+	var clr spotColorType
+	var ok bool
+
+	clr, ok = f.getSpotColor(nameStr)
+	if ok {
+		f.color.fill.mode = colorModeSpot
+		f.color.fill.spotStr = nameStr
+		f.color.fill.str = sprintf("/CS%d cs %.3f scn", clr.id, float64(byteBound(tint))/100)
+		f.colorFlag = f.color.fill.str != f.color.text.str
+		if f.page > 0 {
+			f.out(f.color.fill.str)
+		}
+	}
+}
+
+// SetTextSpotColor sets the current text color to the spot color associated
+// with nameStr. An error occurs if the name is not associated with a color.
+// The value for tint ranges from 0 (no intensity) to 100 (full intensity). It
+// is quietly bounded to this range.
+func (f *Fpdf) SetTextSpotColor(nameStr string, tint byte) {
+	var clr spotColorType
+	var ok bool
+
+	clr, ok = f.getSpotColor(nameStr)
+	if ok {
+		f.color.text.mode = colorModeSpot
+		f.color.text.spotStr = nameStr
+		f.color.text.str = sprintf("/CS%d cs %.3f scn", clr.id, float64(byteBound(tint))/100)
+		f.colorFlag = f.color.text.str != f.color.text.str
+	}
+}
+
+func (f *Fpdf) returnSpotColor(clr colorType) (name string, c, m, y, k byte) {
+	var spotClr spotColorType
+	var ok bool
+
+	name = clr.spotStr
+	if name != "" {
+		spotClr, ok = f.getSpotColor(name)
+		if ok {
+			c = spotClr.val.c
+			m = spotClr.val.m
+			y = spotClr.val.y
+			k = spotClr.val.k
+		}
+	}
+	return
+}
+
+// GetDrawSpotColor returns the most recently used spot color information for
+// drawing. This will not be the current drawing color if some other color type
+// such as RGB is active. If no spot color has been set for drawing, zero
+// values are returned.
+func (f *Fpdf) GetDrawSpotColor() (name string, c, m, y, k byte) {
+	return f.returnSpotColor(f.color.draw)
+}
+
+// GetTextSpotColor returns the most recently used spot color information for
+// text output. This will not be the current text color if some other color
+// type such as RGB is active. If no spot color has been set for text, zero
+// values are returned.
+func (f *Fpdf) GetTextSpotColor() (name string, c, m, y, k byte) {
+	return f.returnSpotColor(f.color.text)
+}
+
+// GetFillSpotColor returns the most recently used spot color information for
+// fill output. This will not be the current fill color if some other color
+// type such as RGB is active. If no fill spot color has been set, zero values
+// are returned.
+func (f *Fpdf) GetFillSpotColor() (name string, c, m, y, k byte) {
+	return f.returnSpotColor(f.color.fill)
+}
+
+func (f *Fpdf) putSpotColors() {
+	for k, v := range f.spotColorMap {
+		f.newobj()
+		f.outf("[/Separation /%s", strings.Replace(k, " ", "#20", -1))
+		f.out("/DeviceCMYK <<")
+		f.out("/Range [0 1 0 1 0 1 0 1] /C0 [0 0 0 0] ")
+		f.outf("/C1 [%.3f %.3f %.3f %.3f] ", float64(v.val.c)/100, float64(v.val.m)/100,
+			float64(v.val.y)/100, float64(v.val.k)/100)
+		f.out("/FunctionType 2 /Domain [0 1] /N 1>>]")
+		f.out("endobj")
+		v.objID = f.n
+		f.spotColorMap[k] = v
+	}
+}
+
+func (f *Fpdf) spotColorPutResourceDict() {
+	f.out("/ColorSpace <<")
+	for _, clr := range f.spotColorMap {
+		f.outf("/CS%d %d 0 R", clr.id, clr.objID)
+	}
+	f.out(">>")
+}

+ 35 - 0
vendor/github.com/jung-kurt/gofpdf/subwrite.go

@@ -0,0 +1,35 @@
+package gofpdf
+
+// Adapted from http://www.fpdf.org/en/script/script61.php by Wirus and released with the FPDF license.
+
+// SubWrite prints text from the current position in the same way as Write().
+// ht is the line height in the unit of measure specified in New(). str
+// specifies the text to write. subFontSize is the size of the font in points.
+// subOffset is the vertical offset of the text in points; a positive value
+// indicates a superscript, a negative value indicates a subscript. link is the
+// identifier returned by AddLink() or 0 for no internal link. linkStr is a
+// target URL or empty for no external link. A non--zero value for link takes
+// precedence over linkStr.
+//
+// The SubWrite example demonstrates this method.
+func (f *Fpdf) SubWrite(ht float64, str string, subFontSize, subOffset float64, link int, linkStr string) {
+	if f.err != nil {
+		return
+	}
+	// resize font
+	subFontSizeOld := f.fontSizePt
+	f.SetFontSize(subFontSize)
+	// reposition y
+	subOffset = (((subFontSize - subFontSizeOld) / f.k) * 0.3) + (subOffset / f.k)
+	subX := f.x
+	subY := f.y
+	f.SetXY(subX, subY-subOffset)
+	//Output text
+	f.write(ht, str, link, linkStr)
+	// restore y position
+	subX = f.x
+	subY = f.y
+	f.SetXY(subX, subY+subOffset)
+	// restore font size
+	f.SetFontSize(subFontSizeOld)
+}

+ 246 - 0
vendor/github.com/jung-kurt/gofpdf/svgbasic.go

@@ -0,0 +1,246 @@
+/*
+ * Copyright (c) 2014 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 (
+	"encoding/xml"
+	"fmt"
+	"io/ioutil"
+	"strconv"
+	"strings"
+)
+
+var pathCmdSub *strings.Replacer
+
+func init() {
+	// Handle permitted constructions like "100L200,230"
+	pathCmdSub = strings.NewReplacer(",", " ",
+		"L", " L ", "l", " l ",
+		"C", " C ", "c", " c ",
+		"M", " M ", "m", " m ",
+		"H", " H ", "h", " h ",
+		"V", " V ", "v", " v ",
+		"Q", " Q ", "q", " q ",
+		"Z", " Z ", "z", " z ")
+}
+
+// SVGBasicSegmentType describes a single curve or position segment
+type SVGBasicSegmentType struct {
+	Cmd byte // See http://www.w3.org/TR/SVG/paths.html for path command structure
+	Arg [6]float64
+}
+
+func absolutizePath(segs []SVGBasicSegmentType) {
+	var x, y float64
+	var segPtr *SVGBasicSegmentType
+	adjust := func(pos int, adjX, adjY float64) {
+		segPtr.Arg[pos] += adjX
+		segPtr.Arg[pos+1] += adjY
+	}
+	for j, seg := range segs {
+		segPtr = &segs[j]
+		if j == 0 && seg.Cmd == 'm' {
+			segPtr.Cmd = 'M'
+		}
+		switch segPtr.Cmd {
+		case 'M':
+			x = seg.Arg[0]
+			y = seg.Arg[1]
+		case 'm':
+			adjust(0, x, y)
+			segPtr.Cmd = 'M'
+			x = segPtr.Arg[0]
+			y = segPtr.Arg[1]
+		case 'L':
+			x = seg.Arg[0]
+			y = seg.Arg[1]
+		case 'l':
+			adjust(0, x, y)
+			segPtr.Cmd = 'L'
+			x = segPtr.Arg[0]
+			y = segPtr.Arg[1]
+		case 'C':
+			x = seg.Arg[4]
+			y = seg.Arg[5]
+		case 'c':
+			adjust(0, x, y)
+			adjust(2, x, y)
+			adjust(4, x, y)
+			segPtr.Cmd = 'C'
+			x = segPtr.Arg[4]
+			y = segPtr.Arg[5]
+		case 'Q':
+			x = seg.Arg[2]
+			y = seg.Arg[3]
+		case 'q':
+			adjust(0, x, y)
+			adjust(2, x, y)
+			segPtr.Cmd = 'Q'
+			x = segPtr.Arg[2]
+			y = segPtr.Arg[3]
+		case 'H':
+			x = seg.Arg[0]
+		case 'h':
+			segPtr.Arg[0] += x
+			segPtr.Cmd = 'H'
+			x += seg.Arg[0]
+		case 'V':
+			y = seg.Arg[0]
+		case 'v':
+			segPtr.Arg[0] += y
+			segPtr.Cmd = 'V'
+			y += seg.Arg[0]
+		case 'z':
+			segPtr.Cmd = 'Z'
+		}
+	}
+}
+
+func pathParse(pathStr string) (segs []SVGBasicSegmentType, err error) {
+	var seg SVGBasicSegmentType
+	var j, argJ, argCount, prevArgCount int
+	setup := func(n int) {
+		// It is not strictly necessary to clear arguments, but result may be clearer
+		// to caller
+		for j := 0; j < len(seg.Arg); j++ {
+			seg.Arg[j] = 0.0
+		}
+		argJ = 0
+		argCount = n
+		prevArgCount = n
+	}
+	var str string
+	var c byte
+	pathStr = pathCmdSub.Replace(pathStr)
+	strList := strings.Fields(pathStr)
+	count := len(strList)
+	for j = 0; j < count && err == nil; j++ {
+		str = strList[j]
+		if argCount == 0 { // Look for path command or argument continuation
+			c = str[0]
+			if c == '-' || (c >= '0' && c <= '9') { // More arguments
+				if j > 0 {
+					setup(prevArgCount)
+					// Repeat previous action
+					if seg.Cmd == 'M' {
+						seg.Cmd = 'L'
+					} else if seg.Cmd == 'm' {
+						seg.Cmd = 'l'
+					}
+				} else {
+					err = fmt.Errorf("expecting SVG path command at first position, got %s", str)
+				}
+			}
+		}
+		if err == nil {
+			if argCount == 0 {
+				seg.Cmd = str[0]
+				switch seg.Cmd {
+				case 'M', 'm': // Absolute/relative moveto: x, y
+					setup(2)
+				case 'C', 'c': // Absolute/relative Bézier curve: cx0, cy0, cx1, cy1, x1, y1
+					setup(6)
+				case 'H', 'h': // Absolute/relative horizontal line to: x
+					setup(1)
+				case 'L', 'l': // Absolute/relative lineto: x, y
+					setup(2)
+				case 'Q', 'q': // Absolute/relative quadratic curve: x0, y0, x1, y1
+					setup(4)
+				case 'V', 'v': // Absolute/relative vertical line to: y
+					setup(1)
+				case 'Z', 'z': // closepath instruction (takes no arguments)
+					segs = append(segs, seg)
+				default:
+					err = fmt.Errorf("expecting SVG path command at position %d, got %s", j, str)
+				}
+			} else {
+				seg.Arg[argJ], err = strconv.ParseFloat(str, 64)
+				if err == nil {
+					argJ++
+					argCount--
+					if argCount == 0 {
+						segs = append(segs, seg)
+					}
+				}
+			}
+		}
+	}
+	if err == nil {
+		if argCount == 0 {
+			absolutizePath(segs)
+		} else {
+			err = fmt.Errorf("expecting additional (%d) numeric arguments", argCount)
+		}
+	}
+	return
+}
+
+// SVGBasicType aggregates the information needed to describe a multi-segment
+// basic vector image
+type SVGBasicType struct {
+	Wd, Ht   float64
+	Segments [][]SVGBasicSegmentType
+}
+
+// SVGBasicParse parses a simple scalable vector graphics (SVG) buffer into a
+// descriptor. Only a small subset of the SVG standard, in particular the path
+// information generated by jSignature, is supported. The returned path data
+// includes only the commands 'M' (absolute moveto: x, y), 'L' (absolute
+// lineto: x, y), 'C' (absolute cubic Bézier curve: cx0, cy0, cx1, cy1,
+// x1,y1), 'Q' (absolute quadratic Bézier curve: x0, y0, x1, y1) and 'Z'
+// (closepath).
+func SVGBasicParse(buf []byte) (sig SVGBasicType, err error) {
+	type pathType struct {
+		D string `xml:"d,attr"`
+	}
+	type srcType struct {
+		Wd    float64    `xml:"width,attr"`
+		Ht    float64    `xml:"height,attr"`
+		Paths []pathType `xml:"path"`
+	}
+	var src srcType
+	err = xml.Unmarshal(buf, &src)
+	if err == nil {
+		if src.Wd > 0 && src.Ht > 0 {
+			sig.Wd, sig.Ht = src.Wd, src.Ht
+			var segs []SVGBasicSegmentType
+			for _, path := range src.Paths {
+				if err == nil {
+					segs, err = pathParse(path.D)
+					if err == nil {
+						sig.Segments = append(sig.Segments, segs)
+					}
+				}
+			}
+		} else {
+			err = fmt.Errorf("unacceptable values for basic SVG extent: %.2f x %.2f",
+				sig.Wd, sig.Ht)
+		}
+	}
+	return
+}
+
+// SVGBasicFileParse parses a simple scalable vector graphics (SVG) file into a
+// basic descriptor. The SVGBasicWrite() example demonstrates this method.
+func SVGBasicFileParse(svgFileStr string) (sig SVGBasicType, err error) {
+	var buf []byte
+	buf, err = ioutil.ReadFile(svgFileStr)
+	if err == nil {
+		sig, err = SVGBasicParse(buf)
+	}
+	return
+}

+ 85 - 0
vendor/github.com/jung-kurt/gofpdf/svgwrite.go

@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2014 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
+
+// SVGBasicWrite renders the paths encoded in the basic SVG image specified by
+// sb. The scale value is used to convert the coordinates in the path to the
+// unit of measure specified in New(). The current position (as set with a call
+// to SetXY()) is used as the origin of the image. The current line cap style
+// (as set with SetLineCapStyle()), line width (as set with SetLineWidth()),
+// and draw color (as set with SetDrawColor()) are used in drawing the image
+// paths.
+func (f *Fpdf) SVGBasicWrite(sb *SVGBasicType, scale float64) {
+	originX, originY := f.GetXY()
+	var x, y, newX, newY float64
+	var cx0, cy0, cx1, cy1 float64
+	var path []SVGBasicSegmentType
+	var seg SVGBasicSegmentType
+	var startX, startY float64
+	sval := func(origin float64, arg int) float64 {
+		return origin + scale*seg.Arg[arg]
+	}
+	xval := func(arg int) float64 {
+		return sval(originX, arg)
+	}
+	yval := func(arg int) float64 {
+		return sval(originY, arg)
+	}
+	val := func(arg int) (float64, float64) {
+		return xval(arg), yval(arg + 1)
+	}
+	for j := 0; j < len(sb.Segments) && f.Ok(); j++ {
+		path = sb.Segments[j]
+		for k := 0; k < len(path) && f.Ok(); k++ {
+			seg = path[k]
+			switch seg.Cmd {
+			case 'M':
+				x, y = val(0)
+				startX, startY = x, y
+				f.SetXY(x, y)
+			case 'L':
+				newX, newY = val(0)
+				f.Line(x, y, newX, newY)
+				x, y = newX, newY
+			case 'C':
+				cx0, cy0 = val(0)
+				cx1, cy1 = val(2)
+				newX, newY = val(4)
+				f.CurveCubic(x, y, cx0, cy0, newX, newY, cx1, cy1, "D")
+				x, y = newX, newY
+			case 'Q':
+				cx0, cy0 = val(0)
+				newX, newY = val(2)
+				f.Curve(x, y, cx0, cy0, newX, newY, "D")
+				x, y = newX, newY
+			case 'H':
+				newX = xval(0)
+				f.Line(x, y, newX, y)
+				x = newX
+			case 'V':
+				newY = yval(0)
+				f.Line(x, y, x, newY)
+				y = newY
+			case 'Z':
+				f.Line(x, y, startX, startY)
+				x, y = startX, startY
+			default:
+				f.SetErrorf("Unexpected path command '%c'", seg.Cmd)
+			}
+		}
+	}
+}

+ 273 - 0
vendor/github.com/jung-kurt/gofpdf/template.go

@@ -0,0 +1,273 @@
+package gofpdf
+
+/*
+ * Copyright (c) 2015 Kurt Jung (Gmail: kurt.w.jung),
+ *   Marcus Downing, Jan Slabon (Setasign)
+ *
+ * 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.
+ */
+
+import (
+	"encoding/gob"
+	"sort"
+)
+
+// CreateTemplate defines a new template using the current page size.
+func (f *Fpdf) CreateTemplate(fn func(*Tpl)) Template {
+	return newTpl(PointType{0, 0}, f.curPageSize, f.defOrientation, f.unitStr, f.fontDirStr, fn, f)
+}
+
+// CreateTemplateCustom starts a template, using the given bounds.
+func (f *Fpdf) CreateTemplateCustom(corner PointType, size SizeType, fn func(*Tpl)) Template {
+	return newTpl(corner, size, f.defOrientation, f.unitStr, f.fontDirStr, fn, f)
+}
+
+// CreateTemplate creates a template that is not attached to any document.
+//
+// This function is deprecated; it incorrectly assumes that a page with a width
+// smaller than its height is oriented in portrait mode, otherwise it assumes
+// landscape mode. This causes problems when placing the template in a master
+// document where this condition does not apply. CreateTpl() is a similar
+// function that lets you specify the orientation to avoid this problem.
+func CreateTemplate(corner PointType, size SizeType, unitStr, fontDirStr string, fn func(*Tpl)) Template {
+	orientationStr := "p"
+	if size.Wd > size.Ht {
+		orientationStr = "l"
+	}
+
+	return CreateTpl(corner, size, orientationStr, unitStr, fontDirStr, fn)
+}
+
+// CreateTpl creates a template not attached to any document
+func CreateTpl(corner PointType, size SizeType, orientationStr, unitStr, fontDirStr string, fn func(*Tpl)) Template {
+	return newTpl(corner, size, orientationStr, unitStr, fontDirStr, fn, nil)
+}
+
+// UseTemplate adds a template to the current page or another template,
+// using the size and position at which it was originally written.
+func (f *Fpdf) UseTemplate(t Template) {
+	if t == nil {
+		f.SetErrorf("template is nil")
+		return
+	}
+	corner, size := t.Size()
+	f.UseTemplateScaled(t, corner, size)
+}
+
+// UseTemplateScaled adds a template to the current page or another template,
+// using the given page coordinates.
+func (f *Fpdf) UseTemplateScaled(t Template, corner PointType, size SizeType) {
+	if t == nil {
+		f.SetErrorf("template is nil")
+		return
+	}
+
+	// You have to add at least a page first
+	if f.page <= 0 {
+		f.SetErrorf("cannot use a template without first adding a page")
+		return
+	}
+
+	// make a note of the fact that we actually use this template, as well as any other templates,
+	// images or fonts it uses
+	f.templates[t.ID()] = t
+	for _, tt := range t.Templates() {
+		f.templates[tt.ID()] = tt
+	}
+	for name, ti := range t.Images() {
+		name = sprintf("t%s-%s", t.ID(), name)
+		f.images[name] = ti
+	}
+
+	// template data
+	_, templateSize := t.Size()
+	scaleX := size.Wd / templateSize.Wd
+	scaleY := size.Ht / templateSize.Ht
+	tx := corner.X * f.k
+	ty := (f.curPageSize.Ht - corner.Y - size.Ht) * f.k
+
+	f.outf("q %.4f 0 0 %.4f %.4f %.4f cm", scaleX, scaleY, tx, ty) // Translate
+	f.outf("/TPL%s Do Q", t.ID())
+}
+
+// Template is an object that can be written to, then used and re-used any number of times within a document.
+type Template interface {
+	ID() string
+	Size() (PointType, SizeType)
+	Bytes() []byte
+	Images() map[string]*ImageInfoType
+	Templates() []Template
+	NumPages() int
+	FromPage(int) (Template, error)
+	FromPages() []Template
+	Serialize() ([]byte, error)
+	gob.GobDecoder
+	gob.GobEncoder
+}
+
+func (f *Fpdf) templateFontCatalog() {
+	var keyList []string
+	var font fontDefType
+	var key string
+	f.out("/Font <<")
+	for key = range f.fonts {
+		keyList = append(keyList, key)
+	}
+	if f.catalogSort {
+		sort.Strings(keyList)
+	}
+	for _, key = range keyList {
+		font = f.fonts[key]
+		f.outf("/F%s %d 0 R", font.i, font.N)
+	}
+	f.out(">>")
+}
+
+// putTemplates writes the templates to the PDF
+func (f *Fpdf) putTemplates() {
+	filter := ""
+	if f.compress {
+		filter = "/Filter /FlateDecode "
+	}
+
+	templates := sortTemplates(f.templates, f.catalogSort)
+	var t Template
+	for _, t = range templates {
+		corner, size := t.Size()
+
+		f.newobj()
+		f.templateObjects[t.ID()] = f.n
+		f.outf("<<%s/Type /XObject", filter)
+		f.out("/Subtype /Form")
+		f.out("/Formtype 1")
+		f.outf("/BBox [%.2f %.2f %.2f %.2f]", corner.X*f.k, corner.Y*f.k, (corner.X+size.Wd)*f.k, (corner.Y+size.Ht)*f.k)
+		if corner.X != 0 || corner.Y != 0 {
+			f.outf("/Matrix [1 0 0 1 %.5f %.5f]", -corner.X*f.k*2, corner.Y*f.k*2)
+		}
+
+		// Template's resource dictionary
+		f.out("/Resources ")
+		f.out("<</ProcSet [/PDF /Text /ImageB /ImageC /ImageI]")
+
+		f.templateFontCatalog()
+
+		tImages := t.Images()
+		tTemplates := t.Templates()
+		if len(tImages) > 0 || len(tTemplates) > 0 {
+			f.out("/XObject <<")
+			{
+				var key string
+				var keyList []string
+				var ti *ImageInfoType
+				for key = range tImages {
+					keyList = append(keyList, key)
+				}
+				if gl.catalogSort {
+					sort.Strings(keyList)
+				}
+				for _, key = range keyList {
+					// for _, ti := range tImages {
+					ti = tImages[key]
+					f.outf("/I%s %d 0 R", ti.i, ti.n)
+				}
+			}
+			for _, tt := range tTemplates {
+				id := tt.ID()
+				if objID, ok := f.templateObjects[id]; ok {
+					f.outf("/TPL%s %d 0 R", id, objID)
+				}
+			}
+			f.out(">>")
+		}
+
+		f.out(">>")
+
+		//  Write the template's byte stream
+		buffer := t.Bytes()
+		// fmt.Println("Put template bytes", string(buffer[:]))
+		if f.compress {
+			buffer = sliceCompress(buffer)
+		}
+		f.outf("/Length %d >>", len(buffer))
+		f.putstream(buffer)
+		f.out("endobj")
+	}
+}
+
+func templateKeyList(mp map[string]Template, sort bool) (keyList []string) {
+	var key string
+	for key = range mp {
+		keyList = append(keyList, key)
+	}
+	if sort {
+		gensort(len(keyList),
+			func(a, b int) bool {
+				return keyList[a] < keyList[b]
+			},
+			func(a, b int) {
+				keyList[a], keyList[b] = keyList[b], keyList[a]
+			})
+	}
+	return
+}
+
+// sortTemplates puts templates in a suitable order based on dependices
+func sortTemplates(templates map[string]Template, catalogSort bool) []Template {
+	chain := make([]Template, 0, len(templates)*2)
+
+	// build a full set of dependency chains
+	var keyList []string
+	var key string
+	var t Template
+	keyList = templateKeyList(templates, catalogSort)
+	for _, key = range keyList {
+		t = templates[key]
+		tlist := templateChainDependencies(t)
+		for _, tt := range tlist {
+			if tt != nil {
+				chain = append(chain, tt)
+			}
+		}
+	}
+
+	// reduce that to make a simple list
+	sorted := make([]Template, 0, len(templates))
+chain:
+	for _, t := range chain {
+		for _, already := range sorted {
+			if t == already {
+				continue chain
+			}
+		}
+		sorted = append(sorted, t)
+	}
+
+	return sorted
+}
+
+//  templateChainDependencies is a recursive function for determining the full chain of template dependencies
+func templateChainDependencies(template Template) []Template {
+	requires := template.Templates()
+	chain := make([]Template, len(requires)*2)
+	for _, req := range requires {
+		chain = append(chain, templateChainDependencies(req)...)
+	}
+	chain = append(chain, template)
+	return chain
+}
+
+// < 0002640  31 20 31 32 20 30 20 52  0a 2f 54 50 4c 32 20 31  |1 12 0 R./TPL2 1|
+// < 0002650  35 20 30 20 52 0a 2f 54  50 4c 31 20 31 34 20 30  |5 0 R./TPL1 14 0|
+
+// > 0002640  31 20 31 32 20 30 20 52  0a 2f 54 50 4c 31 20 31  |1 12 0 R./TPL1 1|
+// > 0002650  34 20 30 20 52 0a 2f 54  50 4c 32 20 31 35 20 30  |4 0 R./TPL2 15 0|

+ 299 - 0
vendor/github.com/jung-kurt/gofpdf/template_impl.go

@@ -0,0 +1,299 @@
+package gofpdf
+
+import (
+	"bytes"
+	"crypto/sha1"
+	"encoding/gob"
+	"errors"
+	"fmt"
+)
+
+/*
+ * Copyright (c) 2015 Kurt Jung (Gmail: kurt.w.jung),
+ *   Marcus Downing, Jan Slabon (Setasign)
+ *
+ * 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.
+ */
+
+// newTpl creates a template, copying graphics settings from a template if one is given
+func newTpl(corner PointType, size SizeType, orientationStr, unitStr, fontDirStr string, fn func(*Tpl), copyFrom *Fpdf) Template {
+	sizeStr := ""
+
+	fpdf := fpdfNew(orientationStr, unitStr, sizeStr, fontDirStr, size)
+	tpl := Tpl{*fpdf}
+	if copyFrom != nil {
+		tpl.loadParamsFromFpdf(copyFrom)
+	}
+	tpl.Fpdf.AddPage()
+	fn(&tpl)
+
+	bytes := make([][]byte, len(tpl.Fpdf.pages))
+	// skip the first page as it will always be empty
+	for x := 1; x < len(bytes); x++ {
+		bytes[x] = tpl.Fpdf.pages[x].Bytes()
+	}
+
+	templates := make([]Template, 0, len(tpl.Fpdf.templates))
+	for _, key := range templateKeyList(tpl.Fpdf.templates, true) {
+		templates = append(templates, tpl.Fpdf.templates[key])
+	}
+	images := tpl.Fpdf.images
+
+	template := FpdfTpl{corner, size, bytes, images, templates, tpl.Fpdf.page}
+	return &template
+}
+
+// FpdfTpl is a concrete implementation of the Template interface.
+type FpdfTpl struct {
+	corner    PointType
+	size      SizeType
+	bytes     [][]byte
+	images    map[string]*ImageInfoType
+	templates []Template
+	page      int
+}
+
+// ID returns the global template identifier
+func (t *FpdfTpl) ID() string {
+	return fmt.Sprintf("%x", sha1.Sum(t.Bytes()))
+}
+
+// Size gives the bounding dimensions of this template
+func (t *FpdfTpl) Size() (corner PointType, size SizeType) {
+	return t.corner, t.size
+}
+
+// Bytes returns the actual template data, not including resources
+func (t *FpdfTpl) Bytes() []byte {
+	return t.bytes[t.page]
+}
+
+// FromPage creates a new template from a specific Page
+func (t *FpdfTpl) FromPage(page int) (Template, error) {
+	// pages start at 1
+	if page == 0 {
+		return nil, errors.New("Pages start at 1 No template will have a page 0")
+	}
+
+	if page > t.NumPages() {
+		return nil, fmt.Errorf("The template does not have a page %d", page)
+	}
+	// if it is already pointing to the correct page
+	// there is no need to create a new template
+	if t.page == page {
+		return t, nil
+	}
+
+	t2 := *t
+	t2.page = page
+	return &t2, nil
+}
+
+// FromPages creates a template slice with all the pages within a template.
+func (t *FpdfTpl) FromPages() []Template {
+	p := make([]Template, t.NumPages())
+	for x := 1; x <= t.NumPages(); x++ {
+		// the only error is when accessing a
+		// non existing template... that can't happen
+		// here
+		p[x-1], _ = t.FromPage(x)
+	}
+
+	return p
+}
+
+// Images returns a list of the images used in this template
+func (t *FpdfTpl) Images() map[string]*ImageInfoType {
+	return t.images
+}
+
+// Templates returns a list of templates used in this template
+func (t *FpdfTpl) Templates() []Template {
+	return t.templates
+}
+
+// NumPages returns the number of available pages within the template. Look at FromPage and FromPages on access to that content.
+func (t *FpdfTpl) NumPages() int {
+	// the first page is empty to
+	// make the pages begin at one
+	return len(t.bytes) - 1
+}
+
+// Serialize turns a template into a byte string for later deserialization
+func (t *FpdfTpl) Serialize() ([]byte, error) {
+	b := new(bytes.Buffer)
+	enc := gob.NewEncoder(b)
+	err := enc.Encode(t)
+
+	return b.Bytes(), err
+}
+
+// DeserializeTemplate creaties a template from a previously serialized
+// template
+func DeserializeTemplate(b []byte) (Template, error) {
+	tpl := new(FpdfTpl)
+	dec := gob.NewDecoder(bytes.NewBuffer(b))
+	err := dec.Decode(tpl)
+	return tpl, err
+}
+
+// childrenImages returns the next layer of children images, it doesn't dig into
+// children of children. Applies template namespace to keys to ensure
+// no collisions. See UseTemplateScaled
+func (t *FpdfTpl) childrenImages() map[string]*ImageInfoType {
+	childrenImgs := make(map[string]*ImageInfoType)
+
+	for x := 0; x < len(t.templates); x++ {
+		imgs := t.templates[x].Images()
+		for key, val := range imgs {
+			name := sprintf("t%s-%s", t.templates[x].ID(), key)
+			childrenImgs[name] = val
+		}
+	}
+
+	return childrenImgs
+}
+
+// childrensTemplates returns the next layer of children templates, it doesn't dig into
+// children of children.
+func (t *FpdfTpl) childrensTemplates() []Template {
+	childrenTmpls := make([]Template, 0)
+
+	for x := 0; x < len(t.templates); x++ {
+		tmpls := t.templates[x].Templates()
+		childrenTmpls = append(childrenTmpls, tmpls...)
+	}
+
+	return childrenTmpls
+}
+
+// GobEncode encodes the receiving template into a byte buffer. Use GobDecode
+// to decode the byte buffer back to a template.
+func (t *FpdfTpl) GobEncode() ([]byte, error) {
+	w := new(bytes.Buffer)
+	encoder := gob.NewEncoder(w)
+
+	childrensTemplates := t.childrensTemplates()
+	firstClassTemplates := make([]Template, 0)
+
+found_continue:
+	for x := 0; x < len(t.templates); x++ {
+		for y := 0; y < len(childrensTemplates); y++ {
+			if childrensTemplates[y].ID() == t.templates[x].ID() {
+				continue found_continue
+			}
+		}
+
+		firstClassTemplates = append(firstClassTemplates, t.templates[x])
+	}
+	err := encoder.Encode(firstClassTemplates)
+
+	childrenImgs := t.childrenImages()
+	firstClassImgs := make(map[string]*ImageInfoType)
+
+	for key, img := range t.images {
+		if _, ok := childrenImgs[key]; !ok {
+			firstClassImgs[key] = img
+		}
+	}
+
+	if err == nil {
+		err = encoder.Encode(firstClassImgs)
+	}
+	if err == nil {
+		err = encoder.Encode(t.corner)
+	}
+	if err == nil {
+		err = encoder.Encode(t.size)
+	}
+	if err == nil {
+		err = encoder.Encode(t.bytes)
+	}
+	if err == nil {
+		err = encoder.Encode(t.page)
+	}
+
+	return w.Bytes(), err
+}
+
+// GobDecode decodes the specified byte buffer into the receiving template.
+func (t *FpdfTpl) GobDecode(buf []byte) error {
+	r := bytes.NewBuffer(buf)
+	decoder := gob.NewDecoder(r)
+
+	firstClassTemplates := make([]*FpdfTpl, 0)
+	err := decoder.Decode(&firstClassTemplates)
+	t.templates = make([]Template, len(firstClassTemplates))
+
+	for x := 0; x < len(t.templates); x++ {
+		t.templates[x] = Template(firstClassTemplates[x])
+	}
+
+	firstClassImages := t.childrenImages()
+
+	t.templates = append(t.childrensTemplates(), t.templates...)
+
+	t.images = make(map[string]*ImageInfoType)
+	if err == nil {
+		err = decoder.Decode(&t.images)
+	}
+
+	for k, v := range firstClassImages {
+		t.images[k] = v
+	}
+
+	if err == nil {
+		err = decoder.Decode(&t.corner)
+	}
+	if err == nil {
+		err = decoder.Decode(&t.size)
+	}
+	if err == nil {
+		err = decoder.Decode(&t.bytes)
+	}
+	if err == nil {
+		err = decoder.Decode(&t.page)
+	}
+
+	return err
+}
+
+// Tpl is an Fpdf used for writing a template. It has most of the facilities of
+// an Fpdf, but cannot add more pages. Tpl is used directly only during the
+// limited time a template is writable.
+type Tpl struct {
+	Fpdf
+}
+
+func (t *Tpl) loadParamsFromFpdf(f *Fpdf) {
+	t.Fpdf.compress = false
+
+	t.Fpdf.k = f.k
+	t.Fpdf.x = f.x
+	t.Fpdf.y = f.y
+	t.Fpdf.lineWidth = f.lineWidth
+	t.Fpdf.capStyle = f.capStyle
+	t.Fpdf.joinStyle = f.joinStyle
+
+	t.Fpdf.color.draw = f.color.draw
+	t.Fpdf.color.fill = f.color.fill
+	t.Fpdf.color.text = f.color.text
+
+	t.Fpdf.fonts = f.fonts
+	t.Fpdf.currentFont = f.currentFont
+	t.Fpdf.fontFamily = f.fontFamily
+	t.Fpdf.fontSize = f.fontSize
+	t.Fpdf.fontSizePt = f.fontSizePt
+	t.Fpdf.fontStyle = f.fontStyle
+	t.Fpdf.ws = f.ws
+}

+ 374 - 0
vendor/github.com/jung-kurt/gofpdf/ttfparser.go

@@ -0,0 +1,374 @@
+/*
+ * Copyright (c) 2013 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
+
+// Utility to parse TTF font files
+// Version:    1.0
+// Date:       2011-06-18
+// Author:     Olivier PLATHEY
+// Port to Go: Kurt Jung, 2013-07-15
+
+import (
+	"encoding/binary"
+	"fmt"
+	"os"
+	"regexp"
+	"strings"
+)
+
+// TtfType contains metrics of a TrueType font.
+type TtfType struct {
+	Embeddable             bool
+	UnitsPerEm             uint16
+	PostScriptName         string
+	Bold                   bool
+	ItalicAngle            int16
+	IsFixedPitch           bool
+	TypoAscender           int16
+	TypoDescender          int16
+	UnderlinePosition      int16
+	UnderlineThickness     int16
+	Xmin, Ymin, Xmax, Ymax int16
+	CapHeight              int16
+	Widths                 []uint16
+	Chars                  map[uint16]uint16
+}
+
+type ttfParser struct {
+	rec              TtfType
+	f                *os.File
+	tables           map[string]uint32
+	numberOfHMetrics uint16
+	numGlyphs        uint16
+}
+
+// TtfParse extracts various metrics from a TrueType font file.
+func TtfParse(fileStr string) (TtfRec TtfType, err error) {
+	var t ttfParser
+	t.f, err = os.Open(fileStr)
+	if err != nil {
+		return
+	}
+	version, err := t.ReadStr(4)
+	if err != nil {
+		return
+	}
+	if version == "OTTO" {
+		err = fmt.Errorf("fonts based on PostScript outlines are not supported")
+		return
+	}
+	if version != "\x00\x01\x00\x00" {
+		err = fmt.Errorf("unrecognized file format")
+		return
+	}
+	numTables := int(t.ReadUShort())
+	t.Skip(3 * 2) // searchRange, entrySelector, rangeShift
+	t.tables = make(map[string]uint32)
+	var tag string
+	for j := 0; j < numTables; j++ {
+		tag, err = t.ReadStr(4)
+		if err != nil {
+			return
+		}
+		t.Skip(4) // checkSum
+		offset := t.ReadULong()
+		t.Skip(4) // length
+		t.tables[tag] = offset
+	}
+	err = t.ParseComponents()
+	if err != nil {
+		return
+	}
+	t.f.Close()
+	TtfRec = t.rec
+	return
+}
+
+func (t *ttfParser) ParseComponents() (err error) {
+	err = t.ParseHead()
+	if err == nil {
+		err = t.ParseHhea()
+		if err == nil {
+			err = t.ParseMaxp()
+			if err == nil {
+				err = t.ParseHmtx()
+				if err == nil {
+					err = t.ParseCmap()
+					if err == nil {
+						err = t.ParseName()
+						if err == nil {
+							err = t.ParseOS2()
+							if err == nil {
+								err = t.ParsePost()
+							}
+						}
+					}
+				}
+			}
+		}
+	}
+	return
+}
+
+func (t *ttfParser) ParseHead() (err error) {
+	err = t.Seek("head")
+	t.Skip(3 * 4) // version, fontRevision, checkSumAdjustment
+	magicNumber := t.ReadULong()
+	if magicNumber != 0x5F0F3CF5 {
+		err = fmt.Errorf("incorrect magic number")
+		return
+	}
+	t.Skip(2) // flags
+	t.rec.UnitsPerEm = t.ReadUShort()
+	t.Skip(2 * 8) // created, modified
+	t.rec.Xmin = t.ReadShort()
+	t.rec.Ymin = t.ReadShort()
+	t.rec.Xmax = t.ReadShort()
+	t.rec.Ymax = t.ReadShort()
+	return
+}
+
+func (t *ttfParser) ParseHhea() (err error) {
+	err = t.Seek("hhea")
+	if err == nil {
+		t.Skip(4 + 15*2)
+		t.numberOfHMetrics = t.ReadUShort()
+	}
+	return
+}
+
+func (t *ttfParser) ParseMaxp() (err error) {
+	err = t.Seek("maxp")
+	if err == nil {
+		t.Skip(4)
+		t.numGlyphs = t.ReadUShort()
+	}
+	return
+}
+
+func (t *ttfParser) ParseHmtx() (err error) {
+	err = t.Seek("hmtx")
+	if err == nil {
+		t.rec.Widths = make([]uint16, 0, 8)
+		for j := uint16(0); j < t.numberOfHMetrics; j++ {
+			t.rec.Widths = append(t.rec.Widths, t.ReadUShort())
+			t.Skip(2) // lsb
+		}
+		if t.numberOfHMetrics < t.numGlyphs {
+			lastWidth := t.rec.Widths[t.numberOfHMetrics-1]
+			for j := t.numberOfHMetrics; j < t.numGlyphs; j++ {
+				t.rec.Widths = append(t.rec.Widths, lastWidth)
+			}
+		}
+	}
+	return
+}
+
+func (t *ttfParser) ParseCmap() (err error) {
+	var offset int64
+	if err = t.Seek("cmap"); err != nil {
+		return
+	}
+	t.Skip(2) // version
+	numTables := int(t.ReadUShort())
+	offset31 := int64(0)
+	for j := 0; j < numTables; j++ {
+		platformID := t.ReadUShort()
+		encodingID := t.ReadUShort()
+		offset = int64(t.ReadULong())
+		if platformID == 3 && encodingID == 1 {
+			offset31 = offset
+		}
+	}
+	if offset31 == 0 {
+		err = fmt.Errorf("no Unicode encoding found")
+		return
+	}
+	startCount := make([]uint16, 0, 8)
+	endCount := make([]uint16, 0, 8)
+	idDelta := make([]int16, 0, 8)
+	idRangeOffset := make([]uint16, 0, 8)
+	t.rec.Chars = make(map[uint16]uint16)
+	t.f.Seek(int64(t.tables["cmap"])+offset31, os.SEEK_SET)
+	format := t.ReadUShort()
+	if format != 4 {
+		err = fmt.Errorf("unexpected subtable format: %d", format)
+		return
+	}
+	t.Skip(2 * 2) // length, language
+	segCount := int(t.ReadUShort() / 2)
+	t.Skip(3 * 2) // searchRange, entrySelector, rangeShift
+	for j := 0; j < segCount; j++ {
+		endCount = append(endCount, t.ReadUShort())
+	}
+	t.Skip(2) // reservedPad
+	for j := 0; j < segCount; j++ {
+		startCount = append(startCount, t.ReadUShort())
+	}
+	for j := 0; j < segCount; j++ {
+		idDelta = append(idDelta, t.ReadShort())
+	}
+	offset, _ = t.f.Seek(int64(0), os.SEEK_CUR)
+	for j := 0; j < segCount; j++ {
+		idRangeOffset = append(idRangeOffset, t.ReadUShort())
+	}
+	for j := 0; j < segCount; j++ {
+		c1 := startCount[j]
+		c2 := endCount[j]
+		d := idDelta[j]
+		ro := idRangeOffset[j]
+		if ro > 0 {
+			t.f.Seek(offset+2*int64(j)+int64(ro), os.SEEK_SET)
+		}
+		for c := c1; c <= c2; c++ {
+			if c == 0xFFFF {
+				break
+			}
+			var gid int32
+			if ro > 0 {
+				gid = int32(t.ReadUShort())
+				if gid > 0 {
+					gid += int32(d)
+				}
+			} else {
+				gid = int32(c) + int32(d)
+			}
+			if gid >= 65536 {
+				gid -= 65536
+			}
+			if gid > 0 {
+				t.rec.Chars[c] = uint16(gid)
+			}
+		}
+	}
+	return
+}
+
+func (t *ttfParser) ParseName() (err error) {
+	err = t.Seek("name")
+	if err == nil {
+		tableOffset, _ := t.f.Seek(0, os.SEEK_CUR)
+		t.rec.PostScriptName = ""
+		t.Skip(2) // format
+		count := t.ReadUShort()
+		stringOffset := t.ReadUShort()
+		for j := uint16(0); j < count && t.rec.PostScriptName == ""; j++ {
+			t.Skip(3 * 2) // platformID, encodingID, languageID
+			nameID := t.ReadUShort()
+			length := t.ReadUShort()
+			offset := t.ReadUShort()
+			if nameID == 6 {
+				// PostScript name
+				t.f.Seek(int64(tableOffset)+int64(stringOffset)+int64(offset), os.SEEK_SET)
+				var s string
+				s, err = t.ReadStr(int(length))
+				if err != nil {
+					return
+				}
+				s = strings.Replace(s, "\x00", "", -1)
+				var re *regexp.Regexp
+				if re, err = regexp.Compile("[(){}<> /%[\\]]"); err != nil {
+					return
+				}
+				t.rec.PostScriptName = re.ReplaceAllString(s, "")
+			}
+		}
+		if t.rec.PostScriptName == "" {
+			err = fmt.Errorf("the name PostScript was not found")
+		}
+	}
+	return
+}
+
+func (t *ttfParser) ParseOS2() (err error) {
+	err = t.Seek("OS/2")
+	if err == nil {
+		version := t.ReadUShort()
+		t.Skip(3 * 2) // xAvgCharWidth, usWeightClass, usWidthClass
+		fsType := t.ReadUShort()
+		t.rec.Embeddable = (fsType != 2) && (fsType&0x200) == 0
+		t.Skip(11*2 + 10 + 4*4 + 4)
+		fsSelection := t.ReadUShort()
+		t.rec.Bold = (fsSelection & 32) != 0
+		t.Skip(2 * 2) // usFirstCharIndex, usLastCharIndex
+		t.rec.TypoAscender = t.ReadShort()
+		t.rec.TypoDescender = t.ReadShort()
+		if version >= 2 {
+			t.Skip(3*2 + 2*4 + 2)
+			t.rec.CapHeight = t.ReadShort()
+		} else {
+			t.rec.CapHeight = 0
+		}
+	}
+	return
+}
+
+func (t *ttfParser) ParsePost() (err error) {
+	err = t.Seek("post")
+	if err == nil {
+		t.Skip(4) // version
+		t.rec.ItalicAngle = t.ReadShort()
+		t.Skip(2) // Skip decimal part
+		t.rec.UnderlinePosition = t.ReadShort()
+		t.rec.UnderlineThickness = t.ReadShort()
+		t.rec.IsFixedPitch = t.ReadULong() != 0
+	}
+	return
+}
+
+func (t *ttfParser) Seek(tag string) (err error) {
+	ofs, ok := t.tables[tag]
+	if ok {
+		t.f.Seek(int64(ofs), os.SEEK_SET)
+	} else {
+		err = fmt.Errorf("table not found: %s", tag)
+	}
+	return
+}
+
+func (t *ttfParser) Skip(n int) {
+	t.f.Seek(int64(n), os.SEEK_CUR)
+}
+
+func (t *ttfParser) ReadStr(length int) (str string, err error) {
+	var n int
+	buf := make([]byte, length)
+	n, err = t.f.Read(buf)
+	if err == nil {
+		if n == length {
+			str = string(buf)
+		} else {
+			err = fmt.Errorf("unable to read %d bytes", length)
+		}
+	}
+	return
+}
+
+func (t *ttfParser) ReadUShort() (val uint16) {
+	binary.Read(t.f, binary.BigEndian, &val)
+	return
+}
+
+func (t *ttfParser) ReadShort() (val int16) {
+	binary.Read(t.f, binary.BigEndian, &val)
+	return
+}
+
+func (t *ttfParser) ReadULong() (val uint32) {
+	binary.Read(t.f, binary.BigEndian, &val)
+	return
+}

+ 1153 - 0
vendor/github.com/jung-kurt/gofpdf/utf8fontfile.go

@@ -0,0 +1,1153 @@
+/*
+ * Copyright (c) 2019 Arteom Korotkiy (Gmail: arteomkorotkiy)
+ *
+ * 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"
+	"encoding/binary"
+	"fmt"
+	"math"
+	"sort"
+)
+
+// flags
+const symbolWords = 1 << 0
+const symbolScale = 1 << 3
+const symbolContinue = 1 << 5
+const symbolAllScale = 1 << 6
+const symbol2x2 = 1 << 7
+
+// CID map Init
+const toUnicode = "/CIDInit /ProcSet findresource begin\n12 dict begin\nbegincmap\n/CIDSystemInfo\n<</Registry (Adobe)\n/Ordering (UCS)\n/Supplement 0\n>> def\n/CMapName /Adobe-Identity-UCS def\n/CMapType 2 def\n1 begincodespacerange\n<0000> <FFFF>\nendcodespacerange\n1 beginbfrange\n<0000> <FFFF> <0000>\nendbfrange\nendcmap\nCMapName currentdict /CMap defineresource pop\nend\nend"
+
+type utf8FontFile struct {
+	fileReader           *fileReader
+	LastRune             int
+	tableDescriptions    map[string]*tableDescription
+	outTablesData        map[string][]byte
+	symbolPosition       []int
+	charSymbolDictionary map[int]int
+	Ascent               int
+	Descent              int
+	fontElementSize      int
+	Bbox                 fontBoxType
+	CapHeight            int
+	StemV                int
+	ItalicAngle          int
+	Flags                int
+	UnderlinePosition    float64
+	UnderlineThickness   float64
+	CharWidths           []int
+	DefaultWidth         float64
+	symbolData           map[int]map[string][]int
+	CodeSymbolDictionary map[int]int
+}
+
+type tableDescription struct {
+	name     string
+	checksum []int
+	position int
+	size     int
+}
+
+type fileReader struct {
+	readerPosition int64
+	array          []byte
+}
+
+func (fr *fileReader) Read(s int) []byte {
+	b := fr.array[fr.readerPosition : fr.readerPosition+int64(s)]
+	fr.readerPosition += int64(s)
+	return b
+}
+
+func (fr *fileReader) seek(shift int64, flag int) (int64, error) {
+	if flag == 0 {
+		fr.readerPosition = shift
+	} else if flag == 1 {
+		fr.readerPosition += shift
+	} else if flag == 2 {
+		fr.readerPosition = int64(len(fr.array)) - shift
+	}
+	return int64(fr.readerPosition), nil
+}
+
+func newUTF8Font(reader *fileReader) *utf8FontFile {
+	utf := utf8FontFile{
+		fileReader: reader,
+	}
+	return &utf
+}
+
+func (utf *utf8FontFile) parseFile() error {
+	utf.fileReader.readerPosition = 0
+	utf.symbolPosition = make([]int, 0)
+	utf.charSymbolDictionary = make(map[int]int)
+	utf.tableDescriptions = make(map[string]*tableDescription)
+	utf.outTablesData = make(map[string][]byte)
+	utf.Ascent = 0
+	utf.Descent = 0
+	codeType := uint32(utf.readUint32())
+	if codeType == 0x4F54544F {
+		return fmt.Errorf("not supported\n ")
+	}
+	if codeType == 0x74746366 {
+		return fmt.Errorf("not supported\n ")
+	}
+	if codeType != 0x00010000 && codeType != 0x74727565 {
+		return fmt.Errorf("Not a TrueType font: codeType=%v\n ", codeType)
+	}
+	utf.generateTableDescriptions()
+	utf.parseTables()
+	return nil
+}
+
+func (utf *utf8FontFile) generateTableDescriptions() {
+
+	tablesCount := utf.readUint16()
+	_ = utf.readUint16()
+	_ = utf.readUint16()
+	_ = utf.readUint16()
+	utf.tableDescriptions = make(map[string]*tableDescription)
+
+	for i := 0; i < tablesCount; i++ {
+		record := tableDescription{
+			name:     utf.readTableName(),
+			checksum: []int{utf.readUint16(), utf.readUint16()},
+			position: utf.readUint32(),
+			size:     utf.readUint32(),
+		}
+		utf.tableDescriptions[record.name] = &record
+	}
+}
+
+func (utf *utf8FontFile) readTableName() string {
+	return string(utf.fileReader.Read(4))
+}
+
+func (utf *utf8FontFile) readUint16() int {
+	s := utf.fileReader.Read(2)
+	return (int(s[0]) << 8) + int(s[1])
+}
+
+func (utf *utf8FontFile) readUint32() int {
+	s := utf.fileReader.Read(4)
+	return (int(s[0]) * 16777216) + (int(s[1]) << 16) + (int(s[2]) << 8) + int(s[3]) // 	16777216  = 1<<24
+}
+
+func (utf *utf8FontFile) calcInt32(x, y []int) []int {
+	answer := make([]int, 2)
+	if y[1] > x[1] {
+		x[1] += 1 << 16
+		x[0]++
+	}
+	answer[1] = x[1] - y[1]
+	if y[0] > x[0] {
+		x[0] += 1 << 16
+	}
+	answer[0] = x[0] - y[0]
+	answer[0] = answer[0] & 0xFFFF
+	return answer
+}
+
+func (utf *utf8FontFile) generateChecksum(data []byte) []int {
+	if (len(data) % 4) != 0 {
+		for i := 0; (len(data) % 4) != 0; i++ {
+			data = append(data, 0)
+		}
+	}
+	answer := []int{0x0000, 0x0000}
+	for i := 0; i < len(data); i += 4 {
+		answer[0] += (int(data[i]) << 8) + int(data[i+1])
+		answer[1] += (int(data[i+2]) << 8) + int(data[i+3])
+		answer[0] += answer[1] >> 16
+		answer[1] = answer[1] & 0xFFFF
+		answer[0] = answer[0] & 0xFFFF
+	}
+	return answer
+}
+
+func (utf *utf8FontFile) seek(shift int) {
+	_, _ = utf.fileReader.seek(int64(shift), 0)
+}
+
+func (utf *utf8FontFile) skip(delta int) {
+	_, _ = utf.fileReader.seek(int64(delta), 1)
+}
+
+//SeekTable position
+func (utf *utf8FontFile) SeekTable(name string) int {
+	return utf.seekTable(name, 0)
+}
+
+func (utf *utf8FontFile) seekTable(name string, offsetInTable int) int {
+	_, _ = utf.fileReader.seek(int64(utf.tableDescriptions[name].position+offsetInTable), 0)
+	return int(utf.fileReader.readerPosition)
+}
+
+func (utf *utf8FontFile) readInt16() int16 {
+	s := utf.fileReader.Read(2)
+	a := (int16(s[0]) << 8) + int16(s[1])
+	if (int(a) & (1 << 15)) == 0 {
+		a = int16(int(a) - (1 << 16))
+	}
+	return a
+}
+
+func (utf *utf8FontFile) getUint16(pos int) int {
+	_, _ = utf.fileReader.seek(int64(pos), 0)
+	s := utf.fileReader.Read(2)
+	return (int(s[0]) << 8) + int(s[1])
+}
+
+func (utf *utf8FontFile) splice(stream []byte, offset int, value []byte) []byte {
+	return append(append(stream[:offset], value...), stream[offset+len(value):]...)
+}
+
+func (utf *utf8FontFile) insertUint16(stream []byte, offset int, value int) []byte {
+	up := make([]byte, 2)
+	binary.BigEndian.PutUint16(up, uint16(value))
+	return utf.splice(stream, offset, up)
+}
+
+func (utf *utf8FontFile) getRange(pos, length int) []byte {
+	utf.fileReader.seek(int64(pos), 0)
+	if length < 1 {
+		return make([]byte, 0)
+	}
+	s := utf.fileReader.Read(length)
+	return s
+}
+
+func (utf *utf8FontFile) getTableData(name string) []byte {
+	desckrip := utf.tableDescriptions[name]
+	if desckrip == nil {
+		return nil
+	}
+	if desckrip.size == 0 {
+		return nil
+	}
+	utf.fileReader.seek(int64(desckrip.position), 0)
+	s := utf.fileReader.Read(desckrip.size)
+	return s
+}
+
+func (utf *utf8FontFile) setOutTable(name string, data []byte) {
+	if data == nil {
+		return
+	}
+	if name == "head" {
+		data = utf.splice(data, 8, []byte{0, 0, 0, 0})
+	}
+	utf.outTablesData[name] = data
+}
+
+func arrayKeys(arr map[int]string) []int {
+	answer := make([]int, len(arr))
+	i := 0
+	for key := range arr {
+		answer[i] = key
+		i++
+	}
+	return answer
+}
+
+func inArray(s int, arr []int) bool {
+	for _, i := range arr {
+		if s == i {
+			return true
+		}
+	}
+	return false
+}
+
+func (utf *utf8FontFile) parseNAMETable() int {
+	namePosition := utf.SeekTable("name")
+	format := utf.readUint16()
+	if format != 0 {
+		fmt.Printf("Illegal format %d\n", format)
+		return format
+	}
+	nameCount := utf.readUint16()
+	stringDataPosition := namePosition + utf.readUint16()
+	names := map[int]string{1: "", 2: "", 3: "", 4: "", 6: ""}
+	keys := arrayKeys(names)
+	counter := len(names)
+	for i := 0; i < nameCount; i++ {
+		system := utf.readUint16()
+		code := utf.readUint16()
+		local := utf.readUint16()
+		nameID := utf.readUint16()
+		size := utf.readUint16()
+		position := utf.readUint16()
+		if !inArray(nameID, keys) {
+			continue
+		}
+		currentName := ""
+		if system == 3 && code == 1 && local == 0x409 {
+			oldPos := utf.fileReader.readerPosition
+			utf.seek(stringDataPosition + position)
+			if size%2 != 0 {
+				fmt.Printf("name is not binar byte format\n")
+				return format
+			}
+			size /= 2
+			currentName = ""
+			for size > 0 {
+				char := utf.readUint16()
+				currentName += string(rune(char))
+				size--
+			}
+			utf.fileReader.readerPosition = oldPos
+			utf.seek(int(oldPos))
+		} else if system == 1 && code == 0 && local == 0 {
+			oldPos := utf.fileReader.readerPosition
+			currentName = string(utf.getRange(stringDataPosition+position, size))
+			utf.fileReader.readerPosition = oldPos
+			utf.seek(int(oldPos))
+		}
+		if currentName != "" && names[nameID] == "" {
+			names[nameID] = currentName
+			counter--
+			if counter == 0 {
+				break
+			}
+		}
+	}
+	return format
+}
+
+func (utf *utf8FontFile) parseHEADTable() {
+	utf.SeekTable("head")
+	utf.skip(18)
+	utf.fontElementSize = utf.readUint16()
+	scale := 1000.0 / float64(utf.fontElementSize)
+	utf.skip(16)
+	xMin := utf.readInt16()
+	yMin := utf.readInt16()
+	xMax := utf.readInt16()
+	yMax := utf.readInt16()
+	utf.Bbox = fontBoxType{int(float64(xMin) * scale), int(float64(yMin) * scale), int(float64(xMax) * scale), int(float64(yMax) * scale)}
+	utf.skip(3 * 2)
+	_ = utf.readUint16()
+	symbolDataFormat := utf.readUint16()
+	if symbolDataFormat != 0 {
+		fmt.Printf("Unknown symbol data format %d\n", symbolDataFormat)
+		return
+	}
+}
+
+func (utf *utf8FontFile) parseHHEATable() int {
+	metricsCount := 0
+	if _, OK := utf.tableDescriptions["hhea"]; OK {
+		scale := 1000.0 / float64(utf.fontElementSize)
+		utf.SeekTable("hhea")
+		utf.skip(4)
+		hheaAscender := utf.readInt16()
+		hheaDescender := utf.readInt16()
+		utf.Ascent = int(float64(hheaAscender) * scale)
+		utf.Descent = int(float64(hheaDescender) * scale)
+		utf.skip(24)
+		metricDataFormat := utf.readUint16()
+		if metricDataFormat != 0 {
+			fmt.Printf("Unknown horizontal metric data format %d\n", metricDataFormat)
+			return 0
+		}
+		metricsCount = utf.readUint16()
+		if metricsCount == 0 {
+			fmt.Printf("Number of horizontal metrics is 0\n")
+			return 0
+		}
+	}
+	return metricsCount
+}
+
+func (utf *utf8FontFile) parseOS2Table() int {
+	var weightType int
+	scale := 1000.0 / float64(utf.fontElementSize)
+	if _, OK := utf.tableDescriptions["OS/2"]; OK {
+		utf.SeekTable("OS/2")
+		version := utf.readUint16()
+		utf.skip(2)
+		weightType = utf.readUint16()
+		utf.skip(2)
+		fsType := utf.readUint16()
+		if fsType == 0x0002 || (fsType&0x0300) != 0 {
+			fmt.Printf("ERROR - copyright restrictions.\n")
+			return 0
+		}
+		utf.skip(20)
+		_ = utf.readInt16()
+
+		utf.skip(36)
+		sTypoAscender := utf.readInt16()
+		sTypoDescender := utf.readInt16()
+		if utf.Ascent == 0 {
+			utf.Ascent = int(float64(sTypoAscender) * scale)
+		}
+		if utf.Descent == 0 {
+			utf.Descent = int(float64(sTypoDescender) * scale)
+		}
+		if version > 1 {
+			utf.skip(16)
+			sCapHeight := utf.readInt16()
+			utf.CapHeight = int(float64(sCapHeight) * scale)
+		} else {
+			utf.CapHeight = utf.Ascent
+		}
+	} else {
+		weightType = 500
+		if utf.Ascent == 0 {
+			utf.Ascent = int(float64(utf.Bbox.Ymax) * scale)
+		}
+		if utf.Descent == 0 {
+			utf.Descent = int(float64(utf.Bbox.Ymin) * scale)
+		}
+		utf.CapHeight = utf.Ascent
+	}
+	utf.StemV = 50 + int(math.Pow(float64(weightType)/65.0, 2))
+	return weightType
+}
+
+func (utf *utf8FontFile) parsePOSTTable(weight int) {
+	utf.SeekTable("post")
+	utf.skip(4)
+	utf.ItalicAngle = int(utf.readInt16()) + utf.readUint16()/65536.0
+	scale := 1000.0 / float64(utf.fontElementSize)
+	utf.UnderlinePosition = float64(utf.readInt16()) * scale
+	utf.UnderlineThickness = float64(utf.readInt16()) * scale
+	fixed := utf.readUint32()
+
+	utf.Flags = 4
+
+	if utf.ItalicAngle != 0 {
+		utf.Flags = utf.Flags | 64
+	}
+	if weight >= 600 {
+		utf.Flags = utf.Flags | 262144
+	}
+	if fixed != 0 {
+		utf.Flags = utf.Flags | 1
+	}
+}
+
+func (utf *utf8FontFile) parseCMAPTable(format int) int {
+	cmapPosition := utf.SeekTable("cmap")
+	utf.skip(2)
+	cmapTableCount := utf.readUint16()
+	cidCMAPPosition := 0
+	for i := 0; i < cmapTableCount; i++ {
+		system := utf.readUint16()
+		coded := utf.readUint16()
+		position := utf.readUint32()
+		oldReaderPosition := utf.fileReader.readerPosition
+		if (system == 3 && coded == 1) || system == 0 { // Microsoft, Unicode
+			format = utf.getUint16(cmapPosition + position)
+			if format == 4 {
+				if cidCMAPPosition == 0 {
+					cidCMAPPosition = cmapPosition + position
+				}
+				break
+			}
+		}
+		utf.seek(int(oldReaderPosition))
+	}
+	if cidCMAPPosition == 0 {
+		fmt.Printf("Font does not have cmap for Unicode\n")
+		return cidCMAPPosition
+	}
+	return cidCMAPPosition
+}
+
+func (utf *utf8FontFile) parseTables() {
+	f := utf.parseNAMETable()
+	utf.parseHEADTable()
+	n := utf.parseHHEATable()
+	w := utf.parseOS2Table()
+	utf.parsePOSTTable(w)
+	runeCMAPPosition := utf.parseCMAPTable(f)
+
+	utf.SeekTable("maxp")
+	utf.skip(4)
+	numSymbols := utf.readUint16()
+
+	symbolCharDictionary := make(map[int][]int)
+	charSymbolDictionary := make(map[int]int)
+	utf.generateSCCSDictionaries(runeCMAPPosition, symbolCharDictionary, charSymbolDictionary)
+
+	scale := 1000.0 / float64(utf.fontElementSize)
+	utf.parseHMTXTable(n, numSymbols, symbolCharDictionary, scale)
+}
+
+func (utf *utf8FontFile) generateCMAP() map[int][]int {
+	cmapPosition := utf.SeekTable("cmap")
+	utf.skip(2)
+	cmapTableCount := utf.readUint16()
+	runeCmapPosition := 0
+	for i := 0; i < cmapTableCount; i++ {
+		system := utf.readUint16()
+		coder := utf.readUint16()
+		position := utf.readUint32()
+		oldPosition := utf.fileReader.readerPosition
+		if (system == 3 && coder == 1) || system == 0 {
+			format := utf.getUint16(cmapPosition + position)
+			if format == 4 {
+				runeCmapPosition = cmapPosition + position
+				break
+			}
+		}
+		utf.seek(int(oldPosition))
+	}
+
+	if runeCmapPosition == 0 {
+		fmt.Printf("Font does not have cmap for Unicode\n")
+		return nil
+	}
+
+	symbolCharDictionary := make(map[int][]int)
+	charSymbolDictionary := make(map[int]int)
+	utf.generateSCCSDictionaries(runeCmapPosition, symbolCharDictionary, charSymbolDictionary)
+
+	utf.charSymbolDictionary = charSymbolDictionary
+
+	return symbolCharDictionary
+}
+
+func (utf *utf8FontFile) parseSymbols(usedRunes map[int]int) (map[int]int, map[int]int, map[int]int, []int) {
+	symbolCollection := map[int]int{0: 0}
+	charSymbolPairCollection := make(map[int]int)
+	for _, char := range usedRunes {
+		if _, OK := utf.charSymbolDictionary[char]; OK {
+			symbolCollection[utf.charSymbolDictionary[char]] = char
+			charSymbolPairCollection[char] = utf.charSymbolDictionary[char]
+
+		}
+		utf.LastRune = max(utf.LastRune, char)
+	}
+
+	begin := utf.tableDescriptions["glyf"].position
+
+	symbolArray := make(map[int]int)
+	symbolCollectionKeys := keySortInt(symbolCollection)
+
+	symbolCounter := 0
+	maxRune := 0
+	for _, oldSymbolIndex := range symbolCollectionKeys {
+		maxRune = max(maxRune, symbolCollection[oldSymbolIndex])
+		symbolArray[oldSymbolIndex] = symbolCounter
+		symbolCounter++
+	}
+	charSymbolPairCollectionKeys := keySortInt(charSymbolPairCollection)
+	runeSymbolPairCollection := make(map[int]int)
+	for _, runa := range charSymbolPairCollectionKeys {
+		runeSymbolPairCollection[runa] = symbolArray[charSymbolPairCollection[runa]]
+	}
+	utf.CodeSymbolDictionary = runeSymbolPairCollection
+
+	symbolCollectionKeys = keySortInt(symbolCollection)
+	for _, oldSymbolIndex := range symbolCollectionKeys {
+		_, symbolArray, symbolCollection, symbolCollectionKeys = utf.getSymbols(oldSymbolIndex, &begin, symbolArray, symbolCollection, symbolCollectionKeys)
+	}
+
+	return runeSymbolPairCollection, symbolArray, symbolCollection, symbolCollectionKeys
+}
+
+func (utf *utf8FontFile) generateCMAPTable(cidSymbolPairCollection map[int]int, numSymbols int) []byte {
+	cidSymbolPairCollectionKeys := keySortInt(cidSymbolPairCollection)
+	cidID := 0
+	cidArray := make(map[int][]int)
+	prevCid := -2
+	prevSymbol := -1
+	for _, cid := range cidSymbolPairCollectionKeys {
+		if cid == (prevCid+1) && cidSymbolPairCollection[cid] == (prevSymbol+1) {
+			if n, OK := cidArray[cidID]; !OK || n == nil {
+				cidArray[cidID] = make([]int, 0)
+			}
+			cidArray[cidID] = append(cidArray[cidID], cidSymbolPairCollection[cid])
+		} else {
+			cidID = cid
+			cidArray[cidID] = make([]int, 0)
+			cidArray[cidID] = append(cidArray[cidID], cidSymbolPairCollection[cid])
+		}
+		prevCid = cid
+		prevSymbol = cidSymbolPairCollection[cid]
+	}
+	cidArrayKeys := keySortArrayRangeMap(cidArray)
+	segCount := len(cidArray) + 1
+
+	searchRange := 1
+	entrySelector := 0
+	for searchRange*2 <= segCount {
+		searchRange = searchRange * 2
+		entrySelector = entrySelector + 1
+	}
+	searchRange = searchRange * 2
+	rangeShift := segCount*2 - searchRange
+	length := 16 + (8 * segCount) + (numSymbols + 1)
+	cmap := []int{0, 1, 3, 1, 0, 12, 4, length, 0, segCount * 2, searchRange, entrySelector, rangeShift}
+
+	for _, start := range cidArrayKeys {
+		endCode := start + (len(cidArray[start]) - 1)
+		cmap = append(cmap, endCode)
+	}
+	cmap = append(cmap, 0xFFFF)
+	cmap = append(cmap, 0)
+
+	for _, cidKey := range cidArrayKeys {
+		cmap = append(cmap, cidKey)
+	}
+	cmap = append(cmap, 0xFFFF)
+	for _, cidKey := range cidArrayKeys {
+		idDelta := -(cidKey - cidArray[cidKey][0])
+		cmap = append(cmap, idDelta)
+	}
+	cmap = append(cmap, 1)
+	for range cidArray {
+		cmap = append(cmap, 0)
+
+	}
+	cmap = append(cmap, 0)
+	for _, start := range cidArrayKeys {
+		for _, glidx := range cidArray[start] {
+			cmap = append(cmap, glidx)
+		}
+	}
+	cmap = append(cmap, 0)
+	cmapstr := make([]byte, 0)
+	for _, cm := range cmap {
+		cmapstr = append(cmapstr, packUint16(cm)...)
+	}
+	return cmapstr
+}
+
+//GenerateСutFont fill utf8FontFile from .utf file, only with runes from usedRunes
+func (utf *utf8FontFile) GenerateСutFont(usedRunes map[int]int) []byte {
+	utf.fileReader.readerPosition = 0
+	utf.symbolPosition = make([]int, 0)
+	utf.charSymbolDictionary = make(map[int]int)
+	utf.tableDescriptions = make(map[string]*tableDescription)
+	utf.outTablesData = make(map[string][]byte)
+	utf.Ascent = 0
+	utf.Descent = 0
+	utf.skip(4)
+	utf.LastRune = 0
+	utf.generateTableDescriptions()
+
+	utf.SeekTable("head")
+	utf.skip(50)
+	LocaFormat := utf.readUint16()
+
+	utf.SeekTable("hhea")
+	utf.skip(34)
+	metricsCount := utf.readUint16()
+	oldMetrics := metricsCount
+
+	utf.SeekTable("maxp")
+	utf.skip(4)
+	numSymbols := utf.readUint16()
+
+	symbolCharDictionary := utf.generateCMAP()
+	if symbolCharDictionary == nil {
+		return nil
+	}
+
+	utf.parseHMTXTable(metricsCount, numSymbols, symbolCharDictionary, 1.0)
+
+	utf.parseLOCATable(LocaFormat, numSymbols)
+
+	cidSymbolPairCollection, symbolArray, symbolCollection, symbolCollectionKeys := utf.parseSymbols(usedRunes)
+
+	metricsCount = len(symbolCollection)
+	numSymbols = metricsCount
+
+	utf.setOutTable("name", utf.getTableData("name"))
+	utf.setOutTable("cvt ", utf.getTableData("cvt "))
+	utf.setOutTable("fpgm", utf.getTableData("fpgm"))
+	utf.setOutTable("prep", utf.getTableData("prep"))
+	utf.setOutTable("gasp", utf.getTableData("gasp"))
+
+	postTable := utf.getTableData("post")
+	postTable = append(append([]byte{0x00, 0x03, 0x00, 0x00}, postTable[4:16]...), []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}...)
+	utf.setOutTable("post", postTable)
+
+	delete(cidSymbolPairCollection, 0)
+
+	utf.setOutTable("cmap", utf.generateCMAPTable(cidSymbolPairCollection, numSymbols))
+
+	symbolData := utf.getTableData("glyf")
+
+	offsets := make([]int, 0)
+	glyfData := make([]byte, 0)
+	pos := 0
+	hmtxData := make([]byte, 0)
+	utf.symbolData = make(map[int]map[string][]int, 0)
+
+	for _, originalSymbolIdx := range symbolCollectionKeys {
+		hm := utf.getMetrics(oldMetrics, originalSymbolIdx)
+		hmtxData = append(hmtxData, hm...)
+
+		offsets = append(offsets, pos)
+		symbolPos := utf.symbolPosition[originalSymbolIdx]
+		symbolLen := utf.symbolPosition[originalSymbolIdx+1] - symbolPos
+		data := symbolData[symbolPos : symbolPos+symbolLen]
+		var up int
+		if symbolLen > 0 {
+			up = unpackUint16(data[0:2])
+		}
+
+		if symbolLen > 2 && (up&(1<<15)) != 0 {
+			posInSymbol := 10
+			flags := symbolContinue
+			nComponentElements := 0
+			for (flags & symbolContinue) != 0 {
+				nComponentElements++
+				up = unpackUint16(data[posInSymbol : posInSymbol+2])
+				flags = up
+				up = unpackUint16(data[posInSymbol+2 : posInSymbol+4])
+				symbolIdx := up
+				if _, OK := utf.symbolData[originalSymbolIdx]; !OK {
+					utf.symbolData[originalSymbolIdx] = make(map[string][]int)
+				}
+				if _, OK := utf.symbolData[originalSymbolIdx]["compSymbols"]; !OK {
+					utf.symbolData[originalSymbolIdx]["compSymbols"] = make([]int, 0)
+				}
+				utf.symbolData[originalSymbolIdx]["compSymbols"] = append(utf.symbolData[originalSymbolIdx]["compSymbols"], symbolIdx)
+				data = utf.insertUint16(data, posInSymbol+2, symbolArray[symbolIdx])
+				posInSymbol += 4
+				if (flags & symbolWords) != 0 {
+					posInSymbol += 4
+				} else {
+					posInSymbol += 2
+				}
+				if (flags & symbolScale) != 0 {
+					posInSymbol += 2
+				} else if (flags & symbolAllScale) != 0 {
+					posInSymbol += 4
+				} else if (flags & symbol2x2) != 0 {
+					posInSymbol += 8
+				}
+			}
+		}
+
+		glyfData = append(glyfData, data...)
+		pos += symbolLen
+		if pos%4 != 0 {
+			padding := 4 - (pos % 4)
+			glyfData = append(glyfData, make([]byte, padding)...)
+			pos += padding
+		}
+	}
+
+	offsets = append(offsets, pos)
+	utf.setOutTable("glyf", glyfData)
+
+	utf.setOutTable("hmtx", hmtxData)
+
+	locaData := make([]byte, 0)
+	if ((pos + 1) >> 1) > 0xFFFF {
+		LocaFormat = 1
+		for _, offset := range offsets {
+			locaData = append(locaData, packUint32(offset)...)
+		}
+	} else {
+		LocaFormat = 0
+		for _, offset := range offsets {
+			locaData = append(locaData, packUint16(offset/2)...)
+		}
+	}
+	utf.setOutTable("loca", locaData)
+
+	headData := utf.getTableData("head")
+	headData = utf.insertUint16(headData, 50, LocaFormat)
+	utf.setOutTable("head", headData)
+
+	hheaData := utf.getTableData("hhea")
+	hheaData = utf.insertUint16(hheaData, 34, metricsCount)
+	utf.setOutTable("hhea", hheaData)
+
+	maxp := utf.getTableData("maxp")
+	maxp = utf.insertUint16(maxp, 4, numSymbols)
+	utf.setOutTable("maxp", maxp)
+
+	os2Data := utf.getTableData("OS/2")
+	utf.setOutTable("OS/2", os2Data)
+
+	return utf.assembleTables()
+}
+
+func (utf *utf8FontFile) getSymbols(originalSymbolIdx int, start *int, symbolSet map[int]int, SymbolsCollection map[int]int, SymbolsCollectionKeys []int) (*int, map[int]int, map[int]int, []int) {
+	symbolPos := utf.symbolPosition[originalSymbolIdx]
+	symbolSize := utf.symbolPosition[originalSymbolIdx+1] - symbolPos
+	if symbolSize == 0 {
+		return start, symbolSet, SymbolsCollection, SymbolsCollectionKeys
+	}
+	utf.seek(*start + symbolPos)
+
+	lineCount := utf.readInt16()
+
+	if lineCount < 0 {
+		utf.skip(8)
+		flags := symbolContinue
+		for flags&symbolContinue != 0 {
+			flags = utf.readUint16()
+			symbolIndex := utf.readUint16()
+			if _, OK := symbolSet[symbolIndex]; !OK {
+				symbolSet[symbolIndex] = len(SymbolsCollection)
+				SymbolsCollection[symbolIndex] = 1
+				SymbolsCollectionKeys = append(SymbolsCollectionKeys, symbolIndex)
+			}
+			oldPosition, _ := utf.fileReader.seek(0, 1)
+			_, _, _, SymbolsCollectionKeys = utf.getSymbols(symbolIndex, start, symbolSet, SymbolsCollection, SymbolsCollectionKeys)
+			utf.seek(int(oldPosition))
+			if flags&symbolWords != 0 {
+				utf.skip(4)
+			} else {
+				utf.skip(2)
+			}
+			if flags&symbolScale != 0 {
+				utf.skip(2)
+			} else if flags&symbolAllScale != 0 {
+				utf.skip(4)
+			} else if flags&symbol2x2 != 0 {
+				utf.skip(8)
+			}
+		}
+	}
+	return start, symbolSet, SymbolsCollection, SymbolsCollectionKeys
+}
+
+func (utf *utf8FontFile) parseHMTXTable(numberOfHMetrics, numSymbols int, symbolToChar map[int][]int, scale float64) {
+	var widths int
+	start := utf.SeekTable("hmtx")
+	arrayWidths := 0
+	var arr []int
+	utf.CharWidths = make([]int, 256*256)
+	charCount := 0
+	arr = unpackUint16Array(utf.getRange(start, numberOfHMetrics*4))
+	for symbol := 0; symbol < numberOfHMetrics; symbol++ {
+		arrayWidths = arr[(symbol*2)+1]
+		if _, OK := symbolToChar[symbol]; OK || symbol == 0 {
+
+			if arrayWidths >= (1 << 15) {
+				arrayWidths = 0
+			}
+			if symbol == 0 {
+				utf.DefaultWidth = scale * float64(arrayWidths)
+				continue
+			}
+			for _, char := range symbolToChar[symbol] {
+				if char != 0 && char != 65535 {
+					widths = int(math.Round(scale * float64(arrayWidths)))
+					if widths == 0 {
+						widths = 65535
+					}
+					if char < 196608 {
+						utf.CharWidths[char] = widths
+						charCount++
+					}
+				}
+			}
+		}
+	}
+	diff := numSymbols - numberOfHMetrics
+	for pos := 0; pos < diff; pos++ {
+		symbol := pos + numberOfHMetrics
+		if _, OK := symbolToChar[symbol]; OK {
+			for _, char := range symbolToChar[symbol] {
+				if char != 0 && char != 65535 {
+					widths = int(math.Round(scale * float64(arrayWidths)))
+					if widths == 0 {
+						widths = 65535
+					}
+					if char < 196608 {
+						utf.CharWidths[char] = widths
+						charCount++
+					}
+				}
+			}
+		}
+	}
+	utf.CharWidths[0] = charCount
+}
+
+func (utf *utf8FontFile) getMetrics(metricCount, gid int) []byte {
+	start := utf.SeekTable("hmtx")
+	var metrics []byte
+	if gid < metricCount {
+		utf.seek(start + (gid * 4))
+		metrics = utf.fileReader.Read(4)
+	} else {
+		utf.seek(start + ((metricCount - 1) * 4))
+		metrics = utf.fileReader.Read(2)
+		utf.seek(start + (metricCount * 2) + (gid * 2))
+		metrics = append(metrics, utf.fileReader.Read(2)...)
+	}
+	return metrics
+}
+
+func (utf *utf8FontFile) parseLOCATable(format, numSymbols int) {
+	start := utf.SeekTable("loca")
+	utf.symbolPosition = make([]int, 0)
+	if format == 0 {
+		data := utf.getRange(start, (numSymbols*2)+2)
+		arr := unpackUint16Array(data)
+		for n := 0; n <= numSymbols; n++ {
+			utf.symbolPosition = append(utf.symbolPosition, arr[n+1]*2)
+		}
+	} else if format == 1 {
+		data := utf.getRange(start, (numSymbols*4)+4)
+		arr := unpackUint32Array(data)
+		for n := 0; n <= numSymbols; n++ {
+			utf.symbolPosition = append(utf.symbolPosition, arr[n+1])
+		}
+	} else {
+		fmt.Printf("Unknown loca table format %d\n", format)
+		return
+	}
+}
+
+func (utf *utf8FontFile) generateSCCSDictionaries(runeCmapPosition int, symbolCharDictionary map[int][]int, charSymbolDictionary map[int]int) {
+	maxRune := 0
+	utf.seek(runeCmapPosition + 2)
+	size := utf.readUint16()
+	rim := runeCmapPosition + size
+	utf.skip(2)
+
+	segmentSize := utf.readUint16() / 2
+	utf.skip(6)
+	completers := make([]int, 0)
+	for i := 0; i < segmentSize; i++ {
+		completers = append(completers, utf.readUint16())
+	}
+	utf.skip(2)
+	beginners := make([]int, 0)
+	for i := 0; i < segmentSize; i++ {
+		beginners = append(beginners, utf.readUint16())
+	}
+	sizes := make([]int, 0)
+	for i := 0; i < segmentSize; i++ {
+		sizes = append(sizes, int(utf.readInt16()))
+	}
+	readerPositionStart := utf.fileReader.readerPosition
+	positions := make([]int, 0)
+	for i := 0; i < segmentSize; i++ {
+		positions = append(positions, utf.readUint16())
+	}
+	var symbol int
+	for n := 0; n < segmentSize; n++ {
+		completePosition := completers[n] + 1
+		for char := beginners[n]; char < completePosition; char++ {
+			if positions[n] == 0 {
+				symbol = (char + sizes[n]) & 0xFFFF
+			} else {
+				position := (char-beginners[n])*2 + positions[n]
+				position = int(readerPositionStart) + 2*n + position
+				if position >= rim {
+					symbol = 0
+				} else {
+					symbol = utf.getUint16(position)
+					if symbol != 0 {
+						symbol = (symbol + sizes[n]) & 0xFFFF
+					}
+				}
+			}
+			charSymbolDictionary[char] = symbol
+			if char < 196608 {
+				maxRune = max(char, maxRune)
+			}
+			symbolCharDictionary[symbol] = append(symbolCharDictionary[symbol], char)
+		}
+	}
+}
+
+func max(i, n int) int {
+	if n > i {
+		return n
+	}
+	return i
+}
+
+func (utf *utf8FontFile) assembleTables() []byte {
+	answer := make([]byte, 0)
+	tablesCount := len(utf.outTablesData)
+	findSize := 1
+	writer := 0
+	for findSize*2 <= tablesCount {
+		findSize = findSize * 2
+		writer = writer + 1
+	}
+	findSize = findSize * 16
+	rOffset := tablesCount*16 - findSize
+
+	answer = append(answer, packHeader(0x00010000, tablesCount, findSize, writer, rOffset)...)
+
+	tables := utf.outTablesData
+	tablesNames := keySortStrings(tables)
+
+	offset := 12 + tablesCount*16
+	begin := 0
+
+	for _, name := range tablesNames {
+		if name == "head" {
+			begin = offset
+		}
+		answer = append(answer, []byte(name)...)
+		checksum := utf.generateChecksum(tables[name])
+		answer = append(answer, pack2Uint16(checksum[0], checksum[1])...)
+		answer = append(answer, pack2Uint32(offset, len(tables[name]))...)
+		paddedLength := (len(tables[name]) + 3) &^ 3
+		offset = offset + paddedLength
+	}
+
+	for _, key := range tablesNames {
+		data := tables[key]
+		data = append(data, []byte{0, 0, 0}...)
+		answer = append(answer, data[:(len(data)&^3)]...)
+	}
+
+	checksum := utf.generateChecksum([]byte(answer))
+	checksum = utf.calcInt32([]int{0xB1B0, 0xAFBA}, checksum)
+	answer = utf.splice(answer, (begin + 8), pack2Uint16(checksum[0], checksum[1]))
+	return answer
+}
+
+func unpackUint16Array(data []byte) []int {
+	answer := make([]int, 1)
+	r := bytes.NewReader(data)
+	bs := make([]byte, 2)
+	var e error
+	var c int
+	c, e = r.Read(bs)
+	for e == nil && c > 0 {
+		answer = append(answer, int(binary.BigEndian.Uint16(bs)))
+		c, e = r.Read(bs)
+	}
+	return answer
+}
+
+func unpackUint32Array(data []byte) []int {
+	answer := make([]int, 1)
+	r := bytes.NewReader(data)
+	bs := make([]byte, 4)
+	var e error
+	var c int
+	c, e = r.Read(bs)
+	for e == nil && c > 0 {
+		answer = append(answer, int(binary.BigEndian.Uint32(bs)))
+		c, e = r.Read(bs)
+	}
+	return answer
+}
+
+func unpackUint16(data []byte) int {
+	return int(binary.BigEndian.Uint16(data))
+}
+
+func packHeader(N uint32, n1, n2, n3, n4 int) []byte {
+	answer := make([]byte, 0)
+	bs4 := make([]byte, 4)
+	binary.BigEndian.PutUint32(bs4, N)
+	answer = append(answer, bs4...)
+	bs := make([]byte, 2)
+	binary.BigEndian.PutUint16(bs, uint16(n1))
+	answer = append(answer, bs...)
+	binary.BigEndian.PutUint16(bs, uint16(n2))
+	answer = append(answer, bs...)
+	binary.BigEndian.PutUint16(bs, uint16(n3))
+	answer = append(answer, bs...)
+	binary.BigEndian.PutUint16(bs, uint16(n4))
+	answer = append(answer, bs...)
+	return answer
+}
+
+func pack2Uint16(n1, n2 int) []byte {
+	answer := make([]byte, 0)
+	bs := make([]byte, 2)
+	binary.BigEndian.PutUint16(bs, uint16(n1))
+	answer = append(answer, bs...)
+	binary.BigEndian.PutUint16(bs, uint16(n2))
+	answer = append(answer, bs...)
+	return answer
+}
+
+func pack2Uint32(n1, n2 int) []byte {
+	answer := make([]byte, 0)
+	bs := make([]byte, 4)
+	binary.BigEndian.PutUint32(bs, uint32(n1))
+	answer = append(answer, bs...)
+	binary.BigEndian.PutUint32(bs, uint32(n2))
+	answer = append(answer, bs...)
+	return answer
+}
+
+func packUint32(n1 int) []byte {
+	bs := make([]byte, 4)
+	binary.BigEndian.PutUint32(bs, uint32(n1))
+	return bs
+}
+
+func packUint16(n1 int) []byte {
+	bs := make([]byte, 2)
+	binary.BigEndian.PutUint16(bs, uint16(n1))
+	return bs
+}
+
+func keySortStrings(s map[string][]byte) []string {
+	keys := make([]string, len(s))
+	i := 0
+	for key := range s {
+		keys[i] = key
+		i++
+	}
+	sort.Strings(keys)
+	return keys
+}
+
+func keySortInt(s map[int]int) []int {
+	keys := make([]int, len(s))
+	i := 0
+	for key := range s {
+		keys[i] = key
+		i++
+	}
+	sort.Ints(keys)
+	return keys
+}
+
+func keySortArrayRangeMap(s map[int][]int) []int {
+	keys := make([]int, len(s))
+	i := 0
+	for key := range s {
+		keys[i] = key
+		i++
+	}
+	sort.Ints(keys)
+	return keys
+}
+
+// UTF8CutFont is a utility function that generates a TrueType font composed
+// only of the runes included in cutset. The rune glyphs are copied from This
+// function is demonstrated in ExampleUTF8CutFont().
+func UTF8CutFont(inBuf []byte, cutset string) (outBuf []byte) {
+	f := newUTF8Font(&fileReader{readerPosition: 0, array: inBuf})
+	runes := map[int]int{}
+	for i, r := range cutset {
+		runes[i] = int(r)
+	}
+	outBuf = f.GenerateСutFont(runes)
+	return
+}

+ 454 - 0
vendor/github.com/jung-kurt/gofpdf/util.go

@@ -0,0 +1,454 @@
+/*
+ * Copyright (c) 2013 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 (
+	"bufio"
+	"bytes"
+	"compress/zlib"
+	"fmt"
+	"io"
+	"math"
+	"os"
+	"path/filepath"
+	"strings"
+)
+
+func round(f float64) int {
+	if f < 0 {
+		return -int(math.Floor(-f + 0.5))
+	}
+	return int(math.Floor(f + 0.5))
+}
+
+func sprintf(fmtStr string, args ...interface{}) string {
+	return fmt.Sprintf(fmtStr, args...)
+}
+
+// fileExist returns true if the specified normal file exists
+func fileExist(filename string) (ok bool) {
+	info, err := os.Stat(filename)
+	if err == nil {
+		if ^os.ModePerm&info.Mode() == 0 {
+			ok = true
+		}
+	}
+	return ok
+}
+
+// fileSize returns the size of the specified file; ok will be false
+// if the file does not exist or is not an ordinary file
+func fileSize(filename string) (size int64, ok bool) {
+	info, err := os.Stat(filename)
+	ok = err == nil
+	if ok {
+		size = info.Size()
+	}
+	return
+}
+
+// bufferFromReader returns a new buffer populated with the contents of the specified Reader
+func bufferFromReader(r io.Reader) (b *bytes.Buffer, err error) {
+	b = new(bytes.Buffer)
+	_, err = b.ReadFrom(r)
+	return
+}
+
+// slicesEqual returns true if the two specified float slices are equal
+func slicesEqual(a, b []float64) bool {
+	if len(a) != len(b) {
+		return false
+	}
+	for i := range a {
+		if a[i] != b[i] {
+			return false
+		}
+	}
+	return true
+}
+
+// sliceCompress returns a zlib-compressed copy of the specified byte array
+func sliceCompress(data []byte) []byte {
+	var buf bytes.Buffer
+	cmp, _ := zlib.NewWriterLevel(&buf, zlib.BestSpeed)
+	cmp.Write(data)
+	cmp.Close()
+	return buf.Bytes()
+}
+
+// sliceUncompress returns an uncompressed copy of the specified zlib-compressed byte array
+func sliceUncompress(data []byte) (outData []byte, err error) {
+	inBuf := bytes.NewReader(data)
+	r, err := zlib.NewReader(inBuf)
+	defer r.Close()
+	if err == nil {
+		var outBuf bytes.Buffer
+		_, err = outBuf.ReadFrom(r)
+		if err == nil {
+			outData = outBuf.Bytes()
+		}
+	}
+	return
+}
+
+// utf8toutf16 converts UTF-8 to UTF-16BE; from http://www.fpdf.org/
+func utf8toutf16(s string, withBOM ...bool) string {
+	bom := true
+	if len(withBOM) > 0 {
+		bom = withBOM[0]
+	}
+	res := make([]byte, 0, 8)
+	if bom {
+		res = append(res, 0xFE, 0xFF)
+	}
+	nb := len(s)
+	i := 0
+	for i < nb {
+		c1 := byte(s[i])
+		i++
+		switch {
+		case c1 >= 224:
+			// 3-byte character
+			c2 := byte(s[i])
+			i++
+			c3 := byte(s[i])
+			i++
+			res = append(res, ((c1&0x0F)<<4)+((c2&0x3C)>>2),
+				((c2&0x03)<<6)+(c3&0x3F))
+		case c1 >= 192:
+			// 2-byte character
+			c2 := byte(s[i])
+			i++
+			res = append(res, ((c1 & 0x1C) >> 2),
+				((c1&0x03)<<6)+(c2&0x3F))
+		default:
+			// Single-byte character
+			res = append(res, 0, c1)
+		}
+	}
+	return string(res)
+}
+
+// intIf returns a if cnd is true, otherwise b
+func intIf(cnd bool, a, b int) int {
+	if cnd {
+		return a
+	}
+	return b
+}
+
+// strIf returns aStr if cnd is true, otherwise bStr
+func strIf(cnd bool, aStr, bStr string) string {
+	if cnd {
+		return aStr
+	}
+	return bStr
+}
+
+// doNothing returns the passed string with no translation.
+func doNothing(s string) string {
+	return s
+}
+
+// Dump the internals of the specified values
+// func dump(fileStr string, a ...interface{}) {
+// 	fl, err := os.OpenFile(fileStr, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
+// 	if err == nil {
+// 		fmt.Fprintf(fl, "----------------\n")
+// 		spew.Fdump(fl, a...)
+// 		fl.Close()
+// 	}
+// }
+
+func repClosure(m map[rune]byte) func(string) string {
+	var buf bytes.Buffer
+	return func(str string) string {
+		var ch byte
+		var ok bool
+		buf.Truncate(0)
+		for _, r := range str {
+			if r < 0x80 {
+				ch = byte(r)
+			} else {
+				ch, ok = m[r]
+				if !ok {
+					ch = byte('.')
+				}
+			}
+			buf.WriteByte(ch)
+		}
+		return buf.String()
+	}
+}
+
+// UnicodeTranslator returns a function that can be used to translate, where
+// possible, utf-8 strings to a form that is compatible with the specified code
+// page. The returned function accepts a string and returns a string.
+//
+// r is a reader that should read a buffer made up of content lines that
+// pertain to the code page of interest. Each line is made up of three
+// whitespace separated fields. The first begins with "!" and is followed by
+// two hexadecimal digits that identify the glyph position in the code page of
+// interest. The second field begins with "U+" and is followed by the unicode
+// code point value. The third is the glyph name. A number of these code page
+// map files are packaged with the gfpdf library in the font directory.
+//
+// An error occurs only if a line is read that does not conform to the expected
+// format. In this case, the returned function is valid but does not perform
+// any rune translation.
+func UnicodeTranslator(r io.Reader) (f func(string) string, err error) {
+	m := make(map[rune]byte)
+	var uPos, cPos uint32
+	var lineStr, nameStr string
+	sc := bufio.NewScanner(r)
+	for sc.Scan() {
+		lineStr = sc.Text()
+		lineStr = strings.TrimSpace(lineStr)
+		if len(lineStr) > 0 {
+			_, err = fmt.Sscanf(lineStr, "!%2X U+%4X %s", &cPos, &uPos, &nameStr)
+			if err == nil {
+				if cPos >= 0x80 {
+					m[rune(uPos)] = byte(cPos)
+				}
+			}
+		}
+	}
+	if err == nil {
+		f = repClosure(m)
+	} else {
+		f = doNothing
+	}
+	return
+}
+
+// UnicodeTranslatorFromFile returns a function that can be used to translate,
+// where possible, utf-8 strings to a form that is compatible with the
+// specified code page. See UnicodeTranslator for more details.
+//
+// fileStr identifies a font descriptor file that maps glyph positions to names.
+//
+// If an error occurs reading the file, the returned function is valid but does
+// not perform any rune translation.
+func UnicodeTranslatorFromFile(fileStr string) (f func(string) string, err error) {
+	var fl *os.File
+	fl, err = os.Open(fileStr)
+	if err == nil {
+		f, err = UnicodeTranslator(fl)
+		fl.Close()
+	} else {
+		f = doNothing
+	}
+	return
+}
+
+// UnicodeTranslatorFromDescriptor returns a function that can be used to
+// translate, where possible, utf-8 strings to a form that is compatible with
+// the specified code page. See UnicodeTranslator for more details.
+//
+// cpStr identifies a code page. A descriptor file in the font directory, set
+// with the fontDirStr argument in the call to New(), should have this name
+// plus the extension ".map". If cpStr is empty, it will be replaced with
+// "cp1252", the gofpdf code page default.
+//
+// If an error occurs reading the descriptor, the returned function is valid
+// but does not perform any rune translation.
+//
+// The CellFormat_codepage example demonstrates this method.
+func (f *Fpdf) UnicodeTranslatorFromDescriptor(cpStr string) (rep func(string) string) {
+	var str string
+	var ok bool
+	if f.err == nil {
+		if len(cpStr) == 0 {
+			cpStr = "cp1252"
+		}
+		str, ok = embeddedMapList[cpStr]
+		if ok {
+			rep, f.err = UnicodeTranslator(strings.NewReader(str))
+		} else {
+			rep, f.err = UnicodeTranslatorFromFile(filepath.Join(f.fontpath, cpStr) + ".map")
+		}
+	} else {
+		rep = doNothing
+	}
+	return
+}
+
+// Transform moves a point by given X, Y offset
+func (p *PointType) Transform(x, y float64) PointType {
+	return PointType{p.X + x, p.Y + y}
+}
+
+// Orientation returns the orientation of a given size:
+// "P" for portrait, "L" for landscape
+func (s *SizeType) Orientation() string {
+	if s == nil || s.Ht == s.Wd {
+		return ""
+	}
+	if s.Wd > s.Ht {
+		return "L"
+	}
+	return "P"
+}
+
+// ScaleBy expands a size by a certain factor
+func (s *SizeType) ScaleBy(factor float64) SizeType {
+	return SizeType{s.Wd * factor, s.Ht * factor}
+}
+
+// ScaleToWidth adjusts the height of a size to match the given width
+func (s *SizeType) ScaleToWidth(width float64) SizeType {
+	height := s.Ht * width / s.Wd
+	return SizeType{width, height}
+}
+
+// ScaleToHeight adjusts the width of a size to match the given height
+func (s *SizeType) ScaleToHeight(height float64) SizeType {
+	width := s.Wd * height / s.Ht
+	return SizeType{width, height}
+}
+
+//The untypedKeyMap structure and its methods are copyrighted 2019 by Arteom Korotkiy (Gmail: arteomkorotkiy).
+//Imitation of untyped Map Array
+type untypedKeyMap struct {
+	keySet   []interface{}
+	valueSet []int
+}
+
+//Get position of key=>value in PHP Array
+func (pa *untypedKeyMap) getIndex(key interface{}) int {
+	if key != nil {
+		for i, mKey := range pa.keySet {
+			if mKey == key {
+				return i
+			}
+		}
+		return -1
+	}
+	return -1
+}
+
+//Put key=>value in PHP Array
+func (pa *untypedKeyMap) put(key interface{}, value int) {
+	if key == nil {
+		var i int
+		for n := 0; ; n++ {
+			i = pa.getIndex(n)
+			if i < 0 {
+				key = n
+				break
+			}
+		}
+		pa.keySet = append(pa.keySet, key)
+		pa.valueSet = append(pa.valueSet, value)
+	} else {
+		i := pa.getIndex(key)
+		if i < 0 {
+			pa.keySet = append(pa.keySet, key)
+			pa.valueSet = append(pa.valueSet, value)
+		} else {
+			pa.valueSet[i] = value
+		}
+	}
+}
+
+//Delete value in PHP Array
+func (pa *untypedKeyMap) delete(key interface{}) {
+	if pa == nil || pa.keySet == nil || pa.valueSet == nil {
+		return
+	}
+	i := pa.getIndex(key)
+	if i >= 0 {
+		if i == 0 {
+			pa.keySet = pa.keySet[1:]
+			pa.valueSet = pa.valueSet[1:]
+		} else if i == len(pa.keySet)-1 {
+			pa.keySet = pa.keySet[:len(pa.keySet)-1]
+			pa.valueSet = pa.valueSet[:len(pa.valueSet)-1]
+		} else {
+			pa.keySet = append(pa.keySet[:i], pa.keySet[i+1:]...)
+			pa.valueSet = append(pa.valueSet[:i], pa.valueSet[i+1:]...)
+		}
+	}
+}
+
+//Get value from PHP Array
+func (pa *untypedKeyMap) get(key interface{}) int {
+	i := pa.getIndex(key)
+	if i >= 0 {
+		return pa.valueSet[i]
+	}
+	return 0
+}
+
+//Imitation of PHP function pop()
+func (pa *untypedKeyMap) pop() {
+	pa.keySet = pa.keySet[:len(pa.keySet)-1]
+	pa.valueSet = pa.valueSet[:len(pa.valueSet)-1]
+}
+
+//Imitation of PHP function array_merge()
+func arrayMerge(arr1, arr2 *untypedKeyMap) *untypedKeyMap {
+	answer := untypedKeyMap{}
+	if arr1 == nil && arr2 == nil {
+		answer = untypedKeyMap{
+			make([]interface{}, 0),
+			make([]int, 0),
+		}
+	} else if arr2 == nil {
+		answer.keySet = arr1.keySet[:]
+		answer.valueSet = arr1.valueSet[:]
+	} else if arr1 == nil {
+		answer.keySet = arr2.keySet[:]
+		answer.valueSet = arr2.valueSet[:]
+	} else {
+		answer.keySet = arr1.keySet[:]
+		answer.valueSet = arr1.valueSet[:]
+		for i := 0; i < len(arr2.keySet); i++ {
+			if arr2.keySet[i] == "interval" {
+				if arr1.getIndex("interval") < 0 {
+					answer.put("interval", arr2.valueSet[i])
+				}
+			} else {
+				answer.put(nil, arr2.valueSet[i])
+			}
+		}
+	}
+	return &answer
+}
+
+func remove(arr []int, key int) []int {
+	n := 0
+	for i, mKey := range arr {
+		if mKey == key {
+			n = i
+		}
+	}
+	if n == 0 {
+		return arr[1:]
+	} else if n == len(arr)-1 {
+		return arr[:len(arr)-1]
+	}
+	return append(arr[:n], arr[n+1:]...)
+}
+
+func isChinese(rune2 rune) bool {
+	// chinese unicode: 4e00-9fa5
+	if rune2 >= rune(0x4e00) && rune2 <= rune(0x9fa5) {
+		return true
+	}
+	return false
+}

+ 22 - 0
vendor/github.com/robfig/cron/v3/.gitignore

@@ -0,0 +1,22 @@
+# Compiled Object files, Static and Dynamic libs (Shared Objects)
+*.o
+*.a
+*.so
+
+# Folders
+_obj
+_test
+
+# Architecture specific extensions/prefixes
+*.[568vq]
+[568vq].out
+
+*.cgo1.go
+*.cgo2.c
+_cgo_defun.c
+_cgo_gotypes.go
+_cgo_export.*
+
+_testmain.go
+
+*.exe

+ 21 - 0
vendor/github.com/robfig/cron/v3/LICENSE

@@ -0,0 +1,21 @@
+Copyright (C) 2012 Rob Figueiredo
+All Rights Reserved.
+
+MIT LICENSE
+
+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.

+ 125 - 0
vendor/github.com/robfig/cron/v3/README.md

@@ -0,0 +1,125 @@
+[![GoDoc](http://godoc.org/github.com/robfig/cron?status.png)](http://godoc.org/github.com/robfig/cron)
+[![Build Status](https://travis-ci.org/robfig/cron.svg?branch=master)](https://travis-ci.org/robfig/cron)
+
+# cron
+
+Cron V3 has been released!
+
+To download the specific tagged release, run:
+
+	go get github.com/robfig/cron/v3@v3.0.0
+
+Import it in your program as:
+
+	import "github.com/robfig/cron/v3"
+
+It requires Go 1.11 or later due to usage of Go Modules.
+
+Refer to the documentation here:
+http://godoc.org/github.com/robfig/cron
+
+The rest of this document describes the the advances in v3 and a list of
+breaking changes for users that wish to upgrade from an earlier version.
+
+## Upgrading to v3 (June 2019)
+
+cron v3 is a major upgrade to the library that addresses all outstanding bugs,
+feature requests, and rough edges. It is based on a merge of master which
+contains various fixes to issues found over the years and the v2 branch which
+contains some backwards-incompatible features like the ability to remove cron
+jobs. In addition, v3 adds support for Go Modules, cleans up rough edges like
+the timezone support, and fixes a number of bugs.
+
+New features:
+
+- Support for Go modules. Callers must now import this library as
+  `github.com/robfig/cron/v3`, instead of `gopkg.in/...`
+
+- Fixed bugs:
+  - 0f01e6b parser: fix combining of Dow and Dom (#70)
+  - dbf3220 adjust times when rolling the clock forward to handle non-existent midnight (#157)
+  - eeecf15 spec_test.go: ensure an error is returned on 0 increment (#144)
+  - 70971dc cron.Entries(): update request for snapshot to include a reply channel (#97)
+  - 1cba5e6 cron: fix: removing a job causes the next scheduled job to run too late (#206)
+
+- Standard cron spec parsing by default (first field is "minute"), with an easy
+  way to opt into the seconds field (quartz-compatible). Although, note that the
+  year field (optional in Quartz) is not supported.
+
+- Extensible, key/value logging via an interface that complies with
+  the https://github.com/go-logr/logr project.
+
+- The new Chain & JobWrapper types allow you to install "interceptors" to add
+  cross-cutting behavior like the following:
+  - Recover any panics from jobs
+  - Delay a job's execution if the previous run hasn't completed yet
+  - Skip a job's execution if the previous run hasn't completed yet
+  - Log each job's invocations
+  - Notification when jobs are completed
+
+It is backwards incompatible with both v1 and v2. These updates are required:
+
+- The v1 branch accepted an optional seconds field at the beginning of the cron
+  spec. This is non-standard and has led to a lot of confusion. The new default
+  parser conforms to the standard as described by [the Cron wikipedia page].
+
+  UPDATING: To retain the old behavior, construct your Cron with a custom
+  parser:
+
+      // Seconds field, required
+      cron.New(cron.WithSeconds())
+
+      // Seconds field, optional
+      cron.New(
+          cron.WithParser(
+              cron.SecondOptional | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor))
+
+- The Cron type now accepts functional options on construction rather than the
+  previous ad-hoc behavior modification mechanisms (setting a field, calling a setter).
+
+  UPDATING: Code that sets Cron.ErrorLogger or calls Cron.SetLocation must be
+  updated to provide those values on construction.
+
+- CRON_TZ is now the recommended way to specify the timezone of a single
+  schedule, which is sanctioned by the specification. The legacy "TZ=" prefix
+  will continue to be supported since it is unambiguous and easy to do so.
+
+  UPDATING: No update is required.
+
+- By default, cron will no longer recover panics in jobs that it runs.
+  Recovering can be surprising (see issue #192) and seems to be at odds with
+  typical behavior of libraries. Relatedly, the `cron.WithPanicLogger` option
+  has been removed to accommodate the more general JobWrapper type.
+
+  UPDATING: To opt into panic recovery and configure the panic logger:
+
+      cron.New(cron.WithChain(
+          cron.Recover(logger),  // or use cron.DefaultLogger
+      ))
+
+- In adding support for https://github.com/go-logr/logr, `cron.WithVerboseLogger` was
+  removed, since it is duplicative with the leveled logging.
+
+  UPDATING: Callers should use `WithLogger` and specify a logger that does not
+  discard `Info` logs. For convenience, one is provided that wraps `*log.Logger`:
+
+      cron.New(
+          cron.WithLogger(cron.VerbosePrintfLogger(logger)))
+
+
+### Background - Cron spec format
+
+There are two cron spec formats in common usage:
+
+- The "standard" cron format, described on [the Cron wikipedia page] and used by
+  the cron Linux system utility.
+
+- The cron format used by [the Quartz Scheduler], commonly used for scheduled
+  jobs in Java software
+
+[the Cron wikipedia page]: https://en.wikipedia.org/wiki/Cron
+[the Quartz Scheduler]: http://www.quartz-scheduler.org/documentation/quartz-2.x/tutorials/crontrigger.html
+
+The original version of this package included an optional "seconds" field, which
+made it incompatible with both of these formats. Now, the "standard" format is
+the default format accepted, and the Quartz format is opt-in.

+ 92 - 0
vendor/github.com/robfig/cron/v3/chain.go

@@ -0,0 +1,92 @@
+package cron
+
+import (
+	"fmt"
+	"runtime"
+	"sync"
+	"time"
+)
+
+// JobWrapper decorates the given Job with some behavior.
+type JobWrapper func(Job) Job
+
+// Chain is a sequence of JobWrappers that decorates submitted jobs with
+// cross-cutting behaviors like logging or synchronization.
+type Chain struct {
+	wrappers []JobWrapper
+}
+
+// NewChain returns a Chain consisting of the given JobWrappers.
+func NewChain(c ...JobWrapper) Chain {
+	return Chain{c}
+}
+
+// Then decorates the given job with all JobWrappers in the chain.
+//
+// This:
+//     NewChain(m1, m2, m3).Then(job)
+// is equivalent to:
+//     m1(m2(m3(job)))
+func (c Chain) Then(j Job) Job {
+	for i := range c.wrappers {
+		j = c.wrappers[len(c.wrappers)-i-1](j)
+	}
+	return j
+}
+
+// Recover panics in wrapped jobs and log them with the provided logger.
+func Recover(logger Logger) JobWrapper {
+	return func(j Job) Job {
+		return FuncJob(func() {
+			defer func() {
+				if r := recover(); r != nil {
+					const size = 64 << 10
+					buf := make([]byte, size)
+					buf = buf[:runtime.Stack(buf, false)]
+					err, ok := r.(error)
+					if !ok {
+						err = fmt.Errorf("%v", r)
+					}
+					logger.Error(err, "panic", "stack", "...\n"+string(buf))
+				}
+			}()
+			j.Run()
+		})
+	}
+}
+
+// DelayIfStillRunning serializes jobs, delaying subsequent runs until the
+// previous one is complete. Jobs running after a delay of more than a minute
+// have the delay logged at Info.
+func DelayIfStillRunning(logger Logger) JobWrapper {
+	return func(j Job) Job {
+		var mu sync.Mutex
+		return FuncJob(func() {
+			start := time.Now()
+			mu.Lock()
+			defer mu.Unlock()
+			if dur := time.Since(start); dur > time.Minute {
+				logger.Info("delay", "duration", dur)
+			}
+			j.Run()
+		})
+	}
+}
+
+// SkipIfStillRunning skips an invocation of the Job if a previous invocation is
+// still running. It logs skips to the given logger at Info level.
+func SkipIfStillRunning(logger Logger) JobWrapper {
+	var ch = make(chan struct{}, 1)
+	ch <- struct{}{}
+	return func(j Job) Job {
+		return FuncJob(func() {
+			select {
+			case v := <-ch:
+				j.Run()
+				ch <- v
+			default:
+				logger.Info("skip")
+			}
+		})
+	}
+}

+ 27 - 0
vendor/github.com/robfig/cron/v3/constantdelay.go

@@ -0,0 +1,27 @@
+package cron
+
+import "time"
+
+// ConstantDelaySchedule represents a simple recurring duty cycle, e.g. "Every 5 minutes".
+// It does not support jobs more frequent than once a second.
+type ConstantDelaySchedule struct {
+	Delay time.Duration
+}
+
+// Every returns a crontab Schedule that activates once every duration.
+// Delays of less than a second are not supported (will round up to 1 second).
+// Any fields less than a Second are truncated.
+func Every(duration time.Duration) ConstantDelaySchedule {
+	if duration < time.Second {
+		duration = time.Second
+	}
+	return ConstantDelaySchedule{
+		Delay: duration - time.Duration(duration.Nanoseconds())%time.Second,
+	}
+}
+
+// Next returns the next time this should be run.
+// This rounds so that the next activation time will be on the second.
+func (schedule ConstantDelaySchedule) Next(t time.Time) time.Time {
+	return t.Add(schedule.Delay - time.Duration(t.Nanosecond())*time.Nanosecond)
+}

+ 350 - 0
vendor/github.com/robfig/cron/v3/cron.go

@@ -0,0 +1,350 @@
+package cron
+
+import (
+	"context"
+	"sort"
+	"sync"
+	"time"
+)
+
+// Cron keeps track of any number of entries, invoking the associated func as
+// specified by the schedule. It may be started, stopped, and the entries may
+// be inspected while running.
+type Cron struct {
+	entries   []*Entry
+	chain     Chain
+	stop      chan struct{}
+	add       chan *Entry
+	remove    chan EntryID
+	snapshot  chan chan []Entry
+	running   bool
+	logger    Logger
+	runningMu sync.Mutex
+	location  *time.Location
+	parser    Parser
+	nextID    EntryID
+	jobWaiter sync.WaitGroup
+}
+
+// Job is an interface for submitted cron jobs.
+type Job interface {
+	Run()
+}
+
+// Schedule describes a job's duty cycle.
+type Schedule interface {
+	// Next returns the next activation time, later than the given time.
+	// Next is invoked initially, and then each time the job is run.
+	Next(time.Time) time.Time
+}
+
+// EntryID identifies an entry within a Cron instance
+type EntryID int
+
+// Entry consists of a schedule and the func to execute on that schedule.
+type Entry struct {
+	// ID is the cron-assigned ID of this entry, which may be used to look up a
+	// snapshot or remove it.
+	ID EntryID
+
+	// Schedule on which this job should be run.
+	Schedule Schedule
+
+	// Next time the job will run, or the zero time if Cron has not been
+	// started or this entry's schedule is unsatisfiable
+	Next time.Time
+
+	// Prev is the last time this job was run, or the zero time if never.
+	Prev time.Time
+
+	// WrappedJob is the thing to run when the Schedule is activated.
+	WrappedJob Job
+
+	// Job is the thing that was submitted to cron.
+	// It is kept around so that user code that needs to get at the job later,
+	// e.g. via Entries() can do so.
+	Job Job
+}
+
+// Valid returns true if this is not the zero entry.
+func (e Entry) Valid() bool { return e.ID != 0 }
+
+// byTime is a wrapper for sorting the entry array by time
+// (with zero time at the end).
+type byTime []*Entry
+
+func (s byTime) Len() int      { return len(s) }
+func (s byTime) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
+func (s byTime) Less(i, j int) bool {
+	// Two zero times should return false.
+	// Otherwise, zero is "greater" than any other time.
+	// (To sort it at the end of the list.)
+	if s[i].Next.IsZero() {
+		return false
+	}
+	if s[j].Next.IsZero() {
+		return true
+	}
+	return s[i].Next.Before(s[j].Next)
+}
+
+// New returns a new Cron job runner, modified by the given options.
+//
+// Available Settings
+//
+//   Time Zone
+//     Description: The time zone in which schedules are interpreted
+//     Default:     time.Local
+//
+//   Parser
+//     Description: Parser converts cron spec strings into cron.Schedules.
+//     Default:     Accepts this spec: https://en.wikipedia.org/wiki/Cron
+//
+//   Chain
+//     Description: Wrap submitted jobs to customize behavior.
+//     Default:     A chain that recovers panics and logs them to stderr.
+//
+// See "cron.With*" to modify the default behavior.
+func New(opts ...Option) *Cron {
+	c := &Cron{
+		entries:   nil,
+		chain:     NewChain(),
+		add:       make(chan *Entry),
+		stop:      make(chan struct{}),
+		snapshot:  make(chan chan []Entry),
+		remove:    make(chan EntryID),
+		running:   false,
+		runningMu: sync.Mutex{},
+		logger:    DefaultLogger,
+		location:  time.Local,
+		parser:    standardParser,
+	}
+	for _, opt := range opts {
+		opt(c)
+	}
+	return c
+}
+
+// FuncJob is a wrapper that turns a func() into a cron.Job
+type FuncJob func()
+
+func (f FuncJob) Run() { f() }
+
+// AddFunc adds a func to the Cron to be run on the given schedule.
+// The spec is parsed using the time zone of this Cron instance as the default.
+// An opaque ID is returned that can be used to later remove it.
+func (c *Cron) AddFunc(spec string, cmd func()) (EntryID, error) {
+	return c.AddJob(spec, FuncJob(cmd))
+}
+
+// AddJob adds a Job to the Cron to be run on the given schedule.
+// The spec is parsed using the time zone of this Cron instance as the default.
+// An opaque ID is returned that can be used to later remove it.
+func (c *Cron) AddJob(spec string, cmd Job) (EntryID, error) {
+	schedule, err := c.parser.Parse(spec)
+	if err != nil {
+		return 0, err
+	}
+	return c.Schedule(schedule, cmd), nil
+}
+
+// Schedule adds a Job to the Cron to be run on the given schedule.
+// The job is wrapped with the configured Chain.
+func (c *Cron) Schedule(schedule Schedule, cmd Job) EntryID {
+	c.runningMu.Lock()
+	defer c.runningMu.Unlock()
+	c.nextID++
+	entry := &Entry{
+		ID:         c.nextID,
+		Schedule:   schedule,
+		WrappedJob: c.chain.Then(cmd),
+		Job:        cmd,
+	}
+	if !c.running {
+		c.entries = append(c.entries, entry)
+	} else {
+		c.add <- entry
+	}
+	return entry.ID
+}
+
+// Entries returns a snapshot of the cron entries.
+func (c *Cron) Entries() []Entry {
+	c.runningMu.Lock()
+	defer c.runningMu.Unlock()
+	if c.running {
+		replyChan := make(chan []Entry, 1)
+		c.snapshot <- replyChan
+		return <-replyChan
+	}
+	return c.entrySnapshot()
+}
+
+// Location gets the time zone location
+func (c *Cron) Location() *time.Location {
+	return c.location
+}
+
+// Entry returns a snapshot of the given entry, or nil if it couldn't be found.
+func (c *Cron) Entry(id EntryID) Entry {
+	for _, entry := range c.Entries() {
+		if id == entry.ID {
+			return entry
+		}
+	}
+	return Entry{}
+}
+
+// Remove an entry from being run in the future.
+func (c *Cron) Remove(id EntryID) {
+	c.runningMu.Lock()
+	defer c.runningMu.Unlock()
+	if c.running {
+		c.remove <- id
+	} else {
+		c.removeEntry(id)
+	}
+}
+
+// Start the cron scheduler in its own goroutine, or no-op if already started.
+func (c *Cron) Start() {
+	c.runningMu.Lock()
+	defer c.runningMu.Unlock()
+	if c.running {
+		return
+	}
+	c.running = true
+	go c.run()
+}
+
+// Run the cron scheduler, or no-op if already running.
+func (c *Cron) Run() {
+	c.runningMu.Lock()
+	if c.running {
+		c.runningMu.Unlock()
+		return
+	}
+	c.running = true
+	c.runningMu.Unlock()
+	c.run()
+}
+
+// run the scheduler.. this is private just due to the need to synchronize
+// access to the 'running' state variable.
+func (c *Cron) run() {
+	c.logger.Info("start")
+
+	// Figure out the next activation times for each entry.
+	now := c.now()
+	for _, entry := range c.entries {
+		entry.Next = entry.Schedule.Next(now)
+		c.logger.Info("schedule", "now", now, "entry", entry.ID, "next", entry.Next)
+	}
+
+	for {
+		// Determine the next entry to run.
+		sort.Sort(byTime(c.entries))
+
+		var timer *time.Timer
+		if len(c.entries) == 0 || c.entries[0].Next.IsZero() {
+			// If there are no entries yet, just sleep - it still handles new entries
+			// and stop requests.
+			timer = time.NewTimer(100000 * time.Hour)
+		} else {
+			timer = time.NewTimer(c.entries[0].Next.Sub(now))
+		}
+
+		for {
+			select {
+			case now = <-timer.C:
+				now = now.In(c.location)
+				c.logger.Info("wake", "now", now)
+
+				// Run every entry whose next time was less than now
+				for _, e := range c.entries {
+					if e.Next.After(now) || e.Next.IsZero() {
+						break
+					}
+					c.startJob(e.WrappedJob)
+					e.Prev = e.Next
+					e.Next = e.Schedule.Next(now)
+					c.logger.Info("run", "now", now, "entry", e.ID, "next", e.Next)
+				}
+
+			case newEntry := <-c.add:
+				timer.Stop()
+				now = c.now()
+				newEntry.Next = newEntry.Schedule.Next(now)
+				c.entries = append(c.entries, newEntry)
+				c.logger.Info("added", "now", now, "entry", newEntry.ID, "next", newEntry.Next)
+
+			case replyChan := <-c.snapshot:
+				replyChan <- c.entrySnapshot()
+				continue
+
+			case <-c.stop:
+				timer.Stop()
+				c.logger.Info("stop")
+				return
+
+			case id := <-c.remove:
+				timer.Stop()
+				now = c.now()
+				c.removeEntry(id)
+				c.logger.Info("removed", "entry", id)
+			}
+
+			break
+		}
+	}
+}
+
+// startJob runs the given job in a new goroutine.
+func (c *Cron) startJob(j Job) {
+	c.jobWaiter.Add(1)
+	go func() {
+		defer c.jobWaiter.Done()
+		j.Run()
+	}()
+}
+
+// now returns current time in c location
+func (c *Cron) now() time.Time {
+	return time.Now().In(c.location)
+}
+
+// Stop stops the cron scheduler if it is running; otherwise it does nothing.
+// A context is returned so the caller can wait for running jobs to complete.
+func (c *Cron) Stop() context.Context {
+	c.runningMu.Lock()
+	defer c.runningMu.Unlock()
+	if c.running {
+		c.stop <- struct{}{}
+		c.running = false
+	}
+	ctx, cancel := context.WithCancel(context.Background())
+	go func() {
+		c.jobWaiter.Wait()
+		cancel()
+	}()
+	return ctx
+}
+
+// entrySnapshot returns a copy of the current cron entry list.
+func (c *Cron) entrySnapshot() []Entry {
+	var entries = make([]Entry, len(c.entries))
+	for i, e := range c.entries {
+		entries[i] = *e
+	}
+	return entries
+}
+
+func (c *Cron) removeEntry(id EntryID) {
+	var entries []*Entry
+	for _, e := range c.entries {
+		if e.ID != id {
+			entries = append(entries, e)
+		}
+	}
+	c.entries = entries
+}

+ 212 - 0
vendor/github.com/robfig/cron/v3/doc.go

@@ -0,0 +1,212 @@
+/*
+Package cron implements a cron spec parser and job runner.
+
+Usage
+
+Callers may register Funcs to be invoked on a given schedule.  Cron will run
+them in their own goroutines.
+
+	c := cron.New()
+	c.AddFunc("30 * * * *", func() { fmt.Println("Every hour on the half hour") })
+	c.AddFunc("30 3-6,20-23 * * *", func() { fmt.Println(".. in the range 3-6am, 8-11pm") })
+	c.AddFunc("CRON_TZ=Asia/Tokyo 30 04 * * * *", func() { fmt.Println("Runs at 04:30 Tokyo time every day") })
+	c.AddFunc("@hourly",      func() { fmt.Println("Every hour, starting an hour from now") })
+	c.AddFunc("@every 1h30m", func() { fmt.Println("Every hour thirty, starting an hour thirty from now") })
+	c.Start()
+	..
+	// Funcs are invoked in their own goroutine, asynchronously.
+	...
+	// Funcs may also be added to a running Cron
+	c.AddFunc("@daily", func() { fmt.Println("Every day") })
+	..
+	// Inspect the cron job entries' next and previous run times.
+	inspect(c.Entries())
+	..
+	c.Stop()  // Stop the scheduler (does not stop any jobs already running).
+
+CRON Expression Format
+
+A cron expression represents a set of times, using 5 space-separated fields.
+
+	Field name   | Mandatory? | Allowed values  | Allowed special characters
+	----------   | ---------- | --------------  | --------------------------
+	Minutes      | Yes        | 0-59            | * / , -
+	Hours        | Yes        | 0-23            | * / , -
+	Day of month | Yes        | 1-31            | * / , - ?
+	Month        | Yes        | 1-12 or JAN-DEC | * / , -
+	Day of week  | Yes        | 0-6 or SUN-SAT  | * / , - ?
+
+Month and Day-of-week field values are case insensitive.  "SUN", "Sun", and
+"sun" are equally accepted.
+
+The specific interpretation of the format is based on the Cron Wikipedia page:
+https://en.wikipedia.org/wiki/Cron
+
+Alternative Formats
+
+Alternative Cron expression formats support other fields like seconds. You can
+implement that by creating a custom Parser as follows.
+
+      cron.New(
+          cron.WithParser(
+              cron.SecondOptional | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor))
+
+The most popular alternative Cron expression format is Quartz:
+http://www.quartz-scheduler.org/documentation/quartz-2.x/tutorials/crontrigger.html
+
+Special Characters
+
+Asterisk ( * )
+
+The asterisk indicates that the cron expression will match for all values of the
+field; e.g., using an asterisk in the 5th field (month) would indicate every
+month.
+
+Slash ( / )
+
+Slashes are used to describe increments of ranges. For example 3-59/15 in the
+1st field (minutes) would indicate the 3rd minute of the hour and every 15
+minutes thereafter. The form "*\/..." is equivalent to the form "first-last/...",
+that is, an increment over the largest possible range of the field.  The form
+"N/..." is accepted as meaning "N-MAX/...", that is, starting at N, use the
+increment until the end of that specific range.  It does not wrap around.
+
+Comma ( , )
+
+Commas are used to separate items of a list. For example, using "MON,WED,FRI" in
+the 5th field (day of week) would mean Mondays, Wednesdays and Fridays.
+
+Hyphen ( - )
+
+Hyphens are used to define ranges. For example, 9-17 would indicate every
+hour between 9am and 5pm inclusive.
+
+Question mark ( ? )
+
+Question mark may be used instead of '*' for leaving either day-of-month or
+day-of-week blank.
+
+Predefined schedules
+
+You may use one of several pre-defined schedules in place of a cron expression.
+
+	Entry                  | Description                                | Equivalent To
+	-----                  | -----------                                | -------------
+	@yearly (or @annually) | Run once a year, midnight, Jan. 1st        | 0 0 1 1 *
+	@monthly               | Run once a month, midnight, first of month | 0 0 1 * *
+	@weekly                | Run once a week, midnight between Sat/Sun  | 0 0 * * 0
+	@daily (or @midnight)  | Run once a day, midnight                   | 0 0 * * *
+	@hourly                | Run once an hour, beginning of hour        | 0 * * * *
+
+Intervals
+
+You may also schedule a job to execute at fixed intervals, starting at the time it's added
+or cron is run. This is supported by formatting the cron spec like this:
+
+    @every <duration>
+
+where "duration" is a string accepted by time.ParseDuration
+(http://golang.org/pkg/time/#ParseDuration).
+
+For example, "@every 1h30m10s" would indicate a schedule that activates after
+1 hour, 30 minutes, 10 seconds, and then every interval after that.
+
+Note: The interval does not take the job runtime into account.  For example,
+if a job takes 3 minutes to run, and it is scheduled to run every 5 minutes,
+it will have only 2 minutes of idle time between each run.
+
+Time zones
+
+By default, all interpretation and scheduling is done in the machine's local
+time zone (time.Local). You can specify a different time zone on construction:
+
+      cron.New(
+          cron.WithLocation(time.UTC))
+
+Individual cron schedules may also override the time zone they are to be
+interpreted in by providing an additional space-separated field at the beginning
+of the cron spec, of the form "CRON_TZ=Asia/Tokyo".
+
+For example:
+
+	# Runs at 6am in time.Local
+	cron.New().AddFunc("0 6 * * ?", ...)
+
+	# Runs at 6am in America/New_York
+	nyc, _ := time.LoadLocation("America/New_York")
+	c := cron.New(cron.WithLocation(nyc))
+	c.AddFunc("0 6 * * ?", ...)
+
+	# Runs at 6am in Asia/Tokyo
+	cron.New().AddFunc("CRON_TZ=Asia/Tokyo 0 6 * * ?", ...)
+
+	# Runs at 6am in Asia/Tokyo
+	c := cron.New(cron.WithLocation(nyc))
+	c.SetLocation("America/New_York")
+	c.AddFunc("CRON_TZ=Asia/Tokyo 0 6 * * ?", ...)
+
+The prefix "TZ=(TIME ZONE)" is also supported for legacy compatibility.
+
+Be aware that jobs scheduled during daylight-savings leap-ahead transitions will
+not be run!
+
+Job Wrappers / Chain
+
+A Cron runner may be configured with a chain of job wrappers to add
+cross-cutting functionality to all submitted jobs. For example, they may be used
+to achieve the following effects:
+
+  - Recover any panics from jobs (activated by default)
+  - Delay a job's execution if the previous run hasn't completed yet
+  - Skip a job's execution if the previous run hasn't completed yet
+  - Log each job's invocations
+
+Install wrappers for all jobs added to a cron using the `cron.WithChain` option:
+
+	cron.New(cron.WithChain(
+		cron.SkipIfStillRunning(logger),
+	))
+
+Install wrappers for individual jobs by explicitly wrapping them:
+
+	job = cron.NewChain(
+		cron.SkipIfStillRunning(logger),
+	).Then(job)
+
+Thread safety
+
+Since the Cron service runs concurrently with the calling code, some amount of
+care must be taken to ensure proper synchronization.
+
+All cron methods are designed to be correctly synchronized as long as the caller
+ensures that invocations have a clear happens-before ordering between them.
+
+Logging
+
+Cron defines a Logger interface that is a subset of the one defined in
+github.com/go-logr/logr. It has two logging levels (Info and Error), and
+parameters are key/value pairs. This makes it possible for cron logging to plug
+into structured logging systems. An adapter, [Verbose]PrintfLogger, is provided
+to wrap the standard library *log.Logger.
+
+For additional insight into Cron operations, verbose logging may be activated
+which will record job runs, scheduling decisions, and added or removed jobs.
+Activate it with a one-off logger as follows:
+
+	cron.New(
+		cron.WithLogger(
+			cron.VerbosePrintfLogger(log.New(os.Stdout, "cron: ", log.LstdFlags))))
+
+
+Implementation
+
+Cron entries are stored in an array, sorted by their next activation time.  Cron
+sleeps until the next job is due to be run.
+
+Upon waking:
+ - it runs each entry that is active on that second
+ - it calculates the next run times for the jobs that were run
+ - it re-sorts the array of entries by next activation time.
+ - it goes to sleep until the soonest job.
+*/
+package cron

+ 3 - 0
vendor/github.com/robfig/cron/v3/go.mod

@@ -0,0 +1,3 @@
+module github.com/robfig/cron/v3
+
+go 1.12

+ 86 - 0
vendor/github.com/robfig/cron/v3/logger.go

@@ -0,0 +1,86 @@
+package cron
+
+import (
+	"io/ioutil"
+	"log"
+	"os"
+	"strings"
+	"time"
+)
+
+// DefaultLogger is used by Cron if none is specified.
+var DefaultLogger Logger = PrintfLogger(log.New(os.Stdout, "cron: ", log.LstdFlags))
+
+// DiscardLogger can be used by callers to discard all log messages.
+var DiscardLogger Logger = PrintfLogger(log.New(ioutil.Discard, "", 0))
+
+// Logger is the interface used in this package for logging, so that any backend
+// can be plugged in. It is a subset of the github.com/go-logr/logr interface.
+type Logger interface {
+	// Info logs routine messages about cron's operation.
+	Info(msg string, keysAndValues ...interface{})
+	// Error logs an error condition.
+	Error(err error, msg string, keysAndValues ...interface{})
+}
+
+// PrintfLogger wraps a Printf-based logger (such as the standard library "log")
+// into an implementation of the Logger interface which logs errors only.
+func PrintfLogger(l interface{ Printf(string, ...interface{}) }) Logger {
+	return printfLogger{l, false}
+}
+
+// VerbosePrintfLogger wraps a Printf-based logger (such as the standard library
+// "log") into an implementation of the Logger interface which logs everything.
+func VerbosePrintfLogger(l interface{ Printf(string, ...interface{}) }) Logger {
+	return printfLogger{l, true}
+}
+
+type printfLogger struct {
+	logger  interface{ Printf(string, ...interface{}) }
+	logInfo bool
+}
+
+func (pl printfLogger) Info(msg string, keysAndValues ...interface{}) {
+	if pl.logInfo {
+		keysAndValues = formatTimes(keysAndValues)
+		pl.logger.Printf(
+			formatString(len(keysAndValues)),
+			append([]interface{}{msg}, keysAndValues...)...)
+	}
+}
+
+func (pl printfLogger) Error(err error, msg string, keysAndValues ...interface{}) {
+	keysAndValues = formatTimes(keysAndValues)
+	pl.logger.Printf(
+		formatString(len(keysAndValues)+2),
+		append([]interface{}{msg, "error", err}, keysAndValues...)...)
+}
+
+// formatString returns a logfmt-like format string for the number of
+// key/values.
+func formatString(numKeysAndValues int) string {
+	var sb strings.Builder
+	sb.WriteString("%s")
+	if numKeysAndValues > 0 {
+		sb.WriteString(", ")
+	}
+	for i := 0; i < numKeysAndValues/2; i++ {
+		if i > 0 {
+			sb.WriteString(", ")
+		}
+		sb.WriteString("%v=%v")
+	}
+	return sb.String()
+}
+
+// formatTimes formats any time.Time values as RFC3339.
+func formatTimes(keysAndValues []interface{}) []interface{} {
+	var formattedArgs []interface{}
+	for _, arg := range keysAndValues {
+		if t, ok := arg.(time.Time); ok {
+			arg = t.Format(time.RFC3339)
+		}
+		formattedArgs = append(formattedArgs, arg)
+	}
+	return formattedArgs
+}

+ 45 - 0
vendor/github.com/robfig/cron/v3/option.go

@@ -0,0 +1,45 @@
+package cron
+
+import (
+	"time"
+)
+
+// Option represents a modification to the default behavior of a Cron.
+type Option func(*Cron)
+
+// WithLocation overrides the timezone of the cron instance.
+func WithLocation(loc *time.Location) Option {
+	return func(c *Cron) {
+		c.location = loc
+	}
+}
+
+// WithSeconds overrides the parser used for interpreting job schedules to
+// include a seconds field as the first one.
+func WithSeconds() Option {
+	return WithParser(NewParser(
+		Second | Minute | Hour | Dom | Month | Dow | Descriptor,
+	))
+}
+
+// WithParser overrides the parser used for interpreting job schedules.
+func WithParser(p Parser) Option {
+	return func(c *Cron) {
+		c.parser = p
+	}
+}
+
+// WithChain specifies Job wrappers to apply to all jobs added to this cron.
+// Refer to the Chain* functions in this package for provided wrappers.
+func WithChain(wrappers ...JobWrapper) Option {
+	return func(c *Cron) {
+		c.chain = NewChain(wrappers...)
+	}
+}
+
+// WithLogger uses the provided logger.
+func WithLogger(logger Logger) Option {
+	return func(c *Cron) {
+		c.logger = logger
+	}
+}

+ 434 - 0
vendor/github.com/robfig/cron/v3/parser.go

@@ -0,0 +1,434 @@
+package cron
+
+import (
+	"fmt"
+	"math"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// Configuration options for creating a parser. Most options specify which
+// fields should be included, while others enable features. If a field is not
+// included the parser will assume a default value. These options do not change
+// the order fields are parse in.
+type ParseOption int
+
+const (
+	Second         ParseOption = 1 << iota // Seconds field, default 0
+	SecondOptional                         // Optional seconds field, default 0
+	Minute                                 // Minutes field, default 0
+	Hour                                   // Hours field, default 0
+	Dom                                    // Day of month field, default *
+	Month                                  // Month field, default *
+	Dow                                    // Day of week field, default *
+	DowOptional                            // Optional day of week field, default *
+	Descriptor                             // Allow descriptors such as @monthly, @weekly, etc.
+)
+
+var places = []ParseOption{
+	Second,
+	Minute,
+	Hour,
+	Dom,
+	Month,
+	Dow,
+}
+
+var defaults = []string{
+	"0",
+	"0",
+	"0",
+	"*",
+	"*",
+	"*",
+}
+
+// A custom Parser that can be configured.
+type Parser struct {
+	options ParseOption
+}
+
+// NewParser creates a Parser with custom options.
+//
+// It panics if more than one Optional is given, since it would be impossible to
+// correctly infer which optional is provided or missing in general.
+//
+// Examples
+//
+//  // Standard parser without descriptors
+//  specParser := NewParser(Minute | Hour | Dom | Month | Dow)
+//  sched, err := specParser.Parse("0 0 15 */3 *")
+//
+//  // Same as above, just excludes time fields
+//  subsParser := NewParser(Dom | Month | Dow)
+//  sched, err := specParser.Parse("15 */3 *")
+//
+//  // Same as above, just makes Dow optional
+//  subsParser := NewParser(Dom | Month | DowOptional)
+//  sched, err := specParser.Parse("15 */3")
+//
+func NewParser(options ParseOption) Parser {
+	optionals := 0
+	if options&DowOptional > 0 {
+		optionals++
+	}
+	if options&SecondOptional > 0 {
+		optionals++
+	}
+	if optionals > 1 {
+		panic("multiple optionals may not be configured")
+	}
+	return Parser{options}
+}
+
+// Parse returns a new crontab schedule representing the given spec.
+// It returns a descriptive error if the spec is not valid.
+// It accepts crontab specs and features configured by NewParser.
+func (p Parser) Parse(spec string) (Schedule, error) {
+	if len(spec) == 0 {
+		return nil, fmt.Errorf("empty spec string")
+	}
+
+	// Extract timezone if present
+	var loc = time.Local
+	if strings.HasPrefix(spec, "TZ=") || strings.HasPrefix(spec, "CRON_TZ=") {
+		var err error
+		i := strings.Index(spec, " ")
+		eq := strings.Index(spec, "=")
+		if loc, err = time.LoadLocation(spec[eq+1 : i]); err != nil {
+			return nil, fmt.Errorf("provided bad location %s: %v", spec[eq+1:i], err)
+		}
+		spec = strings.TrimSpace(spec[i:])
+	}
+
+	// Handle named schedules (descriptors), if configured
+	if strings.HasPrefix(spec, "@") {
+		if p.options&Descriptor == 0 {
+			return nil, fmt.Errorf("parser does not accept descriptors: %v", spec)
+		}
+		return parseDescriptor(spec, loc)
+	}
+
+	// Split on whitespace.
+	fields := strings.Fields(spec)
+
+	// Validate & fill in any omitted or optional fields
+	var err error
+	fields, err = normalizeFields(fields, p.options)
+	if err != nil {
+		return nil, err
+	}
+
+	field := func(field string, r bounds) uint64 {
+		if err != nil {
+			return 0
+		}
+		var bits uint64
+		bits, err = getField(field, r)
+		return bits
+	}
+
+	var (
+		second     = field(fields[0], seconds)
+		minute     = field(fields[1], minutes)
+		hour       = field(fields[2], hours)
+		dayofmonth = field(fields[3], dom)
+		month      = field(fields[4], months)
+		dayofweek  = field(fields[5], dow)
+	)
+	if err != nil {
+		return nil, err
+	}
+
+	return &SpecSchedule{
+		Second:   second,
+		Minute:   minute,
+		Hour:     hour,
+		Dom:      dayofmonth,
+		Month:    month,
+		Dow:      dayofweek,
+		Location: loc,
+	}, nil
+}
+
+// normalizeFields takes a subset set of the time fields and returns the full set
+// with defaults (zeroes) populated for unset fields.
+//
+// As part of performing this function, it also validates that the provided
+// fields are compatible with the configured options.
+func normalizeFields(fields []string, options ParseOption) ([]string, error) {
+	// Validate optionals & add their field to options
+	optionals := 0
+	if options&SecondOptional > 0 {
+		options |= Second
+		optionals++
+	}
+	if options&DowOptional > 0 {
+		options |= Dow
+		optionals++
+	}
+	if optionals > 1 {
+		return nil, fmt.Errorf("multiple optionals may not be configured")
+	}
+
+	// Figure out how many fields we need
+	max := 0
+	for _, place := range places {
+		if options&place > 0 {
+			max++
+		}
+	}
+	min := max - optionals
+
+	// Validate number of fields
+	if count := len(fields); count < min || count > max {
+		if min == max {
+			return nil, fmt.Errorf("expected exactly %d fields, found %d: %s", min, count, fields)
+		}
+		return nil, fmt.Errorf("expected %d to %d fields, found %d: %s", min, max, count, fields)
+	}
+
+	// Populate the optional field if not provided
+	if min < max && len(fields) == min {
+		switch {
+		case options&DowOptional > 0:
+			fields = append(fields, defaults[5]) // TODO: improve access to default
+		case options&SecondOptional > 0:
+			fields = append([]string{defaults[0]}, fields...)
+		default:
+			return nil, fmt.Errorf("unknown optional field")
+		}
+	}
+
+	// Populate all fields not part of options with their defaults
+	n := 0
+	expandedFields := make([]string, len(places))
+	copy(expandedFields, defaults)
+	for i, place := range places {
+		if options&place > 0 {
+			expandedFields[i] = fields[n]
+			n++
+		}
+	}
+	return expandedFields, nil
+}
+
+var standardParser = NewParser(
+	Minute | Hour | Dom | Month | Dow | Descriptor,
+)
+
+// ParseStandard returns a new crontab schedule representing the given
+// standardSpec (https://en.wikipedia.org/wiki/Cron). It requires 5 entries
+// representing: minute, hour, day of month, month and day of week, in that
+// order. It returns a descriptive error if the spec is not valid.
+//
+// It accepts
+//   - Standard crontab specs, e.g. "* * * * ?"
+//   - Descriptors, e.g. "@midnight", "@every 1h30m"
+func ParseStandard(standardSpec string) (Schedule, error) {
+	return standardParser.Parse(standardSpec)
+}
+
+// getField returns an Int with the bits set representing all of the times that
+// the field represents or error parsing field value.  A "field" is a comma-separated
+// list of "ranges".
+func getField(field string, r bounds) (uint64, error) {
+	var bits uint64
+	ranges := strings.FieldsFunc(field, func(r rune) bool { return r == ',' })
+	for _, expr := range ranges {
+		bit, err := getRange(expr, r)
+		if err != nil {
+			return bits, err
+		}
+		bits |= bit
+	}
+	return bits, nil
+}
+
+// getRange returns the bits indicated by the given expression:
+//   number | number "-" number [ "/" number ]
+// or error parsing range.
+func getRange(expr string, r bounds) (uint64, error) {
+	var (
+		start, end, step uint
+		rangeAndStep     = strings.Split(expr, "/")
+		lowAndHigh       = strings.Split(rangeAndStep[0], "-")
+		singleDigit      = len(lowAndHigh) == 1
+		err              error
+	)
+
+	var extra uint64
+	if lowAndHigh[0] == "*" || lowAndHigh[0] == "?" {
+		start = r.min
+		end = r.max
+		extra = starBit
+	} else {
+		start, err = parseIntOrName(lowAndHigh[0], r.names)
+		if err != nil {
+			return 0, err
+		}
+		switch len(lowAndHigh) {
+		case 1:
+			end = start
+		case 2:
+			end, err = parseIntOrName(lowAndHigh[1], r.names)
+			if err != nil {
+				return 0, err
+			}
+		default:
+			return 0, fmt.Errorf("too many hyphens: %s", expr)
+		}
+	}
+
+	switch len(rangeAndStep) {
+	case 1:
+		step = 1
+	case 2:
+		step, err = mustParseInt(rangeAndStep[1])
+		if err != nil {
+			return 0, err
+		}
+
+		// Special handling: "N/step" means "N-max/step".
+		if singleDigit {
+			end = r.max
+		}
+		if step > 1 {
+			extra = 0
+		}
+	default:
+		return 0, fmt.Errorf("too many slashes: %s", expr)
+	}
+
+	if start < r.min {
+		return 0, fmt.Errorf("beginning of range (%d) below minimum (%d): %s", start, r.min, expr)
+	}
+	if end > r.max {
+		return 0, fmt.Errorf("end of range (%d) above maximum (%d): %s", end, r.max, expr)
+	}
+	if start > end {
+		return 0, fmt.Errorf("beginning of range (%d) beyond end of range (%d): %s", start, end, expr)
+	}
+	if step == 0 {
+		return 0, fmt.Errorf("step of range should be a positive number: %s", expr)
+	}
+
+	return getBits(start, end, step) | extra, nil
+}
+
+// parseIntOrName returns the (possibly-named) integer contained in expr.
+func parseIntOrName(expr string, names map[string]uint) (uint, error) {
+	if names != nil {
+		if namedInt, ok := names[strings.ToLower(expr)]; ok {
+			return namedInt, nil
+		}
+	}
+	return mustParseInt(expr)
+}
+
+// mustParseInt parses the given expression as an int or returns an error.
+func mustParseInt(expr string) (uint, error) {
+	num, err := strconv.Atoi(expr)
+	if err != nil {
+		return 0, fmt.Errorf("failed to parse int from %s: %s", expr, err)
+	}
+	if num < 0 {
+		return 0, fmt.Errorf("negative number (%d) not allowed: %s", num, expr)
+	}
+
+	return uint(num), nil
+}
+
+// getBits sets all bits in the range [min, max], modulo the given step size.
+func getBits(min, max, step uint) uint64 {
+	var bits uint64
+
+	// If step is 1, use shifts.
+	if step == 1 {
+		return ^(math.MaxUint64 << (max + 1)) & (math.MaxUint64 << min)
+	}
+
+	// Else, use a simple loop.
+	for i := min; i <= max; i += step {
+		bits |= 1 << i
+	}
+	return bits
+}
+
+// all returns all bits within the given bounds.  (plus the star bit)
+func all(r bounds) uint64 {
+	return getBits(r.min, r.max, 1) | starBit
+}
+
+// parseDescriptor returns a predefined schedule for the expression, or error if none matches.
+func parseDescriptor(descriptor string, loc *time.Location) (Schedule, error) {
+	switch descriptor {
+	case "@yearly", "@annually":
+		return &SpecSchedule{
+			Second:   1 << seconds.min,
+			Minute:   1 << minutes.min,
+			Hour:     1 << hours.min,
+			Dom:      1 << dom.min,
+			Month:    1 << months.min,
+			Dow:      all(dow),
+			Location: loc,
+		}, nil
+
+	case "@monthly":
+		return &SpecSchedule{
+			Second:   1 << seconds.min,
+			Minute:   1 << minutes.min,
+			Hour:     1 << hours.min,
+			Dom:      1 << dom.min,
+			Month:    all(months),
+			Dow:      all(dow),
+			Location: loc,
+		}, nil
+
+	case "@weekly":
+		return &SpecSchedule{
+			Second:   1 << seconds.min,
+			Minute:   1 << minutes.min,
+			Hour:     1 << hours.min,
+			Dom:      all(dom),
+			Month:    all(months),
+			Dow:      1 << dow.min,
+			Location: loc,
+		}, nil
+
+	case "@daily", "@midnight":
+		return &SpecSchedule{
+			Second:   1 << seconds.min,
+			Minute:   1 << minutes.min,
+			Hour:     1 << hours.min,
+			Dom:      all(dom),
+			Month:    all(months),
+			Dow:      all(dow),
+			Location: loc,
+		}, nil
+
+	case "@hourly":
+		return &SpecSchedule{
+			Second:   1 << seconds.min,
+			Minute:   1 << minutes.min,
+			Hour:     all(hours),
+			Dom:      all(dom),
+			Month:    all(months),
+			Dow:      all(dow),
+			Location: loc,
+		}, nil
+
+	}
+
+	const every = "@every "
+	if strings.HasPrefix(descriptor, every) {
+		duration, err := time.ParseDuration(descriptor[len(every):])
+		if err != nil {
+			return nil, fmt.Errorf("failed to parse duration %s: %s", descriptor, err)
+		}
+		return Every(duration), nil
+	}
+
+	return nil, fmt.Errorf("unrecognized descriptor: %s", descriptor)
+}

+ 188 - 0
vendor/github.com/robfig/cron/v3/spec.go

@@ -0,0 +1,188 @@
+package cron
+
+import "time"
+
+// SpecSchedule specifies a duty cycle (to the second granularity), based on a
+// traditional crontab specification. It is computed initially and stored as bit sets.
+type SpecSchedule struct {
+	Second, Minute, Hour, Dom, Month, Dow uint64
+
+	// Override location for this schedule.
+	Location *time.Location
+}
+
+// bounds provides a range of acceptable values (plus a map of name to value).
+type bounds struct {
+	min, max uint
+	names    map[string]uint
+}
+
+// The bounds for each field.
+var (
+	seconds = bounds{0, 59, nil}
+	minutes = bounds{0, 59, nil}
+	hours   = bounds{0, 23, nil}
+	dom     = bounds{1, 31, nil}
+	months  = bounds{1, 12, map[string]uint{
+		"jan": 1,
+		"feb": 2,
+		"mar": 3,
+		"apr": 4,
+		"may": 5,
+		"jun": 6,
+		"jul": 7,
+		"aug": 8,
+		"sep": 9,
+		"oct": 10,
+		"nov": 11,
+		"dec": 12,
+	}}
+	dow = bounds{0, 6, map[string]uint{
+		"sun": 0,
+		"mon": 1,
+		"tue": 2,
+		"wed": 3,
+		"thu": 4,
+		"fri": 5,
+		"sat": 6,
+	}}
+)
+
+const (
+	// Set the top bit if a star was included in the expression.
+	starBit = 1 << 63
+)
+
+// Next returns the next time this schedule is activated, greater than the given
+// time.  If no time can be found to satisfy the schedule, return the zero time.
+func (s *SpecSchedule) Next(t time.Time) time.Time {
+	// General approach
+	//
+	// For Month, Day, Hour, Minute, Second:
+	// Check if the time value matches.  If yes, continue to the next field.
+	// If the field doesn't match the schedule, then increment the field until it matches.
+	// While incrementing the field, a wrap-around brings it back to the beginning
+	// of the field list (since it is necessary to re-verify previous field
+	// values)
+
+	// Convert the given time into the schedule's timezone, if one is specified.
+	// Save the original timezone so we can convert back after we find a time.
+	// Note that schedules without a time zone specified (time.Local) are treated
+	// as local to the time provided.
+	origLocation := t.Location()
+	loc := s.Location
+	if loc == time.Local {
+		loc = t.Location()
+	}
+	if s.Location != time.Local {
+		t = t.In(s.Location)
+	}
+
+	// Start at the earliest possible time (the upcoming second).
+	t = t.Add(1*time.Second - time.Duration(t.Nanosecond())*time.Nanosecond)
+
+	// This flag indicates whether a field has been incremented.
+	added := false
+
+	// If no time is found within five years, return zero.
+	yearLimit := t.Year() + 5
+
+WRAP:
+	if t.Year() > yearLimit {
+		return time.Time{}
+	}
+
+	// Find the first applicable month.
+	// If it's this month, then do nothing.
+	for 1<<uint(t.Month())&s.Month == 0 {
+		// If we have to add a month, reset the other parts to 0.
+		if !added {
+			added = true
+			// Otherwise, set the date at the beginning (since the current time is irrelevant).
+			t = time.Date(t.Year(), t.Month(), 1, 0, 0, 0, 0, loc)
+		}
+		t = t.AddDate(0, 1, 0)
+
+		// Wrapped around.
+		if t.Month() == time.January {
+			goto WRAP
+		}
+	}
+
+	// Now get a day in that month.
+	//
+	// NOTE: This causes issues for daylight savings regimes where midnight does
+	// not exist.  For example: Sao Paulo has DST that transforms midnight on
+	// 11/3 into 1am. Handle that by noticing when the Hour ends up != 0.
+	for !dayMatches(s, t) {
+		if !added {
+			added = true
+			t = time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, loc)
+		}
+		t = t.AddDate(0, 0, 1)
+		// Notice if the hour is no longer midnight due to DST.
+		// Add an hour if it's 23, subtract an hour if it's 1.
+		if t.Hour() != 0 {
+			if t.Hour() > 12 {
+				t = t.Add(time.Duration(24-t.Hour()) * time.Hour)
+			} else {
+				t = t.Add(time.Duration(-t.Hour()) * time.Hour)
+			}
+		}
+
+		if t.Day() == 1 {
+			goto WRAP
+		}
+	}
+
+	for 1<<uint(t.Hour())&s.Hour == 0 {
+		if !added {
+			added = true
+			t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), 0, 0, 0, loc)
+		}
+		t = t.Add(1 * time.Hour)
+
+		if t.Hour() == 0 {
+			goto WRAP
+		}
+	}
+
+	for 1<<uint(t.Minute())&s.Minute == 0 {
+		if !added {
+			added = true
+			t = t.Truncate(time.Minute)
+		}
+		t = t.Add(1 * time.Minute)
+
+		if t.Minute() == 0 {
+			goto WRAP
+		}
+	}
+
+	for 1<<uint(t.Second())&s.Second == 0 {
+		if !added {
+			added = true
+			t = t.Truncate(time.Second)
+		}
+		t = t.Add(1 * time.Second)
+
+		if t.Second() == 0 {
+			goto WRAP
+		}
+	}
+
+	return t.In(origLocation)
+}
+
+// dayMatches returns true if the schedule's day-of-week and day-of-month
+// restrictions are satisfied by the given time.
+func dayMatches(s *SpecSchedule, t time.Time) bool {
+	var (
+		domMatch bool = 1<<uint(t.Day())&s.Dom > 0
+		dowMatch bool = 1<<uint(t.Weekday())&s.Dow > 0
+	)
+	if s.Dom&starBit > 0 || s.Dow&starBit > 0 {
+		return domMatch && dowMatch
+	}
+	return domMatch || dowMatch
+}

+ 4 - 0
vendor/modules.txt

@@ -134,6 +134,8 @@ github.com/jmespath/go-jmespath
 github.com/jonboulle/clockwork
 # github.com/jtolds/gls v4.20.0+incompatible
 github.com/jtolds/gls
+# github.com/jung-kurt/gofpdf v1.10.1
+github.com/jung-kurt/gofpdf
 # github.com/klauspost/compress v1.4.1
 github.com/klauspost/compress/gzip
 github.com/klauspost/compress/flate
@@ -186,6 +188,8 @@ github.com/prometheus/procfs/internal/util
 github.com/rainycape/unidecode
 # github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967
 github.com/robfig/cron
+# github.com/robfig/cron/v3 v3.0.0
+github.com/robfig/cron/v3
 # github.com/russellhaering/goxmldsig v0.0.0-20180430223755-7acd5e4a6ef7
 github.com/russellhaering/goxmldsig
 github.com/russellhaering/goxmldsig/etreeutils