1 51 package org.apache.fop.render.pdf; 52 53 import org.apache.fop.render.PrintRenderer; 55 import org.apache.fop.image.FopImage; 56 import org.apache.fop.image.FopImageException; 57 import org.apache.fop.apps.FOPException; 58 import org.apache.fop.fo.properties.*; 59 import org.apache.fop.layout.inline.*; 60 import org.apache.fop.svg.*; 61 import org.apache.fop.pdf.*; 62 import org.apache.fop.layout.*; 63 import org.apache.fop.image.*; 64 import org.apache.fop.extensions.*; 65 import org.apache.fop.render.pdf.fonts.LazyFont; 66 67 import org.apache.batik.bridge.*; 68 import org.apache.batik.gvt.*; 69 import org.apache.batik.gvt.renderer.*; 70 71 import org.w3c.dom.*; 72 import org.w3c.dom.svg.*; 73 74 import java.io.IOException ; 76 import java.io.OutputStream ; 77 import java.util.List ; 78 import java.util.Map ; 79 import java.awt.geom.AffineTransform ; 80 81 94 public class PDFRenderer extends PrintRenderer { 95 96 99 protected PDFDocument pdfDoc; 100 101 104 protected PDFResources pdfResources; 105 106 109 PDFStream currentStream; 110 111 114 PDFAnnotList currentAnnotList; 115 116 119 PDFPage currentPage; 120 121 PDFColor currentColor; 122 123 float currentLetterSpacing = Float.MAX_VALUE; 124 125 128 boolean textOpen = false; 129 130 134 int prevWordY = 0; 135 136 140 int prevWordX = 0; 141 142 145 int prevWordWidth = 0; 146 147 150 private StringBuffer _wordAreaPDF = new StringBuffer (); 151 152 155 protected java.util.Map options; 156 157 protected List extensions = null; 158 159 162 public PDFRenderer() { 163 this.pdfDoc = new PDFDocument(); 164 } 165 166 171 public void setOptions(java.util.Map options) { 172 this.options = options; 173 174 boolean encrypt = false; 176 String oPassword = ""; 177 String uPassword = ""; 178 boolean allowPrint = true; 179 boolean allowCopyContent = true; 180 boolean allowEditContent = true; 181 boolean allowEditAnnotations = true; 182 String option; 183 184 option = (String ) options.get("ownerPassword"); 185 if (option != null) { 186 encrypt = true; 187 oPassword = option; 188 } 189 option = (String ) options.get("userPassword"); 190 if (option != null) { 191 encrypt = true; 192 uPassword = option; 193 } 194 option = (String ) options.get("allowPrint"); 195 if (option != null) { 196 encrypt = true; 197 allowPrint = option.equals("TRUE"); 198 } 199 option = (String ) options.get("allowCopyContent"); 200 if (option != null) { 201 encrypt = true; 202 allowCopyContent = option.equals("TRUE"); 203 } 204 option = (String ) options.get("allowEditContent"); 205 if (option != null) { 206 encrypt = true; 207 allowEditContent = option.equals("TRUE"); 208 } 209 option = (String ) options.get("allowEditAnnotations"); 210 if (option != null) { 211 encrypt = true; 212 allowEditAnnotations = option.equals("TRUE"); 213 } 214 if (encrypt) { 215 this.pdfDoc.setEncryption(oPassword,uPassword,allowPrint,allowCopyContent, 216 allowEditContent, allowEditAnnotations); 217 } 218 } 219 220 225 public void setProducer(String producer) { 226 this.pdfDoc.setProducer(producer); 227 } 228 229 235 public void startRenderer(OutputStream stream) 236 throws IOException { 237 pdfDoc.outputHeader(stream); 238 } 239 240 246 public void stopRenderer(OutputStream stream) 247 throws IOException { 248 renderRootExtensions(extensions); 249 FontSetup.addToResources(this.pdfDoc, fontInfo); 250 pdfDoc.outputTrailer(stream); 251 252 this.pdfDoc = new PDFDocument(); 254 this.pdfResources = null; 255 extensions = null; 256 currentStream = null; 257 currentAnnotList = null; 258 currentPage = null; 259 currentColor = null; 260 super.stopRenderer(stream); 261 } 262 263 273 protected void addLine(int x1, int y1, int x2, int y2, int th, 274 PDFPathPaint stroke) { 275 closeText(); 276 277 currentStream.add("ET\nq\n" + stroke.getColorSpaceOut(false) 278 + (x1 / 1000f) + " " + (y1 / 1000f) + " m " 279 + (x2 / 1000f) + " " + (y2 / 1000f) + " l " 280 + (th / 1000f) + " w S\n" + "Q\nBT\n"); 281 } 282 283 294 protected void addLine(int x1, int y1, int x2, int y2, int th, int rs, 295 PDFPathPaint stroke) { 296 closeText(); 297 currentStream.add("ET\nq\n" + stroke.getColorSpaceOut(false) 298 + setRuleStylePattern(rs) + (x1 / 1000f) + " " 299 + (y1 / 1000f) + " m " + (x2 / 1000f) + " " 300 + (y2 / 1000f) + " l " + (th / 1000f) + " w S\n" 301 + "Q\nBT\n"); 302 } 303 304 313 protected void addRect(int x, int y, int w, int h, PDFPathPaint stroke) { 314 closeText(); 315 currentStream.add("ET\nq\n" + stroke.getColorSpaceOut(false) 316 + (x / 1000f) + " " + (y / 1000f) + " " 317 + (w / 1000f) + " " + (h / 1000f) + " re s\n" 318 + "Q\nBT\n"); 319 } 320 321 331 protected void addRect(int x, int y, int w, int h, PDFPathPaint stroke, 332 PDFPathPaint fill) { 333 closeText(); 334 currentStream.add("ET\nq\n" + fill.getColorSpaceOut(true) 335 + stroke.getColorSpaceOut(false) + (x / 1000f) 336 + " " + (y / 1000f) + " " + (w / 1000f) + " " 337 + (h / 1000f) + " re b\n" + "Q\nBT\n"); 338 } 339 340 349 protected void addFilledRect(int x, int y, int w, int h, 350 PDFPathPaint fill) { 351 closeText(); 352 currentStream.add("ET\nq\n" + fill.getColorSpaceOut(true) 353 + (x / 1000f) + " " + (y / 1000f) + " " 354 + (w / 1000f) + " " + (h / 1000f) + " re f\n" 355 + "Q\nBT\n"); 356 } 357 358 371 protected void drawImageScaled(int x, int y, int w, int h, 372 FopImage image, 373 FontState fs) { 374 if (image instanceof SVGImage) { 375 try { 376 closeText(); 377 378 SVGDocument svg = ((SVGImage) image).getSVGDocument(); 379 currentStream.add("ET\nq\n"); 380 renderSVGDocument(svg, x, y, fs); 381 currentStream.add("Q\nBT\n"); 382 } catch (FopImageException e) {} 383 384 } else { 385 int xObjectNum = this.pdfDoc.addImage(image); 386 closeText(); 387 currentStream.add("ET\nq\n" + (((float) w) / 1000f) + " 0 0 " 388 + (((float) h) / 1000f) + " " 389 + (((float) x) / 1000f) + " " 390 + (((float) y - h) / 1000f) + " cm\n" + "/Im" 391 + xObjectNum + " Do\nQ\nBT\n"); 392 } 393 } 394 395 408 protected void drawImageClipped(int x, int y, 409 int clipX, int clipY, 410 int clipW, int clipH, 411 FopImage image, 412 FontState fs) { 413 414 float cx1 = ((float) x) / 1000f; 415 float cy1 = ((float) y - clipH) / 1000f; 416 417 float cx2 = ((float) x + clipW) / 1000f; 418 float cy2 = ((float) y) / 1000f; 419 420 int imgX = x - clipX; 421 int imgY = y - clipY; 422 423 int imgW; 424 int imgH; 425 try { 426 imgW = image.getWidth() * 1000; 428 imgH = image.getHeight() * 1000; 429 } catch (FopImageException fie) { 430 log.error("Error obtaining image width and height", fie); 431 return; 432 } 433 434 if (image instanceof SVGImage) { 435 try { 436 closeText(); 437 438 SVGDocument svg = ((SVGImage) image).getSVGDocument(); 439 currentStream.add("ET\nq\n" + 440 cx1 + " " + cy1 + " m\n" + 442 cx2 + " " + cy1 + " l\n" + 443 cx2 + " " + cy2 + " l\n" + 444 cx1 + " " + cy2 + " l\n" + 445 "W\n" + 446 "n\n"); 447 renderSVGDocument(svg, imgX, imgY, fs); 448 currentStream.add("Q\nBT\n"); 449 } catch (FopImageException e) {} 450 451 } else { 452 int xObjectNum = this.pdfDoc.addImage(image); 453 closeText(); 454 currentStream.add("ET\nq\n" + 455 cx1 + " " + cy1 + " m\n" + 457 cx2 + " " + cy1 + " l\n" + 458 cx2 + " " + cy2 + " l\n" + 459 cx1 + " " + cy2 + " l\n" + 460 "W\n" + 461 "n\n" + 462 (((float) imgW) / 1000f) + " 0 0 " + 464 (((float) imgH) / 1000f) + " " + 465 (((float) imgX) / 1000f) + " " + 466 (((float) imgY - imgH) / 1000f) + " cm\n" + 467 "s\n" + 468 "/Im" + xObjectNum + " Do\nQ\nBT\n"); 470 } 471 } 472 473 478 public void renderForeignObjectArea(ForeignObjectArea area) { 479 this.currentXPosition = this.currentXPosition + area.getXOffset(); 481 this.currentYPosition = this.currentYPosition; 482 switch (area.getAlign()) { 483 case TextAlign.START: 484 break; 485 case TextAlign.END: 486 break; 487 case TextAlign.CENTER: 488 case TextAlign.JUSTIFY: 489 break; 490 } 491 switch (area.getVerticalAlign()) { 492 case VerticalAlign.BASELINE: 493 break; 494 case VerticalAlign.MIDDLE: 495 break; 496 case VerticalAlign.SUB: 497 break; 498 case VerticalAlign.SUPER: 499 break; 500 case VerticalAlign.TEXT_TOP: 501 break; 502 case VerticalAlign.TEXT_BOTTOM: 503 break; 504 case VerticalAlign.TOP: 505 break; 506 case VerticalAlign.BOTTOM: 507 break; 508 } 509 closeText(); 510 511 currentStream.add("ET\n"); 513 currentStream.add("q\n"); 515 switch (area.scalingMethod()) { 516 case Scaling.UNIFORM: 517 break; 518 case Scaling.NON_UNIFORM: 519 break; 520 } 521 switch (area.getOverflow()) { 525 case Overflow.VISIBLE: 526 case Overflow.SCROLL: 527 case Overflow.AUTO: 528 break; 529 case Overflow.HIDDEN: 530 break; 531 } 532 533 area.getObject().render(this); 534 currentStream.add("Q\n"); 535 currentStream.add("BT\n"); 536 this.currentXPosition += area.getEffectiveWidth(); 537 } 539 540 545 public void renderSVGArea(SVGArea area) { 546 int x = this.currentXPosition; 548 int y = this.currentYPosition; 549 renderSVGDocument(area.getSVGDocument(), x, y, area.getFontState()); 550 } 551 552 560 protected void renderSVGDocument(Document doc, int x, int y, 561 FontState fs) { 562 float sx = 1; 563 float sy = -1; 564 int xOffset = x; 565 int yOffset = y; 566 567 org.apache.fop.svg.SVGUserAgent userAgent 568 = new org.apache.fop.svg.SVGUserAgent(new AffineTransform ()); 569 userAgent.setLogger(log); 570 571 GVTBuilder builder = new GVTBuilder(); 572 BridgeContext ctx = new BridgeContext(userAgent); 573 TextPainter textPainter = null; 574 Boolean bl = 575 org.apache.fop.configuration.Configuration.getBooleanValue("strokeSVGText"); 576 if (bl == null || bl.booleanValue()) { 577 textPainter = new StrokingTextPainter(); 578 } else { 579 textPainter = new PDFTextPainter(fs); 580 } 581 ctx.setTextPainter(textPainter); 582 583 PDFAElementBridge aBridge = new PDFAElementBridge(); 584 aBridge.setCurrentTransform(new AffineTransform (sx, 0, 0, 585 sy, xOffset / 1000f, yOffset / 1000f)); 586 ctx.putBridge(aBridge); 587 588 GraphicsNode root; 589 try { 590 root = builder.build(ctx, doc); 591 } catch (Exception e) { 592 log.error("svg graphic could not be built: " 593 + e.getMessage(), e); 594 return; 595 } 596 float w = (float) ctx.getDocumentSize().getWidth() * 1000f; 598 float h = (float) ctx.getDocumentSize().getHeight() * 1000f; 599 ctx = null; 600 builder = null; 601 602 607 currentStream.add("q\n"); 608 if (w != 0 && h != 0) { 609 currentStream.add(x / 1000f + " " + y / 1000f + " m\n"); 610 currentStream.add((x + w) / 1000f + " " + y / 1000f + " l\n"); 611 currentStream.add((x + w) / 1000f + " " + (y - h) / 1000f 612 + " l\n"); 613 currentStream.add(x / 1000f + " " + (y - h) / 1000f + " l\n"); 614 currentStream.add("h\n"); 615 currentStream.add("W\n"); 616 currentStream.add("n\n"); 617 } 618 currentStream.add(sx + " 0 0 " + sy + " " + xOffset / 1000f + " " 622 + yOffset / 1000f + " cm\n"); 623 624 SVGSVGElement svg = ((SVGDocument) doc).getRootElement(); 625 AffineTransform at = ViewBox.getPreserveAspectRatioTransform(svg, 626 w / 1000f, h / 1000f); 627 if (!at.isIdentity()) { 628 double[] vals = new double[6]; 629 at.getMatrix(vals); 630 currentStream.add(PDFNumber.doubleOut(vals[0], 8) + " " 631 + PDFNumber.doubleOut(vals[1], 8) + " " 632 + PDFNumber.doubleOut(vals[2], 8) + " " 633 + PDFNumber.doubleOut(vals[3], 8) + " " 634 + PDFNumber.doubleOut(vals[4], 8) + " " 635 + PDFNumber.doubleOut(vals[5], 8) + " cm\n"); 636 } 637 638 PDFGraphics2D graphics = new PDFGraphics2D(true, fs, pdfDoc, 639 currentFontName, 640 currentFontSize, 641 currentXPosition, 642 currentYPosition); 643 graphics.setGraphicContext( 644 new org.apache.batik.ext.awt.g2d.GraphicContext()); 645 646 try { 647 root.paint(graphics); 648 currentStream.add(graphics.getString()); 649 } catch (Exception e) { 650 log.error("svg graphic could not be rendered: " 651 + e.getMessage(), e); 652 } 653 654 currentAnnotList = graphics.getAnnotList(); 655 656 currentStream.add("Q\n"); 657 } 658 659 664 public void renderWordArea(WordArea area) { 665 synchronized (_wordAreaPDF) { 666 StringBuffer pdf = _wordAreaPDF; 667 pdf.setLength(0); 668 669 Map kerning = null; 670 boolean kerningAvailable = false; 671 672 kerning = area.getFontState().getKerning(); 673 if (kerning != null && !kerning.isEmpty()) { 674 kerningAvailable = true; 675 } 676 677 String name = area.getFontState().getFontName(); 678 int size = area.getFontState().getFontSize(); 679 680 boolean useMultiByte = false; 682 Font f = (Font) area.getFontState(). 683 getFontInfo().getFonts().get(name); 684 if (f instanceof LazyFont) { 685 if (((LazyFont) f).getRealFont() instanceof CIDFont) { 686 useMultiByte = true; 687 } 688 } else if (f instanceof CIDFont) { 689 useMultiByte = true; 690 } 691 String startText = useMultiByte ? "<" : "("; 693 String endText = useMultiByte ? "> " : ") "; 694 695 if ((!name.equals(this.currentFontName)) 696 || (size != this.currentFontSize)) { 697 closeText(); 698 699 this.currentFontName = name; 700 this.currentFontSize = size; 701 pdf = pdf.append("/" + name + " " + ((float)size / 1000) + " Tf\n"); 702 } 703 704 float letterspacing = 706 ((float) area.getFontState().getLetterSpacing()) / 1000; 707 if (letterspacing != this.currentLetterSpacing) { 708 this.currentLetterSpacing = letterspacing; 709 closeText(); 710 pdf.append(letterspacing).append(" Tc\n"); 711 } 712 713 PDFColor areaColor = null; 714 if (this.currentFill instanceof PDFColor) { 715 areaColor = (PDFColor) this.currentFill; 716 } 717 718 if (areaColor == null || areaColor.red() != (double) area.getRed() 719 || areaColor.green() != (double) area.getGreen() 720 || areaColor.blue() != (double) area.getBlue()) { 721 722 areaColor = new PDFColor((double) area.getRed(), 723 (double) area.getGreen(), 724 (double) area.getBlue()); 725 726 closeText(); 727 this.currentFill = areaColor; 728 pdf.append(this.currentFill.getColorSpaceOut(true)); 729 } 730 731 int rx = this.currentXPosition; 732 int bl = this.currentYPosition; 733 734 addWordLines(area, rx, bl, size, areaColor); 735 736 if (!textOpen || bl != prevWordY) { 737 closeText(); 738 739 pdf.append("1 0 0 1 " + (rx / 1000f) + " " + (bl / 1000f) 740 + " Tm [" + startText); 741 prevWordY = bl; 742 textOpen = true; 743 } else { 744 int space = prevWordX - rx + prevWordWidth; 746 float emDiff = (float) space / (float) currentFontSize * 1000f; 747 if (emDiff < -33000) { 750 closeText(); 751 752 pdf.append("1 0 0 1 " + (rx / 1000f) + " " + (bl / 1000f) 753 + " Tm [" + startText); 754 textOpen = true; 755 } else { 756 pdf.append(Float.toString(emDiff)); 757 pdf.append(" "); 758 pdf.append(startText); 759 } 760 } 761 prevWordWidth = area.getContentWidth(); 762 prevWordX = rx; 763 764 String s = area.getText(); 765 int l = s.length(); 766 767 for (int i = 0; i < l; i++) { 768 char ch = area.getFontState().mapChar(s.charAt(i)); 769 770 if (!useMultiByte) { 771 if (ch > 127) { 772 pdf.append("\\"); 773 pdf.append(Integer.toOctalString((int) ch)); 774 775 } else { 776 switch (ch) { 777 case '(': 778 case ')': 779 case '\\': 780 pdf.append("\\"); 781 break; 782 } 783 pdf.append(ch); 784 } 785 } else { 786 pdf.append(getUnicodeString(ch)); 787 } 788 789 if (kerningAvailable && (i + 1) < l) { 790 addKerning(pdf, (new Integer ((int) ch)), 791 (new Integer ((int) area.getFontState().mapChar(s.charAt(i + 1)))), 792 kerning, startText, endText); 793 } 794 } 795 pdf.append(endText); 796 797 currentStream.add(pdf.toString()); 798 799 this.currentXPosition += area.getContentWidth(); 800 801 } 802 } 803 804 810 private String getUnicodeString(char c) { 811 812 StringBuffer buf = new StringBuffer (4); 813 814 byte[] uniBytes = null; 815 try { 816 char[] a = {c}; 817 uniBytes = new String (a).getBytes("UnicodeBigUnmarked"); 818 } catch (Exception e) { 819 throw new org.apache.avalon.framework.CascadingRuntimeException("Incompatible VM", e); 821 } 822 823 for (int i = 0; i < uniBytes.length; i++) { 824 int b = (uniBytes[i] < 0) ? (int) (256 + uniBytes[i]) 825 : (int) uniBytes[i]; 826 827 String hexString = Integer.toHexString(b); 828 if (hexString.length() == 1) { 829 buf = buf.append("0" + hexString); 830 } else { 831 buf = buf.append(hexString); 832 } 833 } 834 835 return buf.toString(); 836 } 837 838 842 private void closeText() { 843 if (textOpen) { 844 currentStream.add("] TJ\n"); 845 textOpen = false; 846 prevWordX = 0; 847 prevWordY = 0; 848 } 849 } 850 851 private void addKerning(StringBuffer buf, Integer ch1, Integer ch2, 852 Map kerning, String startText, 853 String endText) { 854 Map kernPair = (Map ) kerning.get(ch1); 855 856 if (kernPair != null) { 857 Integer width = (Integer ) kernPair.get(ch2); 858 if (width != null) { 859 buf.append(endText).append(-(width.intValue())). 860 append(' ').append(startText); 861 } 862 } 863 } 864 865 866 874 public void render(Page page, OutputStream outputStream) 875 throws FOPException, IOException { 876 this.idReferences = page.getIDReferences(); 878 this.pdfResources = this.pdfDoc.getResources(); 879 this.pdfDoc.setIDReferences(idReferences); 880 this.renderPage(page); 881 882 List exts = page.getExtensions(); 883 if (exts != null) { 884 extensions = exts; 885 } 886 887 this.pdfDoc.output(outputStream); 889 } 890 891 896 public void renderPage(Page page) { 897 currentStream = this.pdfDoc.makeStream(); 898 899 this.currentFontName = ""; 900 this.currentFontSize = 0; 901 902 currentStream.add("BT\n"); 903 904 renderRegions(page); 905 906 closeText(); 907 908 float w = page.getWidth(); 909 float h = page.getHeight(); 910 currentStream.add("ET\n"); 911 912 currentPage = this.pdfDoc.makePage(this.pdfResources, currentStream, 913 Math.round(w / 1000), 914 Math.round(h / 1000), page); 915 916 if (page.hasLinks() || currentAnnotList != null) { 917 if (currentAnnotList == null) { 918 currentAnnotList = this.pdfDoc.makeAnnotList(); 919 } 920 currentPage.setAnnotList(currentAnnotList); 921 922 List linkSets = page.getLinkSets(); 923 for (int i = 0; i < linkSets.size(); i++) { 924 LinkSet linkSet = (LinkSet)linkSets.get(i); 925 926 linkSet.align(); 927 String dest = linkSet.getDest(); 928 int linkType = linkSet.getLinkType(); 929 List linkRects = linkSet.getRects(); 930 for (int j = 0; j < linkRects.size(); j++) { 931 LinkedRectangle lrect = (LinkedRectangle)linkRects.get(j); 932 currentAnnotList.addLink( 933 this.pdfDoc.makeLink(lrect.getRectangle(), 934 dest, linkType)); 935 } 936 } 937 currentAnnotList = null; 938 } else { 939 currentAnnotList = null; 941 } 942 943 this.currentFill = null; 945 } 946 947 953 private String setRuleStylePattern(int style) { 954 String rs = ""; 955 switch (style) { 956 case org.apache.fop.fo.properties.RuleStyle.SOLID: 957 rs = "[] 0 d "; 958 break; 959 case org.apache.fop.fo.properties.RuleStyle.DASHED: 960 rs = "[3 3] 0 d "; 961 break; 962 case org.apache.fop.fo.properties.RuleStyle.DOTTED: 963 rs = "[1 3] 0 d "; 964 break; 965 case org.apache.fop.fo.properties.RuleStyle.DOUBLE: 966 rs = "[] 0 d "; 967 break; 968 default: 969 rs = "[] 0 d "; 970 } 971 return rs; 972 } 973 974 979 protected void renderRootExtensions(List extensions) { 980 if (extensions != null) { 981 for (int i = 0; i < extensions.size(); i++) { 982 ExtensionObj ext = (ExtensionObj) extensions.get(i); 983 if (ext instanceof Outline) { 984 renderOutline((Outline) ext); 985 } else if (ext instanceof Destination) { 986 Destination d = (Destination)ext; 987 pdfDoc.addDestination(d.getDestinationName(), d.getInternalDestination()); 988 } 989 } 990 } 991 } 992 993 private void renderOutline(Outline outline) { 994 PDFOutline outlineRoot = pdfDoc.getOutlineRoot(); 995 PDFOutline pdfOutline = null; 996 Outline parent = outline.getParentOutline(); 997 if (parent == null) { 998 pdfOutline = 999 this.pdfDoc.makeOutline(outlineRoot, 1000 outline.getLabel().toString(), 1001 outline.getInternalDestination()); 1002 } else { 1003 PDFOutline pdfParentOutline = 1004 (PDFOutline) parent.getRendererObject(); 1005 if (pdfParentOutline == null) { 1006 log.error("pdfParentOutline is null"); 1007 } else { 1008 pdfOutline = 1009 this.pdfDoc.makeOutline(pdfParentOutline, 1010 outline.getLabel().toString(), 1011 outline.getInternalDestination()); 1012 } 1013 1014 } 1015 outline.setRendererObject(pdfOutline); 1016 1017 List v = outline.getOutlines(); 1019 for (int i = 0; i < v.size(); i++) { 1020 renderOutline((Outline) v.get(i)); 1021 } 1022 } 1023 1024} 1025 | Popular Tags |