From 21876dc015a29d7990a24edebcc866c0d7b12780 Mon Sep 17 00:00:00 2001 From: Javier Uruen Val Date: Fri, 4 Jan 2019 15:24:42 +0100 Subject: [PATCH 1/2] vendorize gofpdf --- vendor/github.com/jung-kurt/gofpdf/LICENSE | 21 + vendor/github.com/jung-kurt/gofpdf/README.md | 235 + .../jung-kurt/gofpdf/README.md.template | 9 + vendor/github.com/jung-kurt/gofpdf/check | 3 + vendor/github.com/jung-kurt/gofpdf/compare.go | 146 + vendor/github.com/jung-kurt/gofpdf/cov | 1 + vendor/github.com/jung-kurt/gofpdf/def.go | 719 +++ vendor/github.com/jung-kurt/gofpdf/doc.go | 251 + .../github.com/jung-kurt/gofpdf/embedded.go | 559 +++ vendor/github.com/jung-kurt/gofpdf/font.go | 472 ++ vendor/github.com/jung-kurt/gofpdf/fpdf.go | 4088 +++++++++++++++++ .../github.com/jung-kurt/gofpdf/fpdftrans.go | 213 + vendor/github.com/jung-kurt/gofpdf/go.mod | 1 + vendor/github.com/jung-kurt/gofpdf/grid.go | 446 ++ .../github.com/jung-kurt/gofpdf/htmlbasic.go | 220 + vendor/github.com/jung-kurt/gofpdf/label.go | 82 + vendor/github.com/jung-kurt/gofpdf/layer.go | 121 + vendor/github.com/jung-kurt/gofpdf/mkdoc | 23 + vendor/github.com/jung-kurt/gofpdf/png.go | 213 + vendor/github.com/jung-kurt/gofpdf/protect.go | 114 + .../github.com/jung-kurt/gofpdf/spotcolor.go | 184 + .../github.com/jung-kurt/gofpdf/subwrite.go | 35 + .../github.com/jung-kurt/gofpdf/svgbasic.go | 210 + .../github.com/jung-kurt/gofpdf/svgwrite.go | 58 + .../github.com/jung-kurt/gofpdf/template.go | 273 ++ .../jung-kurt/gofpdf/template_impl.go | 299 ++ .../github.com/jung-kurt/gofpdf/ttfparser.go | 374 ++ vendor/github.com/jung-kurt/gofpdf/util.go | 316 ++ vendor/vendor.json | 6 + 29 files changed, 9692 insertions(+) create mode 100644 vendor/github.com/jung-kurt/gofpdf/LICENSE create mode 100644 vendor/github.com/jung-kurt/gofpdf/README.md create mode 100644 vendor/github.com/jung-kurt/gofpdf/README.md.template create mode 100755 vendor/github.com/jung-kurt/gofpdf/check create mode 100644 vendor/github.com/jung-kurt/gofpdf/compare.go create mode 100755 vendor/github.com/jung-kurt/gofpdf/cov create mode 100644 vendor/github.com/jung-kurt/gofpdf/def.go create mode 100644 vendor/github.com/jung-kurt/gofpdf/doc.go create mode 100644 vendor/github.com/jung-kurt/gofpdf/embedded.go create mode 100644 vendor/github.com/jung-kurt/gofpdf/font.go create mode 100644 vendor/github.com/jung-kurt/gofpdf/fpdf.go create mode 100644 vendor/github.com/jung-kurt/gofpdf/fpdftrans.go create mode 100644 vendor/github.com/jung-kurt/gofpdf/go.mod create mode 100644 vendor/github.com/jung-kurt/gofpdf/grid.go create mode 100644 vendor/github.com/jung-kurt/gofpdf/htmlbasic.go create mode 100644 vendor/github.com/jung-kurt/gofpdf/label.go create mode 100644 vendor/github.com/jung-kurt/gofpdf/layer.go create mode 100755 vendor/github.com/jung-kurt/gofpdf/mkdoc create mode 100644 vendor/github.com/jung-kurt/gofpdf/png.go create mode 100644 vendor/github.com/jung-kurt/gofpdf/protect.go create mode 100644 vendor/github.com/jung-kurt/gofpdf/spotcolor.go create mode 100644 vendor/github.com/jung-kurt/gofpdf/subwrite.go create mode 100644 vendor/github.com/jung-kurt/gofpdf/svgbasic.go create mode 100644 vendor/github.com/jung-kurt/gofpdf/svgwrite.go create mode 100644 vendor/github.com/jung-kurt/gofpdf/template.go create mode 100644 vendor/github.com/jung-kurt/gofpdf/template_impl.go create mode 100644 vendor/github.com/jung-kurt/gofpdf/ttfparser.go create mode 100644 vendor/github.com/jung-kurt/gofpdf/util.go diff --git a/vendor/github.com/jung-kurt/gofpdf/LICENSE b/vendor/github.com/jung-kurt/gofpdf/LICENSE new file mode 100644 index 00000000..946bb79e --- /dev/null +++ b/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. diff --git a/vendor/github.com/jung-kurt/gofpdf/README.md b/vendor/github.com/jung-kurt/gofpdf/README.md new file mode 100644 index 00000000..c26dfcb9 --- /dev/null +++ b/vendor/github.com/jung-kurt/gofpdf/README.md @@ -0,0 +1,235 @@ +# gofpdf + +![gofpdf](image/logo_gofpdf.jpg?raw=true "gofpdf") + +[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/jung-kurt/gofpdf/master/license.txt) +[![GoDoc](https://godoc.org/github.com/jung-kurt/gofpdf?status.svg)](https://godoc.org/github.com/jung-kurt/gofpdf) +[![Build Status](https://travis-ci.org/jung-kurt/gofpdf.svg?branch=master)](https://travis-ci.org/jung-kurt/gofpdf) + +Package gofpdf implements a PDF document generator with high level support for +text, drawing and images. + +## Features + +* 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 + +gofpdf has no dependencies other than the Go standard library. All tests pass +on Linux, Mac and Windows platforms. + +Like FPDF version 1.7, from which gofpdf is derived, this package does not yet +support UTF-8 fonts. In particular, languages that require more than one code +page such as Chinese, Japanese, and Arabic are not currently supported. This is +explained in [issue 109](https://github.com/jung-kurt/gofpdf/issues/109). However, 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](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(). + +In order to use a different 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](http://www.google.com/fonts/) 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://godoc.org/golang.org/x/tools/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/) work +nicely as a means of contributing 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. + +## Roadmap + +* Handle UTF-8 source text natively. Until then, automatic translation of +UTF-8 runes to code page bytes is provided. +* Improve test coverage as reported by the coverage tool. + + diff --git a/vendor/github.com/jung-kurt/gofpdf/README.md.template b/vendor/github.com/jung-kurt/gofpdf/README.md.template new file mode 100644 index 00000000..8caf5726 --- /dev/null +++ b/vendor/github.com/jung-kurt/gofpdf/README.md.template @@ -0,0 +1,9 @@ +# gofpdf + +![gofpdf](image/logo_gofpdf.jpg?raw=true "gofpdf") + +[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/jung-kurt/gofpdf/master/license.txt) +[![GoDoc](https://godoc.org/github.com/jung-kurt/gofpdf?status.svg)](https://godoc.org/github.com/jung-kurt/gofpdf) +[![Build Status](https://travis-ci.org/jung-kurt/gofpdf.svg?branch=master)](https://travis-ci.org/jung-kurt/gofpdf) + +{{.Doc}} diff --git a/vendor/github.com/jung-kurt/gofpdf/check b/vendor/github.com/jung-kurt/gofpdf/check new file mode 100755 index 00000000..86a5bc67 --- /dev/null +++ b/vendor/github.com/jung-kurt/gofpdf/check @@ -0,0 +1,3 @@ +golint . +go tool vet -all . +gofmt -s -l . diff --git a/vendor/github.com/jung-kurt/gofpdf/compare.go b/vendor/github.com/jung-kurt/gofpdf/compare.go new file mode 100644 index 00000000..f0c286e4 --- /dev/null +++ b/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 +} diff --git a/vendor/github.com/jung-kurt/gofpdf/cov b/vendor/github.com/jung-kurt/gofpdf/cov new file mode 100755 index 00000000..a07edced --- /dev/null +++ b/vendor/github.com/jung-kurt/gofpdf/cov @@ -0,0 +1 @@ +go test -coverprofile=coverage && go tool cover -html=coverage diff --git a/vendor/github.com/jung-kurt/gofpdf/def.go b/vendor/github.com/jung-kurt/gofpdf/def.go new file mode 100644 index 00000000..f19e86b2 --- /dev/null +++ b/vendor/github.com/jung-kurt/gofpdf/def.go @@ -0,0 +1,719 @@ +/* + * 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 +} + +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) + Arc(x, y, rx, ry, degRotate, degStart, degEnd float64, styleStr string) + ArcTo(x, y, rx, ry, degRotate, degStart, degEnd float64) + BeginLayer(id int) + Beziergon(points []PointType, styleStr string) + Bookmark(txtStr string, level int, y float64) + Cell(w, h float64, txtStr string) + 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{}) + 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() + CreateTemplate(fn func(*Tpl)) Template + CreateTemplateCustom(corner PointType, size SizeType, fn func(*Tpl)) Template + Curve(x0, y0, cx, cy, x1, y1 float64, styleStr string) + CurveBezierCubic(x0, y0, cx0, cy0, cx1, cy1, x1, y1 float64, styleStr string) + CurveBezierCubicTo(cx0, cy0, cx1, cy1, x, y float64) + CurveCubic(x0, y0, cx0, cy0, x1, y1, cx1, cy1 float64, styleStr string) + CurveTo(cx, cy, x, y float64) + 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) + GetPageSize() (width, height float64) + GetPageSizeStr(sizeStr string) (size SizeType) + 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) + Line(x1, y1, x2, y2 float64) + LineTo(x, y float64) + LinearGradient(x, y, w, h float64, r1, g1, b1, r2, g2, b2 int, x1, y1, x2, y2 float64) + Link(x, y, w, h float64, link int) + LinkString(x, y, w, h float64, linkStr string) + Ln(h float64) + MoveTo(x, y float64) + MultiCell(w, h float64, txtStr, borderStr, alignStr string, fill bool) + Ok() bool + OpenLayerPane() + Output(w io.Writer) error + OutputAndClose(w io.WriteCloser) error + OutputFileAndClose(fileStr string) error + 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) + SVGBasicWrite(sb *SVGBasicType, scale float64) + 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) + 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) + 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) + SetX(x float64) + SetXY(x, y float64) + SetXmpMetadata(xmpStream []byte) + SetY(y float64) + SplitLines(txt []byte, w float64) [][]byte + String() string + Text(x, y float64, txtStr string) + Transform(tm TransformMatrix) + 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) + TransformTranslate(tx, ty float64) + TransformTranslateX(tx float64) + TransformTranslateY(ty float64) + UnicodeTranslatorFromDescriptor(cpStr string) (rep func(string) string) + UnitToPointConvert(u float64) (pt float64) + UseTemplate(t Template) + UseTemplateScaled(t Template, corner PointType, size SizeType) + Write(h float64, txtStr string) + WriteAligned(width, lineHeight float64, textStr, alignStr string) + WriteLinkID(h float64, displayStr string, linkID int) + WriteLinkString(h float64, displayStr, targetStr string) + Writef(h float64, fmtStr string, args ...interface{}) +} + +// 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 { + 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 + 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 + 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 +} + +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 [256]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 +} + +// 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 [256]int + Size1, Size2 uint32 + Desc FontDescType +} diff --git a/vendor/github.com/jung-kurt/gofpdf/doc.go b/vendor/github.com/jung-kurt/gofpdf/doc.go new file mode 100644 index 00000000..deb92276 --- /dev/null +++ b/vendor/github.com/jung-kurt/gofpdf/doc.go @@ -0,0 +1,251 @@ +/* + * Copyright (c) 2013-2017 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 implements a PDF document generator with high level support for +text, drawing and images. + +Features + +• 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 + +gofpdf has no dependencies other than the Go standard library. All tests pass +on Linux, Mac and Windows platforms. + +Like FPDF version 1.7, from which gofpdf is derived, this package does not yet +support UTF-8 fonts. In particular, languages that require more than one code +page such as Chinese, Japanese, and Arabic are not currently supported. This is +explained in issue 109. However, 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(). + +In order to use a different 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 http://www.google.com/fonts/ and http://dejavu-fonts.org/. + +Related Packages + +The draw2d package (https://github.com/llgcode/draw2d) 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 (https://github.com/golang/lint) and +go vet (https://godoc.org/golang.org/x/tools/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/) work +nicely as a means of contributing 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 +(http://www.fpdf.org/) 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. + +Roadmap + +• Handle UTF-8 source text natively. Until then, automatic translation of +UTF-8 runes to code page bytes is provided. + +• Improve test coverage as reported by the coverage tool. + +*/ +package gofpdf diff --git a/vendor/github.com/jung-kurt/gofpdf/embedded.go b/vendor/github.com/jung-kurt/gofpdf/embedded.go new file mode 100644 index 00000000..1d947b03 --- /dev/null +++ b/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 + `, +} diff --git a/vendor/github.com/jung-kurt/gofpdf/font.go b/vendor/github.com/jung-kurt/gofpdf/font.go new file mode 100644 index 00000000..7443a304 --- /dev/null +++ b/vendor/github.com/jung-kurt/gofpdf/font.go @@ -0,0 +1,472 @@ +/* + * 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) { + 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) { + 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 +} diff --git a/vendor/github.com/jung-kurt/gofpdf/fpdf.go b/vendor/github.com/jung-kurt/gofpdf/fpdf.go new file mode 100644 index 00000000..bc047b11 --- /dev/null +++ b/vendor/github.com/jung-kurt/gofpdf/fpdf.go @@ -0,0 +1,4088 @@ +/* + * 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.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.layerInit() + f.catalogSort = gl.catalogSort + f.creationDate = gl.creationDate + 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}}) +} + +// 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. +// +// 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 +} + +// 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 +} + +// 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.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) { + if r != f.color.draw.ir || g != f.color.draw.ig || b != f.color.draw.ib { + 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) { + if r != f.color.fill.ir || g != f.color.fill.ig || b != f.color.fill.ib { + 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) { + if r != f.color.text.ir || g != f.color.text.ig || b != f.color.text.ib { + 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 := 0 + for _, ch := range []byte(s) { + if ch == 0 { + break + } + w += f.currentFont.Cw[ch] + } + return float64(w) * f.fontSize / 1000 +} + +// 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) { + if f.lineWidth != width { + 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 + } + if capStyle != f.capStyle { + 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 + } + if joinStyle != f.joinStyle { + 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 + if !slicesEqual(scaled, f.dashArray) || dashPhase != f.dashPhase { + 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)) +} + +// 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 || (alpha == f.alpha && blendModeStr == f.blendMode) { + 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) { + if fileStr == "" { + fileStr = strings.Replace(familyStr, " ", "", -1) + strings.ToLower(styleStr) + ".json" + } + + 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) +} + +// 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) { + if f.err != nil { + return + } + + // load font key + var ok bool + fontkey := getFontKey(familyStr, styleStr) + _, ok = f.fonts[fontkey] + + if ok { + return + } + + // 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 selected + if f.fontFamily == familyStr && f.fontStyle == styleStr && f.fontSizePt == size { + return + } + // 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.page > 0 { + f.outf("BT /F%s %.2f Tf ET", f.currentFont.i, f.fontSizePt) + } + return +} + +// 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) { + if f.fontSizePt == size { + return + } + 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) { + if f.fontSize == size { + return + } + 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) { + s := sprintf("BT %.2f %.2f Td (%s) Tj ET", x*f.k, (f.h-y)*f.k, f.escape(txtStr)) + 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) +} + +// 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) + } + txt2 := strings.Replace(txtStr, "\\", "\\\\", -1) + txt2 = strings.Replace(txt2, "(", "\\(", -1) + txt2 = strings.Replace(txt2, ")", "\\)", -1) + // if strings.Contains(txt2, "end of excerpt") { + // dbg("f.h %.2f, f.y %.2f, h %.2f, f.fontSize %.2f, k %.2f", f.h, f.y, h, f.fontSize, k) + // } + s.printf("BT %.2f %.2f Td (%s) Tj ET", (f.x+dx)*k, (f.h-(f.y+dy+.5*h+.3*f.fontSize))*k, txt2) + //BT %.2F %.2F Td (%s) Tj ET',($this->x+$dx)*$k,($this->h-($this->y+.5*$h+.3*$this->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 +} + +// 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. +// +// 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) { + // 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) + nb := len(s) + // if nb > 0 && s[nb-1:nb] == "\n" { + if nb > 0 && []byte(s)[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 + c := []byte(s)[i] + if c == '\n' { + // Explicit line break + if f.ws > 0 { + f.ws = 0 + f.out("0 Tw") + } + 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 == ' ' { + sep = i + ls = l + ns++ + } + l += cw[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") + } + 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) + } + 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" + } + 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) + nb := len(s) + sep := -1 + i := 0 + j := 0 + l := 0.0 + nl := 1 + for i < nb { + // Get next character + c := []byte(s)[i] + if c == '\n' { + // Explicit line break + 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[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++ + } + f.CellFormat(w, h, s[j:i], "", 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 { + 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() + + if width == 0 { + pageWidth, _ := f.GetPageSize() + width = pageWidth - (lMargin + rMargin) + } + + lines := f.SplitLines([]byte(textStr), width) + + 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) - rMargin) + f.Write(lineHeight, lineStr) + f.SetLeftMargin(lMargin) + default: + f.Write(lineHeight, lineStr) + } + } +} + +// 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] +} + +// 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 +} + +// Underline text +func (f *Fpdf) dounderline(x, y float64, txt string) string { + up := float64(f.currentFont.Up) + ut := float64(f.currentFont.Ut) + 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 + 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("< 0 { + var annots fmtBuffer + annots.printf("/Annots [") + for _, pl := range f.pageLinks[n] { + annots.printf("<>>>", 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 <>") + } + 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("<>", len(data)) + f.putstream(data) + } else { + f.outf("<>", 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("<>") + 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("<>", 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] + // Font file embedding + 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 + } + } + + // dbg("font file [%s], ext [%s]", file, file[len(file)-2:]) + 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("< 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("<>") + f.out("endobj") + case "Type1": + fallthrough + case "TrueType": + // Additional Type1 or TrueType/OpenType font + f.newobj() + f.out("< 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("<>", suffix, f.fontFiles[font.File].n) + f.out(s.String()) + f.out("endobj") + default: + f.err = fmt.Errorf("unsupported font type: %s", tp) + return + } + } + } + return +} + +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("< 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("<>", len(pal)) + f.putstream(pal) + } else { + f.outf("<>", 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) + } + } + } +} + +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("<>", + 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("<>", gr.clr1Str, gr.clr2Str) + f.out("endobj") + f1 = f.n + } + f.newobj() + f.outf("<>", + 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() + // 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 + f.outf("/Producer %s", f.textstring("FPDF "+cnFpdfVersion)) + 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 <>", 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("<>") + f.out("endobj") + } + f.newobj() + f.outlineRoot = f.n + f.outf("<>", 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") + } +} diff --git a/vendor/github.com/jung-kurt/gofpdf/fpdftrans.go b/vendor/github.com/jung-kurt/gofpdf/fpdftrans.go new file mode 100644 index 00000000..9cda3977 --- /dev/null +++ b/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") + } +} diff --git a/vendor/github.com/jung-kurt/gofpdf/go.mod b/vendor/github.com/jung-kurt/gofpdf/go.mod new file mode 100644 index 00000000..c7a180d3 --- /dev/null +++ b/vendor/github.com/jung-kurt/gofpdf/go.mod @@ -0,0 +1 @@ +module github.com/jung-kurt/gofpdf diff --git a/vendor/github.com/jung-kurt/gofpdf/grid.go b/vendor/github.com/jung-kurt/gofpdf/grid.go new file mode 100644 index 00000000..aca8508c --- /dev/null +++ b/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)) + } + } +} diff --git a/vendor/github.com/jung-kurt/gofpdf/htmlbasic.go b/vendor/github.com/jung-kurt/gofpdf/htmlbasic.go new file mode 100644 index 00000000..9e4eca4e --- /dev/null +++ b/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" + } + } + } +} diff --git a/vendor/github.com/jung-kurt/gofpdf/label.go b/vendor/github.com/jung-kurt/gofpdf/label.go new file mode 100644 index 00000000..b90d8f32 --- /dev/null +++ b/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 +} diff --git a/vendor/github.com/jung-kurt/gofpdf/layer.go b/vendor/github.com/jung-kurt/gofpdf/layer.go new file mode 100644 index 00000000..bca364f9 --- /dev/null +++ b/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("<>", 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 <>>>", onStr, offStr, onStr) + if f.layer.openLayerPane { + f.out("/PageMode /UseOC") + } + } +} diff --git a/vendor/github.com/jung-kurt/gofpdf/mkdoc b/vendor/github.com/jung-kurt/gofpdf/mkdoc new file mode 100755 index 00000000..8c082c39 --- /dev/null +++ b/vendor/github.com/jung-kurt/gofpdf/mkdoc @@ -0,0 +1,23 @@ +#!/bin/bash + +# https://github.com/jimmyfrasche/autoreadme +autoreadme -f -template README.md.template +# Improve the appearance of the markdown document with features unavailable in godoc +cat README.md | tr '\n' '\v' | sed \ + -e 's/\v##\([^\v]*\)/\v## \1\v\v/g' \ + -e 's/\v• /* /g' \ + -e 's/\(http:\/\/www\.google\.com\/fonts\/\)/[Google Fonts](\1)/g' \ + -e 's/\(http:\/\/dejavu-fonts\.org\/\)/[DejaVu Fonts](\1)/g' \ + -e 's/draw2d.package.(\(https:\/\/github\.com\/llgcode\/draw2d\))/[draw2d](\1) package/g' \ + -e 's/FPDF.library.(\(http:\/\/www\.fpdf\.org\/\))/[FPDF](\1) library/g' \ + -e 's/original.FPDF.library/original [FPDF](http:\/\/www.fpdf.org\/) library/g' \ + -e 's/\(Effective.Go\)/[\1](https:\/\/golang.org\/doc\/effective_go.html)/g' \ + -e 's/\(fpdf_test.go\)/[\1](https:\/\/github.com\/jung-kurt\/gofpdf\/blob\/master\/fpdf_test.go)/g' \ + -e 's/golint.(\(https:\/\/github\.com\/golang\/lint\))/[golint](\1)/g' \ + -e 's/go.vet.(\(https:\/\/godoc\.org\/golang\.org\/x\/tools\/cmd\/vet\))/[go vet](\1)/g' \ + -e 's/test.coverage.(\(https:\/\/blog\.golang\.org\/cover\))/[test coverage](\1)/g' \ + -e 's/Pull.requests.(\(https:\/\/help\.github\.com\/articles\/using\-pull\-requests\/\))/[Pull requests](\1)/g' \ + -e 's/Your change should\v/Your change should\v\v/g' \ + -e 's/issue 109/[issue 109](https:\/\/github.com\/jung-kurt\/gofpdf\/issues\/109)/g' \ + | tr '\v' '\n' > _0 +mv _0 README.md diff --git a/vendor/github.com/jung-kurt/gofpdf/png.go b/vendor/github.com/jung-kurt/gofpdf/png.go new file mode 100644 index 00000000..15002b3f --- /dev/null +++ b/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 +} diff --git a/vendor/github.com/jung-kurt/gofpdf/protect.go b/vendor/github.com/jung-kurt/gofpdf/protect.go new file mode 100644 index 00000000..9a335345 --- /dev/null +++ b/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) +} diff --git a/vendor/github.com/jung-kurt/gofpdf/spotcolor.go b/vendor/github.com/jung-kurt/gofpdf/spotcolor.go new file mode 100644 index 00000000..33aca827 --- /dev/null +++ b/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(">>") +} diff --git a/vendor/github.com/jung-kurt/gofpdf/subwrite.go b/vendor/github.com/jung-kurt/gofpdf/subwrite.go new file mode 100644 index 00000000..e7ebd774 --- /dev/null +++ b/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) +} diff --git a/vendor/github.com/jung-kurt/gofpdf/svgbasic.go b/vendor/github.com/jung-kurt/gofpdf/svgbasic.go new file mode 100644 index 00000000..cb8e91e1 --- /dev/null +++ b/vendor/github.com/jung-kurt/gofpdf/svgbasic.go @@ -0,0 +1,210 @@ +/* + * 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 ") +} + +// 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] + } + } +} + +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 'L', 'l': // Absolute/relative lineto: x, y + setup(2) + 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), and 'C' (absolute cubic Bézier curve: cx0, cy0, cx1, cy1, +// x1,y1). +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 +} diff --git a/vendor/github.com/jung-kurt/gofpdf/svgwrite.go b/vendor/github.com/jung-kurt/gofpdf/svgwrite.go new file mode 100644 index 00000000..04f6cb3c --- /dev/null +++ b/vendor/github.com/jung-kurt/gofpdf/svgwrite.go @@ -0,0 +1,58 @@ +/* + * 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 + val := func(arg int) (float64, float64) { + return originX + scale*seg.Arg[arg], originY + scale*seg.Arg[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) + 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 + default: + f.SetErrorf("Unexpected path command '%c'", seg.Cmd) + } + } + } +} diff --git a/vendor/github.com/jung-kurt/gofpdf/template.go b/vendor/github.com/jung-kurt/gofpdf/template.go new file mode 100644 index 00000000..31204c2f --- /dev/null +++ b/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("< 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| diff --git a/vendor/github.com/jung-kurt/gofpdf/template_impl.go b/vendor/github.com/jung-kurt/gofpdf/template_impl.go new file mode 100644 index 00000000..c5d7be89 --- /dev/null +++ b/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 +} diff --git a/vendor/github.com/jung-kurt/gofpdf/ttfparser.go b/vendor/github.com/jung-kurt/gofpdf/ttfparser.go new file mode 100644 index 00000000..669ab4d8 --- /dev/null +++ b/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 +} diff --git a/vendor/github.com/jung-kurt/gofpdf/util.go b/vendor/github.com/jung-kurt/gofpdf/util.go new file mode 100644 index 00000000..1dad60e7 --- /dev/null +++ b/vendor/github.com/jung-kurt/gofpdf/util.go @@ -0,0 +1,316 @@ +/* + * 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 with BOM; from http://www.fpdf.org/ +func utf8toutf16(s string) string { + res := make([]byte, 0, 8) + 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} +} diff --git a/vendor/vendor.json b/vendor/vendor.json index 2b98fa98..229b3fc3 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -51,6 +51,12 @@ "revision": "a9ddcb8a4b609500fc59c89ccc9ee05f00a5fefd", "revisionTime": "2018-03-16T17:07:08Z" }, + { + "checksumSHA1": "8nXZhgX+GKWIWD1599+g9geMAW4=", + "path": "github.com/jung-kurt/gofpdf", + "revision": "8baf016b4b802d4dfcc5819a4d101a7c5c858981", + "revisionTime": "2018-12-27T15:00:23Z" + }, { "checksumSHA1": "MrqRXuqj5suGXqcRUUwi+YA7PmE=", "origin": "github.com/fatih/color/vendor/github.com/mattn/go-colorable", From 7ed579c49685e83bea009f2c79a9b59530fdfaf0 Mon Sep 17 00:00:00 2001 From: Javier Uruen Val Date: Fri, 4 Jan 2019 15:25:57 +0100 Subject: [PATCH 2/2] add initial support for annotations --- CHANGELOG.md | 2 + README.md | 16 +++- annotations/pdf.go | 138 +++++++++++++++++++++++++++++++ annotations/rm.go | 200 +++++++++++++++++++++++++++++++++++++++++++++ shell/geta.go | 73 +++++++++++++++++ shell/shell.go | 1 + util/zipdoc.go | 63 ++++++++++++++ 7 files changed, 491 insertions(+), 2 deletions(-) create mode 100644 annotations/pdf.go create mode 100644 annotations/rm.go create mode 100644 shell/geta.go diff --git a/CHANGELOG.md b/CHANGELOG.md index f0a32456..2b5b2645 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ * Ignore hidden files/directories by default +* Initial support for annotations + ## rmapi 0.0.4 (October 1, 2018) * Windows fixes diff --git a/README.md b/README.md index 34eda5d9..3fcde448 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,10 @@ You can download an already built version for either Linux or OSX from [releases - [x] upload a specific file - [ ] live syncs +# Annotations + +- Initial support to generate a PDF with annotations from `Quick Sheet` and notebooks. + # Shell ergonomics - [x] autocomplete @@ -102,17 +106,25 @@ E.g: download all the files mget . ``` +## Download a file and generate a PDF with its annoations + +Use `geta` to download either a `Quick Sheets` or `notebook` file and generate a PDF document +with its annotations. + +Please note that its support is very basic for now and only supports one type of pen for now, but +there's work in progress to improve it. + ## Create a directoy Use `mkdir path_to_new_dir` to create a new directory -## Remove a directory or a file +## Remove a directory or a file Use `rm directory_or_file` to remove. If it's directory, it needs to be empty in order to be deleted. You can remove multiple entries at the same time. -## Move/rename a directory or a file +## Move/rename a directory or a file Use `mv source destination` to move or rename a file or directory. diff --git a/annotations/pdf.go b/annotations/pdf.go new file mode 100644 index 00000000..3bbd4e79 --- /dev/null +++ b/annotations/pdf.go @@ -0,0 +1,138 @@ +package annotations + +import ( + "github.com/jung-kurt/gofpdf" + "io/ioutil" + "os" + "path" + "sort" + "strconv" + "strings" +) + +const ( + RmX = 1404 + RmY = 1872 + A4X = 210 + A4Y = 297 + ratioA4X = float32(A4X) / float32(RmX) + ratioA4Y = float32(A4Y) / float32(RmY) + linesExtension = ".rm" +) + +type PdfGenerator struct { + inputDirPath string + outputFilePath string +} + +type page struct { + layers []Layer +} + +func CreatePdfGenerator(inputDirPath, outputFilePath string) PdfGenerator { + return PdfGenerator{inputDirPath: inputDirPath, outputFilePath: outputFilePath} +} + +func (p PdfGenerator) Generate() error { + files, err := ioutil.ReadDir(p.inputDirPath) + if err != nil { + return err + } + + pages := make([]page, 0) + for _, f := range p.rmFiles(files) { + reader, err := CreateRmReader(path.Join(p.inputDirPath, f)) + if err != nil { + return err + } + + layers, err := reader.Parse() + if err != nil { + return err + } + + pages = append(pages, page{layers}) + } + + if err = p.generatePages(pages); err != nil { + return err + } + + return nil +} + +func (p PdfGenerator) rmFiles(files []os.FileInfo) []string { + rmFiles := make([]string, 0) + for _, f := range files { + if f.IsDir() || !strings.HasSuffix(f.Name(), linesExtension) { + continue + } + + pageNumStr := strings.TrimSuffix(f.Name(), linesExtension) + _, err := strconv.Atoi(pageNumStr) + + if err != nil { + continue + } + + rmFiles = append(rmFiles, f.Name()) + } + + sort.Slice(rmFiles, lineFileSorter(rmFiles)) + + return rmFiles +} + +func lineFileSorter(rmFiles []string) func(i int, j int) bool { + return func(i int, j int) bool { + a, _ := strconv.Atoi(strings.TrimSuffix(rmFiles[i], linesExtension)) + b, _ := strconv.Atoi(strings.TrimSuffix(rmFiles[j], linesExtension)) + + return a < b + } +} + + +func (p PdfGenerator) generatePages(pages []page) error { + pdf := gofpdf.New("P", "mm", "A4", "") + + for _, page := range pages { + pdf.AddPage() + + for _, layer := range page.layers { + for _, stroke := range layer.Strokes { + + if len(stroke.Segments) < 1 { + continue + } + + for i := 1; i < len(stroke.Segments); i++ { + s := stroke.Segments[i-1].toA4() + x1 := s.X + y1 := s.Y + + s = stroke.Segments[i].toA4() + x2 := s.X + y2 := s.Y + + pdf.Line(float64(x1), float64(y1), float64(x2), float64(y2)) + } + } + } + } + + return pdf.OutputFileAndClose(p.outputFilePath) +} + +func (p PdfGenerator) setStyle(pdf *gofpdf.Fpdf, stroke Stroke) { + pdf.SetDrawColor(0, 0,0) + pdf.SetLineWidth(0) + pdf.SetLineCapStyle("round") +} + +func (s Segment) toA4() Segment { + s.X *= ratioA4X + s.Y *= ratioA4Y + + return s +} \ No newline at end of file diff --git a/annotations/rm.go b/annotations/rm.go new file mode 100644 index 00000000..f776e6ea --- /dev/null +++ b/annotations/rm.go @@ -0,0 +1,200 @@ +package annotations + +import ( + "encoding/binary" + "fmt" + "io" + "os" +) + +const ( + rmHeader = "reMarkable .lines file, version=3 " + rmHeaderLen = 43 +) + +type rmReader struct { + file io.Reader + path string +} + +type Layer struct { + Strokes []Stroke +} + +type Stroke struct { + Pen uint32 + Color uint32 + Unknown uint32 + Width float32 + Segments []Segment +} + +type Segment struct { + X float32 + Y float32 + Pressure float32 + Tilt float32 + Unknown1 float32 + Unknown2 float32 +} + +func CreateRmReader(path string) (rmReader, error) { + file, err := os.Open(path) + if err != nil { + return rmReader{}, err + } + + return rmReader{file: file, path: path}, nil +} + +func (rm rmReader) Parse() ([]Layer, error) { + if err := rm.checkHeader(); err != nil { + return nil, err + } + + numLayers, err := rm.numLayers() + + if err != nil { + return nil, err + } + + layers := make([]Layer, numLayers) + + for i := uint32(0); i < numLayers; i++ { + numStrokes, err := rm.numStrokes() + if err != nil { + return nil, err + } + + strokes := make([]Stroke, numStrokes) + for j := uint32(0); j < numStrokes; j++ { + stroke, err := rm.parseStroke() + if err != nil { + return nil, err + } + + strokes[j] = stroke + } + + layers[i].Strokes = strokes + } + + return layers, nil +} + +func (rm rmReader) checkHeader() error { + headerLen := rmHeaderLen + buf := make([]byte, rmHeaderLen) + + n, err := rm.file.Read(buf) + + if err != nil { + return err + } + + if n != headerLen { + return fmt.Errorf("%s is not a valid rm file, invalid header length", rm.path) + } + + fileId := string(buf[:rmHeaderLen]) + + if fileId != rmHeader { + return fmt.Errorf("%s is not a valid rm file, wrong header %s", rm.path, fileId) + } + + return nil +} + +func (rm rmReader) numLayers() (uint32, error) { + var numLayers uint32 + + if err := binary.Read(rm.file, binary.LittleEndian, &numLayers); err != nil { + return 0, fmt.Errorf("%s is not a valid rm file, wrong number of layers", rm.path) + } + + return numLayers, nil +} + +func (rm rmReader) numStrokes() (uint32, error) { + var numStrokes uint32 + + if err := binary.Read(rm.file, binary.LittleEndian, &numStrokes); err != nil { + return 0, fmt.Errorf("%s is not a valid rm file, wrong number of strokes", rm.path) + } + + return numStrokes, nil +} + +func (rm rmReader) parseStroke() (Stroke, error) { + var stroke Stroke + + if err := binary.Read(rm.file, binary.LittleEndian, &stroke.Pen); err != nil { + return stroke, fmt.Errorf("%s: failed to parse stroke", rm.path) + } + + if err := binary.Read(rm.file, binary.LittleEndian, &stroke.Color); err != nil { + return stroke, fmt.Errorf("%s: failed to parse stroke", rm.path) + } + + if err := binary.Read(rm.file, binary.LittleEndian, &stroke.Unknown); err != nil { + return stroke, fmt.Errorf("%s: failed to parse stroke", rm.path) + } + + if err := binary.Read(rm.file, binary.LittleEndian, &stroke.Width); err != nil { + return stroke, fmt.Errorf("%s: failed to parse stroke", rm.path) + } + + var numSegments uint32 + if err := binary.Read(rm.file, binary.LittleEndian, &numSegments); err != nil { + return stroke, fmt.Errorf("%s: failed to parse stroke", rm.path) + } + + if numSegments == 0 { + return stroke, nil + } + + stroke.Segments = make([]Segment, numSegments) + + for i := uint32(0); i < numSegments; i++ { + s, err := rm.parseSegment() + + if err != nil { + return stroke, fmt.Errorf("%s: failed to parse segment", rm.path) + } + + stroke.Segments[i] = s + } + + return stroke, nil +} + + +func (rm rmReader) parseSegment() (Segment, error) { + var segment Segment + + if err := binary.Read(rm.file, binary.LittleEndian, &segment.X); err != nil { + return segment, fmt.Errorf("%s: failed to parse segment", rm.path) + } + + if err := binary.Read(rm.file, binary.LittleEndian, &segment.Y); err != nil { + return segment, fmt.Errorf("%s: failed to parse segment", rm.path) + } + + if err := binary.Read(rm.file, binary.LittleEndian, &segment.Pressure); err != nil { + return segment, fmt.Errorf("%s: failed to parse segment", rm.path) + } + + if err := binary.Read(rm.file, binary.LittleEndian, &segment.Tilt); err != nil { + return segment, fmt.Errorf("%s: failed to parse segment", rm.path) + } + + if err := binary.Read(rm.file, binary.LittleEndian, &segment.Unknown1); err != nil { + return segment, fmt.Errorf("%s: failed to parse segment", rm.path) + } + + if err := binary.Read(rm.file, binary.LittleEndian, &segment.Unknown2); err != nil { + return segment, fmt.Errorf("%s: failed to parse segment", rm.path) + } + + return segment, nil +} \ No newline at end of file diff --git a/shell/geta.go b/shell/geta.go new file mode 100644 index 00000000..ff999b4f --- /dev/null +++ b/shell/geta.go @@ -0,0 +1,73 @@ +package shell + +import ( + "errors" + "fmt" + "github.com/juruen/rmapi/annotations" + "github.com/juruen/rmapi/util" + "io/ioutil" + "os" + "path" + + "github.com/abiosoft/ishell" +) + +func getACmd(ctx *ShellCtxt) *ishell.Cmd { + return &ishell.Cmd{ + Name: "geta", + Help: "copy remote file to local and generate a PDF with its annotations", + Completer: createEntryCompleter(ctx), + Func: func(c *ishell.Context) { + if len(c.Args) == 0 { + c.Err(errors.New("missing source file")) + return + } + + srcName := c.Args[0] + + node, err := ctx.api.Filetree.NodeByPath(srcName, ctx.node) + + if err != nil || node.IsDirectory() { + c.Err(errors.New("file doesn't exist")) + return + } + + c.Println(fmt.Sprintf("downlading: [%s]...", srcName)) + + zipName := fmt.Sprintf("%s.zip", node.Name()) + err = ctx.api.FetchDocument(node.Document.ID, zipName) + + if err != nil { + c.Err(errors.New(fmt.Sprintf("Failed to download file %s with %s", srcName, err.Error()))) + return + } + + tmp, err := ioutil.TempDir("", "rmapizip") + if err != nil { + c.Err(errors.New(fmt.Sprintf("Failed to create tmp dir with %s", err.Error()))) + return + } + + defer os.RemoveAll(tmp) + + err = util.Unzip(zipName, tmp) + if err != nil { + c.Err(errors.New(fmt.Sprintf("Failed to unzip %s with %s", srcName, err.Error()))) + return + } + + unzipDir := path.Join(tmp, node.Id()) + pdfName := fmt.Sprintf("%s-annotations.pdf", node.Name()) + + generator := annotations.CreatePdfGenerator(unzipDir, pdfName) + err = generator.Generate() + + if err != nil { + c.Err(errors.New(fmt.Sprintf("Failed to generate annotations for %s with %s", srcName, err.Error()))) + return + } + + c.Printf("Annotations generated in: %s\n", pdfName) + }, + } +} diff --git a/shell/shell.go b/shell/shell.go index c44bc527..05b81c4c 100644 --- a/shell/shell.go +++ b/shell/shell.go @@ -63,6 +63,7 @@ func RunShell(apiCtx *api.ApiCtx) error { shell.AddCmd(mputCmd(ctx)) shell.AddCmd(versionCmd(ctx)) shell.AddCmd(statCmd(ctx)) + shell.AddCmd(getACmd(ctx)) setCustomCompleter(shell) diff --git a/util/zipdoc.go b/util/zipdoc.go index 657b28f4..c2c17df1 100644 --- a/util/zipdoc.go +++ b/util/zipdoc.go @@ -4,7 +4,10 @@ import ( "archive/zip" "encoding/json" "fmt" + "io" "io/ioutil" + "os" + "path/filepath" "strings" "github.com/juruen/rmapi/log" @@ -105,6 +108,66 @@ func CreateZipDirectory(id string) (string, error) { return tmp.Name(), nil } + +func Unzip(src, dest string) error { + r, err := zip.OpenReader(src) + if err != nil { + return err + } + defer func() { + if err := r.Close(); err != nil { + panic(err) + } + }() + + os.MkdirAll(dest, 0755) + + // Closure to address file descriptors issue with all the deferred .Close() methods + extractAndWriteFile := func(f *zip.File) error { + rc, err := f.Open() + if err != nil { + return err + } + defer func() { + if err := rc.Close(); err != nil { + panic(err) + } + }() + + path := filepath.Join(dest, f.Name) + + if f.FileInfo().IsDir() { + os.MkdirAll(path, 0700) + } else { + os.MkdirAll(filepath.Dir(path), 0700) + f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) + if err != nil { + return err + } + defer func() { + if err := f.Close(); err != nil { + panic(err) + } + }() + + _, err = io.Copy(f, rc) + if err != nil { + return err + } + } + return nil + } + + for _, f := range r.File { + err := extractAndWriteFile(f) + if err != nil { + return err + } + } + + return nil +} + func createZipContent(ext string) (string, error) { c := zipDocumentContent{ make(map[string]string),