001/* An attribute with a reference to an image.
002
003 Copyright (c) 2009-2016 The Regents of the University of California.
004 All rights reserved.
005 Permission is hereby granted, without written agreement and without
006 license or royalty fees, to use, copy, modify, and distribute this
007 software and its documentation for any purpose, provided that the above
008 copyright notice and the following two paragraphs appear in all copies
009 of this software.
010
011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
015 SUCH DAMAGE.
016
017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
022 ENHANCEMENTS, OR MODIFICATIONS.
023
024 PT_COPYRIGHT_VERSION_2
025 COPYRIGHTENDKEY
026
027 */
028package ptolemy.vergil.pdfrenderer;
029
030import java.io.File;
031import java.io.IOException;
032import java.io.RandomAccessFile;
033import java.net.URL;
034import java.nio.ByteBuffer;
035import java.nio.channels.FileChannel;
036
037import com.sun.pdfview.PDFFile;
038import com.sun.pdfview.PDFPage;
039
040import ptolemy.data.DoubleToken;
041import ptolemy.data.expr.FileParameter;
042import ptolemy.data.expr.Parameter;
043import ptolemy.data.type.BaseType;
044import ptolemy.kernel.util.Attribute;
045import ptolemy.kernel.util.IllegalActionException;
046import ptolemy.kernel.util.KernelException;
047import ptolemy.kernel.util.NameDuplicationException;
048import ptolemy.kernel.util.NamedObj;
049import ptolemy.kernel.util.Workspace;
050import ptolemy.util.FileUtilities;
051import ptolemy.vergil.kernel.attributes.VisibleAttribute;
052
053///////////////////////////////////////////////////////////////////
054//// PDFAttribute
055
056/**
057 <p>This is an attribute that renders the first page of
058 a specified PDF file.  Its <i>source</i>
059 parameter specifies a file containing the PDF, and
060 its <i>scale</i> attribute specifies a scaling factor, as a percentage.
061 </p>
062 <p>
063 This class uses pdf-renderer, obtainable from
064 <a href="https://java.net/projects/pdf-renderer/#in_browser">https://java.net/projects/pdf-renderer/</a>.
065 This is an "an open source, all Java library which renders PDF documents
066 to the screen using Java2D." This attribute can be put into a
067 Vergil diagram and its visual appearance will be be defined
068 by a PDF file.  Using this attribute requires that
069 PDFRenderer.jar in the classpath, it is usually
070 found in $PTII/lib/PDFRenderer.jar.
071 </p>
072 <p>The pdf-renderer package is licensed under the
073<a href="http://www.gnu.org/copyleft/lesser.html#in_browser">GNU Lesser General Public License (LGPL)</a>.
074 </p>
075 @author Edward A. Lee
076 @version $Id$
077 @since Ptolemy II 8.0
078 @Pt.ProposedRating Yellow (eal)
079 @Pt.AcceptedRating Red (cxh)
080 */
081public class PDFAttribute extends VisibleAttribute {
082    /** Construct an attribute with the given name contained by the
083     *  specified container. The container argument must not be null, or a
084     *  NullPointerException will be thrown.  This attribute will use the
085     *  workspace of the container for synchronization and version counts.
086     *  If the name argument is null, then the name is set to the empty
087     *  string. Increment the version of the workspace.
088     *  @param container The container.
089     *  @param name The name of this attribute.
090     *  @exception IllegalActionException If the attribute is not of an
091     *   acceptable class for the container, or if the name contains a period.
092     *  @exception NameDuplicationException If the name coincides with
093     *   an attribute already in the container.
094     */
095    public PDFAttribute(NamedObj container, String name)
096            throws IllegalActionException, NameDuplicationException {
097        super(container, name);
098
099        _icon = new PDFIcon(this, "_icon");
100        _icon.setPersistent(false);
101
102        source = new FileParameter(this, "source");
103
104        // Put the sample PDF in the local directory so that it stays with this actor.
105        // Use $CLASSSPATH intstead of $PTII so that this class can find sample.pdf
106        // under Web Start.
107        source.setExpression(
108                "$CLASSPATH/ptolemy/vergil/pdfrenderer/sample.pdf");
109
110        scale = new Parameter(this, "scale");
111        scale.setTypeEquals(BaseType.DOUBLE);
112        scale.setExpression("100.0");
113    }
114
115    ///////////////////////////////////////////////////////////////////
116    ////                         parameters                        ////
117
118    /** The scale, as a percentage.
119     * This is a double that defaults to 100.0.
120     */
121    public Parameter scale;
122
123    /** The source image file. This is a file name or URL, where the default
124     *  is "$CLASSPATH/ptolemy/vergil/pdfrenderer/sample.pdf".
125     */
126    public FileParameter source;
127
128    ///////////////////////////////////////////////////////////////////
129    ////                         public methods                    ////
130
131    /** React to a change in the source or scale attributes by changing
132     *  the icon.
133     *  @param attribute The attribute that changed.
134     *  @exception IllegalActionException If the change is not acceptable
135     *   to this container (should not be thrown).
136     */
137    @Override
138    public void attributeChanged(Attribute attribute)
139            throws IllegalActionException {
140        if (attribute == source) {
141            try {
142                ByteBuffer byteBuffer = null;
143                FileChannel channel = null;
144                RandomAccessFile randomAccessFile = null;
145                try {
146                    File file = source.asFile();
147                    randomAccessFile = new RandomAccessFile(file, "r");
148                    channel = randomAccessFile.getChannel();
149                    byteBuffer = channel.map(FileChannel.MapMode.READ_ONLY, 0,
150                            channel.size());
151                } catch (Exception ex) {
152                    URL jarURL = null;
153                    // We might be under WebStart.  In theory, we should be able to read
154                    // the URL and create a ByteBuffer, but there are problems with the non-ascii bytes
155                    // in the pdf file.  The basic idea was to new BufferedOutputStream(new ByteArrayOutputStream()).
156                    try {
157                        jarURL = FileUtilities.nameToURL(source.getExpression(),
158                                null, null);
159                    } catch (Exception ex2) {
160                        throw new IllegalActionException(this, ex,
161                                "Failed to open " + source.getExpression()
162                                        + ". Tried opening as URL, exception was: "
163                                        + ex2);
164                    }
165                    if (!jarURL.toString().startsWith("jar:")) {
166                        throw new IllegalActionException(this, ex,
167                                "Failed to open " + source.getExpression());
168                    } else {
169                        try {
170                            byte[] contents = FileUtilities
171                                    .binaryReadURLToByteArray(jarURL);
172                            byteBuffer = ByteBuffer.wrap(contents);
173                        } catch (Exception ex3) {
174                            throw new IllegalActionException(this, ex,
175                                    "Failed to open " + source.getExpression()
176                                            + ".  Also, tried to open jar URL "
177                                            + jarURL + ", exception was: \n"
178                                            + KernelException
179                                                    .stackTraceToString(ex3));
180                        }
181                    }
182                } finally {
183                    if (channel != null) {
184                        channel.close();
185                    }
186                    if (randomAccessFile != null) {
187                        randomAccessFile.close();
188                    }
189                }
190
191                PDFFile pdffile = new PDFFile(byteBuffer);
192
193                // draw the first page to an image
194                PDFPage page = pdffile.getPage(0);
195
196                _icon.setPage(page);
197            } catch (IOException ex) {
198                // FIXME: Better would be to show an ERROR icon
199                // like ImageAttribute does.
200                throw new IllegalActionException(this, ex,
201                        "Cannot read PDF file.");
202            }
203        } else if (attribute == scale) {
204            double scaleValue = ((DoubleToken) scale.getToken()).doubleValue();
205            _icon.setScale(scaleValue);
206        } else {
207            super.attributeChanged(attribute);
208        }
209    }
210
211    /** Clone the object into the specified workspace. The new object is
212     *  <i>not</i> added to the directory of that workspace (you must do this
213     *  yourself if you want it there).
214     *  The result is an object with no container.
215     *  @param workspace The workspace for the cloned object.
216     *  @exception CloneNotSupportedException Not thrown in this base class
217     *  @return The new Attribute.
218     */
219    @Override
220    public Object clone(Workspace workspace) throws CloneNotSupportedException {
221        PDFAttribute newObject = (PDFAttribute) super.clone(workspace);
222        newObject._icon = (PDFIcon) newObject.getAttribute("_icon");
223        return newObject;
224    }
225
226    ///////////////////////////////////////////////////////////////////
227    ///                         private variables                 ////
228
229    /** The PDF icon. */
230    private PDFIcon _icon;
231}