blob: 34bbce92efbe0efbff20dd30722c67e57b4a26fb [file] [log] [blame]
package org.apache.maven.doxia.module.rtf;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import java.awt.Color;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.StringTokenizer;
import java.util.TreeSet;
import java.util.Vector;
import org.apache.maven.doxia.sink.Sink;
import org.apache.maven.doxia.sink.SinkEventAttributes;
import org.apache.maven.doxia.sink.impl.AbstractTextSink;
import org.apache.maven.doxia.sink.impl.SinkEventAttributeSet;
/**
* <a href="http://en.wikipedia.org/wiki/Rich_Text_Format">RTF</a> Sink implementation.
*
* @since 1.0
*/
public class RtfSink
extends AbstractTextSink
{
/** Paper width, 21 cm */
public static final double DEFAULT_PAPER_WIDTH = 21.; /*cm*/
/** Paper height, 29.7 cm */
public static final double DEFAULT_PAPER_HEIGHT = 29.7; /*cm*/
/** Paper top margin, 2 cm */
public static final double DEFAULT_TOP_MARGIN = 2.; /*cm*/
/** Paper bottom margin, 2 cm */
public static final double DEFAULT_BOTTOM_MARGIN = 2.; /*cm*/
/** Paper left margin, 2 cm */
public static final double DEFAULT_LEFT_MARGIN = 2.; /*cm*/
/** Paper right margin, 2 cm */
public static final double DEFAULT_RIGHT_MARGIN = 2.; /*cm*/
/** Font size, 10 pts */
public static final int DEFAULT_FONT_SIZE = 10; /*pts*/
/** Spacing, 10 pts */
public static final int DEFAULT_SPACING = 10; /*pts*/
/** Resolution, 72 dpi */
public static final int DEFAULT_RESOLUTION = 72; /*dpi*/
/** Image format, bmp */
public static final String DEFAULT_IMAGE_FORMAT = "bmp";
/** Image type, palette */
public static final String DEFAULT_IMAGE_TYPE = "palette";
/** Data format, ascii */
public static final String DEFAULT_DATA_FORMAT = "ascii";
/** Codepage, 1252 */
public static final int DEFAULT_CODE_PAGE = 1252;
/** Constant <code>DEFAULT_CHAR_SET=0</code> */
public static final int DEFAULT_CHAR_SET = 0;
/** Constant <code>IMG_FORMAT_BMP="bmp"</code> */
public static final String IMG_FORMAT_BMP = "bmp";
/** Constant <code>IMG_FORMAT_WMF="wmf"</code> */
public static final String IMG_FORMAT_WMF = "wmf";
/** Constant <code>IMG_TYPE_PALETTE="palette"</code> */
public static final String IMG_TYPE_PALETTE = "palette";
/** Constant <code>IMG_TYPE_RGB="rgb"</code> */
public static final String IMG_TYPE_RGB = "rgb";
/** Constant <code>IMG_DATA_ASCII="ascii"</code> */
public static final String IMG_DATA_ASCII = "ascii";
/** Constant <code>IMG_DATA_RAW="raw"</code> */
public static final String IMG_DATA_RAW = "raw";
/** Constant <code>STYLE_ROMAN=0</code> */
public static final int STYLE_ROMAN = 0;
/** Constant <code>STYLE_ITALIC=1</code> */
public static final int STYLE_ITALIC = 1;
/** Constant <code>STYLE_BOLD=2</code> */
public static final int STYLE_BOLD = 2;
/** Constant <code>STYLE_TYPEWRITER=3</code> */
public static final int STYLE_TYPEWRITER = 3;
private static final int CONTEXT_UNDEFINED = 0;
private static final int CONTEXT_VERBATIM = 1;
private static final int CONTEXT_TABLE = 2;
private static final int UNIT_MILLIMETER = 1;
private static final int UNIT_CENTIMETER = 2;
private static final int UNIT_INCH = 3;
private static final int UNIT_PIXEL = 4;
private static final int LIST_INDENT = 300; /*twips*/
private static final String LIST_ITEM_HEADER = "- ";
private static final int DEFINITION_INDENT = 300; /*twips*/
private static final int CELL_HORIZONTAL_PAD = 60; /*twips*/
private static final int CELL_VERTICAL_PAD = 20; /*twips*/
private static final int BORDER_WIDTH = 15; /*twips*/
private double paperWidth = DEFAULT_PAPER_WIDTH;
private double paperHeight = DEFAULT_PAPER_HEIGHT;
private double topMargin = DEFAULT_TOP_MARGIN;
private double bottomMargin = DEFAULT_BOTTOM_MARGIN;
private double leftMargin = DEFAULT_LEFT_MARGIN;
private double rightMargin = DEFAULT_RIGHT_MARGIN;
protected int fontSize = DEFAULT_FONT_SIZE;
private int resolution = DEFAULT_RESOLUTION;
private String imageFormat = DEFAULT_IMAGE_FORMAT;
private String imageType = DEFAULT_IMAGE_TYPE;
private String imageDataFormat = DEFAULT_DATA_FORMAT;
private boolean imageCompression = true;
private int codePage = DEFAULT_CODE_PAGE;
private int charSet = DEFAULT_CHAR_SET;
private final Hashtable<String, Font> fontTable;
private Context context;
private Paragraph paragraph;
protected Indentation indentation;
protected Space space;
private int listItemIndent;
private final Vector<Integer> numbering;
private final Vector<Counter> itemNumber;
private int style = STYLE_ROMAN;
private int sectionLevel;
private boolean emptyHeader;
private StringBuilder verbatim;
private boolean frame;
private Table table;
private Row row;
private Cell cell;
private Line line;
protected PrintWriter writer;
protected OutputStream stream; // for raw image data
/** Keep track of the closing tags for inline events. */
protected Stack<List<Integer>> inlineStack = new Stack<>();
/** Map of warn messages with a String as key to describe the error type and a Set as value.
* Using to reduce warn messages. */
private Map<String, Set<String>> warnMessages;
// -----------------------------------------------------------------------
/**
* <p>Constructor for RtfSink.</p>
*
* @throws java.io.IOException if any.
*/
protected RtfSink()
throws IOException
{
this( System.out );
}
/**
* <p>Constructor for RtfSink.</p>
*
* @param output not null
* @throws java.io.IOException if any.
*/
protected RtfSink( OutputStream output )
throws IOException
{
this( output, null );
}
/**
* <p>Constructor for RtfSink.</p>
*
* @param output not null
* @param encoding a valid charset
* @throws java.io.IOException if any
*/
protected RtfSink( OutputStream output, String encoding )
throws IOException
{
this.fontTable = new Hashtable<>();
this.numbering = new Vector<>();
this.itemNumber = new Vector<>();
Writer w;
this.stream = new BufferedOutputStream( output );
// TODO: encoding should be consistent with codePage
if ( encoding != null )
{
w = new OutputStreamWriter( stream, encoding );
}
else
{
w = new OutputStreamWriter( stream );
}
this.writer = new PrintWriter( new BufferedWriter( w ) );
init();
}
/**
* setPaperSize.
*
* @param width in cm.
* @param height in cm.
*/
public void setPaperSize( double width /*cm*/, double height /*cm*/ )
{
paperWidth = width;
paperHeight = height;
}
/**
* <p>Setter for the field <code>topMargin</code>.</p>
*
* @param margin margin.
*/
public void setTopMargin( double margin )
{
topMargin = margin;
}
/**
* <p>Setter for the field <code>bottomMargin</code>.</p>
*
* @param margin margin.
*/
public void setBottomMargin( double margin )
{
bottomMargin = margin;
}
/**
* <p>Setter for the field <code>leftMargin</code>.</p>
*
* @param margin margin
*/
public void setLeftMargin( double margin )
{
leftMargin = margin;
}
/**
* <p>Setter for the field <code>rightMargin</code>.</p>
*
* @param margin margin
*/
public void setRightMargin( double margin )
{
rightMargin = margin;
}
/**
* <p>Setter for the field <code>fontSize</code>.</p>
*
* @param size in pts
*/
public void setFontSize( int size /*pts*/ )
{
fontSize = size;
}
/**
* <p>setSpacing.</p>
*
* @param spacing in pts.
*/
public void setSpacing( int spacing /*pts*/ )
{
space.set( 20 * spacing );
}
/**
* <p>Setter for the field <code>resolution</code>.</p>
*
* @param resolution in dpi
*/
public void setResolution( int resolution /*dpi*/ )
{
this.resolution = resolution;
}
/**
* <p>Setter for the field <code>imageFormat</code>.</p>
*
* @param format a {@link java.lang.String} object.
*/
public void setImageFormat( String format )
{
imageFormat = format;
}
/**
* <p>Setter for the field <code>imageType</code>.</p>
*
* @param type a {@link java.lang.String} object.
*/
public void setImageType( String type )
{
imageType = type;
}
/**
* <p>Setter for the field <code>imageDataFormat</code>.</p>
*
* @param format a {@link java.lang.String} object.
*/
public void setImageDataFormat( String format )
{
imageDataFormat = format;
}
/**
* <p>Setter for the field <code>imageCompression</code>.</p>
*
* @param compression a boolean.
*/
public void setImageCompression( boolean compression )
{
imageCompression = compression;
}
/**
* <p>Setter for the field <code>codePage</code>.</p>
*
* @param cp a int.
*/
public void setCodePage( int cp )
{
codePage = cp;
}
/**
* <p>Setter for the field <code>charSet</code>.</p>
*
* @param cs a int.
*/
public void setCharSet( int cs )
{
charSet = cs;
}
/**
* {@inheritDoc}
*/
public void head()
{
init();
writer.println( "{\\rtf1\\ansi\\ansicpg" + codePage + "\\deff0" );
writer.println( "{\\fonttbl" );
writer.println( "{\\f0\\froman\\fcharset" + charSet + " Times;}" );
writer.println( "{\\f1\\fmodern\\fcharset" + charSet + " Courier;}" );
writer.println( "}" );
writer.println( "{\\stylesheet" );
for ( int level = 1; level <= 5; ++level )
{
writer.print( "{\\s" + styleNumber( level ) );
writer.print( "\\outlinelevel" + level );
writer.print( " Section Title " + level );
writer.println( ";}" );
}
writer.println( "}" );
writer.println( "\\paperw" + toTwips( paperWidth, UNIT_CENTIMETER ) );
writer.println( "\\paperh" + toTwips( paperHeight, UNIT_CENTIMETER ) );
writer.println( "\\margl" + toTwips( leftMargin, UNIT_CENTIMETER ) );
writer.println( "\\margr" + toTwips( rightMargin, UNIT_CENTIMETER ) );
writer.println( "\\margt" + toTwips( topMargin, UNIT_CENTIMETER ) );
writer.println( "\\margb" + toTwips( bottomMargin, UNIT_CENTIMETER ) );
space.set( space.get() / 2 );
space.setNext( 0 );
emptyHeader = true;
}
/**
* {@inheritDoc}
*/
public void head_()
{
space.restore();
if ( emptyHeader )
{
space.setNext( 0 );
}
else
{
space.setNext( 2 * space.get() );
}
}
/**
* <p>toTwips.</p>
*
* @param length a double.
* @param unit a int.
* @return a int.
*/
protected int toTwips( double length, int unit )
{
double points;
switch ( unit )
{
case UNIT_MILLIMETER:
points = ( length / 25.4 ) * 72.;
break;
case UNIT_CENTIMETER:
points = ( length / 2.54 ) * 72.;
break;
case UNIT_INCH:
points = length * 72.;
break;
case UNIT_PIXEL:
default:
points = ( length / resolution ) * 72.;
break;
}
return (int) Math.rint( points * 20. );
}
/**
* {@inheritDoc}
*/
public void title()
{
Paragraph p = new Paragraph( STYLE_BOLD, fontSize + 6 );
p.justification = Sink.JUSTIFY_CENTER;
beginParagraph( p );
emptyHeader = false;
}
/**
* {@inheritDoc}
*/
public void title_()
{
endParagraph();
}
/**
* {@inheritDoc}
*/
public void author()
{
Paragraph p = new Paragraph( STYLE_ROMAN, fontSize + 2 );
p.justification = Sink.JUSTIFY_CENTER;
beginParagraph( p );
emptyHeader = false;
}
/**
* {@inheritDoc}
*/
public void author_()
{
endParagraph();
}
/**
* {@inheritDoc}
*/
public void date()
{
Paragraph p = new Paragraph( STYLE_ROMAN, fontSize );
p.justification = Sink.JUSTIFY_CENTER;
beginParagraph( p );
emptyHeader = false;
}
/**
* {@inheritDoc}
*/
public void date_()
{
endParagraph();
}
/**
* {@inheritDoc}
*/
public void body()
{
// nop
}
/**
* {@inheritDoc}
*/
public void body_()
{
writer.println( "}" );
writer.flush();
}
/**
* {@inheritDoc}
*/
public void section1()
{
sectionLevel = 1;
}
/**
* {@inheritDoc}
*/
public void section1_()
{
// nop
}
/**
* {@inheritDoc}
*/
public void section2()
{
sectionLevel = 2;
}
/**
* {@inheritDoc}
*/
public void section2_()
{
// nop
}
/**
* {@inheritDoc}
*/
public void section3()
{
sectionLevel = 3;
}
/**
* {@inheritDoc}
*/
public void section3_()
{
// nop
}
/**
* {@inheritDoc}
*/
public void section4()
{
sectionLevel = 4;
}
/**
* {@inheritDoc}
*/
public void section4_()
{
// nop
}
/**
* {@inheritDoc}
*/
public void section5()
{
sectionLevel = 5;
}
/**
* {@inheritDoc}
*/
public void section5_()
{
// nop
}
/**
* {@inheritDoc}
*/
public void sectionTitle()
{
int stl = STYLE_BOLD;
int size = fontSize;
switch ( sectionLevel )
{
case 1:
size = fontSize + 6;
break;
case 2:
size = fontSize + 4;
break;
case 3:
size = fontSize + 2;
break;
case 4:
break;
case 5:
stl = STYLE_ROMAN;
break;
default:
}
Paragraph p = new Paragraph( stl, size );
p.style = styleNumber( sectionLevel );
beginParagraph( p );
}
/**
* {@inheritDoc}
*/
public void sectionTitle_()
{
endParagraph();
}
private int styleNumber( int level )
{
return level;
}
/**
* {@inheritDoc}
*/
public void list()
{
indentation.add( LIST_INDENT );
space.set( space.get() / 2 );
}
/**
* {@inheritDoc}
*/
public void list_()
{
indentation.restore();
space.restore();
}
/**
* {@inheritDoc}
*/
public void listItem()
{
Paragraph p = new Paragraph();
p.leftIndent = indentation.get() + listItemIndent;
p.firstLineIndent = ( -listItemIndent );
beginParagraph( p );
beginStyle( STYLE_BOLD );
writer.println( LIST_ITEM_HEADER );
endStyle();
indentation.add( listItemIndent );
space.set( space.get() / 2 );
}
/**
* {@inheritDoc}
*/
public void listItem_()
{
endParagraph();
indentation.restore();
space.restore();
}
/** {@inheritDoc} */
public void numberedList( int numbering )
{
this.numbering.addElement( numbering );
itemNumber.addElement( new Counter( 0 ) );
indentation.add( LIST_INDENT );
space.set( space.get() / 2 );
}
/**
* {@inheritDoc}
*/
public void numberedList_()
{
numbering.removeElementAt( numbering.size() - 1 );
itemNumber.removeElementAt( itemNumber.size() - 1 );
indentation.restore();
space.restore();
}
/**
* {@inheritDoc}
*/
public void numberedListItem()
{
( (Counter) itemNumber.lastElement() ).increment();
int indent = 0;
String header = getItemHeader();
Font font = getFont( STYLE_TYPEWRITER, fontSize );
if ( font != null )
{
indent = textWidth( header, font );
}
Paragraph p = new Paragraph();
p.leftIndent = indentation.get() + indent;
p.firstLineIndent = ( -indent );
beginParagraph( p );
beginStyle( STYLE_TYPEWRITER );
writer.println( header );
endStyle();
indentation.add( indent );
space.set( space.get() / 2 );
}
/**
* {@inheritDoc}
*/
public void numberedListItem_()
{
endParagraph();
indentation.restore();
space.restore();
}
private String getItemHeader()
{
int nmb = (Integer) this.numbering.lastElement();
int iNmb = ( (Counter) this.itemNumber.lastElement() ).get();
StringBuilder buf = new StringBuilder();
switch ( nmb )
{
case Sink.NUMBERING_DECIMAL:
default:
buf.append( iNmb );
buf.append( ". " );
while ( buf.length() < 4 )
{
buf.append( ' ' );
}
break;
case Sink.NUMBERING_LOWER_ALPHA:
buf.append( AlphaNumerals.toString( iNmb, true ) );
buf.append( ") " );
break;
case Sink.NUMBERING_UPPER_ALPHA:
buf.append( AlphaNumerals.toString( iNmb, false ) );
buf.append( ". " );
break;
case Sink.NUMBERING_LOWER_ROMAN:
buf.append( RomanNumerals.toString( iNmb, true ) );
buf.append( ") " );
while ( buf.length() < 6 )
{
buf.append( ' ' );
}
break;
case Sink.NUMBERING_UPPER_ROMAN:
buf.append( RomanNumerals.toString( iNmb, false ) );
buf.append( ". " );
while ( buf.length() < 6 )
{
buf.append( ' ' );
}
break;
}
return buf.toString();
}
/**
* {@inheritDoc}
*/
public void definitionList()
{
int next = space.getNext();
indentation.add( LIST_INDENT );
space.set( space.get() / 2 );
space.setNext( next );
}
/**
* {@inheritDoc}
*/
public void definitionList_()
{
indentation.restore();
space.restore();
}
/**
* {@inheritDoc}
*/
public void definitionListItem()
{
int next = space.getNext();
space.set( space.get() / 2 );
space.setNext( next );
}
/**
* {@inheritDoc}
*/
public void definitionListItem_()
{
space.restore();
}
/**
* {@inheritDoc}
*/
public void definedTerm()
{
// nop
}
/**
* {@inheritDoc}
*/
public void definedTerm_()
{
endParagraph();
}
/**
* {@inheritDoc}
*/
public void definition()
{
int next = space.getNext();
indentation.add( DEFINITION_INDENT );
space.set( space.get() / 2 );
space.setNext( next );
}
/**
* {@inheritDoc}
*/
public void definition_()
{
endParagraph();
indentation.restore();
space.restore();
}
/**
* {@inheritDoc}
*/
public void table()
{
// nop
}
/**
* {@inheritDoc}
*/
public void table_()
{
// nop
}
/** {@inheritDoc} */
public void tableRows( int[] justification, boolean grid )
{
table = new Table( justification, grid );
context.set( CONTEXT_TABLE );
}
/**
* {@inheritDoc}
*/
public void tableRows_()
{
boolean bb = false;
boolean br = false;
int offset = ( pageWidth() - ( table.width() + indentation.get() ) ) / 2;
int x0 = indentation.get() + offset;
space.skip();
for ( int i = 0; i < table.rows.size(); ++i )
{
Row r = (Row) table.rows.elementAt( i );
writer.print( "\\trowd" );
writer.print( "\\trleft" + x0 );
writer.print( "\\trgaph" + CELL_HORIZONTAL_PAD );
writer.println( "\\trrh" + r.height() );
if ( table.grid )
{
if ( i == ( table.rows.size() - 1 ) )
{
bb = true;
}
br = false;
}
for ( int j = 0, x = x0; j < table.numColumns; ++j )
{
if ( table.grid )
{
if ( j == ( table.numColumns - 1 ) )
{
br = true;
}
setBorder( true, bb, true, br );
x += BORDER_WIDTH;
}
x += table.columnWidths[j];
writer.println( "\\clvertalc\\cellx" + x );
}
for ( int j = 0; j < table.numColumns; ++j )
{
if ( j >= r.cells.size() )
{
break;
}
Cell c = (Cell) r.cells.elementAt( j );
writer.print( "\\pard\\intbl" );
setJustification( table.justification[j] );
writer.println( "\\plain\\f0\\fs" + ( 2 * fontSize ) );
for ( int k = 0; k < c.lines.size(); ++k )
{
if ( k > 0 )
{
writer.println( "\\line" );
}
Line l = (Line) c.lines.elementAt( k );
for ( int n = 0; n < l.items.size(); ++n )
{
Item item = (Item) l.items.elementAt( n );
writer.print( "{" );
setStyle( item.style );
writer.println( escape( item.text ) );
writer.println( "}" );
}
}
writer.println( "\\cell" );
}
writer.println( "\\row" );
}
context.restore();
}
private int pageWidth()
{
double width = paperWidth - ( leftMargin + rightMargin );
return toTwips( width, UNIT_CENTIMETER );
}
private void setBorder( boolean bt, boolean bb, boolean bl, boolean br )
{
if ( bt )
{
writer.println( "\\clbrdrt\\brdrs\\brdrw" + BORDER_WIDTH );
}
if ( bb )
{
writer.println( "\\clbrdrb\\brdrs\\brdrw" + BORDER_WIDTH );
}
if ( bl )
{
writer.println( "\\clbrdrl\\brdrs\\brdrw" + BORDER_WIDTH );
}
if ( br )
{
writer.println( "\\clbrdrr\\brdrs\\brdrw" + BORDER_WIDTH );
}
}
private void setJustification( int justification )
{
switch ( justification )
{
case Sink.JUSTIFY_LEFT:
default:
writer.println( "\\ql" );
break;
case Sink.JUSTIFY_CENTER:
writer.println( "\\qc" );
break;
case Sink.JUSTIFY_RIGHT:
writer.println( "\\qr" );
break;
}
}
private void setStyle( int style )
{
switch ( style )
{
case STYLE_ITALIC:
writer.println( "\\i" );
break;
case STYLE_BOLD:
writer.println( "\\b" );
break;
case STYLE_TYPEWRITER:
writer.println( "\\f1" );
break;
default:
break;
}
}
/**
* {@inheritDoc}
*/
public void tableRow()
{
row = new Row();
}
/**
* {@inheritDoc}
*/
public void tableRow_()
{
table.add( row );
}
/**
* {@inheritDoc}
*/
public void tableHeaderCell()
{
tableCell();
}
/**
* {@inheritDoc}
*/
public void tableHeaderCell_()
{
tableCell_();
}
/**
* {@inheritDoc}
*/
public void tableCell()
{
cell = new Cell();
line = new Line();
}
/**
* {@inheritDoc}
*/
public void tableCell_()
{
cell.add( line );
row.add( cell );
}
/**
* {@inheritDoc}
*/
public void tableCaption()
{
Paragraph p = new Paragraph();
p.justification = Sink.JUSTIFY_CENTER;
p.spaceBefore /= 2;
beginParagraph( p );
}
/**
* {@inheritDoc}
*/
public void tableCaption_()
{
endParagraph();
}
/**
* {@inheritDoc}
*/
public void paragraph()
{
if ( paragraph == null )
{
beginParagraph( new Paragraph() );
}
}
/**
* {@inheritDoc}
*/
public void paragraph_()
{
endParagraph();
}
private void beginParagraph( Paragraph p )
{
p.begin();
this.paragraph = p;
if ( style != STYLE_ROMAN )
{
beginStyle( style );
}
}
private void endParagraph()
{
if ( paragraph != null )
{
if ( style != STYLE_ROMAN )
{
endStyle();
}
paragraph.end();
paragraph = null;
}
}
/** {@inheritDoc} */
public void verbatim( boolean boxed )
{
verbatim = new StringBuilder();
frame = boxed;
context.set( CONTEXT_VERBATIM );
}
/**
* {@inheritDoc}
*/
public void verbatim_()
{
String text = verbatim.toString();
Paragraph p = new Paragraph();
p.fontStyle = STYLE_TYPEWRITER;
p.frame = frame;
beginParagraph( p );
StringTokenizer t = new StringTokenizer( text, EOL, true );
while ( t.hasMoreTokens() )
{
String s = t.nextToken();
if ( s.equals( EOL ) && t.hasMoreTokens() )
{
writer.println( "\\line" );
}
else
{
writer.println( escape( s ) );
}
}
endParagraph();
context.restore();
}
/**
* {@inheritDoc}
*/
public void figure()
{
// nop
}
/**
* {@inheritDoc}
*/
public void figure_()
{
// nop
}
/** {@inheritDoc} */
public void figureGraphics( String name )
{
Paragraph p = new Paragraph();
p.justification = Sink.JUSTIFY_CENTER;
beginParagraph( p );
try
{
writeImage( name );
}
catch ( Exception e )
{
getLog().error( e.getMessage(), e );
}
endParagraph();
}
private void writeImage( String source )
throws Exception
{
if ( !source.toLowerCase().endsWith( ".ppm" ) )
{
// TODO support more image types!
String msg =
"Unsupported image type for image file: '" + source + "'. Only PPM image type is "
+ "currently supported.";
logMessage( "unsupportedImage", msg );
return;
}
int bytesPerLine;
PBMReader ppm = new PBMReader( source );
WMFWriter.Dib dib = new WMFWriter.Dib();
WMFWriter wmf = new WMFWriter();
int srcWidth = ppm.width();
int srcHeight = ppm.height();
dib.biWidth = srcWidth;
dib.biHeight = srcHeight;
dib.biXPelsPerMeter = (int) ( resolution * 100. / 2.54 );
dib.biYPelsPerMeter = dib.biXPelsPerMeter;
if ( imageType.equals( IMG_TYPE_RGB ) )
{
dib.biBitCount = 24;
dib.biCompression = WMFWriter.Dib.BI_RGB; // no compression
bytesPerLine = 4 * ( ( 3 * srcWidth + 3 ) / 4 );
dib.bitmap = new byte[srcHeight * bytesPerLine];
byte[] l = new byte[3 * srcWidth];
for ( int i = ( srcHeight - 1 ); i >= 0; --i )
{
ppm.read( l, 0, l.length );
for ( int j = 0, k = ( i * bytesPerLine ); j < l.length; j += 3 )
{
// component order = BGR
dib.bitmap[k++] = l[j + 2];
dib.bitmap[k++] = l[j + 1];
dib.bitmap[k++] = l[j];
}
}
}
else
{
dib.biBitCount = 8;
bytesPerLine = 4 * ( ( srcWidth + 3 ) / 4 );
byte[] bitmap = new byte[srcHeight * bytesPerLine];
Vector<Color> colors = new Vector<>( 256 );
colors.addElement( Color.white );
colors.addElement( Color.black );
byte[] l = new byte[3 * srcWidth];
for ( int i = ( srcHeight - 1 ); i >= 0; --i )
{
ppm.read( l, 0, l.length );
for ( int j = 0, k = ( i * bytesPerLine ); j < l.length; )
{
int r = (int) l[j++] & 0xff;
int g = (int) l[j++] & 0xff;
int b = (int) l[j++] & 0xff;
Color color = new Color( r, g, b );
int index = colors.indexOf( color );
if ( index < 0 )
{
if ( colors.size() < colors.capacity() )
{
colors.addElement( color );
index = colors.size() - 1;
}
else
{
index = 1;
}
}
bitmap[k++] = (byte) index;
}
}
dib.biClrUsed = colors.size();
dib.biClrImportant = dib.biClrUsed;
dib.palette = new byte[4 * dib.biClrUsed];
for ( int i = 0, j = 0; i < dib.biClrUsed; ++i, ++j )
{
Color color = (Color) colors.elementAt( i );
dib.palette[j++] = (byte) color.getBlue();
dib.palette[j++] = (byte) color.getGreen();
dib.palette[j++] = (byte) color.getRed();
}
if ( imageCompression )
{
dib.biCompression = WMFWriter.Dib.BI_RLE8;
dib.bitmap = new byte[bitmap.length + ( 2 * ( bitmap.length / 255 + 1 ) )];
dib.biSizeImage = WMFWriter.Dib.rlEncode8( bitmap, 0, bitmap.length, dib.bitmap, 0 );
}
else
{
dib.biCompression = WMFWriter.Dib.BI_RGB;
dib.bitmap = bitmap;
}
}
if ( imageFormat.equals( IMG_FORMAT_WMF ) )
{
int[] parameters;
WMFWriter.Record record;
/*
* See the libwmf library documentation
* (http://www.wvware.com/wmf_doc_index.html)
* for a description of WMF records.
*/
// set mapping mode to MM_TEXT (logical unit = pixel)
parameters = new int[1];
parameters[0] = 1;
record = new WMFWriter.Record( 0x0103, parameters );
wmf.add( record );
// set window origin and dimensions
parameters = new int[2];
record = new WMFWriter.Record( 0x020b, parameters );
wmf.add( record );
parameters = new int[2];
parameters[0] = srcHeight;
parameters[1] = srcWidth;
record = new WMFWriter.Record( 0x020c, parameters );
wmf.add( record );
parameters = new int[WMFWriter.DibBitBltRecord.P_COUNT];
// raster operation = SRCCOPY (0x00cc0020)
parameters[WMFWriter.DibBitBltRecord.P_ROP_H] = 0x00cc;
parameters[WMFWriter.DibBitBltRecord.P_ROP_L] = 0x0020;
parameters[WMFWriter.DibBitBltRecord.P_WIDTH] = srcWidth;
parameters[WMFWriter.DibBitBltRecord.P_HEIGHT] = srcHeight;
record = new WMFWriter.DibBitBltRecord( parameters, dib );
wmf.add( record );
}
if ( imageFormat.equals( IMG_FORMAT_WMF ) )
{
writer.print( "{\\pict\\wmetafile1" );
writer.println( "\\picbmp\\picbpp" + dib.biBitCount );
}
else
{
writer.print( "{\\pict\\dibitmap0\\wbmplanes1" );
writer.print( "\\wbmbitspixel" + dib.biBitCount );
writer.println( "\\wbmwidthbytes" + bytesPerLine );
}
writer.print( "\\picw" + srcWidth );
writer.print( "\\pich" + srcHeight );
writer.print( "\\picwgoal" + toTwips( srcWidth, UNIT_PIXEL ) );
writer.println( "\\pichgoal" + toTwips( srcHeight, UNIT_PIXEL ) );
if ( imageFormat.equals( IMG_FORMAT_WMF ) )
{
if ( imageDataFormat.equals( IMG_DATA_RAW ) )
{
writer.print( "\\bin" + ( 2 * wmf.size() ) + " " );
writer.flush();
wmf.write( stream );
stream.flush();
}
else
{
wmf.print( writer );
}
}
else
{
if ( imageDataFormat.equals( IMG_DATA_RAW ) )
{
writer.print( "\\bin" + ( 2 * dib.size() ) + " " );
writer.flush();
dib.write( stream );
stream.flush();
}
else
{
dib.print( writer );
}
}
writer.println( "}" );
}
/**
* {@inheritDoc}
*/
public void figureCaption()
{
Paragraph p = new Paragraph();
p.justification = Sink.JUSTIFY_CENTER;
p.spaceBefore /= 2;
beginParagraph( p );
}
/**
* {@inheritDoc}
*/
public void figureCaption_()
{
endParagraph();
}
/**
* {@inheritDoc}
*/
public void horizontalRule()
{
writer.print( "\\pard\\li" + indentation.get() );
int skip = space.getNext();
if ( skip > 0 )
{
writer.print( "\\sb" + skip );
}
space.setNext( skip );
writer.print( "\\brdrb\\brdrs\\brdrw" + BORDER_WIDTH );
writer.println( "\\plain\\fs1\\par" );
}
/**
* {@inheritDoc}
*/
public void pageBreak()
{
writer.println( "\\page" );
}
/** {@inheritDoc} */
public void anchor( String name )
{
// nop
}
/**
* {@inheritDoc}
*/
public void anchor_()
{
// nop
}
/** {@inheritDoc} */
public void link( String name )
{
// nop
}
/**
* {@inheritDoc}
*/
public void link_()
{
// nop
}
/**
* {@inheritDoc}
*/
public void inline()
{
inline( null );
}
/** {@inheritDoc} */
public void inline( SinkEventAttributes attributes )
{
List<Integer> tags = new ArrayList<>();
if ( attributes != null )
{
if ( attributes.containsAttribute( SinkEventAttributes.SEMANTICS, "italic" ) )
{
tags.add( 0, this.style );
beginStyle( STYLE_ITALIC );
}
if ( attributes.containsAttribute( SinkEventAttributes.SEMANTICS, "bold" ) )
{
tags.add( 0, this.style );
beginStyle( STYLE_BOLD );
}
if ( attributes.containsAttribute( SinkEventAttributes.SEMANTICS, "code" ) )
{
tags.add( 0, this.style );
beginStyle( STYLE_TYPEWRITER );
}
}
inlineStack.push( tags );
}
/**
* {@inheritDoc}
*/
public void inline_()
{
for ( Integer style: inlineStack.pop() )
{
endStyle();
this.style = style;
}
}
/**
* {@inheritDoc}
*/
public void italic()
{
inline( SinkEventAttributeSet.Semantics.ITALIC );
}
/**
* {@inheritDoc}
*/
public void italic_()
{
inline_();
}
/**
* {@inheritDoc}
*/
public void bold()
{
inline( SinkEventAttributeSet.Semantics.BOLD );
}
/**
* {@inheritDoc}
*/
public void bold_()
{
inline_();
}
/**
* {@inheritDoc}
*/
public void monospaced()
{
inline( SinkEventAttributeSet.Semantics.CODE );
}
/**
* {@inheritDoc}
*/
public void monospaced_()
{
inline_();
}
private void beginStyle( int style )
{
this.style = style;
switch ( context.get() )
{
case CONTEXT_TABLE:
break;
default:
if ( paragraph != null )
{
switch ( style )
{
case STYLE_ITALIC:
writer.println( "{\\i" );
break;
case STYLE_BOLD:
writer.println( "{\\b" );
break;
case STYLE_TYPEWRITER:
writer.println( "{\\f1" );
break;
default:
writer.println( "{" );
break;
}
}
break;
}
}
private void endStyle()
{
style = STYLE_ROMAN;
switch ( context.get() )
{
case CONTEXT_TABLE:
break;
default:
if ( paragraph != null )
{
writer.println( "}" );
}
break;
}
}
/**
* {@inheritDoc}
*/
public void lineBreak()
{
switch ( context.get() )
{
case CONTEXT_TABLE:
cell.add( line );
line = new Line();
break;
default:
writer.println( "\\line" );
break;
}
}
/**
* {@inheritDoc}
*/
public void nonBreakingSpace()
{
switch ( context.get() )
{
case CONTEXT_TABLE:
line.add( new Item( style, " " ) );
break;
default:
writer.println( "\\~" );
break;
}
}
/** {@inheritDoc} */
public void text( String text )
{
switch ( context.get() )
{
case CONTEXT_VERBATIM:
verbatim.append( text );
break;
case CONTEXT_TABLE:
StringTokenizer t = new StringTokenizer( text, EOL, true );
while ( t.hasMoreTokens() )
{
String token = t.nextToken();
if ( token.equals( EOL ) )
{
cell.add( line );
line = new Line();
}
else
{
line.add( new Item( style, normalize( token ) ) );
}
}
break;
default:
if ( paragraph == null )
{
beginParagraph( new Paragraph() );
}
writer.println( escape( normalize( text ) ) );
}
}
/**
* {@inheritDoc}
*
* Unkown events just log a warning message but are ignored otherwise.
* @see org.apache.maven.doxia.sink.Sink#unknown(String,Object[],SinkEventAttributes)
*/
public void unknown( String name, Object[] requiredParams, SinkEventAttributes attributes )
{
String msg = "Unknown Sink event: '" + name + "', ignoring!";
logMessage( "unknownEvent", msg );
}
private static String normalize( String s )
{
int length = s.length();
StringBuilder buffer = new StringBuilder( length );
for ( int i = 0; i < length; ++i )
{
char c = s.charAt( i );
if ( Character.isWhitespace( c ) )
{
if ( buffer.length() == 0 || buffer.charAt( buffer.length() - 1 ) != ' ' )
{
buffer.append( ' ' );
}
}
else
{
buffer.append( c );
}
}
return buffer.toString();
}
private static String escape( String s )
{
int length = s.length();
StringBuilder buffer = new StringBuilder( length );
for ( int i = 0; i < length; ++i )
{
char c = s.charAt( i );
switch ( c )
{
case '\\':
buffer.append( "\\\\" );
break;
case '{':
buffer.append( "\\{" );
break;
case '}':
buffer.append( "\\}" );
break;
default:
buffer.append( c );
}
}
return buffer.toString();
}
/**
* <p>getFont.</p>
*
* @param style a int.
* @param size a int.
* @return a {@link org.apache.maven.doxia.module.rtf.Font} object.
*/
protected Font getFont( int style, int size )
{
Font font = null;
StringBuilder buf = new StringBuilder();
buf.append( style );
buf.append( size );
String key = buf.toString();
Object object = fontTable.get( key );
if ( object == null )
{
try
{
font = new Font( style, size );
fontTable.put( key, font );
}
catch ( Exception e )
{
if ( getLog().isDebugEnabled() )
{
getLog().debug( e.getMessage(), e );
}
}
}
else
{
font = (Font) object;
}
return font;
}
private static int textWidth( String text, Font font )
{
int width = 0;
StringTokenizer t = new StringTokenizer( text, EOL );
while ( t.hasMoreTokens() )
{
int w = font.textExtents( t.nextToken() ).width;
if ( w > width )
{
width = w;
}
}
return width;
}
/**
* {@inheritDoc}
*/
public void flush()
{
writer.flush();
}
/**
* {@inheritDoc}
*/
public void close()
{
writer.close();
if ( getLog().isWarnEnabled() && this.warnMessages != null )
{
for ( Map.Entry<String, Set<String>> entry : this.warnMessages.entrySet() )
{
Set<String> set = entry.getValue();
for ( String msg : set )
{
getLog().warn( msg );
}
}
this.warnMessages = null;
}
init();
}
/**
* If debug mode is enabled, log the <code>msg</code> as is, otherwise add unique msg in <code>warnMessages</code>.
*
* @param key not null
* @param msg not null
* @see #close()
* @since 1.1.1
*/
private void logMessage( String key, String msg )
{
msg = "[RTF Sink] " + msg;
if ( getLog().isDebugEnabled() )
{
getLog().debug( msg );
return;
}
if ( warnMessages == null )
{
warnMessages = new HashMap<>();
}
Set<String> set = warnMessages.get( key );
if ( set == null )
{
set = new TreeSet<>();
}
set.add( msg );
warnMessages.put( key, set );
}
/**
* {@inheritDoc}
*/
protected void init()
{
super.init();
this.fontTable.clear();
this.context = new Context();
this.paragraph = null;
this.indentation = new Indentation( 0 );
this.space = new Space( 20 * DEFAULT_SPACING );
Font font = getFont( STYLE_BOLD, fontSize );
if ( font != null )
{
this.listItemIndent = textWidth( LIST_ITEM_HEADER, font );
}
this.numbering.clear();
this.itemNumber.clear();
this.style = STYLE_ROMAN;
this.sectionLevel = 0;
this.emptyHeader = false;
this.verbatim = null;
this.frame = false;
this.table = null;
this.row = null;
this.cell = null;
this.line = null;
this.warnMessages = null;
}
// -----------------------------------------------------------------------
static class Counter
{
private int value;
Counter( int value )
{
set( value );
}
void set( int value )
{
this.value = value;
}
int get()
{
return value;
}
void increment()
{
increment( 1 );
}
void increment( int value )
{
this.value += value;
}
}
static class Context
{
private int context = CONTEXT_UNDEFINED;
private Vector<Integer> stack = new Vector<>();
void set( int context )
{
stack.addElement( this.context );
this.context = context;
}
void restore()
{
if ( !stack.isEmpty() )
{
context = stack.lastElement();
stack.removeElementAt( stack.size() - 1 );
}
}
int get()
{
return context;
}
}
class Paragraph
{
int style = 0;
int justification = Sink.JUSTIFY_LEFT;
int leftIndent = indentation.get();
int rightIndent = 0;
int firstLineIndent = 0;
int spaceBefore = space.getNext();
int spaceAfter = 0;
boolean frame = false;
int fontStyle = STYLE_ROMAN;
int fontSize = RtfSink.this.fontSize;
Paragraph()
{
// nop
}
Paragraph( int style, int size )
{
fontStyle = style;
fontSize = size;
}
void begin()
{
writer.print( "\\pard" );
if ( style > 0 )
{
writer.print( "\\s" + style );
}
switch ( justification )
{
case Sink.JUSTIFY_LEFT:
default:
break;
case Sink.JUSTIFY_CENTER:
writer.print( "\\qc" );
break;
case Sink.JUSTIFY_RIGHT:
writer.print( "\\qr" );
break;
}
if ( leftIndent != 0 )
{
writer.print( "\\li" + leftIndent );
}
if ( rightIndent != 0 )
{
writer.print( "\\ri" + rightIndent );
}
if ( firstLineIndent != 0 )
{
writer.print( "\\fi" + firstLineIndent );
}
if ( spaceBefore != 0 )
{
writer.print( "\\sb" + spaceBefore );
}
if ( spaceAfter != 0 )
{
writer.print( "\\sa" + spaceAfter );
}
if ( frame )
{
writer.print( "\\box\\brdrs\\brdrw" + BORDER_WIDTH );
}
writer.print( "\\plain" );
switch ( fontStyle )
{
case STYLE_ROMAN:
default:
writer.print( "\\f0" );
break;
case STYLE_ITALIC:
writer.print( "\\f0\\i" );
break;
case STYLE_BOLD:
writer.print( "\\f0\\b" );
break;
case STYLE_TYPEWRITER:
writer.print( "\\f1" );
break;
}
writer.println( "\\fs" + ( 2 * fontSize ) );
}
void end()
{
writer.println( "\\par" );
}
}
class Space
{
private int space;
private int next;
private Vector<Integer> stack = new Vector<>();
Space( int space /*twips*/ )
{
this.space = space;
next = space;
}
void set( int space /*twips*/ )
{
stack.addElement( this.space );
this.space = space;
next = space;
}
int get()
{
return space;
}
void restore()
{
if ( !stack.isEmpty() )
{
space = stack.lastElement();
stack.removeElementAt( stack.size() - 1 );
next = space;
}
}
void setNext( int space /*twips*/ )
{
next = space;
}
int getNext()
{
int nxt = this.next;
this.next = space;
return nxt;
}
void skip()
{
skip( getNext() );
}
void skip( int space /*twips*/ )
{
writer.print( "\\pard" );
if ( ( space -= 10 ) > 0 )
{
writer.print( "\\sb" + space );
}
writer.println( "\\plain\\fs1\\par" );
}
}
static class Indentation
{
private int indent;
private Vector<Integer> stack = new Vector<>();
Indentation( int indent /*twips*/ )
{
this.indent = indent;
}
void set( int indent /*twips*/ )
{
stack.addElement( this.indent );
this.indent = indent;
}
int get()
{
return indent;
}
void restore()
{
if ( !stack.isEmpty() )
{
indent = stack.lastElement();
stack.removeElementAt( stack.size() - 1 );
}
}
void add( int indent /*twips*/ )
{
set( this.indent + indent );
}
}
static class Table
{
int numColumns;
int[] columnWidths;
int[] justification;
boolean grid;
Vector<Row> rows;
Table( int[] justification, boolean grid )
{
numColumns = justification.length;
columnWidths = new int[numColumns];
this.justification = justification;
this.grid = grid;
rows = new Vector<>();
}
void add( Row row )
{
rows.addElement( row );
for ( int i = 0; i < numColumns; ++i )
{
if ( i >= row.cells.size() )
{
break;
}
Cell cell = row.cells.elementAt( i );
int width = cell.boundingBox().width;
if ( width > columnWidths[i] )
{
columnWidths[i] = width;
}
}
}
int width()
{
int width = 0;
for ( int i = 0; i < numColumns; ++i )
{
width += columnWidths[i];
}
if ( grid )
{
width += ( numColumns + 1 ) * BORDER_WIDTH;
}
return width;
}
}
static class Row
{
Vector<Cell> cells = new Vector<>();
void add( Cell cell )
{
cells.addElement( cell );
}
int height()
{
int height = 0;
int numCells = cells.size();
for ( int i = 0; i < numCells; ++i )
{
Cell cell = cells.elementAt( i );
Box box = cell.boundingBox();
if ( box.height > height )
{
height = box.height;
}
}
return height;
}
}
class Cell
{
Vector<Line> lines = new Vector<>();
void add( Line line )
{
lines.addElement( line );
}
Box boundingBox()
{
int width = 0;
int height = 0;
for ( int i = 0; i < lines.size(); ++i )
{
int w = 0;
int h = 0;
Line line = (Line) lines.elementAt( i );
for ( int j = 0; j < line.items.size(); ++j )
{
Item item = (Item) line.items.elementAt( j );
Font font = getFont( item.style, fontSize );
if ( font == null )
{
continue;
}
Font.TextExtents x = font.textExtents( item.text );
w += x.width;
if ( x.height > h )
{
h = x.height;
}
}
if ( w > width )
{
width = w;
}
height += h;
}
width += ( 2 * CELL_HORIZONTAL_PAD );
height += ( 2 * CELL_VERTICAL_PAD );
// allow one more pixel for grid outline
width += toTwips( 1., UNIT_PIXEL );
return new Box( width, height );
}
}
static class Line
{
Vector<Item> items = new Vector<>();
void add( Item item )
{
items.addElement( item );
}
}
static class Item
{
int style;
String text;
Item( int style, String text )
{
this.style = style;
this.text = text;
}
}
static class Box
{
int width;
int height;
Box( int width, int height )
{
this.width = width;
this.height = height;
}
}
}