Java tutorial
/************************************************************************************* * Copyright (c) 2011, 2012, 2013 James Talbut. * jim-emitters@spudsoft.co.uk * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * James Talbut - Initial implementation. ************************************************************************************/ package uk.co.spudsoft.birt.emitters.excel; import java.awt.font.FontRenderContext; import java.awt.font.LineBreakMeasurer; import java.awt.font.TextAttribute; import java.awt.font.TextLayout; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URLConnection; import java.text.AttributedString; import java.text.DateFormat; import java.util.Collection; import java.util.List; import java.util.Locale; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellStyle; import org.apache.poi.ss.usermodel.DataFormat; import org.apache.poi.ss.usermodel.Font; import org.apache.poi.ss.usermodel.PrintSetup; import org.apache.poi.ss.usermodel.RichTextString; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.ss.util.CellRangeAddress; import org.apache.poi.xssf.usermodel.extensions.XSSFCellBorder.BorderSide; import org.eclipse.birt.report.engine.content.IPageContent; import org.eclipse.birt.report.engine.css.engine.StyleConstants; import org.eclipse.birt.report.engine.css.engine.value.DataFormatValue; import org.eclipse.birt.report.engine.css.engine.value.StringValue; import org.eclipse.birt.report.engine.css.engine.value.css.CSSConstants; import org.eclipse.birt.report.engine.ir.DimensionType; import org.eclipse.birt.report.model.api.util.ColorUtil; import org.w3c.dom.css.CSSValue; import uk.co.spudsoft.birt.emitters.excel.framework.Logger; /** * <p> * StyleManagerUtils contains methods implementing the details of converting BIRT styles to POI styles. * </p><p> * StyleManagerUtils is abstract to support a small number of methods that require HSSF/XSSF specific implementations. * * @author Jim Talbut * */ public abstract class StyleManagerUtils { protected Logger log; protected static final FontRenderContext frc = new FontRenderContext(null, true, true); public interface Factory { StyleManagerUtils create(Logger log); } /** * @param log * The Logger to use for any information reports to be made. */ public StyleManagerUtils(Logger log) { this.log = log; } /** * Create a RichTextString representing a given string. * @param value * The string to represent in the RichTextString. * @return * A RichTextString representing value. */ public abstract RichTextString createRichTextString(String value); /** * Compare two objects in a null-safe manner. * @param lhs * The first object to compare. * @param rhs * The second object to compare. * @return * true is both objects are null or lhs.equals(rhs), otherwise false. */ public static boolean objectsEqual(Object lhs, Object rhs) { return (lhs == null) ? (rhs == null) : lhs.equals(rhs); } public static boolean dataFormatsEquivalent(DataFormatValue dataFormat1, DataFormatValue dataFormat2) { if (dataFormat1 == null) { return (dataFormat2 == null); } if (dataFormat2 == null) { return false; } if (!objectsEqual(dataFormat1.getNumberPattern(), dataFormat2.getNumberPattern()) || !objectsEqual(dataFormat1.getDatePattern(), dataFormat2.getDatePattern()) || !objectsEqual(dataFormat1.getDateTimePattern(), dataFormat2.getDateTimePattern()) || !objectsEqual(dataFormat1.getTimePattern(), dataFormat2.getTimePattern())) { return false; } return true; } /** * Convert a BIRT text alignment string into a POI CellStyle constant. * @param alignment * The BIRT alignment string. * @return * One of the CellStyle.ALIGN* constants. */ public short poiAlignmentFromBirtAlignment(String alignment) { if (CSSConstants.CSS_LEFT_VALUE.equals(alignment)) { return CellStyle.ALIGN_LEFT; } if (CSSConstants.CSS_RIGHT_VALUE.equals(alignment)) { return CellStyle.ALIGN_RIGHT; } if (CSSConstants.CSS_CENTER_VALUE.equals(alignment)) { return CellStyle.ALIGN_CENTER; } return CellStyle.ALIGN_GENERAL; } /** * Convert a BIRT font size string (either a dimensioned string or "xx-small" - "xx-large") to a point size. * @param fontSize * The BIRT font size. * @return * An appropriate size in points. */ public short fontSizeInPoints(String fontSize) { if (fontSize == null) { return 11; } if ("xx-small".equals(fontSize)) { return 6; } else if ("x-small".equals(fontSize)) { return 8; } else if ("small".equals(fontSize)) { return 10; } else if ("medium".equals(fontSize)) { return 11; } else if ("large".equals(fontSize)) { return 14; } else if ("x-large".equals(fontSize)) { return 18; } else if ("xx-large".equals(fontSize)) { return 24; } else if ("smaller".equals(fontSize)) { return 10; } else if ("larger".equals(fontSize)) { return 14; } DimensionType dim = DimensionType.parserUnit(fontSize, "pt"); // log.debug( "fontSize: \"", fontSize, "\", parses as: \"", dim.toString(), "\" (", dim.getMeasure(), " ", dim.getUnits(), ")"); if (DimensionType.UNITS_PX.equals(dim.getUnits())) { double px = dim.getMeasure(); double inches = px / 96; double points = 72 * inches; return (short) points; } else if (DimensionType.UNITS_EM.equals(dim.getUnits())) { return (short) (12 * dim.getMeasure()); } else if (DimensionType.UNITS_PERCENTAGE.equals(dim.getUnits())) { return (short) (12 * dim.getMeasure() / 100.0); } else { double points = dim.convertTo(DimensionType.UNITS_PT); return (short) points; } } /** * Obtain a POI column width from a BIRT DimensionType. * @param dim * The BIRT dimension, which must be in absolute units. * @return * The column with in width units, or zero if a suitable conversion could not be performed. */ public int poiColumnWidthFromDimension(DimensionType dim) { if (dim != null) { double mmWidth = dim.getMeasure(); if ((DimensionType.UNITS_CM.equals(dim.getUnits())) || (DimensionType.UNITS_IN.equals(dim.getUnits())) || (DimensionType.UNITS_PT.equals(dim.getUnits())) || (DimensionType.UNITS_PC.equals(dim.getUnits()))) { mmWidth = dim.convertTo("mm"); } int result = ClientAnchorConversions.millimetres2WidthUnits(mmWidth); // log.debug( "Column width in mm: ", mmWidth, "; converted result: ", result ); return result; } else { return 0; } } /** * Object a POI font weight from a BIRT string. * @param fontWeight * The font weight as understood by BIRT. * @return * One of the Font.BOLDWEIGHT_* constants. */ public short poiFontWeightFromBirt(String fontWeight) { if (fontWeight == null) { return 0; } if ("bold".equals(fontWeight)) { return Font.BOLDWEIGHT_BOLD; } return Font.BOLDWEIGHT_NORMAL; } /** * Convert a BIRT font name into a system font name. * <br> * Just returns the passed in name unless that is a known family name ("serif" or "sans-serif"). * @param fontName * The font name from BIRT. * @return * A real font name. */ public String poiFontNameFromBirt(String fontName) { if ("serif".equals(fontName)) { return "Times New Roman"; } else if ("sans-serif".equals(fontName)) { return "Arial"; } else if ("monospace".equals(fontName)) { return "Courier New"; } return fontName; } /** * <p> * Add a colour (specified as "rgb(<i>r</i>, <i>g</i>, <i>b</i>)") to a Font. * </p><p> * In the current implementations the XSSF implementation will always produce exactly the right colour, * whilst the HSSF implementation takes the best approximation from the current palette. * @param workbook * The workbook in which the Font is to be used, needed to obtain the colour palette. * @param font * The font to which the colour is to be added. * @param colour * The colour to add. */ public abstract void addColourToFont(Workbook workbook, Font font, String colour); /** * <p> * Add a colour (specified as "rgb(<i>r</i>, <i>g</i>, <i>b</i>)") as the background colour of a CellStyle. * </p><p> * In the current implementations the XSSF implementation will always produce exactly the right colour, * whilst the HSSF implementation takes the best approximation from the current palette. * @param workbook * The workbook in which the Font is to be used, needed to obtain the colour palette. * @param style * The style to which the colour is to be added. * @param colour * The colour to add. */ public abstract void addBackgroundColourToStyle(Workbook workbook, CellStyle style, String colour); /** * Check whether a cell is empty and unformatted. * @param cell * The cell to consider. * @return * true is the cell is empty and has no style or has no background fill. */ public static boolean cellIsEmpty(Cell cell) { if (cell.getCellType() != Cell.CELL_TYPE_BLANK) { return false; } CellStyle cellStyle = cell.getCellStyle(); if (cellStyle == null) { return true; } if (cellStyle.getFillPattern() == CellStyle.NO_FILL) { return true; } return false; } /** * Apply a BIRT border style to one side of a POI CellStyle. * @param workbook * The workbook that contains the cell being styled. * @param style * The POI CellStyle that is to have the border applied to it. * @param side * The side of the border that is to be applied.<br> * Note that although this value is from XSSFCellBorder it is equally valid for HSSFCellStyles. * @param colour * The colour for the new border. * @param borderStyle * The BIRT style for the new border. * @param width * The width of the new border. */ public abstract void applyBorderStyle(Workbook workbook, CellStyle style, BorderSide side, CSSValue colour, CSSValue borderStyle, CSSValue width); /** * <p> * Convert a MIME string into a Workbook.PICTURE* constant. * </p><p> * In some cases BIRT fails to submit a MIME string, in which case this method falls back to basic data signatures for JPEG and PNG images. * <p> * @param mimeType * The MIME type. * @param data * The image data to consider if no recognisable MIME type is provided. * @return * A Workbook.PICTURE* constant. */ public int poiImageTypeFromMimeType(String mimeType, byte[] data) { if ("image/jpeg".equals(mimeType)) { return Workbook.PICTURE_TYPE_JPEG; } else if ("image/png".equals(mimeType)) { return Workbook.PICTURE_TYPE_PNG; } else { if (null != data) { log.debug("Data bytes: " + " " + Integer.toHexString(data[0]).toUpperCase() + " " + Integer.toHexString(data[1]).toUpperCase() + " " + Integer.toHexString(data[2]).toUpperCase() + " " + Integer.toHexString(data[3]).toUpperCase()); if ((data.length > 2) && (data[0] == (byte) 0xFF) && (data[1] == (byte) 0xD8) && (data[2] == (byte) 0xFF)) { return Workbook.PICTURE_TYPE_JPEG; } if ((data.length > 4) && (data[0] == (byte) 0x89) && (data[1] == (byte) 'P') && (data[2] == (byte) 'N') && (data[3] == (byte) 'G')) { return Workbook.PICTURE_TYPE_PNG; } } return 0; } } /** * Read an InputStream in full and put the results into a byte[]. * <br> * This is needed by the emitter to handle images accessed by URL. * @param stream * The InputStream to read. * @param length * The length of the InputStream * @return * A byte array containing the contents of the InputStream. * @throws IOException */ public byte[] streamToByteArray(InputStream stream, int length) throws IOException { ByteArrayOutputStream buffer; if (length > 0) { buffer = new ByteArrayOutputStream(length); } else { buffer = new ByteArrayOutputStream(); } int nRead; byte[] data = new byte[16384]; while ((nRead = stream.read(data, 0, data.length)) != -1) { buffer.write(data, 0, nRead); } buffer.flush(); return buffer.toByteArray(); } /** * Read an image from a URLConnection into a byte array. * @param conn * The URLConnection to provide the data. * @return * A byte array containing the data downloaded from the URL. */ public byte[] downloadImage(URLConnection conn) { try { int contentLength = conn.getContentLength(); InputStream imageStream = conn.getInputStream(); try { return streamToByteArray(imageStream, contentLength); } finally { imageStream.close(); } } catch (MalformedURLException ex) { log.debug(ex.getClass(), ": ", ex.getMessage()); return null; } catch (IOException ex) { log.debug(ex.getClass(), ": ", ex.getMessage()); return null; } } /** * Convert a BIRT paper size string into a POI PrintSetup.*PAPERSIZE constant. * @param name * The paper size as a BIRT string. * @return * A POI PrintSetup.*PAPERSIZE constant. */ public short getPaperSizeFromString(String name) { if ("a4".equals(name)) { return PrintSetup.A4_PAPERSIZE; } else if ("a3".equals(name)) { return PrintSetup.A3_PAPERSIZE; } else if ("us-letter".equals(name)) { return PrintSetup.LETTER_PAPERSIZE; } return PrintSetup.A4_PAPERSIZE; } /** * Check whether a DimensionType represents an absolute (physical) dimension. * @param dim * The DimensionType to consider. * @return * true if dim represents an absolute measurement. */ public boolean isAbsolute(DimensionType dim) { if (dim == null) { return false; } String units = dim.getUnits(); return DimensionType.UNITS_CM.equals(units) || DimensionType.UNITS_IN.equals(units) || DimensionType.UNITS_MM.equals(units) || DimensionType.UNITS_PT.equals(units) || DimensionType.UNITS_PC.equals(units); } /** * Check whether a DimensionType represents pixels. * @param dim * The DimensionType to consider. * @return * true if dim represents pixels. */ public boolean isPixels(DimensionType dim) { return (dim != null) && DimensionType.UNITS_PX.equals(dim.getUnits()); } /** * <p> * Convert a BIRT number format to a POI data format. * </p><p> * There is no way this function is complete! More special cases will be added as they are found. * </p> * @param birtFormat * A string representing a number format in BIRT. * @return * A string representing a data format in Excel. */ private String poiNumberFormatFromBirt(String birtFormat) { if ("General Number".equalsIgnoreCase(birtFormat)) { return null; } if (birtFormat.startsWith(ExcelEmitter.CUSTOM_NUMBER_FORMAT)) { return birtFormat.substring(ExcelEmitter.CUSTOM_NUMBER_FORMAT.length()); } birtFormat = birtFormat.replace("E00", "E+00"); birtFormat = birtFormat.replaceAll("^([^0#.\\-,E;%\u2030\u00A4']*)", "\"$1\""); int brace = birtFormat.indexOf('{'); if (brace >= 0) { birtFormat = birtFormat.substring(0, brace); } return birtFormat; } /** * <p> * Convert a BIRT date/time format to a POI data format. * </p><p> * This function is likely to be more complete than poiNumberFormatFromBirt, but it is still likely to have issues. * More special cases will be added as they are found. * </p> * @param birtFormat * A string representing a date/time format in BIRT. * @return * A string representing a data format in Excel. */ private String poiDateTimeFormatFromBirt(String birtFormat, Locale locale) { if ("General Date".equalsIgnoreCase(birtFormat)) { birtFormat = DateFormatConverter.getJavaDateTimePattern(DateFormat.LONG, locale); } if ("Long Date".equalsIgnoreCase(birtFormat)) { birtFormat = DateFormatConverter.getJavaDatePattern(DateFormat.LONG, locale); } if ("Medium Date".equalsIgnoreCase(birtFormat)) { birtFormat = DateFormatConverter.getJavaDatePattern(DateFormat.MEDIUM, locale); } if ("Short Date".equalsIgnoreCase(birtFormat)) { birtFormat = DateFormatConverter.getJavaDatePattern(DateFormat.SHORT, locale); } if ("Long Time".equalsIgnoreCase(birtFormat)) { birtFormat = DateFormatConverter.getJavaTimePattern(DateFormat.LONG, locale); } if ("Medium Time".equalsIgnoreCase(birtFormat)) { birtFormat = DateFormatConverter.getJavaTimePattern(DateFormat.MEDIUM, locale); } if ("Short Time".equalsIgnoreCase(birtFormat)) { birtFormat = "kk:mm"; // DateFormatConverter.getJavaTimePattern(DateFormat.SHORT, locale); } return DateFormatConverter.convert(locale, birtFormat); } public static String getNumberFormat(BirtStyle style) { CSSValue dataFormat = style.getProperty(StyleConstants.STYLE_DATA_FORMAT); if (dataFormat instanceof DataFormatValue) { DataFormatValue dataFormatValue = (DataFormatValue) dataFormat; return dataFormatValue.getNumberPattern(); } return null; } public static String getDateFormat(BirtStyle style) { CSSValue dataFormat = style.getProperty(StyleConstants.STYLE_DATA_FORMAT); if (dataFormat instanceof DataFormatValue) { DataFormatValue dataFormatValue = (DataFormatValue) dataFormat; return dataFormatValue.getDatePattern(); } return null; } public static String getDateTimeFormat(BirtStyle style) { CSSValue dataFormat = style.getProperty(StyleConstants.STYLE_DATA_FORMAT); if (dataFormat instanceof DataFormatValue) { DataFormatValue dataFormatValue = (DataFormatValue) dataFormat; return dataFormatValue.getDateTimePattern(); } return null; } public static String getTimeFormat(BirtStyle style) { CSSValue dataFormat = style.getProperty(StyleConstants.STYLE_DATA_FORMAT); if (dataFormat instanceof DataFormatValue) { DataFormatValue dataFormatValue = (DataFormatValue) dataFormat; return dataFormatValue.getTimePattern(); } return null; } public static DataFormatValue cloneDataFormatValue(DataFormatValue dataValue) { DataFormatValue newValue = new DataFormatValue(); newValue.setDateFormat(dataValue.getDatePattern(), dataValue.getDateLocale()); newValue.setDateTimeFormat(dataValue.getDateTimePattern(), dataValue.getDateTimeLocale()); newValue.setTimeFormat(dataValue.getTimePattern(), dataValue.getTimeLocale()); newValue.setNumberFormat(dataValue.getNumberPattern(), dataValue.getNumberLocale()); newValue.setStringFormat(dataValue.getStringPattern(), dataValue.getStringLocale()); return newValue; } public static void setNumberFormat(BirtStyle style, String pattern, String locale) { DataFormatValue dfv = (DataFormatValue) style.getProperty(StyleConstants.STYLE_DATA_FORMAT); if (dfv == null) { dfv = new DataFormatValue(); } else { dfv = cloneDataFormatValue(dfv); } dfv.setNumberFormat(pattern, locale); style.setProperty(StyleConstants.STYLE_DATA_FORMAT, dfv); } public static void setDateFormat(BirtStyle style, String pattern, String locale) { DataFormatValue dfv = (DataFormatValue) style.getProperty(StyleConstants.STYLE_DATA_FORMAT); if (dfv == null) { dfv = new DataFormatValue(); } else { dfv = cloneDataFormatValue(dfv); } dfv.setDateFormat(pattern, locale); style.setProperty(StyleConstants.STYLE_DATA_FORMAT, dfv); } public static void setDateTimeFormat(BirtStyle style, String pattern, String locale) { DataFormatValue dfv = (DataFormatValue) style.getProperty(StyleConstants.STYLE_DATA_FORMAT); if (dfv == null) { dfv = new DataFormatValue(); } else { dfv = cloneDataFormatValue(dfv); } dfv.setDateTimeFormat(pattern, locale); style.setProperty(StyleConstants.STYLE_DATA_FORMAT, dfv); } public static void setTimeFormat(BirtStyle style, String pattern, String locale) { DataFormatValue dfv = (DataFormatValue) style.getProperty(StyleConstants.STYLE_DATA_FORMAT); if (dfv == null) { dfv = new DataFormatValue(); } else { dfv = cloneDataFormatValue(dfv); } dfv.setTimeFormat(pattern, locale); style.setProperty(StyleConstants.STYLE_DATA_FORMAT, dfv); } /** * Apply a BIRT number/date/time format to a POI CellStyle. * @param workbook * The workbook containing the CellStyle (needed to create a new DataFormat). * @param birtStyle * The BIRT style which may contain a number format. * @param poiStyle * The CellStyle that is to receive the number format. */ public void applyNumberFormat(Workbook workbook, BirtStyle birtStyle, CellStyle poiStyle, Locale locale) { String dataFormat = null; String format = getNumberFormat(birtStyle); if (format != null) { log.debug("BIRT number format == ", format); dataFormat = poiNumberFormatFromBirt(format); } else { format = getDateTimeFormat(birtStyle); if (format != null) { log.debug("BIRT date/time format == ", format); dataFormat = poiDateTimeFormatFromBirt(format, locale); } else { format = getTimeFormat(birtStyle); if (format != null) { log.debug("BIRT time format == ", format); dataFormat = poiDateTimeFormatFromBirt(format, locale); } else { format = getDateFormat(birtStyle); if (format != null) { log.debug("BIRT date format == ", format); dataFormat = poiDateTimeFormatFromBirt(format, locale); } } } } if (dataFormat != null) { DataFormat poiFormat = workbook.createDataFormat(); log.debug("Setting POI data format to ", dataFormat); poiStyle.setDataFormat(poiFormat.getFormat(dataFormat)); } } /** * Add font details to an AttributedString. * @param attrString * The AttributedString to modify. * @param font * The font to take attributes from. * @param startIdx * The index of the first character to be attributed (inclusive). * @param endIdx * The index of the last character to be attributed (inclusive). */ protected void addFontAttributes(AttributedString attrString, Font font, int startIdx, int endIdx) { attrString.addAttribute(TextAttribute.FAMILY, font.getFontName(), startIdx, endIdx); attrString.addAttribute(TextAttribute.SIZE, (float) font.getFontHeightInPoints(), startIdx, endIdx); if (font.getBoldweight() == Font.BOLDWEIGHT_BOLD) attrString.addAttribute(TextAttribute.WEIGHT, TextAttribute.WEIGHT_BOLD, startIdx, endIdx); if (font.getItalic()) attrString.addAttribute(TextAttribute.POSTURE, TextAttribute.POSTURE_OBLIQUE, startIdx, endIdx); if (font.getUnderline() == Font.U_SINGLE) attrString.addAttribute(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON, startIdx, endIdx); } /** * Find a RichTextRun that includes a specific index. * @param richTextRuns * The list of RichTextRuns to search. * @param startIndex * The character index being sought. * @return * The index into richTextRuns such that richTextRuns.get(index).startIndex has the largest value less that startIndex. */ protected int getRichTextRunIndexForStart(List<RichTextRun> richTextRuns, int startIndex) { if (richTextRuns.isEmpty()) { return -1; } for (int i = 0; i < richTextRuns.size(); ++i) { if (richTextRuns.get(i).startIndex >= startIndex) { return i - 1; } } return richTextRuns.size() - 1; } /** * Calculate the height of a string formatted according to a set of RichTextRuns and fitted within a give width. * @param sourceText * The string to be measured. * @param defaultFont * The font to be used prior to the first RichTextRun. * @param widthMM * The width of the output. * @param richTextRuns * The list of RichTextRuns to be applied to the string * @return * The heigh, in points, of a box big enough to contain the formatted sourceText. */ public float calculateTextHeightPoints(String sourceText, Font defaultFont, double widthMM, List<RichTextRun> richTextRuns) { log.debug("Calculating height for ", sourceText); final float widthPt = (float) (72 * Math.max(0, widthMM - 6) / 25.4); float totalHeight = 0; String[] textLines = sourceText.split("\n"); int lineStartIndex = 0; String lastLine = null; Font font = defaultFont; for (String textLine : textLines) { if (lastLine != null) { lineStartIndex += lastLine.length() + 1; } lastLine = textLine; AttributedString attrString = new AttributedString(textLine.isEmpty() ? " " : textLine); int runEnd = textLine.length(); int richTextRunIndex = getRichTextRunIndexForStart(richTextRuns, lineStartIndex); if (richTextRunIndex >= 0) { font = richTextRuns.get(richTextRunIndex).font; if ((richTextRunIndex < richTextRuns.size() - 1) && (richTextRuns.get(richTextRunIndex + 1).startIndex < runEnd)) { runEnd = richTextRuns.get(richTextRunIndex + 1).startIndex; } } log.debug("Adding attribute - [", 0, " - ", runEnd, "] = ", defaultFont.getFontName(), " ", defaultFont.getFontHeightInPoints(), "pt"); addFontAttributes(attrString, font, 0, textLine.isEmpty() ? 1 : runEnd); for (++richTextRunIndex; (richTextRunIndex < richTextRuns.size()) && (richTextRuns.get(richTextRunIndex).startIndex < lineStartIndex + textLine.length()); ++richTextRunIndex) { RichTextRun run = richTextRuns.get(richTextRunIndex); RichTextRun nextRun = richTextRunIndex < richTextRuns.size() - 1 ? richTextRuns.get(richTextRunIndex + 1) : null; if ((run.startIndex >= lineStartIndex) && (run.startIndex < lineStartIndex + textLine.length() + 1)) { int startIdx = run.startIndex - lineStartIndex; int endIdx = (nextRun == null ? sourceText.length() : nextRun.startIndex) - lineStartIndex; if (endIdx > textLine.length()) { endIdx = textLine.length(); } if (startIdx < endIdx) { log.debug("Adding attribute: [", startIdx, " - ", endIdx, "] = ", run.font.getFontName(), " ", run.font.getFontHeightInPoints(), "pt"); addFontAttributes(attrString, run.font, startIdx, endIdx); } } } LineBreakMeasurer measurer = new LineBreakMeasurer(attrString.getIterator(), frc); float heightAdjustment = 0.0F; int lineLength = textLine.isEmpty() ? 1 : textLine.length(); while (measurer.getPosition() < lineLength) { TextLayout layout = measurer.nextLayout(widthPt); float lineHeight = layout.getAscent() + layout.getDescent() + layout.getLeading(); if (layout.getDescent() + layout.getLeading() > heightAdjustment) { heightAdjustment = layout.getDescent() + layout.getLeading(); } log.debug("Line: ", textLine, " gives height ", lineHeight, "(", layout.getAscent(), "/", layout.getDescent(), "/", layout.getLeading(), ")"); totalHeight += lineHeight; } totalHeight += heightAdjustment; } log.debug("Height calculated as ", totalHeight); return totalHeight; } protected String contrastColour(int colour[]) { if ((colour[0] == 0) && (colour[1] == 0) && (colour[2] == 0)) { return "white"; } else { return "black"; } } protected int[] rgbOnly(int rgb[]) { if (rgb == null) { return new int[] { 0, 0, 0 }; } else if (rgb.length == 3) { return rgb; } else if (rgb.length > 3) { return new int[] { rgb[rgb.length - 3], rgb[rgb.length - 2], rgb[rgb.length - 1] }; } else if (rgb.length == 2) { return new int[] { rgb[0], rgb[1], 0 }; } else if (rgb.length == 2) { return new int[] { rgb[0], 0, 0 }; } else { return new int[] { 0, 0, 0 }; } } protected int[] rgbOnly(byte rgb[]) { if (rgb == null) { return new int[] { 0, 0, 0 }; } else if (rgb.length >= 3) { return new int[] { (int) rgb[rgb.length - 3] & 0xFF, (int) rgb[rgb.length - 2] & 0xFF, (int) rgb[rgb.length - 1] & 0xFF }; } else if (rgb.length == 2) { return new int[] { (int) rgb[0] & 0xFF, (int) rgb[1] & 0xFF, 0 }; } else if (rgb.length == 2) { return new int[] { (int) rgb[0] & 0xFF, 0, 0 }; } else { return new int[] { 0, 0, 0 }; } } int[] parseColour(String colour, String defaultColour) { if ((colour == null) || (CSSConstants.CSS_TRANSPARENT_VALUE.equals(colour)) || (CSSConstants.CSS_AUTO_VALUE.equals(colour))) { return rgbOnly(ColorUtil.getRGBs(defaultColour)); } else { return rgbOnly(ColorUtil.getRGBs(colour)); } } public abstract Font correctFontColorIfBackground(FontManager fm, Workbook wb, BirtStyle birtStyle, Font font); public void correctFontColorIfBackground(BirtStyle birtStyle) { CSSValue bgColour = birtStyle.getProperty(StyleConstants.STYLE_BACKGROUND_COLOR); CSSValue fgColour = birtStyle.getProperty(StyleConstants.STYLE_COLOR); int bgRgb[] = parseColour(bgColour == null ? null : bgColour.getCssText(), "white"); int fgRgb[] = parseColour(fgColour == null ? null : fgColour.getCssText(), "black"); if ((bgRgb[0] == fgRgb[0]) && (bgRgb[1] == fgRgb[1]) && (bgRgb[2] == fgRgb[2])) { CSSValue newColour = new StringValue(StringValue.CSS_STRING, contrastColour(bgRgb)); birtStyle.setProperty(StyleConstants.STYLE_COLOR, newColour); } } /** * Convert a horizontal position in a column (in mm) to a ClientAnchor DX position. * @param width * The position within the column. * @param colWidth * The width of the column. * @return * A value suitable for use as an argument to setDx2() on ClientAnchor. */ public abstract int anchorDxFromMM(double width, double colWidth); /** * Convert a vertical position in a row (in points) to a ClientAnchor DY position. * @param height * The position within the row. * @param rowHeight * The height of the row. * @return * A value suitable for use as an argument to setDy2() on ClientAnchor. * */ public abstract int anchorDyFromPoints(float height, float rowHeight); /** * Prepare the margin dimensions on the sheet as per the BIRT page. * @param page * The BIRT page. */ public abstract void prepareMarginDimensions(Sheet sheet, IPageContent page); /** * Place a border around a region on the current sheet. * This is used to apply borders to entire rows or entire tables. * @param colStart * The column marking the left-side boundary of the region. * @param colEnd * The column marking the right-side boundary of the region. * @param rowStart * The row marking the top boundary of the region. * @param rowEnd * The row marking the bottom boundary of the region. * @param borderStyle * The BIRT border style to apply to the region. */ public void applyBordersToArea(StyleManager sm, Sheet sheet, int colStart, int colEnd, int rowStart, int rowEnd, BirtStyle borderStyle) { StringBuilder borderMsg = new StringBuilder(); borderMsg.append("applyBordersToArea [").append(colStart).append(",").append(rowStart).append("]-[") .append(colEnd).append(",").append(rowEnd).append("]"); CSSValue borderStyleBottom = borderStyle.getProperty(StyleConstants.STYLE_BORDER_BOTTOM_STYLE); CSSValue borderWidthBottom = borderStyle.getProperty(StyleConstants.STYLE_BORDER_BOTTOM_WIDTH); CSSValue borderColourBottom = borderStyle.getProperty(StyleConstants.STYLE_BORDER_BOTTOM_COLOR); CSSValue borderStyleLeft = borderStyle.getProperty(StyleConstants.STYLE_BORDER_LEFT_STYLE); CSSValue borderWidthLeft = borderStyle.getProperty(StyleConstants.STYLE_BORDER_LEFT_WIDTH); CSSValue borderColourLeft = borderStyle.getProperty(StyleConstants.STYLE_BORDER_LEFT_COLOR); CSSValue borderStyleRight = borderStyle.getProperty(StyleConstants.STYLE_BORDER_RIGHT_STYLE); CSSValue borderWidthRight = borderStyle.getProperty(StyleConstants.STYLE_BORDER_RIGHT_WIDTH); CSSValue borderColourRight = borderStyle.getProperty(StyleConstants.STYLE_BORDER_RIGHT_COLOR); CSSValue borderStyleTop = borderStyle.getProperty(StyleConstants.STYLE_BORDER_TOP_STYLE); CSSValue borderWidthTop = borderStyle.getProperty(StyleConstants.STYLE_BORDER_TOP_WIDTH); CSSValue borderColourTop = borderStyle.getProperty(StyleConstants.STYLE_BORDER_TOP_COLOR); /* borderMsg.append( ", Bottom:" ).append( borderStyleBottom ).append( "/" ).append( borderWidthBottom ).append( "/" + borderColourBottom ); borderMsg.append( ", Left:" ).append( borderStyleLeft ).append( "/" ).append( borderWidthLeft ).append( "/" + borderColourLeft ); borderMsg.append( ", Right:" ).append( borderStyleRight ).append( "/" ).append( borderWidthRight ).append( "/" ).append( borderColourRight ); borderMsg.append( ", Top:" ).append( borderStyleTop ).append( "/" ).append( borderWidthTop ).append( "/" ).append( borderColourTop ); log.debug( borderMsg.toString() ); */ if ((borderStyleBottom == null) || (CSSConstants.CSS_NONE_VALUE.equals(borderStyleBottom)) || (borderWidthBottom == null) || ("0".equals(borderWidthBottom)) || (borderColourBottom == null) || (CSSConstants.CSS_TRANSPARENT_VALUE.equals(borderColourBottom.getCssText()))) { borderStyleBottom = null; borderWidthBottom = null; borderColourBottom = null; } if ((borderStyleLeft == null) || (CSSConstants.CSS_NONE_VALUE.equals(borderStyleLeft)) || (borderWidthLeft == null) || ("0".equals(borderWidthLeft)) || (borderColourLeft == null) || (CSSConstants.CSS_TRANSPARENT_VALUE.equals(borderColourLeft.getCssText()))) { borderStyleLeft = null; borderWidthLeft = null; borderColourLeft = null; } if ((borderStyleRight == null) || (CSSConstants.CSS_NONE_VALUE.equals(borderStyleRight)) || (borderWidthRight == null) || ("0".equals(borderWidthRight)) || (borderColourRight == null) || (CSSConstants.CSS_TRANSPARENT_VALUE.equals(borderColourRight.getCssText()))) { borderStyleRight = null; borderWidthRight = null; borderColourRight = null; } if ((borderStyleTop == null) || (CSSConstants.CSS_NONE_VALUE.equals(borderStyleTop)) || (borderWidthTop == null) || ("0".equals(borderWidthTop)) || (borderColourTop == null) || (CSSConstants.CSS_TRANSPARENT_VALUE.equals(borderColourTop.getCssText()))) { borderStyleTop = null; borderWidthTop = null; borderColourTop = null; } if ((borderStyleBottom != null) || (borderWidthBottom != null) || (borderColourBottom != null) || (borderStyleLeft != null) || (borderWidthLeft != null) || (borderColourLeft != null) || (borderStyleRight != null) || (borderWidthRight != null) || (borderColourRight != null) || (borderStyleTop != null) || (borderWidthTop != null) || (borderColourTop != null)) { for (int row = rowStart; row <= rowEnd; ++row) { Row styleRow = sheet.getRow(row); if (styleRow != null) { for (int col = colStart; col <= colEnd; ++col) { if ((col == colStart) || (col == colEnd) || (row == rowStart) || (row == rowEnd)) { Cell styleCell = styleRow.getCell(col); if (styleCell == null) { log.debug("Creating cell[", row, ",", col, "]"); styleCell = styleRow.createCell(col); } if (styleCell != null) { // log.debug( "Applying border to cell [R" + styleCell.getRowIndex() + "C" + styleCell.getColumnIndex() + "]"); CellStyle newStyle = sm.getStyleWithBorders(styleCell.getCellStyle(), ((row == rowEnd) ? borderStyleBottom : null), ((row == rowEnd) ? borderWidthBottom : null), ((row == rowEnd) ? borderColourBottom : null), ((col == colStart) ? borderStyleLeft : null), ((col == colStart) ? borderWidthLeft : null), ((col == colStart) ? borderColourLeft : null), ((col == colEnd) ? borderStyleRight : null), ((col == colEnd) ? borderWidthRight : null), ((col == colEnd) ? borderColourRight : null), ((row == rowStart) ? borderStyleTop : null), ((row == rowStart) ? borderWidthTop : null), ((row == rowStart) ? borderColourTop : null)); styleCell.setCellStyle(newStyle); } } } } } } } /** * Place a border around a region on the current sheet. * This is used to apply borders to entire rows or entire tables. * @param colStart * The column marking the left-side boundary of the region. * @param colEnd * The column marking the right-side boundary of the region. * @param row * The row to get a bottom border. * @param borderStyle * The BIRT border style to apply to the region. */ public void applyBottomBorderToRow(StyleManager sm, Sheet sheet, int colStart, int colEnd, int row, BirtStyle borderStyle) { CSSValue borderStyleBottom = borderStyle.getProperty(StyleConstants.STYLE_BORDER_BOTTOM_STYLE); CSSValue borderWidthBottom = borderStyle.getProperty(StyleConstants.STYLE_BORDER_BOTTOM_WIDTH); CSSValue borderColourBottom = borderStyle.getProperty(StyleConstants.STYLE_BORDER_BOTTOM_COLOR); if ((borderStyleBottom == null) || (CSSConstants.CSS_NONE_VALUE.equals(borderStyleBottom.getCssText())) || (borderWidthBottom == null) || ("0".equals(borderWidthBottom)) || (borderColourBottom == null) || (CSSConstants.CSS_TRANSPARENT_VALUE.equals(borderColourBottom.getCssText()))) { borderStyleBottom = null; borderWidthBottom = null; borderColourBottom = null; } if ((borderStyleBottom != null) || (borderWidthBottom != null) || (borderColourBottom != null)) { Row styleRow = sheet.getRow(row); if (styleRow != null) { for (int col = colStart; col <= colEnd; ++col) { Cell styleCell = styleRow.getCell(col); if (styleCell == null) { styleCell = styleRow.createCell(col); } if (styleCell != null) { // log.debug( "Applying border to cell [R" + styleCell.getRowIndex() + "C" + styleCell.getColumnIndex() + "]"); CellStyle newStyle = sm.getStyleWithBorders(styleCell.getCellStyle(), borderStyleBottom, borderWidthBottom, borderColourBottom, null, null, null, null, null, null, null, null, null); styleCell.setCellStyle(newStyle); } } } } } public int applyAreaBordersToCell(Collection<AreaBorders> knownAreaBorders, Cell cell, BirtStyle birtCellStyle, int rowIndex, int colIndex) { for (AreaBorders areaBorders : knownAreaBorders) { if ((areaBorders.bottom == rowIndex) && ((areaBorders.left <= colIndex) && (areaBorders.right >= colIndex))) { if ((areaBorders.cssStyle[0] != null) && (areaBorders.cssWidth[0] != null) && (areaBorders.cssColour[0] != null)) { birtCellStyle.setProperty(StyleConstants.STYLE_BORDER_BOTTOM_STYLE, areaBorders.cssStyle[0]); birtCellStyle.setProperty(StyleConstants.STYLE_BORDER_BOTTOM_WIDTH, areaBorders.cssWidth[0]); birtCellStyle.setProperty(StyleConstants.STYLE_BORDER_BOTTOM_COLOR, areaBorders.cssColour[0]); } } if ((areaBorders.left == colIndex) && ((areaBorders.top <= rowIndex) && ((areaBorders.bottom < 0) || (areaBorders.bottom >= rowIndex)))) { if ((areaBorders.cssStyle[1] != null) && (areaBorders.cssWidth[1] != null) && (areaBorders.cssColour[1] != null)) { birtCellStyle.setProperty(StyleConstants.STYLE_BORDER_LEFT_STYLE, areaBorders.cssStyle[1]); birtCellStyle.setProperty(StyleConstants.STYLE_BORDER_LEFT_WIDTH, areaBorders.cssWidth[1]); birtCellStyle.setProperty(StyleConstants.STYLE_BORDER_LEFT_COLOR, areaBorders.cssColour[1]); } } if ((areaBorders.right == colIndex) && ((areaBorders.top <= rowIndex) && ((areaBorders.bottom < 0) || (areaBorders.bottom >= rowIndex)))) { if ((areaBorders.cssStyle[2] != null) && (areaBorders.cssWidth[2] != null) && (areaBorders.cssColour[2] != null)) { birtCellStyle.setProperty(StyleConstants.STYLE_BORDER_RIGHT_STYLE, areaBorders.cssStyle[2]); birtCellStyle.setProperty(StyleConstants.STYLE_BORDER_RIGHT_WIDTH, areaBorders.cssWidth[2]); birtCellStyle.setProperty(StyleConstants.STYLE_BORDER_RIGHT_COLOR, areaBorders.cssColour[2]); } } if ((areaBorders.top == rowIndex) && ((areaBorders.left <= colIndex) && (areaBorders.right >= colIndex))) { if ((areaBorders.cssStyle[3] != null) && (areaBorders.cssWidth[3] != null) && (areaBorders.cssColour[3] != null)) { birtCellStyle.setProperty(StyleConstants.STYLE_BORDER_TOP_STYLE, areaBorders.cssStyle[3]); birtCellStyle.setProperty(StyleConstants.STYLE_BORDER_TOP_WIDTH, areaBorders.cssWidth[3]); birtCellStyle.setProperty(StyleConstants.STYLE_BORDER_TOP_COLOR, areaBorders.cssColour[3]); } } } return colIndex; } public void extendRows(HandlerState state, int startRow, int startCol, int endRow, int endCol) { for (int colNum = startCol; colNum < endCol; ++colNum) { Cell lastCell = null; for (int rowNum = startRow; rowNum < endRow; ++rowNum) { Row row = state.currentSheet.getRow(rowNum); if (row != null) { Cell cell = row.getCell(colNum); if (cell != null) { lastCell = cell; } } } if ((lastCell != null) && (lastCell.getRowIndex() < endRow - 1)) { CellRangeAddress range = new CellRangeAddress(lastCell.getRowIndex(), endRow - 1, lastCell.getColumnIndex(), lastCell.getColumnIndex()); log.debug("Extend: merging from [", range.getFirstRow(), ",", range.getFirstColumn(), "] to [", range.getLastRow(), ",", range.getLastColumn(), "]"); state.currentSheet.addMergedRegion(range); for (int rowNum = lastCell.getRowIndex() + 1; rowNum < endRow; ++rowNum) { Row row = state.currentSheet.getRow(rowNum); if (row == null) { log.error(0, "Creating a row (for column " + colNum + "), this really shouldn't be necessary", null); row = state.currentSheet.createRow(rowNum); } Cell cell = row.createCell(colNum); cell.setCellStyle(lastCell.getCellStyle()); } } } } }