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