5039 lines
148 KiB
Go
5039 lines
148 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.7a1
|
|
// Date: 2011-06-18
|
|
// Author: Olivier PLATHEY
|
|
// Port to Go: Kurt Jung, 2013-07-15
|
|
// Additions: Klaus Wendel, archium GmbH/Gera, 2021-03-02
|
|
|
|
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, density float64) (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 = "."
|
|
}
|
|
// Page density in dpi
|
|
if density == 0.0 {
|
|
density = 72.0
|
|
}
|
|
|
|
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 = density / 25.4
|
|
case "cm":
|
|
f.k = density / 2.54
|
|
case "in", "inch":
|
|
f.k = density
|
|
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, init.Density)
|
|
}
|
|
|
|
// 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, density float64) (f *Fpdf) {
|
|
return fpdfNew(orientationStr, unitStr, sizeStr, fontDirStr, SizeType{0, 0}, density)
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
// RoundedRectExt behaves the same as RoundedRect() but supports a different
|
|
// radius for each corner. A zero radius means squared corner. See
|
|
// RoundedRect() for more details. This method is demonstrated in the
|
|
// RoundedRect() example.
|
|
func (f *Fpdf) RoundedRectExt(x, y, w, h, rTL, rTR, rBR, rBL float64, stylestr string) {
|
|
f.roundedRectPath(x, y, w, h, rTL, rTR, rBR, rBL)
|
|
f.out(fillDrawOp(stylestr))
|
|
}
|
|
|
|
// Circle draws a circle centered on point (x, y) with radius r.
|
|
//
|
|
// styleStr can be "F" for filled, "D" for outlined only, or "DF" or "FD" for
|
|
// outlined and filled. An empty string will be replaced with "D". Drawing uses
|
|
// the current draw color and line width centered on the circle's perimeter.
|
|
// Filling uses the current fill color.
|
|
func (f *Fpdf) Circle(x, y, r float64, styleStr string) {
|
|
f.Ellipse(x, y, r, r, 0, styleStr)
|
|
}
|
|
|
|
// Ellipse draws an ellipse centered at point (x, y). rx and ry specify its
|
|
// horizontal and vertical radii.
|
|
//
|
|
// degRotate specifies the counter-clockwise angle in degrees that the ellipse
|
|
// will be rotated.
|
|
//
|
|
// styleStr can be "F" for filled, "D" for outlined only, or "DF" or "FD" for
|
|
// outlined and filled. An empty string will be replaced with "D". Drawing uses
|
|
// the current draw color and line width centered on the ellipse's perimeter.
|
|
// Filling uses the current fill color.
|
|
//
|
|
// The Circle() example demonstrates this method.
|
|
func (f *Fpdf) Ellipse(x, y, rx, ry, degRotate float64, styleStr string) {
|
|
f.arc(x, y, rx, ry, degRotate, 0, 360, styleStr, false)
|
|
}
|
|
|
|
// Polygon draws a closed figure defined by a series of vertices specified by
|
|
// points. The x and y fields of the points use the units established in New().
|
|
// The last point in the slice will be implicitly joined to the first to close
|
|
// the polygon.
|
|
//
|
|
// styleStr can be "F" for filled, "D" for outlined only, or "DF" or "FD" for
|
|
// outlined and filled. An empty string will be replaced with "D". Drawing uses
|
|
// the current draw color and line width centered on the ellipse's perimeter.
|
|
// Filling uses the current fill color.
|
|
func (f *Fpdf) Polygon(points []PointType, styleStr string) {
|
|
if len(points) > 2 {
|
|
for j, pt := range points {
|
|
if j == 0 {
|
|
f.point(pt.X, pt.Y)
|
|
} else {
|
|
f.outf("%.5f %.5f l ", pt.X*f.k, (f.h-pt.Y)*f.k)
|
|
}
|
|
}
|
|
f.outf("%.5f %.5f l ", points[0].X*f.k, (f.h-points[0].Y)*f.k)
|
|
f.DrawPath(styleStr)
|
|
}
|
|
}
|
|
|
|
// Beziergon draws a closed figure defined by a series of cubic Bézier curve
|
|
// segments. The first point in the slice defines the starting point of the
|
|
// figure. Each three following points p1, p2, p3 represent a curve segment to
|
|
// the point p3 using p1 and p2 as the Bézier control points.
|
|
//
|
|
// The x and y fields of the points use the units established in New().
|
|
//
|
|
// styleStr can be "F" for filled, "D" for outlined only, or "DF" or "FD" for
|
|
// outlined and filled. An empty string will be replaced with "D". Drawing uses
|
|
// the current draw color and line width centered on the ellipse's perimeter.
|
|
// Filling uses the current fill color.
|
|
func (f *Fpdf) Beziergon(points []PointType, styleStr string) {
|
|
|
|
// Thanks, Robert Lillack, for contributing this function.
|
|
|
|
if len(points) < 4 {
|
|
return
|
|
}
|
|
f.point(points[0].XY())
|
|
|
|
points = points[1:]
|
|
for len(points) >= 3 {
|
|
cx0, cy0 := points[0].XY()
|
|
cx1, cy1 := points[1].XY()
|
|
x1, y1 := points[2].XY()
|
|
f.curve(cx0, cy0, cx1, cy1, x1, y1)
|
|
points = points[3:]
|
|
}
|
|
|
|
f.DrawPath(styleStr)
|
|
}
|
|
|
|
// point outputs current point
|
|
func (f *Fpdf) point(x, y float64) {
|
|
f.outf("%.2f %.2f m", x*f.k, (f.h-y)*f.k)
|
|
}
|
|
|
|
// curve outputs a single cubic Bézier curve segment from current point
|
|
func (f *Fpdf) curve(cx0, cy0, cx1, cy1, x, y float64) {
|
|
// Thanks, Robert Lillack, for straightening this out
|
|
f.outf("%.5f %.5f %.5f %.5f %.5f %.5f c", cx0*f.k, (f.h-cy0)*f.k, cx1*f.k,
|
|
(f.h-cy1)*f.k, x*f.k, (f.h-y)*f.k)
|
|
}
|
|
|
|
// Curve draws a single-segment quadratic Bézier curve. The curve starts at
|
|
// the point (x0, y0) and ends at the point (x1, y1). The control point (cx,
|
|
// cy) specifies the curvature. At the start point, the curve is tangent to the
|
|
// straight line between the start point and the control point. At the end
|
|
// point, the curve is tangent to the straight line between the end point and
|
|
// the control point.
|
|
//
|
|
// styleStr can be "F" for filled, "D" for outlined only, or "DF" or "FD" for
|
|
// outlined and filled. An empty string will be replaced with "D". Drawing uses
|
|
// the current draw color, line width, and cap style centered on the curve's
|
|
// path. Filling uses the current fill color.
|
|
//
|
|
// The Circle() example demonstrates this method.
|
|
func (f *Fpdf) Curve(x0, y0, cx, cy, x1, y1 float64, styleStr string) {
|
|
f.point(x0, y0)
|
|
f.outf("%.5f %.5f %.5f %.5f v %s", cx*f.k, (f.h-cy)*f.k, x1*f.k, (f.h-y1)*f.k,
|
|
fillDrawOp(styleStr))
|
|
}
|
|
|
|
// CurveCubic draws a single-segment cubic Bézier curve. This routine performs
|
|
// the same function as CurveBezierCubic() but has a nonstandard argument order.
|
|
// It is retained to preserve backward compatibility.
|
|
func (f *Fpdf) CurveCubic(x0, y0, cx0, cy0, x1, y1, cx1, cy1 float64, styleStr string) {
|
|
// f.point(x0, y0)
|
|
// f.outf("%.5f %.5f %.5f %.5f %.5f %.5f c %s", cx0*f.k, (f.h-cy0)*f.k,
|
|
// cx1*f.k, (f.h-cy1)*f.k, x1*f.k, (f.h-y1)*f.k, fillDrawOp(styleStr))
|
|
f.CurveBezierCubic(x0, y0, cx0, cy0, cx1, cy1, x1, y1, styleStr)
|
|
}
|
|
|
|
// CurveBezierCubic draws a single-segment cubic Bézier curve. The curve starts at
|
|
// the point (x0, y0) and ends at the point (x1, y1). The control points (cx0,
|
|
// cy0) and (cx1, cy1) specify the curvature. At the start point, the curve is
|
|
// tangent to the straight line between the start point and the control point
|
|
// (cx0, cy0). At the end point, the curve is tangent to the straight line
|
|
// between the end point and the control point (cx1, cy1).
|
|
//
|
|
// styleStr can be "F" for filled, "D" for outlined only, or "DF" or "FD" for
|
|
// outlined and filled. An empty string will be replaced with "D". Drawing uses
|
|
// the current draw color, line width, and cap style centered on the curve's
|
|
// path. Filling uses the current fill color.
|
|
//
|
|
// This routine performs the same function as CurveCubic() but uses standard
|
|
// argument order.
|
|
//
|
|
// The Circle() example demonstrates this method.
|
|
func (f *Fpdf) CurveBezierCubic(x0, y0, cx0, cy0, cx1, cy1, x1, y1 float64, styleStr string) {
|
|
f.point(x0, y0)
|
|
f.outf("%.5f %.5f %.5f %.5f %.5f %.5f c %s", cx0*f.k, (f.h-cy0)*f.k,
|
|
cx1*f.k, (f.h-cy1)*f.k, x1*f.k, (f.h-y1)*f.k, fillDrawOp(styleStr))
|
|
}
|
|
|
|
// Arc draws an elliptical arc centered at point (x, y). rx and ry specify its
|
|
// horizontal and vertical radii.
|
|
//
|
|
// degRotate specifies the angle that the arc will be rotated. degStart and
|
|
// degEnd specify the starting and ending angle of the arc. All angles are
|
|
// specified in degrees and measured counter-clockwise from the 3 o'clock
|
|
// position.
|
|
//
|
|
// styleStr can be "F" for filled, "D" for outlined only, or "DF" or "FD" for
|
|
// outlined and filled. An empty string will be replaced with "D". Drawing uses
|
|
// the current draw color, line width, and cap style centered on the arc's
|
|
// path. Filling uses the current fill color.
|
|
//
|
|
// The Circle() example demonstrates this method.
|
|
func (f *Fpdf) Arc(x, y, rx, ry, degRotate, degStart, degEnd float64, styleStr string) {
|
|
f.arc(x, y, rx, ry, degRotate, degStart, degEnd, styleStr, false)
|
|
}
|
|
|
|
// GetAlpha returns the alpha blending channel, which consists of the
|
|
// alpha transparency value and the blend mode. See SetAlpha for more
|
|
// details.
|
|
func (f *Fpdf) GetAlpha() (alpha float64, blendModeStr string) {
|
|
return f.alpha, f.blendMode
|
|
}
|
|
|
|
// SetAlpha sets the alpha blending channel. The blending effect applies to
|
|
// text, drawings and images.
|
|
//
|
|
// alpha must be a value between 0.0 (fully transparent) to 1.0 (fully opaque).
|
|
// Values outside of this range result in an error.
|
|
//
|
|
// blendModeStr must be one of "Normal", "Multiply", "Screen", "Overlay",
|
|
// "Darken", "Lighten", "ColorDodge", "ColorBurn","HardLight", "SoftLight",
|
|
// "Difference", "Exclusion", "Hue", "Saturation", "Color", or "Luminosity". An
|
|
// empty string is replaced with "Normal".
|
|
//
|
|
// To reset normal rendering after applying a blending mode, call this method
|
|
// with alpha set to 1.0 and blendModeStr set to "Normal".
|
|
func (f *Fpdf) SetAlpha(alpha float64, blendModeStr string) {
|
|
if f.err != nil {
|
|
return
|
|
}
|
|
var bl blendModeType
|
|
switch blendModeStr {
|
|
case "Normal", "Multiply", "Screen", "Overlay",
|
|
"Darken", "Lighten", "ColorDodge", "ColorBurn", "HardLight", "SoftLight",
|
|
"Difference", "Exclusion", "Hue", "Saturation", "Color", "Luminosity":
|
|
bl.modeStr = blendModeStr
|
|
case "":
|
|
bl.modeStr = "Normal"
|
|
default:
|
|
f.err = fmt.Errorf("unrecognized blend mode \"%s\"", blendModeStr)
|
|
return
|
|
}
|
|
if alpha < 0.0 || alpha > 1.0 {
|
|
f.err = fmt.Errorf("alpha value (0.0 - 1.0) is out of range: %.3f", alpha)
|
|
return
|
|
}
|
|
f.alpha = alpha
|
|
f.blendMode = blendModeStr
|
|
alphaStr := sprintf("%.3f", alpha)
|
|
keyStr := sprintf("%s %s", alphaStr, blendModeStr)
|
|
pos, ok := f.blendMap[keyStr]
|
|
if !ok {
|
|
pos = len(f.blendList) // at least 1
|
|
f.blendList = append(f.blendList, blendModeType{alphaStr, alphaStr, blendModeStr, 0})
|
|
f.blendMap[keyStr] = pos
|
|
}
|
|
f.outf("/GS%d gs", pos)
|
|
}
|
|
|
|
func (f *Fpdf) gradientClipStart(x, y, w, h float64) {
|
|
// Save current graphic state and set clipping area
|
|
f.outf("q %.2f %.2f %.2f %.2f re W n", x*f.k, (f.h-y)*f.k, w*f.k, -h*f.k)
|
|
// Set up transformation matrix for gradient
|
|
f.outf("%.5f 0 0 %.5f %.5f %.5f cm", w*f.k, h*f.k, x*f.k, (f.h-(y+h))*f.k)
|
|
}
|
|
|
|
func (f *Fpdf) gradientClipEnd() {
|
|
// Restore previous graphic state
|
|
f.out("Q")
|
|
}
|
|
|
|
func (f *Fpdf) gradient(tp, r1, g1, b1, r2, g2, b2 int, x1, y1, x2, y2, r float64) {
|
|
pos := len(f.gradientList)
|
|
clr1 := rgbColorValue(r1, g1, b1, "", "")
|
|
clr2 := rgbColorValue(r2, g2, b2, "", "")
|
|
f.gradientList = append(f.gradientList, gradientType{tp, clr1.str, clr2.str,
|
|
x1, y1, x2, y2, r, 0})
|
|
f.outf("/Sh%d sh", pos)
|
|
}
|
|
|
|
// LinearGradient draws a rectangular area with a blending of one color to
|
|
// another. The rectangle is of width w and height h. Its upper left corner is
|
|
// positioned at point (x, y).
|
|
//
|
|
// Each color is specified with three component values, one each for red, green
|
|
// and blue. The values range from 0 to 255. The first color is specified by
|
|
// (r1, g1, b1) and the second color by (r2, g2, b2).
|
|
//
|
|
// The blending is controlled with a gradient vector that uses normalized
|
|
// coordinates in which the lower left corner is position (0, 0) and the upper
|
|
// right corner is (1, 1). The vector's origin and destination are specified by
|
|
// the points (x1, y1) and (x2, y2). In a linear gradient, blending occurs
|
|
// perpendicularly to the vector. The vector does not necessarily need to be
|
|
// anchored on the rectangle edge. Color 1 is used up to the origin of the
|
|
// vector and color 2 is used beyond the vector's end point. Between the points
|
|
// the colors are gradually blended.
|
|
func (f *Fpdf) LinearGradient(x, y, w, h float64, r1, g1, b1, r2, g2, b2 int, x1, y1, x2, y2 float64) {
|
|
f.gradientClipStart(x, y, w, h)
|
|
f.gradient(2, r1, g1, b1, r2, g2, b2, x1, y1, x2, y2, 0)
|
|
f.gradientClipEnd()
|
|
}
|
|
|
|
// RadialGradient draws a rectangular area with a blending of one color to
|
|
// another. The rectangle is of width w and height h. Its upper left corner is
|
|
// positioned at point (x, y).
|
|
//
|
|
// Each color is specified with three component values, one each for red, green
|
|
// and blue. The values range from 0 to 255. The first color is specified by
|
|
// (r1, g1, b1) and the second color by (r2, g2, b2).
|
|
//
|
|
// The blending is controlled with a point and a circle, both specified with
|
|
// normalized coordinates in which the lower left corner of the rendered
|
|
// rectangle is position (0, 0) and the upper right corner is (1, 1). Color 1
|
|
// begins at the origin point specified by (x1, y1). Color 2 begins at the
|
|
// circle specified by the center point (x2, y2) and radius r. Colors are
|
|
// gradually blended from the origin to the circle. The origin and the circle's
|
|
// center do not necessarily have to coincide, but the origin must be within
|
|
// the circle to avoid rendering problems.
|
|
//
|
|
// The LinearGradient() example demonstrates this method.
|
|
func (f *Fpdf) RadialGradient(x, y, w, h float64, r1, g1, b1, r2, g2, b2 int, x1, y1, x2, y2, r float64) {
|
|
f.gradientClipStart(x, y, w, h)
|
|
f.gradient(3, r1, g1, b1, r2, g2, b2, x1, y1, x2, y2, r)
|
|
f.gradientClipEnd()
|
|
}
|
|
|
|
// ClipRect begins a rectangular clipping operation. The rectangle is of width
|
|
// w and height h. Its upper left corner is positioned at point (x, y). outline
|
|
// is true to draw a border with the current draw color and line width centered
|
|
// on the rectangle's perimeter. Only the outer half of the border will be
|
|
// shown. After calling this method, all rendering operations (for example,
|
|
// Image(), LinearGradient(), etc) will be clipped by the specified rectangle.
|
|
// Call ClipEnd() to restore unclipped operations.
|
|
//
|
|
// This ClipText() example demonstrates this method.
|
|
func (f *Fpdf) ClipRect(x, y, w, h float64, outline bool) {
|
|
f.clipNest++
|
|
f.outf("q %.2f %.2f %.2f %.2f re W %s", x*f.k, (f.h-y)*f.k, w*f.k, -h*f.k, strIf(outline, "S", "n"))
|
|
}
|
|
|
|
// ClipText begins a clipping operation in which rendering is confined to the
|
|
// character string specified by txtStr. The origin (x, y) is on the left of
|
|
// the first character at the baseline. The current font is used. outline is
|
|
// true to draw a border with the current draw color and line width centered on
|
|
// the perimeters of the text characters. Only the outer half of the border
|
|
// will be shown. After calling this method, all rendering operations (for
|
|
// example, Image(), LinearGradient(), etc) will be clipped. Call ClipEnd() to
|
|
// restore unclipped operations.
|
|
func (f *Fpdf) ClipText(x, y float64, txtStr string, outline bool) {
|
|
f.clipNest++
|
|
f.outf("q BT %.5f %.5f Td %d Tr (%s) Tj ET", x*f.k, (f.h-y)*f.k, intIf(outline, 5, 7), f.escape(txtStr))
|
|
}
|
|
|
|
func (f *Fpdf) clipArc(x1, y1, x2, y2, x3, y3 float64) {
|
|
h := f.h
|
|
f.outf("%.5f %.5f %.5f %.5f %.5f %.5f c ", x1*f.k, (h-y1)*f.k,
|
|
x2*f.k, (h-y2)*f.k, x3*f.k, (h-y3)*f.k)
|
|
}
|
|
|
|
// ClipRoundedRect begins a rectangular clipping operation. The rectangle is of
|
|
// width w and height h. Its upper left corner is positioned at point (x, y).
|
|
// The rounded corners of the rectangle are specified by radius r. outline is
|
|
// true to draw a border with the current draw color and line width centered on
|
|
// the rectangle's perimeter. Only the outer half of the border will be shown.
|
|
// After calling this method, all rendering operations (for example, Image(),
|
|
// LinearGradient(), etc) will be clipped by the specified rectangle. Call
|
|
// ClipEnd() to restore unclipped operations.
|
|
//
|
|
// This ClipText() example demonstrates this method.
|
|
func (f *Fpdf) ClipRoundedRect(x, y, w, h, r float64, outline bool) {
|
|
f.ClipRoundedRectExt(x, y, w, h, r, r, r, r, outline)
|
|
}
|
|
|
|
// ClipRoundedRectExt behaves the same as ClipRoundedRect() but supports a
|
|
// different radius for each corner, given by rTL (top-left), rTR (top-right)
|
|
// rBR (bottom-right), rBL (bottom-left). See ClipRoundedRect() for more
|
|
// details. This method is demonstrated in the ClipText() example.
|
|
func (f *Fpdf) ClipRoundedRectExt(x, y, w, h, rTL, rTR, rBR, rBL float64, outline bool) {
|
|
f.clipNest++
|
|
f.roundedRectPath(x, y, w, h, rTL, rTR, rBR, rBL)
|
|
f.outf(" W %s", strIf(outline, "S", "n"))
|
|
}
|
|
|
|
// add a rectangle path with rounded corners.
|
|
// routine shared by RoundedRect() and ClipRoundedRect(), which add the
|
|
// drawing operation
|
|
func (f *Fpdf) roundedRectPath(x, y, w, h, rTL, rTR, rBR, rBL float64) {
|
|
k := f.k
|
|
hp := f.h
|
|
myArc := (4.0 / 3.0) * (math.Sqrt2 - 1.0)
|
|
f.outf("q %.5f %.5f m", (x+rTL)*k, (hp-y)*k)
|
|
xc := x + w - rTR
|
|
yc := y + rTR
|
|
f.outf("%.5f %.5f l", xc*k, (hp-y)*k)
|
|
if rTR != 0 {
|
|
f.clipArc(xc+rTR*myArc, yc-rTR, xc+rTR, yc-rTR*myArc, xc+rTR, yc)
|
|
}
|
|
xc = x + w - rBR
|
|
yc = y + h - rBR
|
|
f.outf("%.5f %.5f l", (x+w)*k, (hp-yc)*k)
|
|
if rBR != 0 {
|
|
f.clipArc(xc+rBR, yc+rBR*myArc, xc+rBR*myArc, yc+rBR, xc, yc+rBR)
|
|
}
|
|
xc = x + rBL
|
|
yc = y + h - rBL
|
|
f.outf("%.5f %.5f l", xc*k, (hp-(y+h))*k)
|
|
if rBL != 0 {
|
|
f.clipArc(xc-rBL*myArc, yc+rBL, xc-rBL, yc+rBL*myArc, xc-rBL, yc)
|
|
}
|
|
xc = x + rTL
|
|
yc = y + rTL
|
|
f.outf("%.5f %.5f l", x*k, (hp-yc)*k)
|
|
if rTL != 0 {
|
|
f.clipArc(xc-rTL, yc-rTL*myArc, xc-rTL*myArc, yc-rTL, xc, yc-rTL)
|
|
}
|
|
}
|
|
|
|
// ClipEllipse begins an elliptical clipping operation. The ellipse is centered
|
|
// at (x, y). Its horizontal and vertical radii are specified by rx and ry.
|
|
// outline is true to draw a border with the current draw color and line width
|
|
// centered on the ellipse's perimeter. Only the outer half of the border will
|
|
// be shown. After calling this method, all rendering operations (for example,
|
|
// Image(), LinearGradient(), etc) will be clipped by the specified ellipse.
|
|
// Call ClipEnd() to restore unclipped operations.
|
|
//
|
|
// This ClipText() example demonstrates this method.
|
|
func (f *Fpdf) ClipEllipse(x, y, rx, ry float64, outline bool) {
|
|
f.clipNest++
|
|
lx := (4.0 / 3.0) * rx * (math.Sqrt2 - 1)
|
|
ly := (4.0 / 3.0) * ry * (math.Sqrt2 - 1)
|
|
k := f.k
|
|
h := f.h
|
|
f.outf("q %.5f %.5f m %.5f %.5f %.5f %.5f %.5f %.5f c",
|
|
(x+rx)*k, (h-y)*k,
|
|
(x+rx)*k, (h-(y-ly))*k,
|
|
(x+lx)*k, (h-(y-ry))*k,
|
|
x*k, (h-(y-ry))*k)
|
|
f.outf("%.5f %.5f %.5f %.5f %.5f %.5f c",
|
|
(x-lx)*k, (h-(y-ry))*k,
|
|
(x-rx)*k, (h-(y-ly))*k,
|
|
(x-rx)*k, (h-y)*k)
|
|
f.outf("%.5f %.5f %.5f %.5f %.5f %.5f c",
|
|
(x-rx)*k, (h-(y+ly))*k,
|
|
(x-lx)*k, (h-(y+ry))*k,
|
|
x*k, (h-(y+ry))*k)
|
|
f.outf("%.5f %.5f %.5f %.5f %.5f %.5f c W %s",
|
|
(x+lx)*k, (h-(y+ry))*k,
|
|
(x+rx)*k, (h-(y+ly))*k,
|
|
(x+rx)*k, (h-y)*k,
|
|
strIf(outline, "S", "n"))
|
|
}
|
|
|
|
// ClipCircle begins a circular clipping operation. The circle is centered at
|
|
// (x, y) and has radius r. outline is true to draw a border with the current
|
|
// draw color and line width centered on the circle's perimeter. Only the outer
|
|
// half of the border will be shown. After calling this method, all rendering
|
|
// operations (for example, Image(), LinearGradient(), etc) will be clipped by
|
|
// the specified circle. Call ClipEnd() to restore unclipped operations.
|
|
//
|
|
// The ClipText() example demonstrates this method.
|
|
func (f *Fpdf) ClipCircle(x, y, r float64, outline bool) {
|
|
f.ClipEllipse(x, y, r, r, outline)
|
|
}
|
|
|
|
// ClipPolygon begins a clipping operation within a polygon. The figure is
|
|
// defined by a series of vertices specified by points. The x and y fields of
|
|
// the points use the units established in New(). The last point in the slice
|
|
// will be implicitly joined to the first to close the polygon. outline is true
|
|
// to draw a border with the current draw color and line width centered on the
|
|
// polygon's perimeter. Only the outer half of the border will be shown. After
|
|
// calling this method, all rendering operations (for example, Image(),
|
|
// LinearGradient(), etc) will be clipped by the specified polygon. Call
|
|
// ClipEnd() to restore unclipped operations.
|
|
//
|
|
// The ClipText() example demonstrates this method.
|
|
func (f *Fpdf) ClipPolygon(points []PointType, outline bool) {
|
|
f.clipNest++
|
|
var s fmtBuffer
|
|
h := f.h
|
|
k := f.k
|
|
s.printf("q ")
|
|
for j, pt := range points {
|
|
s.printf("%.5f %.5f %s ", pt.X*k, (h-pt.Y)*k, strIf(j == 0, "m", "l"))
|
|
}
|
|
s.printf("h W %s", strIf(outline, "S", "n"))
|
|
f.out(s.String())
|
|
}
|
|
|
|
// ClipEnd ends a clipping operation that was started with a call to
|
|
// ClipRect(), ClipRoundedRect(), ClipText(), ClipEllipse(), ClipCircle() or
|
|
// ClipPolygon(). Clipping operations can be nested. The document cannot be
|
|
// successfully output while a clipping operation is active.
|
|
//
|
|
// The ClipText() example demonstrates this method.
|
|
func (f *Fpdf) ClipEnd() {
|
|
if f.err == nil {
|
|
if f.clipNest > 0 {
|
|
f.clipNest--
|
|
f.out("Q")
|
|
} else {
|
|
f.err = fmt.Errorf("error attempting to end clip operation out of sequence")
|
|
}
|
|
}
|
|
}
|
|
|
|
// AddFont imports a TrueType, OpenType or Type1 font and makes it available.
|
|
// It is necessary to generate a font definition file first with the makefont
|
|
// utility. It is not necessary to call this function for the core PDF fonts
|
|
// (courier, helvetica, times, zapfdingbats).
|
|
//
|
|
// The JSON definition file (and the font file itself when embedding) must be
|
|
// present in the font directory. If it is not found, the error "Could not
|
|
// include font definition file" is set.
|
|
//
|
|
// family specifies the font family. The name can be chosen arbitrarily. If it
|
|
// is a standard family name, it will override the corresponding font. This
|
|
// string is used to subsequently set the font with the SetFont method.
|
|
//
|
|
// style specifies the font style. Acceptable values are (case insensitive) the
|
|
// empty string for regular style, "B" for bold, "I" for italic, or "BI" or
|
|
// "IB" for bold and italic combined.
|
|
//
|
|
// fileStr specifies the base name with ".json" extension of the font
|
|
// definition file to be added. The file will be loaded from the font directory
|
|
// specified in the call to New() or SetFontLocation().
|
|
func (f *Fpdf) AddFont(familyStr, styleStr, fileStr string) {
|
|
f.addFont(fontFamilyEscape(familyStr), styleStr, fileStr, false)
|
|
}
|
|
|
|
// AddUTF8Font imports a TrueType font with utf-8 symbols and makes it available.
|
|
// It is necessary to generate a font definition file first with the makefont
|
|
// utility. It is not necessary to call this function for the core PDF fonts
|
|
// (courier, helvetica, times, zapfdingbats).
|
|
//
|
|
// The JSON definition file (and the font file itself when embedding) must be
|
|
// present in the font directory. If it is not found, the error "Could not
|
|
// include font definition file" is set.
|
|
//
|
|
// family specifies the font family. The name can be chosen arbitrarily. If it
|
|
// is a standard family name, it will override the corresponding font. This
|
|
// string is used to subsequently set the font with the SetFont method.
|
|
//
|
|
// style specifies the font style. Acceptable values are (case insensitive) the
|
|
// empty string for regular style, "B" for bold, "I" for italic, or "BI" or
|
|
// "IB" for bold and italic combined.
|
|
//
|
|
// fileStr specifies the base name with ".json" extension of the font
|
|
// definition file to be added. The file will be loaded from the font directory
|
|
// specified in the call to New() or SetFontLocation().
|
|
func (f *Fpdf) AddUTF8Font(familyStr, styleStr, fileStr string) {
|
|
f.addFont(fontFamilyEscape(familyStr), styleStr, fileStr, true)
|
|
}
|
|
|
|
func (f *Fpdf) addFont(familyStr, styleStr, fileStr string, isUTF8 bool) {
|
|
if fileStr == "" {
|
|
if isUTF8 {
|
|
fileStr = strings.Replace(familyStr, " ", "", -1) + strings.ToLower(styleStr) + ".ttf"
|
|
} else {
|
|
fileStr = strings.Replace(familyStr, " ", "", -1) + strings.ToLower(styleStr) + ".json"
|
|
}
|
|
}
|
|
if isUTF8 {
|
|
fontKey := getFontKey(familyStr, styleStr)
|
|
_, ok := f.fonts[fontKey]
|
|
if ok {
|
|
return
|
|
}
|
|
var ttfStat os.FileInfo
|
|
var err error
|
|
fileStr = path.Join(f.fontpath, fileStr)
|
|
ttfStat, err = os.Stat(fileStr)
|
|
if err != nil {
|
|
f.SetError(err)
|
|
return
|
|
}
|
|
originalSize := ttfStat.Size()
|
|
Type := "UTF8"
|
|
var utf8Bytes []byte
|
|
utf8Bytes, err = ioutil.ReadFile(fileStr)
|
|
if err != nil {
|
|
f.SetError(err)
|
|
return
|
|
}
|
|
reader := fileReader{readerPosition: 0, array: utf8Bytes}
|
|
utf8File := newUTF8Font(&reader)
|
|
err = utf8File.parseFile()
|
|
if err != nil {
|
|
f.SetError(err)
|
|
return
|
|
}
|
|
|
|
desc := FontDescType{
|
|
Ascent: int(utf8File.Ascent),
|
|
Descent: int(utf8File.Descent),
|
|
CapHeight: utf8File.CapHeight,
|
|
Flags: utf8File.Flags,
|
|
FontBBox: utf8File.Bbox,
|
|
ItalicAngle: utf8File.ItalicAngle,
|
|
StemV: utf8File.StemV,
|
|
MissingWidth: round(utf8File.DefaultWidth),
|
|
}
|
|
|
|
var sbarr map[int]int
|
|
if f.aliasNbPagesStr == "" {
|
|
sbarr = makeSubsetRange(57)
|
|
} else {
|
|
sbarr = makeSubsetRange(32)
|
|
}
|
|
def := fontDefType{
|
|
Tp: Type,
|
|
Name: fontKey,
|
|
Desc: desc,
|
|
Up: int(round(utf8File.UnderlinePosition)),
|
|
Ut: round(utf8File.UnderlineThickness),
|
|
Cw: utf8File.CharWidths,
|
|
usedRunes: sbarr,
|
|
File: fileStr,
|
|
utf8File: utf8File,
|
|
}
|
|
def.i, _ = generateFontID(def)
|
|
f.fonts[fontKey] = def
|
|
f.fontFiles[fontKey] = fontFileType{
|
|
length1: originalSize,
|
|
fontType: "UTF8",
|
|
}
|
|
f.fontFiles[fileStr] = fontFileType{
|
|
fontType: "UTF8",
|
|
}
|
|
} else {
|
|
if f.fontLoader != nil {
|
|
reader, err := f.fontLoader.Open(fileStr)
|
|
if err == nil {
|
|
f.AddFontFromReader(familyStr, styleStr, reader)
|
|
if closer, ok := reader.(io.Closer); ok {
|
|
closer.Close()
|
|
}
|
|
return
|
|
}
|
|
}
|
|
|
|
fileStr = path.Join(f.fontpath, fileStr)
|
|
file, err := os.Open(fileStr)
|
|
if err != nil {
|
|
f.err = err
|
|
return
|
|
}
|
|
defer file.Close()
|
|
|
|
f.AddFontFromReader(familyStr, styleStr, file)
|
|
}
|
|
}
|
|
|
|
func makeSubsetRange(end int) map[int]int {
|
|
answer := make(map[int]int)
|
|
for i := 0; i < end; i++ {
|
|
answer[i] = 0
|
|
}
|
|
return answer
|
|
}
|
|
|
|
// AddFontFromBytes imports a TrueType, OpenType or Type1 font from static
|
|
// bytes within the executable and makes it available for use in the generated
|
|
// document.
|
|
//
|
|
// family specifies the font family. The name can be chosen arbitrarily. If it
|
|
// is a standard family name, it will override the corresponding font. This
|
|
// string is used to subsequently set the font with the SetFont method.
|
|
//
|
|
// style specifies the font style. Acceptable values are (case insensitive) the
|
|
// empty string for regular style, "B" for bold, "I" for italic, or "BI" or
|
|
// "IB" for bold and italic combined.
|
|
//
|
|
// jsonFileBytes contain all bytes of JSON file.
|
|
//
|
|
// zFileBytes contain all bytes of Z file.
|
|
func (f *Fpdf) AddFontFromBytes(familyStr, styleStr string, jsonFileBytes, zFileBytes []byte) {
|
|
f.addFontFromBytes(fontFamilyEscape(familyStr), styleStr, jsonFileBytes, zFileBytes, nil)
|
|
}
|
|
|
|
// AddUTF8FontFromBytes imports a TrueType font with utf-8 symbols from static
|
|
// bytes within the executable and makes it available for use in the generated
|
|
// document.
|
|
//
|
|
// family specifies the font family. The name can be chosen arbitrarily. If it
|
|
// is a standard family name, it will override the corresponding font. This
|
|
// string is used to subsequently set the font with the SetFont method.
|
|
//
|
|
// style specifies the font style. Acceptable values are (case insensitive) the
|
|
// empty string for regular style, "B" for bold, "I" for italic, or "BI" or
|
|
// "IB" for bold and italic combined.
|
|
//
|
|
// jsonFileBytes contain all bytes of JSON file.
|
|
//
|
|
// zFileBytes contain all bytes of Z file.
|
|
func (f *Fpdf) AddUTF8FontFromBytes(familyStr, styleStr string, utf8Bytes []byte) {
|
|
f.addFontFromBytes(fontFamilyEscape(familyStr), styleStr, nil, nil, utf8Bytes)
|
|
}
|
|
|
|
func (f *Fpdf) addFontFromBytes(familyStr, styleStr string, jsonFileBytes, zFileBytes, utf8Bytes []byte) {
|
|
if f.err != nil {
|
|
return
|
|
}
|
|
|
|
// load font key
|
|
var ok bool
|
|
fontkey := getFontKey(familyStr, styleStr)
|
|
_, ok = f.fonts[fontkey]
|
|
|
|
if ok {
|
|
return
|
|
}
|
|
|
|
if utf8Bytes != nil {
|
|
|
|
// if styleStr == "IB" {
|
|
// styleStr = "BI"
|
|
// }
|
|
|
|
Type := "UTF8"
|
|
reader := fileReader{readerPosition: 0, array: utf8Bytes}
|
|
|
|
utf8File := newUTF8Font(&reader)
|
|
|
|
err := utf8File.parseFile()
|
|
if err != nil {
|
|
fmt.Printf("get metrics Error: %e\n", err)
|
|
return
|
|
}
|
|
desc := FontDescType{
|
|
Ascent: int(utf8File.Ascent),
|
|
Descent: int(utf8File.Descent),
|
|
CapHeight: utf8File.CapHeight,
|
|
Flags: utf8File.Flags,
|
|
FontBBox: utf8File.Bbox,
|
|
ItalicAngle: utf8File.ItalicAngle,
|
|
StemV: utf8File.StemV,
|
|
MissingWidth: round(utf8File.DefaultWidth),
|
|
}
|
|
|
|
var sbarr map[int]int
|
|
if f.aliasNbPagesStr == "" {
|
|
sbarr = makeSubsetRange(57)
|
|
} else {
|
|
sbarr = makeSubsetRange(32)
|
|
}
|
|
def := fontDefType{
|
|
Tp: Type,
|
|
Name: fontkey,
|
|
Desc: desc,
|
|
Up: int(round(utf8File.UnderlinePosition)),
|
|
Ut: round(utf8File.UnderlineThickness),
|
|
Cw: utf8File.CharWidths,
|
|
utf8File: utf8File,
|
|
usedRunes: sbarr,
|
|
}
|
|
def.i, _ = generateFontID(def)
|
|
f.fonts[fontkey] = def
|
|
} else {
|
|
// load font definitions
|
|
var info fontDefType
|
|
err := json.Unmarshal(jsonFileBytes, &info)
|
|
|
|
if err != nil {
|
|
f.err = err
|
|
}
|
|
|
|
if f.err != nil {
|
|
return
|
|
}
|
|
|
|
if info.i, err = generateFontID(info); err != nil {
|
|
f.err = err
|
|
return
|
|
}
|
|
|
|
// search existing encodings
|
|
if len(info.Diff) > 0 {
|
|
n := -1
|
|
|
|
for j, str := range f.diffs {
|
|
if str == info.Diff {
|
|
n = j + 1
|
|
break
|
|
}
|
|
}
|
|
|
|
if n < 0 {
|
|
f.diffs = append(f.diffs, info.Diff)
|
|
n = len(f.diffs)
|
|
}
|
|
|
|
info.DiffN = n
|
|
}
|
|
|
|
// embed font
|
|
if len(info.File) > 0 {
|
|
if info.Tp == "TrueType" {
|
|
f.fontFiles[info.File] = fontFileType{
|
|
length1: int64(info.OriginalSize),
|
|
embedded: true,
|
|
content: zFileBytes,
|
|
}
|
|
} else {
|
|
f.fontFiles[info.File] = fontFileType{
|
|
length1: int64(info.Size1),
|
|
length2: int64(info.Size2),
|
|
embedded: true,
|
|
content: zFileBytes,
|
|
}
|
|
}
|
|
}
|
|
|
|
f.fonts[fontkey] = info
|
|
}
|
|
}
|
|
|
|
// getFontKey is used by AddFontFromReader and GetFontDesc
|
|
func getFontKey(familyStr, styleStr string) string {
|
|
familyStr = strings.ToLower(familyStr)
|
|
styleStr = strings.ToUpper(styleStr)
|
|
if styleStr == "IB" {
|
|
styleStr = "BI"
|
|
}
|
|
return familyStr + styleStr
|
|
}
|
|
|
|
// AddFontFromReader imports a TrueType, OpenType or Type1 font and makes it
|
|
// available using a reader that satisifies the io.Reader interface. See
|
|
// AddFont for details about familyStr and styleStr.
|
|
func (f *Fpdf) AddFontFromReader(familyStr, styleStr string, r io.Reader) {
|
|
if f.err != nil {
|
|
return
|
|
}
|
|
// dbg("Adding family [%s], style [%s]", familyStr, styleStr)
|
|
familyStr = fontFamilyEscape(familyStr)
|
|
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(fontFamilyEscape(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), "S" (strike-out)
|
|
// 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")
|
|
familyStr = fontFamilyEscape(familyStr)
|
|
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)
|
|
}
|
|
f.strikeout = strings.Contains(styleStr, "S")
|
|
if f.strikeout {
|
|
styleStr = strings.Replace(styleStr, "S", "", -1)
|
|
}
|
|
if styleStr == "IB" {
|
|
styleStr = "BI"
|
|
}
|
|
if size == 0.0 {
|
|
size = f.fontSizePt
|
|
}
|
|
|
|
// Test if font is already loaded
|
|
fontKey := familyStr + styleStr
|
|
_, ok = f.fonts[fontKey]
|
|
if !ok {
|
|
// Test if one of the core fonts
|
|
if familyStr == "arial" {
|
|
familyStr = "helvetica"
|
|
}
|
|
_, ok = f.coreFonts[familyStr]
|
|
if ok {
|
|
if familyStr == "symbol" {
|
|
familyStr = "zapfdingbats"
|
|
}
|
|
if familyStr == "zapfdingbats" {
|
|
styleStr = ""
|
|
}
|
|
fontKey = familyStr + styleStr
|
|
_, ok = f.fonts[fontKey]
|
|
if !ok {
|
|
rdr := f.coreFontReader(familyStr, styleStr)
|
|
if f.err == nil {
|
|
f.AddFontFromReader(familyStr, styleStr, rdr)
|
|
}
|
|
if f.err != nil {
|
|
return
|
|
}
|
|
}
|
|
} else {
|
|
f.err = fmt.Errorf("undefined font: %s %s", familyStr, styleStr)
|
|
return
|
|
}
|
|
}
|
|
// Select it
|
|
f.fontFamily = familyStr
|
|
f.fontStyle = styleStr
|
|
f.fontSizePt = size
|
|
f.fontSize = size / f.k
|
|
f.currentFont = f.fonts[fontKey]
|
|
if f.currentFont.Tp == "UTF8" {
|
|
f.isCurrentUTF8 = true
|
|
} else {
|
|
f.isCurrentUTF8 = false
|
|
}
|
|
if f.page > 0 {
|
|
f.outf("BT /F%s %.2f Tf ET", f.currentFont.i, f.fontSizePt)
|
|
}
|
|
return
|
|
}
|
|
|
|
// SetFontStyle sets the style of the current font. See also SetFont()
|
|
func (f *Fpdf) SetFontStyle(styleStr string) {
|
|
f.SetFont(f.fontFamily, styleStr, f.fontSizePt)
|
|
}
|
|
|
|
// SetFontSize defines the size of the current font. Size is specified in
|
|
// points (1/ 72 inch). See also SetFontUnitSize().
|
|
func (f *Fpdf) SetFontSize(size float64) {
|
|
f.fontSizePt = size
|
|
f.fontSize = size / f.k
|
|
if f.page > 0 {
|
|
f.outf("BT /F%s %.2f Tf ET", f.currentFont.i, f.fontSizePt)
|
|
}
|
|
}
|
|
|
|
// SetFontUnitSize defines the size of the current font. Size is specified in
|
|
// the unit of measure specified in New(). See also SetFontSize().
|
|
func (f *Fpdf) SetFontUnitSize(size float64) {
|
|
f.fontSizePt = size * f.k
|
|
f.fontSize = size
|
|
if f.page > 0 {
|
|
f.outf("BT /F%s %.2f Tf ET", f.currentFont.i, f.fontSizePt)
|
|
}
|
|
}
|
|
|
|
// GetFontSize returns the size of the current font in points followed by the
|
|
// size in the unit of measure specified in New(). The second value can be used
|
|
// as a line height value in drawing operations.
|
|
func (f *Fpdf) GetFontSize() (ptSize, unitSize float64) {
|
|
return f.fontSizePt, f.fontSize
|
|
}
|
|
|
|
// AddLink creates a new internal link and returns its identifier. An internal
|
|
// link is a clickable area which directs to another place within the document.
|
|
// The identifier can then be passed to Cell(), Write(), Image() or Link(). The
|
|
// destination is defined with SetLink().
|
|
func (f *Fpdf) AddLink() int {
|
|
f.links = append(f.links, intLinkType{})
|
|
return len(f.links) - 1
|
|
}
|
|
|
|
// SetLink defines the page and position a link points to. See AddLink().
|
|
func (f *Fpdf) SetLink(link int, y float64, page int) {
|
|
if y == -1 {
|
|
y = f.y
|
|
}
|
|
if page == -1 {
|
|
page = f.page
|
|
}
|
|
f.links[link] = intLinkType{page, y}
|
|
}
|
|
|
|
// newLink adds a new clickable link on current page
|
|
func (f *Fpdf) newLink(x, y, w, h float64, link int, linkStr string) {
|
|
// linkList, ok := f.pageLinks[f.page]
|
|
// if !ok {
|
|
// linkList = make([]linkType, 0, 8)
|
|
// f.pageLinks[f.page] = linkList
|
|
// }
|
|
f.pageLinks[f.page] = append(f.pageLinks[f.page],
|
|
linkType{x * f.k, f.hPt - y*f.k, w * f.k, h * f.k, link, linkStr})
|
|
}
|
|
|
|
// Link puts a link on a rectangular area of the page. Text or image links are
|
|
// generally put via Cell(), Write() or Image(), but this method can be useful
|
|
// for instance to define a clickable area inside an image. link is the value
|
|
// returned by AddLink().
|
|
func (f *Fpdf) Link(x, y, w, h float64, link int) {
|
|
f.newLink(x, y, w, h, link, "")
|
|
}
|
|
|
|
// LinkString puts a link on a rectangular area of the page. Text or image
|
|
// links are generally put via Cell(), Write() or Image(), but this method can
|
|
// be useful for instance to define a clickable area inside an image. linkStr
|
|
// is the target URL.
|
|
func (f *Fpdf) LinkString(x, y, w, h float64, linkStr string) {
|
|
f.newLink(x, y, w, h, 0, linkStr)
|
|
}
|
|
|
|
// Bookmark sets a bookmark that will be displayed in a sidebar outline. txtStr
|
|
// is the title of the bookmark. level specifies the level of the bookmark in
|
|
// the outline; 0 is the top level, 1 is just below, and so on. y specifies the
|
|
// vertical position of the bookmark destination in the current page; -1
|
|
// indicates the current position.
|
|
func (f *Fpdf) Bookmark(txtStr string, level int, y float64) {
|
|
if y == -1 {
|
|
y = f.y
|
|
}
|
|
if f.isCurrentUTF8 {
|
|
txtStr = utf8toutf16(txtStr)
|
|
}
|
|
f.outlines = append(f.outlines, outlineType{text: txtStr, level: level, y: y, p: f.PageNo(), prev: -1, last: -1, next: -1, first: -1})
|
|
}
|
|
|
|
// Text prints a character string. The origin (x, y) is on the left of the
|
|
// first character at the baseline. This method permits a string to be placed
|
|
// precisely on the page, but it is usually easier to use Cell(), MultiCell()
|
|
// or Write() which are the standard methods to print text.
|
|
func (f *Fpdf) Text(x, y float64, txtStr string) {
|
|
var txt2 string
|
|
if f.isCurrentUTF8 {
|
|
if f.isRTL {
|
|
txtStr = reverseText(txtStr)
|
|
x -= f.GetStringWidth(txtStr)
|
|
}
|
|
txt2 = f.escape(utf8toutf16(txtStr, false))
|
|
for _, uni := range []rune(txtStr) {
|
|
f.currentFont.usedRunes[int(uni)] = int(uni)
|
|
}
|
|
} else {
|
|
txt2 = f.escape(txtStr)
|
|
}
|
|
s := sprintf("BT %.2f %.2f Td (%s) Tj ET", x*f.k, (f.h-y)*f.k, txt2)
|
|
if f.underline && txtStr != "" {
|
|
s += " " + f.dounderline(x, y, txtStr)
|
|
}
|
|
if f.strikeout && txtStr != "" {
|
|
s += " " + f.dostrikeout(x, y, txtStr)
|
|
}
|
|
if f.colorFlag {
|
|
s = sprintf("q %s %s Q", f.color.text.str, s)
|
|
}
|
|
f.out(s)
|
|
}
|
|
|
|
// SetWordSpacing sets spacing between words of following text. See the
|
|
// WriteAligned() example for a demonstration of its use.
|
|
func (f *Fpdf) SetWordSpacing(space float64) {
|
|
f.out(sprintf("%.5f Tw", space*f.k))
|
|
}
|
|
|
|
// SetTextRenderingMode sets the rendering mode of following text.
|
|
// The mode can be as follows:
|
|
// 0: Fill text
|
|
// 1: Stroke text
|
|
// 2: Fill, then stroke text
|
|
// 3: Neither fill nor stroke text (invisible)
|
|
// 4: Fill text and add to path for clipping
|
|
// 5: Stroke text and add to path for clipping
|
|
// 6: Fills then stroke text and add to path for clipping
|
|
// 7: Add text to path for clipping
|
|
// This method is demonstrated in the SetTextRenderingMode example.
|
|
func (f *Fpdf) SetTextRenderingMode(mode int) {
|
|
if mode >= 0 && mode <= 7 {
|
|
f.out(sprintf("%d Tr", mode))
|
|
}
|
|
}
|
|
|
|
// SetAcceptPageBreakFunc allows the application to control where page breaks
|
|
// occur.
|
|
//
|
|
// fnc is an application function (typically a closure) that is called by the
|
|
// library whenever a page break condition is met. The break is issued if true
|
|
// is returned. The default implementation returns a value according to the
|
|
// mode selected by SetAutoPageBreak. The function provided should not be
|
|
// called by the application.
|
|
//
|
|
// See the example for SetLeftMargin() to see how this function can be used to
|
|
// manage multiple columns.
|
|
func (f *Fpdf) SetAcceptPageBreakFunc(fnc func() bool) {
|
|
f.acceptPageBreak = fnc
|
|
}
|
|
|
|
// CellFormat prints a rectangular cell with optional borders, background color
|
|
// and character string. The upper-left corner of the cell corresponds to the
|
|
// current position. The text can be aligned or centered. After the call, the
|
|
// current position moves to the right or to the next line. It is possible to
|
|
// put a link on the text.
|
|
//
|
|
// An error will be returned if a call to SetFont() has not already taken
|
|
// place before this method is called.
|
|
//
|
|
// If automatic page breaking is enabled and the cell goes beyond the limit, a
|
|
// page break is done before outputting.
|
|
//
|
|
// w and h specify the width and height of the cell. If w is 0, the cell
|
|
// extends up to the right margin. Specifying 0 for h will result in no output,
|
|
// but the current position will be advanced by w.
|
|
//
|
|
// txtStr specifies the text to display.
|
|
//
|
|
// borderStr specifies how the cell border will be drawn. An empty string
|
|
// indicates no border, "1" indicates a full border, and one or more of "L",
|
|
// "T", "R" and "B" indicate the left, top, right and bottom sides of the
|
|
// border.
|
|
//
|
|
// ln indicates where the current position should go after the call. Possible
|
|
// values are 0 (to the right), 1 (to the beginning of the next line), and 2
|
|
// (below). Putting 1 is equivalent to putting 0 and calling Ln() just after.
|
|
//
|
|
// alignStr specifies how the text is to be positioned within the cell.
|
|
// Horizontal alignment is controlled by including "L", "C" or "R" (left,
|
|
// center, right) in alignStr. Vertical alignment is controlled by including
|
|
// "T", "M", "B" or "A" (top, middle, bottom, baseline) in alignStr. The default
|
|
// alignment is left middle.
|
|
//
|
|
// fill is true to paint the cell background or false to leave it transparent.
|
|
//
|
|
// link is the identifier returned by AddLink() or 0 for no internal link.
|
|
//
|
|
// linkStr is a target URL or empty for no external link. A non--zero value for
|
|
// link takes precedence over linkStr.
|
|
func (f *Fpdf) CellFormat(w, h float64, txtStr, borderStr string, ln int,
|
|
alignStr string, fill bool, link int, linkStr string) {
|
|
// dbg("CellFormat. h = %.2f, borderStr = %s", h, borderStr)
|
|
if f.err != nil {
|
|
return
|
|
}
|
|
|
|
if f.currentFont.Name == "" {
|
|
f.err = fmt.Errorf("font has not been set; unable to render text")
|
|
return
|
|
}
|
|
|
|
borderStr = strings.ToUpper(borderStr)
|
|
k := f.k
|
|
if f.y+h > f.pageBreakTrigger && !f.inHeader && !f.inFooter && f.acceptPageBreak() {
|
|
// Automatic page break
|
|
x := f.x
|
|
ws := f.ws
|
|
// dbg("auto page break, x %.2f, ws %.2f", x, ws)
|
|
if ws > 0 {
|
|
f.ws = 0
|
|
f.out("0 Tw")
|
|
}
|
|
f.AddPageFormat(f.curOrientation, f.curPageSize)
|
|
if f.err != nil {
|
|
return
|
|
}
|
|
f.x = x
|
|
if ws > 0 {
|
|
f.ws = ws
|
|
f.outf("%.3f Tw", ws*k)
|
|
}
|
|
}
|
|
if w == 0 {
|
|
w = f.w - f.rMargin - f.x
|
|
}
|
|
var s fmtBuffer
|
|
if fill || borderStr == "1" {
|
|
var op string
|
|
if fill {
|
|
if borderStr == "1" {
|
|
op = "B"
|
|
// dbg("border is '1', fill")
|
|
} else {
|
|
op = "f"
|
|
// dbg("border is empty, fill")
|
|
}
|
|
} else {
|
|
// dbg("border is '1', no fill")
|
|
op = "S"
|
|
}
|
|
/// dbg("(CellFormat) f.x %.2f f.k %.2f", f.x, f.k)
|
|
s.printf("%.2f %.2f %.2f %.2f re %s ", f.x*k, (f.h-f.y)*k, w*k, -h*k, op)
|
|
}
|
|
if len(borderStr) > 0 && borderStr != "1" {
|
|
// fmt.Printf("border is '%s', no fill\n", borderStr)
|
|
x := f.x
|
|
y := f.y
|
|
left := x * k
|
|
top := (f.h - y) * k
|
|
right := (x + w) * k
|
|
bottom := (f.h - (y + h)) * k
|
|
if strings.Contains(borderStr, "L") {
|
|
s.printf("%.2f %.2f m %.2f %.2f l S ", left, top, left, bottom)
|
|
}
|
|
if strings.Contains(borderStr, "T") {
|
|
s.printf("%.2f %.2f m %.2f %.2f l S ", left, top, right, top)
|
|
}
|
|
if strings.Contains(borderStr, "R") {
|
|
s.printf("%.2f %.2f m %.2f %.2f l S ", right, top, right, bottom)
|
|
}
|
|
if strings.Contains(borderStr, "B") {
|
|
s.printf("%.2f %.2f m %.2f %.2f l S ", left, bottom, right, bottom)
|
|
}
|
|
}
|
|
if len(txtStr) > 0 {
|
|
var dx, dy float64
|
|
// Horizontal alignment
|
|
switch {
|
|
case strings.Contains(alignStr, "R"):
|
|
dx = w - f.cMargin - f.GetStringWidth(txtStr)
|
|
case strings.Contains(alignStr, "C"):
|
|
dx = (w - f.GetStringWidth(txtStr)) / 2
|
|
default:
|
|
dx = f.cMargin
|
|
}
|
|
|
|
// Vertical alignment
|
|
switch {
|
|
case strings.Contains(alignStr, "T"):
|
|
dy = (f.fontSize - h) / 2.0
|
|
case strings.Contains(alignStr, "B"):
|
|
dy = (h - f.fontSize) / 2.0
|
|
case strings.Contains(alignStr, "A"):
|
|
var descent float64
|
|
d := f.currentFont.Desc
|
|
if d.Descent == 0 {
|
|
// not defined (standard font?), use average of 19%
|
|
descent = -0.19 * f.fontSize
|
|
} else {
|
|
descent = float64(d.Descent) * f.fontSize / float64(d.Ascent-d.Descent)
|
|
}
|
|
dy = (h-f.fontSize)/2.0 - descent
|
|
default:
|
|
dy = 0
|
|
}
|
|
if f.colorFlag {
|
|
s.printf("q %s ", f.color.text.str)
|
|
}
|
|
//If multibyte, Tw has no effect - do word spacing using an adjustment before each space
|
|
if (f.ws != 0 || alignStr == "J") && f.isCurrentUTF8 { // && f.ws != 0
|
|
if f.isRTL {
|
|
txtStr = reverseText(txtStr)
|
|
}
|
|
wmax := int(math.Ceil((w - 2*f.cMargin) * 1000 / f.fontSize))
|
|
for _, uni := range []rune(txtStr) {
|
|
f.currentFont.usedRunes[int(uni)] = int(uni)
|
|
}
|
|
space := f.escape(utf8toutf16(" ", false))
|
|
strSize := f.GetStringSymbolWidth(txtStr)
|
|
s.printf("BT 0 Tw %.2f %.2f Td [", (f.x+dx)*k, (f.h-(f.y+.5*h+.3*f.fontSize))*k)
|
|
t := strings.Split(txtStr, " ")
|
|
shift := float64((wmax - strSize)) / float64(len(t)-1)
|
|
numt := len(t)
|
|
for i := 0; i < numt; i++ {
|
|
tx := t[i]
|
|
tx = "(" + f.escape(utf8toutf16(tx, false)) + ")"
|
|
s.printf("%s ", tx)
|
|
if (i + 1) < numt {
|
|
s.printf("%.3f(%s) ", -shift, space)
|
|
}
|
|
}
|
|
s.printf("] TJ ET")
|
|
} else {
|
|
var txt2 string
|
|
if f.isCurrentUTF8 {
|
|
if f.isRTL {
|
|
txtStr = reverseText(txtStr)
|
|
}
|
|
txt2 = f.escape(utf8toutf16(txtStr, false))
|
|
for _, uni := range []rune(txtStr) {
|
|
f.currentFont.usedRunes[int(uni)] = int(uni)
|
|
}
|
|
} else {
|
|
|
|
txt2 = strings.Replace(txtStr, "\\", "\\\\", -1)
|
|
txt2 = strings.Replace(txt2, "(", "\\(", -1)
|
|
txt2 = strings.Replace(txt2, ")", "\\)", -1)
|
|
}
|
|
bt := (f.x + dx) * k
|
|
td := (f.h - (f.y + dy + .5*h + .3*f.fontSize)) * k
|
|
s.printf("BT %.2f %.2f Td (%s)Tj ET", bt, td, txt2)
|
|
//BT %.2F %.2F Td (%s) Tj ET',(f.x+dx)*k,(f.h-(f.y+.5*h+.3*f.FontSize))*k,txt2);
|
|
}
|
|
|
|
if f.underline {
|
|
s.printf(" %s", f.dounderline(f.x+dx, f.y+dy+.5*h+.3*f.fontSize, txtStr))
|
|
}
|
|
if f.strikeout {
|
|
s.printf(" %s", f.dostrikeout(f.x+dx, f.y+dy+.5*h+.3*f.fontSize, txtStr))
|
|
}
|
|
if f.colorFlag {
|
|
s.printf(" Q")
|
|
}
|
|
if link > 0 || len(linkStr) > 0 {
|
|
f.newLink(f.x+dx, f.y+dy+.5*h-.5*f.fontSize, f.GetStringWidth(txtStr), f.fontSize, link, linkStr)
|
|
}
|
|
}
|
|
str := s.String()
|
|
if len(str) > 0 {
|
|
f.out(str)
|
|
}
|
|
f.lasth = h
|
|
if ln > 0 {
|
|
// Go to next line
|
|
f.y += h
|
|
if ln == 1 {
|
|
f.x = f.lMargin
|
|
}
|
|
} else {
|
|
f.x += w
|
|
}
|
|
return
|
|
}
|
|
|
|
// Revert string to use in RTL languages
|
|
func reverseText(text string) string {
|
|
oldText := []rune(text)
|
|
newText := make([]rune, len(oldText))
|
|
length := len(oldText) - 1
|
|
for i, r := range oldText {
|
|
newText[length-i] = r
|
|
}
|
|
return string(newText)
|
|
}
|
|
|
|
// Cell is a simpler version of CellFormat with no fill, border, links or
|
|
// special alignment. The Cell_strikeout() example demonstrates this method.
|
|
func (f *Fpdf) Cell(w, h float64, txtStr string) {
|
|
f.CellFormat(w, h, txtStr, "", 0, "L", false, 0, "")
|
|
}
|
|
|
|
// Cellf is a simpler printf-style version of CellFormat with no fill, border,
|
|
// links or special alignment. See documentation for the fmt package for
|
|
// details on fmtStr and args.
|
|
func (f *Fpdf) Cellf(w, h float64, fmtStr string, args ...interface{}) {
|
|
f.CellFormat(w, h, sprintf(fmtStr, args...), "", 0, "L", false, 0, "")
|
|
}
|
|
|
|
// SplitLines splits text into several lines using the current font. Each line
|
|
// has its length limited to a maximum width given by w. This function can be
|
|
// used to determine the total height of wrapped text for vertical placement
|
|
// purposes.
|
|
//
|
|
// This method is useful for codepage-based fonts only. For UTF-8 encoded text,
|
|
// use SplitText().
|
|
//
|
|
// You can use MultiCell if you want to print a text on several lines in a
|
|
// simple way.
|
|
func (f *Fpdf) SplitLines(txt []byte, w float64) [][]byte {
|
|
// Function contributed by Bruno Michel
|
|
lines := [][]byte{}
|
|
cw := f.currentFont.Cw
|
|
wmax := int(math.Ceil((w - 2*f.cMargin) * 1000 / f.fontSize))
|
|
s := bytes.Replace(txt, []byte("\r"), []byte{}, -1)
|
|
nb := len(s)
|
|
for nb > 0 && s[nb-1] == '\n' {
|
|
nb--
|
|
}
|
|
s = s[0:nb]
|
|
sep := -1
|
|
i := 0
|
|
j := 0
|
|
l := 0
|
|
for i < nb {
|
|
c := s[i]
|
|
l += cw[c]
|
|
if c == ' ' || c == '\t' || c == '\n' {
|
|
sep = i
|
|
}
|
|
if c == '\n' || l > wmax {
|
|
if sep == -1 {
|
|
if i == j {
|
|
i++
|
|
}
|
|
sep = i
|
|
} else {
|
|
i = sep + 1
|
|
}
|
|
lines = append(lines, s[j:sep])
|
|
sep = -1
|
|
j = i
|
|
l = 0
|
|
} else {
|
|
i++
|
|
}
|
|
}
|
|
if i != j {
|
|
lines = append(lines, s[j:i])
|
|
}
|
|
return lines
|
|
}
|
|
|
|
// MultiCell supports printing text with line breaks. They can be automatic (as
|
|
// soon as the text reaches the right border of the cell) or explicit (via the
|
|
// \n character). As many cells as necessary are output, one below the other.
|
|
//
|
|
// Text can be aligned, centered or justified. The cell block can be framed and
|
|
// the background painted. See CellFormat() for more details.
|
|
//
|
|
// The current position after calling MultiCell() is the beginning of the next
|
|
// line, equivalent to calling CellFormat with ln equal to 1.
|
|
//
|
|
// w is the width of the cells. A value of zero indicates cells that reach to
|
|
// the right margin.
|
|
//
|
|
// h indicates the line height of each cell in the unit of measure specified in New().
|
|
//
|
|
// Note: this method has a known bug that treats UTF-8 fonts differently than
|
|
// non-UTF-8 fonts. With UTF-8 fonts, all trailing newlines in txtStr are
|
|
// removed. With a non-UTF-8 font, if txtStr has one or more trailing newlines,
|
|
// only the last is removed. In the next major module version, the UTF-8 logic
|
|
// will be changed to match the non-UTF-8 logic. To prepare for that change,
|
|
// applications that use UTF-8 fonts and depend on having all trailing newlines
|
|
// removed should call strings.TrimRight(txtStr, "\r\n") before calling this
|
|
// method.
|
|
func (f *Fpdf) MultiCell(w, h float64, txtStr, borderStr, alignStr string, fill bool) {
|
|
if f.err != nil {
|
|
return
|
|
}
|
|
// dbg("MultiCell")
|
|
if alignStr == "" {
|
|
alignStr = "J"
|
|
}
|
|
cw := f.currentFont.Cw
|
|
if w == 0 {
|
|
w = f.w - f.rMargin - f.x
|
|
}
|
|
wmax := int(math.Ceil((w - 2*f.cMargin) * 1000 / f.fontSize))
|
|
s := strings.Replace(txtStr, "\r", "", -1)
|
|
srune := []rune(s)
|
|
|
|
// remove extra line breaks
|
|
var nb int
|
|
if f.isCurrentUTF8 {
|
|
nb = len(srune)
|
|
for nb > 0 && srune[nb-1] == '\n' {
|
|
nb--
|
|
}
|
|
srune = srune[0:nb]
|
|
} else {
|
|
nb = len(s)
|
|
bytes2 := []byte(s)
|
|
|
|
// for nb > 0 && bytes2[nb-1] == '\n' {
|
|
|
|
// Prior to August 2019, if s ended with a newline, this code stripped it.
|
|
// After that date, to be compatible with the UTF-8 code above, *all*
|
|
// trailing newlines were removed. Because this regression caused at least
|
|
// one application to break (see issue #333), the original behavior has been
|
|
// reinstated with a caveat included in the documentation.
|
|
if nb > 0 && bytes2[nb-1] == '\n' {
|
|
nb--
|
|
}
|
|
s = s[0:nb]
|
|
}
|
|
// dbg("[%s]\n", s)
|
|
var b, b2 string
|
|
b = "0"
|
|
if len(borderStr) > 0 {
|
|
if borderStr == "1" {
|
|
borderStr = "LTRB"
|
|
b = "LRT"
|
|
b2 = "LR"
|
|
} else {
|
|
b2 = ""
|
|
if strings.Contains(borderStr, "L") {
|
|
b2 += "L"
|
|
}
|
|
if strings.Contains(borderStr, "R") {
|
|
b2 += "R"
|
|
}
|
|
if strings.Contains(borderStr, "T") {
|
|
b = b2 + "T"
|
|
} else {
|
|
b = b2
|
|
}
|
|
}
|
|
}
|
|
sep := -1
|
|
i := 0
|
|
j := 0
|
|
l := 0
|
|
ls := 0
|
|
ns := 0
|
|
nl := 1
|
|
for i < nb {
|
|
// Get next character
|
|
var c rune
|
|
if f.isCurrentUTF8 {
|
|
c = srune[i]
|
|
} else {
|
|
c = rune(s[i])
|
|
}
|
|
if c == '\n' {
|
|
// Explicit line break
|
|
if f.ws > 0 {
|
|
f.ws = 0
|
|
f.out("0 Tw")
|
|
}
|
|
|
|
if f.isCurrentUTF8 {
|
|
newAlignStr := alignStr
|
|
if newAlignStr == "J" {
|
|
if f.isRTL {
|
|
newAlignStr = "R"
|
|
} else {
|
|
newAlignStr = "L"
|
|
}
|
|
}
|
|
f.CellFormat(w, h, string(srune[j:i]), b, 2, newAlignStr, fill, 0, "")
|
|
} else {
|
|
f.CellFormat(w, h, s[j:i], b, 2, alignStr, fill, 0, "")
|
|
}
|
|
i++
|
|
sep = -1
|