org.eclipse.birt.report.engine.layout.pdf.PDFImageLM.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.birt.report.engine.layout.pdf.PDFImageLM.java

Source

/***********************************************************************
 * Copyright (c) 2004 Actuate Corporation.
 * 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:
 * Actuate Corporation - initial API and implementation
 ***********************************************************************/

package org.eclipse.birt.report.engine.layout.pdf;

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.logging.Level;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.birt.report.engine.content.Dimension;
import org.eclipse.birt.report.engine.content.IContainerContent;
import org.eclipse.birt.report.engine.content.IContent;
import org.eclipse.birt.report.engine.content.IHyperlinkAction;
import org.eclipse.birt.report.engine.content.IImageContent;
import org.eclipse.birt.report.engine.content.impl.ActionContent;
import org.eclipse.birt.report.engine.content.impl.ReportContent;
import org.eclipse.birt.report.engine.extension.IReportItemExecutor;
import org.eclipse.birt.report.engine.layout.ILineStackingLayoutManager;
import org.eclipse.birt.report.engine.layout.area.IImageArea;
import org.eclipse.birt.report.engine.layout.area.impl.AreaFactory;
import org.eclipse.birt.report.engine.layout.area.impl.BlockContainerArea;
import org.eclipse.birt.report.engine.layout.area.impl.ContainerArea;
import org.eclipse.birt.report.engine.layout.area.impl.ImageArea;
import org.eclipse.birt.report.engine.layout.pdf.util.PropertyUtil;
import org.eclipse.birt.report.model.api.IResourceLocator;
import org.eclipse.birt.report.model.api.ReportDesignHandle;

import com.lowagie.text.BadElementException;
import com.lowagie.text.Image;

/**
 * 
 * This layout manager implements formatting and locating of image content.
 * <p>
 * Image is an atomic component, so it can not be split. if the size exceeds the
 * boundary, user agent should overflow or clip it.
 * <p>
 * if layout manager can not retrieve the intrinsic dimension of image, layout
 * manager set the intrinsic dimension to the default value (1,1). logger will
 * log this error, but this can not interrupt the layout process.
 * <p>
 * this layout manager generate image area which perhaps has border, render
 * should take responsibility to draw the image and its border
 * <p>
 * the dimension algorithm shows as following table:
 * <p>
 * <table>
 * <tr>
 * <td>scale</td>
 * <td>height</td>
 * <td>width</td>
 * <td>notes</td>
 * </tr>
 * <tr>
 * <td rowspan="3">true</td>
 * <td>X</td>
 * <td>X</td>
 * <td rowspan="3">Following the CSS defined algorithm.</td>
 * </tr>
 * <tr>
 * <td>X</td>
 * <td></td>
 * </tr>
 * <tr>
 * <td></td>
 * <td>X</td>
 * </tr>
 * <tr>
 * <td rowspan="4">false</td>
 * <td>X</td>
 * <td>X</td>
 * <td>Use the defined width and height</td>
 * </tr>
 * <tr>
 * <td>X</td>
 * <td></td>
 * <td>Use the defined height and intrinsic width</td>
 * </tr>
 * <tr>
 * <td></td>
 * <td>X</td>
 * <td>Use the intrinsic height, defined width</td>
 * </tr>
 * <tr>
 * <td></td>
 * <td></td>
 * <td>Use the intrinsic size</td>
 * </tr>
 * 
 * </table>
 * 
 */
public class PDFImageLM extends PDFLeafItemLM {

    protected final static int DEFAULT_WIDHT = 212000;

    protected final static int DEFAULT_HEIGHT = 130000;

    protected IImageContent image;

    protected ContainerArea root;

    private Dimension intrinsic;

    private static final String BOOKMARK_PREFIX = "javascript:catchBookmark('";

    public PDFImageLM(PDFLayoutEngineContext context, PDFStackingLM parent, IContent content,
            IReportItemExecutor executor) {
        super(context, parent, content, executor);
        init();

    }

    /**
     * get intrinsic dimension of image in pixels. Now only support png, bmp,
     * jpg, gif.
     * 
     * @param in
     * @return
     * @throws IOException
     * @throws MalformedURLException
     * @throws BadElementException
     */
    protected Dimension getIntrinsicDimension(IImageContent content)
            throws BadElementException, MalformedURLException, IOException {
        Image image = null;
        switch (content.getImageSource()) {
        case IImageContent.IMAGE_FILE:
            ReportDesignHandle design = content.getReportContent().getDesign().getReportDesign();
            URL url = design.findResource(content.getURI(), IResourceLocator.IMAGE,
                    content.getReportContent().getReportContext() == null ? null
                            : content.getReportContent().getReportContext().getAppContext());
            InputStream in = url.openStream();
            try {
                byte[] buffer = new byte[in.available()];
                in.read(buffer);
                image = Image.getInstance(buffer);
            } catch (Exception ex) {
                logger.log(Level.WARNING, ex.getMessage(), ex);
            } finally {
                in.close();
            }
            break;
        case IImageContent.IMAGE_NAME:
        case IImageContent.IMAGE_EXPRESSION:
            image = Image.getInstance(content.getData());
            break;

        case IImageContent.IMAGE_URL:
            image = Image.getInstance(new URL(content.getURI()));
            break;
        default:
            assert (false);
        }
        if (image != null) {
            int resolution = 96;
            int contentResolution = content.getResolution();
            if (contentResolution != 0) {
                resolution = contentResolution;
            }
            return new Dimension((int) (image.getPlainWidth() * 1000 / resolution * 72),
                    (int) (image.getPlainHeight() * 1000 / resolution * 72));
        }
        return null;
    }

    protected Dimension getSpecifiedDimension(IImageContent content, int pWidth, boolean scale) {
        Dimension dim = new Dimension(DEFAULT_WIDHT, DEFAULT_HEIGHT);
        try {
            intrinsic = getIntrinsicDimension(content);
        } catch (Exception e) {
            logger.log(Level.SEVERE, e.getLocalizedMessage());
        }
        int specifiedWidth = getDimensionValue(content.getWidth(), pWidth);
        int specifiedHeight = getDimensionValue(content.getHeight());
        if (intrinsic == null) {
            dim.setDimension(specifiedWidth == 0 ? DEFAULT_WIDHT : specifiedWidth,
                    specifiedHeight == 0 ? DEFAULT_HEIGHT : specifiedHeight);
            return dim;
        }
        if (scale) {
            double ratio = intrinsic.getRatio();

            if (specifiedWidth > 0) {
                if (specifiedHeight > 0) {
                    dim.setDimension(specifiedWidth, specifiedHeight);
                } else {
                    dim.setDimension(specifiedWidth, (int) (specifiedWidth / ratio));
                }
            } else {
                if (specifiedHeight > 0) {
                    dim.setDimension((int) (specifiedHeight * ratio), specifiedHeight);
                } else {
                    dim.setDimension(intrinsic.getWidth(), intrinsic.getHeight());
                }
            }
        } else {
            if (specifiedWidth > 0) {
                if (specifiedHeight > 0) {
                    dim.setDimension(specifiedWidth, specifiedHeight);
                } else {
                    dim.setDimension(specifiedWidth, intrinsic.getHeight());
                }
            } else {
                if (specifiedHeight > 0) {
                    dim.setDimension(intrinsic.getWidth(), specifiedHeight);
                } else {
                    dim.setDimension(intrinsic.getWidth(), intrinsic.getHeight());
                }
            }
        }
        return dim;
    }

    public boolean layoutChildren() {
        if (root == null) {
            return false;
        }
        // For inline image, the hierarchy is LineArea->InlineContainer->ImageArea.
        // the root is InlineContainer, so we just need to add the root directly to its parent(LineArea).
        // In LineAreaLM, the lineArea will enlarge itself to hold the root.
        // For block image, the hierarchy is BlockContainer->ImageArea
        if (PropertyUtil.isInlineElement(image)) {
            //inline image
            assert (parent instanceof ILineStackingLayoutManager);
            ILineStackingLayoutManager lineParent = (ILineStackingLayoutManager) parent;
            if (root.getAllocatedWidth() > parent.getCurrentMaxContentWidth()) {
                if (!lineParent.isEmptyLine()) {
                    boolean ret = lineParent.endLine();
                    assert (ret);
                    return layoutChildren();
                }
            }
        }
        if (root.getAllocatedHeight() > parent.getCurrentMaxContentHeight()) {
            if (!parent.isPageEmpty()) {
                context.setAutoPageBreak(true);
                return true;
            } else {
                parent.addArea(root, false, false);
                return false;
            }
        } else {
            parent.addArea(root, false, false);
            return false;
        }

    }

    protected void init() {
        assert (content instanceof IImageContent);
        image = (IImageContent) content;

        if (PropertyUtil.isInlineElement(image)) {
            root = (ContainerArea) AreaFactory.createInlineContainer(image, true, true);
        } else {
            root = (ContainerArea) AreaFactory.createBlockContainer(image);
        }

        //First, the width of root is set to its parent's max available width.
        root.setAllocatedWidth(parent.getCurrentMaxContentWidth());

        Dimension contentDimension = getSpecifiedDimension(image, root.getContentWidth(), true);
        ImageArea imageArea = (ImageArea) AreaFactory.createImageArea(image);
        imageArea.setWidth(contentDimension.getWidth());
        imageArea.setHeight(contentDimension.getHeight());

        root.addChild(imageArea);
        imageArea.setPosition(root.getContentX(), root.getContentY());

        //Adjust the dimension of root.
        root.setContentWidth(imageArea.getWidth());
        root.setContentHeight(imageArea.getHeight());
        processChartLegend(image, imageArea);
    }

    /**
     * Creates legend for chart.
     * 
     * @param imageContent
     *            the image content of the chart.
     * @param imageArea
     *            the imageArea of the chart.
     */
    private void processChartLegend(IImageContent imageContent, IImageArea imageArea) {
        if (null == intrinsic) {
            return;
        }
        Object imageMapObject = imageContent.getImageMap();
        boolean hasImageMap = (imageMapObject != null) && (imageMapObject instanceof String)
                && (((String) imageMapObject).length() > 0);
        if (hasImageMap) {
            createImageMap((String) imageMapObject, imageArea);
        }
    }

    private void createImageMap(String imageMapObject, IImageArea imageArea) {
        Pattern pattern = Pattern
                .compile("<AREA[^<>]*coords=\"([\\d,]*)\" href=\"([^<>\"]*)\" target=\"([^<>\"]*)\"/>");
        Matcher matcher = pattern.matcher(imageMapObject);
        while (matcher.find()) {
            try {
                int[] area = getArea(matcher.group(1));
                String url = matcher.group(2);
                String targetWindow = matcher.group(3);
                createImageMap(area, imageArea, url, targetWindow);
            } catch (NumberFormatException e) {
                logger.log(Level.WARNING, e.getMessage(), e);
            }
        }
    }

    private void createImageMap(int[] area, IImageArea imageArea, String url, String targetWindow) {
        if (url == null) {
            return;
        }
        ActionContent link = new ActionContent();
        if (isBookmark(url)) {
            String bookmark = getBookmark(url);
            link.setBookmark(bookmark);
        } else {
            link.setHyperlink(url, targetWindow);
        }
        area = getAbsoluteArea(area, imageArea);
        createImageMapContainer(area[0], area[1], area[2], area[3], link);
    }

    /**
     * Creates an image map container, which is an empty container with an hyper
     * link.
     * 
     * @param x
     *            x coordinate of lower left corner of the container.
     * @param y
     *            y coordinate of lower left corner of the container.
     * @param width
     *            width of the container.
     * @param height
     *            height of the container.
     * @param link
     *            destination of the hyperlink.
     */
    private void createImageMapContainer(int x, int y, int width, int height, IHyperlinkAction link) {
        ReportContent reportContent = (ReportContent) image.getReportContent();
        IContainerContent mapContent = reportContent.createContainerContent();
        mapContent.setHyperlinkAction(link);
        BlockContainerArea area = (BlockContainerArea) AreaFactory.createBlockContainer(mapContent);
        area.setPosition(x, y);
        area.setWidth(width);
        area.setHeight(height);
        root.addChild(area);
    }

    /**
     * Calculates the absolute positions of image map when given the position of
     * image. The image map position is relative to the left up corner of the
     * image.
     * 
     * The argument and returned value are both 4 length integer area, the four
     * value of which are x, y of up left corner, width and height respectively.
     * 
     * @param area
     *            rectangle area of a image map.
     * @param imageArea
     *            image area of the image in which the image map is.
     * @return absolute position of the image map.
     */
    private int[] getAbsoluteArea(int[] area, IImageArea imageArea) {
        assert (intrinsic != null);
        for (int i = 0; i < 4; i++) {
            area[i] = getTranslatedLength(area[i]);
        }
        int[] result = new int[4];
        int imageX = imageArea.getX();
        int imageY = imageArea.getY();
        int imageHeight = imageArea.getHeight();
        int imageWidth = imageArea.getWidth();
        int intrinsicWidth = intrinsic.getWidth();
        int intrinsicHeight = intrinsic.getHeight();
        float ratio = (float) imageWidth / (float) intrinsicWidth;
        result[0] = imageX + (int) (area[0] * ratio);
        result[2] = (int) (area[2] * ratio);
        ratio = (float) imageHeight / (float) intrinsicHeight;
        result[1] = imageY + (int) (area[1] * ratio);
        result[3] = (int) (area[3] * ratio);
        return result;
    }

    private int getTranslatedLength(int length) {
        return length * 1000 / 192 * 72;
    }

    /**
     * Check if a url is of an internal bookmark.
     * 
     * @param url
     *            the url string.
     * @return true if and only if the url is of an internal bookmark.
     */
    private boolean isBookmark(String url) {
        return url.startsWith(BOOKMARK_PREFIX) && url.endsWith("')");
    }

    /**
     * Parses out bookmark name from a url for interanl bookmark.
     * 
     * @param url
     *            the url string
     * @return the bookmark name.
     */
    private String getBookmark(String url) {
        int start = url.indexOf(BOOKMARK_PREFIX) + BOOKMARK_PREFIX.length();
        int end = url.length() - 2;
        return url.substring(start, end);
    }

    /**
     * Parse the image map position from a string which is of format "x1, y1, x2,
     * y2".
     * 
     * @param string
     *            the position string.
     * @return a array which contains the x, y coordinate of left up corner,
     *         width and height in sequence.
     * 
     */
    private int[] getArea(String string) {
        String[] rawDatas = string.split(",");
        int[] area = new int[4];
        area[0] = Integer.parseInt(rawDatas[0]);
        area[1] = Integer.parseInt(rawDatas[1]);
        area[2] = Integer.parseInt(rawDatas[4]) - area[0];
        area[3] = Integer.parseInt(rawDatas[5]) - area[1];
        return area;
    }

}