Index: src/scratchpad/src/org/apache/poi/hwpf/model/FSPA.java =================================================================== --- src/scratchpad/src/org/apache/poi/hwpf/model/FSPA.java (revision 0) +++ src/scratchpad/src/org/apache/poi/hwpf/model/FSPA.java (revision 0) @@ -0,0 +1,182 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hwpf.model; + +import org.apache.poi.util.BitField; +import org.apache.poi.util.BitFieldFactory; +import org.apache.poi.util.LittleEndian; + +/** + * File Shape Address structure + * + * @author Squeeself + */ +public class FSPA +{ + public static final int FSPA_SIZE = 26; + private int spid; // Shape identifier. Used to get data position + private int xaLeft; // Enclosing rectangle + private int yaTop; // Enclosing rectangle + private int xaRight; // Enclosing rectangle + private int yaBottom; // Enclosing rectangle + private short options; + private static BitField fHdr = BitFieldFactory.getInstance(0x0001); // 1 in undo when in header + private static BitField bx = BitFieldFactory.getInstance(0x0006); // x pos relative to anchor CP: 0 - page margin, 1 - top of page, 2 - text, 3 - reserved + private static BitField by = BitFieldFactory.getInstance(0x0018); // y pos relative to anchor CP: ditto + private static BitField wr = BitFieldFactory.getInstance(0x01E0); // Text wrapping mode: 0 - like 2 w/o absolute, 1 - no text next to shape, 2 - wrap around absolute object, 3 - wrap as if no object, 4 - wrap tightly around object, 5 - wrap tightly, allow holes, 6-15 - reserved + private static BitField wrk = BitFieldFactory.getInstance(0x1E00); // Text wrapping mode type (for modes 2&4): 0 - wrap both sides, 1 - wrap only left, 2 - wrap only right, 3 - wrap largest side + private static BitField fRcaSimple = BitFieldFactory.getInstance(0x2000); // Overwrites bx if set, forcing rectangle to be page relative + private static BitField fBelowText = BitFieldFactory.getInstance(0x4000); // if true, shape is below text, otherwise above + private static BitField fAnchorLock = BitFieldFactory.getInstance(0x8000); // if true, anchor is locked + private int cTxbx; // Count of textboxes in shape (undo doc only) + + public FSPA() + { + } + + public FSPA(byte[] bytes, int offset) + { + spid = LittleEndian.getInt(bytes, offset); + offset += LittleEndian.INT_SIZE; + xaLeft = LittleEndian.getInt(bytes, offset); + offset += LittleEndian.INT_SIZE; + yaTop = LittleEndian.getInt(bytes, offset); + offset += LittleEndian.INT_SIZE; + xaRight = LittleEndian.getInt(bytes, offset); + offset += LittleEndian.INT_SIZE; + yaBottom = LittleEndian.getInt(bytes, offset); + offset += LittleEndian.INT_SIZE; + options = LittleEndian.getShort(bytes, offset); + offset += LittleEndian.SHORT_SIZE; + cTxbx = LittleEndian.getInt(bytes, offset); + } + + public int getSpid() + { + return spid; + } + + public int getXaLeft() + { + return xaLeft; + } + + public int getYaTop() + { + return yaTop; + } + + public int getXaRight() + { + return xaRight; + } + + public int getYaBottom() + { + return yaBottom; + } + + public boolean isFHdr() + { + return fHdr.isSet(options); + } + + public short getBx() + { + return bx.getShortValue(options); + } + + public short getBy() + { + return by.getShortValue(options); + } + + public short getWr() + { + return wr.getShortValue(options); + } + + public short getWrk() + { + return wrk.getShortValue(options); + } + + public boolean isFRcaSimple() + { + return fRcaSimple.isSet(options); + } + + public boolean isFBelowText() + { + return fBelowText.isSet(options); + } + + public boolean isFAnchorLock() + { + return fAnchorLock.isSet(options); + } + + public int getCTxbx() + { + return cTxbx; + } + + public byte[] toByteArray() + { + int offset = 0; + byte[] buf = new byte[FSPA_SIZE]; + + LittleEndian.putInt(buf, offset, spid); + offset += LittleEndian.INT_SIZE; + LittleEndian.putInt(buf, offset, xaLeft); + offset += LittleEndian.INT_SIZE; + LittleEndian.putInt(buf, offset, yaTop); + offset += LittleEndian.INT_SIZE; + LittleEndian.putInt(buf, offset, xaRight); + offset += LittleEndian.INT_SIZE; + LittleEndian.putInt(buf, offset, yaBottom); + offset += LittleEndian.INT_SIZE; + LittleEndian.putShort(buf, offset, options); + offset += LittleEndian.SHORT_SIZE; + LittleEndian.putInt(buf, offset, cTxbx); + offset += LittleEndian.INT_SIZE; + + return buf; + } + + public String toString() + { + StringBuffer buf = new StringBuffer(); + buf.append("spid: ").append(spid); + buf.append(", xaLeft: ").append(xaLeft); + buf.append(", yaTop: ").append(yaTop); + buf.append(", xaRight: ").append(xaRight); + buf.append(", yaBottom: ").append(yaBottom); + buf.append(", options: ").append(options); + buf.append(" (fHdr: ").append(isFHdr()); + buf.append(", bx: ").append(getBx()); + buf.append(", by: ").append(getBy()); + buf.append(", wr: ").append(getWr()); + buf.append(", wrk: ").append(getWrk()); + buf.append(", fRcaSimple: ").append(isFRcaSimple()); + buf.append(", fBelowText: ").append(isFBelowText()); + buf.append(", fAnchorLock: ").append(isFAnchorLock()); + buf.append("), cTxbx: ").append(cTxbx); + return buf.toString(); + } +} Property changes on: src\scratchpad\src\org\apache\poi\hwpf\model\FSPA.java ___________________________________________________________________ Name: svn:eol-style + native Index: src/scratchpad/src/org/apache/poi/hwpf/model/FSPATable.java =================================================================== --- src/scratchpad/src/org/apache/poi/hwpf/model/FSPATable.java (revision 0) +++ src/scratchpad/src/org/apache/poi/hwpf/model/FSPATable.java (revision 0) @@ -0,0 +1,82 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hwpf.model; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; + +/** + * This class holds all the FSPA (File Shape Address) structures. + * + * @author Squeeself + */ +public class FSPATable +{ + protected ArrayList shapes = new ArrayList(); + protected HashMap cps = new HashMap(); + protected List _text; + + public FSPATable(byte[] tableStream, int fcPlcspa, int lcbPlcspa, List tpt) + { + _text = tpt; + // Will be 0 if no drawing objects in document + if (fcPlcspa == 0) + return; + + PlexOfCps plex = new PlexOfCps(tableStream, fcPlcspa, lcbPlcspa, FSPA.FSPA_SIZE); + for (int i=0; i < plex.length(); i++) + { + GenericPropertyNode property = plex.getProperty(i); + FSPA fspa = new FSPA(property.getBytes(), 0); + + shapes.add(fspa); + cps.put(Integer.valueOf(property.getStart()), Integer.valueOf(i)); + } + } + + public FSPA getFspaFromCp(int cp) + { + Integer idx = (Integer)cps.get(Integer.valueOf(cp)); + if (idx == null) + return null; + return (FSPA)shapes.get(idx.intValue()); + } + + public List getShapes() + { + return shapes; + } + + public String toString() + { + StringBuffer buf = new StringBuffer(); + buf.append("[FPSA PLC size=").append(shapes.size()).append("]\n"); + for (Iterator it = cps.keySet().iterator(); it.hasNext(); ) + { + Integer i = (Integer) it.next(); + FSPA fspa = (FSPA) shapes.get(((Integer)cps.get(i)).intValue()); + buf.append(" [FC: ").append(i.toString()).append("] "); + buf.append(fspa.toString()); + buf.append("\n"); + } + buf.append("[/FSPA PLC]"); + return buf.toString(); + } +} Property changes on: src\scratchpad\src\org\apache\poi\hwpf\model\FSPATable.java ___________________________________________________________________ Name: svn:eol-style + native Index: src/scratchpad/testcases/org/apache/poi/hwpf/data/GaiaTest.doc =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: src\scratchpad\testcases\org\apache\poi\hwpf\data\GaiaTest.doc ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Index: src/scratchpad/testcases/org/apache/poi/hwpf/data/GaiaTestImg.png =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes on: src\scratchpad\testcases\org\apache\poi\hwpf\data\GaiaTestImg.png ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Index: src/scratchpad/src/org/apache/poi/hwpf/model/EscherRecordHolder.java =================================================================== --- src/scratchpad/src/org/apache/poi/hwpf/model/EscherRecordHolder.java (revision 0) +++ src/scratchpad/src/org/apache/poi/hwpf/model/EscherRecordHolder.java (revision 0) @@ -0,0 +1,116 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package org.apache.poi.hwpf.model; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import org.apache.poi.ddf.DefaultEscherRecordFactory; +import org.apache.poi.ddf.EscherContainerRecord; +import org.apache.poi.ddf.EscherRecord; +import org.apache.poi.ddf.EscherRecordFactory; + +/** + * Based on AbstractEscherRecordHolder fomr HSSF. + * + * @author Squeeself + */ +public class EscherRecordHolder +{ + protected ArrayList escherRecords = new ArrayList(); + + public EscherRecordHolder() + { + + } + + public EscherRecordHolder(byte[] data, int offset, int size) + { + fillEscherRecords(data, offset, size); + } + + private void fillEscherRecords(byte[] data, int offset, int size) + { + EscherRecordFactory recordFactory = new DefaultEscherRecordFactory(); + int pos = offset; + while ( pos < offset + size) + { + EscherRecord r = recordFactory.createRecord(data, pos); + escherRecords.add(r); + int bytesRead = r.fillFields(data, pos, recordFactory); + pos += bytesRead + 1; // There is an empty byte between each top-level record in a Word doc + } + } + + public List getEscherRecords() + { + return escherRecords; + } + + public String toString() + { + StringBuffer buffer = new StringBuffer(); + + final String nl = System.getProperty("line.separator"); + if (escherRecords.size() == 0) + buffer.append("No Escher Records Decoded" + nl); + for ( Iterator iterator = escherRecords.iterator(); iterator.hasNext(); ) + { + EscherRecord r = (EscherRecord) iterator.next(); + buffer.append(r.toString()); + } + + return buffer.toString(); + } + + /** + * If we have a EscherContainerRecord as one of our + * children (and most top level escher holders do), + * then return that. + */ + public EscherContainerRecord getEscherContainer() { + for(Iterator it = escherRecords.iterator(); it.hasNext();) { + Object er = it.next(); + if(er instanceof EscherContainerRecord) { + return (EscherContainerRecord)er; + } + } + return null; + } + + /** + * Descends into all our children, returning the + * first EscherRecord with the given id, or null + * if none found + */ + public EscherRecord findFirstWithId(short id) { + return findFirstWithId(id, getEscherRecords()); + } + private EscherRecord findFirstWithId(short id, List records) { + // Check at our level + for(Iterator it = records.iterator(); it.hasNext();) { + EscherRecord r = (EscherRecord)it.next(); + if(r.getRecordId() == id) { + return r; + } + } + + // Then check our children in turn + for(Iterator it = records.iterator(); it.hasNext();) { + EscherRecord r = (EscherRecord)it.next(); + if(r.isContainerRecord()) { + EscherRecord found = + findFirstWithId(id, r.getChildRecords()); + if(found != null) { + return found; + } + } + } + + // Not found in this lot + return null; + } +} Property changes on: src\scratchpad\src\org\apache\poi\hwpf\model\EscherRecordHolder.java ___________________________________________________________________ Name: svn:eol-style + native Index: src/java/org/apache/poi/ddf/EscherClientAnchorRecord.java =================================================================== --- src/java/org/apache/poi/ddf/EscherClientAnchorRecord.java (revision 653573) +++ src/java/org/apache/poi/ddf/EscherClientAnchorRecord.java (working copy) @@ -65,20 +65,27 @@ int size = 0; // Always find 4 two byte entries. Sometimes find 9 - field_1_flag = LittleEndian.getShort( data, pos + size ); size += 2; - field_2_col1 = LittleEndian.getShort( data, pos + size ); size += 2; - field_3_dx1 = LittleEndian.getShort( data, pos + size ); size += 2; - field_4_row1 = LittleEndian.getShort( data, pos + size ); size += 2; - if(bytesRemaining >= 18) { - field_5_dy1 = LittleEndian.getShort( data, pos + size ); size += 2; - field_6_col2 = LittleEndian.getShort( data, pos + size ); size += 2; - field_7_dx2 = LittleEndian.getShort( data, pos + size ); size += 2; - field_8_row2 = LittleEndian.getShort( data, pos + size ); size += 2; - field_9_dy2 = LittleEndian.getShort( data, pos + size ); size += 2; - shortRecord = false; - } else { - shortRecord = true; - } + if (bytesRemaining == 4) // Word format only 4 bytes + { + // Not sure exactly what the format is quite yet, likely a reference to a PLC + } + else + { + field_1_flag = LittleEndian.getShort( data, pos + size ); size += 2; + field_2_col1 = LittleEndian.getShort( data, pos + size ); size += 2; + field_3_dx1 = LittleEndian.getShort( data, pos + size ); size += 2; + field_4_row1 = LittleEndian.getShort( data, pos + size ); size += 2; + if(bytesRemaining >= 18) { + field_5_dy1 = LittleEndian.getShort( data, pos + size ); size += 2; + field_6_col2 = LittleEndian.getShort( data, pos + size ); size += 2; + field_7_dx2 = LittleEndian.getShort( data, pos + size ); size += 2; + field_8_row2 = LittleEndian.getShort( data, pos + size ); size += 2; + field_9_dy2 = LittleEndian.getShort( data, pos + size ); size += 2; + shortRecord = false; + } else { + shortRecord = true; + } + } bytesRemaining -= size; remainingData = new byte[bytesRemaining]; System.arraycopy( data, pos + size, remainingData, 0, bytesRemaining ); Index: src/scratchpad/src/org/apache/poi/hwpf/HWPFDocument.java =================================================================== --- src/scratchpad/src/org/apache/poi/hwpf/HWPFDocument.java (revision 653573) +++ src/scratchpad/src/org/apache/poi/hwpf/HWPFDocument.java (working copy) @@ -53,10 +53,10 @@ protected FileInformationBlock _fib; /** main document stream buffer*/ - private byte[] _mainStream; + protected byte[] _mainStream; /** table stream buffer*/ - private byte[] _tableStream; + protected byte[] _tableStream; /** data stream buffer*/ protected byte[] _dataStream; @@ -93,6 +93,12 @@ /** Holds pictures table */ protected PicturesTable _pictures; + + /** Holds FSBA (shape) information */ + protected FSPATable _fspa; + + /** Escher Drawing Group information */ + protected EscherRecordHolder _dgg; protected HWPFDocument() { @@ -204,9 +210,6 @@ { _dataStream = new byte[0]; } - - // read in the pictures stream - _pictures = new PicturesTable(this, _dataStream); // get the start of text in the main stream int fcMin = _fib.getFcMin(); @@ -226,6 +229,20 @@ _cbt.adjustForDelete(0, 0, cpMin); _pbt.adjustForDelete(0, 0, cpMin); } + + // Read FSPA and Escher information + _fspa = new FSPATable(_tableStream, _fib.getFcPlcspaMom(), _fib.getLcbPlcspaMom(), getTextTable().getTextPieces()); + + if (_fib.getFcDggInfo() != 0) + { + _dgg = new EscherRecordHolder(_tableStream, _fib.getFcDggInfo(), _fib.getLcbDggInfo()); + } else + { + _dgg = new EscherRecordHolder(); + } + + // read in the pictures stream + _pictures = new PicturesTable(this, _dataStream, _mainStream, _fspa, _dgg); _st = new SectionTable(_mainStream, _tableStream, _fib.getFcPlcfsed(), _fib.getLcbPlcfsed(), fcMin, getTextTable().getTextPieces()); _ss = new StyleSheet(_tableStream, _fib.getFcStshf()); Index: src/scratchpad/src/org/apache/poi/hwpf/model/FileInformationBlock.java =================================================================== --- src/scratchpad/src/org/apache/poi/hwpf/model/FileInformationBlock.java (revision 653573) +++ src/scratchpad/src/org/apache/poi/hwpf/model/FileInformationBlock.java (working copy) @@ -308,6 +308,26 @@ { return _fieldHandler.getFieldSize(FIBFieldHandler.PLCFFLDMOM); } + + public int getFcPlcspaMom() + { + return _fieldHandler.getFieldOffset(FIBFieldHandler.PLCSPAMOM); + } + + public int getLcbPlcspaMom() + { + return _fieldHandler.getFieldSize(FIBFieldHandler.PLCSPAMOM); + } + + public int getFcDggInfo() + { + return _fieldHandler.getFieldOffset(FIBFieldHandler.DGGINFO); + } + + public int getLcbDggInfo() + { + return _fieldHandler.getFieldSize(FIBFieldHandler.DGGINFO); + } public void writeTo (byte[] mainStream, HWPFOutputStream tableStream) throws IOException Index: src/scratchpad/src/org/apache/poi/hwpf/model/PicturesTable.java =================================================================== --- src/scratchpad/src/org/apache/poi/hwpf/model/PicturesTable.java (revision 653573) +++ src/scratchpad/src/org/apache/poi/hwpf/model/PicturesTable.java (working copy) @@ -26,7 +26,12 @@ import java.util.List; import java.util.ArrayList; - +import java.util.Iterator; +import org.apache.poi.ddf.DefaultEscherRecordFactory; +import org.apache.poi.ddf.EscherBSERecord; +import org.apache.poi.ddf.EscherBlipRecord; +import org.apache.poi.ddf.EscherRecord; +import org.apache.poi.ddf.EscherRecordFactory; /** * Holds information about all pictures embedded in Word Document either via "Insert -> Picture -> From File" or via @@ -57,6 +62,9 @@ private HWPFDocument _document; private byte[] _dataStream; + private byte[] _mainStream; + private FSPATable _fspa; + private EscherRecordHolder _dgg; /** @link dependency * @stereotype instantiate*/ @@ -67,10 +75,13 @@ * @param document * @param _dataStream */ - public PicturesTable(HWPFDocument _document, byte[] _dataStream) + public PicturesTable(HWPFDocument _document, byte[] _dataStream, byte[] _mainStream, FSPATable fspa, EscherRecordHolder dgg) { this._document = _document; this._dataStream = _dataStream; + this._mainStream = _mainStream; + this._fspa = fspa; + this._dgg = dgg; } /** @@ -83,6 +94,13 @@ } return false; } + + public boolean hasEscherPicture(CharacterRun run) { + if (run.isSpecialCharacter() && !run.isObj() && !run.isOle2() && !run.isData() && run.text().startsWith("\u0008")) { + return true; + } + return false; + } /** * determines whether specified CharacterRun contains reference to a picture @@ -122,6 +140,46 @@ } return null; } + + /** + * Performs a recursive search for pictures in the given list of escher records. + * + * @param escherRecords the escher records. + * @param pictures the list to populate with the pictures. + */ + private void searchForPictures(List escherRecords, List pictures) + { + Iterator recordIter = escherRecords.iterator(); + while (recordIter.hasNext()) + { + Object obj = recordIter.next(); + if (obj instanceof EscherRecord) + { + EscherRecord escherRecord = (EscherRecord) obj; + + if (escherRecord instanceof EscherBSERecord) + { + EscherBSERecord bse = (EscherBSERecord) escherRecord; + EscherBlipRecord blip = bse.getBlipRecord(); + if (blip != null) + { + pictures.add(new Picture(blip.getPicturedata())); + } + else if (bse.getOffset() > 0) + { + // Blip stored in delay stream, which in a word doc, is the main stream + EscherRecordFactory recordFactory = new DefaultEscherRecordFactory(); + blip = (EscherBlipRecord) recordFactory.createRecord(_mainStream, bse.getOffset()); + blip.fillFields(_mainStream, bse.getOffset(), recordFactory); + pictures.add(new Picture(blip.getPicturedata())); + } + } + + // Recursive call. + searchForPictures(escherRecord.getChildRecords(), pictures); + } + } + } /** * Not all documents have all the images concatenated in the data stream @@ -136,7 +194,6 @@ for (int i = 0; i < range.numCharacterRuns(); i++) { CharacterRun run = range.getCharacterRun(i); String text = run.text(); - int j = text.charAt(0); Picture picture = extractPicture(run, false); if (picture != null) { pictures.add(picture); @@ -142,6 +199,8 @@ pictures.add(picture); } } + + searchForPictures(_dgg.getEscherRecords(), pictures); return pictures; } Index: src/scratchpad/src/org/apache/poi/hwpf/usermodel/Picture.java =================================================================== --- src/scratchpad/src/org/apache/poi/hwpf/usermodel/Picture.java (revision 653573) +++ src/scratchpad/src/org/apache/poi/hwpf/usermodel/Picture.java (working copy) @@ -98,6 +98,15 @@ fillImageContent(); } } + + public Picture(byte[] _dataStream) + { + this._dataStream = _dataStream; + this.dataBlockStartOfsset = 0; + this.dataBlockSize = _dataStream.length; + this.pictureBytesStartOffset = 0; + this.size = _dataStream.length; + } private void fillWidthHeight() { Index: src/scratchpad/testcases/org/apache/poi/hwpf/TestHWPFPictures.java =================================================================== --- src/scratchpad/testcases/org/apache/poi/hwpf/TestHWPFPictures.java (revision 653573) +++ src/scratchpad/testcases/org/apache/poi/hwpf/TestHWPFPictures.java (working copy) @@ -35,6 +35,7 @@ private String docAFile; private String docBFile; private String docCFile; + private String docDFile; private String imgAFile; private String imgBFile; @@ -39,6 +40,7 @@ private String imgAFile; private String imgBFile; private String imgCFile; + private String imgDFile; protected void setUp() throws Exception { String dirname = System.getProperty("HWPF.testdata.path"); @@ -46,6 +48,7 @@ docAFile = dirname + "/testPictures.doc"; docBFile = dirname + "/two_images.doc"; docCFile = dirname + "/vector_image.doc"; + docDFile = dirname + "/GaiaTest.doc"; imgAFile = dirname + "/simple_image.jpg"; imgBFile = dirname + "/simple_image.png"; @@ -50,6 +53,7 @@ imgAFile = dirname + "/simple_image.jpg"; imgBFile = dirname + "/simple_image.png"; imgCFile = dirname + "/vector_image.emf"; + imgDFile = dirname + "/GaiaTestImg.png"; } /** @@ -126,7 +130,22 @@ assertEquals(picBytes.length, pic.getContent().length); assertBytesSame(picBytes, pic.getContent()); } - + + public void testEscherDrawing() throws Exception + { + HWPFDocument docD = new HWPFDocument(new FileInputStream(docDFile)); + List allPictures = docD.getPicturesTable().getAllPictures(); + + assertEquals(1, allPictures.size()); + + Picture pic = (Picture) allPictures.get(0); + assertNotNull(pic); + byte[] picD = readFile(imgDFile); + + assertEquals(picD.length, pic.getContent().length); + + assertBytesSame(picD, pic.getContent()); + } private void assertBytesSame(byte[] a, byte[] b) { assertEquals(a.length, b.length);