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 - 2022 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.NestedTableCell;
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.TableCell.CellType;
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.ExifOrientationEnum;
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.engine.util.StyledTextListWriter;
import net.sf.jasperreports.engine.util.StyledTextWriteContext;
import net.sf.jasperreports.export.AccessibilityUtil;
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.AccessibilityTagEnum;
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<>();
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("\n");
writer.write(" \n");
writer.write("\n");
}
else
{
writer.write(htmlHeader);
}
List items = exporterInput.getItems();
for(reportIndex = 0; reportIndex < items.size(); reportIndex++)
{
ExporterInputItem item = items.get(reportIndex);
setCurrentExporterInputItem(item);
List pages = jasperPrint.getPages();
if (pages != null && pages.size() > 0)
{
PageRange pageRange = getPageRange();
int startPageIndex = (pageRange == null || pageRange.getStartPageIndex() == null) ? 0 : pageRange.getStartPageIndex();
int endPageIndex = (pageRange == null || pageRange.getEndPageIndex() == null) ? (pages.size() - 1) : pageRange.getEndPageIndex();
JRPrintPage page = null;
for(pageIndex = startPageIndex; pageIndex <= endPageIndex; pageIndex++)
{
if (Thread.interrupted())
{
throw new ExportInterruptedException();
}
page = pages.get(pageIndex);
/* */
exportPage(page);
if (reportIndex < items.size() - 1 || pageIndex < endPageIndex)
{
if (betweenPagesHtml == null)
{
writer.write("
\n
\n");
}
else
{
writer.write(betweenPagesHtml);
}
}
writer.write("\n");
}
}
}
ReportContext reportContext = getReportContext();
if (fontsToProcess != null && fontsToProcess.size() > 0)// when no resourceHandler, fonts are not processed
{
if (reportContext == null)
{
@SuppressWarnings("deprecation")
HtmlResourceHandler fontHandler =
getExporterOutput().getFontHandler() == null
? getFontHandler()
: getExporterOutput().getFontHandler();
if (fontHandler == null)
{
@SuppressWarnings("deprecation")
HtmlResourceHandler resourceHandler =
getExporterOutput().getResourceHandler() == null
? getResourceHandler()
: getExporterOutput().getResourceHandler();
fontHandler = resourceHandler;
}
for (HtmlFontFamily htmlFontFamily : fontsToProcess.values())
{
// the fontHandler is used only here, to point to the URL that generates the dynamic CSS for static HTML export in server environments;
// in non server environments, the static HTML saved to file system uses the static resource handler to point to the font CSS file,
// which was also saved using a static resource handler
writer.write("\n");
}
// generate script tag on static export only
writer.write("\n");
}
else
{
reportContext.setParameterValue(JsonExporter.REPORT_CONTEXT_PARAMETER_WEB_FONTS, fontsToProcess);
}
}
// place hyperlinksData on reportContext
if (hyperlinksData.size() > 0)
{
//for sure reportContext is not null, because otherwise there would be no item in the hyperilnkData
reportContext.setParameterValue("net.sf.jasperreports.html.hyperlinks", hyperlinksData);
}
if (htmlFooter == null)
{
writer.write(" \n");
writer.write("
\n");
writer.write("\n");
writer.write("\n");
}
else
{
writer.write(htmlFooter);
}
if (flushOutput)
{
writer.flush();
}
}
protected void exportPage(JRPrintPage page) throws IOException
{
HtmlReportConfiguration configuration = getCurrentItemConfiguration();
Tabulator tabulator = new Tabulator(tableFilter, page.getElements(), configuration.isAccessibleHtml());
tabulator.tabulate(getOffsetX(), getOffsetY());
boolean isIgnorePageMargins = configuration.isIgnorePageMargins();
if (!isIgnorePageMargins)
{
PrintPageFormat pageFormat = jasperPrint.getPageFormat(pageIndex);
tabulator.addMargins(pageFormat.getPageWidth(), pageFormat.getPageHeight());
}
Table table = tabulator.getTable();
boolean isWhitePageBackground = configuration.isWhitePageBackground();
if (isWhitePageBackground)
{
setBackcolor(Color.white);
}
CellElementVisitor elementVisitor = new CellElementVisitor();
TableVisitor tableVisitor = new TableVisitor(tabulator, elementVisitor);
exportTable(tableVisitor, table, isWhitePageBackground, true);
if (isWhitePageBackground)
{
restoreBackcolor();
}
JRExportProgressMonitor progressMonitor = configuration.getProgressMonitor();
if (progressMonitor != null)
{
progressMonitor.afterPageExport();
}
}
public void exportElements(List elements) throws IOException
{
Tabulator tabulator = new Tabulator(tableFilter, elements, getCurrentItemConfiguration().isAccessibleHtml());
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;
}
String tableId = null;
if (isMainReportTable)
{
int totalWidth = columns.last().getEndCoord() - columns.first().getStartCoord();
int totalHeight = rows.last().getEndCoord() - rows.first().getStartCoord();
tableId = JR_PAGE_ANCHOR_PREFIX + reportIndex + "_" + (pageIndex + 1);
writer.write("\n");
if (isMainReportTable)
{
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(""); // tags must have content (be closed with separate closing tag), otherwise browsers will make them wrap around the next tag
}
if (text.getBookmarkLevel() != JRAnchor.NO_BOOKMARK)
{
writer.write(""); // tags must have content (be closed with separate closing tag), otherwise browsers will make them wrap around the next tag
}
if (rotationValue != null)
{
writer.write("\n");
writer.write("");
writer.write("");
}
AccessibilityTagEnum headingTag = startHeading(text);
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();
}
endHeading(headingTag);
if (rotationValue != null)
{
writer.write("");
}
endCell(cell.getCellType());
}
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(";");
}
public 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(""); // tags must have content (be closed with separate closing tag), otherwise browsers will make them wrap around the next tag
}
if (image.getBookmarkLevel() != JRAnchor.NO_BOOKMARK)
{
writer.write(""); // tags must have content (be closed with separate closing tag), otherwise browsers will make them wrap around the next tag
}
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();
}
ExifOrientationEnum exifOrientation = ExifOrientationEnum.NORMAL;
DataRenderable dataRenderable = renderer instanceof DataRenderable ? (DataRenderable)renderer : null;
if (dataRenderable != null)
{
exifOrientation = ImageUtil.getExifOrientation(dataRenderable.getData(getJasperReportsContext()));
}
if (
ExifOrientationEnum.LEFT == exifOrientation
|| ExifOrientationEnum.RIGHT == exifOrientation
)
{
double t = normalWidth;
normalWidth = normalHeight;
normalHeight = t;
}
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 + "\"");
}
if (hasHyperlinks)
{
writer.write(" border=\"0\"");
}
if (image.getHyperlinkTooltip() != null)
{
String tooltip = JRStringUtil.encodeXmlAttribute(image.getHyperlinkTooltip());
writer.write(" alt=\"");
writer.write(tooltip);
writer.write("\"");
writer.write(" title=\"");
writer.write(tooltip);
writer.write("\"");
}
else
{
writer.write(" alt=\"\""); // screen readers do not read images with empty alt attribute, which are considered just decorations
}
writer.write("/>");
}
}
if (hyperlinkStarted)
{
endHyperlink();
}
if (useDiv)
{
writer.write(" ");
}
if (imageMapAreas != null)
{
writer.write("\n");
writeImageMap(imageMapName, image, imageMapAreas);
}
}
endCell(cell.getCellType());
}
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(cell.getCellType());
}
protected void writeEllipse(JRPrintEllipse ellipse, TableCell cell) throws IOException
{
startCell(ellipse, cell);
finishStartCell();
float lineDiff = ellipse.getLinePen().getLineWidth() / 2;
writer.write("");
endCell(cell.getCellType());
}
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(cell.getCellType());
}
protected boolean isOblique(JRPrintLine line){
return line.getWidth() > 1 && line.getHeight() > 1;
}
protected void writeGenericElement(JRGenericPrintElement element, TableCell cell) throws IOException, JRException
{
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
{
String htmlFragment = handler.getHtmlFragment(exporterContext, element);
if (htmlFragment == null)
{
handler.exportElement(exporterContext, element, cell);
}
else
{
startCell(element, cell);
StringBuilder styleBuffer = new StringBuilder();
appendElementCellGenericStyle(cell, styleBuffer);
appendBackcolorStyle(cell, styleBuffer);
appendBorderStyle(cell.getBox(), styleBuffer);
writeStyle(styleBuffer);
finishStartCell();
writer.write(htmlFragment);
endCell(cell.getCellType());
}
}
}
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(cell.getCellType());
}
protected void writeNestedTable(Table table, TableVisitor tableVisitor, TableCell cell) throws IOException
{
startCell(cell);
StringBuilder styleBuffer = new StringBuilder();
appendElementCellGenericStyle(cell, styleBuffer);
appendBackcolorStyle(cell, styleBuffer);
appendBorderStyle(cell.getBox(), styleBuffer);
appendPaddingStyle(cell.getBox(), styleBuffer);
writeStyle(styleBuffer);
finishStartCell();
exportTable(tableVisitor, table, false, false);
endCell(cell.getCellType());
}
protected void startCell(JRPrintElement element, TableCell cell) throws IOException
{
startCell(cell.getColumnSpan(), cell.getRowSpan(), cell.getCellType());
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, CellType cellType) throws IOException
{
String tag = "td";
String role = null;
if (cellType == CellType.COLUMN_HEADER)
{
tag = "th";
role = "columnheader";
}
else if (cellType == CellType.ROW_HEADER)
{
tag = "th";
role = "rowheader";
}
writer.write("<");
writer.write(tag);
if (role != null)
{
writer.write(" role=\"");
writer.write(role);
writer.write("\"");
}
if (colSpan > 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(CellType cellType) throws IOException
{
if (
cellType == CellType.COLUMN_HEADER
|| cellType == CellType.ROW_HEADER
)
{
writer.write("\n");
}
else
{
writer.write("\n");
}
}
protected void writeEmptyCell(int colSpan, int rowSpan) throws IOException
{
startCell(colSpan, rowSpan, null);
finishStartCell();
endCell(null);
}
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(cell.getCellType());
}
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 AccessibilityTagEnum startHeading(JRPrintText text) throws IOException
{
AccessibilityTagEnum headingTag = null;
if (text.getPropertiesMap().containsProperty(AccessibilityUtil.PROPERTY_ACCESSIBILITY_TAG))
{
AccessibilityTagEnum accessibilityTag =
AccessibilityTagEnum.getByName(
text.getPropertiesMap().getProperty(AccessibilityUtil.PROPERTY_ACCESSIBILITY_TAG)
);
if (accessibilityTag != null && accessibilityTag.name().startsWith("H"))
{
headingTag = accessibilityTag;
writer.write("<");
writer.write(headingTag.getName());
writer.write(" style=\"margin:0\">");
}
}
return headingTag;
}
protected void endHeading(AccessibilityTagEnum headingTag) throws IOException
{
if (headingTag != null)
{
writer.write("");
}
}
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
{
StyledTextWriteContext context = new StyledTextWriteContext();
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(
context,
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(
context,
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(
StyledTextWriteContext context,
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();
Integer leftIndent = printText.getParagraph().getLeftIndent();
Integer rightIndent = printText.getParagraph().getRightIndent();
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
|| (leftIndent != null && leftIndent > 0)
|| (rightIndent != null && rightIndent > 0)
)
{
writer.write(" 0)
{
writer.write("padding-left:" + leftIndent + "px;");
}
if (rightIndent != null && rightIndent > 0)
{
writer.write("padding-right:" + rightIndent + "px;");
}
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("");
}
String textRunStyle =
getTextRunStyle(
attributes,
locale,
lineSpacing,
lineSpacingSize,
lineSpacingFactor,
backcolor,
hyperlinkStarted
);
String runText = paragraphText.substring(paragraph.getIndex(), runLimit);
context.next(attributes, runText);
if (context.listItemStartsWithNewLine() && !context.isListItemStart() && (context.isListItemEnd() || context.isListStart() || context.isListEnd()))
{
runText = runText.substring(1);
}
context.writeLists(new HtmlStyledTextListWriter(textRunStyle));
exportStyledTextRun(
textRunStyle,
attributes,
runText,
tooltip,
hyperlinkStarted
);
paragraph.setIndex(runLimit);
}
context.next(null, null);
context.writeLists(new HtmlStyledTextListWriter(null));
if (highlightStarted) {
writer.write(" ");
}
if (startedSpan)
{
writer.write("");
}
if (firstLineIndent != null || justifyLastLine ||
(leftIndent != null && leftIndent > 0) ||
(rightIndent != null && rightIndent > 0)
)
{
writer.write("");
}
}
protected void exportStyledTextRun(
String textRunStyle,
Map attributes,
String text,
String tooltip,
boolean hyperlinkStarted
) throws IOException
{
boolean localHyperlink = false;
JRPrintHyperlink hyperlink = (JRPrintHyperlink)attributes.get(JRTextAttribute.HYPERLINK);
if (!hyperlinkStarted && hyperlink != null)
{
localHyperlink = startHyperlink(hyperlink);
}
writer.write("");
StringTokenizer tkzer = new StringTokenizer(text, "\n\u0085", true);
// text is split into paragraphs here because it might have not been split during initial styled text processing
// and was processed as a whole because there was no need to do so due to lack of paragraph styling;
// htmlEncode(String) no longer takes care of newline characters, so we do it here
while(tkzer.hasMoreTokens())
{
String token = tkzer.nextToken();
if ("\n".equals(token))
{
writer.write("
");
}
else if ("\u0085".equals(token))
{
writer.write("
");
}
else
{
writer.write(
JRStringUtil.htmlEncode(token)
);
}
}
writer.write("");
if (localHyperlink)
{
endHyperlink();
}
}
protected String getTextRunStyle(
Map attributes,
Locale locale,
LineSpacingEnum lineSpacing,
Float lineSpacingSize,
float lineSpacingFactor,
Color backcolor,
boolean hyperlinkStarted
) throws IOException
{
StringBuilder styleBuffer = new StringBuilder();
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
styleBuffer.append("font-family: ");
// don't encode single quotes as the output would be too verbose and too much of a chance compared to previous releases
styleBuffer.append(JRStringUtil.encodeXmlAttribute(fontFamily, true));
styleBuffer.append("; ");
Color forecolor = (Color)attributes.get(TextAttribute.FOREGROUND);
if (!hyperlinkStarted || !Color.black.equals(forecolor))
{
styleBuffer.append("color: ");
styleBuffer.append(JRColorUtil.getCssColor(forecolor));
styleBuffer.append("; ");
}
Color runBackcolor = (Color)attributes.get(TextAttribute.BACKGROUND);
if (runBackcolor != null && !runBackcolor.equals(backcolor))
{
styleBuffer.append("background-color: ");
styleBuffer.append(JRColorUtil.getCssColor(runBackcolor));
styleBuffer.append("; ");
}
styleBuffer.append("font-size: ");
styleBuffer.append(toSizeUnit((Float)attributes.get(TextAttribute.SIZE)));
styleBuffer.append(";");
switch (lineSpacing)
{
case ONE_AND_HALF:
{
if (lineSpacingFactor == 0)
{
styleBuffer.append(" line-height: 1.5;");
}
else
{
styleBuffer.append(" line-height: " + lineSpacingFactor + ";");
}
break;
}
case DOUBLE:
{
if (lineSpacingFactor == 0)
{
styleBuffer.append(" line-height: 2.0;");
}
else
{
styleBuffer.append(" line-height: " + lineSpacingFactor + ";");
}
break;
}
case PROPORTIONAL:
{
if (lineSpacingSize != null) {
styleBuffer.append(" line-height: " + lineSpacingSize + ";");
}
break;
}
case AT_LEAST:
case FIXED:
{
if (lineSpacingSize != null) {
styleBuffer.append(" line-height: " + lineSpacingSize + "px;");
}
break;
}
case SINGLE:
default:
{
if (lineSpacingFactor == 0)
{
styleBuffer.append(" line-height: 1; *line-height: normal;");
}
else
{
styleBuffer.append(" line-height: " + lineSpacingFactor + ";");
}
break;
}
}
/*
if (!horizontalAlignment.equals(CSS_TEXT_ALIGN_LEFT))
{
styleBuffer.append(" text-align: ");
styleBuffer.append(horizontalAlignment);
styleBuffer.append(";");
}
*/
if (isBold)
{
styleBuffer.append(" font-weight: bold;");
}
if (isItalic)
{
styleBuffer.append(" font-style: italic;");
}
if (TextAttribute.UNDERLINE_ON.equals(attributes.get(TextAttribute.UNDERLINE)))
{
styleBuffer.append(" text-decoration: underline;");
}
if (TextAttribute.STRIKETHROUGH_ON.equals(attributes.get(TextAttribute.STRIKETHROUGH)))
{
styleBuffer.append(" text-decoration: line-through;");
}
if (TextAttribute.SUPERSCRIPT_SUPER.equals(attributes.get(TextAttribute.SUPERSCRIPT)))
{
styleBuffer.append(" vertical-align: super;");
}
else if (TextAttribute.SUPERSCRIPT_SUB.equals(attributes.get(TextAttribute.SUPERSCRIPT)))
{
styleBuffer.append(" vertical-align: sub;");
}
return styleBuffer.toString();
}
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;
}
@Override
public Void visit(NestedTableCell nestedTableCell, TablePosition position) throws IOException
{
TableCell tableCell = tabulator.getTableCell(position, nestedTableCell);
HtmlExporter.this.writeNestedTable(nestedTableCell.getTable(), 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 | 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 | JRException e)
{
throw new JRRuntimeException(e);
}
}
}
protected class ExporterContext extends BaseExporterContext implements JRHtmlExporterContext
{
@Override
public String getHyperlinkURL(JRPrintHyperlink link)
{
return HtmlExporter.this.getHyperlinkURL(link);
}
}
protected class HtmlStyledTextListWriter implements StyledTextListWriter
{
private String textRunStyle;
public HtmlStyledTextListWriter(String textRunStyle)
{
this.textRunStyle = textRunStyle;
}
@Override
public void startUl()
{
try
{
writer.write("");
}
catch (IOException e)
{
throw new JRRuntimeException(e);
}
}
@Override
public void endUl()
{
try
{
writer.write("
");
}
catch (IOException e)
{
throw new JRRuntimeException(e);
}
}
@Override
public void startOl(String type, int cutStart)
{
try
{
writer.write(" 1)
{
writer.write(" start=\"" + cutStart + "\"");
}
writer.write(">");
}
catch (IOException e)
{
throw new JRRuntimeException(e);
}
}
@Override
public void endOl()
{
try
{
writer.write("
");
}
catch (IOException e)
{
throw new JRRuntimeException(e);
}
}
@Override
public void startLi(boolean noBullet)
{
try
{
writer.write("");
}
catch (IOException e)
{
throw new JRRuntimeException(e);
}
}
@Override
public void endLi()
{
try
{
writer.write(" ");
}
catch (IOException e)
{
throw new JRRuntimeException(e);
}
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy