4999 lines
147 KiB
Go
4999 lines
147 KiB
Go
|
/*
|
||
|
* 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
|
||
|
modDate time.Time
|
||
|
}
|
||
|
|
||
|
type fmtBuffer struct {
|
||
|
bytes.Buffer
|
||
|
}
|
||
|
|
||
|
func (b *fmtBuffer) printf(fmtStr string, args ...interface{}) {
|
||
|
b.Buffer.WriteString(fmt.Sprintf(fmtStr, args...))
|
||
|
}
|
||
|
|
||
|
func fpdfNew(orientationStr, unitStr, sizeStr, fontDirStr string, size SizeType) (f *Fpdf) {
|
||
|
f = new(Fpdf)
|
||
|
if orientationStr == "" {
|
||
|
orientationStr = "p"
|
||
|
} else {
|
||
|
orientationStr = strings.ToLower(orientationStr)
|
||
|
}
|
||
|
if unitStr == "" {
|
||
|
unitStr = "mm"
|
||
|
}
|
||
|
if sizeStr == "" {
|
||
|
sizeStr = "A4"
|
||
|
}
|
||
|
if fontDirStr == "" {
|
||
|
fontDirStr = "."
|
||
|
}
|
||
|
f.page = 0
|
||
|
f.n = 2
|
||
|
f.pages = make([]*bytes.Buffer, 0, 8)
|
||
|
f.pages = append(f.pages, bytes.NewBufferString("")) // pages[0] is unused (1-based)
|
||
|
f.pageSizes = make(map[int]SizeType)
|
||
|
f.pageBoxes = make(map[int]map[string]PageBox)
|
||
|
f.defPageBoxes = make(map[string]PageBox)
|
||
|
f.state = 0
|
||
|
f.fonts = make(map[string]fontDefType)
|
||
|
f.fontFiles = make(map[string]fontFileType)
|
||
|
f.diffs = make([]string, 0, 8)
|
||
|
f.templates = make(map[string]Template)
|
||
|
f.templateObjects = make(map[string]int)
|
||
|
f.importedObjs = make(map[string][]byte, 0)
|
||
|
f.importedObjPos = make(map[string]map[int]string, 0)
|
||
|
f.importedTplObjs = make(map[string]string)
|
||
|
f.importedTplIDs = make(map[string]int, 0)
|
||
|
f.images = make(map[string]*ImageInfoType)
|
||
|
f.pageLinks = make([][]linkType, 0, 8)
|
||
|
f.pageLinks = append(f.pageLinks, make([]linkType, 0, 0)) // pageLinks[0] is unused (1-based)
|
||
|
f.links = make([]intLinkType, 0, 8)
|
||
|
f.links = append(f.links, intLinkType{}) // links[0] is unused (1-based)
|
||
|
f.pageAttachments = make([][]annotationAttach, 0, 8)
|
||
|
f.pageAttachments = append(f.pageAttachments, []annotationAttach{}) //
|
||
|
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.strikeout = false
|
||
|
f.setDrawColor(0, 0, 0)
|
||
|
f.setFillColor(0, 0, 0)
|
||
|
f.setTextColor(0, 0, 0)
|
||
|
f.colorFlag = false
|
||
|
f.ws = 0
|
||
|
f.fontpath = fontDirStr
|
||
|
// Core fonts
|
||
|
f.coreFonts = map[string]bool{
|
||
|
"courier": true,
|
||
|
"helvetica": true,
|
||
|
"times": true,
|
||
|
"symbol": true,
|
||
|
"zapfdingbats": true,
|
||
|
}
|
||
|
// Scale factor
|
||
|
switch unitStr {
|
||
|
case "pt", "point":
|
||
|
f.k = 1.0
|
||
|
case "mm":
|
||
|
f.k = 72.0 / 25.4
|
||
|
case "cm":
|
||
|
f.k = 72.0 / 2.54
|
||
|
case "in", "inch":
|
||
|
f.k = 72.0
|
||
|
default:
|
||
|
f.err = fmt.Errorf("incorrect unit %s", unitStr)
|
||
|
return
|
||
|
}
|
||
|
f.unitStr = unitStr
|
||
|
// Page sizes
|
||
|
f.stdPageSizes = make(map[string]SizeType)
|
||
|
f.stdPageSizes["a3"] = SizeType{841.89, 1190.55}
|
||
|
f.stdPageSizes["a4"] = SizeType{595.28, 841.89}
|
||
|
f.stdPageSizes["a5"] = SizeType{420.94, 595.28}
|
||
|
f.stdPageSizes["a6"] = SizeType{297.64, 420.94}
|
||
|
f.stdPageSizes["a2"] = SizeType{1190.55, 1683.78}
|
||
|
f.stdPageSizes["a1"] = SizeType{1683.78, 2383.94}
|
||
|
f.stdPageSizes["letter"] = SizeType{612, 792}
|
||
|
f.stdPageSizes["legal"] = SizeType{612, 1008}
|
||
|
f.stdPageSizes["tabloid"] = SizeType{792, 1224}
|
||
|
if size.Wd > 0 && size.Ht > 0 {
|
||
|
f.defPageSize = size
|
||
|
} else {
|
||
|
f.defPageSize = f.getpagesizestr(sizeStr)
|
||
|
if f.err != nil {
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
f.curPageSize = f.defPageSize
|
||
|
// Page orientation
|
||
|
switch orientationStr {
|
||
|
case "p", "portrait":
|
||
|
f.defOrientation = "P"
|
||
|
f.w = f.defPageSize.Wd
|
||
|
f.h = f.defPageSize.Ht
|
||
|
// dbg("Assign h: %8.2f", f.h)
|
||
|
case "l", "landscape":
|
||
|
f.defOrientation = "L"
|
||
|
f.w = f.defPageSize.Ht
|
||
|
f.h = f.defPageSize.Wd
|
||
|
default:
|
||
|
f.err = fmt.Errorf("incorrect orientation: %s", orientationStr)
|
||
|
return
|
||
|
}
|
||
|
f.curOrientation = f.defOrientation
|
||
|
f.wPt = f.w * f.k
|
||
|
f.hPt = f.h * f.k
|
||
|
// Page margins (1 cm)
|
||
|
margin := 28.35 / f.k
|
||
|
f.SetMargins(margin, margin, margin)
|
||
|
// Interior cell margin (1 mm)
|
||
|
f.cMargin = margin / 10
|
||
|
// Line width (0.2 mm)
|
||
|
f.lineWidth = 0.567 / f.k
|
||
|
// Automatic page break
|
||
|
f.SetAutoPageBreak(true, 2*margin)
|
||
|
// Default display mode
|
||
|
f.SetDisplayMode("default", "default")
|
||
|
if f.err != nil {
|
||
|
return
|
||
|
}
|
||
|
f.acceptPageBreak = func() bool {
|
||
|
return f.autoPageBreak
|
||
|
}
|
||
|
// Enable compression
|
||
|
f.SetCompression(!gl.noCompress)
|
||
|
f.spotColorMap = make(map[string]spotColorType)
|
||
|
f.blendList = make([]blendModeType, 0, 8)
|
||
|
f.blendList = append(f.blendList, blendModeType{}) // blendList[0] is unused (1-based)
|
||
|
f.blendMap = make(map[string]int)
|
||
|
f.blendMode = "Normal"
|
||
|
f.alpha = 1
|
||
|
f.gradientList = make([]gradientType, 0, 8)
|
||
|
f.gradientList = append(f.gradientList, gradientType{}) // gradientList[0] is unused
|
||
|
// Set default PDF version number
|
||
|
f.pdfVersion = "1.3"
|
||
|
f.SetProducer("FPDF "+cnFpdfVersion, true)
|
||
|
f.layerInit()
|
||
|
f.catalogSort = gl.catalogSort
|
||
|
f.creationDate = gl.creationDate
|
||
|
f.modDate = gl.modDate
|
||
|
f.userUnderlineThickness = 1
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// NewCustom returns a pointer to a new Fpdf instance. Its methods are
|
||
|
// subsequently called to produce a single PDF document. NewCustom() is an
|
||
|
// alternative to New() that provides additional customization. The PageSize()
|
||
|
// example demonstrates this method.
|
||
|
func NewCustom(init *InitType) (f *Fpdf) {
|
||
|
return fpdfNew(init.OrientationStr, init.UnitStr, init.SizeStr, init.FontDirStr, init.Size)
|
||
|
}
|
||
|
|
||
|
// New returns a pointer to a new Fpdf instance. Its methods are subsequently
|
||
|
// called to produce a single PDF document.
|
||
|
//
|
||
|
// orientationStr specifies the default page orientation. For portrait mode,
|
||
|
// specify "P" or "Portrait". For landscape mode, specify "L" or "Landscape".
|
||
|
// An empty string will be replaced with "P".
|
||
|
//
|
||
|
// unitStr specifies the unit of length used in size parameters for elements
|
||
|
// other than fonts, which are always measured in points. Specify "pt" for
|
||
|
// point, "mm" for millimeter, "cm" for centimeter, or "in" for inch. An empty
|
||
|
// string will be replaced with "mm".
|
||
|
//
|
||
|
// sizeStr specifies the page size. Acceptable values are "A3", "A4", "A5",
|
||
|
// "Letter", "Legal", or "Tabloid". An empty string will be replaced with "A4".
|
||
|
//
|
||
|
// fontDirStr specifies the file system location in which font resources will
|
||
|
// be found. An empty string is replaced with ".". This argument only needs to
|
||
|
// reference an actual directory if a font other than one of the core
|
||
|
// fonts is used. The core fonts are "courier", "helvetica" (also called
|
||
|
// "arial"), "times", and "zapfdingbats" (also called "symbol").
|
||
|
func New(orientationStr, unitStr, sizeStr, fontDirStr string) (f *Fpdf) {
|
||
|
return fpdfNew(orientationStr, unitStr, sizeStr, fontDirStr, SizeType{0, 0})
|
||
|
}
|
||
|
|
||
|
// Ok returns true if no processing errors have occurred.
|
||
|
func (f *Fpdf) Ok() bool {
|
||
|
return f.err == nil
|
||
|
}
|
||
|
|
||
|
// Err returns true if a processing error has occurred.
|
||
|
func (f *Fpdf) Err() bool {
|
||
|
return f.err != nil
|
||
|
}
|
||
|
|
||
|
// ClearError unsets the internal Fpdf error. This method should be used with
|
||
|
// care, as an internal error condition usually indicates an unrecoverable
|
||
|
// problem with the generation of a document. It is intended to deal with cases
|
||
|
// in which an error is used to select an alternate form of the document.
|
||
|
func (f *Fpdf) ClearError() {
|
||
|
f.err = nil
|
||
|
}
|
||
|
|
||
|
// SetErrorf sets the internal Fpdf error with formatted text to halt PDF
|
||
|
// generation; this may facilitate error handling by application. If an error
|
||
|
// condition is already set, this call is ignored.
|
||
|
//
|
||
|
// See the documentation for printing in the standard fmt package for details
|
||
|
// about fmtStr and args.
|
||
|
func (f *Fpdf) SetErrorf(fmtStr string, args ...interface{}) {
|
||
|
if f.err == nil {
|
||
|
f.err = fmt.Errorf(fmtStr, args...)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// String satisfies the fmt.Stringer interface and summarizes the Fpdf
|
||
|
// instance.
|
||
|
func (f *Fpdf) String() string {
|
||
|
return "Fpdf " + cnFpdfVersion
|
||
|
}
|
||
|
|
||
|
// SetError sets an error to halt PDF generation. This may facilitate error
|
||
|
// handling by application. See also Ok(), Err() and Error().
|
||
|
func (f *Fpdf) SetError(err error) {
|
||
|
if f.err == nil && err != nil {
|
||
|
f.err = err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Error returns the internal Fpdf error; this will be nil if no error has occurred.
|
||
|
func (f *Fpdf) Error() error {
|
||
|
return f.err
|
||
|
}
|
||
|
|
||
|
// GetPageSize returns the current page's width and height. This is the paper's
|
||
|
// size. To compute the size of the area being used, subtract the margins (see
|
||
|
// GetMargins()).
|
||
|
func (f *Fpdf) GetPageSize() (width, height float64) {
|
||
|
width = f.w
|
||
|
height = f.h
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// GetMargins returns the left, top, right, and bottom margins. The first three
|
||
|
// are set with the SetMargins() method. The bottom margin is set with the
|
||
|
// SetAutoPageBreak() method.
|
||
|
func (f *Fpdf) GetMargins() (left, top, right, bottom float64) {
|
||
|
left = f.lMargin
|
||
|
top = f.tMargin
|
||
|
right = f.rMargin
|
||
|
bottom = f.bMargin
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// SetMargins defines the left, top and right margins. By default, they equal 1
|
||
|
// cm. Call this method to change them. If the value of the right margin is
|
||
|
// less than zero, it is set to the same as the left margin.
|
||
|
func (f *Fpdf) SetMargins(left, top, right float64) {
|
||
|
f.lMargin = left
|
||
|
f.tMargin = top
|
||
|
if right < 0 {
|
||
|
right = left
|
||
|
}
|
||
|
f.rMargin = right
|
||
|
}
|
||
|
|
||
|
// SetLeftMargin defines the left margin. The method can be called before
|
||
|
// creating the first page. If the current abscissa gets out of page, it is
|
||
|
// brought back to the margin.
|
||
|
func (f *Fpdf) SetLeftMargin(margin float64) {
|
||
|
f.lMargin = margin
|
||
|
if f.page > 0 && f.x < margin {
|
||
|
f.x = margin
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// GetCellMargin returns the cell margin. This is the amount of space before
|
||
|
// and after the text within a cell that's left blank, and is in units passed
|
||
|
// to New(). It defaults to 1mm.
|
||
|
func (f *Fpdf) GetCellMargin() float64 {
|
||
|
return f.cMargin
|
||
|
}
|
||
|
|
||
|
// SetCellMargin sets the cell margin. This is the amount of space before and
|
||
|
// after the text within a cell that's left blank, and is in units passed to
|
||
|
// New().
|
||
|
func (f *Fpdf) SetCellMargin(margin float64) {
|
||
|
f.cMargin = margin
|
||
|
}
|
||
|
|
||
|
// SetPageBoxRec sets the page box for the current page, and any following
|
||
|
// pages. Allowable types are trim, trimbox, crop, cropbox, bleed, bleedbox,
|
||
|
// art and artbox box types are case insensitive. See SetPageBox() for a method
|
||
|
// that specifies the coordinates and extent of the page box individually.
|
||
|
func (f *Fpdf) SetPageBoxRec(t string, pb PageBox) {
|
||
|
switch strings.ToLower(t) {
|
||
|
case "trim":
|
||
|
fallthrough
|
||
|
case "trimbox":
|
||
|
t = "TrimBox"
|
||
|
case "crop":
|
||
|
fallthrough
|
||
|
case "cropbox":
|
||
|
t = "CropBox"
|
||
|
case "bleed":
|
||
|
fallthrough
|
||
|
case "bleedbox":
|
||
|
t = "BleedBox"
|
||
|
case "art":
|
||
|
fallthrough
|
||
|
case "artbox":
|
||
|
t = "ArtBox"
|
||
|
default:
|
||
|
f.err = fmt.Errorf("%s is not a valid page box type", t)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
pb.X = pb.X * f.k
|
||
|
pb.Y = pb.Y * f.k
|
||
|
pb.Wd = (pb.Wd * f.k) + pb.X
|
||
|
pb.Ht = (pb.Ht * f.k) + pb.Y
|
||
|
|
||
|
if f.page > 0 {
|
||
|
f.pageBoxes[f.page][t] = pb
|
||
|
}
|
||
|
|
||
|
// always override. page defaults are supplied in addPage function
|
||
|
f.defPageBoxes[t] = pb
|
||
|
}
|
||
|
|
||
|
// SetPageBox sets the page box for the current page, and any following pages.
|
||
|
// Allowable types are trim, trimbox, crop, cropbox, bleed, bleedbox, art and
|
||
|
// artbox box types are case insensitive.
|
||
|
func (f *Fpdf) SetPageBox(t string, x, y, wd, ht float64) {
|
||
|
f.SetPageBoxRec(t, PageBox{SizeType{Wd: wd, Ht: ht}, PointType{X: x, Y: y}})
|
||
|
}
|
||
|
|
||
|
// SetPage sets the current page to that of a valid page in the PDF document.
|
||
|
// pageNum is one-based. The SetPage() example demonstrates this method.
|
||
|
func (f *Fpdf) SetPage(pageNum int) {
|
||
|
if (pageNum > 0) && (pageNum < len(f.pages)) {
|
||
|
f.page = pageNum
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// PageCount returns the number of pages currently in the document. Since page
|
||
|
// numbers in gofpdf are one-based, the page count is the same as the page
|
||
|
// number of the current last page.
|
||
|
func (f *Fpdf) PageCount() int {
|
||
|
return len(f.pages) - 1
|
||
|
}
|
||
|
|
||
|
// SetFontLocation sets the location in the file system of the font and font
|
||
|
// definition files.
|
||
|
func (f *Fpdf) SetFontLocation(fontDirStr string) {
|
||
|
f.fontpath = fontDirStr
|
||
|
}
|
||
|
|
||
|
// SetFontLoader sets a loader used to read font files (.json and .z) from an
|
||
|
// arbitrary source. If a font loader has been specified, it is used to load
|
||
|
// the named font resources when AddFont() is called. If this operation fails,
|
||
|
// an attempt is made to load the resources from the configured font directory
|
||
|
// (see SetFontLocation()).
|
||
|
func (f *Fpdf) SetFontLoader(loader FontLoader) {
|
||
|
f.fontLoader = loader
|
||
|
}
|
||
|
|
||
|
// SetHeaderFuncMode sets the function that lets the application render the
|
||
|
// page header. See SetHeaderFunc() for more details. The value for homeMode
|
||
|
// should be set to true to have the current position set to the left and top
|
||
|
// margin after the header function is called.
|
||
|
func (f *Fpdf) SetHeaderFuncMode(fnc func(), homeMode bool) {
|
||
|
f.headerFnc = fnc
|
||
|
f.headerHomeMode = homeMode
|
||
|
}
|
||
|
|
||
|
// SetHeaderFunc sets the function that lets the application render the page
|
||
|
// header. The specified function is automatically called by AddPage() and
|
||
|
// should not be called directly by the application. The implementation in Fpdf
|
||
|
// is empty, so you have to provide an appropriate function if you want page
|
||
|
// headers. fnc will typically be a closure that has access to the Fpdf
|
||
|
// instance and other document generation variables.
|
||
|
//
|
||
|
// A header is a convenient place to put background content that repeats on
|
||
|
// each page such as a watermark. When this is done, remember to reset the X
|
||
|
// and Y values so the normal content begins where expected. Including a
|
||
|
// watermark on each page is demonstrated in the example for TransformRotate.
|
||
|
//
|
||
|
// This method is demonstrated in the example for AddPage().
|
||
|
func (f *Fpdf) SetHeaderFunc(fnc func()) {
|
||
|
f.headerFnc = fnc
|
||
|
}
|
||
|
|
||
|
// SetFooterFunc sets the function that lets the application render the page
|
||
|
// footer. The specified function is automatically called by AddPage() and
|
||
|
// Close() and should not be called directly by the application. The
|
||
|
// implementation in Fpdf is empty, so you have to provide an appropriate
|
||
|
// function if you want page footers. fnc will typically be a closure that has
|
||
|
// access to the Fpdf instance and other document generation variables. See
|
||
|
// SetFooterFuncLpi for a similar function that passes a last page indicator.
|
||
|
//
|
||
|
// This method is demonstrated in the example for AddPage().
|
||
|
func (f *Fpdf) SetFooterFunc(fnc func()) {
|
||
|
f.footerFnc = fnc
|
||
|
f.footerFncLpi = nil
|
||
|
}
|
||
|
|
||
|
// SetFooterFuncLpi sets the function that lets the application render the page
|
||
|
// footer. The specified function is automatically called by AddPage() and
|
||
|
// Close() and should not be called directly by the application. It is passed a
|
||
|
// boolean that is true if the last page of the document is being rendered. The
|
||
|
// implementation in Fpdf is empty, so you have to provide an appropriate
|
||
|
// function if you want page footers. fnc will typically be a closure that has
|
||
|
// access to the Fpdf instance and other document generation variables.
|
||
|
func (f *Fpdf) SetFooterFuncLpi(fnc func(lastPage bool)) {
|
||
|
f.footerFncLpi = fnc
|
||
|
f.footerFnc = nil
|
||
|
}
|
||
|
|
||
|
// SetTopMargin defines the top margin. The method can be called before
|
||
|
// creating the first page.
|
||
|
func (f *Fpdf) SetTopMargin(margin float64) {
|
||
|
f.tMargin = margin
|
||
|
}
|
||
|
|
||
|
// SetRightMargin defines the right margin. The method can be called before
|
||
|
// creating the first page.
|
||
|
func (f *Fpdf) SetRightMargin(margin float64) {
|
||
|
f.rMargin = margin
|
||
|
}
|
||
|
|
||
|
// GetAutoPageBreak returns true if automatic pages breaks are enabled, false
|
||
|
// otherwise. This is followed by the triggering limit from the bottom of the
|
||
|
// page. This value applies only if automatic page breaks are enabled.
|
||
|
func (f *Fpdf) GetAutoPageBreak() (auto bool, margin float64) {
|
||
|
auto = f.autoPageBreak
|
||
|
margin = f.bMargin
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// SetAutoPageBreak enables or disables the automatic page breaking mode. When
|
||
|
// enabling, the second parameter is the distance from the bottom of the page
|
||
|
// that defines the triggering limit. By default, the mode is on and the margin
|
||
|
// is 2 cm.
|
||
|
func (f *Fpdf) SetAutoPageBreak(auto bool, margin float64) {
|
||
|
f.autoPageBreak = auto
|
||
|
f.bMargin = margin
|
||
|
f.pageBreakTrigger = f.h - margin
|
||
|
}
|
||
|
|
||
|
// SetDisplayMode sets advisory display directives for the document viewer.
|
||
|
// Pages can be displayed entirely on screen, occupy the full width of the
|
||
|
// window, use real size, be scaled by a specific zooming factor or use viewer
|
||
|
// default (configured in the Preferences menu of Adobe Reader). The page
|
||
|
// layout can be specified so that pages are displayed individually or in
|
||
|
// pairs.
|
||
|
//
|
||
|
// zoomStr can be "fullpage" to display the entire page on screen, "fullwidth"
|
||
|
// to use maximum width of window, "real" to use real size (equivalent to 100%
|
||
|
// zoom) or "default" to use viewer default mode.
|
||
|
//
|
||
|
// layoutStr can be "single" (or "SinglePage") to display one page at once,
|
||
|
// "continuous" (or "OneColumn") to display pages continuously, "two" (or
|
||
|
// "TwoColumnLeft") to display two pages on two columns with odd-numbered pages
|
||
|
// on the left, or "TwoColumnRight" to display two pages on two columns with
|
||
|
// odd-numbered pages on the right, or "TwoPageLeft" to display pages two at a
|
||
|
// time with odd-numbered pages on the left, or "TwoPageRight" to display pages
|
||
|
// two at a time with odd-numbered pages on the right, or "default" to use
|
||
|
// viewer default mode.
|
||
|
func (f *Fpdf) SetDisplayMode(zoomStr, layoutStr string) {
|
||
|
if f.err != nil {
|
||
|
return
|
||
|
}
|
||
|
if layoutStr == "" {
|
||
|
layoutStr = "default"
|
||
|
}
|
||
|
switch zoomStr {
|
||
|
case "fullpage", "fullwidth", "real", "default":
|
||
|
f.zoomMode = zoomStr
|
||
|
default:
|
||
|
f.err = fmt.Errorf("incorrect zoom display mode: %s", zoomStr)
|
||
|
return
|
||
|
}
|
||
|
switch layoutStr {
|
||
|
case "single", "continuous", "two", "default", "SinglePage", "OneColumn",
|
||
|
"TwoColumnLeft", "TwoColumnRight", "TwoPageLeft", "TwoPageRight":
|
||
|
f.layoutMode = layoutStr
|
||
|
default:
|
||
|
f.err = fmt.Errorf("incorrect layout display mode: %s", layoutStr)
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// SetDefaultCompression controls the default setting of the internal
|
||
|
// compression flag. See SetCompression() for more details. Compression is on
|
||
|
// by default.
|
||
|
func SetDefaultCompression(compress bool) {
|
||
|
gl.noCompress = !compress
|
||
|
}
|
||
|
|
||
|
// SetCompression activates or deactivates page compression with zlib. When
|
||
|
// activated, the internal representation of each page is compressed, which
|
||
|
// leads to a compression ratio of about 2 for the resulting document.
|
||
|
// Compression is on by default.
|
||
|
func (f *Fpdf) SetCompression(compress bool) {
|
||
|
f.compress = compress
|
||
|
}
|
||
|
|
||
|
// SetProducer defines the producer of the document. isUTF8 indicates if the string
|
||
|
// is encoded in ISO-8859-1 (false) or UTF-8 (true).
|
||
|
func (f *Fpdf) SetProducer(producerStr string, isUTF8 bool) {
|
||
|
if isUTF8 {
|
||
|
producerStr = utf8toutf16(producerStr)
|
||
|
}
|
||
|
f.producer = producerStr
|
||
|
}
|
||
|
|
||
|
// SetTitle defines the title of the document. isUTF8 indicates if the string
|
||
|
// is encoded in ISO-8859-1 (false) or UTF-8 (true).
|
||
|
func (f *Fpdf) SetTitle(titleStr string, isUTF8 bool) {
|
||
|
if isUTF8 {
|
||
|
titleStr = utf8toutf16(titleStr)
|
||
|
}
|
||
|
f.title = titleStr
|
||
|
}
|
||
|
|
||
|
// SetSubject defines the subject of the document. isUTF8 indicates if the
|
||
|
// string is encoded in ISO-8859-1 (false) or UTF-8 (true).
|
||
|
func (f *Fpdf) SetSubject(subjectStr string, isUTF8 bool) {
|
||
|
if isUTF8 {
|
||
|
subjectStr = utf8toutf16(subjectStr)
|
||
|
}
|
||
|
f.subject = subjectStr
|
||
|
}
|
||
|
|
||
|
// SetAuthor defines the author of the document. isUTF8 indicates if the string
|
||
|
// is encoded in ISO-8859-1 (false) or UTF-8 (true).
|
||
|
func (f *Fpdf) SetAuthor(authorStr string, isUTF8 bool) {
|
||
|
if isUTF8 {
|
||
|
authorStr = utf8toutf16(authorStr)
|
||
|
}
|
||
|
f.author = authorStr
|
||
|
}
|
||
|
|
||
|
// SetKeywords defines the keywords of the document. keywordStr is a
|
||
|
// space-delimited string, for example "invoice August". isUTF8 indicates if
|
||
|
// the string is encoded
|
||
|
func (f *Fpdf) SetKeywords(keywordsStr string, isUTF8 bool) {
|
||
|
if isUTF8 {
|
||
|
keywordsStr = utf8toutf16(keywordsStr)
|
||
|
}
|
||
|
f.keywords = keywordsStr
|
||
|
}
|
||
|
|
||
|
// SetCreator defines the creator of the document. isUTF8 indicates if the
|
||
|
// string is encoded in ISO-8859-1 (false) or UTF-8 (true).
|
||
|
func (f *Fpdf) SetCreator(creatorStr string, isUTF8 bool) {
|
||
|
if isUTF8 {
|
||
|
creatorStr = utf8toutf16(creatorStr)
|
||
|
}
|
||
|
f.creator = creatorStr
|
||
|
}
|
||
|
|
||
|
// SetXmpMetadata defines XMP metadata that will be embedded with the document.
|
||
|
func (f *Fpdf) SetXmpMetadata(xmpStream []byte) {
|
||
|
f.xmp = xmpStream
|
||
|
}
|
||
|
|
||
|
// AliasNbPages defines an alias for the total number of pages. It will be
|
||
|
// substituted as the document is closed. An empty string is replaced with the
|
||
|
// string "{nb}".
|
||
|
//
|
||
|
// See the example for AddPage() for a demonstration of this method.
|
||
|
func (f *Fpdf) AliasNbPages(aliasStr string) {
|
||
|
if aliasStr == "" {
|
||
|
aliasStr = "{nb}"
|
||
|
}
|
||
|
f.aliasNbPagesStr = aliasStr
|
||
|
}
|
||
|
|
||
|
// RTL enables right-to-left mode
|
||
|
func (f *Fpdf) RTL() {
|
||
|
f.isRTL = true
|
||
|
}
|
||
|
|
||
|
// LTR disables right-to-left mode
|
||
|
func (f *Fpdf) LTR() {
|
||
|
f.isRTL = false
|
||
|
}
|
||
|
|
||
|
// open begins a document
|
||
|
func (f *Fpdf) open() {
|
||
|
f.state = 1
|
||
|
}
|
||
|
|
||
|
// Close terminates the PDF document. It is not necessary to call this method
|
||
|
// explicitly because Output(), OutputAndClose() and OutputFileAndClose() do it
|
||
|
// automatically. If the document contains no page, AddPage() is called to
|
||
|
// prevent the generation of an invalid document.
|
||
|
func (f *Fpdf) Close() {
|
||
|
if f.err == nil {
|
||
|
if f.clipNest > 0 {
|
||
|
f.err = fmt.Errorf("clip procedure must be explicitly ended")
|
||
|
} else if f.transformNest > 0 {
|
||
|
f.err = fmt.Errorf("transformation procedure must be explicitly ended")
|
||
|
}
|
||
|
}
|
||
|
if f.err != nil {
|
||
|
return
|
||
|
}
|
||
|
if f.state == 3 {
|
||
|
return
|
||
|
}
|
||
|
if f.page == 0 {
|
||
|
f.AddPage()
|
||
|
if f.err != nil {
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
// Page footer
|
||
|
f.inFooter = true
|
||
|
if f.footerFnc != nil {
|
||
|
f.footerFnc()
|
||
|
} else if f.footerFncLpi != nil {
|
||
|
f.footerFncLpi(true)
|
||
|
}
|
||
|
f.inFooter = false
|
||
|
|
||
|
// Close page
|
||
|
f.endpage()
|
||
|
// Close document
|
||
|
f.enddoc()
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// PageSize returns the width and height of the specified page in the units
|
||
|
// established in New(). These return values are followed by the unit of
|
||
|
// measure itself. If pageNum is zero or otherwise out of bounds, it returns
|
||
|
// the default page size, that is, the size of the page that would be added by
|
||
|
// AddPage().
|
||
|
func (f *Fpdf) PageSize(pageNum int) (wd, ht float64, unitStr string) {
|
||
|
sz, ok := f.pageSizes[pageNum]
|
||
|
if ok {
|
||
|
sz.Wd, sz.Ht = sz.Wd/f.k, sz.Ht/f.k
|
||
|
} else {
|
||
|
sz = f.defPageSize // user units
|
||
|
}
|
||
|
return sz.Wd, sz.Ht, f.unitStr
|
||
|
}
|
||
|
|
||
|
// AddPageFormat adds a new page with non-default orientation or size. See
|
||
|
// AddPage() for more details.
|
||
|
//
|
||
|
// See New() for a description of orientationStr.
|
||
|
//
|
||
|
// size specifies the size of the new page in the units established in New().
|
||
|
//
|
||
|
// The PageSize() example demonstrates this method.
|
||
|
func (f *Fpdf) AddPageFormat(orientationStr string, size SizeType) {
|
||
|
if f.err != nil {
|
||
|
return
|
||
|
}
|
||
|
if f.page != len(f.pages)-1 {
|
||
|
f.page = len(f.pages) - 1
|
||
|
}
|
||
|
if f.state == 0 {
|
||
|
f.open()
|
||
|
}
|
||
|
familyStr := f.fontFamily
|
||
|
style := f.fontStyle
|
||
|
if f.underline {
|
||
|
style += "U"
|
||
|
}
|
||
|
if f.strikeout {
|
||
|
style += "S"
|
||
|
}
|
||
|
fontsize := f.fontSizePt
|
||
|
lw := f.lineWidth
|
||
|
dc := f.color.draw
|
||
|
fc := f.color.fill
|
||
|
tc := f.color.text
|
||
|
cf := f.colorFlag
|
||
|
|
||
|
if f.page > 0 {
|
||
|
f.inFooter = true
|
||
|
// Page footer avoid double call on footer.
|
||
|
if f.footerFnc != nil {
|
||
|
f.footerFnc()
|
||
|
|
||
|
} else if f.footerFncLpi != nil {
|
||
|
f.footerFncLpi(false) // not last page.
|
||
|
}
|
||
|
f.inFooter = false
|
||
|
// Close page
|
||
|
f.endpage()
|
||
|
}
|
||
|
// Start new page
|
||
|
f.beginpage(orientationStr, size)
|
||
|
// Set line cap style to current value
|
||
|
// f.out("2 J")
|
||
|
f.outf("%d J", f.capStyle)
|
||
|
// Set line join style to current value
|
||
|
f.outf("%d j", f.joinStyle)
|
||
|
// Set line width
|
||
|
f.lineWidth = lw
|
||
|
f.outf("%.2f w", lw*f.k)
|
||
|
// Set dash pattern
|
||
|
if len(f.dashArray) > 0 {
|
||
|
f.outputDashPattern()
|
||
|
}
|
||
|
// Set font
|
||
|
if familyStr != "" {
|
||
|
f.SetFont(familyStr, style, fontsize)
|
||
|
if f.err != nil {
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
// Set colors
|
||
|
f.color.draw = dc
|
||
|
if dc.str != "0 G" {
|
||
|
f.out(dc.str)
|
||
|
}
|
||
|
f.color.fill = fc
|
||
|
if fc.str != "0 g" {
|
||
|
f.out(fc.str)
|
||
|
}
|
||
|
f.color.text = tc
|
||
|
f.colorFlag = cf
|
||
|
// Page header
|
||
|
if f.headerFnc != nil {
|
||
|
f.inHeader = true
|
||
|
f.headerFnc()
|
||
|
f.inHeader = false
|
||
|
if f.headerHomeMode {
|
||
|
f.SetHomeXY()
|
||
|
}
|
||
|
}
|
||
|
// Restore line width
|
||
|
if f.lineWidth != lw {
|
||
|
f.lineWidth = lw
|
||
|
f.outf("%.2f w", lw*f.k)
|
||
|
}
|
||
|
// Restore font
|
||
|
if familyStr != "" {
|
||
|
f.SetFont(familyStr, style, fontsize)
|
||
|
if f.err != nil {
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
// Restore colors
|
||
|
if f.color.draw.str != dc.str {
|
||
|
f.color.draw = dc
|
||
|
f.out(dc.str)
|
||
|
}
|
||
|
if f.color.fill.str != fc.str {
|
||
|
f.color.fill = fc
|
||
|
f.out(fc.str)
|
||
|
}
|
||
|
f.color.text = tc
|
||
|
f.colorFlag = cf
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// AddPage adds a new page to the document. If a page is already present, the
|
||
|
// Footer() method is called first to output the footer. Then the page is
|
||
|
// added, the current position set to the top-left corner according to the left
|
||
|
// and top margins, and Header() is called to display the header.
|
||
|
//
|
||
|
// The font which was set before calling is automatically restored. There is no
|
||
|
// need to call SetFont() again if you want to continue with the same font. The
|
||
|
// same is true for colors and line width.
|
||
|
//
|
||
|
// The origin of the coordinate system is at the top-left corner and increasing
|
||
|
// ordinates go downwards.
|
||
|
//
|
||
|
// See AddPageFormat() for a version of this method that allows the page size
|
||
|
// and orientation to be different than the default.
|
||
|
func (f *Fpdf) AddPage() {
|
||
|
if f.err != nil {
|
||
|
return
|
||
|
}
|
||
|
// dbg("AddPage")
|
||
|
f.AddPageFormat(f.defOrientation, f.defPageSize)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// PageNo returns the current page number.
|
||
|
//
|
||
|
// See the example for AddPage() for a demonstration of this method.
|
||
|
func (f *Fpdf) PageNo() int {
|
||
|
return f.page
|
||
|
}
|
||
|
|
||
|
func colorComp(v int) (int, float64) {
|
||
|
if v < 0 {
|
||
|
v = 0
|
||
|
} else if v > 255 {
|
||
|
v = 255
|
||
|
}
|
||
|
return v, float64(v) / 255.0
|
||
|
}
|
||
|
|
||
|
func rgbColorValue(r, g, b int, grayStr, fullStr string) (clr colorType) {
|
||
|
clr.ir, clr.r = colorComp(r)
|
||
|
clr.ig, clr.g = colorComp(g)
|
||
|
clr.ib, clr.b = colorComp(b)
|
||
|
clr.mode = colorModeRGB
|
||
|
clr.gray = clr.ir == clr.ig && clr.r == clr.b
|
||
|
if len(grayStr) > 0 {
|
||
|
if clr.gray {
|
||
|
clr.str = sprintf("%.3f %s", clr.r, grayStr)
|
||
|
} else {
|
||
|
clr.str = sprintf("%.3f %.3f %.3f %s", clr.r, clr.g, clr.b, fullStr)
|
||
|
}
|
||
|
} else {
|
||
|
clr.str = sprintf("%.3f %.3f %.3f", clr.r, clr.g, clr.b)
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// SetDrawColor defines the color used for all drawing operations (lines,
|
||
|
// rectangles and cell borders). It is expressed in RGB components (0 - 255).
|
||
|
// The method can be called before the first page is created. The value is
|
||
|
// retained from page to page.
|
||
|
func (f *Fpdf) SetDrawColor(r, g, b int) {
|
||
|
f.setDrawColor(r, g, b)
|
||
|
}
|
||
|
|
||
|
func (f *Fpdf) setDrawColor(r, g, b int) {
|
||
|
f.color.draw = rgbColorValue(r, g, b, "G", "RG")
|
||
|
if f.page > 0 {
|
||
|
f.out(f.color.draw.str)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// GetDrawColor returns the most recently set draw color as RGB components (0 -
|
||
|
// 255). This will not be the current value if a draw color of some other type
|
||
|
// (for example, spot) has been more recently set.
|
||
|
func (f *Fpdf) GetDrawColor() (int, int, int) {
|
||
|
return f.color.draw.ir, f.color.draw.ig, f.color.draw.ib
|
||
|
}
|
||
|
|
||
|
// SetFillColor defines the color used for all filling operations (filled
|
||
|
// rectangles and cell backgrounds). It is expressed in RGB components (0
|
||
|
// -255). The method can be called before the first page is created and the
|
||
|
// value is retained from page to page.
|
||
|
func (f *Fpdf) SetFillColor(r, g, b int) {
|
||
|
f.setFillColor(r, g, b)
|
||
|
}
|
||
|
|
||
|
func (f *Fpdf) setFillColor(r, g, b int) {
|
||
|
f.color.fill = rgbColorValue(r, g, b, "g", "rg")
|
||
|
f.colorFlag = f.color.fill.str != f.color.text.str
|
||
|
if f.page > 0 {
|
||
|
f.out(f.color.fill.str)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// GetFillColor returns the most recently set fill color as RGB components (0 -
|
||
|
// 255). This will not be the current value if a fill color of some other type
|
||
|
// (for example, spot) has been more recently set.
|
||
|
func (f *Fpdf) GetFillColor() (int, int, int) {
|
||
|
return f.color.fill.ir, f.color.fill.ig, f.color.fill.ib
|
||
|
}
|
||
|
|
||
|
// SetTextColor defines the color used for text. It is expressed in RGB
|
||
|
// components (0 - 255). The method can be called before the first page is
|
||
|
// created. The value is retained from page to page.
|
||
|
func (f *Fpdf) SetTextColor(r, g, b int) {
|
||
|
f.setTextColor(r, g, b)
|
||
|
}
|
||
|
|
||
|
func (f *Fpdf) setTextColor(r, g, b int) {
|
||
|
f.color.text = rgbColorValue(r, g, b, "g", "rg")
|
||
|
f.colorFlag = f.color.fill.str != f.color.text.str
|
||
|
}
|
||
|
|
||
|
// GetTextColor returns the most recently set text color as RGB components (0 -
|
||
|
// 255). This will not be the current value if a text color of some other type
|
||
|
// (for example, spot) has been more recently set.
|
||
|
func (f *Fpdf) GetTextColor() (int, int, int) {
|
||
|
return f.color.text.ir, f.color.text.ig, f.color.text.ib
|
||
|
}
|
||
|
|
||
|
// GetStringWidth returns the length of a string in user units. A font must be
|
||
|
// currently selected.
|
||
|
func (f *Fpdf) GetStringWidth(s string) float64 {
|
||
|
if f.err != nil {
|
||
|
return 0
|
||
|
}
|
||
|
w := f.GetStringSymbolWidth(s)
|
||
|
return float64(w) * f.fontSize / 1000
|
||
|
}
|
||
|
|
||
|
// GetStringSymbolWidth returns the length of a string in glyf units. A font must be
|
||
|
// currently selected.
|
||
|
func (f *Fpdf) GetStringSymbolWidth(s string) int {
|
||
|
if f.err != nil {
|
||
|
return 0
|
||
|
}
|
||
|
w := 0
|
||
|
if f.isCurrentUTF8 {
|
||
|
unicode := []rune(s)
|
||
|
for _, char := range unicode {
|
||
|
intChar := int(char)
|
||
|
if len(f.currentFont.Cw) >= intChar && f.currentFont.Cw[intChar] > 0 {
|
||
|
if f.currentFont.Cw[intChar] != 65535 {
|
||
|
w += f.currentFont.Cw[intChar]
|
||
|
}
|
||
|
} else if f.currentFont.Desc.MissingWidth != 0 {
|
||
|
w += f.currentFont.Desc.MissingWidth
|
||
|
} else {
|
||
|
w += 500
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
for _, ch := range []byte(s) {
|
||
|
if ch == 0 {
|
||
|
break
|
||
|
}
|
||
|
w += f.currentFont.Cw[ch]
|
||
|
}
|
||
|
}
|
||
|
return w
|
||
|
}
|
||
|
|
||
|
// SetLineWidth defines the line width. By default, the value equals 0.2 mm.
|
||
|
// The method can be called before the first page is created. The value is
|
||
|
// retained from page to page.
|
||
|
func (f *Fpdf) SetLineWidth(width float64) {
|
||
|
f.setLineWidth(width)
|
||
|
}
|
||
|
|
||
|
func (f *Fpdf) setLineWidth(width float64) {
|
||
|
f.lineWidth = width
|
||
|
if f.page > 0 {
|
||
|
f.outf("%.2f w", width*f.k)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// GetLineWidth returns the current line thickness.
|
||
|
func (f *Fpdf) GetLineWidth() float64 {
|
||
|
return f.lineWidth
|
||
|
}
|
||
|
|
||
|
// SetLineCapStyle defines the line cap style. styleStr should be "butt",
|
||
|
// "round" or "square". A square style projects from the end of the line. The
|
||
|
// method can be called before the first page is created. The value is
|
||
|
// retained from page to page.
|
||
|
func (f *Fpdf) SetLineCapStyle(styleStr string) {
|
||
|
var capStyle int
|
||
|
switch styleStr {
|
||
|
case "round":
|
||
|
capStyle = 1
|
||
|
case "square":
|
||
|
capStyle = 2
|
||
|
default:
|
||
|
capStyle = 0
|
||
|
}
|
||
|
f.capStyle = capStyle
|
||
|
if f.page > 0 {
|
||
|
f.outf("%d J", f.capStyle)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// SetLineJoinStyle defines the line cap style. styleStr should be "miter",
|
||
|
// "round" or "bevel". The method can be called before the first page
|
||
|
// is created. The value is retained from page to page.
|
||
|
func (f *Fpdf) SetLineJoinStyle(styleStr string) {
|
||
|
var joinStyle int
|
||
|
switch styleStr {
|
||
|
case "round":
|
||
|
joinStyle = 1
|
||
|
case "bevel":
|
||
|
joinStyle = 2
|
||
|
default:
|
||
|
joinStyle = 0
|
||
|
}
|
||
|
f.joinStyle = joinStyle
|
||
|
if f.page > 0 {
|
||
|
f.outf("%d j", f.joinStyle)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// SetDashPattern sets the dash pattern that is used to draw lines. The
|
||
|
// dashArray elements are numbers that specify the lengths, in units
|
||
|
// established in New(), of alternating dashes and gaps. The dash phase
|
||
|
// specifies the distance into the dash pattern at which to start the dash. The
|
||
|
// dash pattern is retained from page to page. Call this method with an empty
|
||
|
// array to restore solid line drawing.
|
||
|
//
|
||
|
// The Beziergon() example demonstrates this method.
|
||
|
func (f *Fpdf) SetDashPattern(dashArray []float64, dashPhase float64) {
|
||
|
scaled := make([]float64, len(dashArray))
|
||
|
for i, value := range dashArray {
|
||
|
scaled[i] = value * f.k
|
||
|
}
|
||
|
dashPhase *= f.k
|
||
|
|
||
|
f.dashArray = scaled
|
||
|
f.dashPhase = dashPhase
|
||
|
if f.page > 0 {
|
||
|
f.outputDashPattern()
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
func (f *Fpdf) outputDashPattern() {
|
||
|
var buf bytes.Buffer
|
||
|
buf.WriteByte('[')
|
||
|
for i, value := range f.dashArray {
|
||
|
if i > 0 {
|
||
|
buf.WriteByte(' ')
|
||
|
}
|
||
|
buf.WriteString(strconv.FormatFloat(value, 'f', 2, 64))
|
||
|
}
|
||
|
buf.WriteString("] ")
|
||
|
buf.WriteString(strconv.FormatFloat(f.dashPhase, 'f', 2, 64))
|
||
|
buf.WriteString(" d")
|
||
|
f.outbuf(&buf)
|
||
|
}
|
||
|
|
||
|
// Line draws a line between points (x1, y1) and (x2, y2) using the current
|
||
|
// draw color, line width and cap style.
|
||
|
func (f *Fpdf) Line(x1, y1, x2, y2 float64) {
|
||
|
f.outf("%.2f %.2f m %.2f %.2f l S", x1*f.k, (f.h-y1)*f.k, x2*f.k, (f.h-y2)*f.k)
|
||
|
}
|
||
|
|
||
|
// fillDrawOp corrects path painting operators
|
||
|
func fillDrawOp(styleStr string) (opStr string) {
|
||
|
switch strings.ToUpper(styleStr) {
|
||
|
case "", "D":
|
||
|
// Stroke the path.
|
||
|
opStr = "S"
|
||
|
case "F":
|
||
|
// fill the path, using the nonzero winding number rule
|
||
|
opStr = "f"
|
||
|
case "F*":
|
||
|
// fill the path, using the even-odd rule
|
||
|
opStr = "f*"
|
||
|
case "FD", "DF":
|
||
|
// fill and then stroke the path, using the nonzero winding number rule
|
||
|
opStr = "B"
|
||
|
case "FD*", "DF*":
|
||
|
// fill and then stroke the path, using the even-odd rule
|
||
|
opStr = "B*"
|
||
|
default:
|
||
|
opStr = styleStr
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Rect outputs a rectangle of width w and height h with the upper left corner
|
||
|
// positioned at point (x, y).
|
||
|
//
|
||
|
// It can be drawn (border only), filled (with no border) or both. styleStr can
|
||
|
// be "F" for filled, "D" for outlined only, or "DF" or "FD" for outlined and
|
||
|
// filled. An empty string will be replaced with "D". Drawing uses the current
|
||
|
// draw color and line width centered on the rectangle's perimeter. Filling
|
||
|
// uses the current fill color.
|
||
|
func (f *Fpdf) Rect(x, y, w, h float64, styleStr string) {
|
||
|
f.outf("%.2f %.2f %.2f %.2f re %s", x*f.k, (f.h-y)*f.k, w*f.k, -h*f.k, fillDrawOp(styleStr))
|
||
|
}
|
||
|
|
||
|
// RoundedRect outputs a rectangle of width w and height h with the upper left
|
||
|
// corner positioned at point (x, y). It can be drawn (border only), filled
|
||
|
// (with no border) or both. styleStr can be "F" for filled, "D" for outlined
|
||
|
// only, or "DF" or "FD" for outlined and filled. An empty string will be
|
||
|
// replaced with "D". Drawing uses the current draw color and line width
|
||
|
// centered on the rectangle's perimeter. Filling uses the current fill color.
|
||
|
// The rounded corners of the rectangle are specified by radius r. corners is a
|
||
|
// string that includes "1" to round the upper left corner, "2" to round the
|
||
|
// upper right corner, "3" to round the lower right corner, and "4" to round
|
||
|
// the lower left corner. The RoundedRect example demonstrates this method.
|
||
|
func (f *Fpdf) RoundedRect(x, y, w, h, r float64, corners string, stylestr string) {
|
||
|
// This routine was adapted by Brigham Thompson from a script by Christophe Prugnaud
|
||
|
var rTL, rTR, rBR, rBL float64 // zero means no rounded corner
|
||
|
if strings.Contains(corners, "1") {
|
||
|
rTL = r
|
||
|
}
|
||
|
if strings.Contains(corners, "2") {
|
||
|
rTR = r
|
||
|
}
|
||
|
if strings.Contains(corners, "3") {
|
||
|
rBR = r
|
||
|
}
|
||
|
if strings.Contains(corners, "4") {
|
||
|
rBL = r
|
||
|
}
|
||
|
f.RoundedRectExt(x, y, w, h, rTL, rTR, rBR, rBL, stylestr |