1 // Written in the D language. 2 /* 3 * Distributed under the Boost License V1.0. 4 * 5 * Original Author Olivier Plathey 6 * D translation by Jason den Dulk. 7 */ 8 9 module jaypha.fpdf; 10 11 import jaypha.fpdf_fonts; 12 13 import imageformats; 14 15 import std.array; 16 import std.file; 17 import std.string; 18 import std.json; 19 import std.conv; 20 import std.zlib; 21 import std.traits; 22 import std.algorithm; 23 import std.datetime; 24 import std.stdio; 25 import std.bitmanip; // endianness stuff 26 27 enum fpdfVersion = 1.7; 28 29 float[2][string] StdPageSizes; 30 31 //---------------------------------------------------------------------------- 32 33 shared static this() 34 { 35 StdPageSizes = 36 [ 37 "a3" : [ 841.89, 1190.55 ], 38 "a4" : [ 595.28, 841.89 ], 39 "a5" : [ 420.94, 595.28 ], 40 "letter" : [ 612.0, 792.0 ], 41 "legal" : [ 612.0, 1008.0 ], 42 ]; 43 } 44 45 /**************************************************************************** 46 * class Fpdf 47 ****************************************************************************/ 48 49 //---------------------------------------------------------------------------- 50 class Fpdf 51 //---------------------------------------------------------------------------- 52 { 53 struct LinkInfo 54 { 55 float x,y,w,h; 56 size_t link; 57 string externalLink; 58 } 59 struct LinkDest 60 { 61 size_t page; 62 float y; 63 } 64 65 struct ImageInfo 66 { 67 size_t i,n; 68 ulong w,h; 69 string cs; // Color space 70 uint bpc; // Bits per component. 71 string f; // Filter 72 string dp; // DecodeParams 73 string pal; // Palette 74 ubyte[] trns; // transparency 75 ubyte[] data; 76 ubyte[] smask; 77 } 78 79 protected: 80 size_t page = 0; 81 size_t n = 2; 82 size_t[] offsets; 83 Appender!string buffer; 84 85 uint state = 0; // current document state 86 87 Appender!(string)[size_t] pages; // array containing pages 88 bool compress = true; // compression flag 89 float k; // scale factor (number of points in user unit) 90 char DefOrientation; // default orientation 91 char CurOrientation; // current orientation 92 float[2] DefPageSize; // default page size 93 float[2] CurPageSize; // current page size 94 float[2][size_t] PageSizes; // used for pages with non default sizes or orientations 95 float wPt, hPt; // dimensions of current page in points 96 float w, h; // dimensions of current page in user unit 97 float lMargin; // left margin 98 float tMargin; // top margin 99 float rMargin; // right margin 100 float bMargin; // page break margin 101 float cMargin; // cell margin 102 float x, y; // current position in user unit 103 float lasth; // height of last printed cell 104 float LineWidth; // line width in user unit 105 string fontpath; // path containing fonts 106 string[] CoreFonts; // array of core font names 107 FontFileInfo[string] FontFiles; // array of font files 108 string[] diffs; // array of encoding differences 109 string FontFamily; // current font family 110 string FontStyle; // current font style 111 bool underline; // underlining flag 112 FontInfo CurrentFont; // current font info 113 float FontSizePt = 12.0; // current font size in points 114 float FontSize; // current font size in user unit 115 string DrawColor; // commands for drawing color 116 string FillColor; // commands for filling color 117 string TextColor; // commands for text color 118 bool ColorFlag; // indicates whether fill and text colors are different 119 float ws = 0.0; // word spacing 120 LinkInfo[][size_t] PageLinks; // array of links in pages 121 LinkDest[] links; // array of internal links 122 bool AutoPageBreak; // automatic page breaking 123 float PageBreakTrigger; // threshold used to trigger page breaks 124 bool InHeader; // flag set when processing header 125 bool InFooter; // flag set when processing footer 126 string zoomMode; // zoom display mode 127 float zoom = 0; // numeric zoom 128 string layoutMode; // layout display mode 129 string title; // title 130 string subject; // subject 131 string author; // author 132 string[] keywords; // keywords 133 string creator; // creator 134 string aliasNbPages; // alias for total number of pages 135 float pdfVersion; // PDF version number 136 137 // Image files; 138 ImageInfo[] images; 139 size_t[string] imageInfoIdx; 140 141 // Fonts 142 FontInfo[] fonts; 143 size_t[string] fontInfoIdx; 144 145 public: 146 147 //----------------------------------- 148 149 private void setUnit(string unit) 150 { 151 // Scale factor; 152 switch (unit) 153 { 154 case "pt": k = 1; break; 155 case "mm": k = 72/25.4; break; 156 case "cm": k = 72/2.54; break; 157 case "in": k = 72; break; 158 default: throw new Exception("Unknown unit"); 159 } 160 } 161 162 //----------------------------------- 163 164 this(char orientation = 'P', string unit = "mm", string size = "a4") 165 { 166 setUnit(unit); 167 setup(orientation, _getpagesize(size)); 168 } 169 170 this(char orientation, string unit, float[2] sz) 171 { 172 setUnit(unit); 173 setup(orientation, _getpagesize(sz)); 174 } 175 176 //----------------------------------- 177 178 private void setup(char orientation, float[2] sz) 179 { 180 n = 2; 181 buffer = appender!string(); 182 DrawColor = "0 G"; 183 FillColor = "0 g"; 184 TextColor = "0 g"; 185 186 // Font path 187 static if (__traits(compiles, fpdfFontPath)) 188 { 189 fontpath = fpdfFontPath; 190 if (fontpath[$-1] != '/' && fontpath[$-1] != '\\') 191 fontpath ~= "/"; 192 } 193 194 // Core fonts 195 CoreFonts = [ "courier", "helvetica", "times", "symbol", "zapfdingbats" ]; 196 197 DefPageSize = sz; 198 CurPageSize = sz; 199 200 // Page Orientation 201 if (orientation == 'P') 202 { 203 w = sz[0]; 204 h = sz[1]; 205 } 206 else if (orientation == 'L') 207 { 208 w = sz[1]; 209 h = sz[0]; 210 } 211 else 212 throw new Exception("Unkown orientation"); 213 214 DefOrientation = orientation; 215 CurOrientation = orientation; 216 wPt = w*k; 217 hPt = h*k; 218 219 // Margins 220 auto margin = 28.35/k; // 1cm 221 SetMargins(margin,margin); 222 cMargin = margin/10; // 1mm 223 LineWidth = 0.567/k; // 0.2mm 224 SetAutoPageBreak(true, 2*margin); 225 226 pdfVersion = 1.3; 227 } 228 229 void SetMargins(float left, float top, float right = -1) 230 { 231 lMargin = left; 232 tMargin = top; 233 if (right < 0) 234 rMargin = left; 235 else 236 rMargin = right; 237 } 238 239 void SetLeftMargin(float margin) 240 { 241 lMargin = margin; 242 if (page > 0 && x < margin) 243 x = margin; 244 } 245 246 void SetTopMargin(float margin) 247 { 248 // Set top margin 249 tMargin = margin; 250 } 251 252 void SetRightMargin(float margin) 253 { 254 // Set right margin 255 rMargin = margin; 256 } 257 258 void SetAutoPageBreak(bool isAuto, float margin=0) 259 { 260 // Set auto page break mode and triggering margin 261 AutoPageBreak = isAuto; 262 bMargin = margin; 263 PageBreakTrigger = h-margin; 264 } 265 266 void SetDisplayMode(string zoom, string layout="default") 267 { 268 // Set display mode in viewer 269 if (zoom!="fullpage" && zoom!="fullwidth" && zoom!="real" && zoom!="default") 270 throw new Exception("Incorrect zoom display mode "~ zoom); 271 if(layout!="single" && layout!="continuous" && layout!="two" && layout!="default") 272 throw new Exception("Incorrect layout display mode: "~layout); 273 zoomMode = zoom; 274 this.zoom = 0; 275 layoutMode = layout; 276 } 277 278 void SetDisplayMode(float zoom, string layout="default") 279 { 280 // Set display mode in viewer 281 if(layout!="single" && layout!="continuous" && layout!="two" && layout!="default") 282 throw new Exception("Incorrect layout display mode: "~layout); 283 zoomMode = "custom"; 284 this.zoom = zoom; 285 layoutMode = layout; 286 } 287 288 void SetCompression(bool compress) 289 { 290 // Set page compression TODO is compress available? 291 this.compress = compress; 292 } 293 294 void SetTitle(string title) 295 { 296 // Title of document 297 this.title = title; 298 } 299 300 void SetSubject(string subject) 301 { 302 // Subject of document 303 this.subject = subject; 304 } 305 306 void SetAuthor(string author) 307 { 308 // Author of document 309 this.author = author; 310 } 311 312 void SetKeywords(string[] keywords) 313 { 314 // Keywords of document 315 this.keywords = keywords; 316 } 317 318 void SetCreator(string creator) 319 { 320 // Creator of document 321 this.creator = creator; 322 } 323 324 void AliasNbPages(string nbAlias="{nb}") 325 { 326 // Define an alias for total number of pages 327 aliasNbPages = nbAlias; 328 } 329 330 void Open() 331 { 332 // Begin document 333 state = 1; 334 } 335 336 void Close() 337 { 338 // Terminate document 339 if(state==3) 340 return; 341 if(page==0) 342 AddPage(); 343 // Page footer 344 InFooter = true; 345 Footer(); 346 InFooter = false; 347 // Close page 348 _endpage(); 349 // Close document 350 _enddoc(); 351 } 352 353 //----------------------------------- 354 355 void AddPage() { AddPage(DefOrientation, DefPageSize); } 356 void AddPage(char orientation, string size = null) 357 { 358 if (size is null) AddPage(orientation, DefPageSize); 359 else AddPage(orientation, _getpagesize(size)); 360 } 361 362 void AddPage(char orientation, float[2] size) 363 { 364 // Start a new page 365 if(state==0) 366 Open(); 367 auto family = FontFamily; 368 auto style = FontStyle ~ (underline ? "U" : ""); 369 auto fontsize = FontSizePt; 370 auto lw = LineWidth; 371 auto dc = DrawColor; 372 auto fc = FillColor; 373 auto tc = TextColor; 374 auto cf = ColorFlag; 375 if(page>0) 376 { 377 // Page footer 378 InFooter = true; 379 Footer(); 380 InFooter = false; 381 // Close page 382 _endpage(); 383 } 384 385 // Start new page 386 _beginpage(orientation,size); 387 // Set line cap style to square 388 _out("2 J"); 389 // Set line width 390 LineWidth = lw; 391 _out(format("%.2F w",lw*k)); 392 // Set font 393 if(family) 394 SetFont(family,style,fontsize); 395 // Set colors 396 DrawColor = dc; 397 if (dc!="0 G") 398 _out(dc); 399 FillColor = fc; 400 if(fc!="0 g") 401 _out(fc); 402 TextColor = tc; 403 ColorFlag = cf; 404 // Page header 405 InHeader = true; 406 Header(); 407 InHeader = false; 408 // Restore line width 409 if(LineWidth!=lw) 410 { 411 LineWidth = lw; 412 _out(format("%.2F w",lw*k)); 413 } 414 // Restore font 415 if(family) 416 SetFont(family,style,fontsize); 417 // Restore colors 418 if(DrawColor!=dc) 419 { 420 DrawColor = dc; 421 _out(dc); 422 } 423 if(FillColor!=fc) 424 { 425 FillColor = fc; 426 _out(fc); 427 } 428 TextColor = tc; 429 ColorFlag = cf; 430 } 431 432 //----------------------------------- 433 434 void Header() 435 { 436 // To be implemented in your own inherited class 437 } 438 439 void Footer() 440 { 441 // To be implemented in your own inherited class 442 } 443 444 //----------------------------------- 445 446 ulong PageNo() 447 { 448 // Get current page number 449 return page; 450 } 451 452 //----------------------------------- 453 454 void SetDrawColor(ulong r) 455 { 456 // Set color for all stroking operations 457 DrawColor = format("%.3F G",to!float(r)/255); 458 if(page>0) 459 _out(DrawColor); 460 } 461 462 //----------------------------------- 463 464 void SetDrawColor(ulong r, ulong g, ulong b) 465 { 466 // Set color for all stroking operations 467 DrawColor = format("%.3F %.3F %.3F RG",to!float(r)/255,to!float(g)/255,to!float(b)/255); 468 if(page>0) 469 _out(DrawColor); 470 } 471 472 //----------------------------------- 473 474 void SetFillColor(ulong r) 475 { 476 // Set color for all filling operations 477 FillColor = format("%.3F g",to!float(r)/255); 478 ColorFlag = (FillColor!=TextColor); 479 if(page>0) 480 _out(FillColor); 481 } 482 483 void SetFillColor(ulong r, ulong g, ulong b) 484 { 485 // Set color for all filling operations 486 FillColor = format("%.3F %.3F %.3F rg",to!float(r)/255,to!float(g)/255,to!float(b)/255); 487 ColorFlag = (FillColor!=TextColor); 488 if(page>0) 489 _out(FillColor); 490 } 491 492 //----------------------------------- 493 494 void SetTextColor(ulong r) 495 { 496 TextColor = format("%.3F g",to!float(r)/255); 497 ColorFlag = (FillColor!=TextColor); 498 } 499 500 void SetTextColor(ulong r, ulong g, ulong b) 501 { 502 TextColor = format("%.3F %.3F %.3F rg",to!float(r)/255,to!float(g)/255,to!float(b)/255); 503 ColorFlag = (FillColor!=TextColor); 504 } 505 506 //----------------------------------- 507 508 float GetStringWidth(string s) 509 { 510 // Get width of a string in the current font 511 auto cw = CurrentFont.cw; 512 float w = 0; 513 514 for (auto i=0; i<s.length; ++i) // TODO check for UTF issues 515 w += cw[s[i]]; 516 return w*FontSize/1000; 517 } 518 519 //----------------------------------- 520 521 void SetLineWidth(float width) 522 { 523 // Set line width 524 LineWidth = width; 525 if (page>0) 526 _out(format("%.2F w",width*k)); 527 } 528 529 //----------------------------------- 530 531 void Line(float x1, float y1, float x2, float y2) 532 { 533 // Draw a line 534 _out(format("%.2F %.2F m %.2F %.2F l S",x1*k,(h-y1)*k,x2*k,(h-y2)*k)); 535 } 536 537 //----------------------------------- 538 539 void Rect(float x, float y, float w, float h, string style="") 540 { 541 char op; 542 // Draw a rectangle 543 if(style=="F") 544 op = 'f'; 545 else if (style=="FD" || style=="DF") 546 op = 'B'; 547 else 548 op = 'S'; 549 _out(format("%.2F %.2F %.2F %.2F re %c",x*k,(this.h-y)*k,w*k,-h*k,op)); 550 } 551 552 //----------------------------------- 553 554 void AddFont(string family, string style, string file) 555 { 556 /+ 557 // Add a TrueType, OpenType or Type1 font 558 family = toLower(family); 559 style = toUpper(style); 560 if (style=="IB") 561 style = "BI"; 562 auto fontkey = family ~ style; 563 if (fontkey in fonts) 564 return; 565 auto info = _loadfont(file); 566 info.i = fonts.length+1; 567 if(!info.diff.empty) 568 { 569 long n = -1; 570 // Search existing encodings 571 for (auto i=0; i< diffs.length; ++i) 572 if (diffs[i] == info.diff) 573 { n = i; break; } 574 575 if(n<0) 576 { 577 n = diffs.length+1; 578 diffs[n] = info.diff; 579 } 580 info.diffn = n; 581 } 582 if(!info.file.empty) 583 { 584 // Embedded font 585 if(info.type=="TrueType") 586 FontFiles[info.file] = FontFileInfo(info.originalsize); 587 else 588 FontFiles[info.file] = FontFileInfo(info.size1, info.size2); 589 } 590 fonts[fontkey] = info; 591 +/ 592 } 593 594 //----------------------------------- 595 596 void SetFont(string family, string style=null, float size=0) 597 { 598 599 // Select a font; size given in points 600 if (family.empty) 601 family = FontFamily; 602 else 603 family = toLower(family); 604 style = toUpper(style); 605 if(indexOf(style,'U') != -1) 606 { 607 underline = true; 608 style = replace(style,"U",""); 609 } 610 else 611 underline = false; 612 if (style=="IB") 613 style = "BI"; 614 if (size==0) 615 size = FontSizePt; 616 // Test if font is already selected 617 if (FontFamily==family && FontStyle==style && FontSizePt==size) 618 return; 619 // Test if font is already loaded 620 auto fontkey = family~style; 621 if(!(fontkey in fontInfoIdx)) 622 { 623 // Test if one of the core fonts 624 if(family=="arial") 625 family = "helvetica"; 626 if(canFind(CoreFonts, family)) 627 { 628 if(family=="symbol" || family=="zapfdingbats") 629 style = ""; 630 fontkey = family~style; 631 if(!(fontkey in fontInfoIdx)) 632 { 633 FontInfo info = cast(FontInfo) coreFonts[fontkey]; 634 fontInfoIdx[fontkey] = fonts.length; 635 info.i = fonts.length+1; 636 fonts ~= info; 637 } 638 } 639 else 640 throw new Exception("Undefined font: "~family~" "~style); 641 } 642 // Select it 643 FontFamily = family; 644 FontStyle = style; 645 FontSizePt = size; 646 FontSize = size/k; 647 CurrentFont = fonts[fontInfoIdx[fontkey]]; 648 if(page>0) 649 _out(format("BT /F%d %.2F Tf ET",CurrentFont.i,FontSizePt)); 650 } 651 652 //--------------------------------- 653 654 void SetFontSize(float size) 655 { 656 // Set font size in points 657 if(FontSizePt==size) 658 return; 659 FontSizePt = size; 660 FontSize = size/k; 661 if(page>0) 662 _out(format("BT /F%d %.2F Tf ET",CurrentFont.i,FontSizePt)); 663 } 664 665 //--------------------------------- 666 667 size_t AddLink() 668 { 669 // Create a new internal link 670 links ~= LinkDest(0, 0); 671 return links.length; 672 } 673 674 //--------------------------------- 675 676 void SetLink(size_t link, float y=-1, size_t page=0) 677 { 678 // Set destination of internal link 679 if (y==-1) 680 y = this.y; 681 if(page==0) 682 page = this.page; 683 links[link-1] = LinkDest(page, y); 684 } 685 686 //--------------------------------- 687 688 void Link(float x, float y, float w, float h, size_t link) 689 { 690 // Put a link on the page 691 PageLinks[page] ~= LinkInfo(x*k, hPt-y*k, w*k, h*k, link); 692 } 693 694 //--------------------------------- 695 696 void Link(float x, float y, float w, float h, string link) 697 { 698 // Put a link on the page 699 PageLinks[page] ~= LinkInfo(x*k, hPt-y*k, w*k, h*k, 0, link); 700 } 701 702 //--------------------------------- 703 704 void Text(float x, float y, string txt) 705 { 706 // Output a string 707 auto s = format("BT %.2F %.2F Td (%s) Tj ET",x*k,(h-y)*k,_escape(txt)); 708 if (underline && !txt.empty) 709 s ~= " " ~_dounderline(x,y,txt); 710 if (ColorFlag) 711 s = "q "~TextColor~" "~s~" Q"; 712 _out(s); 713 } 714 715 //--------------------------------- 716 717 bool AcceptPageBreak() 718 { 719 // Accept automatic page break or not 720 return AutoPageBreak; 721 } 722 723 //--------------------------------- 724 725 struct _rect 726 { 727 float x,y,w,h; 728 } 729 730 731 void Cell(float w, float h, string txt, string border, size_t ln, string algn, bool fill, size_t link) 732 { 733 auto r = _cell(w, h, txt, border, ln, algn, fill); 734 if (!txt.empty) 735 Link(r.x,r.y,r.w,r.h,link); 736 } 737 738 void Cell(float w, float h=0, string txt="", string border="0", size_t ln=0, string algn="", bool fill=false, string link=null) 739 { 740 auto r = _cell(w, h, txt, border, ln, algn, fill); 741 if (!link.empty && !txt.empty) 742 Link(r.x,r.y,r.w,r.h,link); 743 } 744 745 _rect _cell(float w, float h, string txt, string border, size_t ln, string algn, bool fill) 746 { 747 // Output a cell 748 749 if(y+h>PageBreakTrigger && !InHeader && !InFooter && AcceptPageBreak()) 750 { 751 // Automatic page break 752 auto x = this.x; 753 auto ws = this.ws; 754 if (ws>0) 755 { 756 this.ws = 0; 757 _out("0 Tw"); 758 } 759 AddPage(CurOrientation,CurPageSize); 760 this.x = x; 761 if (ws>0) 762 { 763 this.ws = ws; 764 _out(format("%.3F Tw",ws*k)); 765 } 766 } 767 if (w==0) 768 w = this.w - rMargin - this.x; 769 auto s = ""; 770 string op; 771 if (fill || border=="1") 772 { 773 if (fill) 774 op = (border=="1") ? "B" : "f"; 775 else 776 op = "S"; 777 s = format("%.2F %.2F %.2F %.2F re %s ",this.x*k,(this.h-this.y)*k,w*k,-h*k,op); 778 } 779 780 if (indexOf(border,'L')!=-1) 781 s ~= format("%.2F %.2F m %.2F %.2F l S ",x*k,(this.h-y)*k,x*k,(this.h-(y+h))*k); 782 if (indexOf(border,'T')!=-1) 783 s ~= format("%.2F %.2F m %.2F %.2F l S ",x*k,(this.h-y)*k,(x+w)*k,(this.h-y)*k); 784 if (indexOf(border,'R')!=-1) 785 s ~= format("%.2F %.2F m %.2F %.2F l S ",(x+w)*k,(this.h-y)*k,(x+w)*k,(this.h-(y+h))*k); 786 if (indexOf(border,'B')!=-1) 787 s ~= format("%.2F %.2F m %.2F %.2F l S ",x*k,(this.h-(y+h))*k,(x+w)*k,(this.h-(y+h))*k); 788 789 _rect r; 790 791 if (!txt.empty) 792 { 793 float dx; 794 if(algn=="R") 795 dx = w-cMargin-GetStringWidth(txt); 796 else if (algn=="C") 797 dx = (w-GetStringWidth(txt))/2; 798 else 799 dx = cMargin; 800 if (ColorFlag) 801 s ~= "q "~TextColor~" "; 802 auto txt2 = txt.replace("\\","\\\\").replace(")","\\)").replace("(","\\(",); 803 s ~= format("BT %.2F %.2F Td (%s) Tj ET",(x+dx)*k,(this.h-(this.y+0.5*h+0.3*FontSize))*k,txt2); 804 if (underline) 805 s ~= " "~_dounderline(x+dx,y+0.5*h+0.3*FontSize,txt); 806 if (ColorFlag) 807 s ~= " Q"; 808 r = _rect(this.x+dx, this.y+0.5*h-0.5*FontSize, GetStringWidth(txt),FontSize); 809 } 810 if(s) 811 _out(s); 812 lasth = h; 813 if (ln>0) 814 { 815 // Go to next line 816 y += h; 817 if (ln==1) 818 this.x = lMargin; 819 } 820 else 821 this.x += w; 822 823 return r; 824 } 825 826 //----------------------------------- 827 828 void MultiCell(float w, float h, string txt, string border="0", string algn="J", bool fill=false) 829 { 830 // Output text with automatic or explicit line breaks 831 auto cw = CurrentFont.cw; 832 if(w==0) 833 w = this.w-rMargin-x; 834 auto wmax = (w-2*cMargin)*1000/FontSize; 835 auto s = txt.replace("\r", ""); 836 auto nb = s.length; 837 if(nb>0 && s[nb-1]=='\n') 838 nb--; 839 string b = null; 840 string b2= null; 841 if (border) 842 { 843 if(border=="1") 844 { 845 border = "LTRB"; 846 b = "LRT"; 847 b2 = "LR"; 848 } 849 else 850 { 851 b2 = ""; 852 if(indexOf(border,'L')!=-1) 853 b2 ~= "L"; 854 if(indexOf(border,'R')!=-1) 855 b2 ~= "R"; 856 b = (indexOf(border,'T')!=-1) ? b2~"T" : b2; 857 } 858 } 859 auto sep = -1; 860 auto i = 0; 861 auto j = 0; 862 auto l = 0; 863 auto ns = 0; 864 auto nl = 1; 865 auto ls = 0; 866 867 while(i<nb) 868 { 869 // Get next character 870 auto c = s[i]; 871 if(c=='\n') 872 { 873 // Explicit line break 874 if(ws>0) 875 { 876 ws = 0; 877 _out("0 Tw"); 878 } 879 Cell(w,h,s[j..i],b,2,algn,fill); 880 i++; 881 sep = -1; 882 j = i; 883 l = 0; 884 ns = 0; 885 nl++; 886 if(border!="0" && nl==2) 887 b = b2; 888 continue; 889 } 890 if(c==' ') 891 { 892 sep = i; 893 ls = l; 894 ns++; 895 } 896 l += cw[c]; 897 if(l>wmax) 898 { 899 // Automatic line break 900 if(sep==-1) 901 { 902 if(i==j) 903 i++; 904 if(ws>0) 905 { 906 ws = 0; 907 _out("0 Tw"); 908 } 909 Cell(w,h,s[j..i],b,2,algn,fill); 910 } 911 else 912 { 913 if(algn=="J") 914 { 915 ws = (ns>1) ? (wmax-ls)/1000*FontSize/(ns-1) : 0; 916 _out(format("%.3F Tw",ws*k)); 917 } 918 Cell(w,h,s[j..sep],b,2,algn,fill); 919 i = sep+1; 920 } 921 sep = -1; 922 j = i; 923 l = 0; 924 ns = 0; 925 nl++; 926 if(border!="0" && nl==2) 927 b = b2; 928 } 929 else 930 i++; 931 } 932 // Last chunk 933 if(ws>0) 934 { 935 ws = 0; 936 _out("0 Tw"); 937 } 938 if(indexOf(border,"B")!=-1) 939 b ~= "B"; 940 Cell(w,h,s[j..i],b,2,algn,fill); 941 x = lMargin; 942 } 943 944 //----------------------------------- 945 946 void Write(float h, string txt, string link=null) 947 { 948 _write(h, txt, link); 949 } 950 951 void Write(float h, string txt, size_t link) 952 { 953 _write(h, txt, link); 954 } 955 956 void _write(T)(float h, string txt, T link) 957 { 958 // Output text in flowing mode 959 auto cw = CurrentFont.cw; 960 auto w = this.w-rMargin-x; 961 auto wmax = (w-2*cMargin)*1000/FontSize; 962 auto s = txt.replace("\r",""); 963 auto nb = s.length; 964 auto sep = -1; 965 auto i = 0; 966 auto j = 0; 967 auto l = 0; 968 auto nl = 1; 969 while(i<nb) 970 { 971 // Get next character 972 auto c = s[i]; 973 if(c=='\n') 974 { 975 // Explicit line break 976 Cell(w,h,s[j..i],"0",2L,"",false,link); 977 i++; 978 sep = -1; 979 j = i; 980 l = 0; 981 if(nl==1) 982 { 983 x = lMargin; 984 w = this.w-rMargin-x; 985 wmax = (w-2*cMargin)*1000/FontSize; 986 } 987 nl++; 988 continue; 989 } 990 if(c==' ') 991 sep = i; 992 l += cw[c]; 993 if (l>wmax) 994 { 995 // Automatic line break 996 if(sep==-1) 997 { 998 if(x>lMargin) 999 { 1000 // Move to next line 1001 x = lMargin; 1002 y += h; 1003 w = w-rMargin-x; 1004 wmax = (w-2*cMargin)*1000/FontSize; 1005 i++; 1006 nl++; 1007 continue; 1008 } 1009 if(i==j) 1010 i++; 1011 Cell(w,h,s[j..i],"0",2L,"",false,link); 1012 } 1013 else 1014 { 1015 Cell(w,h,s[j..sep],"0",2L,"",false,link); 1016 i = sep+1; 1017 } 1018 sep = -1; 1019 j = i; 1020 l = 0; 1021 if(nl==1) 1022 { 1023 x = lMargin; 1024 w = this.w-rMargin-x; 1025 wmax = (w-2*cMargin)*1000/FontSize; 1026 } 1027 nl++; 1028 } 1029 else 1030 i++; 1031 } 1032 // Last chunk 1033 if(i!=j) 1034 Cell(l/1000*FontSize,h,s[j..$],"0",0L,"",false,link); 1035 } 1036 1037 //----------------------------------- 1038 1039 void Ln() 1040 { 1041 Ln(lasth); 1042 } 1043 1044 void Ln(float h) 1045 { 1046 // Line feed; default value is last cell height 1047 x = lMargin; 1048 y += h; 1049 } 1050 1051 //----------------------------------- 1052 1053 void Image(string file, float x, float y, float w, float h, string type, size_t link) 1054 { 1055 auto r = _image(file, x, y, w, h, type); 1056 Link(r.x,r.y,r.w,r.h,link); 1057 } 1058 1059 void Image(string file, float x=-1, float y=-1, float w=0, float h=0, string type=null, string link=null) 1060 { 1061 auto r = _image(file, x, y, w, h, type); 1062 if(!link.empty) 1063 Link(r.x,r.y,r.w,r.h,link); 1064 } 1065 1066 private _rect _image(string file, float x, float y, float w, float h, string type) 1067 { 1068 1069 // Put an image on the page 1070 ImageInfo info; 1071 1072 if(!(file in imageInfoIdx)) 1073 { 1074 // First use of this image, get info 1075 if (type.empty) 1076 { 1077 auto pos = lastIndexOf(file,'.'); 1078 if(pos <=0) 1079 throw new Exception("Image file has no extension and no type was specified: "~file); 1080 type = file[pos+1..$]; 1081 } 1082 1083 switch (toLower(type)) 1084 { 1085 case "jpeg": 1086 case "jpg": 1087 info = _parsejpg(file); 1088 break; 1089 case "png": 1090 info = _parsepng(file); 1091 break; 1092 default: 1093 throw new Exception("Unsupported image type: "~type); 1094 } 1095 auto l = images.length; 1096 imageInfoIdx[file] = l; 1097 info.i = l+1; 1098 images ~= info; 1099 } 1100 else 1101 info = images[imageInfoIdx[file]]; 1102 1103 // Automatic width and height calculation if needed 1104 if (w==0 && h==0) 1105 { 1106 // Put image at 96 dpi 1107 w = -96; 1108 h = -96; 1109 } 1110 if(w<0) 1111 w = -to!float(info.w)*72/w/k; 1112 if(h<0) 1113 h = -to!float(info.h)*72/h/k; 1114 if(w==0) 1115 w = h*to!float(info.w)/to!float(info.h); 1116 if(h==0) 1117 h = w*to!float(info.h)/to!float(info.w); 1118 1119 // Flowing mode 1120 if(y<0) 1121 { 1122 if(this.y+h>PageBreakTrigger && !InHeader && !InFooter && AcceptPageBreak()) 1123 { 1124 // Automatic page break 1125 auto x2 = this.x; 1126 AddPage(CurOrientation,CurPageSize); 1127 this.x = x2; 1128 } 1129 y = this.y; 1130 this.y += h; 1131 } 1132 1133 if(x<0) 1134 x = this.x; 1135 _out(format("q %.2F 0 0 %.2F %.2F %.2F cm /I%d Do Q", w*k, h*k, x*k, (this.h-(y+h))*k, info.i)); 1136 1137 auto r = _rect(x,y,w,h); 1138 return r; 1139 } 1140 1141 //----------------------------------- 1142 1143 float GetX() { return x; } 1144 void SetX(float x) { this.x = (x>=0)? x : w+x; } 1145 float GetY() { return y; } 1146 void SetY(float y) { x = lMargin; this.y = (y >= 0) ? y : h+y; } 1147 1148 //----------------------------------- 1149 1150 void SetXY(float x, float y) 1151 { 1152 // Set x and y positions 1153 SetY(y); 1154 SetX(x); 1155 } 1156 1157 //----------------------------------- 1158 1159 immutable(ubyte)[] Output() 1160 { 1161 if(state<3) 1162 Close(); 1163 return cast(immutable(ubyte)[]) buffer.data; 1164 } 1165 1166 //------------------------------------------------------------ 1167 // Protected methods 1168 1169 protected: 1170 1171 float[2] _getpagesize(string size) 1172 { 1173 size = toLower(size); 1174 if(!(size in StdPageSizes)) 1175 throw new Exception("Unknown page size: "~size); 1176 auto a = StdPageSizes[size]; 1177 return [ a[0]/k, a[1]/k ]; 1178 } 1179 1180 float[2] _getpagesize(float[2] size) 1181 { 1182 if(size[0]>size[1]) 1183 return [ size[1], size[0] ]; 1184 else 1185 return size; 1186 } 1187 1188 //--------------------------------- 1189 1190 void _beginpage(char orientation, float[2] size) 1191 { 1192 ++page; 1193 pages[page] = appender!string(); 1194 state = 2; 1195 x = lMargin; 1196 y = tMargin; 1197 FontFamily = ""; 1198 // Check page size and orientation 1199 if (orientation!=CurOrientation || size[0]!=CurPageSize[0] || size[1]!=CurPageSize[1]) 1200 { 1201 // New size or orientation 1202 if(orientation=='P') 1203 { 1204 w = size[0]; 1205 h = size[1]; 1206 } 1207 else 1208 { 1209 w = size[1]; 1210 h = size[0]; 1211 } 1212 wPt = w*k; 1213 hPt = h*k; 1214 PageBreakTrigger = h-bMargin; 1215 CurOrientation = orientation; 1216 CurPageSize = size; 1217 } 1218 if(orientation!=DefOrientation || size[0]!=DefPageSize[0] || size[1]!=DefPageSize[1]) 1219 PageSizes[page] = [ wPt, hPt ]; 1220 } 1221 1222 //--------------------------------- 1223 1224 void _endpage() 1225 { 1226 state = 1; 1227 } 1228 1229 //--------------------------------- 1230 1231 /+ 1232 FontInfo _loadfont(string file) 1233 { 1234 // Reads a font definition from a JSON file. (Note different from original). 1235 FontInfo info; 1236 auto raw = readText(file); 1237 1238 auto decoded = parseJSON(raw); 1239 auto tLvl = decoded.object; 1240 info.type = tLvl["type"].str; 1241 info.name = tLvl["num"].str; 1242 info.up = tLvl["up"].integer; 1243 info.ut = tLvl["ut"].integer; 1244 info.cw.length = tLvl["cw"].array.length; 1245 foreach(x; tLvl["cw"].array) 1246 info.cw ~= x.uinteger; 1247 return info; 1248 } 1249 +/ 1250 1251 string _escape(string s) 1252 { 1253 // Escape special characters in strings 1254 return s.replace("\\","\\\\") 1255 .replace("(","\\(") 1256 .replace(")","\\)") 1257 .replace("\r","\\r"); 1258 } 1259 1260 string _textstring(string s) 1261 { 1262 // Format a text string 1263 return "("~_escape(s)~")"; 1264 } 1265 1266 string _dounderline(float x, float y, string txt) 1267 { 1268 // Underline text 1269 auto up = CurrentFont.up; 1270 auto ut = CurrentFont.ut; 1271 auto w = GetStringWidth(txt)+ws*txt.length; 1272 return format("%.2F %.2F %.2F %.2F re f",x*k,(h-(y-up/1000*FontSize))*k,w*k,-ut/1000*FontSizePt); 1273 } 1274 1275 ImageInfo _parsejpg(string file) 1276 { 1277 // Extract info from a JPEG file 1278 ImageInfo info; 1279 1280 info.data = cast(ubyte[]) read(file); 1281 auto ifImage = read_jpeg_from_mem(info.data); 1282 1283 info.w = cast(size_t) ifImage.w; 1284 info.h = cast(size_t) ifImage.h; 1285 switch(ifImage.c) 1286 { 1287 case ColFmt.Y: 1288 info.cs = "DeviceGray"; 1289 break; 1290 case ColFmt.RGB: 1291 info.cs = "DeviceRGB"; 1292 break; 1293 default: break; 1294 } 1295 info.bpc = 8; // something about bits? 1296 info.f = "DCTDecode"; 1297 return info; 1298 } 1299 1300 1301 ImageInfo _parsepng(string filename) 1302 { 1303 // Extract info from a PNG file 1304 1305 ImageInfo info; 1306 1307 File file; 1308 file.open(filename); 1309 scope(exit) { file.close(); } 1310 1311 auto sig = _readstream(file, 8); 1312 if (sig != [ 137, 'P','N','G', 13,10,26,10 ]) 1313 throw new Exception("Not a PNG file"); 1314 1315 _readstream(file, 4); 1316 if (_readstream(file, 4) != cast(ubyte[]) "IHDR") 1317 throw new Exception("Unsupported PNG type"); 1318 1319 info.w = _readint(file); 1320 info.h = _readint(file); 1321 info.bpc = _readstream(file,1)[0]; 1322 auto ct = _readstream(file,1)[0]; 1323 switch (ct) 1324 { 1325 case 0: 1326 case 4: 1327 info.cs = "DeviceGrey"; 1328 break; 1329 case 2: 1330 case 6: 1331 info.cs = "DeviceRGB"; 1332 break; 1333 case 3: 1334 info.cs = "Indexed"; 1335 break; 1336 default: 1337 throw new Exception("Unknown color type"); 1338 } 1339 auto compMethod = _readstream(file,1)[0]; 1340 auto filterMethod = _readstream(file,1)[0]; 1341 auto interlaceMethod = _readstream(file,1)[0]; 1342 _readstream(file, 4); 1343 1344 info.dp = "/Predictor 15 /Colors "~(info.cs=="DeviceRGB" ? "3" : "1")~" /BitsPerComponent "~to!string(info.bpc)~" /Columns "~to!string(info.w); 1345 info.f = "FlateDecode"; 1346 1347 // Scan chunks looking for palette, transparency and image data 1348 ubyte[] pal, data = []; 1349 1350 auto finish = false; 1351 do 1352 { 1353 auto n = _readint(file); 1354 string type = cast(string) _readstream(file,4); 1355 switch (type) 1356 { 1357 case "PLTE": 1358 // Read palette 1359 pal = _readstream(file,n); 1360 _readstream(file,4); 1361 break; 1362 case "tRNS": 1363 // Read transparency info 1364 auto t = _readstream(file,n); 1365 if(ct == 0) 1366 info.trns = t[1..2]; 1367 else if(ct == 2) 1368 info.trns = [ t[1], t[3], t[5] ]; 1369 else 1370 { 1371 auto pos = indexOf(cast(string)t, '\0'); 1372 if (pos != -1) 1373 info.trns = [ cast(ubyte)pos ]; 1374 } 1375 _readstream(file,4); 1376 break; 1377 case "IDAT": 1378 data ~= _readstream(file,n); 1379 _readstream(file,4); 1380 break; 1381 case "IEND": 1382 finish = true; 1383 break; 1384 default: 1385 _readstream(file,n+4); 1386 } 1387 } while (!finish); 1388 1389 if (ct >= 4) 1390 { 1391 auto color = appender!(ubyte[])(); 1392 auto alpha = appender!(ubyte[])(); 1393 data = cast(ubyte[]) uncompress(data); 1394 1395 if (ct == 4) 1396 { 1397 // Extract alpha imformation from Gray image 1398 auto len = cast(size_t) (2*info.w); 1399 1400 for(size_t i=0;i<info.h;++i) 1401 { 1402 size_t pos = (1+len)*i; 1403 color.put(data[pos]); 1404 alpha.put(data[pos]); 1405 auto line = data[pos+1..pos+1+len]; 1406 for (size_t j=0; j<line.length; j+=2) 1407 { 1408 color.put(line[j]); 1409 alpha.put(line[j+1]); 1410 } 1411 } 1412 } 1413 else 1414 { 1415 // Extract alpha imformation from RGB image 1416 auto len = cast(size_t) (4*info.w); 1417 for(size_t i=0;i<info.h;++i) 1418 { 1419 size_t pos = (1+len)*i; 1420 color.put(data[pos]); 1421 alpha.put(data[pos]); 1422 auto line = data[pos+1..pos+1+len]; 1423 for (size_t j=0; j<line.length; j+=4) 1424 { 1425 color.put(line[j..j+3]); 1426 alpha.put(line[j+3]); 1427 } 1428 } 1429 } 1430 1431 info.data = cast(ubyte[]) .compress(color.data); 1432 info.smask = cast(ubyte[]) .compress(alpha.data); 1433 if(pdfVersion<1.4) 1434 pdfVersion = 1.4; 1435 } 1436 else 1437 info.data = data; 1438 1439 return info; 1440 } 1441 1442 1443 ubyte[] _readstream(ref File f, uint n) 1444 { 1445 ubyte[] buffer; 1446 buffer.length = n; 1447 1448 return f.rawRead(buffer); 1449 } 1450 1451 uint _readint(ref File f) 1452 { 1453 return bigEndianToNative!uint(_readstream(f,4)[0..4]); 1454 } 1455 1456 /+ 1457 void _parsegif(string file) 1458 { 1459 } 1460 +/ 1461 1462 void _newobj() 1463 { 1464 // Begin a new object 1465 ++n; 1466 offsets.length = n+1; 1467 offsets[n] = buffer.data.length; 1468 _out(to!string(n)~" 0 obj"); 1469 } 1470 1471 void _putstream(string s) 1472 { 1473 _out("stream"); 1474 _out(s); 1475 _out("endstream"); 1476 } 1477 1478 //--------------------------------- 1479 1480 void _out(string s) 1481 { 1482 // Add a line to the document 1483 if(state==2) 1484 { 1485 pages[page].put(s); 1486 pages[page].put("\n"); 1487 } 1488 else 1489 { 1490 buffer.put(s); 1491 buffer.put("\n"); 1492 } 1493 } 1494 1495 //--------------------------------- 1496 1497 void _putpages() 1498 { 1499 auto nb = page; 1500 1501 if(DefOrientation=='P') 1502 { 1503 wPt = DefPageSize[0]*k; 1504 hPt = DefPageSize[1]*k; 1505 } 1506 else 1507 { 1508 wPt = DefPageSize[1]*k; 1509 hPt = DefPageSize[0]*k; 1510 } 1511 auto filter = (compress) ? "/Filter /FlateDecode " : ""; 1512 for(auto n=1;n<=nb;n++) 1513 { 1514 // Page 1515 _newobj(); 1516 _out("<</Type /Page"); 1517 _out("/Parent 1 0 R"); 1518 if(n in PageSizes) 1519 _out(format("/MediaBox [0 0 %.2F %.2F]",PageSizes[n][0],PageSizes[n][1])); 1520 _out("/Resources 2 0 R"); 1521 if(n in PageLinks) 1522 { 1523 // Links 1524 auto annots = "/Annots ["; 1525 foreach(link; PageLinks[n]) 1526 { 1527 auto rect = format("%.2F %.2F %.2F %.2F",link.x,link.y,link.x+link.w,link.y-link.h); 1528 annots ~= "<</Type /Annot /Subtype /Link /Rect ["~rect~"] /Border [0 0 0] "; 1529 if(!link.externalLink.empty) 1530 annots ~= "/A <</S /URI /URI "~_textstring(link.externalLink)~">>>>"; 1531 else 1532 { 1533 auto l = links[link.link-1]; 1534 h = (l.page in PageSizes) ? PageSizes[l.page][1] : hPt; 1535 annots ~= format("/Dest [%d 0 R /XYZ 0 %.2F null]>>",1+2*l.page,h-l.y*k); 1536 } 1537 } 1538 _out(annots~"]"); 1539 } 1540 if(pdfVersion>1.3) 1541 _out("/Group <</Type /Group /S /Transparency /CS /DeviceRGB>>"); 1542 _out("/Contents "~.to!string(this.n+1)~" 0 R>>"); 1543 _out("endobj"); 1544 1545 // Page content 1546 auto p = pages[n].data; 1547 if(!aliasNbPages.empty) 1548 p = p.replace(aliasNbPages,to!string(nb)); 1549 _newobj(); 1550 auto p2 = compress ? (cast(string) .compress(p)) : p; 1551 1552 _out("<<"~filter~"/Length "~to!string(p2.length)~">>"); 1553 _putstream(p2); 1554 _out("endobj"); 1555 } 1556 // Pages root 1557 offsets[1] = buffer.data.length; 1558 _out("1 0 obj"); 1559 _out("<</Type /Pages"); 1560 auto kids = "/Kids ["; 1561 for(auto i=0;i<nb;i++) 1562 kids ~= to!string(3+2*i)~" 0 R "; 1563 _out(kids~"]"); 1564 _out("/Count "~to!string(nb)); 1565 _out(format("/MediaBox [0 0 %.2F %.2F]",wPt,hPt)); 1566 _out(">>"); 1567 _out("endobj"); 1568 } 1569 1570 //--------------------------------- 1571 1572 void _putfonts() 1573 { 1574 auto nf = n; 1575 foreach(diff; diffs) 1576 { 1577 // Encodings 1578 _newobj(); 1579 _out("<</Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences ["~diff~"]>>"); 1580 _out("endobj"); 1581 }/* 1582 foreach(file,info ; FontFiles) 1583 { 1584 // Font file embedding 1585 _newobj(); 1586 FontFiles[file].n = n; 1587 auto font = readFile(fontpath~file); 1588 1589 bool compressed = file[$-2..$]==".z"); 1590 if(!compressed && info.length2 != 0)) 1591 font = font[6,info.length1 ~ font[6+info.length1+6..info.length2]; 1592 _out("<</Length "~to!string(font.length)); 1593 if(compressed) 1594 _out("/Filter /FlateDecode"); 1595 _out("/Length1 "~to!string(info.length2); 1596 if(info.length2 != 0) 1597 _out("/Length2 "~to!string(info.length2)~" /Length3 0"); 1598 _out(">>"); 1599 _putstream(font); 1600 _out("endobj"); 1601 }*/ 1602 foreach(ref font; fonts) 1603 { 1604 // Font objects 1605 font.n = n+1; 1606 auto type = font.type; 1607 auto name = font.name; 1608 if (type=="Core") 1609 { 1610 // Core font 1611 _newobj(); 1612 _out("<</Type /Font"); 1613 _out("/BaseFont /"~name); 1614 _out("/Subtype /Type1"); 1615 if(name!="Symbol" && name!="ZapfDingbats") 1616 _out("/Encoding /WinAnsiEncoding"); 1617 _out(">>"); 1618 _out("endobj"); 1619 } 1620 else if(type=="Type1" || type=="TrueType") 1621 { 1622 // Additional Type1 or TrueType/OpenType font 1623 _newobj(); 1624 _out("<</Type /Font"); 1625 _out("/BaseFont /"~name); 1626 _out("/Subtype /"~type); 1627 _out("/FirstChar 32 /LastChar 255"); 1628 _out("/Widths "~to!string(n+1)~" 0 R"); 1629 _out("/FontDescriptor "~to!string(n+2)~" 0 R"); 1630 if(font.diffn != ulong.max) 1631 _out("/Encoding "~to!string(nf+font.diffn)~" 0 R"); 1632 else 1633 _out("/Encoding /WinAnsiEncoding"); 1634 _out(">>"); 1635 _out("endobj"); 1636 // Widths 1637 _newobj(); 1638 auto s = appender!string; s.put("["); 1639 for(auto i=32;i<=255;i++) 1640 { 1641 s.put(to!string(font.cw[i])); 1642 s.put(" "); 1643 } 1644 s.put("]"); 1645 _out(s.data); 1646 _out("endobj"); 1647 // Descriptor 1648 _newobj(); 1649 s= appender!string; s.put("<</Type /FontDescriptor /FontName /"); s.put(name); 1650 foreach(k,v; font.desc) 1651 s.put(" /"~k~" "~v); 1652 if (!font.file.empty) 1653 s.put(" /FontFile"~(type=="Type1" ? "" : "2")~" "~to!string(FontFiles[font.file].n)~" 0 R"); 1654 _out(s.data~">>"); 1655 _out("endobj"); 1656 } 1657 else 1658 { 1659 throw new Exception("Unsupported font type: "~type); 1660 } 1661 } 1662 } 1663 1664 //--------------------------------- 1665 1666 void _putimages() 1667 { 1668 foreach(ref info; images) 1669 { 1670 _putimage(info); 1671 info.data = info.data.init; 1672 info.smask = null; 1673 } 1674 } 1675 1676 //--------------------------------- 1677 1678 void _putimage(ref ImageInfo info) 1679 { 1680 _newobj(); 1681 info.n = n; 1682 _out("<</Type /XObject"); 1683 _out("/Subtype /Image"); 1684 _out("/Width "~to!string(info.w)); 1685 _out("/Height "~to!string(info.h)); 1686 if(info.cs=="Indexed") 1687 _out("/ColorSpace [/Indexed /DeviceRGB "~to!string(info.pal.length/3-1)~" "~to!string(n+1)~" 0 R]"); 1688 else 1689 { 1690 _out("/ColorSpace /"~info.cs); 1691 if(info.cs=="DeviceCMYK") 1692 _out("/Decode [1 0 1 0 1 0 1 0]"); 1693 } 1694 _out("/BitsPerComponent "~to!string(info.bpc)); 1695 if(!info.f.empty) 1696 _out("/Filter /"~info.f); 1697 if(!info.dp.empty) 1698 _out("/DecodeParms <<"~info.dp~">>"); 1699 1700 if (!info.trns.empty) 1701 { 1702 auto trns = ""; 1703 foreach (i; info.trns) 1704 trns ~= to!string(i)~" "~to!string(i)~" "; 1705 _out("/Mask ["~trns~"]"); 1706 } 1707 1708 if (!info.smask.empty) 1709 _out("/SMask "~to!string(n+1)~" 0 R"); 1710 1711 _out("/Length "~to!string(info.data.length)~">>"); 1712 _putstream(cast(string) info.data); 1713 _out("endobj"); 1714 1715 // Soft mask 1716 if (!info.smask.empty) 1717 { 1718 auto dp = "/Predictor 15 /Colors 1 /BitsPerComponent 8 /Columns "~to!string(info.w); 1719 auto smask = ImageInfo(0,0,info.w,info.h,"DeviceGray", 8, info.f, dp, null,null,info.smask); 1720 _putimage(smask); 1721 } 1722 1723 // Palette 1724 if(info.cs=="Indexed") 1725 { 1726 auto filter = (compress) ? "/Filter /FlateDecode " : ""; 1727 string pal = (compress) ? cast(string) .compress(info.pal) : info.pal; 1728 _newobj(); 1729 _out("<<"~filter~"/Length "~to!string(pal.length)~">>"); 1730 _putstream(pal); 1731 _out("endobj"); 1732 } 1733 } 1734 1735 //--------------------------------- 1736 1737 void _putxobjectdict() 1738 { 1739 foreach(image; images) 1740 _out("/I"~to!string(image.i)~" "~to!string(image.n)~" 0 R"); 1741 } 1742 1743 //--------------------------------- 1744 1745 void _putresourcedict() 1746 { 1747 _out("/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]"); 1748 _out("/Font <<"); 1749 foreach(font; fonts) 1750 _out("/F"~to!string(font.i)~" "~to!string(font.n)~" 0 R"); 1751 _out(">>"); 1752 _out("/XObject <<"); 1753 _putxobjectdict(); 1754 _out(">>"); 1755 } 1756 1757 //--------------------------------- 1758 1759 void _putresources() 1760 { 1761 _putfonts(); 1762 _putimages(); 1763 // Resource dictionary 1764 offsets[2] = buffer.data.length; 1765 _out("2 0 obj"); 1766 _out("<<"); 1767 _putresourcedict(); 1768 _out(">>"); 1769 _out("endobj"); 1770 } 1771 1772 //--------------------------------- 1773 1774 void _putinfo() 1775 { 1776 _out("/Producer "~_textstring("FPDF (for D) "~to!string(fpdfVersion))); 1777 if(!title.empty) 1778 _out("/Title "~_textstring(title)); 1779 if(!subject.empty) 1780 _out("/Subject "~_textstring(subject)); 1781 if(!author.empty) 1782 _out("/Author "~_textstring(author)); 1783 if(!keywords.empty) 1784 _out("/Keywords "~_textstring(keywords.join(","))); 1785 if(!creator.empty) 1786 _out("/Creator "~_textstring(creator)); 1787 1788 auto time = Clock.currTime.toISOString().replace("T",""); 1789 auto dot = indexOf(time, "."); 1790 if (dot == -1) dot = time.length; 1791 _out("/CreationDate "~_textstring("D:"~time[0..dot])); 1792 } 1793 1794 //--------------------------------- 1795 1796 void _putcatalog() 1797 { 1798 _out("/Type /Catalog"); 1799 _out("/Pages 1 0 R"); 1800 if(zoomMode=="fullpage") 1801 _out("/OpenAction [3 0 R /Fit]"); 1802 else if(zoomMode=="fullwidth") 1803 _out("/OpenAction [3 0 R /FitH null]"); 1804 else if(zoomMode=="real") 1805 _out("/OpenAction [3 0 R /XYZ null null 1]"); 1806 else if(zoomMode == "custom") 1807 _out("/OpenAction [3 0 R /XYZ null null "~format("%.2F",zoom/100)~"]"); 1808 if(layoutMode=="single") 1809 _out("/PageLayout /SinglePage"); 1810 else if(layoutMode=="continuous") 1811 _out("/PageLayout /OneColumn"); 1812 else if(layoutMode=="two") 1813 _out("/PageLayout /TwoColumnLeft"); 1814 } 1815 1816 //--------------------------------- 1817 1818 void _putheader() 1819 { 1820 _out("%PDF-"~to!string(pdfVersion)); 1821 } 1822 1823 //--------------------------------- 1824 1825 void _puttrailer() 1826 { 1827 _out("/Size "~to!string(n+1)); 1828 _out("/Root "~to!string(n)~" 0 R"); 1829 _out("/Info "~to!string(n-1)~" 0 R"); 1830 } 1831 1832 //--------------------------------- 1833 1834 void _enddoc() 1835 { 1836 _putheader(); 1837 _putpages(); 1838 _putresources(); 1839 // Info 1840 _newobj(); 1841 _out("<<"); 1842 _putinfo(); 1843 _out(">>"); 1844 _out("endobj"); 1845 // Catalog 1846 _newobj(); 1847 _out("<<"); 1848 _putcatalog(); 1849 _out(">>"); 1850 _out("endobj"); 1851 // Cross-ref 1852 auto o = buffer.data.length; 1853 _out("xref"); 1854 _out("0 "~to!string(n+1)); 1855 _out("0000000000 65535 f "); 1856 for(auto i=1;i<=n;i++) 1857 _out(format("%010d 00000 n ",offsets[i])); 1858 // Trailer 1859 _out("trailer"); 1860 _out("<<"); 1861 _puttrailer(); 1862 _out(">>"); 1863 _out("startxref"); 1864 _out(to!string(o)); 1865 _out("%%EOF"); 1866 state = 3; 1867 } 1868 1869 //--------------------------------- 1870 } 1871 1872 //---------------------------------------------------------------------------- 1873 // An addon to help construct tables. 1874 1875 struct FpdfTable 1876 //---------------------------------------------------------------------------- 1877 { 1878 Fpdf fpdf; 1879 float[] widths; 1880 string[] aligns; 1881 1882 void SetWidths(float[] w) 1883 { 1884 //Set the array of column widths 1885 widths = w; 1886 } 1887 1888 void SetAligns(string[] a) 1889 { 1890 //Set the array of column alignments 1891 aligns = a; 1892 } 1893 1894 void Row(string[] data, float lh) 1895 { 1896 assert(data.length == widths.length); 1897 assert(data.length == aligns.length); 1898 //Calculate the height of the row 1899 ulong nb=0; 1900 1901 for(size_t i=0;i<data.length;++i) 1902 nb=max(nb,NbLines(widths[i],data[i])); 1903 auto h = lh * nb; 1904 1905 //Issue a page break first if needed 1906 CheckPageBreak(h); 1907 1908 //Draw the cells of the row 1909 for(size_t i=0; i<data.length;++i) 1910 { 1911 auto w=widths[i]; 1912 auto a= (i<aligns.length) ? aligns[i] : "L"; 1913 //Save the current position 1914 auto x=fpdf.GetX(); 1915 auto y=fpdf.GetY(); 1916 //Draw the border 1917 fpdf.Rect(x,y,w,h); 1918 //Print the text 1919 fpdf.MultiCell(w,lh,data[i],"0",a); 1920 //Put the position to the right of the cell 1921 fpdf.SetXY(x+w,y); 1922 } 1923 //Go to the next line 1924 fpdf.Ln(h); 1925 } 1926 1927 void CheckPageBreak(float h) 1928 { 1929 //If the height h would cause an overflow, add a new page immediately 1930 if (fpdf.GetY()+h > fpdf.PageBreakTrigger) 1931 fpdf.AddPage(fpdf.CurOrientation); 1932 } 1933 1934 ulong NbLines(float w, string txt) 1935 { 1936 //Computes the number of lines a MultiCell of width w will take 1937 auto cw = fpdf.CurrentFont.cw; 1938 if(w==0) 1939 w = fpdf.w-fpdf.rMargin-fpdf.x; 1940 auto wmax=(w-2*fpdf.cMargin)*1000/fpdf.FontSize; 1941 auto s=txt.replace("\r",""); 1942 auto nb=s.length; 1943 1944 if (nb>0 && s[$-1]=='\n') 1945 --nb; 1946 1947 auto sep=-1; 1948 auto i=0; 1949 auto j=0; 1950 auto l=0; 1951 auto nl=1; 1952 1953 while(i<nb) 1954 { 1955 auto c= s[i]; 1956 if(c=='\n') 1957 { 1958 ++i; 1959 sep=-1; 1960 j=i; 1961 l=0; 1962 nl++; 1963 continue; 1964 } 1965 if(c==' ') 1966 sep=i; 1967 l += cw[c]; 1968 if (l> wmax) 1969 { 1970 if(sep==-1) 1971 { 1972 if(i==j) 1973 i++; 1974 } 1975 else 1976 i=sep+1; 1977 sep=-1; 1978 j=i; 1979 l=0; 1980 nl++; 1981 } 1982 else 1983 ++i; 1984 } 1985 return nl; 1986 } 1987 }