git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1849040 13f79535-47bb-0310-9956-ffa450edef68pull/137/head
@@ -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; | |||
public class Dimension2dDouble extends Dimension2D { | |||
double width; | |||
double height; | |||
public Dimension2dDouble() { | |||
width = 0d; | |||
height = 0d; | |||
} | |||
public Dimension2dDouble(double width, double height) { | |||
this.width = width; | |||
this.height = height; | |||
} | |||
@Override | |||
public double getWidth() { | |||
return width; | |||
} | |||
@Override | |||
public double getHeight() { | |||
return height; | |||
} | |||
@Override | |||
public void setSize(double width, double height) { | |||
this.width = width; | |||
this.height = height; | |||
} | |||
@Override | |||
public boolean equals(Object obj) { | |||
if (obj instanceof Dimension2dDouble) { | |||
Dimension2dDouble other = (Dimension2dDouble) obj; | |||
return width == other.width && height == other.height; | |||
} | |||
return false; | |||
} | |||
@Override | |||
public int hashCode() { | |||
double sum = width + height; | |||
return (int) Math.ceil(sum * (sum + 1) / 2 + width); | |||
} | |||
@Override | |||
public String toString() { | |||
return "Dimension2dDouble[" + width + ", " + height + "]"; | |||
} | |||
import org.apache.poi.util.Removal; | |||
/** | |||
* @deprecated in 4.1.0 - use org.apache.poi.util.Dimension2DDouble | |||
*/ | |||
@Deprecated | |||
@Removal(version = "5.0.0") | |||
public class Dimension2dDouble extends org.apache.poi.util.Dimension2DDouble { | |||
} |
@@ -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"); | |||
} | |||
} | |||
} |
@@ -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 getRecordType() { | |||
return HemfRecordType.getById(recordId); | |||
public HemfRecordType getEmfRecordType() { | |||
return recordType; | |||
} | |||
@Override | |||
public long init(LittleEndianInputStream leis, long recordId, long recordSize) throws IOException { | |||
this.recordId = recordId; | |||
public long init(LittleEndianInputStream leis, long recordSize, long recordId) throws IOException { | |||
recordType = HemfRecordType.getById(recordId); | |||
long skipped = IOUtils.skipFully(leis, recordSize); | |||
if (skipped < recordSize) { | |||
throw new IOException("End of stream reached before record read"); |
@@ -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); | |||
if (clip != null) { | |||
graphicsCtx.translate(-clip.getCenterX(), -clip.getCenterY()); | |||
graphicsCtx.rotate(angle); | |||
graphicsCtx.translate(clip.getCenterX(), clip.getCenterY()); | |||
if (prop.getBkMode() == HwmfBkMode.OPAQUE && opts.isOpaque()) { | |||
graphicsCtx.setPaint(prop.getBackgroundColor().getColor()); | |||
graphicsCtx.fill(clip); | |||
} | |||
if (opts.isClipped()) { | |||
graphicsCtx.setClip(clip); | |||
} | |||
graphicsCtx.setTransform(at); | |||
} | |||
graphicsCtx.translate(reference.getX(), reference.getY()); | |||
graphicsCtx.rotate(angle); | |||
if (prop.getBkMode() == HwmfBkMode.OPAQUE) { | |||
// TODO: validate bounds | |||
graphicsCtx.setBackground(prop.getBackgroundColor().getColor()); | |||
graphicsCtx.fill(new Rectangle2D.Double(0, 0, bounds.getWidth(), bounds.getHeight())); | |||
if (scale != null) { | |||
graphicsCtx.scale(scale.getWidth() < 0 ? -1 : 1, scale.getHeight() < 0 ? -1 : 1); | |||
} | |||
graphicsCtx.translate(dst.getX(), dst.getY()); | |||
graphicsCtx.setColor(prop.getTextColor().getColor()); | |||
graphicsCtx.drawString(as.getIterator(), 0, 0); // (float)bounds.getX(), (float)bounds.getY()); | |||
graphicsCtx.drawString(as.getIterator(), 0, 0); | |||
} finally { | |||
graphicsCtx.setTransform(at); | |||
graphicsCtx.setClip(clipShape); | |||
} | |||
} | |||
private void addAttributes(AttributedString as, HwmfFont font) { | |||
DrawFontManager fontHandler = DrawFactory.getInstance(graphicsCtx).getFontManager(graphicsCtx); | |||
FontInfo fontInfo = fontHandler.getMappedFont(graphicsCtx, font); | |||
as.addAttribute(TextAttribute.FAMILY, fontInfo.getTypeface()); | |||
private void addAttributes(AttributedString as, HwmfFont font, String typeface) { | |||
as.addAttribute(TextAttribute.FAMILY, typeface); | |||
as.addAttribute(TextAttribute.SIZE, getFontHeight(font)); | |||
as.addAttribute(TextAttribute.STRIKETHROUGH, font.isStrikeOut()); | |||
if (font.isStrikeOut()) { | |||
as.addAttribute(TextAttribute.STRIKETHROUGH, TextAttribute.STRIKETHROUGH_ON); | |||
} | |||
if (font.isUnderline()) { | |||
as.addAttribute(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON); | |||
} | |||
@@ -428,4 +550,138 @@ public class HwmfGraphics { | |||
return fontHeight*3/4; | |||
} | |||
} | |||
public void drawImage(BufferedImage img, Rectangle2D srcBounds, Rectangle2D dstBounds) { | |||
HwmfDrawProperties prop = getProperties(); | |||
// handle raster op | |||
// currently the raster op as described in https://docs.microsoft.com/en-us/windows/desktop/gdi/ternary-raster-operations | |||
// are not supported, as we would need to extract the destination image area from the underlying buffered image | |||
// and therefore would make it mandatory that the graphics context must be from a buffered image | |||
// furthermore I doubt the purpose of bitwise image operations on non-black/white images | |||
switch (prop.getRasterOp()) { | |||
case D: | |||
// keep destination, i.e. do nothing | |||
break; | |||
case PATCOPY: | |||
graphicsCtx.setPaint(getFill()); | |||
graphicsCtx.fill(dstBounds); | |||
break; | |||
case BLACKNESS: | |||
graphicsCtx.setPaint(Color.BLACK); | |||
graphicsCtx.fill(dstBounds); | |||
break; | |||
case WHITENESS: | |||
graphicsCtx.setPaint(Color.WHITE); | |||
graphicsCtx.fill(dstBounds); | |||
break; | |||
default: | |||
case SRCCOPY: | |||
final Shape clip = graphicsCtx.getClip(); | |||
// add clipping in case of a source subimage, i.e. a clipped source image | |||
// some dstBounds are horizontal or vertical flipped, so we need to normalize the images | |||
Rectangle2D normalized = new Rectangle2D.Double( | |||
dstBounds.getWidth() >= 0 ? dstBounds.getMinX() : dstBounds.getMaxX(), | |||
dstBounds.getHeight() >= 0 ? dstBounds.getMinY() : dstBounds.getMaxY(), | |||
Math.abs(dstBounds.getWidth()), | |||
Math.abs(dstBounds.getHeight())); | |||
graphicsCtx.clip(normalized); | |||
final AffineTransform at = graphicsCtx.getTransform(); | |||
final Rectangle2D imgBounds = new Rectangle2D.Double(0,0,img.getWidth(),img.getHeight()); | |||
final boolean isImgBounds = (srcBounds.equals(new Rectangle2D.Double())); | |||
final Rectangle2D srcBounds2 = isImgBounds ? imgBounds : srcBounds; | |||
// TODO: apply emf transform | |||
graphicsCtx.translate(dstBounds.getX(), dstBounds.getY()); | |||
graphicsCtx.scale(dstBounds.getWidth()/srcBounds2.getWidth(), dstBounds.getHeight()/srcBounds2.getHeight()); | |||
graphicsCtx.translate(-srcBounds2.getX(), -srcBounds2.getY()); | |||
graphicsCtx.drawImage(img, 0, 0, prop.getBackgroundColor().getColor(), null); | |||
graphicsCtx.setTransform(at); | |||
graphicsCtx.setClip(clip); | |||
break; | |||
} | |||
} | |||
/** | |||
* @return the initial AffineTransform, when this graphics context was created | |||
*/ | |||
public AffineTransform getInitTransform() { | |||
return new AffineTransform(initialAT); | |||
} | |||
/** | |||
* @return the current AffineTransform | |||
*/ | |||
public AffineTransform getTransform() { | |||
return new AffineTransform(graphicsCtx.getTransform()); | |||
} | |||
/** | |||
* Set the current AffineTransform | |||
* @param tx the current AffineTransform | |||
*/ | |||
public void setTransform(AffineTransform tx) { | |||
graphicsCtx.setTransform(tx); | |||
} | |||
private static int clipCnt = 0; | |||
public void setClip(Shape clip, HwmfRegionMode regionMode, boolean useInitialAT) { | |||
final AffineTransform at = graphicsCtx.getTransform(); | |||
if (useInitialAT) { | |||
graphicsCtx.setTransform(initialAT); | |||
} | |||
final Shape oldClip = graphicsCtx.getClip(); | |||
final boolean isEmpty = clip.getBounds2D().isEmpty(); | |||
switch (regionMode) { | |||
case RGN_AND: | |||
if (!isEmpty) { | |||
graphicsCtx.clip(clip); | |||
} | |||
break; | |||
case RGN_OR: | |||
if (!isEmpty) { | |||
if (oldClip == null) { | |||
graphicsCtx.setClip(clip); | |||
} else { | |||
Area area = new Area(oldClip); | |||
area.add(new Area(clip)); | |||
graphicsCtx.setClip(area); | |||
} | |||
} | |||
break; | |||
case RGN_XOR: | |||
if (!isEmpty) { | |||
if (oldClip == null) { | |||
graphicsCtx.setClip(clip); | |||
} else { | |||
Area area = new Area(oldClip); | |||
area.exclusiveOr(new Area(clip)); | |||
graphicsCtx.setClip(area); | |||
} | |||
} | |||
break; | |||
case RGN_DIFF: | |||
if (!isEmpty) { | |||
if (oldClip != null) { | |||
Area area = new Area(oldClip); | |||
area.subtract(new Area(clip)); | |||
graphicsCtx.setClip(area); | |||
} | |||
} | |||
break; | |||
case RGN_COPY: { | |||
graphicsCtx.setClip(isEmpty ? null : clip); | |||
break; | |||
} | |||
} | |||
if (useInitialAT) { | |||
graphicsCtx.setTransform(at); | |||
} | |||
} | |||
} |
@@ -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; | |||
} | |||
} | |||
@Override | |||
public String toString() { | |||
return "{ poly: "+polyToString(poly)+" }"; | |||
} | |||
protected Path2D getShape() { | |||
return (Path2D)poly.clone(); | |||
/** | |||
* @return true, if the shape should be filled | |||
*/ | |||
protected FillDrawStyle getFillDrawStyle() { | |||
return FillDrawStyle.FILL; | |||
} | |||
} | |||
@@ -165,16 +176,13 @@ public class HwmfDraw { | |||
public static class WmfPolyline extends WmfPolygon { | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.polyline; | |||
} | |||
@Override | |||
public void draw(HwmfGraphics ctx) { | |||
Path2D shape = getShape(); | |||
Path2D p = (Path2D)shape.clone(); | |||
p.setWindingRule(getWindingRule(ctx)); | |||
ctx.draw(p); | |||
protected FillDrawStyle getFillDrawStyle() { | |||
return FillDrawStyle.DRAW; | |||
} | |||
} | |||
@@ -184,49 +192,30 @@ public class HwmfDraw { | |||
* are defined in the playback device context. | |||
*/ | |||
public static class WmfEllipse implements HwmfRecord { | |||
/** | |||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of | |||
* the lower-right corner of the bounding rectangle. | |||
*/ | |||
private int bottomRect; | |||
/** | |||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of | |||
* the lower-right corner of the bounding rectangle. | |||
*/ | |||
private int rightRect; | |||
/** | |||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the | |||
* upper-left corner of the bounding rectangle. | |||
*/ | |||
private int topRect; | |||
/** | |||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of | |||
* the upper-left corner of the bounding rectangle. | |||
*/ | |||
private int leftRect; | |||
protected final Rectangle2D bounds = new Rectangle2D.Double(); | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.ellipse; | |||
} | |||
@Override | |||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { | |||
bottomRect = leis.readShort(); | |||
rightRect = leis.readShort(); | |||
topRect = leis.readShort(); | |||
leftRect = leis.readShort(); | |||
return 4*LittleEndianConsts.SHORT_SIZE; | |||
return readBounds(leis, bounds); | |||
} | |||
@Override | |||
public void draw(HwmfGraphics ctx) { | |||
int x = Math.min(leftRect, rightRect); | |||
int y = Math.min(topRect, bottomRect); | |||
int w = Math.abs(leftRect - rightRect - 1); | |||
int h = Math.abs(topRect - bottomRect - 1); | |||
Shape s = new Ellipse2D.Double(x, y, w, h); | |||
ctx.fill(s); | |||
ctx.fill(getShape()); | |||
} | |||
protected Ellipse2D getShape() { | |||
return new Ellipse2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight()); | |||
} | |||
@Override | |||
public String toString() { | |||
return boundsToString(bounds); | |||
} | |||
} | |||
@@ -239,25 +228,25 @@ public class HwmfDraw { | |||
* A 16-bit unsigned integer used to index into the WMF Object Table to get | |||
* the region to be framed. | |||
*/ | |||
private int regionIndex; | |||
protected int regionIndex; | |||
/** | |||
* A 16-bit unsigned integer used to index into the WMF Object Table to get the | |||
* Brush to use for filling the region. | |||
*/ | |||
private int brushIndex; | |||
protected int brushIndex; | |||
/** | |||
* A 16-bit signed integer that defines the height, in logical units, of the | |||
* region frame. | |||
*/ | |||
private int height; | |||
protected int height; | |||
/** | |||
* A 16-bit signed integer that defines the width, in logical units, of the | |||
* region frame. | |||
*/ | |||
private int width; | |||
protected int width; | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.frameRegion; | |||
} | |||
@@ -293,10 +282,10 @@ public class HwmfDraw { | |||
*/ | |||
public static class WmfPolyPolygon implements HwmfRecord { | |||
private List<Path2D> polyList = new ArrayList<>(); | |||
protected final List<Path2D> polyList = new ArrayList<>(); | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.polyPolygon; | |||
} | |||
@@ -325,7 +314,7 @@ public class HwmfDraw { | |||
* An array of 16-bit signed integers that define the coordinates of the polygons. | |||
* (Note: MS-WMF wrongly says unsigned integers ...) | |||
*/ | |||
Path2D poly = new Path2D.Double(); | |||
Path2D poly = new Path2D.Double(Path2D.WIND_EVEN_ODD, nPoints); | |||
for (int i=0; i<nPoints; i++) { | |||
int x = leis.readShort(); | |||
int y = leis.readShort(); | |||
@@ -345,23 +334,81 @@ public class HwmfDraw { | |||
@Override | |||
public void draw(HwmfGraphics ctx) { | |||
if (polyList.isEmpty()) { | |||
Shape shape = getShape(ctx); | |||
if (shape == null) { | |||
return; | |||
} | |||
int windingRule = getWindingRule(ctx); | |||
Area area = null; | |||
for (Path2D poly : polyList) { | |||
Path2D p = (Path2D)poly.clone(); | |||
p.setWindingRule(windingRule); | |||
Area newArea = new Area(p); | |||
if (area == null) { | |||
area = newArea; | |||
} else { | |||
area.exclusiveOr(newArea); | |||
switch (getFillDrawStyle()) { | |||
case DRAW: | |||
ctx.draw(shape); | |||
break; | |||
case FILL: | |||
ctx.fill(shape); | |||
break; | |||
case FILL_DRAW: | |||
ctx.fill(shape); | |||
ctx.draw(shape); | |||
break; | |||
} | |||
} | |||
protected FillDrawStyle getFillDrawStyle() { | |||
// Each polygon SHOULD be outlined using the current pen, and filled using the current brush and | |||
// polygon fill mode that are defined in the playback device context. The polygons defined by this | |||
// record can overlap. | |||
return FillDrawStyle.FILL_DRAW; | |||
} | |||
/** | |||
* @return true, if a polyline should be closed, i.e. is a polygon | |||
*/ | |||
protected boolean isClosed() { | |||
return true; | |||
} | |||
protected Shape getShape(HwmfGraphics ctx) { | |||
int windingRule = ctx.getProperties().getWindingRule(); | |||
if (isClosed()) { | |||
Area area = null; | |||
for (Path2D poly : polyList) { | |||
Path2D p = (Path2D)poly.clone(); | |||
p.setWindingRule(windingRule); | |||
Area newArea = new Area(p); | |||
if (area == null) { | |||
area = newArea; | |||
} else { | |||
area.exclusiveOr(newArea); | |||
} | |||
} | |||
return area; | |||
} else { | |||
Path2D path = new Path2D.Double(); | |||
path.setWindingRule(windingRule); | |||
for (Path2D poly : polyList) { | |||
path.append(poly, false); | |||
} | |||
return path; | |||
} | |||
ctx.fill(area); | |||
} | |||
@Override | |||
public String toString() { | |||
final StringBuilder sb = new StringBuilder(); | |||
sb.append("{ polyList: ["); | |||
boolean isFirst = true; | |||
for (Path2D p : polyList) { | |||
if (!isFirst) { | |||
sb.append(","); | |||
} | |||
isFirst = false; | |||
sb.append("{ points: "); | |||
sb.append(polyToString(p)); | |||
sb.append(" }"); | |||
} | |||
sb.append(" }"); | |||
return sb.toString(); | |||
} | |||
} | |||
@@ -370,92 +417,55 @@ public class HwmfDraw { | |||
* filled by using the brush that are defined in the playback device context. | |||
*/ | |||
public static class WmfRectangle implements HwmfRecord { | |||
/** | |||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of | |||
* the lower-right corner of the rectangle. | |||
*/ | |||
private int bottomRect; | |||
/** | |||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of | |||
* the lower-right corner of the rectangle. | |||
*/ | |||
private int rightRect; | |||
/** | |||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the | |||
* upper-left corner of the rectangle. | |||
*/ | |||
private int topRect; | |||
/** | |||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of | |||
* the upper-left corner of the rectangle. | |||
*/ | |||
private int leftRect; | |||
protected final Rectangle2D bounds = new Rectangle2D.Double(); | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.frameRegion; | |||
} | |||
@Override | |||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { | |||
bottomRect = leis.readShort(); | |||
rightRect = leis.readShort(); | |||
topRect = leis.readShort(); | |||
leftRect = leis.readShort(); | |||
return 4*LittleEndianConsts.SHORT_SIZE; | |||
return readBounds(leis, bounds); | |||
} | |||
@Override | |||
public void draw(HwmfGraphics ctx) { | |||
int x = Math.min(leftRect, rightRect); | |||
int y = Math.min(topRect, bottomRect); | |||
int w = Math.abs(leftRect - rightRect - 1); | |||
int h = Math.abs(topRect - bottomRect - 1); | |||
Shape s = new Rectangle2D.Double(x, y, w, h); | |||
ctx.fill(s); | |||
ctx.fill(bounds); | |||
} | |||
@Override | |||
public String toString() { | |||
return boundsToString(bounds); | |||
} | |||
} | |||
/** | |||
* The META_RECTANGLE record paints a rectangle. The rectangle is outlined by using the pen and | |||
* filled by using the brush that are defined in the playback device context. | |||
* The META_SETPIXEL record sets the pixel at the specified coordinates to the specified color. | |||
*/ | |||
public static class WmfSetPixel implements HwmfRecord { | |||
/** | |||
* A ColorRef Object that defines the color value. | |||
*/ | |||
HwmfColorRef colorRef; | |||
/** | |||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the point | |||
* to be set. | |||
*/ | |||
private int y; | |||
protected final HwmfColorRef colorRef = new HwmfColorRef(); | |||
/** | |||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the point | |||
* to be set. | |||
*/ | |||
private int x; | |||
protected final Point2D point = new Point2D.Double(); | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.setPixel; | |||
} | |||
@Override | |||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { | |||
colorRef = new HwmfColorRef(); | |||
int size = colorRef.init(leis); | |||
y = leis.readShort(); | |||
x = leis.readShort(); | |||
return 2*LittleEndianConsts.SHORT_SIZE+size; | |||
return size+ readPointS(leis, point); | |||
} | |||
@Override | |||
public void draw(HwmfGraphics ctx) { | |||
Shape s = new Rectangle2D.Double(x, y, 1, 1); | |||
Shape s = new Rectangle2D.Double(point.getX(), point.getY(), 1, 1); | |||
ctx.fill(s); | |||
} | |||
} | |||
@@ -469,41 +479,19 @@ public class HwmfDraw { | |||
* A 16-bit signed integer that defines the height, in logical coordinates, of the | |||
* ellipse used to draw the rounded corners. | |||
*/ | |||
private int height; | |||
protected int height; | |||
/** | |||
* A 16-bit signed integer that defines the width, in logical coordinates, of the | |||
* ellipse used to draw the rounded corners. | |||
*/ | |||
private int width; | |||
protected int width; | |||
/** | |||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of | |||
* the lower-right corner of the rectangle. | |||
*/ | |||
private int bottomRect; | |||
/** | |||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of | |||
* the lower-right corner of the rectangle. | |||
*/ | |||
private int rightRect; | |||
/** | |||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the | |||
* upper-left corner of the rectangle. | |||
*/ | |||
private int topRect; | |||
/** | |||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of | |||
* the upper-left corner of the rectangle. | |||
*/ | |||
private int leftRect; | |||
protected final Rectangle2D bounds = new Rectangle2D.Double(); | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.roundRect; | |||
} | |||
@@ -511,21 +499,16 @@ public class HwmfDraw { | |||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { | |||
height = leis.readShort(); | |||
width = leis.readShort(); | |||
bottomRect = leis.readShort(); | |||
rightRect = leis.readShort(); | |||
topRect = leis.readShort(); | |||
leftRect = leis.readShort(); | |||
return 6*LittleEndianConsts.SHORT_SIZE; | |||
return 2*LittleEndianConsts.SHORT_SIZE+readBounds(leis, bounds); | |||
} | |||
@Override | |||
public void draw(HwmfGraphics ctx) { | |||
int x = Math.min(leftRect, rightRect); | |||
int y = Math.min(topRect, bottomRect); | |||
int w = Math.abs(leftRect - rightRect - 1); | |||
int h = Math.abs(topRect - bottomRect - 1); | |||
Shape s = new RoundRectangle2D.Double(x, y, w, h, width, height); | |||
ctx.fill(s); | |||
ctx.fill(getShape()); | |||
} | |||
protected RoundRectangle2D getShape() { | |||
return new RoundRectangle2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), width, height); | |||
} | |||
} | |||
@@ -534,73 +517,61 @@ public class HwmfDraw { | |||
* The META_ARC record draws an elliptical arc. | |||
*/ | |||
public static class WmfArc implements HwmfRecord { | |||
/** | |||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of | |||
* the ending point of the radial line defining the ending point of the arc. | |||
*/ | |||
private int yEndArc; | |||
/** | |||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of | |||
* the ending point of the radial line defining the ending point of the arc. | |||
*/ | |||
private int xEndArc; | |||
/** | |||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of | |||
* the ending point of the radial line defining the starting point of the arc. | |||
*/ | |||
private int yStartArc; | |||
/** | |||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of | |||
* the ending point of the radial line defining the starting point of the arc. | |||
*/ | |||
private int xStartArc; | |||
/** | |||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of | |||
* the lower-right corner of the bounding rectangle. | |||
*/ | |||
private int bottomRect; | |||
/** | |||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of | |||
* the lower-right corner of the bounding rectangle. | |||
*/ | |||
private int rightRect; | |||
/** | |||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the | |||
* upper-left corner of the bounding rectangle. | |||
*/ | |||
private int topRect; | |||
/** | |||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of | |||
* the upper-left corner of the bounding rectangle. | |||
*/ | |||
private int leftRect; | |||
/** starting point of the arc */ | |||
protected final Point2D startPoint = new Point2D.Double(); | |||
/** ending point of the arc */ | |||
protected final Point2D endPoint = new Point2D.Double(); | |||
/** the bounding rectangle */ | |||
protected final Rectangle2D bounds = new Rectangle2D.Double(); | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.arc; | |||
} | |||
@Override | |||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { | |||
yEndArc = leis.readShort(); | |||
xEndArc = leis.readShort(); | |||
yStartArc = leis.readShort(); | |||
xStartArc = leis.readShort(); | |||
bottomRect = leis.readShort(); | |||
rightRect = leis.readShort(); | |||
topRect = leis.readShort(); | |||
leftRect = leis.readShort(); | |||
readPointS(leis, endPoint); | |||
readPointS(leis, startPoint); | |||
readBounds(leis, bounds); | |||
return 8*LittleEndianConsts.SHORT_SIZE; | |||
} | |||
@Override | |||
public void draw(HwmfGraphics ctx) { | |||
int x = Math.min(leftRect, rightRect); | |||
int y = Math.min(topRect, bottomRect); | |||
int w = Math.abs(leftRect - rightRect - 1); | |||
int h = Math.abs(topRect - bottomRect - 1); | |||
double startAngle = Math.toDegrees(Math.atan2(-(yStartArc - (topRect + h / 2.)), xStartArc - (leftRect + w / 2.))); | |||
double endAngle = Math.toDegrees(Math.atan2(-(yEndArc - (topRect + h / 2.)), xEndArc - (leftRect + w / 2.))); | |||
Shape s = getShape(); | |||
switch (getFillDrawStyle()) { | |||
case FILL: | |||
ctx.fill(s); | |||
break; | |||
case DRAW: | |||
ctx.draw(s); | |||
break; | |||
case FILL_DRAW: | |||
ctx.fill(s); | |||
ctx.draw(s); | |||
break; | |||
} | |||
} | |||
protected FillDrawStyle getFillDrawStyle() { | |||
switch (getWmfRecordType()) { | |||
default: | |||
case arc: | |||
return FillDrawStyle.DRAW; | |||
case chord: | |||
case pie: | |||
return FillDrawStyle.FILL_DRAW; | |||
} | |||
} | |||
protected Arc2D getShape() { | |||
double startAngle = Math.toDegrees(Math.atan2(-(startPoint.getY() - bounds.getCenterY()), startPoint.getX() - bounds.getCenterX())); | |||
double endAngle = Math.toDegrees(Math.atan2(-(endPoint.getY() - bounds.getCenterY()), endPoint.getX() - bounds.getCenterX())); | |||
double arcAngle = (endAngle - startAngle) + (endAngle - startAngle > 0 ? 0 : 360); | |||
if (startAngle < 0) { | |||
startAngle += 360; | |||
@@ -608,28 +579,32 @@ public class HwmfDraw { | |||
boolean fillShape; | |||
int arcClosure; | |||
switch (getRecordType()) { | |||
switch (getWmfRecordType()) { | |||
default: | |||
case arc: | |||
arcClosure = Arc2D.OPEN; | |||
fillShape = false; | |||
break; | |||
case chord: | |||
arcClosure = Arc2D.CHORD; | |||
fillShape = true; | |||
break; | |||
case pie: | |||
arcClosure = Arc2D.PIE; | |||
fillShape = true; | |||
break; | |||
} | |||
Shape s = new Arc2D.Double(x, y, w, h, startAngle, arcAngle, arcClosure); | |||
if (fillShape) { | |||
ctx.fill(s); | |||
} else { | |||
ctx.draw(s); | |||
} | |||
return new Arc2D.Double(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), startAngle, arcAngle, arcClosure); | |||
} | |||
@Override | |||
public String toString() { | |||
Arc2D arc = getShape(); | |||
return | |||
"{ startPoint: "+pointToString(startPoint)+ | |||
", endPoint: "+pointToString(endPoint)+ | |||
", startAngle: "+arc.getAngleStart()+ | |||
", extentAngle: "+arc.getAngleExtent()+ | |||
", bounds: "+boundsToString(bounds)+ | |||
" }"; | |||
} | |||
} | |||
@@ -641,7 +616,7 @@ public class HwmfDraw { | |||
public static class WmfPie extends WmfArc { | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.pie; | |||
} | |||
} | |||
@@ -654,7 +629,7 @@ public class HwmfDraw { | |||
public static class WmfChord extends WmfArc { | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.chord; | |||
} | |||
} | |||
@@ -673,10 +648,10 @@ public class HwmfDraw { | |||
* A 16-bit unsigned integer used to index into the WMF Object Table to | |||
* get the object to be selected. | |||
*/ | |||
private int objectIndex; | |||
protected int objectIndex; | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.selectObject; | |||
} | |||
@@ -690,9 +665,113 @@ public class HwmfDraw { | |||
public void draw(HwmfGraphics ctx) { | |||
ctx.applyObjectTableEntry(objectIndex); | |||
} | |||
@Override | |||
public String toString() { | |||
return "{ index: "+objectIndex +" }"; | |||
} | |||
} | |||
private static int getWindingRule(HwmfGraphics ctx) { | |||
return ctx.getProperties().getPolyfillMode().awtFlag; | |||
static int readBounds(LittleEndianInputStream leis, Rectangle2D bounds) { | |||
/** | |||
* The 16-bit signed integers that defines the corners of the bounding rectangle. | |||
*/ | |||
int bottom = leis.readShort(); | |||
int right = leis.readShort(); | |||
int top = leis.readShort(); | |||
int left = leis.readShort(); | |||
int x = Math.min(left, right); | |||
int y = Math.min(top, bottom); | |||
int w = Math.abs(left - right - 1); | |||
int h = Math.abs(top - bottom - 1); | |||
bounds.setRect(x, y, w, h); | |||
return 4 * LittleEndianConsts.SHORT_SIZE; | |||
} | |||
} | |||
static int readRectS(LittleEndianInputStream leis, Rectangle2D bounds) { | |||
/** | |||
* The 16-bit signed integers that defines the corners of the bounding rectangle. | |||
*/ | |||
int left = leis.readShort(); | |||
int top = leis.readShort(); | |||
int right = leis.readShort(); | |||
int bottom = leis.readShort(); | |||
int x = Math.min(left, right); | |||
int y = Math.min(top, bottom); | |||
int w = Math.abs(left - right - 1); | |||
int h = Math.abs(top - bottom - 1); | |||
bounds.setRect(x, y, w, h); | |||
return 4 * LittleEndianConsts.SHORT_SIZE; | |||
} | |||
static int readPointS(LittleEndianInputStream leis, Point2D point) { | |||
/** a signed integer that defines the x/y-coordinate, in logical units. */ | |||
int y = leis.readShort(); | |||
int x = leis.readShort(); | |||
point.setLocation(x, y); | |||
return 2*LittleEndianConsts.SHORT_SIZE; | |||
} | |||
static String polyToString(Path2D poly) { | |||
StringBuilder sb = new StringBuilder(); | |||
sb.append("["); | |||
final PathIterator iter = poly.getPathIterator(null); | |||
double[] pnts = new double[6]; | |||
while (!iter.isDone()) { | |||
int segType = iter.currentSegment(pnts); | |||
switch (segType) { | |||
case PathIterator.SEG_MOVETO: | |||
sb.append("{ type: 'move', x: "+pnts[0]+", y: "+pnts[1]+" }, "); | |||
break; | |||
case PathIterator.SEG_LINETO: | |||
sb.append("{ type: 'lineto', x: "+pnts[0]+", y: "+pnts[1]+" }, "); | |||
break; | |||
case PathIterator.SEG_QUADTO: | |||
sb.append("{ type: 'quad', x1: "+pnts[0]+", y1: "+pnts[1]+", x2: "+pnts[2]+", y2: "+pnts[3]+" }, "); | |||
break; | |||
case PathIterator.SEG_CUBICTO: | |||
sb.append("{ type: 'cubic', x1: "+pnts[0]+", y1: "+pnts[1]+", x2: "+pnts[2]+", y2: "+pnts[3]+", x3: "+pnts[4]+", y3: "+pnts[5]+" }, "); | |||
break; | |||
case PathIterator.SEG_CLOSE: | |||
sb.append("{ type: 'close' }, "); | |||
break; | |||
} | |||
iter.next(); | |||
} | |||
sb.append("]"); | |||
return sb.toString(); | |||
} | |||
@Internal | |||
public static String pointToString(Point2D point) { | |||
return "{ x: "+point.getX()+", y: "+point.getY()+" }"; | |||
} | |||
@Internal | |||
public static String boundsToString(Rectangle2D bounds) { | |||
return "{ x: "+bounds.getX()+", y: "+bounds.getY()+", w: "+bounds.getWidth()+", h: "+bounds.getHeight()+" }"; | |||
} | |||
@Internal | |||
public static String dimToString(Dimension2D dim) { | |||
return "{ w: "+dim.getWidth()+", h: "+dim.getHeight()+" }"; | |||
} | |||
@Internal | |||
public static Rectangle2D normalizeBounds(Rectangle2D bounds) { | |||
return (bounds.getWidth() >= 0 && bounds.getHeight() >= 0) ? bounds | |||
: new Rectangle2D.Double( | |||
bounds.getWidth() >= 0 ? bounds.getMinX() : bounds.getMaxX(), | |||
bounds.getHeight() >= 0 ? bounds.getMinY() : bounds.getMaxY(), | |||
Math.abs(bounds.getWidth()), | |||
Math.abs(bounds.getHeight()) | |||
); | |||
} | |||
} |
@@ -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); | |||
HwmfDrawProperties prop = ctx.getProperties(); | |||
prop.setRasterOp(rasterOperation); | |||
if (bitmap.isValid()) { | |||
ctx.drawImage(getImage(), srcBounds, dstBounds); | |||
} else if (!dstBounds.isEmpty()) { | |||
BufferedImage bi = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB); | |||
ctx.drawImage(bi, new Rectangle2D.Double(0,0,100,100), dstBounds); | |||
} | |||
} | |||
@Override | |||
public void applyObject(HwmfGraphics ctx) { | |||
public BufferedImage getImage() { | |||
return bitmap.getImage(); | |||
} | |||
@Override | |||
public BufferedImage getImage() { | |||
return dib.getImage(); | |||
public String toString() { | |||
return | |||
"{ rasterOperation: '"+rasterOperation+"'"+ | |||
", colorUsage: '"+colorUsage+"'"+ | |||
", srcBounds: "+boundsToString(srcBounds)+ | |||
", dstBounds: "+boundsToString(dstBounds)+ | |||
"}"; | |||
} | |||
} | |||
public static class WmfBitBlt implements HwmfRecord { | |||
/** | |||
* A 32-bit unsigned integer that defines how the source pixels, the current brush in the playback | |||
* device context, and the destination pixels are to be combined to form the new image. | |||
*/ | |||
private HwmfTernaryRasterOp rasterOperation; | |||
/** | |||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the upper-left corner | |||
of the source rectangle. | |||
*/ | |||
private int ySrc; | |||
/** | |||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the upper-left corner | |||
of the source rectangle. | |||
*/ | |||
private int xSrc; | |||
/** | |||
* A 16-bit signed integer that defines the height, in logical units, of the source and | |||
destination rectangles. | |||
*/ | |||
private int height; | |||
/** | |||
* A 16-bit signed integer that defines the width, in logical units, of the source and destination | |||
rectangles. | |||
*/ | |||
private int width; | |||
/** | |||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the upper-left | |||
corner of the destination rectangle. | |||
*/ | |||
private int yDest; | |||
/** | |||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the upper-left | |||
corner of the destination rectangle. | |||
*/ | |||
private int xDest; | |||
/** | |||
* A variable-sized Bitmap16 Object that defines source image content. | |||
* This object MUST be specified, even if the raster operation does not require a source. | |||
*/ | |||
private HwmfBitmap16 target; | |||
public static class WmfBitBlt extends WmfStretchBlt { | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.bitBlt; | |||
} | |||
@@ -671,40 +531,32 @@ public class HwmfFill { | |||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { | |||
boolean hasBitmap = (recordSize/2 != ((recordFunction >> 8) + 3)); | |||
int size = 0; | |||
int rasterOpCode = leis.readUShort(); | |||
int rasterOpIndex = leis.readUShort(); | |||
rasterOperation = HwmfTernaryRasterOp.valueOf(rasterOpIndex); | |||
assert(rasterOpCode == rasterOperation.opCode); | |||
ySrc = leis.readShort(); | |||
xSrc = leis.readShort(); | |||
int size = 2*LittleEndianConsts.SHORT_SIZE; | |||
final Point2D srcPnt = new Point2D.Double(); | |||
size += readPointS(leis, srcPnt); | |||
size = 4*LittleEndianConsts.SHORT_SIZE; | |||
if (!hasBitmap) { | |||
/*int reserved =*/ leis.readShort(); | |||
size += LittleEndianConsts.SHORT_SIZE; | |||
} | |||
height = leis.readShort(); | |||
width = leis.readShort(); | |||
yDest = leis.readShort(); | |||
xDest = leis.readShort(); | |||
size += 4*LittleEndianConsts.SHORT_SIZE; | |||
size += readBounds2(leis, dstBounds); | |||
if (hasBitmap) { | |||
target = new HwmfBitmap16(); | |||
size += target.init(leis); | |||
} | |||
return size; | |||
} | |||
@Override | |||
public void draw(HwmfGraphics ctx) { | |||
srcBounds.setRect(srcPnt.getX(), srcPnt.getY(), dstBounds.getWidth(), dstBounds.getHeight()); | |||
return size; | |||
} | |||
} | |||
@@ -729,36 +581,13 @@ public class HwmfFill { | |||
* A 16-bit unsigned integer that defines the starting scan line in the source. | |||
*/ | |||
private int startScan; | |||
/** | |||
* A 16-bit unsigned integer that defines the y-coordinate, in logical units, of the | |||
* source rectangle. | |||
*/ | |||
private int yDib; | |||
/** | |||
* A 16-bit unsigned integer that defines the x-coordinate, in logical units, of the | |||
* source rectangle. | |||
*/ | |||
private int xDib; | |||
/** | |||
* A 16-bit unsigned integer that defines the height, in logical units, of the | |||
* source and destination rectangles. | |||
*/ | |||
private int height; | |||
/** | |||
* A 16-bit unsigned integer that defines the width, in logical units, of the | |||
* source and destination rectangles. | |||
*/ | |||
private int width; | |||
/** | |||
* A 16-bit unsigned integer that defines the y-coordinate, in logical units, of the | |||
* upper-left corner of the destination rectangle. | |||
*/ | |||
private int yDest; | |||
/** | |||
* A 16-bit unsigned integer that defines the x-coordinate, in logical units, of the | |||
* upper-left corner of the destination rectangle. | |||
*/ | |||
private int xDest; | |||
/** the source rectangle */ | |||
protected final Rectangle2D srcBounds = new Rectangle2D.Double(); | |||
/** the destination rectangle, having the same dimension as the source rectangle */ | |||
protected final Rectangle2D dstBounds = new Rectangle2D.Double(); | |||
/** | |||
* A variable-sized DeviceIndependentBitmap Object that is the source of the color data. | |||
*/ | |||
@@ -766,7 +595,7 @@ public class HwmfFill { | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.setDibToDev; | |||
} | |||
@@ -775,17 +604,19 @@ public class HwmfFill { | |||
colorUsage = ColorUsage.valueOf(leis.readUShort()); | |||
scanCount = leis.readUShort(); | |||
startScan = leis.readUShort(); | |||
yDib = leis.readUShort(); | |||
xDib = leis.readUShort(); | |||
height = leis.readUShort(); | |||
width = leis.readUShort(); | |||
yDest = leis.readUShort(); | |||
xDest = leis.readUShort(); | |||
int size = 9*LittleEndianConsts.SHORT_SIZE; | |||
int size = 3*LittleEndianConsts.SHORT_SIZE; | |||
final Point2D srcPnt = new Point2D.Double(); | |||
size += readPointS(leis, srcPnt); | |||
size += readBounds2(leis, dstBounds); | |||
dib = new HwmfBitmapDib(); | |||
size += dib.init(leis, (int)(recordSize-6-size)); | |||
srcBounds.setRect(srcPnt.getX(), srcPnt.getY(), dstBounds.getWidth(), dstBounds.getHeight()); | |||
return size; | |||
} | |||
@@ -806,52 +637,9 @@ public class HwmfFill { | |||
} | |||
public static class WmfDibBitBlt implements HwmfRecord, HwmfImageRecord, HwmfObjectTableEntry { | |||
/** | |||
* A 32-bit unsigned integer that defines how the source pixels, the current brush | |||
* in the playback device context, and the destination pixels are to be combined to form the | |||
* new image. This code MUST be one of the values in the Ternary Raster Operation Enumeration. | |||
*/ | |||
HwmfTernaryRasterOp rasterOperation; | |||
/** | |||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the source rectangle. | |||
*/ | |||
private int ySrc; | |||
/** | |||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the source rectangle. | |||
*/ | |||
private int xSrc; | |||
/** | |||
* A 16-bit signed integer that defines the height, in logical units, of the source and | |||
* destination rectangles. | |||
*/ | |||
private int height; | |||
/** | |||
* A 16-bit signed integer that defines the width, in logical units, of the source and destination | |||
* rectangles. | |||
*/ | |||
private int width; | |||
/** | |||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the upper-left | |||
* corner of the destination rectangle. | |||
*/ | |||
private int yDest; | |||
/** | |||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the upper-left | |||
* corner of the destination rectangle. | |||
*/ | |||
private int xDest; | |||
/** | |||
* A variable-sized DeviceIndependentBitmap Object that defines image content. | |||
* This object MUST be specified, even if the raster operation does not require a source. | |||
*/ | |||
private HwmfBitmapDib target; | |||
public static class WmfDibBitBlt extends WmfDibStretchBlt { | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.dibBitBlt; | |||
} | |||
@@ -859,47 +647,31 @@ public class HwmfFill { | |||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { | |||
boolean hasBitmap = (recordSize/2 != ((recordFunction >> 8) + 3)); | |||
int size = 0; | |||
int rasterOpCode = leis.readUShort(); | |||
int rasterOpIndex = leis.readUShort(); | |||
rasterOperation = HwmfTernaryRasterOp.valueOf(rasterOpIndex); | |||
assert(rasterOpCode == rasterOperation.opCode); | |||
ySrc = leis.readShort(); | |||
xSrc = leis.readShort(); | |||
size = 4*LittleEndianConsts.SHORT_SIZE; | |||
int size = 2*LittleEndianConsts.SHORT_SIZE; | |||
final Point2D srcPnt = new Point2D.Double(); | |||
size += readPointS(leis, srcPnt); | |||
if (!hasBitmap) { | |||
/*int reserved =*/ leis.readShort(); | |||
size += LittleEndianConsts.SHORT_SIZE; | |||
} | |||
height = leis.readShort(); | |||
width = leis.readShort(); | |||
yDest = leis.readShort(); | |||
xDest = leis.readShort(); | |||
size += 4*LittleEndianConsts.SHORT_SIZE; | |||
size += readBounds2(leis, dstBounds); | |||
if (hasBitmap) { | |||
target = new HwmfBitmapDib(); | |||
size += target.init(leis, (int)(recordSize-6-size)); | |||
} | |||
return size; | |||
} | |||
@Override | |||
public void draw(HwmfGraphics ctx) { | |||
ctx.addObjectTableEntry(this); | |||
} | |||
@Override | |||
public void applyObject(HwmfGraphics ctx) { | |||
} | |||
// the destination rectangle, having the same dimension as the source rectangle | |||
srcBounds.setRect(srcPnt.getX(), srcPnt.getY(), dstBounds.getWidth(), dstBounds.getHeight()); | |||
@Override | |||
public BufferedImage getImage() { | |||
return (target == null) ? null : target.getImage(); | |||
return size; | |||
} | |||
} | |||
@@ -909,53 +681,22 @@ public class HwmfFill { | |||
* in the playback device context, and the destination pixels are to be combined to form the | |||
* new image. This code MUST be one of the values in the Ternary Raster Operation Enumeration. | |||
*/ | |||
private HwmfTernaryRasterOp rasterOperation; | |||
/** | |||
* A 16-bit signed integer that defines the height, in logical units, of the source rectangle. | |||
*/ | |||
private int srcHeight; | |||
/** | |||
* A 16-bit signed integer that defines the width, in logical units, of the source rectangle. | |||
*/ | |||
private int srcWidth; | |||
/** | |||
* A 16-bit signed integer that defines the y-coordinate, in logical units, of the | |||
* upper-left corner of the source rectangle. | |||
*/ | |||
private int ySrc; | |||
/** | |||
* A 16-bit signed integer that defines the x-coordinate, in logical units, of the | |||
* upper-left corner of the source rectangle. | |||
*/ | |||
private int xSrc; | |||
/** | |||
* A 16-bit signed integer that defines the height, in logical units, of the | |||
* destination rectangle. | |||
*/ | |||
private int destHeight; | |||
/** | |||
* A 16-bit signed integer that defines the width, in logical units, of the | |||
* destination rectangle. | |||
*/ | |||
private int destWidth; | |||
/** | |||
* A 16-bit signed integer that defines the y-coordinate, in logical units, | |||
* of the upper-left corner of the destination rectangle. | |||
*/ | |||
private int yDest; | |||
/** | |||
* A 16-bit signed integer that defines the x-coordinate, in logical units, | |||
* of the upper-left corner of the destination rectangle. | |||
*/ | |||
private int xDest; | |||
protected HwmfTernaryRasterOp rasterOperation; | |||
/** the source rectangle */ | |||
protected final Rectangle2D srcBounds = new Rectangle2D.Double(); | |||
/** the destination rectangle */ | |||
protected final Rectangle2D dstBounds = new Rectangle2D.Double(); | |||
/** | |||
* A variable-sized DeviceIndependentBitmap Object that defines image content. | |||
* This object MUST be specified, even if the raster operation does not require a source. | |||
*/ | |||
HwmfBitmapDib target; | |||
protected HwmfBitmapDib target; | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.dibStretchBlt; | |||
} | |||
@@ -963,27 +704,21 @@ public class HwmfFill { | |||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { | |||
boolean hasBitmap = (recordSize > ((recordFunction >> 8) + 3)); | |||
int size = 0; | |||
int rasterOpCode = leis.readUShort(); | |||
int rasterOpIndex = leis.readUShort(); | |||
rasterOperation = HwmfTernaryRasterOp.valueOf(rasterOpIndex); | |||
assert(rasterOpCode == rasterOperation.opCode); | |||
srcHeight = leis.readShort(); | |||
srcWidth = leis.readShort(); | |||
ySrc = leis.readShort(); | |||
xSrc = leis.readShort(); | |||
size = 6*LittleEndianConsts.SHORT_SIZE; | |||
int size = 2*LittleEndianConsts.SHORT_SIZE; | |||
size += readBounds2(leis, srcBounds); | |||
if (!hasBitmap) { | |||
/*int reserved =*/ leis.readShort(); | |||
size += LittleEndianConsts.SHORT_SIZE; | |||
} | |||
destHeight = leis.readShort(); | |||
destWidth = leis.readShort(); | |||
yDest = leis.readShort(); | |||
xDest = leis.readShort(); | |||
size += 4*LittleEndianConsts.SHORT_SIZE; | |||
size += readBounds2(leis, dstBounds); | |||
if (hasBitmap) { | |||
target = new HwmfBitmapDib(); | |||
size += target.init(leis, (int)(recordSize-6-size)); | |||
@@ -996,15 +731,30 @@ public class HwmfFill { | |||
public void draw(HwmfGraphics ctx) { | |||
ctx.addObjectTableEntry(this); | |||
} | |||
@Override | |||
public void applyObject(HwmfGraphics ctx) { | |||
} | |||
@Override | |||
public BufferedImage getImage() { | |||
return target.getImage(); | |||
return (target != null && target.isValid()) ? target.getImage() : null; | |||
} | |||
} | |||
static int readBounds2(LittleEndianInputStream leis, Rectangle2D bounds) { | |||
/** | |||
* The 16-bit signed integers that defines the corners of the bounding rectangle. | |||
*/ | |||
int h = leis.readShort(); | |||
int w = leis.readShort(); | |||
int y = leis.readShort(); | |||
int x = leis.readShort(); | |||
bounds.setRect(x, y, w, h); | |||
return 4 * LittleEndianConsts.SHORT_SIZE; | |||
} | |||
} |
@@ -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; | |||
protected final Point2D reference = new Point2D.Double(); | |||
/** | |||
* A 16-bit signed integer that defines the x-coordinate, in logical units, where the | |||
text string is to be located. | |||
* A 16-bit signed integer that defines the length of the string. | |||
*/ | |||
private int x; | |||
protected int stringLength; | |||
/** | |||
* A 16-bit signed integer that defines the length of the string. | |||
* A 16-bit unsigned integer that defines the use of the application-defined | |||
* rectangle. This member can be a combination of one or more values in the | |||
* ExtTextOutOptions Flags (ETO_*) | |||
*/ | |||
private int stringLength; | |||
/** | |||
* A 16-bit unsigned integer that defines the use of the application-defined | |||
* rectangle. This member can be a combination of one or more values in the | |||
* ExtTextOutOptions Flags (ETO_*) | |||
*/ | |||
private int fwOpts; | |||
protected final WmfExtTextOutOptions options; | |||
/** | |||
* An optional 8-byte Rect Object (section 2.2.2.18) that defines the | |||
* dimensions, in logical coordinates, of a rectangle that is used for clipping, opaquing, or both. | |||
@@ -274,24 +321,32 @@ public class HwmfText { | |||
* Each value is a 16-bit signed integer that defines the coordinate, in logical coordinates, of | |||
* the upper-left corner of the rectangle | |||
*/ | |||
private int left,top,right,bottom; | |||
protected final Rectangle2D bounds = new Rectangle2D.Double(); | |||
/** | |||
* A variable-length string that specifies the text to be drawn. The string does | |||
* not need to be null-terminated, because StringLength specifies the length of the string. If | |||
* the length is odd, an extra byte is placed after it so that the following member (optional Dx) is | |||
* aligned on a 16-bit boundary. | |||
*/ | |||
private byte[] rawTextBytes; | |||
protected byte[] rawTextBytes; | |||
/** | |||
* An optional array of 16-bit signed integers that indicate the distance between | |||
* origins of adjacent character cells. For example, Dx[i] logical units separate the origins of | |||
* character cell i and character cell i + 1. If this field is present, there MUST be the same | |||
* number of values as there are characters in the string. | |||
*/ | |||
private int dx[]; | |||
protected final List<Integer> dx = new ArrayList<>(); | |||
public WmfExtTextOut() { | |||
this(new WmfExtTextOutOptions()); | |||
} | |||
protected WmfExtTextOut(WmfExtTextOutOptions options) { | |||
this.options = options; | |||
} | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.extTextOut; | |||
} | |||
@@ -299,22 +354,17 @@ public class HwmfText { | |||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { | |||
// -6 bytes of record function and length header | |||
final int remainingRecordSize = (int)(recordSize-6); | |||
y = leis.readShort(); | |||
x = leis.readShort(); | |||
int size = readPointS(leis, reference); | |||
stringLength = leis.readShort(); | |||
fwOpts = leis.readUShort(); | |||
int size = 4*LittleEndianConsts.SHORT_SIZE; | |||
size += LittleEndianConsts.SHORT_SIZE; | |||
size += options.init(leis); | |||
// Check if we have a rectangle | |||
if ((ETO_OPAQUE.isSet(fwOpts) || ETO_CLIPPED.isSet(fwOpts)) && size+8<=remainingRecordSize) { | |||
// the bounding rectangle is optional and only read when fwOpts are given | |||
left = leis.readShort(); | |||
top = leis.readShort(); | |||
right = leis.readShort(); | |||
bottom = leis.readShort(); | |||
size += 4*LittleEndianConsts.SHORT_SIZE; | |||
if ((options.isOpaque() || options.isClipped()) && size+8<=remainingRecordSize) { | |||
// the bounding rectangle is optional and only read when options are given | |||
size += readRectS(leis, bounds); | |||
} | |||
rawTextBytes = IOUtils.safelyAllocate(stringLength+(stringLength&1), MAX_RECORD_LENGTH); | |||
@@ -331,9 +381,8 @@ public class HwmfText { | |||
logger.log(POILogger.WARN, "META_EXTTEXTOUT tracking info doesn't cover all characters"); | |||
} | |||
dx = new int[stringLength]; | |||
for (int i=0; i<dxLen; i++) { | |||
dx[i] = leis.readShort(); | |||
dx.add((int)leis.readShort()); | |||
size += LittleEndianConsts.SHORT_SIZE; | |||
} | |||
@@ -342,24 +391,38 @@ public class HwmfText { | |||
@Override | |||
public void draw(HwmfGraphics ctx) { | |||
Rectangle2D bounds = new Rectangle2D.Double(x, y, 0, 0); | |||
ctx.drawString(getTextBytes(), bounds, dx); | |||
ctx.drawString(rawTextBytes, stringLength, reference, null, bounds, options, dx, false); | |||
} | |||
public String getText(Charset charset) { | |||
return new String(getTextBytes(), charset); | |||
public String getText(Charset charset) throws IOException { | |||
return new String(rawTextBytes, charset).substring(0, stringLength); | |||
} | |||
/** | |||
* | |||
* @return a copy of a trimmed byte array of rawTextBytes bytes. | |||
* This includes only the bytes from 0..stringLength. | |||
* This does not include the extra optional padding on the byte array. | |||
*/ | |||
private byte[] getTextBytes() { | |||
byte[] ret = IOUtils.safelyAllocate(stringLength, MAX_RECORD_LENGTH); | |||
System.arraycopy(rawTextBytes, 0, ret, 0, stringLength); | |||
return ret; | |||
public Point2D getReference() { | |||
return reference; | |||
} | |||
public Rectangle2D getBounds() { | |||
return bounds; | |||
} | |||
protected boolean isUnicode() { | |||
return false; | |||
} | |||
@Override | |||
public String toString() { | |||
String text = ""; | |||
try { | |||
text = getText(isUnicode() ? Charsets.UTF_16LE : LocaleUtil.CHARSET_1252); | |||
} catch (IOException ignored) { | |||
} | |||
return | |||
"{ reference: " + pointToString(reference) + | |||
", bounds: " + boundsToString(bounds) + | |||
", text: '"+text.replaceAll("\\p{Cntrl}",".")+"'"+ | |||
"}"; | |||
} | |||
} | |||
@@ -380,57 +443,17 @@ public class HwmfText { | |||
*/ | |||
public static class WmfSetTextAlign implements HwmfRecord { | |||
// *********************************************************************************** | |||
// TextAlignmentMode Flags: | |||
// *********************************************************************************** | |||
/** | |||
* The drawing position in the playback device context MUST NOT be updated after each | |||
* text output call. The reference point MUST be passed to the text output function. | |||
*/ | |||
@SuppressWarnings("unused") | |||
private static final BitField TA_NOUPDATECP = BitFieldFactory.getInstance(0x0000); | |||
/** | |||
* The reference point MUST be on the left edge of the bounding rectangle. | |||
*/ | |||
@SuppressWarnings("unused") | |||
private static final BitField TA_LEFT = BitFieldFactory.getInstance(0x0000); | |||
/** | |||
* The reference point MUST be on the top edge of the bounding rectangle. | |||
*/ | |||
@SuppressWarnings("unused") | |||
private static final BitField TA_TOP = BitFieldFactory.getInstance(0x0000); | |||
/** | |||
* The drawing position in the playback device context MUST be updated after each text | |||
* output call. It MUST be used as the reference point. | |||
* output call. It MUST be used as the reference point.<p> | |||
* | |||
* If the flag is not set, the option TA_NOUPDATECP is active, i.e. the drawing position | |||
* in the playback device context MUST NOT be updated after each text output call. | |||
* The reference point MUST be passed to the text output function. | |||
*/ | |||
@SuppressWarnings("unused") | |||
private static final BitField TA_UPDATECP = BitFieldFactory.getInstance(0x0001); | |||
/** | |||
* The reference point MUST be on the right edge of the bounding rectangle. | |||
*/ | |||
private static final BitField TA_RIGHT = BitFieldFactory.getInstance(0x0002); | |||
/** | |||
* The reference point MUST be aligned horizontally with the center of the bounding | |||
* rectangle. | |||
*/ | |||
private static final BitField TA_CENTER = BitFieldFactory.getInstance(0x0006); | |||
/** | |||
* The reference point MUST be on the bottom edge of the bounding rectangle. | |||
*/ | |||
private static final BitField TA_BOTTOM = BitFieldFactory.getInstance(0x0008); | |||
/** | |||
* The reference point MUST be on the baseline of the text. | |||
*/ | |||
private static final BitField TA_BASELINE = BitFieldFactory.getInstance(0x0018); | |||
/** | |||
* The text MUST be laid out in right-to-left reading order, instead of the default | |||
* left-to-right order. This SHOULD be applied only when the font that is defined in the | |||
@@ -438,43 +461,64 @@ public class HwmfText { | |||
*/ | |||
@SuppressWarnings("unused") | |||
private static final BitField TA_RTLREADING = BitFieldFactory.getInstance(0x0100); | |||
// *********************************************************************************** | |||
// VerticalTextAlignmentMode Flags (e.g. for Kanji fonts) | |||
// *********************************************************************************** | |||
private static final BitField ALIGN_MASK = BitFieldFactory.getInstance(0x0006); | |||
/** | |||
* The reference point MUST be on the top edge of the bounding rectangle. | |||
* Flag TA_LEFT (0x0000): | |||
* The reference point MUST be on the left edge of the bounding rectangle, | |||
* if all bits of the align mask (latin mode) are unset. | |||
* | |||
* Flag VTA_TOP (0x0000): | |||
* The reference point MUST be on the top edge of the bounding rectangle, | |||
* if all bits of the valign mask are unset. | |||
*/ | |||
@SuppressWarnings("unused") | |||
private static final BitField VTA_TOP = BitFieldFactory.getInstance(0x0000); | |||
private static final int ALIGN_LEFT = 0; | |||
/** | |||
* Flag TA_RIGHT (0x0002): | |||
* The reference point MUST be on the right edge of the bounding rectangle. | |||
*/ | |||
@SuppressWarnings("unused") | |||
private static final BitField VTA_RIGHT = BitFieldFactory.getInstance(0x0000); | |||
/** | |||
* | |||
* Flag VTA_BOTTOM (0x0002): | |||
* The reference point MUST be on the bottom edge of the bounding rectangle. | |||
*/ | |||
private static final BitField VTA_BOTTOM = BitFieldFactory.getInstance(0x0002); | |||
private static final int ALIGN_RIGHT = 1; | |||
/** | |||
* The reference point MUST be aligned vertically with the center of the bounding | |||
* Flag TA_CENTER (0x0006) / VTA_CENTER (0x0006): | |||
* The reference point MUST be aligned horizontally with the center of the bounding | |||
* rectangle. | |||
*/ | |||
private static final BitField VTA_CENTER = BitFieldFactory.getInstance(0x0006); | |||
private static final int ALIGN_CENTER = 3; | |||
private static final BitField VALIGN_MASK = BitFieldFactory.getInstance(0x0018); | |||
/** | |||
* Flag TA_TOP (0x0000): | |||
* The reference point MUST be on the top edge of the bounding rectangle, | |||
* if all bits of the valign mask are unset. | |||
* | |||
* Flag VTA_RIGHT (0x0000): | |||
* The reference point MUST be on the right edge of the bounding rectangle, | |||
* if all bits of the align mask (asian mode) are unset. | |||
*/ | |||
private static final int VALIGN_TOP = 0; | |||
/** | |||
* Flag TA_BOTTOM (0x0008): | |||
* The reference point MUST be on the bottom edge of the bounding rectangle. | |||
* | |||
* Flag VTA_LEFT (0x0008): | |||
* The reference point MUST be on the left edge of the bounding rectangle. | |||
*/ | |||
private static final BitField VTA_LEFT = BitFieldFactory.getInstance(0x0008); | |||
private static final int VALIGN_BOTTOM = 1; | |||
/** | |||
* Flag TA_BASELINE (0x0018) / VTA_BASELINE (0x0018): | |||
* The reference point MUST be on the baseline of the text. | |||
*/ | |||
private static final BitField VTA_BASELINE = BitFieldFactory.getInstance(0x0018); | |||
private static final int VALIGN_BASELINE = 3; | |||
/** | |||
* A 16-bit unsigned integer that defines text alignment. | |||
@@ -482,10 +526,10 @@ public class HwmfText { | |||
* for text with a horizontal baseline, and VerticalTextAlignmentMode Flags | |||
* for text with a vertical baseline. | |||
*/ | |||
private int textAlignmentMode; | |||
protected int textAlignmentMode; | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.setTextAlign; | |||
} | |||
@@ -498,52 +542,90 @@ public class HwmfText { | |||
@Override | |||
public void draw(HwmfGraphics ctx) { | |||
HwmfDrawProperties props = ctx.getProperties(); | |||
if (TA_CENTER.isSet(textAlignmentMode)) { | |||
props.setTextAlignLatin(HwmfTextAlignment.CENTER); | |||
} else if (TA_RIGHT.isSet(textAlignmentMode)) { | |||
props.setTextAlignLatin(HwmfTextAlignment.RIGHT); | |||
} else { | |||
props.setTextAlignLatin(HwmfTextAlignment.LEFT); | |||
props.setTextAlignLatin(getAlignLatin()); | |||
props.setTextVAlignLatin(getVAlignLatin()); | |||
props.setTextAlignAsian(getAlignAsian()); | |||
props.setTextVAlignAsian(getVAlignAsian()); | |||
} | |||
@Override | |||
public String toString() { | |||
return | |||
"{ align: '"+ getAlignLatin() + "'" + | |||
", valign: '"+ getVAlignLatin() + "'" + | |||
", alignAsian: '"+ getAlignAsian() + "'" + | |||
", valignAsian: '"+ getVAlignAsian() + "'" + | |||
"}"; | |||
} | |||
private HwmfTextAlignment getAlignLatin() { | |||
switch (ALIGN_MASK.getValue(textAlignmentMode)) { | |||
default: | |||
case ALIGN_LEFT: | |||
return HwmfTextAlignment.LEFT; | |||
case ALIGN_CENTER: | |||
return HwmfTextAlignment.CENTER; | |||
case ALIGN_RIGHT: | |||
return HwmfTextAlignment.RIGHT; | |||
} | |||
} | |||
if (VTA_CENTER.isSet(textAlignmentMode)) { | |||
props.setTextAlignAsian(HwmfTextAlignment.CENTER); | |||
} else if (VTA_LEFT.isSet(textAlignmentMode)) { | |||
props.setTextAlignAsian(HwmfTextAlignment.LEFT); | |||
} else { | |||
props.setTextAlignAsian(HwmfTextAlignment.RIGHT); | |||
private HwmfTextVerticalAlignment getVAlignLatin() { | |||
switch (VALIGN_MASK.getValue(textAlignmentMode)) { | |||
default: | |||
case VALIGN_TOP: | |||
return HwmfTextVerticalAlignment.TOP; | |||
case VALIGN_BASELINE: | |||
return HwmfTextVerticalAlignment.BASELINE; | |||
case VALIGN_BOTTOM: | |||
return HwmfTextVerticalAlignment.BOTTOM; | |||
} | |||
} | |||
if (TA_BASELINE.isSet(textAlignmentMode)) { | |||
props.setTextVAlignLatin(HwmfTextVerticalAlignment.BASELINE); | |||
} else if (TA_BOTTOM.isSet(textAlignmentMode)) { | |||
props.setTextVAlignLatin(HwmfTextVerticalAlignment.BOTTOM); | |||
} else { | |||
props.setTextVAlignLatin(HwmfTextVerticalAlignment.TOP); | |||
private HwmfTextAlignment getAlignAsian() { | |||
switch (getVAlignLatin()) { | |||
default: | |||
case TOP: | |||
return HwmfTextAlignment.RIGHT; | |||
case BASELINE: | |||
return HwmfTextAlignment.CENTER; | |||
case BOTTOM: | |||
return HwmfTextAlignment.LEFT; | |||
} | |||
} | |||
if (VTA_BASELINE.isSet(textAlignmentMode)) { | |||
props.setTextVAlignAsian(HwmfTextVerticalAlignment.BASELINE); | |||
} else if (VTA_BOTTOM.isSet(textAlignmentMode)) { | |||
props.setTextVAlignAsian(HwmfTextVerticalAlignment.BOTTOM); | |||
} else { | |||
props.setTextVAlignAsian(HwmfTextVerticalAlignment.TOP); | |||
private HwmfTextVerticalAlignment getVAlignAsian() { | |||
switch (getAlignLatin()) { | |||
default: | |||
case LEFT: | |||
return HwmfTextVerticalAlignment.TOP; | |||
case CENTER: | |||
return HwmfTextVerticalAlignment.BASELINE; | |||
case RIGHT: | |||
return HwmfTextVerticalAlignment.BOTTOM; | |||
} | |||
} | |||
} | |||
public static class WmfCreateFontIndirect implements HwmfRecord, HwmfObjectTableEntry { | |||
private HwmfFont font; | |||
protected final HwmfFont font; | |||
public WmfCreateFontIndirect() { | |||
this(new HwmfFont()); | |||
} | |||
protected WmfCreateFontIndirect(HwmfFont font) { | |||
this.font = font; | |||
} | |||
@Override | |||
public HwmfRecordType getRecordType() { | |||
public HwmfRecordType getWmfRecordType() { | |||
return HwmfRecordType.createFontIndirect; | |||
} | |||
@Override | |||
public int init(LittleEndianInputStream leis, long recordSize, int recordFunction) throws IOException { | |||
font = new HwmfFont(); | |||
return font.init(leis); | |||
return font.init(leis, recordSize); | |||
} | |||
@Override | |||
@@ -559,5 +641,10 @@ public class HwmfText { | |||
public HwmfFont getFont() { | |||
return font; | |||
} | |||
@Override | |||
public String toString() { | |||
return "{ font: "+font+" } "; | |||
} | |||
} | |||
} |
@@ -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"); | |||
} |