blob: d0eaebd55b49d411badef8cf29c1b8335567b80e [file] [log] [blame]
/*
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.batik.transcoder.wmf.tosvg;
import java.awt.Color;
import java.awt.Font;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import org.apache.batik.ext.awt.geom.Polygon2D;
import org.apache.batik.ext.awt.geom.Polyline2D;
import org.apache.batik.transcoder.wmf.WMFConstants;
/**
* This class holds simple properties about a WMF Metafile. It can be used
* whenever general information must be retrieved about this file.
*
* @version $Id$
*/
public class WMFHeaderProperties extends AbstractWMFReader {
private static final Integer INTEGER_0 = new Integer( 0 );
protected DataInputStream stream;
private int _bleft, _bright, _btop, _bbottom, _bwidth, _bheight;
private int _ileft, _iright, _itop, _ibottom;
private float scale = 1.0f;
private int startX = 0;
private int startY = 0;
private int currentHorizAlign = 0;
private int currentVertAlign = 0;
private WMFFont wf = null;
private static final FontRenderContext fontCtx =
new FontRenderContext(new AffineTransform(), false, true);
private transient boolean firstEffectivePaint = true;
public static final int PEN = 1;
public static final int BRUSH = 2;
public static final int FONT = 3;
public static final int NULL_PEN = 4;
public static final int NULL_BRUSH = 5;
public static final int PALETTE = 6;
public static final int OBJ_BITMAP = 7;
public static final int OBJ_REGION = 8;
/** Creates a new WMFHeaderProperties, and sets the associated WMF File.
* @param wmffile the WMF Metafile
*/
public WMFHeaderProperties(File wmffile) throws IOException {
super();
reset();
stream = new DataInputStream(new BufferedInputStream(new FileInputStream(wmffile)));
read(stream);
stream.close();
}
/** Creates a new WMFHeaderProperties, with no associated file.
*/
public WMFHeaderProperties() {
super();
}
public void closeResource() {
try {
if (stream != null) stream.close();
} catch (IOException e) {
}
}
/** Creates the properties associated file.
*/
public void setFile(File wmffile) throws IOException {
stream = new DataInputStream(new BufferedInputStream(new FileInputStream(wmffile)));
read(stream);
stream.close();
}
/**
* Resets the internal storage and viewport coordinates.
*/
public void reset() {
left = 0;
right = 0;
top = 1000;
bottom = 1000;
inch = 84;
_bleft = -1;
_bright = -1;
_btop = -1;
_bbottom = -1;
_ileft = -1;
_iright = -1;
_itop = -1;
_ibottom = -1;
_bwidth = -1;
_bheight= -1;
vpW = -1;
vpH = -1;
vpX = 0;
vpY = 0;
startX = 0;
startY = 0;
scaleXY = 1f;
firstEffectivePaint = true;
}
/** Get the associated stream.
*/
public DataInputStream getStream() {
return stream;
}
protected boolean readRecords(DataInputStream is) throws IOException {
// effective reading of the rest of the file
short functionId = 1;
int recSize = 0;
int gdiIndex; // the last Object index
int brushObject = -1; // the last brush
int penObject = -1; // the last pen
int fontObject = -1; // the last font
GdiObject gdiObj;
while (functionId > 0) {
recSize = readInt( is );
// Subtract size in 16-bit words of recSize and functionId;
recSize -= 3;
functionId = readShort( is );
if ( functionId <= 0 )
break;
switch ( functionId ) {
case WMFConstants.META_SETMAPMODE: {
int mapmode = readShort( is );
// change isotropic if mode is anisotropic
if (mapmode == WMFConstants.MM_ANISOTROPIC) isotropic = false;
}
break;
case WMFConstants.META_SETWINDOWORG: {
vpY = readShort( is );
vpX = readShort( is );
}
break;
case WMFConstants.META_SETWINDOWEXT: {
vpH = readShort( is );
vpW = readShort( is );
if (! isotropic) scaleXY = (float)vpW / (float)vpH;
vpW = (int)(vpW * scaleXY);
}
break;
case WMFConstants.META_CREATEPENINDIRECT:
{
int objIndex = 0;
int penStyle = readShort( is );
readInt( is ); // width
// color definition
int colorref = readInt( is );
int red = colorref & 0xff;
int green = ( colorref & 0xff00 ) >> 8;
int blue = ( colorref & 0xff0000 ) >> 16;
Color color = new Color( red, green, blue);
if (recSize == 6) readShort(is); // if size greater than 5
if ( penStyle == WMFConstants.META_PS_NULL ) {
objIndex = addObjectAt( NULL_PEN, color, objIndex );
} else {
objIndex = addObjectAt( PEN, color, objIndex );
}
}
break;
case WMFConstants.META_CREATEBRUSHINDIRECT:
{
int objIndex = 0;
int brushStyle = readShort( is );
// color definition
int colorref = readInt( is );
int red = colorref & 0xff;
int green = ( colorref & 0xff00 ) >> 8;
int blue = ( colorref & 0xff0000 ) >> 16;
Color color = new Color( red, green, blue);
readShort( is ); // hatch
if ( brushStyle == WMFConstants.META_PS_NULL ) {
objIndex = addObjectAt( NULL_BRUSH, color, objIndex);
} else
objIndex = addObjectAt(BRUSH, color, objIndex );
}
break;
case WMFConstants.META_SETTEXTALIGN:
int align = readShort( is );
// need to do this, because sometimes there is more than one short
if (recSize > 1) for (int i = 1; i < recSize; i++) readShort( is );
currentHorizAlign = WMFUtilities.getHorizontalAlignment(align);
currentVertAlign = WMFUtilities.getVerticalAlignment(align);
break;
case WMFConstants.META_EXTTEXTOUT: {
int y = readShort( is );
int x = (int)(readShort( is ) * scaleXY);
int lenText = readShort( is );
int flag = readShort( is );
int read = 4; // used to track the actual size really read
boolean clipped = false;
int x1 = 0, y1 = 0, x2 = 0, y2 = 0;
int len;
// determination of clipping property
if ((flag & WMFConstants.ETO_CLIPPED) != 0) {
x1 = (int)(readShort( is ) * scaleXY);
y1 = readShort( is );
x2 = (int)(readShort( is ) * scaleXY);
y2 = readShort( is );
read += 4;
clipped = true;
}
byte[] bstr = new byte[ lenText ];
int i = 0;
for ( ; i < lenText; i++ ) {
bstr[ i ] = is.readByte();
}
String sr = WMFUtilities.decodeString(wf, bstr);
read += (lenText + 1)/2;
/* must do this because WMF strings always have an even number of bytes, even
* if there is an odd number of characters
*/
if (lenText % 2 != 0) is.readByte();
// if the record was not completely read, finish reading
if (read < recSize) for (int j = read; j < recSize; j++) readShort( is );
TextLayout layout = new TextLayout( sr, wf.font, fontCtx );
int lfWidth = (int)layout.getBounds().getWidth();
x = (int)layout.getBounds().getX();
int lfHeight =
(int)getVerticalAlignmentValue(layout, currentVertAlign);
resizeBounds(x, y);
resizeBounds(x+lfWidth, y+lfHeight);
firstEffectivePaint = false;
}
break;
case WMFConstants.META_DRAWTEXT:
case WMFConstants.META_TEXTOUT: {
int len = readShort( is );
int read = 1; // used to track the actual size really read
byte[] bstr = new byte[ len ];
for ( int i = 0; i < len; i++ ) {
bstr[ i ] = is.readByte();
}
String sr = WMFUtilities.decodeString(wf, bstr);
/* must do this because WMF strings always have an even number of bytes, even
* if there is an odd number of characters
*/
if (len % 2 != 0) is.readByte();
read += (len + 1) / 2;
int y = readShort( is );
int x = (int)(readShort( is ) * scaleXY);
read += 2;
// if the record was not completely read, finish reading
if (read < recSize) for (int j = read; j < recSize; j++) readShort( is );
TextLayout layout = new TextLayout( sr, wf.font, fontCtx );
int lfWidth = (int)layout.getBounds().getWidth();
x = (int)layout.getBounds().getX();
int lfHeight =
(int)getVerticalAlignmentValue(layout, currentVertAlign);
resizeBounds(x, y);
resizeBounds(x+lfWidth, y+lfHeight);
}
break;
case WMFConstants.META_CREATEFONTINDIRECT: {
int lfHeight = readShort( is );
float size = (int)(scaleY * lfHeight);
int lfWidth = readShort( is );
int escape = (int)readShort( is );
int orient = (int)readShort( is );
int weight = (int)readShort( is );
int italic = (int)is.readByte();
int underline = (int)is.readByte();
int strikeOut = (int)is.readByte();
int charset = (int)(is.readByte() & 0x00ff);
int lfOutPrecision = is.readByte();
int lfClipPrecision = is.readByte();
int lfQuality = is.readByte();
int lfPitchAndFamily = is.readByte();
int style = italic > 0 ? Font.ITALIC : Font.PLAIN;
style |= (weight > 400) ? Font.BOLD : Font.PLAIN;
// don't need to read the end of the record,
// because it will always be completely used
int len = (2*(recSize-9));
byte[] lfFaceName = new byte[ len ];
byte ch;
for ( int i = 0; i < len; i++ ) lfFaceName[ i ] = is.readByte();
String face = new String( lfFaceName );
// FIXED : management of font names
int d = 0;
while ((d < face.length()) &&
((Character.isLetterOrDigit(face.charAt(d))) ||
(Character.isWhitespace(face.charAt(d))))) d++;
if (d > 0) face = face.substring(0,d);
else face = "System";
if ( size < 0 ) size = -size /* * -1.3 */;
int objIndex = 0;
Font f = new Font(face, style, (int)size);
f = f.deriveFont(size);
WMFFont wf = new WMFFont(f, charset, underline,
strikeOut, italic, weight, orient, escape);
objIndex = addObjectAt( FONT, wf , objIndex );
}
break;
case WMFConstants.META_CREATEREGION: {
int objIndex = 0;
for ( int j = 0; j < recSize; j++ ) readShort(is); // read all fields
objIndex = addObjectAt( PALETTE, INTEGER_0, 0 );
}
break;
case WMFConstants.META_CREATEPALETTE: {
int objIndex = 0;
for ( int j = 0; j < recSize; j++ ) readShort(is); // read all fields
objIndex = addObjectAt( OBJ_REGION, INTEGER_0, 0 );
}
break;
case WMFConstants.META_SELECTOBJECT:
gdiIndex = readShort(is);
if (( gdiIndex & 0x80000000 ) != 0 ) // Stock Object
break;
gdiObj = getObject( gdiIndex );
if ( !gdiObj.used )
break;
switch( gdiObj.type ) {
case PEN:
penObject = gdiIndex;
break;
case BRUSH:
brushObject = gdiIndex;
break;
case FONT: {
this.wf = ((WMFFont)gdiObj.obj);
fontObject = gdiIndex;
}
break;
case NULL_PEN:
penObject = -1;
break;
case NULL_BRUSH:
brushObject = -1;
break;
}
break;
case WMFConstants.META_DELETEOBJECT:
gdiIndex = readShort(is);
gdiObj = getObject( gdiIndex );
if ( gdiIndex == brushObject ) brushObject = -1;
else if ( gdiIndex == penObject ) penObject = -1;
else if ( gdiIndex == fontObject ) fontObject = -1;
gdiObj.clear();
break;
case WMFConstants.META_LINETO: {
int y = readShort( is );
int x = (int)(readShort( is ) * scaleXY);
if (penObject >= 0) {
resizeBounds(startX, startY);
resizeBounds(x, y);
firstEffectivePaint = false;
}
startX = x;
startY = y;
}
break;
case WMFConstants.META_MOVETO: {
startY = readShort( is );
startX = (int)(readShort( is ) * scaleXY);
}
break;
case WMFConstants.META_POLYPOLYGON: {
int count = readShort( is );
int[] pts = new int[ count ];
int ptCount = 0;
for ( int i = 0; i < count; i++ ) {
pts[ i ] = readShort( is );
ptCount += pts[ i ];
}
int offset = count+1;
for ( int i = 0; i < count; i++ ) {
for ( int j = 0; j < pts[ i ]; j++ ) {
// FIXED 115 : correction preliminary images dimensions
int x = (int)(readShort( is ) * scaleXY);
int y = readShort( is );
if ((brushObject >= 0) || (penObject >= 0)) resizeBounds(x, y);
}
}
firstEffectivePaint = false;
}
break;
case WMFConstants.META_POLYGON: {
int count = readShort( is );
float[] _xpts = new float[ count+1 ];
float[] _ypts = new float[ count+1 ];
for ( int i = 0; i < count; i++ ) {
_xpts[i] = readShort( is ) * scaleXY;
_ypts[i] = readShort( is );
}
_xpts[count] = _xpts[0];
_ypts[count] = _ypts[0];
Polygon2D pol = new Polygon2D(_xpts, _ypts, count);
paint(brushObject, penObject, pol);
}
break;
case WMFConstants.META_POLYLINE:
{
int count = readShort( is );
float[] _xpts = new float[ count ];
float[] _ypts = new float[ count ];
for ( int i = 0; i < count; i++ ) {
_xpts[i] = readShort( is ) * scaleXY;
_ypts[i] = readShort( is );
}
Polyline2D pol = new Polyline2D(_xpts, _ypts, count);
paintWithPen(penObject, pol);
}
break;
case WMFConstants.META_ELLIPSE:
case WMFConstants.META_INTERSECTCLIPRECT:
case WMFConstants.META_RECTANGLE: {
int bot = readShort( is );
int right = (int)(readShort( is ) * scaleXY);
int top = readShort( is );
int left = (int)(readShort( is ) * scaleXY);
Rectangle2D.Float rec = new Rectangle2D.Float(left, top, right-left, bot-top);
paint(brushObject, penObject, rec);
}
break;
case WMFConstants.META_ROUNDRECT: {
readShort( is );
readShort( is );
int bot = readShort( is );
int right = (int)(readShort( is ) * scaleXY);
int top = readShort( is );
int left = (int)(readShort( is ) * scaleXY);
Rectangle2D.Float rec = new Rectangle2D.Float(left, top, right-left, bot-top);
paint(brushObject, penObject, rec);
}
break;
case WMFConstants.META_ARC:
case WMFConstants.META_CHORD:
case WMFConstants.META_PIE: {
readShort( is );
readShort( is );
readShort( is );
readShort( is );
int bot = readShort( is );
int right = (int)(readShort( is ) * scaleXY);
int top = readShort( is );
int left = (int)(readShort( is ) * scaleXY);
Rectangle2D.Float rec = new Rectangle2D.Float(left, top, right-left, bot-top);
paint(brushObject, penObject, rec);
}
break;
case WMFConstants.META_PATBLT : {
readInt( is ); // rop
int height = readShort( is );
int width = (int)(readShort( is ) * scaleXY);
int left = (int)(readShort( is ) * scaleXY);
int top = readShort( is );
if (penObject >= 0) resizeBounds(left, top);
if (penObject >= 0) resizeBounds(left+width, top+height);
}
break;
// UPDATED : META_DIBSTRETCHBLT added
case WMFConstants.META_DIBSTRETCHBLT:
{
is.readInt(); // mode
readShort( is ); // heightSrc
readShort( is ); // widthSrc
readShort( is ); // sy
readShort( is ); // sx
float heightDst = (float)readShort( is );
float widthDst = (float)readShort( is ) * scaleXY;
float dy = (float)readShort( is ) * getVpWFactor() * (float)inch / PIXEL_PER_INCH;
float dx = (float)readShort( is ) * getVpWFactor() * (float)inch / PIXEL_PER_INCH * scaleXY;
widthDst = widthDst * getVpWFactor() * (float)inch / PIXEL_PER_INCH;
heightDst = heightDst * getVpHFactor() * (float)inch / PIXEL_PER_INCH;
resizeImageBounds((int)dx, (int)dy);
resizeImageBounds((int)(dx + widthDst), (int)(dy + heightDst));
int len = 2*recSize - 20;
for (int i = 0; i < len; i++) is.readByte();
}
break;
case WMFConstants.META_STRETCHDIB: {
is.readInt(); // mode
readShort( is ); // usage
readShort( is ); // heightSrc
readShort( is ); // widthSrc
readShort( is ); // sy
readShort( is ); // sx
float heightDst = (float)readShort( is );
float widthDst = (float)readShort( is ) * scaleXY;
float dy = (float)readShort( is ) * getVpHFactor() * (float)inch / PIXEL_PER_INCH;
float dx = (float)readShort( is ) * getVpHFactor() * (float)inch / PIXEL_PER_INCH * scaleXY;
widthDst = widthDst * getVpWFactor() * (float)inch / PIXEL_PER_INCH;
heightDst = heightDst * getVpHFactor() * (float)inch / PIXEL_PER_INCH;
resizeImageBounds((int)dx, (int)dy);
resizeImageBounds((int)(dx + widthDst), (int)(dy + heightDst));
int len = 2*recSize - 22;
byte bitmap[] = new byte[len];
for (int i = 0; i < len; i++) bitmap[i] = is.readByte();
}
break;
case WMFConstants.META_DIBBITBLT: {
is.readInt(); // mode
readShort( is ); //sy
readShort( is ); //sx
readShort( is ); // hdc
float height = readShort( is )
* (float)inch / PIXEL_PER_INCH * getVpHFactor();
float width = readShort( is )
* (float)inch / PIXEL_PER_INCH * getVpWFactor() * scaleXY;
float dy =
(float)inch / PIXEL_PER_INCH * getVpHFactor() * readShort( is );
float dx =
(float)inch / PIXEL_PER_INCH * getVpWFactor() * readShort( is ) * scaleXY;
resizeImageBounds((int)dx, (int)dy);
resizeImageBounds((int)(dx + width), (int)(dy + height));
}
break;
default:
for ( int j = 0; j < recSize; j++ )
readShort(is);
break;
}
}
// sets the width, height, etc of the image if the file does not have an APM (in this case it is retrieved
// from the viewport)
if (! isAldus) {
width = vpW;
height = vpH;
right = vpX;
left = vpX + vpW;
top = vpY;
bottom = vpY + vpH;
}
resetBounds();
return true;
}
/** @return the width of the Rectangle bounding the figures enclosed in
* the Metafile, in pixels
*/
public int getWidthBoundsPixels() {
return _bwidth;
}
/** @return the height of the Rectangle bounding the figures enclosed in
* the Metafile, in pixels.
*/
public int getHeightBoundsPixels() {
return _bheight;
}
/** @return the width of the Rectangle bounding the figures enclosed in
* the Metafile, in Metafile Units.
*/
public int getWidthBoundsUnits() {
return (int)((float)inch * (float)_bwidth / PIXEL_PER_INCH);
}
/** @return the height of the Rectangle bounding the figures enclosed in
* the Metafile in Metafile Units.
*/
public int getHeightBoundsUnits() {
return (int)((float)inch * (float)_bheight / PIXEL_PER_INCH);
}
/** @return the X offset of the Rectangle bounding the figures enclosed in
* the Metafile.
*/
public int getXOffset() {
return _bleft;
}
/** @return the Y offset of the Rectangle bounding the figures enclosed in
* the Metafile.
*/
public int getYOffset() {
return _btop;
}
private void resetBounds() {
// calculate geometry size
scale = (float)getWidthPixels() / (float)vpW ;
if (_bright != -1) {
_bright = (int)(scale * (vpX +_bright));
_bleft = (int)(scale * (vpX +_bleft));
_bbottom = (int)(scale * (vpY +_bbottom));
_btop = (int)(scale * (vpY +_btop));
}
// calculate image size
if (_iright != -1) {
_iright = (int)((float)_iright * (float)getWidthPixels() / (float)width);
_ileft = (int)((float)_ileft * (float)getWidthPixels() / (float)width);
_ibottom = (int)((float)_ibottom * (float)getWidthPixels() / (float)width);
_itop = (int)((float)_itop * (float)getWidthPixels() / (float)width);
// merge image and geometry size
if ((_bright == -1) || (_iright > _bright)) _bright = _iright;
if ((_bleft == -1) || (_ileft < _bleft)) _bleft = _ileft;
if ((_btop == -1) || (_itop < _btop)) _btop = _itop;
if ((_bbottom == -1) || (_ibottom > _bbottom)) _bbottom = _ibottom;
}
if ((_bleft != -1) && (_bright != -1)) _bwidth = _bright - _bleft;
if ((_btop != -1) && (_bbottom != -1)) _bheight = _bbottom - _btop;
}
/** resize Bounds for each primitive encountered. Only elements that are in the overall
* width and height of the Metafile are kept.
*/
private void resizeBounds(int x, int y) {
if (_bleft == -1) _bleft = x;
else if (x < _bleft) _bleft = x;
if (_bright == -1) _bright = x;
else if (x > _bright) _bright = x;
if (_btop == -1) _btop = y;
else if (y < _btop) _btop = y;
if (_bbottom == -1) _bbottom = y;
else if (y > _bbottom) _bbottom = y;
}
/** resize Bounds for each image primitive encountered. Only elements that are in the overall
* width and height of the Metafile are kept.
*/
private void resizeImageBounds(int x, int y) {
if (_ileft == -1) _ileft = x;
else if (x < _ileft) _ileft = x;
if (_iright == -1) _iright = x;
else if (x > _iright) _iright = x;
if (_itop == -1) _itop = y;
else if (y < _itop) _itop = y;
if (_ibottom == -1) _ibottom = y;
else if (y > _ibottom) _ibottom = y;
}
/** get the Color corresponding with the Object (pen or brush object).
*/
private Color getColorFromObject(int brushObject) {
Color color = null;
if ( brushObject >= 0 ) {
GdiObject gdiObj = getObject( brushObject );
return (Color)gdiObj.obj;
} else return null;
}
/** Resize the bounds of the WMF image according with the bounds of the geometric
* Shape.
* There will be no resizing if one of the following properties is true :
* <ul>
* <li>the brush and the pen objects are < 0 (null objects)</li>
* <li>the color of the geometric Shape is white, and no other Shapes has occured</li>
* </ul>
*/
private void paint(int brushObject, int penObject, Shape shape) {
if (( brushObject >= 0 ) || (penObject >= 0)) {
Color col;
if (brushObject >= 0) col = getColorFromObject(brushObject);
else col = getColorFromObject(penObject);
if (!(firstEffectivePaint && (col.equals(Color.white)))) {
Rectangle rec = shape.getBounds();
resizeBounds((int)rec.getMinX(), (int)rec.getMinY());
resizeBounds((int)rec.getMaxX(), (int)rec.getMaxY());
firstEffectivePaint = false;
}
}
}
/** Resize the bounds of the WMF image according with the bounds of the geometric
* Shape.
* There will be no resizing if one of the following properties is true :
* <ul>
* <li>the pen objects is < 0 (null object)</li>
* <li>the color of the geometric Shape is white, and no other Shapes has occured</li>
* </ul>
*/
private void paintWithPen(int penObject, Shape shape) {
if (penObject >= 0) {
Color col = getColorFromObject(penObject);
if (!(firstEffectivePaint && (col.equals(Color.white)))) {
Rectangle rec = shape.getBounds();
resizeBounds((int)rec.getMinX(), (int)rec.getMinY());
resizeBounds((int)rec.getMaxX(), (int)rec.getMaxY());
firstEffectivePaint = false;
}
}
}
/** get the vertical Alignment value for the text.
*/
private float getVerticalAlignmentValue(TextLayout layout, int vertAlign) {
if (vertAlign == WMFConstants.TA_BASELINE) return -layout.getAscent();
else if (vertAlign == WMFConstants.TA_TOP) return (layout.getAscent() + layout.getDescent());
else return 0;
}
}