From: Andreas Beeker Date: Fri, 14 Sep 2018 21:37:37 +0000 (+0000) Subject: #60656 - Support export file that contains emf and render it correctly X-Git-Tag: REL_4_1_0~189^2~24 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=a6a55a6ca514f9e76a784aadbec0d6d774ba8b9a;p=poi.git #60656 - Support export file that contains emf and render it correctly git-svn-id: https://svn.apache.org/repos/asf/poi/branches/hemf@1840956 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/src/java/org/apache/poi/util/Dimension2DDouble.java b/src/java/org/apache/poi/util/Dimension2DDouble.java new file mode 100644 index 0000000000..d08906433c --- /dev/null +++ b/src/java/org/apache/poi/util/Dimension2DDouble.java @@ -0,0 +1,73 @@ +/* ==================================================================== + 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.util; + +import java.awt.geom.Dimension2D; + +public class Dimension2DDouble extends Dimension2D { + + double width; + double height; + + public Dimension2DDouble() { + width = 0d; + height = 0d; + } + + public Dimension2DDouble(double width, double height) { + this.width = width; + this.height = height; + } + + @Override + public double getWidth() { + return width; + } + + @Override + public double getHeight() { + return height; + } + + @Override + public void setSize(double width, double height) { + this.width = width; + this.height = height; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof Dimension2DDouble) { + Dimension2DDouble other = (Dimension2DDouble) obj; + return width == other.width && height == other.height; + } + + return false; + } + + @Override + public int hashCode() { + double sum = width + height; + return (int) Math.ceil(sum * (sum + 1) / 2 + width); + } + + @Override + public String toString() { + return "Dimension2DDouble[" + width + ", " + height + "]"; + } +} diff --git a/src/ooxml/java/org/apache/poi/xdgf/geom/Dimension2dDouble.java b/src/ooxml/java/org/apache/poi/xdgf/geom/Dimension2dDouble.java deleted file mode 100644 index 94b887e04e..0000000000 --- a/src/ooxml/java/org/apache/poi/xdgf/geom/Dimension2dDouble.java +++ /dev/null @@ -1,73 +0,0 @@ -/* ==================================================================== - 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.xdgf.geom; - -import java.awt.geom.Dimension2D; - -public class Dimension2dDouble extends Dimension2D { - - double width; - double height; - - public Dimension2dDouble() { - width = 0d; - height = 0d; - } - - public Dimension2dDouble(double width, double height) { - this.width = width; - this.height = height; - } - - @Override - public double getWidth() { - return width; - } - - @Override - public double getHeight() { - return height; - } - - @Override - public void setSize(double width, double height) { - this.width = width; - this.height = height; - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof Dimension2dDouble) { - Dimension2dDouble other = (Dimension2dDouble) obj; - return width == other.width && height == other.height; - } - - return false; - } - - @Override - public int hashCode() { - double sum = width + height; - return (int) Math.ceil(sum * (sum + 1) / 2 + width); - } - - @Override - public String toString() { - return "Dimension2dDouble[" + width + ", " + height + "]"; - } -} diff --git a/src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFPage.java b/src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFPage.java index 43559203fe..c094a6edbf 100644 --- a/src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFPage.java +++ b/src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFPage.java @@ -22,7 +22,7 @@ import java.awt.geom.Rectangle2D; import org.apache.poi.ooxml.POIXMLException; import org.apache.poi.util.Internal; -import org.apache.poi.xdgf.geom.Dimension2dDouble; +import org.apache.poi.util.Dimension2DDouble; import com.microsoft.schemas.office.visio.x2012.main.PageType; @@ -75,14 +75,14 @@ public class XDGFPage { /** * @return width/height of page */ - public Dimension2dDouble getPageSize() { + public Dimension2DDouble getPageSize() { XDGFCell w = _pageSheet.getCell("PageWidth"); XDGFCell h = _pageSheet.getCell("PageHeight"); if (w == null || h == null) throw new POIXMLException("Cannot determine page size"); - return new Dimension2dDouble(Double.parseDouble(w.getValue()), + return new Dimension2DDouble(Double.parseDouble(w.getValue()), Double.parseDouble(h.getValue())); } @@ -109,7 +109,7 @@ public class XDGFPage { * @return bounding box of page */ public Rectangle2D getBoundingBox() { - Dimension2dDouble sz = getPageSize(); + Dimension2DDouble sz = getPageSize(); Point2D.Double offset = getPageOffset(); return new Rectangle2D.Double(-offset.getX(), -offset.getY(), diff --git a/src/ooxml/java/org/apache/poi/xdgf/util/VsdxToPng.java b/src/ooxml/java/org/apache/poi/xdgf/util/VsdxToPng.java index 7706ca19e3..c0ddf5fb0b 100644 --- a/src/ooxml/java/org/apache/poi/xdgf/util/VsdxToPng.java +++ b/src/ooxml/java/org/apache/poi/xdgf/util/VsdxToPng.java @@ -25,7 +25,7 @@ import java.io.*; import javax.imageio.ImageIO; -import org.apache.poi.xdgf.geom.Dimension2dDouble; +import org.apache.poi.util.Dimension2DDouble; import org.apache.poi.xdgf.usermodel.XDGFPage; import org.apache.poi.xdgf.usermodel.XmlVisioDocument; import org.apache.poi.xdgf.usermodel.shape.ShapeDebuggerRenderer; @@ -58,7 +58,7 @@ public class VsdxToPng { public static void renderToPng(XDGFPage page, File outFile, double scale, ShapeRenderer renderer) throws IOException { - Dimension2dDouble sz = page.getPageSize(); + Dimension2DDouble sz = page.getPageSize(); int width = (int) (scale * sz.getWidth()); int height = (int) (scale * sz.getHeight()); diff --git a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfDrawProperties.java b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfDrawProperties.java new file mode 100644 index 0000000000..f2e1957a28 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfDrawProperties.java @@ -0,0 +1,30 @@ +/* ==================================================================== + 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.draw; + +import org.apache.poi.hwmf.draw.HwmfDrawProperties; + +public class HemfDrawProperties extends HwmfDrawProperties { + + public HemfDrawProperties() { + } + + public HemfDrawProperties(HemfDrawProperties other) { + super(other); + } +} diff --git a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java new file mode 100644 index 0000000000..89a764af14 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java @@ -0,0 +1,29 @@ +/* ==================================================================== + 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.draw; + +import java.awt.Graphics2D; +import java.awt.geom.Rectangle2D; + +import org.apache.poi.hwmf.draw.HwmfGraphics; + +public class HemfGraphics extends HwmfGraphics { + public HemfGraphics(Graphics2D graphicsCtx, Rectangle2D bbox) { + super(graphicsCtx,bbox); + } +} diff --git a/src/scratchpad/src/org/apache/poi/hemf/extractor/HemfExtractor.java b/src/scratchpad/src/org/apache/poi/hemf/extractor/HemfExtractor.java deleted file mode 100644 index ab4add4eba..0000000000 --- a/src/scratchpad/src/org/apache/poi/hemf/extractor/HemfExtractor.java +++ /dev/null @@ -1,115 +0,0 @@ -/* ==================================================================== - 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 { - - 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 iterator() { - return new HemfRecordIterator(); - } - - public HemfHeader getHeader() { - return header; - } - - private class HemfRecordIterator implements Iterator { - - private HemfRecord currentRecord; - - 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"); - } - - } -} diff --git a/src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/HemfPlusHeader.java b/src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/HemfPlusHeader.java deleted file mode 100644 index 25947937b4..0000000000 --- a/src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/HemfPlusHeader.java +++ /dev/null @@ -1,82 +0,0 @@ -/* ==================================================================== - 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 + - '}'; - } -} diff --git a/src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/HemfPlusRecord.java b/src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/HemfPlusRecord.java deleted file mode 100644 index 09af3d5cf2..0000000000 --- a/src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/HemfPlusRecord.java +++ /dev/null @@ -1,44 +0,0 @@ -/* ==================================================================== - 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 - * @throws IOException, RecordFormatException - */ - void init(byte[] dataBytes, int recordId, int flags) throws IOException; - - -} diff --git a/src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/HemfPlusRecordType.java b/src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/HemfPlusRecordType.java deleted file mode 100644 index 70837628e1..0000000000 --- a/src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/HemfPlusRecordType.java +++ /dev/null @@ -1,97 +0,0 @@ -/* ==================================================================== - 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; - } -} diff --git a/src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/UnimplementedHemfPlusRecord.java b/src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/UnimplementedHemfPlusRecord.java deleted file mode 100644 index 7e3cbcff4c..0000000000 --- a/src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/UnimplementedHemfPlusRecord.java +++ /dev/null @@ -1,53 +0,0 @@ -/* ==================================================================== - 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; - } -} diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/AbstractHemfComment.java b/src/scratchpad/src/org/apache/poi/hemf/record/AbstractHemfComment.java deleted file mode 100644 index 7ffff6b016..0000000000 --- a/src/scratchpad/src/org/apache/poi/hemf/record/AbstractHemfComment.java +++ /dev/null @@ -1,39 +0,0 @@ -/* ==================================================================== - 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; - } - -} diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/HemfComment.java b/src/scratchpad/src/org/apache/poi/hemf/record/HemfComment.java deleted file mode 100644 index 5d45927bb9..0000000000 --- a/src/scratchpad/src/org/apache/poi/hemf/record/HemfComment.java +++ /dev/null @@ -1,31 +0,0 @@ -/* ==================================================================== - 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); - } -} diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentEMFPlus.java b/src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentEMFPlus.java deleted file mode 100644 index d5d4c10e84..0000000000 --- a/src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentEMFPlus.java +++ /dev/null @@ -1,111 +0,0 @@ -/* ==================================================================== - 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.IOUtils; -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 { - - private static final int MAX_RECORD_LENGTH = 1_000_000; - - - 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 = IOUtils.safelyAllocate(size, MAX_RECORD_LENGTH); - System.arraycopy(bytes, offset, dataBytes, 0, size); - try { - record.init(dataBytes, recordId, flags); - } catch (IOException e) { - throw new RuntimeException(e); - } - return record; - - } - } -} diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentEMFSpool.java b/src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentEMFSpool.java deleted file mode 100644 index 009974d10b..0000000000 --- a/src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentEMFSpool.java +++ /dev/null @@ -1,31 +0,0 @@ -/* ==================================================================== - 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); - } -} diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentPublic.java b/src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentPublic.java deleted file mode 100644 index 5ca5c375ce..0000000000 --- a/src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentPublic.java +++ /dev/null @@ -1,177 +0,0 @@ -/* ==================================================================== - 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.IOUtils; -import org.apache.poi.util.Internal; -import org.apache.poi.util.LittleEndian; -import org.apache.poi.util.LittleEndianConsts; -import org.apache.poi.util.RecordFormatException; - -/** - * Container class for four subtypes of HemfCommentPublic: BeginGroup, EndGroup, MultiFormats - * and Windows Metafile. - */ -@Internal -public class HemfCommentPublic { - - private static final int MAX_RECORD_LENGTH = 1_000_000; - - - /** - * 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 += LittleEndianConsts.INT_SIZE; - List emrFormatList = new ArrayList<>(); - for (long i = 0; i < countFormats; i++) { - emrFormatList.add(new EmrFormat(rawBytes, currentOffset)); - currentOffset += 4 * LittleEndianConsts.INT_SIZE; - } - List list = new ArrayList<>(); - for (EmrFormat emrFormat : emrFormatList) { - byte[] data = IOUtils.safelyAllocate(emrFormat.size, MAX_RECORD_LENGTH); - System.arraycopy(rawBytes, emrFormat.offset-4, data, 0, emrFormat.size); - list.add(new HemfMultiFormatsData(emrFormat.signature, emrFormat.version, data)); - } - return list; - } - - private static class EmrFormat { - long signature; - long version; - int size; - int offset; - - public EmrFormat(byte[] rawBytes, int currentOffset) { - signature = LittleEndian.getUInt(rawBytes, currentOffset); currentOffset += LittleEndianConsts.INT_SIZE; - version = LittleEndian.getUInt(rawBytes, currentOffset); currentOffset += LittleEndianConsts.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 += LittleEndianConsts.INT_SIZE; - //y, can be long, but realistically? - offset = LittleEndian.getInt(rawBytes, currentOffset); currentOffset += LittleEndianConsts.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 = LittleEndianConsts.INT_SIZE;//public comment identifier - int version = LittleEndian.getUShort(rawBytes, offset); offset += LittleEndianConsts.SHORT_SIZE; - int reserved = LittleEndian.getUShort(rawBytes, offset); offset += LittleEndianConsts.SHORT_SIZE; - offset += LittleEndianConsts.INT_SIZE; //checksum - offset += LittleEndianConsts.INT_SIZE; //flags - long winMetafileSizeLong = LittleEndian.getUInt(rawBytes, offset); offset += LittleEndianConsts.INT_SIZE; - if (winMetafileSizeLong == 0L) { - wmfBytes = new byte[0]; - return; - } - wmfBytes = IOUtils.safelyAllocate(winMetafileSizeLong, MAX_RECORD_LENGTH); - System.arraycopy(rawBytes, offset, wmfBytes, 0, wmfBytes.length); - } - - /** - * - * @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; - } - } -} diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentRecord.java b/src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentRecord.java deleted file mode 100644 index af8aa28572..0000000000 --- a/src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentRecord.java +++ /dev/null @@ -1,154 +0,0 @@ -/* ==================================================================== - 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 { - private static final int MAX_RECORD_LENGTH = 1_000_000; - - 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 = IOUtils.safelyAllocate(dataSize+initialBytes.length, MAX_RECORD_LENGTH); - System.arraycopy(initialBytes,0,arr, 0, initialBytes.length); - long read = IOUtils.readFully(leis, arr, initialBytes.length, dataSize); - if (read != dataSize) { - throw new RecordFormatException("InputStream ended before full record could be read"); - } - long toSkip = recordSize-dataSize; - long skipped = IOUtils.skipFully(leis, toSkip); - if (toSkip != skipped) { - throw new RecordFormatException("InputStream ended before full record could be read"); - } - - return arr; - } - - private byte[] readToByteArray(LittleEndianInputStream leis, long dataSize, long recordSize) throws IOException { - - if (recordSize == 0) { - return new byte[0]; - } - - byte[] arr = IOUtils.safelyAllocate(dataSize, MAX_RECORD_LENGTH); - - long read = IOUtils.readFully(leis, arr); - if (read != dataSize) { - throw new RecordFormatException("InputStream ended before full record could be read"); - } - long toSkip = recordSize-dataSize; - long skipped = IOUtils.skipFully(leis, recordSize-dataSize); - if (toSkip != skipped) { - throw new RecordFormatException("InputStream ended before full record could be read"); - } - 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); - } - } -} diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/HemfHeader.java b/src/scratchpad/src/org/apache/poi/hemf/record/HemfHeader.java deleted file mode 100644 index 7a6f876400..0000000000 --- a/src/scratchpad/src/org/apache/poi/hemf/record/HemfHeader.java +++ /dev/null @@ -1,201 +0,0 @@ -/* ==================================================================== - 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 static final int MAX_RECORD_LENGTH = 1_000_000; - - - 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 = IOUtils.safelyAllocate(recordSize, MAX_RECORD_LENGTH); - 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; - } -} diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/HemfRecord.java b/src/scratchpad/src/org/apache/poi/hemf/record/HemfRecord.java deleted file mode 100644 index de1271e696..0000000000 --- a/src/scratchpad/src/org/apache/poi/hemf/record/HemfRecord.java +++ /dev/null @@ -1,41 +0,0 @@ -/* ==================================================================== - 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; - - -} diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/HemfRecordType.java b/src/scratchpad/src/org/apache/poi/hemf/record/HemfRecordType.java deleted file mode 100644 index b1c5857114..0000000000 --- a/src/scratchpad/src/org/apache/poi/hemf/record/HemfRecordType.java +++ /dev/null @@ -1,159 +0,0 @@ -/* ==================================================================== - 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; - } -} diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/HemfText.java b/src/scratchpad/src/org/apache/poi/hemf/record/HemfText.java deleted file mode 100644 index 74b8212846..0000000000 --- a/src/scratchpad/src/org/apache/poi/hemf/record/HemfText.java +++ /dev/null @@ -1,262 +0,0 @@ -/* ==================================================================== - 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 static java.nio.charset.StandardCharsets.UTF_16LE; - -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 static final int MAX_RECORD_LENGTH = 1_000_000; - - 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 = IOUtils.safelyAllocate(recordSizeInt-(7*LittleEndian.INT_SIZE), MAX_RECORD_LENGTH); - 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 text from this text element - * @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 UTF_16LE; - } - - public String getText() throws IOException { - return getText(UTF_16LE); - } - } - - /** - * 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"); - } else if (numCharsLong < 0) { - throw new RecordFormatException("Number of characters can't be < 0"); - } - - numChars = (int)numCharsLong; - rawTextBytes = IOUtils.safelyAllocate(emrTextObjBytes.length-start, MAX_RECORD_LENGTH); - System.arraycopy(emrTextObjBytes, start, rawTextBytes, 0, emrTextObjBytes.length-start); - } - - String getText(Charset charset) throws IOException { - StringBuilder sb = new StringBuilder(); - try (Reader r = new InputStreamReader(new ByteArrayInputStream(rawTextBytes), charset)) { - for (int i = 0; i < numChars; i++) { - sb.appendCodePoint(readCodePoint(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); - } - } - - -} diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/UnimplementedHemfRecord.java b/src/scratchpad/src/org/apache/poi/hemf/record/UnimplementedHemfRecord.java deleted file mode 100644 index 6f3ded48d5..0000000000 --- a/src/scratchpad/src/org/apache/poi/hemf/record/UnimplementedHemfRecord.java +++ /dev/null @@ -1,49 +0,0 @@ -/* ==================================================================== - 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 < recordSize) { - throw new IOException("End of stream reached before record read"); - } - return skipped; - } -} diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java new file mode 100644 index 0000000000..0a459e3cd0 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java @@ -0,0 +1,444 @@ +/* ==================================================================== + 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.emf; + +import java.awt.geom.Rectangle2D; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.function.Supplier; + +import org.apache.poi.hemf.record.emfplus.HemfPlusRecord; +import org.apache.poi.hemf.record.emfplus.HemfPlusRecordIterator; +import org.apache.poi.util.IOUtils; +import org.apache.poi.util.Internal; +import org.apache.poi.util.LittleEndianConsts; +import org.apache.poi.util.LittleEndianInputStream; +import org.apache.poi.util.RecordFormatException; + +/** + * Contains arbitrary data + */ +@Internal +public class HemfComment { + private static final int MAX_RECORD_LENGTH = 1_000_000; + + public enum HemfCommentRecordType { + emfGeneric(-1, EmfCommentDataGeneric::new, false), + emfSpool(0x00000000, EmfCommentDataGeneric::new, false), + emfPlus(0x2B464D45, EmfCommentDataPlus::new, false), + emfPublic(0x43494447, null, false), + emfBeginGroup(0x00000002, EmfCommentDataBeginGroup::new, true), + emfEndGroup(0x00000003, EmfCommentDataEndGroup::new, true), + emfMultiFormats(0x40000004, EmfCommentDataMultiformats::new, true), + emfWMF(0x80000001, EmfCommentDataWMF::new, true), + emfUnicodeString(0x00000040, EmfCommentDataUnicode::new, true), + emfUnicodeEnd(0x00000080, EmfCommentDataUnicode::new, true) + ; + + + public final long id; + public final Supplier constructor; + public final boolean isEmfPublic; + + HemfCommentRecordType(long id, Supplier constructor, boolean isEmfPublic) { + this.id = id; + this.constructor = constructor; + this.isEmfPublic = isEmfPublic; + } + + public static HemfCommentRecordType getById(long id, boolean isEmfPublic) { + for (HemfCommentRecordType wrt : values()) { + if (wrt.id == id && wrt.isEmfPublic == isEmfPublic) return wrt; + } + return emfGeneric; + } + } + + public interface EmfCommentData { + HemfCommentRecordType getCommentRecordType(); + + long init(LittleEndianInputStream leis, long dataSize) throws IOException; + } + + public static class EmfComment implements HemfRecord { + private EmfCommentData data; + + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.comment; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + int startIdx = leis.getReadIndex(); + data = new EmfCommentDataIterator(leis, (int)recordSize, true).next(); + return leis.getReadIndex()-startIdx; + } + + public EmfCommentData getCommentData() { + return data; + } + } + + public static class EmfCommentDataIterator implements Iterator { + + private final LittleEndianInputStream leis; + private final int startIdx; + private final int limit; + private EmfCommentData currentRecord; + /** is the caller the EmfComment */ + private final boolean emfParent; + + public EmfCommentDataIterator(LittleEndianInputStream leis, int limit, boolean emfParent) { + this.leis = leis; + this.limit = limit; + this.emfParent = emfParent; + startIdx = leis.getReadIndex(); + //queue the first non-header record + currentRecord = _next(); + } + + @Override + public boolean hasNext() { + return currentRecord != null; + } + + @Override + public EmfCommentData next() { + EmfCommentData toReturn = currentRecord; + final boolean isEOF = (limit == -1 || leis.getReadIndex() >= startIdx+limit); + // (currentRecord instanceof HemfPlusMisc.EmfEof) + currentRecord = isEOF ? null : _next(); + return toReturn; + } + + private EmfCommentData _next() { + long type, recordSize; + if (currentRecord == null && emfParent) { + type = HemfRecordType.comment.id; + recordSize = limit; + } else { + // A 32-bit unsigned integer from the RecordType enumeration that identifies this record + // as a comment record. This value MUST be 0x00000046. + type = leis.readUInt(); + assert(type == HemfRecordType.comment.id); + // A 32-bit unsigned integer that specifies the size in bytes of this record in the + // metafile. This value MUST be a multiple of 4 bytes. + recordSize = leis.readUInt(); + } + + // A 32-bit unsigned integer that specifies the size, in bytes, of the CommentIdentifier and + // CommentRecordParm fields in the RecordBuffer field that follows. + // It MUST NOT include the size of itself or the size of the AlignmentPadding field, if present. + long dataSize = leis.readUInt(); + + try { + leis.mark(2*LittleEndianConsts.INT_SIZE); + // An optional, 32-bit unsigned integer that identifies the type of comment record. + // See the preceding table for descriptions of these record types. + // Valid comment identifier values are listed in the following table. + // + // If this field contains any other value, the comment record MUST be an EMR_COMMENT record + final int commentIdentifier = (int)leis.readUInt(); + // A 32-bit unsigned integer that identifies the type of public comment record. + final int publicCommentIdentifier = (int)leis.readUInt(); + + final boolean isEmfPublic = (commentIdentifier == HemfCommentRecordType.emfPublic.id); + leis.reset(); + + final HemfCommentRecordType commentType = HemfCommentRecordType.getById + (isEmfPublic ? publicCommentIdentifier : commentIdentifier, isEmfPublic); + assert(commentType != null); + final EmfCommentData record = commentType.constructor.get(); + + long readBytes = record.init(leis, dataSize); + final int skipBytes = (int)(recordSize-4-readBytes); + assert (skipBytes >= 0); + leis.skipFully(skipBytes); + + return record; + } catch (IOException e) { + throw new RecordFormatException(e); + } + } + + @Override + public void remove() { + throw new UnsupportedOperationException("Remove not supported"); + } + + } + + + + /** + * Private data is unknown to EMF; it is meaningful only to applications that know the format of the + * data and how to use it. EMR_COMMENT private data records MAY be ignored. + */ + public static class EmfCommentDataGeneric implements EmfCommentData { + private byte[] privateData; + + @Override + public HemfCommentRecordType getCommentRecordType() { + return HemfCommentRecordType.emfGeneric; + } + + @Override + public long init(LittleEndianInputStream leis, long dataSize) throws IOException { + privateData = IOUtils.safelyAllocate(dataSize, MAX_RECORD_LENGTH); + leis.readFully(privateData); + return privateData.length; + } + } + + /** The EMR_COMMENT_EMFPLUS record contains embedded EMF+ records. */ + public static class EmfCommentDataPlus implements EmfCommentData { + private final List records = new ArrayList<>(); + + @Override + public HemfCommentRecordType getCommentRecordType() { + return HemfCommentRecordType.emfPlus; + } + + @Override + public long init(final LittleEndianInputStream leis, final long dataSize) + throws IOException { + long startIdx = leis.getReadIndex(); + int commentIdentifier = leis.readInt(); + assert (commentIdentifier == HemfCommentRecordType.emfPlus.id); + new HemfPlusRecordIterator(leis, (int)dataSize-LittleEndianConsts.INT_SIZE).forEachRemaining(records::add); + return leis.getReadIndex()-startIdx; + } + + public List getRecords() { + return Collections.unmodifiableList(records); + } + } + + public static class EmfCommentDataBeginGroup implements EmfCommentData { + private final Rectangle2D bounds = new Rectangle2D.Double(); + private String description; + + @Override + public HemfCommentRecordType getCommentRecordType() { + return HemfCommentRecordType.emfBeginGroup; + } + + @Override + public long init(final LittleEndianInputStream leis, final long dataSize) + throws IOException { + final int startIdx = leis.getReadIndex(); + final int commentIdentifier = (int)leis.readUInt(); + assert(commentIdentifier == HemfCommentRecordType.emfPublic.id); + final int publicCommentIdentifier = (int)leis.readUInt(); + assert(publicCommentIdentifier == HemfCommentRecordType.emfBeginGroup.id); + HemfDraw.readRectL(leis, bounds); + + // The number of Unicode characters in the optional description string that follows. + int nDescription = (int)leis.readUInt(); + + byte[] buf = IOUtils.safelyAllocate(nDescription*2, MAX_RECORD_LENGTH); + leis.readFully(buf); + description = new String(buf, StandardCharsets.UTF_16LE); + + return leis.getReadIndex()-startIdx; + } + } + + public static class EmfCommentDataEndGroup implements EmfCommentData { + @Override + public HemfCommentRecordType getCommentRecordType() { + return HemfCommentRecordType.emfEndGroup; + } + + @Override + public long init(final LittleEndianInputStream leis, final long dataSize) + throws IOException { + final int startIdx = leis.getReadIndex(); + final int commentIdentifier = (int)leis.readUInt(); + assert(commentIdentifier == HemfCommentRecordType.emfPublic.id); + final int publicCommentIdentifier = (int)leis.readUInt(); + assert(publicCommentIdentifier == HemfCommentRecordType.emfEndGroup.id); + return leis.getReadIndex()-startIdx; + } + } + + public static class EmfCommentDataMultiformats implements EmfCommentData { + private final Rectangle2D bounds = new Rectangle2D.Double(); + private final List formats = new ArrayList<>(); + + @Override + public HemfCommentRecordType getCommentRecordType() { + return HemfCommentRecordType.emfMultiFormats; + } + + @Override + public long init(final LittleEndianInputStream leis, final long dataSize) + throws IOException { + final int startIdx = leis.getReadIndex(); + final int commentIdentifier = (int)leis.readUInt(); + assert(commentIdentifier == HemfCommentRecordType.emfPublic.id); + final int publicCommentIdentifier = (int)leis.readUInt(); + assert(publicCommentIdentifier == HemfCommentRecordType.emfMultiFormats.id); + HemfDraw.readRectL(leis, bounds); + + // A 32-bit unsigned integer that specifies the number of graphics formats contained in this record. + int countFormats = (int)leis.readUInt(); + for (int i=0; i getFormats() { + return Collections.unmodifiableList(formats); + } + } + + public enum EmfFormatSignature { + ENHMETA_SIGNATURE(0x464D4520), + EPS_SIGNATURE(0x46535045); + + int id; + + EmfFormatSignature(int flag) { + this.id = id; + } + + public static EmfFormatSignature getById(int id) { + for (EmfFormatSignature wrt : values()) { + if (wrt.id == id) return wrt; + } + return null; + } + + } + + public static class EmfCommentDataFormat { + private EmfFormatSignature signature; + private int version; + private int sizeData; + private int offData; + private byte[] rawData; + + public long init(final LittleEndianInputStream leis, final long dataSize, long startIdx) throws IOException { + // A 32-bit unsigned integer that specifies the format of the image data. + signature = EmfFormatSignature.getById(leis.readInt()); + + // A 32-bit unsigned integer that specifies the format version number. + // If the Signature field specifies encapsulated PostScript (EPS), this value MUST be 0x00000001; + // otherwise, this value MUST be ignored. + version = leis.readInt(); + + // A 32-bit unsigned integer that specifies the size of the data in bytes. + sizeData = leis.readInt(); + + // A 32-bit unsigned integer that specifies the offset to the data from the start + // of the identifier field in an EMR_COMMENT_PUBLIC record. The offset MUST be 32-bit aligned. + offData = leis.readInt(); + if (sizeData < 0) { + throw new RecordFormatException("size for emrformat must be > 0"); + } + if (offData < 0) { + throw new RecordFormatException("offset for emrformat must be > 0"); + } + + return 4*LittleEndianConsts.INT_SIZE; + } + + public byte[] getRawData() { + return rawData; + } + } + + public static class EmfCommentDataWMF implements EmfCommentData { + private final Rectangle2D bounds = new Rectangle2D.Double(); + private final List formats = new ArrayList<>(); + + @Override + public HemfCommentRecordType getCommentRecordType() { + return HemfCommentRecordType.emfWMF; + } + + @Override + public long init(final LittleEndianInputStream leis, final long dataSize) + throws IOException { + final int startIdx = leis.getReadIndex(); + final int commentIdentifier = (int)leis.readUInt(); + assert(commentIdentifier == HemfCommentRecordType.emfPublic.id); + final int publicCommentIdentifier = (int)leis.readUInt(); + assert(publicCommentIdentifier == HemfCommentRecordType.emfWMF.id); + + // A 16-bit unsigned integer that specifies the WMF metafile version in terms + //of support for device-independent bitmaps (DIBs) + int version = leis.readUShort(); + + // A 16-bit value that MUST be 0x0000 and MUST be ignored. + leis.skipFully(LittleEndianConsts.SHORT_SIZE); + + // A 32-bit unsigned integer that specifies the checksum for this record. + int checksum = leis.readInt(); + + // A 32-bit value that MUST be 0x00000000 and MUST be ignored. + int flags = leis.readInt(); + + // A 32-bit unsigned integer that specifies the size, in bytes, of the + // WMF metafile in the WinMetafile field. + int winMetafileSize = (int)leis.readUInt(); + + byte[] winMetafile = IOUtils.safelyAllocate(winMetafileSize, MAX_RECORD_LENGTH); + leis.readFully(winMetafile); + + return leis.getReadIndex()-startIdx; + } + } + + public static class EmfCommentDataUnicode implements EmfCommentData { + private final Rectangle2D bounds = new Rectangle2D.Double(); + private final List formats = new ArrayList<>(); + + @Override + public HemfCommentRecordType getCommentRecordType() { + return HemfCommentRecordType.emfUnicodeString; + } + + @Override + public long init(final LittleEndianInputStream leis, final long dataSize) + throws IOException { + throw new RecordFormatException("UNICODE_STRING/UNICODE_END values are reserved in CommentPublic records"); + } + } +} diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java new file mode 100644 index 0000000000..688a9ff66b --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java @@ -0,0 +1,783 @@ +/* ==================================================================== + 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.emf; + +import java.awt.geom.AffineTransform; +import java.awt.geom.Dimension2D; +import java.awt.geom.Path2D; +import java.awt.geom.PathIterator; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.io.IOException; + +import org.apache.poi.hemf.draw.HemfGraphics; +import org.apache.poi.hwmf.draw.HwmfGraphics; +import org.apache.poi.hwmf.record.HwmfDraw; +import org.apache.poi.hwmf.record.HwmfDraw.WmfSelectObject; +import org.apache.poi.util.LittleEndianConsts; +import org.apache.poi.util.LittleEndianInputStream; + +public class HemfDraw { + /** + * The EMR_SELECTOBJECT record adds a graphics object to the current metafile playback device + * context. The object is specified either by its index in the EMF Object Table or by its + * value from the StockObject enumeration. + */ + public static class EmfSelectObject extends WmfSelectObject implements HemfRecord { + + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.selectObject; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + // A 32-bit unsigned integer that specifies either the index of a graphics object in the + // EMF Object Table or the index of a stock object from the StockObject enumeration. + objectIndex = leis.readInt(); + return LittleEndianConsts.INT_SIZE; + } + } + + + /** The EMR_POLYBEZIER record specifies one or more Bezier curves. */ + public static class EmfPolyBezier extends HwmfDraw.WmfPolygon implements HemfRecord { + private final Rectangle2D bounds = new Rectangle2D.Double(); + + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.polyBezier; + } + + protected long readPoint(LittleEndianInputStream leis, Point2D point) { + return readPointL(leis, point); + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + long size = readRectL(leis, bounds); + + /* A 32-bit unsigned integer that specifies the number of points in the points + * array. This value MUST be one more than three times the number of curves to + * be drawn, because each Bezier curve requires two control points and an + * endpoint, and the initial curve requires an additional starting point. + * + * Line width | Device supports wideline | Maximum points allowed + * 1 | n/a | 16K + * > 1 | yes | 16K + * > 1 | no | 1360 + * + * Any extra points MUST be ignored. + */ + final int count = (int)leis.readUInt(); + final int points = Math.min(count, 16384); + size += LittleEndianConsts.INT_SIZE; + + poly.reset(); + + /* Cubic Bezier curves are defined using the endpoints and control points + * specified by the points field. The first curve is drawn from the first + * point to the fourth point, using the second and third points as control + * points. Each subsequent curve in the sequence needs exactly three more points: + * the ending point of the previous curve is used as the starting point, + * the next two points in the sequence are control points, + * and the third is the ending point. + * The cubic Bezier curves SHOULD be drawn using the current pen. + */ + + Point2D pnt[] = { new Point2D.Double(), new Point2D.Double(), new Point2D.Double() }; + + // points-1 because of the first point + final int pointCnt = hasStartPoint() ? points-1 : points; + for (int i=0; i+3= 0); + leis.skipFully(undefinedSpace1); + size += undefinedSpace1; + + bmiSrc = IOUtils.safelyAllocate(cbBmiSrc, MAX_RECORD_LENGTH); + leis.readFully(bmiSrc); + size += cbBmiSrc; + + final int undefinedSpace2 = (int)(offBitsSrc - size - HEADER_SIZE); + assert(undefinedSpace2 >= 0); + leis.skipFully(undefinedSpace2); + size += undefinedSpace2; + + bitsSrc = IOUtils.safelyAllocate(cbBitsSrc, MAX_RECORD_LENGTH); + leis.readFully(bitsSrc); + size += cbBitsSrc; + + return size; + } + + protected boolean srcEqualsDstDimension() { + return false; + } + } + + /** + * The EMR_BITBLT record specifies a block transfer of pixels from a source bitmap to a destination rectangle, + * optionally in combination with a brush pattern, according to a specified raster operation. + */ + public static class EmfBitBlt extends EmfStretchBlt { + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.bitBlt; + } + + @Override + protected boolean srcEqualsDstDimension() { + return false; + } + } + + + /** The EMR_FRAMERGN record draws a border around the specified region using the specified brush. */ + public static class EmfFrameRgn extends HwmfDraw.WmfFrameRegion implements HemfRecord { + private final Rectangle2D bounds = new Rectangle2D.Double(); + private final List rgnRects = new ArrayList<>(); + + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.frameRgn; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + long size = readRectL(leis, bounds); + // A 32-bit unsigned integer that specifies the size of region data, in bytes. + long rgnDataSize = leis.readUInt(); + // A 32-bit unsigned integer that specifies the brush EMF Object Table index. + brushIndex = (int)leis.readUInt(); + // A 32-bit signed integer that specifies the width of the vertical brush stroke, in logical units. + width = leis.readInt(); + // A 32-bit signed integer that specifies the height of the horizontal brush stroke, in logical units. + height = leis.readInt(); + size += 4*LittleEndianConsts.INT_SIZE; + size += readRgnData(leis, rgnRects); + return size; + } + + @Override + public void draw(HwmfGraphics ctx) { + ctx.applyObjectTableEntry(brushIndex); + + Area frame = new Area(); + for (Rectangle2D rct : rgnRects) { + frame.add(new Area(rct)); + } + Rectangle2D frameBounds = frame.getBounds2D(); + AffineTransform at = new AffineTransform(); + at.translate(bounds.getX()-frameBounds.getX(), bounds.getY()-frameBounds.getY()); + at.scale(bounds.getWidth()/frameBounds.getWidth(), bounds.getHeight()/frameBounds.getHeight()); + frame.transform(at); + + ctx.fill(frame); + } + } + + /** The EMR_INVERTRGN record inverts the colors in the specified region. */ + public static class EmfInvertRgn implements HemfRecord { + protected final Rectangle2D bounds = new Rectangle2D.Double(); + protected final List rgnRects = new ArrayList<>(); + + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.invertRgn; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + long size = readRectL(leis, bounds); + // A 32-bit unsigned integer that specifies the size of region data, in bytes. + long rgnDataSize = leis.readUInt(); + size += LittleEndianConsts.INT_SIZE; + size += readRgnData(leis, rgnRects); + return size; + } + } + + /** + * The EMR_PAINTRGN record paints the specified region by using the brush currently selected into the + * playback device context. + */ + public static class EmfPaintRgn extends EmfInvertRgn { + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.paintRgn; + } + } + + /** The EMR_FILLRGN record fills the specified region by using the specified brush. */ + public static class EmfFillRgn extends HwmfFill.WmfFillRegion implements HemfRecord { + protected final Rectangle2D bounds = new Rectangle2D.Double(); + protected final List rgnRects = new ArrayList<>(); + + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.fillRgn; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + long size = readRectL(leis, bounds); + // A 32-bit unsigned integer that specifies the size of region data, in bytes. + long rgnDataSize = leis.readUInt(); + brushIndex = (int)leis.readUInt(); + size += 2*LittleEndianConsts.INT_SIZE; + size += readRgnData(leis, rgnRects); + return size; + } + } + + public static class EmfExtSelectClipRgn implements HemfRecord { + protected HemfRegionMode regionMode; + protected final List rgnRects = new ArrayList<>(); + + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.extSelectClipRgn; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + // A 32-bit unsigned integer that specifies the size of region data in bytes + long rgnDataSize = leis.readUInt(); + // A 32-bit unsigned integer that specifies the way to use the region. + regionMode = HemfRegionMode.valueOf((int)leis.readUInt()); + long size = 2* LittleEndianConsts.INT_SIZE; + + // If RegionMode is RGN_COPY, this data can be omitted and the clip region + // SHOULD be set to the default (NULL) clip region. + if (regionMode != HemfRegionMode.RGN_COPY) { + size += readRgnData(leis, rgnRects); + } + return size; + } + } + + public static class EmfAlphaBlend implements HemfRecord { + /** the destination bounding rectangle in device units */ + protected final Rectangle2D bounds = new Rectangle2D.Double(); + /** the destination rectangle */ + protected final Rectangle2D destRect = new Rectangle2D.Double(); + /** the source rectangle */ + protected final Rectangle2D srcRect = new Rectangle2D.Double(); + /** + * The blend operation code. The only source and destination blend operation that has been defined + * is 0x00, which specifies that the source bitmap MUST be combined with the destination bitmap based + * on the alpha transparency values of the source pixels. + */ + protected byte blendOperation; + /** This value MUST be 0x00 and MUST be ignored. */ + protected byte blendFlags; + /** + * An 8-bit unsigned integer that specifies alpha transparency, which determines the blend of the source + * and destination bitmaps. This value MUST be used on the entire source bitmap. The minimum alpha + * transparency value, zero, corresponds to completely transparent; the maximum value, 0xFF, corresponds + * to completely opaque. In effect, a value of 0xFF specifies that the per-pixel alpha values determine + * the blend of the source and destination bitmaps. + */ + protected int srcConstantAlpha; + /** + * A byte that specifies how source and destination pixels are interpreted with respect to alpha transparency. + * + * 0x00: + * The pixels in the source bitmap do not specify alpha transparency. + * In this case, the SrcConstantAlpha value determines the blend of the source and destination bitmaps. + * Note that in the following equations SrcConstantAlpha is divided by 255, + * which produces a value in the range 0 to 1. + * + * 0x01: "AC_SRC_ALPHA" + * Indicates that the source bitmap is 32 bits-per-pixel and specifies an alpha transparency value + * for each pixel. + */ + protected byte alphaFormat; + /** a world-space to page-space transform to apply to the source bitmap. */ + protected final AffineTransform xFormSrc = new AffineTransform(); + /** the background color of the source bitmap. */ + protected final HwmfColorRef bkColorSrc = new HwmfColorRef(); + /** + * A 32-bit unsigned integer that specifies how to interpret values in the + * color table in the source bitmap header. + */ + protected ColorUsage usageSrc; + + protected final HwmfBitmapDib bitmap = new HwmfBitmapDib(); + + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.alphaBlend; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + final int startIdx = leis.getReadIndex(); + + long size = readRectL(leis, bounds); + size += readBounds2(leis, destRect); + + blendOperation = leis.readByte(); + assert (blendOperation == 0); + blendFlags = leis.readByte(); + assert (blendOperation == 0); + srcConstantAlpha = leis.readUByte(); + alphaFormat = leis.readByte(); + + // A 32-bit signed integer that specifies the logical x-coordinate of the upper-left + // corner of the source rectangle. + final int xSrc = leis.readInt(); + // A 32-bit signed integer that specifies the logical y-coordinate of the upper-left + // corner of the source rectangle. + final int ySrc = leis.readInt(); + + size += 3*LittleEndianConsts.INT_SIZE; + size += readXForm(leis, xFormSrc); + size += bkColorSrc.init(leis); + + usageSrc = ColorUsage.valueOf((int)leis.readUInt()); + + + // A 32-bit unsigned integer that specifies the offset, in bytes, from the + // start of this record to the source bitmap header in the BitmapBuffer field. + final int offBmiSrc = (int)leis.readUInt(); + + // A 32-bit unsigned integer that specifies the size, in bytes, of the source bitmap header. + final int cbBmiSrc = (int)leis.readUInt(); + // A 32-bit unsigned integer that specifies the offset, in bytes, from the + // start of this record to the source bitmap bits in the BitmapBuffer field. + final int offBitsSrc = (int)leis.readUInt(); + // A 32-bit unsigned integer that specifies the size, in bytes, of the source bitmap bits. + final int cbBitsSrc = (int)leis.readUInt(); + + // A 32-bit signed integer that specifies the logical width of the source rectangle. + // This value MUST be greater than zero. + final int cxSrc = leis.readInt(); + // A 32-bit signed integer that specifies the logical height of the source rectangle. + // This value MUST be greater than zero. + final int cySrc = leis.readInt(); + + srcRect.setRect(xSrc, ySrc, cxSrc, cySrc); + + size += 7 * LittleEndianConsts.INT_SIZE; + + size += readBitmap(leis, bitmap, startIdx, offBmiSrc, cbBmiSrc, offBitsSrc, cbBitsSrc); + + return size; + } + } + + public static class EmfSetDiBitsToDevice implements HemfRecord { + protected final Rectangle2D bounds = new Rectangle2D.Double(); + protected final Point2D dest = new Point2D.Double(); + protected final Rectangle2D src = new Rectangle2D.Double(); + protected ColorUsage usageSrc; + protected final HwmfBitmapDib bitmap = new HwmfBitmapDib(); + + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.setDiBitsToDevice; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + int startIdx = leis.getReadIndex(); + + // A WMF RectL object that defines the destination bounding rectangle in device units. + long size = readRectL(leis, bounds); + // the logical x/y-coordinate of the upper-left corner of the destination rectangle. + size += readPointL(leis, dest); + // the source rectangle + size += readBounds2(leis, src); + // A 32-bit unsigned integer that specifies the offset, in bytes, from the + // start of this record to the source bitmap header in the BitmapBuffer field. + final int offBmiSrc = (int)leis.readUInt(); + // A 32-bit unsigned integer that specifies the size, in bytes, of the source bitmap header. + final int cbBmiSrc = (int)leis.readUInt(); + // A 32-bit unsigned integer that specifies the offset, in bytes, from the + // start of this record to the source bitmap bits in the BitmapBuffer field. + final int offBitsSrc = (int)leis.readUInt(); + // A 32-bit unsigned integer that specifies the size, in bytes, of the source bitmap bits. + final int cbBitsSrc = (int)leis.readUInt(); + // A 32-bit unsigned integer that specifies how to interpret values in the color table + // in the source bitmap header. This value MUST be in the DIBColors enumeration + usageSrc = ColorUsage.valueOf((int)leis.readUInt()); + // A 32-bit unsigned integer that specifies the first scan line in the array. + final int iStartScan = (int)leis.readUInt(); + // A 32-bit unsigned integer that specifies the number of scan lines. + final int cScans = (int)leis.readUInt(); + size += 7*LittleEndianConsts.INT_SIZE; + + size += readBitmap(leis, bitmap, startIdx, offBmiSrc, cbBmiSrc, offBitsSrc, cbBitsSrc); + + return size; + } + } + + static long readBitmap(final LittleEndianInputStream leis, final HwmfBitmapDib bitmap, + final int startIdx, final int offBmiSrc, final int cbBmiSrc, final int offBitsSrc, int cbBitsSrc) + throws IOException { + final int offCurr = leis.getReadIndex()-(startIdx-HEADER_SIZE); + final int undefinedSpace1 = offBmiSrc-offCurr; + assert(undefinedSpace1 >= 0); + + final int undefinedSpace2 = offBitsSrc-offCurr-cbBmiSrc-undefinedSpace1; + assert(undefinedSpace2 >= 0); + + leis.skipFully(undefinedSpace1); + + if (cbBmiSrc == 0 || cbBitsSrc == 0) { + return undefinedSpace1; + } + + final LittleEndianInputStream leisDib; + if (undefinedSpace2 == 0) { + leisDib = leis; + } else { + final ByteArrayOutputStream bos = new ByteArrayOutputStream(cbBmiSrc+cbBitsSrc); + final long cbBmiSrcAct = IOUtils.copy(leis, bos, cbBmiSrc); + assert (cbBmiSrcAct == cbBmiSrc); + leis.skipFully(undefinedSpace2); + final long cbBitsSrcAct = IOUtils.copy(leis, bos, cbBitsSrc); + assert (cbBitsSrcAct == cbBitsSrc); + leisDib = new LittleEndianInputStream(new ByteArrayInputStream(bos.toByteArray())); + } + final int dibSize = cbBmiSrc+cbBitsSrc; + final int dibSizeAct = bitmap.init(leisDib, dibSize); + assert (dibSizeAct <= dibSize); + return undefinedSpace1 + cbBmiSrc + undefinedSpace2 + cbBitsSrc; + } + + + static long readRgnData(final LittleEndianInputStream leis, final List rgnRects) { + // *** RegionDataHeader *** + // A 32-bit unsigned integer that specifies the size of this object in bytes. This MUST be 0x00000020. + long rgnHdrSiez = leis.readUInt(); + assert(rgnHdrSiez == 0x20); + // A 32-bit unsigned integer that specifies the region type. This SHOULD be RDH_RECTANGLES (0x00000001) + long rgnHdrType = leis.readUInt(); + assert(rgnHdrType == 1); + // A 32-bit unsigned integer that specifies the number of rectangles in this region. + long rgnCntRect = leis.readUInt(); + // A 32-bit unsigned integer that specifies the size of the buffer of rectangles in bytes. + long rgnCntBytes = leis.readUInt(); + long size = 4*LittleEndianConsts.INT_SIZE; + // A 128-bit WMF RectL object, which specifies the bounds of the region. + Rectangle2D rgnBounds = new Rectangle2D.Double(); + size += readRectL(leis, rgnBounds); + for (int i=0; i = : + // m00 (scaleX) = eM11 (Horizontal scaling component) + // m11 (scaleY) = eM22 (Vertical scaling component) + // m01 (shearX) = eM12 (Horizontal proportionality constant) + // m10 (shearY) = eM21 (Vertical proportionality constant) + // m02 (translateX) = eDx (The horizontal translation component, in logical units.) + // m12 (translateY) = eDy (The vertical translation component, in logical units.) + + // A 32-bit floating-point value of the transform matrix. + double eM11 = leis.readFloat(); + + // A 32-bit floating-point value of the transform matrix. + double eM12 = leis.readFloat(); + + // A 32-bit floating-point value of the transform matrix. + double eM21 = leis.readFloat(); + + // A 32-bit floating-point value of the transform matrix. + double eM22 = leis.readFloat(); + + // A 32-bit floating-point value that contains a horizontal translation component, in logical units. + double eDx = leis.readFloat(); + + // A 32-bit floating-point value that contains a vertical translation component, in logical units. + double eDy = leis.readFloat(); + + xform.setTransform(eM11, eM21, eM12, eM22, eDx, eDy); + + return 6 * LittleEndian.INT_SIZE; + } +} diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFont.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFont.java new file mode 100644 index 0000000000..8690855f16 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFont.java @@ -0,0 +1,464 @@ +/* ==================================================================== + 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.emf; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +import org.apache.poi.common.usermodel.fonts.FontCharset; +import org.apache.poi.hwmf.record.HwmfFont; +import org.apache.poi.util.LittleEndian; +import org.apache.poi.util.LittleEndianConsts; +import org.apache.poi.util.LittleEndianInputStream; + +public class HemfFont extends HwmfFont { + private static final int LOGFONT_SIZE = 92; + private static final int LOGFONTPANOSE_SIZE = 320; + + protected interface LogFontDetails {} + + protected static class LogFontExDv implements LogFontDetails { + protected int[] designVector; + } + + protected static class LogFontPanose implements LogFontDetails { + enum FamilyType { + PAN_ANY, + PAN_NO_FIT, + PAN_FAMILY_TEXT_DISPLAY, + PAN_FAMILY_SCRIPT, + PAN_FAMILY_DECORATIVE, + PAN_FAMILY_PICTORIAL + } + + enum SerifType { + PAN_ANY, + PAN_NO_FIT, + PAN_SERIF_COVE, + PAN_SERIF_OBTUSE_COVE, + PAN_SERIF_SQUARE_COVE, + PAN_SERIF_OBTUSE_SQUARE_COVE, + PAN_SERIF_SQUARE, + PAN_SERIF_THIN, + PAN_SERIF_BONE, + PAN_SERIF_EXAGGERATED, + PAN_SERIF_TRIANGLE, + PAN_SERIF_NORMAL_SANS, + PAN_SERIF_OBTUSE_SANS, + PAN_SERIF_PERP_SANS, + PAN_SERIF_FLARED, + PAN_SERIF_ROUNDED + } + + enum FontWeight { + PAN_ANY, + PAN_NO_FIT, + PAN_WEIGHT_VERY_LIGHT, + PAN_WEIGHT_LIGHT, + PAN_WEIGHT_THIN, + PAN_WEIGHT_BOOK, + PAN_WEIGHT_MEDIUM, + PAN_WEIGHT_DEMI, + PAN_WEIGHT_BOLD, + PAN_WEIGHT_HEAVY, + PAN_WEIGHT_BLACK, + PAN_WEIGHT_NORD + } + + enum Proportion { + PAN_ANY, + PAN_NO_FIT, + PAN_PROP_OLD_STYLE, + PAN_PROP_MODERN, + PAN_PROP_EVEN_WIDTH, + PAN_PROP_EXPANDED, + PAN_PROP_CONDENSED, + PAN_PROP_VERY_EXPANDED, + PAN_PROP_VERY_CONDENSED, + PAN_PROP_MONOSPACED + } + + enum Contrast { + PAN_ANY, + PAN_NO_FIT, + PAN_CONTRAST_NONE, + PAN_CONTRAST_VERY_LOW, + PAN_CONTRAST_LOW, + PAN_CONTRAST_MEDIUM_LOW, + PAN_CONTRAST_MEDIUM, + PAN_CONTRAST_MEDIUM_HIGH, + PAN_CONTRAST_HIGH, + PAN_CONTRAST_VERY_HIGH + } + + enum StrokeVariation { + PAN_ANY, + PAN_NO_FIT, + PAN_STROKE_GRADUAL_DIAG, + PAN_STROKE_GRADUAL_TRAN, + PAN_STROKE_GRADUAL_VERT, + PAN_STROKE_GRADUAL_HORZ, + PAN_STROKE_RAPID_VERT, + PAN_STROKE_RAPID_HORZ, + PAN_STROKE_INSTANT_VERT + } + + enum ArmStyle { + PAN_ANY, + PAN_NO_FIT, + PAN_STRAIGHT_ARMS_HORZ, + PAN_STRAIGHT_ARMS_WEDGE, + PAN_STRAIGHT_ARMS_VERT, + PAN_STRAIGHT_ARMS_SINGLE_SERIF, + PAN_STRAIGHT_ARMS_DOUBLE_SERIF, + PAN_BENT_ARMS_HORZ, + PAN_BENT_ARMS_WEDGE, + PAN_BENT_ARMS_VERT, + PAN_BENT_ARMS_SINGLE_SERIF, + PAN_BENT_ARMS_DOUBLE_SERIF + } + + enum Letterform { + PAN_ANY, + PAN_NO_FIT, + PAN_LETT_NORMAL_CONTACT, + PAN_LETT_NORMAL_WEIGHTED, + PAN_LETT_NORMAL_BOXED, + PAN_LETT_NORMAL_FLATTENED, + PAN_LETT_NORMAL_ROUNDED, + PAN_LETT_NORMAL_OFF_CENTER, + PAN_LETT_NORMAL_SQUARE, + PAN_LETT_OBLIQUE_CONTACT, + PAN_LETT_OBLIQUE_WEIGHTED, + PAN_LETT_OBLIQUE_BOXED, + PAN_LETT_OBLIQUE_FLATTENED, + PAN_LETT_OBLIQUE_ROUNDED, + PAN_LETT_OBLIQUE_OFF_CENTER, + PAN_LETT_OBLIQUE_SQUARE + } + + enum MidLine { + PAN_ANY, + PAN_NO_FIT, + PAN_MIDLINE_STANDARD_TRIMMED, + PAN_MIDLINE_STANDARD_POINTED, + PAN_MIDLINE_STANDARD_SERIFED, + PAN_MIDLINE_HIGH_TRIMMED, + PAN_MIDLINE_HIGH_POINTED, + PAN_MIDLINE_HIGH_SERIFED, + PAN_MIDLINE_CONSTANT_TRIMMED, + PAN_MIDLINE_CONSTANT_POINTED, + PAN_MIDLINE_CONSTANT_SERIFED, + PAN_MIDLINE_LOW_TRIMMED, + PAN_MIDLINE_LOW_POINTED, + PAN_MIDLINE_LOW_SERIFED + } + + enum XHeight { + PAN_ANY, + PAN_NO_FIT, + PAN_XHEIGHT_CONSTANT_SMALL, + PAN_XHEIGHT_CONSTANT_STD, + PAN_XHEIGHT_CONSTANT_LARGE, + PAN_XHEIGHT_DUCKING_SMALL, + PAN_XHEIGHT_DUCKING_STD, + PAN_XHEIGHT_DUCKING_LARGE + } + + protected int styleSize; + protected int vendorId; + protected int culture; + protected FamilyType familyType; + protected SerifType serifStyle; + protected FontWeight weight; + protected Proportion proportion; + protected Contrast contrast; + protected StrokeVariation strokeVariation; + protected ArmStyle armStyle; + protected Letterform letterform; + protected MidLine midLine; + protected XHeight xHeight; + } + + protected String fullname; + protected String style; + protected String script; + + protected LogFontDetails details; + + @Override + public int init(LittleEndianInputStream leis, long recordSize) throws IOException { + // A 32-bit signed integer that specifies the height, in logical units, of the font's + // character cell or character. The character height value, also known as the em size, is the + // character cell height value minus the internal leading value. The font mapper SHOULD + // interpret the value specified in the Height field in the following manner. + // + // 0x00000000 < value: + // The font mapper transforms this value into device units and matches it against + // the cell height of the available fonts. + // + // 0x00000000 + // The font mapper uses a default height value when it searches for a match. + // + // value < 0x00000000: + // The font mapper transforms this value into device units and matches its + // absolute value against the character height of the available fonts. + // + // For all height comparisons, the font mapper SHOULD look for the largest font that does not + // exceed the requested size. + height = leis.readInt(); + + // A 32-bit signed integer that specifies the average width, in logical units, of + // characters in the font. If the Width field value is zero, an appropriate value SHOULD be + // calculated from other LogFont values to find a font that has the typographer's intended + // aspect ratio. + width = leis.readInt(); + + // A 32-bit signed integer that specifies the angle, in tenths of degrees, + // between the escapement vector and the x-axis of the device. The escapement vector is + // parallel to the baseline of a row of text. + // + // When the graphics mode is set to GM_ADVANCED, the escapement angle of the string can + // be specified independently of the orientation angle of the string's characters. + escapement = leis.readInt(); + + // A 32-bit signed integer that specifies the angle, in tenths of degrees, + // between each character's baseline and the x-axis of the device. + orientation = leis.readInt(); + + // A 32-bit signed integer that specifies the weight of the font in the range zero through 1000. + // For example, 400 is normal and 700 is bold. If this value is zero, a default weight can be used. + weight = leis.readInt(); + + // An 8-bit unsigned integer that specifies an italic font if set to 0x01; + // otherwise, it MUST be set to 0x00. + italic = (leis.readUByte() == 0x01); + + // An 8-bit unsigned integer that specifies an underlined font if set to 0x01; + // otherwise, it MUST be set to 0x00. + underline = (leis.readUByte() == 0x01); + + // An 8-bit unsigned integer that specifies a strikeout font if set to 0x01; + // otherwise, it MUST be set to 0x00. + strikeOut = (leis.readUByte() == 0x01); + + // An 8-bit unsigned integer that specifies the set of character glyphs. + // It MUST be a value in the WMF CharacterSet enumeration. + // If the character set is unknown, metafile processing SHOULD NOT attempt + // to translate or interpret strings that are rendered with that font. + // If a typeface name is specified in the Facename field, the CharSet field + // value MUST match the character set of that typeface. + charSet = FontCharset.valueOf(leis.readUByte()); + + // An 8-bit unsigned integer that specifies the output precision. + // The output precision defines how closely the font is required to match the requested height, width, + // character orientation, escapement, pitch, and font type. + // It MUST be a value from the WMF OutPrecision enumeration. + // Applications can use the output precision to control how the font mapper chooses a font when the + // operating system contains more than one font with a specified name. For example, if an operating + // system contains a font named Symbol in rasterized and TrueType forms, an output precision value + // of OUT_TT_PRECIS forces the font mapper to choose the TrueType version. + // A value of OUT_TT_ONLY_PRECIS forces the font mapper to choose a TrueType font, even if it is + // necessary to substitute a TrueType font with another name. + outPrecision = WmfOutPrecision.valueOf(leis.readUByte()); + + // An 8-bit unsigned integer that specifies the clipping precision. + // The clipping precision defines how to clip characters that are partially outside the clipping region. + // It can be one or more of the WMF ClipPrecision Flags + clipPrecision.init(leis); + + // An 8-bit unsigned integer that specifies the output quality. The output quality defines how closely + // to attempt to match the logical-font attributes to those of an actual physical font. + // It MUST be one of the values in the WMF FontQuality enumeration + quality = WmfFontQuality.valueOf(leis.readUByte()); + + // A WMF PitchAndFamily object that specifies the pitch and family of the font. + // Font families describe the look of a font in a general way. + // They are intended for specifying a font when the specified typeface is not available. + pitchAndFamily = leis.readUByte(); + + int size = 5* LittleEndianConsts.INT_SIZE+8*LittleEndianConsts.BYTE_SIZE; + + StringBuilder sb = new StringBuilder(); + + // A string of no more than 32 Unicode characters that specifies the typeface name of the font. + // If the length of this string is less than 32 characters, a terminating NULL MUST be present, + // after which the remainder of this field MUST be ignored. + int readBytes = readString(leis, sb, 32); + if (readBytes == -1) { + throw new IOException("Font facename can't be determined."); + } + facename = sb.toString(); + size += readBytes; + + if (recordSize <= LOGFONT_SIZE) { + return size; + } + + // A string of 64 Unicode characters that contains the font's full name. + // Ifthe length of this string is less than 64 characters, a terminating + // NULL MUST be present, after which the remainder of this field MUST be ignored. + readBytes = readString(leis, sb, 64); + if (readBytes == -1) { + throw new IOException("Font fullname can't be determined."); + } + fullname = sb.toString(); + size += readBytes; + + // A string of 32 Unicode characters that defines the font's style. If the length of + // this string is less than 32 characters, a terminating NULL MUST be present, + // after which the remainder of this field MUST be ignored. + readBytes = readString(leis, sb, 32); + if (readBytes == -1) { + throw new IOException("Font style can't be determined."); + } + style = sb.toString(); + size += readBytes; + + if (recordSize == LOGFONTPANOSE_SIZE) { + // LogFontPanose Object + + LogFontPanose logPan = new LogFontPanose(); + details = logPan; + + int version = leis.readInt(); + + // A 32-bit unsigned integer that specifies the point size at which font + //hinting is performed. If set to zero, font hinting is performed at the point size corresponding + //to the Height field in the LogFont object in the LogFont field. + logPan.styleSize = (int)leis.readUInt(); + + int match = leis.readInt(); + + int reserved = leis.readInt(); + + logPan.vendorId = leis.readInt(); + + logPan.culture = leis.readInt(); + + // An 8-bit unsigned integer that specifies the family type. + // The value MUST be in the FamilyType enumeration table. + logPan.familyType = LogFontPanose.FamilyType.values()[leis.readUByte()]; + + // An 8-bit unsigned integer that specifies the serif style. + // The value MUST be in the SerifType enumeration table. + logPan.serifStyle = LogFontPanose.SerifType.values()[leis.readUByte()]; + + // An 8-bit unsigned integer that specifies the weight of the font. + // The value MUST be in the Weight enumeration table. + logPan.weight = LogFontPanose.FontWeight.values()[leis.readUByte()]; + + // An 8-bit unsigned integer that specifies the proportion of the font. + // The value MUST be in the Proportion enumeration table. + logPan.proportion = LogFontPanose.Proportion.values()[leis.readUByte()]; + + // An 8-bit unsigned integer that specifies the proportion of the font. + // The value MUST be in the Proportion enumeration table. + logPan.contrast = LogFontPanose.Contrast.values()[leis.readUByte()]; + + // An 8-bit unsigned integer that specifies the stroke variation for the font. + // The value MUST be in the StrokeVariation enumeration table. + logPan.strokeVariation = LogFontPanose.StrokeVariation.values()[leis.readUByte()]; + + // An 8-bit unsigned integer that specifies the arm style of the font. + // The value MUST be in the ArmStyle enumeration table. + logPan.armStyle = LogFontPanose.ArmStyle.values()[leis.readUByte()]; + + // An 8-bit unsigned integer that specifies the letterform of the font. + // The value MUST be in the Letterform enumeration table. + logPan.letterform = LogFontPanose.Letterform.values()[leis.readUByte()]; + + // An 8-bit unsigned integer that specifies the midline of the font. + // The value MUST be in the MidLine enumeration table. + logPan.midLine = LogFontPanose.MidLine.values()[leis.readUByte()]; + + // An 8-bit unsigned integer that specifies the x height of the font. + // The value MUST be in the XHeight enumeration table. + logPan.xHeight = LogFontPanose.XHeight.values()[leis.readUByte()]; + + // skip 2 byte to ensure 32-bit alignment of this structure. + leis.skip(2); + + size += 6*LittleEndianConsts.INT_SIZE+10* LittleEndianConsts.BYTE_SIZE+2; + } else { + // LogFontExDv Object + + LogFontExDv logEx = new LogFontExDv(); + details = logEx; + + // A string of 32 Unicode characters that defines the character set of the font. + // If the length of this string is less than 32 characters, a terminating NULL MUST be present, + // after which the remainder of this field MUST be ignored. + readBytes = readString(leis, sb, 32); + if (readBytes == -1) { + throw new IOException("Font script can't be determined."); + } + script = sb.toString(); + size += readBytes; + + // Design Vector + + // A 32-bit unsigned integer that MUST be set to the value 0x08007664. + int signature = leis.readInt(); + assert (signature == 0x08007664); + + // A 32-bit unsigned integer that specifies the number of elements in the + // Values array. It MUST be in the range 0 to 16, inclusive. + int numAxes = leis.readInt(); + assert (0 <= numAxes && numAxes <= 16); + + // An optional array of 32-bit signed integers that specify the values of the font axes of a + // multiple master, OpenType font. The maximum number of values in the array is 16. + if (numAxes > 0) { + logEx.designVector = new int[numAxes]; + for (int i=0; i palette = new ArrayList<>(); + + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.eof; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + + // A 32-bit unsigned integer that specifies the number of palette entries. + int nPalEntries = (int)leis.readUInt(); + // A 32-bit unsigned integer that specifies the offset to the palette entries from the start of this record. + int offPalEntries = (int)leis.readUInt(); + + int size = 2*LittleEndianConsts.INT_SIZE; + int undefinedSpace1 = (int)(offPalEntries - size - HEADER_SIZE); + assert (undefinedSpace1 >= 0); + leis.skipFully(undefinedSpace1); + size += undefinedSpace1; + + for (int i=0; i= 0); + leis.skipFully(undefinedSpace2); + size += undefinedSpace2; + + // A 32-bit unsigned integer that MUST be the same as Size and MUST be the + // last field of the record and hence the metafile. + // LogPaletteEntry objects, if they exist, MUST precede this field. + long sizeLast = leis.readUInt(); + size += LittleEndianConsts.INT_SIZE; + assert ((sizeLast-HEADER_SIZE) == recordSize && recordSize == size); + + return size; + } + } + + /** + * The EMF_SAVEDC record saves the playback device context for later retrieval. + */ + public static class EmfSaveDc implements HemfRecord { + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.saveDc; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + return 0; + } + + @Override + public void draw(HemfGraphics ctx) { + ctx.saveProperties(); + } + } + + /** + * The EMF_RESTOREDC record restores the playback device context from a previously saved device + * context. + */ + public static class EmfRestoreDc implements HemfRecord { + + /** + * SavedDC (4 bytes): A 32-bit signed integer that specifies the saved state to restore relative to + * the current state. This value MUST be negative; –1 represents the state that was most + * recently saved on the stack, –2 the one before that, etc. + */ + private int nSavedDC; + + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.restoreDc; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + nSavedDC = leis.readInt(); + return LittleEndianConsts.INT_SIZE; + } + + @Override + public void draw(HemfGraphics ctx) { + ctx.restoreProperties(nSavedDC); + } + } + + /** + * The META_SETBKCOLOR record sets the background color in the playback device context to a + * specified color, or to the nearest physical color if the device cannot represent the specified color. + */ + public static class EmfSetBkColor implements HemfRecord { + + private HwmfColorRef colorRef; + + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.setBkColor; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + colorRef = new HwmfColorRef(); + return colorRef.init(leis); + } + + @Override + public void draw(HemfGraphics ctx) { + ctx.getProperties().setBackgroundColor(colorRef); + } + } + + + /** + * The EMR_SETBKMODE record specifies the background mix mode of the playback device context. + * The background mix mode is used with text, hatched brushes, and pen styles that are not solid + * lines. + */ + public static class EmfSetBkMode extends WmfSetBkMode implements HemfRecord { + public HemfRecordType getEmfRecordType() { + return HemfRecordType.setBkMode; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + /* + * A 32-bit unsigned integer that specifies the background mode + * and MUST be in the BackgroundMode (section 2.1.4) enumeration + */ + bkMode = HwmfBkMode.valueOf((int)leis.readUInt()); + return LittleEndianConsts.INT_SIZE; + } + } + + /** + * The EMR_SETMAPPERFLAGS record specifies parameters of the process of matching logical fonts to + * physical fonts, which is performed by the font mapper. + */ + public static class EmfSetMapperFlags extends HwmfMisc.WmfSetMapperFlags implements HemfRecord { + public HemfRecordType getEmfRecordType() { + return HemfRecordType.setMapperFlags; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + return super.init(leis, recordSize, (int)recordId); + } + } + + /** + * The EMR_SETMAPMODE record specifies the mapping mode of the playback device context. The + * mapping mode specifies the unit of measure used to transform page space units into device space + * units, and also specifies the orientation of the device's x-axis and y-axis. + */ + public static class EmfSetMapMode extends HwmfMisc.WmfSetMapMode implements HemfRecord { + public HemfRecordType getEmfRecordType() { + return HemfRecordType.setMapMode; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + // A 32-bit unsigned integer whose definition MUST be in the MapMode enumeration + mapMode = HwmfMapMode.valueOf((int)leis.readUInt()); + return LittleEndianConsts.INT_SIZE; + } + } + + /** + * The EMR_SETROP2 record defines a binary raster operation mode. + */ + public static class EmfSetRop2 extends HwmfMisc.WmfSetRop2 implements HemfRecord { + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.setRop2; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + // A 32-bit unsigned integer that specifies the raster operation mode and + // MUST be in the WMF Binary Raster Op enumeration + drawMode = HwmfBinaryRasterOp.valueOf((int)leis.readUInt()); + return LittleEndianConsts.INT_SIZE; + } + } + + + /** + * The EMR_SETSTRETCHBLTMODE record specifies bitmap stretch mode. + */ + public static class EmfSetStretchBltMode extends HwmfMisc.WmfSetStretchBltMode implements HemfRecord { + + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.setStretchBltMode; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + // A 32-bit unsigned integer that specifies the stretch mode and MAY be + // in the StretchMode enumeration. + stretchBltMode = StretchBltMode.valueOf((int)leis.readUInt()); + return LittleEndianConsts.INT_SIZE; + } + } + + /** The EMR_CREATEBRUSHINDIRECT record defines a logical brush for graphics operations. */ + public static class EmfCreateBrushIndirect extends HwmfMisc.WmfCreateBrushIndirect implements HemfRecord { + /** + * A 32-bit unsigned integer that specifies the index of the logical brush object in the + * EMF Object Table. This index MUST be saved so that this object can be reused or modified. + */ + private int brushIdx; + + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.createBrushIndirect; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + brushIdx = (int)leis.readUInt(); + + brushStyle = HwmfBrushStyle.valueOf((int)leis.readUInt()); + colorRef = new HwmfColorRef(); + int size = colorRef.init(leis); + brushHatch = HwmfHatchStyle.valueOf((int)leis.readUInt()); + return size+3*LittleEndianConsts.INT_SIZE; + + } + } + + /** + * The EMR_DELETEOBJECT record deletes a graphics object, which is specified by its index + * in the EMF Object Table + */ + public static class EmfDeleteObject extends HwmfMisc.WmfDeleteObject implements HemfRecord { + + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.deleteobject; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + objectIndex = (int)leis.readUInt(); + return LittleEndianConsts.INT_SIZE; + } + } + + /** The EMR_CREATEPEN record defines a logical pen for graphics operations. */ + public static class EmfCreatePen extends HwmfMisc.WmfCreatePenIndirect implements HemfRecord { + /** + * A 32-bit unsigned integer that specifies the index of the logical palette object + * in the EMF Object Table. This index MUST be saved so that this object can be + * reused or modified. + */ + protected int penIndex; + + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.createPen; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + penIndex = (int)leis.readUInt(); + + // A 32-bit unsigned integer that specifies the PenStyle. + // The value MUST be defined from the PenStyle enumeration table + penStyle = HwmfPenStyle.valueOf((int)leis.readUInt()); + + int widthX = leis.readInt(); + int widthY = leis.readInt(); + dimension.setSize(widthX, widthY); + + int size = colorRef.init(leis); + + return size + 4*LittleEndianConsts.INT_SIZE; + } + } + + public static class EmfExtCreatePen extends EmfCreatePen { + protected HwmfBrushStyle brushStyle; + protected HwmfHatchStyle hatchStyle; + + protected int[] styleEntry; + + protected final HwmfBitmapDib bitmap = new HwmfBitmapDib(); + + + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.extCreatePen; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + final int startIdx = leis.getReadIndex(); + + penIndex = (int)leis.readUInt(); + + // A 32-bit unsigned integer that specifies the offset from the start of this + // record to the DIB header, if the record contains a DIB. + int offBmi = (int)leis.readUInt(); + + // A 32-bit unsigned integer that specifies the size of the DIB header, if the + // record contains a DIB. + int cbBmi = (int)leis.readUInt(); + + // A 32-bit unsigned integer that specifies the offset from the start of this + // record to the DIB bits, if the record contains a DIB. + int offBits = (int)leis.readUInt(); + + // A 32-bit unsigned integer that specifies the size of the DIB bits, if the record + // contains a DIB. + int cbBits = (int)leis.readUInt(); + + // A 32-bit unsigned integer that specifies the PenStyle. + // The value MUST be defined from the PenStyle enumeration table + penStyle = HwmfPenStyle.valueOf((int)leis.readUInt()); + + // A 32-bit unsigned integer that specifies the width of the line drawn by the pen. + // If the pen type in the PenStyle field is PS_GEOMETRIC, this value is the width in logical + // units; otherwise, the width is specified in device units. If the pen type in the PenStyle field is + // PS_COSMETIC, this value MUST be 0x00000001. + long width = leis.readUInt(); + dimension.setSize(width, 0); + + // A 32-bit unsigned integer that specifies a brush style for the pen from the WMF BrushStyle enumeration + // + // If the pen type in the PenStyle field is PS_GEOMETRIC, this value MUST be either BS_SOLID or BS_HATCHED. + // The value of this field can be BS_NULL, but only if the line style specified in PenStyle is PS_NULL. + // The BS_NULL style SHOULD be used to specify a brush that has no effect + brushStyle = HwmfBrushStyle.valueOf((int)leis.readUInt()); + + int size = 8 * LittleEndianConsts.INT_SIZE; + + size += colorRef.init(leis); + + hatchStyle = HwmfHatchStyle.valueOf(leis.readInt()); + + // The number of elements in the array specified in the StyleEntry + // field. This value SHOULD be zero if PenStyle does not specify PS_USERSTYLE. + final int numStyleEntries = (int)leis.readUInt(); + size += 2*LittleEndianConsts.INT_SIZE; + + assert(numStyleEntries == 0 || penStyle.getLineDash() == HwmfLineDash.USERSTYLE); + + // An optional array of 32-bit unsigned integers that defines the lengths of + // dashes and gaps in the line drawn by this pen, when the value of PenStyle is + // PS_USERSTYLE line style for the pen. The array contains a number of entries specified by + // NumStyleEntries, but it is used as if it repeated indefinitely. + // The first entry in the array specifies the length of the first dash. The second entry specifies + // the length of the first gap. Thereafter, lengths of dashes and gaps alternate. + // If the pen type in the PenStyle field is PS_GEOMETRIC, the lengths are specified in logical + // units; otherwise, the lengths are specified in device units. + + styleEntry = new int[numStyleEntries]; + + for (int i=0; i { + + static final int HEADER_SIZE = 2*LittleEndianConsts.INT_SIZE; + + private final LittleEndianInputStream stream; + private HemfRecord currentRecord; + + public HemfRecordIterator(LittleEndianInputStream leis) { + stream = leis; + //queue the first non-header record + currentRecord = _next(); + } + + @Override + public boolean hasNext() { + return currentRecord != null; + } + + @Override + public HemfRecord next() { + HemfRecord toReturn = currentRecord; + currentRecord = (currentRecord instanceof HemfMisc.EmfEof) ? null : _next(); + return toReturn; + } + + private HemfRecord _next() { + if (currentRecord != null && HemfRecordType.eof == currentRecord.getEmfRecordType()) { + return null; + } + long recordId = stream.readUInt(); + long recordSize = stream.readUInt(); + + HemfRecordType type = HemfRecordType.getById(recordId); + if (type == null) { + throw new RecordFormatException("Undefined record of type:"+recordId); + } + final HemfRecord record = type.constructor.get(); + + try { + long remBytes = recordSize-HEADER_SIZE; + long readBytes = record.init(stream, remBytes, recordId); + assert (readBytes <= remBytes); + stream.skipFully((int)(remBytes-readBytes)); + } catch (IOException|RuntimeException e) { + throw new RecordFormatException(e); + } + + return record; + } + + @Override + public void remove() { + throw new UnsupportedOperationException("Remove not supported"); + } + +} \ No newline at end of file diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java new file mode 100644 index 0000000000..0995291005 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java @@ -0,0 +1,165 @@ +/* ==================================================================== + 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.emf; + +import java.util.function.Supplier; + +import org.apache.poi.util.Internal; + +@Internal +public enum HemfRecordType { + + header(0x00000001, HemfHeader::new), + polyBezier(0x00000002, HemfDraw.EmfPolyBezier::new), + polygon(0x00000003, HemfDraw.EmfPolygon::new), + polyline(0x00000004, HemfDraw.EmfPolyline::new), + polyBezierTo(0x00000005, HemfDraw.EmfPolyBezierTo::new), + polylineTo(0x00000006, HemfDraw.EmfPolylineTo::new), + polyPolyline(0x00000007, HemfDraw.EmfPolyPolyline::new), + polyPolygon(0x00000008, HemfDraw.EmfPolyPolygon::new), + setWindowExtEx(0x00000009, HemfWindowing.EmfSetWindowExtEx::new), + setWindowOrgEx(0x0000000A, HemfWindowing.EmfSetWindowOrgEx::new), + setViewportExtEx(0x0000000B, HemfWindowing.EmfSetViewportExtEx::new), + setViewportOrgEx(0x0000000C, HemfWindowing.EmfSetViewportOrgEx::new), + setbrushorgex(0x0000000D, UnimplementedHemfRecord::new), + eof(0x0000000E, HemfMisc.EmfEof::new), + setPixelV(0x0000000F, HemfDraw.EmfSetPixelV::new), + setMapperFlags(0x00000010, HemfMisc.EmfSetMapperFlags::new), + setMapMode(0x00000011, HemfMisc.EmfSetMapMode::new), + setBkMode(0x00000012, HemfMisc.EmfSetBkMode::new), + setPolyfillMode(0x00000013, HemfFill.EmfSetPolyfillMode::new), + setRop2(0x00000014, HemfMisc.EmfSetRop2::new), + setStretchBltMode(0x00000015, HemfMisc.EmfSetStretchBltMode::new), + setTextAlign(0x00000016, HemfText.EmfSetTextAlign::new), + setcoloradjustment(0x00000017, UnimplementedHemfRecord::new), + setTextColor(0x00000018, HemfText.SetTextColor::new), + setBkColor(0x00000019, HemfMisc.EmfSetBkColor::new), + setOffsetClipRgn(0x0000001A, HemfWindowing.EmfSetOffsetClipRgn::new), + setMoveToEx(0x0000001B, HemfDraw.EmfSetMoveToEx::new), + setmetargn(0x0000001C, UnimplementedHemfRecord::new), + setExcludeClipRect(0x0000001D, HemfWindowing.EmfSetExcludeClipRect::new), + setIntersectClipRect(0x0000001E, HemfWindowing.EmfSetIntersectClipRect::new), + scaleViewportExtEx(0x0000001F, HemfWindowing.EmfScaleViewportExtEx::new), + scaleWindowExtEx(0x00000020, HemfWindowing.EmfScaleWindowExtEx::new), + saveDc(0x00000021, HemfMisc.EmfSaveDc::new), + restoreDc(0x00000022, HemfMisc.EmfRestoreDc::new), + setworldtransform(0x00000023, UnimplementedHemfRecord::new), + modifyworldtransform(0x00000024, UnimplementedHemfRecord::new), + selectObject(0x00000025, HemfDraw.EmfSelectObject::new), + createPen(0x00000026, HemfMisc.EmfCreatePen::new), + createBrushIndirect(0x00000027, HemfMisc.EmfCreateBrushIndirect::new), + deleteobject(0x00000028, HemfMisc.EmfDeleteObject::new), + anglearc(0x00000029, UnimplementedHemfRecord::new), + ellipse(0x0000002A, HemfDraw.EmfEllipse::new), + rectangle(0x0000002B, HemfDraw.EmfRectangle::new), + roundRect(0x0000002C, HemfDraw.EmfRoundRect::new), + arc(0x0000002D, HemfDraw.EmfArc::new), + chord(0x0000002E, HemfDraw.EmfChord::new), + pie(0x0000002F, HemfDraw.EmfPie::new), + selectPalette(0x00000030, HemfPalette.EmfSelectPalette::new), + createPalette(0x00000031, HemfPalette.EmfCreatePalette::new), + setPaletteEntries(0x00000032, HemfPalette.EmfSetPaletteEntries::new), + resizePalette(0x00000033, HemfPalette.EmfResizePalette::new), + realizePalette(0x0000034, HemfPalette.EmfRealizePalette::new), + extFloodFill(0x00000035, HemfFill.EmfExtFloodFill::new), + lineTo(0x00000036, HemfDraw.EmfLineTo::new), + arcTo(0x00000037, HemfDraw.EmfArcTo::new), + polyDraw(0x00000038, HemfDraw.EmfPolyDraw::new), + setarcdirection(0x00000039, UnimplementedHemfRecord::new), + setMiterLimit(0x0000003A, HemfMisc.EmfSetMiterLimit::new), + beginpath(0x0000003B, UnimplementedHemfRecord::new), + endpath(0x0000003C, UnimplementedHemfRecord::new), + closefigure(0x0000003D, UnimplementedHemfRecord::new), + fillpath(0x0000003E, UnimplementedHemfRecord::new), + strokeandfillpath(0x0000003F, UnimplementedHemfRecord::new), + strokepath(0x00000040, UnimplementedHemfRecord::new), + flattenpath(0x00000041, UnimplementedHemfRecord::new), + widenpath(0x00000042, UnimplementedHemfRecord::new), + selectclippath(0x00000043, UnimplementedHemfRecord::new), + abortpath(0x00000044, UnimplementedHemfRecord::new), + // no 45 ?! + comment(0x00000046, HemfComment.EmfComment::new), + fillRgn(0x00000047, HemfFill.EmfFillRgn::new), + frameRgn(0x00000048, HemfFill.EmfFrameRgn::new), + invertRgn(0x00000049, HemfFill.EmfInvertRgn::new), + paintRgn(0x0000004A, HemfFill.EmfPaintRgn::new), + extSelectClipRgn(0x0000004B, HemfFill.EmfExtSelectClipRgn::new), + bitBlt(0x0000004C, HemfFill.EmfBitBlt::new), + stretchBlt(0x0000004D, HemfFill.EmfStretchBlt::new), + maskblt(0x0000004E, UnimplementedHemfRecord::new), + plgblt(0x0000004F, UnimplementedHemfRecord::new), + setDiBitsToDevice(0x00000050, HemfFill.EmfSetDiBitsToDevice::new), + stretchdibits(0x00000051, UnimplementedHemfRecord::new), + extCreateFontIndirectW(0x00000052, HemfText.ExtCreateFontIndirectW::new), + exttextouta(0x00000053, HemfText.ExtTextOutA::new), + exttextoutw(0x00000054, HemfText.ExtTextOutW::new), + polyBezier16(0x00000055, HemfDraw.EmfPolyBezier16::new), + polygon16(0x00000056, HemfDraw.EmfPolygon16::new), + polyline16(0x00000057, HemfDraw.EmfPolyline16::new), + polyBezierTo16(0x00000058, HemfDraw.EmfPolyBezierTo16::new), + polylineTo16(0x00000059, HemfDraw.EmfPolylineTo16::new), + polyPolyline16(0x0000005A, HemfDraw.EmfPolyPolyline16::new), + polyPolygon16(0x0000005B, HemfDraw.EmfPolyPolygon16::new), + polyDraw16(0x0000005C, HemfDraw.EmfPolyDraw16::new), + createmonobrush16(0x0000005D, UnimplementedHemfRecord::new), + createdibpatternbrushpt(0x0000005E, UnimplementedHemfRecord::new), + extCreatePen(0x0000005F, HemfMisc.EmfExtCreatePen::new), + polytextouta(0x00000060, HemfText.PolyTextOutA::new), + polytextoutw(0x00000061, HemfText.PolyTextOutW::new), + seticmmode(0x00000062, UnimplementedHemfRecord::new), + createcolorspace(0x00000063, UnimplementedHemfRecord::new), + setcolorspace(0x00000064, UnimplementedHemfRecord::new), + deletecolorspace(0x00000065, UnimplementedHemfRecord::new), + glsrecord(0x00000066, UnimplementedHemfRecord::new), + glsboundedrecord(0x00000067, UnimplementedHemfRecord::new), + pixelformat(0x00000068, UnimplementedHemfRecord::new), + drawescape(0x00000069, UnimplementedHemfRecord::new), + extescape(0x0000006A, UnimplementedHemfRecord::new), + // no 6b ?! + smalltextout(0x0000006C, UnimplementedHemfRecord::new), + forceufimapping(0x0000006D, UnimplementedHemfRecord::new), + namedescape(0x0000006E, UnimplementedHemfRecord::new), + colorcorrectpalette(0x0000006F, UnimplementedHemfRecord::new), + seticmprofilea(0x00000070, UnimplementedHemfRecord::new), + seticmprofilew(0x00000071, UnimplementedHemfRecord::new), + alphaBlend(0x00000072, HemfFill.EmfAlphaBlend::new), + setlayout(0x00000073, UnimplementedHemfRecord::new), + transparentblt(0x00000074, UnimplementedHemfRecord::new), + // no 75 ?! + gradientfill(0x00000076, UnimplementedHemfRecord::new), + setlinkdufis(0x00000077, UnimplementedHemfRecord::new), + settextjustification(0x00000078, HemfText.SetTextJustification::new), + colormatchtargetw(0x00000079, UnimplementedHemfRecord::new), + createcolorspacew(0x0000007A, UnimplementedHemfRecord::new); + + + public final long id; + public final Supplier constructor; + + HemfRecordType(long id, Supplier constructor) { + this.id = id; + this.constructor = constructor; + } + + public static HemfRecordType getById(long id) { + for (HemfRecordType wrt : values()) { + if (wrt.id == id) return wrt; + } + return null; + } +} diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java new file mode 100644 index 0000000000..95c110df10 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java @@ -0,0 +1,317 @@ +/* ==================================================================== + 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.emf; + +import static java.nio.charset.StandardCharsets.UTF_16LE; +import static org.apache.poi.hemf.record.emf.HemfDraw.readRectL; +import static org.apache.poi.hemf.record.emf.HemfDraw.readDimensionFloat; +import static org.apache.poi.hemf.record.emf.HemfDraw.readPointL; + +import java.awt.geom.Dimension2D; +import java.awt.geom.Rectangle2D; +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; + +import org.apache.poi.hwmf.record.HwmfColorRef; +import org.apache.poi.hwmf.record.HwmfText; +import org.apache.poi.hwmf.record.HwmfText.WmfSetTextAlign; +import org.apache.poi.util.Dimension2DDouble; +import org.apache.poi.util.IOUtils; +import org.apache.poi.util.Internal; +import org.apache.poi.util.LittleEndianConsts; +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 static final int MAX_RECORD_LENGTH = 1_000_000; + + public enum EmfGraphicsMode { + GM_COMPATIBLE, GM_ADVANCED + } + + public static class ExtTextOutA implements HemfRecord { + + protected final Rectangle2D boundsIgnored = new Rectangle2D.Double(); + + protected EmfGraphicsMode graphicsMode; + + /** + * The scale factor to apply along the X/Y axis to convert from page space units to .01mm units. + * This SHOULD be used only if the graphics mode specified by iGraphicsMode is GM_COMPATIBLE. + */ + protected final Dimension2D scale = new Dimension2DDouble(); + + protected final EmrTextObject textObject; + + public ExtTextOutA() { + this(false); + } + + protected ExtTextOutA(boolean isUnicode) { + textObject = new EmrTextObject(isUnicode); + } + + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.exttextouta; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + if (recordSize < 0 || Integer.MAX_VALUE <= recordSize) { + throw new RecordFormatException("recordSize must be a positive integer (0-0x7FFFFFFF)"); + } + + // A WMF RectL object. It is not used and MUST be ignored on receipt. + long size = readRectL(leis, boundsIgnored); + + // A 32-bit unsigned integer that specifies the graphics mode from the GraphicsMode enumeration + graphicsMode = EmfGraphicsMode.values()[leis.readInt()-1]; + size += LittleEndianConsts.INT_SIZE; + + size += readDimensionFloat(leis, scale); + + // guarantee to read the rest of the EMRTextObjectRecord + size += textObject.init(leis, recordSize, (int)size); + + return size; + } + + /** + * + * 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 text from this text element + * @throws IOException + */ + public String getText(Charset charset) throws IOException { + return textObject.getText(charset); + } + + /** + * + * @return the x offset for the EmrTextObject + */ + public EmrTextObject getTextObject() { + return textObject; + } + + public EmfGraphicsMode getGraphicsMode() { + return graphicsMode; + } + + public Dimension2D getScale() { + return scale; + } + } + + public static class ExtTextOutW extends ExtTextOutA { + + public ExtTextOutW() { + super(true); + } + + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.exttextoutw; + } + + public String getText() throws IOException { + return getText(UTF_16LE); + } + } + + /** + * The EMR_SETTEXTALIGN record specifies text alignment. + */ + public static class EmfSetTextAlign extends WmfSetTextAlign implements HemfRecord { + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.setTextAlign; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + /** + * A 32-bit unsigned integer that specifies text alignment by using a mask of text alignment flags. + * These are either WMF TextAlignmentMode Flags for text with a horizontal baseline, + * or WMF VerticalTextAlignmentMode Flags for text with a vertical baseline. + * Only one value can be chosen from those that affect horizontal and vertical alignment. + */ + textAlignmentMode = (int)leis.readUInt(); + return LittleEndianConsts.INT_SIZE; + } + } + + /** + * The EMR_SETTEXTCOLOR record defines the current text color. + */ + public static class SetTextColor implements HemfRecord { + /** A WMF ColorRef object that specifies the text color value. */ + private final HwmfColorRef colorRef = new HwmfColorRef(); + + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.setTextColor; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + return colorRef.init(leis); + } + } + + public static class EmrTextObject extends HwmfText.WmfExtTextOut { + protected final boolean isUnicode; + protected final List outputDx = new ArrayList<>(); + + public EmrTextObject(boolean isUnicode) { + super(new EmfExtTextOutOptions()); + this.isUnicode = isUnicode; + } + + @Override + public int init(LittleEndianInputStream leis, final long recordSize, final int offset) throws IOException { + // A WMF PointL object that specifies the coordinates of the reference point used to position the string. + // The reference point is defined by the last EMR_SETTEXTALIGN record. + // If no such record has been set, the default alignment is TA_LEFT,TA_TOP. + long size = readPointL(leis, reference); + // A 32-bit unsigned integer that specifies the number of characters in the string. + stringLength = (int)leis.readUInt(); + // A 32-bit unsigned integer that specifies the offset to the output string, in bytes, + // from the start of the record in which this object is contained. + // This value MUST be 8- or 16-bit aligned, according to the character format. + int offString = (int)leis.readUInt(); + size += 2*LittleEndianConsts.INT_SIZE; + + size += options.init(leis); + // An optional WMF RectL object that defines a clipping and/or opaquing rectangle in logical units. + // This rectangle is applied to the text output performed by the containing record. + if (options.isClipped() || options.isOpaque()) { + size += readRectL(leis, bounds); + } + + // A 32-bit unsigned integer that specifies the offset to an intercharacter spacing array, in bytes, + // from the start of the record in which this object is contained. This value MUST be 32-bit aligned. + int offDx = (int)leis.readUInt(); + size += LittleEndianConsts.INT_SIZE; + + int undefinedSpace1 = (int)(offString-offset-size-2*LittleEndianConsts.INT_SIZE); + assert (undefinedSpace1 >= 0); + leis.skipFully(undefinedSpace1); + size += undefinedSpace1; + + rawTextBytes = IOUtils.safelyAllocate(stringLength*(isUnicode?2:1), MAX_RECORD_LENGTH); + leis.readFully(rawTextBytes); + size += rawTextBytes.length; + + outputDx.clear(); + if (offDx > 0) { + int undefinedSpace2 = (int) (offDx - offset - size - 2 * LittleEndianConsts.INT_SIZE); + assert (undefinedSpace2 >= 0); + leis.skipFully(undefinedSpace2); + size += undefinedSpace2; + + // An array of 32-bit unsigned integers that specify the output spacing between the origins of adjacent + // character cells in logical units. The location of this field is specified by the value of offDx + // in bytes from the start of this record. If spacing is defined, this field contains the same number + // of values as characters in the output string. + // + // If the Options field of the EmrText object contains the ETO_PDY flag, then this buffer + // contains twice as many values as there are characters in the output string, one + // horizontal and one vertical offset for each, in that order. + // + // If ETO_RTLREADING is specified, characters are laid right to left instead of left to right. + // No other options affect the interpretation of this field. + while (size < recordSize) { + outputDx.add((int) leis.readUInt()); + size += LittleEndianConsts.INT_SIZE; + } + } + + return (int)size; + } + } + + + public static class ExtCreateFontIndirectW extends HwmfText.WmfCreateFontIndirect + implements HemfRecord { + int fontIdx; + + public ExtCreateFontIndirectW() { + super(new HemfFont()); + } + + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.extCreateFontIndirectW; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + // A 32-bit unsigned integer that specifies the index of the logical font object + // in the EMF Object Table + fontIdx = (int)leis.readUInt(); + int size = font.init(leis, (int)(recordSize-LittleEndianConsts.INT_SIZE)); + return size+LittleEndianConsts.INT_SIZE; + } + } + + public static class EmfExtTextOutOptions extends HwmfText.WmfExtTextOutOptions { + @Override + public int init(LittleEndianInputStream leis) { + // A 32-bit unsigned integer that specifies how to use the rectangle specified in the Rectangle field. + // This field can be a combination of more than one ExtTextOutOptions enumeration + flag = (int)leis.readUInt(); + return LittleEndianConsts.INT_SIZE; + } + } + + public static class SetTextJustification extends UnimplementedHemfRecord { + + } + + /** + * 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 { + + } + +} diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfWindowing.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfWindowing.java new file mode 100644 index 0000000000..bd48f1b39d --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfWindowing.java @@ -0,0 +1,200 @@ +/* ==================================================================== + 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.emf; + +import java.io.IOException; + +import org.apache.poi.hwmf.record.HwmfWindowing; +import org.apache.poi.util.LittleEndianConsts; +import org.apache.poi.util.LittleEndianInputStream; + +public class HemfWindowing { + + /** + * The EMR_SETWINDOWEXTEX record defines the window extent. + */ + public static class EmfSetWindowExtEx extends HwmfWindowing.WmfSetWindowExt implements HemfRecord { + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.setWindowExtEx; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + // cx (4 bytes): A 32-bit unsigned integer that defines the x-coordinate of the point. + width = (int)leis.readUInt(); + // cy (4 bytes): A 32-bit unsigned integer that defines the y-coordinate of the point. + height = (int)leis.readUInt(); + + return 2*LittleEndianConsts.INT_SIZE; + } + } + + /** + * The EMR_SETWINDOWORGEX record defines the window origin. + */ + public static class EmfSetWindowOrgEx extends HwmfWindowing.WmfSetWindowOrg implements HemfRecord { + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.setWindowOrgEx; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + // x (4 bytes): A 32-bit signed integer that defines the horizontal (x) coordinate of the point. + x = leis.readInt(); + // y (4 bytes): A 32-bit signed integer that defines the vertical (y) coordinate of the point. + y = leis.readInt(); + + return 2*LittleEndianConsts.INT_SIZE; + } + } + + /** + * The EMR_SETVIEWPORTEXTEX record defines the viewport extent. + */ + public static class EmfSetViewportExtEx extends HwmfWindowing.WmfSetViewportExt implements HemfRecord { + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.setViewportExtEx; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + // cx (4 bytes): A 32-bit unsigned integer that defines the x-coordinate of the point. + width = (int)leis.readUInt(); + // cy (4 bytes): A 32-bit unsigned integer that defines the y-coordinate of the point. + height = (int)leis.readUInt(); + + return 2*LittleEndianConsts.INT_SIZE; + } + } + + /** + * The EMR_SETVIEWPORTORGEX record defines the viewport origin. + */ + public static class EmfSetViewportOrgEx extends HwmfWindowing.WmfSetViewportOrg implements HemfRecord { + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.setViewportOrgEx; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + // x (4 bytes): A 32-bit signed integer that defines the horizontal (x) coordinate of the point. + x = leis.readInt(); + // y (4 bytes): A 32-bit signed integer that defines the vertical (y) coordinate of the point. + y = leis.readInt(); + + return 2*LittleEndianConsts.INT_SIZE; + } + } + + /** + * The EMR_OFFSETCLIPRGN record moves the current clipping region in the playback device context + * by the specified offsets. + */ + public static class EmfSetOffsetClipRgn extends HwmfWindowing.WmfOffsetClipRgn implements HemfRecord { + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.setOffsetClipRgn; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + // x (4 bytes): A 32-bit signed integer that defines the horizontal (x) coordinate of the point. + xOffset = leis.readInt(); + // y (4 bytes): A 32-bit signed integer that defines the vertical (y) coordinate of the point. + yOffset = leis.readInt(); + + return 2*LittleEndianConsts.INT_SIZE; + } + } + + /** + * The EMR_EXCLUDECLIPRECT record specifies a new clipping region that consists of the existing + * clipping region minus the specified rectangle. + */ + public static class EmfSetExcludeClipRect extends HwmfWindowing.WmfExcludeClipRect implements HemfRecord { + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.setExcludeClipRect; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + return HemfDraw.readRectL(leis, bounds); + } + } + + /** + * The EMR_INTERSECTCLIPRECT record specifies a new clipping region from the intersection of the + * current clipping region and the specified rectangle. + */ + public static class EmfSetIntersectClipRect extends HwmfWindowing.WmfIntersectClipRect implements HemfRecord { + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.setIntersectClipRect; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + return HemfDraw.readRectL(leis, bounds); + } + } + + /** + * The EMR_SCALEVIEWPORTEXTEX record respecifies the viewport for a device context by using the + * ratios formed by the specified multiplicands and divisors. + */ + public static class EmfScaleViewportExtEx extends HwmfWindowing.WmfScaleViewportExt implements HemfRecord { + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.scaleViewportExtEx; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + xNum = leis.readInt(); + xDenom = leis.readInt(); + yNum = leis.readInt(); + yDenom = leis.readInt(); + return 4*LittleEndianConsts.INT_SIZE; + } + } + + /** + * The EMR_SCALEWINDOWEXTEX record respecifies the window for a playback device context by + * using the ratios formed by the specified multiplicands and divisors. + */ + public static class EmfScaleWindowExtEx extends HwmfWindowing.WmfScaleWindowExt implements HemfRecord { + @Override + public HemfRecordType getEmfRecordType() { + return HemfRecordType.scaleWindowExtEx; + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + xNum = leis.readInt(); + xDenom = leis.readInt(); + yNum = leis.readInt(); + yDenom = leis.readInt(); + return 4*LittleEndianConsts.INT_SIZE; + } + } +} diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/UnimplementedHemfRecord.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/UnimplementedHemfRecord.java new file mode 100644 index 0000000000..e66e2b40d8 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/UnimplementedHemfRecord.java @@ -0,0 +1,49 @@ +/* ==================================================================== + 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.emf; + + +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 getEmfRecordType() { + return HemfRecordType.getById(recordId); + } + + @Override + public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { + this.recordId = recordId; + long skipped = IOUtils.skipFully(leis, recordSize); + if (skipped < recordSize) { + throw new IOException("End of stream reached before record read"); + } + return skipped; + } +} diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusHeader.java b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusHeader.java new file mode 100644 index 0000000000..72a8d31528 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusHeader.java @@ -0,0 +1,87 @@ +/* ==================================================================== + 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.emfplus; + + +import java.io.IOException; + +import org.apache.poi.util.Internal; +import org.apache.poi.util.LittleEndianConsts; +import org.apache.poi.util.LittleEndianInputStream; + +@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 long init(LittleEndianInputStream leis, long dataSize, long recordId, int flags) throws IOException { + this.flags = flags; + version = leis.readUInt(); + + // verify MetafileSignature (20 bits) == 0xDBC01 and + // GraphicsVersion (12 bits) in (1 or 2) + assert((version & 0xFFFFFA00) == 0xDBC01000L && ((version & 0x3FF) == 1 || (version & 0x3FF) == 2)); + + emfPlusFlags = leis.readUInt(); + + logicalDpiX = leis.readUInt(); + logicalDpiY = leis.readUInt(); + return 4* LittleEndianConsts.INT_SIZE; + } + + 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 + + '}'; + } +} \ No newline at end of file diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRecord.java b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRecord.java new file mode 100644 index 0000000000..c53db9855b --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRecord.java @@ -0,0 +1,48 @@ +/* ==================================================================== + 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.emfplus; + + +import java.io.IOException; + +import org.apache.poi.hemf.record.emf.HemfRecordType; +import org.apache.poi.util.Internal; +import org.apache.poi.util.LittleEndianInputStream; + +@Internal +public interface HemfPlusRecord { + + HemfPlusRecordType getRecordType(); + + int getFlags(); + + /** + * Init record from stream + * + * @param leis the little endian input stream + * @param dataSize the size limit for this record + * @param recordId the id of the {@link HemfPlusRecordType} + * @param flags the record flags + * + * @return count of processed bytes + * + * @throws IOException when the inputstream is malformed + */ + long init(LittleEndianInputStream leis, long dataSize, long recordId, int flags) throws IOException; + +} diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRecordIterator.java b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRecordIterator.java new file mode 100644 index 0000000000..a3b56fa8ab --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRecordIterator.java @@ -0,0 +1,98 @@ +/* ==================================================================== + 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.emfplus; + +import java.io.IOException; +import java.util.Iterator; + +import org.apache.poi.util.LittleEndianInputStream; +import org.apache.poi.util.RecordFormatException; + +public class HemfPlusRecordIterator implements Iterator { + + private final LittleEndianInputStream leis; + private final int startIdx; + private final int limit; + private HemfPlusRecord currentRecord; + + public HemfPlusRecordIterator(LittleEndianInputStream leis) { + this(leis, -1); + } + + public HemfPlusRecordIterator(LittleEndianInputStream leis, int limit) { + this.leis = leis; + this.limit = limit; + startIdx = leis.getReadIndex(); + //queue the first non-header record + currentRecord = _next(); + } + + @Override + public boolean hasNext() { + return currentRecord != null; + } + + @Override + public HemfPlusRecord next() { + HemfPlusRecord toReturn = currentRecord; + final boolean isEOF = (limit == -1 || leis.getReadIndex()-startIdx >= limit); + // (currentRecord instanceof HemfPlusMisc.EmfEof) + currentRecord = isEOF ? null : _next(); + return toReturn; + } + + private HemfPlusRecord _next() { + if (currentRecord != null && HemfPlusRecordType.eof == currentRecord.getRecordType()) { + return null; + } + // A 16-bit unsigned integer that identifies this record type + int recordId = leis.readUShort(); + // A 16-bit unsigned integer that provides information about how the operation is + // to be performed, and about the structure of the record. + int flags = leis.readUShort(); + // A 32-bit unsigned integer that specifies the 32-bit-aligned size of the entire + // record in bytes, including the 12-byte record header and record-specific data. + int recordSize = (int)leis.readUInt(); + // A 32-bit unsigned integer that specifies the 32-bit-aligned number of bytes of data + // in the record-specific data that follows. This number does not include the size of + // the invariant part of this record. + int dataSize = (int)leis.readUInt(); + + HemfPlusRecordType type = HemfPlusRecordType.getById(recordId); + if (type == null) { + throw new RecordFormatException("Undefined record of type:"+recordId); + } + final HemfPlusRecord record = type.constructor.get(); + + try { + long readBytes = record.init(leis, dataSize, recordId, flags); + assert (readBytes <= recordSize-12); + leis.skipFully((int)(recordSize-12-readBytes)); + } catch (IOException e) { + throw new RecordFormatException(e); + } + + return record; + } + + @Override + public void remove() { + throw new UnsupportedOperationException("Remove not supported"); + } + +} diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRecordType.java b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRecordType.java new file mode 100644 index 0000000000..2fc1926ff6 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRecordType.java @@ -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.record.emfplus; + +import java.util.function.Supplier; + +import org.apache.poi.util.Internal; + +@Internal +public enum HemfPlusRecordType { + header(0x4001, HemfPlusHeader::new), + eof(0x4002, UnimplementedHemfPlusRecord::new), + comment(0x4003, UnimplementedHemfPlusRecord::new), + getDC(0x4004, UnimplementedHemfPlusRecord::new), + multiFormatStart(0x4005, UnimplementedHemfPlusRecord::new), + multiFormatSection(0x4006, UnimplementedHemfPlusRecord::new), + multiFormatEnd(0x4007, UnimplementedHemfPlusRecord::new), + object(0x4008, UnimplementedHemfPlusRecord::new), + clear(0x4009, UnimplementedHemfPlusRecord::new), + fillRects(0x400A, UnimplementedHemfPlusRecord::new), + drawRects(0x400B, UnimplementedHemfPlusRecord::new), + fillPolygon(0x400C, UnimplementedHemfPlusRecord::new), + drawLines(0x400D, UnimplementedHemfPlusRecord::new), + fillEllipse(0x400E, UnimplementedHemfPlusRecord::new), + drawEllipse(0x400F, UnimplementedHemfPlusRecord::new), + fillPie(0x4010, UnimplementedHemfPlusRecord::new), + drawPie(0x4011, UnimplementedHemfPlusRecord::new), + drawArc(0x4012, UnimplementedHemfPlusRecord::new), + fillRegion(0x4013, UnimplementedHemfPlusRecord::new), + fillPath(0x4014, UnimplementedHemfPlusRecord::new), + drawPath(0x4015, UnimplementedHemfPlusRecord::new), + fillClosedCurve(0x4016, UnimplementedHemfPlusRecord::new), + drawClosedCurve(0x4017, UnimplementedHemfPlusRecord::new), + drawCurve(0x4018, UnimplementedHemfPlusRecord::new), + drawBeziers(0x4019, UnimplementedHemfPlusRecord::new), + drawImage(0x401A, UnimplementedHemfPlusRecord::new), + drawImagePoints(0x401B, UnimplementedHemfPlusRecord::new), + drawString(0x401C, UnimplementedHemfPlusRecord::new), + setRenderingOrigin(0x401D, UnimplementedHemfPlusRecord::new), + setAntiAliasMode(0x401E, UnimplementedHemfPlusRecord::new), + setTextRenderingHint(0x401F, UnimplementedHemfPlusRecord::new), + setTextContrast(0x4020, UnimplementedHemfPlusRecord::new), + setInterpolationMode(0x4021, UnimplementedHemfPlusRecord::new), + setPixelOffsetMode(0x4022, UnimplementedHemfPlusRecord::new), + setComositingMode(0x4023, UnimplementedHemfPlusRecord::new), + setCompositingQuality(0x4024, UnimplementedHemfPlusRecord::new), + save(0x4025, UnimplementedHemfPlusRecord::new), + restore(0x4026, UnimplementedHemfPlusRecord::new), + beginContainer(0x4027, UnimplementedHemfPlusRecord::new), + beginContainerNoParams(0x428, UnimplementedHemfPlusRecord::new), + endContainer(0x4029, UnimplementedHemfPlusRecord::new), + setWorldTransform(0x402A, UnimplementedHemfPlusRecord::new), + resetWorldTransform(0x402B, UnimplementedHemfPlusRecord::new), + multiplyWorldTransform(0x402C, UnimplementedHemfPlusRecord::new), + translateWorldTransform(0x402D, UnimplementedHemfPlusRecord::new), + scaleWorldTransform(0x402E, UnimplementedHemfPlusRecord::new), + rotateWorldTransform(0x402F, UnimplementedHemfPlusRecord::new), + setPageTransform(0x4030, UnimplementedHemfPlusRecord::new), + resetClip(0x4031, UnimplementedHemfPlusRecord::new), + setClipRect(0x4032, UnimplementedHemfPlusRecord::new), + setClipRegion(0x4033, UnimplementedHemfPlusRecord::new), + setClipPath(0x4034, UnimplementedHemfPlusRecord::new), + offsetClip(0x4035, UnimplementedHemfPlusRecord::new), + drawDriverstring(0x4036, UnimplementedHemfPlusRecord::new), + strokeFillPath(0x4037, UnimplementedHemfPlusRecord::new), + serializableObject(0x4038, UnimplementedHemfPlusRecord::new), + setTSGraphics(0x4039, UnimplementedHemfPlusRecord::new), + setTSClip(0x403A, UnimplementedHemfPlusRecord::new); + + + public final long id; + public final Supplier constructor; + + HemfPlusRecordType(long id, Supplier constructor) { + this.id = id; + this.constructor = constructor; + } + + public static HemfPlusRecordType getById(long id) { + for (HemfPlusRecordType wrt : values()) { + if (wrt.id == id) return wrt; + } + return null; + } +} diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/UnimplementedHemfPlusRecord.java b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/UnimplementedHemfPlusRecord.java new file mode 100644 index 0000000000..21d0e14424 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/UnimplementedHemfPlusRecord.java @@ -0,0 +1,59 @@ +/* ==================================================================== + 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.emfplus; + + +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 UnimplementedHemfPlusRecord implements HemfPlusRecord { + + private static final int MAX_RECORD_LENGTH = 1_000_000; + + private long recordId; + private int flags; + private byte[] recordBytes; + + @Override + public HemfPlusRecordType getRecordType() { + return HemfPlusRecordType.getById(recordId); + } + + @Override + public int getFlags() { + return flags; + } + + @Override + public long init(LittleEndianInputStream leis, long dataSize, long recordId, int flags) throws IOException { + this.recordId = recordId; + this.flags = flags; + recordBytes = IOUtils.safelyAllocate(dataSize, MAX_RECORD_LENGTH); + leis.readFully(recordBytes); + return recordBytes.length; + } + + public byte[] getRecordBytes() { + //should probably defensively return a copy. + return recordBytes; + } +} \ No newline at end of file diff --git a/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java b/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java new file mode 100644 index 0000000000..40b04d716a --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java @@ -0,0 +1,77 @@ +/* ==================================================================== + 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.usermodel; + + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Spliterator; +import java.util.function.Consumer; + +import org.apache.poi.hemf.record.emf.HemfHeader; +import org.apache.poi.hemf.record.emf.HemfRecord; +import org.apache.poi.hemf.record.emf.HemfRecordIterator; +import org.apache.poi.util.Internal; +import org.apache.poi.util.LittleEndianInputStream; + +/** + * Read-only EMF extractor. Lots remain + */ +@Internal +public class HemfPicture implements Iterable { + + private final LittleEndianInputStream stream; + private final List records = new ArrayList<>(); + + public HemfPicture(InputStream is) throws IOException { + this(new LittleEndianInputStream(is)); + } + + public HemfPicture(LittleEndianInputStream is) throws IOException { + stream = is; + } + + public HemfHeader getHeader() { + return (HemfHeader)getRecords().get(0); + } + + public List getRecords() { + if (records.isEmpty()) { + new HemfRecordIterator(stream).forEachRemaining(records::add); + } + return records; + } + + @Override + public Iterator iterator() { + return getRecords().iterator(); + } + + @Override + public Spliterator spliterator() { + return getRecords().spliterator(); + } + + @Override + public void forEach(Consumer action) { + getRecords().forEach(action); + } +} diff --git a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java index bd8c9ff9a9..cdbfefe71e 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java @@ -43,32 +43,48 @@ public class HwmfDrawProperties { private final Rectangle2D window; private Rectangle2D viewport; private final Point2D location; - private HwmfMapMode mapMode = HwmfMapMode.MM_ANISOTROPIC; - private HwmfColorRef backgroundColor = new HwmfColorRef(Color.BLACK); - private HwmfBrushStyle brushStyle = HwmfBrushStyle.BS_SOLID; - private HwmfColorRef brushColor = new HwmfColorRef(Color.BLACK); - private HwmfHatchStyle brushHatch = HwmfHatchStyle.HS_HORIZONTAL; + private HwmfMapMode mapMode; + private HwmfColorRef backgroundColor; + private HwmfBrushStyle brushStyle; + private HwmfColorRef brushColor; + private HwmfHatchStyle brushHatch; private BufferedImage brushBitmap; - private double penWidth = 1; - private HwmfPenStyle penStyle = HwmfPenStyle.valueOf(0); - private HwmfColorRef penColor = new HwmfColorRef(Color.BLACK); - private double penMiterLimit = 10; - private HwmfBkMode bkMode = HwmfBkMode.OPAQUE; - private HwmfPolyfillMode polyfillMode = HwmfPolyfillMode.WINDING; + private double penWidth; + private HwmfPenStyle penStyle; + private HwmfColorRef penColor; + private double penMiterLimit; + private HwmfBkMode bkMode; + private HwmfPolyfillMode polyfillMode; private Shape region; private List palette; private int paletteOffset; private HwmfFont font; - private HwmfColorRef textColor = new HwmfColorRef(Color.BLACK); - private HwmfTextAlignment textAlignLatin = HwmfTextAlignment.LEFT; - private HwmfTextVerticalAlignment textVAlignLatin = HwmfTextVerticalAlignment.TOP; - private HwmfTextAlignment textAlignAsian = HwmfTextAlignment.RIGHT; - private HwmfTextVerticalAlignment textVAlignAsian = HwmfTextVerticalAlignment.TOP; + private HwmfColorRef textColor; + private HwmfTextAlignment textAlignLatin; + private HwmfTextVerticalAlignment textVAlignLatin; + private HwmfTextAlignment textAlignAsian; + private HwmfTextVerticalAlignment textVAlignAsian; public HwmfDrawProperties() { window = new Rectangle2D.Double(0, 0, 1, 1); viewport = null; location = new Point2D.Double(0,0); + mapMode = HwmfMapMode.MM_ANISOTROPIC; + backgroundColor = new HwmfColorRef(Color.BLACK); + brushStyle = HwmfBrushStyle.BS_SOLID; + brushColor = new HwmfColorRef(Color.BLACK); + brushHatch = HwmfHatchStyle.HS_HORIZONTAL; + penWidth = 1; + penStyle = HwmfPenStyle.valueOf(0); + penColor = new HwmfColorRef(Color.BLACK); + penMiterLimit = 10; + bkMode = HwmfBkMode.OPAQUE; + polyfillMode = HwmfPolyfillMode.WINDING; + textColor = new HwmfColorRef(Color.BLACK); + textAlignLatin = HwmfTextAlignment.LEFT; + textVAlignLatin = HwmfTextVerticalAlignment.TOP; + textAlignAsian = HwmfTextAlignment.RIGHT; + textVAlignAsian = HwmfTextVerticalAlignment.TOP; } public HwmfDrawProperties(HwmfDrawProperties other) { @@ -86,7 +102,7 @@ public class HwmfDrawProperties { WritableRaster raster = other.brushBitmap.copyData(null); this.brushBitmap = new BufferedImage(cm, raster, isAlphaPremultiplied, null); } - this.penWidth = 1; + this.penWidth = other.penWidth; this.penStyle = (other.penStyle == null) ? null : other.penStyle.clone(); this.penColor = (other.penColor == null) ? null : other.penColor.clone(); this.penMiterLimit = other.penMiterLimit; @@ -101,6 +117,10 @@ public class HwmfDrawProperties { this.paletteOffset = other.paletteOffset; this.font = other.font; this.textColor = (other.textColor == null) ? null : other.textColor.clone(); + this.textAlignLatin = other.textAlignLatin; + this.textVAlignLatin = other.textVAlignLatin; + this.textAlignAsian = other.textAlignAsian; + this.textVAlignAsian = other.textVAlignAsian; } public void setViewportExt(double width, double height) { @@ -149,6 +169,10 @@ public class HwmfDrawProperties { location.setLocation(x, y); } + public void setLocation(Point2D point) { + location.setLocation(point); + } + public Point2D getLocation() { return (Point2D)location.clone(); } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBrushStyle.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBrushStyle.java index 2e8122fb15..903ba592ac 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBrushStyle.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBrushStyle.java @@ -71,7 +71,7 @@ public enum HwmfBrushStyle { this.flag = flag; } - static HwmfBrushStyle valueOf(int flag) { + public static HwmfBrushStyle valueOf(int flag) { for (HwmfBrushStyle bs : values()) { if (bs.flag == flag) return bs; } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java index 48bcc60b44..fb78158a9d 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java @@ -41,31 +41,21 @@ public class HwmfDraw { */ public static class WmfMoveTo implements HwmfRecord { - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units. - */ - private int y; - - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units. - */ - private int x; + protected final Point2D point = new Point2D.Double(); @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.moveTo; } @Override public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { - y = leis.readShort(); - x = leis.readShort(); - return 2*LittleEndianConsts.SHORT_SIZE; + return readPointS(leis, point); } @Override public void draw(HwmfGraphics ctx) { - ctx.getProperties().setLocation(x, y); + ctx.getProperties().setLocation(point); } } @@ -75,36 +65,24 @@ public class HwmfDraw { */ public static class WmfLineTo implements HwmfRecord { - /** - * A 16-bit signed integer that defines the vertical component of the drawing - * destination position, in logical units. - */ - private int y; - - /** - * A 16-bit signed integer that defines the horizontal component of the drawing - * destination position, in logical units. - */ - private int x; + protected final Point2D point = new Point2D.Double(); @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.lineTo; } @Override public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { - y = leis.readShort(); - x = leis.readShort(); - return 2*LittleEndianConsts.SHORT_SIZE; + return readPointS(leis, point); } @Override public void draw(HwmfGraphics ctx) { Point2D start = ctx.getProperties().getLocation(); - Line2D line = new Line2D.Double(start.getX(), start.getY(), x, y); + Line2D line = new Line2D.Double(start, point); ctx.draw(line); - ctx.getProperties().setLocation(x, y); + ctx.getProperties().setLocation(point); } } @@ -115,10 +93,10 @@ public class HwmfDraw { */ public static class WmfPolygon implements HwmfRecord { - private Path2D poly = new Path2D.Double(); + protected Path2D poly = new Path2D.Double(); @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.polygon; } @@ -146,16 +124,26 @@ public class HwmfDraw { @Override public void draw(HwmfGraphics ctx) { - Path2D shape = getShape(); -// shape.closePath(); - Path2D p = (Path2D)shape.clone(); + Path2D p = getShape(ctx); + // don't close the path p.setWindingRule(getWindingRule(ctx)); - ctx.fill(p); + if (isFill()) { + ctx.fill(p); + } else { + ctx.draw(p); + } } - protected Path2D getShape() { + protected Path2D getShape(HwmfGraphics ctx) { return (Path2D)poly.clone(); } + + /** + * @return true, if the shape should be filled + */ + protected boolean isFill() { + return true; + } } /** @@ -165,16 +153,13 @@ public class HwmfDraw { public static class WmfPolyline extends WmfPolygon { @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.polyline; } @Override - public void draw(HwmfGraphics ctx) { - Path2D shape = getShape(); - Path2D p = (Path2D)shape.clone(); - p.setWindingRule(getWindingRule(ctx)); - ctx.draw(p); + protected boolean isFill() { + return false; } } @@ -184,48 +169,21 @@ public class HwmfDraw { * are defined in the playback device context. */ public static class WmfEllipse implements HwmfRecord { - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of - * the lower-right corner of the bounding rectangle. - */ - private int bottomRect; - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of - * the lower-right corner of the bounding rectangle. - */ - private int rightRect; - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of the - * upper-left corner of the bounding rectangle. - */ - private int topRect; - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of - * the upper-left corner of the bounding rectangle. - */ - private int leftRect; + protected final Rectangle2D bounds = new Rectangle2D.Double(); @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.ellipse; } @Override public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { - bottomRect = leis.readShort(); - rightRect = leis.readShort(); - topRect = leis.readShort(); - leftRect = leis.readShort(); - return 4*LittleEndianConsts.SHORT_SIZE; + return readBounds(leis, bounds); } @Override public void draw(HwmfGraphics ctx) { - int x = Math.min(leftRect, rightRect); - int y = Math.min(topRect, bottomRect); - int w = Math.abs(leftRect - rightRect - 1); - int h = Math.abs(topRect - bottomRect - 1); - Shape s = new Ellipse2D.Double(x, y, w, h); + Shape s = new Ellipse2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight()); ctx.fill(s); } } @@ -239,25 +197,25 @@ public class HwmfDraw { * A 16-bit unsigned integer used to index into the WMF Object Table to get * the region to be framed. */ - private int regionIndex; + protected int regionIndex; /** * A 16-bit unsigned integer used to index into the WMF Object Table to get the * Brush to use for filling the region. */ - private int brushIndex; + protected int brushIndex; /** * A 16-bit signed integer that defines the height, in logical units, of the * region frame. */ - private int height; + protected int height; /** * A 16-bit signed integer that defines the width, in logical units, of the * region frame. */ - private int width; + protected int width; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.frameRegion; } @@ -293,10 +251,10 @@ public class HwmfDraw { */ public static class WmfPolyPolygon implements HwmfRecord { - private List polyList = new ArrayList<>(); + protected List polyList = new ArrayList<>(); @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.polyPolygon; } @@ -361,7 +319,20 @@ public class HwmfDraw { area.exclusiveOr(newArea); } } - ctx.fill(area); + + if (isFill()) { + ctx.fill(area); + } else { + ctx.draw(area); + } + } + + + /** + * @return true, if the shape should be filled + */ + protected boolean isFill() { + return true; } } @@ -370,92 +341,50 @@ public class HwmfDraw { * filled by using the brush that are defined in the playback device context. */ public static class WmfRectangle implements HwmfRecord { - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of - * the lower-right corner of the rectangle. - */ - private int bottomRect; - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of - * the lower-right corner of the rectangle. - */ - private int rightRect; - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of the - * upper-left corner of the rectangle. - */ - private int topRect; - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of - * the upper-left corner of the rectangle. - */ - private int leftRect; + protected final Rectangle2D bounds = new Rectangle2D.Double(); @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.frameRegion; } @Override public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { - bottomRect = leis.readShort(); - rightRect = leis.readShort(); - topRect = leis.readShort(); - leftRect = leis.readShort(); - return 4*LittleEndianConsts.SHORT_SIZE; + return readBounds(leis, bounds); } @Override public void draw(HwmfGraphics ctx) { - int x = Math.min(leftRect, rightRect); - int y = Math.min(topRect, bottomRect); - int w = Math.abs(leftRect - rightRect - 1); - int h = Math.abs(topRect - bottomRect - 1); - Shape s = new Rectangle2D.Double(x, y, w, h); - ctx.fill(s); + ctx.fill(bounds); } } /** - * The META_RECTANGLE record paints a rectangle. The rectangle is outlined by using the pen and - * filled by using the brush that are defined in the playback device context. + * The META_SETPIXEL record sets the pixel at the specified coordinates to the specified color. */ public static class WmfSetPixel implements HwmfRecord { /** * A ColorRef Object that defines the color value. */ - HwmfColorRef colorRef; + protected final HwmfColorRef colorRef = new HwmfColorRef(); - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of the point - * to be set. - */ - private int y; - - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of the point - * to be set. - */ - private int x; + protected final Point2D point = new Point2D.Double(); @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.setPixel; } @Override public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { - colorRef = new HwmfColorRef(); int size = colorRef.init(leis); - y = leis.readShort(); - x = leis.readShort(); - return 2*LittleEndianConsts.SHORT_SIZE+size; + return size+ readPointS(leis, point); } @Override public void draw(HwmfGraphics ctx) { - Shape s = new Rectangle2D.Double(x, y, 1, 1); + Shape s = new Rectangle2D.Double(point.getX(), point.getY(), 1, 1); ctx.fill(s); } } @@ -469,41 +398,19 @@ public class HwmfDraw { * A 16-bit signed integer that defines the height, in logical coordinates, of the * ellipse used to draw the rounded corners. */ - private int height; + protected int height; /** * A 16-bit signed integer that defines the width, in logical coordinates, of the * ellipse used to draw the rounded corners. */ - private int width; + protected int width; - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of - * the lower-right corner of the rectangle. - */ - private int bottomRect; - - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of - * the lower-right corner of the rectangle. - */ - private int rightRect; - - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of the - * upper-left corner of the rectangle. - */ - private int topRect; - - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of - * the upper-left corner of the rectangle. - */ - private int leftRect; + protected final Rectangle2D bounds = new Rectangle2D.Double(); @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.roundRect; } @@ -511,20 +418,12 @@ public class HwmfDraw { public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { height = leis.readShort(); width = leis.readShort(); - bottomRect = leis.readShort(); - rightRect = leis.readShort(); - topRect = leis.readShort(); - leftRect = leis.readShort(); - return 6*LittleEndianConsts.SHORT_SIZE; + return 2*LittleEndianConsts.SHORT_SIZE+readBounds(leis, bounds); } @Override public void draw(HwmfGraphics ctx) { - int x = Math.min(leftRect, rightRect); - int y = Math.min(topRect, bottomRect); - int w = Math.abs(leftRect - rightRect - 1); - int h = Math.abs(topRect - bottomRect - 1); - Shape s = new RoundRectangle2D.Double(x, y, w, h, width, height); + Shape s = new RoundRectangle2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), width, height); ctx.fill(s); } } @@ -534,73 +433,34 @@ public class HwmfDraw { * The META_ARC record draws an elliptical arc. */ public static class WmfArc implements HwmfRecord { - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of - * the ending point of the radial line defining the ending point of the arc. - */ - private int yEndArc; - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of - * the ending point of the radial line defining the ending point of the arc. - */ - private int xEndArc; - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of - * the ending point of the radial line defining the starting point of the arc. - */ - private int yStartArc; - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of - * the ending point of the radial line defining the starting point of the arc. - */ - private int xStartArc; - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of - * the lower-right corner of the bounding rectangle. - */ - private int bottomRect; - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of - * the lower-right corner of the bounding rectangle. - */ - private int rightRect; - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of the - * upper-left corner of the bounding rectangle. - */ - private int topRect; - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of - * the upper-left corner of the bounding rectangle. - */ - private int leftRect; + /** starting point of the arc */ + protected final Point2D startPoint = new Point2D.Double(); + + /** ending point of the arc */ + protected final Point2D endPoint = new Point2D.Double(); + + /** the bounding rectangle */ + protected final Rectangle2D bounds = new Rectangle2D.Double(); + @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.arc; } @Override public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { - yEndArc = leis.readShort(); - xEndArc = leis.readShort(); - yStartArc = leis.readShort(); - xStartArc = leis.readShort(); - bottomRect = leis.readShort(); - rightRect = leis.readShort(); - topRect = leis.readShort(); - leftRect = leis.readShort(); + readPointS(leis, endPoint); + readPointS(leis, startPoint); + readBounds(leis, bounds); + return 8*LittleEndianConsts.SHORT_SIZE; } @Override public void draw(HwmfGraphics ctx) { - int x = Math.min(leftRect, rightRect); - int y = Math.min(topRect, bottomRect); - int w = Math.abs(leftRect - rightRect - 1); - int h = Math.abs(topRect - bottomRect - 1); - double startAngle = Math.toDegrees(Math.atan2(-(yStartArc - (topRect + h / 2.)), xStartArc - (leftRect + w / 2.))); - double endAngle = Math.toDegrees(Math.atan2(-(yEndArc - (topRect + h / 2.)), xEndArc - (leftRect + w / 2.))); + double startAngle = Math.toDegrees(Math.atan2(-(startPoint.getY() - bounds.getCenterY()), startPoint.getX() - bounds.getCenterX())); + double endAngle = Math.toDegrees(Math.atan2(-(endPoint.getY() - bounds.getCenterY()), endPoint.getX() - bounds.getCenterX())); double arcAngle = (endAngle - startAngle) + (endAngle - startAngle > 0 ? 0 : 360); if (startAngle < 0) { startAngle += 360; @@ -608,7 +468,7 @@ public class HwmfDraw { boolean fillShape; int arcClosure; - switch (getRecordType()) { + switch (getWmfRecordType()) { default: case arc: arcClosure = Arc2D.OPEN; @@ -624,7 +484,7 @@ public class HwmfDraw { break; } - Shape s = new Arc2D.Double(x, y, w, h, startAngle, arcAngle, arcClosure); + Shape s = new Arc2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), startAngle, arcAngle, arcClosure); if (fillShape) { ctx.fill(s); } else { @@ -641,7 +501,7 @@ public class HwmfDraw { public static class WmfPie extends WmfArc { @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.pie; } } @@ -654,7 +514,7 @@ public class HwmfDraw { public static class WmfChord extends WmfArc { @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.chord; } } @@ -673,10 +533,10 @@ public class HwmfDraw { * A 16-bit unsigned integer used to index into the WMF Object Table to * get the object to be selected. */ - private int objectIndex; + protected int objectIndex; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.selectObject; } @@ -695,4 +555,50 @@ public class HwmfDraw { private static int getWindingRule(HwmfGraphics ctx) { return ctx.getProperties().getPolyfillMode().awtFlag; } - } + + static int readBounds(LittleEndianInputStream leis, Rectangle2D bounds) { + /** + * The 16-bit signed integers that defines the corners of the bounding rectangle. + */ + int bottom = leis.readShort(); + int right = leis.readShort(); + int top = leis.readShort(); + int left = leis.readShort(); + + int x = Math.min(left, right); + int y = Math.min(top, bottom); + int w = Math.abs(left - right - 1); + int h = Math.abs(top - bottom - 1); + + bounds.setRect(x, y, w, h); + + return 4 * LittleEndianConsts.SHORT_SIZE; + } + + static int readRectS(LittleEndianInputStream leis, Rectangle2D bounds) { + /** + * The 16-bit signed integers that defines the corners of the bounding rectangle. + */ + int left = leis.readShort(); + int top = leis.readShort(); + int right = leis.readShort(); + int bottom = leis.readShort(); + + int x = Math.min(left, right); + int y = Math.min(top, bottom); + int w = Math.abs(left - right - 1); + int h = Math.abs(top - bottom - 1); + + bounds.setRect(x, y, w, h); + + return 4 * LittleEndianConsts.SHORT_SIZE; + } + + static int readPointS(LittleEndianInputStream leis, Point2D point) { + /** a signed integer that defines the x/y-coordinate, in logical units. */ + int y = leis.readShort(); + int x = leis.readShort(); + point.setLocation(x, y); + return 2*LittleEndianConsts.SHORT_SIZE; + } +} diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfEscape.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfEscape.java index 6c7ef213b0..a1de4dca46 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfEscape.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfEscape.java @@ -185,7 +185,7 @@ public class HwmfEscape implements HwmfRecord { private byte escapeData[]; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.escape; } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java index ec4d070540..26aa55696b 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java @@ -17,8 +17,12 @@ package org.apache.poi.hwmf.record; +import static org.apache.poi.hwmf.record.HwmfDraw.readPointS; + import java.awt.Shape; import java.awt.geom.Path2D; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.io.IOException; @@ -62,7 +66,7 @@ public class HwmfFill { this.flag = flag; } - static ColorUsage valueOf(int flag) { + public static ColorUsage valueOf(int flag) { for (ColorUsage bs : values()) { if (bs.flag == flag) return bs; } @@ -80,16 +84,16 @@ public class HwmfFill { * A 16-bit unsigned integer used to index into the WMF Object Table to get * the region to be filled. */ - private int regionIndex; + protected int regionIndex; /** * A 16-bit unsigned integer used to index into the WMF Object Table to get the * brush to use for filling the region. */ - private int brushIndex; + protected int brushIndex; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.fillRegion; } @@ -125,7 +129,7 @@ public class HwmfFill { */ int regionIndex; - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.paintRegion; } @@ -155,31 +159,21 @@ public class HwmfFill { /** * A 32-bit ColorRef Object that defines the color value. */ - private HwmfColorRef colorRef; - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of the - * point where filling is to start. - */ - private int yStart; - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of the - * point where filling is to start. - */ - private int xStart; - - + protected final HwmfColorRef colorRef = new HwmfColorRef(); + + /** the point where filling is to start. */ + protected final Point2D start = new Point2D.Double(); + @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.floodFill; } @Override public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { - colorRef = new HwmfColorRef(); int size = colorRef.init(leis); - yStart = leis.readShort(); - xStart = leis.readShort(); - return size+2*LittleEndianConsts.SHORT_SIZE; + size += readPointS(leis, start); + return size; } @Override @@ -215,22 +209,22 @@ public class HwmfFill { this.awtFlag = awtFlag; } - static HwmfPolyfillMode valueOf(int wmfFlag) { + public static HwmfPolyfillMode valueOf(int wmfFlag) { for (HwmfPolyfillMode pm : values()) { if (pm.wmfFlag == wmfFlag) return pm; } return null; } } - + /** - * A 16-bit unsigned integer that defines polygon fill mode. + * An unsigned integer that defines polygon fill mode. * This MUST be one of the values: ALTERNATE = 0x0001, WINDING = 0x0002 */ - private HwmfPolyfillMode polyfillMode; + protected HwmfPolyfillMode polyfillMode; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.setPolyFillMode; } @@ -251,7 +245,7 @@ public class HwmfFill { * The META_EXTFLOODFILL record fills an area with the brush that is defined in * the playback device context. */ - public static class WmfExtFloodFill implements HwmfRecord { + public static class WmfExtFloodFill extends WmfFloodFill { /** * A 16-bit unsigned integer that defines the fill operation to be performed. This @@ -266,38 +260,17 @@ public class HwmfFill { * Filling continues outward in all directions as long as the color is encountered. * This style is useful for filling areas with multicolored boundaries. */ - private int mode; - - /** - * A 32-bit ColorRef Object that defines the color value. - */ - private HwmfColorRef colorRef; - - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of the point - * to be set. - */ - private int y; - - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of the point - * to be set. - */ - private int x; + protected int mode; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.extFloodFill; } @Override public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { mode = leis.readUShort(); - colorRef = new HwmfColorRef(); - int size = colorRef.init(leis); - y = leis.readShort(); - x = leis.readShort(); - return size+3*LittleEndianConsts.SHORT_SIZE; + return super.init(leis, recordSize, recordFunction)+LittleEndianConsts.SHORT_SIZE; } @Override @@ -318,7 +291,7 @@ public class HwmfFill { private int region; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.invertRegion; } @@ -348,30 +321,10 @@ public class HwmfFill { */ private HwmfTernaryRasterOp rasterOperation; - /** - * A 16-bit signed integer that defines the height, in logical units, of the rectangle. - */ - private int height; - - /** - * A 16-bit signed integer that defines the width, in logical units, of the rectangle. - */ - private int width; - - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of the - * upper-left corner of the rectangle to be filled. - */ - private int yLeft; - - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of the - * upper-left corner of the rectangle to be filled. - */ - private int xLeft; - + private final Rectangle2D bounds = new Rectangle2D.Double(); + @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.patBlt; } @@ -383,12 +336,7 @@ public class HwmfFill { rasterOperation = HwmfTernaryRasterOp.valueOf(rasterOpIndex); assert(rasterOpCode == rasterOperation.opCode); - height = leis.readShort(); - width = leis.readShort(); - yLeft = leis.readShort(); - xLeft = leis.readShort(); - - return 6*LittleEndianConsts.SHORT_SIZE; + return readBounds2(leis, bounds)+2*LittleEndianConsts.SHORT_SIZE; } @Override @@ -414,53 +362,22 @@ public class HwmfFill { * in the playback device context, and the destination pixels are to be combined to form the new * image. This code MUST be one of the values in the Ternary Raster Operation Enumeration */ - private HwmfTernaryRasterOp rasterOperation; - - /** - * A 16-bit signed integer that defines the height, in logical units, of the source rectangle. - */ - private int srcHeight; - /** - * A 16-bit signed integer that defines the width, in logical units, of the source rectangle. - */ - private int srcWidth; - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of the upper-left corner - * of the source rectangle. - */ - private int ySrc; - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of the upper-left corner - * of the source rectangle. - */ - private int xSrc; - /** - * A 16-bit signed integer that defines the height, in logical units, of the destination rectangle. - */ - private int destHeight; - /** - * A 16-bit signed integer that defines the width, in logical units, of the destination rectangle. - */ - private int destWidth; - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of the upper-left - * corner of the destination rectangle. - */ - private int yDest; - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of the upper-left - * corner of the destination rectangle. - */ - private int xDest; - + protected HwmfTernaryRasterOp rasterOperation; + + /** the source rectangle */ + protected final Rectangle2D srcBounds = new Rectangle2D.Double(); + + /** the destination rectangle */ + protected final Rectangle2D dstBounds = new Rectangle2D.Double(); + /** * A variable-sized Bitmap16 Object that defines source image content. * This object MUST be specified, even if the raster operation does not require a source. */ - HwmfBitmap16 target; + protected HwmfBitmap16 target; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.stretchBlt; } @@ -469,27 +386,23 @@ public class HwmfFill { public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { boolean hasBitmap = (recordSize > ((recordFunction >> 8) + 3)); - int size = 0; int rasterOpCode = leis.readUShort(); int rasterOpIndex = leis.readUShort(); rasterOperation = HwmfTernaryRasterOp.valueOf(rasterOpIndex); assert(rasterOpCode == rasterOperation.opCode); - srcHeight = leis.readShort(); - srcWidth = leis.readShort(); - ySrc = leis.readShort(); - xSrc = leis.readShort(); - size = 6*LittleEndianConsts.SHORT_SIZE; + int size = 2*LittleEndianConsts.SHORT_SIZE; + + size += readBounds2(leis, srcBounds); + if (!hasBitmap) { /*int reserved =*/ leis.readShort(); size += LittleEndianConsts.SHORT_SIZE; } - destHeight = leis.readShort(); - destWidth = leis.readShort(); - yDest = leis.readShort(); - xDest = leis.readShort(); - size += 4*LittleEndianConsts.SHORT_SIZE; + + size += readBounds2(leis, dstBounds); + if (hasBitmap) { target = new HwmfBitmap16(); size += target.init(leis); @@ -524,46 +437,13 @@ public class HwmfFill { * DIB contains explicit RGB values or indexes into a palette. */ private ColorUsage colorUsage; - /** - * A 16-bit signed integer that defines the height, in logical units, of the - * source rectangle. - */ - private int srcHeight; - /** - * A 16-bit signed integer that defines the width, in logical units, of the - * source rectangle. - */ - private int srcWidth; - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of the - * source rectangle. - */ - private int ySrc; - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of the - * source rectangle. - */ - private int xSrc; - /** - * A 16-bit signed integer that defines the height, in logical units, of the - * destination rectangle. - */ - private int destHeight; - /** - * A 16-bit signed integer that defines the width, in logical units, of the - * destination rectangle. - */ - private int destWidth; - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of the - * upper-left corner of the destination rectangle. - */ - private int yDst; - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of the - * upper-left corner of the destination rectangle. - */ - private int xDst; + + /** the source rectangle. */ + protected final Rectangle2D srcBounds = new Rectangle2D.Double(); + + /** the destination rectangle. */ + protected final Rectangle2D dstBounds = new Rectangle2D.Double(); + /** * A variable-sized DeviceIndependentBitmap Object (section 2.2.2.9) that is the * source of the color data. @@ -571,7 +451,7 @@ public class HwmfFill { private HwmfBitmapDib dib; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.stretchDib; } @@ -585,16 +465,12 @@ public class HwmfFill { assert(rasterOpCode == rasterOperation.opCode); colorUsage = ColorUsage.valueOf(leis.readUShort()); - srcHeight = leis.readShort(); - srcWidth = leis.readShort(); - ySrc = leis.readShort(); - xSrc = leis.readShort(); - destHeight = leis.readShort(); - destWidth = leis.readShort(); - yDst = leis.readShort(); - xDst = leis.readShort(); - - int size = 11*LittleEndianConsts.SHORT_SIZE; + + int size = 3*LittleEndianConsts.SHORT_SIZE; + + size += readBounds2(leis, srcBounds); + size += readBounds2(leis, dstBounds); + dib = new HwmfBitmapDib(); size += dib.init(leis, (int)(recordSize-6-size)); @@ -617,53 +493,10 @@ public class HwmfFill { } } - public static class WmfBitBlt implements HwmfRecord { - - /** - * A 32-bit unsigned integer that defines how the source pixels, the current brush in the playback - * device context, and the destination pixels are to be combined to form the new image. - */ - private HwmfTernaryRasterOp rasterOperation; - - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of the upper-left corner - of the source rectangle. - */ - private int ySrc; - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of the upper-left corner - of the source rectangle. - */ - private int xSrc; - /** - * A 16-bit signed integer that defines the height, in logical units, of the source and - destination rectangles. - */ - private int height; - /** - * A 16-bit signed integer that defines the width, in logical units, of the source and destination - rectangles. - */ - private int width; - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of the upper-left - corner of the destination rectangle. - */ - private int yDest; - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of the upper-left - corner of the destination rectangle. - */ - private int xDest; - - /** - * A variable-sized Bitmap16 Object that defines source image content. - * This object MUST be specified, even if the raster operation does not require a source. - */ - private HwmfBitmap16 target; + public static class WmfBitBlt extends WmfStretchBlt { @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.bitBlt; } @@ -671,40 +504,32 @@ public class HwmfFill { public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { boolean hasBitmap = (recordSize/2 != ((recordFunction >> 8) + 3)); - int size = 0; int rasterOpCode = leis.readUShort(); int rasterOpIndex = leis.readUShort(); rasterOperation = HwmfTernaryRasterOp.valueOf(rasterOpIndex); assert(rasterOpCode == rasterOperation.opCode); - ySrc = leis.readShort(); - xSrc = leis.readShort(); + int size = 2*LittleEndianConsts.SHORT_SIZE; + + final Point2D srcPnt = new Point2D.Double(); + size += readPointS(leis, srcPnt); - size = 4*LittleEndianConsts.SHORT_SIZE; - if (!hasBitmap) { /*int reserved =*/ leis.readShort(); size += LittleEndianConsts.SHORT_SIZE; } - - height = leis.readShort(); - width = leis.readShort(); - yDest = leis.readShort(); - xDest = leis.readShort(); - size += 4*LittleEndianConsts.SHORT_SIZE; + size += readBounds2(leis, dstBounds); + if (hasBitmap) { target = new HwmfBitmap16(); size += target.init(leis); } - - return size; - } - @Override - public void draw(HwmfGraphics ctx) { + srcBounds.setRect(srcPnt.getX(), srcPnt.getY(), dstBounds.getWidth(), dstBounds.getHeight()); + return size; } } @@ -729,36 +554,13 @@ public class HwmfFill { * A 16-bit unsigned integer that defines the starting scan line in the source. */ private int startScan; - /** - * A 16-bit unsigned integer that defines the y-coordinate, in logical units, of the - * source rectangle. - */ - private int yDib; - /** - * A 16-bit unsigned integer that defines the x-coordinate, in logical units, of the - * source rectangle. - */ - private int xDib; - /** - * A 16-bit unsigned integer that defines the height, in logical units, of the - * source and destination rectangles. - */ - private int height; - /** - * A 16-bit unsigned integer that defines the width, in logical units, of the - * source and destination rectangles. - */ - private int width; - /** - * A 16-bit unsigned integer that defines the y-coordinate, in logical units, of the - * upper-left corner of the destination rectangle. - */ - private int yDest; - /** - * A 16-bit unsigned integer that defines the x-coordinate, in logical units, of the - * upper-left corner of the destination rectangle. - */ - private int xDest; + + /** the source rectangle */ + protected final Rectangle2D srcBounds = new Rectangle2D.Double(); + + /** the destination rectangle, having the same dimension as the source rectangle */ + protected final Rectangle2D dstBounds = new Rectangle2D.Double(); + /** * A variable-sized DeviceIndependentBitmap Object that is the source of the color data. */ @@ -766,7 +568,7 @@ public class HwmfFill { @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.setDibToDev; } @@ -775,17 +577,19 @@ public class HwmfFill { colorUsage = ColorUsage.valueOf(leis.readUShort()); scanCount = leis.readUShort(); startScan = leis.readUShort(); - yDib = leis.readUShort(); - xDib = leis.readUShort(); - height = leis.readUShort(); - width = leis.readUShort(); - yDest = leis.readUShort(); - xDest = leis.readUShort(); - - int size = 9*LittleEndianConsts.SHORT_SIZE; + + int size = 3*LittleEndianConsts.SHORT_SIZE; + + final Point2D srcPnt = new Point2D.Double(); + size += readPointS(leis, srcPnt); + + size += readBounds2(leis, dstBounds); + dib = new HwmfBitmapDib(); size += dib.init(leis, (int)(recordSize-6-size)); - + + srcBounds.setRect(srcPnt.getX(), srcPnt.getY(), dstBounds.getWidth(), dstBounds.getHeight()); + return size; } @@ -806,52 +610,9 @@ public class HwmfFill { } - public static class WmfDibBitBlt implements HwmfRecord, HwmfImageRecord, HwmfObjectTableEntry { - - /** - * A 32-bit unsigned integer that defines how the source pixels, the current brush - * in the playback device context, and the destination pixels are to be combined to form the - * new image. This code MUST be one of the values in the Ternary Raster Operation Enumeration. - */ - HwmfTernaryRasterOp rasterOperation; - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of the source rectangle. - */ - private int ySrc; - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of the source rectangle. - */ - private int xSrc; - /** - * A 16-bit signed integer that defines the height, in logical units, of the source and - * destination rectangles. - */ - private int height; - /** - * A 16-bit signed integer that defines the width, in logical units, of the source and destination - * rectangles. - */ - private int width; - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of the upper-left - * corner of the destination rectangle. - */ - private int yDest; - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of the upper-left - * corner of the destination rectangle. - */ - private int xDest; - - /** - * A variable-sized DeviceIndependentBitmap Object that defines image content. - * This object MUST be specified, even if the raster operation does not require a source. - */ - private HwmfBitmapDib target; - - + public static class WmfDibBitBlt extends WmfDibStretchBlt { @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.dibBitBlt; } @@ -859,47 +620,31 @@ public class HwmfFill { public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { boolean hasBitmap = (recordSize/2 != ((recordFunction >> 8) + 3)); - int size = 0; int rasterOpCode = leis.readUShort(); int rasterOpIndex = leis.readUShort(); rasterOperation = HwmfTernaryRasterOp.valueOf(rasterOpIndex); assert(rasterOpCode == rasterOperation.opCode); - ySrc = leis.readShort(); - xSrc = leis.readShort(); - size = 4*LittleEndianConsts.SHORT_SIZE; + int size = 2*LittleEndianConsts.SHORT_SIZE; + + final Point2D srcPnt = new Point2D.Double(); + size += readPointS(leis, srcPnt); if (!hasBitmap) { /*int reserved =*/ leis.readShort(); size += LittleEndianConsts.SHORT_SIZE; } - height = leis.readShort(); - width = leis.readShort(); - yDest = leis.readShort(); - xDest = leis.readShort(); - - size += 4*LittleEndianConsts.SHORT_SIZE; + + size += readBounds2(leis, dstBounds); if (hasBitmap) { target = new HwmfBitmapDib(); size += target.init(leis, (int)(recordSize-6-size)); } - - return size; - } - @Override - public void draw(HwmfGraphics ctx) { - ctx.addObjectTableEntry(this); - } - - @Override - public void applyObject(HwmfGraphics ctx) { - - } + // the destination rectangle, having the same dimension as the source rectangle + srcBounds.setRect(srcPnt.getX(), srcPnt.getY(), dstBounds.getWidth(), dstBounds.getHeight()); - @Override - public BufferedImage getImage() { - return (target == null) ? null : target.getImage(); + return size; } } @@ -909,53 +654,22 @@ public class HwmfFill { * in the playback device context, and the destination pixels are to be combined to form the * new image. This code MUST be one of the values in the Ternary Raster Operation Enumeration. */ - private HwmfTernaryRasterOp rasterOperation; - /** - * A 16-bit signed integer that defines the height, in logical units, of the source rectangle. - */ - private int srcHeight; - /** - * A 16-bit signed integer that defines the width, in logical units, of the source rectangle. - */ - private int srcWidth; - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of the - * upper-left corner of the source rectangle. - */ - private int ySrc; - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of the - * upper-left corner of the source rectangle. - */ - private int xSrc; - /** - * A 16-bit signed integer that defines the height, in logical units, of the - * destination rectangle. - */ - private int destHeight; - /** - * A 16-bit signed integer that defines the width, in logical units, of the - * destination rectangle. - */ - private int destWidth; - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, - * of the upper-left corner of the destination rectangle. - */ - private int yDest; - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, - * of the upper-left corner of the destination rectangle. - */ - private int xDest; + protected HwmfTernaryRasterOp rasterOperation; + + /** the source rectangle */ + protected final Rectangle2D srcBounds = new Rectangle2D.Double(); + + /** the destination rectangle */ + protected final Rectangle2D dstBounds = new Rectangle2D.Double(); + /** * A variable-sized DeviceIndependentBitmap Object that defines image content. * This object MUST be specified, even if the raster operation does not require a source. */ - HwmfBitmapDib target; - + protected HwmfBitmapDib target; + @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.dibStretchBlt; } @@ -963,27 +677,21 @@ public class HwmfFill { public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { boolean hasBitmap = (recordSize > ((recordFunction >> 8) + 3)); - int size = 0; int rasterOpCode = leis.readUShort(); int rasterOpIndex = leis.readUShort(); rasterOperation = HwmfTernaryRasterOp.valueOf(rasterOpIndex); assert(rasterOpCode == rasterOperation.opCode); - srcHeight = leis.readShort(); - srcWidth = leis.readShort(); - ySrc = leis.readShort(); - xSrc = leis.readShort(); - size = 6*LittleEndianConsts.SHORT_SIZE; + int size = 2*LittleEndianConsts.SHORT_SIZE; + + size += readBounds2(leis, srcBounds); if (!hasBitmap) { /*int reserved =*/ leis.readShort(); size += LittleEndianConsts.SHORT_SIZE; } - destHeight = leis.readShort(); - destWidth = leis.readShort(); - yDest = leis.readShort(); - xDest = leis.readShort(); - size += 4*LittleEndianConsts.SHORT_SIZE; + + size += readBounds2(leis, dstBounds); if (hasBitmap) { target = new HwmfBitmapDib(); size += target.init(leis, (int)(recordSize-6-size)); @@ -996,15 +704,30 @@ public class HwmfFill { public void draw(HwmfGraphics ctx) { ctx.addObjectTableEntry(this); } - + @Override public void applyObject(HwmfGraphics ctx) { - + } @Override public BufferedImage getImage() { - return target.getImage(); + return (target == null) ? null : target.getImage(); } } + + static int readBounds2(LittleEndianInputStream leis, Rectangle2D bounds) { + /** + * The 16-bit signed integers that defines the corners of the bounding rectangle. + */ + int h = leis.readShort(); + int w = leis.readShort(); + int y = leis.readShort(); + int x = leis.readShort(); + + bounds.setRect(x, y, w, h); + + return 4 * LittleEndianConsts.SHORT_SIZE; + } + } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFont.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFont.java index f094ae1959..932358740f 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFont.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFont.java @@ -24,6 +24,8 @@ import org.apache.poi.common.usermodel.fonts.FontCharset; import org.apache.poi.common.usermodel.fonts.FontFamily; import org.apache.poi.common.usermodel.fonts.FontInfo; import org.apache.poi.common.usermodel.fonts.FontPitch; +import org.apache.poi.util.BitField; +import org.apache.poi.util.BitFieldFactory; import org.apache.poi.util.LittleEndianConsts; import org.apache.poi.util.LittleEndianInputStream; @@ -90,7 +92,7 @@ public class HwmfFont implements FontInfo { this.flag = flag; } - static WmfOutPrecision valueOf(int flag) { + public static WmfOutPrecision valueOf(int flag) { for (WmfOutPrecision op : values()) { if (op.flag == flag) { return op; @@ -104,22 +106,17 @@ public class HwmfFont implements FontInfo { * ClipPrecision Flags specify clipping precision, which defines how to clip characters that are * partially outside a clipping region. These flags can be combined to specify multiple options. */ - public enum WmfClipPrecision { + public static class WmfClipPrecision { - /** - * Specifies that default clipping MUST be used. - */ - CLIP_DEFAULT_PRECIS (0x00000000), + /** Specifies that default clipping MUST be used. */ + private static final BitField CLIP_DEFAULT_PRECIS = BitFieldFactory.getInstance(0x0000); - /** - * This value SHOULD NOT be used. - */ - CLIP_CHARACTER_PRECIS (0x00000001), - /** - * This value MAY be returned when enumerating rasterized, TrueType and vector fonts. - */ - CLIP_STROKE_PRECIS (0x00000002), + /** This value SHOULD NOT be used. */ + private static final BitField CLIP_CHARACTER_PRECIS = BitFieldFactory.getInstance(0x0001); + + /** This value MAY be returned when enumerating rasterized, TrueType and vector fonts. */ + private static final BitField CLIP_STROKE_PRECIS = BitFieldFactory.getInstance(0x0002); /** * This value is used to control font rotation, as follows: @@ -129,37 +126,25 @@ public class HwmfFont implements FontInfo { * If clear, device fonts SHOULD rotate counterclockwise, but the rotation of other fonts * SHOULD be determined by the orientation of the coordinate system. */ - CLIP_LH_ANGLES (0x00000010), + private static final BitField CLIP_LH_ANGLES = BitFieldFactory.getInstance(0x0010); - /** - * This value SHOULD NOT be used. - */ - CLIP_TT_ALWAYS (0x00000020), + /** This value SHOULD NOT be used. */ + private static final BitField CLIP_TT_ALWAYS = BitFieldFactory.getInstance(0x0020); - /** - * This value specifies that font association SHOULD< be turned off. - */ - CLIP_DFA_DISABLE (0x00000040), + /** This value specifies that font association SHOULD< be turned off. */ + private static final BitField CLIP_DFA_DISABLE = BitFieldFactory.getInstance(0x0040); /** * This value specifies that font embedding MUST be used to render document content; * embedded fonts are read-only. */ - CLIP_EMBEDDED (0x00000080); - + private static final BitField CLIP_EMBEDDED = BitFieldFactory.getInstance(0x0080); int flag; - WmfClipPrecision(int flag) { - this.flag = flag; - } - static WmfClipPrecision valueOf(int flag) { - for (WmfClipPrecision cp : values()) { - if (cp.flag == flag) { - return cp; - } - } - return null; + public int init(LittleEndianInputStream leis) { + flag = leis.readUByte(); + return LittleEndianConsts.BYTE_SIZE; } } @@ -210,7 +195,7 @@ public class HwmfFont implements FontInfo { this.flag = flag; } - static WmfFontQuality valueOf(int flag) { + public static WmfFontQuality valueOf(int flag) { for (WmfFontQuality fq : values()) { if (fq.flag == flag) { return fq; @@ -240,7 +225,7 @@ public class HwmfFont implements FontInfo { * For all height comparisons, the font mapper SHOULD find the largest physical * font that does not exceed the requested size. */ - int height; + protected int height; /** * A 16-bit signed integer that defines the average width, in logical units, of @@ -248,45 +233,45 @@ public class HwmfFont implements FontInfo { * against the digitization aspect ratio of the available fonts to find the closest match, * determined by the absolute value of the difference. */ - int width; + protected int width; /** * A 16-bit signed integer that defines the angle, in tenths of degrees, between the * escapement vector and the x-axis of the device. The escapement vector is parallel * to the base line of a row of text. */ - int escapement; + protected int escapement; /** * A 16-bit signed integer that defines the angle, in tenths of degrees, * between each character's base line and the x-axis of the device. */ - int orientation; + protected int orientation; /** * A 16-bit signed integer that defines the weight of the font in the range 0 * through 1000. For example, 400 is normal and 700 is bold. If this value is 0x0000, * a default weight SHOULD be used. */ - int weight; + protected int weight; /** * A 8-bit Boolean value that specifies the italic attribute of the font. * 0 = not italic / 1 = italic. */ - boolean italic; + protected boolean italic; /** * An 8-bit Boolean value that specifies the underline attribute of the font. * 0 = not underlined / 1 = underlined */ - boolean underline; + protected boolean underline; /** * An 8-bit Boolean value that specifies the strike out attribute of the font. * 0 = not striked out / 1 = striked out */ - boolean strikeOut; + protected boolean strikeOut; /** * An 8-bit unsigned integer that defines the character set. @@ -299,12 +284,12 @@ public class HwmfFont implements FontInfo { * If a typeface name in the FaceName field is specified, the CharSet value MUST match the * character set of that typeface. */ - FontCharset charSet; + protected FontCharset charSet; /** * An 8-bit unsigned integer that defines the output precision. */ - WmfOutPrecision outPrecision; + protected WmfOutPrecision outPrecision; /** * An 8-bit unsigned integer that defines the clipping precision. @@ -312,40 +297,40 @@ public class HwmfFont implements FontInfo { * * @see WmfClipPrecision */ - WmfClipPrecision clipPrecision; + protected final WmfClipPrecision clipPrecision = new WmfClipPrecision(); /** * An 8-bit unsigned integer that defines the output quality. */ - WmfFontQuality quality; + protected WmfFontQuality quality; /** * A PitchAndFamily object that defines the pitch and the family of the font. * Font families specify the look of fonts in a general way and are intended for * specifying fonts when the exact typeface wanted is not available. */ - int pitchAndFamily; + protected int pitchAndFamily; /** * Font families specify the look of fonts in a general way and are * intended for specifying fonts when the exact typeface wanted is not available. * (LSB 4 bits) */ - FontFamily family; + protected FontFamily family; /** * A property of a font that describes the pitch (MSB 2 bits) */ - FontPitch pitch; + protected FontPitch pitch; /** * A null-terminated string of 8-bit Latin-1 [ISO/IEC-8859-1] ANSI * characters that specifies the typeface name of the font. The length of this string MUST NOT * exceed 32 8-bit characters, including the terminating null. */ - String facename; + protected String facename; - public int init(LittleEndianInputStream leis) throws IOException { + public int init(LittleEndianInputStream leis, long recordSize) throws IOException { height = leis.readShort(); width = leis.readShort(); escapement = leis.readShort(); @@ -356,21 +341,17 @@ public class HwmfFont implements FontInfo { strikeOut = leis.readByte() != 0; charSet = FontCharset.valueOf(leis.readUByte()); outPrecision = WmfOutPrecision.valueOf(leis.readUByte()); - clipPrecision = WmfClipPrecision.valueOf(leis.readUByte()); + clipPrecision.init(leis); quality = WmfFontQuality.valueOf(leis.readUByte()); pitchAndFamily = leis.readUByte(); - - byte buf[] = new byte[32], b, readBytes = 0; - do { - if (readBytes == 32) { - throw new IOException("Font facename can't be determined."); - } - buf[readBytes++] = b = leis.readByte(); - } while (b != 0 && b != -1 && readBytes <= 32); - - facename = new String(buf, 0, readBytes-1, StandardCharsets.ISO_8859_1); - + StringBuilder sb = new StringBuilder(); + int readBytes = readString(leis, sb, 32); + if (readBytes == -1) { + throw new IOException("Font facename can't be determined."); + } + facename = sb.toString(); + return 5*LittleEndianConsts.SHORT_SIZE+8*LittleEndianConsts.BYTE_SIZE+readBytes; } @@ -471,4 +452,19 @@ public class HwmfFont implements FontInfo { public void setCharset(FontCharset charset) { throw new UnsupportedOperationException("setCharset not supported by HwmfFont."); } + + protected int readString(LittleEndianInputStream leis, StringBuilder sb, int limit) throws IOException { + byte buf[] = new byte[limit], b, readBytes = 0; + do { + if (readBytes == limit) { + return -1; + } + + buf[readBytes++] = b = leis.readByte(); + } while (b != 0 && b != -1 && readBytes <= limit); + + sb.append(new String(buf, 0, readBytes-1, StandardCharsets.ISO_8859_1)); + + return readBytes; + } } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfHatchStyle.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfHatchStyle.java index 02f896849a..e364d83b3b 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfHatchStyle.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfHatchStyle.java @@ -39,7 +39,7 @@ public enum HwmfHatchStyle { this.flag = flag; } - static HwmfHatchStyle valueOf(int flag) { + public static HwmfHatchStyle valueOf(int flag) { for (HwmfHatchStyle hs : values()) { if (hs.flag == flag) return hs; } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMapMode.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMapMode.java index f4f9a65c27..2e09606bdd 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMapMode.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMapMode.java @@ -105,7 +105,7 @@ public enum HwmfMapMode { this.scale = scale; } - static HwmfMapMode valueOf(int flag) { + public static HwmfMapMode valueOf(int flag) { for (HwmfMapMode mm : values()) { if (mm.flag == flag) return mm; } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java index f5ab077d22..631383d6e4 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java @@ -17,6 +17,7 @@ package org.apache.poi.hwmf.record; +import java.awt.geom.Dimension2D; import java.awt.image.BufferedImage; import java.io.IOException; @@ -24,6 +25,7 @@ import org.apache.poi.hwmf.draw.HwmfDrawProperties; import org.apache.poi.hwmf.draw.HwmfGraphics; import org.apache.poi.hwmf.record.HwmfFill.ColorUsage; import org.apache.poi.hwmf.record.HwmfFill.HwmfImageRecord; +import org.apache.poi.util.Dimension2DDouble; import org.apache.poi.util.LittleEndianConsts; import org.apache.poi.util.LittleEndianInputStream; @@ -34,7 +36,7 @@ public class HwmfMisc { */ public static class WmfSaveDc implements HwmfRecord { @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.saveDc; } @@ -53,7 +55,7 @@ public class HwmfMisc { * The META_SETRELABS record is reserved and not supported. */ public static class WmfSetRelabs implements HwmfRecord { - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.setRelabs; } @@ -81,7 +83,7 @@ public class HwmfMisc { private int nSavedDC; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.restoreDc; } @@ -106,7 +108,7 @@ public class HwmfMisc { private HwmfColorRef colorRef; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.setBkColor; } @@ -140,7 +142,7 @@ public class HwmfMisc { this.flag = flag; } - static HwmfBkMode valueOf(int flag) { + public static HwmfBkMode valueOf(int flag) { for (HwmfBkMode bs : values()) { if (bs.flag == flag) return bs; } @@ -148,9 +150,9 @@ public class HwmfMisc { } } - private HwmfBkMode bkMode; + protected HwmfBkMode bkMode; - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.setBkMode; } @@ -180,7 +182,7 @@ public class HwmfMisc { private int layout; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.setLayout; } @@ -205,10 +207,10 @@ public class HwmfMisc { */ public static class WmfSetMapMode implements HwmfRecord { - private HwmfMapMode mapMode; + protected HwmfMapMode mapMode; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.setMapMode; } @@ -234,12 +236,13 @@ public class HwmfMisc { /** * A 32-bit unsigned integer that defines whether the font mapper should attempt to * match a font's aspect ratio to the current device's aspect ratio. If bit 0 is - * set, the mapper selects only matching fonts. + * set, the font mapper SHOULD select only fonts that match the aspect ratio of the + * output device, as it is currently defined in the playback device context. */ private long mapperValues; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.setMapperFlags; } @@ -262,14 +265,11 @@ public class HwmfMisc { */ public static class WmfSetRop2 implements HwmfRecord { - /** - * A 16-bit unsigned integer that defines the foreground binary raster - * operation mixing mode - */ - private HwmfBinaryRasterOp drawMode; + /** An unsigned integer that defines the foreground binary raster operation mixing mode */ + protected HwmfBinaryRasterOp drawMode; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.setRop2; } @@ -291,24 +291,63 @@ public class HwmfMisc { */ public static class WmfSetStretchBltMode implements HwmfRecord { - /** - * A 16-bit unsigned integer that defines bitmap stretching mode. - * This MUST be one of the values: - * BLACKONWHITE = 0x0001, - * WHITEONBLACK = 0x0002, - * COLORONCOLOR = 0x0003, - * HALFTONE = 0x0004 - */ - private int setStretchBltMode; + public enum StretchBltMode { + /** + * Performs a Boolean AND operation by using the color values for the eliminated and existing pixels. + * If the bitmap is a monochrome bitmap, this mode preserves black pixels at the expense of white pixels. + * + * EMF name: STRETCH_ANDSCANS + */ + BLACKONWHITE(0x0001), + /** + * Performs a Boolean OR operation by using the color values for the eliminated and existing pixels. + * If the bitmap is a monochrome bitmap, this mode preserves white pixels at the expense of black pixels. + * + * EMF name: STRETCH_ORSCANS + */ + WHITEONBLACK(0x0002), + /** + * Deletes the pixels. This mode deletes all eliminated lines of pixels without trying + * to preserve their information. + * + * EMF name: STRETCH_DELETESCANS + */ + COLORONCOLOR(0x0003), + /** + * Maps pixels from the source rectangle into blocks of pixels in the destination rectangle. + * The average color over the destination block of pixels approximates the color of the source + * pixels. + * + * After setting the HALFTONE stretching mode, the brush origin MUST be set to avoid misalignment + * artifacts - in EMF this is done via EmfSetBrushOrgEx + * + * EMF name: STRETCH_HALFTONE + */ + HALFTONE(0x0004); + + public final int flag; + StretchBltMode(int flag) { + this.flag = flag; + } + + public static StretchBltMode valueOf(int flag) { + for (StretchBltMode bs : values()) { + if (bs.flag == flag) return bs; + } + return null; + } + } + + protected StretchBltMode stretchBltMode; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.setStretchBltMode; } @Override public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { - setStretchBltMode = leis.readUShort(); + stretchBltMode = StretchBltMode.valueOf(leis.readUShort()); return LittleEndianConsts.SHORT_SIZE; } @@ -341,7 +380,7 @@ public class HwmfMisc { private HwmfBitmap16 pattern16; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.dibCreatePatternBrush; } @@ -403,10 +442,10 @@ public class HwmfMisc { * A 16-bit unsigned integer used to index into the WMF Object Table to * get the object to be deleted. */ - private int objectIndex; + protected int objectIndex; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.deleteObject; } @@ -418,6 +457,12 @@ public class HwmfMisc { @Override public void draw(HwmfGraphics ctx) { + /* TODO: + * The object specified by this record MUST be deleted from the EMF Object Table. + * If the deleted object is currently selected in the playback device context, + * the default object for that graphics property MUST be restored. + */ + ctx.unsetObjectTableEntry(objectIndex); } } @@ -427,7 +472,7 @@ public class HwmfMisc { private HwmfBitmap16 pattern; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.createPatternBrush; } @@ -452,30 +497,28 @@ public class HwmfMisc { public static class WmfCreatePenIndirect implements HwmfRecord, HwmfObjectTableEntry { - private HwmfPenStyle penStyle; - /** - * A 32-bit PointS Object that specifies a point for the object dimensions. - * The x-coordinate is the pen width. The y-coordinate is ignored. - */ - private int xWidth; - @SuppressWarnings("unused") - private int yWidth; + protected HwmfPenStyle penStyle; + + protected final Dimension2D dimension = new Dimension2DDouble(); /** * A 32-bit ColorRef Object that specifies the pen color value. */ - private HwmfColorRef colorRef; + protected final HwmfColorRef colorRef = new HwmfColorRef(); @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.createPenIndirect; } @Override public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { penStyle = HwmfPenStyle.valueOf(leis.readUShort()); - xWidth = leis.readShort(); - yWidth = leis.readShort(); - colorRef = new HwmfColorRef(); + // A 32-bit PointS Object that specifies a point for the object dimensions. + // The x-coordinate is the pen width. The y-coordinate is ignored. + int xWidth = leis.readShort(); + int yWidth = leis.readShort(); + dimension.setSize(xWidth, yWidth); + int size = colorRef.init(leis); return size+3*LittleEndianConsts.SHORT_SIZE; } @@ -490,7 +533,7 @@ public class HwmfMisc { HwmfDrawProperties p = ctx.getProperties(); p.setPenStyle(penStyle); p.setPenColor(colorRef); - p.setPenWidth(xWidth); + p.setPenWidth(dimension.getWidth()); } } @@ -540,19 +583,14 @@ public class HwmfMisc { * */ public static class WmfCreateBrushIndirect implements HwmfRecord, HwmfObjectTableEntry { - private HwmfBrushStyle brushStyle; + protected HwmfBrushStyle brushStyle; - private HwmfColorRef colorRef; + protected HwmfColorRef colorRef; - /** - * A 16-bit field that specifies the brush hatch type. - * Its interpretation depends on the value of BrushStyle. - * - */ - private HwmfHatchStyle brushHatch; + protected HwmfHatchStyle brushHatch; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.createBrushIndirect; } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfPalette.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfPalette.java index cb80c454ee..cef75f698d 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfPalette.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfPalette.java @@ -39,12 +39,12 @@ public class HwmfPalette { private int values; private Color colorRef; - private PaletteEntry() { + public PaletteEntry() { this.values = PC_RESERVED.set(0); this.colorRef = Color.BLACK; } - private PaletteEntry(PaletteEntry other) { + public PaletteEntry(PaletteEntry other) { this.values = other.values; this.colorRef = other.colorRef; } @@ -100,19 +100,24 @@ public class HwmfPalette { * used with the META_SETPALENTRIES and META_ANIMATEPALETTE record types. * When used with META_CREATEPALETTE, it MUST be 0x0300 */ - private int start; + protected int start; - private List palette = new ArrayList<>(); + protected final List palette = new ArrayList<>(); @Override public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { start = leis.readUShort(); + int size = readPaletteEntries(leis, -1); + return size + LittleEndianConsts.SHORT_SIZE; + } + + protected int readPaletteEntries(LittleEndianInputStream leis, int nbrOfEntries) throws IOException { /** * NumberOfEntries (2 bytes): A 16-bit unsigned integer that defines the number of objects in * aPaletteEntries. */ - int numberOfEntries = leis.readUShort(); - int size = 2*LittleEndianConsts.SHORT_SIZE; + final int numberOfEntries = (nbrOfEntries > -1) ? nbrOfEntries : leis.readUShort(); + int size = (nbrOfEntries > -1) ? 0 : LittleEndianConsts.SHORT_SIZE; for (int i=0; i clazz; + public final Supplier constructor; - HwmfRecordType(int id, Class clazz) { + HwmfRecordType(int id, Supplier constructor) { this.id = id; - this.clazz = clazz; + this.constructor = constructor; } public static HwmfRecordType getById(int id) { diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java index 9c6ed1e7f4..787f444beb 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java @@ -17,8 +17,16 @@ package org.apache.poi.hwmf.record; +import static org.apache.poi.hwmf.record.HwmfDraw.readPointS; +import static org.apache.poi.hwmf.record.HwmfDraw.readRectS; + +import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; +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.hwmf.draw.HwmfDrawProperties; @@ -31,6 +39,7 @@ import org.apache.poi.util.LittleEndianConsts; import org.apache.poi.util.LittleEndianInputStream; import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogger; +import org.apache.poi.util.RecordFormatException; public class HwmfText { private static final POILogger logger = POILogFactory.getLogger(HwmfText.class); @@ -52,7 +61,7 @@ public class HwmfText { private int charExtra; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.setTextCharExtra; } @@ -76,7 +85,7 @@ public class HwmfText { private HwmfColorRef colorRef; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.setTextColor; } @@ -112,7 +121,7 @@ public class HwmfText { private int breakExtra; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.setBkColor; } @@ -159,7 +168,7 @@ public class HwmfText { private int xStart; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.textOut; } @@ -195,40 +204,47 @@ public class HwmfText { return ret; } } - - /** - * The META_EXTTEXTOUT record outputs text by using the font, background color, and text color that - * are defined in the playback device context. Optionally, dimensions can be provided for clipping, - * opaquing, or both. - */ - public static class WmfExtTextOut implements HwmfRecord { + public static class WmfExtTextOutOptions { /** - * Indicates that the background color that is defined in the playback device context + * Indicates that the background color that is defined in the playback device context * SHOULD be used to fill the rectangle. - */ + */ private static final BitField ETO_OPAQUE = BitFieldFactory.getInstance(0x0002); - + /** * Indicates that the text SHOULD be clipped to the rectangle. */ private static final BitField ETO_CLIPPED = BitFieldFactory.getInstance(0x0004); /** - * Indicates that the string to be output SHOULD NOT require further processing - * with respect to the placement of the characters, and an array of character - * placement values SHOULD be provided. This character placement process is + * Indicates that the string to be output SHOULD NOT require further processing + * with respect to the placement of the characters, and an array of character + * placement values SHOULD be provided. This character placement process is * useful for fonts in which diacritical characters affect character spacing. */ private static final BitField ETO_GLYPH_INDEX = BitFieldFactory.getInstance(0x0010); /** - * Indicates that the text MUST be laid out in right-to-left reading order, instead of - * the default left-to-right order. This SHOULD be applied only when the font that is + * Indicates that the text MUST be laid out in right-to-left reading order, instead of + * the default left-to-right order. This SHOULD be applied only when the font that is * defined in the playback device context is either Hebrew or Arabic. */ private static final BitField ETO_RTLREADING = BitFieldFactory.getInstance(0x0080); + /** + * This bit indicates that the record does not specify a bounding rectangle for the + * text output. + */ + private static final BitField ETO_NO_RECT = BitFieldFactory.getInstance(0x0100); + + /** + * This bit indicates that the codes for characters in an output text string are 8 bits, + * derived from the low bytes of 16-bit Unicode UTF16-LE character codes, in which + * the high byte is assumed to be 0. + */ + private static final BitField ETO_SMALL_CHARS = BitFieldFactory.getInstance(0x0200); + /** * Indicates that to display numbers, digits appropriate to the locale SHOULD be used. */ @@ -240,32 +256,58 @@ public class HwmfText { private static final BitField ETO_NUMERICSLATIN = BitFieldFactory.getInstance(0x0800); /** - * Indicates that both horizontal and vertical character displacement values + * This bit indicates that no special operating system processing for glyph placement + * should be performed on right-to-left strings; that is, all glyph positioning + * SHOULD be taken care of by drawing and state records in the metafile + */ + private static final BitField ETO_IGNORELANGUAGE = BitFieldFactory.getInstance(0x1000); + + /** + * Indicates that both horizontal and vertical character displacement values * SHOULD be provided. */ private static final BitField ETO_PDY = BitFieldFactory.getInstance(0x2000); + /** This bit is reserved and SHOULD NOT be used. */ + private static final BitField ETO_REVERSE_INDEX_MAP = BitFieldFactory.getInstance(0x10000); + + protected int flag; + + public int init(LittleEndianInputStream leis) { + flag = leis.readUShort(); + return LittleEndianConsts.SHORT_SIZE; + } + + public boolean isOpaque() { + return ETO_OPAQUE.isSet(flag); + } + + public boolean isClipped() { + return ETO_CLIPPED.isSet(flag); + } + } + + /** + * The META_EXTTEXTOUT record outputs text by using the font, background color, and text color that + * are defined in the playback device context. Optionally, dimensions can be provided for clipping, + * opaquing, or both. + */ + public static class WmfExtTextOut implements HwmfRecord { /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, where the - text string is to be located. + * The location, in logical units, where the text string is to be placed. */ - private int y; + protected final Point2D reference = new Point2D.Double(); + /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, where the - text string is to be located. + * A 16-bit signed integer that defines the length of the string. */ - private int x; + protected int stringLength; /** - * A 16-bit signed integer that defines the length of the string. + * A 16-bit unsigned integer that defines the use of the application-defined + * rectangle. This member can be a combination of one or more values in the + * ExtTextOutOptions Flags (ETO_*) */ - private int stringLength; - - /** - * A 16-bit unsigned integer that defines the use of the application-defined - * rectangle. This member can be a combination of one or more values in the - * ExtTextOutOptions Flags (ETO_*) - */ - private int fwOpts; + protected final WmfExtTextOutOptions options; /** * An optional 8-byte Rect Object (section 2.2.2.18) that defines the * dimensions, in logical coordinates, of a rectangle that is used for clipping, opaquing, or both. @@ -274,14 +316,14 @@ public class HwmfText { * Each value is a 16-bit signed integer that defines the coordinate, in logical coordinates, of * the upper-left corner of the rectangle */ - private int left,top,right,bottom; + protected final Rectangle2D bounds = new Rectangle2D.Double(); /** * A variable-length string that specifies the text to be drawn. The string does * not need to be null-terminated, because StringLength specifies the length of the string. If * the length is odd, an extra byte is placed after it so that the following member (optional Dx) is * aligned on a 16-bit boundary. */ - private byte[] rawTextBytes; + protected byte[] rawTextBytes; /** * An optional array of 16-bit signed integers that indicate the distance between * origins of adjacent character cells. For example, Dx[i] logical units separate the origins of @@ -289,9 +331,17 @@ public class HwmfText { * number of values as there are characters in the string. */ private int dx[]; - + + public WmfExtTextOut() { + this(new WmfExtTextOutOptions()); + } + + protected WmfExtTextOut(WmfExtTextOutOptions options) { + this.options = options; + } + @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.extTextOut; } @@ -299,22 +349,17 @@ public class HwmfText { public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { // -6 bytes of record function and length header final int remainingRecordSize = (int)(recordSize-6); - - y = leis.readShort(); - x = leis.readShort(); + + int size = readPointS(leis, reference); + stringLength = leis.readShort(); - fwOpts = leis.readUShort(); - - int size = 4*LittleEndianConsts.SHORT_SIZE; - + size += LittleEndianConsts.SHORT_SIZE; + size += options.init(leis); + // Check if we have a rectangle - if ((ETO_OPAQUE.isSet(fwOpts) || ETO_CLIPPED.isSet(fwOpts)) && size+8<=remainingRecordSize) { - // the bounding rectangle is optional and only read when fwOpts are given - left = leis.readShort(); - top = leis.readShort(); - right = leis.readShort(); - bottom = leis.readShort(); - size += 4*LittleEndianConsts.SHORT_SIZE; + if ((options.isOpaque() || options.isClipped()) && size+8<=remainingRecordSize) { + // the bounding rectangle is optional and only read when options are given + size += readRectS(leis, bounds); } rawTextBytes = IOUtils.safelyAllocate(stringLength+(stringLength&1), MAX_RECORD_LENGTH); @@ -342,12 +387,46 @@ public class HwmfText { @Override public void draw(HwmfGraphics ctx) { - Rectangle2D bounds = new Rectangle2D.Double(x, y, 0, 0); + Rectangle2D bounds = new Rectangle2D.Double(reference.getX(), reference.getY(), 0, 0); ctx.drawString(getTextBytes(), bounds, dx); } - public String getText(Charset charset) { - return new String(getTextBytes(), charset); + + public String getText(Charset charset) throws IOException { + StringBuilder sb = new StringBuilder(); + try (Reader r = new InputStreamReader(new ByteArrayInputStream(rawTextBytes), charset)) { + for (int i = 0; i < stringLength; i++) { + sb.appendCodePoint(readCodePoint(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); + } + + public Point2D getReference() { + return reference; + } + + public Rectangle2D getBounds() { + return bounds; } /** @@ -482,10 +561,10 @@ public class HwmfText { * for text with a horizontal baseline, and VerticalTextAlignmentMode Flags * for text with a vertical baseline. */ - private int textAlignmentMode; + protected int textAlignmentMode; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.setTextAlign; } @@ -533,17 +612,24 @@ public class HwmfText { } public static class WmfCreateFontIndirect implements HwmfRecord, HwmfObjectTableEntry { - private HwmfFont font; - + protected final HwmfFont font; + + public WmfCreateFontIndirect() { + this(new HwmfFont()); + } + + protected WmfCreateFontIndirect(HwmfFont font) { + this.font = font; + } + @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.createFontIndirect; } @Override public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { - font = new HwmfFont(); - return font.init(leis); + return font.init(leis, recordSize); } @Override diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfWindowing.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfWindowing.java index 42913aad64..389cfbc7dd 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfWindowing.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfWindowing.java @@ -33,18 +33,14 @@ public class HwmfWindowing { */ public static class WmfSetViewportOrg implements HwmfRecord { - /** - * A 16-bit signed integer that defines the vertical offset, in device units. - */ - private int y; + /** A signed integer that defines the vertical offset, in device units. */ + protected int y; - /** - * A 16-bit signed integer that defines the horizontal offset, in device units. - */ - private int x; + /** A signed integer that defines the horizontal offset, in device units. */ + protected int x; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.setViewportOrg; } @@ -67,20 +63,14 @@ public class HwmfWindowing { */ public static class WmfSetViewportExt implements HwmfRecord { - /** - * A 16-bit signed integer that defines the vertical extent - * of the viewport in device units. - */ - private int height; + /** A signed integer that defines the vertical extent of the viewport in device units. */ + protected int height; - /** - * A 16-bit signed integer that defines the horizontal extent - * of the viewport in device units. - */ - private int width; + /** A signed integer that defines the horizontal extent of the viewport in device units. */ + protected int width; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.setViewportExt; } @@ -114,7 +104,7 @@ public class HwmfWindowing { private int xOffset; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.offsetViewportOrg; } @@ -139,18 +129,14 @@ public class HwmfWindowing { */ public static class WmfSetWindowOrg implements HwmfRecord { - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units. - */ - private int y; + /** A signed integer that defines the y-coordinate, in logical units. */ + protected int y; - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units. - */ - private int x; + /** A signed integer that defines the x-coordinate, in logical units. */ + protected int x; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.setWindowOrg; } @@ -182,20 +168,14 @@ public class HwmfWindowing { */ public static class WmfSetWindowExt implements HwmfRecord { - /** - * A 16-bit signed integer that defines the vertical extent of - * the window in logical units. - */ - private int height; + /** A signed integer that defines the vertical extent of the window in logical units. */ + protected int height; - /** - * A 16-bit signed integer that defines the horizontal extent of - * the window in logical units. - */ - private int width; + /** A signed integer that defines the horizontal extent of the window in logical units. */ + protected int width; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.setWindowExt; } @@ -238,7 +218,7 @@ public class HwmfWindowing { private int xOffset; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.offsetWindowOrg; } @@ -264,31 +244,31 @@ public class HwmfWindowing { public static class WmfScaleWindowExt implements HwmfRecord { /** - * A 16-bit signed integer that defines the amount by which to divide the + * A signed integer that defines the amount by which to divide the * result of multiplying the current y-extent by the value of the yNum member. */ - private int yDenom; + protected int yDenom; /** - * A 16-bit signed integer that defines the amount by which to multiply the + * A signed integer that defines the amount by which to multiply the * current y-extent. */ - private int yNum; + protected int yNum; /** - * A 16-bit signed integer that defines the amount by which to divide the + * A signed integer that defines the amount by which to divide the * result of multiplying the current x-extent by the value of the xNum member. */ - private int xDenom; + protected int xDenom; /** - * A 16-bit signed integer that defines the amount by which to multiply the + * A signed integer that defines the amount by which to multiply the * current x-extent. */ - private int xNum; + protected int xNum; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.scaleWindowExt; } @@ -320,31 +300,31 @@ public class HwmfWindowing { public static class WmfScaleViewportExt implements HwmfRecord { /** - * A 16-bit signed integer that defines the amount by which to divide the + * A signed integer that defines the amount by which to divide the * result of multiplying the current y-extent by the value of the yNum member. */ - private int yDenom; + protected int yDenom; /** - * A 16-bit signed integer that defines the amount by which to multiply the + * A signed integer that defines the amount by which to multiply the * current y-extent. */ - private int yNum; + protected int yNum; /** - * A 16-bit signed integer that defines the amount by which to divide the + * A signed integer that defines the amount by which to divide the * result of multiplying the current x-extent by the value of the xNum member. */ - private int xDenom; + protected int xDenom; /** - * A 16-bit signed integer that defines the amount by which to multiply the + * A signed integer that defines the amount by which to multiply the * current x-extent. */ - private int xNum; + protected int xNum; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.scaleViewportExt; } @@ -376,17 +356,17 @@ public class HwmfWindowing { public static class WmfOffsetClipRgn implements HwmfRecord, HwmfObjectTableEntry { /** - * A 16-bit signed integer that defines the number of logical units to move up or down. + * A signed integer that defines the number of logical units to move up or down. */ - private int yOffset; + protected int yOffset; /** - * A 16-bit signed integer that defines the number of logical units to move left or right. + * A signed integer that defines the number of logical units to move left or right. */ - private int xOffset; + protected int xOffset; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.offsetClipRgn; } @@ -413,41 +393,29 @@ public class HwmfWindowing { */ public static class WmfExcludeClipRect implements HwmfRecord, HwmfObjectTableEntry { - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of the - * lower-right corner of the rectangle. - */ - private int bottom; - - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of the - * lower-right corner of the rectangle. - */ - private int right; - - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of the - * upper-left corner of the rectangle. - */ - private int top; - - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of the - * upper-left corner of the rectangle. - */ - private int left; + /** a rectangle in logical units */ + protected final Rectangle2D bounds = new Rectangle2D.Double(); @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.excludeClipRect; } @Override public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { - bottom = leis.readShort(); - right = leis.readShort(); - top = leis.readShort(); - left = leis.readShort(); + // A 16-bit signed integer that defines the y-coordinate, in logical units, of the + // lower-right corner of the rectangle. + final int bottom = leis.readShort(); + // A 16-bit signed integer that defines the x-coordinate, in logical units, of the + // lower-right corner of the rectangle. + final int right = leis.readShort(); + // A 16-bit signed integer that defines the y-coordinate, in logical units, of the + // upper-left corner of the rectangle. + final int top = leis.readShort(); + // A 16-bit signed integer that defines the x-coordinate, in logical units, of the + // upper-left corner of the rectangle. + final int left = leis.readShort(); + bounds.setRect(left, top, right-left, bottom-top); return 4*LittleEndianConsts.SHORT_SIZE; } @@ -468,41 +436,29 @@ public class HwmfWindowing { */ public static class WmfIntersectClipRect implements HwmfRecord, HwmfObjectTableEntry { - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of the - * lower-right corner of the rectangle. - */ - private int bottom; - - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of the - * lower-right corner of the rectangle. - */ - private int right; - - /** - * A 16-bit signed integer that defines the y-coordinate, in logical units, of the - * upper-left corner of the rectangle. - */ - private int top; - - /** - * A 16-bit signed integer that defines the x-coordinate, in logical units, of the - * upper-left corner of the rectangle. - */ - private int left; + /** a rectangle in logical units */ + protected final Rectangle2D bounds = new Rectangle2D.Double(); @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.intersectClipRect; } @Override public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { - bottom = leis.readShort(); - right = leis.readShort(); - top = leis.readShort(); - left = leis.readShort(); + // A 16-bit signed integer that defines the y-coordinate, in logical units, of the + // lower-right corner of the rectangle. + final int bottom = leis.readShort(); + // A 16-bit signed integer that defines the x-coordinate, in logical units, of the + // lower-right corner of the rectangle. + final int right = leis.readShort(); + // A 16-bit signed integer that defines the y-coordinate, in logical units, of the + // upper-left corner of the rectangle. + final int top = leis.readShort(); + // A 16-bit signed integer that defines the x-coordinate, in logical units, of the + // upper-left corner of the rectangle. + final int left = leis.readShort(); + bounds.setRect(left, top, right-left, bottom-top); return 4*LittleEndianConsts.SHORT_SIZE; } @@ -528,7 +484,7 @@ public class HwmfWindowing { private int region; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.selectClipRegion; } @@ -650,7 +606,7 @@ public class HwmfWindowing { private WmfScanObject scanObjects[]; @Override - public HwmfRecordType getRecordType() { + public HwmfRecordType getWmfRecordType() { return HwmfRecordType.createRegion; } diff --git a/src/scratchpad/src/org/apache/poi/hwmf/usermodel/HwmfPicture.java b/src/scratchpad/src/org/apache/poi/hwmf/usermodel/HwmfPicture.java index fa59f4bc31..b380d46143 100644 --- a/src/scratchpad/src/org/apache/poi/hwmf/usermodel/HwmfPicture.java +++ b/src/scratchpad/src/org/apache/poi/hwmf/usermodel/HwmfPicture.java @@ -51,8 +51,7 @@ public class HwmfPicture { public HwmfPicture(InputStream inputStream) throws IOException { - try (BufferedInputStream bis = new BufferedInputStream(inputStream, 10000); - LittleEndianInputStream leis = new LittleEndianInputStream(bis)) { + try (LittleEndianInputStream leis = new LittleEndianInputStream(inputStream)) { placeableHeader = HwmfPlaceableHeader.readHeader(leis); header = new HwmfHeader(leis); @@ -82,17 +81,12 @@ public class HwmfPicture { if (wrt == HwmfRecordType.eof) { break; } - if (wrt.clazz == null) { + if (wrt.constructor == null) { throw new IOException("unsupported record type: "+recordFunction); } - HwmfRecord wr; - try { - wr = wrt.clazz.newInstance(); - records.add(wr); - } catch (Exception e) { - throw (IOException)new IOException("can't create wmf record").initCause(e); - } + final HwmfRecord wr = wrt.constructor.get(); + records.add(wr); consumedSize += wr.init(leis, recordSize, recordFunction); int remainingSize = (int)(recordSize - consumedSize); diff --git a/src/scratchpad/testcases/org/apache/poi/hemf/extractor/HemfExtractorTest.java b/src/scratchpad/testcases/org/apache/poi/hemf/extractor/HemfExtractorTest.java deleted file mode 100644 index 8d32b21a5e..0000000000 --- a/src/scratchpad/testcases/org/apache/poi/hemf/extractor/HemfExtractorTest.java +++ /dev/null @@ -1,198 +0,0 @@ -/* ==================================================================== - 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.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -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.apache.poi.util.IOUtils; -import org.apache.poi.util.RecordFormatException; -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 expectedParts = new HashSet<>(); - 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); - } - - @Test(expected = RecordFormatException.class) - public void testInfiniteLoopOnFile() throws Exception { - InputStream is = null; - try { - is = POIDataSamples.getSpreadSheetInstance().openResourceAsStream("61294.emf"); - - HemfExtractor ex = new HemfExtractor(is); - for (HemfRecord record : ex) { - - } - } finally { - IOUtils.closeQuietly(is); - } - } - - @Test(expected = RecordFormatException.class) - public void testInfiniteLoopOnByteArray() throws Exception { - InputStream is = POIDataSamples.getSpreadSheetInstance().openResourceAsStream("61294.emf"); - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - IOUtils.copy(is, bos); - is.close(); - - HemfExtractor ex = new HemfExtractor(new ByteArrayInputStream(bos.toByteArray())); - for (HemfRecord record : ex) { - - } - } - - /* - govdocs1 064213.doc-0.emf contains an example of extextouta - */ -} \ No newline at end of file diff --git a/src/scratchpad/testcases/org/apache/poi/hemf/hemfplus/extractor/HemfPlusExtractorTest.java b/src/scratchpad/testcases/org/apache/poi/hemf/hemfplus/extractor/HemfPlusExtractorTest.java index f3bdbf7b4f..5811accac5 100644 --- a/src/scratchpad/testcases/org/apache/poi/hemf/hemfplus/extractor/HemfPlusExtractorTest.java +++ b/src/scratchpad/testcases/org/apache/poi/hemf/hemfplus/extractor/HemfPlusExtractorTest.java @@ -25,13 +25,13 @@ 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.apache.poi.hemf.record.emf.HemfComment.EmfComment; +import org.apache.poi.hemf.record.emf.HemfComment.EmfCommentDataPlus; +import org.apache.poi.hemf.record.emf.HemfRecord; +import org.apache.poi.hemf.record.emfplus.HemfPlusHeader; +import org.apache.poi.hemf.record.emfplus.HemfPlusRecord; +import org.apache.poi.hemf.record.emfplus.HemfPlusRecordType; +import org.apache.poi.hemf.usermodel.HemfPicture; import org.junit.Test; public class HemfPlusExtractorTest { @@ -39,7 +39,7 @@ public class HemfPlusExtractorTest { @Test public void testBasic() throws Exception { //test header - HemfCommentEMFPlus emfPlus = getCommentRecord("SimpleEMF_windows.emf", 0); + EmfCommentDataPlus emfPlus = getCommentRecord("SimpleEMF_windows.emf", 0); List records = emfPlus.getRecords(); assertEquals(1, records.size()); assertEquals(HemfPlusRecordType.header, records.get(0).getRecordType()); @@ -72,24 +72,20 @@ public class HemfPlusExtractorTest { } - private HemfCommentEMFPlus getCommentRecord(String testFileName, int recordIndex) throws Exception { - InputStream is = null; - HemfCommentEMFPlus returnRecord = null; + private EmfCommentDataPlus getCommentRecord(String testFileName, int recordIndex) throws Exception { + EmfCommentDataPlus returnRecord = null; - try { - is = POIDataSamples.getSpreadSheetInstance().openResourceAsStream(testFileName); - HemfExtractor ex = new HemfExtractor(is); + try (InputStream is = POIDataSamples.getSpreadSheetInstance().openResourceAsStream(testFileName)) { + HemfPicture ex = new HemfPicture(is); int i = 0; for (HemfRecord record : ex) { if (i == recordIndex) { - HemfCommentRecord commentRecord = ((HemfCommentRecord) record); - returnRecord = (HemfCommentEMFPlus) commentRecord.getComment(); + EmfComment commentRecord = ((EmfComment) record); + returnRecord = (EmfCommentDataPlus) commentRecord.getCommentData(); break; } i++; } - } finally { - is.close(); } return returnRecord; } diff --git a/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java b/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java new file mode 100644 index 0000000000..f0e259df4d --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java @@ -0,0 +1,201 @@ +/* ==================================================================== + 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.usermodel; + +import static org.apache.poi.POITestCase.assertContains; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.awt.geom.Point2D; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.apache.poi.POIDataSamples; +import org.apache.poi.hemf.record.emf.HemfComment; +import org.apache.poi.hemf.record.emf.HemfComment.EmfComment; +import org.apache.poi.hemf.record.emf.HemfComment.EmfCommentDataFormat; +import org.apache.poi.hemf.record.emf.HemfComment.EmfCommentDataMultiformats; +import org.apache.poi.hemf.record.emf.HemfHeader; +import org.apache.poi.hemf.record.emf.HemfRecord; +import org.apache.poi.hemf.record.emf.HemfRecordType; +import org.apache.poi.hemf.record.emf.HemfText; +import org.apache.poi.util.IOUtils; +import org.apache.poi.util.RecordFormatException; +import org.junit.Test; + +public class HemfPictureTest { + + private POIDataSamples samples = POIDataSamples.getSpreadSheetInstance(); + + @Test + public void testBasicWindows() throws Exception { + try (InputStream is = samples.openResourceAsStream("SimpleEMF_windows.emf")) { + HemfPicture pic = new HemfPicture(is); + HemfHeader header = pic.getHeader(); + assertEquals(27864, header.getBytes()); + assertEquals(31, header.getRecords()); + assertEquals(3, header.getHandles()); + assertEquals(346000, header.getMicrometersX()); + assertEquals(194000, header.getMicrometersY()); + + List records = pic.getRecords(); + + assertEquals(31, records.size()); + } + } + + @Test + public void testBasicMac() throws Exception { + try (InputStream is = samples.openResourceAsStream("SimpleEMF_mac.emf")) { + HemfPicture pic = new HemfPicture(is); + HemfHeader header = pic.getHeader(); + + int records = 0; + boolean extractedData = false; + for (HemfRecord record : pic) { + if (record.getEmfRecordType() == HemfRecordType.comment) { + HemfComment.EmfCommentData comment = ((EmfComment) record).getCommentData(); + if (comment instanceof EmfCommentDataMultiformats) { + for (EmfCommentDataFormat d : ((EmfCommentDataMultiformats) comment).getFormats()) { + byte[] data = d.getRawData(); + //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(), records); + } + } + + @Test + public void testMacText() throws Exception { + try (InputStream is = samples.openResourceAsStream("SimpleEMF_mac.emf")) { + HemfPicture pic = new HemfPicture(is); + + double lastY = -1; + double lastX = -1; + //derive this from the font information! + long fudgeFactorX = 1000; + StringBuilder sb = new StringBuilder(); + for (HemfRecord record : pic) { + if (record.getEmfRecordType().equals(HemfRecordType.exttextoutw)) { + HemfText.ExtTextOutW extTextOutW = (HemfText.ExtTextOutW) record; + Point2D reference = extTextOutW.getTextObject().getReference(); + if (lastY > -1 && lastY != reference.getY()) { + sb.append("\n"); + lastX = -1; + } + if (lastX > -1 && reference.getX() - lastX > fudgeFactorX) { + sb.append(" "); + } + sb.append(extTextOutW.getText()); + lastY = reference.getY(); + lastX = reference.getX(); + } + } + String txt = sb.toString(); + assertContains(txt, "Tika http://incubator.apache.org"); + assertContains(txt, "Latest News\n"); + } + } + + @Test + public void testWindowsText() throws Exception { + try (InputStream is = samples.openResourceAsStream("SimpleEMF_windows.emf")) { + HemfPicture pic = new HemfPicture(is); + double lastY = -1; + double lastX = -1; + long fudgeFactorX = 1000;//derive this from the font or frame/bounds information + StringBuilder sb = new StringBuilder(); + Set expectedParts = new HashSet<>(); + expectedParts.add("C:\\Users\\tallison\\"); + expectedParts.add("testPDF.pdf"); + int foundExpected = 0; + for (HemfRecord record : pic) { + if (record.getEmfRecordType().equals(HemfRecordType.exttextoutw)) { + HemfText.ExtTextOutW extTextOutW = (HemfText.ExtTextOutW) record; + Point2D reference = extTextOutW.getTextObject().getReference(); + if (lastY > -1 && lastY != reference.getY()) { + sb.append("\n"); + lastX = -1; + } + if (lastX > -1 && reference.getX() - lastX > fudgeFactorX) { + sb.append(" "); + } + String txt = extTextOutW.getText(); + if (expectedParts.contains(txt)) { + foundExpected++; + } + sb.append(txt); + lastY = reference.getY(); + lastX = reference.getX(); + } + } + String txt = sb.toString(); + assertContains(txt, "C:\\Users\\tallison\\\n"); + assertContains(txt, "asf2-git-1.x\\tika-\n"); + assertEquals(expectedParts.size(), foundExpected); + } + } + + @Test(expected = RecordFormatException.class) + public void testInfiniteLoopOnFile() throws Exception { + try (InputStream is = samples.openResourceAsStream("61294.emf")) { + HemfPicture pic = new HemfPicture(is); + for (HemfRecord record : pic) { + + } + } + } + + @Test(expected = RecordFormatException.class) + public void testInfiniteLoopOnByteArray() throws Exception { + try (InputStream is = samples.openResourceAsStream("61294.emf")) { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + IOUtils.copy(is, bos); + is.close(); + + HemfPicture pic = new HemfPicture(new ByteArrayInputStream(bos.toByteArray())); + for (HemfRecord record : pic) { + + } + } + } + + /* + govdocs1 064213.doc-0.emf contains an example of extextouta + */ +} \ No newline at end of file diff --git a/src/scratchpad/testcases/org/apache/poi/hwmf/TestHwmfParsing.java b/src/scratchpad/testcases/org/apache/poi/hwmf/TestHwmfParsing.java index 1667b67b49..df084cdad3 100644 --- a/src/scratchpad/testcases/org/apache/poi/hwmf/TestHwmfParsing.java +++ b/src/scratchpad/testcases/org/apache/poi/hwmf/TestHwmfParsing.java @@ -222,11 +222,11 @@ public class TestHwmfParsing { //this happens to work on this test file, but you need to //do what Graphics does by maintaining the stack, etc.! for (HwmfRecord r : wmf.getRecords()) { - if (r.getRecordType().equals(HwmfRecordType.createFontIndirect)) { + if (r.getWmfRecordType().equals(HwmfRecordType.createFontIndirect)) { HwmfFont font = ((HwmfText.WmfCreateFontIndirect)r).getFont(); charset = (font.getCharset().getCharset() == null) ? LocaleUtil.CHARSET_1252 : font.getCharset().getCharset(); } - if (r.getRecordType().equals(HwmfRecordType.extTextOut)) { + if (r.getWmfRecordType().equals(HwmfRecordType.extTextOut)) { HwmfText.WmfExtTextOut textOut = (HwmfText.WmfExtTextOut)r; sb.append(textOut.getText(charset)).append("\n"); } @@ -250,11 +250,11 @@ public class TestHwmfParsing { //this happens to work on this test file, but you need to //do what Graphics does by maintaining the stack, etc.! for (HwmfRecord r : wmf.getRecords()) { - if (r.getRecordType().equals(HwmfRecordType.createFontIndirect)) { + if (r.getWmfRecordType().equals(HwmfRecordType.createFontIndirect)) { HwmfFont font = ((HwmfText.WmfCreateFontIndirect)r).getFont(); charset = (font.getCharset().getCharset() == null) ? LocaleUtil.CHARSET_1252 : font.getCharset().getCharset(); } - if (r.getRecordType().equals(HwmfRecordType.extTextOut)) { + if (r.getWmfRecordType().equals(HwmfRecordType.extTextOut)) { HwmfText.WmfExtTextOut textOut = (HwmfText.WmfExtTextOut)r; sb.append(textOut.getText(charset)).append("\n"); }