exc );
}
}
+
+ /**
+ * Skips bytes from a stream. Returns -1L if EOF was hit before
+ * the end of the stream.
+ *
+ * @param in inputstream
+ * @param len length to skip
+ * @return number of bytes skipped
+ * @throws IOException on IOException
+ */
+ public static long skipFully(InputStream in, long len) throws IOException {
+ int total = 0;
+ while (true) {
+ long got = in.skip(len-total);
+ if (got < 0) {
+ return -1L;
+ }
+ total += got;
+ if (total == len) {
+ return total;
+ }
+ }
+ }
}
--- /dev/null
+/* ====================================================================
+ 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.hemf.extractor;
+
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Iterator;
+
+import org.apache.poi.hemf.record.HemfHeader;
+import org.apache.poi.hemf.record.HemfRecord;
+import org.apache.poi.hemf.record.HemfRecordType;
+import org.apache.poi.util.Internal;
+import org.apache.poi.util.LittleEndianInputStream;
+import org.apache.poi.util.RecordFormatException;
+
+/**
+ * Read-only EMF extractor. Lots remain
+ */
+@Internal
+public class HemfExtractor implements Iterable<HemfRecord> {
+
+ private HemfHeader header;
+ private final LittleEndianInputStream stream;
+
+ public HemfExtractor(InputStream is) throws IOException {
+ stream = new LittleEndianInputStream(is);
+ header = new HemfHeader();
+ long recordId = stream.readUInt();
+ long recordSize = stream.readUInt();
+
+ header = new HemfHeader();
+ header.init(stream, recordId, recordSize-8);
+ }
+
+ @Override
+ public Iterator<HemfRecord> iterator() {
+ return new HemfRecordIterator();
+ }
+
+ public HemfHeader getHeader() {
+ return header;
+ }
+
+ private class HemfRecordIterator implements Iterator<HemfRecord> {
+
+ private HemfRecord currentRecord = null;
+
+ HemfRecordIterator() {
+ //queue the first non-header record
+ currentRecord = _next();
+ }
+
+ @Override
+ public boolean hasNext() {
+ return currentRecord != null;
+ }
+
+ @Override
+ public HemfRecord next() {
+ HemfRecord toReturn = currentRecord;
+ currentRecord = _next();
+ return toReturn;
+ }
+
+ private HemfRecord _next() {
+ if (currentRecord != null && currentRecord.getRecordType().equals(HemfRecordType.eof)) {
+ return null;
+ }
+ long recordId = stream.readUInt();
+ long recordSize = stream.readUInt();
+
+ HemfRecord record = null;
+ HemfRecordType type = HemfRecordType.getById(recordId);
+ if (type == null) {
+ throw new RuntimeException("Undefined record of type:"+recordId);
+ }
+ try {
+ record = type.clazz.newInstance();
+ } catch (InstantiationException e) {
+ throw new RuntimeException(e);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ try {
+ record.init(stream, recordId, recordSize-8);
+ } catch (IOException e) {
+ throw new RecordFormatException(e);
+ }
+
+ return record;
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException("Remove not supported");
+ }
+
+ }
+}
--- /dev/null
+/* ====================================================================
+ 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.hemf.hemfplus.record;
+
+
+import java.io.IOException;
+
+import org.apache.poi.util.Internal;
+import org.apache.poi.util.LittleEndian;
+
+@Internal
+public class HemfPlusHeader implements HemfPlusRecord {
+
+ private int flags;
+ private long version; //hack for now; replace with EmfPlusGraphicsVersion object
+ private long emfPlusFlags;
+ private long logicalDpiX;
+ private long logicalDpiY;
+
+ @Override
+ public HemfPlusRecordType getRecordType() {
+ return HemfPlusRecordType.header;
+ }
+
+ public int getFlags() {
+ return flags;
+ }
+
+ @Override
+ public void init(byte[] dataBytes, int recordId, int flags) throws IOException {
+ //assert record id == header
+ this.flags = flags;
+ int offset = 0;
+ this.version = LittleEndian.getUInt(dataBytes, offset); offset += LittleEndian.INT_SIZE;
+ this.emfPlusFlags = LittleEndian.getUInt(dataBytes, offset); offset += LittleEndian.INT_SIZE;
+ this.logicalDpiX = LittleEndian.getUInt(dataBytes, offset); offset += LittleEndian.INT_SIZE;
+ this.logicalDpiY = LittleEndian.getUInt(dataBytes, offset);
+
+ }
+
+ public long getVersion() {
+ return version;
+ }
+
+ public long getEmfPlusFlags() {
+ return emfPlusFlags;
+ }
+
+ public long getLogicalDpiX() {
+ return logicalDpiX;
+ }
+
+ public long getLogicalDpiY() {
+ return logicalDpiY;
+ }
+
+ @Override
+ public String toString() {
+ return "HemfPlusHeader{" +
+ "flags=" + flags +
+ ", version=" + version +
+ ", emfPlusFlags=" + emfPlusFlags +
+ ", logicalDpiX=" + logicalDpiX +
+ ", logicalDpiY=" + logicalDpiY +
+ '}';
+ }
+}
--- /dev/null
+/* ====================================================================
+ 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.hemf.hemfplus.record;
+
+
+import java.io.IOException;
+
+import org.apache.poi.util.Internal;
+
+@Internal
+public interface HemfPlusRecord {
+
+ HemfPlusRecordType getRecordType();
+
+ int getFlags();
+
+ /**
+ *
+ * @param dataBytes these are the bytes that start after the id, flags, record size
+ * and go to the end of the record; they do not include any required padding
+ * at the end.
+ * @param recordId record type id
+ * @param flags flags
+ * @return
+ * @throws IOException, RecordFormatException
+ */
+ void init(byte[] dataBytes, int recordId, int flags) throws IOException;
+
+
+}
--- /dev/null
+/* ====================================================================
+ 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.hemf.hemfplus.record;
+
+import org.apache.poi.util.Internal;
+
+@Internal
+public enum HemfPlusRecordType {
+ header(0x4001, HemfPlusHeader.class),
+ endOfFile(0x4002, UnimplementedHemfPlusRecord.class),
+ comment(0x4003, UnimplementedHemfPlusRecord.class),
+ getDC(0x4004, UnimplementedHemfPlusRecord.class),
+ multiFormatStart(0x4005, UnimplementedHemfPlusRecord.class),
+ multiFormatSection(0x4006, UnimplementedHemfPlusRecord.class),
+ multiFormatEnd(0x4007, UnimplementedHemfPlusRecord.class),
+ object(0x4008, UnimplementedHemfPlusRecord.class),
+ clear(0x4009, UnimplementedHemfPlusRecord.class),
+ fillRects(0x400A, UnimplementedHemfPlusRecord.class),
+ drawRects(0x400B, UnimplementedHemfPlusRecord.class),
+ fillPolygon(0x400C, UnimplementedHemfPlusRecord.class),
+ drawLines(0x400D, UnimplementedHemfPlusRecord.class),
+ fillEllipse(0x400E, UnimplementedHemfPlusRecord.class),
+ drawEllipse(0x400F, UnimplementedHemfPlusRecord.class),
+ fillPie(0x4010, UnimplementedHemfPlusRecord.class),
+ drawPie(0x4011, UnimplementedHemfPlusRecord.class),
+ drawArc(0x4012, UnimplementedHemfPlusRecord.class),
+ fillRegion(0x4013, UnimplementedHemfPlusRecord.class),
+ fillPath(0x4014, UnimplementedHemfPlusRecord.class),
+ drawPath(0x4015, UnimplementedHemfPlusRecord.class),
+ fillClosedCurve(0x4016, UnimplementedHemfPlusRecord.class),
+ drawClosedCurve(0x4017, UnimplementedHemfPlusRecord.class),
+ drawCurve(0x4018, UnimplementedHemfPlusRecord.class),
+ drawBeziers(0x4019, UnimplementedHemfPlusRecord.class),
+ drawImage(0x401A, UnimplementedHemfPlusRecord.class),
+ drawImagePoints(0x401B, UnimplementedHemfPlusRecord.class),
+ drawString(0x401C, UnimplementedHemfPlusRecord.class),
+ setRenderingOrigin(0x401D, UnimplementedHemfPlusRecord.class),
+ setAntiAliasMode(0x401E, UnimplementedHemfPlusRecord.class),
+ setTextRenderingHint(0x401F, UnimplementedHemfPlusRecord.class),
+ setTextContrast(0x4020, UnimplementedHemfPlusRecord.class),
+ setInterpolationMode(0x4021, UnimplementedHemfPlusRecord.class),
+ setPixelOffsetMode(0x4022, UnimplementedHemfPlusRecord.class),
+ setComositingMode(0x4023, UnimplementedHemfPlusRecord.class),
+ setCompositingQuality(0x4024, UnimplementedHemfPlusRecord.class),
+ save(0x4025, UnimplementedHemfPlusRecord.class),
+ restore(0x4026, UnimplementedHemfPlusRecord.class),
+ beginContainer(0x4027, UnimplementedHemfPlusRecord.class),
+ beginContainerNoParams(0x428, UnimplementedHemfPlusRecord.class),
+ endContainer(0x4029, UnimplementedHemfPlusRecord.class),
+ setWorldTransform(0x402A, UnimplementedHemfPlusRecord.class),
+ resetWorldTransform(0x402B, UnimplementedHemfPlusRecord.class),
+ multiplyWorldTransform(0x402C, UnimplementedHemfPlusRecord.class),
+ translateWorldTransform(0x402D, UnimplementedHemfPlusRecord.class),
+ scaleWorldTransform(0x402E, UnimplementedHemfPlusRecord.class),
+ rotateWorldTransform(0x402F, UnimplementedHemfPlusRecord.class),
+ setPageTransform(0x4030, UnimplementedHemfPlusRecord.class),
+ resetClip(0x4031, UnimplementedHemfPlusRecord.class),
+ setClipRect(0x4032, UnimplementedHemfPlusRecord.class),
+ setClipRegion(0x4033, UnimplementedHemfPlusRecord.class),
+ setClipPath(0x4034, UnimplementedHemfPlusRecord.class),
+ offsetClip(0x4035, UnimplementedHemfPlusRecord.class),
+ drawDriverstring(0x4036, UnimplementedHemfPlusRecord.class),
+ strokeFillPath(0x4037, UnimplementedHemfPlusRecord.class),
+ serializableObject(0x4038, UnimplementedHemfPlusRecord.class),
+ setTSGraphics(0x4039, UnimplementedHemfPlusRecord.class),
+ setTSClip(0x403A, UnimplementedHemfPlusRecord.class);
+
+ public final long id;
+ public final Class<? extends HemfPlusRecord> clazz;
+
+ HemfPlusRecordType(long id, Class<? extends HemfPlusRecord> clazz) {
+ this.id = id;
+ this.clazz = clazz;
+ }
+
+ public static HemfPlusRecordType getById(long id) {
+ for (HemfPlusRecordType wrt : values()) {
+ if (wrt.id == id) return wrt;
+ }
+ return null;
+ }
+}
--- /dev/null
+/* ====================================================================
+ 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.hemf.hemfplus.record;
+
+
+import java.io.IOException;
+
+import org.apache.poi.util.Internal;
+
+@Internal
+public class UnimplementedHemfPlusRecord implements HemfPlusRecord {
+
+ private int recordId;
+ private int flags;
+ private byte[] recordBytes;
+
+ @Override
+ public HemfPlusRecordType getRecordType() {
+ return HemfPlusRecordType.getById(recordId);
+ }
+
+ @Override
+ public int getFlags() {
+ return flags;
+ }
+
+ @Override
+ public void init(byte[] recordBytes, int recordId, int flags) throws IOException {
+ this.recordId = recordId;
+ this.flags = flags;
+ this.recordBytes = recordBytes;
+ }
+
+ public byte[] getRecordBytes() {
+ //should probably defensively return a copy.
+ return recordBytes;
+ }
+}
--- /dev/null
+/* ====================================================================
+ 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.hemf.record;
+
+import org.apache.poi.util.Internal;
+
+/**
+ * Syntactic utility to allow for four different
+ * comment classes
+ */
+@Internal
+public abstract class AbstractHemfComment {
+
+ private final byte[] rawBytes;
+
+ public AbstractHemfComment(byte[] rawBytes) {
+ this.rawBytes = rawBytes;
+ }
+
+ public byte[] getRawBytes() {
+ return rawBytes;
+ }
+
+}
--- /dev/null
+/* ====================================================================
+ 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.hemf.record;
+
+import org.apache.poi.util.Internal;
+
+/**
+ * Contains arbitrary data
+ */
+@Internal
+public class HemfComment extends AbstractHemfComment {
+
+ public HemfComment(byte[] rawBytes) {
+ super(rawBytes);
+ }
+}
--- /dev/null
+/* ====================================================================
+ 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.hemf.record;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.poi.hemf.hemfplus.record.HemfPlusRecord;
+import org.apache.poi.hemf.hemfplus.record.HemfPlusRecordType;
+import org.apache.poi.util.Internal;
+import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.RecordFormatException;
+
+/**
+ * An HemfCommentEMFPlus may contain one or more EMFPlus records
+ */
+@Internal
+public class HemfCommentEMFPlus extends AbstractHemfComment {
+
+ long dataSize;
+ public HemfCommentEMFPlus(byte[] rawBytes) {
+ //these rawBytes contain only the EMFPlusRecord(s?)
+ //the EmfComment type, size, datasize and comment identifier have all been stripped.
+ //The EmfPlus type, flags, size, data size should start at rawBytes[0]
+ super(rawBytes);
+
+ }
+
+ public List<HemfPlusRecord> getRecords() {
+ return HemfPlusParser.parse(getRawBytes());
+ }
+
+ private static class HemfPlusParser {
+
+ public static List<HemfPlusRecord> parse(byte[] bytes) {
+ List<HemfPlusRecord> records = new ArrayList<HemfPlusRecord>();
+ int offset = 0;
+ while (offset < bytes.length) {
+ if (offset + 12 > bytes.length) {
+ //if header will go beyond bytes, stop now
+ //TODO: log or throw
+ break;
+ }
+ int type = LittleEndian.getUShort(bytes, offset); offset += LittleEndian.SHORT_SIZE;
+ int flags = LittleEndian.getUShort(bytes, offset); offset += LittleEndian.SHORT_SIZE;
+ long sizeLong = LittleEndian.getUInt(bytes, offset); offset += LittleEndian.INT_SIZE;
+ if (sizeLong >= Integer.MAX_VALUE) {
+ throw new RecordFormatException("size of emf record >= Integer.MAX_VALUE");
+ }
+ int size = (int)sizeLong;
+ long dataSizeLong = LittleEndian.getUInt(bytes, offset); offset += LittleEndian.INT_SIZE;
+ if (dataSizeLong >= Integer.MAX_VALUE) {
+ throw new RuntimeException("data size of emfplus record cannot be >= Integer.MAX_VALUE");
+ }
+ int dataSize = (int)dataSizeLong;
+ if (dataSize + offset > bytes.length) {
+ //TODO: log or throw?
+ break;
+ }
+ HemfPlusRecord record = buildRecord(type, flags, dataSize, offset, bytes);
+ records.add(record);
+ offset += dataSize;
+ }
+ return records;
+ }
+
+ private static HemfPlusRecord buildRecord(int recordId, int flags, int size, int offset, byte[] bytes) {
+ HemfPlusRecord record = null;
+ HemfPlusRecordType type = HemfPlusRecordType.getById(recordId);
+ if (type == null) {
+ throw new RuntimeException("Undefined record of type:"+recordId);
+ }
+ try {
+ record = type.clazz.newInstance();
+ } catch (InstantiationException e) {
+ throw new RuntimeException(e);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ byte[] dataBytes = new byte[size];
+ System.arraycopy(bytes, offset, dataBytes, 0, size);
+ try {
+ record.init(dataBytes, recordId, flags);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ return record;
+
+ }
+ }
+}
--- /dev/null
+/* ====================================================================
+ 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.hemf.record;
+
+import org.apache.poi.util.Internal;
+
+/**
+ * Not yet implemented
+ */
+@Internal
+public class HemfCommentEMFSpool extends AbstractHemfComment {
+
+ public HemfCommentEMFSpool(byte[] rawBytes) {
+ super(rawBytes);
+ }
+}
--- /dev/null
+/* ====================================================================
+ 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.hemf.record;
+
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.poi.util.Internal;
+import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.RecordFormatException;
+
+/**
+ * Container class for four subtypes of HemfCommentPublic: BeginGroup, EndGroup, MultiFormats
+ * and Windows Metafile.
+ */
+@Internal
+public class HemfCommentPublic {
+
+ /**
+ * Stub, to be implemented
+ */
+ public static class BeginGroup extends AbstractHemfComment {
+
+ public BeginGroup(byte[] rawBytes) {
+ super(rawBytes);
+ }
+
+ }
+
+ /**
+ * Stub, to be implemented
+ */
+ public static class EndGroup extends AbstractHemfComment {
+
+ public EndGroup(byte[] rawBytes) {
+ super(rawBytes);
+ }
+ }
+
+ public static class MultiFormats extends AbstractHemfComment {
+
+ public MultiFormats(byte[] rawBytes) {
+ super(rawBytes);
+ }
+
+ /**
+ *
+ * @return a list of HemfMultFormatsData
+ */
+ public List<HemfMultiFormatsData> getData() {
+
+ byte[] rawBytes = getRawBytes();
+ //note that raw bytes includes the public comment identifier
+ int currentOffset = 4 + 16;//4 public comment identifier, 16 for outputrect
+ long countFormats = LittleEndian.getUInt(rawBytes, currentOffset);
+ currentOffset += LittleEndian.INT_SIZE;
+ List<EmrFormat> emrFormatList = new ArrayList<EmrFormat>();
+ for (long i = 0; i < countFormats; i++) {
+ emrFormatList.add(new EmrFormat(rawBytes, currentOffset));
+ currentOffset += 4 * LittleEndian.INT_SIZE;
+ }
+ List<HemfMultiFormatsData> list = new ArrayList<HemfMultiFormatsData>();
+ for (EmrFormat emrFormat : emrFormatList) {
+ byte[] data = new byte[emrFormat.size];
+ System.arraycopy(rawBytes, emrFormat.offset-4, data, 0, emrFormat.size);
+ list.add(new HemfMultiFormatsData(emrFormat.signature, emrFormat.version, data));
+ }
+ return list;
+ }
+
+ private class EmrFormat {
+ long signature;
+ long version;
+ int size;
+ int offset;
+
+ public EmrFormat(byte[] rawBytes, int currentOffset) {
+ signature = LittleEndian.getUInt(rawBytes, currentOffset); currentOffset += LittleEndian.INT_SIZE;
+ version = LittleEndian.getUInt(rawBytes, currentOffset); currentOffset += LittleEndian.INT_SIZE;
+ //spec says this must be a 32bit "aligned" typo for "signed"?
+ //realistically, this has to be an int...
+ size = LittleEndian.getInt(rawBytes, currentOffset); currentOffset += LittleEndian.INT_SIZE;
+ //y, can be long, but realistically?
+ offset = LittleEndian.getInt(rawBytes, currentOffset); currentOffset += LittleEndian.INT_SIZE;
+ if (size < 0) {
+ throw new RecordFormatException("size for emrformat must be > 0");
+ }
+ if (offset < 0) {
+ throw new RecordFormatException("offset for emrformat must be > 0");
+ }
+ }
+ }
+ }
+
+ /**
+ * Stub, to be implemented
+ */
+ public static class WindowsMetafile extends AbstractHemfComment {
+
+ private final byte[] wmfBytes;
+ public WindowsMetafile(byte[] rawBytes) {
+ super(rawBytes);
+ int offset = LittleEndian.INT_SIZE;//public comment identifier
+ int version = LittleEndian.getUShort(rawBytes, offset); offset += LittleEndian.SHORT_SIZE;
+ int reserved = LittleEndian.getUShort(rawBytes, offset); offset += LittleEndian.SHORT_SIZE;
+ offset += LittleEndian.INT_SIZE; //checksum
+ offset += LittleEndian.INT_SIZE; //flags
+ long winMetafileSizeLong = LittleEndian.getUInt(rawBytes, offset); offset += LittleEndian.INT_SIZE;
+ if (winMetafileSizeLong == 0L) {
+ wmfBytes = new byte[0];
+ return;
+ }
+ if (winMetafileSizeLong > Integer.MAX_VALUE) {
+ throw new RecordFormatException("Metafile record length can't be > Integer.MAX_VALUE");
+ }
+ int winMetafileSize = (int)winMetafileSizeLong;
+ wmfBytes = new byte[winMetafileSize];
+ System.arraycopy(rawBytes, offset, wmfBytes, 0, winMetafileSize);
+ }
+
+ /**
+ *
+ * @return an InputStream for the embedded WMF file
+ */
+ public InputStream getWmfInputStream() {
+ return new ByteArrayInputStream(wmfBytes);
+ }
+ }
+
+ /**
+ * This encapulates a single record stored within
+ * a HemfCommentPublic.MultiFormats record.
+ */
+ public static class HemfMultiFormatsData {
+
+ long signature;
+ long version;
+ byte[] data;
+
+ public HemfMultiFormatsData(long signature, long version, byte[] data) {
+ this.signature = signature;
+ this.version = version;
+ this.data = data;
+ }
+
+ public long getSignature() {
+ return signature;
+ }
+
+ public long getVersion() {
+ return version;
+ }
+
+ public byte[] getData() {
+ return data;
+ }
+ }
+}
--- /dev/null
+/* ====================================================================
+ 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.hemf.record;
+
+
+import java.io.IOException;
+
+import org.apache.poi.util.IOUtils;
+import org.apache.poi.util.Internal;
+import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.LittleEndianInputStream;
+import org.apache.poi.util.RecordFormatException;
+
+/**
+ * This is the outer comment record that is recognized
+ * by the initial parse by {@link HemfRecordType#comment}.
+ * However, there are four types of comment: EMR_COMMENT,
+ * EMR_COMMENT_EMFPLUS, EMR_COMMENT_EMFSPOOL, and EMF_COMMENT_PUBLIC.
+ * To get the underlying comment, call {@link #getComment()}.
+ *
+ */
+@Internal
+public class HemfCommentRecord implements HemfRecord {
+
+ public final static long COMMENT_EMFSPOOL = 0x00000000;
+ public final static long COMMENT_EMFPLUS = 0x2B464D45;
+ public final static long COMMENT_PUBLIC = 0x43494447;
+
+
+ private AbstractHemfComment comment;
+ @Override
+ public HemfRecordType getRecordType() {
+ return HemfRecordType.comment;
+ }
+
+ @Override
+ public long init(LittleEndianInputStream leis, long recordId, long recordSize) throws IOException {
+ long dataSize = leis.readUInt(); recordSize -= LittleEndian.INT_SIZE;
+
+ byte[] optionalCommentIndentifierBuffer = new byte[4];
+ leis.readFully(optionalCommentIndentifierBuffer);
+ dataSize = dataSize-LittleEndian.INT_SIZE; //size minus the first int which could be a comment identifier
+ recordSize -= LittleEndian.INT_SIZE;
+ long optionalCommentIdentifier = LittleEndian.getInt(optionalCommentIndentifierBuffer) & 0x00FFFFFFFFL;
+ if (optionalCommentIdentifier == COMMENT_EMFSPOOL) {
+ comment = new HemfCommentEMFSpool(readToByteArray(leis, dataSize, recordSize));
+ } else if (optionalCommentIdentifier == COMMENT_EMFPLUS) {
+ comment = new HemfCommentEMFPlus(readToByteArray(leis, dataSize, recordSize));
+ } else if (optionalCommentIdentifier == COMMENT_PUBLIC) {
+ comment = CommentPublicParser.parse(readToByteArray(leis, dataSize, recordSize));
+ } else {
+ comment = new HemfComment(readToByteArray(optionalCommentIndentifierBuffer, leis, dataSize, recordSize));
+ }
+
+ return recordSize;
+ }
+
+ //this prepends the initial "int" which turned out NOT to be
+ //a signifier of emfplus, spool, public.
+ private byte[] readToByteArray(byte[] initialBytes, LittleEndianInputStream leis,
+ long remainingDataSize, long remainingRecordSize) throws IOException {
+ if (remainingDataSize > Integer.MAX_VALUE) {
+ throw new RecordFormatException("Data size can't be > Integer.MAX_VALUE");
+ }
+
+ if (remainingRecordSize > Integer.MAX_VALUE) {
+ throw new RecordFormatException("Record size can't be > Integer.MAX_VALUE");
+ }
+ if (remainingRecordSize == 0) {
+ return new byte[0];
+ }
+
+ int dataSize = (int)remainingDataSize;
+ int recordSize = (int)remainingRecordSize;
+ byte[] arr = new byte[dataSize+initialBytes.length];
+ System.arraycopy(initialBytes,0,arr, 0, initialBytes.length);
+ IOUtils.readFully(leis, arr, initialBytes.length, dataSize);
+ IOUtils.skipFully(leis, recordSize-dataSize);
+
+ return arr;
+ }
+
+ private byte[] readToByteArray(LittleEndianInputStream leis, long dataSize, long recordSize) throws IOException {
+ assert dataSize < Integer.MAX_VALUE;
+
+ if (recordSize == 0) {
+ return new byte[0];
+ }
+
+ byte[] arr = new byte[(int)dataSize];
+ IOUtils.readFully(leis, arr);
+ IOUtils.skipFully(leis, recordSize-dataSize);
+ return arr;
+ }
+
+ public AbstractHemfComment getComment() {
+ return comment;
+ }
+
+ private static class CommentPublicParser {
+ private static final long WINDOWS_METAFILE = 0x80000001L; //wmf
+ private static final long BEGINGROUP = 0x00000002; //beginning of a group of drawing records
+ private static final long ENDGROUP = 0x00000003; //end of a group of drawing records
+ private static final long MULTIFORMATS = 0x40000004; //allows multiple definitions of an image, including encapsulated postscript
+ private static final long UNICODE_STRING = 0x00000040; //reserved. must not be used
+ private static final long UNICODE_END = 0x00000080; //reserved, must not be used
+
+ private static AbstractHemfComment parse(byte[] bytes) {
+ long publicCommentIdentifier = LittleEndian.getUInt(bytes, 0);
+ if (publicCommentIdentifier == WINDOWS_METAFILE) {
+ return new HemfCommentPublic.WindowsMetafile(bytes);
+ } else if (publicCommentIdentifier == BEGINGROUP) {
+ return new HemfCommentPublic.BeginGroup(bytes);
+ } else if (publicCommentIdentifier == ENDGROUP) {
+ return new HemfCommentPublic.EndGroup(bytes);
+ } else if (publicCommentIdentifier == MULTIFORMATS) {
+ return new HemfCommentPublic.MultiFormats(bytes);
+ } else if (publicCommentIdentifier == UNICODE_STRING || publicCommentIdentifier == UNICODE_END) {
+ throw new RuntimeException("UNICODE_STRING/UNICODE_END values are reserved in CommentPublic records");
+ }
+ throw new RuntimeException("Unrecognized public comment type:" +publicCommentIdentifier + " ; " + WINDOWS_METAFILE);
+ }
+ }
+}
--- /dev/null
+/* ====================================================================
+ 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.hemf.record;
+
+import java.awt.Rectangle;
+import java.io.IOException;
+
+import org.apache.poi.util.IOUtils;
+import org.apache.poi.util.Internal;
+import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.LittleEndianInputStream;
+
+/**
+ * Extracts the full header from EMF files.
+ * @see org.apache.poi.sl.image.ImageHeaderEMF
+ */
+@Internal
+public class HemfHeader implements HemfRecord {
+
+ private Rectangle boundsRectangle;
+ private Rectangle frameRectangle;
+ private long bytes;
+ private long records;
+ private int handles;
+ private long nDescription;
+ private long offDescription;
+ private long nPalEntries;
+ private boolean hasExtension1;
+ private long cbPixelFormat;
+ private long offPixelFormat;
+ private long bOpenGL;
+ private boolean hasExtension2;
+ private long micrometersX;
+ private long micrometersY;
+
+ public Rectangle getBoundsRectangle() {
+ return boundsRectangle;
+ }
+
+ public Rectangle getFrameRectangle() {
+ return frameRectangle;
+ }
+
+ public long getBytes() {
+ return bytes;
+ }
+
+ public long getRecords() {
+ return records;
+ }
+
+ public int getHandles() {
+ return handles;
+ }
+
+ public long getnDescription() {
+ return nDescription;
+ }
+
+ public long getOffDescription() {
+ return offDescription;
+ }
+
+ public long getnPalEntries() {
+ return nPalEntries;
+ }
+
+ public boolean isHasExtension1() {
+ return hasExtension1;
+ }
+
+ public long getCbPixelFormat() {
+ return cbPixelFormat;
+ }
+
+ public long getOffPixelFormat() {
+ return offPixelFormat;
+ }
+
+ public long getbOpenGL() {
+ return bOpenGL;
+ }
+
+ public boolean isHasExtension2() {
+ return hasExtension2;
+ }
+
+ public long getMicrometersX() {
+ return micrometersX;
+ }
+
+ public long getMicrometersY() {
+ return micrometersY;
+ }
+
+ @Override
+ public String toString() {
+ return "HemfHeader{" +
+ "boundsRectangle=" + boundsRectangle +
+ ", frameRectangle=" + frameRectangle +
+ ", bytes=" + bytes +
+ ", records=" + records +
+ ", handles=" + handles +
+ ", nDescription=" + nDescription +
+ ", offDescription=" + offDescription +
+ ", nPalEntries=" + nPalEntries +
+ ", hasExtension1=" + hasExtension1 +
+ ", cbPixelFormat=" + cbPixelFormat +
+ ", offPixelFormat=" + offPixelFormat +
+ ", bOpenGL=" + bOpenGL +
+ ", hasExtension2=" + hasExtension2 +
+ ", micrometersX=" + micrometersX +
+ ", micrometersY=" + micrometersY +
+ '}';
+ }
+
+ @Override
+ public HemfRecordType getRecordType() {
+ return HemfRecordType.header;
+ }
+
+ @Override
+ public long init(LittleEndianInputStream leis, long recordId, long recordSize) throws IOException {
+ if (recordId != 1L) {
+ throw new IOException("Not a valid EMF header. Record type:"+recordId);
+ }
+ //read the record--id and size (2 bytes) have already been read
+ byte[] data = new byte[(int)recordSize];
+ IOUtils.readFully(leis, data);
+
+ int offset = 0;
+
+ //bounds
+ int boundsLeft = LittleEndian.getInt(data, offset); offset += LittleEndian.INT_SIZE;
+ int boundsTop = LittleEndian.getInt(data, offset); offset += LittleEndian.INT_SIZE;
+ int boundsRight = LittleEndian.getInt(data, offset); offset += LittleEndian.INT_SIZE;
+ int boundsBottom = LittleEndian.getInt(data, offset); offset += LittleEndian.INT_SIZE;
+ boundsRectangle = new Rectangle(boundsLeft, boundsTop,
+ boundsRight - boundsLeft, boundsBottom - boundsTop);
+
+ int frameLeft = LittleEndian.getInt(data, offset); offset += LittleEndian.INT_SIZE;
+ int frameTop = LittleEndian.getInt(data, offset); offset += LittleEndian.INT_SIZE;
+ int frameRight = LittleEndian.getInt(data, offset); offset += LittleEndian.INT_SIZE;
+ int frameBottom = LittleEndian.getInt(data, offset); offset += LittleEndian.INT_SIZE;
+ frameRectangle = new Rectangle(frameLeft, frameTop,
+ frameRight - frameLeft, frameBottom - frameTop);
+
+ long recordSignature = LittleEndian.getInt(data, offset); offset += LittleEndian.INT_SIZE;
+ if (recordSignature != 0x464D4520) {
+ throw new IOException("bad record signature: " + recordSignature);
+ }
+
+ long version = LittleEndian.getInt(data, offset); offset += LittleEndian.INT_SIZE;
+ //According to the spec, MSOffice doesn't pay attention to this value.
+ //It _should_ be 0x00010000
+ bytes = LittleEndian.getUInt(data, offset); offset += LittleEndian.INT_SIZE;
+ records = LittleEndian.getUInt(data, offset); offset += LittleEndian.INT_SIZE;
+ handles = LittleEndian.getUShort(data, offset);offset += LittleEndian.SHORT_SIZE;
+ offset += LittleEndian.SHORT_SIZE;//reserved
+ nDescription = LittleEndian.getUInt(data, offset); offset += LittleEndian.INT_SIZE;
+ offDescription = LittleEndian.getUInt(data, offset); offset += LittleEndian.INT_SIZE;
+ nPalEntries = LittleEndian.getUInt(data, offset); offset += LittleEndian.INT_SIZE;
+
+ //should be skips
+ offset += 8;//device
+ offset += 8;//millimeters
+
+
+ if (recordSize+8 >= 100) {
+ hasExtension1 = true;
+ cbPixelFormat = LittleEndian.getUInt(data, offset); offset += LittleEndian.INT_SIZE;
+ offPixelFormat = LittleEndian.getUInt(data, offset); offset += LittleEndian.INT_SIZE;
+ bOpenGL= LittleEndian.getUInt(data, offset); offset += LittleEndian.INT_SIZE;
+ }
+
+ if (recordSize+8 >= 108) {
+ hasExtension2 = true;
+ micrometersX = LittleEndian.getUInt(data, offset); offset += LittleEndian.INT_SIZE;
+ micrometersY = LittleEndian.getUInt(data, offset); offset += LittleEndian.INT_SIZE;
+ }
+ return recordSize;
+ }
+}
--- /dev/null
+/* ====================================================================
+ 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.hemf.record;
+
+
+import java.io.IOException;
+
+import org.apache.poi.util.Internal;
+import org.apache.poi.util.LittleEndianInputStream;
+
+@Internal
+public interface HemfRecord {
+
+ HemfRecordType getRecordType();
+
+ /**
+ * Init record from stream
+ *
+ * @param leis the little endian input stream
+ * @return count of processed bytes
+ * @throws IOException
+ */
+ long init(LittleEndianInputStream leis, long recordId, long recordSize) throws IOException;
+
+
+}
--- /dev/null
+/* ====================================================================
+ 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.hemf.record;
+
+import org.apache.poi.util.Internal;
+
+@Internal
+public enum HemfRecordType {
+
+ header(0x00000001, UnimplementedHemfRecord.class),
+ polybeizer(0x00000002, UnimplementedHemfRecord.class),
+ polygon(0x00000003, UnimplementedHemfRecord.class),
+ polyline(0x00000004, UnimplementedHemfRecord.class),
+ polybezierto(0x00000005, UnimplementedHemfRecord.class),
+ polylineto(0x00000006, UnimplementedHemfRecord.class),
+ polypolyline(0x00000007, UnimplementedHemfRecord.class),
+ polypolygon(0x00000008, UnimplementedHemfRecord.class),
+ setwindowextex(0x00000009, UnimplementedHemfRecord.class),
+ setwindoworgex(0x0000000A, UnimplementedHemfRecord.class),
+ setviewportextex(0x0000000B, UnimplementedHemfRecord.class),
+ setviewportorgex(0x0000000C, UnimplementedHemfRecord.class),
+ setbrushorgex(0x0000000D, UnimplementedHemfRecord.class),
+ eof(0x0000000E, UnimplementedHemfRecord.class),
+ setpixelv(0x0000000F, UnimplementedHemfRecord.class),
+ setmapperflags(0x00000010, UnimplementedHemfRecord.class),
+ setmapmode(0x00000011, UnimplementedHemfRecord.class),
+ setbkmode(0x00000012, UnimplementedHemfRecord.class),
+ setpolyfillmode(0x00000013, UnimplementedHemfRecord.class),
+ setrop2(0x00000014, UnimplementedHemfRecord.class),
+ setstretchbltmode(0x00000015, UnimplementedHemfRecord.class),
+ settextalign(0x00000016, HemfText.SetTextAlign.class),
+ setcoloradjustment(0x00000017, UnimplementedHemfRecord.class),
+ settextcolor(0x00000018, HemfText.SetTextColor.class),
+ setbkcolor(0x00000019, UnimplementedHemfRecord.class),
+ setoffsetcliprgn(0x0000001A, UnimplementedHemfRecord.class),
+ setmovetoex(0x0000001B, UnimplementedHemfRecord.class),
+ setmetargn(0x0000001C, UnimplementedHemfRecord.class),
+ setexcludecliprect(0x0000001D, UnimplementedHemfRecord.class),
+ setintersectcliprect(0x0000001E, UnimplementedHemfRecord.class),
+ scaleviewportextex(0x0000001F, UnimplementedHemfRecord.class),
+ scalewindowextex(0x00000020, UnimplementedHemfRecord.class),
+ savedc(0x00000021, UnimplementedHemfRecord.class),
+ restoredc(0x00000022, UnimplementedHemfRecord.class),
+ setworldtransform(0x00000023, UnimplementedHemfRecord.class),
+ modifyworldtransform(0x00000024, UnimplementedHemfRecord.class),
+ selectobject(0x00000025, UnimplementedHemfRecord.class),
+ createpen(0x00000026, UnimplementedHemfRecord.class),
+ createbrushindirect(0x00000027, UnimplementedHemfRecord.class),
+ deleteobject(0x00000028, UnimplementedHemfRecord.class),
+ anglearc(0x00000029, UnimplementedHemfRecord.class),
+ ellipse(0x0000002A, UnimplementedHemfRecord.class),
+ rectangle(0x0000002B, UnimplementedHemfRecord.class),
+ roundirect(0x0000002C, UnimplementedHemfRecord.class),
+ arc(0x0000002D, UnimplementedHemfRecord.class),
+ chord(0x0000002E, UnimplementedHemfRecord.class),
+ pie(0x0000002F, UnimplementedHemfRecord.class),
+ selectpalette(0x00000030, UnimplementedHemfRecord.class),
+ createpalette(0x00000031, UnimplementedHemfRecord.class),
+ setpaletteentries(0x00000032, UnimplementedHemfRecord.class),
+ resizepalette(0x00000033, UnimplementedHemfRecord.class),
+ realizepalette(0x0000034, UnimplementedHemfRecord.class),
+ extfloodfill(0x00000035, UnimplementedHemfRecord.class),
+ lineto(0x00000036, UnimplementedHemfRecord.class),
+ arcto(0x00000037, UnimplementedHemfRecord.class),
+ polydraw(0x00000038, UnimplementedHemfRecord.class),
+ setarcdirection(0x00000039, UnimplementedHemfRecord.class),
+ setmiterlimit(0x0000003A, UnimplementedHemfRecord.class),
+ beginpath(0x0000003B, UnimplementedHemfRecord.class),
+ endpath(0x0000003C, UnimplementedHemfRecord.class),
+ closefigure(0x0000003D, UnimplementedHemfRecord.class),
+ fillpath(0x0000003E, UnimplementedHemfRecord.class),
+ strokeandfillpath(0x0000003F, UnimplementedHemfRecord.class),
+ strokepath(0x00000040, UnimplementedHemfRecord.class),
+ flattenpath(0x00000041, UnimplementedHemfRecord.class),
+ widenpath(0x00000042, UnimplementedHemfRecord.class),
+ selectclippath(0x00000043, UnimplementedHemfRecord.class),
+ abortpath(0x00000044, UnimplementedHemfRecord.class), //no 45?!
+ comment(0x00000046, HemfCommentRecord.class),
+ fillrgn(0x00000047, UnimplementedHemfRecord.class),
+ framergn(0x00000048, UnimplementedHemfRecord.class),
+ invertrgn(0x00000049, UnimplementedHemfRecord.class),
+ paintrgn(0x0000004A, UnimplementedHemfRecord.class),
+ extselectciprrgn(0x0000004B, UnimplementedHemfRecord.class),
+ bitblt(0x0000004C, UnimplementedHemfRecord.class),
+ stretchblt(0x0000004D, UnimplementedHemfRecord.class),
+ maskblt(0x0000004E, UnimplementedHemfRecord.class),
+ plgblt(0x0000004F, UnimplementedHemfRecord.class),
+ setbitstodevice(0x00000050, UnimplementedHemfRecord.class),
+ stretchdibits(0x00000051, UnimplementedHemfRecord.class),
+ extcreatefontindirectw(0x00000052, HemfText.ExtCreateFontIndirectW.class),
+ exttextouta(0x00000053, HemfText.ExtTextOutA.class),
+ exttextoutw(0x00000054, HemfText.ExtTextOutW.class),
+ polybezier16(0x00000055, UnimplementedHemfRecord.class),
+ polygon16(0x00000056, UnimplementedHemfRecord.class),
+ polyline16(0x00000057, UnimplementedHemfRecord.class),
+ polybezierto16(0x00000058, UnimplementedHemfRecord.class),
+ polylineto16(0x00000059, UnimplementedHemfRecord.class),
+ polypolyline16(0x0000005A, UnimplementedHemfRecord.class),
+ polypolygon16(0x0000005B, UnimplementedHemfRecord.class),
+ polydraw16(0x0000005C, UnimplementedHemfRecord.class),
+ createmonobrush16(0x0000005D, UnimplementedHemfRecord.class),
+ createdibpatternbrushpt(0x0000005E, UnimplementedHemfRecord.class),
+ extcreatepen(0x0000005F, UnimplementedHemfRecord.class),
+ polytextouta(0x00000060, HemfText.PolyTextOutA.class),
+ polytextoutw(0x00000061, HemfText.PolyTextOutW.class),
+ seticmmode(0x00000062, UnimplementedHemfRecord.class),
+ createcolorspace(0x00000063, UnimplementedHemfRecord.class),
+ setcolorspace(0x00000064, UnimplementedHemfRecord.class),
+ deletecolorspace(0x00000065, UnimplementedHemfRecord.class),
+ glsrecord(0x00000066, UnimplementedHemfRecord.class),
+ glsboundedrecord(0x00000067, UnimplementedHemfRecord.class),
+ pixelformat(0x00000068, UnimplementedHemfRecord.class),
+ drawescape(0x00000069, UnimplementedHemfRecord.class),
+ extescape(0x0000006A, UnimplementedHemfRecord.class),//no 6b?!
+ smalltextout(0x0000006C, UnimplementedHemfRecord.class),
+ forceufimapping(0x0000006D, UnimplementedHemfRecord.class),
+ namedescape(0x0000006E, UnimplementedHemfRecord.class),
+ colorcorrectpalette(0x0000006F, UnimplementedHemfRecord.class),
+ seticmprofilea(0x00000070, UnimplementedHemfRecord.class),
+ seticmprofilew(0x00000071, UnimplementedHemfRecord.class),
+ alphablend(0x00000072, UnimplementedHemfRecord.class),
+ setlayout(0x00000073, UnimplementedHemfRecord.class),
+ transparentblt(0x00000074, UnimplementedHemfRecord.class),
+ gradientfill(0x00000076, UnimplementedHemfRecord.class), //no 75?!
+ setlinkdufis(0x00000077, UnimplementedHemfRecord.class),
+ settextjustification(0x00000078, HemfText.SetTextJustification.class),
+ colormatchtargetw(0x00000079, UnimplementedHemfRecord.class),
+ createcolorspacew(0x0000007A, UnimplementedHemfRecord.class);
+
+ public final long id;
+ public final Class<? extends HemfRecord> clazz;
+
+ HemfRecordType(long id, Class<? extends HemfRecord> clazz) {
+ this.id = id;
+ this.clazz = clazz;
+ }
+
+ public static HemfRecordType getById(long id) {
+ for (HemfRecordType wrt : values()) {
+ if (wrt.id == id) return wrt;
+ }
+ return null;
+ }
+}
--- /dev/null
+/* ====================================================================
+ 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.hemf.record;
+
+
+import java.io.ByteArrayInputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.charset.Charset;
+
+import org.apache.poi.util.IOUtils;
+import org.apache.poi.util.Internal;
+import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.LittleEndianInputStream;
+import org.apache.poi.util.RecordFormatException;
+
+/**
+ * Container class to gather all text-related commands
+ * This is starting out as read only, and very little is actually
+ * implemented at this point!
+ */
+@Internal
+public class HemfText {
+
+ private final static Charset UTF16LE = Charset.forName("UTF-16LE");
+
+ public static class ExtCreateFontIndirectW extends UnimplementedHemfRecord {
+ }
+
+ public static class ExtTextOutA implements HemfRecord {
+
+ private long left,top,right,bottom;
+
+ //TODO: translate this to a graphicsmode enum
+ private long graphicsMode;
+
+ private long exScale;
+ private long eyScale;
+ EmrTextObject textObject;
+
+ @Override
+ public HemfRecordType getRecordType() {
+ return HemfRecordType.exttextouta;
+ }
+
+ @Override
+ public long init(LittleEndianInputStream leis, long recordId, long recordSize) throws IOException {
+ //note that the first 2 uInts have been read off and the recordsize has
+ //been decreased by 8
+ left = leis.readInt();
+ top = leis.readInt();
+ right = leis.readInt();
+ bottom = leis.readInt();
+ graphicsMode = leis.readUInt();
+ exScale = leis.readUInt();
+ eyScale = leis.readUInt();
+
+ int recordSizeInt = -1;
+ if (recordSize < Integer.MAX_VALUE) {
+ recordSizeInt = (int)recordSize;
+ } else {
+ throw new RecordFormatException("can't have text length > Integer.MAX_VALUE");
+ }
+ //guarantee to read the rest of the EMRTextObjectRecord
+ //emrtextbytes start after 7*4 bytes read above
+ byte[] emrTextBytes = new byte[recordSizeInt-(7*LittleEndian.INT_SIZE)];
+ IOUtils.readFully(leis, emrTextBytes);
+ textObject = new EmrTextObject(emrTextBytes, getEncodingHint(), 20);//should be 28, but recordSizeInt has already subtracted 8
+ return recordSize;
+ }
+
+ protected Charset getEncodingHint() {
+ return null;
+ }
+
+ /**
+ *
+ * To be implemented! We need to get the current character set
+ * from the current font for {@link ExtTextOutA},
+ * which has to be tracked in the playback device.
+ *
+ * For {@link ExtTextOutW}, the charset is "UTF-16LE"
+ *
+ * @param charset the charset to be used to decode the character bytes
+ * @return
+ * @throws IOException
+ */
+ public String getText(Charset charset) throws IOException {
+ return textObject.getText(charset);
+ }
+
+ /**
+ *
+ * @return the x offset for the EmrTextObject
+ */
+ public long getX() {
+ return textObject.x;
+ }
+
+ /**
+ *
+ * @return the y offset for the EmrTextObject
+ */
+ public long getY() {
+ return textObject.y;
+ }
+
+ public long getLeft() {
+ return left;
+ }
+
+ public long getTop() {
+ return top;
+ }
+
+ public long getRight() {
+ return right;
+ }
+
+ public long getBottom() {
+ return bottom;
+ }
+
+ public long getGraphicsMode() {
+ return graphicsMode;
+ }
+
+ public long getExScale() {
+ return exScale;
+ }
+
+ public long getEyScale() {
+ return eyScale;
+ }
+
+ }
+
+ public static class ExtTextOutW extends ExtTextOutA {
+
+ @Override
+ public HemfRecordType getRecordType() {
+ return HemfRecordType.exttextoutw;
+ }
+
+ @Override
+ protected Charset getEncodingHint() {
+ return UTF16LE;
+ }
+
+ public String getText() throws IOException {
+ return getText(UTF16LE);
+ }
+ }
+
+ /**
+ * Needs to be implemented. Couldn't find example.
+ */
+ public static class PolyTextOutA extends UnimplementedHemfRecord {
+
+ }
+
+ /**
+ * Needs to be implemented. Couldn't find example.
+ */
+ public static class PolyTextOutW extends UnimplementedHemfRecord {
+
+ }
+
+ public static class SetTextAlign extends UnimplementedHemfRecord {
+ }
+
+ public static class SetTextColor extends UnimplementedHemfRecord {
+ }
+
+
+ public static class SetTextJustification extends UnimplementedHemfRecord {
+
+ }
+
+ private static class EmrTextObject {
+ long x;
+ long y;
+ int numChars;
+ byte[] rawTextBytes;//this stores _all_ of the bytes to the end of the EMRTextObject record.
+ //Because of potential variable length encodings, must
+ //carefully read only the numChars from this byte array.
+
+ EmrTextObject(byte[] emrTextObjBytes, Charset charsetHint, int readSoFar) throws IOException {
+
+ int offset = 0;
+ x = LittleEndian.getUInt(emrTextObjBytes, offset); offset+= LittleEndian.INT_SIZE;
+ y = LittleEndian.getUInt(emrTextObjBytes, offset); offset+= LittleEndian.INT_SIZE;
+ long numCharsLong = LittleEndian.getUInt(emrTextObjBytes, offset); offset += LittleEndian.INT_SIZE;
+ long offString = LittleEndian.getUInt(emrTextObjBytes, offset); offset += LittleEndian.INT_SIZE;
+ int start = (int)offString-offset-readSoFar;
+
+ if (numCharsLong == 0) {
+ rawTextBytes = new byte[0];
+ numChars = 0;
+ return;
+ }
+ if (numCharsLong > Integer.MAX_VALUE) {
+ throw new RecordFormatException("Number of characters can't be > Integer.MAX_VALUE");
+ }
+ numChars = (int)numCharsLong;
+ rawTextBytes = new byte[emrTextObjBytes.length-start];
+ System.arraycopy(emrTextObjBytes, start, rawTextBytes, 0, emrTextObjBytes.length-start);
+ }
+
+ String getText(Charset charset) throws IOException {
+ StringBuilder sb = new StringBuilder();
+ Reader r = null;
+ try {
+ r = new InputStreamReader(new ByteArrayInputStream(rawTextBytes), charset);
+ for (int i = 0; i < numChars; i++) {
+ sb.appendCodePoint(readCodePoint(r));
+ }
+ } finally {
+ IOUtils.closeQuietly(r);
+ }
+ return sb.toString();
+ }
+
+ //TODO: move this to IOUtils?
+ private int readCodePoint(Reader r) throws IOException {
+ int c1 = r.read();
+ if (c1 == -1) {
+ throw new EOFException("Tried to read beyond byte array");
+ }
+ if (!Character.isHighSurrogate((char)c1)) {
+ return c1;
+ }
+ int c2 = r.read();
+ if (c2 == -1) {
+ throw new EOFException("Tried to read beyond byte array");
+ }
+ if (!Character.isLowSurrogate((char)c2)) {
+ throw new RecordFormatException("Expected low surrogate after high surrogate");
+ }
+ return Character.toCodePoint((char)c1, (char)c2);
+ }
+ }
+
+
+}
--- /dev/null
+/* ====================================================================
+ 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.hemf.record;
+
+
+import java.io.IOException;
+
+import org.apache.poi.util.IOUtils;
+import org.apache.poi.util.Internal;
+import org.apache.poi.util.LittleEndianInputStream;
+
+@Internal
+public class UnimplementedHemfRecord implements HemfRecord {
+
+ private long recordId;
+ public UnimplementedHemfRecord() {
+
+ }
+
+ @Override
+ public HemfRecordType getRecordType() {
+ return HemfRecordType.getById(recordId);
+ }
+
+ @Override
+ public long init(LittleEndianInputStream leis, long recordId, long recordSize) throws IOException {
+ this.recordId = recordId;
+ long skipped = IOUtils.skipFully(leis, recordSize);
+ if (skipped < 0) {
+ throw new IOException("End of stream reached before record read");
+ }
+ return skipped;
+ }
+}
--- /dev/null
+/* ====================================================================
+ 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.hemf.extractor;
+
+import static org.apache.poi.POITestCase.assertContains;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.InputStream;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.poi.POIDataSamples;
+import org.apache.poi.hemf.record.AbstractHemfComment;
+import org.apache.poi.hemf.record.HemfCommentPublic;
+import org.apache.poi.hemf.record.HemfCommentRecord;
+import org.apache.poi.hemf.record.HemfHeader;
+import org.apache.poi.hemf.record.HemfRecord;
+import org.apache.poi.hemf.record.HemfRecordType;
+import org.apache.poi.hemf.record.HemfText;
+import org.junit.Test;
+
+public class HemfExtractorTest {
+
+ @Test
+ public void testBasicWindows() throws Exception {
+ InputStream is = POIDataSamples.getSpreadSheetInstance().openResourceAsStream("SimpleEMF_windows.emf");
+ HemfExtractor ex = new HemfExtractor(is);
+ HemfHeader header = ex.getHeader();
+ assertEquals(27864, header.getBytes());
+ assertEquals(31, header.getRecords());
+ assertEquals(3, header.getHandles());
+ assertEquals(346000, header.getMicrometersX());
+ assertEquals(194000, header.getMicrometersY());
+
+ int records = 0;
+ for (HemfRecord record : ex) {
+ records++;
+ }
+
+ assertEquals(header.getRecords() - 1, records);
+ }
+
+ @Test
+ public void testBasicMac() throws Exception {
+ InputStream is =
+ POIDataSamples.getSpreadSheetInstance().openResourceAsStream("SimpleEMF_mac.emf");
+ HemfExtractor ex = new HemfExtractor(is);
+ HemfHeader header = ex.getHeader();
+
+ int records = 0;
+ boolean extractedData = false;
+ for (HemfRecord record : ex) {
+ if (record.getRecordType() == HemfRecordType.comment) {
+ AbstractHemfComment comment = ((HemfCommentRecord) record).getComment();
+ if (comment instanceof HemfCommentPublic.MultiFormats) {
+ for (HemfCommentPublic.HemfMultiFormatsData d : ((HemfCommentPublic.MultiFormats) comment).getData()) {
+ byte[] data = d.getData();
+ //make sure header starts at 0
+ assertEquals('%', data[0]);
+ assertEquals('P', data[1]);
+ assertEquals('D', data[2]);
+ assertEquals('F', data[3]);
+
+ //make sure byte array ends at EOF\n
+ assertEquals('E', data[data.length - 4]);
+ assertEquals('O', data[data.length - 3]);
+ assertEquals('F', data[data.length - 2]);
+ assertEquals('\n', data[data.length - 1]);
+ extractedData = true;
+ }
+ }
+ }
+ records++;
+ }
+ assertTrue(extractedData);
+ assertEquals(header.getRecords() - 1, records);
+ }
+
+ @Test
+ public void testMacText() throws Exception {
+ InputStream is =
+ POIDataSamples.getSpreadSheetInstance().openResourceAsStream("SimpleEMF_mac.emf");
+ HemfExtractor ex = new HemfExtractor(is);
+
+ long lastY = -1;
+ long lastX = -1;
+ long fudgeFactorX = 1000;//derive this from the font information!
+ StringBuilder sb = new StringBuilder();
+ for (HemfRecord record : ex) {
+ if (record.getRecordType().equals(HemfRecordType.exttextoutw)) {
+ HemfText.ExtTextOutW extTextOutW = (HemfText.ExtTextOutW) record;
+ if (lastY > -1 && lastY != extTextOutW.getY()) {
+ sb.append("\n");
+ lastX = -1;
+ }
+ if (lastX > -1 && extTextOutW.getX() - lastX > fudgeFactorX) {
+ sb.append(" ");
+ }
+ sb.append(extTextOutW.getText());
+ lastY = extTextOutW.getY();
+ lastX = extTextOutW.getX();
+ }
+ }
+ String txt = sb.toString();
+ assertContains(txt, "Tika http://incubator.apache.org");
+ assertContains(txt, "Latest News\n");
+ }
+
+ @Test
+ public void testWindowsText() throws Exception {
+ InputStream is = POIDataSamples.getSpreadSheetInstance().openResourceAsStream("SimpleEMF_windows.emf");
+ HemfExtractor ex = new HemfExtractor(is);
+ long lastY = -1;
+ long lastX = -1;
+ long fudgeFactorX = 1000;//derive this from the font or frame/bounds information
+ StringBuilder sb = new StringBuilder();
+ Set<String> expectedParts = new HashSet<String>();
+ expectedParts.add("C:\\Users\\tallison\\");
+ expectedParts.add("testPDF.pdf");
+ int foundExpected = 0;
+ for (HemfRecord record : ex) {
+ if (record.getRecordType().equals(HemfRecordType.exttextoutw)) {
+ HemfText.ExtTextOutW extTextOutW = (HemfText.ExtTextOutW) record;
+ if (lastY > -1 && lastY != extTextOutW.getY()) {
+ sb.append("\n");
+ lastX = -1;
+ }
+ if (lastX > -1 && extTextOutW.getX() - lastX > fudgeFactorX) {
+ sb.append(" ");
+ }
+ String txt = extTextOutW.getText();
+ if (expectedParts.contains(txt)) {
+ foundExpected++;
+ }
+ sb.append(txt);
+ lastY = extTextOutW.getY();
+ lastX = extTextOutW.getX();
+ }
+ }
+ String txt = sb.toString();
+ assertContains(txt, "C:\\Users\\tallison\\\n");
+ assertContains(txt, "asf2-git-1.x\\tika-\n");
+ assertEquals(expectedParts.size(), foundExpected);
+ }
+
+ /*
+ govdocs1 064213.doc-0.emf contains an example of extextouta
+ */
+
+}
\ No newline at end of file
--- /dev/null
+/* ====================================================================
+ 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.hemf.hemfplus.extractor;
+
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.poi.POIDataSamples;
+import org.apache.poi.hemf.extractor.HemfExtractor;
+import org.apache.poi.hemf.hemfplus.record.HemfPlusHeader;
+import org.apache.poi.hemf.hemfplus.record.HemfPlusRecord;
+import org.apache.poi.hemf.hemfplus.record.HemfPlusRecordType;
+import org.apache.poi.hemf.record.HemfCommentEMFPlus;
+import org.apache.poi.hemf.record.HemfCommentRecord;
+import org.apache.poi.hemf.record.HemfRecord;
+import org.junit.Test;
+
+public class HemfPlusExtractorTest {
+
+ @Test
+ public void testBasic() throws Exception {
+ //test header
+ HemfCommentEMFPlus emfPlus = getCommentRecord("SimpleEMF_windows.emf", 0);
+ List<HemfPlusRecord> records = emfPlus.getRecords();
+ assertEquals(1, records.size());
+ assertEquals(HemfPlusRecordType.header, records.get(0).getRecordType());
+
+ HemfPlusHeader header = (HemfPlusHeader)records.get(0);
+ assertEquals(240, header.getLogicalDpiX());
+ assertEquals(240, header.getLogicalDpiY());
+ assertEquals(1, header.getFlags());
+ assertEquals(1, header.getEmfPlusFlags());
+
+
+
+ //test that the HemfCommentEMFPlus record at offset 1
+ //contains 6 HemfCommentEMFPlus records within it
+ List<HemfPlusRecordType> expected = new ArrayList<HemfPlusRecordType>();
+ expected.add(HemfPlusRecordType.setPixelOffsetMode);
+ expected.add(HemfPlusRecordType.setAntiAliasMode);
+ expected.add(HemfPlusRecordType.setCompositingQuality);
+ expected.add(HemfPlusRecordType.setPageTransform);
+ expected.add(HemfPlusRecordType.setInterpolationMode);
+ expected.add(HemfPlusRecordType.getDC);
+
+ emfPlus = getCommentRecord("SimpleEMF_windows.emf", 1);
+ records = emfPlus.getRecords();
+ assertEquals(expected.size(), records.size());
+
+ for (int i = 0; i < expected.size(); i++) {
+ assertEquals(expected.get(i), records.get(i).getRecordType());
+ }
+ }
+
+
+ private HemfCommentEMFPlus getCommentRecord(String testFileName, int recordIndex) throws Exception {
+ InputStream is = null;
+ HemfCommentEMFPlus returnRecord = null;
+
+ try {
+ is = POIDataSamples.getSpreadSheetInstance().openResourceAsStream(testFileName);
+ HemfExtractor ex = new HemfExtractor(is);
+ int i = 0;
+ for (HemfRecord record : ex) {
+ if (i == recordIndex) {
+ HemfCommentRecord commentRecord = ((HemfCommentRecord) record);
+ returnRecord = (HemfCommentEMFPlus) commentRecord.getComment();
+ break;
+ }
+ i++;
+ }
+ } finally {
+ is.close();
+ }
+ return returnRecord;
+ }
+}