aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--build.gradle2
-rw-r--r--build.xml2
-rw-r--r--sonar/examples/pom.xml2
-rw-r--r--sonar/excelant/pom.xml2
-rw-r--r--sonar/main/pom.xml2
-rw-r--r--sonar/ooxml-schema-encryption/pom.xml2
-rw-r--r--sonar/ooxml-schema-security/pom.xml2
-rw-r--r--sonar/ooxml-schema/pom.xml2
-rw-r--r--sonar/ooxml/pom.xml2
-rw-r--r--sonar/pom.xml2
-rw-r--r--sonar/scratchpad/pom.xml2
-rw-r--r--src/integrationtest/org/apache/poi/stress/SlideShowHandler.java1
-rw-r--r--src/java/org/apache/poi/hssf/dev/BiffViewer.java12
-rw-r--r--src/java/org/apache/poi/hssf/record/RecordInputStream.java32
-rw-r--r--src/java/org/apache/poi/sl/draw/BitmapImageRenderer.java16
-rw-r--r--src/java/org/apache/poi/sl/draw/DrawFactory.java38
-rw-r--r--src/java/org/apache/poi/sl/draw/DrawFontManagerDefault.java51
-rw-r--r--src/java/org/apache/poi/sl/draw/DrawPictureShape.java61
-rw-r--r--src/java/org/apache/poi/sl/draw/DrawTextParagraph.java8
-rw-r--r--src/java/org/apache/poi/sl/draw/DrawTextShape.java5
-rw-r--r--src/java/org/apache/poi/sl/draw/ImageRenderer.java7
-rw-r--r--src/java/org/apache/poi/util/Dimension2DDouble.java76
-rw-r--r--src/java/org/apache/poi/util/IOUtils.java31
-rw-r--r--src/java/org/apache/poi/util/LittleEndianInputStream.java59
-rw-r--r--src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureConfig.java6
-rw-r--r--src/ooxml/java/org/apache/poi/xdgf/geom/Dimension2dDouble.java61
-rw-r--r--src/ooxml/java/org/apache/poi/xslf/draw/SVGImageRenderer.java6
-rw-r--r--src/ooxml/java/org/apache/poi/xslf/usermodel/XMLSlideShow.java2
-rw-r--r--src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFChart.java2
-rw-r--r--src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFDrawing.java2
-rw-r--r--src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSheet.java4
-rw-r--r--src/ooxml/java/org/apache/poi/xslf/util/PPTX2PNG.java1
-rw-r--r--src/ooxml/testcases/org/apache/poi/sl/TestFonts.java2
-rw-r--r--src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFSimpleShape.java1
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/draw/HemfDrawProperties.java69
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java259
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/draw/HemfImageRenderer.java126
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/extractor/HemfExtractor.java115
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/HemfPlusRecord.java44
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/HemfPlusRecordType.java97
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/record/HemfComment.java31
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentEMFPlus.java111
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentEMFSpool.java31
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentPublic.java177
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentRecord.java154
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/record/HemfHeader.java201
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/record/HemfRecordType.java159
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/record/HemfText.java262
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java465
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java1153
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java719
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFont.java496
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfHeader.java207
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java828
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfPalette.java154
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfPenStyle.java (renamed from src/scratchpad/src/org/apache/poi/hemf/record/AbstractHemfComment.java)32
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecord.java61
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordIterator.java91
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java165
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java332
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfWindowing.java220
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/record/emf/UnimplementedHemfRecord.java (renamed from src/scratchpad/src/org/apache/poi/hemf/record/UnimplementedHemfRecord.java)15
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusHeader.java (renamed from src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/HemfPlusHeader.java)27
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRecord.java (renamed from src/scratchpad/src/org/apache/poi/hemf/record/HemfRecord.java)19
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRecordIterator.java98
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRecordType.java100
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/record/emfplus/UnimplementedHemfPlusRecord.java (renamed from src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/UnimplementedHemfPlusRecord.java)22
-rw-r--r--src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java161
-rw-r--r--src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java104
-rw-r--r--src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java450
-rw-r--r--src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfImageRenderer.java (renamed from src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfSLImageRenderer.java)15
-rw-r--r--src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBitmapDib.java143
-rw-r--r--src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBrushStyle.java2
-rw-r--r--src/scratchpad/src/org/apache/poi/hwmf/record/HwmfColorRef.java10
-rw-r--r--src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java601
-rw-r--r--src/scratchpad/src/org/apache/poi/hwmf/record/HwmfEscape.java2
-rw-r--r--src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java604
-rw-r--r--src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFont.java172
-rw-r--r--src/scratchpad/src/org/apache/poi/hwmf/record/HwmfHatchStyle.java17
-rw-r--r--src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMapMode.java2
-rw-r--r--src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java227
-rw-r--r--src/scratchpad/src/org/apache/poi/hwmf/record/HwmfPalette.java33
-rw-r--r--src/scratchpad/src/org/apache/poi/hwmf/record/HwmfPenStyle.java43
-rw-r--r--src/scratchpad/src/org/apache/poi/hwmf/record/HwmfRecord.java2
-rw-r--r--src/scratchpad/src/org/apache/poi/hwmf/record/HwmfRecordType.java146
-rw-r--r--src/scratchpad/src/org/apache/poi/hwmf/record/HwmfRegionMode.java59
-rw-r--r--src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java457
-rw-r--r--src/scratchpad/src/org/apache/poi/hwmf/record/HwmfWindowing.java489
-rw-r--r--src/scratchpad/src/org/apache/poi/hwmf/usermodel/HwmfPicture.java19
-rw-r--r--src/scratchpad/testcases/org/apache/poi/hemf/extractor/HemfExtractorTest.java198
-rw-r--r--src/scratchpad/testcases/org/apache/poi/hemf/hemfplus/extractor/HemfPlusExtractorTest.java36
-rw-r--r--src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java353
-rw-r--r--src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestPicture.java1
-rw-r--r--src/scratchpad/testcases/org/apache/poi/hwmf/TestHwmfParsing.java8
94 files changed, 8532 insertions, 3368 deletions
diff --git a/build.gradle b/build.gradle
index 63e1d34e8d..9c4afa814b 100644
--- a/build.gradle
+++ b/build.gradle
@@ -91,7 +91,7 @@ subprojects {
// See https://github.com/melix/japicmp-gradle-plugin
apply plugin: 'me.champeau.gradle.japicmp'
- version = '4.0.2-SNAPSHOT'
+ version = '4.1.0-SNAPSHOT'
ext {
japicmpversion = '4.0.0'
}
diff --git a/build.xml b/build.xml
index d7032dc1c2..e512230ac7 100644
--- a/build.xml
+++ b/build.xml
@@ -42,7 +42,7 @@ under the License.
<description>The Apache POI project Ant build.</description>
- <property name="version.id" value="4.0.2"/>
+ <property name="version.id" value="4.1.0"/>
<property name="release.rc" value="RC1"/>
<property environment="env"/>
diff --git a/sonar/examples/pom.xml b/sonar/examples/pom.xml
index 22c576a20d..b6854c9f7b 100644
--- a/sonar/examples/pom.xml
+++ b/sonar/examples/pom.xml
@@ -6,7 +6,7 @@
<parent>
<groupId>org.apache.poi</groupId>
<artifactId>poi-parent</artifactId>
- <version>4.0.2-SNAPSHOT</version>
+ <version>4.1.0-SNAPSHOT</version>
</parent>
<artifactId>poi-examples</artifactId>
<packaging>jar</packaging>
diff --git a/sonar/excelant/pom.xml b/sonar/excelant/pom.xml
index 220d4eb1e9..2953351f8e 100644
--- a/sonar/excelant/pom.xml
+++ b/sonar/excelant/pom.xml
@@ -6,7 +6,7 @@
<parent>
<groupId>org.apache.poi</groupId>
<artifactId>poi-parent</artifactId>
- <version>4.0.2-SNAPSHOT</version>
+ <version>4.1.0-SNAPSHOT</version>
</parent>
<artifactId>poi-excelant</artifactId>
<packaging>jar</packaging>
diff --git a/sonar/main/pom.xml b/sonar/main/pom.xml
index 9bd0af96a2..2c4eed2720 100644
--- a/sonar/main/pom.xml
+++ b/sonar/main/pom.xml
@@ -6,7 +6,7 @@
<parent>
<groupId>org.apache.poi</groupId>
<artifactId>poi-parent</artifactId>
- <version>4.0.2-SNAPSHOT</version>
+ <version>4.1.0-SNAPSHOT</version>
</parent>
<artifactId>poi-main</artifactId>
<packaging>jar</packaging>
diff --git a/sonar/ooxml-schema-encryption/pom.xml b/sonar/ooxml-schema-encryption/pom.xml
index d768a5f4b3..9a0b19d190 100644
--- a/sonar/ooxml-schema-encryption/pom.xml
+++ b/sonar/ooxml-schema-encryption/pom.xml
@@ -6,7 +6,7 @@
<parent>
<groupId>org.apache.poi</groupId>
<artifactId>poi-parent</artifactId>
- <version>4.0.2-SNAPSHOT</version>
+ <version>4.1.0-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<artifactId>poi-ooxml-schema-encryption</artifactId>
diff --git a/sonar/ooxml-schema-security/pom.xml b/sonar/ooxml-schema-security/pom.xml
index 06318b2be9..a9d7418bbf 100644
--- a/sonar/ooxml-schema-security/pom.xml
+++ b/sonar/ooxml-schema-security/pom.xml
@@ -6,7 +6,7 @@
<parent>
<groupId>org.apache.poi</groupId>
<artifactId>poi-parent</artifactId>
- <version>4.0.2-SNAPSHOT</version>
+ <version>4.1.0-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<artifactId>poi-ooxml-schema-security</artifactId>
diff --git a/sonar/ooxml-schema/pom.xml b/sonar/ooxml-schema/pom.xml
index 7bb69433da..3b5f5c28f4 100644
--- a/sonar/ooxml-schema/pom.xml
+++ b/sonar/ooxml-schema/pom.xml
@@ -6,7 +6,7 @@
<parent>
<groupId>org.apache.poi</groupId>
<artifactId>poi-parent</artifactId>
- <version>4.0.2-SNAPSHOT</version>
+ <version>4.1.0-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<artifactId>poi-ooxml-schema</artifactId>
diff --git a/sonar/ooxml/pom.xml b/sonar/ooxml/pom.xml
index 6a6232acaa..7de88404cc 100644
--- a/sonar/ooxml/pom.xml
+++ b/sonar/ooxml/pom.xml
@@ -6,7 +6,7 @@
<parent>
<groupId>org.apache.poi</groupId>
<artifactId>poi-parent</artifactId>
- <version>4.0.2-SNAPSHOT</version>
+ <version>4.1.0-SNAPSHOT</version>
</parent>
<artifactId>poi-ooxml</artifactId>
<packaging>jar</packaging>
diff --git a/sonar/pom.xml b/sonar/pom.xml
index c008c3a1c4..5fce9d97c5 100644
--- a/sonar/pom.xml
+++ b/sonar/pom.xml
@@ -4,7 +4,7 @@
<groupId>org.apache.poi</groupId>
<artifactId>poi-parent</artifactId>
<packaging>pom</packaging>
- <version>4.0.2-SNAPSHOT</version>
+ <version>4.1.0-SNAPSHOT</version>
<name>Apache POI - the Java API for Microsoft Documents</name>
<description>Maven build of Apache POI for Sonar checks</description>
<url>http://poi.apache.org/</url>
diff --git a/sonar/scratchpad/pom.xml b/sonar/scratchpad/pom.xml
index a980e15568..5af72fdee3 100644
--- a/sonar/scratchpad/pom.xml
+++ b/sonar/scratchpad/pom.xml
@@ -6,7 +6,7 @@
<parent>
<groupId>org.apache.poi</groupId>
<artifactId>poi-parent</artifactId>
- <version>4.0.2-SNAPSHOT</version>
+ <version>4.1.0-SNAPSHOT</version>
</parent>
<artifactId>poi-scratchpad</artifactId>
<packaging>jar</packaging>
diff --git a/src/integrationtest/org/apache/poi/stress/SlideShowHandler.java b/src/integrationtest/org/apache/poi/stress/SlideShowHandler.java
index 63ffdbd58c..20460cc2db 100644
--- a/src/integrationtest/org/apache/poi/stress/SlideShowHandler.java
+++ b/src/integrationtest/org/apache/poi/stress/SlideShowHandler.java
@@ -105,7 +105,6 @@ public abstract class SlideShowHandler extends POIFSFileHandler {
for (Slide<?,?> s : ss.getSlides()) {
BufferedImage img = new BufferedImage(pgsize.width, pgsize.height, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = img.createGraphics();
- DrawFactory.getInstance(graphics).fixFonts(graphics);
// default rendering options
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
diff --git a/src/java/org/apache/poi/hssf/dev/BiffViewer.java b/src/java/org/apache/poi/hssf/dev/BiffViewer.java
index cb17d84b56..631be8fd2d 100644
--- a/src/java/org/apache/poi/hssf/dev/BiffViewer.java
+++ b/src/java/org/apache/poi/hssf/dev/BiffViewer.java
@@ -527,20 +527,16 @@ public final class BiffViewer {
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
+ if (b == null || off < 0 || len < 0 || b.length < off+len) {
+ throw new IllegalArgumentException();
+ }
if (_currentPos >= _currentSize) {
fillNextBuffer();
}
if (_currentPos >= _currentSize) {
return -1;
}
- int availSize = _currentSize - _currentPos;
- int result;
- if (len > availSize) {
- System.err.println("Unexpected request to read past end of current biff record");
- result = availSize;
- } else {
- result = len;
- }
+ final int result = Math.min(len, _currentSize - _currentPos);
System.arraycopy(_data, _currentPos, b, off, result);
_currentPos += result;
_overallStreamPos += result;
diff --git a/src/java/org/apache/poi/hssf/record/RecordInputStream.java b/src/java/org/apache/poi/hssf/record/RecordInputStream.java
index 8347655a81..400553721e 100644
--- a/src/java/org/apache/poi/hssf/record/RecordInputStream.java
+++ b/src/java/org/apache/poi/hssf/record/RecordInputStream.java
@@ -106,8 +106,8 @@ public final class RecordInputStream implements LittleEndianInput {
private final LittleEndianInput _lei;
- public SimpleHeaderInput(InputStream in) {
- _lei = getLEI(in);
+ private SimpleHeaderInput(LittleEndianInput lei) {
+ _lei = lei;
}
@Override
public int available() {
@@ -129,8 +129,12 @@ public final class RecordInputStream implements LittleEndianInput {
public RecordInputStream(InputStream in, EncryptionInfo key, int initialOffset) throws RecordFormatException {
if (key == null) {
- _dataInput = getLEI(in);
- _bhi = new SimpleHeaderInput(in);
+ _dataInput = (in instanceof LittleEndianInput)
+ // accessing directly is an optimisation
+ ? (LittleEndianInput)in
+ // less optimal, but should work OK just the same. Often occurs in junit tests.
+ : new LittleEndianInputStream(in);
+ _bhi = new SimpleHeaderInput(_dataInput);
} else {
Biff8DecryptingStream bds = new Biff8DecryptingStream(in, initialOffset, key);
_dataInput = bds;
@@ -195,11 +199,9 @@ public final class RecordInputStream implements LittleEndianInput {
private int readNextSid() {
int nAvailable = _bhi.available();
if (nAvailable < EOFRecord.ENCODED_SIZE) {
- /*if (nAvailable > 0) {
- // some scrap left over?
- // ex45582-22397.xls has one extra byte after the last record
- // Excel reads that file OK
- }*/
+ // some scrap left over, if nAvailable > 0?
+ // ex45582-22397.xls has one extra byte after the last record
+ // Excel reads that file OK
return INVALID_SID_VALUE;
}
int result = _bhi.readRecordSID();
@@ -305,14 +307,8 @@ public final class RecordInputStream implements LittleEndianInput {
@Override
public double readDouble() {
- long valueLongBits = readLong();
- /*if (Double.isNaN(result)) {
- // YK: Excel doesn't write NaN but instead converts the cell type into {@link CellType#ERROR}.
- // HSSF prior to version 3.7 had a bug: it could write Double.NaN but could not read such a file back.
- // This behavior was fixed in POI-3.7.
- //throw new RuntimeException("Did not expect to read NaN"); // (Because Excel typically doesn't write NaN)
- }*/
- return Double.longBitsToDouble(valueLongBits);
+ // YK: Excel doesn't write NaN but instead converts the cell type into {@link CellType#ERROR}.
+ return Double.longBitsToDouble(readLong());
}
public void readPlain(byte[] buf, int off, int len) {
@@ -329,7 +325,7 @@ public final class RecordInputStream implements LittleEndianInput {
readFully(buf, off, len, false);
}
- protected void readFully(byte[] buf, int off, int len, boolean isPlain) {
+ private void readFully(byte[] buf, int off, int len, boolean isPlain) {
int origLen = len;
if (buf == null) {
throw new NullPointerException();
diff --git a/src/java/org/apache/poi/sl/draw/BitmapImageRenderer.java b/src/java/org/apache/poi/sl/draw/BitmapImageRenderer.java
index a92c8dc590..c37ce006a0 100644
--- a/src/java/org/apache/poi/sl/draw/BitmapImageRenderer.java
+++ b/src/java/org/apache/poi/sl/draw/BitmapImageRenderer.java
@@ -40,6 +40,7 @@ import javax.imageio.ImageTypeSpecifier;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.MemoryCacheImageInputStream;
+import org.apache.poi.sl.usermodel.PictureData.PictureType;
import org.apache.poi.util.IOUtils;
import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger;
@@ -48,11 +49,24 @@ import org.apache.poi.util.POILogger;
* For now this class renders only images supported by the javax.imageio.ImageIO framework.
**/
public class BitmapImageRenderer implements ImageRenderer {
- private final static POILogger LOG = POILogFactory.getLogger(ImageRenderer.class);
+ private final static POILogger LOG = POILogFactory.getLogger(BitmapImageRenderer.class);
protected BufferedImage img;
@Override
+ public boolean canRender(String contentType) {
+ PictureType[] pts = {
+ PictureType.JPEG, PictureType.PNG, PictureType.BMP, PictureType.GIF
+ };
+ for (PictureType pt : pts) {
+ if (pt.contentType.equalsIgnoreCase(contentType)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
public void loadImage(InputStream data, String contentType) throws IOException {
img = readImage(data, contentType);
}
diff --git a/src/java/org/apache/poi/sl/draw/DrawFactory.java b/src/java/org/apache/poi/sl/draw/DrawFactory.java
index 98c41ed993..99c9942b67 100644
--- a/src/java/org/apache/poi/sl/draw/DrawFactory.java
+++ b/src/java/org/apache/poi/sl/draw/DrawFactory.java
@@ -22,8 +22,6 @@ import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.text.AttributedString;
-import java.util.HashMap;
-import java.util.Map;
import org.apache.poi.sl.usermodel.Background;
import org.apache.poi.sl.usermodel.ConnectorShape;
@@ -40,18 +38,18 @@ import org.apache.poi.sl.usermodel.TableShape;
import org.apache.poi.sl.usermodel.TextBox;
import org.apache.poi.sl.usermodel.TextParagraph;
import org.apache.poi.sl.usermodel.TextShape;
-import org.apache.poi.util.JvmBugs;
public class DrawFactory {
- protected static final ThreadLocal<DrawFactory> defaultFactory = new ThreadLocal<>();
+ private static final ThreadLocal<DrawFactory> defaultFactory = new ThreadLocal<>();
/**
* Set a custom draw factory for the current thread.
* This is a fallback, for operations where usercode can't set a graphics context.
* Preferably use the rendering hint {@link Drawable#DRAW_FACTORY} to set the factory.
*
- * @param factory
+ * @param factory the custom factory
*/
+ @SuppressWarnings("unused")
public static void setDefaultFactory(DrawFactory factory) {
defaultFactory.set(factory);
}
@@ -170,6 +168,7 @@ public class DrawFactory {
return new DrawBackground(shape);
}
+ @SuppressWarnings("WeakerAccess")
public DrawTextFragment getTextFragment(TextLayout layout, AttributedString str) {
return new DrawTextFragment(layout, str);
}
@@ -214,35 +213,6 @@ public class DrawFactory {
/**
- * Replace font families for Windows JVM 6, which contains a font rendering error.
- * This is likely to be removed, when POI upgrades to JDK 7
- *
- * @param graphics the graphics context which will contain the font mapping
- */
- public void fixFonts(Graphics2D graphics) {
- if (!JvmBugs.hasLineBreakMeasurerBug()) return;
- @SuppressWarnings("unchecked")
- Map<String,String> fontMap = (Map<String,String>)graphics.getRenderingHint(Drawable.FONT_MAP);
- if (fontMap == null) {
- fontMap = new HashMap<>();
- graphics.setRenderingHint(Drawable.FONT_MAP, fontMap);
- }
-
- String fonts[][] = {
- { "Calibri", "Lucida Sans" },
- { "Cambria", "Lucida Bright" },
- { "Times New Roman", "Lucida Bright" },
- { "serif", "Lucida Bright" }
- };
-
- for (String f[] : fonts) {
- if (!fontMap.containsKey(f[0])) {
- fontMap.put(f[0], f[1]);
- }
- }
- }
-
- /**
* Return a FontManager, either registered beforehand or a default implementation
*
* @param graphics the graphics context holding potentially a font manager
diff --git a/src/java/org/apache/poi/sl/draw/DrawFontManagerDefault.java b/src/java/org/apache/poi/sl/draw/DrawFontManagerDefault.java
index c439fc926f..a428943699 100644
--- a/src/java/org/apache/poi/sl/draw/DrawFontManagerDefault.java
+++ b/src/java/org/apache/poi/sl/draw/DrawFontManagerDefault.java
@@ -22,6 +22,8 @@ package org.apache.poi.sl.draw;
import java.awt.Font;
import java.awt.Graphics2D;
import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
import org.apache.poi.common.usermodel.fonts.FontInfo;
import org.apache.poi.sl.draw.Drawable.DrawableHint;
@@ -33,6 +35,13 @@ import org.apache.poi.sl.draw.Drawable.DrawableHint;
*/
public class DrawFontManagerDefault implements DrawFontManager {
+ protected final Set<String> knownSymbolFonts = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
+
+ public DrawFontManagerDefault() {
+ knownSymbolFonts.add("Wingdings");
+ knownSymbolFonts.add("Symbol");
+ }
+
@Override
public FontInfo getMappedFont(Graphics2D graphics, FontInfo fontInfo) {
return getFontWithFallback(graphics, Drawable.FONT_MAP, fontInfo);
@@ -49,25 +58,35 @@ public class DrawFontManagerDefault implements DrawFontManager {
public String mapFontCharset(Graphics2D graphics, FontInfo fontInfo, String text) {
// TODO: find a real charset mapping solution instead of hard coding for Wingdings
- String attStr = text;
- if (fontInfo != null && "Wingdings".equalsIgnoreCase(fontInfo.getTypeface())) {
- // wingdings doesn't contain high-surrogates, so chars are ok
- boolean changed = false;
- char chrs[] = attStr.toCharArray();
- for (int i=0; i<chrs.length; i++) {
- // only change valid chars
- if ((0x20 <= chrs[i] && chrs[i] <= 0x7f) ||
- (0xa0 <= chrs[i] && chrs[i] <= 0xff)) {
- chrs[i] |= 0xf000;
- changed = true;
- }
- }
+ return (fontInfo != null && knownSymbolFonts.contains(fontInfo.getTypeface()))
+ ? mapSymbolChars(text)
+ : text;
+ }
- if (changed) {
- attStr = new String(chrs);
+ /**
+ * Symbol fonts like "Wingdings" or "Symbol" have glyphs mapped to a Unicode private use range via the Java font loader,
+ * although a system font viewer might show you the glyphs in the ASCII range.
+ * This helper function maps the chars of the text string to the corresponding private use range chars.
+ *
+ * @param text the input string, typically consists of ASCII chars
+ * @return the mapped string, typically consists of chars in the range of 0xf000 to 0xf0ff
+ *
+ * @since POI 4.0.0
+ */
+ public static String mapSymbolChars(String text) {
+ // wingdings doesn't contain high-surrogates, so chars are ok
+ boolean changed = false;
+ char chrs[] = text.toCharArray();
+ for (int i=0; i<chrs.length; i++) {
+ // only change valid chars
+ if ((0x20 <= chrs[i] && chrs[i] <= 0x7f) ||
+ (0xa0 <= chrs[i] && chrs[i] <= 0xff)) {
+ chrs[i] |= 0xf000;
+ changed = true;
}
}
- return attStr;
+
+ return changed ? new String(chrs) : text;
}
@Override
diff --git a/src/java/org/apache/poi/sl/draw/DrawPictureShape.java b/src/java/org/apache/poi/sl/draw/DrawPictureShape.java
index fcca6d07a7..70194bdcd7 100644
--- a/src/java/org/apache/poi/sl/draw/DrawPictureShape.java
+++ b/src/java/org/apache/poi/sl/draw/DrawPictureShape.java
@@ -24,17 +24,20 @@ import java.awt.geom.Rectangle2D;
import java.io.IOException;
import org.apache.poi.sl.usermodel.PictureData;
-import org.apache.poi.sl.usermodel.PictureData.PictureType;
-import org.apache.poi.util.POILogFactory;
-import org.apache.poi.util.POILogger;
import org.apache.poi.sl.usermodel.PictureShape;
import org.apache.poi.sl.usermodel.RectAlign;
+import org.apache.poi.util.POILogFactory;
+import org.apache.poi.util.POILogger;
public class DrawPictureShape extends DrawSimpleShape {
private static final POILogger LOG = POILogFactory.getLogger(DrawPictureShape.class);
- private static final String WMF_IMAGE_RENDERER = "org.apache.poi.hwmf.draw.HwmfSLImageRenderer";
-
+ private static final String[] KNOWN_RENDERER = {
+ "org.apache.poi.hwmf.draw.HwmfImageRenderer",
+ "org.apache.poi.hemf.draw.HemfImageRenderer",
+ "org.apache.poi.xslf.draw.SVGImageRenderer"
+ };
+
public DrawPictureShape(PictureShape<?,?> shape) {
super(shape);
}
@@ -59,29 +62,47 @@ public class DrawPictureShape extends DrawSimpleShape {
/**
* Returns an ImageRenderer for the PictureData
*
- * @param graphics
+ * @param graphics the graphics context
* @return the image renderer
*/
+ @SuppressWarnings({"WeakerAccess", "unchecked"})
public static ImageRenderer getImageRenderer(Graphics2D graphics, String contentType) {
- ImageRenderer renderer = (ImageRenderer)graphics.getRenderingHint(Drawable.IMAGE_RENDERER);
- if (renderer != null) {
+ final ImageRenderer renderer = (ImageRenderer)graphics.getRenderingHint(Drawable.IMAGE_RENDERER);
+ if (renderer != null && renderer.canRender(contentType)) {
return renderer;
}
-
- if (PictureType.WMF.contentType.equals(contentType)) {
+
+ // first try with our default image renderer
+ final BitmapImageRenderer bir = new BitmapImageRenderer();
+ if (bir.canRender(contentType)) {
+ return bir;
+ }
+
+ // then iterate through the scratchpad renderers
+ //
+ // this could be nicely implemented via a j.u.ServiceLoader, but OSGi makes things complicated ...
+ // https://blog.osgi.org/2013/02/javautilserviceloader-in-osgi.html
+ // ... therefore falling back to classloading attempts
+ ClassLoader cl = ImageRenderer.class.getClassLoader();
+ for (String kr : KNOWN_RENDERER) {
+ final ImageRenderer ir;
try {
- @SuppressWarnings("unchecked")
- Class<? extends ImageRenderer> irc = (Class<? extends ImageRenderer>)
- DrawPictureShape.class.getClassLoader().loadClass(WMF_IMAGE_RENDERER);
- return irc.newInstance();
- } catch (Exception e) {
- // WMF image renderer is not on the classpath, continuing with BitmapRenderer
- // although this doesn't make much sense ...
- LOG.log(POILogger.ERROR, "WMF image renderer is not on the classpath - include poi-scratchpad jar!", e);
+ ir = ((Class<? extends ImageRenderer>)cl.loadClass(kr)).newInstance();
+ } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
+ // scratchpad was not on the path, ignore and continue
+ LOG.log(POILogger.INFO, "Known image renderer '"+kr+" not found/loaded - include poi-scratchpad jar!", e);
+ continue;
+ }
+ if (ir.canRender(contentType)) {
+ return ir;
}
}
-
- return new BitmapImageRenderer();
+
+ LOG.log(POILogger.WARN, "No suiteable image renderer found for content-type '"+
+ contentType+"' - include poi-scratchpad jar!");
+
+ // falling back to BitmapImageRenderer, at least it gracefully handles invalid images
+ return bir;
}
@Override
diff --git a/src/java/org/apache/poi/sl/draw/DrawTextParagraph.java b/src/java/org/apache/poi/sl/draw/DrawTextParagraph.java
index 938d46230a..1ab4af3a1e 100644
--- a/src/java/org/apache/poi/sl/draw/DrawTextParagraph.java
+++ b/src/java/org/apache/poi/sl/draw/DrawTextParagraph.java
@@ -254,7 +254,6 @@ public class DrawTextParagraph implements Drawable {
lines.clear();
DrawFactory fact = DrawFactory.getInstance(graphics);
- fact.fixFonts(graphics);
StringBuilder text = new StringBuilder();
AttributedString at = getAttributedString(graphics, text);
boolean emptyParagraph = text.toString().trim().isEmpty();
@@ -635,13 +634,6 @@ public class DrawTextParagraph implements Drawable {
* <li>determine the font group - a text run can have different font groups. Depending on the chars,
* the correct font group needs to be used
*
- * @param graphics
- * @param dfm
- * @param attList
- * @param beginIndex
- * @param run
- * @param runText
- *
* @see <a href="https://blogs.msdn.microsoft.com/officeinteroperability/2013/04/22/office-open-xml-themes-schemes-and-fonts/">Office Open XML Themes, Schemes, and Fonts</a>
*/
private void processGlyphs(Graphics2D graphics, DrawFontManager dfm, List<AttributedStringData> attList, final int beginIndex, TextRun run, String runText) {
diff --git a/src/java/org/apache/poi/sl/draw/DrawTextShape.java b/src/java/org/apache/poi/sl/draw/DrawTextShape.java
index 413ab218c9..c4dd65bb75 100644
--- a/src/java/org/apache/poi/sl/draw/DrawTextShape.java
+++ b/src/java/org/apache/poi/sl/draw/DrawTextShape.java
@@ -40,8 +40,6 @@ public class DrawTextShape extends DrawSimpleShape {
@Override
public void drawContent(Graphics2D graphics) {
- DrawFactory.getInstance(graphics).fixFonts(graphics);
-
TextShape<?,?> s = getShape();
Rectangle2D anchor = DrawShape.getAnchor(graphics, s);
@@ -219,10 +217,9 @@ public class DrawTextShape extends DrawSimpleShape {
graphics.addRenderingHints(oldGraphics.getRenderingHints());
graphics.setTransform(oldGraphics.getTransform());
}
- DrawFactory.getInstance(graphics).fixFonts(graphics);
return drawParagraphs(graphics, 0, 0);
}
-
+
@Override
protected TextShape<?,? extends TextParagraph<?,?,? extends TextRun>> getShape() {
return (TextShape<?,? extends TextParagraph<?,?,? extends TextRun>>)shape;
diff --git a/src/java/org/apache/poi/sl/draw/ImageRenderer.java b/src/java/org/apache/poi/sl/draw/ImageRenderer.java
index 7ecc96a967..b2355b3012 100644
--- a/src/java/org/apache/poi/sl/draw/ImageRenderer.java
+++ b/src/java/org/apache/poi/sl/draw/ImageRenderer.java
@@ -76,6 +76,13 @@ import java.io.InputStream;
*/
public interface ImageRenderer {
/**
+ * Determines if this image renderer implementation supports the given contentType
+ * @param contentType the image content type
+ * @return if the content type is supported
+ */
+ boolean canRender(String contentType);
+
+ /**
* Load and buffer the image
*
* @param data the raw image stream
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..227c0cddd5
--- /dev/null
+++ b/src/java/org/apache/poi/util/Dimension2DDouble.java
@@ -0,0 +1,76 @@
+/* ====================================================================
+ 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;
+
+/**
+ * @since 4.1.0
+ */
+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/java/org/apache/poi/util/IOUtils.java b/src/java/org/apache/poi/util/IOUtils.java
index 009510ffc6..3dd4cb0d57 100644
--- a/src/java/org/apache/poi/util/IOUtils.java
+++ b/src/java/org/apache/poi/util/IOUtils.java
@@ -50,6 +50,7 @@ public final class IOUtils {
* @param maxOverride The number of bytes that should be possible to be allocated in one step.
* @since 4.0.0
*/
+ @SuppressWarnings("unused")
public static void setByteArrayMaxOverride(int maxOverride) {
BYTE_ARRAY_MAX_OVERRIDE = maxOverride;
}
@@ -395,13 +396,35 @@ public final class IOUtils {
* @throws IOException If copying the data fails.
*/
public static long copy(InputStream inp, OutputStream out) throws IOException {
+ return copy(inp, out, -1);
+ }
+
+ /**
+ * Copies all the data from the given InputStream to the OutputStream. It
+ * leaves both streams open, so you will still need to close them once done.
+ *
+ * @param inp The {@link InputStream} which provides the data
+ * @param out The {@link OutputStream} to write the data to
+ * @param limit limit the copied bytes - use {@code -1} for no limit
+ * @return the amount of bytes copied
+ *
+ * @throws IOException If copying the data fails.
+ */
+ public static long copy(InputStream inp, OutputStream out, long limit) throws IOException {
final byte[] buff = new byte[4096];
long totalCount = 0;
- for (int count; (count = inp.read(buff)) != -1; totalCount += count) {
- if (count > 0) {
- out.write(buff, 0, count);
+ int readBytes = -1;
+ do {
+ int todoBytes = (int)((limit < 0) ? buff.length : Math.min(limit-totalCount, buff.length));
+ if (todoBytes > 0) {
+ readBytes = inp.read(buff, 0, todoBytes);
+ if (readBytes > 0) {
+ out.write(buff, 0, readBytes);
+ totalCount += readBytes;
+ }
}
- }
+ } while (readBytes >= 0 && (limit == -1 || totalCount < limit));
+
return totalCount;
}
diff --git a/src/java/org/apache/poi/util/LittleEndianInputStream.java b/src/java/org/apache/poi/util/LittleEndianInputStream.java
index 886720f3f1..6199c4dd12 100644
--- a/src/java/org/apache/poi/util/LittleEndianInputStream.java
+++ b/src/java/org/apache/poi/util/LittleEndianInputStream.java
@@ -17,6 +17,7 @@
package org.apache.poi.util;
+import java.io.BufferedInputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -29,12 +30,16 @@ import java.io.InputStream;
*/
public class LittleEndianInputStream extends FilterInputStream implements LittleEndianInput {
+ private static final int BUFFERED_SIZE = 8096;
+
private static final int EOF = -1;
+ private int readIndex = 0;
+ private int markIndex = -1;
public LittleEndianInputStream(InputStream is) {
- super(is);
+ super(is.markSupported() ? is : new BufferedInputStream(is, BUFFERED_SIZE));
}
-
+
@Override
@SuppressForbidden("just delegating")
public int available() {
@@ -60,7 +65,18 @@ public class LittleEndianInputStream extends FilterInputStream implements Little
}
return LittleEndian.getUByte(buf);
}
-
+
+ /**
+ * get a float value, reads it in little endian format
+ * then converts the resulting revolting IEEE 754 (curse them) floating
+ * point number to a happy java float
+ *
+ * @return the float (32-bit) value
+ */
+ public float readFloat() {
+ return Float.intBitsToFloat( readInt() );
+ }
+
@Override
public double readDouble() {
return Double.longBitsToDouble(readLong());
@@ -137,14 +153,42 @@ public class LittleEndianInputStream extends FilterInputStream implements Little
}
}
- //Makes repeated calls to super.read() until length is read or EOF is reached
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ int readBytes = super.read(b, off, len);
+ readIndex += readBytes;
+ return readBytes;
+ }
+
+ @Override
+ public synchronized void mark(int readlimit) {
+ super.mark(readlimit);
+ markIndex = readIndex;
+ }
+
+ @Override
+ public synchronized void reset() throws IOException {
+ super.reset();
+ if (markIndex > -1) {
+ readIndex = markIndex;
+ markIndex = -1;
+ }
+ }
+
+ public int getReadIndex() {
+ return readIndex;
+ }
+
+
+
+ //Makes repeated calls to super.read() until length is read or EOF is reached
private int _read(byte[] buffer, int offset, int length) throws IOException {
//lifted directly from org.apache.commons.io.IOUtils 2.4
int remaining = length;
while (remaining > 0) {
int location = length - remaining;
int count = read(buffer, offset + location, remaining);
- if (EOF == count) { // EOF
+ if (EOF == count) {
break;
}
remaining -= count;
@@ -157,4 +201,9 @@ public class LittleEndianInputStream extends FilterInputStream implements Little
public void readPlain(byte[] buf, int off, int len) {
readFully(buf, off, len);
}
+
+
+ public void skipFully(int len) throws IOException {
+ IOUtils.skipFully(this, len);
+ }
}
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureConfig.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureConfig.java
index 71a42ef9e1..0977a403a1 100644
--- a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureConfig.java
+++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureConfig.java
@@ -177,7 +177,7 @@ public class SignatureConfig {
/**
* if true, the signature is added to the existing signatures
*
- * @since POI 4.0.2
+ * @since POI 4.1.0
*/
private boolean allowMultipleSignatures = false;
@@ -1019,7 +1019,7 @@ public class SignatureConfig {
/**
* @return true, if multiple signatures can be attached
*
- * @since POI 4.0.2
+ * @since POI 4.1.0
*/
public boolean isAllowMultipleSignatures() {
return allowMultipleSignatures;
@@ -1031,7 +1031,7 @@ public class SignatureConfig {
* @param allowMultipleSignatures if true, the signature will be added,
* otherwise all existing signatures will be replaced by the current
*
- * @since POI 4.0.2
+ * @since POI 4.1.0
*/
public void setAllowMultipleSignatures(boolean allowMultipleSignatures) {
this.allowMultipleSignatures = allowMultipleSignatures;
diff --git a/src/ooxml/java/org/apache/poi/xdgf/geom/Dimension2dDouble.java b/src/ooxml/java/org/apache/poi/xdgf/geom/Dimension2dDouble.java
index 94b887e04e..2363abb939 100644
--- a/src/ooxml/java/org/apache/poi/xdgf/geom/Dimension2dDouble.java
+++ b/src/ooxml/java/org/apache/poi/xdgf/geom/Dimension2dDouble.java
@@ -17,57 +17,12 @@
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 + "]";
- }
+import org.apache.poi.util.Removal;
+
+/**
+ * @deprecated in 4.1.0 - use org.apache.poi.util.Dimension2DDouble
+ */
+@Deprecated
+@Removal(version = "5.0.0")
+public class Dimension2dDouble extends org.apache.poi.util.Dimension2DDouble {
}
diff --git a/src/ooxml/java/org/apache/poi/xslf/draw/SVGImageRenderer.java b/src/ooxml/java/org/apache/poi/xslf/draw/SVGImageRenderer.java
index 2a03dda691..a40c169516 100644
--- a/src/ooxml/java/org/apache/poi/xslf/draw/SVGImageRenderer.java
+++ b/src/ooxml/java/org/apache/poi/xslf/draw/SVGImageRenderer.java
@@ -39,6 +39,7 @@ import org.apache.batik.ext.awt.image.renderable.ClipRable8Bit;
import org.apache.batik.gvt.GraphicsNode;
import org.apache.batik.util.XMLResourceDescriptor;
import org.apache.poi.sl.draw.ImageRenderer;
+import org.apache.poi.sl.usermodel.PictureData;
import org.w3c.dom.Document;
public class SVGImageRenderer implements ImageRenderer {
@@ -133,4 +134,9 @@ public class SVGImageRenderer implements ImageRenderer {
return true;
}
+
+ @Override
+ public boolean canRender(String contentType) {
+ return PictureData.PictureType.SVG.contentType.equalsIgnoreCase(contentType);
+ }
}
diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XMLSlideShow.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XMLSlideShow.java
index b0cbc59dd1..c5e47415df 100644
--- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XMLSlideShow.java
+++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XMLSlideShow.java
@@ -310,7 +310,7 @@ public class XMLSlideShow extends POIXMLDocument
/**
* This method is used to create template for chart XML.
* @return Xslf chart object
- * @since POI 4.0.2
+ * @since POI 4.1.0
*/
public XSLFChart createChart() {
int chartIdx = findNextAvailableFileNameIndex(XSLFRelation.CHART, _charts.size() + 1);
diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFChart.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFChart.java
index 5e8d73cd06..97f3ec23ee 100644
--- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFChart.java
+++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFChart.java
@@ -112,7 +112,7 @@ public final class XSLFChart extends XDDFChart {
* @param rID relation id
* @param anchor size and location of chart
* @return graphic frame object
- * @since POI 4.0.2
+ * @since POI 4.1.0
*/
static CTGraphicalObjectFrame prototype(int shapeId, String rID, Rectangle2D anchor) {
CTGraphicalObjectFrame frame = CTGraphicalObjectFrame.Factory.newInstance();
diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFDrawing.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFDrawing.java
index ba93988d0b..dca6fed131 100644
--- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFDrawing.java
+++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFDrawing.java
@@ -112,7 +112,7 @@ public class XSLFDrawing {
*
* @param rID relation id of chart
* @param rect2D Chart Bounding values
- * @since POI 4.0.2
+ * @since POI 4.1.0
*/
public void addChart(String rID, Rectangle2D rect2D) {
CTGraphicalObjectFrame sp = _spTree.addNewGraphicFrame();
diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSheet.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSheet.java
index 2bcf063990..d76142a4c4 100644
--- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSheet.java
+++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSheet.java
@@ -724,7 +724,7 @@ implements XSLFShapeContainer, Sheet<XSLFShape,XSLFTextParagraph> {
* this method will add chart into slide
* with default height, width, x and y
* @param chart xslf chart object
- * @since POI 4.0.2
+ * @since POI 4.1.0
*/
public void addChart(XSLFChart chart) {
Rectangle2D rect2D = new java.awt.Rectangle(XDDFChart.DEFAULT_X, XDDFChart.DEFAULT_Y,
@@ -737,7 +737,7 @@ implements XSLFShapeContainer, Sheet<XSLFShape,XSLFTextParagraph> {
* this method will add chart into slide
* with given height, width, x and y
* @param chart xslf chart object
- * @since POI 4.0.2
+ * @since POI 4.1.0
*/
public void addChart(XSLFChart chart, Rectangle2D rect2D) {
RelationPart rp = addRelation(null, XSLFRelation.CHART, chart);
diff --git a/src/ooxml/java/org/apache/poi/xslf/util/PPTX2PNG.java b/src/ooxml/java/org/apache/poi/xslf/util/PPTX2PNG.java
index b3288d46f3..2ae1293113 100644
--- a/src/ooxml/java/org/apache/poi/xslf/util/PPTX2PNG.java
+++ b/src/ooxml/java/org/apache/poi/xslf/util/PPTX2PNG.java
@@ -139,7 +139,6 @@ public class PPTX2PNG {
BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = img.createGraphics();
- DrawFactory.getInstance(graphics).fixFonts(graphics);
// default rendering options
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
diff --git a/src/ooxml/testcases/org/apache/poi/sl/TestFonts.java b/src/ooxml/testcases/org/apache/poi/sl/TestFonts.java
index c7e950d236..39de26a073 100644
--- a/src/ooxml/testcases/org/apache/poi/sl/TestFonts.java
+++ b/src/ooxml/testcases/org/apache/poi/sl/TestFonts.java
@@ -130,8 +130,6 @@ public class TestFonts {
graphics.setRenderingHint(Drawable.FONT_FALLBACK, fallbackMap);
graphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
- DrawFactory.getInstance(graphics).fixFonts(graphics);
-
tb.resizeToFitText(graphics);
graphics.dispose();
diff --git a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFSimpleShape.java b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFSimpleShape.java
index e35ed35a29..ddbfe26652 100644
--- a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFSimpleShape.java
+++ b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFSimpleShape.java
@@ -361,7 +361,6 @@ public class TestXSLFSimpleShape {
BufferedImage img = new BufferedImage(pgsize.width, pgsize.height, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = img.createGraphics();
- DrawFactory.getInstance(graphics).fixFonts(graphics);
// default rendering options
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
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..98b07d4c6b
--- /dev/null
+++ b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfDrawProperties.java
@@ -0,0 +1,69 @@
+/* ====================================================================
+ 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.Shape;
+import java.awt.geom.Path2D;
+
+import org.apache.poi.hwmf.draw.HwmfDrawProperties;
+
+public class HemfDrawProperties extends HwmfDrawProperties {
+
+ /** Path for path bracket operations */
+ protected Path2D path = null;
+ protected boolean usePathBracket = false;
+
+
+ public HemfDrawProperties() {
+ }
+
+ public HemfDrawProperties(HemfDrawProperties other) {
+ super(other);
+ path = (other.path != null) ? (Path2D)other.path.clone() : null;
+ // TODO: check how to clone
+ clip = other.clip;
+ }
+
+ /**
+ * @return the current path used for bracket operations
+ */
+ public Path2D getPath() {
+ return path;
+ }
+
+ /**
+ * Un-/Sets the bracket path
+ * @param path the bracket path
+ */
+ public void setPath(Path2D path) {
+ this.path = path;
+ }
+
+ /**
+ * Use path (bracket) or graphics context for drawing operations
+ * @return {@code true}, if the drawing should go to the path bracket,
+ * if {@code false} draw directly to the graphics context
+ */
+ public boolean getUsePathBracket() {
+ return usePathBracket;
+ }
+
+ public void setUsePathBracket(boolean usePathBracket) {
+ this.usePathBracket = usePathBracket;
+ }
+}
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..c7e99965f5
--- /dev/null
+++ b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java
@@ -0,0 +1,259 @@
+/* ====================================================================
+ 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 static org.apache.poi.hwmf.record.HwmfBrushStyle.BS_NULL;
+import static org.apache.poi.hwmf.record.HwmfBrushStyle.BS_SOLID;
+
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.geom.Path2D;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.util.function.Consumer;
+
+import org.apache.poi.hemf.record.emf.HemfRecord;
+import org.apache.poi.hwmf.draw.HwmfDrawProperties;
+import org.apache.poi.hwmf.draw.HwmfGraphics;
+import org.apache.poi.hwmf.record.HwmfColorRef;
+import org.apache.poi.hwmf.record.HwmfObjectTableEntry;
+import org.apache.poi.hwmf.record.HwmfPenStyle;
+import org.apache.poi.util.Internal;
+
+public class HemfGraphics extends HwmfGraphics {
+
+ private static final HwmfColorRef WHITE = new HwmfColorRef(Color.WHITE);
+ private static final HwmfColorRef LTGRAY = new HwmfColorRef(new Color(0x00C0C0C0));
+ private static final HwmfColorRef GRAY = new HwmfColorRef(new Color(0x00808080));
+ private static final HwmfColorRef DKGRAY = new HwmfColorRef(new Color(0x00404040));
+ private static final HwmfColorRef BLACK = new HwmfColorRef(Color.BLACK);
+
+
+ public HemfGraphics(Graphics2D graphicsCtx, Rectangle2D bbox) {
+ super(graphicsCtx,bbox);
+ // add dummy entry for object ind ex 0, as emf is 1-based
+ objectIndexes.set(0);
+ }
+
+ @Override
+ public HemfDrawProperties getProperties() {
+ return (HemfDrawProperties)super.getProperties();
+ }
+
+ @Override
+ protected HemfDrawProperties newProperties(HwmfDrawProperties oldProps) {
+ return (oldProps == null)
+ ? new HemfDrawProperties()
+ : new HemfDrawProperties((HemfDrawProperties)oldProps);
+ }
+
+ public void draw(HemfRecord r) {
+ r.draw(this);
+ }
+
+ @Internal
+ public void draw(Consumer<Path2D> pathConsumer, FillDrawStyle fillDraw) {
+ final HemfDrawProperties prop = getProperties();
+ final boolean useBracket = prop.getUsePathBracket();
+
+ final Path2D path;
+ if (useBracket) {
+ path = prop.getPath();
+ } else {
+ path = new Path2D.Double();
+ path.setWindingRule(prop.getWindingRule());
+ }
+
+ // add dummy move-to at start, to handle invalid emfs not containing that move-to
+ if (path.getCurrentPoint() == null) {
+ Point2D pnt = prop.getLocation();
+ path.moveTo(pnt.getX(), pnt.getY());
+ }
+
+ try {
+ pathConsumer.accept(path);
+ } catch (Exception e) {
+ // workaround if a path has been started and no MoveTo command
+ // has been specified before the first lineTo/splineTo
+ final Point2D loc = prop.getLocation();
+ path.moveTo(loc.getX(), loc.getY());
+ pathConsumer.accept(path);
+ }
+
+ Point2D curPnt = path.getCurrentPoint();
+ if (curPnt == null) {
+ return;
+ }
+
+ prop.setLocation(curPnt);
+ if (!useBracket) {
+ switch (fillDraw) {
+ case FILL:
+ super.fill(path);
+ break;
+ case DRAW:
+ super.draw(path);
+ break;
+ case FILL_DRAW:
+ super.fill(path);
+ super.draw(path);
+ break;
+ }
+ }
+
+ }
+
+ /**
+ * Adds or sets an record of type {@link HwmfObjectTableEntry} to the object table.
+ * If the {@code index} is less than 1, the method acts the same as
+ * {@link HwmfGraphics#addObjectTableEntry(HwmfObjectTableEntry)}, otherwise the
+ * index is used to access the object table.
+ * As the table is filled successively, the index must be between 1 and size+1
+ *
+ * @param entry the record to be stored
+ * @param index the index to be overwritten, regardless if its content was unset before
+ *
+ * @see HwmfGraphics#addObjectTableEntry(HwmfObjectTableEntry)
+ */
+ public void addObjectTableEntry(HwmfObjectTableEntry entry, int index) {
+ if (index < 1) {
+ throw new IndexOutOfBoundsException("Object table entry index in EMF must be > 0 - invalid index: "+index);
+ }
+
+ objectIndexes.set(index);
+ objectTable.put(index, entry);
+ }
+
+ @Override
+ public void applyObjectTableEntry(int index) {
+ if ((index & 0x80000000) != 0) {
+ selectStockObject(index);
+ } else {
+ super.applyObjectTableEntry(index);
+ }
+ }
+
+ private void selectStockObject(int objectIndex) {
+ final HemfDrawProperties prop = getProperties();
+ switch (objectIndex) {
+ case 0x80000000:
+ // WHITE_BRUSH - A white, solid-color brush
+ // BrushStyle: BS_SOLID
+ // Color: 0x00FFFFFF
+ prop.setBrushColor(WHITE);
+ prop.setBrushStyle(BS_SOLID);
+ break;
+ case 0x80000001:
+ // LTGRAY_BRUSH - A light gray, solid-color brush
+ // BrushStyle: BS_SOLID
+ // Color: 0x00C0C0C0
+ prop.setBrushColor(LTGRAY);
+ prop.setBrushStyle(BS_SOLID);
+ break;
+ case 0x80000002:
+ // GRAY_BRUSH - A gray, solid-color brush
+ // BrushStyle: BS_SOLID
+ // Color: 0x00808080
+ prop.setBrushColor(GRAY);
+ prop.setBrushStyle(BS_SOLID);
+ break;
+ case 0x80000003:
+ // DKGRAY_BRUSH - A dark gray, solid color brush
+ // BrushStyle: BS_SOLID
+ // Color: 0x00404040
+ prop.setBrushColor(DKGRAY);
+ prop.setBrushStyle(BS_SOLID);
+ break;
+ case 0x80000004:
+ // BLACK_BRUSH - A black, solid color brush
+ // BrushStyle: BS_SOLID
+ // Color: 0x00000000
+ prop.setBrushColor(BLACK);
+ prop.setBrushStyle(BS_SOLID);
+ break;
+ case 0x80000005:
+ // NULL_BRUSH - A null brush
+ // BrushStyle: BS_NULL
+ prop.setBrushStyle(BS_NULL);
+ break;
+ case 0x80000006:
+ // WHITE_PEN - A white, solid-color pen
+ // PenStyle: PS_COSMETIC + PS_SOLID
+ // ColorRef: 0x00FFFFFF
+ prop.setPenStyle(HwmfPenStyle.valueOf(0));
+ prop.setPenWidth(1);
+ prop.setPenColor(WHITE);
+ break;
+ case 0x80000007:
+ // BLACK_PEN - A black, solid-color pen
+ // PenStyle: PS_COSMETIC + PS_SOLID
+ // ColorRef: 0x00000000
+ prop.setPenStyle(HwmfPenStyle.valueOf(0));
+ prop.setPenWidth(1);
+ prop.setPenColor(BLACK);
+ break;
+ case 0x80000008:
+ // NULL_PEN - A null pen
+ // PenStyle: PS_NULL
+ prop.setPenStyle(HwmfPenStyle.valueOf(HwmfPenStyle.HwmfLineDash.NULL.wmfFlag));
+ break;
+ case 0x8000000A:
+ // OEM_FIXED_FONT - A fixed-width, OEM character set
+ // Charset: OEM_CHARSET
+ // PitchAndFamily: FF_DONTCARE + FIXED_PITCH
+ break;
+ case 0x8000000B:
+ // ANSI_FIXED_FONT - A fixed-width font
+ // Charset: ANSI_CHARSET
+ // PitchAndFamily: FF_DONTCARE + FIXED_PITCH
+ break;
+ case 0x8000000C:
+ // ANSI_VAR_FONT - A variable-width font
+ // Charset: ANSI_CHARSET
+ // PitchAndFamily: FF_DONTCARE + VARIABLE_PITCH
+ break;
+ case 0x8000000D:
+ // SYSTEM_FONT - A font that is guaranteed to be available in the operating system
+ break;
+ case 0x8000000E:
+ // DEVICE_DEFAULT_FONT
+ // The default font that is provided by the graphics device driver for the current output device
+ break;
+ case 0x8000000F:
+ // DEFAULT_PALETTE
+ // The default palette that is defined for the current output device.
+ break;
+ case 0x80000010:
+ // SYSTEM_FIXED_FONT
+ // A fixed-width font that is guaranteed to be available in the operating system.
+ break;
+ case 0x80000011:
+ // DEFAULT_GUI_FONT
+ // The default font that is used for user interface objects such as menus and dialog boxes.
+ break;
+ case 0x80000012:
+ // DC_BRUSH
+ // The solid-color brush that is currently selected in the playback device context.
+ break;
+ case 0x80000013:
+ // DC_PEN
+ // The solid-color pen that is currently selected in the playback device context.
+ break;
+ }
+ }
+}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/draw/HemfImageRenderer.java b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfImageRenderer.java
new file mode 100644
index 0000000000..712c2e6c0e
--- /dev/null
+++ b/src/scratchpad/src/org/apache/poi/hemf/draw/HemfImageRenderer.java
@@ -0,0 +1,126 @@
+/* ====================================================================
+ 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.Dimension;
+import java.awt.Graphics2D;
+import java.awt.Insets;
+import java.awt.RenderingHints;
+import java.awt.geom.Dimension2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+import java.awt.image.RescaleOp;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.poi.hemf.usermodel.HemfPicture;
+import org.apache.poi.sl.draw.ImageRenderer;
+import org.apache.poi.sl.usermodel.PictureData;
+import org.apache.poi.util.Units;
+
+public class HemfImageRenderer implements ImageRenderer {
+ HemfPicture image;
+ double alpha;
+
+ @Override
+ public boolean canRender(String contentType) {
+ return PictureData.PictureType.EMF.contentType.equalsIgnoreCase(contentType);
+ }
+
+ @Override
+ public void loadImage(InputStream data, String contentType) throws IOException {
+ if (!PictureData.PictureType.EMF.contentType.equals(contentType)) {
+ throw new IOException("Invalid picture type");
+ }
+ image = new HemfPicture(data);
+ }
+
+ @Override
+ public void loadImage(byte[] data, String contentType) throws IOException {
+ if (!PictureData.PictureType.EMF.contentType.equals(contentType)) {
+ throw new IOException("Invalid picture type");
+ }
+ image = new HemfPicture(new ByteArrayInputStream(data));
+ }
+
+ @Override
+ public Dimension getDimension() {
+ int width = 0, height = 0;
+ if (image != null) {
+ Dimension2D dim = image.getSize();
+ width = Units.pointsToPixel(dim.getWidth());
+ // keep aspect ratio for height
+ height = Units.pointsToPixel(dim.getHeight());
+ }
+ return new Dimension(width, height);
+ }
+
+ @Override
+ public void setAlpha(double alpha) {
+ this.alpha = alpha;
+ }
+
+ @Override
+ public BufferedImage getImage() {
+ return getImage(getDimension());
+ }
+
+ @Override
+ public BufferedImage getImage(Dimension dim) {
+ if (image == null) {
+ return new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
+ }
+
+ BufferedImage bufImg = new BufferedImage((int)dim.getWidth(), (int)dim.getHeight(), BufferedImage.TYPE_INT_ARGB);
+ Graphics2D g = bufImg.createGraphics();
+ g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+ g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
+ g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
+ g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
+ image.draw(g, new Rectangle2D.Double(0,0,dim.getWidth(),dim.getHeight()));
+ g.dispose();
+
+ if (alpha != 0) {
+ BufferedImage newImg = new BufferedImage((int)dim.getWidth(), (int)dim.getHeight(), BufferedImage.TYPE_INT_ARGB);
+ g = newImg.createGraphics();
+ RescaleOp op = new RescaleOp(new float[]{1.0f, 1.0f, 1.0f, (float)alpha}, new float[]{0,0,0,0}, null);
+ g.drawImage(bufImg, op, 0, 0);
+ g.dispose();
+ bufImg = newImg;
+ }
+
+ return bufImg;
+ }
+
+ @Override
+ public boolean drawImage(Graphics2D graphics, Rectangle2D anchor) {
+ return drawImage(graphics, anchor, null);
+ }
+
+ @Override
+ public boolean drawImage(Graphics2D graphics, Rectangle2D anchor, Insets clip) {
+ if (image == null) {
+ return false;
+ } else {
+ image.draw(graphics, anchor);
+ return true;
+ }
+ }
+
+}
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<HemfRecord> {
-
- private HemfHeader header;
- private final LittleEndianInputStream stream;
-
- public HemfExtractor(InputStream is) throws IOException {
- stream = new LittleEndianInputStream(is);
- header = new HemfHeader();
- long recordId = stream.readUInt();
- long recordSize = stream.readUInt();
-
- header = new HemfHeader();
- header.init(stream, recordId, recordSize-8);
- }
-
- @Override
- public Iterator<HemfRecord> iterator() {
- return new HemfRecordIterator();
- }
-
- public HemfHeader getHeader() {
- return header;
- }
-
- private class HemfRecordIterator implements Iterator<HemfRecord> {
-
- private HemfRecord currentRecord;
-
- 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/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<? extends HemfPlusRecord> clazz;
-
- HemfPlusRecordType(long id, Class<? extends HemfPlusRecord> clazz) {
- this.id = id;
- this.clazz = clazz;
- }
-
- public static HemfPlusRecordType getById(long id) {
- for (HemfPlusRecordType wrt : values()) {
- if (wrt.id == id) return wrt;
- }
- return null;
- }
-}
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<HemfPlusRecord> getRecords() {
- return HemfPlusParser.parse(getRawBytes());
- }
-
- private static class HemfPlusParser {
-
- public static List<HemfPlusRecord> parse(byte[] bytes) {
- List<HemfPlusRecord> 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<HemfMultiFormatsData> getData() {
-
- byte[] rawBytes = getRawBytes();
- //note that raw bytes includes the public comment identifier
- int currentOffset = 4 + 16;//4 public comment identifier, 16 for outputrect
- long countFormats = LittleEndian.getUInt(rawBytes, currentOffset);
- currentOffset += LittleEndianConsts.INT_SIZE;
- List<EmrFormat> emrFormatList = new ArrayList<>();
- for (long i = 0; i < countFormats; i++) {
- emrFormatList.add(new EmrFormat(rawBytes, currentOffset));
- currentOffset += 4 * LittleEndianConsts.INT_SIZE;
- }
- List<HemfMultiFormatsData> 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/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<? extends HemfRecord> clazz;
-
- HemfRecordType(long id, Class<? extends HemfRecord> clazz) {
- this.id = id;
- this.clazz = clazz;
- }
-
- public static HemfRecordType getById(long id) {
- for (HemfRecordType wrt : values()) {
- if (wrt.id == id) return wrt;
- }
- return null;
- }
-}
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/emf/HemfComment.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java
new file mode 100644
index 0000000000..6b8bd13df9
--- /dev/null
+++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java
@@ -0,0 +1,465 @@
+/* ====================================================================
+ 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.hwmf.usermodel.HwmfPicture;
+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.LocaleUtil;
+import org.apache.poi.util.RecordFormatException;
+
+/**
+ * Contains arbitrary data
+ */
+@Internal
+public class HemfComment {
+ private static final int MAX_RECORD_LENGTH = HwmfPicture.MAX_RECORD_LENGTH;
+
+ 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<? extends EmfCommentData> constructor;
+ public final boolean isEmfPublic;
+
+ HemfCommentRecordType(long id, Supplier<? extends EmfCommentData> 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;
+ }
+
+ @Override
+ public String toString() {
+ return "{ data: "+data+" }";
+ }
+ }
+
+ public static class EmfCommentDataIterator implements Iterator<EmfCommentData> {
+
+ 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.
+ try {
+ type = leis.readUInt();
+ } catch (RuntimeException e) {
+ // EOF
+ return null;
+ }
+ 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;
+ }
+
+ @Override
+ public String toString() {
+ return "\""+new String(privateData, LocaleUtil.CHARSET_1252).replaceAll("\\p{Cntrl}", ".")+"\"";
+ }
+ }
+
+ /** The EMR_COMMENT_EMFPLUS record contains embedded EMF+ records. */
+ public static class EmfCommentDataPlus implements EmfCommentData {
+ private final List<HemfPlusRecord> 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<HemfPlusRecord> 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<EmfCommentDataFormat> 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<countFormats; i++) {
+ EmfCommentDataFormat fmt = new EmfCommentDataFormat();
+ long readBytes = fmt.init(leis, dataSize, startIdx);
+ formats.add(fmt);
+ if (readBytes == 0) {
+ // binary data is appended without DataFormat header
+ break;
+ }
+ }
+
+ for (EmfCommentDataFormat fmt : formats) {
+ int skip = fmt.offData-(leis.getReadIndex()-startIdx);
+ leis.skipFully(skip);
+ fmt.rawData = IOUtils.safelyAllocate(fmt.sizeData, MAX_RECORD_LENGTH);
+ int readBytes = leis.read(fmt.rawData);
+ if (readBytes < fmt.sizeData) {
+ // EOF
+ break;
+ }
+ }
+
+ return leis.getReadIndex()-startIdx;
+ }
+
+ public List<EmfCommentDataFormat> 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<EmfCommentDataFormat> 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);
+ // some emf comments are truncated, so we don't use readFully here
+ leis.read(winMetafile);
+
+ return leis.getReadIndex()-startIdx;
+ }
+ }
+
+ public static class EmfCommentDataUnicode implements EmfCommentData {
+ private final Rectangle2D bounds = new Rectangle2D.Double();
+ private final List<EmfCommentDataFormat> 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..7e495e2868
--- /dev/null
+++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java
@@ -0,0 +1,1153 @@
+/* ====================================================================
+ 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 org.apache.poi.hwmf.record.HwmfDraw.boundsToString;
+import static org.apache.poi.hwmf.record.HwmfDraw.normalizeBounds;
+
+import java.awt.Shape;
+import java.awt.geom.Arc2D;
+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.HemfDrawProperties;
+import org.apache.poi.hemf.draw.HemfGraphics;
+import org.apache.poi.hwmf.draw.HwmfGraphics.FillDrawStyle;
+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 {
+
+ private static final String[] STOCK_IDS = {
+ "0x80000000 /* WHITE_BRUSH */",
+ "0x80000001 /* LTGRAY_BRUSH */",
+ "0x80000002 /* GRAY_BRUSH */",
+ "0x80000003 /* DKGRAY_BRUSH */",
+ "0x80000004 /* BLACK_BRUSH */",
+ "0x80000005 /* NULL_BRUSH */",
+ "0x80000006 /* WHITE_PEN */",
+ "0x80000007 /* BLACK_PEN */",
+ "0x80000008 /* NULL_PEN */",
+ "0x8000000A /* OEM_FIXED_FONT */",
+ "0x8000000B /* ANSI_FIXED_FONT */",
+ "0x8000000C /* ANSI_VAR_FONT */",
+ "0x8000000D /* SYSTEM_FONT */",
+ "0x8000000E /* DEVICE_DEFAULT_FONT */",
+ "0x8000000F /* DEFAULT_PALETTE */",
+ "0x80000010 /* SYSTEM_FIXED_FONT */",
+ "0x80000011 /* DEFAULT_GUI_FONT */",
+ "0x80000012 /* DC_BRUSH */",
+ "0x80000013 /* DC_PEN */"
+ };
+
+ @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;
+ }
+
+ @Override
+ public String toString() {
+ return "{ index: "+
+ (((objectIndex & 0x80000000) != 0 && (objectIndex & 0x3FFFFFFF) <= 13 )
+ ? STOCK_IDS[objectIndex & 0x3FFFFFFF]
+ : objectIndex)+" }";
+ }
+
+ }
+
+
+ /** 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 = new Path2D.Double(Path2D.WIND_EVEN_ODD, points+2);
+
+ /* 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() };
+
+ int i=0;
+ if (hasStartPoint()) {
+ if (i < points) {
+ size += readPoint(leis, pnt[0]);
+ poly.moveTo(pnt[0].getX(), pnt[0].getY());
+ i++;
+ }
+ } else {
+ poly.moveTo(0, 0);
+ }
+
+ for (; i+2<points; i+=3) {
+ size += readPoint(leis, pnt[0]);
+ size += readPoint(leis, pnt[1]);
+ size += readPoint(leis, pnt[2]);
+
+ poly.curveTo(
+ pnt[0].getX(),pnt[0].getY(),
+ pnt[1].getX(),pnt[1].getY(),
+ pnt[2].getX(),pnt[2].getY()
+ );
+ }
+
+ return size;
+ }
+
+ /**
+ * @return true, if start point is in the list of points. false, if start point is taken from the context
+ */
+ protected boolean hasStartPoint() {
+ return true;
+ }
+
+ @Override
+ protected FillDrawStyle getFillDrawStyle() {
+ // The cubic Bezier curves SHOULD be drawn using the current pen.
+ return FillDrawStyle.DRAW;
+ }
+
+ @Override
+ public void draw(HemfGraphics ctx) {
+ ctx.draw(path -> path.append(poly, !hasStartPoint()), getFillDrawStyle());
+ }
+ }
+
+ /**
+ * The EMR_POLYBEZIER16 record specifies one or more Bezier curves.
+ * The curves are drawn using the current pen.
+ */
+ public static class EmfPolyBezier16 extends EmfPolyBezier {
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.polyBezier16;
+ }
+
+ protected long readPoint(LittleEndianInputStream leis, Point2D point) {
+ return readPointS(leis, point);
+ }
+ }
+
+
+ /**
+ * The EMR_POLYGON record specifies a polygon consisting of two or more vertexes connected by
+ * straight lines.
+ */
+ public static class EmfPolygon extends HwmfDraw.WmfPolygon implements HemfRecord {
+ private final Rectangle2D bounds = new Rectangle2D.Double();
+
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.polygon;
+ }
+
+ 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);
+
+ // see PolyBezier about limits
+ final int count = (int)leis.readUInt();
+ final int points = Math.min(count, 16384);
+ size += LittleEndianConsts.INT_SIZE;
+
+ poly = new Path2D.Double(Path2D.WIND_EVEN_ODD, points);
+
+ Point2D pnt = new Point2D.Double();
+ for (int i=0; i<points; i++) {
+ size += readPoint(leis, pnt);
+ if (i==0) {
+ if (hasStartPoint()) {
+ poly.moveTo(pnt.getX(), pnt.getY());
+ } else {
+ // if this path is connected to the current position (= has no start point)
+ // the first entry is a dummy entry and will be skipped later
+ poly.moveTo(0,0);
+ poly.lineTo(pnt.getX(), pnt.getY());
+ }
+ } else {
+ poly.lineTo(pnt.getX(), pnt.getY());
+ }
+ }
+
+ return size;
+ }
+
+ /**
+ * @return true, if start point is in the list of points. false, if start point is taken from the context
+ */
+ protected boolean hasStartPoint() {
+ return true;
+ }
+
+ @Override
+ protected FillDrawStyle getFillDrawStyle() {
+ // The polygon SHOULD be outlined using the current pen and filled using the current brush and
+ // polygon fill mode. The polygon SHOULD be closed automatically by drawing a line from the last
+ // vertex to the first.
+ return FillDrawStyle.FILL_DRAW;
+ }
+
+ @Override
+ public void draw(HemfGraphics ctx) {
+ ctx.draw(path -> path.append(poly, false), getFillDrawStyle());
+ }
+ }
+
+ /**
+ * The EMR_POLYGON16 record specifies a polygon consisting of two or more vertexes connected by straight lines.
+ * The polygon is outlined by using the current pen and filled by using the current brush and polygon fill mode.
+ * The polygon is closed automatically by drawing a line from the last vertex to the first
+ */
+ public static class EmfPolygon16 extends EmfPolygon {
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.polygon16;
+ }
+
+ @Override
+ protected long readPoint(LittleEndianInputStream leis, Point2D point) {
+ return readPointS(leis, point);
+ }
+ }
+
+ /**
+ * The EMR_POLYLINE record specifies a series of line segments by connecting the points in the
+ * specified array.
+ */
+ public static class EmfPolyline extends EmfPolygon {
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.polyline;
+ }
+
+ @Override
+ protected FillDrawStyle getFillDrawStyle() {
+ // The line segments SHOULD be drawn using the current pen.
+ return FillDrawStyle.DRAW;
+ }
+ }
+
+ /**
+ * The EMR_POLYLINE16 record specifies a series of line segments by connecting the points in the
+ * specified array.
+ */
+ public static class EmfPolyline16 extends EmfPolyline {
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.polyline16;
+ }
+
+ @Override
+ protected long readPoint(LittleEndianInputStream leis, Point2D point) {
+ return readPointS(leis, point);
+ }
+ }
+
+ /**
+ * The EMR_POLYBEZIERTO record specifies one or more Bezier curves based upon the current
+ * position.
+ */
+ public static class EmfPolyBezierTo extends EmfPolyBezier {
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.polyBezierTo;
+ }
+
+ @Override
+ protected boolean hasStartPoint() {
+ return false;
+ }
+
+ @Override
+ public void draw(HemfGraphics ctx) {
+ polyTo(ctx, poly, getFillDrawStyle());
+ }
+ }
+
+ /**
+ * The EMR_POLYBEZIERTO16 record specifies one or more Bezier curves based on the current
+ * position.
+ */
+ public static class EmfPolyBezierTo16 extends EmfPolyBezierTo {
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.polyBezierTo16;
+ }
+
+ @Override
+ protected long readPoint(LittleEndianInputStream leis, Point2D point) {
+ return readPointS(leis, point);
+ }
+ }
+
+ /** The EMR_POLYLINETO record specifies one or more straight lines based upon the current position. */
+ public static class EmfPolylineTo extends EmfPolyline {
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.polylineTo;
+ }
+
+ @Override
+ protected boolean hasStartPoint() {
+ return false;
+ }
+
+ @Override
+ public void draw(HemfGraphics ctx) {
+ polyTo(ctx, poly, getFillDrawStyle());
+ }
+ }
+
+ /**
+ * The EMR_POLYLINETO16 record specifies one or more straight lines based upon the current position.
+ * A line is drawn from the current position to the first point specified by the points field by using the
+ * current pen. For each additional line, drawing is performed from the ending point of the previous
+ * line to the next point specified by points.
+ */
+ public static class EmfPolylineTo16 extends EmfPolylineTo {
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.polylineTo16;
+ }
+
+ @Override
+ protected long readPoint(LittleEndianInputStream leis, Point2D point) {
+ return readPointS(leis, point);
+ }
+ }
+
+ /**
+ * The EMR_POLYPOLYGON record specifies a series of closed polygons.
+ */
+ public static class EmfPolyPolygon extends HwmfDraw.WmfPolyPolygon implements HemfRecord {
+ private final Rectangle2D bounds = new Rectangle2D.Double();
+
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.polyPolygon;
+ }
+
+ 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 polygons.
+ long numberOfPolygons = leis.readUInt();
+ // A 32-bit unsigned integer that specifies the total number of points in all polygons.
+ long count = Math.min(16384, leis.readUInt());
+
+ size += 2 * LittleEndianConsts.INT_SIZE;
+
+ // An array of 32-bit unsigned integers that specifies the point count for each polygon.
+ long[] polygonPointCount = new long[(int)numberOfPolygons];
+
+ size += numberOfPolygons * LittleEndianConsts.INT_SIZE;
+
+ for (int i=0; i<numberOfPolygons; i++) {
+ polygonPointCount[i] = leis.readUInt();
+ }
+
+ Point2D pnt = new Point2D.Double();
+ for (long nPoints : polygonPointCount) {
+ /**
+ * An array of WMF PointL objects that specifies the points for all polygons in logical units.
+ * The number of points is specified by the Count field value.
+ */
+ Path2D poly = new Path2D.Double(Path2D.WIND_EVEN_ODD, (int)nPoints);
+ for (int i=0; i<nPoints; i++) {
+ size += readPoint(leis, pnt);
+ if (i == 0) {
+ poly.moveTo(pnt.getX(), pnt.getY());
+ } else {
+ poly.lineTo(pnt.getX(), pnt.getY());
+ }
+ }
+ if (isClosed()) {
+ poly.closePath();
+ }
+ polyList.add(poly);
+ }
+ return size;
+ }
+
+
+ @Override
+ public void draw(HemfGraphics ctx) {
+ Shape shape = getShape(ctx);
+ if (shape == null) {
+ return;
+ }
+
+ ctx.draw(path -> path.append(shape, false), getFillDrawStyle());
+ }
+ }
+
+ /**
+ * The EMR_POLYPOLYGON16 record specifies a series of closed polygons. Each polygon is outlined
+ * using the current pen, and filled using the current brush and polygon fill mode.
+ * The polygons drawn by this record can overlap.
+ */
+ public static class EmfPolyPolygon16 extends EmfPolyPolygon {
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.polyPolygon16;
+ }
+
+ @Override
+ protected long readPoint(LittleEndianInputStream leis, Point2D point) {
+ return readPointS(leis, point);
+ }
+ }
+
+ /**
+ * The EMR_POLYPOLYLINE record specifies multiple series of connected line segments.
+ */
+ public static class EmfPolyPolyline extends EmfPolyPolygon {
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.polyPolyline;
+ }
+
+ @Override
+ protected boolean isClosed() {
+ return false;
+ }
+
+ @Override
+ protected FillDrawStyle getFillDrawStyle() {
+ return FillDrawStyle.DRAW;
+ }
+ }
+
+ /** The EMR_POLYPOLYLINE16 record specifies multiple series of connected line segments. */
+ public static class EmfPolyPolyline16 extends EmfPolyPolyline {
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.polyPolyline16;
+ }
+
+ @Override
+ protected long readPoint(LittleEndianInputStream leis, Point2D point) {
+ return readPointS(leis, point);
+ }
+ }
+
+ /**
+ * The EMR_SETPIXELV record defines the color of the pixel at the specified logical coordinates.
+ */
+ public static class EmfSetPixelV extends HwmfDraw.WmfSetPixel implements HemfRecord {
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.setPixelV;
+ }
+
+ @Override
+ public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+ long size = readPointL(leis, point);
+ size += colorRef.init(leis);
+ return size;
+ }
+ }
+
+ /**
+ * The EMR_MOVETOEX record specifies coordinates of the new current position, in logical units.
+ */
+ public static class EmfSetMoveToEx extends HwmfDraw.WmfMoveTo implements HemfRecord {
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.setMoveToEx;
+ }
+
+ @Override
+ public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+ return readPointL(leis, point);
+ }
+
+ @Override
+ public void draw(final HemfGraphics ctx) {
+ ctx.draw((path) -> path.moveTo(point.getX(), point.getY()), FillDrawStyle.NONE);
+ }
+ }
+
+ /**
+ * The EMR_ARC record specifies an elliptical arc.
+ * It resets the current position to the end point of the arc.
+ */
+ public static class EmfArc extends HwmfDraw.WmfArc implements HemfRecord {
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.arc;
+ }
+
+ @Override
+ public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+ long size = readRectL(leis, bounds);
+ size += readPointL(leis, startPoint);
+ size += readPointL(leis, endPoint);
+ return size;
+ }
+
+ @Override
+ public void draw(HemfGraphics ctx) {
+ ctx.draw(path -> path.append(getShape(), false), getFillDrawStyle());
+ }
+ }
+
+ /**
+ * The EMR_CHORD record specifies a chord, which is a region bounded by the intersection of an
+ * ellipse and a line segment, called a secant. The chord is outlined by using the current pen
+ * and filled by using the current brush.
+ */
+ public static class EmfChord extends HwmfDraw.WmfChord implements HemfRecord {
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.chord;
+ }
+
+ @Override
+ public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+ long size = readRectL(leis, bounds);
+ size += readPointL(leis, startPoint);
+ size += readPointL(leis, endPoint);
+ return size;
+ }
+
+ @Override
+ public void draw(HemfGraphics ctx) {
+ ctx.draw(path -> path.append(getShape(), false), getFillDrawStyle());
+ }
+ }
+
+ /**
+ * The EMR_PIE record specifies a pie-shaped wedge bounded by the intersection of an ellipse and two
+ * radials. The pie is outlined by using the current pen and filled by using the current brush.
+ */
+ public static class EmfPie extends HwmfDraw.WmfPie implements HemfRecord {
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.pie;
+ }
+
+ @Override
+ public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+ long size = readRectL(leis, bounds);
+ size += readPointL(leis, startPoint);
+ size += readPointL(leis, endPoint);
+ return size;
+ }
+
+ @Override
+ public void draw(HemfGraphics ctx) {
+ ctx.draw(path -> path.append(getShape(), false), getFillDrawStyle());
+ }
+ }
+
+ /**
+ * The EMR_ELLIPSE record specifies an ellipse. The center of the ellipse is the center of the specified
+ * bounding rectangle. The ellipse is outlined by using the current pen and is filled by using the current
+ * brush.
+ */
+ public static class EmfEllipse extends HwmfDraw.WmfEllipse implements HemfRecord {
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.ellipse;
+ }
+
+ @Override
+ public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+ return readRectL(leis, bounds);
+ }
+
+ @Override
+ public void draw(HemfGraphics ctx) {
+ ctx.draw(path -> path.append(getShape(), false), FillDrawStyle.FILL_DRAW);
+ }
+ }
+
+ /**
+ * The EMR_RECTANGLE record draws a rectangle. The rectangle is outlined by using the current pen
+ * and filled by using the current brush.
+ */
+ public static class EmfRectangle extends HwmfDraw.WmfRectangle implements HemfRecord {
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.rectangle;
+ }
+
+ @Override
+ public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+ return readRectL(leis, bounds);
+ }
+
+ @Override
+ public void draw(HemfGraphics ctx) {
+ ctx.draw(path -> path.append(normalizeBounds(bounds), false), FillDrawStyle.FILL_DRAW);
+ }
+ }
+
+ /**
+ * The EMR_ROUNDRECT record specifies a rectangle with rounded corners. The rectangle is outlined
+ * by using the current pen and filled by using the current brush.
+ */
+ public static class EmfRoundRect extends HwmfDraw.WmfRoundRect implements HemfRecord {
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.roundRect;
+ }
+
+ @Override
+ public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+ long size = readRectL(leis, bounds);
+
+ // A 32-bit unsigned integer that defines the x-coordinate of the point.
+ width = (int)leis.readUInt();
+ height = (int)leis.readUInt();
+
+ return size + 2*LittleEndianConsts.INT_SIZE;
+ }
+
+ @Override
+ public void draw(HemfGraphics ctx) {
+ ctx.draw(path -> path.append(getShape(), false), FillDrawStyle.FILL_DRAW);
+ }
+ }
+
+ /**
+ * The EMR_LINETO record specifies a line from the current position up to, but not including, the
+ * specified point. It resets the current position to the specified point.
+ */
+ public static class EmfLineTo extends HwmfDraw.WmfLineTo implements HemfRecord {
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.lineTo;
+ }
+
+ @Override
+ public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+ return readPointL(leis, point);
+ }
+
+ @Override
+ public void draw(final HemfGraphics ctx) {
+ ctx.draw((path) -> path.lineTo(point.getX(), point.getY()), FillDrawStyle.DRAW);
+ }
+ }
+
+ /**
+ * The EMR_ARCTO record specifies an elliptical arc.
+ * It resets the current position to the end point of the arc.
+ */
+ public static class EmfArcTo extends HwmfDraw.WmfArc implements HemfRecord {
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.arcTo;
+ }
+
+ @Override
+ public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+ long size = readRectL(leis, bounds);
+ size += readPointL(leis, startPoint);
+ size += readPointL(leis, endPoint);
+ return size;
+ }
+
+ @Override
+ public void draw(final HemfGraphics ctx) {
+ final Arc2D arc = getShape();
+ ctx.draw((path) -> path.append(arc, true), getFillDrawStyle());
+ }
+ }
+
+ /** The EMR_POLYDRAW record specifies a set of line segments and Bezier curves. */
+ public static class EmfPolyDraw extends HwmfDraw.WmfPolygon implements HemfRecord {
+ private final Rectangle2D bounds = new Rectangle2D.Double();
+
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.polyDraw;
+ }
+
+ 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);
+ int count = (int)leis.readUInt();
+ size += LittleEndianConsts.INT_SIZE;
+ Point2D points[] = new Point2D[count];
+ for (int i=0; i<count; i++) {
+ points[i] = new Point2D.Double();
+ size += readPoint(leis, points[i]);
+ }
+
+ poly = new Path2D.Double(Path2D.WIND_EVEN_ODD, count);
+
+ for (int i=0; i<count; i++) {
+ int mode = leis.readUByte();
+ switch (mode & 0x06) {
+ // PT_LINETO
+ // Specifies that a line is to be drawn from the current position to this point, which
+ // then becomes the new current position.
+ case 0x02:
+ poly.lineTo(points[i].getX(), points[i].getY());
+ break;
+ // PT_BEZIERTO
+ // Specifies that this point is a control point or ending point for a Bezier curve.
+ // PT_BEZIERTO types always occur in sets of three.
+ // The current position defines the starting point for the Bezier curve.
+ // The first two PT_BEZIERTO points are the control points,
+ // and the third PT_BEZIERTO point is the ending point.
+ // The ending point becomes the new current position.
+ // If there are not three consecutive PT_BEZIERTO points, an error results.
+ case 0x04:
+ int mode2 = leis.readUByte();
+ int mode3 = leis.readUByte();
+ assert(mode2 == 0x04 && (mode3 == 0x04 || mode3 == 0x05));
+ poly.curveTo(
+ points[i].getX(), points[i].getY(),
+ points[i+1].getX(), points[i+1].getY(),
+ points[i+2].getX(), points[i+2].getY()
+ );
+ // update mode for closePath handling below
+ mode = mode3;
+ i+=2;
+ break;
+ // PT_MOVETO
+ // Specifies that this point starts a disjoint figure. This point becomes the new current position.
+ case 0x06:
+ poly.moveTo(points[i].getX(), points[i].getY());
+ break;
+ default:
+ // TODO: log error
+ break;
+ }
+
+ // PT_CLOSEFIGURE
+ // A PT_LINETO or PT_BEZIERTO type can be combined with this value by using the bitwise operator OR
+ // to indicate that the corresponding point is the last point in a figure and the figure is closed.
+ // The current position is set to the ending point of the closing line.
+ if ((mode & 0x01) == 0x01) {
+ this.poly.closePath();
+ }
+ }
+ size += count;
+ return size;
+ }
+
+ @Override
+ protected FillDrawStyle getFillDrawStyle() {
+ // Draws a set of line segments and Bezier curves.
+ return FillDrawStyle.DRAW;
+ }
+
+ @Override
+ public void draw(HemfGraphics ctx) {
+ ctx.draw(path -> path.append(poly, false), getFillDrawStyle());
+ }
+ }
+
+ public static class EmfPolyDraw16 extends EmfPolyDraw {
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.polyDraw16;
+ }
+
+ protected long readPoint(LittleEndianInputStream leis, Point2D point) {
+ return readPointS(leis, point);
+ }
+ }
+
+ /**
+ * This record opens a path bracket in the current playback device context.
+ *
+ * After a path bracket is open, an application can begin processing records to define
+ * the points that lie in the path. An application MUST close an open path bracket by
+ * processing the EMR_ENDPATH record.
+ *
+ * When an application processes the EMR_BEGINPATH record, all previous paths
+ * MUST be discarded from the playback device context.
+ */
+ public static class EmfBeginPath implements HemfRecord {
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.beginPath;
+ }
+
+ @Override
+ public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+ return 0;
+ }
+
+ @Override
+ public void draw(HemfGraphics ctx) {
+ final HemfDrawProperties prop = ctx.getProperties();
+ prop.setPath(new Path2D.Double());
+ prop.setUsePathBracket(true);
+ }
+
+ @Override
+ public String toString() {
+ return "{}";
+ }
+ }
+
+ /**
+ * This record closes a path bracket and selects the path defined by the bracket into
+ * the playback device context.
+ */
+ public static class EmfEndPath implements HemfRecord {
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.endPath;
+ }
+
+ @Override
+ public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+ return 0;
+ }
+
+ @Override
+ public void draw(HemfGraphics ctx) {
+ final HemfDrawProperties prop = ctx.getProperties();
+ prop.setUsePathBracket(false);
+ }
+
+ @Override
+ public String toString() {
+ return "{}";
+ }
+ }
+
+ /**
+ * This record aborts a path bracket or discards the path from a closed path bracket.
+ */
+ public static class EmfAbortPath implements HemfRecord {
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.abortPath;
+ }
+
+ @Override
+ public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+ return 0;
+ }
+
+ @Override
+ public void draw(HemfGraphics ctx) {
+ final HemfDrawProperties prop = ctx.getProperties();
+ prop.setPath(null);
+ prop.setUsePathBracket(false);
+ }
+
+ @Override
+ public String toString() {
+ return "{}";
+ }
+ }
+
+ /**
+ * This record closes an open figure in a path.
+ *
+ * Processing the EMR_CLOSEFIGURE record MUST close the figure by drawing a line
+ * from the current position to the first point of the figure, and then it MUST connect
+ * the lines by using the line join style. If a figure is closed by processing the
+ * EMR_LINETO record instead of the EMR_CLOSEFIGURE record, end caps are
+ * used to create the corner instead of a join.
+ *
+ * The EMR_CLOSEFIGURE record SHOULD only be used if there is an open path
+ * bracket in the playback device context.
+ *
+ * A figure in a path is open unless it is explicitly closed by processing this record.
+ * Note: A figure can be open even if the current point and the starting point of the
+ * figure are the same.
+ *
+ * After processing the EMR_CLOSEFIGURE record, adding a line or curve to the path
+ * MUST start a new figure.
+ */
+ public static class EmfCloseFigure implements HemfRecord {
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.closeFigure;
+ }
+
+ @Override
+ public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+ return 0;
+ }
+
+ @Override
+ public void draw(HemfGraphics ctx) {
+ final HemfDrawProperties prop = ctx.getProperties();
+ final Path2D path = prop.getPath();
+ if (path != null && path.getCurrentPoint() != null) {
+ path.closePath();
+ prop.setLocation(path.getCurrentPoint());
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "{}";
+ }
+ }
+
+ /**
+ * This record transforms any curves in the selected path into the playback device
+ * context; each curve MUST be turned into a sequence of lines.
+ */
+ public static class EmfFlattenPath implements HemfRecord {
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.flattenPath;
+ }
+
+ @Override
+ public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+ return 0;
+ }
+ }
+
+ /**
+ * This record redefines the current path as the area that would be painted if the path
+ * were drawn using the pen currently selected into the playback device context.
+ */
+ public static class EmfWidenPath implements HemfRecord {
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.widenPath;
+ }
+
+ @Override
+ public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return "{}";
+ }
+ }
+
+ /**
+ * The EMR_STROKEPATH record renders the specified path by using the current pen.
+ */
+ public static class EmfStrokePath implements HemfRecord {
+ protected final Rectangle2D bounds = new Rectangle2D.Double();
+
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.strokePath;
+ }
+
+ @Override
+ public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+ // A 128-bit WMF RectL object, which specifies bounding rectangle, in device units
+ return (recordSize == 0) ? 0 : readRectL(leis, bounds);
+ }
+
+ @Override
+ public void draw(HemfGraphics ctx) {
+ HemfDrawProperties props = ctx.getProperties();
+ Path2D path = props.getPath();
+ path.setWindingRule(ctx.getProperties().getWindingRule());
+ ctx.draw(path);
+ }
+
+ @Override
+ public String toString() {
+ return boundsToString(bounds);
+ }
+ }
+
+
+ /**
+ * The EMR_FILLPATH record closes any open figures in the current path and fills the path's interior by
+ * using the current brush and polygon-filling mode.
+ */
+ public static class EmfFillPath extends EmfStrokePath {
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.fillPath;
+ }
+
+ @Override
+ public void draw(HemfGraphics ctx) {
+ final HemfDrawProperties prop = ctx.getProperties();
+ final Path2D origPath = prop.getPath();
+ if (origPath.getCurrentPoint() == null) {
+ return;
+ }
+ final Path2D path = (Path2D)origPath.clone();
+ path.closePath();
+ path.setWindingRule(ctx.getProperties().getWindingRule());
+ ctx.fill(path);
+ }
+ }
+
+ /**
+ * The EMR_STROKEANDFILLPATH record closes any open figures in a path, strokes the outline of the
+ * path by using the current pen, and fills its interior by using the current brush.
+ */
+ public static class EmfStrokeAndFillPath extends EmfStrokePath {
+ protected final Rectangle2D bounds = new Rectangle2D.Double();
+
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.strokeAndFillPath;
+ }
+
+ @Override
+ public void draw(HemfGraphics ctx) {
+ HemfDrawProperties props = ctx.getProperties();
+ Path2D path = props.getPath();
+ path.closePath();
+ path.setWindingRule(ctx.getProperties().getWindingRule());
+ ctx.fill(path);
+ ctx.draw(path);
+ }
+ }
+
+ static long readRectL(LittleEndianInputStream leis, Rectangle2D bounds) {
+ /* A 32-bit signed integer that defines the x coordinate, in logical coordinates,
+ * of the ... corner of the rectangle.
+ */
+ final int left = leis.readInt();
+ final int top = leis.readInt();
+ final int right = leis.readInt();
+ final int bottom = leis.readInt();
+ bounds.setRect(left, top, right-left, bottom-top);
+
+ return 4 * LittleEndianConsts.INT_SIZE;
+ }
+
+ static long readPointS(LittleEndianInputStream leis, Point2D point) {
+ // x (2 bytes): A 16-bit signed integer that defines the horizontal (x) coordinate of the point.
+ final int x = leis.readShort();
+ // y (2 bytes): A 16-bit signed integer that defines the vertical (y) coordinate of the point.
+ final int y = leis.readShort();
+ point.setLocation(x, y);
+
+ return 2*LittleEndianConsts.SHORT_SIZE;
+
+ }
+ static long readPointL(LittleEndianInputStream leis, Point2D point) {
+ // x (4 bytes): A 32-bit signed integer that defines the horizontal (x) coordinate of the point.
+ final int x = leis.readInt();
+ // y (4 bytes): A 32-bit signed integer that defines the vertical (y) coordinate of the point.
+ final int y = leis.readInt();
+ point.setLocation(x, y);
+
+ return 2*LittleEndianConsts.INT_SIZE;
+
+ }
+
+ static long readDimensionFloat(LittleEndianInputStream leis, Dimension2D dimension) {
+ final double width = leis.readFloat();
+ final double height = leis.readFloat();
+ dimension.setSize(width, height);
+ return 2*LittleEndianConsts.INT_SIZE;
+ }
+
+ static long readDimensionInt(LittleEndianInputStream leis, Dimension2D dimension) {
+ // although the spec says "use unsigned ints", there are examples out there using signed ints
+ final double width = leis.readInt();
+ final double height = leis.readInt();
+ dimension.setSize(width, height);
+ return 2*LittleEndianConsts.INT_SIZE;
+ }
+
+ private static void polyTo(final HemfGraphics ctx, final Path2D poly, FillDrawStyle fillDrawStyle) {
+ if (poly.getCurrentPoint() == null) {
+ return;
+ }
+
+ final PathIterator pi = poly.getPathIterator(null);
+ // ignore empty polys and dummy start point (moveTo)
+ pi.next();
+ if (pi.isDone()) {
+ return;
+ }
+
+ ctx.draw((path) -> path.append(pi, true), fillDrawStyle);
+ }
+}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java
new file mode 100644
index 0000000000..8e19e87116
--- /dev/null
+++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java
@@ -0,0 +1,719 @@
+/* ====================================================================
+ 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 org.apache.poi.hemf.record.emf.HemfDraw.readPointL;
+import static org.apache.poi.hemf.record.emf.HemfDraw.readRectL;
+import static org.apache.poi.hemf.record.emf.HemfRecordIterator.HEADER_SIZE;
+import static org.apache.poi.hwmf.record.HwmfDraw.boundsToString;
+import static org.apache.poi.hwmf.record.HwmfDraw.pointToString;
+
+import java.awt.Shape;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Area;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.poi.hemf.draw.HemfDrawProperties;
+import org.apache.poi.hemf.draw.HemfGraphics;
+import org.apache.poi.hwmf.draw.HwmfGraphics;
+import org.apache.poi.hwmf.record.HwmfBitmapDib;
+import org.apache.poi.hwmf.record.HwmfColorRef;
+import org.apache.poi.hwmf.record.HwmfDraw;
+import org.apache.poi.hwmf.record.HwmfFill;
+import org.apache.poi.hwmf.record.HwmfFill.ColorUsage;
+import org.apache.poi.hwmf.record.HwmfRegionMode;
+import org.apache.poi.hwmf.record.HwmfTernaryRasterOp;
+import org.apache.poi.util.IOUtils;
+import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.LittleEndianConsts;
+import org.apache.poi.util.LittleEndianInputStream;
+
+public class HemfFill {
+ private static final int MAX_RECORD_LENGTH = 10_000_000;
+
+ /**
+ * The EMR_SETPOLYFILLMODE record defines polygon fill mode.
+ */
+ public static class EmfSetPolyfillMode extends HwmfFill.WmfSetPolyfillMode implements HemfRecord {
+
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.setPolyfillMode;
+ }
+
+ @Override
+ public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+ // A 32-bit unsigned integer that specifies the polygon fill mode and
+ // MUST be in the PolygonFillMode enumeration.
+ polyFillMode = HwmfPolyfillMode.valueOf((int)leis.readUInt());
+ return LittleEndianConsts.INT_SIZE;
+ }
+ }
+
+ public static class EmfExtFloodFill extends HwmfFill.WmfExtFloodFill implements HemfRecord {
+
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.extFloodFill;
+ }
+
+ @Override
+ public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+ long size = readPointL(leis, start);
+ size += colorRef.init(leis);
+ // A 32-bit unsigned integer that specifies how to use the Color value to determine the area for
+ // the flood fill operation. The value MUST be in the FloodFill enumeration
+ mode = (int)leis.readUInt();
+ return size + LittleEndianConsts.INT_SIZE;
+ }
+ }
+
+ /**
+ * The EMR_STRETCHBLT 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, stretching or
+ * compressing the output to fit the dimensions of the destination, if necessary.
+ */
+ public static class EmfStretchBlt extends HwmfFill.WmfStretchDib implements HemfRecord {
+ protected final Rectangle2D bounds = new Rectangle2D.Double();
+
+ /** An XForm object that specifies a world-space to page-space transform to apply to the source bitmap. */
+ protected final AffineTransform xFormSrc = new AffineTransform();
+
+ /** A WMF ColorRef object that specifies the background color of the source bitmap. */
+ protected final HwmfColorRef bkColorSrc = new HwmfColorRef();
+
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.stretchBlt;
+ }
+
+ @Override
+ public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+ int startIdx = leis.getReadIndex();
+
+ long size = readRectL(leis, bounds);
+
+ size += readBounds2(leis, this.dstBounds);
+
+ // A 32-bit unsigned integer that specifies the raster operation code. This code defines how the
+ // color data of the source rectangle is to be combined with the color data of the destination
+ // rectangle and optionally a brush pattern, to achieve the final color.
+ int rasterOpIndex = (int)leis.readUInt();
+
+ rasterOperation = HwmfTernaryRasterOp.valueOf(rasterOpIndex >>> 16);
+
+ size += LittleEndianConsts.INT_SIZE;
+
+ final Point2D srcPnt = new Point2D.Double();
+ size += readPointL(leis, srcPnt);
+
+ size += readXForm(leis, xFormSrc);
+
+ size += bkColorSrc.init(leis);
+
+ colorUsage = 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();
+ size += 3*LittleEndianConsts.INT_SIZE;
+ if (size >= recordSize) {
+ return size;
+ }
+
+ // 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();
+ size += 2*LittleEndianConsts.INT_SIZE;
+
+ if (size >= recordSize) {
+ return size;
+ }
+
+ if (srcEqualsDstDimension()) {
+ srcBounds.setRect(srcPnt.getX(), srcPnt.getY(), dstBounds.getWidth(), dstBounds.getHeight());
+ } else {
+ int srcWidth = leis.readInt();
+ int srcHeight = leis.readInt();
+ size += 2 * LittleEndianConsts.INT_SIZE;
+ srcBounds.setRect(srcPnt.getX(), srcPnt.getY(), srcWidth, srcHeight);
+ }
+
+ size += readBitmap(leis, bitmap, startIdx, offBmiSrc, cbBmiSrc, offBitsSrc, cbBitsSrc);
+
+ return size;
+ }
+
+ protected boolean srcEqualsDstDimension() {
+ return false;
+ }
+
+ @Override
+ public void draw(HemfGraphics ctx) {
+ HemfDrawProperties prop = ctx.getProperties();
+ prop.setBackgroundColor(this.bkColorSrc);
+ super.draw(ctx);
+ }
+
+ @Override
+ public String toString() {
+ return
+ "{ bounds: "+boundsToString(bounds)+
+ ", xFormSrc: { scaleX: "+xFormSrc.getScaleX()+", shearX: "+xFormSrc.getShearX()+", transX: "+xFormSrc.getTranslateX()+", scaleY: "+xFormSrc.getScaleY()+", shearY: "+xFormSrc.getShearY()+", transY: "+xFormSrc.getTranslateY()+" }"+
+ ", bkColorSrc: "+bkColorSrc+
+ ","+super.toString().substring(1);
+ }
+ }
+
+ /**
+ * The EMR_STRETCHDIBITS 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, stretching or compressing the output to fit the dimensions of the destination, if necessary.
+ */
+ public static class EmfStretchDiBits extends HwmfFill.WmfStretchDib implements HemfRecord {
+ protected final Rectangle2D bounds = new Rectangle2D.Double();
+
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.stretchDiBits;
+ }
+
+ @Override
+ public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+ final int startIdx = leis.getReadIndex();
+
+ long size = readRectL(leis, bounds);
+
+ // A 32-bit signed integer that specifies the logical x-coordinate of the upper-left
+ // corner of the destination rectangle.
+ int xDest = leis.readInt();
+ int yDest = leis.readInt();
+ size += 2*LittleEndianConsts.INT_SIZE;
+
+ size += readBounds2(leis, srcBounds);
+
+ // A 32-bit unsigned integer that specifies the offset, in bytes from the start
+ // of this record to the source bitmap header.
+ int offBmiSrc = (int)leis.readUInt();
+
+ // A 32-bit unsigned integer that specifies the size, in bytes, of the source bitmap header.
+ 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.
+ int offBitsSrc = (int)leis.readUInt();
+
+ // A 32-bit unsigned integer that specifies the size, in bytes, of the source bitmap bits.
+ 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
+ colorUsage = ColorUsage.valueOf(leis.readInt());
+
+ // A 32-bit unsigned integer that specifies a raster operation code.
+ // These codes define how the color data of the source rectangle is to be combined with the color data
+ // of the destination rectangle and optionally a brush pattern, to achieve the final color.
+ // The value MUST be in the WMF Ternary Raster Operation enumeration
+ int rasterOpIndex = (int)leis.readUInt();
+ rasterOperation = HwmfTernaryRasterOp.valueOf(rasterOpIndex >>> 16);
+
+ // A 32-bit signed integer that specifies the logical width of the destination rectangle.
+ int cxDest = leis.readInt();
+
+ // A 32-bit signed integer that specifies the logical height of the destination rectangle.
+ int cyDest = leis.readInt();
+
+ dstBounds.setRect(xDest, yDest, cxDest, cyDest);
+
+ size += 8*LittleEndianConsts.INT_SIZE;
+
+ size += readBitmap(leis, bitmap, startIdx, offBmiSrc, cbBmiSrc, offBitsSrc, cbBitsSrc);
+
+ return size;
+ }
+ }
+
+ /**
+ * 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<Rectangle2D> 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);
+ ctx.fill(getShape());
+ }
+
+ protected Shape getShape() {
+ return getRgnShape(rgnRects);
+ }
+ }
+
+ /** 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<Rectangle2D> 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;
+ }
+
+ protected Shape getShape() {
+ return getRgnShape(rgnRects);
+ }
+ }
+
+ /**
+ * 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<Rectangle2D> 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;
+ }
+
+ protected Shape getShape() {
+ return getRgnShape(rgnRects);
+ }
+ }
+
+ public static class EmfExtSelectClipRgn implements HemfRecord {
+ protected HwmfRegionMode regionMode;
+ protected final List<Rectangle2D> 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 = HwmfRegionMode.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 != HwmfRegionMode.RGN_COPY) {
+ size += readRgnData(leis, rgnRects);
+ }
+ return size;
+ }
+
+ protected Shape getShape() {
+ return getRgnShape(rgnRects);
+ }
+
+ @Override
+ public void draw(HemfGraphics ctx) {
+ HemfDrawProperties prop = ctx.getProperties();
+ ctx.setClip(getShape(), regionMode, true);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("{ regionMode: '"+regionMode+"'");
+ sb.append(", regions: [");
+ boolean isFirst = true;
+ for (Rectangle2D r : rgnRects) {
+ if (!isFirst) {
+ sb.append(",");
+ }
+ isFirst = false;
+ sb.append(boundsToString(r));
+ }
+ sb.append("]}");
+ return sb.toString();
+ }
+ }
+
+ 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;
+ }
+ }
+
+ /**
+ * The EMR_SETDIBITSTODEVICE record specifies a block transfer of pixels from specified scanlines of
+ * a source bitmap to a destination rectangle.
+ */
+ 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;
+ }
+
+ @Override
+ public String toString() {
+ return
+ "{ bounds: " + boundsToString(bounds) +
+ ", dest: " + pointToString(dest) +
+ ", src: " + boundsToString(src) +
+ ", usageSrc: '" + usageSrc + "'" +
+ ", bitmap: " + bitmap +
+ "}";
+ }
+ }
+
+ static long readBitmap(final LittleEndianInputStream leis, final HwmfBitmapDib bitmap,
+ final int startIdx, final int offBmi, final int cbBmi, final int offBits, int cbBits)
+ throws IOException {
+ if (offBmi == 0) {
+ return 0;
+ }
+
+ final int offCurr = leis.getReadIndex()-(startIdx-HEADER_SIZE);
+ final int undefinedSpace1 = offBmi-offCurr;
+ if (undefinedSpace1 < 0) {
+ return 0;
+ }
+
+ final int undefinedSpace2 = offBits-offCurr-cbBmi-undefinedSpace1;
+ assert(undefinedSpace2 >= 0);
+
+ leis.skipFully(undefinedSpace1);
+
+ if (cbBmi == 0 || cbBits == 0) {
+ return undefinedSpace1;
+ }
+
+ final int dibSize = cbBmi+cbBits;
+ if (undefinedSpace2 == 0) {
+ return undefinedSpace1 + bitmap.init(leis, dibSize);
+ }
+
+ final ByteArrayOutputStream bos = new ByteArrayOutputStream(cbBmi+cbBits);
+ final long cbBmiSrcAct = IOUtils.copy(leis, bos, cbBmi);
+ assert (cbBmiSrcAct == cbBmi);
+ leis.skipFully(undefinedSpace2);
+ final long cbBitsSrcAct = IOUtils.copy(leis, bos, cbBits);
+ assert (cbBitsSrcAct == cbBits);
+
+ final LittleEndianInputStream leisDib = new LittleEndianInputStream(new ByteArrayInputStream(bos.toByteArray()));
+ final int dibSizeAct = bitmap.init(leisDib, dibSize);
+ assert (dibSizeAct <= dibSize);
+ return undefinedSpace1 + cbBmi + undefinedSpace2 + cbBits;
+ }
+
+
+ static long readRgnData(final LittleEndianInputStream leis, final List<Rectangle2D> rgnRects) {
+ // *** RegionDataHeader ***
+ // A 32-bit unsigned integer that specifies the size of this object in bytes. This MUST be 0x00000020.
+ long rgnHdrSize = leis.readUInt();
+ assert(rgnHdrSize == 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<rgnCntRect; i++) {
+ Rectangle2D rgnRct = new Rectangle2D.Double();
+ size += readRectL(leis, rgnRct);
+ rgnRects.add(rgnRct);
+ }
+ return size;
+ }
+
+
+ static int readBounds2(LittleEndianInputStream leis, Rectangle2D bounds) {
+ /**
+ * The 32-bit signed integers that defines the corners of the bounding rectangle.
+ */
+ int x = leis.readInt();
+ int y = leis.readInt();
+ int w = leis.readInt();
+ int h = leis.readInt();
+
+ bounds.setRect(x, y, w, h);
+
+ return 4 * LittleEndianConsts.INT_SIZE;
+ }
+
+ static int readXForm(LittleEndianInputStream leis, AffineTransform xform) {
+ // mapping <java AffineTransform> = <xform>
+
+ // m00 (scaleX) = eM11 (Horizontal scaling component)
+ double m00 = leis.readFloat();
+
+ // m01 (shearX) = eM12 (Horizontal proportionality constant)
+ double m01 = leis.readFloat();
+
+ // m10 (shearY) = eM21 (Vertical proportionality constant)
+ double m10 = leis.readFloat();
+
+ // m11 (scaleY) = eM22 (Vertical scaling component)
+ double m11 = leis.readFloat();
+
+ // m02 (translateX) = eDx (The horizontal translation component, in logical units.)
+ double m02 = leis.readFloat();
+
+ // m12 (translateY) = eDy (The vertical translation component, in logical units.)
+ double m12 = leis.readFloat();
+
+ xform.setTransform(m00, m10, m01, m11, m02, m12);
+
+ return 6 * LittleEndian.INT_SIZE;
+ }
+
+ protected static Shape getRgnShape(List<Rectangle2D> rgnRects) {
+ if (rgnRects.size() == 1) {
+ return rgnRects.get(0);
+ }
+ final Area frame = new Area();
+ rgnRects.forEach((rct) -> frame.add(new Area(rct)));
+ return frame;
+ }
+}
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..a7eb4c8b1c
--- /dev/null
+++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFont.java
@@ -0,0 +1,496 @@
+/* ====================================================================
+ 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;
+
+ @Override
+ public String toString() {
+ return "{ designVectorLen: " + (designVector == null ? 0 : designVector.length) + " }";
+ }
+ }
+
+ 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;
+
+ @Override
+ public String toString() {
+ return
+ "{ styleSize: " + styleSize +
+ ", vendorId: " + vendorId +
+ ", culture: " + culture +
+ ", familyType: '" + familyType + "'" +
+ ", serifStyle: '" + serifStyle + "'" +
+ ", weight: '" + weight + "'" +
+ ", proportion: '" + proportion + "'" +
+ ", contrast: '" + contrast + "'" +
+ ", strokeVariation: '" + strokeVariation + "'" +
+ ", armStyle: '" + armStyle + "'" +
+ ", letterform: '" + letterform + "'" +
+ ", midLine: '" + midLine + "'" +
+ ", 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() != 0x00);
+
+ // An 8-bit unsigned integer that specifies an underlined font if set to 0x01;
+ // otherwise, it MUST be set to 0x00.
+ underline = (leis.readUByte() != 0x00);
+
+ // An 8-bit unsigned integer that specifies a strikeout font if set to 0x01;
+ // otherwise, it MUST be set to 0x00.
+ strikeOut = (leis.readUByte() != 0x00);
+
+ // 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();
+ // some non-conformant applications don't write the magic code in
+ // 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<numAxes; i++) {
+ logEx.designVector[i] = leis.readInt();
+ }
+ }
+ size += (2+numAxes)*LittleEndianConsts.INT_SIZE;
+ }
+
+ return size;
+ }
+
+ @Override
+ public String toString() {
+ return
+ "{ fullname: '" + (fullname == null ? "" : fullname) + "'" +
+ ", style: '" + (style == null ? "" : style) + "'" +
+ ", script: '" + (script == null ? "" : script) + "'" +
+ ", details: " + details +
+ "," + super.toString().substring(1);
+ }
+
+ @Override
+ protected int readString(LittleEndianInputStream leis, StringBuilder sb, int limit) throws IOException {
+ sb.setLength(0);
+ byte buf[] = new byte[limit*2];
+ leis.readFully(buf);
+
+ int b1, b2, readBytes = 0;
+ do {
+ if (readBytes == limit*2) {
+ return -1;
+ }
+
+ b1 = buf[readBytes++];
+ b2 = buf[readBytes++];
+ } while ((b1 != 0 || b2 != 0) && b1 != -1 && b2 != -1 && readBytes <= limit*2);
+
+ sb.append(new String(buf, 0, readBytes-2, StandardCharsets.UTF_16LE));
+
+ return limit*2;
+ }
+}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfHeader.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfHeader.java
new file mode 100644
index 0000000000..6c1cf5cadd
--- /dev/null
+++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfHeader.java
@@ -0,0 +1,207 @@
+/* ====================================================================
+ 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 org.apache.poi.hemf.record.emf.HemfDraw.readDimensionInt;
+import static org.apache.poi.hemf.record.emf.HemfDraw.readRectL;
+import static org.apache.poi.hemf.record.emf.HemfRecordIterator.HEADER_SIZE;
+import static org.apache.poi.hwmf.record.HwmfDraw.boundsToString;
+import static org.apache.poi.hwmf.record.HwmfDraw.dimToString;
+
+import java.awt.geom.Dimension2D;
+import java.awt.geom.Rectangle2D;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.poi.util.Dimension2DDouble;
+import org.apache.poi.util.Internal;
+import org.apache.poi.util.LittleEndianConsts;
+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 final Rectangle2D boundsRectangle = new Rectangle2D.Double();
+ private final Rectangle2D frameRectangle = new Rectangle2D.Double();
+ private long bytes;
+ private long records;
+ private int handles;
+ private String description;
+ private long nPalEntries;
+ private boolean hasExtension1;
+ private long cbPixelFormat;
+ private long offPixelFormat;
+ private long bOpenGL;
+ private boolean hasExtension2;
+ private final Dimension2D deviceDimension = new Dimension2DDouble();
+ private final Dimension2D milliDimension = new Dimension2DDouble();
+ private final Dimension2D microDimension = new Dimension2DDouble();
+
+
+ public Rectangle2D getBoundsRectangle() {
+ return boundsRectangle;
+ }
+
+ public Rectangle2D getFrameRectangle() {
+ return frameRectangle;
+ }
+
+ public long getBytes() {
+ return bytes;
+ }
+
+ public long getRecords() {
+ return records;
+ }
+
+ public int getHandles() {
+ return handles;
+ }
+
+ public String getDescription() { return description; }
+
+ 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 Dimension2D getDeviceDimension() {
+ return deviceDimension;
+ }
+
+ public Dimension2D getMilliDimension() {
+ return milliDimension;
+ }
+
+ public Dimension2D getMicroDimension() {
+ return microDimension;
+ }
+
+ @Override
+ public String toString() {
+ return "HemfHeader{" +
+ "boundsRectangle: " + boundsToString(boundsRectangle) +
+ ", frameRectangle: " + boundsToString(frameRectangle) +
+ ", bytes: " + bytes +
+ ", records: " + records +
+ ", handles: " + handles +
+ ", description: '" + (description == null ? "" : description) + "'" +
+ ", nPalEntries: " + nPalEntries +
+ ", hasExtension1: " + hasExtension1 +
+ ", cbPixelFormat: " + cbPixelFormat +
+ ", offPixelFormat: " + offPixelFormat +
+ ", bOpenGL: " + bOpenGL +
+ ", hasExtension2: " + hasExtension2 +
+ ", deviceDimension: " + dimToString(deviceDimension) +
+ ", microDimension: " + dimToString(microDimension) +
+ ", milliDimension: " + dimToString(milliDimension) +
+ '}';
+ }
+
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.header;
+ }
+
+ @Override
+ public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+ if (recordId != HemfRecordType.header.id) {
+ throw new IOException("Not a valid EMF header. Record type:"+recordId);
+ }
+
+ int startIdx = leis.getReadIndex();
+
+ //bounds
+ long size = readRectL(leis, boundsRectangle);
+ size += readRectL(leis, frameRectangle);
+
+ int recordSignature = leis.readInt();
+ if (recordSignature != 0x464D4520) {
+ throw new IOException("bad record signature: " + recordSignature);
+ }
+
+ long version = leis.readInt();
+ //According to the spec, MSOffice doesn't pay attention to this value.
+ //It _should_ be 0x00010000
+ bytes = leis.readUInt();
+ records = leis.readUInt();
+ handles = leis.readUShort();
+ //reserved
+ leis.skipFully(LittleEndianConsts.SHORT_SIZE);
+
+ int nDescription = (int)leis.readUInt();
+ int offDescription = (int)leis.readUInt();
+ nPalEntries = leis.readUInt();
+
+ size += 8*LittleEndianConsts.INT_SIZE;
+
+ size += readDimensionInt(leis, deviceDimension);
+ size += readDimensionInt(leis, milliDimension);
+
+ if (nDescription > 0 && offDescription > 0) {
+ int skip = (int)(offDescription - (size + HEADER_SIZE));
+ leis.mark(skip+nDescription*2);
+ leis.skipFully(skip);
+ byte[] buf = new byte[(nDescription-1)*2];
+ leis.readFully(buf);
+ description = new String(buf, StandardCharsets.UTF_16LE).replace((char)0, ' ').trim();
+ leis.reset();
+ }
+
+ if (size+12 <= recordSize) {
+ hasExtension1 = true;
+ cbPixelFormat = leis.readUInt();
+ offPixelFormat = leis.readUInt();
+ bOpenGL = leis.readUInt();
+ size += 3*LittleEndianConsts.INT_SIZE;
+ }
+
+ if (size+8 <= recordSize) {
+ hasExtension2 = true;
+ size += readDimensionInt(leis, microDimension);
+ }
+
+ return size;
+ }
+}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java
new file mode 100644
index 0000000000..5e04e932d6
--- /dev/null
+++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java
@@ -0,0 +1,828 @@
+/* ====================================================================
+ 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 org.apache.poi.hemf.record.emf.HemfDraw.readPointL;
+import static org.apache.poi.hemf.record.emf.HemfFill.readBitmap;
+import static org.apache.poi.hemf.record.emf.HemfFill.readXForm;
+import static org.apache.poi.hemf.record.emf.HemfRecordIterator.HEADER_SIZE;
+
+import java.awt.geom.AffineTransform;
+import java.awt.geom.NoninvertibleTransformException;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Function;
+
+import org.apache.poi.hemf.draw.HemfDrawProperties;
+import org.apache.poi.hemf.draw.HemfGraphics;
+import org.apache.poi.hwmf.draw.HwmfDrawProperties;
+import org.apache.poi.hwmf.draw.HwmfGraphics;
+import org.apache.poi.hwmf.record.HwmfBinaryRasterOp;
+import org.apache.poi.hwmf.record.HwmfBitmapDib;
+import org.apache.poi.hwmf.record.HwmfBrushStyle;
+import org.apache.poi.hwmf.record.HwmfColorRef;
+import org.apache.poi.hwmf.record.HwmfFill;
+import org.apache.poi.hwmf.record.HwmfHatchStyle;
+import org.apache.poi.hwmf.record.HwmfMapMode;
+import org.apache.poi.hwmf.record.HwmfMisc;
+import org.apache.poi.hwmf.record.HwmfMisc.WmfSetBkMode;
+import org.apache.poi.hwmf.record.HwmfObjectTableEntry;
+import org.apache.poi.hwmf.record.HwmfPalette.PaletteEntry;
+import org.apache.poi.hwmf.record.HwmfPenStyle;
+import org.apache.poi.hwmf.record.HwmfPenStyle.HwmfLineDash;
+import org.apache.poi.util.LittleEndianConsts;
+import org.apache.poi.util.LittleEndianInputStream;
+
+public class HemfMisc {
+
+ public enum HemfModifyWorldTransformMode {
+ /**
+ * Reset the current transform using the identity matrix.
+ * In this mode, the specified transform data is ignored.
+ */
+ MWT_IDENTITY(1),
+ /**
+ * Multiply the current transform. In this mode, the specified transform data is the left multiplicand,
+ * and the transform that is currently defined in the playback device context is the right multiplicand.
+ */
+ MWT_LEFTMULTIPLY(2),
+ /**
+ * Multiply the current transform. In this mode, the specified transform data is the right multiplicand,
+ * and the transform that is currently defined in the playback device context is the left multiplicand.
+ */
+ MWT_RIGHTMULTIPLY(3),
+ /**
+ * Perform the function of an EMR_SETWORLDTRANSFORM record
+ */
+ MWT_SET(4)
+ ;
+
+ public final int id;
+
+ HemfModifyWorldTransformMode(int id) {
+ this.id = id;
+ }
+
+ public static HemfModifyWorldTransformMode valueOf(int id) {
+ for (HemfModifyWorldTransformMode wrt : values()) {
+ if (wrt.id == id) return wrt;
+ }
+ return null;
+ }
+ }
+
+
+ public static class EmfEof implements HemfRecord {
+ protected final List<PaletteEntry> palette = new ArrayList<>();
+
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.eof;
+ }
+
+ @Override
+ public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+ final int startIdx = leis.getReadIndex();
+
+ // A 32-bit unsigned integer that specifies the number of palette entries.
+ final int nPalEntries = (int) leis.readUInt();
+ // A 32-bit unsigned integer that specifies the offset to the palette entries from the start of this record.
+ final int offPalEntries = (int) leis.readUInt();
+
+ int size = 2 * LittleEndianConsts.INT_SIZE;
+
+ if (nPalEntries > 0 && offPalEntries > 0) {
+ int undefinedSpace1 = (int) (offPalEntries - (size + HEADER_SIZE));
+ assert (undefinedSpace1 >= 0);
+ leis.skipFully(undefinedSpace1);
+ size += undefinedSpace1;
+
+ for (int i = 0; i < nPalEntries; i++) {
+ PaletteEntry pe = new PaletteEntry();
+ size += pe.init(leis);
+ }
+
+ int undefinedSpace2 = (int) (recordSize - size - LittleEndianConsts.INT_SIZE);
+ assert (undefinedSpace2 >= 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;
+ // some files store the whole file size in sizeLast, other just the last record size
+ // assert (sizeLast == size+HEADER_SIZE);
+ assert (recordSize == size);
+
+ return size;
+ }
+ }
+
+ /**
+ * The EMF_SAVEDC record saves the playback device context for later retrieval.
+ */
+ public static class EmfSaveDc extends HwmfMisc.WmfSaveDc implements HemfRecord {
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.saveDc;
+ }
+
+ @Override
+ public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+ return 0;
+ }
+ }
+
+ /**
+ * The EMF_RESTOREDC record restores the playback device context from a previously saved device
+ * context.
+ */
+ public static class EmfRestoreDc extends HwmfMisc.WmfRestoreDc implements HemfRecord {
+
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.restoreDc;
+ }
+
+ @Override
+ public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+ // 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.
+ nSavedDC = leis.readInt();
+ return LittleEndianConsts.INT_SIZE;
+ }
+ }
+
+ /**
+ * 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 extends HwmfMisc.WmfSetBkColor implements HemfRecord {
+
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.setBkColor;
+ }
+
+ @Override
+ public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+ return colorRef.init(leis);
+ }
+ }
+
+
+ /**
+ * 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;
+ }
+
+ @Override
+ public void draw(HemfGraphics ctx) {
+ ctx.addObjectTableEntry(this, brushIdx);
+ }
+
+
+ @Override
+ public String toString() {
+ return
+ "{ brushIndex: "+brushIdx+
+ ", brushStyle: '"+brushStyle+"'"+
+ ", colorRef: "+colorRef+
+ ", brushHatch: '"+brushHatch+"' }";
+ }
+ }
+
+ /**
+ * The EMR_CREATEDIBPATTERNBRUSHPT record defines a pattern brush for graphics operations.
+ * The pattern is specified by a DIB.
+ */
+ public static class EmfCreateDibPatternBrushPt extends HwmfMisc.WmfDibCreatePatternBrush implements HemfRecord {
+ protected int brushIdx;
+
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.createDibPatternBrushPt;
+ }
+
+ @Override
+ public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+ final int startIdx = leis.getReadIndex();
+
+ style = HwmfBrushStyle.BS_DIBPATTERNPT;
+
+ // A 32-bit unsigned integer that specifies the index of the pattern brush
+ // object in the EMF Object Table
+ brushIdx = (int)leis.readUInt();
+
+ // A 32-bit unsigned integer that specifies how to interpret values in the color
+ // table in the DIB header. This value MUST be in the DIBColors enumeration
+ colorUsage = HwmfFill.ColorUsage.valueOf((int)leis.readUInt());
+
+ // A 32-bit unsigned integer that specifies the offset from the start of this
+ // record to the DIB header.
+ final int offBmi = leis.readInt();
+
+ // A 32-bit unsigned integer that specifies the size of the DIB header.
+ final int cbBmi = leis.readInt();
+
+ // A 32-bit unsigned integer that specifies the offset from the start of this record to the DIB bits.
+ final int offBits = leis.readInt();
+
+ // A 32-bit unsigned integer that specifies the size of the DIB bits.
+ final int cbBits = leis.readInt();
+
+ int size = 6*LittleEndianConsts.INT_SIZE;
+
+ patternDib = new HwmfBitmapDib();
+ size += readBitmap(leis, patternDib, startIdx, offBmi, cbBmi, offBits, cbBits);
+ return size;
+ }
+
+ @Override
+ public void draw(HemfGraphics ctx) {
+ ctx.addObjectTableEntry(this, brushIdx);
+ }
+
+ }
+
+ /**
+ * 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;
+ }
+
+ @Override
+ public void draw(HemfGraphics ctx) {
+ ctx.addObjectTableEntry(this, penIndex);
+ }
+
+ @Override
+ public String toString() {
+ return super.toString().replaceFirst("\\{", "{ penIndex: "+penIndex+", ");
+ }
+ }
+
+ public static class EmfExtCreatePen extends EmfCreatePen {
+ protected HwmfBrushStyle brushStyle;
+ protected HwmfHatchStyle hatchStyle;
+
+ 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
+ final HemfPenStyle emfPS = HemfPenStyle.valueOf((int) leis.readUInt());
+ penStyle = emfPS;
+
+ // 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);
+ int size = 7 * LittleEndianConsts.INT_SIZE;
+
+ // 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());
+
+ size += LittleEndianConsts.INT_SIZE;
+
+ size += colorRef.init(leis);
+
+ hatchStyle = HwmfHatchStyle.valueOf(leis.readInt());
+ size += LittleEndianConsts.INT_SIZE;
+
+ // 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 += 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.
+
+ float[] dashPattern = new float[numStyleEntries];
+
+ for (int i = 0; i < numStyleEntries; i++) {
+ dashPattern[i] = (int) leis.readUInt();
+ }
+
+ if (penStyle.getLineDash() == HwmfLineDash.USERSTYLE) {
+ emfPS.setLineDashes(dashPattern);
+ }
+
+ size += numStyleEntries * LittleEndianConsts.INT_SIZE;
+
+ size += readBitmap(leis, bitmap, startIdx, offBmi, cbBmi, offBits, cbBits);
+
+ return size;
+ }
+
+ @Override
+ public String toString() {
+ // TODO: add style entries + bmp
+ return
+ "{ brushStyle: '"+brushStyle+"'"+
+ ", hatchStyle: '"+hatchStyle+"'"+
+ ", dashPattern: "+ Arrays.toString(penStyle.getLineDashes())+
+ ", "+super.toString().substring(1);
+ }
+ }
+
+ /**
+ * The EMR_SETMITERLIMIT record specifies the limit for the length of miter joins for the playback
+ * device context.
+ */
+ public static class EmfSetMiterLimit implements HemfRecord {
+ protected int miterLimit;
+
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.setMiterLimit;
+ }
+
+ @Override
+ public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+ miterLimit = (int) leis.readUInt();
+ return LittleEndianConsts.INT_SIZE;
+ }
+
+ @Override
+ public void draw(HemfGraphics ctx) {
+ ctx.getProperties().setPenMiterLimit(miterLimit);
+ }
+
+ @Override
+ public String toString() {
+ return "{ miterLimit: "+miterLimit+" }";
+ }
+ }
+
+
+ public static class EmfSetBrushOrgEx implements HemfRecord {
+ protected final Point2D origin = new Point2D.Double();
+
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.setBrushOrgEx;
+ }
+
+ @Override
+ public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+ return readPointL(leis, origin);
+ }
+
+ @Override
+ public String toString() {
+ return "{ x: "+origin.getX()+", y: "+origin.getY()+" }";
+ }
+ }
+
+ public static class EmfSetWorldTransform implements HemfRecord {
+ protected final AffineTransform xForm = new AffineTransform();
+
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.setWorldTransform;
+ }
+
+ @Override
+ public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+ return readXForm(leis, xForm);
+ }
+
+ @Override
+ public void draw(HemfGraphics ctx) {
+ ctx.updateWindowMapMode();
+ AffineTransform tx = ctx.getTransform();
+ tx.concatenate(xForm);
+ ctx.setTransform(tx);
+ }
+
+ @Override
+ public String toString() {
+ return
+ "{ xForm: " +
+ "{ scaleX: "+xForm.getScaleX()+
+ ", shearX: "+xForm.getShearX()+
+ ", transX: "+xForm.getTranslateX()+
+ ", scaleY: "+xForm.getScaleY()+
+ ", shearY: "+xForm.getShearY()+
+ ", transY: "+xForm.getTranslateY()+" } }";
+ }
+ }
+
+ public static class EmfModifyWorldTransform implements HemfRecord {
+ protected final AffineTransform xForm = new AffineTransform();
+ protected HemfModifyWorldTransformMode modifyWorldTransformMode;
+ protected HemfHeader header;
+
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.modifyWorldTransform;
+ }
+
+ @Override
+ public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+ // An XForm object that defines a two-dimensional linear transform in logical units.
+ // This transform is used according to the ModifyWorldTransformMode to define a new value for
+ // the world-space to page-space transform in the playback device context.
+ int size = readXForm(leis, xForm);
+
+ // A 32-bit unsigned integer that specifies how the transform specified in Xform is used.
+ // This value MUST be in the ModifyWorldTransformMode enumeration
+ modifyWorldTransformMode = HemfModifyWorldTransformMode.valueOf((int)leis.readUInt());
+
+ return size + LittleEndianConsts.INT_SIZE;
+ }
+
+ @Override
+ public void setHeader(HemfHeader header) {
+ this.header = header;
+ }
+
+ @Override
+ public void draw(HemfGraphics ctx) {
+ if (modifyWorldTransformMode == null) {
+ return;
+ }
+
+ final HemfDrawProperties prop = ctx.getProperties();
+
+ final AffineTransform tx;
+ switch (modifyWorldTransformMode) {
+ case MWT_LEFTMULTIPLY:
+
+ AffineTransform wsTrans;
+ final Rectangle2D win = prop.getWindow();
+ boolean noSetWindowExYet = win.getWidth() == 1 && win.getHeight() == 1;
+ if (noSetWindowExYet) {
+ // TODO: understand world-space transformation [MSDN-WRLDPGSPC]
+ // experimental and horrible solved, because the world-space transformation behind it
+ // is not understood :(
+ // only found one example which had landscape bounds and transform of 90 degress
+
+ try {
+ wsTrans = xForm.createInverse();
+ } catch (NoninvertibleTransformException e) {
+ wsTrans = new AffineTransform();
+ }
+
+ Rectangle2D emfBounds = header.getBoundsRectangle();
+
+ if (xForm.getShearX() == -1.0 && xForm.getShearY() == 1.0) {
+ // rotate 90 deg
+ wsTrans.translate(-emfBounds.getHeight(), emfBounds.getHeight());
+ }
+ } else {
+ wsTrans = adaptXForm(ctx.getTransform());
+ }
+
+ tx = ctx.getTransform();
+ tx.concatenate(wsTrans);
+ break;
+ case MWT_RIGHTMULTIPLY:
+ tx = ctx.getTransform();
+ tx.preConcatenate(adaptXForm(tx));
+ break;
+ case MWT_IDENTITY:
+ ctx.updateWindowMapMode();
+ tx = ctx.getTransform();
+ break;
+ default:
+ case MWT_SET:
+ ctx.updateWindowMapMode();
+ tx = ctx.getTransform();
+ tx.concatenate(adaptXForm(tx));
+ break;
+ }
+ ctx.setTransform(tx);
+ }
+
+ /**
+ * adapt xform depending on the base transformation (... experimental ...)
+ */
+ private AffineTransform adaptXForm(AffineTransform other) {
+ // normalize signed zero
+ Function<Double,Double> nn = (d) -> (d == 0. ? 0. : d);
+ double yDiff = Math.signum(nn.apply(xForm.getTranslateY())) == Math.signum(nn.apply(other.getTranslateY())) ? 1. : -1.;
+ double xDiff = Math.signum(nn.apply(xForm.getTranslateX())) == Math.signum(nn.apply(other.getTranslateX())) ? 1. : -1.;
+ return new AffineTransform(
+ xForm.getScaleX() == 0 ? 1. : xForm.getScaleX(),
+ yDiff * xForm.getShearY(),
+ xDiff * xForm.getShearX(),
+ xForm.getScaleY() == 0. ? 1. : xForm.getScaleY(),
+ xForm.getTranslateX(),
+ xForm.getTranslateY()
+ );
+ }
+
+ @Override
+ public String toString() {
+ return
+ "{ xForm: " +
+ "{ scaleX: "+xForm.getScaleX()+
+ ", shearX: "+xForm.getShearX()+
+ ", transX: "+xForm.getTranslateX()+
+ ", scaleY: "+xForm.getScaleY()+
+ ", shearY: "+xForm.getShearY()+
+ ", transY: "+xForm.getTranslateY()+" }"+
+ ", modifyWorldTransformMode: '"+modifyWorldTransformMode+"' }";
+ }
+ }
+
+ public static class EmfCreateMonoBrush implements HemfRecord, HwmfObjectTableEntry {
+ /**
+ * 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;
+
+ protected HwmfFill.ColorUsage colorUsage;
+ protected final HwmfBitmapDib bitmap = new HwmfBitmapDib();
+
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.createMonoBrush;
+ }
+
+ @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 how to interpret values in the color
+ // table in the DIB header. This value MUST be in the DIBColors enumeration
+ colorUsage = HwmfFill.ColorUsage.valueOf((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();
+
+ int size = 6 * LittleEndianConsts.INT_SIZE;
+
+ size += readBitmap(leis, bitmap, startIdx, offBmi, cbBmi, offBits, cbBits);
+
+ return size;
+ }
+
+ @Override
+ public void draw(HemfGraphics ctx) {
+ ctx.addObjectTableEntry(this, penIndex);
+ }
+
+ @Override
+ public void applyObject(HwmfGraphics ctx) {
+ if (!bitmap.isValid()) {
+ return;
+ }
+ HwmfDrawProperties props = ctx.getProperties();
+ props.setBrushStyle(HwmfBrushStyle.BS_PATTERN);
+ BufferedImage bmp = bitmap.getImage();
+ props.setBrushBitmap(bmp);
+ }
+
+ @Override
+ public String toString() {
+ return
+ "{ penIndex: " + penIndex +
+ ", colorUsage: " + colorUsage +
+ ", bitmap: " + bitmap +
+ "}";
+ }
+ }
+}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfPalette.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfPalette.java
new file mode 100644
index 0000000000..9811cb2c3d
--- /dev/null
+++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfPalette.java
@@ -0,0 +1,154 @@
+/* ====================================================================
+ 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.hemf.draw.HemfGraphics;
+import org.apache.poi.hwmf.record.HwmfPalette;
+import org.apache.poi.util.LittleEndianConsts;
+import org.apache.poi.util.LittleEndianInputStream;
+
+public class HemfPalette {
+ /** The EMR_SELECTPALETTE record specifies a logical palette for the playback device context. */
+ public static class EmfSelectPalette extends HwmfPalette.WmfSelectPalette implements HemfRecord {
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.selectPalette;
+ }
+
+ @Override
+ public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+ /*
+ * A 32-bit unsigned integer that specifies either the index of a LogPalette object
+ * in the EMF Object Table or the value DEFAULT_PALETTE, which is the index
+ * of a stock object palette from the StockObject enumeration
+ */
+ paletteIndex = (int)leis.readUInt();
+ return LittleEndianConsts.INT_SIZE;
+ }
+ }
+
+ /** The EMR_CREATEPALETTE record defines a logical palette for graphics operations. */
+ public static class EmfCreatePalette extends HwmfPalette.WmfCreatePalette implements HemfRecord {
+
+ protected int paletteIndex;
+
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.createPalette;
+ }
+
+ @Override
+ public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+ start = 0x0300;
+ /* 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.
+ */
+ paletteIndex = (int)leis.readUInt();
+ /* A 16-bit unsigned integer that specifies the version number of the system. This MUST be 0x0300. */
+ int version = leis.readUShort();
+ assert(version == 0x0300);
+ int size = readPaletteEntries(leis, -1);
+ return size + LittleEndianConsts.INT_SIZE + LittleEndianConsts.SHORT_SIZE;
+ }
+
+ @Override
+ public void draw(HemfGraphics ctx) {
+ ctx.addObjectTableEntry(this, paletteIndex);
+ }
+ }
+
+ /**
+ * The EMR_SETPALETTEENTRIES record defines RGB color values in a range of entries for an existing
+ * LogPalette object.
+ */
+ public static class EmfSetPaletteEntries extends HwmfPalette.WmfSetPaletteEntries implements HemfRecord {
+ /** specifies the palette EMF Object Table index. */
+ int paletteIndex;
+
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.setPaletteEntries;
+ }
+
+ @Override
+ public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+ // A 32-bit unsigned integer that specifies the palette EMF Object Table index.
+ paletteIndex = (int)leis.readUInt();
+ // A 32-bit unsigned integer that specifies the index of the first entry to set.
+ start = (int)leis.readUInt();
+ // A 32-bit unsigned integer that specifies the number of entries.
+ int nbrOfEntries = (int)leis.readUInt();
+ int size = readPaletteEntries(leis, nbrOfEntries);
+ return size + 3*LittleEndianConsts.INT_SIZE;
+ }
+
+ @Override
+ public void draw(HemfGraphics ctx) {
+ ctx.addObjectTableEntry(this, paletteIndex);
+ }
+ }
+
+ /**
+ * The EMR_RESIZEPALETTE record increases or decreases the size of an existing LogPalette object
+ */
+ public static class EmfResizePalette extends HwmfPalette.WmfResizePalette implements HemfRecord {
+ /** specifies the palette EMF Object Table index. */
+ int paletteIndex;
+
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.resizePalette;
+ }
+
+ @Override
+ public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+ // A 32-bit unsigned integer that specifies the index of the palette object in the EMF Object Table
+ paletteIndex = (int)leis.readUInt();
+
+ // A 32-bit unsigned integer that specifies the number of entries in the palette after resizing.
+ // The value MUST be less than or equal to 0x00000400 and greater than 0x00000000.
+ numberOfEntries = (int)leis.readUInt();
+
+ return 2*LittleEndianConsts.INT_SIZE;
+ }
+
+ @Override
+ public void draw(HemfGraphics ctx) {
+ ctx.addObjectTableEntry(this, paletteIndex);
+ }
+ }
+
+ /**
+ * This record maps palette entries from the current LogPalette object to the system_palette.
+ * This EMF record specifies no parameters.
+ */
+ public static class EmfRealizePalette extends HwmfPalette.WmfRealizePalette implements HemfRecord {
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.realizePalette;
+ }
+
+ @Override
+ public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+ return 0;
+ }
+ }
+}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/AbstractHemfComment.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfPenStyle.java
index 7ffff6b016..74e9459923 100644
--- a/src/scratchpad/src/org/apache/poi/hemf/record/AbstractHemfComment.java
+++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfPenStyle.java
@@ -15,25 +15,31 @@
limitations under the License.
==================================================================== */
-package org.apache.poi.hemf.record;
+package org.apache.poi.hemf.record.emf;
-import org.apache.poi.util.Internal;
+import org.apache.poi.hwmf.record.HwmfPenStyle;
-/**
- * Syntactic utility to allow for four different
- * comment classes
- */
-@Internal
-public abstract class AbstractHemfComment {
+public class HemfPenStyle extends HwmfPenStyle {
- private final byte[] rawBytes;
+ private float[] dashPattern;
- public AbstractHemfComment(byte[] rawBytes) {
- this.rawBytes = rawBytes;
+ public static HemfPenStyle valueOf(int flag) {
+ HemfPenStyle ps = new HemfPenStyle();
+ ps.flag = flag;
+ return ps;
}
- public byte[] getRawBytes() {
- return rawBytes;
+ @Override
+ public float[] getLineDashes() {
+ return (getLineDash() == HwmfLineDash.USERSTYLE) ? dashPattern : super.getLineDashes();
}
+ public void setLineDashes(float[] dashPattern) {
+ this.dashPattern = (dashPattern == null) ? null : dashPattern.clone();
+ }
+
+ @Override
+ public HemfPenStyle clone() {
+ return (HemfPenStyle)super.clone();
+ }
}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecord.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecord.java
new file mode 100644
index 0000000000..41e9f30442
--- /dev/null
+++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecord.java
@@ -0,0 +1,61 @@
+/* ====================================================================
+ 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.hemf.draw.HemfGraphics;
+import org.apache.poi.hwmf.record.HwmfRecord;
+import org.apache.poi.util.Internal;
+import org.apache.poi.util.LittleEndianInputStream;
+
+@Internal
+public interface HemfRecord {
+
+ HemfRecordType getEmfRecordType();
+
+ /**
+ * Init record from stream
+ *
+ * @param leis the little endian input stream
+ * @param recordSize the size limit for this record
+ * @param recordId the id of the {@link HemfRecordType}
+ *
+ * @return count of processed bytes
+ *
+ * @throws IOException when the inputstream is malformed
+ */
+ long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException;
+
+ /**
+ * Draws the record, the default redirects to the parent WMF record drawing
+ * @param ctx the drawing context
+ */
+ default void draw(HemfGraphics ctx) {
+ if (this instanceof HwmfRecord) {
+ ((HwmfRecord) this).draw(ctx);
+ }
+ }
+
+ /**
+ * Sets the header reference, in case the record needs to refer to it
+ * @param header the emf header
+ */
+ default void setHeader(HemfHeader header) {}
+}
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordIterator.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordIterator.java
new file mode 100644
index 0000000000..dfa68670e1
--- /dev/null
+++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordIterator.java
@@ -0,0 +1,91 @@
+/* ====================================================================
+ 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.util.Iterator;
+
+import org.apache.poi.util.LittleEndianConsts;
+import org.apache.poi.util.LittleEndianInputStream;
+import org.apache.poi.util.RecordFormatException;
+
+public class HemfRecordIterator implements Iterator<HemfRecord> {
+
+ 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;
+ }
+
+ final int readIndex = stream.getReadIndex();
+
+ final long recordId, recordSize;
+ try {
+ recordId = stream.readUInt();
+ recordSize = stream.readUInt();
+ } catch (RuntimeException e) {
+ // EOF
+ return null;
+ }
+
+ HemfRecordType type = HemfRecordType.getById(recordId);
+ if (type == null) {
+ throw new RecordFormatException("Undefined record of type: "+recordId+" at "+Integer.toHexString(readIndex));
+ }
+ 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..9d25a9c145
--- /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, HemfMisc.EmfSetBrushOrgEx::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.EmfSetTextColor::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, HemfMisc.EmfSetWorldTransform::new),
+ modifyWorldTransform(0x00000024, HemfMisc.EmfModifyWorldTransform::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, HemfDraw.EmfBeginPath::new),
+ endPath(0x0000003C, HemfDraw.EmfEndPath::new),
+ closeFigure(0x0000003D, HemfDraw.EmfCloseFigure::new),
+ fillPath(0x0000003E, HemfDraw.EmfFillPath::new),
+ strokeAndFillPath(0x0000003F, HemfDraw.EmfStrokeAndFillPath::new),
+ strokePath(0x00000040, HemfDraw.EmfStrokePath::new),
+ flattenPath(0x00000041, HemfDraw.EmfFlattenPath::new),
+ widenPath(0x00000042, HemfDraw.EmfWidenPath::new),
+ selectClipPath(0x00000043, HemfWindowing.EmfSelectClipPath::new),
+ abortPath(0x00000044, HemfDraw.EmfAbortPath::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, HemfFill.EmfStretchDiBits::new),
+ extCreateFontIndirectW(0x00000052, HemfText.EmfExtCreateFontIndirectW::new),
+ extTextOutA(0x00000053, HemfText.EmfExtTextOutA::new),
+ extTextOutW(0x00000054, HemfText.EmfExtTextOutW::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),
+ createMonoBrush(0x0000005D, HemfMisc.EmfCreateMonoBrush::new),
+ createDibPatternBrushPt(0x0000005E, HemfMisc.EmfCreateDibPatternBrushPt::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<? extends HemfRecord> constructor;
+
+ HemfRecordType(long id, Supplier<? extends HemfRecord> 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..7e785315c0
--- /dev/null
+++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java
@@ -0,0 +1,332 @@
+/* ====================================================================
+ 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.readDimensionFloat;
+import static org.apache.poi.hemf.record.emf.HemfDraw.readPointL;
+import static org.apache.poi.hemf.record.emf.HemfDraw.readRectL;
+import static org.apache.poi.hemf.record.emf.HemfRecordIterator.HEADER_SIZE;
+
+import java.awt.geom.Dimension2D;
+import java.awt.geom.Rectangle2D;
+import java.io.IOException;
+import java.nio.charset.Charset;
+
+import org.apache.poi.hemf.draw.HemfGraphics;
+import org.apache.poi.hwmf.draw.HwmfGraphics;
+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 EmfExtTextOutA extends HwmfText.WmfExtTextOut implements HemfRecord {
+
+ protected 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();
+
+ public EmfExtTextOutA() {
+ super(new EmfExtTextOutOptions());
+ }
+
+ @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);
+
+ // 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.
+ 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;
+
+ // handle dx before string and other way round
+ final String order = (offDx < offString) ? "ds" : "sd";
+ // the next byte index after the string ends
+ int strEnd = (int)((offDx <= HEADER_SIZE) ? recordSize : offDx-HEADER_SIZE);
+ for (char op : order.toCharArray()) {
+ switch (op) {
+ case 'd': {
+ dx.clear();
+ int undefinedSpace2 = (int) (offDx - (size + HEADER_SIZE));
+ if (offDx > 0 && undefinedSpace2 >= 0 && offDx-HEADER_SIZE < recordSize) {
+ 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.
+ final int maxSize = (int)Math.min((offDx < offString) ? (offString-HEADER_SIZE) : recordSize, recordSize);
+ while (size <= maxSize-LittleEndianConsts.INT_SIZE) {
+ dx.add((int) leis.readUInt());
+ size += LittleEndianConsts.INT_SIZE;
+ }
+ }
+ if (dx.size() < stringLength) {
+ // invalid dx array
+ dx.clear();
+ }
+ strEnd = (int)recordSize;
+ break;
+ }
+ default:
+ case 's': {
+ int undefinedSpace1 = (int)(offString - (size + HEADER_SIZE));
+ if (offString > 0 && undefinedSpace1 >= 0 && offString-HEADER_SIZE < recordSize) {
+ leis.skipFully(undefinedSpace1);
+ size += undefinedSpace1;
+
+ // read all available bytes and not just "stringLength * 1(ansi)/2(unicode)"
+ // in case we need to deal with surrogate pairs
+ final int maxSize = (int)(Math.min(recordSize, strEnd)-size);
+ rawTextBytes = IOUtils.safelyAllocate(maxSize, MAX_RECORD_LENGTH);
+ leis.readFully(rawTextBytes);
+ size += maxSize;
+ break;
+ }
+ }
+ }
+ }
+
+ return size;
+ }
+
+ /**
+ *
+ * To be implemented! We need to get the current character set
+ * from the current font for {@link EmfExtTextOutA},
+ * which has to be tracked in the playback device.
+ *
+ * For {@link EmfExtTextOutW}, 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 super.getText(charset);
+ }
+
+ public EmfGraphicsMode getGraphicsMode() {
+ return graphicsMode;
+ }
+
+ public Dimension2D getScale() {
+ return scale;
+ }
+
+ @Override
+ public void draw(HwmfGraphics ctx) {
+ // A 32-bit floating-point value that specifies the scale factor to apply along
+ // the 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.
+ Dimension2D scl = graphicsMode == EmfGraphicsMode.GM_COMPATIBLE ? scale : null;
+ ctx.drawString(rawTextBytes, stringLength, reference, scl, bounds, options, dx, isUnicode());
+ }
+
+ @Override
+ public String toString() {
+ return
+ "{ graphicsMode: '"+graphicsMode+"'"+
+ ", scale: { w: "+scale.getWidth()+", h: "+scale.getHeight()+" },"+
+ super.toString().substring(1);
+ }
+ }
+
+ public static class EmfExtTextOutW extends EmfExtTextOutA {
+
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.extTextOutW;
+ }
+
+ public String getText() throws IOException {
+ return getText(UTF_16LE);
+ }
+
+ protected boolean isUnicode() {
+ return true;
+ }
+ }
+
+ /**
+ * 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 EmfSetTextColor extends HwmfText.WmfSetTextColor implements HemfRecord {
+ @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 EmfExtCreateFontIndirectW extends HwmfText.WmfCreateFontIndirect
+ implements HemfRecord {
+ int fontIdx;
+
+ public EmfExtCreateFontIndirectW() {
+ 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;
+ }
+
+ @Override
+ public void draw(HemfGraphics ctx) {
+ ctx.addObjectTableEntry(this, fontIdx);
+ }
+
+ @Override
+ public String toString() {
+ return "{ index: "+fontIdx+", font: "+font+" } ";
+ }
+ }
+
+ 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..a312dc116e
--- /dev/null
+++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfWindowing.java
@@ -0,0 +1,220 @@
+/* ====================================================================
+ 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 org.apache.poi.hemf.record.emf.HemfDraw.readDimensionInt;
+import static org.apache.poi.hemf.record.emf.HemfDraw.readPointL;
+import static org.apache.poi.hwmf.record.HwmfDraw.normalizeBounds;
+
+import java.io.IOException;
+
+import org.apache.poi.hemf.draw.HemfDrawProperties;
+import org.apache.poi.hemf.draw.HemfGraphics;
+import org.apache.poi.hwmf.record.HwmfRegionMode;
+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 {
+ return readDimensionInt(leis, 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 {
+ return readPointL(leis, origin);
+ }
+ }
+
+ /**
+ * 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 {
+ return readDimensionInt(leis, extents);
+ }
+ }
+
+ /**
+ * 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 {
+ return readPointL(leis, origin);
+ }
+ }
+
+ /**
+ * 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 {
+ return readPointL(leis, offset);
+ }
+ }
+
+ /**
+ * 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, normalizeBounds(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 {
+ double xNum = leis.readInt();
+ double xDenom = leis.readInt();
+ double yNum = leis.readInt();
+ double yDenom = leis.readInt();
+ scale.setSize(xNum / xDenom, yNum / yDenom);
+ 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 {
+ double xNum = leis.readInt();
+ double xDenom = leis.readInt();
+ double yNum = leis.readInt();
+ double yDenom = leis.readInt();
+
+ scale.setSize(xNum / xDenom, yNum / yDenom);
+
+ return 4*LittleEndianConsts.INT_SIZE;
+ }
+ }
+
+ /**
+ * The EMR_SELECTCLIPPATH record specifies the current path as a clipping region for a playback
+ * device context, combining the new region with any existing clipping region using the specified mode.
+ */
+ public static class EmfSelectClipPath implements HemfRecord {
+ protected HwmfRegionMode regionMode;
+
+ @Override
+ public HemfRecordType getEmfRecordType() {
+ return HemfRecordType.selectClipPath;
+ }
+
+ @Override
+ public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+ // A 32-bit unsigned integer that specifies the way to use the path.
+ // The value MUST be in the RegionMode enumeration
+ regionMode = HwmfRegionMode.valueOf(leis.readInt());
+
+ return LittleEndianConsts.INT_SIZE;
+ }
+
+ @Override
+ public void draw(HemfGraphics ctx) {
+ HemfDrawProperties prop = ctx.getProperties();
+ ctx.setClip(prop.getPath(), regionMode, false);
+ }
+
+ @Override
+ public String toString() {
+ return "{ regionMode: '"+regionMode+"' }";
+ }
+ }
+
+} \ No newline at end of file
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/UnimplementedHemfRecord.java b/src/scratchpad/src/org/apache/poi/hemf/record/emf/UnimplementedHemfRecord.java
index 6f3ded48d5..12034376c4 100644
--- a/src/scratchpad/src/org/apache/poi/hemf/record/UnimplementedHemfRecord.java
+++ b/src/scratchpad/src/org/apache/poi/hemf/record/emf/UnimplementedHemfRecord.java
@@ -15,7 +15,7 @@
limitations under the License.
==================================================================== */
-package org.apache.poi.hemf.record;
+package org.apache.poi.hemf.record.emf;
import java.io.IOException;
@@ -27,19 +27,16 @@ import org.apache.poi.util.LittleEndianInputStream;
@Internal
public class UnimplementedHemfRecord implements HemfRecord {
- private long recordId;
- public UnimplementedHemfRecord() {
-
- }
+ private HemfRecordType recordType;
@Override
- public HemfRecordType getRecordType() {
- return HemfRecordType.getById(recordId);
+ public HemfRecordType getEmfRecordType() {
+ return recordType;
}
@Override
- public long init(LittleEndianInputStream leis, long recordId, long recordSize) throws IOException {
- this.recordId = recordId;
+ public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
+ recordType = HemfRecordType.getById(recordId);
long skipped = IOUtils.skipFully(leis, recordSize);
if (skipped < recordSize) {
throw new IOException("End of stream reached before record read");
diff --git a/src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/HemfPlusHeader.java b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusHeader.java
index 25947937b4..6264750a92 100644
--- a/src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/HemfPlusHeader.java
+++ b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusHeader.java
@@ -15,13 +15,14 @@
limitations under the License.
==================================================================== */
-package org.apache.poi.hemf.hemfplus.record;
+package org.apache.poi.hemf.record.emfplus;
import java.io.IOException;
import org.apache.poi.util.Internal;
-import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.LittleEndianConsts;
+import org.apache.poi.util.LittleEndianInputStream;
@Internal
public class HemfPlusHeader implements HemfPlusRecord {
@@ -33,7 +34,7 @@ public class HemfPlusHeader implements HemfPlusRecord {
private long logicalDpiY;
@Override
- public HemfPlusRecordType getRecordType() {
+ public HemfPlusRecordType getEmfPlusRecordType() {
return HemfPlusRecordType.header;
}
@@ -42,15 +43,19 @@ public class HemfPlusHeader implements HemfPlusRecord {
}
@Override
- public void init(byte[] dataBytes, int recordId, int flags) throws IOException {
- //assert record id == header
+ public long init(LittleEndianInputStream leis, long dataSize, long recordId, int flags) throws IOException {
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);
+ 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() {
@@ -79,4 +84,4 @@ public class HemfPlusHeader implements HemfPlusRecord {
", logicalDpiY=" + logicalDpiY +
'}';
}
-}
+} \ No newline at end of file
diff --git a/src/scratchpad/src/org/apache/poi/hemf/record/HemfRecord.java b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRecord.java
index de1271e696..808c166360 100644
--- a/src/scratchpad/src/org/apache/poi/hemf/record/HemfRecord.java
+++ b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRecord.java
@@ -15,27 +15,34 @@
limitations under the License.
==================================================================== */
-package org.apache.poi.hemf.record;
+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 HemfRecord {
+public interface HemfPlusRecord {
- HemfRecordType getRecordType();
+ HemfPlusRecordType getEmfPlusRecordType();
+
+ 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
+ *
+ * @throws IOException when the inputstream is malformed
*/
- long init(LittleEndianInputStream leis, long recordId, long recordSize) throws IOException;
-
+ 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..4c6f0ad919
--- /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<HemfPlusRecord> {
+
+ 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.getEmfPlusRecordType()) {
+ 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<? extends HemfPlusRecord> constructor;
+
+ HemfPlusRecordType(long id, Supplier<? extends HemfPlusRecord> 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/hemfplus/record/UnimplementedHemfPlusRecord.java b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/UnimplementedHemfPlusRecord.java
index 7e3cbcff4c..bc7152cd7e 100644
--- a/src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/UnimplementedHemfPlusRecord.java
+++ b/src/scratchpad/src/org/apache/poi/hemf/record/emfplus/UnimplementedHemfPlusRecord.java
@@ -15,23 +15,27 @@
limitations under the License.
==================================================================== */
-package org.apache.poi.hemf.hemfplus.record;
+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 int recordId;
+ private static final int MAX_RECORD_LENGTH = 1_000_000;
+
+ private HemfPlusRecordType recordType;
private int flags;
private byte[] recordBytes;
@Override
- public HemfPlusRecordType getRecordType() {
- return HemfPlusRecordType.getById(recordId);
+ public HemfPlusRecordType getEmfPlusRecordType() {
+ return recordType;
}
@Override
@@ -40,14 +44,16 @@ public class UnimplementedHemfPlusRecord implements HemfPlusRecord {
}
@Override
- public void init(byte[] recordBytes, int recordId, int flags) throws IOException {
- this.recordId = recordId;
+ public long init(LittleEndianInputStream leis, long dataSize, long recordId, int flags) throws IOException {
+ recordType = HemfPlusRecordType.getById(recordId);
this.flags = flags;
- this.recordBytes = recordBytes;
+ 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..6d53ae23ea
--- /dev/null
+++ b/src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java
@@ -0,0 +1,161 @@
+/* ====================================================================
+ 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.awt.Graphics2D;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Dimension2D;
+import java.awt.geom.Rectangle2D;
+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.draw.HemfGraphics;
+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.hemf.record.emf.HemfWindowing;
+import org.apache.poi.util.Dimension2DDouble;
+import org.apache.poi.util.Internal;
+import org.apache.poi.util.LittleEndianInputStream;
+import org.apache.poi.util.Units;
+
+/**
+ * Read-only EMF extractor. Lots remain
+ */
+@Internal
+public class HemfPicture implements Iterable<HemfRecord> {
+
+ private final LittleEndianInputStream stream;
+ private final List<HemfRecord> records = new ArrayList<>();
+ private boolean isParsed = false;
+
+ 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<HemfRecord> getRecords() {
+ if (!isParsed) {
+ // in case the (first) parsing throws an exception, we can provide the
+ // records up to that point
+ isParsed = true;
+ HemfHeader[] header = new HemfHeader[1];
+ new HemfRecordIterator(stream).forEachRemaining(r -> {
+ if (r instanceof HemfHeader) {
+ header[0] = (HemfHeader) r;
+ }
+ r.setHeader(header[0]);
+ records.add(r);
+ });
+ }
+ return records;
+ }
+
+ @Override
+ public Iterator<HemfRecord> iterator() {
+ return getRecords().iterator();
+ }
+
+ @Override
+ public Spliterator<HemfRecord> spliterator() {
+ return getRecords().spliterator();
+ }
+
+ @Override
+ public void forEach(Consumer<? super HemfRecord> action) {
+ getRecords().forEach(action);
+ }
+
+ /**
+ * Return the image size in points
+ *
+ * @return the image size in points
+ */
+ public Dimension2D getSize() {
+ HemfHeader header = (HemfHeader)getRecords().get(0);
+ final double coeff = (double) Units.EMU_PER_CENTIMETER / Units.EMU_PER_POINT / 10.;
+ Rectangle2D dim = header.getFrameRectangle();
+ double width = dim.getWidth(), height = dim.getHeight();
+ if (dim.isEmpty() || Math.rint(width*coeff) == 0 || Math.rint(height*coeff) == 0) {
+ for (HemfRecord r : getRecords()) {
+ if (r instanceof HemfWindowing.EmfSetWindowExtEx) {
+ Dimension2D d = ((HemfWindowing.EmfSetWindowExtEx)r).getSize();
+ width = d.getWidth();
+ height = d.getHeight();
+ // keep searching - sometimes there's another record
+ }
+ }
+ }
+
+ if (Math.rint(width*coeff) == 0 || Math.rint(height*coeff) == 0) {
+ width = 100;
+ height = 100;
+ }
+
+ return new Dimension2DDouble(Math.abs(width*coeff), Math.abs(height*coeff));
+ }
+
+ private static double minX(Rectangle2D bounds) {
+ return Math.min(bounds.getMinX(), bounds.getMaxX());
+ }
+
+ private static double minY(Rectangle2D bounds) {
+ return Math.min(bounds.getMinY(), bounds.getMaxY());
+ }
+
+ public void draw(Graphics2D ctx, Rectangle2D graphicsBounds) {
+ HemfHeader header = (HemfHeader)getRecords().get(0);
+
+ AffineTransform at = ctx.getTransform();
+ try {
+ Rectangle2D emfBounds = header.getBoundsRectangle();
+
+ // scale output bounds to image bounds
+ ctx.translate(minX(graphicsBounds), minY(graphicsBounds));
+ ctx.scale(graphicsBounds.getWidth()/emfBounds.getWidth(), graphicsBounds.getHeight()/emfBounds.getHeight());
+ ctx.translate(-minX(emfBounds), -minY(emfBounds));
+
+ int idx = 0;
+ HemfGraphics g = new HemfGraphics(ctx, emfBounds);
+ for (HemfRecord r : getRecords()) {
+ try {
+ g.draw(r);
+ } catch (RuntimeException ignored) {
+
+ }
+ idx++;
+ }
+ } finally {
+ ctx.setTransform(at);
+ }
+ }
+
+}
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..8b8358c30f 100644
--- a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java
+++ b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java
@@ -19,7 +19,9 @@ package org.apache.poi.hwmf.draw;
import java.awt.Color;
import java.awt.Shape;
+import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
+import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
@@ -36,6 +38,7 @@ import org.apache.poi.hwmf.record.HwmfMapMode;
import org.apache.poi.hwmf.record.HwmfMisc.WmfSetBkMode.HwmfBkMode;
import org.apache.poi.hwmf.record.HwmfPalette.PaletteEntry;
import org.apache.poi.hwmf.record.HwmfPenStyle;
+import org.apache.poi.hwmf.record.HwmfTernaryRasterOp;
import org.apache.poi.hwmf.record.HwmfText.HwmfTextAlignment;
import org.apache.poi.hwmf.record.HwmfText.HwmfTextVerticalAlignment;
@@ -43,36 +46,59 @@ 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<PaletteEntry> 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;
+ private HwmfTernaryRasterOp rasterOp;
+ protected Shape clip;
+ protected final AffineTransform transform = new AffineTransform();
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;
+ rasterOp = HwmfTernaryRasterOp.PATCOPY;
+ clip = null;
+ font = new HwmfFont();
+ font.initDefaults();
}
public HwmfDrawProperties(HwmfDrawProperties other) {
- this.window = (Rectangle2D)other.window.clone();
+ this.window = (other.window == null) ? null : (Rectangle2D)other.window.clone();
this.viewport = (other.viewport == null) ? null : (Rectangle2D)other.viewport.clone();
this.location = (Point2D)other.location.clone();
this.mapMode = other.mapMode;
@@ -86,7 +112,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 +127,13 @@ 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;
+ this.rasterOp = other.rasterOp;
+ this.transform.setTransform(other.transform);
+ this.clip = other.clip;
}
public void setViewportExt(double width, double height) {
@@ -149,6 +182,10 @@ public class HwmfDrawProperties {
location.setLocation(x, y);
}
+ public void setLocation(Point2D point) {
+ location.setLocation(point);
+ }
+
public Point2D getLocation() {
return (Point2D)location.clone();
}
@@ -343,4 +380,35 @@ public class HwmfDrawProperties {
public void setTextVAlignAsian(HwmfTextVerticalAlignment textVAlignAsian) {
this.textVAlignAsian = textVAlignAsian;
}
+
+ /**
+ * @return the current active winding rule ({@link Path2D#WIND_EVEN_ODD} or {@link Path2D#WIND_NON_ZERO})
+ */
+ public int getWindingRule() {
+ return getPolyfillMode().awtFlag;
+ }
+
+ public HwmfTernaryRasterOp getRasterOp() {
+ return rasterOp;
+ }
+
+ public void setRasterOp(HwmfTernaryRasterOp rasterOp) {
+ this.rasterOp = rasterOp;
+ }
+
+ public AffineTransform getTransform() {
+ return transform;
+ }
+
+ public void setTransform(AffineTransform transform) {
+ this.transform.setTransform(transform);
+ }
+
+ public Shape getClip() {
+ return clip;
+ }
+
+ public void setClip(Shape clip) {
+ this.clip = clip;
+ }
}
diff --git a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java
index 5eccd5d7a9..2f9811fc63 100644
--- a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java
+++ b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java
@@ -25,18 +25,26 @@ import java.awt.Paint;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.TexturePaint;
+import java.awt.font.FontRenderContext;
import java.awt.font.TextAttribute;
+import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform;
+import java.awt.geom.Area;
+import java.awt.geom.Dimension2D;
+import java.awt.geom.NoninvertibleTransformException;
+import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.nio.charset.Charset;
import java.text.AttributedString;
-import java.util.ArrayList;
+import java.util.BitSet;
import java.util.LinkedList;
import java.util.List;
-import java.util.ListIterator;
import java.util.NoSuchElementException;
+import java.util.TreeMap;
+import org.apache.commons.codec.Charsets;
+import org.apache.poi.common.usermodel.fonts.FontCharset;
import org.apache.poi.common.usermodel.fonts.FontInfo;
import org.apache.poi.hwmf.record.HwmfBrushStyle;
import org.apache.poi.hwmf.record.HwmfFont;
@@ -46,20 +54,31 @@ import org.apache.poi.hwmf.record.HwmfMisc.WmfSetBkMode.HwmfBkMode;
import org.apache.poi.hwmf.record.HwmfObjectTableEntry;
import org.apache.poi.hwmf.record.HwmfPenStyle;
import org.apache.poi.hwmf.record.HwmfPenStyle.HwmfLineDash;
+import org.apache.poi.hwmf.record.HwmfRegionMode;
+import org.apache.poi.hwmf.record.HwmfText;
+import org.apache.poi.hwmf.record.HwmfText.WmfExtTextOutOptions;
import org.apache.poi.sl.draw.DrawFactory;
import org.apache.poi.sl.draw.DrawFontManager;
+import org.apache.poi.sl.draw.DrawFontManagerDefault;
import org.apache.poi.util.LocaleUtil;
public class HwmfGraphics {
+ public enum FillDrawStyle {
+ NONE, FILL, DRAW, FILL_DRAW
+ }
+
+ protected final List<HwmfDrawProperties> propStack = new LinkedList<>();
+ protected HwmfDrawProperties prop;
+ protected final Graphics2D graphicsCtx;
+ protected final BitSet objectIndexes = new BitSet();
+ protected final TreeMap<Integer,HwmfObjectTableEntry> objectTable = new TreeMap<>();
+ protected final AffineTransform initialAT = new AffineTransform();
+
+
private static final Charset DEFAULT_CHARSET = LocaleUtil.CHARSET_1252;
- private final Graphics2D graphicsCtx;
- private final List<HwmfDrawProperties> propStack = new LinkedList<>();
- private HwmfDrawProperties prop = new HwmfDrawProperties();
- private List<HwmfObjectTableEntry> objectTable = new ArrayList<>();
- /** Bounding box from the placeable header */
+ /** Bounding box from the placeable header */
private final Rectangle2D bbox;
- private final AffineTransform initialAT;
/**
* Initialize a graphics context for wmf rendering
@@ -70,16 +89,26 @@ public class HwmfGraphics {
public HwmfGraphics(Graphics2D graphicsCtx, Rectangle2D bbox) {
this.graphicsCtx = graphicsCtx;
this.bbox = (Rectangle2D)bbox.clone();
- this.initialAT = graphicsCtx.getTransform();
- DrawFactory.getInstance(graphicsCtx).fixFonts(graphicsCtx);
+ this.initialAT.setTransform(graphicsCtx.getTransform());
}
public HwmfDrawProperties getProperties() {
+ if (prop == null) {
+ prop = newProperties(null);
+ }
return prop;
}
+ protected HwmfDrawProperties newProperties(HwmfDrawProperties oldProps) {
+ return (oldProps == null) ? new HwmfDrawProperties() : new HwmfDrawProperties(oldProps);
+ }
+
public void draw(Shape shape) {
- HwmfLineDash lineDash = prop.getPenStyle().getLineDash();
+ HwmfPenStyle ps = getProperties().getPenStyle();
+ if (ps == null) {
+ return;
+ }
+ HwmfLineDash lineDash = ps.getLineDash();
if (lineDash == HwmfLineDash.NULL) {
// line is not drawn
return;
@@ -89,40 +118,44 @@ public class HwmfGraphics {
// first draw a solid background line (depending on bkmode)
// only makes sense if the line is not solid
- if (prop.getBkMode() == HwmfBkMode.OPAQUE && (lineDash != HwmfLineDash.SOLID && lineDash != HwmfLineDash.INSIDEFRAME)) {
+ if (getProperties().getBkMode() == HwmfBkMode.OPAQUE && (lineDash != HwmfLineDash.SOLID && lineDash != HwmfLineDash.INSIDEFRAME)) {
graphicsCtx.setStroke(new BasicStroke(stroke.getLineWidth()));
- graphicsCtx.setColor(prop.getBackgroundColor().getColor());
+ graphicsCtx.setColor(getProperties().getBackgroundColor().getColor());
graphicsCtx.draw(shape);
}
// then draw the (dashed) line
graphicsCtx.setStroke(stroke);
- graphicsCtx.setColor(prop.getPenColor().getColor());
+ graphicsCtx.setColor(getProperties().getPenColor().getColor());
graphicsCtx.draw(shape);
}
public void fill(Shape shape) {
+ HwmfDrawProperties prop = getProperties();
if (prop.getBrushStyle() != HwmfBrushStyle.BS_NULL) {
-// GeneralPath gp = new GeneralPath(shape);
-// gp.setWindingRule(prop.getPolyfillMode().awtFlag);
+ if (prop.getBkMode() == HwmfBkMode.OPAQUE) {
+ graphicsCtx.setPaint(prop.getBackgroundColor().getColor());
+ graphicsCtx.fill(shape);
+ }
+
graphicsCtx.setPaint(getFill());
graphicsCtx.fill(shape);
}
- draw(shape);
+// draw(shape);
}
protected BasicStroke getStroke() {
// TODO: fix line width calculation
- float width = (float)prop.getPenWidth();
+ float width = (float)getProperties().getPenWidth();
if (width == 0) {
width = 1;
}
- HwmfPenStyle ps = prop.getPenStyle();
+ HwmfPenStyle ps = getProperties().getPenStyle();
int cap = ps.getLineCap().awtFlag;
int join = ps.getLineJoin().awtFlag;
- float miterLimit = (float)prop.getPenMiterLimit();
- float dashes[] = ps.getLineDash().dashes;
+ float miterLimit = (float)getProperties().getPenMiterLimit();
+ float dashes[] = ps.getLineDashes();
boolean dashAlt = ps.isAlternateDash();
// This value is not an integer index into the dash pattern array.
// Instead, it is a floating-point value that specifies a linear distance.
@@ -132,7 +165,7 @@ public class HwmfGraphics {
}
protected Paint getFill() {
- switch (prop.getBrushStyle()) {
+ switch (getProperties().getBrushStyle()) {
default:
case BS_INDEXED:
case BS_PATTERN8X8:
@@ -148,20 +181,20 @@ public class HwmfGraphics {
}
protected Paint getSolidFill() {
- return prop.getBrushColor().getColor();
+ return getProperties().getBrushColor().getColor();
}
protected Paint getHatchedFill() {
int dim = 7, mid = 3;
BufferedImage bi = new BufferedImage(dim, dim, BufferedImage.TYPE_4BYTE_ABGR);
Graphics2D g = bi.createGraphics();
- Color c = (prop.getBkMode() == HwmfBkMode.TRANSPARENT)
+ Color c = (getProperties().getBkMode() == HwmfBkMode.TRANSPARENT)
? new Color(0, true)
- : prop.getBackgroundColor().getColor();
+ : getProperties().getBackgroundColor().getColor();
g.setColor(c);
g.fillRect(0, 0, dim, dim);
- g.setColor(prop.getBrushColor().getColor());
- HwmfHatchStyle h = prop.getBrushHatch();
+ g.setColor(getProperties().getBrushColor().getColor());
+ HwmfHatchStyle h = getProperties().getBrushHatch();
if (h == HwmfHatchStyle.HS_HORIZONTAL || h == HwmfHatchStyle.HS_CROSS) {
g.drawLine(0, mid, dim, mid);
}
@@ -174,12 +207,13 @@ public class HwmfGraphics {
if (h == HwmfHatchStyle.HS_BDIAGONAL || h == HwmfHatchStyle.HS_DIAGCROSS) {
g.drawLine(0, dim, dim, 0);
}
+ // TODO: handle new HS_* enumeration values
g.dispose();
return new TexturePaint(bi, new Rectangle(0,0,dim,dim));
}
protected Paint getPatternPaint() {
- BufferedImage bi = prop.getBrushBitmap();
+ BufferedImage bi = getProperties().getBrushBitmap();
return (bi == null) ? null
: new TexturePaint(bi, new Rectangle(0,0,bi.getWidth(),bi.getHeight()));
}
@@ -196,15 +230,9 @@ public class HwmfGraphics {
* @param entry
*/
public void addObjectTableEntry(HwmfObjectTableEntry entry) {
- ListIterator<HwmfObjectTableEntry> oIter = objectTable.listIterator();
- while (oIter.hasNext()) {
- HwmfObjectTableEntry tableEntry = oIter.next();
- if (tableEntry == null) {
- oIter.set(entry);
- return;
- }
- }
- objectTable.add(entry);
+ int objIdx = objectIndexes.nextClearBit(0);
+ objectIndexes.set(objIdx);
+ objectTable.put(objIdx, entry);
}
/**
@@ -237,15 +265,25 @@ public class HwmfGraphics {
* @throws IndexOutOfBoundsException if the index is out of range
*/
public void unsetObjectTableEntry(int index) {
- objectTable.set(index, null);
+ if (index < 0) {
+ throw new IndexOutOfBoundsException("Invalid index: "+index);
+ }
+ // sometime emfs remove object table entries before they set them
+ // so ignore requests, if the table entry doesn't exist
+ objectTable.remove(index);
+ objectIndexes.clear(index);
}
/**
* Saves the current properties to the stack
*/
public void saveProperties() {
- propStack.add(prop);
- prop = new HwmfDrawProperties(prop);
+ final HwmfDrawProperties p = getProperties();
+ assert(p != null);
+ p.setTransform(graphicsCtx.getTransform());
+ p.setClip(graphicsCtx.getClip());
+ propStack.add(p);
+ prop = newProperties(p);
}
/**
@@ -260,7 +298,7 @@ public class HwmfGraphics {
}
int stackIndex = index;
if (stackIndex < 0) {
- int curIdx = propStack.indexOf(prop);
+ int curIdx = propStack.indexOf(getProperties());
if (curIdx == -1) {
// the current element is not pushed to the stacked, i.e. it's the last
curIdx = propStack.size();
@@ -271,7 +309,16 @@ public class HwmfGraphics {
// roll to last when curIdx == 0
stackIndex = propStack.size()-1;
}
- prop = propStack.get(stackIndex);
+
+ // The playback device context is restored by popping state information off a stack that was created by
+ // prior SAVEDC records
+ // ... so because being a stack, we will remove all entries having a greater index than the stackIndex
+ for (int i=propStack.size()-1; i>=stackIndex; i--) {
+ prop = propStack.remove(i);
+ }
+
+ graphicsCtx.setTransform(prop.getTransform());
+ graphicsCtx.setClip(prop.getClip());
}
/**
@@ -280,16 +327,20 @@ public class HwmfGraphics {
* This methods gathers and sets the corresponding graphics transformations.
*/
public void updateWindowMapMode() {
- Rectangle2D win = prop.getWindow();
- HwmfMapMode mapMode = prop.getMapMode();
+ Rectangle2D win = getProperties().getWindow();
+ Rectangle2D view = getProperties().getViewport();
+ HwmfMapMode mapMode = getProperties().getMapMode();
graphicsCtx.setTransform(initialAT);
switch (mapMode) {
default:
case MM_ANISOTROPIC:
// scale window bounds to output bounds
- graphicsCtx.scale(bbox.getWidth()/win.getWidth(), bbox.getHeight()/win.getHeight());
- graphicsCtx.translate(-win.getX(), -win.getY());
+ if (view != null) {
+ graphicsCtx.translate(view.getCenterX(), view.getCenterY());
+ graphicsCtx.scale(view.getWidth() / win.getWidth(), view.getHeight() / win.getHeight());
+ graphicsCtx.translate(-win.getCenterX(), -win.getCenterY());
+ }
break;
case MM_ISOTROPIC:
// TODO: to be validated ...
@@ -315,11 +366,21 @@ public class HwmfGraphics {
}
}
- public void drawString(byte[] text, Rectangle2D bounds) {
- drawString(text, bounds, null);
+ public void drawString(byte[] text, int length, Point2D reference) {
+ drawString(text, length, reference, null, null, null, null, false);
}
- public void drawString(byte[] text, Rectangle2D bounds, int dx[]) {
+ public void drawString(byte[] text, int length, Point2D reference, Dimension2D scale, Rectangle2D clip, WmfExtTextOutOptions opts, List<Integer> dx, boolean isUnicode) {
+ final HwmfDrawProperties prop = getProperties();
+
+ final AffineTransform at = graphicsCtx.getTransform();
+
+ try {
+ at.createInverse();
+ } catch (NoninvertibleTransformException e) {
+ return;
+ }
+
HwmfFont font = prop.getFont();
if (font == null || text == null || text.length == 0) {
return;
@@ -329,14 +390,34 @@ public class HwmfGraphics {
// TODO: another approx. ...
double fontW = fontH/1.8;
- Charset charset = (font.getCharset().getCharset() == null)?
- DEFAULT_CHARSET : font.getCharset().getCharset();
- String textString = new String(text, charset);
- AttributedString as = new AttributedString(textString);
- if (dx == null || dx.length == 0) {
- addAttributes(as, font);
+ Charset charset;
+ if (isUnicode) {
+ charset = Charsets.UTF_16LE;
} else {
- int[] dxNormed = dx;
+ charset = font.getCharset().getCharset();
+ if (charset == null) {
+ charset = DEFAULT_CHARSET;
+ }
+ }
+
+ String textString = new String(text, charset).substring(0,length).trim();
+
+ if (textString.isEmpty()) {
+ return;
+ }
+
+ DrawFontManager fontHandler = DrawFactory.getInstance(graphicsCtx).getFontManager(graphicsCtx);
+ FontInfo fontInfo = fontHandler.getMappedFont(graphicsCtx, font);
+ if (fontInfo.getCharset() == FontCharset.SYMBOL) {
+ textString = DrawFontManagerDefault.mapSymbolChars(textString);
+ }
+
+ AttributedString as = new AttributedString(textString);
+ addAttributes(as, font, fontInfo.getTypeface());
+
+ // disabled for the time being, as the results aren't promising
+ /*
+ if (dx != null && !dx.isEmpty()) {
//for multi-byte encodings (e.g. Shift_JIS), the byte length
//might not equal the string length().
//The x information is stored in dx[], an array parallel to the
@@ -351,60 +432,101 @@ public class HwmfGraphics {
// needs to be remapped as:
//dxNormed[0] = 13 textString.get(0) = U+30D7
//dxNormed[1] = 14 textString.get(1) = U+30ED
- if (textString.length() != text.length) {
- int codePoints = textString.codePointCount(0, textString.length());
- dxNormed = new int[codePoints];
- int dxPosition = 0;
- for (int offset = 0; offset < textString.length(); ) {
- dxNormed[offset] = dx[dxPosition];
- int[] chars = new int[1];
- int cp = textString.codePointAt(offset);
- chars[0] = cp;
- //now figure out how many bytes it takes to encode that
- //code point in the charset
- int byteLength = new String(chars, 0, chars.length).getBytes(charset).length;
- dxPosition += byteLength;
- offset += Character.charCount(cp);
- }
- }
- for (int i = 0; i < dxNormed.length; i++) {
- addAttributes(as, font);
- // Tracking works as a prefix/advance space on characters whereas
- // dx[...] is the complete width of the current char
- // therefore we need to add the additional/suffix width to the next char
- if (i < dxNormed.length - 1) {
- as.addAttribute(TextAttribute.TRACKING, (dxNormed[i] - fontW) / fontH, i + 1, i + 2);
+
+ final int cps = textString.codePointCount(0, textString.length());
+ final int unicodeSteps = Math.max(dx.size()/cps, 1);
+ int dxPosition = 0, lastDxPosition = 0;
+ int beginIndex = 0;
+ while (beginIndex < textString.length() && dxPosition < dx.size()) {
+ int endIndex = textString.offsetByCodePoints(beginIndex, 1);
+ if (beginIndex > 0) {
+ // Tracking works as a prefix/advance space on characters whereas
+ // dx[...] is the complete width of the current char
+ // therefore we need to add the additional/suffix width to the next char
+
+ as.addAttribute(TextAttribute.TRACKING, (float)((dx.get(lastDxPosition) - fontW) / fontH), beginIndex, endIndex);
}
+ lastDxPosition = dxPosition;
+ dxPosition += (isUnicode) ? unicodeSteps : (endIndex-beginIndex);
+ beginIndex = endIndex;
}
}
-
-
+ */
+
double angle = Math.toRadians(-font.getEscapement()/10.);
-
-
- final AffineTransform at = graphicsCtx.getTransform();
+
+ final HwmfText.HwmfTextAlignment align = prop.getTextAlignLatin();
+ final HwmfText.HwmfTextVerticalAlignment valign = prop.getTextVAlignLatin();
+ final FontRenderContext frc = graphicsCtx.getFontRenderContext();
+ final TextLayout layout = new TextLayout(as.getIterator(), frc);
+
+ final Rectangle2D pixelBounds = layout.getBounds();
+
+ AffineTransform tx = new AffineTransform();
+ switch (align) {
+ default:
+ case LEFT:
+ break;
+ case CENTER:
+ tx.translate(-pixelBounds.getWidth() / 2., 0);
+ break;
+ case RIGHT:
+ tx.translate(-pixelBounds.getWidth(), 0);
+ break;
+ }
+
+ // TODO: check min/max orientation
+ switch (valign) {
+ case TOP:
+ tx.translate(0, layout.getAscent());
+ default:
+ case BASELINE:
+ break;
+ case BOTTOM:
+ tx.translate(0, -(pixelBounds.getHeight()-layout.getDescent()));
+ break;
+ }
+ tx.rotate(angle);
+ Point2D src = new Point2D.Double();
+ Point2D dst = new Point2D.Double();
+ tx.transform(src, dst);
+
+ final Shape clipShape = graphicsCtx.getClip();
try {
- graphicsCtx.translate(bounds.getX(), bounds.getY()+fontH);
+ if (clip != null) {
+ graphicsCtx.translate(-clip.getCenterX(), -clip.getCenterY());
+ graphicsCtx.rotate(angle);
+ graphicsCtx.translate(clip.getCenterX(), clip.getCenterY());
+ if (prop.getBkMode() == HwmfBkMode.OPAQUE && opts.isOpaque()) {
+ graphicsCtx.setPaint(prop.getBackgroundColor().getColor());
+ graphicsCtx.fill(clip);
+ }
+ if (opts.isClipped()) {
+ graphicsCtx.setClip(clip);
+ }
+ graphicsCtx.setTransform(at);
+ }
+
+ graphicsCtx.translate(reference.getX(), reference.getY());
graphicsCtx.rotate(angle);
- if (prop.getBkMode() == HwmfBkMode.OPAQUE) {
- // TODO: validate bounds
- graphicsCtx.setBackground(prop.getBackgroundColor().getColor());
- graphicsCtx.fill(new Rectangle2D.Double(0, 0, bounds.getWidth(), bounds.getHeight()));
+ if (scale != null) {
+ graphicsCtx.scale(scale.getWidth() < 0 ? -1 : 1, scale.getHeight() < 0 ? -1 : 1);
}
+ graphicsCtx.translate(dst.getX(), dst.getY());
graphicsCtx.setColor(prop.getTextColor().getColor());
- graphicsCtx.drawString(as.getIterator(), 0, 0); // (float)bounds.getX(), (float)bounds.getY());
+ graphicsCtx.drawString(as.getIterator(), 0, 0);
} finally {
graphicsCtx.setTransform(at);
+ graphicsCtx.setClip(clipShape);
}
}
-
- private void addAttributes(AttributedString as, HwmfFont font) {
- DrawFontManager fontHandler = DrawFactory.getInstance(graphicsCtx).getFontManager(graphicsCtx);
- FontInfo fontInfo = fontHandler.getMappedFont(graphicsCtx, font);
-
- as.addAttribute(TextAttribute.FAMILY, fontInfo.getTypeface());
+
+ private void addAttributes(AttributedString as, HwmfFont font, String typeface) {
+ as.addAttribute(TextAttribute.FAMILY, typeface);
as.addAttribute(TextAttribute.SIZE, getFontHeight(font));
- as.addAttribute(TextAttribute.STRIKETHROUGH, font.isStrikeOut());
+ if (font.isStrikeOut()) {
+ as.addAttribute(TextAttribute.STRIKETHROUGH, TextAttribute.STRIKETHROUGH_ON);
+ }
if (font.isUnderline()) {
as.addAttribute(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON);
}
@@ -428,4 +550,138 @@ public class HwmfGraphics {
return fontHeight*3/4;
}
}
+
+ public void drawImage(BufferedImage img, Rectangle2D srcBounds, Rectangle2D dstBounds) {
+ HwmfDrawProperties prop = getProperties();
+
+ // handle raster op
+ // currently the raster op as described in https://docs.microsoft.com/en-us/windows/desktop/gdi/ternary-raster-operations
+ // are not supported, as we would need to extract the destination image area from the underlying buffered image
+ // and therefore would make it mandatory that the graphics context must be from a buffered image
+ // furthermore I doubt the purpose of bitwise image operations on non-black/white images
+ switch (prop.getRasterOp()) {
+ case D:
+ // keep destination, i.e. do nothing
+ break;
+ case PATCOPY:
+ graphicsCtx.setPaint(getFill());
+ graphicsCtx.fill(dstBounds);
+ break;
+ case BLACKNESS:
+ graphicsCtx.setPaint(Color.BLACK);
+ graphicsCtx.fill(dstBounds);
+ break;
+ case WHITENESS:
+ graphicsCtx.setPaint(Color.WHITE);
+ graphicsCtx.fill(dstBounds);
+ break;
+ default:
+ case SRCCOPY:
+ final Shape clip = graphicsCtx.getClip();
+
+ // add clipping in case of a source subimage, i.e. a clipped source image
+ // some dstBounds are horizontal or vertical flipped, so we need to normalize the images
+ Rectangle2D normalized = new Rectangle2D.Double(
+ dstBounds.getWidth() >= 0 ? dstBounds.getMinX() : dstBounds.getMaxX(),
+ dstBounds.getHeight() >= 0 ? dstBounds.getMinY() : dstBounds.getMaxY(),
+ Math.abs(dstBounds.getWidth()),
+ Math.abs(dstBounds.getHeight()));
+ graphicsCtx.clip(normalized);
+ final AffineTransform at = graphicsCtx.getTransform();
+
+ final Rectangle2D imgBounds = new Rectangle2D.Double(0,0,img.getWidth(),img.getHeight());
+ final boolean isImgBounds = (srcBounds.equals(new Rectangle2D.Double()));
+ final Rectangle2D srcBounds2 = isImgBounds ? imgBounds : srcBounds;
+
+ // TODO: apply emf transform
+ graphicsCtx.translate(dstBounds.getX(), dstBounds.getY());
+ graphicsCtx.scale(dstBounds.getWidth()/srcBounds2.getWidth(), dstBounds.getHeight()/srcBounds2.getHeight());
+ graphicsCtx.translate(-srcBounds2.getX(), -srcBounds2.getY());
+
+ graphicsCtx.drawImage(img, 0, 0, prop.getBackgroundColor().getColor(), null);
+
+ graphicsCtx.setTransform(at);
+ graphicsCtx.setClip(clip);
+ break;
+ }
+
+ }
+
+ /**
+ * @return the initial AffineTransform, when this graphics context was created
+ */
+ public AffineTransform getInitTransform() {
+ return new AffineTransform(initialAT);
+ }
+
+ /**
+ * @return the current AffineTransform
+ */
+ public AffineTransform getTransform() {
+ return new AffineTransform(graphicsCtx.getTransform());
+ }
+
+ /**
+ * Set the current AffineTransform
+ * @param tx the current AffineTransform
+ */
+ public void setTransform(AffineTransform tx) {
+ graphicsCtx.setTransform(tx);
+ }
+
+ private static int clipCnt = 0;
+
+ public void setClip(Shape clip, HwmfRegionMode regionMode, boolean useInitialAT) {
+ final AffineTransform at = graphicsCtx.getTransform();
+ if (useInitialAT) {
+ graphicsCtx.setTransform(initialAT);
+ }
+ final Shape oldClip = graphicsCtx.getClip();
+ final boolean isEmpty = clip.getBounds2D().isEmpty();
+ switch (regionMode) {
+ case RGN_AND:
+ if (!isEmpty) {
+ graphicsCtx.clip(clip);
+ }
+ break;
+ case RGN_OR:
+ if (!isEmpty) {
+ if (oldClip == null) {
+ graphicsCtx.setClip(clip);
+ } else {
+ Area area = new Area(oldClip);
+ area.add(new Area(clip));
+ graphicsCtx.setClip(area);
+ }
+ }
+ break;
+ case RGN_XOR:
+ if (!isEmpty) {
+ if (oldClip == null) {
+ graphicsCtx.setClip(clip);
+ } else {
+ Area area = new Area(oldClip);
+ area.exclusiveOr(new Area(clip));
+ graphicsCtx.setClip(area);
+ }
+ }
+ break;
+ case RGN_DIFF:
+ if (!isEmpty) {
+ if (oldClip != null) {
+ Area area = new Area(oldClip);
+ area.subtract(new Area(clip));
+ graphicsCtx.setClip(area);
+ }
+ }
+ break;
+ case RGN_COPY: {
+ graphicsCtx.setClip(isEmpty ? null : clip);
+ break;
+ }
+ }
+ if (useInitialAT) {
+ graphicsCtx.setTransform(at);
+ }
+ }
}
diff --git a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfSLImageRenderer.java b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfImageRenderer.java
index e2601bc65e..a87db042d6 100644
--- a/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfSLImageRenderer.java
+++ b/src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfImageRenderer.java
@@ -31,20 +31,25 @@ import java.io.InputStream;
import org.apache.poi.hwmf.usermodel.HwmfPicture;
import org.apache.poi.sl.draw.DrawPictureShape;
import org.apache.poi.sl.draw.ImageRenderer;
-import org.apache.poi.sl.usermodel.PictureData;
+import org.apache.poi.sl.usermodel.PictureData.PictureType;
import org.apache.poi.util.Units;
/**
* Helper class which is instantiated by {@link DrawPictureShape}
* via reflection
*/
-public class HwmfSLImageRenderer implements ImageRenderer {
+public class HwmfImageRenderer implements ImageRenderer {
HwmfPicture image;
double alpha;
-
+
+ @Override
+ public boolean canRender(String contentType) {
+ return PictureType.WMF.contentType.equalsIgnoreCase(contentType);
+ }
+
@Override
public void loadImage(InputStream data, String contentType) throws IOException {
- if (!PictureData.PictureType.WMF.contentType.equals(contentType)) {
+ if (!PictureType.WMF.contentType.equals(contentType)) {
throw new IOException("Invalid picture type");
}
image = new HwmfPicture(data);
@@ -52,7 +57,7 @@ public class HwmfSLImageRenderer implements ImageRenderer {
@Override
public void loadImage(byte[] data, String contentType) throws IOException {
- if (!PictureData.PictureType.WMF.contentType.equals(contentType)) {
+ if (!PictureType.WMF.contentType.equals(contentType)) {
throw new IOException("Invalid picture type");
}
image = new HwmfPicture(new ByteArrayInputStream(data));
diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBitmapDib.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBitmapDib.java
index 82f593963e..a800bb0e05 100644
--- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBitmapDib.java
+++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBitmapDib.java
@@ -17,14 +17,21 @@
package org.apache.poi.hwmf.record;
-import javax.imageio.ImageIO;
+import java.awt.AlphaComposite;
+import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
+import java.awt.LinearGradientPaint;
+import java.awt.MultipleGradientPaint;
+import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
+import javax.imageio.ImageIO;
+
+import org.apache.poi.hwmf.usermodel.HwmfPicture;
import org.apache.poi.util.IOUtils;
import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.LittleEndianConsts;
@@ -38,9 +45,11 @@ import org.apache.poi.util.RecordFormatException;
*/
public class HwmfBitmapDib {
- private static final int MAX_RECORD_LENGTH = 10000000;
+ private static final POILogger logger = POILogFactory.getLogger(HwmfBitmapDib.class);
+ private static final int BMP_HEADER_SIZE = 14;
+ private static final int MAX_RECORD_LENGTH = HwmfPicture.MAX_RECORD_LENGTH;
- public static enum BitCount {
+ public enum BitCount {
/**
* The image SHOULD be in either JPEG or PNG format. <6> Neither of these formats includes
* a color table, so this value specifies that no color table is present. See [JFIF] and [RFC2083]
@@ -122,7 +131,7 @@ public class HwmfBitmapDib {
}
}
- public static enum Compression {
+ public enum Compression {
/**
* The bitmap is in uncompressed red green blue (RGB) format that is not compressed
* and does not use color masks.
@@ -191,9 +200,7 @@ public class HwmfBitmapDib {
}
}
- private final static POILogger logger = POILogFactory.getLogger(HwmfBitmapDib.class);
- private static final int BMP_HEADER_SIZE = 14;
-
+
private int headerSize;
private int headerWidth;
private int headerHeight;
@@ -225,14 +232,36 @@ public class HwmfBitmapDib {
introSize += readColors(leis);
assert(introSize < 10000);
- int fileSize = (headerImageSize < headerSize) ? recordSize : (int)Math.min(introSize+headerImageSize,recordSize);
-
leis.reset();
- imageData = IOUtils.toByteArray(leis, fileSize);
-
- assert( headerSize != 0x0C || ((((headerWidth * headerPlanes * headerBitCount.flag + 31) & ~31) / 8) * Math.abs(headerHeight)) == headerImageSize);
- return fileSize;
+ // The size and format of this data is determined by information in the DIBHeaderInfo field. If
+ // it is a BitmapCoreHeader, the size in bytes MUST be calculated as follows:
+
+ int bodySize = ((((headerWidth * headerPlanes * headerBitCount.flag + 31) & ~31) / 8) * Math.abs(headerHeight));
+
+ // This formula SHOULD also be used to calculate the size of aData when DIBHeaderInfo is a
+ // BitmapInfoHeader Object, using values from that object, but only if its Compression value is
+ // BI_RGB, BI_BITFIELDS, or BI_CMYK.
+ // Otherwise, the size of aData MUST be the BitmapInfoHeader Object value ImageSize.
+
+ assert( headerSize != 0x0C || bodySize == headerImageSize);
+
+ if (headerSize == 0x0C ||
+ headerCompression == Compression.BI_RGB ||
+ headerCompression == Compression.BI_BITFIELDS ||
+ headerCompression == Compression.BI_CMYK) {
+ int fileSize = (int)Math.min(introSize+bodySize,recordSize);
+ imageData = IOUtils.safelyAllocate(fileSize, MAX_RECORD_LENGTH);
+ leis.readFully(imageData, 0, introSize);
+ leis.skipFully(recordSize-fileSize);
+ // emfs are sometimes truncated, read as much as possible
+ int readBytes = leis.read(imageData, introSize, fileSize-introSize);
+ return introSize+(recordSize-fileSize)+readBytes;
+ } else {
+ imageData = IOUtils.safelyAllocate(recordSize, MAX_RECORD_LENGTH);
+ leis.readFully(imageData);
+ return recordSize;
+ }
}
protected int readHeader(LittleEndianInputStream leis) throws IOException {
@@ -262,6 +291,9 @@ public class HwmfBitmapDib {
headerBitCount = BitCount.valueOf(leis.readUShort());
size += 4*LittleEndianConsts.SHORT_SIZE;
} else {
+ // fix header size, sometimes this is invalid
+ headerSize = 40;
+
// BitmapInfoHeader
// A 32-bit signed integer that defines the width of the DIB, in pixels.
// This value MUST be positive.
@@ -306,7 +338,6 @@ public class HwmfBitmapDib {
headerColorImportant = leis.readUInt();
size += 8*LittleEndianConsts.INT_SIZE+2*LittleEndianConsts.SHORT_SIZE;
}
- assert(size == headerSize);
return size;
}
@@ -374,11 +405,35 @@ public class HwmfBitmapDib {
return size;
}
+ public boolean isValid() {
+ // the recordsize ended before the image data
+ if (imageData == null) {
+ return false;
+ }
+
+ // ignore all black mono-brushes
+ if (this.headerBitCount == BitCount.BI_BITCOUNT_1) {
+ if (colorTable == null) {
+ return false;
+ }
+
+ for (Color c : colorTable) {
+ if (!Color.BLACK.equals(c)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ return true;
+ }
+
public InputStream getBMPStream() {
return new ByteArrayInputStream(getBMPData());
}
- private byte[] getBMPData() {
+ public byte[] getBMPData() {
if (imageData == null) {
throw new RecordFormatException("bitmap not initialized ... need to call init() before");
}
@@ -407,14 +462,56 @@ public class HwmfBitmapDib {
public BufferedImage getImage() {
try {
return ImageIO.read(getBMPStream());
- } catch (IOException e) {
- logger.log(POILogger.ERROR, "invalid bitmap data - returning black opaque image");
- BufferedImage bi = new BufferedImage(headerWidth, headerHeight, BufferedImage.TYPE_INT_ARGB);
- Graphics2D g = bi.createGraphics();
- g.setPaint(Color.black);
- g.fillRect(0, 0, headerWidth, headerHeight);
- g.dispose();
- return bi;
+ } catch (IOException|RuntimeException e) {
+ logger.log(POILogger.ERROR, "invalid bitmap data - returning placeholder image");
+ return getPlaceholder();
}
}
+
+ @Override
+ public String toString() {
+ return
+ "{ headerSize: " + headerSize +
+ ", width: " + headerWidth +
+ ", height: " + headerHeight +
+ ", planes: " + headerPlanes +
+ ", bitCount: '" + headerBitCount + "'" +
+ ", compression: '" + headerCompression + "'" +
+ ", imageSize: " + headerImageSize +
+ ", xPelsPerMeter: " + headerXPelsPerMeter +
+ ", yPelsPerMeter: " + headerYPelsPerMeter +
+ ", colorUsed: " + headerColorUsed +
+ ", colorImportant: " + headerColorImportant +
+ ", imageSize: " + (imageData == null ? 0 : imageData.length) +
+ "}";
+ }
+
+ protected BufferedImage getPlaceholder() {
+ BufferedImage bi = new BufferedImage(headerWidth, headerHeight, BufferedImage.TYPE_INT_ARGB);
+ Graphics2D g = bi.createGraphics();
+ g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+ g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
+ g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
+ g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
+
+ g.setComposite(AlphaComposite.Clear);
+ g.fillRect(0, 0, headerWidth, headerHeight);
+
+ final int arcs = Math.min(headerWidth, headerHeight) / 7;
+
+ Color bg = Color.LIGHT_GRAY;
+ Color fg = Color.GRAY;
+ LinearGradientPaint lgp = new LinearGradientPaint(0f, 0f, 5, 5,
+ new float[] {0,.1f,.1001f}, new Color[] {fg,fg,bg}, MultipleGradientPaint.CycleMethod.REFLECT);
+ g.setComposite(AlphaComposite.SrcOver.derive(0.4f));
+ g.setPaint(lgp);
+ g.fillRoundRect(0, 0, headerWidth-1, headerHeight-1, arcs, arcs);
+
+ g.setColor(Color.DARK_GRAY);
+ g.setComposite(AlphaComposite.Src);
+ g.setStroke(new BasicStroke(2));
+ g.drawRoundRect(0, 0, headerWidth-1, headerHeight-1, arcs, arcs);
+ g.dispose();
+ return bi;
+ }
}
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/HwmfColorRef.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfColorRef.java
index 5b24fbc7e9..a757617752 100644
--- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfColorRef.java
+++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfColorRef.java
@@ -19,6 +19,7 @@ package org.apache.poi.hwmf.record;
import java.awt.Color;
import java.io.IOException;
+import java.util.Locale;
import org.apache.poi.util.LittleEndianConsts;
import org.apache.poi.util.LittleEndianInputStream;
@@ -53,6 +54,10 @@ public class HwmfColorRef implements Cloneable {
return colorRef;
}
+ public void setColor(Color color) {
+ colorRef = color;
+ }
+
/**
* Creates a new object of the same class and with the
* same contents as this object.
@@ -69,4 +74,9 @@ public class HwmfColorRef implements Cloneable {
throw new InternalError();
}
}
+
+ @Override
+ public String toString() {
+ return String.format(Locale.ROOT, "%#08X", colorRef.getRGB()&0xFFFFFF);
+ }
}
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..17cd3c83d8 100644
--- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java
+++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java
@@ -20,9 +20,11 @@ package org.apache.poi.hwmf.record;
import java.awt.Shape;
import java.awt.geom.Arc2D;
import java.awt.geom.Area;
+import java.awt.geom.Dimension2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
+import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
@@ -31,6 +33,8 @@ import java.util.ArrayList;
import java.util.List;
import org.apache.poi.hwmf.draw.HwmfGraphics;
+import org.apache.poi.hwmf.draw.HwmfGraphics.FillDrawStyle;
+import org.apache.poi.util.Internal;
import org.apache.poi.util.LittleEndianConsts;
import org.apache.poi.util.LittleEndianInputStream;
@@ -41,31 +45,26 @@ 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);
+ }
+
+ @Override
+ public String toString() {
+ return pointToString(point);
}
}
@@ -75,36 +74,29 @@ 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);
+ }
+
+ @Override
+ public String toString() {
+ return pointToString(point);
}
}
@@ -115,10 +107,10 @@ public class HwmfDraw {
*/
public static class WmfPolygon implements HwmfRecord {
- private Path2D poly = new Path2D.Double();
+ protected Path2D poly;
@Override
- public HwmfRecordType getRecordType() {
+ public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.polygon;
}
@@ -129,6 +121,7 @@ public class HwmfDraw {
*/
int numberofPoints = leis.readShort();
+ poly = new Path2D.Double(Path2D.WIND_EVEN_ODD, numberofPoints);
for (int i=0; i<numberofPoints; i++) {
// A 16-bit signed integer that defines the horizontal (x) coordinate of the point.
int x = leis.readShort();
@@ -146,15 +139,33 @@ public class HwmfDraw {
@Override
public void draw(HwmfGraphics ctx) {
- Path2D shape = getShape();
-// shape.closePath();
- Path2D p = (Path2D)shape.clone();
- p.setWindingRule(getWindingRule(ctx));
- ctx.fill(p);
+ Path2D p = (Path2D)poly.clone();
+ // don't close the path
+ p.setWindingRule(ctx.getProperties().getWindingRule());
+ switch (getFillDrawStyle()) {
+ case FILL:
+ ctx.fill(p);
+ break;
+ case DRAW:
+ ctx.draw(p);
+ break;
+ case FILL_DRAW:
+ ctx.fill(p);
+ ctx.draw(p);
+ break;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "{ poly: "+polyToString(poly)+" }";
}
- protected Path2D getShape() {
- return (Path2D)poly.clone();
+ /**
+ * @return true, if the shape should be filled
+ */
+ protected FillDrawStyle getFillDrawStyle() {
+ return FillDrawStyle.FILL;
}
}
@@ -165,16 +176,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 FillDrawStyle getFillDrawStyle() {
+ return FillDrawStyle.DRAW;
}
}
@@ -184,49 +192,30 @@ 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);
- ctx.fill(s);
+ ctx.fill(getShape());
+ }
+
+ protected Ellipse2D getShape() {
+ return new Ellipse2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight());
+ }
+
+ @Override
+ public String toString() {
+ return boundsToString(bounds);
}
}
@@ -239,25 +228,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 +282,10 @@ public class HwmfDraw {
*/
public static class WmfPolyPolygon implements HwmfRecord {
- private List<Path2D> polyList = new ArrayList<>();
+ protected final List<Path2D> polyList = new ArrayList<>();
@Override
- public HwmfRecordType getRecordType() {
+ public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.polyPolygon;
}
@@ -325,7 +314,7 @@ public class HwmfDraw {
* An array of 16-bit signed integers that define the coordinates of the polygons.
* (Note: MS-WMF wrongly says unsigned integers ...)
*/
- Path2D poly = new Path2D.Double();
+ Path2D poly = new Path2D.Double(Path2D.WIND_EVEN_ODD, nPoints);
for (int i=0; i<nPoints; i++) {
int x = leis.readShort();
int y = leis.readShort();
@@ -345,23 +334,81 @@ public class HwmfDraw {
@Override
public void draw(HwmfGraphics ctx) {
- if (polyList.isEmpty()) {
+ Shape shape = getShape(ctx);
+ if (shape == null) {
return;
}
-
- int windingRule = getWindingRule(ctx);
- Area area = null;
- for (Path2D poly : polyList) {
- Path2D p = (Path2D)poly.clone();
- p.setWindingRule(windingRule);
- Area newArea = new Area(p);
- if (area == null) {
- area = newArea;
- } else {
- area.exclusiveOr(newArea);
+
+ switch (getFillDrawStyle()) {
+ case DRAW:
+ ctx.draw(shape);
+ break;
+ case FILL:
+ ctx.fill(shape);
+ break;
+ case FILL_DRAW:
+ ctx.fill(shape);
+ ctx.draw(shape);
+ break;
+ }
+ }
+
+ protected FillDrawStyle getFillDrawStyle() {
+ // Each polygon SHOULD be outlined using the current pen, and filled using the current brush and
+ // polygon fill mode that are defined in the playback device context. The polygons defined by this
+ // record can overlap.
+ return FillDrawStyle.FILL_DRAW;
+ }
+
+ /**
+ * @return true, if a polyline should be closed, i.e. is a polygon
+ */
+ protected boolean isClosed() {
+ return true;
+ }
+
+ protected Shape getShape(HwmfGraphics ctx) {
+ int windingRule = ctx.getProperties().getWindingRule();
+
+ if (isClosed()) {
+ Area area = null;
+ for (Path2D poly : polyList) {
+ Path2D p = (Path2D)poly.clone();
+ p.setWindingRule(windingRule);
+ Area newArea = new Area(p);
+ if (area == null) {
+ area = newArea;
+ } else {
+ area.exclusiveOr(newArea);
+ }
}
+ return area;
+ } else {
+ Path2D path = new Path2D.Double();
+ path.setWindingRule(windingRule);
+ for (Path2D poly : polyList) {
+ path.append(poly, false);
+ }
+ return path;
}
- ctx.fill(area);
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("{ polyList: [");
+ boolean isFirst = true;
+ for (Path2D p : polyList) {
+ if (!isFirst) {
+ sb.append(",");
+ }
+ isFirst = false;
+ sb.append("{ points: ");
+ sb.append(polyToString(p));
+ sb.append(" }");
+ }
+ sb.append(" }");
+ return sb.toString();
}
}
@@ -370,92 +417,55 @@ 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);
+ }
+
+ @Override
+ public String toString() {
+ return boundsToString(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;
-
- /**
- * A 16-bit signed integer that defines the y-coordinate, in logical units, of the point
- * to be set.
- */
- private int y;
+ protected final HwmfColorRef colorRef = new HwmfColorRef();
- /**
- * 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 +479,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,21 +499,16 @@ 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);
- ctx.fill(s);
+ ctx.fill(getShape());
+ }
+
+ protected RoundRectangle2D getShape() {
+ return new RoundRectangle2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), width, height);
}
}
@@ -534,73 +517,61 @@ 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.)));
+ Shape s = getShape();
+ switch (getFillDrawStyle()) {
+ case FILL:
+ ctx.fill(s);
+ break;
+ case DRAW:
+ ctx.draw(s);
+ break;
+ case FILL_DRAW:
+ ctx.fill(s);
+ ctx.draw(s);
+ break;
+ }
+ }
+
+ protected FillDrawStyle getFillDrawStyle() {
+ switch (getWmfRecordType()) {
+ default:
+ case arc:
+ return FillDrawStyle.DRAW;
+ case chord:
+ case pie:
+ return FillDrawStyle.FILL_DRAW;
+ }
+ }
+
+ protected Arc2D getShape() {
+ 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,28 +579,32 @@ public class HwmfDraw {
boolean fillShape;
int arcClosure;
- switch (getRecordType()) {
+ switch (getWmfRecordType()) {
default:
case arc:
arcClosure = Arc2D.OPEN;
- fillShape = false;
break;
case chord:
arcClosure = Arc2D.CHORD;
- fillShape = true;
break;
case pie:
arcClosure = Arc2D.PIE;
- fillShape = true;
break;
}
-
- Shape s = new Arc2D.Double(x, y, w, h, startAngle, arcAngle, arcClosure);
- if (fillShape) {
- ctx.fill(s);
- } else {
- ctx.draw(s);
- }
+
+ return new Arc2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), startAngle, arcAngle, arcClosure);
+ }
+
+ @Override
+ public String toString() {
+ Arc2D arc = getShape();
+ return
+ "{ startPoint: "+pointToString(startPoint)+
+ ", endPoint: "+pointToString(endPoint)+
+ ", startAngle: "+arc.getAngleStart()+
+ ", extentAngle: "+arc.getAngleExtent()+
+ ", bounds: "+boundsToString(bounds)+
+ " }";
}
}
@@ -641,7 +616,7 @@ public class HwmfDraw {
public static class WmfPie extends WmfArc {
@Override
- public HwmfRecordType getRecordType() {
+ public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.pie;
}
}
@@ -654,7 +629,7 @@ public class HwmfDraw {
public static class WmfChord extends WmfArc {
@Override
- public HwmfRecordType getRecordType() {
+ public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.chord;
}
}
@@ -673,10 +648,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;
}
@@ -690,9 +665,113 @@ public class HwmfDraw {
public void draw(HwmfGraphics ctx) {
ctx.applyObjectTableEntry(objectIndex);
}
+
+ @Override
+ public String toString() {
+ return "{ index: "+objectIndex +" }";
+ }
}
- 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;
+ }
+
+ static String polyToString(Path2D poly) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("[");
+ final PathIterator iter = poly.getPathIterator(null);
+ double[] pnts = new double[6];
+ while (!iter.isDone()) {
+ int segType = iter.currentSegment(pnts);
+ switch (segType) {
+ case PathIterator.SEG_MOVETO:
+ sb.append("{ type: 'move', x: "+pnts[0]+", y: "+pnts[1]+" }, ");
+ break;
+ case PathIterator.SEG_LINETO:
+ sb.append("{ type: 'lineto', x: "+pnts[0]+", y: "+pnts[1]+" }, ");
+ break;
+ case PathIterator.SEG_QUADTO:
+ sb.append("{ type: 'quad', x1: "+pnts[0]+", y1: "+pnts[1]+", x2: "+pnts[2]+", y2: "+pnts[3]+" }, ");
+ break;
+ case PathIterator.SEG_CUBICTO:
+ sb.append("{ type: 'cubic', x1: "+pnts[0]+", y1: "+pnts[1]+", x2: "+pnts[2]+", y2: "+pnts[3]+", x3: "+pnts[4]+", y3: "+pnts[5]+" }, ");
+ break;
+ case PathIterator.SEG_CLOSE:
+ sb.append("{ type: 'close' }, ");
+ break;
+ }
+ iter.next();
+ }
+ sb.append("]");
+ return sb.toString();
+ }
+
+ @Internal
+ public static String pointToString(Point2D point) {
+ return "{ x: "+point.getX()+", y: "+point.getY()+" }";
+ }
+
+ @Internal
+ public static String boundsToString(Rectangle2D bounds) {
+ return "{ x: "+bounds.getX()+", y: "+bounds.getY()+", w: "+bounds.getWidth()+", h: "+bounds.getHeight()+" }";
+ }
+
+ @Internal
+ public static String dimToString(Dimension2D dim) {
+ return "{ w: "+dim.getWidth()+", h: "+dim.getHeight()+" }";
+ }
+
+ @Internal
+ public static Rectangle2D normalizeBounds(Rectangle2D bounds) {
+ return (bounds.getWidth() >= 0 && bounds.getHeight() >= 0) ? bounds
+ : new Rectangle2D.Double(
+ bounds.getWidth() >= 0 ? bounds.getMinX() : bounds.getMaxX(),
+ bounds.getHeight() >= 0 ? bounds.getMinY() : bounds.getMaxY(),
+ Math.abs(bounds.getWidth()),
+ Math.abs(bounds.getHeight())
+ );
+ }
+
+}
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..3b420e401d 100644
--- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java
+++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java
@@ -17,11 +17,17 @@
package org.apache.poi.hwmf.record;
+import static org.apache.poi.hwmf.record.HwmfDraw.boundsToString;
+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;
+import org.apache.poi.hwmf.draw.HwmfDrawProperties;
import org.apache.poi.hwmf.draw.HwmfGraphics;
import org.apache.poi.util.LittleEndianConsts;
import org.apache.poi.util.LittleEndianInputStream;
@@ -62,7 +68,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 +86,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 +131,7 @@ public class HwmfFill {
*/
int regionIndex;
- public HwmfRecordType getRecordType() {
+ public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.paintRegion;
}
@@ -155,31 +161,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,34 +211,39 @@ 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;
}
@Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
- polyfillMode = HwmfPolyfillMode.valueOf(leis.readUShort() & 3);
+ polyFillMode = HwmfPolyfillMode.valueOf(leis.readUShort() & 3);
return LittleEndianConsts.SHORT_SIZE;
}
@Override
public void draw(HwmfGraphics ctx) {
- ctx.getProperties().setPolyfillMode(polyfillMode);
+ ctx.getProperties().setPolyfillMode(polyFillMode);
+ }
+
+ @Override
+ public String toString() {
+ return "{ polyFillMode: '"+ polyFillMode +"' }";
}
}
@@ -251,7 +252,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 +267,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 +298,7 @@ public class HwmfFill {
private int region;
@Override
- public HwmfRecordType getRecordType() {
+ public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.invertRegion;
}
@@ -348,30 +328,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 +343,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 +369,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 +393,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);
@@ -502,6 +422,15 @@ public class HwmfFill {
public void draw(HwmfGraphics ctx) {
}
+
+ @Override
+ public String toString() {
+ return
+ "{ rasterOperation: '"+rasterOperation+"'"+
+ ", srcBounds: "+boundsToString(srcBounds)+
+ ", dstBounds: "+boundsToString(dstBounds)+
+ "}";
+ }
}
/**
@@ -511,67 +440,34 @@ public class HwmfFill {
* The source of the color data is a DIB, and the destination of the transfer is
* the current output region in the playback device context.
*/
- public static class WmfStretchDib implements HwmfRecord, HwmfImageRecord, HwmfObjectTableEntry {
+ public static class WmfStretchDib implements HwmfRecord, HwmfImageRecord {
/**
* 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;
+ protected HwmfTernaryRasterOp rasterOperation;
/**
* A 16-bit unsigned integer that defines whether the Colors field of the
* 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;
+ protected ColorUsage colorUsage;
+
+ /** 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.
*/
- private HwmfBitmapDib dib;
+ protected final HwmfBitmapDib bitmap = new HwmfBitmapDib();
@Override
- public HwmfRecordType getRecordType() {
+ public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.stretchDib;
}
@@ -585,85 +481,49 @@ 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;
- dib = new HwmfBitmapDib();
- size += dib.init(leis, (int)(recordSize-6-size));
+
+ int size = 3*LittleEndianConsts.SHORT_SIZE;
+
+ size += readBounds2(leis, srcBounds);
+ size += readBounds2(leis, dstBounds);
+
+ size += bitmap.init(leis, (int)(recordSize-6-size));
return size;
}
@Override
public void draw(HwmfGraphics ctx) {
- ctx.addObjectTableEntry(this);
+ HwmfDrawProperties prop = ctx.getProperties();
+ prop.setRasterOp(rasterOperation);
+ if (bitmap.isValid()) {
+ ctx.drawImage(getImage(), srcBounds, dstBounds);
+ } else if (!dstBounds.isEmpty()) {
+ BufferedImage bi = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
+ ctx.drawImage(bi, new Rectangle2D.Double(0,0,100,100), dstBounds);
+ }
}
-
+
@Override
- public void applyObject(HwmfGraphics ctx) {
-
+ public BufferedImage getImage() {
+ return bitmap.getImage();
}
@Override
- public BufferedImage getImage() {
- return dib.getImage();
+ public String toString() {
+ return
+ "{ rasterOperation: '"+rasterOperation+"'"+
+ ", colorUsage: '"+colorUsage+"'"+
+ ", srcBounds: "+boundsToString(srcBounds)+
+ ", dstBounds: "+boundsToString(dstBounds)+
+ "}";
}
}
- 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 +531,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 +581,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 +595,7 @@ public class HwmfFill {
@Override
- public HwmfRecordType getRecordType() {
+ public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.setDibToDev;
}
@@ -775,17 +604,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 +637,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 +647,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 +681,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 +704,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 +731,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 && target.isValid()) ? target.getImage() : null;
}
}
+
+ 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..4413983ef3 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,39 @@ 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;
+
+ public int init(LittleEndianInputStream leis) {
+ flag = leis.readUByte();
+ return LittleEndianConsts.BYTE_SIZE;
}
- static WmfClipPrecision valueOf(int flag) {
- for (WmfClipPrecision cp : values()) {
- if (cp.flag == flag) {
- return cp;
- }
- }
- return null;
+ @Override
+ public String toString() {
+ return
+ (((flag&0x3) == 0 ? "default " : " ")+
+ (CLIP_CHARACTER_PRECIS.isSet(flag) ? "char " : " ")+
+ (CLIP_STROKE_PRECIS.isSet(flag) ? "stroke " : " ")+
+ (CLIP_LH_ANGLES.isSet(flag) ? "angles " : " ")+
+ (CLIP_TT_ALWAYS.isSet(flag) ? "tt_always " : " ")+
+ (CLIP_DFA_DISABLE.isSet(flag) ? "dfa " : " ")+
+ (CLIP_EMBEDDED.isSet(flag) ? "embedded " : " ")
+ ).trim()
+ ;
}
}
@@ -210,7 +209,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 +239,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 +247,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 +298,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 +311,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,24 +355,35 @@ 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;
}
+ public void initDefaults() {
+ height = -12;
+ width = 0;
+ escapement = 0;
+ weight = 400;
+ italic = false;
+ underline = false;
+ strikeOut = false;
+ charSet = FontCharset.ANSI;
+ outPrecision = WmfOutPrecision.OUT_DEFAULT_PRECIS;
+ quality = WmfFontQuality.ANTIALIASED_QUALITY;
+ pitchAndFamily = FontFamily.FF_DONTCARE.getFlag() | (FontPitch.DEFAULT.getNativeId() << 6);
+ facename = "SansSerif";
+ }
+
public int getHeight() {
return height;
}
@@ -471,4 +481,38 @@ public class HwmfFont implements FontInfo {
public void setCharset(FontCharset charset) {
throw new UnsupportedOperationException("setCharset not supported by HwmfFont.");
}
+
+ @Override
+ public String toString() {
+ return "{ height: "+height+
+ ", width: "+width+
+ ", escapment: "+escapement+
+ ", weight: "+weight+
+ ", italic: "+italic+
+ ", underline: "+underline+
+ ", strikeOut: "+strikeOut+
+ ", charset: '"+charSet+"'"+
+ ", outPrecision: '"+outPrecision+"'"+
+ ", clipPrecision: '"+clipPrecision+"'"+
+ ", quality: '"+quality+"'"+
+ ", pitch: '"+getPitch()+"'"+
+ ", family: '"+getFamily()+"'"+
+ ", facename: '"+facename+"'"+
+ "}";
+ }
+
+ 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..6574deaed3 100644
--- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfHatchStyle.java
+++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfHatchStyle.java
@@ -32,14 +32,27 @@ public enum HwmfHatchStyle {
/** +++++ - A horizontal and vertical cross-hatch. */
HS_CROSS(0x0004),
/** xxxxx - A 45-degree crosshatch. */
- HS_DIAGCROSS(0x0005);
+ HS_DIAGCROSS(0x0005),
+ /** The hatch is not a pattern, but is a solid color. */
+ HS_SOLIDCLR(0x0006),
+ /** The hatch is not a pattern, but is a dithered color. */
+ HS_DITHEREDCLR(0x0007),
+ /** The hatch is not a pattern, but is a solid color, defined by the current text (foreground) color. */
+ HS_SOLIDTEXTCLR(0x0008),
+ /** The hatch is not a pattern, but is a dithered color, defined by the current text (foreground) color. */
+ HS_DITHEREDTEXTCLR(0x0009),
+ /** The hatch is not a pattern, but is a solid color, defined by the current background color. */
+ HS_SOLIDBKCLR(0x000A),
+ /** The hatch is not a pattern, but is a dithered color, defined by the current background color. */
+ HS_DITHEREDBKCLR(0x000B)
+ ;
int flag;
HwmfHatchStyle(int flag) {
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..eef15e30d7 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;
}
@@ -47,13 +49,18 @@ public class HwmfMisc {
public void draw(HwmfGraphics ctx) {
ctx.saveProperties();
}
+
+ @Override
+ public String toString() {
+ return "{}";
+ }
}
/**
* The META_SETRELABS record is reserved and not supported.
*/
public static class WmfSetRelabs implements HwmfRecord {
- public HwmfRecordType getRecordType() {
+ public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.setRelabs;
}
@@ -78,10 +85,10 @@ public class HwmfMisc {
* member is positive, nSavedDC represents a specific instance of the state to be restored. If
* this member is negative, nSavedDC represents an instance relative to the current state.
*/
- private int nSavedDC;
+ protected int nSavedDC;
@Override
- public HwmfRecordType getRecordType() {
+ public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.restoreDc;
}
@@ -95,6 +102,11 @@ public class HwmfMisc {
public void draw(HwmfGraphics ctx) {
ctx.restoreProperties(nSavedDC);
}
+
+ @Override
+ public String toString() {
+ return "{ nSavedDC: "+nSavedDC+" }";
+ }
}
/**
@@ -103,16 +115,15 @@ public class HwmfMisc {
*/
public static class WmfSetBkColor implements HwmfRecord {
- private HwmfColorRef colorRef;
+ protected final HwmfColorRef colorRef = new HwmfColorRef();
@Override
- public HwmfRecordType getRecordType() {
+ public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.setBkColor;
}
@Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
- colorRef = new HwmfColorRef();
return colorRef.init(leis);
}
@@ -120,6 +131,11 @@ public class HwmfMisc {
public void draw(HwmfGraphics ctx) {
ctx.getProperties().setBackgroundColor(colorRef);
}
+
+ @Override
+ public String toString() {
+ return "{ colorRef: "+colorRef+" }";
+ }
}
/**
@@ -140,7 +156,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 +164,9 @@ public class HwmfMisc {
}
}
- private HwmfBkMode bkMode;
+ protected HwmfBkMode bkMode;
- public HwmfRecordType getRecordType() {
+ public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.setBkMode;
}
@@ -163,6 +179,11 @@ public class HwmfMisc {
public void draw(HwmfGraphics ctx) {
ctx.getProperties().setBkMode(bkMode);
}
+
+ @Override
+ public String toString() {
+ return "{ bkMode: '"+bkMode+"' }";
+ }
}
/**
@@ -180,7 +201,7 @@ public class HwmfMisc {
private int layout;
@Override
- public HwmfRecordType getRecordType() {
+ public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.setLayout;
}
@@ -205,10 +226,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;
}
@@ -223,6 +244,11 @@ public class HwmfMisc {
ctx.getProperties().setMapMode(mapMode);
ctx.updateWindowMapMode();
}
+
+ @Override
+ public String toString() {
+ return "{ mapMode: '"+mapMode+"' }";
+ }
}
/**
@@ -234,12 +260,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;
}
@@ -253,6 +280,11 @@ public class HwmfMisc {
public void draw(HwmfGraphics ctx) {
}
+
+ @Override
+ public String toString() {
+ return "{ mapperValues: "+mapperValues+" }";
+ }
}
/**
@@ -262,14 +294,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;
}
@@ -283,6 +312,11 @@ public class HwmfMisc {
public void draw(HwmfGraphics ctx) {
}
+
+ @Override
+ public String toString() {
+ return "{ drawMode: '"+drawMode+"' }";
+ }
}
/**
@@ -291,24 +325,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;
}
@@ -316,6 +389,11 @@ public class HwmfMisc {
public void draw(HwmfGraphics ctx) {
}
+
+ @Override
+ public String toString() {
+ return "{ stretchBltMode: '"+stretchBltMode+"' }";
+ }
}
/**
@@ -324,7 +402,7 @@ public class HwmfMisc {
*/
public static class WmfDibCreatePatternBrush implements HwmfRecord, HwmfImageRecord, HwmfObjectTableEntry {
- private HwmfBrushStyle style;
+ protected HwmfBrushStyle style;
/**
* A 16-bit unsigned integer that defines whether the Colors field of a DIB
@@ -335,13 +413,13 @@ public class HwmfMisc {
*
* If the Style field specified anything but BS_PATTERN, this field MUST be one of the ColorUsage values.
*/
- private ColorUsage colorUsage;
+ protected ColorUsage colorUsage;
- private HwmfBitmapDib patternDib;
+ protected HwmfBitmapDib patternDib;
private HwmfBitmap16 pattern16;
@Override
- public HwmfRecordType getRecordType() {
+ public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.dibCreatePatternBrush;
}
@@ -376,6 +454,9 @@ public class HwmfMisc {
@Override
public void applyObject(HwmfGraphics ctx) {
+ if (patternDib != null && !patternDib.isValid()) {
+ return;
+ }
HwmfDrawProperties prop = ctx.getProperties();
prop.setBrushStyle(style);
prop.setBrushBitmap(getImage());
@@ -383,7 +464,7 @@ public class HwmfMisc {
@Override
public BufferedImage getImage() {
- if (patternDib != null) {
+ if (patternDib != null && patternDib.isValid()) {
return patternDib.getImage();
} else if (pattern16 != null) {
return pattern16.getImage();
@@ -403,10 +484,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,8 +499,19 @@ 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);
}
+
+ @Override
+ public String toString() {
+ return "{ index: "+objectIndex+" }";
+ }
}
public static class WmfCreatePatternBrush implements HwmfRecord, HwmfObjectTableEntry {
@@ -427,7 +519,7 @@ public class HwmfMisc {
private HwmfBitmap16 pattern;
@Override
- public HwmfRecordType getRecordType() {
+ public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.createPatternBrush;
}
@@ -452,30 +544,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 +580,15 @@ public class HwmfMisc {
HwmfDrawProperties p = ctx.getProperties();
p.setPenStyle(penStyle);
p.setPenColor(colorRef);
- p.setPenWidth(xWidth);
+ p.setPenWidth(dimension.getWidth());
+ }
+
+ @Override
+ public String toString() {
+ return
+ "{ penStyle: "+penStyle+
+ ", dimension: { width: "+dimension.getWidth()+", height: "+dimension.getHeight()+" }"+
+ ", colorRef: "+colorRef+"}";
}
}
@@ -540,19 +638,14 @@ public class HwmfMisc {
* </table>
*/
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;
}
@@ -577,5 +670,13 @@ public class HwmfMisc {
p.setBrushColor(colorRef);
p.setBrushHatch(brushHatch);
}
+
+ @Override
+ public String toString() {
+ return
+ "{ brushStyle: '"+brushStyle+"'"+
+ ", colorRef: "+colorRef+
+ ", brushHatch: '"+brushHatch+"' }";
+ }
}
} \ No newline at end of file
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<PaletteEntry> palette = new ArrayList<>();
+ protected final List<PaletteEntry> 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<numberOfEntries; i++) {
PaletteEntry pe = new PaletteEntry();
size += pe.init(leis);
@@ -144,7 +149,7 @@ public class HwmfPalette {
*/
public static class WmfCreatePalette extends WmfPaletteParent implements HwmfObjectTableEntry {
@Override
- public HwmfRecordType getRecordType() {
+ public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.createPalette;
}
@@ -160,7 +165,7 @@ public class HwmfPalette {
*/
public static class WmfSetPaletteEntries extends WmfPaletteParent {
@Override
- public HwmfRecordType getRecordType() {
+ public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.setPalEntries;
}
@@ -197,10 +202,10 @@ public class HwmfPalette {
* A 16-bit unsigned integer that defines the number of entries in
* the logical palette.
*/
- int numberOfEntries;
+ protected int numberOfEntries;
@Override
- public HwmfRecordType getRecordType() {
+ public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.resizePalette;
}
@@ -238,10 +243,10 @@ public class HwmfPalette {
* A 16-bit unsigned integer used to index into the WMF Object Table to get
* the Palette Object to be selected.
*/
- private int paletteIndex;
+ protected int paletteIndex;
@Override
- public HwmfRecordType getRecordType() {
+ public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.selectPalette;
}
@@ -263,7 +268,7 @@ public class HwmfPalette {
*/
public static class WmfRealizePalette implements HwmfRecord {
@Override
- public HwmfRecordType getRecordType() {
+ public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.realizePalette;
}
@@ -292,7 +297,7 @@ public class HwmfPalette {
*/
public static class WmfAnimatePalette extends WmfPaletteParent {
@Override
- public HwmfRecordType getRecordType() {
+ public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.animatePalette;
}
diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfPenStyle.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfPenStyle.java
index d432b63216..776d48a3e7 100644
--- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfPenStyle.java
+++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfPenStyle.java
@@ -136,12 +136,13 @@ public class HwmfPenStyle implements Cloneable {
}
}
- private static final BitField SUBSECTION_DASH = BitFieldFactory.getInstance(0x0007);
- private static final BitField SUBSECTION_ALTERNATE = BitFieldFactory.getInstance(0x0008);
- private static final BitField SUBSECTION_ENDCAP = BitFieldFactory.getInstance(0x0300);
- private static final BitField SUBSECTION_JOIN = BitFieldFactory.getInstance(0x3000);
-
- private int flag;
+ private static final BitField SUBSECTION_DASH = BitFieldFactory.getInstance(0x00007);
+ private static final BitField SUBSECTION_ALTERNATE = BitFieldFactory.getInstance(0x00008);
+ private static final BitField SUBSECTION_ENDCAP = BitFieldFactory.getInstance(0x00300);
+ private static final BitField SUBSECTION_JOIN = BitFieldFactory.getInstance(0x03000);
+ private static final BitField SUBSECTION_GEOMETRIC = BitFieldFactory.getInstance(0x10000);
+
+ protected int flag;
public static HwmfPenStyle valueOf(int flag) {
HwmfPenStyle ps = new HwmfPenStyle();
@@ -160,7 +161,16 @@ public class HwmfPenStyle implements Cloneable {
public HwmfLineDash getLineDash() {
return HwmfLineDash.valueOf(SUBSECTION_DASH.getValue(flag));
}
-
+
+ /**
+ * Convienence method which should be used instead of accessing {@link HwmfLineDash#dashes}
+ * directly, so an subclass can provide user-style dashes
+ *
+ * @return the dash pattern
+ */
+ public float[] getLineDashes() {
+ return getLineDash().dashes;
+ }
/**
* The pen sets every other pixel (this style is applicable only for cosmetic pens).
@@ -169,6 +179,14 @@ public class HwmfPenStyle implements Cloneable {
return SUBSECTION_ALTERNATE.isSet(flag);
}
+ /**
+ * A pen type that specifies a line with a width that is measured in logical units
+ * and a style that can contain any of the attributes of a brush.
+ */
+ public boolean isGeometric() {
+ return SUBSECTION_GEOMETRIC.isSet(flag);
+ }
+
/**
* Creates a new object of the same class and with the
@@ -186,4 +204,15 @@ public class HwmfPenStyle implements Cloneable {
throw new InternalError();
}
}
+
+ @Override
+ public String toString() {
+ return
+ "{ lineCap: '"+getLineCap()+"'"+
+ ", lineDash: '"+getLineDash()+"'"+
+ ", lineJoin: '"+getLineJoin()+"'"+
+ (isAlternateDash()?", alternateDash: true ":"")+
+ (isGeometric()?", geometric: true ":"")+
+ "}";
+ }
}
diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfRecord.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfRecord.java
index 6c7680f077..2673e80783 100644
--- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfRecord.java
+++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfRecord.java
@@ -23,7 +23,7 @@ import org.apache.poi.hwmf.draw.HwmfGraphics;
import org.apache.poi.util.LittleEndianInputStream;
public interface HwmfRecord {
- HwmfRecordType getRecordType();
+ HwmfRecordType getWmfRecordType();
/**
* Init record from stream
diff --git a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfRecordType.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfRecordType.java
index 5d903e45a6..297808debd 100644
--- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfRecordType.java
+++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfRecordType.java
@@ -17,6 +17,8 @@
package org.apache.poi.hwmf.record;
+import java.util.function.Supplier;
+
/**
* Available record types for WMF
*
@@ -24,83 +26,83 @@ package org.apache.poi.hwmf.record;
*/
public enum HwmfRecordType {
eof(0x0000, null)
- ,animatePalette(0x0436, HwmfPalette.WmfAnimatePalette.class)
- ,arc(0x0817, HwmfDraw.WmfArc.class)
- ,bitBlt(0x0922, HwmfFill.WmfBitBlt.class)
- ,chord(0x0830, HwmfDraw.WmfChord.class)
- ,createBrushIndirect(0x02fc, HwmfMisc.WmfCreateBrushIndirect.class)
- ,createFontIndirect(0x02fb, HwmfText.WmfCreateFontIndirect.class)
- ,createPalette(0x00f7, HwmfPalette.WmfCreatePalette.class)
- ,createPatternBrush(0x01f9, HwmfMisc.WmfCreatePatternBrush.class)
- ,createPenIndirect(0x02fa, HwmfMisc.WmfCreatePenIndirect.class)
- ,createRegion(0x06ff, HwmfWindowing.WmfCreateRegion.class)
- ,deleteObject(0x01f0, HwmfMisc.WmfDeleteObject.class)
- ,dibBitBlt(0x0940, HwmfFill.WmfDibBitBlt.class)
- ,dibCreatePatternBrush(0x0142, HwmfMisc.WmfDibCreatePatternBrush.class)
- ,dibStretchBlt(0x0b41, HwmfFill.WmfDibStretchBlt.class)
- ,ellipse(0x0418, HwmfDraw.WmfEllipse.class)
- ,escape(0x0626, HwmfEscape.class)
- ,excludeClipRect(0x0415, HwmfWindowing.WmfExcludeClipRect.class)
- ,extFloodFill(0x0548, HwmfFill.WmfExtFloodFill.class)
- ,extTextOut(0x0a32, HwmfText.WmfExtTextOut.class)
- ,fillRegion(0x0228, HwmfFill.WmfFillRegion.class)
- ,floodFill(0x0419, HwmfFill.WmfFloodFill.class)
- ,frameRegion(0x0429, HwmfDraw.WmfFrameRegion.class)
- ,intersectClipRect(0x0416, HwmfWindowing.WmfIntersectClipRect.class)
- ,invertRegion(0x012a, HwmfFill.WmfInvertRegion.class)
- ,lineTo(0x0213, HwmfDraw.WmfLineTo.class)
- ,moveTo(0x0214, HwmfDraw.WmfMoveTo.class)
- ,offsetClipRgn(0x0220, HwmfWindowing.WmfOffsetClipRgn.class)
- ,offsetViewportOrg(0x0211, HwmfWindowing.WmfOffsetViewportOrg.class)
- ,offsetWindowOrg(0x020f, HwmfWindowing.WmfOffsetWindowOrg.class)
- ,paintRegion(0x012b, HwmfFill.WmfPaintRegion.class)
- ,patBlt(0x061d, HwmfFill.WmfPatBlt.class)
- ,pie(0x081a, HwmfDraw.WmfPie.class)
- ,polygon(0x0324, HwmfDraw.WmfPolygon.class)
- ,polyline(0x0325, HwmfDraw.WmfPolyline.class)
- ,polyPolygon(0x0538, HwmfDraw.WmfPolyPolygon.class)
- ,realizePalette(0x0035, HwmfPalette.WmfRealizePalette.class)
- ,rectangle(0x041b, HwmfDraw.WmfRectangle.class)
- ,resizePalette(0x0139, HwmfPalette.WmfResizePalette.class)
- ,restoreDc(0x0127, HwmfMisc.WmfRestoreDc.class)
- ,roundRect(0x061c, HwmfDraw.WmfRoundRect.class)
- ,saveDc(0x001e, HwmfMisc.WmfSaveDc.class)
- ,scaleViewportExt(0x0412, HwmfWindowing.WmfScaleViewportExt.class)
- ,scaleWindowExt(0x0410, HwmfWindowing.WmfScaleWindowExt.class)
- ,selectClipRegion(0x012c, HwmfWindowing.WmfSelectClipRegion.class)
- ,selectObject(0x012d, HwmfDraw.WmfSelectObject.class)
- ,selectPalette(0x0234, HwmfPalette.WmfSelectPalette.class)
- ,setBkColor(0x0201, HwmfMisc.WmfSetBkColor.class)
- ,setBkMode(0x0102, HwmfMisc.WmfSetBkMode.class)
- ,setDibToDev(0x0d33, HwmfFill.WmfSetDibToDev.class)
- ,setLayout(0x0149, HwmfMisc.WmfSetLayout.class)
- ,setMapMode(0x0103, HwmfMisc.WmfSetMapMode.class)
- ,setMapperFlags(0x0231, HwmfMisc.WmfSetMapperFlags.class)
- ,setPalEntries(0x0037, HwmfPalette.WmfSetPaletteEntries.class)
- ,setPixel(0x041f, HwmfDraw.WmfSetPixel.class)
- ,setPolyFillMode(0x0106, HwmfFill.WmfSetPolyfillMode.class)
- ,setRelabs(0x0105, HwmfMisc.WmfSetRelabs.class)
- ,setRop2(0x0104, HwmfMisc.WmfSetRop2.class)
- ,setStretchBltMode(0x0107, HwmfMisc.WmfSetStretchBltMode.class)
- ,setTextAlign(0x012e, HwmfText.WmfSetTextAlign.class)
- ,setTextCharExtra(0x0108, HwmfText.WmfSetTextCharExtra.class)
- ,setTextColor(0x0209, HwmfText.WmfSetTextColor.class)
- ,setTextJustification(0x020a, HwmfText.WmfSetTextJustification.class)
- ,setViewportExt(0x020e, HwmfWindowing.WmfSetViewportExt.class)
- ,setViewportOrg(0x020d, HwmfWindowing.WmfSetViewportOrg.class)
- ,setWindowExt(0x020c, HwmfWindowing.WmfSetWindowExt.class)
- ,setWindowOrg(0x020b, HwmfWindowing.WmfSetWindowOrg.class)
- ,stretchBlt(0x0b23, HwmfFill.WmfStretchBlt.class)
- ,stretchDib(0x0f43, HwmfFill.WmfStretchDib.class)
- ,textOut(0x0521, HwmfText.WmfTextOut.class)
+ ,animatePalette(0x0436, HwmfPalette.WmfAnimatePalette::new)
+ ,arc(0x0817, HwmfDraw.WmfArc::new)
+ ,bitBlt(0x0922, HwmfFill.WmfBitBlt::new)
+ ,chord(0x0830, HwmfDraw.WmfChord::new)
+ ,createBrushIndirect(0x02fc, HwmfMisc.WmfCreateBrushIndirect::new)
+ ,createFontIndirect(0x02fb, HwmfText.WmfCreateFontIndirect::new)
+ ,createPalette(0x00f7, HwmfPalette.WmfCreatePalette::new)
+ ,createPatternBrush(0x01f9, HwmfMisc.WmfCreatePatternBrush::new)
+ ,createPenIndirect(0x02fa, HwmfMisc.WmfCreatePenIndirect::new)
+ ,createRegion(0x06ff, HwmfWindowing.WmfCreateRegion::new)
+ ,deleteObject(0x01f0, HwmfMisc.WmfDeleteObject::new)
+ ,dibBitBlt(0x0940, HwmfFill.WmfDibBitBlt::new)
+ ,dibCreatePatternBrush(0x0142, HwmfMisc.WmfDibCreatePatternBrush::new)
+ ,dibStretchBlt(0x0b41, HwmfFill.WmfDibStretchBlt::new)
+ ,ellipse(0x0418, HwmfDraw.WmfEllipse::new)
+ ,escape(0x0626, HwmfEscape::new)
+ ,excludeClipRect(0x0415, HwmfWindowing.WmfExcludeClipRect::new)
+ ,extFloodFill(0x0548, HwmfFill.WmfExtFloodFill::new)
+ ,extTextOut(0x0a32, HwmfText.WmfExtTextOut::new)
+ ,fillRegion(0x0228, HwmfFill.WmfFillRegion::new)
+ ,floodFill(0x0419, HwmfFill.WmfFloodFill::new)
+ ,frameRegion(0x0429, HwmfDraw.WmfFrameRegion::new)
+ ,intersectClipRect(0x0416, HwmfWindowing.WmfIntersectClipRect::new)
+ ,invertRegion(0x012a, HwmfFill.WmfInvertRegion::new)
+ ,lineTo(0x0213, HwmfDraw.WmfLineTo::new)
+ ,moveTo(0x0214, HwmfDraw.WmfMoveTo::new)
+ ,offsetClipRgn(0x0220, HwmfWindowing.WmfOffsetClipRgn::new)
+ ,offsetViewportOrg(0x0211, HwmfWindowing.WmfOffsetViewportOrg::new)
+ ,offsetWindowOrg(0x020f, HwmfWindowing.WmfOffsetWindowOrg::new)
+ ,paintRegion(0x012b, HwmfFill.WmfPaintRegion::new)
+ ,patBlt(0x061d, HwmfFill.WmfPatBlt::new)
+ ,pie(0x081a, HwmfDraw.WmfPie::new)
+ ,polygon(0x0324, HwmfDraw.WmfPolygon::new)
+ ,polyline(0x0325, HwmfDraw.WmfPolyline::new)
+ ,polyPolygon(0x0538, HwmfDraw.WmfPolyPolygon::new)
+ ,realizePalette(0x0035, HwmfPalette.WmfRealizePalette::new)
+ ,rectangle(0x041b, HwmfDraw.WmfRectangle::new)
+ ,resizePalette(0x0139, HwmfPalette.WmfResizePalette::new)
+ ,restoreDc(0x0127, HwmfMisc.WmfRestoreDc::new)
+ ,roundRect(0x061c, HwmfDraw.WmfRoundRect::new)
+ ,saveDc(0x001e, HwmfMisc.WmfSaveDc::new)
+ ,scaleViewportExt(0x0412, HwmfWindowing.WmfScaleViewportExt::new)
+ ,scaleWindowExt(0x0410, HwmfWindowing.WmfScaleWindowExt::new)
+ ,selectClipRegion(0x012c, HwmfWindowing.WmfSelectClipRegion::new)
+ ,selectObject(0x012d, HwmfDraw.WmfSelectObject::new)
+ ,selectPalette(0x0234, HwmfPalette.WmfSelectPalette::new)
+ ,setBkColor(0x0201, HwmfMisc.WmfSetBkColor::new)
+ ,setBkMode(0x0102, HwmfMisc.WmfSetBkMode::new)
+ ,setDibToDev(0x0d33, HwmfFill.WmfSetDibToDev::new)
+ ,setLayout(0x0149, HwmfMisc.WmfSetLayout::new)
+ ,setMapMode(0x0103, HwmfMisc.WmfSetMapMode::new)
+ ,setMapperFlags(0x0231, HwmfMisc.WmfSetMapperFlags::new)
+ ,setPalEntries(0x0037, HwmfPalette.WmfSetPaletteEntries::new)
+ ,setPixel(0x041f, HwmfDraw.WmfSetPixel::new)
+ ,setPolyFillMode(0x0106, HwmfFill.WmfSetPolyfillMode::new)
+ ,setRelabs(0x0105, HwmfMisc.WmfSetRelabs::new)
+ ,setRop2(0x0104, HwmfMisc.WmfSetRop2::new)
+ ,setStretchBltMode(0x0107, HwmfMisc.WmfSetStretchBltMode::new)
+ ,setTextAlign(0x012e, HwmfText.WmfSetTextAlign::new)
+ ,setTextCharExtra(0x0108, HwmfText.WmfSetTextCharExtra::new)
+ ,setTextColor(0x0209, HwmfText.WmfSetTextColor::new)
+ ,setTextJustification(0x020a, HwmfText.WmfSetTextJustification::new)
+ ,setViewportExt(0x020e, HwmfWindowing.WmfSetViewportExt::new)
+ ,setViewportOrg(0x020d, HwmfWindowing.WmfSetViewportOrg::new)
+ ,setWindowExt(0x020c, HwmfWindowing.WmfSetWindowExt::new)
+ ,setWindowOrg(0x020b, HwmfWindowing.WmfSetWindowOrg::new)
+ ,stretchBlt(0x0b23, HwmfFill.WmfStretchBlt::new)
+ ,stretchDib(0x0f43, HwmfFill.WmfStretchDib::new)
+ ,textOut(0x0521, HwmfText.WmfTextOut::new)
;
public final int id;
- public final Class<? extends HwmfRecord> clazz;
+ public final Supplier<? extends HwmfRecord> constructor;
- HwmfRecordType(int id, Class<? extends HwmfRecord> clazz) {
+ HwmfRecordType(int id, Supplier<? extends HwmfRecord> 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/HwmfRegionMode.java b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfRegionMode.java
new file mode 100644
index 0000000000..3a56e52e70
--- /dev/null
+++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfRegionMode.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.hwmf.record;
+
+import org.apache.poi.hemf.record.emf.HemfFill;
+
+public enum HwmfRegionMode {
+ /**
+ * The new clipping region includes the intersection (overlapping areas)
+ * of the current clipping region and the current path (or new region).
+ */
+ RGN_AND(0x01),
+ /**
+ * The new clipping region includes the union (combined areas)
+ * of the current clipping region and the current path (or new region).
+ */
+ RGN_OR(0x02),
+ /**
+ * The new clipping region includes the union of the current clipping region
+ * and the current path (or new region) but without the overlapping areas
+ */
+ RGN_XOR(0x03),
+ /**
+ * The new clipping region includes the areas of the current clipping region
+ * with those of the current path (or new region) excluded.
+ */
+ RGN_DIFF(0x04),
+ /**
+ * The new clipping region is the current path (or the new region).
+ */
+ RGN_COPY(0x05);
+
+ int flag;
+ HwmfRegionMode(int flag) {
+ this.flag = flag;
+ }
+
+ public static HwmfRegionMode valueOf(int flag) {
+ for (HwmfRegionMode rm : values()) {
+ if (rm.flag == flag) return rm;
+ }
+ return null;
+ }
+}
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..391215d657 100644
--- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java
+++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java
@@ -17,10 +17,19 @@
package org.apache.poi.hwmf.record;
+import static org.apache.poi.hwmf.record.HwmfDraw.boundsToString;
+import static org.apache.poi.hwmf.record.HwmfDraw.pointToString;
+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.IOException;
import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.commons.codec.Charsets;
import org.apache.poi.hwmf.draw.HwmfDrawProperties;
import org.apache.poi.hwmf.draw.HwmfGraphics;
import org.apache.poi.hwmf.record.HwmfMisc.WmfSetMapMode;
@@ -29,6 +38,7 @@ import org.apache.poi.util.BitFieldFactory;
import org.apache.poi.util.IOUtils;
import org.apache.poi.util.LittleEndianConsts;
import org.apache.poi.util.LittleEndianInputStream;
+import org.apache.poi.util.LocaleUtil;
import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger;
@@ -52,7 +62,7 @@ public class HwmfText {
private int charExtra;
@Override
- public HwmfRecordType getRecordType() {
+ public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.setTextCharExtra;
}
@@ -73,16 +83,15 @@ public class HwmfText {
*/
public static class WmfSetTextColor implements HwmfRecord {
- private HwmfColorRef colorRef;
+ protected final HwmfColorRef colorRef = new HwmfColorRef();
@Override
- public HwmfRecordType getRecordType() {
+ public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.setTextColor;
}
@Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
- colorRef = new HwmfColorRef();
return colorRef.init(leis);
}
@@ -90,6 +99,11 @@ public class HwmfText {
public void draw(HwmfGraphics ctx) {
ctx.getProperties().setTextColor(colorRef);
}
+
+ @Override
+ public String toString() {
+ return "{ colorRef: "+colorRef+" }";
+ }
}
/**
@@ -112,7 +126,7 @@ public class HwmfText {
private int breakExtra;
@Override
- public HwmfRecordType getRecordType() {
+ public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.setBkColor;
}
@@ -147,19 +161,11 @@ public class HwmfText {
* The string is written at the location specified by the XStart and YStart fields.
*/
private byte[] rawTextBytes;
- /**
- * A 16-bit signed integer that defines the vertical (y-axis) coordinate, in logical
- * units, of the point where drawing is to start.
- */
- private int yStart;
- /**
- * A 16-bit signed integer that defines the horizontal (x-axis) coordinate, in
- * logical units, of the point where drawing is to start.
- */
- private int xStart;
-
+
+ protected Point2D reference = new Point2D.Double();
+
@Override
- public HwmfRecordType getRecordType() {
+ public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.textOut;
}
@@ -168,15 +174,19 @@ public class HwmfText {
stringLength = leis.readShort();
rawTextBytes = IOUtils.safelyAllocate(stringLength+(stringLength&1), MAX_RECORD_LENGTH);
leis.readFully(rawTextBytes);
- yStart = leis.readShort();
- xStart = leis.readShort();
+ // A 16-bit signed integer that defines the vertical (y-axis) coordinate, in logical
+ // units, of the point where drawing is to start.
+ int yStart = leis.readShort();
+ // A 16-bit signed integer that defines the horizontal (x-axis) coordinate, in
+ // logical units, of the point where drawing is to start.
+ int xStart = leis.readShort();
+ reference.setLocation(xStart, yStart);
return 3*LittleEndianConsts.SHORT_SIZE+rawTextBytes.length;
}
@Override
public void draw(HwmfGraphics ctx) {
- Rectangle2D bounds = new Rectangle2D.Double(xStart, yStart, 0, 0);
- ctx.drawString(getTextBytes(), bounds);
+ ctx.drawString(getTextBytes(), stringLength, reference);
}
public String getText(Charset charset) {
@@ -195,41 +205,48 @@ 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.
*/
private static final BitField ETO_NUMERICSLOCAL = BitFieldFactory.getInstance(0x0400);
@@ -240,32 +257,62 @@ 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);
+ }
+
+ public boolean isYDisplaced() {
+ return ETO_PDY.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,24 +321,32 @@ 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
* character cell i and character cell i + 1. If this field is present, there MUST be the same
* number of values as there are characters in the string.
*/
- private int dx[];
-
+ protected final List<Integer> dx = new ArrayList<>();
+
+ public WmfExtTextOut() {
+ this(new WmfExtTextOutOptions());
+ }
+
+ protected WmfExtTextOut(WmfExtTextOutOptions options) {
+ this.options = options;
+ }
+
@Override
- public HwmfRecordType getRecordType() {
+ public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.extTextOut;
}
@@ -299,22 +354,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);
@@ -331,9 +381,8 @@ public class HwmfText {
logger.log(POILogger.WARN, "META_EXTTEXTOUT tracking info doesn't cover all characters");
}
- dx = new int[stringLength];
for (int i=0; i<dxLen; i++) {
- dx[i] = leis.readShort();
+ dx.add((int)leis.readShort());
size += LittleEndianConsts.SHORT_SIZE;
}
@@ -342,24 +391,38 @@ public class HwmfText {
@Override
public void draw(HwmfGraphics ctx) {
- Rectangle2D bounds = new Rectangle2D.Double(x, y, 0, 0);
- ctx.drawString(getTextBytes(), bounds, dx);
+ ctx.drawString(rawTextBytes, stringLength, reference, null, bounds, options, dx, false);
}
- public String getText(Charset charset) {
- return new String(getTextBytes(), charset);
+ public String getText(Charset charset) throws IOException {
+ return new String(rawTextBytes, charset).substring(0, stringLength);
}
- /**
- *
- * @return a copy of a trimmed byte array of rawTextBytes bytes.
- * This includes only the bytes from 0..stringLength.
- * This does not include the extra optional padding on the byte array.
- */
- private byte[] getTextBytes() {
- byte[] ret = IOUtils.safelyAllocate(stringLength, MAX_RECORD_LENGTH);
- System.arraycopy(rawTextBytes, 0, ret, 0, stringLength);
- return ret;
+ public Point2D getReference() {
+ return reference;
+ }
+
+ public Rectangle2D getBounds() {
+ return bounds;
+ }
+
+ protected boolean isUnicode() {
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ String text = "";
+ try {
+ text = getText(isUnicode() ? Charsets.UTF_16LE : LocaleUtil.CHARSET_1252);
+ } catch (IOException ignored) {
+ }
+
+ return
+ "{ reference: " + pointToString(reference) +
+ ", bounds: " + boundsToString(bounds) +
+ ", text: '"+text.replaceAll("\\p{Cntrl}",".")+"'"+
+ "}";
}
}
@@ -380,57 +443,17 @@ public class HwmfText {
*/
public static class WmfSetTextAlign implements HwmfRecord {
- // ***********************************************************************************
- // TextAlignmentMode Flags:
- // ***********************************************************************************
-
- /**
- * The drawing position in the playback device context MUST NOT be updated after each
- * text output call. The reference point MUST be passed to the text output function.
- */
- @SuppressWarnings("unused")
- private static final BitField TA_NOUPDATECP = BitFieldFactory.getInstance(0x0000);
-
- /**
- * The reference point MUST be on the left edge of the bounding rectangle.
- */
- @SuppressWarnings("unused")
- private static final BitField TA_LEFT = BitFieldFactory.getInstance(0x0000);
-
- /**
- * The reference point MUST be on the top edge of the bounding rectangle.
- */
- @SuppressWarnings("unused")
- private static final BitField TA_TOP = BitFieldFactory.getInstance(0x0000);
-
/**
* The drawing position in the playback device context MUST be updated after each text
- * output call. It MUST be used as the reference point.
+ * output call. It MUST be used as the reference point.<p>
+ *
+ * If the flag is not set, the option TA_NOUPDATECP is active, i.e. the drawing position
+ * in the playback device context MUST NOT be updated after each text output call.
+ * The reference point MUST be passed to the text output function.
*/
@SuppressWarnings("unused")
private static final BitField TA_UPDATECP = BitFieldFactory.getInstance(0x0001);
-
- /**
- * The reference point MUST be on the right edge of the bounding rectangle.
- */
- private static final BitField TA_RIGHT = BitFieldFactory.getInstance(0x0002);
-
- /**
- * The reference point MUST be aligned horizontally with the center of the bounding
- * rectangle.
- */
- private static final BitField TA_CENTER = BitFieldFactory.getInstance(0x0006);
-
- /**
- * The reference point MUST be on the bottom edge of the bounding rectangle.
- */
- private static final BitField TA_BOTTOM = BitFieldFactory.getInstance(0x0008);
-
- /**
- * The reference point MUST be on the baseline of the text.
- */
- private static final BitField TA_BASELINE = BitFieldFactory.getInstance(0x0018);
-
+
/**
* 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
@@ -438,43 +461,64 @@ public class HwmfText {
*/
@SuppressWarnings("unused")
private static final BitField TA_RTLREADING = BitFieldFactory.getInstance(0x0100);
-
- // ***********************************************************************************
- // VerticalTextAlignmentMode Flags (e.g. for Kanji fonts)
- // ***********************************************************************************
-
+
+
+ private static final BitField ALIGN_MASK = BitFieldFactory.getInstance(0x0006);
+
/**
- * The reference point MUST be on the top edge of the bounding rectangle.
+ * Flag TA_LEFT (0x0000):
+ * The reference point MUST be on the left edge of the bounding rectangle,
+ * if all bits of the align mask (latin mode) are unset.
+ *
+ * Flag VTA_TOP (0x0000):
+ * The reference point MUST be on the top edge of the bounding rectangle,
+ * if all bits of the valign mask are unset.
*/
- @SuppressWarnings("unused")
- private static final BitField VTA_TOP = BitFieldFactory.getInstance(0x0000);
-
+ private static final int ALIGN_LEFT = 0;
+
/**
+ * Flag TA_RIGHT (0x0002):
* The reference point MUST be on the right edge of the bounding rectangle.
- */
- @SuppressWarnings("unused")
- private static final BitField VTA_RIGHT = BitFieldFactory.getInstance(0x0000);
-
- /**
+ *
+ * Flag VTA_BOTTOM (0x0002):
* The reference point MUST be on the bottom edge of the bounding rectangle.
*/
- private static final BitField VTA_BOTTOM = BitFieldFactory.getInstance(0x0002);
-
+ private static final int ALIGN_RIGHT = 1;
+
/**
- * The reference point MUST be aligned vertically with the center of the bounding
+ * Flag TA_CENTER (0x0006) / VTA_CENTER (0x0006):
+ * The reference point MUST be aligned horizontally with the center of the bounding
* rectangle.
*/
- private static final BitField VTA_CENTER = BitFieldFactory.getInstance(0x0006);
-
+ private static final int ALIGN_CENTER = 3;
+
+ private static final BitField VALIGN_MASK = BitFieldFactory.getInstance(0x0018);
+
+ /**
+ * Flag TA_TOP (0x0000):
+ * The reference point MUST be on the top edge of the bounding rectangle,
+ * if all bits of the valign mask are unset.
+ *
+ * Flag VTA_RIGHT (0x0000):
+ * The reference point MUST be on the right edge of the bounding rectangle,
+ * if all bits of the align mask (asian mode) are unset.
+ */
+ private static final int VALIGN_TOP = 0;
+
/**
+ * Flag TA_BOTTOM (0x0008):
+ * The reference point MUST be on the bottom edge of the bounding rectangle.
+ *
+ * Flag VTA_LEFT (0x0008):
* The reference point MUST be on the left edge of the bounding rectangle.
*/
- private static final BitField VTA_LEFT = BitFieldFactory.getInstance(0x0008);
+ private static final int VALIGN_BOTTOM = 1;
/**
+ * Flag TA_BASELINE (0x0018) / VTA_BASELINE (0x0018):
* The reference point MUST be on the baseline of the text.
*/
- private static final BitField VTA_BASELINE = BitFieldFactory.getInstance(0x0018);
+ private static final int VALIGN_BASELINE = 3;
/**
* A 16-bit unsigned integer that defines text alignment.
@@ -482,10 +526,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;
}
@@ -498,52 +542,90 @@ public class HwmfText {
@Override
public void draw(HwmfGraphics ctx) {
HwmfDrawProperties props = ctx.getProperties();
- if (TA_CENTER.isSet(textAlignmentMode)) {
- props.setTextAlignLatin(HwmfTextAlignment.CENTER);
- } else if (TA_RIGHT.isSet(textAlignmentMode)) {
- props.setTextAlignLatin(HwmfTextAlignment.RIGHT);
- } else {
- props.setTextAlignLatin(HwmfTextAlignment.LEFT);
+ props.setTextAlignLatin(getAlignLatin());
+ props.setTextVAlignLatin(getVAlignLatin());
+ props.setTextAlignAsian(getAlignAsian());
+ props.setTextVAlignAsian(getVAlignAsian());
+ }
+
+ @Override
+ public String toString() {
+ return
+ "{ align: '"+ getAlignLatin() + "'" +
+ ", valign: '"+ getVAlignLatin() + "'" +
+ ", alignAsian: '"+ getAlignAsian() + "'" +
+ ", valignAsian: '"+ getVAlignAsian() + "'" +
+ "}";
+ }
+
+ private HwmfTextAlignment getAlignLatin() {
+ switch (ALIGN_MASK.getValue(textAlignmentMode)) {
+ default:
+ case ALIGN_LEFT:
+ return HwmfTextAlignment.LEFT;
+ case ALIGN_CENTER:
+ return HwmfTextAlignment.CENTER;
+ case ALIGN_RIGHT:
+ return HwmfTextAlignment.RIGHT;
}
+ }
- if (VTA_CENTER.isSet(textAlignmentMode)) {
- props.setTextAlignAsian(HwmfTextAlignment.CENTER);
- } else if (VTA_LEFT.isSet(textAlignmentMode)) {
- props.setTextAlignAsian(HwmfTextAlignment.LEFT);
- } else {
- props.setTextAlignAsian(HwmfTextAlignment.RIGHT);
+ private HwmfTextVerticalAlignment getVAlignLatin() {
+ switch (VALIGN_MASK.getValue(textAlignmentMode)) {
+ default:
+ case VALIGN_TOP:
+ return HwmfTextVerticalAlignment.TOP;
+ case VALIGN_BASELINE:
+ return HwmfTextVerticalAlignment.BASELINE;
+ case VALIGN_BOTTOM:
+ return HwmfTextVerticalAlignment.BOTTOM;
}
+ }
- if (TA_BASELINE.isSet(textAlignmentMode)) {
- props.setTextVAlignLatin(HwmfTextVerticalAlignment.BASELINE);
- } else if (TA_BOTTOM.isSet(textAlignmentMode)) {
- props.setTextVAlignLatin(HwmfTextVerticalAlignment.BOTTOM);
- } else {
- props.setTextVAlignLatin(HwmfTextVerticalAlignment.TOP);
+ private HwmfTextAlignment getAlignAsian() {
+ switch (getVAlignLatin()) {
+ default:
+ case TOP:
+ return HwmfTextAlignment.RIGHT;
+ case BASELINE:
+ return HwmfTextAlignment.CENTER;
+ case BOTTOM:
+ return HwmfTextAlignment.LEFT;
}
+ }
- if (VTA_BASELINE.isSet(textAlignmentMode)) {
- props.setTextVAlignAsian(HwmfTextVerticalAlignment.BASELINE);
- } else if (VTA_BOTTOM.isSet(textAlignmentMode)) {
- props.setTextVAlignAsian(HwmfTextVerticalAlignment.BOTTOM);
- } else {
- props.setTextVAlignAsian(HwmfTextVerticalAlignment.TOP);
+ private HwmfTextVerticalAlignment getVAlignAsian() {
+ switch (getAlignLatin()) {
+ default:
+ case LEFT:
+ return HwmfTextVerticalAlignment.TOP;
+ case CENTER:
+ return HwmfTextVerticalAlignment.BASELINE;
+ case RIGHT:
+ return HwmfTextVerticalAlignment.BOTTOM;
}
}
}
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
@@ -559,5 +641,10 @@ public class HwmfText {
public HwmfFont getFont() {
return font;
}
+
+ @Override
+ public String toString() {
+ return "{ font: "+font+" } ";
+ }
}
}
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..bbf5b9eb15 100644
--- a/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfWindowing.java
+++ b/src/scratchpad/src/org/apache/poi/hwmf/record/HwmfWindowing.java
@@ -17,12 +17,23 @@
package org.apache.poi.hwmf.record;
+import static org.apache.poi.hwmf.record.HwmfDraw.boundsToString;
+import static org.apache.poi.hwmf.record.HwmfDraw.dimToString;
+import static org.apache.poi.hwmf.record.HwmfDraw.normalizeBounds;
+import static org.apache.poi.hwmf.record.HwmfDraw.pointToString;
+import static org.apache.poi.hwmf.record.HwmfDraw.readBounds;
+import static org.apache.poi.hwmf.record.HwmfDraw.readPointS;
+
import java.awt.Shape;
import java.awt.geom.Area;
+import java.awt.geom.Dimension2D;
+import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
+import org.apache.poi.hwmf.draw.HwmfDrawProperties;
import org.apache.poi.hwmf.draw.HwmfGraphics;
+import org.apache.poi.util.Dimension2DDouble;
import org.apache.poi.util.LittleEndianConsts;
import org.apache.poi.util.LittleEndianInputStream;
@@ -33,31 +44,33 @@ 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 16-bit signed integer that defines the horizontal offset, in device units.
- */
- private int x;
+ protected final Point2D origin = new Point2D.Double();
@Override
- public HwmfRecordType getRecordType() {
+ public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.setViewportOrg;
}
@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, origin);
}
@Override
public void draw(HwmfGraphics ctx) {
- ctx.getProperties().setViewportOrg(x, y);
+ final HwmfDrawProperties prop = ctx.getProperties();
+ Rectangle2D old = prop.getViewport();
+ double oldX = (old == null ? 0 : old.getX());
+ double oldY = (old == null ? 0 : old.getY());
+ if (oldX != origin.getX() || oldY != origin.getY()) {
+ prop.setViewportOrg(origin.getX(), origin.getY());
+ ctx.updateWindowMapMode();
+ }
+ }
+
+ @Override
+ public String toString() {
+ return pointToString(origin);
}
}
@@ -67,33 +80,38 @@ 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 16-bit signed integer that defines the horizontal extent
- * of the viewport in device units.
- */
- private int width;
+ protected final Dimension2D extents = new Dimension2DDouble();
@Override
- public HwmfRecordType getRecordType() {
+ public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.setViewportExt;
}
@Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
- height = leis.readShort();
- width = leis.readShort();
+ // A signed integer that defines the vertical extent of the viewport in device units.
+ int height = leis.readShort();
+ // A signed integer that defines the horizontal extent of the viewport in device units.
+ int width = leis.readShort();
+ extents.setSize(width, height);
return 2*LittleEndianConsts.SHORT_SIZE;
}
@Override
public void draw(HwmfGraphics ctx) {
- ctx.getProperties().setViewportExt(width, height);
+ final HwmfDrawProperties prop = ctx.getProperties();
+ Rectangle2D old = prop.getViewport();
+ double oldW = (old == null ? 0 : old.getWidth());
+ double oldH = (old == null ? 0 : old.getHeight());
+ if (oldW != extents.getWidth() || oldH != extents.getHeight()) {
+ prop.setViewportExt(extents.getWidth(), extents.getHeight());
+ ctx.updateWindowMapMode();
+ }
+ }
+
+ @Override
+ public String toString() {
+ return dimToString(extents);
}
}
@@ -103,34 +121,33 @@ public class HwmfWindowing {
*/
public static class WmfOffsetViewportOrg implements HwmfRecord {
- /**
- * A 16-bit signed integer that defines the vertical offset, in device units.
- */
- private int yOffset;
-
- /**
- * A 16-bit signed integer that defines the horizontal offset, in device units.
- */
- private int xOffset;
+ protected final Point2D offset = new Point2D.Double();
@Override
- public HwmfRecordType getRecordType() {
+ public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.offsetViewportOrg;
}
@Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
- yOffset = leis.readShort();
- xOffset = leis.readShort();
- return 2*LittleEndianConsts.SHORT_SIZE;
+ return readPointS(leis, offset);
}
@Override
public void draw(HwmfGraphics ctx) {
- Rectangle2D viewport = ctx.getProperties().getViewport();
- double x = (viewport == null) ? 0 : viewport.getX();
- double y = (viewport == null) ? 0 : viewport.getY();
- ctx.getProperties().setViewportOrg(x+xOffset, y+yOffset);
+ final HwmfDrawProperties prop = ctx.getProperties();
+ Rectangle2D viewport = prop.getViewport();
+ if (offset.getX() != 0 || offset.getY() != 0) {
+ double x = (viewport == null) ? 0 : viewport.getX();
+ double y = (viewport == null) ? 0 : viewport.getY();
+ prop.setViewportOrg(x + offset.getX(), y + offset.getY());
+ ctx.updateWindowMapMode();
+ }
+ }
+
+ @Override
+ public String toString() {
+ return pointToString(offset);
}
}
@@ -139,40 +156,41 @@ 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 16-bit signed integer that defines the x-coordinate, in logical units.
- */
- private int x;
+ protected final Point2D origin = new Point2D.Double();
@Override
- public HwmfRecordType getRecordType() {
+ public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.setWindowOrg;
}
@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, origin);
}
@Override
public void draw(HwmfGraphics ctx) {
- ctx.getProperties().setWindowOrg(x, y);
- ctx.updateWindowMapMode();
+ final HwmfDrawProperties prop = ctx.getProperties();
+ final Rectangle2D old = prop.getWindow();
+ double oldX = (old == null ? 0 : old.getX());
+ double oldY = (old == null ? 0 : old.getY());
+ if (oldX != getX() || oldY != getY()) {
+ prop.setWindowOrg(getX(), getY());
+ ctx.updateWindowMapMode();
+ }
}
- public int getY() {
- return y;
+ public double getY() {
+ return origin.getY();
}
- public int getX() {
- return x;
+ public double getX() {
+ return origin.getX();
+ }
+
+ @Override
+ public String toString() {
+ return pointToString(origin);
}
}
@@ -182,42 +200,42 @@ 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 16-bit signed integer that defines the horizontal extent of
- * the window in logical units.
- */
- private int width;
+ protected final Dimension2D size = new Dimension2DDouble();
@Override
- public HwmfRecordType getRecordType() {
+ public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.setWindowExt;
}
@Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
- height = leis.readShort();
- width = leis.readShort();
+ // A signed integer that defines the vertical extent of the window in logical units.
+ int height = leis.readShort();
+ // A signed integer that defines the horizontal extent of the window in logical units.
+ int width = leis.readShort();
+ size.setSize(width, height);
return 2*LittleEndianConsts.SHORT_SIZE;
}
@Override
public void draw(HwmfGraphics ctx) {
- ctx.getProperties().setWindowExt(width, height);
- ctx.updateWindowMapMode();
+ final HwmfDrawProperties prop = ctx.getProperties();
+ Rectangle2D old = prop.getWindow();
+ double oldW = (old == null ? 0 : old.getWidth());
+ double oldH = (old == null ? 0 : old.getHeight());
+ if (oldW != size.getWidth() || oldH != size.getHeight()) {
+ prop.setWindowExt(size.getWidth(), size.getHeight());
+ ctx.updateWindowMapMode();
+ }
}
- public int getHeight() {
- return height;
+ public Dimension2D getSize() {
+ return size;
}
- public int getWidth() {
- return width;
+ @Override
+ public String toString() {
+ return dimToString(size);
}
}
@@ -227,33 +245,31 @@ public class HwmfWindowing {
*/
public static class WmfOffsetWindowOrg implements HwmfRecord {
- /**
- * A 16-bit signed integer that defines the vertical offset, in device units.
- */
- private int yOffset;
-
- /**
- * A 16-bit signed integer that defines the horizontal offset, in device units.
- */
- private int xOffset;
+ protected final Point2D offset = new Point2D.Double();
@Override
- public HwmfRecordType getRecordType() {
+ public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.offsetWindowOrg;
}
@Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
- yOffset = leis.readShort();
- xOffset = leis.readShort();
- return 2*LittleEndianConsts.SHORT_SIZE;
+ return readPointS(leis, offset);
}
@Override
public void draw(HwmfGraphics ctx) {
- Rectangle2D window = ctx.getProperties().getWindow();
- ctx.getProperties().setWindowOrg(window.getX()+xOffset, window.getY()+yOffset);
- ctx.updateWindowMapMode();
+ final HwmfDrawProperties prop = ctx.getProperties();
+ Rectangle2D old = prop.getWindow();
+ if (offset.getX() != 0 || offset.getY() != 0) {
+ prop.setWindowOrg(old.getX() + offset.getX(), old.getY() + offset.getY());
+ ctx.updateWindowMapMode();
+ }
+ }
+
+ @Override
+ public String toString() {
+ return pointToString(offset);
}
}
@@ -263,51 +279,48 @@ public class HwmfWindowing {
*/
public static class WmfScaleWindowExt implements HwmfRecord {
- /**
- * A 16-bit 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;
-
- /**
- * A 16-bit signed integer that defines the amount by which to multiply the
- * current y-extent.
- */
- private int yNum;
-
- /**
- * A 16-bit 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;
-
- /**
- * A 16-bit signed integer that defines the amount by which to multiply the
- * current x-extent.
- */
- private int xNum;
+ protected final Dimension2D scale = new Dimension2DDouble();
@Override
- public HwmfRecordType getRecordType() {
+ public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.scaleWindowExt;
}
@Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
- yDenom = leis.readShort();
- yNum = leis.readShort();
- xDenom = leis.readShort();
- xNum = leis.readShort();
+ // 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.
+ double yDenom = leis.readShort();
+ // A signed integer that defines the amount by which to multiply the
+ // current y-extent.
+ double yNum = leis.readShort();
+ // 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.
+ double xDenom = leis.readShort();
+ // A signed integer that defines the amount by which to multiply the
+ // current x-extent.
+ double xNum = leis.readShort();
+
+ scale.setSize(xNum / xDenom, yNum / yDenom);
+
return 4*LittleEndianConsts.SHORT_SIZE;
}
@Override
public void draw(HwmfGraphics ctx) {
- Rectangle2D window = ctx.getProperties().getWindow();
- double width = window.getWidth() * xNum / xDenom;
- double height = window.getHeight() * yNum / yDenom;
- ctx.getProperties().setWindowExt(width, height);
- ctx.updateWindowMapMode();
+ final HwmfDrawProperties prop = ctx.getProperties();
+ Rectangle2D old = prop.getWindow();
+ if (scale.getWidth() != 1.0 || scale.getHeight() != 1.0) {
+ double width = old.getWidth() * scale.getWidth();
+ double height = old.getHeight() * scale.getHeight();
+ ctx.getProperties().setWindowExt(width, height);
+ ctx.updateWindowMapMode();
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "{ scaleX: "+scale.getWidth()+", scaleY: "+scale.getHeight()+" }";
}
}
@@ -319,53 +332,49 @@ public class HwmfWindowing {
*/
public static class WmfScaleViewportExt implements HwmfRecord {
- /**
- * A 16-bit 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;
-
- /**
- * A 16-bit signed integer that defines the amount by which to multiply the
- * current y-extent.
- */
- private int yNum;
-
- /**
- * A 16-bit 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;
-
- /**
- * A 16-bit signed integer that defines the amount by which to multiply the
- * current x-extent.
- */
- private int xNum;
+ protected final Dimension2D scale = new Dimension2DDouble();
@Override
- public HwmfRecordType getRecordType() {
+ public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.scaleViewportExt;
}
@Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
- yDenom = leis.readShort();
- yNum = leis.readShort();
- xDenom = leis.readShort();
- xNum = leis.readShort();
+ // 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.
+ double yDenom = leis.readShort();
+ // A signed integer that defines the amount by which to multiply the
+ // current y-extent.
+ double yNum = leis.readShort();
+ // 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.
+ double xDenom = leis.readShort();
+ // A signed integer that defines the amount by which to multiply the
+ // current x-extent.
+ double xNum = leis.readShort();
+
+ scale.setSize(xNum / xDenom, yNum / yDenom);
+
return 4*LittleEndianConsts.SHORT_SIZE;
}
@Override
public void draw(HwmfGraphics ctx) {
- Rectangle2D viewport = ctx.getProperties().getViewport();
- if (viewport == null) {
- viewport = ctx.getProperties().getWindow();
+ final HwmfDrawProperties prop = ctx.getProperties();
+ final Rectangle2D old = prop.getViewport() == null ? prop.getWindow() : prop.getViewport();
+
+ if (scale.getWidth() != 1.0 || scale.getHeight() != 1.0) {
+ double width = old.getWidth() * scale.getWidth();
+ double height = old.getHeight() * scale.getHeight();
+ prop.setViewportExt(width, height);
+ ctx.updateWindowMapMode();
}
- double width = viewport.getWidth() * xNum / xDenom;
- double height = viewport.getHeight() * yNum / yDenom;
- ctx.getProperties().setViewportExt(width, height);
+ }
+
+ @Override
+ public String toString() {
+ return "{ scaleX: "+scale.getWidth()+", scaleY: "+scale.getHeight()+" }";
}
}
@@ -375,26 +384,16 @@ 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.
- */
- private int yOffset;
-
- /**
- * A 16-bit signed integer that defines the number of logical units to move left or right.
- */
- private int xOffset;
+ protected final Point2D offset = new Point2D.Double();
@Override
- public HwmfRecordType getRecordType() {
+ public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.offsetClipRgn;
}
@Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
- yOffset = leis.readShort();
- xOffset = leis.readShort();
- return 2*LittleEndianConsts.SHORT_SIZE;
+ return readPointS(leis, offset);
}
@Override
@@ -405,6 +404,11 @@ public class HwmfWindowing {
@Override
public void applyObject(HwmfGraphics ctx) {
}
+
+ @Override
+ public String toString() {
+ return pointToString(offset);
+ }
}
/**
@@ -413,42 +417,17 @@ 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();
- return 4*LittleEndianConsts.SHORT_SIZE;
+ return readBounds(leis, bounds);
}
@Override
@@ -458,6 +437,12 @@ public class HwmfWindowing {
@Override
public void applyObject(HwmfGraphics ctx) {
+ ctx.setClip(normalizeBounds(bounds), HwmfRegionMode.RGN_DIFF, false);
+ }
+
+ @Override
+ public String toString() {
+ return boundsToString(bounds);
}
}
@@ -468,42 +453,17 @@ 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();
- return 4*LittleEndianConsts.SHORT_SIZE;
+ return readBounds(leis, bounds);
}
@Override
@@ -513,6 +473,12 @@ public class HwmfWindowing {
@Override
public void applyObject(HwmfGraphics ctx) {
+ ctx.setClip(bounds, HwmfRegionMode.RGN_AND, true);
+ }
+
+ @Override
+ public String toString() {
+ return boundsToString(bounds);
}
}
@@ -528,7 +494,7 @@ public class HwmfWindowing {
private int region;
@Override
- public HwmfRecordType getRecordType() {
+ public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.selectClipRegion;
}
@@ -620,29 +586,7 @@ public class HwmfWindowing {
*/
private int maxScan;
- /**
- * 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;
+ private Rectangle2D bounds = new Rectangle2D.Double();
/**
* An array of Scan objects that define the scanlines in the region.
@@ -650,7 +594,7 @@ public class HwmfWindowing {
private WmfScanObject scanObjects[];
@Override
- public HwmfRecordType getRecordType() {
+ public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.createRegion;
}
@@ -662,10 +606,19 @@ public class HwmfWindowing {
regionSize = leis.readShort();
scanCount = leis.readShort();
maxScan = leis.readShort();
- left = leis.readShort();
- top = leis.readShort();
- right = leis.readShort();
- bottom = leis.readShort();
+ // A 16-bit signed integer that defines the x-coordinate, in logical units, of the
+ // upper-left corner of the rectangle.
+ double left = leis.readShort();
+ // A 16-bit signed integer that defines the y-coordinate, in logical units, of the
+ // upper-left corner of the rectangle.
+ double top = leis.readShort();
+ // A 16-bit signed integer that defines the x-coordinate, in logical units, of the
+ // lower-right corner of the rectangle.
+ double right = leis.readShort();
+ // A 16-bit signed integer that defines the y-coordinate, in logical units, of the
+ // lower-right corner of the rectangle.
+ double bottom = leis.readShort();
+ bounds.setRect(left, top, right-left, bottom-top);
int size = 9*LittleEndianConsts.SHORT_SIZE+LittleEndianConsts.INT_SIZE;
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..bf91dc864b 100644
--- a/src/scratchpad/src/org/apache/poi/hwmf/usermodel/HwmfPicture.java
+++ b/src/scratchpad/src/org/apache/poi/hwmf/usermodel/HwmfPicture.java
@@ -43,6 +43,9 @@ import org.apache.poi.util.RecordFormatException;
import org.apache.poi.util.Units;
public class HwmfPicture {
+ /** Max. record length - processing longer records will throw an exception */
+ public static final int MAX_RECORD_LENGTH = 50_000_000;
+
private static final POILogger logger = POILogFactory.getLogger(HwmfPicture.class);
final List<HwmfRecord> records = new ArrayList<>();
@@ -51,8 +54,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 +84,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);
@@ -162,7 +159,7 @@ public class HwmfPicture {
if (wOrg == null || wExt == null) {
throw new RuntimeException("invalid wmf file - window records are incomplete.");
}
- return new Rectangle2D.Double(wOrg.getX(), wOrg.getY(), wExt.getWidth(), wExt.getHeight());
+ return new Rectangle2D.Double(wOrg.getX(), wOrg.getY(), wExt.getSize().getWidth(), wExt.getSize().getHeight());
}
}
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<String> 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..b42bac7d5d 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,10 +39,10 @@ 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<HemfPlusRecord> records = emfPlus.getRecords();
assertEquals(1, records.size());
- assertEquals(HemfPlusRecordType.header, records.get(0).getRecordType());
+ assertEquals(HemfPlusRecordType.header, records.get(0).getEmfPlusRecordType());
HemfPlusHeader header = (HemfPlusHeader)records.get(0);
assertEquals(240, header.getLogicalDpiX());
@@ -67,29 +67,25 @@ public class HemfPlusExtractorTest {
assertEquals(expected.size(), records.size());
for (int i = 0; i < expected.size(); i++) {
- assertEquals(expected.get(i), records.get(i).getRecordType());
+ assertEquals(expected.get(i), records.get(i).getEmfPlusRecordType());
}
}
- 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..8ff4742be6
--- /dev/null
+++ b/src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java
@@ -0,0 +1,353 @@
+/* ====================================================================
+ 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.BufferedWriter;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Stream;
+
+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 static final POIDataSamples ss_samples = POIDataSamples.getSpreadSheetInstance();
+ private static final POIDataSamples sl_samples = POIDataSamples.getSlideShowInstance();
+
+ /*
+ @Test
+ @Ignore("Only for manual tests - need to add org.tukaani:xz:1.8 for this to work")
+ public void paint() throws IOException {
+ byte buf[] = new byte[50_000_000];
+
+ // good test samples to validate rendering:
+ // emfs/commoncrawl2/NB/NBWN2YH5VFCLZRFDQU7PB7IDD4UKY7DN_2.emf
+ // emfs/govdocs1/777/777525.ppt_0.emf
+ // emfs/govdocs1/844/844795.ppt_2.emf
+ // emfs/commoncrawl2/TO/TOYZSTNUSW5OFCFUQ6T5FBLIDLCRF3NH_0.emf
+
+ final boolean writeLog = true;
+ final boolean dumpRecords = false;
+ final boolean savePng = true;
+
+ Set<String> passed = new HashSet<>();
+
+ try (BufferedWriter sucWrite = parseEmfLog(passed, "emf-success.txt");
+ BufferedWriter parseError = parseEmfLog(passed, "emf-parse.txt");
+ BufferedWriter renderError = parseEmfLog(passed, "emf-render.txt");
+ SevenZFile sevenZFile = new SevenZFile(new File("tmp/render_emf.7z"))) {
+ for (int idx=0;;idx++) {
+ SevenZArchiveEntry entry = sevenZFile.getNextEntry();
+ if (entry == null) break;
+ final String etName = entry.getName();
+
+ if (entry.isDirectory() || !etName.endsWith(".emf") || passed.contains(etName)) continue;
+
+ System.out.println(etName);
+
+ int size = sevenZFile.read(buf);
+
+ HemfPicture emf = null;
+ try {
+ emf = new HemfPicture(new ByteArrayInputStream(buf, 0, size));
+
+ // initialize parsing
+ emf.getRecords();
+ } catch (Exception|AssertionError e) {
+ if (writeLog) {
+ parseError.write(etName+" "+hashException(e)+"\n");
+ parseError.flush();
+ }
+ System.out.println("parse error");
+ // continue with the read records up to the error anyway
+ if (emf.getRecords().isEmpty()) {
+ continue;
+ }
+ }
+
+ if (dumpRecords) {
+ dumpRecords(emf);
+ }
+
+ Graphics2D g = null;
+ try {
+ Dimension2D dim = emf.getSize();
+ double width = Units.pointsToPixel(dim.getWidth());
+ // keep aspect ratio for height
+ double height = Units.pointsToPixel(dim.getHeight());
+ double max = Math.max(width, height);
+ if (max > 1500.) {
+ width *= 1500. / max;
+ height *= 1500. / max;
+ }
+ width = Math.ceil(width);
+ height = Math.ceil(height);
+
+ BufferedImage bufImg = new BufferedImage((int)width, (int)height, BufferedImage.TYPE_INT_ARGB);
+ g = bufImg.createGraphics();
+ g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+ g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
+ g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
+ g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
+
+ g.setComposite(AlphaComposite.Clear);
+ g.fillRect(0, 0, (int)width, (int)height);
+ g.setComposite(AlphaComposite.Src);
+
+ emf.draw(g, new Rectangle2D.Double(0, 0, width, height));
+
+ final File pngName = new File("build/tmp", etName.replaceFirst(".+/", "").replace(".emf", ".png"));
+ if (savePng) {
+ ImageIO.write(bufImg, "PNG", pngName);
+ }
+ } catch (Exception|AssertionError e) {
+ System.out.println("render error");
+ if (writeLog) {
+ // dumpRecords(emf.getRecords());
+ renderError.write(etName+" "+hashException(e)+"\n");
+ renderError.flush();
+ }
+ continue;
+ } finally {
+ if (g != null) g.dispose();
+ }
+
+ if (writeLog) {
+ sucWrite.write(etName + "\n");
+ sucWrite.flush();
+ }
+ }
+ }
+ } */
+
+ private static int hashException(Throwable e) {
+ StringBuilder sb = new StringBuilder();
+ for (StackTraceElement se : e.getStackTrace()) {
+ sb.append(se.getClassName()+":"+se.getLineNumber());
+ }
+ return sb.toString().hashCode();
+ }
+
+ private static void dumpRecords(HemfPicture emf) throws IOException {
+ FileWriter fw = new FileWriter("record-list.txt");
+ int i = 0;
+ for (HemfRecord r : emf.getRecords()) {
+ if (r.getEmfRecordType() != HemfRecordType.comment) {
+ fw.write(i + " " + r.getEmfRecordType() + " " + r.toString() + "\n");
+ }
+ i++;
+ }
+ fw.close();
+ }
+
+ private static BufferedWriter parseEmfLog(Set<String> passed, String logFile) throws IOException {
+ Path log = Paths.get(logFile);
+
+ StandardOpenOption soo;
+ if (Files.exists(log)) {
+ soo = StandardOpenOption.APPEND;
+ try (Stream<String> stream = Files.lines(log)) {
+ stream.forEach((s) -> passed.add(s.split("\\s")[0]));
+ }
+ } else {
+ soo = StandardOpenOption.CREATE;
+ }
+
+ return Files.newBufferedWriter(log, StandardCharsets.UTF_8, soo);
+ }
+
+ @Test
+ public void testBasicWindows() throws Exception {
+ try (InputStream is = ss_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.getMicroDimension().getWidth());
+ assertEquals(194000, header.getMicroDimension().getHeight());
+
+ List<HemfRecord> records = pic.getRecords();
+
+ assertEquals(31, records.size());
+ }
+ }
+
+ @Test
+ public void testBasicMac() throws Exception {
+ try (InputStream is = ss_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 = ss_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.EmfExtTextOutW extTextOutW = (HemfText.EmfExtTextOutW) record;
+ Point2D reference = extTextOutW.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 = ss_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<String> 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.EmfExtTextOutW extTextOutW = (HemfText.EmfExtTextOutW) record;
+ Point2D reference = extTextOutW.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 = ss_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 = ss_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/hslf/usermodel/TestPicture.java b/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestPicture.java
index 72eee287c2..d071f2dbdc 100644
--- a/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestPicture.java
+++ b/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestPicture.java
@@ -210,7 +210,6 @@ public final class TestPicture {
} else {
BufferedImage img = new BufferedImage(pg.width, pg.height, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = img.createGraphics();
- DrawFactory.getInstance(graphics).fixFonts(graphics);
slide.draw(graphics);
graphics.setColor(Color.BLACK);
graphics.setStroke(new BasicStroke(1));
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");
}