Browse Source

#60656 - Support export file that contains emf and render it correctly

git-svn-id: https://svn.apache.org/repos/asf/poi/branches/hemf@1840956 13f79535-47bb-0310-9956-ffa450edef68
pull/131/head
Andreas Beeker 5 years ago
parent
commit
6e8ccccd02
52 changed files with 5098 additions and 2528 deletions
  1. 7
    7
      src/java/org/apache/poi/util/Dimension2DDouble.java
  2. 4
    4
      src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFPage.java
  3. 2
    2
      src/ooxml/java/org/apache/poi/xdgf/util/VsdxToPng.java
  4. 8
    9
      src/scratchpad/src/org/apache/poi/hemf/draw/HemfDrawProperties.java
  5. 7
    9
      src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java
  6. 0
    115
      src/scratchpad/src/org/apache/poi/hemf/extractor/HemfExtractor.java
  7. 0
    97
      src/scratchpad/src/org/apache/poi/hemf/hemfplus/record/HemfPlusRecordType.java
  8. 0
    39
      src/scratchpad/src/org/apache/poi/hemf/record/AbstractHemfComment.java
  9. 0
    111
      src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentEMFPlus.java
  10. 0
    177
      src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentPublic.java
  11. 0
    154
      src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentRecord.java
  12. 0
    159
      src/scratchpad/src/org/apache/poi/hemf/record/HemfRecordType.java
  13. 0
    262
      src/scratchpad/src/org/apache/poi/hemf/record/HemfText.java
  14. 444
    0
      src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfComment.java
  15. 783
    0
      src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfDraw.java
  16. 620
    0
      src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFill.java
  17. 464
    0
      src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfFont.java
  18. 49
    55
      src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfHeader.java
  19. 447
    0
      src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfMisc.java
  20. 138
    0
      src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfPalette.java
  21. 10
    5
      src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecord.java
  22. 82
    0
      src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordIterator.java
  23. 165
    0
      src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecordType.java
  24. 317
    0
      src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfText.java
  25. 200
    0
      src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfWindowing.java
  26. 3
    3
      src/scratchpad/src/org/apache/poi/hemf/record/emf/UnimplementedHemfRecord.java
  27. 15
    10
      src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusHeader.java
  28. 13
    9
      src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRecord.java
  29. 98
    0
      src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRecordIterator.java
  30. 100
    0
      src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRecordType.java
  31. 11
    5
      src/scratchpad/src/org/apache/poi/hemf/record/emfplus/UnimplementedHemfPlusRecord.java
  32. 77
    0
      src/scratchpad/src/org/apache/poi/hemf/usermodel/HemfPicture.java
  33. 41
    17
      src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java
  34. 1
    1
      src/scratchpad/src/org/apache/poi/hwmf/record/HwmfBrushStyle.java
  35. 139
    233
      src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java
  36. 1
    1
      src/scratchpad/src/org/apache/poi/hwmf/record/HwmfEscape.java
  37. 136
    413
      src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java
  38. 61
    65
      src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFont.java
  39. 1
    1
      src/scratchpad/src/org/apache/poi/hwmf/record/HwmfHatchStyle.java
  40. 1
    1
      src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMapMode.java
  41. 94
    56
      src/scratchpad/src/org/apache/poi/hwmf/record/HwmfMisc.java
  42. 19
    14
      src/scratchpad/src/org/apache/poi/hwmf/record/HwmfPalette.java
  43. 14
    5
      src/scratchpad/src/org/apache/poi/hwmf/record/HwmfPenStyle.java
  44. 1
    1
      src/scratchpad/src/org/apache/poi/hwmf/record/HwmfRecord.java
  45. 74
    72
      src/scratchpad/src/org/apache/poi/hwmf/record/HwmfRecordType.java
  46. 149
    63
      src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java
  47. 79
    123
      src/scratchpad/src/org/apache/poi/hwmf/record/HwmfWindowing.java
  48. 4
    10
      src/scratchpad/src/org/apache/poi/hwmf/usermodel/HwmfPicture.java
  49. 0
    198
      src/scratchpad/testcases/org/apache/poi/hemf/extractor/HemfExtractorTest.java
  50. 14
    18
      src/scratchpad/testcases/org/apache/poi/hemf/hemfplus/extractor/HemfPlusExtractorTest.java
  51. 201
    0
      src/scratchpad/testcases/org/apache/poi/hemf/usermodel/HemfPictureTest.java
  52. 4
    4
      src/scratchpad/testcases/org/apache/poi/hwmf/TestHwmfParsing.java

src/ooxml/java/org/apache/poi/xdgf/geom/Dimension2dDouble.java → src/java/org/apache/poi/util/Dimension2DDouble.java View File

@@ -15,21 +15,21 @@
limitations under the License.
==================================================================== */

package org.apache.poi.xdgf.geom;
package org.apache.poi.util;

import java.awt.geom.Dimension2D;

public class Dimension2dDouble extends Dimension2D {
public class Dimension2DDouble extends Dimension2D {

double width;
double height;

public Dimension2dDouble() {
public Dimension2DDouble() {
width = 0d;
height = 0d;
}

public Dimension2dDouble(double width, double height) {
public Dimension2DDouble(double width, double height) {
this.width = width;
this.height = height;
}
@@ -52,8 +52,8 @@ public class Dimension2dDouble extends Dimension2D {

@Override
public boolean equals(Object obj) {
if (obj instanceof Dimension2dDouble) {
Dimension2dDouble other = (Dimension2dDouble) obj;
if (obj instanceof Dimension2DDouble) {
Dimension2DDouble other = (Dimension2DDouble) obj;
return width == other.width && height == other.height;
}

@@ -68,6 +68,6 @@ public class Dimension2dDouble extends Dimension2D {

@Override
public String toString() {
return "Dimension2dDouble[" + width + ", " + height + "]";
return "Dimension2DDouble[" + width + ", " + height + "]";
}
}

+ 4
- 4
src/ooxml/java/org/apache/poi/xdgf/usermodel/XDGFPage.java View File

@@ -22,7 +22,7 @@ import java.awt.geom.Rectangle2D;

import org.apache.poi.ooxml.POIXMLException;
import org.apache.poi.util.Internal;
import org.apache.poi.xdgf.geom.Dimension2dDouble;
import org.apache.poi.util.Dimension2DDouble;

import com.microsoft.schemas.office.visio.x2012.main.PageType;

@@ -75,14 +75,14 @@ public class XDGFPage {
/**
* @return width/height of page
*/
public Dimension2dDouble getPageSize() {
public Dimension2DDouble getPageSize() {
XDGFCell w = _pageSheet.getCell("PageWidth");
XDGFCell h = _pageSheet.getCell("PageHeight");

if (w == null || h == null)
throw new POIXMLException("Cannot determine page size");

return new Dimension2dDouble(Double.parseDouble(w.getValue()),
return new Dimension2DDouble(Double.parseDouble(w.getValue()),
Double.parseDouble(h.getValue()));
}

@@ -109,7 +109,7 @@ public class XDGFPage {
* @return bounding box of page
*/
public Rectangle2D getBoundingBox() {
Dimension2dDouble sz = getPageSize();
Dimension2DDouble sz = getPageSize();
Point2D.Double offset = getPageOffset();

return new Rectangle2D.Double(-offset.getX(), -offset.getY(),

+ 2
- 2
src/ooxml/java/org/apache/poi/xdgf/util/VsdxToPng.java View File

@@ -25,7 +25,7 @@ import java.io.*;

import javax.imageio.ImageIO;

import org.apache.poi.xdgf.geom.Dimension2dDouble;
import org.apache.poi.util.Dimension2DDouble;
import org.apache.poi.xdgf.usermodel.XDGFPage;
import org.apache.poi.xdgf.usermodel.XmlVisioDocument;
import org.apache.poi.xdgf.usermodel.shape.ShapeDebuggerRenderer;
@@ -58,7 +58,7 @@ public class VsdxToPng {
public static void renderToPng(XDGFPage page, File outFile, double scale,
ShapeRenderer renderer) throws IOException {

Dimension2dDouble sz = page.getPageSize();
Dimension2DDouble sz = page.getPageSize();

int width = (int) (scale * sz.getWidth());
int height = (int) (scale * sz.getHeight());

src/scratchpad/src/org/apache/poi/hemf/record/HemfCommentEMFSpool.java → src/scratchpad/src/org/apache/poi/hemf/draw/HemfDrawProperties.java View File

@@ -15,17 +15,16 @@
limitations under the License.
==================================================================== */

package org.apache.poi.hemf.record;
package org.apache.poi.hemf.draw;

import org.apache.poi.util.Internal;
import org.apache.poi.hwmf.draw.HwmfDrawProperties;

/**
* Not yet implemented
*/
@Internal
public class HemfCommentEMFSpool extends AbstractHemfComment {
public class HemfDrawProperties extends HwmfDrawProperties {

public HemfCommentEMFSpool(byte[] rawBytes) {
super(rawBytes);
public HemfDrawProperties() {
}

public HemfDrawProperties(HemfDrawProperties other) {
super(other);
}
}

src/scratchpad/src/org/apache/poi/hemf/record/HemfComment.java → src/scratchpad/src/org/apache/poi/hemf/draw/HemfGraphics.java View File

@@ -15,17 +15,15 @@
limitations under the License.
==================================================================== */

package org.apache.poi.hemf.record;
package org.apache.poi.hemf.draw;

import org.apache.poi.util.Internal;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;

/**
* Contains arbitrary data
*/
@Internal
public class HemfComment extends AbstractHemfComment {
import org.apache.poi.hwmf.draw.HwmfGraphics;

public HemfComment(byte[] rawBytes) {
super(rawBytes);
public class HemfGraphics extends HwmfGraphics {
public HemfGraphics(Graphics2D graphicsCtx, Rectangle2D bbox) {
super(graphicsCtx,bbox);
}
}

+ 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
- 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
- 39
src/scratchpad/src/org/apache/poi/hemf/record/AbstractHemfComment.java View File

@@ -1,39 +0,0 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */

package org.apache.poi.hemf.record;

import org.apache.poi.util.Internal;

/**
* Syntactic utility to allow for four different
* comment classes
*/
@Internal
public abstract class AbstractHemfComment {

private final byte[] rawBytes;

public AbstractHemfComment(byte[] rawBytes) {
this.rawBytes = rawBytes;
}

public byte[] getRawBytes() {
return rawBytes;
}

}

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


}

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

@@ -0,0 +1,444 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */

package org.apache.poi.hemf.record.emf;

import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.function.Supplier;

import org.apache.poi.hemf.record.emfplus.HemfPlusRecord;
import org.apache.poi.hemf.record.emfplus.HemfPlusRecordIterator;
import org.apache.poi.util.IOUtils;
import org.apache.poi.util.Internal;
import org.apache.poi.util.LittleEndianConsts;
import org.apache.poi.util.LittleEndianInputStream;
import org.apache.poi.util.RecordFormatException;

/**
* Contains arbitrary data
*/
@Internal
public class HemfComment {
private static final int MAX_RECORD_LENGTH = 1_000_000;

public enum HemfCommentRecordType {
emfGeneric(-1, EmfCommentDataGeneric::new, false),
emfSpool(0x00000000, EmfCommentDataGeneric::new, false),
emfPlus(0x2B464D45, EmfCommentDataPlus::new, false),
emfPublic(0x43494447, null, false),
emfBeginGroup(0x00000002, EmfCommentDataBeginGroup::new, true),
emfEndGroup(0x00000003, EmfCommentDataEndGroup::new, true),
emfMultiFormats(0x40000004, EmfCommentDataMultiformats::new, true),
emfWMF(0x80000001, EmfCommentDataWMF::new, true),
emfUnicodeString(0x00000040, EmfCommentDataUnicode::new, true),
emfUnicodeEnd(0x00000080, EmfCommentDataUnicode::new, true)
;


public final long id;
public final Supplier<? 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;
}
}

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.
type = leis.readUInt();
assert(type == HemfRecordType.comment.id);
// A 32-bit unsigned integer that specifies the size in bytes of this record in the
// metafile. This value MUST be a multiple of 4 bytes.
recordSize = leis.readUInt();
}

// A 32-bit unsigned integer that specifies the size, in bytes, of the CommentIdentifier and
// CommentRecordParm fields in the RecordBuffer field that follows.
// It MUST NOT include the size of itself or the size of the AlignmentPadding field, if present.
long dataSize = leis.readUInt();

try {
leis.mark(2*LittleEndianConsts.INT_SIZE);
// An optional, 32-bit unsigned integer that identifies the type of comment record.
// See the preceding table for descriptions of these record types.
// Valid comment identifier values are listed in the following table.
//
// If this field contains any other value, the comment record MUST be an EMR_COMMENT record
final int commentIdentifier = (int)leis.readUInt();
// A 32-bit unsigned integer that identifies the type of public comment record.
final int publicCommentIdentifier = (int)leis.readUInt();

final boolean isEmfPublic = (commentIdentifier == HemfCommentRecordType.emfPublic.id);
leis.reset();

final HemfCommentRecordType commentType = HemfCommentRecordType.getById
(isEmfPublic ? publicCommentIdentifier : commentIdentifier, isEmfPublic);
assert(commentType != null);
final EmfCommentData record = commentType.constructor.get();

long readBytes = record.init(leis, dataSize);
final int skipBytes = (int)(recordSize-4-readBytes);
assert (skipBytes >= 0);
leis.skipFully(skipBytes);

return record;
} catch (IOException e) {
throw new RecordFormatException(e);
}
}

@Override
public void remove() {
throw new UnsupportedOperationException("Remove not supported");
}

}



/**
* Private data is unknown to EMF; it is meaningful only to applications that know the format of the
* data and how to use it. EMR_COMMENT private data records MAY be ignored.
*/
public static class EmfCommentDataGeneric implements EmfCommentData {
private byte[] privateData;

@Override
public HemfCommentRecordType getCommentRecordType() {
return HemfCommentRecordType.emfGeneric;
}

@Override
public long init(LittleEndianInputStream leis, long dataSize) throws IOException {
privateData = IOUtils.safelyAllocate(dataSize, MAX_RECORD_LENGTH);
leis.readFully(privateData);
return privateData.length;
}
}

/** The EMR_COMMENT_EMFPLUS record contains embedded EMF+ records. */
public static class EmfCommentDataPlus implements EmfCommentData {
private final List<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 = new byte[fmt.sizeData];
leis.readFully(fmt.rawData);
}

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);
leis.readFully(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");
}
}
}

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

@@ -0,0 +1,783 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */

package org.apache.poi.hemf.record.emf;

import java.awt.geom.AffineTransform;
import java.awt.geom.Dimension2D;
import java.awt.geom.Path2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.IOException;

import org.apache.poi.hemf.draw.HemfGraphics;
import org.apache.poi.hwmf.draw.HwmfGraphics;
import org.apache.poi.hwmf.record.HwmfDraw;
import org.apache.poi.hwmf.record.HwmfDraw.WmfSelectObject;
import org.apache.poi.util.LittleEndianConsts;
import org.apache.poi.util.LittleEndianInputStream;

public class HemfDraw {
/**
* The EMR_SELECTOBJECT record adds a graphics object to the current metafile playback device
* context. The object is specified either by its index in the EMF Object Table or by its
* value from the StockObject enumeration.
*/
public static class EmfSelectObject extends WmfSelectObject implements HemfRecord {

@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.selectObject;
}

@Override
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
// A 32-bit unsigned integer that specifies either the index of a graphics object in the
// EMF Object Table or the index of a stock object from the StockObject enumeration.
objectIndex = leis.readInt();
return LittleEndianConsts.INT_SIZE;
}
}


/** The EMR_POLYBEZIER record specifies one or more Bezier curves. */
public static class EmfPolyBezier extends HwmfDraw.WmfPolygon implements HemfRecord {
private final Rectangle2D bounds = new Rectangle2D.Double();

@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.polyBezier;
}

protected long readPoint(LittleEndianInputStream leis, Point2D point) {
return readPointL(leis, point);
}

@Override
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
long size = readRectL(leis, bounds);

/* A 32-bit unsigned integer that specifies the number of points in the points
* array. This value MUST be one more than three times the number of curves to
* be drawn, because each Bezier curve requires two control points and an
* endpoint, and the initial curve requires an additional starting point.
*
* Line width | Device supports wideline | Maximum points allowed
* 1 | n/a | 16K
* > 1 | yes | 16K
* > 1 | no | 1360
*
* Any extra points MUST be ignored.
*/
final int count = (int)leis.readUInt();
final int points = Math.min(count, 16384);
size += LittleEndianConsts.INT_SIZE;

poly.reset();

/* Cubic Bezier curves are defined using the endpoints and control points
* specified by the points field. The first curve is drawn from the first
* point to the fourth point, using the second and third points as control
* points. Each subsequent curve in the sequence needs exactly three more points:
* the ending point of the previous curve is used as the starting point,
* the next two points in the sequence are control points,
* and the third is the ending point.
* The cubic Bezier curves SHOULD be drawn using the current pen.
*/

Point2D pnt[] = { new Point2D.Double(), new Point2D.Double(), new Point2D.Double() };

// points-1 because of the first point
final int pointCnt = hasStartPoint() ? points-1 : points;
for (int i=0; i+3<pointCnt; i+=3) {
// x (4 bytes): A 32-bit signed integer that defines the horizontal (x) coordinate of the point.
// y (4 bytes): A 32-bit signed integer that defines the vertical (y) coordinate of the point.
if (i==0 && hasStartPoint()) {
size += readPoint(leis, pnt[0]);
poly.moveTo(pnt[0].getX(),pnt[0].getY());
}

size += readPoint(leis, pnt[0]);
size += readPoint(leis, pnt[1]);
size += readPoint(leis, pnt[2]);

poly.curveTo(
pnt[0].getX(),pnt[0].getY(),
pnt[1].getX(),pnt[1].getY(),
pnt[2].getX(),pnt[2].getY()
);
}

return size;
}

/**
* @return true, if start point is in the list of points. false, if start point is taken from the context
*/
protected boolean hasStartPoint() {
return true;
}
}

/**
* The EMR_POLYBEZIER16 record specifies one or more Bezier curves.
* The curves are drawn using the current pen.
*/
public static class EmfPolyBezier16 extends EmfPolyBezier {
@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.polyBezier16;
}

protected long readPoint(LittleEndianInputStream leis, Point2D point) {
return readPointS(leis, point);
}
}


/**
* The EMR_POLYGON record specifies a polygon consisting of two or more vertexes connected by
* straight lines.
*/
public static class EmfPolygon extends HwmfDraw.WmfPolygon implements HemfRecord {
private final Rectangle2D bounds = new Rectangle2D.Double();

@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.polygon;
}

protected long readPoint(LittleEndianInputStream leis, Point2D point) {
return readPointL(leis, point);
}

@Override
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
long size = readRectL(leis, bounds);

// see PolyBezier about limits
final int count = (int)leis.readUInt();
final int points = Math.min(count, 16384);
size += LittleEndianConsts.INT_SIZE;

Point2D pnt = new Point2D.Double();
for (int i=0; i<points; i++) {
size += readPoint(leis, pnt);
if (i==0) {
poly.moveTo(pnt.getX(), pnt.getY());

if (hasStartPoint()) {
continue;
}

// if this path is connected to the current position (= has no start point)
// the first entry is a dummy entry and will be skipped later
}
poly.lineTo(pnt.getX(), pnt.getY());
}

return size;
}

/**
* @return true, if start point is in the list of points. false, if start point is taken from the context
*/
protected boolean hasStartPoint() {
return true;
}
}

/**
* The EMR_POLYGON16 record specifies a polygon consisting of two or more vertexes connected by straight lines.
* The polygon is outlined by using the current pen and filled by using the current brush and polygon fill mode.
* The polygon is closed automatically by drawing a line from the last vertex to the first
*/
public static class EmfPolygon16 extends EmfPolygon {
@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.polygon16;
}

@Override
protected long readPoint(LittleEndianInputStream leis, Point2D point) {
return readPointS(leis, point);
}
}

/**
* The EMR_POLYLINE record specifies a series of line segments by connecting the points in the
* specified array.
*/
public static class EmfPolyline extends EmfPolygon {
@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.polyline;
}

@Override
protected boolean isFill() {
return false;
}
}

/**
* The EMR_POLYLINE16 record specifies a series of line segments by connecting the points in the
* specified array.
*/
public static class EmfPolyline16 extends EmfPolyline {
@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.polyline16;
}

@Override
protected long readPoint(LittleEndianInputStream leis, Point2D point) {
return readPointS(leis, point);
}
}

/**
* The EMR_POLYBEZIERTO record specifies one or more Bezier curves based upon the current
* position.
*/
public static class EmfPolyBezierTo extends EmfPolyBezier {
@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.polyBezierTo;
}

@Override
protected boolean hasStartPoint() {
return false;
}

@Override
protected Path2D getShape(HwmfGraphics ctx) {
return polyTo(ctx, poly);
}
}

/**
* The EMR_POLYBEZIERTO16 record specifies one or more Bezier curves based on the current
* position.
*/
public static class EmfPolyBezierTo16 extends EmfPolyBezierTo {
@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.polyBezierTo16;
}

@Override
protected long readPoint(LittleEndianInputStream leis, Point2D point) {
return readPointS(leis, point);
}
}

/** The EMR_POLYLINETO record specifies one or more straight lines based upon the current position. */
public static class EmfPolylineTo extends EmfPolyline {
@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.polylineTo;
}

@Override
protected boolean hasStartPoint() {
return false;
}

@Override
protected Path2D getShape(HwmfGraphics ctx) {
return polyTo(ctx, poly);
}
}

/**
* The EMR_POLYLINETO16 record specifies one or more straight lines based upon the current position.
* A line is drawn from the current position to the first point specified by the points field by using the
* current pen. For each additional line, drawing is performed from the ending point of the previous
* line to the next point specified by points.
*/
public static class EmfPolylineTo16 extends EmfPolylineTo {
@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.polylineTo16;
}

@Override
protected long readPoint(LittleEndianInputStream leis, Point2D point) {
return readPointS(leis, point);
}
}

/**
* The EMR_POLYPOLYGON record specifies a series of closed polygons.
*/
public static class EmfPolyPolygon extends HwmfDraw.WmfPolyPolygon implements HemfRecord {
private final Rectangle2D bounds = new Rectangle2D.Double();

@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.polyPolygon;
}

protected long readPoint(LittleEndianInputStream leis, Point2D point) {
return readPointL(leis, point);
}

@Override
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
long size = readRectL(leis, bounds);

// A 32-bit unsigned integer that specifies the number of polygons.
long numberOfPolygons = leis.readUInt();
// A 32-bit unsigned integer that specifies the total number of points in all polygons.
long count = Math.min(16384, leis.readUInt());

size += 2 * LittleEndianConsts.INT_SIZE;

// An array of 32-bit unsigned integers that specifies the point count for each polygon.
long[] polygonPointCount = new long[(int)numberOfPolygons];

size += numberOfPolygons * LittleEndianConsts.INT_SIZE;

for (int i=0; i<numberOfPolygons; i++) {
polygonPointCount[i] = leis.readUInt();
}

Point2D pnt = new Point2D.Double();
for (long nPoints : polygonPointCount) {
/**
* An array of WMF PointL objects that specifies the points for all polygons in logical units.
* The number of points is specified by the Count field value.
*/
Path2D poly = new Path2D.Double();
for (int i=0; i<nPoints; i++) {
size += readPoint(leis, pnt);
if (i == 0) {
poly.moveTo(pnt.getX(), pnt.getY());
} else {
poly.lineTo(pnt.getX(), pnt.getY());
}
}
if (isClosed()) {
poly.closePath();
}
polyList.add(poly);
}
return size;
}

/**
* @return true, if a polyline should be closed, i.e. is a polygon
*/
protected boolean isClosed() {
return true;
}
}

/**
* The EMR_POLYPOLYGON16 record specifies a series of closed polygons. Each polygon is outlined
* using the current pen, and filled using the current brush and polygon fill mode.
* The polygons drawn by this record can overlap.
*/
public static class EmfPolyPolygon16 extends EmfPolyPolygon {
@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.polyPolygon16;
}

@Override
protected long readPoint(LittleEndianInputStream leis, Point2D point) {
return readPointS(leis, point);
}
}

/**
* The EMR_POLYPOLYLINE record specifies multiple series of connected line segments.
*/
public static class EmfPolyPolyline extends EmfPolyPolygon {
@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.polyPolyline;
}

@Override
protected boolean isClosed() {
return false;
}

@Override
protected boolean isFill() {
return false;
}
}

/** The EMR_POLYPOLYLINE16 record specifies multiple series of connected line segments. */
public static class EmfPolyPolyline16 extends EmfPolyPolyline {
@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.polyPolyline16;
}

@Override
protected long readPoint(LittleEndianInputStream leis, Point2D point) {
return readPointS(leis, point);
}
}

/**
* The EMR_SETPIXELV record defines the color of the pixel at the specified logical coordinates.
*/
public static class EmfSetPixelV extends HwmfDraw.WmfSetPixel implements HemfRecord {
@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.setPixelV;
}

@Override
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
long size = readPointL(leis, point);
size += colorRef.init(leis);
return size;
}
}

/**
* The EMR_MOVETOEX record specifies coordinates of the new current position, in logical units.
*/
public static class EmfSetMoveToEx extends HwmfDraw.WmfMoveTo implements HemfRecord {
@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.setMoveToEx;
}

@Override
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
return readPointL(leis, point);
}
}

/**
* The EMR_ARCTO record specifies an elliptical arc.
* It resets the current position to the end point of the arc.
*/
public static class EmfArc extends HwmfDraw.WmfArc implements HemfRecord {
@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.arc;
}

@Override
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
long size = readRectL(leis, bounds);
size += readPointL(leis, startPoint);
size += readPointL(leis, endPoint);
return size;
}

@Override
public void draw(HemfGraphics ctx) {
super.draw(ctx);
ctx.getProperties().setLocation(endPoint);
}
}

/**
* The EMR_CHORD record specifies a chord, which is a region bounded by the intersection of an
* ellipse and a line segment, called a secant. The chord is outlined by using the current pen
* and filled by using the current brush.
*/
public static class EmfChord extends HwmfDraw.WmfChord implements HemfRecord {
@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.chord;
}

@Override
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
long size = readRectL(leis, bounds);
size += readPointL(leis, startPoint);
size += readPointL(leis, endPoint);
return size;
}
}

/**
* The EMR_PIE record specifies a pie-shaped wedge bounded by the intersection of an ellipse and two
* radials. The pie is outlined by using the current pen and filled by using the current brush.
*/
public static class EmfPie extends HwmfDraw.WmfPie implements HemfRecord {
@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.pie;
}

@Override
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
long size = readRectL(leis, bounds);
size += readPointL(leis, startPoint);
size += readPointL(leis, endPoint);
return size;
}
}

/**
* The EMR_ELLIPSE record specifies an ellipse. The center of the ellipse is the center of the specified
* bounding rectangle. The ellipse is outlined by using the current pen and is filled by using the current
* brush.
*/
public static class EmfEllipse extends HwmfDraw.WmfEllipse implements HemfRecord {
@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.ellipse;
}

@Override
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
return readRectL(leis, bounds);
}
}

/**
* The EMR_RECTANGLE record draws a rectangle. The rectangle is outlined by using the current pen
* and filled by using the current brush.
*/
public static class EmfRectangle extends HwmfDraw.WmfRectangle implements HemfRecord {
@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.rectangle;
}

@Override
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
return readRectL(leis, bounds);
}
}

/**
* The EMR_ROUNDRECT record specifies a rectangle with rounded corners. The rectangle is outlined
* by using the current pen and filled by using the current brush.
*/
public static class EmfRoundRect extends HwmfDraw.WmfRoundRect implements HemfRecord {
@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.roundRect;
}

@Override
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
long size = readRectL(leis, bounds);

// A 32-bit unsigned integer that defines the x-coordinate of the point.
width = (int)leis.readUInt();
height = (int)leis.readUInt();

return size + 2*LittleEndianConsts.INT_SIZE;
}
}

/**
* The EMR_LINETO record specifies a line from the current position up to, but not including, the
* specified point. It resets the current position to the specified point.
*/
public static class EmfLineTo extends HwmfDraw.WmfLineTo implements HemfRecord {
@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.lineTo;
}

@Override
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
return readPointL(leis, point);
}
}

/**
* The EMR_ARCTO record specifies an elliptical arc.
* It resets the current position to the end point of the arc.
*/
public static class EmfArcTo extends HwmfDraw.WmfArc implements HemfRecord {
@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.arcTo;
}

@Override
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
long size = readRectL(leis, bounds);
size += readPointL(leis, startPoint);
size += readPointL(leis, endPoint);
return size;
}

@Override
public void draw(HemfGraphics ctx) {
super.draw(ctx);
ctx.getProperties().setLocation(endPoint);
}
}

/** The EMR_POLYDRAW record specifies a set of line segments and Bezier curves. */
public static class EmfPolyDraw extends HwmfDraw.WmfPolygon implements HemfRecord {
private final Rectangle2D bounds = new Rectangle2D.Double();

@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.polyDraw;
}

protected long readPoint(LittleEndianInputStream leis, Point2D point) {
return readPointL(leis, point);
}

@Override
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
long size = readRectL(leis, bounds);
int count = (int)leis.readUInt();
size += LittleEndianConsts.INT_SIZE;
Point2D points[] = new Point2D[count];
for (int i=0; i<count; i++) {
size += readPoint(leis, points[i]);
}

poly.reset();

for (int i=0; i<count; i++) {
int mode = leis.readUByte();
switch (mode & 0x06) {
// PT_LINETO
// Specifies that a line is to be drawn from the current position to this point, which
// then becomes the new current position.
case 0x02:
poly.lineTo(points[i].getX(), points[i].getY());
break;
// PT_BEZIERTO
// Specifies that this point is a control point or ending point for a Bezier curve.
// PT_BEZIERTO types always occur in sets of three.
// The current position defines the starting point for the Bezier curve.
// The first two PT_BEZIERTO points are the control points,
// and the third PT_BEZIERTO point is the ending point.
// The ending point becomes the new current position.
// If there are not three consecutive PT_BEZIERTO points, an error results.
case 0x04:
int mode2 = leis.readUByte();
int mode3 = leis.readUByte();
assert(mode2 == 0x04 && mode3 == 0x04);
poly.curveTo(
points[i].getX(), points[i].getY(),
points[i+1].getX(), points[i+1].getY(),
points[i+2].getX(), points[i+2].getY()
);
i+=2;
break;
// PT_MOVETO
// Specifies that this point starts a disjoint figure. This point becomes the new current position.
case 0x06:
poly.moveTo(points[i].getX(), points[i].getY());
break;
default:
// TODO: log error
break;
}

// PT_CLOSEFIGURE
// A PT_LINETO or PT_BEZIERTO type can be combined with this value by using the bitwise operator OR
// to indicate that the corresponding point is the last point in a figure and the figure is closed.
// The current position is set to the ending point of the closing line.
if ((mode & 0x01) == 0x01) {
this.poly.closePath();
}
}
size += count;
return size;
}
}

public static class EmfPolyDraw16 extends EmfPolyDraw {
@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.polyDraw16;
}

protected long readPoint(LittleEndianInputStream leis, Point2D point) {
return readPointS(leis, point);
}

}
static long readRectL(LittleEndianInputStream leis, Rectangle2D bounds) {
/* A 32-bit signed integer that defines the x coordinate, in logical coordinates,
* of the ... corner of the rectangle.
*/
final int left = leis.readInt();
final int top = leis.readInt();
final int right = leis.readInt();
final int bottom = leis.readInt();
bounds.setRect(left, top, right-left, bottom-top);

return 4 * LittleEndianConsts.INT_SIZE;
}

static long readPointS(LittleEndianInputStream leis, Point2D point) {
// x (2 bytes): A 16-bit signed integer that defines the horizontal (x) coordinate of the point.
final int x = leis.readShort();
// y (2 bytes): A 16-bit signed integer that defines the vertical (y) coordinate of the point.
final int y = leis.readShort();
point.setLocation(x, y);

return 2*LittleEndianConsts.SHORT_SIZE;

}
static long readPointL(LittleEndianInputStream leis, Point2D point) {
// x (4 bytes): A 32-bit signed integer that defines the horizontal (x) coordinate of the point.
final int x = leis.readInt();
// y (4 bytes): A 32-bit signed integer that defines the vertical (y) coordinate of the point.
final int y = leis.readInt();
point.setLocation(x, y);

return 2*LittleEndianConsts.INT_SIZE;

}

static long readDimensionFloat(LittleEndianInputStream leis, Dimension2D dimension) {
final double width = leis.readFloat();
final double height = leis.readFloat();
dimension.setSize(width, height);
return 2*LittleEndianConsts.INT_SIZE;
}

static long readDimensionInt(LittleEndianInputStream leis, Dimension2D dimension) {
final double width = leis.readUInt();
final double height = leis.readUInt();
dimension.setSize(width, height);
return 2*LittleEndianConsts.INT_SIZE;
}

private static Path2D polyTo(HwmfGraphics ctx, Path2D poly) {
Path2D polyCopy = new Path2D.Double();
Point2D start = ctx.getProperties().getLocation();
polyCopy.moveTo(start.getX(), start.getY());

PathIterator iter = poly.getPathIterator(null);
iter.next();

polyCopy.append(iter, true);
return polyCopy;
}


}

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

@@ -0,0 +1,620 @@
/* ====================================================================
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.readRectL;
import static org.apache.poi.hemf.record.emf.HemfDraw.readPointL;
import static org.apache.poi.hemf.record.emf.HemfRecordIterator.HEADER_SIZE;

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

public enum HemfRegionMode {
RGN_AND(0x01),
RGN_OR(0x02),
RGN_XOR(0x03),
RGN_DIFF(0x04),
RGN_COPY(0x05);

int flag;
HemfRegionMode(int flag) {
this.flag = flag;
}

public static HemfRegionMode valueOf(int flag) {
for (HemfRegionMode rm : values()) {
if (rm.flag == flag) return rm;
}
return null;
}

}


/**
* 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.WmfBitBlt 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 byte[] xformSrc = new byte[24];

/** A WMF ColorRef object that specifies 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. This value MUST be in the DIBColors enumeration
*/
protected int usageSrc;

/** The source bitmap header. */
protected byte[] bmiSrc;

/** The source bitmap bits. */
protected byte[] bitsSrc;

@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.stretchBlt;
}

@Override
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
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);

size += LittleEndianConsts.INT_SIZE;

final Point2D srcPnt = new Point2D.Double();
size += readPointL(leis, srcPnt);

leis.readFully(xformSrc);
size += 24;

size += bkColorSrc.init(leis);

usageSrc = (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();

size += 5*LittleEndianConsts.INT_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 + type and size field
final int undefinedSpace1 = (int)(offBmiSrc - size - HEADER_SIZE);
assert(undefinedSpace1 >= 0);
leis.skipFully(undefinedSpace1);
size += undefinedSpace1;

bmiSrc = IOUtils.safelyAllocate(cbBmiSrc, MAX_RECORD_LENGTH);
leis.readFully(bmiSrc);
size += cbBmiSrc;

final int undefinedSpace2 = (int)(offBitsSrc - size - HEADER_SIZE);
assert(undefinedSpace2 >= 0);
leis.skipFully(undefinedSpace2);
size += undefinedSpace2;

bitsSrc = IOUtils.safelyAllocate(cbBitsSrc, MAX_RECORD_LENGTH);
leis.readFully(bitsSrc);
size += cbBitsSrc;

return size;
}

protected boolean srcEqualsDstDimension() {
return false;
}
}

/**
* The EMR_BITBLT record specifies a block transfer of pixels from a source bitmap to a destination rectangle,
* optionally in combination with a brush pattern, according to a specified raster operation.
*/
public static class EmfBitBlt extends EmfStretchBlt {
@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.bitBlt;
}

@Override
protected boolean srcEqualsDstDimension() {
return false;
}
}


/** The EMR_FRAMERGN record draws a border around the specified region using the specified brush. */
public static class EmfFrameRgn extends HwmfDraw.WmfFrameRegion implements HemfRecord {
private final Rectangle2D bounds = new Rectangle2D.Double();
private final List<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);

Area frame = new Area();
for (Rectangle2D rct : rgnRects) {
frame.add(new Area(rct));
}
Rectangle2D frameBounds = frame.getBounds2D();
AffineTransform at = new AffineTransform();
at.translate(bounds.getX()-frameBounds.getX(), bounds.getY()-frameBounds.getY());
at.scale(bounds.getWidth()/frameBounds.getWidth(), bounds.getHeight()/frameBounds.getHeight());
frame.transform(at);

ctx.fill(frame);
}
}

/** The EMR_INVERTRGN record inverts the colors in the specified region. */
public static class EmfInvertRgn implements HemfRecord {
protected final Rectangle2D bounds = new Rectangle2D.Double();
protected final List<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;
}
}

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

public static class EmfExtSelectClipRgn implements HemfRecord {
protected HemfRegionMode 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 = HemfRegionMode.valueOf((int)leis.readUInt());
long size = 2* LittleEndianConsts.INT_SIZE;

// If RegionMode is RGN_COPY, this data can be omitted and the clip region
// SHOULD be set to the default (NULL) clip region.
if (regionMode != HemfRegionMode.RGN_COPY) {
size += readRgnData(leis, rgnRects);
}
return size;
}
}

public static class EmfAlphaBlend implements HemfRecord {
/** the destination bounding rectangle in device units */
protected final Rectangle2D bounds = new Rectangle2D.Double();
/** the destination rectangle */
protected final Rectangle2D destRect = new Rectangle2D.Double();
/** the source rectangle */
protected final Rectangle2D srcRect = new Rectangle2D.Double();
/**
* The blend operation code. The only source and destination blend operation that has been defined
* is 0x00, which specifies that the source bitmap MUST be combined with the destination bitmap based
* on the alpha transparency values of the source pixels.
*/
protected byte blendOperation;
/** This value MUST be 0x00 and MUST be ignored. */
protected byte blendFlags;
/**
* An 8-bit unsigned integer that specifies alpha transparency, which determines the blend of the source
* and destination bitmaps. This value MUST be used on the entire source bitmap. The minimum alpha
* transparency value, zero, corresponds to completely transparent; the maximum value, 0xFF, corresponds
* to completely opaque. In effect, a value of 0xFF specifies that the per-pixel alpha values determine
* the blend of the source and destination bitmaps.
*/
protected int srcConstantAlpha;
/**
* A byte that specifies how source and destination pixels are interpreted with respect to alpha transparency.
*
* 0x00:
* The pixels in the source bitmap do not specify alpha transparency.
* In this case, the SrcConstantAlpha value determines the blend of the source and destination bitmaps.
* Note that in the following equations SrcConstantAlpha is divided by 255,
* which produces a value in the range 0 to 1.
*
* 0x01: "AC_SRC_ALPHA"
* Indicates that the source bitmap is 32 bits-per-pixel and specifies an alpha transparency value
* for each pixel.
*/
protected byte alphaFormat;
/** a world-space to page-space transform to apply to the source bitmap. */
protected final AffineTransform xFormSrc = new AffineTransform();
/** the background color of the source bitmap. */
protected final HwmfColorRef bkColorSrc = new HwmfColorRef();
/**
* A 32-bit unsigned integer that specifies how to interpret values in the
* color table in the source bitmap header.
*/
protected ColorUsage usageSrc;

protected final HwmfBitmapDib bitmap = new HwmfBitmapDib();

@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.alphaBlend;
}

@Override
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
final int startIdx = leis.getReadIndex();

long size = readRectL(leis, bounds);
size += readBounds2(leis, destRect);

blendOperation = leis.readByte();
assert (blendOperation == 0);
blendFlags = leis.readByte();
assert (blendOperation == 0);
srcConstantAlpha = leis.readUByte();
alphaFormat = leis.readByte();

// A 32-bit signed integer that specifies the logical x-coordinate of the upper-left
// corner of the source rectangle.
final int xSrc = leis.readInt();
// A 32-bit signed integer that specifies the logical y-coordinate of the upper-left
// corner of the source rectangle.
final int ySrc = leis.readInt();

size += 3*LittleEndianConsts.INT_SIZE;
size += readXForm(leis, xFormSrc);
size += bkColorSrc.init(leis);

usageSrc = ColorUsage.valueOf((int)leis.readUInt());


// A 32-bit unsigned integer that specifies the offset, in bytes, from the
// start of this record to the source bitmap header in the BitmapBuffer field.
final int offBmiSrc = (int)leis.readUInt();

// A 32-bit unsigned integer that specifies the size, in bytes, of the source bitmap header.
final int cbBmiSrc = (int)leis.readUInt();
// A 32-bit unsigned integer that specifies the offset, in bytes, from the
// start of this record to the source bitmap bits in the BitmapBuffer field.
final int offBitsSrc = (int)leis.readUInt();
// A 32-bit unsigned integer that specifies the size, in bytes, of the source bitmap bits.
final int cbBitsSrc = (int)leis.readUInt();

// A 32-bit signed integer that specifies the logical width of the source rectangle.
// This value MUST be greater than zero.
final int cxSrc = leis.readInt();
// A 32-bit signed integer that specifies the logical height of the source rectangle.
// This value MUST be greater than zero.
final int cySrc = leis.readInt();

srcRect.setRect(xSrc, ySrc, cxSrc, cySrc);

size += 7 * LittleEndianConsts.INT_SIZE;

size += readBitmap(leis, bitmap, startIdx, offBmiSrc, cbBmiSrc, offBitsSrc, cbBitsSrc);

return size;
}
}

public static class EmfSetDiBitsToDevice implements HemfRecord {
protected final Rectangle2D bounds = new Rectangle2D.Double();
protected final Point2D dest = new Point2D.Double();
protected final Rectangle2D src = new Rectangle2D.Double();
protected ColorUsage usageSrc;
protected final HwmfBitmapDib bitmap = new HwmfBitmapDib();

@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.setDiBitsToDevice;
}

@Override
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
int startIdx = leis.getReadIndex();

// A WMF RectL object that defines the destination bounding rectangle in device units.
long size = readRectL(leis, bounds);
// the logical x/y-coordinate of the upper-left corner of the destination rectangle.
size += readPointL(leis, dest);
// the source rectangle
size += readBounds2(leis, src);
// A 32-bit unsigned integer that specifies the offset, in bytes, from the
// start of this record to the source bitmap header in the BitmapBuffer field.
final int offBmiSrc = (int)leis.readUInt();
// A 32-bit unsigned integer that specifies the size, in bytes, of the source bitmap header.
final int cbBmiSrc = (int)leis.readUInt();
// A 32-bit unsigned integer that specifies the offset, in bytes, from the
// start of this record to the source bitmap bits in the BitmapBuffer field.
final int offBitsSrc = (int)leis.readUInt();
// A 32-bit unsigned integer that specifies the size, in bytes, of the source bitmap bits.
final int cbBitsSrc = (int)leis.readUInt();
// A 32-bit unsigned integer that specifies how to interpret values in the color table
// in the source bitmap header. This value MUST be in the DIBColors enumeration
usageSrc = ColorUsage.valueOf((int)leis.readUInt());
// A 32-bit unsigned integer that specifies the first scan line in the array.
final int iStartScan = (int)leis.readUInt();
// A 32-bit unsigned integer that specifies the number of scan lines.
final int cScans = (int)leis.readUInt();
size += 7*LittleEndianConsts.INT_SIZE;

size += readBitmap(leis, bitmap, startIdx, offBmiSrc, cbBmiSrc, offBitsSrc, cbBitsSrc);

return size;
}
}

static long readBitmap(final LittleEndianInputStream leis, final HwmfBitmapDib bitmap,
final int startIdx, final int offBmiSrc, final int cbBmiSrc, final int offBitsSrc, int cbBitsSrc)
throws IOException {
final int offCurr = leis.getReadIndex()-(startIdx-HEADER_SIZE);
final int undefinedSpace1 = offBmiSrc-offCurr;
assert(undefinedSpace1 >= 0);

final int undefinedSpace2 = offBitsSrc-offCurr-cbBmiSrc-undefinedSpace1;
assert(undefinedSpace2 >= 0);

leis.skipFully(undefinedSpace1);

if (cbBmiSrc == 0 || cbBitsSrc == 0) {
return undefinedSpace1;
}

final LittleEndianInputStream leisDib;
if (undefinedSpace2 == 0) {
leisDib = leis;
} else {
final ByteArrayOutputStream bos = new ByteArrayOutputStream(cbBmiSrc+cbBitsSrc);
final long cbBmiSrcAct = IOUtils.copy(leis, bos, cbBmiSrc);
assert (cbBmiSrcAct == cbBmiSrc);
leis.skipFully(undefinedSpace2);
final long cbBitsSrcAct = IOUtils.copy(leis, bos, cbBitsSrc);
assert (cbBitsSrcAct == cbBitsSrc);
leisDib = new LittleEndianInputStream(new ByteArrayInputStream(bos.toByteArray()));
}
final int dibSize = cbBmiSrc+cbBitsSrc;
final int dibSizeAct = bitmap.init(leisDib, dibSize);
assert (dibSizeAct <= dibSize);
return undefinedSpace1 + cbBmiSrc + undefinedSpace2 + cbBitsSrc;
}


static long readRgnData(final LittleEndianInputStream leis, final List<Rectangle2D> rgnRects) {
// *** RegionDataHeader ***
// A 32-bit unsigned integer that specifies the size of this object in bytes. This MUST be 0x00000020.
long rgnHdrSiez = leis.readUInt();
assert(rgnHdrSiez == 0x20);
// A 32-bit unsigned integer that specifies the region type. This SHOULD be RDH_RECTANGLES (0x00000001)
long rgnHdrType = leis.readUInt();
assert(rgnHdrType == 1);
// A 32-bit unsigned integer that specifies the number of rectangles in this region.
long rgnCntRect = leis.readUInt();
// A 32-bit unsigned integer that specifies the size of the buffer of rectangles in bytes.
long rgnCntBytes = leis.readUInt();
long size = 4*LittleEndianConsts.INT_SIZE;
// A 128-bit WMF RectL object, which specifies the bounds of the region.
Rectangle2D rgnBounds = new Rectangle2D.Double();
size += readRectL(leis, rgnBounds);
for (int i=0; i<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)
// m11 (scaleY) = eM22 (Vertical scaling component)
// m01 (shearX) = eM12 (Horizontal proportionality constant)
// m10 (shearY) = eM21 (Vertical proportionality constant)
// m02 (translateX) = eDx (The horizontal translation component, in logical units.)
// m12 (translateY) = eDy (The vertical translation component, in logical units.)

// A 32-bit floating-point value of the transform matrix.
double eM11 = leis.readFloat();

// A 32-bit floating-point value of the transform matrix.
double eM12 = leis.readFloat();

// A 32-bit floating-point value of the transform matrix.
double eM21 = leis.readFloat();

// A 32-bit floating-point value of the transform matrix.
double eM22 = leis.readFloat();

// A 32-bit floating-point value that contains a horizontal translation component, in logical units.
double eDx = leis.readFloat();

// A 32-bit floating-point value that contains a vertical translation component, in logical units.
double eDy = leis.readFloat();

xform.setTransform(eM11, eM21, eM12, eM22, eDx, eDy);

return 6 * LittleEndian.INT_SIZE;
}
}

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

@@ -0,0 +1,464 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */

package org.apache.poi.hemf.record.emf;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

import org.apache.poi.common.usermodel.fonts.FontCharset;
import org.apache.poi.hwmf.record.HwmfFont;
import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.LittleEndianConsts;
import org.apache.poi.util.LittleEndianInputStream;

public class HemfFont extends HwmfFont {
private static final int LOGFONT_SIZE = 92;
private static final int LOGFONTPANOSE_SIZE = 320;

protected interface LogFontDetails {}

protected static class LogFontExDv implements LogFontDetails {
protected int[] designVector;
}

protected static class LogFontPanose implements LogFontDetails {
enum FamilyType {
PAN_ANY,
PAN_NO_FIT,
PAN_FAMILY_TEXT_DISPLAY,
PAN_FAMILY_SCRIPT,
PAN_FAMILY_DECORATIVE,
PAN_FAMILY_PICTORIAL
}

enum SerifType {
PAN_ANY,
PAN_NO_FIT,
PAN_SERIF_COVE,
PAN_SERIF_OBTUSE_COVE,
PAN_SERIF_SQUARE_COVE,
PAN_SERIF_OBTUSE_SQUARE_COVE,
PAN_SERIF_SQUARE,
PAN_SERIF_THIN,
PAN_SERIF_BONE,
PAN_SERIF_EXAGGERATED,
PAN_SERIF_TRIANGLE,
PAN_SERIF_NORMAL_SANS,
PAN_SERIF_OBTUSE_SANS,
PAN_SERIF_PERP_SANS,
PAN_SERIF_FLARED,
PAN_SERIF_ROUNDED
}

enum FontWeight {
PAN_ANY,
PAN_NO_FIT,
PAN_WEIGHT_VERY_LIGHT,
PAN_WEIGHT_LIGHT,
PAN_WEIGHT_THIN,
PAN_WEIGHT_BOOK,
PAN_WEIGHT_MEDIUM,
PAN_WEIGHT_DEMI,
PAN_WEIGHT_BOLD,
PAN_WEIGHT_HEAVY,
PAN_WEIGHT_BLACK,
PAN_WEIGHT_NORD
}

enum Proportion {
PAN_ANY,
PAN_NO_FIT,
PAN_PROP_OLD_STYLE,
PAN_PROP_MODERN,
PAN_PROP_EVEN_WIDTH,
PAN_PROP_EXPANDED,
PAN_PROP_CONDENSED,
PAN_PROP_VERY_EXPANDED,
PAN_PROP_VERY_CONDENSED,
PAN_PROP_MONOSPACED
}

enum Contrast {
PAN_ANY,
PAN_NO_FIT,
PAN_CONTRAST_NONE,
PAN_CONTRAST_VERY_LOW,
PAN_CONTRAST_LOW,
PAN_CONTRAST_MEDIUM_LOW,
PAN_CONTRAST_MEDIUM,
PAN_CONTRAST_MEDIUM_HIGH,
PAN_CONTRAST_HIGH,
PAN_CONTRAST_VERY_HIGH
}

enum StrokeVariation {
PAN_ANY,
PAN_NO_FIT,
PAN_STROKE_GRADUAL_DIAG,
PAN_STROKE_GRADUAL_TRAN,
PAN_STROKE_GRADUAL_VERT,
PAN_STROKE_GRADUAL_HORZ,
PAN_STROKE_RAPID_VERT,
PAN_STROKE_RAPID_HORZ,
PAN_STROKE_INSTANT_VERT
}

enum ArmStyle {
PAN_ANY,
PAN_NO_FIT,
PAN_STRAIGHT_ARMS_HORZ,
PAN_STRAIGHT_ARMS_WEDGE,
PAN_STRAIGHT_ARMS_VERT,
PAN_STRAIGHT_ARMS_SINGLE_SERIF,
PAN_STRAIGHT_ARMS_DOUBLE_SERIF,
PAN_BENT_ARMS_HORZ,
PAN_BENT_ARMS_WEDGE,
PAN_BENT_ARMS_VERT,
PAN_BENT_ARMS_SINGLE_SERIF,
PAN_BENT_ARMS_DOUBLE_SERIF
}

enum Letterform {
PAN_ANY,
PAN_NO_FIT,
PAN_LETT_NORMAL_CONTACT,
PAN_LETT_NORMAL_WEIGHTED,
PAN_LETT_NORMAL_BOXED,
PAN_LETT_NORMAL_FLATTENED,
PAN_LETT_NORMAL_ROUNDED,
PAN_LETT_NORMAL_OFF_CENTER,
PAN_LETT_NORMAL_SQUARE,
PAN_LETT_OBLIQUE_CONTACT,
PAN_LETT_OBLIQUE_WEIGHTED,
PAN_LETT_OBLIQUE_BOXED,
PAN_LETT_OBLIQUE_FLATTENED,
PAN_LETT_OBLIQUE_ROUNDED,
PAN_LETT_OBLIQUE_OFF_CENTER,
PAN_LETT_OBLIQUE_SQUARE
}

enum MidLine {
PAN_ANY,
PAN_NO_FIT,
PAN_MIDLINE_STANDARD_TRIMMED,
PAN_MIDLINE_STANDARD_POINTED,
PAN_MIDLINE_STANDARD_SERIFED,
PAN_MIDLINE_HIGH_TRIMMED,
PAN_MIDLINE_HIGH_POINTED,
PAN_MIDLINE_HIGH_SERIFED,
PAN_MIDLINE_CONSTANT_TRIMMED,
PAN_MIDLINE_CONSTANT_POINTED,
PAN_MIDLINE_CONSTANT_SERIFED,
PAN_MIDLINE_LOW_TRIMMED,
PAN_MIDLINE_LOW_POINTED,
PAN_MIDLINE_LOW_SERIFED
}

enum XHeight {
PAN_ANY,
PAN_NO_FIT,
PAN_XHEIGHT_CONSTANT_SMALL,
PAN_XHEIGHT_CONSTANT_STD,
PAN_XHEIGHT_CONSTANT_LARGE,
PAN_XHEIGHT_DUCKING_SMALL,
PAN_XHEIGHT_DUCKING_STD,
PAN_XHEIGHT_DUCKING_LARGE
}

protected int styleSize;
protected int vendorId;
protected int culture;
protected FamilyType familyType;
protected SerifType serifStyle;
protected FontWeight weight;
protected Proportion proportion;
protected Contrast contrast;
protected StrokeVariation strokeVariation;
protected ArmStyle armStyle;
protected Letterform letterform;
protected MidLine midLine;
protected XHeight xHeight;
}

protected String fullname;
protected String style;
protected String script;

protected LogFontDetails details;

@Override
public int init(LittleEndianInputStream leis, long recordSize) throws IOException {
// A 32-bit signed integer that specifies the height, in logical units, of the font's
// character cell or character. The character height value, also known as the em size, is the
// character cell height value minus the internal leading value. The font mapper SHOULD
// interpret the value specified in the Height field in the following manner.
//
// 0x00000000 < value:
// The font mapper transforms this value into device units and matches it against
// the cell height of the available fonts.
//
// 0x00000000
// The font mapper uses a default height value when it searches for a match.
//
// value < 0x00000000:
// The font mapper transforms this value into device units and matches its
// absolute value against the character height of the available fonts.
//
// For all height comparisons, the font mapper SHOULD look for the largest font that does not
// exceed the requested size.
height = leis.readInt();

// A 32-bit signed integer that specifies the average width, in logical units, of
// characters in the font. If the Width field value is zero, an appropriate value SHOULD be
// calculated from other LogFont values to find a font that has the typographer's intended
// aspect ratio.
width = leis.readInt();

// A 32-bit signed integer that specifies the angle, in tenths of degrees,
// between the escapement vector and the x-axis of the device. The escapement vector is
// parallel to the baseline of a row of text.
//
// When the graphics mode is set to GM_ADVANCED, the escapement angle of the string can
// be specified independently of the orientation angle of the string's characters.
escapement = leis.readInt();

// A 32-bit signed integer that specifies the angle, in tenths of degrees,
// between each character's baseline and the x-axis of the device.
orientation = leis.readInt();

// A 32-bit signed integer that specifies the weight of the font in the range zero through 1000.
// For example, 400 is normal and 700 is bold. If this value is zero, a default weight can be used.
weight = leis.readInt();

// An 8-bit unsigned integer that specifies an italic font if set to 0x01;
// otherwise, it MUST be set to 0x00.
italic = (leis.readUByte() == 0x01);

// An 8-bit unsigned integer that specifies an underlined font if set to 0x01;
// otherwise, it MUST be set to 0x00.
underline = (leis.readUByte() == 0x01);

// An 8-bit unsigned integer that specifies a strikeout font if set to 0x01;
// otherwise, it MUST be set to 0x00.
strikeOut = (leis.readUByte() == 0x01);

// An 8-bit unsigned integer that specifies the set of character glyphs.
// It MUST be a value in the WMF CharacterSet enumeration.
// If the character set is unknown, metafile processing SHOULD NOT attempt
// to translate or interpret strings that are rendered with that font.
// If a typeface name is specified in the Facename field, the CharSet field
// value MUST match the character set of that typeface.
charSet = FontCharset.valueOf(leis.readUByte());

// An 8-bit unsigned integer that specifies the output precision.
// The output precision defines how closely the font is required to match the requested height, width,
// character orientation, escapement, pitch, and font type.
// It MUST be a value from the WMF OutPrecision enumeration.
// Applications can use the output precision to control how the font mapper chooses a font when the
// operating system contains more than one font with a specified name. For example, if an operating
// system contains a font named Symbol in rasterized and TrueType forms, an output precision value
// of OUT_TT_PRECIS forces the font mapper to choose the TrueType version.
// A value of OUT_TT_ONLY_PRECIS forces the font mapper to choose a TrueType font, even if it is
// necessary to substitute a TrueType font with another name.
outPrecision = WmfOutPrecision.valueOf(leis.readUByte());

// An 8-bit unsigned integer that specifies the clipping precision.
// The clipping precision defines how to clip characters that are partially outside the clipping region.
// It can be one or more of the WMF ClipPrecision Flags
clipPrecision.init(leis);

// An 8-bit unsigned integer that specifies the output quality. The output quality defines how closely
// to attempt to match the logical-font attributes to those of an actual physical font.
// It MUST be one of the values in the WMF FontQuality enumeration
quality = WmfFontQuality.valueOf(leis.readUByte());

// A WMF PitchAndFamily object that specifies the pitch and family of the font.
// Font families describe the look of a font in a general way.
// They are intended for specifying a font when the specified typeface is not available.
pitchAndFamily = leis.readUByte();

int size = 5* LittleEndianConsts.INT_SIZE+8*LittleEndianConsts.BYTE_SIZE;

StringBuilder sb = new StringBuilder();

// A string of no more than 32 Unicode characters that specifies the typeface name of the font.
// If the length of this string is less than 32 characters, a terminating NULL MUST be present,
// after which the remainder of this field MUST be ignored.
int readBytes = readString(leis, sb, 32);
if (readBytes == -1) {
throw new IOException("Font facename can't be determined.");
}
facename = sb.toString();
size += readBytes;

if (recordSize <= LOGFONT_SIZE) {
return size;
}

// A string of 64 Unicode characters that contains the font's full name.
// Ifthe length of this string is less than 64 characters, a terminating
// NULL MUST be present, after which the remainder of this field MUST be ignored.
readBytes = readString(leis, sb, 64);
if (readBytes == -1) {
throw new IOException("Font fullname can't be determined.");
}
fullname = sb.toString();
size += readBytes;

// A string of 32 Unicode characters that defines the font's style. If the length of
// this string is less than 32 characters, a terminating NULL MUST be present,
// after which the remainder of this field MUST be ignored.
readBytes = readString(leis, sb, 32);
if (readBytes == -1) {
throw new IOException("Font style can't be determined.");
}
style = sb.toString();
size += readBytes;

if (recordSize == LOGFONTPANOSE_SIZE) {
// LogFontPanose Object

LogFontPanose logPan = new LogFontPanose();
details = logPan;

int version = leis.readInt();

// A 32-bit unsigned integer that specifies the point size at which font
//hinting is performed. If set to zero, font hinting is performed at the point size corresponding
//to the Height field in the LogFont object in the LogFont field.
logPan.styleSize = (int)leis.readUInt();

int match = leis.readInt();

int reserved = leis.readInt();

logPan.vendorId = leis.readInt();

logPan.culture = leis.readInt();

// An 8-bit unsigned integer that specifies the family type.
// The value MUST be in the FamilyType enumeration table.
logPan.familyType = LogFontPanose.FamilyType.values()[leis.readUByte()];

// An 8-bit unsigned integer that specifies the serif style.
// The value MUST be in the SerifType enumeration table.
logPan.serifStyle = LogFontPanose.SerifType.values()[leis.readUByte()];

// An 8-bit unsigned integer that specifies the weight of the font.
// The value MUST be in the Weight enumeration table.
logPan.weight = LogFontPanose.FontWeight.values()[leis.readUByte()];

// An 8-bit unsigned integer that specifies the proportion of the font.
// The value MUST be in the Proportion enumeration table.
logPan.proportion = LogFontPanose.Proportion.values()[leis.readUByte()];

// An 8-bit unsigned integer that specifies the proportion of the font.
// The value MUST be in the Proportion enumeration table.
logPan.contrast = LogFontPanose.Contrast.values()[leis.readUByte()];

// An 8-bit unsigned integer that specifies the stroke variation for the font.
// The value MUST be in the StrokeVariation enumeration table.
logPan.strokeVariation = LogFontPanose.StrokeVariation.values()[leis.readUByte()];

// An 8-bit unsigned integer that specifies the arm style of the font.
// The value MUST be in the ArmStyle enumeration table.
logPan.armStyle = LogFontPanose.ArmStyle.values()[leis.readUByte()];

// An 8-bit unsigned integer that specifies the letterform of the font.
// The value MUST be in the Letterform enumeration table.
logPan.letterform = LogFontPanose.Letterform.values()[leis.readUByte()];

// An 8-bit unsigned integer that specifies the midline of the font.
// The value MUST be in the MidLine enumeration table.
logPan.midLine = LogFontPanose.MidLine.values()[leis.readUByte()];

// An 8-bit unsigned integer that specifies the x height of the font.
// The value MUST be in the XHeight enumeration table.
logPan.xHeight = LogFontPanose.XHeight.values()[leis.readUByte()];

// skip 2 byte to ensure 32-bit alignment of this structure.
leis.skip(2);

size += 6*LittleEndianConsts.INT_SIZE+10* LittleEndianConsts.BYTE_SIZE+2;
} else {
// LogFontExDv Object

LogFontExDv logEx = new LogFontExDv();
details = logEx;

// A string of 32 Unicode characters that defines the character set of the font.
// If the length of this string is less than 32 characters, a terminating NULL MUST be present,
// after which the remainder of this field MUST be ignored.
readBytes = readString(leis, sb, 32);
if (readBytes == -1) {
throw new IOException("Font script can't be determined.");
}
script = sb.toString();
size += readBytes;

// Design Vector

// A 32-bit unsigned integer that MUST be set to the value 0x08007664.
int signature = leis.readInt();
assert (signature == 0x08007664);

// A 32-bit unsigned integer that specifies the number of elements in the
// Values array. It MUST be in the range 0 to 16, inclusive.
int numAxes = leis.readInt();
assert (0 <= numAxes && numAxes <= 16);

// An optional array of 32-bit signed integers that specify the values of the font axes of a
// multiple master, OpenType font. The maximum number of values in the array is 16.
if (numAxes > 0) {
logEx.designVector = new int[numAxes];
for (int i=0; i<numAxes; i++) {
logEx.designVector[i] = leis.readInt();
}
}
size += (2+numAxes)*LittleEndianConsts.INT_SIZE;
}




return size;
}

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

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

@@ -15,14 +15,20 @@
limitations under the License.
==================================================================== */

package org.apache.poi.hemf.record;
package org.apache.poi.hemf.record.emf;

import java.awt.Rectangle;
import static org.apache.poi.hemf.record.emf.HemfDraw.readDimensionFloat;
import static org.apache.poi.hemf.record.emf.HemfDraw.readDimensionInt;
import static org.apache.poi.hemf.record.emf.HemfDraw.readRectL;

import java.awt.geom.Dimension2D;
import java.awt.geom.Rectangle2D;
import java.io.IOException;

import org.apache.poi.util.IOUtils;
import org.apache.poi.util.Dimension2DDouble;
import org.apache.poi.util.Internal;
import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.LittleEndianConsts;
import org.apache.poi.util.LittleEndianInputStream;

/**
@@ -35,8 +41,8 @@ public class HemfHeader implements HemfRecord {
private static final int MAX_RECORD_LENGTH = 1_000_000;


private Rectangle boundsRectangle;
private Rectangle frameRectangle;
private final Rectangle2D boundsRectangle = new Rectangle2D.Double();
private final Rectangle2D frameRectangle = new Rectangle2D.Double();
private long bytes;
private long records;
private int handles;
@@ -48,14 +54,16 @@ public class HemfHeader implements HemfRecord {
private long offPixelFormat;
private long bOpenGL;
private boolean hasExtension2;
private long micrometersX;
private long micrometersY;
private final Dimension2D deviceDimension = new Dimension2DDouble();
private final Dimension2D milliDimension = new Dimension2DDouble();
private final Dimension2D microDimension = new Dimension2DDouble();


public Rectangle getBoundsRectangle() {
public Rectangle2D getBoundsRectangle() {
return boundsRectangle;
}

public Rectangle getFrameRectangle() {
public Rectangle2D getFrameRectangle() {
return frameRectangle;
}

@@ -104,11 +112,11 @@ public class HemfHeader implements HemfRecord {
}

public long getMicrometersX() {
return micrometersX;
return (long)microDimension.getWidth();
}

public long getMicrometersY() {
return micrometersY;
return (long)microDimension.getHeight();
}

@Override
@@ -127,75 +135,61 @@ public class HemfHeader implements HemfRecord {
", offPixelFormat=" + offPixelFormat +
", bOpenGL=" + bOpenGL +
", hasExtension2=" + hasExtension2 +
", micrometersX=" + micrometersX +
", micrometersY=" + micrometersY +
", micrometersX=" + getMicrometersX() +
", micrometersY=" + getMicrometersY() +
'}';
}

@Override
public HemfRecordType getRecordType() {
public HemfRecordType getEmfRecordType() {
return HemfRecordType.header;
}

@Override
public long init(LittleEndianInputStream leis, long recordId, long recordSize) throws IOException {
if (recordId != 1L) {
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);
}
//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;
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 = LittleEndian.getInt(data, offset); offset += LittleEndian.INT_SIZE;
long version = leis.readInt();
//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;
bytes = leis.readUInt();
records = leis.readUInt();
handles = leis.readUShort();
//reserved
leis.skipFully(LittleEndianConsts.SHORT_SIZE);

nDescription = leis.readUInt();
offDescription = leis.readUInt();
nPalEntries = leis.readUInt();

//should be skips
offset += 8;//device
offset += 8;//millimeters
size += 8*LittleEndianConsts.INT_SIZE;

size += readDimensionInt(leis, deviceDimension);
size += readDimensionInt(leis, milliDimension);

if (recordSize+8 >= 100) {
if (size+12 <= recordSize) {
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;
cbPixelFormat = leis.readUInt();
offPixelFormat = leis.readUInt();
bOpenGL = leis.readUInt();
size += 3*LittleEndianConsts.INT_SIZE;
}

if (recordSize+8 >= 108) {
if (size+8 <= recordSize) {
hasExtension2 = true;
micrometersX = LittleEndian.getUInt(data, offset); offset += LittleEndian.INT_SIZE;
micrometersY = LittleEndian.getUInt(data, offset); offset += LittleEndian.INT_SIZE;
size += readDimensionInt(leis, microDimension);
}
return recordSize;
return size;
}
}

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

@@ -0,0 +1,447 @@
/* ====================================================================
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.HemfFill.readBitmap;
import static org.apache.poi.hemf.record.emf.HemfRecordIterator.HEADER_SIZE;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.poi.hemf.draw.HemfGraphics;
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.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.HwmfPalette.PaletteEntry;
import org.apache.poi.hwmf.record.HwmfPenStyle;
import org.apache.poi.hwmf.record.HwmfPenStyle.HwmfLineDash;
import org.apache.poi.util.IOUtils;
import org.apache.poi.util.LittleEndianConsts;
import org.apache.poi.util.LittleEndianInputStream;

public class HemfMisc {
private static final int MAX_RECORD_LENGTH = 10_000_000;

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 {

// A 32-bit unsigned integer that specifies the number of palette entries.
int nPalEntries = (int)leis.readUInt();
// A 32-bit unsigned integer that specifies the offset to the palette entries from the start of this record.
int offPalEntries = (int)leis.readUInt();

int size = 2*LittleEndianConsts.INT_SIZE;
int undefinedSpace1 = (int)(offPalEntries - size - HEADER_SIZE);
assert (undefinedSpace1 >= 0);
leis.skipFully(undefinedSpace1);
size += undefinedSpace1;

for (int i=0; i<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;
assert ((sizeLast-HEADER_SIZE) == recordSize && recordSize == size);

return size;
}
}

/**
* The EMF_SAVEDC record saves the playback device context for later retrieval.
*/
public static class EmfSaveDc implements HemfRecord {
@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.saveDc;
}

@Override
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
return 0;
}

@Override
public void draw(HemfGraphics ctx) {
ctx.saveProperties();
}
}

/**
* The EMF_RESTOREDC record restores the playback device context from a previously saved device
* context.
*/
public static class EmfRestoreDc implements HemfRecord {

/**
* SavedDC (4 bytes): A 32-bit signed integer that specifies the saved state to restore relative to
* the current state. This value MUST be negative; –1 represents the state that was most
* recently saved on the stack, –2 the one before that, etc.
*/
private int nSavedDC;

@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.restoreDc;
}

@Override
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
nSavedDC = leis.readInt();
return LittleEndianConsts.INT_SIZE;
}

@Override
public void draw(HemfGraphics ctx) {
ctx.restoreProperties(nSavedDC);
}
}

/**
* The META_SETBKCOLOR record sets the background color in the playback device context to a
* specified color, or to the nearest physical color if the device cannot represent the specified color.
*/
public static class EmfSetBkColor implements HemfRecord {

private HwmfColorRef colorRef;

@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.setBkColor;
}

@Override
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
colorRef = new HwmfColorRef();
return colorRef.init(leis);
}

@Override
public void draw(HemfGraphics ctx) {
ctx.getProperties().setBackgroundColor(colorRef);
}
}


/**
* The EMR_SETBKMODE record specifies the background mix mode of the playback device context.
* The background mix mode is used with text, hatched brushes, and pen styles that are not solid
* lines.
*/
public static class EmfSetBkMode extends WmfSetBkMode implements HemfRecord {
public HemfRecordType getEmfRecordType() {
return HemfRecordType.setBkMode;
}

@Override
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
/*
* A 32-bit unsigned integer that specifies the background mode
* and MUST be in the BackgroundMode (section 2.1.4) enumeration
*/
bkMode = HwmfBkMode.valueOf((int)leis.readUInt());
return LittleEndianConsts.INT_SIZE;
}
}

/**
* The EMR_SETMAPPERFLAGS record specifies parameters of the process of matching logical fonts to
* physical fonts, which is performed by the font mapper.
*/
public static class EmfSetMapperFlags extends HwmfMisc.WmfSetMapperFlags implements HemfRecord {
public HemfRecordType getEmfRecordType() {
return HemfRecordType.setMapperFlags;
}

@Override
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
return super.init(leis, recordSize, (int)recordId);
}
}

/**
* The EMR_SETMAPMODE record specifies the mapping mode of the playback device context. The
* mapping mode specifies the unit of measure used to transform page space units into device space
* units, and also specifies the orientation of the device's x-axis and y-axis.
*/
public static class EmfSetMapMode extends HwmfMisc.WmfSetMapMode implements HemfRecord {
public HemfRecordType getEmfRecordType() {
return HemfRecordType.setMapMode;
}

@Override
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
// A 32-bit unsigned integer whose definition MUST be in the MapMode enumeration
mapMode = HwmfMapMode.valueOf((int)leis.readUInt());
return LittleEndianConsts.INT_SIZE;
}
}

/**
* The EMR_SETROP2 record defines a binary raster operation mode.
*/
public static class EmfSetRop2 extends HwmfMisc.WmfSetRop2 implements HemfRecord {
@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.setRop2;
}

@Override
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
// A 32-bit unsigned integer that specifies the raster operation mode and
// MUST be in the WMF Binary Raster Op enumeration
drawMode = HwmfBinaryRasterOp.valueOf((int)leis.readUInt());
return LittleEndianConsts.INT_SIZE;
}
}


/**
* The EMR_SETSTRETCHBLTMODE record specifies bitmap stretch mode.
*/
public static class EmfSetStretchBltMode extends HwmfMisc.WmfSetStretchBltMode implements HemfRecord {

@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.setStretchBltMode;
}

@Override
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
// A 32-bit unsigned integer that specifies the stretch mode and MAY be
// in the StretchMode enumeration.
stretchBltMode = StretchBltMode.valueOf((int)leis.readUInt());
return LittleEndianConsts.INT_SIZE;
}
}

/** The EMR_CREATEBRUSHINDIRECT record defines a logical brush for graphics operations. */
public static class EmfCreateBrushIndirect extends HwmfMisc.WmfCreateBrushIndirect implements HemfRecord {
/**
* A 32-bit unsigned integer that specifies the index of the logical brush object in the
* EMF Object Table. This index MUST be saved so that this object can be reused or modified.
*/
private int brushIdx;

@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.createBrushIndirect;
}

@Override
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
brushIdx = (int)leis.readUInt();

brushStyle = HwmfBrushStyle.valueOf((int)leis.readUInt());
colorRef = new HwmfColorRef();
int size = colorRef.init(leis);
brushHatch = HwmfHatchStyle.valueOf((int)leis.readUInt());
return size+3*LittleEndianConsts.INT_SIZE;

}
}

/**
* The EMR_DELETEOBJECT record deletes a graphics object, which is specified by its index
* in the EMF Object Table
*/
public static class EmfDeleteObject extends HwmfMisc.WmfDeleteObject implements HemfRecord {

@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.deleteobject;
}

@Override
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
objectIndex = (int)leis.readUInt();
return LittleEndianConsts.INT_SIZE;
}
}

/** The EMR_CREATEPEN record defines a logical pen for graphics operations. */
public static class EmfCreatePen extends HwmfMisc.WmfCreatePenIndirect implements HemfRecord {
/**
* A 32-bit unsigned integer that specifies the index of the logical palette object
* in the EMF Object Table. This index MUST be saved so that this object can be
* reused or modified.
*/
protected int penIndex;

@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.createPen;
}

@Override
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
penIndex = (int)leis.readUInt();

// A 32-bit unsigned integer that specifies the PenStyle.
// The value MUST be defined from the PenStyle enumeration table
penStyle = HwmfPenStyle.valueOf((int)leis.readUInt());

int widthX = leis.readInt();
int widthY = leis.readInt();
dimension.setSize(widthX, widthY);

int size = colorRef.init(leis);

return size + 4*LittleEndianConsts.INT_SIZE;
}
}

public static class EmfExtCreatePen extends EmfCreatePen {
protected HwmfBrushStyle brushStyle;
protected HwmfHatchStyle hatchStyle;

protected int[] styleEntry;

protected final HwmfBitmapDib bitmap = new HwmfBitmapDib();


@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.extCreatePen;
}

@Override
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
final int startIdx = leis.getReadIndex();

penIndex = (int)leis.readUInt();

// A 32-bit unsigned integer that specifies the offset from the start of this
// record to the DIB header, if the record contains a DIB.
int offBmi = (int)leis.readUInt();

// A 32-bit unsigned integer that specifies the size of the DIB header, if the
// record contains a DIB.
int cbBmi = (int)leis.readUInt();

// A 32-bit unsigned integer that specifies the offset from the start of this
// record to the DIB bits, if the record contains a DIB.
int offBits = (int)leis.readUInt();

// A 32-bit unsigned integer that specifies the size of the DIB bits, if the record
// contains a DIB.
int cbBits = (int)leis.readUInt();

// A 32-bit unsigned integer that specifies the PenStyle.
// The value MUST be defined from the PenStyle enumeration table
penStyle = HwmfPenStyle.valueOf((int)leis.readUInt());

// A 32-bit unsigned integer that specifies the width of the line drawn by the pen.
// If the pen type in the PenStyle field is PS_GEOMETRIC, this value is the width in logical
// units; otherwise, the width is specified in device units. If the pen type in the PenStyle field is
// PS_COSMETIC, this value MUST be 0x00000001.
long width = leis.readUInt();
dimension.setSize(width, 0);

// A 32-bit unsigned integer that specifies a brush style for the pen from the WMF BrushStyle enumeration
//
// If the pen type in the PenStyle field is PS_GEOMETRIC, this value MUST be either BS_SOLID or BS_HATCHED.
// The value of this field can be BS_NULL, but only if the line style specified in PenStyle is PS_NULL.
// The BS_NULL style SHOULD be used to specify a brush that has no effect
brushStyle = HwmfBrushStyle.valueOf((int)leis.readUInt());

int size = 8 * LittleEndianConsts.INT_SIZE;

size += colorRef.init(leis);

hatchStyle = HwmfHatchStyle.valueOf(leis.readInt());

// The number of elements in the array specified in the StyleEntry
// field. This value SHOULD be zero if PenStyle does not specify PS_USERSTYLE.
final int numStyleEntries = (int)leis.readUInt();
size += 2*LittleEndianConsts.INT_SIZE;

assert(numStyleEntries == 0 || penStyle.getLineDash() == HwmfLineDash.USERSTYLE);

// An optional array of 32-bit unsigned integers that defines the lengths of
// dashes and gaps in the line drawn by this pen, when the value of PenStyle is
// PS_USERSTYLE line style for the pen. The array contains a number of entries specified by
// NumStyleEntries, but it is used as if it repeated indefinitely.
// The first entry in the array specifies the length of the first dash. The second entry specifies
// the length of the first gap. Thereafter, lengths of dashes and gaps alternate.
// If the pen type in the PenStyle field is PS_GEOMETRIC, the lengths are specified in logical
// units; otherwise, the lengths are specified in device units.

styleEntry = new int[numStyleEntries];

for (int i=0; i<numStyleEntries; i++) {
styleEntry[i] = (int)leis.readUInt();
}

size += numStyleEntries * LittleEndianConsts.INT_SIZE;

size += readBitmap(leis, bitmap, startIdx, offBmi, cbBmi, offBits, cbBits);

return size;
}
}

/**
* 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);
}
}
}

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

@@ -0,0 +1,138 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */

package org.apache.poi.hemf.record.emf;

import java.io.IOException;

import org.apache.poi.hwmf.record.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;
}
}

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

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

/**
* 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/HemfRecord.java → src/scratchpad/src/org/apache/poi/hemf/record/emf/HemfRecord.java View File

@@ -15,27 +15,32 @@
limitations under the License.
==================================================================== */

package org.apache.poi.hemf.record;
package org.apache.poi.hemf.record.emf;


import java.io.IOException;

import org.apache.poi.hemf.draw.HemfGraphics;
import org.apache.poi.util.Internal;
import org.apache.poi.util.LittleEndianInputStream;

@Internal
public interface HemfRecord {

HemfRecordType getRecordType();
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
*
* @throws IOException when the inputstream is malformed
*/
long init(LittleEndianInputStream leis, long recordId, long recordSize) throws IOException;

long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException;

default void draw(HemfGraphics ctx) {}
}

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

@@ -0,0 +1,82 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */

package org.apache.poi.hemf.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;
}
long recordId = stream.readUInt();
long recordSize = stream.readUInt();

HemfRecordType type = HemfRecordType.getById(recordId);
if (type == null) {
throw new RecordFormatException("Undefined record of type:"+recordId);
}
final HemfRecord record = type.constructor.get();

try {
long remBytes = recordSize-HEADER_SIZE;
long readBytes = record.init(stream, remBytes, recordId);
assert (readBytes <= remBytes);
stream.skipFully((int)(remBytes-readBytes));
} catch (IOException|RuntimeException e) {
throw new RecordFormatException(e);
}

return record;
}

@Override
public void remove() {
throw new UnsupportedOperationException("Remove not supported");
}

}

+ 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, UnimplementedHemfRecord::new),
eof(0x0000000E, HemfMisc.EmfEof::new),
setPixelV(0x0000000F, HemfDraw.EmfSetPixelV::new),
setMapperFlags(0x00000010, HemfMisc.EmfSetMapperFlags::new),
setMapMode(0x00000011, HemfMisc.EmfSetMapMode::new),
setBkMode(0x00000012, HemfMisc.EmfSetBkMode::new),
setPolyfillMode(0x00000013, HemfFill.EmfSetPolyfillMode::new),
setRop2(0x00000014, HemfMisc.EmfSetRop2::new),
setStretchBltMode(0x00000015, HemfMisc.EmfSetStretchBltMode::new),
setTextAlign(0x00000016, HemfText.EmfSetTextAlign::new),
setcoloradjustment(0x00000017, UnimplementedHemfRecord::new),
setTextColor(0x00000018, HemfText.SetTextColor::new),
setBkColor(0x00000019, HemfMisc.EmfSetBkColor::new),
setOffsetClipRgn(0x0000001A, HemfWindowing.EmfSetOffsetClipRgn::new),
setMoveToEx(0x0000001B, HemfDraw.EmfSetMoveToEx::new),
setmetargn(0x0000001C, UnimplementedHemfRecord::new),
setExcludeClipRect(0x0000001D, HemfWindowing.EmfSetExcludeClipRect::new),
setIntersectClipRect(0x0000001E, HemfWindowing.EmfSetIntersectClipRect::new),
scaleViewportExtEx(0x0000001F, HemfWindowing.EmfScaleViewportExtEx::new),
scaleWindowExtEx(0x00000020, HemfWindowing.EmfScaleWindowExtEx::new),
saveDc(0x00000021, HemfMisc.EmfSaveDc::new),
restoreDc(0x00000022, HemfMisc.EmfRestoreDc::new),
setworldtransform(0x00000023, UnimplementedHemfRecord::new),
modifyworldtransform(0x00000024, UnimplementedHemfRecord::new),
selectObject(0x00000025, HemfDraw.EmfSelectObject::new),
createPen(0x00000026, HemfMisc.EmfCreatePen::new),
createBrushIndirect(0x00000027, HemfMisc.EmfCreateBrushIndirect::new),
deleteobject(0x00000028, HemfMisc.EmfDeleteObject::new),
anglearc(0x00000029, UnimplementedHemfRecord::new),
ellipse(0x0000002A, HemfDraw.EmfEllipse::new),
rectangle(0x0000002B, HemfDraw.EmfRectangle::new),
roundRect(0x0000002C, HemfDraw.EmfRoundRect::new),
arc(0x0000002D, HemfDraw.EmfArc::new),
chord(0x0000002E, HemfDraw.EmfChord::new),
pie(0x0000002F, HemfDraw.EmfPie::new),
selectPalette(0x00000030, HemfPalette.EmfSelectPalette::new),
createPalette(0x00000031, HemfPalette.EmfCreatePalette::new),
setPaletteEntries(0x00000032, HemfPalette.EmfSetPaletteEntries::new),
resizePalette(0x00000033, HemfPalette.EmfResizePalette::new),
realizePalette(0x0000034, HemfPalette.EmfRealizePalette::new),
extFloodFill(0x00000035, HemfFill.EmfExtFloodFill::new),
lineTo(0x00000036, HemfDraw.EmfLineTo::new),
arcTo(0x00000037, HemfDraw.EmfArcTo::new),
polyDraw(0x00000038, HemfDraw.EmfPolyDraw::new),
setarcdirection(0x00000039, UnimplementedHemfRecord::new),
setMiterLimit(0x0000003A, HemfMisc.EmfSetMiterLimit::new),
beginpath(0x0000003B, UnimplementedHemfRecord::new),
endpath(0x0000003C, UnimplementedHemfRecord::new),
closefigure(0x0000003D, UnimplementedHemfRecord::new),
fillpath(0x0000003E, UnimplementedHemfRecord::new),
strokeandfillpath(0x0000003F, UnimplementedHemfRecord::new),
strokepath(0x00000040, UnimplementedHemfRecord::new),
flattenpath(0x00000041, UnimplementedHemfRecord::new),
widenpath(0x00000042, UnimplementedHemfRecord::new),
selectclippath(0x00000043, UnimplementedHemfRecord::new),
abortpath(0x00000044, UnimplementedHemfRecord::new),
// no 45 ?!
comment(0x00000046, HemfComment.EmfComment::new),
fillRgn(0x00000047, HemfFill.EmfFillRgn::new),
frameRgn(0x00000048, HemfFill.EmfFrameRgn::new),
invertRgn(0x00000049, HemfFill.EmfInvertRgn::new),
paintRgn(0x0000004A, HemfFill.EmfPaintRgn::new),
extSelectClipRgn(0x0000004B, HemfFill.EmfExtSelectClipRgn::new),
bitBlt(0x0000004C, HemfFill.EmfBitBlt::new),
stretchBlt(0x0000004D, HemfFill.EmfStretchBlt::new),
maskblt(0x0000004E, UnimplementedHemfRecord::new),
plgblt(0x0000004F, UnimplementedHemfRecord::new),
setDiBitsToDevice(0x00000050, HemfFill.EmfSetDiBitsToDevice::new),
stretchdibits(0x00000051, UnimplementedHemfRecord::new),
extCreateFontIndirectW(0x00000052, HemfText.ExtCreateFontIndirectW::new),
exttextouta(0x00000053, HemfText.ExtTextOutA::new),
exttextoutw(0x00000054, HemfText.ExtTextOutW::new),
polyBezier16(0x00000055, HemfDraw.EmfPolyBezier16::new),
polygon16(0x00000056, HemfDraw.EmfPolygon16::new),
polyline16(0x00000057, HemfDraw.EmfPolyline16::new),
polyBezierTo16(0x00000058, HemfDraw.EmfPolyBezierTo16::new),
polylineTo16(0x00000059, HemfDraw.EmfPolylineTo16::new),
polyPolyline16(0x0000005A, HemfDraw.EmfPolyPolyline16::new),
polyPolygon16(0x0000005B, HemfDraw.EmfPolyPolygon16::new),
polyDraw16(0x0000005C, HemfDraw.EmfPolyDraw16::new),
createmonobrush16(0x0000005D, UnimplementedHemfRecord::new),
createdibpatternbrushpt(0x0000005E, UnimplementedHemfRecord::new),
extCreatePen(0x0000005F, HemfMisc.EmfExtCreatePen::new),
polytextouta(0x00000060, HemfText.PolyTextOutA::new),
polytextoutw(0x00000061, HemfText.PolyTextOutW::new),
seticmmode(0x00000062, UnimplementedHemfRecord::new),
createcolorspace(0x00000063, UnimplementedHemfRecord::new),
setcolorspace(0x00000064, UnimplementedHemfRecord::new),
deletecolorspace(0x00000065, UnimplementedHemfRecord::new),
glsrecord(0x00000066, UnimplementedHemfRecord::new),
glsboundedrecord(0x00000067, UnimplementedHemfRecord::new),
pixelformat(0x00000068, UnimplementedHemfRecord::new),
drawescape(0x00000069, UnimplementedHemfRecord::new),
extescape(0x0000006A, UnimplementedHemfRecord::new),
// no 6b ?!
smalltextout(0x0000006C, UnimplementedHemfRecord::new),
forceufimapping(0x0000006D, UnimplementedHemfRecord::new),
namedescape(0x0000006E, UnimplementedHemfRecord::new),
colorcorrectpalette(0x0000006F, UnimplementedHemfRecord::new),
seticmprofilea(0x00000070, UnimplementedHemfRecord::new),
seticmprofilew(0x00000071, UnimplementedHemfRecord::new),
alphaBlend(0x00000072, HemfFill.EmfAlphaBlend::new),
setlayout(0x00000073, UnimplementedHemfRecord::new),
transparentblt(0x00000074, UnimplementedHemfRecord::new),
// no 75 ?!
gradientfill(0x00000076, UnimplementedHemfRecord::new),
setlinkdufis(0x00000077, UnimplementedHemfRecord::new),
settextjustification(0x00000078, HemfText.SetTextJustification::new),
colormatchtargetw(0x00000079, UnimplementedHemfRecord::new),
createcolorspacew(0x0000007A, UnimplementedHemfRecord::new);


public final long id;
public final Supplier<? 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;
}
}

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

@@ -0,0 +1,317 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */

package org.apache.poi.hemf.record.emf;

import static java.nio.charset.StandardCharsets.UTF_16LE;
import static org.apache.poi.hemf.record.emf.HemfDraw.readRectL;
import static org.apache.poi.hemf.record.emf.HemfDraw.readDimensionFloat;
import static org.apache.poi.hemf.record.emf.HemfDraw.readPointL;

import java.awt.geom.Dimension2D;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;

import org.apache.poi.hwmf.record.HwmfColorRef;
import org.apache.poi.hwmf.record.HwmfText;
import org.apache.poi.hwmf.record.HwmfText.WmfSetTextAlign;
import org.apache.poi.util.Dimension2DDouble;
import org.apache.poi.util.IOUtils;
import org.apache.poi.util.Internal;
import org.apache.poi.util.LittleEndianConsts;
import org.apache.poi.util.LittleEndianInputStream;
import org.apache.poi.util.RecordFormatException;

/**
* Container class to gather all text-related commands
* This is starting out as read only, and very little is actually
* implemented at this point!
*/
@Internal
public class HemfText {

private static final int MAX_RECORD_LENGTH = 1_000_000;

public enum EmfGraphicsMode {
GM_COMPATIBLE, GM_ADVANCED
}

public static class ExtTextOutA implements HemfRecord {

protected final Rectangle2D boundsIgnored = new Rectangle2D.Double();

protected EmfGraphicsMode graphicsMode;

/**
* The scale factor to apply along the X/Y axis to convert from page space units to .01mm units.
* This SHOULD be used only if the graphics mode specified by iGraphicsMode is GM_COMPATIBLE.
*/
protected final Dimension2D scale = new Dimension2DDouble();

protected final EmrTextObject textObject;

public ExtTextOutA() {
this(false);
}

protected ExtTextOutA(boolean isUnicode) {
textObject = new EmrTextObject(isUnicode);
}

@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.exttextouta;
}

@Override
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
if (recordSize < 0 || Integer.MAX_VALUE <= recordSize) {
throw new RecordFormatException("recordSize must be a positive integer (0-0x7FFFFFFF)");
}

// A WMF RectL object. It is not used and MUST be ignored on receipt.
long size = readRectL(leis, boundsIgnored);

// A 32-bit unsigned integer that specifies the graphics mode from the GraphicsMode enumeration
graphicsMode = EmfGraphicsMode.values()[leis.readInt()-1];
size += LittleEndianConsts.INT_SIZE;

size += readDimensionFloat(leis, scale);

// guarantee to read the rest of the EMRTextObjectRecord
size += textObject.init(leis, recordSize, (int)size);

return size;
}

/**
*
* To be implemented! We need to get the current character set
* from the current font for {@link ExtTextOutA},
* which has to be tracked in the playback device.
*
* For {@link ExtTextOutW}, the charset is "UTF-16LE"
*
* @param charset the charset to be used to decode the character bytes
* @return text from this text element
* @throws IOException
*/
public String getText(Charset charset) throws IOException {
return textObject.getText(charset);
}

/**
*
* @return the x offset for the EmrTextObject
*/
public EmrTextObject getTextObject() {
return textObject;
}

public EmfGraphicsMode getGraphicsMode() {
return graphicsMode;
}

public Dimension2D getScale() {
return scale;
}
}

public static class ExtTextOutW extends ExtTextOutA {

public ExtTextOutW() {
super(true);
}

@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.exttextoutw;
}

public String getText() throws IOException {
return getText(UTF_16LE);
}
}

/**
* The EMR_SETTEXTALIGN record specifies text alignment.
*/
public static class EmfSetTextAlign extends WmfSetTextAlign implements HemfRecord {
@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.setTextAlign;
}

@Override
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
/**
* A 32-bit unsigned integer that specifies text alignment by using a mask of text alignment flags.
* These are either WMF TextAlignmentMode Flags for text with a horizontal baseline,
* or WMF VerticalTextAlignmentMode Flags for text with a vertical baseline.
* Only one value can be chosen from those that affect horizontal and vertical alignment.
*/
textAlignmentMode = (int)leis.readUInt();
return LittleEndianConsts.INT_SIZE;
}
}

/**
* The EMR_SETTEXTCOLOR record defines the current text color.
*/
public static class SetTextColor implements HemfRecord {
/** A WMF ColorRef object that specifies the text color value. */
private final HwmfColorRef colorRef = new HwmfColorRef();

@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.setTextColor;
}

@Override
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
return colorRef.init(leis);
}
}

public static class EmrTextObject extends HwmfText.WmfExtTextOut {
protected final boolean isUnicode;
protected final List<Integer> outputDx = new ArrayList<>();

public EmrTextObject(boolean isUnicode) {
super(new EmfExtTextOutOptions());
this.isUnicode = isUnicode;
}

@Override
public int init(LittleEndianInputStream leis, final long recordSize, final int offset) throws IOException {
// A WMF PointL object that specifies the coordinates of the reference point used to position the string.
// The reference point is defined by the last EMR_SETTEXTALIGN record.
// If no such record has been set, the default alignment is TA_LEFT,TA_TOP.
long size = readPointL(leis, reference);
// A 32-bit unsigned integer that specifies the number of characters in the string.
stringLength = (int)leis.readUInt();
// A 32-bit unsigned integer that specifies the offset to the output string, in bytes,
// from the start of the record in which this object is contained.
// This value MUST be 8- or 16-bit aligned, according to the character format.
int offString = (int)leis.readUInt();
size += 2*LittleEndianConsts.INT_SIZE;

size += options.init(leis);
// An optional WMF RectL object that defines a clipping and/or opaquing rectangle in logical units.
// This rectangle is applied to the text output performed by the containing record.
if (options.isClipped() || options.isOpaque()) {
size += readRectL(leis, bounds);
}

// A 32-bit unsigned integer that specifies the offset to an intercharacter spacing array, in bytes,
// from the start of the record in which this object is contained. This value MUST be 32-bit aligned.
int offDx = (int)leis.readUInt();
size += LittleEndianConsts.INT_SIZE;

int undefinedSpace1 = (int)(offString-offset-size-2*LittleEndianConsts.INT_SIZE);
assert (undefinedSpace1 >= 0);
leis.skipFully(undefinedSpace1);
size += undefinedSpace1;

rawTextBytes = IOUtils.safelyAllocate(stringLength*(isUnicode?2:1), MAX_RECORD_LENGTH);
leis.readFully(rawTextBytes);
size += rawTextBytes.length;

outputDx.clear();
if (offDx > 0) {
int undefinedSpace2 = (int) (offDx - offset - size - 2 * LittleEndianConsts.INT_SIZE);
assert (undefinedSpace2 >= 0);
leis.skipFully(undefinedSpace2);
size += undefinedSpace2;

// An array of 32-bit unsigned integers that specify the output spacing between the origins of adjacent
// character cells in logical units. The location of this field is specified by the value of offDx
// in bytes from the start of this record. If spacing is defined, this field contains the same number
// of values as characters in the output string.
//
// If the Options field of the EmrText object contains the ETO_PDY flag, then this buffer
// contains twice as many values as there are characters in the output string, one
// horizontal and one vertical offset for each, in that order.
//
// If ETO_RTLREADING is specified, characters are laid right to left instead of left to right.
// No other options affect the interpretation of this field.
while (size < recordSize) {
outputDx.add((int) leis.readUInt());
size += LittleEndianConsts.INT_SIZE;
}
}

return (int)size;
}
}


public static class ExtCreateFontIndirectW extends HwmfText.WmfCreateFontIndirect
implements HemfRecord {
int fontIdx;

public ExtCreateFontIndirectW() {
super(new HemfFont());
}

@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.extCreateFontIndirectW;
}

@Override
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
// A 32-bit unsigned integer that specifies the index of the logical font object
// in the EMF Object Table
fontIdx = (int)leis.readUInt();
int size = font.init(leis, (int)(recordSize-LittleEndianConsts.INT_SIZE));
return size+LittleEndianConsts.INT_SIZE;
}
}

public static class EmfExtTextOutOptions extends HwmfText.WmfExtTextOutOptions {
@Override
public int init(LittleEndianInputStream leis) {
// A 32-bit unsigned integer that specifies how to use the rectangle specified in the Rectangle field.
// This field can be a combination of more than one ExtTextOutOptions enumeration
flag = (int)leis.readUInt();
return LittleEndianConsts.INT_SIZE;
}
}

public static class SetTextJustification extends UnimplementedHemfRecord {

}

/**
* Needs to be implemented. Couldn't find example.
*/
public static class PolyTextOutA extends UnimplementedHemfRecord {

}

/**
* Needs to be implemented. Couldn't find example.
*/
public static class PolyTextOutW extends UnimplementedHemfRecord {

}

}

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

@@ -0,0 +1,200 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */

package org.apache.poi.hemf.record.emf;

import java.io.IOException;

import org.apache.poi.hwmf.record.HwmfWindowing;
import org.apache.poi.util.LittleEndianConsts;
import org.apache.poi.util.LittleEndianInputStream;

public class HemfWindowing {

/**
* The EMR_SETWINDOWEXTEX record defines the window extent.
*/
public static class EmfSetWindowExtEx extends HwmfWindowing.WmfSetWindowExt implements HemfRecord {
@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.setWindowExtEx;
}

@Override
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
// cx (4 bytes): A 32-bit unsigned integer that defines the x-coordinate of the point.
width = (int)leis.readUInt();
// cy (4 bytes): A 32-bit unsigned integer that defines the y-coordinate of the point.
height = (int)leis.readUInt();

return 2*LittleEndianConsts.INT_SIZE;
}
}

/**
* The EMR_SETWINDOWORGEX record defines the window origin.
*/
public static class EmfSetWindowOrgEx extends HwmfWindowing.WmfSetWindowOrg implements HemfRecord {
@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.setWindowOrgEx;
}

@Override
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
// x (4 bytes): A 32-bit signed integer that defines the horizontal (x) coordinate of the point.
x = leis.readInt();
// y (4 bytes): A 32-bit signed integer that defines the vertical (y) coordinate of the point.
y = leis.readInt();

return 2*LittleEndianConsts.INT_SIZE;
}
}

/**
* The EMR_SETVIEWPORTEXTEX record defines the viewport extent.
*/
public static class EmfSetViewportExtEx extends HwmfWindowing.WmfSetViewportExt implements HemfRecord {
@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.setViewportExtEx;
}

@Override
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
// cx (4 bytes): A 32-bit unsigned integer that defines the x-coordinate of the point.
width = (int)leis.readUInt();
// cy (4 bytes): A 32-bit unsigned integer that defines the y-coordinate of the point.
height = (int)leis.readUInt();

return 2*LittleEndianConsts.INT_SIZE;
}
}

/**
* The EMR_SETVIEWPORTORGEX record defines the viewport origin.
*/
public static class EmfSetViewportOrgEx extends HwmfWindowing.WmfSetViewportOrg implements HemfRecord {
@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.setViewportOrgEx;
}

@Override
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
// x (4 bytes): A 32-bit signed integer that defines the horizontal (x) coordinate of the point.
x = leis.readInt();
// y (4 bytes): A 32-bit signed integer that defines the vertical (y) coordinate of the point.
y = leis.readInt();

return 2*LittleEndianConsts.INT_SIZE;
}
}

/**
* The EMR_OFFSETCLIPRGN record moves the current clipping region in the playback device context
* by the specified offsets.
*/
public static class EmfSetOffsetClipRgn extends HwmfWindowing.WmfOffsetClipRgn implements HemfRecord {
@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.setOffsetClipRgn;
}

@Override
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
// x (4 bytes): A 32-bit signed integer that defines the horizontal (x) coordinate of the point.
xOffset = leis.readInt();
// y (4 bytes): A 32-bit signed integer that defines the vertical (y) coordinate of the point.
yOffset = leis.readInt();

return 2*LittleEndianConsts.INT_SIZE;
}
}

/**
* The EMR_EXCLUDECLIPRECT record specifies a new clipping region that consists of the existing
* clipping region minus the specified rectangle.
*/
public static class EmfSetExcludeClipRect extends HwmfWindowing.WmfExcludeClipRect implements HemfRecord {
@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.setExcludeClipRect;
}

@Override
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
return HemfDraw.readRectL(leis, bounds);
}
}

/**
* The EMR_INTERSECTCLIPRECT record specifies a new clipping region from the intersection of the
* current clipping region and the specified rectangle.
*/
public static class EmfSetIntersectClipRect extends HwmfWindowing.WmfIntersectClipRect implements HemfRecord {
@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.setIntersectClipRect;
}

@Override
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
return HemfDraw.readRectL(leis, bounds);
}
}

/**
* The EMR_SCALEVIEWPORTEXTEX record respecifies the viewport for a device context by using the
* ratios formed by the specified multiplicands and divisors.
*/
public static class EmfScaleViewportExtEx extends HwmfWindowing.WmfScaleViewportExt implements HemfRecord {
@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.scaleViewportExtEx;
}

@Override
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
xNum = leis.readInt();
xDenom = leis.readInt();
yNum = leis.readInt();
yDenom = leis.readInt();
return 4*LittleEndianConsts.INT_SIZE;
}
}

/**
* The EMR_SCALEWINDOWEXTEX record respecifies the window for a playback device context by
* using the ratios formed by the specified multiplicands and divisors.
*/
public static class EmfScaleWindowExtEx extends HwmfWindowing.WmfScaleWindowExt implements HemfRecord {
@Override
public HemfRecordType getEmfRecordType() {
return HemfRecordType.scaleWindowExtEx;
}

@Override
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
xNum = leis.readInt();
xDenom = leis.readInt();
yNum = leis.readInt();
yDenom = leis.readInt();
return 4*LittleEndianConsts.INT_SIZE;
}
}
}

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;
@@ -33,12 +33,12 @@ public class UnimplementedHemfRecord implements HemfRecord {
}

@Override
public HemfRecordType getRecordType() {
public HemfRecordType getEmfRecordType() {
return HemfRecordType.getById(recordId);
}

@Override
public long init(LittleEndianInputStream leis, long recordId, long recordSize) throws IOException {
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
this.recordId = recordId;
long skipped = IOUtils.skipFully(leis, recordSize);
if (skipped < recordSize) {

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 {
@@ -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/hemfplus/record/HemfPlusRecord.java → src/scratchpad/src/org/apache/poi/hemf/record/emfplus/HemfPlusRecord.java View File

@@ -15,12 +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.hemf.record.emf.HemfRecordType;
import org.apache.poi.util.Internal;
import org.apache.poi.util.LittleEndianInputStream;

@Internal
public interface HemfPlusRecord {
@@ -30,15 +32,17 @@ public interface HemfPlusRecord {
int getFlags();

/**
* Init record from stream
*
* @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
* @param leis the little endian input stream
* @param dataSize the size limit for this record
* @param recordId the id of the {@link HemfPlusRecordType}
* @param flags the record flags
*
* @return count of processed bytes
*
* @throws IOException when the inputstream is malformed
*/
void init(byte[] dataBytes, int recordId, int flags) 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.getRecordType()) {
return null;
}
// A 16-bit unsigned integer that identifies this record type
int recordId = leis.readUShort();
// A 16-bit unsigned integer that provides information about how the operation is
// to be performed, and about the structure of the record.
int flags = leis.readUShort();
// A 32-bit unsigned integer that specifies the 32-bit-aligned size of the entire
// record in bytes, including the 12-byte record header and record-specific data.
int recordSize = (int)leis.readUInt();
// A 32-bit unsigned integer that specifies the 32-bit-aligned number of bytes of data
// in the record-specific data that follows. This number does not include the size of
// the invariant part of this record.
int dataSize = (int)leis.readUInt();

HemfPlusRecordType type = HemfPlusRecordType.getById(recordId);
if (type == null) {
throw new RecordFormatException("Undefined record of type:"+recordId);
}
final HemfPlusRecord record = type.constructor.get();

try {
long readBytes = record.init(leis, dataSize, recordId, flags);
assert (readBytes <= recordSize-12);
leis.skipFully((int)(recordSize-12-readBytes));
} catch (IOException e) {
throw new RecordFormatException(e);
}

return record;
}

@Override
public void remove() {
throw new UnsupportedOperationException("Remove not supported");
}

}

+ 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,17 +15,21 @@
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 long recordId;
private int flags;
private byte[] recordBytes;

@@ -40,14 +44,16 @@ public class UnimplementedHemfPlusRecord implements HemfPlusRecord {
}

@Override
public void init(byte[] recordBytes, int recordId, int flags) throws IOException {
public long init(LittleEndianInputStream leis, long dataSize, long recordId, int flags) throws IOException {
this.recordId = 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;
}
}
}

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

@@ -0,0 +1,77 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */

package org.apache.poi.hemf.usermodel;


import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Spliterator;
import java.util.function.Consumer;

import org.apache.poi.hemf.record.emf.HemfHeader;
import org.apache.poi.hemf.record.emf.HemfRecord;
import org.apache.poi.hemf.record.emf.HemfRecordIterator;
import org.apache.poi.util.Internal;
import org.apache.poi.util.LittleEndianInputStream;

/**
* Read-only EMF extractor. Lots remain
*/
@Internal
public class HemfPicture implements Iterable<HemfRecord> {

private final LittleEndianInputStream stream;
private final List<HemfRecord> records = new ArrayList<>();

public HemfPicture(InputStream is) throws IOException {
this(new LittleEndianInputStream(is));
}

public HemfPicture(LittleEndianInputStream is) throws IOException {
stream = is;
}

public HemfHeader getHeader() {
return (HemfHeader)getRecords().get(0);
}

public List<HemfRecord> getRecords() {
if (records.isEmpty()) {
new HemfRecordIterator(stream).forEachRemaining(records::add);
}
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);
}
}

+ 41
- 17
src/scratchpad/src/org/apache/poi/hwmf/draw/HwmfDrawProperties.java View File

@@ -43,32 +43,48 @@ public class HwmfDrawProperties {
private final Rectangle2D window;
private Rectangle2D viewport;
private final Point2D location;
private HwmfMapMode mapMode = HwmfMapMode.MM_ANISOTROPIC;
private HwmfColorRef backgroundColor = new HwmfColorRef(Color.BLACK);
private HwmfBrushStyle brushStyle = HwmfBrushStyle.BS_SOLID;
private HwmfColorRef brushColor = new HwmfColorRef(Color.BLACK);
private HwmfHatchStyle brushHatch = HwmfHatchStyle.HS_HORIZONTAL;
private HwmfMapMode mapMode;
private HwmfColorRef backgroundColor;
private HwmfBrushStyle brushStyle;
private HwmfColorRef brushColor;
private HwmfHatchStyle brushHatch;
private BufferedImage brushBitmap;
private double penWidth = 1;
private HwmfPenStyle penStyle = HwmfPenStyle.valueOf(0);
private HwmfColorRef penColor = new HwmfColorRef(Color.BLACK);
private double penMiterLimit = 10;
private HwmfBkMode bkMode = HwmfBkMode.OPAQUE;
private HwmfPolyfillMode polyfillMode = HwmfPolyfillMode.WINDING;
private double penWidth;
private HwmfPenStyle penStyle;
private HwmfColorRef penColor;
private double penMiterLimit;
private HwmfBkMode bkMode;
private HwmfPolyfillMode polyfillMode;
private Shape region;
private List<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;

public HwmfDrawProperties() {
window = new Rectangle2D.Double(0, 0, 1, 1);
viewport = null;
location = new Point2D.Double(0,0);
mapMode = HwmfMapMode.MM_ANISOTROPIC;
backgroundColor = new HwmfColorRef(Color.BLACK);
brushStyle = HwmfBrushStyle.BS_SOLID;
brushColor = new HwmfColorRef(Color.BLACK);
brushHatch = HwmfHatchStyle.HS_HORIZONTAL;
penWidth = 1;
penStyle = HwmfPenStyle.valueOf(0);
penColor = new HwmfColorRef(Color.BLACK);
penMiterLimit = 10;
bkMode = HwmfBkMode.OPAQUE;
polyfillMode = HwmfPolyfillMode.WINDING;
textColor = new HwmfColorRef(Color.BLACK);
textAlignLatin = HwmfTextAlignment.LEFT;
textVAlignLatin = HwmfTextVerticalAlignment.TOP;
textAlignAsian = HwmfTextAlignment.RIGHT;
textVAlignAsian = HwmfTextVerticalAlignment.TOP;
}
public HwmfDrawProperties(HwmfDrawProperties other) {
@@ -86,7 +102,7 @@ public class HwmfDrawProperties {
WritableRaster raster = other.brushBitmap.copyData(null);
this.brushBitmap = new BufferedImage(cm, raster, isAlphaPremultiplied, null);
}
this.penWidth = 1;
this.penWidth = other.penWidth;
this.penStyle = (other.penStyle == null) ? null : other.penStyle.clone();
this.penColor = (other.penColor == null) ? null : other.penColor.clone();
this.penMiterLimit = other.penMiterLimit;
@@ -101,6 +117,10 @@ public class HwmfDrawProperties {
this.paletteOffset = other.paletteOffset;
this.font = other.font;
this.textColor = (other.textColor == null) ? null : other.textColor.clone();
this.textAlignLatin = other.textAlignLatin;
this.textVAlignLatin = other.textVAlignLatin;
this.textAlignAsian = other.textAlignAsian;
this.textVAlignAsian = other.textVAlignAsian;
}
public void setViewportExt(double width, double height) {
@@ -149,6 +169,10 @@ public class HwmfDrawProperties {
location.setLocation(x, y);
}

public void setLocation(Point2D point) {
location.setLocation(point);
}

public Point2D getLocation() {
return (Point2D)location.clone();
}

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

+ 139
- 233
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfDraw.java View File

@@ -41,31 +41,21 @@ public class HwmfDraw {
*/
public static class WmfMoveTo implements HwmfRecord {

/**
* A 16-bit signed integer that defines the y-coordinate, in logical units.
*/
private int y;

/**
* A 16-bit signed integer that defines the x-coordinate, in logical units.
*/
private int x;
protected final Point2D point = new Point2D.Double();

@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.moveTo;
}

@Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
y = leis.readShort();
x = leis.readShort();
return 2*LittleEndianConsts.SHORT_SIZE;
return readPointS(leis, point);
}

@Override
public void draw(HwmfGraphics ctx) {
ctx.getProperties().setLocation(x, y);
ctx.getProperties().setLocation(point);
}
}

@@ -75,36 +65,24 @@ public class HwmfDraw {
*/
public static class WmfLineTo implements HwmfRecord {

/**
* A 16-bit signed integer that defines the vertical component of the drawing
* destination position, in logical units.
*/
private int y;

/**
* A 16-bit signed integer that defines the horizontal component of the drawing
* destination position, in logical units.
*/
private int x;
protected final Point2D point = new Point2D.Double();

@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.lineTo;
}

@Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
y = leis.readShort();
x = leis.readShort();
return 2*LittleEndianConsts.SHORT_SIZE;
return readPointS(leis, point);
}

@Override
public void draw(HwmfGraphics ctx) {
Point2D start = ctx.getProperties().getLocation();
Line2D line = new Line2D.Double(start.getX(), start.getY(), x, y);
Line2D line = new Line2D.Double(start, point);
ctx.draw(line);
ctx.getProperties().setLocation(x, y);
ctx.getProperties().setLocation(point);
}
}

@@ -115,10 +93,10 @@ public class HwmfDraw {
*/
public static class WmfPolygon implements HwmfRecord {

private Path2D poly = new Path2D.Double();
protected Path2D poly = new Path2D.Double();
@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.polygon;
}

@@ -146,16 +124,26 @@ public class HwmfDraw {

@Override
public void draw(HwmfGraphics ctx) {
Path2D shape = getShape();
// shape.closePath();
Path2D p = (Path2D)shape.clone();
Path2D p = getShape(ctx);
// don't close the path
p.setWindingRule(getWindingRule(ctx));
ctx.fill(p);
if (isFill()) {
ctx.fill(p);
} else {
ctx.draw(p);
}
}

protected Path2D getShape() {
protected Path2D getShape(HwmfGraphics ctx) {
return (Path2D)poly.clone();
}

/**
* @return true, if the shape should be filled
*/
protected boolean isFill() {
return true;
}
}

/**
@@ -165,16 +153,13 @@ public class HwmfDraw {
public static class WmfPolyline extends WmfPolygon {

@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.polyline;
}

@Override
public void draw(HwmfGraphics ctx) {
Path2D shape = getShape();
Path2D p = (Path2D)shape.clone();
p.setWindingRule(getWindingRule(ctx));
ctx.draw(p);
protected boolean isFill() {
return false;
}
}

@@ -184,48 +169,21 @@ public class HwmfDraw {
* are defined in the playback device context.
*/
public static class WmfEllipse implements HwmfRecord {
/**
* A 16-bit signed integer that defines the y-coordinate, in logical units, of
* the lower-right corner of the bounding rectangle.
*/
private int bottomRect;
/**
* A 16-bit signed integer that defines the x-coordinate, in logical units, of
* the lower-right corner of the bounding rectangle.
*/
private int rightRect;
/**
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the
* upper-left corner of the bounding rectangle.
*/
private int topRect;
/**
* A 16-bit signed integer that defines the x-coordinate, in logical units, of
* the upper-left corner of the bounding rectangle.
*/
private int leftRect;
protected final Rectangle2D bounds = new Rectangle2D.Double();

@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.ellipse;
}

@Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
bottomRect = leis.readShort();
rightRect = leis.readShort();
topRect = leis.readShort();
leftRect = leis.readShort();
return 4*LittleEndianConsts.SHORT_SIZE;
return readBounds(leis, bounds);
}

@Override
public void draw(HwmfGraphics ctx) {
int x = Math.min(leftRect, rightRect);
int y = Math.min(topRect, bottomRect);
int w = Math.abs(leftRect - rightRect - 1);
int h = Math.abs(topRect - bottomRect - 1);
Shape s = new Ellipse2D.Double(x, y, w, h);
Shape s = new Ellipse2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight());
ctx.fill(s);
}
}
@@ -239,25 +197,25 @@ public class HwmfDraw {
* A 16-bit unsigned integer used to index into the WMF Object Table to get
* the region to be framed.
*/
private int regionIndex;
protected int regionIndex;
/**
* A 16-bit unsigned integer used to index into the WMF Object Table to get the
* Brush to use for filling the region.
*/
private int brushIndex;
protected int brushIndex;
/**
* A 16-bit signed integer that defines the height, in logical units, of the
* region frame.
*/
private int height;
protected int height;
/**
* A 16-bit signed integer that defines the width, in logical units, of the
* region frame.
*/
private int width;
protected int width;

@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.frameRegion;
}

@@ -293,10 +251,10 @@ public class HwmfDraw {
*/
public static class WmfPolyPolygon implements HwmfRecord {

private List<Path2D> polyList = new ArrayList<>();
protected List<Path2D> polyList = new ArrayList<>();
@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.polyPolygon;
}

@@ -361,7 +319,20 @@ public class HwmfDraw {
area.exclusiveOr(newArea);
}
}
ctx.fill(area);

if (isFill()) {
ctx.fill(area);
} else {
ctx.draw(area);
}
}


/**
* @return true, if the shape should be filled
*/
protected boolean isFill() {
return true;
}
}

@@ -370,92 +341,50 @@ public class HwmfDraw {
* filled by using the brush that are defined in the playback device context.
*/
public static class WmfRectangle implements HwmfRecord {
/**
* A 16-bit signed integer that defines the y-coordinate, in logical units, of
* the lower-right corner of the rectangle.
*/
private int bottomRect;
/**
* A 16-bit signed integer that defines the x-coordinate, in logical units, of
* the lower-right corner of the rectangle.
*/
private int rightRect;
/**
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the
* upper-left corner of the rectangle.
*/
private int topRect;
/**
* A 16-bit signed integer that defines the x-coordinate, in logical units, of
* the upper-left corner of the rectangle.
*/
private int leftRect;
protected final Rectangle2D bounds = new Rectangle2D.Double();

@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.frameRegion;
}

@Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
bottomRect = leis.readShort();
rightRect = leis.readShort();
topRect = leis.readShort();
leftRect = leis.readShort();
return 4*LittleEndianConsts.SHORT_SIZE;
return readBounds(leis, bounds);
}

@Override
public void draw(HwmfGraphics ctx) {
int x = Math.min(leftRect, rightRect);
int y = Math.min(topRect, bottomRect);
int w = Math.abs(leftRect - rightRect - 1);
int h = Math.abs(topRect - bottomRect - 1);
Shape s = new Rectangle2D.Double(x, y, w, h);
ctx.fill(s);
ctx.fill(bounds);
}
}

/**
* The META_RECTANGLE record paints a rectangle. The rectangle is outlined by using the pen and
* filled by using the brush that are defined in the playback device context.
* The META_SETPIXEL record sets the pixel at the specified coordinates to the specified color.
*/
public static class WmfSetPixel implements HwmfRecord {
/**
* A ColorRef Object that defines the color value.
*/
HwmfColorRef colorRef;
protected final HwmfColorRef colorRef = new HwmfColorRef();

/**
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the point
* to be set.
*/
private int y;

/**
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the point
* to be set.
*/
private int x;
protected final Point2D point = new Point2D.Double();


@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.setPixel;
}

@Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
colorRef = new HwmfColorRef();
int size = colorRef.init(leis);
y = leis.readShort();
x = leis.readShort();
return 2*LittleEndianConsts.SHORT_SIZE+size;
return size+ readPointS(leis, point);
}

@Override
public void draw(HwmfGraphics ctx) {
Shape s = new Rectangle2D.Double(x, y, 1, 1);
Shape s = new Rectangle2D.Double(point.getX(), point.getY(), 1, 1);
ctx.fill(s);
}
}
@@ -469,41 +398,19 @@ public class HwmfDraw {
* A 16-bit signed integer that defines the height, in logical coordinates, of the
* ellipse used to draw the rounded corners.
*/
private int height;
protected int height;

/**
* A 16-bit signed integer that defines the width, in logical coordinates, of the
* ellipse used to draw the rounded corners.
*/
private int width;
protected int width;

/**
* A 16-bit signed integer that defines the y-coordinate, in logical units, of
* the lower-right corner of the rectangle.
*/
private int bottomRect;

/**
* A 16-bit signed integer that defines the x-coordinate, in logical units, of
* the lower-right corner of the rectangle.
*/
private int rightRect;

/**
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the
* upper-left corner of the rectangle.
*/
private int topRect;

/**
* A 16-bit signed integer that defines the x-coordinate, in logical units, of
* the upper-left corner of the rectangle.
*/
private int leftRect;
protected final Rectangle2D bounds = new Rectangle2D.Double();


@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.roundRect;
}

@@ -511,20 +418,12 @@ public class HwmfDraw {
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
height = leis.readShort();
width = leis.readShort();
bottomRect = leis.readShort();
rightRect = leis.readShort();
topRect = leis.readShort();
leftRect = leis.readShort();
return 6*LittleEndianConsts.SHORT_SIZE;
return 2*LittleEndianConsts.SHORT_SIZE+readBounds(leis, bounds);
}

@Override
public void draw(HwmfGraphics ctx) {
int x = Math.min(leftRect, rightRect);
int y = Math.min(topRect, bottomRect);
int w = Math.abs(leftRect - rightRect - 1);
int h = Math.abs(topRect - bottomRect - 1);
Shape s = new RoundRectangle2D.Double(x, y, w, h, width, height);
Shape s = new RoundRectangle2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), width, height);
ctx.fill(s);
}
}
@@ -534,73 +433,34 @@ public class HwmfDraw {
* The META_ARC record draws an elliptical arc.
*/
public static class WmfArc implements HwmfRecord {
/**
* A 16-bit signed integer that defines the y-coordinate, in logical units, of
* the ending point of the radial line defining the ending point of the arc.
*/
private int yEndArc;
/**
* A 16-bit signed integer that defines the x-coordinate, in logical units, of
* the ending point of the radial line defining the ending point of the arc.
*/
private int xEndArc;
/**
* A 16-bit signed integer that defines the y-coordinate, in logical units, of
* the ending point of the radial line defining the starting point of the arc.
*/
private int yStartArc;
/**
* A 16-bit signed integer that defines the x-coordinate, in logical units, of
* the ending point of the radial line defining the starting point of the arc.
*/
private int xStartArc;
/**
* A 16-bit signed integer that defines the y-coordinate, in logical units, of
* the lower-right corner of the bounding rectangle.
*/
private int bottomRect;
/**
* A 16-bit signed integer that defines the x-coordinate, in logical units, of
* the lower-right corner of the bounding rectangle.
*/
private int rightRect;
/**
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the
* upper-left corner of the bounding rectangle.
*/
private int topRect;
/**
* A 16-bit signed integer that defines the x-coordinate, in logical units, of
* the upper-left corner of the bounding rectangle.
*/
private int leftRect;
/** starting point of the arc */
protected final Point2D startPoint = new Point2D.Double();

/** ending point of the arc */
protected final Point2D endPoint = new Point2D.Double();

/** the bounding rectangle */
protected final Rectangle2D bounds = new Rectangle2D.Double();


@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.arc;
}

@Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
yEndArc = leis.readShort();
xEndArc = leis.readShort();
yStartArc = leis.readShort();
xStartArc = leis.readShort();
bottomRect = leis.readShort();
rightRect = leis.readShort();
topRect = leis.readShort();
leftRect = leis.readShort();
readPointS(leis, endPoint);
readPointS(leis, startPoint);
readBounds(leis, bounds);

return 8*LittleEndianConsts.SHORT_SIZE;
}

@Override
public void draw(HwmfGraphics ctx) {
int x = Math.min(leftRect, rightRect);
int y = Math.min(topRect, bottomRect);
int w = Math.abs(leftRect - rightRect - 1);
int h = Math.abs(topRect - bottomRect - 1);
double startAngle = Math.toDegrees(Math.atan2(-(yStartArc - (topRect + h / 2.)), xStartArc - (leftRect + w / 2.)));
double endAngle = Math.toDegrees(Math.atan2(-(yEndArc - (topRect + h / 2.)), xEndArc - (leftRect + w / 2.)));
double startAngle = Math.toDegrees(Math.atan2(-(startPoint.getY() - bounds.getCenterY()), startPoint.getX() - bounds.getCenterX()));
double endAngle = Math.toDegrees(Math.atan2(-(endPoint.getY() - bounds.getCenterY()), endPoint.getX() - bounds.getCenterX()));
double arcAngle = (endAngle - startAngle) + (endAngle - startAngle > 0 ? 0 : 360);
if (startAngle < 0) {
startAngle += 360;
@@ -608,7 +468,7 @@ public class HwmfDraw {

boolean fillShape;
int arcClosure;
switch (getRecordType()) {
switch (getWmfRecordType()) {
default:
case arc:
arcClosure = Arc2D.OPEN;
@@ -624,7 +484,7 @@ public class HwmfDraw {
break;
}
Shape s = new Arc2D.Double(x, y, w, h, startAngle, arcAngle, arcClosure);
Shape s = new Arc2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), startAngle, arcAngle, arcClosure);
if (fillShape) {
ctx.fill(s);
} else {
@@ -641,7 +501,7 @@ public class HwmfDraw {
public static class WmfPie extends WmfArc {

@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.pie;
}
}
@@ -654,7 +514,7 @@ public class HwmfDraw {
public static class WmfChord extends WmfArc {

@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.chord;
}
}
@@ -673,10 +533,10 @@ public class HwmfDraw {
* A 16-bit unsigned integer used to index into the WMF Object Table to
* get the object to be selected.
*/
private int objectIndex;
protected int objectIndex;

@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.selectObject;
}

@@ -695,4 +555,50 @@ public class HwmfDraw {
private static int getWindingRule(HwmfGraphics ctx) {
return ctx.getProperties().getPolyfillMode().awtFlag;
}
}

static int readBounds(LittleEndianInputStream leis, Rectangle2D bounds) {
/**
* The 16-bit signed integers that defines the corners of the bounding rectangle.
*/
int bottom = leis.readShort();
int right = leis.readShort();
int top = leis.readShort();
int left = leis.readShort();

int x = Math.min(left, right);
int y = Math.min(top, bottom);
int w = Math.abs(left - right - 1);
int h = Math.abs(top - bottom - 1);

bounds.setRect(x, y, w, h);

return 4 * LittleEndianConsts.SHORT_SIZE;
}

static int readRectS(LittleEndianInputStream leis, Rectangle2D bounds) {
/**
* The 16-bit signed integers that defines the corners of the bounding rectangle.
*/
int left = leis.readShort();
int top = leis.readShort();
int right = leis.readShort();
int bottom = leis.readShort();

int x = Math.min(left, right);
int y = Math.min(top, bottom);
int w = Math.abs(left - right - 1);
int h = Math.abs(top - bottom - 1);

bounds.setRect(x, y, w, h);

return 4 * LittleEndianConsts.SHORT_SIZE;
}

static int readPointS(LittleEndianInputStream leis, Point2D point) {
/** a signed integer that defines the x/y-coordinate, in logical units. */
int y = leis.readShort();
int x = leis.readShort();
point.setLocation(x, y);
return 2*LittleEndianConsts.SHORT_SIZE;
}
}

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

+ 136
- 413
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfFill.java View File

@@ -17,8 +17,12 @@

package org.apache.poi.hwmf.record;

import static org.apache.poi.hwmf.record.HwmfDraw.readPointS;

import java.awt.Shape;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.IOException;

@@ -62,7 +66,7 @@ public class HwmfFill {
this.flag = flag;
}

static ColorUsage valueOf(int flag) {
public static ColorUsage valueOf(int flag) {
for (ColorUsage bs : values()) {
if (bs.flag == flag) return bs;
}
@@ -80,16 +84,16 @@ public class HwmfFill {
* A 16-bit unsigned integer used to index into the WMF Object Table to get
* the region to be filled.
*/
private int regionIndex;
protected int regionIndex;

/**
* A 16-bit unsigned integer used to index into the WMF Object Table to get the
* brush to use for filling the region.
*/
private int brushIndex;
protected int brushIndex;
@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.fillRegion;
}
@@ -125,7 +129,7 @@ public class HwmfFill {
*/
int regionIndex;

public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.paintRegion;
}
@@ -155,31 +159,21 @@ public class HwmfFill {
/**
* A 32-bit ColorRef Object that defines the color value.
*/
private HwmfColorRef colorRef;
/**
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the
* point where filling is to start.
*/
private int yStart;
/**
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the
* point where filling is to start.
*/
private int xStart;
protected final HwmfColorRef colorRef = new HwmfColorRef();

/** the point where filling is to start. */
protected final Point2D start = new Point2D.Double();

@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.floodFill;
}
@Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
colorRef = new HwmfColorRef();
int size = colorRef.init(leis);
yStart = leis.readShort();
xStart = leis.readShort();
return size+2*LittleEndianConsts.SHORT_SIZE;
size += readPointS(leis, start);
return size;
}

@Override
@@ -215,22 +209,22 @@ public class HwmfFill {
this.awtFlag = awtFlag;
}

static HwmfPolyfillMode valueOf(int wmfFlag) {
public static HwmfPolyfillMode valueOf(int wmfFlag) {
for (HwmfPolyfillMode pm : values()) {
if (pm.wmfFlag == wmfFlag) return pm;
}
return null;
}
}
/**
* A 16-bit unsigned integer that defines polygon fill mode.
* An unsigned integer that defines polygon fill mode.
* This MUST be one of the values: ALTERNATE = 0x0001, WINDING = 0x0002
*/
private HwmfPolyfillMode polyfillMode;
protected HwmfPolyfillMode polyfillMode;
@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.setPolyFillMode;
}
@@ -251,7 +245,7 @@ public class HwmfFill {
* The META_EXTFLOODFILL record fills an area with the brush that is defined in
* the playback device context.
*/
public static class WmfExtFloodFill implements HwmfRecord {
public static class WmfExtFloodFill extends WmfFloodFill {
/**
* A 16-bit unsigned integer that defines the fill operation to be performed. This
@@ -266,38 +260,17 @@ public class HwmfFill {
* Filling continues outward in all directions as long as the color is encountered.
* This style is useful for filling areas with multicolored boundaries.
*/
private int mode;
/**
* A 32-bit ColorRef Object that defines the color value.
*/
private HwmfColorRef colorRef;
/**
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the point
* to be set.
*/
private int y;
/**
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the point
* to be set.
*/
private int x;
protected int mode;
@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.extFloodFill;
}
@Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
mode = leis.readUShort();
colorRef = new HwmfColorRef();
int size = colorRef.init(leis);
y = leis.readShort();
x = leis.readShort();
return size+3*LittleEndianConsts.SHORT_SIZE;
return super.init(leis, recordSize, recordFunction)+LittleEndianConsts.SHORT_SIZE;
}

@Override
@@ -318,7 +291,7 @@ public class HwmfFill {
private int region;
@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.invertRegion;
}
@@ -348,30 +321,10 @@ public class HwmfFill {
*/
private HwmfTernaryRasterOp rasterOperation;
/**
* A 16-bit signed integer that defines the height, in logical units, of the rectangle.
*/
private int height;
/**
* A 16-bit signed integer that defines the width, in logical units, of the rectangle.
*/
private int width;
/**
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the
* upper-left corner of the rectangle to be filled.
*/
private int yLeft;
/**
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the
* upper-left corner of the rectangle to be filled.
*/
private int xLeft;
private final Rectangle2D bounds = new Rectangle2D.Double();

@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.patBlt;
}
@@ -383,12 +336,7 @@ public class HwmfFill {
rasterOperation = HwmfTernaryRasterOp.valueOf(rasterOpIndex);
assert(rasterOpCode == rasterOperation.opCode);
height = leis.readShort();
width = leis.readShort();
yLeft = leis.readShort();
xLeft = leis.readShort();

return 6*LittleEndianConsts.SHORT_SIZE;
return readBounds2(leis, bounds)+2*LittleEndianConsts.SHORT_SIZE;
}

@Override
@@ -414,53 +362,22 @@ public class HwmfFill {
* in the playback device context, and the destination pixels are to be combined to form the new
* image. This code MUST be one of the values in the Ternary Raster Operation Enumeration
*/
private HwmfTernaryRasterOp rasterOperation;
/**
* A 16-bit signed integer that defines the height, in logical units, of the source rectangle.
*/
private int srcHeight;
/**
* A 16-bit signed integer that defines the width, in logical units, of the source rectangle.
*/
private int srcWidth;
/**
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the upper-left corner
* of the source rectangle.
*/
private int ySrc;
/**
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the upper-left corner
* of the source rectangle.
*/
private int xSrc;
/**
* A 16-bit signed integer that defines the height, in logical units, of the destination rectangle.
*/
private int destHeight;
/**
* A 16-bit signed integer that defines the width, in logical units, of the destination rectangle.
*/
private int destWidth;
/**
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the upper-left
* corner of the destination rectangle.
*/
private int yDest;
/**
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the upper-left
* corner of the destination rectangle.
*/
private int xDest;
protected HwmfTernaryRasterOp rasterOperation;

/** the source rectangle */
protected final Rectangle2D srcBounds = new Rectangle2D.Double();

/** the destination rectangle */
protected final Rectangle2D dstBounds = new Rectangle2D.Double();

/**
* A variable-sized Bitmap16 Object that defines source image content.
* This object MUST be specified, even if the raster operation does not require a source.
*/
HwmfBitmap16 target;
protected HwmfBitmap16 target;
@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.stretchBlt;
}
@@ -469,27 +386,23 @@ public class HwmfFill {
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
boolean hasBitmap = (recordSize > ((recordFunction >> 8) + 3));

int size = 0;
int rasterOpCode = leis.readUShort();
int rasterOpIndex = leis.readUShort();
rasterOperation = HwmfTernaryRasterOp.valueOf(rasterOpIndex);
assert(rasterOpCode == rasterOperation.opCode);

srcHeight = leis.readShort();
srcWidth = leis.readShort();
ySrc = leis.readShort();
xSrc = leis.readShort();
size = 6*LittleEndianConsts.SHORT_SIZE;
int size = 2*LittleEndianConsts.SHORT_SIZE;

size += readBounds2(leis, srcBounds);

if (!hasBitmap) {
/*int reserved =*/ leis.readShort();
size += LittleEndianConsts.SHORT_SIZE;
}
destHeight = leis.readShort();
destWidth = leis.readShort();
yDest = leis.readShort();
xDest = leis.readShort();
size += 4*LittleEndianConsts.SHORT_SIZE;

size += readBounds2(leis, dstBounds);

if (hasBitmap) {
target = new HwmfBitmap16();
size += target.init(leis);
@@ -524,46 +437,13 @@ public class HwmfFill {
* DIB contains explicit RGB values or indexes into a palette.
*/
private ColorUsage colorUsage;
/**
* A 16-bit signed integer that defines the height, in logical units, of the
* source rectangle.
*/
private int srcHeight;
/**
* A 16-bit signed integer that defines the width, in logical units, of the
* source rectangle.
*/
private int srcWidth;
/**
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the
* source rectangle.
*/
private int ySrc;
/**
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the
* source rectangle.
*/
private int xSrc;
/**
* A 16-bit signed integer that defines the height, in logical units, of the
* destination rectangle.
*/
private int destHeight;
/**
* A 16-bit signed integer that defines the width, in logical units, of the
* destination rectangle.
*/
private int destWidth;
/**
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the
* upper-left corner of the destination rectangle.
*/
private int yDst;
/**
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the
* upper-left corner of the destination rectangle.
*/
private int xDst;

/** the source rectangle. */
protected final Rectangle2D srcBounds = new Rectangle2D.Double();

/** the destination rectangle. */
protected final Rectangle2D dstBounds = new Rectangle2D.Double();

/**
* A variable-sized DeviceIndependentBitmap Object (section 2.2.2.9) that is the
* source of the color data.
@@ -571,7 +451,7 @@ public class HwmfFill {
private HwmfBitmapDib dib;
@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.stretchDib;
}
@@ -585,16 +465,12 @@ public class HwmfFill {
assert(rasterOpCode == rasterOperation.opCode);

colorUsage = ColorUsage.valueOf(leis.readUShort());
srcHeight = leis.readShort();
srcWidth = leis.readShort();
ySrc = leis.readShort();
xSrc = leis.readShort();
destHeight = leis.readShort();
destWidth = leis.readShort();
yDst = leis.readShort();
xDst = leis.readShort();
int size = 11*LittleEndianConsts.SHORT_SIZE;

int size = 3*LittleEndianConsts.SHORT_SIZE;

size += readBounds2(leis, srcBounds);
size += readBounds2(leis, dstBounds);

dib = new HwmfBitmapDib();
size += dib.init(leis, (int)(recordSize-6-size));

@@ -617,53 +493,10 @@ public class HwmfFill {
}
}
public static class WmfBitBlt implements HwmfRecord {

/**
* A 32-bit unsigned integer that defines how the source pixels, the current brush in the playback
* device context, and the destination pixels are to be combined to form the new image.
*/
private HwmfTernaryRasterOp rasterOperation;
/**
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the upper-left corner
of the source rectangle.
*/
private int ySrc;
/**
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the upper-left corner
of the source rectangle.
*/
private int xSrc;
/**
* A 16-bit signed integer that defines the height, in logical units, of the source and
destination rectangles.
*/
private int height;
/**
* A 16-bit signed integer that defines the width, in logical units, of the source and destination
rectangles.
*/
private int width;
/**
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the upper-left
corner of the destination rectangle.
*/
private int yDest;
/**
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the upper-left
corner of the destination rectangle.
*/
private int xDest;
/**
* A variable-sized Bitmap16 Object that defines source image content.
* This object MUST be specified, even if the raster operation does not require a source.
*/
private HwmfBitmap16 target;
public static class WmfBitBlt extends WmfStretchBlt {

@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.bitBlt;
}
@@ -671,40 +504,32 @@ public class HwmfFill {
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
boolean hasBitmap = (recordSize/2 != ((recordFunction >> 8) + 3));

int size = 0;
int rasterOpCode = leis.readUShort();
int rasterOpIndex = leis.readUShort();
rasterOperation = HwmfTernaryRasterOp.valueOf(rasterOpIndex);
assert(rasterOpCode == rasterOperation.opCode);

ySrc = leis.readShort();
xSrc = leis.readShort();
int size = 2*LittleEndianConsts.SHORT_SIZE;

final Point2D srcPnt = new Point2D.Double();
size += readPointS(leis, srcPnt);

size = 4*LittleEndianConsts.SHORT_SIZE;
if (!hasBitmap) {
/*int reserved =*/ leis.readShort();
size += LittleEndianConsts.SHORT_SIZE;
}
height = leis.readShort();
width = leis.readShort();
yDest = leis.readShort();
xDest = leis.readShort();

size += 4*LittleEndianConsts.SHORT_SIZE;
size += readBounds2(leis, dstBounds);

if (hasBitmap) {
target = new HwmfBitmap16();
size += target.init(leis);
}
return size;
}

@Override
public void draw(HwmfGraphics ctx) {
srcBounds.setRect(srcPnt.getX(), srcPnt.getY(), dstBounds.getWidth(), dstBounds.getHeight());
return size;
}
}

@@ -729,36 +554,13 @@ public class HwmfFill {
* A 16-bit unsigned integer that defines the starting scan line in the source.
*/
private int startScan;
/**
* A 16-bit unsigned integer that defines the y-coordinate, in logical units, of the
* source rectangle.
*/
private int yDib;
/**
* A 16-bit unsigned integer that defines the x-coordinate, in logical units, of the
* source rectangle.
*/
private int xDib;
/**
* A 16-bit unsigned integer that defines the height, in logical units, of the
* source and destination rectangles.
*/
private int height;
/**
* A 16-bit unsigned integer that defines the width, in logical units, of the
* source and destination rectangles.
*/
private int width;
/**
* A 16-bit unsigned integer that defines the y-coordinate, in logical units, of the
* upper-left corner of the destination rectangle.
*/
private int yDest;
/**
* A 16-bit unsigned integer that defines the x-coordinate, in logical units, of the
* upper-left corner of the destination rectangle.
*/
private int xDest;

/** the source rectangle */
protected final Rectangle2D srcBounds = new Rectangle2D.Double();

/** the destination rectangle, having the same dimension as the source rectangle */
protected final Rectangle2D dstBounds = new Rectangle2D.Double();

/**
* A variable-sized DeviceIndependentBitmap Object that is the source of the color data.
*/
@@ -766,7 +568,7 @@ public class HwmfFill {
@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.setDibToDev;
}
@@ -775,17 +577,19 @@ public class HwmfFill {
colorUsage = ColorUsage.valueOf(leis.readUShort());
scanCount = leis.readUShort();
startScan = leis.readUShort();
yDib = leis.readUShort();
xDib = leis.readUShort();
height = leis.readUShort();
width = leis.readUShort();
yDest = leis.readUShort();
xDest = leis.readUShort();
int size = 9*LittleEndianConsts.SHORT_SIZE;
int size = 3*LittleEndianConsts.SHORT_SIZE;
final Point2D srcPnt = new Point2D.Double();
size += readPointS(leis, srcPnt);
size += readBounds2(leis, dstBounds);
dib = new HwmfBitmapDib();
size += dib.init(leis, (int)(recordSize-6-size));

srcBounds.setRect(srcPnt.getX(), srcPnt.getY(), dstBounds.getWidth(), dstBounds.getHeight());

return size;
}

@@ -806,52 +610,9 @@ public class HwmfFill {
}


public static class WmfDibBitBlt implements HwmfRecord, HwmfImageRecord, HwmfObjectTableEntry {

/**
* A 32-bit unsigned integer that defines how the source pixels, the current brush
* in the playback device context, and the destination pixels are to be combined to form the
* new image. This code MUST be one of the values in the Ternary Raster Operation Enumeration.
*/
HwmfTernaryRasterOp rasterOperation;
/**
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the source rectangle.
*/
private int ySrc;
/**
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the source rectangle.
*/
private int xSrc;
/**
* A 16-bit signed integer that defines the height, in logical units, of the source and
* destination rectangles.
*/
private int height;
/**
* A 16-bit signed integer that defines the width, in logical units, of the source and destination
* rectangles.
*/
private int width;
/**
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the upper-left
* corner of the destination rectangle.
*/
private int yDest;
/**
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the upper-left
* corner of the destination rectangle.
*/
private int xDest;
/**
* A variable-sized DeviceIndependentBitmap Object that defines image content.
* This object MUST be specified, even if the raster operation does not require a source.
*/
private HwmfBitmapDib target;
public static class WmfDibBitBlt extends WmfDibStretchBlt {
@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.dibBitBlt;
}
@@ -859,47 +620,31 @@ public class HwmfFill {
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
boolean hasBitmap = (recordSize/2 != ((recordFunction >> 8) + 3));

int size = 0;
int rasterOpCode = leis.readUShort();
int rasterOpIndex = leis.readUShort();
rasterOperation = HwmfTernaryRasterOp.valueOf(rasterOpIndex);
assert(rasterOpCode == rasterOperation.opCode);

ySrc = leis.readShort();
xSrc = leis.readShort();
size = 4*LittleEndianConsts.SHORT_SIZE;
int size = 2*LittleEndianConsts.SHORT_SIZE;

final Point2D srcPnt = new Point2D.Double();
size += readPointS(leis, srcPnt);
if (!hasBitmap) {
/*int reserved =*/ leis.readShort();
size += LittleEndianConsts.SHORT_SIZE;
}
height = leis.readShort();
width = leis.readShort();
yDest = leis.readShort();
xDest = leis.readShort();
size += 4*LittleEndianConsts.SHORT_SIZE;

size += readBounds2(leis, dstBounds);
if (hasBitmap) {
target = new HwmfBitmapDib();
size += target.init(leis, (int)(recordSize-6-size));
}
return size;
}

@Override
public void draw(HwmfGraphics ctx) {
ctx.addObjectTableEntry(this);
}
@Override
public void applyObject(HwmfGraphics ctx) {
}
// the destination rectangle, having the same dimension as the source rectangle
srcBounds.setRect(srcPnt.getX(), srcPnt.getY(), dstBounds.getWidth(), dstBounds.getHeight());

@Override
public BufferedImage getImage() {
return (target == null) ? null : target.getImage();
return size;
}
}

@@ -909,53 +654,22 @@ public class HwmfFill {
* in the playback device context, and the destination pixels are to be combined to form the
* new image. This code MUST be one of the values in the Ternary Raster Operation Enumeration.
*/
private HwmfTernaryRasterOp rasterOperation;
/**
* A 16-bit signed integer that defines the height, in logical units, of the source rectangle.
*/
private int srcHeight;
/**
* A 16-bit signed integer that defines the width, in logical units, of the source rectangle.
*/
private int srcWidth;
/**
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the
* upper-left corner of the source rectangle.
*/
private int ySrc;
/**
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the
* upper-left corner of the source rectangle.
*/
private int xSrc;
/**
* A 16-bit signed integer that defines the height, in logical units, of the
* destination rectangle.
*/
private int destHeight;
/**
* A 16-bit signed integer that defines the width, in logical units, of the
* destination rectangle.
*/
private int destWidth;
/**
* A 16-bit signed integer that defines the y-coordinate, in logical units,
* of the upper-left corner of the destination rectangle.
*/
private int yDest;
/**
* A 16-bit signed integer that defines the x-coordinate, in logical units,
* of the upper-left corner of the destination rectangle.
*/
private int xDest;
protected HwmfTernaryRasterOp rasterOperation;

/** the source rectangle */
protected final Rectangle2D srcBounds = new Rectangle2D.Double();

/** the destination rectangle */
protected final Rectangle2D dstBounds = new Rectangle2D.Double();

/**
* A variable-sized DeviceIndependentBitmap Object that defines image content.
* This object MUST be specified, even if the raster operation does not require a source.
*/
HwmfBitmapDib target;
protected HwmfBitmapDib target;

@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.dibStretchBlt;
}
@@ -963,27 +677,21 @@ public class HwmfFill {
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
boolean hasBitmap = (recordSize > ((recordFunction >> 8) + 3));

int size = 0;
int rasterOpCode = leis.readUShort();
int rasterOpIndex = leis.readUShort();
rasterOperation = HwmfTernaryRasterOp.valueOf(rasterOpIndex);
assert(rasterOpCode == rasterOperation.opCode);

srcHeight = leis.readShort();
srcWidth = leis.readShort();
ySrc = leis.readShort();
xSrc = leis.readShort();
size = 6*LittleEndianConsts.SHORT_SIZE;
int size = 2*LittleEndianConsts.SHORT_SIZE;

size += readBounds2(leis, srcBounds);
if (!hasBitmap) {
/*int reserved =*/ leis.readShort();
size += LittleEndianConsts.SHORT_SIZE;
}
destHeight = leis.readShort();
destWidth = leis.readShort();
yDest = leis.readShort();
xDest = leis.readShort();
size += 4*LittleEndianConsts.SHORT_SIZE;

size += readBounds2(leis, dstBounds);
if (hasBitmap) {
target = new HwmfBitmapDib();
size += target.init(leis, (int)(recordSize-6-size));
@@ -996,15 +704,30 @@ public class HwmfFill {
public void draw(HwmfGraphics ctx) {
ctx.addObjectTableEntry(this);
}
@Override
public void applyObject(HwmfGraphics ctx) {
}

@Override
public BufferedImage getImage() {
return target.getImage();
return (target == null) ? null : target.getImage();
}
}

static int readBounds2(LittleEndianInputStream leis, Rectangle2D bounds) {
/**
* The 16-bit signed integers that defines the corners of the bounding rectangle.
*/
int h = leis.readShort();
int w = leis.readShort();
int y = leis.readShort();
int x = leis.readShort();

bounds.setRect(x, y, w, h);

return 4 * LittleEndianConsts.SHORT_SIZE;
}

}

+ 61
- 65
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,25 @@ public class HwmfFont implements FontInfo {
* If clear, device fonts SHOULD rotate counterclockwise, but the rotation of other fonts
* SHOULD be determined by the orientation of the coordinate system.
*/
CLIP_LH_ANGLES (0x00000010),
private static final BitField CLIP_LH_ANGLES = BitFieldFactory.getInstance(0x0010);

/**
* This value SHOULD NOT be used.
*/
CLIP_TT_ALWAYS (0x00000020),
/** This value SHOULD NOT be used. */
private static final BitField CLIP_TT_ALWAYS = BitFieldFactory.getInstance(0x0020);

/**
* This value specifies that font association SHOULD< be turned off.
*/
CLIP_DFA_DISABLE (0x00000040),
/** This value specifies that font association SHOULD< be turned off. */
private static final BitField CLIP_DFA_DISABLE = BitFieldFactory.getInstance(0x0040);

/**
* This value specifies that font embedding MUST be used to render document content;
* embedded fonts are read-only.
*/
CLIP_EMBEDDED (0x00000080);

private static final BitField CLIP_EMBEDDED = BitFieldFactory.getInstance(0x0080);

int flag;
WmfClipPrecision(int flag) {
this.flag = flag;
}

static WmfClipPrecision valueOf(int flag) {
for (WmfClipPrecision cp : values()) {
if (cp.flag == flag) {
return cp;
}
}
return null;
public int init(LittleEndianInputStream leis) {
flag = leis.readUByte();
return LittleEndianConsts.BYTE_SIZE;
}
}

@@ -210,7 +195,7 @@ public class HwmfFont implements FontInfo {
this.flag = flag;
}

static WmfFontQuality valueOf(int flag) {
public static WmfFontQuality valueOf(int flag) {
for (WmfFontQuality fq : values()) {
if (fq.flag == flag) {
return fq;
@@ -240,7 +225,7 @@ public class HwmfFont implements FontInfo {
* For all height comparisons, the font mapper SHOULD find the largest physical
* font that does not exceed the requested size.
*/
int height;
protected int height;

/**
* A 16-bit signed integer that defines the average width, in logical units, of
@@ -248,45 +233,45 @@ public class HwmfFont implements FontInfo {
* against the digitization aspect ratio of the available fonts to find the closest match,
* determined by the absolute value of the difference.
*/
int width;
protected int width;

/**
* A 16-bit signed integer that defines the angle, in tenths of degrees, between the
* escapement vector and the x-axis of the device. The escapement vector is parallel
* to the base line of a row of text.
*/
int escapement;
protected int escapement;

/**
* A 16-bit signed integer that defines the angle, in tenths of degrees,
* between each character's base line and the x-axis of the device.
*/
int orientation;
protected int orientation;

/**
* A 16-bit signed integer that defines the weight of the font in the range 0
* through 1000. For example, 400 is normal and 700 is bold. If this value is 0x0000,
* a default weight SHOULD be used.
*/
int weight;
protected int weight;

/**
* A 8-bit Boolean value that specifies the italic attribute of the font.
* 0 = not italic / 1 = italic.
*/
boolean italic;
protected boolean italic;

/**
* An 8-bit Boolean value that specifies the underline attribute of the font.
* 0 = not underlined / 1 = underlined
*/
boolean underline;
protected boolean underline;

/**
* An 8-bit Boolean value that specifies the strike out attribute of the font.
* 0 = not striked out / 1 = striked out
*/
boolean strikeOut;
protected boolean strikeOut;

/**
* An 8-bit unsigned integer that defines the character set.
@@ -299,12 +284,12 @@ public class HwmfFont implements FontInfo {
* If a typeface name in the FaceName field is specified, the CharSet value MUST match the
* character set of that typeface.
*/
FontCharset charSet;
protected FontCharset charSet;

/**
* An 8-bit unsigned integer that defines the output precision.
*/
WmfOutPrecision outPrecision;
protected WmfOutPrecision outPrecision;

/**
* An 8-bit unsigned integer that defines the clipping precision.
@@ -312,40 +297,40 @@ public class HwmfFont implements FontInfo {
*
* @see WmfClipPrecision
*/
WmfClipPrecision clipPrecision;
protected final WmfClipPrecision clipPrecision = new WmfClipPrecision();

/**
* An 8-bit unsigned integer that defines the output quality.
*/
WmfFontQuality quality;
protected WmfFontQuality quality;

/**
* A PitchAndFamily object that defines the pitch and the family of the font.
* Font families specify the look of fonts in a general way and are intended for
* specifying fonts when the exact typeface wanted is not available.
*/
int pitchAndFamily;
protected int pitchAndFamily;
/**
* Font families specify the look of fonts in a general way and are
* intended for specifying fonts when the exact typeface wanted is not available.
* (LSB 4 bits)
*/
FontFamily family;
protected FontFamily family;
/**
* A property of a font that describes the pitch (MSB 2 bits)
*/
FontPitch pitch;
protected FontPitch pitch;

/**
* A null-terminated string of 8-bit Latin-1 [ISO/IEC-8859-1] ANSI
* characters that specifies the typeface name of the font. The length of this string MUST NOT
* exceed 32 8-bit characters, including the terminating null.
*/
String facename;
protected String facename;

public int init(LittleEndianInputStream leis) throws IOException {
public int init(LittleEndianInputStream leis, long recordSize) throws IOException {
height = leis.readShort();
width = leis.readShort();
escapement = leis.readShort();
@@ -356,21 +341,17 @@ public class HwmfFont implements FontInfo {
strikeOut = leis.readByte() != 0;
charSet = FontCharset.valueOf(leis.readUByte());
outPrecision = WmfOutPrecision.valueOf(leis.readUByte());
clipPrecision = WmfClipPrecision.valueOf(leis.readUByte());
clipPrecision.init(leis);
quality = WmfFontQuality.valueOf(leis.readUByte());
pitchAndFamily = leis.readUByte();
byte buf[] = new byte[32], b, readBytes = 0;
do {
if (readBytes == 32) {
throw new IOException("Font facename can't be determined.");
}

buf[readBytes++] = b = leis.readByte();
} while (b != 0 && b != -1 && readBytes <= 32);
facename = new String(buf, 0, readBytes-1, StandardCharsets.ISO_8859_1);
StringBuilder sb = new StringBuilder();
int readBytes = readString(leis, sb, 32);
if (readBytes == -1) {
throw new IOException("Font facename can't be determined.");
}
facename = sb.toString();

return 5*LittleEndianConsts.SHORT_SIZE+8*LittleEndianConsts.BYTE_SIZE+readBytes;
}

@@ -471,4 +452,19 @@ public class HwmfFont implements FontInfo {
public void setCharset(FontCharset charset) {
throw new UnsupportedOperationException("setCharset not supported by HwmfFont.");
}

protected int readString(LittleEndianInputStream leis, StringBuilder sb, int limit) throws IOException {
byte buf[] = new byte[limit], b, readBytes = 0;
do {
if (readBytes == limit) {
return -1;
}

buf[readBytes++] = b = leis.readByte();
} while (b != 0 && b != -1 && readBytes <= limit);

sb.append(new String(buf, 0, readBytes-1, StandardCharsets.ISO_8859_1));

return readBytes;
}
}

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

@@ -39,7 +39,7 @@ public enum HwmfHatchStyle {
this.flag = flag;
}

static HwmfHatchStyle valueOf(int flag) {
public static HwmfHatchStyle valueOf(int flag) {
for (HwmfHatchStyle hs : values()) {
if (hs.flag == flag) return hs;
}

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

+ 94
- 56
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;
}

@@ -53,7 +55,7 @@ public class HwmfMisc {
* The META_SETRELABS record is reserved and not supported.
*/
public static class WmfSetRelabs implements HwmfRecord {
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.setRelabs;
}

@@ -81,7 +83,7 @@ public class HwmfMisc {
private int nSavedDC;

@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.restoreDc;
}

@@ -106,7 +108,7 @@ public class HwmfMisc {
private HwmfColorRef colorRef;

@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.setBkColor;
}

@@ -140,7 +142,7 @@ public class HwmfMisc {
this.flag = flag;
}

static HwmfBkMode valueOf(int flag) {
public static HwmfBkMode valueOf(int flag) {
for (HwmfBkMode bs : values()) {
if (bs.flag == flag) return bs;
}
@@ -148,9 +150,9 @@ public class HwmfMisc {
}
}

private HwmfBkMode bkMode;
protected HwmfBkMode bkMode;

public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.setBkMode;
}

@@ -180,7 +182,7 @@ public class HwmfMisc {
private int layout;

@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.setLayout;
}

@@ -205,10 +207,10 @@ public class HwmfMisc {
*/
public static class WmfSetMapMode implements HwmfRecord {

private HwmfMapMode mapMode;
protected HwmfMapMode mapMode;

@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.setMapMode;
}

@@ -234,12 +236,13 @@ public class HwmfMisc {
/**
* A 32-bit unsigned integer that defines whether the font mapper should attempt to
* match a font's aspect ratio to the current device's aspect ratio. If bit 0 is
* set, the mapper selects only matching fonts.
* set, the font mapper SHOULD select only fonts that match the aspect ratio of the
* output device, as it is currently defined in the playback device context.
*/
private long mapperValues;

@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.setMapperFlags;
}

@@ -262,14 +265,11 @@ public class HwmfMisc {
*/
public static class WmfSetRop2 implements HwmfRecord {

/**
* A 16-bit unsigned integer that defines the foreground binary raster
* operation mixing mode
*/
private HwmfBinaryRasterOp drawMode;
/** An unsigned integer that defines the foreground binary raster operation mixing mode */
protected HwmfBinaryRasterOp drawMode;

@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.setRop2;
}

@@ -291,24 +291,63 @@ public class HwmfMisc {
*/
public static class WmfSetStretchBltMode implements HwmfRecord {

/**
* A 16-bit unsigned integer that defines bitmap stretching mode.
* This MUST be one of the values:
* BLACKONWHITE = 0x0001,
* WHITEONBLACK = 0x0002,
* COLORONCOLOR = 0x0003,
* HALFTONE = 0x0004
*/
private int setStretchBltMode;
public enum StretchBltMode {
/**
* Performs a Boolean AND operation by using the color values for the eliminated and existing pixels.
* If the bitmap is a monochrome bitmap, this mode preserves black pixels at the expense of white pixels.
*
* EMF name: STRETCH_ANDSCANS
*/
BLACKONWHITE(0x0001),
/**
* Performs a Boolean OR operation by using the color values for the eliminated and existing pixels.
* If the bitmap is a monochrome bitmap, this mode preserves white pixels at the expense of black pixels.
*
* EMF name: STRETCH_ORSCANS
*/
WHITEONBLACK(0x0002),
/**
* Deletes the pixels. This mode deletes all eliminated lines of pixels without trying
* to preserve their information.
*
* EMF name: STRETCH_DELETESCANS
*/
COLORONCOLOR(0x0003),
/**
* Maps pixels from the source rectangle into blocks of pixels in the destination rectangle.
* The average color over the destination block of pixels approximates the color of the source
* pixels.
*
* After setting the HALFTONE stretching mode, the brush origin MUST be set to avoid misalignment
* artifacts - in EMF this is done via EmfSetBrushOrgEx
*
* EMF name: STRETCH_HALFTONE
*/
HALFTONE(0x0004);

public final int flag;
StretchBltMode(int flag) {
this.flag = flag;
}

public static StretchBltMode valueOf(int flag) {
for (StretchBltMode bs : values()) {
if (bs.flag == flag) return bs;
}
return null;
}
}

protected StretchBltMode stretchBltMode;

@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.setStretchBltMode;
}

@Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
setStretchBltMode = leis.readUShort();
stretchBltMode = StretchBltMode.valueOf(leis.readUShort());
return LittleEndianConsts.SHORT_SIZE;
}

@@ -341,7 +380,7 @@ public class HwmfMisc {
private HwmfBitmap16 pattern16;

@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.dibCreatePatternBrush;
}

@@ -403,10 +442,10 @@ public class HwmfMisc {
* A 16-bit unsigned integer used to index into the WMF Object Table to
* get the object to be deleted.
*/
private int objectIndex;
protected int objectIndex;

@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.deleteObject;
}

@@ -418,6 +457,12 @@ public class HwmfMisc {

@Override
public void draw(HwmfGraphics ctx) {
/* TODO:
* The object specified by this record MUST be deleted from the EMF Object Table.
* If the deleted object is currently selected in the playback device context,
* the default object for that graphics property MUST be restored.
*/

ctx.unsetObjectTableEntry(objectIndex);
}
}
@@ -427,7 +472,7 @@ public class HwmfMisc {
private HwmfBitmap16 pattern;

@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.createPatternBrush;
}

@@ -452,30 +497,28 @@ public class HwmfMisc {

public static class WmfCreatePenIndirect implements HwmfRecord, HwmfObjectTableEntry {

private HwmfPenStyle penStyle;
/**
* A 32-bit PointS Object that specifies a point for the object dimensions.
* The x-coordinate is the pen width. The y-coordinate is ignored.
*/
private int xWidth;
@SuppressWarnings("unused")
private int yWidth;
protected HwmfPenStyle penStyle;

protected final Dimension2D dimension = new Dimension2DDouble();
/**
* A 32-bit ColorRef Object that specifies the pen color value.
*/
private HwmfColorRef colorRef;
protected final HwmfColorRef colorRef = new HwmfColorRef();

@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.createPenIndirect;
}

@Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
penStyle = HwmfPenStyle.valueOf(leis.readUShort());
xWidth = leis.readShort();
yWidth = leis.readShort();
colorRef = new HwmfColorRef();
// A 32-bit PointS Object that specifies a point for the object dimensions.
// The x-coordinate is the pen width. The y-coordinate is ignored.
int xWidth = leis.readShort();
int yWidth = leis.readShort();
dimension.setSize(xWidth, yWidth);

int size = colorRef.init(leis);
return size+3*LittleEndianConsts.SHORT_SIZE;
}
@@ -490,7 +533,7 @@ public class HwmfMisc {
HwmfDrawProperties p = ctx.getProperties();
p.setPenStyle(penStyle);
p.setPenColor(colorRef);
p.setPenWidth(xWidth);
p.setPenWidth(dimension.getWidth());
}
}

@@ -540,19 +583,14 @@ public class HwmfMisc {
* </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;
}


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


+ 14
- 5
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfPenStyle.java View File

@@ -136,11 +136,12 @@ 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 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);

private int flag;
public static HwmfPenStyle valueOf(int flag) {
@@ -169,6 +170,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

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

+ 149
- 63
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfText.java View File

@@ -17,8 +17,16 @@

package org.apache.poi.hwmf.record;

import static org.apache.poi.hwmf.record.HwmfDraw.readPointS;
import static org.apache.poi.hwmf.record.HwmfDraw.readRectS;

import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.ByteArrayInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.Charset;

import org.apache.poi.hwmf.draw.HwmfDrawProperties;
@@ -31,6 +39,7 @@ import org.apache.poi.util.LittleEndianConsts;
import org.apache.poi.util.LittleEndianInputStream;
import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger;
import org.apache.poi.util.RecordFormatException;

public class HwmfText {
private static final POILogger logger = POILogFactory.getLogger(HwmfText.class);
@@ -52,7 +61,7 @@ public class HwmfText {
private int charExtra;
@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.setTextCharExtra;
}
@@ -76,7 +85,7 @@ public class HwmfText {
private HwmfColorRef colorRef;
@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.setTextColor;
}
@@ -112,7 +121,7 @@ public class HwmfText {
private int breakExtra;
@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.setBkColor;
}
@@ -159,7 +168,7 @@ public class HwmfText {
private int xStart;
@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.textOut;
}
@@ -195,40 +204,47 @@ public class HwmfText {
return ret;
}
}
/**
* The META_EXTTEXTOUT record outputs text by using the font, background color, and text color that
* are defined in the playback device context. Optionally, dimensions can be provided for clipping,
* opaquing, or both.
*/
public static class WmfExtTextOut implements HwmfRecord {

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

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

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

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

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

/**
* Indicates that to display numbers, digits appropriate to the locale SHOULD be used.
*/
@@ -240,32 +256,58 @@ public class HwmfText {
private static final BitField ETO_NUMERICSLATIN = BitFieldFactory.getInstance(0x0800);

/**
* Indicates that both horizontal and vertical character displacement values
* This bit indicates that no special operating system processing for glyph placement
* should be performed on right-to-left strings; that is, all glyph positioning
* SHOULD be taken care of by drawing and state records in the metafile
*/
private static final BitField ETO_IGNORELANGUAGE = BitFieldFactory.getInstance(0x1000);

/**
* Indicates that both horizontal and vertical character displacement values
* SHOULD be provided.
*/
private static final BitField ETO_PDY = BitFieldFactory.getInstance(0x2000);

/** This bit is reserved and SHOULD NOT be used. */
private static final BitField ETO_REVERSE_INDEX_MAP = BitFieldFactory.getInstance(0x10000);

protected int flag;

public int init(LittleEndianInputStream leis) {
flag = leis.readUShort();
return LittleEndianConsts.SHORT_SIZE;
}

public boolean isOpaque() {
return ETO_OPAQUE.isSet(flag);
}

public boolean isClipped() {
return ETO_CLIPPED.isSet(flag);
}
}

/**
* The META_EXTTEXTOUT record outputs text by using the font, background color, and text color that
* are defined in the playback device context. Optionally, dimensions can be provided for clipping,
* opaquing, or both.
*/
public static class WmfExtTextOut implements HwmfRecord {
/**
* A 16-bit signed integer that defines the y-coordinate, in logical units, where the
text string is to be located.
* The location, in logical units, where the text string is to be placed.
*/
private int y;
protected final Point2D reference = new Point2D.Double();

/**
* A 16-bit signed integer that defines the x-coordinate, in logical units, where the
text string is to be located.
* A 16-bit signed integer that defines the length of the string.
*/
private int x;
protected int stringLength;
/**
* A 16-bit signed integer that defines the length of the string.
* A 16-bit unsigned integer that defines the use of the application-defined
* rectangle. This member can be a combination of one or more values in the
* ExtTextOutOptions Flags (ETO_*)
*/
private int stringLength;

/**
* A 16-bit unsigned integer that defines the use of the application-defined
* rectangle. This member can be a combination of one or more values in the
* ExtTextOutOptions Flags (ETO_*)
*/
private int fwOpts;
protected final WmfExtTextOutOptions options;
/**
* An optional 8-byte Rect Object (section 2.2.2.18) that defines the
* dimensions, in logical coordinates, of a rectangle that is used for clipping, opaquing, or both.
@@ -274,14 +316,14 @@ public class HwmfText {
* Each value is a 16-bit signed integer that defines the coordinate, in logical coordinates, of
* the upper-left corner of the rectangle
*/
private int left,top,right,bottom;
protected final Rectangle2D bounds = new Rectangle2D.Double();
/**
* A variable-length string that specifies the text to be drawn. The string does
* not need to be null-terminated, because StringLength specifies the length of the string. If
* the length is odd, an extra byte is placed after it so that the following member (optional Dx) is
* aligned on a 16-bit boundary.
*/
private byte[] rawTextBytes;
protected byte[] rawTextBytes;
/**
* An optional array of 16-bit signed integers that indicate the distance between
* origins of adjacent character cells. For example, Dx[i] logical units separate the origins of
@@ -289,9 +331,17 @@ public class HwmfText {
* number of values as there are characters in the string.
*/
private int dx[];

public WmfExtTextOut() {
this(new WmfExtTextOutOptions());
}

protected WmfExtTextOut(WmfExtTextOutOptions options) {
this.options = options;
}

@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.extTextOut;
}
@@ -299,22 +349,17 @@ public class HwmfText {
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
// -6 bytes of record function and length header
final int remainingRecordSize = (int)(recordSize-6);
y = leis.readShort();
x = leis.readShort();
int size = readPointS(leis, reference);
stringLength = leis.readShort();
fwOpts = leis.readUShort();
int size = 4*LittleEndianConsts.SHORT_SIZE;
size += LittleEndianConsts.SHORT_SIZE;
size += options.init(leis);

// Check if we have a rectangle
if ((ETO_OPAQUE.isSet(fwOpts) || ETO_CLIPPED.isSet(fwOpts)) && size+8<=remainingRecordSize) {
// the bounding rectangle is optional and only read when fwOpts are given
left = leis.readShort();
top = leis.readShort();
right = leis.readShort();
bottom = leis.readShort();
size += 4*LittleEndianConsts.SHORT_SIZE;
if ((options.isOpaque() || options.isClipped()) && size+8<=remainingRecordSize) {
// the bounding rectangle is optional and only read when options are given
size += readRectS(leis, bounds);
}
rawTextBytes = IOUtils.safelyAllocate(stringLength+(stringLength&1), MAX_RECORD_LENGTH);
@@ -342,12 +387,46 @@ public class HwmfText {

@Override
public void draw(HwmfGraphics ctx) {
Rectangle2D bounds = new Rectangle2D.Double(x, y, 0, 0);
Rectangle2D bounds = new Rectangle2D.Double(reference.getX(), reference.getY(), 0, 0);
ctx.drawString(getTextBytes(), bounds, dx);
}

public String getText(Charset charset) {
return new String(getTextBytes(), charset);

public String getText(Charset charset) throws IOException {
StringBuilder sb = new StringBuilder();
try (Reader r = new InputStreamReader(new ByteArrayInputStream(rawTextBytes), charset)) {
for (int i = 0; i < stringLength; i++) {
sb.appendCodePoint(readCodePoint(r));
}
}
return sb.toString();
}

//TODO: move this to IOUtils?
private int readCodePoint(Reader r) throws IOException {
int c1 = r.read();
if (c1 == -1) {
throw new EOFException("Tried to read beyond byte array");
}
if (!Character.isHighSurrogate((char)c1)) {
return c1;
}
int c2 = r.read();
if (c2 == -1) {
throw new EOFException("Tried to read beyond byte array");
}
if (!Character.isLowSurrogate((char)c2)) {
throw new RecordFormatException("Expected low surrogate after high surrogate");
}
return Character.toCodePoint((char)c1, (char)c2);
}

public Point2D getReference() {
return reference;
}

public Rectangle2D getBounds() {
return bounds;
}

/**
@@ -482,10 +561,10 @@ public class HwmfText {
* for text with a horizontal baseline, and VerticalTextAlignmentMode Flags
* for text with a vertical baseline.
*/
private int textAlignmentMode;
protected int textAlignmentMode;
@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.setTextAlign;
}
@@ -533,17 +612,24 @@ public class HwmfText {
}
public static class WmfCreateFontIndirect implements HwmfRecord, HwmfObjectTableEntry {
private HwmfFont font;
protected final HwmfFont font;

public WmfCreateFontIndirect() {
this(new HwmfFont());
}

protected WmfCreateFontIndirect(HwmfFont font) {
this.font = font;
}

@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.createFontIndirect;
}
@Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
font = new HwmfFont();
return font.init(leis);
return font.init(leis, recordSize);
}

@Override

+ 79
- 123
src/scratchpad/src/org/apache/poi/hwmf/record/HwmfWindowing.java View File

@@ -33,18 +33,14 @@ public class HwmfWindowing {
*/
public static class WmfSetViewportOrg implements HwmfRecord {

/**
* A 16-bit signed integer that defines the vertical offset, in device units.
*/
private int y;
/** A signed integer that defines the vertical offset, in device units. */
protected int y;

/**
* A 16-bit signed integer that defines the horizontal offset, in device units.
*/
private int x;
/** A signed integer that defines the horizontal offset, in device units. */
protected int x;

@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.setViewportOrg;
}

@@ -67,20 +63,14 @@ public class HwmfWindowing {
*/
public static class WmfSetViewportExt implements HwmfRecord {

/**
* A 16-bit signed integer that defines the vertical extent
* of the viewport in device units.
*/
private int height;
/** A signed integer that defines the vertical extent of the viewport in device units. */
protected int height;

/**
* A 16-bit signed integer that defines the horizontal extent
* of the viewport in device units.
*/
private int width;
/** A signed integer that defines the horizontal extent of the viewport in device units. */
protected int width;

@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.setViewportExt;
}

@@ -114,7 +104,7 @@ public class HwmfWindowing {
private int xOffset;

@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.offsetViewportOrg;
}

@@ -139,18 +129,14 @@ public class HwmfWindowing {
*/
public static class WmfSetWindowOrg implements HwmfRecord {

/**
* A 16-bit signed integer that defines the y-coordinate, in logical units.
*/
private int y;
/** A signed integer that defines the y-coordinate, in logical units. */
protected int y;

/**
* A 16-bit signed integer that defines the x-coordinate, in logical units.
*/
private int x;
/** A signed integer that defines the x-coordinate, in logical units. */
protected int x;

@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.setWindowOrg;
}

@@ -182,20 +168,14 @@ public class HwmfWindowing {
*/
public static class WmfSetWindowExt implements HwmfRecord {

/**
* A 16-bit signed integer that defines the vertical extent of
* the window in logical units.
*/
private int height;
/** A signed integer that defines the vertical extent of the window in logical units. */
protected int height;

/**
* A 16-bit signed integer that defines the horizontal extent of
* the window in logical units.
*/
private int width;
/** A signed integer that defines the horizontal extent of the window in logical units. */
protected int width;

@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.setWindowExt;
}

@@ -238,7 +218,7 @@ public class HwmfWindowing {
private int xOffset;

@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.offsetWindowOrg;
}

@@ -264,31 +244,31 @@ public class HwmfWindowing {
public static class WmfScaleWindowExt implements HwmfRecord {

/**
* A 16-bit signed integer that defines the amount by which to divide the
* A signed integer that defines the amount by which to divide the
* result of multiplying the current y-extent by the value of the yNum member.
*/
private int yDenom;
protected int yDenom;

/**
* A 16-bit signed integer that defines the amount by which to multiply the
* A signed integer that defines the amount by which to multiply the
* current y-extent.
*/
private int yNum;
protected int yNum;

/**
* A 16-bit signed integer that defines the amount by which to divide the
* A signed integer that defines the amount by which to divide the
* result of multiplying the current x-extent by the value of the xNum member.
*/
private int xDenom;
protected int xDenom;

/**
* A 16-bit signed integer that defines the amount by which to multiply the
* A signed integer that defines the amount by which to multiply the
* current x-extent.
*/
private int xNum;
protected int xNum;

@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.scaleWindowExt;
}

@@ -320,31 +300,31 @@ public class HwmfWindowing {
public static class WmfScaleViewportExt implements HwmfRecord {

/**
* A 16-bit signed integer that defines the amount by which to divide the
* A signed integer that defines the amount by which to divide the
* result of multiplying the current y-extent by the value of the yNum member.
*/
private int yDenom;
protected int yDenom;

/**
* A 16-bit signed integer that defines the amount by which to multiply the
* A signed integer that defines the amount by which to multiply the
* current y-extent.
*/
private int yNum;
protected int yNum;

/**
* A 16-bit signed integer that defines the amount by which to divide the
* A signed integer that defines the amount by which to divide the
* result of multiplying the current x-extent by the value of the xNum member.
*/
private int xDenom;
protected int xDenom;

/**
* A 16-bit signed integer that defines the amount by which to multiply the
* A signed integer that defines the amount by which to multiply the
* current x-extent.
*/
private int xNum;
protected int xNum;

@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.scaleViewportExt;
}

@@ -376,17 +356,17 @@ public class HwmfWindowing {
public static class WmfOffsetClipRgn implements HwmfRecord, HwmfObjectTableEntry {

/**
* A 16-bit signed integer that defines the number of logical units to move up or down.
* A signed integer that defines the number of logical units to move up or down.
*/
private int yOffset;
protected int yOffset;

/**
* A 16-bit signed integer that defines the number of logical units to move left or right.
* A signed integer that defines the number of logical units to move left or right.
*/
private int xOffset;
protected int xOffset;

@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.offsetClipRgn;
}

@@ -413,41 +393,29 @@ public class HwmfWindowing {
*/
public static class WmfExcludeClipRect implements HwmfRecord, HwmfObjectTableEntry {

/**
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the
* lower-right corner of the rectangle.
*/
private int bottom;

/**
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the
* lower-right corner of the rectangle.
*/
private int right;

/**
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the
* upper-left corner of the rectangle.
*/
private int top;

/**
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the
* upper-left corner of the rectangle.
*/
private int left;
/** a rectangle in logical units */
protected final Rectangle2D bounds = new Rectangle2D.Double();

@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.excludeClipRect;
}

@Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
bottom = leis.readShort();
right = leis.readShort();
top = leis.readShort();
left = leis.readShort();
// A 16-bit signed integer that defines the y-coordinate, in logical units, of the
// lower-right corner of the rectangle.
final int bottom = leis.readShort();
// A 16-bit signed integer that defines the x-coordinate, in logical units, of the
// lower-right corner of the rectangle.
final int right = leis.readShort();
// A 16-bit signed integer that defines the y-coordinate, in logical units, of the
// upper-left corner of the rectangle.
final int top = leis.readShort();
// A 16-bit signed integer that defines the x-coordinate, in logical units, of the
// upper-left corner of the rectangle.
final int left = leis.readShort();
bounds.setRect(left, top, right-left, bottom-top);
return 4*LittleEndianConsts.SHORT_SIZE;
}

@@ -468,41 +436,29 @@ public class HwmfWindowing {
*/
public static class WmfIntersectClipRect implements HwmfRecord, HwmfObjectTableEntry {

/**
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the
* lower-right corner of the rectangle.
*/
private int bottom;

/**
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the
* lower-right corner of the rectangle.
*/
private int right;

/**
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the
* upper-left corner of the rectangle.
*/
private int top;

/**
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the
* upper-left corner of the rectangle.
*/
private int left;
/** a rectangle in logical units */
protected final Rectangle2D bounds = new Rectangle2D.Double();

@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.intersectClipRect;
}

@Override
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
bottom = leis.readShort();
right = leis.readShort();
top = leis.readShort();
left = leis.readShort();
// A 16-bit signed integer that defines the y-coordinate, in logical units, of the
// lower-right corner of the rectangle.
final int bottom = leis.readShort();
// A 16-bit signed integer that defines the x-coordinate, in logical units, of the
// lower-right corner of the rectangle.
final int right = leis.readShort();
// A 16-bit signed integer that defines the y-coordinate, in logical units, of the
// upper-left corner of the rectangle.
final int top = leis.readShort();
// A 16-bit signed integer that defines the x-coordinate, in logical units, of the
// upper-left corner of the rectangle.
final int left = leis.readShort();
bounds.setRect(left, top, right-left, bottom-top);
return 4*LittleEndianConsts.SHORT_SIZE;
}

@@ -528,7 +484,7 @@ public class HwmfWindowing {
private int region;

@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.selectClipRegion;
}

@@ -650,7 +606,7 @@ public class HwmfWindowing {
private WmfScanObject scanObjects[];

@Override
public HwmfRecordType getRecordType() {
public HwmfRecordType getWmfRecordType() {
return HwmfRecordType.createRegion;
}


+ 4
- 10
src/scratchpad/src/org/apache/poi/hwmf/usermodel/HwmfPicture.java View File

@@ -51,8 +51,7 @@ public class HwmfPicture {
public HwmfPicture(InputStream inputStream) throws IOException {

try (BufferedInputStream bis = new BufferedInputStream(inputStream, 10000);
LittleEndianInputStream leis = new LittleEndianInputStream(bis)) {
try (LittleEndianInputStream leis = new LittleEndianInputStream(inputStream)) {
placeableHeader = HwmfPlaceableHeader.readHeader(leis);
header = new HwmfHeader(leis);

@@ -82,17 +81,12 @@ public class HwmfPicture {
if (wrt == HwmfRecordType.eof) {
break;
}
if (wrt.clazz == null) {
if (wrt.constructor == null) {
throw new IOException("unsupported record type: "+recordFunction);
}

HwmfRecord wr;
try {
wr = wrt.clazz.newInstance();
records.add(wr);
} catch (Exception e) {
throw (IOException)new IOException("can't create wmf record").initCause(e);
}
final HwmfRecord wr = wrt.constructor.get();
records.add(wr);

consumedSize += wr.init(leis, recordSize, recordFunction);
int remainingSize = (int)(recordSize - consumedSize);

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

+ 14
- 18
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,7 +39,7 @@ public class HemfPlusExtractorTest {
@Test
public void testBasic() throws Exception {
//test header
HemfCommentEMFPlus emfPlus = getCommentRecord("SimpleEMF_windows.emf", 0);
EmfCommentDataPlus emfPlus = getCommentRecord("SimpleEMF_windows.emf", 0);
List<HemfPlusRecord> records = emfPlus.getRecords();
assertEquals(1, records.size());
assertEquals(HemfPlusRecordType.header, records.get(0).getRecordType());
@@ -72,24 +72,20 @@ public class HemfPlusExtractorTest {
}


private HemfCommentEMFPlus getCommentRecord(String testFileName, int recordIndex) throws Exception {
InputStream is = null;
HemfCommentEMFPlus returnRecord = null;
private EmfCommentDataPlus getCommentRecord(String testFileName, int recordIndex) throws Exception {
EmfCommentDataPlus returnRecord = null;

try {
is = POIDataSamples.getSpreadSheetInstance().openResourceAsStream(testFileName);
HemfExtractor ex = new HemfExtractor(is);
try (InputStream is = POIDataSamples.getSpreadSheetInstance().openResourceAsStream(testFileName)) {
HemfPicture ex = new HemfPicture(is);
int i = 0;
for (HemfRecord record : ex) {
if (i == recordIndex) {
HemfCommentRecord commentRecord = ((HemfCommentRecord) record);
returnRecord = (HemfCommentEMFPlus) commentRecord.getComment();
EmfComment commentRecord = ((EmfComment) record);
returnRecord = (EmfCommentDataPlus) commentRecord.getCommentData();
break;
}
i++;
}
} finally {
is.close();
}
return returnRecord;
}

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

@@ -0,0 +1,201 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */


package org.apache.poi.hemf.usermodel;

import static org.apache.poi.POITestCase.assertContains;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import java.awt.geom.Point2D;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.poi.POIDataSamples;
import org.apache.poi.hemf.record.emf.HemfComment;
import org.apache.poi.hemf.record.emf.HemfComment.EmfComment;
import org.apache.poi.hemf.record.emf.HemfComment.EmfCommentDataFormat;
import org.apache.poi.hemf.record.emf.HemfComment.EmfCommentDataMultiformats;
import org.apache.poi.hemf.record.emf.HemfHeader;
import org.apache.poi.hemf.record.emf.HemfRecord;
import org.apache.poi.hemf.record.emf.HemfRecordType;
import org.apache.poi.hemf.record.emf.HemfText;
import org.apache.poi.util.IOUtils;
import org.apache.poi.util.RecordFormatException;
import org.junit.Test;

public class HemfPictureTest {

private POIDataSamples samples = POIDataSamples.getSpreadSheetInstance();

@Test
public void testBasicWindows() throws Exception {
try (InputStream is = samples.openResourceAsStream("SimpleEMF_windows.emf")) {
HemfPicture pic = new HemfPicture(is);
HemfHeader header = pic.getHeader();
assertEquals(27864, header.getBytes());
assertEquals(31, header.getRecords());
assertEquals(3, header.getHandles());
assertEquals(346000, header.getMicrometersX());
assertEquals(194000, header.getMicrometersY());

List<HemfRecord> records = pic.getRecords();

assertEquals(31, records.size());
}
}

@Test
public void testBasicMac() throws Exception {
try (InputStream is = samples.openResourceAsStream("SimpleEMF_mac.emf")) {
HemfPicture pic = new HemfPicture(is);
HemfHeader header = pic.getHeader();

int records = 0;
boolean extractedData = false;
for (HemfRecord record : pic) {
if (record.getEmfRecordType() == HemfRecordType.comment) {
HemfComment.EmfCommentData comment = ((EmfComment) record).getCommentData();
if (comment instanceof EmfCommentDataMultiformats) {
for (EmfCommentDataFormat d : ((EmfCommentDataMultiformats) comment).getFormats()) {
byte[] data = d.getRawData();
//make sure header starts at 0
assertEquals('%', data[0]);
assertEquals('P', data[1]);
assertEquals('D', data[2]);
assertEquals('F', data[3]);

//make sure byte array ends at EOF\n
assertEquals('E', data[data.length - 4]);
assertEquals('O', data[data.length - 3]);
assertEquals('F', data[data.length - 2]);
assertEquals('\n', data[data.length - 1]);
extractedData = true;
}
}
}
records++;
}
assertTrue(extractedData);
assertEquals(header.getRecords(), records);
}
}

@Test
public void testMacText() throws Exception {
try (InputStream is = samples.openResourceAsStream("SimpleEMF_mac.emf")) {
HemfPicture pic = new HemfPicture(is);

double lastY = -1;
double lastX = -1;
//derive this from the font information!
long fudgeFactorX = 1000;
StringBuilder sb = new StringBuilder();
for (HemfRecord record : pic) {
if (record.getEmfRecordType().equals(HemfRecordType.exttextoutw)) {
HemfText.ExtTextOutW extTextOutW = (HemfText.ExtTextOutW) record;
Point2D reference = extTextOutW.getTextObject().getReference();
if (lastY > -1 && lastY != reference.getY()) {
sb.append("\n");
lastX = -1;
}
if (lastX > -1 && reference.getX() - lastX > fudgeFactorX) {
sb.append(" ");
}
sb.append(extTextOutW.getText());
lastY = reference.getY();
lastX = reference.getX();
}
}
String txt = sb.toString();
assertContains(txt, "Tika http://incubator.apache.org");
assertContains(txt, "Latest News\n");
}
}

@Test
public void testWindowsText() throws Exception {
try (InputStream is = samples.openResourceAsStream("SimpleEMF_windows.emf")) {
HemfPicture pic = new HemfPicture(is);
double lastY = -1;
double lastX = -1;
long fudgeFactorX = 1000;//derive this from the font or frame/bounds information
StringBuilder sb = new StringBuilder();
Set<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.ExtTextOutW extTextOutW = (HemfText.ExtTextOutW) record;
Point2D reference = extTextOutW.getTextObject().getReference();
if (lastY > -1 && lastY != reference.getY()) {
sb.append("\n");
lastX = -1;
}
if (lastX > -1 && reference.getX() - lastX > fudgeFactorX) {
sb.append(" ");
}
String txt = extTextOutW.getText();
if (expectedParts.contains(txt)) {
foundExpected++;
}
sb.append(txt);
lastY = reference.getY();
lastX = reference.getX();
}
}
String txt = sb.toString();
assertContains(txt, "C:\\Users\\tallison\\\n");
assertContains(txt, "asf2-git-1.x\\tika-\n");
assertEquals(expectedParts.size(), foundExpected);
}
}

@Test(expected = RecordFormatException.class)
public void testInfiniteLoopOnFile() throws Exception {
try (InputStream is = samples.openResourceAsStream("61294.emf")) {
HemfPicture pic = new HemfPicture(is);
for (HemfRecord record : pic) {

}
}
}

@Test(expected = RecordFormatException.class)
public void testInfiniteLoopOnByteArray() throws Exception {
try (InputStream is = samples.openResourceAsStream("61294.emf")) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
IOUtils.copy(is, bos);
is.close();

HemfPicture pic = new HemfPicture(new ByteArrayInputStream(bos.toByteArray()));
for (HemfRecord record : pic) {

}
}
}

/*
govdocs1 064213.doc-0.emf contains an example of extextouta
*/
}

+ 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