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