<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> | |
<html> | |
<head> | |
<title>BIRT Extension Mechanism, Part 1: Custom Report Items</title> | |
<meta http-equiv="Content-Type" content="text/html; charset=windows-1252"> | |
<link href="../article.css" type="text/css" rel="stylesheet"> | |
</head> | |
<body> | |
<h1>BIRT Extension Mechanism, Part 1: Custom Report Items</h1> | |
<div class="summary"> | |
<h2>Abstract</h2> | |
<p>This article introduces the extension mechanism of BIRT report | |
model, engine and designer, and shows how to create custom custom report items | |
step-by-step.</p> | |
<div class="author">By Zhiqiang Qian, Actuate Corporation</div> | |
<div class="copyright">Copyright © 2008 Actuate Corporation.</div> | |
<div class="date">December 9, 2008</div> | |
</div> | |
<div class="content"> | |
<h2>Introduction</h2> | |
<p>For many people, <a href="http://eclipse.org/birt">BIRT (Business | |
Intelligence and Reporting Tools)</a> may be | |
only a reporting tool. But in fact, the real strength of | |
BIRT is not only its built-in reporting functionality, but also | |
its | |
extension capabilities. Using extensions, a user can easily extend the | |
functionality of BIRT, creating custom extended report items, adding custom | |
emitters, and even having custom editor pages. The power of extensions | |
enables the user to create | |
custom reporting features that can meet very specific requirements. In | |
this | |
article, we explore how to leverage the BIRT extension mechanism to | |
extend the BIRT capabilities. The provided samples | |
cover most of the essential extension points that provided by BIRT report | |
model, engine and designer.</p> | |
<p> | |
This article assumes the reader already has the knowledge about the Eclipse extension mechanism. | |
If you are not familiar with that, you can read this article first about the <a href="http://www.eclipse.org/articles/Article-Plug-in-architecture/plugin_architecture.html"> | |
Eclipse Plug-in Architecture</a> to get some initial concept. | |
</p> | |
<p> | |
This article also uses an Eclipse plug-in project to hold all the source code and demonstrate the extension mechanism. | |
You can either import the project directly from this <a href="extension-tutorial-1.zip">archive</a> or create your | |
own plug-in project through the Eclipse IDE. | |
</p> | |
<div class="figure"><img style="width: 500px; height: 496px;" alt="" src="images/create-project.png"></div> | |
<h2>The Basics</h2> | |
<p>BIRT already contains the basic Label and Text report items, | |
but these items can only show horizontal text. Sometimes, user | |
would | |
like to show angled text; in this case, alternative solution is needed. | |
In the following sections, we introduce how to implement a | |
RotatedText | |
extended report item through BIRT extensions, and how to enhance | |
the functionality step-by-step by implementing more extension points.</p> | |
<p>Note: This example is a modified version based | |
on the <code>RotatedText</code> sample on BIRT CVS, the original code | |
can be retrieved from <a href="http://dev.eclipse.org/viewcvs/index.cgi/?root=BIRT_Project">Eclipse CVS</a>.</p> | |
<p>To implement an extended report item, we need look at the following extension points:</p> | |
<ul> | |
<li>org.eclipse.birt.report.model.reportItemModel</li> | |
<li>org.eclipse.birt.report.designer.ui.reportitemUI</li> | |
<li>org.eclipse.birt.report.engine.reportitemPresentation</li> | |
</ul> | |
<br> | |
<div class="figure"><img style="width: 514px; height: 156px;" alt="" src="images/extensions.png"></div> | |
<p>These three extensions provide the basics to implement an extended | |
report item. In brief, | |
<code>org.eclipse.birt.report.model.reportItemModel</code> provides the report | |
model extensibility, <code>org.eclipse.birt.report.designer.ui.reportitemUI</code> | |
provides the report designer extensibility, and | |
<code>org.eclipse.birt.report.engine.reportitemPresentation</code> provides the | |
report engine extensibility.</p> | |
<h3>org.eclipse.birt.report.model.reportItemModel</h3> | |
<p>This extension point is provided by the BIRT Report Model. Normally the user | |
uses this extension point to define the extended report item model.</p> | |
<div class="figure"><img style="width: 1036px; height: 224px;" alt="" src="images/model-extension.png"></div> | |
<p>First we need define a new report item model | |
extension. To achieve this, we create a "reportItem" element under the extension and specify the <code>extensionName</code> property as "RotatedText". | |
The extension name is the identifier | |
for the extended item, it's also the only symbol that connects between the report model | |
and engine/designer extensions.</p> | |
<p>Then we define the model factory class as | |
<code>org.eclipse.birt.sample.reportitem.rotatedtext.RotatedTextItemFactory</code>. | |
This factory class will be used to create and initialize the | |
<code>IReportItem</code> instance, as well as providing optional localization support:</p> | |
<pre>public class RotatedTextItemFactory extends ReportItemFactory | |
{ | |
public IReportItem newReportItem( DesignElementHandle modelHandle ) | |
{ | |
if ( modelHandle instanceof ExtendedItemHandle && RotatedTextItem.EXTENSION_NAME.equals( ( (ExtendedItemHandle) modelHandle ).getExtensionName( ) ) ) | |
{ | |
return new RotatedTextItem( (ExtendedItemHandle) modelHandle ); | |
} | |
return null; | |
} | |
public IMessages getMessages( ) | |
{ | |
// TODO implement this to support localization | |
return null; | |
} | |
}</pre> | |
<p>From the "reportItem" properties page, we can see some other settings like "defaultStyle", "extendsFrom", etc. | |
They can be used to control more capabilities of the extended item. As we are not using them in this example, | |
we just omit them here.</p> | |
<p>Here we define two new properties for the <code>RotatedText</code> extended report item:</p> | |
<ul> | |
<li><em>rotationAngle</em>, the rotation angle, integer type, default as 0.</li> | |
<div class="figure"><br><img style="width: 1001px; height: 279px;" alt="" src="images/rotationangle-property.png"></div> | |
<li><em>text</em>, the text content, string type, default as "Rotated Text".</li> | |
<div class="figure"><img style="width: 1000px; height: 274px;" alt="" src="images/text-property.png"></div> | |
</ul> | |
<p>We can see in the properties page there are also some settings that we didn't touch. Normally they are for more complex property types and | |
are out of the scope of this article, so we just omit them here.</p> | |
<p> | |
Once we completed the model definition, we need give a <code>IReportItem</code> | |
implementation. This class is used to encapsulate the extended report | |
item model, and providing some convenient property accessing APIs:</p> | |
<pre>public class RotatedTextItem extends ReportItem | |
{ | |
public static final String EXTENSION_NAME = "RotatedText"; //$NON-NLS-1$ | |
public static final String TEXT_PROP = "text"; //$NON-NLS-1$ | |
public static final String ROTATION_ANGLE_PROP = "rotationAngle"; //$NON-NLS-1$ | |
private ExtendedItemHandle modelHandle; | |
RotatedTextItem( ExtendedItemHandle modelHandle ) | |
{ | |
this.modelHandle = modelHandle; | |
} | |
public String getText( ) | |
{ | |
return modelHandle.getStringProperty( TEXT_PROP ); | |
} | |
public int getRotationAngle( ) | |
{ | |
return modelHandle.getIntProperty( ROTATION_ANGLE_PROP ); | |
} | |
public void setText( String value ) throws SemanticException | |
{ | |
modelHandle.setProperty( TEXT_PROP, value ); | |
} | |
public void setRotationAngle( int value ) throws SemanticException | |
{ | |
modelHandle.setProperty( ROTATION_ANGLE_PROP, value ); | |
} | |
}</pre> | |
<p>We can see most of the methods here are just getter/setters. For | |
a simple extended report item, this is normally enough.</p> | |
<h3>org.eclipse.birt.report.designer.ui.reportitemUI</h3> | |
<p>This extension point is provided by BIRT Designer, the user uses this | |
extension point to define the UI behavior for extended report | |
items.</p> | |
<div class="figure"><img style="width: 1001px; height: 233px;" alt="" src="images/ui-extension.png"></div> | |
<p> | |
First we need create a "model" element under the extension. This is to bind the Designer extension with the Model extension. To do | |
this, we specify the <code>extensionName</code> property as "RotatedText", which exactly matches the model extension name.</p> | |
<p>The next step, we create some other elements like "palette", "editor" and "outline" under the extension, and specify the detailed settings for this extended report item as following:</p> | |
<ul> | |
<li><em>Palette</em> specifies the icon to be shown in Palette view, as well as the | |
palette category. If the category is not specified, the default "Report Items" category will be used.</li> | |
<div class="figure"><img style="width: 495px; height: 111px;" alt="" src="images/palette-detail.png"></div> | |
<li><em>Editor</em> specifies the report item visibility in different editor pages, and whether the resizing control is enabled.</li> | |
<div class="figure"><img style="width: 495px; height: 133px;" alt="" src="images/editor-detail.png"></div> | |
<li><em>Outline</em> specifies the icon to be shown in Outline view. Normally this is the same as the one in Palette view.</li> | |
<div class="figure"><img style="width: 497px; height: 90px;" alt="" src="images/icon-detail.png"></div> | |
</ul> | |
<p>The last step, we need specify the UI provider for this extended report item.</p> | |
<p>The UI provider defines how to display and interact with the | |
extended report item within the editor. BIRT designer support three types of | |
basic UI providers:</p> | |
<ul> | |
<li>The <em>Label UI Provider</em> implements the <code>IReportItemLabelProvider</code> interface, which simply manipulates and displays a text content.</li> | |
<li>The <em>Image UI Provider</em> implements the <code>IReportItemImageProvider</code> interface, which simply manipulates and displays an image content.</li> | |
<li>the <em>Figure UI Provider</em> implements the <code>IRportItemFigureProvider</code>, which uses the Figure interface from <a href="http://www.eclipse.org/gef/">GEF</a>, providing more flexibility and interactivity support.</li> | |
</ul> | |
<p>In this example, we choose to implement the simplest Label UI | |
Provider first, in subsequent examples, we will introduce other types of UI providers.</p> | |
<p>To register the Label UI provider, we create a "reportItemLabelUI" element under the extension, and specify the implementor class, which must implement <code>IReportItemLabelProvider</code> interface.</p> | |
<div class="figure"><img style="width: 498px; height: 94px;" alt="" src="images/labelui-detail.png"></div> | |
<p> | |
Here our Label UI Provider implementor class is <code>org.eclipse.birt.sample.reportitem.rotatedText.RotatedTextLabelUI</code>; | |
the code is very simple:</p> | |
<pre>public class RotatedTextLabelUI implements IReportItemLabelProvider | |
{ | |
public String getLabel( ExtendedItemHandle handle ) | |
{ | |
try | |
{ | |
IReportItem item = handle.getReportItem( ); | |
if ( item instanceof RotatedTextItem ) | |
{ | |
return ( (RotatedTextItem) item ).getText( ); | |
} | |
} | |
catch ( ExtendedElementException e ) | |
{ | |
e.printStackTrace( ); | |
} | |
return null; | |
} | |
}</pre> | |
<p>You can see that what it does is to read the text property value from the model and return it as a string.</p> | |
<p>After we completed the designer extension, we can run the designer and check what we have: | |
the Palette view now contains the new <code>RotatedText</code> extended report item.</p> | |
<div class="figure"><img style="width: 341px; height: 408px;" alt="" src="images/palette.png"> </div> | |
<p>If you right-click anywhere in the editor, you can see the <code>RotatedText</code> item is | |
also available in the "Insert" context menu.</p> | |
<div class="figure"><img style="width: 487px; height: 395px;" alt="" src="images/layout.png"></div> | |
<p>When you insert one RotatedText item into the layout, it appears in the Outline view.</p> | |
<div class="figure"><img style="width: 347px; height: 438px;" alt="" src="images/outline.png"></div> | |
<p>Select the RotatedText item, and open the <em>Properties</em> view. It immediately | |
lists all the properties that belong to this extended report item. Among | |
them, we can see the "Rotation Angle" and "Text | |
Content" as well as other inherited properties. The user can edit these | |
property values through this view. | |
</p> | |
<div class="figure"><img style="width: 728px; height: 346px;" alt="" src="images/properties.png"></div> | |
<p>Now that the designer extension is done, we'll continue to the Report Engine extension.</p> | |
<h3>org.eclipse.birt.report.engine.reportitemPresentation</h3> | |
<p>This extension point is provided by BIRT Report Engine, the user uses | |
this extension point to define the presentation behavior of the | |
extended report item.</p> | |
<div class="figure"><img style="width: 1003px; height: 166px;" alt="" src="images/engine-extension.png"></div> | |
<p>First we bind the engine extension with the model extension. To achieve this, we create a "reportItem" element under the engine extension and | |
specify the <code>name</code> property as "RotatedText", also we specify the presentation implementor class | |
as <code>org.eclipse.birt.sample.reportitem.rotatedtext.RotatedTextPresentationImpl</code>. | |
The presentation implementor class must implement the | |
<code>IReportItemPresentation</code> interface.</p> | |
<p>Our plan for implementing the rotated text effect is to create an image | |
dynamically, in this image, we use <em>Swing</em> graphics API to render the | |
rotated text.</p> | |
<p>Here is the code that we need:</p> | |
<pre>public class RotatedTextPresentationImpl extends ReportItemPresentationBase | |
{ | |
private RotatedTextItem textItem; | |
public void setModelObject( ExtendedItemHandle modelHandle ) | |
{ | |
try | |
{ | |
textItem = (RotatedTextItem) modelHandle.getReportItem( ); | |
} | |
catch ( ExtendedElementException e ) | |
{ | |
e.printStackTrace( ); | |
} | |
} | |
public int getOutputType( ) | |
{ | |
return OUTPUT_AS_IMAGE; | |
} | |
public Object onRowSets( IRowSet[] rowSets ) throws BirtException<br> | |
{ | |
if ( textItem == null ) | |
{ | |
return null; | |
} | |
int angle = textItem.getRotationAngle( ); | |
String text = textItem.getText( ); | |
BufferedImage rotatedImage = SwingGraphicsUtil.createRotatedTextImage( text, angle, new Font( "Default", 0, 12 ) ); //$NON-NLS-1$ | |
ByteArrayInputStream bis = null; | |
try | |
{ | |
ImageIO.setUseCache( false ); | |
ByteArrayOutputStream baos = new ByteArrayOutputStream( ); | |
ImageOutputStream ios = ImageIO.createImageOutputStream( baos ); | |
ImageIO.write( rotatedImage, "png", ios ); //$NON-NLS-1$ | |
ios.flush( ); | |
ios.close( ); | |
bis = new ByteArrayInputStream( baos.toByteArray( ) ); | |
} | |
catch ( IOException e ) | |
{ | |
e.printStackTrace( ); | |
} | |
return bis; | |
} | |
}</pre> | |
<p>This implementation | |
generate an <code>Image</code> dynamically in the <code>onRowSets()</code> method. To return | |
the image content, we create an in-memory stream and put the image content into it.</p> | |
<p>Here is the utility class that implements the key rotation rendering algorithm:</p> | |
<pre>public class SwingGraphicsUtil | |
{ | |
public static BufferedImage createRotatedTextImage( String text, int angle, Font ft ) | |
{ | |
Graphics2D g2d = null; | |
try | |
{ | |
if ( text == null || text.trim( ).length( ) == 0 ) | |
{ | |
return null; | |
} | |
BufferedImage stringImage = new BufferedImage( 1, 1, BufferedImage.TYPE_INT_ARGB ); | |
g2d = (Graphics2D) stringImage.getGraphics( ); | |
g2d.setFont( ft ); | |
FontMetrics fm = g2d.getFontMetrics( ); | |
Rectangle2D bounds = fm.getStringBounds( text, g2d ); | |
TextLayout tl = new TextLayout( text, ft, g2d.getFontRenderContext( ) ); | |
g2d.dispose( ); | |
g2d = null; | |
return createRotatedImage( tl, (int) bounds.getWidth( ), (int) bounds.getHeight( ), angle ); | |
} | |
catch ( Exception e ) | |
{ | |
e.printStackTrace( ); | |
if ( g2d != null ) | |
{ | |
g2d.dispose( ); | |
} | |
} | |
return null; | |
} | |
private static BufferedImage createRotatedImage( Object src, int width, int height, int angle ) | |
{ | |
angle = angle % 360; | |
if ( angle < 0 ) | |
{ | |
angle += 360; | |
} | |
if ( angle == 0 ) | |
{ | |
return renderRotatedObject( src, 0, width, height, 0, 0 ); | |
} | |
else if ( angle == 90 ) | |
{ | |
return renderRotatedObject( src, -Math.PI / 2, height, width, -width, 0 ); | |
} | |
else if ( angle == 180 ) | |
{ | |
return renderRotatedObject( src, Math.PI, width, height, -width, -height ); | |
} | |
else if ( angle == 270 ) | |
{ | |
return renderRotatedObject( src, Math.PI / 2, height, width, 0, -height ); | |
} | |
else if ( angle > 0 && angle < 90 ) | |
{ | |
double angleInRadians = ( ( -angle * Math.PI ) / 180.0 ); | |
double cosTheta = Math.abs( Math.cos( angleInRadians ) ); | |
double sineTheta = Math.abs( Math.sin( angleInRadians ) ); | |
int dW = (int) ( width * cosTheta + height * sineTheta ); | |
int dH = (int) ( width * sineTheta + height * cosTheta ); | |
return renderRotatedObject( src, angleInRadians, dW, dH, -width * sineTheta * sineTheta, width * sineTheta * cosTheta ); | |
} | |
else if ( angle > 90 && angle < 180 ) | |
{ | |
double angleInRadians = ( ( -angle * Math.PI ) / 180.0 ); | |
double cosTheta = Math.abs( Math.cos( angleInRadians ) ); | |
double sineTheta = Math.abs( Math.sin( angleInRadians ) ); | |
int dW = (int) ( width * cosTheta + height * sineTheta ); | |
int dH = (int) ( width * sineTheta + height * cosTheta ); | |
return renderRotatedObject( src, angleInRadians, dW, dH, -( width + height * sineTheta * cosTheta ), -height / 2 ); | |
} | |
else if ( angle > 180 && angle < 270 ) | |
{ | |
double angleInRadians = ( ( -angle * Math.PI ) / 180.0 ); | |
double cosTheta = Math.abs( Math.cos( angleInRadians ) ); | |
double sineTheta = Math.abs( Math.sin( angleInRadians ) ); | |
int dW = (int) ( width * cosTheta + height * sineTheta ); | |
int dH = (int) ( width * sineTheta + height * cosTheta ); | |
return renderRotatedObject( src, angleInRadians, dW, dH, -( width * cosTheta * cosTheta ), -( height + width * cosTheta * sineTheta ) ); | |
} | |
else if ( angle > 270 && angle < 360 ) | |
{ | |
double angleInRadians = ( ( -angle * Math.PI ) / 180.0 ); | |
double cosTheta = Math.abs( Math.cos( angleInRadians ) ); | |
double sineTheta = Math.abs( Math.sin( angleInRadians ) ); | |
int dW = (int) ( width * cosTheta + height * sineTheta ); | |
int dH = (int) ( width * sineTheta + height * cosTheta ); | |
return renderRotatedObject( src, angleInRadians, dW, dH, ( height * cosTheta * sineTheta ), -( height * sineTheta * sineTheta ) ); | |
} | |
return renderRotatedObject( src, 0, width, height, 0, 0 ); | |
} | |
private static BufferedImage renderRotatedObject( Object src, double angle, int width, int height, double tx, double ty ) | |
{ | |
BufferedImage dest = new BufferedImage( width, height, BufferedImage.TYPE_INT_ARGB ); | |
Graphics2D g2d = (Graphics2D) dest.getGraphics( ); | |
g2d.setColor( Color.black ); | |
g2d.setRenderingHint( RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON ); | |
g2d.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON ); | |
AffineTransform at = AffineTransform.getRotateInstance( angle ); | |
at.translate( tx, ty ); | |
g2d.setTransform( at ); | |
if ( src instanceof TextLayout ) | |
{ | |
TextLayout tl = (TextLayout) src; | |
tl.draw( g2d, 0, tl.getAscent( ) ); | |
} | |
else if ( src instanceof Image ) | |
{ | |
g2d.drawImage( (Image) src, 0, 0, null ); | |
} | |
g2d.dispose( ); | |
return dest; | |
} | |
}</pre> | |
<h2>The Result</h2> | |
<p>Create a report and insert some RotatedText items first:</p> | |
<div class="figure"><img style="width: 865px; height: 273px;" alt="" src="images/design-layout.png"></div> | |
<p>Here we have inserted quite a few RotatedText items that with different angle settings.</p> | |
<p>When we preview the report as HTML, it looks like this:</p> | |
<div class="figure"><img style="width: 703px; height: 218px;" alt="" src="images/html-output.png"></div> | |
<p>Preview the report as PDF, it results like this:</p> | |
<div class="figure"><img style="width: 757px; height: 482px;" alt="" src="images/pdf-output.png"></div> | |
<p>Done! We've successfully implemented a <code>RotatedText</code> extended | |
report item for BIRT, and integrated it with Designer and Report Engine.</p> | |
</div> | |
<h2>Summary</h2> | |
<p>BIRT provides very good extension mechanism, which allows user | |
to create custom reporting features. This article introduced how to write | |
a custom RotatedText extended report item for BIRT.</p> | |
<h2>Resources</h2> | |
<p>[1] <a href="extension-tutorial-1.zip">The complete source code</a>: you can extract and import it into Eclipse workspace as a plug-in project directly</p> | |
<p>[2] BIRT Official Site: <a href="http://eclipse.org/birt">http://eclipse.org/birt</a></p> | |
<p>[3] How to access Eclipse CVS: <a href="http://wiki.eclipse.org/CVS_Howto">http://wiki.eclipse.org/CVS_Howto</a></p> | |
</div> | |
</body> | |
</html> |