net.sf.jasperreports.engine.export.HtmlExporter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jasperreports Show documentation
Show all versions of jasperreports Show documentation
Free Java Reporting Library
/*
* JasperReports - Free Java Reporting Library.
* Copyright (C) 2001 - 2019 TIBCO Software Inc. All rights reserved.
* http://www.jaspersoft.com
*
* Unless you have purchased a commercial license agreement from Jaspersoft,
* the following license terms apply:
*
* This program is part of JasperReports.
*
* JasperReports is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* JasperReports is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with JasperReports. If not, see .
*/
package net.sf.jasperreports.engine.export;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.font.TextAttribute;
import java.awt.geom.Dimension2D;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.Writer;
import java.text.AttributedCharacterIterator;
import java.text.AttributedCharacterIterator.Attribute;
import java.text.AttributedString;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Locale;
import java.util.Map;
import java.util.SortedSet;
import java.util.StringTokenizer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import net.sf.jasperreports.annotations.properties.Property;
import net.sf.jasperreports.annotations.properties.PropertyScope;
import net.sf.jasperreports.components.headertoolbar.HeaderToolbarElement;
import net.sf.jasperreports.crosstabs.interactive.CrosstabInteractiveJsonHandler;
import net.sf.jasperreports.engine.DefaultJasperReportsContext;
import net.sf.jasperreports.engine.JRAnchor;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JRGenericElementType;
import net.sf.jasperreports.engine.JRGenericPrintElement;
import net.sf.jasperreports.engine.JRLineBox;
import net.sf.jasperreports.engine.JRPen;
import net.sf.jasperreports.engine.JRPrintElement;
import net.sf.jasperreports.engine.JRPrintElementIndex;
import net.sf.jasperreports.engine.JRPrintEllipse;
import net.sf.jasperreports.engine.JRPrintFrame;
import net.sf.jasperreports.engine.JRPrintGraphicElement;
import net.sf.jasperreports.engine.JRPrintHyperlink;
import net.sf.jasperreports.engine.JRPrintHyperlinkParameter;
import net.sf.jasperreports.engine.JRPrintImage;
import net.sf.jasperreports.engine.JRPrintImageArea;
import net.sf.jasperreports.engine.JRPrintImageAreaHyperlink;
import net.sf.jasperreports.engine.JRPrintLine;
import net.sf.jasperreports.engine.JRPrintPage;
import net.sf.jasperreports.engine.JRPrintRectangle;
import net.sf.jasperreports.engine.JRPrintText;
import net.sf.jasperreports.engine.JRPropertiesUtil;
import net.sf.jasperreports.engine.JRRuntimeException;
import net.sf.jasperreports.engine.JasperReportsContext;
import net.sf.jasperreports.engine.PrintElementId;
import net.sf.jasperreports.engine.PrintElementVisitor;
import net.sf.jasperreports.engine.PrintPageFormat;
import net.sf.jasperreports.engine.ReportContext;
import net.sf.jasperreports.engine.export.tabulator.Cell;
import net.sf.jasperreports.engine.export.tabulator.CellVisitor;
import net.sf.jasperreports.engine.export.tabulator.Column;
import net.sf.jasperreports.engine.export.tabulator.ElementCell;
import net.sf.jasperreports.engine.export.tabulator.FrameCell;
import net.sf.jasperreports.engine.export.tabulator.LayeredCell;
import net.sf.jasperreports.engine.export.tabulator.Row;
import net.sf.jasperreports.engine.export.tabulator.SplitCell;
import net.sf.jasperreports.engine.export.tabulator.Table;
import net.sf.jasperreports.engine.export.tabulator.TableCell;
import net.sf.jasperreports.engine.export.tabulator.TablePosition;
import net.sf.jasperreports.engine.export.tabulator.Tabulator;
import net.sf.jasperreports.engine.type.HorizontalImageAlignEnum;
import net.sf.jasperreports.engine.type.HorizontalTextAlignEnum;
import net.sf.jasperreports.engine.type.HyperlinkTypeEnum;
import net.sf.jasperreports.engine.type.LineDirectionEnum;
import net.sf.jasperreports.engine.type.LineSpacingEnum;
import net.sf.jasperreports.engine.type.LineStyleEnum;
import net.sf.jasperreports.engine.type.ModeEnum;
import net.sf.jasperreports.engine.type.RotationEnum;
import net.sf.jasperreports.engine.type.RunDirectionEnum;
import net.sf.jasperreports.engine.type.ScaleImageEnum;
import net.sf.jasperreports.engine.type.VerticalImageAlignEnum;
import net.sf.jasperreports.engine.util.HyperlinkData;
import net.sf.jasperreports.engine.util.ImageUtil;
import net.sf.jasperreports.engine.util.JRCloneUtils;
import net.sf.jasperreports.engine.util.JRColorUtil;
import net.sf.jasperreports.engine.util.JRStringUtil;
import net.sf.jasperreports.engine.util.JRStyledText;
import net.sf.jasperreports.engine.util.JRTextAttribute;
import net.sf.jasperreports.engine.util.JRTypeSniffer;
import net.sf.jasperreports.engine.util.Pair;
import net.sf.jasperreports.export.ExportInterruptedException;
import net.sf.jasperreports.export.ExporterInputItem;
import net.sf.jasperreports.export.HtmlExporterConfiguration;
import net.sf.jasperreports.export.HtmlReportConfiguration;
import net.sf.jasperreports.export.type.HtmlBorderCollapseEnum;
import net.sf.jasperreports.properties.PropertyConstants;
import net.sf.jasperreports.renderers.AreaHyperlinksRenderable;
import net.sf.jasperreports.renderers.DataRenderable;
import net.sf.jasperreports.renderers.DimensionRenderable;
import net.sf.jasperreports.renderers.Renderable;
import net.sf.jasperreports.renderers.RenderersCache;
import net.sf.jasperreports.renderers.ResourceRenderer;
import net.sf.jasperreports.renderers.util.RendererUtil;
import net.sf.jasperreports.renderers.util.SvgDataSniffer;
import net.sf.jasperreports.renderers.util.SvgFontProcessor;
import net.sf.jasperreports.search.HitTermInfo;
import net.sf.jasperreports.search.SpansInfo;
import net.sf.jasperreports.util.Base64Util;
/**
* @author Lucian Chirita ([email protected])
*/
public class HtmlExporter extends AbstractHtmlExporter
{
private static final Log log = LogFactory.getLog(HtmlExporter.class);
private static final String EXCEPTION_MESSAGE_KEY_INTERNAL_ERROR = "export.html.internal.error";
private static final String EXCEPTION_MESSAGE_KEY_UNEXPECTED_ROTATION_VALUE = "export.html.unexpected.rotation.value";
/**
* The exporter key, as used in
* {@link GenericElementHandlerEnviroment#getElementHandler(JRGenericElementType, String)}.
*/
public static final String HTML_EXPORTER_KEY = JRPropertiesUtil.PROPERTY_PREFIX + "html";
/**
*
*/
public static final String HTML_EXPORTER_PROPERTIES_PREFIX = JRPropertiesUtil.PROPERTY_PREFIX + "export.html.";
/**
* Property that provides the value for the class
CSS style property to be applied
* to elements in the table generated for the report. The value of this property
* will be used as the value for the class
attribute of the <td>
tag for the element when exported to HTML and/or
* the class
attribute of the <span>
or <div>
tag for the element, when exported to XHTML/CSS.
*/
@Property(
category = PropertyConstants.CATEGORY_EXPORT,
scopes = {PropertyScope.ELEMENT},
sinceVersion = PropertyConstants.VERSION_4_0_1
)
public static final String PROPERTY_HTML_CLASS = HTML_EXPORTER_PROPERTIES_PREFIX + "class";
/**
*
*/
@Property(
category = PropertyConstants.CATEGORY_EXPORT,
scopes = {PropertyScope.ELEMENT},
sinceVersion = PropertyConstants.VERSION_3_7_0
)
public static final String PROPERTY_HTML_ID = HTML_EXPORTER_PROPERTIES_PREFIX + "id";
protected JRHyperlinkTargetProducerFactory targetProducerFactory;
protected Map rendererToImagePathMap;
protected Map,String> imageMaps;
protected RenderersCache renderersCache;
protected Writer writer;
protected int reportIndex;
protected int pageIndex;
protected LinkedList backcolorStack = new LinkedList();
protected ExporterFilter tableFilter;
protected int pointerEventsNoneStack = 0;
private List hyperlinksData = new ArrayList();
private boolean defaultIndentFirstLine;
private boolean defaultJustifyLastLine;
public HtmlExporter()
{
this(DefaultJasperReportsContext.getInstance());
}
public HtmlExporter(JasperReportsContext jasperReportsContext)
{
super(jasperReportsContext);
exporterContext = new ExporterContext();
}
@Override
public String getExporterKey()
{
return HTML_EXPORTER_KEY;
}
@Override
public String getExporterPropertiesPrefix()
{
return HTML_EXPORTER_PROPERTIES_PREFIX;
}
@Override
public void exportReport() throws JRException
{
/* */
ensureJasperReportsContext();
ensureInput();
rendererToImagePathMap = new HashMap();
imageMaps = new HashMap,String>();
renderersCache = new RenderersCache(getJasperReportsContext());
fontsToProcess = new HashMap();
//FIXMENOW check all exporter properties that are supposed to work at report level
initExport();
ensureOutput();
writer = getExporterOutput().getWriter();
try
{
exportReportToWriter();
}
catch (IOException e)
{
throw
new JRException(
EXCEPTION_MESSAGE_KEY_OUTPUT_WRITER_ERROR,
new Object[]{jasperPrint.getName()},
e);
}
finally
{
getExporterOutput().close();
resetExportContext();
}
}
@Override
protected Class getConfigurationInterface()
{
return HtmlExporterConfiguration.class;
}
@Override
protected Class getItemConfigurationInterface()
{
return HtmlReportConfiguration.class;
}
@Override
@SuppressWarnings("deprecation")
protected void ensureOutput()
{
if (exporterOutput == null)
{
exporterOutput =
new net.sf.jasperreports.export.parameters.ParametersHtmlExporterOutput(
getJasperReportsContext(),
getParameters(),
getCurrentJasperPrint()
);
}
}
@Override
protected void initExport()
{
super.initExport();
}
@Override
protected void initReport()
{
super.initReport();
HtmlReportConfiguration configuration = getCurrentItemConfiguration();
if (configuration.isRemoveEmptySpaceBetweenRows())
{
log.info("Removing empty space between rows not supported");
}
// this is the filter used to create the table, taking in consideration unhandled generic elements
tableFilter = new GenericElementsFilterDecorator(jasperReportsContext, HTML_EXPORTER_KEY, filter);
defaultIndentFirstLine = propertiesUtil.getBooleanProperty(jasperPrint, JRPrintText.PROPERTY_AWT_INDENT_FIRST_LINE, true);
defaultJustifyLastLine = propertiesUtil.getBooleanProperty(jasperPrint, JRPrintText.PROPERTY_AWT_JUSTIFY_LAST_LINE, false);
}
@Override
protected void setJasperReportsContext(JasperReportsContext jasperReportsContext)
{
super.setJasperReportsContext(jasperReportsContext);
targetProducerFactory = new DefaultHyperlinkTargetProducerFactory(jasperReportsContext);
}
protected void exportReportToWriter() throws JRException, IOException
{
HtmlExporterConfiguration configuration = getCurrentConfiguration();
String htmlHeader = configuration.getHtmlHeader();
String betweenPagesHtml = configuration.getBetweenPagesHtml();
String htmlFooter = configuration.getHtmlFooter();
boolean flushOutput = configuration.isFlushOutput();//FIXMEEXPORT maybe move flush flag to output
if (htmlHeader == null)
{
writer.write("\n");
writer.write("\n");
writer.write("\n");
writer.write(" \n");
writer.write(" \n");
writer.write(" \n");
writer.write("\n");
writer.write("\n");
writer.write(" elements) throws IOException
{
Tabulator tabulator = new Tabulator(tableFilter, elements);
tabulator.tabulate();
Table table = tabulator.getTable();
CellElementVisitor elementVisitor = new CellElementVisitor();
TableVisitor tableVisitor = new TableVisitor(tabulator, elementVisitor);
exportTable(tableVisitor, table, false, false);
}
protected void exportTable(TableVisitor tableVisitor, Table table, boolean whiteBackground, boolean isMainReportTable) throws IOException
{
SortedSet columns = table.getColumns().getUserEntries();
SortedSet rows = table.getRows().getUserEntries();
if (columns.isEmpty() || rows.isEmpty())
{
// TODO lucianc empty page
return;
}
if (isMainReportTable)
{
int totalWidth = columns.last().getEndCoord() - columns.first().getStartCoord();
writer.write("\n");
// TODO lucianc check whether we can use the first row for setting col widths
writer.write("\n");
for (Column col : columns)
{
writer.write(" \n");
}
writer.write(" \n");
for (Row row : rows)
{
writer.write("\n");
int emptySpan = 0;
for (Column col : columns)
{
Cell cell = row.getCell(col);
if (cell == null)
{
++emptySpan;
}
else
{
if (emptySpan > 0)
{
writeEmptyCell(emptySpan, 1);
}
emptySpan = 0;
TablePosition position = new TablePosition(table, col, row);
cell.accept(tableVisitor, position);
}
}
if (emptySpan > 0)
{
writeEmptyCell(emptySpan, 1);
}
writer.write(" \n");
}
writer.write("
\n");
}
protected void writeText(JRPrintText text, TableCell cell)
throws IOException
{
JRStyledText styledText = getStyledText(text);
int textLength = styledText == null ? 0 : styledText.length();
startCell(text, cell);
if (text.getRunDirectionValue() == RunDirectionEnum.RTL)
{
writer.write(" dir=\"rtl\"");
}
StringBuilder styleBuffer = new StringBuilder();
String verticalAlignment = HTML_VERTICAL_ALIGN_TOP;
switch (text.getVerticalTextAlign())
{
case BOTTOM :
{
verticalAlignment = HTML_VERTICAL_ALIGN_BOTTOM;
break;
}
case MIDDLE :
{
verticalAlignment = HTML_VERTICAL_ALIGN_MIDDLE;
break;
}
case TOP :
case JUSTIFIED :
default :
{
verticalAlignment = HTML_VERTICAL_ALIGN_TOP;
}
}
appendElementCellGenericStyle(cell, styleBuffer);
appendBackcolorStyle(cell, styleBuffer);
appendBorderStyle(cell.getBox(), styleBuffer);
appendPaddingStyle(text.getLineBox(), styleBuffer);
String horizontalAlignment = CSS_TEXT_ALIGN_LEFT;
if (textLength > 0)
{
switch (text.getHorizontalTextAlign())
{
case RIGHT :
{
horizontalAlignment = CSS_TEXT_ALIGN_RIGHT;
break;
}
case CENTER :
{
horizontalAlignment = CSS_TEXT_ALIGN_CENTER;
break;
}
case JUSTIFIED :
{
horizontalAlignment = CSS_TEXT_ALIGN_JUSTIFY;
break;
}
case LEFT :
default :
{
horizontalAlignment = CSS_TEXT_ALIGN_LEFT;
}
}
}
if (getCurrentItemConfiguration().isWrapBreakWord())
{
styleBuffer.append("width: " + toSizeUnit(text.getWidth()) + "; ");
styleBuffer.append("word-wrap: break-word; ");
}
if (text.getLineBreakOffsets() != null)
{
//if we have line breaks saved in the text, set nowrap so that
//the text only wraps at the explicit positions
styleBuffer.append("white-space: nowrap; ");
}
styleBuffer.append("text-indent: " + text.getParagraph().getFirstLineIndent() + "px; ");
String rotationValue = null;
StringBuilder spanStyleBuffer = new StringBuilder();
StringBuilder divStyleBuffer = new StringBuilder();
if (text.getRotationValue() == RotationEnum.NONE)
{
if (!verticalAlignment.equals(HTML_VERTICAL_ALIGN_TOP))
{
styleBuffer.append(" vertical-align: ");
styleBuffer.append(verticalAlignment);
styleBuffer.append(";");
}
//writing text align every time even when it's left
//because IE8 with transitional defaults to center
styleBuffer.append("text-align: ");
styleBuffer.append(horizontalAlignment);
styleBuffer.append(";");
}
else
{
rotationValue = setRotationStyles(text, horizontalAlignment,
spanStyleBuffer, divStyleBuffer);
}
writeStyle(styleBuffer);
finishStartCell();
if (text.getAnchorName() != null)
{
writer.write("");
}
if (text.getBookmarkLevel() != JRAnchor.NO_BOOKMARK)
{
writer.write("");
}
if (rotationValue != null)
{
writer.write("\n");
writer.write("");
writer.write("");
}
boolean hyperlinkStarted = startHyperlink(text);
if (textLength > 0)
{
//only use text tooltip when no hyperlink present
// String textTooltip = hyperlinkStarted ? null : text.getHyperlinkTooltip();
exportStyledText(text, styledText, text.getHyperlinkTooltip(), hyperlinkStarted);
}
if (hyperlinkStarted)
{
endHyperlink();
}
if (rotationValue != null)
{
writer.write("");
}
endCell();
}
protected String setRotationStyles(JRPrintText text, String horizontalAlignment,
StringBuilder spanStyleBuffer, StringBuilder divStyleBuffer)
{
String rotationValue;
int textWidth = text.getWidth() - text.getLineBox().getLeftPadding() - text.getLineBox().getRightPadding();
int textHeight = text.getHeight() - text.getLineBox().getTopPadding() - text.getLineBox().getBottomPadding();
int rotatedWidth;
int rotatedHeight;
int rotationIE;
int rotationAngle;
int translateX;
int translateY;
switch (text.getRotationValue())
{
case LEFT :
{
translateX = - (textHeight - textWidth) / 2;
translateY = (textHeight - textWidth) / 2;
rotatedWidth = textHeight;
rotatedHeight = textWidth;
rotationIE = 3;
rotationAngle = -90;
rotationValue = "left";
break;
}
case RIGHT :
{
translateX = - (textHeight - textWidth) / 2;
translateY = (textHeight - textWidth) / 2;
rotatedWidth = textHeight;
rotatedHeight = textWidth;
rotationIE = 1;
rotationAngle = 90;
rotationValue = "right";
break;
}
case UPSIDE_DOWN :
{
translateX = 0;
translateY = 0;
rotatedWidth = textWidth;
rotatedHeight = textHeight;
rotationIE = 2;
rotationAngle = 180;
rotationValue = "upsideDown";
break;
}
case NONE :
default :
{
throw
new JRRuntimeException(
EXCEPTION_MESSAGE_KEY_UNEXPECTED_ROTATION_VALUE,
new Object[]{text.getRotationValue()}
);
}
}
appendSizeStyle(textWidth, textHeight, divStyleBuffer);
appendSizeStyle(rotatedWidth, rotatedHeight, spanStyleBuffer);
spanStyleBuffer.append("text-align: ");
spanStyleBuffer.append(horizontalAlignment);
spanStyleBuffer.append(";");
spanStyleBuffer.append("-webkit-transform: translate(" + translateX + "px," + translateY + "px) ");
spanStyleBuffer.append("rotate(" + rotationAngle + "deg); ");
spanStyleBuffer.append("-moz-transform: translate(" + translateX + "px," + translateY + "px) ");
spanStyleBuffer.append("rotate(" + rotationAngle + "deg); ");
spanStyleBuffer.append("-ms-transform: translate(" + translateX + "px," + translateY + "px) ");
spanStyleBuffer.append("rotate(" + rotationAngle + "deg); ");
spanStyleBuffer.append("-o-transform: translate(" + translateX + "px," + translateY + "px) ");
spanStyleBuffer.append("rotate(" + rotationAngle + "deg); ");
spanStyleBuffer.append("filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=" + rotationIE + "); ");
return rotationValue;
}
protected void appendSizeStyle(int width, int height, StringBuilder styleBuffer)
{
styleBuffer.append("width:");
styleBuffer.append(toSizeUnit(width));
styleBuffer.append(";");
styleBuffer.append("height:");
styleBuffer.append(toSizeUnit(height));
styleBuffer.append(";");
}
protected void writeImage(JRPrintImage image, TableCell cell)
throws IOException, JRException
{
startCell(image, cell);
int availableImageWidth = image.getWidth() - image.getLineBox().getLeftPadding() - image.getLineBox().getRightPadding();
if (availableImageWidth < 0)
{
availableImageWidth = 0;
}
int availableImageHeight = image.getHeight() - image.getLineBox().getTopPadding() - image.getLineBox().getBottomPadding();
if (availableImageHeight < 0)
{
availableImageHeight = 0;
}
String horizontalAlignment = getImageHorizontalAlignmentStyle(image);
String verticalAlignment = getImageVerticalAlignmentStyle(image);
StringBuilder styleBuffer = new StringBuilder();
ScaleImageEnum scaleImage = image.getScaleImageValue();
if (scaleImage != ScaleImageEnum.CLIP)
{
// clipped images are absolutely positioned within a div
if (!horizontalAlignment.equals(CSS_TEXT_ALIGN_LEFT))
{
styleBuffer.append("text-align: ");
styleBuffer.append(horizontalAlignment);
styleBuffer.append(";");
}
if (!verticalAlignment.equals(HTML_VERTICAL_ALIGN_TOP))
{
styleBuffer.append(" vertical-align: ");
styleBuffer.append(verticalAlignment);
styleBuffer.append(";");
}
}
RotationEnum rotation = image.getRotation();
Renderable renderer = image.getRenderer();
boolean isLazy = RendererUtil.isLazy(renderer);
if (
isLazy
|| (scaleImage == ScaleImageEnum.CLIP && availableImageHeight > 0)
|| rotation != RotationEnum.NONE
)
{
// some browsers need td height so that height: 100% works on the div used for clipped images.
// we're using the height without paddings because that's closest to the HTML size model.
styleBuffer.append("height: ");
styleBuffer.append(toSizeUnit(availableImageHeight));
styleBuffer.append("; ");
}
appendElementCellGenericStyle(cell, styleBuffer);
appendBackcolorStyle(cell, styleBuffer);
boolean addedToStyle = appendBorderStyle(cell.getBox(), styleBuffer);
if (!addedToStyle)
{
appendPen(
styleBuffer,
image.getLinePen(),
null
);
}
appendPaddingStyle(image.getLineBox(), styleBuffer);
writeStyle(styleBuffer);
finishStartCell();
if (image.getAnchorName() != null)
{
writer.write("");
}
if (image.getBookmarkLevel() != JRAnchor.NO_BOOKMARK)
{
writer.write("");
}
if (renderer != null)
{
boolean useBackgroundImage =
((isLazy
&& ((scaleImage == ScaleImageEnum.RETAIN_SHAPE || scaleImage == ScaleImageEnum.REAL_HEIGHT || scaleImage == ScaleImageEnum.REAL_SIZE)
|| !(image.getHorizontalImageAlign() == HorizontalImageAlignEnum.LEFT && image.getVerticalImageAlign() == VerticalImageAlignEnum.TOP)))
|| rotation != RotationEnum.NONE)
&& isUseBackgroundImageToAlign(image);
boolean useDiv = (scaleImage == ScaleImageEnum.CLIP || useBackgroundImage);
if (useDiv)
{
writer.write("\n");
}
boolean hasAreaHyperlinks =
renderer instanceof AreaHyperlinksRenderable
&& ((AreaHyperlinksRenderable)renderer).hasImageAreaHyperlinks();
boolean hasHyperlinks = false;
boolean hyperlinkStarted;
if (hasAreaHyperlinks)
{
hyperlinkStarted = false;
hasHyperlinks = true;
}
else
{
hyperlinkStarted = startHyperlink(image);
hasHyperlinks = hyperlinkStarted;
}
String imageMapName = null;
List imageMapAreas = null;
if (hasAreaHyperlinks)
{
Rectangle renderingArea = new Rectangle(image.getWidth(), image.getHeight());
if (renderer instanceof DataRenderable)
{
imageMapName = imageMaps.get(new Pair(renderer.getId(), renderingArea));
}
if (imageMapName == null)
{
Renderable originalRenderer = image.getRenderer();
imageMapName = "map_" + getElementIndex(cell).toString() + "-" + originalRenderer.getId();//use renderer.getId()?
imageMapAreas = ((AreaHyperlinksRenderable) originalRenderer).getImageAreaHyperlinks(renderingArea);//FIXMECHART
if (renderer instanceof DataRenderable)
{
imageMaps.put(new Pair(renderer.getId(), renderingArea), imageMapName);
}
}
}
InternalImageProcessor imageProcessor =
new InternalImageProcessor(
image,
isLazy,
!useBackgroundImage && scaleImage != ScaleImageEnum.FILL_FRAME && !isLazy,
cell,
availableImageWidth,
availableImageHeight
);
InternalImageProcessorResult imageProcessorResult = null;
try
{
imageProcessorResult = imageProcessor.process(renderer);
}
catch (Exception e)
{
Renderable onErrorRenderer = getRendererUtil().handleImageError(e, image.getOnErrorTypeValue());
if (onErrorRenderer != null)
{
imageProcessorResult = imageProcessor.process(onErrorRenderer);
}
}
if (imageProcessorResult != null)
{
if (useBackgroundImage)
{
int width = availableImageWidth;
int height = availableImageHeight;
int translateX = 0;
int translateY = 0;
int angle = 0;
String backgroundSize = null;
switch (rotation)
{
case LEFT:
width = availableImageHeight;
height = availableImageWidth;
translateX = (availableImageWidth - availableImageHeight) / 2;
translateY = - translateX;
angle = -90;
break;
case RIGHT:
width = availableImageHeight;
height = availableImageWidth;
translateX = (availableImageWidth - availableImageHeight) / 2;
translateY = - translateX;
angle = 90;
break;
case UPSIDE_DOWN:
angle = 180;
break;
case NONE:
default:
break;
}
switch (scaleImage)
{
case FILL_FRAME :
{
backgroundSize = "100% 100%";
break;
}
case CLIP :
{
backgroundSize = "auto";
break;
}
case RETAIN_SHAPE :
default :
{
backgroundSize = "contain";
}
}
writer.write("");
writer.write("");
writer.write("");
}
else if (imageProcessorResult.isEmbededSvgData)
{
writer.write("");
}
else
{
writer.write(" 0)
{
double normalWidth = availableImageWidth;
double normalHeight = availableImageHeight;
Dimension2D dimension = imageProcessorResult.dimension;
if (dimension != null)
{
normalWidth = dimension.getWidth();
normalHeight = dimension.getHeight();
}
double ratio = normalWidth / normalHeight;
if ( ratio > (double)availableImageWidth / (double)availableImageHeight )
{
writer.write(" style=\"width: ");
writer.write(toSizeUnit(availableImageWidth));
writer.write("\"");
}
else
{
writer.write(" style=\"height: ");
writer.write(toSizeUnit(availableImageHeight));
writer.write("\"");
}
}
}
}
if (imageMapName != null)
{
writer.write(" usemap=\"#" + imageMapName + "\"");
}
writer.write(" alt=\"\"");
if (hasHyperlinks)
{
writer.write(" border=\"0\"");
}
if (image.getHyperlinkTooltip() != null)
{
writer.write(" title=\"");
writer.write(JRStringUtil.encodeXmlAttribute(image.getHyperlinkTooltip()));
writer.write("\"");
}
writer.write("/>");
}
}
if (hyperlinkStarted)
{
endHyperlink();
}
if (useDiv)
{
writer.write(" ");
}
if (imageMapAreas != null)
{
writer.write("\n");
writeImageMap(imageMapName, image, imageMapAreas);
}
}
endCell();
}
private class InternalImageProcessor
{
private final JRPrintImage imageElement;
private final RenderersCache imageRenderersCache;
private final boolean isLazy;
private final boolean embedImage;
private final boolean needDimension;
private final TableCell cell;
private final int availableImageWidth;
private final int availableImageHeight;
protected InternalImageProcessor(
JRPrintImage imageElement,
boolean isLazy,
boolean needDimension,
TableCell cell,
int availableImageWidth,
int availableImageHeight
)
{
this.imageElement = imageElement;
this.imageRenderersCache = imageElement.isUsingCache() ? renderersCache : new RenderersCache(getJasperReportsContext());
this.isLazy = isLazy;
this.embedImage = isEmbedImage(imageElement);
this.needDimension = needDimension;
this.cell = cell;
this.availableImageWidth = availableImageWidth;
this.availableImageHeight = availableImageHeight;
}
protected InternalImageProcessorResult process(Renderable renderer) throws JRException, IOException
{
String imageSource = null;
Dimension2D dimension = null;
boolean isEmbededSvgData = false;
if (isLazy)
{
// we do not cache imagePath for lazy images because the short location string is already cached inside the render itself
imageSource = RendererUtil.getResourceLocation(renderer);
}
else
{
if (renderer instanceof ResourceRenderer)
{
renderer = imageRenderersCache.getLoadedRenderer((ResourceRenderer)renderer);
}
// check dimension first, to avoid caching renderers that might not be used eventually, due to their dimension errors
if (needDimension)
{
DimensionRenderable dimensionRenderer = imageRenderersCache.getDimensionRenderable(renderer);
dimension = dimensionRenderer == null ? null : dimensionRenderer.getDimension(jasperReportsContext);
}
if (
!embedImage //we do not cache imagePath for embedded images because it is too big
&& renderer instanceof DataRenderable //we do not cache imagePath for non-data renderers because they render width different width/height each time
&& rendererToImagePathMap.containsKey(renderer.getId())
)
{
imageSource = rendererToImagePathMap.get(renderer.getId());
}
else
{
if (embedImage)
{
Dimension dim = null;
if (
imageElement.getRotation() == RotationEnum.LEFT
|| imageElement.getRotation() == RotationEnum.RIGHT
)
{
dim = new Dimension(availableImageHeight, availableImageWidth);
}
else
{
dim = new Dimension(availableImageWidth, availableImageHeight);
}
DataRenderable dataRenderer = null;
if (isConvertSvgToImage(imageElement))
{
dataRenderer =
getRendererUtil().getImageDataRenderable(
imageRenderersCache,
renderer,
dim,
ModeEnum.OPAQUE == imageElement.getModeValue() ? imageElement.getBackcolor() : null
);
}
else
{
dataRenderer =
getRendererUtil().getDataRenderable(
renderer,
dim,
ModeEnum.OPAQUE == imageElement.getModeValue() ? imageElement.getBackcolor() : null
);
}
byte[] imageData = dataRenderer.getData(jasperReportsContext);
SvgDataSniffer.SvgInfo svgInfo = getRendererUtil().getSvgInfo(imageData);
if (svgInfo != null)
{
if (isEmbeddedSvgUseFonts(imageElement))
{
Locale locale = getLocale();
SvgFontProcessor svgFontProcessor = new SvgFontProcessor(jasperReportsContext, locale)
{
@Override
public String getFontFamily(String fontFamily, Locale locale)
{
// Here we rely on the ability of FontUtil.getFontInfoIgnoreCase(fontFamily, locale) method to
// find fonts from font extensions based on the java.awt.Font.getFamily() of their font faces.
// This is because the SVG produced by Batik stores the family name of the AWT fonts used to
// render text on the Batik Graphics2D implementation, as it knows nothing about family names from JR extensions.
return HtmlExporter.this.getFontFamily(true, fontFamily, locale);
}
};
imageData = svgFontProcessor.process(imageData);
}
isEmbededSvgData = true;
String encoding = svgInfo.getEncoding();
imageSource = new String(imageData, encoding == null ? "UTF-8" : encoding);
// we might have received needDimension false above, as a hint, but if we arrive here,
// we definitely need to attempt getting the dimension of the SVG, regardless of scale image type
DimensionRenderable dimensionRenderer = imageRenderersCache.getDimensionRenderable(renderer);
dimension = dimensionRenderer == null ? null : dimensionRenderer.getDimension(jasperReportsContext);
}
else
{
String imageMimeType = JRTypeSniffer.getImageTypeValue(imageData).getMimeType();
ByteArrayInputStream bais = new ByteArrayInputStream(imageData);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Base64Util.encode(bais, baos);
imageSource = "data:" + imageMimeType + ";base64," + new String(baos.toByteArray(), "UTF-8"); // UTF-8 is fine as we just need an ASCII compatible encoding for the Base64 array
}
//don't cache embedded imageSource as they are not image paths
}
else
{
@SuppressWarnings("deprecation")
HtmlResourceHandler imageHandler =
getImageHandler() == null
? getExporterOutput().getImageHandler()
: getImageHandler();
if (imageHandler != null)
{
Dimension dim = null;
if (
imageElement.getRotation() == RotationEnum.LEFT
|| imageElement.getRotation() == RotationEnum.RIGHT
)
{
dim = new Dimension(availableImageHeight, availableImageWidth);
}
else
{
dim = new Dimension(availableImageWidth, availableImageHeight);
}
DataRenderable dataRenderer = null;
if (isConvertSvgToImage(imageElement))
{
dataRenderer =
getRendererUtil().getImageDataRenderable(
imageRenderersCache,
renderer,
dim,
ModeEnum.OPAQUE == imageElement.getModeValue() ? imageElement.getBackcolor() : null
);
}
else
{
dataRenderer =
getRendererUtil().getDataRenderable(
renderer,
dim,
ModeEnum.OPAQUE == imageElement.getModeValue() ? imageElement.getBackcolor() : null
);
}
byte[] imageData = dataRenderer.getData(jasperReportsContext);
String fileExtension =
getRendererUtil().isSvgData(imageData)
? RendererUtil.SVG_FILE_EXTENSION
: JRTypeSniffer.getImageTypeValue(imageData).getFileExtension();
String imageName = getImageName(getElementIndex(cell), fileExtension);
imageHandler.handleResource(imageName, imageData);
imageSource = imageHandler.getResourcePath(imageName);
if (dataRenderer == renderer)
{
//cache imagePath only for true ImageRenderable instances because the wrapping ones render with different width/height each time
rendererToImagePathMap.put(renderer.getId(), imageSource);
}
}
//does not make sense to cache null imagePath, in the absence of an image handler
}
}
}
return
new InternalImageProcessorResult(
imageSource,
dimension,
isEmbededSvgData
);
}
}
private static class InternalImageProcessorResult
{
protected final String imageSource;
protected final Dimension2D dimension;
protected final boolean isEmbededSvgData;
protected InternalImageProcessorResult(
String imagePath,
Dimension2D dimension,
boolean isEmbededSvgData
)
{
this.imageSource = imagePath;
this.dimension = dimension;
this.isEmbededSvgData = isEmbededSvgData;
}
}
protected String getImageHorizontalAlignmentStyle(JRPrintImage image)
{
String horizontalAlignment = CSS_TEXT_ALIGN_LEFT;
switch (image.getHorizontalImageAlign())
{
case RIGHT :
{
horizontalAlignment = CSS_TEXT_ALIGN_RIGHT;
break;
}
case CENTER :
{
horizontalAlignment = CSS_TEXT_ALIGN_CENTER;
break;
}
case LEFT :
default :
{
horizontalAlignment = CSS_TEXT_ALIGN_LEFT;
}
}
return horizontalAlignment;
}
protected String getImageVerticalAlignmentStyle(JRPrintImage image)
{
String verticalAlignment = HTML_VERTICAL_ALIGN_TOP;
switch (image.getVerticalImageAlign())
{
case BOTTOM :
{
verticalAlignment = HTML_VERTICAL_ALIGN_BOTTOM;
break;
}
case MIDDLE :
{
verticalAlignment = HTML_VERTICAL_ALIGN_MIDDLE;
break;
}
case TOP :
default :
{
verticalAlignment = HTML_VERTICAL_ALIGN_TOP;
}
}
return verticalAlignment;
}
protected JRPrintElementIndex getElementIndex(TableCell cell)
{
String elementAddress = cell.getElementAddress();
JRPrintElementIndex elementIndex = new JRPrintElementIndex(reportIndex, pageIndex,
elementAddress);
return elementIndex;
}
protected void writeImageMap(String imageMapName, JRPrintImage image, List imageMapAreas) throws IOException
{
writer.write("\n");
}
protected void writeImageAreaCoordinates(int[] coords) throws IOException
{
if (coords != null && coords.length > 0)
{
StringBuilder coordsEnum = new StringBuilder(coords.length * 4);
coordsEnum.append((int)toZoom(coords[0]));
for (int i = 1; i < coords.length; i++)
{
coordsEnum.append(',');
coordsEnum.append((int)toZoom(coords[i]));
}
writer.write(" coords=\"" + coordsEnum + "\"");
}
}
protected void writeImageAreaHyperlink(JRPrintHyperlink hyperlink) throws IOException
{
if (getReportContext() != null)
{
if (hyperlink.getLinkType() != null)
{
int id = hyperlink.hashCode() & 0x7FFFFFFF;
writer.write(" class=\"_jrHyperLink " + JRStringUtil.encodeXmlAttribute(hyperlink.getLinkType()) + "\" data-id=\"" + id + "\"");
HyperlinkData hyperlinkData = new HyperlinkData();
hyperlinkData.setId(String.valueOf(id));
hyperlinkData.setHref(getHyperlinkURL(hyperlink));
hyperlinkData.setSelector("._jrHyperLink." + hyperlink.getLinkType());
hyperlinkData.setHyperlink(hyperlink);
hyperlinksData.add(hyperlinkData);
}
}
else
{
String href = getHyperlinkURL(hyperlink);
if (href == null)
{
writer.write(" nohref=\"nohref\"");
}
else
{
writer.write(" href=\"" + JRStringUtil.encodeXmlAttribute(href) + "\"");
String target = getHyperlinkTarget(hyperlink);
if (target != null)
{
writer.write(" target=\"");
writer.write(JRStringUtil.encodeXmlAttribute(target));
writer.write("\"");
}
}
}
if (hyperlink.getHyperlinkTooltip() != null)
{
writer.write(" title=\"");
writer.write(JRStringUtil.encodeXmlAttribute(hyperlink.getHyperlinkTooltip()));
writer.write("\"");
}
}
protected void writeRectangle(JRPrintRectangle rectangle, TableCell cell) throws IOException
{
startCell(rectangle, cell);
int radius = rectangle.getRadius();
if (radius == 0)
{
StringBuilder styleBuffer = new StringBuilder();
appendElementCellGenericStyle(cell, styleBuffer);
appendBackcolorStyle(cell, styleBuffer);
appendPen(
styleBuffer,
rectangle.getLinePen(),
null
);
writeStyle(styleBuffer);
}
finishStartCell();
if (radius != 0)
{
float lineDiff = rectangle.getLinePen().getLineWidth() / 2;
writer.write("");
}
endCell();
}
protected void writeEllipse(JRPrintEllipse ellipse, TableCell cell) throws IOException
{
startCell(ellipse, cell);
finishStartCell();
float lineDiff = ellipse.getLinePen().getLineWidth() / 2;
writer.write("");
endCell();
}
protected void writeSvgStyle(JRPrintGraphicElement element) throws IOException
{
writer.write("style=\"fill:" + JRColorUtil.getCssColor(element.getBackcolor()) + ";");
writer.write("stroke:" + JRColorUtil.getCssColor(element.getLinePen().getLineColor()) + ";");
writer.write("stroke-width:" + element.getLinePen().getLineWidth() + ";");
switch (element.getLinePen().getLineStyleValue())
{
case DOTTED :
{
writer.write("stroke-dasharray:" + element.getLinePen().getLineWidth() + "," + element.getLinePen().getLineWidth() + ";");
break;
}
case DASHED :
{
writer.write("stroke-dasharray:" + 5 * element.getLinePen().getLineWidth() + "," + 3 * element.getLinePen().getLineWidth() + ";");
break;
}
case DOUBLE : //FIXME: there is no built-in svg support for double stroke style; strokes could be rendered twice as a workaround
case SOLID :
default :
{
break;
}
}
}
protected void writeLine(JRPrintLine line, TableCell cell)
throws IOException
{
startCell(line, cell);
if(isOblique(line))
{
finishStartCell();
int width = line.getWidth();
int height = line.getHeight();
LineDirectionEnum lineDirection = line.getDirectionValue();
int y1 = lineDirection == LineDirectionEnum.BOTTOM_UP ? height : 0;
int y2 = lineDirection == LineDirectionEnum.BOTTOM_UP ? 0 : height;
writer.write("");
}
else
{
StringBuilder styleBuffer = new StringBuilder();
appendElementCellGenericStyle(cell, styleBuffer);
appendBackcolorStyle(cell, styleBuffer);
String side = null;
float ratio = line.getWidth() / line.getHeight();
if (ratio > 1)
{
if (line.getDirectionValue() == LineDirectionEnum.TOP_DOWN)
{
side = "top";
}
else
{
side = "bottom";
}
}
else
{
if (line.getDirectionValue() == LineDirectionEnum.TOP_DOWN)
{
side = "left";
}
else
{
side = "right";
}
}
appendPen(
styleBuffer,
line.getLinePen(),
side
);
writeStyle(styleBuffer);
finishStartCell();
}
endCell();
}
protected boolean isOblique(JRPrintLine line){
return line.getWidth() > 1 && line.getHeight() > 1;
}
protected void writeGenericElement(JRGenericPrintElement element, TableCell cell) throws IOException
{
GenericElementHtmlHandler handler = (GenericElementHtmlHandler)
GenericElementHandlerEnviroment.getInstance(getJasperReportsContext()).getElementHandler(
element.getGenericType(), HTML_EXPORTER_KEY);
if (handler == null)
{
if (log.isDebugEnabled())
{
log.debug("No HTML generic element handler for "
+ element.getGenericType());
}
writeEmptyCell(cell.getColumnSpan(), cell.getRowSpan());// TODO lucianc backcolor/borders?
}
else
{
startCell(element, cell);
StringBuilder styleBuffer = new StringBuilder();
appendElementCellGenericStyle(cell, styleBuffer);
appendBackcolorStyle(cell, styleBuffer);
appendBorderStyle(cell.getBox(), styleBuffer);
writeStyle(styleBuffer);
finishStartCell();
String htmlFragment = handler.getHtmlFragment(exporterContext, element);
if (htmlFragment != null)
{
writer.write(htmlFragment);
}
endCell();
}
}
protected void writeLayers(List layers, TableVisitor tableVisitor, TableCell cell) throws IOException
{
startCell(cell);
StringBuilder styleBuffer = new StringBuilder();
appendElementCellGenericStyle(cell, styleBuffer);
appendBackcolorStyle(cell, styleBuffer);
appendBorderStyle(cell.getBox(), styleBuffer);
writeStyle(styleBuffer);
finishStartCell();
// layers need to always specify backcolors
setBackcolor(null);
writer.write("\n");
for (ListIterator it = layers.listIterator(); it.hasNext();)
{
Table table = it.next();
StringBuilder layerStyleBuffer = new StringBuilder();
if (it.hasNext()) {
layerStyleBuffer.append("position: absolute; overflow: hidden; ");
} else {
layerStyleBuffer.append("position: relative; ");
}
layerStyleBuffer.append("width: 100%; height: 100%; ");
if (it.previousIndex() > 0) {
layerStyleBuffer.append("pointer-events: none; ");
}
writer.write("\n");
++pointerEventsNoneStack;
exportTable(tableVisitor, table, false, false);
--pointerEventsNoneStack;
writer.write("\n");
}
writer.write("\n");
restoreBackcolor();
endCell();
}
protected void startCell(JRPrintElement element, TableCell cell) throws IOException
{
startCell(cell.getColumnSpan(), cell.getRowSpan());
String dataAttr = getDataAttributes(element, cell);
if (dataAttr != null)
{
writer.write(dataAttr);
}
}
public String getDataAttributes(JRPrintElement element, TableCell cell)
{
StringBuilder sb = new StringBuilder();
String id = getCellProperty(element, cell, PROPERTY_HTML_ID);
if (id != null)
{
sb.append(" id=\"" + JRStringUtil.encodeXmlAttribute(id) +"\"");
}
String clazz = getCellProperty(element, cell, PROPERTY_HTML_CLASS);
if (clazz != null)
{
sb.append(" class=\"" + JRStringUtil.encodeXmlAttribute(clazz) +"\"");
}
String colUuid = getCellProperty(element, cell, HeaderToolbarElement.PROPERTY_COLUMN_UUID);//FIXMEJIVE register properties like this in a pluggable way; extensions?
if (colUuid != null)
{
sb.append(" data-coluuid=\"" + JRStringUtil.encodeXmlAttribute(colUuid) + "\"");
}
String cellId = getCellProperty(element, cell, HeaderToolbarElement.PROPERTY_CELL_ID);
if (cellId != null)
{
sb.append(" data-cellid=\"" + JRStringUtil.encodeXmlAttribute(cellId) + "\"");
}
String tableUuid = getCellProperty(element, cell, HeaderToolbarElement.PROPERTY_TABLE_UUID);
if (tableUuid != null)
{
sb.append(" data-tableuuid=\"" + JRStringUtil.encodeXmlAttribute(tableUuid) + "\"");
}
String columnIndex = getCellProperty(element, cell, HeaderToolbarElement.PROPERTY_COLUMN_INDEX);
if (columnIndex != null)
{
sb.append(" data-colidx=\"" + JRStringUtil.encodeXmlAttribute(columnIndex) + "\"");
}
String xtabId = getCellProperty(element, cell, CrosstabInteractiveJsonHandler.PROPERTY_CROSSTAB_ID);
if (xtabId != null)
{
sb.append(" " + CrosstabInteractiveJsonHandler.ATTRIBUTE_CROSSTAB_ID + "=\""
+ JRStringUtil.encodeXmlAttribute(xtabId) + "\"");
}
String xtabColIdx = getCellProperty(element, cell, CrosstabInteractiveJsonHandler.PROPERTY_COLUMN_INDEX);
if (xtabColIdx != null)
{
sb.append(" " + CrosstabInteractiveJsonHandler.ATTRIBUTE_COLUMN_INDEX + "=\""
+ JRStringUtil.encodeXmlAttribute(xtabColIdx) + "\"");
}
return sb.length() > 0 ? sb.toString() : null;
}
protected String getCellProperty(JRPrintElement element, TableCell cell, String key)
{
String property = null;
if (element != null)
{
property = getPropertiesUtil().getProperty(element, key);
}
if (property == null)
{
Tabulator tabulator = cell.getTabulator();
for (FrameCell parentCell = cell.getCell().getParent();
parentCell != null && property == null;
parentCell = parentCell.getParent())
{
JRPrintElement parentElement = tabulator.getCellElement(parentCell);
property = getPropertiesUtil().getProperty(parentElement, key);
}
}
return property;
}
protected void startCell(TableCell cell) throws IOException
{
startCell(cell.getElement(), cell);
}
protected void startCell(int colSpan, int rowSpan) throws IOException
{
writer.write(" 1)
{
writer.write(" colspan=\"");
writer.write(Integer.toString(colSpan));
writer.write("\"");
}
if (rowSpan > 1)
{
writer.write(" rowspan=\"");
writer.write(Integer.toString(rowSpan));
writer.write("\"");
}
}
protected void finishStartCell() throws IOException
{
writer.write(">\n");
}
protected void endCell() throws IOException
{
writer.write(" \n");
}
protected void writeEmptyCell(int colSpan, int rowSpan) throws IOException
{
startCell(colSpan, rowSpan);
finishStartCell();
endCell();
}
protected void writeFrameCell(TableCell cell) throws IOException
{
startCell(cell);
StringBuilder styleBuffer = new StringBuilder();
appendElementCellGenericStyle(cell, styleBuffer);
appendBackcolorStyle(cell, styleBuffer);
appendBorderStyle(cell.getBox(), styleBuffer);
writeStyle(styleBuffer);
finishStartCell();
endCell();
}
protected void writeStyle(StringBuilder styleBuffer) throws IOException
{
if (styleBuffer.length() > 0)
{
writer.write(" style=\"");
writer.write(styleBuffer.toString());
writer.write("\"");
}
}
protected void appendElementCellGenericStyle(TableCell cell, StringBuilder styleBuffer)
{
if (pointerEventsNoneStack > 0 && cell.getElement() != null)
{
styleBuffer.append("pointer-events: auto; ");
}
}
protected void setBackcolor(Color color)
{
backcolorStack.addFirst(color);
}
protected void restoreBackcolor()
{
backcolorStack.removeFirst();
}
protected boolean matchesBackcolor(Color backcolor)
{
if (backcolorStack.isEmpty())
{
return false;
}
Color currentBackcolor = backcolorStack.getFirst();
return currentBackcolor != null && backcolor.getRGB() == currentBackcolor.getRGB();
}
protected Color appendBackcolorStyle(TableCell cell, StringBuilder styleBuffer)
{
Color cellBackcolor = cell.getBackcolor();
if (cellBackcolor != null && !matchesBackcolor(cellBackcolor))
{
styleBuffer.append("background-color: ");
styleBuffer.append(JRColorUtil.getCssColor(cellBackcolor));
styleBuffer.append("; ");
return cellBackcolor;
}
return null;
}
protected boolean appendBorderStyle(JRLineBox box, StringBuilder styleBuffer)
{
boolean addedToStyle = false;
if (box != null)
{
LineStyleEnum tps = box.getTopPen().getLineStyleValue();
LineStyleEnum lps = box.getLeftPen().getLineStyleValue();
LineStyleEnum bps = box.getBottomPen().getLineStyleValue();
LineStyleEnum rps = box.getRightPen().getLineStyleValue();
float tpw = box.getTopPen().getLineWidth();
float lpw = box.getLeftPen().getLineWidth();
float bpw = box.getBottomPen().getLineWidth();
float rpw = box.getRightPen().getLineWidth();
if (0f < tpw && tpw < 1f) {
tpw = 1f;
}
if (0f < lpw && lpw < 1f) {
lpw = 1f;
}
if (0f < bpw && bpw < 1f) {
bpw = 1f;
}
if (0f < rpw && rpw < 1f) {
rpw = 1f;
}
Color tpc = box.getTopPen().getLineColor();
// try to compact all borders into one css property
if (tps == lps && // same line style
tps == bps &&
tps == rps &&
tpw == lpw && // same line width
tpw == bpw &&
tpw == rpw &&
tpc.equals(box.getLeftPen().getLineColor()) && // same line color
tpc.equals(box.getBottomPen().getLineColor()) &&
tpc.equals(box.getRightPen().getLineColor()))
{
addedToStyle |= appendPen(
styleBuffer,
box.getTopPen(),
null
);
} else {
addedToStyle |= appendPen(
styleBuffer,
box.getTopPen(),
"top"
);
addedToStyle |= appendPen(
styleBuffer,
box.getLeftPen(),
"left"
);
addedToStyle |= appendPen(
styleBuffer,
box.getBottomPen(),
"bottom"
);
addedToStyle |= appendPen(
styleBuffer,
box.getRightPen(),
"right"
);
}
}
return addedToStyle;
}
protected boolean appendPen(StringBuilder sb, JRPen pen, String side)
{
boolean addedToStyle = false;
float borderWidth = pen.getLineWidth();
if (0f < borderWidth && borderWidth < 1f)
{
borderWidth = 1f;
}
String borderStyle = null;
switch (pen.getLineStyleValue())
{
case DOUBLE :
{
borderStyle = "double";
break;
}
case DOTTED :
{
borderStyle = "dotted";
break;
}
case DASHED :
{
borderStyle = "dashed";
break;
}
case SOLID :
default :
{
borderStyle = "solid";
break;
}
}
if (borderWidth > 0f)
{
sb.append("border");
if (side != null)
{
sb.append("-");
sb.append(side);
}
sb.append(": ");
sb.append(toSizeUnit(borderWidth));
sb.append(" ");
sb.append(borderStyle);
sb.append(" ");
sb.append(JRColorUtil.getCssColor(pen.getLineColor()));
sb.append("; ");
addedToStyle = true;
}
return addedToStyle;
}
protected boolean appendPaddingStyle(JRLineBox box, StringBuilder styleBuffer)
{
boolean addedToStyle = false;
if (box != null)
{
Integer tp = box.getTopPadding();
Integer lp = box.getLeftPadding();
Integer bp = box.getBottomPadding();
Integer rp = box.getRightPadding();
// try to compact all paddings into one css property
if (tp == lp && tp == bp && tp == rp)
{
addedToStyle |= appendPadding(
styleBuffer,
tp,
null
);
} else
{
addedToStyle |= appendPadding(
styleBuffer,
box.getTopPadding(),
"top"
);
addedToStyle |= appendPadding(
styleBuffer,
box.getLeftPadding(),
"left"
);
addedToStyle |= appendPadding(
styleBuffer,
box.getBottomPadding(),
"bottom"
);
addedToStyle |= appendPadding(
styleBuffer,
box.getRightPadding(),
"right"
);
}
}
return addedToStyle;
}
protected boolean appendPadding(StringBuilder sb, Integer padding, String side)
{
boolean addedToStyle = false;
if (padding > 0)
{
sb.append("padding");
if (side != null)
{
sb.append("-");
sb.append(side);
}
sb.append(": ");
sb.append(toSizeUnit(padding));
sb.append("; ");
addedToStyle = true;
}
return addedToStyle;
}
protected boolean startHyperlink(JRPrintHyperlink link) throws IOException
{
boolean hyperlinkStarted = false,
canWrite = false;
if (getReportContext() != null)
{
Boolean ignoreHyperlink = HyperlinkUtil.getIgnoreHyperlink(HtmlReportConfiguration.PROPERTY_IGNORE_HYPERLINK, link);
if (ignoreHyperlink == null)
{
ignoreHyperlink = getCurrentItemConfiguration().isIgnoreHyperlink();
}
if (!ignoreHyperlink && link.getLinkType() != null)
{
canWrite = true;
int id = link.hashCode() & 0x7FFFFFFF;
writer.write("");
}
return hyperlinkStarted;
}
protected void endHyperlink() throws IOException
{
if (getReportContext() != null) {
writer.write("");
}
else
{
writer.write("");
}
}
protected String getHyperlinkURL(JRPrintHyperlink link)
{
return resolveHyperlinkURL(reportIndex, link);
}
protected String resolveHyperlinkURL(int reportIndex, JRPrintHyperlink link)
{
String href = null;
Boolean ignoreHyperlink = HyperlinkUtil.getIgnoreHyperlink(HtmlReportConfiguration.PROPERTY_IGNORE_HYPERLINK, link);
if (ignoreHyperlink == null)
{
ignoreHyperlink = getCurrentItemConfiguration().isIgnoreHyperlink();
}
if (!ignoreHyperlink)
{
JRHyperlinkProducer customHandler = getHyperlinkProducer(link);
if (customHandler == null)
{
switch(link.getHyperlinkTypeValue())
{
case REFERENCE :
{
if (link.getHyperlinkReference() != null)
{
href = link.getHyperlinkReference();
}
break;
}
case LOCAL_ANCHOR :
{
if (link.getHyperlinkAnchor() != null)
{
href = "#" + link.getHyperlinkAnchor();
}
break;
}
case LOCAL_PAGE :
{
if (link.getHyperlinkPage() != null)
{
href = "#" + JR_PAGE_ANCHOR_PREFIX + reportIndex + "_" + link.getHyperlinkPage().toString();
}
break;
}
case REMOTE_ANCHOR :
{
if (
link.getHyperlinkReference() != null &&
link.getHyperlinkAnchor() != null
)
{
href = link.getHyperlinkReference() + "#" + link.getHyperlinkAnchor();
}
break;
}
case REMOTE_PAGE :
{
if (
link.getHyperlinkReference() != null &&
link.getHyperlinkPage() != null
)
{
href = link.getHyperlinkReference() + "#" + JR_PAGE_ANCHOR_PREFIX + "0_" + link.getHyperlinkPage().toString();
}
break;
}
case NONE :
default :
{
break;
}
}
}
else
{
href = customHandler.getHyperlink(link);
}
}
return href;
}
protected String getHyperlinkTarget(JRPrintHyperlink link)
{
String target = null;
JRHyperlinkTargetProducer producer = targetProducerFactory.getHyperlinkTargetProducer(link.getLinkTarget());
if (producer == null)
{
switch(link.getHyperlinkTargetValue())
{
case BLANK :
{
target = "_blank";//FIXME make reverse for html markup hyperlinks
break;
}
case PARENT :
{
target = "_parent";
break;
}
case TOP :
{
target = "_top";
break;
}
case CUSTOM :
{
boolean paramFound = false;
List parameters = link.getHyperlinkParameters() == null ? null : link.getHyperlinkParameters().getParameters();
if (parameters != null)
{
for(Iterator it = parameters.iterator(); it.hasNext();)
{
JRPrintHyperlinkParameter parameter = it.next();
if (link.getLinkTarget().equals(parameter.getName()))
{
target = parameter.getValue() == null ? null : parameter.getValue().toString();
paramFound = true;
break;
}
}
}
if (!paramFound)
{
target = link.getLinkTarget();
}
break;
}
case SELF :
default :
{
}
}
}
else
{
target = producer.getHyperlinkTarget(link);
}
return target;
}
public String toSizeUnit(float size)
{
Number number = toZoom(size);
if (number.intValue() == number.floatValue())
{
number = number.intValue();
}
return String.valueOf(number) + getCurrentItemConfiguration().getSizeUnit().getName();
}
protected float toZoom(float size)//FIXMEEXPORT cache this
{
float zoom = DEFAULT_ZOOM;
Float zoomRatio = getCurrentItemConfiguration().getZoomRatio();
if (zoomRatio != null)
{
zoom = zoomRatio;
if (zoom <= 0)
{
throw
new JRRuntimeException(
EXCEPTION_MESSAGE_KEY_INVALID_ZOOM_RATIO,
new Object[]{zoom}
);
}
}
return (zoom * size);
}
private void addSearchAttributes(JRStyledText styledText, JRPrintText textElement) {
ReportContext reportContext = getReportContext();
if (reportContext != null) {
SpansInfo spansInfo = (SpansInfo) reportContext.getParameterValue("net.sf.jasperreports.search.term.highlighter");
PrintElementId pei = PrintElementId.forElement(textElement);
if (spansInfo != null && spansInfo.hasHitTermsInfo(pei.toString())) {
List hitTermInfos = JRCloneUtils.cloneList(spansInfo.getHitTermsInfo(pei.toString()));
short[] lineBreakOffsets = textElement.getLineBreakOffsets();
if (lineBreakOffsets != null && lineBreakOffsets.length > 0) {
int sz = lineBreakOffsets.length;
for (HitTermInfo ti: hitTermInfos) {
for (int i = 0; i < sz; i++) {
if (lineBreakOffsets[i] <= ti.getStart()) {
ti.setStart(ti.getStart() + 1);
ti.setEnd(ti.getEnd() + 1);
} else {
break;
}
}
}
}
AttributedString attributedString = styledText.getAttributedString();
for (int i = 0, ln = hitTermInfos.size(); i < ln; i = i + spansInfo.getTermsPerQuery()) {
attributedString.addAttribute(JRTextAttribute.SEARCH_HIGHLIGHT, Color.yellow, hitTermInfos.get(i).getStart(), hitTermInfos.get(i + spansInfo.getTermsPerQuery() - 1).getEnd());
}
}
} else {
if (log.isDebugEnabled()) {
log.debug("No ReportContext to hold search data!");
}
}
}
protected void exportStyledText(JRPrintText printText, JRStyledText styledText, String tooltip, boolean hyperlinkStarted) throws IOException
{
String allText = styledText.getText();
addSearchAttributes(styledText, printText);
AttributedCharacterIterator allParagraphs = styledText.getAttributedString().getIterator();
int tokenPosition = 0;
int prevParagraphStart = 0;
String prevParagraphText = null;
boolean indentFirstLine = true;
Integer firstLineIndent = printText.getParagraph().getFirstLineIndent();
if (firstLineIndent != 0)
{
indentFirstLine = defaultIndentFirstLine;
if (printText.getPropertiesMap().containsProperty(JRPrintText.PROPERTY_AWT_INDENT_FIRST_LINE))
{
indentFirstLine = propertiesUtil.getBooleanProperty(printText, JRPrintText.PROPERTY_AWT_INDENT_FIRST_LINE, defaultIndentFirstLine);
}
}
boolean justifyLastLine = false;
if (HorizontalTextAlignEnum.JUSTIFIED == printText.getHorizontalTextAlign())
{
justifyLastLine = defaultJustifyLastLine;
if (printText.getPropertiesMap().containsProperty(JRPrintText.PROPERTY_AWT_JUSTIFY_LAST_LINE))
{
justifyLastLine = propertiesUtil.getBooleanProperty(printText, JRPrintText.PROPERTY_AWT_JUSTIFY_LAST_LINE, defaultJustifyLastLine);
}
}
boolean isFirstParagraph = true;
boolean isLastParagraph = false;
if (
(firstLineIndent != 0 && allText.indexOf('\n') > 0)
|| (!indentFirstLine || justifyLastLine)
)
{
StringTokenizer tkzer = new StringTokenizer(allText, "\n", true);
// text is split into paragraphs, using the newline character as delimiter
while(tkzer.hasMoreTokens())
{
String token = tkzer.nextToken();
if ("\n".equals(token))
{
exportParagraph(
printText, allParagraphs, prevParagraphStart, prevParagraphText,
isFirstParagraph && !indentFirstLine ? (Integer)0 : firstLineIndent,
isLastParagraph && justifyLastLine,
tooltip, hyperlinkStarted
);
isFirstParagraph = false;
isLastParagraph = !tkzer.hasMoreTokens();
prevParagraphStart = tokenPosition + (tkzer.hasMoreTokens() || tokenPosition == 0 ? 1 : 0);
prevParagraphText = null;
}
else
{
prevParagraphStart = tokenPosition;
prevParagraphText = token;
}
tokenPosition += token.length();
}
}
else
{
prevParagraphText = allText;
firstLineIndent = null; // null means we don't need to use a to force first line indent as it was already dealt-with in style
}
if (prevParagraphStart < allText.length())
{
exportParagraph(
printText, allParagraphs, prevParagraphStart, prevParagraphText,
isFirstParagraph && !indentFirstLine ? (Integer)0 : firstLineIndent,
justifyLastLine, // isLastParagraph would be considered true here, so no point in keeping && operation
tooltip, hyperlinkStarted
);
}
}
protected void exportParagraph(
JRPrintText printText,
AttributedCharacterIterator allParagraphs,
int paragraphStart,
String paragraphText,
Integer firstLineIndent,
boolean justifyLastLine,
String tooltip,
boolean hyperlinkStarted
) throws IOException
{
Locale locale = getTextLocale(printText);
LineSpacingEnum lineSpacing = printText.getParagraph().getLineSpacing();
Float lineSpacingSize = printText.getParagraph().getLineSpacingSize();
float lineSpacingFactor = printText.getLineSpacingFactor();
Color backcolor = printText.getBackcolor();
AttributedCharacterIterator paragraph = null;
if (paragraphText == null)
{
paragraphText = "\n";
paragraph =
new AttributedString(
paragraphText,
new AttributedString(
allParagraphs,
paragraphStart,
paragraphStart + paragraphText.length()
).getIterator().getAttributes()
).getIterator();
}
else
{
paragraph =
new AttributedString(
allParagraphs,
paragraphStart,
paragraphStart + paragraphText.length()
).getIterator();
}
if (firstLineIndent != null || justifyLastLine)
{
writer.write("");
}
int runLimit = 0;
boolean first = true;
boolean startedSpan = false;
boolean highlightStarted = false;
while(runLimit < paragraphText.length() && (runLimit = paragraph.getRunLimit()) <= paragraphText.length())
{
//if there are several text runs, write the tooltip into a parent
if (first && runLimit < paragraphText.length() && tooltip != null)
{
startedSpan = true;
writer.write("");
//reset the tooltip so that inner s to not use it
tooltip = null;
}
first = false;
Map attributes = paragraph.getAttributes();
Color highlightColor = (Color) attributes.get(JRTextAttribute.SEARCH_HIGHLIGHT);
if (highlightColor != null && !highlightStarted) {
highlightStarted = true;
writer.write("");
} else if (highlightColor == null && highlightStarted) {
highlightStarted = false;
writer.write("");
}
exportStyledTextRun(
attributes,
paragraphText.substring(paragraph.getIndex(), runLimit),
tooltip,
locale,
lineSpacing,
lineSpacingSize,
lineSpacingFactor,
backcolor,
hyperlinkStarted
);
paragraph.setIndex(runLimit);
}
if (highlightStarted) {
writer.write(" ");
}
if (startedSpan)
{
writer.write("");
}
if (firstLineIndent != null || justifyLastLine)
{
writer.write("");
}
}
protected void exportStyledTextRun(
Map attributes,
String text,
String tooltip,
Locale locale,
LineSpacingEnum lineSpacing,
Float lineSpacingSize,
float lineSpacingFactor,
Color backcolor,
boolean hyperlinkStarted
) throws IOException
{
boolean localHyperlink = false;
JRPrintHyperlink hyperlink = (JRPrintHyperlink)attributes.get(JRTextAttribute.HYPERLINK);
if (!hyperlinkStarted && hyperlink != null)
{
localHyperlink = startHyperlink(hyperlink);
}
boolean isBold = TextAttribute.WEIGHT_BOLD.equals(attributes.get(TextAttribute.WEIGHT));
boolean isItalic = TextAttribute.POSTURE_OBLIQUE.equals(attributes.get(TextAttribute.POSTURE));
String fontFamily = resolveFontFamily(attributes, locale);
// do not put single quotes around family name here because the value might already contain quotes,
// especially if it is coming from font extension export configuration
writer.write("");
writer.write(
JRStringUtil.htmlEncode(text)
);
writer.write("");
if (localHyperlink)
{
endHyperlink();
}
}
protected class TableVisitor implements CellVisitor
{
private final Tabulator tabulator;
private final PrintElementVisitor elementVisitor;
public TableVisitor(Tabulator tabulator, PrintElementVisitor elementVisitor)
{
this.tabulator = tabulator;
this.elementVisitor = elementVisitor;
}
@Override
public Void visit(ElementCell cell, TablePosition position)
{
TableCell tableCell = tabulator.getTableCell(position, cell);
JRPrintElement element = tableCell.getElement();
element.accept(elementVisitor, tableCell);
return null;
}
@Override
public Void visit(SplitCell cell, TablePosition position)
{
//NOP
return null;
}
@Override
public Void visit(FrameCell frameCell, TablePosition position) throws IOException
{
TableCell tableCell = tabulator.getTableCell(position, frameCell);
HtmlExporter.this.writeFrameCell(tableCell);
return null;
}
@Override
public Void visit(LayeredCell layeredCell, TablePosition position)
throws IOException
{
TableCell tableCell = tabulator.getTableCell(position, layeredCell);
HtmlExporter.this.writeLayers(layeredCell.getLayers(), this, tableCell);
return null;
}
}
protected class CellElementVisitor implements PrintElementVisitor
{
@Override
public void visit(JRPrintText textElement, TableCell cell)
{
try
{
writeText(textElement, cell);
}
catch (IOException e)
{
throw new JRRuntimeException(e);
}
}
@Override
public void visit(JRPrintImage image, TableCell cell)
{
try
{
writeImage(image, cell);
}
catch (IOException e)
{
throw new JRRuntimeException(e);
}
catch (JRException e)
{
throw new JRRuntimeException(e);
}
}
@Override
public void visit(JRPrintRectangle rectangle, TableCell cell)
{
try
{
writeRectangle(rectangle, cell);
}
catch (IOException e)
{
throw new JRRuntimeException(e);
}
}
@Override
public void visit(JRPrintLine line, TableCell cell)
{
try
{
writeLine(line, cell);
}
catch (IOException e)
{
throw new JRRuntimeException(e);
}
}
@Override
public void visit(JRPrintEllipse ellipse, TableCell cell)
{
try
{
writeEllipse(ellipse, cell);
}
catch (IOException e)
{
throw new JRRuntimeException(e);
}
}
@Override
public void visit(JRPrintFrame frame, TableCell cell)
{
throw
new JRRuntimeException(
EXCEPTION_MESSAGE_KEY_INTERNAL_ERROR,
(Object[])null
);
}
@Override
public void visit(JRGenericPrintElement printElement, TableCell cell)
{
try
{
writeGenericElement(printElement, cell);
}
catch (IOException e)
{
throw new JRRuntimeException(e);
}
}
}
protected class ExporterContext extends BaseExporterContext implements JRHtmlExporterContext
{
@Override
public String getHyperlinkURL(JRPrintHyperlink link)
{
return HtmlExporter.this.getHyperlinkURL(link);
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy