org.eclipse.birt.report.engine.nLayout.area.impl.ImageAreaLayout.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.birt.report.engine.nLayout.area.impl.ImageAreaLayout.java

Source

/***********************************************************************
 * Copyright (c) 2009 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.nLayout.area.impl;

import java.io.IOException;
import java.net.MalformedURLException;
import java.util.Map;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.birt.core.exception.BirtException;
import org.eclipse.birt.report.engine.api.IEngineTask;
import org.eclipse.birt.report.engine.api.IPDFRenderOption;
import org.eclipse.birt.report.engine.content.Dimension;
import org.eclipse.birt.report.engine.content.IHyperlinkAction;
import org.eclipse.birt.report.engine.content.IImageContent;
import org.eclipse.birt.report.engine.content.IReportContent;
import org.eclipse.birt.report.engine.content.IStyle;
import org.eclipse.birt.report.engine.content.ITextContent;
import org.eclipse.birt.report.engine.content.impl.ActionContent;
import org.eclipse.birt.report.engine.content.impl.ObjectContent;
import org.eclipse.birt.report.engine.emitter.ImageReader;
import org.eclipse.birt.report.engine.i18n.EngineResourceHandle;
import org.eclipse.birt.report.engine.i18n.MessageConstants;
import org.eclipse.birt.report.engine.ir.ImageItemDesign;
import org.eclipse.birt.report.engine.layout.pdf.util.PropertyUtil;
import org.eclipse.birt.report.engine.nLayout.LayoutContext;
import org.eclipse.birt.report.engine.nLayout.area.IImageArea;
import org.eclipse.birt.report.engine.nLayout.area.ILayout;
import org.eclipse.birt.report.engine.nLayout.area.style.BoxStyle;

import com.ibm.icu.util.ULocale;
import com.lowagie.text.BadElementException;
import com.lowagie.text.Image;

public class ImageAreaLayout implements ILayout {

    private ILayout layout = null;
    private ContainerArea parent;
    private IImageContent content;
    private ImageReader reader;
    private Image imageObject = null;
    private LayoutContext context;

    protected static Logger logger = Logger.getLogger(ImageAreaLayout.class.getName());

    private static Pattern pattern = Pattern.compile(" ([^=]*)=\"([^\"]*)\"");

    public ImageAreaLayout(ContainerArea parent, LayoutContext context, IImageContent content) {
        this.parent = parent;
        this.content = content;
        this.context = context;
    }

    public void layout() throws BirtException {
        initialize();
        if (layout != null) {
            layout.layout();
        }
    }

    protected void initialize() throws BirtException {
        // choose the layout manager
        reader = new ImageReader(content, context.getSupportedImageFormats());
        int result = reader.read();
        switch (result) {
        case ImageReader.RESOURCE_UNREACHABLE:
            // display the alt text or prompt object not accessible.
            layout = createAltTextLayout(ImageReader.RESOURCE_UNREACHABLE);
            break;
        case ImageReader.UNSUPPORTED_OBJECTS:
            // display the alt text or prompt unsupported objects.
            layout = createAltTextLayout(ImageReader.UNSUPPORTED_OBJECTS);
            break;
        case ImageReader.OBJECT_LOADED_SUCCESSFULLY:
            //the object is accessible.
            if (reader.getType() == ImageReader.TYPE_IMAGE_OBJECT
                    || reader.getType() == ImageReader.TYPE_CONVERTED_SVG_OBJECT) {
                try {
                    imageObject = Image.getInstance(reader.getByteArray());
                } catch (Exception e) {
                    logger.log(Level.WARNING, e.getLocalizedMessage());
                }
                // unrecognized image formats.
                if (imageObject == null) {
                    layout = createAltTextLayout(ImageReader.UNSUPPORTED_OBJECTS);
                    break;
                }
            }
            layout = new ConcreteImageLayout(context, parent, content, reader.getByteArray());
            break;
        }
    }

    private ILayout createAltTextLayout(int altTextType) {
        ITextContent altTextContent = createAltText((IImageContent) content, altTextType);
        if (null == altTextContent) {
            return null;
        }
        BlockTextArea bta = new BlockTextArea(parent, context, altTextContent);
        bta.setHelpText(altTextContent.getText());
        return bta;
    }

    private ITextContent createAltText(IImageContent imageContent, int altTextType) {
        IReportContent report = imageContent.getReportContent();
        if (report == null) {
            return null;
        }
        ITextContent altTextContent = report.createTextContent(imageContent);
        altTextContent.setParent(imageContent.getParent());
        String alt = imageContent.getAltText();
        if (null == alt) {
            ULocale locale = ULocale.forLocale(context.getLocale());
            if (locale == null) {
                locale = ULocale.getDefault();
            }
            EngineResourceHandle resourceHandle = new EngineResourceHandle(locale);
            if (altTextType == ImageReader.UNSUPPORTED_OBJECTS) {
                if (reader.getType() == ImageReader.TYPE_FLASH_OBJECT) {
                    alt = resourceHandle.getMessage(MessageConstants.FLASH_OBJECT_NOT_SUPPORTED_PROMPT);
                } else {
                    alt = resourceHandle.getMessage(MessageConstants.REPORT_ITEM_NOT_SUPPORTED_PROMPT);
                }
            }
            if (altTextType == ImageReader.RESOURCE_UNREACHABLE) {
                alt = resourceHandle.getMessage(MessageConstants.RESOURCE_UNREACHABLE_PROMPT);
            }
        }
        altTextContent.setText(alt);
        return altTextContent;
    }

    class ConcreteImageLayout implements ILayout {

        /** The DpiX */
        private int resolutionX = 0;

        /** The DpiY */
        private int resolutionY = 0;

        //private Image imageObject = null;
        private byte[] data;

        private ContainerArea parent;

        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_JAVASCRIPT_PREFIX = "javascript:catchBookmark('";

        private static final String BOOKMARK_URL_PREFIX = "__bookmark=";

        private static final String BOOKMARK_ANCHOR_PREFIX = "#";

        private LayoutContext context;

        private boolean fitToContainer = false;

        private BlockTextArea innerText = null;

        public ConcreteImageLayout(LayoutContext context, ContainerArea parent, IImageContent content,
                byte[] data) {
            this.context = context;
            this.image = content;
            this.parent = parent;
            this.data = data;

            Object reportItemDesign = content.getGenerateBy();
            if (null != reportItemDesign) {
                if (reportItemDesign instanceof ImageItemDesign) {
                    fitToContainer = ((ImageItemDesign) reportItemDesign).isFitToContainer();
                }
            }
        }

        /**
         * get intrinsic dimension of image in pixels. Now only support png,
         * bmp, jpg, gif.
         * 
         * @return
         * @throws IOException
         * @throws MalformedURLException
         * @throws BadElementException
         */
        protected Dimension getIntrinsicDimension(IImageContent content, Image image) {
            if (image != null) {
                return new Dimension((int) (image.getPlainWidth() * 1000 / resolutionX * 72),
                        (int) (image.getPlainHeight() * 1000 / resolutionY * 72));
            }
            return null;
        }

        protected Dimension getSpecifiedDimension(IImageContent content, int pWidth, boolean scale) {
            // prepare the DPI for the image.
            int imageFileDpiX = 0;
            int imageFileDpiY = 0;

            if (reader.getType() == ImageReader.TYPE_IMAGE_OBJECT
                    || reader.getType() == ImageReader.TYPE_CONVERTED_SVG_OBJECT) {
                if (imageObject != null) {
                    imageFileDpiX = imageObject.getDpiX();
                    imageFileDpiY = imageObject.getDpiY();
                }
            }
            resolutionX = PropertyUtil.getImageDpi(content, imageFileDpiX, context.getDpi());
            resolutionY = PropertyUtil.getImageDpi(content, imageFileDpiY, context.getDpi());

            try {
                intrinsic = getIntrinsicDimension(content, imageObject);
            } catch (Exception e) {
                logger.log(Level.SEVERE, e.getLocalizedMessage());
            }
            int specifiedWidth = PropertyUtil.getImageDimensionValue(content, content.getWidth(), context.getDpi(),
                    pWidth);
            int specifiedHeight = PropertyUtil.getImageDimensionValue(content, content.getHeight(),
                    context.getDpi(), -1);

            Dimension dim = new Dimension(DEFAULT_WIDHT, DEFAULT_HEIGHT);
            if (intrinsic == null) {
                dim.setDimension(specifiedWidth == -1 ? DEFAULT_WIDHT : specifiedWidth,
                        specifiedHeight == -1 ? DEFAULT_HEIGHT : specifiedHeight);
                return dim;
            }
            if (scale) // always does 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 void layout() throws BirtException {
            init();

            boolean isEmptyLine = true;
            boolean innerTextInserted = false;
            if ("pdf".equalsIgnoreCase(context.getFormat()) && reader.getType() == ImageReader.TYPE_FLASH_OBJECT) {
                innerTextInserted = true;
                innerText = createInnerTextLayout();
                innerText.content.getStyle().setProperty(IStyle.STYLE_TEXT_ALIGN, IStyle.CENTER_VALUE);
                innerText.setVerticalAlign(IStyle.MIDDLE_VALUE);
                innerText.setIgnoreReordering(true);
                // save current root status
                if (PropertyUtil.isInlineElement(image)) {
                    // inline image
                    InlineStackingArea lineParent = (InlineStackingArea) parent;
                    isEmptyLine = lineParent.isEmptyLine();
                }
                int lastIP = root.currentIP;
                int lastBP = root.currentBP;

                innerText.layout();
                // set the text position manually.
                innerText.setAllocatedPosition(0, 0);
                int rootHeight = root.getContentHeight();
                if (rootHeight < innerText.getHeight()) {
                    innerText.setHeight((rootHeight - 1000) > 0 ? (rootHeight - 1000) : 0);
                    innerText.setNeedClip(true);
                }
                // restore the root status.
                root.currentIP = lastIP;
                root.currentBP = lastBP;
            }

            // 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 InlineStackingArea);
                InlineStackingArea lineParent = (InlineStackingArea) parent;
                if (root.getAllocatedWidth() > parent.getCurrentMaxContentWidth()) {
                    if ((innerTextInserted && !isEmptyLine) || (!innerTextInserted && !lineParent.isEmptyLine())) {
                        lineParent.endLine(false);
                        layout();
                    } else {
                        parent.add(root);
                        root.finished = true;
                        parent.update(root);
                    }
                } else {
                    parent.add(root);
                    root.finished = true;
                    parent.update(root);
                }
            } else {
                parent.add(root);
                if (!parent.isInInlineStacking && context.isAutoPageBreak()) {
                    int aHeight = root.getAllocatedHeight();
                    if (aHeight + parent.getAbsoluteBP() > context.getMaxBP()) {
                        parent.autoPageBreak();
                    }
                }
                root.finished = true;
                parent.update(root);
            }
            checkDisplayNone();
        }

        protected void checkDisplayNone() {
            if (context != null && context.isDisplayNone()) {
                int aHeight = root.getAllocatedHeight();
                parent.setCurrentBP(parent.currentBP - aHeight);
                root.height = 0;
            }
        }

        protected void init() throws BirtException {

            if (PropertyUtil.isInlineElement(image)) {
                root = new ImageInlineContainer(parent, context, image);
            } else {
                root = new ImageBlockContainer(parent, context, image);
            }

            root.initialize();

            // First, the width of root is set to its parent's max available
            // width.
            root.setAllocatedWidth(parent.getMaxAvaWidth());
            root.setMaxAvaWidth(root.getContentWidth());
            Dimension contentDimension = getSpecifiedDimension(image, root.getContentWidth(), true);
            ImageArea imageArea = createImageArea(image);
            imageArea.setParent(root);
            // implement fitToContainer the maxHeight is the image's max
            // possible height in an empty page.
            int maxHeight = root.getMaxAvaHeight();
            int maxWidth = root.getMaxAvaWidth();
            int cHeight = contentDimension.getHeight();
            int cWidth = contentDimension.getWidth();

            int actualHeight = cHeight;
            int actualWidth = cWidth;

            if (cHeight > maxHeight || cWidth > maxWidth) {
                if (fitToContainer) {
                    float rh = ((float) maxHeight) / cHeight;
                    float rw = ((float) maxWidth) / cWidth;
                    if (rh > rw) {
                        actualHeight = (int) ((float) cHeight * maxWidth / cWidth);
                        actualWidth = maxWidth;
                    } else {
                        actualHeight = maxHeight;
                        actualWidth = (int) ((float) cWidth * maxHeight / cHeight);
                    }
                    imageArea.setWidth(actualWidth);
                    imageArea.setHeight(actualHeight);
                    root.setContentWidth(imageArea.getWidth());
                    root.setContentHeight(imageArea.getHeight());
                } else {
                    // Fix Bugzilla  Bug 268921 [Automation][Regression]Fit to
                    // page does not work in PDF
                    if (context.getPageOverflow() == IPDFRenderOption.FIT_TO_PAGE_SIZE
                            || context.getPageOverflow() == IPDFRenderOption.ENLARGE_PAGE_SIZE) {
                        imageArea.setWidth(actualWidth);
                        imageArea.setHeight(actualHeight);
                        root.setContentHeight(actualHeight);
                        root.setContentWidth(actualWidth);
                    } else {
                        imageArea.setWidth(actualWidth);
                        imageArea.setHeight(actualHeight);
                        root.setNeedClip(true);
                        root.setContentHeight(Math.min(maxHeight, cHeight));
                        root.setContentWidth(Math.min(maxWidth, cWidth));
                        //Fix Bugzilla - Bug 271555   The right and bottom border are still shown even the chart exceeds the page size and got cut in PDF [1200]
                        //a temporary solution. root should set the same dimension with imageArea, but currently can not find a solution to avoid empty page when a large image is put into a grid 
                        if (maxWidth < cWidth) {
                            // default box style is unmodified.
                            // creates a new instance when style is default style.
                            if (root.getBoxStyle() == BoxStyle.DEFAULT)
                                root.setBoxStyle(new BoxStyle(BoxStyle.DEFAULT));
                            root.getBoxStyle().setRightBorder(null);
                        }
                        if (maxHeight < cHeight) {
                            if (root.getBoxStyle() == BoxStyle.DEFAULT)
                                root.setBoxStyle(new BoxStyle(BoxStyle.DEFAULT));
                            root.getBoxStyle().setBottomBorder(null);
                        }
                    }
                }
            } else {
                imageArea.setWidth(actualWidth);
                imageArea.setHeight(actualHeight);
                root.setContentWidth(imageArea.getWidth());
                root.setContentHeight(imageArea.getHeight());
            }
            root.addChild(imageArea);
            imageArea.setPosition(root.getContentX(), root.getContentY());

            if (context.getEngineTaskType() != IEngineTask.TASK_RUN) {
                processChartLegend(image, imageArea);
            }
            root.finished = false;
        }

        protected ImageArea createImageArea(IImageContent content) {
            ImageArea area = new ImageArea();
            String mimeType = content.getMIMEType();
            String extension = content.getExtension();
            area.setExtension(extension);
            area.setMIMEType(mimeType);
            switch (content.getImageSource()) {
            case IImageContent.IMAGE_FILE:
            case IImageContent.IMAGE_URL:
                area.setUrl(content.getURI());
                break;
            case IImageContent.IMAGE_NAME:
                area.setUrl("NamedImage_" + content.getURI());
                break;
            case IImageContent.IMAGE_EXPRESSION:
                break;
            }

            area.setData(data);

            if (reader.getType() == ImageReader.TYPE_SVG_OBJECT) {
                area.setMIMEType("image/svg+xml");
                area.setExtension(".svg");
            }
            if (reader.getType() == ImageReader.TYPE_CONVERTED_SVG_OBJECT) {
                // this SVG has been converted into JPEG.
                area.setMIMEType("image/jpeg");
                area.setExtension(".jpg");
            }

            if (content instanceof ObjectContent) {
                ObjectContent object = (ObjectContent) content;
                area.setParameters(object.getParamters());
            }
            area.setAction(content.getHyperlinkAction());
            return area;
        }

        private BlockTextArea createInnerTextLayout() {
            IReportContent report = image.getReportContent();
            if (report == null) {
                return null;
            }
            ITextContent promptTextContent = report.createTextContent(image);
            ULocale locale = ULocale.forLocale(context.getLocale());
            if (locale == null) {
                locale = ULocale.getDefault();
            }
            EngineResourceHandle resourceHandle = new EngineResourceHandle(locale);

            String prompt = resourceHandle.getMessage(MessageConstants.UPDATE_USER_AGENT_PROMPT);

            promptTextContent.setText(prompt);
            return new BlockTextArea(root, context, promptTextContent);
        }

        /**
         * 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) {
            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) {
            if (imageMapObject == null) {
                return;
            }
            String[] maps = imageMapObject.split("/>");

            for (String map : maps) {
                map = map.trim();
                if (map.length() == 0) {
                    continue;
                }
                Map<String, String> attributes = new TreeMap<String, String>();
                Matcher matcher = pattern.matcher(map);
                while (matcher.find()) {
                    attributes.put(matcher.group(1), matcher.group(2));
                }
                try {
                    if (attributes.size() > 0) {
                        int[] area = getArea(attributes.get("coords"));
                        if (area == null) {
                            continue;
                        }
                        String url = attributes.get("href");
                        //does not support javascript in href.
                        if (url != null && url.startsWith("javascript:")) {
                            url = null;
                        }
                        String targetWindow = attributes.get("target");
                        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 || url.length() == 0) {
                return;
            }
            url = url.replaceAll("&amp;", "&");
            ActionContent link = new ActionContent();
            String bookmark = getBookmark(url);
            if (bookmark != null) {
                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) {
            BlockContainerArea area = new BlockContainerArea();
            area.setAction(link);
            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) {
            int[] result = new int[4];
            if (intrinsic == null) {
                // this case is for SVG chart.
                // the image map of SVG chart is in Point.
                int imageX = imageArea.getX();
                int imageY = imageArea.getY();
                result[0] = imageX + (int) (area[0] * 1000);
                result[2] = (int) (area[2] * 1000);
                result[1] = imageY + (int) (area[1] * 1000);
                result[3] = (int) (area[3] * 1000);
            } else {
                for (int i = 0; i < 4;) {
                    area[i] = getTranslatedLengthX(area[i]);
                    i++;
                    area[i] = getTranslatedLengthY(area[i]);
                    i++;
                }
                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;
        }

        /**
         * 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) {
            if (string == null) {
                return null;
            }
            String[] rawDatas = string.split(",");
            if (rawDatas.length == 8) {
                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;
            } else {
                if (rawDatas.length >= 6)
                    return generateRectangleByPolygon(rawDatas);
            }
            return null;
        }

        private int[] generateRectangleByPolygon(String[] values) {
            int[] intValues = convertToInt(values);
            int[] xValues = new int[values.length / 2];
            for (int i = 0; i < values.length; i = i + 2) {
                xValues[i / 2] = intValues[i];
            }
            int[] yValues = new int[values.length / 2];
            for (int i = 1; i < values.length; i = i + 2) {
                yValues[i / 2] = intValues[i];
            }
            int maxX = getMax(xValues);
            int maxY = getMax(yValues);
            int minX = getMin(xValues);
            int minY = getMin(yValues);
            int avaX = getAva(xValues);
            int avaY = getAva(yValues);
            return new int[] { avaX - (avaX - minX) / 2, avaY - (avaY - minY) / 2, (maxX - minX) / 2,
                    (maxY - minY) / 2 };
        }

        private int getMax(int[] values) {
            int max = Integer.MIN_VALUE;
            for (int i = 0; i < values.length; i++) {
                if (values[i] > max) {
                    max = values[i];
                }
            }
            return max;
        }

        private int getAva(int[] values) {
            int total = 0;
            for (int i = 0; i < values.length; i++) {
                total += values[i];
            }
            return total / values.length;
        }

        private int getMin(int[] values) {
            int min = Integer.MAX_VALUE;
            for (int i = 0; i < values.length; i++) {
                if (values[i] < min) {
                    min = values[i];
                }
            }
            return min;
        }

        private int[] convertToInt(String[] values) {
            int[] intValues = new int[values.length];
            for (int i = 0; i < values.length; i++) {
                intValues[i] = Integer.parseInt(values[i]);
            }
            return intValues;
        }

        private int getTranslatedLengthX(int length) {
            return length * 1000 / resolutionX * 72;
        }

        private int getTranslatedLengthY(int length) {
            return length * 1000 / resolutionY * 72;
        }

        /**
         * 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_URL_PREFIX);
            int end = -1;
            if (start != -1) {
                start += BOOKMARK_URL_PREFIX.length();
                end = url.indexOf("&", start);
                if (end == -1) {
                    end = url.length();
                }
                return url.substring(start, end);
            } else if (url.startsWith(BOOKMARK_ANCHOR_PREFIX)) {
                start = BOOKMARK_ANCHOR_PREFIX.length();
                end = url.length();
                return url.substring(start, end);
            } else if (url.startsWith(BOOKMARK_JAVASCRIPT_PREFIX) && url.endsWith("')")) {
                start = BOOKMARK_JAVASCRIPT_PREFIX.length();
                end = url.length() - 2;
                return url.substring(start, end);
            }
            return null;
        }

        protected void close() {
            // if ( !PropertyUtil.isInlineElement( image ) )
            // We align inline elements (here - inline container parenting the
            // inline image) in LineLayout, but not block-level image.
            // Invoke it here, since it should not be done by ContainerLayout
            // always.
            // TODO: Check if this can be done in a neater way.
            // parent.align( root );
        }

    }
}