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