git-svn-id: https://svn.apache.org/repos/asf/poi/branches/hemf@1840956 13f79535-47bb-0310-9956-ffa450edef68pull/131/head
@@ -15,21 +15,21 @@ | |||
limitations under the License. | |||
==================================================================== */ | |||
package org.apache.poi.xdgf.geom; | |||
package org.apache.poi.util; | |||
import java.awt.geom.Dimension2D; | |||
public class Dimension2dDouble extends Dimension2D { | |||
public class Dimension2DDouble extends Dimension2D { | |||
double width; | |||
double height; | |||
public Dimension2dDouble() { | |||
public Dimension2DDouble() { | |||
width = 0d; | |||
height = 0d; | |||
} | |||
public Dimension2dDouble(double width, double height) { | |||
public Dimension2DDouble(double width, double height) { | |||
this.width = width; | |||
this.height = height; | |||
} | |||
@@ -52,8 +52,8 @@ public class Dimension2dDouble extends Dimension2D { | |||
@Override | |||
public boolean equals(Object obj) { | |||
if (obj instanceof Dimension2dDouble) { | |||
Dimension2dDouble other = (Dimension2dDouble) obj; | |||
if (obj instanceof Dimension2DDouble) { | |||
Dimension2DDouble other = (Dimension2DDouble) obj; | |||
return width == other.width && height == other.height; | |||
} | |||
@@ -68,6 +68,6 @@ public class Dimension2dDouble extends Dimension2D { | |||
@Override | |||
public String toString() { | |||
return "Dimension2dDouble[" + width + ", " + height + "]"; | |||
return "Dimension2DDouble[" + width + ", " + height + "]"; | |||
} | |||
} |
@@ -22,7 +22,7 @@ import java.awt.geom.Rectangle2D; | |||
import org.apache.poi.ooxml.POIXMLException; | |||
import org.apache.poi.util.Internal; | |||
import org.apache.poi.xdgf.geom.Dimension2dDouble; | |||
import org.apache.poi.util.Dimension2DDouble; | |||
import com.microsoft.schemas.office.visio.x2012.main.PageType; | |||
@@ -75,14 +75,14 @@ public class XDGFPage { | |||
/** | |||
* @return width/height of page | |||
*/ | |||
public Dimension2dDouble getPageSize() { | |||
public Dimension2DDouble getPageSize() { | |||
XDGFCell w = _pageSheet.getCell("PageWidth"); | |||
XDGFCell h = _pageSheet.getCell("PageHeight"); | |||
if (w == null || h == null) | |||
throw new POIXMLException("Cannot determine page size"); | |||
return new Dimension2dDouble(Double.parseDouble(w.getValue()), | |||
return new Dimension2DDouble(Double.parseDouble(w.getValue()), | |||
Double.parseDouble(h.getValue())); | |||
} | |||
@@ -109,7 +109,7 @@ public class XDGFPage { | |||
* @return bounding box of page | |||
*/ | |||
public Rectangle2D getBoundingBox() { | |||
Dimension2dDouble sz = getPageSize(); | |||
Dimension2DDouble sz = getPageSize(); | |||
Point2D.Double offset = getPageOffset(); | |||
return new Rectangle2D.Double(-offset.getX(), -offset.getY(), |
@@ -25,7 +25,7 @@ import java.io.*; | |||
import javax.imageio.ImageIO; | |||
import org.apache.poi.xdgf.geom.Dimension2dDouble; | |||
import org.apache.poi.util.Dimension2DDouble; | |||
import org.apache.poi.xdgf.usermodel.XDGFPage; | |||
import org.apache.poi.xdgf.usermodel.XmlVisioDocument; | |||
import org.apache.poi.xdgf.usermodel.shape.ShapeDebuggerRenderer; | |||
@@ -58,7 +58,7 @@ public class VsdxToPng { | |||
public static void renderToPng(XDGFPage page, File outFile, double scale, | |||
ShapeRenderer renderer) throws IOException { | |||
Dimension2dDouble sz = page.getPageSize(); | |||
Dimension2DDouble sz = page.getPageSize(); | |||
int width = (int) (scale * sz.getWidth()); | |||
int height = (int) (scale * sz.getHeight()); |
@@ -15,17 +15,16 @@ | |||
limitations under the License. | |||
==================================================================== */ | |||
package org.apache.poi.hemf.record; | |||
package org.apache.poi.hemf.draw; | |||
import org.apache.poi.util.Internal; | |||
import org.apache.poi.hwmf.draw.HwmfDrawProperties; | |||
/** | |||
* Not yet implemented | |||
*/ | |||
@Internal | |||
public class HemfCommentEMFSpool extends AbstractHemfComment { | |||
public class HemfDrawProperties extends HwmfDrawProperties { | |||
public HemfCommentEMFSpool(byte[] rawBytes) { | |||
super(rawBytes); | |||
public HemfDrawProperties() { | |||
} | |||
public HemfDrawProperties(HemfDrawProperties other) { | |||
super(other); | |||
} | |||
} |
@@ -15,17 +15,15 @@ | |||
limitations under the License. | |||
==================================================================== */ | |||
package org.apache.poi.hemf.record; | |||
package org.apache.poi.hemf.draw; | |||
import org.apache.poi.util.Internal; | |||
import java.awt.Graphics2D; | |||
import java.awt.geom.Rectangle2D; | |||
/** | |||
* Contains arbitrary data | |||
*/ | |||
@Internal | |||
public class HemfComment extends AbstractHemfComment { | |||
import org.apache.poi.hwmf.draw.HwmfGraphics; | |||
public HemfComment(byte[] rawBytes) { | |||
super(rawBytes); | |||
public class HemfGraphics extends HwmfGraphics { | |||
public HemfGraphics(Graphics2D graphicsCtx, Rectangle2D bbox) { | |||
super(graphicsCtx,bbox); | |||
} | |||
} |
@@ -1,115 +0,0 @@ | |||
/* ==================================================================== | |||
Licensed to the Apache Software Foundation (ASF) under one or more | |||
contributor license agreements. See the NOTICE file distributed with | |||
this work for additional information regarding copyright ownership. | |||
The ASF licenses this file to You under the Apache License, Version 2.0 | |||
(the "License"); you may not use this file except in compliance with | |||
the License. You may obtain a copy of the License at | |||
http://www.apache.org/licenses/LICENSE-2.0 | |||
Unless required by applicable law or agreed to in writing, software | |||
distributed under the License is distributed on an "AS IS" BASIS, | |||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
See the License for the specific language governing permissions and | |||
limitations under the License. | |||
==================================================================== */ | |||
package org.apache.poi.hemf.extractor; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.util.Iterator; | |||
import org.apache.poi.hemf.record.HemfHeader; | |||
import org.apache.poi.hemf.record.HemfRecord; | |||
import org.apache.poi.hemf.record.HemfRecordType; | |||
import org.apache.poi.util.Internal; | |||
import org.apache.poi.util.LittleEndianInputStream; | |||
import org.apache.poi.util.RecordFormatException; | |||
/** | |||
* Read-only EMF extractor. Lots remain | |||
*/ | |||
@Internal | |||
public class HemfExtractor implements Iterable<HemfRecord> { | |||
private HemfHeader header; | |||
private final LittleEndianInputStream stream; | |||
public HemfExtractor(InputStream is) throws IOException { | |||
stream = new LittleEndianInputStream(is); | |||
header = new HemfHeader(); | |||
long recordId = stream.readUInt(); | |||
long recordSize = stream.readUInt(); | |||
header = new HemfHeader(); | |||
header.init(stream, recordId, recordSize-8); | |||
} | |||
@Override | |||
public Iterator<HemfRecord> iterator() { | |||
return new HemfRecordIterator(); | |||
} | |||
public HemfHeader getHeader() { | |||
return header; | |||
} | |||
private class HemfRecordIterator implements Iterator<HemfRecord> { | |||
private HemfRecord currentRecord; | |||
HemfRecordIterator() { | |||
//queue the first non-header record | |||
currentRecord = _next(); | |||
} | |||
@Override | |||
public boolean hasNext() { | |||
return currentRecord != null; | |||
} | |||
@Override | |||
public HemfRecord next() { | |||
HemfRecord toReturn = currentRecord; | |||
currentRecord = _next(); | |||
return toReturn; | |||
} | |||
private HemfRecord _next() { | |||
if (currentRecord != null && currentRecord.getRecordType().equals(HemfRecordType.eof)) { | |||
return null; | |||
} | |||
long recordId = stream.readUInt(); | |||
long recordSize = stream.readUInt(); | |||
HemfRecord record = null; | |||
HemfRecordType type = HemfRecordType.getById(recordId); | |||
if (type == null) { | |||
throw new RuntimeException("Undefined record of type:"+recordId); | |||
} | |||
try { | |||
record = type.clazz.newInstance(); | |||
} catch (InstantiationException e) { | |||
throw new RuntimeException(e); | |||
} catch (IllegalAccessException e) { | |||
throw new RuntimeException(e); | |||
} | |||
try { | |||
record.init(stream, recordId, recordSize-8); | |||
} catch (IOException e) { | |||
throw new RecordFormatException(e); | |||
} | |||
return record; | |||
} | |||
@Override | |||
public void remove() { | |||
throw new UnsupportedOperationException("Remove not supported"); | |||
} | |||
} | |||
} |
@@ -1,97 +0,0 @@ | |||
/* ==================================================================== | |||
Licensed to the Apache Software Foundation (ASF) under one or more | |||
contributor license agreements. See the NOTICE file distributed with | |||
this work for additional information regarding copyright ownership. | |||
The ASF licenses this file to You under the Apache License, Version 2.0 | |||
(the "License"); you may not use this file except in compliance with | |||
the License. You may obtain a copy of the License at | |||
http://www.apache.org/licenses/LICENSE-2.0 | |||
Unless required by applicable law or agreed to in writing, software | |||
distributed under the License is distributed on an "AS IS" BASIS, | |||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
See the License for the specific language governing permissions and | |||
limitations under the License. | |||
==================================================================== */ | |||
package org.apache.poi.hemf.hemfplus.record; | |||
import org.apache.poi.util.Internal; | |||
@Internal | |||
public enum HemfPlusRecordType { | |||
header(0x4001, HemfPlusHeader.class), | |||
endOfFile(0x4002, UnimplementedHemfPlusRecord.class), | |||
comment(0x4003, UnimplementedHemfPlusRecord.class), | |||
getDC(0x4004, UnimplementedHemfPlusRecord.class), | |||
multiFormatStart(0x4005, UnimplementedHemfPlusRecord.class), | |||
multiFormatSection(0x4006, UnimplementedHemfPlusRecord.class), | |||
multiFormatEnd(0x4007, UnimplementedHemfPlusRecord.class), | |||
object(0x4008, UnimplementedHemfPlusRecord.class), | |||
clear(0x4009, UnimplementedHemfPlusRecord.class), | |||
fillRects(0x400A, UnimplementedHemfPlusRecord.class), | |||
drawRects(0x400B, UnimplementedHemfPlusRecord.class), | |||
fillPolygon(0x400C, UnimplementedHemfPlusRecord.class), | |||
drawLines(0x400D, UnimplementedHemfPlusRecord.class), | |||
fillEllipse(0x400E, UnimplementedHemfPlusRecord.class), | |||
drawEllipse(0x400F, UnimplementedHemfPlusRecord.class), | |||
fillPie(0x4010, UnimplementedHemfPlusRecord.class), | |||
drawPie(0x4011, UnimplementedHemfPlusRecord.class), | |||
drawArc(0x4012, UnimplementedHemfPlusRecord.class), | |||
fillRegion(0x4013, UnimplementedHemfPlusRecord.class), | |||
fillPath(0x4014, UnimplementedHemfPlusRecord.class), | |||
drawPath(0x4015, UnimplementedHemfPlusRecord.class), | |||
fillClosedCurve(0x4016, UnimplementedHemfPlusRecord.class), | |||
drawClosedCurve(0x4017, UnimplementedHemfPlusRecord.class), | |||
drawCurve(0x4018, UnimplementedHemfPlusRecord.class), | |||
drawBeziers(0x4019, UnimplementedHemfPlusRecord.class), | |||
drawImage(0x401A, UnimplementedHemfPlusRecord.class), | |||
drawImagePoints(0x401B, UnimplementedHemfPlusRecord.class), | |||
drawString(0x401C, UnimplementedHemfPlusRecord.class), | |||
setRenderingOrigin(0x401D, UnimplementedHemfPlusRecord.class), | |||
setAntiAliasMode(0x401E, UnimplementedHemfPlusRecord.class), | |||
setTextRenderingHint(0x401F, UnimplementedHemfPlusRecord.class), | |||
setTextContrast(0x4020, UnimplementedHemfPlusRecord.class), | |||
setInterpolationMode(0x4021, UnimplementedHemfPlusRecord.class), | |||
setPixelOffsetMode(0x4022, UnimplementedHemfPlusRecord.class), | |||
setComositingMode(0x4023, UnimplementedHemfPlusRecord.class), | |||
setCompositingQuality(0x4024, UnimplementedHemfPlusRecord.class), | |||
save(0x4025, UnimplementedHemfPlusRecord.class), | |||
restore(0x4026, UnimplementedHemfPlusRecord.class), | |||
beginContainer(0x4027, UnimplementedHemfPlusRecord.class), | |||
beginContainerNoParams(0x428, UnimplementedHemfPlusRecord.class), | |||
endContainer(0x4029, UnimplementedHemfPlusRecord.class), | |||
setWorldTransform(0x402A, UnimplementedHemfPlusRecord.class), | |||
resetWorldTransform(0x402B, UnimplementedHemfPlusRecord.class), | |||
multiplyWorldTransform(0x402C, UnimplementedHemfPlusRecord.class), | |||
translateWorldTransform(0x402D, UnimplementedHemfPlusRecord.class), | |||
scaleWorldTransform(0x402E, UnimplementedHemfPlusRecord.class), | |||
rotateWorldTransform(0x402F, UnimplementedHemfPlusRecord.class), | |||
setPageTransform(0x4030, UnimplementedHemfPlusRecord.class), | |||
resetClip(0x4031, UnimplementedHemfPlusRecord.class), | |||
setClipRect(0x4032, UnimplementedHemfPlusRecord.class), | |||
setClipRegion(0x4033, UnimplementedHemfPlusRecord.class), | |||
setClipPath(0x4034, UnimplementedHemfPlusRecord.class), | |||
offsetClip(0x4035, UnimplementedHemfPlusRecord.class), | |||
drawDriverstring(0x4036, UnimplementedHemfPlusRecord.class), | |||
strokeFillPath(0x4037, UnimplementedHemfPlusRecord.class), | |||
serializableObject(0x4038, UnimplementedHemfPlusRecord.class), | |||
setTSGraphics(0x4039, UnimplementedHemfPlusRecord.class), | |||
setTSClip(0x403A, UnimplementedHemfPlusRecord.class); | |||
public final long id; | |||
public final Class<? extends HemfPlusRecord> clazz; | |||
HemfPlusRecordType(long id, Class<? extends HemfPlusRecord> clazz) { | |||
this.id = id; | |||
this.clazz = clazz; | |||
} | |||
public static HemfPlusRecordType getById(long id) { | |||
for (HemfPlusRecordType wrt : values()) { | |||
if (wrt.id == id) return wrt; | |||
} | |||
return null; | |||
} | |||
} |
@@ -1,39 +0,0 @@ | |||
/* ==================================================================== | |||
Licensed to the Apache Software Foundation (ASF) under one or more | |||
contributor license agreements. See the NOTICE file distributed with | |||
this work for additional information regarding copyright ownership. | |||
The ASF licenses this file to You under the Apache License, Version 2.0 | |||
(the "License"); you may not use this file except in compliance with | |||
the License. You may obtain a copy of the License at | |||
http://www.apache.org/licenses/LICENSE-2.0 | |||
Unless required by applicable law or agreed to in writing, software | |||
distributed under the License is distributed on an "AS IS" BASIS, | |||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
See the License for the specific language governing permissions and | |||
limitations under the License. | |||
==================================================================== */ | |||
package org.apache.poi.hemf.record; | |||
import org.apache.poi.util.Internal; | |||
/** | |||
* Syntactic utility to allow for four different | |||
* comment classes | |||
*/ | |||
@Internal | |||
public abstract class AbstractHemfComment { | |||
private final byte[] rawBytes; | |||
public AbstractHemfComment(byte[] rawBytes) { | |||
this.rawBytes = rawBytes; | |||
} | |||
public byte[] getRawBytes() { | |||
return rawBytes; | |||
} | |||
} |
@@ -1,111 +0,0 @@ | |||
/* ==================================================================== | |||
Licensed to the Apache Software Foundation (ASF) under one or more | |||
contributor license agreements. See the NOTICE file distributed with | |||
this work for additional information regarding copyright ownership. | |||
The ASF licenses this file to You under the Apache License, Version 2.0 | |||
(the "License"); you may not use this file except in compliance with | |||
the License. You may obtain a copy of the License at | |||
http://www.apache.org/licenses/LICENSE-2.0 | |||
Unless required by applicable law or agreed to in writing, software | |||
distributed under the License is distributed on an "AS IS" BASIS, | |||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
See the License for the specific language governing permissions and | |||
limitations under the License. | |||
==================================================================== */ | |||
package org.apache.poi.hemf.record; | |||
import java.io.IOException; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import org.apache.poi.hemf.hemfplus.record.HemfPlusRecord; | |||
import org.apache.poi.hemf.hemfplus.record.HemfPlusRecordType; | |||
import org.apache.poi.util.IOUtils; | |||
import org.apache.poi.util.Internal; | |||
import org.apache.poi.util.LittleEndian; | |||
import org.apache.poi.util.RecordFormatException; | |||
/** | |||
* An HemfCommentEMFPlus may contain one or more EMFPlus records | |||
*/ | |||
@Internal | |||
public class HemfCommentEMFPlus extends AbstractHemfComment { | |||
private static final int MAX_RECORD_LENGTH = 1_000_000; | |||
long dataSize; | |||
public HemfCommentEMFPlus(byte[] rawBytes) { | |||
//these rawBytes contain only the EMFPlusRecord(s?) | |||
//the EmfComment type, size, datasize and comment identifier have all been stripped. | |||
//The EmfPlus type, flags, size, data size should start at rawBytes[0] | |||
super(rawBytes); | |||
} | |||
public List<HemfPlusRecord> getRecords() { | |||
return HemfPlusParser.parse(getRawBytes()); | |||
} | |||
private static class HemfPlusParser { | |||
public static List<HemfPlusRecord> parse(byte[] bytes) { | |||
List<HemfPlusRecord> records = new ArrayList<>(); | |||
int offset = 0; | |||
while (offset < bytes.length) { | |||
if (offset + 12 > bytes.length) { | |||
//if header will go beyond bytes, stop now | |||
//TODO: log or throw | |||
break; | |||
} | |||
int type = LittleEndian.getUShort(bytes, offset); offset += LittleEndian.SHORT_SIZE; | |||
int flags = LittleEndian.getUShort(bytes, offset); offset += LittleEndian.SHORT_SIZE; | |||
long sizeLong = LittleEndian.getUInt(bytes, offset); offset += LittleEndian.INT_SIZE; | |||
if (sizeLong >= Integer.MAX_VALUE) { | |||
throw new RecordFormatException("size of emf record >= Integer.MAX_VALUE"); | |||
} | |||
int size = (int)sizeLong; | |||
long dataSizeLong = LittleEndian.getUInt(bytes, offset); offset += LittleEndian.INT_SIZE; | |||
if (dataSizeLong >= Integer.MAX_VALUE) { | |||
throw new RuntimeException("data size of emfplus record cannot be >= Integer.MAX_VALUE"); | |||
} | |||
int dataSize = (int)dataSizeLong; | |||
if (dataSize + offset > bytes.length) { | |||
//TODO: log or throw? | |||
break; | |||
} | |||
HemfPlusRecord record = buildRecord(type, flags, dataSize, offset, bytes); | |||
records.add(record); | |||
offset += dataSize; | |||
} | |||
return records; | |||
} | |||
private static HemfPlusRecord buildRecord(int recordId, int flags, int size, int offset, byte[] bytes) { | |||
HemfPlusRecord record = null; | |||
HemfPlusRecordType type = HemfPlusRecordType.getById(recordId); | |||
if (type == null) { | |||
throw new RuntimeException("Undefined record of type:"+recordId); | |||
} | |||
try { | |||
record = type.clazz.newInstance(); | |||
} catch (InstantiationException e) { | |||
throw new RuntimeException(e); | |||
} catch (IllegalAccessException e) { | |||
throw new RuntimeException(e); | |||
} | |||
byte[] dataBytes = IOUtils.safelyAllocate(size, MAX_RECORD_LENGTH); | |||
System.arraycopy(bytes, offset, dataBytes, 0, size); | |||
try { | |||
record.init(dataBytes, recordId, flags); | |||
} catch (IOException e) { | |||
throw new RuntimeException(e); | |||
} | |||
return record; | |||
} | |||
} | |||
} |
@@ -1,177 +0,0 @@ | |||
/* ==================================================================== | |||
Licensed to the Apache Software Foundation (ASF) under one or more | |||
contributor license agreements. See the NOTICE file distributed with | |||
this work for additional information regarding copyright ownership. | |||
The ASF licenses this file to You under the Apache License, Version 2.0 | |||
(the "License"); you may not use this file except in compliance with | |||
the License. You may obtain a copy of the License at | |||
http://www.apache.org/licenses/LICENSE-2.0 | |||
Unless required by applicable law or agreed to in writing, software | |||
distributed under the License is distributed on an "AS IS" BASIS, | |||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
See the License for the specific language governing permissions and | |||
limitations under the License. | |||
==================================================================== */ | |||
package org.apache.poi.hemf.record; | |||
import java.io.ByteArrayInputStream; | |||
import java.io.InputStream; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import org.apache.poi.util.IOUtils; | |||
import org.apache.poi.util.Internal; | |||
import org.apache.poi.util.LittleEndian; | |||
import org.apache.poi.util.LittleEndianConsts; | |||
import org.apache.poi.util.RecordFormatException; | |||
/** | |||
* Container class for four subtypes of HemfCommentPublic: BeginGroup, EndGroup, MultiFormats | |||
* and Windows Metafile. | |||
*/ | |||
@Internal | |||
public class HemfCommentPublic { | |||
private static final int MAX_RECORD_LENGTH = 1_000_000; | |||
/** | |||
* Stub, to be implemented | |||
*/ | |||
public static class BeginGroup extends AbstractHemfComment { | |||
public BeginGroup(byte[] rawBytes) { | |||
super(rawBytes); | |||
} | |||
} | |||
/** | |||
* Stub, to be implemented | |||
*/ | |||
public static class EndGroup extends AbstractHemfComment { | |||
public EndGroup(byte[] rawBytes) { | |||
super(rawBytes); | |||
} | |||
} | |||
public static class MultiFormats extends AbstractHemfComment { | |||
public MultiFormats(byte[] rawBytes) { | |||
super(rawBytes); | |||
} | |||
/** | |||
* | |||
* @return a list of HemfMultFormatsData | |||
*/ | |||
public List<HemfMultiFormatsData> getData() { | |||
byte[] rawBytes = getRawBytes(); | |||
//note that raw bytes includes the public comment identifier | |||
int currentOffset = 4 + 16;//4 public comment identifier, 16 for outputrect | |||
long countFormats = LittleEndian.getUInt(rawBytes, currentOffset); | |||
currentOffset += LittleEndianConsts.INT_SIZE; | |||
List<EmrFormat> emrFormatList = new ArrayList<>(); | |||
for (long i = 0; i < countFormats; i++) { | |||
emrFormatList.add(new EmrFormat(rawBytes, currentOffset)); | |||
currentOffset += 4 * LittleEndianConsts.INT_SIZE; | |||
} | |||
List<HemfMultiFormatsData> list = new ArrayList<>(); | |||
for (EmrFormat emrFormat : emrFormatList) { | |||
byte[] data = IOUtils.safelyAllocate(emrFormat.size, MAX_RECORD_LENGTH); | |||
System.arraycopy(rawBytes, emrFormat.offset-4, data, 0, emrFormat.size); | |||
list.add(new HemfMultiFormatsData(emrFormat.signature, emrFormat.version, data)); | |||
} | |||
return list; | |||
} | |||
private static class EmrFormat { | |||
long signature; | |||
long version; | |||
int size; | |||
int offset; | |||
public EmrFormat(byte[] rawBytes, int currentOffset) { | |||
signature = LittleEndian.getUInt(rawBytes, currentOffset); currentOffset += LittleEndianConsts.INT_SIZE; | |||
version = LittleEndian.getUInt(rawBytes, currentOffset); currentOffset += LittleEndianConsts.INT_SIZE; | |||
//spec says this must be a 32bit "aligned" typo for "signed"? | |||
//realistically, this has to be an int... | |||
size = LittleEndian.getInt(rawBytes, currentOffset); currentOffset += LittleEndianConsts.INT_SIZE; | |||
//y, can be long, but realistically? | |||
offset = LittleEndian.getInt(rawBytes, currentOffset); currentOffset += LittleEndianConsts.INT_SIZE; | |||
if (size < 0) { | |||
throw new RecordFormatException("size for emrformat must be > 0"); | |||
} | |||
if (offset < 0) { | |||
throw new RecordFormatException("offset for emrformat must be > 0"); | |||
} | |||
} | |||
} | |||
} | |||
/** | |||
* Stub, to be implemented | |||
*/ | |||
public static class WindowsMetafile extends AbstractHemfComment { | |||
private final byte[] wmfBytes; | |||
public WindowsMetafile(byte[] rawBytes) { | |||
super(rawBytes); | |||
int offset = LittleEndianConsts.INT_SIZE;//public comment identifier | |||
int version = LittleEndian.getUShort(rawBytes, offset); offset += LittleEndianConsts.SHORT_SIZE; | |||
int reserved = LittleEndian.getUShort(rawBytes, offset); offset += LittleEndianConsts.SHORT_SIZE; | |||
offset += LittleEndianConsts.INT_SIZE; //checksum | |||
offset += LittleEndianConsts.INT_SIZE; //flags | |||
long winMetafileSizeLong = LittleEndian.getUInt(rawBytes, offset); offset += LittleEndianConsts.INT_SIZE; | |||
if (winMetafileSizeLong == 0L) { | |||
wmfBytes = new byte[0]; | |||
return; | |||
} | |||
wmfBytes = IOUtils.safelyAllocate(winMetafileSizeLong, MAX_RECORD_LENGTH); | |||
System.arraycopy(rawBytes, offset, wmfBytes, 0, wmfBytes.length); | |||
} | |||
/** | |||
* | |||
* @return an InputStream for the embedded WMF file | |||
*/ | |||
public InputStream getWmfInputStream() { | |||
return new ByteArrayInputStream(wmfBytes); | |||
} | |||
} | |||
/** | |||
* This encapulates a single record stored within | |||
* a HemfCommentPublic.MultiFormats record. | |||
*/ | |||
public static class HemfMultiFormatsData { | |||
long signature; | |||
long version; | |||
byte[] data; | |||
public HemfMultiFormatsData(long signature, long version, byte[] data) { | |||
this.signature = signature; | |||
this.version = version; | |||
this.data = data; | |||
} | |||
public long getSignature() { | |||
return signature; | |||
} | |||
public long getVersion() { | |||
return version; | |||
} | |||
public byte[] getData() { | |||
return data; | |||
} | |||
} | |||
} |
@@ -1,154 +0,0 @@ | |||
/* ==================================================================== | |||
Licensed to the Apache Software Foundation (ASF) under one or more | |||
contributor license agreements. See the NOTICE file distributed with | |||
this work for additional information regarding copyright ownership. | |||
The ASF licenses this file to You under the Apache License, Version 2.0 | |||
(the "License"); you may not use this file except in compliance with | |||
the License. You may obtain a copy of the License at | |||
http://www.apache.org/licenses/LICENSE-2.0 | |||
Unless required by applicable law or agreed to in writing, software | |||
distributed under the License is distributed on an "AS IS" BASIS, | |||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
See the License for the specific language governing permissions and | |||
limitations under the License. | |||
==================================================================== */ | |||
package org.apache.poi.hemf.record; | |||
import java.io.IOException; | |||
import org.apache.poi.util.IOUtils; | |||
import org.apache.poi.util.Internal; | |||
import org.apache.poi.util.LittleEndian; | |||
import org.apache.poi.util.LittleEndianInputStream; | |||
import org.apache.poi.util.RecordFormatException; | |||
/** | |||
* This is the outer comment record that is recognized | |||
* by the initial parse by {@link HemfRecordType#comment}. | |||
* However, there are four types of comment: EMR_COMMENT, | |||
* EMR_COMMENT_EMFPLUS, EMR_COMMENT_EMFSPOOL, and EMF_COMMENT_PUBLIC. | |||
* To get the underlying comment, call {@link #getComment()}. | |||
* | |||
*/ | |||
@Internal | |||
public class HemfCommentRecord implements HemfRecord { | |||
private static final int MAX_RECORD_LENGTH = 1_000_000; | |||
public final static long COMMENT_EMFSPOOL = 0x00000000; | |||
public final static long COMMENT_EMFPLUS = 0x2B464D45; | |||
public final static long COMMENT_PUBLIC = 0x43494447; | |||
private AbstractHemfComment comment; | |||
@Override | |||
public HemfRecordType getRecordType() { | |||
return HemfRecordType.comment; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordId, long recordSize) throws IOException { | |||
long dataSize = leis.readUInt(); recordSize -= LittleEndian.INT_SIZE; | |||
byte[] optionalCommentIndentifierBuffer = new byte[4]; | |||
leis.readFully(optionalCommentIndentifierBuffer); | |||
dataSize = dataSize-LittleEndian.INT_SIZE; //size minus the first int which could be a comment identifier | |||
recordSize -= LittleEndian.INT_SIZE; | |||
long optionalCommentIdentifier = LittleEndian.getInt(optionalCommentIndentifierBuffer) & 0x00FFFFFFFFL; | |||
if (optionalCommentIdentifier == COMMENT_EMFSPOOL) { | |||
comment = new HemfCommentEMFSpool(readToByteArray(leis, dataSize, recordSize)); | |||
} else if (optionalCommentIdentifier == COMMENT_EMFPLUS) { | |||
comment = new HemfCommentEMFPlus(readToByteArray(leis, dataSize, recordSize)); | |||
} else if (optionalCommentIdentifier == COMMENT_PUBLIC) { | |||
comment = CommentPublicParser.parse(readToByteArray(leis, dataSize, recordSize)); | |||
} else { | |||
comment = new HemfComment(readToByteArray(optionalCommentIndentifierBuffer, leis, dataSize, recordSize)); | |||
} | |||
return recordSize; | |||
} | |||
//this prepends the initial "int" which turned out NOT to be | |||
//a signifier of emfplus, spool, public. | |||
private byte[] readToByteArray(byte[] initialBytes, LittleEndianInputStream leis, | |||
long remainingDataSize, long remainingRecordSize) throws IOException { | |||
if (remainingDataSize > Integer.MAX_VALUE) { | |||
throw new RecordFormatException("Data size can't be > Integer.MAX_VALUE"); | |||
} | |||
if (remainingRecordSize > Integer.MAX_VALUE) { | |||
throw new RecordFormatException("Record size can't be > Integer.MAX_VALUE"); | |||
} | |||
if (remainingRecordSize == 0) { | |||
return new byte[0]; | |||
} | |||
int dataSize = (int)remainingDataSize; | |||
int recordSize = (int)remainingRecordSize; | |||
byte[] arr = IOUtils.safelyAllocate(dataSize+initialBytes.length, MAX_RECORD_LENGTH); | |||
System.arraycopy(initialBytes,0,arr, 0, initialBytes.length); | |||
long read = IOUtils.readFully(leis, arr, initialBytes.length, dataSize); | |||
if (read != dataSize) { | |||
throw new RecordFormatException("InputStream ended before full record could be read"); | |||
} | |||
long toSkip = recordSize-dataSize; | |||
long skipped = IOUtils.skipFully(leis, toSkip); | |||
if (toSkip != skipped) { | |||
throw new RecordFormatException("InputStream ended before full record could be read"); | |||
} | |||
return arr; | |||
} | |||
private byte[] readToByteArray(LittleEndianInputStream leis, long dataSize, long recordSize) throws IOException { | |||
if (recordSize == 0) { | |||
return new byte[0]; | |||
} | |||
byte[] arr = IOUtils.safelyAllocate(dataSize, MAX_RECORD_LENGTH); | |||
long read = IOUtils.readFully(leis, arr); | |||
if (read != dataSize) { | |||
throw new RecordFormatException("InputStream ended before full record could be read"); | |||
} | |||
long toSkip = recordSize-dataSize; | |||
long skipped = IOUtils.skipFully(leis, recordSize-dataSize); | |||
if (toSkip != skipped) { | |||
throw new RecordFormatException("InputStream ended before full record could be read"); | |||
} | |||
return arr; | |||
} | |||
public AbstractHemfComment getComment() { | |||
return comment; | |||
} | |||
private static class CommentPublicParser { | |||
private static final long WINDOWS_METAFILE = 0x80000001L; //wmf | |||
private static final long BEGINGROUP = 0x00000002; //beginning of a group of drawing records | |||
private static final long ENDGROUP = 0x00000003; //end of a group of drawing records | |||
private static final long MULTIFORMATS = 0x40000004; //allows multiple definitions of an image, including encapsulated postscript | |||
private static final long UNICODE_STRING = 0x00000040; //reserved. must not be used | |||
private static final long UNICODE_END = 0x00000080; //reserved, must not be used | |||
private static AbstractHemfComment parse(byte[] bytes) { | |||
long publicCommentIdentifier = LittleEndian.getUInt(bytes, 0); | |||
if (publicCommentIdentifier == WINDOWS_METAFILE) { | |||
return new HemfCommentPublic.WindowsMetafile(bytes); | |||
} else if (publicCommentIdentifier == BEGINGROUP) { | |||
return new HemfCommentPublic.BeginGroup(bytes); | |||
} else if (publicCommentIdentifier == ENDGROUP) { | |||
return new HemfCommentPublic.EndGroup(bytes); | |||
} else if (publicCommentIdentifier == MULTIFORMATS) { | |||
return new HemfCommentPublic.MultiFormats(bytes); | |||
} else if (publicCommentIdentifier == UNICODE_STRING || publicCommentIdentifier == UNICODE_END) { | |||
throw new RuntimeException("UNICODE_STRING/UNICODE_END values are reserved in CommentPublic records"); | |||
} | |||
throw new RuntimeException("Unrecognized public comment type:" +publicCommentIdentifier + " ; " + WINDOWS_METAFILE); | |||
} | |||
} | |||
} |
@@ -1,159 +0,0 @@ | |||
/* ==================================================================== | |||
Licensed to the Apache Software Foundation (ASF) under one or more | |||
contributor license agreements. See the NOTICE file distributed with | |||
this work for additional information regarding copyright ownership. | |||
The ASF licenses this file to You under the Apache License, Version 2.0 | |||
(the "License"); you may not use this file except in compliance with | |||
the License. You may obtain a copy of the License at | |||
http://www.apache.org/licenses/LICENSE-2.0 | |||
Unless required by applicable law or agreed to in writing, software | |||
distributed under the License is distributed on an "AS IS" BASIS, | |||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
See the License for the specific language governing permissions and | |||
limitations under the License. | |||
==================================================================== */ | |||
package org.apache.poi.hemf.record; | |||
import org.apache.poi.util.Internal; | |||
@Internal | |||
public enum HemfRecordType { | |||
header(0x00000001, UnimplementedHemfRecord.class), | |||
polybeizer(0x00000002, UnimplementedHemfRecord.class), | |||
polygon(0x00000003, UnimplementedHemfRecord.class), | |||
polyline(0x00000004, UnimplementedHemfRecord.class), | |||
polybezierto(0x00000005, UnimplementedHemfRecord.class), | |||
polylineto(0x00000006, UnimplementedHemfRecord.class), | |||
polypolyline(0x00000007, UnimplementedHemfRecord.class), | |||
polypolygon(0x00000008, UnimplementedHemfRecord.class), | |||
setwindowextex(0x00000009, UnimplementedHemfRecord.class), | |||
setwindoworgex(0x0000000A, UnimplementedHemfRecord.class), | |||
setviewportextex(0x0000000B, UnimplementedHemfRecord.class), | |||
setviewportorgex(0x0000000C, UnimplementedHemfRecord.class), | |||
setbrushorgex(0x0000000D, UnimplementedHemfRecord.class), | |||
eof(0x0000000E, UnimplementedHemfRecord.class), | |||
setpixelv(0x0000000F, UnimplementedHemfRecord.class), | |||
setmapperflags(0x00000010, UnimplementedHemfRecord.class), | |||
setmapmode(0x00000011, UnimplementedHemfRecord.class), | |||
setbkmode(0x00000012, UnimplementedHemfRecord.class), | |||
setpolyfillmode(0x00000013, UnimplementedHemfRecord.class), | |||
setrop2(0x00000014, UnimplementedHemfRecord.class), | |||
setstretchbltmode(0x00000015, UnimplementedHemfRecord.class), | |||
settextalign(0x00000016, HemfText.SetTextAlign.class), | |||
setcoloradjustment(0x00000017, UnimplementedHemfRecord.class), | |||
settextcolor(0x00000018, HemfText.SetTextColor.class), | |||
setbkcolor(0x00000019, UnimplementedHemfRecord.class), | |||
setoffsetcliprgn(0x0000001A, UnimplementedHemfRecord.class), | |||
setmovetoex(0x0000001B, UnimplementedHemfRecord.class), | |||
setmetargn(0x0000001C, UnimplementedHemfRecord.class), | |||
setexcludecliprect(0x0000001D, UnimplementedHemfRecord.class), | |||
setintersectcliprect(0x0000001E, UnimplementedHemfRecord.class), | |||
scaleviewportextex(0x0000001F, UnimplementedHemfRecord.class), | |||
scalewindowextex(0x00000020, UnimplementedHemfRecord.class), | |||
savedc(0x00000021, UnimplementedHemfRecord.class), | |||
restoredc(0x00000022, UnimplementedHemfRecord.class), | |||
setworldtransform(0x00000023, UnimplementedHemfRecord.class), | |||
modifyworldtransform(0x00000024, UnimplementedHemfRecord.class), | |||
selectobject(0x00000025, UnimplementedHemfRecord.class), | |||
createpen(0x00000026, UnimplementedHemfRecord.class), | |||
createbrushindirect(0x00000027, UnimplementedHemfRecord.class), | |||
deleteobject(0x00000028, UnimplementedHemfRecord.class), | |||
anglearc(0x00000029, UnimplementedHemfRecord.class), | |||
ellipse(0x0000002A, UnimplementedHemfRecord.class), | |||
rectangle(0x0000002B, UnimplementedHemfRecord.class), | |||
roundirect(0x0000002C, UnimplementedHemfRecord.class), | |||
arc(0x0000002D, UnimplementedHemfRecord.class), | |||
chord(0x0000002E, UnimplementedHemfRecord.class), | |||
pie(0x0000002F, UnimplementedHemfRecord.class), | |||
selectpalette(0x00000030, UnimplementedHemfRecord.class), | |||
createpalette(0x00000031, UnimplementedHemfRecord.class), | |||
setpaletteentries(0x00000032, UnimplementedHemfRecord.class), | |||
resizepalette(0x00000033, UnimplementedHemfRecord.class), | |||
realizepalette(0x0000034, UnimplementedHemfRecord.class), | |||
extfloodfill(0x00000035, UnimplementedHemfRecord.class), | |||
lineto(0x00000036, UnimplementedHemfRecord.class), | |||
arcto(0x00000037, UnimplementedHemfRecord.class), | |||
polydraw(0x00000038, UnimplementedHemfRecord.class), | |||
setarcdirection(0x00000039, UnimplementedHemfRecord.class), | |||
setmiterlimit(0x0000003A, UnimplementedHemfRecord.class), | |||
beginpath(0x0000003B, UnimplementedHemfRecord.class), | |||
endpath(0x0000003C, UnimplementedHemfRecord.class), | |||
closefigure(0x0000003D, UnimplementedHemfRecord.class), | |||
fillpath(0x0000003E, UnimplementedHemfRecord.class), | |||
strokeandfillpath(0x0000003F, UnimplementedHemfRecord.class), | |||
strokepath(0x00000040, UnimplementedHemfRecord.class), | |||
flattenpath(0x00000041, UnimplementedHemfRecord.class), | |||
widenpath(0x00000042, UnimplementedHemfRecord.class), | |||
selectclippath(0x00000043, UnimplementedHemfRecord.class), | |||
abortpath(0x00000044, UnimplementedHemfRecord.class), //no 45?! | |||
comment(0x00000046, HemfCommentRecord.class), | |||
fillrgn(0x00000047, UnimplementedHemfRecord.class), | |||
framergn(0x00000048, UnimplementedHemfRecord.class), | |||
invertrgn(0x00000049, UnimplementedHemfRecord.class), | |||
paintrgn(0x0000004A, UnimplementedHemfRecord.class), | |||
extselectciprrgn(0x0000004B, UnimplementedHemfRecord.class), | |||
bitblt(0x0000004C, UnimplementedHemfRecord.class), | |||
stretchblt(0x0000004D, UnimplementedHemfRecord.class), | |||
maskblt(0x0000004E, UnimplementedHemfRecord.class), | |||
plgblt(0x0000004F, UnimplementedHemfRecord.class), | |||
setbitstodevice(0x00000050, UnimplementedHemfRecord.class), | |||
stretchdibits(0x00000051, UnimplementedHemfRecord.class), | |||
extcreatefontindirectw(0x00000052, HemfText.ExtCreateFontIndirectW.class), | |||
exttextouta(0x00000053, HemfText.ExtTextOutA.class), | |||
exttextoutw(0x00000054, HemfText.ExtTextOutW.class), | |||
polybezier16(0x00000055, UnimplementedHemfRecord.class), | |||
polygon16(0x00000056, UnimplementedHemfRecord.class), | |||
polyline16(0x00000057, UnimplementedHemfRecord.class), | |||
polybezierto16(0x00000058, UnimplementedHemfRecord.class), | |||
polylineto16(0x00000059, UnimplementedHemfRecord.class), | |||
polypolyline16(0x0000005A, UnimplementedHemfRecord.class), | |||
polypolygon16(0x0000005B, UnimplementedHemfRecord.class), | |||
polydraw16(0x0000005C, UnimplementedHemfRecord.class), | |||
createmonobrush16(0x0000005D, UnimplementedHemfRecord.class), | |||
createdibpatternbrushpt(0x0000005E, UnimplementedHemfRecord.class), | |||
extcreatepen(0x0000005F, UnimplementedHemfRecord.class), | |||
polytextouta(0x00000060, HemfText.PolyTextOutA.class), | |||
polytextoutw(0x00000061, HemfText.PolyTextOutW.class), | |||
seticmmode(0x00000062, UnimplementedHemfRecord.class), | |||
createcolorspace(0x00000063, UnimplementedHemfRecord.class), | |||
setcolorspace(0x00000064, UnimplementedHemfRecord.class), | |||
deletecolorspace(0x00000065, UnimplementedHemfRecord.class), | |||
glsrecord(0x00000066, UnimplementedHemfRecord.class), | |||
glsboundedrecord(0x00000067, UnimplementedHemfRecord.class), | |||
pixelformat(0x00000068, UnimplementedHemfRecord.class), | |||
drawescape(0x00000069, UnimplementedHemfRecord.class), | |||
extescape(0x0000006A, UnimplementedHemfRecord.class),//no 6b?! | |||
smalltextout(0x0000006C, UnimplementedHemfRecord.class), | |||
forceufimapping(0x0000006D, UnimplementedHemfRecord.class), | |||
namedescape(0x0000006E, UnimplementedHemfRecord.class), | |||
colorcorrectpalette(0x0000006F, UnimplementedHemfRecord.class), | |||
seticmprofilea(0x00000070, UnimplementedHemfRecord.class), | |||
seticmprofilew(0x00000071, UnimplementedHemfRecord.class), | |||
alphablend(0x00000072, UnimplementedHemfRecord.class), | |||
setlayout(0x00000073, UnimplementedHemfRecord.class), | |||
transparentblt(0x00000074, UnimplementedHemfRecord.class), | |||
gradientfill(0x00000076, UnimplementedHemfRecord.class), //no 75?! | |||
setlinkdufis(0x00000077, UnimplementedHemfRecord.class), | |||
settextjustification(0x00000078, HemfText.SetTextJustification.class), | |||
colormatchtargetw(0x00000079, UnimplementedHemfRecord.class), | |||
createcolorspacew(0x0000007A, UnimplementedHemfRecord.class); | |||
public final long id; | |||
public final Class<? extends HemfRecord> clazz; | |||
HemfRecordType(long id, Class<? extends HemfRecord> clazz) { | |||
this.id = id; | |||
this.clazz = clazz; | |||
} | |||
public static HemfRecordType getById(long id) { | |||
for (HemfRecordType wrt : values()) { | |||
if (wrt.id == id) return wrt; | |||
} | |||
return null; | |||
} | |||
} |
@@ -1,262 +0,0 @@ | |||
/* ==================================================================== | |||
Licensed to the Apache Software Foundation (ASF) under one or more | |||
contributor license agreements. See the NOTICE file distributed with | |||
this work for additional information regarding copyright ownership. | |||
The ASF licenses this file to You under the Apache License, Version 2.0 | |||
(the "License"); you may not use this file except in compliance with | |||
the License. You may obtain a copy of the License at | |||
http://www.apache.org/licenses/LICENSE-2.0 | |||
Unless required by applicable law or agreed to in writing, software | |||
distributed under the License is distributed on an "AS IS" BASIS, | |||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
See the License for the specific language governing permissions and | |||
limitations under the License. | |||
==================================================================== */ | |||
package org.apache.poi.hemf.record; | |||
import static java.nio.charset.StandardCharsets.UTF_16LE; | |||
import java.io.ByteArrayInputStream; | |||
import java.io.EOFException; | |||
import java.io.IOException; | |||
import java.io.InputStreamReader; | |||
import java.io.Reader; | |||
import java.nio.charset.Charset; | |||
import org.apache.poi.util.IOUtils; | |||
import org.apache.poi.util.Internal; | |||
import org.apache.poi.util.LittleEndian; | |||
import org.apache.poi.util.LittleEndianInputStream; | |||
import org.apache.poi.util.RecordFormatException; | |||
/** | |||
* Container class to gather all text-related commands | |||
* This is starting out as read only, and very little is actually | |||
* implemented at this point! | |||
*/ | |||
@Internal | |||
public class HemfText { | |||
private static final int MAX_RECORD_LENGTH = 1_000_000; | |||
public static class ExtCreateFontIndirectW extends UnimplementedHemfRecord { | |||
} | |||
public static class ExtTextOutA implements HemfRecord { | |||
private long left,top,right,bottom; | |||
//TODO: translate this to a graphicsmode enum | |||
private long graphicsMode; | |||
private long exScale; | |||
private long eyScale; | |||
EmrTextObject textObject; | |||
@Override | |||
public HemfRecordType getRecordType() { | |||
return HemfRecordType.exttextouta; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordId, long recordSize) throws IOException { | |||
//note that the first 2 uInts have been read off and the recordsize has | |||
//been decreased by 8 | |||
left = leis.readInt(); | |||
top = leis.readInt(); | |||
right = leis.readInt(); | |||
bottom = leis.readInt(); | |||
graphicsMode = leis.readUInt(); | |||
exScale = leis.readUInt(); | |||
eyScale = leis.readUInt(); | |||
int recordSizeInt = -1; | |||
if (recordSize < Integer.MAX_VALUE) { | |||
recordSizeInt = (int)recordSize; | |||
} else { | |||
throw new RecordFormatException("can't have text length > Integer.MAX_VALUE"); | |||
} | |||
//guarantee to read the rest of the EMRTextObjectRecord | |||
//emrtextbytes start after 7*4 bytes read above | |||
byte[] emrTextBytes = IOUtils.safelyAllocate(recordSizeInt-(7*LittleEndian.INT_SIZE), MAX_RECORD_LENGTH); | |||
IOUtils.readFully(leis, emrTextBytes); | |||
textObject = new EmrTextObject(emrTextBytes, getEncodingHint(), 20);//should be 28, but recordSizeInt has already subtracted 8 | |||
return recordSize; | |||
} | |||
protected Charset getEncodingHint() { | |||
return null; | |||
} | |||
/** | |||
* | |||
* To be implemented! We need to get the current character set | |||
* from the current font for {@link ExtTextOutA}, | |||
* which has to be tracked in the playback device. | |||
* | |||
* For {@link ExtTextOutW}, the charset is "UTF-16LE" | |||
* | |||
* @param charset the charset to be used to decode the character bytes | |||
* @return text from this text element | |||
* @throws IOException | |||
*/ | |||
public String getText(Charset charset) throws IOException { | |||
return textObject.getText(charset); | |||
} | |||
/** | |||
* | |||
* @return the x offset for the EmrTextObject | |||
*/ | |||
public long getX() { | |||
return textObject.x; | |||
} | |||
/** | |||
* | |||
* @return the y offset for the EmrTextObject | |||
*/ | |||
public long getY() { | |||
return textObject.y; | |||
} | |||
public long getLeft() { | |||
return left; | |||
} | |||
public long getTop() { | |||
return top; | |||
} | |||
public long getRight() { | |||
return right; | |||
} | |||
public long getBottom() { | |||
return bottom; | |||
} | |||
public long getGraphicsMode() { | |||
return graphicsMode; | |||
} | |||
public long getExScale() { | |||
return exScale; | |||
} | |||
public long getEyScale() { | |||
return eyScale; | |||
} | |||
} | |||
public static class ExtTextOutW extends ExtTextOutA { | |||
@Override | |||
public HemfRecordType getRecordType() { | |||
return HemfRecordType.exttextoutw; | |||
} | |||
@Override | |||
protected Charset getEncodingHint() { | |||
return UTF_16LE; | |||
} | |||
public String getText() throws IOException { | |||
return getText(UTF_16LE); | |||
} | |||
} | |||
/** | |||
* Needs to be implemented. Couldn't find example. | |||
*/ | |||
public static class PolyTextOutA extends UnimplementedHemfRecord { | |||
} | |||
/** | |||
* Needs to be implemented. Couldn't find example. | |||
*/ | |||
public static class PolyTextOutW extends UnimplementedHemfRecord { | |||
} | |||
public static class SetTextAlign extends UnimplementedHemfRecord { | |||
} | |||
public static class SetTextColor extends UnimplementedHemfRecord { | |||
} | |||
public static class SetTextJustification extends UnimplementedHemfRecord { | |||
} | |||
private static class EmrTextObject { | |||
long x; | |||
long y; | |||
int numChars; | |||
byte[] rawTextBytes;//this stores _all_ of the bytes to the end of the EMRTextObject record. | |||
//Because of potential variable length encodings, must | |||
//carefully read only the numChars from this byte array. | |||
EmrTextObject(byte[] emrTextObjBytes, Charset charsetHint, int readSoFar) throws IOException { | |||
int offset = 0; | |||
x = LittleEndian.getUInt(emrTextObjBytes, offset); offset+= LittleEndian.INT_SIZE; | |||
y = LittleEndian.getUInt(emrTextObjBytes, offset); offset+= LittleEndian.INT_SIZE; | |||
long numCharsLong = LittleEndian.getUInt(emrTextObjBytes, offset); offset += LittleEndian.INT_SIZE; | |||
long offString = LittleEndian.getUInt(emrTextObjBytes, offset); offset += LittleEndian.INT_SIZE; | |||
int start = (int)offString-offset-readSoFar; | |||
if (numCharsLong == 0) { | |||
rawTextBytes = new byte[0]; | |||
numChars = 0; | |||
return; | |||
} | |||
if (numCharsLong > Integer.MAX_VALUE) { | |||
throw new RecordFormatException("Number of characters can't be > Integer.MAX_VALUE"); | |||
} else if (numCharsLong < 0) { | |||
throw new RecordFormatException("Number of characters can't be < 0"); | |||
} | |||
numChars = (int)numCharsLong; | |||
rawTextBytes = IOUtils.safelyAllocate(emrTextObjBytes.length-start, MAX_RECORD_LENGTH); | |||
System.arraycopy(emrTextObjBytes, start, rawTextBytes, 0, emrTextObjBytes.length-start); | |||
} | |||
String getText(Charset charset) throws IOException { | |||
StringBuilder sb = new StringBuilder(); | |||
try (Reader r = new InputStreamReader(new ByteArrayInputStream(rawTextBytes), charset)) { | |||
for (int i = 0; i < numChars; i++) { | |||
sb.appendCodePoint(readCodePoint(r)); | |||
} | |||
} | |||
return sb.toString(); | |||
} | |||
//TODO: move this to IOUtils? | |||
private int readCodePoint(Reader r) throws IOException { | |||
int c1 = r.read(); | |||
if (c1 == -1) { | |||
throw new EOFException("Tried to read beyond byte array"); | |||
} | |||
if (!Character.isHighSurrogate((char)c1)) { | |||
return c1; | |||
} | |||
int c2 = r.read(); | |||
if (c2 == -1) { | |||
throw new EOFException("Tried to read beyond byte array"); | |||
} | |||
if (!Character.isLowSurrogate((char)c2)) { | |||
throw new RecordFormatException("Expected low surrogate after high surrogate"); | |||
} | |||
return Character.toCodePoint((char)c1, (char)c2); | |||
} | |||
} | |||
} |
@@ -0,0 +1,444 @@ | |||
/* ==================================================================== | |||
Licensed to the Apache Software Foundation (ASF) under one or more | |||
contributor license agreements. See the NOTICE file distributed with | |||
this work for additional information regarding copyright ownership. | |||
The ASF licenses this file to You under the Apache License, Version 2.0 | |||
(the "License"); you may not use this file except in compliance with | |||
the License. You may obtain a copy of the License at | |||
http://www.apache.org/licenses/LICENSE-2.0 | |||
Unless required by applicable law or agreed to in writing, software | |||
distributed under the License is distributed on an "AS IS" BASIS, | |||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
See the License for the specific language governing permissions and | |||
limitations under the License. | |||
==================================================================== */ | |||
package org.apache.poi.hemf.record.emf; | |||
import java.awt.geom.Rectangle2D; | |||
import java.io.IOException; | |||
import java.nio.charset.StandardCharsets; | |||
import java.util.ArrayList; | |||
import java.util.Collections; | |||
import java.util.Iterator; | |||
import java.util.List; | |||
import java.util.function.Supplier; | |||
import org.apache.poi.hemf.record.emfplus.HemfPlusRecord; | |||
import org.apache.poi.hemf.record.emfplus.HemfPlusRecordIterator; | |||
import org.apache.poi.util.IOUtils; | |||
import org.apache.poi.util.Internal; | |||
import org.apache.poi.util.LittleEndianConsts; | |||
import org.apache.poi.util.LittleEndianInputStream; | |||
import org.apache.poi.util.RecordFormatException; | |||
/** | |||
* Contains arbitrary data | |||
*/ | |||
@Internal | |||
public class HemfComment { | |||
private static final int MAX_RECORD_LENGTH = 1_000_000; | |||
public enum HemfCommentRecordType { | |||
emfGeneric(-1, EmfCommentDataGeneric::new, false), | |||
emfSpool(0x00000000, EmfCommentDataGeneric::new, false), | |||
emfPlus(0x2B464D45, EmfCommentDataPlus::new, false), | |||
emfPublic(0x43494447, null, false), | |||
emfBeginGroup(0x00000002, EmfCommentDataBeginGroup::new, true), | |||
emfEndGroup(0x00000003, EmfCommentDataEndGroup::new, true), | |||
emfMultiFormats(0x40000004, EmfCommentDataMultiformats::new, true), | |||
emfWMF(0x80000001, EmfCommentDataWMF::new, true), | |||
emfUnicodeString(0x00000040, EmfCommentDataUnicode::new, true), | |||
emfUnicodeEnd(0x00000080, EmfCommentDataUnicode::new, true) | |||
; | |||
public final long id; | |||
public final Supplier<? extends EmfCommentData> constructor; | |||
public final boolean isEmfPublic; | |||
HemfCommentRecordType(long id, Supplier<? extends EmfCommentData> constructor, boolean isEmfPublic) { | |||
this.id = id; | |||
this.constructor = constructor; | |||
this.isEmfPublic = isEmfPublic; | |||
} | |||
public static HemfCommentRecordType getById(long id, boolean isEmfPublic) { | |||
for (HemfCommentRecordType wrt : values()) { | |||
if (wrt.id == id && wrt.isEmfPublic == isEmfPublic) return wrt; | |||
} | |||
return emfGeneric; | |||
} | |||
} | |||
public interface EmfCommentData { | |||
HemfCommentRecordType getCommentRecordType(); | |||
long init(LittleEndianInputStream leis, long dataSize) throws IOException; | |||
} | |||
public static class EmfComment implements HemfRecord { | |||
private EmfCommentData data; | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.comment; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
int startIdx = leis.getReadIndex(); | |||
data = new EmfCommentDataIterator(leis, (int)recordSize, true).next(); | |||
return leis.getReadIndex()-startIdx; | |||
} | |||
public EmfCommentData getCommentData() { | |||
return data; | |||
} | |||
} | |||
public static class EmfCommentDataIterator implements Iterator<EmfCommentData> { | |||
private final LittleEndianInputStream leis; | |||
private final int startIdx; | |||
private final int limit; | |||
private EmfCommentData currentRecord; | |||
/** is the caller the EmfComment */ | |||
private final boolean emfParent; | |||
public EmfCommentDataIterator(LittleEndianInputStream leis, int limit, boolean emfParent) { | |||
this.leis = leis; | |||
this.limit = limit; | |||
this.emfParent = emfParent; | |||
startIdx = leis.getReadIndex(); | |||
//queue the first non-header record | |||
currentRecord = _next(); | |||
} | |||
@Override | |||
public boolean hasNext() { | |||
return currentRecord != null; | |||
} | |||
@Override | |||
public EmfCommentData next() { | |||
EmfCommentData toReturn = currentRecord; | |||
final boolean isEOF = (limit == -1 || leis.getReadIndex() >= startIdx+limit); | |||
// (currentRecord instanceof HemfPlusMisc.EmfEof) | |||
currentRecord = isEOF ? null : _next(); | |||
return toReturn; | |||
} | |||
private EmfCommentData _next() { | |||
long type, recordSize; | |||
if (currentRecord == null && emfParent) { | |||
type = HemfRecordType.comment.id; | |||
recordSize = limit; | |||
} else { | |||
// A 32-bit unsigned integer from the RecordType enumeration that identifies this record | |||
// as a comment record. This value MUST be 0x00000046. | |||
type = leis.readUInt(); | |||
assert(type == HemfRecordType.comment.id); | |||
// A 32-bit unsigned integer that specifies the size in bytes of this record in the | |||
// metafile. This value MUST be a multiple of 4 bytes. | |||
recordSize = leis.readUInt(); | |||
} | |||
// A 32-bit unsigned integer that specifies the size, in bytes, of the CommentIdentifier and | |||
// CommentRecordParm fields in the RecordBuffer field that follows. | |||
// It MUST NOT include the size of itself or the size of the AlignmentPadding field, if present. | |||
long dataSize = leis.readUInt(); | |||
try { | |||
leis.mark(2*LittleEndianConsts.INT_SIZE); | |||
// An optional, 32-bit unsigned integer that identifies the type of comment record. | |||
// See the preceding table for descriptions of these record types. | |||
// Valid comment identifier values are listed in the following table. | |||
// | |||
// If this field contains any other value, the comment record MUST be an EMR_COMMENT record | |||
final int commentIdentifier = (int)leis.readUInt(); | |||
// A 32-bit unsigned integer that identifies the type of public comment record. | |||
final int publicCommentIdentifier = (int)leis.readUInt(); | |||
final boolean isEmfPublic = (commentIdentifier == HemfCommentRecordType.emfPublic.id); | |||
leis.reset(); | |||
final HemfCommentRecordType commentType = HemfCommentRecordType.getById | |||
(isEmfPublic ? publicCommentIdentifier : commentIdentifier, isEmfPublic); | |||
assert(commentType != null); | |||
final EmfCommentData record = commentType.constructor.get(); | |||
long readBytes = record.init(leis, dataSize); | |||
final int skipBytes = (int)(recordSize-4-readBytes); | |||
assert (skipBytes >= 0); | |||
leis.skipFully(skipBytes); | |||
return record; | |||
} catch (IOException e) { | |||
throw new RecordFormatException(e); | |||
} | |||
} | |||
@Override | |||
public void remove() { | |||
throw new UnsupportedOperationException("Remove not supported"); | |||
} | |||
} | |||
/** | |||
* Private data is unknown to EMF; it is meaningful only to applications that know the format of the | |||
* data and how to use it. EMR_COMMENT private data records MAY be ignored. | |||
*/ | |||
public static class EmfCommentDataGeneric implements EmfCommentData { | |||
private byte[] privateData; | |||
@Override | |||
public HemfCommentRecordType getCommentRecordType() { | |||
return HemfCommentRecordType.emfGeneric; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long dataSize) throws IOException { | |||
privateData = IOUtils.safelyAllocate(dataSize, MAX_RECORD_LENGTH); | |||
leis.readFully(privateData); | |||
return privateData.length; | |||
} | |||
} | |||
/** The EMR_COMMENT_EMFPLUS record contains embedded EMF+ records. */ | |||
public static class EmfCommentDataPlus implements EmfCommentData { | |||
private final List<HemfPlusRecord> records = new ArrayList<>(); | |||
@Override | |||
public HemfCommentRecordType getCommentRecordType() { | |||
return HemfCommentRecordType.emfPlus; | |||
} | |||
@Override | |||
public long init(final LittleEndianInputStream leis, final long dataSize) | |||
throws IOException { | |||
long startIdx = leis.getReadIndex(); | |||
int commentIdentifier = leis.readInt(); | |||
assert (commentIdentifier == HemfCommentRecordType.emfPlus.id); | |||
new HemfPlusRecordIterator(leis, (int)dataSize-LittleEndianConsts.INT_SIZE).forEachRemaining(records::add); | |||
return leis.getReadIndex()-startIdx; | |||
} | |||
public List<HemfPlusRecord> getRecords() { | |||
return Collections.unmodifiableList(records); | |||
} | |||
} | |||
public static class EmfCommentDataBeginGroup implements EmfCommentData { | |||
private final Rectangle2D bounds = new Rectangle2D.Double(); | |||
private String description; | |||
@Override | |||
public HemfCommentRecordType getCommentRecordType() { | |||
return HemfCommentRecordType.emfBeginGroup; | |||
} | |||
@Override | |||
public long init(final LittleEndianInputStream leis, final long dataSize) | |||
throws IOException { | |||
final int startIdx = leis.getReadIndex(); | |||
final int commentIdentifier = (int)leis.readUInt(); | |||
assert(commentIdentifier == HemfCommentRecordType.emfPublic.id); | |||
final int publicCommentIdentifier = (int)leis.readUInt(); | |||
assert(publicCommentIdentifier == HemfCommentRecordType.emfBeginGroup.id); | |||
HemfDraw.readRectL(leis, bounds); | |||
// The number of Unicode characters in the optional description string that follows. | |||
int nDescription = (int)leis.readUInt(); | |||
byte[] buf = IOUtils.safelyAllocate(nDescription*2, MAX_RECORD_LENGTH); | |||
leis.readFully(buf); | |||
description = new String(buf, StandardCharsets.UTF_16LE); | |||
return leis.getReadIndex()-startIdx; | |||
} | |||
} | |||
public static class EmfCommentDataEndGroup implements EmfCommentData { | |||
@Override | |||
public HemfCommentRecordType getCommentRecordType() { | |||
return HemfCommentRecordType.emfEndGroup; | |||
} | |||
@Override | |||
public long init(final LittleEndianInputStream leis, final long dataSize) | |||
throws IOException { | |||
final int startIdx = leis.getReadIndex(); | |||
final int commentIdentifier = (int)leis.readUInt(); | |||
assert(commentIdentifier == HemfCommentRecordType.emfPublic.id); | |||
final int publicCommentIdentifier = (int)leis.readUInt(); | |||
assert(publicCommentIdentifier == HemfCommentRecordType.emfEndGroup.id); | |||
return leis.getReadIndex()-startIdx; | |||
} | |||
} | |||
public static class EmfCommentDataMultiformats implements EmfCommentData { | |||
private final Rectangle2D bounds = new Rectangle2D.Double(); | |||
private final List<EmfCommentDataFormat> formats = new ArrayList<>(); | |||
@Override | |||
public HemfCommentRecordType getCommentRecordType() { | |||
return HemfCommentRecordType.emfMultiFormats; | |||
} | |||
@Override | |||
public long init(final LittleEndianInputStream leis, final long dataSize) | |||
throws IOException { | |||
final int startIdx = leis.getReadIndex(); | |||
final int commentIdentifier = (int)leis.readUInt(); | |||
assert(commentIdentifier == HemfCommentRecordType.emfPublic.id); | |||
final int publicCommentIdentifier = (int)leis.readUInt(); | |||
assert(publicCommentIdentifier == HemfCommentRecordType.emfMultiFormats.id); | |||
HemfDraw.readRectL(leis, bounds); | |||
// A 32-bit unsigned integer that specifies the number of graphics formats contained in this record. | |||
int countFormats = (int)leis.readUInt(); | |||
for (int i=0; i<countFormats; i++) { | |||
EmfCommentDataFormat fmt = new EmfCommentDataFormat(); | |||
long readBytes = fmt.init(leis, dataSize, startIdx); | |||
formats.add(fmt); | |||
if (readBytes == 0) { | |||
// binary data is appended without DataFormat header | |||
break; | |||
} | |||
} | |||
for (EmfCommentDataFormat fmt : formats) { | |||
int skip = fmt.offData-(leis.getReadIndex()-startIdx); | |||
leis.skipFully(skip); | |||
fmt.rawData = new byte[fmt.sizeData]; | |||
leis.readFully(fmt.rawData); | |||
} | |||
return leis.getReadIndex()-startIdx; | |||
} | |||
public List<EmfCommentDataFormat> getFormats() { | |||
return Collections.unmodifiableList(formats); | |||
} | |||
} | |||
public enum EmfFormatSignature { | |||
ENHMETA_SIGNATURE(0x464D4520), | |||
EPS_SIGNATURE(0x46535045); | |||
int id; | |||
EmfFormatSignature(int flag) { | |||
this.id = id; | |||
} | |||
public static EmfFormatSignature getById(int id) { | |||
for (EmfFormatSignature wrt : values()) { | |||
if (wrt.id == id) return wrt; | |||
} | |||
return null; | |||
} | |||
} | |||
public static class EmfCommentDataFormat { | |||
private EmfFormatSignature signature; | |||
private int version; | |||
private int sizeData; | |||
private int offData; | |||
private byte[] rawData; | |||
public long init(final LittleEndianInputStream leis, final long dataSize, long startIdx) throws IOException { | |||
// A 32-bit unsigned integer that specifies the format of the image data. | |||
signature = EmfFormatSignature.getById(leis.readInt()); | |||
// A 32-bit unsigned integer that specifies the format version number. | |||
// If the Signature field specifies encapsulated PostScript (EPS), this value MUST be 0x00000001; | |||
// otherwise, this value MUST be ignored. | |||
version = leis.readInt(); | |||
// A 32-bit unsigned integer that specifies the size of the data in bytes. | |||
sizeData = leis.readInt(); | |||
// A 32-bit unsigned integer that specifies the offset to the data from the start | |||
// of the identifier field in an EMR_COMMENT_PUBLIC record. The offset MUST be 32-bit aligned. | |||
offData = leis.readInt(); | |||
if (sizeData < 0) { | |||
throw new RecordFormatException("size for emrformat must be > 0"); | |||
} | |||
if (offData < 0) { | |||
throw new RecordFormatException("offset for emrformat must be > 0"); | |||
} | |||
return 4*LittleEndianConsts.INT_SIZE; | |||
} | |||
public byte[] getRawData() { | |||
return rawData; | |||
} | |||
} | |||
public static class EmfCommentDataWMF implements EmfCommentData { | |||
private final Rectangle2D bounds = new Rectangle2D.Double(); | |||
private final List<EmfCommentDataFormat> formats = new ArrayList<>(); | |||
@Override | |||
public HemfCommentRecordType getCommentRecordType() { | |||
return HemfCommentRecordType.emfWMF; | |||
} | |||
@Override | |||
public long init(final LittleEndianInputStream leis, final long dataSize) | |||
throws IOException { | |||
final int startIdx = leis.getReadIndex(); | |||
final int commentIdentifier = (int)leis.readUInt(); | |||
assert(commentIdentifier == HemfCommentRecordType.emfPublic.id); | |||
final int publicCommentIdentifier = (int)leis.readUInt(); | |||
assert(publicCommentIdentifier == HemfCommentRecordType.emfWMF.id); | |||
// A 16-bit unsigned integer that specifies the WMF metafile version in terms | |||
//of support for device-independent bitmaps (DIBs) | |||
int version = leis.readUShort(); | |||
// A 16-bit value that MUST be 0x0000 and MUST be ignored. | |||
leis.skipFully(LittleEndianConsts.SHORT_SIZE); | |||
// A 32-bit unsigned integer that specifies the checksum for this record. | |||
int checksum = leis.readInt(); | |||
// A 32-bit value that MUST be 0x00000000 and MUST be ignored. | |||
int flags = leis.readInt(); | |||
// A 32-bit unsigned integer that specifies the size, in bytes, of the | |||
// WMF metafile in the WinMetafile field. | |||
int winMetafileSize = (int)leis.readUInt(); | |||
byte[] winMetafile = IOUtils.safelyAllocate(winMetafileSize, MAX_RECORD_LENGTH); | |||
leis.readFully(winMetafile); | |||
return leis.getReadIndex()-startIdx; | |||
} | |||
} | |||
public static class EmfCommentDataUnicode implements EmfCommentData { | |||
private final Rectangle2D bounds = new Rectangle2D.Double(); | |||
private final List<EmfCommentDataFormat> formats = new ArrayList<>(); | |||
@Override | |||
public HemfCommentRecordType getCommentRecordType() { | |||
return HemfCommentRecordType.emfUnicodeString; | |||
} | |||
@Override | |||
public long init(final LittleEndianInputStream leis, final long dataSize) | |||
throws IOException { | |||
throw new RecordFormatException("UNICODE_STRING/UNICODE_END values are reserved in CommentPublic records"); | |||
} | |||
} | |||
} |
@@ -0,0 +1,783 @@ | |||
/* ==================================================================== | |||
Licensed to the Apache Software Foundation (ASF) under one or more | |||
contributor license agreements. See the NOTICE file distributed with | |||
this work for additional information regarding copyright ownership. | |||
The ASF licenses this file to You under the Apache License, Version 2.0 | |||
(the "License"); you may not use this file except in compliance with | |||
the License. You may obtain a copy of the License at | |||
http://www.apache.org/licenses/LICENSE-2.0 | |||
Unless required by applicable law or agreed to in writing, software | |||
distributed under the License is distributed on an "AS IS" BASIS, | |||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
See the License for the specific language governing permissions and | |||
limitations under the License. | |||
==================================================================== */ | |||
package org.apache.poi.hemf.record.emf; | |||
import java.awt.geom.AffineTransform; | |||
import java.awt.geom.Dimension2D; | |||
import java.awt.geom.Path2D; | |||
import java.awt.geom.PathIterator; | |||
import java.awt.geom.Point2D; | |||
import java.awt.geom.Rectangle2D; | |||
import java.io.IOException; | |||
import org.apache.poi.hemf.draw.HemfGraphics; | |||
import org.apache.poi.hwmf.draw.HwmfGraphics; | |||
import org.apache.poi.hwmf.record.HwmfDraw; | |||
import org.apache.poi.hwmf.record.HwmfDraw.WmfSelectObject; | |||
import org.apache.poi.util.LittleEndianConsts; | |||
import org.apache.poi.util.LittleEndianInputStream; | |||
public class HemfDraw { | |||
/** | |||
* The EMR_SELECTOBJECT record adds a graphics object to the current metafile playback device | |||
* context. The object is specified either by its index in the EMF Object Table or by its | |||
* value from the StockObject enumeration. | |||
*/ | |||
public static class EmfSelectObject extends WmfSelectObject implements HemfRecord { | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.selectObject; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
// A 32-bit unsigned integer that specifies either the index of a graphics object in the | |||
// EMF Object Table or the index of a stock object from the StockObject enumeration. | |||
objectIndex = leis.readInt(); | |||
return LittleEndianConsts.INT_SIZE; | |||
} | |||
} | |||
/** The EMR_POLYBEZIER record specifies one or more Bezier curves. */ | |||
public static class EmfPolyBezier extends HwmfDraw.WmfPolygon implements HemfRecord { | |||
private final Rectangle2D bounds = new Rectangle2D.Double(); | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.polyBezier; | |||
} | |||
protected long readPoint(LittleEndianInputStream leis, Point2D point) { | |||
return readPointL(leis, point); | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
long size = readRectL(leis, bounds); | |||
/* A 32-bit unsigned integer that specifies the number of points in the points | |||
* array. This value MUST be one more than three times the number of curves to | |||
* be drawn, because each Bezier curve requires two control points and an | |||
* endpoint, and the initial curve requires an additional starting point. | |||
* | |||
* Line width | Device supports wideline | Maximum points allowed | |||
* 1 | n/a | 16K | |||
* > 1 | yes | 16K | |||
* > 1 | no | 1360 | |||
* | |||
* Any extra points MUST be ignored. | |||
*/ | |||
final int count = (int)leis.readUInt(); | |||
final int points = Math.min(count, 16384); | |||
size += LittleEndianConsts.INT_SIZE; | |||
poly.reset(); | |||
/* Cubic Bezier curves are defined using the endpoints and control points | |||
* specified by the points field. The first curve is drawn from the first | |||
* point to the fourth point, using the second and third points as control | |||
* points. Each subsequent curve in the sequence needs exactly three more points: | |||
* the ending point of the previous curve is used as the starting point, | |||
* the next two points in the sequence are control points, | |||
* and the third is the ending point. | |||
* The cubic Bezier curves SHOULD be drawn using the current pen. | |||
*/ | |||
Point2D pnt[] = { new Point2D.Double(), new Point2D.Double(), new Point2D.Double() }; | |||
// points-1 because of the first point | |||
final int pointCnt = hasStartPoint() ? points-1 : points; | |||
for (int i=0; i+3<pointCnt; i+=3) { | |||
// x (4 bytes): A 32-bit signed integer that defines the horizontal (x) coordinate of the point. | |||
// y (4 bytes): A 32-bit signed integer that defines the vertical (y) coordinate of the point. | |||
if (i==0 && hasStartPoint()) { | |||
size += readPoint(leis, pnt[0]); | |||
poly.moveTo(pnt[0].getX(),pnt[0].getY()); | |||
} | |||
size += readPoint(leis, pnt[0]); | |||
size += readPoint(leis, pnt[1]); | |||
size += readPoint(leis, pnt[2]); | |||
poly.curveTo( | |||
pnt[0].getX(),pnt[0].getY(), | |||
pnt[1].getX(),pnt[1].getY(), | |||
pnt[2].getX(),pnt[2].getY() | |||
); | |||
} | |||
return size; | |||
} | |||
/** | |||
* @return true, if start point is in the list of points. false, if start point is taken from the context | |||
*/ | |||
protected boolean hasStartPoint() { | |||
return true; | |||
} | |||
} | |||
/** | |||
* The EMR_POLYBEZIER16 record specifies one or more Bezier curves. | |||
* The curves are drawn using the current pen. | |||
*/ | |||
public static class EmfPolyBezier16 extends EmfPolyBezier { | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.polyBezier16; | |||
} | |||
protected long readPoint(LittleEndianInputStream leis, Point2D point) { | |||
return readPointS(leis, point); | |||
} | |||
} | |||
/** | |||
* The EMR_POLYGON record specifies a polygon consisting of two or more vertexes connected by | |||
* straight lines. | |||
*/ | |||
public static class EmfPolygon extends HwmfDraw.WmfPolygon implements HemfRecord { | |||
private final Rectangle2D bounds = new Rectangle2D.Double(); | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.polygon; | |||
} | |||
protected long readPoint(LittleEndianInputStream leis, Point2D point) { | |||
return readPointL(leis, point); | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
long size = readRectL(leis, bounds); | |||
// see PolyBezier about limits | |||
final int count = (int)leis.readUInt(); | |||
final int points = Math.min(count, 16384); | |||
size += LittleEndianConsts.INT_SIZE; | |||
Point2D pnt = new Point2D.Double(); | |||
for (int i=0; i<points; i++) { | |||
size += readPoint(leis, pnt); | |||
if (i==0) { | |||
poly.moveTo(pnt.getX(), pnt.getY()); | |||
if (hasStartPoint()) { | |||
continue; | |||
} | |||
// if this path is connected to the current position (= has no start point) | |||
// the first entry is a dummy entry and will be skipped later | |||
} | |||
poly.lineTo(pnt.getX(), pnt.getY()); | |||
} | |||
return size; | |||
} | |||
/** | |||
* @return true, if start point is in the list of points. false, if start point is taken from the context | |||
*/ | |||
protected boolean hasStartPoint() { | |||
return true; | |||
} | |||
} | |||
/** | |||
* The EMR_POLYGON16 record specifies a polygon consisting of two or more vertexes connected by straight lines. | |||
* The polygon is outlined by using the current pen and filled by using the current brush and polygon fill mode. | |||
* The polygon is closed automatically by drawing a line from the last vertex to the first | |||
*/ | |||
public static class EmfPolygon16 extends EmfPolygon { | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.polygon16; | |||
} | |||
@Override | |||
protected long readPoint(LittleEndianInputStream leis, Point2D point) { | |||
return readPointS(leis, point); | |||
} | |||
} | |||
/** | |||
* The EMR_POLYLINE record specifies a series of line segments by connecting the points in the | |||
* specified array. | |||
*/ | |||
public static class EmfPolyline extends EmfPolygon { | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.polyline; | |||
} | |||
@Override | |||
protected boolean isFill() { | |||
return false; | |||
} | |||
} | |||
/** | |||
* The EMR_POLYLINE16 record specifies a series of line segments by connecting the points in the | |||
* specified array. | |||
*/ | |||
public static class EmfPolyline16 extends EmfPolyline { | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.polyline16; | |||
} | |||
@Override | |||
protected long readPoint(LittleEndianInputStream leis, Point2D point) { | |||
return readPointS(leis, point); | |||
} | |||
} | |||
/** | |||
* The EMR_POLYBEZIERTO record specifies one or more Bezier curves based upon the current | |||
* position. | |||
*/ | |||
public static class EmfPolyBezierTo extends EmfPolyBezier { | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.polyBezierTo; | |||
} | |||
@Override | |||
protected boolean hasStartPoint() { | |||
return false; | |||
} | |||
@Override | |||
protected Path2D getShape(HwmfGraphics ctx) { | |||
return polyTo(ctx, poly); | |||
} | |||
} | |||
/** | |||
* The EMR_POLYBEZIERTO16 record specifies one or more Bezier curves based on the current | |||
* position. | |||
*/ | |||
public static class EmfPolyBezierTo16 extends EmfPolyBezierTo { | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.polyBezierTo16; | |||
} | |||
@Override | |||
protected long readPoint(LittleEndianInputStream leis, Point2D point) { | |||
return readPointS(leis, point); | |||
} | |||
} | |||
/** The EMR_POLYLINETO record specifies one or more straight lines based upon the current position. */ | |||
public static class EmfPolylineTo extends EmfPolyline { | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.polylineTo; | |||
} | |||
@Override | |||
protected boolean hasStartPoint() { | |||
return false; | |||
} | |||
@Override | |||
protected Path2D getShape(HwmfGraphics ctx) { | |||
return polyTo(ctx, poly); | |||
} | |||
} | |||
/** | |||
* The EMR_POLYLINETO16 record specifies one or more straight lines based upon the current position. | |||
* A line is drawn from the current position to the first point specified by the points field by using the | |||
* current pen. For each additional line, drawing is performed from the ending point of the previous | |||
* line to the next point specified by points. | |||
*/ | |||
public static class EmfPolylineTo16 extends EmfPolylineTo { | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.polylineTo16; | |||
} | |||
@Override | |||
protected long readPoint(LittleEndianInputStream leis, Point2D point) { | |||
return readPointS(leis, point); | |||
} | |||
} | |||
/** | |||
* The EMR_POLYPOLYGON record specifies a series of closed polygons. | |||
*/ | |||
public static class EmfPolyPolygon extends HwmfDraw.WmfPolyPolygon implements HemfRecord { | |||
private final Rectangle2D bounds = new Rectangle2D.Double(); | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.polyPolygon; | |||
} | |||
protected long readPoint(LittleEndianInputStream leis, Point2D point) { | |||
return readPointL(leis, point); | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
long size = readRectL(leis, bounds); | |||
// A 32-bit unsigned integer that specifies the number of polygons. | |||
long numberOfPolygons = leis.readUInt(); | |||
// A 32-bit unsigned integer that specifies the total number of points in all polygons. | |||
long count = Math.min(16384, leis.readUInt()); | |||
size += 2 * LittleEndianConsts.INT_SIZE; | |||
// An array of 32-bit unsigned integers that specifies the point count for each polygon. | |||
long[] polygonPointCount = new long[(int)numberOfPolygons]; | |||
size += numberOfPolygons * LittleEndianConsts.INT_SIZE; | |||
for (int i=0; i<numberOfPolygons; i++) { | |||
polygonPointCount[i] = leis.readUInt(); | |||
} | |||
Point2D pnt = new Point2D.Double(); | |||
for (long nPoints : polygonPointCount) { | |||
/** | |||
* An array of WMF PointL objects that specifies the points for all polygons in logical units. | |||
* The number of points is specified by the Count field value. | |||
*/ | |||
Path2D poly = new Path2D.Double(); | |||
for (int i=0; i<nPoints; i++) { | |||
size += readPoint(leis, pnt); | |||
if (i == 0) { | |||
poly.moveTo(pnt.getX(), pnt.getY()); | |||
} else { | |||
poly.lineTo(pnt.getX(), pnt.getY()); | |||
} | |||
} | |||
if (isClosed()) { | |||
poly.closePath(); | |||
} | |||
polyList.add(poly); | |||
} | |||
return size; | |||
} | |||
/** | |||
* @return true, if a polyline should be closed, i.e. is a polygon | |||
*/ | |||
protected boolean isClosed() { | |||
return true; | |||
} | |||
} | |||
/** | |||
* The EMR_POLYPOLYGON16 record specifies a series of closed polygons. Each polygon is outlined | |||
* using the current pen, and filled using the current brush and polygon fill mode. | |||
* The polygons drawn by this record can overlap. | |||
*/ | |||
public static class EmfPolyPolygon16 extends EmfPolyPolygon { | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.polyPolygon16; | |||
} | |||
@Override | |||
protected long readPoint(LittleEndianInputStream leis, Point2D point) { | |||
return readPointS(leis, point); | |||
} | |||
} | |||
/** | |||
* The EMR_POLYPOLYLINE record specifies multiple series of connected line segments. | |||
*/ | |||
public static class EmfPolyPolyline extends EmfPolyPolygon { | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.polyPolyline; | |||
} | |||
@Override | |||
protected boolean isClosed() { | |||
return false; | |||
} | |||
@Override | |||
protected boolean isFill() { | |||
return false; | |||
} | |||
} | |||
/** The EMR_POLYPOLYLINE16 record specifies multiple series of connected line segments. */ | |||
public static class EmfPolyPolyline16 extends EmfPolyPolyline { | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.polyPolyline16; | |||
} | |||
@Override | |||
protected long readPoint(LittleEndianInputStream leis, Point2D point) { | |||
return readPointS(leis, point); | |||
} | |||
} | |||
/** | |||
* The EMR_SETPIXELV record defines the color of the pixel at the specified logical coordinates. | |||
*/ | |||
public static class EmfSetPixelV extends HwmfDraw.WmfSetPixel implements HemfRecord { | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.setPixelV; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
long size = readPointL(leis, point); | |||
size += colorRef.init(leis); | |||
return size; | |||
} | |||
} | |||
/** | |||
* The EMR_MOVETOEX record specifies coordinates of the new current position, in logical units. | |||
*/ | |||
public static class EmfSetMoveToEx extends HwmfDraw.WmfMoveTo implements HemfRecord { | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.setMoveToEx; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
return readPointL(leis, point); | |||
} | |||
} | |||
/** | |||
* The EMR_ARCTO record specifies an elliptical arc. | |||
* It resets the current position to the end point of the arc. | |||
*/ | |||
public static class EmfArc extends HwmfDraw.WmfArc implements HemfRecord { | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.arc; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
long size = readRectL(leis, bounds); | |||
size += readPointL(leis, startPoint); | |||
size += readPointL(leis, endPoint); | |||
return size; | |||
} | |||
@Override | |||
public void draw(HemfGraphics ctx) { | |||
super.draw(ctx); | |||
ctx.getProperties().setLocation(endPoint); | |||
} | |||
} | |||
/** | |||
* The EMR_CHORD record specifies a chord, which is a region bounded by the intersection of an | |||
* ellipse and a line segment, called a secant. The chord is outlined by using the current pen | |||
* and filled by using the current brush. | |||
*/ | |||
public static class EmfChord extends HwmfDraw.WmfChord implements HemfRecord { | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.chord; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
long size = readRectL(leis, bounds); | |||
size += readPointL(leis, startPoint); | |||
size += readPointL(leis, endPoint); | |||
return size; | |||
} | |||
} | |||
/** | |||
* The EMR_PIE record specifies a pie-shaped wedge bounded by the intersection of an ellipse and two | |||
* radials. The pie is outlined by using the current pen and filled by using the current brush. | |||
*/ | |||
public static class EmfPie extends HwmfDraw.WmfPie implements HemfRecord { | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.pie; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
long size = readRectL(leis, bounds); | |||
size += readPointL(leis, startPoint); | |||
size += readPointL(leis, endPoint); | |||
return size; | |||
} | |||
} | |||
/** | |||
* The EMR_ELLIPSE record specifies an ellipse. The center of the ellipse is the center of the specified | |||
* bounding rectangle. The ellipse is outlined by using the current pen and is filled by using the current | |||
* brush. | |||
*/ | |||
public static class EmfEllipse extends HwmfDraw.WmfEllipse implements HemfRecord { | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.ellipse; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
return readRectL(leis, bounds); | |||
} | |||
} | |||
/** | |||
* The EMR_RECTANGLE record draws a rectangle. The rectangle is outlined by using the current pen | |||
* and filled by using the current brush. | |||
*/ | |||
public static class EmfRectangle extends HwmfDraw.WmfRectangle implements HemfRecord { | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.rectangle; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
return readRectL(leis, bounds); | |||
} | |||
} | |||
/** | |||
* The EMR_ROUNDRECT record specifies a rectangle with rounded corners. The rectangle is outlined | |||
* by using the current pen and filled by using the current brush. | |||
*/ | |||
public static class EmfRoundRect extends HwmfDraw.WmfRoundRect implements HemfRecord { | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.roundRect; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
long size = readRectL(leis, bounds); | |||
// A 32-bit unsigned integer that defines the x-coordinate of the point. | |||
width = (int)leis.readUInt(); | |||
height = (int)leis.readUInt(); | |||
return size + 2*LittleEndianConsts.INT_SIZE; | |||
} | |||
} | |||
/** | |||
* The EMR_LINETO record specifies a line from the current position up to, but not including, the | |||
* specified point. It resets the current position to the specified point. | |||
*/ | |||
public static class EmfLineTo extends HwmfDraw.WmfLineTo implements HemfRecord { | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.lineTo; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
return readPointL(leis, point); | |||
} | |||
} | |||
/** | |||
* The EMR_ARCTO record specifies an elliptical arc. | |||
* It resets the current position to the end point of the arc. | |||
*/ | |||
public static class EmfArcTo extends HwmfDraw.WmfArc implements HemfRecord { | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.arcTo; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
long size = readRectL(leis, bounds); | |||
size += readPointL(leis, startPoint); | |||
size += readPointL(leis, endPoint); | |||
return size; | |||
} | |||
@Override | |||
public void draw(HemfGraphics ctx) { | |||
super.draw(ctx); | |||
ctx.getProperties().setLocation(endPoint); | |||
} | |||
} | |||
/** The EMR_POLYDRAW record specifies a set of line segments and Bezier curves. */ | |||
public static class EmfPolyDraw extends HwmfDraw.WmfPolygon implements HemfRecord { | |||
private final Rectangle2D bounds = new Rectangle2D.Double(); | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.polyDraw; | |||
} | |||
protected long readPoint(LittleEndianInputStream leis, Point2D point) { | |||
return readPointL(leis, point); | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
long size = readRectL(leis, bounds); | |||
int count = (int)leis.readUInt(); | |||
size += LittleEndianConsts.INT_SIZE; | |||
Point2D points[] = new Point2D[count]; | |||
for (int i=0; i<count; i++) { | |||
size += readPoint(leis, points[i]); | |||
} | |||
poly.reset(); | |||
for (int i=0; i<count; i++) { | |||
int mode = leis.readUByte(); | |||
switch (mode & 0x06) { | |||
// PT_LINETO | |||
// Specifies that a line is to be drawn from the current position to this point, which | |||
// then becomes the new current position. | |||
case 0x02: | |||
poly.lineTo(points[i].getX(), points[i].getY()); | |||
break; | |||
// PT_BEZIERTO | |||
// Specifies that this point is a control point or ending point for a Bezier curve. | |||
// PT_BEZIERTO types always occur in sets of three. | |||
// The current position defines the starting point for the Bezier curve. | |||
// The first two PT_BEZIERTO points are the control points, | |||
// and the third PT_BEZIERTO point is the ending point. | |||
// The ending point becomes the new current position. | |||
// If there are not three consecutive PT_BEZIERTO points, an error results. | |||
case 0x04: | |||
int mode2 = leis.readUByte(); | |||
int mode3 = leis.readUByte(); | |||
assert(mode2 == 0x04 && mode3 == 0x04); | |||
poly.curveTo( | |||
points[i].getX(), points[i].getY(), | |||
points[i+1].getX(), points[i+1].getY(), | |||
points[i+2].getX(), points[i+2].getY() | |||
); | |||
i+=2; | |||
break; | |||
// PT_MOVETO | |||
// Specifies that this point starts a disjoint figure. This point becomes the new current position. | |||
case 0x06: | |||
poly.moveTo(points[i].getX(), points[i].getY()); | |||
break; | |||
default: | |||
// TODO: log error | |||
break; | |||
} | |||
// PT_CLOSEFIGURE | |||
// A PT_LINETO or PT_BEZIERTO type can be combined with this value by using the bitwise operator OR | |||
// to indicate that the corresponding point is the last point in a figure and the figure is closed. | |||
// The current position is set to the ending point of the closing line. | |||
if ((mode & 0x01) == 0x01) { | |||
this.poly.closePath(); | |||
} | |||
} | |||
size += count; | |||
return size; | |||
} | |||
} | |||
public static class EmfPolyDraw16 extends EmfPolyDraw { | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.polyDraw16; | |||
} | |||
protected long readPoint(LittleEndianInputStream leis, Point2D point) { | |||
return readPointS(leis, point); | |||
} | |||
} | |||
static long readRectL(LittleEndianInputStream leis, Rectangle2D bounds) { | |||
/* A 32-bit signed integer that defines the x coordinate, in logical coordinates, | |||
* of the ... corner of the rectangle. | |||
*/ | |||
final int left = leis.readInt(); | |||
final int top = leis.readInt(); | |||
final int right = leis.readInt(); | |||
final int bottom = leis.readInt(); | |||
bounds.setRect(left, top, right-left, bottom-top); | |||
return 4 * LittleEndianConsts.INT_SIZE; | |||
} | |||
static long readPointS(LittleEndianInputStream leis, Point2D point) { | |||
// x (2 bytes): A 16-bit signed integer that defines the horizontal (x) coordinate of the point. | |||
final int x = leis.readShort(); | |||
// y (2 bytes): A 16-bit signed integer that defines the vertical (y) coordinate of the point. | |||
final int y = leis.readShort(); | |||
point.setLocation(x, y); | |||
return 2*LittleEndianConsts.SHORT_SIZE; | |||
} | |||
static long readPointL(LittleEndianInputStream leis, Point2D point) { | |||
// x (4 bytes): A 32-bit signed integer that defines the horizontal (x) coordinate of the point. | |||
final int x = leis.readInt(); | |||
// y (4 bytes): A 32-bit signed integer that defines the vertical (y) coordinate of the point. | |||
final int y = leis.readInt(); | |||
point.setLocation(x, y); | |||
return 2*LittleEndianConsts.INT_SIZE; | |||
} | |||
static long readDimensionFloat(LittleEndianInputStream leis, Dimension2D dimension) { | |||
final double width = leis.readFloat(); | |||
final double height = leis.readFloat(); | |||
dimension.setSize(width, height); | |||
return 2*LittleEndianConsts.INT_SIZE; | |||
} | |||
static long readDimensionInt(LittleEndianInputStream leis, Dimension2D dimension) { | |||
final double width = leis.readUInt(); | |||
final double height = leis.readUInt(); | |||
dimension.setSize(width, height); | |||
return 2*LittleEndianConsts.INT_SIZE; | |||
} | |||
private static Path2D polyTo(HwmfGraphics ctx, Path2D poly) { | |||
Path2D polyCopy = new Path2D.Double(); | |||
Point2D start = ctx.getProperties().getLocation(); | |||
polyCopy.moveTo(start.getX(), start.getY()); | |||
PathIterator iter = poly.getPathIterator(null); | |||
iter.next(); | |||
polyCopy.append(iter, true); | |||
return polyCopy; | |||
} | |||
} |
@@ -0,0 +1,620 @@ | |||
/* ==================================================================== | |||
Licensed to the Apache Software Foundation (ASF) under one or more | |||
contributor license agreements. See the NOTICE file distributed with | |||
this work for additional information regarding copyright ownership. | |||
The ASF licenses this file to You under the Apache License, Version 2.0 | |||
(the "License"); you may not use this file except in compliance with | |||
the License. You may obtain a copy of the License at | |||
http://www.apache.org/licenses/LICENSE-2.0 | |||
Unless required by applicable law or agreed to in writing, software | |||
distributed under the License is distributed on an "AS IS" BASIS, | |||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
See the License for the specific language governing permissions and | |||
limitations under the License. | |||
==================================================================== */ | |||
package org.apache.poi.hemf.record.emf; | |||
import static org.apache.poi.hemf.record.emf.HemfDraw.readRectL; | |||
import static org.apache.poi.hemf.record.emf.HemfDraw.readPointL; | |||
import static org.apache.poi.hemf.record.emf.HemfRecordIterator.HEADER_SIZE; | |||
import java.awt.geom.AffineTransform; | |||
import java.awt.geom.Area; | |||
import java.awt.geom.Point2D; | |||
import java.awt.geom.Rectangle2D; | |||
import java.io.ByteArrayInputStream; | |||
import java.io.ByteArrayOutputStream; | |||
import java.io.IOException; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import org.apache.poi.hwmf.draw.HwmfGraphics; | |||
import org.apache.poi.hwmf.record.HwmfBitmapDib; | |||
import org.apache.poi.hwmf.record.HwmfColorRef; | |||
import org.apache.poi.hwmf.record.HwmfDraw; | |||
import org.apache.poi.hwmf.record.HwmfFill; | |||
import org.apache.poi.hwmf.record.HwmfFill.ColorUsage; | |||
import org.apache.poi.hwmf.record.HwmfTernaryRasterOp; | |||
import org.apache.poi.util.IOUtils; | |||
import org.apache.poi.util.LittleEndian; | |||
import org.apache.poi.util.LittleEndianConsts; | |||
import org.apache.poi.util.LittleEndianInputStream; | |||
public class HemfFill { | |||
private static final int MAX_RECORD_LENGTH = 10_000_000; | |||
public enum HemfRegionMode { | |||
RGN_AND(0x01), | |||
RGN_OR(0x02), | |||
RGN_XOR(0x03), | |||
RGN_DIFF(0x04), | |||
RGN_COPY(0x05); | |||
int flag; | |||
HemfRegionMode(int flag) { | |||
this.flag = flag; | |||
} | |||
public static HemfRegionMode valueOf(int flag) { | |||
for (HemfRegionMode rm : values()) { | |||
if (rm.flag == flag) return rm; | |||
} | |||
return null; | |||
} | |||
} | |||
/** | |||
* The EMR_SETPOLYFILLMODE record defines polygon fill mode. | |||
*/ | |||
public static class EmfSetPolyfillMode extends HwmfFill.WmfSetPolyfillMode implements HemfRecord { | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.setPolyfillMode; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
// A 32-bit unsigned integer that specifies the polygon fill mode and | |||
// MUST be in the PolygonFillMode enumeration. | |||
polyfillMode = HwmfPolyfillMode.valueOf((int)leis.readUInt()); | |||
return LittleEndianConsts.INT_SIZE; | |||
} | |||
} | |||
public static class EmfExtFloodFill extends HwmfFill.WmfExtFloodFill implements HemfRecord { | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.extFloodFill; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
long size = readPointL(leis, start); | |||
size = colorRef.init(leis); | |||
// A 32-bit unsigned integer that specifies how to use the Color value to determine the area for | |||
// the flood fill operation. The value MUST be in the FloodFill enumeration | |||
mode = (int)leis.readUInt(); | |||
return size + LittleEndianConsts.INT_SIZE; | |||
} | |||
} | |||
/** | |||
* The EMR_STRETCHBLT record specifies a block transfer of pixels from a source bitmap to a destination rectangle, | |||
* optionally in combination with a brush pattern, according to a specified raster operation, stretching or | |||
* compressing the output to fit the dimensions of the destination, if necessary. | |||
*/ | |||
public static class EmfStretchBlt extends HwmfFill.WmfBitBlt implements HemfRecord { | |||
protected final Rectangle2D bounds = new Rectangle2D.Double(); | |||
/** An XForm object that specifies a world-space to page-space transform to apply to the source bitmap. */ | |||
protected final byte[] xformSrc = new byte[24]; | |||
/** A WMF ColorRef object that specifies the background color of the source bitmap. */ | |||
protected final HwmfColorRef bkColorSrc = new HwmfColorRef(); | |||
/** | |||
* A 32-bit unsigned integer that specifies how to interpret values in the color table in | |||
* the source bitmap header. This value MUST be in the DIBColors enumeration | |||
*/ | |||
protected int usageSrc; | |||
/** The source bitmap header. */ | |||
protected byte[] bmiSrc; | |||
/** The source bitmap bits. */ | |||
protected byte[] bitsSrc; | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.stretchBlt; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
long size = readRectL(leis, bounds); | |||
size += readBounds2(leis, this.dstBounds); | |||
// A 32-bit unsigned integer that specifies the raster operation code. This code defines how the | |||
// color data of the source rectangle is to be combined with the color data of the destination | |||
// rectangle and optionally a brush pattern, to achieve the final color. | |||
int rasterOpIndex = (int)leis.readUInt(); | |||
rasterOperation = HwmfTernaryRasterOp.valueOf(rasterOpIndex); | |||
size += LittleEndianConsts.INT_SIZE; | |||
final Point2D srcPnt = new Point2D.Double(); | |||
size += readPointL(leis, srcPnt); | |||
leis.readFully(xformSrc); | |||
size += 24; | |||
size += bkColorSrc.init(leis); | |||
usageSrc = (int)leis.readUInt(); | |||
// A 32-bit unsigned integer that specifies the offset, in bytes, from the | |||
// start of this record to the source bitmap header in the BitmapBuffer field. | |||
final int offBmiSrc = (int)leis.readUInt(); | |||
// A 32-bit unsigned integer that specifies the size, in bytes, of the source bitmap header. | |||
final int cbBmiSrc = (int)leis.readUInt(); | |||
// A 32-bit unsigned integer that specifies the offset, in bytes, from the | |||
// start of this record to the source bitmap bits in the BitmapBuffer field. | |||
final int offBitsSrc = (int)leis.readUInt(); | |||
// A 32-bit unsigned integer that specifies the size, in bytes, of the source bitmap bits. | |||
final int cbBitsSrc = (int)leis.readUInt(); | |||
size += 5*LittleEndianConsts.INT_SIZE; | |||
if (srcEqualsDstDimension()) { | |||
srcBounds.setRect(srcPnt.getX(), srcPnt.getY(), dstBounds.getWidth(), dstBounds.getHeight()); | |||
} else { | |||
int srcWidth = leis.readInt(); | |||
int srcHeight = leis.readInt(); | |||
size += 2 * LittleEndianConsts.INT_SIZE; | |||
srcBounds.setRect(srcPnt.getX(), srcPnt.getY(), srcWidth, srcHeight); | |||
} | |||
// size + type and size field | |||
final int undefinedSpace1 = (int)(offBmiSrc - size - HEADER_SIZE); | |||
assert(undefinedSpace1 >= 0); | |||
leis.skipFully(undefinedSpace1); | |||
size += undefinedSpace1; | |||
bmiSrc = IOUtils.safelyAllocate(cbBmiSrc, MAX_RECORD_LENGTH); | |||
leis.readFully(bmiSrc); | |||
size += cbBmiSrc; | |||
final int undefinedSpace2 = (int)(offBitsSrc - size - HEADER_SIZE); | |||
assert(undefinedSpace2 >= 0); | |||
leis.skipFully(undefinedSpace2); | |||
size += undefinedSpace2; | |||
bitsSrc = IOUtils.safelyAllocate(cbBitsSrc, MAX_RECORD_LENGTH); | |||
leis.readFully(bitsSrc); | |||
size += cbBitsSrc; | |||
return size; | |||
} | |||
protected boolean srcEqualsDstDimension() { | |||
return false; | |||
} | |||
} | |||
/** | |||
* The EMR_BITBLT record specifies a block transfer of pixels from a source bitmap to a destination rectangle, | |||
* optionally in combination with a brush pattern, according to a specified raster operation. | |||
*/ | |||
public static class EmfBitBlt extends EmfStretchBlt { | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.bitBlt; | |||
} | |||
@Override | |||
protected boolean srcEqualsDstDimension() { | |||
return false; | |||
} | |||
} | |||
/** The EMR_FRAMERGN record draws a border around the specified region using the specified brush. */ | |||
public static class EmfFrameRgn extends HwmfDraw.WmfFrameRegion implements HemfRecord { | |||
private final Rectangle2D bounds = new Rectangle2D.Double(); | |||
private final List<Rectangle2D> rgnRects = new ArrayList<>(); | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.frameRgn; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
long size = readRectL(leis, bounds); | |||
// A 32-bit unsigned integer that specifies the size of region data, in bytes. | |||
long rgnDataSize = leis.readUInt(); | |||
// A 32-bit unsigned integer that specifies the brush EMF Object Table index. | |||
brushIndex = (int)leis.readUInt(); | |||
// A 32-bit signed integer that specifies the width of the vertical brush stroke, in logical units. | |||
width = leis.readInt(); | |||
// A 32-bit signed integer that specifies the height of the horizontal brush stroke, in logical units. | |||
height = leis.readInt(); | |||
size += 4*LittleEndianConsts.INT_SIZE; | |||
size += readRgnData(leis, rgnRects); | |||
return size; | |||
} | |||
@Override | |||
public void draw(HwmfGraphics ctx) { | |||
ctx.applyObjectTableEntry(brushIndex); | |||
Area frame = new Area(); | |||
for (Rectangle2D rct : rgnRects) { | |||
frame.add(new Area(rct)); | |||
} | |||
Rectangle2D frameBounds = frame.getBounds2D(); | |||
AffineTransform at = new AffineTransform(); | |||
at.translate(bounds.getX()-frameBounds.getX(), bounds.getY()-frameBounds.getY()); | |||
at.scale(bounds.getWidth()/frameBounds.getWidth(), bounds.getHeight()/frameBounds.getHeight()); | |||
frame.transform(at); | |||
ctx.fill(frame); | |||
} | |||
} | |||
/** The EMR_INVERTRGN record inverts the colors in the specified region. */ | |||
public static class EmfInvertRgn implements HemfRecord { | |||
protected final Rectangle2D bounds = new Rectangle2D.Double(); | |||
protected final List<Rectangle2D> rgnRects = new ArrayList<>(); | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.invertRgn; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
long size = readRectL(leis, bounds); | |||
// A 32-bit unsigned integer that specifies the size of region data, in bytes. | |||
long rgnDataSize = leis.readUInt(); | |||
size += LittleEndianConsts.INT_SIZE; | |||
size += readRgnData(leis, rgnRects); | |||
return size; | |||
} | |||
} | |||
/** | |||
* The EMR_PAINTRGN record paints the specified region by using the brush currently selected into the | |||
* playback device context. | |||
*/ | |||
public static class EmfPaintRgn extends EmfInvertRgn { | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.paintRgn; | |||
} | |||
} | |||
/** The EMR_FILLRGN record fills the specified region by using the specified brush. */ | |||
public static class EmfFillRgn extends HwmfFill.WmfFillRegion implements HemfRecord { | |||
protected final Rectangle2D bounds = new Rectangle2D.Double(); | |||
protected final List<Rectangle2D> rgnRects = new ArrayList<>(); | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.fillRgn; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
long size = readRectL(leis, bounds); | |||
// A 32-bit unsigned integer that specifies the size of region data, in bytes. | |||
long rgnDataSize = leis.readUInt(); | |||
brushIndex = (int)leis.readUInt(); | |||
size += 2*LittleEndianConsts.INT_SIZE; | |||
size += readRgnData(leis, rgnRects); | |||
return size; | |||
} | |||
} | |||
public static class EmfExtSelectClipRgn implements HemfRecord { | |||
protected HemfRegionMode regionMode; | |||
protected final List<Rectangle2D> rgnRects = new ArrayList<>(); | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.extSelectClipRgn; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
// A 32-bit unsigned integer that specifies the size of region data in bytes | |||
long rgnDataSize = leis.readUInt(); | |||
// A 32-bit unsigned integer that specifies the way to use the region. | |||
regionMode = HemfRegionMode.valueOf((int)leis.readUInt()); | |||
long size = 2* LittleEndianConsts.INT_SIZE; | |||
// If RegionMode is RGN_COPY, this data can be omitted and the clip region | |||
// SHOULD be set to the default (NULL) clip region. | |||
if (regionMode != HemfRegionMode.RGN_COPY) { | |||
size += readRgnData(leis, rgnRects); | |||
} | |||
return size; | |||
} | |||
} | |||
public static class EmfAlphaBlend implements HemfRecord { | |||
/** the destination bounding rectangle in device units */ | |||
protected final Rectangle2D bounds = new Rectangle2D.Double(); | |||
/** the destination rectangle */ | |||
protected final Rectangle2D destRect = new Rectangle2D.Double(); | |||
/** the source rectangle */ | |||
protected final Rectangle2D srcRect = new Rectangle2D.Double(); | |||
/** | |||
* The blend operation code. The only source and destination blend operation that has been defined | |||
* is 0x00, which specifies that the source bitmap MUST be combined with the destination bitmap based | |||
* on the alpha transparency values of the source pixels. | |||
*/ | |||
protected byte blendOperation; | |||
/** This value MUST be 0x00 and MUST be ignored. */ | |||
protected byte blendFlags; | |||
/** | |||
* An 8-bit unsigned integer that specifies alpha transparency, which determines the blend of the source | |||
* and destination bitmaps. This value MUST be used on the entire source bitmap. The minimum alpha | |||
* transparency value, zero, corresponds to completely transparent; the maximum value, 0xFF, corresponds | |||
* to completely opaque. In effect, a value of 0xFF specifies that the per-pixel alpha values determine | |||
* the blend of the source and destination bitmaps. | |||
*/ | |||
protected int srcConstantAlpha; | |||
/** | |||
* A byte that specifies how source and destination pixels are interpreted with respect to alpha transparency. | |||
* | |||
* 0x00: | |||
* The pixels in the source bitmap do not specify alpha transparency. | |||
* In this case, the SrcConstantAlpha value determines the blend of the source and destination bitmaps. | |||
* Note that in the following equations SrcConstantAlpha is divided by 255, | |||
* which produces a value in the range 0 to 1. | |||
* | |||
* 0x01: "AC_SRC_ALPHA" | |||
* Indicates that the source bitmap is 32 bits-per-pixel and specifies an alpha transparency value | |||
* for each pixel. | |||
*/ | |||
protected byte alphaFormat; | |||
/** a world-space to page-space transform to apply to the source bitmap. */ | |||
protected final AffineTransform xFormSrc = new AffineTransform(); | |||
/** the background color of the source bitmap. */ | |||
protected final HwmfColorRef bkColorSrc = new HwmfColorRef(); | |||
/** | |||
* A 32-bit unsigned integer that specifies how to interpret values in the | |||
* color table in the source bitmap header. | |||
*/ | |||
protected ColorUsage usageSrc; | |||
protected final HwmfBitmapDib bitmap = new HwmfBitmapDib(); | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.alphaBlend; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
final int startIdx = leis.getReadIndex(); | |||
long size = readRectL(leis, bounds); | |||
size += readBounds2(leis, destRect); | |||
blendOperation = leis.readByte(); | |||
assert (blendOperation == 0); | |||
blendFlags = leis.readByte(); | |||
assert (blendOperation == 0); | |||
srcConstantAlpha = leis.readUByte(); | |||
alphaFormat = leis.readByte(); | |||
// A 32-bit signed integer that specifies the logical x-coordinate of the upper-left | |||
// corner of the source rectangle. | |||
final int xSrc = leis.readInt(); | |||
// A 32-bit signed integer that specifies the logical y-coordinate of the upper-left | |||
// corner of the source rectangle. | |||
final int ySrc = leis.readInt(); | |||
size += 3*LittleEndianConsts.INT_SIZE; | |||
size += readXForm(leis, xFormSrc); | |||
size += bkColorSrc.init(leis); | |||
usageSrc = ColorUsage.valueOf((int)leis.readUInt()); | |||
// A 32-bit unsigned integer that specifies the offset, in bytes, from the | |||
// start of this record to the source bitmap header in the BitmapBuffer field. | |||
final int offBmiSrc = (int)leis.readUInt(); | |||
// A 32-bit unsigned integer that specifies the size, in bytes, of the source bitmap header. | |||
final int cbBmiSrc = (int)leis.readUInt(); | |||
// A 32-bit unsigned integer that specifies the offset, in bytes, from the | |||
// start of this record to the source bitmap bits in the BitmapBuffer field. | |||
final int offBitsSrc = (int)leis.readUInt(); | |||
// A 32-bit unsigned integer that specifies the size, in bytes, of the source bitmap bits. | |||
final int cbBitsSrc = (int)leis.readUInt(); | |||
// A 32-bit signed integer that specifies the logical width of the source rectangle. | |||
// This value MUST be greater than zero. | |||
final int cxSrc = leis.readInt(); | |||
// A 32-bit signed integer that specifies the logical height of the source rectangle. | |||
// This value MUST be greater than zero. | |||
final int cySrc = leis.readInt(); | |||
srcRect.setRect(xSrc, ySrc, cxSrc, cySrc); | |||
size += 7 * LittleEndianConsts.INT_SIZE; | |||
size += readBitmap(leis, bitmap, startIdx, offBmiSrc, cbBmiSrc, offBitsSrc, cbBitsSrc); | |||
return size; | |||
} | |||
} | |||
public static class EmfSetDiBitsToDevice implements HemfRecord { | |||
protected final Rectangle2D bounds = new Rectangle2D.Double(); | |||
protected final Point2D dest = new Point2D.Double(); | |||
protected final Rectangle2D src = new Rectangle2D.Double(); | |||
protected ColorUsage usageSrc; | |||
protected final HwmfBitmapDib bitmap = new HwmfBitmapDib(); | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.setDiBitsToDevice; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
int startIdx = leis.getReadIndex(); | |||
// A WMF RectL object that defines the destination bounding rectangle in device units. | |||
long size = readRectL(leis, bounds); | |||
// the logical x/y-coordinate of the upper-left corner of the destination rectangle. | |||
size += readPointL(leis, dest); | |||
// the source rectangle | |||
size += readBounds2(leis, src); | |||
// A 32-bit unsigned integer that specifies the offset, in bytes, from the | |||
// start of this record to the source bitmap header in the BitmapBuffer field. | |||
final int offBmiSrc = (int)leis.readUInt(); | |||
// A 32-bit unsigned integer that specifies the size, in bytes, of the source bitmap header. | |||
final int cbBmiSrc = (int)leis.readUInt(); | |||
// A 32-bit unsigned integer that specifies the offset, in bytes, from the | |||
// start of this record to the source bitmap bits in the BitmapBuffer field. | |||
final int offBitsSrc = (int)leis.readUInt(); | |||
// A 32-bit unsigned integer that specifies the size, in bytes, of the source bitmap bits. | |||
final int cbBitsSrc = (int)leis.readUInt(); | |||
// A 32-bit unsigned integer that specifies how to interpret values in the color table | |||
// in the source bitmap header. This value MUST be in the DIBColors enumeration | |||
usageSrc = ColorUsage.valueOf((int)leis.readUInt()); | |||
// A 32-bit unsigned integer that specifies the first scan line in the array. | |||
final int iStartScan = (int)leis.readUInt(); | |||
// A 32-bit unsigned integer that specifies the number of scan lines. | |||
final int cScans = (int)leis.readUInt(); | |||
size += 7*LittleEndianConsts.INT_SIZE; | |||
size += readBitmap(leis, bitmap, startIdx, offBmiSrc, cbBmiSrc, offBitsSrc, cbBitsSrc); | |||
return size; | |||
} | |||
} | |||
static long readBitmap(final LittleEndianInputStream leis, final HwmfBitmapDib bitmap, | |||
final int startIdx, final int offBmiSrc, final int cbBmiSrc, final int offBitsSrc, int cbBitsSrc) | |||
throws IOException { | |||
final int offCurr = leis.getReadIndex()-(startIdx-HEADER_SIZE); | |||
final int undefinedSpace1 = offBmiSrc-offCurr; | |||
assert(undefinedSpace1 >= 0); | |||
final int undefinedSpace2 = offBitsSrc-offCurr-cbBmiSrc-undefinedSpace1; | |||
assert(undefinedSpace2 >= 0); | |||
leis.skipFully(undefinedSpace1); | |||
if (cbBmiSrc == 0 || cbBitsSrc == 0) { | |||
return undefinedSpace1; | |||
} | |||
final LittleEndianInputStream leisDib; | |||
if (undefinedSpace2 == 0) { | |||
leisDib = leis; | |||
} else { | |||
final ByteArrayOutputStream bos = new ByteArrayOutputStream(cbBmiSrc+cbBitsSrc); | |||
final long cbBmiSrcAct = IOUtils.copy(leis, bos, cbBmiSrc); | |||
assert (cbBmiSrcAct == cbBmiSrc); | |||
leis.skipFully(undefinedSpace2); | |||
final long cbBitsSrcAct = IOUtils.copy(leis, bos, cbBitsSrc); | |||
assert (cbBitsSrcAct == cbBitsSrc); | |||
leisDib = new LittleEndianInputStream(new ByteArrayInputStream(bos.toByteArray())); | |||
} | |||
final int dibSize = cbBmiSrc+cbBitsSrc; | |||
final int dibSizeAct = bitmap.init(leisDib, dibSize); | |||
assert (dibSizeAct <= dibSize); | |||
return undefinedSpace1 + cbBmiSrc + undefinedSpace2 + cbBitsSrc; | |||
} | |||
static long readRgnData(final LittleEndianInputStream leis, final List<Rectangle2D> rgnRects) { | |||
// *** RegionDataHeader *** | |||
// A 32-bit unsigned integer that specifies the size of this object in bytes. This MUST be 0x00000020. | |||
long rgnHdrSiez = leis.readUInt(); | |||
assert(rgnHdrSiez == 0x20); | |||
// A 32-bit unsigned integer that specifies the region type. This SHOULD be RDH_RECTANGLES (0x00000001) | |||
long rgnHdrType = leis.readUInt(); | |||
assert(rgnHdrType == 1); | |||
// A 32-bit unsigned integer that specifies the number of rectangles in this region. | |||
long rgnCntRect = leis.readUInt(); | |||
// A 32-bit unsigned integer that specifies the size of the buffer of rectangles in bytes. | |||
long rgnCntBytes = leis.readUInt(); | |||
long size = 4*LittleEndianConsts.INT_SIZE; | |||
// A 128-bit WMF RectL object, which specifies the bounds of the region. | |||
Rectangle2D rgnBounds = new Rectangle2D.Double(); | |||
size += readRectL(leis, rgnBounds); | |||
for (int i=0; i<rgnCntRect; i++) { | |||
Rectangle2D rgnRct = new Rectangle2D.Double(); | |||
size += readRectL(leis, rgnRct); | |||
rgnRects.add(rgnRct); | |||
} | |||
return size; | |||
} | |||
static int readBounds2(LittleEndianInputStream leis, Rectangle2D bounds) { | |||
/** | |||
* The 32-bit signed integers that defines the corners of the bounding rectangle. | |||
*/ | |||
int x = leis.readInt(); | |||
int y = leis.readInt(); | |||
int w = leis.readInt(); | |||
int h = leis.readInt(); | |||
bounds.setRect(x, y, w, h); | |||
return 4 * LittleEndianConsts.INT_SIZE; | |||
} | |||
static int readXForm(LittleEndianInputStream leis, AffineTransform xform) { | |||
// mapping <java AffineTransform> = <xform>: | |||
// m00 (scaleX) = eM11 (Horizontal scaling component) | |||
// m11 (scaleY) = eM22 (Vertical scaling component) | |||
// m01 (shearX) = eM12 (Horizontal proportionality constant) | |||
// m10 (shearY) = eM21 (Vertical proportionality constant) | |||
// m02 (translateX) = eDx (The horizontal translation component, in logical units.) | |||
// m12 (translateY) = eDy (The vertical translation component, in logical units.) | |||
// A 32-bit floating-point value of the transform matrix. | |||
double eM11 = leis.readFloat(); | |||
// A 32-bit floating-point value of the transform matrix. | |||
double eM12 = leis.readFloat(); | |||
// A 32-bit floating-point value of the transform matrix. | |||
double eM21 = leis.readFloat(); | |||
// A 32-bit floating-point value of the transform matrix. | |||
double eM22 = leis.readFloat(); | |||
// A 32-bit floating-point value that contains a horizontal translation component, in logical units. | |||
double eDx = leis.readFloat(); | |||
// A 32-bit floating-point value that contains a vertical translation component, in logical units. | |||
double eDy = leis.readFloat(); | |||
xform.setTransform(eM11, eM21, eM12, eM22, eDx, eDy); | |||
return 6 * LittleEndian.INT_SIZE; | |||
} | |||
} |
@@ -0,0 +1,464 @@ | |||
/* ==================================================================== | |||
Licensed to the Apache Software Foundation (ASF) under one or more | |||
contributor license agreements. See the NOTICE file distributed with | |||
this work for additional information regarding copyright ownership. | |||
The ASF licenses this file to You under the Apache License, Version 2.0 | |||
(the "License"); you may not use this file except in compliance with | |||
the License. You may obtain a copy of the License at | |||
http://www.apache.org/licenses/LICENSE-2.0 | |||
Unless required by applicable law or agreed to in writing, software | |||
distributed under the License is distributed on an "AS IS" BASIS, | |||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
See the License for the specific language governing permissions and | |||
limitations under the License. | |||
==================================================================== */ | |||
package org.apache.poi.hemf.record.emf; | |||
import java.io.IOException; | |||
import java.nio.charset.StandardCharsets; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import org.apache.poi.common.usermodel.fonts.FontCharset; | |||
import org.apache.poi.hwmf.record.HwmfFont; | |||
import org.apache.poi.util.LittleEndian; | |||
import org.apache.poi.util.LittleEndianConsts; | |||
import org.apache.poi.util.LittleEndianInputStream; | |||
public class HemfFont extends HwmfFont { | |||
private static final int LOGFONT_SIZE = 92; | |||
private static final int LOGFONTPANOSE_SIZE = 320; | |||
protected interface LogFontDetails {} | |||
protected static class LogFontExDv implements LogFontDetails { | |||
protected int[] designVector; | |||
} | |||
protected static class LogFontPanose implements LogFontDetails { | |||
enum FamilyType { | |||
PAN_ANY, | |||
PAN_NO_FIT, | |||
PAN_FAMILY_TEXT_DISPLAY, | |||
PAN_FAMILY_SCRIPT, | |||
PAN_FAMILY_DECORATIVE, | |||
PAN_FAMILY_PICTORIAL | |||
} | |||
enum SerifType { | |||
PAN_ANY, | |||
PAN_NO_FIT, | |||
PAN_SERIF_COVE, | |||
PAN_SERIF_OBTUSE_COVE, | |||
PAN_SERIF_SQUARE_COVE, | |||
PAN_SERIF_OBTUSE_SQUARE_COVE, | |||
PAN_SERIF_SQUARE, | |||
PAN_SERIF_THIN, | |||
PAN_SERIF_BONE, | |||
PAN_SERIF_EXAGGERATED, | |||
PAN_SERIF_TRIANGLE, | |||
PAN_SERIF_NORMAL_SANS, | |||
PAN_SERIF_OBTUSE_SANS, | |||
PAN_SERIF_PERP_SANS, | |||
PAN_SERIF_FLARED, | |||
PAN_SERIF_ROUNDED | |||
} | |||
enum FontWeight { | |||
PAN_ANY, | |||
PAN_NO_FIT, | |||
PAN_WEIGHT_VERY_LIGHT, | |||
PAN_WEIGHT_LIGHT, | |||
PAN_WEIGHT_THIN, | |||
PAN_WEIGHT_BOOK, | |||
PAN_WEIGHT_MEDIUM, | |||
PAN_WEIGHT_DEMI, | |||
PAN_WEIGHT_BOLD, | |||
PAN_WEIGHT_HEAVY, | |||
PAN_WEIGHT_BLACK, | |||
PAN_WEIGHT_NORD | |||
} | |||
enum Proportion { | |||
PAN_ANY, | |||
PAN_NO_FIT, | |||
PAN_PROP_OLD_STYLE, | |||
PAN_PROP_MODERN, | |||
PAN_PROP_EVEN_WIDTH, | |||
PAN_PROP_EXPANDED, | |||
PAN_PROP_CONDENSED, | |||
PAN_PROP_VERY_EXPANDED, | |||
PAN_PROP_VERY_CONDENSED, | |||
PAN_PROP_MONOSPACED | |||
} | |||
enum Contrast { | |||
PAN_ANY, | |||
PAN_NO_FIT, | |||
PAN_CONTRAST_NONE, | |||
PAN_CONTRAST_VERY_LOW, | |||
PAN_CONTRAST_LOW, | |||
PAN_CONTRAST_MEDIUM_LOW, | |||
PAN_CONTRAST_MEDIUM, | |||
PAN_CONTRAST_MEDIUM_HIGH, | |||
PAN_CONTRAST_HIGH, | |||
PAN_CONTRAST_VERY_HIGH | |||
} | |||
enum StrokeVariation { | |||
PAN_ANY, | |||
PAN_NO_FIT, | |||
PAN_STROKE_GRADUAL_DIAG, | |||
PAN_STROKE_GRADUAL_TRAN, | |||
PAN_STROKE_GRADUAL_VERT, | |||
PAN_STROKE_GRADUAL_HORZ, | |||
PAN_STROKE_RAPID_VERT, | |||
PAN_STROKE_RAPID_HORZ, | |||
PAN_STROKE_INSTANT_VERT | |||
} | |||
enum ArmStyle { | |||
PAN_ANY, | |||
PAN_NO_FIT, | |||
PAN_STRAIGHT_ARMS_HORZ, | |||
PAN_STRAIGHT_ARMS_WEDGE, | |||
PAN_STRAIGHT_ARMS_VERT, | |||
PAN_STRAIGHT_ARMS_SINGLE_SERIF, | |||
PAN_STRAIGHT_ARMS_DOUBLE_SERIF, | |||
PAN_BENT_ARMS_HORZ, | |||
PAN_BENT_ARMS_WEDGE, | |||
PAN_BENT_ARMS_VERT, | |||
PAN_BENT_ARMS_SINGLE_SERIF, | |||
PAN_BENT_ARMS_DOUBLE_SERIF | |||
} | |||
enum Letterform { | |||
PAN_ANY, | |||
PAN_NO_FIT, | |||
PAN_LETT_NORMAL_CONTACT, | |||
PAN_LETT_NORMAL_WEIGHTED, | |||
PAN_LETT_NORMAL_BOXED, | |||
PAN_LETT_NORMAL_FLATTENED, | |||
PAN_LETT_NORMAL_ROUNDED, | |||
PAN_LETT_NORMAL_OFF_CENTER, | |||
PAN_LETT_NORMAL_SQUARE, | |||
PAN_LETT_OBLIQUE_CONTACT, | |||
PAN_LETT_OBLIQUE_WEIGHTED, | |||
PAN_LETT_OBLIQUE_BOXED, | |||
PAN_LETT_OBLIQUE_FLATTENED, | |||
PAN_LETT_OBLIQUE_ROUNDED, | |||
PAN_LETT_OBLIQUE_OFF_CENTER, | |||
PAN_LETT_OBLIQUE_SQUARE | |||
} | |||
enum MidLine { | |||
PAN_ANY, | |||
PAN_NO_FIT, | |||
PAN_MIDLINE_STANDARD_TRIMMED, | |||
PAN_MIDLINE_STANDARD_POINTED, | |||
PAN_MIDLINE_STANDARD_SERIFED, | |||
PAN_MIDLINE_HIGH_TRIMMED, | |||
PAN_MIDLINE_HIGH_POINTED, | |||
PAN_MIDLINE_HIGH_SERIFED, | |||
PAN_MIDLINE_CONSTANT_TRIMMED, | |||
PAN_MIDLINE_CONSTANT_POINTED, | |||
PAN_MIDLINE_CONSTANT_SERIFED, | |||
PAN_MIDLINE_LOW_TRIMMED, | |||
PAN_MIDLINE_LOW_POINTED, | |||
PAN_MIDLINE_LOW_SERIFED | |||
} | |||
enum XHeight { | |||
PAN_ANY, | |||
PAN_NO_FIT, | |||
PAN_XHEIGHT_CONSTANT_SMALL, | |||
PAN_XHEIGHT_CONSTANT_STD, | |||
PAN_XHEIGHT_CONSTANT_LARGE, | |||
PAN_XHEIGHT_DUCKING_SMALL, | |||
PAN_XHEIGHT_DUCKING_STD, | |||
PAN_XHEIGHT_DUCKING_LARGE | |||
} | |||
protected int styleSize; | |||
protected int vendorId; | |||
protected int culture; | |||
protected FamilyType familyType; | |||
protected SerifType serifStyle; | |||
protected FontWeight weight; | |||
protected Proportion proportion; | |||
protected Contrast contrast; | |||
protected StrokeVariation strokeVariation; | |||
protected ArmStyle armStyle; | |||
protected Letterform letterform; | |||
protected MidLine midLine; | |||
protected XHeight xHeight; | |||
} | |||
protected String fullname; | |||
protected String style; | |||
protected String script; | |||
protected LogFontDetails details; | |||
@Override | |||
public int init(LittleEndianInputStream leis, long recordSize) throws IOException { | |||
// A 32-bit signed integer that specifies the height, in logical units, of the font's | |||
// character cell or character. The character height value, also known as the em size, is the | |||
// character cell height value minus the internal leading value. The font mapper SHOULD | |||
// interpret the value specified in the Height field in the following manner. | |||
// | |||
// 0x00000000 < value: | |||
// The font mapper transforms this value into device units and matches it against | |||
// the cell height of the available fonts. | |||
// | |||
// 0x00000000 | |||
// The font mapper uses a default height value when it searches for a match. | |||
// | |||
// value < 0x00000000: | |||
// The font mapper transforms this value into device units and matches its | |||
// absolute value against the character height of the available fonts. | |||
// | |||
// For all height comparisons, the font mapper SHOULD look for the largest font that does not | |||
// exceed the requested size. | |||
height = leis.readInt(); | |||
// A 32-bit signed integer that specifies the average width, in logical units, of | |||
// characters in the font. If the Width field value is zero, an appropriate value SHOULD be | |||
// calculated from other LogFont values to find a font that has the typographer's intended | |||
// aspect ratio. | |||
width = leis.readInt(); | |||
// A 32-bit signed integer that specifies the angle, in tenths of degrees, | |||
// between the escapement vector and the x-axis of the device. The escapement vector is | |||
// parallel to the baseline of a row of text. | |||
// | |||
// When the graphics mode is set to GM_ADVANCED, the escapement angle of the string can | |||
// be specified independently of the orientation angle of the string's characters. | |||
escapement = leis.readInt(); | |||
// A 32-bit signed integer that specifies the angle, in tenths of degrees, | |||
// between each character's baseline and the x-axis of the device. | |||
orientation = leis.readInt(); | |||
// A 32-bit signed integer that specifies the weight of the font in the range zero through 1000. | |||
// For example, 400 is normal and 700 is bold. If this value is zero, a default weight can be used. | |||
weight = leis.readInt(); | |||
// An 8-bit unsigned integer that specifies an italic font if set to 0x01; | |||
// otherwise, it MUST be set to 0x00. | |||
italic = (leis.readUByte() == 0x01); | |||
// An 8-bit unsigned integer that specifies an underlined font if set to 0x01; | |||
// otherwise, it MUST be set to 0x00. | |||
underline = (leis.readUByte() == 0x01); | |||
// An 8-bit unsigned integer that specifies a strikeout font if set to 0x01; | |||
// otherwise, it MUST be set to 0x00. | |||
strikeOut = (leis.readUByte() == 0x01); | |||
// An 8-bit unsigned integer that specifies the set of character glyphs. | |||
// It MUST be a value in the WMF CharacterSet enumeration. | |||
// If the character set is unknown, metafile processing SHOULD NOT attempt | |||
// to translate or interpret strings that are rendered with that font. | |||
// If a typeface name is specified in the Facename field, the CharSet field | |||
// value MUST match the character set of that typeface. | |||
charSet = FontCharset.valueOf(leis.readUByte()); | |||
// An 8-bit unsigned integer that specifies the output precision. | |||
// The output precision defines how closely the font is required to match the requested height, width, | |||
// character orientation, escapement, pitch, and font type. | |||
// It MUST be a value from the WMF OutPrecision enumeration. | |||
// Applications can use the output precision to control how the font mapper chooses a font when the | |||
// operating system contains more than one font with a specified name. For example, if an operating | |||
// system contains a font named Symbol in rasterized and TrueType forms, an output precision value | |||
// of OUT_TT_PRECIS forces the font mapper to choose the TrueType version. | |||
// A value of OUT_TT_ONLY_PRECIS forces the font mapper to choose a TrueType font, even if it is | |||
// necessary to substitute a TrueType font with another name. | |||
outPrecision = WmfOutPrecision.valueOf(leis.readUByte()); | |||
// An 8-bit unsigned integer that specifies the clipping precision. | |||
// The clipping precision defines how to clip characters that are partially outside the clipping region. | |||
// It can be one or more of the WMF ClipPrecision Flags | |||
clipPrecision.init(leis); | |||
// An 8-bit unsigned integer that specifies the output quality. The output quality defines how closely | |||
// to attempt to match the logical-font attributes to those of an actual physical font. | |||
// It MUST be one of the values in the WMF FontQuality enumeration | |||
quality = WmfFontQuality.valueOf(leis.readUByte()); | |||
// A WMF PitchAndFamily object that specifies the pitch and family of the font. | |||
// Font families describe the look of a font in a general way. | |||
// They are intended for specifying a font when the specified typeface is not available. | |||
pitchAndFamily = leis.readUByte(); | |||
int size = 5* LittleEndianConsts.INT_SIZE+8*LittleEndianConsts.BYTE_SIZE; | |||
StringBuilder sb = new StringBuilder(); | |||
// A string of no more than 32 Unicode characters that specifies the typeface name of the font. | |||
// If the length of this string is less than 32 characters, a terminating NULL MUST be present, | |||
// after which the remainder of this field MUST be ignored. | |||
int readBytes = readString(leis, sb, 32); | |||
if (readBytes == -1) { | |||
throw new IOException("Font facename can't be determined."); | |||
} | |||
facename = sb.toString(); | |||
size += readBytes; | |||
if (recordSize <= LOGFONT_SIZE) { | |||
return size; | |||
} | |||
// A string of 64 Unicode characters that contains the font's full name. | |||
// Ifthe length of this string is less than 64 characters, a terminating | |||
// NULL MUST be present, after which the remainder of this field MUST be ignored. | |||
readBytes = readString(leis, sb, 64); | |||
if (readBytes == -1) { | |||
throw new IOException("Font fullname can't be determined."); | |||
} | |||
fullname = sb.toString(); | |||
size += readBytes; | |||
// A string of 32 Unicode characters that defines the font's style. If the length of | |||
// this string is less than 32 characters, a terminating NULL MUST be present, | |||
// after which the remainder of this field MUST be ignored. | |||
readBytes = readString(leis, sb, 32); | |||
if (readBytes == -1) { | |||
throw new IOException("Font style can't be determined."); | |||
} | |||
style = sb.toString(); | |||
size += readBytes; | |||
if (recordSize == LOGFONTPANOSE_SIZE) { | |||
// LogFontPanose Object | |||
LogFontPanose logPan = new LogFontPanose(); | |||
details = logPan; | |||
int version = leis.readInt(); | |||
// A 32-bit unsigned integer that specifies the point size at which font | |||
//hinting is performed. If set to zero, font hinting is performed at the point size corresponding | |||
//to the Height field in the LogFont object in the LogFont field. | |||
logPan.styleSize = (int)leis.readUInt(); | |||
int match = leis.readInt(); | |||
int reserved = leis.readInt(); | |||
logPan.vendorId = leis.readInt(); | |||
logPan.culture = leis.readInt(); | |||
// An 8-bit unsigned integer that specifies the family type. | |||
// The value MUST be in the FamilyType enumeration table. | |||
logPan.familyType = LogFontPanose.FamilyType.values()[leis.readUByte()]; | |||
// An 8-bit unsigned integer that specifies the serif style. | |||
// The value MUST be in the SerifType enumeration table. | |||
logPan.serifStyle = LogFontPanose.SerifType.values()[leis.readUByte()]; | |||
// An 8-bit unsigned integer that specifies the weight of the font. | |||
// The value MUST be in the Weight enumeration table. | |||
logPan.weight = LogFontPanose.FontWeight.values()[leis.readUByte()]; | |||
// An 8-bit unsigned integer that specifies the proportion of the font. | |||
// The value MUST be in the Proportion enumeration table. | |||
logPan.proportion = LogFontPanose.Proportion.values()[leis.readUByte()]; | |||
// An 8-bit unsigned integer that specifies the proportion of the font. | |||
// The value MUST be in the Proportion enumeration table. | |||
logPan.contrast = LogFontPanose.Contrast.values()[leis.readUByte()]; | |||
// An 8-bit unsigned integer that specifies the stroke variation for the font. | |||
// The value MUST be in the StrokeVariation enumeration table. | |||
logPan.strokeVariation = LogFontPanose.StrokeVariation.values()[leis.readUByte()]; | |||
// An 8-bit unsigned integer that specifies the arm style of the font. | |||
// The value MUST be in the ArmStyle enumeration table. | |||
logPan.armStyle = LogFontPanose.ArmStyle.values()[leis.readUByte()]; | |||
// An 8-bit unsigned integer that specifies the letterform of the font. | |||
// The value MUST be in the Letterform enumeration table. | |||
logPan.letterform = LogFontPanose.Letterform.values()[leis.readUByte()]; | |||
// An 8-bit unsigned integer that specifies the midline of the font. | |||
// The value MUST be in the MidLine enumeration table. | |||
logPan.midLine = LogFontPanose.MidLine.values()[leis.readUByte()]; | |||
// An 8-bit unsigned integer that specifies the x height of the font. | |||
// The value MUST be in the XHeight enumeration table. | |||
logPan.xHeight = LogFontPanose.XHeight.values()[leis.readUByte()]; | |||
// skip 2 byte to ensure 32-bit alignment of this structure. | |||
leis.skip(2); | |||
size += 6*LittleEndianConsts.INT_SIZE+10* LittleEndianConsts.BYTE_SIZE+2; | |||
} else { | |||
// LogFontExDv Object | |||
LogFontExDv logEx = new LogFontExDv(); | |||
details = logEx; | |||
// A string of 32 Unicode characters that defines the character set of the font. | |||
// If the length of this string is less than 32 characters, a terminating NULL MUST be present, | |||
// after which the remainder of this field MUST be ignored. | |||
readBytes = readString(leis, sb, 32); | |||
if (readBytes == -1) { | |||
throw new IOException("Font script can't be determined."); | |||
} | |||
script = sb.toString(); | |||
size += readBytes; | |||
// Design Vector | |||
// A 32-bit unsigned integer that MUST be set to the value 0x08007664. | |||
int signature = leis.readInt(); | |||
assert (signature == 0x08007664); | |||
// A 32-bit unsigned integer that specifies the number of elements in the | |||
// Values array. It MUST be in the range 0 to 16, inclusive. | |||
int numAxes = leis.readInt(); | |||
assert (0 <= numAxes && numAxes <= 16); | |||
// An optional array of 32-bit signed integers that specify the values of the font axes of a | |||
// multiple master, OpenType font. The maximum number of values in the array is 16. | |||
if (numAxes > 0) { | |||
logEx.designVector = new int[numAxes]; | |||
for (int i=0; i<numAxes; i++) { | |||
logEx.designVector[i] = leis.readInt(); | |||
} | |||
} | |||
size += (2+numAxes)*LittleEndianConsts.INT_SIZE; | |||
} | |||
return size; | |||
} | |||
@Override | |||
protected int readString(LittleEndianInputStream leis, StringBuilder sb, int limit) throws IOException { | |||
sb.setLength(0); | |||
byte buf[] = new byte[limit*2]; | |||
leis.readFully(buf); | |||
int b1, b2, readBytes = 0; | |||
do { | |||
if (readBytes == limit*2) { | |||
return -1; | |||
} | |||
b1 = buf[readBytes++]; | |||
b2 = buf[readBytes++]; | |||
} while ((b1 != 0 || b2 != 0) && b1 != -1 && b2 != -1 && readBytes <= limit*2); | |||
sb.append(new String(buf, 0, readBytes-2, StandardCharsets.UTF_16LE)); | |||
return limit*2; | |||
} | |||
} |
@@ -15,14 +15,20 @@ | |||
limitations under the License. | |||
==================================================================== */ | |||
package org.apache.poi.hemf.record; | |||
package org.apache.poi.hemf.record.emf; | |||
import java.awt.Rectangle; | |||
import static org.apache.poi.hemf.record.emf.HemfDraw.readDimensionFloat; | |||
import static org.apache.poi.hemf.record.emf.HemfDraw.readDimensionInt; | |||
import static org.apache.poi.hemf.record.emf.HemfDraw.readRectL; | |||
import java.awt.geom.Dimension2D; | |||
import java.awt.geom.Rectangle2D; | |||
import java.io.IOException; | |||
import org.apache.poi.util.IOUtils; | |||
import org.apache.poi.util.Dimension2DDouble; | |||
import org.apache.poi.util.Internal; | |||
import org.apache.poi.util.LittleEndian; | |||
import org.apache.poi.util.LittleEndianConsts; | |||
import org.apache.poi.util.LittleEndianInputStream; | |||
/** | |||
@@ -35,8 +41,8 @@ public class HemfHeader implements HemfRecord { | |||
private static final int MAX_RECORD_LENGTH = 1_000_000; | |||
private Rectangle boundsRectangle; | |||
private Rectangle frameRectangle; | |||
private final Rectangle2D boundsRectangle = new Rectangle2D.Double(); | |||
private final Rectangle2D frameRectangle = new Rectangle2D.Double(); | |||
private long bytes; | |||
private long records; | |||
private int handles; | |||
@@ -48,14 +54,16 @@ public class HemfHeader implements HemfRecord { | |||
private long offPixelFormat; | |||
private long bOpenGL; | |||
private boolean hasExtension2; | |||
private long micrometersX; | |||
private long micrometersY; | |||
private final Dimension2D deviceDimension = new Dimension2DDouble(); | |||
private final Dimension2D milliDimension = new Dimension2DDouble(); | |||
private final Dimension2D microDimension = new Dimension2DDouble(); | |||
public Rectangle getBoundsRectangle() { | |||
public Rectangle2D getBoundsRectangle() { | |||
return boundsRectangle; | |||
} | |||
public Rectangle getFrameRectangle() { | |||
public Rectangle2D getFrameRectangle() { | |||
return frameRectangle; | |||
} | |||
@@ -104,11 +112,11 @@ public class HemfHeader implements HemfRecord { | |||
} | |||
public long getMicrometersX() { | |||
return micrometersX; | |||
return (long)microDimension.getWidth(); | |||
} | |||
public long getMicrometersY() { | |||
return micrometersY; | |||
return (long)microDimension.getHeight(); | |||
} | |||
@Override | |||
@@ -127,75 +135,61 @@ public class HemfHeader implements HemfRecord { | |||
", offPixelFormat=" + offPixelFormat + | |||
", bOpenGL=" + bOpenGL + | |||
", hasExtension2=" + hasExtension2 + | |||
", micrometersX=" + micrometersX + | |||
", micrometersY=" + micrometersY + | |||
", micrometersX=" + getMicrometersX() + | |||
", micrometersY=" + getMicrometersY() + | |||
'}'; | |||
} | |||
@Override | |||
public HemfRecordType getRecordType() { | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.header; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordId, long recordSize) throws IOException { | |||
if (recordId != 1L) { | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
if (recordId != HemfRecordType.header.id) { | |||
throw new IOException("Not a valid EMF header. Record type:"+recordId); | |||
} | |||
//read the record--id and size (2 bytes) have already been read | |||
byte[] data = IOUtils.safelyAllocate(recordSize, MAX_RECORD_LENGTH); | |||
IOUtils.readFully(leis, data); | |||
int offset = 0; | |||
//bounds | |||
int boundsLeft = LittleEndian.getInt(data, offset); offset += LittleEndian.INT_SIZE; | |||
int boundsTop = LittleEndian.getInt(data, offset); offset += LittleEndian.INT_SIZE; | |||
int boundsRight = LittleEndian.getInt(data, offset); offset += LittleEndian.INT_SIZE; | |||
int boundsBottom = LittleEndian.getInt(data, offset); offset += LittleEndian.INT_SIZE; | |||
boundsRectangle = new Rectangle(boundsLeft, boundsTop, | |||
boundsRight - boundsLeft, boundsBottom - boundsTop); | |||
int frameLeft = LittleEndian.getInt(data, offset); offset += LittleEndian.INT_SIZE; | |||
int frameTop = LittleEndian.getInt(data, offset); offset += LittleEndian.INT_SIZE; | |||
int frameRight = LittleEndian.getInt(data, offset); offset += LittleEndian.INT_SIZE; | |||
int frameBottom = LittleEndian.getInt(data, offset); offset += LittleEndian.INT_SIZE; | |||
frameRectangle = new Rectangle(frameLeft, frameTop, | |||
frameRight - frameLeft, frameBottom - frameTop); | |||
long recordSignature = LittleEndian.getInt(data, offset); offset += LittleEndian.INT_SIZE; | |||
long size = readRectL(leis, boundsRectangle); | |||
size += readRectL(leis, frameRectangle); | |||
int recordSignature = leis.readInt(); | |||
if (recordSignature != 0x464D4520) { | |||
throw new IOException("bad record signature: " + recordSignature); | |||
} | |||
long version = LittleEndian.getInt(data, offset); offset += LittleEndian.INT_SIZE; | |||
long version = leis.readInt(); | |||
//According to the spec, MSOffice doesn't pay attention to this value. | |||
//It _should_ be 0x00010000 | |||
bytes = LittleEndian.getUInt(data, offset); offset += LittleEndian.INT_SIZE; | |||
records = LittleEndian.getUInt(data, offset); offset += LittleEndian.INT_SIZE; | |||
handles = LittleEndian.getUShort(data, offset);offset += LittleEndian.SHORT_SIZE; | |||
offset += LittleEndian.SHORT_SIZE;//reserved | |||
nDescription = LittleEndian.getUInt(data, offset); offset += LittleEndian.INT_SIZE; | |||
offDescription = LittleEndian.getUInt(data, offset); offset += LittleEndian.INT_SIZE; | |||
nPalEntries = LittleEndian.getUInt(data, offset); offset += LittleEndian.INT_SIZE; | |||
bytes = leis.readUInt(); | |||
records = leis.readUInt(); | |||
handles = leis.readUShort(); | |||
//reserved | |||
leis.skipFully(LittleEndianConsts.SHORT_SIZE); | |||
nDescription = leis.readUInt(); | |||
offDescription = leis.readUInt(); | |||
nPalEntries = leis.readUInt(); | |||
//should be skips | |||
offset += 8;//device | |||
offset += 8;//millimeters | |||
size += 8*LittleEndianConsts.INT_SIZE; | |||
size += readDimensionInt(leis, deviceDimension); | |||
size += readDimensionInt(leis, milliDimension); | |||
if (recordSize+8 >= 100) { | |||
if (size+12 <= recordSize) { | |||
hasExtension1 = true; | |||
cbPixelFormat = LittleEndian.getUInt(data, offset); offset += LittleEndian.INT_SIZE; | |||
offPixelFormat = LittleEndian.getUInt(data, offset); offset += LittleEndian.INT_SIZE; | |||
bOpenGL= LittleEndian.getUInt(data, offset); offset += LittleEndian.INT_SIZE; | |||
cbPixelFormat = leis.readUInt(); | |||
offPixelFormat = leis.readUInt(); | |||
bOpenGL = leis.readUInt(); | |||
size += 3*LittleEndianConsts.INT_SIZE; | |||
} | |||
if (recordSize+8 >= 108) { | |||
if (size+8 <= recordSize) { | |||
hasExtension2 = true; | |||
micrometersX = LittleEndian.getUInt(data, offset); offset += LittleEndian.INT_SIZE; | |||
micrometersY = LittleEndian.getUInt(data, offset); offset += LittleEndian.INT_SIZE; | |||
size += readDimensionInt(leis, microDimension); | |||
} | |||
return recordSize; | |||
return size; | |||
} | |||
} |
@@ -0,0 +1,447 @@ | |||
/* ==================================================================== | |||
Licensed to the Apache Software Foundation (ASF) under one or more | |||
contributor license agreements. See the NOTICE file distributed with | |||
this work for additional information regarding copyright ownership. | |||
The ASF licenses this file to You under the Apache License, Version 2.0 | |||
(the "License"); you may not use this file except in compliance with | |||
the License. You may obtain a copy of the License at | |||
http://www.apache.org/licenses/LICENSE-2.0 | |||
Unless required by applicable law or agreed to in writing, software | |||
distributed under the License is distributed on an "AS IS" BASIS, | |||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
See the License for the specific language governing permissions and | |||
limitations under the License. | |||
==================================================================== */ | |||
package org.apache.poi.hemf.record.emf; | |||
import static org.apache.poi.hemf.record.emf.HemfDraw.readDimensionInt; | |||
import static org.apache.poi.hemf.record.emf.HemfFill.readBitmap; | |||
import static org.apache.poi.hemf.record.emf.HemfRecordIterator.HEADER_SIZE; | |||
import java.io.IOException; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import org.apache.poi.hemf.draw.HemfGraphics; | |||
import org.apache.poi.hwmf.record.HwmfBinaryRasterOp; | |||
import org.apache.poi.hwmf.record.HwmfBitmapDib; | |||
import org.apache.poi.hwmf.record.HwmfBrushStyle; | |||
import org.apache.poi.hwmf.record.HwmfColorRef; | |||
import org.apache.poi.hwmf.record.HwmfHatchStyle; | |||
import org.apache.poi.hwmf.record.HwmfMapMode; | |||
import org.apache.poi.hwmf.record.HwmfMisc; | |||
import org.apache.poi.hwmf.record.HwmfMisc.WmfSetBkMode; | |||
import org.apache.poi.hwmf.record.HwmfPalette.PaletteEntry; | |||
import org.apache.poi.hwmf.record.HwmfPenStyle; | |||
import org.apache.poi.hwmf.record.HwmfPenStyle.HwmfLineDash; | |||
import org.apache.poi.util.IOUtils; | |||
import org.apache.poi.util.LittleEndianConsts; | |||
import org.apache.poi.util.LittleEndianInputStream; | |||
public class HemfMisc { | |||
private static final int MAX_RECORD_LENGTH = 10_000_000; | |||
public static class EmfEof implements HemfRecord { | |||
protected final List<PaletteEntry> palette = new ArrayList<>(); | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.eof; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
// A 32-bit unsigned integer that specifies the number of palette entries. | |||
int nPalEntries = (int)leis.readUInt(); | |||
// A 32-bit unsigned integer that specifies the offset to the palette entries from the start of this record. | |||
int offPalEntries = (int)leis.readUInt(); | |||
int size = 2*LittleEndianConsts.INT_SIZE; | |||
int undefinedSpace1 = (int)(offPalEntries - size - HEADER_SIZE); | |||
assert (undefinedSpace1 >= 0); | |||
leis.skipFully(undefinedSpace1); | |||
size += undefinedSpace1; | |||
for (int i=0; i<nPalEntries; i++) { | |||
PaletteEntry pe = new PaletteEntry(); | |||
size += pe.init(leis); | |||
} | |||
int undefinedSpace2 = (int)(recordSize - size - LittleEndianConsts.INT_SIZE); | |||
assert (undefinedSpace2 >= 0); | |||
leis.skipFully(undefinedSpace2); | |||
size += undefinedSpace2; | |||
// A 32-bit unsigned integer that MUST be the same as Size and MUST be the | |||
// last field of the record and hence the metafile. | |||
// LogPaletteEntry objects, if they exist, MUST precede this field. | |||
long sizeLast = leis.readUInt(); | |||
size += LittleEndianConsts.INT_SIZE; | |||
assert ((sizeLast-HEADER_SIZE) == recordSize && recordSize == size); | |||
return size; | |||
} | |||
} | |||
/** | |||
* The EMF_SAVEDC record saves the playback device context for later retrieval. | |||
*/ | |||
public static class EmfSaveDc implements HemfRecord { | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.saveDc; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
return 0; | |||
} | |||
@Override | |||
public void draw(HemfGraphics ctx) { | |||
ctx.saveProperties(); | |||
} | |||
} | |||
/** | |||
* The EMF_RESTOREDC record restores the playback device context from a previously saved device | |||
* context. | |||
*/ | |||
public static class EmfRestoreDc implements HemfRecord { | |||
/** | |||
* SavedDC (4 bytes): A 32-bit signed integer that specifies the saved state to restore relative to | |||
* the current state. This value MUST be negative; –1 represents the state that was most | |||
* recently saved on the stack, –2 the one before that, etc. | |||
*/ | |||
private int nSavedDC; | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.restoreDc; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
nSavedDC = leis.readInt(); | |||
return LittleEndianConsts.INT_SIZE; | |||
} | |||
@Override | |||
public void draw(HemfGraphics ctx) { | |||
ctx.restoreProperties(nSavedDC); | |||
} | |||
} | |||
/** | |||
* The META_SETBKCOLOR record sets the background color in the playback device context to a | |||
* specified color, or to the nearest physical color if the device cannot represent the specified color. | |||
*/ | |||
public static class EmfSetBkColor implements HemfRecord { | |||
private HwmfColorRef colorRef; | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.setBkColor; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
colorRef = new HwmfColorRef(); | |||
return colorRef.init(leis); | |||
} | |||
@Override | |||
public void draw(HemfGraphics ctx) { | |||
ctx.getProperties().setBackgroundColor(colorRef); | |||
} | |||
} | |||
/** | |||
* The EMR_SETBKMODE record specifies the background mix mode of the playback device context. | |||
* The background mix mode is used with text, hatched brushes, and pen styles that are not solid | |||
* lines. | |||
*/ | |||
public static class EmfSetBkMode extends WmfSetBkMode implements HemfRecord { | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.setBkMode; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
/* | |||
* A 32-bit unsigned integer that specifies the background mode | |||
* and MUST be in the BackgroundMode (section 2.1.4) enumeration | |||
*/ | |||
bkMode = HwmfBkMode.valueOf((int)leis.readUInt()); | |||
return LittleEndianConsts.INT_SIZE; | |||
} | |||
} | |||
/** | |||
* The EMR_SETMAPPERFLAGS record specifies parameters of the process of matching logical fonts to | |||
* physical fonts, which is performed by the font mapper. | |||
*/ | |||
public static class EmfSetMapperFlags extends HwmfMisc.WmfSetMapperFlags implements HemfRecord { | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.setMapperFlags; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
return super.init(leis, recordSize, (int)recordId); | |||
} | |||
} | |||
/** | |||
* The EMR_SETMAPMODE record specifies the mapping mode of the playback device context. The | |||
* mapping mode specifies the unit of measure used to transform page space units into device space | |||
* units, and also specifies the orientation of the device's x-axis and y-axis. | |||
*/ | |||
public static class EmfSetMapMode extends HwmfMisc.WmfSetMapMode implements HemfRecord { | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.setMapMode; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
// A 32-bit unsigned integer whose definition MUST be in the MapMode enumeration | |||
mapMode = HwmfMapMode.valueOf((int)leis.readUInt()); | |||
return LittleEndianConsts.INT_SIZE; | |||
} | |||
} | |||
/** | |||
* The EMR_SETROP2 record defines a binary raster operation mode. | |||
*/ | |||
public static class EmfSetRop2 extends HwmfMisc.WmfSetRop2 implements HemfRecord { | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.setRop2; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
// A 32-bit unsigned integer that specifies the raster operation mode and | |||
// MUST be in the WMF Binary Raster Op enumeration | |||
drawMode = HwmfBinaryRasterOp.valueOf((int)leis.readUInt()); | |||
return LittleEndianConsts.INT_SIZE; | |||
} | |||
} | |||
/** | |||
* The EMR_SETSTRETCHBLTMODE record specifies bitmap stretch mode. | |||
*/ | |||
public static class EmfSetStretchBltMode extends HwmfMisc.WmfSetStretchBltMode implements HemfRecord { | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.setStretchBltMode; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
// A 32-bit unsigned integer that specifies the stretch mode and MAY be | |||
// in the StretchMode enumeration. | |||
stretchBltMode = StretchBltMode.valueOf((int)leis.readUInt()); | |||
return LittleEndianConsts.INT_SIZE; | |||
} | |||
} | |||
/** The EMR_CREATEBRUSHINDIRECT record defines a logical brush for graphics operations. */ | |||
public static class EmfCreateBrushIndirect extends HwmfMisc.WmfCreateBrushIndirect implements HemfRecord { | |||
/** | |||
* A 32-bit unsigned integer that specifies the index of the logical brush object in the | |||
* EMF Object Table. This index MUST be saved so that this object can be reused or modified. | |||
*/ | |||
private int brushIdx; | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.createBrushIndirect; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
brushIdx = (int)leis.readUInt(); | |||
brushStyle = HwmfBrushStyle.valueOf((int)leis.readUInt()); | |||
colorRef = new HwmfColorRef(); | |||
int size = colorRef.init(leis); | |||
brushHatch = HwmfHatchStyle.valueOf((int)leis.readUInt()); | |||
return size+3*LittleEndianConsts.INT_SIZE; | |||
} | |||
} | |||
/** | |||
* The EMR_DELETEOBJECT record deletes a graphics object, which is specified by its index | |||
* in the EMF Object Table | |||
*/ | |||
public static class EmfDeleteObject extends HwmfMisc.WmfDeleteObject implements HemfRecord { | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.deleteobject; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
objectIndex = (int)leis.readUInt(); | |||
return LittleEndianConsts.INT_SIZE; | |||
} | |||
} | |||
/** The EMR_CREATEPEN record defines a logical pen for graphics operations. */ | |||
public static class EmfCreatePen extends HwmfMisc.WmfCreatePenIndirect implements HemfRecord { | |||
/** | |||
* A 32-bit unsigned integer that specifies the index of the logical palette object | |||
* in the EMF Object Table. This index MUST be saved so that this object can be | |||
* reused or modified. | |||
*/ | |||
protected int penIndex; | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.createPen; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
penIndex = (int)leis.readUInt(); | |||
// A 32-bit unsigned integer that specifies the PenStyle. | |||
// The value MUST be defined from the PenStyle enumeration table | |||
penStyle = HwmfPenStyle.valueOf((int)leis.readUInt()); | |||
int widthX = leis.readInt(); | |||
int widthY = leis.readInt(); | |||
dimension.setSize(widthX, widthY); | |||
int size = colorRef.init(leis); | |||
return size + 4*LittleEndianConsts.INT_SIZE; | |||
} | |||
} | |||
public static class EmfExtCreatePen extends EmfCreatePen { | |||
protected HwmfBrushStyle brushStyle; | |||
protected HwmfHatchStyle hatchStyle; | |||
protected int[] styleEntry; | |||
protected final HwmfBitmapDib bitmap = new HwmfBitmapDib(); | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.extCreatePen; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
final int startIdx = leis.getReadIndex(); | |||
penIndex = (int)leis.readUInt(); | |||
// A 32-bit unsigned integer that specifies the offset from the start of this | |||
// record to the DIB header, if the record contains a DIB. | |||
int offBmi = (int)leis.readUInt(); | |||
// A 32-bit unsigned integer that specifies the size of the DIB header, if the | |||
// record contains a DIB. | |||
int cbBmi = (int)leis.readUInt(); | |||
// A 32-bit unsigned integer that specifies the offset from the start of this | |||
// record to the DIB bits, if the record contains a DIB. | |||
int offBits = (int)leis.readUInt(); | |||
// A 32-bit unsigned integer that specifies the size of the DIB bits, if the record | |||
// contains a DIB. | |||
int cbBits = (int)leis.readUInt(); | |||
// A 32-bit unsigned integer that specifies the PenStyle. | |||
// The value MUST be defined from the PenStyle enumeration table | |||
penStyle = HwmfPenStyle.valueOf((int)leis.readUInt()); | |||
// A 32-bit unsigned integer that specifies the width of the line drawn by the pen. | |||
// If the pen type in the PenStyle field is PS_GEOMETRIC, this value is the width in logical | |||
// units; otherwise, the width is specified in device units. If the pen type in the PenStyle field is | |||
// PS_COSMETIC, this value MUST be 0x00000001. | |||
long width = leis.readUInt(); | |||
dimension.setSize(width, 0); | |||
// A 32-bit unsigned integer that specifies a brush style for the pen from the WMF BrushStyle enumeration | |||
// | |||
// If the pen type in the PenStyle field is PS_GEOMETRIC, this value MUST be either BS_SOLID or BS_HATCHED. | |||
// The value of this field can be BS_NULL, but only if the line style specified in PenStyle is PS_NULL. | |||
// The BS_NULL style SHOULD be used to specify a brush that has no effect | |||
brushStyle = HwmfBrushStyle.valueOf((int)leis.readUInt()); | |||
int size = 8 * LittleEndianConsts.INT_SIZE; | |||
size += colorRef.init(leis); | |||
hatchStyle = HwmfHatchStyle.valueOf(leis.readInt()); | |||
// The number of elements in the array specified in the StyleEntry | |||
// field. This value SHOULD be zero if PenStyle does not specify PS_USERSTYLE. | |||
final int numStyleEntries = (int)leis.readUInt(); | |||
size += 2*LittleEndianConsts.INT_SIZE; | |||
assert(numStyleEntries == 0 || penStyle.getLineDash() == HwmfLineDash.USERSTYLE); | |||
// An optional array of 32-bit unsigned integers that defines the lengths of | |||
// dashes and gaps in the line drawn by this pen, when the value of PenStyle is | |||
// PS_USERSTYLE line style for the pen. The array contains a number of entries specified by | |||
// NumStyleEntries, but it is used as if it repeated indefinitely. | |||
// The first entry in the array specifies the length of the first dash. The second entry specifies | |||
// the length of the first gap. Thereafter, lengths of dashes and gaps alternate. | |||
// If the pen type in the PenStyle field is PS_GEOMETRIC, the lengths are specified in logical | |||
// units; otherwise, the lengths are specified in device units. | |||
styleEntry = new int[numStyleEntries]; | |||
for (int i=0; i<numStyleEntries; i++) { | |||
styleEntry[i] = (int)leis.readUInt(); | |||
} | |||
size += numStyleEntries * LittleEndianConsts.INT_SIZE; | |||
size += readBitmap(leis, bitmap, startIdx, offBmi, cbBmi, offBits, cbBits); | |||
return size; | |||
} | |||
} | |||
/** | |||
* The EMR_SETMITERLIMIT record specifies the limit for the length of miter joins for the playback | |||
* device context. | |||
*/ | |||
public static class EmfSetMiterLimit implements HemfRecord { | |||
protected int miterLimit; | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.setMiterLimit; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
miterLimit = (int)leis.readUInt(); | |||
return LittleEndianConsts.INT_SIZE; | |||
} | |||
@Override | |||
public void draw(HemfGraphics ctx) { | |||
ctx.getProperties().setPenMiterLimit(miterLimit); | |||
} | |||
} | |||
} |
@@ -0,0 +1,138 @@ | |||
/* ==================================================================== | |||
Licensed to the Apache Software Foundation (ASF) under one or more | |||
contributor license agreements. See the NOTICE file distributed with | |||
this work for additional information regarding copyright ownership. | |||
The ASF licenses this file to You under the Apache License, Version 2.0 | |||
(the "License"); you may not use this file except in compliance with | |||
the License. You may obtain a copy of the License at | |||
http://www.apache.org/licenses/LICENSE-2.0 | |||
Unless required by applicable law or agreed to in writing, software | |||
distributed under the License is distributed on an "AS IS" BASIS, | |||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
See the License for the specific language governing permissions and | |||
limitations under the License. | |||
==================================================================== */ | |||
package org.apache.poi.hemf.record.emf; | |||
import java.io.IOException; | |||
import org.apache.poi.hwmf.record.HwmfPalette; | |||
import org.apache.poi.util.LittleEndianConsts; | |||
import org.apache.poi.util.LittleEndianInputStream; | |||
public class HemfPalette { | |||
/** The EMR_SELECTPALETTE record specifies a logical palette for the playback device context. */ | |||
public static class EmfSelectPalette extends HwmfPalette.WmfSelectPalette implements HemfRecord { | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.selectPalette; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
/* | |||
* A 32-bit unsigned integer that specifies either the index of a LogPalette object | |||
* in the EMF Object Table or the value DEFAULT_PALETTE, which is the index | |||
* of a stock object palette from the StockObject enumeration | |||
*/ | |||
paletteIndex = (int)leis.readUInt(); | |||
return LittleEndianConsts.INT_SIZE; | |||
} | |||
} | |||
/** The EMR_CREATEPALETTE record defines a logical palette for graphics operations. */ | |||
public static class EmfCreatePalette extends HwmfPalette.WmfCreatePalette implements HemfRecord { | |||
protected int paletteIndex; | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.createPalette; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
start = 0x0300; | |||
/* A 32-bit unsigned integer that specifies the index of the logical palette object | |||
* in the EMF Object Table. This index MUST be saved so that this object can be | |||
* reused or modified. | |||
*/ | |||
paletteIndex = (int)leis.readUInt(); | |||
/* A 16-bit unsigned integer that specifies the version number of the system. This MUST be 0x0300. */ | |||
int version = leis.readUShort(); | |||
assert(version == 0x0300); | |||
int size = readPaletteEntries(leis, -1); | |||
return size + LittleEndianConsts.INT_SIZE + LittleEndianConsts.SHORT_SIZE; | |||
} | |||
} | |||
/** | |||
* The EMR_SETPALETTEENTRIES record defines RGB color values in a range of entries for an existing | |||
* LogPalette object. | |||
*/ | |||
public static class EmfSetPaletteEntries extends HwmfPalette.WmfSetPaletteEntries implements HemfRecord { | |||
/** specifies the palette EMF Object Table index. */ | |||
int paletteIndex; | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.setPaletteEntries; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
// A 32-bit unsigned integer that specifies the palette EMF Object Table index. | |||
paletteIndex = (int)leis.readUInt(); | |||
// A 32-bit unsigned integer that specifies the index of the first entry to set. | |||
start = (int)leis.readUInt(); | |||
// A 32-bit unsigned integer that specifies the number of entries. | |||
int nbrOfEntries = (int)leis.readUInt(); | |||
int size = readPaletteEntries(leis, nbrOfEntries); | |||
return size + 3*LittleEndianConsts.INT_SIZE; | |||
} | |||
} | |||
/** | |||
* The EMR_RESIZEPALETTE record increases or decreases the size of an existing LogPalette object | |||
*/ | |||
public static class EmfResizePalette extends HwmfPalette.WmfResizePalette implements HemfRecord { | |||
/** specifies the palette EMF Object Table index. */ | |||
int paletteIndex; | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.resizePalette; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
// A 32-bit unsigned integer that specifies the index of the palette object in the EMF Object Table | |||
paletteIndex = (int)leis.readUInt(); | |||
// A 32-bit unsigned integer that specifies the number of entries in the palette after resizing. | |||
// The value MUST be less than or equal to 0x00000400 and greater than 0x00000000. | |||
numberOfEntries = (int)leis.readUInt(); | |||
return 2*LittleEndianConsts.INT_SIZE; | |||
} | |||
} | |||
/** | |||
* This record maps palette entries from the current LogPalette object to the system_palette. | |||
* This EMF record specifies no parameters. | |||
*/ | |||
public static class EmfRealizePalette extends HwmfPalette.WmfRealizePalette implements HemfRecord { | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.realizePalette; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
return 0; | |||
} | |||
} | |||
} |
@@ -15,27 +15,32 @@ | |||
limitations under the License. | |||
==================================================================== */ | |||
package org.apache.poi.hemf.record; | |||
package org.apache.poi.hemf.record.emf; | |||
import java.io.IOException; | |||
import org.apache.poi.hemf.draw.HemfGraphics; | |||
import org.apache.poi.util.Internal; | |||
import org.apache.poi.util.LittleEndianInputStream; | |||
@Internal | |||
public interface HemfRecord { | |||
HemfRecordType getRecordType(); | |||
HemfRecordType getEmfRecordType(); | |||
/** | |||
* Init record from stream | |||
* | |||
* @param leis the little endian input stream | |||
* @param recordSize the size limit for this record | |||
* @param recordId the id of the {@link HemfRecordType} | |||
* | |||
* @return count of processed bytes | |||
* @throws IOException | |||
* | |||
* @throws IOException when the inputstream is malformed | |||
*/ | |||
long init(LittleEndianInputStream leis, long recordId, long recordSize) throws IOException; | |||
long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException; | |||
default void draw(HemfGraphics ctx) {} | |||
} |
@@ -0,0 +1,82 @@ | |||
/* ==================================================================== | |||
Licensed to the Apache Software Foundation (ASF) under one or more | |||
contributor license agreements. See the NOTICE file distributed with | |||
this work for additional information regarding copyright ownership. | |||
The ASF licenses this file to You under the Apache License, Version 2.0 | |||
(the "License"); you may not use this file except in compliance with | |||
the License. You may obtain a copy of the License at | |||
http://www.apache.org/licenses/LICENSE-2.0 | |||
Unless required by applicable law or agreed to in writing, software | |||
distributed under the License is distributed on an "AS IS" BASIS, | |||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
See the License for the specific language governing permissions and | |||
limitations under the License. | |||
==================================================================== */ | |||
package org.apache.poi.hemf.record.emf; | |||
import java.io.IOException; | |||
import java.util.Iterator; | |||
import org.apache.poi.util.LittleEndianConsts; | |||
import org.apache.poi.util.LittleEndianInputStream; | |||
import org.apache.poi.util.RecordFormatException; | |||
public class HemfRecordIterator implements Iterator<HemfRecord> { | |||
static final int HEADER_SIZE = 2*LittleEndianConsts.INT_SIZE; | |||
private final LittleEndianInputStream stream; | |||
private HemfRecord currentRecord; | |||
public HemfRecordIterator(LittleEndianInputStream leis) { | |||
stream = leis; | |||
//queue the first non-header record | |||
currentRecord = _next(); | |||
} | |||
@Override | |||
public boolean hasNext() { | |||
return currentRecord != null; | |||
} | |||
@Override | |||
public HemfRecord next() { | |||
HemfRecord toReturn = currentRecord; | |||
currentRecord = (currentRecord instanceof HemfMisc.EmfEof) ? null : _next(); | |||
return toReturn; | |||
} | |||
private HemfRecord _next() { | |||
if (currentRecord != null && HemfRecordType.eof == currentRecord.getEmfRecordType()) { | |||
return null; | |||
} | |||
long recordId = stream.readUInt(); | |||
long recordSize = stream.readUInt(); | |||
HemfRecordType type = HemfRecordType.getById(recordId); | |||
if (type == null) { | |||
throw new RecordFormatException("Undefined record of type:"+recordId); | |||
} | |||
final HemfRecord record = type.constructor.get(); | |||
try { | |||
long remBytes = recordSize-HEADER_SIZE; | |||
long readBytes = record.init(stream, remBytes, recordId); | |||
assert (readBytes <= remBytes); | |||
stream.skipFully((int)(remBytes-readBytes)); | |||
} catch (IOException|RuntimeException e) { | |||
throw new RecordFormatException(e); | |||
} | |||
return record; | |||
} | |||
@Override | |||
public void remove() { | |||
throw new UnsupportedOperationException("Remove not supported"); | |||
} | |||
} |
@@ -0,0 +1,165 @@ | |||
/* ==================================================================== | |||
Licensed to the Apache Software Foundation (ASF) under one or more | |||
contributor license agreements. See the NOTICE file distributed with | |||
this work for additional information regarding copyright ownership. | |||
The ASF licenses this file to You under the Apache License, Version 2.0 | |||
(the "License"); you may not use this file except in compliance with | |||
the License. You may obtain a copy of the License at | |||
http://www.apache.org/licenses/LICENSE-2.0 | |||
Unless required by applicable law or agreed to in writing, software | |||
distributed under the License is distributed on an "AS IS" BASIS, | |||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
See the License for the specific language governing permissions and | |||
limitations under the License. | |||
==================================================================== */ | |||
package org.apache.poi.hemf.record.emf; | |||
import java.util.function.Supplier; | |||
import org.apache.poi.util.Internal; | |||
@Internal | |||
public enum HemfRecordType { | |||
header(0x00000001, HemfHeader::new), | |||
polyBezier(0x00000002, HemfDraw.EmfPolyBezier::new), | |||
polygon(0x00000003, HemfDraw.EmfPolygon::new), | |||
polyline(0x00000004, HemfDraw.EmfPolyline::new), | |||
polyBezierTo(0x00000005, HemfDraw.EmfPolyBezierTo::new), | |||
polylineTo(0x00000006, HemfDraw.EmfPolylineTo::new), | |||
polyPolyline(0x00000007, HemfDraw.EmfPolyPolyline::new), | |||
polyPolygon(0x00000008, HemfDraw.EmfPolyPolygon::new), | |||
setWindowExtEx(0x00000009, HemfWindowing.EmfSetWindowExtEx::new), | |||
setWindowOrgEx(0x0000000A, HemfWindowing.EmfSetWindowOrgEx::new), | |||
setViewportExtEx(0x0000000B, HemfWindowing.EmfSetViewportExtEx::new), | |||
setViewportOrgEx(0x0000000C, HemfWindowing.EmfSetViewportOrgEx::new), | |||
setbrushorgex(0x0000000D, UnimplementedHemfRecord::new), | |||
eof(0x0000000E, HemfMisc.EmfEof::new), | |||
setPixelV(0x0000000F, HemfDraw.EmfSetPixelV::new), | |||
setMapperFlags(0x00000010, HemfMisc.EmfSetMapperFlags::new), | |||
setMapMode(0x00000011, HemfMisc.EmfSetMapMode::new), | |||
setBkMode(0x00000012, HemfMisc.EmfSetBkMode::new), | |||
setPolyfillMode(0x00000013, HemfFill.EmfSetPolyfillMode::new), | |||
setRop2(0x00000014, HemfMisc.EmfSetRop2::new), | |||
setStretchBltMode(0x00000015, HemfMisc.EmfSetStretchBltMode::new), | |||
setTextAlign(0x00000016, HemfText.EmfSetTextAlign::new), | |||
setcoloradjustment(0x00000017, UnimplementedHemfRecord::new), | |||
setTextColor(0x00000018, HemfText.SetTextColor::new), | |||
setBkColor(0x00000019, HemfMisc.EmfSetBkColor::new), | |||
setOffsetClipRgn(0x0000001A, HemfWindowing.EmfSetOffsetClipRgn::new), | |||
setMoveToEx(0x0000001B, HemfDraw.EmfSetMoveToEx::new), | |||
setmetargn(0x0000001C, UnimplementedHemfRecord::new), | |||
setExcludeClipRect(0x0000001D, HemfWindowing.EmfSetExcludeClipRect::new), | |||
setIntersectClipRect(0x0000001E, HemfWindowing.EmfSetIntersectClipRect::new), | |||
scaleViewportExtEx(0x0000001F, HemfWindowing.EmfScaleViewportExtEx::new), | |||
scaleWindowExtEx(0x00000020, HemfWindowing.EmfScaleWindowExtEx::new), | |||
saveDc(0x00000021, HemfMisc.EmfSaveDc::new), | |||
restoreDc(0x00000022, HemfMisc.EmfRestoreDc::new), | |||
setworldtransform(0x00000023, UnimplementedHemfRecord::new), | |||
modifyworldtransform(0x00000024, UnimplementedHemfRecord::new), | |||
selectObject(0x00000025, HemfDraw.EmfSelectObject::new), | |||
createPen(0x00000026, HemfMisc.EmfCreatePen::new), | |||
createBrushIndirect(0x00000027, HemfMisc.EmfCreateBrushIndirect::new), | |||
deleteobject(0x00000028, HemfMisc.EmfDeleteObject::new), | |||
anglearc(0x00000029, UnimplementedHemfRecord::new), | |||
ellipse(0x0000002A, HemfDraw.EmfEllipse::new), | |||
rectangle(0x0000002B, HemfDraw.EmfRectangle::new), | |||
roundRect(0x0000002C, HemfDraw.EmfRoundRect::new), | |||
arc(0x0000002D, HemfDraw.EmfArc::new), | |||
chord(0x0000002E, HemfDraw.EmfChord::new), | |||
pie(0x0000002F, HemfDraw.EmfPie::new), | |||
selectPalette(0x00000030, HemfPalette.EmfSelectPalette::new), | |||
createPalette(0x00000031, HemfPalette.EmfCreatePalette::new), | |||
setPaletteEntries(0x00000032, HemfPalette.EmfSetPaletteEntries::new), | |||
resizePalette(0x00000033, HemfPalette.EmfResizePalette::new), | |||
realizePalette(0x0000034, HemfPalette.EmfRealizePalette::new), | |||
extFloodFill(0x00000035, HemfFill.EmfExtFloodFill::new), | |||
lineTo(0x00000036, HemfDraw.EmfLineTo::new), | |||
arcTo(0x00000037, HemfDraw.EmfArcTo::new), | |||
polyDraw(0x00000038, HemfDraw.EmfPolyDraw::new), | |||
setarcdirection(0x00000039, UnimplementedHemfRecord::new), | |||
setMiterLimit(0x0000003A, HemfMisc.EmfSetMiterLimit::new), | |||
beginpath(0x0000003B, UnimplementedHemfRecord::new), | |||
endpath(0x0000003C, UnimplementedHemfRecord::new), | |||
closefigure(0x0000003D, UnimplementedHemfRecord::new), | |||
fillpath(0x0000003E, UnimplementedHemfRecord::new), | |||
strokeandfillpath(0x0000003F, UnimplementedHemfRecord::new), | |||
strokepath(0x00000040, UnimplementedHemfRecord::new), | |||
flattenpath(0x00000041, UnimplementedHemfRecord::new), | |||
widenpath(0x00000042, UnimplementedHemfRecord::new), | |||
selectclippath(0x00000043, UnimplementedHemfRecord::new), | |||
abortpath(0x00000044, UnimplementedHemfRecord::new), | |||
// no 45 ?! | |||
comment(0x00000046, HemfComment.EmfComment::new), | |||
fillRgn(0x00000047, HemfFill.EmfFillRgn::new), | |||
frameRgn(0x00000048, HemfFill.EmfFrameRgn::new), | |||
invertRgn(0x00000049, HemfFill.EmfInvertRgn::new), | |||
paintRgn(0x0000004A, HemfFill.EmfPaintRgn::new), | |||
extSelectClipRgn(0x0000004B, HemfFill.EmfExtSelectClipRgn::new), | |||
bitBlt(0x0000004C, HemfFill.EmfBitBlt::new), | |||
stretchBlt(0x0000004D, HemfFill.EmfStretchBlt::new), | |||
maskblt(0x0000004E, UnimplementedHemfRecord::new), | |||
plgblt(0x0000004F, UnimplementedHemfRecord::new), | |||
setDiBitsToDevice(0x00000050, HemfFill.EmfSetDiBitsToDevice::new), | |||
stretchdibits(0x00000051, UnimplementedHemfRecord::new), | |||
extCreateFontIndirectW(0x00000052, HemfText.ExtCreateFontIndirectW::new), | |||
exttextouta(0x00000053, HemfText.ExtTextOutA::new), | |||
exttextoutw(0x00000054, HemfText.ExtTextOutW::new), | |||
polyBezier16(0x00000055, HemfDraw.EmfPolyBezier16::new), | |||
polygon16(0x00000056, HemfDraw.EmfPolygon16::new), | |||
polyline16(0x00000057, HemfDraw.EmfPolyline16::new), | |||
polyBezierTo16(0x00000058, HemfDraw.EmfPolyBezierTo16::new), | |||
polylineTo16(0x00000059, HemfDraw.EmfPolylineTo16::new), | |||
polyPolyline16(0x0000005A, HemfDraw.EmfPolyPolyline16::new), | |||
polyPolygon16(0x0000005B, HemfDraw.EmfPolyPolygon16::new), | |||
polyDraw16(0x0000005C, HemfDraw.EmfPolyDraw16::new), | |||
createmonobrush16(0x0000005D, UnimplementedHemfRecord::new), | |||
createdibpatternbrushpt(0x0000005E, UnimplementedHemfRecord::new), | |||
extCreatePen(0x0000005F, HemfMisc.EmfExtCreatePen::new), | |||
polytextouta(0x00000060, HemfText.PolyTextOutA::new), | |||
polytextoutw(0x00000061, HemfText.PolyTextOutW::new), | |||
seticmmode(0x00000062, UnimplementedHemfRecord::new), | |||
createcolorspace(0x00000063, UnimplementedHemfRecord::new), | |||
setcolorspace(0x00000064, UnimplementedHemfRecord::new), | |||
deletecolorspace(0x00000065, UnimplementedHemfRecord::new), | |||
glsrecord(0x00000066, UnimplementedHemfRecord::new), | |||
glsboundedrecord(0x00000067, UnimplementedHemfRecord::new), | |||
pixelformat(0x00000068, UnimplementedHemfRecord::new), | |||
drawescape(0x00000069, UnimplementedHemfRecord::new), | |||
extescape(0x0000006A, UnimplementedHemfRecord::new), | |||
// no 6b ?! | |||
smalltextout(0x0000006C, UnimplementedHemfRecord::new), | |||
forceufimapping(0x0000006D, UnimplementedHemfRecord::new), | |||
namedescape(0x0000006E, UnimplementedHemfRecord::new), | |||
colorcorrectpalette(0x0000006F, UnimplementedHemfRecord::new), | |||
seticmprofilea(0x00000070, UnimplementedHemfRecord::new), | |||
seticmprofilew(0x00000071, UnimplementedHemfRecord::new), | |||
alphaBlend(0x00000072, HemfFill.EmfAlphaBlend::new), | |||
setlayout(0x00000073, UnimplementedHemfRecord::new), | |||
transparentblt(0x00000074, UnimplementedHemfRecord::new), | |||
// no 75 ?! | |||
gradientfill(0x00000076, UnimplementedHemfRecord::new), | |||
setlinkdufis(0x00000077, UnimplementedHemfRecord::new), | |||
settextjustification(0x00000078, HemfText.SetTextJustification::new), | |||
colormatchtargetw(0x00000079, UnimplementedHemfRecord::new), | |||
createcolorspacew(0x0000007A, UnimplementedHemfRecord::new); | |||
public final long id; | |||
public final Supplier<? extends HemfRecord> constructor; | |||
HemfRecordType(long id, Supplier<? extends HemfRecord> constructor) { | |||
this.id = id; | |||
this.constructor = constructor; | |||
} | |||
public static HemfRecordType getById(long id) { | |||
for (HemfRecordType wrt : values()) { | |||
if (wrt.id == id) return wrt; | |||
} | |||
return null; | |||
} | |||
} |
@@ -0,0 +1,317 @@ | |||
/* ==================================================================== | |||
Licensed to the Apache Software Foundation (ASF) under one or more | |||
contributor license agreements. See the NOTICE file distributed with | |||
this work for additional information regarding copyright ownership. | |||
The ASF licenses this file to You under the Apache License, Version 2.0 | |||
(the "License"); you may not use this file except in compliance with | |||
the License. You may obtain a copy of the License at | |||
http://www.apache.org/licenses/LICENSE-2.0 | |||
Unless required by applicable law or agreed to in writing, software | |||
distributed under the License is distributed on an "AS IS" BASIS, | |||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
See the License for the specific language governing permissions and | |||
limitations under the License. | |||
==================================================================== */ | |||
package org.apache.poi.hemf.record.emf; | |||
import static java.nio.charset.StandardCharsets.UTF_16LE; | |||
import static org.apache.poi.hemf.record.emf.HemfDraw.readRectL; | |||
import static org.apache.poi.hemf.record.emf.HemfDraw.readDimensionFloat; | |||
import static org.apache.poi.hemf.record.emf.HemfDraw.readPointL; | |||
import java.awt.geom.Dimension2D; | |||
import java.awt.geom.Rectangle2D; | |||
import java.io.IOException; | |||
import java.nio.charset.Charset; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import org.apache.poi.hwmf.record.HwmfColorRef; | |||
import org.apache.poi.hwmf.record.HwmfText; | |||
import org.apache.poi.hwmf.record.HwmfText.WmfSetTextAlign; | |||
import org.apache.poi.util.Dimension2DDouble; | |||
import org.apache.poi.util.IOUtils; | |||
import org.apache.poi.util.Internal; | |||
import org.apache.poi.util.LittleEndianConsts; | |||
import org.apache.poi.util.LittleEndianInputStream; | |||
import org.apache.poi.util.RecordFormatException; | |||
/** | |||
* Container class to gather all text-related commands | |||
* This is starting out as read only, and very little is actually | |||
* implemented at this point! | |||
*/ | |||
@Internal | |||
public class HemfText { | |||
private static final int MAX_RECORD_LENGTH = 1_000_000; | |||
public enum EmfGraphicsMode { | |||
GM_COMPATIBLE, GM_ADVANCED | |||
} | |||
public static class ExtTextOutA implements HemfRecord { | |||
protected final Rectangle2D boundsIgnored = new Rectangle2D.Double(); | |||
protected EmfGraphicsMode graphicsMode; | |||
/** | |||
* The scale factor to apply along the X/Y axis to convert from page space units to .01mm units. | |||
* This SHOULD be used only if the graphics mode specified by iGraphicsMode is GM_COMPATIBLE. | |||
*/ | |||
protected final Dimension2D scale = new Dimension2DDouble(); | |||
protected final EmrTextObject textObject; | |||
public ExtTextOutA() { | |||
this(false); | |||
} | |||
protected ExtTextOutA(boolean isUnicode) { | |||
textObject = new EmrTextObject(isUnicode); | |||
} | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.exttextouta; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
if (recordSize < 0 || Integer.MAX_VALUE <= recordSize) { | |||
throw new RecordFormatException("recordSize must be a positive integer (0-0x7FFFFFFF)"); | |||
} | |||
// A WMF RectL object. It is not used and MUST be ignored on receipt. | |||
long size = readRectL(leis, boundsIgnored); | |||
// A 32-bit unsigned integer that specifies the graphics mode from the GraphicsMode enumeration | |||
graphicsMode = EmfGraphicsMode.values()[leis.readInt()-1]; | |||
size += LittleEndianConsts.INT_SIZE; | |||
size += readDimensionFloat(leis, scale); | |||
// guarantee to read the rest of the EMRTextObjectRecord | |||
size += textObject.init(leis, recordSize, (int)size); | |||
return size; | |||
} | |||
/** | |||
* | |||
* To be implemented! We need to get the current character set | |||
* from the current font for {@link ExtTextOutA}, | |||
* which has to be tracked in the playback device. | |||
* | |||
* For {@link ExtTextOutW}, the charset is "UTF-16LE" | |||
* | |||
* @param charset the charset to be used to decode the character bytes | |||
* @return text from this text element | |||
* @throws IOException | |||
*/ | |||
public String getText(Charset charset) throws IOException { | |||
return textObject.getText(charset); | |||
} | |||
/** | |||
* | |||
* @return the x offset for the EmrTextObject | |||
*/ | |||
public EmrTextObject getTextObject() { | |||
return textObject; | |||
} | |||
public EmfGraphicsMode getGraphicsMode() { | |||
return graphicsMode; | |||
} | |||
public Dimension2D getScale() { | |||
return scale; | |||
} | |||
} | |||
public static class ExtTextOutW extends ExtTextOutA { | |||
public ExtTextOutW() { | |||
super(true); | |||
} | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.exttextoutw; | |||
} | |||
public String getText() throws IOException { | |||
return getText(UTF_16LE); | |||
} | |||
} | |||
/** | |||
* The EMR_SETTEXTALIGN record specifies text alignment. | |||
*/ | |||
public static class EmfSetTextAlign extends WmfSetTextAlign implements HemfRecord { | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.setTextAlign; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
/** | |||
* A 32-bit unsigned integer that specifies text alignment by using a mask of text alignment flags. | |||
* These are either WMF TextAlignmentMode Flags for text with a horizontal baseline, | |||
* or WMF VerticalTextAlignmentMode Flags for text with a vertical baseline. | |||
* Only one value can be chosen from those that affect horizontal and vertical alignment. | |||
*/ | |||
textAlignmentMode = (int)leis.readUInt(); | |||
return LittleEndianConsts.INT_SIZE; | |||
} | |||
} | |||
/** | |||
* The EMR_SETTEXTCOLOR record defines the current text color. | |||
*/ | |||
public static class SetTextColor implements HemfRecord { | |||
/** A WMF ColorRef object that specifies the text color value. */ | |||
private final HwmfColorRef colorRef = new HwmfColorRef(); | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.setTextColor; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
return colorRef.init(leis); | |||
} | |||
} | |||
public static class EmrTextObject extends HwmfText.WmfExtTextOut { | |||
protected final boolean isUnicode; | |||
protected final List<Integer> outputDx = new ArrayList<>(); | |||
public EmrTextObject(boolean isUnicode) { | |||
super(new EmfExtTextOutOptions()); | |||
this.isUnicode = isUnicode; | |||
} | |||
@Override | |||
public int init(LittleEndianInputStream leis, final long recordSize, final int offset) throws IOException { | |||
// A WMF PointL object that specifies the coordinates of the reference point used to position the string. | |||
// The reference point is defined by the last EMR_SETTEXTALIGN record. | |||
// If no such record has been set, the default alignment is TA_LEFT,TA_TOP. | |||
long size = readPointL(leis, reference); | |||
// A 32-bit unsigned integer that specifies the number of characters in the string. | |||
stringLength = (int)leis.readUInt(); | |||
// A 32-bit unsigned integer that specifies the offset to the output string, in bytes, | |||
// from the start of the record in which this object is contained. | |||
// This value MUST be 8- or 16-bit aligned, according to the character format. | |||
int offString = (int)leis.readUInt(); | |||
size += 2*LittleEndianConsts.INT_SIZE; | |||
size += options.init(leis); | |||
// An optional WMF RectL object that defines a clipping and/or opaquing rectangle in logical units. | |||
// This rectangle is applied to the text output performed by the containing record. | |||
if (options.isClipped() || options.isOpaque()) { | |||
size += readRectL(leis, bounds); | |||
} | |||
// A 32-bit unsigned integer that specifies the offset to an intercharacter spacing array, in bytes, | |||
// from the start of the record in which this object is contained. This value MUST be 32-bit aligned. | |||
int offDx = (int)leis.readUInt(); | |||
size += LittleEndianConsts.INT_SIZE; | |||
int undefinedSpace1 = (int)(offString-offset-size-2*LittleEndianConsts.INT_SIZE); | |||
assert (undefinedSpace1 >= 0); | |||
leis.skipFully(undefinedSpace1); | |||
size += undefinedSpace1; | |||
rawTextBytes = IOUtils.safelyAllocate(stringLength*(isUnicode?2:1), MAX_RECORD_LENGTH); | |||
leis.readFully(rawTextBytes); | |||
size += rawTextBytes.length; | |||
outputDx.clear(); | |||
if (offDx > 0) { | |||
int undefinedSpace2 = (int) (offDx - offset - size - 2 * LittleEndianConsts.INT_SIZE); | |||
assert (undefinedSpace2 >= 0); | |||
leis.skipFully(undefinedSpace2); | |||
size += undefinedSpace2; | |||
// An array of 32-bit unsigned integers that specify the output spacing between the origins of adjacent | |||
// character cells in logical units. The location of this field is specified by the value of offDx | |||
// in bytes from the start of this record. If spacing is defined, this field contains the same number | |||
// of values as characters in the output string. | |||
// | |||
// If the Options field of the EmrText object contains the ETO_PDY flag, then this buffer | |||
// contains twice as many values as there are characters in the output string, one | |||
// horizontal and one vertical offset for each, in that order. | |||
// | |||
// If ETO_RTLREADING is specified, characters are laid right to left instead of left to right. | |||
// No other options affect the interpretation of this field. | |||
while (size < recordSize) { | |||
outputDx.add((int) leis.readUInt()); | |||
size += LittleEndianConsts.INT_SIZE; | |||
} | |||
} | |||
return (int)size; | |||
} | |||
} | |||
public static class ExtCreateFontIndirectW extends HwmfText.WmfCreateFontIndirect | |||
implements HemfRecord { | |||
int fontIdx; | |||
public ExtCreateFontIndirectW() { | |||
super(new HemfFont()); | |||
} | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.extCreateFontIndirectW; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
// A 32-bit unsigned integer that specifies the index of the logical font object | |||
// in the EMF Object Table | |||
fontIdx = (int)leis.readUInt(); | |||
int size = font.init(leis, (int)(recordSize-LittleEndianConsts.INT_SIZE)); | |||
return size+LittleEndianConsts.INT_SIZE; | |||
} | |||
} | |||
public static class EmfExtTextOutOptions extends HwmfText.WmfExtTextOutOptions { | |||
@Override | |||
public int init(LittleEndianInputStream leis) { | |||
// A 32-bit unsigned integer that specifies how to use the rectangle specified in the Rectangle field. | |||
// This field can be a combination of more than one ExtTextOutOptions enumeration | |||
flag = (int)leis.readUInt(); | |||
return LittleEndianConsts.INT_SIZE; | |||
} | |||
} | |||
public static class SetTextJustification extends UnimplementedHemfRecord { | |||
} | |||
/** | |||
* Needs to be implemented. Couldn't find example. | |||
*/ | |||
public static class PolyTextOutA extends UnimplementedHemfRecord { | |||
} | |||
/** | |||
* Needs to be implemented. Couldn't find example. | |||
*/ | |||
public static class PolyTextOutW extends UnimplementedHemfRecord { | |||
} | |||
} |
@@ -0,0 +1,200 @@ | |||
/* ==================================================================== | |||
Licensed to the Apache Software Foundation (ASF) under one or more | |||
contributor license agreements. See the NOTICE file distributed with | |||
this work for additional information regarding copyright ownership. | |||
The ASF licenses this file to You under the Apache License, Version 2.0 | |||
(the "License"); you may not use this file except in compliance with | |||
the License. You may obtain a copy of the License at | |||
http://www.apache.org/licenses/LICENSE-2.0 | |||
Unless required by applicable law or agreed to in writing, software | |||
distributed under the License is distributed on an "AS IS" BASIS, | |||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
See the License for the specific language governing permissions and | |||
limitations under the License. | |||
==================================================================== */ | |||
package org.apache.poi.hemf.record.emf; | |||
import java.io.IOException; | |||
import org.apache.poi.hwmf.record.HwmfWindowing; | |||
import org.apache.poi.util.LittleEndianConsts; | |||
import org.apache.poi.util.LittleEndianInputStream; | |||
public class HemfWindowing { | |||
/** | |||
* The EMR_SETWINDOWEXTEX record defines the window extent. | |||
*/ | |||
public static class EmfSetWindowExtEx extends HwmfWindowing.WmfSetWindowExt implements HemfRecord { | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.setWindowExtEx; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
// cx (4 bytes): A 32-bit unsigned integer that defines the x-coordinate of the point. | |||
width = (int)leis.readUInt(); | |||
// cy (4 bytes): A 32-bit unsigned integer that defines the y-coordinate of the point. | |||
height = (int)leis.readUInt(); | |||
return 2*LittleEndianConsts.INT_SIZE; | |||
} | |||
} | |||
/** | |||
* The EMR_SETWINDOWORGEX record defines the window origin. | |||
*/ | |||
public static class EmfSetWindowOrgEx extends HwmfWindowing.WmfSetWindowOrg implements HemfRecord { | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.setWindowOrgEx; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
// x (4 bytes): A 32-bit signed integer that defines the horizontal (x) coordinate of the point. | |||
x = leis.readInt(); | |||
// y (4 bytes): A 32-bit signed integer that defines the vertical (y) coordinate of the point. | |||
y = leis.readInt(); | |||
return 2*LittleEndianConsts.INT_SIZE; | |||
} | |||
} | |||
/** | |||
* The EMR_SETVIEWPORTEXTEX record defines the viewport extent. | |||
*/ | |||
public static class EmfSetViewportExtEx extends HwmfWindowing.WmfSetViewportExt implements HemfRecord { | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.setViewportExtEx; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
// cx (4 bytes): A 32-bit unsigned integer that defines the x-coordinate of the point. | |||
width = (int)leis.readUInt(); | |||
// cy (4 bytes): A 32-bit unsigned integer that defines the y-coordinate of the point. | |||
height = (int)leis.readUInt(); | |||
return 2*LittleEndianConsts.INT_SIZE; | |||
} | |||
} | |||
/** | |||
* The EMR_SETVIEWPORTORGEX record defines the viewport origin. | |||
*/ | |||
public static class EmfSetViewportOrgEx extends HwmfWindowing.WmfSetViewportOrg implements HemfRecord { | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.setViewportOrgEx; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
// x (4 bytes): A 32-bit signed integer that defines the horizontal (x) coordinate of the point. | |||
x = leis.readInt(); | |||
// y (4 bytes): A 32-bit signed integer that defines the vertical (y) coordinate of the point. | |||
y = leis.readInt(); | |||
return 2*LittleEndianConsts.INT_SIZE; | |||
} | |||
} | |||
/** | |||
* The EMR_OFFSETCLIPRGN record moves the current clipping region in the playback device context | |||
* by the specified offsets. | |||
*/ | |||
public static class EmfSetOffsetClipRgn extends HwmfWindowing.WmfOffsetClipRgn implements HemfRecord { | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.setOffsetClipRgn; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
// x (4 bytes): A 32-bit signed integer that defines the horizontal (x) coordinate of the point. | |||
xOffset = leis.readInt(); | |||
// y (4 bytes): A 32-bit signed integer that defines the vertical (y) coordinate of the point. | |||
yOffset = leis.readInt(); | |||
return 2*LittleEndianConsts.INT_SIZE; | |||
} | |||
} | |||
/** | |||
* The EMR_EXCLUDECLIPRECT record specifies a new clipping region that consists of the existing | |||
* clipping region minus the specified rectangle. | |||
*/ | |||
public static class EmfSetExcludeClipRect extends HwmfWindowing.WmfExcludeClipRect implements HemfRecord { | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.setExcludeClipRect; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
return HemfDraw.readRectL(leis, bounds); | |||
} | |||
} | |||
/** | |||
* The EMR_INTERSECTCLIPRECT record specifies a new clipping region from the intersection of the | |||
* current clipping region and the specified rectangle. | |||
*/ | |||
public static class EmfSetIntersectClipRect extends HwmfWindowing.WmfIntersectClipRect implements HemfRecord { | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.setIntersectClipRect; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
return HemfDraw.readRectL(leis, bounds); | |||
} | |||
} | |||
/** | |||
* The EMR_SCALEVIEWPORTEXTEX record respecifies the viewport for a device context by using the | |||
* ratios formed by the specified multiplicands and divisors. | |||
*/ | |||
public static class EmfScaleViewportExtEx extends HwmfWindowing.WmfScaleViewportExt implements HemfRecord { | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.scaleViewportExtEx; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
xNum = leis.readInt(); | |||
xDenom = leis.readInt(); | |||
yNum = leis.readInt(); | |||
yDenom = leis.readInt(); | |||
return 4*LittleEndianConsts.INT_SIZE; | |||
} | |||
} | |||
/** | |||
* The EMR_SCALEWINDOWEXTEX record respecifies the window for a playback device context by | |||
* using the ratios formed by the specified multiplicands and divisors. | |||
*/ | |||
public static class EmfScaleWindowExtEx extends HwmfWindowing.WmfScaleWindowExt implements HemfRecord { | |||
@Override | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.scaleWindowExtEx; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
xNum = leis.readInt(); | |||
xDenom = leis.readInt(); | |||
yNum = leis.readInt(); | |||
yDenom = leis.readInt(); | |||
return 4*LittleEndianConsts.INT_SIZE; | |||
} | |||
} | |||
} |
@@ -15,7 +15,7 @@ | |||
limitations under the License. | |||
==================================================================== */ | |||
package org.apache.poi.hemf.record; | |||
package org.apache.poi.hemf.record.emf; | |||
import java.io.IOException; | |||
@@ -33,12 +33,12 @@ public class UnimplementedHemfRecord implements HemfRecord { | |||
} | |||
@Override | |||
public HemfRecordType getRecordType() { | |||
public HemfRecordType getEmfRecordType() { | |||
return HemfRecordType.getById(recordId); | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordId, long recordSize) throws IOException { | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
this.recordId = recordId; | |||
long skipped = IOUtils.skipFully(leis, recordSize); | |||
if (skipped < recordSize) { |
@@ -15,13 +15,14 @@ | |||
limitations under the License. | |||
==================================================================== */ | |||
package org.apache.poi.hemf.hemfplus.record; | |||
package org.apache.poi.hemf.record.emfplus; | |||
import java.io.IOException; | |||
import org.apache.poi.util.Internal; | |||
import org.apache.poi.util.LittleEndian; | |||
import org.apache.poi.util.LittleEndianConsts; | |||
import org.apache.poi.util.LittleEndianInputStream; | |||
@Internal | |||
public class HemfPlusHeader implements HemfPlusRecord { | |||
@@ -42,15 +43,19 @@ public class HemfPlusHeader implements HemfPlusRecord { | |||
} | |||
@Override | |||
public void init(byte[] dataBytes, int recordId, int flags) throws IOException { | |||
//assert record id == header | |||
public long init(LittleEndianInputStream leis, long dataSize, long recordId, int flags) throws IOException { | |||
this.flags = flags; | |||
int offset = 0; | |||
this.version = LittleEndian.getUInt(dataBytes, offset); offset += LittleEndian.INT_SIZE; | |||
this.emfPlusFlags = LittleEndian.getUInt(dataBytes, offset); offset += LittleEndian.INT_SIZE; | |||
this.logicalDpiX = LittleEndian.getUInt(dataBytes, offset); offset += LittleEndian.INT_SIZE; | |||
this.logicalDpiY = LittleEndian.getUInt(dataBytes, offset); | |||
version = leis.readUInt(); | |||
// verify MetafileSignature (20 bits) == 0xDBC01 and | |||
// GraphicsVersion (12 bits) in (1 or 2) | |||
assert((version & 0xFFFFFA00) == 0xDBC01000L && ((version & 0x3FF) == 1 || (version & 0x3FF) == 2)); | |||
emfPlusFlags = leis.readUInt(); | |||
logicalDpiX = leis.readUInt(); | |||
logicalDpiY = leis.readUInt(); | |||
return 4* LittleEndianConsts.INT_SIZE; | |||
} | |||
public long getVersion() { | |||
@@ -79,4 +84,4 @@ public class HemfPlusHeader implements HemfPlusRecord { | |||
", logicalDpiY=" + logicalDpiY + | |||
'}'; | |||
} | |||
} | |||
} |
@@ -15,12 +15,14 @@ | |||
limitations under the License. | |||
==================================================================== */ | |||
package org.apache.poi.hemf.hemfplus.record; | |||
package org.apache.poi.hemf.record.emfplus; | |||
import java.io.IOException; | |||
import org.apache.poi.hemf.record.emf.HemfRecordType; | |||
import org.apache.poi.util.Internal; | |||
import org.apache.poi.util.LittleEndianInputStream; | |||
@Internal | |||
public interface HemfPlusRecord { | |||
@@ -30,15 +32,17 @@ public interface HemfPlusRecord { | |||
int getFlags(); | |||
/** | |||
* Init record from stream | |||
* | |||
* @param dataBytes these are the bytes that start after the id, flags, record size | |||
* and go to the end of the record; they do not include any required padding | |||
* at the end. | |||
* @param recordId record type id | |||
* @param flags flags | |||
* @throws IOException, RecordFormatException | |||
* @param leis the little endian input stream | |||
* @param dataSize the size limit for this record | |||
* @param recordId the id of the {@link HemfPlusRecordType} | |||
* @param flags the record flags | |||
* | |||
* @return count of processed bytes | |||
* | |||
* @throws IOException when the inputstream is malformed | |||
*/ | |||
void init(byte[] dataBytes, int recordId, int flags) throws IOException; | |||
long init(LittleEndianInputStream leis, long dataSize, long recordId, int flags) throws IOException; | |||
} |
@@ -0,0 +1,98 @@ | |||
/* ==================================================================== | |||
Licensed to the Apache Software Foundation (ASF) under one or more | |||
contributor license agreements. See the NOTICE file distributed with | |||
this work for additional information regarding copyright ownership. | |||
The ASF licenses this file to You under the Apache License, Version 2.0 | |||
(the "License"); you may not use this file except in compliance with | |||
the License. You may obtain a copy of the License at | |||
http://www.apache.org/licenses/LICENSE-2.0 | |||
Unless required by applicable law or agreed to in writing, software | |||
distributed under the License is distributed on an "AS IS" BASIS, | |||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
See the License for the specific language governing permissions and | |||
limitations under the License. | |||
==================================================================== */ | |||
package org.apache.poi.hemf.record.emfplus; | |||
import java.io.IOException; | |||
import java.util.Iterator; | |||
import org.apache.poi.util.LittleEndianInputStream; | |||
import org.apache.poi.util.RecordFormatException; | |||
public class HemfPlusRecordIterator implements Iterator<HemfPlusRecord> { | |||
private final LittleEndianInputStream leis; | |||
private final int startIdx; | |||
private final int limit; | |||
private HemfPlusRecord currentRecord; | |||
public HemfPlusRecordIterator(LittleEndianInputStream leis) { | |||
this(leis, -1); | |||
} | |||
public HemfPlusRecordIterator(LittleEndianInputStream leis, int limit) { | |||
this.leis = leis; | |||
this.limit = limit; | |||
startIdx = leis.getReadIndex(); | |||
//queue the first non-header record | |||
currentRecord = _next(); | |||
} | |||
@Override | |||
public boolean hasNext() { | |||
return currentRecord != null; | |||
} | |||
@Override | |||
public HemfPlusRecord next() { | |||
HemfPlusRecord toReturn = currentRecord; | |||
final boolean isEOF = (limit == -1 || leis.getReadIndex()-startIdx >= limit); | |||
// (currentRecord instanceof HemfPlusMisc.EmfEof) | |||
currentRecord = isEOF ? null : _next(); | |||
return toReturn; | |||
} | |||
private HemfPlusRecord _next() { | |||
if (currentRecord != null && HemfPlusRecordType.eof == currentRecord.getRecordType()) { | |||
return null; | |||
} | |||
// A 16-bit unsigned integer that identifies this record type | |||
int recordId = leis.readUShort(); | |||
// A 16-bit unsigned integer that provides information about how the operation is | |||
// to be performed, and about the structure of the record. | |||
int flags = leis.readUShort(); | |||
// A 32-bit unsigned integer that specifies the 32-bit-aligned size of the entire | |||
// record in bytes, including the 12-byte record header and record-specific data. | |||
int recordSize = (int)leis.readUInt(); | |||
// A 32-bit unsigned integer that specifies the 32-bit-aligned number of bytes of data | |||
// in the record-specific data that follows. This number does not include the size of | |||
// the invariant part of this record. | |||
int dataSize = (int)leis.readUInt(); | |||
HemfPlusRecordType type = HemfPlusRecordType.getById(recordId); | |||
if (type == null) { | |||
throw new RecordFormatException("Undefined record of type:"+recordId); | |||
} | |||
final HemfPlusRecord record = type.constructor.get(); | |||
try { | |||
long readBytes = record.init(leis, dataSize, recordId, flags); | |||
assert (readBytes <= recordSize-12); | |||
leis.skipFully((int)(recordSize-12-readBytes)); | |||
} catch (IOException e) { | |||
throw new RecordFormatException(e); | |||
} | |||
return record; | |||
} | |||
@Override | |||
public void remove() { | |||
throw new UnsupportedOperationException("Remove not supported"); | |||
} | |||
} |
@@ -0,0 +1,100 @@ | |||
/* ==================================================================== | |||
Licensed to the Apache Software Foundation (ASF) under one or more | |||
contributor license agreements. See the NOTICE file distributed with | |||
this work for additional information regarding copyright ownership. | |||
The ASF licenses this file to You under the Apache License, Version 2.0 | |||
(the "License"); you may not use this file except in compliance with | |||
the License. You may obtain a copy of the License at | |||
http://www.apache.org/licenses/LICENSE-2.0 | |||
Unless required by applicable law or agreed to in writing, software | |||
distributed under the License is distributed on an "AS IS" BASIS, | |||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
See the License for the specific language governing permissions and | |||
limitations under the License. | |||
==================================================================== */ | |||
package org.apache.poi.hemf.record.emfplus; | |||
import java.util.function.Supplier; | |||
import org.apache.poi.util.Internal; | |||
@Internal | |||
public enum HemfPlusRecordType { | |||
header(0x4001, HemfPlusHeader::new), | |||
eof(0x4002, UnimplementedHemfPlusRecord::new), | |||
comment(0x4003, UnimplementedHemfPlusRecord::new), | |||
getDC(0x4004, UnimplementedHemfPlusRecord::new), | |||
multiFormatStart(0x4005, UnimplementedHemfPlusRecord::new), | |||
multiFormatSection(0x4006, UnimplementedHemfPlusRecord::new), | |||
multiFormatEnd(0x4007, UnimplementedHemfPlusRecord::new), | |||
object(0x4008, UnimplementedHemfPlusRecord::new), | |||
clear(0x4009, UnimplementedHemfPlusRecord::new), | |||
fillRects(0x400A, UnimplementedHemfPlusRecord::new), | |||
drawRects(0x400B, UnimplementedHemfPlusRecord::new), | |||
fillPolygon(0x400C, UnimplementedHemfPlusRecord::new), | |||
drawLines(0x400D, UnimplementedHemfPlusRecord::new), | |||
fillEllipse(0x400E, UnimplementedHemfPlusRecord::new), | |||
drawEllipse(0x400F, UnimplementedHemfPlusRecord::new), | |||
fillPie(0x4010, UnimplementedHemfPlusRecord::new), | |||
drawPie(0x4011, UnimplementedHemfPlusRecord::new), | |||
drawArc(0x4012, UnimplementedHemfPlusRecord::new), | |||
fillRegion(0x4013, UnimplementedHemfPlusRecord::new), | |||
fillPath(0x4014, UnimplementedHemfPlusRecord::new), | |||
drawPath(0x4015, UnimplementedHemfPlusRecord::new), | |||
fillClosedCurve(0x4016, UnimplementedHemfPlusRecord::new), | |||
drawClosedCurve(0x4017, UnimplementedHemfPlusRecord::new), | |||
drawCurve(0x4018, UnimplementedHemfPlusRecord::new), | |||
drawBeziers(0x4019, UnimplementedHemfPlusRecord::new), | |||
drawImage(0x401A, UnimplementedHemfPlusRecord::new), | |||
drawImagePoints(0x401B, UnimplementedHemfPlusRecord::new), | |||
drawString(0x401C, UnimplementedHemfPlusRecord::new), | |||
setRenderingOrigin(0x401D, UnimplementedHemfPlusRecord::new), | |||
setAntiAliasMode(0x401E, UnimplementedHemfPlusRecord::new), | |||
setTextRenderingHint(0x401F, UnimplementedHemfPlusRecord::new), | |||
setTextContrast(0x4020, UnimplementedHemfPlusRecord::new), | |||
setInterpolationMode(0x4021, UnimplementedHemfPlusRecord::new), | |||
setPixelOffsetMode(0x4022, UnimplementedHemfPlusRecord::new), | |||
setComositingMode(0x4023, UnimplementedHemfPlusRecord::new), | |||
setCompositingQuality(0x4024, UnimplementedHemfPlusRecord::new), | |||
save(0x4025, UnimplementedHemfPlusRecord::new), | |||
restore(0x4026, UnimplementedHemfPlusRecord::new), | |||
beginContainer(0x4027, UnimplementedHemfPlusRecord::new), | |||
beginContainerNoParams(0x428, UnimplementedHemfPlusRecord::new), | |||
endContainer(0x4029, UnimplementedHemfPlusRecord::new), | |||
setWorldTransform(0x402A, UnimplementedHemfPlusRecord::new), | |||
resetWorldTransform(0x402B, UnimplementedHemfPlusRecord::new), | |||
multiplyWorldTransform(0x402C, UnimplementedHemfPlusRecord::new), | |||
translateWorldTransform(0x402D, UnimplementedHemfPlusRecord::new), | |||
scaleWorldTransform(0x402E, UnimplementedHemfPlusRecord::new), | |||
rotateWorldTransform(0x402F, UnimplementedHemfPlusRecord::new), | |||
setPageTransform(0x4030, UnimplementedHemfPlusRecord::new), | |||
resetClip(0x4031, UnimplementedHemfPlusRecord::new), | |||
setClipRect(0x4032, UnimplementedHemfPlusRecord::new), | |||
setClipRegion(0x4033, UnimplementedHemfPlusRecord::new), | |||
setClipPath(0x4034, UnimplementedHemfPlusRecord::new), | |||
offsetClip(0x4035, UnimplementedHemfPlusRecord::new), | |||
drawDriverstring(0x4036, UnimplementedHemfPlusRecord::new), | |||
strokeFillPath(0x4037, UnimplementedHemfPlusRecord::new), | |||
serializableObject(0x4038, UnimplementedHemfPlusRecord::new), | |||
setTSGraphics(0x4039, UnimplementedHemfPlusRecord::new), | |||
setTSClip(0x403A, UnimplementedHemfPlusRecord::new); | |||
public final long id; | |||
public final Supplier<? extends HemfPlusRecord> constructor; | |||
HemfPlusRecordType(long id, Supplier<? extends HemfPlusRecord> constructor) { | |||
this.id = id; | |||
this.constructor = constructor; | |||
} | |||
public static HemfPlusRecordType getById(long id) { | |||
for (HemfPlusRecordType wrt : values()) { | |||
if (wrt.id == id) return wrt; | |||
} | |||
return null; | |||
} | |||
} |
@@ -15,17 +15,21 @@ | |||
limitations under the License. | |||
==================================================================== */ | |||
package org.apache.poi.hemf.hemfplus.record; | |||
package org.apache.poi.hemf.record.emfplus; | |||
import java.io.IOException; | |||
import org.apache.poi.util.IOUtils; | |||
import org.apache.poi.util.Internal; | |||
import org.apache.poi.util.LittleEndianInputStream; | |||
@Internal | |||
public class UnimplementedHemfPlusRecord implements HemfPlusRecord { | |||
private int recordId; | |||
private static final int MAX_RECORD_LENGTH = 1_000_000; | |||
private long recordId; | |||
private int flags; | |||
private byte[] recordBytes; | |||
@@ -40,14 +44,16 @@ public class UnimplementedHemfPlusRecord implements HemfPlusRecord { | |||
} | |||
@Override | |||
public void init(byte[] recordBytes, int recordId, int flags) throws IOException { | |||
public long init(LittleEndianInputStream leis, long dataSize, long recordId, int flags) throws IOException { | |||
this.recordId = recordId; | |||
this.flags = flags; | |||
this.recordBytes = recordBytes; | |||
recordBytes = IOUtils.safelyAllocate(dataSize, MAX_RECORD_LENGTH); | |||
leis.readFully(recordBytes); | |||
return recordBytes.length; | |||
} | |||
public byte[] getRecordBytes() { | |||
//should probably defensively return a copy. | |||
return recordBytes; | |||
} | |||
} | |||
} |
@@ -0,0 +1,77 @@ | |||
/* ==================================================================== | |||
Licensed to the Apache Software Foundation (ASF) under one or more | |||
contributor license agreements. See the NOTICE file distributed with | |||
this work for additional information regarding copyright ownership. | |||
The ASF licenses this file to You under the Apache License, Version 2.0 | |||
(the "License"); you may not use this file except in compliance with | |||
the License. You may obtain a copy of the License at | |||
http://www.apache.org/licenses/LICENSE-2.0 | |||
Unless required by applicable law or agreed to in writing, software | |||
distributed under the License is distributed on an "AS IS" BASIS, | |||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
See the License for the specific language governing permissions and | |||
limitations under the License. | |||
==================================================================== */ | |||
package org.apache.poi.hemf.usermodel; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.util.ArrayList; | |||
import java.util.Iterator; | |||
import java.util.List; | |||
import java.util.Spliterator; | |||
import java.util.function.Consumer; | |||
import org.apache.poi.hemf.record.emf.HemfHeader; | |||
import org.apache.poi.hemf.record.emf.HemfRecord; | |||
import org.apache.poi.hemf.record.emf.HemfRecordIterator; | |||
import org.apache.poi.util.Internal; | |||
import org.apache.poi.util.LittleEndianInputStream; | |||
/** | |||
* Read-only EMF extractor. Lots remain | |||
*/ | |||
@Internal | |||
public class HemfPicture implements Iterable<HemfRecord> { | |||
private final LittleEndianInputStream stream; | |||
private final List<HemfRecord> records = new ArrayList<>(); | |||
public HemfPicture(InputStream is) throws IOException { | |||
this(new LittleEndianInputStream(is)); | |||
} | |||
public HemfPicture(LittleEndianInputStream is) throws IOException { | |||
stream = is; | |||
} | |||
public HemfHeader getHeader() { | |||
return (HemfHeader)getRecords().get(0); | |||
} | |||
public List<HemfRecord> getRecords() { | |||
if (records.isEmpty()) { | |||
new HemfRecordIterator(stream).forEachRemaining(records::add); | |||
} | |||
return records; | |||
} | |||
@Override | |||
public Iterator<HemfRecord> iterator() { | |||
return getRecords().iterator(); | |||
} | |||
@Override | |||
public Spliterator<HemfRecord> spliterator() { | |||
return getRecords().spliterator(); | |||
} | |||
@Override | |||
public void forEach(Consumer<? super HemfRecord> action) { | |||
getRecords().forEach(action); | |||
} | |||
} |
@@ -43,32 +43,48 @@ public class HwmfDrawProperties { | |||
private final Rectangle2D window; | |||
private Rectangle2D viewport; | |||
private final Point2D location; | |||
private HwmfMapMode mapMode = HwmfMapMode.MM_ANISOTROPIC; | |||
private HwmfColorRef backgroundColor = new HwmfColorRef(Color.BLACK); | |||
private HwmfBrushStyle brushStyle = HwmfBrushStyle.BS_SOLID; | |||
private HwmfColorRef brushColor = new HwmfColorRef(Color.BLACK); | |||
private HwmfHatchStyle brushHatch = HwmfHatchStyle.HS_HORIZONTAL; | |||
private HwmfMapMode mapMode; | |||
private HwmfColorRef backgroundColor; | |||
private HwmfBrushStyle brushStyle; | |||
private HwmfColorRef brushColor; | |||
private HwmfHatchStyle brushHatch; | |||
private BufferedImage brushBitmap; | |||
private double penWidth = 1; | |||
private HwmfPenStyle penStyle = HwmfPenStyle.valueOf(0); | |||
private HwmfColorRef penColor = new HwmfColorRef(Color.BLACK); | |||
private double penMiterLimit = 10; | |||
private HwmfBkMode bkMode = HwmfBkMode.OPAQUE; | |||
private HwmfPolyfillMode polyfillMode = HwmfPolyfillMode.WINDING; | |||
private double penWidth; | |||
private HwmfPenStyle penStyle; | |||
private HwmfColorRef penColor; | |||
private double penMiterLimit; | |||
private HwmfBkMode bkMode; | |||
private HwmfPolyfillMode polyfillMode; | |||
private Shape region; | |||
private List<PaletteEntry> palette; | |||
private int paletteOffset; | |||
private HwmfFont font; | |||
private HwmfColorRef textColor = new HwmfColorRef(Color.BLACK); | |||
private HwmfTextAlignment textAlignLatin = HwmfTextAlignment.LEFT; | |||
private HwmfTextVerticalAlignment textVAlignLatin = HwmfTextVerticalAlignment.TOP; | |||
private HwmfTextAlignment textAlignAsian = HwmfTextAlignment.RIGHT; | |||
private HwmfTextVerticalAlignment textVAlignAsian = HwmfTextVerticalAlignment.TOP; | |||
private HwmfColorRef textColor; | |||
private HwmfTextAlignment textAlignLatin; | |||
private HwmfTextVerticalAlignment textVAlignLatin; | |||
private HwmfTextAlignment textAlignAsian; | |||
private HwmfTextVerticalAlignment textVAlignAsian; | |||
public HwmfDrawProperties() { | |||
window = new Rectangle2D.Double(0, 0, 1, 1); | |||
viewport = null; | |||
location = new Point2D.Double(0,0); | |||
mapMode = HwmfMapMode.MM_ANISOTROPIC; | |||
backgroundColor = new HwmfColorRef(Color.BLACK); | |||
brushStyle = HwmfBrushStyle.BS_SOLID; | |||
brushColor = new HwmfColorRef(Color.BLACK); | |||
brushHatch = HwmfHatchStyle.HS_HORIZONTAL; | |||
penWidth = 1; | |||
penStyle = HwmfPenStyle.valueOf(0); | |||
penColor = new HwmfColorRef(Color.BLACK); | |||
penMiterLimit = 10; | |||
bkMode = HwmfBkMode.OPAQUE; | |||
polyfillMode = HwmfPolyfillMode.WINDING; | |||
textColor = new HwmfColorRef(Color.BLACK); | |||
textAlignLatin = HwmfTextAlignment.LEFT; | |||
textVAlignLatin = HwmfTextVerticalAlignment.TOP; | |||
textAlignAsian = HwmfTextAlignment.RIGHT; | |||
textVAlignAsian = HwmfTextVerticalAlignment.TOP; | |||
} | |||
public HwmfDrawProperties(HwmfDrawProperties other) { | |||
@@ -86,7 +102,7 @@ public class HwmfDrawProperties { | |||
WritableRaster raster = other.brushBitmap.copyData(null); | |||
this.brushBitmap = new BufferedImage(cm, raster, isAlphaPremultiplied, null); | |||
} | |||
this.penWidth = 1; | |||
this.penWidth = other.penWidth; | |||
this.penStyle = (other.penStyle == null) ? null : other.penStyle.clone(); | |||
this.penColor = (other.penColor == null) ? null : other.penColor.clone(); | |||
this.penMiterLimit = other.penMiterLimit; | |||
@@ -101,6 +117,10 @@ public class HwmfDrawProperties { | |||
this.paletteOffset = other.paletteOffset; | |||
this.font = other.font; | |||
this.textColor = (other.textColor == null) ? null : other.textColor.clone(); | |||
this.textAlignLatin = other.textAlignLatin; | |||
this.textVAlignLatin = other.textVAlignLatin; | |||
this.textAlignAsian = other.textAlignAsian; | |||
this.textVAlignAsian = other.textVAlignAsian; | |||
} | |||
public void setViewportExt(double width, double height) { | |||
@@ -149,6 +169,10 @@ public class HwmfDrawProperties { | |||
location.setLocation(x, y); | |||
} | |||
public void setLocation(Point2D point) { | |||
location.setLocation(point); | |||
} | |||
public Point2D getLocation() { | |||
return (Point2D)location.clone(); | |||
} |
@@ -71,7 +71,7 @@ public enum HwmfBrushStyle { | |||
this.flag = flag; | |||
} | |||
static HwmfBrushStyle valueOf(int flag) { | |||
public static HwmfBrushStyle valueOf(int flag) { | |||
for (HwmfBrushStyle bs : values()) { | |||
if (bs.flag == flag) return bs; | |||
} |
@@ -41,31 +41,21 @@ public class HwmfDraw { | |||
*/ | |||
public static class WmfMoveTo implements HwmfRecord { | |||
/** | |||
* A 16-bit signed integer that defines the y-coordinate, in logical units. | |||
*/ | |||
private int y; | |||
/** | |||
* A 16-bit signed integer that defines the x-coordinate, in logical units. | |||
*/ | |||
private int x; | |||
protected final Point2D point = new Point2D.Double(); | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.moveTo; | |||
} | |||
@Override | |||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { | |||
y = leis.readShort(); | |||
x = leis.readShort(); | |||
return 2*LittleEndianConsts.SHORT_SIZE; | |||
return readPointS(leis, point); | |||
} | |||
@Override | |||
public void draw(HwmfGraphics ctx) { | |||
ctx.getProperties().setLocation(x, y); | |||
ctx.getProperties().setLocation(point); | |||
} | |||
} | |||
@@ -75,36 +65,24 @@ public class HwmfDraw { | |||
*/ | |||
public static class WmfLineTo implements HwmfRecord { | |||
/** | |||
* A 16-bit signed integer that defines the vertical component of the drawing | |||
* destination position, in logical units. | |||
*/ | |||
private int y; | |||
/** | |||
* A 16-bit signed integer that defines the horizontal component of the drawing | |||
* destination position, in logical units. | |||
*/ | |||
private int x; | |||
protected final Point2D point = new Point2D.Double(); | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.lineTo; | |||
} | |||
@Override | |||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { | |||
y = leis.readShort(); | |||
x = leis.readShort(); | |||
return 2*LittleEndianConsts.SHORT_SIZE; | |||
return readPointS(leis, point); | |||
} | |||
@Override | |||
public void draw(HwmfGraphics ctx) { | |||
Point2D start = ctx.getProperties().getLocation(); | |||
Line2D line = new Line2D.Double(start.getX(), start.getY(), x, y); | |||
Line2D line = new Line2D.Double(start, point); | |||
ctx.draw(line); | |||
ctx.getProperties().setLocation(x, y); | |||
ctx.getProperties().setLocation(point); | |||
} | |||
} | |||
@@ -115,10 +93,10 @@ public class HwmfDraw { | |||
*/ | |||
public static class WmfPolygon implements HwmfRecord { | |||
private Path2D poly = new Path2D.Double(); | |||
protected Path2D poly = new Path2D.Double(); | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.polygon; | |||
} | |||
@@ -146,16 +124,26 @@ public class HwmfDraw { | |||
@Override | |||
public void draw(HwmfGraphics ctx) { | |||
Path2D shape = getShape(); | |||
// shape.closePath(); | |||
Path2D p = (Path2D)shape.clone(); | |||
Path2D p = getShape(ctx); | |||
// don't close the path | |||
p.setWindingRule(getWindingRule(ctx)); | |||
ctx.fill(p); | |||
if (isFill()) { | |||
ctx.fill(p); | |||
} else { | |||
ctx.draw(p); | |||
} | |||
} | |||
protected Path2D getShape() { | |||
protected Path2D getShape(HwmfGraphics ctx) { | |||
return (Path2D)poly.clone(); | |||
} | |||
/** | |||
* @return true, if the shape should be filled | |||
*/ | |||
protected boolean isFill() { | |||
return true; | |||
} | |||
} | |||
/** | |||
@@ -165,16 +153,13 @@ public class HwmfDraw { | |||
public static class WmfPolyline extends WmfPolygon { | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.polyline; | |||
} | |||
@Override | |||
public void draw(HwmfGraphics ctx) { | |||
Path2D shape = getShape(); | |||
Path2D p = (Path2D)shape.clone(); | |||
p.setWindingRule(getWindingRule(ctx)); | |||
ctx.draw(p); | |||
protected boolean isFill() { | |||
return false; | |||
} | |||
} | |||
@@ -184,48 +169,21 @@ public class HwmfDraw { | |||
* are defined in the playback device context. | |||
*/ | |||
public static class WmfEllipse implements HwmfRecord { | |||
/** | |||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of | |||
* the lower-right corner of the bounding rectangle. | |||
*/ | |||
private int bottomRect; | |||
/** | |||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of | |||
* the lower-right corner of the bounding rectangle. | |||
*/ | |||
private int rightRect; | |||
/** | |||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the | |||
* upper-left corner of the bounding rectangle. | |||
*/ | |||
private int topRect; | |||
/** | |||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of | |||
* the upper-left corner of the bounding rectangle. | |||
*/ | |||
private int leftRect; | |||
protected final Rectangle2D bounds = new Rectangle2D.Double(); | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.ellipse; | |||
} | |||
@Override | |||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { | |||
bottomRect = leis.readShort(); | |||
rightRect = leis.readShort(); | |||
topRect = leis.readShort(); | |||
leftRect = leis.readShort(); | |||
return 4*LittleEndianConsts.SHORT_SIZE; | |||
return readBounds(leis, bounds); | |||
} | |||
@Override | |||
public void draw(HwmfGraphics ctx) { | |||
int x = Math.min(leftRect, rightRect); | |||
int y = Math.min(topRect, bottomRect); | |||
int w = Math.abs(leftRect - rightRect - 1); | |||
int h = Math.abs(topRect - bottomRect - 1); | |||
Shape s = new Ellipse2D.Double(x, y, w, h); | |||
Shape s = new Ellipse2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight()); | |||
ctx.fill(s); | |||
} | |||
} | |||
@@ -239,25 +197,25 @@ public class HwmfDraw { | |||
* A 16-bit unsigned integer used to index into the WMF Object Table to get | |||
* the region to be framed. | |||
*/ | |||
private int regionIndex; | |||
protected int regionIndex; | |||
/** | |||
* A 16-bit unsigned integer used to index into the WMF Object Table to get the | |||
* Brush to use for filling the region. | |||
*/ | |||
private int brushIndex; | |||
protected int brushIndex; | |||
/** | |||
* A 16-bit signed integer that defines the height, in logical units, of the | |||
* region frame. | |||
*/ | |||
private int height; | |||
protected int height; | |||
/** | |||
* A 16-bit signed integer that defines the width, in logical units, of the | |||
* region frame. | |||
*/ | |||
private int width; | |||
protected int width; | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.frameRegion; | |||
} | |||
@@ -293,10 +251,10 @@ public class HwmfDraw { | |||
*/ | |||
public static class WmfPolyPolygon implements HwmfRecord { | |||
private List<Path2D> polyList = new ArrayList<>(); | |||
protected List<Path2D> polyList = new ArrayList<>(); | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.polyPolygon; | |||
} | |||
@@ -361,7 +319,20 @@ public class HwmfDraw { | |||
area.exclusiveOr(newArea); | |||
} | |||
} | |||
ctx.fill(area); | |||
if (isFill()) { | |||
ctx.fill(area); | |||
} else { | |||
ctx.draw(area); | |||
} | |||
} | |||
/** | |||
* @return true, if the shape should be filled | |||
*/ | |||
protected boolean isFill() { | |||
return true; | |||
} | |||
} | |||
@@ -370,92 +341,50 @@ public class HwmfDraw { | |||
* filled by using the brush that are defined in the playback device context. | |||
*/ | |||
public static class WmfRectangle implements HwmfRecord { | |||
/** | |||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of | |||
* the lower-right corner of the rectangle. | |||
*/ | |||
private int bottomRect; | |||
/** | |||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of | |||
* the lower-right corner of the rectangle. | |||
*/ | |||
private int rightRect; | |||
/** | |||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the | |||
* upper-left corner of the rectangle. | |||
*/ | |||
private int topRect; | |||
/** | |||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of | |||
* the upper-left corner of the rectangle. | |||
*/ | |||
private int leftRect; | |||
protected final Rectangle2D bounds = new Rectangle2D.Double(); | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.frameRegion; | |||
} | |||
@Override | |||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { | |||
bottomRect = leis.readShort(); | |||
rightRect = leis.readShort(); | |||
topRect = leis.readShort(); | |||
leftRect = leis.readShort(); | |||
return 4*LittleEndianConsts.SHORT_SIZE; | |||
return readBounds(leis, bounds); | |||
} | |||
@Override | |||
public void draw(HwmfGraphics ctx) { | |||
int x = Math.min(leftRect, rightRect); | |||
int y = Math.min(topRect, bottomRect); | |||
int w = Math.abs(leftRect - rightRect - 1); | |||
int h = Math.abs(topRect - bottomRect - 1); | |||
Shape s = new Rectangle2D.Double(x, y, w, h); | |||
ctx.fill(s); | |||
ctx.fill(bounds); | |||
} | |||
} | |||
/** | |||
* The META_RECTANGLE record paints a rectangle. The rectangle is outlined by using the pen and | |||
* filled by using the brush that are defined in the playback device context. | |||
* The META_SETPIXEL record sets the pixel at the specified coordinates to the specified color. | |||
*/ | |||
public static class WmfSetPixel implements HwmfRecord { | |||
/** | |||
* A ColorRef Object that defines the color value. | |||
*/ | |||
HwmfColorRef colorRef; | |||
protected final HwmfColorRef colorRef = new HwmfColorRef(); | |||
/** | |||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the point | |||
* to be set. | |||
*/ | |||
private int y; | |||
/** | |||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the point | |||
* to be set. | |||
*/ | |||
private int x; | |||
protected final Point2D point = new Point2D.Double(); | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.setPixel; | |||
} | |||
@Override | |||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { | |||
colorRef = new HwmfColorRef(); | |||
int size = colorRef.init(leis); | |||
y = leis.readShort(); | |||
x = leis.readShort(); | |||
return 2*LittleEndianConsts.SHORT_SIZE+size; | |||
return size+ readPointS(leis, point); | |||
} | |||
@Override | |||
public void draw(HwmfGraphics ctx) { | |||
Shape s = new Rectangle2D.Double(x, y, 1, 1); | |||
Shape s = new Rectangle2D.Double(point.getX(), point.getY(), 1, 1); | |||
ctx.fill(s); | |||
} | |||
} | |||
@@ -469,41 +398,19 @@ public class HwmfDraw { | |||
* A 16-bit signed integer that defines the height, in logical coordinates, of the | |||
* ellipse used to draw the rounded corners. | |||
*/ | |||
private int height; | |||
protected int height; | |||
/** | |||
* A 16-bit signed integer that defines the width, in logical coordinates, of the | |||
* ellipse used to draw the rounded corners. | |||
*/ | |||
private int width; | |||
protected int width; | |||
/** | |||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of | |||
* the lower-right corner of the rectangle. | |||
*/ | |||
private int bottomRect; | |||
/** | |||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of | |||
* the lower-right corner of the rectangle. | |||
*/ | |||
private int rightRect; | |||
/** | |||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the | |||
* upper-left corner of the rectangle. | |||
*/ | |||
private int topRect; | |||
/** | |||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of | |||
* the upper-left corner of the rectangle. | |||
*/ | |||
private int leftRect; | |||
protected final Rectangle2D bounds = new Rectangle2D.Double(); | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.roundRect; | |||
} | |||
@@ -511,20 +418,12 @@ public class HwmfDraw { | |||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { | |||
height = leis.readShort(); | |||
width = leis.readShort(); | |||
bottomRect = leis.readShort(); | |||
rightRect = leis.readShort(); | |||
topRect = leis.readShort(); | |||
leftRect = leis.readShort(); | |||
return 6*LittleEndianConsts.SHORT_SIZE; | |||
return 2*LittleEndianConsts.SHORT_SIZE+readBounds(leis, bounds); | |||
} | |||
@Override | |||
public void draw(HwmfGraphics ctx) { | |||
int x = Math.min(leftRect, rightRect); | |||
int y = Math.min(topRect, bottomRect); | |||
int w = Math.abs(leftRect - rightRect - 1); | |||
int h = Math.abs(topRect - bottomRect - 1); | |||
Shape s = new RoundRectangle2D.Double(x, y, w, h, width, height); | |||
Shape s = new RoundRectangle2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), width, height); | |||
ctx.fill(s); | |||
} | |||
} | |||
@@ -534,73 +433,34 @@ public class HwmfDraw { | |||
* The META_ARC record draws an elliptical arc. | |||
*/ | |||
public static class WmfArc implements HwmfRecord { | |||
/** | |||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of | |||
* the ending point of the radial line defining the ending point of the arc. | |||
*/ | |||
private int yEndArc; | |||
/** | |||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of | |||
* the ending point of the radial line defining the ending point of the arc. | |||
*/ | |||
private int xEndArc; | |||
/** | |||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of | |||
* the ending point of the radial line defining the starting point of the arc. | |||
*/ | |||
private int yStartArc; | |||
/** | |||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of | |||
* the ending point of the radial line defining the starting point of the arc. | |||
*/ | |||
private int xStartArc; | |||
/** | |||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of | |||
* the lower-right corner of the bounding rectangle. | |||
*/ | |||
private int bottomRect; | |||
/** | |||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of | |||
* the lower-right corner of the bounding rectangle. | |||
*/ | |||
private int rightRect; | |||
/** | |||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the | |||
* upper-left corner of the bounding rectangle. | |||
*/ | |||
private int topRect; | |||
/** | |||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of | |||
* the upper-left corner of the bounding rectangle. | |||
*/ | |||
private int leftRect; | |||
/** starting point of the arc */ | |||
protected final Point2D startPoint = new Point2D.Double(); | |||
/** ending point of the arc */ | |||
protected final Point2D endPoint = new Point2D.Double(); | |||
/** the bounding rectangle */ | |||
protected final Rectangle2D bounds = new Rectangle2D.Double(); | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.arc; | |||
} | |||
@Override | |||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { | |||
yEndArc = leis.readShort(); | |||
xEndArc = leis.readShort(); | |||
yStartArc = leis.readShort(); | |||
xStartArc = leis.readShort(); | |||
bottomRect = leis.readShort(); | |||
rightRect = leis.readShort(); | |||
topRect = leis.readShort(); | |||
leftRect = leis.readShort(); | |||
readPointS(leis, endPoint); | |||
readPointS(leis, startPoint); | |||
readBounds(leis, bounds); | |||
return 8*LittleEndianConsts.SHORT_SIZE; | |||
} | |||
@Override | |||
public void draw(HwmfGraphics ctx) { | |||
int x = Math.min(leftRect, rightRect); | |||
int y = Math.min(topRect, bottomRect); | |||
int w = Math.abs(leftRect - rightRect - 1); | |||
int h = Math.abs(topRect - bottomRect - 1); | |||
double startAngle = Math.toDegrees(Math.atan2(-(yStartArc - (topRect + h / 2.)), xStartArc - (leftRect + w / 2.))); | |||
double endAngle = Math.toDegrees(Math.atan2(-(yEndArc - (topRect + h / 2.)), xEndArc - (leftRect + w / 2.))); | |||
double startAngle = Math.toDegrees(Math.atan2(-(startPoint.getY() - bounds.getCenterY()), startPoint.getX() - bounds.getCenterX())); | |||
double endAngle = Math.toDegrees(Math.atan2(-(endPoint.getY() - bounds.getCenterY()), endPoint.getX() - bounds.getCenterX())); | |||
double arcAngle = (endAngle - startAngle) + (endAngle - startAngle > 0 ? 0 : 360); | |||
if (startAngle < 0) { | |||
startAngle += 360; | |||
@@ -608,7 +468,7 @@ public class HwmfDraw { | |||
boolean fillShape; | |||
int arcClosure; | |||
switch (getRecordType()) { | |||
switch (getWmfRecordType()) { | |||
default: | |||
case arc: | |||
arcClosure = Arc2D.OPEN; | |||
@@ -624,7 +484,7 @@ public class HwmfDraw { | |||
break; | |||
} | |||
Shape s = new Arc2D.Double(x, y, w, h, startAngle, arcAngle, arcClosure); | |||
Shape s = new Arc2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), startAngle, arcAngle, arcClosure); | |||
if (fillShape) { | |||
ctx.fill(s); | |||
} else { | |||
@@ -641,7 +501,7 @@ public class HwmfDraw { | |||
public static class WmfPie extends WmfArc { | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.pie; | |||
} | |||
} | |||
@@ -654,7 +514,7 @@ public class HwmfDraw { | |||
public static class WmfChord extends WmfArc { | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.chord; | |||
} | |||
} | |||
@@ -673,10 +533,10 @@ public class HwmfDraw { | |||
* A 16-bit unsigned integer used to index into the WMF Object Table to | |||
* get the object to be selected. | |||
*/ | |||
private int objectIndex; | |||
protected int objectIndex; | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.selectObject; | |||
} | |||
@@ -695,4 +555,50 @@ public class HwmfDraw { | |||
private static int getWindingRule(HwmfGraphics ctx) { | |||
return ctx.getProperties().getPolyfillMode().awtFlag; | |||
} | |||
} | |||
static int readBounds(LittleEndianInputStream leis, Rectangle2D bounds) { | |||
/** | |||
* The 16-bit signed integers that defines the corners of the bounding rectangle. | |||
*/ | |||
int bottom = leis.readShort(); | |||
int right = leis.readShort(); | |||
int top = leis.readShort(); | |||
int left = leis.readShort(); | |||
int x = Math.min(left, right); | |||
int y = Math.min(top, bottom); | |||
int w = Math.abs(left - right - 1); | |||
int h = Math.abs(top - bottom - 1); | |||
bounds.setRect(x, y, w, h); | |||
return 4 * LittleEndianConsts.SHORT_SIZE; | |||
} | |||
static int readRectS(LittleEndianInputStream leis, Rectangle2D bounds) { | |||
/** | |||
* The 16-bit signed integers that defines the corners of the bounding rectangle. | |||
*/ | |||
int left = leis.readShort(); | |||
int top = leis.readShort(); | |||
int right = leis.readShort(); | |||
int bottom = leis.readShort(); | |||
int x = Math.min(left, right); | |||
int y = Math.min(top, bottom); | |||
int w = Math.abs(left - right - 1); | |||
int h = Math.abs(top - bottom - 1); | |||
bounds.setRect(x, y, w, h); | |||
return 4 * LittleEndianConsts.SHORT_SIZE; | |||
} | |||
static int readPointS(LittleEndianInputStream leis, Point2D point) { | |||
/** a signed integer that defines the x/y-coordinate, in logical units. */ | |||
int y = leis.readShort(); | |||
int x = leis.readShort(); | |||
point.setLocation(x, y); | |||
return 2*LittleEndianConsts.SHORT_SIZE; | |||
} | |||
} |
@@ -185,7 +185,7 @@ public class HwmfEscape implements HwmfRecord { | |||
private byte escapeData[]; | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.escape; | |||
} | |||
@@ -17,8 +17,12 @@ | |||
package org.apache.poi.hwmf.record; | |||
import static org.apache.poi.hwmf.record.HwmfDraw.readPointS; | |||
import java.awt.Shape; | |||
import java.awt.geom.Path2D; | |||
import java.awt.geom.Point2D; | |||
import java.awt.geom.Rectangle2D; | |||
import java.awt.image.BufferedImage; | |||
import java.io.IOException; | |||
@@ -62,7 +66,7 @@ public class HwmfFill { | |||
this.flag = flag; | |||
} | |||
static ColorUsage valueOf(int flag) { | |||
public static ColorUsage valueOf(int flag) { | |||
for (ColorUsage bs : values()) { | |||
if (bs.flag == flag) return bs; | |||
} | |||
@@ -80,16 +84,16 @@ public class HwmfFill { | |||
* A 16-bit unsigned integer used to index into the WMF Object Table to get | |||
* the region to be filled. | |||
*/ | |||
private int regionIndex; | |||
protected int regionIndex; | |||
/** | |||
* A 16-bit unsigned integer used to index into the WMF Object Table to get the | |||
* brush to use for filling the region. | |||
*/ | |||
private int brushIndex; | |||
protected int brushIndex; | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.fillRegion; | |||
} | |||
@@ -125,7 +129,7 @@ public class HwmfFill { | |||
*/ | |||
int regionIndex; | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.paintRegion; | |||
} | |||
@@ -155,31 +159,21 @@ public class HwmfFill { | |||
/** | |||
* A 32-bit ColorRef Object that defines the color value. | |||
*/ | |||
private HwmfColorRef colorRef; | |||
/** | |||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the | |||
* point where filling is to start. | |||
*/ | |||
private int yStart; | |||
/** | |||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the | |||
* point where filling is to start. | |||
*/ | |||
private int xStart; | |||
protected final HwmfColorRef colorRef = new HwmfColorRef(); | |||
/** the point where filling is to start. */ | |||
protected final Point2D start = new Point2D.Double(); | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.floodFill; | |||
} | |||
@Override | |||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { | |||
colorRef = new HwmfColorRef(); | |||
int size = colorRef.init(leis); | |||
yStart = leis.readShort(); | |||
xStart = leis.readShort(); | |||
return size+2*LittleEndianConsts.SHORT_SIZE; | |||
size += readPointS(leis, start); | |||
return size; | |||
} | |||
@Override | |||
@@ -215,22 +209,22 @@ public class HwmfFill { | |||
this.awtFlag = awtFlag; | |||
} | |||
static HwmfPolyfillMode valueOf(int wmfFlag) { | |||
public static HwmfPolyfillMode valueOf(int wmfFlag) { | |||
for (HwmfPolyfillMode pm : values()) { | |||
if (pm.wmfFlag == wmfFlag) return pm; | |||
} | |||
return null; | |||
} | |||
} | |||
/** | |||
* A 16-bit unsigned integer that defines polygon fill mode. | |||
* An unsigned integer that defines polygon fill mode. | |||
* This MUST be one of the values: ALTERNATE = 0x0001, WINDING = 0x0002 | |||
*/ | |||
private HwmfPolyfillMode polyfillMode; | |||
protected HwmfPolyfillMode polyfillMode; | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.setPolyFillMode; | |||
} | |||
@@ -251,7 +245,7 @@ public class HwmfFill { | |||
* The META_EXTFLOODFILL record fills an area with the brush that is defined in | |||
* the playback device context. | |||
*/ | |||
public static class WmfExtFloodFill implements HwmfRecord { | |||
public static class WmfExtFloodFill extends WmfFloodFill { | |||
/** | |||
* A 16-bit unsigned integer that defines the fill operation to be performed. This | |||
@@ -266,38 +260,17 @@ public class HwmfFill { | |||
* Filling continues outward in all directions as long as the color is encountered. | |||
* This style is useful for filling areas with multicolored boundaries. | |||
*/ | |||
private int mode; | |||
/** | |||
* A 32-bit ColorRef Object that defines the color value. | |||
*/ | |||
private HwmfColorRef colorRef; | |||
/** | |||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the point | |||
* to be set. | |||
*/ | |||
private int y; | |||
/** | |||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the point | |||
* to be set. | |||
*/ | |||
private int x; | |||
protected int mode; | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.extFloodFill; | |||
} | |||
@Override | |||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { | |||
mode = leis.readUShort(); | |||
colorRef = new HwmfColorRef(); | |||
int size = colorRef.init(leis); | |||
y = leis.readShort(); | |||
x = leis.readShort(); | |||
return size+3*LittleEndianConsts.SHORT_SIZE; | |||
return super.init(leis, recordSize, recordFunction)+LittleEndianConsts.SHORT_SIZE; | |||
} | |||
@Override | |||
@@ -318,7 +291,7 @@ public class HwmfFill { | |||
private int region; | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.invertRegion; | |||
} | |||
@@ -348,30 +321,10 @@ public class HwmfFill { | |||
*/ | |||
private HwmfTernaryRasterOp rasterOperation; | |||
/** | |||
* A 16-bit signed integer that defines the height, in logical units, of the rectangle. | |||
*/ | |||
private int height; | |||
/** | |||
* A 16-bit signed integer that defines the width, in logical units, of the rectangle. | |||
*/ | |||
private int width; | |||
/** | |||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the | |||
* upper-left corner of the rectangle to be filled. | |||
*/ | |||
private int yLeft; | |||
/** | |||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the | |||
* upper-left corner of the rectangle to be filled. | |||
*/ | |||
private int xLeft; | |||
private final Rectangle2D bounds = new Rectangle2D.Double(); | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.patBlt; | |||
} | |||
@@ -383,12 +336,7 @@ public class HwmfFill { | |||
rasterOperation = HwmfTernaryRasterOp.valueOf(rasterOpIndex); | |||
assert(rasterOpCode == rasterOperation.opCode); | |||
height = leis.readShort(); | |||
width = leis.readShort(); | |||
yLeft = leis.readShort(); | |||
xLeft = leis.readShort(); | |||
return 6*LittleEndianConsts.SHORT_SIZE; | |||
return readBounds2(leis, bounds)+2*LittleEndianConsts.SHORT_SIZE; | |||
} | |||
@Override | |||
@@ -414,53 +362,22 @@ public class HwmfFill { | |||
* in the playback device context, and the destination pixels are to be combined to form the new | |||
* image. This code MUST be one of the values in the Ternary Raster Operation Enumeration | |||
*/ | |||
private HwmfTernaryRasterOp rasterOperation; | |||
/** | |||
* A 16-bit signed integer that defines the height, in logical units, of the source rectangle. | |||
*/ | |||
private int srcHeight; | |||
/** | |||
* A 16-bit signed integer that defines the width, in logical units, of the source rectangle. | |||
*/ | |||
private int srcWidth; | |||
/** | |||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the upper-left corner | |||
* of the source rectangle. | |||
*/ | |||
private int ySrc; | |||
/** | |||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the upper-left corner | |||
* of the source rectangle. | |||
*/ | |||
private int xSrc; | |||
/** | |||
* A 16-bit signed integer that defines the height, in logical units, of the destination rectangle. | |||
*/ | |||
private int destHeight; | |||
/** | |||
* A 16-bit signed integer that defines the width, in logical units, of the destination rectangle. | |||
*/ | |||
private int destWidth; | |||
/** | |||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the upper-left | |||
* corner of the destination rectangle. | |||
*/ | |||
private int yDest; | |||
/** | |||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the upper-left | |||
* corner of the destination rectangle. | |||
*/ | |||
private int xDest; | |||
protected HwmfTernaryRasterOp rasterOperation; | |||
/** the source rectangle */ | |||
protected final Rectangle2D srcBounds = new Rectangle2D.Double(); | |||
/** the destination rectangle */ | |||
protected final Rectangle2D dstBounds = new Rectangle2D.Double(); | |||
/** | |||
* A variable-sized Bitmap16 Object that defines source image content. | |||
* This object MUST be specified, even if the raster operation does not require a source. | |||
*/ | |||
HwmfBitmap16 target; | |||
protected HwmfBitmap16 target; | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.stretchBlt; | |||
} | |||
@@ -469,27 +386,23 @@ public class HwmfFill { | |||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { | |||
boolean hasBitmap = (recordSize > ((recordFunction >> 8) + 3)); | |||
int size = 0; | |||
int rasterOpCode = leis.readUShort(); | |||
int rasterOpIndex = leis.readUShort(); | |||
rasterOperation = HwmfTernaryRasterOp.valueOf(rasterOpIndex); | |||
assert(rasterOpCode == rasterOperation.opCode); | |||
srcHeight = leis.readShort(); | |||
srcWidth = leis.readShort(); | |||
ySrc = leis.readShort(); | |||
xSrc = leis.readShort(); | |||
size = 6*LittleEndianConsts.SHORT_SIZE; | |||
int size = 2*LittleEndianConsts.SHORT_SIZE; | |||
size += readBounds2(leis, srcBounds); | |||
if (!hasBitmap) { | |||
/*int reserved =*/ leis.readShort(); | |||
size += LittleEndianConsts.SHORT_SIZE; | |||
} | |||
destHeight = leis.readShort(); | |||
destWidth = leis.readShort(); | |||
yDest = leis.readShort(); | |||
xDest = leis.readShort(); | |||
size += 4*LittleEndianConsts.SHORT_SIZE; | |||
size += readBounds2(leis, dstBounds); | |||
if (hasBitmap) { | |||
target = new HwmfBitmap16(); | |||
size += target.init(leis); | |||
@@ -524,46 +437,13 @@ public class HwmfFill { | |||
* DIB contains explicit RGB values or indexes into a palette. | |||
*/ | |||
private ColorUsage colorUsage; | |||
/** | |||
* A 16-bit signed integer that defines the height, in logical units, of the | |||
* source rectangle. | |||
*/ | |||
private int srcHeight; | |||
/** | |||
* A 16-bit signed integer that defines the width, in logical units, of the | |||
* source rectangle. | |||
*/ | |||
private int srcWidth; | |||
/** | |||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the | |||
* source rectangle. | |||
*/ | |||
private int ySrc; | |||
/** | |||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the | |||
* source rectangle. | |||
*/ | |||
private int xSrc; | |||
/** | |||
* A 16-bit signed integer that defines the height, in logical units, of the | |||
* destination rectangle. | |||
*/ | |||
private int destHeight; | |||
/** | |||
* A 16-bit signed integer that defines the width, in logical units, of the | |||
* destination rectangle. | |||
*/ | |||
private int destWidth; | |||
/** | |||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the | |||
* upper-left corner of the destination rectangle. | |||
*/ | |||
private int yDst; | |||
/** | |||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the | |||
* upper-left corner of the destination rectangle. | |||
*/ | |||
private int xDst; | |||
/** the source rectangle. */ | |||
protected final Rectangle2D srcBounds = new Rectangle2D.Double(); | |||
/** the destination rectangle. */ | |||
protected final Rectangle2D dstBounds = new Rectangle2D.Double(); | |||
/** | |||
* A variable-sized DeviceIndependentBitmap Object (section 2.2.2.9) that is the | |||
* source of the color data. | |||
@@ -571,7 +451,7 @@ public class HwmfFill { | |||
private HwmfBitmapDib dib; | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.stretchDib; | |||
} | |||
@@ -585,16 +465,12 @@ public class HwmfFill { | |||
assert(rasterOpCode == rasterOperation.opCode); | |||
colorUsage = ColorUsage.valueOf(leis.readUShort()); | |||
srcHeight = leis.readShort(); | |||
srcWidth = leis.readShort(); | |||
ySrc = leis.readShort(); | |||
xSrc = leis.readShort(); | |||
destHeight = leis.readShort(); | |||
destWidth = leis.readShort(); | |||
yDst = leis.readShort(); | |||
xDst = leis.readShort(); | |||
int size = 11*LittleEndianConsts.SHORT_SIZE; | |||
int size = 3*LittleEndianConsts.SHORT_SIZE; | |||
size += readBounds2(leis, srcBounds); | |||
size += readBounds2(leis, dstBounds); | |||
dib = new HwmfBitmapDib(); | |||
size += dib.init(leis, (int)(recordSize-6-size)); | |||
@@ -617,53 +493,10 @@ public class HwmfFill { | |||
} | |||
} | |||
public static class WmfBitBlt implements HwmfRecord { | |||
/** | |||
* A 32-bit unsigned integer that defines how the source pixels, the current brush in the playback | |||
* device context, and the destination pixels are to be combined to form the new image. | |||
*/ | |||
private HwmfTernaryRasterOp rasterOperation; | |||
/** | |||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the upper-left corner | |||
of the source rectangle. | |||
*/ | |||
private int ySrc; | |||
/** | |||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the upper-left corner | |||
of the source rectangle. | |||
*/ | |||
private int xSrc; | |||
/** | |||
* A 16-bit signed integer that defines the height, in logical units, of the source and | |||
destination rectangles. | |||
*/ | |||
private int height; | |||
/** | |||
* A 16-bit signed integer that defines the width, in logical units, of the source and destination | |||
rectangles. | |||
*/ | |||
private int width; | |||
/** | |||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the upper-left | |||
corner of the destination rectangle. | |||
*/ | |||
private int yDest; | |||
/** | |||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the upper-left | |||
corner of the destination rectangle. | |||
*/ | |||
private int xDest; | |||
/** | |||
* A variable-sized Bitmap16 Object that defines source image content. | |||
* This object MUST be specified, even if the raster operation does not require a source. | |||
*/ | |||
private HwmfBitmap16 target; | |||
public static class WmfBitBlt extends WmfStretchBlt { | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.bitBlt; | |||
} | |||
@@ -671,40 +504,32 @@ public class HwmfFill { | |||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { | |||
boolean hasBitmap = (recordSize/2 != ((recordFunction >> 8) + 3)); | |||
int size = 0; | |||
int rasterOpCode = leis.readUShort(); | |||
int rasterOpIndex = leis.readUShort(); | |||
rasterOperation = HwmfTernaryRasterOp.valueOf(rasterOpIndex); | |||
assert(rasterOpCode == rasterOperation.opCode); | |||
ySrc = leis.readShort(); | |||
xSrc = leis.readShort(); | |||
int size = 2*LittleEndianConsts.SHORT_SIZE; | |||
final Point2D srcPnt = new Point2D.Double(); | |||
size += readPointS(leis, srcPnt); | |||
size = 4*LittleEndianConsts.SHORT_SIZE; | |||
if (!hasBitmap) { | |||
/*int reserved =*/ leis.readShort(); | |||
size += LittleEndianConsts.SHORT_SIZE; | |||
} | |||
height = leis.readShort(); | |||
width = leis.readShort(); | |||
yDest = leis.readShort(); | |||
xDest = leis.readShort(); | |||
size += 4*LittleEndianConsts.SHORT_SIZE; | |||
size += readBounds2(leis, dstBounds); | |||
if (hasBitmap) { | |||
target = new HwmfBitmap16(); | |||
size += target.init(leis); | |||
} | |||
return size; | |||
} | |||
@Override | |||
public void draw(HwmfGraphics ctx) { | |||
srcBounds.setRect(srcPnt.getX(), srcPnt.getY(), dstBounds.getWidth(), dstBounds.getHeight()); | |||
return size; | |||
} | |||
} | |||
@@ -729,36 +554,13 @@ public class HwmfFill { | |||
* A 16-bit unsigned integer that defines the starting scan line in the source. | |||
*/ | |||
private int startScan; | |||
/** | |||
* A 16-bit unsigned integer that defines the y-coordinate, in logical units, of the | |||
* source rectangle. | |||
*/ | |||
private int yDib; | |||
/** | |||
* A 16-bit unsigned integer that defines the x-coordinate, in logical units, of the | |||
* source rectangle. | |||
*/ | |||
private int xDib; | |||
/** | |||
* A 16-bit unsigned integer that defines the height, in logical units, of the | |||
* source and destination rectangles. | |||
*/ | |||
private int height; | |||
/** | |||
* A 16-bit unsigned integer that defines the width, in logical units, of the | |||
* source and destination rectangles. | |||
*/ | |||
private int width; | |||
/** | |||
* A 16-bit unsigned integer that defines the y-coordinate, in logical units, of the | |||
* upper-left corner of the destination rectangle. | |||
*/ | |||
private int yDest; | |||
/** | |||
* A 16-bit unsigned integer that defines the x-coordinate, in logical units, of the | |||
* upper-left corner of the destination rectangle. | |||
*/ | |||
private int xDest; | |||
/** the source rectangle */ | |||
protected final Rectangle2D srcBounds = new Rectangle2D.Double(); | |||
/** the destination rectangle, having the same dimension as the source rectangle */ | |||
protected final Rectangle2D dstBounds = new Rectangle2D.Double(); | |||
/** | |||
* A variable-sized DeviceIndependentBitmap Object that is the source of the color data. | |||
*/ | |||
@@ -766,7 +568,7 @@ public class HwmfFill { | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.setDibToDev; | |||
} | |||
@@ -775,17 +577,19 @@ public class HwmfFill { | |||
colorUsage = ColorUsage.valueOf(leis.readUShort()); | |||
scanCount = leis.readUShort(); | |||
startScan = leis.readUShort(); | |||
yDib = leis.readUShort(); | |||
xDib = leis.readUShort(); | |||
height = leis.readUShort(); | |||
width = leis.readUShort(); | |||
yDest = leis.readUShort(); | |||
xDest = leis.readUShort(); | |||
int size = 9*LittleEndianConsts.SHORT_SIZE; | |||
int size = 3*LittleEndianConsts.SHORT_SIZE; | |||
final Point2D srcPnt = new Point2D.Double(); | |||
size += readPointS(leis, srcPnt); | |||
size += readBounds2(leis, dstBounds); | |||
dib = new HwmfBitmapDib(); | |||
size += dib.init(leis, (int)(recordSize-6-size)); | |||
srcBounds.setRect(srcPnt.getX(), srcPnt.getY(), dstBounds.getWidth(), dstBounds.getHeight()); | |||
return size; | |||
} | |||
@@ -806,52 +610,9 @@ public class HwmfFill { | |||
} | |||
public static class WmfDibBitBlt implements HwmfRecord, HwmfImageRecord, HwmfObjectTableEntry { | |||
/** | |||
* A 32-bit unsigned integer that defines how the source pixels, the current brush | |||
* in the playback device context, and the destination pixels are to be combined to form the | |||
* new image. This code MUST be one of the values in the Ternary Raster Operation Enumeration. | |||
*/ | |||
HwmfTernaryRasterOp rasterOperation; | |||
/** | |||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the source rectangle. | |||
*/ | |||
private int ySrc; | |||
/** | |||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the source rectangle. | |||
*/ | |||
private int xSrc; | |||
/** | |||
* A 16-bit signed integer that defines the height, in logical units, of the source and | |||
* destination rectangles. | |||
*/ | |||
private int height; | |||
/** | |||
* A 16-bit signed integer that defines the width, in logical units, of the source and destination | |||
* rectangles. | |||
*/ | |||
private int width; | |||
/** | |||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the upper-left | |||
* corner of the destination rectangle. | |||
*/ | |||
private int yDest; | |||
/** | |||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the upper-left | |||
* corner of the destination rectangle. | |||
*/ | |||
private int xDest; | |||
/** | |||
* A variable-sized DeviceIndependentBitmap Object that defines image content. | |||
* This object MUST be specified, even if the raster operation does not require a source. | |||
*/ | |||
private HwmfBitmapDib target; | |||
public static class WmfDibBitBlt extends WmfDibStretchBlt { | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.dibBitBlt; | |||
} | |||
@@ -859,47 +620,31 @@ public class HwmfFill { | |||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { | |||
boolean hasBitmap = (recordSize/2 != ((recordFunction >> 8) + 3)); | |||
int size = 0; | |||
int rasterOpCode = leis.readUShort(); | |||
int rasterOpIndex = leis.readUShort(); | |||
rasterOperation = HwmfTernaryRasterOp.valueOf(rasterOpIndex); | |||
assert(rasterOpCode == rasterOperation.opCode); | |||
ySrc = leis.readShort(); | |||
xSrc = leis.readShort(); | |||
size = 4*LittleEndianConsts.SHORT_SIZE; | |||
int size = 2*LittleEndianConsts.SHORT_SIZE; | |||
final Point2D srcPnt = new Point2D.Double(); | |||
size += readPointS(leis, srcPnt); | |||
if (!hasBitmap) { | |||
/*int reserved =*/ leis.readShort(); | |||
size += LittleEndianConsts.SHORT_SIZE; | |||
} | |||
height = leis.readShort(); | |||
width = leis.readShort(); | |||
yDest = leis.readShort(); | |||
xDest = leis.readShort(); | |||
size += 4*LittleEndianConsts.SHORT_SIZE; | |||
size += readBounds2(leis, dstBounds); | |||
if (hasBitmap) { | |||
target = new HwmfBitmapDib(); | |||
size += target.init(leis, (int)(recordSize-6-size)); | |||
} | |||
return size; | |||
} | |||
@Override | |||
public void draw(HwmfGraphics ctx) { | |||
ctx.addObjectTableEntry(this); | |||
} | |||
@Override | |||
public void applyObject(HwmfGraphics ctx) { | |||
} | |||
// the destination rectangle, having the same dimension as the source rectangle | |||
srcBounds.setRect(srcPnt.getX(), srcPnt.getY(), dstBounds.getWidth(), dstBounds.getHeight()); | |||
@Override | |||
public BufferedImage getImage() { | |||
return (target == null) ? null : target.getImage(); | |||
return size; | |||
} | |||
} | |||
@@ -909,53 +654,22 @@ public class HwmfFill { | |||
* in the playback device context, and the destination pixels are to be combined to form the | |||
* new image. This code MUST be one of the values in the Ternary Raster Operation Enumeration. | |||
*/ | |||
private HwmfTernaryRasterOp rasterOperation; | |||
/** | |||
* A 16-bit signed integer that defines the height, in logical units, of the source rectangle. | |||
*/ | |||
private int srcHeight; | |||
/** | |||
* A 16-bit signed integer that defines the width, in logical units, of the source rectangle. | |||
*/ | |||
private int srcWidth; | |||
/** | |||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the | |||
* upper-left corner of the source rectangle. | |||
*/ | |||
private int ySrc; | |||
/** | |||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the | |||
* upper-left corner of the source rectangle. | |||
*/ | |||
private int xSrc; | |||
/** | |||
* A 16-bit signed integer that defines the height, in logical units, of the | |||
* destination rectangle. | |||
*/ | |||
private int destHeight; | |||
/** | |||
* A 16-bit signed integer that defines the width, in logical units, of the | |||
* destination rectangle. | |||
*/ | |||
private int destWidth; | |||
/** | |||
* A 16-bit signed integer that defines the y-coordinate, in logical units, | |||
* of the upper-left corner of the destination rectangle. | |||
*/ | |||
private int yDest; | |||
/** | |||
* A 16-bit signed integer that defines the x-coordinate, in logical units, | |||
* of the upper-left corner of the destination rectangle. | |||
*/ | |||
private int xDest; | |||
protected HwmfTernaryRasterOp rasterOperation; | |||
/** the source rectangle */ | |||
protected final Rectangle2D srcBounds = new Rectangle2D.Double(); | |||
/** the destination rectangle */ | |||
protected final Rectangle2D dstBounds = new Rectangle2D.Double(); | |||
/** | |||
* A variable-sized DeviceIndependentBitmap Object that defines image content. | |||
* This object MUST be specified, even if the raster operation does not require a source. | |||
*/ | |||
HwmfBitmapDib target; | |||
protected HwmfBitmapDib target; | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.dibStretchBlt; | |||
} | |||
@@ -963,27 +677,21 @@ public class HwmfFill { | |||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { | |||
boolean hasBitmap = (recordSize > ((recordFunction >> 8) + 3)); | |||
int size = 0; | |||
int rasterOpCode = leis.readUShort(); | |||
int rasterOpIndex = leis.readUShort(); | |||
rasterOperation = HwmfTernaryRasterOp.valueOf(rasterOpIndex); | |||
assert(rasterOpCode == rasterOperation.opCode); | |||
srcHeight = leis.readShort(); | |||
srcWidth = leis.readShort(); | |||
ySrc = leis.readShort(); | |||
xSrc = leis.readShort(); | |||
size = 6*LittleEndianConsts.SHORT_SIZE; | |||
int size = 2*LittleEndianConsts.SHORT_SIZE; | |||
size += readBounds2(leis, srcBounds); | |||
if (!hasBitmap) { | |||
/*int reserved =*/ leis.readShort(); | |||
size += LittleEndianConsts.SHORT_SIZE; | |||
} | |||
destHeight = leis.readShort(); | |||
destWidth = leis.readShort(); | |||
yDest = leis.readShort(); | |||
xDest = leis.readShort(); | |||
size += 4*LittleEndianConsts.SHORT_SIZE; | |||
size += readBounds2(leis, dstBounds); | |||
if (hasBitmap) { | |||
target = new HwmfBitmapDib(); | |||
size += target.init(leis, (int)(recordSize-6-size)); | |||
@@ -996,15 +704,30 @@ public class HwmfFill { | |||
public void draw(HwmfGraphics ctx) { | |||
ctx.addObjectTableEntry(this); | |||
} | |||
@Override | |||
public void applyObject(HwmfGraphics ctx) { | |||
} | |||
@Override | |||
public BufferedImage getImage() { | |||
return target.getImage(); | |||
return (target == null) ? null : target.getImage(); | |||
} | |||
} | |||
static int readBounds2(LittleEndianInputStream leis, Rectangle2D bounds) { | |||
/** | |||
* The 16-bit signed integers that defines the corners of the bounding rectangle. | |||
*/ | |||
int h = leis.readShort(); | |||
int w = leis.readShort(); | |||
int y = leis.readShort(); | |||
int x = leis.readShort(); | |||
bounds.setRect(x, y, w, h); | |||
return 4 * LittleEndianConsts.SHORT_SIZE; | |||
} | |||
} |
@@ -24,6 +24,8 @@ import org.apache.poi.common.usermodel.fonts.FontCharset; | |||
import org.apache.poi.common.usermodel.fonts.FontFamily; | |||
import org.apache.poi.common.usermodel.fonts.FontInfo; | |||
import org.apache.poi.common.usermodel.fonts.FontPitch; | |||
import org.apache.poi.util.BitField; | |||
import org.apache.poi.util.BitFieldFactory; | |||
import org.apache.poi.util.LittleEndianConsts; | |||
import org.apache.poi.util.LittleEndianInputStream; | |||
@@ -90,7 +92,7 @@ public class HwmfFont implements FontInfo { | |||
this.flag = flag; | |||
} | |||
static WmfOutPrecision valueOf(int flag) { | |||
public static WmfOutPrecision valueOf(int flag) { | |||
for (WmfOutPrecision op : values()) { | |||
if (op.flag == flag) { | |||
return op; | |||
@@ -104,22 +106,17 @@ public class HwmfFont implements FontInfo { | |||
* ClipPrecision Flags specify clipping precision, which defines how to clip characters that are | |||
* partially outside a clipping region. These flags can be combined to specify multiple options. | |||
*/ | |||
public enum WmfClipPrecision { | |||
public static class WmfClipPrecision { | |||
/** | |||
* Specifies that default clipping MUST be used. | |||
*/ | |||
CLIP_DEFAULT_PRECIS (0x00000000), | |||
/** Specifies that default clipping MUST be used. */ | |||
private static final BitField CLIP_DEFAULT_PRECIS = BitFieldFactory.getInstance(0x0000); | |||
/** | |||
* This value SHOULD NOT be used. | |||
*/ | |||
CLIP_CHARACTER_PRECIS (0x00000001), | |||
/** | |||
* This value MAY be returned when enumerating rasterized, TrueType and vector fonts. | |||
*/ | |||
CLIP_STROKE_PRECIS (0x00000002), | |||
/** This value SHOULD NOT be used. */ | |||
private static final BitField CLIP_CHARACTER_PRECIS = BitFieldFactory.getInstance(0x0001); | |||
/** This value MAY be returned when enumerating rasterized, TrueType and vector fonts. */ | |||
private static final BitField CLIP_STROKE_PRECIS = BitFieldFactory.getInstance(0x0002); | |||
/** | |||
* This value is used to control font rotation, as follows: | |||
@@ -129,37 +126,25 @@ public class HwmfFont implements FontInfo { | |||
* If clear, device fonts SHOULD rotate counterclockwise, but the rotation of other fonts | |||
* SHOULD be determined by the orientation of the coordinate system. | |||
*/ | |||
CLIP_LH_ANGLES (0x00000010), | |||
private static final BitField CLIP_LH_ANGLES = BitFieldFactory.getInstance(0x0010); | |||
/** | |||
* This value SHOULD NOT be used. | |||
*/ | |||
CLIP_TT_ALWAYS (0x00000020), | |||
/** This value SHOULD NOT be used. */ | |||
private static final BitField CLIP_TT_ALWAYS = BitFieldFactory.getInstance(0x0020); | |||
/** | |||
* This value specifies that font association SHOULD< be turned off. | |||
*/ | |||
CLIP_DFA_DISABLE (0x00000040), | |||
/** This value specifies that font association SHOULD< be turned off. */ | |||
private static final BitField CLIP_DFA_DISABLE = BitFieldFactory.getInstance(0x0040); | |||
/** | |||
* This value specifies that font embedding MUST be used to render document content; | |||
* embedded fonts are read-only. | |||
*/ | |||
CLIP_EMBEDDED (0x00000080); | |||
private static final BitField CLIP_EMBEDDED = BitFieldFactory.getInstance(0x0080); | |||
int flag; | |||
WmfClipPrecision(int flag) { | |||
this.flag = flag; | |||
} | |||
static WmfClipPrecision valueOf(int flag) { | |||
for (WmfClipPrecision cp : values()) { | |||
if (cp.flag == flag) { | |||
return cp; | |||
} | |||
} | |||
return null; | |||
public int init(LittleEndianInputStream leis) { | |||
flag = leis.readUByte(); | |||
return LittleEndianConsts.BYTE_SIZE; | |||
} | |||
} | |||
@@ -210,7 +195,7 @@ public class HwmfFont implements FontInfo { | |||
this.flag = flag; | |||
} | |||
static WmfFontQuality valueOf(int flag) { | |||
public static WmfFontQuality valueOf(int flag) { | |||
for (WmfFontQuality fq : values()) { | |||
if (fq.flag == flag) { | |||
return fq; | |||
@@ -240,7 +225,7 @@ public class HwmfFont implements FontInfo { | |||
* For all height comparisons, the font mapper SHOULD find the largest physical | |||
* font that does not exceed the requested size. | |||
*/ | |||
int height; | |||
protected int height; | |||
/** | |||
* A 16-bit signed integer that defines the average width, in logical units, of | |||
@@ -248,45 +233,45 @@ public class HwmfFont implements FontInfo { | |||
* against the digitization aspect ratio of the available fonts to find the closest match, | |||
* determined by the absolute value of the difference. | |||
*/ | |||
int width; | |||
protected int width; | |||
/** | |||
* A 16-bit signed integer that defines the angle, in tenths of degrees, between the | |||
* escapement vector and the x-axis of the device. The escapement vector is parallel | |||
* to the base line of a row of text. | |||
*/ | |||
int escapement; | |||
protected int escapement; | |||
/** | |||
* A 16-bit signed integer that defines the angle, in tenths of degrees, | |||
* between each character's base line and the x-axis of the device. | |||
*/ | |||
int orientation; | |||
protected int orientation; | |||
/** | |||
* A 16-bit signed integer that defines the weight of the font in the range 0 | |||
* through 1000. For example, 400 is normal and 700 is bold. If this value is 0x0000, | |||
* a default weight SHOULD be used. | |||
*/ | |||
int weight; | |||
protected int weight; | |||
/** | |||
* A 8-bit Boolean value that specifies the italic attribute of the font. | |||
* 0 = not italic / 1 = italic. | |||
*/ | |||
boolean italic; | |||
protected boolean italic; | |||
/** | |||
* An 8-bit Boolean value that specifies the underline attribute of the font. | |||
* 0 = not underlined / 1 = underlined | |||
*/ | |||
boolean underline; | |||
protected boolean underline; | |||
/** | |||
* An 8-bit Boolean value that specifies the strike out attribute of the font. | |||
* 0 = not striked out / 1 = striked out | |||
*/ | |||
boolean strikeOut; | |||
protected boolean strikeOut; | |||
/** | |||
* An 8-bit unsigned integer that defines the character set. | |||
@@ -299,12 +284,12 @@ public class HwmfFont implements FontInfo { | |||
* If a typeface name in the FaceName field is specified, the CharSet value MUST match the | |||
* character set of that typeface. | |||
*/ | |||
FontCharset charSet; | |||
protected FontCharset charSet; | |||
/** | |||
* An 8-bit unsigned integer that defines the output precision. | |||
*/ | |||
WmfOutPrecision outPrecision; | |||
protected WmfOutPrecision outPrecision; | |||
/** | |||
* An 8-bit unsigned integer that defines the clipping precision. | |||
@@ -312,40 +297,40 @@ public class HwmfFont implements FontInfo { | |||
* | |||
* @see WmfClipPrecision | |||
*/ | |||
WmfClipPrecision clipPrecision; | |||
protected final WmfClipPrecision clipPrecision = new WmfClipPrecision(); | |||
/** | |||
* An 8-bit unsigned integer that defines the output quality. | |||
*/ | |||
WmfFontQuality quality; | |||
protected WmfFontQuality quality; | |||
/** | |||
* A PitchAndFamily object that defines the pitch and the family of the font. | |||
* Font families specify the look of fonts in a general way and are intended for | |||
* specifying fonts when the exact typeface wanted is not available. | |||
*/ | |||
int pitchAndFamily; | |||
protected int pitchAndFamily; | |||
/** | |||
* Font families specify the look of fonts in a general way and are | |||
* intended for specifying fonts when the exact typeface wanted is not available. | |||
* (LSB 4 bits) | |||
*/ | |||
FontFamily family; | |||
protected FontFamily family; | |||
/** | |||
* A property of a font that describes the pitch (MSB 2 bits) | |||
*/ | |||
FontPitch pitch; | |||
protected FontPitch pitch; | |||
/** | |||
* A null-terminated string of 8-bit Latin-1 [ISO/IEC-8859-1] ANSI | |||
* characters that specifies the typeface name of the font. The length of this string MUST NOT | |||
* exceed 32 8-bit characters, including the terminating null. | |||
*/ | |||
String facename; | |||
protected String facename; | |||
public int init(LittleEndianInputStream leis) throws IOException { | |||
public int init(LittleEndianInputStream leis, long recordSize) throws IOException { | |||
height = leis.readShort(); | |||
width = leis.readShort(); | |||
escapement = leis.readShort(); | |||
@@ -356,21 +341,17 @@ public class HwmfFont implements FontInfo { | |||
strikeOut = leis.readByte() != 0; | |||
charSet = FontCharset.valueOf(leis.readUByte()); | |||
outPrecision = WmfOutPrecision.valueOf(leis.readUByte()); | |||
clipPrecision = WmfClipPrecision.valueOf(leis.readUByte()); | |||
clipPrecision.init(leis); | |||
quality = WmfFontQuality.valueOf(leis.readUByte()); | |||
pitchAndFamily = leis.readUByte(); | |||
byte buf[] = new byte[32], b, readBytes = 0; | |||
do { | |||
if (readBytes == 32) { | |||
throw new IOException("Font facename can't be determined."); | |||
} | |||
buf[readBytes++] = b = leis.readByte(); | |||
} while (b != 0 && b != -1 && readBytes <= 32); | |||
facename = new String(buf, 0, readBytes-1, StandardCharsets.ISO_8859_1); | |||
StringBuilder sb = new StringBuilder(); | |||
int readBytes = readString(leis, sb, 32); | |||
if (readBytes == -1) { | |||
throw new IOException("Font facename can't be determined."); | |||
} | |||
facename = sb.toString(); | |||
return 5*LittleEndianConsts.SHORT_SIZE+8*LittleEndianConsts.BYTE_SIZE+readBytes; | |||
} | |||
@@ -471,4 +452,19 @@ public class HwmfFont implements FontInfo { | |||
public void setCharset(FontCharset charset) { | |||
throw new UnsupportedOperationException("setCharset not supported by HwmfFont."); | |||
} | |||
protected int readString(LittleEndianInputStream leis, StringBuilder sb, int limit) throws IOException { | |||
byte buf[] = new byte[limit], b, readBytes = 0; | |||
do { | |||
if (readBytes == limit) { | |||
return -1; | |||
} | |||
buf[readBytes++] = b = leis.readByte(); | |||
} while (b != 0 && b != -1 && readBytes <= limit); | |||
sb.append(new String(buf, 0, readBytes-1, StandardCharsets.ISO_8859_1)); | |||
return readBytes; | |||
} | |||
} |
@@ -39,7 +39,7 @@ public enum HwmfHatchStyle { | |||
this.flag = flag; | |||
} | |||
static HwmfHatchStyle valueOf(int flag) { | |||
public static HwmfHatchStyle valueOf(int flag) { | |||
for (HwmfHatchStyle hs : values()) { | |||
if (hs.flag == flag) return hs; | |||
} |
@@ -105,7 +105,7 @@ public enum HwmfMapMode { | |||
this.scale = scale; | |||
} | |||
static HwmfMapMode valueOf(int flag) { | |||
public static HwmfMapMode valueOf(int flag) { | |||
for (HwmfMapMode mm : values()) { | |||
if (mm.flag == flag) return mm; | |||
} |
@@ -17,6 +17,7 @@ | |||
package org.apache.poi.hwmf.record; | |||
import java.awt.geom.Dimension2D; | |||
import java.awt.image.BufferedImage; | |||
import java.io.IOException; | |||
@@ -24,6 +25,7 @@ import org.apache.poi.hwmf.draw.HwmfDrawProperties; | |||
import org.apache.poi.hwmf.draw.HwmfGraphics; | |||
import org.apache.poi.hwmf.record.HwmfFill.ColorUsage; | |||
import org.apache.poi.hwmf.record.HwmfFill.HwmfImageRecord; | |||
import org.apache.poi.util.Dimension2DDouble; | |||
import org.apache.poi.util.LittleEndianConsts; | |||
import org.apache.poi.util.LittleEndianInputStream; | |||
@@ -34,7 +36,7 @@ public class HwmfMisc { | |||
*/ | |||
public static class WmfSaveDc implements HwmfRecord { | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.saveDc; | |||
} | |||
@@ -53,7 +55,7 @@ public class HwmfMisc { | |||
* The META_SETRELABS record is reserved and not supported. | |||
*/ | |||
public static class WmfSetRelabs implements HwmfRecord { | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.setRelabs; | |||
} | |||
@@ -81,7 +83,7 @@ public class HwmfMisc { | |||
private int nSavedDC; | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.restoreDc; | |||
} | |||
@@ -106,7 +108,7 @@ public class HwmfMisc { | |||
private HwmfColorRef colorRef; | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.setBkColor; | |||
} | |||
@@ -140,7 +142,7 @@ public class HwmfMisc { | |||
this.flag = flag; | |||
} | |||
static HwmfBkMode valueOf(int flag) { | |||
public static HwmfBkMode valueOf(int flag) { | |||
for (HwmfBkMode bs : values()) { | |||
if (bs.flag == flag) return bs; | |||
} | |||
@@ -148,9 +150,9 @@ public class HwmfMisc { | |||
} | |||
} | |||
private HwmfBkMode bkMode; | |||
protected HwmfBkMode bkMode; | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.setBkMode; | |||
} | |||
@@ -180,7 +182,7 @@ public class HwmfMisc { | |||
private int layout; | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.setLayout; | |||
} | |||
@@ -205,10 +207,10 @@ public class HwmfMisc { | |||
*/ | |||
public static class WmfSetMapMode implements HwmfRecord { | |||
private HwmfMapMode mapMode; | |||
protected HwmfMapMode mapMode; | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.setMapMode; | |||
} | |||
@@ -234,12 +236,13 @@ public class HwmfMisc { | |||
/** | |||
* A 32-bit unsigned integer that defines whether the font mapper should attempt to | |||
* match a font's aspect ratio to the current device's aspect ratio. If bit 0 is | |||
* set, the mapper selects only matching fonts. | |||
* set, the font mapper SHOULD select only fonts that match the aspect ratio of the | |||
* output device, as it is currently defined in the playback device context. | |||
*/ | |||
private long mapperValues; | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.setMapperFlags; | |||
} | |||
@@ -262,14 +265,11 @@ public class HwmfMisc { | |||
*/ | |||
public static class WmfSetRop2 implements HwmfRecord { | |||
/** | |||
* A 16-bit unsigned integer that defines the foreground binary raster | |||
* operation mixing mode | |||
*/ | |||
private HwmfBinaryRasterOp drawMode; | |||
/** An unsigned integer that defines the foreground binary raster operation mixing mode */ | |||
protected HwmfBinaryRasterOp drawMode; | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.setRop2; | |||
} | |||
@@ -291,24 +291,63 @@ public class HwmfMisc { | |||
*/ | |||
public static class WmfSetStretchBltMode implements HwmfRecord { | |||
/** | |||
* A 16-bit unsigned integer that defines bitmap stretching mode. | |||
* This MUST be one of the values: | |||
* BLACKONWHITE = 0x0001, | |||
* WHITEONBLACK = 0x0002, | |||
* COLORONCOLOR = 0x0003, | |||
* HALFTONE = 0x0004 | |||
*/ | |||
private int setStretchBltMode; | |||
public enum StretchBltMode { | |||
/** | |||
* Performs a Boolean AND operation by using the color values for the eliminated and existing pixels. | |||
* If the bitmap is a monochrome bitmap, this mode preserves black pixels at the expense of white pixels. | |||
* | |||
* EMF name: STRETCH_ANDSCANS | |||
*/ | |||
BLACKONWHITE(0x0001), | |||
/** | |||
* Performs a Boolean OR operation by using the color values for the eliminated and existing pixels. | |||
* If the bitmap is a monochrome bitmap, this mode preserves white pixels at the expense of black pixels. | |||
* | |||
* EMF name: STRETCH_ORSCANS | |||
*/ | |||
WHITEONBLACK(0x0002), | |||
/** | |||
* Deletes the pixels. This mode deletes all eliminated lines of pixels without trying | |||
* to preserve their information. | |||
* | |||
* EMF name: STRETCH_DELETESCANS | |||
*/ | |||
COLORONCOLOR(0x0003), | |||
/** | |||
* Maps pixels from the source rectangle into blocks of pixels in the destination rectangle. | |||
* The average color over the destination block of pixels approximates the color of the source | |||
* pixels. | |||
* | |||
* After setting the HALFTONE stretching mode, the brush origin MUST be set to avoid misalignment | |||
* artifacts - in EMF this is done via EmfSetBrushOrgEx | |||
* | |||
* EMF name: STRETCH_HALFTONE | |||
*/ | |||
HALFTONE(0x0004); | |||
public final int flag; | |||
StretchBltMode(int flag) { | |||
this.flag = flag; | |||
} | |||
public static StretchBltMode valueOf(int flag) { | |||
for (StretchBltMode bs : values()) { | |||
if (bs.flag == flag) return bs; | |||
} | |||
return null; | |||
} | |||
} | |||
protected StretchBltMode stretchBltMode; | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.setStretchBltMode; | |||
} | |||
@Override | |||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { | |||
setStretchBltMode = leis.readUShort(); | |||
stretchBltMode = StretchBltMode.valueOf(leis.readUShort()); | |||
return LittleEndianConsts.SHORT_SIZE; | |||
} | |||
@@ -341,7 +380,7 @@ public class HwmfMisc { | |||
private HwmfBitmap16 pattern16; | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.dibCreatePatternBrush; | |||
} | |||
@@ -403,10 +442,10 @@ public class HwmfMisc { | |||
* A 16-bit unsigned integer used to index into the WMF Object Table to | |||
* get the object to be deleted. | |||
*/ | |||
private int objectIndex; | |||
protected int objectIndex; | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.deleteObject; | |||
} | |||
@@ -418,6 +457,12 @@ public class HwmfMisc { | |||
@Override | |||
public void draw(HwmfGraphics ctx) { | |||
/* TODO: | |||
* The object specified by this record MUST be deleted from the EMF Object Table. | |||
* If the deleted object is currently selected in the playback device context, | |||
* the default object for that graphics property MUST be restored. | |||
*/ | |||
ctx.unsetObjectTableEntry(objectIndex); | |||
} | |||
} | |||
@@ -427,7 +472,7 @@ public class HwmfMisc { | |||
private HwmfBitmap16 pattern; | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.createPatternBrush; | |||
} | |||
@@ -452,30 +497,28 @@ public class HwmfMisc { | |||
public static class WmfCreatePenIndirect implements HwmfRecord, HwmfObjectTableEntry { | |||
private HwmfPenStyle penStyle; | |||
/** | |||
* A 32-bit PointS Object that specifies a point for the object dimensions. | |||
* The x-coordinate is the pen width. The y-coordinate is ignored. | |||
*/ | |||
private int xWidth; | |||
@SuppressWarnings("unused") | |||
private int yWidth; | |||
protected HwmfPenStyle penStyle; | |||
protected final Dimension2D dimension = new Dimension2DDouble(); | |||
/** | |||
* A 32-bit ColorRef Object that specifies the pen color value. | |||
*/ | |||
private HwmfColorRef colorRef; | |||
protected final HwmfColorRef colorRef = new HwmfColorRef(); | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.createPenIndirect; | |||
} | |||
@Override | |||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { | |||
penStyle = HwmfPenStyle.valueOf(leis.readUShort()); | |||
xWidth = leis.readShort(); | |||
yWidth = leis.readShort(); | |||
colorRef = new HwmfColorRef(); | |||
// A 32-bit PointS Object that specifies a point for the object dimensions. | |||
// The x-coordinate is the pen width. The y-coordinate is ignored. | |||
int xWidth = leis.readShort(); | |||
int yWidth = leis.readShort(); | |||
dimension.setSize(xWidth, yWidth); | |||
int size = colorRef.init(leis); | |||
return size+3*LittleEndianConsts.SHORT_SIZE; | |||
} | |||
@@ -490,7 +533,7 @@ public class HwmfMisc { | |||
HwmfDrawProperties p = ctx.getProperties(); | |||
p.setPenStyle(penStyle); | |||
p.setPenColor(colorRef); | |||
p.setPenWidth(xWidth); | |||
p.setPenWidth(dimension.getWidth()); | |||
} | |||
} | |||
@@ -540,19 +583,14 @@ public class HwmfMisc { | |||
* </table> | |||
*/ | |||
public static class WmfCreateBrushIndirect implements HwmfRecord, HwmfObjectTableEntry { | |||
private HwmfBrushStyle brushStyle; | |||
protected HwmfBrushStyle brushStyle; | |||
private HwmfColorRef colorRef; | |||
protected HwmfColorRef colorRef; | |||
/** | |||
* A 16-bit field that specifies the brush hatch type. | |||
* Its interpretation depends on the value of BrushStyle. | |||
* | |||
*/ | |||
private HwmfHatchStyle brushHatch; | |||
protected HwmfHatchStyle brushHatch; | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.createBrushIndirect; | |||
} | |||
@@ -39,12 +39,12 @@ public class HwmfPalette { | |||
private int values; | |||
private Color colorRef; | |||
private PaletteEntry() { | |||
public PaletteEntry() { | |||
this.values = PC_RESERVED.set(0); | |||
this.colorRef = Color.BLACK; | |||
} | |||
private PaletteEntry(PaletteEntry other) { | |||
public PaletteEntry(PaletteEntry other) { | |||
this.values = other.values; | |||
this.colorRef = other.colorRef; | |||
} | |||
@@ -100,19 +100,24 @@ public class HwmfPalette { | |||
* used with the META_SETPALENTRIES and META_ANIMATEPALETTE record types. | |||
* When used with META_CREATEPALETTE, it MUST be 0x0300 | |||
*/ | |||
private int start; | |||
protected int start; | |||
private List<PaletteEntry> palette = new ArrayList<>(); | |||
protected final List<PaletteEntry> palette = new ArrayList<>(); | |||
@Override | |||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { | |||
start = leis.readUShort(); | |||
int size = readPaletteEntries(leis, -1); | |||
return size + LittleEndianConsts.SHORT_SIZE; | |||
} | |||
protected int readPaletteEntries(LittleEndianInputStream leis, int nbrOfEntries) throws IOException { | |||
/** | |||
* NumberOfEntries (2 bytes): A 16-bit unsigned integer that defines the number of objects in | |||
* aPaletteEntries. | |||
*/ | |||
int numberOfEntries = leis.readUShort(); | |||
int size = 2*LittleEndianConsts.SHORT_SIZE; | |||
final int numberOfEntries = (nbrOfEntries > -1) ? nbrOfEntries : leis.readUShort(); | |||
int size = (nbrOfEntries > -1) ? 0 : LittleEndianConsts.SHORT_SIZE; | |||
for (int i=0; i<numberOfEntries; i++) { | |||
PaletteEntry pe = new PaletteEntry(); | |||
size += pe.init(leis); | |||
@@ -144,7 +149,7 @@ public class HwmfPalette { | |||
*/ | |||
public static class WmfCreatePalette extends WmfPaletteParent implements HwmfObjectTableEntry { | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.createPalette; | |||
} | |||
@@ -160,7 +165,7 @@ public class HwmfPalette { | |||
*/ | |||
public static class WmfSetPaletteEntries extends WmfPaletteParent { | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.setPalEntries; | |||
} | |||
@@ -197,10 +202,10 @@ public class HwmfPalette { | |||
* A 16-bit unsigned integer that defines the number of entries in | |||
* the logical palette. | |||
*/ | |||
int numberOfEntries; | |||
protected int numberOfEntries; | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.resizePalette; | |||
} | |||
@@ -238,10 +243,10 @@ public class HwmfPalette { | |||
* A 16-bit unsigned integer used to index into the WMF Object Table to get | |||
* the Palette Object to be selected. | |||
*/ | |||
private int paletteIndex; | |||
protected int paletteIndex; | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.selectPalette; | |||
} | |||
@@ -263,7 +268,7 @@ public class HwmfPalette { | |||
*/ | |||
public static class WmfRealizePalette implements HwmfRecord { | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.realizePalette; | |||
} | |||
@@ -292,7 +297,7 @@ public class HwmfPalette { | |||
*/ | |||
public static class WmfAnimatePalette extends WmfPaletteParent { | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.animatePalette; | |||
} | |||
@@ -136,11 +136,12 @@ public class HwmfPenStyle implements Cloneable { | |||
} | |||
} | |||
private static final BitField SUBSECTION_DASH = BitFieldFactory.getInstance(0x0007); | |||
private static final BitField SUBSECTION_ALTERNATE = BitFieldFactory.getInstance(0x0008); | |||
private static final BitField SUBSECTION_ENDCAP = BitFieldFactory.getInstance(0x0300); | |||
private static final BitField SUBSECTION_JOIN = BitFieldFactory.getInstance(0x3000); | |||
private static final BitField SUBSECTION_DASH = BitFieldFactory.getInstance(0x00007); | |||
private static final BitField SUBSECTION_ALTERNATE = BitFieldFactory.getInstance(0x00008); | |||
private static final BitField SUBSECTION_ENDCAP = BitFieldFactory.getInstance(0x00300); | |||
private static final BitField SUBSECTION_JOIN = BitFieldFactory.getInstance(0x03000); | |||
private static final BitField SUBSECTION_GEOMETRIC = BitFieldFactory.getInstance(0x10000); | |||
private int flag; | |||
public static HwmfPenStyle valueOf(int flag) { | |||
@@ -169,6 +170,14 @@ public class HwmfPenStyle implements Cloneable { | |||
return SUBSECTION_ALTERNATE.isSet(flag); | |||
} | |||
/** | |||
* A pen type that specifies a line with a width that is measured in logical units | |||
* and a style that can contain any of the attributes of a brush. | |||
*/ | |||
public boolean isGeometric() { | |||
return SUBSECTION_GEOMETRIC.isSet(flag); | |||
} | |||
/** | |||
* Creates a new object of the same class and with the |
@@ -23,7 +23,7 @@ import org.apache.poi.hwmf.draw.HwmfGraphics; | |||
import org.apache.poi.util.LittleEndianInputStream; | |||
public interface HwmfRecord { | |||
HwmfRecordType getRecordType(); | |||
HwmfRecordType getWmfRecordType(); | |||
/** | |||
* Init record from stream |
@@ -17,6 +17,8 @@ | |||
package org.apache.poi.hwmf.record; | |||
import java.util.function.Supplier; | |||
/** | |||
* Available record types for WMF | |||
* | |||
@@ -24,83 +26,83 @@ package org.apache.poi.hwmf.record; | |||
*/ | |||
public enum HwmfRecordType { | |||
eof(0x0000, null) | |||
,animatePalette(0x0436, HwmfPalette.WmfAnimatePalette.class) | |||
,arc(0x0817, HwmfDraw.WmfArc.class) | |||
,bitBlt(0x0922, HwmfFill.WmfBitBlt.class) | |||
,chord(0x0830, HwmfDraw.WmfChord.class) | |||
,createBrushIndirect(0x02fc, HwmfMisc.WmfCreateBrushIndirect.class) | |||
,createFontIndirect(0x02fb, HwmfText.WmfCreateFontIndirect.class) | |||
,createPalette(0x00f7, HwmfPalette.WmfCreatePalette.class) | |||
,createPatternBrush(0x01f9, HwmfMisc.WmfCreatePatternBrush.class) | |||
,createPenIndirect(0x02fa, HwmfMisc.WmfCreatePenIndirect.class) | |||
,createRegion(0x06ff, HwmfWindowing.WmfCreateRegion.class) | |||
,deleteObject(0x01f0, HwmfMisc.WmfDeleteObject.class) | |||
,dibBitBlt(0x0940, HwmfFill.WmfDibBitBlt.class) | |||
,dibCreatePatternBrush(0x0142, HwmfMisc.WmfDibCreatePatternBrush.class) | |||
,dibStretchBlt(0x0b41, HwmfFill.WmfDibStretchBlt.class) | |||
,ellipse(0x0418, HwmfDraw.WmfEllipse.class) | |||
,escape(0x0626, HwmfEscape.class) | |||
,excludeClipRect(0x0415, HwmfWindowing.WmfExcludeClipRect.class) | |||
,extFloodFill(0x0548, HwmfFill.WmfExtFloodFill.class) | |||
,extTextOut(0x0a32, HwmfText.WmfExtTextOut.class) | |||
,fillRegion(0x0228, HwmfFill.WmfFillRegion.class) | |||
,floodFill(0x0419, HwmfFill.WmfFloodFill.class) | |||
,frameRegion(0x0429, HwmfDraw.WmfFrameRegion.class) | |||
,intersectClipRect(0x0416, HwmfWindowing.WmfIntersectClipRect.class) | |||
,invertRegion(0x012a, HwmfFill.WmfInvertRegion.class) | |||
,lineTo(0x0213, HwmfDraw.WmfLineTo.class) | |||
,moveTo(0x0214, HwmfDraw.WmfMoveTo.class) | |||
,offsetClipRgn(0x0220, HwmfWindowing.WmfOffsetClipRgn.class) | |||
,offsetViewportOrg(0x0211, HwmfWindowing.WmfOffsetViewportOrg.class) | |||
,offsetWindowOrg(0x020f, HwmfWindowing.WmfOffsetWindowOrg.class) | |||
,paintRegion(0x012b, HwmfFill.WmfPaintRegion.class) | |||
,patBlt(0x061d, HwmfFill.WmfPatBlt.class) | |||
,pie(0x081a, HwmfDraw.WmfPie.class) | |||
,polygon(0x0324, HwmfDraw.WmfPolygon.class) | |||
,polyline(0x0325, HwmfDraw.WmfPolyline.class) | |||
,polyPolygon(0x0538, HwmfDraw.WmfPolyPolygon.class) | |||
,realizePalette(0x0035, HwmfPalette.WmfRealizePalette.class) | |||
,rectangle(0x041b, HwmfDraw.WmfRectangle.class) | |||
,resizePalette(0x0139, HwmfPalette.WmfResizePalette.class) | |||
,restoreDc(0x0127, HwmfMisc.WmfRestoreDc.class) | |||
,roundRect(0x061c, HwmfDraw.WmfRoundRect.class) | |||
,saveDc(0x001e, HwmfMisc.WmfSaveDc.class) | |||
,scaleViewportExt(0x0412, HwmfWindowing.WmfScaleViewportExt.class) | |||
,scaleWindowExt(0x0410, HwmfWindowing.WmfScaleWindowExt.class) | |||
,selectClipRegion(0x012c, HwmfWindowing.WmfSelectClipRegion.class) | |||
,selectObject(0x012d, HwmfDraw.WmfSelectObject.class) | |||
,selectPalette(0x0234, HwmfPalette.WmfSelectPalette.class) | |||
,setBkColor(0x0201, HwmfMisc.WmfSetBkColor.class) | |||
,setBkMode(0x0102, HwmfMisc.WmfSetBkMode.class) | |||
,setDibToDev(0x0d33, HwmfFill.WmfSetDibToDev.class) | |||
,setLayout(0x0149, HwmfMisc.WmfSetLayout.class) | |||
,setMapMode(0x0103, HwmfMisc.WmfSetMapMode.class) | |||
,setMapperFlags(0x0231, HwmfMisc.WmfSetMapperFlags.class) | |||
,setPalEntries(0x0037, HwmfPalette.WmfSetPaletteEntries.class) | |||
,setPixel(0x041f, HwmfDraw.WmfSetPixel.class) | |||
,setPolyFillMode(0x0106, HwmfFill.WmfSetPolyfillMode.class) | |||
,setRelabs(0x0105, HwmfMisc.WmfSetRelabs.class) | |||
,setRop2(0x0104, HwmfMisc.WmfSetRop2.class) | |||
,setStretchBltMode(0x0107, HwmfMisc.WmfSetStretchBltMode.class) | |||
,setTextAlign(0x012e, HwmfText.WmfSetTextAlign.class) | |||
,setTextCharExtra(0x0108, HwmfText.WmfSetTextCharExtra.class) | |||
,setTextColor(0x0209, HwmfText.WmfSetTextColor.class) | |||
,setTextJustification(0x020a, HwmfText.WmfSetTextJustification.class) | |||
,setViewportExt(0x020e, HwmfWindowing.WmfSetViewportExt.class) | |||
,setViewportOrg(0x020d, HwmfWindowing.WmfSetViewportOrg.class) | |||
,setWindowExt(0x020c, HwmfWindowing.WmfSetWindowExt.class) | |||
,setWindowOrg(0x020b, HwmfWindowing.WmfSetWindowOrg.class) | |||
,stretchBlt(0x0b23, HwmfFill.WmfStretchBlt.class) | |||
,stretchDib(0x0f43, HwmfFill.WmfStretchDib.class) | |||
,textOut(0x0521, HwmfText.WmfTextOut.class) | |||
,animatePalette(0x0436, HwmfPalette.WmfAnimatePalette::new) | |||
,arc(0x0817, HwmfDraw.WmfArc::new) | |||
,bitBlt(0x0922, HwmfFill.WmfBitBlt::new) | |||
,chord(0x0830, HwmfDraw.WmfChord::new) | |||
,createBrushIndirect(0x02fc, HwmfMisc.WmfCreateBrushIndirect::new) | |||
,createFontIndirect(0x02fb, HwmfText.WmfCreateFontIndirect::new) | |||
,createPalette(0x00f7, HwmfPalette.WmfCreatePalette::new) | |||
,createPatternBrush(0x01f9, HwmfMisc.WmfCreatePatternBrush::new) | |||
,createPenIndirect(0x02fa, HwmfMisc.WmfCreatePenIndirect::new) | |||
,createRegion(0x06ff, HwmfWindowing.WmfCreateRegion::new) | |||
,deleteObject(0x01f0, HwmfMisc.WmfDeleteObject::new) | |||
,dibBitBlt(0x0940, HwmfFill.WmfDibBitBlt::new) | |||
,dibCreatePatternBrush(0x0142, HwmfMisc.WmfDibCreatePatternBrush::new) | |||
,dibStretchBlt(0x0b41, HwmfFill.WmfDibStretchBlt::new) | |||
,ellipse(0x0418, HwmfDraw.WmfEllipse::new) | |||
,escape(0x0626, HwmfEscape::new) | |||
,excludeClipRect(0x0415, HwmfWindowing.WmfExcludeClipRect::new) | |||
,extFloodFill(0x0548, HwmfFill.WmfExtFloodFill::new) | |||
,extTextOut(0x0a32, HwmfText.WmfExtTextOut::new) | |||
,fillRegion(0x0228, HwmfFill.WmfFillRegion::new) | |||
,floodFill(0x0419, HwmfFill.WmfFloodFill::new) | |||
,frameRegion(0x0429, HwmfDraw.WmfFrameRegion::new) | |||
,intersectClipRect(0x0416, HwmfWindowing.WmfIntersectClipRect::new) | |||
,invertRegion(0x012a, HwmfFill.WmfInvertRegion::new) | |||
,lineTo(0x0213, HwmfDraw.WmfLineTo::new) | |||
,moveTo(0x0214, HwmfDraw.WmfMoveTo::new) | |||
,offsetClipRgn(0x0220, HwmfWindowing.WmfOffsetClipRgn::new) | |||
,offsetViewportOrg(0x0211, HwmfWindowing.WmfOffsetViewportOrg::new) | |||
,offsetWindowOrg(0x020f, HwmfWindowing.WmfOffsetWindowOrg::new) | |||
,paintRegion(0x012b, HwmfFill.WmfPaintRegion::new) | |||
,patBlt(0x061d, HwmfFill.WmfPatBlt::new) | |||
,pie(0x081a, HwmfDraw.WmfPie::new) | |||
,polygon(0x0324, HwmfDraw.WmfPolygon::new) | |||
,polyline(0x0325, HwmfDraw.WmfPolyline::new) | |||
,polyPolygon(0x0538, HwmfDraw.WmfPolyPolygon::new) | |||
,realizePalette(0x0035, HwmfPalette.WmfRealizePalette::new) | |||
,rectangle(0x041b, HwmfDraw.WmfRectangle::new) | |||
,resizePalette(0x0139, HwmfPalette.WmfResizePalette::new) | |||
,restoreDc(0x0127, HwmfMisc.WmfRestoreDc::new) | |||
,roundRect(0x061c, HwmfDraw.WmfRoundRect::new) | |||
,saveDc(0x001e, HwmfMisc.WmfSaveDc::new) | |||
,scaleViewportExt(0x0412, HwmfWindowing.WmfScaleViewportExt::new) | |||
,scaleWindowExt(0x0410, HwmfWindowing.WmfScaleWindowExt::new) | |||
,selectClipRegion(0x012c, HwmfWindowing.WmfSelectClipRegion::new) | |||
,selectObject(0x012d, HwmfDraw.WmfSelectObject::new) | |||
,selectPalette(0x0234, HwmfPalette.WmfSelectPalette::new) | |||
,setBkColor(0x0201, HwmfMisc.WmfSetBkColor::new) | |||
,setBkMode(0x0102, HwmfMisc.WmfSetBkMode::new) | |||
,setDibToDev(0x0d33, HwmfFill.WmfSetDibToDev::new) | |||
,setLayout(0x0149, HwmfMisc.WmfSetLayout::new) | |||
,setMapMode(0x0103, HwmfMisc.WmfSetMapMode::new) | |||
,setMapperFlags(0x0231, HwmfMisc.WmfSetMapperFlags::new) | |||
,setPalEntries(0x0037, HwmfPalette.WmfSetPaletteEntries::new) | |||
,setPixel(0x041f, HwmfDraw.WmfSetPixel::new) | |||
,setPolyFillMode(0x0106, HwmfFill.WmfSetPolyfillMode::new) | |||
,setRelabs(0x0105, HwmfMisc.WmfSetRelabs::new) | |||
,setRop2(0x0104, HwmfMisc.WmfSetRop2::new) | |||
,setStretchBltMode(0x0107, HwmfMisc.WmfSetStretchBltMode::new) | |||
,setTextAlign(0x012e, HwmfText.WmfSetTextAlign::new) | |||
,setTextCharExtra(0x0108, HwmfText.WmfSetTextCharExtra::new) | |||
,setTextColor(0x0209, HwmfText.WmfSetTextColor::new) | |||
,setTextJustification(0x020a, HwmfText.WmfSetTextJustification::new) | |||
,setViewportExt(0x020e, HwmfWindowing.WmfSetViewportExt::new) | |||
,setViewportOrg(0x020d, HwmfWindowing.WmfSetViewportOrg::new) | |||
,setWindowExt(0x020c, HwmfWindowing.WmfSetWindowExt::new) | |||
,setWindowOrg(0x020b, HwmfWindowing.WmfSetWindowOrg::new) | |||
,stretchBlt(0x0b23, HwmfFill.WmfStretchBlt::new) | |||
,stretchDib(0x0f43, HwmfFill.WmfStretchDib::new) | |||
,textOut(0x0521, HwmfText.WmfTextOut::new) | |||
; | |||
public final int id; | |||
public final Class<? extends HwmfRecord> clazz; | |||
public final Supplier<? extends HwmfRecord> constructor; | |||
HwmfRecordType(int id, Class<? extends HwmfRecord> clazz) { | |||
HwmfRecordType(int id, Supplier<? extends HwmfRecord> constructor) { | |||
this.id = id; | |||
this.clazz = clazz; | |||
this.constructor = constructor; | |||
} | |||
public static HwmfRecordType getById(int id) { |
@@ -17,8 +17,16 @@ | |||
package org.apache.poi.hwmf.record; | |||
import static org.apache.poi.hwmf.record.HwmfDraw.readPointS; | |||
import static org.apache.poi.hwmf.record.HwmfDraw.readRectS; | |||
import java.awt.geom.Point2D; | |||
import java.awt.geom.Rectangle2D; | |||
import java.io.ByteArrayInputStream; | |||
import java.io.EOFException; | |||
import java.io.IOException; | |||
import java.io.InputStreamReader; | |||
import java.io.Reader; | |||
import java.nio.charset.Charset; | |||
import org.apache.poi.hwmf.draw.HwmfDrawProperties; | |||
@@ -31,6 +39,7 @@ import org.apache.poi.util.LittleEndianConsts; | |||
import org.apache.poi.util.LittleEndianInputStream; | |||
import org.apache.poi.util.POILogFactory; | |||
import org.apache.poi.util.POILogger; | |||
import org.apache.poi.util.RecordFormatException; | |||
public class HwmfText { | |||
private static final POILogger logger = POILogFactory.getLogger(HwmfText.class); | |||
@@ -52,7 +61,7 @@ public class HwmfText { | |||
private int charExtra; | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.setTextCharExtra; | |||
} | |||
@@ -76,7 +85,7 @@ public class HwmfText { | |||
private HwmfColorRef colorRef; | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.setTextColor; | |||
} | |||
@@ -112,7 +121,7 @@ public class HwmfText { | |||
private int breakExtra; | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.setBkColor; | |||
} | |||
@@ -159,7 +168,7 @@ public class HwmfText { | |||
private int xStart; | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.textOut; | |||
} | |||
@@ -195,40 +204,47 @@ public class HwmfText { | |||
return ret; | |||
} | |||
} | |||
/** | |||
* The META_EXTTEXTOUT record outputs text by using the font, background color, and text color that | |||
* are defined in the playback device context. Optionally, dimensions can be provided for clipping, | |||
* opaquing, or both. | |||
*/ | |||
public static class WmfExtTextOut implements HwmfRecord { | |||
public static class WmfExtTextOutOptions { | |||
/** | |||
* Indicates that the background color that is defined in the playback device context | |||
* Indicates that the background color that is defined in the playback device context | |||
* SHOULD be used to fill the rectangle. | |||
*/ | |||
*/ | |||
private static final BitField ETO_OPAQUE = BitFieldFactory.getInstance(0x0002); | |||
/** | |||
* Indicates that the text SHOULD be clipped to the rectangle. | |||
*/ | |||
private static final BitField ETO_CLIPPED = BitFieldFactory.getInstance(0x0004); | |||
/** | |||
* Indicates that the string to be output SHOULD NOT require further processing | |||
* with respect to the placement of the characters, and an array of character | |||
* placement values SHOULD be provided. This character placement process is | |||
* Indicates that the string to be output SHOULD NOT require further processing | |||
* with respect to the placement of the characters, and an array of character | |||
* placement values SHOULD be provided. This character placement process is | |||
* useful for fonts in which diacritical characters affect character spacing. | |||
*/ | |||
private static final BitField ETO_GLYPH_INDEX = BitFieldFactory.getInstance(0x0010); | |||
/** | |||
* Indicates that the text MUST be laid out in right-to-left reading order, instead of | |||
* the default left-to-right order. This SHOULD be applied only when the font that is | |||
* Indicates that the text MUST be laid out in right-to-left reading order, instead of | |||
* the default left-to-right order. This SHOULD be applied only when the font that is | |||
* defined in the playback device context is either Hebrew or Arabic. | |||
*/ | |||
private static final BitField ETO_RTLREADING = BitFieldFactory.getInstance(0x0080); | |||
/** | |||
* This bit indicates that the record does not specify a bounding rectangle for the | |||
* text output. | |||
*/ | |||
private static final BitField ETO_NO_RECT = BitFieldFactory.getInstance(0x0100); | |||
/** | |||
* This bit indicates that the codes for characters in an output text string are 8 bits, | |||
* derived from the low bytes of 16-bit Unicode UTF16-LE character codes, in which | |||
* the high byte is assumed to be 0. | |||
*/ | |||
private static final BitField ETO_SMALL_CHARS = BitFieldFactory.getInstance(0x0200); | |||
/** | |||
* Indicates that to display numbers, digits appropriate to the locale SHOULD be used. | |||
*/ | |||
@@ -240,32 +256,58 @@ public class HwmfText { | |||
private static final BitField ETO_NUMERICSLATIN = BitFieldFactory.getInstance(0x0800); | |||
/** | |||
* Indicates that both horizontal and vertical character displacement values | |||
* This bit indicates that no special operating system processing for glyph placement | |||
* should be performed on right-to-left strings; that is, all glyph positioning | |||
* SHOULD be taken care of by drawing and state records in the metafile | |||
*/ | |||
private static final BitField ETO_IGNORELANGUAGE = BitFieldFactory.getInstance(0x1000); | |||
/** | |||
* Indicates that both horizontal and vertical character displacement values | |||
* SHOULD be provided. | |||
*/ | |||
private static final BitField ETO_PDY = BitFieldFactory.getInstance(0x2000); | |||
/** This bit is reserved and SHOULD NOT be used. */ | |||
private static final BitField ETO_REVERSE_INDEX_MAP = BitFieldFactory.getInstance(0x10000); | |||
protected int flag; | |||
public int init(LittleEndianInputStream leis) { | |||
flag = leis.readUShort(); | |||
return LittleEndianConsts.SHORT_SIZE; | |||
} | |||
public boolean isOpaque() { | |||
return ETO_OPAQUE.isSet(flag); | |||
} | |||
public boolean isClipped() { | |||
return ETO_CLIPPED.isSet(flag); | |||
} | |||
} | |||
/** | |||
* The META_EXTTEXTOUT record outputs text by using the font, background color, and text color that | |||
* are defined in the playback device context. Optionally, dimensions can be provided for clipping, | |||
* opaquing, or both. | |||
*/ | |||
public static class WmfExtTextOut implements HwmfRecord { | |||
/** | |||
* A 16-bit signed integer that defines the y-coordinate, in logical units, where the | |||
text string is to be located. | |||
* The location, in logical units, where the text string is to be placed. | |||
*/ | |||
private int y; | |||
protected final Point2D reference = new Point2D.Double(); | |||
/** | |||
* A 16-bit signed integer that defines the x-coordinate, in logical units, where the | |||
text string is to be located. | |||
* A 16-bit signed integer that defines the length of the string. | |||
*/ | |||
private int x; | |||
protected int stringLength; | |||
/** | |||
* A 16-bit signed integer that defines the length of the string. | |||
* A 16-bit unsigned integer that defines the use of the application-defined | |||
* rectangle. This member can be a combination of one or more values in the | |||
* ExtTextOutOptions Flags (ETO_*) | |||
*/ | |||
private int stringLength; | |||
/** | |||
* A 16-bit unsigned integer that defines the use of the application-defined | |||
* rectangle. This member can be a combination of one or more values in the | |||
* ExtTextOutOptions Flags (ETO_*) | |||
*/ | |||
private int fwOpts; | |||
protected final WmfExtTextOutOptions options; | |||
/** | |||
* An optional 8-byte Rect Object (section 2.2.2.18) that defines the | |||
* dimensions, in logical coordinates, of a rectangle that is used for clipping, opaquing, or both. | |||
@@ -274,14 +316,14 @@ public class HwmfText { | |||
* Each value is a 16-bit signed integer that defines the coordinate, in logical coordinates, of | |||
* the upper-left corner of the rectangle | |||
*/ | |||
private int left,top,right,bottom; | |||
protected final Rectangle2D bounds = new Rectangle2D.Double(); | |||
/** | |||
* A variable-length string that specifies the text to be drawn. The string does | |||
* not need to be null-terminated, because StringLength specifies the length of the string. If | |||
* the length is odd, an extra byte is placed after it so that the following member (optional Dx) is | |||
* aligned on a 16-bit boundary. | |||
*/ | |||
private byte[] rawTextBytes; | |||
protected byte[] rawTextBytes; | |||
/** | |||
* An optional array of 16-bit signed integers that indicate the distance between | |||
* origins of adjacent character cells. For example, Dx[i] logical units separate the origins of | |||
@@ -289,9 +331,17 @@ public class HwmfText { | |||
* number of values as there are characters in the string. | |||
*/ | |||
private int dx[]; | |||
public WmfExtTextOut() { | |||
this(new WmfExtTextOutOptions()); | |||
} | |||
protected WmfExtTextOut(WmfExtTextOutOptions options) { | |||
this.options = options; | |||
} | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.extTextOut; | |||
} | |||
@@ -299,22 +349,17 @@ public class HwmfText { | |||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { | |||
// -6 bytes of record function and length header | |||
final int remainingRecordSize = (int)(recordSize-6); | |||
y = leis.readShort(); | |||
x = leis.readShort(); | |||
int size = readPointS(leis, reference); | |||
stringLength = leis.readShort(); | |||
fwOpts = leis.readUShort(); | |||
int size = 4*LittleEndianConsts.SHORT_SIZE; | |||
size += LittleEndianConsts.SHORT_SIZE; | |||
size += options.init(leis); | |||
// Check if we have a rectangle | |||
if ((ETO_OPAQUE.isSet(fwOpts) || ETO_CLIPPED.isSet(fwOpts)) && size+8<=remainingRecordSize) { | |||
// the bounding rectangle is optional and only read when fwOpts are given | |||
left = leis.readShort(); | |||
top = leis.readShort(); | |||
right = leis.readShort(); | |||
bottom = leis.readShort(); | |||
size += 4*LittleEndianConsts.SHORT_SIZE; | |||
if ((options.isOpaque() || options.isClipped()) && size+8<=remainingRecordSize) { | |||
// the bounding rectangle is optional and only read when options are given | |||
size += readRectS(leis, bounds); | |||
} | |||
rawTextBytes = IOUtils.safelyAllocate(stringLength+(stringLength&1), MAX_RECORD_LENGTH); | |||
@@ -342,12 +387,46 @@ public class HwmfText { | |||
@Override | |||
public void draw(HwmfGraphics ctx) { | |||
Rectangle2D bounds = new Rectangle2D.Double(x, y, 0, 0); | |||
Rectangle2D bounds = new Rectangle2D.Double(reference.getX(), reference.getY(), 0, 0); | |||
ctx.drawString(getTextBytes(), bounds, dx); | |||
} | |||
public String getText(Charset charset) { | |||
return new String(getTextBytes(), charset); | |||
public String getText(Charset charset) throws IOException { | |||
StringBuilder sb = new StringBuilder(); | |||
try (Reader r = new InputStreamReader(new ByteArrayInputStream(rawTextBytes), charset)) { | |||
for (int i = 0; i < stringLength; i++) { | |||
sb.appendCodePoint(readCodePoint(r)); | |||
} | |||
} | |||
return sb.toString(); | |||
} | |||
//TODO: move this to IOUtils? | |||
private int readCodePoint(Reader r) throws IOException { | |||
int c1 = r.read(); | |||
if (c1 == -1) { | |||
throw new EOFException("Tried to read beyond byte array"); | |||
} | |||
if (!Character.isHighSurrogate((char)c1)) { | |||
return c1; | |||
} | |||
int c2 = r.read(); | |||
if (c2 == -1) { | |||
throw new EOFException("Tried to read beyond byte array"); | |||
} | |||
if (!Character.isLowSurrogate((char)c2)) { | |||
throw new RecordFormatException("Expected low surrogate after high surrogate"); | |||
} | |||
return Character.toCodePoint((char)c1, (char)c2); | |||
} | |||
public Point2D getReference() { | |||
return reference; | |||
} | |||
public Rectangle2D getBounds() { | |||
return bounds; | |||
} | |||
/** | |||
@@ -482,10 +561,10 @@ public class HwmfText { | |||
* for text with a horizontal baseline, and VerticalTextAlignmentMode Flags | |||
* for text with a vertical baseline. | |||
*/ | |||
private int textAlignmentMode; | |||
protected int textAlignmentMode; | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.setTextAlign; | |||
} | |||
@@ -533,17 +612,24 @@ public class HwmfText { | |||
} | |||
public static class WmfCreateFontIndirect implements HwmfRecord, HwmfObjectTableEntry { | |||
private HwmfFont font; | |||
protected final HwmfFont font; | |||
public WmfCreateFontIndirect() { | |||
this(new HwmfFont()); | |||
} | |||
protected WmfCreateFontIndirect(HwmfFont font) { | |||
this.font = font; | |||
} | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.createFontIndirect; | |||
} | |||
@Override | |||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { | |||
font = new HwmfFont(); | |||
return font.init(leis); | |||
return font.init(leis, recordSize); | |||
} | |||
@Override |
@@ -33,18 +33,14 @@ public class HwmfWindowing { | |||
*/ | |||
public static class WmfSetViewportOrg implements HwmfRecord { | |||
/** | |||
* A 16-bit signed integer that defines the vertical offset, in device units. | |||
*/ | |||
private int y; | |||
/** A signed integer that defines the vertical offset, in device units. */ | |||
protected int y; | |||
/** | |||
* A 16-bit signed integer that defines the horizontal offset, in device units. | |||
*/ | |||
private int x; | |||
/** A signed integer that defines the horizontal offset, in device units. */ | |||
protected int x; | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.setViewportOrg; | |||
} | |||
@@ -67,20 +63,14 @@ public class HwmfWindowing { | |||
*/ | |||
public static class WmfSetViewportExt implements HwmfRecord { | |||
/** | |||
* A 16-bit signed integer that defines the vertical extent | |||
* of the viewport in device units. | |||
*/ | |||
private int height; | |||
/** A signed integer that defines the vertical extent of the viewport in device units. */ | |||
protected int height; | |||
/** | |||
* A 16-bit signed integer that defines the horizontal extent | |||
* of the viewport in device units. | |||
*/ | |||
private int width; | |||
/** A signed integer that defines the horizontal extent of the viewport in device units. */ | |||
protected int width; | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.setViewportExt; | |||
} | |||
@@ -114,7 +104,7 @@ public class HwmfWindowing { | |||
private int xOffset; | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.offsetViewportOrg; | |||
} | |||
@@ -139,18 +129,14 @@ public class HwmfWindowing { | |||
*/ | |||
public static class WmfSetWindowOrg implements HwmfRecord { | |||
/** | |||
* A 16-bit signed integer that defines the y-coordinate, in logical units. | |||
*/ | |||
private int y; | |||
/** A signed integer that defines the y-coordinate, in logical units. */ | |||
protected int y; | |||
/** | |||
* A 16-bit signed integer that defines the x-coordinate, in logical units. | |||
*/ | |||
private int x; | |||
/** A signed integer that defines the x-coordinate, in logical units. */ | |||
protected int x; | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.setWindowOrg; | |||
} | |||
@@ -182,20 +168,14 @@ public class HwmfWindowing { | |||
*/ | |||
public static class WmfSetWindowExt implements HwmfRecord { | |||
/** | |||
* A 16-bit signed integer that defines the vertical extent of | |||
* the window in logical units. | |||
*/ | |||
private int height; | |||
/** A signed integer that defines the vertical extent of the window in logical units. */ | |||
protected int height; | |||
/** | |||
* A 16-bit signed integer that defines the horizontal extent of | |||
* the window in logical units. | |||
*/ | |||
private int width; | |||
/** A signed integer that defines the horizontal extent of the window in logical units. */ | |||
protected int width; | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.setWindowExt; | |||
} | |||
@@ -238,7 +218,7 @@ public class HwmfWindowing { | |||
private int xOffset; | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.offsetWindowOrg; | |||
} | |||
@@ -264,31 +244,31 @@ public class HwmfWindowing { | |||
public static class WmfScaleWindowExt implements HwmfRecord { | |||
/** | |||
* A 16-bit signed integer that defines the amount by which to divide the | |||
* A signed integer that defines the amount by which to divide the | |||
* result of multiplying the current y-extent by the value of the yNum member. | |||
*/ | |||
private int yDenom; | |||
protected int yDenom; | |||
/** | |||
* A 16-bit signed integer that defines the amount by which to multiply the | |||
* A signed integer that defines the amount by which to multiply the | |||
* current y-extent. | |||
*/ | |||
private int yNum; | |||
protected int yNum; | |||
/** | |||
* A 16-bit signed integer that defines the amount by which to divide the | |||
* A signed integer that defines the amount by which to divide the | |||
* result of multiplying the current x-extent by the value of the xNum member. | |||
*/ | |||
private int xDenom; | |||
protected int xDenom; | |||
/** | |||
* A 16-bit signed integer that defines the amount by which to multiply the | |||
* A signed integer that defines the amount by which to multiply the | |||
* current x-extent. | |||
*/ | |||
private int xNum; | |||
protected int xNum; | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.scaleWindowExt; | |||
} | |||
@@ -320,31 +300,31 @@ public class HwmfWindowing { | |||
public static class WmfScaleViewportExt implements HwmfRecord { | |||
/** | |||
* A 16-bit signed integer that defines the amount by which to divide the | |||
* A signed integer that defines the amount by which to divide the | |||
* result of multiplying the current y-extent by the value of the yNum member. | |||
*/ | |||
private int yDenom; | |||
protected int yDenom; | |||
/** | |||
* A 16-bit signed integer that defines the amount by which to multiply the | |||
* A signed integer that defines the amount by which to multiply the | |||
* current y-extent. | |||
*/ | |||
private int yNum; | |||
protected int yNum; | |||
/** | |||
* A 16-bit signed integer that defines the amount by which to divide the | |||
* A signed integer that defines the amount by which to divide the | |||
* result of multiplying the current x-extent by the value of the xNum member. | |||
*/ | |||
private int xDenom; | |||
protected int xDenom; | |||
/** | |||
* A 16-bit signed integer that defines the amount by which to multiply the | |||
* A signed integer that defines the amount by which to multiply the | |||
* current x-extent. | |||
*/ | |||
private int xNum; | |||
protected int xNum; | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.scaleViewportExt; | |||
} | |||
@@ -376,17 +356,17 @@ public class HwmfWindowing { | |||
public static class WmfOffsetClipRgn implements HwmfRecord, HwmfObjectTableEntry { | |||
/** | |||
* A 16-bit signed integer that defines the number of logical units to move up or down. | |||
* A signed integer that defines the number of logical units to move up or down. | |||
*/ | |||
private int yOffset; | |||
protected int yOffset; | |||
/** | |||
* A 16-bit signed integer that defines the number of logical units to move left or right. | |||
* A signed integer that defines the number of logical units to move left or right. | |||
*/ | |||
private int xOffset; | |||
protected int xOffset; | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.offsetClipRgn; | |||
} | |||
@@ -413,41 +393,29 @@ public class HwmfWindowing { | |||
*/ | |||
public static class WmfExcludeClipRect implements HwmfRecord, HwmfObjectTableEntry { | |||
/** | |||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the | |||
* lower-right corner of the rectangle. | |||
*/ | |||
private int bottom; | |||
/** | |||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the | |||
* lower-right corner of the rectangle. | |||
*/ | |||
private int right; | |||
/** | |||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the | |||
* upper-left corner of the rectangle. | |||
*/ | |||
private int top; | |||
/** | |||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the | |||
* upper-left corner of the rectangle. | |||
*/ | |||
private int left; | |||
/** a rectangle in logical units */ | |||
protected final Rectangle2D bounds = new Rectangle2D.Double(); | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.excludeClipRect; | |||
} | |||
@Override | |||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { | |||
bottom = leis.readShort(); | |||
right = leis.readShort(); | |||
top = leis.readShort(); | |||
left = leis.readShort(); | |||
// A 16-bit signed integer that defines the y-coordinate, in logical units, of the | |||
// lower-right corner of the rectangle. | |||
final int bottom = leis.readShort(); | |||
// A 16-bit signed integer that defines the x-coordinate, in logical units, of the | |||
// lower-right corner of the rectangle. | |||
final int right = leis.readShort(); | |||
// A 16-bit signed integer that defines the y-coordinate, in logical units, of the | |||
// upper-left corner of the rectangle. | |||
final int top = leis.readShort(); | |||
// A 16-bit signed integer that defines the x-coordinate, in logical units, of the | |||
// upper-left corner of the rectangle. | |||
final int left = leis.readShort(); | |||
bounds.setRect(left, top, right-left, bottom-top); | |||
return 4*LittleEndianConsts.SHORT_SIZE; | |||
} | |||
@@ -468,41 +436,29 @@ public class HwmfWindowing { | |||
*/ | |||
public static class WmfIntersectClipRect implements HwmfRecord, HwmfObjectTableEntry { | |||
/** | |||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the | |||
* lower-right corner of the rectangle. | |||
*/ | |||
private int bottom; | |||
/** | |||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the | |||
* lower-right corner of the rectangle. | |||
*/ | |||
private int right; | |||
/** | |||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the | |||
* upper-left corner of the rectangle. | |||
*/ | |||
private int top; | |||
/** | |||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the | |||
* upper-left corner of the rectangle. | |||
*/ | |||
private int left; | |||
/** a rectangle in logical units */ | |||
protected final Rectangle2D bounds = new Rectangle2D.Double(); | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.intersectClipRect; | |||
} | |||
@Override | |||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { | |||
bottom = leis.readShort(); | |||
right = leis.readShort(); | |||
top = leis.readShort(); | |||
left = leis.readShort(); | |||
// A 16-bit signed integer that defines the y-coordinate, in logical units, of the | |||
// lower-right corner of the rectangle. | |||
final int bottom = leis.readShort(); | |||
// A 16-bit signed integer that defines the x-coordinate, in logical units, of the | |||
// lower-right corner of the rectangle. | |||
final int right = leis.readShort(); | |||
// A 16-bit signed integer that defines the y-coordinate, in logical units, of the | |||
// upper-left corner of the rectangle. | |||
final int top = leis.readShort(); | |||
// A 16-bit signed integer that defines the x-coordinate, in logical units, of the | |||
// upper-left corner of the rectangle. | |||
final int left = leis.readShort(); | |||
bounds.setRect(left, top, right-left, bottom-top); | |||
return 4*LittleEndianConsts.SHORT_SIZE; | |||
} | |||
@@ -528,7 +484,7 @@ public class HwmfWindowing { | |||
private int region; | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.selectClipRegion; | |||
} | |||
@@ -650,7 +606,7 @@ public class HwmfWindowing { | |||
private WmfScanObject scanObjects[]; | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.createRegion; | |||
} | |||
@@ -51,8 +51,7 @@ public class HwmfPicture { | |||
public HwmfPicture(InputStream inputStream) throws IOException { | |||
try (BufferedInputStream bis = new BufferedInputStream(inputStream, 10000); | |||
LittleEndianInputStream leis = new LittleEndianInputStream(bis)) { | |||
try (LittleEndianInputStream leis = new LittleEndianInputStream(inputStream)) { | |||
placeableHeader = HwmfPlaceableHeader.readHeader(leis); | |||
header = new HwmfHeader(leis); | |||
@@ -82,17 +81,12 @@ public class HwmfPicture { | |||
if (wrt == HwmfRecordType.eof) { | |||
break; | |||
} | |||
if (wrt.clazz == null) { | |||
if (wrt.constructor == null) { | |||
throw new IOException("unsupported record type: "+recordFunction); | |||
} | |||
HwmfRecord wr; | |||
try { | |||
wr = wrt.clazz.newInstance(); | |||
records.add(wr); | |||
} catch (Exception e) { | |||
throw (IOException)new IOException("can't create wmf record").initCause(e); | |||
} | |||
final HwmfRecord wr = wrt.constructor.get(); | |||
records.add(wr); | |||
consumedSize += wr.init(leis, recordSize, recordFunction); | |||
int remainingSize = (int)(recordSize - consumedSize); |
@@ -1,198 +0,0 @@ | |||
/* ==================================================================== | |||
Licensed to the Apache Software Foundation (ASF) under one or more | |||
contributor license agreements. See the NOTICE file distributed with | |||
this work for additional information regarding copyright ownership. | |||
The ASF licenses this file to You under the Apache License, Version 2.0 | |||
(the "License"); you may not use this file except in compliance with | |||
the License. You may obtain a copy of the License at | |||
http://www.apache.org/licenses/LICENSE-2.0 | |||
Unless required by applicable law or agreed to in writing, software | |||
distributed under the License is distributed on an "AS IS" BASIS, | |||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
See the License for the specific language governing permissions and | |||
limitations under the License. | |||
==================================================================== */ | |||
package org.apache.poi.hemf.extractor; | |||
import static org.apache.poi.POITestCase.assertContains; | |||
import static org.junit.Assert.assertEquals; | |||
import static org.junit.Assert.assertTrue; | |||
import java.io.ByteArrayInputStream; | |||
import java.io.ByteArrayOutputStream; | |||
import java.io.InputStream; | |||
import java.util.HashSet; | |||
import java.util.Set; | |||
import org.apache.poi.POIDataSamples; | |||
import org.apache.poi.hemf.record.AbstractHemfComment; | |||
import org.apache.poi.hemf.record.HemfCommentPublic; | |||
import org.apache.poi.hemf.record.HemfCommentRecord; | |||
import org.apache.poi.hemf.record.HemfHeader; | |||
import org.apache.poi.hemf.record.HemfRecord; | |||
import org.apache.poi.hemf.record.HemfRecordType; | |||
import org.apache.poi.hemf.record.HemfText; | |||
import org.apache.poi.util.IOUtils; | |||
import org.apache.poi.util.RecordFormatException; | |||
import org.junit.Test; | |||
public class HemfExtractorTest { | |||
@Test | |||
public void testBasicWindows() throws Exception { | |||
InputStream is = POIDataSamples.getSpreadSheetInstance().openResourceAsStream("SimpleEMF_windows.emf"); | |||
HemfExtractor ex = new HemfExtractor(is); | |||
HemfHeader header = ex.getHeader(); | |||
assertEquals(27864, header.getBytes()); | |||
assertEquals(31, header.getRecords()); | |||
assertEquals(3, header.getHandles()); | |||
assertEquals(346000, header.getMicrometersX()); | |||
assertEquals(194000, header.getMicrometersY()); | |||
int records = 0; | |||
for (HemfRecord record : ex) { | |||
records++; | |||
} | |||
assertEquals(header.getRecords() - 1, records); | |||
} | |||
@Test | |||
public void testBasicMac() throws Exception { | |||
InputStream is = | |||
POIDataSamples.getSpreadSheetInstance().openResourceAsStream("SimpleEMF_mac.emf"); | |||
HemfExtractor ex = new HemfExtractor(is); | |||
HemfHeader header = ex.getHeader(); | |||
int records = 0; | |||
boolean extractedData = false; | |||
for (HemfRecord record : ex) { | |||
if (record.getRecordType() == HemfRecordType.comment) { | |||
AbstractHemfComment comment = ((HemfCommentRecord) record).getComment(); | |||
if (comment instanceof HemfCommentPublic.MultiFormats) { | |||
for (HemfCommentPublic.HemfMultiFormatsData d : ((HemfCommentPublic.MultiFormats) comment).getData()) { | |||
byte[] data = d.getData(); | |||
//make sure header starts at 0 | |||
assertEquals('%', data[0]); | |||
assertEquals('P', data[1]); | |||
assertEquals('D', data[2]); | |||
assertEquals('F', data[3]); | |||
//make sure byte array ends at EOF\n | |||
assertEquals('E', data[data.length - 4]); | |||
assertEquals('O', data[data.length - 3]); | |||
assertEquals('F', data[data.length - 2]); | |||
assertEquals('\n', data[data.length - 1]); | |||
extractedData = true; | |||
} | |||
} | |||
} | |||
records++; | |||
} | |||
assertTrue(extractedData); | |||
assertEquals(header.getRecords() - 1, records); | |||
} | |||
@Test | |||
public void testMacText() throws Exception { | |||
InputStream is = | |||
POIDataSamples.getSpreadSheetInstance().openResourceAsStream("SimpleEMF_mac.emf"); | |||
HemfExtractor ex = new HemfExtractor(is); | |||
long lastY = -1; | |||
long lastX = -1; | |||
long fudgeFactorX = 1000;//derive this from the font information! | |||
StringBuilder sb = new StringBuilder(); | |||
for (HemfRecord record : ex) { | |||
if (record.getRecordType().equals(HemfRecordType.exttextoutw)) { | |||
HemfText.ExtTextOutW extTextOutW = (HemfText.ExtTextOutW) record; | |||
if (lastY > -1 && lastY != extTextOutW.getY()) { | |||
sb.append("\n"); | |||
lastX = -1; | |||
} | |||
if (lastX > -1 && extTextOutW.getX() - lastX > fudgeFactorX) { | |||
sb.append(" "); | |||
} | |||
sb.append(extTextOutW.getText()); | |||
lastY = extTextOutW.getY(); | |||
lastX = extTextOutW.getX(); | |||
} | |||
} | |||
String txt = sb.toString(); | |||
assertContains(txt, "Tika http://incubator.apache.org"); | |||
assertContains(txt, "Latest News\n"); | |||
} | |||
@Test | |||
public void testWindowsText() throws Exception { | |||
InputStream is = POIDataSamples.getSpreadSheetInstance().openResourceAsStream("SimpleEMF_windows.emf"); | |||
HemfExtractor ex = new HemfExtractor(is); | |||
long lastY = -1; | |||
long lastX = -1; | |||
long fudgeFactorX = 1000;//derive this from the font or frame/bounds information | |||
StringBuilder sb = new StringBuilder(); | |||
Set<String> expectedParts = new HashSet<>(); | |||
expectedParts.add("C:\\Users\\tallison\\"); | |||
expectedParts.add("testPDF.pdf"); | |||
int foundExpected = 0; | |||
for (HemfRecord record : ex) { | |||
if (record.getRecordType().equals(HemfRecordType.exttextoutw)) { | |||
HemfText.ExtTextOutW extTextOutW = (HemfText.ExtTextOutW) record; | |||
if (lastY > -1 && lastY != extTextOutW.getY()) { | |||
sb.append("\n"); | |||
lastX = -1; | |||
} | |||
if (lastX > -1 && extTextOutW.getX() - lastX > fudgeFactorX) { | |||
sb.append(" "); | |||
} | |||
String txt = extTextOutW.getText(); | |||
if (expectedParts.contains(txt)) { | |||
foundExpected++; | |||
} | |||
sb.append(txt); | |||
lastY = extTextOutW.getY(); | |||
lastX = extTextOutW.getX(); | |||
} | |||
} | |||
String txt = sb.toString(); | |||
assertContains(txt, "C:\\Users\\tallison\\\n"); | |||
assertContains(txt, "asf2-git-1.x\\tika-\n"); | |||
assertEquals(expectedParts.size(), foundExpected); | |||
} | |||
@Test(expected = RecordFormatException.class) | |||
public void testInfiniteLoopOnFile() throws Exception { | |||
InputStream is = null; | |||
try { | |||
is = POIDataSamples.getSpreadSheetInstance().openResourceAsStream("61294.emf"); | |||
HemfExtractor ex = new HemfExtractor(is); | |||
for (HemfRecord record : ex) { | |||
} | |||
} finally { | |||
IOUtils.closeQuietly(is); | |||
} | |||
} | |||
@Test(expected = RecordFormatException.class) | |||
public void testInfiniteLoopOnByteArray() throws Exception { | |||
InputStream is = POIDataSamples.getSpreadSheetInstance().openResourceAsStream("61294.emf"); | |||
ByteArrayOutputStream bos = new ByteArrayOutputStream(); | |||
IOUtils.copy(is, bos); | |||
is.close(); | |||
HemfExtractor ex = new HemfExtractor(new ByteArrayInputStream(bos.toByteArray())); | |||
for (HemfRecord record : ex) { | |||
} | |||
} | |||
/* | |||
govdocs1 064213.doc-0.emf contains an example of extextouta | |||
*/ | |||
} |
@@ -25,13 +25,13 @@ import java.util.ArrayList; | |||
import java.util.List; | |||
import org.apache.poi.POIDataSamples; | |||
import org.apache.poi.hemf.extractor.HemfExtractor; | |||
import org.apache.poi.hemf.hemfplus.record.HemfPlusHeader; | |||
import org.apache.poi.hemf.hemfplus.record.HemfPlusRecord; | |||
import org.apache.poi.hemf.hemfplus.record.HemfPlusRecordType; | |||
import org.apache.poi.hemf.record.HemfCommentEMFPlus; | |||
import org.apache.poi.hemf.record.HemfCommentRecord; | |||
import org.apache.poi.hemf.record.HemfRecord; | |||
import org.apache.poi.hemf.record.emf.HemfComment.EmfComment; | |||
import org.apache.poi.hemf.record.emf.HemfComment.EmfCommentDataPlus; | |||
import org.apache.poi.hemf.record.emf.HemfRecord; | |||
import org.apache.poi.hemf.record.emfplus.HemfPlusHeader; | |||
import org.apache.poi.hemf.record.emfplus.HemfPlusRecord; | |||
import org.apache.poi.hemf.record.emfplus.HemfPlusRecordType; | |||
import org.apache.poi.hemf.usermodel.HemfPicture; | |||
import org.junit.Test; | |||
public class HemfPlusExtractorTest { | |||
@@ -39,7 +39,7 @@ public class HemfPlusExtractorTest { | |||
@Test | |||
public void testBasic() throws Exception { | |||
//test header | |||
HemfCommentEMFPlus emfPlus = getCommentRecord("SimpleEMF_windows.emf", 0); | |||
EmfCommentDataPlus emfPlus = getCommentRecord("SimpleEMF_windows.emf", 0); | |||
List<HemfPlusRecord> records = emfPlus.getRecords(); | |||
assertEquals(1, records.size()); | |||
assertEquals(HemfPlusRecordType.header, records.get(0).getRecordType()); | |||
@@ -72,24 +72,20 @@ public class HemfPlusExtractorTest { | |||
} | |||
private HemfCommentEMFPlus getCommentRecord(String testFileName, int recordIndex) throws Exception { | |||
InputStream is = null; | |||
HemfCommentEMFPlus returnRecord = null; | |||
private EmfCommentDataPlus getCommentRecord(String testFileName, int recordIndex) throws Exception { | |||
EmfCommentDataPlus returnRecord = null; | |||
try { | |||
is = POIDataSamples.getSpreadSheetInstance().openResourceAsStream(testFileName); | |||
HemfExtractor ex = new HemfExtractor(is); | |||
try (InputStream is = POIDataSamples.getSpreadSheetInstance().openResourceAsStream(testFileName)) { | |||
HemfPicture ex = new HemfPicture(is); | |||
int i = 0; | |||
for (HemfRecord record : ex) { | |||
if (i == recordIndex) { | |||
HemfCommentRecord commentRecord = ((HemfCommentRecord) record); | |||
returnRecord = (HemfCommentEMFPlus) commentRecord.getComment(); | |||
EmfComment commentRecord = ((EmfComment) record); | |||
returnRecord = (EmfCommentDataPlus) commentRecord.getCommentData(); | |||
break; | |||
} | |||
i++; | |||
} | |||
} finally { | |||
is.close(); | |||
} | |||
return returnRecord; | |||
} |
@@ -0,0 +1,201 @@ | |||
/* ==================================================================== | |||
Licensed to the Apache Software Foundation (ASF) under one or more | |||
contributor license agreements. See the NOTICE file distributed with | |||
this work for additional information regarding copyright ownership. | |||
The ASF licenses this file to You under the Apache License, Version 2.0 | |||
(the "License"); you may not use this file except in compliance with | |||
the License. You may obtain a copy of the License at | |||
http://www.apache.org/licenses/LICENSE-2.0 | |||
Unless required by applicable law or agreed to in writing, software | |||
distributed under the License is distributed on an "AS IS" BASIS, | |||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
See the License for the specific language governing permissions and | |||
limitations under the License. | |||
==================================================================== */ | |||
package org.apache.poi.hemf.usermodel; | |||
import static org.apache.poi.POITestCase.assertContains; | |||
import static org.junit.Assert.assertEquals; | |||
import static org.junit.Assert.assertTrue; | |||
import java.awt.geom.Point2D; | |||
import java.io.ByteArrayInputStream; | |||
import java.io.ByteArrayOutputStream; | |||
import java.io.InputStream; | |||
import java.util.HashSet; | |||
import java.util.List; | |||
import java.util.Set; | |||
import org.apache.poi.POIDataSamples; | |||
import org.apache.poi.hemf.record.emf.HemfComment; | |||
import org.apache.poi.hemf.record.emf.HemfComment.EmfComment; | |||
import org.apache.poi.hemf.record.emf.HemfComment.EmfCommentDataFormat; | |||
import org.apache.poi.hemf.record.emf.HemfComment.EmfCommentDataMultiformats; | |||
import org.apache.poi.hemf.record.emf.HemfHeader; | |||
import org.apache.poi.hemf.record.emf.HemfRecord; | |||
import org.apache.poi.hemf.record.emf.HemfRecordType; | |||
import org.apache.poi.hemf.record.emf.HemfText; | |||
import org.apache.poi.util.IOUtils; | |||
import org.apache.poi.util.RecordFormatException; | |||
import org.junit.Test; | |||
public class HemfPictureTest { | |||
private POIDataSamples samples = POIDataSamples.getSpreadSheetInstance(); | |||
@Test | |||
public void testBasicWindows() throws Exception { | |||
try (InputStream is = samples.openResourceAsStream("SimpleEMF_windows.emf")) { | |||
HemfPicture pic = new HemfPicture(is); | |||
HemfHeader header = pic.getHeader(); | |||
assertEquals(27864, header.getBytes()); | |||
assertEquals(31, header.getRecords()); | |||
assertEquals(3, header.getHandles()); | |||
assertEquals(346000, header.getMicrometersX()); | |||
assertEquals(194000, header.getMicrometersY()); | |||
List<HemfRecord> records = pic.getRecords(); | |||
assertEquals(31, records.size()); | |||
} | |||
} | |||
@Test | |||
public void testBasicMac() throws Exception { | |||
try (InputStream is = samples.openResourceAsStream("SimpleEMF_mac.emf")) { | |||
HemfPicture pic = new HemfPicture(is); | |||
HemfHeader header = pic.getHeader(); | |||
int records = 0; | |||
boolean extractedData = false; | |||
for (HemfRecord record : pic) { | |||
if (record.getEmfRecordType() == HemfRecordType.comment) { | |||
HemfComment.EmfCommentData comment = ((EmfComment) record).getCommentData(); | |||
if (comment instanceof EmfCommentDataMultiformats) { | |||
for (EmfCommentDataFormat d : ((EmfCommentDataMultiformats) comment).getFormats()) { | |||
byte[] data = d.getRawData(); | |||
//make sure header starts at 0 | |||
assertEquals('%', data[0]); | |||
assertEquals('P', data[1]); | |||
assertEquals('D', data[2]); | |||
assertEquals('F', data[3]); | |||
//make sure byte array ends at EOF\n | |||
assertEquals('E', data[data.length - 4]); | |||
assertEquals('O', data[data.length - 3]); | |||
assertEquals('F', data[data.length - 2]); | |||
assertEquals('\n', data[data.length - 1]); | |||
extractedData = true; | |||
} | |||
} | |||
} | |||
records++; | |||
} | |||
assertTrue(extractedData); | |||
assertEquals(header.getRecords(), records); | |||
} | |||
} | |||
@Test | |||
public void testMacText() throws Exception { | |||
try (InputStream is = samples.openResourceAsStream("SimpleEMF_mac.emf")) { | |||
HemfPicture pic = new HemfPicture(is); | |||
double lastY = -1; | |||
double lastX = -1; | |||
//derive this from the font information! | |||
long fudgeFactorX = 1000; | |||
StringBuilder sb = new StringBuilder(); | |||
for (HemfRecord record : pic) { | |||
if (record.getEmfRecordType().equals(HemfRecordType.exttextoutw)) { | |||
HemfText.ExtTextOutW extTextOutW = (HemfText.ExtTextOutW) record; | |||
Point2D reference = extTextOutW.getTextObject().getReference(); | |||
if (lastY > -1 && lastY != reference.getY()) { | |||
sb.append("\n"); | |||
lastX = -1; | |||
} | |||
if (lastX > -1 && reference.getX() - lastX > fudgeFactorX) { | |||
sb.append(" "); | |||
} | |||
sb.append(extTextOutW.getText()); | |||
lastY = reference.getY(); | |||
lastX = reference.getX(); | |||
} | |||
} | |||
String txt = sb.toString(); | |||
assertContains(txt, "Tika http://incubator.apache.org"); | |||
assertContains(txt, "Latest News\n"); | |||
} | |||
} | |||
@Test | |||
public void testWindowsText() throws Exception { | |||
try (InputStream is = samples.openResourceAsStream("SimpleEMF_windows.emf")) { | |||
HemfPicture pic = new HemfPicture(is); | |||
double lastY = -1; | |||
double lastX = -1; | |||
long fudgeFactorX = 1000;//derive this from the font or frame/bounds information | |||
StringBuilder sb = new StringBuilder(); | |||
Set<String> expectedParts = new HashSet<>(); | |||
expectedParts.add("C:\\Users\\tallison\\"); | |||
expectedParts.add("testPDF.pdf"); | |||
int foundExpected = 0; | |||
for (HemfRecord record : pic) { | |||
if (record.getEmfRecordType().equals(HemfRecordType.exttextoutw)) { | |||
HemfText.ExtTextOutW extTextOutW = (HemfText.ExtTextOutW) record; | |||
Point2D reference = extTextOutW.getTextObject().getReference(); | |||
if (lastY > -1 && lastY != reference.getY()) { | |||
sb.append("\n"); | |||
lastX = -1; | |||
} | |||
if (lastX > -1 && reference.getX() - lastX > fudgeFactorX) { | |||
sb.append(" "); | |||
} | |||
String txt = extTextOutW.getText(); | |||
if (expectedParts.contains(txt)) { | |||
foundExpected++; | |||
} | |||
sb.append(txt); | |||
lastY = reference.getY(); | |||
lastX = reference.getX(); | |||
} | |||
} | |||
String txt = sb.toString(); | |||
assertContains(txt, "C:\\Users\\tallison\\\n"); | |||
assertContains(txt, "asf2-git-1.x\\tika-\n"); | |||
assertEquals(expectedParts.size(), foundExpected); | |||
} | |||
} | |||
@Test(expected = RecordFormatException.class) | |||
public void testInfiniteLoopOnFile() throws Exception { | |||
try (InputStream is = samples.openResourceAsStream("61294.emf")) { | |||
HemfPicture pic = new HemfPicture(is); | |||
for (HemfRecord record : pic) { | |||
} | |||
} | |||
} | |||
@Test(expected = RecordFormatException.class) | |||
public void testInfiniteLoopOnByteArray() throws Exception { | |||
try (InputStream is = samples.openResourceAsStream("61294.emf")) { | |||
ByteArrayOutputStream bos = new ByteArrayOutputStream(); | |||
IOUtils.copy(is, bos); | |||
is.close(); | |||
HemfPicture pic = new HemfPicture(new ByteArrayInputStream(bos.toByteArray())); | |||
for (HemfRecord record : pic) { | |||
} | |||
} | |||
} | |||
/* | |||
govdocs1 064213.doc-0.emf contains an example of extextouta | |||
*/ | |||
} |
@@ -222,11 +222,11 @@ public class TestHwmfParsing { | |||
//this happens to work on this test file, but you need to | |||
//do what Graphics does by maintaining the stack, etc.! | |||
for (HwmfRecord r : wmf.getRecords()) { | |||
if (r.getRecordType().equals(HwmfRecordType.createFontIndirect)) { | |||
if (r.getWmfRecordType().equals(HwmfRecordType.createFontIndirect)) { | |||
HwmfFont font = ((HwmfText.WmfCreateFontIndirect)r).getFont(); | |||
charset = (font.getCharset().getCharset() == null) ? LocaleUtil.CHARSET_1252 : font.getCharset().getCharset(); | |||
} | |||
if (r.getRecordType().equals(HwmfRecordType.extTextOut)) { | |||
if (r.getWmfRecordType().equals(HwmfRecordType.extTextOut)) { | |||
HwmfText.WmfExtTextOut textOut = (HwmfText.WmfExtTextOut)r; | |||
sb.append(textOut.getText(charset)).append("\n"); | |||
} | |||
@@ -250,11 +250,11 @@ public class TestHwmfParsing { | |||
//this happens to work on this test file, but you need to | |||
//do what Graphics does by maintaining the stack, etc.! | |||
for (HwmfRecord r : wmf.getRecords()) { | |||
if (r.getRecordType().equals(HwmfRecordType.createFontIndirect)) { | |||
if (r.getWmfRecordType().equals(HwmfRecordType.createFontIndirect)) { | |||
HwmfFont font = ((HwmfText.WmfCreateFontIndirect)r).getFont(); | |||
charset = (font.getCharset().getCharset() == null) ? LocaleUtil.CHARSET_1252 : font.getCharset().getCharset(); | |||
} | |||
if (r.getRecordType().equals(HwmfRecordType.extTextOut)) { | |||
if (r.getWmfRecordType().equals(HwmfRecordType.extTextOut)) { | |||
HwmfText.WmfExtTextOut textOut = (HwmfText.WmfExtTextOut)r; | |||
sb.append(textOut.getText(charset)).append("\n"); | |||
} |