Browse Source

#60656 - EMF image support in slideshows

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1849040 13f79535-47bb-0310-9956-ffa450edef68
pull/137/head
Andreas Beeker 5 years ago
parent
commit
a5b67054b0
94 changed files with 8532 additions and 3368 deletions
  1. 1
    1
      build.gradle
  2. 1
    1
      build.xml
  3. 1
    1
      sonar/examples/pom.xml
  4. 1
    1
      sonar/excelant/pom.xml
  5. 1
    1
      sonar/main/pom.xml
  6. 1
    1
      sonar/ooxml-schema-encryption/pom.xml
  7. 1
    1
      sonar/ooxml-schema-security/pom.xml
  8. 1
    1
      sonar/ooxml-schema/pom.xml
  9. 1
    1
      sonar/ooxml/pom.xml
  10. 1
    1
      sonar/pom.xml
  11. 1
    1
      sonar/scratchpad/pom.xml
  12. 0
    1
      src/integrationtest/org/apache/poi/stress/SlideShowHandler.java
  13. 4
    8
      src/java/org/apache/poi/hssf/dev/BiffViewer.java
  14. 14
    18
      src/java/org/apache/poi/hssf/record/RecordInputStream.java
  15. 15
    1
      src/java/org/apache/poi/sl/draw/BitmapImageRenderer.java
  16. 4
    34
      src/java/org/apache/poi/sl/draw/DrawFactory.java
  17. 35
    16
      src/java/org/apache/poi/sl/draw/DrawFontManagerDefault.java
  18. 41
    20
      src/java/org/apache/poi/sl/draw/DrawPictureShape.java
  19. 0
    8
      src/java/org/apache/poi/sl/draw/DrawTextParagraph.java
  20. 1
    4
      src/java/org/apache/poi/sl/draw/DrawTextShape.java
  21. 7
    0
      src/java/org/apache/poi/sl/draw/ImageRenderer.java
  22. 76
    0
      src/java/org/apache/poi/util/Dimension2DDouble.java
  23. 27
    4
      src/java/org/apache/poi/util/IOUtils.java
  24. 54
    5
      src/java/org/apache/poi/util/LittleEndianInputStream.java
  25. 3
    3
      src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureConfig.java
  26. 8
    53
      src/ooxml/java/org/apache/poi/xdgf/geom/Dimension2dDouble.java
  27. 6
    0
      src/ooxml/java/org/apache/poi/xslf/draw/SVGImageRenderer.java
  28. 1
    1
      src/ooxml/java/org/apache/poi/xslf/usermodel/XMLSlideShow.java
  29. 1
    1
      src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFChart.java
  30. 1
    1
      src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFDrawing.java
  31. 2
    2
      src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSheet.java
  32. 0
    1
      src/ooxml/java/org/apache/poi/xslf/util/PPTX2PNG.java
  33. 0
    2
      src/ooxml/testcases/org/apache/poi/sl/TestFonts.java
  34. 0
    1
      src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFSimpleShape.java
  35. 69
    0
      src/scratchpad/src/org/apache/poi/hemf/draw/HemfDrawProperties.java
  36. 259
    0
      src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java
  37. 126
    0
      src/scratchpad/src/org/apache/poi/hemf/draw/HemfImageRenderer.java
  38. 0
    115
      src/scratchpad/src/org/apache/poi/hemf/extractor/HemfExtractor.java
  39. 0
    44
      src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/HemfPlusRecord.java
  40. 0
    97
      src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/HemfPlusRecordType.java
  41. 0
    31
      src/scratchpad/src/org/apache/poi/hemf/record/HemfComment.java
  42. 0
    111
      src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentEMFPlus.java
  43. 0
    31
      src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentEMFSpool.java
  44. 0
    177
      src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentPublic.java
  45. 0
    154
      src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentRecord.java
  46. 0
    201
      src/scratchpad/src/org/apache/poi/hemf/record/HemfHeader.java
  47. 0
    159
      src/scratchpad/src/org/apache/poi/hemf/record/HemfRecordType.java
  48. 0
    262
      src/scratchpad/src/org/apache/poi/hemf/record/HemfText.java
  49. 465
    0
      src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java
  50. 1153
    0
      src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java
  51. 719
    0
      src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java
  52. 496
    0
      src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFont.java
  53. 207
    0
      src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfHeader.java
  54. 828
    0
      src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java
  55. 154
    0
      src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfPalette.java
  56. 19
    13
      src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfPenStyle.java
  57. 61
    0
      src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecord.java
  58. 91
    0
      src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordIterator.java
  59. 165
    0
      src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java
  60. 332
    0
      src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java
  61. 220
    0
      src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfWindowing.java
  62. 6
    9
      src/scratchpad/src/org/apache/poi/hemf/record/emf/UnimplementedHemfRecord.java
  63. 16
    11
      src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusHeader.java
  64. 13
    6
      src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRecord.java
  65. 98
    0
      src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRecordIterator.java
  66. 100
    0
      src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRecordType.java
  67. 14
    8
      src/scratchpad/src/org/apache/poi/hemf/record/emfplus/UnimplementedHemfPlusRecord.java
  68. 161
    0
      src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java
  69. 86
    18
      src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java
  70. 353
    97
      src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java
  71. 10
    5
      src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfImageRenderer.java
  72. 120
    23
      src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBitmapDib.java
  73. 1
    1
      src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBrushStyle.java
  74. 10
    0
      src/scratchpad/src/org/apache/poi/hwmf/record/HwmfColorRef.java
  75. 340
    261
      src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java
  76. 1
    1
      src/scratchpad/src/org/apache/poi/hwmf/record/HwmfEscape.java
  77. 177
    427
      src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java
  78. 108
    64
      src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFont.java
  79. 15
    2
      src/scratchpad/src/org/apache/poi/hwmf/record/HwmfHatchStyle.java
  80. 1
    1
      src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMapMode.java
  81. 164
    63
      src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java
  82. 19
    14
      src/scratchpad/src/org/apache/poi/hwmf/record/HwmfPalette.java
  83. 36
    7
      src/scratchpad/src/org/apache/poi/hwmf/record/HwmfPenStyle.java
  84. 1
    1
      src/scratchpad/src/org/apache/poi/hwmf/record/HwmfRecord.java
  85. 74
    72
      src/scratchpad/src/org/apache/poi/hwmf/record/HwmfRecordType.java
  86. 59
    0
      src/scratchpad/src/org/apache/poi/hwmf/record/HwmfRegionMode.java
  87. 272
    185
      src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java
  88. 221
    268
      src/scratchpad/src/org/apache/poi/hwmf/record/HwmfWindowing.java
  89. 8
    11
      src/scratchpad/src/org/apache/poi/hwmf/usermodel/HwmfPicture.java
  90. 0
    198
      src/scratchpad/testcases/org/apache/poi/hemf/extractor/HemfExtractorTest.java
  91. 16
    20
      src/scratchpad/testcases/org/apache/poi/hemf/hemfplus/extractor/HemfPlusExtractorTest.java
  92. 353
    0
      src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java
  93. 0
    1
      src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestPicture.java
  94. 4
    4
      src/scratchpad/testcases/org/apache/poi/hwmf/TestHwmfParsing.java

+ 1
- 1
build.gradle View File

@@ -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'
}

+ 1
- 1
build.xml View File

@@ -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"/>

+ 1
- 1
sonar/examples/pom.xml View File

@@ -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>

+ 1
- 1
sonar/excelant/pom.xml View File

@@ -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>

+ 1
- 1
sonar/main/pom.xml View File

@@ -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>

+ 1
- 1
sonar/ooxml-schema-encryption/pom.xml View File

@@ -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>

+ 1
- 1
sonar/ooxml-schema-security/pom.xml View File

@@ -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>

+ 1
- 1
sonar/ooxml-schema/pom.xml View File

@@ -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>

+ 1
- 1
sonar/ooxml/pom.xml View File

@@ -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>

+ 1
- 1
sonar/pom.xml View File

@@ -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>

+ 1
- 1
sonar/scratchpad/pom.xml View File

@@ -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>

+ 0
- 1
src/integrationtest/org/apache/poi/stress/SlideShowHandler.java View File

@@ -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);

+ 4
- 8
src/java/org/apache/poi/hssf/dev/BiffViewer.java View File

@@ -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;

+ 14
- 18
src/java/org/apache/poi/hssf/record/RecordInputStream.java View File

@@ -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();

+ 15
- 1
src/java/org/apache/poi/sl/draw/BitmapImageRenderer.java View File

@@ -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,10 +49,23 @@ 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);

+ 4
- 34
src/java/org/apache/poi/sl/draw/DrawFactory.java View File

@@ -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);
}
@@ -213,35 +212,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
*

+ 35
- 16
src/java/org/apache/poi/sl/draw/DrawFontManagerDefault.java View File

@@ -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

+ 41
- 20
src/java/org/apache/poi/sl/draw/DrawPictureShape.java View File

@@ -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

+ 0
- 8
src/java/org/apache/poi/sl/draw/DrawTextParagraph.java View File

@@ -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) {

+ 1
- 4
src/java/org/apache/poi/sl/draw/DrawTextShape.java View File

@@ -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;

+ 7
- 0
src/java/org/apache/poi/sl/draw/ImageRenderer.java View File

@@ -75,6 +75,13 @@ import java.io.InputStream;
* </pre>
*/
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
*

+ 76
- 0
src/java/org/apache/poi/util/Dimension2DDouble.java View File

@@ -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 + "]";
}
}

+ 27
- 4
src/java/org/apache/poi/util/IOUtils.java View File

@@ -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;
}


+ 54
- 5
src/java/org/apache/poi/util/LittleEndianInputStream.java View File

@@ -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);
}
}

+ 3
- 3
src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureConfig.java View File

@@ -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;

+ 8
- 53
src/ooxml/java/org/apache/poi/xdgf/geom/Dimension2dDouble.java View File

@@ -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 {
}

+ 6
- 0
src/ooxml/java/org/apache/poi/xslf/draw/SVGImageRenderer.java View File

@@ -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);
}
}

+ 1
- 1
src/ooxml/java/org/apache/poi/xslf/usermodel/XMLSlideShow.java View File

@@ -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);

+ 1
- 1
src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFChart.java View File

@@ -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();

+ 1
- 1
src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFDrawing.java View File

@@ -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();

+ 2
- 2
src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSheet.java View File

@@ -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);

+ 0
- 1
src/ooxml/java/org/apache/poi/xslf/util/PPTX2PNG.java View File

@@ -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);

+ 0
- 2
src/ooxml/testcases/org/apache/poi/sl/TestFonts.java View File

@@ -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();


+ 0
- 1
src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFSimpleShape.java View File

@@ -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);

+ 69
- 0
src/scratchpad/src/org/apache/poi/hemf/draw/HemfDrawProperties.java View File

@@ -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;
}
}

+ 259
- 0
src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java View File

@@ -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;
}
}
}

+ 126
- 0
src/scratchpad/src/org/apache/poi/hemf/draw/HemfImageRenderer.java View File

@@ -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;
}
}

}

+ 0
- 115
src/scratchpad/src/org/apache/poi/hemf/extractor/HemfExtractor.java View File

@@ -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");
}

}
}

+ 0
- 44
src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/HemfPlusRecord.java View File

@@ -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;


}

+ 0
- 97
src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/HemfPlusRecordType.java View File

@@ -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;
}
}

+ 0
- 31
src/scratchpad/src/org/apache/poi/hemf/record/HemfComment.java View File

@@ -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);
}
}

+ 0
- 111
src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentEMFPlus.java View File

@@ -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;

}
}
}

+ 0
- 31
src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentEMFSpool.java View File

@@ -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);
}
}

+ 0
- 177
src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentPublic.java View File

@@ -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;
}
}
}

+ 0
- 154
src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentRecord.java View File

@@ -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);
}
}
}

+ 0
- 201
src/scratchpad/src/org/apache/poi/hemf/record/HemfHeader.java View File

@@ -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;
}
}

+ 0
- 159
src/scratchpad/src/org/apache/poi/hemf/record/HemfRecordType.java View File

@@ -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;
}
}

+ 0
- 262
src/scratchpad/src/org/apache/poi/hemf/record/HemfText.java View File

@@ -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);
}
}


}

+ 465
- 0
src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java View File

@@ -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");
}
}
}

+ 1153
- 0
src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java
File diff suppressed because it is too large
View File


+ 719
- 0
src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java View File

@@ -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;
}
}

+ 496
- 0
src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFont.java View File

@@ -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;
}
}

+ 207
- 0
src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfHeader.java View File

@@ -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;
}
}

+ 828
- 0
src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java View File

@@ -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 +
"}";
}
}
}

+ 154
- 0
src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfPalette.java View File

@@ -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;
}
}
}

src/scratchpad/src/org/apache/poi/hemf/record/AbstractHemfComment.java → src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfPenStyle.java View File

@@ -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();
}
}

+ 61
- 0
src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecord.java View File

@@ -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) {}
}

+ 91
- 0
src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordIterator.java View File

@@ -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");
}

}

+ 165
- 0
src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java View File

@@ -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;
}
}

+ 332
- 0
src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java View File

@@ -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 {

}

}

+ 220
- 0
src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfWindowing.java View File

@@ -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+"' }";
}
}

}

src/scratchpad/src/org/apache/poi/hemf/record/UnimplementedHemfRecord.java → src/scratchpad/src/org/apache/poi/hemf/record/emf/UnimplementedHemfRecord.java View File

@@ -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");

src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/HemfPlusHeader.java → src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusHeader.java View File

@@ -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 +
'}';
}
}
}

src/scratchpad/src/org/apache/poi/hemf/record/HemfRecord.java → src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRecord.java View File

@@ -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;

}

+ 98
- 0
src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRecordIterator.java View File

@@ -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");
}

}

+ 100
- 0
src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRecordType.java View File

@@ -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;
}
}

src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/UnimplementedHemfPlusRecord.java → src/scratchpad/src/org/apache/poi/hemf/record/emfplus/UnimplementedHemfPlusRecord.java View File

@@ -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;
}
}
}

+ 161
- 0
src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java View File

@@ -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);
}
}

}

+ 86
- 18
src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java View File

@@ -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;
}
}

+ 353
- 97
src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfGraphics.java View File

@@ -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);
}
}
}

src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfSLImageRenderer.java → src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfImageRenderer.java View File

@@ -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));

+ 120
- 23
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBitmapDib.java View File

@@ -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;
}
}

+ 1
- 1
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBrushStyle.java View File

@@ -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;
}

+ 10
- 0
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfColorRef.java View File

@@ -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);
}
}

+ 340
- 261
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java View File

@@ -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())
);
}

}

+ 1
- 1
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfEscape.java View File

@@ -185,7 +185,7 @@ public class HwmfEscape implements HwmfRecord {
private byte escapeData[];
@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.escape;
}

+ 177
- 427
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java View File

@@ -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;
}

}

+ 108
- 64
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFont.java View File

@@ -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;
}
}

+ 15
- 2
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfHatchStyle.java View File

@@ -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;
}

+ 1
- 1
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMapMode.java View File

@@ -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;
}

+ 164
- 63
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java View File

@@ -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+"' }";
}
}
}

+ 19
- 14
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfPalette.java View File

@@ -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;
}


+ 36
- 7
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfPenStyle.java View File

@@ -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 ":"")+
"}";
}
}

+ 1
- 1
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfRecord.java View File

@@ -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

+ 74
- 72
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfRecordType.java View File

@@ -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) {

+ 59
- 0
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfRegionMode.java View File

@@ -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;
}
}

+ 272
- 185
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java View File

@@ -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,40 +205,47 @@ public class HwmfText {
return ret;
}
}
/**
* The META_EXTTEXTOUT record outputs text by using the font, background color, and text color that
* are defined in the playback device context. Optionally, dimensions can be provided for clipping,
* opaquing, or both.
*/
public static class WmfExtTextOut implements HwmfRecord {

public static class WmfExtTextOutOptions {
/**
* Indicates that the background color that is defined in the playback device context
* Indicates that the background color that is defined in the playback device context
* SHOULD be used to fill the rectangle.
*/
*/
private static final BitField ETO_OPAQUE = BitFieldFactory.getInstance(0x0002);
/**
* Indicates that the text SHOULD be clipped to the rectangle.
*/
private static final BitField ETO_CLIPPED = BitFieldFactory.getInstance(0x0004);

/**
* Indicates that the string to be output SHOULD NOT require further processing
* with respect to the placement of the characters, and an array of character
* placement values SHOULD be provided. This character placement process is
* Indicates that the string to be output SHOULD NOT require further processing
* with respect to the placement of the characters, and an array of character
* placement values SHOULD be provided. This character placement process is
* useful for fonts in which diacritical characters affect character spacing.
*/
private static final BitField ETO_GLYPH_INDEX = BitFieldFactory.getInstance(0x0010);

/**
* Indicates that the text MUST be laid out in right-to-left reading order, instead of
* the default left-to-right order. This SHOULD be applied only when the font that is
* Indicates that the text MUST be laid out in right-to-left reading order, instead of
* the default left-to-right order. This SHOULD be applied only when the font that is
* defined in the playback device context is either Hebrew or Arabic.
*/
private static final BitField ETO_RTLREADING = BitFieldFactory.getInstance(0x0080);

/**
* This bit indicates that the record does not specify a bounding rectangle for the
* text output.
*/
private static final BitField ETO_NO_RECT = BitFieldFactory.getInstance(0x0100);

/**
* This bit indicates that the codes for characters in an output text string are 8 bits,
* derived from the low bytes of 16-bit Unicode UTF16-LE character codes, in which
* the high byte is assumed to be 0.
*/
private static final BitField ETO_SMALL_CHARS = BitFieldFactory.getInstance(0x0200);

/**
* Indicates that to display numbers, digits appropriate to the locale SHOULD be used.
*/
@@ -240,32 +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+" } ";
}
}
}

+ 221
- 268
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfWindowing.java View File

@@ -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;


+ 8
- 11
src/scratchpad/src/org/apache/poi/hwmf/usermodel/HwmfPicture.java View File

@@ -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());
}
}

+ 0
- 198
src/scratchpad/testcases/org/apache/poi/hemf/extractor/HemfExtractorTest.java View File

@@ -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
*/
}

+ 16
- 20
src/scratchpad/testcases/org/apache/poi/hemf/hemfplus/extractor/HemfPlusExtractorTest.java View File

@@ -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;
}

+ 353
- 0
src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java View File

@@ -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
*/
}

+ 0
- 1
src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestPicture.java View File

@@ -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));

+ 4
- 4
src/scratchpad/testcases/org/apache/poi/hwmf/TestHwmfParsing.java View File

@@ -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");
}

Loading…
Cancel
Save