001package org.apache.maven.doxia.module.rtf; 002 003/* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022import java.awt.Color; 023import java.io.BufferedOutputStream; 024import java.io.BufferedWriter; 025import java.io.IOException; 026import java.io.OutputStream; 027import java.io.OutputStreamWriter; 028import java.io.PrintWriter; 029import java.io.Writer; 030import java.util.ArrayList; 031import java.util.HashMap; 032import java.util.Hashtable; 033import java.util.List; 034import java.util.Map; 035import java.util.Set; 036import java.util.Stack; 037import java.util.StringTokenizer; 038import java.util.TreeSet; 039import java.util.Vector; 040 041import org.apache.maven.doxia.sink.Sink; 042import org.apache.maven.doxia.sink.SinkEventAttributes; 043import org.apache.maven.doxia.sink.impl.AbstractTextSink; 044import org.apache.maven.doxia.sink.impl.SinkEventAttributeSet; 045 046/** 047 * <a href="http://en.wikipedia.org/wiki/Rich_Text_Format">RTF</a> Sink implementation. 048 * 049 * @since 1.0 050 */ 051public class RtfSink 052 extends AbstractTextSink 053{ 054 /** Paper width, 21 cm */ 055 public static final double DEFAULT_PAPER_WIDTH = 21.; /*cm*/ 056 057 /** Paper height, 29.7 cm */ 058 public static final double DEFAULT_PAPER_HEIGHT = 29.7; /*cm*/ 059 060 /** Paper top margin, 2 cm */ 061 public static final double DEFAULT_TOP_MARGIN = 2.; /*cm*/ 062 063 /** Paper bottom margin, 2 cm */ 064 public static final double DEFAULT_BOTTOM_MARGIN = 2.; /*cm*/ 065 066 /** Paper left margin, 2 cm */ 067 public static final double DEFAULT_LEFT_MARGIN = 2.; /*cm*/ 068 069 /** Paper right margin, 2 cm */ 070 public static final double DEFAULT_RIGHT_MARGIN = 2.; /*cm*/ 071 072 /** Font size, 10 pts */ 073 public static final int DEFAULT_FONT_SIZE = 10; /*pts*/ 074 075 /** Spacing, 10 pts */ 076 public static final int DEFAULT_SPACING = 10; /*pts*/ 077 078 /** Resolution, 72 dpi */ 079 public static final int DEFAULT_RESOLUTION = 72; /*dpi*/ 080 081 /** Image format, bmp */ 082 public static final String DEFAULT_IMAGE_FORMAT = "bmp"; 083 084 /** Image type, palette */ 085 public static final String DEFAULT_IMAGE_TYPE = "palette"; 086 087 /** Data format, ascii */ 088 public static final String DEFAULT_DATA_FORMAT = "ascii"; 089 090 /** Codepage, 1252 */ 091 public static final int DEFAULT_CODE_PAGE = 1252; 092 093 /** Constant <code>DEFAULT_CHAR_SET=0</code> */ 094 public static final int DEFAULT_CHAR_SET = 0; 095 096 /** Constant <code>IMG_FORMAT_BMP="bmp"</code> */ 097 public static final String IMG_FORMAT_BMP = "bmp"; 098 099 /** Constant <code>IMG_FORMAT_WMF="wmf"</code> */ 100 public static final String IMG_FORMAT_WMF = "wmf"; 101 102 /** Constant <code>IMG_TYPE_PALETTE="palette"</code> */ 103 public static final String IMG_TYPE_PALETTE = "palette"; 104 105 /** Constant <code>IMG_TYPE_RGB="rgb"</code> */ 106 public static final String IMG_TYPE_RGB = "rgb"; 107 108 /** Constant <code>IMG_DATA_ASCII="ascii"</code> */ 109 public static final String IMG_DATA_ASCII = "ascii"; 110 111 /** Constant <code>IMG_DATA_RAW="raw"</code> */ 112 public static final String IMG_DATA_RAW = "raw"; 113 114 /** Constant <code>STYLE_ROMAN=0</code> */ 115 public static final int STYLE_ROMAN = 0; 116 117 /** Constant <code>STYLE_ITALIC=1</code> */ 118 public static final int STYLE_ITALIC = 1; 119 120 /** Constant <code>STYLE_BOLD=2</code> */ 121 public static final int STYLE_BOLD = 2; 122 123 /** Constant <code>STYLE_TYPEWRITER=3</code> */ 124 public static final int STYLE_TYPEWRITER = 3; 125 126 private static final int CONTEXT_UNDEFINED = 0; 127 128 private static final int CONTEXT_VERBATIM = 1; 129 130 private static final int CONTEXT_TABLE = 2; 131 132 private static final int UNIT_MILLIMETER = 1; 133 134 private static final int UNIT_CENTIMETER = 2; 135 136 private static final int UNIT_INCH = 3; 137 138 private static final int UNIT_PIXEL = 4; 139 140 private static final int LIST_INDENT = 300; /*twips*/ 141 142 private static final String LIST_ITEM_HEADER = "- "; 143 144 private static final int DEFINITION_INDENT = 300; /*twips*/ 145 146 private static final int CELL_HORIZONTAL_PAD = 60; /*twips*/ 147 148 private static final int CELL_VERTICAL_PAD = 20; /*twips*/ 149 150 private static final int BORDER_WIDTH = 15; /*twips*/ 151 152 private double paperWidth = DEFAULT_PAPER_WIDTH; 153 154 private double paperHeight = DEFAULT_PAPER_HEIGHT; 155 156 private double topMargin = DEFAULT_TOP_MARGIN; 157 158 private double bottomMargin = DEFAULT_BOTTOM_MARGIN; 159 160 private double leftMargin = DEFAULT_LEFT_MARGIN; 161 162 private double rightMargin = DEFAULT_RIGHT_MARGIN; 163 164 protected int fontSize = DEFAULT_FONT_SIZE; 165 166 private int resolution = DEFAULT_RESOLUTION; 167 168 private String imageFormat = DEFAULT_IMAGE_FORMAT; 169 170 private String imageType = DEFAULT_IMAGE_TYPE; 171 172 private String imageDataFormat = DEFAULT_DATA_FORMAT; 173 174 private boolean imageCompression = true; 175 176 private int codePage = DEFAULT_CODE_PAGE; 177 178 private int charSet = DEFAULT_CHAR_SET; 179 180 private final Hashtable<String, Font> fontTable; 181 182 private Context context; 183 184 private Paragraph paragraph; 185 186 protected Indentation indentation; 187 188 protected Space space; 189 190 private int listItemIndent; 191 192 private final Vector<Integer> numbering; 193 194 private final Vector<Counter> itemNumber; 195 196 private int style = STYLE_ROMAN; 197 198 private int sectionLevel; 199 200 private boolean emptyHeader; 201 202 private StringBuilder verbatim; 203 204 private boolean frame; 205 206 private Table table; 207 208 private Row row; 209 210 private Cell cell; 211 212 private Line line; 213 214 protected PrintWriter writer; 215 216 protected OutputStream stream; // for raw image data 217 218 /** Keep track of the closing tags for inline events. */ 219 protected Stack<List<Integer>> inlineStack = new Stack<>(); 220 221 /** Map of warn messages with a String as key to describe the error type and a Set as value. 222 * Using to reduce warn messages. */ 223 private Map<String, Set<String>> warnMessages; 224 225 // ----------------------------------------------------------------------- 226 227 /** 228 * <p>Constructor for RtfSink.</p> 229 * 230 * @throws java.io.IOException if any. 231 */ 232 protected RtfSink() 233 throws IOException 234 { 235 this( System.out ); 236 } 237 238 /** 239 * <p>Constructor for RtfSink.</p> 240 * 241 * @param output not null 242 * @throws java.io.IOException if any. 243 */ 244 protected RtfSink( OutputStream output ) 245 throws IOException 246 { 247 this( output, null ); 248 } 249 250 /** 251 * <p>Constructor for RtfSink.</p> 252 * 253 * @param output not null 254 * @param encoding a valid charset 255 * @throws java.io.IOException if any 256 */ 257 protected RtfSink( OutputStream output, String encoding ) 258 throws IOException 259 { 260 this.fontTable = new Hashtable<>(); 261 this.numbering = new Vector<>(); 262 this.itemNumber = new Vector<>(); 263 264 Writer w; 265 this.stream = new BufferedOutputStream( output ); 266 // TODO: encoding should be consistent with codePage 267 if ( encoding != null ) 268 { 269 w = new OutputStreamWriter( stream, encoding ); 270 } 271 else 272 { 273 w = new OutputStreamWriter( stream ); 274 } 275 this.writer = new PrintWriter( new BufferedWriter( w ) ); 276 277 init(); 278 } 279 280 /** 281 * setPaperSize. 282 * 283 * @param width in cm. 284 * @param height in cm. 285 */ 286 public void setPaperSize( double width /*cm*/, double height /*cm*/ ) 287 { 288 paperWidth = width; 289 paperHeight = height; 290 } 291 292 /** 293 * <p>Setter for the field <code>topMargin</code>.</p> 294 * 295 * @param margin margin. 296 */ 297 public void setTopMargin( double margin ) 298 { 299 topMargin = margin; 300 } 301 302 /** 303 * <p>Setter for the field <code>bottomMargin</code>.</p> 304 * 305 * @param margin margin. 306 */ 307 public void setBottomMargin( double margin ) 308 { 309 bottomMargin = margin; 310 } 311 312 /** 313 * <p>Setter for the field <code>leftMargin</code>.</p> 314 * 315 * @param margin margin 316 */ 317 public void setLeftMargin( double margin ) 318 { 319 leftMargin = margin; 320 } 321 322 /** 323 * <p>Setter for the field <code>rightMargin</code>.</p> 324 * 325 * @param margin margin 326 */ 327 public void setRightMargin( double margin ) 328 { 329 rightMargin = margin; 330 } 331 332 /** 333 * <p>Setter for the field <code>fontSize</code>.</p> 334 * 335 * @param size in pts 336 */ 337 public void setFontSize( int size /*pts*/ ) 338 { 339 fontSize = size; 340 } 341 342 /** 343 * <p>setSpacing.</p> 344 * 345 * @param spacing in pts. 346 */ 347 public void setSpacing( int spacing /*pts*/ ) 348 { 349 space.set( 20 * spacing ); 350 } 351 352 /** 353 * <p>Setter for the field <code>resolution</code>.</p> 354 * 355 * @param resolution in dpi 356 */ 357 public void setResolution( int resolution /*dpi*/ ) 358 { 359 this.resolution = resolution; 360 } 361 362 /** 363 * <p>Setter for the field <code>imageFormat</code>.</p> 364 * 365 * @param format a {@link java.lang.String} object. 366 */ 367 public void setImageFormat( String format ) 368 { 369 imageFormat = format; 370 } 371 372 /** 373 * <p>Setter for the field <code>imageType</code>.</p> 374 * 375 * @param type a {@link java.lang.String} object. 376 */ 377 public void setImageType( String type ) 378 { 379 imageType = type; 380 } 381 382 /** 383 * <p>Setter for the field <code>imageDataFormat</code>.</p> 384 * 385 * @param format a {@link java.lang.String} object. 386 */ 387 public void setImageDataFormat( String format ) 388 { 389 imageDataFormat = format; 390 } 391 392 /** 393 * <p>Setter for the field <code>imageCompression</code>.</p> 394 * 395 * @param compression a boolean. 396 */ 397 public void setImageCompression( boolean compression ) 398 { 399 imageCompression = compression; 400 } 401 402 /** 403 * <p>Setter for the field <code>codePage</code>.</p> 404 * 405 * @param cp a int. 406 */ 407 public void setCodePage( int cp ) 408 { 409 codePage = cp; 410 } 411 412 /** 413 * <p>Setter for the field <code>charSet</code>.</p> 414 * 415 * @param cs a int. 416 */ 417 public void setCharSet( int cs ) 418 { 419 charSet = cs; 420 } 421 422 /** 423 * {@inheritDoc} 424 */ 425 public void head() 426 { 427 init(); 428 429 writer.println( "{\\rtf1\\ansi\\ansicpg" + codePage + "\\deff0" ); 430 431 writer.println( "{\\fonttbl" ); 432 writer.println( "{\\f0\\froman\\fcharset" + charSet + " Times;}" ); 433 writer.println( "{\\f1\\fmodern\\fcharset" + charSet + " Courier;}" ); 434 writer.println( "}" ); 435 436 writer.println( "{\\stylesheet" ); 437 for ( int level = 1; level <= 5; ++level ) 438 { 439 writer.print( "{\\s" + styleNumber( level ) ); 440 writer.print( "\\outlinelevel" + level ); 441 writer.print( " Section Title " + level ); 442 writer.println( ";}" ); 443 } 444 writer.println( "}" ); 445 446 writer.println( "\\paperw" + toTwips( paperWidth, UNIT_CENTIMETER ) ); 447 writer.println( "\\paperh" + toTwips( paperHeight, UNIT_CENTIMETER ) ); 448 writer.println( "\\margl" + toTwips( leftMargin, UNIT_CENTIMETER ) ); 449 writer.println( "\\margr" + toTwips( rightMargin, UNIT_CENTIMETER ) ); 450 writer.println( "\\margt" + toTwips( topMargin, UNIT_CENTIMETER ) ); 451 writer.println( "\\margb" + toTwips( bottomMargin, UNIT_CENTIMETER ) ); 452 453 space.set( space.get() / 2 ); 454 space.setNext( 0 ); 455 456 emptyHeader = true; 457 } 458 459 /** 460 * {@inheritDoc} 461 */ 462 public void head_() 463 { 464 space.restore(); 465 if ( emptyHeader ) 466 { 467 space.setNext( 0 ); 468 } 469 else 470 { 471 space.setNext( 2 * space.get() ); 472 } 473 } 474 475 /** 476 * <p>toTwips.</p> 477 * 478 * @param length a double. 479 * @param unit a int. 480 * @return a int. 481 */ 482 protected int toTwips( double length, int unit ) 483 { 484 double points; 485 486 switch ( unit ) 487 { 488 case UNIT_MILLIMETER: 489 points = ( length / 25.4 ) * 72.; 490 break; 491 case UNIT_CENTIMETER: 492 points = ( length / 2.54 ) * 72.; 493 break; 494 case UNIT_INCH: 495 points = length * 72.; 496 break; 497 case UNIT_PIXEL: 498 default: 499 points = ( length / resolution ) * 72.; 500 break; 501 } 502 503 return (int) Math.rint( points * 20. ); 504 } 505 506 /** 507 * {@inheritDoc} 508 */ 509 public void title() 510 { 511 Paragraph p = new Paragraph( STYLE_BOLD, fontSize + 6 ); 512 p.justification = Sink.JUSTIFY_CENTER; 513 beginParagraph( p ); 514 emptyHeader = false; 515 } 516 517 /** 518 * {@inheritDoc} 519 */ 520 public void title_() 521 { 522 endParagraph(); 523 } 524 525 /** 526 * {@inheritDoc} 527 */ 528 public void author() 529 { 530 Paragraph p = new Paragraph( STYLE_ROMAN, fontSize + 2 ); 531 p.justification = Sink.JUSTIFY_CENTER; 532 beginParagraph( p ); 533 emptyHeader = false; 534 } 535 536 /** 537 * {@inheritDoc} 538 */ 539 public void author_() 540 { 541 endParagraph(); 542 } 543 544 /** 545 * {@inheritDoc} 546 */ 547 public void date() 548 { 549 Paragraph p = new Paragraph( STYLE_ROMAN, fontSize ); 550 p.justification = Sink.JUSTIFY_CENTER; 551 beginParagraph( p ); 552 emptyHeader = false; 553 } 554 555 /** 556 * {@inheritDoc} 557 */ 558 public void date_() 559 { 560 endParagraph(); 561 } 562 563 /** 564 * {@inheritDoc} 565 */ 566 public void body() 567 { 568 // nop 569 } 570 571 /** 572 * {@inheritDoc} 573 */ 574 public void body_() 575 { 576 writer.println( "}" ); 577 writer.flush(); 578 } 579 580 /** 581 * {@inheritDoc} 582 */ 583 public void section1() 584 { 585 sectionLevel = 1; 586 } 587 588 /** 589 * {@inheritDoc} 590 */ 591 public void section1_() 592 { 593 // nop 594 } 595 596 /** 597 * {@inheritDoc} 598 */ 599 public void section2() 600 { 601 sectionLevel = 2; 602 } 603 604 /** 605 * {@inheritDoc} 606 */ 607 public void section2_() 608 { 609 // nop 610 } 611 612 /** 613 * {@inheritDoc} 614 */ 615 public void section3() 616 { 617 sectionLevel = 3; 618 } 619 620 /** 621 * {@inheritDoc} 622 */ 623 public void section3_() 624 { 625 // nop 626 } 627 628 /** 629 * {@inheritDoc} 630 */ 631 public void section4() 632 { 633 sectionLevel = 4; 634 } 635 636 /** 637 * {@inheritDoc} 638 */ 639 public void section4_() 640 { 641 // nop 642 } 643 644 /** 645 * {@inheritDoc} 646 */ 647 public void section5() 648 { 649 sectionLevel = 5; 650 } 651 652 /** 653 * {@inheritDoc} 654 */ 655 public void section5_() 656 { 657 // nop 658 } 659 660 /** 661 * {@inheritDoc} 662 */ 663 public void sectionTitle() 664 { 665 int stl = STYLE_BOLD; 666 int size = fontSize; 667 668 switch ( sectionLevel ) 669 { 670 case 1: 671 size = fontSize + 6; 672 break; 673 case 2: 674 size = fontSize + 4; 675 break; 676 case 3: 677 size = fontSize + 2; 678 break; 679 case 4: 680 break; 681 case 5: 682 stl = STYLE_ROMAN; 683 break; 684 default: 685 } 686 687 Paragraph p = new Paragraph( stl, size ); 688 p.style = styleNumber( sectionLevel ); 689 690 beginParagraph( p ); 691 } 692 693 /** 694 * {@inheritDoc} 695 */ 696 public void sectionTitle_() 697 { 698 endParagraph(); 699 } 700 701 private int styleNumber( int level ) 702 { 703 return level; 704 } 705 706 /** 707 * {@inheritDoc} 708 */ 709 public void list() 710 { 711 indentation.add( LIST_INDENT ); 712 space.set( space.get() / 2 ); 713 } 714 715 /** 716 * {@inheritDoc} 717 */ 718 public void list_() 719 { 720 indentation.restore(); 721 space.restore(); 722 } 723 724 /** 725 * {@inheritDoc} 726 */ 727 public void listItem() 728 { 729 Paragraph p = new Paragraph(); 730 p.leftIndent = indentation.get() + listItemIndent; 731 p.firstLineIndent = ( -listItemIndent ); 732 beginParagraph( p ); 733 734 beginStyle( STYLE_BOLD ); 735 writer.println( LIST_ITEM_HEADER ); 736 endStyle(); 737 738 indentation.add( listItemIndent ); 739 space.set( space.get() / 2 ); 740 } 741 742 /** 743 * {@inheritDoc} 744 */ 745 public void listItem_() 746 { 747 endParagraph(); 748 749 indentation.restore(); 750 space.restore(); 751 } 752 753 /** {@inheritDoc} */ 754 public void numberedList( int numbering ) 755 { 756 this.numbering.addElement( numbering ); 757 itemNumber.addElement( new Counter( 0 ) ); 758 759 indentation.add( LIST_INDENT ); 760 space.set( space.get() / 2 ); 761 } 762 763 /** 764 * {@inheritDoc} 765 */ 766 public void numberedList_() 767 { 768 numbering.removeElementAt( numbering.size() - 1 ); 769 itemNumber.removeElementAt( itemNumber.size() - 1 ); 770 771 indentation.restore(); 772 space.restore(); 773 } 774 775 /** 776 * {@inheritDoc} 777 */ 778 public void numberedListItem() 779 { 780 ( (Counter) itemNumber.lastElement() ).increment(); 781 782 int indent = 0; 783 String header = getItemHeader(); 784 Font font = getFont( STYLE_TYPEWRITER, fontSize ); 785 if ( font != null ) 786 { 787 indent = textWidth( header, font ); 788 } 789 790 Paragraph p = new Paragraph(); 791 p.leftIndent = indentation.get() + indent; 792 p.firstLineIndent = ( -indent ); 793 beginParagraph( p ); 794 795 beginStyle( STYLE_TYPEWRITER ); 796 writer.println( header ); 797 endStyle(); 798 799 indentation.add( indent ); 800 space.set( space.get() / 2 ); 801 } 802 803 /** 804 * {@inheritDoc} 805 */ 806 public void numberedListItem_() 807 { 808 endParagraph(); 809 810 indentation.restore(); 811 space.restore(); 812 } 813 814 private String getItemHeader() 815 { 816 int nmb = (Integer) this.numbering.lastElement(); 817 int iNmb = ( (Counter) this.itemNumber.lastElement() ).get(); 818 StringBuilder buf = new StringBuilder(); 819 820 switch ( nmb ) 821 { 822 case Sink.NUMBERING_DECIMAL: 823 default: 824 buf.append( iNmb ); 825 buf.append( ". " ); 826 while ( buf.length() < 4 ) 827 { 828 buf.append( ' ' ); 829 } 830 break; 831 832 case Sink.NUMBERING_LOWER_ALPHA: 833 buf.append( AlphaNumerals.toString( iNmb, true ) ); 834 buf.append( ") " ); 835 break; 836 837 case Sink.NUMBERING_UPPER_ALPHA: 838 buf.append( AlphaNumerals.toString( iNmb, false ) ); 839 buf.append( ". " ); 840 break; 841 842 case Sink.NUMBERING_LOWER_ROMAN: 843 buf.append( RomanNumerals.toString( iNmb, true ) ); 844 buf.append( ") " ); 845 while ( buf.length() < 6 ) 846 { 847 buf.append( ' ' ); 848 } 849 break; 850 851 case Sink.NUMBERING_UPPER_ROMAN: 852 buf.append( RomanNumerals.toString( iNmb, false ) ); 853 buf.append( ". " ); 854 while ( buf.length() < 6 ) 855 { 856 buf.append( ' ' ); 857 } 858 break; 859 } 860 861 return buf.toString(); 862 } 863 864 /** 865 * {@inheritDoc} 866 */ 867 public void definitionList() 868 { 869 int next = space.getNext(); 870 871 indentation.add( LIST_INDENT ); 872 space.set( space.get() / 2 ); 873 space.setNext( next ); 874 } 875 876 /** 877 * {@inheritDoc} 878 */ 879 public void definitionList_() 880 { 881 indentation.restore(); 882 space.restore(); 883 } 884 885 /** 886 * {@inheritDoc} 887 */ 888 public void definitionListItem() 889 { 890 int next = space.getNext(); 891 space.set( space.get() / 2 ); 892 space.setNext( next ); 893 } 894 895 /** 896 * {@inheritDoc} 897 */ 898 public void definitionListItem_() 899 { 900 space.restore(); 901 } 902 903 /** 904 * {@inheritDoc} 905 */ 906 public void definedTerm() 907 { 908 // nop 909 } 910 911 /** 912 * {@inheritDoc} 913 */ 914 public void definedTerm_() 915 { 916 endParagraph(); 917 } 918 919 /** 920 * {@inheritDoc} 921 */ 922 public void definition() 923 { 924 int next = space.getNext(); 925 926 indentation.add( DEFINITION_INDENT ); 927 space.set( space.get() / 2 ); 928 space.setNext( next ); 929 } 930 931 /** 932 * {@inheritDoc} 933 */ 934 public void definition_() 935 { 936 endParagraph(); 937 938 indentation.restore(); 939 space.restore(); 940 } 941 942 /** 943 * {@inheritDoc} 944 */ 945 public void table() 946 { 947 // nop 948 } 949 950 /** 951 * {@inheritDoc} 952 */ 953 public void table_() 954 { 955 // nop 956 } 957 958 /** {@inheritDoc} */ 959 public void tableRows( int[] justification, boolean grid ) 960 961 { 962 table = new Table( justification, grid ); 963 context.set( CONTEXT_TABLE ); 964 } 965 966 /** 967 * {@inheritDoc} 968 */ 969 public void tableRows_() 970 { 971 boolean bb = false; 972 boolean br = false; 973 974 int offset = ( pageWidth() - ( table.width() + indentation.get() ) ) / 2; 975 int x0 = indentation.get() + offset; 976 977 space.skip(); 978 979 for ( int i = 0; i < table.rows.size(); ++i ) 980 { 981 Row r = (Row) table.rows.elementAt( i ); 982 983 writer.print( "\\trowd" ); 984 writer.print( "\\trleft" + x0 ); 985 writer.print( "\\trgaph" + CELL_HORIZONTAL_PAD ); 986 writer.println( "\\trrh" + r.height() ); 987 988 if ( table.grid ) 989 { 990 if ( i == ( table.rows.size() - 1 ) ) 991 { 992 bb = true; 993 } 994 br = false; 995 } 996 997 for ( int j = 0, x = x0; j < table.numColumns; ++j ) 998 { 999 if ( table.grid ) 1000 { 1001 if ( j == ( table.numColumns - 1 ) ) 1002 { 1003 br = true; 1004 } 1005 setBorder( true, bb, true, br ); 1006 x += BORDER_WIDTH; 1007 } 1008 x += table.columnWidths[j]; 1009 writer.println( "\\clvertalc\\cellx" + x ); 1010 } 1011 1012 for ( int j = 0; j < table.numColumns; ++j ) 1013 { 1014 if ( j >= r.cells.size() ) 1015 { 1016 break; 1017 } 1018 Cell c = (Cell) r.cells.elementAt( j ); 1019 1020 writer.print( "\\pard\\intbl" ); 1021 setJustification( table.justification[j] ); 1022 writer.println( "\\plain\\f0\\fs" + ( 2 * fontSize ) ); 1023 1024 for ( int k = 0; k < c.lines.size(); ++k ) 1025 { 1026 if ( k > 0 ) 1027 { 1028 writer.println( "\\line" ); 1029 } 1030 Line l = (Line) c.lines.elementAt( k ); 1031 1032 for ( int n = 0; n < l.items.size(); ++n ) 1033 { 1034 Item item = (Item) l.items.elementAt( n ); 1035 writer.print( "{" ); 1036 setStyle( item.style ); 1037 writer.println( escape( item.text ) ); 1038 writer.println( "}" ); 1039 } 1040 } 1041 1042 writer.println( "\\cell" ); 1043 } 1044 1045 writer.println( "\\row" ); 1046 } 1047 1048 context.restore(); 1049 } 1050 1051 private int pageWidth() 1052 { 1053 double width = paperWidth - ( leftMargin + rightMargin ); 1054 return toTwips( width, UNIT_CENTIMETER ); 1055 } 1056 1057 private void setBorder( boolean bt, boolean bb, boolean bl, boolean br ) 1058 { 1059 if ( bt ) 1060 { 1061 writer.println( "\\clbrdrt\\brdrs\\brdrw" + BORDER_WIDTH ); 1062 } 1063 if ( bb ) 1064 { 1065 writer.println( "\\clbrdrb\\brdrs\\brdrw" + BORDER_WIDTH ); 1066 } 1067 if ( bl ) 1068 { 1069 writer.println( "\\clbrdrl\\brdrs\\brdrw" + BORDER_WIDTH ); 1070 } 1071 if ( br ) 1072 { 1073 writer.println( "\\clbrdrr\\brdrs\\brdrw" + BORDER_WIDTH ); 1074 } 1075 } 1076 1077 private void setJustification( int justification ) 1078 { 1079 switch ( justification ) 1080 { 1081 case Sink.JUSTIFY_LEFT: 1082 default: 1083 writer.println( "\\ql" ); 1084 break; 1085 case Sink.JUSTIFY_CENTER: 1086 writer.println( "\\qc" ); 1087 break; 1088 case Sink.JUSTIFY_RIGHT: 1089 writer.println( "\\qr" ); 1090 break; 1091 } 1092 } 1093 1094 private void setStyle( int style ) 1095 { 1096 switch ( style ) 1097 { 1098 case STYLE_ITALIC: 1099 writer.println( "\\i" ); 1100 break; 1101 case STYLE_BOLD: 1102 writer.println( "\\b" ); 1103 break; 1104 case STYLE_TYPEWRITER: 1105 writer.println( "\\f1" ); 1106 break; 1107 default: 1108 break; 1109 } 1110 } 1111 1112 /** 1113 * {@inheritDoc} 1114 */ 1115 public void tableRow() 1116 { 1117 row = new Row(); 1118 } 1119 1120 /** 1121 * {@inheritDoc} 1122 */ 1123 public void tableRow_() 1124 { 1125 table.add( row ); 1126 } 1127 1128 /** 1129 * {@inheritDoc} 1130 */ 1131 public void tableHeaderCell() 1132 { 1133 tableCell(); 1134 } 1135 1136 /** 1137 * {@inheritDoc} 1138 */ 1139 public void tableHeaderCell_() 1140 { 1141 tableCell_(); 1142 } 1143 1144 /** 1145 * {@inheritDoc} 1146 */ 1147 public void tableCell() 1148 { 1149 cell = new Cell(); 1150 line = new Line(); 1151 } 1152 1153 /** 1154 * {@inheritDoc} 1155 */ 1156 public void tableCell_() 1157 { 1158 cell.add( line ); 1159 row.add( cell ); 1160 } 1161 1162 /** 1163 * {@inheritDoc} 1164 */ 1165 public void tableCaption() 1166 { 1167 Paragraph p = new Paragraph(); 1168 p.justification = Sink.JUSTIFY_CENTER; 1169 p.spaceBefore /= 2; 1170 beginParagraph( p ); 1171 } 1172 1173 /** 1174 * {@inheritDoc} 1175 */ 1176 public void tableCaption_() 1177 { 1178 endParagraph(); 1179 } 1180 1181 /** 1182 * {@inheritDoc} 1183 */ 1184 public void paragraph() 1185 { 1186 if ( paragraph == null ) 1187 { 1188 beginParagraph( new Paragraph() ); 1189 } 1190 } 1191 1192 /** 1193 * {@inheritDoc} 1194 */ 1195 public void paragraph_() 1196 { 1197 endParagraph(); 1198 } 1199 1200 private void beginParagraph( Paragraph p ) 1201 { 1202 p.begin(); 1203 this.paragraph = p; 1204 if ( style != STYLE_ROMAN ) 1205 { 1206 beginStyle( style ); 1207 } 1208 } 1209 1210 private void endParagraph() 1211 { 1212 if ( paragraph != null ) 1213 { 1214 if ( style != STYLE_ROMAN ) 1215 { 1216 endStyle(); 1217 } 1218 paragraph.end(); 1219 paragraph = null; 1220 } 1221 } 1222 1223 /** {@inheritDoc} */ 1224 public void verbatim( boolean boxed ) 1225 { 1226 verbatim = new StringBuilder(); 1227 frame = boxed; 1228 1229 context.set( CONTEXT_VERBATIM ); 1230 } 1231 1232 /** 1233 * {@inheritDoc} 1234 */ 1235 public void verbatim_() 1236 { 1237 String text = verbatim.toString(); 1238 1239 Paragraph p = new Paragraph(); 1240 p.fontStyle = STYLE_TYPEWRITER; 1241 p.frame = frame; 1242 1243 beginParagraph( p ); 1244 1245 StringTokenizer t = new StringTokenizer( text, EOL, true ); 1246 while ( t.hasMoreTokens() ) 1247 { 1248 String s = t.nextToken(); 1249 if ( s.equals( EOL ) && t.hasMoreTokens() ) 1250 { 1251 writer.println( "\\line" ); 1252 } 1253 else 1254 { 1255 writer.println( escape( s ) ); 1256 } 1257 } 1258 1259 endParagraph(); 1260 1261 context.restore(); 1262 } 1263 1264 /** 1265 * {@inheritDoc} 1266 */ 1267 public void figure() 1268 { 1269 // nop 1270 } 1271 1272 /** 1273 * {@inheritDoc} 1274 */ 1275 public void figure_() 1276 { 1277 // nop 1278 } 1279 1280 /** {@inheritDoc} */ 1281 public void figureGraphics( String name ) 1282 { 1283 Paragraph p = new Paragraph(); 1284 p.justification = Sink.JUSTIFY_CENTER; 1285 beginParagraph( p ); 1286 1287 try 1288 { 1289 writeImage( name ); 1290 } 1291 catch ( Exception e ) 1292 { 1293 getLog().error( e.getMessage(), e ); 1294 } 1295 1296 endParagraph(); 1297 } 1298 1299 private void writeImage( String source ) 1300 throws Exception 1301 { 1302 if ( !source.toLowerCase().endsWith( ".ppm" ) ) 1303 { 1304 // TODO support more image types! 1305 String msg = 1306 "Unsupported image type for image file: '" + source + "'. Only PPM image type is " 1307 + "currently supported."; 1308 logMessage( "unsupportedImage", msg ); 1309 1310 return; 1311 } 1312 1313 int bytesPerLine; 1314 PBMReader ppm = new PBMReader( source ); 1315 WMFWriter.Dib dib = new WMFWriter.Dib(); 1316 WMFWriter wmf = new WMFWriter(); 1317 1318 int srcWidth = ppm.width(); 1319 int srcHeight = ppm.height(); 1320 1321 dib.biWidth = srcWidth; 1322 dib.biHeight = srcHeight; 1323 dib.biXPelsPerMeter = (int) ( resolution * 100. / 2.54 ); 1324 dib.biYPelsPerMeter = dib.biXPelsPerMeter; 1325 1326 if ( imageType.equals( IMG_TYPE_RGB ) ) 1327 { 1328 dib.biBitCount = 24; 1329 dib.biCompression = WMFWriter.Dib.BI_RGB; // no compression 1330 1331 bytesPerLine = 4 * ( ( 3 * srcWidth + 3 ) / 4 ); 1332 dib.bitmap = new byte[srcHeight * bytesPerLine]; 1333 1334 byte[] l = new byte[3 * srcWidth]; 1335 for ( int i = ( srcHeight - 1 ); i >= 0; --i ) 1336 { 1337 ppm.read( l, 0, l.length ); 1338 for ( int j = 0, k = ( i * bytesPerLine ); j < l.length; j += 3 ) 1339 { 1340 // component order = BGR 1341 dib.bitmap[k++] = l[j + 2]; 1342 dib.bitmap[k++] = l[j + 1]; 1343 dib.bitmap[k++] = l[j]; 1344 } 1345 } 1346 } 1347 else 1348 { 1349 dib.biBitCount = 8; 1350 1351 bytesPerLine = 4 * ( ( srcWidth + 3 ) / 4 ); 1352 byte[] bitmap = new byte[srcHeight * bytesPerLine]; 1353 1354 Vector<Color> colors = new Vector<>( 256 ); 1355 colors.addElement( Color.white ); 1356 colors.addElement( Color.black ); 1357 1358 byte[] l = new byte[3 * srcWidth]; 1359 for ( int i = ( srcHeight - 1 ); i >= 0; --i ) 1360 { 1361 ppm.read( l, 0, l.length ); 1362 for ( int j = 0, k = ( i * bytesPerLine ); j < l.length; ) 1363 { 1364 int r = (int) l[j++] & 0xff; 1365 int g = (int) l[j++] & 0xff; 1366 int b = (int) l[j++] & 0xff; 1367 Color color = new Color( r, g, b ); 1368 int index = colors.indexOf( color ); 1369 if ( index < 0 ) 1370 { 1371 if ( colors.size() < colors.capacity() ) 1372 { 1373 colors.addElement( color ); 1374 index = colors.size() - 1; 1375 } 1376 else 1377 { 1378 index = 1; 1379 } 1380 } 1381 bitmap[k++] = (byte) index; 1382 } 1383 } 1384 1385 dib.biClrUsed = colors.size(); 1386 dib.biClrImportant = dib.biClrUsed; 1387 dib.palette = new byte[4 * dib.biClrUsed]; 1388 for ( int i = 0, j = 0; i < dib.biClrUsed; ++i, ++j ) 1389 { 1390 Color color = (Color) colors.elementAt( i ); 1391 dib.palette[j++] = (byte) color.getBlue(); 1392 dib.palette[j++] = (byte) color.getGreen(); 1393 dib.palette[j++] = (byte) color.getRed(); 1394 } 1395 1396 if ( imageCompression ) 1397 { 1398 dib.biCompression = WMFWriter.Dib.BI_RLE8; 1399 dib.bitmap = new byte[bitmap.length + ( 2 * ( bitmap.length / 255 + 1 ) )]; 1400 dib.biSizeImage = WMFWriter.Dib.rlEncode8( bitmap, 0, bitmap.length, dib.bitmap, 0 ); 1401 } 1402 else 1403 { 1404 dib.biCompression = WMFWriter.Dib.BI_RGB; 1405 dib.bitmap = bitmap; 1406 } 1407 } 1408 1409 if ( imageFormat.equals( IMG_FORMAT_WMF ) ) 1410 { 1411 int[] parameters; 1412 WMFWriter.Record record; 1413 1414 /* 1415 * See the libwmf library documentation 1416 * (http://www.wvware.com/wmf_doc_index.html) 1417 * for a description of WMF records. 1418 */ 1419 1420 // set mapping mode to MM_TEXT (logical unit = pixel) 1421 parameters = new int[1]; 1422 parameters[0] = 1; 1423 record = new WMFWriter.Record( 0x0103, parameters ); 1424 wmf.add( record ); 1425 1426 // set window origin and dimensions 1427 parameters = new int[2]; 1428 record = new WMFWriter.Record( 0x020b, parameters ); 1429 wmf.add( record ); 1430 parameters = new int[2]; 1431 parameters[0] = srcHeight; 1432 parameters[1] = srcWidth; 1433 record = new WMFWriter.Record( 0x020c, parameters ); 1434 wmf.add( record ); 1435 1436 parameters = new int[WMFWriter.DibBitBltRecord.P_COUNT]; 1437 // raster operation = SRCCOPY (0x00cc0020) 1438 parameters[WMFWriter.DibBitBltRecord.P_ROP_H] = 0x00cc; 1439 parameters[WMFWriter.DibBitBltRecord.P_ROP_L] = 0x0020; 1440 parameters[WMFWriter.DibBitBltRecord.P_WIDTH] = srcWidth; 1441 parameters[WMFWriter.DibBitBltRecord.P_HEIGHT] = srcHeight; 1442 record = new WMFWriter.DibBitBltRecord( parameters, dib ); 1443 wmf.add( record ); 1444 } 1445 1446 if ( imageFormat.equals( IMG_FORMAT_WMF ) ) 1447 { 1448 writer.print( "{\\pict\\wmetafile1" ); 1449 writer.println( "\\picbmp\\picbpp" + dib.biBitCount ); 1450 } 1451 else 1452 { 1453 writer.print( "{\\pict\\dibitmap0\\wbmplanes1" ); 1454 writer.print( "\\wbmbitspixel" + dib.biBitCount ); 1455 writer.println( "\\wbmwidthbytes" + bytesPerLine ); 1456 } 1457 1458 writer.print( "\\picw" + srcWidth ); 1459 writer.print( "\\pich" + srcHeight ); 1460 writer.print( "\\picwgoal" + toTwips( srcWidth, UNIT_PIXEL ) ); 1461 writer.println( "\\pichgoal" + toTwips( srcHeight, UNIT_PIXEL ) ); 1462 1463 if ( imageFormat.equals( IMG_FORMAT_WMF ) ) 1464 { 1465 if ( imageDataFormat.equals( IMG_DATA_RAW ) ) 1466 { 1467 writer.print( "\\bin" + ( 2 * wmf.size() ) + " " ); 1468 writer.flush(); 1469 wmf.write( stream ); 1470 stream.flush(); 1471 } 1472 else 1473 { 1474 wmf.print( writer ); 1475 } 1476 } 1477 else 1478 { 1479 if ( imageDataFormat.equals( IMG_DATA_RAW ) ) 1480 { 1481 writer.print( "\\bin" + ( 2 * dib.size() ) + " " ); 1482 writer.flush(); 1483 dib.write( stream ); 1484 stream.flush(); 1485 } 1486 else 1487 { 1488 dib.print( writer ); 1489 } 1490 } 1491 1492 writer.println( "}" ); 1493 } 1494 1495 /** 1496 * {@inheritDoc} 1497 */ 1498 public void figureCaption() 1499 { 1500 Paragraph p = new Paragraph(); 1501 p.justification = Sink.JUSTIFY_CENTER; 1502 p.spaceBefore /= 2; 1503 beginParagraph( p ); 1504 } 1505 1506 /** 1507 * {@inheritDoc} 1508 */ 1509 public void figureCaption_() 1510 { 1511 endParagraph(); 1512 } 1513 1514 /** 1515 * {@inheritDoc} 1516 */ 1517 public void horizontalRule() 1518 { 1519 writer.print( "\\pard\\li" + indentation.get() ); 1520 1521 int skip = space.getNext(); 1522 if ( skip > 0 ) 1523 { 1524 writer.print( "\\sb" + skip ); 1525 } 1526 space.setNext( skip ); 1527 1528 writer.print( "\\brdrb\\brdrs\\brdrw" + BORDER_WIDTH ); 1529 writer.println( "\\plain\\fs1\\par" ); 1530 } 1531 1532 /** 1533 * {@inheritDoc} 1534 */ 1535 public void pageBreak() 1536 { 1537 writer.println( "\\page" ); 1538 } 1539 1540 /** {@inheritDoc} */ 1541 public void anchor( String name ) 1542 { 1543 // nop 1544 } 1545 1546 /** 1547 * {@inheritDoc} 1548 */ 1549 public void anchor_() 1550 { 1551 // nop 1552 } 1553 1554 /** {@inheritDoc} */ 1555 public void link( String name ) 1556 { 1557 // nop 1558 } 1559 1560 /** 1561 * {@inheritDoc} 1562 */ 1563 public void link_() 1564 { 1565 // nop 1566 } 1567 1568 /** 1569 * {@inheritDoc} 1570 */ 1571 public void inline() 1572 { 1573 inline( null ); 1574 } 1575 1576 /** {@inheritDoc} */ 1577 public void inline( SinkEventAttributes attributes ) 1578 { 1579 List<Integer> tags = new ArrayList<>(); 1580 1581 if ( attributes != null ) 1582 { 1583 1584 if ( attributes.containsAttribute( SinkEventAttributes.SEMANTICS, "italic" ) ) 1585 { 1586 tags.add( 0, this.style ); 1587 beginStyle( STYLE_ITALIC ); 1588 } 1589 1590 if ( attributes.containsAttribute( SinkEventAttributes.SEMANTICS, "bold" ) ) 1591 { 1592 tags.add( 0, this.style ); 1593 beginStyle( STYLE_BOLD ); 1594 } 1595 1596 if ( attributes.containsAttribute( SinkEventAttributes.SEMANTICS, "code" ) ) 1597 { 1598 tags.add( 0, this.style ); 1599 beginStyle( STYLE_TYPEWRITER ); 1600 } 1601 1602 } 1603 1604 inlineStack.push( tags ); 1605 } 1606 1607 /** 1608 * {@inheritDoc} 1609 */ 1610 public void inline_() 1611 { 1612 for ( Integer style: inlineStack.pop() ) 1613 { 1614 endStyle(); 1615 this.style = style; 1616 } 1617 } 1618 1619 /** 1620 * {@inheritDoc} 1621 */ 1622 public void italic() 1623 { 1624 inline( SinkEventAttributeSet.Semantics.ITALIC ); 1625 } 1626 1627 /** 1628 * {@inheritDoc} 1629 */ 1630 public void italic_() 1631 { 1632 inline_(); 1633 } 1634 1635 /** 1636 * {@inheritDoc} 1637 */ 1638 public void bold() 1639 { 1640 inline( SinkEventAttributeSet.Semantics.BOLD ); 1641 } 1642 1643 /** 1644 * {@inheritDoc} 1645 */ 1646 public void bold_() 1647 { 1648 inline_(); 1649 } 1650 1651 /** 1652 * {@inheritDoc} 1653 */ 1654 public void monospaced() 1655 { 1656 inline( SinkEventAttributeSet.Semantics.CODE ); 1657 } 1658 1659 /** 1660 * {@inheritDoc} 1661 */ 1662 public void monospaced_() 1663 { 1664 inline_(); 1665 } 1666 1667 private void beginStyle( int style ) 1668 { 1669 this.style = style; 1670 1671 switch ( context.get() ) 1672 { 1673 case CONTEXT_TABLE: 1674 break; 1675 default: 1676 if ( paragraph != null ) 1677 { 1678 switch ( style ) 1679 { 1680 case STYLE_ITALIC: 1681 writer.println( "{\\i" ); 1682 break; 1683 case STYLE_BOLD: 1684 writer.println( "{\\b" ); 1685 break; 1686 case STYLE_TYPEWRITER: 1687 writer.println( "{\\f1" ); 1688 break; 1689 default: 1690 writer.println( "{" ); 1691 break; 1692 } 1693 } 1694 break; 1695 } 1696 } 1697 1698 private void endStyle() 1699 { 1700 style = STYLE_ROMAN; 1701 1702 switch ( context.get() ) 1703 { 1704 case CONTEXT_TABLE: 1705 break; 1706 default: 1707 if ( paragraph != null ) 1708 { 1709 writer.println( "}" ); 1710 } 1711 break; 1712 } 1713 } 1714 1715 /** 1716 * {@inheritDoc} 1717 */ 1718 public void lineBreak() 1719 { 1720 switch ( context.get() ) 1721 { 1722 case CONTEXT_TABLE: 1723 cell.add( line ); 1724 line = new Line(); 1725 break; 1726 default: 1727 writer.println( "\\line" ); 1728 break; 1729 } 1730 } 1731 1732 /** 1733 * {@inheritDoc} 1734 */ 1735 public void nonBreakingSpace() 1736 { 1737 switch ( context.get() ) 1738 { 1739 case CONTEXT_TABLE: 1740 line.add( new Item( style, " " ) ); 1741 break; 1742 default: 1743 writer.println( "\\~" ); 1744 break; 1745 } 1746 } 1747 1748 /** {@inheritDoc} */ 1749 public void text( String text ) 1750 { 1751 switch ( context.get() ) 1752 { 1753 case CONTEXT_VERBATIM: 1754 verbatim.append( text ); 1755 break; 1756 1757 case CONTEXT_TABLE: 1758 StringTokenizer t = new StringTokenizer( text, EOL, true ); 1759 while ( t.hasMoreTokens() ) 1760 { 1761 String token = t.nextToken(); 1762 if ( token.equals( EOL ) ) 1763 { 1764 cell.add( line ); 1765 line = new Line(); 1766 } 1767 else 1768 { 1769 line.add( new Item( style, normalize( token ) ) ); 1770 } 1771 } 1772 break; 1773 1774 default: 1775 if ( paragraph == null ) 1776 { 1777 beginParagraph( new Paragraph() ); 1778 } 1779 writer.println( escape( normalize( text ) ) ); 1780 } 1781 } 1782 1783 /** 1784 * {@inheritDoc} 1785 * 1786 * Unkown events just log a warning message but are ignored otherwise. 1787 * @see org.apache.maven.doxia.sink.Sink#unknown(String,Object[],SinkEventAttributes) 1788 */ 1789 public void unknown( String name, Object[] requiredParams, SinkEventAttributes attributes ) 1790 { 1791 String msg = "Unknown Sink event: '" + name + "', ignoring!"; 1792 logMessage( "unknownEvent", msg ); 1793 } 1794 1795 private static String normalize( String s ) 1796 { 1797 int length = s.length(); 1798 StringBuilder buffer = new StringBuilder( length ); 1799 1800 for ( int i = 0; i < length; ++i ) 1801 { 1802 char c = s.charAt( i ); 1803 1804 if ( Character.isWhitespace( c ) ) 1805 { 1806 if ( buffer.length() == 0 || buffer.charAt( buffer.length() - 1 ) != ' ' ) 1807 { 1808 buffer.append( ' ' ); 1809 } 1810 } 1811 1812 else 1813 { 1814 buffer.append( c ); 1815 } 1816 } 1817 1818 return buffer.toString(); 1819 } 1820 1821 private static String escape( String s ) 1822 { 1823 int length = s.length(); 1824 StringBuilder buffer = new StringBuilder( length ); 1825 1826 for ( int i = 0; i < length; ++i ) 1827 { 1828 char c = s.charAt( i ); 1829 switch ( c ) 1830 { 1831 case '\\': 1832 buffer.append( "\\\\" ); 1833 break; 1834 case '{': 1835 buffer.append( "\\{" ); 1836 break; 1837 case '}': 1838 buffer.append( "\\}" ); 1839 break; 1840 default: 1841 buffer.append( c ); 1842 } 1843 } 1844 1845 return buffer.toString(); 1846 } 1847 1848 /** 1849 * <p>getFont.</p> 1850 * 1851 * @param style a int. 1852 * @param size a int. 1853 * @return a {@link org.apache.maven.doxia.module.rtf.Font} object. 1854 */ 1855 protected Font getFont( int style, int size ) 1856 { 1857 Font font = null; 1858 1859 StringBuilder buf = new StringBuilder(); 1860 buf.append( style ); 1861 buf.append( size ); 1862 String key = buf.toString(); 1863 1864 Object object = fontTable.get( key ); 1865 if ( object == null ) 1866 { 1867 try 1868 { 1869 font = new Font( style, size ); 1870 fontTable.put( key, font ); 1871 } 1872 catch ( Exception e ) 1873 { 1874 if ( getLog().isDebugEnabled() ) 1875 { 1876 getLog().debug( e.getMessage(), e ); 1877 } 1878 } 1879 } 1880 else 1881 { 1882 font = (Font) object; 1883 } 1884 1885 return font; 1886 } 1887 1888 private static int textWidth( String text, Font font ) 1889 { 1890 int width = 0; 1891 StringTokenizer t = new StringTokenizer( text, EOL ); 1892 1893 while ( t.hasMoreTokens() ) 1894 { 1895 int w = font.textExtents( t.nextToken() ).width; 1896 if ( w > width ) 1897 { 1898 width = w; 1899 } 1900 } 1901 1902 return width; 1903 } 1904 1905 1906 /** 1907 * {@inheritDoc} 1908 */ 1909 public void flush() 1910 { 1911 writer.flush(); 1912 } 1913 1914 /** 1915 * {@inheritDoc} 1916 */ 1917 public void close() 1918 { 1919 writer.close(); 1920 1921 if ( getLog().isWarnEnabled() && this.warnMessages != null ) 1922 { 1923 for ( Map.Entry<String, Set<String>> entry : this.warnMessages.entrySet() ) 1924 { 1925 1926 Set<String> set = entry.getValue(); 1927 1928 for ( String msg : set ) 1929 { 1930 getLog().warn( msg ); 1931 } 1932 } 1933 1934 this.warnMessages = null; 1935 } 1936 1937 init(); 1938 } 1939 1940 /** 1941 * If debug mode is enabled, log the <code>msg</code> as is, otherwise add unique msg in <code>warnMessages</code>. 1942 * 1943 * @param key not null 1944 * @param msg not null 1945 * @see #close() 1946 * @since 1.1.1 1947 */ 1948 private void logMessage( String key, String msg ) 1949 { 1950 msg = "[RTF Sink] " + msg; 1951 if ( getLog().isDebugEnabled() ) 1952 { 1953 getLog().debug( msg ); 1954 1955 return; 1956 } 1957 1958 if ( warnMessages == null ) 1959 { 1960 warnMessages = new HashMap<>(); 1961 } 1962 1963 Set<String> set = warnMessages.get( key ); 1964 if ( set == null ) 1965 { 1966 set = new TreeSet<>(); 1967 } 1968 set.add( msg ); 1969 warnMessages.put( key, set ); 1970 } 1971 1972 /** 1973 * {@inheritDoc} 1974 */ 1975 protected void init() 1976 { 1977 super.init(); 1978 1979 this.fontTable.clear(); 1980 this.context = new Context(); 1981 this.paragraph = null; 1982 this.indentation = new Indentation( 0 ); 1983 this.space = new Space( 20 * DEFAULT_SPACING ); 1984 Font font = getFont( STYLE_BOLD, fontSize ); 1985 if ( font != null ) 1986 { 1987 this.listItemIndent = textWidth( LIST_ITEM_HEADER, font ); 1988 } 1989 this.numbering.clear(); 1990 this.itemNumber.clear(); 1991 this.style = STYLE_ROMAN; 1992 this.sectionLevel = 0; 1993 this.emptyHeader = false; 1994 this.verbatim = null; 1995 this.frame = false; 1996 this.table = null; 1997 this.row = null; 1998 this.cell = null; 1999 this.line = null; 2000 this.warnMessages = null; 2001 } 2002 2003 // ----------------------------------------------------------------------- 2004 2005 static class Counter 2006 { 2007 private int value; 2008 2009 Counter( int value ) 2010 { 2011 set( value ); 2012 } 2013 2014 void set( int value ) 2015 { 2016 this.value = value; 2017 } 2018 2019 int get() 2020 { 2021 return value; 2022 } 2023 2024 void increment() 2025 { 2026 increment( 1 ); 2027 } 2028 2029 void increment( int value ) 2030 { 2031 this.value += value; 2032 } 2033 } 2034 2035 static class Context 2036 { 2037 private int context = CONTEXT_UNDEFINED; 2038 2039 private Vector<Integer> stack = new Vector<>(); 2040 2041 void set( int context ) 2042 { 2043 stack.addElement( this.context ); 2044 this.context = context; 2045 } 2046 2047 void restore() 2048 { 2049 if ( !stack.isEmpty() ) 2050 { 2051 context = stack.lastElement(); 2052 stack.removeElementAt( stack.size() - 1 ); 2053 } 2054 } 2055 2056 int get() 2057 { 2058 return context; 2059 } 2060 } 2061 2062 class Paragraph 2063 { 2064 int style = 0; 2065 2066 int justification = Sink.JUSTIFY_LEFT; 2067 2068 int leftIndent = indentation.get(); 2069 2070 int rightIndent = 0; 2071 2072 int firstLineIndent = 0; 2073 2074 int spaceBefore = space.getNext(); 2075 2076 int spaceAfter = 0; 2077 2078 boolean frame = false; 2079 2080 int fontStyle = STYLE_ROMAN; 2081 2082 int fontSize = RtfSink.this.fontSize; 2083 2084 Paragraph() 2085 { 2086 // nop 2087 } 2088 2089 Paragraph( int style, int size ) 2090 { 2091 fontStyle = style; 2092 fontSize = size; 2093 } 2094 2095 void begin() 2096 { 2097 writer.print( "\\pard" ); 2098 if ( style > 0 ) 2099 { 2100 writer.print( "\\s" + style ); 2101 } 2102 switch ( justification ) 2103 { 2104 case Sink.JUSTIFY_LEFT: 2105 default: 2106 break; 2107 case Sink.JUSTIFY_CENTER: 2108 writer.print( "\\qc" ); 2109 break; 2110 case Sink.JUSTIFY_RIGHT: 2111 writer.print( "\\qr" ); 2112 break; 2113 } 2114 if ( leftIndent != 0 ) 2115 { 2116 writer.print( "\\li" + leftIndent ); 2117 } 2118 if ( rightIndent != 0 ) 2119 { 2120 writer.print( "\\ri" + rightIndent ); 2121 } 2122 if ( firstLineIndent != 0 ) 2123 { 2124 writer.print( "\\fi" + firstLineIndent ); 2125 } 2126 if ( spaceBefore != 0 ) 2127 { 2128 writer.print( "\\sb" + spaceBefore ); 2129 } 2130 if ( spaceAfter != 0 ) 2131 { 2132 writer.print( "\\sa" + spaceAfter ); 2133 } 2134 2135 if ( frame ) 2136 { 2137 writer.print( "\\box\\brdrs\\brdrw" + BORDER_WIDTH ); 2138 } 2139 2140 writer.print( "\\plain" ); 2141 switch ( fontStyle ) 2142 { 2143 case STYLE_ROMAN: 2144 default: 2145 writer.print( "\\f0" ); 2146 break; 2147 case STYLE_ITALIC: 2148 writer.print( "\\f0\\i" ); 2149 break; 2150 case STYLE_BOLD: 2151 writer.print( "\\f0\\b" ); 2152 break; 2153 case STYLE_TYPEWRITER: 2154 writer.print( "\\f1" ); 2155 break; 2156 } 2157 writer.println( "\\fs" + ( 2 * fontSize ) ); 2158 } 2159 2160 void end() 2161 { 2162 writer.println( "\\par" ); 2163 } 2164 } 2165 2166 class Space 2167 { 2168 private int space; 2169 2170 private int next; 2171 2172 private Vector<Integer> stack = new Vector<>(); 2173 2174 Space( int space /*twips*/ ) 2175 { 2176 this.space = space; 2177 next = space; 2178 } 2179 2180 void set( int space /*twips*/ ) 2181 { 2182 stack.addElement( this.space ); 2183 this.space = space; 2184 next = space; 2185 } 2186 2187 int get() 2188 { 2189 return space; 2190 } 2191 2192 void restore() 2193 { 2194 if ( !stack.isEmpty() ) 2195 { 2196 space = stack.lastElement(); 2197 stack.removeElementAt( stack.size() - 1 ); 2198 next = space; 2199 } 2200 } 2201 2202 void setNext( int space /*twips*/ ) 2203 { 2204 next = space; 2205 } 2206 2207 int getNext() 2208 { 2209 int nxt = this.next; 2210 this.next = space; 2211 return nxt; 2212 } 2213 2214 void skip() 2215 { 2216 skip( getNext() ); 2217 } 2218 2219 void skip( int space /*twips*/ ) 2220 { 2221 writer.print( "\\pard" ); 2222 if ( ( space -= 10 ) > 0 ) 2223 { 2224 writer.print( "\\sb" + space ); 2225 } 2226 writer.println( "\\plain\\fs1\\par" ); 2227 } 2228 } 2229 2230 static class Indentation 2231 { 2232 private int indent; 2233 2234 private Vector<Integer> stack = new Vector<>(); 2235 2236 Indentation( int indent /*twips*/ ) 2237 { 2238 this.indent = indent; 2239 } 2240 2241 void set( int indent /*twips*/ ) 2242 { 2243 stack.addElement( this.indent ); 2244 this.indent = indent; 2245 } 2246 2247 int get() 2248 { 2249 return indent; 2250 } 2251 2252 void restore() 2253 { 2254 if ( !stack.isEmpty() ) 2255 { 2256 indent = stack.lastElement(); 2257 stack.removeElementAt( stack.size() - 1 ); 2258 } 2259 } 2260 2261 void add( int indent /*twips*/ ) 2262 { 2263 set( this.indent + indent ); 2264 } 2265 } 2266 2267 static class Table 2268 { 2269 int numColumns; 2270 2271 int[] columnWidths; 2272 2273 int[] justification; 2274 2275 boolean grid; 2276 2277 Vector<Row> rows; 2278 2279 Table( int[] justification, boolean grid ) 2280 { 2281 numColumns = justification.length; 2282 columnWidths = new int[numColumns]; 2283 this.justification = justification; 2284 this.grid = grid; 2285 rows = new Vector<>(); 2286 } 2287 2288 void add( Row row ) 2289 { 2290 rows.addElement( row ); 2291 2292 for ( int i = 0; i < numColumns; ++i ) 2293 { 2294 if ( i >= row.cells.size() ) 2295 { 2296 break; 2297 } 2298 Cell cell = row.cells.elementAt( i ); 2299 int width = cell.boundingBox().width; 2300 if ( width > columnWidths[i] ) 2301 { 2302 columnWidths[i] = width; 2303 } 2304 } 2305 } 2306 2307 int width() 2308 { 2309 int width = 0; 2310 for ( int i = 0; i < numColumns; ++i ) 2311 { 2312 width += columnWidths[i]; 2313 } 2314 if ( grid ) 2315 { 2316 width += ( numColumns + 1 ) * BORDER_WIDTH; 2317 } 2318 return width; 2319 } 2320 } 2321 2322 static class Row 2323 { 2324 Vector<Cell> cells = new Vector<>(); 2325 2326 void add( Cell cell ) 2327 { 2328 cells.addElement( cell ); 2329 } 2330 2331 int height() 2332 { 2333 int height = 0; 2334 int numCells = cells.size(); 2335 for ( int i = 0; i < numCells; ++i ) 2336 { 2337 Cell cell = cells.elementAt( i ); 2338 Box box = cell.boundingBox(); 2339 if ( box.height > height ) 2340 { 2341 height = box.height; 2342 } 2343 } 2344 return height; 2345 } 2346 } 2347 2348 class Cell 2349 { 2350 Vector<Line> lines = new Vector<>(); 2351 2352 void add( Line line ) 2353 { 2354 lines.addElement( line ); 2355 } 2356 2357 Box boundingBox() 2358 { 2359 int width = 0; 2360 int height = 0; 2361 2362 for ( int i = 0; i < lines.size(); ++i ) 2363 { 2364 int w = 0; 2365 int h = 0; 2366 Line line = (Line) lines.elementAt( i ); 2367 2368 for ( int j = 0; j < line.items.size(); ++j ) 2369 { 2370 Item item = (Item) line.items.elementAt( j ); 2371 Font font = getFont( item.style, fontSize ); 2372 if ( font == null ) 2373 { 2374 continue; 2375 } 2376 Font.TextExtents x = font.textExtents( item.text ); 2377 w += x.width; 2378 if ( x.height > h ) 2379 { 2380 h = x.height; 2381 } 2382 } 2383 2384 if ( w > width ) 2385 { 2386 width = w; 2387 } 2388 height += h; 2389 } 2390 2391 width += ( 2 * CELL_HORIZONTAL_PAD ); 2392 height += ( 2 * CELL_VERTICAL_PAD ); 2393 2394 // allow one more pixel for grid outline 2395 width += toTwips( 1., UNIT_PIXEL ); 2396 2397 return new Box( width, height ); 2398 } 2399 } 2400 2401 static class Line 2402 { 2403 Vector<Item> items = new Vector<>(); 2404 2405 void add( Item item ) 2406 { 2407 items.addElement( item ); 2408 } 2409 } 2410 2411 static class Item 2412 { 2413 int style; 2414 2415 String text; 2416 2417 Item( int style, String text ) 2418 { 2419 this.style = style; 2420 this.text = text; 2421 } 2422 } 2423 2424 static class Box 2425 { 2426 int width; 2427 2428 int height; 2429 2430 Box( int width, int height ) 2431 { 2432 this.width = width; 2433 this.height = height; 2434 } 2435 } 2436}