edu.harvard.hul.ois.jhove.module.GifModule Maven / Gradle / Ivy
/**********************************************************************
* Jhove - JSTOR/Harvard Object Validation Environment
* Copyright 2003-2007 by JSTOR and the President and Fellows of Harvard College
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* 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.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
**********************************************************************/
package edu.harvard.hul.ois.jhove.module;
import java.io.*;
import java.util.*;
import edu.harvard.hul.ois.jhove.*;
import edu.harvard.hul.ois.jhove.Agent.Builder;
import edu.harvard.hul.ois.jhove.module.gif.*;
import org.xml.sax.XMLReader;
import org.xml.sax.SAXException;
import javax.xml.parsers.SAXParserFactory;
import edu.harvard.hul.ois.jhove.module.gif.MessageConstants;
/**
* Module for identification and validation of GIF files.
*
* @author Gary McGath
*
*/
public class GifModule extends ModuleBase
{
/******************************************************************
* DEBUGGING FIELDS.
* All debugging fields should be set to false for release code.
******************************************************************/
/* Set to true to allow application identifiers to be case-insensitive. */
private static final boolean debug_appIdentCaseInsens = false;
/******************************************************************
* PRIVATE CLASS FIELDS.
******************************************************************/
private static final String NAME = "GIF-hul";
private static final String RELEASE = "1.3";
private static final int [] DATE = {2006, 9, 5};
private static final String [] FORMAT = {"GIF",
"Graphics Interchange Format"};
private static final String COVERAGE = "GIF87a, GIF89a";
private static final String [] MIMETYPE = {"image/gif"};
private static final String WELLFORMED = "A GIF file is well-formed if " +
"it has a header block; a sequence of properly formed control, " +
"graphic-rendering, and special purpose blocks; and a trailer block";
private static final String VALIDITY = "A GIF file is valid if " +
"well-formed, has at most one global color map, and at most one " +
"graphic control extension preceding an image descriptor or a plain " +
"text extension";
private static final String REPINFO = "Additional representation " +
"information includes: NISO Z39.87 Digital Still Image Technical " +
"Metadata, and block-specific metadata";
private static final String NOTE = "'GIF' and 'Graphics Interchange " +
"Format' are trademarks of " +
"Compuserve Interactive Services Inc.";
private static final String RIGHTS = "Copyright 2003-2007 by JSTOR and " +
"the President and Fellows of Harvard College. " +
"Released under the GNU Lesser General Public License.";
/* Block type values */
private static final int
EXT_BLOCK = 0X21,
APPLICATION_EXT = 0XFF,
COMMENT_EXT = 0XFE,
GRAPHIC_CONTROL_EXT = 0XF9,
IMAGE_DESC = 0X2C,
PLAIN_TEXT_EXT = 0X01,
TRAILER = 0X3B;
/******************************************************************
* PRIVATE INSTANCE FIELDS.
******************************************************************/
/* First 6 bytes of file */
protected byte _sig[];
/* Checksummer object */
protected Checksummer _ckSummer;
/* XMP property */
protected Property _xmpProp;
/* Input stream wrapper which handles checksums */
protected ChecksumInputStream _cstream;
/* Data input stream wrapped around _cstream */
protected DataInputStream _dstream;
/* Flag for presence of global color table */
protected boolean _globalColorTableFlag;
/* Size of global color table */
protected int _globalColorTableSize;
/* Count of graphic control extensions preceding
* something to modify */
protected int _gceCounter;
/* Top-level metadata property */
protected Property _metadata;
/* Blocks list property */
protected List _blocksList;
/* Total count of graphic and plain text extension blocks */
protected int _numGraphicBlocks;
/******************************************************************
* CLASS CONSTRUCTOR.
******************************************************************/
/**
* Instantiate a GifModule object.
*/
public GifModule ()
{
super (NAME, RELEASE, DATE, FORMAT, COVERAGE, MIMETYPE, WELLFORMED,
VALIDITY, REPINFO, NOTE, RIGHTS, false);
_vendor = Agent.harvardInstance();
Document doc = new Document ("GIF (Graphics Interchange Format): A " +
"standard defining a mechanism for the " +
"storage and transmission of raster-" +
"based graphics information",
DocumentType.REPORT);
Builder builder = new Agent.Builder("Compuserve Interactive Services Inc.",
AgentType.COMMERCIAL).address ("5000 Arlington Centre Blvd., Columbus, OS 43220").telephone ("(614) 457-8600").web ("http://www.compuserve.com/");
Agent cmpsrvAgent = builder.build();
doc.setAuthor (cmpsrvAgent);
doc.setDate ("1987-06-15");
doc.setIdentifier (new Identifier ("http://www.w3.org/Graphics/GIF/spec-gif87.txt",
IdentifierType.URL));
_specification.add (doc);
doc = new Document ("Graphics Interchange Format",
DocumentType.REPORT);
doc.setEdition ("Version 89a");
doc.setAuthor (cmpsrvAgent);
doc.setDate ("1987-06-15");
doc.setIdentifier (new Identifier ("http://www.w3.org/Graphics/GIF/spec-gif89a.txt",
IdentifierType.URL));
_specification.add (doc);
Signature sig = new InternalSignature ("GIF", SignatureType.MAGIC,
SignatureUseType.MANDATORY, 0);
_signature.add (sig);
sig = new InternalSignature ("87a", SignatureType.MAGIC,
SignatureUseType.MANDATORY_IF_APPLICABLE,
3, "For version 87a");
_signature.add (sig);
sig = new InternalSignature ("89a", SignatureType.MAGIC,
SignatureUseType.MANDATORY_IF_APPLICABLE,
3, "For version 89a");
_signature.add (sig);
sig = new ExternalSignature (".gif", SignatureType.EXTENSION,
SignatureUseType.OPTIONAL);
_signature.add (sig);
_bigEndian = false;
}
/******************************************************************
* Parsing methods.
******************************************************************/
/**
* Check if the digital object conforms to this Module's
* internal signature information.
*
* @param file A File object for the object being parsed
* @param stream An InputStream, positioned at its beginning,
* which is generated from the object to be parsed
* @param info A fresh RepInfo object which will be modified
* to reflect the results of the test
*/
@Override
public void checkSignatures (File file,
InputStream stream,
RepInfo info)
throws IOException
{
int[] sigBytes = { 'G', 'I', 'F', '8', '*', 'a' };
int i;
int ch;
try {
_dstream = null;
_dstream = getBufferedDataStream (stream, _je != null ?
_je.getBufferSize () : 0);
for (i = 0; i < 4; i++) {
ch = readUnsignedByte(_dstream, this);
if (ch != sigBytes[i]) {
info.setWellFormed (false);
return;
}
}
/* Byte 4 can be either 7 or 9 */
ch = readUnsignedByte (_dstream, this);
if (ch != (int) '7' && ch != (int) '9') {
info.setWellFormed (false);
return;
}
ch = readUnsignedByte (_dstream, this);
if (ch != sigBytes[5]) {
info.setWellFormed (false);
return;
}
info.setModule (this);
info.setFormat (_format[0]);
info.setMimeType (_mimeType[0]);
info.setSigMatch(_name);
}
catch (Exception e) {
// Reading a very short file may take us here.
info.setWellFormed (false);
return;
}
}
/**
* Parse the content of a purported GIF stream digital object and store the
* results in RepInfo.
*
* @param stream An InputStream, positioned at its beginning,
* which is generated from the object to be parsed
* @param info A fresh RepInfo object which will be modified
* to reflect the results of the parsing
* @param parseIndex Must be 0 in first call to parse
. If
* parse
returns a nonzero value, it must be
* called again with parseIndex
* equal to that return value.
*/
@Override
public int parse (InputStream stream, RepInfo info, int parseIndex)
throws IOException
{
initParse ();
info.setModule (this);
info.setFormat (_format[0]);
info.setMimeType (_mimeType[0]);
_blocksList = new LinkedList ();
Property _blocks = new Property ("Blocks",
PropertyType.PROPERTY,
PropertyArity.LIST,
_blocksList);
// We may have already done the checksums while converting a
// temporary file.
_ckSummer = null;
if (_app != null && _je.getChecksumFlag () &&
info.getChecksum().isEmpty()) {
_ckSummer = new Checksummer ();
_cstream = new ChecksumInputStream (stream, _ckSummer);
_dstream = getBufferedDataStream (_cstream, _app != null ?
_je.getBufferSize () : 0);
}
else {
_dstream = getBufferedDataStream (stream, _app != null ?
_je.getBufferSize () : 0);
}
if (!readSig (info)) {
return 0;
}
/* If we got this far, take note that the signature is OK. */
info.setSigMatch(_name);
if (!readLSD (info)) {
return 0;
}
boolean moreToCome = true;
while (moreToCome) {
moreToCome = readBlock (info);
if (info.getWellFormed () == RepInfo.FALSE) {
return 0;
}
}
if (_ckSummer != null){
/* We may not have actually hit the end of file. If we're calculating
* checksums on the fly, we have to read and discard whatever is
* left, so it will get checksummed. */
for (;;) {
try {
long n = skipBytes (_dstream, 2048, this);
if (n == 0) {
break;
}
}
catch (Exception e) {
break;
}
}
info.setSize (_cstream.getNBytes ());
info.setChecksum (new Checksum (_ckSummer.getCRC32 (),
ChecksumType.CRC32));
String value = _ckSummer.getMD5 ();
if (value != null) {
info.setChecksum (new Checksum (value, ChecksumType.MD5));
}
if ((value = _ckSummer.getSHA1 ()) != null) {
info.setChecksum (new Checksum (value, ChecksumType.SHA1));
}
}
Property[] metaArray;
if (_xmpProp != null) {
// Making this an array rather than a list is a pain, but it's policy
metaArray = new Property[3];
}
else {
metaArray = new Property[2];
}
_metadata = new Property ("GIFMetadata",
PropertyType.PROPERTY,
PropertyArity.ARRAY,
metaArray);
metaArray[1] = _blocks; // this comes after GraphicRenderingBlocks,
// which we can't calculate yet
metaArray[0] = new Property ("GraphicRenderingBlocks",
PropertyType.INTEGER,
new Integer (_numGraphicBlocks));
if (_xmpProp != null) {
metaArray[2] = _xmpProp;
}
info.setProperty (_metadata);
return 0;
}
/**
* Initializes the state of the module for parsing.
*/
@Override
protected void initParse ()
{
super.initParse ();
_sig = new byte[6];
_globalColorTableFlag = false;
_globalColorTableSize = 0;
_gceCounter = 0;
_numGraphicBlocks = 0;
}
/* Read the 6-byte signature. */
protected boolean readSig (RepInfo info) throws IOException
{
int nbyt = 0;
while (nbyt < 6) {
try {
int ch = readUnsignedByte (_dstream, this);
if (nbyt < 6) {
_sig[(int) nbyt] = (byte) ch;
}
nbyt++;
//if (_ckSummer != null) {
// _ckSummer.update (ch);
//}
}
catch (EOFException e) {
info.setMessage(new ErrorMessage (MessageConstants.ERR_GIF_HEADER_INVALID, 0));
info.setWellFormed (RepInfo.FALSE);
return false;
}
}
String sigStr = new String (_sig);
if ("GIF89a".equals (sigStr)) {
info.setVersion ("89a");
info.setProfile ("GIF 89a");
}
else if ("GIF87a".equals (sigStr)) {
info.setVersion ("87a");
info.setProfile ("GIF 87a");
}
else {
info.setMessage(new ErrorMessage (MessageConstants.ERR_GIF_HEADER_INVALID, 0));
info.setWellFormed (RepInfo.FALSE);
return false;
}
return true;
}
/* Read the Logical Screen Descriptor. */
protected boolean readLSD (RepInfo info) throws IOException
{
Vector propVec = new Vector (8);
/* GIF data is always little-endian. */
int width = readUnsignedShort (_dstream);
propVec.add (new Property ("LogicalScreenWidth",
PropertyType.INTEGER,
new Integer (width)));
int height = readUnsignedShort (_dstream);
propVec.add (new Property ("LogicalScreenHeight",
PropertyType.INTEGER,
new Integer (height)));
int packedFields = readUnsignedByte (_dstream, this);
_globalColorTableFlag = (packedFields & 0X80) != 0;
int bitsPerColor = ((packedFields & 0X70) >> 4) + 1;
propVec.add (new Property ("ColorResolution",
PropertyType.INTEGER,
new Integer (bitsPerColor)));
boolean sortFlag = (packedFields & 0X8) != 0;
int rawGlobalColorTableSize = packedFields & 0X7;
if (_globalColorTableFlag) {
_globalColorTableSize = 3 *
(1 << (rawGlobalColorTableSize + 1));
}
int bgColorIndex = readUnsignedByte (_dstream, this);
propVec.add (new Property ("BackgroundColorIndex",
PropertyType.INTEGER,
new Integer (bgColorIndex)));
int pixAspectRatio = readUnsignedByte (_dstream, this);
// The pixel aspect ratio is turned into a real aspect
// ratio by a formula, but we just report the raw number.
propVec.add (new Property ("PixelAspectRatio",
PropertyType.SHORT,
new Short ((short) pixAspectRatio)));
propVec.add (addByteProperty ("GlobalColorTableFlag",
_globalColorTableFlag ? 1 : 0,
GifStrings.GLOBAL_COLOR_TABLE_FLAG));
propVec.add (addByteProperty ("GlobalColorTableSortFlag",
sortFlag ? 1 : 0,
GifStrings.COLOR_TABLE_SORT_FLAG));
propVec.add (new Property ("GlobalColorTableSize",
PropertyType.SHORT,
new Short ((short) rawGlobalColorTableSize)));
Property prop = new Property ("LogicalScreenDescriptor",
PropertyType.PROPERTY,
PropertyArity.ARRAY,
vectorToPropArray (propVec));
_blocksList.add (prop);
// Make a property with the global color table, if present
if (_globalColorTableFlag) {
short[] gctArray = new short[_globalColorTableSize];
for (int i = 0; i < _globalColorTableSize; i++) {
gctArray[i] = (short) _dstream.readUnsignedByte ();
}
_blocksList.add (new Property ("GlobalColorTable",
PropertyType.SHORT,
PropertyArity.ARRAY,
gctArray));
}
return true;
}
/* Read Graphic blocks, Special blocks, and the trailer.
* Return false if we get an error that prevents further
* progress, or if we encounter the Trailer. */
protected boolean readBlock (RepInfo info) throws IOException
{
int type;
try {
type = readUnsignedByte (_dstream, this);
}
catch (EOFException e) {
// The spec isn't fully clear on whether a trailer is
// required, but seems to imply it is.
info.setWellFormed (RepInfo.FALSE);
info.setMessage (new ErrorMessage (
MessageConstants.ERR_TRAILER_BLOCK_MISSING, _nByte));
return false;
}
try {
switch (type) {
case EXT_BLOCK:
return readExtBlock (info);
case IMAGE_DESC:
return readImage (info);
case TRAILER:
return false; // end of file
default:
info.setWellFormed (RepInfo.FALSE);
info.setMessage (new ErrorMessage
(MessageConstants.ERR_BLOCK_TYPE_UNKNOWN,
"Type = " + type, _nByte));
return false;
}
}
catch (EOFException e) {
// An EOF in the middle of a block is definitely a problem
info.setWellFormed (RepInfo.FALSE);
info.setMessage (new ErrorMessage
(MessageConstants.ERR_UNEXPECTED_END_OF_FILE, _nByte));
return false;
}
}
/* Read an extension block.
*/
protected boolean readExtBlock (RepInfo info) throws IOException
{
int subtype = readUnsignedByte (_dstream, this);
switch (subtype) {
case APPLICATION_EXT:
return readAppExtension (info);
case COMMENT_EXT:
return readCommentExtension (info);
case GRAPHIC_CONTROL_EXT:
return readGraphicsCtlBlock (info);
case PLAIN_TEXT_EXT:
return readPlainTextExtension (info);
default:
info.setWellFormed (RepInfo.FALSE);
info.setMessage (new ErrorMessage
(MessageConstants.ERR_EXTENSION_BLOCK_TYPE_UNKNOWN,
"Type = " + subtype,
_nByte));
return false;
}
}
/* Read an image descriptor and the subsequent data.
* We are positioned just after the type byte of the
* image descriptor.
*/
protected boolean readImage (RepInfo info) throws IOException
{
++_numGraphicBlocks;
Vector propVec = new Vector (7);
NisoImageMetadata niso = new NisoImageMetadata ();
Property nisoProp = new Property ("NisoImageMetadata",
PropertyType.NISOIMAGEMETADATA, niso);
// GIF doesn't have a lot of options, so several
// NISO properties are constants.
niso.setMimeType ("image/gif");
niso.setByteOrder ("little-endian");
niso.setCompressionScheme(5); // LZW
niso.setColorSpace (3); // palette color
niso.setOrientation(1); // normal
niso.setBitsPerSample (new int[] {8});
_gceCounter = 0;
int leftPos = readUnsignedShort (_dstream);
propVec.add (new Property ("ImageLeftPosition",
PropertyType.INTEGER,
new Integer (leftPos)));
int topPos = readUnsignedShort (_dstream);
propVec.add (new Property ("ImageTopPosition",
PropertyType.INTEGER,
new Integer (topPos)));
int width = readUnsignedShort (_dstream);
niso.setImageWidth (width);
int height = readUnsignedShort (_dstream);
niso.setImageLength (height);
int packedFields = readUnsignedByte (_dstream, this);
int interlaceFlag = (packedFields & 0X40) >> 6;
propVec.add (addByteProperty ("InterlaceFlag",
interlaceFlag,
GifStrings.INTERLACE_FLAG));
int localColorTableFlag = (packedFields & 0X80) >> 7;
propVec.add (addByteProperty ("LocalColorTableFlag",
localColorTableFlag,
GifStrings.LOCAL_COLOR_TABLE_FLAG));
int sortFlag = (packedFields & 0X20) >> 5;
propVec.add (addByteProperty ("LocalColorTableSortFlag",
sortFlag,
GifStrings.COLOR_TABLE_SORT_FLAG));
int localColorTableSize = 0;
int rawLocalColorTableSize = packedFields & 0X7;
propVec.add (new Property ("LocalColorTableSize",
PropertyType.SHORT,
new Short ((short) rawLocalColorTableSize)));
propVec.add (nisoProp);
if (localColorTableFlag != 0) {
localColorTableSize =
3 * (1 << (rawLocalColorTableSize + 1));
skipBytes (_dstream, localColorTableSize, this);
}
Property prop = new Property ("ImageDescriptor",
PropertyType.PROPERTY,
PropertyArity.ARRAY,
vectorToPropArray (propVec));
_blocksList.add (prop);
// Skip over the LZW minimum code size
readUnsignedByte (_dstream, this);
// Now read sub-blocks till we get one of zero size.
for (;;) {
int blockSize = readUnsignedByte (_dstream, this);
if (blockSize == 0) {
break;
}
skipBytes (_dstream, blockSize, this);
}
return true;
}
/* Read an application extension block and fill in the appropriate
* properties */
protected boolean readAppExtension (RepInfo info)
throws IOException
{
int blockSize = readUnsignedByte (_dstream, this);
if (blockSize != 11) {
info.setMessage (new ErrorMessage
(MessageConstants.ERR_APP_EXTENSION_BLOCK_SIZE_INVALID,
_nByte));
info.setWellFormed (RepInfo.FALSE);
return false;
}
Vector propVec = new Vector (3);
StringBuffer appIdent = new StringBuffer ();
int i;
for (i = 0; i < 8; i++) {
appIdent.append((char) readUnsignedByte (_dstream, this));
}
propVec.add (new Property ("ApplicationIdentifier",
PropertyType.STRING,
appIdent.toString ()));
short[] appAuth = new short[3];
for (i = 0; i < 3; i++) {
appAuth[i] = (short) readUnsignedByte (_dstream, this);
}
propVec.add (new Property ("ApplicationAuthenticationCode",
PropertyType.SHORT,
PropertyArity.ARRAY,
appAuth));
int appDataSize = 0;
// We are interested in the application extension for XMP.
if (("XMP Data".equals(appIdent.toString()) ||
(debug_appIdentCaseInsens &&
"xmp data".equalsIgnoreCase(appIdent.toString()))) &&
appAuth[0] == (short) 'X' &&
appAuth[1] == (short) 'M' &&
appAuth[2] == (short) 'P') {
appDataSize = readXMP ();
}
else {
// Zip through the application data blocks, totalling their size
for (;;) {
int subBlockSize = readUnsignedByte (_dstream, this);
appDataSize += subBlockSize + 1;
if (subBlockSize == 0) {
break;
}
skipBytes (_dstream, subBlockSize, this);
}
}
propVec.add (new Property ("ApplicationDataSize",
PropertyType.INTEGER,
new Integer (appDataSize)));
Property prop = new Property ("ApplicationExtension",
PropertyType.PROPERTY,
PropertyArity.ARRAY,
vectorToPropArray(propVec));
_blocksList.add (prop);
return true;
}
/* Read an application extension block and fill in the appropriate
* properties. A comment extension should, by recommendation,
* contain ASCII, but actually can contain anything. Nulls
* are skipped, but everything else is included as is. */
protected boolean readCommentExtension (RepInfo info)
throws IOException
{
StringBuffer buf = new StringBuffer ();
for (;;) {
int subBlockSize = readUnsignedByte (_dstream, this);
if (subBlockSize == 0) {
break;
}
for (int i = 0; i < subBlockSize; i++) {
int ch = readUnsignedByte (_dstream, this);
if (ch != 0) {
buf.append ((char) ch);
}
}
}
Property prop = new Property ("CommentExtension",
PropertyType.STRING,
buf.toString ());
return true;
}
/* Read an application extension block and fill in the appropriate
* properties */
protected boolean readPlainTextExtension (RepInfo info)
throws IOException
{
++_numGraphicBlocks;
_gceCounter = 0;
int blockSize = readUnsignedByte (_dstream, this);
if (blockSize != 12) {
info.setMessage (new ErrorMessage
(MessageConstants.ERR_PLAIN_TEXT_EXTENSION_BLOCK_SIZE_INVALID,
_nByte));
info.setWellFormed (RepInfo.FALSE);
return false;
}
// A plain text extension requires a global color table
if (!_globalColorTableFlag) {
info.setMessage (new ErrorMessage
(MessageConstants.ERR_PLAIN_TEXT_EXTENSION_COLOR_TABLE_MISSING,
_nByte));
info.setValid (false);
}
Vector propVec = new Vector (9);
int textLeft = readUnsignedShort (_dstream);
propVec.add (new Property ("TextGridLeftPosition",
PropertyType.INTEGER,
new Integer (textLeft)));
int textTop = readUnsignedShort (_dstream);
propVec.add (new Property ("TextGridTopPosition",
PropertyType.INTEGER,
new Integer (textTop)));
int textGWidth = readUnsignedShort (_dstream);
propVec.add (new Property ("TextGridWidth",
PropertyType.INTEGER,
new Integer (textGWidth)));
int textGHeight = readUnsignedShort (_dstream);
propVec.add (new Property ("TextGridHeight",
PropertyType.INTEGER,
new Integer (textGHeight)));
int charCWidth = readUnsignedByte (_dstream, this);
propVec.add (new Property ("CharacterCellWidth",
PropertyType.SHORT,
new Short ((short) charCWidth)));
int charCHeight = readUnsignedByte (_dstream, this);
propVec.add (new Property ("CharacterCellHeight",
PropertyType.SHORT,
new Short ((short) charCHeight)));
int textFgIdx = readUnsignedByte (_dstream, this);
propVec.add (new Property ("TextForegroundColorIndex",
PropertyType.SHORT,
new Short ((short) textFgIdx)));
int textBgIdx = readUnsignedByte (_dstream, this);
propVec.add (new Property ("TextBackgroundColorIndex",
PropertyType.SHORT,
new Short ((short) textBgIdx)));
// Read the text data. The GIF recommendation states that
// characters less than 0X20 or greater than 0XF7 (why F7?
// It's on at least 2 independent copies of the spec, so
// apparently it's not a typo, or else is a well-entrenched one)
// should be represented as spaces.
StringBuffer buf = new StringBuffer ();
for (;;) {
int subBlockSize = readUnsignedByte (_dstream, this);
if (subBlockSize == 0) {
break;
}
for (int i = 0; i < subBlockSize; i++) {
int ch = readUnsignedByte (_dstream, this);
if (ch >= 0X20 || ch <= 0XF7) {
buf.append ((char) ch);
}
else {
buf.append (' ');
}
}
}
propVec.add (new Property ("PlainTextData",
PropertyType.STRING,
buf.toString ()));
Property prop = new Property ("PlainTextExtension",
PropertyType.PROPERTY,
PropertyArity.ARRAY,
vectorToPropArray(propVec));
_blocksList.add (prop);
return true;
}
/* Read a graphics control block and fill in the
* relevant properties */
protected boolean readGraphicsCtlBlock (RepInfo info)
throws IOException
{
Vector propVec = new Vector (5);
if (++_gceCounter > 1) {
info.setMessage (new ErrorMessage
(MessageConstants.ERR_GRAPH_CTL_BLOCK_MULTIPLE,
_nByte));
info.setWellFormed (RepInfo.FALSE);
}
int blockSize = readUnsignedByte (_dstream, this);
if (blockSize != 4) {
info.setMessage (new ErrorMessage
(MessageConstants.ERR_GRAPH_CTL_BLOCK_SIZE_INVALID,
_nByte));
info.setWellFormed (RepInfo.FALSE);
return false;
}
int packedFields = readUnsignedByte (_dstream, this);
int dispMethod = (packedFields & 0X1C) >> 3;
propVec.add (addByteProperty ("DisposalMethod",
dispMethod,
GifStrings.GCE_DISPOSAL_METHOD));
int userInputFlag = (packedFields & 2) >> 1;
propVec.add (addByteProperty ("UserInputFlag",
userInputFlag,
GifStrings.GCE_USER_INPUT_FLAG));
int transparencyFlag = packedFields & 1;
propVec.add (addByteProperty ("TransparencyFlag",
transparencyFlag,
GifStrings.GCE_TRANSPARENCY_FLAG));
int delayTime = readUnsignedShort(_dstream);
propVec.add (new Property ("DelayTime",
PropertyType.INTEGER,
new Integer (delayTime)));
int transIndex = readUnsignedByte (_dstream, this);
propVec.add (new Property ("TransparentColorIndex",
PropertyType.SHORT,
new Short ((short) transIndex)));
// Skip the block terminator.
readUnsignedByte (_dstream, this);
Property prop = new Property ("GraphicControlExtension",
PropertyType.PROPERTY,
PropertyArity.ARRAY,
vectorToPropArray(propVec));
_blocksList.add (prop);
return true;
}
/* Read and process an XMP block and return the number of
* bytes read. When we reach a 0 byte, we've hit the
* end of the "magic" trailer.
*/
protected int readXMP () throws IOException
{
// Read bytes till we get to the trailer. Annoyingly,
// we don't know how big the byte buffer has to be,
// so we build a List of fixed-size buffers. We don't add
// curBuf to bufList till it's full.
List bufList = new LinkedList ();
final int bufsiz = 4096;
byte[] curBuf = new byte[bufsiz];
int curBufOff = 0;
// Fill up buffers till we hit a null.
for (;;) {
int ch = readUnsignedByte (_dstream, this);
if (ch == 0) {
// Read past second null, which concludes trailer
readUnsignedByte (_dstream, this);
break;
}
if (curBufOff == bufsiz) {
bufList.add (curBuf);
curBuf = new byte[bufsiz];
curBufOff = 0;
}
curBuf[curBufOff++] = (byte) ch;
}
// Consolidate the buffers into one big buffer.
// The magic trailer is 258 bytes long, of which 256
// bytes were actually read into the buffers, so we
// set our target to 256 bytes less than the total.
int appDataSize = bufList.size() * bufsiz + curBufOff + 2;
int totalSize = appDataSize - 258;
byte[] bigBuf = new byte[totalSize];
int bigBufOff = 0;
ListIterator iter = bufList.listIterator();
int i;
l1:
while (iter.hasNext ()) {
byte[] buf = (byte []) iter.next ();
for (i = 0; i < bufsiz; i++) {
bigBuf[bigBufOff++] = buf[i];
if (bigBufOff >= totalSize) {
break l1;
}
}
}
// Finally curBuf gets added
for (i = 0; i < curBufOff; i++) {
if (bigBufOff >= totalSize) {
break;
}
bigBuf[bigBufOff++] = curBuf[i];
}
// OK. All that was just to get the XMP into one big byte
// buffer. Now process it.
final String badMetadata = "Invalid or ill-formed XMP metadata";
try {
ByteArrayInputStream strm =
new ByteArrayInputStream (bigBuf);
ByteArrayXMPSource src = new ByteArrayXMPSource (strm, "UTF-8");
// Create an InputSource to feed the parser.
SAXParserFactory factory =
SAXParserFactory.newInstance();
factory.setNamespaceAware (true);
XMLReader parser = factory.newSAXParser ().getXMLReader ();
XMPHandler handler = new XMPHandler ();
parser.setContentHandler (handler);
parser.setErrorHandler (handler);
// We have to parse twice. The first time, we may get
// an encoding change as part of an exception thrown. If this
// happens, we create a new InputSource with the encoding, and
// continue.
try {
parser.parse (src);
_xmpProp = src.makeProperty ();
return appDataSize;
}
catch (SAXException se) {
String msg = se.getMessage ();
if (msg != null && msg.startsWith ("ENC=")) {
String encoding = msg.substring (5);
try {
//The only permitted encoding is UTF-8, but
//that may come under various aliased names,
//so we assume the encoding is legitimate.
src = new ByteArrayXMPSource (strm, encoding);
parser.parse (src);
}
catch (UnsupportedEncodingException uee) {
return appDataSize;
}
}
_xmpProp = src.makeProperty ();
return appDataSize;
}
}
catch (Exception e) {
return appDataSize;
}
}
protected Property addByteProperty (String name, int value,
String [] labels)
{
if (!_je.getShowRawFlag ()) {
try {
return new Property (name, PropertyType.STRING, labels[value]);
}
catch (Exception e) {
// fall through
}
}
return new Property (name, PropertyType.BYTE, new Byte ((byte) value));
}
/* GIF is always little-endian, so readUnsignedShort can
* unambiguously drop its endian argument */
protected int readUnsignedShort (DataInputStream stream)
throws IOException
{
return readUnsignedShort (stream, false, this);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy