org.pentaho.reporting.libraries.pixie.wmf.WmfFile.java Source code

Java tutorial

Introduction

Here is the source code for org.pentaho.reporting.libraries.pixie.wmf.WmfFile.java

Source

/*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* Copyright (c) 2001 - 2013 Object Refinery Ltd, Hitachi Vantara and Contributors..  All rights reserved.
*/

package org.pentaho.reporting.libraries.pixie.wmf;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pentaho.reporting.libraries.base.util.FastStack;
import org.pentaho.reporting.libraries.pixie.wmf.records.CommandFactory;
import org.pentaho.reporting.libraries.pixie.wmf.records.MfCmd;
import org.pentaho.reporting.libraries.pixie.wmf.records.MfCmdSetWindowExt;
import org.pentaho.reporting.libraries.pixie.wmf.records.MfCmdSetWindowOrg;

import java.awt.*;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;

/**
 * Parses and replays the WmfFile.
 */
public class WmfFile {
    private static final Log logger = LogFactory.getLog(WmfFile.class);

    public static final int QUALITY_NO = 0; // Can't convert.
    public static final int QUALITY_MAYBE = 1; // Might be able to convert.
    public static final int QUALITY_YES = 2; // Can convert.

    // Maximal picture size is 1200x1200. A average wmf file scales easily
    // to 20000 and more, so we have to limit the pixel image's size.

    private static final int MAX_PICTURE_SIZE = getMaxPictureSize();

    private static int getMaxPictureSize() {
        return 1200;
    }

    private WmfObject[] objects;
    private FastStack dcStack;
    private MfPalette palette;

    //private String inName;
    private InputStream in;
    private MfHeader header;
    private int fileSize;
    private int filePos;

    private ArrayList records;
    private Graphics2D graphics;

    private int maxWidth;
    private int maxHeight;
    private int imageWidth;
    private int imageHeight;

    private int minX;
    private int minY;
    private int imageX;
    private int imageY;

    /**
     * Initialize metafile for reading from an URL. Width and height will be computed automatically.
     *
     * @param input the URL from where to read.
     * @throws IOException if any other error occured.
     */
    public WmfFile(final URL input) throws IOException {
        this(input, -1, -1);
    }

    /**
     * Initialize metafile for reading from file. Width and height will be computed automatically.
     *
     * @param input the name of the file from where to read.
     * @throws IOException if any other error occured.
     */
    public WmfFile(final String input) throws IOException {
        this(input, -1, -1);
    }

    /**
     * Initialize metafile for reading from an URL.
     *
     * @param imageWidth  the target width of the image or -1 for automatic mode.
     * @param imageHeight the target height of the image or -1 for automatic mode.
     * @param input       the URL from where to read.
     * @throws IOException if any other error occured.
     */
    public WmfFile(final URL input, final int imageWidth, final int imageHeight) throws IOException {
        this(new BufferedInputStream(input.openStream()), imageWidth, imageHeight);
    }

    /**
     * Initialize metafile for reading from filename.
     *
     * @param imageWidth  the target width of the image or -1 for automatic mode.
     * @param imageHeight the target height of the image or -1 for automatic mode.
     * @param inName      the file name from where to read.
     * @throws FileNotFoundException if the file was not found.
     * @throws IOException           if any other error occured.
     */
    public WmfFile(final String inName, final int imageWidth, final int imageHeight)
            throws FileNotFoundException, IOException {
        this(new BufferedInputStream(new FileInputStream(inName)), imageWidth, imageHeight);
    }

    /**
     * Initialize metafile for reading from the given input stream.
     *
     * @param imageWidth  the target width of the image or -1 for automatic mode.
     * @param imageHeight the target height of the image or -1 for automatic mode.
     * @param in          the stream from where to read.
     * @throws IOException if any other error occured.
     */
    public WmfFile(final InputStream in, final int imageWidth, final int imageHeight) throws IOException {
        this.in = in;
        this.imageWidth = imageWidth;
        this.imageHeight = imageHeight;
        records = new ArrayList();
        dcStack = new FastStack(100);
        palette = new MfPalette();
        readHeader();
        parseRecords();
        resetStates();
    }

    public Dimension getImageSize() {
        return new Dimension(maxWidth, maxHeight);
    }

    private void resetStates() {
        Arrays.fill(objects, null);
        dcStack.clear();
        dcStack.push(new MfDcState(this));
    }

    public MfPalette getPalette() {
        return palette;
    }

    /**
     * Return Placeable and Windows headers that were read earlier.
     *
     * @return the meta-file header.
     */
    public MfHeader getHeader() {
        return header;
    }

    public Graphics2D getGraphics2D() {
        return graphics;
    }

    /**
     * Check class invariant.
     */
    private void assertValid() {
        if (filePos < 0 || filePos > fileSize) {
            throw new IllegalStateException("WmfFile is not valid");
        }
    }

    /**
     * Read Placeable and Windows headers.
     */
    private MfHeader readHeader() throws IOException {
        header = new MfHeader();
        header.read(in);
        if (header.isValid()) {
            fileSize = header.getFileSize();
            objects = new WmfObject[header.getObjectsSize()];
            filePos = header.getHeaderSize();
            return header;
        } else {
            throw new IOException("The given file is not a real metafile");
        }
    }

    /**
     * Fetch a record.
     *
     * @return the next record read or null, if the end-of-file has been reached.
     * @throws IOException if an IO-Error occurs.
     */
    private MfRecord readNextRecord() throws IOException {
        if (filePos >= fileSize) {
            return null;
        }

        assertValid();

        final MfRecord record = new MfRecord(in);
        filePos += record.getLength();
        return record;
    }

    /**
     * Read and interpret the body of the metafile.
     *
     * @throws IOException if an IO-Error occurs.
     */
    private void parseRecords() throws IOException {
        minX = Integer.MAX_VALUE;
        minY = Integer.MAX_VALUE;
        maxWidth = 0;
        maxHeight = 0;

        final CommandFactory cmdFactory = CommandFactory.getInstance();
        MfRecord mf;
        while ((mf = readNextRecord()) != null) {
            final MfCmd cmd = cmdFactory.getCommand(mf.getType());
            if (cmd == null) {
                logger.info("Failed to parse record " + mf.getType());
            } else {
                cmd.setRecord(mf);

                if (cmd.getFunction() == MfType.SET_WINDOW_ORG) {
                    final MfCmdSetWindowOrg worg = (MfCmdSetWindowOrg) cmd;
                    final Point p = worg.getTarget();
                    minX = Math.min(p.x, minX);
                    minY = Math.min(p.y, minY);
                } else if (cmd.getFunction() == MfType.SET_WINDOW_EXT) {
                    final MfCmdSetWindowExt worg = (MfCmdSetWindowExt) cmd;
                    final Dimension d = worg.getDimension();
                    maxWidth = Math.max(maxWidth, d.width);
                    maxHeight = Math.max(maxHeight, d.height);
                }
                records.add(cmd);
            }
        }
        in.close();
        in = null;

        // make sure that we don't have invalid values in case no
        // setWindow records were found ...
        if (minX == Integer.MAX_VALUE) {
            minX = 0;
        }
        if (minY == Integer.MAX_VALUE) {
            minY = 0;
        }

        //System.out.println(records.size() + " records read");
        //System.out.println("Image Extends: " + maxWidth + " " + maxHeight);
        if (imageWidth < 1 || imageHeight < 1) {
            scaleToFit(MAX_PICTURE_SIZE, MAX_PICTURE_SIZE);
        } else {
            scaleToFit(imageWidth, imageHeight);
        }
    }

    /**
     * Scales the WMF-image to the given width and height while preserving the aspect ration.
     *
     * @param fitWidth  the target width.
     * @param fitHeight the target height.
     */
    public void scaleToFit(final float fitWidth, final float fitHeight) {
        final float percentX = (fitWidth * 100) / maxWidth;
        final float percentY = (fitHeight * 100) / maxHeight;
        scalePercent(percentX < percentY ? percentX : percentY);
    }

    /**
     * Scale the image to a certain percentage.
     *
     * @param percent the scaling percentage <!-- Yes, this is from iText lib -->
     */
    public void scalePercent(final float percent) {
        scalePercent(percent, percent);
    }

    /**
     * Scale the width and height of an image to a certain percentage.
     *
     * @param percentX the scaling percentage of the width
     * @param percentY the scaling percentage of the height <!-- Yes, this is from iText lib -->
     */
    public void scalePercent(final float percentX, final float percentY) {
        imageWidth = (int) ((maxWidth * percentX) / 100f);
        imageHeight = (int) ((maxHeight * percentY) / 100f);
        imageX = (int) ((minX * percentX) / 100f);
        imageY = (int) ((minY * percentY) / 100f);
    }

    public static void main(final String[] args) throws Exception {
        final WmfFile wmf = new WmfFile("./head/pixie/res/a0.wmf", 800, 600);
        wmf.replay();
        // System.out.println(wmf.imageWidth + ", " + wmf.imageHeight);
    }

    public MfDcState getCurrentState() {
        return (MfDcState) dcStack.peek();
    }

    // pushes a state on the stack
    public void saveDCState() {
        final MfDcState currentState = getCurrentState();
        dcStack.push(new MfDcState(currentState));

    }

    public int getStateCount() {
        return dcStack.size();
    }

    /**
     * Restores a state. The stateCount specifies the number of states to discard to find the correct one.
     *
     * @param stateCount the state count.
     */
    public void restoreDCState(final int stateCount) {
        if ((stateCount > 0) == false) {
            throw new IllegalArgumentException();
        }
        // this is contrary to Caolans description of the WMF file format, but
        // Batik also ignores the stateCount parameter.
        dcStack.pop();
        getCurrentState().restoredState();
    }

    /**
     * Return the next free slot from the objects table.
     *
     * @return the next new free slot in the objects-registry
     */
    protected int findFreeSlot() {
        for (int slot = 0; slot < objects.length; slot++) {
            if (objects[slot] == null) {
                return slot;
            }
        }

        throw new IllegalStateException("No free slot");
    }

    public void storeObject(final WmfObject o) {
        final int idx = findFreeSlot();
        objects[idx] = o;
    }

    public void deleteObject(final int slot) {
        if ((slot < 0) || (slot >= objects.length)) {
            throw new IllegalArgumentException("Range violation");
        }

        objects[slot] = null;
    }

    public WmfObject getObject(final int slot) {
        if ((slot < 0) || (slot >= objects.length)) {
            throw new IllegalStateException("Range violation");
        }

        return objects[slot];
    }

    public MfLogBrush getBrushObject(final int slot) {
        final WmfObject obj = getObject(slot);
        if (obj.getType() == WmfObject.OBJ_BRUSH) {
            return (MfLogBrush) obj;
        }
        throw new IllegalStateException("Object " + slot + " was no brush");
    }

    public MfLogPen getPenObject(final int slot) {
        final WmfObject obj = getObject(slot);
        if (obj.getType() == WmfObject.OBJ_PEN) {
            return (MfLogPen) obj;
        }
        throw new IllegalStateException("Object " + slot + " was no pen");
    }

    public MfLogRegion getRegionObject(final int slot) {
        final WmfObject obj = getObject(slot);
        if (obj.getType() == WmfObject.OBJ_REGION) {
            return (MfLogRegion) obj;
        }
        throw new IllegalStateException("Object " + slot + " was no region");
    }

    public synchronized BufferedImage replay() {
        return replay(imageWidth, imageHeight);
    }

    public synchronized BufferedImage replay(final int imageX, final int imageY) {
        final BufferedImage image = new BufferedImage(imageX, imageY, BufferedImage.TYPE_INT_ARGB);
        final Graphics2D graphics = image.createGraphics();

        // clear the image area ...
        graphics.setPaint(new Color(0, 0, 0, 0));
        graphics.fill(new Rectangle(0, 0, imageX, imageY));

        draw(graphics, new Rectangle2D.Float(0, 0, imageX, imageY));
        graphics.dispose();
        return image;
    }

    public synchronized void draw(final Graphics2D graphics, final Rectangle2D bounds) {

        // this adjusts imageWidth and imageHeight
        scaleToFit((float) bounds.getWidth(), (float) bounds.getHeight());
        // adjust translation if needed ...
        graphics.translate(bounds.getX(), bounds.getY());
        // adjust to the image origin
        graphics.translate(-imageX, -imageY);

        this.graphics = graphics;

        for (int i = 0; i < records.size(); i++) {
            try {
                final MfCmd command = (MfCmd) records.get(i);
                command.setScale((float) imageWidth / (float) maxWidth, (float) imageHeight / (float) maxHeight);
                command.replay(this);
            } catch (Exception e) {
                logger.warn("Error while processing image record #" + i, e);
            }
        }
        resetStates();
    }

    /**
     * Returns the preferred size of the drawable. If the drawable is aspect ratio aware, these bounds should be used to
     * compute the preferred aspect ratio for this drawable.
     *
     * @return the preferred size.
     */
    public Dimension getPreferredSize() {
        return new Dimension(imageWidth, imageHeight);
    }

    /**
     * Returns true, if this drawable will preserve an aspect ratio during the drawing.
     *
     * @return true, if an aspect ratio is preserved, false otherwise.
     */
    public boolean isPreserveAspectRatio() {
        return true;
    }

    public String toString() {
        final StringBuffer bo = new StringBuffer(500);
        bo.append("WmfFile={width=");
        bo.append(imageWidth);
        bo.append(", height=");
        bo.append(imageHeight);
        bo.append(", recordCount=");
        bo.append(records.size());
        bo.append(", records={\n");
        for (int i = 0; i < records.size(); i++) {
            final MfCmd cmd = (MfCmd) records.get(i);
            bo.append(i);
            bo.append(',');
            bo.append(cmd.toString());
            bo.append('\n');
        }
        bo.append("}\n");
        return bo.toString();
    }
}