codeberg.org/go-pdf/fpdf@v0.11.1/fpdf.go (about)

     1  // Copyright ©2023 The go-pdf Authors. All rights reserved.
     2  // Use of this source code is governed by a MIT-style
     3  // license that can be found in the LICENSE file.
     4  
     5  /*
     6   * Copyright (c) 2013-2014 Kurt Jung (Gmail: kurt.w.jung)
     7   *
     8   * Permission to use, copy, modify, and distribute this software for any
     9   * purpose with or without fee is hereby granted, provided that the above
    10   * copyright notice and this permission notice appear in all copies.
    11   *
    12   * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
    13   * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
    14   * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
    15   * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
    16   * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
    17   * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
    18   * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
    19   */
    20  
    21  package fpdf
    22  
    23  // Version: 1.7
    24  // Date:    2011-06-18
    25  // Author:  Olivier PLATHEY
    26  // Port to Go: Kurt Jung, 2013-07-15
    27  
    28  import (
    29  	"bytes"
    30  	"encoding/json"
    31  	"fmt"
    32  	"image"
    33  	"image/color"
    34  	"image/gif"
    35  	"image/jpeg"
    36  	"image/png"
    37  	"io"
    38  	"math"
    39  	"os"
    40  	"path"
    41  	"sort"
    42  	"strconv"
    43  	"strings"
    44  	"time"
    45  )
    46  
    47  var gl struct {
    48  	catalogSort  bool
    49  	noCompress   bool // Initial zero value indicates compression
    50  	creationDate time.Time
    51  	modDate      time.Time
    52  }
    53  
    54  type fmtBuffer struct {
    55  	bytes.Buffer
    56  }
    57  
    58  func (b *fmtBuffer) printf(fmtStr string, args ...interface{}) {
    59  	b.Buffer.WriteString(fmt.Sprintf(fmtStr, args...))
    60  }
    61  
    62  func fpdfNew(orientationStr, unitStr, sizeStr, fontDirStr string, size SizeType) (f *Fpdf) {
    63  	f = new(Fpdf)
    64  	if orientationStr == "" {
    65  		orientationStr = "p"
    66  	} else {
    67  		orientationStr = strings.ToLower(orientationStr)
    68  	}
    69  	if unitStr == "" {
    70  		unitStr = "mm"
    71  	}
    72  	if sizeStr == "" {
    73  		sizeStr = "A4"
    74  	}
    75  	if fontDirStr == "" {
    76  		fontDirStr = "."
    77  	}
    78  	f.page = 0
    79  	f.n = 2
    80  	f.pages = make([]*bytes.Buffer, 0, 8)
    81  	f.pages = append(f.pages, bytes.NewBufferString("")) // pages[0] is unused (1-based)
    82  	f.pageSizes = make(map[int]SizeType)
    83  	f.pageBoxes = make(map[int]map[string]PageBox)
    84  	f.defPageBoxes = make(map[string]PageBox)
    85  	f.state = 0
    86  	f.fonts = make(map[string]fontDefType)
    87  	f.fontFiles = make(map[string]fontFileType)
    88  	f.diffs = make([]string, 0, 8)
    89  	f.templates = make(map[string]Template)
    90  	f.templateObjects = make(map[string]int)
    91  	f.importedObjs = make(map[string][]byte)
    92  	f.importedObjPos = make(map[string]map[int]string)
    93  	f.importedTplObjs = make(map[string]string)
    94  	f.importedTplIDs = make(map[string]int)
    95  	f.images = make(map[string]*ImageInfoType)
    96  	f.pageLinks = make([][]linkType, 0, 8)
    97  	f.pageLinks = append(f.pageLinks, make([]linkType, 0)) // pageLinks[0] is unused (1-based)
    98  	f.links = make([]intLinkType, 0, 8)
    99  	f.links = append(f.links, intLinkType{}) // links[0] is unused (1-based)
   100  	f.pageAttachments = make([][]annotationAttach, 0, 8)
   101  	f.pageAttachments = append(f.pageAttachments, []annotationAttach{}) //
   102  	f.aliasMap = make(map[string]string)
   103  	f.inHeader = false
   104  	f.inFooter = false
   105  	f.lasth = 0
   106  	f.fontFamily = ""
   107  	f.fontStyle = ""
   108  	f.SetFontSize(12)
   109  	f.underline = false
   110  	f.strikeout = false
   111  	f.setDrawColor(0, 0, 0)
   112  	f.setFillColor(0, 0, 0)
   113  	f.setTextColor(0, 0, 0)
   114  	f.colorFlag = false
   115  	f.ws = 0
   116  	f.fontpath = fontDirStr
   117  	// Core fonts
   118  	f.coreFonts = map[string]bool{
   119  		"courier":      true,
   120  		"helvetica":    true,
   121  		"times":        true,
   122  		"symbol":       true,
   123  		"zapfdingbats": true,
   124  	}
   125  	// Scale factor
   126  	switch unitStr {
   127  	case "pt", "point":
   128  		f.k = 1.0
   129  	case "mm":
   130  		f.k = 72.0 / 25.4
   131  	case "cm":
   132  		f.k = 72.0 / 2.54
   133  	case "in", "inch":
   134  		f.k = 72.0
   135  	default:
   136  		f.err = fmt.Errorf("incorrect unit %s", unitStr)
   137  		return
   138  	}
   139  	f.unitStr = unitStr
   140  	// Page sizes
   141  	f.stdPageSizes = make(map[string]SizeType)
   142  	f.stdPageSizes["a3"] = SizeType{841.89, 1190.55}
   143  	f.stdPageSizes["a4"] = SizeType{595.28, 841.89}
   144  	f.stdPageSizes["a5"] = SizeType{420.94, 595.28}
   145  	f.stdPageSizes["a6"] = SizeType{297.64, 420.94}
   146  	f.stdPageSizes["a7"] = SizeType{209.76, 297.64}
   147  	f.stdPageSizes["a2"] = SizeType{1190.55, 1683.78}
   148  	f.stdPageSizes["a1"] = SizeType{1683.78, 2383.94}
   149  	f.stdPageSizes["letter"] = SizeType{612, 792}
   150  	f.stdPageSizes["legal"] = SizeType{612, 1008}
   151  	f.stdPageSizes["tabloid"] = SizeType{792, 1224}
   152  	if size.Wd > 0 && size.Ht > 0 {
   153  		f.defPageSize = size
   154  	} else {
   155  		f.defPageSize = f.getpagesizestr(sizeStr)
   156  		if f.err != nil {
   157  			return
   158  		}
   159  	}
   160  	f.curPageSize = f.defPageSize
   161  	// Page orientation
   162  	switch orientationStr {
   163  	case "p", "portrait":
   164  		f.defOrientation = "P"
   165  		f.w = f.defPageSize.Wd
   166  		f.h = f.defPageSize.Ht
   167  		// dbg("Assign h: %8.2f", f.h)
   168  	case "l", "landscape":
   169  		f.defOrientation = "L"
   170  		f.w = f.defPageSize.Ht
   171  		f.h = f.defPageSize.Wd
   172  	default:
   173  		f.err = fmt.Errorf("incorrect orientation: %s", orientationStr)
   174  		return
   175  	}
   176  	f.curOrientation = f.defOrientation
   177  	f.wPt = f.w * f.k
   178  	f.hPt = f.h * f.k
   179  	// Page margins (1 cm)
   180  	margin := 28.35 / f.k
   181  	f.SetMargins(margin, margin, margin)
   182  	// Interior cell margin (1 mm)
   183  	f.cMargin = margin / 10
   184  	// Line width (0.2 mm)
   185  	f.lineWidth = 0.567 / f.k
   186  	// 	Automatic page break
   187  	f.SetAutoPageBreak(true, 2*margin)
   188  	// Default display mode
   189  	f.SetDisplayMode("default", "default")
   190  	if f.err != nil {
   191  		return
   192  	}
   193  	f.acceptPageBreak = func() bool {
   194  		return f.autoPageBreak
   195  	}
   196  	// Enable compression
   197  	f.SetCompression(!gl.noCompress)
   198  	f.spotColorMap = make(map[string]spotColorType)
   199  	f.blendList = make([]blendModeType, 0, 8)
   200  	f.blendList = append(f.blendList, blendModeType{}) // blendList[0] is unused (1-based)
   201  	f.blendMap = make(map[string]int)
   202  	f.blendMode = "Normal"
   203  	f.alpha = 1
   204  	f.gradientList = make([]gradientType, 0, 8)
   205  	f.gradientList = append(f.gradientList, gradientType{}) // gradientList[0] is unused
   206  	// Set default PDF version number
   207  	f.pdfVersion = pdfVers1_3
   208  	f.SetProducer("FPDF "+cnFpdfVersion, true)
   209  	f.layerInit()
   210  	f.catalogSort = gl.catalogSort
   211  	f.creationDate = gl.creationDate
   212  	f.modDate = gl.modDate
   213  	f.userUnderlineThickness = 1
   214  
   215  	// create a large enough buffer for formatting float64s.
   216  	// math.MaxInt64  needs 19.
   217  	// math.MaxUint64 needs 20.
   218  	f.fmt.buf = make([]byte, 24)
   219  	return
   220  }
   221  
   222  // NewCustom returns a pointer to a new Fpdf instance. Its methods are
   223  // subsequently called to produce a single PDF document. NewCustom() is an
   224  // alternative to New() that provides additional customization. The PageSize()
   225  // example demonstrates this method.
   226  func NewCustom(init *InitType) (f *Fpdf) {
   227  	return fpdfNew(init.OrientationStr, init.UnitStr, init.SizeStr, init.FontDirStr, init.Size)
   228  }
   229  
   230  // New returns a pointer to a new Fpdf instance. Its methods are subsequently
   231  // called to produce a single PDF document.
   232  //
   233  // orientationStr specifies the default page orientation. For portrait mode,
   234  // specify "P" or "Portrait". For landscape mode, specify "L" or "Landscape".
   235  // An empty string will be replaced with "P".
   236  //
   237  // unitStr specifies the unit of length used in size parameters for elements
   238  // other than fonts, which are always measured in points. Specify "pt" for
   239  // point, "mm" for millimeter, "cm" for centimeter, or "in" for inch. An empty
   240  // string will be replaced with "mm".
   241  //
   242  // sizeStr specifies the page size. Acceptable values are "A1", "A2", "A3", "A4", "A5",
   243  // "A6", "A7", "Letter", "Legal", or "Tabloid". An empty string will be replaced with "A4".
   244  //
   245  // fontDirStr specifies the file system location in which font resources will
   246  // be found. An empty string is replaced with ".". This argument only needs to
   247  // reference an actual directory if a font other than one of the core
   248  // fonts is used. The core fonts are "courier", "helvetica" (also called
   249  // "arial"), "times", and "zapfdingbats" (also called "symbol").
   250  func New(orientationStr, unitStr, sizeStr, fontDirStr string) (f *Fpdf) {
   251  	return fpdfNew(orientationStr, unitStr, sizeStr, fontDirStr, SizeType{0, 0})
   252  }
   253  
   254  // Ok returns true if no processing errors have occurred.
   255  func (f *Fpdf) Ok() bool {
   256  	return f.err == nil
   257  }
   258  
   259  // Err returns true if a processing error has occurred.
   260  func (f *Fpdf) Err() bool {
   261  	return f.err != nil
   262  }
   263  
   264  // ClearError unsets the internal Fpdf error. This method should be used with
   265  // care, as an internal error condition usually indicates an unrecoverable
   266  // problem with the generation of a document. It is intended to deal with cases
   267  // in which an error is used to select an alternate form of the document.
   268  func (f *Fpdf) ClearError() {
   269  	f.err = nil
   270  }
   271  
   272  // SetErrorf sets the internal Fpdf error with formatted text to halt PDF
   273  // generation; this may facilitate error handling by application. If an error
   274  // condition is already set, this call is ignored.
   275  //
   276  // See the documentation for printing in the standard fmt package for details
   277  // about fmtStr and args.
   278  func (f *Fpdf) SetErrorf(fmtStr string, args ...interface{}) {
   279  	if f.err == nil {
   280  		f.err = fmt.Errorf(fmtStr, args...)
   281  	}
   282  }
   283  
   284  // String satisfies the fmt.Stringer interface and summarizes the Fpdf
   285  // instance.
   286  func (f *Fpdf) String() string {
   287  	return "Fpdf " + cnFpdfVersion
   288  }
   289  
   290  // SetError sets an error to halt PDF generation. This may facilitate error
   291  // handling by application. See also Ok(), Err() and Error().
   292  func (f *Fpdf) SetError(err error) {
   293  	if f.err == nil && err != nil {
   294  		f.err = err
   295  	}
   296  }
   297  
   298  // Error returns the internal Fpdf error; this will be nil if no error has occurred.
   299  func (f *Fpdf) Error() error {
   300  	return f.err
   301  }
   302  
   303  // GetPageSize returns the current page's width and height. This is the paper's
   304  // size. To compute the size of the area being used, subtract the margins (see
   305  // GetMargins()).
   306  func (f *Fpdf) GetPageSize() (width, height float64) {
   307  	width = f.w
   308  	height = f.h
   309  	return
   310  }
   311  
   312  // GetMargins returns the left, top, right, and bottom margins. The first three
   313  // are set with the SetMargins() method. The bottom margin is set with the
   314  // SetAutoPageBreak() method.
   315  func (f *Fpdf) GetMargins() (left, top, right, bottom float64) {
   316  	left = f.lMargin
   317  	top = f.tMargin
   318  	right = f.rMargin
   319  	bottom = f.bMargin
   320  	return
   321  }
   322  
   323  // SetMargins defines the left, top and right margins. By default, they equal 1
   324  // cm. Call this method to change them. If the value of the right margin is
   325  // less than zero, it is set to the same as the left margin.
   326  func (f *Fpdf) SetMargins(left, top, right float64) {
   327  	f.lMargin = left
   328  	f.tMargin = top
   329  	if right < 0 {
   330  		right = left
   331  	}
   332  	f.rMargin = right
   333  }
   334  
   335  // SetLeftMargin defines the left margin. The method can be called before
   336  // creating the first page. If the current abscissa gets out of page, it is
   337  // brought back to the margin.
   338  func (f *Fpdf) SetLeftMargin(margin float64) {
   339  	f.lMargin = margin
   340  	if f.page > 0 && f.x < margin {
   341  		f.x = margin
   342  	}
   343  }
   344  
   345  // GetCellMargin returns the cell margin. This is the amount of space before
   346  // and after the text within a cell that's left blank, and is in units passed
   347  // to New(). It defaults to 1mm.
   348  func (f *Fpdf) GetCellMargin() float64 {
   349  	return f.cMargin
   350  }
   351  
   352  // SetCellMargin sets the cell margin. This is the amount of space before and
   353  // after the text within a cell that's left blank, and is in units passed to
   354  // New().
   355  func (f *Fpdf) SetCellMargin(margin float64) {
   356  	f.cMargin = margin
   357  }
   358  
   359  // SetPageBoxRec sets the page box for the current page, and any following
   360  // pages. Allowable types are trim, trimbox, crop, cropbox, bleed, bleedbox,
   361  // art and artbox box types are case insensitive. See SetPageBox() for a method
   362  // that specifies the coordinates and extent of the page box individually.
   363  func (f *Fpdf) SetPageBoxRec(t string, pb PageBox) {
   364  	switch strings.ToLower(t) {
   365  	case "trim":
   366  		fallthrough
   367  	case "trimbox":
   368  		t = "TrimBox"
   369  	case "crop":
   370  		fallthrough
   371  	case "cropbox":
   372  		t = "CropBox"
   373  	case "bleed":
   374  		fallthrough
   375  	case "bleedbox":
   376  		t = "BleedBox"
   377  	case "art":
   378  		fallthrough
   379  	case "artbox":
   380  		t = "ArtBox"
   381  	default:
   382  		f.err = fmt.Errorf("%s is not a valid page box type", t)
   383  		return
   384  	}
   385  
   386  	pb.X = pb.X * f.k
   387  	pb.Y = pb.Y * f.k
   388  	pb.Wd = (pb.Wd * f.k) + pb.X
   389  	pb.Ht = (pb.Ht * f.k) + pb.Y
   390  
   391  	if f.page > 0 {
   392  		f.pageBoxes[f.page][t] = pb
   393  	}
   394  
   395  	// always override. page defaults are supplied in addPage function
   396  	f.defPageBoxes[t] = pb
   397  }
   398  
   399  // SetPageBox sets the page box for the current page, and any following pages.
   400  // Allowable types are trim, trimbox, crop, cropbox, bleed, bleedbox, art and
   401  // artbox box types are case insensitive.
   402  func (f *Fpdf) SetPageBox(t string, x, y, wd, ht float64) {
   403  	f.SetPageBoxRec(t, PageBox{SizeType{Wd: wd, Ht: ht}, PointType{X: x, Y: y}})
   404  }
   405  
   406  // SetPage sets the current page to that of a valid page in the PDF document.
   407  // pageNum is one-based. The SetPage() example demonstrates this method.
   408  func (f *Fpdf) SetPage(pageNum int) {
   409  	if (pageNum > 0) && (pageNum < len(f.pages)) {
   410  		f.page = pageNum
   411  	}
   412  }
   413  
   414  // PageCount returns the number of pages currently in the document. Since page
   415  // numbers in gofpdf are one-based, the page count is the same as the page
   416  // number of the current last page.
   417  func (f *Fpdf) PageCount() int {
   418  	return len(f.pages) - 1
   419  }
   420  
   421  // GetFontLocation returns the location in the file system of the font and font
   422  // definition files.
   423  func (f *Fpdf) GetFontLocation() string {
   424  	return f.fontpath
   425  }
   426  
   427  // SetFontLocation sets the location in the file system of the font and font
   428  // definition files.
   429  func (f *Fpdf) SetFontLocation(fontDirStr string) {
   430  	f.fontpath = fontDirStr
   431  }
   432  
   433  // GetFontLoader returns the loader used to read font files (.json and .z) from
   434  // an arbitrary source.
   435  func (f *Fpdf) GetFontLoader() FontLoader {
   436  	return f.fontLoader
   437  }
   438  
   439  // SetFontLoader sets a loader used to read font files (.json and .z) from an
   440  // arbitrary source. If a font loader has been specified, it is used to load
   441  // the named font resources when AddFont() is called. If this operation fails,
   442  // an attempt is made to load the resources from the configured font directory
   443  // (see SetFontLocation()).
   444  func (f *Fpdf) SetFontLoader(loader FontLoader) {
   445  	f.fontLoader = loader
   446  }
   447  
   448  // SetHeaderFuncMode sets the function that lets the application render the
   449  // page header. See SetHeaderFunc() for more details. The value for homeMode
   450  // should be set to true to have the current position set to the left and top
   451  // margin after the header function is called.
   452  func (f *Fpdf) SetHeaderFuncMode(fnc func(), homeMode bool) {
   453  	f.headerFnc = fnc
   454  	f.headerHomeMode = homeMode
   455  }
   456  
   457  // SetHeaderFunc sets the function that lets the application render the page
   458  // header. The specified function is automatically called by AddPage() and
   459  // should not be called directly by the application. The implementation in Fpdf
   460  // is empty, so you have to provide an appropriate function if you want page
   461  // headers. fnc will typically be a closure that has access to the Fpdf
   462  // instance and other document generation variables.
   463  //
   464  // A header is a convenient place to put background content that repeats on
   465  // each page such as a watermark. When this is done, remember to reset the X
   466  // and Y values so the normal content begins where expected. Including a
   467  // watermark on each page is demonstrated in the example for TransformRotate.
   468  //
   469  // This method is demonstrated in the example for AddPage().
   470  func (f *Fpdf) SetHeaderFunc(fnc func()) {
   471  	f.headerFnc = fnc
   472  }
   473  
   474  // SetFooterFunc sets the function that lets the application render the page
   475  // footer. The specified function is automatically called by AddPage() and
   476  // Close() and should not be called directly by the application. The
   477  // implementation in Fpdf is empty, so you have to provide an appropriate
   478  // function if you want page footers. fnc will typically be a closure that has
   479  // access to the Fpdf instance and other document generation variables. See
   480  // SetFooterFuncLpi for a similar function that passes a last page indicator.
   481  //
   482  // This method is demonstrated in the example for AddPage().
   483  func (f *Fpdf) SetFooterFunc(fnc func()) {
   484  	f.footerFnc = fnc
   485  	f.footerFncLpi = nil
   486  }
   487  
   488  // SetFooterFuncLpi sets the function that lets the application render the page
   489  // footer. The specified function is automatically called by AddPage() and
   490  // Close() and should not be called directly by the application. It is passed a
   491  // boolean that is true if the last page of the document is being rendered. The
   492  // implementation in Fpdf is empty, so you have to provide an appropriate
   493  // function if you want page footers. fnc will typically be a closure that has
   494  // access to the Fpdf instance and other document generation variables.
   495  func (f *Fpdf) SetFooterFuncLpi(fnc func(lastPage bool)) {
   496  	f.footerFncLpi = fnc
   497  	f.footerFnc = nil
   498  }
   499  
   500  // SetTopMargin defines the top margin. The method can be called before
   501  // creating the first page.
   502  func (f *Fpdf) SetTopMargin(margin float64) {
   503  	f.tMargin = margin
   504  }
   505  
   506  // SetRightMargin defines the right margin. The method can be called before
   507  // creating the first page.
   508  func (f *Fpdf) SetRightMargin(margin float64) {
   509  	f.rMargin = margin
   510  }
   511  
   512  // GetAutoPageBreak returns true if automatic pages breaks are enabled, false
   513  // otherwise. This is followed by the triggering limit from the bottom of the
   514  // page. This value applies only if automatic page breaks are enabled.
   515  func (f *Fpdf) GetAutoPageBreak() (auto bool, margin float64) {
   516  	auto = f.autoPageBreak
   517  	margin = f.bMargin
   518  	return
   519  }
   520  
   521  // SetAutoPageBreak enables or disables the automatic page breaking mode. When
   522  // enabling, the second parameter is the distance from the bottom of the page
   523  // that defines the triggering limit. By default, the mode is on and the margin
   524  // is 2 cm.
   525  func (f *Fpdf) SetAutoPageBreak(auto bool, margin float64) {
   526  	f.autoPageBreak = auto
   527  	f.bMargin = margin
   528  	f.pageBreakTrigger = f.h - margin
   529  }
   530  
   531  // GetDisplayMode returns the current display mode. See SetDisplayMode() for details.
   532  func (f *Fpdf) GetDisplayMode() (zoomStr, layoutStr string) {
   533  	return f.zoomMode, f.layoutMode
   534  }
   535  
   536  // SetDisplayMode sets advisory display directives for the document viewer.
   537  // Pages can be displayed entirely on screen, occupy the full width of the
   538  // window, use real size, be scaled by a specific zooming factor or use viewer
   539  // default (configured in the Preferences menu of Adobe Reader). The page
   540  // layout can be specified so that pages are displayed individually or in
   541  // pairs.
   542  //
   543  // zoomStr can be "fullpage" to display the entire page on screen, "fullwidth"
   544  // to use maximum width of window, "real" to use real size (equivalent to 100%
   545  // zoom) or "default" to use viewer default mode.
   546  //
   547  // layoutStr can be "single" (or "SinglePage") to display one page at once,
   548  // "continuous" (or "OneColumn") to display pages continuously, "two" (or
   549  // "TwoColumnLeft") to display two pages on two columns with odd-numbered pages
   550  // on the left, or "TwoColumnRight" to display two pages on two columns with
   551  // odd-numbered pages on the right, or "TwoPageLeft" to display pages two at a
   552  // time with odd-numbered pages on the left, or "TwoPageRight" to display pages
   553  // two at a time with odd-numbered pages on the right, or "default" to use
   554  // viewer default mode.
   555  func (f *Fpdf) SetDisplayMode(zoomStr, layoutStr string) {
   556  	if f.err != nil {
   557  		return
   558  	}
   559  	if layoutStr == "" {
   560  		layoutStr = "default"
   561  	}
   562  	switch zoomStr {
   563  	case "fullpage", "fullwidth", "real", "default":
   564  		f.zoomMode = zoomStr
   565  	default:
   566  		f.err = fmt.Errorf("incorrect zoom display mode: %s", zoomStr)
   567  		return
   568  	}
   569  	switch layoutStr {
   570  	case "single", "continuous", "two", "default", "SinglePage", "OneColumn",
   571  		"TwoColumnLeft", "TwoColumnRight", "TwoPageLeft", "TwoPageRight":
   572  		f.layoutMode = layoutStr
   573  	default:
   574  		f.err = fmt.Errorf("incorrect layout display mode: %s", layoutStr)
   575  		return
   576  	}
   577  }
   578  
   579  // SetDefaultCompression controls the default setting of the internal
   580  // compression flag. See SetCompression() for more details. Compression is on
   581  // by default.
   582  func SetDefaultCompression(compress bool) {
   583  	gl.noCompress = !compress
   584  }
   585  
   586  // GetCompression returns whether page compression is enabled.
   587  func (f *Fpdf) GetCompression() bool {
   588  	return f.compress
   589  }
   590  
   591  // SetCompression activates or deactivates page compression with zlib. When
   592  // activated, the internal representation of each page is compressed, which
   593  // leads to a compression ratio of about 2 for the resulting document.
   594  // Compression is on by default.
   595  func (f *Fpdf) SetCompression(compress bool) {
   596  	f.compress = compress
   597  }
   598  
   599  // GetProducer returns the producer of the document as ISO-8859-1 or UTF-16BE.
   600  func (f *Fpdf) GetProducer() string {
   601  	return f.producer
   602  }
   603  
   604  // SetProducer defines the producer of the document. isUTF8 indicates if the string
   605  // is encoded in ISO-8859-1 (false) or UTF-8 (true).
   606  func (f *Fpdf) SetProducer(producerStr string, isUTF8 bool) {
   607  	if isUTF8 {
   608  		producerStr = utf8toutf16(producerStr)
   609  	}
   610  	f.producer = producerStr
   611  }
   612  
   613  // GetTitle returns the title of the document as ISO-8859-1 or UTF-16BE.
   614  func (f *Fpdf) GetTitle() string {
   615  	return f.title
   616  }
   617  
   618  // SetTitle defines the title of the document. isUTF8 indicates if the string
   619  // is encoded in ISO-8859-1 (false) or UTF-8 (true).
   620  func (f *Fpdf) SetTitle(titleStr string, isUTF8 bool) {
   621  	if isUTF8 {
   622  		titleStr = utf8toutf16(titleStr)
   623  	}
   624  	f.title = titleStr
   625  }
   626  
   627  // GetSubject returns the subject of the document as ISO-8859-1 or UTF-16BE.
   628  func (f *Fpdf) GetSubject() string {
   629  	return f.subject
   630  }
   631  
   632  // SetSubject defines the subject of the document. isUTF8 indicates if the
   633  // string is encoded in ISO-8859-1 (false) or UTF-8 (true).
   634  func (f *Fpdf) SetSubject(subjectStr string, isUTF8 bool) {
   635  	if isUTF8 {
   636  		subjectStr = utf8toutf16(subjectStr)
   637  	}
   638  	f.subject = subjectStr
   639  }
   640  
   641  // GetAuthor returns the author of the document as ISO-8859-1 or UTF-16BE.
   642  func (f *Fpdf) GetAuthor() string {
   643  	return f.author
   644  }
   645  
   646  // SetAuthor defines the author of the document. isUTF8 indicates if the string
   647  // is encoded in ISO-8859-1 (false) or UTF-8 (true).
   648  func (f *Fpdf) SetAuthor(authorStr string, isUTF8 bool) {
   649  	if isUTF8 {
   650  		authorStr = utf8toutf16(authorStr)
   651  	}
   652  	f.author = authorStr
   653  }
   654  
   655  // GetLang returns the natural language of the document (e.g. "de-CH").
   656  func (f *Fpdf) GetLang() string {
   657  	return f.lang
   658  }
   659  
   660  // SetLang defines the natural language of the document (e.g. "de-CH").
   661  func (f *Fpdf) SetLang(lang string) {
   662  	f.lang = lang
   663  }
   664  
   665  // GetKeywords returns the keywords of the document as ISO-8859-1 or UTF-16BE.
   666  func (f *Fpdf) GetKeywords() string {
   667  	return f.keywords
   668  }
   669  
   670  // SetKeywords defines the keywords of the document. keywordStr is a
   671  // space-delimited string, for example "invoice August". isUTF8 indicates if
   672  // the string is encoded
   673  func (f *Fpdf) SetKeywords(keywordsStr string, isUTF8 bool) {
   674  	if isUTF8 {
   675  		keywordsStr = utf8toutf16(keywordsStr)
   676  	}
   677  	f.keywords = keywordsStr
   678  }
   679  
   680  // GetCreator returns the creator of the document as ISO-8859-1 or UTF-16BE.
   681  func (f *Fpdf) GetCreator() string {
   682  	return f.creator
   683  }
   684  
   685  // SetCreator defines the creator of the document. isUTF8 indicates if the
   686  // string is encoded in ISO-8859-1 (false) or UTF-8 (true).
   687  func (f *Fpdf) SetCreator(creatorStr string, isUTF8 bool) {
   688  	if isUTF8 {
   689  		creatorStr = utf8toutf16(creatorStr)
   690  	}
   691  	f.creator = creatorStr
   692  }
   693  
   694  // GetXmpMetadata returns the XMP metadata that will be embedded with the document.
   695  func (f *Fpdf) GetXmpMetadata() []byte {
   696  	return []byte(string(f.xmp))
   697  }
   698  
   699  // SetXmpMetadata defines XMP metadata that will be embedded with the document.
   700  func (f *Fpdf) SetXmpMetadata(xmpStream []byte) {
   701  	f.xmp = xmpStream
   702  }
   703  
   704  // AliasNbPages defines an alias for the total number of pages. It will be
   705  // substituted as the document is closed. An empty string is replaced with the
   706  // string "{nb}".
   707  //
   708  // See the example for AddPage() for a demonstration of this method.
   709  func (f *Fpdf) AliasNbPages(aliasStr string) {
   710  	if aliasStr == "" {
   711  		aliasStr = "{nb}"
   712  	}
   713  	f.aliasNbPagesStr = aliasStr
   714  }
   715  
   716  // RTL enables right-to-left mode
   717  func (f *Fpdf) RTL() {
   718  	f.isRTL = true
   719  }
   720  
   721  // LTR disables right-to-left mode
   722  func (f *Fpdf) LTR() {
   723  	f.isRTL = false
   724  }
   725  
   726  // open begins a document
   727  func (f *Fpdf) open() {
   728  	f.state = 1
   729  }
   730  
   731  // Close terminates the PDF document. It is not necessary to call this method
   732  // explicitly because Output(), OutputAndClose() and OutputFileAndClose() do it
   733  // automatically. If the document contains no page, AddPage() is called to
   734  // prevent the generation of an invalid document.
   735  func (f *Fpdf) Close() {
   736  	if f.err == nil {
   737  		if f.clipNest > 0 {
   738  			f.err = fmt.Errorf("clip procedure must be explicitly ended")
   739  		} else if f.transformNest > 0 {
   740  			f.err = fmt.Errorf("transformation procedure must be explicitly ended")
   741  		}
   742  	}
   743  	if f.err != nil {
   744  		return
   745  	}
   746  	if f.state == 3 {
   747  		return
   748  	}
   749  	if f.page == 0 {
   750  		f.AddPage()
   751  		if f.err != nil {
   752  			return
   753  		}
   754  	}
   755  	// Page footer
   756  	f.inFooter = true
   757  	if f.footerFnc != nil {
   758  		f.footerFnc()
   759  	} else if f.footerFncLpi != nil {
   760  		f.footerFncLpi(true)
   761  	}
   762  	f.inFooter = false
   763  
   764  	// Close page
   765  	f.endpage()
   766  	// Close document
   767  	f.enddoc()
   768  }
   769  
   770  // PageSize returns the width and height of the specified page in the units
   771  // established in New(). These return values are followed by the unit of
   772  // measure itself. If pageNum is zero or otherwise out of bounds, it returns
   773  // the default page size, that is, the size of the page that would be added by
   774  // AddPage().
   775  func (f *Fpdf) PageSize(pageNum int) (wd, ht float64, unitStr string) {
   776  	sz, ok := f.pageSizes[pageNum]
   777  	if ok {
   778  		sz.Wd, sz.Ht = sz.Wd/f.k, sz.Ht/f.k
   779  	} else {
   780  		sz = f.defPageSize // user units
   781  	}
   782  	return sz.Wd, sz.Ht, f.unitStr
   783  }
   784  
   785  // AddPageFormat adds a new page with non-default orientation or size. See
   786  // AddPage() for more details.
   787  //
   788  // See New() for a description of orientationStr.
   789  //
   790  // size specifies the size of the new page in the units established in New().
   791  //
   792  // The PageSize() example demonstrates this method.
   793  func (f *Fpdf) AddPageFormat(orientationStr string, size SizeType) {
   794  	if f.err != nil {
   795  		return
   796  	}
   797  	if f.page != len(f.pages)-1 {
   798  		f.page = len(f.pages) - 1
   799  	}
   800  	if f.state == 0 {
   801  		f.open()
   802  	}
   803  	familyStr := f.fontFamily
   804  	style := f.fontStyle
   805  	if f.underline {
   806  		style += "U"
   807  	}
   808  	if f.strikeout {
   809  		style += "S"
   810  	}
   811  	fontsize := f.fontSizePt
   812  	lw := f.lineWidth
   813  	dc := f.color.draw
   814  	fc := f.color.fill
   815  	tc := f.color.text
   816  	cf := f.colorFlag
   817  
   818  	if f.page > 0 {
   819  		f.inFooter = true
   820  		// Page footer avoid double call on footer.
   821  		if f.footerFnc != nil {
   822  			f.footerFnc()
   823  
   824  		} else if f.footerFncLpi != nil {
   825  			f.footerFncLpi(false) // not last page.
   826  		}
   827  		f.inFooter = false
   828  		// Close page
   829  		f.endpage()
   830  	}
   831  	// Start new page
   832  	f.beginpage(orientationStr, size)
   833  	// 	Set line cap style to current value
   834  	// f.out("2 J")
   835  	f.outf("%d J", f.capStyle)
   836  	// 	Set line join style to current value
   837  	f.outf("%d j", f.joinStyle)
   838  	// Set line width
   839  	f.lineWidth = lw
   840  	f.outf("%.2f w", lw*f.k)
   841  	// Set dash pattern
   842  	if len(f.dashArray) > 0 {
   843  		f.outputDashPattern()
   844  	}
   845  	// 	Set font
   846  	if familyStr != "" {
   847  		f.SetFont(familyStr, style, fontsize)
   848  		if f.err != nil {
   849  			return
   850  		}
   851  	}
   852  	// 	Set colors
   853  	f.color.draw = dc
   854  	if dc.str != "0 G" {
   855  		f.out(dc.str)
   856  	}
   857  	f.color.fill = fc
   858  	if fc.str != "0 g" {
   859  		f.out(fc.str)
   860  	}
   861  	f.color.text = tc
   862  	f.colorFlag = cf
   863  	// 	Page header
   864  	if f.headerFnc != nil {
   865  		f.inHeader = true
   866  		f.headerFnc()
   867  		f.inHeader = false
   868  		if f.headerHomeMode {
   869  			f.SetHomeXY()
   870  		}
   871  	}
   872  	// 	Restore line width
   873  	if f.lineWidth != lw {
   874  		f.lineWidth = lw
   875  		f.outf("%.2f w", lw*f.k)
   876  	}
   877  	// Restore font
   878  	if familyStr != "" {
   879  		f.SetFont(familyStr, style, fontsize)
   880  		if f.err != nil {
   881  			return
   882  		}
   883  	}
   884  	// Restore colors
   885  	if f.color.draw.str != dc.str {
   886  		f.color.draw = dc
   887  		f.out(dc.str)
   888  	}
   889  	if f.color.fill.str != fc.str {
   890  		f.color.fill = fc
   891  		f.out(fc.str)
   892  	}
   893  	f.color.text = tc
   894  	f.colorFlag = cf
   895  }
   896  
   897  // AddPage adds a new page to the document. If a page is already present, the
   898  // Footer() method is called first to output the footer. Then the page is
   899  // added, the current position set to the top-left corner according to the left
   900  // and top margins, and Header() is called to display the header.
   901  //
   902  // The font which was set before calling is automatically restored. There is no
   903  // need to call SetFont() again if you want to continue with the same font. The
   904  // same is true for colors and line width.
   905  //
   906  // The origin of the coordinate system is at the top-left corner and increasing
   907  // ordinates go downwards.
   908  //
   909  // See AddPageFormat() for a version of this method that allows the page size
   910  // and orientation to be different than the default.
   911  func (f *Fpdf) AddPage() {
   912  	if f.err != nil {
   913  		return
   914  	}
   915  	// dbg("AddPage")
   916  	f.AddPageFormat(f.defOrientation, f.defPageSize)
   917  }
   918  
   919  // PageNo returns the current page number.
   920  //
   921  // See the example for AddPage() for a demonstration of this method.
   922  func (f *Fpdf) PageNo() int {
   923  	return f.page
   924  }
   925  
   926  func colorComp(v int) (int, float64) {
   927  	if v < 0 {
   928  		v = 0
   929  	} else if v > 255 {
   930  		v = 255
   931  	}
   932  	return v, float64(v) / 255.0
   933  }
   934  
   935  func (f *Fpdf) rgbColorValue(r, g, b int, grayStr, fullStr string) (clr colorType) {
   936  	clr.ir, clr.r = colorComp(r)
   937  	clr.ig, clr.g = colorComp(g)
   938  	clr.ib, clr.b = colorComp(b)
   939  	clr.mode = colorModeRGB
   940  	clr.gray = clr.ir == clr.ig && clr.r == clr.b
   941  	const prec = 3
   942  	if len(grayStr) > 0 {
   943  		if clr.gray {
   944  			// clr.str = sprintf("%.3f %s", clr.r, grayStr)
   945  			f.fmt.col.Reset()
   946  			f.fmt.col.WriteString(f.fmtF64(clr.r, prec))
   947  			f.fmt.col.WriteString(" ")
   948  			f.fmt.col.WriteString(grayStr)
   949  			clr.str = f.fmt.col.String()
   950  		} else {
   951  			// clr.str = sprintf("%.3f %.3f %.3f %s", clr.r, clr.g, clr.b, fullStr)
   952  			f.fmt.col.Reset()
   953  			f.fmt.col.WriteString(f.fmtF64(clr.r, prec))
   954  			f.fmt.col.WriteString(" ")
   955  			f.fmt.col.WriteString(f.fmtF64(clr.g, prec))
   956  			f.fmt.col.WriteString(" ")
   957  			f.fmt.col.WriteString(f.fmtF64(clr.b, prec))
   958  			f.fmt.col.WriteString(" ")
   959  			f.fmt.col.WriteString(fullStr)
   960  			clr.str = f.fmt.col.String()
   961  		}
   962  	} else {
   963  		// clr.str = sprintf("%.3f %.3f %.3f", clr.r, clr.g, clr.b)
   964  		f.fmt.col.Reset()
   965  		f.fmt.col.WriteString(f.fmtF64(clr.r, prec))
   966  		f.fmt.col.WriteString(" ")
   967  		f.fmt.col.WriteString(f.fmtF64(clr.g, prec))
   968  		f.fmt.col.WriteString(" ")
   969  		f.fmt.col.WriteString(f.fmtF64(clr.b, prec))
   970  		clr.str = f.fmt.col.String()
   971  	}
   972  	return
   973  }
   974  
   975  // SetDrawColor defines the color used for all drawing operations (lines,
   976  // rectangles and cell borders). It is expressed in RGB components (0 - 255).
   977  // The method can be called before the first page is created. The value is
   978  // retained from page to page.
   979  func (f *Fpdf) SetDrawColor(r, g, b int) {
   980  	f.setDrawColor(r, g, b)
   981  }
   982  
   983  func (f *Fpdf) setDrawColor(r, g, b int) {
   984  	f.color.draw = f.rgbColorValue(r, g, b, "G", "RG")
   985  	if f.page > 0 {
   986  		f.out(f.color.draw.str)
   987  	}
   988  }
   989  
   990  // GetDrawColor returns the most recently set draw color as RGB components (0 -
   991  // 255). This will not be the current value if a draw color of some other type
   992  // (for example, spot) has been more recently set.
   993  func (f *Fpdf) GetDrawColor() (int, int, int) {
   994  	return f.color.draw.ir, f.color.draw.ig, f.color.draw.ib
   995  }
   996  
   997  // SetFillColor defines the color used for all filling operations (filled
   998  // rectangles and cell backgrounds). It is expressed in RGB components (0
   999  // -255). The method can be called before the first page is created and the
  1000  // value is retained from page to page.
  1001  func (f *Fpdf) SetFillColor(r, g, b int) {
  1002  	f.setFillColor(r, g, b)
  1003  }
  1004  
  1005  func (f *Fpdf) setFillColor(r, g, b int) {
  1006  	f.color.fill = f.rgbColorValue(r, g, b, "g", "rg")
  1007  	f.colorFlag = f.color.fill.str != f.color.text.str
  1008  	if f.page > 0 {
  1009  		f.out(f.color.fill.str)
  1010  	}
  1011  }
  1012  
  1013  // GetFillColor returns the most recently set fill color as RGB components (0 -
  1014  // 255). This will not be the current value if a fill color of some other type
  1015  // (for example, spot) has been more recently set.
  1016  func (f *Fpdf) GetFillColor() (int, int, int) {
  1017  	return f.color.fill.ir, f.color.fill.ig, f.color.fill.ib
  1018  }
  1019  
  1020  // SetTextColor defines the color used for text. It is expressed in RGB
  1021  // components (0 - 255). The method can be called before the first page is
  1022  // created. The value is retained from page to page.
  1023  func (f *Fpdf) SetTextColor(r, g, b int) {
  1024  	f.setTextColor(r, g, b)
  1025  }
  1026  
  1027  func (f *Fpdf) setTextColor(r, g, b int) {
  1028  	f.color.text = f.rgbColorValue(r, g, b, "g", "rg")
  1029  	f.colorFlag = f.color.fill.str != f.color.text.str
  1030  }
  1031  
  1032  // GetTextColor returns the most recently set text color as RGB components (0 -
  1033  // 255). This will not be the current value if a text color of some other type
  1034  // (for example, spot) has been more recently set.
  1035  func (f *Fpdf) GetTextColor() (int, int, int) {
  1036  	return f.color.text.ir, f.color.text.ig, f.color.text.ib
  1037  }
  1038  
  1039  // GetStringWidth returns the length of a string in user units. A font must be
  1040  // currently selected.
  1041  func (f *Fpdf) GetStringWidth(s string) float64 {
  1042  	if f.err != nil {
  1043  		return 0
  1044  	}
  1045  	w := f.GetStringSymbolWidth(s)
  1046  	return float64(w) * f.fontSize / 1000
  1047  }
  1048  
  1049  // GetStringSymbolWidth returns the length of a string in glyf units. A font must be
  1050  // currently selected.
  1051  func (f *Fpdf) GetStringSymbolWidth(s string) int {
  1052  	if f.err != nil {
  1053  		return 0
  1054  	}
  1055  	w := 0
  1056  	if f.isCurrentUTF8 {
  1057  		for _, char := range s {
  1058  			intChar := int(char)
  1059  			if len(f.currentFont.Cw) >= intChar && f.currentFont.Cw[intChar] > 0 {
  1060  				if f.currentFont.Cw[intChar] != 65535 {
  1061  					w += f.currentFont.Cw[intChar]
  1062  				}
  1063  			} else if f.currentFont.Desc.MissingWidth != 0 {
  1064  				w += f.currentFont.Desc.MissingWidth
  1065  			} else {
  1066  				w += 500
  1067  			}
  1068  		}
  1069  	} else {
  1070  		for _, ch := range []byte(s) {
  1071  			if ch == 0 {
  1072  				break
  1073  			}
  1074  			w += f.currentFont.Cw[ch]
  1075  		}
  1076  	}
  1077  	return w
  1078  }
  1079  
  1080  // SetLineWidth defines the line width. By default, the value equals 0.2 mm.
  1081  // The method can be called before the first page is created. The value is
  1082  // retained from page to page.
  1083  func (f *Fpdf) SetLineWidth(width float64) {
  1084  	f.setLineWidth(width)
  1085  }
  1086  
  1087  func (f *Fpdf) setLineWidth(width float64) {
  1088  	f.lineWidth = width
  1089  	if f.page > 0 {
  1090  		f.out(f.fmtF64(width*f.k, 2) + " w")
  1091  	}
  1092  }
  1093  
  1094  // GetLineWidth returns the current line thickness.
  1095  func (f *Fpdf) GetLineWidth() float64 {
  1096  	return f.lineWidth
  1097  }
  1098  
  1099  // GetLineCapStyle returns the current line cap style.
  1100  func (f *Fpdf) GetLineCapStyle() string {
  1101  	switch f.capStyle {
  1102  	case 1:
  1103  		return "round"
  1104  	case 2:
  1105  		return "square"
  1106  	default:
  1107  		return "butt"
  1108  	}
  1109  }
  1110  
  1111  // SetLineCapStyle defines the line cap style. styleStr should be "butt",
  1112  // "round" or "square". A square style projects from the end of the line. The
  1113  // method can be called before the first page is created. The value is
  1114  // retained from page to page.
  1115  func (f *Fpdf) SetLineCapStyle(styleStr string) {
  1116  	var capStyle int
  1117  	switch styleStr {
  1118  	case "round":
  1119  		capStyle = 1
  1120  	case "square":
  1121  		capStyle = 2
  1122  	default:
  1123  		capStyle = 0
  1124  	}
  1125  	f.capStyle = capStyle
  1126  	if f.page > 0 {
  1127  		f.outf("%d J", f.capStyle)
  1128  	}
  1129  }
  1130  
  1131  // GetLineJoinStyle returns the current line join style.
  1132  func (f *Fpdf) GetLineJoinStyle() string {
  1133  	switch f.joinStyle {
  1134  	case 1:
  1135  		return "round"
  1136  	case 2:
  1137  		return "bevel"
  1138  	default:
  1139  		return "miter"
  1140  	}
  1141  }
  1142  
  1143  // SetLineJoinStyle defines the line cap style. styleStr should be "miter",
  1144  // "round" or "bevel". The method can be called before the first page
  1145  // is created. The value is retained from page to page.
  1146  func (f *Fpdf) SetLineJoinStyle(styleStr string) {
  1147  	var joinStyle int
  1148  	switch styleStr {
  1149  	case "round":
  1150  		joinStyle = 1
  1151  	case "bevel":
  1152  		joinStyle = 2
  1153  	default:
  1154  		joinStyle = 0
  1155  	}
  1156  	f.joinStyle = joinStyle
  1157  	if f.page > 0 {
  1158  		f.outf("%d j", f.joinStyle)
  1159  	}
  1160  }
  1161  
  1162  // SetDashPattern sets the dash pattern that is used to draw lines. The
  1163  // dashArray elements are numbers that specify the lengths, in units
  1164  // established in New(), of alternating dashes and gaps. The dash phase
  1165  // specifies the distance into the dash pattern at which to start the dash. The
  1166  // dash pattern is retained from page to page. Call this method with an empty
  1167  // array to restore solid line drawing.
  1168  //
  1169  // The Beziergon() example demonstrates this method.
  1170  func (f *Fpdf) SetDashPattern(dashArray []float64, dashPhase float64) {
  1171  	scaled := make([]float64, len(dashArray))
  1172  	for i, value := range dashArray {
  1173  		scaled[i] = value * f.k
  1174  	}
  1175  	dashPhase *= f.k
  1176  
  1177  	f.dashArray = scaled
  1178  	f.dashPhase = dashPhase
  1179  	if f.page > 0 {
  1180  		f.outputDashPattern()
  1181  	}
  1182  
  1183  }
  1184  
  1185  func (f *Fpdf) outputDashPattern() {
  1186  	var buf bytes.Buffer
  1187  	buf.WriteByte('[')
  1188  	for i, value := range f.dashArray {
  1189  		if i > 0 {
  1190  			buf.WriteByte(' ')
  1191  		}
  1192  		buf.WriteString(strconv.FormatFloat(value, 'f', 2, 64))
  1193  	}
  1194  	buf.WriteString("] ")
  1195  	buf.WriteString(strconv.FormatFloat(f.dashPhase, 'f', 2, 64))
  1196  	buf.WriteString(" d")
  1197  	f.outbuf(&buf)
  1198  }
  1199  
  1200  // Line draws a line between points (x1, y1) and (x2, y2) using the current
  1201  // draw color, line width and cap style.
  1202  func (f *Fpdf) Line(x1, y1, x2, y2 float64) {
  1203  	// 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)
  1204  	const prec = 2
  1205  	f.putF64(x1*f.k, prec)
  1206  	f.put(" ")
  1207  	f.putF64((f.h-y1)*f.k, prec)
  1208  	f.put(" m ")
  1209  	f.putF64(x2*f.k, prec)
  1210  	f.put(" ")
  1211  	f.putF64((f.h-y2)*f.k, prec)
  1212  	f.put(" l S\n")
  1213  }
  1214  
  1215  // fillDrawOp corrects path painting operators
  1216  func fillDrawOp(styleStr string) (opStr string) {
  1217  	switch strings.ToUpper(styleStr) {
  1218  	case "", "D":
  1219  		// Stroke the path.
  1220  		opStr = "S"
  1221  	case "F":
  1222  		// fill the path, using the nonzero winding number rule
  1223  		opStr = "f"
  1224  	case "F*":
  1225  		// fill the path, using the even-odd rule
  1226  		opStr = "f*"
  1227  	case "FD", "DF":
  1228  		// fill and then stroke the path, using the nonzero winding number rule
  1229  		opStr = "B"
  1230  	case "FD*", "DF*":
  1231  		// fill and then stroke the path, using the even-odd rule
  1232  		opStr = "B*"
  1233  	default:
  1234  		opStr = styleStr
  1235  	}
  1236  	return
  1237  }
  1238  
  1239  // Rect outputs a rectangle of width w and height h with the upper left corner
  1240  // positioned at point (x, y).
  1241  //
  1242  // It can be drawn (border only), filled (with no border) or both. styleStr can
  1243  // be "F" for filled, "D" for outlined only, or "DF" or "FD" for outlined and
  1244  // filled. An empty string will be replaced with "D". Drawing uses the current
  1245  // draw color and line width centered on the rectangle's perimeter. Filling
  1246  // uses the current fill color.
  1247  func (f *Fpdf) Rect(x, y, w, h float64, styleStr string) {
  1248  	// f.outf("%.2f %.2f %.2f %.2f re %s", x*f.k, (f.h-y)*f.k, w*f.k, -h*f.k, fillDrawOp(styleStr))
  1249  	const prec = 2
  1250  	f.putF64(x*f.k, prec)
  1251  	f.put(" ")
  1252  	f.putF64((f.h-y)*f.k, prec)
  1253  	f.put(" ")
  1254  	f.putF64(w*f.k, prec)
  1255  	f.put(" ")
  1256  	f.putF64(-h*f.k, prec)
  1257  	f.put(" re " + fillDrawOp(styleStr) + "\n")
  1258  }
  1259  
  1260  // RoundedRect outputs a rectangle of width w and height h with the upper left
  1261  // corner positioned at point (x, y). It can be drawn (border only), filled
  1262  // (with no border) or both. styleStr can be "F" for filled, "D" for outlined
  1263  // only, or "DF" or "FD" for outlined and filled. An empty string will be
  1264  // replaced with "D". Drawing uses the current draw color and line width
  1265  // centered on the rectangle's perimeter. Filling uses the current fill color.
  1266  // The rounded corners of the rectangle are specified by radius r. corners is a
  1267  // string that includes "1" to round the upper left corner, "2" to round the
  1268  // upper right corner, "3" to round the lower right corner, and "4" to round
  1269  // the lower left corner. The RoundedRect example demonstrates this method.
  1270  func (f *Fpdf) RoundedRect(x, y, w, h, r float64, corners string, stylestr string) {
  1271  	// This routine was adapted by Brigham Thompson from a script by Christophe Prugnaud
  1272  	var rTL, rTR, rBR, rBL float64 // zero means no rounded corner
  1273  	if strings.Contains(corners, "1") {
  1274  		rTL = r
  1275  	}
  1276  	if strings.Contains(corners, "2") {
  1277  		rTR = r
  1278  	}
  1279  	if strings.Contains(corners, "3") {
  1280  		rBR = r
  1281  	}
  1282  	if strings.Contains(corners, "4") {
  1283  		rBL = r
  1284  	}
  1285  	f.RoundedRectExt(x, y, w, h, rTL, rTR, rBR, rBL, stylestr)
  1286  }
  1287  
  1288  // RoundedRectExt behaves the same as RoundedRect() but supports a different
  1289  // radius for each corner. A zero radius means squared corner. See
  1290  // RoundedRect() for more details. This method is demonstrated in the
  1291  // RoundedRect() example.
  1292  func (f *Fpdf) RoundedRectExt(x, y, w, h, rTL, rTR, rBR, rBL float64, stylestr string) {
  1293  	f.roundedRectPath(x, y, w, h, rTL, rTR, rBR, rBL)
  1294  	f.out(fillDrawOp(stylestr))
  1295  	f.out("Q")
  1296  }
  1297  
  1298  // Circle draws a circle centered on point (x, y) with radius r.
  1299  //
  1300  // styleStr can be "F" for filled, "D" for outlined only, or "DF" or "FD" for
  1301  // outlined and filled. An empty string will be replaced with "D". Drawing uses
  1302  // the current draw color and line width centered on the circle's perimeter.
  1303  // Filling uses the current fill color.
  1304  func (f *Fpdf) Circle(x, y, r float64, styleStr string) {
  1305  	f.Ellipse(x, y, r, r, 0, styleStr)
  1306  }
  1307  
  1308  // Ellipse draws an ellipse centered at point (x, y). rx and ry specify its
  1309  // horizontal and vertical radii.
  1310  //
  1311  // degRotate specifies the counter-clockwise angle in degrees that the ellipse
  1312  // will be rotated.
  1313  //
  1314  // styleStr can be "F" for filled, "D" for outlined only, or "DF" or "FD" for
  1315  // outlined and filled. An empty string will be replaced with "D". Drawing uses
  1316  // the current draw color and line width centered on the ellipse's perimeter.
  1317  // Filling uses the current fill color.
  1318  //
  1319  // The Circle() example demonstrates this method.
  1320  func (f *Fpdf) Ellipse(x, y, rx, ry, degRotate float64, styleStr string) {
  1321  	f.arc(x, y, rx, ry, degRotate, 0, 360, styleStr, false)
  1322  }
  1323  
  1324  // Polygon draws a closed figure defined by a series of vertices specified by
  1325  // points. The x and y fields of the points use the units established in New().
  1326  // The last point in the slice will be implicitly joined to the first to close
  1327  // the polygon.
  1328  //
  1329  // styleStr can be "F" for filled, "D" for outlined only, or "DF" or "FD" for
  1330  // outlined and filled. An empty string will be replaced with "D". Drawing uses
  1331  // the current draw color and line width centered on the ellipse's perimeter.
  1332  // Filling uses the current fill color.
  1333  func (f *Fpdf) Polygon(points []PointType, styleStr string) {
  1334  	if len(points) > 2 {
  1335  		const prec = 5
  1336  		for j, pt := range points {
  1337  			if j == 0 {
  1338  				f.point(pt.X, pt.Y)
  1339  			} else {
  1340  				// f.outf("%.5f %.5f l ", pt.X*f.k, (f.h-pt.Y)*f.k)
  1341  				f.putF64(pt.X*f.k, prec)
  1342  				f.put(" ")
  1343  				f.putF64((f.h-pt.Y)*f.k, prec)
  1344  				f.put(" l \n")
  1345  			}
  1346  		}
  1347  		// f.outf("%.5f %.5f l ", points[0].X*f.k, (f.h-points[0].Y)*f.k)
  1348  		f.putF64(points[0].X*f.k, prec)
  1349  		f.put(" ")
  1350  		f.putF64((f.h-points[0].Y)*f.k, prec)
  1351  		f.put(" l \n")
  1352  		f.DrawPath(styleStr)
  1353  	}
  1354  }
  1355  
  1356  // Beziergon draws a closed figure defined by a series of cubic Bézier curve
  1357  // segments. The first point in the slice defines the starting point of the
  1358  // figure. Each three following points p1, p2, p3 represent a curve segment to
  1359  // the point p3 using p1 and p2 as the Bézier control points.
  1360  //
  1361  // The x and y fields of the points use the units established in New().
  1362  //
  1363  // styleStr can be "F" for filled, "D" for outlined only, or "DF" or "FD" for
  1364  // outlined and filled. An empty string will be replaced with "D". Drawing uses
  1365  // the current draw color and line width centered on the ellipse's perimeter.
  1366  // Filling uses the current fill color.
  1367  func (f *Fpdf) Beziergon(points []PointType, styleStr string) {
  1368  
  1369  	// Thanks, Robert Lillack, for contributing this function.
  1370  
  1371  	if len(points) < 4 {
  1372  		return
  1373  	}
  1374  	f.point(points[0].XY())
  1375  
  1376  	points = points[1:]
  1377  	for len(points) >= 3 {
  1378  		cx0, cy0 := points[0].XY()
  1379  		cx1, cy1 := points[1].XY()
  1380  		x1, y1 := points[2].XY()
  1381  		f.curve(cx0, cy0, cx1, cy1, x1, y1)
  1382  		points = points[3:]
  1383  	}
  1384  
  1385  	f.DrawPath(styleStr)
  1386  }
  1387  
  1388  // point outputs current point
  1389  func (f *Fpdf) point(x, y float64) {
  1390  	// f.outf("%.2f %.2f m", x*f.k, (f.h-y)*f.k)
  1391  	f.putF64(x*f.k, 2)
  1392  	f.put(" ")
  1393  	f.putF64((f.h-y)*f.k, 2)
  1394  	f.put(" m\n")
  1395  }
  1396  
  1397  // curve outputs a single cubic Bézier curve segment from current point
  1398  func (f *Fpdf) curve(cx0, cy0, cx1, cy1, x, y float64) {
  1399  	// Thanks, Robert Lillack, for straightening this out
  1400  	// f.outf("%.5f %.5f %.5f %.5f %.5f %.5f c", cx0*f.k, (f.h-cy0)*f.k, cx1*f.k,
  1401  	// 	(f.h-cy1)*f.k, x*f.k, (f.h-y)*f.k)
  1402  	const prec = 5
  1403  	f.putF64(cx0*f.k, prec)
  1404  	f.put(" ")
  1405  	f.putF64((f.h-cy0)*f.k, prec)
  1406  	f.put(" ")
  1407  	f.putF64(cx1*f.k, prec)
  1408  	f.put(" ")
  1409  	f.putF64((f.h-cy1)*f.k, prec)
  1410  	f.put(" ")
  1411  	f.putF64(x*f.k, prec)
  1412  	f.put(" ")
  1413  	f.putF64((f.h-y)*f.k, prec)
  1414  	f.put(" c\n")
  1415  }
  1416  
  1417  // Curve draws a single-segment quadratic Bézier curve. The curve starts at
  1418  // the point (x0, y0) and ends at the point (x1, y1). The control point (cx,
  1419  // cy) specifies the curvature. At the start point, the curve is tangent to the
  1420  // straight line between the start point and the control point. At the end
  1421  // point, the curve is tangent to the straight line between the end point and
  1422  // the control point.
  1423  //
  1424  // styleStr can be "F" for filled, "D" for outlined only, or "DF" or "FD" for
  1425  // outlined and filled. An empty string will be replaced with "D". Drawing uses
  1426  // the current draw color, line width, and cap style centered on the curve's
  1427  // path. Filling uses the current fill color.
  1428  //
  1429  // The Circle() example demonstrates this method.
  1430  func (f *Fpdf) Curve(x0, y0, cx, cy, x1, y1 float64, styleStr string) {
  1431  	f.point(x0, y0)
  1432  	// f.outf("%.5f %.5f %.5f %.5f v %s", cx*f.k, (f.h-cy)*f.k, x1*f.k, (f.h-y1)*f.k,
  1433  	// 	fillDrawOp(styleStr))
  1434  	const prec = 5
  1435  	f.putF64(cx*f.k, prec)
  1436  	f.put(" ")
  1437  	f.putF64((f.h-cy)*f.k, prec)
  1438  	f.put(" ")
  1439  	f.putF64(x1*f.k, prec)
  1440  	f.put(" ")
  1441  	f.putF64((f.h-y1)*f.k, prec)
  1442  	f.put(" v " + fillDrawOp(styleStr) + "\n")
  1443  }
  1444  
  1445  // CurveCubic draws a single-segment cubic Bézier curve. This routine performs
  1446  // the same function as CurveBezierCubic() but has a nonstandard argument order.
  1447  // It is retained to preserve backward compatibility.
  1448  func (f *Fpdf) CurveCubic(x0, y0, cx0, cy0, x1, y1, cx1, cy1 float64, styleStr string) {
  1449  	// f.point(x0, y0)
  1450  	// f.outf("%.5f %.5f %.5f %.5f %.5f %.5f c %s", cx0*f.k, (f.h-cy0)*f.k,
  1451  	// cx1*f.k, (f.h-cy1)*f.k, x1*f.k, (f.h-y1)*f.k, fillDrawOp(styleStr))
  1452  	f.CurveBezierCubic(x0, y0, cx0, cy0, cx1, cy1, x1, y1, styleStr)
  1453  }
  1454  
  1455  // CurveBezierCubic draws a single-segment cubic Bézier curve. The curve starts at
  1456  // the point (x0, y0) and ends at the point (x1, y1). The control points (cx0,
  1457  // cy0) and (cx1, cy1) specify the curvature. At the start point, the curve is
  1458  // tangent to the straight line between the start point and the control point
  1459  // (cx0, cy0). At the end point, the curve is tangent to the straight line
  1460  // between the end point and the control point (cx1, cy1).
  1461  //
  1462  // styleStr can be "F" for filled, "D" for outlined only, or "DF" or "FD" for
  1463  // outlined and filled. An empty string will be replaced with "D". Drawing uses
  1464  // the current draw color, line width, and cap style centered on the curve's
  1465  // path. Filling uses the current fill color.
  1466  //
  1467  // This routine performs the same function as CurveCubic() but uses standard
  1468  // argument order.
  1469  //
  1470  // The Circle() example demonstrates this method.
  1471  func (f *Fpdf) CurveBezierCubic(x0, y0, cx0, cy0, cx1, cy1, x1, y1 float64, styleStr string) {
  1472  	f.point(x0, y0)
  1473  	//	f.outf("%.5f %.5f %.5f %.5f %.5f %.5f c %s", cx0*f.k, (f.h-cy0)*f.k,
  1474  	//		cx1*f.k, (f.h-cy1)*f.k, x1*f.k, (f.h-y1)*f.k, fillDrawOp(styleStr))
  1475  	const prec = 5
  1476  	f.putF64(cx0*f.k, prec)
  1477  	f.put(" ")
  1478  	f.putF64((f.h-cy0)*f.k, prec)
  1479  	f.put(" ")
  1480  	f.putF64(cx1*f.k, prec)
  1481  	f.put(" ")
  1482  	f.putF64((f.h-cy1)*f.k, prec)
  1483  	f.put(" ")
  1484  	f.putF64(x1*f.k, prec)
  1485  	f.put(" ")
  1486  	f.putF64((f.h-y1)*f.k, prec)
  1487  	f.put(" c " + fillDrawOp(styleStr) + "\n")
  1488  }
  1489  
  1490  // Arc draws an elliptical arc centered at point (x, y). rx and ry specify its
  1491  // horizontal and vertical radii.
  1492  //
  1493  // degRotate specifies the angle that the arc will be rotated. degStart and
  1494  // degEnd specify the starting and ending angle of the arc. All angles are
  1495  // specified in degrees and measured counter-clockwise from the 3 o'clock
  1496  // position.
  1497  //
  1498  // styleStr can be "F" for filled, "D" for outlined only, or "DF" or "FD" for
  1499  // outlined and filled. An empty string will be replaced with "D". Drawing uses
  1500  // the current draw color, line width, and cap style centered on the arc's
  1501  // path. Filling uses the current fill color.
  1502  //
  1503  // The Circle() example demonstrates this method.
  1504  func (f *Fpdf) Arc(x, y, rx, ry, degRotate, degStart, degEnd float64, styleStr string) {
  1505  	f.arc(x, y, rx, ry, degRotate, degStart, degEnd, styleStr, false)
  1506  }
  1507  
  1508  // GetAlpha returns the alpha blending channel, which consists of the
  1509  // alpha transparency value and the blend mode. See SetAlpha for more
  1510  // details.
  1511  func (f *Fpdf) GetAlpha() (alpha float64, blendModeStr string) {
  1512  	return f.alpha, f.blendMode
  1513  }
  1514  
  1515  // SetAlpha sets the alpha blending channel. The blending effect applies to
  1516  // text, drawings and images.
  1517  //
  1518  // alpha must be a value between 0.0 (fully transparent) to 1.0 (fully opaque).
  1519  // Values outside of this range result in an error.
  1520  //
  1521  // blendModeStr must be one of "Normal", "Multiply", "Screen", "Overlay",
  1522  // "Darken", "Lighten", "ColorDodge", "ColorBurn","HardLight", "SoftLight",
  1523  // "Difference", "Exclusion", "Hue", "Saturation", "Color", or "Luminosity". An
  1524  // empty string is replaced with "Normal".
  1525  //
  1526  // To reset normal rendering after applying a blending mode, call this method
  1527  // with alpha set to 1.0 and blendModeStr set to "Normal".
  1528  func (f *Fpdf) SetAlpha(alpha float64, blendModeStr string) {
  1529  	if f.err != nil {
  1530  		return
  1531  	}
  1532  	var bl blendModeType
  1533  	switch blendModeStr {
  1534  	case "Normal", "Multiply", "Screen", "Overlay",
  1535  		"Darken", "Lighten", "ColorDodge", "ColorBurn", "HardLight", "SoftLight",
  1536  		"Difference", "Exclusion", "Hue", "Saturation", "Color", "Luminosity":
  1537  		bl.modeStr = blendModeStr
  1538  	case "":
  1539  		bl.modeStr = "Normal"
  1540  	default:
  1541  		f.err = fmt.Errorf("unrecognized blend mode \"%s\"", blendModeStr)
  1542  		return
  1543  	}
  1544  	if alpha < 0.0 || alpha > 1.0 {
  1545  		f.err = fmt.Errorf("alpha value (0.0 - 1.0) is out of range: %.3f", alpha)
  1546  		return
  1547  	}
  1548  	f.alpha = alpha
  1549  	f.blendMode = blendModeStr
  1550  	alphaStr := sprintf("%.3f", alpha)
  1551  	keyStr := sprintf("%s %s", alphaStr, blendModeStr)
  1552  	pos, ok := f.blendMap[keyStr]
  1553  	if !ok {
  1554  		pos = len(f.blendList) // at least 1
  1555  		f.blendList = append(f.blendList, blendModeType{alphaStr, alphaStr, blendModeStr, 0})
  1556  		f.blendMap[keyStr] = pos
  1557  	}
  1558  	if len(f.blendMap) > 0 && f.pdfVersion < pdfVers1_4 {
  1559  		f.pdfVersion = pdfVers1_4
  1560  	}
  1561  	f.outf("/GS%d gs", pos)
  1562  }
  1563  
  1564  func (f *Fpdf) gradientClipStart(x, y, w, h float64) {
  1565  	{
  1566  		const prec = 2
  1567  		// Save current graphic state and set clipping area
  1568  		// f.outf("q %.2f %.2f %.2f %.2f re W n", x*f.k, (f.h-y)*f.k, w*f.k, -h*f.k)
  1569  		f.put("q ")
  1570  		f.putF64(x*f.k, prec)
  1571  		f.put(" ")
  1572  		f.putF64((f.h-y)*f.k, prec)
  1573  		f.put(" ")
  1574  		f.putF64(w*f.k, prec)
  1575  		f.put(" ")
  1576  		f.putF64(-h*f.k, prec)
  1577  		f.put(" re W n\n")
  1578  	}
  1579  	{
  1580  		const prec = 5
  1581  		// Set up transformation matrix for gradient
  1582  		// f.outf("%.5f 0 0 %.5f %.5f %.5f cm", w*f.k, h*f.k, x*f.k, (f.h-(y+h))*f.k)
  1583  		f.putF64(w*f.k, prec)
  1584  		f.put(" 0 0 ")
  1585  		f.putF64(h*f.k, prec)
  1586  		f.put(" ")
  1587  		f.putF64(x*f.k, prec)
  1588  		f.put(" ")
  1589  		f.putF64((f.h-(y+h))*f.k, prec)
  1590  		f.put(" cm\n")
  1591  	}
  1592  }
  1593  
  1594  func (f *Fpdf) gradientClipEnd() {
  1595  	// Restore previous graphic state
  1596  	f.out("Q")
  1597  }
  1598  
  1599  func (f *Fpdf) gradient(tp, r1, g1, b1, r2, g2, b2 int, x1, y1, x2, y2, r float64) {
  1600  	pos := len(f.gradientList)
  1601  	clr1 := f.rgbColorValue(r1, g1, b1, "", "")
  1602  	clr2 := f.rgbColorValue(r2, g2, b2, "", "")
  1603  	f.gradientList = append(f.gradientList, gradientType{tp, clr1.str, clr2.str,
  1604  		x1, y1, x2, y2, r, 0})
  1605  	f.outf("/Sh%d sh", pos)
  1606  }
  1607  
  1608  // LinearGradient draws a rectangular area with a blending of one color to
  1609  // another. The rectangle is of width w and height h. Its upper left corner is
  1610  // positioned at point (x, y).
  1611  //
  1612  // Each color is specified with three component values, one each for red, green
  1613  // and blue. The values range from 0 to 255. The first color is specified by
  1614  // (r1, g1, b1) and the second color by (r2, g2, b2).
  1615  //
  1616  // The blending is controlled with a gradient vector that uses normalized
  1617  // coordinates in which the lower left corner is position (0, 0) and the upper
  1618  // right corner is (1, 1). The vector's origin and destination are specified by
  1619  // the points (x1, y1) and (x2, y2). In a linear gradient, blending occurs
  1620  // perpendicularly to the vector. The vector does not necessarily need to be
  1621  // anchored on the rectangle edge. Color 1 is used up to the origin of the
  1622  // vector and color 2 is used beyond the vector's end point. Between the points
  1623  // the colors are gradually blended.
  1624  func (f *Fpdf) LinearGradient(x, y, w, h float64, r1, g1, b1, r2, g2, b2 int, x1, y1, x2, y2 float64) {
  1625  	f.gradientClipStart(x, y, w, h)
  1626  	f.gradient(2, r1, g1, b1, r2, g2, b2, x1, y1, x2, y2, 0)
  1627  	f.gradientClipEnd()
  1628  }
  1629  
  1630  // RadialGradient draws a rectangular area with a blending of one color to
  1631  // another. The rectangle is of width w and height h. Its upper left corner is
  1632  // positioned at point (x, y).
  1633  //
  1634  // Each color is specified with three component values, one each for red, green
  1635  // and blue. The values range from 0 to 255. The first color is specified by
  1636  // (r1, g1, b1) and the second color by (r2, g2, b2).
  1637  //
  1638  // The blending is controlled with a point and a circle, both specified with
  1639  // normalized coordinates in which the lower left corner of the rendered
  1640  // rectangle is position (0, 0) and the upper right corner is (1, 1). Color 1
  1641  // begins at the origin point specified by (x1, y1). Color 2 begins at the
  1642  // circle specified by the center point (x2, y2) and radius r. Colors are
  1643  // gradually blended from the origin to the circle. The origin and the circle's
  1644  // center do not necessarily have to coincide, but the origin must be within
  1645  // the circle to avoid rendering problems.
  1646  //
  1647  // The LinearGradient() example demonstrates this method.
  1648  func (f *Fpdf) RadialGradient(x, y, w, h float64, r1, g1, b1, r2, g2, b2 int, x1, y1, x2, y2, r float64) {
  1649  	f.gradientClipStart(x, y, w, h)
  1650  	f.gradient(3, r1, g1, b1, r2, g2, b2, x1, y1, x2, y2, r)
  1651  	f.gradientClipEnd()
  1652  }
  1653  
  1654  // ClipRect begins a rectangular clipping operation. The rectangle is of width
  1655  // w and height h. Its upper left corner is positioned at point (x, y). outline
  1656  // is true to draw a border with the current draw color and line width centered
  1657  // on the rectangle's perimeter. Only the outer half of the border will be
  1658  // shown. After calling this method, all rendering operations (for example,
  1659  // Image(), LinearGradient(), etc) will be clipped by the specified rectangle.
  1660  // Call ClipEnd() to restore unclipped operations.
  1661  //
  1662  // This ClipText() example demonstrates this method.
  1663  func (f *Fpdf) ClipRect(x, y, w, h float64, outline bool) {
  1664  	f.clipNest++
  1665  	// 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"))
  1666  	const prec = 2
  1667  	f.put("q ")
  1668  	f.putF64(x*f.k, prec)
  1669  	f.put(" ")
  1670  	f.putF64((f.h-y)*f.k, prec)
  1671  	f.put(" ")
  1672  	f.putF64(w*f.k, prec)
  1673  	f.put(" ")
  1674  	f.putF64(-h*f.k, prec)
  1675  	f.put(" re W " + strIf(outline, "S", "n") + "\n")
  1676  }
  1677  
  1678  // ClipText begins a clipping operation in which rendering is confined to the
  1679  // character string specified by txtStr. The origin (x, y) is on the left of
  1680  // the first character at the baseline. The current font is used. outline is
  1681  // true to draw a border with the current draw color and line width centered on
  1682  // the perimeters of the text characters. Only the outer half of the border
  1683  // will be shown. After calling this method, all rendering operations (for
  1684  // example, Image(), LinearGradient(), etc) will be clipped. Call ClipEnd() to
  1685  // restore unclipped operations.
  1686  func (f *Fpdf) ClipText(x, y float64, txtStr string, outline bool) {
  1687  	f.clipNest++
  1688  	// 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))
  1689  	const prec = 5
  1690  	f.put("q BT ")
  1691  	f.putF64(x*f.k, prec)
  1692  	f.put(" ")
  1693  	f.putF64((f.h-y)*f.k, prec)
  1694  	f.put(" Td ")
  1695  	f.putInt(intIf(outline, 5, 7))
  1696  	f.put(" Tr (")
  1697  	f.put(f.escape(txtStr))
  1698  	f.put(") Tj ET\n")
  1699  }
  1700  
  1701  func (f *Fpdf) clipArc(x1, y1, x2, y2, x3, y3 float64) {
  1702  	h := f.h
  1703  	// f.outf("%.5f %.5f %.5f %.5f %.5f %.5f c ", x1*f.k, (h-y1)*f.k,
  1704  	// 	x2*f.k, (h-y2)*f.k, x3*f.k, (h-y3)*f.k)
  1705  	const prec = 5
  1706  	f.putF64(x1*f.k, prec)
  1707  	f.put(" ")
  1708  	f.putF64((h-y1)*f.k, prec)
  1709  	f.put(" ")
  1710  	f.putF64(x2*f.k, prec)
  1711  	f.put(" ")
  1712  	f.putF64((h-y2)*f.k, prec)
  1713  	f.put(" ")
  1714  	f.putF64(x3*f.k, prec)
  1715  	f.put(" ")
  1716  	f.putF64((h-y3)*f.k, prec)
  1717  	f.put(" c \n")
  1718  }
  1719  
  1720  // ClipRoundedRect begins a rectangular clipping operation. The rectangle is of
  1721  // width w and height h. Its upper left corner is positioned at point (x, y).
  1722  // The rounded corners of the rectangle are specified by radius r. outline is
  1723  // true to draw a border with the current draw color and line width centered on
  1724  // the rectangle's perimeter. Only the outer half of the border will be shown.
  1725  // After calling this method, all rendering operations (for example, Image(),
  1726  // LinearGradient(), etc) will be clipped by the specified rectangle. Call
  1727  // ClipEnd() to restore unclipped operations.
  1728  //
  1729  // This ClipText() example demonstrates this method.
  1730  func (f *Fpdf) ClipRoundedRect(x, y, w, h, r float64, outline bool) {
  1731  	f.ClipRoundedRectExt(x, y, w, h, r, r, r, r, outline)
  1732  }
  1733  
  1734  // ClipRoundedRectExt behaves the same as ClipRoundedRect() but supports a
  1735  // different radius for each corner, given by rTL (top-left), rTR (top-right)
  1736  // rBR (bottom-right), rBL (bottom-left). See ClipRoundedRect() for more
  1737  // details. This method is demonstrated in the ClipText() example.
  1738  func (f *Fpdf) ClipRoundedRectExt(x, y, w, h, rTL, rTR, rBR, rBL float64, outline bool) {
  1739  	f.clipNest++
  1740  	f.roundedRectPath(x, y, w, h, rTL, rTR, rBR, rBL)
  1741  	f.outf(" W %s", strIf(outline, "S", "n"))
  1742  }
  1743  
  1744  // add a rectangle path with rounded corners.
  1745  // routine shared by RoundedRect() and ClipRoundedRect(), which add the
  1746  // drawing operation
  1747  func (f *Fpdf) roundedRectPath(x, y, w, h, rTL, rTR, rBR, rBL float64) {
  1748  	k := f.k
  1749  	hp := f.h
  1750  	myArc := (4.0 / 3.0) * (math.Sqrt2 - 1.0)
  1751  	// f.outf("q %.5f %.5f m", (x+rTL)*k, (hp-y)*k)
  1752  	const prec = 5
  1753  	f.put("q ")
  1754  	f.putF64((x+rTL)*k, prec)
  1755  	f.put(" ")
  1756  	f.putF64((hp-y)*k, prec)
  1757  	f.put(" m\n")
  1758  	xc := x + w - rTR
  1759  	yc := y + rTR
  1760  	// f.outf("%.5f %.5f l", xc*k, (hp-y)*k)
  1761  	f.putF64(xc*k, prec)
  1762  	f.put(" ")
  1763  	f.putF64((hp-y)*k, prec)
  1764  	f.put(" l\n")
  1765  	if rTR != 0 {
  1766  		f.clipArc(xc+rTR*myArc, yc-rTR, xc+rTR, yc-rTR*myArc, xc+rTR, yc)
  1767  	}
  1768  	xc = x + w - rBR
  1769  	yc = y + h - rBR
  1770  	// f.outf("%.5f %.5f l", (x+w)*k, (hp-yc)*k)
  1771  	f.putF64((x+w)*k, prec)
  1772  	f.put(" ")
  1773  	f.putF64((hp-yc)*k, prec)
  1774  	f.put(" l\n")
  1775  	if rBR != 0 {
  1776  		f.clipArc(xc+rBR, yc+rBR*myArc, xc+rBR*myArc, yc+rBR, xc, yc+rBR)
  1777  	}
  1778  	xc = x + rBL
  1779  	yc = y + h - rBL
  1780  	// f.outf("%.5f %.5f l", xc*k, (hp-(y+h))*k)
  1781  	f.putF64(xc*k, prec)
  1782  	f.put(" ")
  1783  	f.putF64((hp-(y+h))*k, prec)
  1784  	f.put(" l\n")
  1785  	if rBL != 0 {
  1786  		f.clipArc(xc-rBL*myArc, yc+rBL, xc-rBL, yc+rBL*myArc, xc-rBL, yc)
  1787  	}
  1788  	xc = x + rTL
  1789  	yc = y + rTL
  1790  	// f.outf("%.5f %.5f l", x*k, (hp-yc)*k)
  1791  	f.putF64(x*k, prec)
  1792  	f.put(" ")
  1793  	f.putF64((hp-yc)*k, prec)
  1794  	f.put(" l\n")
  1795  	if rTL != 0 {
  1796  		f.clipArc(xc-rTL, yc-rTL*myArc, xc-rTL*myArc, yc-rTL, xc, yc-rTL)
  1797  	}
  1798  }
  1799  
  1800  // ClipEllipse begins an elliptical clipping operation. The ellipse is centered
  1801  // at (x, y). Its horizontal and vertical radii are specified by rx and ry.
  1802  // outline is true to draw a border with the current draw color and line width
  1803  // centered on the ellipse's perimeter. Only the outer half of the border will
  1804  // be shown. After calling this method, all rendering operations (for example,
  1805  // Image(), LinearGradient(), etc) will be clipped by the specified ellipse.
  1806  // Call ClipEnd() to restore unclipped operations.
  1807  //
  1808  // This ClipText() example demonstrates this method.
  1809  func (f *Fpdf) ClipEllipse(x, y, rx, ry float64, outline bool) {
  1810  	f.clipNest++
  1811  	lx := (4.0 / 3.0) * rx * (math.Sqrt2 - 1)
  1812  	ly := (4.0 / 3.0) * ry * (math.Sqrt2 - 1)
  1813  	k := f.k
  1814  	h := f.h
  1815  	//	f.outf("q %.5f %.5f m %.5f %.5f %.5f %.5f %.5f %.5f c",
  1816  	//		(x+rx)*k, (h-y)*k,
  1817  	//		(x+rx)*k, (h-(y-ly))*k,
  1818  	//		(x+lx)*k, (h-(y-ry))*k,
  1819  	//		x*k, (h-(y-ry))*k)
  1820  	const prec = 5
  1821  	f.put("q ")
  1822  	f.putF64((x+rx)*k, prec)
  1823  	f.put(" ")
  1824  	f.putF64((h-y)*k, prec)
  1825  	f.put(" m ")
  1826  	f.putF64((x+rx)*k, prec)
  1827  	f.put(" ")
  1828  	f.putF64((h-(y-ly))*k, prec)
  1829  	f.put(" ")
  1830  	f.putF64((x+lx)*k, prec)
  1831  	f.put(" ")
  1832  	f.putF64((h-(y-ry))*k, prec)
  1833  	f.put(" ")
  1834  	f.putF64(x*k, prec)
  1835  	f.put(" ")
  1836  	f.putF64((h-(y-ry))*k, prec)
  1837  	f.put(" c\n")
  1838  
  1839  	//	f.outf("%.5f %.5f %.5f %.5f %.5f %.5f c",
  1840  	//		(x-lx)*k, (h-(y-ry))*k,
  1841  	//		(x-rx)*k, (h-(y-ly))*k,
  1842  	//		(x-rx)*k, (h-y)*k)
  1843  	f.putF64((x-lx)*k, prec)
  1844  	f.put(" ")
  1845  	f.putF64((h-(y-ry))*k, prec)
  1846  	f.put(" ")
  1847  	f.putF64((x-rx)*k, prec)
  1848  	f.put(" ")
  1849  	f.putF64((h-(y-ly))*k, prec)
  1850  	f.put(" ")
  1851  	f.putF64((x-rx)*k, prec)
  1852  	f.put(" ")
  1853  	f.putF64((h-y)*k, prec)
  1854  	f.put(" c\n")
  1855  
  1856  	//	f.outf("%.5f %.5f %.5f %.5f %.5f %.5f c",
  1857  	//		(x-rx)*k, (h-(y+ly))*k,
  1858  	//		(x-lx)*k, (h-(y+ry))*k,
  1859  	//		x*k, (h-(y+ry))*k)
  1860  	f.putF64((x-rx)*k, prec)
  1861  	f.put(" ")
  1862  	f.putF64((h-(y+ly))*k, prec)
  1863  	f.put(" ")
  1864  	f.putF64((x-lx)*k, prec)
  1865  	f.put(" ")
  1866  	f.putF64((h-(y+ry))*k, prec)
  1867  	f.put(" ")
  1868  	f.putF64(x*k, prec)
  1869  	f.put(" ")
  1870  	f.putF64((h-(y+ry))*k, prec)
  1871  	f.put(" c\n")
  1872  
  1873  	//	f.outf("%.5f %.5f %.5f %.5f %.5f %.5f c W %s",
  1874  	//		(x+lx)*k, (h-(y+ry))*k,
  1875  	//		(x+rx)*k, (h-(y+ly))*k,
  1876  	//		(x+rx)*k, (h-y)*k,
  1877  	//		strIf(outline, "S", "n"))
  1878  	f.putF64((x+lx)*k, prec)
  1879  	f.put(" ")
  1880  	f.putF64((h-(y+ry))*k, prec)
  1881  	f.put(" ")
  1882  	f.putF64((x+rx)*k, prec)
  1883  	f.put(" ")
  1884  	f.putF64((h-(y+ly))*k, prec)
  1885  	f.put(" ")
  1886  	f.putF64((x+rx)*k, prec)
  1887  	f.put(" ")
  1888  	f.putF64((h-y)*k, prec)
  1889  	f.put(" c W " + strIf(outline, "S", "n") + "\n")
  1890  }
  1891  
  1892  // ClipCircle begins a circular clipping operation. The circle is centered at
  1893  // (x, y) and has radius r. outline is true to draw a border with the current
  1894  // draw color and line width centered on the circle's perimeter. Only the outer
  1895  // half of the border will be shown. After calling this method, all rendering
  1896  // operations (for example, Image(), LinearGradient(), etc) will be clipped by
  1897  // the specified circle. Call ClipEnd() to restore unclipped operations.
  1898  //
  1899  // The ClipText() example demonstrates this method.
  1900  func (f *Fpdf) ClipCircle(x, y, r float64, outline bool) {
  1901  	f.ClipEllipse(x, y, r, r, outline)
  1902  }
  1903  
  1904  // ClipPolygon begins a clipping operation within a polygon. The figure is
  1905  // defined by a series of vertices specified by points. The x and y fields of
  1906  // the points use the units established in New(). The last point in the slice
  1907  // will be implicitly joined to the first to close the polygon. outline is true
  1908  // to draw a border with the current draw color and line width centered on the
  1909  // polygon's perimeter. Only the outer half of the border will be shown. After
  1910  // calling this method, all rendering operations (for example, Image(),
  1911  // LinearGradient(), etc) will be clipped by the specified polygon. Call
  1912  // ClipEnd() to restore unclipped operations.
  1913  //
  1914  // The ClipText() example demonstrates this method.
  1915  func (f *Fpdf) ClipPolygon(points []PointType, outline bool) {
  1916  	f.clipNest++
  1917  	var s fmtBuffer
  1918  	h := f.h
  1919  	k := f.k
  1920  	s.printf("q ")
  1921  	for j, pt := range points {
  1922  		s.printf("%.5f %.5f %s ", pt.X*k, (h-pt.Y)*k, strIf(j == 0, "m", "l"))
  1923  	}
  1924  	s.printf("h W %s", strIf(outline, "S", "n"))
  1925  	f.out(s.String())
  1926  }
  1927  
  1928  // ClipEnd ends a clipping operation that was started with a call to
  1929  // ClipRect(), ClipRoundedRect(), ClipText(), ClipEllipse(), ClipCircle() or
  1930  // ClipPolygon(). Clipping operations can be nested. The document cannot be
  1931  // successfully output while a clipping operation is active.
  1932  //
  1933  // The ClipText() example demonstrates this method.
  1934  func (f *Fpdf) ClipEnd() {
  1935  	if f.err == nil {
  1936  		if f.clipNest > 0 {
  1937  			f.clipNest--
  1938  			f.out("Q")
  1939  		} else {
  1940  			f.err = fmt.Errorf("error attempting to end clip operation out of sequence")
  1941  		}
  1942  	}
  1943  }
  1944  
  1945  // AddFont imports a TrueType, OpenType or Type1 font and makes it available.
  1946  // It is necessary to generate a font definition file first with the makefont
  1947  // utility. It is not necessary to call this function for the core PDF fonts
  1948  // (courier, helvetica, times, zapfdingbats).
  1949  //
  1950  // The JSON definition file (and the font file itself when embedding) must be
  1951  // present in the font directory. If it is not found, the error "Could not
  1952  // include font definition file" is set.
  1953  //
  1954  // family specifies the font family. The name can be chosen arbitrarily. If it
  1955  // is a standard family name, it will override the corresponding font. This
  1956  // string is used to subsequently set the font with the SetFont method.
  1957  //
  1958  // style specifies the font style. Acceptable values are (case insensitive) the
  1959  // empty string for regular style, "B" for bold, "I" for italic, or "BI" or
  1960  // "IB" for bold and italic combined.
  1961  //
  1962  // fileStr specifies the base name with ".json" extension of the font
  1963  // definition file to be added. The file will be loaded from the font directory
  1964  // specified in the call to New() or SetFontLocation().
  1965  func (f *Fpdf) AddFont(familyStr, styleStr, fileStr string) {
  1966  	f.addFont(fontFamilyEscape(familyStr), styleStr, fileStr, false)
  1967  }
  1968  
  1969  // AddUTF8Font imports a TrueType font with utf-8 symbols and makes it available.
  1970  // It is necessary to generate a font definition file first with the makefont
  1971  // utility. It is not necessary to call this function for the core PDF fonts
  1972  // (courier, helvetica, times, zapfdingbats).
  1973  //
  1974  // The JSON definition file (and the font file itself when embedding) must be
  1975  // present in the font directory. If it is not found, the error "Could not
  1976  // include font definition file" is set.
  1977  //
  1978  // family specifies the font family. The name can be chosen arbitrarily. If it
  1979  // is a standard family name, it will override the corresponding font. This
  1980  // string is used to subsequently set the font with the SetFont method.
  1981  //
  1982  // style specifies the font style. Acceptable values are (case insensitive) the
  1983  // empty string for regular style, "B" for bold, "I" for italic, or "BI" or
  1984  // "IB" for bold and italic combined.
  1985  //
  1986  // fileStr specifies the base name with ".json" extension of the font
  1987  // definition file to be added. The file will be loaded from the font directory
  1988  // specified in the call to New() or SetFontLocation().
  1989  func (f *Fpdf) AddUTF8Font(familyStr, styleStr, fileStr string) {
  1990  	f.addFont(fontFamilyEscape(familyStr), styleStr, fileStr, true)
  1991  }
  1992  
  1993  func (f *Fpdf) addFont(familyStr, styleStr, fileStr string, isUTF8 bool) {
  1994  	if fileStr == "" {
  1995  		if isUTF8 {
  1996  			fileStr = strings.Replace(familyStr, " ", "", -1) + strings.ToLower(styleStr) + ".ttf"
  1997  		} else {
  1998  			fileStr = strings.Replace(familyStr, " ", "", -1) + strings.ToLower(styleStr) + ".json"
  1999  		}
  2000  	}
  2001  	if isUTF8 {
  2002  		fontKey := getFontKey(familyStr, styleStr)
  2003  		_, ok := f.fonts[fontKey]
  2004  		if ok {
  2005  			return
  2006  		}
  2007  		var ttfStat os.FileInfo
  2008  		var err error
  2009  		fileStr = path.Join(f.fontpath, fileStr)
  2010  		ttfStat, err = os.Stat(fileStr)
  2011  		if err != nil {
  2012  			f.SetError(err)
  2013  			return
  2014  		}
  2015  		originalSize := ttfStat.Size()
  2016  		Type := "UTF8"
  2017  		var utf8Bytes []byte
  2018  		utf8Bytes, err = os.ReadFile(fileStr)
  2019  		if err != nil {
  2020  			f.SetError(err)
  2021  			return
  2022  		}
  2023  		reader := fileReader{readerPosition: 0, array: utf8Bytes}
  2024  		utf8File := newUTF8Font(&reader)
  2025  		err = utf8File.parseFile()
  2026  		if err != nil {
  2027  			f.SetError(err)
  2028  			return
  2029  		}
  2030  
  2031  		desc := FontDescType{
  2032  			Ascent:       int(utf8File.Ascent),
  2033  			Descent:      int(utf8File.Descent),
  2034  			CapHeight:    utf8File.CapHeight,
  2035  			Flags:        utf8File.Flags,
  2036  			FontBBox:     utf8File.Bbox,
  2037  			ItalicAngle:  utf8File.ItalicAngle,
  2038  			StemV:        utf8File.StemV,
  2039  			MissingWidth: round(utf8File.DefaultWidth),
  2040  		}
  2041  
  2042  		var sbarr map[int]int
  2043  		if f.aliasNbPagesStr == "" {
  2044  			sbarr = makeSubsetRange(57)
  2045  		} else {
  2046  			sbarr = makeSubsetRange(32)
  2047  		}
  2048  		def := fontDefType{
  2049  			Tp:        Type,
  2050  			Name:      fontKey,
  2051  			Desc:      desc,
  2052  			Up:        int(round(utf8File.UnderlinePosition)),
  2053  			Ut:        round(utf8File.UnderlineThickness),
  2054  			Cw:        utf8File.CharWidths,
  2055  			usedRunes: sbarr,
  2056  			File:      fileStr,
  2057  			utf8File:  utf8File,
  2058  		}
  2059  		def.i, _ = generateFontID(def)
  2060  		f.fonts[fontKey] = def
  2061  		f.fontFiles[fontKey] = fontFileType{
  2062  			length1:  originalSize,
  2063  			fontType: "UTF8",
  2064  		}
  2065  		f.fontFiles[fileStr] = fontFileType{
  2066  			fontType: "UTF8",
  2067  		}
  2068  	} else {
  2069  		if f.fontLoader != nil {
  2070  			reader, err := f.fontLoader.Open(fileStr)
  2071  			if err == nil {
  2072  				f.AddFontFromReader(familyStr, styleStr, reader)
  2073  				if closer, ok := reader.(io.Closer); ok {
  2074  					closer.Close()
  2075  				}
  2076  				return
  2077  			}
  2078  		}
  2079  
  2080  		fileStr = path.Join(f.fontpath, fileStr)
  2081  		file, err := os.Open(fileStr)
  2082  		if err != nil {
  2083  			f.err = err
  2084  			return
  2085  		}
  2086  		defer file.Close()
  2087  
  2088  		f.AddFontFromReader(familyStr, styleStr, file)
  2089  	}
  2090  }
  2091  
  2092  func makeSubsetRange(end int) map[int]int {
  2093  	answer := make(map[int]int)
  2094  	for i := 0; i < end; i++ {
  2095  		answer[i] = 0
  2096  	}
  2097  	return answer
  2098  }
  2099  
  2100  // AddFontFromBytes imports a TrueType, OpenType or Type1 font from static
  2101  // bytes within the executable and makes it available for use in the generated
  2102  // document.
  2103  //
  2104  // family specifies the font family. The name can be chosen arbitrarily. If it
  2105  // is a standard family name, it will override the corresponding font. This
  2106  // string is used to subsequently set the font with the SetFont method.
  2107  //
  2108  // style specifies the font style. Acceptable values are (case insensitive) the
  2109  // empty string for regular style, "B" for bold, "I" for italic, or "BI" or
  2110  // "IB" for bold and italic combined.
  2111  //
  2112  // jsonFileBytes contain all bytes of JSON file.
  2113  //
  2114  // zFileBytes contain all bytes of Z file.
  2115  func (f *Fpdf) AddFontFromBytes(familyStr, styleStr string, jsonFileBytes, zFileBytes []byte) {
  2116  	f.addFontFromBytes(fontFamilyEscape(familyStr), styleStr, jsonFileBytes, zFileBytes, nil)
  2117  }
  2118  
  2119  // AddUTF8FontFromBytes  imports a TrueType font with utf-8 symbols from static
  2120  // bytes within the executable and makes it available for use in the generated
  2121  // document.
  2122  //
  2123  // family specifies the font family. The name can be chosen arbitrarily. If it
  2124  // is a standard family name, it will override the corresponding font. This
  2125  // string is used to subsequently set the font with the SetFont method.
  2126  //
  2127  // style specifies the font style. Acceptable values are (case insensitive) the
  2128  // empty string for regular style, "B" for bold, "I" for italic, or "BI" or
  2129  // "IB" for bold and italic combined.
  2130  //
  2131  // jsonFileBytes contain all bytes of JSON file.
  2132  //
  2133  // zFileBytes contain all bytes of Z file.
  2134  func (f *Fpdf) AddUTF8FontFromBytes(familyStr, styleStr string, utf8Bytes []byte) {
  2135  	f.addFontFromBytes(fontFamilyEscape(familyStr), styleStr, nil, nil, utf8Bytes)
  2136  }
  2137  
  2138  func (f *Fpdf) addFontFromBytes(familyStr, styleStr string, jsonFileBytes, zFileBytes, utf8Bytes []byte) {
  2139  	if f.err != nil {
  2140  		return
  2141  	}
  2142  
  2143  	// load font key
  2144  	var ok bool
  2145  	fontkey := getFontKey(familyStr, styleStr)
  2146  	_, ok = f.fonts[fontkey]
  2147  
  2148  	if ok {
  2149  		return
  2150  	}
  2151  
  2152  	if utf8Bytes != nil {
  2153  
  2154  		// if styleStr == "IB" {
  2155  		// 	styleStr = "BI"
  2156  		// }
  2157  
  2158  		Type := "UTF8"
  2159  		reader := fileReader{readerPosition: 0, array: utf8Bytes}
  2160  
  2161  		utf8File := newUTF8Font(&reader)
  2162  
  2163  		err := utf8File.parseFile()
  2164  		if err != nil {
  2165  			fmt.Printf("get metrics Error: %e\n", err)
  2166  			return
  2167  		}
  2168  		desc := FontDescType{
  2169  			Ascent:       int(utf8File.Ascent),
  2170  			Descent:      int(utf8File.Descent),
  2171  			CapHeight:    utf8File.CapHeight,
  2172  			Flags:        utf8File.Flags,
  2173  			FontBBox:     utf8File.Bbox,
  2174  			ItalicAngle:  utf8File.ItalicAngle,
  2175  			StemV:        utf8File.StemV,
  2176  			MissingWidth: round(utf8File.DefaultWidth),
  2177  		}
  2178  
  2179  		var sbarr map[int]int
  2180  		if f.aliasNbPagesStr == "" {
  2181  			sbarr = makeSubsetRange(57)
  2182  		} else {
  2183  			sbarr = makeSubsetRange(32)
  2184  		}
  2185  		def := fontDefType{
  2186  			Tp:        Type,
  2187  			Name:      fontkey,
  2188  			Desc:      desc,
  2189  			Up:        int(round(utf8File.UnderlinePosition)),
  2190  			Ut:        round(utf8File.UnderlineThickness),
  2191  			Cw:        utf8File.CharWidths,
  2192  			utf8File:  utf8File,
  2193  			usedRunes: sbarr,
  2194  		}
  2195  		def.i, _ = generateFontID(def)
  2196  		f.fonts[fontkey] = def
  2197  	} else {
  2198  		// load font definitions
  2199  		var info fontDefType
  2200  		err := json.Unmarshal(jsonFileBytes, &info)
  2201  
  2202  		if err != nil {
  2203  			f.err = err
  2204  		}
  2205  
  2206  		if f.err != nil {
  2207  			return
  2208  		}
  2209  
  2210  		if info.i, err = generateFontID(info); err != nil {
  2211  			f.err = err
  2212  			return
  2213  		}
  2214  
  2215  		// search existing encodings
  2216  		if len(info.Diff) > 0 {
  2217  			n := -1
  2218  
  2219  			for j, str := range f.diffs {
  2220  				if str == info.Diff {
  2221  					n = j + 1
  2222  					break
  2223  				}
  2224  			}
  2225  
  2226  			if n < 0 {
  2227  				f.diffs = append(f.diffs, info.Diff)
  2228  				n = len(f.diffs)
  2229  			}
  2230  
  2231  			info.DiffN = n
  2232  		}
  2233  
  2234  		// embed font
  2235  		if len(info.File) > 0 {
  2236  			if info.Tp == "TrueType" {
  2237  				f.fontFiles[info.File] = fontFileType{
  2238  					length1:  int64(info.OriginalSize),
  2239  					embedded: true,
  2240  					content:  zFileBytes,
  2241  				}
  2242  			} else {
  2243  				f.fontFiles[info.File] = fontFileType{
  2244  					length1:  int64(info.Size1),
  2245  					length2:  int64(info.Size2),
  2246  					embedded: true,
  2247  					content:  zFileBytes,
  2248  				}
  2249  			}
  2250  		}
  2251  
  2252  		f.fonts[fontkey] = info
  2253  	}
  2254  }
  2255  
  2256  // getFontKey is used by AddFontFromReader and GetFontDesc
  2257  func getFontKey(familyStr, styleStr string) string {
  2258  	familyStr = strings.ToLower(familyStr)
  2259  	styleStr = strings.ToUpper(styleStr)
  2260  	if styleStr == "IB" {
  2261  		styleStr = "BI"
  2262  	}
  2263  	return familyStr + styleStr
  2264  }
  2265  
  2266  // AddFontFromReader imports a TrueType, OpenType or Type1 font and makes it
  2267  // available using a reader that satisifies the io.Reader interface. See
  2268  // AddFont for details about familyStr and styleStr.
  2269  func (f *Fpdf) AddFontFromReader(familyStr, styleStr string, r io.Reader) {
  2270  	if f.err != nil {
  2271  		return
  2272  	}
  2273  	// dbg("Adding family [%s], style [%s]", familyStr, styleStr)
  2274  	familyStr = fontFamilyEscape(familyStr)
  2275  	var ok bool
  2276  	fontkey := getFontKey(familyStr, styleStr)
  2277  	_, ok = f.fonts[fontkey]
  2278  	if ok {
  2279  		return
  2280  	}
  2281  	info := f.loadfont(r)
  2282  	if f.err != nil {
  2283  		return
  2284  	}
  2285  	if len(info.Diff) > 0 {
  2286  		// Search existing encodings
  2287  		n := -1
  2288  		for j, str := range f.diffs {
  2289  			if str == info.Diff {
  2290  				n = j + 1
  2291  				break
  2292  			}
  2293  		}
  2294  		if n < 0 {
  2295  			f.diffs = append(f.diffs, info.Diff)
  2296  			n = len(f.diffs)
  2297  		}
  2298  		info.DiffN = n
  2299  	}
  2300  	// dbg("font [%s], type [%s]", info.File, info.Tp)
  2301  	if len(info.File) > 0 {
  2302  		// Embedded font
  2303  		if info.Tp == "TrueType" {
  2304  			f.fontFiles[info.File] = fontFileType{length1: int64(info.OriginalSize)}
  2305  		} else {
  2306  			f.fontFiles[info.File] = fontFileType{length1: int64(info.Size1), length2: int64(info.Size2)}
  2307  		}
  2308  	}
  2309  	f.fonts[fontkey] = info
  2310  }
  2311  
  2312  // GetFontDesc returns the font descriptor, which can be used for
  2313  // example to find the baseline of a font. If familyStr is empty
  2314  // current font descriptor will be returned.
  2315  // See FontDescType for documentation about the font descriptor.
  2316  // See AddFont for details about familyStr and styleStr.
  2317  func (f *Fpdf) GetFontDesc(familyStr, styleStr string) FontDescType {
  2318  	if familyStr == "" {
  2319  		return f.currentFont.Desc
  2320  	}
  2321  	return f.fonts[getFontKey(fontFamilyEscape(familyStr), styleStr)].Desc
  2322  }
  2323  
  2324  // SetFont sets the font used to print character strings. It is mandatory to
  2325  // call this method at least once before printing text or the resulting
  2326  // document will not be valid.
  2327  //
  2328  // The font can be either a standard one or a font added via the AddFont()
  2329  // method or AddFontFromReader() method. Standard fonts use the Windows
  2330  // encoding cp1252 (Western Europe).
  2331  //
  2332  // The method can be called before the first page is created and the font is
  2333  // kept from page to page. If you just wish to change the current font size, it
  2334  // is simpler to call SetFontSize().
  2335  //
  2336  // Note: the font definition file must be accessible. An error is set if the
  2337  // file cannot be read.
  2338  //
  2339  // familyStr specifies the font family. It can be either a name defined by
  2340  // AddFont(), AddFontFromReader() or one of the standard families (case
  2341  // insensitive): "Courier" for fixed-width, "Helvetica" or "Arial" for sans
  2342  // serif, "Times" for serif, "Symbol" or "ZapfDingbats" for symbolic.
  2343  //
  2344  // styleStr can be "B" (bold), "I" (italic), "U" (underscore), "S" (strike-out)
  2345  // or any combination. The default value (specified with an empty string) is
  2346  // regular. Bold and italic styles do not apply to Symbol and ZapfDingbats.
  2347  //
  2348  // size is the font size measured in points. The default value is the current
  2349  // size. If no size has been specified since the beginning of the document, the
  2350  // value taken is 12.
  2351  func (f *Fpdf) SetFont(familyStr, styleStr string, size float64) {
  2352  	// dbg("SetFont x %.2f, lMargin %.2f", f.x, f.lMargin)
  2353  
  2354  	if f.err != nil {
  2355  		return
  2356  	}
  2357  	// dbg("SetFont")
  2358  	familyStr = fontFamilyEscape(familyStr)
  2359  	var ok bool
  2360  	if familyStr == "" {
  2361  		familyStr = f.fontFamily
  2362  	} else {
  2363  		familyStr = strings.ToLower(familyStr)
  2364  	}
  2365  	styleStr = strings.ToUpper(styleStr)
  2366  	f.underline = strings.Contains(styleStr, "U")
  2367  	if f.underline {
  2368  		styleStr = strings.Replace(styleStr, "U", "", -1)
  2369  	}
  2370  	f.strikeout = strings.Contains(styleStr, "S")
  2371  	if f.strikeout {
  2372  		styleStr = strings.Replace(styleStr, "S", "", -1)
  2373  	}
  2374  	if styleStr == "IB" {
  2375  		styleStr = "BI"
  2376  	}
  2377  	if size == 0.0 {
  2378  		size = f.fontSizePt
  2379  	}
  2380  
  2381  	// Test if font is already loaded
  2382  	fontKey := familyStr + styleStr
  2383  	_, ok = f.fonts[fontKey]
  2384  	if !ok {
  2385  		// Test if one of the core fonts
  2386  		if familyStr == "arial" {
  2387  			familyStr = "helvetica"
  2388  		}
  2389  		_, ok = f.coreFonts[familyStr]
  2390  		if ok {
  2391  			if familyStr == "symbol" {
  2392  				familyStr = "zapfdingbats"
  2393  			}
  2394  			if familyStr == "zapfdingbats" {
  2395  				styleStr = ""
  2396  			}
  2397  			fontKey = familyStr + styleStr
  2398  			_, ok = f.fonts[fontKey]
  2399  			if !ok {
  2400  				rdr := f.coreFontReader(familyStr, styleStr)
  2401  				if f.err == nil {
  2402  					defer rdr.Close()
  2403  					f.AddFontFromReader(familyStr, styleStr, rdr)
  2404  				}
  2405  				if f.err != nil {
  2406  					return
  2407  				}
  2408  			}
  2409  		} else {
  2410  			f.err = fmt.Errorf("undefined font: %s %s", familyStr, styleStr)
  2411  			return
  2412  		}
  2413  	}
  2414  	// Select it
  2415  	f.fontFamily = familyStr
  2416  	f.fontStyle = styleStr
  2417  	f.fontSizePt = size
  2418  	f.fontSize = size / f.k
  2419  	f.currentFont = f.fonts[fontKey]
  2420  	if f.currentFont.Tp == "UTF8" {
  2421  		f.isCurrentUTF8 = true
  2422  	} else {
  2423  		f.isCurrentUTF8 = false
  2424  	}
  2425  	if f.page > 0 {
  2426  		f.outf("BT /F%s %.2f Tf ET", f.currentFont.i, f.fontSizePt)
  2427  	}
  2428  }
  2429  
  2430  // GetFontFamily returns the family of the current font. See SetFont() for details.
  2431  func (f *Fpdf) GetFontFamily() string {
  2432  	return f.fontFamily
  2433  }
  2434  
  2435  // GetFontStyle returns the style of the current font. See SetFont() for details.
  2436  func (f *Fpdf) GetFontStyle() string {
  2437  	styleStr := f.fontStyle
  2438  
  2439  	if f.underline {
  2440  		styleStr += "U"
  2441  	}
  2442  	if f.strikeout {
  2443  		styleStr += "S"
  2444  	}
  2445  
  2446  	return styleStr
  2447  }
  2448  
  2449  // SetFontStyle sets the style of the current font. See also SetFont()
  2450  func (f *Fpdf) SetFontStyle(styleStr string) {
  2451  	f.SetFont(f.fontFamily, styleStr, f.fontSizePt)
  2452  }
  2453  
  2454  // SetFontSize defines the size of the current font. Size is specified in
  2455  // points (1/ 72 inch). See also SetFontUnitSize().
  2456  func (f *Fpdf) SetFontSize(size float64) {
  2457  	f.fontSizePt = size
  2458  	f.fontSize = size / f.k
  2459  	if f.page > 0 {
  2460  		f.outf("BT /F%s %.2f Tf ET", f.currentFont.i, f.fontSizePt)
  2461  	}
  2462  }
  2463  
  2464  // SetFontUnitSize defines the size of the current font. Size is specified in
  2465  // the unit of measure specified in New(). See also SetFontSize().
  2466  func (f *Fpdf) SetFontUnitSize(size float64) {
  2467  	f.fontSizePt = size * f.k
  2468  	f.fontSize = size
  2469  	if f.page > 0 {
  2470  		f.outf("BT /F%s %.2f Tf ET", f.currentFont.i, f.fontSizePt)
  2471  	}
  2472  }
  2473  
  2474  // GetFontSize returns the size of the current font in points followed by the
  2475  // size in the unit of measure specified in New(). The second value can be used
  2476  // as a line height value in drawing operations.
  2477  func (f *Fpdf) GetFontSize() (ptSize, unitSize float64) {
  2478  	return f.fontSizePt, f.fontSize
  2479  }
  2480  
  2481  // AddLink creates a new internal link and returns its identifier. An internal
  2482  // link is a clickable area which directs to another place within the document.
  2483  // The identifier can then be passed to Cell(), Write(), Image() or Link(). The
  2484  // destination is defined with SetLink().
  2485  func (f *Fpdf) AddLink() int {
  2486  	f.links = append(f.links, intLinkType{})
  2487  	return len(f.links) - 1
  2488  }
  2489  
  2490  // SetLink defines the page and position a link points to. See AddLink().
  2491  func (f *Fpdf) SetLink(link int, y float64, page int) {
  2492  	if y == -1 {
  2493  		y = f.y
  2494  	}
  2495  	if page == -1 {
  2496  		page = f.page
  2497  	}
  2498  	f.links[link] = intLinkType{page, y}
  2499  }
  2500  
  2501  // newLink adds a new clickable link on current page
  2502  func (f *Fpdf) newLink(x, y, w, h float64, link int, linkStr string) {
  2503  	// linkList, ok := f.pageLinks[f.page]
  2504  	// if !ok {
  2505  	// linkList = make([]linkType, 0, 8)
  2506  	// f.pageLinks[f.page] = linkList
  2507  	// }
  2508  	f.pageLinks[f.page] = append(f.pageLinks[f.page],
  2509  		linkType{x * f.k, f.hPt - y*f.k, w * f.k, h * f.k, link, linkStr})
  2510  }
  2511  
  2512  // Link puts a link on a rectangular area of the page. Text or image links are
  2513  // generally put via Cell(), Write() or Image(), but this method can be useful
  2514  // for instance to define a clickable area inside an image. link is the value
  2515  // returned by AddLink().
  2516  func (f *Fpdf) Link(x, y, w, h float64, link int) {
  2517  	f.newLink(x, y, w, h, link, "")
  2518  }
  2519  
  2520  // LinkString puts a link on a rectangular area of the page. Text or image
  2521  // links are generally put via Cell(), Write() or Image(), but this method can
  2522  // be useful for instance to define a clickable area inside an image. linkStr
  2523  // is the target URL.
  2524  func (f *Fpdf) LinkString(x, y, w, h float64, linkStr string) {
  2525  	f.newLink(x, y, w, h, 0, linkStr)
  2526  }
  2527  
  2528  // Bookmark sets a bookmark that will be displayed in a sidebar outline. txtStr
  2529  // is the title of the bookmark. level specifies the level of the bookmark in
  2530  // the outline; 0 is the top level, 1 is just below, and so on. y specifies the
  2531  // vertical position of the bookmark destination in the current page; -1
  2532  // indicates the current position.
  2533  func (f *Fpdf) Bookmark(txtStr string, level int, y float64) {
  2534  	if y == -1 {
  2535  		y = f.y
  2536  	}
  2537  	if f.isCurrentUTF8 {
  2538  		txtStr = utf8toutf16(txtStr)
  2539  	}
  2540  	f.outlines = append(f.outlines, outlineType{text: txtStr, level: level, y: y, p: f.PageNo(), prev: -1, last: -1, next: -1, first: -1})
  2541  }
  2542  
  2543  // Text prints a character string. The origin (x, y) is on the left of the
  2544  // first character at the baseline. This method permits a string to be placed
  2545  // precisely on the page, but it is usually easier to use Cell(), MultiCell()
  2546  // or Write() which are the standard methods to print text.
  2547  func (f *Fpdf) Text(x, y float64, txtStr string) {
  2548  	var txt2 string
  2549  	if f.isCurrentUTF8 {
  2550  		if f.isRTL {
  2551  			txtStr = reverseText(txtStr)
  2552  			x -= f.GetStringWidth(txtStr)
  2553  		}
  2554  		txt2 = f.escape(utf8toutf16(txtStr, false))
  2555  		for _, uni := range txtStr {
  2556  			f.currentFont.usedRunes[int(uni)] = int(uni)
  2557  		}
  2558  	} else {
  2559  		txt2 = f.escape(txtStr)
  2560  	}
  2561  	s := sprintf("BT %.2f %.2f Td (%s) Tj ET", x*f.k, (f.h-y)*f.k, txt2)
  2562  	if f.underline && txtStr != "" {
  2563  		s += " " + f.dounderline(x, y, txtStr)
  2564  	}
  2565  	if f.strikeout && txtStr != "" {
  2566  		s += " " + f.dostrikeout(x, y, txtStr)
  2567  	}
  2568  	if f.colorFlag {
  2569  		s = sprintf("q %s %s Q", f.color.text.str, s)
  2570  	}
  2571  	f.out(s)
  2572  }
  2573  
  2574  // GetWordSpacing returns the spacing between words of following text.
  2575  func (f *Fpdf) GetWordSpacing() float64 {
  2576  	return f.ws
  2577  }
  2578  
  2579  // SetWordSpacing sets spacing between words of following text. See the
  2580  // WriteAligned() example for a demonstration of its use.
  2581  func (f *Fpdf) SetWordSpacing(space float64) {
  2582  	f.ws = space
  2583  	f.out(sprintf("%.5f Tw", space*f.k))
  2584  }
  2585  
  2586  // SetTextRenderingMode sets the rendering mode of following text.
  2587  // The mode can be as follows:
  2588  // 0: Fill text
  2589  // 1: Stroke text
  2590  // 2: Fill, then stroke text
  2591  // 3: Neither fill nor stroke text (invisible)
  2592  // 4: Fill text and add to path for clipping
  2593  // 5: Stroke text and add to path for clipping
  2594  // 6: Fills then stroke text and add to path for clipping
  2595  // 7: Add text to path for clipping
  2596  // This method is demonstrated in the SetTextRenderingMode example.
  2597  func (f *Fpdf) SetTextRenderingMode(mode int) {
  2598  	if mode >= 0 && mode <= 7 {
  2599  		f.out(sprintf("%d Tr", mode))
  2600  	}
  2601  }
  2602  
  2603  // SetAcceptPageBreakFunc allows the application to control where page breaks
  2604  // occur.
  2605  //
  2606  // fnc is an application function (typically a closure) that is called by the
  2607  // library whenever a page break condition is met. The break is issued if true
  2608  // is returned. The default implementation returns a value according to the
  2609  // mode selected by SetAutoPageBreak. The function provided should not be
  2610  // called by the application.
  2611  //
  2612  // See the example for SetLeftMargin() to see how this function can be used to
  2613  // manage multiple columns.
  2614  func (f *Fpdf) SetAcceptPageBreakFunc(fnc func() bool) {
  2615  	f.acceptPageBreak = fnc
  2616  }
  2617  
  2618  // CellFormat prints a rectangular cell with optional borders, background color
  2619  // and character string. The upper-left corner of the cell corresponds to the
  2620  // current position. The text can be aligned or centered. After the call, the
  2621  // current position moves to the right or to the next line. It is possible to
  2622  // put a link on the text.
  2623  //
  2624  // An error will be returned if a call to SetFont() has not already taken
  2625  // place before this method is called.
  2626  //
  2627  // If automatic page breaking is enabled and the cell goes beyond the limit, a
  2628  // page break is done before outputting.
  2629  //
  2630  // w and h specify the width and height of the cell. If w is 0, the cell
  2631  // extends up to the right margin. Specifying 0 for h will result in no output,
  2632  // but the current position will be advanced by w.
  2633  //
  2634  // txtStr specifies the text to display.
  2635  //
  2636  // borderStr specifies how the cell border will be drawn. An empty string
  2637  // indicates no border, "1" indicates a full border, and one or more of "L",
  2638  // "T", "R" and "B" indicate the left, top, right and bottom sides of the
  2639  // border.
  2640  //
  2641  // ln indicates where the current position should go after the call. Possible
  2642  // values are 0 (to the right), 1 (to the beginning of the next line), and 2
  2643  // (below). Putting 1 is equivalent to putting 0 and calling Ln() just after.
  2644  //
  2645  // alignStr specifies how the text is to be positioned within the cell.
  2646  // Horizontal alignment is controlled by including "L", "C" or "R" (left,
  2647  // center, right) in alignStr. Vertical alignment is controlled by including
  2648  // "T", "M", "B" or "A" (top, middle, bottom, baseline) in alignStr. The default
  2649  // alignment is left middle.
  2650  //
  2651  // fill is true to paint the cell background or false to leave it transparent.
  2652  //
  2653  // link is the identifier returned by AddLink() or 0 for no internal link.
  2654  //
  2655  // linkStr is a target URL or empty for no external link. A non--zero value for
  2656  // link takes precedence over linkStr.
  2657  func (f *Fpdf) CellFormat(w, h float64, txtStr, borderStr string, ln int,
  2658  	alignStr string, fill bool, link int, linkStr string) {
  2659  	// dbg("CellFormat. h = %.2f, borderStr = %s", h, borderStr)
  2660  	if f.err != nil {
  2661  		return
  2662  	}
  2663  
  2664  	if f.currentFont.Name == "" {
  2665  		f.err = fmt.Errorf("font has not been set; unable to render text")
  2666  		return
  2667  	}
  2668  
  2669  	borderStr = strings.ToUpper(borderStr)
  2670  	k := f.k
  2671  	if f.y+h > f.pageBreakTrigger && !f.inHeader && !f.inFooter && f.acceptPageBreak() {
  2672  		// Automatic page break
  2673  		x := f.x
  2674  		ws := f.ws
  2675  		// dbg("auto page break, x %.2f, ws %.2f", x, ws)
  2676  		if ws > 0 {
  2677  			f.ws = 0
  2678  			f.out("0 Tw")
  2679  		}
  2680  		f.AddPageFormat(f.curOrientation, f.curPageSize)
  2681  		if f.err != nil {
  2682  			return
  2683  		}
  2684  		f.x = x
  2685  		if ws > 0 {
  2686  			f.ws = ws
  2687  			// f.outf("%.3f Tw", ws*k)
  2688  			f.putF64(ws*k, 3)
  2689  			f.put(" Tw\n")
  2690  		}
  2691  	}
  2692  	if w == 0 {
  2693  		w = f.w - f.rMargin - f.x
  2694  	}
  2695  	var s fmtBuffer
  2696  	if h > 0 && (fill || borderStr == "1") {
  2697  		var op string
  2698  		if fill {
  2699  			if borderStr == "1" {
  2700  				op = "B"
  2701  				// dbg("border is '1', fill")
  2702  			} else {
  2703  				op = "f"
  2704  				// dbg("border is empty, fill")
  2705  			}
  2706  		} else {
  2707  			// dbg("border is '1', no fill")
  2708  			op = "S"
  2709  		}
  2710  		/// dbg("(CellFormat) f.x %.2f f.k %.2f", f.x, f.k)
  2711  		s.printf("%.2f %.2f %.2f %.2f re %s ", f.x*k, (f.h-f.y)*k, w*k, -h*k, op)
  2712  	}
  2713  	if len(borderStr) > 0 && borderStr != "1" {
  2714  		// fmt.Printf("border is '%s', no fill\n", borderStr)
  2715  		x := f.x
  2716  		y := f.y
  2717  		left := x * k
  2718  		top := (f.h - y) * k
  2719  		right := (x + w) * k
  2720  		bottom := (f.h - (y + h)) * k
  2721  		if strings.Contains(borderStr, "L") {
  2722  			s.printf("%.2f %.2f m %.2f %.2f l S ", left, top, left, bottom)
  2723  		}
  2724  		if strings.Contains(borderStr, "T") {
  2725  			s.printf("%.2f %.2f m %.2f %.2f l S ", left, top, right, top)
  2726  		}
  2727  		if strings.Contains(borderStr, "R") {
  2728  			s.printf("%.2f %.2f m %.2f %.2f l S ", right, top, right, bottom)
  2729  		}
  2730  		if strings.Contains(borderStr, "B") {
  2731  			s.printf("%.2f %.2f m %.2f %.2f l S ", left, bottom, right, bottom)
  2732  		}
  2733  	}
  2734  	if len(txtStr) > 0 {
  2735  		var dx, dy float64
  2736  		// Horizontal alignment
  2737  		switch {
  2738  		case strings.Contains(alignStr, "R"):
  2739  			dx = w - f.cMargin - f.GetStringWidth(txtStr)
  2740  		case strings.Contains(alignStr, "C"):
  2741  			dx = (w - f.GetStringWidth(txtStr)) / 2
  2742  		default:
  2743  			dx = f.cMargin
  2744  		}
  2745  
  2746  		// Vertical alignment
  2747  		switch {
  2748  		case strings.Contains(alignStr, "T"):
  2749  			dy = (f.fontSize - h) / 2.0
  2750  		case strings.Contains(alignStr, "B"):
  2751  			dy = (h - f.fontSize) / 2.0
  2752  		case strings.Contains(alignStr, "A"):
  2753  			var descent float64
  2754  			d := f.currentFont.Desc
  2755  			if d.Descent == 0 {
  2756  				// not defined (standard font?), use average of 19%
  2757  				descent = -0.19 * f.fontSize
  2758  			} else {
  2759  				descent = float64(d.Descent) * f.fontSize / float64(d.Ascent-d.Descent)
  2760  			}
  2761  			dy = (h-f.fontSize)/2.0 - descent
  2762  		default:
  2763  			dy = 0
  2764  		}
  2765  		if f.colorFlag {
  2766  			s.printf("q %s ", f.color.text.str)
  2767  		}
  2768  		//If multibyte, Tw has no effect - do word spacing using an adjustment before each space
  2769  		if (f.ws != 0 || alignStr == "J") && f.isCurrentUTF8 { // && f.ws != 0
  2770  			if f.isRTL {
  2771  				txtStr = reverseText(txtStr)
  2772  			}
  2773  			wmax := int(math.Ceil((w - 2*f.cMargin) * 1000 / f.fontSize))
  2774  			for _, uni := range txtStr {
  2775  				f.currentFont.usedRunes[int(uni)] = int(uni)
  2776  			}
  2777  			space := f.escape(utf8toutf16(" ", false))
  2778  			strSize := f.GetStringSymbolWidth(txtStr)
  2779  			s.printf("BT 0 Tw %.2f %.2f Td [", (f.x+dx)*k, (f.h-(f.y+.5*h+.3*f.fontSize))*k)
  2780  			t := strings.Split(txtStr, " ")
  2781  			shift := float64((wmax - strSize)) / float64(len(t)-1)
  2782  			numt := len(t)
  2783  			for i := 0; i < numt; i++ {
  2784  				tx := t[i]
  2785  				tx = "(" + f.escape(utf8toutf16(tx, false)) + ")"
  2786  				s.printf("%s ", tx)
  2787  				if (i + 1) < numt {
  2788  					s.printf("%.3f(%s) ", -shift, space)
  2789  				}
  2790  			}
  2791  			s.printf("] TJ ET")
  2792  		} else {
  2793  			var txt2 string
  2794  			if f.isCurrentUTF8 {
  2795  				if f.isRTL {
  2796  					txtStr = reverseText(txtStr)
  2797  				}
  2798  				txt2 = f.escape(utf8toutf16(txtStr, false))
  2799  				for _, uni := range txtStr {
  2800  					f.currentFont.usedRunes[int(uni)] = int(uni)
  2801  				}
  2802  			} else {
  2803  
  2804  				txt2 = strings.Replace(txtStr, "\\", "\\\\", -1)
  2805  				txt2 = strings.Replace(txt2, "(", "\\(", -1)
  2806  				txt2 = strings.Replace(txt2, ")", "\\)", -1)
  2807  			}
  2808  			bt := (f.x + dx) * k
  2809  			td := (f.h - (f.y + dy + .5*h + .3*f.fontSize)) * k
  2810  			s.printf("BT %.2f %.2f Td (%s)Tj ET", bt, td, txt2)
  2811  			//BT %.2F %.2F Td (%s) Tj ET',(f.x+dx)*k,(f.h-(f.y+.5*h+.3*f.FontSize))*k,txt2);
  2812  		}
  2813  
  2814  		if f.underline {
  2815  			s.printf(" %s", f.dounderline(f.x+dx, f.y+dy+.5*h+.3*f.fontSize, txtStr))
  2816  		}
  2817  		if f.strikeout {
  2818  			s.printf(" %s", f.dostrikeout(f.x+dx, f.y+dy+.5*h+.3*f.fontSize, txtStr))
  2819  		}
  2820  		if f.colorFlag {
  2821  			s.printf(" Q")
  2822  		}
  2823  		if link > 0 || len(linkStr) > 0 {
  2824  			f.newLink(f.x+dx, f.y+dy+.5*h-.5*f.fontSize, f.GetStringWidth(txtStr), f.fontSize, link, linkStr)
  2825  		}
  2826  	}
  2827  	str := s.String()
  2828  	if len(str) > 0 {
  2829  		f.out(str)
  2830  	}
  2831  	f.lasth = h
  2832  	if ln > 0 {
  2833  		// Go to next line
  2834  		f.y += h
  2835  		if ln == 1 {
  2836  			f.x = f.lMargin
  2837  		}
  2838  	} else {
  2839  		f.x += w
  2840  	}
  2841  }
  2842  
  2843  // Revert string to use in RTL languages
  2844  func reverseText(text string) string {
  2845  	oldText := []rune(text)
  2846  	newText := make([]rune, len(oldText))
  2847  	length := len(oldText) - 1
  2848  	for i, r := range oldText {
  2849  		newText[length-i] = r
  2850  	}
  2851  	return string(newText)
  2852  }
  2853  
  2854  // Cell is a simpler version of CellFormat with no fill, border, links or
  2855  // special alignment. The Cell_strikeout() example demonstrates this method.
  2856  func (f *Fpdf) Cell(w, h float64, txtStr string) {
  2857  	f.CellFormat(w, h, txtStr, "", 0, "L", false, 0, "")
  2858  }
  2859  
  2860  // Cellf is a simpler printf-style version of CellFormat with no fill, border,
  2861  // links or special alignment. See documentation for the fmt package for
  2862  // details on fmtStr and args.
  2863  func (f *Fpdf) Cellf(w, h float64, fmtStr string, args ...interface{}) {
  2864  	f.CellFormat(w, h, sprintf(fmtStr, args...), "", 0, "L", false, 0, "")
  2865  }
  2866  
  2867  // SplitLines splits text into several lines using the current font. Each line
  2868  // has its length limited to a maximum width given by w. This function can be
  2869  // used to determine the total height of wrapped text for vertical placement
  2870  // purposes.
  2871  //
  2872  // This method is useful for codepage-based fonts only. For UTF-8 encoded text,
  2873  // use SplitText().
  2874  //
  2875  // You can use MultiCell if you want to print a text on several lines in a
  2876  // simple way.
  2877  func (f *Fpdf) SplitLines(txt []byte, w float64) [][]byte {
  2878  	// Function contributed by Bruno Michel
  2879  	lines := [][]byte{}
  2880  	cw := f.currentFont.Cw
  2881  	wmax := int(math.Ceil((w - 2*f.cMargin) * 1000 / f.fontSize))
  2882  	s := bytes.Replace(txt, []byte("\r"), []byte{}, -1)
  2883  	nb := len(s)
  2884  	for nb > 0 && s[nb-1] == '\n' {
  2885  		nb--
  2886  	}
  2887  	s = s[0:nb]
  2888  	sep := -1
  2889  	i := 0
  2890  	j := 0
  2891  	l := 0
  2892  	for i < nb {
  2893  		c := s[i]
  2894  		l += cw[c]
  2895  		if c == ' ' || c == '\t' || c == '\n' {
  2896  			sep = i
  2897  		}
  2898  		if c == '\n' || l > wmax {
  2899  			if sep == -1 {
  2900  				if i == j {
  2901  					i++
  2902  				}
  2903  				sep = i
  2904  			} else {
  2905  				i = sep + 1
  2906  			}
  2907  			lines = append(lines, s[j:sep])
  2908  			sep = -1
  2909  			j = i
  2910  			l = 0
  2911  		} else {
  2912  			i++
  2913  		}
  2914  	}
  2915  	if i != j {
  2916  		lines = append(lines, s[j:i])
  2917  	}
  2918  	return lines
  2919  }
  2920  
  2921  // MultiCell supports printing text with line breaks. They can be automatic (as
  2922  // soon as the text reaches the right border of the cell) or explicit (via the
  2923  // \n character). As many cells as necessary are output, one below the other.
  2924  //
  2925  // Text can be aligned, centered or justified. The cell block can be framed and
  2926  // the background painted. See CellFormat() for more details.
  2927  //
  2928  // The current position after calling MultiCell() is the beginning of the next
  2929  // line, equivalent to calling CellFormat with ln equal to 1.
  2930  //
  2931  // w is the width of the cells. A value of zero indicates cells that reach to
  2932  // the right margin.
  2933  //
  2934  // h indicates the line height of each cell in the unit of measure specified in New().
  2935  //
  2936  // Note: this method has a known bug that treats UTF-8 fonts differently than
  2937  // non-UTF-8 fonts. With UTF-8 fonts, all trailing newlines in txtStr are
  2938  // removed. With a non-UTF-8 font, if txtStr has one or more trailing newlines,
  2939  // only the last is removed. In the next major module version, the UTF-8 logic
  2940  // will be changed to match the non-UTF-8 logic. To prepare for that change,
  2941  // applications that use UTF-8 fonts and depend on having all trailing newlines
  2942  // removed should call strings.TrimRight(txtStr, "\r\n") before calling this
  2943  // method.
  2944  func (f *Fpdf) MultiCell(w, h float64, txtStr, borderStr, alignStr string, fill bool) {
  2945  	if f.err != nil {
  2946  		return
  2947  	}
  2948  	// dbg("MultiCell")
  2949  	if alignStr == "" {
  2950  		alignStr = "J"
  2951  	}
  2952  	cw := f.currentFont.Cw
  2953  	if w == 0 {
  2954  		w = f.w - f.rMargin - f.x
  2955  	}
  2956  	wmax := int(math.Ceil((w - 2*f.cMargin) * 1000 / f.fontSize))
  2957  	s := strings.Replace(txtStr, "\r", "", -1)
  2958  	srune := []rune(s)
  2959  
  2960  	// remove extra line breaks
  2961  	var nb int
  2962  	if f.isCurrentUTF8 {
  2963  		nb = len(srune)
  2964  		for nb > 0 && srune[nb-1] == '\n' {
  2965  			nb--
  2966  		}
  2967  		srune = srune[0:nb]
  2968  	} else {
  2969  		nb = len(s)
  2970  		bytes2 := []byte(s)
  2971  
  2972  		// for nb > 0 && bytes2[nb-1] == '\n' {
  2973  
  2974  		// Prior to August 2019, if s ended with a newline, this code stripped it.
  2975  		// After that date, to be compatible with the UTF-8 code above, *all*
  2976  		// trailing newlines were removed. Because this regression caused at least
  2977  		// one application to break (see issue #333), the original behavior has been
  2978  		// reinstated with a caveat included in the documentation.
  2979  		if nb > 0 && bytes2[nb-1] == '\n' {
  2980  			nb--
  2981  		}
  2982  		s = s[0:nb]
  2983  	}
  2984  	// dbg("[%s]\n", s)
  2985  	var b, b2 string
  2986  	b = "0"
  2987  	if len(borderStr) > 0 {
  2988  		if borderStr == "1" {
  2989  			borderStr = "LTRB"
  2990  			b = "LRT"
  2991  			b2 = "LR"
  2992  		} else {
  2993  			b2 = ""
  2994  			if strings.Contains(borderStr, "L") {
  2995  				b2 += "L"
  2996  			}
  2997  			if strings.Contains(borderStr, "R") {
  2998  				b2 += "R"
  2999  			}
  3000  			if strings.Contains(borderStr, "T") {
  3001  				b = b2 + "T"
  3002  			} else {
  3003  				b = b2
  3004  			}
  3005  		}
  3006  	}
  3007  	sep := -1
  3008  	i := 0
  3009  	j := 0
  3010  	l := 0
  3011  	ls := 0
  3012  	ns := 0
  3013  	nl := 1
  3014  	for i < nb {
  3015  		// Get next character
  3016  		var c rune
  3017  		if f.isCurrentUTF8 {
  3018  			c = srune[i]
  3019  		} else {
  3020  			c = rune(s[i])
  3021  		}
  3022  		if c == '\n' {
  3023  			// Explicit line break
  3024  			if f.ws > 0 {
  3025  				f.ws = 0
  3026  				f.out("0 Tw")
  3027  			}
  3028  
  3029  			if f.isCurrentUTF8 {
  3030  				newAlignStr := alignStr
  3031  				if newAlignStr == "J" {
  3032  					if f.isRTL {
  3033  						newAlignStr = "R"
  3034  					} else {
  3035  						newAlignStr = "L"
  3036  					}
  3037  				}
  3038  				f.CellFormat(w, h, string(srune[j:i]), b, 2, newAlignStr, fill, 0, "")
  3039  			} else {
  3040  				f.CellFormat(w, h, s[j:i], b, 2, alignStr, fill, 0, "")
  3041  			}
  3042  			i++
  3043  			sep = -1
  3044  			j = i
  3045  			l = 0
  3046  			ns = 0
  3047  			nl++
  3048  			if len(borderStr) > 0 && nl == 2 {
  3049  				b = b2
  3050  			}
  3051  			continue
  3052  		}
  3053  		if c == ' ' || isChinese(c) {
  3054  			sep = i
  3055  			ls = l
  3056  			ns++
  3057  		}
  3058  		if int(c) >= len(cw) {
  3059  			f.err = fmt.Errorf("character outside the supported range: %s", string(c))
  3060  			return
  3061  		}
  3062  		if cw[int(c)] == 0 { //Marker width 0 used for missing symbols
  3063  			l += f.currentFont.Desc.MissingWidth
  3064  		} else if cw[int(c)] != 65535 { //Marker width 65535 used for zero width symbols
  3065  			l += cw[int(c)]
  3066  		}
  3067  		if l > wmax {
  3068  			// Automatic line break
  3069  			if sep == -1 {
  3070  				if i == j {
  3071  					i++
  3072  				}
  3073  				if f.ws > 0 {
  3074  					f.ws = 0
  3075  					f.out("0 Tw")
  3076  				}
  3077  				if f.isCurrentUTF8 {
  3078  					f.CellFormat(w, h, string(srune[j:i]), b, 2, alignStr, fill, 0, "")
  3079  				} else {
  3080  					f.CellFormat(w, h, s[j:i], b, 2, alignStr, fill, 0, "")
  3081  				}
  3082  			} else {
  3083  				if alignStr == "J" {
  3084  					if ns > 1 {
  3085  						f.ws = float64((wmax-ls)/1000) * f.fontSize / float64(ns-1)
  3086  					} else {
  3087  						f.ws = 0
  3088  					}
  3089  					// f.outf("%.3f Tw", f.ws*f.k)
  3090  					f.putF64(f.ws*f.k, 3)
  3091  					f.put(" Tw\n")
  3092  				}
  3093  				if f.isCurrentUTF8 {
  3094  					f.CellFormat(w, h, string(srune[j:sep]), b, 2, alignStr, fill, 0, "")
  3095  				} else {
  3096  					f.CellFormat(w, h, s[j:sep], b, 2, alignStr, fill, 0, "")
  3097  				}
  3098  				i = sep + 1
  3099  			}
  3100  			sep = -1
  3101  			j = i
  3102  			l = 0
  3103  			ns = 0
  3104  			nl++
  3105  			if len(borderStr) > 0 && nl == 2 {
  3106  				b = b2
  3107  			}
  3108  		} else {
  3109  			i++
  3110  		}
  3111  	}
  3112  	// Last chunk
  3113  	if f.ws > 0 {
  3114  		f.ws = 0
  3115  		f.out("0 Tw")
  3116  	}
  3117  	if len(borderStr) > 0 && strings.Contains(borderStr, "B") {
  3118  		b += "B"
  3119  	}
  3120  	if f.isCurrentUTF8 {
  3121  		if alignStr == "J" {
  3122  			if f.isRTL {
  3123  				alignStr = "R"
  3124  			} else {
  3125  				alignStr = ""
  3126  			}
  3127  		}
  3128  		f.CellFormat(w, h, string(srune[j:i]), b, 2, alignStr, fill, 0, "")
  3129  	} else {
  3130  		f.CellFormat(w, h, s[j:i], b, 2, alignStr, fill, 0, "")
  3131  	}
  3132  	f.x = f.lMargin
  3133  }
  3134  
  3135  // write outputs text in flowing mode
  3136  func (f *Fpdf) write(h float64, txtStr string, link int, linkStr string) {
  3137  	// dbg("Write")
  3138  	cw := f.currentFont.Cw
  3139  	w := f.w - f.rMargin - f.x
  3140  	wmax := (w - 2*f.cMargin) * 1000 / f.fontSize
  3141  	s := strings.Replace(txtStr, "\r", "", -1)
  3142  	var nb int
  3143  	if f.isCurrentUTF8 {
  3144  		nb = len([]rune(s))
  3145  		if nb == 1 && s == " " {
  3146  			f.x += f.GetStringWidth(s)
  3147  			return
  3148  		}
  3149  	} else {
  3150  		nb = len(s)
  3151  	}
  3152  	sep := -1
  3153  	i := 0
  3154  	j := 0
  3155  	l := 0.0
  3156  	nl := 1
  3157  	for i < nb {
  3158  		// Get next character
  3159  		var c rune
  3160  		if f.isCurrentUTF8 {
  3161  			c = []rune(s)[i]
  3162  		} else {
  3163  			c = rune(byte(s[i]))
  3164  		}
  3165  		if c == '\n' {
  3166  			// Explicit line break
  3167  			if f.isCurrentUTF8 {
  3168  				f.CellFormat(w, h, string([]rune(s)[j:i]), "", 2, "", false, link, linkStr)
  3169  			} else {
  3170  				f.CellFormat(w, h, s[j:i], "", 2, "", false, link, linkStr)
  3171  			}
  3172  			i++
  3173  			sep = -1
  3174  			j = i
  3175  			l = 0.0
  3176  			if nl == 1 {
  3177  				f.x = f.lMargin
  3178  				w = f.w - f.rMargin - f.x
  3179  				wmax = (w - 2*f.cMargin) * 1000 / f.fontSize
  3180  			}
  3181  			nl++
  3182  			continue
  3183  		}
  3184  		if c == ' ' {
  3185  			sep = i
  3186  		}
  3187  		l += float64(cw[int(c)])
  3188  		if l > wmax {
  3189  			// Automatic line break
  3190  			if sep == -1 {
  3191  				if f.x > f.lMargin {
  3192  					// Move to next line
  3193  					f.x = f.lMargin
  3194  					f.y += h
  3195  					w = f.w - f.rMargin - f.x
  3196  					wmax = (w - 2*f.cMargin) * 1000 / f.fontSize
  3197  					i++
  3198  					nl++
  3199  					continue
  3200  				}
  3201  				if i == j {
  3202  					i++
  3203  				}
  3204  				if f.isCurrentUTF8 {
  3205  					f.CellFormat(w, h, string([]rune(s)[j:i]), "", 2, "", false, link, linkStr)
  3206  				} else {
  3207  					f.CellFormat(w, h, s[j:i], "", 2, "", false, link, linkStr)
  3208  				}
  3209  			} else {
  3210  				if f.isCurrentUTF8 {
  3211  					f.CellFormat(w, h, string([]rune(s)[j:sep]), "", 2, "", false, link, linkStr)
  3212  				} else {
  3213  					f.CellFormat(w, h, s[j:sep], "", 2, "", false, link, linkStr)
  3214  				}
  3215  				i = sep + 1
  3216  			}
  3217  			sep = -1
  3218  			j = i
  3219  			l = 0.0
  3220  			if nl == 1 {
  3221  				f.x = f.lMargin
  3222  				w = f.w - f.rMargin - f.x
  3223  				wmax = (w - 2*f.cMargin) * 1000 / f.fontSize
  3224  			}
  3225  			nl++
  3226  		} else {
  3227  			i++
  3228  		}
  3229  	}
  3230  	// Last chunk
  3231  	if i != j {
  3232  		if f.isCurrentUTF8 {
  3233  			f.CellFormat(l/1000*f.fontSize, h, string([]rune(s)[j:]), "", 0, "", false, link, linkStr)
  3234  		} else {
  3235  			f.CellFormat(l/1000*f.fontSize, h, s[j:], "", 0, "", false, link, linkStr)
  3236  		}
  3237  	}
  3238  }
  3239  
  3240  // Write prints text from the current position. When the right margin is
  3241  // reached (or the \n character is met) a line break occurs and text continues
  3242  // from the left margin. Upon method exit, the current position is left just at
  3243  // the end of the text.
  3244  //
  3245  // It is possible to put a link on the text.
  3246  //
  3247  // h indicates the line height in the unit of measure specified in New().
  3248  func (f *Fpdf) Write(h float64, txtStr string) {
  3249  	f.write(h, txtStr, 0, "")
  3250  }
  3251  
  3252  // Writef is like Write but uses printf-style formatting. See the documentation
  3253  // for package fmt for more details on fmtStr and args.
  3254  func (f *Fpdf) Writef(h float64, fmtStr string, args ...interface{}) {
  3255  	f.write(h, sprintf(fmtStr, args...), 0, "")
  3256  }
  3257  
  3258  // WriteLinkString writes text that when clicked launches an external URL. See
  3259  // Write() for argument details.
  3260  func (f *Fpdf) WriteLinkString(h float64, displayStr, targetStr string) {
  3261  	f.write(h, displayStr, 0, targetStr)
  3262  }
  3263  
  3264  // WriteLinkID writes text that when clicked jumps to another location in the
  3265  // PDF. linkID is an identifier returned by AddLink(). See Write() for argument
  3266  // details.
  3267  func (f *Fpdf) WriteLinkID(h float64, displayStr string, linkID int) {
  3268  	f.write(h, displayStr, linkID, "")
  3269  }
  3270  
  3271  // WriteAligned is an implementation of Write that makes it possible to align
  3272  // text.
  3273  //
  3274  // width indicates the width of the box the text will be drawn in. This is in
  3275  // the unit of measure specified in New(). If it is set to 0, the bounding box
  3276  // of the page will be taken (pageWidth - leftMargin - rightMargin).
  3277  //
  3278  // lineHeight indicates the line height in the unit of measure specified in
  3279  // New().
  3280  //
  3281  // alignStr sees to horizontal alignment of the given textStr. The options are
  3282  // "L", "C" and "R" (Left, Center, Right). The default is "L".
  3283  func (f *Fpdf) WriteAligned(width, lineHeight float64, textStr, alignStr string) {
  3284  	lMargin, _, rMargin, _ := f.GetMargins()
  3285  
  3286  	pageWidth, _ := f.GetPageSize()
  3287  	if width == 0 {
  3288  		width = pageWidth - (lMargin + rMargin)
  3289  	}
  3290  
  3291  	var lines []string
  3292  
  3293  	if f.isCurrentUTF8 {
  3294  		lines = f.SplitText(textStr, width)
  3295  	} else {
  3296  		for _, line := range f.SplitLines([]byte(textStr), width) {
  3297  			lines = append(lines, string(line))
  3298  		}
  3299  	}
  3300  
  3301  	for _, lineBt := range lines {
  3302  		lineStr := string(lineBt)
  3303  		lineWidth := f.GetStringWidth(lineStr)
  3304  
  3305  		switch alignStr {
  3306  		case "C":
  3307  			f.SetLeftMargin(lMargin + ((width - lineWidth) / 2))
  3308  			f.Write(lineHeight, lineStr)
  3309  			f.SetLeftMargin(lMargin)
  3310  		case "R":
  3311  			f.SetLeftMargin(lMargin + (width - lineWidth) - 2.01*f.cMargin)
  3312  			f.Write(lineHeight, lineStr)
  3313  			f.SetLeftMargin(lMargin)
  3314  		default:
  3315  			f.SetRightMargin(pageWidth - lMargin - width)
  3316  			f.Write(lineHeight, lineStr)
  3317  			f.SetRightMargin(rMargin)
  3318  		}
  3319  	}
  3320  }
  3321  
  3322  // Ln performs a line break. The current abscissa goes back to the left margin
  3323  // and the ordinate increases by the amount passed in parameter. A negative
  3324  // value of h indicates the height of the last printed cell.
  3325  //
  3326  // This method is demonstrated in the example for MultiCell.
  3327  func (f *Fpdf) Ln(h float64) {
  3328  	f.x = f.lMargin
  3329  	if h < 0 {
  3330  		f.y += f.lasth
  3331  	} else {
  3332  		f.y += h
  3333  	}
  3334  }
  3335  
  3336  // ImageTypeFromMime returns the image type used in various image-related
  3337  // functions (for example, Image()) that is associated with the specified MIME
  3338  // type. For example, "jpg" is returned if mimeStr is "image/jpeg". An error is
  3339  // set if the specified MIME type is not supported.
  3340  func (f *Fpdf) ImageTypeFromMime(mimeStr string) (tp string) {
  3341  	switch mimeStr {
  3342  	case "image/png":
  3343  		tp = "png"
  3344  	case "image/jpg":
  3345  		tp = "jpg"
  3346  	case "image/jpeg":
  3347  		tp = "jpg"
  3348  	case "image/gif":
  3349  		tp = "gif"
  3350  	default:
  3351  		f.SetErrorf("unsupported image type: %s", mimeStr)
  3352  	}
  3353  	return
  3354  }
  3355  
  3356  func (f *Fpdf) imageOut(info *ImageInfoType, x, y, w, h float64, allowNegativeX, flow bool, link int, linkStr string) {
  3357  	// Automatic width and height calculation if needed
  3358  	if w == 0 && h == 0 {
  3359  		// Put image at 96 dpi
  3360  		w = -96
  3361  		h = -96
  3362  	}
  3363  	if w == -1 {
  3364  		// Set image width to whatever value for dpi we read
  3365  		// from the image or that was set manually
  3366  		w = -info.dpi
  3367  	}
  3368  	if h == -1 {
  3369  		// Set image height to whatever value for dpi we read
  3370  		// from the image or that was set manually
  3371  		h = -info.dpi
  3372  	}
  3373  	if w < 0 {
  3374  		w = -info.w * 72.0 / w / f.k
  3375  	}
  3376  	if h < 0 {
  3377  		h = -info.h * 72.0 / h / f.k
  3378  	}
  3379  	if w == 0 {
  3380  		w = h * info.w / info.h
  3381  	}
  3382  	if h == 0 {
  3383  		h = w * info.h / info.w
  3384  	}
  3385  	// Flowing mode
  3386  	if flow {
  3387  		if f.y+h > f.pageBreakTrigger && !f.inHeader && !f.inFooter && f.acceptPageBreak() {
  3388  			// Automatic page break
  3389  			x2 := f.x
  3390  			f.AddPageFormat(f.curOrientation, f.curPageSize)
  3391  			if f.err != nil {
  3392  				return
  3393  			}
  3394  			f.x = x2
  3395  		}
  3396  		y = f.y
  3397  		f.y += h
  3398  	}
  3399  	if !allowNegativeX {
  3400  		if x < 0 {
  3401  			x = f.x
  3402  		}
  3403  	}
  3404  	// dbg("h %.2f", h)
  3405  	// q 85.04 0 0 NaN 28.35 NaN cm /I2 Do Q
  3406  	// f.outf("q %.5f 0 0 %.5f %.5f %.5f cm /I%s Do Q", w*f.k, h*f.k, x*f.k, (f.h-(y+h))*f.k, info.i)
  3407  	const prec = 5
  3408  	f.put("q ")
  3409  	f.putF64(w*f.k, prec)
  3410  	f.put(" 0 0 ")
  3411  	f.putF64(h*f.k, prec)
  3412  	f.put(" ")
  3413  	f.putF64(x*f.k, prec)
  3414  	f.put(" ")
  3415  	f.putF64((f.h-(y+h))*f.k, prec)
  3416  	f.put(" cm /I" + info.i + " Do Q\n")
  3417  	if link > 0 || len(linkStr) > 0 {
  3418  		f.newLink(x, y, w, h, link, linkStr)
  3419  	}
  3420  }
  3421  
  3422  // Image puts a JPEG, PNG or GIF image in the current page.
  3423  //
  3424  // Deprecated in favor of ImageOptions -- see that function for
  3425  // details on the behavior of arguments
  3426  func (f *Fpdf) Image(imageNameStr string, x, y, w, h float64, flow bool, tp string, link int, linkStr string) {
  3427  	options := ImageOptions{
  3428  		ReadDpi:   false,
  3429  		ImageType: tp,
  3430  	}
  3431  	f.ImageOptions(imageNameStr, x, y, w, h, flow, options, link, linkStr)
  3432  }
  3433  
  3434  // ImageOptions puts a JPEG, PNG or GIF image in the current page. The size it
  3435  // will take on the page can be specified in different ways. If both w and h
  3436  // are 0, the image is rendered at 96 dpi. If either w or h is zero, it will be
  3437  // calculated from the other dimension so that the aspect ratio is maintained.
  3438  // If w and/or h are -1, the dpi for that dimension will be read from the
  3439  // ImageInfoType object. PNG files can contain dpi information, and if present,
  3440  // this information will be populated in the ImageInfoType object and used in
  3441  // Width, Height, and Extent calculations. Otherwise, the SetDpi function can
  3442  // be used to change the dpi from the default of 72.
  3443  //
  3444  // If w and h are any other negative value, their absolute values
  3445  // indicate their dpi extents.
  3446  //
  3447  // Supported JPEG formats are 24 bit, 32 bit and gray scale. Supported PNG
  3448  // formats are 24 bit, indexed color, and 8 bit indexed gray scale. If a GIF
  3449  // image is animated, only the first frame is rendered. Transparency is
  3450  // supported. It is possible to put a link on the image.
  3451  //
  3452  // imageNameStr may be the name of an image as registered with a call to either
  3453  // RegisterImageReader() or RegisterImage(). In the first case, the image is
  3454  // loaded using an io.Reader. This is generally useful when the image is
  3455  // obtained from some other means than as a disk-based file. In the second
  3456  // case, the image is loaded as a file. Alternatively, imageNameStr may
  3457  // directly specify a sufficiently qualified filename.
  3458  //
  3459  // However the image is loaded, if it is used more than once only one copy is
  3460  // embedded in the file.
  3461  //
  3462  // If x is negative, the current abscissa is used.
  3463  //
  3464  // If flow is true, the current y value is advanced after placing the image and
  3465  // a page break may be made if necessary.
  3466  //
  3467  // If link refers to an internal page anchor (that is, it is non-zero; see
  3468  // AddLink()), the image will be a clickable internal link. Otherwise, if
  3469  // linkStr specifies a URL, the image will be a clickable external link.
  3470  func (f *Fpdf) ImageOptions(imageNameStr string, x, y, w, h float64, flow bool, options ImageOptions, link int, linkStr string) {
  3471  	if f.err != nil {
  3472  		return
  3473  	}
  3474  	info := f.RegisterImageOptions(imageNameStr, options)
  3475  	if f.err != nil {
  3476  		return
  3477  	}
  3478  	f.imageOut(info, x, y, w, h, options.AllowNegativePosition, flow, link, linkStr)
  3479  }
  3480  
  3481  // RegisterImageReader registers an image, reading it from Reader r, adding it
  3482  // to the PDF file but not adding it to the page.
  3483  //
  3484  // This function is now deprecated in favor of RegisterImageOptionsReader
  3485  func (f *Fpdf) RegisterImageReader(imgName, tp string, r io.Reader) (info *ImageInfoType) {
  3486  	options := ImageOptions{
  3487  		ReadDpi:   false,
  3488  		ImageType: tp,
  3489  	}
  3490  	return f.RegisterImageOptionsReader(imgName, options, r)
  3491  }
  3492  
  3493  // ImageOptions provides a place to hang any options we want to use while
  3494  // parsing an image.
  3495  //
  3496  // ImageType's possible values are (case insensitive):
  3497  // "JPG", "JPEG", "PNG" and "GIF". If empty, the type is inferred from
  3498  // the file extension.
  3499  //
  3500  // ReadDpi defines whether to attempt to automatically read the image
  3501  // dpi information from the image file. Normally, this should be set
  3502  // to true (understanding that not all images will have this info
  3503  // available). However, for backwards compatibility with previous
  3504  // versions of the API, it defaults to false.
  3505  //
  3506  // AllowNegativePosition can be set to true in order to prevent the default
  3507  // coercion of negative x values to the current x position.
  3508  type ImageOptions struct {
  3509  	ImageType             string
  3510  	ReadDpi               bool
  3511  	AllowNegativePosition bool
  3512  }
  3513  
  3514  // RegisterImageOptionsReader registers an image, reading it from Reader r, adding it
  3515  // to the PDF file but not adding it to the page. Use Image() with the same
  3516  // name to add the image to the page. Note that tp should be specified in this
  3517  // case.
  3518  //
  3519  // See Image() for restrictions on the image and the options parameters.
  3520  func (f *Fpdf) RegisterImageOptionsReader(imgName string, options ImageOptions, r io.Reader) (info *ImageInfoType) {
  3521  	// Thanks, Ivan Daniluk, for generalizing this code to use the Reader interface.
  3522  	if f.err != nil {
  3523  		return
  3524  	}
  3525  	info, ok := f.images[imgName]
  3526  	if ok {
  3527  		return
  3528  	}
  3529  
  3530  	// First use of this image, get info
  3531  	if options.ImageType == "" {
  3532  		f.err = fmt.Errorf("image type should be specified if reading from custom reader")
  3533  		return
  3534  	}
  3535  	options.ImageType = strings.ToLower(options.ImageType)
  3536  	if options.ImageType == "jpeg" {
  3537  		options.ImageType = "jpg"
  3538  	}
  3539  	switch options.ImageType {
  3540  	case "jpg":
  3541  		info = f.parsejpg(r)
  3542  	case "png":
  3543  		info = f.parsepng(r, options.ReadDpi)
  3544  	case "gif":
  3545  		info = f.parsegif(r)
  3546  	default:
  3547  		f.err = fmt.Errorf("unsupported image type: %s", options.ImageType)
  3548  	}
  3549  	if f.err != nil {
  3550  		return
  3551  	}
  3552  
  3553  	if info.i, f.err = generateImageID(info); f.err != nil {
  3554  		return
  3555  	}
  3556  	f.images[imgName] = info
  3557  
  3558  	return
  3559  }
  3560  
  3561  // RegisterImage registers an image, adding it to the PDF file but not adding
  3562  // it to the page. Use Image() with the same filename to add the image to the
  3563  // page. Note that Image() calls this function, so this function is only
  3564  // necessary if you need information about the image before placing it.
  3565  //
  3566  // This function is now deprecated in favor of RegisterImageOptions.
  3567  // See Image() for restrictions on the image and the "tp" parameters.
  3568  func (f *Fpdf) RegisterImage(fileStr, tp string) (info *ImageInfoType) {
  3569  	options := ImageOptions{
  3570  		ReadDpi:   false,
  3571  		ImageType: tp,
  3572  	}
  3573  	return f.RegisterImageOptions(fileStr, options)
  3574  }
  3575  
  3576  // RegisterImageOptions registers an image, adding it to the PDF file but not
  3577  // adding it to the page. Use Image() with the same filename to add the image
  3578  // to the page. Note that Image() calls this function, so this function is only
  3579  // necessary if you need information about the image before placing it. See
  3580  // Image() for restrictions on the image and the "tp" parameters.
  3581  func (f *Fpdf) RegisterImageOptions(fileStr string, options ImageOptions) (info *ImageInfoType) {
  3582  	info, ok := f.images[fileStr]
  3583  	if ok {
  3584  		return
  3585  	}
  3586  
  3587  	file, err := os.Open(fileStr)
  3588  	if err != nil {
  3589  		f.err = err
  3590  		return
  3591  	}
  3592  	defer file.Close()
  3593  
  3594  	// First use of this image, get info
  3595  	if options.ImageType == "" {
  3596  		pos := strings.LastIndex(fileStr, ".")
  3597  		if pos < 0 {
  3598  			f.err = fmt.Errorf("image file has no extension and no type was specified: %s", fileStr)
  3599  			return
  3600  		}
  3601  		options.ImageType = fileStr[pos+1:]
  3602  	}
  3603  
  3604  	return f.RegisterImageOptionsReader(fileStr, options, file)
  3605  }
  3606  
  3607  // GetImageInfo returns information about the registered image specified by
  3608  // imageStr. If the image has not been registered, nil is returned. The
  3609  // internal error is not modified by this method.
  3610  func (f *Fpdf) GetImageInfo(imageStr string) (info *ImageInfoType) {
  3611  	return f.images[imageStr]
  3612  }
  3613  
  3614  // ImportObjects imports objects from gofpdi into current document
  3615  func (f *Fpdf) ImportObjects(objs map[string][]byte) {
  3616  	for k, v := range objs {
  3617  		f.importedObjs[k] = v
  3618  	}
  3619  }
  3620  
  3621  // ImportObjPos imports object hash positions from gofpdi
  3622  func (f *Fpdf) ImportObjPos(objPos map[string]map[int]string) {
  3623  	for k, v := range objPos {
  3624  		f.importedObjPos[k] = v
  3625  	}
  3626  }
  3627  
  3628  // putImportedTemplates writes the imported template objects to the PDF
  3629  func (f *Fpdf) putImportedTemplates() {
  3630  	nOffset := f.n + 1
  3631  
  3632  	// keep track of list of sha1 hashes (to be replaced with integers)
  3633  	objsIDHash := make([]string, len(f.importedObjs))
  3634  
  3635  	// actual object data with new id
  3636  	objsIDData := make([][]byte, len(f.importedObjs))
  3637  
  3638  	// Populate hash slice and data slice
  3639  	i := 0
  3640  	for k, v := range f.importedObjs {
  3641  		objsIDHash[i] = k
  3642  		objsIDData[i] = v
  3643  
  3644  		i++
  3645  	}
  3646  
  3647  	// Populate a lookup table to get an object id from a hash
  3648  	hashToObjID := make(map[string]int, len(f.importedObjs))
  3649  	for i = 0; i < len(objsIDHash); i++ {
  3650  		hashToObjID[objsIDHash[i]] = i + nOffset
  3651  	}
  3652  
  3653  	// Now, replace hashes inside data with %040d object id
  3654  	for i = 0; i < len(objsIDData); i++ {
  3655  		// get hash
  3656  		hash := objsIDHash[i]
  3657  
  3658  		for pos, h := range f.importedObjPos[hash] {
  3659  			// Convert object id into a 40 character string padded with spaces
  3660  			objIDPadded := fmt.Sprintf("%40s", fmt.Sprintf("%d", hashToObjID[h]))
  3661  
  3662  			// Convert objIDPadded into []byte
  3663  			objIDBytes := []byte(objIDPadded)
  3664  
  3665  			// Replace sha1 hash with object id padded
  3666  			for j := pos; j < pos+40; j++ {
  3667  				objsIDData[i][j] = objIDBytes[j-pos]
  3668  			}
  3669  		}
  3670  
  3671  		// Save objsIDHash so that procset dictionary has the correct object ids
  3672  		f.importedTplIDs[hash] = i + nOffset
  3673  	}
  3674  
  3675  	// Now, put objects
  3676  	for i = 0; i < len(objsIDData); i++ {
  3677  		f.newobj()
  3678  		f.out(string(objsIDData[i]))
  3679  	}
  3680  }
  3681  
  3682  // UseImportedTemplate uses imported template from gofpdi. It draws imported
  3683  // PDF page onto page.
  3684  func (f *Fpdf) UseImportedTemplate(tplName string, scaleX float64, scaleY float64, tX float64, tY float64) {
  3685  	f.outf("q 0 J 1 w 0 j 0 G 0 g q %.4F 0 0 %.4F %.4F %.4F cm %s Do Q Q\n", scaleX*f.k, scaleY*f.k, tX*f.k, (tY+f.h)*f.k, tplName)
  3686  }
  3687  
  3688  // ImportTemplates imports gofpdi template names into importedTplObjs for
  3689  // inclusion in the procset dictionary
  3690  func (f *Fpdf) ImportTemplates(tpls map[string]string) {
  3691  	for tplName, tplID := range tpls {
  3692  		f.importedTplObjs[tplName] = tplID
  3693  	}
  3694  }
  3695  
  3696  // GetConversionRatio returns the conversion ratio based on the unit given when
  3697  // creating the PDF.
  3698  func (f *Fpdf) GetConversionRatio() float64 {
  3699  	return f.k
  3700  }
  3701  
  3702  // GetXY returns the abscissa and ordinate of the current position.
  3703  //
  3704  // Note: the value returned for the abscissa will be affected by the current
  3705  // cell margin. To account for this, you may need to either add the value
  3706  // returned by GetCellMargin() to it or call SetCellMargin(0) to remove the
  3707  // cell margin.
  3708  func (f *Fpdf) GetXY() (float64, float64) {
  3709  	return f.x, f.y
  3710  }
  3711  
  3712  // GetX returns the abscissa of the current position.
  3713  //
  3714  // Note: the value returned will be affected by the current cell margin. To
  3715  // account for this, you may need to either add the value returned by
  3716  // GetCellMargin() to it or call SetCellMargin(0) to remove the cell margin.
  3717  func (f *Fpdf) GetX() float64 {
  3718  	return f.x
  3719  }
  3720  
  3721  // SetX defines the abscissa of the current position. If the passed value is
  3722  // negative, it is relative to the right of the page.
  3723  func (f *Fpdf) SetX(x float64) {
  3724  	if x >= 0 {
  3725  		f.x = x
  3726  	} else {
  3727  		f.x = f.w + x
  3728  	}
  3729  }
  3730  
  3731  // GetY returns the ordinate of the current position.
  3732  func (f *Fpdf) GetY() float64 {
  3733  	return f.y
  3734  }
  3735  
  3736  // SetY moves the current abscissa back to the left margin and sets the
  3737  // ordinate. If the passed value is negative, it is relative to the bottom of
  3738  // the page.
  3739  func (f *Fpdf) SetY(y float64) {
  3740  	// dbg("SetY x %.2f, lMargin %.2f", f.x, f.lMargin)
  3741  	f.x = f.lMargin
  3742  	if y >= 0 {
  3743  		f.y = y
  3744  	} else {
  3745  		f.y = f.h + y
  3746  	}
  3747  }
  3748  
  3749  // SetHomeXY is a convenience method that sets the current position to the left
  3750  // and top margins.
  3751  func (f *Fpdf) SetHomeXY() {
  3752  	f.SetY(f.tMargin)
  3753  	f.SetX(f.lMargin)
  3754  }
  3755  
  3756  // SetXY defines the abscissa and ordinate of the current position. If the
  3757  // passed values are negative, they are relative respectively to the right and
  3758  // bottom of the page.
  3759  func (f *Fpdf) SetXY(x, y float64) {
  3760  	f.SetY(y)
  3761  	f.SetX(x)
  3762  }
  3763  
  3764  // SetProtection applies certain constraints on the finished PDF document.
  3765  //
  3766  // actionFlag is a bitflag that controls various document operations.
  3767  // CnProtectPrint allows the document to be printed. CnProtectModify allows a
  3768  // document to be modified by a PDF editor. CnProtectCopy allows text and
  3769  // images to be copied into the system clipboard. CnProtectAnnotForms allows
  3770  // annotations and forms to be added by a PDF editor. These values can be
  3771  // combined by or-ing them together, for example,
  3772  // CnProtectCopy|CnProtectModify. This flag is advisory; not all PDF readers
  3773  // implement the constraints that this argument attempts to control.
  3774  //
  3775  // userPassStr specifies the password that will need to be provided to view the
  3776  // contents of the PDF. The permissions specified by actionFlag will apply.
  3777  //
  3778  // ownerPassStr specifies the password that will need to be provided to gain
  3779  // full access to the document regardless of the actionFlag value. An empty
  3780  // string for this argument will be replaced with a random value, effectively
  3781  // prohibiting full access to the document.
  3782  func (f *Fpdf) SetProtection(actionFlag byte, userPassStr, ownerPassStr string) {
  3783  	if f.err != nil {
  3784  		return
  3785  	}
  3786  	f.protect.setProtection(actionFlag, userPassStr, ownerPassStr)
  3787  }
  3788  
  3789  // OutputAndClose sends the PDF document to the writer specified by w. This
  3790  // method will close both f and w, even if an error is detected and no document
  3791  // is produced.
  3792  func (f *Fpdf) OutputAndClose(w io.WriteCloser) error {
  3793  	_ = f.Output(w)
  3794  	err := w.Close()
  3795  	if err != nil {
  3796  		return fmt.Errorf("could not close writer: %w", err)
  3797  	}
  3798  	return f.err
  3799  }
  3800  
  3801  // OutputFileAndClose creates or truncates the file specified by fileStr and
  3802  // writes the PDF document to it. This method will close f and the newly
  3803  // written file, even if an error is detected and no document is produced.
  3804  //
  3805  // Most examples demonstrate the use of this method.
  3806  func (f *Fpdf) OutputFileAndClose(fileStr string) error {
  3807  	if f.err != nil {
  3808  		return f.err
  3809  	}
  3810  
  3811  	pdfFile, err := os.Create(fileStr)
  3812  	if err != nil {
  3813  		f.err = err
  3814  		return f.err
  3815  	}
  3816  
  3817  	_ = f.Output(pdfFile)
  3818  
  3819  	err = pdfFile.Close()
  3820  	if err != nil {
  3821  		return fmt.Errorf("could not close output file: %w", err)
  3822  	}
  3823  
  3824  	return f.err
  3825  }
  3826  
  3827  // Output sends the PDF document to the writer specified by w. No output will
  3828  // take place if an error has occurred in the document generation process. w
  3829  // remains open after this function returns. After returning, f is in a closed
  3830  // state and its methods should not be called.
  3831  func (f *Fpdf) Output(w io.Writer) error {
  3832  	if f.err != nil {
  3833  		return f.err
  3834  	}
  3835  	// dbg("Output")
  3836  	if f.state < 3 {
  3837  		f.Close()
  3838  	}
  3839  	_, err := f.buffer.WriteTo(w)
  3840  	if err != nil {
  3841  		f.err = err
  3842  	}
  3843  	return f.err
  3844  }
  3845  
  3846  func (f *Fpdf) getpagesizestr(sizeStr string) (size SizeType) {
  3847  	if f.err != nil {
  3848  		return
  3849  	}
  3850  	sizeStr = strings.ToLower(sizeStr)
  3851  	// dbg("Size [%s]", sizeStr)
  3852  	var ok bool
  3853  	size, ok = f.stdPageSizes[sizeStr]
  3854  	if ok {
  3855  		// dbg("found %s", sizeStr)
  3856  		size.Wd /= f.k
  3857  		size.Ht /= f.k
  3858  
  3859  	} else {
  3860  		f.err = fmt.Errorf("unknown page size %s", sizeStr)
  3861  	}
  3862  	return
  3863  }
  3864  
  3865  // GetPageSizeStr returns the SizeType for the given sizeStr (that is A4, A3, etc..)
  3866  func (f *Fpdf) GetPageSizeStr(sizeStr string) (size SizeType) {
  3867  	return f.getpagesizestr(sizeStr)
  3868  }
  3869  
  3870  func (f *Fpdf) beginpage(orientationStr string, size SizeType) {
  3871  	if f.err != nil {
  3872  		return
  3873  	}
  3874  	f.page++
  3875  	// add the default page boxes, if any exist, to the page
  3876  	f.pageBoxes[f.page] = make(map[string]PageBox)
  3877  	for box, pb := range f.defPageBoxes {
  3878  		f.pageBoxes[f.page][box] = pb
  3879  	}
  3880  	f.pages = append(f.pages, bytes.NewBufferString(""))
  3881  	f.pageLinks = append(f.pageLinks, make([]linkType, 0))
  3882  	f.pageAttachments = append(f.pageAttachments, []annotationAttach{})
  3883  	f.state = 2
  3884  	f.x = f.lMargin
  3885  	f.y = f.tMargin
  3886  	f.fontFamily = ""
  3887  	// Check page size and orientation
  3888  	if orientationStr == "" {
  3889  		orientationStr = f.defOrientation
  3890  	} else {
  3891  		orientationStr = strings.ToUpper(orientationStr[0:1])
  3892  	}
  3893  	if orientationStr != f.curOrientation || size.Wd != f.curPageSize.Wd || size.Ht != f.curPageSize.Ht {
  3894  		// New size or orientation
  3895  		if orientationStr == "P" {
  3896  			f.w = size.Wd
  3897  			f.h = size.Ht
  3898  		} else {
  3899  			f.w = size.Ht
  3900  			f.h = size.Wd
  3901  		}
  3902  		f.wPt = f.w * f.k
  3903  		f.hPt = f.h * f.k
  3904  		f.pageBreakTrigger = f.h - f.bMargin
  3905  		f.curOrientation = orientationStr
  3906  		f.curPageSize = size
  3907  	}
  3908  	if orientationStr != f.defOrientation || size.Wd != f.defPageSize.Wd || size.Ht != f.defPageSize.Ht {
  3909  		f.pageSizes[f.page] = SizeType{f.wPt, f.hPt}
  3910  	}
  3911  }
  3912  
  3913  func (f *Fpdf) endpage() {
  3914  	f.EndLayer()
  3915  	f.state = 1
  3916  }
  3917  
  3918  // Load a font definition file from the given Reader
  3919  func (f *Fpdf) loadfont(r io.Reader) (def fontDefType) {
  3920  	if f.err != nil {
  3921  		return
  3922  	}
  3923  	// dbg("Loading font [%s]", fontStr)
  3924  	var buf bytes.Buffer
  3925  	_, err := buf.ReadFrom(r)
  3926  	if err != nil {
  3927  		f.err = err
  3928  		return
  3929  	}
  3930  	err = json.Unmarshal(buf.Bytes(), &def)
  3931  	if err != nil {
  3932  		f.err = err
  3933  		return
  3934  	}
  3935  
  3936  	if def.i, err = generateFontID(def); err != nil {
  3937  		f.err = err
  3938  	}
  3939  	// dump(def)
  3940  	return
  3941  }
  3942  
  3943  // Escape special characters in strings
  3944  func (f *Fpdf) escape(s string) string {
  3945  	s = strings.Replace(s, "\\", "\\\\", -1)
  3946  	s = strings.Replace(s, "(", "\\(", -1)
  3947  	s = strings.Replace(s, ")", "\\)", -1)
  3948  	s = strings.Replace(s, "\r", "\\r", -1)
  3949  	return s
  3950  }
  3951  
  3952  // textstring formats a text string
  3953  func (f *Fpdf) textstring(s string) string {
  3954  	if f.protect.encrypted {
  3955  		b := []byte(s)
  3956  		f.protect.rc4(uint32(f.n), &b)
  3957  		s = string(b)
  3958  	}
  3959  	return "(" + f.escape(s) + ")"
  3960  }
  3961  
  3962  func blankCount(str string) (count int) {
  3963  	l := len(str)
  3964  	for j := 0; j < l; j++ {
  3965  		if byte(' ') == str[j] {
  3966  			count++
  3967  		}
  3968  	}
  3969  	return
  3970  }
  3971  
  3972  // GetUnderlineThickness returns the current text underline thickness multiplier.
  3973  func (f *Fpdf) GetUnderlineThickness() float64 {
  3974  	return f.userUnderlineThickness
  3975  }
  3976  
  3977  // SetUnderlineThickness accepts a multiplier for adjusting the text underline
  3978  // thickness, defaulting to 1. See SetUnderlineThickness example.
  3979  func (f *Fpdf) SetUnderlineThickness(thickness float64) {
  3980  	f.userUnderlineThickness = thickness
  3981  }
  3982  
  3983  // Underline text
  3984  func (f *Fpdf) dounderline(x, y float64, txt string) string {
  3985  	up := float64(f.currentFont.Up)
  3986  	ut := float64(f.currentFont.Ut) * f.userUnderlineThickness
  3987  	w := f.GetStringWidth(txt) + f.ws*float64(blankCount(txt))
  3988  	return sprintf("%.2f %.2f %.2f %.2f re f", x*f.k,
  3989  		(f.h-(y-up/1000*f.fontSize))*f.k, w*f.k, -ut/1000*f.fontSizePt)
  3990  }
  3991  
  3992  func (f *Fpdf) dostrikeout(x, y float64, txt string) string {
  3993  	up := float64(f.currentFont.Up)
  3994  	ut := float64(f.currentFont.Ut)
  3995  	w := f.GetStringWidth(txt) + f.ws*float64(blankCount(txt))
  3996  	return sprintf("%.2f %.2f %.2f %.2f re f", x*f.k,
  3997  		(f.h-(y+4*up/1000*f.fontSize))*f.k, w*f.k, -ut/1000*f.fontSizePt)
  3998  }
  3999  
  4000  func (f *Fpdf) newImageInfo() *ImageInfoType {
  4001  	// default dpi to 72 unless told otherwise
  4002  	return &ImageInfoType{scale: f.k, dpi: 72}
  4003  }
  4004  
  4005  // parsejpg extracts info from io.Reader with JPEG data
  4006  // Thank you, Bruno Michel, for providing this code.
  4007  func (f *Fpdf) parsejpg(r io.Reader) (info *ImageInfoType) {
  4008  	info = f.newImageInfo()
  4009  	var (
  4010  		data bytes.Buffer
  4011  		err  error
  4012  	)
  4013  	_, err = data.ReadFrom(r)
  4014  	if err != nil {
  4015  		f.err = err
  4016  		return
  4017  	}
  4018  	info.data = data.Bytes()
  4019  
  4020  	config, err := jpeg.DecodeConfig(bytes.NewReader(info.data))
  4021  	if err != nil {
  4022  		f.err = err
  4023  		return
  4024  	}
  4025  	info.w = float64(config.Width)
  4026  	info.h = float64(config.Height)
  4027  	info.f = "DCTDecode"
  4028  	info.bpc = 8
  4029  	switch config.ColorModel {
  4030  	case color.GrayModel:
  4031  		info.cs = "DeviceGray"
  4032  	case color.YCbCrModel:
  4033  		info.cs = "DeviceRGB"
  4034  	case color.CMYKModel:
  4035  		info.cs = "DeviceCMYK"
  4036  	default:
  4037  		f.err = fmt.Errorf("image JPEG buffer has unsupported color space (%v)", config.ColorModel)
  4038  		return
  4039  	}
  4040  	return
  4041  }
  4042  
  4043  // parsepng extracts info from a PNG data
  4044  func (f *Fpdf) parsepng(r io.Reader, readdpi bool) (info *ImageInfoType) {
  4045  	buf, err := newRBuffer(r)
  4046  	if err != nil {
  4047  		f.err = err
  4048  		return
  4049  	}
  4050  	return f.parsepngstream(buf, readdpi)
  4051  }
  4052  
  4053  // parsegif extracts info from a GIF data (via PNG conversion)
  4054  func (f *Fpdf) parsegif(r io.Reader) (info *ImageInfoType) {
  4055  	data, err := newRBuffer(r)
  4056  	if err != nil {
  4057  		f.err = err
  4058  		return
  4059  	}
  4060  	var img image.Image
  4061  	img, err = gif.Decode(data)
  4062  	if err != nil {
  4063  		f.err = err
  4064  		return
  4065  	}
  4066  	pngBuf := new(bytes.Buffer)
  4067  	err = png.Encode(pngBuf, img)
  4068  	if err != nil {
  4069  		f.err = err
  4070  		return
  4071  	}
  4072  	return f.parsepngstream(&rbuffer{p: pngBuf.Bytes()}, false)
  4073  }
  4074  
  4075  // newobj begins a new object
  4076  func (f *Fpdf) newobj() {
  4077  	// dbg("newobj")
  4078  	f.n++
  4079  	for j := len(f.offsets); j <= f.n; j++ {
  4080  		f.offsets = append(f.offsets, 0)
  4081  	}
  4082  	f.offsets[f.n] = f.buffer.Len()
  4083  	f.outf("%d 0 obj", f.n)
  4084  }
  4085  
  4086  func (f *Fpdf) putstream(b []byte) {
  4087  	// dbg("putstream")
  4088  	if f.protect.encrypted {
  4089  		f.protect.rc4(uint32(f.n), &b)
  4090  	}
  4091  	f.out("stream")
  4092  	f.out(string(b))
  4093  	f.out("endstream")
  4094  }
  4095  
  4096  // out; Add a line to the document
  4097  func (f *Fpdf) out(s string) {
  4098  	if f.state == 2 {
  4099  		must(f.pages[f.page].WriteString(s))
  4100  		must(f.pages[f.page].WriteString("\n"))
  4101  	} else {
  4102  		must(f.buffer.WriteString(s))
  4103  		must(f.buffer.WriteString("\n"))
  4104  	}
  4105  }
  4106  
  4107  func (f *Fpdf) put(s string) {
  4108  	if f.state == 2 {
  4109  		f.pages[f.page].WriteString(s)
  4110  	} else {
  4111  		f.buffer.WriteString(s)
  4112  	}
  4113  }
  4114  
  4115  // outbuf adds a buffered line to the document
  4116  func (f *Fpdf) outbuf(r io.Reader) {
  4117  	if f.state == 2 {
  4118  		must64(f.pages[f.page].ReadFrom(r))
  4119  		must(f.pages[f.page].WriteString("\n"))
  4120  	} else {
  4121  		must64(f.buffer.ReadFrom(r))
  4122  		must(f.buffer.WriteString("\n"))
  4123  	}
  4124  }
  4125  
  4126  // RawWriteStr writes a string directly to the PDF generation buffer. This is a
  4127  // low-level function that is not required for normal PDF construction. An
  4128  // understanding of the PDF specification is needed to use this method
  4129  // correctly.
  4130  func (f *Fpdf) RawWriteStr(str string) {
  4131  	f.out(str)
  4132  }
  4133  
  4134  // RawWriteBuf writes the contents of the specified buffer directly to the PDF
  4135  // generation buffer. This is a low-level function that is not required for
  4136  // normal PDF construction. An understanding of the PDF specification is needed
  4137  // to use this method correctly.
  4138  func (f *Fpdf) RawWriteBuf(r io.Reader) {
  4139  	f.outbuf(r)
  4140  }
  4141  
  4142  // outf adds a formatted line to the document
  4143  func (f *Fpdf) outf(fmtStr string, args ...interface{}) {
  4144  	f.out(sprintf(fmtStr, args...))
  4145  }
  4146  
  4147  func (f *Fpdf) putF64(v float64, prec int) {
  4148  	f.put(f.fmtF64(v, prec))
  4149  }
  4150  
  4151  // fmtF64 converts the floating-point number f to a string with precision prec.
  4152  func (f *Fpdf) fmtF64(v float64, prec int) string {
  4153  	return string(strconv.AppendFloat(f.fmt.buf[:0], v, 'f', prec, 64))
  4154  }
  4155  
  4156  func (f *Fpdf) putInt(v int) {
  4157  	f.put(f.fmtInt(v))
  4158  }
  4159  
  4160  func (f *Fpdf) fmtInt(v int) string {
  4161  	return string(strconv.AppendInt(f.fmt.buf[:0], int64(v), 10))
  4162  }
  4163  
  4164  // SetDefaultCatalogSort sets the default value of the catalog sort flag that
  4165  // will be used when initializing a new Fpdf instance. See SetCatalogSort() for
  4166  // more details.
  4167  func SetDefaultCatalogSort(flag bool) {
  4168  	gl.catalogSort = flag
  4169  }
  4170  
  4171  // GetCatalogSort returns the document's internal catalog sort flag.
  4172  func (f *Fpdf) GetCatalogSort() bool {
  4173  	return f.catalogSort
  4174  }
  4175  
  4176  // SetCatalogSort sets a flag that will be used, if true, to consistently order
  4177  // the document's internal resource catalogs. This method is typically only
  4178  // used for test purposes to facilitate PDF comparison.
  4179  func (f *Fpdf) SetCatalogSort(flag bool) {
  4180  	f.catalogSort = flag
  4181  }
  4182  
  4183  // SetDefaultCreationDate sets the default value of the document creation date
  4184  // that will be used when initializing a new Fpdf instance. See
  4185  // SetCreationDate() for more details.
  4186  func SetDefaultCreationDate(tm time.Time) {
  4187  	gl.creationDate = tm
  4188  }
  4189  
  4190  // SetDefaultModificationDate sets the default value of the document modification date
  4191  // that will be used when initializing a new Fpdf instance. See
  4192  // SetCreationDate() for more details.
  4193  func SetDefaultModificationDate(tm time.Time) {
  4194  	gl.modDate = tm
  4195  }
  4196  
  4197  // GetCreationDate returns the document's internal CreationDate value.
  4198  func (f *Fpdf) GetCreationDate() time.Time {
  4199  	return f.creationDate
  4200  }
  4201  
  4202  // SetCreationDate fixes the document's internal CreationDate value. By
  4203  // default, the time when the document is generated is used for this value.
  4204  // This method is typically only used for testing purposes to facilitate PDF
  4205  // comparison. Specify a zero-value time to revert to the default behavior.
  4206  func (f *Fpdf) SetCreationDate(tm time.Time) {
  4207  	f.creationDate = tm
  4208  }
  4209  
  4210  // GetModificationDate returns the document's internal ModDate value.
  4211  func (f *Fpdf) GetModificationDate() time.Time {
  4212  	return f.modDate
  4213  }
  4214  
  4215  // SetModificationDate fixes the document's internal ModDate value.
  4216  // See `SetCreationDate` for more details.
  4217  func (f *Fpdf) SetModificationDate(tm time.Time) {
  4218  	f.modDate = tm
  4219  }
  4220  
  4221  // GetJavascript returns the Adobe JavaScript for the document.
  4222  //
  4223  // GetJavascript returns an empty string if no javascript was
  4224  // previously defined.
  4225  func (f *Fpdf) GetJavascript() string {
  4226  	if f.javascript == nil {
  4227  		return ""
  4228  	}
  4229  	return *f.javascript
  4230  }
  4231  
  4232  // SetJavascript adds Adobe JavaScript to the document.
  4233  func (f *Fpdf) SetJavascript(script string) {
  4234  	f.javascript = &script
  4235  }
  4236  
  4237  // RegisterAlias adds an (alias, replacement) pair to the document so we can
  4238  // replace all occurrences of that alias after writing but before the document
  4239  // is closed. Functions ExampleFpdf_RegisterAlias() and
  4240  // ExampleFpdf_RegisterAlias_utf8() in fpdf_test.go demonstrate this method.
  4241  func (f *Fpdf) RegisterAlias(alias, replacement string) {
  4242  	// Note: map[string]string assignments embed literal escape ("\00") sequences
  4243  	// into utf16 key and value strings. Consequently, subsequent search/replace
  4244  	// operations will fail unexpectedly if utf8toutf16() conversions take place
  4245  	// here. Instead, conversions are deferred until the actual search/replace
  4246  	// operation takes place when the PDF output is generated.
  4247  	f.aliasMap[alias] = replacement
  4248  }
  4249  
  4250  func (f *Fpdf) replaceAliases() {
  4251  	for mode := 0; mode < 2; mode++ {
  4252  		for alias, replacement := range f.aliasMap {
  4253  			if mode == 1 {
  4254  				alias = utf8toutf16(alias, false)
  4255  				replacement = utf8toutf16(replacement, false)
  4256  			}
  4257  			for n := 1; n <= f.page; n++ {
  4258  				s := f.pages[n].String()
  4259  				if strings.Contains(s, alias) {
  4260  					s = strings.Replace(s, alias, replacement, -1)
  4261  					f.pages[n].Truncate(0)
  4262  					f.pages[n].WriteString(s)
  4263  				}
  4264  			}
  4265  		}
  4266  	}
  4267  }
  4268  
  4269  func (f *Fpdf) putpages() {
  4270  	var wPt, hPt float64
  4271  	var pageSize SizeType
  4272  	var ok bool
  4273  	nb := f.page
  4274  	if len(f.aliasNbPagesStr) > 0 {
  4275  		// Replace number of pages
  4276  		f.RegisterAlias(f.aliasNbPagesStr, sprintf("%d", nb))
  4277  	}
  4278  	f.replaceAliases()
  4279  	if f.defOrientation == "P" {
  4280  		wPt = f.defPageSize.Wd * f.k
  4281  		hPt = f.defPageSize.Ht * f.k
  4282  	} else {
  4283  		wPt = f.defPageSize.Ht * f.k
  4284  		hPt = f.defPageSize.Wd * f.k
  4285  	}
  4286  	pagesObjectNumbers := make([]int, nb+1) // 1-based
  4287  	for n := 1; n <= nb; n++ {
  4288  		// Page
  4289  		f.newobj()
  4290  		pagesObjectNumbers[n] = f.n // save for /Kids
  4291  		f.out("<</Type /Page")
  4292  		f.out("/Parent 1 0 R")
  4293  		pageSize, ok = f.pageSizes[n]
  4294  		if ok {
  4295  			f.outf("/MediaBox [0 0 %.2f %.2f]", pageSize.Wd, pageSize.Ht)
  4296  		}
  4297  		for t, pb := range f.pageBoxes[n] {
  4298  			f.outf("/%s [%.2f %.2f %.2f %.2f]", t, pb.X, pb.Y, pb.Wd, pb.Ht)
  4299  		}
  4300  		f.out("/Resources 2 0 R")
  4301  		// Links
  4302  		if len(f.pageLinks[n])+len(f.pageAttachments[n]) > 0 {
  4303  			var annots fmtBuffer
  4304  			annots.printf("/Annots [")
  4305  			for _, pl := range f.pageLinks[n] {
  4306  				annots.printf("<</Type /Annot /Subtype /Link /Rect [%.2f %.2f %.2f %.2f] /Border [0 0 0] ",
  4307  					pl.x, pl.y, pl.x+pl.wd, pl.y-pl.ht)
  4308  				if pl.link == 0 {
  4309  					annots.printf("/A <</S /URI /URI %s>>>>", f.textstring(pl.linkStr))
  4310  				} else {
  4311  					l := f.links[pl.link]
  4312  					var sz SizeType
  4313  					var h float64
  4314  					sz, ok = f.pageSizes[l.page]
  4315  					if ok {
  4316  						h = sz.Ht
  4317  					} else {
  4318  						h = hPt
  4319  					}
  4320  					// dbg("h [%.2f], l.y [%.2f] f.k [%.2f]\n", h, l.y, f.k)
  4321  					annots.printf("/Dest [%d 0 R /XYZ 0 %.2f null]>>", 1+2*l.page, h-l.y*f.k)
  4322  				}
  4323  			}
  4324  			f.putAttachmentAnnotationLinks(&annots, n)
  4325  			annots.printf("]")
  4326  			f.out(annots.String())
  4327  		}
  4328  		if f.pdfVersion > pdfVers1_3 {
  4329  			f.out("/Group <</Type /Group /S /Transparency /CS /DeviceRGB>>")
  4330  		}
  4331  		f.outf("/Contents %d 0 R>>", f.n+1)
  4332  		f.out("endobj")
  4333  		// Page content
  4334  		f.newobj()
  4335  		if f.compress {
  4336  			mem := xmem.compress(f.pages[n].Bytes())
  4337  			data := mem.bytes()
  4338  			f.outf("<</Filter /FlateDecode /Length %d>>", len(data))
  4339  			f.putstream(data)
  4340  			mem.release()
  4341  		} else {
  4342  			f.outf("<</Length %d>>", f.pages[n].Len())
  4343  			f.putstream(f.pages[n].Bytes())
  4344  		}
  4345  		f.out("endobj")
  4346  	}
  4347  	// Pages root
  4348  	f.offsets[1] = f.buffer.Len()
  4349  	f.out("1 0 obj")
  4350  	f.out("<</Type /Pages")
  4351  	var kids fmtBuffer
  4352  	kids.printf("/Kids [")
  4353  	for i := 1; i <= nb; i++ {
  4354  		kids.printf("%d 0 R ", pagesObjectNumbers[i])
  4355  	}
  4356  	kids.printf("]")
  4357  	f.out(kids.String())
  4358  	f.outf("/Count %d", nb)
  4359  	f.outf("/MediaBox [0 0 %.2f %.2f]", wPt, hPt)
  4360  	f.out(">>")
  4361  	f.out("endobj")
  4362  }
  4363  
  4364  func (f *Fpdf) putfonts() {
  4365  	if f.err != nil {
  4366  		return
  4367  	}
  4368  	nf := f.n
  4369  	for _, diff := range f.diffs {
  4370  		// Encodings
  4371  		f.newobj()
  4372  		f.outf("<</Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences [%s]>>", diff)
  4373  		f.out("endobj")
  4374  	}
  4375  	{
  4376  		var fileList []string
  4377  		var info fontFileType
  4378  		var file string
  4379  		for file = range f.fontFiles {
  4380  			fileList = append(fileList, file)
  4381  		}
  4382  		if f.catalogSort {
  4383  			sort.SliceStable(fileList, func(i, j int) bool { return fileList[i] < fileList[j] })
  4384  		}
  4385  		for _, file = range fileList {
  4386  			info = f.fontFiles[file]
  4387  			if info.fontType != "UTF8" {
  4388  				f.newobj()
  4389  				info.n = f.n
  4390  				f.fontFiles[file] = info
  4391  
  4392  				var font []byte
  4393  
  4394  				if info.embedded {
  4395  					font = info.content
  4396  				} else {
  4397  					var err error
  4398  					font, err = f.loadFontFile(file)
  4399  					if err != nil {
  4400  						f.err = err
  4401  						return
  4402  					}
  4403  				}
  4404  				compressed := file[len(file)-2:] == ".z"
  4405  				if !compressed && info.length2 > 0 {
  4406  					buf := font[6:info.length1]
  4407  					buf = append(buf, font[6+info.length1+6:info.length2]...)
  4408  					font = buf
  4409  				}
  4410  				f.outf("<</Length %d", len(font))
  4411  				if compressed {
  4412  					f.out("/Filter /FlateDecode")
  4413  				}
  4414  				f.outf("/Length1 %d", info.length1)
  4415  				if info.length2 > 0 {
  4416  					f.outf("/Length2 %d /Length3 0", info.length2)
  4417  				}
  4418  				f.out(">>")
  4419  				f.putstream(font)
  4420  				f.out("endobj")
  4421  			}
  4422  		}
  4423  	}
  4424  	{
  4425  		var keyList []string
  4426  		var font fontDefType
  4427  		var key string
  4428  		for key = range f.fonts {
  4429  			keyList = append(keyList, key)
  4430  		}
  4431  		if f.catalogSort {
  4432  			sort.SliceStable(keyList, func(i, j int) bool { return keyList[i] < keyList[j] })
  4433  		}
  4434  		for _, key = range keyList {
  4435  			font = f.fonts[key]
  4436  			// Font objects
  4437  			font.N = f.n + 1
  4438  			f.fonts[key] = font
  4439  			tp := font.Tp
  4440  			name := font.Name
  4441  			switch tp {
  4442  			case "Core":
  4443  				// Core font
  4444  				f.newobj()
  4445  				f.out("<</Type /Font")
  4446  				f.outf("/BaseFont /%s", name)
  4447  				f.out("/Subtype /Type1")
  4448  				if name != "Symbol" && name != "ZapfDingbats" {
  4449  					f.out("/Encoding /WinAnsiEncoding")
  4450  				}
  4451  				f.out(">>")
  4452  				f.out("endobj")
  4453  			case "Type1":
  4454  				fallthrough
  4455  			case "TrueType":
  4456  				// Additional Type1 or TrueType/OpenType font
  4457  				f.newobj()
  4458  				f.out("<</Type /Font")
  4459  				f.outf("/BaseFont /%s", name)
  4460  				f.outf("/Subtype /%s", tp)
  4461  				f.out("/FirstChar 32 /LastChar 255")
  4462  				f.outf("/Widths %d 0 R", f.n+1)
  4463  				f.outf("/FontDescriptor %d 0 R", f.n+2)
  4464  				if font.DiffN > 0 {
  4465  					f.outf("/Encoding %d 0 R", nf+font.DiffN)
  4466  				} else {
  4467  					f.out("/Encoding /WinAnsiEncoding")
  4468  				}
  4469  				f.out(">>")
  4470  				f.out("endobj")
  4471  				// Widths
  4472  				f.newobj()
  4473  				var s fmtBuffer
  4474  				s.WriteString("[")
  4475  				for j := 32; j < 256; j++ {
  4476  					s.printf("%d ", font.Cw[j])
  4477  				}
  4478  				s.WriteString("]")
  4479  				f.out(s.String())
  4480  				f.out("endobj")
  4481  				// Descriptor
  4482  				f.newobj()
  4483  				s.Truncate(0)
  4484  				s.printf("<</Type /FontDescriptor /FontName /%s ", name)
  4485  				s.printf("/Ascent %d ", font.Desc.Ascent)
  4486  				s.printf("/Descent %d ", font.Desc.Descent)
  4487  				s.printf("/CapHeight %d ", font.Desc.CapHeight)
  4488  				s.printf("/Flags %d ", font.Desc.Flags)
  4489  				s.printf("/FontBBox [%d %d %d %d] ", font.Desc.FontBBox.Xmin, font.Desc.FontBBox.Ymin,
  4490  					font.Desc.FontBBox.Xmax, font.Desc.FontBBox.Ymax)
  4491  				s.printf("/ItalicAngle %d ", font.Desc.ItalicAngle)
  4492  				s.printf("/StemV %d ", font.Desc.StemV)
  4493  				s.printf("/MissingWidth %d ", font.Desc.MissingWidth)
  4494  				var suffix string
  4495  				if tp != "Type1" {
  4496  					suffix = "2"
  4497  				}
  4498  				s.printf("/FontFile%s %d 0 R>>", suffix, f.fontFiles[font.File].n)
  4499  				f.out(s.String())
  4500  				f.out("endobj")
  4501  			case "UTF8":
  4502  				fontName := "utf8" + font.Name
  4503  				usedRunes := font.usedRunes
  4504  				delete(usedRunes, 0)
  4505  				utf8FontStream := font.utf8File.GenerateCutFont(usedRunes)
  4506  				utf8FontSize := len(utf8FontStream)
  4507  				CodeSignDictionary := font.utf8File.CodeSymbolDictionary
  4508  				delete(CodeSignDictionary, 0)
  4509  
  4510  				f.newobj()
  4511  				f.out(fmt.Sprintf("<</Type /Font\n/Subtype /Type0\n/BaseFont /%s\n/Encoding /Identity-H\n/DescendantFonts [%d 0 R]\n/ToUnicode %d 0 R>>\n"+"endobj", fontName, f.n+1, f.n+2))
  4512  
  4513  				f.newobj()
  4514  				f.out("<</Type /Font\n/Subtype /CIDFontType2\n/BaseFont /" + fontName + "\n" +
  4515  					"/CIDSystemInfo " + strconv.Itoa(f.n+2) + " 0 R\n/FontDescriptor " + strconv.Itoa(f.n+3) + " 0 R")
  4516  				if font.Desc.MissingWidth != 0 {
  4517  					f.out("/DW " + strconv.Itoa(font.Desc.MissingWidth))
  4518  				}
  4519  				f.generateCIDFontMap(&font, font.utf8File.LastRune)
  4520  				f.out("/CIDToGIDMap " + strconv.Itoa(f.n+4) + " 0 R>>")
  4521  				f.out("endobj")
  4522  
  4523  				f.newobj()
  4524  				f.out("<</Length " + strconv.Itoa(len(toUnicode)) + ">>")
  4525  				f.putstream([]byte(toUnicode))
  4526  				f.out("endobj")
  4527  
  4528  				// CIDInfo
  4529  				f.newobj()
  4530  				f.out("<</Registry (Adobe)\n/Ordering (UCS)\n/Supplement 0>>")
  4531  				f.out("endobj")
  4532  
  4533  				// Font descriptor
  4534  				f.newobj()
  4535  				var s fmtBuffer
  4536  				s.printf("<</Type /FontDescriptor /FontName /%s\n /Ascent %d", fontName, font.Desc.Ascent)
  4537  				s.printf(" /Descent %d", font.Desc.Descent)
  4538  				s.printf(" /CapHeight %d", font.Desc.CapHeight)
  4539  				v := font.Desc.Flags
  4540  				v = v | 4
  4541  				v = v &^ 32
  4542  				s.printf(" /Flags %d", v)
  4543  				s.printf("/FontBBox [%d %d %d %d] ", font.Desc.FontBBox.Xmin, font.Desc.FontBBox.Ymin,
  4544  					font.Desc.FontBBox.Xmax, font.Desc.FontBBox.Ymax)
  4545  				s.printf(" /ItalicAngle %d", font.Desc.ItalicAngle)
  4546  				s.printf(" /StemV %d", font.Desc.StemV)
  4547  				s.printf(" /MissingWidth %d", font.Desc.MissingWidth)
  4548  				s.printf("/FontFile2 %d 0 R", f.n+2)
  4549  				s.printf(">>")
  4550  				f.out(s.String())
  4551  				f.out("endobj")
  4552  
  4553  				// Embed CIDToGIDMap
  4554  				cidToGidMap := make([]byte, 256*256*2)
  4555  
  4556  				for cc, glyph := range CodeSignDictionary {
  4557  					cidToGidMap[cc*2] = byte(glyph >> 8)
  4558  					cidToGidMap[cc*2+1] = byte(glyph & 0xFF)
  4559  				}
  4560  
  4561  				mem := xmem.compress(cidToGidMap)
  4562  				cidToGidMap = mem.bytes()
  4563  				f.newobj()
  4564  				f.out("<</Length " + strconv.Itoa(len(cidToGidMap)) + "/Filter /FlateDecode>>")
  4565  				f.putstream(cidToGidMap)
  4566  				f.out("endobj")
  4567  				mem.release()
  4568  
  4569  				//Font file
  4570  				mem = xmem.compress(utf8FontStream)
  4571  				compressedFontStream := mem.bytes()
  4572  				f.newobj()
  4573  				f.out("<</Length " + strconv.Itoa(len(compressedFontStream)))
  4574  				f.out("/Filter /FlateDecode")
  4575  				f.out("/Length1 " + strconv.Itoa(utf8FontSize))
  4576  				f.out(">>")
  4577  				f.putstream(compressedFontStream)
  4578  				f.out("endobj")
  4579  				mem.release()
  4580  			default:
  4581  				f.err = fmt.Errorf("unsupported font type: %s", tp)
  4582  				return
  4583  			}
  4584  		}
  4585  	}
  4586  }
  4587  
  4588  func (f *Fpdf) generateCIDFontMap(font *fontDefType, LastRune int) {
  4589  	rangeID := 0
  4590  	cidArray := make(map[int]*untypedKeyMap)
  4591  	cidArrayKeys := make([]int, 0)
  4592  	prevCid := -2
  4593  	prevWidth := -1
  4594  	interval := false
  4595  	startCid := 1
  4596  	cwLen := LastRune + 1
  4597  
  4598  	// for each character
  4599  	for cid := startCid; cid < cwLen; cid++ {
  4600  		if font.Cw[cid] == 0x00 {
  4601  			continue
  4602  		}
  4603  		width := font.Cw[cid]
  4604  		if width == 65535 {
  4605  			width = 0
  4606  		}
  4607  		if numb, OK := font.usedRunes[cid]; cid > 255 && (!OK || numb == 0) {
  4608  			continue
  4609  		}
  4610  
  4611  		if cid == prevCid+1 {
  4612  			if width == prevWidth {
  4613  
  4614  				if width == cidArray[rangeID].get(0) {
  4615  					cidArray[rangeID].put(nil, width)
  4616  				} else {
  4617  					cidArray[rangeID].pop()
  4618  					rangeID = prevCid
  4619  					r := untypedKeyMap{
  4620  						valueSet: make([]int, 0),
  4621  						keySet:   make([]interface{}, 0),
  4622  					}
  4623  					cidArray[rangeID] = &r
  4624  					cidArrayKeys = append(cidArrayKeys, rangeID)
  4625  					cidArray[rangeID].put(nil, prevWidth)
  4626  					cidArray[rangeID].put(nil, width)
  4627  				}
  4628  				interval = true
  4629  				cidArray[rangeID].put("interval", 1)
  4630  			} else {
  4631  				if interval {
  4632  					// new range
  4633  					rangeID = cid
  4634  					r := untypedKeyMap{
  4635  						valueSet: make([]int, 0),
  4636  						keySet:   make([]interface{}, 0),
  4637  					}
  4638  					cidArray[rangeID] = &r
  4639  					cidArrayKeys = append(cidArrayKeys, rangeID)
  4640  					cidArray[rangeID].put(nil, width)
  4641  				} else {
  4642  					cidArray[rangeID].put(nil, width)
  4643  				}
  4644  				interval = false
  4645  			}
  4646  		} else {
  4647  			rangeID = cid
  4648  			r := untypedKeyMap{
  4649  				valueSet: make([]int, 0),
  4650  				keySet:   make([]interface{}, 0),
  4651  			}
  4652  			cidArray[rangeID] = &r
  4653  			cidArrayKeys = append(cidArrayKeys, rangeID)
  4654  			cidArray[rangeID].put(nil, width)
  4655  			interval = false
  4656  		}
  4657  		prevCid = cid
  4658  		prevWidth = width
  4659  
  4660  	}
  4661  	previousKey := -1
  4662  	nextKey := -1
  4663  	isInterval := false
  4664  	for g := 0; g < len(cidArrayKeys); {
  4665  		key := cidArrayKeys[g]
  4666  		ws := *cidArray[key]
  4667  		cws := len(ws.keySet)
  4668  		if (key == nextKey) && (!isInterval) && (ws.getIndex("interval") < 0 || cws < 4) {
  4669  			if cidArray[key].getIndex("interval") >= 0 {
  4670  				cidArray[key].delete("interval")
  4671  			}
  4672  			cidArray[previousKey] = arrayMerge(cidArray[previousKey], cidArray[key])
  4673  			cidArrayKeys = remove(cidArrayKeys, key)
  4674  		} else {
  4675  			g++
  4676  			previousKey = key
  4677  		}
  4678  		nextKey = key + cws
  4679  		// ui := ws.getIndex("interval")
  4680  		// ui = ui + 1
  4681  		if ws.getIndex("interval") >= 0 {
  4682  			if cws > 3 {
  4683  				isInterval = true
  4684  			} else {
  4685  				isInterval = false
  4686  			}
  4687  			cidArray[key].delete("interval")
  4688  			nextKey--
  4689  		} else {
  4690  			isInterval = false
  4691  		}
  4692  	}
  4693  	var w fmtBuffer
  4694  	for _, k := range cidArrayKeys {
  4695  		ws := cidArray[k]
  4696  		if len(arrayCountValues(ws.valueSet)) == 1 {
  4697  			w.printf(" %d %d %d", k, k+len(ws.valueSet)-1, ws.get(0))
  4698  		} else {
  4699  			w.printf(" %d [ %s ]\n", k, implode(" ", ws.valueSet))
  4700  		}
  4701  	}
  4702  	f.out("/W [" + w.String() + " ]")
  4703  }
  4704  
  4705  func implode(sep string, arr []int) string {
  4706  	var s fmtBuffer
  4707  	for i := 0; i < len(arr)-1; i++ {
  4708  		s.printf("%v", arr[i])
  4709  		s.printf("%s", sep)
  4710  	}
  4711  	if len(arr) > 0 {
  4712  		s.printf("%v", arr[len(arr)-1])
  4713  	}
  4714  	return s.String()
  4715  }
  4716  
  4717  // arrayCountValues counts the occurrences of each item in the $mp array.
  4718  func arrayCountValues(mp []int) map[int]int {
  4719  	answer := make(map[int]int)
  4720  	for _, v := range mp {
  4721  		answer[v] = answer[v] + 1
  4722  	}
  4723  	return answer
  4724  }
  4725  
  4726  func (f *Fpdf) loadFontFile(name string) ([]byte, error) {
  4727  	if f.fontLoader != nil {
  4728  		reader, err := f.fontLoader.Open(name)
  4729  		if err == nil {
  4730  			data, err := io.ReadAll(reader)
  4731  			if closer, ok := reader.(io.Closer); ok {
  4732  				closer.Close()
  4733  			}
  4734  			return data, err
  4735  		}
  4736  	}
  4737  	return os.ReadFile(path.Join(f.fontpath, name))
  4738  }
  4739  
  4740  func (f *Fpdf) putimages() {
  4741  	var keyList []string
  4742  	var key string
  4743  	for key = range f.images {
  4744  		keyList = append(keyList, key)
  4745  	}
  4746  
  4747  	// Sort the keyList []string by the corresponding image's width.
  4748  	if f.catalogSort {
  4749  		sort.SliceStable(keyList, func(i, j int) bool { return f.images[keyList[i]].w < f.images[keyList[j]].w })
  4750  	}
  4751  
  4752  	// Maintain a list of inserted image SHA-1 hashes, with their
  4753  	// corresponding object ID number.
  4754  	insertedImages := map[string]int{}
  4755  
  4756  	for _, key = range keyList {
  4757  		image := f.images[key]
  4758  
  4759  		// Check if this image has already been inserted using it's SHA-1 hash.
  4760  		insertedImageObjN, isFound := insertedImages[image.i]
  4761  
  4762  		// If found, skip inserting the image as a new object, and
  4763  		// use the object ID from the insertedImages map.
  4764  		// If not, insert the image into the PDF and store the object ID.
  4765  		if isFound {
  4766  			image.n = insertedImageObjN
  4767  		} else {
  4768  			f.putimage(image)
  4769  			insertedImages[image.i] = image.n
  4770  		}
  4771  	}
  4772  }
  4773  
  4774  func (f *Fpdf) putimage(info *ImageInfoType) {
  4775  	f.newobj()
  4776  	info.n = f.n
  4777  	f.out("<</Type /XObject")
  4778  	f.out("/Subtype /Image")
  4779  	f.outf("/Width %d", int(info.w))
  4780  	f.outf("/Height %d", int(info.h))
  4781  	if info.cs == "Indexed" {
  4782  		f.outf("/ColorSpace [/Indexed /DeviceRGB %d %d 0 R]", len(info.pal)/3-1, f.n+1)
  4783  	} else {
  4784  		f.outf("/ColorSpace /%s", info.cs)
  4785  		if info.cs == "DeviceCMYK" {
  4786  			f.out("/Decode [1 0 1 0 1 0 1 0]")
  4787  		}
  4788  	}
  4789  	f.outf("/BitsPerComponent %d", info.bpc)
  4790  	if len(info.f) > 0 {
  4791  		f.outf("/Filter /%s", info.f)
  4792  	}
  4793  	if len(info.dp) > 0 {
  4794  		f.outf("/DecodeParms <<%s>>", info.dp)
  4795  	}
  4796  	if len(info.trns) > 0 {
  4797  		var trns fmtBuffer
  4798  		for _, v := range info.trns {
  4799  			trns.printf("%d %d ", v, v)
  4800  		}
  4801  		f.outf("/Mask [%s]", trns.String())
  4802  	}
  4803  	if info.smask != nil {
  4804  		f.outf("/SMask %d 0 R", f.n+1)
  4805  	}
  4806  	f.outf("/Length %d>>", len(info.data))
  4807  	f.putstream(info.data)
  4808  	f.out("endobj")
  4809  	// 	Soft mask
  4810  	if len(info.smask) > 0 {
  4811  		smask := &ImageInfoType{
  4812  			w:     info.w,
  4813  			h:     info.h,
  4814  			cs:    "DeviceGray",
  4815  			bpc:   8,
  4816  			f:     info.f,
  4817  			dp:    sprintf("/Predictor 15 /Colors 1 /BitsPerComponent 8 /Columns %d", int(info.w)),
  4818  			data:  info.smask,
  4819  			scale: f.k,
  4820  		}
  4821  		f.putimage(smask)
  4822  	}
  4823  	// 	Palette
  4824  	if info.cs == "Indexed" {
  4825  		f.newobj()
  4826  		if f.compress {
  4827  			mem := xmem.compress(info.pal)
  4828  			pal := mem.bytes()
  4829  			f.outf("<</Filter /FlateDecode /Length %d>>", len(pal))
  4830  			f.putstream(pal)
  4831  			mem.release()
  4832  		} else {
  4833  			f.outf("<</Length %d>>", len(info.pal))
  4834  			f.putstream(info.pal)
  4835  		}
  4836  		f.out("endobj")
  4837  	}
  4838  }
  4839  
  4840  func (f *Fpdf) putxobjectdict() {
  4841  	{
  4842  		var image *ImageInfoType
  4843  		var key string
  4844  		var keyList []string
  4845  		for key = range f.images {
  4846  			keyList = append(keyList, key)
  4847  		}
  4848  		if f.catalogSort {
  4849  			sort.SliceStable(keyList, func(i, j int) bool { return f.images[keyList[i]].i < f.images[keyList[j]].i })
  4850  		}
  4851  		for _, key = range keyList {
  4852  			image = f.images[key]
  4853  			f.outf("/I%s %d 0 R", image.i, image.n)
  4854  		}
  4855  	}
  4856  	{
  4857  		var keyList []string
  4858  		var key string
  4859  		var tpl Template
  4860  		keyList = templateKeyList(f.templates, f.catalogSort)
  4861  		for _, key = range keyList {
  4862  			tpl = f.templates[key]
  4863  			// for _, tpl := range f.templates {
  4864  			id := tpl.ID()
  4865  			if objID, ok := f.templateObjects[id]; ok {
  4866  				f.outf("/TPL%s %d 0 R", id, objID)
  4867  			}
  4868  		}
  4869  	}
  4870  	{
  4871  		for tplName, objID := range f.importedTplObjs {
  4872  			// here replace obj id hash with n
  4873  			f.outf("%s %d 0 R", tplName, f.importedTplIDs[objID])
  4874  		}
  4875  	}
  4876  }
  4877  
  4878  func (f *Fpdf) putresourcedict() {
  4879  	f.out("/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]")
  4880  	f.out("/Font <<")
  4881  	{
  4882  		var keyList []string
  4883  		var font fontDefType
  4884  		var key string
  4885  		for key = range f.fonts {
  4886  			keyList = append(keyList, key)
  4887  		}
  4888  		if f.catalogSort {
  4889  			sort.SliceStable(keyList, func(i, j int) bool { return f.fonts[keyList[i]].i < f.fonts[keyList[j]].i })
  4890  		}
  4891  		for _, key = range keyList {
  4892  			font = f.fonts[key]
  4893  			f.outf("/F%s %d 0 R", font.i, font.N)
  4894  		}
  4895  	}
  4896  	f.out(">>")
  4897  	f.out("/XObject <<")
  4898  	f.putxobjectdict()
  4899  	f.out(">>")
  4900  	count := len(f.blendList)
  4901  	if count > 1 {
  4902  		f.out("/ExtGState <<")
  4903  		for j := 1; j < count; j++ {
  4904  			f.outf("/GS%d %d 0 R", j, f.blendList[j].objNum)
  4905  		}
  4906  		f.out(">>")
  4907  	}
  4908  	count = len(f.gradientList)
  4909  	if count > 1 {
  4910  		f.out("/Shading <<")
  4911  		for j := 1; j < count; j++ {
  4912  			f.outf("/Sh%d %d 0 R", j, f.gradientList[j].objNum)
  4913  		}
  4914  		f.out(">>")
  4915  	}
  4916  	// Layers
  4917  	f.layerPutResourceDict()
  4918  	f.spotColorPutResourceDict()
  4919  }
  4920  
  4921  func (f *Fpdf) putBlendModes() {
  4922  	count := len(f.blendList)
  4923  	for j := 1; j < count; j++ {
  4924  		bl := f.blendList[j]
  4925  		f.newobj()
  4926  		f.blendList[j].objNum = f.n
  4927  		f.outf("<</Type /ExtGState /ca %s /CA %s /BM /%s>>",
  4928  			bl.fillStr, bl.strokeStr, bl.modeStr)
  4929  		f.out("endobj")
  4930  	}
  4931  }
  4932  
  4933  func (f *Fpdf) putGradients() {
  4934  	count := len(f.gradientList)
  4935  	for j := 1; j < count; j++ {
  4936  		var f1 int
  4937  		gr := f.gradientList[j]
  4938  		if gr.tp == 2 || gr.tp == 3 {
  4939  			f.newobj()
  4940  			f.outf("<</FunctionType 2 /Domain [0.0 1.0] /C0 [%s] /C1 [%s] /N 1>>", gr.clr1Str, gr.clr2Str)
  4941  			f.out("endobj")
  4942  			f1 = f.n
  4943  		}
  4944  		f.newobj()
  4945  		f.outf("<</ShadingType %d /ColorSpace /DeviceRGB", gr.tp)
  4946  		if gr.tp == 2 {
  4947  			f.outf("/Coords [%.5f %.5f %.5f %.5f] /Function %d 0 R /Extend [true true]>>",
  4948  				gr.x1, gr.y1, gr.x2, gr.y2, f1)
  4949  		} else if gr.tp == 3 {
  4950  			f.outf("/Coords [%.5f %.5f 0 %.5f %.5f %.5f] /Function %d 0 R /Extend [true true]>>",
  4951  				gr.x1, gr.y1, gr.x2, gr.y2, gr.r, f1)
  4952  		}
  4953  		f.out("endobj")
  4954  		f.gradientList[j].objNum = f.n
  4955  	}
  4956  }
  4957  
  4958  func (f *Fpdf) putjavascript() {
  4959  	if f.javascript == nil {
  4960  		return
  4961  	}
  4962  
  4963  	f.newobj()
  4964  	f.nJs = f.n
  4965  	f.out("<<")
  4966  	f.outf("/Names [(EmbeddedJS) %d 0 R]", f.n+1)
  4967  	f.out(">>")
  4968  	f.out("endobj")
  4969  	f.newobj()
  4970  	f.out("<<")
  4971  	f.out("/S /JavaScript")
  4972  	f.outf("/JS %s", f.textstring(*f.javascript))
  4973  	f.out(">>")
  4974  	f.out("endobj")
  4975  }
  4976  
  4977  func (f *Fpdf) putresources() {
  4978  	if f.err != nil {
  4979  		return
  4980  	}
  4981  	f.layerPutLayers()
  4982  	f.putBlendModes()
  4983  	f.putGradients()
  4984  	f.putSpotColors()
  4985  	f.putfonts()
  4986  	if f.err != nil {
  4987  		return
  4988  	}
  4989  	f.putimages()
  4990  	f.putTemplates()
  4991  	f.putImportedTemplates() // gofpdi
  4992  	// 	Resource dictionary
  4993  	f.offsets[2] = f.buffer.Len()
  4994  	f.out("2 0 obj")
  4995  	f.out("<<")
  4996  	f.putresourcedict()
  4997  	f.out(">>")
  4998  	f.out("endobj")
  4999  	f.putjavascript()
  5000  	if f.protect.encrypted {
  5001  		f.newobj()
  5002  		f.protect.objNum = f.n
  5003  		f.out("<<")
  5004  		f.out("/Filter /Standard")
  5005  		f.out("/V 1")
  5006  		f.out("/R 2")
  5007  		f.outf("/O (%s)", f.escape(string(f.protect.oValue)))
  5008  		f.outf("/U (%s)", f.escape(string(f.protect.uValue)))
  5009  		f.outf("/P %d", f.protect.pValue)
  5010  		f.out(">>")
  5011  		f.out("endobj")
  5012  	}
  5013  }
  5014  
  5015  // returns Now() if tm is zero
  5016  func timeOrNow(tm time.Time) time.Time {
  5017  	if tm.IsZero() {
  5018  		return time.Now()
  5019  	}
  5020  	return tm
  5021  }
  5022  
  5023  func (f *Fpdf) putinfo() {
  5024  	if len(f.producer) > 0 {
  5025  		f.outf("/Producer %s", f.textstring(f.producer))
  5026  	}
  5027  	if len(f.title) > 0 {
  5028  		f.outf("/Title %s", f.textstring(f.title))
  5029  	}
  5030  	if len(f.subject) > 0 {
  5031  		f.outf("/Subject %s", f.textstring(f.subject))
  5032  	}
  5033  	if len(f.author) > 0 {
  5034  		f.outf("/Author %s", f.textstring(f.author))
  5035  	}
  5036  	if len(f.keywords) > 0 {
  5037  		f.outf("/Keywords %s", f.textstring(f.keywords))
  5038  	}
  5039  	if len(f.creator) > 0 {
  5040  		f.outf("/Creator %s", f.textstring(f.creator))
  5041  	}
  5042  	creation := timeOrNow(f.creationDate)
  5043  	f.outf("/CreationDate %s", f.textstring("D:"+creation.Format("20060102150405")))
  5044  	mod := timeOrNow(f.modDate)
  5045  	f.outf("/ModDate %s", f.textstring("D:"+mod.Format("20060102150405")))
  5046  }
  5047  
  5048  func (f *Fpdf) putcatalog() {
  5049  	f.out("/Type /Catalog")
  5050  	f.out("/Pages 1 0 R")
  5051  	if f.lang != "" {
  5052  		f.outf("/Lang (%s)", f.lang)
  5053  	}
  5054  	switch f.zoomMode {
  5055  	case "fullpage":
  5056  		f.out("/OpenAction [3 0 R /Fit]")
  5057  	case "fullwidth":
  5058  		f.out("/OpenAction [3 0 R /FitH null]")
  5059  	case "real":
  5060  		f.out("/OpenAction [3 0 R /XYZ null null 1]")
  5061  	}
  5062  	// } 	else if !is_string($this->zoomMode))
  5063  	// 		$this->out('/OpenAction [3 0 R /XYZ null null '.sprintf('%.2f',$this->zoomMode/100).']');
  5064  	switch f.layoutMode {
  5065  	case "single", "SinglePage":
  5066  		f.out("/PageLayout /SinglePage")
  5067  	case "continuous", "OneColumn":
  5068  		f.out("/PageLayout /OneColumn")
  5069  	case "two", "TwoColumnLeft":
  5070  		f.out("/PageLayout /TwoColumnLeft")
  5071  	case "TwoColumnRight":
  5072  		f.out("/PageLayout /TwoColumnRight")
  5073  	case "TwoPageLeft", "TwoPageRight":
  5074  		if f.pdfVersion < pdfVers1_5 {
  5075  			f.pdfVersion = pdfVers1_5
  5076  		}
  5077  		f.out("/PageLayout /" + f.layoutMode)
  5078  	}
  5079  	// Bookmarks
  5080  	if len(f.outlines) > 0 {
  5081  		f.outf("/Outlines %d 0 R", f.outlineRoot)
  5082  		f.out("/PageMode /UseOutlines")
  5083  	}
  5084  	// Layers
  5085  	f.layerPutCatalog()
  5086  	// XMP metadata
  5087  	if len(f.xmp) != 0 {
  5088  		f.outf("/Metadata %d 0 R", f.nXMP)
  5089  	}
  5090  	// Name dictionary :
  5091  	//	-> Javascript
  5092  	//	-> Embedded files
  5093  	f.out("/Names <<")
  5094  	// JavaScript
  5095  	if f.javascript != nil {
  5096  		f.outf("/JavaScript %d 0 R", f.nJs)
  5097  	}
  5098  	// Embedded files
  5099  	f.outf("/EmbeddedFiles %s", f.getEmbeddedFiles())
  5100  	f.out(">>")
  5101  }
  5102  
  5103  func (f *Fpdf) putheader() {
  5104  	f.outf("%%PDF-%s", f.pdfVersion)
  5105  	f.out("%µ¶")
  5106  }
  5107  
  5108  func (f *Fpdf) puttrailer() {
  5109  	f.outf("/Size %d", f.n+1)
  5110  	f.outf("/Root %d 0 R", f.n)
  5111  	f.outf("/Info %d 0 R", f.n-1)
  5112  	if f.protect.encrypted {
  5113  		f.outf("/Encrypt %d 0 R", f.protect.objNum)
  5114  		f.out("/ID [()()]")
  5115  	}
  5116  }
  5117  
  5118  func (f *Fpdf) putxmp() {
  5119  	if len(f.xmp) == 0 {
  5120  		return
  5121  	}
  5122  	f.newobj()
  5123  	f.nXMP = f.n
  5124  	f.outf("<< /Type /Metadata /Subtype /XML /Length %d >>", len(f.xmp))
  5125  	f.putstream(f.xmp)
  5126  	f.out("endobj")
  5127  }
  5128  
  5129  func (f *Fpdf) putbookmarks() {
  5130  	nb := len(f.outlines)
  5131  	if nb > 0 {
  5132  		lru := make(map[int]int)
  5133  		level := 0
  5134  		for i, o := range f.outlines {
  5135  			if o.level > 0 {
  5136  				parent := lru[o.level-1]
  5137  				f.outlines[i].parent = parent
  5138  				f.outlines[parent].last = i
  5139  				if o.level > level {
  5140  					f.outlines[parent].first = i
  5141  				}
  5142  			} else {
  5143  				f.outlines[i].parent = nb
  5144  			}
  5145  			if o.level <= level && i > 0 {
  5146  				prev := lru[o.level]
  5147  				f.outlines[prev].next = i
  5148  				f.outlines[i].prev = prev
  5149  			}
  5150  			lru[o.level] = i
  5151  			level = o.level
  5152  		}
  5153  		n := f.n + 1
  5154  		for _, o := range f.outlines {
  5155  			f.newobj()
  5156  			f.outf("<</Title %s", f.textstring(o.text))
  5157  			f.outf("/Parent %d 0 R", n+o.parent)
  5158  			if o.prev != -1 {
  5159  				f.outf("/Prev %d 0 R", n+o.prev)
  5160  			}
  5161  			if o.next != -1 {
  5162  				f.outf("/Next %d 0 R", n+o.next)
  5163  			}
  5164  			if o.first != -1 {
  5165  				f.outf("/First %d 0 R", n+o.first)
  5166  			}
  5167  			if o.last != -1 {
  5168  				f.outf("/Last %d 0 R", n+o.last)
  5169  			}
  5170  			f.outf("/Dest [%d 0 R /XYZ 0 %.2f null]", 1+2*o.p, (f.h-o.y)*f.k)
  5171  			f.out("/Count 0>>")
  5172  			f.out("endobj")
  5173  		}
  5174  		f.newobj()
  5175  		f.outlineRoot = f.n
  5176  		f.outf("<</Type /Outlines /First %d 0 R", n)
  5177  		f.outf("/Last %d 0 R>>", n+lru[0])
  5178  		f.out("endobj")
  5179  	}
  5180  }
  5181  
  5182  func (f *Fpdf) enddoc() {
  5183  	if f.err != nil {
  5184  		return
  5185  	}
  5186  	f.layerEndDoc()
  5187  	f.putheader()
  5188  	// Embedded files
  5189  	f.putAttachments()
  5190  	f.putAnnotationsAttachments()
  5191  	f.putpages()
  5192  	f.putresources()
  5193  	if f.err != nil {
  5194  		return
  5195  	}
  5196  	// Bookmarks
  5197  	f.putbookmarks()
  5198  	// Metadata
  5199  	f.putxmp()
  5200  	// 	Info
  5201  	f.newobj()
  5202  	f.out("<<")
  5203  	f.putinfo()
  5204  	f.out(">>")
  5205  	f.out("endobj")
  5206  	// 	Catalog
  5207  	f.newobj()
  5208  	f.out("<<")
  5209  	f.putcatalog()
  5210  	f.out(">>")
  5211  	f.out("endobj")
  5212  	// Cross-ref
  5213  	o := f.buffer.Len()
  5214  	f.out("xref")
  5215  	f.outf("0 %d", f.n+1)
  5216  	f.out("0000000000 65535 f ")
  5217  	for j := 1; j <= f.n; j++ {
  5218  		f.outf("%010d 00000 n ", f.offsets[j])
  5219  	}
  5220  	// Trailer
  5221  	f.out("trailer")
  5222  	f.out("<<")
  5223  	f.puttrailer()
  5224  	f.out(">>")
  5225  	f.out("startxref")
  5226  	f.outf("%d", o)
  5227  	f.out("%%EOF")
  5228  	f.state = 3
  5229  }
  5230  
  5231  // Path Drawing
  5232  
  5233  // MoveTo moves the stylus to (x, y) without drawing the path from the
  5234  // previous point. Paths must start with a MoveTo to set the original
  5235  // stylus location or the result is undefined.
  5236  //
  5237  // Create a "path" by moving a virtual stylus around the page (with
  5238  // MoveTo, LineTo, CurveTo, CurveBezierCubicTo, ArcTo & ClosePath)
  5239  // then draw it or  fill it in (with DrawPath). The main advantage of
  5240  // using the path drawing routines rather than multiple Fpdf.Line is
  5241  // that PDF creates nice line joins at the angles, rather than just
  5242  // overlaying the lines.
  5243  func (f *Fpdf) MoveTo(x, y float64) {
  5244  	f.point(x, y)
  5245  	f.x, f.y = x, y
  5246  }
  5247  
  5248  // LineTo creates a line from the current stylus location to (x, y), which
  5249  // becomes the new stylus location. Note that this only creates the line in
  5250  // the path; it does not actually draw the line on the page.
  5251  //
  5252  // The MoveTo() example demonstrates this method.
  5253  func (f *Fpdf) LineTo(x, y float64) {
  5254  	// f.outf("%.2f %.2f l", x*f.k, (f.h-y)*f.k)
  5255  	const prec = 2
  5256  	f.putF64(x*f.k, prec)
  5257  	f.put(" ")
  5258  
  5259  	f.putF64((f.h-y)*f.k, prec)
  5260  	f.put(" l\n")
  5261  
  5262  	f.x, f.y = x, y
  5263  }
  5264  
  5265  // CurveTo creates a single-segment quadratic Bézier curve. The curve starts at
  5266  // the current stylus location and ends at the point (x, y). The control point
  5267  // (cx, cy) specifies the curvature. At the start point, the curve is tangent
  5268  // to the straight line between the current stylus location and the control
  5269  // point. At the end point, the curve is tangent to the straight line between
  5270  // the end point and the control point.
  5271  //
  5272  // The MoveTo() example demonstrates this method.
  5273  func (f *Fpdf) CurveTo(cx, cy, x, y float64) {
  5274  	// f.outf("%.5f %.5f %.5f %.5f v", cx*f.k, (f.h-cy)*f.k, x*f.k, (f.h-y)*f.k)
  5275  	const prec = 5
  5276  	f.putF64(cx*f.k, prec)
  5277  	f.put(" ")
  5278  	f.putF64((f.h-cy)*f.k, prec)
  5279  	f.put(" ")
  5280  	f.putF64(x*f.k, prec)
  5281  	f.put(" ")
  5282  	f.putF64((f.h-y)*f.k, prec)
  5283  	f.put(" v\n")
  5284  	f.x, f.y = x, y
  5285  }
  5286  
  5287  // CurveBezierCubicTo creates a single-segment cubic Bézier curve. The curve
  5288  // starts at the current stylus location and ends at the point (x, y). The
  5289  // control points (cx0, cy0) and (cx1, cy1) specify the curvature. At the
  5290  // current stylus, the curve is tangent to the straight line between the
  5291  // current stylus location and the control point (cx0, cy0). At the end point,
  5292  // the curve is tangent to the straight line between the end point and the
  5293  // control point (cx1, cy1).
  5294  //
  5295  // The MoveTo() example demonstrates this method.
  5296  func (f *Fpdf) CurveBezierCubicTo(cx0, cy0, cx1, cy1, x, y float64) {
  5297  	f.curve(cx0, cy0, cx1, cy1, x, y)
  5298  	f.x, f.y = x, y
  5299  }
  5300  
  5301  // ClosePath creates a line from the current location to the last MoveTo point
  5302  // (if not the same) and mark the path as closed so the first and last lines
  5303  // join nicely.
  5304  //
  5305  // The MoveTo() example demonstrates this method.
  5306  func (f *Fpdf) ClosePath() {
  5307  	f.outf("h")
  5308  }
  5309  
  5310  // DrawPath actually draws the path on the page.
  5311  //
  5312  // styleStr can be "F" for filled, "D" for outlined only, or "DF" or "FD" for
  5313  // outlined and filled. An empty string will be replaced with "D".
  5314  // Path-painting operators as defined in the PDF specification are also
  5315  // allowed: "S" (Stroke the path), "s" (Close and stroke the path),
  5316  // "f" (fill the path, using the nonzero winding number), "f*"
  5317  // (Fill the path, using the even-odd rule), "B" (Fill and then stroke
  5318  // the path, using the nonzero winding number rule), "B*" (Fill and
  5319  // then stroke the path, using the even-odd rule), "b" (Close, fill,
  5320  // and then stroke the path, using the nonzero winding number rule) and
  5321  // "b*" (Close, fill, and then stroke the path, using the even-odd
  5322  // rule).
  5323  // Drawing uses the current draw color, line width, and cap style
  5324  // centered on the
  5325  // path. Filling uses the current fill color.
  5326  //
  5327  // The MoveTo() example demonstrates this method.
  5328  func (f *Fpdf) DrawPath(styleStr string) {
  5329  	f.outf("%s", fillDrawOp(styleStr))
  5330  }
  5331  
  5332  // ArcTo draws an elliptical arc centered at point (x, y). rx and ry specify its
  5333  // horizontal and vertical radii. If the start of the arc is not at
  5334  // the current position, a connecting line will be drawn.
  5335  //
  5336  // degRotate specifies the angle that the arc will be rotated. degStart and
  5337  // degEnd specify the starting and ending angle of the arc. All angles are
  5338  // specified in degrees and measured counter-clockwise from the 3 o'clock
  5339  // position.
  5340  //
  5341  // styleStr can be "F" for filled, "D" for outlined only, or "DF" or "FD" for
  5342  // outlined and filled. An empty string will be replaced with "D". Drawing uses
  5343  // the current draw color, line width, and cap style centered on the arc's
  5344  // path. Filling uses the current fill color.
  5345  //
  5346  // The MoveTo() example demonstrates this method.
  5347  func (f *Fpdf) ArcTo(x, y, rx, ry, degRotate, degStart, degEnd float64) {
  5348  	f.arc(x, y, rx, ry, degRotate, degStart, degEnd, "", true)
  5349  }
  5350  
  5351  func (f *Fpdf) arc(x, y, rx, ry, degRotate, degStart, degEnd float64,
  5352  	styleStr string, path bool) {
  5353  	x *= f.k
  5354  	y = (f.h - y) * f.k
  5355  	rx *= f.k
  5356  	ry *= f.k
  5357  	segments := int(degEnd-degStart) / 60
  5358  	if segments < 2 {
  5359  		segments = 2
  5360  	}
  5361  	angleStart := degStart * math.Pi / 180
  5362  	angleEnd := degEnd * math.Pi / 180
  5363  	angleTotal := angleEnd - angleStart
  5364  	dt := angleTotal / float64(segments)
  5365  	dtm := dt / 3
  5366  	if degRotate != 0 {
  5367  		a := -degRotate * math.Pi / 180
  5368  		sin, cos := math.Sincos(a)
  5369  		//	f.outf("q %.5f %.5f %.5f %.5f %.5f %.5f cm",
  5370  		//		math.Cos(a), -1*math.Sin(a),
  5371  		//		math.Sin(a), math.Cos(a), x, y)
  5372  		const prec = 5
  5373  		f.put("q ")
  5374  		f.putF64(cos, prec)
  5375  		f.put(" ")
  5376  		f.putF64(-1*sin, prec)
  5377  		f.put(" ")
  5378  		f.putF64(sin, prec)
  5379  		f.put(" ")
  5380  		f.putF64(cos, prec)
  5381  		f.put(" ")
  5382  		f.putF64(x, prec)
  5383  		f.put(" ")
  5384  		f.putF64(y, prec)
  5385  		f.put(" cm\n")
  5386  
  5387  		x = 0
  5388  		y = 0
  5389  	}
  5390  	t := angleStart
  5391  	a0 := x + rx*math.Cos(t)
  5392  	b0 := y + ry*math.Sin(t)
  5393  	c0 := -rx * math.Sin(t)
  5394  	d0 := ry * math.Cos(t)
  5395  	sx := a0 / f.k // start point of arc
  5396  	sy := f.h - (b0 / f.k)
  5397  	if path {
  5398  		if f.x != sx || f.y != sy {
  5399  			// Draw connecting line to start point
  5400  			f.LineTo(sx, sy)
  5401  		}
  5402  	} else {
  5403  		f.point(sx, sy)
  5404  	}
  5405  	for j := 1; j <= segments; j++ {
  5406  		// Draw this bit of the total curve
  5407  		t = (float64(j) * dt) + angleStart
  5408  		a1 := x + rx*math.Cos(t)
  5409  		b1 := y + ry*math.Sin(t)
  5410  		c1 := -rx * math.Sin(t)
  5411  		d1 := ry * math.Cos(t)
  5412  		f.curve((a0+(c0*dtm))/f.k,
  5413  			f.h-((b0+(d0*dtm))/f.k),
  5414  			(a1-(c1*dtm))/f.k,
  5415  			f.h-((b1-(d1*dtm))/f.k),
  5416  			a1/f.k,
  5417  			f.h-(b1/f.k))
  5418  		a0 = a1
  5419  		b0 = b1
  5420  		c0 = c1
  5421  		d0 = d1
  5422  		if path {
  5423  			f.x = a1 / f.k
  5424  			f.y = f.h - (b1 / f.k)
  5425  		}
  5426  	}
  5427  	if !path {
  5428  		f.out(fillDrawOp(styleStr))
  5429  	}
  5430  	if degRotate != 0 {
  5431  		f.out("Q")
  5432  	}
  5433  }