Index: src/scratchpad/src/org/apache/poi/hemf/record/HemfComment.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- src/scratchpad/src/org/apache/poi/hemf/record/HemfComment.java (revision ) +++ src/scratchpad/src/org/apache/poi/hemf/record/HemfComment.java (revision ) @@ -0,0 +1,31 @@ +/* ==================================================================== + 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); + } +} Index: src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/UnimplementedHemfPlusRecord.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/UnimplementedHemfPlusRecord.java (revision ) +++ src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/UnimplementedHemfPlusRecord.java (revision ) @@ -0,0 +1,53 @@ +/* ==================================================================== + 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; + } +} Index: src/scratchpad/testcases/org/apache/poi/hemf/extractor/HemfExtractorTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- src/scratchpad/testcases/org/apache/poi/hemf/extractor/HemfExtractorTest.java (revision ) +++ src/scratchpad/testcases/org/apache/poi/hemf/extractor/HemfExtractorTest.java (revision ) @@ -0,0 +1,158 @@ +/* ==================================================================== + 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 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.HemfRecord; +import org.apache.poi.hemf.record.HemfRecordType; +import org.apache.poi.hemf.record.HemfText; +import org.apache.poi.hemf.record.HemfHeader; +import org.junit.Test; + +public class HemfExtractorTest { + static { + System.setProperty("POI.testdata.path", "C:/users/tallison/Idea Projects/poi-trunk/test-data"); + } + + @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; + HemfText.EmrTextObject emrTextObject = extTextOutW.getTextObject(); + if (lastY > -1 && lastY != emrTextObject.getY()) { + sb.append("\n"); + lastX = -1; + } + if (lastX > -1 && emrTextObject.getX() - lastX > fudgeFactorX) { + sb.append(" "); + } + sb.append(emrTextObject.getText()); + lastY = emrTextObject.getY(); + lastX = emrTextObject.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 information! + StringBuilder sb = new StringBuilder(); + for (HemfRecord record : ex) { + if (record.getRecordType().equals(HemfRecordType.exttextoutw)) { + HemfText.ExtTextOutW extTextOutW = (HemfText.ExtTextOutW) record; + HemfText.EmrTextObject emrTextObject = extTextOutW.getTextObject(); + if (lastY > -1 && lastY != emrTextObject.getY()) { + sb.append("\n"); + lastX = -1; + } + if (lastX > -1 && emrTextObject.getX() - lastX > fudgeFactorX) { + sb.append(" "); + } + sb.append(emrTextObject.getText()); + lastY = emrTextObject.getY(); + lastX = emrTextObject.getX(); + } + } + String txt = sb.toString(); + assertContains(txt, "C:\\Users\\tallison\\\n"); + assertContains(txt, "asf2-git-1.x\\tika-\n"); + } + + +} \ No newline at end of file Index: src/scratchpad/src/org/apache/poi/hemf/record/UnimplementedHemfRecord.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- src/scratchpad/src/org/apache/poi/hemf/record/UnimplementedHemfRecord.java (revision ) +++ src/scratchpad/src/org/apache/poi/hemf/record/UnimplementedHemfRecord.java (revision ) @@ -0,0 +1,46 @@ +/* ==================================================================== + 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; + + @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; + } +} Index: src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentPublic.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentPublic.java (revision ) +++ src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentPublic.java (revision ) @@ -0,0 +1,149 @@ +/* ==================================================================== + 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.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 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 emrFormatList = new ArrayList(); + for (long i = 0; i < countFormats; i++) { + emrFormatList.add(new EmrFormat(rawBytes, currentOffset)); + currentOffset += 4 * LittleEndian.INT_SIZE; + } + List list = new ArrayList(); + 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 { + + public WindowsMetafile(byte[] rawBytes) { + super(rawBytes); + } + } + + /** + * 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; + } + } +} Index: src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/HemfPlusRecordType.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/HemfPlusRecordType.java (revision ) +++ src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/HemfPlusRecordType.java (revision ) @@ -0,0 +1,97 @@ +/* ==================================================================== + 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 clazz; + + HemfPlusRecordType(long id, Class 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; + } +} Index: src/scratchpad/src/org/apache/poi/hemf/record/HemfHeader.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- src/scratchpad/src/org/apache/poi/hemf/record/HemfHeader.java (revision ) +++ src/scratchpad/src/org/apache/poi/hemf/record/HemfHeader.java (revision ) @@ -0,0 +1,260 @@ +/* ==================================================================== + 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.Internal; +import org.apache.poi.util.LittleEndianInputStream; + +/** + * Extracts the full header from EMF files. + * @see org.apache.poi.sl.image.ImageHeaderEMF + */ +@Internal +public class HemfHeader { + + 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; + + /** + * Parses the EMF header from the InputStream and leaves the stream + * at the end of the header/beginning of the first record. + * + * @param is InputStream to read from + * @return header + * @throws IOException on exception + */ + public static HemfHeader parse(LittleEndianInputStream is) throws IOException { + HemfHeader header = new HemfHeader(); + long type = is.readUInt(); + if (type != 1L) { + throw new IOException("Not a valid EMF header"); + } + //if >= 108, extension2, if >=100, extension1, else EMFMetafileHeader + long headerSize = is.readUInt(); + + //bounds + int boundsLeft = is.readInt(); + int boundsTop = is.readInt(); + int boundsRight = is.readInt(); + int boundsBottom = is.readInt(); + header.setBoundsRectangle(new Rectangle(boundsLeft, boundsTop, + boundsRight - boundsLeft, boundsBottom - boundsTop)); + + int frameLeft = is.readInt(); + int frameTop = is.readInt(); + int frameRight = is.readInt(); + int frameBottom = is.readInt(); + header.setFrameRectangle(new Rectangle(frameLeft, frameTop, + frameRight - frameLeft, frameBottom - frameTop)); + + long recordSignature = is.readInt(); + if (recordSignature != 0x464D4520) { + throw new IOException("bad record signature: " + recordSignature); + } + + long version = is.readInt(); + if (version != 0x00010000) { + throw new IOException("bad version: " + version); + } + header.setBytes(is.readUInt()); + header.setRecords(is.readUInt()); + header.setHandles(is.readUShort()); + is.readUShort();//reserved + header.setnDescription(is.readUInt()); + header.setOffDescription(is.readUInt()); + header.setnPalEntries(is.readUInt()); + + //should be skips + is.readFully(new byte[8]); //device + is.readFully(new byte[8]); //millimeters + + if (headerSize >= 100) { + header.setHasExtension1(true); + header.setCbPixelFormat(is.readUInt()); + header.setOffPixelFormat(is.readUInt()); + header.setbOpenGL(is.readUInt()); + } + + if (headerSize >= 108) { + header.setHasExtension2(true); + header.setMicrometersX(is.readUInt()); + header.setMicrometersY(is.readUInt()); + } + + //should we keep track of bytes read and slurp + //whatever is left over headerSize? + return header; + } + + private void setBoundsRectangle(Rectangle boundsRectangle) { + this.boundsRectangle = boundsRectangle; + } + + private void setFrameRectangle(Rectangle frameRectangle) { + this.frameRectangle = frameRectangle; + } + + private void setBytes(long bytes) { + this.bytes = bytes; + } + + private void setRecords(long records) { + this.records = records; + } + + private void setHandles(int handles) { + this.handles = handles; + } + + private void setnDescription(long nDescription) { + this.nDescription = nDescription; + } + + private void setOffDescription(long offDescription) { + this.offDescription = offDescription; + } + + private void setnPalEntries(long nPalEntries) { + this.nPalEntries = nPalEntries; + } + + private void setHasExtension1(boolean hasExtension1) { + this.hasExtension1 = hasExtension1; + } + + private void setCbPixelFormat(long cbPixelFormat) { + this.cbPixelFormat = cbPixelFormat; + } + + private void setOffPixelFormat(long offPixelFormat) { + this.offPixelFormat = offPixelFormat; + } + + private void setbOpenGL(long bOpenGL) { + this.bOpenGL = bOpenGL; + } + + private void setHasExtension2(boolean hasExtension2) { + this.hasExtension2 = hasExtension2; + } + + private void setMicrometersX(long micrometersX) { + this.micrometersX = micrometersX; + } + + private void setMicrometersY(long micrometersY) { + this.micrometersY = 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 + + '}'; + } +} Index: src/scratchpad/src/org/apache/poi/hemf/record/HemfText.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- src/scratchpad/src/org/apache/poi/hemf/record/HemfText.java (revision ) +++ src/scratchpad/src/org/apache/poi/hemf/record/HemfText.java (revision ) @@ -0,0 +1,146 @@ +/* ==================================================================== + 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.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; +import org.apache.poi.util.StringUtil; + +/** + * 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 extends UnimplementedHemfRecord { + + } + + public static class ExtTextOutW 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.exttextoutw; + } + + @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.readUInt(); + top = leis.readUInt(); + right = leis.readUInt(); + bottom = leis.readUInt(); + 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, UTF16LE, 20);//should be 28, but recordSizeInt has already subtracted 8 + return recordSize; + } + + public EmrTextObject getTextObject() { + return textObject; + } + } + + public static class PolyTextOutA extends UnimplementedHemfRecord { + + } + + public static class PolyTextOutW extends UnimplementedHemfRecord { + + } + + public static class SetTextAlign extends UnimplementedHemfRecord { + } + + public static class SetTextColor extends UnimplementedHemfRecord { + } + + + public class SetTextJustification extends UnimplementedHemfRecord { + } + + public static class EmrTextObject { + long x; + long y; + + String text; + public EmrTextObject(byte[] emrTextObjBytes, Charset charset, 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 numChars = 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 (charset.equals(UTF16LE)) { + text = StringUtil.getFromUnicodeLE(emrTextObjBytes, start, (int)numChars); + } + } + + public long getX() { + return x; + } + + public long getY() { + return y; + } + + public String getText() { + return text; + } + } + + +} Index: src/scratchpad/testcases/org/apache/poi/hemf/hemfplus/extractor/HemfPlusExtractorTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- src/scratchpad/testcases/org/apache/poi/hemf/hemfplus/extractor/HemfPlusExtractorTest.java (revision ) +++ src/scratchpad/testcases/org/apache/poi/hemf/hemfplus/extractor/HemfPlusExtractorTest.java (revision ) @@ -0,0 +1,100 @@ +/* ==================================================================== + 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 { + + static { + System.setProperty("POI.testdata.path", "C:/users/tallison/Idea Projects/poi-trunk/test-data"); + } + + @Test + public void testBasic() throws Exception { + //test header + HemfCommentEMFPlus emfPlus = getCommentRecord("SimpleEMF_windows.emf", 0); + List 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 expected = new ArrayList(); + 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; + } +} Index: src/java/org/apache/poi/util/IOUtils.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- src/java/org/apache/poi/util/IOUtils.java (revision 1778162) +++ src/java/org/apache/poi/util/IOUtils.java (revision ) @@ -251,4 +251,27 @@ 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; + } + } + } } Index: src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/HemfPlusHeader.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/HemfPlusHeader.java (revision ) +++ src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/HemfPlusHeader.java (revision ) @@ -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.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 + + '}'; + } +} Index: src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentEMFSpool.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentEMFSpool.java (revision ) +++ src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentEMFSpool.java (revision ) @@ -0,0 +1,31 @@ +/* ==================================================================== + 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); + } +} Index: src/scratchpad/src/org/apache/poi/hemf/record/HemfRecord.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- src/scratchpad/src/org/apache/poi/hemf/record/HemfRecord.java (revision ) +++ src/scratchpad/src/org/apache/poi/hemf/record/HemfRecord.java (revision ) @@ -0,0 +1,41 @@ +/* ==================================================================== + 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; + + +} Index: src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/HemfPlusRecord.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/HemfPlusRecord.java (revision ) +++ src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/HemfPlusRecord.java (revision ) @@ -0,0 +1,45 @@ +/* ==================================================================== + 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; + + +} Index: src/scratchpad/src/org/apache/poi/hemf/record/AbstractHemfComment.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- src/scratchpad/src/org/apache/poi/hemf/record/AbstractHemfComment.java (revision ) +++ src/scratchpad/src/org/apache/poi/hemf/record/AbstractHemfComment.java (revision ) @@ -0,0 +1,39 @@ +/* ==================================================================== + 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; + } + +} Index: src/scratchpad/src/org/apache/poi/hemf/extractor/HemfExtractor.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- src/scratchpad/src/org/apache/poi/hemf/extractor/HemfExtractor.java (revision ) +++ src/scratchpad/src/org/apache/poi/hemf/extractor/HemfExtractor.java (revision ) @@ -0,0 +1,109 @@ +/* ==================================================================== + 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.HemfRecord; +import org.apache.poi.hemf.record.HemfRecordType; +import org.apache.poi.hemf.record.HemfHeader; +import org.apache.poi.util.Internal; +import org.apache.poi.util.LittleEndianInputStream; + +/** + * Read-only EMF extractor. Lots remain + */ +@Internal +public class HemfExtractor implements Iterable { + + private final HemfHeader header; + private final LittleEndianInputStream stream; + + public HemfExtractor(InputStream is) throws IOException { + stream = new LittleEndianInputStream(is); + header = HemfHeader.parse(stream); + } + + public HemfHeader getHeader() { + return header; + } + + @Override + public Iterator iterator() { + return new HemfRecordIterator(); + } + + private class HemfRecordIterator implements Iterator { + + private HemfRecord currentRecord = null; + private long recordsRead = 1;//header is the first record, and that was already read! + + 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() { + recordsRead++; + if (recordsRead > header.getRecords()) { + 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 RuntimeException(e); + } + return record; + } + + @Override + public void remove() { + throw new UnsupportedOperationException("Remove not supported"); + } + + } +} Index: src/scratchpad/src/org/apache/poi/hemf/record/HemfRecordType.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- src/scratchpad/src/org/apache/poi/hemf/record/HemfRecordType.java (revision ) +++ src/scratchpad/src/org/apache/poi/hemf/record/HemfRecordType.java (revision ) @@ -0,0 +1,159 @@ +/* ==================================================================== + 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 clazz; + + HemfRecordType(long id, Class 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; + } +} Index: src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentRecord.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentRecord.java (revision ) +++ src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentRecord.java (revision ) @@ -0,0 +1,121 @@ +/* ==================================================================== + 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; + +/** + * 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(); + + dataSize = dataSize-4; //size minus the first int which could be a comment identifier + + byte[] optionalCommentIndentifierBuffer = new byte[4]; + leis.readFully(optionalCommentIndentifierBuffer); + long optionalCommentIdentifier = LittleEndian.getInt(optionalCommentIndentifierBuffer) & 0x00FFFFFFFFL; + if (optionalCommentIdentifier == COMMENT_EMFSPOOL) { + comment = new HemfCommentEMFSpool(readToByteArray(leis, dataSize)); + } else if (optionalCommentIdentifier == COMMENT_EMFPLUS) { + comment = new HemfCommentEMFPlus(readToByteArray(leis, dataSize)); + } else if (optionalCommentIdentifier == COMMENT_PUBLIC) { + comment = CommentPublicParser.parse(readToByteArray(leis, dataSize)); + } else { + comment = new HemfComment(readToByteArray(optionalCommentIndentifierBuffer, leis, dataSize)); + } + + 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 dataSize) throws IOException { + long actualDataSize = dataSize+initialBytes.length; + assert actualDataSize < Integer.MAX_VALUE; + + byte[] arr = new byte[(int)actualDataSize]; + System.arraycopy(initialBytes,0,arr, 0, initialBytes.length); + IOUtils.readFully(leis, arr, initialBytes.length, (int)dataSize-initialBytes.length); + return arr; + } + + private byte[] readToByteArray(LittleEndianInputStream leis, long dataSize) throws IOException { + assert dataSize < Integer.MAX_VALUE; + + byte[] arr = new byte[(int)dataSize]; + IOUtils.readFully(leis, arr); + return arr; + } + + public AbstractHemfComment getComment() { + return comment; + } + + private static class CommentPublicParser { + private static final long WINDOWS_METAFILE = 0x80000001; //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); + } + } +} Index: src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentEMFPlus.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentEMFPlus.java (revision ) +++ src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentEMFPlus.java (revision ) @@ -0,0 +1,107 @@ +/* ==================================================================== + 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 getRecords() { + return HemfPlusParser.parse(getRawBytes()); + } + + private static class HemfPlusParser { + + public static List parse(byte[] bytes) { + List records = new ArrayList(); + 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; + + } + } +}