mirror of https://github.com/apache/poi.git
Bug 60656 - EMF image support in slideshows
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1858625 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
dfbf788201
commit
ea0e01acc1
|
@ -48,21 +48,21 @@ public enum FileMagic {
|
||||||
0x09, 0x00, // sid=0x0009
|
0x09, 0x00, // sid=0x0009
|
||||||
0x04, 0x00, // size=0x0004
|
0x04, 0x00, // size=0x0004
|
||||||
0x00, 0x00, // unused
|
0x00, 0x00, // unused
|
||||||
0x70, 0x00 // 0x70 = multiple values
|
'?', 0x00 // '?' = multiple values
|
||||||
}),
|
}),
|
||||||
/** BIFF3 raw stream - for Excel 3 */
|
/** BIFF3 raw stream - for Excel 3 */
|
||||||
BIFF3(new byte[]{
|
BIFF3(new byte[]{
|
||||||
0x09, 0x02, // sid=0x0209
|
0x09, 0x02, // sid=0x0209
|
||||||
0x06, 0x00, // size=0x0006
|
0x06, 0x00, // size=0x0006
|
||||||
0x00, 0x00, // unused
|
0x00, 0x00, // unused
|
||||||
0x70, 0x00 // 0x70 = multiple values
|
'?', 0x00 // '?' = multiple values
|
||||||
}),
|
}),
|
||||||
/** BIFF4 raw stream - for Excel 4 */
|
/** BIFF4 raw stream - for Excel 4 */
|
||||||
BIFF4(new byte[]{
|
BIFF4(new byte[]{
|
||||||
0x09, 0x04, // sid=0x0409
|
0x09, 0x04, // sid=0x0409
|
||||||
0x06, 0x00, // size=0x0006
|
0x06, 0x00, // size=0x0006
|
||||||
0x00, 0x00, // unused
|
0x00, 0x00, // unused
|
||||||
0x70, 0x00 // 0x70 = multiple values
|
'?', 0x00 // '? = multiple values
|
||||||
},new byte[]{
|
},new byte[]{
|
||||||
0x09, 0x04, // sid=0x0409
|
0x09, 0x04, // sid=0x0409
|
||||||
0x06, 0x00, // size=0x0006
|
0x06, 0x00, // size=0x0006
|
||||||
|
@ -78,18 +78,22 @@ public enum FileMagic {
|
||||||
/** PDF document */
|
/** PDF document */
|
||||||
PDF("%PDF"),
|
PDF("%PDF"),
|
||||||
/** Some different HTML documents */
|
/** Some different HTML documents */
|
||||||
HTML("<!DOCTYP".getBytes(UTF_8),
|
HTML("<!DOCTYP",
|
||||||
"<html".getBytes(UTF_8),
|
"<html","\n\r<html","\r\n<html","\r<html","\n<html",
|
||||||
"\n\r<html".getBytes(UTF_8),
|
"<HTML","\r\n<HTML","\n\r<HTML","\r<HTML","\n<HTML"),
|
||||||
"\r\n<html".getBytes(UTF_8),
|
|
||||||
"\r<html".getBytes(UTF_8),
|
|
||||||
"\n<html".getBytes(UTF_8),
|
|
||||||
"<HTML".getBytes(UTF_8),
|
|
||||||
"\r\n<HTML".getBytes(UTF_8),
|
|
||||||
"\n\r<HTML".getBytes(UTF_8),
|
|
||||||
"\r<HTML".getBytes(UTF_8),
|
|
||||||
"\n<HTML".getBytes(UTF_8)),
|
|
||||||
WORD2(new byte[]{ (byte)0xdb, (byte)0xa5, 0x2d, 0x00}),
|
WORD2(new byte[]{ (byte)0xdb, (byte)0xa5, 0x2d, 0x00}),
|
||||||
|
/** JPEG image */
|
||||||
|
JPEG(
|
||||||
|
new byte[]{ (byte)0xFF, (byte)0xD8, (byte)0xFF, (byte)0xDB },
|
||||||
|
new byte[]{ (byte)0xFF, (byte)0xD8, (byte)0xFF, (byte)0xE0, '?', '?', 'J', 'F', 'I', 'F', 0x00, 0x01 },
|
||||||
|
new byte[]{ (byte)0xFF, (byte)0xD8, (byte)0xFF, (byte)0xEE },
|
||||||
|
new byte[]{ (byte)0xFF, (byte)0xD8, (byte)0xFF, (byte)0xE1, '?', '?', 'E', 'x', 'i', 'f', 0x00, 0x00 }),
|
||||||
|
/** GIF image */
|
||||||
|
GIF("GIF87a","GIF89a"),
|
||||||
|
/** PNG Image */
|
||||||
|
PNG(new byte[]{ (byte)0x89, 'P', 'N', 'G', 0x0D, 0x0A, 0x1A, 0x0A }),
|
||||||
|
/** TIFF Image */
|
||||||
|
TIFF("II*\u0000", "MM\u0000*" ),
|
||||||
// keep UNKNOWN always as last enum!
|
// keep UNKNOWN always as last enum!
|
||||||
/** UNKNOWN magic */
|
/** UNKNOWN magic */
|
||||||
UNKNOWN(new byte[0]);
|
UNKNOWN(new byte[0]);
|
||||||
|
@ -100,13 +104,17 @@ public enum FileMagic {
|
||||||
this.magic = new byte[1][8];
|
this.magic = new byte[1][8];
|
||||||
LittleEndian.putLong(this.magic[0], 0, magic);
|
LittleEndian.putLong(this.magic[0], 0, magic);
|
||||||
}
|
}
|
||||||
|
|
||||||
FileMagic(byte[]... magic) {
|
FileMagic(byte[]... magic) {
|
||||||
this.magic = magic;
|
this.magic = magic;
|
||||||
}
|
}
|
||||||
|
|
||||||
FileMagic(String magic) {
|
FileMagic(String... magic) {
|
||||||
this(magic.getBytes(LocaleUtil.CHARSET_1252));
|
this.magic = new byte[magic.length][];
|
||||||
|
int i=0;
|
||||||
|
for (String s : magic) {
|
||||||
|
this.magic[i++] = s.getBytes(LocaleUtil.CHARSET_1252);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static FileMagic valueOf(byte[] magic) {
|
public static FileMagic valueOf(byte[] magic) {
|
||||||
|
@ -123,9 +131,7 @@ public enum FileMagic {
|
||||||
private static boolean findMagic(byte[] expected, byte[] actual) {
|
private static boolean findMagic(byte[] expected, byte[] actual) {
|
||||||
int i=0;
|
int i=0;
|
||||||
for (byte expectedByte : expected) {
|
for (byte expectedByte : expected) {
|
||||||
byte actualByte = actual[i++];
|
if (actual[i++] != expectedByte && expectedByte != '?') {
|
||||||
if ((actualByte != expectedByte &&
|
|
||||||
(expectedByte != 0x70 || (actualByte != 0x10 && actualByte != 0x20 && actualByte != 0x40)))) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -204,6 +204,9 @@ public class LittleEndianInputStream extends FilterInputStream implements Little
|
||||||
|
|
||||||
|
|
||||||
public void skipFully(int len) throws IOException {
|
public void skipFully(int len) throws IOException {
|
||||||
|
if (len == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
long skipped = IOUtils.skipFully(this, len);
|
long skipped = IOUtils.skipFully(this, len);
|
||||||
if (skipped > Integer.MAX_VALUE) {
|
if (skipped > Integer.MAX_VALUE) {
|
||||||
throw new IOException("can't skip further than "+Integer.MAX_VALUE);
|
throw new IOException("can't skip further than "+Integer.MAX_VALUE);
|
||||||
|
|
|
@ -221,6 +221,10 @@ public class HemfComment {
|
||||||
return privateData.length;
|
return privateData.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public byte[] getPrivateData() {
|
||||||
|
return privateData;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "\""+new String(privateData, LocaleUtil.CHARSET_1252).replaceAll("\\p{Cntrl}", ".")+"\"";
|
return "\""+new String(privateData, LocaleUtil.CHARSET_1252).replaceAll("\\p{Cntrl}", ".")+"\"";
|
||||||
|
@ -350,7 +354,15 @@ public class HemfComment {
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum EmfFormatSignature {
|
public enum EmfFormatSignature {
|
||||||
|
/**
|
||||||
|
* The value of this member is the sequence of ASCII characters "FME ",
|
||||||
|
* which happens to be the reverse of the string "EMF", and it denotes EMF record data.
|
||||||
|
*/
|
||||||
ENHMETA_SIGNATURE(0x464D4520),
|
ENHMETA_SIGNATURE(0x464D4520),
|
||||||
|
/**
|
||||||
|
* The value of this member is the sequence of ASCII characters "FSPE", which happens to be the reverse
|
||||||
|
* of the string "EPSF", and it denotes encapsulated PostScript (EPS) format data.
|
||||||
|
*/
|
||||||
EPS_SIGNATURE(0x46535045);
|
EPS_SIGNATURE(0x46535045);
|
||||||
|
|
||||||
int id;
|
int id;
|
||||||
|
@ -403,6 +415,10 @@ public class HemfComment {
|
||||||
public byte[] getRawData() {
|
public byte[] getRawData() {
|
||||||
return rawData;
|
return rawData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public EmfFormatSignature getSignature() {
|
||||||
|
return signature;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class EmfCommentDataWMF implements EmfCommentData {
|
public static class EmfCommentDataWMF implements EmfCommentData {
|
||||||
|
|
|
@ -682,7 +682,7 @@ public class HemfFill {
|
||||||
return 4 * LittleEndianConsts.INT_SIZE;
|
return 4 * LittleEndianConsts.INT_SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int readXForm(LittleEndianInputStream leis, AffineTransform xform) {
|
public static int readXForm(LittleEndianInputStream leis, AffineTransform xform) {
|
||||||
// mapping <java AffineTransform> = <xform>
|
// mapping <java AffineTransform> = <xform>
|
||||||
|
|
||||||
// m00 (scaleX) = eM11 (Horizontal scaling component)
|
// m00 (scaleX) = eM11 (Horizontal scaling component)
|
||||||
|
|
|
@ -151,4 +151,59 @@ public class HemfPalette {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The EMR_SETICMMODE record specifies the mode of Image Color Management (ICM) for graphics operations.
|
||||||
|
*/
|
||||||
|
public static class EmfSetIcmMode implements HemfRecord {
|
||||||
|
/** The ICMMode enumeration defines values that specify when to turn on and off ICM. */
|
||||||
|
public enum ICMMode {
|
||||||
|
/**
|
||||||
|
* Turns off Image Color Management (ICM) in the playback device context.
|
||||||
|
* Turns on old-style color correction of halftones.
|
||||||
|
*/
|
||||||
|
ICM_OFF(0x01),
|
||||||
|
/**
|
||||||
|
* Turns on ICM in the playback device context.
|
||||||
|
* Turns off old-style color correction of halftones.
|
||||||
|
*/
|
||||||
|
ICM_ON(0x02),
|
||||||
|
/**
|
||||||
|
* Queries the current state of color management in the playback device context.
|
||||||
|
*/
|
||||||
|
ICM_QUERY(0x03),
|
||||||
|
/**
|
||||||
|
* Turns off ICM in the playback device context, and turns off old-style color correction of halftones.
|
||||||
|
*/
|
||||||
|
ICM_DONE_OUTSIDEDC(0x04)
|
||||||
|
;
|
||||||
|
|
||||||
|
public final int id;
|
||||||
|
|
||||||
|
ICMMode(int id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ICMMode valueOf(int id) {
|
||||||
|
for (ICMMode wrt : values()) {
|
||||||
|
if (wrt.id == id) return wrt;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private ICMMode icmMode;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HemfRecordType getEmfRecordType() {
|
||||||
|
return HemfRecordType.seticmmode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
|
||||||
|
icmMode = ICMMode.valueOf(leis.readInt());
|
||||||
|
return LittleEndianConsts.INT_SIZE;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -121,7 +121,7 @@ public enum HemfRecordType {
|
||||||
extCreatePen(0x0000005F, HemfMisc.EmfExtCreatePen::new),
|
extCreatePen(0x0000005F, HemfMisc.EmfExtCreatePen::new),
|
||||||
polytextouta(0x00000060, HemfText.PolyTextOutA::new),
|
polytextouta(0x00000060, HemfText.PolyTextOutA::new),
|
||||||
polytextoutw(0x00000061, HemfText.PolyTextOutW::new),
|
polytextoutw(0x00000061, HemfText.PolyTextOutW::new),
|
||||||
seticmmode(0x00000062, UnimplementedHemfRecord::new),
|
seticmmode(0x00000062, HemfPalette.EmfSetIcmMode::new),
|
||||||
createcolorspace(0x00000063, UnimplementedHemfRecord::new),
|
createcolorspace(0x00000063, UnimplementedHemfRecord::new),
|
||||||
setcolorspace(0x00000064, UnimplementedHemfRecord::new),
|
setcolorspace(0x00000064, UnimplementedHemfRecord::new),
|
||||||
deletecolorspace(0x00000065, UnimplementedHemfRecord::new),
|
deletecolorspace(0x00000065, UnimplementedHemfRecord::new),
|
||||||
|
|
|
@ -0,0 +1,609 @@
|
||||||
|
/* ====================================================================
|
||||||
|
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.awt.Color;
|
||||||
|
import java.awt.geom.AffineTransform;
|
||||||
|
import java.awt.geom.Point2D;
|
||||||
|
import java.awt.geom.Rectangle2D;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
|
||||||
|
import org.apache.commons.math3.linear.LUDecomposition;
|
||||||
|
import org.apache.commons.math3.linear.MatrixUtils;
|
||||||
|
import org.apache.commons.math3.linear.RealMatrix;
|
||||||
|
import org.apache.poi.hemf.record.emf.HemfFill;
|
||||||
|
import org.apache.poi.hemf.record.emfplus.HemfPlusMisc.EmfPlusObjectId;
|
||||||
|
import org.apache.poi.util.BitField;
|
||||||
|
import org.apache.poi.util.BitFieldFactory;
|
||||||
|
import org.apache.poi.util.IOUtils;
|
||||||
|
import org.apache.poi.util.LittleEndian;
|
||||||
|
import org.apache.poi.util.LittleEndianConsts;
|
||||||
|
import org.apache.poi.util.LittleEndianInputStream;
|
||||||
|
import org.apache.poi.util.StringUtil;
|
||||||
|
|
||||||
|
public class HemfPlusDraw {
|
||||||
|
private static final int MAX_OBJECT_SIZE = 1_000_000;
|
||||||
|
|
||||||
|
public enum UnitType {
|
||||||
|
/** Specifies a unit of logical distance within the world space. */
|
||||||
|
World(0x00),
|
||||||
|
/** Specifies a unit of distance based on the characteristics of the physical display. */
|
||||||
|
Display(0x01),
|
||||||
|
/** Specifies a unit of 1 pixel. */
|
||||||
|
Pixel(0x02),
|
||||||
|
/** Specifies a unit of 1 printer's point, or 1/72 inch. */
|
||||||
|
Point(0x03),
|
||||||
|
/** Specifies a unit of 1 inch. */
|
||||||
|
Inch(0x04),
|
||||||
|
/** Specifies a unit of 1/300 inch. */
|
||||||
|
Document(0x05),
|
||||||
|
/** Specifies a unit of 1 millimeter. */
|
||||||
|
Millimeter(0x06)
|
||||||
|
;
|
||||||
|
|
||||||
|
public final int id;
|
||||||
|
|
||||||
|
UnitType(int id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static UnitType valueOf(int id) {
|
||||||
|
for (UnitType wrt : values()) {
|
||||||
|
if (wrt.id == id) return wrt;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface EmfPlusCompressed {
|
||||||
|
/**
|
||||||
|
* This bit indicates whether the data in the RectData field is compressed.
|
||||||
|
* If set, RectData contains an EmfPlusRect object.
|
||||||
|
* If clear, RectData contains an EmfPlusRectF object object.
|
||||||
|
*/
|
||||||
|
BitField COMPRESSED = BitFieldFactory.getInstance(0x4000);
|
||||||
|
|
||||||
|
int getFlags();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The index in the EMF+ Object Table to associate with the object
|
||||||
|
* created by this record. The value MUST be zero to 63, inclusive.
|
||||||
|
*/
|
||||||
|
default boolean isCompressed() {
|
||||||
|
return COMPRESSED.isSet(getFlags());
|
||||||
|
}
|
||||||
|
|
||||||
|
default BiFunction<LittleEndianInputStream, Rectangle2D, Integer> getReadRect() {
|
||||||
|
return isCompressed() ? HemfPlusDraw::readRectS : HemfPlusDraw::readRectF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The EmfPlusDrawPath record specifies drawing a graphics path
|
||||||
|
*/
|
||||||
|
public static class EmfPlusDrawPath implements HemfPlusRecord {
|
||||||
|
private int flags;
|
||||||
|
private int penId;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HemfPlusRecordType getEmfPlusRecordType() {
|
||||||
|
return HemfPlusRecordType.drawPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getFlags() {
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long init(LittleEndianInputStream leis, long dataSize, long recordId, int flags) throws IOException {
|
||||||
|
this.flags = flags;
|
||||||
|
|
||||||
|
// A 32-bit unsigned integer that specifies an index in the EMF+ Object Table for an EmfPlusPen object
|
||||||
|
// to use for drawing the EmfPlusPath. The value MUST be zero to 63, inclusive.
|
||||||
|
penId = leis.readInt();
|
||||||
|
assert (0 <= penId && penId <= 63);
|
||||||
|
|
||||||
|
assert (dataSize == LittleEndianConsts.INT_SIZE);
|
||||||
|
|
||||||
|
return LittleEndianConsts.INT_SIZE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The EmfPlusFillRects record specifies filling the interiors of a series of rectangles.
|
||||||
|
*/
|
||||||
|
public static class EmfPlusFillRects implements HemfPlusRecord, EmfPlusCompressed {
|
||||||
|
/**
|
||||||
|
* If set, brushId specifies a color as an EmfPlusARGB object.
|
||||||
|
* If clear, brushId contains the index of an EmfPlusBrush object in the EMF+ Object Table.
|
||||||
|
*/
|
||||||
|
private static final BitField SOLID_COLOR = BitFieldFactory.getInstance(0x8000);
|
||||||
|
|
||||||
|
private int flags;
|
||||||
|
private int brushId;
|
||||||
|
private final ArrayList<Rectangle2D> rectData = new ArrayList<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HemfPlusRecordType getEmfPlusRecordType() {
|
||||||
|
return HemfPlusRecordType.fillRects;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getFlags() {
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long init(LittleEndianInputStream leis, long dataSize, long recordId, int flags) throws IOException {
|
||||||
|
this.flags = flags;
|
||||||
|
|
||||||
|
// A 32-bit unsigned integer that defines the brush, the content of which is
|
||||||
|
// determined by the S bit in the Flags field.
|
||||||
|
brushId = leis.readInt();
|
||||||
|
|
||||||
|
// A 32-bit unsigned integer that specifies the number of rectangles in the RectData field.
|
||||||
|
int count = leis.readInt();
|
||||||
|
|
||||||
|
BiFunction<LittleEndianInputStream, Rectangle2D, Integer> readRect = getReadRect();
|
||||||
|
|
||||||
|
rectData.ensureCapacity(count);
|
||||||
|
|
||||||
|
int size = 2 * LittleEndianConsts.INT_SIZE;
|
||||||
|
for (int i = 0; i<count; i++) {
|
||||||
|
Rectangle2D rect = new Rectangle2D.Double();
|
||||||
|
size += readRect.apply(leis, rect);
|
||||||
|
rectData.add(rect);
|
||||||
|
}
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class EmfPlusDrawImagePoints implements HemfPlusRecord, EmfPlusObjectId, EmfPlusCompressed {
|
||||||
|
/**
|
||||||
|
* This bit indicates that the rendering of the image includes applying an effect.
|
||||||
|
* If set, an object of the Effect class MUST have been specified in an earlier EmfPlusSerializableObject record.
|
||||||
|
*/
|
||||||
|
private static final BitField EFFECT = BitFieldFactory.getInstance(0x2000);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This bit indicates whether the PointData field specifies relative or absolute locations.
|
||||||
|
* If set, each element in PointData specifies a location in the coordinate space that is relative to the
|
||||||
|
* location specified by the previous element in the array. In the case of the first element in PointData,
|
||||||
|
* a previous location at coordinates (0,0) is assumed.
|
||||||
|
* If clear, PointData specifies absolute locations according to the {@link #isCompressed()} flag.
|
||||||
|
*
|
||||||
|
* Note If this flag is set, the {@link #isCompressed()} flag (above) is undefined and MUST be ignored.
|
||||||
|
*/
|
||||||
|
private static final BitField POSITION = BitFieldFactory.getInstance(0x0800);
|
||||||
|
|
||||||
|
private int flags;
|
||||||
|
private int imageAttributesID;
|
||||||
|
private UnitType srcUnit;
|
||||||
|
private final Rectangle2D srcRect = new Rectangle2D.Double();
|
||||||
|
private final Point2D upperLeft = new Point2D.Double();
|
||||||
|
private final Point2D lowerRight = new Point2D.Double();
|
||||||
|
private final Point2D lowerLeft = new Point2D.Double();
|
||||||
|
private final AffineTransform trans = new AffineTransform();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HemfPlusRecordType getEmfPlusRecordType() {
|
||||||
|
return HemfPlusRecordType.drawImagePoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getFlags() {
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long init(LittleEndianInputStream leis, long dataSize, long recordId, int flags) throws IOException {
|
||||||
|
this.flags = flags;
|
||||||
|
|
||||||
|
// A 32-bit unsigned integer that contains the index of the
|
||||||
|
// optional EmfPlusImageAttributes object in the EMF+ Object Table.
|
||||||
|
imageAttributesID = leis.readInt();
|
||||||
|
|
||||||
|
// A 32-bit signed integer that defines the units of the SrcRect field.
|
||||||
|
// It MUST be the UnitPixel value of the UnitType enumeration
|
||||||
|
srcUnit = UnitType.valueOf(leis.readInt());
|
||||||
|
assert(srcUnit == UnitType.Pixel);
|
||||||
|
|
||||||
|
int size = 2 * LittleEndianConsts.INT_SIZE;
|
||||||
|
|
||||||
|
// An EmfPlusRectF object that defines a portion of the image to be rendered.
|
||||||
|
size += readRectF(leis, srcRect);
|
||||||
|
|
||||||
|
// A 32-bit unsigned integer that specifies the number of points in the PointData array.
|
||||||
|
// Exactly 3 points MUST be specified.
|
||||||
|
int count = leis.readInt();
|
||||||
|
assert(count == 3);
|
||||||
|
size += LittleEndianConsts.INT_SIZE;
|
||||||
|
|
||||||
|
BiFunction<LittleEndianInputStream, Point2D, Integer> readPoint;
|
||||||
|
|
||||||
|
if (POSITION.isSet(flags)) {
|
||||||
|
// If the POSITION flag is set in the Flags, the points specify relative locations.
|
||||||
|
readPoint = HemfPlusDraw::readPointR;
|
||||||
|
} else if (isCompressed()) {
|
||||||
|
// If the POSITION bit is clear and the COMPRESSED bit is set in the Flags field, the points
|
||||||
|
// specify absolute locations with integer values.
|
||||||
|
readPoint = HemfPlusDraw::readPointS;
|
||||||
|
} else {
|
||||||
|
// If the POSITION bit is clear and the COMPRESSED bit is clear in the Flags field, the points
|
||||||
|
// specify absolute locations with floating-point values.
|
||||||
|
readPoint = HemfPlusDraw::readPointF;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: handle relative coordinates
|
||||||
|
|
||||||
|
// An array of Count points that specify three points of a parallelogram.
|
||||||
|
// The three points represent the upper-left, upper-right, and lower-left corners of the parallelogram.
|
||||||
|
// The fourth point of the parallelogram is extrapolated from the first three.
|
||||||
|
// The portion of the image specified by the SrcRect field SHOULD have scaling and shearing transforms
|
||||||
|
// applied if necessary to fit inside the parallelogram.
|
||||||
|
|
||||||
|
|
||||||
|
// size += readPoint.apply(leis, upperLeft);
|
||||||
|
// size += readPoint.apply(leis, upperRight);
|
||||||
|
// size += readPoint.apply(leis, lowerLeft);
|
||||||
|
|
||||||
|
size += readPoint.apply(leis, lowerLeft);
|
||||||
|
size += readPoint.apply(leis, lowerRight);
|
||||||
|
size += readPoint.apply(leis, upperLeft);
|
||||||
|
|
||||||
|
// https://math.stackexchange.com/questions/2772737/how-to-transform-arbitrary-rectangle-into-specific-parallelogram
|
||||||
|
|
||||||
|
RealMatrix para2normal = MatrixUtils.createRealMatrix(new double[][] {
|
||||||
|
{ lowerLeft.getX(), lowerRight.getX(), upperLeft.getX() },
|
||||||
|
{ lowerLeft.getY(), lowerRight.getY(), upperLeft.getY() },
|
||||||
|
{ 1, 1, 1 }
|
||||||
|
});
|
||||||
|
|
||||||
|
RealMatrix rect2normal = MatrixUtils.createRealMatrix(new double[][]{
|
||||||
|
{ srcRect.getMinX(), srcRect.getMaxX(), srcRect.getMinX() },
|
||||||
|
{ srcRect.getMinY(), srcRect.getMinY(), srcRect.getMaxY() },
|
||||||
|
{ 1, 1, 1 }
|
||||||
|
});
|
||||||
|
|
||||||
|
RealMatrix normal2rect = new LUDecomposition(rect2normal).getSolver().getInverse();
|
||||||
|
double[][] m = para2normal.multiply(normal2rect).getData();
|
||||||
|
trans.setTransform(round10(m[0][0]), round10(m[1][0]), round10(m[0][1]), round10(m[1][1]), round10(m[0][2]), round10(m[1][2]));
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The EmfPlusDrawImage record specifies drawing a scaled image. */
|
||||||
|
public static class EmfPlusDrawImage implements HemfPlusRecord, EmfPlusObjectId, EmfPlusCompressed {
|
||||||
|
private int flags;
|
||||||
|
private int imageAttributesID;
|
||||||
|
private UnitType srcUnit;
|
||||||
|
private final Rectangle2D srcRect = new Rectangle2D.Double();
|
||||||
|
private final Rectangle2D rectData = new Rectangle2D.Double();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HemfPlusRecordType getEmfPlusRecordType() {
|
||||||
|
return HemfPlusRecordType.drawImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getFlags() {
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long init(LittleEndianInputStream leis, long dataSize, long recordId, int flags) throws IOException {
|
||||||
|
this.flags = flags;
|
||||||
|
|
||||||
|
// A 32-bit unsigned integer that contains the index of the
|
||||||
|
// optional EmfPlusImageAttributes object in the EMF+ Object Table.
|
||||||
|
imageAttributesID = leis.readInt();
|
||||||
|
|
||||||
|
// A 32-bit signed integer that defines the units of the SrcRect field.
|
||||||
|
// It MUST be the UnitPixel value of the UnitType enumeration
|
||||||
|
srcUnit = UnitType.valueOf(leis.readInt());
|
||||||
|
assert(srcUnit == UnitType.Pixel);
|
||||||
|
|
||||||
|
int size = 2 * LittleEndianConsts.INT_SIZE;
|
||||||
|
|
||||||
|
// An EmfPlusRectF object that specifies a portion of the image to be rendered. The portion of the image
|
||||||
|
// specified by this rectangle is scaled to fit the destination rectangle specified by the RectData field.
|
||||||
|
size += readRectF(leis, srcRect);
|
||||||
|
|
||||||
|
// Either an EmfPlusRect or EmfPlusRectF object that defines the bounding box of the image. The portion of
|
||||||
|
// the image specified by the SrcRect field is scaled to fit this rectangle.
|
||||||
|
size += getReadRect().apply(leis, rectData);
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The EmfPlusFillRegion record specifies filling the interior of a graphics region. */
|
||||||
|
public static class EmfPlusFillRegion implements HemfPlusRecord {
|
||||||
|
private static final BitField SOLID_COLOR = BitFieldFactory.getInstance(0x8000);
|
||||||
|
|
||||||
|
private int flags;
|
||||||
|
private final byte[] brushId = new byte[LittleEndianConsts.INT_SIZE];
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HemfPlusRecordType getEmfPlusRecordType() {
|
||||||
|
return HemfPlusRecordType.fillRegion;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getFlags() {
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSolidColor() {
|
||||||
|
return SOLID_COLOR.isSet(getFlags());
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBrushId() {
|
||||||
|
return (isSolidColor()) ? -1 : LittleEndian.getInt(brushId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Color getSolidColor() {
|
||||||
|
return (isSolidColor()) ? new Color(brushId[2], brushId[1], brushId[0], brushId[3]) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long init(LittleEndianInputStream leis, long dataSize, long recordId, int flags) throws IOException {
|
||||||
|
this.flags = flags;
|
||||||
|
|
||||||
|
// A 32-bit unsigned integer that defines the brush, the content of which is determined by
|
||||||
|
// the SOLID_COLOR bit in the Flags field.
|
||||||
|
// If SOLID_COLOR is set, BrushId specifies a color as an EmfPlusARGB object.
|
||||||
|
// If clear, BrushId contains the index of an EmfPlusBrush object in the EMF+ Object Table.
|
||||||
|
leis.readFully(brushId);
|
||||||
|
|
||||||
|
return LittleEndianConsts.INT_SIZE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The EmfPlusFillPath record specifies filling the interior of a graphics path. */
|
||||||
|
public static class EmfPlusFillPath extends EmfPlusFillRegion {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HemfPlusRecordType getEmfPlusRecordType() {
|
||||||
|
return HemfPlusRecordType.fillPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The EmfPlusDrawDriverString record specifies text output with character positions. */
|
||||||
|
public static class EmfPlusDrawDriverString implements HemfPlusRecord, EmfPlusObjectId {
|
||||||
|
/**
|
||||||
|
* If set, brushId specifies a color as an EmfPlusARGB object.
|
||||||
|
* If clear, brushId contains the index of an EmfPlusBrush object in the EMF+ Object Table.
|
||||||
|
*/
|
||||||
|
private static final BitField SOLID_COLOR = BitFieldFactory.getInstance(0x8000);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If set, the positions of character glyphs SHOULD be specified in a character map lookup table.
|
||||||
|
* If clear, the glyph positions SHOULD be obtained from an array of coordinates.
|
||||||
|
*/
|
||||||
|
private static final BitField CMAP_LOOKUP = BitFieldFactory.getInstance(0x0001);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If set, the string SHOULD be rendered vertically.
|
||||||
|
* If clear, the string SHOULD be rendered horizontally.
|
||||||
|
*/
|
||||||
|
private static final BitField VERTICAL = BitFieldFactory.getInstance(0x0002);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If set, character glyph positions SHOULD be calculated relative to the position of the first glyph.
|
||||||
|
* If clear, the glyph positions SHOULD be obtained from an array of coordinates.
|
||||||
|
*/
|
||||||
|
private static final BitField REALIZED_ADVANCE = BitFieldFactory.getInstance(0x0004);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If set, less memory SHOULD be used to cache anti-aliased glyphs, which produces lower quality text rendering.
|
||||||
|
* If clear, more memory SHOULD be used, which produces higher quality text rendering.
|
||||||
|
*/
|
||||||
|
private static final BitField LIMIT_SUBPIXEL = BitFieldFactory.getInstance(0x0008);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private int flags;
|
||||||
|
private int brushId;
|
||||||
|
private int optionsFlags;
|
||||||
|
private String glyphs;
|
||||||
|
private final List<Point2D> glpyhPos = new ArrayList<>();
|
||||||
|
private final AffineTransform transformMatrix = new AffineTransform();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HemfPlusRecordType getEmfPlusRecordType() {
|
||||||
|
return HemfPlusRecordType.drawDriverstring;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getFlags() {
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long init(LittleEndianInputStream leis, long dataSize, long recordId, int flags) throws IOException {
|
||||||
|
this.flags = flags;
|
||||||
|
|
||||||
|
// A 32-bit unsigned integer that specifies either the foreground color of the text or a graphics brush,
|
||||||
|
// depending on the value of the SOLID_COLOR flag in the Flags.
|
||||||
|
brushId = leis.readInt();
|
||||||
|
|
||||||
|
// A 32-bit unsigned integer that specifies the spacing, orientation, and quality of rendering for the
|
||||||
|
// string. This value MUST be composed of DriverStringOptions flags
|
||||||
|
optionsFlags = leis.readInt();
|
||||||
|
|
||||||
|
// A 32-bit unsigned integer that specifies whether a transform matrix is present in the
|
||||||
|
// TransformMatrix field.
|
||||||
|
boolean hasMatrix = leis.readInt() == 1;
|
||||||
|
|
||||||
|
// A 32-bit unsigned integer that specifies number of glyphs in the string.
|
||||||
|
int glyphCount = leis.readInt();
|
||||||
|
|
||||||
|
int size = 4 * LittleEndianConsts.INT_SIZE;
|
||||||
|
|
||||||
|
// TOOD: implement Non-Cmap-Lookup correctly
|
||||||
|
|
||||||
|
// If the CMAP_LOOKUP flag in the optionsFlags field is set, each value in this array specifies a
|
||||||
|
// Unicode character. Otherwise, each value specifies an index to a character glyph in the EmfPlusFont
|
||||||
|
// object specified by the ObjectId value in Flags field.
|
||||||
|
byte[] glyphBuf = IOUtils.toByteArray(leis, glyphCount*2, MAX_OBJECT_SIZE);
|
||||||
|
glyphs = StringUtil.getFromUnicodeLE(glyphBuf);
|
||||||
|
|
||||||
|
size += glyphBuf.length;
|
||||||
|
|
||||||
|
// An array of EmfPlusPointF objects that specify the output position of each character glyph.
|
||||||
|
// There MUST be GlyphCount elements, which have a one-to-one correspondence with the elements
|
||||||
|
// in the Glyphs array.
|
||||||
|
//
|
||||||
|
// Glyph positions are calculated from the position of the first glyph if the REALIZED_ADVANCE flag in
|
||||||
|
// Options flags is set. In this case, GlyphPos specifies the position of the first glyph only.
|
||||||
|
int glyphPosCnt = REALIZED_ADVANCE.isSet(optionsFlags) ? 1 : glyphCount;
|
||||||
|
for (int i=0; i<glyphCount; i++) {
|
||||||
|
Point2D p = new Point2D.Double();
|
||||||
|
size += readPointF(leis, p);
|
||||||
|
glpyhPos.add(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasMatrix) {
|
||||||
|
size += HemfFill.readXForm(leis, transformMatrix);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The EmfPlusDrawRects record specifies drawing a series of rectangles. */
|
||||||
|
public static class EmfPlusDrawRects implements HemfPlusRecord, EmfPlusObjectId, EmfPlusCompressed {
|
||||||
|
private int flags;
|
||||||
|
private final List<Rectangle2D> rectData = new ArrayList<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HemfPlusRecordType getEmfPlusRecordType() {
|
||||||
|
return HemfPlusRecordType.drawRects;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getFlags() {
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long init(LittleEndianInputStream leis, long dataSize, long recordId, int flags) throws IOException {
|
||||||
|
this.flags = flags;
|
||||||
|
|
||||||
|
// A 32-bit unsigned integer that specifies the number of rectangles in the RectData member.
|
||||||
|
int count = leis.readInt();
|
||||||
|
int size = LittleEndianConsts.INT_SIZE;
|
||||||
|
|
||||||
|
BiFunction<LittleEndianInputStream, Rectangle2D, Integer> readRect = getReadRect();
|
||||||
|
|
||||||
|
for (int i=0; i<count; i++) {
|
||||||
|
Rectangle2D rect = new Rectangle2D.Double();
|
||||||
|
size += readRect.apply(leis, rect);
|
||||||
|
}
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static double round10(double d) {
|
||||||
|
return new BigDecimal(d).setScale(10, BigDecimal.ROUND_HALF_UP).doubleValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
static int readRectS(LittleEndianInputStream leis, Rectangle2D bounds) {
|
||||||
|
// A 16-bit signed integer that defines the ... coordinate
|
||||||
|
final int x = leis.readShort();
|
||||||
|
final int y = leis.readShort();
|
||||||
|
final int width = leis.readShort();
|
||||||
|
final int height = leis.readShort();
|
||||||
|
bounds.setRect(x, y, width, height);
|
||||||
|
|
||||||
|
return 4 * LittleEndianConsts.SHORT_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int readRectF(LittleEndianInputStream leis, Rectangle2D bounds) {
|
||||||
|
// A 32-bit floating-point that defines the ... coordinate
|
||||||
|
final double x = leis.readFloat();
|
||||||
|
final double y = leis.readFloat();
|
||||||
|
final double width = leis.readFloat();
|
||||||
|
final double height = leis.readFloat();
|
||||||
|
bounds.setRect(x, y, width, height);
|
||||||
|
|
||||||
|
return 4 * LittleEndianConsts.INT_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The EmfPlusPoint object specifies an ordered pair of integer (X,Y) values that define an absolute
|
||||||
|
* location in a coordinate space.
|
||||||
|
*/
|
||||||
|
static int readPointS(LittleEndianInputStream leis, Point2D point) {
|
||||||
|
double x = leis.readShort();
|
||||||
|
double y = leis.readShort();
|
||||||
|
point.setLocation(x,y);
|
||||||
|
return 2*LittleEndianConsts.SHORT_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The EmfPlusPointF object specifies an ordered pair of floating-point (X,Y) values that define an
|
||||||
|
* absolute location in a coordinate space.
|
||||||
|
*/
|
||||||
|
static int readPointF(LittleEndianInputStream leis, Point2D point) {
|
||||||
|
double x = leis.readFloat();
|
||||||
|
double y = leis.readFloat();
|
||||||
|
point.setLocation(x,y);
|
||||||
|
return 2*LittleEndianConsts.INT_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The EmfPlusPointR object specifies an ordered pair of integer (X,Y) values that define a relative
|
||||||
|
* location in a coordinate space.
|
||||||
|
*/
|
||||||
|
static int readPointR(LittleEndianInputStream leis, Point2D point) {
|
||||||
|
int[] p = { 0 };
|
||||||
|
int size = readEmfPlusInteger(leis, p);
|
||||||
|
double x = p[0];
|
||||||
|
size += readEmfPlusInteger(leis, p);
|
||||||
|
double y = p[0];
|
||||||
|
point.setLocation(x,y);
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int readEmfPlusInteger(LittleEndianInputStream leis, int[] value) {
|
||||||
|
value[0] = leis.readByte();
|
||||||
|
// check for EmfPlusInteger7 value
|
||||||
|
if ((value[0] & 0x80) == 0) {
|
||||||
|
return LittleEndianConsts.BYTE_SIZE;
|
||||||
|
}
|
||||||
|
// ok we've read a EmfPlusInteger15
|
||||||
|
value[0] = ((value[0] << 8) | leis.readByte()) & 0x7FFF;
|
||||||
|
return LittleEndianConsts.SHORT_SIZE;
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,15 +20,40 @@ package org.apache.poi.hemf.record.emfplus;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.apache.poi.util.BitField;
|
||||||
|
import org.apache.poi.util.BitFieldFactory;
|
||||||
import org.apache.poi.util.Internal;
|
import org.apache.poi.util.Internal;
|
||||||
import org.apache.poi.util.LittleEndianConsts;
|
import org.apache.poi.util.LittleEndianConsts;
|
||||||
import org.apache.poi.util.LittleEndianInputStream;
|
import org.apache.poi.util.LittleEndianInputStream;
|
||||||
|
|
||||||
@Internal
|
@Internal
|
||||||
public class HemfPlusHeader implements HemfPlusRecord {
|
public class HemfPlusHeader implements HemfPlusRecord {
|
||||||
|
/**
|
||||||
|
* The GraphicsVersion enumeration defines versions of operating system graphics that are used to
|
||||||
|
* create EMF+ metafiles.
|
||||||
|
*/
|
||||||
|
public enum GraphicsVersion {
|
||||||
|
V1(0x0001),
|
||||||
|
V1_1(0x0002)
|
||||||
|
;
|
||||||
|
|
||||||
|
public final int id;
|
||||||
|
|
||||||
|
GraphicsVersion(int id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GraphicsVersion valueOf(int id) {
|
||||||
|
for (GraphicsVersion wrt : values()) {
|
||||||
|
if (wrt.id == id) return wrt;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private int flags;
|
private int flags;
|
||||||
private long version; //hack for now; replace with EmfPlusGraphicsVersion object
|
private final EmfPlusGraphicsVersion version = new EmfPlusGraphicsVersion();
|
||||||
private long emfPlusFlags;
|
private long emfPlusFlags;
|
||||||
private long logicalDpiX;
|
private long logicalDpiX;
|
||||||
private long logicalDpiY;
|
private long logicalDpiY;
|
||||||
|
@ -45,11 +70,9 @@ public class HemfPlusHeader implements HemfPlusRecord {
|
||||||
@Override
|
@Override
|
||||||
public long init(LittleEndianInputStream leis, long dataSize, long recordId, int flags) throws IOException {
|
public long init(LittleEndianInputStream leis, long dataSize, long recordId, int flags) throws IOException {
|
||||||
this.flags = flags;
|
this.flags = flags;
|
||||||
version = leis.readUInt();
|
version.init(leis);
|
||||||
|
|
||||||
// verify MetafileSignature (20 bits) == 0xDBC01 and
|
assert(version.getMetafileSignature() == 0xDBC01 && version.getGraphicsVersion() != null);
|
||||||
// GraphicsVersion (12 bits) in (1 or 2)
|
|
||||||
assert((version & 0xFFFFFA00) == 0xDBC01000L && ((version & 0x3FF) == 1 || (version & 0x3FF) == 2));
|
|
||||||
|
|
||||||
emfPlusFlags = leis.readUInt();
|
emfPlusFlags = leis.readUInt();
|
||||||
|
|
||||||
|
@ -58,7 +81,7 @@ public class HemfPlusHeader implements HemfPlusRecord {
|
||||||
return 4* LittleEndianConsts.INT_SIZE;
|
return 4* LittleEndianConsts.INT_SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getVersion() {
|
public EmfPlusGraphicsVersion getVersion() {
|
||||||
return version;
|
return version;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,4 +107,38 @@ public class HemfPlusHeader implements HemfPlusRecord {
|
||||||
", logicalDpiY=" + logicalDpiY +
|
", logicalDpiY=" + logicalDpiY +
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class EmfPlusGraphicsVersion {
|
||||||
|
private static final BitField METAFILE_SIGNATURE = BitFieldFactory.getInstance(0xFFFFF000);
|
||||||
|
|
||||||
|
private static final BitField GRAPHICS_VERSION = BitFieldFactory.getInstance(0x00000FFF);
|
||||||
|
|
||||||
|
|
||||||
|
private int metafileSignature;
|
||||||
|
private GraphicsVersion graphicsVersion;
|
||||||
|
|
||||||
|
|
||||||
|
public int getMetafileSignature() {
|
||||||
|
return metafileSignature;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GraphicsVersion getGraphicsVersion() {
|
||||||
|
return graphicsVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long init(LittleEndianInputStream leis) throws IOException {
|
||||||
|
int val = leis.readInt();
|
||||||
|
// A value that identifies the type of metafile. The value for an EMF+ metafile is 0xDBC01.
|
||||||
|
metafileSignature = METAFILE_SIGNATURE.getValue(val);
|
||||||
|
// The version of operating system graphics. This value MUST be defined in the GraphicsVersion enumeration
|
||||||
|
graphicsVersion = GraphicsVersion.valueOf(GRAPHICS_VERSION.getValue(val));
|
||||||
|
|
||||||
|
return LittleEndianConsts.INT_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
return "{ metafileSignature=0x"+Integer.toHexString(metafileSignature)+
|
||||||
|
" , graphicsVersion='"+graphicsVersion+"' }";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,346 @@
|
||||||
|
/* ====================================================================
|
||||||
|
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 static org.apache.poi.hemf.record.emfplus.HemfPlusDraw.readRectF;
|
||||||
|
|
||||||
|
import java.awt.geom.AffineTransform;
|
||||||
|
import java.awt.geom.Point2D;
|
||||||
|
import java.awt.geom.Rectangle2D;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.apache.poi.hemf.record.emf.HemfFill;
|
||||||
|
import org.apache.poi.util.BitField;
|
||||||
|
import org.apache.poi.util.BitFieldFactory;
|
||||||
|
import org.apache.poi.util.LittleEndian;
|
||||||
|
import org.apache.poi.util.LittleEndianConsts;
|
||||||
|
import org.apache.poi.util.LittleEndianInputStream;
|
||||||
|
|
||||||
|
public class HemfPlusMisc {
|
||||||
|
public interface EmfPlusObjectId {
|
||||||
|
BitField OBJECT_ID = BitFieldFactory.getInstance(0x00FF);
|
||||||
|
|
||||||
|
int getFlags();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The index in the EMF+ Object Table to associate with the object
|
||||||
|
* created by this record. The value MUST be zero to 63, inclusive.
|
||||||
|
*/
|
||||||
|
default int getObjectId() {
|
||||||
|
return OBJECT_ID.getValue(getFlags());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum CombineMode {
|
||||||
|
CombineModeReplace(0x00000000),
|
||||||
|
CombineModeIntersect(0x00000001),
|
||||||
|
CombineModeUnion(0x00000002),
|
||||||
|
CombineModeXOR(0x00000003),
|
||||||
|
CombineModeExclude(0x00000004),
|
||||||
|
CombineModeComplement(0x00000005)
|
||||||
|
;
|
||||||
|
|
||||||
|
public final int id;
|
||||||
|
|
||||||
|
CombineMode(int id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CombineMode valueOf(int id) {
|
||||||
|
for (CombineMode wrt : values()) {
|
||||||
|
if (wrt.id == id) return wrt;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static abstract class EmfPlusFlagOnly implements HemfPlusRecord {
|
||||||
|
private int flags;
|
||||||
|
private HemfPlusRecordType recordType;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getFlags() {
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final HemfPlusRecordType getEmfPlusRecordType() {
|
||||||
|
return recordType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long init(LittleEndianInputStream leis, long dataSize, long recordId, int flags) throws IOException {
|
||||||
|
this.flags = flags;
|
||||||
|
assert(dataSize == 0);
|
||||||
|
recordType = HemfPlusRecordType.getById(recordId);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class EmfPlusEOF extends EmfPlusFlagOnly {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The EmfPlusSetPixelOffsetMode record specifies how pixels are centered with respect to the
|
||||||
|
* coordinates of the drawing surface.
|
||||||
|
*/
|
||||||
|
public static class EmfPlusSetPixelOffsetMode extends EmfPlusFlagOnly {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The EmfPlusSetAntiAliasMode record specifies the anti-aliasing mode for text output.
|
||||||
|
*/
|
||||||
|
public static class EmfPlusSetAntiAliasMode extends EmfPlusFlagOnly {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The EmfPlusSetCompositingMode record specifies how source colors are combined with background colors.
|
||||||
|
*/
|
||||||
|
public static class EmfPlusSetCompositingMode extends EmfPlusFlagOnly {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The EmfPlusSetCompositingQuality record specifies the desired level of quality for creating
|
||||||
|
* composite images from multiple objects.
|
||||||
|
*/
|
||||||
|
public static class EmfPlusSetCompositingQuality extends EmfPlusFlagOnly {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The EmfPlusSetInterpolationMode record specifies how image scaling, including stretching and
|
||||||
|
* shrinking, is performed.
|
||||||
|
*/
|
||||||
|
public static class EmfPlusSetInterpolationMode extends EmfPlusFlagOnly {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The EmfPlusGetDC record specifies that subsequent EMF records encountered in the metafile
|
||||||
|
* SHOULD be processed.
|
||||||
|
*/
|
||||||
|
public static class EmfPlusGetDC extends EmfPlusFlagOnly {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The EmfPlusSetTextRenderingHint record specifies the quality of text rendering, including the type
|
||||||
|
* of anti-aliasing.
|
||||||
|
*/
|
||||||
|
public static class EmfPlusSetTextRenderingHint extends EmfPlusFlagOnly {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The EmfPlusResetWorldTransform record resets the current world space transform to the identify matrix.
|
||||||
|
*/
|
||||||
|
public static class EmfPlusResetWorldTransform extends EmfPlusFlagOnly {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The EmfPlusSetWorldTransform record sets the world transform according to the values in a
|
||||||
|
* specified transform matrix.
|
||||||
|
*/
|
||||||
|
public static class EmfPlusSetWorldTransform implements HemfPlusRecord {
|
||||||
|
private int flags;
|
||||||
|
private final AffineTransform matrixData = new AffineTransform();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HemfPlusRecordType getEmfPlusRecordType() {
|
||||||
|
return HemfPlusRecordType.setWorldTransform;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getFlags() {
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long init(LittleEndianInputStream leis, long dataSize, long recordId, int flags) throws IOException {
|
||||||
|
this.flags = flags;
|
||||||
|
|
||||||
|
return HemfFill.readXForm(leis, matrixData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The EmfPlusMultiplyWorldTransform record multiplies the current world space transform by a
|
||||||
|
* specified transform matrix.
|
||||||
|
*/
|
||||||
|
public static class EmfPlusMultiplyWorldTransform extends EmfPlusSetWorldTransform {
|
||||||
|
@Override
|
||||||
|
public HemfPlusRecordType getEmfPlusRecordType() {
|
||||||
|
return HemfPlusRecordType.multiplyWorldTransform;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The EmfPlusSetPageTransform record specifies scaling factors and units for converting page space
|
||||||
|
* coordinates to device space coordinates.
|
||||||
|
*/
|
||||||
|
public static class EmfPlusSetPageTransform implements HemfPlusRecord {
|
||||||
|
private int flags;
|
||||||
|
private double pageScale;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HemfPlusRecordType getEmfPlusRecordType() {
|
||||||
|
return HemfPlusRecordType.setPageTransform;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getFlags() {
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long init(LittleEndianInputStream leis, long dataSize, long recordId, int flags) throws IOException {
|
||||||
|
this.flags = flags;
|
||||||
|
pageScale = leis.readFloat();
|
||||||
|
return LittleEndianConsts.INT_SIZE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The EmfPlusSetClipRegion record combines the current clipping region with another graphics region.
|
||||||
|
*/
|
||||||
|
public static class EmfPlusSetClipRegion extends EmfPlusSetClipPath {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The EmfPlusSetClipPath record combines the current clipping region with a graphics path.
|
||||||
|
*/
|
||||||
|
public static class EmfPlusSetClipPath extends EmfPlusFlagOnly implements EmfPlusObjectId {
|
||||||
|
private static final BitField COMBINE_MODE = BitFieldFactory.getInstance(0x0F00);
|
||||||
|
|
||||||
|
public CombineMode getCombineMode() {
|
||||||
|
return CombineMode.valueOf(COMBINE_MODE.getValue(getFlags()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The EmfPlusSetClipRect record combines the current clipping region with a rectangle. */
|
||||||
|
public static class EmfPlusSetClipRect implements HemfPlusRecord {
|
||||||
|
private static final BitField COMBINE_MODE = BitFieldFactory.getInstance(0x0F00);
|
||||||
|
|
||||||
|
private int flags;
|
||||||
|
private final Rectangle2D clipRect = new Rectangle2D.Double();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HemfPlusRecordType getEmfPlusRecordType() {
|
||||||
|
return HemfPlusRecordType.setClipRect;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getFlags() {
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CombineMode getCombineMode() {
|
||||||
|
return CombineMode.valueOf(COMBINE_MODE.getValue(getFlags()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long init(LittleEndianInputStream leis, long dataSize, long recordId, int flags) throws IOException {
|
||||||
|
this.flags = flags;
|
||||||
|
|
||||||
|
// An EmfPlusRectF object that defines the rectangle to use in the CombineMode operation.
|
||||||
|
return readRectF(leis, clipRect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** The EmfPlusResetClip record resets the current clipping region for the world space to infinity. */
|
||||||
|
public static class EmfPlusResetClip extends EmfPlusFlagOnly {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The EmfPlusSave record saves the graphics state, identified by a specified index, on a stack of saved
|
||||||
|
* graphics states.
|
||||||
|
*/
|
||||||
|
public static class EmfPlusSave implements HemfPlusRecord {
|
||||||
|
private int flags;
|
||||||
|
private int stackIndex;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HemfPlusRecordType getEmfPlusRecordType() {
|
||||||
|
return HemfPlusRecordType.save;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getFlags() {
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long init(LittleEndianInputStream leis, long dataSize, long recordId, int flags) throws IOException {
|
||||||
|
this.flags = flags;
|
||||||
|
|
||||||
|
// A 32-bit unsigned integer that specifies a level to associate with the graphics state.
|
||||||
|
// The level value can be used by a subsequent EmfPlusRestore record operation to retrieve the graphics state.
|
||||||
|
stackIndex = leis.readInt();
|
||||||
|
|
||||||
|
return LittleEndianConsts.INT_SIZE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The EmfPlusRestore record restores the graphics state, identified by a specified index, from a stack
|
||||||
|
* of saved graphics states.
|
||||||
|
*/
|
||||||
|
public static class EmfPlusRestore extends EmfPlusSave {
|
||||||
|
@Override
|
||||||
|
public HemfPlusRecordType getEmfPlusRecordType() {
|
||||||
|
return HemfPlusRecordType.restore;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The EmfPlusSetRenderingOrigin record specifies the rendering origin for graphics output. */
|
||||||
|
public static class EmfPlusSetRenderingOrigin implements HemfPlusRecord {
|
||||||
|
int flags;
|
||||||
|
Point2D origin = new Point2D.Double();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HemfPlusRecordType getEmfPlusRecordType() {
|
||||||
|
return HemfPlusRecordType.setRenderingOrigin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getFlags() {
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Point2D getOrigin() {
|
||||||
|
return origin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long init(LittleEndianInputStream leis, long dataSize, long recordId, int flags) throws IOException {
|
||||||
|
this.flags = flags;
|
||||||
|
|
||||||
|
// A 32-bit unsigned integer that defines the horizontal coordinate value of the rendering origin.
|
||||||
|
double x = leis.readUInt();
|
||||||
|
// A 32-bit unsigned integer that defines the vertical coordinate value of the rendering origin.
|
||||||
|
double y = leis.readUInt();
|
||||||
|
|
||||||
|
origin.setLocation(x,y);
|
||||||
|
|
||||||
|
return LittleEndianConsts.INT_SIZE*2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,588 @@
|
||||||
|
/* ====================================================================
|
||||||
|
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.awt.Color;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import org.apache.poi.hemf.record.emfplus.HemfPlusHeader.EmfPlusGraphicsVersion;
|
||||||
|
import org.apache.poi.hemf.record.emfplus.HemfPlusMisc.EmfPlusObjectId;
|
||||||
|
import org.apache.poi.util.BitField;
|
||||||
|
import org.apache.poi.util.BitFieldFactory;
|
||||||
|
import org.apache.poi.util.IOUtils;
|
||||||
|
import org.apache.poi.util.LittleEndianConsts;
|
||||||
|
import org.apache.poi.util.LittleEndianInputStream;
|
||||||
|
|
||||||
|
public class HemfPlusObject {
|
||||||
|
private static final int MAX_OBJECT_SIZE = 50_000_000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ObjectType enumeration defines types of graphics objects that can be created and used in graphics operations.
|
||||||
|
*/
|
||||||
|
public enum EmfPlusObjectType {
|
||||||
|
/**
|
||||||
|
* The object is not a valid object.
|
||||||
|
*/
|
||||||
|
INVALID(0x00000000, EmfPlusUnknownData::new),
|
||||||
|
/**
|
||||||
|
* Brush objects fill graphics regions.
|
||||||
|
*/
|
||||||
|
BRUSH(0x00000001, EmfPlusUnknownData::new),
|
||||||
|
/**
|
||||||
|
* Pen objects draw graphics lines.
|
||||||
|
*/
|
||||||
|
PEN(0x00000002, EmfPlusUnknownData::new),
|
||||||
|
/**
|
||||||
|
* Path objects specify sequences of lines, curves, and shapes.
|
||||||
|
*/
|
||||||
|
PATH(0x00000003, EmfPlusUnknownData::new),
|
||||||
|
/**
|
||||||
|
* Region objects specify areas of the output surface.
|
||||||
|
*/
|
||||||
|
REGION(0x00000004, EmfPlusUnknownData::new),
|
||||||
|
/**
|
||||||
|
* Image objects encapsulate bitmaps and metafiles.
|
||||||
|
*/
|
||||||
|
IMAGE(0x00000005, EmfPlusImage::new),
|
||||||
|
/**
|
||||||
|
* Font objects specify font properties, including typeface style, em size, and font family.
|
||||||
|
*/
|
||||||
|
FONT(0x00000006, EmfPlusUnknownData::new),
|
||||||
|
/**
|
||||||
|
* String format objects specify text layout, including alignment, orientation, tab stops, clipping,
|
||||||
|
* and digit substitution for languages that do not use Western European digits.
|
||||||
|
*/
|
||||||
|
STRING_FORMAT(0x00000007, EmfPlusUnknownData::new),
|
||||||
|
/**
|
||||||
|
* Image attribute objects specify operations on pixels during image rendering, including color
|
||||||
|
* adjustment, grayscale adjustment, gamma correction, and color mapping.
|
||||||
|
*/
|
||||||
|
IMAGE_ATTRIBUTES(0x00000008, EmfPlusImageAttributes::new),
|
||||||
|
/**
|
||||||
|
* Custom line cap objects specify shapes to draw at the ends of a graphics line, including
|
||||||
|
* squares, circles, and diamonds.
|
||||||
|
*/
|
||||||
|
CUSTOM_LINE_CAP(0x00000009, EmfPlusUnknownData::new);
|
||||||
|
|
||||||
|
public final int id;
|
||||||
|
public final Supplier<? extends EmfPlusObjectData> constructor;
|
||||||
|
|
||||||
|
EmfPlusObjectType(int id, Supplier<? extends EmfPlusObjectData> constructor) {
|
||||||
|
this.id = id;
|
||||||
|
this.constructor = constructor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static EmfPlusObjectType valueOf(int id) {
|
||||||
|
for (EmfPlusObjectType wrt : values()) {
|
||||||
|
if (wrt.id == id) return wrt;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum EmfPlusImageDataType {
|
||||||
|
UNKNOWN(0x00000000),
|
||||||
|
BITMAP(0x00000001),
|
||||||
|
METAFILE(0x00000002),
|
||||||
|
CONTINUED(-1);
|
||||||
|
|
||||||
|
public final int id;
|
||||||
|
|
||||||
|
EmfPlusImageDataType(int id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static EmfPlusImageDataType valueOf(int id) {
|
||||||
|
for (EmfPlusImageDataType wrt : values()) {
|
||||||
|
if (wrt.id == id) return wrt;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum EmfPlusBitmapDataType {
|
||||||
|
PIXEL(0x00000000),
|
||||||
|
COMPRESSED(0x00000001);
|
||||||
|
|
||||||
|
public final int id;
|
||||||
|
|
||||||
|
EmfPlusBitmapDataType(int id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static EmfPlusBitmapDataType valueOf(int id) {
|
||||||
|
for (EmfPlusBitmapDataType wrt : values()) {
|
||||||
|
if (wrt.id == id) return wrt;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum EmfPlusPixelFormat {
|
||||||
|
UNDEFINED(0X00000000),
|
||||||
|
INDEXED_1BPP(0X00030101),
|
||||||
|
INDEXED_4BPP(0X00030402),
|
||||||
|
INDEXED_8BPP(0X00030803),
|
||||||
|
GRAYSCALE_16BPP(0X00101004),
|
||||||
|
RGB555_16BPP(0X00021005),
|
||||||
|
RGB565_16BPP(0X00021006),
|
||||||
|
ARGB1555_16BPP(0X00061007),
|
||||||
|
RGB_24BPP(0X00021808),
|
||||||
|
RGB_32BPP(0X00022009),
|
||||||
|
ARGB_32BPP(0X0026200A),
|
||||||
|
PARGB_32BPP(0X000E200B),
|
||||||
|
RGB_48BPP(0X0010300C),
|
||||||
|
ARGB_64BPP(0X0034400D),
|
||||||
|
PARGB_64BPP(0X001A400E),
|
||||||
|
;
|
||||||
|
|
||||||
|
private static final BitField CANONICAL = BitFieldFactory.getInstance(0x00200000);
|
||||||
|
private static final BitField EXTCOLORS = BitFieldFactory.getInstance(0x00100000);
|
||||||
|
private static final BitField PREMULTI = BitFieldFactory.getInstance(0x00080000);
|
||||||
|
private static final BitField ALPHA = BitFieldFactory.getInstance(0x00040000);
|
||||||
|
private static final BitField GDI = BitFieldFactory.getInstance(0x00020000);
|
||||||
|
private static final BitField PALETTE = BitFieldFactory.getInstance(0x00010000);
|
||||||
|
private static final BitField BPP = BitFieldFactory.getInstance(0x0000FF00);
|
||||||
|
private static final BitField INDEX = BitFieldFactory.getInstance(0x000000FF);
|
||||||
|
|
||||||
|
public final int id;
|
||||||
|
|
||||||
|
EmfPlusPixelFormat(int id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static EmfPlusPixelFormat valueOf(int id) {
|
||||||
|
for (EmfPlusPixelFormat wrt : values()) {
|
||||||
|
if (wrt.id == id) return wrt;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The pixel format enumeration index.
|
||||||
|
*/
|
||||||
|
public int getGDIEnumIndex() {
|
||||||
|
return id == -1 ? -1 : INDEX.getValue(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The total number of bits per pixel.
|
||||||
|
*/
|
||||||
|
public int getBitsPerPixel() {
|
||||||
|
return id == -1 ? -1 : BPP.getValue(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If set, the pixel values are indexes into a palette.
|
||||||
|
* If clear, the pixel values are actual colors.
|
||||||
|
*/
|
||||||
|
public boolean isPaletteIndexed() {
|
||||||
|
return id != -1 && PALETTE.isSet(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If set, the pixel format is supported in Windows GDI.
|
||||||
|
* If clear, the pixel format is not supported in Windows GDI.
|
||||||
|
*/
|
||||||
|
public boolean isGDISupported() {
|
||||||
|
return id != -1 && GDI.isSet(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If set, the pixel format includes an alpha transparency component.
|
||||||
|
* If clear, the pixel format does not include a component that specifies transparency.
|
||||||
|
*/
|
||||||
|
public boolean isAlpha() {
|
||||||
|
return id != -1 && ALPHA.isSet(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If set, each color component in the pixel has been premultiplied by the pixel's alpha transparency value.
|
||||||
|
* If clear, each color component is multiplied by the pixel's alpha transparency value when the source pixel
|
||||||
|
* is blended with the destination pixel.
|
||||||
|
*/
|
||||||
|
public boolean isPreMultiplied() {
|
||||||
|
return id != -1 && PREMULTI.isSet(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If set, the pixel format supports extended colors in 16-bits per channel.
|
||||||
|
* If clear, extended colors are not supported.
|
||||||
|
*/
|
||||||
|
public boolean isExtendedColors() {
|
||||||
|
return id != -1 && EXTCOLORS.isSet(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If set, the pixel format is "canonical", which means that 32 bits per pixel are
|
||||||
|
* supported, with 24-bits for color components and an 8-bit alpha channel.
|
||||||
|
* If clear, the pixel format is not canonical.
|
||||||
|
*/
|
||||||
|
public boolean isCanonical() {
|
||||||
|
return id != -1 && CANONICAL.isSet(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum EmfPlusMetafileDataType {
|
||||||
|
Wmf(0x00000001),
|
||||||
|
WmfPlaceable(0x00000002),
|
||||||
|
Emf(0x00000003),
|
||||||
|
EmfPlusOnly(0x00000004),
|
||||||
|
EmfPlusDual(0x00000005);
|
||||||
|
|
||||||
|
public final int id;
|
||||||
|
|
||||||
|
EmfPlusMetafileDataType(int id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static EmfPlusMetafileDataType valueOf(int id) {
|
||||||
|
for (EmfPlusMetafileDataType wrt : values()) {
|
||||||
|
if (wrt.id == id) return wrt;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The WrapMode enumeration defines how the pattern from a texture or gradient brush is tiled
|
||||||
|
* across a shape or at shape boundaries, when it is smaller than the area being filled.
|
||||||
|
*/
|
||||||
|
public enum EmfPlusWrapMode {
|
||||||
|
WRAP_MODE_TILE(0x00000000),
|
||||||
|
WRAP_MODE_TILE_FLIP_X(0x00000001),
|
||||||
|
WRAP_MODE_TILE_FLIP_Y(0x00000002),
|
||||||
|
WRAP_MODE_TILE_FLIP_XY(0x00000003),
|
||||||
|
WRAP_MODE_CLAMP(0x00000004)
|
||||||
|
;
|
||||||
|
|
||||||
|
public final int id;
|
||||||
|
|
||||||
|
EmfPlusWrapMode(int id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static EmfPlusWrapMode valueOf(int id) {
|
||||||
|
for (EmfPlusWrapMode wrt : values()) {
|
||||||
|
if (wrt.id == id) return wrt;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum EmfPlusObjectClamp {
|
||||||
|
/** The object is clamped to a rectangle. */
|
||||||
|
RectClamp(0x00000000),
|
||||||
|
/** The object is clamped to a bitmap. */
|
||||||
|
BitmapClamp(0x00000001)
|
||||||
|
;
|
||||||
|
|
||||||
|
public final int id;
|
||||||
|
|
||||||
|
EmfPlusObjectClamp(int id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static EmfPlusObjectClamp valueOf(int id) {
|
||||||
|
for (EmfPlusObjectClamp wrt : values()) {
|
||||||
|
if (wrt.id == id) return wrt;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The EmfPlusObject record specifies an object for use in graphics operations. The object definition
|
||||||
|
* can span multiple records, which is indicated by the value of the Flags field.
|
||||||
|
*/
|
||||||
|
public static class EmfPlusObject implements HemfPlusRecord, EmfPlusObjectId {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates that the object definition continues on in the next EmfPlusObject
|
||||||
|
* record. This flag is never set in the final record that defines the object.
|
||||||
|
*/
|
||||||
|
private static final BitField CONTINUABLE = BitFieldFactory.getInstance(0x8000);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies the metafileType of object to be created by this record, from the
|
||||||
|
* ObjectType enumeration
|
||||||
|
*/
|
||||||
|
private static final BitField OBJECT_TYPE = BitFieldFactory.getInstance(0x7F00);
|
||||||
|
|
||||||
|
private int flags;
|
||||||
|
// for debugging
|
||||||
|
private int objectId;
|
||||||
|
private EmfPlusObjectData objectData;
|
||||||
|
private int totalObjectSize;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HemfPlusRecordType getEmfPlusRecordType() {
|
||||||
|
return HemfPlusRecordType.object;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getFlags() {
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EmfPlusObjectType getObjectType() {
|
||||||
|
return EmfPlusObjectType.valueOf(OBJECT_TYPE.getValue(flags));
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T extends EmfPlusObjectData> T getObjectData() {
|
||||||
|
return (T)objectData;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long init(LittleEndianInputStream leis, long dataSize, long recordId, int flags) throws IOException {
|
||||||
|
this.flags = flags;
|
||||||
|
|
||||||
|
objectId = getObjectId();
|
||||||
|
EmfPlusObjectType objectType = getObjectType();
|
||||||
|
assert (objectType != null);
|
||||||
|
|
||||||
|
int size = 0;
|
||||||
|
|
||||||
|
totalObjectSize = 0;
|
||||||
|
int dataSize2 = (int) dataSize;
|
||||||
|
|
||||||
|
if (CONTINUABLE.isSet(flags)) {
|
||||||
|
// If the record is continuable, when the continue bit is set, this field will be present.
|
||||||
|
// Continuing objects have multiple EMF+ records starting with EmfPlusContinuedObjectRecord.
|
||||||
|
// Each EmfPlusContinuedObjectRecord will contain a TotalObjectSize. Once TotalObjectSize number
|
||||||
|
// of bytes has been read, the next EMF+ record will not be treated as part of the continuing object.
|
||||||
|
totalObjectSize = leis.readInt();
|
||||||
|
size += LittleEndianConsts.INT_SIZE;
|
||||||
|
dataSize2 -= LittleEndianConsts.INT_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
objectData = objectType.constructor.get();
|
||||||
|
size += objectData.init(leis, dataSize2, objectType, flags);
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface EmfPlusObjectData {
|
||||||
|
long init(LittleEndianInputStream leis, long dataSize, EmfPlusObjectType objectType, int flags) throws IOException;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class EmfPlusUnknownData implements EmfPlusObjectData {
|
||||||
|
private EmfPlusObjectType objectType;
|
||||||
|
private final EmfPlusGraphicsVersion graphicsVersion = new EmfPlusGraphicsVersion();
|
||||||
|
private byte[] objectDataBytes;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long init(LittleEndianInputStream leis, long dataSize, EmfPlusObjectType objectType, int flags) throws IOException {
|
||||||
|
this.objectType = objectType;
|
||||||
|
|
||||||
|
long size = graphicsVersion.init(leis);
|
||||||
|
|
||||||
|
objectDataBytes = IOUtils.toByteArray(leis, dataSize - size, MAX_OBJECT_SIZE);
|
||||||
|
|
||||||
|
return dataSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class EmfPlusImage implements EmfPlusObjectData {
|
||||||
|
private final EmfPlusGraphicsVersion graphicsVersion = new EmfPlusGraphicsVersion();
|
||||||
|
private EmfPlusImageDataType imageDataType;
|
||||||
|
private int bitmapWidth;
|
||||||
|
private int bitmapHeight;
|
||||||
|
private int bitmapStride;
|
||||||
|
private EmfPlusPixelFormat pixelFormat;
|
||||||
|
private EmfPlusBitmapDataType bitmapType;
|
||||||
|
private byte[] imageData;
|
||||||
|
private EmfPlusMetafileDataType metafileType;
|
||||||
|
private int metafileDataSize;
|
||||||
|
|
||||||
|
public EmfPlusImageDataType getImageDataType() {
|
||||||
|
return imageDataType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getImageData() {
|
||||||
|
return imageData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EmfPlusPixelFormat getPixelFormat() {
|
||||||
|
return pixelFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EmfPlusBitmapDataType getBitmapType() {
|
||||||
|
return bitmapType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBitmapWidth() {
|
||||||
|
return bitmapWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBitmapHeight() {
|
||||||
|
return bitmapHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBitmapStride() {
|
||||||
|
return bitmapStride;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EmfPlusMetafileDataType getMetafileType() {
|
||||||
|
return metafileType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long init(LittleEndianInputStream leis, long dataSize, EmfPlusObjectType objectType, int flags) throws IOException {
|
||||||
|
leis.mark(LittleEndianConsts.INT_SIZE);
|
||||||
|
long size = graphicsVersion.init(leis);
|
||||||
|
|
||||||
|
if (graphicsVersion.getGraphicsVersion() == null || graphicsVersion.getMetafileSignature() != 0xDBC01) {
|
||||||
|
// CONTINUABLE is not always correctly set, so we check the version field if this record is continued
|
||||||
|
imageDataType = EmfPlusImageDataType.CONTINUED;
|
||||||
|
leis.reset();
|
||||||
|
size = 0;
|
||||||
|
} else {
|
||||||
|
imageDataType = EmfPlusImageDataType.valueOf(leis.readInt());
|
||||||
|
size += LittleEndianConsts.INT_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (imageDataType == null) {
|
||||||
|
imageDataType = EmfPlusImageDataType.UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
int fileSize;
|
||||||
|
switch (imageDataType) {
|
||||||
|
default:
|
||||||
|
case UNKNOWN:
|
||||||
|
case CONTINUED:
|
||||||
|
bitmapWidth = -1;
|
||||||
|
bitmapHeight = -1;
|
||||||
|
bitmapStride = -1;
|
||||||
|
bitmapType = null;
|
||||||
|
pixelFormat = null;
|
||||||
|
|
||||||
|
fileSize = (int) (dataSize);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BITMAP:
|
||||||
|
// A 32-bit signed integer that specifies the width in pixels of the area occupied by the bitmap.
|
||||||
|
// If the image is compressed, according to the Type field, this value is undefined and MUST be ignored.
|
||||||
|
bitmapWidth = leis.readInt();
|
||||||
|
// A 32-bit signed integer that specifies the height in pixels of the area occupied by the bitmap.
|
||||||
|
// If the image is compressed, according to the Type field, this value is undefined and MUST be ignored.
|
||||||
|
bitmapHeight = leis.readInt();
|
||||||
|
// A 32-bit signed integer that specifies the byte offset between the beginning of one scan-line
|
||||||
|
// and the next. This value is the number of bytes per pixel, which is specified in the PixelFormat
|
||||||
|
// field, multiplied by the width in pixels, which is specified in the Width field.
|
||||||
|
// The value of this field MUST be a multiple of four. If the image is compressed, according to the
|
||||||
|
// Type field, this value is undefined and MUST be ignored.
|
||||||
|
bitmapStride = leis.readInt();
|
||||||
|
// A 32-bit unsigned integer that specifies the format of the pixels that make up the bitmap image.
|
||||||
|
// The supported pixel formats are specified in the PixelFormat enumeration
|
||||||
|
int pixelFormatInt = leis.readInt();
|
||||||
|
// A 32-bit unsigned integer that specifies the metafileType of data in the BitmapData field.
|
||||||
|
// This value MUST be defined in the BitmapDataType enumeration
|
||||||
|
bitmapType = EmfPlusBitmapDataType.valueOf(leis.readInt());
|
||||||
|
size += 5 * LittleEndianConsts.INT_SIZE;
|
||||||
|
|
||||||
|
pixelFormat = (bitmapType == EmfPlusBitmapDataType.PIXEL)
|
||||||
|
? EmfPlusPixelFormat.valueOf(pixelFormatInt)
|
||||||
|
: EmfPlusPixelFormat.UNDEFINED;
|
||||||
|
assert (pixelFormat != null);
|
||||||
|
|
||||||
|
fileSize = (int) (dataSize - size);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case METAFILE:
|
||||||
|
// A 32-bit unsigned integer that specifies the type of metafile that is embedded in the
|
||||||
|
// MetafileData field. This value MUST be defined in the MetafileDataType enumeration
|
||||||
|
metafileType = EmfPlusMetafileDataType.valueOf(leis.readInt());
|
||||||
|
|
||||||
|
// A 32-bit unsigned integer that specifies the size in bytes of the
|
||||||
|
// metafile data in the MetafileData field.
|
||||||
|
metafileDataSize = leis.readInt();
|
||||||
|
|
||||||
|
size += 2 * LittleEndianConsts.INT_SIZE;
|
||||||
|
|
||||||
|
// ignore metafileDataSize, which might ignore a (placeable) header in front
|
||||||
|
// and also use the remaining bytes, which might contain padding bytes ...
|
||||||
|
fileSize = (int) (dataSize - size);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert (fileSize <= dataSize - size);
|
||||||
|
|
||||||
|
imageData = IOUtils.toByteArray(leis, fileSize, MAX_OBJECT_SIZE);
|
||||||
|
|
||||||
|
// TODO: remove padding bytes between placeable WMF header and body?
|
||||||
|
|
||||||
|
return size + fileSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class EmfPlusImageAttributes implements EmfPlusObjectData {
|
||||||
|
private final EmfPlusGraphicsVersion graphicsVersion = new EmfPlusGraphicsVersion();
|
||||||
|
private EmfPlusWrapMode wrapMode;
|
||||||
|
private Color clampColor;
|
||||||
|
private EmfPlusObjectClamp objectClamp;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long init(LittleEndianInputStream leis, long dataSize, EmfPlusObjectType objectType, int flags) throws IOException {
|
||||||
|
// An EmfPlusGraphicsVersion object that specifies the version of operating system graphics that
|
||||||
|
// was used to create this object.
|
||||||
|
long size = graphicsVersion.init(leis);
|
||||||
|
|
||||||
|
// A 32-bit field that is not used and MUST be ignored.
|
||||||
|
leis.skip(LittleEndianConsts.INT_SIZE);
|
||||||
|
|
||||||
|
// A 32-bit unsigned integer that specifies how to handle edge conditions with a value from the WrapMode enumeration
|
||||||
|
wrapMode = EmfPlusWrapMode.valueOf(leis.readInt());
|
||||||
|
|
||||||
|
// An EmfPlusARGB object that specifies the edge color to use when the WrapMode value is WrapModeClamp.
|
||||||
|
// This color is visible when the source rectangle processed by an EmfPlusDrawImage record is larger than the image itself.
|
||||||
|
byte[] buf = new byte[LittleEndianConsts.INT_SIZE];
|
||||||
|
leis.readFully(buf);
|
||||||
|
clampColor = new Color(buf[2], buf[1], buf[0], buf[3]);
|
||||||
|
|
||||||
|
// A 32-bit signed integer that specifies the object clamping behavior. It is not used until this object
|
||||||
|
// is applied to an image being drawn. This value MUST be one of the values defined in the following table.
|
||||||
|
objectClamp = EmfPlusObjectClamp.valueOf(leis.readInt());
|
||||||
|
|
||||||
|
// A value that SHOULD be set to zero and MUST be ignored upon receipt.
|
||||||
|
leis.skip(LittleEndianConsts.INT_SIZE);
|
||||||
|
|
||||||
|
return size + 5*LittleEndianConsts.INT_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EmfPlusGraphicsVersion getGraphicsVersion() {
|
||||||
|
return graphicsVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EmfPlusWrapMode getWrapMode() {
|
||||||
|
return wrapMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Color getClampColor() {
|
||||||
|
return clampColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EmfPlusObjectClamp getObjectClamp() {
|
||||||
|
return objectClamp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -50,7 +50,8 @@ public class HemfPlusRecordIterator implements Iterator<HemfPlusRecord> {
|
||||||
@Override
|
@Override
|
||||||
public HemfPlusRecord next() {
|
public HemfPlusRecord next() {
|
||||||
HemfPlusRecord toReturn = currentRecord;
|
HemfPlusRecord toReturn = currentRecord;
|
||||||
final boolean isEOF = (limit == -1 || leis.getReadIndex()-startIdx >= limit);
|
// add the size for recordId/flags/recordSize/dataSize = 12 bytes
|
||||||
|
final boolean isEOF = (limit == -1 || (leis.getReadIndex()-startIdx)+12 >= limit);
|
||||||
// (currentRecord instanceof HemfPlusMisc.EmfEof)
|
// (currentRecord instanceof HemfPlusMisc.EmfEof)
|
||||||
currentRecord = isEOF ? null : _next();
|
currentRecord = isEOF ? null : _next();
|
||||||
return toReturn;
|
return toReturn;
|
||||||
|
|
|
@ -24,16 +24,16 @@ import org.apache.poi.util.Internal;
|
||||||
@Internal
|
@Internal
|
||||||
public enum HemfPlusRecordType {
|
public enum HemfPlusRecordType {
|
||||||
header(0x4001, HemfPlusHeader::new),
|
header(0x4001, HemfPlusHeader::new),
|
||||||
eof(0x4002, UnimplementedHemfPlusRecord::new),
|
eof(0x4002, HemfPlusMisc.EmfPlusEOF::new),
|
||||||
comment(0x4003, UnimplementedHemfPlusRecord::new),
|
comment(0x4003, UnimplementedHemfPlusRecord::new),
|
||||||
getDC(0x4004, UnimplementedHemfPlusRecord::new),
|
getDC(0x4004, HemfPlusMisc.EmfPlusGetDC::new),
|
||||||
multiFormatStart(0x4005, UnimplementedHemfPlusRecord::new),
|
multiFormatStart(0x4005, UnimplementedHemfPlusRecord::new),
|
||||||
multiFormatSection(0x4006, UnimplementedHemfPlusRecord::new),
|
multiFormatSection(0x4006, UnimplementedHemfPlusRecord::new),
|
||||||
multiFormatEnd(0x4007, UnimplementedHemfPlusRecord::new),
|
multiFormatEnd(0x4007, UnimplementedHemfPlusRecord::new),
|
||||||
object(0x4008, UnimplementedHemfPlusRecord::new),
|
object(0x4008, HemfPlusObject.EmfPlusObject::new),
|
||||||
clear(0x4009, UnimplementedHemfPlusRecord::new),
|
clear(0x4009, UnimplementedHemfPlusRecord::new),
|
||||||
fillRects(0x400A, UnimplementedHemfPlusRecord::new),
|
fillRects(0x400A, HemfPlusDraw.EmfPlusFillRects::new),
|
||||||
drawRects(0x400B, UnimplementedHemfPlusRecord::new),
|
drawRects(0x400B, HemfPlusDraw.EmfPlusDrawRects::new),
|
||||||
fillPolygon(0x400C, UnimplementedHemfPlusRecord::new),
|
fillPolygon(0x400C, UnimplementedHemfPlusRecord::new),
|
||||||
drawLines(0x400D, UnimplementedHemfPlusRecord::new),
|
drawLines(0x400D, UnimplementedHemfPlusRecord::new),
|
||||||
fillEllipse(0x400E, UnimplementedHemfPlusRecord::new),
|
fillEllipse(0x400E, UnimplementedHemfPlusRecord::new),
|
||||||
|
@ -41,42 +41,42 @@ public enum HemfPlusRecordType {
|
||||||
fillPie(0x4010, UnimplementedHemfPlusRecord::new),
|
fillPie(0x4010, UnimplementedHemfPlusRecord::new),
|
||||||
drawPie(0x4011, UnimplementedHemfPlusRecord::new),
|
drawPie(0x4011, UnimplementedHemfPlusRecord::new),
|
||||||
drawArc(0x4012, UnimplementedHemfPlusRecord::new),
|
drawArc(0x4012, UnimplementedHemfPlusRecord::new),
|
||||||
fillRegion(0x4013, UnimplementedHemfPlusRecord::new),
|
fillRegion(0x4013, HemfPlusDraw.EmfPlusFillRegion::new),
|
||||||
fillPath(0x4014, UnimplementedHemfPlusRecord::new),
|
fillPath(0x4014, HemfPlusDraw.EmfPlusFillPath::new),
|
||||||
drawPath(0x4015, UnimplementedHemfPlusRecord::new),
|
drawPath(0x4015, HemfPlusDraw.EmfPlusDrawPath::new),
|
||||||
fillClosedCurve(0x4016, UnimplementedHemfPlusRecord::new),
|
fillClosedCurve(0x4016, UnimplementedHemfPlusRecord::new),
|
||||||
drawClosedCurve(0x4017, UnimplementedHemfPlusRecord::new),
|
drawClosedCurve(0x4017, UnimplementedHemfPlusRecord::new),
|
||||||
drawCurve(0x4018, UnimplementedHemfPlusRecord::new),
|
drawCurve(0x4018, UnimplementedHemfPlusRecord::new),
|
||||||
drawBeziers(0x4019, UnimplementedHemfPlusRecord::new),
|
drawBeziers(0x4019, UnimplementedHemfPlusRecord::new),
|
||||||
drawImage(0x401A, UnimplementedHemfPlusRecord::new),
|
drawImage(0x401A, HemfPlusDraw.EmfPlusDrawImage::new),
|
||||||
drawImagePoints(0x401B, UnimplementedHemfPlusRecord::new),
|
drawImagePoints(0x401B, HemfPlusDraw.EmfPlusDrawImagePoints::new),
|
||||||
drawString(0x401C, UnimplementedHemfPlusRecord::new),
|
drawString(0x401C, UnimplementedHemfPlusRecord::new),
|
||||||
setRenderingOrigin(0x401D, UnimplementedHemfPlusRecord::new),
|
setRenderingOrigin(0x401D, HemfPlusMisc.EmfPlusSetRenderingOrigin::new),
|
||||||
setAntiAliasMode(0x401E, UnimplementedHemfPlusRecord::new),
|
setAntiAliasMode(0x401E, HemfPlusMisc.EmfPlusSetAntiAliasMode::new),
|
||||||
setTextRenderingHint(0x401F, UnimplementedHemfPlusRecord::new),
|
setTextRenderingHint(0x401F, HemfPlusMisc.EmfPlusSetTextRenderingHint::new),
|
||||||
setTextContrast(0x4020, UnimplementedHemfPlusRecord::new),
|
setTextContrast(0x4020, UnimplementedHemfPlusRecord::new),
|
||||||
setInterpolationMode(0x4021, UnimplementedHemfPlusRecord::new),
|
setInterpolationMode(0x4021, HemfPlusMisc.EmfPlusSetInterpolationMode::new),
|
||||||
setPixelOffsetMode(0x4022, UnimplementedHemfPlusRecord::new),
|
setPixelOffsetMode(0x4022, HemfPlusMisc.EmfPlusSetPixelOffsetMode::new),
|
||||||
setComositingMode(0x4023, UnimplementedHemfPlusRecord::new),
|
setCompositingMode(0x4023, HemfPlusMisc.EmfPlusSetCompositingMode::new),
|
||||||
setCompositingQuality(0x4024, UnimplementedHemfPlusRecord::new),
|
setCompositingQuality(0x4024, HemfPlusMisc.EmfPlusSetCompositingQuality::new),
|
||||||
save(0x4025, UnimplementedHemfPlusRecord::new),
|
save(0x4025, HemfPlusMisc.EmfPlusSave::new),
|
||||||
restore(0x4026, UnimplementedHemfPlusRecord::new),
|
restore(0x4026, HemfPlusMisc.EmfPlusRestore::new),
|
||||||
beginContainer(0x4027, UnimplementedHemfPlusRecord::new),
|
beginContainer(0x4027, UnimplementedHemfPlusRecord::new),
|
||||||
beginContainerNoParams(0x428, UnimplementedHemfPlusRecord::new),
|
beginContainerNoParams(0x428, UnimplementedHemfPlusRecord::new),
|
||||||
endContainer(0x4029, UnimplementedHemfPlusRecord::new),
|
endContainer(0x4029, UnimplementedHemfPlusRecord::new),
|
||||||
setWorldTransform(0x402A, UnimplementedHemfPlusRecord::new),
|
setWorldTransform(0x402A, HemfPlusMisc.EmfPlusSetWorldTransform::new),
|
||||||
resetWorldTransform(0x402B, UnimplementedHemfPlusRecord::new),
|
resetWorldTransform(0x402B, HemfPlusMisc.EmfPlusResetWorldTransform::new),
|
||||||
multiplyWorldTransform(0x402C, UnimplementedHemfPlusRecord::new),
|
multiplyWorldTransform(0x402C, HemfPlusMisc.EmfPlusMultiplyWorldTransform::new),
|
||||||
translateWorldTransform(0x402D, UnimplementedHemfPlusRecord::new),
|
translateWorldTransform(0x402D, UnimplementedHemfPlusRecord::new),
|
||||||
scaleWorldTransform(0x402E, UnimplementedHemfPlusRecord::new),
|
scaleWorldTransform(0x402E, UnimplementedHemfPlusRecord::new),
|
||||||
rotateWorldTransform(0x402F, UnimplementedHemfPlusRecord::new),
|
rotateWorldTransform(0x402F, UnimplementedHemfPlusRecord::new),
|
||||||
setPageTransform(0x4030, UnimplementedHemfPlusRecord::new),
|
setPageTransform(0x4030, HemfPlusMisc.EmfPlusSetPageTransform::new),
|
||||||
resetClip(0x4031, UnimplementedHemfPlusRecord::new),
|
resetClip(0x4031, HemfPlusMisc.EmfPlusResetClip::new),
|
||||||
setClipRect(0x4032, UnimplementedHemfPlusRecord::new),
|
setClipRect(0x4032, HemfPlusMisc.EmfPlusSetClipRect::new),
|
||||||
setClipRegion(0x4033, UnimplementedHemfPlusRecord::new),
|
setClipRegion(0x4033, HemfPlusMisc.EmfPlusSetClipRegion::new),
|
||||||
setClipPath(0x4034, UnimplementedHemfPlusRecord::new),
|
setClipPath(0x4034, HemfPlusMisc.EmfPlusSetClipPath::new),
|
||||||
offsetClip(0x4035, UnimplementedHemfPlusRecord::new),
|
offsetClip(0x4035, UnimplementedHemfPlusRecord::new),
|
||||||
drawDriverstring(0x4036, UnimplementedHemfPlusRecord::new),
|
drawDriverstring(0x4036, HemfPlusDraw.EmfPlusDrawDriverString::new),
|
||||||
strokeFillPath(0x4037, UnimplementedHemfPlusRecord::new),
|
strokeFillPath(0x4037, UnimplementedHemfPlusRecord::new),
|
||||||
serializableObject(0x4038, UnimplementedHemfPlusRecord::new),
|
serializableObject(0x4038, UnimplementedHemfPlusRecord::new),
|
||||||
setTSGraphics(0x4039, UnimplementedHemfPlusRecord::new),
|
setTSGraphics(0x4039, UnimplementedHemfPlusRecord::new),
|
||||||
|
|
|
@ -0,0 +1,342 @@
|
||||||
|
/* ====================================================================
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
contributor license agreements. See the NOTICE file distributed with
|
||||||
|
this work for additional information regarding copyright ownership.
|
||||||
|
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
(the "License"); you may not use this file except in compliance with
|
||||||
|
the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
==================================================================== */
|
||||||
|
|
||||||
|
package org.apache.poi.hemf.usermodel;
|
||||||
|
|
||||||
|
import java.awt.Transparency;
|
||||||
|
import java.awt.color.ColorSpace;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.awt.image.ComponentColorModel;
|
||||||
|
import java.awt.image.DataBuffer;
|
||||||
|
import java.awt.image.DataBufferByte;
|
||||||
|
import java.awt.image.PixelInterleavedSampleModel;
|
||||||
|
import java.awt.image.Raster;
|
||||||
|
import java.awt.image.WritableRaster;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.Deque;
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
|
||||||
|
import org.apache.poi.hemf.record.emf.HemfComment;
|
||||||
|
import org.apache.poi.hemf.record.emf.HemfRecord;
|
||||||
|
import org.apache.poi.hemf.record.emfplus.HemfPlusObject;
|
||||||
|
import org.apache.poi.hwmf.record.HwmfBitmapDib;
|
||||||
|
import org.apache.poi.hwmf.record.HwmfFill;
|
||||||
|
import org.apache.poi.hwmf.usermodel.HwmfEmbedded;
|
||||||
|
import org.apache.poi.hwmf.usermodel.HwmfEmbeddedType;
|
||||||
|
import org.apache.poi.poifs.filesystem.FileMagic;
|
||||||
|
|
||||||
|
public class HemfEmbeddedIterator implements Iterator<HwmfEmbedded> {
|
||||||
|
|
||||||
|
private final Deque<Iterator<?>> iterStack = new ArrayDeque<>();
|
||||||
|
private Object current;
|
||||||
|
|
||||||
|
public HemfEmbeddedIterator(HemfPicture emf) {
|
||||||
|
this(emf.getRecords().iterator());
|
||||||
|
}
|
||||||
|
|
||||||
|
public HemfEmbeddedIterator(Iterator<HemfRecord> recordIterator) {
|
||||||
|
iterStack.add(recordIterator);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
if (iterStack.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current != null) {
|
||||||
|
// don't search twice and potentially skip items
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterator<?> iter;
|
||||||
|
do {
|
||||||
|
iter = iterStack.peek();
|
||||||
|
while (iter.hasNext()) {
|
||||||
|
Object obj = iter.next();
|
||||||
|
if (obj instanceof HemfComment.EmfComment) {
|
||||||
|
HemfComment.EmfCommentData cd = ((HemfComment.EmfComment)obj).getCommentData();
|
||||||
|
if (
|
||||||
|
cd instanceof HemfComment.EmfCommentDataWMF ||
|
||||||
|
cd instanceof HemfComment.EmfCommentDataGeneric
|
||||||
|
) {
|
||||||
|
current = obj;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cd instanceof HemfComment.EmfCommentDataMultiformats) {
|
||||||
|
Iterator<?> iter2 = ((HemfComment.EmfCommentDataMultiformats)cd).getFormats().iterator();
|
||||||
|
if (iter2.hasNext()) {
|
||||||
|
iterStack.push(iter2);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cd instanceof HemfComment.EmfCommentDataPlus) {
|
||||||
|
Iterator<?> iter2 = ((HemfComment.EmfCommentDataPlus)cd).getRecords().iterator();
|
||||||
|
if (iter2.hasNext()) {
|
||||||
|
iter = iter2;
|
||||||
|
iterStack.push(iter2);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj instanceof HemfComment.EmfCommentDataFormat) {
|
||||||
|
current = obj;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj instanceof HemfPlusObject.EmfPlusObject && ((HemfPlusObject.EmfPlusObject)obj).getObjectType() == HemfPlusObject.EmfPlusObjectType.IMAGE) {
|
||||||
|
current = obj;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj instanceof HwmfFill.WmfStretchDib) {
|
||||||
|
HwmfBitmapDib bitmap = ((HwmfFill.WmfStretchDib) obj).getBitmap();
|
||||||
|
if (bitmap.isValid()) {
|
||||||
|
current = obj;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
iterStack.pop();
|
||||||
|
} while (!iterStack.isEmpty());
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HwmfEmbedded next() {
|
||||||
|
HwmfEmbedded emb;
|
||||||
|
if ((emb = checkEmfCommentDataWMF()) != null) {
|
||||||
|
return emb;
|
||||||
|
}
|
||||||
|
if ((emb = checkEmfCommentDataGeneric()) != null) {
|
||||||
|
return emb;
|
||||||
|
}
|
||||||
|
if ((emb = checkEmfCommentDataFormat()) != null) {
|
||||||
|
return emb;
|
||||||
|
}
|
||||||
|
if ((emb = checkEmfPlusObject()) != null) {
|
||||||
|
return emb;
|
||||||
|
}
|
||||||
|
if ((emb = checkWmfStretchDib()) != null) {
|
||||||
|
return emb;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private HwmfEmbedded checkEmfCommentDataWMF() {
|
||||||
|
if (!(current instanceof HemfComment.EmfCommentDataWMF && ((HemfComment.EmfComment)current).getCommentData() instanceof HemfComment.EmfCommentDataWMF)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
HemfComment.EmfCommentDataWMF wmf = (HemfComment.EmfCommentDataWMF)((HemfComment.EmfComment)current).getCommentData();
|
||||||
|
HwmfEmbedded emb = new HwmfEmbedded();
|
||||||
|
emb.setEmbeddedType(HwmfEmbeddedType.WMF);
|
||||||
|
emb.setData(wmf.getWMFData());
|
||||||
|
current = null;
|
||||||
|
return emb;
|
||||||
|
}
|
||||||
|
|
||||||
|
private HwmfEmbedded checkEmfCommentDataGeneric() {
|
||||||
|
if (!(current instanceof HemfComment.EmfComment && ((HemfComment.EmfComment)current).getCommentData() instanceof HemfComment.EmfCommentDataGeneric)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
HemfComment.EmfCommentDataGeneric cdg = (HemfComment.EmfCommentDataGeneric)((HemfComment.EmfComment)current).getCommentData();
|
||||||
|
HwmfEmbedded emb = new HwmfEmbedded();
|
||||||
|
emb.setEmbeddedType(HwmfEmbeddedType.UNKNOWN);
|
||||||
|
emb.setData(cdg.getPrivateData());
|
||||||
|
current = null;
|
||||||
|
return emb;
|
||||||
|
}
|
||||||
|
|
||||||
|
private HwmfEmbedded checkEmfCommentDataFormat() {
|
||||||
|
if (!(current instanceof HemfComment.EmfCommentDataFormat)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
HemfComment.EmfCommentDataFormat cdf = (HemfComment.EmfCommentDataFormat)current;
|
||||||
|
HwmfEmbedded emb = new HwmfEmbedded();
|
||||||
|
boolean isEmf = (cdf.getSignature() == HemfComment.EmfFormatSignature.ENHMETA_SIGNATURE);
|
||||||
|
emb.setEmbeddedType(isEmf ? HwmfEmbeddedType.EMF : HwmfEmbeddedType.EPS);
|
||||||
|
emb.setData(cdf.getRawData());
|
||||||
|
current = null;
|
||||||
|
return emb;
|
||||||
|
}
|
||||||
|
|
||||||
|
private HwmfEmbedded checkWmfStretchDib() {
|
||||||
|
if (!(current instanceof HwmfFill.WmfStretchDib)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
HwmfEmbedded emb = new HwmfEmbedded();
|
||||||
|
emb.setData(((HwmfFill.WmfStretchDib) current).getBitmap().getBMPData());
|
||||||
|
emb.setEmbeddedType(HwmfEmbeddedType.BMP);
|
||||||
|
current = null;
|
||||||
|
return emb;
|
||||||
|
}
|
||||||
|
|
||||||
|
private HwmfEmbedded checkEmfPlusObject() {
|
||||||
|
if (!(current instanceof HemfPlusObject.EmfPlusObject)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
HemfPlusObject.EmfPlusObject epo = (HemfPlusObject.EmfPlusObject)current;
|
||||||
|
assert(epo.getObjectType() == HemfPlusObject.EmfPlusObjectType.IMAGE);
|
||||||
|
HemfPlusObject.EmfPlusImage img = epo.getObjectData();
|
||||||
|
assert(img.getImageDataType() != null);
|
||||||
|
|
||||||
|
HwmfEmbedded emb = getEmfPlusImageData();
|
||||||
|
|
||||||
|
HwmfEmbeddedType et;
|
||||||
|
switch (img.getImageDataType()) {
|
||||||
|
case BITMAP:
|
||||||
|
if (img.getBitmapType() == HemfPlusObject.EmfPlusBitmapDataType.COMPRESSED) {
|
||||||
|
switch (FileMagic.valueOf(emb.getRawData())) {
|
||||||
|
case JPEG:
|
||||||
|
et = HwmfEmbeddedType.JPEG;
|
||||||
|
break;
|
||||||
|
case GIF:
|
||||||
|
et = HwmfEmbeddedType.GIF;
|
||||||
|
break;
|
||||||
|
case PNG:
|
||||||
|
et = HwmfEmbeddedType.PNG;
|
||||||
|
break;
|
||||||
|
case TIFF:
|
||||||
|
et = HwmfEmbeddedType.TIFF;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
et = HwmfEmbeddedType.BITMAP;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
et = HwmfEmbeddedType.PNG;
|
||||||
|
compressGDIBitmap(img, emb, et);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case METAFILE:
|
||||||
|
assert(img.getMetafileType() != null);
|
||||||
|
switch (img.getMetafileType()) {
|
||||||
|
case Wmf:
|
||||||
|
case WmfPlaceable:
|
||||||
|
et = HwmfEmbeddedType.WMF;
|
||||||
|
break;
|
||||||
|
case Emf:
|
||||||
|
case EmfPlusDual:
|
||||||
|
case EmfPlusOnly:
|
||||||
|
et = HwmfEmbeddedType.EMF;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
et = HwmfEmbeddedType.UNKNOWN;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
et = HwmfEmbeddedType.UNKNOWN;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
emb.setEmbeddedType(et);
|
||||||
|
|
||||||
|
return emb;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compress GDIs internal format to something useful
|
||||||
|
*/
|
||||||
|
private void compressGDIBitmap(HemfPlusObject.EmfPlusImage img, HwmfEmbedded emb, HwmfEmbeddedType et) {
|
||||||
|
final int width = img.getBitmapWidth();
|
||||||
|
final int height = img.getBitmapHeight();
|
||||||
|
final int stride = img.getBitmapStride();
|
||||||
|
final HemfPlusObject.EmfPlusPixelFormat pf = img.getPixelFormat();
|
||||||
|
|
||||||
|
int[] nBits, bOffs;
|
||||||
|
switch (pf) {
|
||||||
|
case ARGB_32BPP:
|
||||||
|
nBits = new int[]{8, 8, 8, 8};
|
||||||
|
bOffs = new int[]{2, 1, 0, 3};
|
||||||
|
break;
|
||||||
|
case RGB_24BPP:
|
||||||
|
nBits = new int[]{8, 8, 8};
|
||||||
|
bOffs = new int[]{2, 1, 0};
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new RuntimeException("not yet implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
|
||||||
|
ComponentColorModel cm = new ComponentColorModel
|
||||||
|
(cs, nBits, pf.isAlpha(), pf.isPreMultiplied(), Transparency.TRANSLUCENT, DataBuffer.TYPE_BYTE);
|
||||||
|
PixelInterleavedSampleModel csm =
|
||||||
|
new PixelInterleavedSampleModel(cm.getTransferType(), width, height, cm.getNumColorComponents(), stride, bOffs);
|
||||||
|
|
||||||
|
byte d[] = emb.getRawData();
|
||||||
|
WritableRaster raster = (WritableRaster) Raster.createRaster(csm, new DataBufferByte(d, d.length), null);
|
||||||
|
|
||||||
|
BufferedImage bi = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
|
// use HwmfEmbeddedType literal for conversion
|
||||||
|
ImageIO.write(bi, et.toString(), bos);
|
||||||
|
emb.setData(bos.toByteArray());
|
||||||
|
} catch (IOException e) {
|
||||||
|
// TODO: throw appropriate exception
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private HwmfEmbedded getEmfPlusImageData() {
|
||||||
|
HemfPlusObject.EmfPlusObject epo = (HemfPlusObject.EmfPlusObject)current;
|
||||||
|
assert(epo.getObjectType() == HemfPlusObject.EmfPlusObjectType.IMAGE);
|
||||||
|
|
||||||
|
final int objectId = epo.getObjectId();
|
||||||
|
|
||||||
|
HwmfEmbedded emb = new HwmfEmbedded();
|
||||||
|
|
||||||
|
HemfPlusObject.EmfPlusImage img = (HemfPlusObject.EmfPlusImage)epo.getObjectData();
|
||||||
|
assert(img.getImageDataType() != null);
|
||||||
|
|
||||||
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
|
try {
|
||||||
|
for (;;) {
|
||||||
|
bos.write(img.getImageData());
|
||||||
|
|
||||||
|
current = null;
|
||||||
|
//noinspection ConstantConditions
|
||||||
|
if (hasNext() &&
|
||||||
|
(current instanceof HemfPlusObject.EmfPlusObject) &&
|
||||||
|
((epo = (HemfPlusObject.EmfPlusObject) current).getObjectId() == objectId)
|
||||||
|
) {
|
||||||
|
img = (HemfPlusObject.EmfPlusImage)epo.getObjectData();
|
||||||
|
} else {
|
||||||
|
return emb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
// ByteArrayOutputStream doesn't throw IOException
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
emb.setData(bos.toByteArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -35,6 +35,7 @@ import org.apache.poi.hemf.record.emf.HemfHeader;
|
||||||
import org.apache.poi.hemf.record.emf.HemfRecord;
|
import org.apache.poi.hemf.record.emf.HemfRecord;
|
||||||
import org.apache.poi.hemf.record.emf.HemfRecordIterator;
|
import org.apache.poi.hemf.record.emf.HemfRecordIterator;
|
||||||
import org.apache.poi.hemf.record.emf.HemfWindowing;
|
import org.apache.poi.hemf.record.emf.HemfWindowing;
|
||||||
|
import org.apache.poi.hwmf.usermodel.HwmfEmbedded;
|
||||||
import org.apache.poi.util.Dimension2DDouble;
|
import org.apache.poi.util.Dimension2DDouble;
|
||||||
import org.apache.poi.util.Internal;
|
import org.apache.poi.util.Internal;
|
||||||
import org.apache.poi.util.LittleEndianInputStream;
|
import org.apache.poi.util.LittleEndianInputStream;
|
||||||
|
@ -158,4 +159,7 @@ public class HemfPicture implements Iterable<HemfRecord> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Iterable<HwmfEmbedded> getEmbeddings() {
|
||||||
|
return () -> new HemfEmbeddedIterator(HemfPicture.this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -142,7 +142,7 @@ public class HwmfGraphics {
|
||||||
graphicsCtx.fill(shape);
|
graphicsCtx.fill(shape);
|
||||||
}
|
}
|
||||||
|
|
||||||
// draw(shape);
|
draw(shape);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected BasicStroke getStroke() {
|
protected BasicStroke getStroke() {
|
||||||
|
|
|
@ -25,6 +25,7 @@ import java.awt.LinearGradientPaint;
|
||||||
import java.awt.MultipleGradientPaint;
|
import java.awt.MultipleGradientPaint;
|
||||||
import java.awt.RenderingHints;
|
import java.awt.RenderingHints;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.awt.image.IndexColorModel;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
@ -460,12 +461,29 @@ public class HwmfBitmapDib {
|
||||||
}
|
}
|
||||||
|
|
||||||
public BufferedImage getImage() {
|
public BufferedImage getImage() {
|
||||||
|
return getImage(null, null, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BufferedImage getImage(Color foreground, Color background, boolean hasAlpha) {
|
||||||
|
BufferedImage bi;
|
||||||
try {
|
try {
|
||||||
return ImageIO.read(getBMPStream());
|
bi = ImageIO.read(getBMPStream());
|
||||||
} catch (IOException|RuntimeException e) {
|
} catch (IOException|RuntimeException e) {
|
||||||
logger.log(POILogger.ERROR, "invalid bitmap data - returning placeholder image");
|
logger.log(POILogger.ERROR, "invalid bitmap data - returning placeholder image");
|
||||||
return getPlaceholder();
|
return getPlaceholder();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (foreground != null && background != null && headerBitCount == HwmfBitmapDib.BitCount.BI_BITCOUNT_1) {
|
||||||
|
IndexColorModel cmOld = (IndexColorModel)bi.getColorModel();
|
||||||
|
int transPixel = hasAlpha ? (((cmOld.getRGB(0) & 0xFFFFFF) == 0) ? 0 : 1) : -1;
|
||||||
|
int transferType = bi.getData().getTransferType();
|
||||||
|
int fg = foreground.getRGB(), bg = background.getRGB();
|
||||||
|
int[] cmap = { (transPixel == 0 ? bg : fg), (transPixel == 1 ? bg : fg) };
|
||||||
|
IndexColorModel cmNew = new IndexColorModel(1, cmap.length, cmap, 0, hasAlpha, transPixel, transferType);
|
||||||
|
bi = new BufferedImage(cmNew, bi.getRaster(), false, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return bi;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -18,10 +18,13 @@
|
||||||
package org.apache.poi.hwmf.record;
|
package org.apache.poi.hwmf.record;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import org.apache.poi.hwmf.draw.HwmfGraphics;
|
import org.apache.poi.hwmf.draw.HwmfGraphics;
|
||||||
import org.apache.poi.util.HexDump;
|
import org.apache.poi.util.HexDump;
|
||||||
import org.apache.poi.util.IOUtils;
|
import org.apache.poi.util.IOUtils;
|
||||||
|
import org.apache.poi.util.LittleEndian;
|
||||||
|
import org.apache.poi.util.LittleEndianCP950Reader;
|
||||||
import org.apache.poi.util.LittleEndianConsts;
|
import org.apache.poi.util.LittleEndianConsts;
|
||||||
import org.apache.poi.util.LittleEndianInputStream;
|
import org.apache.poi.util.LittleEndianInputStream;
|
||||||
|
|
||||||
|
@ -30,135 +33,140 @@ import org.apache.poi.util.LittleEndianInputStream;
|
||||||
* might not be directly accessible through WMF records
|
* might not be directly accessible through WMF records
|
||||||
*/
|
*/
|
||||||
public class HwmfEscape implements HwmfRecord {
|
public class HwmfEscape implements HwmfRecord {
|
||||||
|
private static final int MAX_OBJECT_SIZE = 0xFFFF;
|
||||||
|
|
||||||
public enum EscapeFunction {
|
public enum EscapeFunction {
|
||||||
/** Notifies the printer driver that the application has finished writing to a page. */
|
/** Notifies the printer driver that the application has finished writing to a page. */
|
||||||
NEWFRAME(0x0001),
|
NEWFRAME(0x0001, WmfEscapeUnknownData::new),
|
||||||
/** Stops processing the current document. */
|
/** Stops processing the current document. */
|
||||||
ABORTDOC(0x0002),
|
ABORTDOC(0x0002, WmfEscapeUnknownData::new),
|
||||||
/** Notifies the printer driver that the application has finished writing to a band. */
|
/** Notifies the printer driver that the application has finished writing to a band. */
|
||||||
NEXTBAND(0x0003),
|
NEXTBAND(0x0003, WmfEscapeUnknownData::new),
|
||||||
/** Sets color table values. */
|
/** Sets color table values. */
|
||||||
SETCOLORTABLE(0x0004),
|
SETCOLORTABLE(0x0004, WmfEscapeUnknownData::new),
|
||||||
/** Gets color table values. */
|
/** Gets color table values. */
|
||||||
GETCOLORTABLE(0x0005),
|
GETCOLORTABLE(0x0005, WmfEscapeUnknownData::new),
|
||||||
/** Causes all pending output to be flushed to the output device. */
|
/** Causes all pending output to be flushed to the output device. */
|
||||||
FLUSHOUT(0x0006),
|
FLUSHOUT(0x0006, WmfEscapeUnknownData::new),
|
||||||
/** Indicates that the printer driver SHOULD print text only, and no graphics. */
|
/** Indicates that the printer driver SHOULD print text only, and no graphics. */
|
||||||
DRAFTMODE(0x0007),
|
DRAFTMODE(0x0007, WmfEscapeUnknownData::new),
|
||||||
/** Queries a printer driver to determine whether a specific escape function is supported on the output device it drives. */
|
/** Queries a printer driver to determine whether a specific escape function is supported on the output device it drives. */
|
||||||
QUERYESCSUPPORT(0x0008),
|
QUERYESCSUPPORT(0x0008, WmfEscapeUnknownData::new),
|
||||||
/** Sets the application-defined function that allows a print job to be canceled during printing. */
|
/** Sets the application-defined function that allows a print job to be canceled during printing. */
|
||||||
SETABORTPROC(0x0009),
|
SETABORTPROC(0x0009, WmfEscapeUnknownData::new),
|
||||||
/** Notifies the printer driver that a new print job is starting. */
|
/** Notifies the printer driver that a new print job is starting. */
|
||||||
STARTDOC(0x000A),
|
STARTDOC(0x000A, WmfEscapeUnknownData::new),
|
||||||
/** Notifies the printer driver that the current print job is ending. */
|
/** Notifies the printer driver that the current print job is ending. */
|
||||||
ENDDOC(0x000B),
|
ENDDOC(0x000B, WmfEscapeUnknownData::new),
|
||||||
/** Retrieves the physical page size currently selected on an output device. */
|
/** Retrieves the physical page size currently selected on an output device. */
|
||||||
GETPHYSPAGESIZE(0x000C),
|
GETPHYSPAGESIZE(0x000C, WmfEscapeUnknownData::new),
|
||||||
/** Retrieves the offset from the upper-left corner of the physical page where the actual printing or drawing begins. */
|
/** Retrieves the offset from the upper-left corner of the physical page where the actual printing or drawing begins. */
|
||||||
GETPRINTINGOFFSET(0x000D),
|
GETPRINTINGOFFSET(0x000D, WmfEscapeUnknownData::new),
|
||||||
/** Retrieves the scaling factors for the x-axis and the y-axis of a printer. */
|
/** Retrieves the scaling factors for the x-axis and the y-axis of a printer. */
|
||||||
GETSCALINGFACTOR(0x000E),
|
GETSCALINGFACTOR(0x000E, WmfEscapeUnknownData::new),
|
||||||
/** Used to embed an enhanced metafile format (EMF) metafile within a WMF metafile. */
|
/** Used to embed an enhanced metafile format (EMF) metafile within a WMF metafile. */
|
||||||
META_ESCAPE_ENHANCED_METAFILE(0x000F),
|
META_ESCAPE_ENHANCED_METAFILE(0x000F, WmfEscapeEMF::new),
|
||||||
/** Sets the width of a pen in pixels. */
|
/** Sets the width of a pen in pixels. */
|
||||||
SETPENWIDTH(0x0010),
|
SETPENWIDTH(0x0010, WmfEscapeUnknownData::new),
|
||||||
/** Sets the number of copies. */
|
/** Sets the number of copies. */
|
||||||
SETCOPYCOUNT(0x0011),
|
SETCOPYCOUNT(0x0011, WmfEscapeUnknownData::new),
|
||||||
/** Sets the source, such as a particular paper tray or bin on a printer, for output forms. */
|
/** Sets the source, such as a particular paper tray or bin on a printer, for output forms. */
|
||||||
SETPAPERSOURCE(0x0012),
|
SETPAPERSOURCE(0x0012, WmfEscapeUnknownData::new),
|
||||||
/** This record passes through arbitrary data. */
|
/** This record passes through arbitrary data. */
|
||||||
PASSTHROUGH(0x0013),
|
PASSTHROUGH(0x0013, WmfEscapeUnknownData::new),
|
||||||
/** Gets information concerning graphics technology that is supported on a device. */
|
/** Gets information concerning graphics technology that is supported on a device. */
|
||||||
GETTECHNOLOGY(0x0014),
|
GETTECHNOLOGY(0x0014, WmfEscapeUnknownData::new),
|
||||||
/** Specifies the line-drawing mode to use in output to a device. */
|
/** Specifies the line-drawing mode to use in output to a device. */
|
||||||
SETLINECAP(0x0015),
|
SETLINECAP(0x0015, WmfEscapeUnknownData::new),
|
||||||
/** Specifies the line-joining mode to use in output to a device. */
|
/** Specifies the line-joining mode to use in output to a device. */
|
||||||
SETLINEJOIN(0x0016),
|
SETLINEJOIN(0x0016, WmfEscapeUnknownData::new),
|
||||||
/** Sets the limit for the length of miter joins to use in output to a device. */
|
/** Sets the limit for the length of miter joins to use in output to a device. */
|
||||||
SETMITERLIMIT(0x0017),
|
SETMITERLIMIT(0x0017, WmfEscapeUnknownData::new),
|
||||||
/** Retrieves or specifies settings concerning banding on a device, such as the number of bands. */
|
/** Retrieves or specifies settings concerning banding on a device, such as the number of bands. */
|
||||||
BANDINFO(0x0018),
|
BANDINFO(0x0018, WmfEscapeUnknownData::new),
|
||||||
/** Draws a rectangle with a defined pattern. */
|
/** Draws a rectangle with a defined pattern. */
|
||||||
DRAWPATTERNRECT(0x0019),
|
DRAWPATTERNRECT(0x0019, WmfEscapeUnknownData::new),
|
||||||
/** Retrieves the physical pen size currently defined on a device. */
|
/** Retrieves the physical pen size currently defined on a device. */
|
||||||
GETVECTORPENSIZE(0x001A),
|
GETVECTORPENSIZE(0x001A, WmfEscapeUnknownData::new),
|
||||||
/** Retrieves the physical brush size currently defined on a device. */
|
/** Retrieves the physical brush size currently defined on a device. */
|
||||||
GETVECTORBRUSHSIZE(0x001B),
|
GETVECTORBRUSHSIZE(0x001B, WmfEscapeUnknownData::new),
|
||||||
/** Enables or disables double-sided (duplex) printing on a device. */
|
/** Enables or disables double-sided (duplex) printing on a device. */
|
||||||
ENABLEDUPLEX(0x001C),
|
ENABLEDUPLEX(0x001C, WmfEscapeUnknownData::new),
|
||||||
/** Retrieves or specifies the source of output forms on a device. */
|
/** Retrieves or specifies the source of output forms on a device. */
|
||||||
GETSETPAPERBINS(0x001D),
|
GETSETPAPERBINS(0x001D, WmfEscapeUnknownData::new),
|
||||||
/** Retrieves or specifies the paper orientation on a device. */
|
/** Retrieves or specifies the paper orientation on a device. */
|
||||||
GETSETPRINTORIENT(0x001E),
|
GETSETPRINTORIENT(0x001E, WmfEscapeUnknownData::new),
|
||||||
/** Retrieves information concerning the sources of different forms on an output device. */
|
/** Retrieves information concerning the sources of different forms on an output device. */
|
||||||
ENUMPAPERBINS(0x001F),
|
ENUMPAPERBINS(0x001F, WmfEscapeUnknownData::new),
|
||||||
/** Specifies the scaling of device-independent bitmaps (DIBs). */
|
/** Specifies the scaling of device-independent bitmaps (DIBs). */
|
||||||
SETDIBSCALING(0x0020),
|
SETDIBSCALING(0x0020, WmfEscapeUnknownData::new),
|
||||||
/** Indicates the start and end of an encapsulated PostScript (EPS) section. */
|
/** Indicates the start and end of an encapsulated PostScript (EPS) section. */
|
||||||
EPSPRINTING(0x0021),
|
EPSPRINTING(0x0021, WmfEscapeUnknownData::new),
|
||||||
/** Queries a printer driver for paper dimensions and other forms data. */
|
/** Queries a printer driver for paper dimensions and other forms data. */
|
||||||
ENUMPAPERMETRICS(0x0022),
|
ENUMPAPERMETRICS(0x0022, WmfEscapeUnknownData::new),
|
||||||
/** Retrieves or specifies paper dimensions and other forms data on an output device. */
|
/** Retrieves or specifies paper dimensions and other forms data on an output device. */
|
||||||
GETSETPAPERMETRICS(0x0023),
|
GETSETPAPERMETRICS(0x0023, WmfEscapeUnknownData::new),
|
||||||
/** Sends arbitrary PostScript data to an output device. */
|
/** Sends arbitrary PostScript data to an output device. */
|
||||||
POSTSCRIPT_DATA(0x0025),
|
POSTSCRIPT_DATA(0x0025, WmfEscapeUnknownData::new),
|
||||||
/** Notifies an output device to ignore PostScript data. */
|
/** Notifies an output device to ignore PostScript data. */
|
||||||
POSTSCRIPT_IGNORE(0x0026),
|
POSTSCRIPT_IGNORE(0x0026, WmfEscapeUnknownData::new),
|
||||||
/** Gets the device units currently configured on an output device. */
|
/** Gets the device units currently configured on an output device. */
|
||||||
GETDEVICEUNITS(0x002A),
|
GETDEVICEUNITS(0x002A, WmfEscapeUnknownData::new),
|
||||||
/** Gets extended text metrics currently configured on an output device. */
|
/** Gets extended text metrics currently configured on an output device. */
|
||||||
GETEXTENDEDTEXTMETRICS(0x0100),
|
GETEXTENDEDTEXTMETRICS(0x0100, WmfEscapeUnknownData::new),
|
||||||
/** Gets the font kern table currently defined on an output device. */
|
/** Gets the font kern table currently defined on an output device. */
|
||||||
GETPAIRKERNTABLE(0x0102),
|
GETPAIRKERNTABLE(0x0102, WmfEscapeUnknownData::new),
|
||||||
/** Draws text using the currently selected font, background color, and text color. */
|
/** Draws text using the currently selected font, background color, and text color. */
|
||||||
EXTTEXTOUT(0x0200),
|
EXTTEXTOUT(0x0200, WmfEscapeUnknownData::new),
|
||||||
/** Gets the font face name currently configured on a device. */
|
/** Gets the font face name currently configured on a device. */
|
||||||
GETFACENAME(0x0201),
|
GETFACENAME(0x0201, WmfEscapeUnknownData::new),
|
||||||
/** Sets the font face name on a device. */
|
/** Sets the font face name on a device. */
|
||||||
DOWNLOADFACE(0x0202),
|
DOWNLOADFACE(0x0202, WmfEscapeUnknownData::new),
|
||||||
/** Queries a printer driver about the support for metafiles on an output device. */
|
/** Queries a printer driver about the support for metafiles on an output device. */
|
||||||
METAFILE_DRIVER(0x0801),
|
METAFILE_DRIVER(0x0801, WmfEscapeUnknownData::new),
|
||||||
/** Queries the printer driver about its support for DIBs on an output device. */
|
/** Queries the printer driver about its support for DIBs on an output device. */
|
||||||
QUERYDIBSUPPORT(0x0C01),
|
QUERYDIBSUPPORT(0x0C01, WmfEscapeUnknownData::new),
|
||||||
/** Opens a path. */
|
/** Opens a path. */
|
||||||
BEGIN_PATH(0x1000),
|
BEGIN_PATH(0x1000, WmfEscapeUnknownData::new),
|
||||||
/** Defines a clip region that is bounded by a path. The input MUST be a 16-bit quantity that defines the action to take. */
|
/** Defines a clip region that is bounded by a path. The input MUST be a 16-bit quantity that defines the action to take. */
|
||||||
CLIP_TO_PATH(0x1001),
|
CLIP_TO_PATH(0x1001, WmfEscapeUnknownData::new),
|
||||||
/** Ends a path. */
|
/** Ends a path. */
|
||||||
END_PATH(0x1002),
|
END_PATH(0x1002, WmfEscapeUnknownData::new),
|
||||||
/** The same as STARTDOC specified with a NULL document and output filename, data in raw mode, and a type of zero. */
|
/** The same as STARTDOC specified with a NULL document and output filename, data in raw mode, and a type of zero. */
|
||||||
OPEN_CHANNEL(0x100E),
|
OPEN_CHANNEL(0x100E, WmfEscapeUnknownData::new),
|
||||||
/** Instructs the printer driver to download sets of PostScript procedures. */
|
/** Instructs the printer driver to download sets of PostScript procedures. */
|
||||||
DOWNLOADHEADER(0x100F),
|
DOWNLOADHEADER(0x100F, WmfEscapeUnknownData::new),
|
||||||
/** The same as ENDDOC. See OPEN_CHANNEL. */
|
/** The same as ENDDOC. See OPEN_CHANNEL. */
|
||||||
CLOSE_CHANNEL(0x1010),
|
CLOSE_CHANNEL(0x1010, WmfEscapeUnknownData::new),
|
||||||
/** Sends arbitrary data directly to a printer driver, which is expected to process this data only when in PostScript mode. */
|
/** Sends arbitrary data directly to a printer driver, which is expected to process this data only when in PostScript mode. */
|
||||||
POSTSCRIPT_PASSTHROUGH(0x1013),
|
POSTSCRIPT_PASSTHROUGH(0x1013, WmfEscapeUnknownData::new),
|
||||||
/** Sends arbitrary data directly to the printer driver. */
|
/** Sends arbitrary data directly to the printer driver. */
|
||||||
ENCAPSULATED_POSTSCRIPT(0x1014),
|
ENCAPSULATED_POSTSCRIPT(0x1014, WmfEscapeUnknownData::new),
|
||||||
/** Sets the printer driver to either PostScript or GDI mode. */
|
/** Sets the printer driver to either PostScript or GDI mode. */
|
||||||
POSTSCRIPT_IDENTIFY(0x1015),
|
POSTSCRIPT_IDENTIFY(0x1015, WmfEscapeUnknownData::new),
|
||||||
/** Inserts a block of raw data into a PostScript stream. The input MUST be
|
/** Inserts a block of raw data into a PostScript stream. The input MUST be
|
||||||
a 32-bit quantity specifying the number of bytes to inject, a 16-bit quantity specifying the
|
a 32-bit quantity specifying the number of bytes to inject, a 16-bit quantity specifying the
|
||||||
injection point, and a 16-bit quantity specifying the page number, followed by the bytes to
|
injection point, and a 16-bit quantity specifying the page number, followed by the bytes to
|
||||||
inject. */
|
inject. */
|
||||||
POSTSCRIPT_INJECTION(0x1016),
|
POSTSCRIPT_INJECTION(0x1016, WmfEscapeUnknownData::new),
|
||||||
/** Checks whether the printer supports a JPEG image. */
|
/** Checks whether the printer supports a JPEG image. */
|
||||||
CHECKJPEGFORMAT(0x1017),
|
CHECKJPEGFORMAT(0x1017, WmfEscapeUnknownData::new),
|
||||||
/** Checks whether the printer supports a PNG image */
|
/** Checks whether the printer supports a PNG image */
|
||||||
CHECKPNGFORMAT(0x1018),
|
CHECKPNGFORMAT(0x1018, WmfEscapeUnknownData::new),
|
||||||
/** Gets information on a specified feature setting for a PostScript printer driver. */
|
/** Gets information on a specified feature setting for a PostScript printer driver. */
|
||||||
GET_PS_FEATURESETTING(0x1019),
|
GET_PS_FEATURESETTING(0x1019, WmfEscapeUnknownData::new),
|
||||||
/** Enables applications to write documents to a file or to a printer in XML Paper Specification (XPS) format. */
|
/** Enables applications to write documents to a file or to a printer in XML Paper Specification (XPS) format. */
|
||||||
MXDC_ESCAPE(0x101A),
|
MXDC_ESCAPE(0x101A, WmfEscapeUnknownData::new),
|
||||||
/** Enables applications to include private procedures and other arbitrary data in documents. */
|
/** Enables applications to include private procedures and other arbitrary data in documents. */
|
||||||
SPCLPASSTHROUGH2(0x11D8);
|
SPCLPASSTHROUGH2(0x11D8, WmfEscapeUnknownData::new);
|
||||||
|
|
||||||
int flag;
|
public int flag;
|
||||||
EscapeFunction(int flag) {
|
public final Supplier<? extends HwmfEscape.HwmfEscapeData> constructor;
|
||||||
|
|
||||||
|
|
||||||
|
EscapeFunction(int flag, Supplier<? extends HwmfEscape.HwmfEscapeData> constructor) {
|
||||||
this.flag = flag;
|
this.flag = flag;
|
||||||
|
this.constructor = constructor;
|
||||||
}
|
}
|
||||||
|
|
||||||
static EscapeFunction valueOf(int flag) {
|
static EscapeFunction valueOf(int flag) {
|
||||||
|
@ -169,21 +177,18 @@ public class HwmfEscape implements HwmfRecord {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface HwmfEscapeData {
|
||||||
|
public int init(LittleEndianInputStream leis, long recordSize, EscapeFunction escapeFunction) throws IOException;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A 16-bit unsigned integer that defines the escape function. The
|
* A 16-bit unsigned integer that defines the escape function. The
|
||||||
* value MUST be from the MetafileEscapes enumeration.
|
* value MUST be from the MetafileEscapes enumeration.
|
||||||
*/
|
*/
|
||||||
private EscapeFunction escapeFunction;
|
private EscapeFunction escapeFunction;
|
||||||
/**
|
private HwmfEscapeData escapeData;
|
||||||
* A 16-bit unsigned integer that specifies the size, in bytes, of the
|
|
||||||
* EscapeData field.
|
|
||||||
*/
|
|
||||||
private int byteCount;
|
|
||||||
/**
|
|
||||||
* An array of bytes of size ByteCount.
|
|
||||||
*/
|
|
||||||
private byte[] escapeData;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HwmfRecordType getWmfRecordType() {
|
public HwmfRecordType getWmfRecordType() {
|
||||||
return HwmfRecordType.escape;
|
return HwmfRecordType.escape;
|
||||||
|
@ -192,10 +197,22 @@ public class HwmfEscape implements HwmfRecord {
|
||||||
@Override
|
@Override
|
||||||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
|
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
|
||||||
escapeFunction = EscapeFunction.valueOf(leis.readUShort());
|
escapeFunction = EscapeFunction.valueOf(leis.readUShort());
|
||||||
byteCount = leis.readUShort();
|
// A 16-bit unsigned integer that specifies the size, in bytes, of the EscapeData field.
|
||||||
escapeData = IOUtils.toByteArray(leis,byteCount);
|
int byteCount = leis.readUShort();
|
||||||
|
int size = 2*LittleEndianConsts.SHORT_SIZE;
|
||||||
|
|
||||||
return 2*LittleEndianConsts.SHORT_SIZE+byteCount;
|
escapeData = escapeFunction.constructor.get();
|
||||||
|
size += escapeData.init(leis, byteCount, escapeFunction);
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EscapeFunction getEscapeFunction() {
|
||||||
|
return escapeFunction;
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T extends HwmfEscapeData> T getEscapeData() {
|
||||||
|
return (T)escapeData;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -206,7 +223,126 @@ public class HwmfEscape implements HwmfRecord {
|
||||||
public String toString() {
|
public String toString() {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.append("escape - function: "+escapeFunction+"\n");
|
sb.append("escape - function: "+escapeFunction+"\n");
|
||||||
sb.append(HexDump.dump(escapeData, 0, 0));
|
sb.append(escapeData.toString());
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class WmfEscapeUnknownData implements HwmfEscapeData {
|
||||||
|
EscapeFunction escapeFunction;
|
||||||
|
private byte[] escapeDataBytes;
|
||||||
|
|
||||||
|
public byte[] getEscapeDataBytes() {
|
||||||
|
return escapeDataBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int init(LittleEndianInputStream leis, long recordSize, EscapeFunction escapeFunction) throws IOException {
|
||||||
|
this.escapeFunction = escapeFunction;
|
||||||
|
escapeDataBytes = IOUtils.toByteArray(leis,recordSize,MAX_OBJECT_SIZE);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return HexDump.dump(escapeDataBytes, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class WmfEscapeEMF implements HwmfEscapeData {
|
||||||
|
// The magic for EMF parts, i.e. the byte sequence for "WMFC"
|
||||||
|
private static final int EMF_COMMENT_IDENTIFIER = 0x43464D57;
|
||||||
|
|
||||||
|
int commentIdentifier;
|
||||||
|
int commentType;
|
||||||
|
int version;
|
||||||
|
int checksum;
|
||||||
|
int flags;
|
||||||
|
int commentRecordCount;
|
||||||
|
int currentRecordSize;
|
||||||
|
int remainingBytes;
|
||||||
|
int emfRecordSize;
|
||||||
|
byte[] emfData;
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int init(LittleEndianInputStream leis, long recordSize, EscapeFunction escapeFunction) throws IOException {
|
||||||
|
if (recordSize < LittleEndianConsts.INT_SIZE) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A 32-bit unsigned integer that defines this record as a WMF Comment record.
|
||||||
|
int commentIdentifier = leis.readInt();
|
||||||
|
|
||||||
|
if (commentIdentifier != EMF_COMMENT_IDENTIFIER) {
|
||||||
|
// there are some WMF implementation using this record as a MFCOMMENT or similar
|
||||||
|
// if the commentIdentifier doesn't match, then return immediately
|
||||||
|
return LittleEndianConsts.INT_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A 32-bit unsigned integer that identifies the type of comment in this record.
|
||||||
|
// This value MUST be 0x00000001.
|
||||||
|
commentType = leis.readInt();
|
||||||
|
assert(commentType == 0x00000001);
|
||||||
|
|
||||||
|
// A 32-bit unsigned integer that specifies EMF metafile interoperability. This SHOULD be 0x00010000.
|
||||||
|
version = leis.readInt();
|
||||||
|
|
||||||
|
// A 16-bit unsigned integer used to validate the correctness of the embedded EMF stream.
|
||||||
|
// This value MUST be the one's-complement of the result of applying an XOR operation to all WORDs in the EMF stream.
|
||||||
|
checksum = leis.readUShort();
|
||||||
|
|
||||||
|
// This 32-bit unsigned integer is unused and MUST be set to zero.
|
||||||
|
flags = leis.readInt();
|
||||||
|
assert(flags == 0);
|
||||||
|
|
||||||
|
// A 32-bit unsigned integer that specifies the total number of consecutive META_ESCAPE_ENHANCED_METAFILE
|
||||||
|
// records that contain the embedded EMF metafile.
|
||||||
|
commentRecordCount = leis.readInt();
|
||||||
|
|
||||||
|
// A 32-bit unsigned integer that specifies the size, in bytes, of the EnhancedMetafileData field.
|
||||||
|
// This value MUST be less than or equal to 8,192.
|
||||||
|
currentRecordSize = leis.readInt();
|
||||||
|
assert(0 <= currentRecordSize && currentRecordSize <= 0x2000);
|
||||||
|
|
||||||
|
// A 32-bit unsigned integer that specifies the number of bytes in the EMF stream that remain to be
|
||||||
|
// processed after this record. Those additional EMF bytes MUST follow in the EnhancedMetafileData
|
||||||
|
// fields of subsequent META_ESCAPE_ENHANDED_METAFILE escape records.
|
||||||
|
remainingBytes = leis.readInt();
|
||||||
|
|
||||||
|
// A 32-bit unsigned integer that specifies the total size of the EMF stream embedded in this
|
||||||
|
// sequence of META_ESCAPE_ENHANCED_METAFILE records.
|
||||||
|
emfRecordSize = leis.readInt();
|
||||||
|
|
||||||
|
|
||||||
|
// A segment of an EMF file. The bytes in consecutive META_ESCAPE_ENHANCED_METAFILE records
|
||||||
|
// MUST be concatenated to represent the entire embedded EMF file.
|
||||||
|
emfData = IOUtils.toByteArray(leis, currentRecordSize, MAX_OBJECT_SIZE);
|
||||||
|
|
||||||
|
return LittleEndianConsts.INT_SIZE*8+ LittleEndianConsts.SHORT_SIZE+emfData.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isValid() {
|
||||||
|
return commentIdentifier == EMF_COMMENT_IDENTIFIER;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCommentRecordCount() {
|
||||||
|
return commentRecordCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCurrentRecordSize() {
|
||||||
|
return currentRecordSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRemainingBytes() {
|
||||||
|
return remainingBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getEmfRecordSize() {
|
||||||
|
return emfRecordSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getEmfData() {
|
||||||
|
return emfData;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ package org.apache.poi.hwmf.record;
|
||||||
import static org.apache.poi.hwmf.record.HwmfDraw.boundsToString;
|
import static org.apache.poi.hwmf.record.HwmfDraw.boundsToString;
|
||||||
import static org.apache.poi.hwmf.record.HwmfDraw.readPointS;
|
import static org.apache.poi.hwmf.record.HwmfDraw.readPointS;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
import java.awt.Shape;
|
import java.awt.Shape;
|
||||||
import java.awt.geom.Path2D;
|
import java.awt.geom.Path2D;
|
||||||
import java.awt.geom.Point2D;
|
import java.awt.geom.Point2D;
|
||||||
|
@ -29,6 +30,7 @@ import java.io.IOException;
|
||||||
|
|
||||||
import org.apache.poi.hwmf.draw.HwmfDrawProperties;
|
import org.apache.poi.hwmf.draw.HwmfDrawProperties;
|
||||||
import org.apache.poi.hwmf.draw.HwmfGraphics;
|
import org.apache.poi.hwmf.draw.HwmfGraphics;
|
||||||
|
import org.apache.poi.hwmf.record.HwmfMisc.WmfSetBkMode.HwmfBkMode;
|
||||||
import org.apache.poi.util.LittleEndianConsts;
|
import org.apache.poi.util.LittleEndianConsts;
|
||||||
import org.apache.poi.util.LittleEndianInputStream;
|
import org.apache.poi.util.LittleEndianInputStream;
|
||||||
|
|
||||||
|
@ -37,7 +39,29 @@ public class HwmfFill {
|
||||||
* A record which contains an image (to be extracted)
|
* A record which contains an image (to be extracted)
|
||||||
*/
|
*/
|
||||||
public interface HwmfImageRecord {
|
public interface HwmfImageRecord {
|
||||||
BufferedImage getImage();
|
|
||||||
|
default BufferedImage getImage() {
|
||||||
|
return getImage(Color.BLACK, new Color(0x00FFFFFF, true), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide an image using the fore-/background color, in case of a 1-bit pattern
|
||||||
|
* @param foreground the foreground color
|
||||||
|
* @param background the background color
|
||||||
|
* @param hasAlpha if true, the background color is rendered transparent - see {@link HwmfMisc.WmfSetBkMode.HwmfBkMode}
|
||||||
|
* @return the image
|
||||||
|
*
|
||||||
|
* @since POI 4.1.1
|
||||||
|
*/
|
||||||
|
BufferedImage getImage(Color foreground, Color background, boolean hasAlpha);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the raw BMP data
|
||||||
|
*
|
||||||
|
* @see <a href="https://en.wikipedia.org/wiki/BMP_file_format">BMP format</a>
|
||||||
|
* @since POI 4.1.1
|
||||||
|
*/
|
||||||
|
byte[] getBMPData();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -497,7 +521,9 @@ public class HwmfFill {
|
||||||
HwmfDrawProperties prop = ctx.getProperties();
|
HwmfDrawProperties prop = ctx.getProperties();
|
||||||
prop.setRasterOp(rasterOperation);
|
prop.setRasterOp(rasterOperation);
|
||||||
if (bitmap.isValid()) {
|
if (bitmap.isValid()) {
|
||||||
ctx.drawImage(getImage(), srcBounds, dstBounds);
|
BufferedImage bi = bitmap.getImage(prop.getPenColor().getColor(), prop.getBackgroundColor().getColor(),
|
||||||
|
prop.getBkMode() == HwmfBkMode.TRANSPARENT);
|
||||||
|
ctx.drawImage(bi, srcBounds, dstBounds);
|
||||||
} else if (!dstBounds.isEmpty()) {
|
} else if (!dstBounds.isEmpty()) {
|
||||||
BufferedImage bi = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
|
BufferedImage bi = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
|
||||||
ctx.drawImage(bi, new Rectangle2D.Double(0,0,100,100), dstBounds);
|
ctx.drawImage(bi, new Rectangle2D.Double(0,0,100,100), dstBounds);
|
||||||
|
@ -505,8 +531,17 @@ public class HwmfFill {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BufferedImage getImage() {
|
public BufferedImage getImage(Color foreground, Color background, boolean hasAlpha) {
|
||||||
return bitmap.getImage();
|
return bitmap.getImage(foreground,background,hasAlpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HwmfBitmapDib getBitmap() {
|
||||||
|
return bitmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getBMPData() {
|
||||||
|
return bitmap.getBMPData();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -631,8 +666,13 @@ public class HwmfFill {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BufferedImage getImage() {
|
public BufferedImage getImage(Color foreground, Color background, boolean hasAlpha) {
|
||||||
return dib.getImage();
|
return dib.getImage(foreground,background,hasAlpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getBMPData() {
|
||||||
|
return dib.getBMPData();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -738,8 +778,13 @@ public class HwmfFill {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BufferedImage getImage() {
|
public BufferedImage getImage(Color foreground, Color background, boolean hasAlpha) {
|
||||||
return (target != null && target.isValid()) ? target.getImage() : null;
|
return (target != null && target.isValid()) ? target.getImage(foreground,background,hasAlpha) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getBMPData() {
|
||||||
|
return (target != null && target.isValid()) ? target.getBMPData() : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
package org.apache.poi.hwmf.record;
|
package org.apache.poi.hwmf.record;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
import java.awt.geom.Dimension2D;
|
import java.awt.geom.Dimension2D;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -25,6 +26,7 @@ import org.apache.poi.hwmf.draw.HwmfDrawProperties;
|
||||||
import org.apache.poi.hwmf.draw.HwmfGraphics;
|
import org.apache.poi.hwmf.draw.HwmfGraphics;
|
||||||
import org.apache.poi.hwmf.record.HwmfFill.ColorUsage;
|
import org.apache.poi.hwmf.record.HwmfFill.ColorUsage;
|
||||||
import org.apache.poi.hwmf.record.HwmfFill.HwmfImageRecord;
|
import org.apache.poi.hwmf.record.HwmfFill.HwmfImageRecord;
|
||||||
|
import org.apache.poi.hwmf.record.HwmfMisc.WmfSetBkMode.HwmfBkMode;
|
||||||
import org.apache.poi.util.Dimension2DDouble;
|
import org.apache.poi.util.Dimension2DDouble;
|
||||||
import org.apache.poi.util.LittleEndianConsts;
|
import org.apache.poi.util.LittleEndianConsts;
|
||||||
import org.apache.poi.util.LittleEndianInputStream;
|
import org.apache.poi.util.LittleEndianInputStream;
|
||||||
|
@ -459,19 +461,31 @@ public class HwmfMisc {
|
||||||
}
|
}
|
||||||
HwmfDrawProperties prop = ctx.getProperties();
|
HwmfDrawProperties prop = ctx.getProperties();
|
||||||
prop.setBrushStyle(style);
|
prop.setBrushStyle(style);
|
||||||
prop.setBrushBitmap(getImage());
|
prop.setBrushBitmap(getImage(prop.getBrushColor().getColor(), prop.getBackgroundColor().getColor(),
|
||||||
|
prop.getBkMode() == HwmfBkMode.TRANSPARENT));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BufferedImage getImage() {
|
public BufferedImage getImage(Color foreground, Color background, boolean hasAlpha) {
|
||||||
if (patternDib != null && patternDib.isValid()) {
|
if (patternDib != null && patternDib.isValid()) {
|
||||||
return patternDib.getImage();
|
return patternDib.getImage(foreground, background, hasAlpha);
|
||||||
} else if (pattern16 != null) {
|
} else if (pattern16 != null) {
|
||||||
return pattern16.getImage();
|
return pattern16.getImage();
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getBMPData() {
|
||||||
|
if (patternDib != null && patternDib.isValid()) {
|
||||||
|
return patternDib.getBMPData();
|
||||||
|
} else if (pattern16 != null) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -66,7 +66,13 @@ public class HwmfPlaceableHeader {
|
||||||
* This value can be used to determine whether the metafile has become corrupted.
|
* This value can be used to determine whether the metafile has become corrupted.
|
||||||
*/
|
*/
|
||||||
leis.readShort();
|
leis.readShort();
|
||||||
|
|
||||||
|
// sometimes the placeable header is filled/aligned to dwords.
|
||||||
|
// check for padding 0 bytes.
|
||||||
|
leis.mark(LittleEndianConsts.INT_SIZE);
|
||||||
|
if (leis.readShort() != 0) {
|
||||||
|
leis.reset();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static HwmfPlaceableHeader readHeader(LittleEndianInputStream leis) throws IOException {
|
public static HwmfPlaceableHeader readHeader(LittleEndianInputStream leis) throws IOException {
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
/* ====================================================================
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
contributor license agreements. See the NOTICE file distributed with
|
||||||
|
this work for additional information regarding copyright ownership.
|
||||||
|
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
(the "License"); you may not use this file except in compliance with
|
||||||
|
the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
==================================================================== */
|
||||||
|
|
||||||
|
package org.apache.poi.hwmf.usermodel;
|
||||||
|
|
||||||
|
import org.apache.poi.util.Beta;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An embedded resource - this class hides the logic of chained emf+ object records and other internals.
|
||||||
|
* Consider its API as unstable for now, i.e. there's no guarantee for backward compatibility
|
||||||
|
*/
|
||||||
|
@Beta
|
||||||
|
public class HwmfEmbedded {
|
||||||
|
|
||||||
|
private HwmfEmbeddedType embeddedType;
|
||||||
|
private byte[] data;
|
||||||
|
|
||||||
|
public HwmfEmbeddedType getEmbeddedType() {
|
||||||
|
return embeddedType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getRawData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEmbeddedType(HwmfEmbeddedType embeddedType) {
|
||||||
|
this.embeddedType = embeddedType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setData(byte[] data) {
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,140 @@
|
||||||
|
/* ====================================================================
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
contributor license agreements. See the NOTICE file distributed with
|
||||||
|
this work for additional information regarding copyright ownership.
|
||||||
|
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
(the "License"); you may not use this file except in compliance with
|
||||||
|
the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
==================================================================== */
|
||||||
|
|
||||||
|
package org.apache.poi.hwmf.usermodel;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.Deque;
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
import org.apache.poi.hwmf.record.HwmfEscape;
|
||||||
|
import org.apache.poi.hwmf.record.HwmfEscape.EscapeFunction;
|
||||||
|
import org.apache.poi.hwmf.record.HwmfEscape.WmfEscapeEMF;
|
||||||
|
import org.apache.poi.hwmf.record.HwmfFill.HwmfImageRecord;
|
||||||
|
import org.apache.poi.hwmf.record.HwmfRecord;
|
||||||
|
|
||||||
|
public class HwmfEmbeddedIterator implements Iterator<HwmfEmbedded> {
|
||||||
|
|
||||||
|
private final Deque<Iterator<?>> iterStack = new ArrayDeque<>();
|
||||||
|
private Object current;
|
||||||
|
|
||||||
|
public HwmfEmbeddedIterator(HwmfPicture wmf) {
|
||||||
|
this(wmf.getRecords().iterator());
|
||||||
|
}
|
||||||
|
|
||||||
|
public HwmfEmbeddedIterator(Iterator<HwmfRecord> recordIterator) {
|
||||||
|
iterStack.add(recordIterator);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
if (iterStack.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current != null) {
|
||||||
|
// don't search twice and potentially skip items
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterator<?> iter;
|
||||||
|
do {
|
||||||
|
iter = iterStack.peek();
|
||||||
|
while (iter.hasNext()) {
|
||||||
|
Object obj = iter.next();
|
||||||
|
if (obj instanceof HwmfImageRecord) {
|
||||||
|
current = obj;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj instanceof HwmfEscape && ((HwmfEscape)obj).getEscapeFunction() == EscapeFunction.META_ESCAPE_ENHANCED_METAFILE) {
|
||||||
|
WmfEscapeEMF emfData = ((HwmfEscape)obj).getEscapeData();
|
||||||
|
if (emfData.isValid()) {
|
||||||
|
current = obj;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
iterStack.pop();
|
||||||
|
} while (!iterStack.isEmpty());
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HwmfEmbedded next() {
|
||||||
|
HwmfEmbedded emb;
|
||||||
|
if ((emb = checkHwmfImageRecord()) != null) {
|
||||||
|
return emb;
|
||||||
|
}
|
||||||
|
if ((emb = checkHwmfEscapeRecord()) != null) {
|
||||||
|
return emb;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private HwmfEmbedded checkHwmfImageRecord() {
|
||||||
|
if (!(current instanceof HwmfImageRecord)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
HwmfImageRecord hir = (HwmfImageRecord)current;
|
||||||
|
current = null;
|
||||||
|
|
||||||
|
HwmfEmbedded emb = new HwmfEmbedded();
|
||||||
|
emb.setEmbeddedType(HwmfEmbeddedType.BMP);
|
||||||
|
emb.setData(hir.getBMPData());
|
||||||
|
|
||||||
|
return emb;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private HwmfEmbedded checkHwmfEscapeRecord() {
|
||||||
|
if (!(current instanceof HwmfEscape)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final HwmfEscape esc = (HwmfEscape)current;
|
||||||
|
assert(esc.getEscapeFunction() == EscapeFunction.META_ESCAPE_ENHANCED_METAFILE);
|
||||||
|
|
||||||
|
WmfEscapeEMF img = esc.getEscapeData();
|
||||||
|
assert(img.isValid());
|
||||||
|
current = null;
|
||||||
|
|
||||||
|
final HwmfEmbedded emb = new HwmfEmbedded();
|
||||||
|
emb.setEmbeddedType(HwmfEmbeddedType.EMF);
|
||||||
|
|
||||||
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
|
try {
|
||||||
|
for (;;) {
|
||||||
|
bos.write(img.getEmfData());
|
||||||
|
|
||||||
|
current = null;
|
||||||
|
if (img.getRemainingBytes() > 0 && hasNext() && (current instanceof HwmfEscape)) {
|
||||||
|
img = ((HwmfEscape)current).getEscapeData();
|
||||||
|
} else {
|
||||||
|
return emb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
// ByteArrayOutputStream doesn't throw IOException
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
emb.setData(bos.toByteArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
/* ====================================================================
|
||||||
|
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
contributor license agreements. See the NOTICE file distributed with
|
||||||
|
this work for additional information regarding copyright ownership.
|
||||||
|
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
(the "License"); you may not use this file except in compliance with
|
||||||
|
the License. You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
==================================================================== */
|
||||||
|
|
||||||
|
package org.apache.poi.hwmf.usermodel;
|
||||||
|
|
||||||
|
public enum HwmfEmbeddedType {
|
||||||
|
BITMAP(".bitmap"),
|
||||||
|
WMF(".wmf"),
|
||||||
|
EMF(".emf"),
|
||||||
|
EPS(".eps"),
|
||||||
|
JPEG(".jpg"),
|
||||||
|
GIF(".gif"),
|
||||||
|
TIFF(".tiff"),
|
||||||
|
PNG(".png"),
|
||||||
|
BMP(".bmp"),
|
||||||
|
UNKNOWN(".dat");
|
||||||
|
|
||||||
|
public final String extension;
|
||||||
|
|
||||||
|
HwmfEmbeddedType(String extension) {
|
||||||
|
this.extension = extension;
|
||||||
|
}
|
||||||
|
}
|
|
@ -127,8 +127,10 @@ public class HwmfPicture {
|
||||||
ctx.scale(graphicsBounds.getWidth()/wmfBounds.getWidth(), graphicsBounds.getHeight()/wmfBounds.getHeight());
|
ctx.scale(graphicsBounds.getWidth()/wmfBounds.getWidth(), graphicsBounds.getHeight()/wmfBounds.getHeight());
|
||||||
|
|
||||||
HwmfGraphics g = new HwmfGraphics(ctx, wmfBounds);
|
HwmfGraphics g = new HwmfGraphics(ctx, wmfBounds);
|
||||||
|
int idx = 0;
|
||||||
for (HwmfRecord r : records) {
|
for (HwmfRecord r : records) {
|
||||||
r.draw(g);
|
r.draw(g);
|
||||||
|
idx++;
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
ctx.setTransform(at);
|
ctx.setTransform(at);
|
||||||
|
@ -184,4 +186,8 @@ public class HwmfPicture {
|
||||||
double coeff = Units.POINT_DPI/inch;
|
double coeff = Units.POINT_DPI/inch;
|
||||||
return new Dimension((int)Math.round(bounds.getWidth()*coeff), (int)Math.round(bounds.getHeight()*coeff));
|
return new Dimension((int)Math.round(bounds.getWidth()*coeff), (int)Math.round(bounds.getHeight()*coeff));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Iterable<HwmfEmbedded> getEmbeddings() {
|
||||||
|
return () -> new HwmfEmbeddedIterator(HwmfPicture.this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,7 @@ import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.nio.file.StandardOpenOption;
|
import java.nio.file.StandardOpenOption;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -51,6 +52,8 @@ import org.apache.poi.hemf.record.emf.HemfRecordType;
|
||||||
import org.apache.poi.hemf.record.emf.HemfText;
|
import org.apache.poi.hemf.record.emf.HemfText;
|
||||||
import org.apache.poi.hwmf.record.HwmfRecord;
|
import org.apache.poi.hwmf.record.HwmfRecord;
|
||||||
import org.apache.poi.hwmf.record.HwmfText;
|
import org.apache.poi.hwmf.record.HwmfText;
|
||||||
|
import org.apache.poi.hwmf.usermodel.HwmfEmbedded;
|
||||||
|
import org.apache.poi.hwmf.usermodel.HwmfEmbeddedType;
|
||||||
import org.apache.poi.hwmf.usermodel.HwmfPicture;
|
import org.apache.poi.hwmf.usermodel.HwmfPicture;
|
||||||
import org.apache.poi.util.IOUtils;
|
import org.apache.poi.util.IOUtils;
|
||||||
import org.apache.poi.util.RecordFormatException;
|
import org.apache.poi.util.RecordFormatException;
|
||||||
|
@ -73,16 +76,17 @@ public class HemfPictureTest {
|
||||||
// emfs/govdocs1/844/844795.ppt_2.emf
|
// emfs/govdocs1/844/844795.ppt_2.emf
|
||||||
// emfs/commoncrawl2/TO/TOYZSTNUSW5OFCFUQ6T5FBLIDLCRF3NH_0.emf
|
// emfs/commoncrawl2/TO/TOYZSTNUSW5OFCFUQ6T5FBLIDLCRF3NH_0.emf
|
||||||
|
|
||||||
final boolean writeLog = true;
|
final boolean writeLog = false;
|
||||||
final boolean dumpRecords = false;
|
final boolean dumpRecords = false;
|
||||||
final boolean savePng = true;
|
final boolean savePng = false;
|
||||||
|
final boolean dumpEmbedded = true;
|
||||||
|
|
||||||
Set<String> passed = new HashSet<>();
|
Set<String> passed = new HashSet<>();
|
||||||
|
|
||||||
try (BufferedWriter sucWrite = parseEmfLog(passed, "emf-success.txt");
|
try (BufferedWriter sucWrite = parseEmfLog(passed, "emf-success.txt");
|
||||||
BufferedWriter parseError = parseEmfLog(passed, "emf-parse.txt");
|
BufferedWriter parseError = parseEmfLog(passed, "emf-parse.txt");
|
||||||
BufferedWriter renderError = parseEmfLog(passed, "emf-render.txt");
|
BufferedWriter renderError = parseEmfLog(passed, "emf-render.txt");
|
||||||
SevenZFile sevenZFile = new SevenZFile(new File("tmp/render_emf.7z"))) {
|
SevenZFile sevenZFile = new SevenZFile(new File("tmp/plus_emf.7z"))) {
|
||||||
for (int idx=0;;idx++) {
|
for (int idx=0;;idx++) {
|
||||||
SevenZArchiveEntry entry = sevenZFile.getNextEntry();
|
SevenZArchiveEntry entry = sevenZFile.getNextEntry();
|
||||||
if (entry == null) break;
|
if (entry == null) break;
|
||||||
|
@ -90,6 +94,11 @@ public class HemfPictureTest {
|
||||||
|
|
||||||
if (entry.isDirectory() || !etName.endsWith(".emf") || passed.contains(etName)) continue;
|
if (entry.isDirectory() || !etName.endsWith(".emf") || passed.contains(etName)) continue;
|
||||||
|
|
||||||
|
if (!etName.equals("emfs/commoncrawl2/2S/2SYMYPLNJURGCXJKLNZCJQGIBHVMQTRS_0.emf")) continue;
|
||||||
|
|
||||||
|
// emfs/commoncrawl2/ZJ/ZJT2BZPLQR7DKSKYLYL6GRDEUM2KIO5F_4.emf
|
||||||
|
// emfs/govdocs1/005/005203.ppt_3.emf
|
||||||
|
|
||||||
System.out.println(etName);
|
System.out.println(etName);
|
||||||
|
|
||||||
int size = sevenZFile.read(buf);
|
int size = sevenZFile.read(buf);
|
||||||
|
@ -116,6 +125,18 @@ public class HemfPictureTest {
|
||||||
dumpRecords(emf);
|
dumpRecords(emf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (dumpEmbedded) {
|
||||||
|
int embIdx = 0;
|
||||||
|
for (HwmfEmbedded emb : emf.getEmbeddings()) {
|
||||||
|
final File embName = new File("build/tmp", "emb_"+etName.replaceFirst(".+/", "").replace(".emf", "_"+embIdx + emb.getEmbeddedType().extension) );
|
||||||
|
// try (FileOutputStream fos = new FileOutputStream(embName)) {
|
||||||
|
// fos.write(emb.getRawData());
|
||||||
|
// }
|
||||||
|
embIdx++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Graphics2D g = null;
|
Graphics2D g = null;
|
||||||
try {
|
try {
|
||||||
Dimension2D dim = emf.getSize();
|
Dimension2D dim = emf.getSize();
|
||||||
|
@ -194,7 +215,7 @@ public class HemfPictureTest {
|
||||||
if (Files.exists(log)) {
|
if (Files.exists(log)) {
|
||||||
soo = StandardOpenOption.APPEND;
|
soo = StandardOpenOption.APPEND;
|
||||||
try (Stream<String> stream = Files.lines(log)) {
|
try (Stream<String> stream = Files.lines(log)) {
|
||||||
stream.forEach((s) -> passed.add(s.split("\\s")[0]));
|
stream.filter(s -> !s.startsWith("#")).forEach((s) -> passed.add(s.split("\\s")[0]));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
soo = StandardOpenOption.CREATE;
|
soo = StandardOpenOption.CREATE;
|
||||||
|
@ -380,7 +401,28 @@ public class HemfPictureTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
@Test
|
||||||
govdocs1 064213.doc-0.emf contains an example of extextouta
|
public void nestedWmfEmf() throws Exception {
|
||||||
*/
|
try (InputStream is = sl_samples.openResourceAsStream("nested_wmf.emf")) {
|
||||||
|
HemfPicture emf1 = new HemfPicture(is);
|
||||||
|
List<HwmfEmbedded> embeds = new ArrayList<>();
|
||||||
|
emf1.getEmbeddings().forEach(embeds::add);
|
||||||
|
assertEquals(1, embeds.size());
|
||||||
|
assertEquals(HwmfEmbeddedType.WMF, embeds.get(0).getEmbeddedType());
|
||||||
|
|
||||||
|
HwmfPicture wmf = new HwmfPicture(new ByteArrayInputStream(embeds.get(0).getRawData()));
|
||||||
|
embeds.clear();
|
||||||
|
wmf.getEmbeddings().forEach(embeds::add);
|
||||||
|
assertEquals(3, embeds.size());
|
||||||
|
assertEquals(HwmfEmbeddedType.EMF, embeds.get(0).getEmbeddedType());
|
||||||
|
|
||||||
|
HemfPicture emf2 = new HemfPicture(new ByteArrayInputStream(embeds.get(0).getRawData()));
|
||||||
|
embeds.clear();
|
||||||
|
emf2.getEmbeddings().forEach(embeds::add);
|
||||||
|
assertTrue(embeds.isEmpty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* govdocs1 064213.doc-0.emf contains an example of extextouta */
|
||||||
}
|
}
|
|
@ -47,6 +47,7 @@ import org.apache.poi.hwmf.record.HwmfFont;
|
||||||
import org.apache.poi.hwmf.record.HwmfRecord;
|
import org.apache.poi.hwmf.record.HwmfRecord;
|
||||||
import org.apache.poi.hwmf.record.HwmfRecordType;
|
import org.apache.poi.hwmf.record.HwmfRecordType;
|
||||||
import org.apache.poi.hwmf.record.HwmfText;
|
import org.apache.poi.hwmf.record.HwmfText;
|
||||||
|
import org.apache.poi.hwmf.usermodel.HwmfEmbedded;
|
||||||
import org.apache.poi.hwmf.usermodel.HwmfPicture;
|
import org.apache.poi.hwmf.usermodel.HwmfPicture;
|
||||||
import org.apache.poi.sl.usermodel.PictureData;
|
import org.apache.poi.sl.usermodel.PictureData;
|
||||||
import org.apache.poi.sl.usermodel.PictureData.PictureType;
|
import org.apache.poi.sl.usermodel.PictureData.PictureType;
|
||||||
|
@ -82,8 +83,10 @@ public class TestHwmfParsing {
|
||||||
@Test
|
@Test
|
||||||
@Ignore("This is work-in-progress and not a real unit test ...")
|
@Ignore("This is work-in-progress and not a real unit test ...")
|
||||||
public void paint() throws IOException {
|
public void paint() throws IOException {
|
||||||
File f = samples.getFile("santa.wmf");
|
boolean dumpEmbedded = true;
|
||||||
// File f = new File("bla.wmf");
|
|
||||||
|
// File f = samples.getFile("santa.wmf");
|
||||||
|
File f = new File("testme.wmf");
|
||||||
FileInputStream fis = new FileInputStream(f);
|
FileInputStream fis = new FileInputStream(f);
|
||||||
HwmfPicture wmf = new HwmfPicture(fis);
|
HwmfPicture wmf = new HwmfPicture(fis);
|
||||||
fis.close();
|
fis.close();
|
||||||
|
@ -92,12 +95,10 @@ public class TestHwmfParsing {
|
||||||
int width = Units.pointsToPixel(dim.getWidth());
|
int width = Units.pointsToPixel(dim.getWidth());
|
||||||
// keep aspect ratio for height
|
// keep aspect ratio for height
|
||||||
int height = Units.pointsToPixel(dim.getHeight());
|
int height = Units.pointsToPixel(dim.getHeight());
|
||||||
double max = Math.max(width, height);
|
double scale = (width > height) ? 1500 / width : 1500 / width;
|
||||||
if (max > 1500) {
|
width *= scale;
|
||||||
width *= 1500/max;
|
height *= scale;
|
||||||
height *= 1500/max;
|
|
||||||
}
|
|
||||||
|
|
||||||
BufferedImage bufImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
|
BufferedImage bufImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
|
||||||
Graphics2D g = bufImg.createGraphics();
|
Graphics2D g = bufImg.createGraphics();
|
||||||
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||||
|
@ -110,6 +111,17 @@ public class TestHwmfParsing {
|
||||||
g.dispose();
|
g.dispose();
|
||||||
|
|
||||||
ImageIO.write(bufImg, "PNG", new File("bla.png"));
|
ImageIO.write(bufImg, "PNG", new File("bla.png"));
|
||||||
|
|
||||||
|
if (dumpEmbedded) {
|
||||||
|
int embIdx = 0;
|
||||||
|
for (HwmfEmbedded emb : wmf.getEmbeddings()) {
|
||||||
|
final File embName = new File("build/tmp", "emb_"+embIdx + emb.getEmbeddedType().extension);
|
||||||
|
try (FileOutputStream fos = new FileOutputStream(embName)) {
|
||||||
|
fos.write(emb.getRawData());
|
||||||
|
}
|
||||||
|
embIdx++;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -190,7 +202,7 @@ public class TestHwmfParsing {
|
||||||
int width = Units.pointsToPixel(dim.getWidth());
|
int width = Units.pointsToPixel(dim.getWidth());
|
||||||
// keep aspect ratio for height
|
// keep aspect ratio for height
|
||||||
int height = Units.pointsToPixel(dim.getHeight());
|
int height = Units.pointsToPixel(dim.getHeight());
|
||||||
|
|
||||||
BufferedImage bufImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
|
BufferedImage bufImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
|
||||||
Graphics2D g = bufImg.createGraphics();
|
Graphics2D g = bufImg.createGraphics();
|
||||||
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||||
|
|
Binary file not shown.
Loading…
Reference in New Issue