0001: /**
0002: * ===========================================
0003: * JFreeReport : a free Java reporting library
0004: * ===========================================
0005: *
0006: * Project Info: http://reporting.pentaho.org/
0007: *
0008: * (C) Copyright 2001-2007, by Object Refinery Ltd, Pentaho Corporation and Contributors.
0009: *
0010: * This library is free software; you can redistribute it and/or modify it under the terms
0011: * of the GNU Lesser General Public License as published by the Free Software Foundation;
0012: * either version 2.1 of the License, or (at your option) any later version.
0013: *
0014: * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
0015: * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
0016: * See the GNU Lesser General Public License for more details.
0017: *
0018: * You should have received a copy of the GNU Lesser General Public License along with this
0019: * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
0020: * Boston, MA 02111-1307, USA.
0021: *
0022: * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
0023: * in the United States and other countries.]
0024: *
0025: * ------------
0026: * HtmlPrinter.java
0027: * ------------
0028: * (C) Copyright 2001-2007, by Object Refinery Ltd, Pentaho Corporation and Contributors.
0029: */package org.jfree.report.modules.output.table.html;
0030:
0031: import java.awt.Color;
0032: import java.awt.Image;
0033: import java.io.BufferedInputStream;
0034: import java.io.BufferedOutputStream;
0035: import java.io.BufferedWriter;
0036: import java.io.ByteArrayInputStream;
0037: import java.io.IOException;
0038: import java.io.InputStream;
0039: import java.io.OutputStream;
0040: import java.io.OutputStreamWriter;
0041: import java.io.Writer;
0042: import java.net.URL;
0043: import java.text.NumberFormat;
0044: import java.util.HashMap;
0045: import java.util.HashSet;
0046:
0047: import org.jfree.fonts.encoding.EncodingRegistry;
0048: import org.jfree.io.IOUtils;
0049: import org.jfree.report.ElementAlignment;
0050: import org.jfree.report.ImageContainer;
0051: import org.jfree.report.InvalidReportStateException;
0052: import org.jfree.report.JFreeReportInfo;
0053: import org.jfree.report.LocalImageContainer;
0054: import org.jfree.report.URLImageContainer;
0055: import org.jfree.report.layout.model.Border;
0056: import org.jfree.report.layout.model.BorderEdge;
0057: import org.jfree.report.layout.model.LogicalPageBox;
0058: import org.jfree.report.layout.model.RenderBox;
0059: import org.jfree.report.layout.model.context.BoxDefinition;
0060: import org.jfree.report.layout.output.ContentProcessingException;
0061: import org.jfree.report.layout.output.LogicalPageKey;
0062: import org.jfree.report.layout.output.OutputProcessorMetaData;
0063: import org.jfree.report.layout.output.RenderUtility;
0064: import org.jfree.report.modules.output.table.base.SheetLayout;
0065: import org.jfree.report.modules.output.table.base.TableCellDefinition;
0066: import org.jfree.report.modules.output.table.base.TableContentProducer;
0067: import org.jfree.report.modules.output.table.base.TableRectangle;
0068: import org.jfree.report.modules.output.table.html.helper.GlobalStyleManager;
0069: import org.jfree.report.modules.output.table.html.helper.InlineStyleManager;
0070: import org.jfree.report.modules.output.table.html.helper.StyleBuilder;
0071: import org.jfree.report.modules.output.table.html.helper.StyleManager;
0072: import org.jfree.report.modules.output.table.html.util.HtmlColors;
0073: import org.jfree.report.resourceloader.ImageFactory;
0074: import org.jfree.report.style.ElementStyleKeys;
0075: import org.jfree.report.style.StyleSheet;
0076: import org.jfree.report.style.TextStyleKeys;
0077: import org.jfree.report.style.WhitespaceCollapse;
0078: import org.jfree.report.util.MemoryByteArrayOutputStream;
0079: import org.jfree.report.util.MemoryStringWriter;
0080: import org.jfree.report.util.geom.StrictGeomUtility;
0081: import org.jfree.repository.ContentCreationException;
0082: import org.jfree.repository.ContentIOException;
0083: import org.jfree.repository.ContentItem;
0084: import org.jfree.repository.ContentLocation;
0085: import org.jfree.repository.LibRepositoryBoot;
0086: import org.jfree.repository.NameGenerator;
0087: import org.jfree.resourceloader.ResourceData;
0088: import org.jfree.resourceloader.ResourceKey;
0089: import org.jfree.resourceloader.ResourceLoadingException;
0090: import org.jfree.resourceloader.ResourceManager;
0091: import org.jfree.util.Configuration;
0092: import org.jfree.util.Log;
0093: import org.jfree.util.ObjectUtilities;
0094: import org.jfree.util.StackableRuntimeException;
0095: import org.jfree.util.StringUtils;
0096: import org.jfree.xmlns.common.AttributeList;
0097: import org.jfree.xmlns.writer.DefaultTagDescription;
0098: import org.jfree.xmlns.writer.XmlWriter;
0099: import org.jfree.xmlns.writer.XmlWriterSupport;
0100:
0101: /**
0102: * This class is the actual HTML-emitter.
0103: *
0104: * @author Thomas Morgner
0105: */
0106: public abstract class HtmlPrinter implements HtmlContentGenerator {
0107:
0108: private static class ImageData {
0109: private byte[] imageData;
0110: private String mimeType;
0111: private String originalFileName;
0112:
0113: private ImageData(final byte[] imageData,
0114: final String mimeType, final String originalFileName) {
0115: if (imageData == null) {
0116: throw new NullPointerException();
0117: }
0118: if (mimeType == null) {
0119: throw new NullPointerException();
0120: }
0121: if (originalFileName == null) {
0122: throw new NullPointerException();
0123: }
0124:
0125: this .imageData = imageData;
0126: this .mimeType = mimeType;
0127: this .originalFileName = originalFileName;
0128: }
0129:
0130: public byte[] getImageData() {
0131: return imageData;
0132: }
0133:
0134: public String getMimeType() {
0135: return mimeType;
0136: }
0137:
0138: public String getOriginalFileName() {
0139: return originalFileName;
0140: }
0141: }
0142:
0143: private static final String GENERATOR = JFreeReportInfo
0144: .getInstance().getName()
0145: + " version " + JFreeReportInfo.getInstance().getVersion();
0146:
0147: public static final String XHTML_NAMESPACE = "http://www.w3.org/1999/xhtml";
0148:
0149: private static final String[] XHTML_HEADER = { "<!DOCTYPE html",
0150: " PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"",
0151: " \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">" };
0152:
0153: private Configuration configuration;
0154: //private OutputProcessorMetaData metaData;
0155:
0156: private XmlWriter xmlWriter;
0157: private boolean assumeZeroMargins;
0158: private boolean assumeZeroBorders;
0159: private boolean assumeZeroPaddings;
0160:
0161: private ContentLocation contentLocation;
0162: private NameGenerator contentNameGenerator;
0163: private ContentLocation dataLocation;
0164: private NameGenerator dataNameGenerator;
0165:
0166: private ResourceManager resourceManager;
0167: private HashMap knownImages;
0168: private HashMap knownResources;
0169: private HashSet validRawTypes;
0170:
0171: private URLRewriter urlRewriter;
0172: private ContentItem documentContentItem;
0173: private StyleManager styleManager;
0174: private boolean allowRawLinkTargets;
0175: private boolean copyExternalImages;
0176: private StyleBuilder styleBuilder;
0177: private static final String[] EMPTY_CELL_ATTRNAMES = new String[] { "font-size" };
0178: private static final String[] EMPTY_CELL_ATTRVALS = new String[] { "1pt" };
0179:
0180: private MemoryStringWriter bufferWriter;
0181: private BufferedWriter writer;
0182: private ContentItem styleFile;
0183: private String styleFileUrl;
0184:
0185: protected HtmlPrinter(final ResourceManager resourceManager) {
0186: if (resourceManager == null) {
0187: throw new NullPointerException(
0188: "A resource-manager must be given.");
0189: }
0190:
0191: this .resourceManager = resourceManager;
0192: this .knownResources = new HashMap();
0193: this .knownImages = new HashMap();
0194: this .styleBuilder = new StyleBuilder();
0195:
0196: this .validRawTypes = new HashSet();
0197: this .validRawTypes.add("image/gif");
0198: this .validRawTypes.add("image/x-xbitmap");
0199: this .validRawTypes.add("image/gi_");
0200: this .validRawTypes.add("image/jpeg");
0201: this .validRawTypes.add("image/jpg");
0202: this .validRawTypes.add("image/jp_");
0203: this .validRawTypes.add("application/jpg");
0204: this .validRawTypes.add("application/x-jpg");
0205: this .validRawTypes.add("image/pjpeg");
0206: this .validRawTypes.add("image/pipeg");
0207: this .validRawTypes.add("image/vnd.swiftview-jpeg");
0208: this .validRawTypes.add("image/x-xbitmap");
0209: this .validRawTypes.add("image/png");
0210: this .validRawTypes.add("application/png");
0211: this .validRawTypes.add("application/x-png");
0212:
0213: assumeZeroMargins = true;
0214: assumeZeroBorders = true;
0215: assumeZeroPaddings = true;
0216:
0217: // this primitive implementation assumes that the both repositories are
0218: // the same ..
0219: urlRewriter = new FileSystemURLRewriter();
0220: }
0221:
0222: protected boolean isAllowRawLinkTargets() {
0223: return allowRawLinkTargets;
0224: }
0225:
0226: protected Configuration getConfiguration() {
0227: return configuration;
0228: }
0229:
0230: protected boolean isAssumeZeroMargins() {
0231: return assumeZeroMargins;
0232: }
0233:
0234: protected void setAssumeZeroMargins(final boolean assumeZeroMargins) {
0235: this .assumeZeroMargins = assumeZeroMargins;
0236: }
0237:
0238: protected boolean isAssumeZeroBorders() {
0239: return assumeZeroBorders;
0240: }
0241:
0242: protected void setAssumeZeroBorders(final boolean assumeZeroBorders) {
0243: this .assumeZeroBorders = assumeZeroBorders;
0244: }
0245:
0246: protected boolean isAssumeZeroPaddings() {
0247: return assumeZeroPaddings;
0248: }
0249:
0250: protected void setAssumeZeroPaddings(
0251: final boolean assumeZeroPaddings) {
0252: this .assumeZeroPaddings = assumeZeroPaddings;
0253: }
0254:
0255: public ContentLocation getContentLocation() {
0256: return contentLocation;
0257: }
0258:
0259: public NameGenerator getContentNameGenerator() {
0260: return contentNameGenerator;
0261: }
0262:
0263: public ContentLocation getDataLocation() {
0264: return dataLocation;
0265: }
0266:
0267: public NameGenerator getDataNameGenerator() {
0268: return dataNameGenerator;
0269: }
0270:
0271: public void setDataWriter(final ContentLocation dataLocation,
0272: final NameGenerator dataNameGenerator) {
0273: this .dataNameGenerator = dataNameGenerator;
0274: this .dataLocation = dataLocation;
0275: }
0276:
0277: public void setContentWriter(final ContentLocation contentLocation,
0278: final NameGenerator contentNameGenerator) {
0279: this .contentNameGenerator = contentNameGenerator;
0280: this .contentLocation = contentLocation;
0281: }
0282:
0283: public ResourceManager getResourceManager() {
0284: return resourceManager;
0285: }
0286:
0287: public URLRewriter getUrlRewriter() {
0288: return urlRewriter;
0289: }
0290:
0291: public void setUrlRewriter(final URLRewriter urlRewriter) {
0292: if (urlRewriter == null) {
0293: throw new NullPointerException();
0294: }
0295: this .urlRewriter = urlRewriter;
0296: }
0297:
0298: public ContentItem getDocumentContentItem() {
0299: return documentContentItem;
0300: }
0301:
0302: public void setDocumentContentItem(
0303: final ContentItem documentContentItem) {
0304: this .documentContentItem = documentContentItem;
0305: }
0306:
0307: public String writeRaw(final ResourceKey source) throws IOException {
0308: if (copyExternalImages == false) {
0309: final Object identifier = source.getIdentifier();
0310: if (identifier instanceof URL) {
0311: final URL url = (URL) identifier;
0312: final String protocol = url.getProtocol();
0313: if ("http".equalsIgnoreCase(protocol)
0314: || "https".equalsIgnoreCase(protocol)
0315: || "ftp".equalsIgnoreCase(protocol)) {
0316: return url.toExternalForm();
0317: }
0318: }
0319: }
0320:
0321: if (dataLocation == null) {
0322: return null;
0323: }
0324:
0325: try {
0326: final ResourceData resourceData = resourceManager
0327: .load(source);
0328: final String mimeType = queryMimeType(resourceData);
0329: if (isValidImage(mimeType)) {
0330: // lets do some voodo ..
0331: final ContentItem item = dataLocation
0332: .createItem(dataNameGenerator
0333: .generateName(
0334: extractFilename(resourceData),
0335: mimeType));
0336: if (item.isWriteable()) {
0337: item.setAttribute(
0338: LibRepositoryBoot.REPOSITORY_DOMAIN,
0339: LibRepositoryBoot.CONTENT_TYPE, mimeType);
0340:
0341: // write it out ..
0342: final InputStream stream = new BufferedInputStream(
0343: resourceData
0344: .getResourceAsStream(resourceManager));
0345: try {
0346: final OutputStream outputStream = new BufferedOutputStream(
0347: item.getOutputStream());
0348: try {
0349: IOUtils.getInstance().copyStreams(stream,
0350: outputStream);
0351: } finally {
0352: outputStream.close();
0353: }
0354: } finally {
0355: stream.close();
0356: }
0357:
0358: return urlRewriter.rewrite(documentContentItem,
0359: item);
0360: }
0361: }
0362: } catch (ResourceLoadingException e) {
0363: // Ok, loading the resource failed. Not a problem, so we will
0364: // recode the raw-object instead ..
0365: } catch (ContentIOException e) {
0366: // ignore it ..
0367: } catch (URLRewriteException e) {
0368: Log.warn("Rewriting the URL failed.", e);
0369: throw new StackableRuntimeException("Failed", e);
0370: }
0371: return null;
0372: }
0373:
0374: /**
0375: * Tests, whether the given URL points to a supported file format for common browsers. Returns true if the URL
0376: * references a JPEG, PNG or GIF image, false otherwise.
0377: * <p/>
0378: * The checked filetypes are the ones recommended by the W3C.
0379: *
0380: * @param url the url that should be tested.
0381: * @return true, if the content type is supported by the browsers, false otherwise.
0382: */
0383: protected boolean isSupportedImageFormat(final URL url) {
0384: final String file = url.getFile();
0385: if (StringUtils.endsWithIgnoreCase(file, ".jpg")) {
0386: return true;
0387: }
0388: if (StringUtils.endsWithIgnoreCase(file, ".jpeg")) {
0389: return true;
0390: }
0391: if (StringUtils.endsWithIgnoreCase(file, ".png")) {
0392: return true;
0393: }
0394: if (StringUtils.endsWithIgnoreCase(file, ".gif")) {
0395: return true;
0396: }
0397: return false;
0398: }
0399:
0400: private ImageData getImageData(final ImageContainer image)
0401: throws IOException {
0402: URL url = null;
0403: // The image has an assigned URL ...
0404: if (image instanceof URLImageContainer) {
0405: final URLImageContainer urlImage = (URLImageContainer) image;
0406:
0407: url = urlImage.getSourceURL();
0408: // if we have an source to load the image data from ..
0409: if (url != null) {
0410: if (urlImage.isLoadable()
0411: && isSupportedImageFormat(url)) {
0412: final MemoryByteArrayOutputStream bout = new MemoryByteArrayOutputStream();
0413: final InputStream urlIn = new BufferedInputStream(
0414: urlImage.getSourceURL().openStream());
0415: try {
0416: IOUtils.getInstance().copyStreams(urlIn, bout);
0417: } finally {
0418: urlIn.close();
0419: bout.close();
0420: }
0421: final byte[] imageData = bout.toByteArray();
0422: final String mimeType = queryMimeType(imageData);
0423: final String originalFileName = IOUtils
0424: .getInstance().getFileName(url);
0425: return new ImageData(imageData, mimeType,
0426: originalFileName);
0427: }
0428: }
0429: }
0430:
0431: if (image instanceof LocalImageContainer) {
0432: // Check, whether the imagereference contains an AWT image.
0433: // if so, then we can use that image instance for the recoding
0434: final LocalImageContainer li = (LocalImageContainer) image;
0435: Image awtImage = li.getImage();
0436: if (awtImage == null) {
0437: if (url != null) {
0438: awtImage = ImageFactory.getInstance().createImage(
0439: url);
0440: }
0441: }
0442: if (awtImage != null) {
0443: // now encode the image. We don't need to create digest data
0444: // for the image contents, as the image is perfectly identifyable
0445: // by its URL
0446: final byte[] imageData = RenderUtility
0447: .encodeImage(awtImage);
0448: final String mimeType = "image/png";
0449: final String originalFileName;
0450: if (url != null) {
0451: originalFileName = IOUtils.getInstance()
0452: .getFileName(url);
0453: } else {
0454: originalFileName = "picture.png";
0455: }
0456: return new ImageData(imageData, mimeType,
0457: originalFileName);
0458: }
0459: }
0460: return null;
0461: }
0462:
0463: public String writeImage(final ImageContainer image)
0464: throws ContentIOException, IOException {
0465: if (dataLocation == null) {
0466: return null;
0467: }
0468:
0469: final String cacheKey;
0470: if (image instanceof URLImageContainer) {
0471: final URLImageContainer uic = (URLImageContainer) image;
0472: cacheKey = uic.getSourceURLString();
0473: final String retval = (String) knownImages.get(cacheKey);
0474: if (retval != null) {
0475: return retval;
0476: }
0477:
0478: final String sourceURLString = uic.getSourceURLString();
0479: if (uic.isLoadable() == false && sourceURLString != null) {
0480: knownImages.put(cacheKey, sourceURLString);
0481: return sourceURLString;
0482: }
0483: } else {
0484: cacheKey = null;
0485: }
0486:
0487: final ImageData data = getImageData(image);
0488: if (data == null) {
0489: return null;
0490: }
0491:
0492: try {
0493: // write the encoded picture ...
0494: final String filename = IOUtils.getInstance()
0495: .stripFileExtension(data.getOriginalFileName());
0496: final ContentItem dataFile = dataLocation
0497: .createItem(dataNameGenerator.generateName(
0498: filename, data.getMimeType()));
0499: final String contentURL = urlRewriter.rewrite(
0500: documentContentItem, dataFile);
0501:
0502: // a png encoder is included in JCommon ...
0503: final OutputStream out = new BufferedOutputStream(dataFile
0504: .getOutputStream());
0505: try {
0506: out.write(data.getImageData());
0507: out.flush();
0508: } finally {
0509: out.close();
0510: }
0511: if (cacheKey != null) {
0512: knownImages.put(cacheKey, contentURL);
0513: }
0514:
0515: return contentURL;
0516: } catch (ContentCreationException cce) {
0517: // Can't create the content
0518: Log
0519: .warn("Failed to create the content image: Reason given was: "
0520: + cce.getMessage());
0521: return null;
0522: } catch (URLRewriteException re) {
0523: // cannot handle this ..
0524: Log.warn("Failed to write the URL: Reason given was: "
0525: + re.getMessage());
0526: return null;
0527: }
0528: }
0529:
0530: private String extractFilename(final ResourceData resourceData) {
0531: final String filename = (String) resourceData
0532: .getAttribute(ResourceData.FILENAME);
0533: if (filename == null) {
0534: return "image";
0535: }
0536:
0537: return IOUtils.getInstance().stripFileExtension(filename);
0538: }
0539:
0540: private String queryMimeType(final ResourceData resourceData)
0541: throws ResourceLoadingException, IOException {
0542: final Object contentType = resourceData
0543: .getAttribute(ResourceData.CONTENT_TYPE);
0544: if (contentType instanceof String) {
0545: return (String) contentType;
0546: }
0547:
0548: // now we are getting very primitive .. (Kids, dont do this at home)
0549: final byte[] data = new byte[12];
0550: resourceData.getResource(resourceManager, data, 0, data.length);
0551: return queryMimeType(data);
0552: }
0553:
0554: private String queryMimeType(final byte[] data) throws IOException {
0555: final ByteArrayInputStream stream = new ByteArrayInputStream(
0556: data);
0557: if (isGIF(stream)) {
0558: return "image/gif";
0559: }
0560: stream.reset();
0561: if (isJPEG(stream)) {
0562: return "image/jpeg";
0563: }
0564: stream.reset();
0565: if (isPNG(stream)) {
0566: return "image/png";
0567: }
0568: return null;
0569: }
0570:
0571: private boolean isPNG(final ByteArrayInputStream data) {
0572: final int[] PNF_FINGERPRINT = { 137, 80, 78, 71, 13, 10, 26, 10 };
0573: for (int i = 0; i < PNF_FINGERPRINT.length; i++) {
0574: if (PNF_FINGERPRINT[i] != data.read()) {
0575: return false;
0576: }
0577: }
0578: return true;
0579: }
0580:
0581: private boolean isJPEG(final InputStream data) throws IOException {
0582: final int[] JPG_FINGERPRINT_1 = { 0xFF, 0xD8, 0xFF, 0xE0 };
0583: for (int i = 0; i < JPG_FINGERPRINT_1.length; i++) {
0584: if (JPG_FINGERPRINT_1[i] != data.read()) {
0585: return false;
0586: }
0587: }
0588: // then skip two bytes ..
0589: if (data.read() == -1) {
0590: return false;
0591: }
0592: if (data.read() == -1) {
0593: return false;
0594: }
0595:
0596: final int[] JPG_FINGERPRINT_2 = { 0x4A, 0x46, 0x49, 0x46, 0x00 };
0597: for (int i = 0; i < JPG_FINGERPRINT_2.length; i++) {
0598: if (JPG_FINGERPRINT_2[i] != data.read()) {
0599: return false;
0600: }
0601: }
0602: return true;
0603: }
0604:
0605: private boolean isGIF(final InputStream data) throws IOException {
0606: final int[] GIF_FINGERPRINT = { 'G', 'I', 'F', '8' };
0607: for (int i = 0; i < GIF_FINGERPRINT.length; i++) {
0608: if (GIF_FINGERPRINT[i] != data.read()) {
0609: return false;
0610: }
0611: }
0612: return true;
0613: }
0614:
0615: private boolean isValidImage(final String mimeType) {
0616: return validRawTypes.contains(mimeType);
0617: }
0618:
0619: private boolean isCreateBodyFragment() {
0620: return "true".equals(getConfiguration().getConfigProperty(
0621: HtmlTableModule.BODY_FRAGMENT, "false"));
0622: }
0623:
0624: private boolean isEmptyCellsUseCSS() {
0625: return "true".equals(getConfiguration().getConfigProperty(
0626: HtmlTableModule.EMPTY_CELLS_USE_CSS, "false"));
0627: }
0628:
0629: private boolean isTableRowBorderDefinition() {
0630: return "true".equals(getConfiguration().getConfigProperty(
0631: HtmlTableModule.TABLE_ROW_BORDER_DEFINITION, "false"));
0632: }
0633:
0634: private boolean isProportionalColumnWidths() {
0635: return "true".equals(getConfiguration().getConfigProperty(
0636: HtmlTableModule.PROPORTIONAL_COLUMN_WIDTHS, "false"));
0637: }
0638:
0639: private AttributeList createCellAttributes(
0640: final TableRectangle rect, final RenderBox content,
0641: final TableCellDefinition background,
0642: final String[] extraStyleKeys,
0643: final String[] extraStyleValues) {
0644: if (content == null) {
0645: styleBuilder.clear();
0646: } else {
0647: styleBuilder = produceTextStyle(styleBuilder, content,
0648: true, false);
0649: }
0650:
0651: // Add the extra styles
0652: if (extraStyleKeys != null && extraStyleValues != null
0653: && extraStyleKeys.length == extraStyleValues.length) {
0654: for (int i = 0; i < extraStyleKeys.length; ++i) {
0655: styleBuilder.append(extraStyleKeys[i],
0656: extraStyleValues[i], false);
0657: }
0658: }
0659:
0660: if (background != null) {
0661: final Color colorValue = (background.getBackgroundColor());
0662: if (colorValue != null) {
0663: styleBuilder.append("background-color", HtmlColors
0664: .getColorString(colorValue));
0665: }
0666:
0667: styleBuilder.append("border-top", styleBuilder
0668: .printEdgeAsCSS(background.getTop()));
0669: styleBuilder.append("border-left", styleBuilder
0670: .printEdgeAsCSS(background.getLeft()));
0671: styleBuilder.append("border-bottom", styleBuilder
0672: .printEdgeAsCSS(background.getBottom()));
0673: styleBuilder.append("border-right", styleBuilder
0674: .printEdgeAsCSS(background.getRight()));
0675: }
0676:
0677: final AttributeList attrList = new AttributeList();
0678: if (content != null) {
0679: // ignore for now ..
0680: final int rowSpan = rect.getRowSpan();
0681: if (rowSpan > 1) {
0682: attrList.setAttribute(XHTML_NAMESPACE, "rowspan",
0683: String.valueOf(rowSpan));
0684: }
0685: final int colSpan = rect.getColumnSpan();
0686: if (colSpan > 1) {
0687: attrList.setAttribute(XHTML_NAMESPACE, "colspan",
0688: String.valueOf(colSpan));
0689: }
0690:
0691: final ElementAlignment verticalAlignment = content
0692: .getNodeLayoutProperties().getVerticalAlignment();
0693: attrList.setAttribute(HtmlPrinter.XHTML_NAMESPACE,
0694: "valign",
0695: translateVerticalAlignment(verticalAlignment));
0696: }
0697:
0698: styleManager.updateStyle(styleBuilder, attrList);
0699: return attrList;
0700: }
0701:
0702: /**
0703: * Translates the JFreeReport horizontal element alignment into a HTML alignment constant.
0704: *
0705: * @param ea the element alignment
0706: * @return the translated alignment name.
0707: */
0708: private String translateVerticalAlignment(final ElementAlignment ea) {
0709: if (ea == ElementAlignment.BOTTOM) {
0710: return "bottom";
0711: }
0712: if (ea == ElementAlignment.MIDDLE) {
0713: return "middle";
0714: }
0715: return "top";
0716: }
0717:
0718: private AttributeList createRowAttributes(
0719: final SheetLayout sheetLayout, final int row) {
0720: // todo: Check for common backgrounds and top/bottom borders
0721: // todo: Check for global style ..
0722: final AttributeList attrList = new AttributeList();
0723: final int rowHeight = (int) StrictGeomUtility
0724: .toExternalValue(sheetLayout.getRowHeight(row));
0725:
0726: if (isTableRowBorderDefinition()) {
0727: styleBuilder.clear();
0728: final Color commonBackgroundColor = getCommonBackgroundColor(
0729: sheetLayout, row);
0730: final BorderEdge top = getCommonTopBorderEdge(sheetLayout,
0731: row);
0732: final BorderEdge bottom = getCommonBottomBorderEdge(
0733: sheetLayout, row);
0734: if (commonBackgroundColor != null) {
0735: styleBuilder.append("background-color", HtmlColors
0736: .getColorString(commonBackgroundColor));
0737: }
0738: styleBuilder.append("border-top", styleBuilder
0739: .printEdgeAsCSS(top));
0740: styleBuilder.append("border-bottom", styleBuilder
0741: .printEdgeAsCSS(bottom));
0742: styleBuilder.append("height", styleBuilder
0743: .getPointConverter().format(rowHeight), "pt");
0744: styleManager.updateStyle(styleBuilder, attrList);
0745: } else {
0746: // equally expensive and makes text more readable (helps with debugging)
0747: attrList.setAttribute(HtmlPrinter.XHTML_NAMESPACE, "style",
0748: "height: " + rowHeight + "pt");
0749: }
0750: return attrList;
0751: }
0752:
0753: private BorderEdge getCommonTopBorderEdge(
0754: final SheetLayout sheetLayout, final int row) {
0755: BorderEdge bg = BorderEdge.EMPTY;
0756: final int columnCount = sheetLayout.getColumnCount();
0757: for (int col = 0; col < columnCount; col += 1) {
0758: final TableCellDefinition backgroundAt = sheetLayout
0759: .getBackgroundAt(row, col);
0760: if (backgroundAt == null) {
0761: return BorderEdge.EMPTY;
0762: }
0763: if (col == 0) {
0764: bg = backgroundAt.getTop();
0765: } else if (ObjectUtilities.equal(bg, backgroundAt.getTop()) == false) {
0766: return BorderEdge.EMPTY;
0767: }
0768: }
0769: return bg;
0770: }
0771:
0772: private BorderEdge getCommonBottomBorderEdge(
0773: final SheetLayout sheetLayout, final int row) {
0774: BorderEdge bg = BorderEdge.EMPTY;
0775: final int columnCount = sheetLayout.getColumnCount();
0776: for (int col = 0; col < columnCount; col += 1) {
0777: final TableCellDefinition backgroundAt = sheetLayout
0778: .getBackgroundAt(row, col);
0779: if (backgroundAt == null) {
0780: return BorderEdge.EMPTY;
0781: }
0782: if (col == 0) {
0783: bg = backgroundAt.getBottom();
0784: } else if (ObjectUtilities.equal(bg, backgroundAt
0785: .getBottom()) == false) {
0786: return BorderEdge.EMPTY;
0787: }
0788: }
0789: return bg;
0790: }
0791:
0792: private Color getCommonBackgroundColor(
0793: final SheetLayout sheetLayout, final int row) {
0794: Color bg = null;
0795: final int columnCount = sheetLayout.getColumnCount();
0796: for (int col = 0; col < columnCount; col += 1) {
0797: final TableCellDefinition backgroundAt = sheetLayout
0798: .getBackgroundAt(row, col);
0799: if (backgroundAt == null) {
0800: return null;
0801: }
0802:
0803: if (col == 0) {
0804: bg = backgroundAt.getBackgroundColor();
0805: } else {
0806: if (ObjectUtilities.equal(bg, backgroundAt
0807: .getBackgroundColor()) == false) {
0808: return null;
0809: }
0810: }
0811: }
0812: return bg;
0813: }
0814:
0815: private AttributeList createSheetNameAttributes() {
0816: final AttributeList tableAttrList = new AttributeList();
0817:
0818: final String additionalStyleClass = getConfiguration()
0819: .getConfigProperty(
0820: "org.jfree.report.modules.output.table.html.SheetNameClass");
0821: if (additionalStyleClass != null) {
0822: tableAttrList.setAttribute(XHTML_NAMESPACE, "class",
0823: additionalStyleClass);
0824: }
0825:
0826: return tableAttrList;
0827: }
0828:
0829: private AttributeList createTableAttributes(
0830: final SheetLayout sheetLayout) {
0831: final int noc = sheetLayout.getColumnCount();
0832: styleBuilder.clear();
0833: if ((noc > 0) && (isProportionalColumnWidths() == false)) {
0834: final int width = (int) StrictGeomUtility
0835: .toExternalValue(sheetLayout.getCellWidth(0, noc));
0836: styleBuilder.append("width", width + "pt");
0837: } else {
0838: // Consume the complete width for proportional column widths
0839: styleBuilder.append("width", "100%");
0840: }
0841:
0842: // style += "table-layout: fixed;";
0843: if (isTableRowBorderDefinition()) {
0844: styleBuilder.append("border-collapse", "collapse");
0845: }
0846: if (isEmptyCellsUseCSS()) {
0847: styleBuilder.append("empty-cells", "show");
0848: }
0849:
0850: final String additionalStyleClass = getConfiguration()
0851: .getConfigProperty(
0852: "org.jfree.report.modules.output.table.html.StyleClass");
0853:
0854: final AttributeList tableAttrList = new AttributeList();
0855: if (additionalStyleClass != null) {
0856: tableAttrList.setAttribute(XHTML_NAMESPACE, "class",
0857: additionalStyleClass);
0858: }
0859: tableAttrList.setAttribute(XHTML_NAMESPACE, "cellspacing", "0");
0860: tableAttrList.setAttribute(XHTML_NAMESPACE, "cellpadding", "0");
0861:
0862: styleManager.updateStyle(styleBuilder, tableAttrList);
0863: return tableAttrList;
0864: }
0865:
0866: private void writeColumnDeclaration(final SheetLayout sheetLayout)
0867: throws IOException {
0868: final int colCount = sheetLayout.getColumnCount();
0869: final int fullWidth = (int) StrictGeomUtility
0870: .toExternalValue(sheetLayout.getMaxWidth());
0871: final boolean proportionalColumnWidths = isProportionalColumnWidths();
0872:
0873: final NumberFormat pointConverter = styleBuilder
0874: .getPointConverter();
0875: final NumberFormat pointIntConverter = styleBuilder
0876: .getPointIntConverter();
0877: for (int col = 0; col < colCount; col++) {
0878: // Print the table.
0879: final int width = (int) StrictGeomUtility
0880: .toExternalValue(sheetLayout.getCellWidth(col,
0881: col + 1));
0882: styleBuilder.clear();
0883: if (proportionalColumnWidths) {
0884: final double colWidth = width * 100.0d / fullWidth;
0885: styleBuilder.append("width", pointConverter
0886: .format(colWidth) + '%');
0887: } else {
0888: styleBuilder.append("width", pointIntConverter
0889: .format(width)
0890: + "pt");
0891: }
0892:
0893: xmlWriter.writeTag(null, "col", "style", styleBuilder
0894: .toString(), XmlWriterSupport.CLOSE);
0895: }
0896: }
0897:
0898: public void print(final LogicalPageKey logicalPageKey,
0899: final LogicalPageBox logicalPage,
0900: final TableContentProducer contentProducer,
0901: final OutputProcessorMetaData metaData,
0902: final boolean incremental)
0903: throws ContentProcessingException {
0904: this .configuration = metaData.getConfiguration();
0905: this .allowRawLinkTargets = "true"
0906: .equals(configuration
0907: .getConfigProperty(HtmlTableModule.ALLOW_RAW_LINK_TARGETS));
0908: this .copyExternalImages = "true"
0909: .equals(configuration
0910: .getConfigProperty(HtmlTableModule.COPY_EXTERNAL_IMAGES));
0911:
0912: try {
0913: final SheetLayout sheetLayout = contentProducer
0914: .getSheetLayout();
0915:
0916: if (documentContentItem == null) {
0917: documentContentItem = contentLocation
0918: .createItem(contentNameGenerator.generateName(
0919: null, "text/html"));
0920:
0921: final OutputStream out = documentContentItem
0922: .getOutputStream();
0923: final String encoding = configuration
0924: .getConfigProperty(HtmlTableModule.ENCODING,
0925: EncodingRegistry
0926: .getPlatformDefaultEncoding());
0927: writer = new BufferedWriter(new OutputStreamWriter(out,
0928: encoding));
0929:
0930: final DefaultTagDescription td = new DefaultTagDescription();
0931: td.configure(getConfiguration(),
0932: "org.jfree.report.modules.output.table.html.");
0933:
0934: if (isCreateBodyFragment() == false) {
0935: if (isInlineStylesRequested()) {
0936: this .styleManager = new InlineStyleManager();
0937: this .xmlWriter = new XmlWriter(writer, td);
0938: this .xmlWriter.addImpliedNamespace(
0939: HtmlPrinter.XHTML_NAMESPACE, "");
0940: this .xmlWriter.setHtmlCompatiblityMode(true);
0941: writeCompleteHeader(xmlWriter, contentProducer,
0942: null, null);
0943: } else {
0944: if (isExternalStyleSheetRequested()) {
0945: this .styleFile = dataLocation
0946: .createItem(dataNameGenerator
0947: .generateName("style",
0948: "text/css"));
0949: this .styleFileUrl = urlRewriter.rewrite(
0950: documentContentItem, styleFile);
0951: }
0952:
0953: this .styleManager = new GlobalStyleManager();
0954: if (isForceBufferedWriting() == false
0955: && styleFile != null) {
0956: this .xmlWriter = new XmlWriter(writer, td);
0957: this .xmlWriter.addImpliedNamespace(
0958: HtmlPrinter.XHTML_NAMESPACE, "");
0959: this .xmlWriter
0960: .setHtmlCompatiblityMode(true);
0961: writeCompleteHeader(xmlWriter,
0962: contentProducer, styleFileUrl, null);
0963: } else {
0964: this .bufferWriter = new MemoryStringWriter(
0965: 1024 * 512);
0966: this .xmlWriter = new XmlWriter(
0967: bufferWriter, td);
0968: this .xmlWriter.setAdditionalIndent(1);
0969: this .xmlWriter.addImpliedNamespace(
0970: HtmlPrinter.XHTML_NAMESPACE, "");
0971: this .xmlWriter
0972: .setHtmlCompatiblityMode(true);
0973: }
0974: }
0975:
0976: this .xmlWriter.writeTag(
0977: HtmlPrinter.XHTML_NAMESPACE, "body",
0978: XmlWriterSupport.OPEN);
0979: } else {
0980: this .styleManager = new InlineStyleManager();
0981: this .xmlWriter = new XmlWriter(writer, td);
0982: this .xmlWriter.addImpliedNamespace(
0983: HtmlPrinter.XHTML_NAMESPACE, "");
0984: this .xmlWriter.setHtmlCompatiblityMode(true);
0985: }
0986:
0987: // table name
0988: final String sheetName = contentProducer.getSheetName();
0989: if (sheetName != null) {
0990: xmlWriter.writeTag(HtmlPrinter.XHTML_NAMESPACE,
0991: "h1", createSheetNameAttributes(),
0992: XmlWriterSupport.OPEN);
0993: xmlWriter.writeText(xmlWriter.normalizeLocal(
0994: sheetName, true));
0995: xmlWriter.writeCloseTag();
0996: }
0997:
0998: // table
0999: xmlWriter.writeTag(HtmlPrinter.XHTML_NAMESPACE,
1000: "table", createTableAttributes(sheetLayout),
1001: XmlWriterSupport.OPEN);
1002: writeColumnDeclaration(sheetLayout);
1003: }
1004:
1005: final int colCount = sheetLayout.getColumnCount();
1006: // final int rowCount = sheetLayout.getRowCount();
1007: final boolean emptyCellsUseCSS = isEmptyCellsUseCSS();
1008:
1009: final int startRow = contentProducer.getFinishedRows();
1010: final int finishRow = contentProducer.getFilledRows();
1011: // Log.debug ("Processing: " + startRow + " " + finishRow + " " + incremental);
1012: final HtmlTextExtractor textExtractor = new HtmlTextExtractor(
1013: metaData, xmlWriter, styleManager, this );
1014:
1015: for (int row = startRow; row < finishRow; row++) {
1016: xmlWriter.writeTag(HtmlPrinter.XHTML_NAMESPACE, "tr",
1017: createRowAttributes(sheetLayout, row),
1018: XmlWriterSupport.OPEN);
1019: for (int col = 0; col < colCount; col++) {
1020: final RenderBox content = contentProducer
1021: .getContent(row, col);
1022: final TableCellDefinition background = sheetLayout
1023: .getBackgroundAt(row, col);
1024:
1025: if (content == null && background == null) {
1026: if (emptyCellsUseCSS) {
1027: xmlWriter.writeTag(
1028: HtmlPrinter.XHTML_NAMESPACE, "td",
1029: XmlWriterSupport.CLOSE);
1030: } else {
1031: final AttributeList attrs = new AttributeList();
1032: attrs.setAttribute(
1033: HtmlPrinter.XHTML_NAMESPACE,
1034: "style", "font-size: 1pt");
1035: xmlWriter.writeTag(
1036: HtmlPrinter.XHTML_NAMESPACE, "td",
1037: attrs, XmlWriterSupport.OPEN);
1038: xmlWriter.writeText(" ");
1039: xmlWriter.writeCloseTag();
1040: }
1041:
1042: continue;
1043: }
1044:
1045: if (content != null) {
1046: if (content.isCommited() == false) {
1047: throw new InvalidReportStateException(
1048: "Uncommited content encountered: "
1049: + row + ", " + col + ' '
1050: + content);
1051: }
1052:
1053: final long contentOffset = contentProducer
1054: .getContentOffset(row, col);
1055: final TableRectangle rectangle = sheetLayout
1056: .getTableBounds(content.getX(), content
1057: .getY()
1058: + contentOffset, content
1059: .getWidth(), content
1060: .getHeight(), null);
1061: if (rectangle.isOrigin(col, row) == false) {
1062: // A spanned cell ..
1063: continue;
1064: }
1065:
1066: final TableCellDefinition realBackground;
1067: if (background == null
1068: || (rectangle.getColumnSpan() == 1 && rectangle
1069: .getRowSpan() == 1)) {
1070: realBackground = background;
1071: } else {
1072: realBackground = sheetLayout
1073: .getBackgroundAt(rectangle.getX1(),
1074: rectangle.getY1(),
1075: rectangle.getColumnSpan(),
1076: rectangle.getRowSpan());
1077: }
1078:
1079: final AttributeList cellAttributes = createCellAttributes(
1080: rectangle, content, realBackground,
1081: null, null);
1082: xmlWriter.writeTag(HtmlPrinter.XHTML_NAMESPACE,
1083: "td", cellAttributes,
1084: XmlWriterSupport.OPEN);
1085: if (background != null) {
1086: final String anchor = background
1087: .getAnchor();
1088: if (anchor != null) {
1089: xmlWriter.writeTag(
1090: HtmlPrinter.XHTML_NAMESPACE,
1091: "a", "name", normalize(anchor,
1092: xmlWriter),
1093: XmlWriterSupport.CLOSE);
1094: }
1095: }
1096: // the style of the content-box itself is already contained in the <td> tag. So there is no need
1097: // to duplicate the style here
1098: textExtractor.performOutput(content);
1099:
1100: xmlWriter.writeCloseTag();
1101: content.setFinished(true);
1102: } else {
1103: // Background cannot be null at this point ..
1104: final String anchor = background.getAnchor();
1105: if (anchor == null && emptyCellsUseCSS) {
1106: final AttributeList cellAttributes = createCellAttributes(
1107: null, null, background, null, null);
1108: xmlWriter.writeTag(
1109: HtmlPrinter.XHTML_NAMESPACE, "td",
1110: cellAttributes,
1111: XmlWriterSupport.CLOSE);
1112: } else {
1113: final AttributeList cellAttributes = createCellAttributes(
1114: null, null, background,
1115: EMPTY_CELL_ATTRNAMES,
1116: EMPTY_CELL_ATTRVALS);
1117: xmlWriter.writeTag(
1118: HtmlPrinter.XHTML_NAMESPACE, "td",
1119: cellAttributes,
1120: XmlWriterSupport.OPEN);
1121: if (anchor != null) {
1122: xmlWriter.writeTag(
1123: HtmlPrinter.XHTML_NAMESPACE,
1124: "a", "name", normalize(anchor,
1125: xmlWriter),
1126: XmlWriterSupport.CLOSE);
1127: }
1128: xmlWriter.writeText(" ");
1129: xmlWriter.writeCloseTag();
1130:
1131: }
1132: }
1133: }
1134: xmlWriter.writeCloseTag();
1135: }
1136:
1137: if (incremental == false) {
1138: performCloseFile(contentProducer);
1139:
1140: xmlWriter = null;
1141: try {
1142: writer.close();
1143: } catch (IOException e) {
1144: // ignored ..
1145: }
1146: writer = null;
1147: bufferWriter = null;
1148: documentContentItem = null;
1149: }
1150: } catch (IOException ioe) {
1151: xmlWriter = null;
1152: try {
1153: if (writer != null) {
1154: writer.close();
1155: }
1156: } catch (IOException e) {
1157: // ignored ..
1158: }
1159: writer = null;
1160: bufferWriter = null;
1161: documentContentItem = null;
1162: styleFile = null;
1163:
1164: // ignore for now ..
1165: throw new ContentProcessingException(
1166: "IOError while creating content", ioe);
1167: } catch (ContentIOException e) {
1168: xmlWriter = null;
1169: try {
1170: if (writer != null) {
1171: writer.close();
1172: }
1173: } catch (IOException ex) {
1174: // ignored ..
1175: }
1176: writer = null;
1177: bufferWriter = null;
1178: documentContentItem = null;
1179: styleFile = null;
1180:
1181: throw new ContentProcessingException(
1182: "Content-IOError while creating content", e);
1183: } catch (URLRewriteException e) {
1184: xmlWriter = null;
1185: try {
1186: if (writer != null) {
1187: writer.close();
1188: }
1189: } catch (IOException ex) {
1190: // ignored ..
1191: }
1192: writer = null;
1193: bufferWriter = null;
1194: documentContentItem = null;
1195: styleFile = null;
1196:
1197: throw new ContentProcessingException(
1198: "Cannot create URL for external stylesheet", e);
1199: }
1200: }
1201:
1202: private void writeCompleteHeader(final XmlWriter docWriter,
1203: final TableContentProducer contentProducer,
1204: final String url, final String inlineStyleSheet)
1205: throws IOException {
1206: final String encoding = configuration.getConfigProperty(
1207: HtmlTableModule.ENCODING, EncodingRegistry
1208: .getPlatformDefaultEncoding());
1209:
1210: docWriter.writeXmlDeclaration(encoding);
1211: for (int i = 0; i < XHTML_HEADER.length; i++) {
1212: docWriter.writeText(HtmlPrinter.XHTML_HEADER[i]);
1213: docWriter.writeNewLine();
1214: }
1215: docWriter.writeTag(HtmlPrinter.XHTML_NAMESPACE, "html",
1216: XmlWriterSupport.OPEN);
1217: docWriter.writeTag(HtmlPrinter.XHTML_NAMESPACE, "head",
1218: XmlWriterSupport.OPEN);
1219:
1220: final String title = configuration
1221: .getConfigProperty(HtmlTableModule.TITLE);
1222: if (title != null) {
1223: docWriter.writeTag(HtmlPrinter.XHTML_NAMESPACE, "title",
1224: XmlWriterSupport.OPEN);
1225: docWriter.writeText(xmlWriter.normalizeLocal(title, false));
1226: docWriter.writeCloseTag();
1227: }
1228: // if no single title defined, use the sheetname function previously computed
1229: else if (contentProducer.getSheetName() != null) {
1230: docWriter.writeTag(HtmlPrinter.XHTML_NAMESPACE, "title",
1231: XmlWriterSupport.OPEN);
1232: docWriter.writeText(xmlWriter.normalizeLocal(
1233: contentProducer.getSheetName(), true));
1234: docWriter.writeCloseTag();
1235: } else {
1236: docWriter.writeTag(HtmlPrinter.XHTML_NAMESPACE, "title",
1237: XmlWriterSupport.OPEN);
1238: docWriter.writeText(" ");
1239: docWriter.writeCloseTag();
1240: }
1241:
1242: writeMeta(docWriter, "subject", configuration
1243: .getConfigProperty(HtmlTableModule.SUBJECT));
1244: writeMeta(docWriter, "author", configuration
1245: .getConfigProperty(HtmlTableModule.AUTHOR));
1246: writeMeta(docWriter, "keywords", configuration
1247: .getConfigProperty(HtmlTableModule.KEYWORDS));
1248: writeMeta(docWriter, "generator", GENERATOR);
1249:
1250: final AttributeList metaAttrs = new AttributeList();
1251: metaAttrs.setAttribute(HtmlPrinter.XHTML_NAMESPACE,
1252: "http-equiv", "content-type");
1253: metaAttrs.setAttribute(HtmlPrinter.XHTML_NAMESPACE, "content",
1254: "text/html; charset=" + encoding);
1255: docWriter.writeTag(HtmlPrinter.XHTML_NAMESPACE, "meta",
1256: metaAttrs, XmlWriterSupport.CLOSE);
1257:
1258: if (url != null) {
1259: final AttributeList attrList = new AttributeList();
1260: attrList.setAttribute(HtmlPrinter.XHTML_NAMESPACE, "type",
1261: "text/css");
1262: attrList.setAttribute(HtmlPrinter.XHTML_NAMESPACE, "rel",
1263: "stylesheet");
1264: attrList.setAttribute(HtmlPrinter.XHTML_NAMESPACE, "href",
1265: url);
1266:
1267: docWriter.writeTag(HtmlPrinter.XHTML_NAMESPACE, "link",
1268: attrList, XmlWriterSupport.CLOSE);
1269: } else if (inlineStyleSheet != null) {
1270: docWriter.writeTag(HtmlPrinter.XHTML_NAMESPACE, "style",
1271: "type", "text/css", XmlWriterSupport.OPEN);
1272: docWriter.writeText(inlineStyleSheet);
1273: docWriter.writeCloseTag();
1274: }
1275: docWriter.writeCloseTag();
1276: }
1277:
1278: private void performCloseFile(
1279: final TableContentProducer contentProducer)
1280: throws IOException, ContentIOException {
1281: xmlWriter.writeCloseTag(); // for the opening table ..
1282:
1283: if (isCreateBodyFragment() != false) {
1284: xmlWriter.close();
1285: return;
1286: }
1287:
1288: if (styleFile != null) {
1289: final String encoding = configuration.getConfigProperty(
1290: HtmlTableModule.ENCODING, EncodingRegistry
1291: .getPlatformDefaultEncoding());
1292: final Writer styleOut = new OutputStreamWriter(
1293: new BufferedOutputStream(styleFile
1294: .getOutputStream()), encoding);
1295: styleManager.write(styleOut);
1296: styleOut.flush();
1297: styleOut.close();
1298:
1299: if (isForceBufferedWriting() == false) {
1300: // A complete header had been written when the processing started ..
1301: this .xmlWriter.writeCloseTag(); // for the body tag
1302: this .xmlWriter.writeCloseTag(); // for the HTML tag
1303: this .xmlWriter.close();
1304: return;
1305: }
1306: }
1307: if (isInlineStylesRequested()) {
1308: this .xmlWriter.writeCloseTag(); // for the body tag
1309: this .xmlWriter.writeCloseTag(); // for the HTML tag
1310: this .xmlWriter.close();
1311: return;
1312: }
1313:
1314: final XmlWriter docWriter = new XmlWriter(writer, xmlWriter
1315: .getTagDescription());
1316: docWriter.addImpliedNamespace(HtmlPrinter.XHTML_NAMESPACE, "");
1317: docWriter.setHtmlCompatiblityMode(true);
1318:
1319: if (styleFile != null) {
1320: // now its time to write the header with the link to the style-sheet-file
1321: writeCompleteHeader(docWriter, contentProducer,
1322: styleFileUrl, null);
1323: } else {
1324: final String globalStyleSheet = styleManager
1325: .getGlobalStyleSheet();
1326: writeCompleteHeader(docWriter, contentProducer, null,
1327: globalStyleSheet);
1328: }
1329:
1330: xmlWriter.writeCloseTag(); // for the body ..
1331: xmlWriter.flush();
1332: docWriter.writeText(bufferWriter.toString());
1333:
1334: docWriter.writeCloseTag(); // for the html ..
1335: docWriter.close();
1336: }
1337:
1338: private boolean isForceBufferedWriting() {
1339: return "true"
1340: .equals(configuration
1341: .getConfigProperty(HtmlTableModule.FORCE_BUFFER_WRITING));
1342: }
1343:
1344: protected static String normalize(final String anchor,
1345: final XmlWriter xmlWriter) {
1346: if (anchor == null) {
1347: return null;
1348: }
1349:
1350: // raw values are ok, which means that we just have to check whether the anchor breaks anything.
1351: if (anchor.indexOf('<') == -1 && anchor.indexOf('>') == -1
1352: && anchor.indexOf('"') == -1) {
1353: return anchor;
1354: }
1355:
1356: return xmlWriter.normalizeLocal(anchor, true);
1357: }
1358:
1359: private void writeMeta(final XmlWriter writer, final String name,
1360: final String value) throws IOException {
1361: if (value == null) {
1362: return;
1363: }
1364: final AttributeList attrList = new AttributeList();
1365: attrList
1366: .setAttribute(HtmlPrinter.XHTML_NAMESPACE, "name", name);
1367: attrList.setAttribute(HtmlPrinter.XHTML_NAMESPACE, "content",
1368: value);
1369: writer.writeTag(HtmlPrinter.XHTML_NAMESPACE, "meta", attrList,
1370: XmlWriterSupport.CLOSE);
1371: }
1372:
1373: //
1374: // /**
1375: // * Checks, whether the engine should generate a stylesheet that is directly inserted into the header of the
1376: // * generated file.
1377: // *
1378: // * This method will return false, if "createBodyFragment" is enabled, as a body-fragment has no header.
1379: // *
1380: // * This methos will also return false, if inline-styles are requested.
1381: // *
1382: // * @return true, if the engine generates an stylesheet that is contained in the header, false otherwise.
1383: // */
1384: // private boolean isInternalStyleSheetRequested ()
1385: // {
1386: // if (isCreateBodyFragment())
1387: // {
1388: // // body-fragments have no header ..
1389: // return false;
1390: // }
1391: //
1392: // // We will add the style-declarations directly to the HTML elements ..
1393: // if (isInlineStylesRequested())
1394: // {
1395: // return false;
1396: // }
1397: //
1398: // final boolean externalStyle = "true".equals
1399: // (configuration.getConfigProperty("org.jfree.report.modules.output.table.html.ExternalStyle", "true"));
1400: // // User explicitly requested internal styles by disabeling the external-style property.
1401: // return externalStyle == false;
1402: // }
1403:
1404: private boolean isInlineStylesRequested() {
1405: return "true".equals(configuration
1406: .getConfigProperty(HtmlTableModule.INLINE_STYLE));
1407: }
1408:
1409: private boolean isExternalStyleSheetRequested() {
1410: if (isCreateBodyFragment()) {
1411: // body-fragments have no header ..
1412: return false;
1413: }
1414:
1415: // We will add the style-declarations directly to the HTML elements ..
1416: if (isInlineStylesRequested()) {
1417: return false;
1418: }
1419:
1420: // Without the ability to create external files, we cannot create external stylesheet.
1421: if (dataLocation == null) {
1422: return false;
1423: }
1424:
1425: final boolean externalStyle = "true".equals(configuration
1426: .getConfigProperty(HtmlTableModule.EXTERNALIZE_STYLE,
1427: "true"));
1428: // User explicitly requested internal styles by disabeling the external-style property.
1429: return externalStyle;
1430:
1431: }
1432:
1433: public static StyleBuilder produceTextStyle(
1434: StyleBuilder styleBuilder, final RenderBox box,
1435: final boolean includeBorder,
1436: final boolean includeWhitespaceCollapse) {
1437: if (styleBuilder == null) {
1438: styleBuilder = new StyleBuilder();
1439: }
1440:
1441: final StyleSheet styleSheet = box.getStyleSheet();
1442: final Color textColor = (Color) styleSheet
1443: .getStyleProperty(ElementStyleKeys.PAINT);
1444: final Color backgroundColor = (Color) styleSheet
1445: .getStyleProperty(ElementStyleKeys.BACKGROUND_COLOR);
1446:
1447: styleBuilder.clear();
1448: if (includeBorder) {
1449: if (backgroundColor != null) {
1450: styleBuilder.append("background-color", HtmlColors
1451: .getColorString(backgroundColor));
1452: }
1453:
1454: final BoxDefinition boxDefinition = box.getBoxDefinition();
1455: final Border border = boxDefinition.getBorder();
1456: final BorderEdge top = border.getTop();
1457: if (top != null) {
1458: styleBuilder.append("border-top", styleBuilder
1459: .printEdgeAsCSS(top));
1460: }
1461: final BorderEdge left = border.getLeft();
1462: if (left != null) {
1463: styleBuilder.append("border-left", styleBuilder
1464: .printEdgeAsCSS(left));
1465: }
1466: final BorderEdge bottom = border.getBottom();
1467: if (bottom != null) {
1468: styleBuilder.append("border-bottom", styleBuilder
1469: .printEdgeAsCSS(bottom));
1470: }
1471: final BorderEdge right = border.getRight();
1472: if (right != null) {
1473: styleBuilder.append("border-right", styleBuilder
1474: .printEdgeAsCSS(right));
1475: }
1476:
1477: final long paddingTop = boxDefinition.getPaddingTop();
1478: final long paddingLeft = boxDefinition.getPaddingLeft();
1479: final long paddingBottom = boxDefinition.getPaddingBottom();
1480: final long paddingRight = boxDefinition.getPaddingRight();
1481: if (paddingTop > 0) {
1482: styleBuilder.append("padding-top", String
1483: .valueOf(StrictGeomUtility
1484: .toExternalValue(paddingTop)), "pt");
1485: }
1486: if (paddingLeft > 0) {
1487: styleBuilder.append("padding-left", String
1488: .valueOf(StrictGeomUtility
1489: .toExternalValue(paddingLeft)), "pt");
1490: }
1491: if (paddingBottom > 0) {
1492: styleBuilder.append("padding-bottom", String
1493: .valueOf(StrictGeomUtility
1494: .toExternalValue(paddingBottom)), "pt");
1495: }
1496: if (paddingRight > 0) {
1497: styleBuilder.append("padding-right", String
1498: .valueOf(StrictGeomUtility
1499: .toExternalValue(paddingRight)), "pt");
1500: }
1501: }
1502: if (textColor != null) {
1503: styleBuilder.append("color", HtmlColors
1504: .getColorString(textColor));
1505: }
1506: styleBuilder.append("font-family", box
1507: .getStaticBoxLayoutProperties().getFontFamily());
1508: styleBuilder.append("font-size", String.valueOf(styleSheet
1509: .getDoubleStyleProperty(TextStyleKeys.FONTSIZE, 0)),
1510: "pt");
1511: if (styleSheet.getBooleanStyleProperty(TextStyleKeys.BOLD)) {
1512: styleBuilder.append("font-weight", "bold");
1513: } else {
1514: styleBuilder.append("font-weight", "normal");
1515: }
1516:
1517: if (styleSheet.getBooleanStyleProperty(TextStyleKeys.ITALIC)) {
1518: styleBuilder.append("font-style", "italic");
1519: } else {
1520: styleBuilder.append("font-style", "normal");
1521: }
1522:
1523: final boolean underlined = styleSheet
1524: .getBooleanStyleProperty(TextStyleKeys.UNDERLINED);
1525: final boolean strikeThrough = styleSheet
1526: .getBooleanStyleProperty(TextStyleKeys.STRIKETHROUGH);
1527: if (underlined && strikeThrough) {
1528: styleBuilder.append("text-decoration",
1529: "underline line-through");
1530: } else if (strikeThrough) {
1531: styleBuilder.append("text-decoration", "line-through");
1532: }
1533: if (underlined) {
1534: styleBuilder.append("text-decoration", "underline");
1535: } else {
1536: styleBuilder.append("text-decoration", "none");
1537: }
1538:
1539: final ElementAlignment align = (ElementAlignment) styleSheet
1540: .getStyleProperty(ElementStyleKeys.ALIGNMENT);
1541: styleBuilder.append("text-align",
1542: translateHorizontalAlignment(align));
1543:
1544: final double wordSpacing = styleSheet.getDoubleStyleProperty(
1545: TextStyleKeys.WORD_SPACING, 0);
1546: styleBuilder.append("word-spacing", styleBuilder
1547: .getPointConverter().format(wordSpacing), "pt");
1548:
1549: final double minLetterSpacing = styleSheet
1550: .getDoubleStyleProperty(
1551: TextStyleKeys.X_MIN_LETTER_SPACING, 0);
1552: final double maxLetterSpacing = styleSheet
1553: .getDoubleStyleProperty(
1554: TextStyleKeys.X_MAX_LETTER_SPACING, 0);
1555: styleBuilder.append("letter-spacing", styleBuilder
1556: .getPointConverter().format(
1557: Math.min(minLetterSpacing, maxLetterSpacing)),
1558: "pt");
1559:
1560: // if (includeWhitespaceCollapse)
1561: // {
1562: // final WhitespaceCollapse wsCollapse = (WhitespaceCollapse)
1563: // styleSheet.getStyleProperty(TextStyleKeys.WHITE_SPACE_COLLAPSE);
1564: // if (WhitespaceCollapse.PRESERVE.equals(wsCollapse))
1565: // {
1566: // styleBuilder.append("white-space", "pre");
1567: // }
1568: // else if (WhitespaceCollapse.PRESERVE_BREAKS.equals(wsCollapse))
1569: // {
1570: // styleBuilder.append("white-space", "nowrap");
1571: // }
1572: // else
1573: // {
1574: // styleBuilder.append("white-space", "normal");
1575: // }
1576: // }
1577:
1578: return styleBuilder;
1579: }
1580:
1581: /**
1582: * Translates the JFreeReport horizontal element alignment into a HTML alignment constant.
1583: *
1584: * @param ea the element alignment
1585: * @return the translated alignment name.
1586: */
1587: public static String translateHorizontalAlignment(
1588: final ElementAlignment ea) {
1589: if (ea == ElementAlignment.RIGHT) {
1590: return "right";
1591: }
1592: if (ea == ElementAlignment.CENTER) {
1593: return "center";
1594: }
1595: return "left";
1596: }
1597:
1598: public void registerFailure(final ResourceKey source) {
1599: knownResources.put(source, Boolean.FALSE);
1600: }
1601:
1602: public void registerContent(final ResourceKey source,
1603: final String name) {
1604: knownResources.put(source, name);
1605: }
1606:
1607: public boolean isRegistered(final ResourceKey source) {
1608: return knownResources.containsKey(source);
1609: }
1610:
1611: public String getRegisteredName(final ResourceKey source) {
1612: final Object o = knownResources.get(source);
1613: if (o instanceof String) {
1614: return (String) o;
1615: }
1616: return null;
1617: }
1618: }
|