mirror of https://github.com/apache/poi.git
#60656 - EMF image support in slideshows
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1849040 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
commit
e896d921a4
|
@ -91,7 +91,7 @@ subprojects {
|
|||
// See https://github.com/melix/japicmp-gradle-plugin
|
||||
apply plugin: 'me.champeau.gradle.japicmp'
|
||||
|
||||
version = '4.0.2-SNAPSHOT'
|
||||
version = '4.1.0-SNAPSHOT'
|
||||
ext {
|
||||
japicmpversion = '4.0.0'
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ under the License.
|
|||
|
||||
<description>The Apache POI project Ant build.</description>
|
||||
|
||||
<property name="version.id" value="4.0.2"/>
|
||||
<property name="version.id" value="4.1.0"/>
|
||||
<property name="release.rc" value="RC1"/>
|
||||
|
||||
<property environment="env"/>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
<artifactId>poi-parent</artifactId>
|
||||
<version>4.0.2-SNAPSHOT</version>
|
||||
<version>4.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>poi-examples</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
<artifactId>poi-parent</artifactId>
|
||||
<version>4.0.2-SNAPSHOT</version>
|
||||
<version>4.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>poi-excelant</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
<artifactId>poi-parent</artifactId>
|
||||
<version>4.0.2-SNAPSHOT</version>
|
||||
<version>4.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>poi-main</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
<artifactId>poi-parent</artifactId>
|
||||
<version>4.0.2-SNAPSHOT</version>
|
||||
<version>4.1.0-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
<artifactId>poi-ooxml-schema-encryption</artifactId>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
<artifactId>poi-parent</artifactId>
|
||||
<version>4.0.2-SNAPSHOT</version>
|
||||
<version>4.1.0-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
<artifactId>poi-ooxml-schema-security</artifactId>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
<artifactId>poi-parent</artifactId>
|
||||
<version>4.0.2-SNAPSHOT</version>
|
||||
<version>4.1.0-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
<artifactId>poi-ooxml-schema</artifactId>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
<artifactId>poi-parent</artifactId>
|
||||
<version>4.0.2-SNAPSHOT</version>
|
||||
<version>4.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>poi-ooxml</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<groupId>org.apache.poi</groupId>
|
||||
<artifactId>poi-parent</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<version>4.0.2-SNAPSHOT</version>
|
||||
<version>4.1.0-SNAPSHOT</version>
|
||||
<name>Apache POI - the Java API for Microsoft Documents</name>
|
||||
<description>Maven build of Apache POI for Sonar checks</description>
|
||||
<url>http://poi.apache.org/</url>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>org.apache.poi</groupId>
|
||||
<artifactId>poi-parent</artifactId>
|
||||
<version>4.0.2-SNAPSHOT</version>
|
||||
<version>4.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>poi-scratchpad</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
|
|
@ -105,7 +105,6 @@ public abstract class SlideShowHandler extends POIFSFileHandler {
|
|||
for (Slide<?,?> s : ss.getSlides()) {
|
||||
BufferedImage img = new BufferedImage(pgsize.width, pgsize.height, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D graphics = img.createGraphics();
|
||||
DrawFactory.getInstance(graphics).fixFonts(graphics);
|
||||
|
||||
// default rendering options
|
||||
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
|
|
|
@ -527,20 +527,16 @@ public final class BiffViewer {
|
|||
}
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
if (b == null || off < 0 || len < 0 || b.length < off+len) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
if (_currentPos >= _currentSize) {
|
||||
fillNextBuffer();
|
||||
}
|
||||
if (_currentPos >= _currentSize) {
|
||||
return -1;
|
||||
}
|
||||
int availSize = _currentSize - _currentPos;
|
||||
int result;
|
||||
if (len > availSize) {
|
||||
System.err.println("Unexpected request to read past end of current biff record");
|
||||
result = availSize;
|
||||
} else {
|
||||
result = len;
|
||||
}
|
||||
final int result = Math.min(len, _currentSize - _currentPos);
|
||||
System.arraycopy(_data, _currentPos, b, off, result);
|
||||
_currentPos += result;
|
||||
_overallStreamPos += result;
|
||||
|
|
|
@ -106,8 +106,8 @@ public final class RecordInputStream implements LittleEndianInput {
|
|||
|
||||
private final LittleEndianInput _lei;
|
||||
|
||||
public SimpleHeaderInput(InputStream in) {
|
||||
_lei = getLEI(in);
|
||||
private SimpleHeaderInput(LittleEndianInput lei) {
|
||||
_lei = lei;
|
||||
}
|
||||
@Override
|
||||
public int available() {
|
||||
|
@ -129,8 +129,12 @@ public final class RecordInputStream implements LittleEndianInput {
|
|||
|
||||
public RecordInputStream(InputStream in, EncryptionInfo key, int initialOffset) throws RecordFormatException {
|
||||
if (key == null) {
|
||||
_dataInput = getLEI(in);
|
||||
_bhi = new SimpleHeaderInput(in);
|
||||
_dataInput = (in instanceof LittleEndianInput)
|
||||
// accessing directly is an optimisation
|
||||
? (LittleEndianInput)in
|
||||
// less optimal, but should work OK just the same. Often occurs in junit tests.
|
||||
: new LittleEndianInputStream(in);
|
||||
_bhi = new SimpleHeaderInput(_dataInput);
|
||||
} else {
|
||||
Biff8DecryptingStream bds = new Biff8DecryptingStream(in, initialOffset, key);
|
||||
_dataInput = bds;
|
||||
|
@ -195,11 +199,9 @@ public final class RecordInputStream implements LittleEndianInput {
|
|||
private int readNextSid() {
|
||||
int nAvailable = _bhi.available();
|
||||
if (nAvailable < EOFRecord.ENCODED_SIZE) {
|
||||
/*if (nAvailable > 0) {
|
||||
// some scrap left over?
|
||||
// ex45582-22397.xls has one extra byte after the last record
|
||||
// Excel reads that file OK
|
||||
}*/
|
||||
// some scrap left over, if nAvailable > 0?
|
||||
// ex45582-22397.xls has one extra byte after the last record
|
||||
// Excel reads that file OK
|
||||
return INVALID_SID_VALUE;
|
||||
}
|
||||
int result = _bhi.readRecordSID();
|
||||
|
@ -305,14 +307,8 @@ public final class RecordInputStream implements LittleEndianInput {
|
|||
|
||||
@Override
|
||||
public double readDouble() {
|
||||
long valueLongBits = readLong();
|
||||
/*if (Double.isNaN(result)) {
|
||||
// YK: Excel doesn't write NaN but instead converts the cell type into {@link CellType#ERROR}.
|
||||
// HSSF prior to version 3.7 had a bug: it could write Double.NaN but could not read such a file back.
|
||||
// This behavior was fixed in POI-3.7.
|
||||
//throw new RuntimeException("Did not expect to read NaN"); // (Because Excel typically doesn't write NaN)
|
||||
}*/
|
||||
return Double.longBitsToDouble(valueLongBits);
|
||||
// YK: Excel doesn't write NaN but instead converts the cell type into {@link CellType#ERROR}.
|
||||
return Double.longBitsToDouble(readLong());
|
||||
}
|
||||
|
||||
public void readPlain(byte[] buf, int off, int len) {
|
||||
|
@ -329,7 +325,7 @@ public final class RecordInputStream implements LittleEndianInput {
|
|||
readFully(buf, off, len, false);
|
||||
}
|
||||
|
||||
protected void readFully(byte[] buf, int off, int len, boolean isPlain) {
|
||||
private void readFully(byte[] buf, int off, int len, boolean isPlain) {
|
||||
int origLen = len;
|
||||
if (buf == null) {
|
||||
throw new NullPointerException();
|
||||
|
|
|
@ -40,6 +40,7 @@ import javax.imageio.ImageTypeSpecifier;
|
|||
import javax.imageio.stream.ImageInputStream;
|
||||
import javax.imageio.stream.MemoryCacheImageInputStream;
|
||||
|
||||
import org.apache.poi.sl.usermodel.PictureData.PictureType;
|
||||
import org.apache.poi.util.IOUtils;
|
||||
import org.apache.poi.util.POILogFactory;
|
||||
import org.apache.poi.util.POILogger;
|
||||
|
@ -48,10 +49,23 @@ import org.apache.poi.util.POILogger;
|
|||
* For now this class renders only images supported by the javax.imageio.ImageIO framework.
|
||||
**/
|
||||
public class BitmapImageRenderer implements ImageRenderer {
|
||||
private final static POILogger LOG = POILogFactory.getLogger(ImageRenderer.class);
|
||||
private final static POILogger LOG = POILogFactory.getLogger(BitmapImageRenderer.class);
|
||||
|
||||
protected BufferedImage img;
|
||||
|
||||
@Override
|
||||
public boolean canRender(String contentType) {
|
||||
PictureType[] pts = {
|
||||
PictureType.JPEG, PictureType.PNG, PictureType.BMP, PictureType.GIF
|
||||
};
|
||||
for (PictureType pt : pts) {
|
||||
if (pt.contentType.equalsIgnoreCase(contentType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadImage(InputStream data, String contentType) throws IOException {
|
||||
img = readImage(data, contentType);
|
||||
|
|
|
@ -22,8 +22,6 @@ import java.awt.font.TextLayout;
|
|||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.text.AttributedString;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.poi.sl.usermodel.Background;
|
||||
import org.apache.poi.sl.usermodel.ConnectorShape;
|
||||
|
@ -40,18 +38,18 @@ import org.apache.poi.sl.usermodel.TableShape;
|
|||
import org.apache.poi.sl.usermodel.TextBox;
|
||||
import org.apache.poi.sl.usermodel.TextParagraph;
|
||||
import org.apache.poi.sl.usermodel.TextShape;
|
||||
import org.apache.poi.util.JvmBugs;
|
||||
|
||||
public class DrawFactory {
|
||||
protected static final ThreadLocal<DrawFactory> defaultFactory = new ThreadLocal<>();
|
||||
private static final ThreadLocal<DrawFactory> defaultFactory = new ThreadLocal<>();
|
||||
|
||||
/**
|
||||
* Set a custom draw factory for the current thread.
|
||||
* This is a fallback, for operations where usercode can't set a graphics context.
|
||||
* Preferably use the rendering hint {@link Drawable#DRAW_FACTORY} to set the factory.
|
||||
*
|
||||
* @param factory
|
||||
* @param factory the custom factory
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static void setDefaultFactory(DrawFactory factory) {
|
||||
defaultFactory.set(factory);
|
||||
}
|
||||
|
@ -170,6 +168,7 @@ public class DrawFactory {
|
|||
return new DrawBackground(shape);
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public DrawTextFragment getTextFragment(TextLayout layout, AttributedString str) {
|
||||
return new DrawTextFragment(layout, str);
|
||||
}
|
||||
|
@ -213,35 +212,6 @@ public class DrawFactory {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Replace font families for Windows JVM 6, which contains a font rendering error.
|
||||
* This is likely to be removed, when POI upgrades to JDK 7
|
||||
*
|
||||
* @param graphics the graphics context which will contain the font mapping
|
||||
*/
|
||||
public void fixFonts(Graphics2D graphics) {
|
||||
if (!JvmBugs.hasLineBreakMeasurerBug()) return;
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String,String> fontMap = (Map<String,String>)graphics.getRenderingHint(Drawable.FONT_MAP);
|
||||
if (fontMap == null) {
|
||||
fontMap = new HashMap<>();
|
||||
graphics.setRenderingHint(Drawable.FONT_MAP, fontMap);
|
||||
}
|
||||
|
||||
String fonts[][] = {
|
||||
{ "Calibri", "Lucida Sans" },
|
||||
{ "Cambria", "Lucida Bright" },
|
||||
{ "Times New Roman", "Lucida Bright" },
|
||||
{ "serif", "Lucida Bright" }
|
||||
};
|
||||
|
||||
for (String f[] : fonts) {
|
||||
if (!fontMap.containsKey(f[0])) {
|
||||
fontMap.put(f[0], f[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a FontManager, either registered beforehand or a default implementation
|
||||
*
|
||||
|
|
|
@ -22,6 +22,8 @@ package org.apache.poi.sl.draw;
|
|||
import java.awt.Font;
|
||||
import java.awt.Graphics2D;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import org.apache.poi.common.usermodel.fonts.FontInfo;
|
||||
import org.apache.poi.sl.draw.Drawable.DrawableHint;
|
||||
|
@ -33,6 +35,13 @@ import org.apache.poi.sl.draw.Drawable.DrawableHint;
|
|||
*/
|
||||
public class DrawFontManagerDefault implements DrawFontManager {
|
||||
|
||||
protected final Set<String> knownSymbolFonts = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
|
||||
|
||||
public DrawFontManagerDefault() {
|
||||
knownSymbolFonts.add("Wingdings");
|
||||
knownSymbolFonts.add("Symbol");
|
||||
}
|
||||
|
||||
@Override
|
||||
public FontInfo getMappedFont(Graphics2D graphics, FontInfo fontInfo) {
|
||||
return getFontWithFallback(graphics, Drawable.FONT_MAP, fontInfo);
|
||||
|
@ -49,25 +58,35 @@ public class DrawFontManagerDefault implements DrawFontManager {
|
|||
|
||||
public String mapFontCharset(Graphics2D graphics, FontInfo fontInfo, String text) {
|
||||
// TODO: find a real charset mapping solution instead of hard coding for Wingdings
|
||||
String attStr = text;
|
||||
if (fontInfo != null && "Wingdings".equalsIgnoreCase(fontInfo.getTypeface())) {
|
||||
// wingdings doesn't contain high-surrogates, so chars are ok
|
||||
boolean changed = false;
|
||||
char chrs[] = attStr.toCharArray();
|
||||
for (int i=0; i<chrs.length; i++) {
|
||||
// only change valid chars
|
||||
if ((0x20 <= chrs[i] && chrs[i] <= 0x7f) ||
|
||||
(0xa0 <= chrs[i] && chrs[i] <= 0xff)) {
|
||||
chrs[i] |= 0xf000;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
return (fontInfo != null && knownSymbolFonts.contains(fontInfo.getTypeface()))
|
||||
? mapSymbolChars(text)
|
||||
: text;
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
attStr = new String(chrs);
|
||||
/**
|
||||
* Symbol fonts like "Wingdings" or "Symbol" have glyphs mapped to a Unicode private use range via the Java font loader,
|
||||
* although a system font viewer might show you the glyphs in the ASCII range.
|
||||
* This helper function maps the chars of the text string to the corresponding private use range chars.
|
||||
*
|
||||
* @param text the input string, typically consists of ASCII chars
|
||||
* @return the mapped string, typically consists of chars in the range of 0xf000 to 0xf0ff
|
||||
*
|
||||
* @since POI 4.0.0
|
||||
*/
|
||||
public static String mapSymbolChars(String text) {
|
||||
// wingdings doesn't contain high-surrogates, so chars are ok
|
||||
boolean changed = false;
|
||||
char chrs[] = text.toCharArray();
|
||||
for (int i=0; i<chrs.length; i++) {
|
||||
// only change valid chars
|
||||
if ((0x20 <= chrs[i] && chrs[i] <= 0x7f) ||
|
||||
(0xa0 <= chrs[i] && chrs[i] <= 0xff)) {
|
||||
chrs[i] |= 0xf000;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
return attStr;
|
||||
|
||||
return changed ? new String(chrs) : text;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -24,17 +24,20 @@ import java.awt.geom.Rectangle2D;
|
|||
import java.io.IOException;
|
||||
|
||||
import org.apache.poi.sl.usermodel.PictureData;
|
||||
import org.apache.poi.sl.usermodel.PictureData.PictureType;
|
||||
import org.apache.poi.util.POILogFactory;
|
||||
import org.apache.poi.util.POILogger;
|
||||
import org.apache.poi.sl.usermodel.PictureShape;
|
||||
import org.apache.poi.sl.usermodel.RectAlign;
|
||||
import org.apache.poi.util.POILogFactory;
|
||||
import org.apache.poi.util.POILogger;
|
||||
|
||||
|
||||
public class DrawPictureShape extends DrawSimpleShape {
|
||||
private static final POILogger LOG = POILogFactory.getLogger(DrawPictureShape.class);
|
||||
private static final String WMF_IMAGE_RENDERER = "org.apache.poi.hwmf.draw.HwmfSLImageRenderer";
|
||||
|
||||
private static final String[] KNOWN_RENDERER = {
|
||||
"org.apache.poi.hwmf.draw.HwmfImageRenderer",
|
||||
"org.apache.poi.hemf.draw.HemfImageRenderer",
|
||||
"org.apache.poi.xslf.draw.SVGImageRenderer"
|
||||
};
|
||||
|
||||
public DrawPictureShape(PictureShape<?,?> shape) {
|
||||
super(shape);
|
||||
}
|
||||
|
@ -59,29 +62,47 @@ public class DrawPictureShape extends DrawSimpleShape {
|
|||
/**
|
||||
* Returns an ImageRenderer for the PictureData
|
||||
*
|
||||
* @param graphics
|
||||
* @param graphics the graphics context
|
||||
* @return the image renderer
|
||||
*/
|
||||
@SuppressWarnings({"WeakerAccess", "unchecked"})
|
||||
public static ImageRenderer getImageRenderer(Graphics2D graphics, String contentType) {
|
||||
ImageRenderer renderer = (ImageRenderer)graphics.getRenderingHint(Drawable.IMAGE_RENDERER);
|
||||
if (renderer != null) {
|
||||
final ImageRenderer renderer = (ImageRenderer)graphics.getRenderingHint(Drawable.IMAGE_RENDERER);
|
||||
if (renderer != null && renderer.canRender(contentType)) {
|
||||
return renderer;
|
||||
}
|
||||
|
||||
if (PictureType.WMF.contentType.equals(contentType)) {
|
||||
|
||||
// first try with our default image renderer
|
||||
final BitmapImageRenderer bir = new BitmapImageRenderer();
|
||||
if (bir.canRender(contentType)) {
|
||||
return bir;
|
||||
}
|
||||
|
||||
// then iterate through the scratchpad renderers
|
||||
//
|
||||
// this could be nicely implemented via a j.u.ServiceLoader, but OSGi makes things complicated ...
|
||||
// https://blog.osgi.org/2013/02/javautilserviceloader-in-osgi.html
|
||||
// ... therefore falling back to classloading attempts
|
||||
ClassLoader cl = ImageRenderer.class.getClassLoader();
|
||||
for (String kr : KNOWN_RENDERER) {
|
||||
final ImageRenderer ir;
|
||||
try {
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<? extends ImageRenderer> irc = (Class<? extends ImageRenderer>)
|
||||
DrawPictureShape.class.getClassLoader().loadClass(WMF_IMAGE_RENDERER);
|
||||
return irc.newInstance();
|
||||
} catch (Exception e) {
|
||||
// WMF image renderer is not on the classpath, continuing with BitmapRenderer
|
||||
// although this doesn't make much sense ...
|
||||
LOG.log(POILogger.ERROR, "WMF image renderer is not on the classpath - include poi-scratchpad jar!", e);
|
||||
ir = ((Class<? extends ImageRenderer>)cl.loadClass(kr)).newInstance();
|
||||
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
|
||||
// scratchpad was not on the path, ignore and continue
|
||||
LOG.log(POILogger.INFO, "Known image renderer '"+kr+" not found/loaded - include poi-scratchpad jar!", e);
|
||||
continue;
|
||||
}
|
||||
if (ir.canRender(contentType)) {
|
||||
return ir;
|
||||
}
|
||||
}
|
||||
|
||||
return new BitmapImageRenderer();
|
||||
|
||||
LOG.log(POILogger.WARN, "No suiteable image renderer found for content-type '"+
|
||||
contentType+"' - include poi-scratchpad jar!");
|
||||
|
||||
// falling back to BitmapImageRenderer, at least it gracefully handles invalid images
|
||||
return bir;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -254,7 +254,6 @@ public class DrawTextParagraph implements Drawable {
|
|||
lines.clear();
|
||||
|
||||
DrawFactory fact = DrawFactory.getInstance(graphics);
|
||||
fact.fixFonts(graphics);
|
||||
StringBuilder text = new StringBuilder();
|
||||
AttributedString at = getAttributedString(graphics, text);
|
||||
boolean emptyParagraph = text.toString().trim().isEmpty();
|
||||
|
@ -635,13 +634,6 @@ public class DrawTextParagraph implements Drawable {
|
|||
* <li>determine the font group - a text run can have different font groups. Depending on the chars,
|
||||
* the correct font group needs to be used
|
||||
*
|
||||
* @param graphics
|
||||
* @param dfm
|
||||
* @param attList
|
||||
* @param beginIndex
|
||||
* @param run
|
||||
* @param runText
|
||||
*
|
||||
* @see <a href="https://blogs.msdn.microsoft.com/officeinteroperability/2013/04/22/office-open-xml-themes-schemes-and-fonts/">Office Open XML Themes, Schemes, and Fonts</a>
|
||||
*/
|
||||
private void processGlyphs(Graphics2D graphics, DrawFontManager dfm, List<AttributedStringData> attList, final int beginIndex, TextRun run, String runText) {
|
||||
|
|
|
@ -40,8 +40,6 @@ public class DrawTextShape extends DrawSimpleShape {
|
|||
|
||||
@Override
|
||||
public void drawContent(Graphics2D graphics) {
|
||||
DrawFactory.getInstance(graphics).fixFonts(graphics);
|
||||
|
||||
TextShape<?,?> s = getShape();
|
||||
|
||||
Rectangle2D anchor = DrawShape.getAnchor(graphics, s);
|
||||
|
@ -219,10 +217,9 @@ public class DrawTextShape extends DrawSimpleShape {
|
|||
graphics.addRenderingHints(oldGraphics.getRenderingHints());
|
||||
graphics.setTransform(oldGraphics.getTransform());
|
||||
}
|
||||
DrawFactory.getInstance(graphics).fixFonts(graphics);
|
||||
return drawParagraphs(graphics, 0, 0);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected TextShape<?,? extends TextParagraph<?,?,? extends TextRun>> getShape() {
|
||||
return (TextShape<?,? extends TextParagraph<?,?,? extends TextRun>>)shape;
|
||||
|
|
|
@ -75,6 +75,13 @@ import java.io.InputStream;
|
|||
* </pre>
|
||||
*/
|
||||
public interface ImageRenderer {
|
||||
/**
|
||||
* Determines if this image renderer implementation supports the given contentType
|
||||
* @param contentType the image content type
|
||||
* @return if the content type is supported
|
||||
*/
|
||||
boolean canRender(String contentType);
|
||||
|
||||
/**
|
||||
* Load and buffer the image
|
||||
*
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
==================================================================== */
|
||||
|
||||
package org.apache.poi.util;
|
||||
|
||||
import java.awt.geom.Dimension2D;
|
||||
|
||||
/**
|
||||
* @since 4.1.0
|
||||
*/
|
||||
public class Dimension2DDouble extends Dimension2D {
|
||||
|
||||
double width;
|
||||
double height;
|
||||
|
||||
public Dimension2DDouble() {
|
||||
width = 0d;
|
||||
height = 0d;
|
||||
}
|
||||
|
||||
public Dimension2DDouble(double width, double height) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSize(double width, double height) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof Dimension2DDouble) {
|
||||
Dimension2DDouble other = (Dimension2DDouble) obj;
|
||||
return width == other.width && height == other.height;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
double sum = width + height;
|
||||
return (int) Math.ceil(sum * (sum + 1) / 2 + width);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Dimension2DDouble[" + width + ", " + height + "]";
|
||||
}
|
||||
}
|
|
@ -50,6 +50,7 @@ public final class IOUtils {
|
|||
* @param maxOverride The number of bytes that should be possible to be allocated in one step.
|
||||
* @since 4.0.0
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static void setByteArrayMaxOverride(int maxOverride) {
|
||||
BYTE_ARRAY_MAX_OVERRIDE = maxOverride;
|
||||
}
|
||||
|
@ -395,13 +396,35 @@ public final class IOUtils {
|
|||
* @throws IOException If copying the data fails.
|
||||
*/
|
||||
public static long copy(InputStream inp, OutputStream out) throws IOException {
|
||||
return copy(inp, out, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies all the data from the given InputStream to the OutputStream. It
|
||||
* leaves both streams open, so you will still need to close them once done.
|
||||
*
|
||||
* @param inp The {@link InputStream} which provides the data
|
||||
* @param out The {@link OutputStream} to write the data to
|
||||
* @param limit limit the copied bytes - use {@code -1} for no limit
|
||||
* @return the amount of bytes copied
|
||||
*
|
||||
* @throws IOException If copying the data fails.
|
||||
*/
|
||||
public static long copy(InputStream inp, OutputStream out, long limit) throws IOException {
|
||||
final byte[] buff = new byte[4096];
|
||||
long totalCount = 0;
|
||||
for (int count; (count = inp.read(buff)) != -1; totalCount += count) {
|
||||
if (count > 0) {
|
||||
out.write(buff, 0, count);
|
||||
int readBytes = -1;
|
||||
do {
|
||||
int todoBytes = (int)((limit < 0) ? buff.length : Math.min(limit-totalCount, buff.length));
|
||||
if (todoBytes > 0) {
|
||||
readBytes = inp.read(buff, 0, todoBytes);
|
||||
if (readBytes > 0) {
|
||||
out.write(buff, 0, readBytes);
|
||||
totalCount += readBytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (readBytes >= 0 && (limit == -1 || totalCount < limit));
|
||||
|
||||
return totalCount;
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
package org.apache.poi.util;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
@ -29,12 +30,16 @@ import java.io.InputStream;
|
|||
*/
|
||||
public class LittleEndianInputStream extends FilterInputStream implements LittleEndianInput {
|
||||
|
||||
private static final int BUFFERED_SIZE = 8096;
|
||||
|
||||
private static final int EOF = -1;
|
||||
private int readIndex = 0;
|
||||
private int markIndex = -1;
|
||||
|
||||
public LittleEndianInputStream(InputStream is) {
|
||||
super(is);
|
||||
super(is.markSupported() ? is : new BufferedInputStream(is, BUFFERED_SIZE));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@SuppressForbidden("just delegating")
|
||||
public int available() {
|
||||
|
@ -60,7 +65,18 @@ public class LittleEndianInputStream extends FilterInputStream implements Little
|
|||
}
|
||||
return LittleEndian.getUByte(buf);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* get a float value, reads it in little endian format
|
||||
* then converts the resulting revolting IEEE 754 (curse them) floating
|
||||
* point number to a happy java float
|
||||
*
|
||||
* @return the float (32-bit) value
|
||||
*/
|
||||
public float readFloat() {
|
||||
return Float.intBitsToFloat( readInt() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public double readDouble() {
|
||||
return Double.longBitsToDouble(readLong());
|
||||
|
@ -137,14 +153,42 @@ public class LittleEndianInputStream extends FilterInputStream implements Little
|
|||
}
|
||||
}
|
||||
|
||||
//Makes repeated calls to super.read() until length is read or EOF is reached
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
int readBytes = super.read(b, off, len);
|
||||
readIndex += readBytes;
|
||||
return readBytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void mark(int readlimit) {
|
||||
super.mark(readlimit);
|
||||
markIndex = readIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void reset() throws IOException {
|
||||
super.reset();
|
||||
if (markIndex > -1) {
|
||||
readIndex = markIndex;
|
||||
markIndex = -1;
|
||||
}
|
||||
}
|
||||
|
||||
public int getReadIndex() {
|
||||
return readIndex;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//Makes repeated calls to super.read() until length is read or EOF is reached
|
||||
private int _read(byte[] buffer, int offset, int length) throws IOException {
|
||||
//lifted directly from org.apache.commons.io.IOUtils 2.4
|
||||
int remaining = length;
|
||||
while (remaining > 0) {
|
||||
int location = length - remaining;
|
||||
int count = read(buffer, offset + location, remaining);
|
||||
if (EOF == count) { // EOF
|
||||
if (EOF == count) {
|
||||
break;
|
||||
}
|
||||
remaining -= count;
|
||||
|
@ -157,4 +201,9 @@ public class LittleEndianInputStream extends FilterInputStream implements Little
|
|||
public void readPlain(byte[] buf, int off, int len) {
|
||||
readFully(buf, off, len);
|
||||
}
|
||||
|
||||
|
||||
public void skipFully(int len) throws IOException {
|
||||
IOUtils.skipFully(this, len);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -177,7 +177,7 @@ public class SignatureConfig {
|
|||
/**
|
||||
* if true, the signature is added to the existing signatures
|
||||
*
|
||||
* @since POI 4.0.2
|
||||
* @since POI 4.1.0
|
||||
*/
|
||||
private boolean allowMultipleSignatures = false;
|
||||
|
||||
|
@ -1019,7 +1019,7 @@ public class SignatureConfig {
|
|||
/**
|
||||
* @return true, if multiple signatures can be attached
|
||||
*
|
||||
* @since POI 4.0.2
|
||||
* @since POI 4.1.0
|
||||
*/
|
||||
public boolean isAllowMultipleSignatures() {
|
||||
return allowMultipleSignatures;
|
||||
|
@ -1031,7 +1031,7 @@ public class SignatureConfig {
|
|||
* @param allowMultipleSignatures if true, the signature will be added,
|
||||
* otherwise all existing signatures will be replaced by the current
|
||||
*
|
||||
* @since POI 4.0.2
|
||||
* @since POI 4.1.0
|
||||
*/
|
||||
public void setAllowMultipleSignatures(boolean allowMultipleSignatures) {
|
||||
this.allowMultipleSignatures = allowMultipleSignatures;
|
||||
|
|
|
@ -17,57 +17,12 @@
|
|||
|
||||
package org.apache.poi.xdgf.geom;
|
||||
|
||||
import java.awt.geom.Dimension2D;
|
||||
import org.apache.poi.util.Removal;
|
||||
|
||||
public class Dimension2dDouble extends Dimension2D {
|
||||
|
||||
double width;
|
||||
double height;
|
||||
|
||||
public Dimension2dDouble() {
|
||||
width = 0d;
|
||||
height = 0d;
|
||||
}
|
||||
|
||||
public Dimension2dDouble(double width, double height) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSize(double width, double height) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof Dimension2dDouble) {
|
||||
Dimension2dDouble other = (Dimension2dDouble) obj;
|
||||
return width == other.width && height == other.height;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
double sum = width + height;
|
||||
return (int) Math.ceil(sum * (sum + 1) / 2 + width);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Dimension2dDouble[" + width + ", " + height + "]";
|
||||
}
|
||||
/**
|
||||
* @deprecated in 4.1.0 - use org.apache.poi.util.Dimension2DDouble
|
||||
*/
|
||||
@Deprecated
|
||||
@Removal(version = "5.0.0")
|
||||
public class Dimension2dDouble extends org.apache.poi.util.Dimension2DDouble {
|
||||
}
|
||||
|
|
|
@ -39,6 +39,7 @@ import org.apache.batik.ext.awt.image.renderable.ClipRable8Bit;
|
|||
import org.apache.batik.gvt.GraphicsNode;
|
||||
import org.apache.batik.util.XMLResourceDescriptor;
|
||||
import org.apache.poi.sl.draw.ImageRenderer;
|
||||
import org.apache.poi.sl.usermodel.PictureData;
|
||||
import org.w3c.dom.Document;
|
||||
|
||||
public class SVGImageRenderer implements ImageRenderer {
|
||||
|
@ -133,4 +134,9 @@ public class SVGImageRenderer implements ImageRenderer {
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRender(String contentType) {
|
||||
return PictureData.PictureType.SVG.contentType.equalsIgnoreCase(contentType);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -310,7 +310,7 @@ public class XMLSlideShow extends POIXMLDocument
|
|||
/**
|
||||
* This method is used to create template for chart XML.
|
||||
* @return Xslf chart object
|
||||
* @since POI 4.0.2
|
||||
* @since POI 4.1.0
|
||||
*/
|
||||
public XSLFChart createChart() {
|
||||
int chartIdx = findNextAvailableFileNameIndex(XSLFRelation.CHART, _charts.size() + 1);
|
||||
|
|
|
@ -112,7 +112,7 @@ public final class XSLFChart extends XDDFChart {
|
|||
* @param rID relation id
|
||||
* @param anchor size and location of chart
|
||||
* @return graphic frame object
|
||||
* @since POI 4.0.2
|
||||
* @since POI 4.1.0
|
||||
*/
|
||||
static CTGraphicalObjectFrame prototype(int shapeId, String rID, Rectangle2D anchor) {
|
||||
CTGraphicalObjectFrame frame = CTGraphicalObjectFrame.Factory.newInstance();
|
||||
|
|
|
@ -112,7 +112,7 @@ public class XSLFDrawing {
|
|||
*
|
||||
* @param rID relation id of chart
|
||||
* @param rect2D Chart Bounding values
|
||||
* @since POI 4.0.2
|
||||
* @since POI 4.1.0
|
||||
*/
|
||||
public void addChart(String rID, Rectangle2D rect2D) {
|
||||
CTGraphicalObjectFrame sp = _spTree.addNewGraphicFrame();
|
||||
|
|
|
@ -724,7 +724,7 @@ implements XSLFShapeContainer, Sheet<XSLFShape,XSLFTextParagraph> {
|
|||
* this method will add chart into slide
|
||||
* with default height, width, x and y
|
||||
* @param chart xslf chart object
|
||||
* @since POI 4.0.2
|
||||
* @since POI 4.1.0
|
||||
*/
|
||||
public void addChart(XSLFChart chart) {
|
||||
Rectangle2D rect2D = new java.awt.Rectangle(XDDFChart.DEFAULT_X, XDDFChart.DEFAULT_Y,
|
||||
|
@ -737,7 +737,7 @@ implements XSLFShapeContainer, Sheet<XSLFShape,XSLFTextParagraph> {
|
|||
* this method will add chart into slide
|
||||
* with given height, width, x and y
|
||||
* @param chart xslf chart object
|
||||
* @since POI 4.0.2
|
||||
* @since POI 4.1.0
|
||||
*/
|
||||
public void addChart(XSLFChart chart, Rectangle2D rect2D) {
|
||||
RelationPart rp = addRelation(null, XSLFRelation.CHART, chart);
|
||||
|
|
|
@ -139,7 +139,6 @@ public class PPTX2PNG {
|
|||
|
||||
BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D graphics = img.createGraphics();
|
||||
DrawFactory.getInstance(graphics).fixFonts(graphics);
|
||||
|
||||
// default rendering options
|
||||
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
|
|
|
@ -130,8 +130,6 @@ public class TestFonts {
|
|||
graphics.setRenderingHint(Drawable.FONT_FALLBACK, fallbackMap);
|
||||
graphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
|
||||
|
||||
DrawFactory.getInstance(graphics).fixFonts(graphics);
|
||||
|
||||
tb.resizeToFitText(graphics);
|
||||
graphics.dispose();
|
||||
|
||||
|
|
|
@ -361,7 +361,6 @@ public class TestXSLFSimpleShape {
|
|||
|
||||
BufferedImage img = new BufferedImage(pgsize.width, pgsize.height, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D graphics = img.createGraphics();
|
||||
DrawFactory.getInstance(graphics).fixFonts(graphics);
|
||||
|
||||
// default rendering options
|
||||
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
==================================================================== */
|
||||
|
||||
package org.apache.poi.hemf.draw;
|
||||
|
||||
import java.awt.Shape;
|
||||
import java.awt.geom.Path2D;
|
||||
|
||||
import org.apache.poi.hwmf.draw.HwmfDrawProperties;
|
||||
|
||||
public class HemfDrawProperties extends HwmfDrawProperties {
|
||||
|
||||
/** Path for path bracket operations */
|
||||
protected Path2D path = null;
|
||||
protected boolean usePathBracket = false;
|
||||
|
||||
|
||||
public HemfDrawProperties() {
|
||||
}
|
||||
|
||||
public HemfDrawProperties(HemfDrawProperties other) {
|
||||
super(other);
|
||||
path = (other.path != null) ? (Path2D)other.path.clone() : null;
|
||||
// TODO: check how to clone
|
||||
clip = other.clip;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the current path used for bracket operations
|
||||
*/
|
||||
public Path2D getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Un-/Sets the bracket path
|
||||
* @param path the bracket path
|
||||
*/
|
||||
public void setPath(Path2D path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use path (bracket) or graphics context for drawing operations
|
||||
* @return {@code true}, if the drawing should go to the path bracket,
|
||||
* if {@code false} draw directly to the graphics context
|
||||
*/
|
||||
public boolean getUsePathBracket() {
|
||||
return usePathBracket;
|
||||
}
|
||||
|
||||
public void setUsePathBracket(boolean usePathBracket) {
|
||||
this.usePathBracket = usePathBracket;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,259 @@
|
|||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
==================================================================== */
|
||||
|
||||
package org.apache.poi.hemf.draw;
|
||||
|
||||
import static org.apache.poi.hwmf.record.HwmfBrushStyle.BS_NULL;
|
||||
import static org.apache.poi.hwmf.record.HwmfBrushStyle.BS_SOLID;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.geom.Path2D;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.apache.poi.hemf.record.emf.HemfRecord;
|
||||
import org.apache.poi.hwmf.draw.HwmfDrawProperties;
|
||||
import org.apache.poi.hwmf.draw.HwmfGraphics;
|
||||
import org.apache.poi.hwmf.record.HwmfColorRef;
|
||||
import org.apache.poi.hwmf.record.HwmfObjectTableEntry;
|
||||
import org.apache.poi.hwmf.record.HwmfPenStyle;
|
||||
import org.apache.poi.util.Internal;
|
||||
|
||||
public class HemfGraphics extends HwmfGraphics {
|
||||
|
||||
private static final HwmfColorRef WHITE = new HwmfColorRef(Color.WHITE);
|
||||
private static final HwmfColorRef LTGRAY = new HwmfColorRef(new Color(0x00C0C0C0));
|
||||
private static final HwmfColorRef GRAY = new HwmfColorRef(new Color(0x00808080));
|
||||
private static final HwmfColorRef DKGRAY = new HwmfColorRef(new Color(0x00404040));
|
||||
private static final HwmfColorRef BLACK = new HwmfColorRef(Color.BLACK);
|
||||
|
||||
|
||||
public HemfGraphics(Graphics2D graphicsCtx, Rectangle2D bbox) {
|
||||
super(graphicsCtx,bbox);
|
||||
// add dummy entry for object ind ex 0, as emf is 1-based
|
||||
objectIndexes.set(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HemfDrawProperties getProperties() {
|
||||
return (HemfDrawProperties)super.getProperties();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HemfDrawProperties newProperties(HwmfDrawProperties oldProps) {
|
||||
return (oldProps == null)
|
||||
? new HemfDrawProperties()
|
||||
: new HemfDrawProperties((HemfDrawProperties)oldProps);
|
||||
}
|
||||
|
||||
public void draw(HemfRecord r) {
|
||||
r.draw(this);
|
||||
}
|
||||
|
||||
@Internal
|
||||
public void draw(Consumer<Path2D> pathConsumer, FillDrawStyle fillDraw) {
|
||||
final HemfDrawProperties prop = getProperties();
|
||||
final boolean useBracket = prop.getUsePathBracket();
|
||||
|
||||
final Path2D path;
|
||||
if (useBracket) {
|
||||
path = prop.getPath();
|
||||
} else {
|
||||
path = new Path2D.Double();
|
||||
path.setWindingRule(prop.getWindingRule());
|
||||
}
|
||||
|
||||
// add dummy move-to at start, to handle invalid emfs not containing that move-to
|
||||
if (path.getCurrentPoint() == null) {
|
||||
Point2D pnt = prop.getLocation();
|
||||
path.moveTo(pnt.getX(), pnt.getY());
|
||||
}
|
||||
|
||||
try {
|
||||
pathConsumer.accept(path);
|
||||
} catch (Exception e) {
|
||||
// workaround if a path has been started and no MoveTo command
|
||||
// has been specified before the first lineTo/splineTo
|
||||
final Point2D loc = prop.getLocation();
|
||||
path.moveTo(loc.getX(), loc.getY());
|
||||
pathConsumer.accept(path);
|
||||
}
|
||||
|
||||
Point2D curPnt = path.getCurrentPoint();
|
||||
if (curPnt == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
prop.setLocation(curPnt);
|
||||
if (!useBracket) {
|
||||
switch (fillDraw) {
|
||||
case FILL:
|
||||
super.fill(path);
|
||||
break;
|
||||
case DRAW:
|
||||
super.draw(path);
|
||||
break;
|
||||
case FILL_DRAW:
|
||||
super.fill(path);
|
||||
super.draw(path);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds or sets an record of type {@link HwmfObjectTableEntry} to the object table.
|
||||
* If the {@code index} is less than 1, the method acts the same as
|
||||
* {@link HwmfGraphics#addObjectTableEntry(HwmfObjectTableEntry)}, otherwise the
|
||||
* index is used to access the object table.
|
||||
* As the table is filled successively, the index must be between 1 and size+1
|
||||
*
|
||||
* @param entry the record to be stored
|
||||
* @param index the index to be overwritten, regardless if its content was unset before
|
||||
*
|
||||
* @see HwmfGraphics#addObjectTableEntry(HwmfObjectTableEntry)
|
||||
*/
|
||||
public void addObjectTableEntry(HwmfObjectTableEntry entry, int index) {
|
||||
if (index < 1) {
|
||||
throw new IndexOutOfBoundsException("Object table entry index in EMF must be > 0 - invalid index: "+index);
|
||||
}
|
||||
|
||||
objectIndexes.set(index);
|
||||
objectTable.put(index, entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyObjectTableEntry(int index) {
|
||||
if ((index & 0x80000000) != 0) {
|
||||
selectStockObject(index);
|
||||
} else {
|
||||
super.applyObjectTableEntry(index);
|
||||
}
|
||||
}
|
||||
|
||||
private void selectStockObject(int objectIndex) {
|
||||
final HemfDrawProperties prop = getProperties();
|
||||
switch (objectIndex) {
|
||||
case 0x80000000:
|
||||
// WHITE_BRUSH - A white, solid-color brush
|
||||
// BrushStyle: BS_SOLID
|
||||
// Color: 0x00FFFFFF
|
||||
prop.setBrushColor(WHITE);
|
||||
prop.setBrushStyle(BS_SOLID);
|
||||
break;
|
||||
case 0x80000001:
|
||||
// LTGRAY_BRUSH - A light gray, solid-color brush
|
||||
// BrushStyle: BS_SOLID
|
||||
// Color: 0x00C0C0C0
|
||||
prop.setBrushColor(LTGRAY);
|
||||
prop.setBrushStyle(BS_SOLID);
|
||||
break;
|
||||
case 0x80000002:
|
||||
// GRAY_BRUSH - A gray, solid-color brush
|
||||
// BrushStyle: BS_SOLID
|
||||
// Color: 0x00808080
|
||||
prop.setBrushColor(GRAY);
|
||||
prop.setBrushStyle(BS_SOLID);
|
||||
break;
|
||||
case 0x80000003:
|
||||
// DKGRAY_BRUSH - A dark gray, solid color brush
|
||||
// BrushStyle: BS_SOLID
|
||||
// Color: 0x00404040
|
||||
prop.setBrushColor(DKGRAY);
|
||||
prop.setBrushStyle(BS_SOLID);
|
||||
break;
|
||||
case 0x80000004:
|
||||
// BLACK_BRUSH - A black, solid color brush
|
||||
// BrushStyle: BS_SOLID
|
||||
// Color: 0x00000000
|
||||
prop.setBrushColor(BLACK);
|
||||
prop.setBrushStyle(BS_SOLID);
|
||||
break;
|
||||
case 0x80000005:
|
||||
// NULL_BRUSH - A null brush
|
||||
// BrushStyle: BS_NULL
|
||||
prop.setBrushStyle(BS_NULL);
|
||||
break;
|
||||
case 0x80000006:
|
||||
// WHITE_PEN - A white, solid-color pen
|
||||
// PenStyle: PS_COSMETIC + PS_SOLID
|
||||
// ColorRef: 0x00FFFFFF
|
||||
prop.setPenStyle(HwmfPenStyle.valueOf(0));
|
||||
prop.setPenWidth(1);
|
||||
prop.setPenColor(WHITE);
|
||||
break;
|
||||
case 0x80000007:
|
||||
// BLACK_PEN - A black, solid-color pen
|
||||
// PenStyle: PS_COSMETIC + PS_SOLID
|
||||
// ColorRef: 0x00000000
|
||||
prop.setPenStyle(HwmfPenStyle.valueOf(0));
|
||||
prop.setPenWidth(1);
|
||||
prop.setPenColor(BLACK);
|
||||
break;
|
||||
case 0x80000008:
|
||||
// NULL_PEN - A null pen
|
||||
// PenStyle: PS_NULL
|
||||
prop.setPenStyle(HwmfPenStyle.valueOf(HwmfPenStyle.HwmfLineDash.NULL.wmfFlag));
|
||||
break;
|
||||
case 0x8000000A:
|
||||
// OEM_FIXED_FONT - A fixed-width, OEM character set
|
||||
// Charset: OEM_CHARSET
|
||||
// PitchAndFamily: FF_DONTCARE + FIXED_PITCH
|
||||
break;
|
||||
case 0x8000000B:
|
||||
// ANSI_FIXED_FONT - A fixed-width font
|
||||
// Charset: ANSI_CHARSET
|
||||
// PitchAndFamily: FF_DONTCARE + FIXED_PITCH
|
||||
break;
|
||||
case 0x8000000C:
|
||||
// ANSI_VAR_FONT - A variable-width font
|
||||
// Charset: ANSI_CHARSET
|
||||
// PitchAndFamily: FF_DONTCARE + VARIABLE_PITCH
|
||||
break;
|
||||
case 0x8000000D:
|
||||
// SYSTEM_FONT - A font that is guaranteed to be available in the operating system
|
||||
break;
|
||||
case 0x8000000E:
|
||||
// DEVICE_DEFAULT_FONT
|
||||
// The default font that is provided by the graphics device driver for the current output device
|
||||
break;
|
||||
case 0x8000000F:
|
||||
// DEFAULT_PALETTE
|
||||
// The default palette that is defined for the current output device.
|
||||
break;
|
||||
case 0x80000010:
|
||||
// SYSTEM_FIXED_FONT
|
||||
// A fixed-width font that is guaranteed to be available in the operating system.
|
||||
break;
|
||||
case 0x80000011:
|
||||
// DEFAULT_GUI_FONT
|
||||
// The default font that is used for user interface objects such as menus and dialog boxes.
|
||||
break;
|
||||
case 0x80000012:
|
||||
// DC_BRUSH
|
||||
// The solid-color brush that is currently selected in the playback device context.
|
||||
break;
|
||||
case 0x80000013:
|
||||
// DC_PEN
|
||||
// The solid-color pen that is currently selected in the playback device context.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
==================================================================== */
|
||||
|
||||
package org.apache.poi.hemf.draw;
|
||||
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Insets;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.geom.Dimension2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.RescaleOp;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.apache.poi.hemf.usermodel.HemfPicture;
|
||||
import org.apache.poi.sl.draw.ImageRenderer;
|
||||
import org.apache.poi.sl.usermodel.PictureData;
|
||||
import org.apache.poi.util.Units;
|
||||
|
||||
public class HemfImageRenderer implements ImageRenderer {
|
||||
HemfPicture image;
|
||||
double alpha;
|
||||
|
||||
@Override
|
||||
public boolean canRender(String contentType) {
|
||||
return PictureData.PictureType.EMF.contentType.equalsIgnoreCase(contentType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadImage(InputStream data, String contentType) throws IOException {
|
||||
if (!PictureData.PictureType.EMF.contentType.equals(contentType)) {
|
||||
throw new IOException("Invalid picture type");
|
||||
}
|
||||
image = new HemfPicture(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadImage(byte[] data, String contentType) throws IOException {
|
||||
if (!PictureData.PictureType.EMF.contentType.equals(contentType)) {
|
||||
throw new IOException("Invalid picture type");
|
||||
}
|
||||
image = new HemfPicture(new ByteArrayInputStream(data));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getDimension() {
|
||||
int width = 0, height = 0;
|
||||
if (image != null) {
|
||||
Dimension2D dim = image.getSize();
|
||||
width = Units.pointsToPixel(dim.getWidth());
|
||||
// keep aspect ratio for height
|
||||
height = Units.pointsToPixel(dim.getHeight());
|
||||
}
|
||||
return new Dimension(width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(double alpha) {
|
||||
this.alpha = alpha;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BufferedImage getImage() {
|
||||
return getImage(getDimension());
|
||||
}
|
||||
|
||||
@Override
|
||||
public BufferedImage getImage(Dimension dim) {
|
||||
if (image == null) {
|
||||
return new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
|
||||
}
|
||||
|
||||
BufferedImage bufImg = new BufferedImage((int)dim.getWidth(), (int)dim.getHeight(), BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D g = bufImg.createGraphics();
|
||||
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
|
||||
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
|
||||
g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
|
||||
image.draw(g, new Rectangle2D.Double(0,0,dim.getWidth(),dim.getHeight()));
|
||||
g.dispose();
|
||||
|
||||
if (alpha != 0) {
|
||||
BufferedImage newImg = new BufferedImage((int)dim.getWidth(), (int)dim.getHeight(), BufferedImage.TYPE_INT_ARGB);
|
||||
g = newImg.createGraphics();
|
||||
RescaleOp op = new RescaleOp(new float[]{1.0f, 1.0f, 1.0f, (float)alpha}, new float[]{0,0,0,0}, null);
|
||||
g.drawImage(bufImg, op, 0, 0);
|
||||
g.dispose();
|
||||
bufImg = newImg;
|
||||
}
|
||||
|
||||
return bufImg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean drawImage(Graphics2D graphics, Rectangle2D anchor) {
|
||||
return drawImage(graphics, anchor, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean drawImage(Graphics2D graphics, Rectangle2D anchor, Insets clip) {
|
||||
if (image == null) {
|
||||
return false;
|
||||
} else {
|
||||
image.draw(graphics, anchor);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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,44 +0,0 @@
|
|||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
==================================================================== */
|
||||
|
||||
package org.apache.poi.hemf.hemfplus.record;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.poi.util.Internal;
|
||||
|
||||
@Internal
|
||||
public interface HemfPlusRecord {
|
||||
|
||||
HemfPlusRecordType getRecordType();
|
||||
|
||||
int getFlags();
|
||||
|
||||
/**
|
||||
*
|
||||
* @param dataBytes these are the bytes that start after the id, flags, record size
|
||||
* and go to the end of the record; they do not include any required padding
|
||||
* at the end.
|
||||
* @param recordId record type id
|
||||
* @param flags flags
|
||||
* @throws IOException, RecordFormatException
|
||||
*/
|
||||
void init(byte[] dataBytes, int recordId, int flags) throws IOException;
|
||||
|
||||
|
||||
}
|
|
@ -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,31 +0,0 @@
|
|||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
==================================================================== */
|
||||
|
||||
package org.apache.poi.hemf.record;
|
||||
|
||||
import org.apache.poi.util.Internal;
|
||||
|
||||
/**
|
||||
* Contains arbitrary data
|
||||
*/
|
||||
@Internal
|
||||
public class HemfComment extends AbstractHemfComment {
|
||||
|
||||
public HemfComment(byte[] rawBytes) {
|
||||
super(rawBytes);
|
||||
}
|
||||
}
|
|
@ -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,31 +0,0 @@
|
|||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
==================================================================== */
|
||||
|
||||
package org.apache.poi.hemf.record;
|
||||
|
||||
import org.apache.poi.util.Internal;
|
||||
|
||||
/**
|
||||
* Not yet implemented
|
||||
*/
|
||||
@Internal
|
||||
public class HemfCommentEMFSpool extends AbstractHemfComment {
|
||||
|
||||
public HemfCommentEMFSpool(byte[] rawBytes) {
|
||||
super(rawBytes);
|
||||
}
|
||||
}
|
|
@ -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,201 +0,0 @@
|
|||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
==================================================================== */
|
||||
|
||||
package org.apache.poi.hemf.record;
|
||||
|
||||
import java.awt.Rectangle;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.poi.util.IOUtils;
|
||||
import org.apache.poi.util.Internal;
|
||||
import org.apache.poi.util.LittleEndian;
|
||||
import org.apache.poi.util.LittleEndianInputStream;
|
||||
|
||||
/**
|
||||
* Extracts the full header from EMF files.
|
||||
* @see org.apache.poi.sl.image.ImageHeaderEMF
|
||||
*/
|
||||
@Internal
|
||||
public class HemfHeader implements HemfRecord {
|
||||
|
||||
private static final int MAX_RECORD_LENGTH = 1_000_000;
|
||||
|
||||
|
||||
private Rectangle boundsRectangle;
|
||||
private Rectangle frameRectangle;
|
||||
private long bytes;
|
||||
private long records;
|
||||
private int handles;
|
||||
private long nDescription;
|
||||
private long offDescription;
|
||||
private long nPalEntries;
|
||||
private boolean hasExtension1;
|
||||
private long cbPixelFormat;
|
||||
private long offPixelFormat;
|
||||
private long bOpenGL;
|
||||
private boolean hasExtension2;
|
||||
private long micrometersX;
|
||||
private long micrometersY;
|
||||
|
||||
public Rectangle getBoundsRectangle() {
|
||||
return boundsRectangle;
|
||||
}
|
||||
|
||||
public Rectangle getFrameRectangle() {
|
||||
return frameRectangle;
|
||||
}
|
||||
|
||||
public long getBytes() {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public long getRecords() {
|
||||
return records;
|
||||
}
|
||||
|
||||
public int getHandles() {
|
||||
return handles;
|
||||
}
|
||||
|
||||
public long getnDescription() {
|
||||
return nDescription;
|
||||
}
|
||||
|
||||
public long getOffDescription() {
|
||||
return offDescription;
|
||||
}
|
||||
|
||||
public long getnPalEntries() {
|
||||
return nPalEntries;
|
||||
}
|
||||
|
||||
public boolean isHasExtension1() {
|
||||
return hasExtension1;
|
||||
}
|
||||
|
||||
public long getCbPixelFormat() {
|
||||
return cbPixelFormat;
|
||||
}
|
||||
|
||||
public long getOffPixelFormat() {
|
||||
return offPixelFormat;
|
||||
}
|
||||
|
||||
public long getbOpenGL() {
|
||||
return bOpenGL;
|
||||
}
|
||||
|
||||
public boolean isHasExtension2() {
|
||||
return hasExtension2;
|
||||
}
|
||||
|
||||
public long getMicrometersX() {
|
||||
return micrometersX;
|
||||
}
|
||||
|
||||
public long getMicrometersY() {
|
||||
return micrometersY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "HemfHeader{" +
|
||||
"boundsRectangle=" + boundsRectangle +
|
||||
", frameRectangle=" + frameRectangle +
|
||||
", bytes=" + bytes +
|
||||
", records=" + records +
|
||||
", handles=" + handles +
|
||||
", nDescription=" + nDescription +
|
||||
", offDescription=" + offDescription +
|
||||
", nPalEntries=" + nPalEntries +
|
||||
", hasExtension1=" + hasExtension1 +
|
||||
", cbPixelFormat=" + cbPixelFormat +
|
||||
", offPixelFormat=" + offPixelFormat +
|
||||
", bOpenGL=" + bOpenGL +
|
||||
", hasExtension2=" + hasExtension2 +
|
||||
", micrometersX=" + micrometersX +
|
||||
", micrometersY=" + micrometersY +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public HemfRecordType getRecordType() {
|
||||
return HemfRecordType.header;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis, long recordId, long recordSize) throws IOException {
|
||||
if (recordId != 1L) {
|
||||
throw new IOException("Not a valid EMF header. Record type:"+recordId);
|
||||
}
|
||||
//read the record--id and size (2 bytes) have already been read
|
||||
byte[] data = IOUtils.safelyAllocate(recordSize, MAX_RECORD_LENGTH);
|
||||
IOUtils.readFully(leis, data);
|
||||
|
||||
int offset = 0;
|
||||
|
||||
//bounds
|
||||
int boundsLeft = LittleEndian.getInt(data, offset); offset += LittleEndian.INT_SIZE;
|
||||
int boundsTop = LittleEndian.getInt(data, offset); offset += LittleEndian.INT_SIZE;
|
||||
int boundsRight = LittleEndian.getInt(data, offset); offset += LittleEndian.INT_SIZE;
|
||||
int boundsBottom = LittleEndian.getInt(data, offset); offset += LittleEndian.INT_SIZE;
|
||||
boundsRectangle = new Rectangle(boundsLeft, boundsTop,
|
||||
boundsRight - boundsLeft, boundsBottom - boundsTop);
|
||||
|
||||
int frameLeft = LittleEndian.getInt(data, offset); offset += LittleEndian.INT_SIZE;
|
||||
int frameTop = LittleEndian.getInt(data, offset); offset += LittleEndian.INT_SIZE;
|
||||
int frameRight = LittleEndian.getInt(data, offset); offset += LittleEndian.INT_SIZE;
|
||||
int frameBottom = LittleEndian.getInt(data, offset); offset += LittleEndian.INT_SIZE;
|
||||
frameRectangle = new Rectangle(frameLeft, frameTop,
|
||||
frameRight - frameLeft, frameBottom - frameTop);
|
||||
|
||||
long recordSignature = LittleEndian.getInt(data, offset); offset += LittleEndian.INT_SIZE;
|
||||
if (recordSignature != 0x464D4520) {
|
||||
throw new IOException("bad record signature: " + recordSignature);
|
||||
}
|
||||
|
||||
long version = LittleEndian.getInt(data, offset); offset += LittleEndian.INT_SIZE;
|
||||
//According to the spec, MSOffice doesn't pay attention to this value.
|
||||
//It _should_ be 0x00010000
|
||||
bytes = LittleEndian.getUInt(data, offset); offset += LittleEndian.INT_SIZE;
|
||||
records = LittleEndian.getUInt(data, offset); offset += LittleEndian.INT_SIZE;
|
||||
handles = LittleEndian.getUShort(data, offset);offset += LittleEndian.SHORT_SIZE;
|
||||
offset += LittleEndian.SHORT_SIZE;//reserved
|
||||
nDescription = LittleEndian.getUInt(data, offset); offset += LittleEndian.INT_SIZE;
|
||||
offDescription = LittleEndian.getUInt(data, offset); offset += LittleEndian.INT_SIZE;
|
||||
nPalEntries = LittleEndian.getUInt(data, offset); offset += LittleEndian.INT_SIZE;
|
||||
|
||||
//should be skips
|
||||
offset += 8;//device
|
||||
offset += 8;//millimeters
|
||||
|
||||
|
||||
if (recordSize+8 >= 100) {
|
||||
hasExtension1 = true;
|
||||
cbPixelFormat = LittleEndian.getUInt(data, offset); offset += LittleEndian.INT_SIZE;
|
||||
offPixelFormat = LittleEndian.getUInt(data, offset); offset += LittleEndian.INT_SIZE;
|
||||
bOpenGL= LittleEndian.getUInt(data, offset); offset += LittleEndian.INT_SIZE;
|
||||
}
|
||||
|
||||
if (recordSize+8 >= 108) {
|
||||
hasExtension2 = true;
|
||||
micrometersX = LittleEndian.getUInt(data, offset); offset += LittleEndian.INT_SIZE;
|
||||
micrometersY = LittleEndian.getUInt(data, offset); offset += LittleEndian.INT_SIZE;
|
||||
}
|
||||
return recordSize;
|
||||
}
|
||||
}
|
|
@ -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,465 @@
|
|||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
==================================================================== */
|
||||
|
||||
package org.apache.poi.hemf.record.emf;
|
||||
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.apache.poi.hemf.record.emfplus.HemfPlusRecord;
|
||||
import org.apache.poi.hemf.record.emfplus.HemfPlusRecordIterator;
|
||||
import org.apache.poi.hwmf.usermodel.HwmfPicture;
|
||||
import org.apache.poi.util.IOUtils;
|
||||
import org.apache.poi.util.Internal;
|
||||
import org.apache.poi.util.LittleEndianConsts;
|
||||
import org.apache.poi.util.LittleEndianInputStream;
|
||||
import org.apache.poi.util.LocaleUtil;
|
||||
import org.apache.poi.util.RecordFormatException;
|
||||
|
||||
/**
|
||||
* Contains arbitrary data
|
||||
*/
|
||||
@Internal
|
||||
public class HemfComment {
|
||||
private static final int MAX_RECORD_LENGTH = HwmfPicture.MAX_RECORD_LENGTH;
|
||||
|
||||
public enum HemfCommentRecordType {
|
||||
emfGeneric(-1, EmfCommentDataGeneric::new, false),
|
||||
emfSpool(0x00000000, EmfCommentDataGeneric::new, false),
|
||||
emfPlus(0x2B464D45, EmfCommentDataPlus::new, false),
|
||||
emfPublic(0x43494447, null, false),
|
||||
emfBeginGroup(0x00000002, EmfCommentDataBeginGroup::new, true),
|
||||
emfEndGroup(0x00000003, EmfCommentDataEndGroup::new, true),
|
||||
emfMultiFormats(0x40000004, EmfCommentDataMultiformats::new, true),
|
||||
emfWMF(0x80000001, EmfCommentDataWMF::new, true),
|
||||
emfUnicodeString(0x00000040, EmfCommentDataUnicode::new, true),
|
||||
emfUnicodeEnd(0x00000080, EmfCommentDataUnicode::new, true)
|
||||
;
|
||||
|
||||
|
||||
public final long id;
|
||||
public final Supplier<? extends EmfCommentData> constructor;
|
||||
public final boolean isEmfPublic;
|
||||
|
||||
HemfCommentRecordType(long id, Supplier<? extends EmfCommentData> constructor, boolean isEmfPublic) {
|
||||
this.id = id;
|
||||
this.constructor = constructor;
|
||||
this.isEmfPublic = isEmfPublic;
|
||||
}
|
||||
|
||||
public static HemfCommentRecordType getById(long id, boolean isEmfPublic) {
|
||||
for (HemfCommentRecordType wrt : values()) {
|
||||
if (wrt.id == id && wrt.isEmfPublic == isEmfPublic) return wrt;
|
||||
}
|
||||
return emfGeneric;
|
||||
}
|
||||
}
|
||||
|
||||
public interface EmfCommentData {
|
||||
HemfCommentRecordType getCommentRecordType();
|
||||
|
||||
long init(LittleEndianInputStream leis, long dataSize) throws IOException;
|
||||
}
|
||||
|
||||
public static class EmfComment implements HemfRecord {
|
||||
private EmfCommentData data;
|
||||
|
||||
@Override
|
||||
public HemfRecordType getEmfRecordType() {
|
||||
return HemfRecordType.comment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
|
||||
int startIdx = leis.getReadIndex();
|
||||
data = new EmfCommentDataIterator(leis, (int)recordSize, true).next();
|
||||
return leis.getReadIndex()-startIdx;
|
||||
}
|
||||
|
||||
public EmfCommentData getCommentData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{ data: "+data+" }";
|
||||
}
|
||||
}
|
||||
|
||||
public static class EmfCommentDataIterator implements Iterator<EmfCommentData> {
|
||||
|
||||
private final LittleEndianInputStream leis;
|
||||
private final int startIdx;
|
||||
private final int limit;
|
||||
private EmfCommentData currentRecord;
|
||||
/** is the caller the EmfComment */
|
||||
private final boolean emfParent;
|
||||
|
||||
public EmfCommentDataIterator(LittleEndianInputStream leis, int limit, boolean emfParent) {
|
||||
this.leis = leis;
|
||||
this.limit = limit;
|
||||
this.emfParent = emfParent;
|
||||
startIdx = leis.getReadIndex();
|
||||
//queue the first non-header record
|
||||
currentRecord = _next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return currentRecord != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EmfCommentData next() {
|
||||
EmfCommentData toReturn = currentRecord;
|
||||
final boolean isEOF = (limit == -1 || leis.getReadIndex() >= startIdx+limit);
|
||||
// (currentRecord instanceof HemfPlusMisc.EmfEof)
|
||||
currentRecord = isEOF ? null : _next();
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
private EmfCommentData _next() {
|
||||
long type, recordSize;
|
||||
if (currentRecord == null && emfParent) {
|
||||
type = HemfRecordType.comment.id;
|
||||
recordSize = limit;
|
||||
} else {
|
||||
// A 32-bit unsigned integer from the RecordType enumeration that identifies this record
|
||||
// as a comment record. This value MUST be 0x00000046.
|
||||
try {
|
||||
type = leis.readUInt();
|
||||
} catch (RuntimeException e) {
|
||||
// EOF
|
||||
return null;
|
||||
}
|
||||
assert(type == HemfRecordType.comment.id);
|
||||
// A 32-bit unsigned integer that specifies the size in bytes of this record in the
|
||||
// metafile. This value MUST be a multiple of 4 bytes.
|
||||
recordSize = leis.readUInt();
|
||||
}
|
||||
|
||||
// A 32-bit unsigned integer that specifies the size, in bytes, of the CommentIdentifier and
|
||||
// CommentRecordParm fields in the RecordBuffer field that follows.
|
||||
// It MUST NOT include the size of itself or the size of the AlignmentPadding field, if present.
|
||||
long dataSize = leis.readUInt();
|
||||
|
||||
try {
|
||||
leis.mark(2*LittleEndianConsts.INT_SIZE);
|
||||
// An optional, 32-bit unsigned integer that identifies the type of comment record.
|
||||
// See the preceding table for descriptions of these record types.
|
||||
// Valid comment identifier values are listed in the following table.
|
||||
//
|
||||
// If this field contains any other value, the comment record MUST be an EMR_COMMENT record
|
||||
final int commentIdentifier = (int)leis.readUInt();
|
||||
// A 32-bit unsigned integer that identifies the type of public comment record.
|
||||
final int publicCommentIdentifier = (int)leis.readUInt();
|
||||
|
||||
final boolean isEmfPublic = (commentIdentifier == HemfCommentRecordType.emfPublic.id);
|
||||
leis.reset();
|
||||
|
||||
final HemfCommentRecordType commentType = HemfCommentRecordType.getById
|
||||
(isEmfPublic ? publicCommentIdentifier : commentIdentifier, isEmfPublic);
|
||||
assert(commentType != null);
|
||||
final EmfCommentData record = commentType.constructor.get();
|
||||
|
||||
long readBytes = record.init(leis, dataSize);
|
||||
final int skipBytes = (int)(recordSize-4-readBytes);
|
||||
assert (skipBytes >= 0);
|
||||
leis.skipFully(skipBytes);
|
||||
|
||||
return record;
|
||||
} catch (IOException e) {
|
||||
throw new RecordFormatException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException("Remove not supported");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Private data is unknown to EMF; it is meaningful only to applications that know the format of the
|
||||
* data and how to use it. EMR_COMMENT private data records MAY be ignored.
|
||||
*/
|
||||
public static class EmfCommentDataGeneric implements EmfCommentData {
|
||||
private byte[] privateData;
|
||||
|
||||
@Override
|
||||
public HemfCommentRecordType getCommentRecordType() {
|
||||
return HemfCommentRecordType.emfGeneric;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis, long dataSize) throws IOException {
|
||||
privateData = IOUtils.safelyAllocate(dataSize, MAX_RECORD_LENGTH);
|
||||
leis.readFully(privateData);
|
||||
return privateData.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "\""+new String(privateData, LocaleUtil.CHARSET_1252).replaceAll("\\p{Cntrl}", ".")+"\"";
|
||||
}
|
||||
}
|
||||
|
||||
/** The EMR_COMMENT_EMFPLUS record contains embedded EMF+ records. */
|
||||
public static class EmfCommentDataPlus implements EmfCommentData {
|
||||
private final List<HemfPlusRecord> records = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public HemfCommentRecordType getCommentRecordType() {
|
||||
return HemfCommentRecordType.emfPlus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(final LittleEndianInputStream leis, final long dataSize)
|
||||
throws IOException {
|
||||
long startIdx = leis.getReadIndex();
|
||||
int commentIdentifier = leis.readInt();
|
||||
assert (commentIdentifier == HemfCommentRecordType.emfPlus.id);
|
||||
new HemfPlusRecordIterator(leis, (int)dataSize-LittleEndianConsts.INT_SIZE).forEachRemaining(records::add);
|
||||
return leis.getReadIndex()-startIdx;
|
||||
}
|
||||
|
||||
public List<HemfPlusRecord> getRecords() {
|
||||
return Collections.unmodifiableList(records);
|
||||
}
|
||||
}
|
||||
|
||||
public static class EmfCommentDataBeginGroup implements EmfCommentData {
|
||||
private final Rectangle2D bounds = new Rectangle2D.Double();
|
||||
private String description;
|
||||
|
||||
@Override
|
||||
public HemfCommentRecordType getCommentRecordType() {
|
||||
return HemfCommentRecordType.emfBeginGroup;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(final LittleEndianInputStream leis, final long dataSize)
|
||||
throws IOException {
|
||||
final int startIdx = leis.getReadIndex();
|
||||
final int commentIdentifier = (int)leis.readUInt();
|
||||
assert(commentIdentifier == HemfCommentRecordType.emfPublic.id);
|
||||
final int publicCommentIdentifier = (int)leis.readUInt();
|
||||
assert(publicCommentIdentifier == HemfCommentRecordType.emfBeginGroup.id);
|
||||
HemfDraw.readRectL(leis, bounds);
|
||||
|
||||
// The number of Unicode characters in the optional description string that follows.
|
||||
int nDescription = (int)leis.readUInt();
|
||||
|
||||
byte[] buf = IOUtils.safelyAllocate(nDescription*2, MAX_RECORD_LENGTH);
|
||||
leis.readFully(buf);
|
||||
description = new String(buf, StandardCharsets.UTF_16LE);
|
||||
|
||||
return leis.getReadIndex()-startIdx;
|
||||
}
|
||||
}
|
||||
|
||||
public static class EmfCommentDataEndGroup implements EmfCommentData {
|
||||
@Override
|
||||
public HemfCommentRecordType getCommentRecordType() {
|
||||
return HemfCommentRecordType.emfEndGroup;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(final LittleEndianInputStream leis, final long dataSize)
|
||||
throws IOException {
|
||||
final int startIdx = leis.getReadIndex();
|
||||
final int commentIdentifier = (int)leis.readUInt();
|
||||
assert(commentIdentifier == HemfCommentRecordType.emfPublic.id);
|
||||
final int publicCommentIdentifier = (int)leis.readUInt();
|
||||
assert(publicCommentIdentifier == HemfCommentRecordType.emfEndGroup.id);
|
||||
return leis.getReadIndex()-startIdx;
|
||||
}
|
||||
}
|
||||
|
||||
public static class EmfCommentDataMultiformats implements EmfCommentData {
|
||||
private final Rectangle2D bounds = new Rectangle2D.Double();
|
||||
private final List<EmfCommentDataFormat> formats = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public HemfCommentRecordType getCommentRecordType() {
|
||||
return HemfCommentRecordType.emfMultiFormats;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(final LittleEndianInputStream leis, final long dataSize)
|
||||
throws IOException {
|
||||
final int startIdx = leis.getReadIndex();
|
||||
final int commentIdentifier = (int)leis.readUInt();
|
||||
assert(commentIdentifier == HemfCommentRecordType.emfPublic.id);
|
||||
final int publicCommentIdentifier = (int)leis.readUInt();
|
||||
assert(publicCommentIdentifier == HemfCommentRecordType.emfMultiFormats.id);
|
||||
HemfDraw.readRectL(leis, bounds);
|
||||
|
||||
// A 32-bit unsigned integer that specifies the number of graphics formats contained in this record.
|
||||
int countFormats = (int)leis.readUInt();
|
||||
for (int i=0; i<countFormats; i++) {
|
||||
EmfCommentDataFormat fmt = new EmfCommentDataFormat();
|
||||
long readBytes = fmt.init(leis, dataSize, startIdx);
|
||||
formats.add(fmt);
|
||||
if (readBytes == 0) {
|
||||
// binary data is appended without DataFormat header
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (EmfCommentDataFormat fmt : formats) {
|
||||
int skip = fmt.offData-(leis.getReadIndex()-startIdx);
|
||||
leis.skipFully(skip);
|
||||
fmt.rawData = IOUtils.safelyAllocate(fmt.sizeData, MAX_RECORD_LENGTH);
|
||||
int readBytes = leis.read(fmt.rawData);
|
||||
if (readBytes < fmt.sizeData) {
|
||||
// EOF
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return leis.getReadIndex()-startIdx;
|
||||
}
|
||||
|
||||
public List<EmfCommentDataFormat> getFormats() {
|
||||
return Collections.unmodifiableList(formats);
|
||||
}
|
||||
}
|
||||
|
||||
public enum EmfFormatSignature {
|
||||
ENHMETA_SIGNATURE(0x464D4520),
|
||||
EPS_SIGNATURE(0x46535045);
|
||||
|
||||
int id;
|
||||
|
||||
EmfFormatSignature(int flag) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public static EmfFormatSignature getById(int id) {
|
||||
for (EmfFormatSignature wrt : values()) {
|
||||
if (wrt.id == id) return wrt;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class EmfCommentDataFormat {
|
||||
private EmfFormatSignature signature;
|
||||
private int version;
|
||||
private int sizeData;
|
||||
private int offData;
|
||||
private byte[] rawData;
|
||||
|
||||
public long init(final LittleEndianInputStream leis, final long dataSize, long startIdx) throws IOException {
|
||||
// A 32-bit unsigned integer that specifies the format of the image data.
|
||||
signature = EmfFormatSignature.getById(leis.readInt());
|
||||
|
||||
// A 32-bit unsigned integer that specifies the format version number.
|
||||
// If the Signature field specifies encapsulated PostScript (EPS), this value MUST be 0x00000001;
|
||||
// otherwise, this value MUST be ignored.
|
||||
version = leis.readInt();
|
||||
|
||||
// A 32-bit unsigned integer that specifies the size of the data in bytes.
|
||||
sizeData = leis.readInt();
|
||||
|
||||
// A 32-bit unsigned integer that specifies the offset to the data from the start
|
||||
// of the identifier field in an EMR_COMMENT_PUBLIC record. The offset MUST be 32-bit aligned.
|
||||
offData = leis.readInt();
|
||||
if (sizeData < 0) {
|
||||
throw new RecordFormatException("size for emrformat must be > 0");
|
||||
}
|
||||
if (offData < 0) {
|
||||
throw new RecordFormatException("offset for emrformat must be > 0");
|
||||
}
|
||||
|
||||
return 4*LittleEndianConsts.INT_SIZE;
|
||||
}
|
||||
|
||||
public byte[] getRawData() {
|
||||
return rawData;
|
||||
}
|
||||
}
|
||||
|
||||
public static class EmfCommentDataWMF implements EmfCommentData {
|
||||
private final Rectangle2D bounds = new Rectangle2D.Double();
|
||||
private final List<EmfCommentDataFormat> formats = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public HemfCommentRecordType getCommentRecordType() {
|
||||
return HemfCommentRecordType.emfWMF;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(final LittleEndianInputStream leis, final long dataSize) throws IOException {
|
||||
final int startIdx = leis.getReadIndex();
|
||||
final int commentIdentifier = (int)leis.readUInt();
|
||||
assert(commentIdentifier == HemfCommentRecordType.emfPublic.id);
|
||||
final int publicCommentIdentifier = (int)leis.readUInt();
|
||||
assert(publicCommentIdentifier == HemfCommentRecordType.emfWMF.id);
|
||||
|
||||
// A 16-bit unsigned integer that specifies the WMF metafile version in terms
|
||||
//of support for device-independent bitmaps (DIBs)
|
||||
int version = leis.readUShort();
|
||||
|
||||
// A 16-bit value that MUST be 0x0000 and MUST be ignored.
|
||||
leis.skipFully(LittleEndianConsts.SHORT_SIZE);
|
||||
|
||||
// A 32-bit unsigned integer that specifies the checksum for this record.
|
||||
int checksum = leis.readInt();
|
||||
|
||||
// A 32-bit value that MUST be 0x00000000 and MUST be ignored.
|
||||
int flags = leis.readInt();
|
||||
|
||||
// A 32-bit unsigned integer that specifies the size, in bytes, of the
|
||||
// WMF metafile in the WinMetafile field.
|
||||
int winMetafileSize = (int)leis.readUInt();
|
||||
|
||||
byte[] winMetafile = IOUtils.safelyAllocate(winMetafileSize, MAX_RECORD_LENGTH);
|
||||
// some emf comments are truncated, so we don't use readFully here
|
||||
leis.read(winMetafile);
|
||||
|
||||
return leis.getReadIndex()-startIdx;
|
||||
}
|
||||
}
|
||||
|
||||
public static class EmfCommentDataUnicode implements EmfCommentData {
|
||||
private final Rectangle2D bounds = new Rectangle2D.Double();
|
||||
private final List<EmfCommentDataFormat> formats = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public HemfCommentRecordType getCommentRecordType() {
|
||||
return HemfCommentRecordType.emfUnicodeString;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(final LittleEndianInputStream leis, final long dataSize)
|
||||
throws IOException {
|
||||
throw new RecordFormatException("UNICODE_STRING/UNICODE_END values are reserved in CommentPublic records");
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,719 @@
|
|||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
==================================================================== */
|
||||
|
||||
package org.apache.poi.hemf.record.emf;
|
||||
|
||||
import static org.apache.poi.hemf.record.emf.HemfDraw.readPointL;
|
||||
import static org.apache.poi.hemf.record.emf.HemfDraw.readRectL;
|
||||
import static org.apache.poi.hemf.record.emf.HemfRecordIterator.HEADER_SIZE;
|
||||
import static org.apache.poi.hwmf.record.HwmfDraw.boundsToString;
|
||||
import static org.apache.poi.hwmf.record.HwmfDraw.pointToString;
|
||||
|
||||
import java.awt.Shape;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.Area;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.poi.hemf.draw.HemfDrawProperties;
|
||||
import org.apache.poi.hemf.draw.HemfGraphics;
|
||||
import org.apache.poi.hwmf.draw.HwmfGraphics;
|
||||
import org.apache.poi.hwmf.record.HwmfBitmapDib;
|
||||
import org.apache.poi.hwmf.record.HwmfColorRef;
|
||||
import org.apache.poi.hwmf.record.HwmfDraw;
|
||||
import org.apache.poi.hwmf.record.HwmfFill;
|
||||
import org.apache.poi.hwmf.record.HwmfFill.ColorUsage;
|
||||
import org.apache.poi.hwmf.record.HwmfRegionMode;
|
||||
import org.apache.poi.hwmf.record.HwmfTernaryRasterOp;
|
||||
import org.apache.poi.util.IOUtils;
|
||||
import org.apache.poi.util.LittleEndian;
|
||||
import org.apache.poi.util.LittleEndianConsts;
|
||||
import org.apache.poi.util.LittleEndianInputStream;
|
||||
|
||||
public class HemfFill {
|
||||
private static final int MAX_RECORD_LENGTH = 10_000_000;
|
||||
|
||||
/**
|
||||
* The EMR_SETPOLYFILLMODE record defines polygon fill mode.
|
||||
*/
|
||||
public static class EmfSetPolyfillMode extends HwmfFill.WmfSetPolyfillMode implements HemfRecord {
|
||||
|
||||
@Override
|
||||
public HemfRecordType getEmfRecordType() {
|
||||
return HemfRecordType.setPolyfillMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
|
||||
// A 32-bit unsigned integer that specifies the polygon fill mode and
|
||||
// MUST be in the PolygonFillMode enumeration.
|
||||
polyFillMode = HwmfPolyfillMode.valueOf((int)leis.readUInt());
|
||||
return LittleEndianConsts.INT_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
public static class EmfExtFloodFill extends HwmfFill.WmfExtFloodFill implements HemfRecord {
|
||||
|
||||
@Override
|
||||
public HemfRecordType getEmfRecordType() {
|
||||
return HemfRecordType.extFloodFill;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
|
||||
long size = readPointL(leis, start);
|
||||
size += colorRef.init(leis);
|
||||
// A 32-bit unsigned integer that specifies how to use the Color value to determine the area for
|
||||
// the flood fill operation. The value MUST be in the FloodFill enumeration
|
||||
mode = (int)leis.readUInt();
|
||||
return size + LittleEndianConsts.INT_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The EMR_STRETCHBLT record specifies a block transfer of pixels from a source bitmap to a destination rectangle,
|
||||
* optionally in combination with a brush pattern, according to a specified raster operation, stretching or
|
||||
* compressing the output to fit the dimensions of the destination, if necessary.
|
||||
*/
|
||||
public static class EmfStretchBlt extends HwmfFill.WmfStretchDib implements HemfRecord {
|
||||
protected final Rectangle2D bounds = new Rectangle2D.Double();
|
||||
|
||||
/** An XForm object that specifies a world-space to page-space transform to apply to the source bitmap. */
|
||||
protected final AffineTransform xFormSrc = new AffineTransform();
|
||||
|
||||
/** A WMF ColorRef object that specifies the background color of the source bitmap. */
|
||||
protected final HwmfColorRef bkColorSrc = new HwmfColorRef();
|
||||
|
||||
@Override
|
||||
public HemfRecordType getEmfRecordType() {
|
||||
return HemfRecordType.stretchBlt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
|
||||
int startIdx = leis.getReadIndex();
|
||||
|
||||
long size = readRectL(leis, bounds);
|
||||
|
||||
size += readBounds2(leis, this.dstBounds);
|
||||
|
||||
// A 32-bit unsigned integer that specifies the raster operation code. This code defines how the
|
||||
// color data of the source rectangle is to be combined with the color data of the destination
|
||||
// rectangle and optionally a brush pattern, to achieve the final color.
|
||||
int rasterOpIndex = (int)leis.readUInt();
|
||||
|
||||
rasterOperation = HwmfTernaryRasterOp.valueOf(rasterOpIndex >>> 16);
|
||||
|
||||
size += LittleEndianConsts.INT_SIZE;
|
||||
|
||||
final Point2D srcPnt = new Point2D.Double();
|
||||
size += readPointL(leis, srcPnt);
|
||||
|
||||
size += readXForm(leis, xFormSrc);
|
||||
|
||||
size += bkColorSrc.init(leis);
|
||||
|
||||
colorUsage = ColorUsage.valueOf((int)leis.readUInt());
|
||||
|
||||
// A 32-bit unsigned integer that specifies the offset, in bytes, from the
|
||||
// start of this record to the source bitmap header in the BitmapBuffer field.
|
||||
final int offBmiSrc = (int)leis.readUInt();
|
||||
|
||||
// A 32-bit unsigned integer that specifies the size, in bytes, of the source bitmap header.
|
||||
final int cbBmiSrc = (int)leis.readUInt();
|
||||
size += 3*LittleEndianConsts.INT_SIZE;
|
||||
if (size >= recordSize) {
|
||||
return size;
|
||||
}
|
||||
|
||||
// A 32-bit unsigned integer that specifies the offset, in bytes, from the
|
||||
// start of this record to the source bitmap bits in the BitmapBuffer field.
|
||||
final int offBitsSrc = (int)leis.readUInt();
|
||||
|
||||
// A 32-bit unsigned integer that specifies the size, in bytes, of the source bitmap bits.
|
||||
final int cbBitsSrc = (int)leis.readUInt();
|
||||
size += 2*LittleEndianConsts.INT_SIZE;
|
||||
|
||||
if (size >= recordSize) {
|
||||
return size;
|
||||
}
|
||||
|
||||
if (srcEqualsDstDimension()) {
|
||||
srcBounds.setRect(srcPnt.getX(), srcPnt.getY(), dstBounds.getWidth(), dstBounds.getHeight());
|
||||
} else {
|
||||
int srcWidth = leis.readInt();
|
||||
int srcHeight = leis.readInt();
|
||||
size += 2 * LittleEndianConsts.INT_SIZE;
|
||||
srcBounds.setRect(srcPnt.getX(), srcPnt.getY(), srcWidth, srcHeight);
|
||||
}
|
||||
|
||||
size += readBitmap(leis, bitmap, startIdx, offBmiSrc, cbBmiSrc, offBitsSrc, cbBitsSrc);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
protected boolean srcEqualsDstDimension() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(HemfGraphics ctx) {
|
||||
HemfDrawProperties prop = ctx.getProperties();
|
||||
prop.setBackgroundColor(this.bkColorSrc);
|
||||
super.draw(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return
|
||||
"{ bounds: "+boundsToString(bounds)+
|
||||
", xFormSrc: { scaleX: "+xFormSrc.getScaleX()+", shearX: "+xFormSrc.getShearX()+", transX: "+xFormSrc.getTranslateX()+", scaleY: "+xFormSrc.getScaleY()+", shearY: "+xFormSrc.getShearY()+", transY: "+xFormSrc.getTranslateY()+" }"+
|
||||
", bkColorSrc: "+bkColorSrc+
|
||||
","+super.toString().substring(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The EMR_STRETCHDIBITS record specifies a block transfer of pixels from a source bitmap to a
|
||||
* destination rectangle, optionally in combination with a brush pattern, according to a specified raster
|
||||
* operation, stretching or compressing the output to fit the dimensions of the destination, if necessary.
|
||||
*/
|
||||
public static class EmfStretchDiBits extends HwmfFill.WmfStretchDib implements HemfRecord {
|
||||
protected final Rectangle2D bounds = new Rectangle2D.Double();
|
||||
|
||||
@Override
|
||||
public HemfRecordType getEmfRecordType() {
|
||||
return HemfRecordType.stretchDiBits;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
|
||||
final int startIdx = leis.getReadIndex();
|
||||
|
||||
long size = readRectL(leis, bounds);
|
||||
|
||||
// A 32-bit signed integer that specifies the logical x-coordinate of the upper-left
|
||||
// corner of the destination rectangle.
|
||||
int xDest = leis.readInt();
|
||||
int yDest = leis.readInt();
|
||||
size += 2*LittleEndianConsts.INT_SIZE;
|
||||
|
||||
size += readBounds2(leis, srcBounds);
|
||||
|
||||
// A 32-bit unsigned integer that specifies the offset, in bytes from the start
|
||||
// of this record to the source bitmap header.
|
||||
int offBmiSrc = (int)leis.readUInt();
|
||||
|
||||
// A 32-bit unsigned integer that specifies the size, in bytes, of the source bitmap header.
|
||||
int cbBmiSrc = (int)leis.readUInt();
|
||||
|
||||
// A 32-bit unsigned integer that specifies the offset, in bytes, from the
|
||||
// start of this record to the source bitmap bits.
|
||||
int offBitsSrc = (int)leis.readUInt();
|
||||
|
||||
// A 32-bit unsigned integer that specifies the size, in bytes, of the source bitmap bits.
|
||||
int cbBitsSrc = (int)leis.readUInt();
|
||||
|
||||
// A 32-bit unsigned integer that specifies how to interpret values in the color table
|
||||
// in the source bitmap header. This value MUST be in the DIBColors enumeration
|
||||
colorUsage = ColorUsage.valueOf(leis.readInt());
|
||||
|
||||
// A 32-bit unsigned integer that specifies a raster operation code.
|
||||
// These codes define how the color data of the source rectangle is to be combined with the color data
|
||||
// of the destination rectangle and optionally a brush pattern, to achieve the final color.
|
||||
// The value MUST be in the WMF Ternary Raster Operation enumeration
|
||||
int rasterOpIndex = (int)leis.readUInt();
|
||||
rasterOperation = HwmfTernaryRasterOp.valueOf(rasterOpIndex >>> 16);
|
||||
|
||||
// A 32-bit signed integer that specifies the logical width of the destination rectangle.
|
||||
int cxDest = leis.readInt();
|
||||
|
||||
// A 32-bit signed integer that specifies the logical height of the destination rectangle.
|
||||
int cyDest = leis.readInt();
|
||||
|
||||
dstBounds.setRect(xDest, yDest, cxDest, cyDest);
|
||||
|
||||
size += 8*LittleEndianConsts.INT_SIZE;
|
||||
|
||||
size += readBitmap(leis, bitmap, startIdx, offBmiSrc, cbBmiSrc, offBitsSrc, cbBitsSrc);
|
||||
|
||||
return size;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The EMR_BITBLT record specifies a block transfer of pixels from a source bitmap to a destination rectangle,
|
||||
* optionally in combination with a brush pattern, according to a specified raster operation.
|
||||
*/
|
||||
public static class EmfBitBlt extends EmfStretchBlt {
|
||||
@Override
|
||||
public HemfRecordType getEmfRecordType() {
|
||||
return HemfRecordType.bitBlt;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean srcEqualsDstDimension() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** The EMR_FRAMERGN record draws a border around the specified region using the specified brush. */
|
||||
public static class EmfFrameRgn extends HwmfDraw.WmfFrameRegion implements HemfRecord {
|
||||
private final Rectangle2D bounds = new Rectangle2D.Double();
|
||||
private final List<Rectangle2D> rgnRects = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public HemfRecordType getEmfRecordType() {
|
||||
return HemfRecordType.frameRgn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
|
||||
long size = readRectL(leis, bounds);
|
||||
// A 32-bit unsigned integer that specifies the size of region data, in bytes.
|
||||
long rgnDataSize = leis.readUInt();
|
||||
// A 32-bit unsigned integer that specifies the brush EMF Object Table index.
|
||||
brushIndex = (int)leis.readUInt();
|
||||
// A 32-bit signed integer that specifies the width of the vertical brush stroke, in logical units.
|
||||
width = leis.readInt();
|
||||
// A 32-bit signed integer that specifies the height of the horizontal brush stroke, in logical units.
|
||||
height = leis.readInt();
|
||||
size += 4*LittleEndianConsts.INT_SIZE;
|
||||
size += readRgnData(leis, rgnRects);
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(HwmfGraphics ctx) {
|
||||
ctx.applyObjectTableEntry(brushIndex);
|
||||
ctx.fill(getShape());
|
||||
}
|
||||
|
||||
protected Shape getShape() {
|
||||
return getRgnShape(rgnRects);
|
||||
}
|
||||
}
|
||||
|
||||
/** The EMR_INVERTRGN record inverts the colors in the specified region. */
|
||||
public static class EmfInvertRgn implements HemfRecord {
|
||||
protected final Rectangle2D bounds = new Rectangle2D.Double();
|
||||
protected final List<Rectangle2D> rgnRects = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public HemfRecordType getEmfRecordType() {
|
||||
return HemfRecordType.invertRgn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
|
||||
long size = readRectL(leis, bounds);
|
||||
// A 32-bit unsigned integer that specifies the size of region data, in bytes.
|
||||
long rgnDataSize = leis.readUInt();
|
||||
size += LittleEndianConsts.INT_SIZE;
|
||||
size += readRgnData(leis, rgnRects);
|
||||
return size;
|
||||
}
|
||||
|
||||
protected Shape getShape() {
|
||||
return getRgnShape(rgnRects);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The EMR_PAINTRGN record paints the specified region by using the brush currently selected into the
|
||||
* playback device context.
|
||||
*/
|
||||
public static class EmfPaintRgn extends EmfInvertRgn {
|
||||
@Override
|
||||
public HemfRecordType getEmfRecordType() {
|
||||
return HemfRecordType.paintRgn;
|
||||
}
|
||||
}
|
||||
|
||||
/** The EMR_FILLRGN record fills the specified region by using the specified brush. */
|
||||
public static class EmfFillRgn extends HwmfFill.WmfFillRegion implements HemfRecord {
|
||||
protected final Rectangle2D bounds = new Rectangle2D.Double();
|
||||
protected final List<Rectangle2D> rgnRects = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public HemfRecordType getEmfRecordType() {
|
||||
return HemfRecordType.fillRgn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
|
||||
long size = readRectL(leis, bounds);
|
||||
// A 32-bit unsigned integer that specifies the size of region data, in bytes.
|
||||
long rgnDataSize = leis.readUInt();
|
||||
brushIndex = (int)leis.readUInt();
|
||||
size += 2*LittleEndianConsts.INT_SIZE;
|
||||
size += readRgnData(leis, rgnRects);
|
||||
return size;
|
||||
}
|
||||
|
||||
protected Shape getShape() {
|
||||
return getRgnShape(rgnRects);
|
||||
}
|
||||
}
|
||||
|
||||
public static class EmfExtSelectClipRgn implements HemfRecord {
|
||||
protected HwmfRegionMode regionMode;
|
||||
protected final List<Rectangle2D> rgnRects = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public HemfRecordType getEmfRecordType() {
|
||||
return HemfRecordType.extSelectClipRgn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
|
||||
// A 32-bit unsigned integer that specifies the size of region data in bytes
|
||||
long rgnDataSize = leis.readUInt();
|
||||
// A 32-bit unsigned integer that specifies the way to use the region.
|
||||
regionMode = HwmfRegionMode.valueOf((int)leis.readUInt());
|
||||
long size = 2* LittleEndianConsts.INT_SIZE;
|
||||
|
||||
// If RegionMode is RGN_COPY, this data can be omitted and the clip region
|
||||
// SHOULD be set to the default (NULL) clip region.
|
||||
if (regionMode != HwmfRegionMode.RGN_COPY) {
|
||||
size += readRgnData(leis, rgnRects);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
protected Shape getShape() {
|
||||
return getRgnShape(rgnRects);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(HemfGraphics ctx) {
|
||||
HemfDrawProperties prop = ctx.getProperties();
|
||||
ctx.setClip(getShape(), regionMode, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("{ regionMode: '"+regionMode+"'");
|
||||
sb.append(", regions: [");
|
||||
boolean isFirst = true;
|
||||
for (Rectangle2D r : rgnRects) {
|
||||
if (!isFirst) {
|
||||
sb.append(",");
|
||||
}
|
||||
isFirst = false;
|
||||
sb.append(boundsToString(r));
|
||||
}
|
||||
sb.append("]}");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
public static class EmfAlphaBlend implements HemfRecord {
|
||||
/** the destination bounding rectangle in device units */
|
||||
protected final Rectangle2D bounds = new Rectangle2D.Double();
|
||||
/** the destination rectangle */
|
||||
protected final Rectangle2D destRect = new Rectangle2D.Double();
|
||||
/** the source rectangle */
|
||||
protected final Rectangle2D srcRect = new Rectangle2D.Double();
|
||||
/**
|
||||
* The blend operation code. The only source and destination blend operation that has been defined
|
||||
* is 0x00, which specifies that the source bitmap MUST be combined with the destination bitmap based
|
||||
* on the alpha transparency values of the source pixels.
|
||||
*/
|
||||
protected byte blendOperation;
|
||||
/** This value MUST be 0x00 and MUST be ignored. */
|
||||
protected byte blendFlags;
|
||||
/**
|
||||
* An 8-bit unsigned integer that specifies alpha transparency, which determines the blend of the source
|
||||
* and destination bitmaps. This value MUST be used on the entire source bitmap. The minimum alpha
|
||||
* transparency value, zero, corresponds to completely transparent; the maximum value, 0xFF, corresponds
|
||||
* to completely opaque. In effect, a value of 0xFF specifies that the per-pixel alpha values determine
|
||||
* the blend of the source and destination bitmaps.
|
||||
*/
|
||||
protected int srcConstantAlpha;
|
||||
/**
|
||||
* A byte that specifies how source and destination pixels are interpreted with respect to alpha transparency.
|
||||
*
|
||||
* 0x00:
|
||||
* The pixels in the source bitmap do not specify alpha transparency.
|
||||
* In this case, the SrcConstantAlpha value determines the blend of the source and destination bitmaps.
|
||||
* Note that in the following equations SrcConstantAlpha is divided by 255,
|
||||
* which produces a value in the range 0 to 1.
|
||||
*
|
||||
* 0x01: "AC_SRC_ALPHA"
|
||||
* Indicates that the source bitmap is 32 bits-per-pixel and specifies an alpha transparency value
|
||||
* for each pixel.
|
||||
*/
|
||||
protected byte alphaFormat;
|
||||
/** a world-space to page-space transform to apply to the source bitmap. */
|
||||
protected final AffineTransform xFormSrc = new AffineTransform();
|
||||
/** the background color of the source bitmap. */
|
||||
protected final HwmfColorRef bkColorSrc = new HwmfColorRef();
|
||||
/**
|
||||
* A 32-bit unsigned integer that specifies how to interpret values in the
|
||||
* color table in the source bitmap header.
|
||||
*/
|
||||
protected ColorUsage usageSrc;
|
||||
|
||||
protected final HwmfBitmapDib bitmap = new HwmfBitmapDib();
|
||||
|
||||
@Override
|
||||
public HemfRecordType getEmfRecordType() {
|
||||
return HemfRecordType.alphaBlend;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
|
||||
final int startIdx = leis.getReadIndex();
|
||||
|
||||
long size = readRectL(leis, bounds);
|
||||
size += readBounds2(leis, destRect);
|
||||
|
||||
blendOperation = leis.readByte();
|
||||
assert (blendOperation == 0);
|
||||
blendFlags = leis.readByte();
|
||||
assert (blendOperation == 0);
|
||||
srcConstantAlpha = leis.readUByte();
|
||||
alphaFormat = leis.readByte();
|
||||
|
||||
// A 32-bit signed integer that specifies the logical x-coordinate of the upper-left
|
||||
// corner of the source rectangle.
|
||||
final int xSrc = leis.readInt();
|
||||
// A 32-bit signed integer that specifies the logical y-coordinate of the upper-left
|
||||
// corner of the source rectangle.
|
||||
final int ySrc = leis.readInt();
|
||||
|
||||
size += 3*LittleEndianConsts.INT_SIZE;
|
||||
size += readXForm(leis, xFormSrc);
|
||||
size += bkColorSrc.init(leis);
|
||||
|
||||
usageSrc = ColorUsage.valueOf((int)leis.readUInt());
|
||||
|
||||
|
||||
// A 32-bit unsigned integer that specifies the offset, in bytes, from the
|
||||
// start of this record to the source bitmap header in the BitmapBuffer field.
|
||||
final int offBmiSrc = (int)leis.readUInt();
|
||||
|
||||
// A 32-bit unsigned integer that specifies the size, in bytes, of the source bitmap header.
|
||||
final int cbBmiSrc = (int)leis.readUInt();
|
||||
// A 32-bit unsigned integer that specifies the offset, in bytes, from the
|
||||
// start of this record to the source bitmap bits in the BitmapBuffer field.
|
||||
final int offBitsSrc = (int)leis.readUInt();
|
||||
// A 32-bit unsigned integer that specifies the size, in bytes, of the source bitmap bits.
|
||||
final int cbBitsSrc = (int)leis.readUInt();
|
||||
|
||||
// A 32-bit signed integer that specifies the logical width of the source rectangle.
|
||||
// This value MUST be greater than zero.
|
||||
final int cxSrc = leis.readInt();
|
||||
// A 32-bit signed integer that specifies the logical height of the source rectangle.
|
||||
// This value MUST be greater than zero.
|
||||
final int cySrc = leis.readInt();
|
||||
|
||||
srcRect.setRect(xSrc, ySrc, cxSrc, cySrc);
|
||||
|
||||
size += 7 * LittleEndianConsts.INT_SIZE;
|
||||
|
||||
size += readBitmap(leis, bitmap, startIdx, offBmiSrc, cbBmiSrc, offBitsSrc, cbBitsSrc);
|
||||
|
||||
return size;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The EMR_SETDIBITSTODEVICE record specifies a block transfer of pixels from specified scanlines of
|
||||
* a source bitmap to a destination rectangle.
|
||||
*/
|
||||
public static class EmfSetDiBitsToDevice implements HemfRecord {
|
||||
protected final Rectangle2D bounds = new Rectangle2D.Double();
|
||||
protected final Point2D dest = new Point2D.Double();
|
||||
protected final Rectangle2D src = new Rectangle2D.Double();
|
||||
protected ColorUsage usageSrc;
|
||||
protected final HwmfBitmapDib bitmap = new HwmfBitmapDib();
|
||||
|
||||
@Override
|
||||
public HemfRecordType getEmfRecordType() {
|
||||
return HemfRecordType.setDiBitsToDevice;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
|
||||
int startIdx = leis.getReadIndex();
|
||||
|
||||
// A WMF RectL object that defines the destination bounding rectangle in device units.
|
||||
long size = readRectL(leis, bounds);
|
||||
// the logical x/y-coordinate of the upper-left corner of the destination rectangle.
|
||||
size += readPointL(leis, dest);
|
||||
// the source rectangle
|
||||
size += readBounds2(leis, src);
|
||||
// A 32-bit unsigned integer that specifies the offset, in bytes, from the
|
||||
// start of this record to the source bitmap header in the BitmapBuffer field.
|
||||
final int offBmiSrc = (int)leis.readUInt();
|
||||
// A 32-bit unsigned integer that specifies the size, in bytes, of the source bitmap header.
|
||||
final int cbBmiSrc = (int)leis.readUInt();
|
||||
// A 32-bit unsigned integer that specifies the offset, in bytes, from the
|
||||
// start of this record to the source bitmap bits in the BitmapBuffer field.
|
||||
final int offBitsSrc = (int)leis.readUInt();
|
||||
// A 32-bit unsigned integer that specifies the size, in bytes, of the source bitmap bits.
|
||||
final int cbBitsSrc = (int)leis.readUInt();
|
||||
// A 32-bit unsigned integer that specifies how to interpret values in the color table
|
||||
// in the source bitmap header. This value MUST be in the DIBColors enumeration
|
||||
usageSrc = ColorUsage.valueOf((int)leis.readUInt());
|
||||
// A 32-bit unsigned integer that specifies the first scan line in the array.
|
||||
final int iStartScan = (int)leis.readUInt();
|
||||
// A 32-bit unsigned integer that specifies the number of scan lines.
|
||||
final int cScans = (int)leis.readUInt();
|
||||
size += 7*LittleEndianConsts.INT_SIZE;
|
||||
|
||||
size += readBitmap(leis, bitmap, startIdx, offBmiSrc, cbBmiSrc, offBitsSrc, cbBitsSrc);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return
|
||||
"{ bounds: " + boundsToString(bounds) +
|
||||
", dest: " + pointToString(dest) +
|
||||
", src: " + boundsToString(src) +
|
||||
", usageSrc: '" + usageSrc + "'" +
|
||||
", bitmap: " + bitmap +
|
||||
"}";
|
||||
}
|
||||
}
|
||||
|
||||
static long readBitmap(final LittleEndianInputStream leis, final HwmfBitmapDib bitmap,
|
||||
final int startIdx, final int offBmi, final int cbBmi, final int offBits, int cbBits)
|
||||
throws IOException {
|
||||
if (offBmi == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
final int offCurr = leis.getReadIndex()-(startIdx-HEADER_SIZE);
|
||||
final int undefinedSpace1 = offBmi-offCurr;
|
||||
if (undefinedSpace1 < 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
final int undefinedSpace2 = offBits-offCurr-cbBmi-undefinedSpace1;
|
||||
assert(undefinedSpace2 >= 0);
|
||||
|
||||
leis.skipFully(undefinedSpace1);
|
||||
|
||||
if (cbBmi == 0 || cbBits == 0) {
|
||||
return undefinedSpace1;
|
||||
}
|
||||
|
||||
final int dibSize = cbBmi+cbBits;
|
||||
if (undefinedSpace2 == 0) {
|
||||
return undefinedSpace1 + bitmap.init(leis, dibSize);
|
||||
}
|
||||
|
||||
final ByteArrayOutputStream bos = new ByteArrayOutputStream(cbBmi+cbBits);
|
||||
final long cbBmiSrcAct = IOUtils.copy(leis, bos, cbBmi);
|
||||
assert (cbBmiSrcAct == cbBmi);
|
||||
leis.skipFully(undefinedSpace2);
|
||||
final long cbBitsSrcAct = IOUtils.copy(leis, bos, cbBits);
|
||||
assert (cbBitsSrcAct == cbBits);
|
||||
|
||||
final LittleEndianInputStream leisDib = new LittleEndianInputStream(new ByteArrayInputStream(bos.toByteArray()));
|
||||
final int dibSizeAct = bitmap.init(leisDib, dibSize);
|
||||
assert (dibSizeAct <= dibSize);
|
||||
return undefinedSpace1 + cbBmi + undefinedSpace2 + cbBits;
|
||||
}
|
||||
|
||||
|
||||
static long readRgnData(final LittleEndianInputStream leis, final List<Rectangle2D> rgnRects) {
|
||||
// *** RegionDataHeader ***
|
||||
// A 32-bit unsigned integer that specifies the size of this object in bytes. This MUST be 0x00000020.
|
||||
long rgnHdrSize = leis.readUInt();
|
||||
assert(rgnHdrSize == 0x20);
|
||||
// A 32-bit unsigned integer that specifies the region type. This SHOULD be RDH_RECTANGLES (0x00000001)
|
||||
long rgnHdrType = leis.readUInt();
|
||||
assert(rgnHdrType == 1);
|
||||
// A 32-bit unsigned integer that specifies the number of rectangles in this region.
|
||||
long rgnCntRect = leis.readUInt();
|
||||
// A 32-bit unsigned integer that specifies the size of the buffer of rectangles in bytes.
|
||||
long rgnCntBytes = leis.readUInt();
|
||||
long size = 4*LittleEndianConsts.INT_SIZE;
|
||||
// A 128-bit WMF RectL object, which specifies the bounds of the region.
|
||||
Rectangle2D rgnBounds = new Rectangle2D.Double();
|
||||
size += readRectL(leis, rgnBounds);
|
||||
for (int i=0; i<rgnCntRect; i++) {
|
||||
Rectangle2D rgnRct = new Rectangle2D.Double();
|
||||
size += readRectL(leis, rgnRct);
|
||||
rgnRects.add(rgnRct);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
|
||||
static int readBounds2(LittleEndianInputStream leis, Rectangle2D bounds) {
|
||||
/**
|
||||
* The 32-bit signed integers that defines the corners of the bounding rectangle.
|
||||
*/
|
||||
int x = leis.readInt();
|
||||
int y = leis.readInt();
|
||||
int w = leis.readInt();
|
||||
int h = leis.readInt();
|
||||
|
||||
bounds.setRect(x, y, w, h);
|
||||
|
||||
return 4 * LittleEndianConsts.INT_SIZE;
|
||||
}
|
||||
|
||||
static int readXForm(LittleEndianInputStream leis, AffineTransform xform) {
|
||||
// mapping <java AffineTransform> = <xform>
|
||||
|
||||
// m00 (scaleX) = eM11 (Horizontal scaling component)
|
||||
double m00 = leis.readFloat();
|
||||
|
||||
// m01 (shearX) = eM12 (Horizontal proportionality constant)
|
||||
double m01 = leis.readFloat();
|
||||
|
||||
// m10 (shearY) = eM21 (Vertical proportionality constant)
|
||||
double m10 = leis.readFloat();
|
||||
|
||||
// m11 (scaleY) = eM22 (Vertical scaling component)
|
||||
double m11 = leis.readFloat();
|
||||
|
||||
// m02 (translateX) = eDx (The horizontal translation component, in logical units.)
|
||||
double m02 = leis.readFloat();
|
||||
|
||||
// m12 (translateY) = eDy (The vertical translation component, in logical units.)
|
||||
double m12 = leis.readFloat();
|
||||
|
||||
xform.setTransform(m00, m10, m01, m11, m02, m12);
|
||||
|
||||
return 6 * LittleEndian.INT_SIZE;
|
||||
}
|
||||
|
||||
protected static Shape getRgnShape(List<Rectangle2D> rgnRects) {
|
||||
if (rgnRects.size() == 1) {
|
||||
return rgnRects.get(0);
|
||||
}
|
||||
final Area frame = new Area();
|
||||
rgnRects.forEach((rct) -> frame.add(new Area(rct)));
|
||||
return frame;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,496 @@
|
|||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
==================================================================== */
|
||||
|
||||
package org.apache.poi.hemf.record.emf;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.poi.common.usermodel.fonts.FontCharset;
|
||||
import org.apache.poi.hwmf.record.HwmfFont;
|
||||
import org.apache.poi.util.LittleEndian;
|
||||
import org.apache.poi.util.LittleEndianConsts;
|
||||
import org.apache.poi.util.LittleEndianInputStream;
|
||||
|
||||
public class HemfFont extends HwmfFont {
|
||||
private static final int LOGFONT_SIZE = 92;
|
||||
private static final int LOGFONTPANOSE_SIZE = 320;
|
||||
|
||||
protected interface LogFontDetails {}
|
||||
|
||||
protected static class LogFontExDv implements LogFontDetails {
|
||||
protected int[] designVector;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{ designVectorLen: " + (designVector == null ? 0 : designVector.length) + " }";
|
||||
}
|
||||
}
|
||||
|
||||
protected static class LogFontPanose implements LogFontDetails {
|
||||
enum FamilyType {
|
||||
PAN_ANY,
|
||||
PAN_NO_FIT,
|
||||
PAN_FAMILY_TEXT_DISPLAY,
|
||||
PAN_FAMILY_SCRIPT,
|
||||
PAN_FAMILY_DECORATIVE,
|
||||
PAN_FAMILY_PICTORIAL
|
||||
}
|
||||
|
||||
enum SerifType {
|
||||
PAN_ANY,
|
||||
PAN_NO_FIT,
|
||||
PAN_SERIF_COVE,
|
||||
PAN_SERIF_OBTUSE_COVE,
|
||||
PAN_SERIF_SQUARE_COVE,
|
||||
PAN_SERIF_OBTUSE_SQUARE_COVE,
|
||||
PAN_SERIF_SQUARE,
|
||||
PAN_SERIF_THIN,
|
||||
PAN_SERIF_BONE,
|
||||
PAN_SERIF_EXAGGERATED,
|
||||
PAN_SERIF_TRIANGLE,
|
||||
PAN_SERIF_NORMAL_SANS,
|
||||
PAN_SERIF_OBTUSE_SANS,
|
||||
PAN_SERIF_PERP_SANS,
|
||||
PAN_SERIF_FLARED,
|
||||
PAN_SERIF_ROUNDED
|
||||
}
|
||||
|
||||
enum FontWeight {
|
||||
PAN_ANY,
|
||||
PAN_NO_FIT,
|
||||
PAN_WEIGHT_VERY_LIGHT,
|
||||
PAN_WEIGHT_LIGHT,
|
||||
PAN_WEIGHT_THIN,
|
||||
PAN_WEIGHT_BOOK,
|
||||
PAN_WEIGHT_MEDIUM,
|
||||
PAN_WEIGHT_DEMI,
|
||||
PAN_WEIGHT_BOLD,
|
||||
PAN_WEIGHT_HEAVY,
|
||||
PAN_WEIGHT_BLACK,
|
||||
PAN_WEIGHT_NORD
|
||||
}
|
||||
|
||||
enum Proportion {
|
||||
PAN_ANY,
|
||||
PAN_NO_FIT,
|
||||
PAN_PROP_OLD_STYLE,
|
||||
PAN_PROP_MODERN,
|
||||
PAN_PROP_EVEN_WIDTH,
|
||||
PAN_PROP_EXPANDED,
|
||||
PAN_PROP_CONDENSED,
|
||||
PAN_PROP_VERY_EXPANDED,
|
||||
PAN_PROP_VERY_CONDENSED,
|
||||
PAN_PROP_MONOSPACED
|
||||
}
|
||||
|
||||
enum Contrast {
|
||||
PAN_ANY,
|
||||
PAN_NO_FIT,
|
||||
PAN_CONTRAST_NONE,
|
||||
PAN_CONTRAST_VERY_LOW,
|
||||
PAN_CONTRAST_LOW,
|
||||
PAN_CONTRAST_MEDIUM_LOW,
|
||||
PAN_CONTRAST_MEDIUM,
|
||||
PAN_CONTRAST_MEDIUM_HIGH,
|
||||
PAN_CONTRAST_HIGH,
|
||||
PAN_CONTRAST_VERY_HIGH
|
||||
}
|
||||
|
||||
enum StrokeVariation {
|
||||
PAN_ANY,
|
||||
PAN_NO_FIT,
|
||||
PAN_STROKE_GRADUAL_DIAG,
|
||||
PAN_STROKE_GRADUAL_TRAN,
|
||||
PAN_STROKE_GRADUAL_VERT,
|
||||
PAN_STROKE_GRADUAL_HORZ,
|
||||
PAN_STROKE_RAPID_VERT,
|
||||
PAN_STROKE_RAPID_HORZ,
|
||||
PAN_STROKE_INSTANT_VERT
|
||||
}
|
||||
|
||||
enum ArmStyle {
|
||||
PAN_ANY,
|
||||
PAN_NO_FIT,
|
||||
PAN_STRAIGHT_ARMS_HORZ,
|
||||
PAN_STRAIGHT_ARMS_WEDGE,
|
||||
PAN_STRAIGHT_ARMS_VERT,
|
||||
PAN_STRAIGHT_ARMS_SINGLE_SERIF,
|
||||
PAN_STRAIGHT_ARMS_DOUBLE_SERIF,
|
||||
PAN_BENT_ARMS_HORZ,
|
||||
PAN_BENT_ARMS_WEDGE,
|
||||
PAN_BENT_ARMS_VERT,
|
||||
PAN_BENT_ARMS_SINGLE_SERIF,
|
||||
PAN_BENT_ARMS_DOUBLE_SERIF
|
||||
}
|
||||
|
||||
enum Letterform {
|
||||
PAN_ANY,
|
||||
PAN_NO_FIT,
|
||||
PAN_LETT_NORMAL_CONTACT,
|
||||
PAN_LETT_NORMAL_WEIGHTED,
|
||||
PAN_LETT_NORMAL_BOXED,
|
||||
PAN_LETT_NORMAL_FLATTENED,
|
||||
PAN_LETT_NORMAL_ROUNDED,
|
||||
PAN_LETT_NORMAL_OFF_CENTER,
|
||||
PAN_LETT_NORMAL_SQUARE,
|
||||
PAN_LETT_OBLIQUE_CONTACT,
|
||||
PAN_LETT_OBLIQUE_WEIGHTED,
|
||||
PAN_LETT_OBLIQUE_BOXED,
|
||||
PAN_LETT_OBLIQUE_FLATTENED,
|
||||
PAN_LETT_OBLIQUE_ROUNDED,
|
||||
PAN_LETT_OBLIQUE_OFF_CENTER,
|
||||
PAN_LETT_OBLIQUE_SQUARE
|
||||
}
|
||||
|
||||
enum MidLine {
|
||||
PAN_ANY,
|
||||
PAN_NO_FIT,
|
||||
PAN_MIDLINE_STANDARD_TRIMMED,
|
||||
PAN_MIDLINE_STANDARD_POINTED,
|
||||
PAN_MIDLINE_STANDARD_SERIFED,
|
||||
PAN_MIDLINE_HIGH_TRIMMED,
|
||||
PAN_MIDLINE_HIGH_POINTED,
|
||||
PAN_MIDLINE_HIGH_SERIFED,
|
||||
PAN_MIDLINE_CONSTANT_TRIMMED,
|
||||
PAN_MIDLINE_CONSTANT_POINTED,
|
||||
PAN_MIDLINE_CONSTANT_SERIFED,
|
||||
PAN_MIDLINE_LOW_TRIMMED,
|
||||
PAN_MIDLINE_LOW_POINTED,
|
||||
PAN_MIDLINE_LOW_SERIFED
|
||||
}
|
||||
|
||||
enum XHeight {
|
||||
PAN_ANY,
|
||||
PAN_NO_FIT,
|
||||
PAN_XHEIGHT_CONSTANT_SMALL,
|
||||
PAN_XHEIGHT_CONSTANT_STD,
|
||||
PAN_XHEIGHT_CONSTANT_LARGE,
|
||||
PAN_XHEIGHT_DUCKING_SMALL,
|
||||
PAN_XHEIGHT_DUCKING_STD,
|
||||
PAN_XHEIGHT_DUCKING_LARGE
|
||||
}
|
||||
|
||||
protected int styleSize;
|
||||
protected int vendorId;
|
||||
protected int culture;
|
||||
protected FamilyType familyType;
|
||||
protected SerifType serifStyle;
|
||||
protected FontWeight weight;
|
||||
protected Proportion proportion;
|
||||
protected Contrast contrast;
|
||||
protected StrokeVariation strokeVariation;
|
||||
protected ArmStyle armStyle;
|
||||
protected Letterform letterform;
|
||||
protected MidLine midLine;
|
||||
protected XHeight xHeight;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return
|
||||
"{ styleSize: " + styleSize +
|
||||
", vendorId: " + vendorId +
|
||||
", culture: " + culture +
|
||||
", familyType: '" + familyType + "'" +
|
||||
", serifStyle: '" + serifStyle + "'" +
|
||||
", weight: '" + weight + "'" +
|
||||
", proportion: '" + proportion + "'" +
|
||||
", contrast: '" + contrast + "'" +
|
||||
", strokeVariation: '" + strokeVariation + "'" +
|
||||
", armStyle: '" + armStyle + "'" +
|
||||
", letterform: '" + letterform + "'" +
|
||||
", midLine: '" + midLine + "'" +
|
||||
", xHeight: '" + xHeight + "'" +
|
||||
"}";
|
||||
}
|
||||
}
|
||||
|
||||
protected String fullname;
|
||||
protected String style;
|
||||
protected String script;
|
||||
|
||||
protected LogFontDetails details;
|
||||
|
||||
@Override
|
||||
public int init(LittleEndianInputStream leis, long recordSize) throws IOException {
|
||||
// A 32-bit signed integer that specifies the height, in logical units, of the font's
|
||||
// character cell or character. The character height value, also known as the em size, is the
|
||||
// character cell height value minus the internal leading value. The font mapper SHOULD
|
||||
// interpret the value specified in the Height field in the following manner.
|
||||
//
|
||||
// 0x00000000 < value:
|
||||
// The font mapper transforms this value into device units and matches it against
|
||||
// the cell height of the available fonts.
|
||||
//
|
||||
// 0x00000000
|
||||
// The font mapper uses a default height value when it searches for a match.
|
||||
//
|
||||
// value < 0x00000000:
|
||||
// The font mapper transforms this value into device units and matches its
|
||||
// absolute value against the character height of the available fonts.
|
||||
//
|
||||
// For all height comparisons, the font mapper SHOULD look for the largest font that does not
|
||||
// exceed the requested size.
|
||||
height = leis.readInt();
|
||||
|
||||
// A 32-bit signed integer that specifies the average width, in logical units, of
|
||||
// characters in the font. If the Width field value is zero, an appropriate value SHOULD be
|
||||
// calculated from other LogFont values to find a font that has the typographer's intended
|
||||
// aspect ratio.
|
||||
width = leis.readInt();
|
||||
|
||||
// A 32-bit signed integer that specifies the angle, in tenths of degrees,
|
||||
// between the escapement vector and the x-axis of the device. The escapement vector is
|
||||
// parallel to the baseline of a row of text.
|
||||
//
|
||||
// When the graphics mode is set to GM_ADVANCED, the escapement angle of the string can
|
||||
// be specified independently of the orientation angle of the string's characters.
|
||||
escapement = leis.readInt();
|
||||
|
||||
// A 32-bit signed integer that specifies the angle, in tenths of degrees,
|
||||
// between each character's baseline and the x-axis of the device.
|
||||
orientation = leis.readInt();
|
||||
|
||||
// A 32-bit signed integer that specifies the weight of the font in the range zero through 1000.
|
||||
// For example, 400 is normal and 700 is bold. If this value is zero, a default weight can be used.
|
||||
weight = leis.readInt();
|
||||
|
||||
// An 8-bit unsigned integer that specifies an italic font if set to 0x01;
|
||||
// otherwise, it MUST be set to 0x00.
|
||||
italic = (leis.readUByte() != 0x00);
|
||||
|
||||
// An 8-bit unsigned integer that specifies an underlined font if set to 0x01;
|
||||
// otherwise, it MUST be set to 0x00.
|
||||
underline = (leis.readUByte() != 0x00);
|
||||
|
||||
// An 8-bit unsigned integer that specifies a strikeout font if set to 0x01;
|
||||
// otherwise, it MUST be set to 0x00.
|
||||
strikeOut = (leis.readUByte() != 0x00);
|
||||
|
||||
// An 8-bit unsigned integer that specifies the set of character glyphs.
|
||||
// It MUST be a value in the WMF CharacterSet enumeration.
|
||||
// If the character set is unknown, metafile processing SHOULD NOT attempt
|
||||
// to translate or interpret strings that are rendered with that font.
|
||||
// If a typeface name is specified in the Facename field, the CharSet field
|
||||
// value MUST match the character set of that typeface.
|
||||
charSet = FontCharset.valueOf(leis.readUByte());
|
||||
|
||||
// An 8-bit unsigned integer that specifies the output precision.
|
||||
// The output precision defines how closely the font is required to match the requested height, width,
|
||||
// character orientation, escapement, pitch, and font type.
|
||||
// It MUST be a value from the WMF OutPrecision enumeration.
|
||||
// Applications can use the output precision to control how the font mapper chooses a font when the
|
||||
// operating system contains more than one font with a specified name. For example, if an operating
|
||||
// system contains a font named Symbol in rasterized and TrueType forms, an output precision value
|
||||
// of OUT_TT_PRECIS forces the font mapper to choose the TrueType version.
|
||||
// A value of OUT_TT_ONLY_PRECIS forces the font mapper to choose a TrueType font, even if it is
|
||||
// necessary to substitute a TrueType font with another name.
|
||||
outPrecision = WmfOutPrecision.valueOf(leis.readUByte());
|
||||
|
||||
// An 8-bit unsigned integer that specifies the clipping precision.
|
||||
// The clipping precision defines how to clip characters that are partially outside the clipping region.
|
||||
// It can be one or more of the WMF ClipPrecision Flags
|
||||
clipPrecision.init(leis);
|
||||
|
||||
// An 8-bit unsigned integer that specifies the output quality. The output quality defines how closely
|
||||
// to attempt to match the logical-font attributes to those of an actual physical font.
|
||||
// It MUST be one of the values in the WMF FontQuality enumeration
|
||||
quality = WmfFontQuality.valueOf(leis.readUByte());
|
||||
|
||||
// A WMF PitchAndFamily object that specifies the pitch and family of the font.
|
||||
// Font families describe the look of a font in a general way.
|
||||
// They are intended for specifying a font when the specified typeface is not available.
|
||||
pitchAndFamily = leis.readUByte();
|
||||
|
||||
int size = 5* LittleEndianConsts.INT_SIZE+8*LittleEndianConsts.BYTE_SIZE;
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
// A string of no more than 32 Unicode characters that specifies the typeface name of the font.
|
||||
// If the length of this string is less than 32 characters, a terminating NULL MUST be present,
|
||||
// after which the remainder of this field MUST be ignored.
|
||||
int readBytes = readString(leis, sb, 32);
|
||||
if (readBytes == -1) {
|
||||
throw new IOException("Font facename can't be determined.");
|
||||
}
|
||||
facename = sb.toString();
|
||||
size += readBytes;
|
||||
|
||||
if (recordSize <= LOGFONT_SIZE) {
|
||||
return size;
|
||||
}
|
||||
|
||||
// A string of 64 Unicode characters that contains the font's full name.
|
||||
// Ifthe length of this string is less than 64 characters, a terminating
|
||||
// NULL MUST be present, after which the remainder of this field MUST be ignored.
|
||||
readBytes = readString(leis, sb, 64);
|
||||
if (readBytes == -1) {
|
||||
throw new IOException("Font fullname can't be determined.");
|
||||
}
|
||||
fullname = sb.toString();
|
||||
size += readBytes;
|
||||
|
||||
// A string of 32 Unicode characters that defines the font's style. If the length of
|
||||
// this string is less than 32 characters, a terminating NULL MUST be present,
|
||||
// after which the remainder of this field MUST be ignored.
|
||||
readBytes = readString(leis, sb, 32);
|
||||
if (readBytes == -1) {
|
||||
throw new IOException("Font style can't be determined.");
|
||||
}
|
||||
style = sb.toString();
|
||||
size += readBytes;
|
||||
|
||||
if (recordSize == LOGFONTPANOSE_SIZE) {
|
||||
// LogFontPanose Object
|
||||
|
||||
LogFontPanose logPan = new LogFontPanose();
|
||||
details = logPan;
|
||||
|
||||
int version = leis.readInt();
|
||||
|
||||
// A 32-bit unsigned integer that specifies the point size at which font
|
||||
//hinting is performed. If set to zero, font hinting is performed at the point size corresponding
|
||||
//to the Height field in the LogFont object in the LogFont field.
|
||||
logPan.styleSize = (int)leis.readUInt();
|
||||
|
||||
int match = leis.readInt();
|
||||
|
||||
int reserved = leis.readInt();
|
||||
|
||||
logPan.vendorId = leis.readInt();
|
||||
|
||||
logPan.culture = leis.readInt();
|
||||
|
||||
// An 8-bit unsigned integer that specifies the family type.
|
||||
// The value MUST be in the FamilyType enumeration table.
|
||||
logPan.familyType = LogFontPanose.FamilyType.values()[leis.readUByte()];
|
||||
|
||||
// An 8-bit unsigned integer that specifies the serif style.
|
||||
// The value MUST be in the SerifType enumeration table.
|
||||
logPan.serifStyle = LogFontPanose.SerifType.values()[leis.readUByte()];
|
||||
|
||||
// An 8-bit unsigned integer that specifies the weight of the font.
|
||||
// The value MUST be in the Weight enumeration table.
|
||||
logPan.weight = LogFontPanose.FontWeight.values()[leis.readUByte()];
|
||||
|
||||
// An 8-bit unsigned integer that specifies the proportion of the font.
|
||||
// The value MUST be in the Proportion enumeration table.
|
||||
logPan.proportion = LogFontPanose.Proportion.values()[leis.readUByte()];
|
||||
|
||||
// An 8-bit unsigned integer that specifies the proportion of the font.
|
||||
// The value MUST be in the Proportion enumeration table.
|
||||
logPan.contrast = LogFontPanose.Contrast.values()[leis.readUByte()];
|
||||
|
||||
// An 8-bit unsigned integer that specifies the stroke variation for the font.
|
||||
// The value MUST be in the StrokeVariation enumeration table.
|
||||
logPan.strokeVariation = LogFontPanose.StrokeVariation.values()[leis.readUByte()];
|
||||
|
||||
// An 8-bit unsigned integer that specifies the arm style of the font.
|
||||
// The value MUST be in the ArmStyle enumeration table.
|
||||
logPan.armStyle = LogFontPanose.ArmStyle.values()[leis.readUByte()];
|
||||
|
||||
// An 8-bit unsigned integer that specifies the letterform of the font.
|
||||
// The value MUST be in the Letterform enumeration table.
|
||||
logPan.letterform = LogFontPanose.Letterform.values()[leis.readUByte()];
|
||||
|
||||
// An 8-bit unsigned integer that specifies the midline of the font.
|
||||
// The value MUST be in the MidLine enumeration table.
|
||||
logPan.midLine = LogFontPanose.MidLine.values()[leis.readUByte()];
|
||||
|
||||
// An 8-bit unsigned integer that specifies the x height of the font.
|
||||
// The value MUST be in the XHeight enumeration table.
|
||||
logPan.xHeight = LogFontPanose.XHeight.values()[leis.readUByte()];
|
||||
|
||||
// skip 2 byte to ensure 32-bit alignment of this structure.
|
||||
leis.skip(2);
|
||||
|
||||
size += 6*LittleEndianConsts.INT_SIZE+10* LittleEndianConsts.BYTE_SIZE+2;
|
||||
} else {
|
||||
// LogFontExDv Object
|
||||
|
||||
LogFontExDv logEx = new LogFontExDv();
|
||||
details = logEx;
|
||||
|
||||
// A string of 32 Unicode characters that defines the character set of the font.
|
||||
// If the length of this string is less than 32 characters, a terminating NULL MUST be present,
|
||||
// after which the remainder of this field MUST be ignored.
|
||||
readBytes = readString(leis, sb, 32);
|
||||
if (readBytes == -1) {
|
||||
throw new IOException("Font script can't be determined.");
|
||||
}
|
||||
script = sb.toString();
|
||||
size += readBytes;
|
||||
|
||||
// Design Vector
|
||||
|
||||
// A 32-bit unsigned integer that MUST be set to the value 0x08007664.
|
||||
int signature = leis.readInt();
|
||||
// some non-conformant applications don't write the magic code in
|
||||
// assert (signature == 0x08007664);
|
||||
|
||||
// A 32-bit unsigned integer that specifies the number of elements in the
|
||||
// Values array. It MUST be in the range 0 to 16, inclusive.
|
||||
int numAxes = leis.readInt();
|
||||
assert (0 <= numAxes && numAxes <= 16);
|
||||
|
||||
// An optional array of 32-bit signed integers that specify the values of the font axes of a
|
||||
// multiple master, OpenType font. The maximum number of values in the array is 16.
|
||||
if (numAxes > 0) {
|
||||
logEx.designVector = new int[numAxes];
|
||||
for (int i=0; i<numAxes; i++) {
|
||||
logEx.designVector[i] = leis.readInt();
|
||||
}
|
||||
}
|
||||
size += (2+numAxes)*LittleEndianConsts.INT_SIZE;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return
|
||||
"{ fullname: '" + (fullname == null ? "" : fullname) + "'" +
|
||||
", style: '" + (style == null ? "" : style) + "'" +
|
||||
", script: '" + (script == null ? "" : script) + "'" +
|
||||
", details: " + details +
|
||||
"," + super.toString().substring(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int readString(LittleEndianInputStream leis, StringBuilder sb, int limit) throws IOException {
|
||||
sb.setLength(0);
|
||||
byte buf[] = new byte[limit*2];
|
||||
leis.readFully(buf);
|
||||
|
||||
int b1, b2, readBytes = 0;
|
||||
do {
|
||||
if (readBytes == limit*2) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
b1 = buf[readBytes++];
|
||||
b2 = buf[readBytes++];
|
||||
} while ((b1 != 0 || b2 != 0) && b1 != -1 && b2 != -1 && readBytes <= limit*2);
|
||||
|
||||
sb.append(new String(buf, 0, readBytes-2, StandardCharsets.UTF_16LE));
|
||||
|
||||
return limit*2;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,207 @@
|
|||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
==================================================================== */
|
||||
|
||||
package org.apache.poi.hemf.record.emf;
|
||||
|
||||
import static org.apache.poi.hemf.record.emf.HemfDraw.readDimensionInt;
|
||||
import static org.apache.poi.hemf.record.emf.HemfDraw.readRectL;
|
||||
import static org.apache.poi.hemf.record.emf.HemfRecordIterator.HEADER_SIZE;
|
||||
import static org.apache.poi.hwmf.record.HwmfDraw.boundsToString;
|
||||
import static org.apache.poi.hwmf.record.HwmfDraw.dimToString;
|
||||
|
||||
import java.awt.geom.Dimension2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.apache.poi.util.Dimension2DDouble;
|
||||
import org.apache.poi.util.Internal;
|
||||
import org.apache.poi.util.LittleEndianConsts;
|
||||
import org.apache.poi.util.LittleEndianInputStream;
|
||||
|
||||
/**
|
||||
* Extracts the full header from EMF files.
|
||||
* @see org.apache.poi.sl.image.ImageHeaderEMF
|
||||
*/
|
||||
@Internal
|
||||
public class HemfHeader implements HemfRecord {
|
||||
|
||||
private static final int MAX_RECORD_LENGTH = 1_000_000;
|
||||
|
||||
|
||||
private final Rectangle2D boundsRectangle = new Rectangle2D.Double();
|
||||
private final Rectangle2D frameRectangle = new Rectangle2D.Double();
|
||||
private long bytes;
|
||||
private long records;
|
||||
private int handles;
|
||||
private String description;
|
||||
private long nPalEntries;
|
||||
private boolean hasExtension1;
|
||||
private long cbPixelFormat;
|
||||
private long offPixelFormat;
|
||||
private long bOpenGL;
|
||||
private boolean hasExtension2;
|
||||
private final Dimension2D deviceDimension = new Dimension2DDouble();
|
||||
private final Dimension2D milliDimension = new Dimension2DDouble();
|
||||
private final Dimension2D microDimension = new Dimension2DDouble();
|
||||
|
||||
|
||||
public Rectangle2D getBoundsRectangle() {
|
||||
return boundsRectangle;
|
||||
}
|
||||
|
||||
public Rectangle2D getFrameRectangle() {
|
||||
return frameRectangle;
|
||||
}
|
||||
|
||||
public long getBytes() {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public long getRecords() {
|
||||
return records;
|
||||
}
|
||||
|
||||
public int getHandles() {
|
||||
return handles;
|
||||
}
|
||||
|
||||
public String getDescription() { return description; }
|
||||
|
||||
public long getnPalEntries() {
|
||||
return nPalEntries;
|
||||
}
|
||||
|
||||
public boolean isHasExtension1() {
|
||||
return hasExtension1;
|
||||
}
|
||||
|
||||
public long getCbPixelFormat() {
|
||||
return cbPixelFormat;
|
||||
}
|
||||
|
||||
public long getOffPixelFormat() {
|
||||
return offPixelFormat;
|
||||
}
|
||||
|
||||
public long getbOpenGL() {
|
||||
return bOpenGL;
|
||||
}
|
||||
|
||||
public boolean isHasExtension2() {
|
||||
return hasExtension2;
|
||||
}
|
||||
|
||||
public Dimension2D getDeviceDimension() {
|
||||
return deviceDimension;
|
||||
}
|
||||
|
||||
public Dimension2D getMilliDimension() {
|
||||
return milliDimension;
|
||||
}
|
||||
|
||||
public Dimension2D getMicroDimension() {
|
||||
return microDimension;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "HemfHeader{" +
|
||||
"boundsRectangle: " + boundsToString(boundsRectangle) +
|
||||
", frameRectangle: " + boundsToString(frameRectangle) +
|
||||
", bytes: " + bytes +
|
||||
", records: " + records +
|
||||
", handles: " + handles +
|
||||
", description: '" + (description == null ? "" : description) + "'" +
|
||||
", nPalEntries: " + nPalEntries +
|
||||
", hasExtension1: " + hasExtension1 +
|
||||
", cbPixelFormat: " + cbPixelFormat +
|
||||
", offPixelFormat: " + offPixelFormat +
|
||||
", bOpenGL: " + bOpenGL +
|
||||
", hasExtension2: " + hasExtension2 +
|
||||
", deviceDimension: " + dimToString(deviceDimension) +
|
||||
", microDimension: " + dimToString(microDimension) +
|
||||
", milliDimension: " + dimToString(milliDimension) +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public HemfRecordType getEmfRecordType() {
|
||||
return HemfRecordType.header;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
|
||||
if (recordId != HemfRecordType.header.id) {
|
||||
throw new IOException("Not a valid EMF header. Record type:"+recordId);
|
||||
}
|
||||
|
||||
int startIdx = leis.getReadIndex();
|
||||
|
||||
//bounds
|
||||
long size = readRectL(leis, boundsRectangle);
|
||||
size += readRectL(leis, frameRectangle);
|
||||
|
||||
int recordSignature = leis.readInt();
|
||||
if (recordSignature != 0x464D4520) {
|
||||
throw new IOException("bad record signature: " + recordSignature);
|
||||
}
|
||||
|
||||
long version = leis.readInt();
|
||||
//According to the spec, MSOffice doesn't pay attention to this value.
|
||||
//It _should_ be 0x00010000
|
||||
bytes = leis.readUInt();
|
||||
records = leis.readUInt();
|
||||
handles = leis.readUShort();
|
||||
//reserved
|
||||
leis.skipFully(LittleEndianConsts.SHORT_SIZE);
|
||||
|
||||
int nDescription = (int)leis.readUInt();
|
||||
int offDescription = (int)leis.readUInt();
|
||||
nPalEntries = leis.readUInt();
|
||||
|
||||
size += 8*LittleEndianConsts.INT_SIZE;
|
||||
|
||||
size += readDimensionInt(leis, deviceDimension);
|
||||
size += readDimensionInt(leis, milliDimension);
|
||||
|
||||
if (nDescription > 0 && offDescription > 0) {
|
||||
int skip = (int)(offDescription - (size + HEADER_SIZE));
|
||||
leis.mark(skip+nDescription*2);
|
||||
leis.skipFully(skip);
|
||||
byte[] buf = new byte[(nDescription-1)*2];
|
||||
leis.readFully(buf);
|
||||
description = new String(buf, StandardCharsets.UTF_16LE).replace((char)0, ' ').trim();
|
||||
leis.reset();
|
||||
}
|
||||
|
||||
if (size+12 <= recordSize) {
|
||||
hasExtension1 = true;
|
||||
cbPixelFormat = leis.readUInt();
|
||||
offPixelFormat = leis.readUInt();
|
||||
bOpenGL = leis.readUInt();
|
||||
size += 3*LittleEndianConsts.INT_SIZE;
|
||||
}
|
||||
|
||||
if (size+8 <= recordSize) {
|
||||
hasExtension2 = true;
|
||||
size += readDimensionInt(leis, microDimension);
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,828 @@
|
|||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
==================================================================== */
|
||||
|
||||
package org.apache.poi.hemf.record.emf;
|
||||
|
||||
import static org.apache.poi.hemf.record.emf.HemfDraw.readPointL;
|
||||
import static org.apache.poi.hemf.record.emf.HemfFill.readBitmap;
|
||||
import static org.apache.poi.hemf.record.emf.HemfFill.readXForm;
|
||||
import static org.apache.poi.hemf.record.emf.HemfRecordIterator.HEADER_SIZE;
|
||||
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.NoninvertibleTransformException;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.apache.poi.hemf.draw.HemfDrawProperties;
|
||||
import org.apache.poi.hemf.draw.HemfGraphics;
|
||||
import org.apache.poi.hwmf.draw.HwmfDrawProperties;
|
||||
import org.apache.poi.hwmf.draw.HwmfGraphics;
|
||||
import org.apache.poi.hwmf.record.HwmfBinaryRasterOp;
|
||||
import org.apache.poi.hwmf.record.HwmfBitmapDib;
|
||||
import org.apache.poi.hwmf.record.HwmfBrushStyle;
|
||||
import org.apache.poi.hwmf.record.HwmfColorRef;
|
||||
import org.apache.poi.hwmf.record.HwmfFill;
|
||||
import org.apache.poi.hwmf.record.HwmfHatchStyle;
|
||||
import org.apache.poi.hwmf.record.HwmfMapMode;
|
||||
import org.apache.poi.hwmf.record.HwmfMisc;
|
||||
import org.apache.poi.hwmf.record.HwmfMisc.WmfSetBkMode;
|
||||
import org.apache.poi.hwmf.record.HwmfObjectTableEntry;
|
||||
import org.apache.poi.hwmf.record.HwmfPalette.PaletteEntry;
|
||||
import org.apache.poi.hwmf.record.HwmfPenStyle;
|
||||
import org.apache.poi.hwmf.record.HwmfPenStyle.HwmfLineDash;
|
||||
import org.apache.poi.util.LittleEndianConsts;
|
||||
import org.apache.poi.util.LittleEndianInputStream;
|
||||
|
||||
public class HemfMisc {
|
||||
|
||||
public enum HemfModifyWorldTransformMode {
|
||||
/**
|
||||
* Reset the current transform using the identity matrix.
|
||||
* In this mode, the specified transform data is ignored.
|
||||
*/
|
||||
MWT_IDENTITY(1),
|
||||
/**
|
||||
* Multiply the current transform. In this mode, the specified transform data is the left multiplicand,
|
||||
* and the transform that is currently defined in the playback device context is the right multiplicand.
|
||||
*/
|
||||
MWT_LEFTMULTIPLY(2),
|
||||
/**
|
||||
* Multiply the current transform. In this mode, the specified transform data is the right multiplicand,
|
||||
* and the transform that is currently defined in the playback device context is the left multiplicand.
|
||||
*/
|
||||
MWT_RIGHTMULTIPLY(3),
|
||||
/**
|
||||
* Perform the function of an EMR_SETWORLDTRANSFORM record
|
||||
*/
|
||||
MWT_SET(4)
|
||||
;
|
||||
|
||||
public final int id;
|
||||
|
||||
HemfModifyWorldTransformMode(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public static HemfModifyWorldTransformMode valueOf(int id) {
|
||||
for (HemfModifyWorldTransformMode wrt : values()) {
|
||||
if (wrt.id == id) return wrt;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class EmfEof implements HemfRecord {
|
||||
protected final List<PaletteEntry> palette = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public HemfRecordType getEmfRecordType() {
|
||||
return HemfRecordType.eof;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
|
||||
final int startIdx = leis.getReadIndex();
|
||||
|
||||
// A 32-bit unsigned integer that specifies the number of palette entries.
|
||||
final int nPalEntries = (int) leis.readUInt();
|
||||
// A 32-bit unsigned integer that specifies the offset to the palette entries from the start of this record.
|
||||
final int offPalEntries = (int) leis.readUInt();
|
||||
|
||||
int size = 2 * LittleEndianConsts.INT_SIZE;
|
||||
|
||||
if (nPalEntries > 0 && offPalEntries > 0) {
|
||||
int undefinedSpace1 = (int) (offPalEntries - (size + HEADER_SIZE));
|
||||
assert (undefinedSpace1 >= 0);
|
||||
leis.skipFully(undefinedSpace1);
|
||||
size += undefinedSpace1;
|
||||
|
||||
for (int i = 0; i < nPalEntries; i++) {
|
||||
PaletteEntry pe = new PaletteEntry();
|
||||
size += pe.init(leis);
|
||||
}
|
||||
|
||||
int undefinedSpace2 = (int) (recordSize - size - LittleEndianConsts.INT_SIZE);
|
||||
assert (undefinedSpace2 >= 0);
|
||||
leis.skipFully(undefinedSpace2);
|
||||
size += undefinedSpace2;
|
||||
}
|
||||
|
||||
// A 32-bit unsigned integer that MUST be the same as Size and MUST be the
|
||||
// last field of the record and hence the metafile.
|
||||
// LogPaletteEntry objects, if they exist, MUST precede this field.
|
||||
long sizeLast = leis.readUInt();
|
||||
size += LittleEndianConsts.INT_SIZE;
|
||||
// some files store the whole file size in sizeLast, other just the last record size
|
||||
// assert (sizeLast == size+HEADER_SIZE);
|
||||
assert (recordSize == size);
|
||||
|
||||
return size;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The EMF_SAVEDC record saves the playback device context for later retrieval.
|
||||
*/
|
||||
public static class EmfSaveDc extends HwmfMisc.WmfSaveDc implements HemfRecord {
|
||||
@Override
|
||||
public HemfRecordType getEmfRecordType() {
|
||||
return HemfRecordType.saveDc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The EMF_RESTOREDC record restores the playback device context from a previously saved device
|
||||
* context.
|
||||
*/
|
||||
public static class EmfRestoreDc extends HwmfMisc.WmfRestoreDc implements HemfRecord {
|
||||
|
||||
@Override
|
||||
public HemfRecordType getEmfRecordType() {
|
||||
return HemfRecordType.restoreDc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
|
||||
// A 32-bit signed integer that specifies the saved state to restore relative to
|
||||
// the current state. This value MUST be negative; –1 represents the state that was most
|
||||
// recently saved on the stack, –2 the one before that, etc.
|
||||
nSavedDC = leis.readInt();
|
||||
return LittleEndianConsts.INT_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The META_SETBKCOLOR record sets the background color in the playback device context to a
|
||||
* specified color, or to the nearest physical color if the device cannot represent the specified color.
|
||||
*/
|
||||
public static class EmfSetBkColor extends HwmfMisc.WmfSetBkColor implements HemfRecord {
|
||||
|
||||
@Override
|
||||
public HemfRecordType getEmfRecordType() {
|
||||
return HemfRecordType.setBkColor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
|
||||
return colorRef.init(leis);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The EMR_SETBKMODE record specifies the background mix mode of the playback device context.
|
||||
* The background mix mode is used with text, hatched brushes, and pen styles that are not solid
|
||||
* lines.
|
||||
*/
|
||||
public static class EmfSetBkMode extends WmfSetBkMode implements HemfRecord {
|
||||
public HemfRecordType getEmfRecordType() {
|
||||
return HemfRecordType.setBkMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
|
||||
/*
|
||||
* A 32-bit unsigned integer that specifies the background mode
|
||||
* and MUST be in the BackgroundMode (section 2.1.4) enumeration
|
||||
*/
|
||||
bkMode = HwmfBkMode.valueOf((int) leis.readUInt());
|
||||
return LittleEndianConsts.INT_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The EMR_SETMAPPERFLAGS record specifies parameters of the process of matching logical fonts to
|
||||
* physical fonts, which is performed by the font mapper.
|
||||
*/
|
||||
public static class EmfSetMapperFlags extends HwmfMisc.WmfSetMapperFlags implements HemfRecord {
|
||||
public HemfRecordType getEmfRecordType() {
|
||||
return HemfRecordType.setMapperFlags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
|
||||
return super.init(leis, recordSize, (int) recordId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The EMR_SETMAPMODE record specifies the mapping mode of the playback device context. The
|
||||
* mapping mode specifies the unit of measure used to transform page space units into device space
|
||||
* units, and also specifies the orientation of the device's x-axis and y-axis.
|
||||
*/
|
||||
public static class EmfSetMapMode extends HwmfMisc.WmfSetMapMode implements HemfRecord {
|
||||
public HemfRecordType getEmfRecordType() {
|
||||
return HemfRecordType.setMapMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
|
||||
// A 32-bit unsigned integer whose definition MUST be in the MapMode enumeration
|
||||
mapMode = HwmfMapMode.valueOf((int) leis.readUInt());
|
||||
return LittleEndianConsts.INT_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The EMR_SETROP2 record defines a binary raster operation mode.
|
||||
*/
|
||||
public static class EmfSetRop2 extends HwmfMisc.WmfSetRop2 implements HemfRecord {
|
||||
@Override
|
||||
public HemfRecordType getEmfRecordType() {
|
||||
return HemfRecordType.setRop2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
|
||||
// A 32-bit unsigned integer that specifies the raster operation mode and
|
||||
// MUST be in the WMF Binary Raster Op enumeration
|
||||
drawMode = HwmfBinaryRasterOp.valueOf((int) leis.readUInt());
|
||||
return LittleEndianConsts.INT_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The EMR_SETSTRETCHBLTMODE record specifies bitmap stretch mode.
|
||||
*/
|
||||
public static class EmfSetStretchBltMode extends HwmfMisc.WmfSetStretchBltMode implements HemfRecord {
|
||||
|
||||
@Override
|
||||
public HemfRecordType getEmfRecordType() {
|
||||
return HemfRecordType.setStretchBltMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
|
||||
// A 32-bit unsigned integer that specifies the stretch mode and MAY be
|
||||
// in the StretchMode enumeration.
|
||||
stretchBltMode = StretchBltMode.valueOf((int) leis.readUInt());
|
||||
return LittleEndianConsts.INT_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The EMR_CREATEBRUSHINDIRECT record defines a logical brush for graphics operations.
|
||||
*/
|
||||
public static class EmfCreateBrushIndirect extends HwmfMisc.WmfCreateBrushIndirect implements HemfRecord {
|
||||
/**
|
||||
* A 32-bit unsigned integer that specifies the index of the logical brush object in the
|
||||
* EMF Object Table. This index MUST be saved so that this object can be reused or modified.
|
||||
*/
|
||||
private int brushIdx;
|
||||
|
||||
@Override
|
||||
public HemfRecordType getEmfRecordType() {
|
||||
return HemfRecordType.createBrushIndirect;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
|
||||
brushIdx = (int) leis.readUInt();
|
||||
|
||||
brushStyle = HwmfBrushStyle.valueOf((int) leis.readUInt());
|
||||
colorRef = new HwmfColorRef();
|
||||
int size = colorRef.init(leis);
|
||||
brushHatch = HwmfHatchStyle.valueOf((int) leis.readUInt());
|
||||
return size + 3 * LittleEndianConsts.INT_SIZE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(HemfGraphics ctx) {
|
||||
ctx.addObjectTableEntry(this, brushIdx);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return
|
||||
"{ brushIndex: "+brushIdx+
|
||||
", brushStyle: '"+brushStyle+"'"+
|
||||
", colorRef: "+colorRef+
|
||||
", brushHatch: '"+brushHatch+"' }";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The EMR_CREATEDIBPATTERNBRUSHPT record defines a pattern brush for graphics operations.
|
||||
* The pattern is specified by a DIB.
|
||||
*/
|
||||
public static class EmfCreateDibPatternBrushPt extends HwmfMisc.WmfDibCreatePatternBrush implements HemfRecord {
|
||||
protected int brushIdx;
|
||||
|
||||
@Override
|
||||
public HemfRecordType getEmfRecordType() {
|
||||
return HemfRecordType.createDibPatternBrushPt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
|
||||
final int startIdx = leis.getReadIndex();
|
||||
|
||||
style = HwmfBrushStyle.BS_DIBPATTERNPT;
|
||||
|
||||
// A 32-bit unsigned integer that specifies the index of the pattern brush
|
||||
// object in the EMF Object Table
|
||||
brushIdx = (int)leis.readUInt();
|
||||
|
||||
// A 32-bit unsigned integer that specifies how to interpret values in the color
|
||||
// table in the DIB header. This value MUST be in the DIBColors enumeration
|
||||
colorUsage = HwmfFill.ColorUsage.valueOf((int)leis.readUInt());
|
||||
|
||||
// A 32-bit unsigned integer that specifies the offset from the start of this
|
||||
// record to the DIB header.
|
||||
final int offBmi = leis.readInt();
|
||||
|
||||
// A 32-bit unsigned integer that specifies the size of the DIB header.
|
||||
final int cbBmi = leis.readInt();
|
||||
|
||||
// A 32-bit unsigned integer that specifies the offset from the start of this record to the DIB bits.
|
||||
final int offBits = leis.readInt();
|
||||
|
||||
// A 32-bit unsigned integer that specifies the size of the DIB bits.
|
||||
final int cbBits = leis.readInt();
|
||||
|
||||
int size = 6*LittleEndianConsts.INT_SIZE;
|
||||
|
||||
patternDib = new HwmfBitmapDib();
|
||||
size += readBitmap(leis, patternDib, startIdx, offBmi, cbBmi, offBits, cbBits);
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(HemfGraphics ctx) {
|
||||
ctx.addObjectTableEntry(this, brushIdx);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The EMR_DELETEOBJECT record deletes a graphics object, which is specified by its index
|
||||
* in the EMF Object Table
|
||||
*/
|
||||
public static class EmfDeleteObject extends HwmfMisc.WmfDeleteObject implements HemfRecord {
|
||||
|
||||
@Override
|
||||
public HemfRecordType getEmfRecordType() {
|
||||
return HemfRecordType.deleteobject;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
|
||||
objectIndex = (int) leis.readUInt();
|
||||
return LittleEndianConsts.INT_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The EMR_CREATEPEN record defines a logical pen for graphics operations.
|
||||
*/
|
||||
public static class EmfCreatePen extends HwmfMisc.WmfCreatePenIndirect implements HemfRecord {
|
||||
/**
|
||||
* A 32-bit unsigned integer that specifies the index of the logical palette object
|
||||
* in the EMF Object Table. This index MUST be saved so that this object can be
|
||||
* reused or modified.
|
||||
*/
|
||||
protected int penIndex;
|
||||
|
||||
@Override
|
||||
public HemfRecordType getEmfRecordType() {
|
||||
return HemfRecordType.createPen;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
|
||||
penIndex = (int) leis.readUInt();
|
||||
|
||||
// A 32-bit unsigned integer that specifies the PenStyle.
|
||||
// The value MUST be defined from the PenStyle enumeration table
|
||||
penStyle = HwmfPenStyle.valueOf((int) leis.readUInt());
|
||||
|
||||
int widthX = leis.readInt();
|
||||
int widthY = leis.readInt();
|
||||
dimension.setSize(widthX, widthY);
|
||||
|
||||
int size = colorRef.init(leis);
|
||||
|
||||
return size + 4 * LittleEndianConsts.INT_SIZE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(HemfGraphics ctx) {
|
||||
ctx.addObjectTableEntry(this, penIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString().replaceFirst("\\{", "{ penIndex: "+penIndex+", ");
|
||||
}
|
||||
}
|
||||
|
||||
public static class EmfExtCreatePen extends EmfCreatePen {
|
||||
protected HwmfBrushStyle brushStyle;
|
||||
protected HwmfHatchStyle hatchStyle;
|
||||
|
||||
protected final HwmfBitmapDib bitmap = new HwmfBitmapDib();
|
||||
|
||||
|
||||
@Override
|
||||
public HemfRecordType getEmfRecordType() {
|
||||
return HemfRecordType.extCreatePen;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
|
||||
final int startIdx = leis.getReadIndex();
|
||||
|
||||
penIndex = (int) leis.readUInt();
|
||||
|
||||
// A 32-bit unsigned integer that specifies the offset from the start of this
|
||||
// record to the DIB header, if the record contains a DIB.
|
||||
int offBmi = (int) leis.readUInt();
|
||||
|
||||
// A 32-bit unsigned integer that specifies the size of the DIB header, if the
|
||||
// record contains a DIB.
|
||||
int cbBmi = (int) leis.readUInt();
|
||||
|
||||
// A 32-bit unsigned integer that specifies the offset from the start of this
|
||||
// record to the DIB bits, if the record contains a DIB.
|
||||
int offBits = (int) leis.readUInt();
|
||||
|
||||
// A 32-bit unsigned integer that specifies the size of the DIB bits, if the record
|
||||
// contains a DIB.
|
||||
int cbBits = (int) leis.readUInt();
|
||||
|
||||
// A 32-bit unsigned integer that specifies the PenStyle.
|
||||
// The value MUST be defined from the PenStyle enumeration table
|
||||
final HemfPenStyle emfPS = HemfPenStyle.valueOf((int) leis.readUInt());
|
||||
penStyle = emfPS;
|
||||
|
||||
// A 32-bit unsigned integer that specifies the width of the line drawn by the pen.
|
||||
// If the pen type in the PenStyle field is PS_GEOMETRIC, this value is the width in logical
|
||||
// units; otherwise, the width is specified in device units. If the pen type in the PenStyle field is
|
||||
// PS_COSMETIC, this value MUST be 0x00000001.
|
||||
long width = leis.readUInt();
|
||||
dimension.setSize(width, 0);
|
||||
int size = 7 * LittleEndianConsts.INT_SIZE;
|
||||
|
||||
// A 32-bit unsigned integer that specifies a brush style for the pen from the WMF BrushStyle enumeration
|
||||
//
|
||||
// If the pen type in the PenStyle field is PS_GEOMETRIC, this value MUST be either BS_SOLID or BS_HATCHED.
|
||||
// The value of this field can be BS_NULL, but only if the line style specified in PenStyle is PS_NULL.
|
||||
// The BS_NULL style SHOULD be used to specify a brush that has no effect
|
||||
brushStyle = HwmfBrushStyle.valueOf((int) leis.readUInt());
|
||||
|
||||
size += LittleEndianConsts.INT_SIZE;
|
||||
|
||||
size += colorRef.init(leis);
|
||||
|
||||
hatchStyle = HwmfHatchStyle.valueOf(leis.readInt());
|
||||
size += LittleEndianConsts.INT_SIZE;
|
||||
|
||||
// The number of elements in the array specified in the StyleEntry
|
||||
// field. This value SHOULD be zero if PenStyle does not specify PS_USERSTYLE.
|
||||
final int numStyleEntries = (int) leis.readUInt();
|
||||
size += LittleEndianConsts.INT_SIZE;
|
||||
|
||||
assert (numStyleEntries == 0 || penStyle.getLineDash() == HwmfLineDash.USERSTYLE);
|
||||
|
||||
// An optional array of 32-bit unsigned integers that defines the lengths of
|
||||
// dashes and gaps in the line drawn by this pen, when the value of PenStyle is
|
||||
// PS_USERSTYLE line style for the pen. The array contains a number of entries specified by
|
||||
// NumStyleEntries, but it is used as if it repeated indefinitely.
|
||||
// The first entry in the array specifies the length of the first dash. The second entry specifies
|
||||
// the length of the first gap. Thereafter, lengths of dashes and gaps alternate.
|
||||
// If the pen type in the PenStyle field is PS_GEOMETRIC, the lengths are specified in logical
|
||||
// units; otherwise, the lengths are specified in device units.
|
||||
|
||||
float[] dashPattern = new float[numStyleEntries];
|
||||
|
||||
for (int i = 0; i < numStyleEntries; i++) {
|
||||
dashPattern[i] = (int) leis.readUInt();
|
||||
}
|
||||
|
||||
if (penStyle.getLineDash() == HwmfLineDash.USERSTYLE) {
|
||||
emfPS.setLineDashes(dashPattern);
|
||||
}
|
||||
|
||||
size += numStyleEntries * LittleEndianConsts.INT_SIZE;
|
||||
|
||||
size += readBitmap(leis, bitmap, startIdx, offBmi, cbBmi, offBits, cbBits);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
// TODO: add style entries + bmp
|
||||
return
|
||||
"{ brushStyle: '"+brushStyle+"'"+
|
||||
", hatchStyle: '"+hatchStyle+"'"+
|
||||
", dashPattern: "+ Arrays.toString(penStyle.getLineDashes())+
|
||||
", "+super.toString().substring(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The EMR_SETMITERLIMIT record specifies the limit for the length of miter joins for the playback
|
||||
* device context.
|
||||
*/
|
||||
public static class EmfSetMiterLimit implements HemfRecord {
|
||||
protected int miterLimit;
|
||||
|
||||
@Override
|
||||
public HemfRecordType getEmfRecordType() {
|
||||
return HemfRecordType.setMiterLimit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
|
||||
miterLimit = (int) leis.readUInt();
|
||||
return LittleEndianConsts.INT_SIZE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(HemfGraphics ctx) {
|
||||
ctx.getProperties().setPenMiterLimit(miterLimit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{ miterLimit: "+miterLimit+" }";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class EmfSetBrushOrgEx implements HemfRecord {
|
||||
protected final Point2D origin = new Point2D.Double();
|
||||
|
||||
@Override
|
||||
public HemfRecordType getEmfRecordType() {
|
||||
return HemfRecordType.setBrushOrgEx;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
|
||||
return readPointL(leis, origin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{ x: "+origin.getX()+", y: "+origin.getY()+" }";
|
||||
}
|
||||
}
|
||||
|
||||
public static class EmfSetWorldTransform implements HemfRecord {
|
||||
protected final AffineTransform xForm = new AffineTransform();
|
||||
|
||||
@Override
|
||||
public HemfRecordType getEmfRecordType() {
|
||||
return HemfRecordType.setWorldTransform;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
|
||||
return readXForm(leis, xForm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(HemfGraphics ctx) {
|
||||
ctx.updateWindowMapMode();
|
||||
AffineTransform tx = ctx.getTransform();
|
||||
tx.concatenate(xForm);
|
||||
ctx.setTransform(tx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return
|
||||
"{ xForm: " +
|
||||
"{ scaleX: "+xForm.getScaleX()+
|
||||
", shearX: "+xForm.getShearX()+
|
||||
", transX: "+xForm.getTranslateX()+
|
||||
", scaleY: "+xForm.getScaleY()+
|
||||
", shearY: "+xForm.getShearY()+
|
||||
", transY: "+xForm.getTranslateY()+" } }";
|
||||
}
|
||||
}
|
||||
|
||||
public static class EmfModifyWorldTransform implements HemfRecord {
|
||||
protected final AffineTransform xForm = new AffineTransform();
|
||||
protected HemfModifyWorldTransformMode modifyWorldTransformMode;
|
||||
protected HemfHeader header;
|
||||
|
||||
@Override
|
||||
public HemfRecordType getEmfRecordType() {
|
||||
return HemfRecordType.modifyWorldTransform;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
|
||||
// An XForm object that defines a two-dimensional linear transform in logical units.
|
||||
// This transform is used according to the ModifyWorldTransformMode to define a new value for
|
||||
// the world-space to page-space transform in the playback device context.
|
||||
int size = readXForm(leis, xForm);
|
||||
|
||||
// A 32-bit unsigned integer that specifies how the transform specified in Xform is used.
|
||||
// This value MUST be in the ModifyWorldTransformMode enumeration
|
||||
modifyWorldTransformMode = HemfModifyWorldTransformMode.valueOf((int)leis.readUInt());
|
||||
|
||||
return size + LittleEndianConsts.INT_SIZE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHeader(HemfHeader header) {
|
||||
this.header = header;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(HemfGraphics ctx) {
|
||||
if (modifyWorldTransformMode == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final HemfDrawProperties prop = ctx.getProperties();
|
||||
|
||||
final AffineTransform tx;
|
||||
switch (modifyWorldTransformMode) {
|
||||
case MWT_LEFTMULTIPLY:
|
||||
|
||||
AffineTransform wsTrans;
|
||||
final Rectangle2D win = prop.getWindow();
|
||||
boolean noSetWindowExYet = win.getWidth() == 1 && win.getHeight() == 1;
|
||||
if (noSetWindowExYet) {
|
||||
// TODO: understand world-space transformation [MSDN-WRLDPGSPC]
|
||||
// experimental and horrible solved, because the world-space transformation behind it
|
||||
// is not understood :(
|
||||
// only found one example which had landscape bounds and transform of 90 degress
|
||||
|
||||
try {
|
||||
wsTrans = xForm.createInverse();
|
||||
} catch (NoninvertibleTransformException e) {
|
||||
wsTrans = new AffineTransform();
|
||||
}
|
||||
|
||||
Rectangle2D emfBounds = header.getBoundsRectangle();
|
||||
|
||||
if (xForm.getShearX() == -1.0 && xForm.getShearY() == 1.0) {
|
||||
// rotate 90 deg
|
||||
wsTrans.translate(-emfBounds.getHeight(), emfBounds.getHeight());
|
||||
}
|
||||
} else {
|
||||
wsTrans = adaptXForm(ctx.getTransform());
|
||||
}
|
||||
|
||||
tx = ctx.getTransform();
|
||||
tx.concatenate(wsTrans);
|
||||
break;
|
||||
case MWT_RIGHTMULTIPLY:
|
||||
tx = ctx.getTransform();
|
||||
tx.preConcatenate(adaptXForm(tx));
|
||||
break;
|
||||
case MWT_IDENTITY:
|
||||
ctx.updateWindowMapMode();
|
||||
tx = ctx.getTransform();
|
||||
break;
|
||||
default:
|
||||
case MWT_SET:
|
||||
ctx.updateWindowMapMode();
|
||||
tx = ctx.getTransform();
|
||||
tx.concatenate(adaptXForm(tx));
|
||||
break;
|
||||
}
|
||||
ctx.setTransform(tx);
|
||||
}
|
||||
|
||||
/**
|
||||
* adapt xform depending on the base transformation (... experimental ...)
|
||||
*/
|
||||
private AffineTransform adaptXForm(AffineTransform other) {
|
||||
// normalize signed zero
|
||||
Function<Double,Double> nn = (d) -> (d == 0. ? 0. : d);
|
||||
double yDiff = Math.signum(nn.apply(xForm.getTranslateY())) == Math.signum(nn.apply(other.getTranslateY())) ? 1. : -1.;
|
||||
double xDiff = Math.signum(nn.apply(xForm.getTranslateX())) == Math.signum(nn.apply(other.getTranslateX())) ? 1. : -1.;
|
||||
return new AffineTransform(
|
||||
xForm.getScaleX() == 0 ? 1. : xForm.getScaleX(),
|
||||
yDiff * xForm.getShearY(),
|
||||
xDiff * xForm.getShearX(),
|
||||
xForm.getScaleY() == 0. ? 1. : xForm.getScaleY(),
|
||||
xForm.getTranslateX(),
|
||||
xForm.getTranslateY()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return
|
||||
"{ xForm: " +
|
||||
"{ scaleX: "+xForm.getScaleX()+
|
||||
", shearX: "+xForm.getShearX()+
|
||||
", transX: "+xForm.getTranslateX()+
|
||||
", scaleY: "+xForm.getScaleY()+
|
||||
", shearY: "+xForm.getShearY()+
|
||||
", transY: "+xForm.getTranslateY()+" }"+
|
||||
", modifyWorldTransformMode: '"+modifyWorldTransformMode+"' }";
|
||||
}
|
||||
}
|
||||
|
||||
public static class EmfCreateMonoBrush implements HemfRecord, HwmfObjectTableEntry {
|
||||
/**
|
||||
* A 32-bit unsigned integer that specifies the index of the logical palette object
|
||||
* in the EMF Object Table. This index MUST be saved so that this object can be
|
||||
* reused or modified.
|
||||
*/
|
||||
protected int penIndex;
|
||||
|
||||
protected HwmfFill.ColorUsage colorUsage;
|
||||
protected final HwmfBitmapDib bitmap = new HwmfBitmapDib();
|
||||
|
||||
@Override
|
||||
public HemfRecordType getEmfRecordType() {
|
||||
return HemfRecordType.createMonoBrush;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
|
||||
final int startIdx = leis.getReadIndex();
|
||||
|
||||
penIndex = (int) leis.readUInt();
|
||||
|
||||
// A 32-bit unsigned integer that specifies how to interpret values in the color
|
||||
// table in the DIB header. This value MUST be in the DIBColors enumeration
|
||||
colorUsage = HwmfFill.ColorUsage.valueOf((int) leis.readUInt());
|
||||
|
||||
// A 32-bit unsigned integer that specifies the offset from the start of this
|
||||
// record to the DIB header, if the record contains a DIB.
|
||||
int offBmi = (int) leis.readUInt();
|
||||
|
||||
// A 32-bit unsigned integer that specifies the size of the DIB header, if the
|
||||
// record contains a DIB.
|
||||
int cbBmi = (int) leis.readUInt();
|
||||
|
||||
// A 32-bit unsigned integer that specifies the offset from the start of this
|
||||
// record to the DIB bits, if the record contains a DIB.
|
||||
int offBits = (int) leis.readUInt();
|
||||
|
||||
// A 32-bit unsigned integer that specifies the size of the DIB bits, if the record
|
||||
// contains a DIB.
|
||||
int cbBits = (int) leis.readUInt();
|
||||
|
||||
int size = 6 * LittleEndianConsts.INT_SIZE;
|
||||
|
||||
size += readBitmap(leis, bitmap, startIdx, offBmi, cbBmi, offBits, cbBits);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(HemfGraphics ctx) {
|
||||
ctx.addObjectTableEntry(this, penIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyObject(HwmfGraphics ctx) {
|
||||
if (!bitmap.isValid()) {
|
||||
return;
|
||||
}
|
||||
HwmfDrawProperties props = ctx.getProperties();
|
||||
props.setBrushStyle(HwmfBrushStyle.BS_PATTERN);
|
||||
BufferedImage bmp = bitmap.getImage();
|
||||
props.setBrushBitmap(bmp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return
|
||||
"{ penIndex: " + penIndex +
|
||||
", colorUsage: " + colorUsage +
|
||||
", bitmap: " + bitmap +
|
||||
"}";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
==================================================================== */
|
||||
|
||||
package org.apache.poi.hemf.record.emf;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.poi.hemf.draw.HemfGraphics;
|
||||
import org.apache.poi.hwmf.record.HwmfPalette;
|
||||
import org.apache.poi.util.LittleEndianConsts;
|
||||
import org.apache.poi.util.LittleEndianInputStream;
|
||||
|
||||
public class HemfPalette {
|
||||
/** The EMR_SELECTPALETTE record specifies a logical palette for the playback device context. */
|
||||
public static class EmfSelectPalette extends HwmfPalette.WmfSelectPalette implements HemfRecord {
|
||||
@Override
|
||||
public HemfRecordType getEmfRecordType() {
|
||||
return HemfRecordType.selectPalette;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
|
||||
/*
|
||||
* A 32-bit unsigned integer that specifies either the index of a LogPalette object
|
||||
* in the EMF Object Table or the value DEFAULT_PALETTE, which is the index
|
||||
* of a stock object palette from the StockObject enumeration
|
||||
*/
|
||||
paletteIndex = (int)leis.readUInt();
|
||||
return LittleEndianConsts.INT_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
/** The EMR_CREATEPALETTE record defines a logical palette for graphics operations. */
|
||||
public static class EmfCreatePalette extends HwmfPalette.WmfCreatePalette implements HemfRecord {
|
||||
|
||||
protected int paletteIndex;
|
||||
|
||||
@Override
|
||||
public HemfRecordType getEmfRecordType() {
|
||||
return HemfRecordType.createPalette;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
|
||||
start = 0x0300;
|
||||
/* A 32-bit unsigned integer that specifies the index of the logical palette object
|
||||
* in the EMF Object Table. This index MUST be saved so that this object can be
|
||||
* reused or modified.
|
||||
*/
|
||||
paletteIndex = (int)leis.readUInt();
|
||||
/* A 16-bit unsigned integer that specifies the version number of the system. This MUST be 0x0300. */
|
||||
int version = leis.readUShort();
|
||||
assert(version == 0x0300);
|
||||
int size = readPaletteEntries(leis, -1);
|
||||
return size + LittleEndianConsts.INT_SIZE + LittleEndianConsts.SHORT_SIZE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(HemfGraphics ctx) {
|
||||
ctx.addObjectTableEntry(this, paletteIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The EMR_SETPALETTEENTRIES record defines RGB color values in a range of entries for an existing
|
||||
* LogPalette object.
|
||||
*/
|
||||
public static class EmfSetPaletteEntries extends HwmfPalette.WmfSetPaletteEntries implements HemfRecord {
|
||||
/** specifies the palette EMF Object Table index. */
|
||||
int paletteIndex;
|
||||
|
||||
@Override
|
||||
public HemfRecordType getEmfRecordType() {
|
||||
return HemfRecordType.setPaletteEntries;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
|
||||
// A 32-bit unsigned integer that specifies the palette EMF Object Table index.
|
||||
paletteIndex = (int)leis.readUInt();
|
||||
// A 32-bit unsigned integer that specifies the index of the first entry to set.
|
||||
start = (int)leis.readUInt();
|
||||
// A 32-bit unsigned integer that specifies the number of entries.
|
||||
int nbrOfEntries = (int)leis.readUInt();
|
||||
int size = readPaletteEntries(leis, nbrOfEntries);
|
||||
return size + 3*LittleEndianConsts.INT_SIZE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(HemfGraphics ctx) {
|
||||
ctx.addObjectTableEntry(this, paletteIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The EMR_RESIZEPALETTE record increases or decreases the size of an existing LogPalette object
|
||||
*/
|
||||
public static class EmfResizePalette extends HwmfPalette.WmfResizePalette implements HemfRecord {
|
||||
/** specifies the palette EMF Object Table index. */
|
||||
int paletteIndex;
|
||||
|
||||
@Override
|
||||
public HemfRecordType getEmfRecordType() {
|
||||
return HemfRecordType.resizePalette;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
|
||||
// A 32-bit unsigned integer that specifies the index of the palette object in the EMF Object Table
|
||||
paletteIndex = (int)leis.readUInt();
|
||||
|
||||
// A 32-bit unsigned integer that specifies the number of entries in the palette after resizing.
|
||||
// The value MUST be less than or equal to 0x00000400 and greater than 0x00000000.
|
||||
numberOfEntries = (int)leis.readUInt();
|
||||
|
||||
return 2*LittleEndianConsts.INT_SIZE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(HemfGraphics ctx) {
|
||||
ctx.addObjectTableEntry(this, paletteIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This record maps palette entries from the current LogPalette object to the system_palette.
|
||||
* This EMF record specifies no parameters.
|
||||
*/
|
||||
public static class EmfRealizePalette extends HwmfPalette.WmfRealizePalette implements HemfRecord {
|
||||
@Override
|
||||
public HemfRecordType getEmfRecordType() {
|
||||
return HemfRecordType.realizePalette;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,25 +15,31 @@
|
|||
limitations under the License.
|
||||
==================================================================== */
|
||||
|
||||
package org.apache.poi.hemf.record;
|
||||
package org.apache.poi.hemf.record.emf;
|
||||
|
||||
import org.apache.poi.util.Internal;
|
||||
import org.apache.poi.hwmf.record.HwmfPenStyle;
|
||||
|
||||
/**
|
||||
* Syntactic utility to allow for four different
|
||||
* comment classes
|
||||
*/
|
||||
@Internal
|
||||
public abstract class AbstractHemfComment {
|
||||
public class HemfPenStyle extends HwmfPenStyle {
|
||||
|
||||
private final byte[] rawBytes;
|
||||
private float[] dashPattern;
|
||||
|
||||
public AbstractHemfComment(byte[] rawBytes) {
|
||||
this.rawBytes = rawBytes;
|
||||
public static HemfPenStyle valueOf(int flag) {
|
||||
HemfPenStyle ps = new HemfPenStyle();
|
||||
ps.flag = flag;
|
||||
return ps;
|
||||
}
|
||||
|
||||
public byte[] getRawBytes() {
|
||||
return rawBytes;
|
||||
@Override
|
||||
public float[] getLineDashes() {
|
||||
return (getLineDash() == HwmfLineDash.USERSTYLE) ? dashPattern : super.getLineDashes();
|
||||
}
|
||||
|
||||
public void setLineDashes(float[] dashPattern) {
|
||||
this.dashPattern = (dashPattern == null) ? null : dashPattern.clone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HemfPenStyle clone() {
|
||||
return (HemfPenStyle)super.clone();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
==================================================================== */
|
||||
|
||||
package org.apache.poi.hemf.record.emf;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.poi.hemf.draw.HemfGraphics;
|
||||
import org.apache.poi.hwmf.record.HwmfRecord;
|
||||
import org.apache.poi.util.Internal;
|
||||
import org.apache.poi.util.LittleEndianInputStream;
|
||||
|
||||
@Internal
|
||||
public interface HemfRecord {
|
||||
|
||||
HemfRecordType getEmfRecordType();
|
||||
|
||||
/**
|
||||
* Init record from stream
|
||||
*
|
||||
* @param leis the little endian input stream
|
||||
* @param recordSize the size limit for this record
|
||||
* @param recordId the id of the {@link HemfRecordType}
|
||||
*
|
||||
* @return count of processed bytes
|
||||
*
|
||||
* @throws IOException when the inputstream is malformed
|
||||
*/
|
||||
long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException;
|
||||
|
||||
/**
|
||||
* Draws the record, the default redirects to the parent WMF record drawing
|
||||
* @param ctx the drawing context
|
||||
*/
|
||||
default void draw(HemfGraphics ctx) {
|
||||
if (this instanceof HwmfRecord) {
|
||||
((HwmfRecord) this).draw(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the header reference, in case the record needs to refer to it
|
||||
* @param header the emf header
|
||||
*/
|
||||
default void setHeader(HemfHeader header) {}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
==================================================================== */
|
||||
|
||||
package org.apache.poi.hemf.record.emf;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.apache.poi.util.LittleEndianConsts;
|
||||
import org.apache.poi.util.LittleEndianInputStream;
|
||||
import org.apache.poi.util.RecordFormatException;
|
||||
|
||||
public class HemfRecordIterator implements Iterator<HemfRecord> {
|
||||
|
||||
static final int HEADER_SIZE = 2*LittleEndianConsts.INT_SIZE;
|
||||
|
||||
private final LittleEndianInputStream stream;
|
||||
private HemfRecord currentRecord;
|
||||
|
||||
public HemfRecordIterator(LittleEndianInputStream leis) {
|
||||
stream = leis;
|
||||
//queue the first non-header record
|
||||
currentRecord = _next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return currentRecord != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HemfRecord next() {
|
||||
HemfRecord toReturn = currentRecord;
|
||||
currentRecord = (currentRecord instanceof HemfMisc.EmfEof) ? null : _next();
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
private HemfRecord _next() {
|
||||
if (currentRecord != null && HemfRecordType.eof == currentRecord.getEmfRecordType()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final int readIndex = stream.getReadIndex();
|
||||
|
||||
final long recordId, recordSize;
|
||||
try {
|
||||
recordId = stream.readUInt();
|
||||
recordSize = stream.readUInt();
|
||||
} catch (RuntimeException e) {
|
||||
// EOF
|
||||
return null;
|
||||
}
|
||||
|
||||
HemfRecordType type = HemfRecordType.getById(recordId);
|
||||
if (type == null) {
|
||||
throw new RecordFormatException("Undefined record of type: "+recordId+" at "+Integer.toHexString(readIndex));
|
||||
}
|
||||
final HemfRecord record = type.constructor.get();
|
||||
|
||||
try {
|
||||
long remBytes = recordSize-HEADER_SIZE;
|
||||
long readBytes = record.init(stream, remBytes, recordId);
|
||||
assert (readBytes <= remBytes);
|
||||
stream.skipFully((int)(remBytes-readBytes));
|
||||
} catch (IOException|RuntimeException e) {
|
||||
throw new RecordFormatException(e);
|
||||
}
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException("Remove not supported");
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,165 @@
|
|||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
==================================================================== */
|
||||
|
||||
package org.apache.poi.hemf.record.emf;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.apache.poi.util.Internal;
|
||||
|
||||
@Internal
|
||||
public enum HemfRecordType {
|
||||
|
||||
header(0x00000001, HemfHeader::new),
|
||||
polyBezier(0x00000002, HemfDraw.EmfPolyBezier::new),
|
||||
polygon(0x00000003, HemfDraw.EmfPolygon::new),
|
||||
polyline(0x00000004, HemfDraw.EmfPolyline::new),
|
||||
polyBezierTo(0x00000005, HemfDraw.EmfPolyBezierTo::new),
|
||||
polylineTo(0x00000006, HemfDraw.EmfPolylineTo::new),
|
||||
polyPolyline(0x00000007, HemfDraw.EmfPolyPolyline::new),
|
||||
polyPolygon(0x00000008, HemfDraw.EmfPolyPolygon::new),
|
||||
setWindowExtEx(0x00000009, HemfWindowing.EmfSetWindowExtEx::new),
|
||||
setWindowOrgEx(0x0000000A, HemfWindowing.EmfSetWindowOrgEx::new),
|
||||
setViewportExtEx(0x0000000B, HemfWindowing.EmfSetViewportExtEx::new),
|
||||
setViewportOrgEx(0x0000000C, HemfWindowing.EmfSetViewportOrgEx::new),
|
||||
setBrushOrgEx(0x0000000D, HemfMisc.EmfSetBrushOrgEx::new),
|
||||
eof(0x0000000E, HemfMisc.EmfEof::new),
|
||||
setPixelV(0x0000000F, HemfDraw.EmfSetPixelV::new),
|
||||
setMapperFlags(0x00000010, HemfMisc.EmfSetMapperFlags::new),
|
||||
setMapMode(0x00000011, HemfMisc.EmfSetMapMode::new),
|
||||
setBkMode(0x00000012, HemfMisc.EmfSetBkMode::new),
|
||||
setPolyfillMode(0x00000013, HemfFill.EmfSetPolyfillMode::new),
|
||||
setRop2(0x00000014, HemfMisc.EmfSetRop2::new),
|
||||
setStretchBltMode(0x00000015, HemfMisc.EmfSetStretchBltMode::new),
|
||||
setTextAlign(0x00000016, HemfText.EmfSetTextAlign::new),
|
||||
setcoloradjustment(0x00000017, UnimplementedHemfRecord::new),
|
||||
setTextColor(0x00000018, HemfText.EmfSetTextColor::new),
|
||||
setBkColor(0x00000019, HemfMisc.EmfSetBkColor::new),
|
||||
setOffsetClipRgn(0x0000001A, HemfWindowing.EmfSetOffsetClipRgn::new),
|
||||
setMoveToEx(0x0000001B, HemfDraw.EmfSetMoveToEx::new),
|
||||
setmetargn(0x0000001C, UnimplementedHemfRecord::new),
|
||||
setExcludeClipRect(0x0000001D, HemfWindowing.EmfSetExcludeClipRect::new),
|
||||
setIntersectClipRect(0x0000001E, HemfWindowing.EmfSetIntersectClipRect::new),
|
||||
scaleViewportExtEx(0x0000001F, HemfWindowing.EmfScaleViewportExtEx::new),
|
||||
scaleWindowExtEx(0x00000020, HemfWindowing.EmfScaleWindowExtEx::new),
|
||||
saveDc(0x00000021, HemfMisc.EmfSaveDc::new),
|
||||
restoreDc(0x00000022, HemfMisc.EmfRestoreDc::new),
|
||||
setWorldTransform(0x00000023, HemfMisc.EmfSetWorldTransform::new),
|
||||
modifyWorldTransform(0x00000024, HemfMisc.EmfModifyWorldTransform::new),
|
||||
selectObject(0x00000025, HemfDraw.EmfSelectObject::new),
|
||||
createPen(0x00000026, HemfMisc.EmfCreatePen::new),
|
||||
createBrushIndirect(0x00000027, HemfMisc.EmfCreateBrushIndirect::new),
|
||||
deleteobject(0x00000028, HemfMisc.EmfDeleteObject::new),
|
||||
anglearc(0x00000029, UnimplementedHemfRecord::new),
|
||||
ellipse(0x0000002A, HemfDraw.EmfEllipse::new),
|
||||
rectangle(0x0000002B, HemfDraw.EmfRectangle::new),
|
||||
roundRect(0x0000002C, HemfDraw.EmfRoundRect::new),
|
||||
arc(0x0000002D, HemfDraw.EmfArc::new),
|
||||
chord(0x0000002E, HemfDraw.EmfChord::new),
|
||||
pie(0x0000002F, HemfDraw.EmfPie::new),
|
||||
selectPalette(0x00000030, HemfPalette.EmfSelectPalette::new),
|
||||
createPalette(0x00000031, HemfPalette.EmfCreatePalette::new),
|
||||
setPaletteEntries(0x00000032, HemfPalette.EmfSetPaletteEntries::new),
|
||||
resizePalette(0x00000033, HemfPalette.EmfResizePalette::new),
|
||||
realizePalette(0x0000034, HemfPalette.EmfRealizePalette::new),
|
||||
extFloodFill(0x00000035, HemfFill.EmfExtFloodFill::new),
|
||||
lineTo(0x00000036, HemfDraw.EmfLineTo::new),
|
||||
arcTo(0x00000037, HemfDraw.EmfArcTo::new),
|
||||
polyDraw(0x00000038, HemfDraw.EmfPolyDraw::new),
|
||||
setarcdirection(0x00000039, UnimplementedHemfRecord::new),
|
||||
setMiterLimit(0x0000003A, HemfMisc.EmfSetMiterLimit::new),
|
||||
beginPath(0x0000003B, HemfDraw.EmfBeginPath::new),
|
||||
endPath(0x0000003C, HemfDraw.EmfEndPath::new),
|
||||
closeFigure(0x0000003D, HemfDraw.EmfCloseFigure::new),
|
||||
fillPath(0x0000003E, HemfDraw.EmfFillPath::new),
|
||||
strokeAndFillPath(0x0000003F, HemfDraw.EmfStrokeAndFillPath::new),
|
||||
strokePath(0x00000040, HemfDraw.EmfStrokePath::new),
|
||||
flattenPath(0x00000041, HemfDraw.EmfFlattenPath::new),
|
||||
widenPath(0x00000042, HemfDraw.EmfWidenPath::new),
|
||||
selectClipPath(0x00000043, HemfWindowing.EmfSelectClipPath::new),
|
||||
abortPath(0x00000044, HemfDraw.EmfAbortPath::new),
|
||||
// no 45 ?!
|
||||
comment(0x00000046, HemfComment.EmfComment::new),
|
||||
fillRgn(0x00000047, HemfFill.EmfFillRgn::new),
|
||||
frameRgn(0x00000048, HemfFill.EmfFrameRgn::new),
|
||||
invertRgn(0x00000049, HemfFill.EmfInvertRgn::new),
|
||||
paintRgn(0x0000004A, HemfFill.EmfPaintRgn::new),
|
||||
extSelectClipRgn(0x0000004B, HemfFill.EmfExtSelectClipRgn::new),
|
||||
bitBlt(0x0000004C, HemfFill.EmfBitBlt::new),
|
||||
stretchBlt(0x0000004D, HemfFill.EmfStretchBlt::new),
|
||||
maskblt(0x0000004E, UnimplementedHemfRecord::new),
|
||||
plgblt(0x0000004F, UnimplementedHemfRecord::new),
|
||||
setDiBitsToDevice(0x00000050, HemfFill.EmfSetDiBitsToDevice::new),
|
||||
stretchDiBits(0x00000051, HemfFill.EmfStretchDiBits::new),
|
||||
extCreateFontIndirectW(0x00000052, HemfText.EmfExtCreateFontIndirectW::new),
|
||||
extTextOutA(0x00000053, HemfText.EmfExtTextOutA::new),
|
||||
extTextOutW(0x00000054, HemfText.EmfExtTextOutW::new),
|
||||
polyBezier16(0x00000055, HemfDraw.EmfPolyBezier16::new),
|
||||
polygon16(0x00000056, HemfDraw.EmfPolygon16::new),
|
||||
polyline16(0x00000057, HemfDraw.EmfPolyline16::new),
|
||||
polyBezierTo16(0x00000058, HemfDraw.EmfPolyBezierTo16::new),
|
||||
polylineTo16(0x00000059, HemfDraw.EmfPolylineTo16::new),
|
||||
polyPolyline16(0x0000005A, HemfDraw.EmfPolyPolyline16::new),
|
||||
polyPolygon16(0x0000005B, HemfDraw.EmfPolyPolygon16::new),
|
||||
polyDraw16(0x0000005C, HemfDraw.EmfPolyDraw16::new),
|
||||
createMonoBrush(0x0000005D, HemfMisc.EmfCreateMonoBrush::new),
|
||||
createDibPatternBrushPt(0x0000005E, HemfMisc.EmfCreateDibPatternBrushPt::new),
|
||||
extCreatePen(0x0000005F, HemfMisc.EmfExtCreatePen::new),
|
||||
polytextouta(0x00000060, HemfText.PolyTextOutA::new),
|
||||
polytextoutw(0x00000061, HemfText.PolyTextOutW::new),
|
||||
seticmmode(0x00000062, UnimplementedHemfRecord::new),
|
||||
createcolorspace(0x00000063, UnimplementedHemfRecord::new),
|
||||
setcolorspace(0x00000064, UnimplementedHemfRecord::new),
|
||||
deletecolorspace(0x00000065, UnimplementedHemfRecord::new),
|
||||
glsrecord(0x00000066, UnimplementedHemfRecord::new),
|
||||
glsboundedrecord(0x00000067, UnimplementedHemfRecord::new),
|
||||
pixelformat(0x00000068, UnimplementedHemfRecord::new),
|
||||
drawescape(0x00000069, UnimplementedHemfRecord::new),
|
||||
extescape(0x0000006A, UnimplementedHemfRecord::new),
|
||||
// no 6b ?!
|
||||
smalltextout(0x0000006C, UnimplementedHemfRecord::new),
|
||||
forceufimapping(0x0000006D, UnimplementedHemfRecord::new),
|
||||
namedescape(0x0000006E, UnimplementedHemfRecord::new),
|
||||
colorcorrectpalette(0x0000006F, UnimplementedHemfRecord::new),
|
||||
seticmprofilea(0x00000070, UnimplementedHemfRecord::new),
|
||||
seticmprofilew(0x00000071, UnimplementedHemfRecord::new),
|
||||
alphaBlend(0x00000072, HemfFill.EmfAlphaBlend::new),
|
||||
setlayout(0x00000073, UnimplementedHemfRecord::new),
|
||||
transparentblt(0x00000074, UnimplementedHemfRecord::new),
|
||||
// no 75 ?!
|
||||
gradientfill(0x00000076, UnimplementedHemfRecord::new),
|
||||
setlinkdufis(0x00000077, UnimplementedHemfRecord::new),
|
||||
settextjustification(0x00000078, HemfText.SetTextJustification::new),
|
||||
colormatchtargetw(0x00000079, UnimplementedHemfRecord::new),
|
||||
createcolorspacew(0x0000007A, UnimplementedHemfRecord::new);
|
||||
|
||||
|
||||
public final long id;
|
||||
public final Supplier<? extends HemfRecord> constructor;
|
||||
|
||||
HemfRecordType(long id, Supplier<? extends HemfRecord> constructor) {
|
||||
this.id = id;
|
||||
this.constructor = constructor;
|
||||
}
|
||||
|
||||
public static HemfRecordType getById(long id) {
|
||||
for (HemfRecordType wrt : values()) {
|
||||
if (wrt.id == id) return wrt;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,332 @@
|
|||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
==================================================================== */
|
||||
|
||||
package org.apache.poi.hemf.record.emf;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_16LE;
|
||||
import static org.apache.poi.hemf.record.emf.HemfDraw.readDimensionFloat;
|
||||
import static org.apache.poi.hemf.record.emf.HemfDraw.readPointL;
|
||||
import static org.apache.poi.hemf.record.emf.HemfDraw.readRectL;
|
||||
import static org.apache.poi.hemf.record.emf.HemfRecordIterator.HEADER_SIZE;
|
||||
|
||||
import java.awt.geom.Dimension2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import org.apache.poi.hemf.draw.HemfGraphics;
|
||||
import org.apache.poi.hwmf.draw.HwmfGraphics;
|
||||
import org.apache.poi.hwmf.record.HwmfText;
|
||||
import org.apache.poi.hwmf.record.HwmfText.WmfSetTextAlign;
|
||||
import org.apache.poi.util.Dimension2DDouble;
|
||||
import org.apache.poi.util.IOUtils;
|
||||
import org.apache.poi.util.Internal;
|
||||
import org.apache.poi.util.LittleEndianConsts;
|
||||
import org.apache.poi.util.LittleEndianInputStream;
|
||||
import org.apache.poi.util.RecordFormatException;
|
||||
|
||||
/**
|
||||
* Container class to gather all text-related commands
|
||||
* This is starting out as read only, and very little is actually
|
||||
* implemented at this point!
|
||||
*/
|
||||
@Internal
|
||||
public class HemfText {
|
||||
|
||||
private static final int MAX_RECORD_LENGTH = 1_000_000;
|
||||
|
||||
public enum EmfGraphicsMode {
|
||||
GM_COMPATIBLE, GM_ADVANCED
|
||||
}
|
||||
|
||||
public static class EmfExtTextOutA extends HwmfText.WmfExtTextOut implements HemfRecord {
|
||||
|
||||
protected Rectangle2D boundsIgnored = new Rectangle2D.Double();
|
||||
protected EmfGraphicsMode graphicsMode;
|
||||
|
||||
/**
|
||||
* The scale factor to apply along the X/Y axis to convert from page space units to .01mm units.
|
||||
* This SHOULD be used only if the graphics mode specified by iGraphicsMode is GM_COMPATIBLE.
|
||||
*/
|
||||
protected final Dimension2D scale = new Dimension2DDouble();
|
||||
|
||||
public EmfExtTextOutA() {
|
||||
super(new EmfExtTextOutOptions());
|
||||
}
|
||||
|
||||
@Override
|
||||
public HemfRecordType getEmfRecordType() {
|
||||
return HemfRecordType.extTextOutA;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
|
||||
if (recordSize < 0 || Integer.MAX_VALUE <= recordSize) {
|
||||
throw new RecordFormatException("recordSize must be a positive integer (0-0x7FFFFFFF)");
|
||||
}
|
||||
|
||||
// A WMF RectL object. It is not used and MUST be ignored on receipt.
|
||||
long size = readRectL(leis, boundsIgnored);
|
||||
|
||||
// A 32-bit unsigned integer that specifies the graphics mode from the GraphicsMode enumeration
|
||||
graphicsMode = EmfGraphicsMode.values()[leis.readInt()-1];
|
||||
size += LittleEndianConsts.INT_SIZE;
|
||||
|
||||
size += readDimensionFloat(leis, scale);
|
||||
|
||||
// A WMF PointL object that specifies the coordinates of the reference point used to position the string.
|
||||
// The reference point is defined by the last EMR_SETTEXTALIGN record.
|
||||
// If no such record has been set, the default alignment is TA_LEFT,TA_TOP.
|
||||
size += readPointL(leis, reference);
|
||||
// A 32-bit unsigned integer that specifies the number of characters in the string.
|
||||
stringLength = (int)leis.readUInt();
|
||||
// A 32-bit unsigned integer that specifies the offset to the output string, in bytes,
|
||||
// from the start of the record in which this object is contained.
|
||||
// This value MUST be 8- or 16-bit aligned, according to the character format.
|
||||
int offString = (int)leis.readUInt();
|
||||
size += 2*LittleEndianConsts.INT_SIZE;
|
||||
|
||||
size += options.init(leis);
|
||||
// An optional WMF RectL object that defines a clipping and/or opaquing rectangle in logical units.
|
||||
// This rectangle is applied to the text output performed by the containing record.
|
||||
if (options.isClipped() || options.isOpaque()) {
|
||||
size += readRectL(leis, bounds);
|
||||
}
|
||||
|
||||
// A 32-bit unsigned integer that specifies the offset to an intercharacter spacing array, in bytes,
|
||||
// from the start of the record in which this object is contained. This value MUST be 32-bit aligned.
|
||||
int offDx = (int)leis.readUInt();
|
||||
size += LittleEndianConsts.INT_SIZE;
|
||||
|
||||
// handle dx before string and other way round
|
||||
final String order = (offDx < offString) ? "ds" : "sd";
|
||||
// the next byte index after the string ends
|
||||
int strEnd = (int)((offDx <= HEADER_SIZE) ? recordSize : offDx-HEADER_SIZE);
|
||||
for (char op : order.toCharArray()) {
|
||||
switch (op) {
|
||||
case 'd': {
|
||||
dx.clear();
|
||||
int undefinedSpace2 = (int) (offDx - (size + HEADER_SIZE));
|
||||
if (offDx > 0 && undefinedSpace2 >= 0 && offDx-HEADER_SIZE < recordSize) {
|
||||
leis.skipFully(undefinedSpace2);
|
||||
size += undefinedSpace2;
|
||||
|
||||
// An array of 32-bit unsigned integers that specify the output spacing between the origins of adjacent
|
||||
// character cells in logical units. The location of this field is specified by the value of offDx
|
||||
// in bytes from the start of this record. If spacing is defined, this field contains the same number
|
||||
// of values as characters in the output string.
|
||||
//
|
||||
// If the Options field of the EmrText object contains the ETO_PDY flag, then this buffer
|
||||
// contains twice as many values as there are characters in the output string, one
|
||||
// horizontal and one vertical offset for each, in that order.
|
||||
//
|
||||
// If ETO_RTLREADING is specified, characters are laid right to left instead of left to right.
|
||||
// No other options affect the interpretation of this field.
|
||||
final int maxSize = (int)Math.min((offDx < offString) ? (offString-HEADER_SIZE) : recordSize, recordSize);
|
||||
while (size <= maxSize-LittleEndianConsts.INT_SIZE) {
|
||||
dx.add((int) leis.readUInt());
|
||||
size += LittleEndianConsts.INT_SIZE;
|
||||
}
|
||||
}
|
||||
if (dx.size() < stringLength) {
|
||||
// invalid dx array
|
||||
dx.clear();
|
||||
}
|
||||
strEnd = (int)recordSize;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
case 's': {
|
||||
int undefinedSpace1 = (int)(offString - (size + HEADER_SIZE));
|
||||
if (offString > 0 && undefinedSpace1 >= 0 && offString-HEADER_SIZE < recordSize) {
|
||||
leis.skipFully(undefinedSpace1);
|
||||
size += undefinedSpace1;
|
||||
|
||||
// read all available bytes and not just "stringLength * 1(ansi)/2(unicode)"
|
||||
// in case we need to deal with surrogate pairs
|
||||
final int maxSize = (int)(Math.min(recordSize, strEnd)-size);
|
||||
rawTextBytes = IOUtils.safelyAllocate(maxSize, MAX_RECORD_LENGTH);
|
||||
leis.readFully(rawTextBytes);
|
||||
size += maxSize;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* To be implemented! We need to get the current character set
|
||||
* from the current font for {@link EmfExtTextOutA},
|
||||
* which has to be tracked in the playback device.
|
||||
*
|
||||
* For {@link EmfExtTextOutW}, the charset is "UTF-16LE"
|
||||
*
|
||||
* @param charset the charset to be used to decode the character bytes
|
||||
* @return text from this text element
|
||||
* @throws IOException
|
||||
*/
|
||||
public String getText(Charset charset) throws IOException {
|
||||
return super.getText(charset);
|
||||
}
|
||||
|
||||
public EmfGraphicsMode getGraphicsMode() {
|
||||
return graphicsMode;
|
||||
}
|
||||
|
||||
public Dimension2D getScale() {
|
||||
return scale;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(HwmfGraphics ctx) {
|
||||
// A 32-bit floating-point value that specifies the scale factor to apply along
|
||||
// the axis to convert from page space units to .01mm units.
|
||||
// This SHOULD be used only if the graphics mode specified by iGraphicsMode is GM_COMPATIBLE.
|
||||
Dimension2D scl = graphicsMode == EmfGraphicsMode.GM_COMPATIBLE ? scale : null;
|
||||
ctx.drawString(rawTextBytes, stringLength, reference, scl, bounds, options, dx, isUnicode());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return
|
||||
"{ graphicsMode: '"+graphicsMode+"'"+
|
||||
", scale: { w: "+scale.getWidth()+", h: "+scale.getHeight()+" },"+
|
||||
super.toString().substring(1);
|
||||
}
|
||||
}
|
||||
|
||||
public static class EmfExtTextOutW extends EmfExtTextOutA {
|
||||
|
||||
@Override
|
||||
public HemfRecordType getEmfRecordType() {
|
||||
return HemfRecordType.extTextOutW;
|
||||
}
|
||||
|
||||
public String getText() throws IOException {
|
||||
return getText(UTF_16LE);
|
||||
}
|
||||
|
||||
protected boolean isUnicode() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The EMR_SETTEXTALIGN record specifies text alignment.
|
||||
*/
|
||||
public static class EmfSetTextAlign extends WmfSetTextAlign implements HemfRecord {
|
||||
@Override
|
||||
public HemfRecordType getEmfRecordType() {
|
||||
return HemfRecordType.setTextAlign;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
|
||||
/**
|
||||
* A 32-bit unsigned integer that specifies text alignment by using a mask of text alignment flags.
|
||||
* These are either WMF TextAlignmentMode Flags for text with a horizontal baseline,
|
||||
* or WMF VerticalTextAlignmentMode Flags for text with a vertical baseline.
|
||||
* Only one value can be chosen from those that affect horizontal and vertical alignment.
|
||||
*/
|
||||
textAlignmentMode = (int)leis.readUInt();
|
||||
return LittleEndianConsts.INT_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The EMR_SETTEXTCOLOR record defines the current text color.
|
||||
*/
|
||||
public static class EmfSetTextColor extends HwmfText.WmfSetTextColor implements HemfRecord {
|
||||
@Override
|
||||
public HemfRecordType getEmfRecordType() {
|
||||
return HemfRecordType.setTextColor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
|
||||
return colorRef.init(leis);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static class EmfExtCreateFontIndirectW extends HwmfText.WmfCreateFontIndirect
|
||||
implements HemfRecord {
|
||||
int fontIdx;
|
||||
|
||||
public EmfExtCreateFontIndirectW() {
|
||||
super(new HemfFont());
|
||||
}
|
||||
|
||||
@Override
|
||||
public HemfRecordType getEmfRecordType() {
|
||||
return HemfRecordType.extCreateFontIndirectW;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
|
||||
// A 32-bit unsigned integer that specifies the index of the logical font object
|
||||
// in the EMF Object Table
|
||||
fontIdx = (int)leis.readUInt();
|
||||
int size = font.init(leis, (int)(recordSize-LittleEndianConsts.INT_SIZE));
|
||||
return size+LittleEndianConsts.INT_SIZE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(HemfGraphics ctx) {
|
||||
ctx.addObjectTableEntry(this, fontIdx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{ index: "+fontIdx+", font: "+font+" } ";
|
||||
}
|
||||
}
|
||||
|
||||
public static class EmfExtTextOutOptions extends HwmfText.WmfExtTextOutOptions {
|
||||
@Override
|
||||
public int init(LittleEndianInputStream leis) {
|
||||
// A 32-bit unsigned integer that specifies how to use the rectangle specified in the Rectangle field.
|
||||
// This field can be a combination of more than one ExtTextOutOptions enumeration
|
||||
flag = (int)leis.readUInt();
|
||||
return LittleEndianConsts.INT_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
public static class SetTextJustification extends UnimplementedHemfRecord {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Needs to be implemented. Couldn't find example.
|
||||
*/
|
||||
public static class PolyTextOutA extends UnimplementedHemfRecord {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Needs to be implemented. Couldn't find example.
|
||||
*/
|
||||
public static class PolyTextOutW extends UnimplementedHemfRecord {
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,220 @@
|
|||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
==================================================================== */
|
||||
|
||||
package org.apache.poi.hemf.record.emf;
|
||||
|
||||
import static org.apache.poi.hemf.record.emf.HemfDraw.readDimensionInt;
|
||||
import static org.apache.poi.hemf.record.emf.HemfDraw.readPointL;
|
||||
import static org.apache.poi.hwmf.record.HwmfDraw.normalizeBounds;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.poi.hemf.draw.HemfDrawProperties;
|
||||
import org.apache.poi.hemf.draw.HemfGraphics;
|
||||
import org.apache.poi.hwmf.record.HwmfRegionMode;
|
||||
import org.apache.poi.hwmf.record.HwmfWindowing;
|
||||
import org.apache.poi.util.LittleEndianConsts;
|
||||
import org.apache.poi.util.LittleEndianInputStream;
|
||||
|
||||
public class HemfWindowing {
|
||||
|
||||
/**
|
||||
* The EMR_SETWINDOWEXTEX record defines the window extent.
|
||||
*/
|
||||
public static class EmfSetWindowExtEx extends HwmfWindowing.WmfSetWindowExt implements HemfRecord {
|
||||
@Override
|
||||
public HemfRecordType getEmfRecordType() {
|
||||
return HemfRecordType.setWindowExtEx;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
|
||||
return readDimensionInt(leis, size);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The EMR_SETWINDOWORGEX record defines the window origin.
|
||||
*/
|
||||
public static class EmfSetWindowOrgEx extends HwmfWindowing.WmfSetWindowOrg implements HemfRecord {
|
||||
@Override
|
||||
public HemfRecordType getEmfRecordType() {
|
||||
return HemfRecordType.setWindowOrgEx;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
|
||||
return readPointL(leis, origin);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The EMR_SETVIEWPORTEXTEX record defines the viewport extent.
|
||||
*/
|
||||
public static class EmfSetViewportExtEx extends HwmfWindowing.WmfSetViewportExt implements HemfRecord {
|
||||
@Override
|
||||
public HemfRecordType getEmfRecordType() {
|
||||
return HemfRecordType.setViewportExtEx;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
|
||||
return readDimensionInt(leis, extents);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The EMR_SETVIEWPORTORGEX record defines the viewport origin.
|
||||
*/
|
||||
public static class EmfSetViewportOrgEx extends HwmfWindowing.WmfSetViewportOrg implements HemfRecord {
|
||||
@Override
|
||||
public HemfRecordType getEmfRecordType() {
|
||||
return HemfRecordType.setViewportOrgEx;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
|
||||
return readPointL(leis, origin);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The EMR_OFFSETCLIPRGN record moves the current clipping region in the playback device context
|
||||
* by the specified offsets.
|
||||
*/
|
||||
public static class EmfSetOffsetClipRgn extends HwmfWindowing.WmfOffsetClipRgn implements HemfRecord {
|
||||
@Override
|
||||
public HemfRecordType getEmfRecordType() {
|
||||
return HemfRecordType.setOffsetClipRgn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
|
||||
return readPointL(leis, offset);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The EMR_EXCLUDECLIPRECT record specifies a new clipping region that consists of the existing
|
||||
* clipping region minus the specified rectangle.
|
||||
*/
|
||||
public static class EmfSetExcludeClipRect extends HwmfWindowing.WmfExcludeClipRect implements HemfRecord {
|
||||
@Override
|
||||
public HemfRecordType getEmfRecordType() {
|
||||
return HemfRecordType.setExcludeClipRect;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
|
||||
return HemfDraw.readRectL(leis, bounds);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The EMR_INTERSECTCLIPRECT record specifies a new clipping region from the intersection of the
|
||||
* current clipping region and the specified rectangle.
|
||||
*/
|
||||
public static class EmfSetIntersectClipRect extends HwmfWindowing.WmfIntersectClipRect implements HemfRecord {
|
||||
@Override
|
||||
public HemfRecordType getEmfRecordType() {
|
||||
return HemfRecordType.setIntersectClipRect;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
|
||||
return HemfDraw.readRectL(leis, normalizeBounds(bounds));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The EMR_SCALEVIEWPORTEXTEX record respecifies the viewport for a device context by using the
|
||||
* ratios formed by the specified multiplicands and divisors.
|
||||
*/
|
||||
public static class EmfScaleViewportExtEx extends HwmfWindowing.WmfScaleViewportExt implements HemfRecord {
|
||||
@Override
|
||||
public HemfRecordType getEmfRecordType() {
|
||||
return HemfRecordType.scaleViewportExtEx;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
|
||||
double xNum = leis.readInt();
|
||||
double xDenom = leis.readInt();
|
||||
double yNum = leis.readInt();
|
||||
double yDenom = leis.readInt();
|
||||
scale.setSize(xNum / xDenom, yNum / yDenom);
|
||||
return 4*LittleEndianConsts.INT_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The EMR_SCALEWINDOWEXTEX record respecifies the window for a playback device context by
|
||||
* using the ratios formed by the specified multiplicands and divisors.
|
||||
*/
|
||||
public static class EmfScaleWindowExtEx extends HwmfWindowing.WmfScaleWindowExt implements HemfRecord {
|
||||
@Override
|
||||
public HemfRecordType getEmfRecordType() {
|
||||
return HemfRecordType.scaleWindowExtEx;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
|
||||
double xNum = leis.readInt();
|
||||
double xDenom = leis.readInt();
|
||||
double yNum = leis.readInt();
|
||||
double yDenom = leis.readInt();
|
||||
|
||||
scale.setSize(xNum / xDenom, yNum / yDenom);
|
||||
|
||||
return 4*LittleEndianConsts.INT_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The EMR_SELECTCLIPPATH record specifies the current path as a clipping region for a playback
|
||||
* device context, combining the new region with any existing clipping region using the specified mode.
|
||||
*/
|
||||
public static class EmfSelectClipPath implements HemfRecord {
|
||||
protected HwmfRegionMode regionMode;
|
||||
|
||||
@Override
|
||||
public HemfRecordType getEmfRecordType() {
|
||||
return HemfRecordType.selectClipPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
|
||||
// A 32-bit unsigned integer that specifies the way to use the path.
|
||||
// The value MUST be in the RegionMode enumeration
|
||||
regionMode = HwmfRegionMode.valueOf(leis.readInt());
|
||||
|
||||
return LittleEndianConsts.INT_SIZE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(HemfGraphics ctx) {
|
||||
HemfDrawProperties prop = ctx.getProperties();
|
||||
ctx.setClip(prop.getPath(), regionMode, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{ regionMode: '"+regionMode+"' }";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -15,7 +15,7 @@
|
|||
limitations under the License.
|
||||
==================================================================== */
|
||||
|
||||
package org.apache.poi.hemf.record;
|
||||
package org.apache.poi.hemf.record.emf;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -27,19 +27,16 @@ import org.apache.poi.util.LittleEndianInputStream;
|
|||
@Internal
|
||||
public class UnimplementedHemfRecord implements HemfRecord {
|
||||
|
||||
private long recordId;
|
||||
public UnimplementedHemfRecord() {
|
||||
private HemfRecordType recordType;
|
||||
|
||||
@Override
|
||||
public HemfRecordType getEmfRecordType() {
|
||||
return recordType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HemfRecordType getRecordType() {
|
||||
return HemfRecordType.getById(recordId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long init(LittleEndianInputStream leis, long recordId, long recordSize) throws IOException {
|
||||
this.recordId = recordId;
|
||||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException {
|
||||
recordType = HemfRecordType.getById(recordId);
|
||||
long skipped = IOUtils.skipFully(leis, recordSize);
|
||||
if (skipped < recordSize) {
|
||||
throw new IOException("End of stream reached before record read");
|
|
@ -15,13 +15,14 @@
|
|||
limitations under the License.
|
||||
==================================================================== */
|
||||
|
||||
package org.apache.poi.hemf.hemfplus.record;
|
||||
package org.apache.poi.hemf.record.emfplus;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.poi.util.Internal;
|
||||
import org.apache.poi.util.LittleEndian;
|
||||
import org.apache.poi.util.LittleEndianConsts;
|
||||
import org.apache.poi.util.LittleEndianInputStream;
|
||||
|
||||
@Internal
|
||||
public class HemfPlusHeader implements HemfPlusRecord {
|
||||
|
@ -33,7 +34,7 @@ public class HemfPlusHeader implements HemfPlusRecord {
|
|||
private long logicalDpiY;
|
||||
|
||||
@Override
|
||||
public HemfPlusRecordType getRecordType() {
|
||||
public HemfPlusRecordType getEmfPlusRecordType() {
|
||||
return HemfPlusRecordType.header;
|
||||
}
|
||||
|
||||
|
@ -42,15 +43,19 @@ public class HemfPlusHeader implements HemfPlusRecord {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void init(byte[] dataBytes, int recordId, int flags) throws IOException {
|
||||
//assert record id == header
|
||||
public long init(LittleEndianInputStream leis, long dataSize, long recordId, int flags) throws IOException {
|
||||
this.flags = flags;
|
||||
int offset = 0;
|
||||
this.version = LittleEndian.getUInt(dataBytes, offset); offset += LittleEndian.INT_SIZE;
|
||||
this.emfPlusFlags = LittleEndian.getUInt(dataBytes, offset); offset += LittleEndian.INT_SIZE;
|
||||
this.logicalDpiX = LittleEndian.getUInt(dataBytes, offset); offset += LittleEndian.INT_SIZE;
|
||||
this.logicalDpiY = LittleEndian.getUInt(dataBytes, offset);
|
||||
version = leis.readUInt();
|
||||
|
||||
// verify MetafileSignature (20 bits) == 0xDBC01 and
|
||||
// GraphicsVersion (12 bits) in (1 or 2)
|
||||
assert((version & 0xFFFFFA00) == 0xDBC01000L && ((version & 0x3FF) == 1 || (version & 0x3FF) == 2));
|
||||
|
||||
emfPlusFlags = leis.readUInt();
|
||||
|
||||
logicalDpiX = leis.readUInt();
|
||||
logicalDpiY = leis.readUInt();
|
||||
return 4* LittleEndianConsts.INT_SIZE;
|
||||
}
|
||||
|
||||
public long getVersion() {
|
||||
|
@ -79,4 +84,4 @@ public class HemfPlusHeader implements HemfPlusRecord {
|
|||
", logicalDpiY=" + logicalDpiY +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,27 +15,34 @@
|
|||
limitations under the License.
|
||||
==================================================================== */
|
||||
|
||||
package org.apache.poi.hemf.record;
|
||||
package org.apache.poi.hemf.record.emfplus;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.poi.hemf.record.emf.HemfRecordType;
|
||||
import org.apache.poi.util.Internal;
|
||||
import org.apache.poi.util.LittleEndianInputStream;
|
||||
|
||||
@Internal
|
||||
public interface HemfRecord {
|
||||
public interface HemfPlusRecord {
|
||||
|
||||
HemfRecordType getRecordType();
|
||||
HemfPlusRecordType getEmfPlusRecordType();
|
||||
|
||||
int getFlags();
|
||||
|
||||
/**
|
||||
* Init record from stream
|
||||
*
|
||||
* @param leis the little endian input stream
|
||||
* @param dataSize the size limit for this record
|
||||
* @param recordId the id of the {@link HemfPlusRecordType}
|
||||
* @param flags the record flags
|
||||
*
|
||||
* @return count of processed bytes
|
||||
* @throws IOException
|
||||
*
|
||||
* @throws IOException when the inputstream is malformed
|
||||
*/
|
||||
long init(LittleEndianInputStream leis, long recordId, long recordSize) throws IOException;
|
||||
|
||||
long init(LittleEndianInputStream leis, long dataSize, long recordId, int flags) throws IOException;
|
||||
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
==================================================================== */
|
||||
|
||||
package org.apache.poi.hemf.record.emfplus;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.apache.poi.util.LittleEndianInputStream;
|
||||
import org.apache.poi.util.RecordFormatException;
|
||||
|
||||
public class HemfPlusRecordIterator implements Iterator<HemfPlusRecord> {
|
||||
|
||||
private final LittleEndianInputStream leis;
|
||||
private final int startIdx;
|
||||
private final int limit;
|
||||
private HemfPlusRecord currentRecord;
|
||||
|
||||
public HemfPlusRecordIterator(LittleEndianInputStream leis) {
|
||||
this(leis, -1);
|
||||
}
|
||||
|
||||
public HemfPlusRecordIterator(LittleEndianInputStream leis, int limit) {
|
||||
this.leis = leis;
|
||||
this.limit = limit;
|
||||
startIdx = leis.getReadIndex();
|
||||
//queue the first non-header record
|
||||
currentRecord = _next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return currentRecord != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HemfPlusRecord next() {
|
||||
HemfPlusRecord toReturn = currentRecord;
|
||||
final boolean isEOF = (limit == -1 || leis.getReadIndex()-startIdx >= limit);
|
||||
// (currentRecord instanceof HemfPlusMisc.EmfEof)
|
||||
currentRecord = isEOF ? null : _next();
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
private HemfPlusRecord _next() {
|
||||
if (currentRecord != null && HemfPlusRecordType.eof == currentRecord.getEmfPlusRecordType()) {
|
||||
return null;
|
||||
}
|
||||
// A 16-bit unsigned integer that identifies this record type
|
||||
int recordId = leis.readUShort();
|
||||
// A 16-bit unsigned integer that provides information about how the operation is
|
||||
// to be performed, and about the structure of the record.
|
||||
int flags = leis.readUShort();
|
||||
// A 32-bit unsigned integer that specifies the 32-bit-aligned size of the entire
|
||||
// record in bytes, including the 12-byte record header and record-specific data.
|
||||
int recordSize = (int)leis.readUInt();
|
||||
// A 32-bit unsigned integer that specifies the 32-bit-aligned number of bytes of data
|
||||
// in the record-specific data that follows. This number does not include the size of
|
||||
// the invariant part of this record.
|
||||
int dataSize = (int)leis.readUInt();
|
||||
|
||||
HemfPlusRecordType type = HemfPlusRecordType.getById(recordId);
|
||||
if (type == null) {
|
||||
throw new RecordFormatException("Undefined record of type:"+recordId);
|
||||
}
|
||||
final HemfPlusRecord record = type.constructor.get();
|
||||
|
||||
try {
|
||||
long readBytes = record.init(leis, dataSize, recordId, flags);
|
||||
assert (readBytes <= recordSize-12);
|
||||
leis.skipFully((int)(recordSize-12-readBytes));
|
||||
} catch (IOException e) {
|
||||
throw new RecordFormatException(e);
|
||||
}
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException("Remove not supported");
|
||||
}
|
||||
|
||||
}
|
|
@ -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,23 +15,27 @@
|
|||
limitations under the License.
|
||||
==================================================================== */
|
||||
|
||||
package org.apache.poi.hemf.hemfplus.record;
|
||||
package org.apache.poi.hemf.record.emfplus;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.poi.util.IOUtils;
|
||||
import org.apache.poi.util.Internal;
|
||||
import org.apache.poi.util.LittleEndianInputStream;
|
||||
|
||||
@Internal
|
||||
public class UnimplementedHemfPlusRecord implements HemfPlusRecord {
|
||||
|
||||
private int recordId;
|
||||
private static final int MAX_RECORD_LENGTH = 1_000_000;
|
||||
|
||||
private HemfPlusRecordType recordType;
|
||||
private int flags;
|
||||
private byte[] recordBytes;
|
||||
|
||||
@Override
|
||||
public HemfPlusRecordType getRecordType() {
|
||||
return HemfPlusRecordType.getById(recordId);
|
||||
public HemfPlusRecordType getEmfPlusRecordType() {
|
||||
return recordType;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -40,14 +44,16 @@ public class UnimplementedHemfPlusRecord implements HemfPlusRecord {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void init(byte[] recordBytes, int recordId, int flags) throws IOException {
|
||||
this.recordId = recordId;
|
||||
public long init(LittleEndianInputStream leis, long dataSize, long recordId, int flags) throws IOException {
|
||||
recordType = HemfPlusRecordType.getById(recordId);
|
||||
this.flags = flags;
|
||||
this.recordBytes = recordBytes;
|
||||
recordBytes = IOUtils.safelyAllocate(dataSize, MAX_RECORD_LENGTH);
|
||||
leis.readFully(recordBytes);
|
||||
return recordBytes.length;
|
||||
}
|
||||
|
||||
public byte[] getRecordBytes() {
|
||||
//should probably defensively return a copy.
|
||||
return recordBytes;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,161 @@
|
|||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
==================================================================== */
|
||||
|
||||
package org.apache.poi.hemf.usermodel;
|
||||
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.Dimension2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Spliterator;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.apache.poi.hemf.draw.HemfGraphics;
|
||||
import org.apache.poi.hemf.record.emf.HemfHeader;
|
||||
import org.apache.poi.hemf.record.emf.HemfRecord;
|
||||
import org.apache.poi.hemf.record.emf.HemfRecordIterator;
|
||||
import org.apache.poi.hemf.record.emf.HemfWindowing;
|
||||
import org.apache.poi.util.Dimension2DDouble;
|
||||
import org.apache.poi.util.Internal;
|
||||
import org.apache.poi.util.LittleEndianInputStream;
|
||||
import org.apache.poi.util.Units;
|
||||
|
||||
/**
|
||||
* Read-only EMF extractor. Lots remain
|
||||
*/
|
||||
@Internal
|
||||
public class HemfPicture implements Iterable<HemfRecord> {
|
||||
|
||||
private final LittleEndianInputStream stream;
|
||||
private final List<HemfRecord> records = new ArrayList<>();
|
||||
private boolean isParsed = false;
|
||||
|
||||
public HemfPicture(InputStream is) throws IOException {
|
||||
this(new LittleEndianInputStream(is));
|
||||
}
|
||||
|
||||
public HemfPicture(LittleEndianInputStream is) throws IOException {
|
||||
stream = is;
|
||||
}
|
||||
|
||||
public HemfHeader getHeader() {
|
||||
return (HemfHeader)getRecords().get(0);
|
||||
}
|
||||
|
||||
public List<HemfRecord> getRecords() {
|
||||
if (!isParsed) {
|
||||
// in case the (first) parsing throws an exception, we can provide the
|
||||
// records up to that point
|
||||
isParsed = true;
|
||||
HemfHeader[] header = new HemfHeader[1];
|
||||
new HemfRecordIterator(stream).forEachRemaining(r -> {
|
||||
if (r instanceof HemfHeader) {
|
||||
header[0] = (HemfHeader) r;
|
||||
}
|
||||
r.setHeader(header[0]);
|
||||
records.add(r);
|
||||
});
|
||||
}
|
||||
return records;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<HemfRecord> iterator() {
|
||||
return getRecords().iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Spliterator<HemfRecord> spliterator() {
|
||||
return getRecords().spliterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forEach(Consumer<? super HemfRecord> action) {
|
||||
getRecords().forEach(action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the image size in points
|
||||
*
|
||||
* @return the image size in points
|
||||
*/
|
||||
public Dimension2D getSize() {
|
||||
HemfHeader header = (HemfHeader)getRecords().get(0);
|
||||
final double coeff = (double) Units.EMU_PER_CENTIMETER / Units.EMU_PER_POINT / 10.;
|
||||
Rectangle2D dim = header.getFrameRectangle();
|
||||
double width = dim.getWidth(), height = dim.getHeight();
|
||||
if (dim.isEmpty() || Math.rint(width*coeff) == 0 || Math.rint(height*coeff) == 0) {
|
||||
for (HemfRecord r : getRecords()) {
|
||||
if (r instanceof HemfWindowing.EmfSetWindowExtEx) {
|
||||
Dimension2D d = ((HemfWindowing.EmfSetWindowExtEx)r).getSize();
|
||||
width = d.getWidth();
|
||||
height = d.getHeight();
|
||||
// keep searching - sometimes there's another record
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Math.rint(width*coeff) == 0 || Math.rint(height*coeff) == 0) {
|
||||
width = 100;
|
||||
height = 100;
|
||||
}
|
||||
|
||||
return new Dimension2DDouble(Math.abs(width*coeff), Math.abs(height*coeff));
|
||||
}
|
||||
|
||||
private static double minX(Rectangle2D bounds) {
|
||||
return Math.min(bounds.getMinX(), bounds.getMaxX());
|
||||
}
|
||||
|
||||
private static double minY(Rectangle2D bounds) {
|
||||
return Math.min(bounds.getMinY(), bounds.getMaxY());
|
||||
}
|
||||
|
||||
public void draw(Graphics2D ctx, Rectangle2D graphicsBounds) {
|
||||
HemfHeader header = (HemfHeader)getRecords().get(0);
|
||||
|
||||
AffineTransform at = ctx.getTransform();
|
||||
try {
|
||||
Rectangle2D emfBounds = header.getBoundsRectangle();
|
||||
|
||||
// scale output bounds to image bounds
|
||||
ctx.translate(minX(graphicsBounds), minY(graphicsBounds));
|
||||
ctx.scale(graphicsBounds.getWidth()/emfBounds.getWidth(), graphicsBounds.getHeight()/emfBounds.getHeight());
|
||||
ctx.translate(-minX(emfBounds), -minY(emfBounds));
|
||||
|
||||
int idx = 0;
|
||||
HemfGraphics g = new HemfGraphics(ctx, emfBounds);
|
||||
for (HemfRecord r : getRecords()) {
|
||||
try {
|
||||
g.draw(r);
|
||||
} catch (RuntimeException ignored) {
|
||||
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
} finally {
|
||||
ctx.setTransform(at);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -19,7 +19,9 @@ package org.apache.poi.hwmf.draw;
|
|||
|
||||
import java.awt.Color;
|
||||
import java.awt.Shape;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.Area;
|
||||
import java.awt.geom.Path2D;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
@ -36,6 +38,7 @@ import org.apache.poi.hwmf.record.HwmfMapMode;
|
|||
import org.apache.poi.hwmf.record.HwmfMisc.WmfSetBkMode.HwmfBkMode;
|
||||
import org.apache.poi.hwmf.record.HwmfPalette.PaletteEntry;
|
||||
import org.apache.poi.hwmf.record.HwmfPenStyle;
|
||||
import org.apache.poi.hwmf.record.HwmfTernaryRasterOp;
|
||||
import org.apache.poi.hwmf.record.HwmfText.HwmfTextAlignment;
|
||||
import org.apache.poi.hwmf.record.HwmfText.HwmfTextVerticalAlignment;
|
||||
|
||||
|
@ -43,36 +46,59 @@ public class HwmfDrawProperties {
|
|||
private final Rectangle2D window;
|
||||
private Rectangle2D viewport;
|
||||
private final Point2D location;
|
||||
private HwmfMapMode mapMode = HwmfMapMode.MM_ANISOTROPIC;
|
||||
private HwmfColorRef backgroundColor = new HwmfColorRef(Color.BLACK);
|
||||
private HwmfBrushStyle brushStyle = HwmfBrushStyle.BS_SOLID;
|
||||
private HwmfColorRef brushColor = new HwmfColorRef(Color.BLACK);
|
||||
private HwmfHatchStyle brushHatch = HwmfHatchStyle.HS_HORIZONTAL;
|
||||
private HwmfMapMode mapMode;
|
||||
private HwmfColorRef backgroundColor;
|
||||
private HwmfBrushStyle brushStyle;
|
||||
private HwmfColorRef brushColor;
|
||||
private HwmfHatchStyle brushHatch;
|
||||
private BufferedImage brushBitmap;
|
||||
private double penWidth = 1;
|
||||
private HwmfPenStyle penStyle = HwmfPenStyle.valueOf(0);
|
||||
private HwmfColorRef penColor = new HwmfColorRef(Color.BLACK);
|
||||
private double penMiterLimit = 10;
|
||||
private HwmfBkMode bkMode = HwmfBkMode.OPAQUE;
|
||||
private HwmfPolyfillMode polyfillMode = HwmfPolyfillMode.WINDING;
|
||||
private double penWidth;
|
||||
private HwmfPenStyle penStyle;
|
||||
private HwmfColorRef penColor;
|
||||
private double penMiterLimit;
|
||||
private HwmfBkMode bkMode;
|
||||
private HwmfPolyfillMode polyfillMode;
|
||||
private Shape region;
|
||||
private List<PaletteEntry> palette;
|
||||
private int paletteOffset;
|
||||
private HwmfFont font;
|
||||
private HwmfColorRef textColor = new HwmfColorRef(Color.BLACK);
|
||||
private HwmfTextAlignment textAlignLatin = HwmfTextAlignment.LEFT;
|
||||
private HwmfTextVerticalAlignment textVAlignLatin = HwmfTextVerticalAlignment.TOP;
|
||||
private HwmfTextAlignment textAlignAsian = HwmfTextAlignment.RIGHT;
|
||||
private HwmfTextVerticalAlignment textVAlignAsian = HwmfTextVerticalAlignment.TOP;
|
||||
private HwmfColorRef textColor;
|
||||
private HwmfTextAlignment textAlignLatin;
|
||||
private HwmfTextVerticalAlignment textVAlignLatin;
|
||||
private HwmfTextAlignment textAlignAsian;
|
||||
private HwmfTextVerticalAlignment textVAlignAsian;
|
||||
private HwmfTernaryRasterOp rasterOp;
|
||||
protected Shape clip;
|
||||
protected final AffineTransform transform = new AffineTransform();
|
||||
|
||||
public HwmfDrawProperties() {
|
||||
window = new Rectangle2D.Double(0, 0, 1, 1);
|
||||
viewport = null;
|
||||
location = new Point2D.Double(0,0);
|
||||
mapMode = HwmfMapMode.MM_ANISOTROPIC;
|
||||
backgroundColor = new HwmfColorRef(Color.BLACK);
|
||||
brushStyle = HwmfBrushStyle.BS_SOLID;
|
||||
brushColor = new HwmfColorRef(Color.BLACK);
|
||||
brushHatch = HwmfHatchStyle.HS_HORIZONTAL;
|
||||
penWidth = 1;
|
||||
penStyle = HwmfPenStyle.valueOf(0);
|
||||
penColor = new HwmfColorRef(Color.BLACK);
|
||||
penMiterLimit = 10;
|
||||
bkMode = HwmfBkMode.OPAQUE;
|
||||
polyfillMode = HwmfPolyfillMode.WINDING;
|
||||
textColor = new HwmfColorRef(Color.BLACK);
|
||||
textAlignLatin = HwmfTextAlignment.LEFT;
|
||||
textVAlignLatin = HwmfTextVerticalAlignment.TOP;
|
||||
textAlignAsian = HwmfTextAlignment.RIGHT;
|
||||
textVAlignAsian = HwmfTextVerticalAlignment.TOP;
|
||||
rasterOp = HwmfTernaryRasterOp.PATCOPY;
|
||||
clip = null;
|
||||
font = new HwmfFont();
|
||||
font.initDefaults();
|
||||
}
|
||||
|
||||
public HwmfDrawProperties(HwmfDrawProperties other) {
|
||||
this.window = (Rectangle2D)other.window.clone();
|
||||
this.window = (other.window == null) ? null : (Rectangle2D)other.window.clone();
|
||||
this.viewport = (other.viewport == null) ? null : (Rectangle2D)other.viewport.clone();
|
||||
this.location = (Point2D)other.location.clone();
|
||||
this.mapMode = other.mapMode;
|
||||
|
@ -86,7 +112,7 @@ public class HwmfDrawProperties {
|
|||
WritableRaster raster = other.brushBitmap.copyData(null);
|
||||
this.brushBitmap = new BufferedImage(cm, raster, isAlphaPremultiplied, null);
|
||||
}
|
||||
this.penWidth = 1;
|
||||
this.penWidth = other.penWidth;
|
||||
this.penStyle = (other.penStyle == null) ? null : other.penStyle.clone();
|
||||
this.penColor = (other.penColor == null) ? null : other.penColor.clone();
|
||||
this.penMiterLimit = other.penMiterLimit;
|
||||
|
@ -101,6 +127,13 @@ public class HwmfDrawProperties {
|
|||
this.paletteOffset = other.paletteOffset;
|
||||
this.font = other.font;
|
||||
this.textColor = (other.textColor == null) ? null : other.textColor.clone();
|
||||
this.textAlignLatin = other.textAlignLatin;
|
||||
this.textVAlignLatin = other.textVAlignLatin;
|
||||
this.textAlignAsian = other.textAlignAsian;
|
||||
this.textVAlignAsian = other.textVAlignAsian;
|
||||
this.rasterOp = other.rasterOp;
|
||||
this.transform.setTransform(other.transform);
|
||||
this.clip = other.clip;
|
||||
}
|
||||
|
||||
public void setViewportExt(double width, double height) {
|
||||
|
@ -149,6 +182,10 @@ public class HwmfDrawProperties {
|
|||
location.setLocation(x, y);
|
||||
}
|
||||
|
||||
public void setLocation(Point2D point) {
|
||||
location.setLocation(point);
|
||||
}
|
||||
|
||||
public Point2D getLocation() {
|
||||
return (Point2D)location.clone();
|
||||
}
|
||||
|
@ -343,4 +380,35 @@ public class HwmfDrawProperties {
|
|||
public void setTextVAlignAsian(HwmfTextVerticalAlignment textVAlignAsian) {
|
||||
this.textVAlignAsian = textVAlignAsian;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the current active winding rule ({@link Path2D#WIND_EVEN_ODD} or {@link Path2D#WIND_NON_ZERO})
|
||||
*/
|
||||
public int getWindingRule() {
|
||||
return getPolyfillMode().awtFlag;
|
||||
}
|
||||
|
||||
public HwmfTernaryRasterOp getRasterOp() {
|
||||
return rasterOp;
|
||||
}
|
||||
|
||||
public void setRasterOp(HwmfTernaryRasterOp rasterOp) {
|
||||
this.rasterOp = rasterOp;
|
||||
}
|
||||
|
||||
public AffineTransform getTransform() {
|
||||
return transform;
|
||||
}
|
||||
|
||||
public void setTransform(AffineTransform transform) {
|
||||
this.transform.setTransform(transform);
|
||||
}
|
||||
|
||||
public Shape getClip() {
|
||||
return clip;
|
||||
}
|
||||
|
||||
public void setClip(Shape clip) {
|
||||
this.clip = clip;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,18 +25,26 @@ import java.awt.Paint;
|
|||
import java.awt.Rectangle;
|
||||
import java.awt.Shape;
|
||||
import java.awt.TexturePaint;
|
||||
import java.awt.font.FontRenderContext;
|
||||
import java.awt.font.TextAttribute;
|
||||
import java.awt.font.TextLayout;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.Area;
|
||||
import java.awt.geom.Dimension2D;
|
||||
import java.awt.geom.NoninvertibleTransformException;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.nio.charset.Charset;
|
||||
import java.text.AttributedString;
|
||||
import java.util.ArrayList;
|
||||
import java.util.BitSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.apache.commons.codec.Charsets;
|
||||
import org.apache.poi.common.usermodel.fonts.FontCharset;
|
||||
import org.apache.poi.common.usermodel.fonts.FontInfo;
|
||||
import org.apache.poi.hwmf.record.HwmfBrushStyle;
|
||||
import org.apache.poi.hwmf.record.HwmfFont;
|
||||
|
@ -46,20 +54,31 @@ import org.apache.poi.hwmf.record.HwmfMisc.WmfSetBkMode.HwmfBkMode;
|
|||
import org.apache.poi.hwmf.record.HwmfObjectTableEntry;
|
||||
import org.apache.poi.hwmf.record.HwmfPenStyle;
|
||||
import org.apache.poi.hwmf.record.HwmfPenStyle.HwmfLineDash;
|
||||
import org.apache.poi.hwmf.record.HwmfRegionMode;
|
||||
import org.apache.poi.hwmf.record.HwmfText;
|
||||
import org.apache.poi.hwmf.record.HwmfText.WmfExtTextOutOptions;
|
||||
import org.apache.poi.sl.draw.DrawFactory;
|
||||
import org.apache.poi.sl.draw.DrawFontManager;
|
||||
import org.apache.poi.sl.draw.DrawFontManagerDefault;
|
||||
import org.apache.poi.util.LocaleUtil;
|
||||
|
||||
public class HwmfGraphics {
|
||||
|
||||
public enum FillDrawStyle {
|
||||
NONE, FILL, DRAW, FILL_DRAW
|
||||
}
|
||||
|
||||
protected final List<HwmfDrawProperties> propStack = new LinkedList<>();
|
||||
protected HwmfDrawProperties prop;
|
||||
protected final Graphics2D graphicsCtx;
|
||||
protected final BitSet objectIndexes = new BitSet();
|
||||
protected final TreeMap<Integer,HwmfObjectTableEntry> objectTable = new TreeMap<>();
|
||||
protected final AffineTransform initialAT = new AffineTransform();
|
||||
|
||||
|
||||
private static final Charset DEFAULT_CHARSET = LocaleUtil.CHARSET_1252;
|
||||
private final Graphics2D graphicsCtx;
|
||||
private final List<HwmfDrawProperties> propStack = new LinkedList<>();
|
||||
private HwmfDrawProperties prop = new HwmfDrawProperties();
|
||||
private List<HwmfObjectTableEntry> objectTable = new ArrayList<>();
|
||||
/** Bounding box from the placeable header */
|
||||
/** Bounding box from the placeable header */
|
||||
private final Rectangle2D bbox;
|
||||
private final AffineTransform initialAT;
|
||||
|
||||
/**
|
||||
* Initialize a graphics context for wmf rendering
|
||||
|
@ -70,16 +89,26 @@ public class HwmfGraphics {
|
|||
public HwmfGraphics(Graphics2D graphicsCtx, Rectangle2D bbox) {
|
||||
this.graphicsCtx = graphicsCtx;
|
||||
this.bbox = (Rectangle2D)bbox.clone();
|
||||
this.initialAT = graphicsCtx.getTransform();
|
||||
DrawFactory.getInstance(graphicsCtx).fixFonts(graphicsCtx);
|
||||
this.initialAT.setTransform(graphicsCtx.getTransform());
|
||||
}
|
||||
|
||||
public HwmfDrawProperties getProperties() {
|
||||
if (prop == null) {
|
||||
prop = newProperties(null);
|
||||
}
|
||||
return prop;
|
||||
}
|
||||
|
||||
protected HwmfDrawProperties newProperties(HwmfDrawProperties oldProps) {
|
||||
return (oldProps == null) ? new HwmfDrawProperties() : new HwmfDrawProperties(oldProps);
|
||||
}
|
||||
|
||||
public void draw(Shape shape) {
|
||||
HwmfLineDash lineDash = prop.getPenStyle().getLineDash();
|
||||
HwmfPenStyle ps = getProperties().getPenStyle();
|
||||
if (ps == null) {
|
||||
return;
|
||||
}
|
||||
HwmfLineDash lineDash = ps.getLineDash();
|
||||
if (lineDash == HwmfLineDash.NULL) {
|
||||
// line is not drawn
|
||||
return;
|
||||
|
@ -89,40 +118,44 @@ public class HwmfGraphics {
|
|||
|
||||
// first draw a solid background line (depending on bkmode)
|
||||
// only makes sense if the line is not solid
|
||||
if (prop.getBkMode() == HwmfBkMode.OPAQUE && (lineDash != HwmfLineDash.SOLID && lineDash != HwmfLineDash.INSIDEFRAME)) {
|
||||
if (getProperties().getBkMode() == HwmfBkMode.OPAQUE && (lineDash != HwmfLineDash.SOLID && lineDash != HwmfLineDash.INSIDEFRAME)) {
|
||||
graphicsCtx.setStroke(new BasicStroke(stroke.getLineWidth()));
|
||||
graphicsCtx.setColor(prop.getBackgroundColor().getColor());
|
||||
graphicsCtx.setColor(getProperties().getBackgroundColor().getColor());
|
||||
graphicsCtx.draw(shape);
|
||||
}
|
||||
|
||||
// then draw the (dashed) line
|
||||
graphicsCtx.setStroke(stroke);
|
||||
graphicsCtx.setColor(prop.getPenColor().getColor());
|
||||
graphicsCtx.setColor(getProperties().getPenColor().getColor());
|
||||
graphicsCtx.draw(shape);
|
||||
}
|
||||
|
||||
public void fill(Shape shape) {
|
||||
HwmfDrawProperties prop = getProperties();
|
||||
if (prop.getBrushStyle() != HwmfBrushStyle.BS_NULL) {
|
||||
// GeneralPath gp = new GeneralPath(shape);
|
||||
// gp.setWindingRule(prop.getPolyfillMode().awtFlag);
|
||||
if (prop.getBkMode() == HwmfBkMode.OPAQUE) {
|
||||
graphicsCtx.setPaint(prop.getBackgroundColor().getColor());
|
||||
graphicsCtx.fill(shape);
|
||||
}
|
||||
|
||||
graphicsCtx.setPaint(getFill());
|
||||
graphicsCtx.fill(shape);
|
||||
}
|
||||
|
||||
draw(shape);
|
||||
// draw(shape);
|
||||
}
|
||||
|
||||
protected BasicStroke getStroke() {
|
||||
// TODO: fix line width calculation
|
||||
float width = (float)prop.getPenWidth();
|
||||
float width = (float)getProperties().getPenWidth();
|
||||
if (width == 0) {
|
||||
width = 1;
|
||||
}
|
||||
HwmfPenStyle ps = prop.getPenStyle();
|
||||
HwmfPenStyle ps = getProperties().getPenStyle();
|
||||
int cap = ps.getLineCap().awtFlag;
|
||||
int join = ps.getLineJoin().awtFlag;
|
||||
float miterLimit = (float)prop.getPenMiterLimit();
|
||||
float dashes[] = ps.getLineDash().dashes;
|
||||
float miterLimit = (float)getProperties().getPenMiterLimit();
|
||||
float dashes[] = ps.getLineDashes();
|
||||
boolean dashAlt = ps.isAlternateDash();
|
||||
// This value is not an integer index into the dash pattern array.
|
||||
// Instead, it is a floating-point value that specifies a linear distance.
|
||||
|
@ -132,7 +165,7 @@ public class HwmfGraphics {
|
|||
}
|
||||
|
||||
protected Paint getFill() {
|
||||
switch (prop.getBrushStyle()) {
|
||||
switch (getProperties().getBrushStyle()) {
|
||||
default:
|
||||
case BS_INDEXED:
|
||||
case BS_PATTERN8X8:
|
||||
|
@ -148,20 +181,20 @@ public class HwmfGraphics {
|
|||
}
|
||||
|
||||
protected Paint getSolidFill() {
|
||||
return prop.getBrushColor().getColor();
|
||||
return getProperties().getBrushColor().getColor();
|
||||
}
|
||||
|
||||
protected Paint getHatchedFill() {
|
||||
int dim = 7, mid = 3;
|
||||
BufferedImage bi = new BufferedImage(dim, dim, BufferedImage.TYPE_4BYTE_ABGR);
|
||||
Graphics2D g = bi.createGraphics();
|
||||
Color c = (prop.getBkMode() == HwmfBkMode.TRANSPARENT)
|
||||
Color c = (getProperties().getBkMode() == HwmfBkMode.TRANSPARENT)
|
||||
? new Color(0, true)
|
||||
: prop.getBackgroundColor().getColor();
|
||||
: getProperties().getBackgroundColor().getColor();
|
||||
g.setColor(c);
|
||||
g.fillRect(0, 0, dim, dim);
|
||||
g.setColor(prop.getBrushColor().getColor());
|
||||
HwmfHatchStyle h = prop.getBrushHatch();
|
||||
g.setColor(getProperties().getBrushColor().getColor());
|
||||
HwmfHatchStyle h = getProperties().getBrushHatch();
|
||||
if (h == HwmfHatchStyle.HS_HORIZONTAL || h == HwmfHatchStyle.HS_CROSS) {
|
||||
g.drawLine(0, mid, dim, mid);
|
||||
}
|
||||
|
@ -174,12 +207,13 @@ public class HwmfGraphics {
|
|||
if (h == HwmfHatchStyle.HS_BDIAGONAL || h == HwmfHatchStyle.HS_DIAGCROSS) {
|
||||
g.drawLine(0, dim, dim, 0);
|
||||
}
|
||||
// TODO: handle new HS_* enumeration values
|
||||
g.dispose();
|
||||
return new TexturePaint(bi, new Rectangle(0,0,dim,dim));
|
||||
}
|
||||
|
||||
protected Paint getPatternPaint() {
|
||||
BufferedImage bi = prop.getBrushBitmap();
|
||||
BufferedImage bi = getProperties().getBrushBitmap();
|
||||
return (bi == null) ? null
|
||||
: new TexturePaint(bi, new Rectangle(0,0,bi.getWidth(),bi.getHeight()));
|
||||
}
|
||||
|
@ -196,15 +230,9 @@ public class HwmfGraphics {
|
|||
* @param entry
|
||||
*/
|
||||
public void addObjectTableEntry(HwmfObjectTableEntry entry) {
|
||||
ListIterator<HwmfObjectTableEntry> oIter = objectTable.listIterator();
|
||||
while (oIter.hasNext()) {
|
||||
HwmfObjectTableEntry tableEntry = oIter.next();
|
||||
if (tableEntry == null) {
|
||||
oIter.set(entry);
|
||||
return;
|
||||
}
|
||||
}
|
||||
objectTable.add(entry);
|
||||
int objIdx = objectIndexes.nextClearBit(0);
|
||||
objectIndexes.set(objIdx);
|
||||
objectTable.put(objIdx, entry);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -237,15 +265,25 @@ public class HwmfGraphics {
|
|||
* @throws IndexOutOfBoundsException if the index is out of range
|
||||
*/
|
||||
public void unsetObjectTableEntry(int index) {
|
||||
objectTable.set(index, null);
|
||||
if (index < 0) {
|
||||
throw new IndexOutOfBoundsException("Invalid index: "+index);
|
||||
}
|
||||
// sometime emfs remove object table entries before they set them
|
||||
// so ignore requests, if the table entry doesn't exist
|
||||
objectTable.remove(index);
|
||||
objectIndexes.clear(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the current properties to the stack
|
||||
*/
|
||||
public void saveProperties() {
|
||||
propStack.add(prop);
|
||||
prop = new HwmfDrawProperties(prop);
|
||||
final HwmfDrawProperties p = getProperties();
|
||||
assert(p != null);
|
||||
p.setTransform(graphicsCtx.getTransform());
|
||||
p.setClip(graphicsCtx.getClip());
|
||||
propStack.add(p);
|
||||
prop = newProperties(p);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -260,7 +298,7 @@ public class HwmfGraphics {
|
|||
}
|
||||
int stackIndex = index;
|
||||
if (stackIndex < 0) {
|
||||
int curIdx = propStack.indexOf(prop);
|
||||
int curIdx = propStack.indexOf(getProperties());
|
||||
if (curIdx == -1) {
|
||||
// the current element is not pushed to the stacked, i.e. it's the last
|
||||
curIdx = propStack.size();
|
||||
|
@ -271,7 +309,16 @@ public class HwmfGraphics {
|
|||
// roll to last when curIdx == 0
|
||||
stackIndex = propStack.size()-1;
|
||||
}
|
||||
prop = propStack.get(stackIndex);
|
||||
|
||||
// The playback device context is restored by popping state information off a stack that was created by
|
||||
// prior SAVEDC records
|
||||
// ... so because being a stack, we will remove all entries having a greater index than the stackIndex
|
||||
for (int i=propStack.size()-1; i>=stackIndex; i--) {
|
||||
prop = propStack.remove(i);
|
||||
}
|
||||
|
||||
graphicsCtx.setTransform(prop.getTransform());
|
||||
graphicsCtx.setClip(prop.getClip());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -280,16 +327,20 @@ public class HwmfGraphics {
|
|||
* This methods gathers and sets the corresponding graphics transformations.
|
||||
*/
|
||||
public void updateWindowMapMode() {
|
||||
Rectangle2D win = prop.getWindow();
|
||||
HwmfMapMode mapMode = prop.getMapMode();
|
||||
Rectangle2D win = getProperties().getWindow();
|
||||
Rectangle2D view = getProperties().getViewport();
|
||||
HwmfMapMode mapMode = getProperties().getMapMode();
|
||||
graphicsCtx.setTransform(initialAT);
|
||||
|
||||
switch (mapMode) {
|
||||
default:
|
||||
case MM_ANISOTROPIC:
|
||||
// scale window bounds to output bounds
|
||||
graphicsCtx.scale(bbox.getWidth()/win.getWidth(), bbox.getHeight()/win.getHeight());
|
||||
graphicsCtx.translate(-win.getX(), -win.getY());
|
||||
if (view != null) {
|
||||
graphicsCtx.translate(view.getCenterX(), view.getCenterY());
|
||||
graphicsCtx.scale(view.getWidth() / win.getWidth(), view.getHeight() / win.getHeight());
|
||||
graphicsCtx.translate(-win.getCenterX(), -win.getCenterY());
|
||||
}
|
||||
break;
|
||||
case MM_ISOTROPIC:
|
||||
// TODO: to be validated ...
|
||||
|
@ -315,11 +366,21 @@ public class HwmfGraphics {
|
|||
}
|
||||
}
|
||||
|
||||
public void drawString(byte[] text, Rectangle2D bounds) {
|
||||
drawString(text, bounds, null);
|
||||
public void drawString(byte[] text, int length, Point2D reference) {
|
||||
drawString(text, length, reference, null, null, null, null, false);
|
||||
}
|
||||
|
||||
public void drawString(byte[] text, Rectangle2D bounds, int dx[]) {
|
||||
public void drawString(byte[] text, int length, Point2D reference, Dimension2D scale, Rectangle2D clip, WmfExtTextOutOptions opts, List<Integer> dx, boolean isUnicode) {
|
||||
final HwmfDrawProperties prop = getProperties();
|
||||
|
||||
final AffineTransform at = graphicsCtx.getTransform();
|
||||
|
||||
try {
|
||||
at.createInverse();
|
||||
} catch (NoninvertibleTransformException e) {
|
||||
return;
|
||||
}
|
||||
|
||||
HwmfFont font = prop.getFont();
|
||||
if (font == null || text == null || text.length == 0) {
|
||||
return;
|
||||
|
@ -329,14 +390,34 @@ public class HwmfGraphics {
|
|||
// TODO: another approx. ...
|
||||
double fontW = fontH/1.8;
|
||||
|
||||
Charset charset = (font.getCharset().getCharset() == null)?
|
||||
DEFAULT_CHARSET : font.getCharset().getCharset();
|
||||
String textString = new String(text, charset);
|
||||
AttributedString as = new AttributedString(textString);
|
||||
if (dx == null || dx.length == 0) {
|
||||
addAttributes(as, font);
|
||||
Charset charset;
|
||||
if (isUnicode) {
|
||||
charset = Charsets.UTF_16LE;
|
||||
} else {
|
||||
int[] dxNormed = dx;
|
||||
charset = font.getCharset().getCharset();
|
||||
if (charset == null) {
|
||||
charset = DEFAULT_CHARSET;
|
||||
}
|
||||
}
|
||||
|
||||
String textString = new String(text, charset).substring(0,length).trim();
|
||||
|
||||
if (textString.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
DrawFontManager fontHandler = DrawFactory.getInstance(graphicsCtx).getFontManager(graphicsCtx);
|
||||
FontInfo fontInfo = fontHandler.getMappedFont(graphicsCtx, font);
|
||||
if (fontInfo.getCharset() == FontCharset.SYMBOL) {
|
||||
textString = DrawFontManagerDefault.mapSymbolChars(textString);
|
||||
}
|
||||
|
||||
AttributedString as = new AttributedString(textString);
|
||||
addAttributes(as, font, fontInfo.getTypeface());
|
||||
|
||||
// disabled for the time being, as the results aren't promising
|
||||
/*
|
||||
if (dx != null && !dx.isEmpty()) {
|
||||
//for multi-byte encodings (e.g. Shift_JIS), the byte length
|
||||
//might not equal the string length().
|
||||
//The x information is stored in dx[], an array parallel to the
|
||||
|
@ -351,60 +432,101 @@ public class HwmfGraphics {
|
|||
// needs to be remapped as:
|
||||
//dxNormed[0] = 13 textString.get(0) = U+30D7
|
||||
//dxNormed[1] = 14 textString.get(1) = U+30ED
|
||||
if (textString.length() != text.length) {
|
||||
int codePoints = textString.codePointCount(0, textString.length());
|
||||
dxNormed = new int[codePoints];
|
||||
int dxPosition = 0;
|
||||
for (int offset = 0; offset < textString.length(); ) {
|
||||
dxNormed[offset] = dx[dxPosition];
|
||||
int[] chars = new int[1];
|
||||
int cp = textString.codePointAt(offset);
|
||||
chars[0] = cp;
|
||||
//now figure out how many bytes it takes to encode that
|
||||
//code point in the charset
|
||||
int byteLength = new String(chars, 0, chars.length).getBytes(charset).length;
|
||||
dxPosition += byteLength;
|
||||
offset += Character.charCount(cp);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < dxNormed.length; i++) {
|
||||
addAttributes(as, font);
|
||||
// Tracking works as a prefix/advance space on characters whereas
|
||||
// dx[...] is the complete width of the current char
|
||||
// therefore we need to add the additional/suffix width to the next char
|
||||
if (i < dxNormed.length - 1) {
|
||||
as.addAttribute(TextAttribute.TRACKING, (dxNormed[i] - fontW) / fontH, i + 1, i + 2);
|
||||
|
||||
final int cps = textString.codePointCount(0, textString.length());
|
||||
final int unicodeSteps = Math.max(dx.size()/cps, 1);
|
||||
int dxPosition = 0, lastDxPosition = 0;
|
||||
int beginIndex = 0;
|
||||
while (beginIndex < textString.length() && dxPosition < dx.size()) {
|
||||
int endIndex = textString.offsetByCodePoints(beginIndex, 1);
|
||||
if (beginIndex > 0) {
|
||||
// Tracking works as a prefix/advance space on characters whereas
|
||||
// dx[...] is the complete width of the current char
|
||||
// therefore we need to add the additional/suffix width to the next char
|
||||
|
||||
as.addAttribute(TextAttribute.TRACKING, (float)((dx.get(lastDxPosition) - fontW) / fontH), beginIndex, endIndex);
|
||||
}
|
||||
lastDxPosition = dxPosition;
|
||||
dxPosition += (isUnicode) ? unicodeSteps : (endIndex-beginIndex);
|
||||
beginIndex = endIndex;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
*/
|
||||
|
||||
double angle = Math.toRadians(-font.getEscapement()/10.);
|
||||
|
||||
|
||||
final AffineTransform at = graphicsCtx.getTransform();
|
||||
|
||||
final HwmfText.HwmfTextAlignment align = prop.getTextAlignLatin();
|
||||
final HwmfText.HwmfTextVerticalAlignment valign = prop.getTextVAlignLatin();
|
||||
final FontRenderContext frc = graphicsCtx.getFontRenderContext();
|
||||
final TextLayout layout = new TextLayout(as.getIterator(), frc);
|
||||
|
||||
final Rectangle2D pixelBounds = layout.getBounds();
|
||||
|
||||
AffineTransform tx = new AffineTransform();
|
||||
switch (align) {
|
||||
default:
|
||||
case LEFT:
|
||||
break;
|
||||
case CENTER:
|
||||
tx.translate(-pixelBounds.getWidth() / 2., 0);
|
||||
break;
|
||||
case RIGHT:
|
||||
tx.translate(-pixelBounds.getWidth(), 0);
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO: check min/max orientation
|
||||
switch (valign) {
|
||||
case TOP:
|
||||
tx.translate(0, layout.getAscent());
|
||||
default:
|
||||
case BASELINE:
|
||||
break;
|
||||
case BOTTOM:
|
||||
tx.translate(0, -(pixelBounds.getHeight()-layout.getDescent()));
|
||||
break;
|
||||
}
|
||||
tx.rotate(angle);
|
||||
Point2D src = new Point2D.Double();
|
||||
Point2D dst = new Point2D.Double();
|
||||
tx.transform(src, dst);
|
||||
|
||||
final Shape clipShape = graphicsCtx.getClip();
|
||||
try {
|
||||
graphicsCtx.translate(bounds.getX(), bounds.getY()+fontH);
|
||||
graphicsCtx.rotate(angle);
|
||||
if (prop.getBkMode() == HwmfBkMode.OPAQUE) {
|
||||
// TODO: validate bounds
|
||||
graphicsCtx.setBackground(prop.getBackgroundColor().getColor());
|
||||
graphicsCtx.fill(new Rectangle2D.Double(0, 0, bounds.getWidth(), bounds.getHeight()));
|
||||
if (clip != null) {
|
||||
graphicsCtx.translate(-clip.getCenterX(), -clip.getCenterY());
|
||||
graphicsCtx.rotate(angle);
|
||||
graphicsCtx.translate(clip.getCenterX(), clip.getCenterY());
|
||||
if (prop.getBkMode() == HwmfBkMode.OPAQUE && opts.isOpaque()) {
|
||||
graphicsCtx.setPaint(prop.getBackgroundColor().getColor());
|
||||
graphicsCtx.fill(clip);
|
||||
}
|
||||
if (opts.isClipped()) {
|
||||
graphicsCtx.setClip(clip);
|
||||
}
|
||||
graphicsCtx.setTransform(at);
|
||||
}
|
||||
|
||||
graphicsCtx.translate(reference.getX(), reference.getY());
|
||||
graphicsCtx.rotate(angle);
|
||||
if (scale != null) {
|
||||
graphicsCtx.scale(scale.getWidth() < 0 ? -1 : 1, scale.getHeight() < 0 ? -1 : 1);
|
||||
}
|
||||
graphicsCtx.translate(dst.getX(), dst.getY());
|
||||
graphicsCtx.setColor(prop.getTextColor().getColor());
|
||||
graphicsCtx.drawString(as.getIterator(), 0, 0); // (float)bounds.getX(), (float)bounds.getY());
|
||||
graphicsCtx.drawString(as.getIterator(), 0, 0);
|
||||
} finally {
|
||||
graphicsCtx.setTransform(at);
|
||||
graphicsCtx.setClip(clipShape);
|
||||
}
|
||||
}
|
||||
|
||||
private void addAttributes(AttributedString as, HwmfFont font) {
|
||||
DrawFontManager fontHandler = DrawFactory.getInstance(graphicsCtx).getFontManager(graphicsCtx);
|
||||
FontInfo fontInfo = fontHandler.getMappedFont(graphicsCtx, font);
|
||||
|
||||
as.addAttribute(TextAttribute.FAMILY, fontInfo.getTypeface());
|
||||
|
||||
private void addAttributes(AttributedString as, HwmfFont font, String typeface) {
|
||||
as.addAttribute(TextAttribute.FAMILY, typeface);
|
||||
as.addAttribute(TextAttribute.SIZE, getFontHeight(font));
|
||||
as.addAttribute(TextAttribute.STRIKETHROUGH, font.isStrikeOut());
|
||||
if (font.isStrikeOut()) {
|
||||
as.addAttribute(TextAttribute.STRIKETHROUGH, TextAttribute.STRIKETHROUGH_ON);
|
||||
}
|
||||
if (font.isUnderline()) {
|
||||
as.addAttribute(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON);
|
||||
}
|
||||
|
@ -428,4 +550,138 @@ public class HwmfGraphics {
|
|||
return fontHeight*3/4;
|
||||
}
|
||||
}
|
||||
|
||||
public void drawImage(BufferedImage img, Rectangle2D srcBounds, Rectangle2D dstBounds) {
|
||||
HwmfDrawProperties prop = getProperties();
|
||||
|
||||
// handle raster op
|
||||
// currently the raster op as described in https://docs.microsoft.com/en-us/windows/desktop/gdi/ternary-raster-operations
|
||||
// are not supported, as we would need to extract the destination image area from the underlying buffered image
|
||||
// and therefore would make it mandatory that the graphics context must be from a buffered image
|
||||
// furthermore I doubt the purpose of bitwise image operations on non-black/white images
|
||||
switch (prop.getRasterOp()) {
|
||||
case D:
|
||||
// keep destination, i.e. do nothing
|
||||
break;
|
||||
case PATCOPY:
|
||||
graphicsCtx.setPaint(getFill());
|
||||
graphicsCtx.fill(dstBounds);
|
||||
break;
|
||||
case BLACKNESS:
|
||||
graphicsCtx.setPaint(Color.BLACK);
|
||||
graphicsCtx.fill(dstBounds);
|
||||
break;
|
||||
case WHITENESS:
|
||||
graphicsCtx.setPaint(Color.WHITE);
|
||||
graphicsCtx.fill(dstBounds);
|
||||
break;
|
||||
default:
|
||||
case SRCCOPY:
|
||||
final Shape clip = graphicsCtx.getClip();
|
||||
|
||||
// add clipping in case of a source subimage, i.e. a clipped source image
|
||||
// some dstBounds are horizontal or vertical flipped, so we need to normalize the images
|
||||
Rectangle2D normalized = new Rectangle2D.Double(
|
||||
dstBounds.getWidth() >= 0 ? dstBounds.getMinX() : dstBounds.getMaxX(),
|
||||
dstBounds.getHeight() >= 0 ? dstBounds.getMinY() : dstBounds.getMaxY(),
|
||||
Math.abs(dstBounds.getWidth()),
|
||||
Math.abs(dstBounds.getHeight()));
|
||||
graphicsCtx.clip(normalized);
|
||||
final AffineTransform at = graphicsCtx.getTransform();
|
||||
|
||||
final Rectangle2D imgBounds = new Rectangle2D.Double(0,0,img.getWidth(),img.getHeight());
|
||||
final boolean isImgBounds = (srcBounds.equals(new Rectangle2D.Double()));
|
||||
final Rectangle2D srcBounds2 = isImgBounds ? imgBounds : srcBounds;
|
||||
|
||||
// TODO: apply emf transform
|
||||
graphicsCtx.translate(dstBounds.getX(), dstBounds.getY());
|
||||
graphicsCtx.scale(dstBounds.getWidth()/srcBounds2.getWidth(), dstBounds.getHeight()/srcBounds2.getHeight());
|
||||
graphicsCtx.translate(-srcBounds2.getX(), -srcBounds2.getY());
|
||||
|
||||
graphicsCtx.drawImage(img, 0, 0, prop.getBackgroundColor().getColor(), null);
|
||||
|
||||
graphicsCtx.setTransform(at);
|
||||
graphicsCtx.setClip(clip);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the initial AffineTransform, when this graphics context was created
|
||||
*/
|
||||
public AffineTransform getInitTransform() {
|
||||
return new AffineTransform(initialAT);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the current AffineTransform
|
||||
*/
|
||||
public AffineTransform getTransform() {
|
||||
return new AffineTransform(graphicsCtx.getTransform());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current AffineTransform
|
||||
* @param tx the current AffineTransform
|
||||
*/
|
||||
public void setTransform(AffineTransform tx) {
|
||||
graphicsCtx.setTransform(tx);
|
||||
}
|
||||
|
||||
private static int clipCnt = 0;
|
||||
|
||||
public void setClip(Shape clip, HwmfRegionMode regionMode, boolean useInitialAT) {
|
||||
final AffineTransform at = graphicsCtx.getTransform();
|
||||
if (useInitialAT) {
|
||||
graphicsCtx.setTransform(initialAT);
|
||||
}
|
||||
final Shape oldClip = graphicsCtx.getClip();
|
||||
final boolean isEmpty = clip.getBounds2D().isEmpty();
|
||||
switch (regionMode) {
|
||||
case RGN_AND:
|
||||
if (!isEmpty) {
|
||||
graphicsCtx.clip(clip);
|
||||
}
|
||||
break;
|
||||
case RGN_OR:
|
||||
if (!isEmpty) {
|
||||
if (oldClip == null) {
|
||||
graphicsCtx.setClip(clip);
|
||||
} else {
|
||||
Area area = new Area(oldClip);
|
||||
area.add(new Area(clip));
|
||||
graphicsCtx.setClip(area);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case RGN_XOR:
|
||||
if (!isEmpty) {
|
||||
if (oldClip == null) {
|
||||
graphicsCtx.setClip(clip);
|
||||
} else {
|
||||
Area area = new Area(oldClip);
|
||||
area.exclusiveOr(new Area(clip));
|
||||
graphicsCtx.setClip(area);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case RGN_DIFF:
|
||||
if (!isEmpty) {
|
||||
if (oldClip != null) {
|
||||
Area area = new Area(oldClip);
|
||||
area.subtract(new Area(clip));
|
||||
graphicsCtx.setClip(area);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case RGN_COPY: {
|
||||
graphicsCtx.setClip(isEmpty ? null : clip);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (useInitialAT) {
|
||||
graphicsCtx.setTransform(at);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,20 +31,25 @@ import java.io.InputStream;
|
|||
import org.apache.poi.hwmf.usermodel.HwmfPicture;
|
||||
import org.apache.poi.sl.draw.DrawPictureShape;
|
||||
import org.apache.poi.sl.draw.ImageRenderer;
|
||||
import org.apache.poi.sl.usermodel.PictureData;
|
||||
import org.apache.poi.sl.usermodel.PictureData.PictureType;
|
||||
import org.apache.poi.util.Units;
|
||||
|
||||
/**
|
||||
* Helper class which is instantiated by {@link DrawPictureShape}
|
||||
* via reflection
|
||||
*/
|
||||
public class HwmfSLImageRenderer implements ImageRenderer {
|
||||
public class HwmfImageRenderer implements ImageRenderer {
|
||||
HwmfPicture image;
|
||||
double alpha;
|
||||
|
||||
|
||||
@Override
|
||||
public boolean canRender(String contentType) {
|
||||
return PictureType.WMF.contentType.equalsIgnoreCase(contentType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadImage(InputStream data, String contentType) throws IOException {
|
||||
if (!PictureData.PictureType.WMF.contentType.equals(contentType)) {
|
||||
if (!PictureType.WMF.contentType.equals(contentType)) {
|
||||
throw new IOException("Invalid picture type");
|
||||
}
|
||||
image = new HwmfPicture(data);
|
||||
|
@ -52,7 +57,7 @@ public class HwmfSLImageRenderer implements ImageRenderer {
|
|||
|
||||
@Override
|
||||
public void loadImage(byte[] data, String contentType) throws IOException {
|
||||
if (!PictureData.PictureType.WMF.contentType.equals(contentType)) {
|
||||
if (!PictureType.WMF.contentType.equals(contentType)) {
|
||||
throw new IOException("Invalid picture type");
|
||||
}
|
||||
image = new HwmfPicture(new ByteArrayInputStream(data));
|
|
@ -17,14 +17,21 @@
|
|||
|
||||
package org.apache.poi.hwmf.record;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.AlphaComposite;
|
||||
import java.awt.BasicStroke;
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.LinearGradientPaint;
|
||||
import java.awt.MultipleGradientPaint;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import org.apache.poi.hwmf.usermodel.HwmfPicture;
|
||||
import org.apache.poi.util.IOUtils;
|
||||
import org.apache.poi.util.LittleEndian;
|
||||
import org.apache.poi.util.LittleEndianConsts;
|
||||
|
@ -38,9 +45,11 @@ import org.apache.poi.util.RecordFormatException;
|
|||
*/
|
||||
public class HwmfBitmapDib {
|
||||
|
||||
private static final int MAX_RECORD_LENGTH = 10000000;
|
||||
private static final POILogger logger = POILogFactory.getLogger(HwmfBitmapDib.class);
|
||||
private static final int BMP_HEADER_SIZE = 14;
|
||||
private static final int MAX_RECORD_LENGTH = HwmfPicture.MAX_RECORD_LENGTH;
|
||||
|
||||
public static enum BitCount {
|
||||
public enum BitCount {
|
||||
/**
|
||||
* The image SHOULD be in either JPEG or PNG format. <6> Neither of these formats includes
|
||||
* a color table, so this value specifies that no color table is present. See [JFIF] and [RFC2083]
|
||||
|
@ -122,7 +131,7 @@ public class HwmfBitmapDib {
|
|||
}
|
||||
}
|
||||
|
||||
public static enum Compression {
|
||||
public enum Compression {
|
||||
/**
|
||||
* The bitmap is in uncompressed red green blue (RGB) format that is not compressed
|
||||
* and does not use color masks.
|
||||
|
@ -191,9 +200,7 @@ public class HwmfBitmapDib {
|
|||
}
|
||||
}
|
||||
|
||||
private final static POILogger logger = POILogFactory.getLogger(HwmfBitmapDib.class);
|
||||
private static final int BMP_HEADER_SIZE = 14;
|
||||
|
||||
|
||||
private int headerSize;
|
||||
private int headerWidth;
|
||||
private int headerHeight;
|
||||
|
@ -225,14 +232,36 @@ public class HwmfBitmapDib {
|
|||
introSize += readColors(leis);
|
||||
assert(introSize < 10000);
|
||||
|
||||
int fileSize = (headerImageSize < headerSize) ? recordSize : (int)Math.min(introSize+headerImageSize,recordSize);
|
||||
|
||||
leis.reset();
|
||||
imageData = IOUtils.toByteArray(leis, fileSize);
|
||||
|
||||
assert( headerSize != 0x0C || ((((headerWidth * headerPlanes * headerBitCount.flag + 31) & ~31) / 8) * Math.abs(headerHeight)) == headerImageSize);
|
||||
|
||||
return fileSize;
|
||||
// The size and format of this data is determined by information in the DIBHeaderInfo field. If
|
||||
// it is a BitmapCoreHeader, the size in bytes MUST be calculated as follows:
|
||||
|
||||
int bodySize = ((((headerWidth * headerPlanes * headerBitCount.flag + 31) & ~31) / 8) * Math.abs(headerHeight));
|
||||
|
||||
// This formula SHOULD also be used to calculate the size of aData when DIBHeaderInfo is a
|
||||
// BitmapInfoHeader Object, using values from that object, but only if its Compression value is
|
||||
// BI_RGB, BI_BITFIELDS, or BI_CMYK.
|
||||
// Otherwise, the size of aData MUST be the BitmapInfoHeader Object value ImageSize.
|
||||
|
||||
assert( headerSize != 0x0C || bodySize == headerImageSize);
|
||||
|
||||
if (headerSize == 0x0C ||
|
||||
headerCompression == Compression.BI_RGB ||
|
||||
headerCompression == Compression.BI_BITFIELDS ||
|
||||
headerCompression == Compression.BI_CMYK) {
|
||||
int fileSize = (int)Math.min(introSize+bodySize,recordSize);
|
||||
imageData = IOUtils.safelyAllocate(fileSize, MAX_RECORD_LENGTH);
|
||||
leis.readFully(imageData, 0, introSize);
|
||||
leis.skipFully(recordSize-fileSize);
|
||||
// emfs are sometimes truncated, read as much as possible
|
||||
int readBytes = leis.read(imageData, introSize, fileSize-introSize);
|
||||
return introSize+(recordSize-fileSize)+readBytes;
|
||||
} else {
|
||||
imageData = IOUtils.safelyAllocate(recordSize, MAX_RECORD_LENGTH);
|
||||
leis.readFully(imageData);
|
||||
return recordSize;
|
||||
}
|
||||
}
|
||||
|
||||
protected int readHeader(LittleEndianInputStream leis) throws IOException {
|
||||
|
@ -262,6 +291,9 @@ public class HwmfBitmapDib {
|
|||
headerBitCount = BitCount.valueOf(leis.readUShort());
|
||||
size += 4*LittleEndianConsts.SHORT_SIZE;
|
||||
} else {
|
||||
// fix header size, sometimes this is invalid
|
||||
headerSize = 40;
|
||||
|
||||
// BitmapInfoHeader
|
||||
// A 32-bit signed integer that defines the width of the DIB, in pixels.
|
||||
// This value MUST be positive.
|
||||
|
@ -306,7 +338,6 @@ public class HwmfBitmapDib {
|
|||
headerColorImportant = leis.readUInt();
|
||||
size += 8*LittleEndianConsts.INT_SIZE+2*LittleEndianConsts.SHORT_SIZE;
|
||||
}
|
||||
assert(size == headerSize);
|
||||
return size;
|
||||
}
|
||||
|
||||
|
@ -374,11 +405,35 @@ public class HwmfBitmapDib {
|
|||
return size;
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
// the recordsize ended before the image data
|
||||
if (imageData == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ignore all black mono-brushes
|
||||
if (this.headerBitCount == BitCount.BI_BITCOUNT_1) {
|
||||
if (colorTable == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (Color c : colorTable) {
|
||||
if (!Color.BLACK.equals(c)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public InputStream getBMPStream() {
|
||||
return new ByteArrayInputStream(getBMPData());
|
||||
}
|
||||
|
||||
private byte[] getBMPData() {
|
||||
public byte[] getBMPData() {
|
||||
if (imageData == null) {
|
||||
throw new RecordFormatException("bitmap not initialized ... need to call init() before");
|
||||
}
|
||||
|
@ -407,14 +462,56 @@ public class HwmfBitmapDib {
|
|||
public BufferedImage getImage() {
|
||||
try {
|
||||
return ImageIO.read(getBMPStream());
|
||||
} catch (IOException e) {
|
||||
logger.log(POILogger.ERROR, "invalid bitmap data - returning black opaque image");
|
||||
BufferedImage bi = new BufferedImage(headerWidth, headerHeight, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D g = bi.createGraphics();
|
||||
g.setPaint(Color.black);
|
||||
g.fillRect(0, 0, headerWidth, headerHeight);
|
||||
g.dispose();
|
||||
return bi;
|
||||
} catch (IOException|RuntimeException e) {
|
||||
logger.log(POILogger.ERROR, "invalid bitmap data - returning placeholder image");
|
||||
return getPlaceholder();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return
|
||||
"{ headerSize: " + headerSize +
|
||||
", width: " + headerWidth +
|
||||
", height: " + headerHeight +
|
||||
", planes: " + headerPlanes +
|
||||
", bitCount: '" + headerBitCount + "'" +
|
||||
", compression: '" + headerCompression + "'" +
|
||||
", imageSize: " + headerImageSize +
|
||||
", xPelsPerMeter: " + headerXPelsPerMeter +
|
||||
", yPelsPerMeter: " + headerYPelsPerMeter +
|
||||
", colorUsed: " + headerColorUsed +
|
||||
", colorImportant: " + headerColorImportant +
|
||||
", imageSize: " + (imageData == null ? 0 : imageData.length) +
|
||||
"}";
|
||||
}
|
||||
|
||||
protected BufferedImage getPlaceholder() {
|
||||
BufferedImage bi = new BufferedImage(headerWidth, headerHeight, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D g = bi.createGraphics();
|
||||
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
|
||||
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
|
||||
g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
|
||||
|
||||
g.setComposite(AlphaComposite.Clear);
|
||||
g.fillRect(0, 0, headerWidth, headerHeight);
|
||||
|
||||
final int arcs = Math.min(headerWidth, headerHeight) / 7;
|
||||
|
||||
Color bg = Color.LIGHT_GRAY;
|
||||
Color fg = Color.GRAY;
|
||||
LinearGradientPaint lgp = new LinearGradientPaint(0f, 0f, 5, 5,
|
||||
new float[] {0,.1f,.1001f}, new Color[] {fg,fg,bg}, MultipleGradientPaint.CycleMethod.REFLECT);
|
||||
g.setComposite(AlphaComposite.SrcOver.derive(0.4f));
|
||||
g.setPaint(lgp);
|
||||
g.fillRoundRect(0, 0, headerWidth-1, headerHeight-1, arcs, arcs);
|
||||
|
||||
g.setColor(Color.DARK_GRAY);
|
||||
g.setComposite(AlphaComposite.Src);
|
||||
g.setStroke(new BasicStroke(2));
|
||||
g.drawRoundRect(0, 0, headerWidth-1, headerHeight-1, arcs, arcs);
|
||||
g.dispose();
|
||||
return bi;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ package org.apache.poi.hwmf.record;
|
|||
|
||||
import java.awt.Color;
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.apache.poi.util.LittleEndianConsts;
|
||||
import org.apache.poi.util.LittleEndianInputStream;
|
||||
|
@ -53,6 +54,10 @@ public class HwmfColorRef implements Cloneable {
|
|||
return colorRef;
|
||||
}
|
||||
|
||||
public void setColor(Color color) {
|
||||
colorRef = color;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new object of the same class and with the
|
||||
* same contents as this object.
|
||||
|
@ -69,4 +74,9 @@ public class HwmfColorRef implements Cloneable {
|
|||
throw new InternalError();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format(Locale.ROOT, "%#08X", colorRef.getRGB()&0xFFFFFF);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,9 +20,11 @@ package org.apache.poi.hwmf.record;
|
|||
import java.awt.Shape;
|
||||
import java.awt.geom.Arc2D;
|
||||
import java.awt.geom.Area;
|
||||
import java.awt.geom.Dimension2D;
|
||||
import java.awt.geom.Ellipse2D;
|
||||
import java.awt.geom.Line2D;
|
||||
import java.awt.geom.Path2D;
|
||||
import java.awt.geom.PathIterator;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.awt.geom.RoundRectangle2D;
|
||||
|
@ -31,6 +33,8 @@ import java.util.ArrayList;
|
|||
import java.util.List;
|
||||
|
||||
import org.apache.poi.hwmf.draw.HwmfGraphics;
|
||||
import org.apache.poi.hwmf.draw.HwmfGraphics.FillDrawStyle;
|
||||
import org.apache.poi.util.Internal;
|
||||
import org.apache.poi.util.LittleEndianConsts;
|
||||
import org.apache.poi.util.LittleEndianInputStream;
|
||||
|
||||
|
@ -41,31 +45,26 @@ public class HwmfDraw {
|
|||
*/
|
||||
public static class WmfMoveTo implements HwmfRecord {
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the y-coordinate, in logical units.
|
||||
*/
|
||||
private int y;
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the x-coordinate, in logical units.
|
||||
*/
|
||||
private int x;
|
||||
protected final Point2D point = new Point2D.Double();
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.moveTo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
|
||||
y = leis.readShort();
|
||||
x = leis.readShort();
|
||||
return 2*LittleEndianConsts.SHORT_SIZE;
|
||||
return readPointS(leis, point);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(HwmfGraphics ctx) {
|
||||
ctx.getProperties().setLocation(x, y);
|
||||
ctx.getProperties().setLocation(point);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return pointToString(point);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -75,36 +74,29 @@ public class HwmfDraw {
|
|||
*/
|
||||
public static class WmfLineTo implements HwmfRecord {
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the vertical component of the drawing
|
||||
* destination position, in logical units.
|
||||
*/
|
||||
private int y;
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the horizontal component of the drawing
|
||||
* destination position, in logical units.
|
||||
*/
|
||||
private int x;
|
||||
protected final Point2D point = new Point2D.Double();
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.lineTo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
|
||||
y = leis.readShort();
|
||||
x = leis.readShort();
|
||||
return 2*LittleEndianConsts.SHORT_SIZE;
|
||||
return readPointS(leis, point);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(HwmfGraphics ctx) {
|
||||
Point2D start = ctx.getProperties().getLocation();
|
||||
Line2D line = new Line2D.Double(start.getX(), start.getY(), x, y);
|
||||
Line2D line = new Line2D.Double(start, point);
|
||||
ctx.draw(line);
|
||||
ctx.getProperties().setLocation(x, y);
|
||||
ctx.getProperties().setLocation(point);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return pointToString(point);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -115,10 +107,10 @@ public class HwmfDraw {
|
|||
*/
|
||||
public static class WmfPolygon implements HwmfRecord {
|
||||
|
||||
private Path2D poly = new Path2D.Double();
|
||||
protected Path2D poly;
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.polygon;
|
||||
}
|
||||
|
||||
|
@ -129,6 +121,7 @@ public class HwmfDraw {
|
|||
*/
|
||||
int numberofPoints = leis.readShort();
|
||||
|
||||
poly = new Path2D.Double(Path2D.WIND_EVEN_ODD, numberofPoints);
|
||||
for (int i=0; i<numberofPoints; i++) {
|
||||
// A 16-bit signed integer that defines the horizontal (x) coordinate of the point.
|
||||
int x = leis.readShort();
|
||||
|
@ -146,15 +139,33 @@ public class HwmfDraw {
|
|||
|
||||
@Override
|
||||
public void draw(HwmfGraphics ctx) {
|
||||
Path2D shape = getShape();
|
||||
// shape.closePath();
|
||||
Path2D p = (Path2D)shape.clone();
|
||||
p.setWindingRule(getWindingRule(ctx));
|
||||
ctx.fill(p);
|
||||
Path2D p = (Path2D)poly.clone();
|
||||
// don't close the path
|
||||
p.setWindingRule(ctx.getProperties().getWindingRule());
|
||||
switch (getFillDrawStyle()) {
|
||||
case FILL:
|
||||
ctx.fill(p);
|
||||
break;
|
||||
case DRAW:
|
||||
ctx.draw(p);
|
||||
break;
|
||||
case FILL_DRAW:
|
||||
ctx.fill(p);
|
||||
ctx.draw(p);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected Path2D getShape() {
|
||||
return (Path2D)poly.clone();
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{ poly: "+polyToString(poly)+" }";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true, if the shape should be filled
|
||||
*/
|
||||
protected FillDrawStyle getFillDrawStyle() {
|
||||
return FillDrawStyle.FILL;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -165,16 +176,13 @@ public class HwmfDraw {
|
|||
public static class WmfPolyline extends WmfPolygon {
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.polyline;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(HwmfGraphics ctx) {
|
||||
Path2D shape = getShape();
|
||||
Path2D p = (Path2D)shape.clone();
|
||||
p.setWindingRule(getWindingRule(ctx));
|
||||
ctx.draw(p);
|
||||
protected FillDrawStyle getFillDrawStyle() {
|
||||
return FillDrawStyle.DRAW;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -184,49 +192,30 @@ public class HwmfDraw {
|
|||
* are defined in the playback device context.
|
||||
*/
|
||||
public static class WmfEllipse implements HwmfRecord {
|
||||
/**
|
||||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of
|
||||
* the lower-right corner of the bounding rectangle.
|
||||
*/
|
||||
private int bottomRect;
|
||||
/**
|
||||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of
|
||||
* the lower-right corner of the bounding rectangle.
|
||||
*/
|
||||
private int rightRect;
|
||||
/**
|
||||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the
|
||||
* upper-left corner of the bounding rectangle.
|
||||
*/
|
||||
private int topRect;
|
||||
/**
|
||||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of
|
||||
* the upper-left corner of the bounding rectangle.
|
||||
*/
|
||||
private int leftRect;
|
||||
protected final Rectangle2D bounds = new Rectangle2D.Double();
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.ellipse;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
|
||||
bottomRect = leis.readShort();
|
||||
rightRect = leis.readShort();
|
||||
topRect = leis.readShort();
|
||||
leftRect = leis.readShort();
|
||||
return 4*LittleEndianConsts.SHORT_SIZE;
|
||||
return readBounds(leis, bounds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(HwmfGraphics ctx) {
|
||||
int x = Math.min(leftRect, rightRect);
|
||||
int y = Math.min(topRect, bottomRect);
|
||||
int w = Math.abs(leftRect - rightRect - 1);
|
||||
int h = Math.abs(topRect - bottomRect - 1);
|
||||
Shape s = new Ellipse2D.Double(x, y, w, h);
|
||||
ctx.fill(s);
|
||||
ctx.fill(getShape());
|
||||
}
|
||||
|
||||
protected Ellipse2D getShape() {
|
||||
return new Ellipse2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return boundsToString(bounds);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -239,25 +228,25 @@ public class HwmfDraw {
|
|||
* A 16-bit unsigned integer used to index into the WMF Object Table to get
|
||||
* the region to be framed.
|
||||
*/
|
||||
private int regionIndex;
|
||||
protected int regionIndex;
|
||||
/**
|
||||
* A 16-bit unsigned integer used to index into the WMF Object Table to get the
|
||||
* Brush to use for filling the region.
|
||||
*/
|
||||
private int brushIndex;
|
||||
protected int brushIndex;
|
||||
/**
|
||||
* A 16-bit signed integer that defines the height, in logical units, of the
|
||||
* region frame.
|
||||
*/
|
||||
private int height;
|
||||
protected int height;
|
||||
/**
|
||||
* A 16-bit signed integer that defines the width, in logical units, of the
|
||||
* region frame.
|
||||
*/
|
||||
private int width;
|
||||
protected int width;
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.frameRegion;
|
||||
}
|
||||
|
||||
|
@ -293,10 +282,10 @@ public class HwmfDraw {
|
|||
*/
|
||||
public static class WmfPolyPolygon implements HwmfRecord {
|
||||
|
||||
private List<Path2D> polyList = new ArrayList<>();
|
||||
protected final List<Path2D> polyList = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.polyPolygon;
|
||||
}
|
||||
|
||||
|
@ -325,7 +314,7 @@ public class HwmfDraw {
|
|||
* An array of 16-bit signed integers that define the coordinates of the polygons.
|
||||
* (Note: MS-WMF wrongly says unsigned integers ...)
|
||||
*/
|
||||
Path2D poly = new Path2D.Double();
|
||||
Path2D poly = new Path2D.Double(Path2D.WIND_EVEN_ODD, nPoints);
|
||||
for (int i=0; i<nPoints; i++) {
|
||||
int x = leis.readShort();
|
||||
int y = leis.readShort();
|
||||
|
@ -345,23 +334,81 @@ public class HwmfDraw {
|
|||
|
||||
@Override
|
||||
public void draw(HwmfGraphics ctx) {
|
||||
if (polyList.isEmpty()) {
|
||||
Shape shape = getShape(ctx);
|
||||
if (shape == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int windingRule = getWindingRule(ctx);
|
||||
Area area = null;
|
||||
for (Path2D poly : polyList) {
|
||||
Path2D p = (Path2D)poly.clone();
|
||||
p.setWindingRule(windingRule);
|
||||
Area newArea = new Area(p);
|
||||
if (area == null) {
|
||||
area = newArea;
|
||||
} else {
|
||||
area.exclusiveOr(newArea);
|
||||
}
|
||||
|
||||
switch (getFillDrawStyle()) {
|
||||
case DRAW:
|
||||
ctx.draw(shape);
|
||||
break;
|
||||
case FILL:
|
||||
ctx.fill(shape);
|
||||
break;
|
||||
case FILL_DRAW:
|
||||
ctx.fill(shape);
|
||||
ctx.draw(shape);
|
||||
break;
|
||||
}
|
||||
ctx.fill(area);
|
||||
}
|
||||
|
||||
protected FillDrawStyle getFillDrawStyle() {
|
||||
// Each polygon SHOULD be outlined using the current pen, and filled using the current brush and
|
||||
// polygon fill mode that are defined in the playback device context. The polygons defined by this
|
||||
// record can overlap.
|
||||
return FillDrawStyle.FILL_DRAW;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true, if a polyline should be closed, i.e. is a polygon
|
||||
*/
|
||||
protected boolean isClosed() {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected Shape getShape(HwmfGraphics ctx) {
|
||||
int windingRule = ctx.getProperties().getWindingRule();
|
||||
|
||||
if (isClosed()) {
|
||||
Area area = null;
|
||||
for (Path2D poly : polyList) {
|
||||
Path2D p = (Path2D)poly.clone();
|
||||
p.setWindingRule(windingRule);
|
||||
Area newArea = new Area(p);
|
||||
if (area == null) {
|
||||
area = newArea;
|
||||
} else {
|
||||
area.exclusiveOr(newArea);
|
||||
}
|
||||
}
|
||||
return area;
|
||||
} else {
|
||||
Path2D path = new Path2D.Double();
|
||||
path.setWindingRule(windingRule);
|
||||
for (Path2D poly : polyList) {
|
||||
path.append(poly, false);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append("{ polyList: [");
|
||||
boolean isFirst = true;
|
||||
for (Path2D p : polyList) {
|
||||
if (!isFirst) {
|
||||
sb.append(",");
|
||||
}
|
||||
isFirst = false;
|
||||
sb.append("{ points: ");
|
||||
sb.append(polyToString(p));
|
||||
sb.append(" }");
|
||||
}
|
||||
sb.append(" }");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -370,92 +417,55 @@ public class HwmfDraw {
|
|||
* filled by using the brush that are defined in the playback device context.
|
||||
*/
|
||||
public static class WmfRectangle implements HwmfRecord {
|
||||
/**
|
||||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of
|
||||
* the lower-right corner of the rectangle.
|
||||
*/
|
||||
private int bottomRect;
|
||||
/**
|
||||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of
|
||||
* the lower-right corner of the rectangle.
|
||||
*/
|
||||
private int rightRect;
|
||||
/**
|
||||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the
|
||||
* upper-left corner of the rectangle.
|
||||
*/
|
||||
private int topRect;
|
||||
/**
|
||||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of
|
||||
* the upper-left corner of the rectangle.
|
||||
*/
|
||||
private int leftRect;
|
||||
protected final Rectangle2D bounds = new Rectangle2D.Double();
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.frameRegion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
|
||||
bottomRect = leis.readShort();
|
||||
rightRect = leis.readShort();
|
||||
topRect = leis.readShort();
|
||||
leftRect = leis.readShort();
|
||||
return 4*LittleEndianConsts.SHORT_SIZE;
|
||||
return readBounds(leis, bounds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(HwmfGraphics ctx) {
|
||||
int x = Math.min(leftRect, rightRect);
|
||||
int y = Math.min(topRect, bottomRect);
|
||||
int w = Math.abs(leftRect - rightRect - 1);
|
||||
int h = Math.abs(topRect - bottomRect - 1);
|
||||
Shape s = new Rectangle2D.Double(x, y, w, h);
|
||||
ctx.fill(s);
|
||||
ctx.fill(bounds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return boundsToString(bounds);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The META_RECTANGLE record paints a rectangle. The rectangle is outlined by using the pen and
|
||||
* filled by using the brush that are defined in the playback device context.
|
||||
* The META_SETPIXEL record sets the pixel at the specified coordinates to the specified color.
|
||||
*/
|
||||
public static class WmfSetPixel implements HwmfRecord {
|
||||
/**
|
||||
* A ColorRef Object that defines the color value.
|
||||
*/
|
||||
HwmfColorRef colorRef;
|
||||
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 +479,19 @@ public class HwmfDraw {
|
|||
* A 16-bit signed integer that defines the height, in logical coordinates, of the
|
||||
* ellipse used to draw the rounded corners.
|
||||
*/
|
||||
private int height;
|
||||
protected int height;
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the width, in logical coordinates, of the
|
||||
* ellipse used to draw the rounded corners.
|
||||
*/
|
||||
private int width;
|
||||
protected int width;
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of
|
||||
* the lower-right corner of the rectangle.
|
||||
*/
|
||||
private int bottomRect;
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of
|
||||
* the lower-right corner of the rectangle.
|
||||
*/
|
||||
private int rightRect;
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the
|
||||
* upper-left corner of the rectangle.
|
||||
*/
|
||||
private int topRect;
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of
|
||||
* the upper-left corner of the rectangle.
|
||||
*/
|
||||
private int leftRect;
|
||||
protected final Rectangle2D bounds = new Rectangle2D.Double();
|
||||
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.roundRect;
|
||||
}
|
||||
|
||||
|
@ -511,21 +499,16 @@ public class HwmfDraw {
|
|||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
|
||||
height = leis.readShort();
|
||||
width = leis.readShort();
|
||||
bottomRect = leis.readShort();
|
||||
rightRect = leis.readShort();
|
||||
topRect = leis.readShort();
|
||||
leftRect = leis.readShort();
|
||||
return 6*LittleEndianConsts.SHORT_SIZE;
|
||||
return 2*LittleEndianConsts.SHORT_SIZE+readBounds(leis, bounds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(HwmfGraphics ctx) {
|
||||
int x = Math.min(leftRect, rightRect);
|
||||
int y = Math.min(topRect, bottomRect);
|
||||
int w = Math.abs(leftRect - rightRect - 1);
|
||||
int h = Math.abs(topRect - bottomRect - 1);
|
||||
Shape s = new RoundRectangle2D.Double(x, y, w, h, width, height);
|
||||
ctx.fill(s);
|
||||
ctx.fill(getShape());
|
||||
}
|
||||
|
||||
protected RoundRectangle2D getShape() {
|
||||
return new RoundRectangle2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), width, height);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -534,73 +517,61 @@ public class HwmfDraw {
|
|||
* The META_ARC record draws an elliptical arc.
|
||||
*/
|
||||
public static class WmfArc implements HwmfRecord {
|
||||
/**
|
||||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of
|
||||
* the ending point of the radial line defining the ending point of the arc.
|
||||
*/
|
||||
private int yEndArc;
|
||||
/**
|
||||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of
|
||||
* the ending point of the radial line defining the ending point of the arc.
|
||||
*/
|
||||
private int xEndArc;
|
||||
/**
|
||||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of
|
||||
* the ending point of the radial line defining the starting point of the arc.
|
||||
*/
|
||||
private int yStartArc;
|
||||
/**
|
||||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of
|
||||
* the ending point of the radial line defining the starting point of the arc.
|
||||
*/
|
||||
private int xStartArc;
|
||||
/**
|
||||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of
|
||||
* the lower-right corner of the bounding rectangle.
|
||||
*/
|
||||
private int bottomRect;
|
||||
/**
|
||||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of
|
||||
* the lower-right corner of the bounding rectangle.
|
||||
*/
|
||||
private int rightRect;
|
||||
/**
|
||||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the
|
||||
* upper-left corner of the bounding rectangle.
|
||||
*/
|
||||
private int topRect;
|
||||
/**
|
||||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of
|
||||
* the upper-left corner of the bounding rectangle.
|
||||
*/
|
||||
private int leftRect;
|
||||
/** starting point of the arc */
|
||||
protected final Point2D startPoint = new Point2D.Double();
|
||||
|
||||
/** ending point of the arc */
|
||||
protected final Point2D endPoint = new Point2D.Double();
|
||||
|
||||
/** the bounding rectangle */
|
||||
protected final Rectangle2D bounds = new Rectangle2D.Double();
|
||||
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.arc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
|
||||
yEndArc = leis.readShort();
|
||||
xEndArc = leis.readShort();
|
||||
yStartArc = leis.readShort();
|
||||
xStartArc = leis.readShort();
|
||||
bottomRect = leis.readShort();
|
||||
rightRect = leis.readShort();
|
||||
topRect = leis.readShort();
|
||||
leftRect = leis.readShort();
|
||||
readPointS(leis, endPoint);
|
||||
readPointS(leis, startPoint);
|
||||
readBounds(leis, bounds);
|
||||
|
||||
return 8*LittleEndianConsts.SHORT_SIZE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(HwmfGraphics ctx) {
|
||||
int x = Math.min(leftRect, rightRect);
|
||||
int y = Math.min(topRect, bottomRect);
|
||||
int w = Math.abs(leftRect - rightRect - 1);
|
||||
int h = Math.abs(topRect - bottomRect - 1);
|
||||
double startAngle = Math.toDegrees(Math.atan2(-(yStartArc - (topRect + h / 2.)), xStartArc - (leftRect + w / 2.)));
|
||||
double endAngle = Math.toDegrees(Math.atan2(-(yEndArc - (topRect + h / 2.)), xEndArc - (leftRect + w / 2.)));
|
||||
Shape s = getShape();
|
||||
switch (getFillDrawStyle()) {
|
||||
case FILL:
|
||||
ctx.fill(s);
|
||||
break;
|
||||
case DRAW:
|
||||
ctx.draw(s);
|
||||
break;
|
||||
case FILL_DRAW:
|
||||
ctx.fill(s);
|
||||
ctx.draw(s);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected FillDrawStyle getFillDrawStyle() {
|
||||
switch (getWmfRecordType()) {
|
||||
default:
|
||||
case arc:
|
||||
return FillDrawStyle.DRAW;
|
||||
case chord:
|
||||
case pie:
|
||||
return FillDrawStyle.FILL_DRAW;
|
||||
}
|
||||
}
|
||||
|
||||
protected Arc2D getShape() {
|
||||
double startAngle = Math.toDegrees(Math.atan2(-(startPoint.getY() - bounds.getCenterY()), startPoint.getX() - bounds.getCenterX()));
|
||||
double endAngle = Math.toDegrees(Math.atan2(-(endPoint.getY() - bounds.getCenterY()), endPoint.getX() - bounds.getCenterX()));
|
||||
double arcAngle = (endAngle - startAngle) + (endAngle - startAngle > 0 ? 0 : 360);
|
||||
if (startAngle < 0) {
|
||||
startAngle += 360;
|
||||
|
@ -608,28 +579,32 @@ public class HwmfDraw {
|
|||
|
||||
boolean fillShape;
|
||||
int arcClosure;
|
||||
switch (getRecordType()) {
|
||||
switch (getWmfRecordType()) {
|
||||
default:
|
||||
case arc:
|
||||
arcClosure = Arc2D.OPEN;
|
||||
fillShape = false;
|
||||
break;
|
||||
case chord:
|
||||
arcClosure = Arc2D.CHORD;
|
||||
fillShape = true;
|
||||
break;
|
||||
case pie:
|
||||
arcClosure = Arc2D.PIE;
|
||||
fillShape = true;
|
||||
break;
|
||||
}
|
||||
|
||||
Shape s = new Arc2D.Double(x, y, w, h, startAngle, arcAngle, arcClosure);
|
||||
if (fillShape) {
|
||||
ctx.fill(s);
|
||||
} else {
|
||||
ctx.draw(s);
|
||||
}
|
||||
|
||||
return new Arc2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), startAngle, arcAngle, arcClosure);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
Arc2D arc = getShape();
|
||||
return
|
||||
"{ startPoint: "+pointToString(startPoint)+
|
||||
", endPoint: "+pointToString(endPoint)+
|
||||
", startAngle: "+arc.getAngleStart()+
|
||||
", extentAngle: "+arc.getAngleExtent()+
|
||||
", bounds: "+boundsToString(bounds)+
|
||||
" }";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -641,7 +616,7 @@ public class HwmfDraw {
|
|||
public static class WmfPie extends WmfArc {
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.pie;
|
||||
}
|
||||
}
|
||||
|
@ -654,7 +629,7 @@ public class HwmfDraw {
|
|||
public static class WmfChord extends WmfArc {
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.chord;
|
||||
}
|
||||
}
|
||||
|
@ -673,10 +648,10 @@ public class HwmfDraw {
|
|||
* A 16-bit unsigned integer used to index into the WMF Object Table to
|
||||
* get the object to be selected.
|
||||
*/
|
||||
private int objectIndex;
|
||||
protected int objectIndex;
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.selectObject;
|
||||
}
|
||||
|
||||
|
@ -690,9 +665,113 @@ public class HwmfDraw {
|
|||
public void draw(HwmfGraphics ctx) {
|
||||
ctx.applyObjectTableEntry(objectIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{ index: "+objectIndex +" }";
|
||||
}
|
||||
}
|
||||
|
||||
private static int getWindingRule(HwmfGraphics ctx) {
|
||||
return ctx.getProperties().getPolyfillMode().awtFlag;
|
||||
static int readBounds(LittleEndianInputStream leis, Rectangle2D bounds) {
|
||||
/**
|
||||
* The 16-bit signed integers that defines the corners of the bounding rectangle.
|
||||
*/
|
||||
int bottom = leis.readShort();
|
||||
int right = leis.readShort();
|
||||
int top = leis.readShort();
|
||||
int left = leis.readShort();
|
||||
|
||||
int x = Math.min(left, right);
|
||||
int y = Math.min(top, bottom);
|
||||
int w = Math.abs(left - right - 1);
|
||||
int h = Math.abs(top - bottom - 1);
|
||||
|
||||
bounds.setRect(x, y, w, h);
|
||||
|
||||
return 4 * LittleEndianConsts.SHORT_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
static int readRectS(LittleEndianInputStream leis, Rectangle2D bounds) {
|
||||
/**
|
||||
* The 16-bit signed integers that defines the corners of the bounding rectangle.
|
||||
*/
|
||||
int left = leis.readShort();
|
||||
int top = leis.readShort();
|
||||
int right = leis.readShort();
|
||||
int bottom = leis.readShort();
|
||||
|
||||
int x = Math.min(left, right);
|
||||
int y = Math.min(top, bottom);
|
||||
int w = Math.abs(left - right - 1);
|
||||
int h = Math.abs(top - bottom - 1);
|
||||
|
||||
bounds.setRect(x, y, w, h);
|
||||
|
||||
return 4 * LittleEndianConsts.SHORT_SIZE;
|
||||
}
|
||||
|
||||
static int readPointS(LittleEndianInputStream leis, Point2D point) {
|
||||
/** a signed integer that defines the x/y-coordinate, in logical units. */
|
||||
int y = leis.readShort();
|
||||
int x = leis.readShort();
|
||||
point.setLocation(x, y);
|
||||
return 2*LittleEndianConsts.SHORT_SIZE;
|
||||
}
|
||||
|
||||
static String polyToString(Path2D poly) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("[");
|
||||
final PathIterator iter = poly.getPathIterator(null);
|
||||
double[] pnts = new double[6];
|
||||
while (!iter.isDone()) {
|
||||
int segType = iter.currentSegment(pnts);
|
||||
switch (segType) {
|
||||
case PathIterator.SEG_MOVETO:
|
||||
sb.append("{ type: 'move', x: "+pnts[0]+", y: "+pnts[1]+" }, ");
|
||||
break;
|
||||
case PathIterator.SEG_LINETO:
|
||||
sb.append("{ type: 'lineto', x: "+pnts[0]+", y: "+pnts[1]+" }, ");
|
||||
break;
|
||||
case PathIterator.SEG_QUADTO:
|
||||
sb.append("{ type: 'quad', x1: "+pnts[0]+", y1: "+pnts[1]+", x2: "+pnts[2]+", y2: "+pnts[3]+" }, ");
|
||||
break;
|
||||
case PathIterator.SEG_CUBICTO:
|
||||
sb.append("{ type: 'cubic', x1: "+pnts[0]+", y1: "+pnts[1]+", x2: "+pnts[2]+", y2: "+pnts[3]+", x3: "+pnts[4]+", y3: "+pnts[5]+" }, ");
|
||||
break;
|
||||
case PathIterator.SEG_CLOSE:
|
||||
sb.append("{ type: 'close' }, ");
|
||||
break;
|
||||
}
|
||||
iter.next();
|
||||
}
|
||||
sb.append("]");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Internal
|
||||
public static String pointToString(Point2D point) {
|
||||
return "{ x: "+point.getX()+", y: "+point.getY()+" }";
|
||||
}
|
||||
|
||||
@Internal
|
||||
public static String boundsToString(Rectangle2D bounds) {
|
||||
return "{ x: "+bounds.getX()+", y: "+bounds.getY()+", w: "+bounds.getWidth()+", h: "+bounds.getHeight()+" }";
|
||||
}
|
||||
|
||||
@Internal
|
||||
public static String dimToString(Dimension2D dim) {
|
||||
return "{ w: "+dim.getWidth()+", h: "+dim.getHeight()+" }";
|
||||
}
|
||||
|
||||
@Internal
|
||||
public static Rectangle2D normalizeBounds(Rectangle2D bounds) {
|
||||
return (bounds.getWidth() >= 0 && bounds.getHeight() >= 0) ? bounds
|
||||
: new Rectangle2D.Double(
|
||||
bounds.getWidth() >= 0 ? bounds.getMinX() : bounds.getMaxX(),
|
||||
bounds.getHeight() >= 0 ? bounds.getMinY() : bounds.getMaxY(),
|
||||
Math.abs(bounds.getWidth()),
|
||||
Math.abs(bounds.getHeight())
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -185,7 +185,7 @@ public class HwmfEscape implements HwmfRecord {
|
|||
private byte escapeData[];
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.escape;
|
||||
}
|
||||
|
||||
|
|
|
@ -17,11 +17,17 @@
|
|||
|
||||
package org.apache.poi.hwmf.record;
|
||||
|
||||
import static org.apache.poi.hwmf.record.HwmfDraw.boundsToString;
|
||||
import static org.apache.poi.hwmf.record.HwmfDraw.readPointS;
|
||||
|
||||
import java.awt.Shape;
|
||||
import java.awt.geom.Path2D;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.poi.hwmf.draw.HwmfDrawProperties;
|
||||
import org.apache.poi.hwmf.draw.HwmfGraphics;
|
||||
import org.apache.poi.util.LittleEndianConsts;
|
||||
import org.apache.poi.util.LittleEndianInputStream;
|
||||
|
@ -62,7 +68,7 @@ public class HwmfFill {
|
|||
this.flag = flag;
|
||||
}
|
||||
|
||||
static ColorUsage valueOf(int flag) {
|
||||
public static ColorUsage valueOf(int flag) {
|
||||
for (ColorUsage bs : values()) {
|
||||
if (bs.flag == flag) return bs;
|
||||
}
|
||||
|
@ -80,16 +86,16 @@ public class HwmfFill {
|
|||
* A 16-bit unsigned integer used to index into the WMF Object Table to get
|
||||
* the region to be filled.
|
||||
*/
|
||||
private int regionIndex;
|
||||
protected int regionIndex;
|
||||
|
||||
/**
|
||||
* A 16-bit unsigned integer used to index into the WMF Object Table to get the
|
||||
* brush to use for filling the region.
|
||||
*/
|
||||
private int brushIndex;
|
||||
protected int brushIndex;
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.fillRegion;
|
||||
}
|
||||
|
||||
|
@ -125,7 +131,7 @@ public class HwmfFill {
|
|||
*/
|
||||
int regionIndex;
|
||||
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.paintRegion;
|
||||
}
|
||||
|
||||
|
@ -155,31 +161,21 @@ public class HwmfFill {
|
|||
/**
|
||||
* A 32-bit ColorRef Object that defines the color value.
|
||||
*/
|
||||
private HwmfColorRef colorRef;
|
||||
/**
|
||||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the
|
||||
* point where filling is to start.
|
||||
*/
|
||||
private int yStart;
|
||||
/**
|
||||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the
|
||||
* point where filling is to start.
|
||||
*/
|
||||
private int xStart;
|
||||
|
||||
|
||||
protected final HwmfColorRef colorRef = new HwmfColorRef();
|
||||
|
||||
/** the point where filling is to start. */
|
||||
protected final Point2D start = new Point2D.Double();
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.floodFill;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
|
||||
colorRef = new HwmfColorRef();
|
||||
int size = colorRef.init(leis);
|
||||
yStart = leis.readShort();
|
||||
xStart = leis.readShort();
|
||||
return size+2*LittleEndianConsts.SHORT_SIZE;
|
||||
size += readPointS(leis, start);
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -215,34 +211,39 @@ public class HwmfFill {
|
|||
this.awtFlag = awtFlag;
|
||||
}
|
||||
|
||||
static HwmfPolyfillMode valueOf(int wmfFlag) {
|
||||
public static HwmfPolyfillMode valueOf(int wmfFlag) {
|
||||
for (HwmfPolyfillMode pm : values()) {
|
||||
if (pm.wmfFlag == wmfFlag) return pm;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A 16-bit unsigned integer that defines polygon fill mode.
|
||||
* An unsigned integer that defines polygon fill mode.
|
||||
* This MUST be one of the values: ALTERNATE = 0x0001, WINDING = 0x0002
|
||||
*/
|
||||
private HwmfPolyfillMode polyfillMode;
|
||||
protected HwmfPolyfillMode polyFillMode;
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.setPolyFillMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
|
||||
polyfillMode = HwmfPolyfillMode.valueOf(leis.readUShort() & 3);
|
||||
polyFillMode = HwmfPolyfillMode.valueOf(leis.readUShort() & 3);
|
||||
return LittleEndianConsts.SHORT_SIZE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(HwmfGraphics ctx) {
|
||||
ctx.getProperties().setPolyfillMode(polyfillMode);
|
||||
ctx.getProperties().setPolyfillMode(polyFillMode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{ polyFillMode: '"+ polyFillMode +"' }";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -251,7 +252,7 @@ public class HwmfFill {
|
|||
* The META_EXTFLOODFILL record fills an area with the brush that is defined in
|
||||
* the playback device context.
|
||||
*/
|
||||
public static class WmfExtFloodFill implements HwmfRecord {
|
||||
public static class WmfExtFloodFill extends WmfFloodFill {
|
||||
|
||||
/**
|
||||
* A 16-bit unsigned integer that defines the fill operation to be performed. This
|
||||
|
@ -266,38 +267,17 @@ public class HwmfFill {
|
|||
* Filling continues outward in all directions as long as the color is encountered.
|
||||
* This style is useful for filling areas with multicolored boundaries.
|
||||
*/
|
||||
private int mode;
|
||||
|
||||
/**
|
||||
* A 32-bit ColorRef Object that defines the color value.
|
||||
*/
|
||||
private HwmfColorRef colorRef;
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the point
|
||||
* to be set.
|
||||
*/
|
||||
private int y;
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the point
|
||||
* to be set.
|
||||
*/
|
||||
private int x;
|
||||
protected int mode;
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.extFloodFill;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
|
||||
mode = leis.readUShort();
|
||||
colorRef = new HwmfColorRef();
|
||||
int size = colorRef.init(leis);
|
||||
y = leis.readShort();
|
||||
x = leis.readShort();
|
||||
return size+3*LittleEndianConsts.SHORT_SIZE;
|
||||
return super.init(leis, recordSize, recordFunction)+LittleEndianConsts.SHORT_SIZE;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -318,7 +298,7 @@ public class HwmfFill {
|
|||
private int region;
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.invertRegion;
|
||||
}
|
||||
|
||||
|
@ -348,30 +328,10 @@ public class HwmfFill {
|
|||
*/
|
||||
private HwmfTernaryRasterOp rasterOperation;
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the height, in logical units, of the rectangle.
|
||||
*/
|
||||
private int height;
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the width, in logical units, of the rectangle.
|
||||
*/
|
||||
private int width;
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the
|
||||
* upper-left corner of the rectangle to be filled.
|
||||
*/
|
||||
private int yLeft;
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the
|
||||
* upper-left corner of the rectangle to be filled.
|
||||
*/
|
||||
private int xLeft;
|
||||
|
||||
private final Rectangle2D bounds = new Rectangle2D.Double();
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.patBlt;
|
||||
}
|
||||
|
||||
|
@ -383,12 +343,7 @@ public class HwmfFill {
|
|||
rasterOperation = HwmfTernaryRasterOp.valueOf(rasterOpIndex);
|
||||
assert(rasterOpCode == rasterOperation.opCode);
|
||||
|
||||
height = leis.readShort();
|
||||
width = leis.readShort();
|
||||
yLeft = leis.readShort();
|
||||
xLeft = leis.readShort();
|
||||
|
||||
return 6*LittleEndianConsts.SHORT_SIZE;
|
||||
return readBounds2(leis, bounds)+2*LittleEndianConsts.SHORT_SIZE;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -414,53 +369,22 @@ public class HwmfFill {
|
|||
* in the playback device context, and the destination pixels are to be combined to form the new
|
||||
* image. This code MUST be one of the values in the Ternary Raster Operation Enumeration
|
||||
*/
|
||||
private HwmfTernaryRasterOp rasterOperation;
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the height, in logical units, of the source rectangle.
|
||||
*/
|
||||
private int srcHeight;
|
||||
/**
|
||||
* A 16-bit signed integer that defines the width, in logical units, of the source rectangle.
|
||||
*/
|
||||
private int srcWidth;
|
||||
/**
|
||||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the upper-left corner
|
||||
* of the source rectangle.
|
||||
*/
|
||||
private int ySrc;
|
||||
/**
|
||||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the upper-left corner
|
||||
* of the source rectangle.
|
||||
*/
|
||||
private int xSrc;
|
||||
/**
|
||||
* A 16-bit signed integer that defines the height, in logical units, of the destination rectangle.
|
||||
*/
|
||||
private int destHeight;
|
||||
/**
|
||||
* A 16-bit signed integer that defines the width, in logical units, of the destination rectangle.
|
||||
*/
|
||||
private int destWidth;
|
||||
/**
|
||||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the upper-left
|
||||
* corner of the destination rectangle.
|
||||
*/
|
||||
private int yDest;
|
||||
/**
|
||||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the upper-left
|
||||
* corner of the destination rectangle.
|
||||
*/
|
||||
private int xDest;
|
||||
|
||||
protected HwmfTernaryRasterOp rasterOperation;
|
||||
|
||||
/** the source rectangle */
|
||||
protected final Rectangle2D srcBounds = new Rectangle2D.Double();
|
||||
|
||||
/** the destination rectangle */
|
||||
protected final Rectangle2D dstBounds = new Rectangle2D.Double();
|
||||
|
||||
/**
|
||||
* A variable-sized Bitmap16 Object that defines source image content.
|
||||
* This object MUST be specified, even if the raster operation does not require a source.
|
||||
*/
|
||||
HwmfBitmap16 target;
|
||||
protected HwmfBitmap16 target;
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.stretchBlt;
|
||||
}
|
||||
|
||||
|
@ -469,27 +393,23 @@ public class HwmfFill {
|
|||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
|
||||
boolean hasBitmap = (recordSize > ((recordFunction >> 8) + 3));
|
||||
|
||||
int size = 0;
|
||||
int rasterOpCode = leis.readUShort();
|
||||
int rasterOpIndex = leis.readUShort();
|
||||
|
||||
rasterOperation = HwmfTernaryRasterOp.valueOf(rasterOpIndex);
|
||||
assert(rasterOpCode == rasterOperation.opCode);
|
||||
|
||||
srcHeight = leis.readShort();
|
||||
srcWidth = leis.readShort();
|
||||
ySrc = leis.readShort();
|
||||
xSrc = leis.readShort();
|
||||
size = 6*LittleEndianConsts.SHORT_SIZE;
|
||||
int size = 2*LittleEndianConsts.SHORT_SIZE;
|
||||
|
||||
size += readBounds2(leis, srcBounds);
|
||||
|
||||
if (!hasBitmap) {
|
||||
/*int reserved =*/ leis.readShort();
|
||||
size += LittleEndianConsts.SHORT_SIZE;
|
||||
}
|
||||
destHeight = leis.readShort();
|
||||
destWidth = leis.readShort();
|
||||
yDest = leis.readShort();
|
||||
xDest = leis.readShort();
|
||||
size += 4*LittleEndianConsts.SHORT_SIZE;
|
||||
|
||||
size += readBounds2(leis, dstBounds);
|
||||
|
||||
if (hasBitmap) {
|
||||
target = new HwmfBitmap16();
|
||||
size += target.init(leis);
|
||||
|
@ -502,6 +422,15 @@ public class HwmfFill {
|
|||
public void draw(HwmfGraphics ctx) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return
|
||||
"{ rasterOperation: '"+rasterOperation+"'"+
|
||||
", srcBounds: "+boundsToString(srcBounds)+
|
||||
", dstBounds: "+boundsToString(dstBounds)+
|
||||
"}";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -511,67 +440,34 @@ public class HwmfFill {
|
|||
* The source of the color data is a DIB, and the destination of the transfer is
|
||||
* the current output region in the playback device context.
|
||||
*/
|
||||
public static class WmfStretchDib implements HwmfRecord, HwmfImageRecord, HwmfObjectTableEntry {
|
||||
public static class WmfStretchDib implements HwmfRecord, HwmfImageRecord {
|
||||
/**
|
||||
* A 32-bit unsigned integer that defines how the source pixels, the current brush in
|
||||
* the playback device context, and the destination pixels are to be combined to
|
||||
* form the new image.
|
||||
*/
|
||||
private HwmfTernaryRasterOp rasterOperation;
|
||||
protected HwmfTernaryRasterOp rasterOperation;
|
||||
|
||||
/**
|
||||
* A 16-bit unsigned integer that defines whether the Colors field of the
|
||||
* DIB contains explicit RGB values or indexes into a palette.
|
||||
*/
|
||||
private ColorUsage colorUsage;
|
||||
/**
|
||||
* A 16-bit signed integer that defines the height, in logical units, of the
|
||||
* source rectangle.
|
||||
*/
|
||||
private int srcHeight;
|
||||
/**
|
||||
* A 16-bit signed integer that defines the width, in logical units, of the
|
||||
* source rectangle.
|
||||
*/
|
||||
private int srcWidth;
|
||||
/**
|
||||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the
|
||||
* source rectangle.
|
||||
*/
|
||||
private int ySrc;
|
||||
/**
|
||||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the
|
||||
* source rectangle.
|
||||
*/
|
||||
private int xSrc;
|
||||
/**
|
||||
* A 16-bit signed integer that defines the height, in logical units, of the
|
||||
* destination rectangle.
|
||||
*/
|
||||
private int destHeight;
|
||||
/**
|
||||
* A 16-bit signed integer that defines the width, in logical units, of the
|
||||
* destination rectangle.
|
||||
*/
|
||||
private int destWidth;
|
||||
/**
|
||||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the
|
||||
* upper-left corner of the destination rectangle.
|
||||
*/
|
||||
private int yDst;
|
||||
/**
|
||||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the
|
||||
* upper-left corner of the destination rectangle.
|
||||
*/
|
||||
private int xDst;
|
||||
protected ColorUsage colorUsage;
|
||||
|
||||
/** the source rectangle. */
|
||||
protected final Rectangle2D srcBounds = new Rectangle2D.Double();
|
||||
|
||||
/** the destination rectangle. */
|
||||
protected final Rectangle2D dstBounds = new Rectangle2D.Double();
|
||||
|
||||
/**
|
||||
* A variable-sized DeviceIndependentBitmap Object (section 2.2.2.9) that is the
|
||||
* source of the color data.
|
||||
*/
|
||||
private HwmfBitmapDib dib;
|
||||
protected final HwmfBitmapDib bitmap = new HwmfBitmapDib();
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.stretchDib;
|
||||
}
|
||||
|
||||
|
@ -585,85 +481,49 @@ public class HwmfFill {
|
|||
assert(rasterOpCode == rasterOperation.opCode);
|
||||
|
||||
colorUsage = ColorUsage.valueOf(leis.readUShort());
|
||||
srcHeight = leis.readShort();
|
||||
srcWidth = leis.readShort();
|
||||
ySrc = leis.readShort();
|
||||
xSrc = leis.readShort();
|
||||
destHeight = leis.readShort();
|
||||
destWidth = leis.readShort();
|
||||
yDst = leis.readShort();
|
||||
xDst = leis.readShort();
|
||||
|
||||
int size = 11*LittleEndianConsts.SHORT_SIZE;
|
||||
dib = new HwmfBitmapDib();
|
||||
size += dib.init(leis, (int)(recordSize-6-size));
|
||||
|
||||
int size = 3*LittleEndianConsts.SHORT_SIZE;
|
||||
|
||||
size += readBounds2(leis, srcBounds);
|
||||
size += readBounds2(leis, dstBounds);
|
||||
|
||||
size += bitmap.init(leis, (int)(recordSize-6-size));
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(HwmfGraphics ctx) {
|
||||
ctx.addObjectTableEntry(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyObject(HwmfGraphics ctx) {
|
||||
|
||||
HwmfDrawProperties prop = ctx.getProperties();
|
||||
prop.setRasterOp(rasterOperation);
|
||||
if (bitmap.isValid()) {
|
||||
ctx.drawImage(getImage(), srcBounds, dstBounds);
|
||||
} else if (!dstBounds.isEmpty()) {
|
||||
BufferedImage bi = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
|
||||
ctx.drawImage(bi, new Rectangle2D.Double(0,0,100,100), dstBounds);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public BufferedImage getImage() {
|
||||
return dib.getImage();
|
||||
return bitmap.getImage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return
|
||||
"{ rasterOperation: '"+rasterOperation+"'"+
|
||||
", colorUsage: '"+colorUsage+"'"+
|
||||
", srcBounds: "+boundsToString(srcBounds)+
|
||||
", dstBounds: "+boundsToString(dstBounds)+
|
||||
"}";
|
||||
}
|
||||
}
|
||||
|
||||
public static class WmfBitBlt implements HwmfRecord {
|
||||
|
||||
/**
|
||||
* A 32-bit unsigned integer that defines how the source pixels, the current brush in the playback
|
||||
* device context, and the destination pixels are to be combined to form the new image.
|
||||
*/
|
||||
private HwmfTernaryRasterOp rasterOperation;
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the upper-left corner
|
||||
of the source rectangle.
|
||||
*/
|
||||
private int ySrc;
|
||||
/**
|
||||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the upper-left corner
|
||||
of the source rectangle.
|
||||
*/
|
||||
private int xSrc;
|
||||
/**
|
||||
* A 16-bit signed integer that defines the height, in logical units, of the source and
|
||||
destination rectangles.
|
||||
*/
|
||||
private int height;
|
||||
/**
|
||||
* A 16-bit signed integer that defines the width, in logical units, of the source and destination
|
||||
rectangles.
|
||||
*/
|
||||
private int width;
|
||||
/**
|
||||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the upper-left
|
||||
corner of the destination rectangle.
|
||||
*/
|
||||
private int yDest;
|
||||
/**
|
||||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the upper-left
|
||||
corner of the destination rectangle.
|
||||
*/
|
||||
private int xDest;
|
||||
|
||||
/**
|
||||
* A variable-sized Bitmap16 Object that defines source image content.
|
||||
* This object MUST be specified, even if the raster operation does not require a source.
|
||||
*/
|
||||
private HwmfBitmap16 target;
|
||||
public static class WmfBitBlt extends WmfStretchBlt {
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.bitBlt;
|
||||
}
|
||||
|
||||
|
@ -671,41 +531,33 @@ 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);
|
||||
}
|
||||
|
||||
srcBounds.setRect(srcPnt.getX(), srcPnt.getY(), dstBounds.getWidth(), dstBounds.getHeight());
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(HwmfGraphics ctx) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -729,36 +581,13 @@ public class HwmfFill {
|
|||
* A 16-bit unsigned integer that defines the starting scan line in the source.
|
||||
*/
|
||||
private int startScan;
|
||||
/**
|
||||
* A 16-bit unsigned integer that defines the y-coordinate, in logical units, of the
|
||||
* source rectangle.
|
||||
*/
|
||||
private int yDib;
|
||||
/**
|
||||
* A 16-bit unsigned integer that defines the x-coordinate, in logical units, of the
|
||||
* source rectangle.
|
||||
*/
|
||||
private int xDib;
|
||||
/**
|
||||
* A 16-bit unsigned integer that defines the height, in logical units, of the
|
||||
* source and destination rectangles.
|
||||
*/
|
||||
private int height;
|
||||
/**
|
||||
* A 16-bit unsigned integer that defines the width, in logical units, of the
|
||||
* source and destination rectangles.
|
||||
*/
|
||||
private int width;
|
||||
/**
|
||||
* A 16-bit unsigned integer that defines the y-coordinate, in logical units, of the
|
||||
* upper-left corner of the destination rectangle.
|
||||
*/
|
||||
private int yDest;
|
||||
/**
|
||||
* A 16-bit unsigned integer that defines the x-coordinate, in logical units, of the
|
||||
* upper-left corner of the destination rectangle.
|
||||
*/
|
||||
private int xDest;
|
||||
|
||||
/** the source rectangle */
|
||||
protected final Rectangle2D srcBounds = new Rectangle2D.Double();
|
||||
|
||||
/** the destination rectangle, having the same dimension as the source rectangle */
|
||||
protected final Rectangle2D dstBounds = new Rectangle2D.Double();
|
||||
|
||||
/**
|
||||
* A variable-sized DeviceIndependentBitmap Object that is the source of the color data.
|
||||
*/
|
||||
|
@ -766,7 +595,7 @@ public class HwmfFill {
|
|||
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.setDibToDev;
|
||||
}
|
||||
|
||||
|
@ -775,17 +604,19 @@ public class HwmfFill {
|
|||
colorUsage = ColorUsage.valueOf(leis.readUShort());
|
||||
scanCount = leis.readUShort();
|
||||
startScan = leis.readUShort();
|
||||
yDib = leis.readUShort();
|
||||
xDib = leis.readUShort();
|
||||
height = leis.readUShort();
|
||||
width = leis.readUShort();
|
||||
yDest = leis.readUShort();
|
||||
xDest = leis.readUShort();
|
||||
|
||||
int size = 9*LittleEndianConsts.SHORT_SIZE;
|
||||
|
||||
int size = 3*LittleEndianConsts.SHORT_SIZE;
|
||||
|
||||
final Point2D srcPnt = new Point2D.Double();
|
||||
size += readPointS(leis, srcPnt);
|
||||
|
||||
size += readBounds2(leis, dstBounds);
|
||||
|
||||
dib = new HwmfBitmapDib();
|
||||
size += dib.init(leis, (int)(recordSize-6-size));
|
||||
|
||||
|
||||
srcBounds.setRect(srcPnt.getX(), srcPnt.getY(), dstBounds.getWidth(), dstBounds.getHeight());
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
|
@ -806,52 +637,9 @@ public class HwmfFill {
|
|||
}
|
||||
|
||||
|
||||
public static class WmfDibBitBlt implements HwmfRecord, HwmfImageRecord, HwmfObjectTableEntry {
|
||||
|
||||
/**
|
||||
* A 32-bit unsigned integer that defines how the source pixels, the current brush
|
||||
* in the playback device context, and the destination pixels are to be combined to form the
|
||||
* new image. This code MUST be one of the values in the Ternary Raster Operation Enumeration.
|
||||
*/
|
||||
HwmfTernaryRasterOp rasterOperation;
|
||||
/**
|
||||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the source rectangle.
|
||||
*/
|
||||
private int ySrc;
|
||||
/**
|
||||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the source rectangle.
|
||||
*/
|
||||
private int xSrc;
|
||||
/**
|
||||
* A 16-bit signed integer that defines the height, in logical units, of the source and
|
||||
* destination rectangles.
|
||||
*/
|
||||
private int height;
|
||||
/**
|
||||
* A 16-bit signed integer that defines the width, in logical units, of the source and destination
|
||||
* rectangles.
|
||||
*/
|
||||
private int width;
|
||||
/**
|
||||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the upper-left
|
||||
* corner of the destination rectangle.
|
||||
*/
|
||||
private int yDest;
|
||||
/**
|
||||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the upper-left
|
||||
* corner of the destination rectangle.
|
||||
*/
|
||||
private int xDest;
|
||||
|
||||
/**
|
||||
* A variable-sized DeviceIndependentBitmap Object that defines image content.
|
||||
* This object MUST be specified, even if the raster operation does not require a source.
|
||||
*/
|
||||
private HwmfBitmapDib target;
|
||||
|
||||
|
||||
public static class WmfDibBitBlt extends WmfDibStretchBlt {
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.dibBitBlt;
|
||||
}
|
||||
|
||||
|
@ -859,48 +647,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();
|
||||
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));
|
||||
}
|
||||
|
||||
|
||||
// the destination rectangle, having the same dimension as the source rectangle
|
||||
srcBounds.setRect(srcPnt.getX(), srcPnt.getY(), dstBounds.getWidth(), dstBounds.getHeight());
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(HwmfGraphics ctx) {
|
||||
ctx.addObjectTableEntry(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyObject(HwmfGraphics ctx) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public BufferedImage getImage() {
|
||||
return (target == null) ? null : target.getImage();
|
||||
}
|
||||
}
|
||||
|
||||
public static class WmfDibStretchBlt implements HwmfRecord, HwmfImageRecord, HwmfObjectTableEntry {
|
||||
|
@ -909,53 +681,22 @@ public class HwmfFill {
|
|||
* in the playback device context, and the destination pixels are to be combined to form the
|
||||
* new image. This code MUST be one of the values in the Ternary Raster Operation Enumeration.
|
||||
*/
|
||||
private HwmfTernaryRasterOp rasterOperation;
|
||||
/**
|
||||
* A 16-bit signed integer that defines the height, in logical units, of the source rectangle.
|
||||
*/
|
||||
private int srcHeight;
|
||||
/**
|
||||
* A 16-bit signed integer that defines the width, in logical units, of the source rectangle.
|
||||
*/
|
||||
private int srcWidth;
|
||||
/**
|
||||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the
|
||||
* upper-left corner of the source rectangle.
|
||||
*/
|
||||
private int ySrc;
|
||||
/**
|
||||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the
|
||||
* upper-left corner of the source rectangle.
|
||||
*/
|
||||
private int xSrc;
|
||||
/**
|
||||
* A 16-bit signed integer that defines the height, in logical units, of the
|
||||
* destination rectangle.
|
||||
*/
|
||||
private int destHeight;
|
||||
/**
|
||||
* A 16-bit signed integer that defines the width, in logical units, of the
|
||||
* destination rectangle.
|
||||
*/
|
||||
private int destWidth;
|
||||
/**
|
||||
* A 16-bit signed integer that defines the y-coordinate, in logical units,
|
||||
* of the upper-left corner of the destination rectangle.
|
||||
*/
|
||||
private int yDest;
|
||||
/**
|
||||
* A 16-bit signed integer that defines the x-coordinate, in logical units,
|
||||
* of the upper-left corner of the destination rectangle.
|
||||
*/
|
||||
private int xDest;
|
||||
protected HwmfTernaryRasterOp rasterOperation;
|
||||
|
||||
/** the source rectangle */
|
||||
protected final Rectangle2D srcBounds = new Rectangle2D.Double();
|
||||
|
||||
/** the destination rectangle */
|
||||
protected final Rectangle2D dstBounds = new Rectangle2D.Double();
|
||||
|
||||
/**
|
||||
* A variable-sized DeviceIndependentBitmap Object that defines image content.
|
||||
* This object MUST be specified, even if the raster operation does not require a source.
|
||||
*/
|
||||
HwmfBitmapDib target;
|
||||
|
||||
protected HwmfBitmapDib target;
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.dibStretchBlt;
|
||||
}
|
||||
|
||||
|
@ -963,27 +704,21 @@ public class HwmfFill {
|
|||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
|
||||
boolean hasBitmap = (recordSize > ((recordFunction >> 8) + 3));
|
||||
|
||||
int size = 0;
|
||||
int rasterOpCode = leis.readUShort();
|
||||
int rasterOpIndex = leis.readUShort();
|
||||
|
||||
rasterOperation = HwmfTernaryRasterOp.valueOf(rasterOpIndex);
|
||||
assert(rasterOpCode == rasterOperation.opCode);
|
||||
|
||||
srcHeight = leis.readShort();
|
||||
srcWidth = leis.readShort();
|
||||
ySrc = leis.readShort();
|
||||
xSrc = leis.readShort();
|
||||
size = 6*LittleEndianConsts.SHORT_SIZE;
|
||||
int size = 2*LittleEndianConsts.SHORT_SIZE;
|
||||
|
||||
size += readBounds2(leis, srcBounds);
|
||||
if (!hasBitmap) {
|
||||
/*int reserved =*/ leis.readShort();
|
||||
size += LittleEndianConsts.SHORT_SIZE;
|
||||
}
|
||||
destHeight = leis.readShort();
|
||||
destWidth = leis.readShort();
|
||||
yDest = leis.readShort();
|
||||
xDest = leis.readShort();
|
||||
size += 4*LittleEndianConsts.SHORT_SIZE;
|
||||
|
||||
size += readBounds2(leis, dstBounds);
|
||||
if (hasBitmap) {
|
||||
target = new HwmfBitmapDib();
|
||||
size += target.init(leis, (int)(recordSize-6-size));
|
||||
|
@ -996,15 +731,30 @@ public class HwmfFill {
|
|||
public void draw(HwmfGraphics ctx) {
|
||||
ctx.addObjectTableEntry(this);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void applyObject(HwmfGraphics ctx) {
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public BufferedImage getImage() {
|
||||
return target.getImage();
|
||||
return (target != null && target.isValid()) ? target.getImage() : null;
|
||||
}
|
||||
}
|
||||
|
||||
static int readBounds2(LittleEndianInputStream leis, Rectangle2D bounds) {
|
||||
/**
|
||||
* The 16-bit signed integers that defines the corners of the bounding rectangle.
|
||||
*/
|
||||
int h = leis.readShort();
|
||||
int w = leis.readShort();
|
||||
int y = leis.readShort();
|
||||
int x = leis.readShort();
|
||||
|
||||
bounds.setRect(x, y, w, h);
|
||||
|
||||
return 4 * LittleEndianConsts.SHORT_SIZE;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -24,6 +24,8 @@ import org.apache.poi.common.usermodel.fonts.FontCharset;
|
|||
import org.apache.poi.common.usermodel.fonts.FontFamily;
|
||||
import org.apache.poi.common.usermodel.fonts.FontInfo;
|
||||
import org.apache.poi.common.usermodel.fonts.FontPitch;
|
||||
import org.apache.poi.util.BitField;
|
||||
import org.apache.poi.util.BitFieldFactory;
|
||||
import org.apache.poi.util.LittleEndianConsts;
|
||||
import org.apache.poi.util.LittleEndianInputStream;
|
||||
|
||||
|
@ -90,7 +92,7 @@ public class HwmfFont implements FontInfo {
|
|||
this.flag = flag;
|
||||
}
|
||||
|
||||
static WmfOutPrecision valueOf(int flag) {
|
||||
public static WmfOutPrecision valueOf(int flag) {
|
||||
for (WmfOutPrecision op : values()) {
|
||||
if (op.flag == flag) {
|
||||
return op;
|
||||
|
@ -104,22 +106,17 @@ public class HwmfFont implements FontInfo {
|
|||
* ClipPrecision Flags specify clipping precision, which defines how to clip characters that are
|
||||
* partially outside a clipping region. These flags can be combined to specify multiple options.
|
||||
*/
|
||||
public enum WmfClipPrecision {
|
||||
public static class WmfClipPrecision {
|
||||
|
||||
/**
|
||||
* Specifies that default clipping MUST be used.
|
||||
*/
|
||||
CLIP_DEFAULT_PRECIS (0x00000000),
|
||||
/** Specifies that default clipping MUST be used. */
|
||||
private static final BitField CLIP_DEFAULT_PRECIS = BitFieldFactory.getInstance(0x0000);
|
||||
|
||||
/**
|
||||
* This value SHOULD NOT be used.
|
||||
*/
|
||||
CLIP_CHARACTER_PRECIS (0x00000001),
|
||||
|
||||
/**
|
||||
* This value MAY be returned when enumerating rasterized, TrueType and vector fonts.
|
||||
*/
|
||||
CLIP_STROKE_PRECIS (0x00000002),
|
||||
/** This value SHOULD NOT be used. */
|
||||
private static final BitField CLIP_CHARACTER_PRECIS = BitFieldFactory.getInstance(0x0001);
|
||||
|
||||
/** This value MAY be returned when enumerating rasterized, TrueType and vector fonts. */
|
||||
private static final BitField CLIP_STROKE_PRECIS = BitFieldFactory.getInstance(0x0002);
|
||||
|
||||
/**
|
||||
* This value is used to control font rotation, as follows:
|
||||
|
@ -129,37 +126,39 @@ public class HwmfFont implements FontInfo {
|
|||
* If clear, device fonts SHOULD rotate counterclockwise, but the rotation of other fonts
|
||||
* SHOULD be determined by the orientation of the coordinate system.
|
||||
*/
|
||||
CLIP_LH_ANGLES (0x00000010),
|
||||
private static final BitField CLIP_LH_ANGLES = BitFieldFactory.getInstance(0x0010);
|
||||
|
||||
/**
|
||||
* This value SHOULD NOT be used.
|
||||
*/
|
||||
CLIP_TT_ALWAYS (0x00000020),
|
||||
/** This value SHOULD NOT be used. */
|
||||
private static final BitField CLIP_TT_ALWAYS = BitFieldFactory.getInstance(0x0020);
|
||||
|
||||
/**
|
||||
* This value specifies that font association SHOULD< be turned off.
|
||||
*/
|
||||
CLIP_DFA_DISABLE (0x00000040),
|
||||
/** This value specifies that font association SHOULD< be turned off. */
|
||||
private static final BitField CLIP_DFA_DISABLE = BitFieldFactory.getInstance(0x0040);
|
||||
|
||||
/**
|
||||
* This value specifies that font embedding MUST be used to render document content;
|
||||
* embedded fonts are read-only.
|
||||
*/
|
||||
CLIP_EMBEDDED (0x00000080);
|
||||
|
||||
private static final BitField CLIP_EMBEDDED = BitFieldFactory.getInstance(0x0080);
|
||||
|
||||
int flag;
|
||||
WmfClipPrecision(int flag) {
|
||||
this.flag = flag;
|
||||
|
||||
public int init(LittleEndianInputStream leis) {
|
||||
flag = leis.readUByte();
|
||||
return LittleEndianConsts.BYTE_SIZE;
|
||||
}
|
||||
|
||||
static WmfClipPrecision valueOf(int flag) {
|
||||
for (WmfClipPrecision cp : values()) {
|
||||
if (cp.flag == flag) {
|
||||
return cp;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@Override
|
||||
public String toString() {
|
||||
return
|
||||
(((flag&0x3) == 0 ? "default " : " ")+
|
||||
(CLIP_CHARACTER_PRECIS.isSet(flag) ? "char " : " ")+
|
||||
(CLIP_STROKE_PRECIS.isSet(flag) ? "stroke " : " ")+
|
||||
(CLIP_LH_ANGLES.isSet(flag) ? "angles " : " ")+
|
||||
(CLIP_TT_ALWAYS.isSet(flag) ? "tt_always " : " ")+
|
||||
(CLIP_DFA_DISABLE.isSet(flag) ? "dfa " : " ")+
|
||||
(CLIP_EMBEDDED.isSet(flag) ? "embedded " : " ")
|
||||
).trim()
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -210,7 +209,7 @@ public class HwmfFont implements FontInfo {
|
|||
this.flag = flag;
|
||||
}
|
||||
|
||||
static WmfFontQuality valueOf(int flag) {
|
||||
public static WmfFontQuality valueOf(int flag) {
|
||||
for (WmfFontQuality fq : values()) {
|
||||
if (fq.flag == flag) {
|
||||
return fq;
|
||||
|
@ -240,7 +239,7 @@ public class HwmfFont implements FontInfo {
|
|||
* For all height comparisons, the font mapper SHOULD find the largest physical
|
||||
* font that does not exceed the requested size.
|
||||
*/
|
||||
int height;
|
||||
protected int height;
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the average width, in logical units, of
|
||||
|
@ -248,45 +247,45 @@ public class HwmfFont implements FontInfo {
|
|||
* against the digitization aspect ratio of the available fonts to find the closest match,
|
||||
* determined by the absolute value of the difference.
|
||||
*/
|
||||
int width;
|
||||
protected int width;
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the angle, in tenths of degrees, between the
|
||||
* escapement vector and the x-axis of the device. The escapement vector is parallel
|
||||
* to the base line of a row of text.
|
||||
*/
|
||||
int escapement;
|
||||
protected int escapement;
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the angle, in tenths of degrees,
|
||||
* between each character's base line and the x-axis of the device.
|
||||
*/
|
||||
int orientation;
|
||||
protected int orientation;
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the weight of the font in the range 0
|
||||
* through 1000. For example, 400 is normal and 700 is bold. If this value is 0x0000,
|
||||
* a default weight SHOULD be used.
|
||||
*/
|
||||
int weight;
|
||||
protected int weight;
|
||||
|
||||
/**
|
||||
* A 8-bit Boolean value that specifies the italic attribute of the font.
|
||||
* 0 = not italic / 1 = italic.
|
||||
*/
|
||||
boolean italic;
|
||||
protected boolean italic;
|
||||
|
||||
/**
|
||||
* An 8-bit Boolean value that specifies the underline attribute of the font.
|
||||
* 0 = not underlined / 1 = underlined
|
||||
*/
|
||||
boolean underline;
|
||||
protected boolean underline;
|
||||
|
||||
/**
|
||||
* An 8-bit Boolean value that specifies the strike out attribute of the font.
|
||||
* 0 = not striked out / 1 = striked out
|
||||
*/
|
||||
boolean strikeOut;
|
||||
protected boolean strikeOut;
|
||||
|
||||
/**
|
||||
* An 8-bit unsigned integer that defines the character set.
|
||||
|
@ -299,12 +298,12 @@ public class HwmfFont implements FontInfo {
|
|||
* If a typeface name in the FaceName field is specified, the CharSet value MUST match the
|
||||
* character set of that typeface.
|
||||
*/
|
||||
FontCharset charSet;
|
||||
protected FontCharset charSet;
|
||||
|
||||
/**
|
||||
* An 8-bit unsigned integer that defines the output precision.
|
||||
*/
|
||||
WmfOutPrecision outPrecision;
|
||||
protected WmfOutPrecision outPrecision;
|
||||
|
||||
/**
|
||||
* An 8-bit unsigned integer that defines the clipping precision.
|
||||
|
@ -312,40 +311,40 @@ public class HwmfFont implements FontInfo {
|
|||
*
|
||||
* @see WmfClipPrecision
|
||||
*/
|
||||
WmfClipPrecision clipPrecision;
|
||||
protected final WmfClipPrecision clipPrecision = new WmfClipPrecision();
|
||||
|
||||
/**
|
||||
* An 8-bit unsigned integer that defines the output quality.
|
||||
*/
|
||||
WmfFontQuality quality;
|
||||
protected WmfFontQuality quality;
|
||||
|
||||
/**
|
||||
* A PitchAndFamily object that defines the pitch and the family of the font.
|
||||
* Font families specify the look of fonts in a general way and are intended for
|
||||
* specifying fonts when the exact typeface wanted is not available.
|
||||
*/
|
||||
int pitchAndFamily;
|
||||
protected int pitchAndFamily;
|
||||
|
||||
/**
|
||||
* Font families specify the look of fonts in a general way and are
|
||||
* intended for specifying fonts when the exact typeface wanted is not available.
|
||||
* (LSB 4 bits)
|
||||
*/
|
||||
FontFamily family;
|
||||
protected FontFamily family;
|
||||
|
||||
/**
|
||||
* A property of a font that describes the pitch (MSB 2 bits)
|
||||
*/
|
||||
FontPitch pitch;
|
||||
protected FontPitch pitch;
|
||||
|
||||
/**
|
||||
* A null-terminated string of 8-bit Latin-1 [ISO/IEC-8859-1] ANSI
|
||||
* characters that specifies the typeface name of the font. The length of this string MUST NOT
|
||||
* exceed 32 8-bit characters, including the terminating null.
|
||||
*/
|
||||
String facename;
|
||||
protected String facename;
|
||||
|
||||
public int init(LittleEndianInputStream leis) throws IOException {
|
||||
public int init(LittleEndianInputStream leis, long recordSize) throws IOException {
|
||||
height = leis.readShort();
|
||||
width = leis.readShort();
|
||||
escapement = leis.readShort();
|
||||
|
@ -356,24 +355,35 @@ public class HwmfFont implements FontInfo {
|
|||
strikeOut = leis.readByte() != 0;
|
||||
charSet = FontCharset.valueOf(leis.readUByte());
|
||||
outPrecision = WmfOutPrecision.valueOf(leis.readUByte());
|
||||
clipPrecision = WmfClipPrecision.valueOf(leis.readUByte());
|
||||
clipPrecision.init(leis);
|
||||
quality = WmfFontQuality.valueOf(leis.readUByte());
|
||||
pitchAndFamily = leis.readUByte();
|
||||
|
||||
byte buf[] = new byte[32], b, readBytes = 0;
|
||||
do {
|
||||
if (readBytes == 32) {
|
||||
throw new IOException("Font facename can't be determined.");
|
||||
}
|
||||
|
||||
buf[readBytes++] = b = leis.readByte();
|
||||
} while (b != 0 && b != -1 && readBytes <= 32);
|
||||
|
||||
facename = new String(buf, 0, readBytes-1, StandardCharsets.ISO_8859_1);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
int readBytes = readString(leis, sb, 32);
|
||||
if (readBytes == -1) {
|
||||
throw new IOException("Font facename can't be determined.");
|
||||
}
|
||||
facename = sb.toString();
|
||||
|
||||
return 5*LittleEndianConsts.SHORT_SIZE+8*LittleEndianConsts.BYTE_SIZE+readBytes;
|
||||
}
|
||||
|
||||
public void initDefaults() {
|
||||
height = -12;
|
||||
width = 0;
|
||||
escapement = 0;
|
||||
weight = 400;
|
||||
italic = false;
|
||||
underline = false;
|
||||
strikeOut = false;
|
||||
charSet = FontCharset.ANSI;
|
||||
outPrecision = WmfOutPrecision.OUT_DEFAULT_PRECIS;
|
||||
quality = WmfFontQuality.ANTIALIASED_QUALITY;
|
||||
pitchAndFamily = FontFamily.FF_DONTCARE.getFlag() | (FontPitch.DEFAULT.getNativeId() << 6);
|
||||
facename = "SansSerif";
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
@ -471,4 +481,38 @@ public class HwmfFont implements FontInfo {
|
|||
public void setCharset(FontCharset charset) {
|
||||
throw new UnsupportedOperationException("setCharset not supported by HwmfFont.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{ height: "+height+
|
||||
", width: "+width+
|
||||
", escapment: "+escapement+
|
||||
", weight: "+weight+
|
||||
", italic: "+italic+
|
||||
", underline: "+underline+
|
||||
", strikeOut: "+strikeOut+
|
||||
", charset: '"+charSet+"'"+
|
||||
", outPrecision: '"+outPrecision+"'"+
|
||||
", clipPrecision: '"+clipPrecision+"'"+
|
||||
", quality: '"+quality+"'"+
|
||||
", pitch: '"+getPitch()+"'"+
|
||||
", family: '"+getFamily()+"'"+
|
||||
", facename: '"+facename+"'"+
|
||||
"}";
|
||||
}
|
||||
|
||||
protected int readString(LittleEndianInputStream leis, StringBuilder sb, int limit) throws IOException {
|
||||
byte buf[] = new byte[limit], b, readBytes = 0;
|
||||
do {
|
||||
if (readBytes == limit) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
buf[readBytes++] = b = leis.readByte();
|
||||
} while (b != 0 && b != -1 && readBytes <= limit);
|
||||
|
||||
sb.append(new String(buf, 0, readBytes-1, StandardCharsets.ISO_8859_1));
|
||||
|
||||
return readBytes;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,14 +32,27 @@ public enum HwmfHatchStyle {
|
|||
/** +++++ - A horizontal and vertical cross-hatch. */
|
||||
HS_CROSS(0x0004),
|
||||
/** xxxxx - A 45-degree crosshatch. */
|
||||
HS_DIAGCROSS(0x0005);
|
||||
HS_DIAGCROSS(0x0005),
|
||||
/** The hatch is not a pattern, but is a solid color. */
|
||||
HS_SOLIDCLR(0x0006),
|
||||
/** The hatch is not a pattern, but is a dithered color. */
|
||||
HS_DITHEREDCLR(0x0007),
|
||||
/** The hatch is not a pattern, but is a solid color, defined by the current text (foreground) color. */
|
||||
HS_SOLIDTEXTCLR(0x0008),
|
||||
/** The hatch is not a pattern, but is a dithered color, defined by the current text (foreground) color. */
|
||||
HS_DITHEREDTEXTCLR(0x0009),
|
||||
/** The hatch is not a pattern, but is a solid color, defined by the current background color. */
|
||||
HS_SOLIDBKCLR(0x000A),
|
||||
/** The hatch is not a pattern, but is a dithered color, defined by the current background color. */
|
||||
HS_DITHEREDBKCLR(0x000B)
|
||||
;
|
||||
|
||||
int flag;
|
||||
HwmfHatchStyle(int flag) {
|
||||
this.flag = flag;
|
||||
}
|
||||
|
||||
static HwmfHatchStyle valueOf(int flag) {
|
||||
public static HwmfHatchStyle valueOf(int flag) {
|
||||
for (HwmfHatchStyle hs : values()) {
|
||||
if (hs.flag == flag) return hs;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -47,13 +49,18 @@ public class HwmfMisc {
|
|||
public void draw(HwmfGraphics ctx) {
|
||||
ctx.saveProperties();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{}";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The META_SETRELABS record is reserved and not supported.
|
||||
*/
|
||||
public static class WmfSetRelabs implements HwmfRecord {
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.setRelabs;
|
||||
}
|
||||
|
||||
|
@ -78,10 +85,10 @@ public class HwmfMisc {
|
|||
* member is positive, nSavedDC represents a specific instance of the state to be restored. If
|
||||
* this member is negative, nSavedDC represents an instance relative to the current state.
|
||||
*/
|
||||
private int nSavedDC;
|
||||
protected int nSavedDC;
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.restoreDc;
|
||||
}
|
||||
|
||||
|
@ -95,6 +102,11 @@ public class HwmfMisc {
|
|||
public void draw(HwmfGraphics ctx) {
|
||||
ctx.restoreProperties(nSavedDC);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{ nSavedDC: "+nSavedDC+" }";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -103,16 +115,15 @@ public class HwmfMisc {
|
|||
*/
|
||||
public static class WmfSetBkColor implements HwmfRecord {
|
||||
|
||||
private HwmfColorRef colorRef;
|
||||
protected final HwmfColorRef colorRef = new HwmfColorRef();
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.setBkColor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
|
||||
colorRef = new HwmfColorRef();
|
||||
return colorRef.init(leis);
|
||||
}
|
||||
|
||||
|
@ -120,6 +131,11 @@ public class HwmfMisc {
|
|||
public void draw(HwmfGraphics ctx) {
|
||||
ctx.getProperties().setBackgroundColor(colorRef);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{ colorRef: "+colorRef+" }";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -140,7 +156,7 @@ public class HwmfMisc {
|
|||
this.flag = flag;
|
||||
}
|
||||
|
||||
static HwmfBkMode valueOf(int flag) {
|
||||
public static HwmfBkMode valueOf(int flag) {
|
||||
for (HwmfBkMode bs : values()) {
|
||||
if (bs.flag == flag) return bs;
|
||||
}
|
||||
|
@ -148,9 +164,9 @@ public class HwmfMisc {
|
|||
}
|
||||
}
|
||||
|
||||
private HwmfBkMode bkMode;
|
||||
protected HwmfBkMode bkMode;
|
||||
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.setBkMode;
|
||||
}
|
||||
|
||||
|
@ -163,6 +179,11 @@ public class HwmfMisc {
|
|||
public void draw(HwmfGraphics ctx) {
|
||||
ctx.getProperties().setBkMode(bkMode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{ bkMode: '"+bkMode+"' }";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -180,7 +201,7 @@ public class HwmfMisc {
|
|||
private int layout;
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.setLayout;
|
||||
}
|
||||
|
||||
|
@ -205,10 +226,10 @@ public class HwmfMisc {
|
|||
*/
|
||||
public static class WmfSetMapMode implements HwmfRecord {
|
||||
|
||||
private HwmfMapMode mapMode;
|
||||
protected HwmfMapMode mapMode;
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.setMapMode;
|
||||
}
|
||||
|
||||
|
@ -223,6 +244,11 @@ public class HwmfMisc {
|
|||
ctx.getProperties().setMapMode(mapMode);
|
||||
ctx.updateWindowMapMode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{ mapMode: '"+mapMode+"' }";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -234,12 +260,13 @@ public class HwmfMisc {
|
|||
/**
|
||||
* A 32-bit unsigned integer that defines whether the font mapper should attempt to
|
||||
* match a font's aspect ratio to the current device's aspect ratio. If bit 0 is
|
||||
* set, the mapper selects only matching fonts.
|
||||
* set, the font mapper SHOULD select only fonts that match the aspect ratio of the
|
||||
* output device, as it is currently defined in the playback device context.
|
||||
*/
|
||||
private long mapperValues;
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.setMapperFlags;
|
||||
}
|
||||
|
||||
|
@ -253,6 +280,11 @@ public class HwmfMisc {
|
|||
public void draw(HwmfGraphics ctx) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{ mapperValues: "+mapperValues+" }";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -262,14 +294,11 @@ public class HwmfMisc {
|
|||
*/
|
||||
public static class WmfSetRop2 implements HwmfRecord {
|
||||
|
||||
/**
|
||||
* A 16-bit unsigned integer that defines the foreground binary raster
|
||||
* operation mixing mode
|
||||
*/
|
||||
private HwmfBinaryRasterOp drawMode;
|
||||
/** An unsigned integer that defines the foreground binary raster operation mixing mode */
|
||||
protected HwmfBinaryRasterOp drawMode;
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.setRop2;
|
||||
}
|
||||
|
||||
|
@ -283,6 +312,11 @@ public class HwmfMisc {
|
|||
public void draw(HwmfGraphics ctx) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{ drawMode: '"+drawMode+"' }";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -291,24 +325,63 @@ public class HwmfMisc {
|
|||
*/
|
||||
public static class WmfSetStretchBltMode implements HwmfRecord {
|
||||
|
||||
/**
|
||||
* A 16-bit unsigned integer that defines bitmap stretching mode.
|
||||
* This MUST be one of the values:
|
||||
* BLACKONWHITE = 0x0001,
|
||||
* WHITEONBLACK = 0x0002,
|
||||
* COLORONCOLOR = 0x0003,
|
||||
* HALFTONE = 0x0004
|
||||
*/
|
||||
private int setStretchBltMode;
|
||||
public enum StretchBltMode {
|
||||
/**
|
||||
* Performs a Boolean AND operation by using the color values for the eliminated and existing pixels.
|
||||
* If the bitmap is a monochrome bitmap, this mode preserves black pixels at the expense of white pixels.
|
||||
*
|
||||
* EMF name: STRETCH_ANDSCANS
|
||||
*/
|
||||
BLACKONWHITE(0x0001),
|
||||
/**
|
||||
* Performs a Boolean OR operation by using the color values for the eliminated and existing pixels.
|
||||
* If the bitmap is a monochrome bitmap, this mode preserves white pixels at the expense of black pixels.
|
||||
*
|
||||
* EMF name: STRETCH_ORSCANS
|
||||
*/
|
||||
WHITEONBLACK(0x0002),
|
||||
/**
|
||||
* Deletes the pixels. This mode deletes all eliminated lines of pixels without trying
|
||||
* to preserve their information.
|
||||
*
|
||||
* EMF name: STRETCH_DELETESCANS
|
||||
*/
|
||||
COLORONCOLOR(0x0003),
|
||||
/**
|
||||
* Maps pixels from the source rectangle into blocks of pixels in the destination rectangle.
|
||||
* The average color over the destination block of pixels approximates the color of the source
|
||||
* pixels.
|
||||
*
|
||||
* After setting the HALFTONE stretching mode, the brush origin MUST be set to avoid misalignment
|
||||
* artifacts - in EMF this is done via EmfSetBrushOrgEx
|
||||
*
|
||||
* EMF name: STRETCH_HALFTONE
|
||||
*/
|
||||
HALFTONE(0x0004);
|
||||
|
||||
public final int flag;
|
||||
StretchBltMode(int flag) {
|
||||
this.flag = flag;
|
||||
}
|
||||
|
||||
public static StretchBltMode valueOf(int flag) {
|
||||
for (StretchBltMode bs : values()) {
|
||||
if (bs.flag == flag) return bs;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected StretchBltMode stretchBltMode;
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.setStretchBltMode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
|
||||
setStretchBltMode = leis.readUShort();
|
||||
stretchBltMode = StretchBltMode.valueOf(leis.readUShort());
|
||||
return LittleEndianConsts.SHORT_SIZE;
|
||||
}
|
||||
|
||||
|
@ -316,6 +389,11 @@ public class HwmfMisc {
|
|||
public void draw(HwmfGraphics ctx) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{ stretchBltMode: '"+stretchBltMode+"' }";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -324,7 +402,7 @@ public class HwmfMisc {
|
|||
*/
|
||||
public static class WmfDibCreatePatternBrush implements HwmfRecord, HwmfImageRecord, HwmfObjectTableEntry {
|
||||
|
||||
private HwmfBrushStyle style;
|
||||
protected HwmfBrushStyle style;
|
||||
|
||||
/**
|
||||
* A 16-bit unsigned integer that defines whether the Colors field of a DIB
|
||||
|
@ -335,13 +413,13 @@ public class HwmfMisc {
|
|||
*
|
||||
* If the Style field specified anything but BS_PATTERN, this field MUST be one of the ColorUsage values.
|
||||
*/
|
||||
private ColorUsage colorUsage;
|
||||
protected ColorUsage colorUsage;
|
||||
|
||||
private HwmfBitmapDib patternDib;
|
||||
protected HwmfBitmapDib patternDib;
|
||||
private HwmfBitmap16 pattern16;
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.dibCreatePatternBrush;
|
||||
}
|
||||
|
||||
|
@ -376,6 +454,9 @@ public class HwmfMisc {
|
|||
|
||||
@Override
|
||||
public void applyObject(HwmfGraphics ctx) {
|
||||
if (patternDib != null && !patternDib.isValid()) {
|
||||
return;
|
||||
}
|
||||
HwmfDrawProperties prop = ctx.getProperties();
|
||||
prop.setBrushStyle(style);
|
||||
prop.setBrushBitmap(getImage());
|
||||
|
@ -383,7 +464,7 @@ public class HwmfMisc {
|
|||
|
||||
@Override
|
||||
public BufferedImage getImage() {
|
||||
if (patternDib != null) {
|
||||
if (patternDib != null && patternDib.isValid()) {
|
||||
return patternDib.getImage();
|
||||
} else if (pattern16 != null) {
|
||||
return pattern16.getImage();
|
||||
|
@ -403,10 +484,10 @@ public class HwmfMisc {
|
|||
* A 16-bit unsigned integer used to index into the WMF Object Table to
|
||||
* get the object to be deleted.
|
||||
*/
|
||||
private int objectIndex;
|
||||
protected int objectIndex;
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.deleteObject;
|
||||
}
|
||||
|
||||
|
@ -418,8 +499,19 @@ public class HwmfMisc {
|
|||
|
||||
@Override
|
||||
public void draw(HwmfGraphics ctx) {
|
||||
/* TODO:
|
||||
* The object specified by this record MUST be deleted from the EMF Object Table.
|
||||
* If the deleted object is currently selected in the playback device context,
|
||||
* the default object for that graphics property MUST be restored.
|
||||
*/
|
||||
|
||||
ctx.unsetObjectTableEntry(objectIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{ index: "+objectIndex+" }";
|
||||
}
|
||||
}
|
||||
|
||||
public static class WmfCreatePatternBrush implements HwmfRecord, HwmfObjectTableEntry {
|
||||
|
@ -427,7 +519,7 @@ public class HwmfMisc {
|
|||
private HwmfBitmap16 pattern;
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.createPatternBrush;
|
||||
}
|
||||
|
||||
|
@ -452,30 +544,28 @@ public class HwmfMisc {
|
|||
|
||||
public static class WmfCreatePenIndirect implements HwmfRecord, HwmfObjectTableEntry {
|
||||
|
||||
private HwmfPenStyle penStyle;
|
||||
/**
|
||||
* A 32-bit PointS Object that specifies a point for the object dimensions.
|
||||
* The x-coordinate is the pen width. The y-coordinate is ignored.
|
||||
*/
|
||||
private int xWidth;
|
||||
@SuppressWarnings("unused")
|
||||
private int yWidth;
|
||||
protected HwmfPenStyle penStyle;
|
||||
|
||||
protected final Dimension2D dimension = new Dimension2DDouble();
|
||||
/**
|
||||
* A 32-bit ColorRef Object that specifies the pen color value.
|
||||
*/
|
||||
private HwmfColorRef colorRef;
|
||||
protected final HwmfColorRef colorRef = new HwmfColorRef();
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.createPenIndirect;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
|
||||
penStyle = HwmfPenStyle.valueOf(leis.readUShort());
|
||||
xWidth = leis.readShort();
|
||||
yWidth = leis.readShort();
|
||||
colorRef = new HwmfColorRef();
|
||||
// A 32-bit PointS Object that specifies a point for the object dimensions.
|
||||
// The x-coordinate is the pen width. The y-coordinate is ignored.
|
||||
int xWidth = leis.readShort();
|
||||
int yWidth = leis.readShort();
|
||||
dimension.setSize(xWidth, yWidth);
|
||||
|
||||
int size = colorRef.init(leis);
|
||||
return size+3*LittleEndianConsts.SHORT_SIZE;
|
||||
}
|
||||
|
@ -490,7 +580,15 @@ public class HwmfMisc {
|
|||
HwmfDrawProperties p = ctx.getProperties();
|
||||
p.setPenStyle(penStyle);
|
||||
p.setPenColor(colorRef);
|
||||
p.setPenWidth(xWidth);
|
||||
p.setPenWidth(dimension.getWidth());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return
|
||||
"{ penStyle: "+penStyle+
|
||||
", dimension: { width: "+dimension.getWidth()+", height: "+dimension.getHeight()+" }"+
|
||||
", colorRef: "+colorRef+"}";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -540,19 +638,14 @@ public class HwmfMisc {
|
|||
* </table>
|
||||
*/
|
||||
public static class WmfCreateBrushIndirect implements HwmfRecord, HwmfObjectTableEntry {
|
||||
private HwmfBrushStyle brushStyle;
|
||||
protected HwmfBrushStyle brushStyle;
|
||||
|
||||
private HwmfColorRef colorRef;
|
||||
protected HwmfColorRef colorRef;
|
||||
|
||||
/**
|
||||
* A 16-bit field that specifies the brush hatch type.
|
||||
* Its interpretation depends on the value of BrushStyle.
|
||||
*
|
||||
*/
|
||||
private HwmfHatchStyle brushHatch;
|
||||
protected HwmfHatchStyle brushHatch;
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.createBrushIndirect;
|
||||
}
|
||||
|
||||
|
@ -577,5 +670,13 @@ public class HwmfMisc {
|
|||
p.setBrushColor(colorRef);
|
||||
p.setBrushHatch(brushHatch);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return
|
||||
"{ brushStyle: '"+brushStyle+"'"+
|
||||
", colorRef: "+colorRef+
|
||||
", brushHatch: '"+brushHatch+"' }";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,12 +136,13 @@ public class HwmfPenStyle implements Cloneable {
|
|||
}
|
||||
}
|
||||
|
||||
private static final BitField SUBSECTION_DASH = BitFieldFactory.getInstance(0x0007);
|
||||
private static final BitField SUBSECTION_ALTERNATE = BitFieldFactory.getInstance(0x0008);
|
||||
private static final BitField SUBSECTION_ENDCAP = BitFieldFactory.getInstance(0x0300);
|
||||
private static final BitField SUBSECTION_JOIN = BitFieldFactory.getInstance(0x3000);
|
||||
|
||||
private int flag;
|
||||
private static final BitField SUBSECTION_DASH = BitFieldFactory.getInstance(0x00007);
|
||||
private static final BitField SUBSECTION_ALTERNATE = BitFieldFactory.getInstance(0x00008);
|
||||
private static final BitField SUBSECTION_ENDCAP = BitFieldFactory.getInstance(0x00300);
|
||||
private static final BitField SUBSECTION_JOIN = BitFieldFactory.getInstance(0x03000);
|
||||
private static final BitField SUBSECTION_GEOMETRIC = BitFieldFactory.getInstance(0x10000);
|
||||
|
||||
protected int flag;
|
||||
|
||||
public static HwmfPenStyle valueOf(int flag) {
|
||||
HwmfPenStyle ps = new HwmfPenStyle();
|
||||
|
@ -160,7 +161,16 @@ public class HwmfPenStyle implements Cloneable {
|
|||
public HwmfLineDash getLineDash() {
|
||||
return HwmfLineDash.valueOf(SUBSECTION_DASH.getValue(flag));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convienence method which should be used instead of accessing {@link HwmfLineDash#dashes}
|
||||
* directly, so an subclass can provide user-style dashes
|
||||
*
|
||||
* @return the dash pattern
|
||||
*/
|
||||
public float[] getLineDashes() {
|
||||
return getLineDash().dashes;
|
||||
}
|
||||
|
||||
/**
|
||||
* The pen sets every other pixel (this style is applicable only for cosmetic pens).
|
||||
|
@ -169,6 +179,14 @@ public class HwmfPenStyle implements Cloneable {
|
|||
return SUBSECTION_ALTERNATE.isSet(flag);
|
||||
}
|
||||
|
||||
/**
|
||||
* A pen type that specifies a line with a width that is measured in logical units
|
||||
* and a style that can contain any of the attributes of a brush.
|
||||
*/
|
||||
public boolean isGeometric() {
|
||||
return SUBSECTION_GEOMETRIC.isSet(flag);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new object of the same class and with the
|
||||
|
@ -186,4 +204,15 @@ public class HwmfPenStyle implements Cloneable {
|
|||
throw new InternalError();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return
|
||||
"{ lineCap: '"+getLineCap()+"'"+
|
||||
", lineDash: '"+getLineDash()+"'"+
|
||||
", lineJoin: '"+getLineJoin()+"'"+
|
||||
(isAlternateDash()?", alternateDash: true ":"")+
|
||||
(isGeometric()?", geometric: true ":"")+
|
||||
"}";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
==================================================================== */
|
||||
|
||||
package org.apache.poi.hwmf.record;
|
||||
|
||||
import org.apache.poi.hemf.record.emf.HemfFill;
|
||||
|
||||
public enum HwmfRegionMode {
|
||||
/**
|
||||
* The new clipping region includes the intersection (overlapping areas)
|
||||
* of the current clipping region and the current path (or new region).
|
||||
*/
|
||||
RGN_AND(0x01),
|
||||
/**
|
||||
* The new clipping region includes the union (combined areas)
|
||||
* of the current clipping region and the current path (or new region).
|
||||
*/
|
||||
RGN_OR(0x02),
|
||||
/**
|
||||
* The new clipping region includes the union of the current clipping region
|
||||
* and the current path (or new region) but without the overlapping areas
|
||||
*/
|
||||
RGN_XOR(0x03),
|
||||
/**
|
||||
* The new clipping region includes the areas of the current clipping region
|
||||
* with those of the current path (or new region) excluded.
|
||||
*/
|
||||
RGN_DIFF(0x04),
|
||||
/**
|
||||
* The new clipping region is the current path (or the new region).
|
||||
*/
|
||||
RGN_COPY(0x05);
|
||||
|
||||
int flag;
|
||||
HwmfRegionMode(int flag) {
|
||||
this.flag = flag;
|
||||
}
|
||||
|
||||
public static HwmfRegionMode valueOf(int flag) {
|
||||
for (HwmfRegionMode rm : values()) {
|
||||
if (rm.flag == flag) return rm;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -17,10 +17,19 @@
|
|||
|
||||
package org.apache.poi.hwmf.record;
|
||||
|
||||
import static org.apache.poi.hwmf.record.HwmfDraw.boundsToString;
|
||||
import static org.apache.poi.hwmf.record.HwmfDraw.pointToString;
|
||||
import static org.apache.poi.hwmf.record.HwmfDraw.readPointS;
|
||||
import static org.apache.poi.hwmf.record.HwmfDraw.readRectS;
|
||||
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.codec.Charsets;
|
||||
import org.apache.poi.hwmf.draw.HwmfDrawProperties;
|
||||
import org.apache.poi.hwmf.draw.HwmfGraphics;
|
||||
import org.apache.poi.hwmf.record.HwmfMisc.WmfSetMapMode;
|
||||
|
@ -29,6 +38,7 @@ import org.apache.poi.util.BitFieldFactory;
|
|||
import org.apache.poi.util.IOUtils;
|
||||
import org.apache.poi.util.LittleEndianConsts;
|
||||
import org.apache.poi.util.LittleEndianInputStream;
|
||||
import org.apache.poi.util.LocaleUtil;
|
||||
import org.apache.poi.util.POILogFactory;
|
||||
import org.apache.poi.util.POILogger;
|
||||
|
||||
|
@ -52,7 +62,7 @@ public class HwmfText {
|
|||
private int charExtra;
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.setTextCharExtra;
|
||||
}
|
||||
|
||||
|
@ -73,16 +83,15 @@ public class HwmfText {
|
|||
*/
|
||||
public static class WmfSetTextColor implements HwmfRecord {
|
||||
|
||||
private HwmfColorRef colorRef;
|
||||
protected final HwmfColorRef colorRef = new HwmfColorRef();
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.setTextColor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
|
||||
colorRef = new HwmfColorRef();
|
||||
return colorRef.init(leis);
|
||||
}
|
||||
|
||||
|
@ -90,6 +99,11 @@ public class HwmfText {
|
|||
public void draw(HwmfGraphics ctx) {
|
||||
ctx.getProperties().setTextColor(colorRef);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{ colorRef: "+colorRef+" }";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -112,7 +126,7 @@ public class HwmfText {
|
|||
private int breakExtra;
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.setBkColor;
|
||||
}
|
||||
|
||||
|
@ -147,19 +161,11 @@ public class HwmfText {
|
|||
* The string is written at the location specified by the XStart and YStart fields.
|
||||
*/
|
||||
private byte[] rawTextBytes;
|
||||
/**
|
||||
* A 16-bit signed integer that defines the vertical (y-axis) coordinate, in logical
|
||||
* units, of the point where drawing is to start.
|
||||
*/
|
||||
private int yStart;
|
||||
/**
|
||||
* A 16-bit signed integer that defines the horizontal (x-axis) coordinate, in
|
||||
* logical units, of the point where drawing is to start.
|
||||
*/
|
||||
private int xStart;
|
||||
|
||||
|
||||
protected Point2D reference = new Point2D.Double();
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.textOut;
|
||||
}
|
||||
|
||||
|
@ -168,15 +174,19 @@ public class HwmfText {
|
|||
stringLength = leis.readShort();
|
||||
rawTextBytes = IOUtils.safelyAllocate(stringLength+(stringLength&1), MAX_RECORD_LENGTH);
|
||||
leis.readFully(rawTextBytes);
|
||||
yStart = leis.readShort();
|
||||
xStart = leis.readShort();
|
||||
// A 16-bit signed integer that defines the vertical (y-axis) coordinate, in logical
|
||||
// units, of the point where drawing is to start.
|
||||
int yStart = leis.readShort();
|
||||
// A 16-bit signed integer that defines the horizontal (x-axis) coordinate, in
|
||||
// logical units, of the point where drawing is to start.
|
||||
int xStart = leis.readShort();
|
||||
reference.setLocation(xStart, yStart);
|
||||
return 3*LittleEndianConsts.SHORT_SIZE+rawTextBytes.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(HwmfGraphics ctx) {
|
||||
Rectangle2D bounds = new Rectangle2D.Double(xStart, yStart, 0, 0);
|
||||
ctx.drawString(getTextBytes(), bounds);
|
||||
ctx.drawString(getTextBytes(), stringLength, reference);
|
||||
}
|
||||
|
||||
public String getText(Charset charset) {
|
||||
|
@ -195,40 +205,47 @@ public class HwmfText {
|
|||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The META_EXTTEXTOUT record outputs text by using the font, background color, and text color that
|
||||
* are defined in the playback device context. Optionally, dimensions can be provided for clipping,
|
||||
* opaquing, or both.
|
||||
*/
|
||||
public static class WmfExtTextOut implements HwmfRecord {
|
||||
|
||||
public static class WmfExtTextOutOptions {
|
||||
/**
|
||||
* Indicates that the background color that is defined in the playback device context
|
||||
* Indicates that the background color that is defined in the playback device context
|
||||
* SHOULD be used to fill the rectangle.
|
||||
*/
|
||||
*/
|
||||
private static final BitField ETO_OPAQUE = BitFieldFactory.getInstance(0x0002);
|
||||
|
||||
|
||||
/**
|
||||
* Indicates that the text SHOULD be clipped to the rectangle.
|
||||
*/
|
||||
private static final BitField ETO_CLIPPED = BitFieldFactory.getInstance(0x0004);
|
||||
|
||||
/**
|
||||
* Indicates that the string to be output SHOULD NOT require further processing
|
||||
* with respect to the placement of the characters, and an array of character
|
||||
* placement values SHOULD be provided. This character placement process is
|
||||
* Indicates that the string to be output SHOULD NOT require further processing
|
||||
* with respect to the placement of the characters, and an array of character
|
||||
* placement values SHOULD be provided. This character placement process is
|
||||
* useful for fonts in which diacritical characters affect character spacing.
|
||||
*/
|
||||
private static final BitField ETO_GLYPH_INDEX = BitFieldFactory.getInstance(0x0010);
|
||||
|
||||
/**
|
||||
* Indicates that the text MUST be laid out in right-to-left reading order, instead of
|
||||
* the default left-to-right order. This SHOULD be applied only when the font that is
|
||||
* Indicates that the text MUST be laid out in right-to-left reading order, instead of
|
||||
* the default left-to-right order. This SHOULD be applied only when the font that is
|
||||
* defined in the playback device context is either Hebrew or Arabic.
|
||||
*/
|
||||
private static final BitField ETO_RTLREADING = BitFieldFactory.getInstance(0x0080);
|
||||
|
||||
/**
|
||||
* This bit indicates that the record does not specify a bounding rectangle for the
|
||||
* text output.
|
||||
*/
|
||||
private static final BitField ETO_NO_RECT = BitFieldFactory.getInstance(0x0100);
|
||||
|
||||
/**
|
||||
* This bit indicates that the codes for characters in an output text string are 8 bits,
|
||||
* derived from the low bytes of 16-bit Unicode UTF16-LE character codes, in which
|
||||
* the high byte is assumed to be 0.
|
||||
*/
|
||||
private static final BitField ETO_SMALL_CHARS = BitFieldFactory.getInstance(0x0200);
|
||||
|
||||
/**
|
||||
* Indicates that to display numbers, digits appropriate to the locale SHOULD be used.
|
||||
*/
|
||||
|
@ -240,32 +257,62 @@ public class HwmfText {
|
|||
private static final BitField ETO_NUMERICSLATIN = BitFieldFactory.getInstance(0x0800);
|
||||
|
||||
/**
|
||||
* Indicates that both horizontal and vertical character displacement values
|
||||
* This bit indicates that no special operating system processing for glyph placement
|
||||
* should be performed on right-to-left strings; that is, all glyph positioning
|
||||
* SHOULD be taken care of by drawing and state records in the metafile
|
||||
*/
|
||||
private static final BitField ETO_IGNORELANGUAGE = BitFieldFactory.getInstance(0x1000);
|
||||
|
||||
/**
|
||||
* Indicates that both horizontal and vertical character displacement values
|
||||
* SHOULD be provided.
|
||||
*/
|
||||
private static final BitField ETO_PDY = BitFieldFactory.getInstance(0x2000);
|
||||
|
||||
/** This bit is reserved and SHOULD NOT be used. */
|
||||
private static final BitField ETO_REVERSE_INDEX_MAP = BitFieldFactory.getInstance(0x10000);
|
||||
|
||||
protected int flag;
|
||||
|
||||
public int init(LittleEndianInputStream leis) {
|
||||
flag = leis.readUShort();
|
||||
return LittleEndianConsts.SHORT_SIZE;
|
||||
}
|
||||
|
||||
public boolean isOpaque() {
|
||||
return ETO_OPAQUE.isSet(flag);
|
||||
}
|
||||
|
||||
public boolean isClipped() {
|
||||
return ETO_CLIPPED.isSet(flag);
|
||||
}
|
||||
|
||||
public boolean isYDisplaced() {
|
||||
return ETO_PDY.isSet(flag);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The META_EXTTEXTOUT record outputs text by using the font, background color, and text color that
|
||||
* are defined in the playback device context. Optionally, dimensions can be provided for clipping,
|
||||
* opaquing, or both.
|
||||
*/
|
||||
public static class WmfExtTextOut implements HwmfRecord {
|
||||
/**
|
||||
* A 16-bit signed integer that defines the y-coordinate, in logical units, where the
|
||||
text string is to be located.
|
||||
* The location, in logical units, where the text string is to be placed.
|
||||
*/
|
||||
private int y;
|
||||
/**
|
||||
* A 16-bit signed integer that defines the x-coordinate, in logical units, where the
|
||||
text string is to be located.
|
||||
*/
|
||||
private int x;
|
||||
protected final Point2D reference = new Point2D.Double();
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the length of the string.
|
||||
*/
|
||||
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 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_*)
|
||||
*/
|
||||
protected final WmfExtTextOutOptions options;
|
||||
/**
|
||||
* An optional 8-byte Rect Object (section 2.2.2.18) that defines the
|
||||
* dimensions, in logical coordinates, of a rectangle that is used for clipping, opaquing, or both.
|
||||
|
@ -274,24 +321,32 @@ public class HwmfText {
|
|||
* Each value is a 16-bit signed integer that defines the coordinate, in logical coordinates, of
|
||||
* the upper-left corner of the rectangle
|
||||
*/
|
||||
private int left,top,right,bottom;
|
||||
protected final Rectangle2D bounds = new Rectangle2D.Double();
|
||||
/**
|
||||
* A variable-length string that specifies the text to be drawn. The string does
|
||||
* not need to be null-terminated, because StringLength specifies the length of the string. If
|
||||
* the length is odd, an extra byte is placed after it so that the following member (optional Dx) is
|
||||
* aligned on a 16-bit boundary.
|
||||
*/
|
||||
private byte[] rawTextBytes;
|
||||
protected byte[] rawTextBytes;
|
||||
/**
|
||||
* An optional array of 16-bit signed integers that indicate the distance between
|
||||
* origins of adjacent character cells. For example, Dx[i] logical units separate the origins of
|
||||
* character cell i and character cell i + 1. If this field is present, there MUST be the same
|
||||
* number of values as there are characters in the string.
|
||||
*/
|
||||
private int dx[];
|
||||
|
||||
protected final List<Integer> dx = new ArrayList<>();
|
||||
|
||||
public WmfExtTextOut() {
|
||||
this(new WmfExtTextOutOptions());
|
||||
}
|
||||
|
||||
protected WmfExtTextOut(WmfExtTextOutOptions options) {
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.extTextOut;
|
||||
}
|
||||
|
||||
|
@ -299,22 +354,17 @@ public class HwmfText {
|
|||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
|
||||
// -6 bytes of record function and length header
|
||||
final int remainingRecordSize = (int)(recordSize-6);
|
||||
|
||||
y = leis.readShort();
|
||||
x = leis.readShort();
|
||||
|
||||
int size = readPointS(leis, reference);
|
||||
|
||||
stringLength = leis.readShort();
|
||||
fwOpts = leis.readUShort();
|
||||
|
||||
int size = 4*LittleEndianConsts.SHORT_SIZE;
|
||||
|
||||
size += LittleEndianConsts.SHORT_SIZE;
|
||||
size += options.init(leis);
|
||||
|
||||
// Check if we have a rectangle
|
||||
if ((ETO_OPAQUE.isSet(fwOpts) || ETO_CLIPPED.isSet(fwOpts)) && size+8<=remainingRecordSize) {
|
||||
// the bounding rectangle is optional and only read when fwOpts are given
|
||||
left = leis.readShort();
|
||||
top = leis.readShort();
|
||||
right = leis.readShort();
|
||||
bottom = leis.readShort();
|
||||
size += 4*LittleEndianConsts.SHORT_SIZE;
|
||||
if ((options.isOpaque() || options.isClipped()) && size+8<=remainingRecordSize) {
|
||||
// the bounding rectangle is optional and only read when options are given
|
||||
size += readRectS(leis, bounds);
|
||||
}
|
||||
|
||||
rawTextBytes = IOUtils.safelyAllocate(stringLength+(stringLength&1), MAX_RECORD_LENGTH);
|
||||
|
@ -331,9 +381,8 @@ public class HwmfText {
|
|||
logger.log(POILogger.WARN, "META_EXTTEXTOUT tracking info doesn't cover all characters");
|
||||
}
|
||||
|
||||
dx = new int[stringLength];
|
||||
for (int i=0; i<dxLen; i++) {
|
||||
dx[i] = leis.readShort();
|
||||
dx.add((int)leis.readShort());
|
||||
size += LittleEndianConsts.SHORT_SIZE;
|
||||
}
|
||||
|
||||
|
@ -342,24 +391,38 @@ public class HwmfText {
|
|||
|
||||
@Override
|
||||
public void draw(HwmfGraphics ctx) {
|
||||
Rectangle2D bounds = new Rectangle2D.Double(x, y, 0, 0);
|
||||
ctx.drawString(getTextBytes(), bounds, dx);
|
||||
ctx.drawString(rawTextBytes, stringLength, reference, null, bounds, options, dx, false);
|
||||
}
|
||||
|
||||
public String getText(Charset charset) {
|
||||
return new String(getTextBytes(), charset);
|
||||
public String getText(Charset charset) throws IOException {
|
||||
return new String(rawTextBytes, charset).substring(0, stringLength);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return a copy of a trimmed byte array of rawTextBytes bytes.
|
||||
* This includes only the bytes from 0..stringLength.
|
||||
* This does not include the extra optional padding on the byte array.
|
||||
*/
|
||||
private byte[] getTextBytes() {
|
||||
byte[] ret = IOUtils.safelyAllocate(stringLength, MAX_RECORD_LENGTH);
|
||||
System.arraycopy(rawTextBytes, 0, ret, 0, stringLength);
|
||||
return ret;
|
||||
public Point2D getReference() {
|
||||
return reference;
|
||||
}
|
||||
|
||||
public Rectangle2D getBounds() {
|
||||
return bounds;
|
||||
}
|
||||
|
||||
protected boolean isUnicode() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String text = "";
|
||||
try {
|
||||
text = getText(isUnicode() ? Charsets.UTF_16LE : LocaleUtil.CHARSET_1252);
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
|
||||
return
|
||||
"{ reference: " + pointToString(reference) +
|
||||
", bounds: " + boundsToString(bounds) +
|
||||
", text: '"+text.replaceAll("\\p{Cntrl}",".")+"'"+
|
||||
"}";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -380,57 +443,17 @@ public class HwmfText {
|
|||
*/
|
||||
public static class WmfSetTextAlign implements HwmfRecord {
|
||||
|
||||
// ***********************************************************************************
|
||||
// TextAlignmentMode Flags:
|
||||
// ***********************************************************************************
|
||||
|
||||
/**
|
||||
* The drawing position in the playback device context MUST NOT be updated after each
|
||||
* text output call. The reference point MUST be passed to the text output function.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
private static final BitField TA_NOUPDATECP = BitFieldFactory.getInstance(0x0000);
|
||||
|
||||
/**
|
||||
* The reference point MUST be on the left edge of the bounding rectangle.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
private static final BitField TA_LEFT = BitFieldFactory.getInstance(0x0000);
|
||||
|
||||
/**
|
||||
* The reference point MUST be on the top edge of the bounding rectangle.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
private static final BitField TA_TOP = BitFieldFactory.getInstance(0x0000);
|
||||
|
||||
/**
|
||||
* The drawing position in the playback device context MUST be updated after each text
|
||||
* output call. It MUST be used as the reference point.
|
||||
* output call. It MUST be used as the reference point.<p>
|
||||
*
|
||||
* If the flag is not set, the option TA_NOUPDATECP is active, i.e. the drawing position
|
||||
* in the playback device context MUST NOT be updated after each text output call.
|
||||
* The reference point MUST be passed to the text output function.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
private static final BitField TA_UPDATECP = BitFieldFactory.getInstance(0x0001);
|
||||
|
||||
/**
|
||||
* The reference point MUST be on the right edge of the bounding rectangle.
|
||||
*/
|
||||
private static final BitField TA_RIGHT = BitFieldFactory.getInstance(0x0002);
|
||||
|
||||
/**
|
||||
* The reference point MUST be aligned horizontally with the center of the bounding
|
||||
* rectangle.
|
||||
*/
|
||||
private static final BitField TA_CENTER = BitFieldFactory.getInstance(0x0006);
|
||||
|
||||
/**
|
||||
* The reference point MUST be on the bottom edge of the bounding rectangle.
|
||||
*/
|
||||
private static final BitField TA_BOTTOM = BitFieldFactory.getInstance(0x0008);
|
||||
|
||||
/**
|
||||
* The reference point MUST be on the baseline of the text.
|
||||
*/
|
||||
private static final BitField TA_BASELINE = BitFieldFactory.getInstance(0x0018);
|
||||
|
||||
|
||||
/**
|
||||
* The text MUST be laid out in right-to-left reading order, instead of the default
|
||||
* left-to-right order. This SHOULD be applied only when the font that is defined in the
|
||||
|
@ -438,43 +461,64 @@ public class HwmfText {
|
|||
*/
|
||||
@SuppressWarnings("unused")
|
||||
private static final BitField TA_RTLREADING = BitFieldFactory.getInstance(0x0100);
|
||||
|
||||
// ***********************************************************************************
|
||||
// VerticalTextAlignmentMode Flags (e.g. for Kanji fonts)
|
||||
// ***********************************************************************************
|
||||
|
||||
|
||||
|
||||
private static final BitField ALIGN_MASK = BitFieldFactory.getInstance(0x0006);
|
||||
|
||||
/**
|
||||
* The reference point MUST be on the top edge of the bounding rectangle.
|
||||
* Flag TA_LEFT (0x0000):
|
||||
* The reference point MUST be on the left edge of the bounding rectangle,
|
||||
* if all bits of the align mask (latin mode) are unset.
|
||||
*
|
||||
* Flag VTA_TOP (0x0000):
|
||||
* The reference point MUST be on the top edge of the bounding rectangle,
|
||||
* if all bits of the valign mask are unset.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
private static final BitField VTA_TOP = BitFieldFactory.getInstance(0x0000);
|
||||
|
||||
private static final int ALIGN_LEFT = 0;
|
||||
|
||||
/**
|
||||
* Flag TA_RIGHT (0x0002):
|
||||
* The reference point MUST be on the right edge of the bounding rectangle.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
private static final BitField VTA_RIGHT = BitFieldFactory.getInstance(0x0000);
|
||||
|
||||
/**
|
||||
*
|
||||
* Flag VTA_BOTTOM (0x0002):
|
||||
* The reference point MUST be on the bottom edge of the bounding rectangle.
|
||||
*/
|
||||
private static final BitField VTA_BOTTOM = BitFieldFactory.getInstance(0x0002);
|
||||
|
||||
private static final int ALIGN_RIGHT = 1;
|
||||
|
||||
/**
|
||||
* The reference point MUST be aligned vertically with the center of the bounding
|
||||
* Flag TA_CENTER (0x0006) / VTA_CENTER (0x0006):
|
||||
* The reference point MUST be aligned horizontally with the center of the bounding
|
||||
* rectangle.
|
||||
*/
|
||||
private static final BitField VTA_CENTER = BitFieldFactory.getInstance(0x0006);
|
||||
|
||||
private static final int ALIGN_CENTER = 3;
|
||||
|
||||
private static final BitField VALIGN_MASK = BitFieldFactory.getInstance(0x0018);
|
||||
|
||||
/**
|
||||
* Flag TA_TOP (0x0000):
|
||||
* The reference point MUST be on the top edge of the bounding rectangle,
|
||||
* if all bits of the valign mask are unset.
|
||||
*
|
||||
* Flag VTA_RIGHT (0x0000):
|
||||
* The reference point MUST be on the right edge of the bounding rectangle,
|
||||
* if all bits of the align mask (asian mode) are unset.
|
||||
*/
|
||||
private static final int VALIGN_TOP = 0;
|
||||
|
||||
/**
|
||||
* Flag TA_BOTTOM (0x0008):
|
||||
* The reference point MUST be on the bottom edge of the bounding rectangle.
|
||||
*
|
||||
* Flag VTA_LEFT (0x0008):
|
||||
* The reference point MUST be on the left edge of the bounding rectangle.
|
||||
*/
|
||||
private static final BitField VTA_LEFT = BitFieldFactory.getInstance(0x0008);
|
||||
private static final int VALIGN_BOTTOM = 1;
|
||||
|
||||
/**
|
||||
* Flag TA_BASELINE (0x0018) / VTA_BASELINE (0x0018):
|
||||
* The reference point MUST be on the baseline of the text.
|
||||
*/
|
||||
private static final BitField VTA_BASELINE = BitFieldFactory.getInstance(0x0018);
|
||||
private static final int VALIGN_BASELINE = 3;
|
||||
|
||||
/**
|
||||
* A 16-bit unsigned integer that defines text alignment.
|
||||
|
@ -482,10 +526,10 @@ public class HwmfText {
|
|||
* for text with a horizontal baseline, and VerticalTextAlignmentMode Flags
|
||||
* for text with a vertical baseline.
|
||||
*/
|
||||
private int textAlignmentMode;
|
||||
protected int textAlignmentMode;
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.setTextAlign;
|
||||
}
|
||||
|
||||
|
@ -498,52 +542,90 @@ public class HwmfText {
|
|||
@Override
|
||||
public void draw(HwmfGraphics ctx) {
|
||||
HwmfDrawProperties props = ctx.getProperties();
|
||||
if (TA_CENTER.isSet(textAlignmentMode)) {
|
||||
props.setTextAlignLatin(HwmfTextAlignment.CENTER);
|
||||
} else if (TA_RIGHT.isSet(textAlignmentMode)) {
|
||||
props.setTextAlignLatin(HwmfTextAlignment.RIGHT);
|
||||
} else {
|
||||
props.setTextAlignLatin(HwmfTextAlignment.LEFT);
|
||||
}
|
||||
props.setTextAlignLatin(getAlignLatin());
|
||||
props.setTextVAlignLatin(getVAlignLatin());
|
||||
props.setTextAlignAsian(getAlignAsian());
|
||||
props.setTextVAlignAsian(getVAlignAsian());
|
||||
}
|
||||
|
||||
if (VTA_CENTER.isSet(textAlignmentMode)) {
|
||||
props.setTextAlignAsian(HwmfTextAlignment.CENTER);
|
||||
} else if (VTA_LEFT.isSet(textAlignmentMode)) {
|
||||
props.setTextAlignAsian(HwmfTextAlignment.LEFT);
|
||||
} else {
|
||||
props.setTextAlignAsian(HwmfTextAlignment.RIGHT);
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return
|
||||
"{ align: '"+ getAlignLatin() + "'" +
|
||||
", valign: '"+ getVAlignLatin() + "'" +
|
||||
", alignAsian: '"+ getAlignAsian() + "'" +
|
||||
", valignAsian: '"+ getVAlignAsian() + "'" +
|
||||
"}";
|
||||
}
|
||||
|
||||
if (TA_BASELINE.isSet(textAlignmentMode)) {
|
||||
props.setTextVAlignLatin(HwmfTextVerticalAlignment.BASELINE);
|
||||
} else if (TA_BOTTOM.isSet(textAlignmentMode)) {
|
||||
props.setTextVAlignLatin(HwmfTextVerticalAlignment.BOTTOM);
|
||||
} else {
|
||||
props.setTextVAlignLatin(HwmfTextVerticalAlignment.TOP);
|
||||
private HwmfTextAlignment getAlignLatin() {
|
||||
switch (ALIGN_MASK.getValue(textAlignmentMode)) {
|
||||
default:
|
||||
case ALIGN_LEFT:
|
||||
return HwmfTextAlignment.LEFT;
|
||||
case ALIGN_CENTER:
|
||||
return HwmfTextAlignment.CENTER;
|
||||
case ALIGN_RIGHT:
|
||||
return HwmfTextAlignment.RIGHT;
|
||||
}
|
||||
}
|
||||
|
||||
if (VTA_BASELINE.isSet(textAlignmentMode)) {
|
||||
props.setTextVAlignAsian(HwmfTextVerticalAlignment.BASELINE);
|
||||
} else if (VTA_BOTTOM.isSet(textAlignmentMode)) {
|
||||
props.setTextVAlignAsian(HwmfTextVerticalAlignment.BOTTOM);
|
||||
} else {
|
||||
props.setTextVAlignAsian(HwmfTextVerticalAlignment.TOP);
|
||||
private HwmfTextVerticalAlignment getVAlignLatin() {
|
||||
switch (VALIGN_MASK.getValue(textAlignmentMode)) {
|
||||
default:
|
||||
case VALIGN_TOP:
|
||||
return HwmfTextVerticalAlignment.TOP;
|
||||
case VALIGN_BASELINE:
|
||||
return HwmfTextVerticalAlignment.BASELINE;
|
||||
case VALIGN_BOTTOM:
|
||||
return HwmfTextVerticalAlignment.BOTTOM;
|
||||
}
|
||||
}
|
||||
|
||||
private HwmfTextAlignment getAlignAsian() {
|
||||
switch (getVAlignLatin()) {
|
||||
default:
|
||||
case TOP:
|
||||
return HwmfTextAlignment.RIGHT;
|
||||
case BASELINE:
|
||||
return HwmfTextAlignment.CENTER;
|
||||
case BOTTOM:
|
||||
return HwmfTextAlignment.LEFT;
|
||||
}
|
||||
}
|
||||
|
||||
private HwmfTextVerticalAlignment getVAlignAsian() {
|
||||
switch (getAlignLatin()) {
|
||||
default:
|
||||
case LEFT:
|
||||
return HwmfTextVerticalAlignment.TOP;
|
||||
case CENTER:
|
||||
return HwmfTextVerticalAlignment.BASELINE;
|
||||
case RIGHT:
|
||||
return HwmfTextVerticalAlignment.BOTTOM;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class WmfCreateFontIndirect implements HwmfRecord, HwmfObjectTableEntry {
|
||||
private HwmfFont font;
|
||||
|
||||
protected final HwmfFont font;
|
||||
|
||||
public WmfCreateFontIndirect() {
|
||||
this(new HwmfFont());
|
||||
}
|
||||
|
||||
protected WmfCreateFontIndirect(HwmfFont font) {
|
||||
this.font = font;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.createFontIndirect;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
|
||||
font = new HwmfFont();
|
||||
return font.init(leis);
|
||||
return font.init(leis, recordSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -559,5 +641,10 @@ public class HwmfText {
|
|||
public HwmfFont getFont() {
|
||||
return font;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{ font: "+font+" } ";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,12 +17,23 @@
|
|||
|
||||
package org.apache.poi.hwmf.record;
|
||||
|
||||
import static org.apache.poi.hwmf.record.HwmfDraw.boundsToString;
|
||||
import static org.apache.poi.hwmf.record.HwmfDraw.dimToString;
|
||||
import static org.apache.poi.hwmf.record.HwmfDraw.normalizeBounds;
|
||||
import static org.apache.poi.hwmf.record.HwmfDraw.pointToString;
|
||||
import static org.apache.poi.hwmf.record.HwmfDraw.readBounds;
|
||||
import static org.apache.poi.hwmf.record.HwmfDraw.readPointS;
|
||||
|
||||
import java.awt.Shape;
|
||||
import java.awt.geom.Area;
|
||||
import java.awt.geom.Dimension2D;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.poi.hwmf.draw.HwmfDrawProperties;
|
||||
import org.apache.poi.hwmf.draw.HwmfGraphics;
|
||||
import org.apache.poi.util.Dimension2DDouble;
|
||||
import org.apache.poi.util.LittleEndianConsts;
|
||||
import org.apache.poi.util.LittleEndianInputStream;
|
||||
|
||||
|
@ -33,31 +44,33 @@ public class HwmfWindowing {
|
|||
*/
|
||||
public static class WmfSetViewportOrg implements HwmfRecord {
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the vertical offset, in device units.
|
||||
*/
|
||||
private int y;
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the horizontal offset, in device units.
|
||||
*/
|
||||
private int x;
|
||||
protected final Point2D origin = new Point2D.Double();
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.setViewportOrg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
|
||||
y = leis.readShort();
|
||||
x = leis.readShort();
|
||||
return 2*LittleEndianConsts.SHORT_SIZE;
|
||||
return readPointS(leis, origin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(HwmfGraphics ctx) {
|
||||
ctx.getProperties().setViewportOrg(x, y);
|
||||
final HwmfDrawProperties prop = ctx.getProperties();
|
||||
Rectangle2D old = prop.getViewport();
|
||||
double oldX = (old == null ? 0 : old.getX());
|
||||
double oldY = (old == null ? 0 : old.getY());
|
||||
if (oldX != origin.getX() || oldY != origin.getY()) {
|
||||
prop.setViewportOrg(origin.getX(), origin.getY());
|
||||
ctx.updateWindowMapMode();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return pointToString(origin);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -67,33 +80,38 @@ public class HwmfWindowing {
|
|||
*/
|
||||
public static class WmfSetViewportExt implements HwmfRecord {
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the vertical extent
|
||||
* of the viewport in device units.
|
||||
*/
|
||||
private int height;
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the horizontal extent
|
||||
* of the viewport in device units.
|
||||
*/
|
||||
private int width;
|
||||
protected final Dimension2D extents = new Dimension2DDouble();
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.setViewportExt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
|
||||
height = leis.readShort();
|
||||
width = leis.readShort();
|
||||
// A signed integer that defines the vertical extent of the viewport in device units.
|
||||
int height = leis.readShort();
|
||||
// A signed integer that defines the horizontal extent of the viewport in device units.
|
||||
int width = leis.readShort();
|
||||
extents.setSize(width, height);
|
||||
return 2*LittleEndianConsts.SHORT_SIZE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(HwmfGraphics ctx) {
|
||||
ctx.getProperties().setViewportExt(width, height);
|
||||
final HwmfDrawProperties prop = ctx.getProperties();
|
||||
Rectangle2D old = prop.getViewport();
|
||||
double oldW = (old == null ? 0 : old.getWidth());
|
||||
double oldH = (old == null ? 0 : old.getHeight());
|
||||
if (oldW != extents.getWidth() || oldH != extents.getHeight()) {
|
||||
prop.setViewportExt(extents.getWidth(), extents.getHeight());
|
||||
ctx.updateWindowMapMode();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return dimToString(extents);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -103,34 +121,33 @@ public class HwmfWindowing {
|
|||
*/
|
||||
public static class WmfOffsetViewportOrg implements HwmfRecord {
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the vertical offset, in device units.
|
||||
*/
|
||||
private int yOffset;
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the horizontal offset, in device units.
|
||||
*/
|
||||
private int xOffset;
|
||||
protected final Point2D offset = new Point2D.Double();
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.offsetViewportOrg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
|
||||
yOffset = leis.readShort();
|
||||
xOffset = leis.readShort();
|
||||
return 2*LittleEndianConsts.SHORT_SIZE;
|
||||
return readPointS(leis, offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(HwmfGraphics ctx) {
|
||||
Rectangle2D viewport = ctx.getProperties().getViewport();
|
||||
double x = (viewport == null) ? 0 : viewport.getX();
|
||||
double y = (viewport == null) ? 0 : viewport.getY();
|
||||
ctx.getProperties().setViewportOrg(x+xOffset, y+yOffset);
|
||||
final HwmfDrawProperties prop = ctx.getProperties();
|
||||
Rectangle2D viewport = prop.getViewport();
|
||||
if (offset.getX() != 0 || offset.getY() != 0) {
|
||||
double x = (viewport == null) ? 0 : viewport.getX();
|
||||
double y = (viewport == null) ? 0 : viewport.getY();
|
||||
prop.setViewportOrg(x + offset.getX(), y + offset.getY());
|
||||
ctx.updateWindowMapMode();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return pointToString(offset);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -139,40 +156,41 @@ public class HwmfWindowing {
|
|||
*/
|
||||
public static class WmfSetWindowOrg implements HwmfRecord {
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the y-coordinate, in logical units.
|
||||
*/
|
||||
private int y;
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the x-coordinate, in logical units.
|
||||
*/
|
||||
private int x;
|
||||
protected final Point2D origin = new Point2D.Double();
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.setWindowOrg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
|
||||
y = leis.readShort();
|
||||
x = leis.readShort();
|
||||
return 2*LittleEndianConsts.SHORT_SIZE;
|
||||
return readPointS(leis, origin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(HwmfGraphics ctx) {
|
||||
ctx.getProperties().setWindowOrg(x, y);
|
||||
ctx.updateWindowMapMode();
|
||||
final HwmfDrawProperties prop = ctx.getProperties();
|
||||
final Rectangle2D old = prop.getWindow();
|
||||
double oldX = (old == null ? 0 : old.getX());
|
||||
double oldY = (old == null ? 0 : old.getY());
|
||||
if (oldX != getX() || oldY != getY()) {
|
||||
prop.setWindowOrg(getX(), getY());
|
||||
ctx.updateWindowMapMode();
|
||||
}
|
||||
}
|
||||
|
||||
public int getY() {
|
||||
return y;
|
||||
public double getY() {
|
||||
return origin.getY();
|
||||
}
|
||||
|
||||
public int getX() {
|
||||
return x;
|
||||
public double getX() {
|
||||
return origin.getX();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return pointToString(origin);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -182,42 +200,42 @@ public class HwmfWindowing {
|
|||
*/
|
||||
public static class WmfSetWindowExt implements HwmfRecord {
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the vertical extent of
|
||||
* the window in logical units.
|
||||
*/
|
||||
private int height;
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the horizontal extent of
|
||||
* the window in logical units.
|
||||
*/
|
||||
private int width;
|
||||
protected final Dimension2D size = new Dimension2DDouble();
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.setWindowExt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
|
||||
height = leis.readShort();
|
||||
width = leis.readShort();
|
||||
// A signed integer that defines the vertical extent of the window in logical units.
|
||||
int height = leis.readShort();
|
||||
// A signed integer that defines the horizontal extent of the window in logical units.
|
||||
int width = leis.readShort();
|
||||
size.setSize(width, height);
|
||||
return 2*LittleEndianConsts.SHORT_SIZE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(HwmfGraphics ctx) {
|
||||
ctx.getProperties().setWindowExt(width, height);
|
||||
ctx.updateWindowMapMode();
|
||||
final HwmfDrawProperties prop = ctx.getProperties();
|
||||
Rectangle2D old = prop.getWindow();
|
||||
double oldW = (old == null ? 0 : old.getWidth());
|
||||
double oldH = (old == null ? 0 : old.getHeight());
|
||||
if (oldW != size.getWidth() || oldH != size.getHeight()) {
|
||||
prop.setWindowExt(size.getWidth(), size.getHeight());
|
||||
ctx.updateWindowMapMode();
|
||||
}
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
return height;
|
||||
public Dimension2D getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public int getWidth() {
|
||||
return width;
|
||||
@Override
|
||||
public String toString() {
|
||||
return dimToString(size);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -227,33 +245,31 @@ public class HwmfWindowing {
|
|||
*/
|
||||
public static class WmfOffsetWindowOrg implements HwmfRecord {
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the vertical offset, in device units.
|
||||
*/
|
||||
private int yOffset;
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the horizontal offset, in device units.
|
||||
*/
|
||||
private int xOffset;
|
||||
protected final Point2D offset = new Point2D.Double();
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.offsetWindowOrg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
|
||||
yOffset = leis.readShort();
|
||||
xOffset = leis.readShort();
|
||||
return 2*LittleEndianConsts.SHORT_SIZE;
|
||||
return readPointS(leis, offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(HwmfGraphics ctx) {
|
||||
Rectangle2D window = ctx.getProperties().getWindow();
|
||||
ctx.getProperties().setWindowOrg(window.getX()+xOffset, window.getY()+yOffset);
|
||||
ctx.updateWindowMapMode();
|
||||
final HwmfDrawProperties prop = ctx.getProperties();
|
||||
Rectangle2D old = prop.getWindow();
|
||||
if (offset.getX() != 0 || offset.getY() != 0) {
|
||||
prop.setWindowOrg(old.getX() + offset.getX(), old.getY() + offset.getY());
|
||||
ctx.updateWindowMapMode();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return pointToString(offset);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -263,51 +279,48 @@ public class HwmfWindowing {
|
|||
*/
|
||||
public static class WmfScaleWindowExt implements HwmfRecord {
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the amount by which to divide the
|
||||
* result of multiplying the current y-extent by the value of the yNum member.
|
||||
*/
|
||||
private int yDenom;
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the amount by which to multiply the
|
||||
* current y-extent.
|
||||
*/
|
||||
private int yNum;
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the amount by which to divide the
|
||||
* result of multiplying the current x-extent by the value of the xNum member.
|
||||
*/
|
||||
private int xDenom;
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the amount by which to multiply the
|
||||
* current x-extent.
|
||||
*/
|
||||
private int xNum;
|
||||
protected final Dimension2D scale = new Dimension2DDouble();
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.scaleWindowExt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
|
||||
yDenom = leis.readShort();
|
||||
yNum = leis.readShort();
|
||||
xDenom = leis.readShort();
|
||||
xNum = leis.readShort();
|
||||
// A signed integer that defines the amount by which to divide the
|
||||
// result of multiplying the current y-extent by the value of the yNum member.
|
||||
double yDenom = leis.readShort();
|
||||
// A signed integer that defines the amount by which to multiply the
|
||||
// current y-extent.
|
||||
double yNum = leis.readShort();
|
||||
// A signed integer that defines the amount by which to divide the
|
||||
// result of multiplying the current x-extent by the value of the xNum member.
|
||||
double xDenom = leis.readShort();
|
||||
// A signed integer that defines the amount by which to multiply the
|
||||
// current x-extent.
|
||||
double xNum = leis.readShort();
|
||||
|
||||
scale.setSize(xNum / xDenom, yNum / yDenom);
|
||||
|
||||
return 4*LittleEndianConsts.SHORT_SIZE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(HwmfGraphics ctx) {
|
||||
Rectangle2D window = ctx.getProperties().getWindow();
|
||||
double width = window.getWidth() * xNum / xDenom;
|
||||
double height = window.getHeight() * yNum / yDenom;
|
||||
ctx.getProperties().setWindowExt(width, height);
|
||||
ctx.updateWindowMapMode();
|
||||
final HwmfDrawProperties prop = ctx.getProperties();
|
||||
Rectangle2D old = prop.getWindow();
|
||||
if (scale.getWidth() != 1.0 || scale.getHeight() != 1.0) {
|
||||
double width = old.getWidth() * scale.getWidth();
|
||||
double height = old.getHeight() * scale.getHeight();
|
||||
ctx.getProperties().setWindowExt(width, height);
|
||||
ctx.updateWindowMapMode();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{ scaleX: "+scale.getWidth()+", scaleY: "+scale.getHeight()+" }";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -319,53 +332,49 @@ public class HwmfWindowing {
|
|||
*/
|
||||
public static class WmfScaleViewportExt implements HwmfRecord {
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the amount by which to divide the
|
||||
* result of multiplying the current y-extent by the value of the yNum member.
|
||||
*/
|
||||
private int yDenom;
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the amount by which to multiply the
|
||||
* current y-extent.
|
||||
*/
|
||||
private int yNum;
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the amount by which to divide the
|
||||
* result of multiplying the current x-extent by the value of the xNum member.
|
||||
*/
|
||||
private int xDenom;
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the amount by which to multiply the
|
||||
* current x-extent.
|
||||
*/
|
||||
private int xNum;
|
||||
protected final Dimension2D scale = new Dimension2DDouble();
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.scaleViewportExt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
|
||||
yDenom = leis.readShort();
|
||||
yNum = leis.readShort();
|
||||
xDenom = leis.readShort();
|
||||
xNum = leis.readShort();
|
||||
// A signed integer that defines the amount by which to divide the
|
||||
// result of multiplying the current y-extent by the value of the yNum member.
|
||||
double yDenom = leis.readShort();
|
||||
// A signed integer that defines the amount by which to multiply the
|
||||
// current y-extent.
|
||||
double yNum = leis.readShort();
|
||||
// A signed integer that defines the amount by which to divide the
|
||||
// result of multiplying the current x-extent by the value of the xNum member.
|
||||
double xDenom = leis.readShort();
|
||||
// A signed integer that defines the amount by which to multiply the
|
||||
// current x-extent.
|
||||
double xNum = leis.readShort();
|
||||
|
||||
scale.setSize(xNum / xDenom, yNum / yDenom);
|
||||
|
||||
return 4*LittleEndianConsts.SHORT_SIZE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(HwmfGraphics ctx) {
|
||||
Rectangle2D viewport = ctx.getProperties().getViewport();
|
||||
if (viewport == null) {
|
||||
viewport = ctx.getProperties().getWindow();
|
||||
final HwmfDrawProperties prop = ctx.getProperties();
|
||||
final Rectangle2D old = prop.getViewport() == null ? prop.getWindow() : prop.getViewport();
|
||||
|
||||
if (scale.getWidth() != 1.0 || scale.getHeight() != 1.0) {
|
||||
double width = old.getWidth() * scale.getWidth();
|
||||
double height = old.getHeight() * scale.getHeight();
|
||||
prop.setViewportExt(width, height);
|
||||
ctx.updateWindowMapMode();
|
||||
}
|
||||
double width = viewport.getWidth() * xNum / xDenom;
|
||||
double height = viewport.getHeight() * yNum / yDenom;
|
||||
ctx.getProperties().setViewportExt(width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{ scaleX: "+scale.getWidth()+", scaleY: "+scale.getHeight()+" }";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -375,26 +384,16 @@ public class HwmfWindowing {
|
|||
*/
|
||||
public static class WmfOffsetClipRgn implements HwmfRecord, HwmfObjectTableEntry {
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the number of logical units to move up or down.
|
||||
*/
|
||||
private int yOffset;
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the number of logical units to move left or right.
|
||||
*/
|
||||
private int xOffset;
|
||||
protected final Point2D offset = new Point2D.Double();
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.offsetClipRgn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
|
||||
yOffset = leis.readShort();
|
||||
xOffset = leis.readShort();
|
||||
return 2*LittleEndianConsts.SHORT_SIZE;
|
||||
return readPointS(leis, offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -405,6 +404,11 @@ public class HwmfWindowing {
|
|||
@Override
|
||||
public void applyObject(HwmfGraphics ctx) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return pointToString(offset);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -413,42 +417,17 @@ public class HwmfWindowing {
|
|||
*/
|
||||
public static class WmfExcludeClipRect implements HwmfRecord, HwmfObjectTableEntry {
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the
|
||||
* lower-right corner of the rectangle.
|
||||
*/
|
||||
private int bottom;
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the
|
||||
* lower-right corner of the rectangle.
|
||||
*/
|
||||
private int right;
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the
|
||||
* upper-left corner of the rectangle.
|
||||
*/
|
||||
private int top;
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the
|
||||
* upper-left corner of the rectangle.
|
||||
*/
|
||||
private int left;
|
||||
/** a rectangle in logical units */
|
||||
protected final Rectangle2D bounds = new Rectangle2D.Double();
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.excludeClipRect;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
|
||||
bottom = leis.readShort();
|
||||
right = leis.readShort();
|
||||
top = leis.readShort();
|
||||
left = leis.readShort();
|
||||
return 4*LittleEndianConsts.SHORT_SIZE;
|
||||
return readBounds(leis, bounds);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -458,6 +437,12 @@ public class HwmfWindowing {
|
|||
|
||||
@Override
|
||||
public void applyObject(HwmfGraphics ctx) {
|
||||
ctx.setClip(normalizeBounds(bounds), HwmfRegionMode.RGN_DIFF, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return boundsToString(bounds);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -468,42 +453,17 @@ public class HwmfWindowing {
|
|||
*/
|
||||
public static class WmfIntersectClipRect implements HwmfRecord, HwmfObjectTableEntry {
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the
|
||||
* lower-right corner of the rectangle.
|
||||
*/
|
||||
private int bottom;
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the
|
||||
* lower-right corner of the rectangle.
|
||||
*/
|
||||
private int right;
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the
|
||||
* upper-left corner of the rectangle.
|
||||
*/
|
||||
private int top;
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the
|
||||
* upper-left corner of the rectangle.
|
||||
*/
|
||||
private int left;
|
||||
/** a rectangle in logical units */
|
||||
protected final Rectangle2D bounds = new Rectangle2D.Double();
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.intersectClipRect;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException {
|
||||
bottom = leis.readShort();
|
||||
right = leis.readShort();
|
||||
top = leis.readShort();
|
||||
left = leis.readShort();
|
||||
return 4*LittleEndianConsts.SHORT_SIZE;
|
||||
return readBounds(leis, bounds);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -513,6 +473,12 @@ public class HwmfWindowing {
|
|||
|
||||
@Override
|
||||
public void applyObject(HwmfGraphics ctx) {
|
||||
ctx.setClip(bounds, HwmfRegionMode.RGN_AND, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return boundsToString(bounds);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -528,7 +494,7 @@ public class HwmfWindowing {
|
|||
private int region;
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.selectClipRegion;
|
||||
}
|
||||
|
||||
|
@ -620,29 +586,7 @@ public class HwmfWindowing {
|
|||
*/
|
||||
private int maxScan;
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the
|
||||
* lower-right corner of the rectangle.
|
||||
*/
|
||||
private int bottom;
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the
|
||||
* lower-right corner of the rectangle.
|
||||
*/
|
||||
private int right;
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the
|
||||
* upper-left corner of the rectangle.
|
||||
*/
|
||||
private int top;
|
||||
|
||||
/**
|
||||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the
|
||||
* upper-left corner of the rectangle.
|
||||
*/
|
||||
private int left;
|
||||
private Rectangle2D bounds = new Rectangle2D.Double();
|
||||
|
||||
/**
|
||||
* An array of Scan objects that define the scanlines in the region.
|
||||
|
@ -650,7 +594,7 @@ public class HwmfWindowing {
|
|||
private WmfScanObject scanObjects[];
|
||||
|
||||
@Override
|
||||
public HwmfRecordType getRecordType() {
|
||||
public HwmfRecordType getWmfRecordType() {
|
||||
return HwmfRecordType.createRegion;
|
||||
}
|
||||
|
||||
|
@ -662,10 +606,19 @@ public class HwmfWindowing {
|
|||
regionSize = leis.readShort();
|
||||
scanCount = leis.readShort();
|
||||
maxScan = leis.readShort();
|
||||
left = leis.readShort();
|
||||
top = leis.readShort();
|
||||
right = leis.readShort();
|
||||
bottom = leis.readShort();
|
||||
// A 16-bit signed integer that defines the x-coordinate, in logical units, of the
|
||||
// upper-left corner of the rectangle.
|
||||
double left = leis.readShort();
|
||||
// A 16-bit signed integer that defines the y-coordinate, in logical units, of the
|
||||
// upper-left corner of the rectangle.
|
||||
double top = leis.readShort();
|
||||
// A 16-bit signed integer that defines the x-coordinate, in logical units, of the
|
||||
// lower-right corner of the rectangle.
|
||||
double right = leis.readShort();
|
||||
// A 16-bit signed integer that defines the y-coordinate, in logical units, of the
|
||||
// lower-right corner of the rectangle.
|
||||
double bottom = leis.readShort();
|
||||
bounds.setRect(left, top, right-left, bottom-top);
|
||||
|
||||
int size = 9*LittleEndianConsts.SHORT_SIZE+LittleEndianConsts.INT_SIZE;
|
||||
|
||||
|
|
|
@ -43,6 +43,9 @@ import org.apache.poi.util.RecordFormatException;
|
|||
import org.apache.poi.util.Units;
|
||||
|
||||
public class HwmfPicture {
|
||||
/** Max. record length - processing longer records will throw an exception */
|
||||
public static final int MAX_RECORD_LENGTH = 50_000_000;
|
||||
|
||||
private static final POILogger logger = POILogFactory.getLogger(HwmfPicture.class);
|
||||
|
||||
final List<HwmfRecord> records = new ArrayList<>();
|
||||
|
@ -51,8 +54,7 @@ public class HwmfPicture {
|
|||
|
||||
public HwmfPicture(InputStream inputStream) throws IOException {
|
||||
|
||||
try (BufferedInputStream bis = new BufferedInputStream(inputStream, 10000);
|
||||
LittleEndianInputStream leis = new LittleEndianInputStream(bis)) {
|
||||
try (LittleEndianInputStream leis = new LittleEndianInputStream(inputStream)) {
|
||||
placeableHeader = HwmfPlaceableHeader.readHeader(leis);
|
||||
header = new HwmfHeader(leis);
|
||||
|
||||
|
@ -82,17 +84,12 @@ public class HwmfPicture {
|
|||
if (wrt == HwmfRecordType.eof) {
|
||||
break;
|
||||
}
|
||||
if (wrt.clazz == null) {
|
||||
if (wrt.constructor == null) {
|
||||
throw new IOException("unsupported record type: "+recordFunction);
|
||||
}
|
||||
|
||||
HwmfRecord wr;
|
||||
try {
|
||||
wr = wrt.clazz.newInstance();
|
||||
records.add(wr);
|
||||
} catch (Exception e) {
|
||||
throw (IOException)new IOException("can't create wmf record").initCause(e);
|
||||
}
|
||||
final HwmfRecord wr = wrt.constructor.get();
|
||||
records.add(wr);
|
||||
|
||||
consumedSize += wr.init(leis, recordSize, recordFunction);
|
||||
int remainingSize = (int)(recordSize - consumedSize);
|
||||
|
@ -162,7 +159,7 @@ public class HwmfPicture {
|
|||
if (wOrg == null || wExt == null) {
|
||||
throw new RuntimeException("invalid wmf file - window records are incomplete.");
|
||||
}
|
||||
return new Rectangle2D.Double(wOrg.getX(), wOrg.getY(), wExt.getWidth(), wExt.getHeight());
|
||||
return new Rectangle2D.Double(wOrg.getX(), wOrg.getY(), wExt.getSize().getWidth(), wExt.getSize().getHeight());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,10 +39,10 @@ public class HemfPlusExtractorTest {
|
|||
@Test
|
||||
public void testBasic() throws Exception {
|
||||
//test header
|
||||
HemfCommentEMFPlus emfPlus = getCommentRecord("SimpleEMF_windows.emf", 0);
|
||||
EmfCommentDataPlus emfPlus = getCommentRecord("SimpleEMF_windows.emf", 0);
|
||||
List<HemfPlusRecord> records = emfPlus.getRecords();
|
||||
assertEquals(1, records.size());
|
||||
assertEquals(HemfPlusRecordType.header, records.get(0).getRecordType());
|
||||
assertEquals(HemfPlusRecordType.header, records.get(0).getEmfPlusRecordType());
|
||||
|
||||
HemfPlusHeader header = (HemfPlusHeader)records.get(0);
|
||||
assertEquals(240, header.getLogicalDpiX());
|
||||
|
@ -67,29 +67,25 @@ public class HemfPlusExtractorTest {
|
|||
assertEquals(expected.size(), records.size());
|
||||
|
||||
for (int i = 0; i < expected.size(); i++) {
|
||||
assertEquals(expected.get(i), records.get(i).getRecordType());
|
||||
assertEquals(expected.get(i), records.get(i).getEmfPlusRecordType());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private HemfCommentEMFPlus getCommentRecord(String testFileName, int recordIndex) throws Exception {
|
||||
InputStream is = null;
|
||||
HemfCommentEMFPlus returnRecord = null;
|
||||
private EmfCommentDataPlus getCommentRecord(String testFileName, int recordIndex) throws Exception {
|
||||
EmfCommentDataPlus returnRecord = null;
|
||||
|
||||
try {
|
||||
is = POIDataSamples.getSpreadSheetInstance().openResourceAsStream(testFileName);
|
||||
HemfExtractor ex = new HemfExtractor(is);
|
||||
try (InputStream is = POIDataSamples.getSpreadSheetInstance().openResourceAsStream(testFileName)) {
|
||||
HemfPicture ex = new HemfPicture(is);
|
||||
int i = 0;
|
||||
for (HemfRecord record : ex) {
|
||||
if (i == recordIndex) {
|
||||
HemfCommentRecord commentRecord = ((HemfCommentRecord) record);
|
||||
returnRecord = (HemfCommentEMFPlus) commentRecord.getComment();
|
||||
EmfComment commentRecord = ((EmfComment) record);
|
||||
returnRecord = (EmfCommentDataPlus) commentRecord.getCommentData();
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
} finally {
|
||||
is.close();
|
||||
}
|
||||
return returnRecord;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,353 @@
|
|||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
==================================================================== */
|
||||
|
||||
|
||||
package org.apache.poi.hemf.usermodel;
|
||||
|
||||
import static org.apache.poi.POITestCase.assertContains;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.awt.geom.Point2D;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.poi.POIDataSamples;
|
||||
import org.apache.poi.hemf.record.emf.HemfComment;
|
||||
import org.apache.poi.hemf.record.emf.HemfComment.EmfComment;
|
||||
import org.apache.poi.hemf.record.emf.HemfComment.EmfCommentDataFormat;
|
||||
import org.apache.poi.hemf.record.emf.HemfComment.EmfCommentDataMultiformats;
|
||||
import org.apache.poi.hemf.record.emf.HemfHeader;
|
||||
import org.apache.poi.hemf.record.emf.HemfRecord;
|
||||
import org.apache.poi.hemf.record.emf.HemfRecordType;
|
||||
import org.apache.poi.hemf.record.emf.HemfText;
|
||||
import org.apache.poi.util.IOUtils;
|
||||
import org.apache.poi.util.RecordFormatException;
|
||||
import org.junit.Test;
|
||||
|
||||
public class HemfPictureTest {
|
||||
|
||||
private static final POIDataSamples ss_samples = POIDataSamples.getSpreadSheetInstance();
|
||||
private static final POIDataSamples sl_samples = POIDataSamples.getSlideShowInstance();
|
||||
|
||||
/*
|
||||
@Test
|
||||
@Ignore("Only for manual tests - need to add org.tukaani:xz:1.8 for this to work")
|
||||
public void paint() throws IOException {
|
||||
byte buf[] = new byte[50_000_000];
|
||||
|
||||
// good test samples to validate rendering:
|
||||
// emfs/commoncrawl2/NB/NBWN2YH5VFCLZRFDQU7PB7IDD4UKY7DN_2.emf
|
||||
// emfs/govdocs1/777/777525.ppt_0.emf
|
||||
// emfs/govdocs1/844/844795.ppt_2.emf
|
||||
// emfs/commoncrawl2/TO/TOYZSTNUSW5OFCFUQ6T5FBLIDLCRF3NH_0.emf
|
||||
|
||||
final boolean writeLog = true;
|
||||
final boolean dumpRecords = false;
|
||||
final boolean savePng = true;
|
||||
|
||||
Set<String> passed = new HashSet<>();
|
||||
|
||||
try (BufferedWriter sucWrite = parseEmfLog(passed, "emf-success.txt");
|
||||
BufferedWriter parseError = parseEmfLog(passed, "emf-parse.txt");
|
||||
BufferedWriter renderError = parseEmfLog(passed, "emf-render.txt");
|
||||
SevenZFile sevenZFile = new SevenZFile(new File("tmp/render_emf.7z"))) {
|
||||
for (int idx=0;;idx++) {
|
||||
SevenZArchiveEntry entry = sevenZFile.getNextEntry();
|
||||
if (entry == null) break;
|
||||
final String etName = entry.getName();
|
||||
|
||||
if (entry.isDirectory() || !etName.endsWith(".emf") || passed.contains(etName)) continue;
|
||||
|
||||
System.out.println(etName);
|
||||
|
||||
int size = sevenZFile.read(buf);
|
||||
|
||||
HemfPicture emf = null;
|
||||
try {
|
||||
emf = new HemfPicture(new ByteArrayInputStream(buf, 0, size));
|
||||
|
||||
// initialize parsing
|
||||
emf.getRecords();
|
||||
} catch (Exception|AssertionError e) {
|
||||
if (writeLog) {
|
||||
parseError.write(etName+" "+hashException(e)+"\n");
|
||||
parseError.flush();
|
||||
}
|
||||
System.out.println("parse error");
|
||||
// continue with the read records up to the error anyway
|
||||
if (emf.getRecords().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (dumpRecords) {
|
||||
dumpRecords(emf);
|
||||
}
|
||||
|
||||
Graphics2D g = null;
|
||||
try {
|
||||
Dimension2D dim = emf.getSize();
|
||||
double width = Units.pointsToPixel(dim.getWidth());
|
||||
// keep aspect ratio for height
|
||||
double height = Units.pointsToPixel(dim.getHeight());
|
||||
double max = Math.max(width, height);
|
||||
if (max > 1500.) {
|
||||
width *= 1500. / max;
|
||||
height *= 1500. / max;
|
||||
}
|
||||
width = Math.ceil(width);
|
||||
height = Math.ceil(height);
|
||||
|
||||
BufferedImage bufImg = new BufferedImage((int)width, (int)height, BufferedImage.TYPE_INT_ARGB);
|
||||
g = bufImg.createGraphics();
|
||||
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
|
||||
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
|
||||
g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
|
||||
|
||||
g.setComposite(AlphaComposite.Clear);
|
||||
g.fillRect(0, 0, (int)width, (int)height);
|
||||
g.setComposite(AlphaComposite.Src);
|
||||
|
||||
emf.draw(g, new Rectangle2D.Double(0, 0, width, height));
|
||||
|
||||
final File pngName = new File("build/tmp", etName.replaceFirst(".+/", "").replace(".emf", ".png"));
|
||||
if (savePng) {
|
||||
ImageIO.write(bufImg, "PNG", pngName);
|
||||
}
|
||||
} catch (Exception|AssertionError e) {
|
||||
System.out.println("render error");
|
||||
if (writeLog) {
|
||||
// dumpRecords(emf.getRecords());
|
||||
renderError.write(etName+" "+hashException(e)+"\n");
|
||||
renderError.flush();
|
||||
}
|
||||
continue;
|
||||
} finally {
|
||||
if (g != null) g.dispose();
|
||||
}
|
||||
|
||||
if (writeLog) {
|
||||
sucWrite.write(etName + "\n");
|
||||
sucWrite.flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
} */
|
||||
|
||||
private static int hashException(Throwable e) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (StackTraceElement se : e.getStackTrace()) {
|
||||
sb.append(se.getClassName()+":"+se.getLineNumber());
|
||||
}
|
||||
return sb.toString().hashCode();
|
||||
}
|
||||
|
||||
private static void dumpRecords(HemfPicture emf) throws IOException {
|
||||
FileWriter fw = new FileWriter("record-list.txt");
|
||||
int i = 0;
|
||||
for (HemfRecord r : emf.getRecords()) {
|
||||
if (r.getEmfRecordType() != HemfRecordType.comment) {
|
||||
fw.write(i + " " + r.getEmfRecordType() + " " + r.toString() + "\n");
|
||||
}
|
||||
i++;
|
||||
}
|
||||
fw.close();
|
||||
}
|
||||
|
||||
private static BufferedWriter parseEmfLog(Set<String> passed, String logFile) throws IOException {
|
||||
Path log = Paths.get(logFile);
|
||||
|
||||
StandardOpenOption soo;
|
||||
if (Files.exists(log)) {
|
||||
soo = StandardOpenOption.APPEND;
|
||||
try (Stream<String> stream = Files.lines(log)) {
|
||||
stream.forEach((s) -> passed.add(s.split("\\s")[0]));
|
||||
}
|
||||
} else {
|
||||
soo = StandardOpenOption.CREATE;
|
||||
}
|
||||
|
||||
return Files.newBufferedWriter(log, StandardCharsets.UTF_8, soo);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBasicWindows() throws Exception {
|
||||
try (InputStream is = ss_samples.openResourceAsStream("SimpleEMF_windows.emf")) {
|
||||
HemfPicture pic = new HemfPicture(is);
|
||||
HemfHeader header = pic.getHeader();
|
||||
assertEquals(27864, header.getBytes());
|
||||
assertEquals(31, header.getRecords());
|
||||
assertEquals(3, header.getHandles());
|
||||
assertEquals(346000, header.getMicroDimension().getWidth());
|
||||
assertEquals(194000, header.getMicroDimension().getHeight());
|
||||
|
||||
List<HemfRecord> records = pic.getRecords();
|
||||
|
||||
assertEquals(31, records.size());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBasicMac() throws Exception {
|
||||
try (InputStream is = ss_samples.openResourceAsStream("SimpleEMF_mac.emf")) {
|
||||
HemfPicture pic = new HemfPicture(is);
|
||||
HemfHeader header = pic.getHeader();
|
||||
|
||||
int records = 0;
|
||||
boolean extractedData = false;
|
||||
for (HemfRecord record : pic) {
|
||||
if (record.getEmfRecordType() == HemfRecordType.comment) {
|
||||
HemfComment.EmfCommentData comment = ((EmfComment) record).getCommentData();
|
||||
if (comment instanceof EmfCommentDataMultiformats) {
|
||||
for (EmfCommentDataFormat d : ((EmfCommentDataMultiformats) comment).getFormats()) {
|
||||
byte[] data = d.getRawData();
|
||||
//make sure header starts at 0
|
||||
assertEquals('%', data[0]);
|
||||
assertEquals('P', data[1]);
|
||||
assertEquals('D', data[2]);
|
||||
assertEquals('F', data[3]);
|
||||
|
||||
//make sure byte array ends at EOF\n
|
||||
assertEquals('E', data[data.length - 4]);
|
||||
assertEquals('O', data[data.length - 3]);
|
||||
assertEquals('F', data[data.length - 2]);
|
||||
assertEquals('\n', data[data.length - 1]);
|
||||
extractedData = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
records++;
|
||||
}
|
||||
assertTrue(extractedData);
|
||||
assertEquals(header.getRecords(), records);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMacText() throws Exception {
|
||||
try (InputStream is = ss_samples.openResourceAsStream("SimpleEMF_mac.emf")) {
|
||||
HemfPicture pic = new HemfPicture(is);
|
||||
|
||||
double lastY = -1;
|
||||
double lastX = -1;
|
||||
//derive this from the font information!
|
||||
long fudgeFactorX = 1000;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (HemfRecord record : pic) {
|
||||
if (record.getEmfRecordType().equals(HemfRecordType.extTextOutW)) {
|
||||
HemfText.EmfExtTextOutW extTextOutW = (HemfText.EmfExtTextOutW) record;
|
||||
Point2D reference = extTextOutW.getReference();
|
||||
if (lastY > -1 && lastY != reference.getY()) {
|
||||
sb.append("\n");
|
||||
lastX = -1;
|
||||
}
|
||||
if (lastX > -1 && reference.getX() - lastX > fudgeFactorX) {
|
||||
sb.append(" ");
|
||||
}
|
||||
sb.append(extTextOutW.getText());
|
||||
lastY = reference.getY();
|
||||
lastX = reference.getX();
|
||||
}
|
||||
}
|
||||
String txt = sb.toString();
|
||||
assertContains(txt, "Tika http://incubator.apache.org");
|
||||
assertContains(txt, "Latest News\n");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWindowsText() throws Exception {
|
||||
try (InputStream is = ss_samples.openResourceAsStream("SimpleEMF_windows.emf")) {
|
||||
HemfPicture pic = new HemfPicture(is);
|
||||
double lastY = -1;
|
||||
double lastX = -1;
|
||||
long fudgeFactorX = 1000;//derive this from the font or frame/bounds information
|
||||
StringBuilder sb = new StringBuilder();
|
||||
Set<String> expectedParts = new HashSet<>();
|
||||
expectedParts.add("C:\\Users\\tallison\\");
|
||||
expectedParts.add("testPDF.pdf");
|
||||
int foundExpected = 0;
|
||||
for (HemfRecord record : pic) {
|
||||
if (record.getEmfRecordType().equals(HemfRecordType.extTextOutW)) {
|
||||
HemfText.EmfExtTextOutW extTextOutW = (HemfText.EmfExtTextOutW) record;
|
||||
Point2D reference = extTextOutW.getReference();
|
||||
if (lastY > -1 && lastY != reference.getY()) {
|
||||
sb.append("\n");
|
||||
lastX = -1;
|
||||
}
|
||||
if (lastX > -1 && reference.getX() - lastX > fudgeFactorX) {
|
||||
sb.append(" ");
|
||||
}
|
||||
String txt = extTextOutW.getText();
|
||||
if (expectedParts.contains(txt)) {
|
||||
foundExpected++;
|
||||
}
|
||||
sb.append(txt);
|
||||
lastY = reference.getY();
|
||||
lastX = reference.getX();
|
||||
}
|
||||
}
|
||||
String txt = sb.toString();
|
||||
assertContains(txt, "C:\\Users\\tallison\\\n");
|
||||
assertContains(txt, "asf2-git-1.x\\tika-\n");
|
||||
assertEquals(expectedParts.size(), foundExpected);
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = RecordFormatException.class)
|
||||
public void testInfiniteLoopOnFile() throws Exception {
|
||||
try (InputStream is = ss_samples.openResourceAsStream("61294.emf")) {
|
||||
HemfPicture pic = new HemfPicture(is);
|
||||
for (HemfRecord record : pic) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = RecordFormatException.class)
|
||||
public void testInfiniteLoopOnByteArray() throws Exception {
|
||||
try (InputStream is = ss_samples.openResourceAsStream("61294.emf")) {
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
IOUtils.copy(is, bos);
|
||||
is.close();
|
||||
|
||||
HemfPicture pic = new HemfPicture(new ByteArrayInputStream(bos.toByteArray()));
|
||||
for (HemfRecord record : pic) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
govdocs1 064213.doc-0.emf contains an example of extextouta
|
||||
*/
|
||||
}
|
|
@ -210,7 +210,6 @@ public final class TestPicture {
|
|||
} else {
|
||||
BufferedImage img = new BufferedImage(pg.width, pg.height, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D graphics = img.createGraphics();
|
||||
DrawFactory.getInstance(graphics).fixFonts(graphics);
|
||||
slide.draw(graphics);
|
||||
graphics.setColor(Color.BLACK);
|
||||
graphics.setStroke(new BasicStroke(1));
|
||||
|
|
|
@ -222,11 +222,11 @@ public class TestHwmfParsing {
|
|||
//this happens to work on this test file, but you need to
|
||||
//do what Graphics does by maintaining the stack, etc.!
|
||||
for (HwmfRecord r : wmf.getRecords()) {
|
||||
if (r.getRecordType().equals(HwmfRecordType.createFontIndirect)) {
|
||||
if (r.getWmfRecordType().equals(HwmfRecordType.createFontIndirect)) {
|
||||
HwmfFont font = ((HwmfText.WmfCreateFontIndirect)r).getFont();
|
||||
charset = (font.getCharset().getCharset() == null) ? LocaleUtil.CHARSET_1252 : font.getCharset().getCharset();
|
||||
}
|
||||
if (r.getRecordType().equals(HwmfRecordType.extTextOut)) {
|
||||
if (r.getWmfRecordType().equals(HwmfRecordType.extTextOut)) {
|
||||
HwmfText.WmfExtTextOut textOut = (HwmfText.WmfExtTextOut)r;
|
||||
sb.append(textOut.getText(charset)).append("\n");
|
||||
}
|
||||
|
@ -250,11 +250,11 @@ public class TestHwmfParsing {
|
|||
//this happens to work on this test file, but you need to
|
||||
//do what Graphics does by maintaining the stack, etc.!
|
||||
for (HwmfRecord r : wmf.getRecords()) {
|
||||
if (r.getRecordType().equals(HwmfRecordType.createFontIndirect)) {
|
||||
if (r.getWmfRecordType().equals(HwmfRecordType.createFontIndirect)) {
|
||||
HwmfFont font = ((HwmfText.WmfCreateFontIndirect)r).getFont();
|
||||
charset = (font.getCharset().getCharset() == null) ? LocaleUtil.CHARSET_1252 : font.getCharset().getCharset();
|
||||
}
|
||||
if (r.getRecordType().equals(HwmfRecordType.extTextOut)) {
|
||||
if (r.getWmfRecordType().equals(HwmfRecordType.extTextOut)) {
|
||||
HwmfText.WmfExtTextOut textOut = (HwmfText.WmfExtTextOut)r;
|
||||
sb.append(textOut.getText(charset)).append("\n");
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue