All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.jasperreports.engine.export.HtmlExporter Maven / Gradle / Ivy

There is a newer version: 6.21.3
Show newest version
/*
 * 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"); 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"); } 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(" 0) { double normalWidth = availableImageWidth; double normalHeight = availableImageHeight; Dimension2D dimension = imageProcessorResult.dimension; if (dimension != null) { normalWidth = dimension.getWidth(); normalHeight = dimension.getHeight(); writer.write(" viewBox=\"0 0 "); writer.write(String.valueOf(normalWidth)); writer.write(" "); writer.write(String.valueOf(normalHeight)); writer.write("\""); } double ratio = normalWidth / normalHeight; if ( ratio > (double)availableImageWidth / (double)availableImageHeight ) { writer.write(" width=\""); writer.write(String.valueOf(availableImageWidth)); writer.write("\""); } else { writer.write(" height=\""); writer.write(String.valueOf(availableImageHeight)); writer.write("\""); } } } } writer.write(">\n"); writer.write(imageProcessorResult.imageSource); 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"); for (ListIterator it = imageMapAreas.listIterator(imageMapAreas.size()); it.hasPrevious();) { JRPrintImageAreaHyperlink areaHyperlink = it.previous(); JRPrintHyperlink link = areaHyperlink.getHyperlink(); JRPrintImageArea area = areaHyperlink.getArea(); writer.write(" \n"); } if (image.getHyperlinkTypeValue() != HyperlinkTypeEnum.NONE) { writer.write(" \n"); } 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(""); 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(""); 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(""); 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); } } } }