diff options
author | Glen Stampoultzis <glens@apache.org> | 2004-04-09 11:45:38 +0000 |
---|---|---|
committer | Glen Stampoultzis <glens@apache.org> | 2004-04-09 11:45:38 +0000 |
commit | b6ea214cc1e42332b7198954c63a783935b4bdf4 (patch) | |
tree | 9ffdd5aea352c9c258ed2df5fa1a4deb4d63792e /src | |
parent | 0873a633af2dc09fd9eab9ee1b2d5835e0d7fc2d (diff) | |
download | poi-b6ea214cc1e42332b7198954c63a783935b4bdf4.tar.gz poi-b6ea214cc1e42332b7198954c63a783935b4bdf4.zip |
Ported the drawing stuff from the rel_2_branch. Given the effort this took I'm really really wanting to move to subversion.
git-svn-id: https://svn.apache.org/repos/asf/jakarta/poi/trunk@353542 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'src')
122 files changed, 18684 insertions, 109 deletions
diff --git a/src/contrib/src/org/apache/poi/contrib/metrics/FontMetricsDumper.java b/src/contrib/src/org/apache/poi/contrib/metrics/FontMetricsDumper.java new file mode 100644 index 0000000000..2a02ae9fbf --- /dev/null +++ b/src/contrib/src/org/apache/poi/contrib/metrics/FontMetricsDumper.java @@ -0,0 +1,65 @@ +package org.apache.poi.contrib.metrics; + +import java.awt.*; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Properties; + +public class FontMetricsDumper +{ + public static void main( String[] args ) throws IOException + { + + Properties props = new Properties(); + + Font[] allFonts = GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts(); + for ( int i = 0; i < allFonts.length; i++ ) + { + String fontName = allFonts[i].getFontName(); + + Font font = new Font(fontName, Font.BOLD, 10); + FontMetrics fontMetrics = Toolkit.getDefaultToolkit().getFontMetrics(font); + int fontHeight = fontMetrics.getHeight(); + + props.setProperty("font." + fontName + ".height", fontHeight+""); + StringBuffer characters = new StringBuffer(); + for (char c = 'a'; c <= 'z'; c++) + { + characters.append( c + ", " ); + } + for (char c = 'A'; c <= 'Z'; c++) + { + characters.append( c + ", " ); + } + for (char c = '0'; c <= '9'; c++) + { + characters.append( c + ", " ); + } + StringBuffer widths = new StringBuffer(); + for (char c = 'a'; c <= 'z'; c++) + { + widths.append( fontMetrics.getWidths()[c] + ", " ); + } + for (char c = 'A'; c <= 'Z'; c++) + { + widths.append( fontMetrics.getWidths()[c] + ", " ); + } + for (char c = '0'; c <= '9'; c++) + { + widths.append( fontMetrics.getWidths()[c] + ", " ); + } + props.setProperty("font." + fontName + ".characters", characters.toString()); + props.setProperty("font." + fontName + ".widths", widths.toString()); + } + + FileOutputStream fileOut = new FileOutputStream("font_metrics.properties"); + try + { + props.store(fileOut, "Font Metrics"); + } + finally + { + fileOut.close(); + } + } +} diff --git a/src/documentation/content/xdocs/hssf/quick-guide.xml b/src/documentation/content/xdocs/hssf/quick-guide.xml index 8940bc5fe1..0ef0ccb629 100644 --- a/src/documentation/content/xdocs/hssf/quick-guide.xml +++ b/src/documentation/content/xdocs/hssf/quick-guide.xml @@ -40,6 +40,9 @@ <li><link href="#Splits">Create split and freeze panes.</link></li> <li><link href="#Repeating">Repeating rows and columns.</link></li> <li><link href="#HeaderFooter">Headers and Footers.</link></li> + <li><link href="#DrawingShapes">Drawing Shapes.</link></li> + <li><link href="#StylingShapes">Styling Shapes.</link></li> + <li><link href="#Graphics2d">Shapes and Graphics2d.</link></li> </ul> </section> <section><title>Features</title> @@ -692,6 +695,212 @@ fileOut.close(); </source> </section> + + <anchor id="DrawingShapes"/> + <section><title>Drawing Shapes</title> + <p> + POI supports drawing shapes using the Microsoft Office + drawing tools. Shapes on a sheet are organized in a + hiearchy of groups and and shapes. The top-most shape + is the patriarch. This is not visisble on the sheet + at all. To start drawing you need to call <code>createPatriarch</code> + on the <code>HSSFSheet</code> class. This has the + effect erasing any other shape information stored + in that sheet. By default POI will leave shape + records alone in the sheet unless you make a call to + this method. + </p> + <p> + To create a shape you have to go through the following + steps: + </p> + <ol> + <li>Create the patriarch.</li> + <li>Create an anchor to position the shape on the sheet.</li> + <li>Ask the patriarch to create the shape.</li> + <li>Set the shape type (line, oval, rectangle etc...)</li> + <li>Set any other style details converning the shape. (eg: + line thickness, etc...)</li> + </ol> + <source> + HSSFPatriarch patriarch = sheet.createDrawingPatriarch(); + a = new HSSFClientAnchor( 0, 0, 1023, 255, (short) 1, 0, (short) 1, 0 ); + HSSFSimpleShape shape1 = patriarch.createSimpleShape(a1); + shape1.setShapeType(HSSFSimpleShape.OBJECT_TYPE_LINE); + </source> + <p> + Text boxes are created using a different call: + </p> + <source> + HSSFTextbox textbox1 = patriarch.createTextbox( + new HSSFClientAnchor(0,0,0,0,(short)1,1,(short)2,2)); + textbox1.setString(new HSSFRichTextString("This is a test") ); + </source> + <p> + It's possible to use different fonts to style parts of + the text in the textbox. Here's how: + </p> + <source> + HSSFFont font = wb.createFont(); + font.setItalic(true); + font.setUnderline(HSSFFont.U_DOUBLE); + HSSFRichTextString string = new HSSFRichTextString("Woo!!!"); + string.applyFont(2,5,font); + textbox.setString(string ); + </source> + <p> + Just as can be done manually using Excel, it is possible + to group shapes together. This is done by calling + <code>createGroup()</code> and then creating the shapes + using those groups. + </p> + <p> + It's also possible to create groups within groups. + </p> + <warning>Any group you create should contain at least two + other shapes or subgroups.</warning> + <p> + Here's how to create a shape group: + </p> + <source> + // Create a shape group. + HSSFShapeGroup group = patriarch.createGroup( + new HSSFClientAnchor(0,0,900,200,(short)2,2,(short)2,2)); + + // Create a couple of lines in the group. + HSSFSimpleShape shape1 = group.createShape(new HSSFChildAnchor(3,3,500,500)); + shape1.setShapeType(HSSFSimpleShape.OBJECT_TYPE_LINE); + ( (HSSFChildAnchor) shape1.getAnchor() ).setAnchor((short)3,3,500,500); + HSSFSimpleShape shape2 = group.createShape(new HSSFChildAnchor((short)1,200,400,600)); + shape2.setShapeType(HSSFSimpleShape.OBJECT_TYPE_LINE); + </source> + <p> + If you're being observant you'll noticed that the shapes + that are added to the group use a new type of anchor: + the <code>HSSFChildAnchor</code>. What happens is that + the created group has it's own coordinate space for + shapes that are placed into it. POI defaults this to + (0,0,1023,255) but you are able to change it as desired. + Here's how: + </p> + <source> + myGroup.setCoordinates(10,10,20,20); // top-left, bottom-right + </source> + <p> + If you create a group within a group it's also going + to have it's own coordinate space. + </p> + </section> + + <anchor id="StylingShapes"/> + <section><title>Styling Shapes</title> + <p> + By default shapes can look a little plain. It's possible + to apply different styles to the shapes however. The + sorts of things that can currently be done are: + </p> + <ul> + <li>Change the fill color.</li> + <li>Make a shape with no fill color.</li> + <li>Change the thickness of the lines.</li> + <li>Change the style of the lines. Eg: dashed, dotted.</li> + <li>Change the line color.</li> + </ul> + <p> + Here's an examples of how this is done: + </p> + <source> + HSSFSimpleShape s = patriarch.createSimpleShape(a); + s.setShapeType(HSSFSimpleShape.OBJECT_TYPE_OVAL); + s.setLineStyleColor(10,10,10); + s.setFillColor(90,10,200); + s.setLineWidth(HSSFShape.LINEWIDTH_ONE_PT * 3); + s.setLineStyle(HSSFShape.LINESTYLE_DOTSYS); + </source> + </section> + <anchor id="Graphics2d"/> + <section><title>Shapes and Graphics2d</title> + <p> + While the native POI shape drawing commands are the + recommended way to draw shapes in a shape it's sometimes + desirable to use a standard API for compatibility with + external libraries. With this in mind we created some + wrappers for <code>Graphics</code> and <code>Graphics2d</code>. + </p> + <warning> + It's important to not however before continuing that + <code>Graphics2d</code> is a poor match to the capabilities + of the Microsoft Office drawing commands. The older + <code>Graphics</code> class offers a closer match but is + still a square peg in a round hole. + </warning> + <p> + All Graphics commands are issued into an <code>HSSFShapeGroup</code>. + Here's how it's done: + </p> + <source> + a = new HSSFClientAnchor( 0, 0, 1023, 255, (short) 1, 0, (short) 1, 0 ); + group = patriarch.createGroup( a ); + group.setCoordinates( 0, 0, 80 * 4 , 12 * 23 ); + float verticalPointsPerPixel = a.getAnchorHeightInPoints(sheet) / (float)Math.abs(group.getY2() - group.getY1()); + g = new EscherGraphics( group, wb, Color.black, verticalPointsPerPixel ); + g2d = new EscherGraphics2d( g ); + drawChemicalStructure( g2d ); + </source> + <p> + The first thing we do is create the group and set it's coordinates + to match what we plan to draw. Next we calculate a reasonable + fontSizeMultipler then create the EscherGraphics object. + Since what we really want is a <code>Graphics2d</code> + object we create an EscherGraphics2d object and pass in + the graphics object we created. Finally we call a routine + that draws into the EscherGraphics2d object. + </p> + <p> + The vertical points per pixel deserves some more explanation. + One of the difficulties in converting Graphics calls + into escher drawing calls is that Excel does not have + the concept of absolute pixel positions. It measures + it's cell widths in 'characters' and the cell heights in points. + Unfortunately it's not defined exactly what type of character it's + measuring. Presumably this is due to the fact that the Excel will be + using different fonts on different platforms or even within the same + platform. + </p> + <p> + Because of this constraint we've had to implement the concept of a + verticalPointsPerPixel. This the amount the font should be scaled by when + you issue commands such as drawString(). To calculate this value + use the follow formula: + </p> + <source> + multipler = groupHeightInPoints / heightOfGroup + </source> + <p> + The height of the group is calculated fairly simply by calculating the + difference between the y coordinates of the bounding box of the shape. The + height of the group can be calculated by using a convenience called + <code>HSSFClientAnchor.getAnchorHeightInPoints()</code>. + </p> + <p> + Many of the functions supported by the graphics classes + are not complete. Here's some of the functions that are known + to work. + </p> + <ul> + <li>fillRect()</li> + <li>fillOval()</li> + <li>drawString()</li> + <li>drawOval()</li> + <li>drawLine()</li> + <li>clearRect()</li> + </ul> + <p> + Functions that are not supported will return and log a message + using the POI logging infrastructure (disabled by default). + </p> + </section> + </section> </section> </body> diff --git a/src/examples/src/org/apache/poi/hssf/usermodel/examples/OfficeDrawing.java b/src/examples/src/org/apache/poi/hssf/usermodel/examples/OfficeDrawing.java new file mode 100644 index 0000000000..255fed7cb2 --- /dev/null +++ b/src/examples/src/org/apache/poi/hssf/usermodel/examples/OfficeDrawing.java @@ -0,0 +1,255 @@ +package org.apache.poi.hssf.usermodel.examples; + +import org.apache.poi.hssf.usermodel.*; + +import java.io.IOException; +import java.io.FileOutputStream; +import java.io.FileNotFoundException; + +/** + * Demonstrates how to use the office drawing capabilities of POI. + * + * @author Glen Stampoultzis (glens at apache.org) + */ +public class OfficeDrawing +{ + public static void main(String[] args) + throws IOException + { + // Create the workbook and sheets. + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sheet1 = wb.createSheet("new sheet"); + HSSFSheet sheet2 = wb.createSheet("second sheet"); + HSSFSheet sheet3 = wb.createSheet("third sheet"); + HSSFSheet sheet4 = wb.createSheet("fourth sheet"); + + // Draw stuff in them + drawSheet1( sheet1 ); + drawSheet2( sheet2 ); + drawSheet3( sheet3 ); + drawSheet4( sheet4, wb ); + + // Write the file out. + FileOutputStream fileOut = new FileOutputStream("workbook.xls"); + wb.write(fileOut); + fileOut.close(); + } + + private static void drawSheet1( HSSFSheet sheet1 ) + { + // Create a row and size one of the cells reasonably large. + HSSFRow row = sheet1.createRow(2); + row.setHeight((short) 2800); + sheet1.setColumnWidth((short) 2, (short) 9000); + + // Create the drawing patriarch. This is the top level container for + // all shapes. + HSSFPatriarch patriarch = sheet1.createDrawingPatriarch(); + + // Draw some lines and an oval. + drawLinesToCenter( patriarch ); + drawManyLines( patriarch ); + drawOval( patriarch ); + drawPolygon( patriarch ); + + // Draw a rectangle. + HSSFSimpleShape rect = patriarch.createSimpleShape( new HSSFClientAnchor(100, 100, 900, 200, (short)0, 0, (short)0, 0) ); + rect.setShapeType(HSSFSimpleShape.OBJECT_TYPE_RECTANGLE); + } + + private static void drawSheet2( HSSFSheet sheet2 ) + { + // Create a row and size one of the cells reasonably large. + HSSFRow row = sheet2.createRow(2); + row.setHeightInPoints(240); + sheet2.setColumnWidth((short) 2, (short) 9000); + + // Create the drawing patriarch. This is the top level container for + // all shapes. This will clear out any existing shapes for that sheet. + HSSFPatriarch patriarch = sheet2.createDrawingPatriarch(); + + // Draw a grid in one of the cells. + drawGrid( patriarch ); + } + + private static void drawSheet3( HSSFSheet sheet3 ) + { + // Create a row and size one of the cells reasonably large + HSSFRow row = sheet3.createRow(2); + row.setHeightInPoints(140); + sheet3.setColumnWidth((short) 2, (short) 9000); + + // Create the drawing patriarch. This is the top level container for + // all shapes. This will clear out any existing shapes for that sheet. + HSSFPatriarch patriarch = sheet3.createDrawingPatriarch(); + + // Create a shape group. + HSSFShapeGroup group = patriarch.createGroup( + new HSSFClientAnchor(0,0,900,200,(short)2,2,(short)2,2)); + + // Create a couple of lines in the group. + HSSFSimpleShape shape1 = group.createShape(new HSSFChildAnchor(3,3,500,500)); + shape1.setShapeType(HSSFSimpleShape.OBJECT_TYPE_LINE); + ( (HSSFChildAnchor) shape1.getAnchor() ).setAnchor((short)3,3,500,500); + HSSFSimpleShape shape2 = group.createShape(new HSSFChildAnchor((short)1,200,400,600)); + shape2.setShapeType(HSSFSimpleShape.OBJECT_TYPE_LINE); + + } + + private static void drawSheet4( HSSFSheet sheet4, HSSFWorkbook wb ) + { + // Create the drawing patriarch. This is the top level container for + // all shapes. This will clear out any existing shapes for that sheet. + HSSFPatriarch patriarch = sheet4.createDrawingPatriarch(); + + // Create a couple of textboxes + HSSFTextbox textbox1 = patriarch.createTextbox( + new HSSFClientAnchor(0,0,0,0,(short)1,1,(short)2,2)); + textbox1.setString(new HSSFRichTextString("This is a test") ); + HSSFTextbox textbox2 = patriarch.createTextbox( + new HSSFClientAnchor(0,0,900,100,(short)3,3,(short)3,4)); + textbox2.setString(new HSSFRichTextString("Woo") ); + textbox2.setFillColor(200,0,0); + textbox2.setLineStyle(HSSFSimpleShape.LINESTYLE_DOTGEL); + + // Create third one with some fancy font styling. + HSSFTextbox textbox3 = patriarch.createTextbox( + new HSSFClientAnchor(0,0,900,100,(short)4,4,(short)5,4+1)); + HSSFFont font = wb.createFont(); + font.setItalic(true); + font.setUnderline(HSSFFont.U_DOUBLE); + HSSFRichTextString string = new HSSFRichTextString("Woo!!!"); + string.applyFont(2,5,font); + textbox3.setString(string ); + textbox3.setFillColor(0x08000030); + textbox3.setLineStyle(HSSFSimpleShape.LINESTYLE_NONE); // no line around the textbox. + textbox3.setNoFill(true); // make it transparent + } + + private static void drawOval( HSSFPatriarch patriarch ) + { + // Create an oval and style to taste. + HSSFClientAnchor a = new HSSFClientAnchor(); + a.setAnchor((short)2, 2, 20, 20, (short) 2, 2, 190, 80); + HSSFSimpleShape s = patriarch.createSimpleShape(a); + s.setShapeType(HSSFSimpleShape.OBJECT_TYPE_OVAL); + s.setLineStyleColor(10,10,10); + s.setFillColor(90,10,200); + s.setLineWidth(HSSFShape.LINEWIDTH_ONE_PT * 3); + s.setLineStyle(HSSFShape.LINESTYLE_DOTSYS); + } + + private static void drawPolygon( HSSFPatriarch patriarch ) + { + // HSSFClientAnchor a = new HSSFClientAnchor( 0, 0, 1023, 255, (short) 2, 2, (short) 3, 3 ); + // HSSFPolygon p = patriarch.createPolygon(a); + // p.setPolygonDrawArea(100,100); + // p.setPoints( new int[]{30, 90, 50}, new int[]{88, 5, 44} ); + + + HSSFClientAnchor a = new HSSFClientAnchor(); + a.setAnchor( (short) 2, 2, 0, 0, (short) 3, 3, 1023, 255 ); + HSSFShapeGroup g = patriarch.createGroup( a ); + g.setCoordinates(0,0,200,200); + HSSFPolygon p1 = g.createPolygon( new HSSFChildAnchor( 0, 0, 200, 200 ) ); + p1.setPolygonDrawArea( 100, 100 ); + p1.setPoints( new int[]{0, 90, 50}, new int[]{5, 5, 44} ); + p1.setFillColor( 0, 255, 0 ); + HSSFPolygon p2 = g.createPolygon( new HSSFChildAnchor( 20, 20, 200, 200 ) ); + p2.setPolygonDrawArea( 200, 200 ); + p2.setPoints( new int[]{120, 20, 150}, new int[]{105, 30, 195} ); + p2.setFillColor( 255, 0, 0 ); + } + + private static void drawManyLines( HSSFPatriarch patriarch ) + { + // Draw bunch of lines + int x1 = 100; + int y1 = 100; + int x2 = 800; + int y2 = 200; + int color = 0; + for (int i = 0; i < 10; i++) + { + HSSFClientAnchor a2 = new HSSFClientAnchor(); + a2.setAnchor((short) 2, 2, x1, y1, (short) 2, 2, x2, y2); + HSSFSimpleShape shape2 = patriarch.createSimpleShape(a2); + shape2.setShapeType(HSSFSimpleShape.OBJECT_TYPE_LINE); + shape2.setLineStyleColor(color); + y1 -= 10; + y2 -= 10; + color += 30; + } + } + + private static void drawGrid( HSSFPatriarch patriarch ) + { + // This draws a grid of lines. Since the coordinates space fixed at + // 1024 by 256 we use a ratio to get a reasonably square grids. + + double xRatio = 3.22; + double yRatio = 0.6711; + + int x1 = 000; + int y1 = 000; + int x2 = 000; + int y2 = 200; + for (int i = 0; i < 20; i++) + { + HSSFClientAnchor a2 = new HSSFClientAnchor(); + a2.setAnchor((short) 2, 2, (int) ( x1 * xRatio ), (int) ( y1 * yRatio ), + (short) 2, 2, (int) ( x2 * xRatio ), (int) ( y2 * yRatio )); + HSSFSimpleShape shape2 = patriarch.createSimpleShape(a2); + shape2.setShapeType(HSSFSimpleShape.OBJECT_TYPE_LINE); + + x1 += 10; + x2 += 10; + } + + x1 = 000; + y1 = 000; + x2 = 200; + y2 = 000; + for (int i = 0; i < 20; i++) + { + HSSFClientAnchor a2 = new HSSFClientAnchor(); + a2.setAnchor((short) 2, 2, (int) ( x1 * xRatio ), (int) ( y1 * yRatio ), + (short) 2, 2, (int) ( x2 * xRatio ), (int) ( y2 * yRatio )); + HSSFSimpleShape shape2 = patriarch.createSimpleShape(a2); + shape2.setShapeType(HSSFSimpleShape.OBJECT_TYPE_LINE); + + y1 += 10; + y2 += 10; + } + } + + private static void drawLinesToCenter( HSSFPatriarch patriarch ) + { + // Draw some lines from and to the corners + { + HSSFClientAnchor a1 = new HSSFClientAnchor(); + a1.setAnchor( (short)2, 2, 0, 0, (short) 2, 2, 512, 128); + HSSFSimpleShape shape1 = patriarch.createSimpleShape(a1); + shape1.setShapeType(HSSFSimpleShape.OBJECT_TYPE_LINE); + } + { + HSSFClientAnchor a1 = new HSSFClientAnchor(); + a1.setAnchor( (short)2, 2, 512, 128, (short) 2, 2, 1024, 0); + HSSFSimpleShape shape1 = patriarch.createSimpleShape(a1); + shape1.setShapeType(HSSFSimpleShape.OBJECT_TYPE_LINE); + } + { + HSSFClientAnchor a1 = new HSSFClientAnchor(); + a1.setAnchor( (short)1, 1, 0, 0, (short) 1, 1, 512, 100); + HSSFSimpleShape shape1 = patriarch.createSimpleShape(a1); + shape1.setShapeType(HSSFSimpleShape.OBJECT_TYPE_LINE); + } + { + HSSFClientAnchor a1 = new HSSFClientAnchor(); + a1.setAnchor( (short)1, 1, 512, 100, (short) 1, 1, 1024, 0); + HSSFSimpleShape shape1 = patriarch.createSimpleShape(a1); + shape1.setShapeType(HSSFSimpleShape.OBJECT_TYPE_LINE); + } + + } +} diff --git a/src/examples/src/org/apache/poi/hssf/usermodel/examples/OfficeDrawingWithGraphics.java b/src/examples/src/org/apache/poi/hssf/usermodel/examples/OfficeDrawingWithGraphics.java new file mode 100644 index 0000000000..0c54b0f19e --- /dev/null +++ b/src/examples/src/org/apache/poi/hssf/usermodel/examples/OfficeDrawingWithGraphics.java @@ -0,0 +1,87 @@ +package org.apache.poi.hssf.usermodel.examples; + +import org.apache.poi.hssf.usermodel.*; + +import java.awt.*; +import java.io.FileOutputStream; +import java.io.IOException; + +/** + * Demonstrates the use of the EscherGraphics2d library. + * + * @author Glen Stampoultzis (glens at apache.org) + */ +public class OfficeDrawingWithGraphics +{ + public static void main( String[] args ) throws IOException + { + // Create a workbook with one sheet and size the first three somewhat + // larger so we can fit the chemical structure diagram in. + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sheet = wb.createSheet( "my drawing" ); + sheet.setColumnWidth((short)1, (short)(256 * 27)); + HSSFRow row1 = sheet.createRow(0); + row1.setHeightInPoints(10 * 15); + HSSFRow row2 = sheet.createRow(1); + row2.setHeightInPoints(5 * 15); + HSSFRow row3 = sheet.createRow(2); + row3.setHeightInPoints(10 * 15); + + // Add some cells so we can test that the anchoring works when we + // sort them. + row1.createCell((short)0).setCellValue("C"); + row2.createCell((short)0).setCellValue("A"); + row3.createCell((short)0).setCellValue("B"); + + // Create the top level drawing patriarch. + HSSFPatriarch patriarch = sheet.createDrawingPatriarch(); + + HSSFClientAnchor a; + HSSFShapeGroup group; + EscherGraphics g; + EscherGraphics2d g2d; + // Anchor entirely within one cell. + a = new HSSFClientAnchor( 0, 0, 1023, 255, (short) 1, 0, (short) 1, 0 ); + group = patriarch.createGroup( a ); + group.setCoordinates( 0, 0, 320, 276 ); + float verticalPointsPerPixel = a.getAnchorHeightInPoints(sheet) / (float)Math.abs(group.getY2() - group.getY1()); + g = new EscherGraphics( group, wb, Color.black, verticalPointsPerPixel ); + g2d = new EscherGraphics2d( g ); + drawStar( g2d ); + + a = new HSSFClientAnchor( 0, 0, 1023, 255, (short) 1, 1, (short) 1, 1 ); + group = patriarch.createGroup( a ); + group.setCoordinates( 0, 0, 640, 276 ); + verticalPointsPerPixel = a.getAnchorHeightInPoints(sheet) / (float)Math.abs(group.getY2() - group.getY1()); +// verticalPixelsPerPoint = (float)Math.abs(group.getY2() - group.getY1()) / a.getAnchorHeightInPoints(sheet); + g = new EscherGraphics( group, wb, Color.black, verticalPointsPerPixel ); + g2d = new EscherGraphics2d( g ); + drawStar( g2d ); + + FileOutputStream out = new FileOutputStream("workbook.xls"); + wb.write(out); + out.close(); + + } + + private static void drawStar( EscherGraphics2d g2d ) + { + g2d.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON ); + for (double i = 0; i < Math.PI; i += 0.1) + { + g2d.setColor( new Color((int)(i * 5343062d) ) ); + int x1 = (int) ( Math.cos(i) * 160.0 ) + 160; + int y1 = (int) ( Math.sin(i) * 138.0 ) + 138; + int x2 = (int) ( -Math.cos(i) * 160.0 ) + 160; + int y2 = (int) ( -Math.sin(i) * 138.0 ) + 138; + g2d.drawLine(x1,y1,x2,y2); + } + g2d.setFont(new Font("SansSerif",Font.BOLD | Font.ITALIC, 20)); + g2d.drawString("EscherGraphics2d",70,100); + g2d.setColor(Color.yellow); + g2d.fillOval( 160-20,138-20,40,40); + g2d.setColor(Color.black); + g2d.fillPolygon(new int[] {-10+160,0+160,10+160,0+160}, new int[] {0+138,10+138,0+138,-10+138}, 4); + g2d.drawPolygon(new int[] {-160+160,0+160,160+160,0+160}, new int[] {0+138,138+138,0+138,-138+138}, 4); + } +} diff --git a/src/java/org/apache/poi/ddf/DefaultEscherRecordFactory.java b/src/java/org/apache/poi/ddf/DefaultEscherRecordFactory.java new file mode 100644 index 0000000000..09d8c5f816 --- /dev/null +++ b/src/java/org/apache/poi/ddf/DefaultEscherRecordFactory.java @@ -0,0 +1,114 @@ +package org.apache.poi.ddf; + +import org.apache.poi.hssf.record.RecordFormatException; + +import java.lang.reflect.Constructor; +import java.util.HashMap; +import java.util.Map; + +/** + * Generates escher records when provided the byte array containing those records. + * + * @author Glen Stampoultzis + * @see EscherRecordFactory + */ +public class DefaultEscherRecordFactory + implements EscherRecordFactory +{ + private static Class[] escherRecordClasses = { + EscherBSERecord.class, EscherOptRecord.class, EscherClientAnchorRecord.class, EscherDgRecord.class, + EscherSpgrRecord.class, EscherSpRecord.class, EscherClientDataRecord.class, EscherDggRecord.class, + EscherSplitMenuColorsRecord.class, EscherChildAnchorRecord.class, EscherTextboxRecord.class + }; + private static Map recordsMap = recordsToMap( escherRecordClasses ); + + /** + * Creates an instance of the escher record factory + */ + public DefaultEscherRecordFactory() + { + } + + /** + * Generates an escher record including the any children contained under that record. + * An exception is thrown if the record could not be generated. + * + * @param data The byte array containing the records + * @param offset The starting offset into the byte array + * @return The generated escher record + */ + public EscherRecord createRecord( byte[] data, int offset ) + { + EscherRecord.EscherRecordHeader header = EscherRecord.EscherRecordHeader.readHeader( data, offset ); + if ( ( header.getOptions() & (short) 0x000F ) == (short) 0x000F ) + { + EscherContainerRecord r = new EscherContainerRecord(); + r.setRecordId( header.getRecordId() ); + r.setOptions( header.getOptions() ); + return r; + } + else if ( header.getRecordId() >= EscherBlipRecord.RECORD_ID_START && header.getRecordId() <= EscherBlipRecord.RECORD_ID_END ) + { + EscherBlipRecord r = new EscherBlipRecord(); + r.setRecordId( header.getRecordId() ); + r.setOptions( header.getOptions() ); + return r; + } + else + { + Constructor recordConstructor = (Constructor) recordsMap.get( new Short( header.getRecordId() ) ); + EscherRecord escherRecord = null; + if ( recordConstructor != null ) + { + try + { + escherRecord = (EscherRecord) recordConstructor.newInstance( new Object[]{} ); + escherRecord.setRecordId( header.getRecordId() ); + escherRecord.setOptions( header.getOptions() ); + } + catch ( Exception e ) + { + escherRecord = null; + } + } + return escherRecord == null ? new UnknownEscherRecord() : escherRecord; + } + } + + /** + * Converts from a list of classes into a map that contains the record id as the key and + * the Constructor in the value part of the map. It does this by using reflection to look up + * the RECORD_ID field then using reflection again to find a reference to the constructor. + * + * @param records The records to convert + * @return The map containing the id/constructor pairs. + */ + private static Map recordsToMap( Class[] records ) + { + Map result = new HashMap(); + Constructor constructor; + + for ( int i = 0; i < records.length; i++ ) + { + Class record = null; + short sid = 0; + + record = records[i]; + try + { + sid = record.getField( "RECORD_ID" ).getShort( null ); + constructor = record.getConstructor( new Class[] + { + } ); + } + catch ( Exception illegalArgumentException ) + { + throw new RecordFormatException( + "Unable to determine record types" ); + } + result.put( new Short( sid ), constructor ); + } + return result; + } + +} diff --git a/src/java/org/apache/poi/ddf/EscherArrayProperty.java b/src/java/org/apache/poi/ddf/EscherArrayProperty.java new file mode 100644 index 0000000000..8979b30a29 --- /dev/null +++ b/src/java/org/apache/poi/ddf/EscherArrayProperty.java @@ -0,0 +1,162 @@ +package org.apache.poi.ddf; + +import org.apache.poi.util.LittleEndian; +import org.apache.poi.util.HexDump; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +/** + * Escher array properties are the most wierd construction ever invented + * with all sorts of special cases. I'm hopeful I've got them all. + * + * @author Glen Stampoultzis (glens at superlinksoftware.com) + */ +public class EscherArrayProperty + extends EscherComplexProperty +{ + private static final int FIXED_SIZE = 3 * 2; + + public EscherArrayProperty( short id, byte[] complexData ) + { + super( id, checkComplexData(complexData) ); + } + + public EscherArrayProperty( short propertyNumber, boolean isBlipId, byte[] complexData ) + { + super( propertyNumber, isBlipId, checkComplexData(complexData) ); + } + + private static byte[] checkComplexData( byte[] complexData ) + { + if (complexData == null || complexData.length == 0) + complexData = new byte[6]; + + return complexData; + } + + public int getNumberOfElementsInArray() + { + return LittleEndian.getUShort( complexData, 0 ); + } + + public void setNumberOfElementsInArray( int numberOfElements ) + { + int expectedArraySize = numberOfElements * getActualSizeOfElements(getSizeOfElements()) + FIXED_SIZE; + if ( expectedArraySize != complexData.length ) + { + byte[] newArray = new byte[expectedArraySize]; + System.arraycopy( complexData, 0, newArray, 0, complexData.length ); + complexData = newArray; + } + LittleEndian.putShort( complexData, 0, (short) numberOfElements ); + } + + public int getNumberOfElementsInMemory() + { + return LittleEndian.getUShort( complexData, 2 ); + } + + public void setNumberOfElementsInMemory( int numberOfElements ) + { + int expectedArraySize = numberOfElements * getActualSizeOfElements(getSizeOfElements()) + FIXED_SIZE; + if ( expectedArraySize != complexData.length ) + { + byte[] newArray = new byte[expectedArraySize]; + System.arraycopy( complexData, 0, newArray, 0, expectedArraySize ); + complexData = newArray; + } + LittleEndian.putShort( complexData, 2, (short) numberOfElements ); + } + + public short getSizeOfElements() + { + return LittleEndian.getShort( complexData, 4 ); + } + + public void setSizeOfElements( int sizeOfElements ) + { + LittleEndian.putShort( complexData, 4, (short) sizeOfElements ); + + int expectedArraySize = getNumberOfElementsInArray() * getActualSizeOfElements(getSizeOfElements()) + FIXED_SIZE; + if ( expectedArraySize != complexData.length ) + { + // Keep just the first 6 bytes. The rest is no good to us anyway. + byte[] newArray = new byte[expectedArraySize]; + System.arraycopy( complexData, 0, newArray, 0, 6 ); + complexData = newArray; + } + } + + public byte[] getElement( int index ) + { + int actualSize = getActualSizeOfElements(getSizeOfElements()); + byte[] result = new byte[actualSize]; + System.arraycopy(complexData, FIXED_SIZE + index * actualSize, result, 0, result.length ); + return result; + } + + public void setElement( int index, byte[] element ) + { + int actualSize = getActualSizeOfElements(getSizeOfElements()); + System.arraycopy( element, 0, complexData, FIXED_SIZE + index * actualSize, actualSize); + } + + public String toString() + { + String nl = System.getProperty("line.separator"); + + StringBuffer results = new StringBuffer(); + results.append(" {EscherArrayProperty:" + nl); + results.append(" Num Elements: " + getNumberOfElementsInArray() + nl); + results.append(" Num Elements In Memory: " + getNumberOfElementsInMemory() + nl); + results.append(" Size of elements: " + getSizeOfElements() + nl); + for (int i = 0; i < getNumberOfElementsInArray(); i++) + { + results.append(" Element " + i + ": " + HexDump.toHex(getElement(i)) + nl); + } + results.append("}" + nl); + + return "propNum: " + getPropertyNumber() + + ", propName: " + EscherProperties.getPropertyName( getPropertyNumber() ) + + ", complex: " + isComplex() + + ", blipId: " + isBlipId() + + ", data: " + nl + results.toString(); + } + + /** + * We have this method because the way in which arrays in escher works + * is screwed for seemly arbitary reasons. While most properties are + * fairly consistent and have a predictable array size, escher arrays + * have special cases. + * + * @param data The data array containing the escher array information + * @param offset The offset into the array to start reading from. + * @return the number of bytes used by this complex property. + */ + public int setArrayData( byte[] data, int offset ) + { + short numElements = LittleEndian.getShort(data, offset); + short numReserved = LittleEndian.getShort(data, offset + 2); + short sizeOfElements = LittleEndian.getShort(data, offset + 4); + + int arraySize = getActualSizeOfElements(sizeOfElements) * numElements; + if (arraySize == complexData.length) + complexData = new byte[arraySize + 6]; // Calculation missing the header for some reason + System.arraycopy(data, offset, complexData, 0, complexData.length ); + return complexData.length; + } + + /** + * Sometimes the element size is stored as a negative number. We + * negate it and shift it to get the real value. + */ + public static int getActualSizeOfElements(short sizeOfElements) + { + if (sizeOfElements < 0) + return (short) ( ( -sizeOfElements ) >> 2 ); + else + return sizeOfElements; + } + +} diff --git a/src/java/org/apache/poi/ddf/EscherBSERecord.java b/src/java/org/apache/poi/ddf/EscherBSERecord.java new file mode 100644 index 0000000000..2d170ab070 --- /dev/null +++ b/src/java/org/apache/poi/ddf/EscherBSERecord.java @@ -0,0 +1,383 @@ +package org.apache.poi.ddf; + +import org.apache.poi.util.LittleEndian; +import org.apache.poi.util.HexDump; + +import java.io.ByteArrayOutputStream; +import java.io.PrintWriter; + +/** + * The BSE record is related closely to the <code>EscherBlipRecord</code> and stores + * extra information about the blip. + * + * @author Glen Stampoultzis + * @see EscherBlipRecord + */ +public class EscherBSERecord + extends EscherRecord +{ + public static final short RECORD_ID = (short) 0xF007; + public static final String RECORD_DESCRIPTION = "MsofbtBSE"; + + public static final byte BT_ERROR = 0; + public static final byte BT_UNKNOWN = 1; + public static final byte BT_EMF = 2; + public static final byte BT_WMF = 3; + public static final byte BT_PICT = 4; + public static final byte BT_JPEG = 5; + public static final byte BT_PNG = 6; + public static final byte BT_DIB = 7; + + private byte field_1_blipTypeWin32; + private byte field_2_blipTypeMacOS; + private byte[] field_3_uid; // 16 bytes + private short field_4_tag; + private int field_5_size; + private int field_6_ref; + private int field_7_offset; + private byte field_8_usage; + private byte field_9_name; + private byte field_10_unused2; + private byte field_11_unused3; + + private byte[] remainingData; + + /** + * This method deserializes the record from a byte array. + * + * @param data The byte array containing the escher record information + * @param offset The starting offset into <code>data</code>. + * @param recordFactory May be null since this is not a container record. + * @return The number of bytes read from the byte array. + */ + public int fillFields( byte[] data, int offset, + EscherRecordFactory recordFactory + ) + { + int bytesRemaining = readHeader( data, offset ); + int pos = offset + 8; + field_1_blipTypeWin32 = data[pos]; + field_2_blipTypeMacOS = data[pos + 1]; + System.arraycopy( data, pos + 2, field_3_uid = new byte[16], 0, 16 ); + field_4_tag = LittleEndian.getShort( data, pos + 18 ); + field_5_size = LittleEndian.getInt( data, pos + 20 ); + field_6_ref = LittleEndian.getInt( data, pos + 24 ); + field_7_offset = LittleEndian.getInt( data, pos + 28 ); + field_8_usage = data[pos + 32]; + field_9_name = data[pos + 33]; + field_10_unused2 = data[pos + 34]; + field_11_unused3 = data[pos + 35]; + bytesRemaining -= 36; + remainingData = new byte[bytesRemaining]; + System.arraycopy( data, pos + 36, remainingData, 0, bytesRemaining ); + return bytesRemaining + 8 + 36; + + } + + /** + * This method serializes this escher record into a byte array. + * + * @param offset The offset into <code>data</code> to start writing the record data to. + * @param data The byte array to serialize to. + * @param listener A listener to retrieve start and end callbacks. Use a <code>NullEscherSerailizationListener</code> to ignore these events. + * @return The number of bytes written. + * @see NullEscherSerializationListener + */ + public int serialize( int offset, byte[] data, EscherSerializationListener listener ) + { + listener.beforeRecordSerialize( offset, getRecordId(), this ); + + LittleEndian.putShort( data, offset, getOptions() ); + LittleEndian.putShort( data, offset + 2, getRecordId() ); + int remainingBytes = remainingData.length + 36; + LittleEndian.putInt( data, offset + 4, remainingBytes ); + + data[offset + 8] = field_1_blipTypeWin32; + data[offset + 9] = field_2_blipTypeMacOS; + for ( int i = 0; i < 16; i++ ) + data[offset + 10 + i] = field_3_uid[i]; + LittleEndian.putShort( data, offset + 26, field_4_tag ); + LittleEndian.putInt( data, offset + 28, field_5_size ); + LittleEndian.putInt( data, offset + 32, field_6_ref ); + LittleEndian.putInt( data, offset + 36, field_7_offset ); + data[offset + 40] = field_8_usage; + data[offset + 41] = field_9_name; + data[offset + 42] = field_10_unused2; + data[offset + 43] = field_11_unused3; + System.arraycopy( remainingData, 0, data, offset + 44, remainingData.length ); + int pos = offset + 8 + 36 + remainingData.length; + + listener.afterRecordSerialize(pos, getRecordId(), pos - offset, this); + return pos - offset; + } + + /** + * Returns the number of bytes that are required to serialize this record. + * + * @return Number of bytes + */ + public int getRecordSize() + { + return 8 + 1 + 1 + 16 + 2 + 4 + 4 + 4 + 1 + 1 + 1 + 1 + remainingData.length; + } + + /** + * The short name for this record + */ + public String getRecordName() + { + return "BSE"; + } + + /** + * The expected blip type under windows (failure to match this blip type will result in + * Excel converting to this format). + */ + public byte getBlipTypeWin32() + { + return field_1_blipTypeWin32; + } + + /** + * Set the expected win32 blip type + */ + public void setBlipTypeWin32( byte blipTypeWin32 ) + { + this.field_1_blipTypeWin32 = blipTypeWin32; + } + + /** + * The expected blip type under MacOS (failure to match this blip type will result in + * Excel converting to this format). + */ + public byte getBlipTypeMacOS() + { + return field_2_blipTypeMacOS; + } + + /** + * Set the expected MacOS blip type + */ + public void setBlipTypeMacOS( byte blipTypeMacOS ) + { + this.field_2_blipTypeMacOS = blipTypeMacOS; + } + + /** + * 16 byte MD4 checksum. + */ + public byte[] getUid() + { + return field_3_uid; + } + + /** + * 16 byte MD4 checksum. + */ + public void setUid( byte[] uid ) + { + this.field_3_uid = uid; + } + + /** + * unused + */ + public short getTag() + { + return field_4_tag; + } + + /** + * unused + */ + public void setTag( short tag ) + { + this.field_4_tag = tag; + } + + /** + * Blip size in stream. + */ + public int getSize() + { + return field_5_size; + } + + /** + * Blip size in stream. + */ + public void setSize( int size ) + { + this.field_5_size = size; + } + + /** + * The reference count of this blip. + */ + public int getRef() + { + return field_6_ref; + } + + /** + * The reference count of this blip. + */ + public void setRef( int ref ) + { + this.field_6_ref = ref; + } + + /** + * File offset in the delay stream. + */ + public int getOffset() + { + return field_7_offset; + } + + /** + * File offset in the delay stream. + */ + public void setOffset( int offset ) + { + this.field_7_offset = offset; + } + + /** + * Defines the way this blip is used. + */ + public byte getUsage() + { + return field_8_usage; + } + + /** + * Defines the way this blip is used. + */ + public void setUsage( byte usage ) + { + this.field_8_usage = usage; + } + + /** + * The length in characters of the blip name. + */ + public byte getName() + { + return field_9_name; + } + + /** + * The length in characters of the blip name. + */ + public void setName( byte name ) + { + this.field_9_name = name; + } + + public byte getUnused2() + { + return field_10_unused2; + } + + public void setUnused2( byte unused2 ) + { + this.field_10_unused2 = unused2; + } + + public byte getUnused3() + { + return field_11_unused3; + } + + public void setUnused3( byte unused3 ) + { + this.field_11_unused3 = unused3; + } + + /** + * Any remaining data in this record. + */ + public byte[] getRemainingData() + { + return remainingData; + } + + /** + * Any remaining data in this record. + */ + public void setRemainingData( byte[] remainingData ) + { + this.remainingData = remainingData; + } + + /** + * Calculate the string representation of this object + */ + public String toString() + { + String nl = System.getProperty( "line.separator" ); + + String extraData; + ByteArrayOutputStream b = new ByteArrayOutputStream(); + try + { + HexDump.dump( this.remainingData, 0, b, 0 ); + extraData = b.toString(); + } + catch ( Exception e ) + { + extraData = e.toString(); + } + return getClass().getName() + ":" + nl + + " RecordId: 0x" + HexDump.toHex( RECORD_ID ) + nl + + " Options: 0x" + HexDump.toHex( getOptions() ) + nl + + " BlipTypeWin32: " + field_1_blipTypeWin32 + nl + + " BlipTypeMacOS: " + field_2_blipTypeMacOS + nl + + " SUID: " + HexDump.toHex(field_3_uid) + nl + + " Tag: " + field_4_tag + nl + + " Size: " + field_5_size + nl + + " Ref: " + field_6_ref + nl + + " Offset: " + field_7_offset + nl + + " Usage: " + field_8_usage + nl + + " Name: " + field_9_name + nl + + " Unused2: " + field_10_unused2 + nl + + " Unused3: " + field_11_unused3 + nl + + " Extra Data:" + nl + extraData; + + + } + + /** + * Retrieve the string representation given a blip id. + */ + public String getBlipType( byte b ) + { + switch ( b ) + { + case BT_ERROR: + return " ERROR"; + case BT_UNKNOWN: + return " UNKNOWN"; + case BT_EMF: + return " EMF"; + case BT_WMF: + return " WMF"; + case BT_PICT: + return " PICT"; + case BT_JPEG: + return " JPEG"; + case BT_PNG: + return " PNG"; + case BT_DIB: + return " DIB"; + default: + if ( b < 32 ) + return " NotKnown"; + else + return " Client"; + } + } + + +} diff --git a/src/java/org/apache/poi/ddf/EscherBlipRecord.java b/src/java/org/apache/poi/ddf/EscherBlipRecord.java new file mode 100644 index 0000000000..ca8be718b4 --- /dev/null +++ b/src/java/org/apache/poi/ddf/EscherBlipRecord.java @@ -0,0 +1,417 @@ +package org.apache.poi.ddf; + +import org.apache.poi.hssf.record.RecordFormatException; +import org.apache.poi.util.HexDump; +import org.apache.poi.util.LittleEndian; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.IOException; +import java.util.zip.InflaterInputStream; +import java.util.zip.DeflaterOutputStream; + +/** + * The blip record is used to hold details about large binary objects that occur in escher such + * as JPEG, GIF, PICT and WMF files. The contents of the stream is usually compressed. Inflate + * can be used to decompress the data. + * + * @author Glen Stampoultzis + * @see java.util.zip.Inflater + */ +public class EscherBlipRecord + extends EscherRecord +{ + public static final short RECORD_ID_START = (short) 0xF018; + public static final short RECORD_ID_END = (short) 0xF117; + public static final String RECORD_DESCRIPTION = "msofbtBlip"; + private static final int HEADER_SIZE = 8; + + private byte[] field_1_secondaryUID; + private int field_2_cacheOfSize; + private int field_3_boundaryTop; + private int field_4_boundaryLeft; + private int field_5_boundaryWidth; + private int field_6_boundaryHeight; + private int field_7_width; + private int field_8_height; + private int field_9_cacheOfSavedSize; + private byte field_10_compressionFlag; + private byte field_11_filter; + private byte[] field_12_data; + + + /** + * This method deserializes the record from a byte array. + * + * @param data The byte array containing the escher record information + * @param offset The starting offset into <code>data</code>. + * @param recordFactory May be null since this is not a container record. + * @return The number of bytes read from the byte array. + */ + public int fillFields( byte[] data, int offset, + EscherRecordFactory recordFactory + ) + { + int bytesAfterHeader = readHeader( data, offset ); + int pos = offset + HEADER_SIZE; + + int size = 0; + field_1_secondaryUID = new byte[16]; + System.arraycopy( data, pos + size, field_1_secondaryUID, 0, 16 ); size += 16; + field_2_cacheOfSize = LittleEndian.getInt( data, pos + size );size+=4; + field_3_boundaryTop = LittleEndian.getInt( data, pos + size );size+=4; + field_4_boundaryLeft = LittleEndian.getInt( data, pos + size );size+=4; + field_5_boundaryWidth = LittleEndian.getInt( data, pos + size );size+=4; + field_6_boundaryHeight = LittleEndian.getInt( data, pos + size );size+=4; + field_7_width = LittleEndian.getInt( data, pos + size );size+=4; + field_8_height = LittleEndian.getInt( data, pos + size );size+=4; + field_9_cacheOfSavedSize = LittleEndian.getInt( data, pos + size );size+=4; + field_10_compressionFlag = data[pos + size]; size++; + field_11_filter = data[pos + size]; size++; + + int bytesRemaining = bytesAfterHeader - size; + field_12_data = new byte[bytesRemaining]; + System.arraycopy(data, pos + size, field_12_data, 0, bytesRemaining); + + return bytesRemaining + HEADER_SIZE + bytesAfterHeader; + } + + + /** + * This method serializes this escher record into a byte array. + * + * @param offset The offset into <code>data</code> to start writing the record data to. + * @param data The byte array to serialize to. + * @param listener A listener to retrieve start and end callbacks. Use a <code>NullEscherSerailizationListener</code> to ignore these events. + * @return The number of bytes written. + * + * @see NullEscherSerializationListener + */ + public int serialize( int offset, byte[] data, EscherSerializationListener listener ) + { + listener.beforeRecordSerialize(offset, getRecordId(), this); + + LittleEndian.putShort( data, offset, getOptions() ); + LittleEndian.putShort( data, offset + 2, getRecordId() ); + int remainingBytes = field_12_data.length + 36; + LittleEndian.putInt( data, offset + 4, remainingBytes ); + + int pos = offset + HEADER_SIZE; + System.arraycopy(field_1_secondaryUID, 0, data, pos, 16 ); pos += 16; + LittleEndian.putInt( data, pos, field_2_cacheOfSize); pos += 4; + LittleEndian.putInt( data, pos, field_3_boundaryTop); pos += 4; + LittleEndian.putInt( data, pos, field_4_boundaryLeft); pos += 4; + LittleEndian.putInt( data, pos, field_5_boundaryWidth); pos += 4; + LittleEndian.putInt( data, pos, field_6_boundaryHeight); pos += 4; + LittleEndian.putInt( data, pos, field_7_width); pos += 4; + LittleEndian.putInt( data, pos, field_8_height); pos += 4; + LittleEndian.putInt( data, pos, field_9_cacheOfSavedSize); pos += 4; + data[pos++] = field_10_compressionFlag; + data[pos++] = field_11_filter; + System.arraycopy(field_12_data, 0, data, pos, field_12_data.length); pos += field_12_data.length; + + listener.afterRecordSerialize(pos, getRecordId(), pos - offset, this); + return pos - offset; + } + + /** + * Returns the number of bytes that are required to serialize this record. + * + * @return Number of bytes + */ + public int getRecordSize() + { + return 58 + field_12_data.length; + } + + /** + * The short name for this record + */ + public String getRecordName() + { + return "Blip"; + } + + /** + * Retrieve the secondary UID + */ + public byte[] getSecondaryUID() + { + return field_1_secondaryUID; + } + + /** + * Set the secondary UID + */ + public void setSecondaryUID( byte[] field_1_secondaryUID ) + { + this.field_1_secondaryUID = field_1_secondaryUID; + } + + /** + * Retrieve the cache of the metafile size + */ + public int getCacheOfSize() + { + return field_2_cacheOfSize; + } + + /** + * Set the cache of the metafile size + */ + public void setCacheOfSize( int field_2_cacheOfSize ) + { + this.field_2_cacheOfSize = field_2_cacheOfSize; + } + + /** + * Retrieve the top boundary of the metafile drawing commands + */ + public int getBoundaryTop() + { + return field_3_boundaryTop; + } + + /** + * Set the top boundary of the metafile drawing commands + */ + public void setBoundaryTop( int field_3_boundaryTop ) + { + this.field_3_boundaryTop = field_3_boundaryTop; + } + + /** + * Retrieve the left boundary of the metafile drawing commands + */ + public int getBoundaryLeft() + { + return field_4_boundaryLeft; + } + + /** + * Set the left boundary of the metafile drawing commands + */ + public void setBoundaryLeft( int field_4_boundaryLeft ) + { + this.field_4_boundaryLeft = field_4_boundaryLeft; + } + + /** + * Retrieve the boundary width of the metafile drawing commands + */ + public int getBoundaryWidth() + { + return field_5_boundaryWidth; + } + + /** + * Set the boundary width of the metafile drawing commands + */ + public void setBoundaryWidth( int field_5_boundaryWidth ) + { + this.field_5_boundaryWidth = field_5_boundaryWidth; + } + + /** + * Retrieve the boundary height of the metafile drawing commands + */ + public int getBoundaryHeight() + { + return field_6_boundaryHeight; + } + + /** + * Set the boundary height of the metafile drawing commands + */ + public void setBoundaryHeight( int field_6_boundaryHeight ) + { + this.field_6_boundaryHeight = field_6_boundaryHeight; + } + + /** + * Retrieve the width of the metafile in EMU's (English Metric Units). + */ + public int getWidth() + { + return field_7_width; + } + + /** + * Set the width of the metafile in EMU's (English Metric Units). + */ + public void setWidth( int width ) + { + this.field_7_width = width; + } + + /** + * Retrieve the height of the metafile in EMU's (English Metric Units). + */ + public int getHeight() + { + return field_8_height; + } + + /** + * Set the height of the metafile in EMU's (English Metric Units). + */ + public void setHeight( int height ) + { + this.field_8_height = height; + } + + /** + * Retrieve the cache of the saved size + */ + public int getCacheOfSavedSize() + { + return field_9_cacheOfSavedSize; + } + + /** + * Set the cache of the saved size + */ + public void setCacheOfSavedSize( int field_9_cacheOfSavedSize ) + { + this.field_9_cacheOfSavedSize = field_9_cacheOfSavedSize; + } + + /** + * Is the contents of the blip compressed? + */ + public byte getCompressionFlag() + { + return field_10_compressionFlag; + } + + /** + * Set whether the contents of the blip is compressed + */ + public void setCompressionFlag( byte field_10_compressionFlag ) + { + this.field_10_compressionFlag = field_10_compressionFlag; + } + + /** + * Filter should always be 0 + */ + public byte getFilter() + { + return field_11_filter; + } + + /** + * Filter should always be 0 + */ + public void setFilter( byte field_11_filter ) + { + this.field_11_filter = field_11_filter; + } + + /** + * The BLIP data + */ + public byte[] getData() + { + return field_12_data; + } + + /** + * The BLIP data + */ + public void setData( byte[] field_12_data ) + { + this.field_12_data = field_12_data; + } + + /** + * The string representation of this record. + * + * @return A string + */ + public String toString() + { + String nl = System.getProperty( "line.separator" ); + + String extraData; + ByteArrayOutputStream b = new ByteArrayOutputStream(); + try + { + HexDump.dump( this.field_12_data, 0, b, 0 ); + extraData = b.toString(); + } + catch ( Exception e ) + { + extraData = e.toString(); + } + return getClass().getName() + ":" + nl + + " RecordId: 0x" + HexDump.toHex( getRecordId() ) + nl + + " Options: 0x" + HexDump.toHex( getOptions() ) + nl + + " Secondary UID: " + HexDump.toHex( field_1_secondaryUID ) + nl + + " CacheOfSize: " + field_2_cacheOfSize + nl + + " BoundaryTop: " + field_3_boundaryTop + nl + + " BoundaryLeft: " + field_4_boundaryLeft + nl + + " BoundaryWidth: " + field_5_boundaryWidth + nl + + " BoundaryHeight: " + field_6_boundaryHeight + nl + + " X: " + field_7_width + nl + + " Y: " + field_8_height + nl + + " CacheOfSavedSize: " + field_9_cacheOfSavedSize + nl + + " CompressionFlag: " + field_10_compressionFlag + nl + + " Filter: " + field_11_filter + nl + + " Data:" + nl + extraData; + } + + /** + * Compress the contents of the provided array + * + * @param data An uncompressed byte array + * @see DeflaterOutputStream#write(int b) + */ + public static byte[] compress( byte[] data ) + { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + DeflaterOutputStream deflaterOutputStream = new DeflaterOutputStream( out ); + try + { + for ( int i = 0; i < data.length; i++ ) + deflaterOutputStream.write( data[i] ); + } + catch ( IOException e ) + { + throw new RecordFormatException( e.toString() ); + } + + return out.toByteArray(); + } + + /** + * Decompresses a byte array. + * + * @param data The compressed byte array + * @param pos The starting position into the byte array + * @param length The number of compressed bytes to decompress + * @return An uncompressed byte array + * @see InflaterInputStream#read + */ + public static byte[] decompress( byte[] data, int pos, int length ) + { + byte[] compressedData = new byte[length]; + System.arraycopy( data, pos + 50, compressedData, 0, length ); + InputStream compressedInputStream = new ByteArrayInputStream( compressedData ); + InflaterInputStream inflaterInputStream = new InflaterInputStream( compressedInputStream ); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + int c; + try + { + while ( ( c = inflaterInputStream.read() ) != -1 ) + out.write( c ); + } + catch ( IOException e ) + { + throw new RecordFormatException( e.toString() ); + } + return out.toByteArray(); + } + +} diff --git a/src/java/org/apache/poi/ddf/EscherBoolProperty.java b/src/java/org/apache/poi/ddf/EscherBoolProperty.java new file mode 100644 index 0000000000..86a7a0a5c5 --- /dev/null +++ b/src/java/org/apache/poi/ddf/EscherBoolProperty.java @@ -0,0 +1,50 @@ +package org.apache.poi.ddf; + +/** + * Represents a boolean property. The actual utility of this property is in doubt because many + * of the properties marked as boolean seem to actually contain special values. In other words + * they're not true booleans. + * + * @author Glen Stampoultzis + * @see EscherSimpleProperty + * @see EscherProperty + */ +public class EscherBoolProperty + extends EscherSimpleProperty +{ + /** + * Create an instance of an escher boolean property. + * + * @param propertyNumber The property number + * @param value The 32 bit value of this bool property + */ + public EscherBoolProperty( short propertyNumber, int value ) + { + super( propertyNumber, false, false, value ); + } + + /** + * Whether this boolean property is true + */ + public boolean isTrue() + { + return propertyValue != 0; + } + + /** + * Whether this boolean property is false + */ + public boolean isFalse() + { + return propertyValue == 0; + } + +// public String toString() +// { +// return "propNum: " + getPropertyNumber() +// + ", complex: " + isComplex() +// + ", blipId: " + isBlipId() +// + ", value: " + (getValue() != 0); +// } + +} diff --git a/src/java/org/apache/poi/ddf/EscherChildAnchorRecord.java b/src/java/org/apache/poi/ddf/EscherChildAnchorRecord.java new file mode 100644 index 0000000000..f53ba7d726 --- /dev/null +++ b/src/java/org/apache/poi/ddf/EscherChildAnchorRecord.java @@ -0,0 +1,176 @@ +package org.apache.poi.ddf; + +import org.apache.poi.util.HexDump; +import org.apache.poi.util.LittleEndian; + +/** + * The escher child achor record is used to specify the position of a shape under an + * existing group. The first level of shape records use a EscherClientAnchor record instead. + * + * @author Glen Stampoultzis + * @see EscherChildAnchorRecord + */ +public class EscherChildAnchorRecord + extends EscherRecord +{ + public static final short RECORD_ID = (short) 0xF00F; + public static final String RECORD_DESCRIPTION = "MsofbtChildAnchor"; + + private int field_1_dx1; + private int field_2_dy1; + private int field_3_dx2; + private int field_4_dy2; + + /** + * This method deserializes the record from a byte array. + * + * @param data The byte array containing the escher record information + * @param offset The starting offset into <code>data</code>. + * @param recordFactory May be null since this is not a container record. + * @return The number of bytes read from the byte array. + */ + public int fillFields( byte[] data, int offset, EscherRecordFactory recordFactory ) + { + int bytesRemaining = readHeader( data, offset ); + int pos = offset + 8; + int size = 0; + field_1_dx1 = LittleEndian.getInt( data, pos + size );size+=4; + field_2_dy1 = LittleEndian.getInt( data, pos + size );size+=4; + field_3_dx2 = LittleEndian.getInt( data, pos + size );size+=4; + field_4_dy2 = LittleEndian.getInt( data, pos + size );size+=4; + return 8 + size; + } + + /** + * This method serializes this escher record into a byte array. + * + * @param offset The offset into <code>data</code> to start writing the record data to. + * @param data The byte array to serialize to. + * @param listener A listener to retrieve start and end callbacks. Use a <code>NullEscherSerailizationListener</code> to ignore these events. + * @return The number of bytes written. + * @see NullEscherSerializationListener + */ + public int serialize( int offset, byte[] data, EscherSerializationListener listener ) + { + listener.beforeRecordSerialize( offset, getRecordId(), this ); + int pos = offset; + LittleEndian.putShort( data, pos, getOptions() ); pos += 2; + LittleEndian.putShort( data, pos, getRecordId() ); pos += 2; + LittleEndian.putInt( data, pos, getRecordSize()-8 ); pos += 4; + LittleEndian.putInt( data, pos, field_1_dx1 ); pos += 4; + LittleEndian.putInt( data, pos, field_2_dy1 ); pos += 4; + LittleEndian.putInt( data, pos, field_3_dx2 ); pos += 4; + LittleEndian.putInt( data, pos, field_4_dy2 ); pos += 4; + + listener.afterRecordSerialize( pos, getRecordId(), pos - offset, this ); + return pos - offset; + } + + /** + * Returns the number of bytes that are required to serialize this record. + * + * @return Number of bytes + */ + public int getRecordSize() + { + return 8 + 4 * 4; + } + + /** + * The record id for the EscherChildAnchorRecord. + */ + public short getRecordId() + { + return RECORD_ID; + } + + /** + * The short name for this record + */ + public String getRecordName() + { + return "ChildAnchor"; + } + + /** + * The string representation of this record + */ + public String toString() + { + String nl = System.getProperty("line.separator"); + + return getClass().getName() + ":" + nl + + " RecordId: 0x" + HexDump.toHex(RECORD_ID) + nl + + " Options: 0x" + HexDump.toHex(getOptions()) + nl + + " X1: " + field_1_dx1 + nl + + " Y1: " + field_2_dy1 + nl + + " X2: " + field_3_dx2 + nl + + " Y2: " + field_4_dy2 + nl ; + + } + + /** + * Retrieves offset within the parent coordinate space for the top left point. + */ + public int getDx1() + { + return field_1_dx1; + } + + /** + * Sets offset within the parent coordinate space for the top left point. + */ + public void setDx1( int field_1_dx1 ) + { + this.field_1_dx1 = field_1_dx1; + } + + /** + * Gets offset within the parent coordinate space for the top left point. + */ + public int getDy1() + { + return field_2_dy1; + } + + /** + * Sets offset within the parent coordinate space for the top left point. + */ + public void setDy1( int field_2_dy1 ) + { + this.field_2_dy1 = field_2_dy1; + } + + /** + * Retrieves offset within the parent coordinate space for the bottom right point. + */ + public int getDx2() + { + return field_3_dx2; + } + + /** + * Sets offset within the parent coordinate space for the bottom right point. + */ + public void setDx2( int field_3_dx2 ) + { + this.field_3_dx2 = field_3_dx2; + } + + /** + * Gets the offset within the parent coordinate space for the bottom right point. + */ + public int getDy2() + { + return field_4_dy2; + } + + /** + * Sets the offset within the parent coordinate space for the bottom right point. + */ + public void setDy2( int field_4_dy2 ) + { + this.field_4_dy2 = field_4_dy2; + } + +} diff --git a/src/java/org/apache/poi/ddf/EscherClientAnchorRecord.java b/src/java/org/apache/poi/ddf/EscherClientAnchorRecord.java new file mode 100644 index 0000000000..d61d77b7a7 --- /dev/null +++ b/src/java/org/apache/poi/ddf/EscherClientAnchorRecord.java @@ -0,0 +1,317 @@ +package org.apache.poi.ddf; + +import org.apache.poi.util.HexDump; +import org.apache.poi.util.LittleEndian; + +import java.io.ByteArrayOutputStream; + +/** + * The escher client anchor specifies which rows and cells the shape is bound to as well as + * the offsets within those cells. Each cell is 1024 units wide by 256 units long regardless + * of the actual size of the cell. The EscherClientAnchorRecord only applies to the top-most + * shapes. Shapes contained in groups are bound using the EscherChildAnchorRecords. + * + * @author Glen Stampoultzis + * @see EscherChildAnchorRecord + */ +public class EscherClientAnchorRecord + extends EscherRecord +{ + public static final short RECORD_ID = (short) 0xF010; + public static final String RECORD_DESCRIPTION = "MsofbtClientAnchor"; + + private short field_1_flag; + private short field_2_col1; + private short field_3_dx1; + private short field_4_row1; + private short field_5_dy1; + private short field_6_col2; + private short field_7_dx2; + private short field_8_row2; + private short field_9_dy2; + private byte[] remainingData; + + /** + * This method deserializes the record from a byte array. + * + * @param data The byte array containing the escher record information + * @param offset The starting offset into <code>data</code>. + * @param recordFactory May be null since this is not a container record. + * @return The number of bytes read from the byte array. + */ + public int fillFields( byte[] data, int offset, EscherRecordFactory recordFactory ) + { + int bytesRemaining = readHeader( data, offset ); + int pos = offset + 8; + int size = 0; + field_1_flag = LittleEndian.getShort( data, pos + size ); size += 2; + field_2_col1 = LittleEndian.getShort( data, pos + size ); size += 2; + field_3_dx1 = LittleEndian.getShort( data, pos + size ); size += 2; + field_4_row1 = LittleEndian.getShort( data, pos + size ); size += 2; + field_5_dy1 = LittleEndian.getShort( data, pos + size ); size += 2; + field_6_col2 = LittleEndian.getShort( data, pos + size ); size += 2; + field_7_dx2 = LittleEndian.getShort( data, pos + size ); size += 2; + field_8_row2 = LittleEndian.getShort( data, pos + size ); size += 2; + field_9_dy2 = LittleEndian.getShort( data, pos + size ); size += 2; + bytesRemaining -= size; + remainingData = new byte[bytesRemaining]; + System.arraycopy( data, pos + size, remainingData, 0, bytesRemaining ); + return 8 + size + bytesRemaining; + } + + /** + * This method serializes this escher record into a byte array. + * + * @param offset The offset into <code>data</code> to start writing the record data to. + * @param data The byte array to serialize to. + * @param listener A listener to retrieve start and end callbacks. Use a <code>NullEscherSerailizationListener</code> to ignore these events. + * @return The number of bytes written. + * @see NullEscherSerializationListener + */ + public int serialize( int offset, byte[] data, EscherSerializationListener listener ) + { + listener.beforeRecordSerialize( offset, getRecordId(), this ); + + if (remainingData == null) remainingData = new byte[0]; + LittleEndian.putShort( data, offset, getOptions() ); + LittleEndian.putShort( data, offset + 2, getRecordId() ); + int remainingBytes = remainingData.length + 18; + LittleEndian.putInt( data, offset + 4, remainingBytes ); + LittleEndian.putShort( data, offset + 8, field_1_flag ); + LittleEndian.putShort( data, offset + 10, field_2_col1 ); + LittleEndian.putShort( data, offset + 12, field_3_dx1 ); + LittleEndian.putShort( data, offset + 14, field_4_row1 ); + LittleEndian.putShort( data, offset + 16, field_5_dy1 ); + LittleEndian.putShort( data, offset + 18, field_6_col2 ); + LittleEndian.putShort( data, offset + 20, field_7_dx2 ); + LittleEndian.putShort( data, offset + 22, field_8_row2 ); + LittleEndian.putShort( data, offset + 24, field_9_dy2 ); + System.arraycopy( remainingData, 0, data, offset + 26, remainingData.length ); + int pos = offset + 8 + 18 + remainingData.length; + + listener.afterRecordSerialize( pos, getRecordId(), pos - offset, this ); + return pos - offset; + } + + /** + * Returns the number of bytes that are required to serialize this record. + * + * @return Number of bytes + */ + public int getRecordSize() + { + return 8 + 18 + (remainingData == null ? 0 : remainingData.length); + } + + /** + * The record id for this record. + */ + public short getRecordId() + { + return RECORD_ID; + } + + /** + * The short name for this record + */ + public String getRecordName() + { + return "ClientAnchor"; + } + + /** + * Returns the string representation for this record. + * + * @return A string + */ + public String toString() + { + String nl = System.getProperty("line.separator"); + + String extraData; + ByteArrayOutputStream b = new ByteArrayOutputStream(); + try + { + HexDump.dump(this.remainingData, 0, b, 0); + extraData = b.toString(); + } + catch ( Exception e ) + { + extraData = "error"; + } + return getClass().getName() + ":" + nl + + " RecordId: 0x" + HexDump.toHex(RECORD_ID) + nl + + " Options: 0x" + HexDump.toHex(getOptions()) + nl + + " Flag: " + field_1_flag + nl + + " Col1: " + field_2_col1 + nl + + " DX1: " + field_3_dx1 + nl + + " Row1: " + field_4_row1 + nl + + " DY1: " + field_5_dy1 + nl + + " Col2: " + field_6_col2 + nl + + " DX2: " + field_7_dx2 + nl + + " Row2: " + field_8_row2 + nl + + " DY2: " + field_9_dy2 + nl + + " Extra Data:" + nl + extraData; + + } + + /** + * 0 = Move and size with Cells, 2 = Move but don't size with cells, 3 = Don't move or size with cells. + */ + public short getFlag() + { + return field_1_flag; + } + + /** + * 0 = Move and size with Cells, 2 = Move but don't size with cells, 3 = Don't move or size with cells. + */ + public void setFlag( short field_1_flag ) + { + this.field_1_flag = field_1_flag; + } + + /** + * The column number for the top-left position. 0 based. + */ + public short getCol1() + { + return field_2_col1; + } + + /** + * The column number for the top-left position. 0 based. + */ + public void setCol1( short field_2_col1 ) + { + this.field_2_col1 = field_2_col1; + } + + /** + * The x offset within the top-left cell. Range is from 0 to 1023. + */ + public short getDx1() + { + return field_3_dx1; + } + + /** + * The x offset within the top-left cell. Range is from 0 to 1023. + */ + public void setDx1( short field_3_dx1 ) + { + this.field_3_dx1 = field_3_dx1; + } + + /** + * The row number for the top-left corner of the shape. + */ + public short getRow1() + { + return field_4_row1; + } + + /** + * The row number for the top-left corner of the shape. + */ + public void setRow1( short field_4_row1 ) + { + this.field_4_row1 = field_4_row1; + } + + /** + * The y offset within the top-left corner of the current shape. + */ + public short getDy1() + { + return field_5_dy1; + } + + /** + * The y offset within the top-left corner of the current shape. + */ + public void setDy1( short field_5_dy1 ) + { + this.field_5_dy1 = field_5_dy1; + } + + /** + * The column of the bottom right corner of this shape. + */ + public short getCol2() + { + return field_6_col2; + } + + /** + * The column of the bottom right corner of this shape. + */ + public void setCol2( short field_6_col2 ) + { + this.field_6_col2 = field_6_col2; + } + + /** + * The x offset withing the cell for the bottom-right corner of this shape. + */ + public short getDx2() + { + return field_7_dx2; + } + + /** + * The x offset withing the cell for the bottom-right corner of this shape. + */ + public void setDx2( short field_7_dx2 ) + { + this.field_7_dx2 = field_7_dx2; + } + + /** + * The row number for the bottom-right corner of the current shape. + */ + public short getRow2() + { + return field_8_row2; + } + + /** + * The row number for the bottom-right corner of the current shape. + */ + public void setRow2( short field_8_row2 ) + { + this.field_8_row2 = field_8_row2; + } + + /** + * The y offset withing the cell for the bottom-right corner of this shape. + */ + public short getDy2() + { + return field_9_dy2; + } + + /** + * The y offset withing the cell for the bottom-right corner of this shape. + */ + public void setDy2( short field_9_dy2 ) + { + this.field_9_dy2 = field_9_dy2; + } + + /** + * Any remaining data in the record + */ + public byte[] getRemainingData() + { + return remainingData; + } + + /** + * Any remaining data in the record + */ + public void setRemainingData( byte[] remainingData ) + { + this.remainingData = remainingData; + } +} diff --git a/src/java/org/apache/poi/ddf/EscherClientDataRecord.java b/src/java/org/apache/poi/ddf/EscherClientDataRecord.java new file mode 100644 index 0000000000..d568996f47 --- /dev/null +++ b/src/java/org/apache/poi/ddf/EscherClientDataRecord.java @@ -0,0 +1,130 @@ +package org.apache.poi.ddf; + +import org.apache.poi.util.HexDump; +import org.apache.poi.util.LittleEndian; + +import java.io.ByteArrayOutputStream; + +/** + * The EscherClientDataRecord is used to store client specific data about the position of a + * shape within a container. + * + * @author Glen Stampoultzis + */ +public class EscherClientDataRecord + extends EscherRecord +{ + public static final short RECORD_ID = (short) 0xF011; + public static final String RECORD_DESCRIPTION = "MsofbtClientData"; + + private byte[] remainingData; + + /** + * This method deserializes the record from a byte array. + * + * @param data The byte array containing the escher record information + * @param offset The starting offset into <code>data</code>. + * @param recordFactory May be null since this is not a container record. + * @return The number of bytes read from the byte array. + */ + public int fillFields( byte[] data, int offset, EscherRecordFactory recordFactory ) + { + int bytesRemaining = readHeader( data, offset ); + int pos = offset + 8; + remainingData = new byte[bytesRemaining]; + System.arraycopy( data, pos, remainingData, 0, bytesRemaining ); + return 8 + bytesRemaining; + } + + /** + * This method serializes this escher record into a byte array. + * + * @param offset The offset into <code>data</code> to start writing the record data to. + * @param data The byte array to serialize to. + * @param listener A listener to retrieve start and end callbacks. Use a <code>NullEscherSerailizationListener</code> to ignore these events. + * @return The number of bytes written. + * @see NullEscherSerializationListener + */ + public int serialize( int offset, byte[] data, EscherSerializationListener listener ) + { + listener.beforeRecordSerialize( offset, getRecordId(), this ); + + if (remainingData == null) remainingData = new byte[0]; + LittleEndian.putShort( data, offset, getOptions() ); + LittleEndian.putShort( data, offset + 2, getRecordId() ); + LittleEndian.putInt( data, offset + 4, remainingData.length ); + System.arraycopy( remainingData, 0, data, offset + 8, remainingData.length ); + int pos = offset + 8 + remainingData.length; + + listener.afterRecordSerialize( pos, getRecordId(), pos - offset, this ); + return pos - offset; + } + + /** + * Returns the number of bytes that are required to serialize this record. + * + * @return Number of bytes + */ + public int getRecordSize() + { + return 8 + (remainingData == null ? 0 : remainingData.length); + } + + /** + * Returns the identifier of this record. + */ + public short getRecordId() + { + return RECORD_ID; + } + + /** + * The short name for this record + */ + public String getRecordName() + { + return "ClientData"; + } + + /** + * Returns the string representation of this record. + */ + public String toString() + { + String nl = System.getProperty("line.separator"); + + String extraData; + ByteArrayOutputStream b = new ByteArrayOutputStream(); + try + { + HexDump.dump(this.remainingData, 0, b, 0); + extraData = b.toString(); + } + catch ( Exception e ) + { + extraData = "error"; + } + return getClass().getName() + ":" + nl + + " RecordId: 0x" + HexDump.toHex(RECORD_ID) + nl + + " Options: 0x" + HexDump.toHex(getOptions()) + nl + + " Extra Data:" + nl + + extraData; + + } + + /** + * Any data recording this record. + */ + public byte[] getRemainingData() + { + return remainingData; + } + + /** + * Any data recording this record. + */ + public void setRemainingData( byte[] remainingData ) + { + this.remainingData = remainingData; + } +} diff --git a/src/java/org/apache/poi/ddf/EscherComplexProperty.java b/src/java/org/apache/poi/ddf/EscherComplexProperty.java new file mode 100644 index 0000000000..4788a66c71 --- /dev/null +++ b/src/java/org/apache/poi/ddf/EscherComplexProperty.java @@ -0,0 +1,152 @@ +package org.apache.poi.ddf; + +import org.apache.poi.util.LittleEndian; +import org.apache.poi.util.HexDump; + +import java.util.Arrays; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +/** + * A complex property differs from a simple property in that the data can not fit inside a 32 bit + * integer. See the specification for more detailed information regarding exactly what is + * stored here. + * + * @author Glen Stampoultzis + */ +public class EscherComplexProperty + extends EscherProperty +{ + byte[] complexData = new byte[0]; + + /** + * Create a complex property using the property id and a byte array containing the complex + * data value. + * + * @param id The id consists of the property number, a flag indicating whether this is a blip id and a flag + * indicating that this is a complex property. + * @param complexData The value of this property. + */ + public EscherComplexProperty( short id, byte[] complexData ) + { + super( id ); + this.complexData = complexData; + } + + /** + * Create a complex property using the property number, a flag to indicate whether this is a + * blip reference and the complex property data. + * + * @param propertyNumber The property number + * @param isBlipId Whether this is a blip id. Should be false. + * @param complexData The value of this complex property. + */ + public EscherComplexProperty( short propertyNumber, boolean isBlipId, byte[] complexData ) + { + super( propertyNumber, true, isBlipId ); + this.complexData = complexData; + } + + /** + * Serializes the simple part of this property. ie the first 6 bytes. + */ + public int serializeSimplePart( byte[] data, int pos ) + { + LittleEndian.putShort(data, pos, getId()); + LittleEndian.putInt(data, pos + 2, complexData.length); + return 6; + } + + /** + * Serializes the complex part of this property + * + * @param data The data array to serialize to + * @param pos The offset within data to start serializing to. + * @return The number of bytes serialized. + */ + public int serializeComplexPart( byte[] data, int pos ) + { + System.arraycopy(complexData, 0, data, pos, complexData.length); + return complexData.length; + } + + /** + * Get the complex data value. + */ + public byte[] getComplexData() + { + return complexData; + } + + /** + * Determine whether this property is equal to another property. + * + * @param o The object to compare to. + * @return True if the objects are equal. + */ + public boolean equals( Object o ) + { + if ( this == o ) return true; + if ( !( o instanceof EscherComplexProperty ) ) return false; + + final EscherComplexProperty escherComplexProperty = (EscherComplexProperty) o; + + if ( !Arrays.equals( complexData, escherComplexProperty.complexData ) ) return false; + + return true; + } + + /** + * Caclulates the number of bytes required to serialize this property. + * + * @return Number of bytes + */ + public int getPropertySize() + { + return 6 + complexData.length; + } + + /** + * Calculates a hashcode for this property. + */ + public int hashCode() + { + return getId() * 11; + } + + /** + * Retrieves the string representation for this property. + */ + public String toString() + { + String dataStr; + ByteArrayOutputStream b = new ByteArrayOutputStream(); + try + { + HexDump.dump( this.complexData, 0, b, 0 ); + dataStr = b.toString(); + } + catch ( Exception e ) + { + dataStr = e.toString(); + } + finally + { + try + { + b.close(); + } + catch ( IOException e ) + { + e.printStackTrace(); + } + } + + return "propNum: " + getPropertyNumber() + + ", propName: " + EscherProperties.getPropertyName( getPropertyNumber() ) + + ", complex: " + isComplex() + + ", blipId: " + isBlipId() + + ", data: " + System.getProperty("line.separator") + dataStr; + } + +} diff --git a/src/java/org/apache/poi/ddf/EscherContainerRecord.java b/src/java/org/apache/poi/ddf/EscherContainerRecord.java new file mode 100644 index 0000000000..41c7a7ec9e --- /dev/null +++ b/src/java/org/apache/poi/ddf/EscherContainerRecord.java @@ -0,0 +1,168 @@ +package org.apache.poi.ddf; + +import org.apache.poi.util.HexDump; +import org.apache.poi.util.LittleEndian; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.io.PrintWriter; + +/** + * Escher container records store other escher records as children. + * The container records themselves never store any information beyond + * the standard header used by all escher records. This one record is + * used to represent many different types of records. + * + * @author Glen Stampoultzis + */ +public class EscherContainerRecord extends EscherRecord +{ + public static final short DGG_CONTAINER = (short)0xF000; + public static final short BSTORE_CONTAINER = (short)0xF001; + public static final short DG_CONTAINER = (short)0xF002; + public static final short SPGR_CONTAINER = (short)0xF003; + public static final short SP_CONTAINER = (short)0xF004; + public static final short SOLVER_CONTAINER = (short)0xF005; + + private List childRecords = new ArrayList(); + + public int fillFields( byte[] data, int offset, EscherRecordFactory recordFactory ) + { + int bytesRemaining = readHeader( data, offset ); + int bytesWritten = 8; + offset += 8; + while ( bytesRemaining > 0 && offset < data.length ) + { + EscherRecord child = recordFactory.createRecord(data, offset); + int childBytesWritten = child.fillFields( data, offset, recordFactory ); + bytesWritten += childBytesWritten; + offset += childBytesWritten; + bytesRemaining -= childBytesWritten; + getChildRecords().add( child ); + if (offset >= data.length && bytesRemaining > 0) + { + System.out.println("WARNING: " + bytesRemaining + " bytes remaining but no space left"); + } + } + return bytesWritten; + } + + public int serialize( int offset, byte[] data, EscherSerializationListener listener ) + { + listener.beforeRecordSerialize( offset, getRecordId(), this ); + + LittleEndian.putShort(data, offset, getOptions()); + LittleEndian.putShort(data, offset+2, getRecordId()); + int remainingBytes = 0; + for ( Iterator iterator = getChildRecords().iterator(); iterator.hasNext(); ) + { + EscherRecord r = (EscherRecord) iterator.next(); + remainingBytes += r.getRecordSize(); + } + LittleEndian.putInt(data, offset+4, remainingBytes); + int pos = offset+8; + for ( Iterator iterator = getChildRecords().iterator(); iterator.hasNext(); ) + { + EscherRecord r = (EscherRecord) iterator.next(); + pos += r.serialize(pos, data, listener ); + } + + listener.afterRecordSerialize( pos, getRecordId(), pos - offset, this ); + return pos - offset; + } + + public int getRecordSize() + { + int childRecordsSize = 0; + for ( Iterator iterator = getChildRecords().iterator(); iterator.hasNext(); ) + { + EscherRecord r = (EscherRecord) iterator.next(); + childRecordsSize += r.getRecordSize(); + } + return 8 + childRecordsSize; + } + + public List getChildRecords() + { + return childRecords; + } + + public void setChildRecords( List childRecords ) + { + this.childRecords = childRecords; + } + + public String getRecordName() + { + switch ((short)getRecordId()) + { + case DGG_CONTAINER: + return "DggContainer"; + case BSTORE_CONTAINER: + return "BStoreContainer"; + case DG_CONTAINER: + return "DgContainer"; + case SPGR_CONTAINER: + return "SpgrContainer"; + case SP_CONTAINER: + return "SpContainer"; + case SOLVER_CONTAINER: + return "SolverContainer"; + default: + return "Container 0x" + HexDump.toHex(getRecordId()); + } + } + + public void display( PrintWriter w, int indent ) + { + super.display( w, indent ); + for ( Iterator iterator = childRecords.iterator(); iterator.hasNext(); ) + { + EscherRecord escherRecord = (EscherRecord) iterator.next(); + escherRecord.display( w, indent + 1 ); + } + } + + public void addChildRecord( EscherRecord record ) + { + this.childRecords.add( record ); + } + + public String toString() + { + String nl = System.getProperty( "line.separator" ); + + StringBuffer children = new StringBuffer(); + if ( getChildRecords().size() > 0 ) + { + children.append( " children: " + nl ); + for ( Iterator iterator = getChildRecords().iterator(); iterator.hasNext(); ) + { + EscherRecord record = (EscherRecord) iterator.next(); + children.append( record.toString() ); +// children.append( nl ); + } + } + + return getClass().getName() + " (" + getRecordName() + "):" + nl + + " isContainer: " + isContainerRecord() + nl + + " options: 0x" + HexDump.toHex( getOptions() ) + nl + + " recordId: 0x" + HexDump.toHex( getRecordId() ) + nl + + " numchildren: " + getChildRecords().size() + nl + + children.toString(); + + } + + public EscherSpRecord getChildById( short recordId ) + { + for ( Iterator iterator = childRecords.iterator(); iterator.hasNext(); ) + { + EscherRecord escherRecord = (EscherRecord) iterator.next(); + if (escherRecord.getRecordId() == recordId) + return (EscherSpRecord) escherRecord; + } + return null; + } + +} diff --git a/src/java/org/apache/poi/ddf/EscherDgRecord.java b/src/java/org/apache/poi/ddf/EscherDgRecord.java new file mode 100644 index 0000000000..049bf383a6 --- /dev/null +++ b/src/java/org/apache/poi/ddf/EscherDgRecord.java @@ -0,0 +1,163 @@ +package org.apache.poi.ddf; + +import org.apache.poi.util.HexDump; +import org.apache.poi.util.LittleEndian; + +/** + * This record simply holds the number of shapes in the drawing group and the + * last shape id used for this drawing group. + * + * @author Glen Stampoultzis + */ +public class EscherDgRecord + extends EscherRecord +{ + public static final short RECORD_ID = (short) 0xF008; + public static final String RECORD_DESCRIPTION = "MsofbtDg"; + + private int field_1_numShapes; + private int field_2_lastMSOSPID; + + /** + * This method deserializes the record from a byte array. + * + * @param data The byte array containing the escher record information + * @param offset The starting offset into <code>data</code>. + * @param recordFactory May be null since this is not a container record. + * @return The number of bytes read from the byte array. + */ + public int fillFields( byte[] data, int offset, EscherRecordFactory recordFactory ) + { + int bytesRemaining = readHeader( data, offset ); + int pos = offset + 8; + int size = 0; + field_1_numShapes = LittleEndian.getInt( data, pos + size ); size += 4; + field_2_lastMSOSPID = LittleEndian.getInt( data, pos + size ); size += 4; +// bytesRemaining -= size; +// remainingData = new byte[bytesRemaining]; +// System.arraycopy( data, pos + size, remainingData, 0, bytesRemaining ); + return getRecordSize(); + } + + /** + * This method serializes this escher record into a byte array. + * + * @param offset The offset into <code>data</code> to start writing the record data to. + * @param data The byte array to serialize to. + * @param listener A listener to retrieve start and end callbacks. Use a <code>NullEscherSerailizationListener</code> to ignore these events. + * @return The number of bytes written. + * @see NullEscherSerializationListener + */ + public int serialize( int offset, byte[] data, EscherSerializationListener listener ) + { + listener.beforeRecordSerialize( offset, getRecordId(), this ); + + LittleEndian.putShort( data, offset, getOptions() ); + LittleEndian.putShort( data, offset + 2, getRecordId() ); + LittleEndian.putInt( data, offset + 4, 8 ); + LittleEndian.putInt( data, offset + 8, field_1_numShapes ); + LittleEndian.putInt( data, offset + 12, field_2_lastMSOSPID ); +// System.arraycopy( remainingData, 0, data, offset + 26, remainingData.length ); +// int pos = offset + 8 + 18 + remainingData.length; + + listener.afterRecordSerialize( offset + 16, getRecordId(), getRecordSize(), this ); + return getRecordSize(); + } + + /** + * Returns the number of bytes that are required to serialize this record. + * + * @return Number of bytes + */ + public int getRecordSize() + { + return 8 + 8; + } + + public short getRecordId() + { + return RECORD_ID; + } + + /** + * The short name for this record + */ + public String getRecordName() + { + return "Dg"; + } + + /** + * Returns the string representation of this record. + */ + public String toString() + { + String nl = System.getProperty("line.separator"); + +// String extraData; +// ByteArrayOutputStream b = new ByteArrayOutputStream(); +// try +// { +// HexDump.dump(this.remainingData, 0, b, 0); +// extraData = b.toString(); +// } +// catch ( Exception e ) +// { +// extraData = "error"; +// } + return getClass().getName() + ":" + nl + + " RecordId: 0x" + HexDump.toHex(RECORD_ID) + nl + + " Options: 0x" + HexDump.toHex(getOptions()) + nl + + " NumShapes: " + field_1_numShapes + nl + + " LastMSOSPID: " + field_2_lastMSOSPID + nl; + + } + + /** + * The number of shapes in this drawing group. + */ + public int getNumShapes() + { + return field_1_numShapes; + } + + /** + * The number of shapes in this drawing group. + */ + public void setNumShapes( int field_1_numShapes ) + { + this.field_1_numShapes = field_1_numShapes; + } + + /** + * The last shape id used in this drawing group. + */ + public int getLastMSOSPID() + { + return field_2_lastMSOSPID; + } + + /** + * The last shape id used in this drawing group. + */ + public void setLastMSOSPID( int field_2_lastMSOSPID ) + { + this.field_2_lastMSOSPID = field_2_lastMSOSPID; + } + + /** + * Gets the drawing group id for this record. This is encoded in the + * instance part of the option record. + * + * @return a drawing group id. + */ + public short getDrawingGroupId() + { + return (short) ( getOptions() >> 4 ); + } + + public void incrementShapeCount() + { + this.field_1_numShapes++; + } +} diff --git a/src/java/org/apache/poi/ddf/EscherDggRecord.java b/src/java/org/apache/poi/ddf/EscherDggRecord.java new file mode 100644 index 0000000000..1adcf82fc4 --- /dev/null +++ b/src/java/org/apache/poi/ddf/EscherDggRecord.java @@ -0,0 +1,241 @@ +package org.apache.poi.ddf; + +import org.apache.poi.util.HexDump; +import org.apache.poi.util.LittleEndian; +import org.apache.poi.hssf.record.RecordFormatException; + +import java.lang.reflect.Array; +import java.util.*; + +/** + * This record defines the drawing groups used for a particular sheet. + */ +public class EscherDggRecord + extends EscherRecord +{ + public static final short RECORD_ID = (short) 0xF006; + public static final String RECORD_DESCRIPTION = "MsofbtDgg"; + + private int field_1_shapeIdMax; +// private int field_2_numIdClusters; // for some reason the number of clusters is actually the real number + 1 + private int field_3_numShapesSaved; + private int field_4_drawingsSaved; + private FileIdCluster[] field_5_fileIdClusters; + + public static class FileIdCluster + { + public FileIdCluster( int drawingGroupId, int numShapeIdsUsed ) + { + this.field_1_drawingGroupId = drawingGroupId; + this.field_2_numShapeIdsUsed = numShapeIdsUsed; + } + + private int field_1_drawingGroupId; + private int field_2_numShapeIdsUsed; + + public int getDrawingGroupId() + { + return field_1_drawingGroupId; + } + + public int getNumShapeIdsUsed() + { + return field_2_numShapeIdsUsed; + } + + public void incrementShapeId( ) + { + this.field_2_numShapeIdsUsed++; + } + } + + /** + * This method deserializes the record from a byte array. + * + * @param data The byte array containing the escher record information + * @param offset The starting offset into <code>data</code>. + * @param recordFactory May be null since this is not a container record. + * @return The number of bytes read from the byte array. + */ + public int fillFields( byte[] data, int offset, EscherRecordFactory recordFactory ) + { + int bytesRemaining = readHeader( data, offset ); + int pos = offset + 8; + int size = 0; + field_1_shapeIdMax = LittleEndian.getInt( data, pos + size );size+=4; + int field_2_numIdClusters = LittleEndian.getInt( data, pos + size );size+=4; + field_3_numShapesSaved = LittleEndian.getInt( data, pos + size );size+=4; + field_4_drawingsSaved = LittleEndian.getInt( data, pos + size );size+=4; + field_5_fileIdClusters = new FileIdCluster[field_2_numIdClusters-1]; + for (int i = 0; i < field_2_numIdClusters-1; i++) + { + field_5_fileIdClusters[i] = new FileIdCluster(LittleEndian.getInt( data, pos + size ), LittleEndian.getInt( data, pos + size + 4 )); + size += 8; + } + bytesRemaining -= size; + if (bytesRemaining != 0) + throw new RecordFormatException("Expecting no remaining data but got " + bytesRemaining + " byte(s)."); + return 8 + size + bytesRemaining; + } + + /** + * This method serializes this escher record into a byte array. + * + * @param offset The offset into <code>data</code> to start writing the record data to. + * @param data The byte array to serialize to. + * @param listener A listener to retrieve start and end callbacks. Use a <code>NullEscherSerailizationListener</code> to ignore these events. + * @return The number of bytes written. + * + * @see NullEscherSerializationListener + */ + public int serialize( int offset, byte[] data, EscherSerializationListener listener ) + { + listener.beforeRecordSerialize( offset, getRecordId(), this ); + + int pos = offset; + LittleEndian.putShort( data, pos, getOptions() ); pos += 2; + LittleEndian.putShort( data, pos, getRecordId() ); pos += 2; + int remainingBytes = getRecordSize() - 8; + LittleEndian.putInt( data, pos, remainingBytes ); pos += 4; + LittleEndian.putInt( data, pos, field_1_shapeIdMax ); pos += 4; + LittleEndian.putInt( data, pos, getNumIdClusters() ); pos += 4; + LittleEndian.putInt( data, pos, field_3_numShapesSaved ); pos += 4; + LittleEndian.putInt( data, pos, field_4_drawingsSaved ); pos += 4; + for ( int i = 0; i < field_5_fileIdClusters.length; i++ ) + { + LittleEndian.putInt( data, pos, field_5_fileIdClusters[i].field_1_drawingGroupId ); pos += 4; + LittleEndian.putInt( data, pos, field_5_fileIdClusters[i].field_2_numShapeIdsUsed ); pos += 4; + } + + listener.afterRecordSerialize( pos, getRecordId(), getRecordSize(), this ); + return getRecordSize(); + } + + /** + * Returns the number of bytes that are required to serialize this record. + * + * @return Number of bytes + */ + public int getRecordSize() + { + return 8 + 16 + (8 * field_5_fileIdClusters.length); + } + + public short getRecordId() + { + return RECORD_ID; + } + + /** + * The short name for this record + */ + public String getRecordName() + { + return "Dgg"; + } + + public String toString() + { + String nl = System.getProperty("line.separator"); + +// String extraData; +// ByteArrayOutputStream b = new ByteArrayOutputStream(); +// try +// { +// HexDump.dump(this.remainingData, 0, b, 0); +// extraData = b.toString(); +// } +// catch ( Exception e ) +// { +// extraData = "error"; +// } + StringBuffer field_5_string = new StringBuffer(); + for ( int i = 0; i < field_5_fileIdClusters.length; i++ ) + { + field_5_string.append(" DrawingGroupId").append(i+1).append(": "); + field_5_string.append(field_5_fileIdClusters[i].field_1_drawingGroupId); + field_5_string.append(nl); + field_5_string.append(" NumShapeIdsUsed").append(i+1).append(": "); + field_5_string.append(field_5_fileIdClusters[i].field_2_numShapeIdsUsed); + field_5_string.append(nl); + } + return getClass().getName() + ":" + nl + + " RecordId: 0x" + HexDump.toHex(RECORD_ID) + nl + + " Options: 0x" + HexDump.toHex(getOptions()) + nl + + " ShapeIdMax: " + field_1_shapeIdMax + nl + + " NumIdClusters: " + getNumIdClusters() + nl + + " NumShapesSaved: " + field_3_numShapesSaved + nl + + " DrawingsSaved: " + field_4_drawingsSaved + nl + + "" + field_5_string.toString(); + + } + + public int getShapeIdMax() + { + return field_1_shapeIdMax; + } + + /** + * The maximum is actually the next available. shape id. + */ + public void setShapeIdMax( int field_1_shapeIdMax ) + { + this.field_1_shapeIdMax = field_1_shapeIdMax; + } + + public int getNumIdClusters() + { + return field_5_fileIdClusters.length + 1; + } + + public int getNumShapesSaved() + { + return field_3_numShapesSaved; + } + + public void setNumShapesSaved( int field_3_numShapesSaved ) + { + this.field_3_numShapesSaved = field_3_numShapesSaved; + } + + public int getDrawingsSaved() + { + return field_4_drawingsSaved; + } + + public void setDrawingsSaved( int field_4_drawingsSaved ) + { + this.field_4_drawingsSaved = field_4_drawingsSaved; + } + + public FileIdCluster[] getFileIdClusters() + { + return field_5_fileIdClusters; + } + + public void setFileIdClusters( FileIdCluster[] field_5_fileIdClusters ) + { + this.field_5_fileIdClusters = field_5_fileIdClusters; + } + + public void addCluster( int dgId, int numShapedUsed ) + { + List clusters = new ArrayList(Arrays.asList(field_5_fileIdClusters)); + clusters.add(new FileIdCluster(dgId, numShapedUsed)); + Collections.sort(clusters, new Comparator() + { + public int compare( Object o1, Object o2 ) + { + FileIdCluster f1 = (FileIdCluster) o1; + FileIdCluster f2 = (FileIdCluster) o2; + if (f1.getDrawingGroupId() == f2.getDrawingGroupId()) + return 0; + if (f1.getDrawingGroupId() < f2.getDrawingGroupId()) + return -1; + else + return +1; + } + } ); + field_5_fileIdClusters = (FileIdCluster[]) clusters.toArray( new FileIdCluster[clusters.size()] ); + } +} diff --git a/src/java/org/apache/poi/ddf/EscherDump.java b/src/java/org/apache/poi/ddf/EscherDump.java new file mode 100644 index 0000000000..7930486964 --- /dev/null +++ b/src/java/org/apache/poi/ddf/EscherDump.java @@ -0,0 +1,990 @@ +/* ==================================================================== + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2003 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Apache" and "Apache Software Foundation" and + * "Apache POI" must not be used to endorse or promote products + * derived from this software without prior written permission. For + * written permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * "Apache POI", nor may "Apache" appear in their name, without + * prior written permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + */ + +package org.apache.poi.ddf; + +import org.apache.poi.util.HexDump; +import org.apache.poi.util.HexRead; +import org.apache.poi.util.LittleEndian; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; +import java.util.zip.InflaterInputStream; + +/** + * Used to dump the contents of escher records to a PrintStream. + * + * @author Glen Stampoultzis (glens at apache.org) + */ +public class EscherDump +{ + + public EscherDump() + { + } + + /** + * Decodes the escher stream from a byte array and dumps the results to + * a print stream. + * + * @param data The data array containing the escher records. + * @param offset The starting offset within the data array. + * @param size The number of bytes to read. + * @param out The output stream to write the results to. + * + */ + public void dump( byte[] data, int offset, int size, PrintStream out ) throws IOException, LittleEndian.BufferUnderrunException + { + EscherRecordFactory recordFactory = new DefaultEscherRecordFactory(); + int pos = offset; + while ( pos < offset + size ) + { + EscherRecord r = recordFactory.createRecord(data, pos); + int bytesRead = r.fillFields(data, pos, recordFactory ); + System.out.println( r.toString() ); + pos += bytesRead; + } + } + + /** + * This version of dump is a translation from the open office escher dump routine. + * + * @param maxLength The number of bytes to read + * @param in An input stream to read from. + * @param out An output stream to write to. + */ + public void dumpOld( long maxLength, InputStream in, PrintStream out ) throws IOException, LittleEndian.BufferUnderrunException + { + long remainingBytes = maxLength; + short options; // 4 bits for the version and 12 bits for the instance + short recordId; + int recordBytesRemaining; // including enclosing records + StringBuffer stringBuf = new StringBuffer(); + short nDumpSize; + String recordName; + + boolean atEOF = false; + + while ( !atEOF && ( remainingBytes > 0 ) ) + { + stringBuf = new StringBuffer(); + options = LittleEndian.readShort( in ); + recordId = LittleEndian.readShort( in ); + recordBytesRemaining = LittleEndian.readInt( in ); + + remainingBytes -= 2 + 2 + 4; + + switch ( recordId ) + { + case (short) 0xF000: + recordName = "MsofbtDggContainer"; + break; + case (short) 0xF006: + recordName = "MsofbtDgg"; + break; + case (short) 0xF016: + recordName = "MsofbtCLSID"; + break; + case (short) 0xF00B: + recordName = "MsofbtOPT"; + break; + case (short) 0xF11A: + recordName = "MsofbtColorMRU"; + break; + case (short) 0xF11E: + recordName = "MsofbtSplitMenuColors"; + break; + case (short) 0xF001: + recordName = "MsofbtBstoreContainer"; + break; + case (short) 0xF007: + recordName = "MsofbtBSE"; + break; + case (short) 0xF002: + recordName = "MsofbtDgContainer"; + break; + case (short) 0xF008: + recordName = "MsofbtDg"; + break; + case (short) 0xF118: + recordName = "MsofbtRegroupItem"; + break; + case (short) 0xF120: + recordName = "MsofbtColorScheme"; + break; + case (short) 0xF003: + recordName = "MsofbtSpgrContainer"; + break; + case (short) 0xF004: + recordName = "MsofbtSpContainer"; + break; + case (short) 0xF009: + recordName = "MsofbtSpgr"; + break; + case (short) 0xF00A: + recordName = "MsofbtSp"; + break; + case (short) 0xF00C: + recordName = "MsofbtTextbox"; + break; + case (short) 0xF00D: + recordName = "MsofbtClientTextbox"; + break; + case (short) 0xF00E: + recordName = "MsofbtAnchor"; + break; + case (short) 0xF00F: + recordName = "MsofbtChildAnchor"; + break; + case (short) 0xF010: + recordName = "MsofbtClientAnchor"; + break; + case (short) 0xF011: + recordName = "MsofbtClientData"; + break; + case (short) 0xF11F: + recordName = "MsofbtOleObject"; + break; + case (short) 0xF11D: + recordName = "MsofbtDeletedPspl"; + break; + case (short) 0xF005: + recordName = "MsofbtSolverContainer"; + break; + case (short) 0xF012: + recordName = "MsofbtConnectorRule"; + break; + case (short) 0xF013: + recordName = "MsofbtAlignRule"; + break; + case (short) 0xF014: + recordName = "MsofbtArcRule"; + break; + case (short) 0xF015: + recordName = "MsofbtClientRule"; + break; + case (short) 0xF017: + recordName = "MsofbtCalloutRule"; + break; + case (short) 0xF119: + recordName = "MsofbtSelection"; + break; + case (short) 0xF122: + recordName = "MsofbtUDefProp"; + break; + default: + if ( recordId >= (short) 0xF018 && recordId <= (short) 0xF117 ) + recordName = "MsofbtBLIP"; + else if ( ( options & (short) 0x000F ) == (short) 0x000F ) + recordName = "UNKNOWN container"; + else + recordName = "UNKNOWN ID"; + } + + stringBuf.append( " " ); + stringBuf.append( HexDump.toHex( recordId ) ); + stringBuf.append( " " ).append( recordName ).append( " [" ); + stringBuf.append( HexDump.toHex( options ) ); + stringBuf.append( ',' ); + stringBuf.append( HexDump.toHex( recordBytesRemaining ) ); + stringBuf.append( "] instance: " ); + stringBuf.append( HexDump.toHex( ( (short) ( options >> 4 ) ) ) ); + out.println( stringBuf.toString() ); + + + if ( recordId == (short) 0xF007 && 36 <= remainingBytes && 36 <= recordBytesRemaining ) + { // BSE, FBSE + // ULONG nP = pIn->GetRecPos(); + + byte n8; + // short n16; + // int n32; + + stringBuf = new StringBuffer( " btWin32: " ); + n8 = (byte) in.read(); + stringBuf.append( HexDump.toHex( n8 ) ); + stringBuf.append( getBlipType( n8 ) ); + stringBuf.append( " btMacOS: " ); + n8 = (byte) in.read(); + stringBuf.append( HexDump.toHex( n8 ) ); + stringBuf.append( getBlipType( n8 ) ); + out.println( stringBuf.toString() ); + + out.println( " rgbUid:" ); + HexDump.dump( in, out, 0, 16 ); + + out.print( " tag: " ); + outHex( 2, in, out ); + out.println(); + out.print( " size: " ); + outHex( 4, in, out ); + out.println(); + out.print( " cRef: " ); + outHex( 4, in, out ); + out.println(); + out.print( " offs: " ); + outHex( 4, in, out ); + out.println(); + out.print( " usage: " ); + outHex( 1, in, out ); + out.println(); + out.print( " cbName: " ); + outHex( 1, in, out ); + out.println(); + out.print( " unused2: " ); + outHex( 1, in, out ); + out.println(); + out.print( " unused3: " ); + outHex( 1, in, out ); + out.println(); + + // subtract the number of bytes we've read + remainingBytes -= 36; + //n -= pIn->GetRecPos() - nP; + recordBytesRemaining = 0; // loop to MsofbtBLIP + } + else if ( recordId == (short) 0xF010 && 0x12 <= remainingBytes && 0x12 <= recordBytesRemaining ) + { // ClientAnchor + //ULONG nP = pIn->GetRecPos(); + // short n16; + + out.print( " Flag: " ); + outHex( 2, in, out ); + out.println(); + out.print( " Col1: " ); + outHex( 2, in, out ); + out.print( " dX1: " ); + outHex( 2, in, out ); + out.print( " Row1: " ); + outHex( 2, in, out ); + out.print( " dY1: " ); + outHex( 2, in, out ); + out.println(); + out.print( " Col2: " ); + outHex( 2, in, out ); + out.print( " dX2: " ); + outHex( 2, in, out ); + out.print( " Row2: " ); + outHex( 2, in, out ); + out.print( " dY2: " ); + outHex( 2, in, out ); + out.println(); + + remainingBytes -= 18; + recordBytesRemaining -= 18; + + } + else if ( recordId == (short) 0xF00B || recordId == (short) 0xF122 ) + { // OPT + int nComplex = 0; + out.println( " PROPID VALUE" ); + while ( recordBytesRemaining >= 6 + nComplex && remainingBytes >= 6 + nComplex ) + { + short n16; + int n32; + n16 = LittleEndian.readShort( in ); + n32 = LittleEndian.readInt( in ); + + recordBytesRemaining -= 6; + remainingBytes -= 6; + out.print( " " ); + out.print( HexDump.toHex( n16 ) ); + out.print( " (" ); + int propertyId = n16 & (short) 0x3FFF; + out.print( " " + propertyId ); + if ( ( n16 & (short) 0x8000 ) == 0 ) + { + if ( ( n16 & (short) 0x4000 ) != 0 ) + out.print( ", fBlipID" ); + out.print( ") " ); + + out.print( HexDump.toHex( n32 ) ); + + if ( ( n16 & (short) 0x4000 ) == 0 ) + { + out.print( " (" ); + out.print( dec1616( n32 ) ); + out.print( ')' ); + out.print( " {" + propName( (short)propertyId ) + "}" ); + } + out.println(); + } + else + { + out.print( ", fComplex) " ); + out.print( HexDump.toHex( n32 ) ); + out.print( " - Complex prop len" ); + out.println( " {" + propName( (short)propertyId ) + "}" ); + + nComplex += n32; + } + + } + // complex property data + while ( ( nComplex & remainingBytes ) > 0 ) + { + nDumpSize = ( nComplex > (int) remainingBytes ) ? (short) remainingBytes : (short) nComplex; + HexDump.dump( in, out, 0, nDumpSize ); + nComplex -= nDumpSize; + recordBytesRemaining -= nDumpSize; + remainingBytes -= nDumpSize; + } + } + else if ( recordId == (short) 0xF012 ) + { + out.print( " Connector rule: " ); + out.print( LittleEndian.readInt( in ) ); + out.print( " ShapeID A: " ); + out.print( LittleEndian.readInt( in ) ); + out.print( " ShapeID B: " ); + out.print( LittleEndian.readInt( in ) ); + out.print( " ShapeID connector: " ); + out.print( LittleEndian.readInt( in ) ); + out.print( " Connect pt A: " ); + out.print( LittleEndian.readInt( in ) ); + out.print( " Connect pt B: " ); + out.println( LittleEndian.readInt( in ) ); + + recordBytesRemaining -= 24; + remainingBytes -= 24; + } + else if ( recordId >= (short) 0xF018 && recordId < (short) 0xF117 ) + { + out.println( " Secondary UID: " ); + HexDump.dump( in, out, 0, 16 ); + out.println( " Cache of size: " + HexDump.toHex( LittleEndian.readInt( in ) ) ); + out.println( " Boundary top: " + HexDump.toHex( LittleEndian.readInt( in ) ) ); + out.println( " Boundary left: " + HexDump.toHex( LittleEndian.readInt( in ) ) ); + out.println( " Boundary width: " + HexDump.toHex( LittleEndian.readInt( in ) ) ); + out.println( " Boundary height: " + HexDump.toHex( LittleEndian.readInt( in ) ) ); + out.println( " X: " + HexDump.toHex( LittleEndian.readInt( in ) ) ); + out.println( " Y: " + HexDump.toHex( LittleEndian.readInt( in ) ) ); + out.println( " Cache of saved size: " + HexDump.toHex( LittleEndian.readInt( in ) ) ); + out.println( " Compression Flag: " + HexDump.toHex( (byte) in.read() ) ); + out.println( " Filter: " + HexDump.toHex( (byte) in.read() ) ); + out.println( " Data (after decompression): " ); + + recordBytesRemaining -= 34 + 16; + remainingBytes -= 34 + 16; + + nDumpSize = ( recordBytesRemaining > (int) remainingBytes ) ? (short) remainingBytes : (short) recordBytesRemaining; + + + byte[] buf = new byte[nDumpSize]; + int read = in.read( buf ); + while ( read != -1 && read < nDumpSize ) + read += in.read( buf, read, buf.length ); + ByteArrayInputStream bin = new ByteArrayInputStream( buf ); + + InputStream in1 = new InflaterInputStream( bin ); + int bytesToDump = -1; + HexDump.dump( in1, out, 0, bytesToDump ); + + recordBytesRemaining -= nDumpSize; + remainingBytes -= nDumpSize; + + } + + boolean isContainer = ( options & (short) 0x000F ) == (short) 0x000F; + if ( isContainer && remainingBytes >= 0 ) + { // Container + if ( recordBytesRemaining <= (int) remainingBytes ) + out.println( " completed within" ); + else + out.println( " continued elsewhere" ); + } + else if ( remainingBytes >= 0 ) + // -> 0x0000 ... 0x0FFF + { + nDumpSize = ( recordBytesRemaining > (int) remainingBytes ) ? (short) remainingBytes : (short) recordBytesRemaining; + + if ( nDumpSize != 0 ) + { + HexDump.dump( in, out, 0, nDumpSize ); + remainingBytes -= nDumpSize; + } + } + else + out.println( " >> OVERRUN <<" ); + } + + } + + /** + * Returns a property name given a property id. This is used only by the + * old escher dump routine. + * + * @param propertyId The property number for the name + * @return A descriptive name. + */ + private String propName( short propertyId ) + { + class PropName { + public PropName( int id, String name ) + { + this.id = id; + this.name = name; + } + + int id; + String name; + } + + final PropName[] props = new PropName[] { + new PropName(4, "transform.rotation"), + new PropName(119, "protection.lockrotation"), + new PropName(120, "protection.lockaspectratio"), + new PropName(121, "protection.lockposition"), + new PropName(122, "protection.lockagainstselect"), + new PropName(123, "protection.lockcropping"), + new PropName(124, "protection.lockvertices"), + new PropName(125, "protection.locktext"), + new PropName(126, "protection.lockadjusthandles"), + new PropName(127, "protection.lockagainstgrouping"), + new PropName(128, "text.textid"), + new PropName(129, "text.textleft"), + new PropName(130, "text.texttop"), + new PropName(131, "text.textright"), + new PropName(132, "text.textbottom"), + new PropName(133, "text.wraptext"), + new PropName(134, "text.scaletext"), + new PropName(135, "text.anchortext"), + new PropName(136, "text.textflow"), + new PropName(137, "text.fontrotation"), + new PropName(138, "text.idofnextshape"), + new PropName(139, "text.bidir"), + new PropName(187, "text.singleclickselects"), + new PropName(188, "text.usehostmargins"), + new PropName(189, "text.rotatetextwithshape"), + new PropName(190, "text.sizeshapetofittext"), + new PropName(191, "text.sizetexttofitshape"), + new PropName(192, "geotext.unicode"), + new PropName(193, "geotext.rtftext"), + new PropName(194, "geotext.alignmentoncurve"), + new PropName(195, "geotext.defaultpointsize"), + new PropName(196, "geotext.textspacing"), + new PropName(197, "geotext.fontfamilyname"), + new PropName(240, "geotext.reverseroworder"), + new PropName(241, "geotext.hastexteffect"), + new PropName(242, "geotext.rotatecharacters"), + new PropName(243, "geotext.kerncharacters"), + new PropName(244, "geotext.tightortrack"), + new PropName(245, "geotext.stretchtofitshape"), + new PropName(246, "geotext.charboundingbox"), + new PropName(247, "geotext.scaletextonpath"), + new PropName(248, "geotext.stretchcharheight"), + new PropName(249, "geotext.nomeasurealongpath"), + new PropName(250, "geotext.boldfont"), + new PropName(251, "geotext.italicfont"), + new PropName(252, "geotext.underlinefont"), + new PropName(253, "geotext.shadowfont"), + new PropName(254, "geotext.smallcapsfont"), + new PropName(255, "geotext.strikethroughfont"), + new PropName(256, "blip.cropfromtop"), + new PropName(257, "blip.cropfrombottom"), + new PropName(258, "blip.cropfromleft"), + new PropName(259, "blip.cropfromright"), + new PropName(260, "blip.bliptodisplay"), + new PropName(261, "blip.blipfilename"), + new PropName(262, "blip.blipflags"), + new PropName(263, "blip.transparentcolor"), + new PropName(264, "blip.contrastsetting"), + new PropName(265, "blip.brightnesssetting"), + new PropName(266, "blip.gamma"), + new PropName(267, "blip.pictureid"), + new PropName(268, "blip.doublemod"), + new PropName(269, "blip.picturefillmod"), + new PropName(270, "blip.pictureline"), + new PropName(271, "blip.printblip"), + new PropName(272, "blip.printblipfilename"), + new PropName(273, "blip.printflags"), + new PropName(316, "blip.nohittestpicture"), + new PropName(317, "blip.picturegray"), + new PropName(318, "blip.picturebilevel"), + new PropName(319, "blip.pictureactive"), + new PropName(320, "geometry.left"), + new PropName(321, "geometry.top"), + new PropName(322, "geometry.right"), + new PropName(323, "geometry.bottom"), + new PropName(324, "geometry.shapepath"), + new PropName(325, "geometry.vertices"), + new PropName(326, "geometry.segmentinfo"), + new PropName(327, "geometry.adjustvalue"), + new PropName(328, "geometry.adjust2value"), + new PropName(329, "geometry.adjust3value"), + new PropName(330, "geometry.adjust4value"), + new PropName(331, "geometry.adjust5value"), + new PropName(332, "geometry.adjust6value"), + new PropName(333, "geometry.adjust7value"), + new PropName(334, "geometry.adjust8value"), + new PropName(335, "geometry.adjust9value"), + new PropName(336, "geometry.adjust10value"), + new PropName(378, "geometry.shadowOK"), + new PropName(379, "geometry.3dok"), + new PropName(380, "geometry.lineok"), + new PropName(381, "geometry.geotextok"), + new PropName(382, "geometry.fillshadeshapeok"), + new PropName(383, "geometry.fillok"), + new PropName(384, "fill.filltype"), + new PropName(385, "fill.fillcolor"), + new PropName(386, "fill.fillopacity"), + new PropName(387, "fill.fillbackcolor"), + new PropName(388, "fill.backopacity"), + new PropName(389, "fill.crmod"), + new PropName(390, "fill.patterntexture"), + new PropName(391, "fill.blipfilename"), + new PropName(392, "fill.blipflags"), + new PropName(393, "fill.width"), + new PropName(394, "fill.height"), + new PropName(395, "fill.angle"), + new PropName(396, "fill.focus"), + new PropName(397, "fill.toleft"), + new PropName(398, "fill.totop"), + new PropName(399, "fill.toright"), + new PropName(400, "fill.tobottom"), + new PropName(401, "fill.rectleft"), + new PropName(402, "fill.recttop"), + new PropName(403, "fill.rectright"), + new PropName(404, "fill.rectbottom"), + new PropName(405, "fill.dztype"), + new PropName(406, "fill.shadepreset"), + new PropName(407, "fill.shadecolors"), + new PropName(408, "fill.originx"), + new PropName(409, "fill.originy"), + new PropName(410, "fill.shapeoriginx"), + new PropName(411, "fill.shapeoriginy"), + new PropName(412, "fill.shadetype"), + new PropName(443, "fill.filled"), + new PropName(444, "fill.hittestfill"), + new PropName(445, "fill.shape"), + new PropName(446, "fill.userect"), + new PropName(447, "fill.nofillhittest"), + new PropName(448, "linestyle.color"), + new PropName(449, "linestyle.opacity"), + new PropName(450, "linestyle.backcolor"), + new PropName(451, "linestyle.crmod"), + new PropName(452, "linestyle.linetype"), + new PropName(453, "linestyle.fillblip"), + new PropName(454, "linestyle.fillblipname"), + new PropName(455, "linestyle.fillblipflags"), + new PropName(456, "linestyle.fillwidth"), + new PropName(457, "linestyle.fillheight"), + new PropName(458, "linestyle.filldztype"), + new PropName(459, "linestyle.linewidth"), + new PropName(460, "linestyle.linemiterlimit"), + new PropName(461, "linestyle.linestyle"), + new PropName(462, "linestyle.linedashing"), + new PropName(463, "linestyle.linedashstyle"), + new PropName(464, "linestyle.linestartarrowhead"), + new PropName(465, "linestyle.lineendarrowhead"), + new PropName(466, "linestyle.linestartarrowwidth"), + new PropName(467, "linestyle.lineestartarrowlength"), + new PropName(468, "linestyle.lineendarrowwidth"), + new PropName(469, "linestyle.lineendarrowlength"), + new PropName(470, "linestyle.linejoinstyle"), + new PropName(471, "linestyle.lineendcapstyle"), + new PropName(507, "linestyle.arrowheadsok"), + new PropName(508, "linestyle.anyline"), + new PropName(509, "linestyle.hitlinetest"), + new PropName(510, "linestyle.linefillshape"), + new PropName(511, "linestyle.nolinedrawdash"), + new PropName(512, "shadowstyle.type"), + new PropName(513, "shadowstyle.color"), + new PropName(514, "shadowstyle.highlight"), + new PropName(515, "shadowstyle.crmod"), + new PropName(516, "shadowstyle.opacity"), + new PropName(517, "shadowstyle.offsetx"), + new PropName(518, "shadowstyle.offsety"), + new PropName(519, "shadowstyle.secondoffsetx"), + new PropName(520, "shadowstyle.secondoffsety"), + new PropName(521, "shadowstyle.scalextox"), + new PropName(522, "shadowstyle.scaleytox"), + new PropName(523, "shadowstyle.scalextoy"), + new PropName(524, "shadowstyle.scaleytoy"), + new PropName(525, "shadowstyle.perspectivex"), + new PropName(526, "shadowstyle.perspectivey"), + new PropName(527, "shadowstyle.weight"), + new PropName(528, "shadowstyle.originx"), + new PropName(529, "shadowstyle.originy"), + new PropName(574, "shadowstyle.shadow"), + new PropName(575, "shadowstyle.shadowobsured"), + new PropName(576, "perspective.type"), + new PropName(577, "perspective.offsetx"), + new PropName(578, "perspective.offsety"), + new PropName(579, "perspective.scalextox"), + new PropName(580, "perspective.scaleytox"), + new PropName(581, "perspective.scalextoy"), + new PropName(582, "perspective.scaleytox"), + new PropName(583, "perspective.perspectivex"), + new PropName(584, "perspective.perspectivey"), + new PropName(585, "perspective.weight"), + new PropName(586, "perspective.originx"), + new PropName(587, "perspective.originy"), + new PropName(639, "perspective.perspectiveon"), + new PropName(640, "3d.specularamount"), + new PropName(661, "3d.diffuseamount"), + new PropName(662, "3d.shininess"), + new PropName(663, "3d.edgethickness"), + new PropName(664, "3d.extrudeforward"), + new PropName(665, "3d.extrudebackward"), + new PropName(666, "3d.extrudeplane"), + new PropName(667, "3d.extrusioncolor"), + new PropName(648, "3d.crmod"), + new PropName(700, "3d.3deffect"), + new PropName(701, "3d.metallic"), + new PropName(702, "3d.useextrusioncolor"), + new PropName(703, "3d.lightface"), + new PropName(704, "3dstyle.yrotationangle"), + new PropName(705, "3dstyle.xrotationangle"), + new PropName(706, "3dstyle.rotationaxisx"), + new PropName(707, "3dstyle.rotationaxisy"), + new PropName(708, "3dstyle.rotationaxisz"), + new PropName(709, "3dstyle.rotationangle"), + new PropName(710, "3dstyle.rotationcenterx"), + new PropName(711, "3dstyle.rotationcentery"), + new PropName(712, "3dstyle.rotationcenterz"), + new PropName(713, "3dstyle.rendermode"), + new PropName(714, "3dstyle.tolerance"), + new PropName(715, "3dstyle.xviewpoint"), + new PropName(716, "3dstyle.yviewpoint"), + new PropName(717, "3dstyle.zviewpoint"), + new PropName(718, "3dstyle.originx"), + new PropName(719, "3dstyle.originy"), + new PropName(720, "3dstyle.skewangle"), + new PropName(721, "3dstyle.skewamount"), + new PropName(722, "3dstyle.ambientintensity"), + new PropName(723, "3dstyle.keyx"), + new PropName(724, "3dstyle.keyy"), + new PropName(725, "3dstyle.keyz"), + new PropName(726, "3dstyle.keyintensity"), + new PropName(727, "3dstyle.fillx"), + new PropName(728, "3dstyle.filly"), + new PropName(729, "3dstyle.fillz"), + new PropName(730, "3dstyle.fillintensity"), + new PropName(763, "3dstyle.constrainrotation"), + new PropName(764, "3dstyle.rotationcenterauto"), + new PropName(765, "3dstyle.parallel"), + new PropName(766, "3dstyle.keyharsh"), + new PropName(767, "3dstyle.fillharsh"), + new PropName(769, "shape.master"), + new PropName(771, "shape.connectorstyle"), + new PropName(772, "shape.blackandwhitesettings"), + new PropName(773, "shape.wmodepurebw"), + new PropName(774, "shape.wmodebw"), + new PropName(826, "shape.oleicon"), + new PropName(827, "shape.preferrelativeresize"), + new PropName(828, "shape.lockshapetype"), + new PropName(830, "shape.deleteattachedobject"), + new PropName(831, "shape.backgroundshape"), + new PropName(832, "callout.callouttype"), + new PropName(833, "callout.xycalloutgap"), + new PropName(834, "callout.calloutangle"), + new PropName(835, "callout.calloutdroptype"), + new PropName(836, "callout.calloutdropspecified"), + new PropName(837, "callout.calloutlengthspecified"), + new PropName(889, "callout.iscallout"), + new PropName(890, "callout.calloutaccentbar"), + new PropName(891, "callout.callouttextborder"), + new PropName(892, "callout.calloutminusx"), + new PropName(893, "callout.calloutminusy"), + new PropName(894, "callout.dropauto"), + new PropName(895, "callout.lengthspecified"), + new PropName(896, "groupshape.shapename"), + new PropName(897, "groupshape.description"), + new PropName(898, "groupshape.hyperlink"), + new PropName(899, "groupshape.wrappolygonvertices"), + new PropName(900, "groupshape.wrapdistleft"), + new PropName(901, "groupshape.wrapdisttop"), + new PropName(902, "groupshape.wrapdistright"), + new PropName(903, "groupshape.wrapdistbottom"), + new PropName(904, "groupshape.regroupid"), + new PropName(953, "groupshape.editedwrap"), + new PropName(954, "groupshape.behinddocument"), + new PropName(955, "groupshape.ondblclicknotify"), + new PropName(956, "groupshape.isbutton"), + new PropName(957, "groupshape.1dadjustment"), + new PropName(958, "groupshape.hidden"), + new PropName(959, "groupshape.print"), + }; + + for ( int i = 0; i < props.length; i++ ) + { + if (props[i].id == propertyId) + { + return props[i].name; + } + } + + return "unknown property"; + } + + /** + * Returns the blip description given a blip id. + * + * @param b blip id + * @return A description. + */ + private String getBlipType( byte b ) + { + switch ( b ) + { + case 0: + return " ERROR"; + case 1: + return " UNKNOWN"; + case 2: + return " EMF"; + case 3: + return " WMF"; + case 4: + return " PICT"; + case 5: + return " JPEG"; + case 6: + return " PNG"; + case 7: + return " DIB"; + default: + if ( b < 32 ) + return " NotKnown"; + else + return " Client"; + } + } + + /** + * Straight conversion from OO. Converts a type of float. + */ + private String dec1616( int n32 ) + { + String result = ""; + result += (short) ( n32 >> 16 ); + result += '.'; + result += (short) ( n32 & (short) 0xFFFF ); + return result; + } + + /** + * Dumps out a hex value by reading from a input stream. + * + * @param bytes How many bytes this hex value consists of. + * @param in The stream to read the hex value from. + * @param out The stream to write the nicely formatted hex value to. + */ + private void outHex( int bytes, InputStream in, PrintStream out ) throws IOException, LittleEndian.BufferUnderrunException + { + switch ( bytes ) + { + case 1: + out.print( HexDump.toHex( (byte) in.read() ) ); + break; + case 2: + out.print( HexDump.toHex( LittleEndian.readShort( in ) ) ); + break; + case 4: + out.print( HexDump.toHex( LittleEndian.readInt( in ) ) ); + break; + default: + throw new IOException( "Unable to output variable of that width" ); + } + } + + /** + * A simple test stub. + */ + public static void main( String[] args ) throws IOException + { + String dump = + "0F 00 00 F0 89 07 00 00 00 00 06 F0 18 00 00 00 " + + "05 04 00 00 02 00 00 00 05 00 00 00 01 00 00 00 " + + "01 00 00 00 05 00 00 00 4F 00 01 F0 2F 07 00 00 " + + "42 00 07 F0 B7 01 00 00 03 04 3F 14 AE 6B 0F 65 " + + "B0 48 BF 5E 94 63 80 E8 91 73 FF 00 93 01 00 00 " + + "01 00 00 00 00 00 00 00 00 00 FF FF 20 54 1C F0 " + + "8B 01 00 00 3F 14 AE 6B 0F 65 B0 48 BF 5E 94 63 " + + "80 E8 91 73 92 0E 00 00 00 00 00 00 00 00 00 00 " + + "D1 07 00 00 DD 05 00 00 4A AD 6F 00 8A C5 53 00 " + + "59 01 00 00 00 FE 78 9C E3 9B C4 00 04 AC 77 D9 " + + "2F 32 08 32 FD E7 61 F8 FF 0F C8 FD 05 C5 30 19 " + + "10 90 63 90 FA 0F 06 0C 8C 0C 5C 70 19 43 30 EB " + + "0E FB 05 86 85 0C DB 18 58 80 72 8C 70 16 0B 83 " + + "05 56 51 29 88 C9 60 D9 69 0C 6C 20 26 23 03 C8 " + + "74 B0 A8 0E 03 07 FB 45 56 C7 A2 CC C4 1C 06 66 " + + "A0 0D 2C 40 39 5E 86 4C 06 3D A0 4E 10 D0 60 D9 " + + "C8 58 CC E8 CF B0 80 61 3A 8A 7E 0D C6 23 AC 4F " + + "E0 E2 98 B6 12 2B 06 73 9D 12 E3 52 56 59 F6 08 " + + "8A CC 52 66 A3 50 FF 96 2B 94 E9 DF 4C A1 FE 2D " + + "3A 03 AB 9F 81 C2 F0 A3 54 BF 0F 85 EE A7 54 FF " + + "40 FB 7F A0 E3 9F D2 F4 4F 71 FE 19 58 FF 2B 31 " + + "7F 67 36 3B 25 4F 99 1B 4E 53 A6 5F 89 25 95 E9 " + + "C4 00 C7 83 12 F3 1F 26 35 4A D3 D2 47 0E 0A C3 " + + "41 8E C9 8A 52 37 DC 15 A1 D0 0D BC 4C 06 0C 2B " + + "28 2C 13 28 D4 EF 43 61 5A A0 58 3F 85 71 E0 4B " + + "69 9E 64 65 FE 39 C0 E5 22 30 1D 30 27 0E 74 3A " + + "18 60 FD 4A CC B1 2C 13 7D 07 36 2D 2A 31 85 B2 " + + "6A 0D 74 1D 1D 22 4D 99 FE 60 0A F5 9B EC 1C 58 " + + "FD 67 06 56 3F 38 0D 84 3C A5 30 0E 28 D3 AF C4 " + + "A4 CA FA 44 7A 0D 65 6E 60 7F 4D A1 1B 24 58 F7 " + + "49 AF A5 CC 0D CC DF 19 FE 03 00 F0 B1 25 4D 42 " + + "00 07 F0 E1 01 00 00 03 04 39 50 BE 98 B0 6F 57 " + + "24 31 70 5D 23 2F 9F 10 66 FF 00 BD 01 00 00 01 " + + "00 00 00 00 00 00 00 00 00 FF FF 20 54 1C F0 B5 " + + "01 00 00 39 50 BE 98 B0 6F 57 24 31 70 5D 23 2F " + + "9F 10 66 DA 03 00 00 00 00 00 00 00 00 00 00 D1 " + + "07 00 00 DD 05 00 00 4A AD 6F 00 8A C5 53 00 83 " + + "01 00 00 00 FE 78 9C A5 52 BF 4B 42 51 14 3E F7 " + + "DC 77 7A 16 45 48 8B 3C 48 A8 16 15 0D 6C 88 D0 " + + "04 C3 40 A3 32 1C 84 96 08 21 04 A1 C5 5C A2 35 " + + "82 C0 35 6A AB 1C 6A 6B A8 24 5A 83 68 08 84 84 " + + "96 A2 86 A0 7F C2 86 5E E7 5E F5 41 E4 10 BC 03 " + + "1F E7 FB F1 CE B9 F7 F1 9E 7C 05 2E 7A 37 9B E0 " + + "45 7B 10 EC 6F 96 5F 1D 74 13 55 7E B0 6C 5D 20 " + + "60 C0 49 A2 9A BD 99 4F 50 83 1B 30 38 13 0E 33 " + + "60 A6 A7 6B B5 37 EB F4 10 FA 14 15 A0 B6 6B 37 " + + "0C 1E B3 49 73 5B A5 C2 26 48 3E C1 E0 6C 08 4A " + + "30 C9 93 AA 02 B8 20 13 62 05 4E E1 E8 D7 7C C0 " + + "B8 14 95 5E BE B8 A7 CF 1E BE 55 2C 56 B9 78 DF " + + "08 7E 88 4C 27 FF 7B DB FF 7A DD B7 1A 17 67 34 " + + "6A AE BA DA 35 D1 E7 72 BE FE EC 6E FE DA E5 7C " + + "3D EC 7A DE 03 FD 50 06 0B 23 F2 0E F3 B2 A5 11 " + + "91 0D 4C B5 B5 F3 BF 94 C1 8F 24 F7 D9 6F 60 94 " + + "3B C9 9A F3 1C 6B E7 BB F0 2E 49 B2 25 2B C6 B1 " + + "EE 69 EE 15 63 4F 71 7D CE 85 CC C8 35 B9 C3 28 " + + "28 CE D0 5C 67 79 F2 4A A2 14 23 A4 38 43 73 9D " + + "2D 69 2F C1 08 31 9F C5 5C 9B EB 7B C5 69 19 B3 " + + "B4 81 F3 DC E3 B4 8E 8B CC B3 94 53 5A E7 41 2A " + + "63 9A AA 38 C5 3D 48 BB EC 57 59 6F 2B AD 73 1F " + + "1D 60 92 AE 70 8C BB 8F CE 31 C1 3C 49 27 4A EB " + + "DC A4 5B 8C D1 0B 0E 73 37 E9 11 A7 99 C7 E8 41 " + + "69 B0 7F 00 96 F2 A7 E8 42 00 07 F0 B4 01 00 00 " + + "03 04 1A BA F9 D6 A9 B9 3A 03 08 61 E9 90 FF 7B " + + "9E E6 FF 00 90 01 00 00 01 00 00 00 00 00 00 00 " + + "00 00 FF FF 20 54 1C F0 88 01 00 00 1A BA F9 D6 " + + "A9 B9 3A 03 08 61 E9 90 FF 7B 9E E6 12 0E 00 00 " + + "00 00 00 00 00 00 00 00 D1 07 00 00 DD 05 00 00 " + + "4A AD 6F 00 8A C5 53 00 56 01 00 00 00 FE 78 9C " + + "E3 13 62 00 02 D6 BB EC 17 19 04 99 FE F3 30 FC " + + "FF 07 E4 FE 82 62 98 0C 08 C8 31 48 FD 07 03 06 " + + "46 06 2E B8 8C 21 98 75 87 FD 02 C3 42 86 6D 0C " + + "2C 40 39 46 38 8B 85 C1 02 AB A8 14 C4 64 B0 EC " + + "34 06 36 10 93 91 01 64 3A 58 54 87 81 83 FD 22 " + + "AB 63 51 66 62 0E 03 33 D0 06 16 A0 1C 2F 43 26 " + + "83 1E 50 27 08 68 B0 6C 64 2C 66 F4 67 58 C0 30 " + + "1D 45 BF 06 E3 11 D6 27 70 71 4C 5B 89 15 83 B9 " + + "4E 89 71 29 AB 2C 7B 04 45 66 29 B3 51 A8 7F CB " + + "15 CA F4 6F A6 50 FF 16 9D 81 D5 CF 40 61 F8 51 " + + "AA DF 87 42 F7 53 AA 7F A0 FD 3F D0 F1 4F 69 FA " + + "A7 38 FF 0C AC FF 95 98 BF 33 9B 9D 92 A7 CC 0D " + + "A7 29 D3 AF C4 92 CA 74 62 80 E3 41 89 F9 0F 93 " + + "1A A5 69 E9 23 07 85 E1 20 C7 64 45 A9 1B EE 8A " + + "50 E8 06 5E 26 03 86 15 14 96 09 14 EA F7 A1 30 " + + "2D 50 AC 9F C2 38 F0 A5 34 4F B2 32 FF 1C E0 72 " + + "11 98 0E 98 13 07 38 1D 28 31 C7 B2 4C F4 1D D8 " + + "B4 A0 C4 14 CA AA 35 D0 75 64 88 34 65 FA 83 29 " + + "D4 6F B2 73 60 F5 9F A1 54 FF 0E CA D3 40 C8 53 " + + "0A E3 E0 09 85 6E 50 65 7D 22 BD 86 32 37 B0 BF " + + "A6 D0 0D 12 AC FB A4 D7 52 E6 06 E6 EF 0C FF 01 " + + "97 1D 12 C7 42 00 07 F0 C3 01 00 00 03 04 BA 4C " + + "B6 23 BA 8B 27 BE C8 55 59 86 24 9F 89 D4 FF 00 " + + "9F 01 00 00 01 00 00 00 00 00 00 00 00 00 FF FF " + + "20 54 1C F0 97 01 00 00 BA 4C B6 23 BA 8B 27 BE " + + "C8 55 59 86 24 9F 89 D4 AE 0E 00 00 00 00 00 00 " + + "00 00 00 00 D1 07 00 00 DD 05 00 00 4A AD 6F 00 " + + "8A C5 53 00 65 01 00 00 00 FE 78 9C E3 5B C7 00 " + + "04 AC 77 D9 2F 32 08 32 FD E7 61 F8 FF 0F C8 FD " + + "05 C5 30 19 10 90 63 90 FA 0F 06 0C 8C 0C 5C 70 " + + "19 43 30 EB 0E FB 05 86 85 0C DB 18 58 80 72 8C " + + "70 16 0B 83 05 56 51 29 88 C9 60 D9 69 0C 6C 20 " + + "26 23 03 C8 74 B0 A8 0E 03 07 FB 45 56 C7 A2 CC " + + "C4 1C 06 66 A0 0D 2C 40 39 5E 86 4C 06 3D A0 4E " + + "10 D0 60 99 C6 B8 98 D1 9F 61 01 C3 74 14 FD 1A " + + "8C 2B D8 84 B1 88 4B A5 A5 75 03 01 50 DF 59 46 " + + "77 46 0F A8 3C A6 AB 88 15 83 B9 5E 89 B1 8B D5 " + + "97 2D 82 22 B3 94 29 D5 BF E5 CA C0 EA DF AC 43 " + + "A1 FD 14 EA 67 A0 30 FC 28 D5 EF 43 A1 FB 7D 87 " + + "B8 FF 07 3A FE 07 3A FD 53 EA 7E 0A C3 4F 89 F9 " + + "0E 73 EA 69 79 CA DC 70 8A 32 FD 4A 2C 5E 4C DF " + + "87 7A 3C BC E0 A5 30 1E 3E 31 C5 33 AC A0 30 2F " + + "52 A8 DF 87 C2 30 A4 54 3F A5 65 19 85 65 A9 12 " + + "D3 2B 16 0D 8A CB 13 4A F3 E3 27 E6 09 03 9D 0E " + + "06 58 BF 12 B3 13 CB C1 01 4E 8B 4A 4C 56 AC 91 " + + "03 5D 37 86 48 53 A6 3F 98 42 FD 26 3B 07 56 FF " + + "99 1D 14 EA A7 CC 7E 70 1A 08 79 42 61 1C 3C A5 " + + "D0 0D 9C 6C C2 32 6B 29 73 03 DB 6B CA DC C0 F8 " + + "97 F5 AD CC 1A CA DC C0 F4 83 32 37 B0 A4 30 CE " + + "FC C7 48 99 1B FE 33 32 FC 07 00 6C CC 2E 23 33 " + + "00 0B F0 12 00 00 00 BF 00 08 00 08 00 81 01 09 " + + "00 00 08 C0 01 40 00 00 08 40 00 1E F1 10 00 00 " + + "00 0D 00 00 08 0C 00 00 08 17 00 00 08 F7 00 00 " + + "10 "; + + // Decode the stream to bytes + byte[] bytes = HexRead.readData( new ByteArrayInputStream( dump.getBytes() ), -1 ); + // Create a new instance of the escher dumper + EscherDump dumper = new EscherDump(); + // Dump the contents of scher to screen. +// dumper.dumpOld( bytes.length, new ByteArrayInputStream( bytes ), System.out ); + dumper.dump(bytes, 0, bytes.length, System.out); + + } + + public void dump( int recordSize, byte[] data, PrintStream out ) throws IOException, LittleEndian.BufferUnderrunException + { +// ByteArrayInputStream is = new ByteArrayInputStream( data ); +// dump( recordSize, is, out ); + dump( data, 0, recordSize, System.out ); + } +} diff --git a/src/java/org/apache/poi/ddf/EscherOptRecord.java b/src/java/org/apache/poi/ddf/EscherOptRecord.java new file mode 100644 index 0000000000..4efab8ba3d --- /dev/null +++ b/src/java/org/apache/poi/ddf/EscherOptRecord.java @@ -0,0 +1,174 @@ +package org.apache.poi.ddf; + +import org.apache.poi.util.LittleEndian; +import org.apache.poi.util.HexDump; + +import java.util.*; +import java.io.IOException; + +/** + * The opt record is used to store property values for a shape. It is the key to determining + * the attributes of a shape. Properties can be of two types: simple or complex. Simple types + * are fixed length. Complex properties are variable length. + * + * @author Glen Stampoultzis + */ +public class EscherOptRecord + extends EscherRecord +{ + public static final short RECORD_ID = (short) 0xF00B; + public static final String RECORD_DESCRIPTION = "msofbtOPT"; + + private List properties = new ArrayList(); + + /** + * This method deserializes the record from a byte array. + * + * @param data The byte array containing the escher record information + * @param offset The starting offset into <code>data</code>. + * @param recordFactory May be null since this is not a container record. + * @return The number of bytes read from the byte array. + */ + public int fillFields( byte[] data, int offset, EscherRecordFactory recordFactory ) + { + int bytesRemaining = readHeader( data, offset ); + int pos = offset + 8; + + EscherPropertyFactory f = new EscherPropertyFactory(); + properties = f.createProperties( data, pos, getInstance() ); + return bytesRemaining + 8; + } + + /** + * This method serializes this escher record into a byte array. + * + * @param offset The offset into <code>data</code> to start writing the record data to. + * @param data The byte array to serialize to. + * @param listener A listener to retrieve start and end callbacks. Use a <code>NullEscherSerailizationListener</code> to ignore these events. + * + * @return The number of bytes written. + * @see NullEscherSerializationListener + */ + public int serialize( int offset, byte[] data, EscherSerializationListener listener ) + { + listener.beforeRecordSerialize( offset, getRecordId(), this ); + + LittleEndian.putShort( data, offset, getOptions() ); + LittleEndian.putShort( data, offset + 2, getRecordId() ); + LittleEndian.putInt( data, offset + 4, getPropertiesSize() ); + int pos = offset + 8; + for ( Iterator iterator = properties.iterator(); iterator.hasNext(); ) + { + EscherProperty escherProperty = (EscherProperty) iterator.next(); + pos += escherProperty.serializeSimplePart( data, pos ); + } + for ( Iterator iterator = properties.iterator(); iterator.hasNext(); ) + { + EscherProperty escherProperty = (EscherProperty) iterator.next(); + pos += escherProperty.serializeComplexPart( data, pos ); + } + listener.afterRecordSerialize( pos, getRecordId(), pos - offset, this ); + return pos - offset; + } + + /** + * Returns the number of bytes that are required to serialize this record. + * + * @return Number of bytes + */ + public int getRecordSize() + { + return 8 + getPropertiesSize(); + } + + /** + * Automatically recalculate the correct option + */ + public short getOptions() + { + setOptions( (short) ( ( properties.size() << 4 ) | 0x3 ) ); + return super.getOptions(); + } + + /** + * The short name for this record + */ + public String getRecordName() + { + return "Opt"; + } + + private int getPropertiesSize() + { + int totalSize = 0; + for ( Iterator iterator = properties.iterator(); iterator.hasNext(); ) + { + EscherProperty escherProperty = (EscherProperty) iterator.next(); + totalSize += escherProperty.getPropertySize(); + } + return totalSize; + } + + /** + * Retrieve the string representation of this record. + */ + public String toString() + { + String nl = System.getProperty( "line.separator" ); + StringBuffer propertiesBuf = new StringBuffer(); + for ( Iterator iterator = properties.iterator(); iterator.hasNext(); ) + propertiesBuf.append( " " + + iterator.next().toString() + + nl ); + + return "org.apache.poi.ddf.EscherOptRecord:" + nl + + " isContainer: " + isContainerRecord() + nl + + " options: 0x" + HexDump.toHex( getOptions() ) + nl + + " recordId: 0x" + HexDump.toHex( getRecordId() ) + nl + + " numchildren: " + getChildRecords().size() + nl + + " properties:" + nl + + propertiesBuf.toString(); + } + + /** + * The list of properties stored by this record. + */ + public List getEscherProperties() + { + return properties; + } + + /** + * The list of properties stored by this record. + */ + public EscherProperty getEscherProperty( int index ) + { + return (EscherProperty) properties.get( index ); + } + + /** + * Add a property to this record. + */ + public void addEscherProperty( EscherProperty prop ) + { + properties.add( prop ); + } + + /** + * Records should be sorted by property number before being stored. + */ + public void sortProperties() + { + Collections.sort( properties, new Comparator() + { + public int compare( Object o1, Object o2 ) + { + EscherProperty p1 = (EscherProperty) o1; + EscherProperty p2 = (EscherProperty) o2; + return new Short( p1.getPropertyNumber() ).compareTo( new Short( p2.getPropertyNumber() ) ); + } + } ); + } + + +} diff --git a/src/java/org/apache/poi/ddf/EscherProperties.java b/src/java/org/apache/poi/ddf/EscherProperties.java new file mode 100644 index 0000000000..ea1c8745c5 --- /dev/null +++ b/src/java/org/apache/poi/ddf/EscherProperties.java @@ -0,0 +1,607 @@ +package org.apache.poi.ddf; + +import java.util.HashMap; +import java.util.Map; + +/** + * Provides a list of all known escher properties including the description and + * type. + * + * @author Glen Stampoultzis (glens at apache.org) + */ +public class EscherProperties +{ + + // Property constants + public static final short TRANSFORM__ROTATION = 4; + public static final short PROTECTION__LOCKROTATION = 119; + public static final short PROTECTION__LOCKASPECTRATIO = 120; + public static final short PROTECTION__LOCKPOSITION = 121; + public static final short PROTECTION__LOCKAGAINSTSELECT = 122; + public static final short PROTECTION__LOCKCROPPING = 123; + public static final short PROTECTION__LOCKVERTICES = 124; + public static final short PROTECTION__LOCKTEXT = 125; + public static final short PROTECTION__LOCKADJUSTHANDLES = 126; + public static final short PROTECTION__LOCKAGAINSTGROUPING = 127; + public static final short TEXT__TEXTID = 128; + public static final short TEXT__TEXTLEFT = 129; + public static final short TEXT__TEXTTOP = 130; + public static final short TEXT__TEXTRIGHT = 131; + public static final short TEXT__TEXTBOTTOM = 132; + public static final short TEXT__WRAPTEXT = 133; + public static final short TEXT__SCALETEXT = 134; + public static final short TEXT__ANCHORTEXT = 135; + public static final short TEXT__TEXTFLOW = 136; + public static final short TEXT__FONTROTATION = 137; + public static final short TEXT__IDOFNEXTSHAPE = 138; + public static final short TEXT__BIDIR = 139; + public static final short TEXT__SINGLECLICKSELECTS = 187; + public static final short TEXT__USEHOSTMARGINS = 188; + public static final short TEXT__ROTATETEXTWITHSHAPE = 189; + public static final short TEXT__SIZESHAPETOFITTEXT = 190; + public static final short TEXT__SIZE_TEXT_TO_FIT_SHAPE = 191 ; + public static final short GEOTEXT__UNICODE = 192; + public static final short GEOTEXT__RTFTEXT = 193; + public static final short GEOTEXT__ALIGNMENTONCURVE = 194; + public static final short GEOTEXT__DEFAULTPOINTSIZE = 195; + public static final short GEOTEXT__TEXTSPACING = 196; + public static final short GEOTEXT__FONTFAMILYNAME = 197; + public static final short GEOTEXT__REVERSEROWORDER = 240; + public static final short GEOTEXT__HASTEXTEFFECT = 241; + public static final short GEOTEXT__ROTATECHARACTERS = 242; + public static final short GEOTEXT__KERNCHARACTERS = 243; + public static final short GEOTEXT__TIGHTORTRACK = 244; + public static final short GEOTEXT__STRETCHTOFITSHAPE = 245; + public static final short GEOTEXT__CHARBOUNDINGBOX = 246; + public static final short GEOTEXT__SCALETEXTONPATH = 247; + public static final short GEOTEXT__STRETCHCHARHEIGHT = 248; + public static final short GEOTEXT__NOMEASUREALONGPATH = 249; + public static final short GEOTEXT__BOLDFONT = 250; + public static final short GEOTEXT__ITALICFONT = 251; + public static final short GEOTEXT__UNDERLINEFONT = 252; + public static final short GEOTEXT__SHADOWFONT = 253; + public static final short GEOTEXT__SMALLCAPSFONT = 254; + public static final short GEOTEXT__STRIKETHROUGHFONT = 255; + public static final short BLIP__CROPFROMTOP = 256; + public static final short BLIP__CROPFROMBOTTOM = 257; + public static final short BLIP__CROPFROMLEFT = 258; + public static final short BLIP__CROPFROMRIGHT = 259; + public static final short BLIP__BLIPTODISPLAY = 260; + public static final short BLIP__BLIPFILENAME = 261; + public static final short BLIP__BLIPFLAGS = 262; + public static final short BLIP__TRANSPARENTCOLOR = 263; + public static final short BLIP__CONTRASTSETTING = 264; + public static final short BLIP__BRIGHTNESSSETTING = 265; + public static final short BLIP__GAMMA = 266; + public static final short BLIP__PICTUREID = 267; + public static final short BLIP__DOUBLEMOD = 268; + public static final short BLIP__PICTUREFILLMOD = 269; + public static final short BLIP__PICTURELINE = 270; + public static final short BLIP__PRINTBLIP = 271; + public static final short BLIP__PRINTBLIPFILENAME = 272; + public static final short BLIP__PRINTFLAGS = 273; + public static final short BLIP__NOHITTESTPICTURE = 316; + public static final short BLIP__PICTUREGRAY = 317; + public static final short BLIP__PICTUREBILEVEL = 318; + public static final short BLIP__PICTUREACTIVE = 319; + public static final short GEOMETRY__LEFT = 320; + public static final short GEOMETRY__TOP = 321; + public static final short GEOMETRY__RIGHT = 322; + public static final short GEOMETRY__BOTTOM = 323; + public static final short GEOMETRY__SHAPEPATH = 324; + public static final short GEOMETRY__VERTICES = 325; + public static final short GEOMETRY__SEGMENTINFO = 326; + public static final short GEOMETRY__ADJUSTVALUE = 327; + public static final short GEOMETRY__ADJUST2VALUE = 328; + public static final short GEOMETRY__ADJUST3VALUE = 329; + public static final short GEOMETRY__ADJUST4VALUE = 330; + public static final short GEOMETRY__ADJUST5VALUE = 331; + public static final short GEOMETRY__ADJUST6VALUE = 332; + public static final short GEOMETRY__ADJUST7VALUE = 333; + public static final short GEOMETRY__ADJUST8VALUE = 334; + public static final short GEOMETRY__ADJUST9VALUE = 335; + public static final short GEOMETRY__ADJUST10VALUE = 336; + public static final short GEOMETRY__SHADOWok = 378; + public static final short GEOMETRY__3DOK = 379; + public static final short GEOMETRY__LINEOK = 380; + public static final short GEOMETRY__GEOTEXTOK = 381; + public static final short GEOMETRY__FILLSHADESHAPEOK = 382; + public static final short GEOMETRY__FILLOK = 383; + public static final short FILL__FILLTYPE = 384; + public static final short FILL__FILLCOLOR = 385 ; + public static final short FILL__FILLOPACITY = 386; + public static final short FILL__FILLBACKCOLOR = 387; + public static final short FILL__BACKOPACITY = 388; + public static final short FILL__CRMOD = 389; + public static final short FILL__PATTERNTEXTURE = 390; + public static final short FILL__BLIPFILENAME = 391; + public static final short FILL__BLIPFLAGS = 392; + public static final short FILL__WIDTH = 393; + public static final short FILL__HEIGHT = 394; + public static final short FILL__ANGLE = 395; + public static final short FILL__FOCUS = 396; + public static final short FILL__TOLEFT = 397; + public static final short FILL__TOTOP = 398; + public static final short FILL__TORIGHT = 399; + public static final short FILL__TOBOTTOM = 400; + public static final short FILL__RECTLEFT = 401; + public static final short FILL__RECTTOP = 402; + public static final short FILL__RECTRIGHT = 403; + public static final short FILL__RECTBOTTOM = 404; + public static final short FILL__DZTYPE = 405; + public static final short FILL__SHADEPRESET = 406; + public static final short FILL__SHADECOLORS = 407; + public static final short FILL__ORIGINX = 408; + public static final short FILL__ORIGINY = 409; + public static final short FILL__SHAPEORIGINX = 410; + public static final short FILL__SHAPEORIGINY = 411; + public static final short FILL__SHADETYPE = 412; + public static final short FILL__FILLED = 443; + public static final short FILL__HITTESTFILL = 444; + public static final short FILL__SHAPE = 445; + public static final short FILL__USERECT = 446; + public static final short FILL__NOFILLHITTEST = 447; + public static final short LINESTYLE__COLOR = 448 ; + public static final short LINESTYLE__OPACITY = 449; + public static final short LINESTYLE__BACKCOLOR = 450; + public static final short LINESTYLE__CRMOD = 451; + public static final short LINESTYLE__LINETYPE = 452; + public static final short LINESTYLE__FILLBLIP = 453; + public static final short LINESTYLE__FILLBLIPNAME = 454; + public static final short LINESTYLE__FILLBLIPFLAGS = 455; + public static final short LINESTYLE__FILLWIDTH = 456; + public static final short LINESTYLE__FILLHEIGHT = 457; + public static final short LINESTYLE__FILLDZTYPE = 458; + public static final short LINESTYLE__LINEWIDTH = 459; + public static final short LINESTYLE__LINEMITERLIMIT = 460; + public static final short LINESTYLE__LINESTYLE = 461; + public static final short LINESTYLE__LINEDASHING = 462; + public static final short LINESTYLE__LINEDASHSTYLE = 463; + public static final short LINESTYLE__LINESTARTARROWHEAD = 464; + public static final short LINESTYLE__LINEENDARROWHEAD = 465; + public static final short LINESTYLE__LINESTARTARROWWIDTH = 466; + public static final short LINESTYLE__LINEESTARTARROWLENGTH = 467; + public static final short LINESTYLE__LINEENDARROWWIDTH = 468; + public static final short LINESTYLE__LINEENDARROWLENGTH = 469; + public static final short LINESTYLE__LINEJOINSTYLE = 470; + public static final short LINESTYLE__LINEENDCAPSTYLE = 471; + public static final short LINESTYLE__ARROWHEADSOK = 507; + public static final short LINESTYLE__ANYLINE = 508; + public static final short LINESTYLE__HITLINETEST = 509; + public static final short LINESTYLE__LINEFILLSHAPE = 510; + public static final short LINESTYLE__NOLINEDRAWDASH = 511; + public static final short SHADOWSTYLE__TYPE = 512; + public static final short SHADOWSTYLE__COLOR = 513; + public static final short SHADOWSTYLE__HIGHLIGHT = 514; + public static final short SHADOWSTYLE__CRMOD = 515; + public static final short SHADOWSTYLE__OPACITY = 516; + public static final short SHADOWSTYLE__OFFSETX = 517; + public static final short SHADOWSTYLE__OFFSETY = 518; + public static final short SHADOWSTYLE__SECONDOFFSETX = 519; + public static final short SHADOWSTYLE__SECONDOFFSETY = 520; + public static final short SHADOWSTYLE__SCALEXTOX = 521; + public static final short SHADOWSTYLE__SCALEYTOX = 522; + public static final short SHADOWSTYLE__SCALEXTOY = 523; + public static final short SHADOWSTYLE__SCALEYTOY = 524; + public static final short SHADOWSTYLE__PERSPECTIVEX = 525; + public static final short SHADOWSTYLE__PERSPECTIVEY = 526; + public static final short SHADOWSTYLE__WEIGHT = 527; + public static final short SHADOWSTYLE__ORIGINX = 528; + public static final short SHADOWSTYLE__ORIGINY = 529; + public static final short SHADOWSTYLE__SHADOW = 574; + public static final short SHADOWSTYLE__SHADOWOBSURED = 575; + public static final short PERSPECTIVE__TYPE = 576; + public static final short PERSPECTIVE__OFFSETX = 577; + public static final short PERSPECTIVE__OFFSETY = 578; + public static final short PERSPECTIVE__SCALEXTOX = 579; + public static final short PERSPECTIVE__SCALEYTOX = 580; + public static final short PERSPECTIVE__SCALEXTOY = 581; + public static final short PERSPECTIVE__SCALEYTOY = 582; + public static final short PERSPECTIVE__PERSPECTIVEX = 583; + public static final short PERSPECTIVE__PERSPECTIVEY = 584; + public static final short PERSPECTIVE__WEIGHT = 585; + public static final short PERSPECTIVE__ORIGINX = 586; + public static final short PERSPECTIVE__ORIGINY = 587; + public static final short PERSPECTIVE__PERSPECTIVEON = 639; + public static final short THREED__SPECULARAMOUNT = 640; + public static final short THREED__DIFFUSEAMOUNT = 661; + public static final short THREED__SHININESS = 662; + public static final short THREED__EDGETHICKNESS = 663; + public static final short THREED__EXTRUDEFORWARD = 664; + public static final short THREED__EXTRUDEBACKWARD = 665; + public static final short THREED__EXTRUDEPLANE = 666; + public static final short THREED__EXTRUSIONCOLOR = 667; + public static final short THREED__CRMOD = 648; + public static final short THREED__3DEFFECT = 700; + public static final short THREED__METALLIC = 701; + public static final short THREED__USEEXTRUSIONCOLOR = 702; + public static final short THREED__LIGHTFACE = 703; + public static final short THREEDSTYLE__YROTATIONANGLE = 704; + public static final short THREEDSTYLE__XROTATIONANGLE = 705; + public static final short THREEDSTYLE__ROTATIONAXISX = 706; + public static final short THREEDSTYLE__ROTATIONAXISY = 707; + public static final short THREEDSTYLE__ROTATIONAXISZ = 708; + public static final short THREEDSTYLE__ROTATIONANGLE = 709; + public static final short THREEDSTYLE__ROTATIONCENTERX = 710; + public static final short THREEDSTYLE__ROTATIONCENTERY = 711; + public static final short THREEDSTYLE__ROTATIONCENTERZ = 712; + public static final short THREEDSTYLE__RENDERMODE = 713; + public static final short THREEDSTYLE__TOLERANCE = 714; + public static final short THREEDSTYLE__XVIEWPOINT = 715; + public static final short THREEDSTYLE__YVIEWPOINT = 716; + public static final short THREEDSTYLE__ZVIEWPOINT = 717; + public static final short THREEDSTYLE__ORIGINX = 718; + public static final short THREEDSTYLE__ORIGINY = 719; + public static final short THREEDSTYLE__SKEWANGLE = 720; + public static final short THREEDSTYLE__SKEWAMOUNT = 721; + public static final short THREEDSTYLE__AMBIENTINTENSITY = 722; + public static final short THREEDSTYLE__KEYX = 723; + public static final short THREEDSTYLE__KEYY = 724; + public static final short THREEDSTYLE__KEYZ = 725; + public static final short THREEDSTYLE__KEYINTENSITY = 726; + public static final short THREEDSTYLE__FILLX = 727; + public static final short THREEDSTYLE__FILLY = 728; + public static final short THREEDSTYLE__FILLZ = 729; + public static final short THREEDSTYLE__FILLINTENSITY = 730; + public static final short THREEDSTYLE__CONSTRAINROTATION = 763; + public static final short THREEDSTYLE__ROTATIONCENTERAUTO = 764; + public static final short THREEDSTYLE__PARALLEL = 765; + public static final short THREEDSTYLE__KEYHARSH = 766; + public static final short THREEDSTYLE__FILLHARSH = 767; + public static final short SHAPE__MASTER = 769; + public static final short SHAPE__CONNECTORSTYLE = 771; + public static final short SHAPE__BLACKANDWHITESETTINGS = 772; + public static final short SHAPE__WMODEPUREBW = 773; + public static final short SHAPE__WMODEBW = 774; + public static final short SHAPE__OLEICON = 826; + public static final short SHAPE__PREFERRELATIVERESIZE = 827; + public static final short SHAPE__LOCKSHAPETYPE = 828; + public static final short SHAPE__DELETEATTACHEDOBJECT = 830; + public static final short SHAPE__BACKGROUNDSHAPE = 831; + public static final short CALLOUT__CALLOUTTYPE = 832; + public static final short CALLOUT__XYCALLOUTGAP = 833; + public static final short CALLOUT__CALLOUTANGLE = 834; + public static final short CALLOUT__CALLOUTDROPTYPE = 835; + public static final short CALLOUT__CALLOUTDROPSPECIFIED = 836; + public static final short CALLOUT__CALLOUTLENGTHSPECIFIED = 837; + public static final short CALLOUT__ISCALLOUT = 889; + public static final short CALLOUT__CALLOUTACCENTBAR = 890; + public static final short CALLOUT__CALLOUTTEXTBORDER = 891; + public static final short CALLOUT__CALLOUTMINUSX = 892; + public static final short CALLOUT__CALLOUTMINUSY = 893; + public static final short CALLOUT__DROPAUTO = 894; + public static final short CALLOUT__LENGTHSPECIFIED = 895; + public static final short GROUPSHAPE__SHAPENAME = 896; + public static final short GROUPSHAPE__DESCRIPTION = 897; + public static final short GROUPSHAPE__HYPERLINK = 898; + public static final short GROUPSHAPE__WRAPPOLYGONVERTICES = 899; + public static final short GROUPSHAPE__WRAPDISTLEFT = 900; + public static final short GROUPSHAPE__WRAPDISTTOP = 901; + public static final short GROUPSHAPE__WRAPDISTRIGHT = 902; + public static final short GROUPSHAPE__WRAPDISTBOTTOM = 903; + public static final short GROUPSHAPE__REGROUPID = 904; + public static final short GROUPSHAPE__EDITEDWRAP = 953; + public static final short GROUPSHAPE__BEHINDDOCUMENT = 954; + public static final short GROUPSHAPE__ONDBLCLICKNOTIFY = 955; + public static final short GROUPSHAPE__ISBUTTON = 956; + public static final short GROUPSHAPE__1DADJUSTMENT = 957; + public static final short GROUPSHAPE__HIDDEN = 958; + public static final short GROUPSHAPE__PRINT = 959; + + + private static Map properties; + + private static void initProps() + { + if ( properties == null ) + { + properties = new HashMap(); + addProp( TRANSFORM__ROTATION, data( "transform.rotation" ) ); + addProp( PROTECTION__LOCKROTATION , data( "protection.lockrotation" ) ); + addProp( PROTECTION__LOCKASPECTRATIO , data( "protection.lockaspectratio" ) ); + addProp( PROTECTION__LOCKPOSITION , data( "protection.lockposition" ) ); + addProp( PROTECTION__LOCKAGAINSTSELECT , data( "protection.lockagainstselect" ) ); + addProp( PROTECTION__LOCKCROPPING , data( "protection.lockcropping" ) ); + addProp( PROTECTION__LOCKVERTICES , data( "protection.lockvertices" ) ); + addProp( PROTECTION__LOCKTEXT , data( "protection.locktext" ) ); + addProp( PROTECTION__LOCKADJUSTHANDLES , data( "protection.lockadjusthandles" ) ); + addProp( PROTECTION__LOCKAGAINSTGROUPING , data( "protection.lockagainstgrouping", EscherPropertyMetaData.TYPE_BOOLEAN ) ); + addProp( TEXT__TEXTID , data( "text.textid" ) ); + addProp( TEXT__TEXTLEFT , data( "text.textleft" ) ); + addProp( TEXT__TEXTTOP , data( "text.texttop" ) ); + addProp( TEXT__TEXTRIGHT , data( "text.textright" ) ); + addProp( TEXT__TEXTBOTTOM , data( "text.textbottom" ) ); + addProp( TEXT__WRAPTEXT , data( "text.wraptext" ) ); + addProp( TEXT__SCALETEXT , data( "text.scaletext" ) ); + addProp( TEXT__ANCHORTEXT , data( "text.anchortext" ) ); + addProp( TEXT__TEXTFLOW , data( "text.textflow" ) ); + addProp( TEXT__FONTROTATION , data( "text.fontrotation" ) ); + addProp( TEXT__IDOFNEXTSHAPE , data( "text.idofnextshape" ) ); + addProp( TEXT__BIDIR , data( "text.bidir" ) ); + addProp( TEXT__SINGLECLICKSELECTS , data( "text.singleclickselects" ) ); + addProp( TEXT__USEHOSTMARGINS , data( "text.usehostmargins" ) ); + addProp( TEXT__ROTATETEXTWITHSHAPE , data( "text.rotatetextwithshape" ) ); + addProp( TEXT__SIZESHAPETOFITTEXT , data( "text.sizeshapetofittext" ) ); + addProp( TEXT__SIZE_TEXT_TO_FIT_SHAPE, data( "text.sizetexttofitshape", EscherPropertyMetaData.TYPE_BOOLEAN ) ); + addProp( GEOTEXT__UNICODE , data( "geotext.unicode" ) ); + addProp( GEOTEXT__RTFTEXT , data( "geotext.rtftext" ) ); + addProp( GEOTEXT__ALIGNMENTONCURVE , data( "geotext.alignmentoncurve" ) ); + addProp( GEOTEXT__DEFAULTPOINTSIZE , data( "geotext.defaultpointsize" ) ); + addProp( GEOTEXT__TEXTSPACING , data( "geotext.textspacing" ) ); + addProp( GEOTEXT__FONTFAMILYNAME , data( "geotext.fontfamilyname" ) ); + addProp( GEOTEXT__REVERSEROWORDER , data( "geotext.reverseroworder" ) ); + addProp( GEOTEXT__HASTEXTEFFECT , data( "geotext.hastexteffect" ) ); + addProp( GEOTEXT__ROTATECHARACTERS , data( "geotext.rotatecharacters" ) ); + addProp( GEOTEXT__KERNCHARACTERS , data( "geotext.kerncharacters" ) ); + addProp( GEOTEXT__TIGHTORTRACK , data( "geotext.tightortrack" ) ); + addProp( GEOTEXT__STRETCHTOFITSHAPE , data( "geotext.stretchtofitshape" ) ); + addProp( GEOTEXT__CHARBOUNDINGBOX , data( "geotext.charboundingbox" ) ); + addProp( GEOTEXT__SCALETEXTONPATH , data( "geotext.scaletextonpath" ) ); + addProp( GEOTEXT__STRETCHCHARHEIGHT , data( "geotext.stretchcharheight" ) ); + addProp( GEOTEXT__NOMEASUREALONGPATH , data( "geotext.nomeasurealongpath" ) ); + addProp( GEOTEXT__BOLDFONT , data( "geotext.boldfont" ) ); + addProp( GEOTEXT__ITALICFONT , data( "geotext.italicfont" ) ); + addProp( GEOTEXT__UNDERLINEFONT , data( "geotext.underlinefont" ) ); + addProp( GEOTEXT__SHADOWFONT , data( "geotext.shadowfont" ) ); + addProp( GEOTEXT__SMALLCAPSFONT , data( "geotext.smallcapsfont" ) ); + addProp( GEOTEXT__STRIKETHROUGHFONT , data( "geotext.strikethroughfont" ) ); + addProp( BLIP__CROPFROMTOP , data( "blip.cropfromtop" ) ); + addProp( BLIP__CROPFROMBOTTOM , data( "blip.cropfrombottom" ) ); + addProp( BLIP__CROPFROMLEFT , data( "blip.cropfromleft" ) ); + addProp( BLIP__CROPFROMRIGHT , data( "blip.cropfromright" ) ); + addProp( BLIP__BLIPTODISPLAY , data( "blip.bliptodisplay" ) ); + addProp( BLIP__BLIPFILENAME , data( "blip.blipfilename" ) ); + addProp( BLIP__BLIPFLAGS , data( "blip.blipflags" ) ); + addProp( BLIP__TRANSPARENTCOLOR , data( "blip.transparentcolor" ) ); + addProp( BLIP__CONTRASTSETTING , data( "blip.contrastsetting" ) ); + addProp( BLIP__BRIGHTNESSSETTING , data( "blip.brightnesssetting" ) ); + addProp( BLIP__GAMMA , data( "blip.gamma" ) ); + addProp( BLIP__PICTUREID , data( "blip.pictureid" ) ); + addProp( BLIP__DOUBLEMOD , data( "blip.doublemod" ) ); + addProp( BLIP__PICTUREFILLMOD , data( "blip.picturefillmod" ) ); + addProp( BLIP__PICTURELINE , data( "blip.pictureline" ) ); + addProp( BLIP__PRINTBLIP , data( "blip.printblip" ) ); + addProp( BLIP__PRINTBLIPFILENAME , data( "blip.printblipfilename" ) ); + addProp( BLIP__PRINTFLAGS , data( "blip.printflags" ) ); + addProp( BLIP__NOHITTESTPICTURE , data( "blip.nohittestpicture" ) ); + addProp( BLIP__PICTUREGRAY , data( "blip.picturegray" ) ); + addProp( BLIP__PICTUREBILEVEL , data( "blip.picturebilevel" ) ); + addProp( BLIP__PICTUREACTIVE , data( "blip.pictureactive" ) ); + addProp( GEOMETRY__LEFT , data( "geometry.left" ) ); + addProp( GEOMETRY__TOP , data( "geometry.top" ) ); + addProp( GEOMETRY__RIGHT , data( "geometry.right" ) ); + addProp( GEOMETRY__BOTTOM , data( "geometry.bottom" ) ); + addProp( GEOMETRY__SHAPEPATH , data( "geometry.shapepath", EscherPropertyMetaData.TYPE_SHAPEPATH ) ); + addProp( GEOMETRY__VERTICES , data( "geometry.vertices" , EscherPropertyMetaData.TYPE_ARRAY ) ); + addProp( GEOMETRY__SEGMENTINFO , data( "geometry.segmentinfo", EscherPropertyMetaData.TYPE_ARRAY ) ); + addProp( GEOMETRY__ADJUSTVALUE , data( "geometry.adjustvalue" ) ); + addProp( GEOMETRY__ADJUST2VALUE , data( "geometry.adjust2value" ) ); + addProp( GEOMETRY__ADJUST3VALUE , data( "geometry.adjust3value" ) ); + addProp( GEOMETRY__ADJUST4VALUE , data( "geometry.adjust4value" ) ); + addProp( GEOMETRY__ADJUST5VALUE , data( "geometry.adjust5value" ) ); + addProp( GEOMETRY__ADJUST6VALUE , data( "geometry.adjust6value" ) ); + addProp( GEOMETRY__ADJUST7VALUE , data( "geometry.adjust7value" ) ); + addProp( GEOMETRY__ADJUST8VALUE , data( "geometry.adjust8value" ) ); + addProp( GEOMETRY__ADJUST9VALUE , data( "geometry.adjust9value" ) ); + addProp( GEOMETRY__ADJUST10VALUE , data( "geometry.adjust10value" ) ); + addProp( GEOMETRY__SHADOWok , data( "geometry.shadowOK" ) ); + addProp( GEOMETRY__3DOK , data( "geometry.3dok" ) ); + addProp( GEOMETRY__LINEOK , data( "geometry.lineok" ) ); + addProp( GEOMETRY__GEOTEXTOK , data( "geometry.geotextok" ) ); + addProp( GEOMETRY__FILLSHADESHAPEOK , data( "geometry.fillshadeshapeok" ) ); + addProp( GEOMETRY__FILLOK , data( "geometry.fillok", EscherPropertyMetaData.TYPE_BOOLEAN ) ); + addProp( FILL__FILLTYPE , data( "fill.filltype" ) ); + addProp( FILL__FILLCOLOR, data( "fill.fillcolor", EscherPropertyMetaData.TYPE_RGB ) ); + addProp( FILL__FILLOPACITY , data( "fill.fillopacity" ) ); + addProp( FILL__FILLBACKCOLOR , data( "fill.fillbackcolor", EscherPropertyMetaData.TYPE_RGB ) ); + addProp( FILL__BACKOPACITY , data( "fill.backopacity" ) ); + addProp( FILL__CRMOD , data( "fill.crmod" ) ); + addProp( FILL__PATTERNTEXTURE , data( "fill.patterntexture" ) ); + addProp( FILL__BLIPFILENAME , data( "fill.blipfilename" ) ); + addProp( FILL__BLIPFLAGS, data( "fill.blipflags" ) ); + addProp( FILL__WIDTH , data( "fill.width" ) ); + addProp( FILL__HEIGHT , data( "fill.height" ) ); + addProp( FILL__ANGLE , data( "fill.angle" ) ); + addProp( FILL__FOCUS , data( "fill.focus" ) ); + addProp( FILL__TOLEFT , data( "fill.toleft" ) ); + addProp( FILL__TOTOP , data( "fill.totop" ) ); + addProp( FILL__TORIGHT , data( "fill.toright" ) ); + addProp( FILL__TOBOTTOM , data( "fill.tobottom" ) ); + addProp( FILL__RECTLEFT , data( "fill.rectleft" ) ); + addProp( FILL__RECTTOP , data( "fill.recttop" ) ); + addProp( FILL__RECTRIGHT , data( "fill.rectright" ) ); + addProp( FILL__RECTBOTTOM , data( "fill.rectbottom" ) ); + addProp( FILL__DZTYPE , data( "fill.dztype" ) ); + addProp( FILL__SHADEPRESET , data( "fill.shadepreset" ) ); + addProp( FILL__SHADECOLORS , data( "fill.shadecolors", EscherPropertyMetaData.TYPE_ARRAY ) ); + addProp( FILL__ORIGINX , data( "fill.originx" ) ); + addProp( FILL__ORIGINY , data( "fill.originy" ) ); + addProp( FILL__SHAPEORIGINX , data( "fill.shapeoriginx" ) ); + addProp( FILL__SHAPEORIGINY , data( "fill.shapeoriginy" ) ); + addProp( FILL__SHADETYPE , data( "fill.shadetype" ) ); + addProp( FILL__FILLED , data( "fill.filled" ) ); + addProp( FILL__HITTESTFILL , data( "fill.hittestfill" ) ); + addProp( FILL__SHAPE , data( "fill.shape" ) ); + addProp( FILL__USERECT , data( "fill.userect" ) ); + addProp( FILL__NOFILLHITTEST , data( "fill.nofillhittest", EscherPropertyMetaData.TYPE_BOOLEAN ) ); + addProp( LINESTYLE__COLOR, data( "linestyle.color", EscherPropertyMetaData.TYPE_RGB ) ); + addProp( LINESTYLE__OPACITY , data( "linestyle.opacity" ) ); + addProp( LINESTYLE__BACKCOLOR , data( "linestyle.backcolor", EscherPropertyMetaData.TYPE_RGB ) ); + addProp( LINESTYLE__CRMOD , data( "linestyle.crmod" ) ); + addProp( LINESTYLE__LINETYPE , data( "linestyle.linetype" ) ); + addProp( LINESTYLE__FILLBLIP , data( "linestyle.fillblip" ) ); + addProp( LINESTYLE__FILLBLIPNAME , data( "linestyle.fillblipname" ) ); + addProp( LINESTYLE__FILLBLIPFLAGS , data( "linestyle.fillblipflags" ) ); + addProp( LINESTYLE__FILLWIDTH , data( "linestyle.fillwidth" ) ); + addProp( LINESTYLE__FILLHEIGHT , data( "linestyle.fillheight" ) ); + addProp( LINESTYLE__FILLDZTYPE , data( "linestyle.filldztype" ) ); + addProp( LINESTYLE__LINEWIDTH , data( "linestyle.linewidth" ) ); + addProp( LINESTYLE__LINEMITERLIMIT , data( "linestyle.linemiterlimit" ) ); + addProp( LINESTYLE__LINESTYLE , data( "linestyle.linestyle" ) ); + addProp( LINESTYLE__LINEDASHING , data( "linestyle.linedashing" ) ); + addProp( LINESTYLE__LINEDASHSTYLE , data( "linestyle.linedashstyle", EscherPropertyMetaData.TYPE_ARRAY ) ); + addProp( LINESTYLE__LINESTARTARROWHEAD , data( "linestyle.linestartarrowhead" ) ); + addProp( LINESTYLE__LINEENDARROWHEAD , data( "linestyle.lineendarrowhead" ) ); + addProp( LINESTYLE__LINESTARTARROWWIDTH , data( "linestyle.linestartarrowwidth" ) ); + addProp( LINESTYLE__LINEESTARTARROWLENGTH , data( "linestyle.lineestartarrowlength" ) ); + addProp( LINESTYLE__LINEENDARROWWIDTH , data( "linestyle.lineendarrowwidth" ) ); + addProp( LINESTYLE__LINEENDARROWLENGTH , data( "linestyle.lineendarrowlength" ) ); + addProp( LINESTYLE__LINEJOINSTYLE , data( "linestyle.linejoinstyle" ) ); + addProp( LINESTYLE__LINEENDCAPSTYLE , data( "linestyle.lineendcapstyle" ) ); + addProp( LINESTYLE__ARROWHEADSOK , data( "linestyle.arrowheadsok" ) ); + addProp( LINESTYLE__ANYLINE , data( "linestyle.anyline" ) ); + addProp( LINESTYLE__HITLINETEST , data( "linestyle.hitlinetest" ) ); + addProp( LINESTYLE__LINEFILLSHAPE , data( "linestyle.linefillshape" ) ); + addProp( LINESTYLE__NOLINEDRAWDASH , data( "linestyle.nolinedrawdash", EscherPropertyMetaData.TYPE_BOOLEAN ) ); + addProp( SHADOWSTYLE__TYPE , data( "shadowstyle.type" ) ); + addProp( SHADOWSTYLE__COLOR , data( "shadowstyle.color", EscherPropertyMetaData.TYPE_RGB ) ); + addProp( SHADOWSTYLE__HIGHLIGHT , data( "shadowstyle.highlight" ) ); + addProp( SHADOWSTYLE__CRMOD , data( "shadowstyle.crmod" ) ); + addProp( SHADOWSTYLE__OPACITY , data( "shadowstyle.opacity" ) ); + addProp( SHADOWSTYLE__OFFSETX , data( "shadowstyle.offsetx" ) ); + addProp( SHADOWSTYLE__OFFSETY , data( "shadowstyle.offsety" ) ); + addProp( SHADOWSTYLE__SECONDOFFSETX , data( "shadowstyle.secondoffsetx" ) ); + addProp( SHADOWSTYLE__SECONDOFFSETY , data( "shadowstyle.secondoffsety" ) ); + addProp( SHADOWSTYLE__SCALEXTOX , data( "shadowstyle.scalextox" ) ); + addProp( SHADOWSTYLE__SCALEYTOX , data( "shadowstyle.scaleytox" ) ); + addProp( SHADOWSTYLE__SCALEXTOY , data( "shadowstyle.scalextoy" ) ); + addProp( SHADOWSTYLE__SCALEYTOY , data( "shadowstyle.scaleytoy" ) ); + addProp( SHADOWSTYLE__PERSPECTIVEX , data( "shadowstyle.perspectivex" ) ); + addProp( SHADOWSTYLE__PERSPECTIVEY , data( "shadowstyle.perspectivey" ) ); + addProp( SHADOWSTYLE__WEIGHT , data( "shadowstyle.weight" ) ); + addProp( SHADOWSTYLE__ORIGINX , data( "shadowstyle.originx" ) ); + addProp( SHADOWSTYLE__ORIGINY , data( "shadowstyle.originy" ) ); + addProp( SHADOWSTYLE__SHADOW , data( "shadowstyle.shadow" ) ); + addProp( SHADOWSTYLE__SHADOWOBSURED , data( "shadowstyle.shadowobsured" ) ); + addProp( PERSPECTIVE__TYPE , data( "perspective.type" ) ); + addProp( PERSPECTIVE__OFFSETX , data( "perspective.offsetx" ) ); + addProp( PERSPECTIVE__OFFSETY , data( "perspective.offsety" ) ); + addProp( PERSPECTIVE__SCALEXTOX , data( "perspective.scalextox" ) ); + addProp( PERSPECTIVE__SCALEYTOX , data( "perspective.scaleytox" ) ); + addProp( PERSPECTIVE__SCALEXTOY , data( "perspective.scalextoy" ) ); + addProp( PERSPECTIVE__SCALEYTOY , data( "perspective.scaleytoy" ) ); + addProp( PERSPECTIVE__PERSPECTIVEX , data( "perspective.perspectivex" ) ); + addProp( PERSPECTIVE__PERSPECTIVEY , data( "perspective.perspectivey" ) ); + addProp( PERSPECTIVE__WEIGHT , data( "perspective.weight" ) ); + addProp( PERSPECTIVE__ORIGINX , data( "perspective.originx" ) ); + addProp( PERSPECTIVE__ORIGINY , data( "perspective.originy" ) ); + addProp( PERSPECTIVE__PERSPECTIVEON , data( "perspective.perspectiveon" ) ); + addProp( THREED__SPECULARAMOUNT , data( "3d.specularamount" ) ); + addProp( THREED__DIFFUSEAMOUNT , data( "3d.diffuseamount" ) ); + addProp( THREED__SHININESS , data( "3d.shininess" ) ); + addProp( THREED__EDGETHICKNESS , data( "3d.edgethickness" ) ); + addProp( THREED__EXTRUDEFORWARD , data( "3d.extrudeforward" ) ); + addProp( THREED__EXTRUDEBACKWARD , data( "3d.extrudebackward" ) ); + addProp( THREED__EXTRUDEPLANE , data( "3d.extrudeplane" ) ); + addProp( THREED__EXTRUSIONCOLOR , data( "3d.extrusioncolor", EscherPropertyMetaData.TYPE_RGB ) ); + addProp( THREED__CRMOD , data( "3d.crmod" ) ); + addProp( THREED__3DEFFECT , data( "3d.3deffect" ) ); + addProp( THREED__METALLIC , data( "3d.metallic" ) ); + addProp( THREED__USEEXTRUSIONCOLOR , data( "3d.useextrusioncolor", EscherPropertyMetaData.TYPE_RGB ) ); + addProp( THREED__LIGHTFACE , data( "3d.lightface" ) ); + addProp( THREEDSTYLE__YROTATIONANGLE , data( "3dstyle.yrotationangle" ) ); + addProp( THREEDSTYLE__XROTATIONANGLE , data( "3dstyle.xrotationangle" ) ); + addProp( THREEDSTYLE__ROTATIONAXISX , data( "3dstyle.rotationaxisx" ) ); + addProp( THREEDSTYLE__ROTATIONAXISY , data( "3dstyle.rotationaxisy" ) ); + addProp( THREEDSTYLE__ROTATIONAXISZ , data( "3dstyle.rotationaxisz" ) ); + addProp( THREEDSTYLE__ROTATIONANGLE , data( "3dstyle.rotationangle" ) ); + addProp( THREEDSTYLE__ROTATIONCENTERX , data( "3dstyle.rotationcenterx" ) ); + addProp( THREEDSTYLE__ROTATIONCENTERY , data( "3dstyle.rotationcentery" ) ); + addProp( THREEDSTYLE__ROTATIONCENTERZ , data( "3dstyle.rotationcenterz" ) ); + addProp( THREEDSTYLE__RENDERMODE , data( "3dstyle.rendermode" ) ); + addProp( THREEDSTYLE__TOLERANCE , data( "3dstyle.tolerance" ) ); + addProp( THREEDSTYLE__XVIEWPOINT , data( "3dstyle.xviewpoint" ) ); + addProp( THREEDSTYLE__YVIEWPOINT , data( "3dstyle.yviewpoint" ) ); + addProp( THREEDSTYLE__ZVIEWPOINT , data( "3dstyle.zviewpoint" ) ); + addProp( THREEDSTYLE__ORIGINX , data( "3dstyle.originx" ) ); + addProp( THREEDSTYLE__ORIGINY , data( "3dstyle.originy" ) ); + addProp( THREEDSTYLE__SKEWANGLE , data( "3dstyle.skewangle" ) ); + addProp( THREEDSTYLE__SKEWAMOUNT , data( "3dstyle.skewamount" ) ); + addProp( THREEDSTYLE__AMBIENTINTENSITY , data( "3dstyle.ambientintensity" ) ); + addProp( THREEDSTYLE__KEYX , data( "3dstyle.keyx" ) ); + addProp( THREEDSTYLE__KEYY , data( "3dstyle.keyy" ) ); + addProp( THREEDSTYLE__KEYZ , data( "3dstyle.keyz" ) ); + addProp( THREEDSTYLE__KEYINTENSITY , data( "3dstyle.keyintensity" ) ); + addProp( THREEDSTYLE__FILLX , data( "3dstyle.fillx" ) ); + addProp( THREEDSTYLE__FILLY , data( "3dstyle.filly" ) ); + addProp( THREEDSTYLE__FILLZ , data( "3dstyle.fillz" ) ); + addProp( THREEDSTYLE__FILLINTENSITY , data( "3dstyle.fillintensity" ) ); + addProp( THREEDSTYLE__CONSTRAINROTATION , data( "3dstyle.constrainrotation" ) ); + addProp( THREEDSTYLE__ROTATIONCENTERAUTO , data( "3dstyle.rotationcenterauto" ) ); + addProp( THREEDSTYLE__PARALLEL , data( "3dstyle.parallel" ) ); + addProp( THREEDSTYLE__KEYHARSH , data( "3dstyle.keyharsh" ) ); + addProp( THREEDSTYLE__FILLHARSH , data( "3dstyle.fillharsh" ) ); + addProp( SHAPE__MASTER , data( "shape.master" ) ); + addProp( SHAPE__CONNECTORSTYLE , data( "shape.connectorstyle" ) ); + addProp( SHAPE__BLACKANDWHITESETTINGS , data( "shape.blackandwhitesettings" ) ); + addProp( SHAPE__WMODEPUREBW , data( "shape.wmodepurebw" ) ); + addProp( SHAPE__WMODEBW , data( "shape.wmodebw" ) ); + addProp( SHAPE__OLEICON , data( "shape.oleicon" ) ); + addProp( SHAPE__PREFERRELATIVERESIZE , data( "shape.preferrelativeresize" ) ); + addProp( SHAPE__LOCKSHAPETYPE , data( "shape.lockshapetype" ) ); + addProp( SHAPE__DELETEATTACHEDOBJECT , data( "shape.deleteattachedobject" ) ); + addProp( SHAPE__BACKGROUNDSHAPE , data( "shape.backgroundshape" ) ); + addProp( CALLOUT__CALLOUTTYPE , data( "callout.callouttype" ) ); + addProp( CALLOUT__XYCALLOUTGAP , data( "callout.xycalloutgap" ) ); + addProp( CALLOUT__CALLOUTANGLE , data( "callout.calloutangle" ) ); + addProp( CALLOUT__CALLOUTDROPTYPE , data( "callout.calloutdroptype" ) ); + addProp( CALLOUT__CALLOUTDROPSPECIFIED , data( "callout.calloutdropspecified" ) ); + addProp( CALLOUT__CALLOUTLENGTHSPECIFIED , data( "callout.calloutlengthspecified" ) ); + addProp( CALLOUT__ISCALLOUT , data( "callout.iscallout" ) ); + addProp( CALLOUT__CALLOUTACCENTBAR , data( "callout.calloutaccentbar" ) ); + addProp( CALLOUT__CALLOUTTEXTBORDER , data( "callout.callouttextborder" ) ); + addProp( CALLOUT__CALLOUTMINUSX , data( "callout.calloutminusx" ) ); + addProp( CALLOUT__CALLOUTMINUSY , data( "callout.calloutminusy" ) ); + addProp( CALLOUT__DROPAUTO , data( "callout.dropauto" ) ); + addProp( CALLOUT__LENGTHSPECIFIED , data( "callout.lengthspecified" ) ); + addProp( GROUPSHAPE__SHAPENAME , data( "groupshape.shapename" ) ); + addProp( GROUPSHAPE__DESCRIPTION , data( "groupshape.description" ) ); + addProp( GROUPSHAPE__HYPERLINK , data( "groupshape.hyperlink" ) ); + addProp( GROUPSHAPE__WRAPPOLYGONVERTICES , data( "groupshape.wrappolygonvertices", EscherPropertyMetaData.TYPE_ARRAY ) ); + addProp( GROUPSHAPE__WRAPDISTLEFT , data( "groupshape.wrapdistleft" ) ); + addProp( GROUPSHAPE__WRAPDISTTOP , data( "groupshape.wrapdisttop" ) ); + addProp( GROUPSHAPE__WRAPDISTRIGHT , data( "groupshape.wrapdistright" ) ); + addProp( GROUPSHAPE__WRAPDISTBOTTOM , data( "groupshape.wrapdistbottom" ) ); + addProp( GROUPSHAPE__REGROUPID , data( "groupshape.regroupid" ) ); + addProp( GROUPSHAPE__EDITEDWRAP , data( "groupshape.editedwrap" ) ); + addProp( GROUPSHAPE__BEHINDDOCUMENT , data( "groupshape.behinddocument" ) ); + addProp( GROUPSHAPE__ONDBLCLICKNOTIFY , data( "groupshape.ondblclicknotify" ) ); + addProp( GROUPSHAPE__ISBUTTON , data( "groupshape.isbutton" ) ); + addProp( GROUPSHAPE__1DADJUSTMENT , data( "groupshape.1dadjustment" ) ); + addProp( GROUPSHAPE__HIDDEN , data( "groupshape.hidden" ) ); + addProp( GROUPSHAPE__PRINT , data( "groupshape.print", EscherPropertyMetaData.TYPE_BOOLEAN ) ); + } + } + + private static void addProp( int s, EscherPropertyMetaData data ) + { + properties.put( new Short( (short) s ), data ); + } + + private static EscherPropertyMetaData data( String propName, byte type ) + { + return new EscherPropertyMetaData( propName, type ); + } + + private static EscherPropertyMetaData data( String propName ) + { + return new EscherPropertyMetaData( propName ); + } + + public static String getPropertyName( short propertyId ) + { + initProps(); + EscherPropertyMetaData o = (EscherPropertyMetaData) properties.get( new Short( propertyId ) ); + return o == null ? "unknown" : o.getDescription(); + } + + public static byte getPropertyType( short propertyId ) + { + initProps(); + EscherPropertyMetaData escherPropertyMetaData = (EscherPropertyMetaData) properties.get( new Short( propertyId ) ); + return escherPropertyMetaData == null ? 0 : escherPropertyMetaData.getType(); + } +} + + + diff --git a/src/java/org/apache/poi/ddf/EscherProperty.java b/src/java/org/apache/poi/ddf/EscherProperty.java new file mode 100644 index 0000000000..787ccf2f8d --- /dev/null +++ b/src/java/org/apache/poi/ddf/EscherProperty.java @@ -0,0 +1,78 @@ +package org.apache.poi.ddf; + +/** + * This is the abstract base class for all escher properties. + * + * @see EscherOptRecord + * + * @author Glen Stampoultzis (glens at apache.org) + */ +abstract public class EscherProperty +{ + private short id; + + /** + * The id is distinct from the actual property number. The id includes the property number the blip id + * flag and an indicator whether the property is complex or not. + */ + public EscherProperty( short id ) + { + this.id = id; + } + + /** + * Constructs a new escher property. The three parameters are combined to form a property + * id. + */ + public EscherProperty( short propertyNumber, boolean isComplex, boolean isBlipId ) + { + this.id = (short)(propertyNumber + + (isComplex ? 0x8000 : 0x0) + + (isBlipId ? 0x4000 : 0x0)); + } + + public short getId() + { + return id; + } + + public short getPropertyNumber() + { + return (short) ( id & (short) 0x3FFF ); + } + + public boolean isComplex() + { + return ( id & (short) 0x8000 ) != 0; + } + + public boolean isBlipId() + { + return ( id & (short) 0x4000 ) != 0; + } + + public String getName() + { + return EscherProperties.getPropertyName(id); + } + + /** + * Most properties are just 6 bytes in length. Override this if we're + * dealing with complex properties. + */ + public int getPropertySize() + { + return 6; + } + + /** + * Escher properties consist of a simple fixed length part and a complex variable length part. + * The fixed length part is serialized first. + */ + abstract public int serializeSimplePart( byte[] data, int pos ); + /** + * Escher properties consist of a simple fixed length part and a complex variable length part. + * The fixed length part is serialized first. + */ + abstract public int serializeComplexPart( byte[] data, int pos ); +} diff --git a/src/java/org/apache/poi/ddf/EscherPropertyFactory.java b/src/java/org/apache/poi/ddf/EscherPropertyFactory.java new file mode 100644 index 0000000000..cc6b7ba7a8 --- /dev/null +++ b/src/java/org/apache/poi/ddf/EscherPropertyFactory.java @@ -0,0 +1,91 @@ +package org.apache.poi.ddf; + +import org.apache.poi.util.LittleEndian; +import org.apache.poi.hssf.record.RecordFormatException; + +import java.util.List; +import java.util.ArrayList; +import java.util.Iterator; + +/** + * Generates a property given a reference into the byte array storing that property. + * + * @author Glen Stampoultzis + */ +public class EscherPropertyFactory +{ + /** + * Create new properties from a byte array. + * + * @param data The byte array containing the property + * @param offset The starting offset into the byte array + * @return The new properties + */ + public List createProperties( byte[] data, int offset, short numProperties ) + { + List results = new ArrayList(); + + int pos = offset; + int complexBytes = 0; +// while ( bytesRemaining >= 6 ) + for (int i = 0; i < numProperties; i++) + { + short propId; + int propData; + propId = LittleEndian.getShort( data, pos ); + propData = LittleEndian.getInt( data, pos + 2 ); + short propNumber = (short) ( propId & (short) 0x3FFF ); + boolean isComplex = ( propId & (short) 0x8000 ) != 0; + boolean isBlipId = ( propId & (short) 0x4000 ) != 0; + if ( isComplex ) + complexBytes = propData; + else + complexBytes = 0; + byte propertyType = EscherProperties.getPropertyType( (short) propNumber ); + if ( propertyType == EscherPropertyMetaData.TYPE_BOOLEAN ) + results.add( new EscherBoolProperty( propNumber, propData ) ); + else if ( propertyType == EscherPropertyMetaData.TYPE_RGB ) + results.add( new EscherRGBProperty( propNumber, propData ) ); + else if ( propertyType == EscherPropertyMetaData.TYPE_SHAPEPATH ) + results.add( new EscherShapePathProperty( propNumber, propData ) ); + else + { + if ( !isComplex ) + results.add( new EscherSimpleProperty( propNumber, propData ) ); + else + { + if ( propertyType == EscherPropertyMetaData.TYPE_ARRAY) + results.add( new EscherArrayProperty( propId, new byte[propData]) ); + else + results.add( new EscherComplexProperty( propId, new byte[propData]) ); + + } + } + pos += 6; +// bytesRemaining -= 6 + complexBytes; + } + + // Get complex data + for ( Iterator iterator = results.iterator(); iterator.hasNext(); ) + { + EscherProperty p = (EscherProperty) iterator.next(); + if (p instanceof EscherComplexProperty) + { + if (p instanceof EscherArrayProperty) + { + pos += ((EscherArrayProperty)p).setArrayData(data, pos); + } + else + { + byte[] complexData = ((EscherComplexProperty)p).getComplexData(); + System.arraycopy(data, pos, complexData, 0, complexData.length); + pos += complexData.length; + } + } + } + + return results; + } + + +} diff --git a/src/java/org/apache/poi/ddf/EscherPropertyMetaData.java b/src/java/org/apache/poi/ddf/EscherPropertyMetaData.java new file mode 100644 index 0000000000..97576064e7 --- /dev/null +++ b/src/java/org/apache/poi/ddf/EscherPropertyMetaData.java @@ -0,0 +1,51 @@ +package org.apache.poi.ddf; + +/** + * This class stores the type and description of an escher property. + * + * @author Glen Stampoultzis (glens at apache.org) + */ +public class EscherPropertyMetaData +{ + // Escher property types. + public final static byte TYPE_UNKNOWN = (byte) 0; + public final static byte TYPE_BOOLEAN = (byte) 1; + public final static byte TYPE_RGB = (byte) 2; + public final static byte TYPE_SHAPEPATH = (byte) 3; + public final static byte TYPE_SIMPLE = (byte)4; + public final static byte TYPE_ARRAY = (byte)5;; + + private String description; + private byte type; + + + /** + * @param description The description of the escher property. + */ + public EscherPropertyMetaData( String description ) + { + this.description = description; + } + + /** + * + * @param description The description of the escher property. + * @param type The type of the property. + */ + public EscherPropertyMetaData( String description, byte type ) + { + this.description = description; + this.type = type; + } + + public String getDescription() + { + return description; + } + + public byte getType() + { + return type; + } + +} diff --git a/src/java/org/apache/poi/ddf/EscherRGBProperty.java b/src/java/org/apache/poi/ddf/EscherRGBProperty.java new file mode 100644 index 0000000000..1b8c9d2952 --- /dev/null +++ b/src/java/org/apache/poi/ddf/EscherRGBProperty.java @@ -0,0 +1,37 @@ +package org.apache.poi.ddf; + +/** + * A color property. + * + * @author Glen Stampoultzis (glens at apache.org) + */ +public class EscherRGBProperty + extends EscherSimpleProperty +{ + + public EscherRGBProperty( short propertyNumber, int rgbColor ) + { + super( propertyNumber, false, false, rgbColor ); + } + + public int getRgbColor() + { + return propertyValue; + } + + public byte getRed() + { + return (byte) ( propertyValue & 0xFF ); + } + + public byte getGreen() + { + return (byte) ( (propertyValue >> 8) & 0xFF ); + } + + public byte getBlue() + { + return (byte) ( (propertyValue >> 16) & 0xFF ); + } + +} diff --git a/src/java/org/apache/poi/ddf/EscherRecord.java b/src/java/org/apache/poi/ddf/EscherRecord.java new file mode 100644 index 0000000000..0001bbfec0 --- /dev/null +++ b/src/java/org/apache/poi/ddf/EscherRecord.java @@ -0,0 +1,273 @@ +package org.apache.poi.ddf; + +import org.apache.poi.util.LittleEndian; + +import java.io.PrintWriter; +import java.util.Collections; +import java.util.List; + +/** + * The base abstract record from which all escher records are defined. Subclasses will need + * to define methods for serialization/deserialization and for determining the record size. + * + * @author Glen Stampoultzis + */ +abstract public class EscherRecord +{ + private short options; + private short recordId; + + /** + * Create a new instance + */ + public EscherRecord() + { + } + + /** + * Delegates to fillFields(byte[], int, EscherRecordFactory) + * + * @see #fillFields(byte[], int, org.apache.poi.ddf.EscherRecordFactory) + */ + protected int fillFields( byte[] data, EscherRecordFactory f ) + { + return fillFields( data, 0, f ); + } + + /** + * The contract of this method is to deserialize an escher record including + * it's children. + * + * @param data The byte array containing the serialized escher + * records. + * @param offset The offset into the byte array. + * @param recordFactory A factory for creating new escher records. + * @return The number of bytes written. + */ + public abstract int fillFields( byte[] data, int offset, EscherRecordFactory recordFactory ); + + /** + * Reads the 8 byte header information and populates the <code>options</code> + * and <code>recordId</code> records. + * + * @param data the byte array to read from + * @param offset the offset to start reading from + * @return the number of bytes remaining in this record. This + * may include the children if this is a container. + */ + protected int readHeader( byte[] data, int offset ) + { + EscherRecordHeader header = EscherRecordHeader.readHeader(data, offset); + options = header.getOptions(); + recordId = header.getRecordId(); + return header.getRemainingBytes(); + } + + /** + * Determine whether this is a container record by inspecting the option + * field. + * @return true is this is a container field. + */ + public boolean isContainerRecord() + { + return (options & (short)0x000f) == (short)0x000f; + } + + /** + * @return The options field for this record. All records have one. + */ + public short getOptions() + { + return options; + } + + /** + * Set the options this this record. Container records should have the + * last nibble set to 0xF. + */ + public void setOptions( short options ) + { + this.options = options; + } + + /** + * Serializes to a new byte array. This is done by delegating to + * serialize(int, byte[]); + * + * @return the serialized record. + * @see #serialize(int, byte[]) + */ + public byte[] serialize() + { + byte[] retval = new byte[getRecordSize()]; + + serialize( 0, retval ); + return retval; + } + + /** + * Serializes to an existing byte array without serialization listener. + * This is done by delegating to serialize(int, byte[], EscherSerializationListener). + * + * @param offset the offset within the data byte array. + * @param data the data array to serialize to. + * @return The number of bytes written. + * + * @see #serialize(int, byte[], org.apache.poi.ddf.EscherSerializationListener) + */ + public int serialize( int offset, byte[] data) + { + return serialize( offset, data, new NullEscherSerializationListener() ); + } + + /** + * Serializes the record to an existing byte array. + * + * @param offset the offset within the byte array + * @param data the data array to serialize to + * @param listener a listener for begin and end serialization events. This + * is useful because the serialization is + * hierarchical/recursive and sometimes you need to be able + * break into that. + * @return the number of bytes written. + */ + public abstract int serialize( int offset, byte[] data, EscherSerializationListener listener ); + + /** + * Subclasses should effeciently return the number of bytes required to + * serialize the record. + * + * @return number of bytes + */ + abstract public int getRecordSize(); + + /** + * Return the current record id. + * + * @return The 16 bit record id. + */ + public short getRecordId() + { + return recordId; + } + + /** + * Sets the record id for this record. + */ + public void setRecordId( short recordId ) + { + this.recordId = recordId; + } + + /** + * @return Returns the children of this record. By default this will + * be an empty list. EscherCotainerRecord is the only record + * that may contain children. + * + * @see EscherContainerRecord + */ + public List getChildRecords() { return Collections.EMPTY_LIST; } + + /** + * Sets the child records for this record. By default this will throw + * an exception as only EscherContainerRecords may have children. + * + * @param childRecords Not used in base implementation. + */ + public void setChildRecords( List childRecords ) { throw new IllegalArgumentException("This record does not support child records."); } + + /** + * Escher records may need to be clonable in the future. + */ + public Object clone() + { + throw new RuntimeException( "The class " + getClass().getName() + " needs to define a clone method" ); + } + + /** + * Returns the indexed child record. + */ + public EscherRecord getChild( int index ) + { + return (EscherRecord) getChildRecords().get(index); + } + + /** + * The display methods allows escher variables to print the record names + * according to their hierarchy. + * + * @param w The print writer to output to. + * @param indent The current indent level. + */ + public void display(PrintWriter w, int indent) + { + for (int i = 0; i < indent * 4; i++) w.print(' '); + w.println(getRecordName()); + } + + /** + * Subclasses should return the short name for this escher record. + */ + public abstract String getRecordName(); + + /** + * Returns the instance part of the option record. + * + * @return The instance part of the record + */ + public short getInstance() + { + return (short) ( options >> 4 ); + } + + /** + * This class reads the standard escher header. + */ + static class EscherRecordHeader + { + private short options; + private short recordId; + private int remainingBytes; + + private EscherRecordHeader() + { + } + + public static EscherRecordHeader readHeader( byte[] data, int offset ) + { + EscherRecordHeader header = new EscherRecordHeader(); + header.options = LittleEndian.getShort(data, offset); + header.recordId = LittleEndian.getShort(data, offset + 2); + header.remainingBytes = LittleEndian.getInt( data, offset + 4 ); + return header; + } + + + public short getOptions() + { + return options; + } + + public short getRecordId() + { + return recordId; + } + + public int getRemainingBytes() + { + return remainingBytes; + } + + public String toString() + { + return "EscherRecordHeader{" + + "options=" + options + + ", recordId=" + recordId + + ", remainingBytes=" + remainingBytes + + "}"; + } + + + } + +} diff --git a/src/java/org/apache/poi/ddf/EscherRecordFactory.java b/src/java/org/apache/poi/ddf/EscherRecordFactory.java new file mode 100644 index 0000000000..4c6e22c8a6 --- /dev/null +++ b/src/java/org/apache/poi/ddf/EscherRecordFactory.java @@ -0,0 +1,16 @@ +package org.apache.poi.ddf; + +/** + * The escher record factory interface allows for the creation of escher + * records from a pointer into a data array. + * + * @author Glen Stampoultzis (glens at apache.org) + */ +public interface EscherRecordFactory +{ + /** + * Create a new escher record from the data provided. Does not attempt + * to fill the contents of the record however. + */ + EscherRecord createRecord( byte[] data, int offset ); +} diff --git a/src/java/org/apache/poi/ddf/EscherSerializationListener.java b/src/java/org/apache/poi/ddf/EscherSerializationListener.java new file mode 100644 index 0000000000..ddea267c7e --- /dev/null +++ b/src/java/org/apache/poi/ddf/EscherSerializationListener.java @@ -0,0 +1,27 @@ +package org.apache.poi.ddf; + +/** + * Interface for listening to escher serialization events. + * + * @author Glen Stampoultzis (glens at apache.org) + */ +public interface EscherSerializationListener +{ + /** + * Fired before a given escher record is serialized. + * + * @param offset The position in the data array at which the record will be serialized. + * @param recordId The id of the record about to be serialized. + */ + void beforeRecordSerialize(int offset, short recordId, EscherRecord record); + + /** + * Fired after a record has been serialized. + * + * @param offset The position of the end of the serialized record + 1 + * @param recordId The id of the record about to be serialized + * @param size The number of bytes written for this record. If it is a container + * record then this will include the size of any included records. + */ + void afterRecordSerialize(int offset, short recordId, int size, EscherRecord record); +} diff --git a/src/java/org/apache/poi/ddf/EscherShapePathProperty.java b/src/java/org/apache/poi/ddf/EscherShapePathProperty.java new file mode 100644 index 0000000000..df70e9c83f --- /dev/null +++ b/src/java/org/apache/poi/ddf/EscherShapePathProperty.java @@ -0,0 +1,25 @@ +package org.apache.poi.ddf; + +/** + * Defines the constants for the various possible shape paths. + * + * @author Glen Stampoultzis (glens at apache.org) + */ +public class EscherShapePathProperty + extends EscherSimpleProperty +{ + + public static final int LINE_OF_STRAIGHT_SEGMENTS = 0; + public static final int CLOSED_POLYGON = 1; + public static final int CURVES = 2; + public static final int CLOSED_CURVES = 3; + public static final int COMPLEX = 4; + + public EscherShapePathProperty( short propertyNumber, int shapePath ) + { + super( propertyNumber, false, false, shapePath ); + } + + + +} diff --git a/src/java/org/apache/poi/ddf/EscherSimpleProperty.java b/src/java/org/apache/poi/ddf/EscherSimpleProperty.java new file mode 100644 index 0000000000..b61474efdd --- /dev/null +++ b/src/java/org/apache/poi/ddf/EscherSimpleProperty.java @@ -0,0 +1,103 @@ +package org.apache.poi.ddf; + +import org.apache.poi.util.LittleEndian; +import org.apache.poi.util.HexDump; + +/** + * A simple property is of fixed length and as a property number in addition + * to a 32-bit value. Properties that can't be stored in only 32-bits are + * stored as EscherComplexProperty objects. + * + * @author Glen Stampoultzis (glens at apache.org) + */ +public class EscherSimpleProperty extends EscherProperty +{ + protected int propertyValue; + + /** + * The id is distinct from the actual property number. The id includes the property number the blip id + * flag and an indicator whether the property is complex or not. + */ + public EscherSimpleProperty( short id, int propertyValue ) + { + super( id ); + this.propertyValue = propertyValue; + } + + /** + * Constructs a new escher property. The three parameters are combined to form a property + * id. + */ + public EscherSimpleProperty( short propertyNumber, boolean isComplex, boolean isBlipId, int propertyValue ) + { + super( propertyNumber, isComplex, isBlipId ); + this.propertyValue = propertyValue; + } + + /** + * Serialize the simple part of the escher record. + * + * @return the number of bytes serialized. + */ + public int serializeSimplePart( byte[] data, int offset ) + { + LittleEndian.putShort(data, offset, getId()); + LittleEndian.putInt(data, offset + 2, propertyValue); + return 6; + } + + /** + * Escher properties consist of a simple fixed length part and a complex variable length part. + * The fixed length part is serialized first. + */ + public int serializeComplexPart( byte[] data, int pos ) + { + return 0; + } + + /** + * @return Return the 32 bit value of this property. + */ + public int getPropertyValue() + { + return propertyValue; + } + + /** + * Returns true if one escher property is equal to another. + */ + public boolean equals( Object o ) + { + if ( this == o ) return true; + if ( !( o instanceof EscherSimpleProperty ) ) return false; + + final EscherSimpleProperty escherSimpleProperty = (EscherSimpleProperty) o; + + if ( propertyValue != escherSimpleProperty.propertyValue ) return false; + if ( getId() != escherSimpleProperty.getId() ) return false; + + return true; + } + + /** + * Returns a hashcode so that this object can be stored in collections that + * require the use of such things. + */ + public int hashCode() + { + return propertyValue; + } + + /** + * @return the string representation of this property. + */ + public String toString() + { + return "propNum: " + getPropertyNumber() + + ", propName: " + EscherProperties.getPropertyName( getPropertyNumber() ) + + ", complex: " + isComplex() + + ", blipId: " + isBlipId() + + ", value: " + propertyValue + " (0x" + HexDump.toHex(propertyValue) + ")"; + } + +} diff --git a/src/java/org/apache/poi/ddf/EscherSpRecord.java b/src/java/org/apache/poi/ddf/EscherSpRecord.java new file mode 100644 index 0000000000..a5e2be9ff9 --- /dev/null +++ b/src/java/org/apache/poi/ddf/EscherSpRecord.java @@ -0,0 +1,201 @@ +package org.apache.poi.ddf; + +import org.apache.poi.util.HexDump; +import org.apache.poi.util.LittleEndian; + +/** + * Together the the EscherOptRecord this record defines some of the basic + * properties of a shape. + * + * @author Glen Stampoultzis (glens at apache.org) + */ +public class EscherSpRecord + extends EscherRecord +{ + public static final short RECORD_ID = (short) 0xF00A; + public static final String RECORD_DESCRIPTION = "MsofbtSp"; + + public static final int FLAG_GROUP = 0x0001; + public static final int FLAG_CHILD = 0x0002; + public static final int FLAG_PATRIARCH = 0x0004; + public static final int FLAG_DELETED = 0x0008; + public static final int FLAG_OLESHAPE = 0x0010; + public static final int FLAG_HAVEMASTER = 0x0020; + public static final int FLAG_FLIPHORIZ = 0x0040; + public static final int FLAG_FLIPVERT = 0x0080; + public static final int FLAG_CONNECTOR = 0x0100; + public static final int FLAG_HAVEANCHOR = 0x0200; + public static final int FLAG_BACKGROUND = 0x0400; + public static final int FLAG_HASSHAPETYPE = 0x0800; + + private int field_1_shapeId; + private int field_2_flags; + + /** + * This method deserializes the record from a byte array. + * + * @param data The byte array containing the escher record information + * @param offset The starting offset into <code>data</code>. + * @param recordFactory May be null since this is not a container record. + * @return The number of bytes read from the byte array. + */ + public int fillFields( byte[] data, int offset, EscherRecordFactory recordFactory ) + { + int bytesRemaining = readHeader( data, offset ); + int pos = offset + 8; + int size = 0; + field_1_shapeId = LittleEndian.getInt( data, pos + size ); size += 4; + field_2_flags = LittleEndian.getInt( data, pos + size ); size += 4; +// bytesRemaining -= size; +// remainingData = new byte[bytesRemaining]; +// System.arraycopy( data, pos + size, remainingData, 0, bytesRemaining ); + return getRecordSize(); + } + + /** + * This method serializes this escher record into a byte array. + * + * @param offset The offset into <code>data</code> to start writing the record data to. + * @param data The byte array to serialize to. + * @param listener A listener to retrieve start and end callbacks. Use a <code>NullEscherSerailizationListener</code> to ignore these events. + * @return The number of bytes written. + * + * @see NullEscherSerializationListener + */ + public int serialize( int offset, byte[] data, EscherSerializationListener listener ) + { + listener.beforeRecordSerialize( offset, getRecordId(), this ); + LittleEndian.putShort( data, offset, getOptions() ); + LittleEndian.putShort( data, offset + 2, getRecordId() ); + int remainingBytes = 8; + LittleEndian.putInt( data, offset + 4, remainingBytes ); + LittleEndian.putInt( data, offset + 8, field_1_shapeId ); + LittleEndian.putInt( data, offset + 12, field_2_flags ); +// System.arraycopy( remainingData, 0, data, offset + 26, remainingData.length ); +// int pos = offset + 8 + 18 + remainingData.length; + listener.afterRecordSerialize( offset + getRecordSize(), getRecordId(), getRecordSize(), this ); + return 8 + 8; + } + + /** + * Returns the number of bytes that are required to serialize this record. + * + * @return Number of bytes + */ + public int getRecordSize() + { + return 8 + 8; + } + + /** + * @return the 16 bit identifier for this record. + */ + public short getRecordId() + { + return RECORD_ID; + } + + /** + * The short name for this record + */ + public String getRecordName() + { + return "Sp"; + } + + /** + * @return the string representing this shape. + */ + public String toString() + { + String nl = System.getProperty("line.separator"); + + return getClass().getName() + ":" + nl + + " RecordId: 0x" + HexDump.toHex(RECORD_ID) + nl + + " Options: 0x" + HexDump.toHex(getOptions()) + nl + + " ShapeId: " + field_1_shapeId + nl + + " Flags: " + decodeFlags(field_2_flags) + " (0x" + HexDump.toHex(field_2_flags) + ")" + nl; + + } + + /** + * Converts the shape flags into a more descriptive name. + */ + private String decodeFlags( int flags ) + { + StringBuffer result = new StringBuffer(); + result.append( ( flags & FLAG_GROUP ) != 0 ? "|GROUP" : "" ); + result.append( ( flags & FLAG_CHILD ) != 0 ? "|CHILD" : "" ); + result.append( ( flags & FLAG_PATRIARCH ) != 0 ? "|PATRIARCH" : "" ); + result.append( ( flags & FLAG_DELETED ) != 0 ? "|DELETED" : "" ); + result.append( ( flags & FLAG_OLESHAPE ) != 0 ? "|OLESHAPE" : "" ); + result.append( ( flags & FLAG_HAVEMASTER ) != 0 ? "|HAVEMASTER" : "" ); + result.append( ( flags & FLAG_FLIPHORIZ ) != 0 ? "|FLIPHORIZ" : "" ); + result.append( ( flags & FLAG_FLIPVERT ) != 0 ? "|FLIPVERT" : "" ); + result.append( ( flags & FLAG_CONNECTOR ) != 0 ? "|CONNECTOR" : "" ); + result.append( ( flags & FLAG_HAVEANCHOR ) != 0 ? "|HAVEANCHOR" : "" ); + result.append( ( flags & FLAG_BACKGROUND ) != 0 ? "|BACKGROUND" : "" ); + result.append( ( flags & FLAG_HASSHAPETYPE ) != 0 ? "|HASSHAPETYPE" : "" ); + + result.deleteCharAt(0); + return result.toString(); + } + + /** + * @return A number that identifies this shape + */ + public int getShapeId() + { + return field_1_shapeId; + } + + /** + * Sets a number that identifies this shape. + */ + public void setShapeId( int field_1_shapeId ) + { + this.field_1_shapeId = field_1_shapeId; + } + + /** + * The flags that apply to this shape. + * + * @see #FLAG_GROUP + * @see #FLAG_CHILD + * @see #FLAG_PATRIARCH + * @see #FLAG_DELETED + * @see #FLAG_OLESHAPE + * @see #FLAG_HAVEMASTER + * @see #FLAG_FLIPHORIZ + * @see #FLAG_FLIPVERT + * @see #FLAG_CONNECTOR + * @see #FLAG_HAVEANCHOR + * @see #FLAG_BACKGROUND + * @see #FLAG_HASSHAPETYPE + */ + public int getFlags() + { + return field_2_flags; + } + + /** + * The flags that apply to this shape. + * + * @see #FLAG_GROUP + * @see #FLAG_CHILD + * @see #FLAG_PATRIARCH + * @see #FLAG_DELETED + * @see #FLAG_OLESHAPE + * @see #FLAG_HAVEMASTER + * @see #FLAG_FLIPHORIZ + * @see #FLAG_FLIPVERT + * @see #FLAG_CONNECTOR + * @see #FLAG_HAVEANCHOR + * @see #FLAG_BACKGROUND + * @see #FLAG_HASSHAPETYPE + */ + public void setFlags( int field_2_flags ) + { + this.field_2_flags = field_2_flags; + } +} diff --git a/src/java/org/apache/poi/ddf/EscherSpgrRecord.java b/src/java/org/apache/poi/ddf/EscherSpgrRecord.java new file mode 100644 index 0000000000..7e86108e0d --- /dev/null +++ b/src/java/org/apache/poi/ddf/EscherSpgrRecord.java @@ -0,0 +1,192 @@ +package org.apache.poi.ddf; + +import org.apache.poi.util.HexDump; +import org.apache.poi.util.LittleEndian; +import org.apache.poi.hssf.record.RecordFormatException; + +/** + * The spgr record defines information about a shape group. Groups in escher + * are simply another form of shape that you can't physically see. + * + * @author Glen Stampoultzis (glens at apache.org) + */ +public class EscherSpgrRecord + extends EscherRecord +{ + public static final short RECORD_ID = (short) 0xF009; + public static final String RECORD_DESCRIPTION = "MsofbtSpgr"; + + private int field_1_rectX1; + private int field_2_rectY1; + private int field_3_rectX2; + private int field_4_rectY2; + + /** + * This method deserializes the record from a byte array. + * + * @param data The byte array containing the escher record information + * @param offset The starting offset into <code>data</code>. + * @param recordFactory May be null since this is not a container record. + * @return The number of bytes read from the byte array. + */ + public int fillFields( byte[] data, int offset, EscherRecordFactory recordFactory ) + { + int bytesRemaining = readHeader( data, offset ); + int pos = offset + 8; + int size = 0; + field_1_rectX1 = LittleEndian.getInt( data, pos + size );size+=4; + field_2_rectY1 = LittleEndian.getInt( data, pos + size );size+=4; + field_3_rectX2 = LittleEndian.getInt( data, pos + size );size+=4; + field_4_rectY2 = LittleEndian.getInt( data, pos + size );size+=4; + bytesRemaining -= size; + if (bytesRemaining != 0) throw new RecordFormatException("Expected no remaining bytes but got " + bytesRemaining); +// remainingData = new byte[bytesRemaining]; +// System.arraycopy( data, pos + size, remainingData, 0, bytesRemaining ); + return 8 + size + bytesRemaining; + } + + /** + * This method serializes this escher record into a byte array. + * + * @param offset The offset into <code>data</code> to start writing the record data to. + * @param data The byte array to serialize to. + * @param listener A listener to retrieve start and end callbacks. Use a <code>NullEscherSerailizationListener</code> to ignore these events. + * @return The number of bytes written. + * + * @see NullEscherSerializationListener + */ + public int serialize( int offset, byte[] data, EscherSerializationListener listener ) + { + listener.beforeRecordSerialize( offset, getRecordId(), this ); + LittleEndian.putShort( data, offset, getOptions() ); + LittleEndian.putShort( data, offset + 2, getRecordId() ); + int remainingBytes = 16; + LittleEndian.putInt( data, offset + 4, remainingBytes ); + LittleEndian.putInt( data, offset + 8, field_1_rectX1 ); + LittleEndian.putInt( data, offset + 12, field_2_rectY1 ); + LittleEndian.putInt( data, offset + 16, field_3_rectX2 ); + LittleEndian.putInt( data, offset + 20, field_4_rectY2 ); +// System.arraycopy( remainingData, 0, data, offset + 26, remainingData.length ); +// int pos = offset + 8 + 18 + remainingData.length; + listener.afterRecordSerialize( offset + getRecordSize(), getRecordId(), offset + getRecordSize(), this ); + return 8 + 16; + } + + /** + * Returns the number of bytes that are required to serialize this record. + * + * @return Number of bytes + */ + public int getRecordSize() + { + return 8 + 16; + } + + /** + * The 16 bit identifier of this shape group record. + */ + public short getRecordId() + { + return RECORD_ID; + } + + /** + * The short name for this record + */ + public String getRecordName() + { + return "Spgr"; + } + + /** + * @return the string representation of this record. + */ + public String toString() + { + String nl = System.getProperty("line.separator"); + +// String extraData; +// ByteArrayOutputStream b = new ByteArrayOutputStream(); +// try +// { +// HexDump.dump(this.remainingData, 0, b, 0); +// extraData = b.toString(); +// } +// catch ( Exception e ) +// { +// extraData = "error"; +// } + return getClass().getName() + ":" + nl + + " RecordId: 0x" + HexDump.toHex(RECORD_ID) + nl + + " Options: 0x" + HexDump.toHex(getOptions()) + nl + + " RectX: " + field_1_rectX1 + nl + + " RectY: " + field_2_rectY1 + nl + + " RectWidth: " + field_3_rectX2 + nl + + " RectHeight: " + field_4_rectY2 + nl; + + } + + /** + * The starting top-left coordinate of child records. + */ + public int getRectX1() + { + return field_1_rectX1; + } + + /** + * The starting top-left coordinate of child records. + */ + public void setRectX1( int x1 ) + { + this.field_1_rectX1 = x1; + } + + /** + * The starting top-left coordinate of child records. + */ + public int getRectY1() + { + return field_2_rectY1; + } + + /** + * The starting top-left coordinate of child records. + */ + public void setRectY1( int y1 ) + { + this.field_2_rectY1 = y1; + } + + /** + * The starting bottom-right coordinate of child records. + */ + public int getRectX2() + { + return field_3_rectX2; + } + + /** + * The starting bottom-right coordinate of child records. + */ + public void setRectX2( int x2 ) + { + this.field_3_rectX2 = x2; + } + + /** + * The starting bottom-right coordinate of child records. + */ + public int getRectY2() + { + return field_4_rectY2; + } + + /** + * The starting bottom-right coordinate of child records. + */ + public void setRectY2( int field_4_rectY2 ) + { + this.field_4_rectY2 = field_4_rectY2; + } +} diff --git a/src/java/org/apache/poi/ddf/EscherSplitMenuColorsRecord.java b/src/java/org/apache/poi/ddf/EscherSplitMenuColorsRecord.java new file mode 100644 index 0000000000..2790d4eacf --- /dev/null +++ b/src/java/org/apache/poi/ddf/EscherSplitMenuColorsRecord.java @@ -0,0 +1,171 @@ +package org.apache.poi.ddf; + +import org.apache.poi.util.HexDump; +import org.apache.poi.util.LittleEndian; +import org.apache.poi.hssf.record.RecordFormatException; + +/** + * A list of the most recently used colours for the drawings contained in + * this document. + * + * @author Glen Stampoultzis (glens at apache.org) + */ +public class EscherSplitMenuColorsRecord + extends EscherRecord +{ + public static final short RECORD_ID = (short) 0xF11E; + public static final String RECORD_DESCRIPTION = "MsofbtSplitMenuColors"; + + private int field_1_color1; + private int field_2_color2; + private int field_3_color3; + private int field_4_color4; + + /** + * This method deserializes the record from a byte array. + * + * @param data The byte array containing the escher record information + * @param offset The starting offset into <code>data</code>. + * @param recordFactory May be null since this is not a container record. + * @return The number of bytes read from the byte array. + */ + public int fillFields( byte[] data, int offset, EscherRecordFactory recordFactory ) + { + int bytesRemaining = readHeader( data, offset ); + int pos = offset + 8; + int size = 0; + field_1_color1 = LittleEndian.getInt( data, pos + size );size+=4; + field_2_color2 = LittleEndian.getInt( data, pos + size );size+=4; + field_3_color3 = LittleEndian.getInt( data, pos + size );size+=4; + field_4_color4 = LittleEndian.getInt( data, pos + size );size+=4; + bytesRemaining -= size; + if (bytesRemaining != 0) + throw new RecordFormatException("Expecting no remaining data but got " + bytesRemaining + " byte(s)."); + return 8 + size + bytesRemaining; + } + + /** + * This method serializes this escher record into a byte array. + * + * @param offset The offset into <code>data</code> to start writing the record data to. + * @param data The byte array to serialize to. + * @param listener A listener to retrieve start and end callbacks. Use a <code>NullEscherSerailizationListener</code> to ignore these events. + * @return The number of bytes written. + * + * @see NullEscherSerializationListener + */ + public int serialize( int offset, byte[] data, EscherSerializationListener listener ) + { +// int field_2_numIdClusters = field_5_fileIdClusters.length + 1; + listener.beforeRecordSerialize( offset, getRecordId(), this ); + + int pos = offset; + LittleEndian.putShort( data, pos, getOptions() ); pos += 2; + LittleEndian.putShort( data, pos, getRecordId() ); pos += 2; + int remainingBytes = getRecordSize() - 8; + + LittleEndian.putInt( data, pos, remainingBytes ); pos += 4; + LittleEndian.putInt( data, pos, field_1_color1 ); pos += 4; + LittleEndian.putInt( data, pos, field_2_color2 ); pos += 4; + LittleEndian.putInt( data, pos, field_3_color3 ); pos += 4; + LittleEndian.putInt( data, pos, field_4_color4 ); pos += 4; + listener.afterRecordSerialize( pos, getRecordId(), pos - offset, this ); + return getRecordSize(); + } + + /** + * Returns the number of bytes that are required to serialize this record. + * + * @return Number of bytes + */ + public int getRecordSize() + { + return 8 + 4 * 4; + } + + /** + * @return the 16 bit identifer for this record. + */ + public short getRecordId() + { + return RECORD_ID; + } + + /** + * The short name for this record + */ + public String getRecordName() + { + return "SplitMenuColors"; + } + + /** + * @return a string representation of this record. + */ + public String toString() + { + String nl = System.getProperty("line.separator"); + +// String extraData; +// ByteArrayOutputStream b = new ByteArrayOutputStream(); +// try +// { +// HexDump.dump(this.remainingData, 0, b, 0); +// extraData = b.toString(); +// } +// catch ( Exception e ) +// { +// extraData = "error"; +// } + return getClass().getName() + ":" + nl + + " RecordId: 0x" + HexDump.toHex(RECORD_ID) + nl + + " Options: 0x" + HexDump.toHex(getOptions()) + nl + + " Color1: 0x" + HexDump.toHex(field_1_color1) + nl + + " Color2: 0x" + HexDump.toHex(field_2_color2) + nl + + " Color3: 0x" + HexDump.toHex(field_3_color3) + nl + + " Color4: 0x" + HexDump.toHex(field_4_color4) + nl + + ""; + + } + + public int getColor1() + { + return field_1_color1; + } + + public void setColor1( int field_1_color1 ) + { + this.field_1_color1 = field_1_color1; + } + + public int getColor2() + { + return field_2_color2; + } + + public void setColor2( int field_2_color2 ) + { + this.field_2_color2 = field_2_color2; + } + + public int getColor3() + { + return field_3_color3; + } + + public void setColor3( int field_3_color3 ) + { + this.field_3_color3 = field_3_color3; + } + + public int getColor4() + { + return field_4_color4; + } + + public void setColor4( int field_4_color4 ) + { + this.field_4_color4 = field_4_color4; + } + +} diff --git a/src/java/org/apache/poi/ddf/EscherTextboxRecord.java b/src/java/org/apache/poi/ddf/EscherTextboxRecord.java new file mode 100644 index 0000000000..34dc4fca29 --- /dev/null +++ b/src/java/org/apache/poi/ddf/EscherTextboxRecord.java @@ -0,0 +1,215 @@ +/* ==================================================================== + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2002 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Apache" and "Apache Software Foundation" and + * "Apache POI" must not be used to endorse or promote products + * derived from this software without prior written permission. For + * written permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * "Apache POI", nor may "Apache" appear in their name, without + * prior written permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + */ +package org.apache.poi.ddf; + +import org.apache.poi.util.HexDump; +import org.apache.poi.util.LittleEndian; +import org.apache.poi.hssf.record.RecordFormatException; + +import java.util.Iterator; +import java.util.List; +import java.util.ArrayList; + +/** + * Supports text boxes + * + * @author Glen Stampoultzis (glens at apache.org) + */ +public class EscherTextboxRecord extends EscherRecord +{ + public static final short RECORD_ID = (short)0xF00D; + public static final String RECORD_DESCRIPTION = "msofbtClientTextbox"; + + private static final byte[] NO_BYTES = new byte[0]; + + /** The data for this record not including the the 8 byte header */ + private byte[] thedata = NO_BYTES; + + public EscherTextboxRecord() + { + } + + /** + * This method deserializes the record from a byte array. + * + * @param data The byte array containing the escher record information + * @param offset The starting offset into <code>data</code>. + * @param recordFactory May be null since this is not a container record. + * @return The number of bytes read from the byte array. + */ + public int fillFields( byte[] data, int offset, EscherRecordFactory recordFactory ) + { + int bytesRemaining = readHeader( data, offset ); + if ( isContainerRecord() ) + { + int bytesWritten = 0; + thedata = new byte[0]; + offset += 8; + bytesWritten += 8; + while ( bytesRemaining > 0 ) + { + EscherRecord child = recordFactory.createRecord( data, offset ); + int childBytesWritten = child.fillFields( data, offset, recordFactory ); + bytesWritten += childBytesWritten; + offset += childBytesWritten; + bytesRemaining -= childBytesWritten; + getChildRecords().add( child ); + } + return bytesWritten; + } + else + { + thedata = new byte[bytesRemaining]; + System.arraycopy( data, offset + 8, thedata, 0, bytesRemaining ); + return bytesRemaining + 8; + } + } + + /** + * Writes this record and any contained records to the supplied byte + * array. + * + * @return the number of bytes written. + */ + public int serialize( int offset, byte[] data, EscherSerializationListener listener ) + { + listener.beforeRecordSerialize( offset, getRecordId(), this ); + + LittleEndian.putShort(data, offset, getOptions()); + LittleEndian.putShort(data, offset+2, getRecordId()); + int remainingBytes = thedata.length; + for ( Iterator iterator = getChildRecords().iterator(); iterator.hasNext(); ) + { + EscherRecord r = (EscherRecord) iterator.next(); + remainingBytes += r.getRecordSize(); + } + LittleEndian.putInt(data, offset+4, remainingBytes); + System.arraycopy(thedata, 0, data, offset+8, thedata.length); + int pos = offset+8+thedata.length; + for ( Iterator iterator = getChildRecords().iterator(); iterator.hasNext(); ) + { + EscherRecord r = (EscherRecord) iterator.next(); + pos += r.serialize(pos, data, listener ); + } + + listener.afterRecordSerialize( pos, getRecordId(), pos - offset, this ); + int size = pos - offset; + if (size != getRecordSize()) + throw new RecordFormatException(size + " bytes written but getRecordSize() reports " + getRecordSize()); + return size; + } + + /** + * Returns any extra data associated with this record. In practice excel + * does not seem to put anything here. + */ + public byte[] getData() + { + return thedata; + } + + /** + * Returns the number of bytes that are required to serialize this record. + * + * @return Number of bytes + */ + public int getRecordSize() + { + return 8 + thedata.length; + } + + public Object clone() + { + // shallow clone + return super.clone(); + } + + /** + * The short name for this record + */ + public String getRecordName() + { + return "ClientTextbox"; + } + + public String toString() + { + String nl = System.getProperty( "line.separator" ); + + String theDumpHex = ""; + try + { + if (thedata.length != 0) + { + theDumpHex = " Extra Data:" + nl; + theDumpHex += HexDump.dump(thedata, 0, 0); + } + } + catch ( Exception e ) + { + theDumpHex = "Error!!"; + } + + return getClass().getName() + ":" + nl + + " isContainer: " + isContainerRecord() + nl + + " options: 0x" + HexDump.toHex( getOptions() ) + nl + + " recordId: 0x" + HexDump.toHex( getRecordId() ) + nl + + " numchildren: " + getChildRecords().size() + nl + + theDumpHex; + } + +} + + + diff --git a/src/java/org/apache/poi/ddf/NullEscherSerializationListener.java b/src/java/org/apache/poi/ddf/NullEscherSerializationListener.java new file mode 100644 index 0000000000..10a1e798c4 --- /dev/null +++ b/src/java/org/apache/poi/ddf/NullEscherSerializationListener.java @@ -0,0 +1,20 @@ +package org.apache.poi.ddf; + +/** + * Ignores all serialization events. + * + * @author Glen Stampoultzis (glens at apache.org) + */ +public class NullEscherSerializationListener implements EscherSerializationListener +{ + public void beforeRecordSerialize( int offset, short recordId, EscherRecord record ) + { + // do nothing + } + + public void afterRecordSerialize( int offset, short recordId, int size, EscherRecord record ) + { + // do nothing + } + +} diff --git a/src/java/org/apache/poi/ddf/TestEscherBSERecord.java b/src/java/org/apache/poi/ddf/TestEscherBSERecord.java new file mode 100644 index 0000000000..3778347d71 --- /dev/null +++ b/src/java/org/apache/poi/ddf/TestEscherBSERecord.java @@ -0,0 +1,87 @@ +package org.apache.poi.ddf; + +import junit.framework.TestCase; +import org.apache.poi.util.HexDump; +import org.apache.poi.util.HexRead; + +import java.io.IOException; + +public class TestEscherBSERecord extends TestCase +{ + public void testFillFields() throws Exception + { + String data = "01 00 00 00 24 00 00 00 05 05 01 02 03 04 " + + " 05 06 07 08 09 0A 0B 0C 0D 0E 0F 00 01 00 00 00 " + + " 00 00 02 00 00 00 03 00 00 00 04 05 06 07"; + EscherBSERecord r = new EscherBSERecord(); + int bytesWritten = r.fillFields( HexRead.readFromString( data ), 0, new DefaultEscherRecordFactory() ); + assertEquals( 44, bytesWritten ); + assertEquals( (short) 0x0001, r.getOptions() ); + assertEquals( EscherBSERecord.BT_JPEG, r.getBlipTypeWin32() ); + assertEquals( EscherBSERecord.BT_JPEG, r.getBlipTypeMacOS() ); + assertEquals( "[01, 02, 03, 04, 05, 06, 07, 08, 09, 0A, 0B, 0C, 0D, 0E, 0F, 00, ]", HexDump.toHex( r.getUid() ) ); + assertEquals( (short) 1, r.getTag() ); + assertEquals( 2, r.getRef() ); + assertEquals( 3, r.getOffset() ); + assertEquals( (byte) 4, r.getUsage() ); + assertEquals( (byte) 5, r.getName() ); + assertEquals( (byte) 6, r.getUnused2() ); + assertEquals( (byte) 7, r.getUnused3() ); + assertEquals( 0, r.getRemainingData().length ); + } + + public void testSerialize() throws Exception + { + EscherBSERecord r = createRecord(); + + byte[] data = new byte[8 + 36]; + int bytesWritten = r.serialize( 0, data, new NullEscherSerializationListener() ); + assertEquals( 44, bytesWritten ); + assertEquals( "[01, 00, 00, 00, 24, 00, 00, 00, 05, 05, 01, 02, 03, 04, " + + "05, 06, 07, 08, 09, 0A, 0B, 0C, 0D, 0E, 0F, 00, 01, 00, 00, 00, " + + "00, 00, 02, 00, 00, 00, 03, 00, 00, 00, 04, 05, 06, 07, ]", + HexDump.toHex( data ) ); + + } + + private EscherBSERecord createRecord() throws IOException + { + EscherBSERecord r = new EscherBSERecord(); + r.setOptions( (short) 0x0001 ); + r.setBlipTypeWin32( EscherBSERecord.BT_JPEG ); + r.setBlipTypeMacOS( EscherBSERecord.BT_JPEG ); + r.setUid( HexRead.readFromString( "01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 00" ) ); + r.setTag( (short) 1 ); + r.setRef( 2 ); + r.setOffset( 3 ); + r.setUsage( (byte) 4 ); + r.setName( (byte) 5 ); + r.setUnused2( (byte) 6 ); + r.setUnused3( (byte) 7 ); + r.setRemainingData( new byte[0] ); + return r; + + } + + public void testToString() throws Exception + { + EscherBSERecord record = createRecord(); + String nl = System.getProperty("line.separator"); + assertEquals( "org.apache.poi.ddf.EscherBSERecord:" + nl + + " RecordId: 0xF007" + nl + + " Options: 0x0001" + nl + + " BlipTypeWin32: 5" + nl + + " BlipTypeMacOS: 5" + nl + + " SUID: [01, 02, 03, 04, 05, 06, 07, 08, 09, 0A, 0B, 0C, 0D, 0E, 0F, 00, ]" + nl + + " Tag: 1" + nl + + " Size: 0" + nl + + " Ref: 2" + nl + + " Offset: 3" + nl + + " Usage: 4" + nl + + " Name: 5" + nl + + " Unused2: 6" + nl + + " Unused3: 7" + nl + + " Extra Data:" + nl, record.toString() ); + } + +} diff --git a/src/java/org/apache/poi/ddf/TestEscherBlipRecord.java b/src/java/org/apache/poi/ddf/TestEscherBlipRecord.java new file mode 100644 index 0000000000..184dc53590 --- /dev/null +++ b/src/java/org/apache/poi/ddf/TestEscherBlipRecord.java @@ -0,0 +1,110 @@ +package org.apache.poi.ddf; + +import junit.framework.TestCase; +import org.apache.poi.util.HexDump; +import org.apache.poi.util.HexRead; + +public class TestEscherBlipRecord extends TestCase +{ + private String dataStr; + private byte[] data; + + protected void setUp() throws Exception + { + dataStr = "2C 15 18 F0 34 00 00 00 01 01 01 01 01 01 01 01 " + + "01 01 01 01 01 01 01 01 06 00 00 00 03 00 00 00 " + + "01 00 00 00 04 00 00 00 02 00 00 00 0A 00 00 00 " + + "0B 00 00 00 05 00 00 00 08 07 01 02"; + data = HexRead.readFromString(dataStr); + } + + public void testSerialize() throws Exception + { + EscherBlipRecord r = new EscherBlipRecord(); + r.setBoundaryLeft(1); + r.setBoundaryHeight(2); + r.setBoundaryTop(3); + r.setBoundaryWidth(4); + r.setCacheOfSavedSize(5); + r.setCacheOfSize(6); + r.setFilter((byte)7); + r.setCompressionFlag((byte)8); + r.setSecondaryUID(new byte[] { (byte)0x01, (byte)0x01, (byte)0x01, (byte)0x01, + (byte)0x01, (byte)0x01, (byte)0x01, (byte)0x01, + (byte)0x01, (byte)0x01, (byte)0x01, (byte)0x01, + (byte)0x01, (byte)0x01, (byte)0x01, (byte)0x01, }); + r.setWidth(10); + r.setHeight(11); + r.setRecordId(EscherBlipRecord.RECORD_ID_START); + r.setOptions((short)5420); + r.setData(new byte[] { (byte)0x01, (byte)0x02 } ); + + byte[] buf = new byte[r.getRecordSize()]; + r.serialize(0, buf, new NullEscherSerializationListener() ); + + assertEquals("[2C, 15, 18, F0, 26, 00, 00, 00, " + + "01, 01, 01, 01, 01, 01, 01, 01, 01, 01, 01, 01, 01, 01, 01, 01, " + + "06, 00, 00, 00, " + // field_2_cacheOfSize + "03, 00, 00, 00, " + // field_3_boundaryTop + "01, 00, 00, 00, " + // field_4_boundaryLeft + "04, 00, 00, 00, " + // field_5_boundaryWidth + "02, 00, 00, 00, " + // field_6_boundaryHeight + "0A, 00, 00, 00, " + // field_7_x + "0B, 00, 00, 00, " + // field_8_y + "05, 00, 00, 00, " + // field_9_cacheOfSavedSize + "08, " + // field_10_compressionFlag + "07, " + // field_11_filter + "01, 02, ]", // field_12_data + HexDump.toHex(buf)); + assertEquals(60, r.getRecordSize() ); + + } + + public void testFillFields() throws Exception + { + EscherBlipRecord r = new EscherBlipRecord(); + r.fillFields( data, 0, new DefaultEscherRecordFactory()); + + assertEquals( EscherBlipRecord.RECORD_ID_START, r.getRecordId() ); + assertEquals( 1, r.getBoundaryLeft() ); + assertEquals( 2, r.getBoundaryHeight() ); + assertEquals( 3, r.getBoundaryTop() ); + assertEquals( 4, r.getBoundaryWidth() ); + assertEquals( 5, r.getCacheOfSavedSize() ); + assertEquals( 6, r.getCacheOfSize() ); + assertEquals( 7, r.getFilter() ); + assertEquals( 8, r.getCompressionFlag() ); + assertEquals( "[01, 01, 01, 01, 01, 01, 01, 01, 01, 01, 01, 01, 01, 01, 01, 01, ]", HexDump.toHex(r.getSecondaryUID() ) ); + assertEquals( 10, r.getWidth() ); + assertEquals( 11, r.getHeight() ); + assertEquals( (short)5420, r.getOptions() ); + assertEquals( "[01, 02, ]", HexDump.toHex( r.getData() ) ); + } + + public void testToString() throws Exception + { + EscherBlipRecord r = new EscherBlipRecord(); + r.fillFields( data, 0, new DefaultEscherRecordFactory() ); + + String nl = System.getProperty("line.separator"); + + assertEquals( "org.apache.poi.ddf.EscherBlipRecord:" + nl + + " RecordId: 0xF018" + nl + + " Options: 0x152C" + nl + + " Secondary UID: [01, 01, 01, 01, 01, 01, 01, 01, 01, 01, 01, 01, 01, 01, 01, 01, ]" + nl + + " CacheOfSize: 6" + nl + + " BoundaryTop: 3" + nl + + " BoundaryLeft: 1" + nl + + " BoundaryWidth: 4" + nl + + " BoundaryHeight: 2" + nl + + " X: 10" + nl + + " Y: 11" + nl + + " CacheOfSavedSize: 5" + nl + + " CompressionFlag: 8" + nl + + " Filter: 7" + nl + + " Data:" + nl + + "00000000 01 02 .." + nl + , r.toString() ); + } + +} diff --git a/src/java/org/apache/poi/ddf/TestEscherBoolProperty.java b/src/java/org/apache/poi/ddf/TestEscherBoolProperty.java new file mode 100644 index 0000000000..4f16ffddfd --- /dev/null +++ b/src/java/org/apache/poi/ddf/TestEscherBoolProperty.java @@ -0,0 +1,13 @@ +package org.apache.poi.ddf; + +import junit.framework.TestCase; + +public class TestEscherBoolProperty extends TestCase +{ + public void testToString() throws Exception + { + EscherBoolProperty p = new EscherBoolProperty((short)1, 1); + assertEquals("propNum: 1, propName: unknown, complex: false, blipId: false, value: 1 (0x00000001)", p.toString()); + } + +} diff --git a/src/java/org/apache/poi/ddf/TestEscherChildAnchorRecord.java b/src/java/org/apache/poi/ddf/TestEscherChildAnchorRecord.java new file mode 100644 index 0000000000..6c58d449e3 --- /dev/null +++ b/src/java/org/apache/poi/ddf/TestEscherChildAnchorRecord.java @@ -0,0 +1,73 @@ +package org.apache.poi.ddf; + +import junit.framework.TestCase; +import org.apache.poi.util.HexDump; +import org.apache.poi.util.HexRead; + +public class TestEscherChildAnchorRecord extends TestCase +{ + public void testSerialize() throws Exception + { + EscherChildAnchorRecord r = createRecord(); + + byte[] data = new byte[8 + 16]; + int bytesWritten = r.serialize( 0, data, new NullEscherSerializationListener() ); + assertEquals( 24, bytesWritten ); + assertEquals( "[01, 00, " + + "0F, F0, " + + "10, 00, 00, 00, " + + "01, 00, 00, 00, " + + "02, 00, 00, 00, " + + "03, 00, 00, 00, " + + "04, 00, 00, 00, ]", HexDump.toHex( data ) ); + } + + public void testFillFields() throws Exception + { + String hexData = "01 00 " + + "0F F0 " + + "10 00 00 00 " + + "01 00 00 00 " + + "02 00 00 00 " + + "03 00 00 00 " + + "04 00 00 00 "; + + byte[] data = HexRead.readFromString( hexData ); + EscherChildAnchorRecord r = new EscherChildAnchorRecord(); + int bytesWritten = r.fillFields( data, new DefaultEscherRecordFactory() ); + + assertEquals( 24, bytesWritten ); + assertEquals( 1, r.getDx1() ); + assertEquals( 2, r.getDy1() ); + assertEquals( 3, r.getDx2() ); + assertEquals( 4, r.getDy2() ); + assertEquals( (short) 0x0001, r.getOptions() ); + } + + public void testToString() throws Exception + { + String nl = System.getProperty( "line.separator" ); + + String expected = "org.apache.poi.ddf.EscherChildAnchorRecord:" + nl + + " RecordId: 0xF00F" + nl + + " Options: 0x0001" + nl + + " X1: 1" + nl + + " Y1: 2" + nl + + " X2: 3" + nl + + " Y2: 4" + nl; + assertEquals( expected, createRecord().toString() ); + } + + private EscherChildAnchorRecord createRecord() + { + EscherChildAnchorRecord r = new EscherChildAnchorRecord(); + r.setRecordId( EscherChildAnchorRecord.RECORD_ID ); + r.setOptions( (short) 0x0001 ); + r.setDx1( 1 ); + r.setDy1( 2 ); + r.setDx2( 3 ); + r.setDy2( 4 ); + return r; + } + +} diff --git a/src/java/org/apache/poi/ddf/TestEscherClientAnchorRecord.java b/src/java/org/apache/poi/ddf/TestEscherClientAnchorRecord.java new file mode 100644 index 0000000000..acd84625df --- /dev/null +++ b/src/java/org/apache/poi/ddf/TestEscherClientAnchorRecord.java @@ -0,0 +1,91 @@ +package org.apache.poi.ddf; + +import junit.framework.TestCase; +import org.apache.poi.util.HexDump; +import org.apache.poi.util.HexRead; + +public class TestEscherClientAnchorRecord extends TestCase +{ + public void testSerialize() throws Exception + { + EscherClientAnchorRecord r = createRecord(); + + byte[] data = new byte[8 + 18 + 2]; + int bytesWritten = r.serialize( 0, data, new NullEscherSerializationListener() ); + assertEquals( 28, bytesWritten ); + assertEquals( "[01, 00, " + + "10, F0, " + + "14, 00, 00, 00, " + + "4D, 00, 37, 00, 21, 00, 58, 00, " + + "0B, 00, 2C, 00, 16, 00, 63, 00, " + + "42, 00, " + + "FF, DD, ]", HexDump.toHex( data ) ); + } + + public void testFillFields() throws Exception + { + String hexData = "01 00 " + + "10 F0 " + + "14 00 00 00 " + + "4D 00 37 00 21 00 58 00 " + + "0B 00 2C 00 16 00 63 00 " + + "42 00 " + + "FF DD"; + byte[] data = HexRead.readFromString( hexData ); + EscherClientAnchorRecord r = new EscherClientAnchorRecord(); + int bytesWritten = r.fillFields( data, new DefaultEscherRecordFactory() ); + + assertEquals( 28, bytesWritten ); + assertEquals( (short) 55, r.getCol1() ); + assertEquals( (short) 44, r.getCol2() ); + assertEquals( (short) 33, r.getDx1() ); + assertEquals( (short) 22, r.getDx2() ); + assertEquals( (short) 11, r.getDy1() ); + assertEquals( (short) 66, r.getDy2() ); + assertEquals( (short) 77, r.getFlag() ); + assertEquals( (short) 88, r.getRow1() ); + assertEquals( (short) 99, r.getRow2() ); + assertEquals( (short) 0x0001, r.getOptions() ); + assertEquals( (byte) 0xFF, r.getRemainingData()[0] ); + assertEquals( (byte) 0xDD, r.getRemainingData()[1] ); + } + + public void testToString() throws Exception + { + String nl = System.getProperty("line.separator"); + + String expected = "org.apache.poi.ddf.EscherClientAnchorRecord:" + nl + + " RecordId: 0xF010" + nl + + " Options: 0x0001" + nl + + " Flag: 77" + nl + + " Col1: 55" + nl + + " DX1: 33" + nl + + " Row1: 88" + nl + + " DY1: 11" + nl + + " Col2: 44" + nl + + " DX2: 22" + nl + + " Row2: 99" + nl + + " DY2: 66" + nl + + " Extra Data:" + nl + + "00000000 FF DD .." + nl; + assertEquals( expected, createRecord().toString() ); + } + + private EscherClientAnchorRecord createRecord() + { + EscherClientAnchorRecord r = new EscherClientAnchorRecord(); + r.setCol1( (short) 55 ); + r.setCol2( (short) 44 ); + r.setDx1( (short) 33 ); + r.setDx2( (short) 22 ); + r.setDy1( (short) 11 ); + r.setDy2( (short) 66 ); + r.setFlag( (short) 77 ); + r.setRow1( (short) 88 ); + r.setRow2( (short) 99 ); + r.setOptions( (short) 0x0001 ); + r.setRemainingData( new byte[]{(byte) 0xFF, (byte) 0xDD} ); + return r; + } + +} diff --git a/src/java/org/apache/poi/ddf/TestEscherClientDataRecord.java b/src/java/org/apache/poi/ddf/TestEscherClientDataRecord.java new file mode 100644 index 0000000000..a91e36896a --- /dev/null +++ b/src/java/org/apache/poi/ddf/TestEscherClientDataRecord.java @@ -0,0 +1,57 @@ +package org.apache.poi.ddf; + +import junit.framework.TestCase; +import org.apache.poi.util.HexDump; +import org.apache.poi.util.HexRead; + +public class TestEscherClientDataRecord extends TestCase +{ + public void testSerialize() throws Exception + { + EscherClientDataRecord r = createRecord(); + + byte[] data = new byte[8]; + int bytesWritten = r.serialize( 0, data, new NullEscherSerializationListener() ); + assertEquals( 8, bytesWritten ); + assertEquals( "[02, 00, " + + "11, F0, " + + "00, 00, 00, 00, ]", + HexDump.toHex( data ) ); + } + + public void testFillFields() throws Exception + { + String hexData = "02 00 " + + "11 F0 " + + "00 00 00 00 "; + byte[] data = HexRead.readFromString( hexData ); + EscherClientDataRecord r = new EscherClientDataRecord(); + int bytesWritten = r.fillFields( data, new DefaultEscherRecordFactory() ); + + assertEquals( 8, bytesWritten ); + assertEquals( (short)0xF011, r.getRecordId() ); + assertEquals( "[]", HexDump.toHex(r.getRemainingData()) ); + } + + public void testToString() throws Exception + { + String nl = System.getProperty("line.separator"); + + String expected = "org.apache.poi.ddf.EscherClientDataRecord:" + nl + + " RecordId: 0xF011" + nl + + " Options: 0x0002" + nl + + " Extra Data:" + nl + + "" ; + assertEquals( expected, createRecord().toString() ); + } + + private EscherClientDataRecord createRecord() + { + EscherClientDataRecord r = new EscherClientDataRecord(); + r.setOptions( (short) 0x0002 ); + r.setRecordId( EscherClientDataRecord.RECORD_ID ); + r.setRemainingData( new byte[] {} ); + return r; + } + +} diff --git a/src/java/org/apache/poi/ddf/TestEscherContainerRecord.java b/src/java/org/apache/poi/ddf/TestEscherContainerRecord.java new file mode 100644 index 0000000000..9d74a3ee3e --- /dev/null +++ b/src/java/org/apache/poi/ddf/TestEscherContainerRecord.java @@ -0,0 +1,98 @@ +package org.apache.poi.ddf; + +import junit.framework.TestCase; +import org.apache.poi.util.HexRead; +import org.apache.poi.util.HexDump; + +public class TestEscherContainerRecord extends TestCase +{ + public void testFillFields() throws Exception + { + EscherRecordFactory f = new DefaultEscherRecordFactory(); + byte[] data = HexRead.readFromString( "0F 02 11 F1 00 00 00 00" ); + EscherRecord r = f.createRecord( data, 0 ); + r.fillFields( data, 0, f ); + assertTrue( r instanceof EscherContainerRecord ); + assertEquals( (short) 0x020F, r.getOptions() ); + assertEquals( (short) 0xF111, r.getRecordId() ); + + data = HexRead.readFromString( "0F 02 11 F1 08 00 00 00" + + " 02 00 22 F2 00 00 00 00" ); + r = f.createRecord( data, 0 ); + r.fillFields( data, 0, f ); + EscherRecord c = r.getChild( 0 ); + assertFalse( c instanceof EscherContainerRecord ); + assertEquals( (short) 0x0002, c.getOptions() ); + assertEquals( (short) 0xF222, c.getRecordId() ); + } + + public void testSerialize() throws Exception + { + UnknownEscherRecord r = new UnknownEscherRecord(); + r.setOptions( (short) 0x123F ); + r.setRecordId( (short) 0xF112 ); + byte[] data = new byte[8]; + r.serialize( 0, data, new NullEscherSerializationListener() ); + + assertEquals( "[3F, 12, 12, F1, 00, 00, 00, 00, ]", HexDump.toHex( data ) ); + + EscherRecord childRecord = new UnknownEscherRecord(); + childRecord.setOptions( (short) 0x9999 ); + childRecord.setRecordId( (short) 0xFF01 ); + r.addChildRecord( childRecord ); + data = new byte[16]; + r.serialize( 0, data, new NullEscherSerializationListener() ); + + assertEquals( "[3F, 12, 12, F1, 08, 00, 00, 00, 99, 99, 01, FF, 00, 00, 00, 00, ]", HexDump.toHex( data ) ); + + } + + public void testToString() throws Exception + { + EscherContainerRecord r = new EscherContainerRecord(); + r.setRecordId( EscherContainerRecord.SP_CONTAINER ); + r.setOptions( (short) 0x000F ); + String nl = System.getProperty( "line.separator" ); + assertEquals( "org.apache.poi.ddf.EscherContainerRecord (SpContainer):" + nl + + " isContainer: true" + nl + + " options: 0x000F" + nl + + " recordId: 0xF004" + nl + + " numchildren: 0" + nl + , r.toString() ); + + EscherOptRecord r2 = new EscherOptRecord(); + r2.setOptions( (short) 0x9876 ); + r2.setRecordId( EscherOptRecord.RECORD_ID ); + + r.addChildRecord( r2 ); + String expected = "org.apache.poi.ddf.EscherContainerRecord (SpContainer):" + nl + + " isContainer: true" + nl + + " options: 0x000F" + nl + + " recordId: 0xF004" + nl + + " numchildren: 1" + nl + + " children: " + nl + + "org.apache.poi.ddf.EscherOptRecord:" + nl + + " isContainer: false" + nl + + " options: 0x0003" + nl + + " recordId: 0xF00B" + nl + + " numchildren: 0" + nl + + " properties:" + nl; + assertEquals( expected, r.toString() ); + + } + + public void testGetRecordSize() throws Exception + { + EscherContainerRecord r = new EscherContainerRecord(); + r.addChildRecord(new EscherRecord() + { + public int fillFields( byte[] data, int offset, EscherRecordFactory recordFactory ) { return 0; } + public int serialize( int offset, byte[] data, EscherSerializationListener listener ) { return 0; } + public int getRecordSize() { return 10; } + public String getRecordName() { return ""; } + } ); + + assertEquals(18, r.getRecordSize()); + } + +} diff --git a/src/java/org/apache/poi/ddf/TestEscherDgRecord.java b/src/java/org/apache/poi/ddf/TestEscherDgRecord.java new file mode 100644 index 0000000000..a7fa9bb52e --- /dev/null +++ b/src/java/org/apache/poi/ddf/TestEscherDgRecord.java @@ -0,0 +1,62 @@ +package org.apache.poi.ddf; + +import junit.framework.TestCase; +import org.apache.poi.util.HexDump; +import org.apache.poi.util.HexRead; + +public class TestEscherDgRecord extends TestCase +{ + public void testSerialize() throws Exception + { + EscherDgRecord r = createRecord(); + + byte[] data = new byte[16]; + int bytesWritten = r.serialize( 0, data, new NullEscherSerializationListener() ); + assertEquals( 16, bytesWritten ); + assertEquals( "[10, 00, " + + "08, F0, " + + "08, 00, 00, 00, " + + "02, 00, 00, 00, " + // num shapes in drawing + "01, 04, 00, 00, ]", // The last MSOSPID given to an SP in this DG + HexDump.toHex( data ) ); + } + + public void testFillFields() throws Exception + { + String hexData = "10 00 " + + "08 F0 " + + "08 00 00 00 " + + "02 00 00 00 " + + "01 04 00 00 "; + byte[] data = HexRead.readFromString( hexData ); + EscherDgRecord r = new EscherDgRecord(); + int bytesWritten = r.fillFields( data, new DefaultEscherRecordFactory() ); + + assertEquals( 16, bytesWritten ); + assertEquals( 2, r.getNumShapes() ); + assertEquals( 1025, r.getLastMSOSPID() ); + } + + public void testToString() throws Exception + { + String nl = System.getProperty("line.separator"); + + String expected = "org.apache.poi.ddf.EscherDgRecord:" + nl + + " RecordId: 0xF008" + nl + + " Options: 0x0010" + nl + + " NumShapes: 2" + nl + + " LastMSOSPID: 1025" + nl; + assertEquals( expected, createRecord().toString() ); + } + + private EscherDgRecord createRecord() + { + EscherDgRecord r = new EscherDgRecord(); + r.setOptions( (short) 0x0010 ); + r.setRecordId( EscherDgRecord.RECORD_ID ); + r.setNumShapes(2); + r.setLastMSOSPID(1025); + return r; + } + +} diff --git a/src/java/org/apache/poi/ddf/TestEscherDggRecord.java b/src/java/org/apache/poi/ddf/TestEscherDggRecord.java new file mode 100644 index 0000000000..b632ae9902 --- /dev/null +++ b/src/java/org/apache/poi/ddf/TestEscherDggRecord.java @@ -0,0 +1,89 @@ +package org.apache.poi.ddf; + +import junit.framework.TestCase; +import org.apache.poi.util.HexDump; +import org.apache.poi.util.HexRead; + +public class TestEscherDggRecord extends TestCase +{ + public void testSerialize() throws Exception + { + EscherDggRecord r = createRecord(); + + byte[] data = new byte[32]; + int bytesWritten = r.serialize( 0, data, new NullEscherSerializationListener() ); + assertEquals( 32, bytesWritten ); + assertEquals( "[00, 00, " + + "06, F0, " + + "18, 00, 00, 00, " + + "02, 04, 00, 00, " + + "02, 00, 00, 00, " + + "02, 00, 00, 00, " + + "01, 00, 00, 00, " + + "01, 00, 00, 00, 02, 00, 00, 00, ]", + HexDump.toHex( data ) ); + } + + public void testFillFields() throws Exception + { + String hexData = "00 00 " + + "06 F0 " + + "18 00 00 00 " + + "02 04 00 00 " + + "02 00 00 00 " + + "02 00 00 00 " + + "01 00 00 00 " + + "01 00 00 00 02 00 00 00"; + byte[] data = HexRead.readFromString( hexData ); + EscherDggRecord r = new EscherDggRecord(); + int bytesWritten = r.fillFields( data, new DefaultEscherRecordFactory() ); + + assertEquals( 32, bytesWritten ); + assertEquals( 0x402, r.getShapeIdMax() ); + assertEquals( 0x02, r.getNumIdClusters() ); + assertEquals( 0x02, r.getNumShapesSaved() ); + assertEquals( 0x01, r.getDrawingsSaved() ); + assertEquals( 1, r.getFileIdClusters().length ); + assertEquals( 0x01, r.getFileIdClusters()[0].getDrawingGroupId()); + assertEquals( 0x02, r.getFileIdClusters()[0].getNumShapeIdsUsed()); + } + + public void testToString() throws Exception + { + String nl = System.getProperty("line.separator"); + + String expected = "org.apache.poi.ddf.EscherDggRecord:" + nl + + " RecordId: 0xF006" + nl + + " Options: 0x0000" + nl + + " ShapeIdMax: 1026" + nl + + " NumIdClusters: 2" + nl + + " NumShapesSaved: 2" + nl + + " DrawingsSaved: 1" + nl + + " DrawingGroupId1: 1" + nl + + " NumShapeIdsUsed1: 2" + nl; + assertEquals( expected, createRecord().toString() ); + } + + private EscherDggRecord createRecord() + { + EscherDggRecord r = new EscherDggRecord(); + r.setOptions( (short) 0x0000 ); + r.setRecordId( EscherDggRecord.RECORD_ID ); + r.setShapeIdMax( 0x402 ); + r.setNumShapesSaved( 0x02 ); + r.setDrawingsSaved( 0x01 ); + r.setFileIdClusters(new EscherDggRecord.FileIdCluster[] { + new EscherDggRecord.FileIdCluster( 1, 2 ) + }); + return r; + } + + public void testGetRecordSize() throws Exception + { + EscherDggRecord r = new EscherDggRecord(); + r.setFileIdClusters(new EscherDggRecord.FileIdCluster[] { new EscherDggRecord.FileIdCluster(0,0) } ); + assertEquals(32,r.getRecordSize()); + + } + +} diff --git a/src/java/org/apache/poi/ddf/TestEscherOptRecord.java b/src/java/org/apache/poi/ddf/TestEscherOptRecord.java new file mode 100644 index 0000000000..dc5511a7b9 --- /dev/null +++ b/src/java/org/apache/poi/ddf/TestEscherOptRecord.java @@ -0,0 +1,140 @@ +package org.apache.poi.ddf; + +import junit.framework.TestCase; +import org.apache.poi.util.HexRead; +import org.apache.poi.util.HexDump; + +import java.io.IOException; + +public class TestEscherOptRecord extends TestCase +{ + + public void testFillFields() throws Exception + { + checkFillFieldsSimple(); + checkFillFieldsComplex(); + } + + private void checkFillFieldsComplex() throws IOException + { + String dataStr = "33 00 " + + "0B F0 " + + "14 00 00 00 " + + "BF 00 01 00 00 00 " + + "01 80 02 00 00 00 " + + "BF 00 01 00 00 00 " + + "01 02"; + + EscherOptRecord r = new EscherOptRecord(); + r.fillFields( HexRead.readFromString( dataStr ), new DefaultEscherRecordFactory() ); + assertEquals( (short) 0x0033, r.getOptions() ); + assertEquals( (short) 0xF00B, r.getRecordId() ); + assertEquals( 3, r.getEscherProperties().size() ); + EscherBoolProperty prop1 = new EscherBoolProperty( EscherProperties.TEXT__SIZE_TEXT_TO_FIT_SHAPE, 1 ); + EscherComplexProperty prop2 = new EscherComplexProperty( (short) 1, false, new byte[] { 0x01, 0x02 } ); + EscherBoolProperty prop3 = new EscherBoolProperty( EscherProperties.TEXT__SIZE_TEXT_TO_FIT_SHAPE, 1 ); + assertEquals( prop1, r.getEscherProperty( 0 ) ); + assertEquals( prop2, r.getEscherProperty( 1 ) ); + assertEquals( prop3, r.getEscherProperty( 2 ) ); + + } + + private void checkFillFieldsSimple() + throws IOException + { + String dataStr = "33 00 " + // options + "0B F0 " + // recordid + "12 00 00 00 " + // remaining bytes + "BF 00 08 00 08 00 " + + "81 01 09 00 00 08 " + + "C0 01 40 00 00 08"; + + EscherOptRecord r = new EscherOptRecord(); + r.fillFields( HexRead.readFromString( dataStr ), new DefaultEscherRecordFactory() ); + assertEquals( (short) 0x0033, r.getOptions() ); + assertEquals( (short) 0xF00B, r.getRecordId() ); + assertEquals( 3, r.getEscherProperties().size() ); + EscherBoolProperty prop1 = new EscherBoolProperty( EscherProperties.TEXT__SIZE_TEXT_TO_FIT_SHAPE, 524296 ); + EscherRGBProperty prop2 = new EscherRGBProperty( EscherProperties.FILL__FILLCOLOR, 0x08000009 ); + EscherRGBProperty prop3 = new EscherRGBProperty( EscherProperties.LINESTYLE__COLOR, 0x08000040 ); + assertEquals( prop1, r.getEscherProperty( 0 ) ); + assertEquals( prop2, r.getEscherProperty( 1 ) ); + assertEquals( prop3, r.getEscherProperty( 2 ) ); + } + + public void testSerialize() throws Exception + { + checkSerializeSimple(); + checkSerializeComplex(); + } + + private void checkSerializeComplex() + { + //Complex escher record + EscherOptRecord r = new EscherOptRecord(); + r.setOptions( (short) 0x0033 ); + r.setRecordId( (short) 0xF00B ); + EscherBoolProperty prop1 = new EscherBoolProperty( EscherProperties.TEXT__SIZE_TEXT_TO_FIT_SHAPE, 1 ); + EscherComplexProperty prop2 = new EscherComplexProperty( (short) 1, false, new byte[] { 0x01, 0x02 } ); + EscherBoolProperty prop3 = new EscherBoolProperty( EscherProperties.TEXT__SIZE_TEXT_TO_FIT_SHAPE, 1 ); + r.addEscherProperty( prop1 ); + r.addEscherProperty( prop2 ); + r.addEscherProperty( prop3 ); + + byte[] data = new byte[28]; + int bytesWritten = r.serialize(0, data, new NullEscherSerializationListener() ); + assertEquals( 28, bytesWritten ); + String dataStr = "[33, 00, " + + "0B, F0, " + + "14, 00, 00, 00, " + + "BF, 00, 01, 00, 00, 00, " + + "01, 80, 02, 00, 00, 00, " + + "BF, 00, 01, 00, 00, 00, " + + "01, 02, ]"; + assertEquals( dataStr, HexDump.toHex(data) ); + + } + + private void checkSerializeSimple() + { + EscherOptRecord r = new EscherOptRecord(); + r.setOptions( (short) 0x0033 ); + r.setRecordId( (short) 0xF00B ); + EscherBoolProperty prop1 = new EscherBoolProperty( EscherProperties.TEXT__SIZE_TEXT_TO_FIT_SHAPE, 1 ); + EscherRGBProperty prop2 = new EscherRGBProperty( EscherProperties.FILL__FILLCOLOR, 0x08000009 ); + EscherRGBProperty prop3 = new EscherRGBProperty( EscherProperties.LINESTYLE__COLOR, 0x08000040 ); + r.addEscherProperty( prop1 ); + r.addEscherProperty( prop2 ); + r.addEscherProperty( prop3 ); + + byte[] data = new byte[26]; + int bytesWritten = r.serialize(0, data, new NullEscherSerializationListener() ); + String dataStr = "[33, 00, " + + "0B, F0, " + + "12, 00, 00, 00, " + + "BF, 00, 01, 00, 00, 00, " + + "81, 01, 09, 00, 00, 08, " + + "C0, 01, 40, 00, 00, 08, ]"; + assertEquals( dataStr, HexDump.toHex(data) ); + assertEquals( 26, bytesWritten ); + } + + public void testToString() throws Exception + { + String nl = System.getProperty("line.separator"); + EscherOptRecord r = new EscherOptRecord(); + r.setOptions((short)0x000F); + r.setRecordId(EscherOptRecord.RECORD_ID); + EscherProperty prop1 = new EscherBoolProperty((short)1, 1); + r.addEscherProperty(prop1); + String expected = "org.apache.poi.ddf.EscherOptRecord:" + nl + + " isContainer: true" + nl + + " options: 0x0013" + nl + + " recordId: 0x" + HexDump.toHex(EscherOptRecord.RECORD_ID) + nl + + " numchildren: 0" + nl + + " properties:" + nl + + " propNum: 1, propName: unknown, complex: false, blipId: false, value: 1 (0x00000001)" + nl; + assertEquals( expected, r.toString()); + } + +} diff --git a/src/java/org/apache/poi/ddf/TestEscherPropertyFactory.java b/src/java/org/apache/poi/ddf/TestEscherPropertyFactory.java new file mode 100644 index 0000000000..3caa156ad6 --- /dev/null +++ b/src/java/org/apache/poi/ddf/TestEscherPropertyFactory.java @@ -0,0 +1,42 @@ +package org.apache.poi.ddf; + +import junit.framework.TestCase; + +import java.util.List; + +import org.apache.poi.util.HexRead; +import org.apache.poi.util.HexDump; + +/** + * @author Glen Stampoultzis (glens @ superlinksoftware.com) + */ +public class TestEscherPropertyFactory extends TestCase +{ + public void testCreateProperties() throws Exception + { + String dataStr = "41 C1 " + // propid, complex ind + "03 00 00 00 " + // size of complex property + "01 00 " + // propid, complex ind + "00 00 00 00 " + // value + "41 C1 " + // propid, complex ind + "03 00 00 00 " + // size of complex property + "01 02 03 " + + "01 02 03 " + ; + byte[] data = HexRead.readFromString( dataStr ); + EscherPropertyFactory f = new EscherPropertyFactory(); + List props = f.createProperties( data, 0, (short)3 ); + EscherComplexProperty p1 = (EscherComplexProperty) props.get( 0 ); + assertEquals( (short)0xC141, p1.getId() ); + assertEquals( "[01, 02, 03, ]", HexDump.toHex( p1.getComplexData() ) ); + + EscherComplexProperty p3 = (EscherComplexProperty) props.get( 2 ); + assertEquals( (short)0xC141, p3.getId() ); + assertEquals( "[01, 02, 03, ]", HexDump.toHex( p3.getComplexData() ) ); + + + } + + + +} diff --git a/src/java/org/apache/poi/ddf/TestEscherSpRecord.java b/src/java/org/apache/poi/ddf/TestEscherSpRecord.java new file mode 100644 index 0000000000..448aee6540 --- /dev/null +++ b/src/java/org/apache/poi/ddf/TestEscherSpRecord.java @@ -0,0 +1,62 @@ +package org.apache.poi.ddf; + +import junit.framework.TestCase; +import org.apache.poi.util.HexDump; +import org.apache.poi.util.HexRead; + +public class TestEscherSpRecord extends TestCase +{ + public void testSerialize() throws Exception + { + EscherSpRecord r = createRecord(); + + byte[] data = new byte[16]; + int bytesWritten = r.serialize( 0, data, new NullEscherSerializationListener() ); + assertEquals( 16, bytesWritten ); + assertEquals( "[02, 00, " + + "0A, F0, " + + "08, 00, 00, 00, " + + "00, 04, 00, 00, " + + "05, 00, 00, 00, ]", + HexDump.toHex( data ) ); + } + + public void testFillFields() throws Exception + { + String hexData = "02 00 " + + "0A F0 " + + "08 00 00 00 " + + "00 04 00 00 " + + "05 00 00 00 "; + byte[] data = HexRead.readFromString( hexData ); + EscherSpRecord r = new EscherSpRecord(); + int bytesWritten = r.fillFields( data, new DefaultEscherRecordFactory() ); + + assertEquals( 16, bytesWritten ); + assertEquals( 0x0400, r.getShapeId() ); + assertEquals( 0x05, r.getFlags() ); + } + + public void testToString() throws Exception + { + String nl = System.getProperty("line.separator"); + + String expected = "org.apache.poi.ddf.EscherSpRecord:" + nl + + " RecordId: 0xF00A" + nl + + " Options: 0x0002" + nl + + " ShapeId: 1024" + nl + + " Flags: GROUP|PATRIARCH (0x00000005)" + nl; + assertEquals( expected, createRecord().toString() ); + } + + private EscherSpRecord createRecord() + { + EscherSpRecord r = new EscherSpRecord(); + r.setOptions( (short) 0x0002 ); + r.setRecordId( EscherSpRecord.RECORD_ID ); + r.setShapeId(0x0400); + r.setFlags(0x05); + return r; + } + +} diff --git a/src/java/org/apache/poi/ddf/TestEscherSpgrRecord.java b/src/java/org/apache/poi/ddf/TestEscherSpgrRecord.java new file mode 100644 index 0000000000..3ec276ab97 --- /dev/null +++ b/src/java/org/apache/poi/ddf/TestEscherSpgrRecord.java @@ -0,0 +1,73 @@ +package org.apache.poi.ddf; + +import junit.framework.TestCase; +import org.apache.poi.util.HexDump; +import org.apache.poi.util.HexRead; + +public class TestEscherSpgrRecord extends TestCase +{ + public void testSerialize() throws Exception + { + EscherSpgrRecord r = createRecord(); + + byte[] data = new byte[24]; + int bytesWritten = r.serialize( 0, data, new NullEscherSerializationListener() ); + assertEquals( 24, bytesWritten ); + assertEquals( "[10, 00, " + + "09, F0, " + + "10, 00, 00, 00, " + + "01, 00, 00, 00, " + // x + "02, 00, 00, 00, " + // y + "03, 00, 00, 00, " + // width + "04, 00, 00, 00, ]", // height + HexDump.toHex( data ) ); + } + + public void testFillFields() throws Exception + { + String hexData = "10 00 " + + "09 F0 " + + "10 00 00 00 " + + "01 00 00 00 " + + "02 00 00 00 " + + "03 00 00 00 " + + "04 00 00 00 "; + byte[] data = HexRead.readFromString( hexData ); + EscherSpgrRecord r = new EscherSpgrRecord(); + int bytesWritten = r.fillFields( data, new DefaultEscherRecordFactory() ); + + assertEquals( 24, bytesWritten ); + assertEquals( 1, r.getRectX1() ); + assertEquals( 2, r.getRectY1() ); + assertEquals( 3, r.getRectX2() ); + assertEquals( 4, r.getRectY2() ); + } + + public void testToString() throws Exception + { + String nl = System.getProperty("line.separator"); + + String expected = "org.apache.poi.ddf.EscherSpgrRecord:" + nl + + " RecordId: 0xF009" + nl + + " Options: 0x0010" + nl + + " RectX: 1" + nl + + " RectY: 2" + nl + + " RectWidth: 3" + nl + + " RectHeight: 4" + nl; + ; + assertEquals( expected, createRecord().toString() ); + } + + private EscherSpgrRecord createRecord() + { + EscherSpgrRecord r = new EscherSpgrRecord(); + r.setOptions( (short) 0x0010 ); + r.setRecordId( EscherSpgrRecord.RECORD_ID ); + r.setRectX1(1); + r.setRectY1(2); + r.setRectX2(3); + r.setRectY2(4); + return r; + } + +} diff --git a/src/java/org/apache/poi/ddf/TestEscherSplitMenuColorsRecord.java b/src/java/org/apache/poi/ddf/TestEscherSplitMenuColorsRecord.java new file mode 100644 index 0000000000..2550800c2e --- /dev/null +++ b/src/java/org/apache/poi/ddf/TestEscherSplitMenuColorsRecord.java @@ -0,0 +1,73 @@ +package org.apache.poi.ddf; + +import junit.framework.TestCase; +import org.apache.poi.util.HexDump; +import org.apache.poi.util.HexRead; + +public class TestEscherSplitMenuColorsRecord extends TestCase +{ + public void testSerialize() throws Exception + { + EscherSplitMenuColorsRecord r = createRecord(); + + byte[] data = new byte[24]; + int bytesWritten = r.serialize( 0, data, new NullEscherSerializationListener() ); + assertEquals( 24, bytesWritten ); + assertEquals( "[40, 00, " + + "1E, F1, " + + "10, 00, 00, 00, " + + "02, 04, 00, 00, " + + "02, 00, 00, 00, " + + "02, 00, 00, 00, " + + "01, 00, 00, 00, ]", + HexDump.toHex( data ) ); + } + + public void testFillFields() throws Exception + { + String hexData = "40 00 " + + "1E F1 " + + "10 00 00 00 " + + "02 04 00 00 " + + "02 00 00 00 " + + "02 00 00 00 " + + "01 00 00 00 "; + byte[] data = HexRead.readFromString( hexData ); + EscherSplitMenuColorsRecord r = new EscherSplitMenuColorsRecord(); + int bytesWritten = r.fillFields( data, new DefaultEscherRecordFactory() ); + + assertEquals( 24, bytesWritten ); + assertEquals( 0x0402, r.getColor1() ); + assertEquals( 0x02, r.getColor2() ); + assertEquals( 0x02, r.getColor3() ); + assertEquals( 0x01, r.getColor4() ); + } + + public void testToString() throws Exception + { + String nl = System.getProperty("line.separator"); + + String expected = "org.apache.poi.ddf.EscherSplitMenuColorsRecord:" + nl + + " RecordId: 0xF11E" + nl + + " Options: 0x0040" + nl + + " Color1: 0x00000402" + nl + + " Color2: 0x00000002" + nl + + " Color3: 0x00000002" + nl + + " Color4: 0x00000001" + nl + + ""; + assertEquals( expected, createRecord().toString() ); + } + + private EscherSplitMenuColorsRecord createRecord() + { + EscherSplitMenuColorsRecord r = new EscherSplitMenuColorsRecord(); + r.setOptions( (short) 0x0040 ); + r.setRecordId( EscherSplitMenuColorsRecord.RECORD_ID ); + r.setColor1( 0x402 ); + r.setColor2( 0x2 ); + r.setColor3( 0x2 ); + r.setColor4( 0x1 ); + return r; + } + +} diff --git a/src/java/org/apache/poi/ddf/TestUnknownEscherRecord.java b/src/java/org/apache/poi/ddf/TestUnknownEscherRecord.java new file mode 100644 index 0000000000..f61cfc6b4d --- /dev/null +++ b/src/java/org/apache/poi/ddf/TestUnknownEscherRecord.java @@ -0,0 +1,106 @@ +package org.apache.poi.ddf; + +import junit.framework.TestCase; +import org.apache.poi.util.HexRead; +import org.apache.poi.util.HexDump; + +public class TestUnknownEscherRecord extends TestCase +{ + public void testFillFields() throws Exception + { + String testData = + "0F 02 " + // options + "11 F1 " + // record id + "00 00 00 00"; // remaining bytes + + UnknownEscherRecord r = new UnknownEscherRecord(); + EscherRecordFactory factory = new DefaultEscherRecordFactory(); + r.fillFields( HexRead.readFromString( testData ), factory ); + + assertEquals( 0x020F, r.getOptions() ); + assertEquals( (short) 0xF111, r.getRecordId() ); + assertTrue( r.isContainerRecord() ); + assertEquals( 8, r.getRecordSize() ); + assertEquals( 0, r.getChildRecords().size() ); + assertEquals( 0, r.getData().length ); + + testData = + "00 02 " + // options + "11 F1 " + // record id + "04 00 00 00 " + // remaining bytes + "01 02 03 04"; + + r = new UnknownEscherRecord(); + r.fillFields( HexRead.readFromString( testData ), factory ); + + assertEquals( 0x0200, r.getOptions() ); + assertEquals( (short) 0xF111, r.getRecordId() ); + assertEquals( 12, r.getRecordSize() ); + assertFalse( r.isContainerRecord() ); + assertEquals( 0, r.getChildRecords().size() ); + assertEquals( 4, r.getData().length ); + assertEquals( 1, r.getData()[0] ); + assertEquals( 2, r.getData()[1] ); + assertEquals( 3, r.getData()[2] ); + assertEquals( 4, r.getData()[3] ); + + testData = + "0F 02 " + // options + "11 F1 " + // record id + "08 00 00 00 " + // remaining bytes + "00 02 " + // options + "FF FF " + // record id + "00 00 00 00"; // remaining bytes + + r = new UnknownEscherRecord(); + r.fillFields( HexRead.readFromString( testData ), factory ); + + assertEquals( 0x020F, r.getOptions() ); + assertEquals( (short) 0xF111, r.getRecordId() ); + assertEquals( 8, r.getRecordSize() ); + assertTrue( r.isContainerRecord() ); + assertEquals( 1, r.getChildRecords().size() ); + assertEquals( (short) 0xFFFF, r.getChild( 0 ).getRecordId() ); + + } + + public void testSerialize() throws Exception + { + UnknownEscherRecord r = new UnknownEscherRecord(); + r.setOptions( (short) 0x1234 ); + r.setRecordId( (short) 0xF112 ); + byte[] data = new byte[8]; + r.serialize( 0, data, new NullEscherSerializationListener() ); + + assertEquals( "[34, 12, 12, F1, 00, 00, 00, 00, ]", HexDump.toHex( data ) ); + + EscherRecord childRecord = new UnknownEscherRecord(); + childRecord.setOptions( (short) 0x9999 ); + childRecord.setRecordId( (short) 0xFF01 ); + r.addChildRecord( childRecord ); + r.setOptions( (short) 0x123F ); + data = new byte[16]; + r.serialize( 0, data, new NullEscherSerializationListener() ); + + assertEquals( "[3F, 12, 12, F1, 08, 00, 00, 00, 99, 99, 01, FF, 00, 00, 00, 00, ]", HexDump.toHex( data ) ); + } + + public void testToString() throws Exception + { + UnknownEscherRecord r = new UnknownEscherRecord(); + r.setOptions( (short) 0x1234 ); + r.setRecordId( (short) 0xF112 ); + byte[] data = new byte[8]; + r.serialize( 0, data, new NullEscherSerializationListener() ); + + String nl = System.getProperty("line.separator"); + assertEquals( "org.apache.poi.ddf.UnknownEscherRecord:" + nl + + " isContainer: false" + nl + + " options: 0x1234" + nl + + " recordId: 0xF112" + nl + + " numchildren: 0" + nl + , r.toString() ); + } + + +} diff --git a/src/java/org/apache/poi/ddf/UnknownEscherRecord.java b/src/java/org/apache/poi/ddf/UnknownEscherRecord.java new file mode 100644 index 0000000000..e2d21fbabe --- /dev/null +++ b/src/java/org/apache/poi/ddf/UnknownEscherRecord.java @@ -0,0 +1,234 @@ +/* ==================================================================== + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2002 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Apache" and "Apache Software Foundation" and + * "Apache POI" must not be used to endorse or promote products + * derived from this software without prior written permission. For + * written permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * "Apache POI", nor may "Apache" appear in their name, without + * prior written permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + */ +package org.apache.poi.ddf; + +import org.apache.poi.util.HexDump; +import org.apache.poi.util.LittleEndian; + +import java.util.Iterator; +import java.util.List; +import java.util.ArrayList; + +/** + * This record is used whenever a escher record is encountered that + * we do not explicitly support. + * + * @author Glen Stampoultzis (glens at apache.org) + */ +public class UnknownEscherRecord extends EscherRecord +{ + private static final byte[] NO_BYTES = new byte[0]; + + /** The data for this record not including the the 8 byte header */ + private byte[] thedata = NO_BYTES; + private List childRecords = new ArrayList(); + + public UnknownEscherRecord() + { + } + + /** + * This method deserializes the record from a byte array. + * + * @param data The byte array containing the escher record information + * @param offset The starting offset into <code>data</code>. + * @param recordFactory May be null since this is not a container record. + * @return The number of bytes read from the byte array. + */ + public int fillFields( byte[] data, int offset, EscherRecordFactory recordFactory ) + { + int bytesRemaining = readHeader( data, offset ); + if ( isContainerRecord() ) + { + int bytesWritten = 0; + thedata = new byte[0]; + offset += 8; + bytesWritten += 8; + while ( bytesRemaining > 0 ) + { + EscherRecord child = recordFactory.createRecord( data, offset ); + int childBytesWritten = child.fillFields( data, offset, recordFactory ); + bytesWritten += childBytesWritten; + offset += childBytesWritten; + bytesRemaining -= childBytesWritten; + getChildRecords().add( child ); + } + return bytesWritten; + } + else + { + thedata = new byte[bytesRemaining]; + System.arraycopy( data, offset + 8, thedata, 0, bytesRemaining ); + return bytesRemaining + 8; + } + } + + /** + * Writes this record and any contained records to the supplied byte + * array. + * + * @return the number of bytes written. + */ + public int serialize( int offset, byte[] data, EscherSerializationListener listener ) + { + listener.beforeRecordSerialize( offset, getRecordId(), this ); + + LittleEndian.putShort(data, offset, getOptions()); + LittleEndian.putShort(data, offset+2, getRecordId()); + int remainingBytes = thedata.length; + for ( Iterator iterator = getChildRecords().iterator(); iterator.hasNext(); ) + { + EscherRecord r = (EscherRecord) iterator.next(); + remainingBytes += r.getRecordSize(); + } + LittleEndian.putInt(data, offset+4, remainingBytes); + System.arraycopy(thedata, 0, data, offset+8, thedata.length); + int pos = offset+8+thedata.length; + for ( Iterator iterator = getChildRecords().iterator(); iterator.hasNext(); ) + { + EscherRecord r = (EscherRecord) iterator.next(); + pos += r.serialize(pos, data, listener ); + } + + listener.afterRecordSerialize( pos, getRecordId(), pos - offset, this ); + return pos - offset; + } + + public byte[] getData() + { + return thedata; + } + + /** + * Returns the number of bytes that are required to serialize this record. + * + * @return Number of bytes + */ + public int getRecordSize() + { + return 8 + thedata.length; + } + + public List getChildRecords() + { + return childRecords; + } + + public void setChildRecords( List childRecords ) + { + this.childRecords = childRecords; + } + + public Object clone() + { + // shallow clone + return super.clone(); + } + + /** + * The short name for this record + */ + public String getRecordName() + { + return "Unknown 0x" + HexDump.toHex(getRecordId()); + } + + public String toString() + { + String nl = System.getProperty( "line.separator" ); + + StringBuffer children = new StringBuffer(); + if ( getChildRecords().size() > 0 ) + { + children.append( " children: " + nl ); + for ( Iterator iterator = getChildRecords().iterator(); iterator.hasNext(); ) + { + EscherRecord record = (EscherRecord) iterator.next(); + children.append( record.toString() ); + children.append( nl ); + } + } + + String theDumpHex = ""; + try + { + if (thedata.length != 0) + { + theDumpHex = " Extra Data:" + nl; + theDumpHex += HexDump.dump(thedata, 0, 0); + } + } + catch ( Exception e ) + { + theDumpHex = "Error!!"; + } + + return getClass().getName() + ":" + nl + + " isContainer: " + isContainerRecord() + nl + + " options: 0x" + HexDump.toHex( getOptions() ) + nl + + " recordId: 0x" + HexDump.toHex( getRecordId() ) + nl + + " numchildren: " + getChildRecords().size() + nl + + theDumpHex + + children.toString(); + } + + public void addChildRecord( EscherRecord childRecord ) + { + getChildRecords().add( childRecord ); + } + +} + + + diff --git a/src/java/org/apache/poi/ddf/package.html b/src/java/org/apache/poi/ddf/package.html new file mode 100644 index 0000000000..3205d487d0 --- /dev/null +++ b/src/java/org/apache/poi/ddf/package.html @@ -0,0 +1,11 @@ +<html> + +<body> + + <p>This package contains classes for decoding the Microsoft Office + Drawing format otherwise known as escher henceforth known in POI + as the Dreadful Drawing Format. + </p> + +</body> +</html>
\ No newline at end of file diff --git a/src/java/org/apache/poi/dev/RecordGenerator.java b/src/java/org/apache/poi/dev/RecordGenerator.java index 3b1338d195..b67c0168b8 100644 --- a/src/java/org/apache/poi/dev/RecordGenerator.java +++ b/src/java/org/apache/poi/dev/RecordGenerator.java @@ -88,15 +88,6 @@ public class RecordGenerator { } - /** - * Description of the Method - * - *@param defintionsDir Description of the Parameter - *@param recordStyleDir Description of the Parameter - *@param destSrcPathDir Description of the Parameter - *@param testSrcPathDir Description of the Parameter - *@exception Exception Description of the Exception - */ private static void generateRecords(String defintionsDir, String recordStyleDir, String destSrcPathDir, String testSrcPathDir) throws Exception { File definitionsFile = new File(defintionsDir); diff --git a/src/java/org/apache/poi/hssf/dev/BiffViewer.java b/src/java/org/apache/poi/hssf/dev/BiffViewer.java index da65d5920c..06e2a24658 100644 --- a/src/java/org/apache/poi/hssf/dev/BiffViewer.java +++ b/src/java/org/apache/poi/hssf/dev/BiffViewer.java @@ -2,7 +2,7 @@ * ==================================================================== * The Apache Software License, Version 1.1 * - * Copyright (c) 2003 The Apache Software Foundation. All rights + * Copyright (c) 2004 The Apache Software Foundation. All rights * reserved. * * Redistribution and use in source and binary forms, with or without @@ -107,7 +107,7 @@ public class BiffViewer { new POIFSFileSystem(new FileInputStream(filename)); InputStream stream = fs.createDocumentInputStream("Workbook"); - Record[] records = createRecords(stream, dump); + createRecords(stream, dump); } catch (Exception e) { e.printStackTrace(); } @@ -128,17 +128,20 @@ public class BiffViewer { public static Record[] createRecords(InputStream in, boolean dump) throws RecordFormatException { ArrayList records = new ArrayList(); - Record last_record = null; + //Record last_record = null; int loc = 0; + RecordDetails activeRecord = null; + try { // long offset = 0; short rectype = 0; do { rectype = LittleEndian.readShort(in); - System.out.println("============================================"); - System.out.println("Offset 0x" + Integer.toHexString(loc) + " (" + loc + ")"); + int startloc = loc; +// System.out.println("============================================"); +// System.out.println("Offset 0x" + Integer.toHexString(loc) + " (" + loc + ")"); loc += 2; if (rectype != 0) { short recsize = LittleEndian.readShort(in); @@ -147,36 +150,27 @@ public class BiffViewer { byte[] data = new byte[(int) recsize]; in.read(data); - if ((rectype == WSBoolRecord.sid) && (recsize == 0)) { - System.out.println(loc); - } loc += recsize; -// offset += 4 + recsize; - if (dump) { - dump(rectype, recsize, data); + Record record = createRecord(rectype, recsize, data ); + if (record.getSid() != ContinueRecord.sid) + { + records.add(record); + if (activeRecord != null) + activeRecord.dump(); + activeRecord = new RecordDetails(rectype, recsize, startloc, data, record); } - Record[] recs = createRecord(rectype, recsize, - data); - // handle MulRK records - - Record record = recs[0]; - - if ((record instanceof UnknownRecord) - && !dump) { - // if we didn't already dump - // just cause dump was on and we're hit an unknow - dumpUnknownRecord(data); + else + { + activeRecord.getRecord().processContinueRecord(data); } - if (record != null) { - if (rectype == ContinueRecord.sid) { - dumpContinueRecord(last_record, dump, data); - } else { - last_record = record; - records.add(record); - } + if (dump) { + dumpRaw(rectype, recsize, data); } } } while (rectype != 0); + + activeRecord.dump(); + } catch (IOException e) { throw new RecordFormatException("Error reading bytes"); } @@ -186,15 +180,14 @@ public class BiffViewer { return retval; } + private static void dumpNormal(Record record, int startloc, short rectype, short recsize) + { + System.out.println("Offset 0x" + Integer.toHexString(startloc) + " (" + startloc + ")"); + System.out.println( "recordid = 0x" + Integer.toHexString( rectype ) + ", size = " + recsize ); + System.out.println( record.toString() ); + } + - /** - * Description of the Method - * - *@param last_record Description of the Parameter - *@param dump Description of the Parameter - *@param data Description of the Parameter - *@exception IOException Description of the Exception - */ private static void dumpContinueRecord(Record last_record, boolean dump, byte[] data) throws IOException { if (last_record == null) { throw new RecordFormatException( @@ -226,12 +219,6 @@ public class BiffViewer { } - /** - * Description of the Method - * - *@param data Description of the Parameter - *@exception IOException Description of the Exception - */ private static void dumpUnknownRecord(byte[] data) throws IOException { // record hex dump it! System.out.println( @@ -247,10 +234,11 @@ public class BiffViewer { } - private static void dump( short rectype, short recsize, byte[] data ) throws IOException + private static void dumpRaw( short rectype, short recsize, byte[] data ) throws IOException { // System.out // .println("fixing to recordize the following"); + System.out.println("============================================"); System.out.print( "rectype = 0x" + Integer.toHexString( rectype ) ); System.out.println( ", recsize = 0x" @@ -275,13 +263,9 @@ public class BiffViewer { * Essentially a duplicate of RecordFactory. Kept seperate as not to screw * up non-debug operations. * - *@param rectype Description of the Parameter - *@param size Description of the Parameter - *@param data Description of the Parameter - *@return Description of the Return Value */ - private static Record[] createRecord( short rectype, short size, + private static Record createRecord( short rectype, short size, byte[] data ) { Record retval = null; @@ -429,6 +413,15 @@ public class BiffViewer { case GridsetRecord.sid: retval = new GridsetRecord( rectype, size, data ); break; + case DrawingGroupRecord.sid: + retval = new DrawingGroupRecord( rectype, size, data ); + break; + case DrawingRecordForBiffViewer.sid: + retval = new DrawingRecordForBiffViewer( rectype, size, data ); + break; + case DrawingSelectionRecord.sid: + retval = new DrawingSelectionRecord( rectype, size, data ); + break; case GutsRecord.sid: retval = new GutsRecord( rectype, size, data ); break; @@ -631,19 +624,24 @@ public class BiffViewer { retval = new PaneRecord( rectype, size, data ); break; case SharedFormulaRecord.sid: - retval = new SharedFormulaRecord( rectype, size, data); - break; + retval = new SharedFormulaRecord( rectype, size, data); + break; + case ObjRecord.sid: + retval = new ObjRecord( rectype, size, data); + break; + case TextObjectRecord.sid: + retval = new TextObjectRecord( rectype, size, data); + break; + case HorizontalPageBreakRecord.sid: + retval = new HorizontalPageBreakRecord( rectype, size, data); + break; + case VerticalPageBreakRecord.sid: + retval = new VerticalPageBreakRecord( rectype, size, data); + break; default: retval = new UnknownRecord( rectype, size, data ); } - if ( realretval == null ) - { - realretval = new Record[1]; - realretval[0] = retval; - System.out.println( "recordid = 0x" + Integer.toHexString( rectype ) + ", size =" + size ); - System.out.println( realretval[0].toString() ); - } - return realretval; + return retval; } @@ -674,6 +672,7 @@ public class BiffViewer { public static void main(String[] args) { try { + System.setProperty("poi.deserialize.escher", "true"); BiffViewer viewer = new BiffViewer(args); if ((args.length > 1) && args[1].equals("on")) { @@ -696,4 +695,50 @@ public class BiffViewer { e.printStackTrace(); } } + + static class RecordDetails + { + short rectype, recsize; + int startloc; + byte[] data; + Record record; + + public RecordDetails( short rectype, short recsize, int startloc, byte[] data, Record record ) + { + this.rectype = rectype; + this.recsize = recsize; + this.startloc = startloc; + this.data = data; + this.record = record; + } + + public short getRectype() + { + return rectype; + } + + public short getRecsize() + { + return recsize; + } + + public byte[] getData() + { + return data; + } + + public Record getRecord() + { + return record; + } + + public void dump() throws IOException + { + if (record instanceof UnknownRecord) + dumpUnknownRecord(data); + else + dumpNormal(record, startloc, rectype, recsize); + } + } + } diff --git a/src/java/org/apache/poi/hssf/model/AbstractShape.java b/src/java/org/apache/poi/hssf/model/AbstractShape.java new file mode 100644 index 0000000000..522220184f --- /dev/null +++ b/src/java/org/apache/poi/hssf/model/AbstractShape.java @@ -0,0 +1,126 @@ +package org.apache.poi.hssf.model; + +import org.apache.poi.ddf.*; +import org.apache.poi.hssf.record.ObjRecord; +import org.apache.poi.hssf.usermodel.*; + +/** + * An abstract shape is the lowlevel model for a shape. + * + * @author Glen Stampoultzis (glens at apache.org) + */ +public abstract class AbstractShape +{ + /** + * Create a new shape object used to create the escher records. + * + * @param hssfShape The simple shape this is based on. + */ + public static AbstractShape createShape( HSSFShape hssfShape, int shapeId ) + { + AbstractShape shape = null; + if (hssfShape instanceof HSSFTextbox) + { + shape = new TextboxShape( (HSSFTextbox)hssfShape, shapeId ); + } + else if (hssfShape instanceof HSSFPolygon) + { + shape = new PolygonShape( (HSSFPolygon) hssfShape, shapeId ); + } + else if (hssfShape instanceof HSSFSimpleShape) + { + HSSFSimpleShape simpleShape = (HSSFSimpleShape) hssfShape; + switch ( simpleShape.getShapeType() ) + { + case HSSFSimpleShape.OBJECT_TYPE_LINE: + shape = new LineShape( simpleShape, shapeId ); + break; + case HSSFSimpleShape.OBJECT_TYPE_OVAL: + case HSSFSimpleShape.OBJECT_TYPE_RECTANGLE: + shape = new SimpleFilledShape( simpleShape, shapeId ); + break; + default: + throw new IllegalArgumentException("Do not know how to handle this type of shape"); + } + } + else + { + throw new IllegalArgumentException("Unknown shape type"); + } + EscherSpRecord sp = shape.getSpContainer().getChildById(EscherSpRecord.RECORD_ID); + if (hssfShape.getParent() != null) + sp.setFlags(sp.getFlags() | EscherSpRecord.FLAG_CHILD); + return shape; + } + + protected AbstractShape() + { + } + + /** + * @return The shape container and it's children that can represent this + * shape. + */ + public abstract EscherContainerRecord getSpContainer(); + + /** + * @return The object record that is associated with this shape. + */ + public abstract ObjRecord getObjRecord(); + + /** + * Creates an escher anchor record from a HSSFAnchor. + * + * @param userAnchor The high level anchor to convert. + * @return An escher anchor record. + */ + protected EscherRecord createAnchor( HSSFAnchor userAnchor ) + { + return ConvertAnchor.createAnchor(userAnchor); + } + + /** + * Add standard properties to the opt record. These properties effect + * all records. + * + * @param shape The user model shape. + * @param opt The opt record to add the properties to. + * @return The number of options added. + */ + protected int addStandardOptions( HSSFShape shape, EscherOptRecord opt ) + { + opt.addEscherProperty( new EscherBoolProperty( EscherProperties.TEXT__SIZE_TEXT_TO_FIT_SHAPE, 0x080000 ) ); +// opt.addEscherProperty( new EscherBoolProperty( EscherProperties.TEXT__SIZE_TEXT_TO_FIT_SHAPE, 0x080008 ) ); + if ( shape.isNoFill() ) + { + // Wonderful... none of the spec's give any clue as to what these constants mean. + opt.addEscherProperty( new EscherBoolProperty( EscherProperties.FILL__NOFILLHITTEST, 0x00110000 ) ); + } + else + { + opt.addEscherProperty( new EscherBoolProperty( EscherProperties.FILL__NOFILLHITTEST, 0x00010000 ) ); + } + opt.addEscherProperty( new EscherRGBProperty( EscherProperties.FILL__FILLCOLOR, shape.getFillColor() ) ); + opt.addEscherProperty( new EscherBoolProperty( EscherProperties.GROUPSHAPE__PRINT, 0x080000 ) ); + opt.addEscherProperty( new EscherRGBProperty( EscherProperties.LINESTYLE__COLOR, shape.getLineStyleColor() ) ); + int options = 5; + if (shape.getLineWidth() != HSSFShape.LINEWIDTH_DEFAULT) + { + opt.addEscherProperty( new EscherSimpleProperty( EscherProperties.LINESTYLE__LINEWIDTH, shape.getLineWidth())); + options++; + } + if (shape.getLineStyle() != HSSFShape.LINESTYLE_SOLID) + { + opt.addEscherProperty( new EscherSimpleProperty( EscherProperties.LINESTYLE__LINEDASHING, shape.getLineStyle())); + opt.addEscherProperty( new EscherSimpleProperty( EscherProperties.LINESTYLE__LINEENDCAPSTYLE, 0)); + if (shape.getLineStyle() == HSSFShape.LINESTYLE_NONE) + opt.addEscherProperty( new EscherBoolProperty( EscherProperties.LINESTYLE__NOLINEDRAWDASH, 0x00080000)); + else + opt.addEscherProperty( new EscherBoolProperty( EscherProperties.LINESTYLE__NOLINEDRAWDASH, 0x00080008)); + options += 3; + } + opt.sortProperties(); + return options; // # options added + } + +} diff --git a/src/java/org/apache/poi/hssf/model/ConvertAnchor.java b/src/java/org/apache/poi/hssf/model/ConvertAnchor.java new file mode 100644 index 0000000000..bb07a121a8 --- /dev/null +++ b/src/java/org/apache/poi/hssf/model/ConvertAnchor.java @@ -0,0 +1,50 @@ +package org.apache.poi.hssf.model; + +import org.apache.poi.ddf.EscherRecord; +import org.apache.poi.ddf.EscherClientAnchorRecord; +import org.apache.poi.ddf.EscherChildAnchorRecord; +import org.apache.poi.hssf.usermodel.HSSFAnchor; +import org.apache.poi.hssf.usermodel.HSSFClientAnchor; +import org.apache.poi.hssf.usermodel.HSSFChildAnchor; + +/** + * $Id$ + */ +public class ConvertAnchor +{ + public static EscherRecord createAnchor( HSSFAnchor userAnchor ) + { + if (userAnchor instanceof HSSFClientAnchor) + { + HSSFClientAnchor a = (HSSFClientAnchor) userAnchor; + + EscherClientAnchorRecord anchor = new EscherClientAnchorRecord(); + anchor.setRecordId( EscherClientAnchorRecord.RECORD_ID ); + anchor.setOptions( (short) 0x0000 ); + anchor.setFlag( (short) 0 ); + anchor.setCol1( (short) Math.min(a.getCol1(), a.getCol2()) ); + anchor.setDx1( (short) Math.min(a.getDx1(), a.getDx2()) ); + anchor.setRow1( (short) Math.min(a.getRow1(), a.getRow2()) ); + anchor.setDy1( (short) Math.min(a.getDy1(), a.getDy2()) ); + + anchor.setCol2( (short) Math.max(a.getCol1(), a.getCol2()) ); + anchor.setDx2( (short) Math.max(a.getDx1(), a.getDx2()) ); + anchor.setRow2( (short) Math.max(a.getRow1(), a.getRow2()) ); + anchor.setDy2( (short) Math.max(a.getDy1(), a.getDy2() ) ); + return anchor; + } + else + { + HSSFChildAnchor a = (HSSFChildAnchor) userAnchor; + EscherChildAnchorRecord anchor = new EscherChildAnchorRecord(); + anchor.setRecordId( EscherChildAnchorRecord.RECORD_ID ); + anchor.setOptions( (short) 0x0000 ); + anchor.setDx1( (short) Math.min(a.getDx1(), a.getDx2()) ); + anchor.setDy1( (short) Math.min(a.getDy1(), a.getDy2()) ); + anchor.setDx2( (short) Math.max(a.getDx2(), a.getDx1()) ); + anchor.setDy2( (short) Math.max(a.getDy2(), a.getDy1()) ); + return anchor; + } + } + +} diff --git a/src/java/org/apache/poi/hssf/model/DrawingManager.java b/src/java/org/apache/poi/hssf/model/DrawingManager.java new file mode 100644 index 0000000000..585f6b90b3 --- /dev/null +++ b/src/java/org/apache/poi/hssf/model/DrawingManager.java @@ -0,0 +1,136 @@ +package org.apache.poi.hssf.model; + +import org.apache.poi.ddf.EscherDggRecord; +import org.apache.poi.ddf.EscherDgRecord; + +import java.util.Map; +import java.util.HashMap; + +/** + * Provides utilities to manage drawing groups. + * + * @author Glen Stampoultzis (glens at apache.org) + */ +public class DrawingManager +{ + EscherDggRecord dgg; + Map dgMap = new HashMap(); // key = Short(drawingId), value=EscherDgRecord + + public DrawingManager( EscherDggRecord dgg ) + { + this.dgg = dgg; + } + + public EscherDgRecord createDgRecord() + { + EscherDgRecord dg = new EscherDgRecord(); + dg.setRecordId( EscherDgRecord.RECORD_ID ); + short dgId = findNewDrawingGroupId(); + dg.setOptions( (short) ( dgId << 4 ) ); + dg.setNumShapes( 0 ); + dg.setLastMSOSPID( -1 ); + dgg.addCluster( dgId, 0 ); + dgg.setDrawingsSaved( dgg.getDrawingsSaved() + 1 ); + dgMap.put( new Short( dgId ), dg ); + return dg; + } + + /** + * Allocates new shape id for the new drawing group id. + * + * @return a new shape id. + */ + public int allocateShapeId(short drawingGroupId) + { + // Get the last shape id for this drawing group. + EscherDgRecord dg = (EscherDgRecord) dgMap.get(new Short(drawingGroupId)); + int lastShapeId = dg.getLastMSOSPID(); + + + // Have we run out of shapes for this cluster? + int newShapeId = 0; + if (lastShapeId % 1024 == 1023) + { + // Yes: + // Find the starting shape id of the next free cluster + newShapeId = findFreeSPIDBlock(); + // Create a new cluster in the dgg record. + dgg.addCluster(drawingGroupId, 1); + } + else + { + // No: + // Find the cluster for this drawing group with free space. + for (int i = 0; i < dgg.getFileIdClusters().length; i++) + { + EscherDggRecord.FileIdCluster c = dgg.getFileIdClusters()[i]; + if (c.getDrawingGroupId() == drawingGroupId) + { + if (c.getNumShapeIdsUsed() != 1024) + { + // Increment the number of shapes used for this cluster. + c.incrementShapeId(); + } + } + // If the last shape id = -1 then we know to find a free block; + if (dg.getLastMSOSPID() == -1) + { + newShapeId = findFreeSPIDBlock(); + } + else + { + // The new shape id to be the last shapeid of this cluster + 1 + newShapeId = dg.getLastMSOSPID() + 1; + } + } + } + // Increment the total number of shapes used in the dgg. + dgg.setNumShapesSaved(dgg.getNumShapesSaved() + 1); + // Is the new shape id >= max shape id for dgg? + if (newShapeId >= dgg.getShapeIdMax()) + { + // Yes: + // Set the max shape id = new shape id + 1 + dgg.setShapeIdMax(newShapeId + 1); + } + // Set last shape id for this drawing group. + dg.setLastMSOSPID(newShapeId); + // Increased the number of shapes used for this drawing group. + dg.incrementShapeCount(); + + + return newShapeId; + } + + //////////// Non-public methods ///////////// + short findNewDrawingGroupId() + { + short dgId = 1; + while ( drawingGroupExists( dgId ) ) + dgId++; + return dgId; + } + + boolean drawingGroupExists( short dgId ) + { + for ( int i = 0; i < dgg.getFileIdClusters().length; i++ ) + { + if ( dgg.getFileIdClusters()[i].getDrawingGroupId() == dgId ) + return true; + } + return false; + } + + int findFreeSPIDBlock() + { + int max = dgg.getShapeIdMax(); + int next = ( ( max / 1024 ) + 1 ) * 1024; + return next; + } + + public EscherDggRecord getDgg() + { + return dgg; + } + +} diff --git a/src/java/org/apache/poi/hssf/model/LineShape.java b/src/java/org/apache/poi/hssf/model/LineShape.java new file mode 100644 index 0000000000..0b4e4d1e51 --- /dev/null +++ b/src/java/org/apache/poi/hssf/model/LineShape.java @@ -0,0 +1,105 @@ +package org.apache.poi.hssf.model; + +import org.apache.poi.ddf.*; +import org.apache.poi.hssf.record.*; +import org.apache.poi.hssf.usermodel.*; + +/** + * Represents a line shape and creates all the line specific low level records. + * + * @author Glen Stampoultzis (glens at apache.org) + */ +public class LineShape + extends AbstractShape +{ + private EscherContainerRecord spContainer; + private ObjRecord objRecord; + + /** + * Creates the line shape from the highlevel user shape. All low level + * records are created at this point. + * + * @param hssfShape The user model shape. + * @param shapeId The identifier to use for this shape. + */ + LineShape( HSSFSimpleShape hssfShape, int shapeId ) + { + spContainer = createSpContainer(hssfShape, shapeId); + objRecord = createObjRecord(hssfShape, shapeId); + } + + /** + * Creates the lowerlevel escher records for this shape. + */ + private EscherContainerRecord createSpContainer(HSSFSimpleShape hssfShape, int shapeId) + { + HSSFShape shape = hssfShape; + + EscherContainerRecord spContainer = new EscherContainerRecord(); + EscherSpRecord sp = new EscherSpRecord(); + EscherOptRecord opt = new EscherOptRecord(); + EscherRecord anchor = new EscherClientAnchorRecord(); + EscherClientDataRecord clientData = new EscherClientDataRecord(); + + spContainer.setRecordId( EscherContainerRecord.SP_CONTAINER ); + spContainer.setOptions( (short) 0x000F ); + sp.setRecordId( EscherSpRecord.RECORD_ID ); + sp.setOptions( (short) ( (EscherAggregate.ST_LINE << 4) | 0x2 ) ); + + sp.setShapeId( shapeId ); + sp.setFlags( EscherSpRecord.FLAG_HAVEANCHOR | EscherSpRecord.FLAG_HASSHAPETYPE ); + opt.setRecordId( EscherOptRecord.RECORD_ID ); + opt.addEscherProperty( new EscherShapePathProperty( EscherProperties.GEOMETRY__SHAPEPATH, EscherShapePathProperty.COMPLEX ) ); + opt.addEscherProperty( new EscherBoolProperty( EscherProperties.LINESTYLE__NOLINEDRAWDASH, 1048592 ) ); + addStandardOptions(shape, opt); + HSSFAnchor userAnchor = shape.getAnchor(); + if (userAnchor.isHorizontallyFlipped()) + sp.setFlags(sp.getFlags() | EscherSpRecord.FLAG_FLIPHORIZ); + if (userAnchor.isVerticallyFlipped()) + sp.setFlags(sp.getFlags() | EscherSpRecord.FLAG_FLIPVERT); + anchor = createAnchor(userAnchor); + clientData.setRecordId( EscherClientDataRecord.RECORD_ID ); + clientData.setOptions( (short) 0x0000 ); + + spContainer.addChildRecord(sp); + spContainer.addChildRecord(opt); + spContainer.addChildRecord(anchor); + spContainer.addChildRecord(clientData); + + return spContainer; + } + + /** + * Creates the low level OBJ record for this shape. + */ + private ObjRecord createObjRecord(HSSFShape hssfShape, int shapeId) + { + HSSFShape shape = hssfShape; + + ObjRecord obj = new ObjRecord(); + CommonObjectDataSubRecord c = new CommonObjectDataSubRecord(); + c.setObjectType((short) ((HSSFSimpleShape)shape).getShapeType()); + c.setObjectId((short) ( shapeId )); + c.setLocked(true); + c.setPrintable(true); + c.setAutofill(true); + c.setAutoline(true); + EndSubRecord e = new EndSubRecord(); + + obj.addSubRecord(c); + obj.addSubRecord(e); + + return obj; + } + + public EscherContainerRecord getSpContainer() + { + return spContainer; + } + + public ObjRecord getObjRecord() + { + return objRecord; + } + +} diff --git a/src/java/org/apache/poi/hssf/model/PolygonShape.java b/src/java/org/apache/poi/hssf/model/PolygonShape.java new file mode 100644 index 0000000000..fccc92e6ac --- /dev/null +++ b/src/java/org/apache/poi/hssf/model/PolygonShape.java @@ -0,0 +1,143 @@ +package org.apache.poi.hssf.model; + +import org.apache.poi.ddf.*; +import org.apache.poi.hssf.record.ObjRecord; +import org.apache.poi.hssf.record.EscherAggregate; +import org.apache.poi.hssf.record.CommonObjectDataSubRecord; +import org.apache.poi.hssf.record.EndSubRecord; +import org.apache.poi.hssf.usermodel.HSSFSimpleShape; +import org.apache.poi.hssf.usermodel.HSSFShape; +import org.apache.poi.hssf.usermodel.HSSFPolygon; +import org.apache.poi.util.LittleEndian; + +public class PolygonShape + extends AbstractShape +{ + public final static short OBJECT_TYPE_MICROSOFT_OFFICE_DRAWING = 30; + + private EscherContainerRecord spContainer; + private ObjRecord objRecord; + + /** + * Creates the low evel records for an polygon. + * + * @param hssfShape The highlevel shape. + * @param shapeId The shape id to use for this shape. + */ + PolygonShape( HSSFPolygon hssfShape, int shapeId ) + { + spContainer = createSpContainer( hssfShape, shapeId ); + objRecord = createObjRecord( hssfShape, shapeId ); + } + + /** + * Generates the shape records for this shape. + * + */ + private EscherContainerRecord createSpContainer( HSSFPolygon hssfShape, int shapeId ) + { + HSSFShape shape = hssfShape; + + EscherContainerRecord spContainer = new EscherContainerRecord(); + EscherSpRecord sp = new EscherSpRecord(); + EscherOptRecord opt = new EscherOptRecord(); + EscherClientDataRecord clientData = new EscherClientDataRecord(); + + spContainer.setRecordId( EscherContainerRecord.SP_CONTAINER ); + spContainer.setOptions( (short) 0x000F ); + sp.setRecordId( EscherSpRecord.RECORD_ID ); + sp.setOptions( (short) ( ( EscherAggregate.ST_DONUT << 4 ) | 0x2 ) ); + sp.setShapeId( shapeId ); + if (hssfShape.getParent() == null) + sp.setFlags( EscherSpRecord.FLAG_HAVEANCHOR | EscherSpRecord.FLAG_HASSHAPETYPE ); + else + sp.setFlags( EscherSpRecord.FLAG_CHILD | EscherSpRecord.FLAG_HAVEANCHOR | EscherSpRecord.FLAG_HASSHAPETYPE ); + opt.setRecordId( EscherOptRecord.RECORD_ID ); + opt.addEscherProperty(new EscherSimpleProperty(EscherProperties.TRANSFORM__ROTATION, false, false, 0)); + opt.addEscherProperty(new EscherSimpleProperty(EscherProperties.GEOMETRY__RIGHT, false, false, hssfShape.getDrawAreaWidth())); + opt.addEscherProperty(new EscherSimpleProperty(EscherProperties.GEOMETRY__BOTTOM, false, false, hssfShape.getDrawAreaHeight())); + opt.addEscherProperty(new EscherShapePathProperty(EscherProperties.GEOMETRY__SHAPEPATH, EscherShapePathProperty.COMPLEX)); + EscherArrayProperty verticesProp = new EscherArrayProperty(EscherProperties.GEOMETRY__VERTICES, false, new byte[0] ); + verticesProp.setNumberOfElementsInArray(hssfShape.getXPoints().length+1); + verticesProp.setNumberOfElementsInMemory(hssfShape.getXPoints().length+1); + verticesProp.setSizeOfElements(0xFFF0); + for (int i = 0; i < hssfShape.getXPoints().length; i++) + { + byte[] data = new byte[4]; + LittleEndian.putShort(data, 0, (short)hssfShape.getXPoints()[i]); + LittleEndian.putShort(data, 2, (short)hssfShape.getYPoints()[i]); + verticesProp.setElement(i, data); + } + int point = hssfShape.getXPoints().length; + byte[] data = new byte[4]; + LittleEndian.putShort(data, 0, (short)hssfShape.getXPoints()[0]); + LittleEndian.putShort(data, 2, (short)hssfShape.getYPoints()[0]); + verticesProp.setElement(point, data); + opt.addEscherProperty(verticesProp); + EscherArrayProperty segmentsProp = new EscherArrayProperty(EscherProperties.GEOMETRY__SEGMENTINFO, false, null ); + segmentsProp.setSizeOfElements(0x0002); + segmentsProp.setNumberOfElementsInArray(hssfShape.getXPoints().length * 2 + 4); + segmentsProp.setNumberOfElementsInMemory(hssfShape.getXPoints().length * 2 + 4); + segmentsProp.setElement(0, new byte[] { (byte)0x00, (byte)0x40 } ); + segmentsProp.setElement(1, new byte[] { (byte)0x00, (byte)0xAC } ); + for (int i = 0; i < hssfShape.getXPoints().length; i++) + { + segmentsProp.setElement(2 + i * 2, new byte[] { (byte)0x01, (byte)0x00 } ); + segmentsProp.setElement(3 + i * 2, new byte[] { (byte)0x00, (byte)0xAC } ); + } + segmentsProp.setElement(segmentsProp.getNumberOfElementsInArray() - 2, new byte[] { (byte)0x01, (byte)0x60 } ); + segmentsProp.setElement(segmentsProp.getNumberOfElementsInArray() - 1, new byte[] { (byte)0x00, (byte)0x80 } ); + opt.addEscherProperty(segmentsProp); + opt.addEscherProperty(new EscherSimpleProperty(EscherProperties.GEOMETRY__FILLOK, false, false, 0x00010001)); + opt.addEscherProperty(new EscherSimpleProperty(EscherProperties.LINESTYLE__LINESTARTARROWHEAD, false, false, 0x0)); + opt.addEscherProperty(new EscherSimpleProperty(EscherProperties.LINESTYLE__LINEENDARROWHEAD, false, false, 0x0)); + opt.addEscherProperty(new EscherSimpleProperty(EscherProperties.LINESTYLE__LINEENDCAPSTYLE, false, false, 0x0)); + + addStandardOptions(shape, opt); + + EscherRecord anchor = createAnchor( shape.getAnchor() ); + clientData.setRecordId( EscherClientDataRecord.RECORD_ID ); + clientData.setOptions( (short) 0x0000 ); + + spContainer.addChildRecord( sp ); + spContainer.addChildRecord( opt ); + spContainer.addChildRecord( anchor ); + spContainer.addChildRecord( clientData ); + + return spContainer; + } + + /** + * Creates the low level OBJ record for this shape. + */ + private ObjRecord createObjRecord( HSSFShape hssfShape, int shapeId ) + { + HSSFShape shape = hssfShape; + + ObjRecord obj = new ObjRecord(); + CommonObjectDataSubRecord c = new CommonObjectDataSubRecord(); + c.setObjectType( OBJECT_TYPE_MICROSOFT_OFFICE_DRAWING ); + c.setObjectId( (short) ( shapeId ) ); + c.setLocked( true ); + c.setPrintable( true ); + c.setAutofill( true ); + c.setAutoline( true ); + EndSubRecord e = new EndSubRecord(); + + obj.addSubRecord( c ); + obj.addSubRecord( e ); + + return obj; + } + + public EscherContainerRecord getSpContainer() + { + return spContainer; + } + + public ObjRecord getObjRecord() + { + return objRecord; + } + +} diff --git a/src/java/org/apache/poi/hssf/model/Sheet.java b/src/java/org/apache/poi/hssf/model/Sheet.java index 456606bc2d..0adcd531aa 100644 --- a/src/java/org/apache/poi/hssf/model/Sheet.java +++ b/src/java/org/apache/poi/hssf/model/Sheet.java @@ -122,6 +122,8 @@ public class Sheet implements Model private Iterator rowRecIterator = null; protected int eofLoc = 0; protected ProtectRecord protect = null; + protected PageBreakRecord rowBreaks = null; + protected PageBreakRecord colBreaks = null; public static final byte PANE_LOWER_RIGHT = (byte)0; public static final byte PANE_UPPER_RIGHT = (byte)1; @@ -155,7 +157,7 @@ public class Sheet implements Model */ public static Sheet createSheet(List recs, int sheetnum, int offset) { - log.logFormatted(log.DEBUG, + log.logFormatted(POILogger.DEBUG, "Sheet createSheet (existing file) with %", new Integer(recs.size())); Sheet retval = new Sheet(); @@ -170,18 +172,18 @@ public class Sheet implements Model if (rec.getSid() == LabelRecord.sid) { - log.log(log.DEBUG, "Hit label record."); + log.log(POILogger.DEBUG, "Hit label record."); retval.containsLabels = true; } else if (rec.getSid() == BOFRecord.sid) { bofEofNestingLevel++; - log.log(log.DEBUG, "Hit BOF record. Nesting increased to " + bofEofNestingLevel); + log.log(POILogger.DEBUG, "Hit BOF record. Nesting increased to " + bofEofNestingLevel); } else if (rec.getSid() == EOFRecord.sid) { --bofEofNestingLevel; - log.log(log.DEBUG, "Hit EOF record. Nesting decreased to " + bofEofNestingLevel); + log.log(POILogger.DEBUG, "Hit EOF record. Nesting decreased to " + bofEofNestingLevel); if (bofEofNestingLevel == 0) { records.add(rec); retval.eofLoc = k; @@ -296,6 +298,14 @@ public class Sheet implements Model { rec = null; } + else if (rec.getSid() == PageBreakRecord.HORIZONTAL_SID) + { + retval.rowBreaks = (PageBreakRecord)rec; + } + else if (rec.getSid() == PageBreakRecord.VERTICAL_SID) + { + retval.colBreaks = (PageBreakRecord)rec; + } if (rec != null) @@ -312,7 +322,7 @@ public class Sheet implements Model { retval.cells = new ValueRecordsAggregate(); } - log.log(log.DEBUG, "sheet createSheet (existing file) exited"); + log.log(POILogger.DEBUG, "sheet createSheet (existing file) exited"); return retval; } @@ -371,7 +381,7 @@ public class Sheet implements Model public static Sheet createSheet(List records, int sheetnum) { - log.log(log.DEBUG, + log.log(POILogger.DEBUG, "Sheet createSheet (exisiting file) assumed offset 0"); return createSheet(records, sheetnum, 0); } @@ -386,7 +396,7 @@ public class Sheet implements Model public static Sheet createSheet() { - log.log(log.DEBUG, "Sheet createsheet from scratch called"); + log.log(POILogger.DEBUG, "Sheet createsheet from scratch called"); Sheet retval = new Sheet(); ArrayList records = new ArrayList(30); @@ -409,6 +419,10 @@ public class Sheet implements Model (DefaultRowHeightRecord) retval.createDefaultRowHeight(); records.add( retval.defaultrowheight ); records.add( retval.createWSBool() ); + retval.rowBreaks = new PageBreakRecord(PageBreakRecord.HORIZONTAL_SID); + records.add(retval.rowBreaks); + retval.colBreaks = new PageBreakRecord(PageBreakRecord.VERTICAL_SID); + records.add(retval.colBreaks); retval.header = (HeaderRecord) retval.createHeader(); records.add( retval.header ); retval.footer = (FooterRecord) retval.createFooter(); @@ -421,7 +435,7 @@ public class Sheet implements Model (DefaultColWidthRecord) retval.createDefaultColWidth(); records.add( retval.defaultcolwidth); retval.dims = ( DimensionsRecord ) retval.createDimensions(); - retval.dimsloc = 19; + retval.dimsloc = records.size()-1; records.add(retval.dims); records.add(retval.windowTwo = retval.createWindowTwo()); retval.setLoc(records.size() - 1); @@ -432,7 +446,7 @@ public class Sheet implements Model records.add(retval.protect); records.add(retval.createEOF()); retval.records = records; - log.log(log.DEBUG, "Sheet createsheet from scratch exit"); + log.log(POILogger.DEBUG, "Sheet createsheet from scratch exit"); return retval; } @@ -576,7 +590,7 @@ public class Sheet implements Model public void convertLabelRecords(Workbook wb) { - log.log(log.DEBUG, "convertLabelRecords called"); + log.log(POILogger.DEBUG, "convertLabelRecords called"); if (containsLabels) { for (int k = 0; k < records.size(); k++) @@ -600,7 +614,7 @@ public class Sheet implements Model } } } - log.log(log.DEBUG, "convertLabelRecords exit"); + log.log(POILogger.DEBUG, "convertLabelRecords exit"); } /** @@ -614,8 +628,8 @@ public class Sheet implements Model { checkCells(); checkRows(); - log.log(log.DEBUG, "Sheet.getNumRecords"); - log.logFormatted(log.DEBUG, "returning % + % + % - 2 = %", new int[] + log.log(POILogger.DEBUG, "Sheet.getNumRecords"); + log.logFormatted(POILogger.DEBUG, "returning % + % + % - 2 = %", new int[] { records.size(), cells.getPhysicalNumberOfCells(), rows.getPhysicalNumberOfRows(), @@ -638,8 +652,8 @@ public class Sheet implements Model public void setDimensions(int firstrow, short firstcol, int lastrow, short lastcol) { - log.log(log.DEBUG, "Sheet.setDimensions"); - log.log(log.DEBUG, + log.log(POILogger.DEBUG, "Sheet.setDimensions"); + log.log(POILogger.DEBUG, (new StringBuffer("firstrow")).append(firstrow) .append("firstcol").append(firstcol).append("lastrow") .append(lastrow).append("lastcol").append(lastcol) @@ -2560,4 +2574,192 @@ public class Sheet implements Model { return protect; } + + public int aggregateDrawingRecords(DrawingManager drawingManager) + { + int loc = findFirstRecordLocBySid(DrawingRecord.sid); + boolean noDrawingRecordsFound = loc == -1; + if (noDrawingRecordsFound) + { + EscherAggregate aggregate = new EscherAggregate( drawingManager ); + loc = findFirstRecordLocBySid(EscherAggregate.sid); + if (loc == -1) + { + loc = findFirstRecordLocBySid( WindowTwoRecord.sid ); + } + else + { + getRecords().remove(loc); + } + getRecords().add( loc, aggregate ); + return loc; + } + else + { + List records = getRecords(); + EscherAggregate r = EscherAggregate.createAggregate( records, loc, drawingManager ); + int startloc = loc; + while ( loc + 1 < records.size() + && records.get( loc ) instanceof DrawingRecord + && records.get( loc + 1 ) instanceof ObjRecord ) + { + loc += 2; + } + int endloc = loc-1; + for(int i = 0; i < (endloc - startloc + 1); i++) + records.remove(startloc); + records.add(startloc, r); + + return startloc; + } + } + + /** + * Perform any work necessary before the sheet is about to be serialized. + * For instance the escher aggregates size needs to be calculated before + * serialization so that the dgg record (which occurs first) can be written. + */ + public void preSerialize() + { + for ( Iterator iterator = getRecords().iterator(); iterator.hasNext(); ) + { + Record r = (Record) iterator.next(); + if (r instanceof EscherAggregate) + r.getRecordSize(); // Trigger flatterning of user model and corresponding update of dgg record. + } + } + + /** + * Shifts all the page breaks in the range "count" number of rows/columns + * @param breaks The page record to be shifted + * @param start Starting "main" value to shift breaks + * @param stop Ending "main" value to shift breaks + * @param count number of units (rows/columns) to shift by + */ + public void shiftBreaks(PageBreakRecord breaks, short start, short stop, int count) { + + if(rowBreaks == null) + return; + Iterator iterator = breaks.getBreaksIterator(); + List shiftedBreak = new ArrayList(); + while(iterator.hasNext()) + { + PageBreakRecord.Break breakItem = (PageBreakRecord.Break)iterator.next(); + short breakLocation = breakItem.main; + boolean inStart = (breakLocation >= start); + boolean inEnd = (breakLocation <= stop); + if(inStart && inEnd) + shiftedBreak.add(breakItem); + } + + iterator = shiftedBreak.iterator(); + while (iterator.hasNext()) { + PageBreakRecord.Break breakItem = (PageBreakRecord.Break)iterator.next(); + breaks.removeBreak(breakItem.main); + breaks.addBreak((short)(breakItem.main+count), breakItem.subFrom, breakItem.subTo); + } + } + + /** + * Sets a page break at the indicated row + * @param row + */ + public void setRowBreak(int row, short fromCol, short toCol) { + rowBreaks.addBreak((short)row, fromCol, toCol); + } + + /** + * Removes a page break at the indicated row + * @param row + */ + public void removeRowBreak(int row) { + rowBreaks.removeBreak((short)row); + } + + /** + * Queries if the specified row has a page break + * @param row + * @return true if the specified row has a page break + */ + public boolean isRowBroken(int row) { + return rowBreaks.getBreak((short)row) != null; + } + + /** + * Sets a page break at the indicated column + * @param row + */ + public void setColumnBreak(short column, short fromRow, short toRow) { + colBreaks.addBreak(column, fromRow, toRow); + } + + /** + * Removes a page break at the indicated column + * @param row + */ + public void removeColumnBreak(short column) { + colBreaks.removeBreak(column); + } + + /** + * Queries if the specified column has a page break + * @param row + * @return true if the specified column has a page break + */ + public boolean isColumnBroken(short column) { + return colBreaks.getBreak(column) != null; + } + + /** + * Shifts the horizontal page breaks for the indicated count + * @param startingRow + * @param endingRow + * @param count + */ + public void shiftRowBreaks(int startingRow, int endingRow, int count) { + shiftBreaks(rowBreaks, (short)startingRow, (short)endingRow, (short)count); + } + + /** + * Shifts the vertical page breaks for the indicated count + * @param startingCol + * @param endingCol + * @param count + */ + public void shiftColumnBreaks(short startingCol, short endingCol, short count) { + shiftBreaks(colBreaks, startingCol, endingCol, count); + } + + /** + * Returns all the row page breaks + * @return + */ + public Iterator getRowBreaks() { + return rowBreaks.getBreaksIterator(); + } + + /** + * Returns the number of row page breaks + * @return + */ + public int getNumRowBreaks(){ + return (int)rowBreaks.getNumBreaks(); + } + + /** + * Returns all the column page breaks + * @return + */ + public Iterator getColumnBreaks(){ + return colBreaks.getBreaksIterator(); + } + + /** + * Returns the number of column page breaks + * @return + */ + public int getNumColumnBreaks(){ + return (int)colBreaks.getNumBreaks(); + } + } diff --git a/src/java/org/apache/poi/hssf/model/SimpleFilledShape.java b/src/java/org/apache/poi/hssf/model/SimpleFilledShape.java new file mode 100644 index 0000000000..dadb02d79d --- /dev/null +++ b/src/java/org/apache/poi/hssf/model/SimpleFilledShape.java @@ -0,0 +1,111 @@ +package org.apache.poi.hssf.model; + +import org.apache.poi.ddf.*; +import org.apache.poi.hssf.record.ObjRecord; +import org.apache.poi.hssf.record.EscherAggregate; +import org.apache.poi.hssf.record.CommonObjectDataSubRecord; +import org.apache.poi.hssf.record.EndSubRecord; +import org.apache.poi.hssf.usermodel.HSSFSimpleShape; +import org.apache.poi.hssf.usermodel.HSSFShape; + +public class SimpleFilledShape + extends AbstractShape +{ + private EscherContainerRecord spContainer; + private ObjRecord objRecord; + + /** + * Creates the low evel records for an oval. + * + * @param hssfShape The highlevel shape. + * @param shapeId The shape id to use for this shape. + */ + SimpleFilledShape( HSSFSimpleShape hssfShape, int shapeId ) + { + spContainer = createSpContainer( hssfShape, shapeId ); + objRecord = createObjRecord( hssfShape, shapeId ); + } + + /** + * Generates the shape records for this shape. + * + * @param hssfShape + * @param shapeId + * @return + */ + private EscherContainerRecord createSpContainer( HSSFSimpleShape hssfShape, int shapeId ) + { + HSSFShape shape = hssfShape; + + EscherContainerRecord spContainer = new EscherContainerRecord(); + EscherSpRecord sp = new EscherSpRecord(); + EscherOptRecord opt = new EscherOptRecord(); + EscherClientDataRecord clientData = new EscherClientDataRecord(); + + spContainer.setRecordId( EscherContainerRecord.SP_CONTAINER ); + spContainer.setOptions( (short) 0x000F ); + sp.setRecordId( EscherSpRecord.RECORD_ID ); + short shapeType = objTypeToShapeType( hssfShape.getShapeType() ); + sp.setOptions( (short) ( ( shapeType << 4 ) | 0x2 ) ); + sp.setShapeId( shapeId ); + sp.setFlags( EscherSpRecord.FLAG_HAVEANCHOR | EscherSpRecord.FLAG_HASSHAPETYPE ); + opt.setRecordId( EscherOptRecord.RECORD_ID ); + addStandardOptions(shape, opt); + EscherRecord anchor = createAnchor( shape.getAnchor() ); + clientData.setRecordId( EscherClientDataRecord.RECORD_ID ); + clientData.setOptions( (short) 0x0000 ); + + spContainer.addChildRecord( sp ); + spContainer.addChildRecord( opt ); + spContainer.addChildRecord( anchor ); + spContainer.addChildRecord( clientData ); + + return spContainer; + } + + private short objTypeToShapeType( int objType ) + { + short shapeType; + if (objType == HSSFSimpleShape.OBJECT_TYPE_OVAL) + shapeType = EscherAggregate.ST_ELLIPSE; + else if (objType == HSSFSimpleShape.OBJECT_TYPE_RECTANGLE) + shapeType = EscherAggregate.ST_RECTANGLE; + else + throw new IllegalArgumentException("Unable to handle an object of this type"); + return shapeType; + } + + /** + * Creates the low level OBJ record for this shape. + */ + private ObjRecord createObjRecord( HSSFShape hssfShape, int shapeId ) + { + HSSFShape shape = hssfShape; + + ObjRecord obj = new ObjRecord(); + CommonObjectDataSubRecord c = new CommonObjectDataSubRecord(); + c.setObjectType( (short) ( (HSSFSimpleShape) shape ).getShapeType() ); + c.setObjectId( (short) ( shapeId ) ); + c.setLocked( true ); + c.setPrintable( true ); + c.setAutofill( true ); + c.setAutoline( true ); + EndSubRecord e = new EndSubRecord(); + + obj.addSubRecord( c ); + obj.addSubRecord( e ); + + return obj; + } + + public EscherContainerRecord getSpContainer() + { + return spContainer; + } + + public ObjRecord getObjRecord() + { + return objRecord; + } + +} diff --git a/src/java/org/apache/poi/hssf/model/TestDrawingManager.java b/src/java/org/apache/poi/hssf/model/TestDrawingManager.java new file mode 100644 index 0000000000..606ffccc34 --- /dev/null +++ b/src/java/org/apache/poi/hssf/model/TestDrawingManager.java @@ -0,0 +1,81 @@ +package org.apache.poi.hssf.model; + +import junit.framework.TestCase; +import org.apache.poi.ddf.EscherDggRecord; +import org.apache.poi.ddf.EscherDgRecord; + +public class TestDrawingManager extends TestCase +{ + public void testFindFreeSPIDBlock() throws Exception + { + EscherDggRecord dgg = new EscherDggRecord(); + DrawingManager dm = new DrawingManager( dgg ); + dgg.setShapeIdMax( 1024 ); + assertEquals( 2048, dm.findFreeSPIDBlock() ); + dgg.setShapeIdMax( 1025 ); + assertEquals( 2048, dm.findFreeSPIDBlock() ); + dgg.setShapeIdMax( 2047 ); + assertEquals( 2048, dm.findFreeSPIDBlock() ); + } + + public void testFindNewDrawingGroupId() throws Exception + { + EscherDggRecord dgg = new EscherDggRecord(); + dgg.setDrawingsSaved( 1 ); + dgg.setFileIdClusters( new EscherDggRecord.FileIdCluster[]{ + new EscherDggRecord.FileIdCluster( 2, 10 )} ); + DrawingManager dm = new DrawingManager( dgg ); + assertEquals( 1, dm.findNewDrawingGroupId() ); + dgg.setFileIdClusters( new EscherDggRecord.FileIdCluster[]{ + new EscherDggRecord.FileIdCluster( 1, 10 ), + new EscherDggRecord.FileIdCluster( 2, 10 )} ); + assertEquals( 3, dm.findNewDrawingGroupId() ); + } + + public void testDrawingGroupExists() throws Exception + { + EscherDggRecord dgg = new EscherDggRecord(); + dgg.setDrawingsSaved( 1 ); + dgg.setFileIdClusters( new EscherDggRecord.FileIdCluster[]{ + new EscherDggRecord.FileIdCluster( 2, 10 )} ); + DrawingManager dm = new DrawingManager( dgg ); + assertFalse( dm.drawingGroupExists( (short) 1 ) ); + assertTrue( dm.drawingGroupExists( (short) 2 ) ); + assertFalse( dm.drawingGroupExists( (short) 3 ) ); + } + + public void testCreateDgRecord() throws Exception + { + EscherDggRecord dgg = new EscherDggRecord(); + dgg.setDrawingsSaved( 0 ); + dgg.setFileIdClusters( new EscherDggRecord.FileIdCluster[]{} ); + DrawingManager dm = new DrawingManager( dgg ); + + EscherDgRecord dgRecord = dm.createDgRecord(); + assertEquals( -1, dgRecord.getLastMSOSPID() ); + assertEquals( 0, dgRecord.getNumShapes() ); + assertEquals( 1, dm.getDgg().getDrawingsSaved() ); + assertEquals( 1, dm.getDgg().getFileIdClusters().length ); + assertEquals( 1, dm.getDgg().getFileIdClusters()[0].getDrawingGroupId() ); + assertEquals( 0, dm.getDgg().getFileIdClusters()[0].getNumShapeIdsUsed() ); + } + + public void testAllocateShapeId() throws Exception + { + EscherDggRecord dgg = new EscherDggRecord(); + dgg.setDrawingsSaved( 0 ); + dgg.setFileIdClusters( new EscherDggRecord.FileIdCluster[]{} ); + DrawingManager dm = new DrawingManager( dgg ); + + EscherDgRecord dg = dm.createDgRecord(); + int shapeId = dm.allocateShapeId( dg.getDrawingGroupId() ); + assertEquals( 1024, shapeId ); + assertEquals( 1025, dgg.getShapeIdMax() ); + assertEquals( 1, dgg.getDrawingsSaved() ); + assertEquals( 1, dgg.getFileIdClusters()[0].getDrawingGroupId() ); + assertEquals( 1, dgg.getFileIdClusters()[0].getNumShapeIdsUsed() ); + assertEquals( 1024, dg.getLastMSOSPID() ); + assertEquals( 1, dg.getNumShapes() ); + } + +} diff --git a/src/java/org/apache/poi/hssf/model/TextboxShape.java b/src/java/org/apache/poi/hssf/model/TextboxShape.java new file mode 100644 index 0000000000..02143974db --- /dev/null +++ b/src/java/org/apache/poi/hssf/model/TextboxShape.java @@ -0,0 +1,151 @@ +package org.apache.poi.hssf.model; + +import org.apache.poi.ddf.*; +import org.apache.poi.hssf.record.*; +import org.apache.poi.hssf.usermodel.*; + +/** + * Represents an textbox shape and converts between the highlevel records + * and lowlevel records for an oval. + * + * @author Glen Stampoultzis (glens at apache.org) + */ +public class TextboxShape + extends AbstractShape +{ + private EscherContainerRecord spContainer; + private TextObjectRecord textObjectRecord; + private ObjRecord objRecord; + private EscherTextboxRecord escherTextbox; + + /** + * Creates the low evel records for an textbox. + * + * @param hssfShape The highlevel shape. + * @param shapeId The shape id to use for this shape. + */ + TextboxShape( HSSFTextbox hssfShape, int shapeId ) + { + spContainer = createSpContainer( hssfShape, shapeId ); + objRecord = createObjRecord( hssfShape, shapeId ); + textObjectRecord = createTextObjectRecord( hssfShape, shapeId ); + } + + /** + * Creates the low level OBJ record for this shape. + */ + private ObjRecord createObjRecord( HSSFTextbox hssfShape, int shapeId ) + { + HSSFShape shape = hssfShape; + + ObjRecord obj = new ObjRecord(); + CommonObjectDataSubRecord c = new CommonObjectDataSubRecord(); + c.setObjectType( (short) ( (HSSFSimpleShape) shape ).getShapeType() ); + c.setObjectId( (short) ( shapeId ) ); + c.setLocked( true ); + c.setPrintable( true ); + c.setAutofill( true ); + c.setAutoline( true ); + EndSubRecord e = new EndSubRecord(); + + obj.addSubRecord( c ); + obj.addSubRecord( e ); + + return obj; + } + + /** + * Generates the escher shape records for this shape. + * + * @param hssfShape + * @param shapeId + * @return + */ + private EscherContainerRecord createSpContainer( HSSFTextbox hssfShape, int shapeId ) + { + HSSFTextbox shape = hssfShape; + + EscherContainerRecord spContainer = new EscherContainerRecord(); + EscherSpRecord sp = new EscherSpRecord(); + EscherOptRecord opt = new EscherOptRecord(); + EscherRecord anchor = new EscherClientAnchorRecord(); + EscherClientDataRecord clientData = new EscherClientDataRecord(); + escherTextbox = new EscherTextboxRecord(); + + spContainer.setRecordId( EscherContainerRecord.SP_CONTAINER ); + spContainer.setOptions( (short) 0x000F ); + sp.setRecordId( EscherSpRecord.RECORD_ID ); + sp.setOptions( (short) ( ( EscherAggregate.ST_TEXTBOX << 4 ) | 0x2 ) ); + + sp.setShapeId( shapeId ); + sp.setFlags( EscherSpRecord.FLAG_HAVEANCHOR | EscherSpRecord.FLAG_HASSHAPETYPE ); + opt.setRecordId( EscherOptRecord.RECORD_ID ); + // opt.addEscherProperty( new EscherBoolProperty( EscherProperties.PROTECTION__LOCKAGAINSTGROUPING, 262144 ) ); + opt.addEscherProperty( new EscherSimpleProperty( EscherProperties.TEXT__TEXTID, 0 ) ); + opt.addEscherProperty( new EscherSimpleProperty( EscherProperties.TEXT__TEXTLEFT, shape.getMarginLeft() ) ); + opt.addEscherProperty( new EscherSimpleProperty( EscherProperties.TEXT__TEXTRIGHT, shape.getMarginRight() ) ); + opt.addEscherProperty( new EscherSimpleProperty( EscherProperties.TEXT__TEXTBOTTOM, shape.getMarginBottom() ) ); + opt.addEscherProperty( new EscherSimpleProperty( EscherProperties.TEXT__TEXTTOP, shape.getMarginTop() ) ); + addStandardOptions( shape, opt ); + HSSFAnchor userAnchor = shape.getAnchor(); + // if (userAnchor.isHorizontallyFlipped()) + // sp.setFlags(sp.getFlags() | EscherSpRecord.FLAG_FLIPHORIZ); + // if (userAnchor.isVerticallyFlipped()) + // sp.setFlags(sp.getFlags() | EscherSpRecord.FLAG_FLIPVERT); + anchor = createAnchor( userAnchor ); + clientData.setRecordId( EscherClientDataRecord.RECORD_ID ); + clientData.setOptions( (short) 0x0000 ); + escherTextbox.setRecordId( EscherTextboxRecord.RECORD_ID ); + escherTextbox.setOptions( (short) 0x0000 ); + + spContainer.addChildRecord( sp ); + spContainer.addChildRecord( opt ); + spContainer.addChildRecord( anchor ); + spContainer.addChildRecord( clientData ); + spContainer.addChildRecord( escherTextbox ); + + return spContainer; + } + + /** + * Textboxes also have an extra TXO record associated with them that most + * other shapes dont have. + */ + private TextObjectRecord createTextObjectRecord( HSSFTextbox hssfShape, int shapeId ) + { + HSSFTextbox shape = hssfShape; + + TextObjectRecord obj = new TextObjectRecord(); + obj.setHorizontalTextAlignment( TextObjectRecord.HORIZONTAL_TEXT_ALIGNMENT_LEFT_ALIGNED ); + obj.setVerticalTextAlignment( TextObjectRecord.VERTICAL_TEXT_ALIGNMENT_TOP ); + obj.setTextLocked( true ); + obj.setTextOrientation( TextObjectRecord.TEXT_ORIENTATION_NONE ); + int frLength = ( shape.getString().numFormattingRuns() + 1 ) * 8; + obj.setFormattingRunLength( (short) frLength ); + obj.setTextLength( (short) shape.getString().length() ); + obj.setStr( shape.getString() ); + obj.setReserved7( 0 ); + + return obj; + } + + public EscherContainerRecord getSpContainer() + { + return spContainer; + } + + public ObjRecord getObjRecord() + { + return objRecord; + } + + public TextObjectRecord getTextObjectRecord() + { + return textObjectRecord; + } + + public EscherRecord getEscherTextbox() + { + return escherTextbox; + } +} diff --git a/src/java/org/apache/poi/hssf/model/Workbook.java b/src/java/org/apache/poi/hssf/model/Workbook.java index e3593414c6..7a0a3e33a7 100644 --- a/src/java/org/apache/poi/hssf/model/Workbook.java +++ b/src/java/org/apache/poi/hssf/model/Workbook.java @@ -60,6 +60,7 @@ import org.apache.poi.hssf.util.HSSFColor; import org.apache.poi.hssf.util.SheetReferences; import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogger; +import org.apache.poi.ddf.*; import java.util.ArrayList; import java.util.Iterator; @@ -133,6 +134,7 @@ public class Workbook implements Model { protected int numfonts = 0; // hold the number of font records private short maxformatid = -1; // holds the max format id private boolean uses1904datewindowing = false; // whether 1904 date windowing is being used + private DrawingManager drawingManager; private static POILogger log = POILogFactory.getLogger(Workbook.class); @@ -2090,6 +2092,56 @@ public class Workbook implements Model { return palette; } + /** + * Creates a drawing group record. If it already exists then it's left + * alone. + */ + public void createDrawingGroup() + { + int dggLoc = findFirstRecordLocBySid(EscherContainerRecord.DGG_CONTAINER); + if (dggLoc == -1) + { + EscherContainerRecord dggContainer = new EscherContainerRecord(); + EscherDggRecord dgg = new EscherDggRecord(); + EscherOptRecord opt = new EscherOptRecord(); + EscherSplitMenuColorsRecord splitMenuColors = new EscherSplitMenuColorsRecord(); + + dggContainer.setRecordId((short) 0xF000); + dggContainer.setOptions((short) 0x000F); + dgg.setRecordId(EscherDggRecord.RECORD_ID); + dgg.setOptions((short)0x0000); + dgg.setShapeIdMax(1024); + dgg.setNumShapesSaved(0); + dgg.setDrawingsSaved(0); + dgg.setFileIdClusters(new EscherDggRecord.FileIdCluster[] {} ); + drawingManager = new DrawingManager(dgg); + opt.setRecordId((short) 0xF00B); + opt.setOptions((short) 0x0033); + opt.addEscherProperty( new EscherBoolProperty(EscherProperties.TEXT__SIZE_TEXT_TO_FIT_SHAPE, 524296) ); + opt.addEscherProperty( new EscherRGBProperty(EscherProperties.FILL__FILLCOLOR, 134217737) ); + opt.addEscherProperty( new EscherRGBProperty(EscherProperties.LINESTYLE__COLOR, 134217792) ); + splitMenuColors.setRecordId((short) 0xF11E); + splitMenuColors.setOptions((short) 0x0040); + splitMenuColors.setColor1(0x0800000D); + splitMenuColors.setColor2(0x0800000C); + splitMenuColors.setColor3(0x08000017); + splitMenuColors.setColor4(0x100000F7); + + dggContainer.addChildRecord(dgg); + dggContainer.addChildRecord(opt); + dggContainer.addChildRecord(splitMenuColors); + + DrawingGroupRecord drawingGroup = new DrawingGroupRecord(); + drawingGroup.addEscherRecord(dggContainer); + int loc = findFirstRecordLocBySid(CountryRecord.sid); + getRecords().add(loc+1, drawingGroup); + } + } + + public DrawingManager getDrawingManager() + { + return drawingManager; + } } diff --git a/src/java/org/apache/poi/hssf/record/AbstractEscherHolderRecord.java b/src/java/org/apache/poi/hssf/record/AbstractEscherHolderRecord.java new file mode 100644 index 0000000000..59d85acff6 --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/AbstractEscherHolderRecord.java @@ -0,0 +1,266 @@ + +/* ==================================================================== + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2003 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Apache" and "Apache Software Foundation" and + * "Apache POI" must not be used to endorse or promote products + * derived from this software without prior written permission. For + * written permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * "Apache POI", nor may "Apache" appear in their name, without + * prior written permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + */ + + +package org.apache.poi.hssf.record; + + + +import org.apache.poi.ddf.DefaultEscherRecordFactory; +import org.apache.poi.ddf.EscherRecord; +import org.apache.poi.ddf.EscherRecordFactory; +import org.apache.poi.ddf.NullEscherSerializationListener; +import org.apache.poi.util.LittleEndian; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * The escher container record is used to hold escher records. It is abstract and + * must be subclassed for maximum benefit. + * + * @author Glen Stampoultzis (glens at apache.org) + */ +public abstract class AbstractEscherHolderRecord + extends Record +{ + private static final boolean DESERIALISE = System.getProperty("poi.deserialize.escher") != null; + + private List escherRecords; + private byte[] rawData; + + + public AbstractEscherHolderRecord() + { + escherRecords = new ArrayList(); + } + + /** + * Constructs a Bar record and sets its fields appropriately. + * + * @param id id must be 0x1017 or an exception + * will be throw upon validation + * @param size size the size of the data area of the record + * @param data data of the record (should not contain sid/len) + */ + + public AbstractEscherHolderRecord(short id, short size, byte [] data) + { + super(id, size, data); + + } + + /** + * Constructs a Bar record and sets its fields appropriately. + * + * @param id id must be 0x1017 or an exception + * will be throw upon validation + * @param size size the size of the data area of the record + * @param data data of the record (should not contain sid/len) + * @param offset of the record's data + */ + + public AbstractEscherHolderRecord(short id, short size, byte [] data, int offset) + { + super(id, size, data, offset); + + } + + /** + * Checks the sid matches the expected side for this record + * + * @param id the expected sid. + */ + protected void validateSid(short id) + { + if (id != getSid()) + { + throw new RecordFormatException("Not a Bar record"); + } + } + + protected void fillFields(byte [] data, short size, int offset) + { + escherRecords = new ArrayList(); + if (! DESERIALISE ) + { + rawData = new byte[size]; + System.arraycopy(data, offset, rawData, 0, size); + } + else + { + EscherRecordFactory recordFactory = new DefaultEscherRecordFactory(); + int pos = offset; + while ( pos < offset + size ) + { + EscherRecord r = recordFactory.createRecord(data, pos); + int bytesRead = r.fillFields(data, pos, recordFactory ); + escherRecords.add(r); + pos += bytesRead; + } + } + } + + public String toString() + { + StringBuffer buffer = new StringBuffer(); + + final String nl = System.getProperty("line.separator"); + buffer.append("[" + getRecordName() + "]" + nl); + for ( Iterator iterator = escherRecords.iterator(); iterator.hasNext(); ) + { + EscherRecord r = (EscherRecord) iterator.next(); + buffer.append(r.toString()); + } + buffer.append("[/" + getRecordName() + "]" + nl); + + return buffer.toString(); + } + + protected abstract String getRecordName(); + + public int serialize(int offset, byte[] data) + { + if (escherRecords.size() == 0 && rawData != null) + { + System.arraycopy( rawData, 0, data, offset, rawData.length); + return rawData.length; + } + else + { + collapseShapeInformation(); + + LittleEndian.putShort(data, 0 + offset, getSid()); + LittleEndian.putShort(data, 2 + offset, (short)(getRecordSize() - 4)); + + int pos = offset + 4; + for ( Iterator iterator = escherRecords.iterator(); iterator.hasNext(); ) + { + EscherRecord r = (EscherRecord) iterator.next(); + pos += r.serialize(pos, data, new NullEscherSerializationListener() ); + } + + return getRecordSize(); + } + } + + /** + * Size of record (including 4 byte header) + */ + public int getRecordSize() + { + if (escherRecords.size() == 0 && rawData != null) + { + return rawData.length; + } + else + { + collapseShapeInformation(); + + int size = 4; + for ( Iterator iterator = escherRecords.iterator(); iterator.hasNext(); ) + { + EscherRecord r = (EscherRecord) iterator.next(); + size += r.getRecordSize(); + } + return size; + } + } + + private void collapseShapeInformation() + { + + } + + public abstract short getSid(); + + public Object clone() + { + throw new IllegalStateException("Not implemented yet."); + } + + public void addEscherRecord(int index, EscherRecord element) + { + escherRecords.add( index, element ); + } + + public boolean addEscherRecord(EscherRecord element) + { + return escherRecords.add( element ); + } + + public List getEscherRecords() + { + return escherRecords; + } + + public void clearEscherRecords() + { + escherRecords.clear(); + } + + + public EscherRecord getEscherRecord(int index) + { + return (EscherRecord) escherRecords.get(index); + } + + +} // END OF CLASS + + + + diff --git a/src/java/org/apache/poi/hssf/record/CommonObjectDataSubRecord.java b/src/java/org/apache/poi/hssf/record/CommonObjectDataSubRecord.java new file mode 100644 index 0000000000..65c76d6041 --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/CommonObjectDataSubRecord.java @@ -0,0 +1,498 @@ + +/* ==================================================================== + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2003 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Apache" and "Apache Software Foundation" and + * "Apache POI" must not be used to endorse or promote products + * derived from this software without prior written permission. For + * written permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * "Apache POI", nor may "Apache" appear in their name, without + * prior written permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + */ + + +package org.apache.poi.hssf.record; + + + +import org.apache.poi.util.*; + +/** + * The common object data record is used to store all common preferences for an excel object. + * NOTE: This source is automatically generated please do not modify this file. Either subclass or + * remove the record in src/records/definitions. + + * @author Glen Stampoultzis (glens at apache.org) + */ +public class CommonObjectDataSubRecord + extends SubRecord +{ + public final static short sid = 0x15; + private short field_1_objectType; + public final static short OBJECT_TYPE_GROUP = 0; + public final static short OBJECT_TYPE_LINE = 1; + public final static short OBJECT_TYPE_RECTANGLE = 2; + public final static short OBJECT_TYPE_OVAL = 3; + public final static short OBJECT_TYPE_ARC = 4; + public final static short OBJECT_TYPE_CHART = 5; + public final static short OBJECT_TYPE_TEXT = 6; + public final static short OBJECT_TYPE_BUTTON = 7; + public final static short OBJECT_TYPE_PICTURE = 8; + public final static short OBJECT_TYPE_POLYGON = 9; + public final static short OBJECT_TYPE_RESERVED1 = 10; + public final static short OBJECT_TYPE_CHECKBOX = 11; + public final static short OBJECT_TYPE_OPTION_BUTTON = 12; + public final static short OBJECT_TYPE_EDIT_BOX = 13; + public final static short OBJECT_TYPE_LABEL = 14; + public final static short OBJECT_TYPE_DIALOG_BOX = 15; + public final static short OBJECT_TYPE_SPINNER = 16; + public final static short OBJECT_TYPE_SCROLL_BAR = 17; + public final static short OBJECT_TYPE_LIST_BOX = 18; + public final static short OBJECT_TYPE_GROUP_BOX = 19; + public final static short OBJECT_TYPE_COMBO_BOX = 20; + public final static short OBJECT_TYPE_RESERVED2 = 21; + public final static short OBJECT_TYPE_RESERVED3 = 22; + public final static short OBJECT_TYPE_RESERVED4 = 23; + public final static short OBJECT_TYPE_RESERVED5 = 24; + public final static short OBJECT_TYPE_COMMENT = 25; + public final static short OBJECT_TYPE_RESERVED6 = 26; + public final static short OBJECT_TYPE_RESERVED7 = 27; + public final static short OBJECT_TYPE_RESERVED8 = 28; + public final static short OBJECT_TYPE_RESERVED9 = 29; + public final static short OBJECT_TYPE_MICROSOFT_OFFICE_DRAWING = 30; + private short field_2_objectId; + private short field_3_option; + private BitField locked = new BitField(0x1); + private BitField printable = new BitField(0x10); + private BitField autofill = new BitField(0x2000); + private BitField autoline = new BitField(0x4000); + private int field_4_reserved1; + private int field_5_reserved2; + private int field_6_reserved3; + + + public CommonObjectDataSubRecord() + { + + } + + /** + * Constructs a CommonObjectData record and sets its fields appropriately. + * + * @param id id must be 0x15 or an exception + * will be throw upon validation + * @param size size the size of the data area of the record + * @param data data of the record (should not contain sid/len) + */ + + public CommonObjectDataSubRecord(short id, short size, byte [] data) + { + super(id, size, data); + + } + + /** + * Constructs a CommonObjectData record and sets its fields appropriately. + * + * @param id id must be 0x15 or an exception + * will be throw upon validation + * @param size size the size of the data area of the record + * @param data data of the record (should not contain sid/len) + * @param offset of the record's data + */ + + public CommonObjectDataSubRecord(short id, short size, byte [] data, int offset) + { + super(id, size, data, offset); + + } + + /** + * Checks the sid matches the expected side for this record + * + * @param id the expected sid. + */ + protected void validateSid(short id) + { + if (id != sid) + { + throw new RecordFormatException("Not a CommonObjectData record"); + } + } + + protected void fillFields(byte [] data, short size, int offset) + { + + int pos = 0; + field_1_objectType = LittleEndian.getShort(data, pos + 0x0 + offset); + field_2_objectId = LittleEndian.getShort(data, pos + 0x2 + offset); + field_3_option = LittleEndian.getShort(data, pos + 0x4 + offset); + field_4_reserved1 = LittleEndian.getInt(data, pos + 0x6 + offset); + field_5_reserved2 = LittleEndian.getInt(data, pos + 0xa + offset); + field_6_reserved3 = LittleEndian.getInt(data, pos + 0xe + offset); + + } + + public String toString() + { + StringBuffer buffer = new StringBuffer(); + + buffer.append("[ftCmo]\n"); + buffer.append(" .objectType = ") + .append("0x").append(HexDump.toHex( getObjectType ())) + .append(" (").append( getObjectType() ).append(" )"); + buffer.append(System.getProperty("line.separator")); + buffer.append(" .objectId = ") + .append("0x").append(HexDump.toHex( getObjectId ())) + .append(" (").append( getObjectId() ).append(" )"); + buffer.append(System.getProperty("line.separator")); + buffer.append(" .option = ") + .append("0x").append(HexDump.toHex( getOption ())) + .append(" (").append( getOption() ).append(" )"); + buffer.append(System.getProperty("line.separator")); + buffer.append(" .locked = ").append(isLocked()).append('\n'); + buffer.append(" .printable = ").append(isPrintable()).append('\n'); + buffer.append(" .autofill = ").append(isAutofill()).append('\n'); + buffer.append(" .autoline = ").append(isAutoline()).append('\n'); + buffer.append(" .reserved1 = ") + .append("0x").append(HexDump.toHex( getReserved1 ())) + .append(" (").append( getReserved1() ).append(" )"); + buffer.append(System.getProperty("line.separator")); + buffer.append(" .reserved2 = ") + .append("0x").append(HexDump.toHex( getReserved2 ())) + .append(" (").append( getReserved2() ).append(" )"); + buffer.append(System.getProperty("line.separator")); + buffer.append(" .reserved3 = ") + .append("0x").append(HexDump.toHex( getReserved3 ())) + .append(" (").append( getReserved3() ).append(" )"); + buffer.append(System.getProperty("line.separator")); + + buffer.append("[/ftCmo]\n"); + return buffer.toString(); + } + + public int serialize(int offset, byte[] data) + { + int pos = 0; + + LittleEndian.putShort(data, 0 + offset, sid); + LittleEndian.putShort(data, 2 + offset, (short)(getRecordSize() - 4)); + + LittleEndian.putShort(data, 4 + offset + pos, field_1_objectType); + LittleEndian.putShort(data, 6 + offset + pos, field_2_objectId); + LittleEndian.putShort(data, 8 + offset + pos, field_3_option); + LittleEndian.putInt(data, 10 + offset + pos, field_4_reserved1); + LittleEndian.putInt(data, 14 + offset + pos, field_5_reserved2); + LittleEndian.putInt(data, 18 + offset + pos, field_6_reserved3); + + return getRecordSize(); + } + + /** + * Size of record (exluding 4 byte header) + */ + public int getRecordSize() + { + return 4 + 2 + 2 + 2 + 4 + 4 + 4; + } + + public short getSid() + { + return this.sid; + } + + public Object clone() { + CommonObjectDataSubRecord rec = new CommonObjectDataSubRecord(); + + rec.field_1_objectType = field_1_objectType; + rec.field_2_objectId = field_2_objectId; + rec.field_3_option = field_3_option; + rec.field_4_reserved1 = field_4_reserved1; + rec.field_5_reserved2 = field_5_reserved2; + rec.field_6_reserved3 = field_6_reserved3; + return rec; + } + + + /** + * Get the object type field for the CommonObjectData record. + * + * @return One of + * OBJECT_TYPE_GROUP + * OBJECT_TYPE_LINE + * OBJECT_TYPE_RECTANGLE + * OBJECT_TYPE_OVAL + * OBJECT_TYPE_ARC + * OBJECT_TYPE_CHART + * OBJECT_TYPE_TEXT + * OBJECT_TYPE_BUTTON + * OBJECT_TYPE_PICTURE + * OBJECT_TYPE_POLYGON + * OBJECT_TYPE_RESERVED1 + * OBJECT_TYPE_CHECKBOX + * OBJECT_TYPE_OPTION_BUTTON + * OBJECT_TYPE_EDIT_BOX + * OBJECT_TYPE_LABEL + * OBJECT_TYPE_DIALOG_BOX + * OBJECT_TYPE_SPINNER + * OBJECT_TYPE_SCROLL_BAR + * OBJECT_TYPE_LIST_BOX + * OBJECT_TYPE_GROUP_BOX + * OBJECT_TYPE_COMBO_BOX + * OBJECT_TYPE_RESERVED2 + * OBJECT_TYPE_RESERVED3 + * OBJECT_TYPE_RESERVED4 + * OBJECT_TYPE_RESERVED5 + * OBJECT_TYPE_COMMENT + * OBJECT_TYPE_RESERVED6 + * OBJECT_TYPE_RESERVED7 + * OBJECT_TYPE_RESERVED8 + * OBJECT_TYPE_RESERVED9 + * OBJECT_TYPE_MICROSOFT_OFFICE_DRAWING + */ + public short getObjectType() + { + return field_1_objectType; + } + + /** + * Set the object type field for the CommonObjectData record. + * + * @param field_1_objectType + * One of + * OBJECT_TYPE_GROUP + * OBJECT_TYPE_LINE + * OBJECT_TYPE_RECTANGLE + * OBJECT_TYPE_OVAL + * OBJECT_TYPE_ARC + * OBJECT_TYPE_CHART + * OBJECT_TYPE_TEXT + * OBJECT_TYPE_BUTTON + * OBJECT_TYPE_PICTURE + * OBJECT_TYPE_POLYGON + * OBJECT_TYPE_RESERVED1 + * OBJECT_TYPE_CHECKBOX + * OBJECT_TYPE_OPTION_BUTTON + * OBJECT_TYPE_EDIT_BOX + * OBJECT_TYPE_LABEL + * OBJECT_TYPE_DIALOG_BOX + * OBJECT_TYPE_SPINNER + * OBJECT_TYPE_SCROLL_BAR + * OBJECT_TYPE_LIST_BOX + * OBJECT_TYPE_GROUP_BOX + * OBJECT_TYPE_COMBO_BOX + * OBJECT_TYPE_RESERVED2 + * OBJECT_TYPE_RESERVED3 + * OBJECT_TYPE_RESERVED4 + * OBJECT_TYPE_RESERVED5 + * OBJECT_TYPE_COMMENT + * OBJECT_TYPE_RESERVED6 + * OBJECT_TYPE_RESERVED7 + * OBJECT_TYPE_RESERVED8 + * OBJECT_TYPE_RESERVED9 + * OBJECT_TYPE_MICROSOFT_OFFICE_DRAWING + */ + public void setObjectType(short field_1_objectType) + { + this.field_1_objectType = field_1_objectType; + } + + /** + * Get the object id field for the CommonObjectData record. + */ + public short getObjectId() + { + return field_2_objectId; + } + + /** + * Set the object id field for the CommonObjectData record. + */ + public void setObjectId(short field_2_objectId) + { + this.field_2_objectId = field_2_objectId; + } + + /** + * Get the option field for the CommonObjectData record. + */ + public short getOption() + { + return field_3_option; + } + + /** + * Set the option field for the CommonObjectData record. + */ + public void setOption(short field_3_option) + { + this.field_3_option = field_3_option; + } + + /** + * Get the reserved1 field for the CommonObjectData record. + */ + public int getReserved1() + { + return field_4_reserved1; + } + + /** + * Set the reserved1 field for the CommonObjectData record. + */ + public void setReserved1(int field_4_reserved1) + { + this.field_4_reserved1 = field_4_reserved1; + } + + /** + * Get the reserved2 field for the CommonObjectData record. + */ + public int getReserved2() + { + return field_5_reserved2; + } + + /** + * Set the reserved2 field for the CommonObjectData record. + */ + public void setReserved2(int field_5_reserved2) + { + this.field_5_reserved2 = field_5_reserved2; + } + + /** + * Get the reserved3 field for the CommonObjectData record. + */ + public int getReserved3() + { + return field_6_reserved3; + } + + /** + * Set the reserved3 field for the CommonObjectData record. + */ + public void setReserved3(int field_6_reserved3) + { + this.field_6_reserved3 = field_6_reserved3; + } + + /** + * Sets the locked field value. + * true if object is locked when sheet has been protected + */ + public void setLocked(boolean value) + { + field_3_option = locked.setShortBoolean(field_3_option, value); + } + + /** + * true if object is locked when sheet has been protected + * @return the locked field value. + */ + public boolean isLocked() + { + return locked.isSet(field_3_option); + } + + /** + * Sets the printable field value. + * object appears when printed + */ + public void setPrintable(boolean value) + { + field_3_option = printable.setShortBoolean(field_3_option, value); + } + + /** + * object appears when printed + * @return the printable field value. + */ + public boolean isPrintable() + { + return printable.isSet(field_3_option); + } + + /** + * Sets the autofill field value. + * whether object uses an automatic fill style + */ + public void setAutofill(boolean value) + { + field_3_option = autofill.setShortBoolean(field_3_option, value); + } + + /** + * whether object uses an automatic fill style + * @return the autofill field value. + */ + public boolean isAutofill() + { + return autofill.isSet(field_3_option); + } + + /** + * Sets the autoline field value. + * whether object uses an automatic line style + */ + public void setAutoline(boolean value) + { + field_3_option = autoline.setShortBoolean(field_3_option, value); + } + + /** + * whether object uses an automatic line style + * @return the autoline field value. + */ + public boolean isAutoline() + { + return autoline.isSet(field_3_option); + } + + +} // END OF CLASS + + diff --git a/src/java/org/apache/poi/hssf/record/DrawingGroupRecord.java b/src/java/org/apache/poi/hssf/record/DrawingGroupRecord.java new file mode 100644 index 0000000000..9d3ab19ce1 --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/DrawingGroupRecord.java @@ -0,0 +1,30 @@ +package org.apache.poi.hssf.record; + +public class DrawingGroupRecord extends AbstractEscherHolderRecord +{ + public static final short sid = 0xEB; + + public DrawingGroupRecord() + { + } + + public DrawingGroupRecord( short id, short size, byte[] data ) + { + super( id, size, data ); + } + + public DrawingGroupRecord( short id, short size, byte[] data, int offset ) + { + super( id, size, data, offset ); + } + + protected String getRecordName() + { + return "MSODRAWINGGROUP"; + } + + public short getSid() + { + return sid; + } +} diff --git a/src/java/org/apache/poi/hssf/record/DrawingRecord.java b/src/java/org/apache/poi/hssf/record/DrawingRecord.java new file mode 100644 index 0000000000..cb847afeb6 --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/DrawingRecord.java @@ -0,0 +1,97 @@ +package org.apache.poi.hssf.record; + +import org.apache.poi.util.LittleEndian; + +public class DrawingRecord extends Record +{ + public static final short sid = 0xEC; + + private byte[] recordData; + + public DrawingRecord() + { + } + + public DrawingRecord( short id, short size, byte[] data ) + { + super( id, size, data ); + } + + public DrawingRecord( short id, short size, byte[] data, int offset ) + { + super( id, size, data, offset ); + } + + /** + * Checks the sid matches the expected side for this record + * + * @param id the expected sid. + */ + protected void validateSid(short id) + { + if (id != sid) + { + throw new RecordFormatException("Not a MSODRAWING record"); + } + } + + protected void fillFields( byte[] data, short size, int offset ) + { + if (offset == 0 && size == data.length) + { + recordData = data; + } + else + { + recordData = new byte[size]; + System.arraycopy(data, offset, recordData, 0, size); + } + } + + protected void fillFields( byte[] data, short size ) + { + recordData = data; + } + + public int serialize( int offset, byte[] data ) + { + if (recordData == null) + { + recordData = new byte[ 0 ]; + } + LittleEndian.putShort(data, 0 + offset, sid); + LittleEndian.putShort(data, 2 + offset, ( short ) (recordData.length)); + if (recordData.length > 0) + { + System.arraycopy(recordData, 0, data, 4 + offset, recordData.length); + } + return getRecordSize(); + } + + public int getRecordSize() + { + int retval = 4; + + if (recordData != null) + { + retval += recordData.length; + } + return retval; + } + + public short getSid() + { + return sid; + } + + public byte[] getData() + { + return recordData; + } + + public void setData( byte[] thedata ) + { + this.recordData = thedata; + } + +} diff --git a/src/java/org/apache/poi/hssf/record/DrawingRecordForBiffViewer.java b/src/java/org/apache/poi/hssf/record/DrawingRecordForBiffViewer.java new file mode 100644 index 0000000000..2dbd71418f --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/DrawingRecordForBiffViewer.java @@ -0,0 +1,35 @@ +package org.apache.poi.hssf.record; + +/** + * This is purely for the biff viewer. During normal operations we don't want + * to be seeing this. + */ +public class DrawingRecordForBiffViewer + extends AbstractEscherHolderRecord +{ + public static final short sid = 0xEC; + + public DrawingRecordForBiffViewer() + { + } + + public DrawingRecordForBiffViewer( short id, short size, byte[] data ) + { + super( id, size, data ); + } + + public DrawingRecordForBiffViewer( short id, short size, byte[] data, int offset ) + { + super( id, size, data, offset ); + } + + protected String getRecordName() + { + return "MSODRAWING"; + } + + public short getSid() + { + return sid; + } +} diff --git a/src/java/org/apache/poi/hssf/record/DrawingSelectionRecord.java b/src/java/org/apache/poi/hssf/record/DrawingSelectionRecord.java new file mode 100644 index 0000000000..2ae8660fc1 --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/DrawingSelectionRecord.java @@ -0,0 +1,30 @@ +package org.apache.poi.hssf.record; + +public class DrawingSelectionRecord extends AbstractEscherHolderRecord +{ + public static final short sid = 0xED; + + public DrawingSelectionRecord() + { + } + + public DrawingSelectionRecord( short id, short size, byte[] data ) + { + super( id, size, data ); + } + + public DrawingSelectionRecord( short id, short size, byte[] data, int offset ) + { + super( id, size, data, offset ); + } + + protected String getRecordName() + { + return "MSODRAWINGSELECTION"; + } + + public short getSid() + { + return sid; + } +} diff --git a/src/java/org/apache/poi/hssf/record/EndSubRecord.java b/src/java/org/apache/poi/hssf/record/EndSubRecord.java new file mode 100644 index 0000000000..22ac23462d --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/EndSubRecord.java @@ -0,0 +1,176 @@ + +/* ==================================================================== + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2003 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Apache" and "Apache Software Foundation" and + * "Apache POI" must not be used to endorse or promote products + * derived from this software without prior written permission. For + * written permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * "Apache POI", nor may "Apache" appear in their name, without + * prior written permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + */ + + +package org.apache.poi.hssf.record; + + + +import org.apache.poi.util.*; + +/** + * The end data record is used to denote the end of the subrecords. + * NOTE: This source is automatically generated please do not modify this file. Either subclass or + * remove the record in src/records/definitions. + + * @author Glen Stampoultzis (glens at apache.org) + */ +public class EndSubRecord + extends SubRecord +{ + public final static short sid = 0x00; + + + public EndSubRecord() + { + + } + + /** + * Constructs a End record and sets its fields appropriately. + * + * @param id id must be 0x00 or an exception + * will be throw upon validation + * @param size size the size of the data area of the record + * @param data data of the record (should not contain sid/len) + */ + + public EndSubRecord(short id, short size, byte [] data) + { + super(id, size, data); + + } + + /** + * Constructs a End record and sets its fields appropriately. + * + * @param id id must be 0x00 or an exception + * will be throw upon validation + * @param size size the size of the data area of the record + * @param data data of the record (should not contain sid/len) + * @param offset of the record's data + */ + + public EndSubRecord(short id, short size, byte [] data, int offset) + { + super(id, size, data, offset); + + } + + /** + * Checks the sid matches the expected side for this record + * + * @param id the expected sid. + */ + protected void validateSid(short id) + { + if (id != sid) + { + throw new RecordFormatException("Not a End record"); + } + } + + protected void fillFields(byte [] data, short size, int offset) + { + + int pos = 0; + + } + + public String toString() + { + StringBuffer buffer = new StringBuffer(); + + buffer.append("[ftEnd]\n"); + + buffer.append("[/ftEnd]\n"); + return buffer.toString(); + } + + public int serialize(int offset, byte[] data) + { + int pos = 0; + + LittleEndian.putShort(data, 0 + offset, sid); + LittleEndian.putShort(data, 2 + offset, (short)(getRecordSize() - 4)); + + + return getRecordSize(); + } + + /** + * Size of record (exluding 4 byte header) + */ + public int getRecordSize() + { + return 4 ; + } + + public short getSid() + { + return this.sid; + } + + public Object clone() { + EndSubRecord rec = new EndSubRecord(); + + return rec; + } + + + +} // END OF CLASS + + diff --git a/src/java/org/apache/poi/hssf/record/EscherAggregate.java b/src/java/org/apache/poi/hssf/record/EscherAggregate.java new file mode 100644 index 0000000000..a45621927b --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/EscherAggregate.java @@ -0,0 +1,696 @@ +package org.apache.poi.hssf.record; + +import org.apache.poi.ddf.*; +import org.apache.poi.hssf.usermodel.*; +import org.apache.poi.hssf.model.AbstractShape; +import org.apache.poi.hssf.model.TextboxShape; +import org.apache.poi.hssf.model.DrawingManager; +import org.apache.poi.hssf.model.ConvertAnchor; + +import java.util.*; + +/** + * This class is used to aggregate the MSODRAWING and OBJ record + * combinations. This is necessary due to the bizare way in which + * these records are serialized. What happens is that you get a + * combination of MSODRAWING -> OBJ -> MSODRAWING -> OBJ records + * but the escher records are serialized _across_ the MSODRAWING + * records. + * <p> + * It gets even worse when you start looking at TXO records. + * <p> + * So what we do with this class is aggregate lazily. That is + * we don't aggregate the MSODRAWING -> OBJ records unless we + * need to modify them. + * + * + * @author Glen Stampoultzis (glens at apache.org) + */ +public class EscherAggregate extends AbstractEscherHolderRecord +{ + public static final short sid = 9876; + + public static final short ST_MIN = (short) 0; + public static final short ST_NOT_PRIMATIVE = ST_MIN; + public static final short ST_RECTANGLE = (short) 1; + public static final short ST_ROUNDRECTANGLE = (short) 2; + public static final short ST_ELLIPSE = (short) 3; + public static final short ST_DIAMOND = (short) 4; + public static final short ST_ISOCELESTRIANGLE = (short) 5; + public static final short ST_RIGHTTRIANGLE = (short) 6; + public static final short ST_PARALLELOGRAM = (short) 7; + public static final short ST_TRAPEZOID = (short) 8; + public static final short ST_HEXAGON = (short) 9; + public static final short ST_OCTAGON = (short) 10; + public static final short ST_PLUS = (short) 11; + public static final short ST_STAR = (short) 12; + public static final short ST_ARROW = (short) 13; + public static final short ST_THICKARROW = (short) 14; + public static final short ST_HOMEPLATE = (short) 15; + public static final short ST_CUBE = (short) 16; + public static final short ST_BALLOON = (short) 17; + public static final short ST_SEAL = (short) 18; + public static final short ST_ARC = (short) 19; + public static final short ST_LINE = (short) 20; + public static final short ST_PLAQUE = (short) 21; + public static final short ST_CAN = (short) 22; + public static final short ST_DONUT = (short) 23; + public static final short ST_TEXTSIMPLE = (short) 24; + public static final short ST_TEXTOCTAGON = (short) 25; + public static final short ST_TEXTHEXAGON = (short) 26; + public static final short ST_TEXTCURVE = (short) 27; + public static final short ST_TEXTWAVE = (short) 28; + public static final short ST_TEXTRING = (short) 29; + public static final short ST_TEXTONCURVE = (short) 30; + public static final short ST_TEXTONRING = (short) 31; + public static final short ST_STRAIGHTCONNECTOR1 = (short) 32; + public static final short ST_BENTCONNECTOR2 = (short) 33; + public static final short ST_BENTCONNECTOR3 = (short) 34; + public static final short ST_BENTCONNECTOR4 = (short) 35; + public static final short ST_BENTCONNECTOR5 = (short) 36; + public static final short ST_CURVEDCONNECTOR2 = (short) 37; + public static final short ST_CURVEDCONNECTOR3 = (short) 38; + public static final short ST_CURVEDCONNECTOR4 = (short) 39; + public static final short ST_CURVEDCONNECTOR5 = (short) 40; + public static final short ST_CALLOUT1 = (short) 41; + public static final short ST_CALLOUT2 = (short) 42; + public static final short ST_CALLOUT3 = (short) 43; + public static final short ST_ACCENTCALLOUT1 = (short) 44; + public static final short ST_ACCENTCALLOUT2 = (short) 45; + public static final short ST_ACCENTCALLOUT3 = (short) 46; + public static final short ST_BORDERCALLOUT1 = (short) 47; + public static final short ST_BORDERCALLOUT2 = (short) 48; + public static final short ST_BORDERCALLOUT3 = (short) 49; + public static final short ST_ACCENTBORDERCALLOUT1 = (short) 50; + public static final short ST_ACCENTBORDERCALLOUT2 = (short) 51; + public static final short ST_ACCENTBORDERCALLOUT3 = (short) 52; + public static final short ST_RIBBON = (short) 53; + public static final short ST_RIBBON2 = (short) 54; + public static final short ST_CHEVRON = (short) 55; + public static final short ST_PENTAGON = (short) 56; + public static final short ST_NOSMOKING = (short) 57; + public static final short ST_SEAL8 = (short) 58; + public static final short ST_SEAL16 = (short) 59; + public static final short ST_SEAL32 = (short) 60; + public static final short ST_WEDGERECTCALLOUT = (short) 61; + public static final short ST_WEDGERRECTCALLOUT = (short) 62; + public static final short ST_WEDGEELLIPSECALLOUT = (short) 63; + public static final short ST_WAVE = (short) 64; + public static final short ST_FOLDEDCORNER = (short) 65; + public static final short ST_LEFTARROW = (short) 66; + public static final short ST_DOWNARROW = (short) 67; + public static final short ST_UPARROW = (short) 68; + public static final short ST_LEFTRIGHTARROW = (short) 69; + public static final short ST_UPDOWNARROW = (short) 70; + public static final short ST_IRREGULARSEAL1 = (short) 71; + public static final short ST_IRREGULARSEAL2 = (short) 72; + public static final short ST_LIGHTNINGBOLT = (short) 73; + public static final short ST_HEART = (short) 74; + public static final short ST_PICTUREFRAME = (short) 75; + public static final short ST_QUADARROW = (short) 76; + public static final short ST_LEFTARROWCALLOUT = (short) 77; + public static final short ST_RIGHTARROWCALLOUT = (short) 78; + public static final short ST_UPARROWCALLOUT = (short) 79; + public static final short ST_DOWNARROWCALLOUT = (short) 80; + public static final short ST_LEFTRIGHTARROWCALLOUT = (short) 81; + public static final short ST_UPDOWNARROWCALLOUT = (short) 82; + public static final short ST_QUADARROWCALLOUT = (short) 83; + public static final short ST_BEVEL = (short) 84; + public static final short ST_LEFTBRACKET = (short) 85; + public static final short ST_RIGHTBRACKET = (short) 86; + public static final short ST_LEFTBRACE = (short) 87; + public static final short ST_RIGHTBRACE = (short) 88; + public static final short ST_LEFTUPARROW = (short) 89; + public static final short ST_BENTUPARROW = (short) 90; + public static final short ST_BENTARROW = (short) 91; + public static final short ST_SEAL24 = (short) 92; + public static final short ST_STRIPEDRIGHTARROW = (short) 93; + public static final short ST_NOTCHEDRIGHTARROW = (short) 94; + public static final short ST_BLOCKARC = (short) 95; + public static final short ST_SMILEYFACE = (short) 96; + public static final short ST_VERTICALSCROLL = (short) 97; + public static final short ST_HORIZONTALSCROLL = (short) 98; + public static final short ST_CIRCULARARROW = (short) 99; + public static final short ST_NOTCHEDCIRCULARARROW = (short) 100; + public static final short ST_UTURNARROW = (short) 101; + public static final short ST_CURVEDRIGHTARROW = (short) 102; + public static final short ST_CURVEDLEFTARROW = (short) 103; + public static final short ST_CURVEDUPARROW = (short) 104; + public static final short ST_CURVEDDOWNARROW = (short) 105; + public static final short ST_CLOUDCALLOUT = (short) 106; + public static final short ST_ELLIPSERIBBON = (short) 107; + public static final short ST_ELLIPSERIBBON2 = (short) 108; + public static final short ST_FLOWCHARTPROCESS = (short) 109; + public static final short ST_FLOWCHARTDECISION = (short) 110; + public static final short ST_FLOWCHARTINPUTOUTPUT = (short) 111; + public static final short ST_FLOWCHARTPREDEFINEDPROCESS = (short) 112; + public static final short ST_FLOWCHARTINTERNALSTORAGE = (short) 113; + public static final short ST_FLOWCHARTDOCUMENT = (short) 114; + public static final short ST_FLOWCHARTMULTIDOCUMENT = (short) 115; + public static final short ST_FLOWCHARTTERMINATOR = (short) 116; + public static final short ST_FLOWCHARTPREPARATION = (short) 117; + public static final short ST_FLOWCHARTMANUALINPUT = (short) 118; + public static final short ST_FLOWCHARTMANUALOPERATION = (short) 119; + public static final short ST_FLOWCHARTCONNECTOR = (short) 120; + public static final short ST_FLOWCHARTPUNCHEDCARD = (short) 121; + public static final short ST_FLOWCHARTPUNCHEDTAPE = (short) 122; + public static final short ST_FLOWCHARTSUMMINGJUNCTION = (short) 123; + public static final short ST_FLOWCHARTOR = (short) 124; + public static final short ST_FLOWCHARTCOLLATE = (short) 125; + public static final short ST_FLOWCHARTSORT = (short) 126; + public static final short ST_FLOWCHARTEXTRACT = (short) 127; + public static final short ST_FLOWCHARTMERGE = (short) 128; + public static final short ST_FLOWCHARTOFFLINESTORAGE = (short) 129; + public static final short ST_FLOWCHARTONLINESTORAGE = (short) 130; + public static final short ST_FLOWCHARTMAGNETICTAPE = (short) 131; + public static final short ST_FLOWCHARTMAGNETICDISK = (short) 132; + public static final short ST_FLOWCHARTMAGNETICDRUM = (short) 133; + public static final short ST_FLOWCHARTDISPLAY = (short) 134; + public static final short ST_FLOWCHARTDELAY = (short) 135; + public static final short ST_TEXTPLAINTEXT = (short) 136; + public static final short ST_TEXTSTOP = (short) 137; + public static final short ST_TEXTTRIANGLE = (short) 138; + public static final short ST_TEXTTRIANGLEINVERTED = (short) 139; + public static final short ST_TEXTCHEVRON = (short) 140; + public static final short ST_TEXTCHEVRONINVERTED = (short) 141; + public static final short ST_TEXTRINGINSIDE = (short) 142; + public static final short ST_TEXTRINGOUTSIDE = (short) 143; + public static final short ST_TEXTARCHUPCURVE = (short) 144; + public static final short ST_TEXTARCHDOWNCURVE = (short) 145; + public static final short ST_TEXTCIRCLECURVE = (short) 146; + public static final short ST_TEXTBUTTONCURVE = (short) 147; + public static final short ST_TEXTARCHUPPOUR = (short) 148; + public static final short ST_TEXTARCHDOWNPOUR = (short) 149; + public static final short ST_TEXTCIRCLEPOUR = (short) 150; + public static final short ST_TEXTBUTTONPOUR = (short) 151; + public static final short ST_TEXTCURVEUP = (short) 152; + public static final short ST_TEXTCURVEDOWN = (short) 153; + public static final short ST_TEXTCASCADEUP = (short) 154; + public static final short ST_TEXTCASCADEDOWN = (short) 155; + public static final short ST_TEXTWAVE1 = (short) 156; + public static final short ST_TEXTWAVE2 = (short) 157; + public static final short ST_TEXTWAVE3 = (short) 158; + public static final short ST_TEXTWAVE4 = (short) 159; + public static final short ST_TEXTINFLATE = (short) 160; + public static final short ST_TEXTDEFLATE = (short) 161; + public static final short ST_TEXTINFLATEBOTTOM = (short) 162; + public static final short ST_TEXTDEFLATEBOTTOM = (short) 163; + public static final short ST_TEXTINFLATETOP = (short) 164; + public static final short ST_TEXTDEFLATETOP = (short) 165; + public static final short ST_TEXTDEFLATEINFLATE = (short) 166; + public static final short ST_TEXTDEFLATEINFLATEDEFLATE = (short) 167; + public static final short ST_TEXTFADERIGHT = (short) 168; + public static final short ST_TEXTFADELEFT = (short) 169; + public static final short ST_TEXTFADEUP = (short) 170; + public static final short ST_TEXTFADEDOWN = (short) 171; + public static final short ST_TEXTSLANTUP = (short) 172; + public static final short ST_TEXTSLANTDOWN = (short) 173; + public static final short ST_TEXTCANUP = (short) 174; + public static final short ST_TEXTCANDOWN = (short) 175; + public static final short ST_FLOWCHARTALTERNATEPROCESS = (short) 176; + public static final short ST_FLOWCHARTOFFPAGECONNECTOR = (short) 177; + public static final short ST_CALLOUT90 = (short) 178; + public static final short ST_ACCENTCALLOUT90 = (short) 179; + public static final short ST_BORDERCALLOUT90 = (short) 180; + public static final short ST_ACCENTBORDERCALLOUT90 = (short) 181; + public static final short ST_LEFTRIGHTUPARROW = (short) 182; + public static final short ST_SUN = (short) 183; + public static final short ST_MOON = (short) 184; + public static final short ST_BRACKETPAIR = (short) 185; + public static final short ST_BRACEPAIR = (short) 186; + public static final short ST_SEAL4 = (short) 187; + public static final short ST_DOUBLEWAVE = (short) 188; + public static final short ST_ACTIONBUTTONBLANK = (short) 189; + public static final short ST_ACTIONBUTTONHOME = (short) 190; + public static final short ST_ACTIONBUTTONHELP = (short) 191; + public static final short ST_ACTIONBUTTONINFORMATION = (short) 192; + public static final short ST_ACTIONBUTTONFORWARDNEXT = (short) 193; + public static final short ST_ACTIONBUTTONBACKPREVIOUS = (short) 194; + public static final short ST_ACTIONBUTTONEND = (short) 195; + public static final short ST_ACTIONBUTTONBEGINNING = (short) 196; + public static final short ST_ACTIONBUTTONRETURN = (short) 197; + public static final short ST_ACTIONBUTTONDOCUMENT = (short) 198; + public static final short ST_ACTIONBUTTONSOUND = (short) 199; + public static final short ST_ACTIONBUTTONMOVIE = (short) 200; + public static final short ST_HOSTCONTROL = (short) 201; + public static final short ST_TEXTBOX = (short) 202; + public static final short ST_NIL = (short) 0x0FFF; + + protected HSSFPatriarch patriarch; + + /** Maps shape container objects to their OBJ records */ + private Map shapeToObj = new HashMap(); + private DrawingManager drawingManager; + private short drawingGroupId; + + public EscherAggregate( DrawingManager drawingManager ) + { + this.drawingManager = drawingManager; + } + + /** + * @return Returns the current sid. + */ + public short getSid() + { + return sid; + } + + /** + * Unused since this is an aggregate record. Use createAggregate(). + * + * @see #createAggregate + */ + protected void fillFields( byte[] data, short size, int offset ) + { + throw new IllegalStateException( "Should not reach here" ); + } + + /** + * Calculates the string representation of this record. This is + * simply a dump of all the records. + */ + public String toString() + { + String nl = System.getProperty( "line.separtor" ); + + StringBuffer result = new StringBuffer(); + result.append( '[' ).append( getRecordName() ).append( ']' + nl ); + for ( Iterator iterator = getEscherRecords().iterator(); iterator.hasNext(); ) + { + EscherRecord escherRecord = (EscherRecord) iterator.next(); + result.append( escherRecord.toString() ); + } + result.append( "[/" ).append( getRecordName() ).append( ']' + nl ); + + return result.toString(); + } + + /** + * Collapses the drawing records into an aggregate. + */ + public static EscherAggregate createAggregate( List records, int locFirstDrawingRecord, DrawingManager drawingManager ) + { + // Keep track of any shape records created so we can match them back to the object id's. + // Textbox objects are also treated as shape objects. + final List shapeRecords = new ArrayList(); + EscherRecordFactory recordFactory = new DefaultEscherRecordFactory() + { + public EscherRecord createRecord( byte[] data, int offset ) + { + EscherRecord r = super.createRecord( data, offset ); + if ( r.getRecordId() == EscherClientDataRecord.RECORD_ID || r.getRecordId() == EscherTextboxRecord.RECORD_ID ) + { + shapeRecords.add( r ); + } + return r; + } + }; + + // Calculate the size of the buffer + EscherAggregate agg = new EscherAggregate(drawingManager); + int loc = locFirstDrawingRecord; + int dataSize = 0; + while ( loc + 1 < records.size() + && sid( records, loc ) == DrawingRecord.sid + && isObjectRecord( records, loc + 1 ) ) + { + dataSize += ( (DrawingRecord) records.get( loc ) ).getData().length; + loc += 2; + } + + // Create one big buffer + byte buffer[] = new byte[dataSize]; + int offset = 0; + loc = locFirstDrawingRecord; + while ( loc + 1 < records.size() + && sid( records, loc ) == DrawingRecord.sid + && isObjectRecord( records, loc + 1 ) ) + { + DrawingRecord drawingRecord = (DrawingRecord) records.get( loc ); + System.arraycopy( drawingRecord.getData(), 0, buffer, offset, drawingRecord.getData().length ); + offset += drawingRecord.getData().length; + loc += 2; + } + + // Decode the shapes + // agg.escherRecords = new ArrayList(); + int pos = 0; + while ( pos < dataSize ) + { + EscherRecord r = recordFactory.createRecord( buffer, pos ); + int bytesRead = r.fillFields( buffer, pos, recordFactory ); + agg.addEscherRecord( r ); + pos += bytesRead; + } + + // Associate the object records with the shapes + loc = locFirstDrawingRecord; + int shapeIndex = 0; + agg.shapeToObj = new HashMap(); + while ( loc + 1 < records.size() + && sid( records, loc ) == DrawingRecord.sid + && isObjectRecord( records, loc + 1 ) ) + { + Record objRecord = (Record) records.get( loc + 1 ); + agg.shapeToObj.put( shapeRecords.get( shapeIndex++ ), objRecord ); + loc += 2; + } + + return agg; + + } + + /** + * Serializes this aggregate to a byte array. Since this is an aggregate + * record it will effectively serialize the aggregated records. + * + * @param offset The offset into the start of the array. + * @param data The byte array to serialize to. + * @return The number of bytes serialized. + */ + public int serialize( int offset, byte[] data ) + { + convertUserModelToRecords(); + + // Determine buffer size + List records = getEscherRecords(); + int size = getEscherRecordSize( records ); + byte[] buffer = new byte[size]; + + + // Serialize escher records into one big data structure and keep note of ending offsets. + final List spEndingOffsets = new ArrayList(); + final List shapes = new ArrayList(); + int pos = 0; + for ( Iterator iterator = records.iterator(); iterator.hasNext(); ) + { + EscherRecord e = (EscherRecord) iterator.next(); + pos += e.serialize( pos, buffer, new EscherSerializationListener() + { + public void beforeRecordSerialize( int offset, short recordId, EscherRecord record ) + { + } + + public void afterRecordSerialize( int offset, short recordId, int size, EscherRecord record ) + { + if ( recordId == EscherClientDataRecord.RECORD_ID || recordId == EscherTextboxRecord.RECORD_ID ) + { + spEndingOffsets.add( new Integer( offset ) ); + shapes.add( record ); + } + } + } ); + } + // todo: fix this + shapes.add( 0, null ); + spEndingOffsets.add( 0, null ); + + // Split escher records into separate MSODRAWING and OBJ, TXO records. (We don't break on + // the first one because it's the patriach). + pos = offset; + for ( int i = 1; i < shapes.size(); i++ ) + { + int endOffset = ( (Integer) spEndingOffsets.get( i ) ).intValue() - 1; + int startOffset; + if ( i == 1 ) + startOffset = 0; + else + startOffset = ( (Integer) spEndingOffsets.get( i - 1 ) ).intValue(); + + // Create and write a new MSODRAWING record + DrawingRecord drawing = new DrawingRecord(); + byte[] drawingData = new byte[endOffset - startOffset + 1]; + System.arraycopy( buffer, startOffset, drawingData, 0, drawingData.length ); + drawing.setData( drawingData ); + int temp = drawing.serialize( pos, data ); + pos += temp; + + // Write the matching OBJ record + Record obj = (Record) shapeToObj.get( shapes.get( i ) ); + temp = obj.serialize( pos, data ); + pos += temp; + + } + + int bytesWritten = pos - offset; + if ( bytesWritten != getRecordSize() ) + throw new RecordFormatException( bytesWritten + " bytes written but getRecordSize() reports " + getRecordSize() ); + return bytesWritten; + } + + /** + * How many bytes do the raw escher records contain. + * @param records List of escher records + * @return the number of bytes + */ + private int getEscherRecordSize( List records ) + { + int size = 0; + for ( Iterator iterator = records.iterator(); iterator.hasNext(); ) + size += ( (EscherRecord) iterator.next() ).getRecordSize(); + return size; + } + + /** + * The number of bytes required to serialize this record. + */ + public int getRecordSize() + { + convertUserModelToRecords(); + List records = getEscherRecords(); + int rawEscherSize = getEscherRecordSize( records ); + int drawingRecordSize = rawEscherSize + ( shapeToObj.size() ) * 4; + int objRecordSize = 0; + for ( Iterator iterator = shapeToObj.values().iterator(); iterator.hasNext(); ) + { + Record r = (Record) iterator.next(); + objRecordSize += r.getRecordSize(); + } + return drawingRecordSize + objRecordSize; + } + + /** + * Associates an escher record to an OBJ record or a TXO record. + */ + public Object assoicateShapeToObjRecord( EscherRecord r, Record objRecord ) + { + return shapeToObj.put( r, objRecord ); + } + + public HSSFPatriarch getPatriarch() + { + return patriarch; + } + + public void setPatriarch( HSSFPatriarch patriarch ) + { + this.patriarch = patriarch; + } + + public void clear() + { + clearEscherRecords(); + shapeToObj.clear(); +// lastShapeId = 1024; + } + + protected String getRecordName() + { + return "ESCHERAGGREGATE"; + } + + // =============== Private methods ======================== + + private static boolean isObjectRecord( List records, int loc ) + { + return sid( records, loc ) == ObjRecord.sid || sid( records, loc ) == TextObjectRecord.sid; + } + + private void convertUserModelToRecords() + { + if ( patriarch != null ) + { + shapeToObj.clear(); + clearEscherRecords(); + if ( patriarch.getChildren().size() != 0 ) + { + convertPatriarch( patriarch ); + EscherContainerRecord dgContainer = (EscherContainerRecord) getEscherRecord( 0 ); + EscherContainerRecord spgrContainer = null; + for ( int i = 0; i < dgContainer.getChildRecords().size(); i++ ) + if ( dgContainer.getChild( i ).getRecordId() == EscherContainerRecord.SPGR_CONTAINER ) + spgrContainer = (EscherContainerRecord) dgContainer.getChild( i ); + convertShapes( patriarch, spgrContainer, shapeToObj ); + + patriarch = null; + } + } + } + + private void convertShapes( HSSFShapeContainer parent, EscherContainerRecord escherParent, Map shapeToObj ) + { + if ( escherParent == null ) throw new IllegalArgumentException( "Parent record required" ); + + List shapes = parent.getChildren(); + for ( Iterator iterator = shapes.iterator(); iterator.hasNext(); ) + { + HSSFShape shape = (HSSFShape) iterator.next(); + if ( shape instanceof HSSFShapeGroup ) + { + convertGroup( (HSSFShapeGroup) shape, escherParent, shapeToObj ); + } + else + { + AbstractShape shapeModel = AbstractShape.createShape( + shape, + drawingManager.allocateShapeId(drawingGroupId) ); + shapeToObj.put( findClientData( shapeModel.getSpContainer() ), shapeModel.getObjRecord() ); + if ( shapeModel instanceof TextboxShape ) + { + EscherRecord escherTextbox = ( (TextboxShape) shapeModel ).getEscherTextbox(); + shapeToObj.put( escherTextbox, ( (TextboxShape) shapeModel ).getTextObjectRecord() ); + // escherParent.addChildRecord(escherTextbox); + } + escherParent.addChildRecord( shapeModel.getSpContainer() ); + } + } + } + + private void convertGroup( HSSFShapeGroup shape, EscherContainerRecord escherParent, Map shapeToObj ) + { + EscherContainerRecord spgrContainer = new EscherContainerRecord(); + EscherContainerRecord spContainer = new EscherContainerRecord(); + EscherSpgrRecord spgr = new EscherSpgrRecord(); + EscherSpRecord sp = new EscherSpRecord(); + EscherOptRecord opt = new EscherOptRecord(); + EscherRecord anchor; + EscherClientDataRecord clientData = new EscherClientDataRecord(); + + spgrContainer.setRecordId( EscherContainerRecord.SPGR_CONTAINER ); + spgrContainer.setOptions( (short) 0x000F ); + spContainer.setRecordId( EscherContainerRecord.SP_CONTAINER ); + spContainer.setOptions( (short) 0x000F ); + spgr.setRecordId( EscherSpgrRecord.RECORD_ID ); + spgr.setOptions( (short) 0x0001 ); + spgr.setRectX1( shape.getX1() ); + spgr.setRectY1( shape.getY1() ); + spgr.setRectX2( shape.getX2() ); + spgr.setRectY2( shape.getY2() ); + sp.setRecordId( EscherSpRecord.RECORD_ID ); + sp.setOptions( (short) 0x0002 ); + int shapeId = drawingManager.allocateShapeId(drawingGroupId); + sp.setShapeId( shapeId ); + if (shape.getAnchor() instanceof HSSFClientAnchor) + sp.setFlags( EscherSpRecord.FLAG_GROUP | EscherSpRecord.FLAG_HAVEANCHOR ); + else + sp.setFlags( EscherSpRecord.FLAG_GROUP | EscherSpRecord.FLAG_HAVEANCHOR | EscherSpRecord.FLAG_CHILD ); + opt.setRecordId( EscherOptRecord.RECORD_ID ); + opt.setOptions( (short) 0x0023 ); + opt.addEscherProperty( new EscherBoolProperty( EscherProperties.PROTECTION__LOCKAGAINSTGROUPING, 0x00040004 ) ); + opt.addEscherProperty( new EscherBoolProperty( EscherProperties.GROUPSHAPE__PRINT, 0x00080000 ) ); + + anchor = ConvertAnchor.createAnchor( shape.getAnchor() ); +// clientAnchor.setCol1( ( (HSSFClientAnchor) shape.getAnchor() ).getCol1() ); +// clientAnchor.setRow1( (short) ( (HSSFClientAnchor) shape.getAnchor() ).getRow1() ); +// clientAnchor.setDx1( (short) shape.getAnchor().getDx1() ); +// clientAnchor.setDy1( (short) shape.getAnchor().getDy1() ); +// clientAnchor.setCol2( ( (HSSFClientAnchor) shape.getAnchor() ).getCol2() ); +// clientAnchor.setRow2( (short) ( (HSSFClientAnchor) shape.getAnchor() ).getRow2() ); +// clientAnchor.setDx2( (short) shape.getAnchor().getDx2() ); +// clientAnchor.setDy2( (short) shape.getAnchor().getDy2() ); + clientData.setRecordId( EscherClientDataRecord.RECORD_ID ); + clientData.setOptions( (short) 0x0000 ); + + spgrContainer.addChildRecord( spContainer ); + spContainer.addChildRecord( spgr ); + spContainer.addChildRecord( sp ); + spContainer.addChildRecord( opt ); + spContainer.addChildRecord( anchor ); + spContainer.addChildRecord( clientData ); + + ObjRecord obj = new ObjRecord(); + CommonObjectDataSubRecord cmo = new CommonObjectDataSubRecord(); + cmo.setObjectType( CommonObjectDataSubRecord.OBJECT_TYPE_GROUP ); + cmo.setObjectId( (short) ( shapeId ) ); + cmo.setLocked( true ); + cmo.setPrintable( true ); + cmo.setAutofill( true ); + cmo.setAutoline( true ); + GroupMarkerSubRecord gmo = new GroupMarkerSubRecord(); + EndSubRecord end = new EndSubRecord(); + obj.addSubRecord( cmo ); + obj.addSubRecord( gmo ); + obj.addSubRecord( end ); + shapeToObj.put( clientData, obj ); + + escherParent.addChildRecord( spgrContainer ); + + convertShapes( shape, spgrContainer, shapeToObj ); + + } + + private EscherRecord findClientData( EscherContainerRecord spContainer ) + { + for ( Iterator iterator = spContainer.getChildRecords().iterator(); iterator.hasNext(); ) + { + EscherRecord r = (EscherRecord) iterator.next(); + if ( r.getRecordId() == EscherClientDataRecord.RECORD_ID ) + return r; + } + throw new IllegalArgumentException( "Can not find client data record" ); + } + + private void convertPatriarch( HSSFPatriarch patriarch ) + { + EscherContainerRecord dgContainer = new EscherContainerRecord(); + EscherDgRecord dg; + EscherContainerRecord spgrContainer = new EscherContainerRecord(); + EscherContainerRecord spContainer1 = new EscherContainerRecord(); + EscherSpgrRecord spgr = new EscherSpgrRecord(); + EscherSpRecord sp1 = new EscherSpRecord(); + + dgContainer.setRecordId( EscherContainerRecord.DG_CONTAINER ); + dgContainer.setOptions( (short) 0x000F ); + dg = drawingManager.createDgRecord(); + drawingGroupId = dg.getDrawingGroupId(); +// dg.setOptions( (short) ( drawingId << 4 ) ); +// dg.setNumShapes( getNumberOfShapes( patriarch ) ); +// dg.setLastMSOSPID( 0 ); // populated after all shape id's are assigned. + spgrContainer.setRecordId( EscherContainerRecord.SPGR_CONTAINER ); + spgrContainer.setOptions( (short) 0x000F ); + spContainer1.setRecordId( EscherContainerRecord.SP_CONTAINER ); + spContainer1.setOptions( (short) 0x000F ); + spgr.setRecordId( EscherSpgrRecord.RECORD_ID ); + spgr.setOptions( (short) 0x0001 ); // don't know what the 1 is for. + spgr.setRectX1( patriarch.getX1() ); + spgr.setRectY1( patriarch.getY1() ); + spgr.setRectX2( patriarch.getX2() ); + spgr.setRectY2( patriarch.getY2() ); + sp1.setRecordId( EscherSpRecord.RECORD_ID ); + sp1.setOptions( (short) 0x0002 ); + sp1.setShapeId( drawingManager.allocateShapeId(dg.getDrawingGroupId()) ); + sp1.setFlags( EscherSpRecord.FLAG_GROUP | EscherSpRecord.FLAG_PATRIARCH ); + + dgContainer.addChildRecord( dg ); + dgContainer.addChildRecord( spgrContainer ); + spgrContainer.addChildRecord( spContainer1 ); + spContainer1.addChildRecord( spgr ); + spContainer1.addChildRecord( sp1 ); + + addEscherRecord( dgContainer ); + } + + /** Retrieve the number of shapes (including the patriarch). */ +// private int getNumberOfShapes( HSSFPatriarch patriarch ) +// { +// return patriarch.countOfAllChildren(); +// } + + private static short sid( List records, int loc ) + { + return ( (Record) records.get( loc ) ).getSid(); + } + + +} diff --git a/src/java/org/apache/poi/hssf/record/GroupMarkerSubRecord.java b/src/java/org/apache/poi/hssf/record/GroupMarkerSubRecord.java new file mode 100644 index 0000000000..56209aefd2 --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/GroupMarkerSubRecord.java @@ -0,0 +1,176 @@ + +/* ==================================================================== + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2003 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Apache" and "Apache Software Foundation" and + * "Apache POI" must not be used to endorse or promote products + * derived from this software without prior written permission. For + * written permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * "Apache POI", nor may "Apache" appear in their name, without + * prior written permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + */ + + +package org.apache.poi.hssf.record; + + + +import org.apache.poi.util.*; + +/** + * The group marker record is used as a position holder for groups. + + * @author Glen Stampoultzis (glens at apache.org) + */ +public class GroupMarkerSubRecord + extends SubRecord +{ + public final static short sid = 0x06; + + private byte[] reserved = new byte[0]; // would really love to know what goes in here. + + public GroupMarkerSubRecord() + { + + } + + /** + * Constructs a group marker record and sets its fields appropriately. + * + * @param id id must be 0x00 or an exception + * will be throw upon validation + * @param size size the size of the data area of the record + * @param data data of the record (should not contain sid/len) + */ + + public GroupMarkerSubRecord(short id, short size, byte [] data) + { + super(id, size, data); + + } + + /** + * Constructs a group marker record and sets its fields appropriately. + * + * @param id id must be 0x00 or an exception + * will be throw upon validation + * @param size size the size of the data area of the record + * @param data data of the record (should not contain sid/len) + * @param offset of the record's data + */ + + public GroupMarkerSubRecord(short id, short size, byte [] data, int offset) + { + super(id, size, data, offset); + + } + + /** + * Checks the sid matches the expected side for this record + * + * @param id the expected sid. + */ + protected void validateSid(short id) + { + if (id != sid) + { + throw new RecordFormatException("Not a Group Marker record"); + } + } + + protected void fillFields(byte [] data, short size, int offset) + { +// int pos = 0; + reserved = new byte[size]; + System.arraycopy(data, offset, reserved, 0, size); + } + + public String toString() + { + StringBuffer buffer = new StringBuffer(); + + String nl = System.getProperty("line.separator"); + buffer.append("[ftGmo]" + nl); + buffer.append(" reserved = ").append(HexDump.toHex(reserved)).append(nl); + buffer.append("[/ftGmo]" + nl); + return buffer.toString(); + } + + public int serialize(int offset, byte[] data) + { + LittleEndian.putShort(data, 0 + offset, sid); + LittleEndian.putShort(data, 2 + offset, (short)(getRecordSize() - 4)); + System.arraycopy(reserved, 0, data, offset + 4, getRecordSize() - 4); + + return getRecordSize(); + } + + /** + * Size of record (exluding 4 byte header) + */ + public int getRecordSize() + { + return 4 + reserved.length; + } + + public short getSid() + { + return sid; + } + + public Object clone() { + GroupMarkerSubRecord rec = new GroupMarkerSubRecord(); + rec.reserved = new byte[reserved.length]; + for ( int i = 0; i < reserved.length; i++ ) + rec.reserved[i] = reserved[i]; + return rec; + } + + + +} // END OF CLASS + + diff --git a/src/java/org/apache/poi/hssf/record/HorizontalPageBreakRecord.java b/src/java/org/apache/poi/hssf/record/HorizontalPageBreakRecord.java new file mode 100644 index 0000000000..b7d1eb5121 --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/HorizontalPageBreakRecord.java @@ -0,0 +1,107 @@ +/* ==================================================================== + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2004 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Apache" and "Apache Software Foundation" and + * "Apache POI" must not be used to endorse or promote products + * derived from this software without prior written permission. For + * written permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * "Apache POI", nor may "Apache" appear in their name, without + * prior written permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + */ +package org.apache.poi.hssf.record; + +/** + * HorizontalPageBreak record that stores page breaks at rows + * <p> + * This class is just used so that SID compares work properly in the RecordFactory + * @see PageBreakRecord + * @author Danny Mui (dmui at apache dot org) + */ +public class HorizontalPageBreakRecord extends PageBreakRecord { + + public static final short sid = PageBreakRecord.HORIZONTAL_SID; + + /** + * + */ + public HorizontalPageBreakRecord() { + super(); + } + + /** + * @param sid + */ + public HorizontalPageBreakRecord(short sid) { + super(sid); + } + + /** + * @param id + * @param size + * @param data + */ + public HorizontalPageBreakRecord(short id, short size, byte[] data) { + super(id, size, data); + } + + /** + * @param id + * @param size + * @param data + * @param offset + */ + public HorizontalPageBreakRecord(short id, short size, byte[] data, int offset) { + super(id, size, data, offset); + } + + /* (non-Javadoc) + * @see org.apache.poi.hssf.record.Record#getSid() + */ + public short getSid() { + return sid; + } + +} diff --git a/src/java/org/apache/poi/hssf/record/ObjRecord.java b/src/java/org/apache/poi/hssf/record/ObjRecord.java new file mode 100644 index 0000000000..5939b487ab --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/ObjRecord.java @@ -0,0 +1,227 @@ +/* ==================================================================== + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2003 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Apache" and "Apache Software Foundation" and + * "Apache POI" must not be used to endorse or promote products + * derived from this software without prior written permission. For + * written permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * "Apache POI", nor may "Apache" appear in their name, without + * prior written permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + */ + + +package org.apache.poi.hssf.record; + + + +import org.apache.poi.util.*; + +import java.util.List; +import java.util.Iterator; +import java.util.ArrayList; + +/** + * The obj record is used to hold various graphic objects and controls. + * + * @author Glen Stampoultzis (glens at apache.org) + */ +public class ObjRecord + extends Record +{ + public final static short sid = 0x5D; + private List subrecords; + + //00000000 15 00 12 00 01 00 01 00 11 60 00 00 00 00 00 0D .........`...... + //00000010 26 01 00 00 00 00 00 00 00 00 &......... + + + public ObjRecord() + { + subrecords = new ArrayList(2); + } + + /** + * Constructs a OBJ record and sets its fields appropriately. + * + * @param id id must be 0x5D or an exception + * will be throw upon validation + * @param size size the size of the data area of the record + * @param data data of the record (should not contain sid/len) + */ + + public ObjRecord(short id, short size, byte [] data) + { + super(id, size, data); + } + + /** + * Constructs a obj record and sets its fields appropriately. + * + * @param id id must be 0x5D or an exception + * will be throw upon validation + * @param size size the size of the data area of the record + * @param data data of the record (should not contain sid/len) + * @param offset of the record's data + */ + public ObjRecord(short id, short size, byte[] data, int offset) + { + super(id, size, data, offset); + } + + /** + * Checks the sid matches the expected side for this record + * + * @param id the expected sid. + */ + protected void validateSid(short id) + { + if (id != sid) + { + throw new RecordFormatException("Not an OBJ record"); + } + } + + protected void fillFields(byte [] data, short size, int offset) + { + subrecords = new ArrayList(); + int pos = offset; + while (pos - offset < size) + { + short subRecordSid = LittleEndian.getShort(data, pos); + short subRecordSize = LittleEndian.getShort(data, pos + 2); + Record subRecord = SubRecord.createSubRecord(subRecordSid, subRecordSize, data, pos + 4); + subrecords.add(subRecord); + pos += 4 + subRecordSize; + } + + } + + public String toString() + { + StringBuffer buffer = new StringBuffer(); + + buffer.append("[OBJ]\n"); + for ( Iterator iterator = subrecords.iterator(); iterator.hasNext(); ) + { + Record record = (Record) iterator.next(); + buffer.append("SUBRECORD: " + record.toString()); + } + buffer.append("[/OBJ]\n"); + return buffer.toString(); + } + + public int serialize(int offset, byte[] data) + { + int pos = 0; + + LittleEndian.putShort(data, 0 + offset, sid); + LittleEndian.putShort(data, 2 + offset, (short)(getRecordSize() - 4)); + + pos = offset + 4; + for ( Iterator iterator = subrecords.iterator(); iterator.hasNext(); ) + { + Record record = (Record) iterator.next(); + pos += record.serialize(pos, data); + } + + return getRecordSize(); + } + + /** + * Size of record (excluding 4 byte header) + */ + public int getRecordSize() + { + int size = 0; + for ( Iterator iterator = subrecords.iterator(); iterator.hasNext(); ) + { + Record record = (Record) iterator.next(); + size += record.getRecordSize(); + } + return 4 + size; + } + + public short getSid() + { + return sid; + } + + public List getSubRecords() + { + return subrecords; + } + + public void clearSubRecords() + { + subrecords.clear(); + } + + public void addSubRecord(int index, Object element) + { + subrecords.add( index, element ); + } + + public boolean addSubRecord(Object o) + { + return subrecords.add( o ); + } + + public Object clone() + { + ObjRecord rec = new ObjRecord(); + rec.subrecords = new ArrayList(); + + for ( Iterator iterator = subrecords.iterator(); iterator.hasNext(); ) + subrecords.add(( (Record) iterator.next() ).clone()); + + return rec; + } + +} // END OF CLASS + + + + diff --git a/src/java/org/apache/poi/hssf/record/PageBreakRecord.java b/src/java/org/apache/poi/hssf/record/PageBreakRecord.java new file mode 100644 index 0000000000..6420e5e400 --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/PageBreakRecord.java @@ -0,0 +1,304 @@ +/* ==================================================================== + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2004 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Apache" and "Apache Software Foundation" and + * "Apache POI" must not be used to endorse or promote products + * derived from this software without prior written permission. For + * written permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * "Apache POI", nor may "Apache" appear in their name, without + * prior written permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + */ +package org.apache.poi.hssf.record; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.poi.util.LittleEndian; + +/** + * Record that contains the functionality page breaks (horizontal and vertical) + * <p> + * The other two classes just specifically set the SIDS for record creation + * @see HorizontalPageBreakRecord + * @see VerticalPageBreakREcord + * + * REFERENCE: Microsoft Excel SDK page 322 and 420 + * @author Danny Mui (dmui at apache dot org) + */ +public class PageBreakRecord extends Record { + public static final short HORIZONTAL_SID = (short)0x1B; + public static final short VERTICAL_SID = (short)0x1A; + public short sid; + private short numBreaks; + private List breaks; + private Map BreakMap; + + /** + * Since both records store 2byte integers (short), no point in + * differentiating it in the records. + * <p> + * The subs (rows or columns, don't seem to be able to set but excel sets + * them automatically) + */ + public class Break + { + + public short main; + public short subFrom; + public short subTo; + + public Break(short main, short subFrom, short subTo) + { + this.main = main; + this.subFrom = subFrom; + this.subTo = subTo; + } + } + + public PageBreakRecord() + { + + } + + /** + * + * @param sid + */ + public PageBreakRecord(short sid) { + super(); + this.sid = sid; + } + + public PageBreakRecord(short id, short size, byte data[]) + { + super(id, size, data); + this.sid = id; + } + + public PageBreakRecord(short id, short size, byte data[], int offset) + { + super(id, size, data, offset); + this.sid = id; + } + + protected void fillFields(byte data[], short size, int offset) + { + short loadedBreaks = LittleEndian.getShort(data, 0 + offset); + setNumBreaks(loadedBreaks); + int pos = 2; + for(int k = 0; k < loadedBreaks; k++) + { + addBreak((short)(LittleEndian.getShort(data, pos + offset) - 1), LittleEndian.getShort(data, pos + 2 + offset), LittleEndian.getShort(data, pos + 4 + offset)); + pos += 6; + } + + } + + public short getSid() + { + return sid; + } + + public int serialize(int offset, byte data[]) + { + int recordsize = getRecordSize(); + int pos = 6; + LittleEndian.putShort(data, offset + 0, getSid()); + LittleEndian.putShort(data, offset + 2, (short)(recordsize - 4)); + LittleEndian.putShort(data, offset + 4, getNumBreaks()); + for(Iterator iterator = getBreaksIterator(); iterator.hasNext();) + { + Break Break = (Break)iterator.next(); + LittleEndian.putShort(data, offset + pos, (short)(Break.main + 1)); + pos += 2; + LittleEndian.putShort(data, offset + pos, Break.subFrom); + pos += 2; + LittleEndian.putShort(data, offset + pos, Break.subTo); + pos += 2; + } + + return recordsize; + } + + protected void validateSid(short id) + { + if(id != HORIZONTAL_SID && id != VERTICAL_SID) + throw new RecordFormatException("NOT A HorizontalPageBreak or VerticalPageBreak RECORD!! " + id); + else + return; + } + + public short getNumBreaks() + { + return breaks != null ? (short)breaks.size() : numBreaks; + } + + public void setNumBreaks(short numBreaks) + { + this.numBreaks = numBreaks; + } + + public Iterator getBreaksIterator() + { + if(breaks == null) + return Collections.EMPTY_LIST.iterator(); + else + return breaks.iterator(); + } + + public String toString() + { + StringBuffer retval = new StringBuffer(); + + if (getSid() != HORIZONTAL_SID && getSid()!= VERTICAL_SID) + return "[INVALIDPAGEBREAK]\n .sid ="+getSid()+"[INVALIDPAGEBREAK]"; + + String label; + String mainLabel; + String subLabel; + + if (getSid() == HORIZONTAL_SID) { + label = "HORIZONTALPAGEBREAK"; + mainLabel = "row"; + subLabel = "col"; + } else { + label = "VERTICALPAGEBREAK"; + mainLabel = "column"; + subLabel = "row"; + } + + retval.append("["+label+"]").append("\n"); + retval.append(" .sid =").append(getSid()).append("\n"); + retval.append(" .numbreaks =").append(getNumBreaks()).append("\n"); + Iterator iterator = getBreaksIterator(); + for(int k = 0; k < getNumBreaks(); k++) + { + Break region = (Break)iterator.next(); + + retval.append(" .").append(mainLabel).append(" (zero-based) =").append(region.main).append("\n"); + retval.append(" .").append(subLabel).append("From =").append(region.subFrom).append("\n"); + retval.append(" .").append(subLabel).append("To =").append(region.subTo).append("\n"); + } + + retval.append("["+label+"]").append("\n"); + return retval.toString(); + } + + /** + * Adds the page break at the specified parameters + * @param main Depending on sid, will determine row or column to put page break (zero-based) + * @param subFrom No user-interface to set (defaults to minumum, 0) + * @param subTo No user-interface to set + */ + public void addBreak(short main, short subFrom, short subTo) + { + if(breaks == null) + { + breaks = new ArrayList(getNumBreaks() + 10); + BreakMap = new HashMap(); + } + Integer key = new Integer(main); + Break region = (Break)BreakMap.get(key); + if(region != null) + { + region.main = main; + region.subFrom = subFrom; + region.subTo = subTo; + } else + { + region = new Break(main, subFrom, subTo); + breaks.add(region); + } + BreakMap.put(key, region); + } + + /** + * Removes the break indicated by the parameter + * @param main (zero-based) + */ + public void removeBreak(short main) + { + Integer rowKey = new Integer(main); + Break region = (Break)BreakMap.get(rowKey); + breaks.remove(region); + BreakMap.remove(rowKey); + } + + public int getRecordSize() + { + return 6 + getNumBreaks() * 6; + } + + /** + * Retrieves the region at the row/column indicated + * @param main + * @return + */ + public Break getBreak(short main) + { + Integer rowKey = new Integer(main); + return (Break)BreakMap.get(rowKey); + } + + /* Clones the page break record + * @see java.lang.Object#clone() + */ + public Object clone() { + PageBreakRecord record = new PageBreakRecord(getSid()); + Iterator iterator = getBreaksIterator(); + while (iterator.hasNext()) { + Break original = (Break)iterator.next(); + record.addBreak(original.main, original.subFrom, original.subTo); + } + return record; + } + + +} diff --git a/src/java/org/apache/poi/hssf/record/RecordFactory.java b/src/java/org/apache/poi/hssf/record/RecordFactory.java index f12f903b06..f2b5b28f00 100644 --- a/src/java/org/apache/poi/hssf/record/RecordFactory.java +++ b/src/java/org/apache/poi/hssf/record/RecordFactory.java @@ -112,7 +112,10 @@ public class RecordFactory FormulaRecord.class, BoolErrRecord.class, ExternSheetRecord.class, NameRecord.class, LeftMarginRecord.class, RightMarginRecord.class, TopMarginRecord.class, BottomMarginRecord.class, - PaletteRecord.class, StringRecord.class, RecalcIdRecord.class, SharedFormulaRecord.class + PaletteRecord.class, StringRecord.class, RecalcIdRecord.class, SharedFormulaRecord.class, + DrawingRecord.class, DrawingGroupRecord.class, DrawingSelectionRecord.class, + ObjRecord.class, TextObjectRecord.class, + HorizontalPageBreakRecord.class, VerticalPageBreakRecord.class }; } else { records = new Class[] @@ -143,7 +146,10 @@ public class RecordFactory BoolErrRecord.class, ExternSheetRecord.class, NameRecord.class, LeftMarginRecord.class, RightMarginRecord.class, TopMarginRecord.class, BottomMarginRecord.class, - PaletteRecord.class, StringRecord.class, RecalcIdRecord.class, SharedFormulaRecord.class + PaletteRecord.class, StringRecord.class, RecalcIdRecord.class, SharedFormulaRecord.class, + DrawingRecord.class, DrawingGroupRecord.class, DrawingSelectionRecord.class, + ObjRecord.class, TextObjectRecord.class, + HorizontalPageBreakRecord.class, VerticalPageBreakRecord.class }; } diff --git a/src/java/org/apache/poi/hssf/record/SubRecord.java b/src/java/org/apache/poi/hssf/record/SubRecord.java new file mode 100644 index 0000000000..6b27eb3e03 --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/SubRecord.java @@ -0,0 +1,98 @@ +/* ==================================================================== + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2003 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Apache" and "Apache Software Foundation" and + * "Apache POI" must not be used to endorse or promote products + * derived from this software without prior written permission. For + * written permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * "Apache POI", nor may "Apache" appear in their name, without + * prior written permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + */ + +package org.apache.poi.hssf.record; + +/** + * Subrecords are part of the OBJ class. + */ +abstract public class SubRecord + extends Record +{ + public SubRecord() + { + } + + public SubRecord( short id, short size, byte[] data ) + { + super( id, size, data ); + } + + public SubRecord( short id, short size, byte[] data, int offset ) + { + super( id, size, data, offset ); + } + + public static Record createSubRecord( short subRecordSid, short size, byte[] data, int offset ) + { + Record r = null; + + switch ( subRecordSid ) + { + case CommonObjectDataSubRecord.sid: + r = new CommonObjectDataSubRecord( subRecordSid, size, data, offset ); + break; + case GroupMarkerSubRecord.sid: + r = new GroupMarkerSubRecord( subRecordSid, size, data, offset ); + break; + case EndSubRecord.sid: + r = new EndSubRecord( subRecordSid, size, data, offset ); + break; + default: + r = new UnknownRecord( subRecordSid, size, data, offset ); + } + + return r; + } +} diff --git a/src/java/org/apache/poi/hssf/record/TestCommonObjectDataSubRecord.java b/src/java/org/apache/poi/hssf/record/TestCommonObjectDataSubRecord.java new file mode 100644 index 0000000000..2ffe2c2ce2 --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/TestCommonObjectDataSubRecord.java @@ -0,0 +1,127 @@ + +/* ==================================================================== + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2003 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Apache" and "Apache Software Foundation" and + * "Apache POI" must not be used to endorse or promote products + * derived from this software without prior written permission. For + * written permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * "Apache POI", nor may "Apache" appear in their name, without + * prior written permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + */ + + +package org.apache.poi.hssf.record; + + +import junit.framework.TestCase; + +/** + * Tests the serialization and deserialization of the CommonObjectDataSubRecord + * class works correctly. Test data taken directly from a real + * Excel file. + * + + * @author Glen Stampoultzis (glens at apache.org) + */ +public class TestCommonObjectDataSubRecord + extends TestCase +{ + byte[] data = new byte[] { + (byte)0x12,(byte)0x00,(byte)0x01,(byte)0x00, + (byte)0x01,(byte)0x00,(byte)0x11,(byte)0x60, + (byte)0x00,(byte)0x00,(byte)0x00,(byte)0x00, + (byte)0x00,(byte)0x0D,(byte)0x26,(byte)0x01, + (byte)0x00,(byte)0x00, + }; + + public TestCommonObjectDataSubRecord(String name) + { + super(name); + } + + public void testLoad() + throws Exception + { + CommonObjectDataSubRecord record = new CommonObjectDataSubRecord((short)0x15, (short)data.length, data); + + + assertEquals( CommonObjectDataSubRecord.OBJECT_TYPE_LIST_BOX, record.getObjectType()); + assertEquals( (short)1, record.getObjectId()); + assertEquals( (short)1, record.getOption()); + assertEquals( true , record.isLocked() ); + assertEquals( false, record.isPrintable() ); + assertEquals( false, record.isAutofill() ); + assertEquals( false, record.isAutoline() ); + assertEquals( (int)24593, record.getReserved1()); + assertEquals( (int)218103808, record.getReserved2()); + assertEquals( (int)294, record.getReserved3()); + assertEquals( 22 , record.getRecordSize() ); + + record.validateSid((short)0x15); + } + + public void testStore() + { + CommonObjectDataSubRecord record = new CommonObjectDataSubRecord(); + + record.setObjectType( CommonObjectDataSubRecord.OBJECT_TYPE_LIST_BOX ); + record.setObjectId( (short) 1); + record.setOption( (short) 1); + record.setLocked( true ); + record.setPrintable( false ); + record.setAutofill( false ); + record.setAutoline( false ); + record.setReserved1( (int) 24593); + record.setReserved2( (int) 218103808); + record.setReserved3( (int) 294); + + byte [] recordBytes = record.serialize(); + assertEquals(recordBytes.length - 4, data.length); + for (int i = 0; i < data.length; i++) + assertEquals("At offset " + i, data[i], recordBytes[i+4]); + } +} diff --git a/src/java/org/apache/poi/hssf/record/TestDrawingGroupRecord.java b/src/java/org/apache/poi/hssf/record/TestDrawingGroupRecord.java new file mode 100644 index 0000000000..4c42eca886 --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/TestDrawingGroupRecord.java @@ -0,0 +1,36 @@ +package org.apache.poi.hssf.record; + +import junit.framework.TestCase; +import org.apache.poi.ddf.EscherContainerRecord; +import org.apache.poi.ddf.EscherSpRecord; +import org.apache.poi.util.HexDump; + +public class TestDrawingGroupRecord extends TestCase +{ + public void testGetRecordSize() + throws Exception + { + DrawingGroupRecord r = new DrawingGroupRecord(); + assertEquals(4, r.getRecordSize()); + + EscherSpRecord sp = new EscherSpRecord(); + sp.setRecordId(EscherSpRecord.RECORD_ID); + sp.setOptions((short) 0x1111); + sp.setFlags(-1); + sp.setShapeId(-1); + EscherContainerRecord dggContainer = new EscherContainerRecord(); + dggContainer.setOptions((short) 0x000F); + dggContainer.setRecordId((short) 0xF000); + dggContainer.addChildRecord(sp); + + r.addEscherRecord(dggContainer); + assertEquals(28, r.getRecordSize()); + + byte[] data = new byte[28]; + int size = r.serialize(0, data); + assertEquals("[EB, 00, 18, 00, 0F, 00, 00, F0, 10, 00, 00, 00, 11, 11, 0A, F0, 08, 00, 00, 00, FF, FF, FF, FF, FF, FF, FF, FF, ]", HexDump.toHex(data)); + assertEquals(28, size); + + assertEquals(24, dggContainer.getRecordSize()); + } +} diff --git a/src/java/org/apache/poi/hssf/record/TestEndSubRecord.java b/src/java/org/apache/poi/hssf/record/TestEndSubRecord.java new file mode 100644 index 0000000000..d338601948 --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/TestEndSubRecord.java @@ -0,0 +1,102 @@ + +/* ==================================================================== + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2003 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Apache" and "Apache Software Foundation" and + * "Apache POI" must not be used to endorse or promote products + * derived from this software without prior written permission. For + * written permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * "Apache POI", nor may "Apache" appear in their name, without + * prior written permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + */ + + +package org.apache.poi.hssf.record; + + +import junit.framework.TestCase; + +/** + * Tests the serialization and deserialization of the EndSubRecord + * class works correctly. Test data taken directly from a real + * Excel file. + * + * @author Glen Stampoultzis (glens at apache.org) + */ +public class TestEndSubRecord + extends TestCase +{ + byte[] data = new byte[] { + + }; + + public TestEndSubRecord(String name) + { + super(name); + } + + public void testLoad() + throws Exception + { + EndSubRecord record = new EndSubRecord((short)0x00, (short)data.length, data); + + + + assertEquals( 4, record.getRecordSize() ); + + record.validateSid((short)0x00); + } + + public void testStore() + { + EndSubRecord record = new EndSubRecord(); + + byte [] recordBytes = record.serialize(); + assertEquals(recordBytes.length - 4, data.length); + for (int i = 0; i < data.length; i++) + assertEquals("At offset " + i, data[i], recordBytes[i+4]); + } +} diff --git a/src/java/org/apache/poi/hssf/record/TestEscherAggregate.java b/src/java/org/apache/poi/hssf/record/TestEscherAggregate.java new file mode 100644 index 0000000000..f7acf2f0d5 --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/TestEscherAggregate.java @@ -0,0 +1,124 @@ +package org.apache.poi.hssf.record; + +import junit.framework.TestCase; +import org.apache.poi.util.HexRead; +import org.apache.poi.util.HexDump; +import org.apache.poi.ddf.EscherContainerRecord; +import org.apache.poi.ddf.EscherSpRecord; +import org.apache.poi.ddf.EscherClientDataRecord; +import org.apache.poi.ddf.EscherDggRecord; +import org.apache.poi.hssf.model.DrawingManager; +import org.apache.poi.hssf.model.Sheet; + +import java.util.ArrayList; +import java.util.List; + +/** + * Tests the EscherAggregate class. + * + * @author Glen Stampoultzis (glens at apache.org) + */ +public class TestEscherAggregate extends TestCase +{ + /** + * Tests that the create aggregate method correctly rejoins escher records together. + * + * @throws Exception + */ + public void testCreateAggregate() throws Exception + { + String msoDrawingRecord1 = + "0F 00 02 F0 20 01 00 00 10 00 08 F0 08 00 00 00 \n" + + "03 00 00 00 02 04 00 00 0F 00 03 F0 08 01 00 00 \n" + + "0F 00 04 F0 28 00 00 00 01 00 09 F0 10 00 00 00 \n" + + "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 \n" + + "02 00 0A F0 08 00 00 00 00 04 00 00 05 00 00 00 \n" + + "0F 00 04 F0 64 00 00 00 42 01 0A F0 08 00 00 00 \n" + + "01 04 00 00 00 0A 00 00 73 00 0B F0 2A 00 00 00 \n" + + "BF 00 08 00 08 00 44 01 04 00 00 00 7F 01 00 00 \n" + + "01 00 BF 01 00 00 11 00 C0 01 40 00 00 08 FF 01 \n" + + "10 00 10 00 BF 03 00 00 08 00 00 00 10 F0 12 00 \n" + + "00 00 00 00 01 00 54 00 05 00 45 00 01 00 88 03 \n" + + "05 00 94 00 00 00 11 F0 00 00 00 00"; + + String msoDrawingRecord2 = + "0F 00 04 F0 64 00 00 00 42 01 0A F0 08 00 00 00 " + + "02 04 00 00 80 0A 00 00 73 00 0B F0 2A 00 00 00 " + + "BF 00 08 00 08 00 44 01 04 00 00 00 7F 01 00 00 " + + "01 00 BF 01 00 00 11 00 C0 01 40 00 00 08 FF 01 " + + "10 00 10 00 BF 03 00 00 08 00 00 00 10 F0 12 00 " + + "00 00 00 00 01 00 8D 03 05 00 E4 00 03 00 4D 03 " + + "0B 00 0C 00 00 00 11 F0 00 00 00 00"; + + DrawingRecord d1 = new DrawingRecord(); + d1.setData( HexRead.readFromString( msoDrawingRecord1 ) ); + + ObjRecord r1 = new ObjRecord(); + + DrawingRecord d2 = new DrawingRecord(); + d2.setData( HexRead.readFromString( msoDrawingRecord2 ) ); + + ObjRecord r2 = new ObjRecord(); + + List records = new ArrayList(); + records.add( d1 ); + records.add( r1 ); + records.add( d2 ); + records.add( r2 ); + + DrawingManager drawingManager = new DrawingManager(new EscherDggRecord() ); + EscherAggregate aggregate = EscherAggregate.createAggregate( records, 0, drawingManager ); + + assertEquals( 1, aggregate.getEscherRecords().size() ); + assertEquals( (short) 0xF002, aggregate.getEscherRecord( 0 ).getRecordId() ); + assertEquals( 2, aggregate.getEscherRecord( 0 ).getChildRecords().size() ); + +// System.out.println( "aggregate = " + aggregate ); + } + + public void testSerialize() throws Exception + { + + EscherContainerRecord container1 = new EscherContainerRecord(); + EscherContainerRecord spContainer1 = new EscherContainerRecord(); + EscherContainerRecord spContainer2 = new EscherContainerRecord(); + EscherContainerRecord spContainer3 = new EscherContainerRecord(); + EscherSpRecord sp1 = new EscherSpRecord(); + EscherSpRecord sp2 = new EscherSpRecord(); + EscherSpRecord sp3 = new EscherSpRecord(); + EscherClientDataRecord d2 = new EscherClientDataRecord(); + EscherClientDataRecord d3 = new EscherClientDataRecord(); + + container1.setOptions( (short) 0x000F ); + spContainer1.setOptions( (short) 0x000F ); + spContainer1.setRecordId( EscherContainerRecord.SP_CONTAINER ); + spContainer2.setOptions( (short) 0x000F ); + spContainer2.setRecordId( EscherContainerRecord.SP_CONTAINER ); + spContainer3.setOptions( (short) 0x000F ); + spContainer3.setRecordId( EscherContainerRecord.SP_CONTAINER ); + d2.setRecordId( EscherClientDataRecord.RECORD_ID ); + d2.setRemainingData( new byte[0] ); + d3.setRecordId( EscherClientDataRecord.RECORD_ID ); + d3.setRemainingData( new byte[0] ); + container1.addChildRecord( spContainer1 ); + container1.addChildRecord( spContainer2 ); + container1.addChildRecord( spContainer3 ); + spContainer1.addChildRecord( sp1 ); + spContainer2.addChildRecord( sp2 ); + spContainer3.addChildRecord( sp3 ); + spContainer2.addChildRecord( d2 ); + spContainer3.addChildRecord( d3 ); + + EscherAggregate aggregate = new EscherAggregate(null); + aggregate.addEscherRecord( container1 ); + aggregate.assoicateShapeToObjRecord( d2, new ObjRecord() ); + aggregate.assoicateShapeToObjRecord( d3, new ObjRecord() ); + + byte[] data = new byte[112]; + int bytesWritten = aggregate.serialize( 0, data ); + assertEquals( 112, bytesWritten ); + assertEquals( "[EC, 00, 40, 00, 0F, 00, 00, 00, 58, 00, 00, 00, 0F, 00, 04, F0, 10, 00, 00, 00, 00, 00, 0A, F0, 08, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 0F, 00, 04, F0, 18, 00, 00, 00, 00, 00, 0A, F0, 08, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 11, F0, 00, 00, 00, 00, 5D, 00, 00, 00, EC, 00, 20, 00, 0F, 00, 04, F0, 18, 00, 00, 00, 00, 00, 0A, F0, 08, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 11, F0, 00, 00, 00, 00, 5D, 00, 00, 00, ]", + HexDump.toHex( data ) ); + } + +} diff --git a/src/java/org/apache/poi/hssf/record/TestTextObjectBaseRecord.java b/src/java/org/apache/poi/hssf/record/TestTextObjectBaseRecord.java new file mode 100644 index 0000000000..1e95653aef --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/TestTextObjectBaseRecord.java @@ -0,0 +1,137 @@ + +/* ==================================================================== + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2003 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Apache" and "Apache Software Foundation" and + * "Apache POI" must not be used to endorse or promote products + * derived from this software without prior written permission. For + * written permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * "Apache POI", nor may "Apache" appear in their name, without + * prior written permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + */ + + +package org.apache.poi.hssf.record; + + +import junit.framework.TestCase; + +/** + * Tests the serialization and deserialization of the TextObjectBaseRecord + * class works correctly. Test data taken directly from a real + * Excel file. + * + + * @author Glen Stampoultzis (glens at apache.org) + */ +public class TestTextObjectBaseRecord + extends TestCase +{ + byte[] data = new byte[] { + 0x44, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, + }; + + public TestTextObjectBaseRecord(String name) + { + super(name); + } + + public void testLoad() + throws Exception + { + TextObjectBaseRecord record = new TextObjectBaseRecord((short)0x1B6, (short)data.length, data); + + +// assertEquals( (short), record.getOptions()); + assertEquals( false, record.isReserved1() ); + assertEquals( TextObjectBaseRecord.HORIZONTAL_TEXT_ALIGNMENT_CENTERED, record.getHorizontalTextAlignment() ); + assertEquals( TextObjectBaseRecord.VERTICAL_TEXT_ALIGNMENT_JUSTIFY, record.getVerticalTextAlignment() ); + assertEquals( 0, record.getReserved2() ); + assertEquals( true, record.isTextLocked() ); + assertEquals( 0, record.getReserved3() ); + assertEquals( TextObjectBaseRecord.TEXT_ORIENTATION_ROT_RIGHT, record.getTextOrientation()); + assertEquals( 0, record.getReserved4()); + assertEquals( 0, record.getReserved5()); + assertEquals( 0, record.getReserved6()); + assertEquals( 2, record.getTextLength()); + assertEquals( 2, record.getFormattingRunLength()); + assertEquals( 0, record.getReserved7()); + + + assertEquals( 22, record.getRecordSize() ); + + record.validateSid((short)0x1B6); + } + + public void testStore() + { + TextObjectBaseRecord record = new TextObjectBaseRecord(); + + + +// record.setOptions( (short) 0x0000); + record.setReserved1( false ); + record.setHorizontalTextAlignment( TextObjectBaseRecord.HORIZONTAL_TEXT_ALIGNMENT_CENTERED ); + record.setVerticalTextAlignment( TextObjectBaseRecord.VERTICAL_TEXT_ALIGNMENT_JUSTIFY ); + record.setReserved2( (short)0 ); + record.setTextLocked( true ); + record.setReserved3( (short)0 ); + record.setTextOrientation( TextObjectBaseRecord.TEXT_ORIENTATION_ROT_RIGHT ); + record.setReserved4( (short)0 ); + record.setReserved5( (short)0 ); + record.setReserved6( (short)0 ); + record.setTextLength( (short)2 ); + record.setFormattingRunLength( (short)2 ); + record.setReserved7( 0 ); + + byte [] recordBytes = record.serialize(); + assertEquals(recordBytes.length - 4, data.length); + for (int i = 0; i < data.length; i++) + assertEquals("At offset " + i, data[i], recordBytes[i+4]); + } +} diff --git a/src/java/org/apache/poi/hssf/record/TextObjectBaseRecord.java b/src/java/org/apache/poi/hssf/record/TextObjectBaseRecord.java new file mode 100644 index 0000000000..21d83a0c87 --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/TextObjectBaseRecord.java @@ -0,0 +1,519 @@ + +/* ==================================================================== + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2003 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Apache" and "Apache Software Foundation" and + * "Apache POI" must not be used to endorse or promote products + * derived from this software without prior written permission. For + * written permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * "Apache POI", nor may "Apache" appear in their name, without + * prior written permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + */ + + +package org.apache.poi.hssf.record; + + + +import org.apache.poi.util.*; + +/** + * The TXO record is used to define the properties of a text box. It is followed + by two continue records unless there is no actual text. The first continue record contains + the text data and the next continue record contains the formatting runs. + * NOTE: This source is automatically generated please do not modify this file. Either subclass or + * remove the record in src/records/definitions. + + * @author Glen Stampoultzis (glens at apache.org) + */ +public class TextObjectBaseRecord + extends Record +{ + public final static short sid = 0x1B6; + private short field_1_options; + private BitField reserved1 = new BitField(0x1); + private BitField HorizontalTextAlignment = new BitField(0x000E); + public final static short HORIZONTAL_TEXT_ALIGNMENT_LEFT_ALIGNED = 1; + public final static short HORIZONTAL_TEXT_ALIGNMENT_CENTERED = 2; + public final static short HORIZONTAL_TEXT_ALIGNMENT_RIGHT_ALIGNED = 3; + public final static short HORIZONTAL_TEXT_ALIGNMENT_JUSTIFIED = 4; + private BitField VerticalTextAlignment = new BitField(0x0070); + public final static short VERTICAL_TEXT_ALIGNMENT_TOP = 1; + public final static short VERTICAL_TEXT_ALIGNMENT_CENTER = 2; + public final static short VERTICAL_TEXT_ALIGNMENT_BOTTOM = 3; + public final static short VERTICAL_TEXT_ALIGNMENT_JUSTIFY = 4; + private BitField reserved2 = new BitField(0x0180); + private BitField textLocked = new BitField(0x200); + private BitField reserved3 = new BitField(0xFC00); + private short field_2_textOrientation; + public final static short TEXT_ORIENTATION_NONE = 0; + public final static short TEXT_ORIENTATION_TOP_TO_BOTTOM = 1; + public final static short TEXT_ORIENTATION_ROT_RIGHT = 2; + public final static short TEXT_ORIENTATION_ROT_LEFT = 3; + private short field_3_reserved4; + private short field_4_reserved5; + private short field_5_reserved6; + private short field_6_textLength; + private short field_7_formattingRunLength; + private int field_8_reserved7; + + + public TextObjectBaseRecord() + { + + } + + /** + * Constructs a TextObjectBase record and sets its fields appropriately. + * + * @param id id must be 0x1B6 or an exception + * will be throw upon validation + * @param size size the size of the data area of the record + * @param data data of the record (should not contain sid/len) + */ + + public TextObjectBaseRecord(short id, short size, byte [] data) + { + super(id, size, data); + + } + + /** + * Constructs a TextObjectBase record and sets its fields appropriately. + * + * @param id id must be 0x1B6 or an exception + * will be throw upon validation + * @param size size the size of the data area of the record + * @param data data of the record (should not contain sid/len) + * @param offset of the record's data + */ + + public TextObjectBaseRecord(short id, short size, byte [] data, int offset) + { + super(id, size, data, offset); + + } + + /** + * Checks the sid matches the expected side for this record + * + * @param id the expected sid. + */ + protected void validateSid(short id) + { + if (id != sid) + { + throw new RecordFormatException("Not a TextObjectBase record"); + } + } + + protected void fillFields(byte [] data, short size, int offset) + { + + int pos = 0; + field_1_options = LittleEndian.getShort(data, pos + 0x0 + offset); + field_2_textOrientation = LittleEndian.getShort(data, pos + 0x2 + offset); + field_3_reserved4 = LittleEndian.getShort(data, pos + 0x4 + offset); + field_4_reserved5 = LittleEndian.getShort(data, pos + 0x6 + offset); + field_5_reserved6 = LittleEndian.getShort(data, pos + 0x8 + offset); + field_6_textLength = LittleEndian.getShort(data, pos + 0xa + offset); + field_7_formattingRunLength = LittleEndian.getShort(data, pos + 0xc + offset); + field_8_reserved7 = LittleEndian.getInt(data, pos + 0xe + offset); + + } + + public String toString() + { + StringBuffer buffer = new StringBuffer(); + + buffer.append("[TXO]\n"); + buffer.append(" .options = ") + .append("0x").append(HexDump.toHex( getOptions ())) + .append(" (").append( getOptions() ).append(" )"); + buffer.append(System.getProperty("line.separator")); + buffer.append(" .reserved1 = ").append(isReserved1()).append('\n'); + buffer.append(" .HorizontalTextAlignment = ").append(getHorizontalTextAlignment()).append('\n'); + buffer.append(" .VerticalTextAlignment = ").append(getVerticalTextAlignment()).append('\n'); + buffer.append(" .reserved2 = ").append(getReserved2()).append('\n'); + buffer.append(" .textLocked = ").append(isTextLocked()).append('\n'); + buffer.append(" .reserved3 = ").append(getReserved3()).append('\n'); + buffer.append(" .textOrientation = ") + .append("0x").append(HexDump.toHex( getTextOrientation ())) + .append(" (").append( getTextOrientation() ).append(" )"); + buffer.append(System.getProperty("line.separator")); + buffer.append(" .reserved4 = ") + .append("0x").append(HexDump.toHex( getReserved4 ())) + .append(" (").append( getReserved4() ).append(" )"); + buffer.append(System.getProperty("line.separator")); + buffer.append(" .reserved5 = ") + .append("0x").append(HexDump.toHex( getReserved5 ())) + .append(" (").append( getReserved5() ).append(" )"); + buffer.append(System.getProperty("line.separator")); + buffer.append(" .reserved6 = ") + .append("0x").append(HexDump.toHex( getReserved6 ())) + .append(" (").append( getReserved6() ).append(" )"); + buffer.append(System.getProperty("line.separator")); + buffer.append(" .textLength = ") + .append("0x").append(HexDump.toHex( getTextLength ())) + .append(" (").append( getTextLength() ).append(" )"); + buffer.append(System.getProperty("line.separator")); + buffer.append(" .formattingRunLength = ") + .append("0x").append(HexDump.toHex( getFormattingRunLength ())) + .append(" (").append( getFormattingRunLength() ).append(" )"); + buffer.append(System.getProperty("line.separator")); + buffer.append(" .reserved7 = ") + .append("0x").append(HexDump.toHex( getReserved7 ())) + .append(" (").append( getReserved7() ).append(" )"); + buffer.append(System.getProperty("line.separator")); + + buffer.append("[/TXO]\n"); + return buffer.toString(); + } + + public int serialize(int offset, byte[] data) + { + int pos = 0; + + LittleEndian.putShort(data, 0 + offset, sid); + LittleEndian.putShort(data, 2 + offset, (short)(getRecordSize() - 4)); + + LittleEndian.putShort(data, 4 + offset + pos, field_1_options); + LittleEndian.putShort(data, 6 + offset + pos, field_2_textOrientation); + LittleEndian.putShort(data, 8 + offset + pos, field_3_reserved4); + LittleEndian.putShort(data, 10 + offset + pos, field_4_reserved5); + LittleEndian.putShort(data, 12 + offset + pos, field_5_reserved6); + LittleEndian.putShort(data, 14 + offset + pos, field_6_textLength); + LittleEndian.putShort(data, 16 + offset + pos, field_7_formattingRunLength); + LittleEndian.putInt(data, 18 + offset + pos, field_8_reserved7); + + return getRecordSize(); + } + + /** + * Size of record (exluding 4 byte header) + */ + public int getRecordSize() + { + return 4 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 4; + } + + public short getSid() + { + return this.sid; + } + + public Object clone() { + TextObjectBaseRecord rec = new TextObjectBaseRecord(); + + rec.field_1_options = field_1_options; + rec.field_2_textOrientation = field_2_textOrientation; + rec.field_3_reserved4 = field_3_reserved4; + rec.field_4_reserved5 = field_4_reserved5; + rec.field_5_reserved6 = field_5_reserved6; + rec.field_6_textLength = field_6_textLength; + rec.field_7_formattingRunLength = field_7_formattingRunLength; + rec.field_8_reserved7 = field_8_reserved7; + return rec; + } + + + + + /** + * Get the options field for the TextObjectBase record. + */ + public short getOptions() + { + return field_1_options; + } + + /** + * Set the options field for the TextObjectBase record. + */ + public void setOptions(short field_1_options) + { + this.field_1_options = field_1_options; + } + + /** + * Get the text orientation field for the TextObjectBase record. + * + * @return One of + * TEXT_ORIENTATION_NONE + * TEXT_ORIENTATION_TOP_TO_BOTTOM + * TEXT_ORIENTATION_ROT_RIGHT + * TEXT_ORIENTATION_ROT_LEFT + */ + public short getTextOrientation() + { + return field_2_textOrientation; + } + + /** + * Set the text orientation field for the TextObjectBase record. + * + * @param field_2_textOrientation + * One of + * TEXT_ORIENTATION_NONE + * TEXT_ORIENTATION_TOP_TO_BOTTOM + * TEXT_ORIENTATION_ROT_RIGHT + * TEXT_ORIENTATION_ROT_LEFT + */ + public void setTextOrientation(short field_2_textOrientation) + { + this.field_2_textOrientation = field_2_textOrientation; + } + + /** + * Get the reserved4 field for the TextObjectBase record. + */ + public short getReserved4() + { + return field_3_reserved4; + } + + /** + * Set the reserved4 field for the TextObjectBase record. + */ + public void setReserved4(short field_3_reserved4) + { + this.field_3_reserved4 = field_3_reserved4; + } + + /** + * Get the reserved5 field for the TextObjectBase record. + */ + public short getReserved5() + { + return field_4_reserved5; + } + + /** + * Set the reserved5 field for the TextObjectBase record. + */ + public void setReserved5(short field_4_reserved5) + { + this.field_4_reserved5 = field_4_reserved5; + } + + /** + * Get the reserved6 field for the TextObjectBase record. + */ + public short getReserved6() + { + return field_5_reserved6; + } + + /** + * Set the reserved6 field for the TextObjectBase record. + */ + public void setReserved6(short field_5_reserved6) + { + this.field_5_reserved6 = field_5_reserved6; + } + + /** + * Get the text length field for the TextObjectBase record. + */ + public short getTextLength() + { + return field_6_textLength; + } + + /** + * Set the text length field for the TextObjectBase record. + */ + public void setTextLength(short field_6_textLength) + { + this.field_6_textLength = field_6_textLength; + } + + /** + * Get the formatting run length field for the TextObjectBase record. + */ + public short getFormattingRunLength() + { + return field_7_formattingRunLength; + } + + /** + * Set the formatting run length field for the TextObjectBase record. + */ + public void setFormattingRunLength(short field_7_formattingRunLength) + { + this.field_7_formattingRunLength = field_7_formattingRunLength; + } + + /** + * Get the reserved7 field for the TextObjectBase record. + */ + public int getReserved7() + { + return field_8_reserved7; + } + + /** + * Set the reserved7 field for the TextObjectBase record. + */ + public void setReserved7(int field_8_reserved7) + { + this.field_8_reserved7 = field_8_reserved7; + } + + /** + * Sets the reserved1 field value. + * reserved field + */ + public void setReserved1(boolean value) + { + field_1_options = reserved1.setShortBoolean(field_1_options, value); + } + + /** + * reserved field + * @return the reserved1 field value. + */ + public boolean isReserved1() + { + return reserved1.isSet(field_1_options); + } + + /** + * Sets the Horizontal text alignment field value. + * + */ + public void setHorizontalTextAlignment(short value) + { + field_1_options = HorizontalTextAlignment.setShortValue(field_1_options, value); + } + + /** + * + * @return the Horizontal text alignment field value. + */ + public short getHorizontalTextAlignment() + { + return HorizontalTextAlignment.getShortValue(field_1_options); + } + + /** + * Sets the Vertical text alignment field value. + * + */ + public void setVerticalTextAlignment(short value) + { + field_1_options = VerticalTextAlignment.setShortValue(field_1_options, value); + } + + /** + * + * @return the Vertical text alignment field value. + */ + public short getVerticalTextAlignment() + { + return VerticalTextAlignment.getShortValue(field_1_options); + } + + /** + * Sets the reserved2 field value. + * + */ + public void setReserved2(short value) + { + field_1_options = reserved2.setShortValue(field_1_options, value); + } + + /** + * + * @return the reserved2 field value. + */ + public short getReserved2() + { + return reserved2.getShortValue(field_1_options); + } + + /** + * Sets the text locked field value. + * Text has been locked + */ + public void setTextLocked(boolean value) + { + field_1_options = textLocked.setShortBoolean(field_1_options, value); + } + + /** + * Text has been locked + * @return the text locked field value. + */ + public boolean isTextLocked() + { + return textLocked.isSet(field_1_options); + } + + /** + * Sets the reserved3 field value. + * + */ + public void setReserved3(short value) + { + field_1_options = reserved3.setShortValue(field_1_options, value); + } + + /** + * + * @return the reserved3 field value. + */ + public short getReserved3() + { + return reserved3.getShortValue(field_1_options); + } + + +} // END OF CLASS + + + + diff --git a/src/java/org/apache/poi/hssf/record/TextObjectRecord.java b/src/java/org/apache/poi/hssf/record/TextObjectRecord.java new file mode 100644 index 0000000000..e5652e802c --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/TextObjectRecord.java @@ -0,0 +1,216 @@ +package org.apache.poi.hssf.record; + +import org.apache.poi.hssf.usermodel.HSSFRichTextString; +import org.apache.poi.util.StringUtil; +import org.apache.poi.util.LittleEndian; +import org.apache.poi.util.HexDump; +import java.io.UnsupportedEncodingException; + +public class TextObjectRecord + extends TextObjectBaseRecord +{ + HSSFRichTextString str = new HSSFRichTextString( "" ); + int continueRecordCount = 0; // how many times has continue record been called? + + public TextObjectRecord() + { + } + + public TextObjectRecord( short id, short size, byte[] data ) + { + super( id, size, data ); + } + + public TextObjectRecord( short id, short size, byte[] data, int offset ) + { + super( id, size, data, offset ); + } + + public int getRecordSize() + { + int continue1Size = 0; + int continue2Size = 0; + if (str.length() != 0) + { + continue1Size = str.length() * 2 + 1 + 4; + continue2Size = (str.numFormattingRuns() + 1) * 8 + 4; + } + return super.getRecordSize() + continue1Size + continue2Size; + } + + public int serialize( int offset, byte[] data ) + { + // Temporarily blank out str so that record size is calculated without the continue records. + HSSFRichTextString temp = str; + str = new HSSFRichTextString(""); + int bytesWritten1 = super.serialize( offset, data ); + str = temp; + + int pos = offset + bytesWritten1; + if ( str.toString().equals( "" ) == false ) + { + ContinueRecord c1 = createContinue1(); + ContinueRecord c2 = createContinue2(); + int bytesWritten2 = c1.serialize( pos, data ); + pos += bytesWritten2; + int bytesWritten3 = c2.serialize( pos, data ); + pos += bytesWritten3; + + int size = bytesWritten1 + bytesWritten2 + bytesWritten3; + if ( size != getRecordSize() ) + throw new RecordFormatException(size + " bytes written but getRecordSize() reports " + getRecordSize()); + return size; + } + if ( bytesWritten1 != getRecordSize() ) + throw new RecordFormatException(bytesWritten1 + " bytes written but getRecordSize() reports " + getRecordSize()); + return bytesWritten1; + } + + private ContinueRecord createContinue1() + { + ContinueRecord c1 = new ContinueRecord(); + byte[] c1Data = new byte[str.length() * 2 + 1]; + try + { + c1Data[0] = 1; + System.arraycopy( str.toString().getBytes( "UTF-16LE" ), 0, c1Data, 1, str.length() * 2 ); + } + catch ( UnsupportedEncodingException e ) + { + throw new RuntimeException( e.getMessage() ); + } + c1.setData( c1Data ); + return c1; + } + + private ContinueRecord createContinue2() + { + ContinueRecord c2 = new ContinueRecord(); + byte[] c2Data = new byte[str.numFormattingRuns() * 8 + 8]; + int pos = 0; + for ( int i = 0; i < str.numFormattingRuns(); i++ ) + { + LittleEndian.putShort( c2Data, pos, (short) str.getIndexOfFormattingRun( i ) ); + pos += 2; + LittleEndian.putShort( c2Data, pos, str.getFontOfFormattingRun( i ) == str.NO_FONT ? 0 : str.getFontOfFormattingRun( i ) ); + pos += 2; + pos += 4; // skip reserved + } + LittleEndian.putShort( c2Data, pos, (short) str.length() ); + pos += 2; + LittleEndian.putShort( c2Data, pos, (short) 0 ); + pos += 2; + pos += 4; // skip reserved + + c2.setData( c2Data ); + + return c2; + } + + public void processContinueRecord( byte[] data ) + { + if ( continueRecordCount == 0 ) + processRawString( data ); + else + processFontRuns( data ); + continueRecordCount++; + } + + private void processFontRuns( byte[] data ) + { + int pos = 0; + do + { + short index = LittleEndian.getShort( data, pos ); + pos += 2; + short iFont = LittleEndian.getShort( data, pos ); + pos += 2; + pos += 4; // skip reserved. + + if ( index >= str.length() ) + break; + str.applyFont( index, str.length(), iFont ); + } + while ( true ); + } + + private void processRawString( byte[] data ) + { + String s; + int pos = 0; + byte compressByte = data[pos++]; + boolean isCompressed = compressByte == 0; + try + { + if ( isCompressed ) + { + s = new String( data, pos, getTextLength(), StringUtil.getPreferredEncoding() ); + } + else + { + s = new String( data, pos, getTextLength() * 2, "UTF-16LE" ); + } + } + catch ( UnsupportedEncodingException e ) + { + throw new RuntimeException( e.getMessage() ); + } + str = new HSSFRichTextString( s ); + } + + public HSSFRichTextString getStr() + { + return str; + } + + public void setStr( HSSFRichTextString str ) + { + this.str = str; + } + + public String toString() + { + StringBuffer buffer = new StringBuffer(); + + buffer.append( "[TXO]\n" ); + buffer.append( " .options = " ) + .append( "0x" ).append( HexDump.toHex( getOptions() ) ) + .append( " (" ).append( getOptions() ).append( " )" ); + buffer.append( System.getProperty( "line.separator" ) ); + buffer.append( " .reserved1 = " ).append( isReserved1() ).append( '\n' ); + buffer.append( " .HorizontalTextAlignment = " ).append( getHorizontalTextAlignment() ).append( '\n' ); + buffer.append( " .VerticalTextAlignment = " ).append( getVerticalTextAlignment() ).append( '\n' ); + buffer.append( " .reserved2 = " ).append( getReserved2() ).append( '\n' ); + buffer.append( " .textLocked = " ).append( isTextLocked() ).append( '\n' ); + buffer.append( " .reserved3 = " ).append( getReserved3() ).append( '\n' ); + buffer.append( " .textOrientation = " ) + .append( "0x" ).append( HexDump.toHex( getTextOrientation() ) ) + .append( " (" ).append( getTextOrientation() ).append( " )" ); + buffer.append( System.getProperty( "line.separator" ) ); + buffer.append( " .reserved4 = " ) + .append( "0x" ).append( HexDump.toHex( getReserved4() ) ) + .append( " (" ).append( getReserved4() ).append( " )" ); + buffer.append( System.getProperty( "line.separator" ) ); + buffer.append( " .reserved5 = " ) + .append( "0x" ).append( HexDump.toHex( getReserved5() ) ) + .append( " (" ).append( getReserved5() ).append( " )" ); + buffer.append( System.getProperty( "line.separator" ) ); + buffer.append( " .reserved6 = " ) + .append( "0x" ).append( HexDump.toHex( getReserved6() ) ) + .append( " (" ).append( getReserved6() ).append( " )" ); + buffer.append( System.getProperty( "line.separator" ) ); + buffer.append( " .textLength = " ) + .append( "0x" ).append( HexDump.toHex( getTextLength() ) ) + .append( " (" ).append( getTextLength() ).append( " )" ); + buffer.append( System.getProperty( "line.separator" ) ); + buffer.append( " .reserved7 = " ) + .append( "0x" ).append( HexDump.toHex( getReserved7() ) ) + .append( " (" ).append( getReserved7() ).append( " )" ); + buffer.append( System.getProperty( "line.separator" ) ); + + buffer.append( " .string = " ).append(str).append('\n'); + + buffer.append( "[/TXO]\n" ); + return buffer.toString(); + } +} diff --git a/src/java/org/apache/poi/hssf/record/UnknownRecord.java b/src/java/org/apache/poi/hssf/record/UnknownRecord.java index 88ddd4ce05..ae6f6006b2 100644 --- a/src/java/org/apache/poi/hssf/record/UnknownRecord.java +++ b/src/java/org/apache/poi/hssf/record/UnknownRecord.java @@ -95,6 +95,13 @@ public class UnknownRecord this.thedata = data; } + public UnknownRecord( short id, short size, byte[] data, int offset ) + { + sid = id; + thedata = new byte[size]; + System.arraycopy(data, offset, thedata, 0, size); + } + /** * spit the record out AS IS. no interperatation or identification */ diff --git a/src/java/org/apache/poi/hssf/record/VerticalPageBreakRecord.java b/src/java/org/apache/poi/hssf/record/VerticalPageBreakRecord.java new file mode 100644 index 0000000000..10a9b7cd4b --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/VerticalPageBreakRecord.java @@ -0,0 +1,107 @@ +/* ==================================================================== + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2004 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Apache" and "Apache Software Foundation" and + * "Apache POI" must not be used to endorse or promote products + * derived from this software without prior written permission. For + * written permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * "Apache POI", nor may "Apache" appear in their name, without + * prior written permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + */ +package org.apache.poi.hssf.record; + +/** + * VerticalPageBreak record that stores page breaks at columns + * <p> + * This class is just used so that SID compares work properly in the RecordFactory + * @see PageBreakRecord + * @author Danny Mui (dmui at apache dot org) + */ +public class VerticalPageBreakRecord extends PageBreakRecord { + + public static final short sid = PageBreakRecord.VERTICAL_SID; + + /** + * + */ + public VerticalPageBreakRecord() { + super(); + } + + /** + * @param sid + */ + public VerticalPageBreakRecord(short sid) { + super(sid); + } + + /** + * @param id + * @param size + * @param data + */ + public VerticalPageBreakRecord(short id, short size, byte[] data) { + super(id, size, data); + } + + /** + * @param id + * @param size + * @param data + * @param offset + */ + public VerticalPageBreakRecord(short id, short size, byte[] data, int offset) { + super(id, size, data, offset); + } + + /* (non-Javadoc) + * @see org.apache.poi.hssf.record.Record#getSid() + */ + public short getSid() { + return PageBreakRecord.VERTICAL_SID; + } + +} diff --git a/src/java/org/apache/poi/hssf/usermodel/DummyGraphics2d.java b/src/java/org/apache/poi/hssf/usermodel/DummyGraphics2d.java new file mode 100644 index 0000000000..44b8e4d83b --- /dev/null +++ b/src/java/org/apache/poi/hssf/usermodel/DummyGraphics2d.java @@ -0,0 +1,773 @@ +package org.apache.poi.hssf.usermodel; + +import java.awt.*; +import java.awt.geom.AffineTransform; +import java.awt.image.BufferedImage; +import java.awt.image.BufferedImageOp; +import java.awt.image.ImageObserver; +import java.awt.image.RenderedImage; +import java.awt.image.renderable.RenderableImage; +import java.awt.font.GlyphVector; +import java.awt.font.FontRenderContext; +import java.util.Map; +import java.text.AttributedCharacterIterator; + +public class DummyGraphics2d + extends Graphics2D +{ + BufferedImage img; + private Graphics2D g2D; + + public DummyGraphics2d() + { + img = new BufferedImage(1000, 1000, 2); + g2D = (Graphics2D)img.getGraphics(); + } + + public void addRenderingHints(Map hints) + { + System.out.println( "addRenderingHinds(Map):" ); + System.out.println( " hints = " + hints ); + g2D.addRenderingHints( hints ); + } + + public void clip(Shape s) + { + System.out.println( "clip(Shape):" ); + System.out.println( " s = " + s ); + g2D.clip( s ); + } + + public void draw(Shape s) + { + System.out.println( "draw(Shape):" ); + System.out.println( "s = " + s ); + g2D.draw( s ); + } + + public void drawGlyphVector(GlyphVector g, float x, float y) + { + System.out.println( "drawGlyphVector(GlyphVector, float, float):" ); + System.out.println( "g = " + g ); + System.out.println( "x = " + x ); + System.out.println( "y = " + y ); + g2D.drawGlyphVector( g, x, y ); + } + + public void drawImage(BufferedImage img, + BufferedImageOp op, + int x, + int y) + { + System.out.println( "drawImage(BufferedImage, BufferedImageOp, x, y):" ); + System.out.println( "img = " + img ); + System.out.println( "op = " + op ); + System.out.println( "x = " + x ); + System.out.println( "y = " + y ); + g2D.drawImage( img, op, x, y ); + } + + public boolean drawImage(Image img, + AffineTransform xform, + ImageObserver obs) + { + System.out.println( "drawImage(Image,AfflineTransform,ImageObserver):" ); + System.out.println( "img = " + img ); + System.out.println( "xform = " + xform ); + System.out.println( "obs = " + obs ); + return g2D.drawImage( img, xform, obs ); + } + + public void drawRenderableImage(RenderableImage img, + AffineTransform xform) + { + System.out.println( "drawRenderableImage(RenderableImage, AfflineTransform):" ); + System.out.println( "img = " + img ); + System.out.println( "xform = " + xform ); + g2D.drawRenderableImage( img, xform ); + } + + public void drawRenderedImage(RenderedImage img, + AffineTransform xform) + { + System.out.println( "drawRenderedImage(RenderedImage, AffineTransform):" ); + System.out.println( "img = " + img ); + System.out.println( "xform = " + xform ); + g2D.drawRenderedImage( img, xform ); + } + + public void drawString(AttributedCharacterIterator iterator, + float x, float y) + { + System.out.println( "drawString(AttributedCharacterIterator):" ); + System.out.println( "iterator = " + iterator ); + System.out.println( "x = " + x ); + System.out.println( "y = " + y ); + g2D.drawString( iterator, x, y ); + } + +// public void drawString(AttributedCharacterIterator iterator, +// int x, int y) +// { +// g2D.drawString( iterator, x, y ); +// } + + public void drawString(String s, float x, float y) + { + System.out.println( "drawString(s,x,y):" ); + System.out.println( "s = " + s ); + System.out.println( "x = " + x ); + System.out.println( "y = " + y ); + g2D.drawString( s, x, y ); + } + +// public void drawString(String str, int x, int y) +// { +// g2D.drawString( str, x, y ); +// } + + public void fill(Shape s) + { + System.out.println( "fill(Shape):" ); + System.out.println( "s = " + s ); + g2D.fill( s ); + } + +// public void fill3DRect(int x, int y, int width, int height, +// boolean raised) { +// g2D.fill3DRect( x, y, width, height, raised ); +// } + + public Color getBackground() + { + System.out.println( "getBackground():" ); + return g2D.getBackground(); + } + + public Composite getComposite() + { + System.out.println( "getComposite():" ); + return g2D.getComposite(); + } + + public GraphicsConfiguration getDeviceConfiguration() + { + System.out.println( "getDeviceConfiguration():" ); + return g2D.getDeviceConfiguration(); + } + + public FontRenderContext getFontRenderContext() + { + System.out.println( "getFontRenderContext():" ); + return g2D.getFontRenderContext(); + } + + public Paint getPaint() + { + System.out.println( "getPaint():" ); + return g2D.getPaint(); + } + + public Object getRenderingHint(RenderingHints.Key hintKey) + { + System.out.println( "getRenderingHint(RenderingHints.Key):" ); + System.out.println( "hintKey = " + hintKey ); + return g2D.getRenderingHint( hintKey ); + } + + public RenderingHints getRenderingHints() + { + System.out.println( "getRenderingHints():" ); + return g2D.getRenderingHints(); + } + + public Stroke getStroke() + { + System.out.println( "getStroke():" ); + return g2D.getStroke(); + } + + public AffineTransform getTransform() + { + System.out.println( "getTransform():" ); + return g2D.getTransform(); + } + + public boolean hit(Rectangle rect, + Shape s, + boolean onStroke) + { + System.out.println( "hit(Rectangle, Shape, onStroke):" ); + System.out.println( "rect = " + rect ); + System.out.println( "s = " + s ); + System.out.println( "onStroke = " + onStroke ); + return g2D.hit( rect, s, onStroke ); + } + + public void rotate(double theta) + { + System.out.println( "rotate(theta):" ); + System.out.println( "theta = " + theta ); + g2D.rotate( theta ); + } + + public void rotate(double theta, double x, double y) + { + System.out.println( "rotate(double,double,double):" ); + System.out.println( "theta = " + theta ); + System.out.println( "x = " + x ); + System.out.println( "y = " + y ); + g2D.rotate( theta, x, y ); + } + + public void scale(double sx, double sy) + { + System.out.println( "scale(double,double):" ); + System.out.println( "sx = " + sx ); + System.out.println( "sy" ); + g2D.scale( sx, sy ); + } + + public void setBackground(Color color) + { + System.out.println( "setBackground(Color):" ); + System.out.println( "color = " + color ); + g2D.setBackground( color ); + } + + public void setComposite(Composite comp) + { + System.out.println( "setComposite(Composite):" ); + System.out.println( "comp = " + comp ); + g2D.setComposite( comp ); + } + + public void setPaint( Paint paint ) + { + System.out.println( "setPain(Paint):" ); + System.out.println( "paint = " + paint ); + g2D.setPaint( paint ); + } + + public void setRenderingHint(RenderingHints.Key hintKey, Object hintValue) + { + System.out.println( "setRenderingHint(RenderingHints.Key, Object):" ); + System.out.println( "hintKey = " + hintKey ); + System.out.println( "hintValue = " + hintValue ); + g2D.setRenderingHint( hintKey, hintValue ); + } + + public void setRenderingHints(Map hints) + { + System.out.println( "setRenderingHints(Map):" ); + System.out.println( "hints = " + hints ); + g2D.setRenderingHints( hints ); + } + + public void setStroke(Stroke s) + { + System.out.println( "setStroke(Stoke):" ); + System.out.println( "s = " + s ); + g2D.setStroke( s ); + } + + public void setTransform(AffineTransform Tx) + { + System.out.println( "setTransform():" ); + System.out.println( "Tx = " + Tx ); + g2D.setTransform( Tx ); + } + + public void shear(double shx, double shy) + { + System.out.println( "shear(shx, dhy):" ); + System.out.println( "shx = " + shx ); + System.out.println( "shy = " + shy ); + g2D.shear( shx, shy ); + } + + public void transform(AffineTransform Tx) + { + System.out.println( "transform(AffineTransform):" ); + System.out.println( "Tx = " + Tx ); + g2D.transform( Tx ); + } + + public void translate(double tx, double ty) + { + System.out.println( "translate(double, double):" ); + System.out.println( "tx = " + tx ); + System.out.println( "ty = " + ty ); + g2D.translate( tx, ty ); + } + +// public void translate(int x, int y) +// { +// g2D.translate( x, y ); +// } + + public void clearRect(int x, int y, int width, int height) + { + System.out.println( "clearRect(int,int,int,int):" ); + System.out.println( "x = " + x ); + System.out.println( "y = " + y ); + System.out.println( "width = " + width ); + System.out.println( "height = " + height ); + g2D.clearRect( x, y, width, height ); + } + + public void clipRect(int x, int y, int width, int height) + { + System.out.println( "clipRect(int, int, int, int):" ); + System.out.println( "x = " + x ); + System.out.println( "y = " + y ); + System.out.println( "width = " + width ); + System.out.println( "height = " + height ); + g2D.clipRect( x, y, width, height ); + } + + public void copyArea(int x, int y, int width, int height, + int dx, int dy) + { + System.out.println( "copyArea(int,int,int,int):" ); + System.out.println( "x = " + x ); + System.out.println( "y = " + y ); + System.out.println( "width = " + width ); + System.out.println( "height = " + height ); + g2D.copyArea( x, y, width, height, dx, dy ); + } + + public Graphics create() + { + System.out.println( "create():" ); + return g2D.create(); + } + + public Graphics create(int x, int y, int width, int height) { + System.out.println( "create(int,int,int,int):" ); + System.out.println( "x = " + x ); + System.out.println( "y = " + y ); + System.out.println( "width = " + width ); + System.out.println( "height = " + height ); + return g2D.create( x, y, width, height ); + } + + public void dispose() + { + System.out.println( "dispose():" ); + g2D.dispose(); + } + + public void draw3DRect(int x, int y, int width, int height, + boolean raised) { + System.out.println( "draw3DRect(int,int,int,int,boolean):" ); + System.out.println( "x = " + x ); + System.out.println( "y = " + y ); + System.out.println( "width = " + width ); + System.out.println( "height = " + height ); + System.out.println( "raised = " + raised ); + g2D.draw3DRect( x, y, width, height, raised ); + } + + public void drawArc(int x, int y, int width, int height, + int startAngle, int arcAngle) + { + System.out.println( "drawArc(int,int,int,int,int,int):" ); + System.out.println( "x = " + x ); + System.out.println( "y = " + y ); + System.out.println( "width = " + width ); + System.out.println( "height = " + height ); + System.out.println( "startAngle = " + startAngle ); + System.out.println( "arcAngle = " + arcAngle ); + g2D.drawArc( x, y, width, height, startAngle, arcAngle ); + } + + public void drawBytes(byte data[], int offset, int length, int x, int y) { + System.out.println( "drawBytes(byte[],int,int,int,int):" ); + System.out.println( "data = " + data ); + System.out.println( "offset = " + offset ); + System.out.println( "length = " + length ); + System.out.println( "x = " + x ); + System.out.println( "y = " + y ); + g2D.drawBytes( data, offset, length, x, y ); + } + + public void drawChars(char data[], int offset, int length, int x, int y) { + System.out.println( "drawChars(data,int,int,int,int):" ); + System.out.println( "data = " + data ); + System.out.println( "offset = " + offset ); + System.out.println( "length = " + length ); + System.out.println( "x = " + x ); + System.out.println( "y = " + y ); + g2D.drawChars( data, offset, length, x, y ); + } + + public boolean drawImage(Image img, + int dx1, int dy1, int dx2, int dy2, + int sx1, int sy1, int sx2, int sy2, + ImageObserver observer) + { + System.out.println( "drawImage(Image,int,int,int,int,int,int,int,int,ImageObserver):" ); + System.out.println( "img = " + img ); + System.out.println( "dx1 = " + dx1 ); + System.out.println( "dy1 = " + dy1 ); + System.out.println( "dx2 = " + dx2 ); + System.out.println( "dy2 = " + dy2 ); + System.out.println( "sx1 = " + sx1 ); + System.out.println( "sy1 = " + sy1 ); + System.out.println( "sx2 = " + sx2 ); + System.out.println( "sy2 = " + sy2 ); + System.out.println( "observer = " + observer ); + return g2D.drawImage( img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, observer ); + } + + public boolean drawImage(Image img, + int dx1, int dy1, int dx2, int dy2, + int sx1, int sy1, int sx2, int sy2, + Color bgcolor, + ImageObserver observer) + { + System.out.println( "drawImage(Image,int,int,int,int,int,int,int,int,Color,ImageObserver):" ); + System.out.println( "img = " + img ); + System.out.println( "dx1 = " + dx1 ); + System.out.println( "dy1 = " + dy1 ); + System.out.println( "dx2 = " + dx2 ); + System.out.println( "dy2 = " + dy2 ); + System.out.println( "sx1 = " + sx1 ); + System.out.println( "sy1 = " + sy1 ); + System.out.println( "sx2 = " + sx2 ); + System.out.println( "sy2 = " + sy2 ); + System.out.println( "bgcolor = " + bgcolor ); + System.out.println( "observer = " + observer ); + return g2D.drawImage( img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, bgcolor, observer ); + } + + public boolean drawImage(Image img, int x, int y, + Color bgcolor, + ImageObserver observer) + { + System.out.println( "drawImage(Image,int,int,Color,ImageObserver):" ); + System.out.println( "img = " + img ); + System.out.println( "x = " + x ); + System.out.println( "y = " + y ); + System.out.println( "bgcolor = " + bgcolor ); + System.out.println( "observer = " + observer ); + return g2D.drawImage( img, x, y, bgcolor, observer ); + } + + public boolean drawImage(Image img, int x, int y, + ImageObserver observer) + { + System.out.println( "drawImage(Image,int,int,observer):" ); + System.out.println( "img = " + img ); + System.out.println( "x = " + x ); + System.out.println( "y = " + y ); + System.out.println( "observer = " + observer ); + return g2D.drawImage( img, x, y, observer ); + } + + public boolean drawImage(Image img, int x, int y, + int width, int height, + Color bgcolor, + ImageObserver observer) + { + System.out.println( "drawImage(Image,int,int,int,int,Color,ImageObserver):" ); + System.out.println( "img = " + img ); + System.out.println( "x = " + x ); + System.out.println( "y = " + y ); + System.out.println( "width = " + width ); + System.out.println( "height = " + height ); + System.out.println( "bgcolor = " + bgcolor ); + System.out.println( "observer = " + observer ); + return g2D.drawImage( img, x, y, width, height, bgcolor, observer ); + } + + public boolean drawImage(Image img, int x, int y, + int width, int height, + ImageObserver observer) + { + System.out.println( "drawImage(Image,int,int,width,height,observer):" ); + System.out.println( "img = " + img ); + System.out.println( "x = " + x ); + System.out.println( "y = " + y ); + System.out.println( "width = " + width ); + System.out.println( "height = " + height ); + System.out.println( "observer = " + observer ); + return g2D.drawImage( img, x, y, width, height, observer ); + } + + public void drawLine(int x1, int y1, int x2, int y2) + { + System.out.println( "drawLine(int,int,int,int):" ); + System.out.println( "x1 = " + x1 ); + System.out.println( "y1 = " + y1 ); + System.out.println( "x2 = " + x2 ); + System.out.println( "y2 = " + y2 ); + g2D.drawLine( x1, y1, x2, y2 ); + } + + public void drawOval(int x, int y, int width, int height) + { + System.out.println( "drawOval(int,int,int,int):" ); + System.out.println( "x = " + x ); + System.out.println( "y = " + y ); + System.out.println( "width = " + width ); + System.out.println( "height = " + height ); + g2D.drawOval( x, y, width, height ); + } + + public void drawPolygon(Polygon p) { + System.out.println( "drawPolygon(Polygon):" ); + System.out.println( "p = " + p ); + g2D.drawPolygon( p ); + } + + public void drawPolygon(int xPoints[], int yPoints[], + int nPoints) + { + System.out.println( "drawPolygon(int[],int[],int):" ); + System.out.println( "xPoints = " + xPoints ); + System.out.println( "yPoints = " + yPoints ); + System.out.println( "nPoints = " + nPoints ); + g2D.drawPolygon( xPoints, yPoints, nPoints ); + } + + public void drawPolyline(int xPoints[], int yPoints[], + int nPoints) + { + System.out.println( "drawPolyline(int[],int[],int):" ); + System.out.println( "xPoints = " + xPoints ); + System.out.println( "yPoints = " + yPoints ); + System.out.println( "nPoints = " + nPoints ); + g2D.drawPolyline( xPoints, yPoints, nPoints ); + } + + public void drawRect(int x, int y, int width, int height) { + System.out.println( "drawRect(int,int,int,int):" ); + System.out.println( "x = " + x ); + System.out.println( "y = " + y ); + System.out.println( "width = " + width ); + System.out.println( "height = " + height ); + g2D.drawRect( x, y, width, height ); + } + + public void drawRoundRect(int x, int y, int width, int height, + int arcWidth, int arcHeight) + { + System.out.println( "drawRoundRect(int,int,int,int,int,int):" ); + System.out.println( "x = " + x ); + System.out.println( "y = " + y ); + System.out.println( "width = " + width ); + System.out.println( "height = " + height ); + System.out.println( "arcWidth = " + arcWidth ); + System.out.println( "arcHeight = " + arcHeight ); + g2D.drawRoundRect( x, y, width, height, arcWidth, arcHeight ); + } + + public void drawString(AttributedCharacterIterator iterator, + int x, int y) + { + System.out.println( "drawString(AttributedCharacterIterator,int,int):" ); + System.out.println( "iterator = " + iterator ); + System.out.println( "x = " + x ); + System.out.println( "y = " + y ); + g2D.drawString( iterator, x, y ); + } + + public void drawString(String str, int x, int y) + { + System.out.println( "drawString(str,int,int):" ); + System.out.println( "str = " + str ); + System.out.println( "x = " + x ); + System.out.println( "y = " + y ); + g2D.drawString( str, x, y ); + } + + public void fill3DRect(int x, int y, int width, int height, + boolean raised) { + System.out.println( "fill3DRect(int,int,int,int,boolean):" ); + System.out.println( "x = " + x ); + System.out.println( "y = " + y ); + System.out.println( "width = " + width ); + System.out.println( "height = " + height ); + System.out.println( "raised = " + raised ); + g2D.fill3DRect( x, y, width, height, raised ); + } + + public void fillArc(int x, int y, int width, int height, + int startAngle, int arcAngle) + { + System.out.println( "fillArc(int,int,int,int,int,int):" ); + System.out.println( "x = " + x ); + System.out.println( "y = " + y ); + System.out.println( "width = " + width ); + System.out.println( "height = " + height ); + System.out.println( "startAngle = " + startAngle ); + System.out.println( "arcAngle = " + arcAngle ); + g2D.fillArc( x, y, width, height, startAngle, arcAngle ); + } + + public void fillOval(int x, int y, int width, int height) + { + System.out.println( "fillOval(int,int,int,int):" ); + System.out.println( "x = " + x ); + System.out.println( "y = " + y ); + System.out.println( "width = " + width ); + System.out.println( "height = " + height ); + g2D.fillOval( x, y, width, height ); + } + + public void fillPolygon(Polygon p) { + System.out.println( "fillPolygon(Polygon):" ); + System.out.println( "p = " + p ); + g2D.fillPolygon( p ); + } + + public void fillPolygon(int xPoints[], int yPoints[], + int nPoints) + { + System.out.println( "fillPolygon(int[],int[],int):" ); + System.out.println( "xPoints = " + xPoints ); + System.out.println( "yPoints = " + yPoints ); + System.out.println( "nPoints = " + nPoints ); + g2D.fillPolygon( xPoints, yPoints, nPoints ); + } + + public void fillRect(int x, int y, int width, int height) + { + System.out.println( "fillRect(int,int,int,int):" ); + System.out.println( "x = " + x ); + System.out.println( "y = " + y ); + System.out.println( "width = " + width ); + System.out.println( "height = " + height ); + g2D.fillRect( x, y, width, height ); + } + + public void fillRoundRect(int x, int y, int width, int height, + int arcWidth, int arcHeight) + { + System.out.println( "fillRoundRect(int,int,int,int,int,int):" ); + System.out.println( "x = " + x ); + System.out.println( "y = " + y ); + System.out.println( "width = " + width ); + System.out.println( "height = " + height ); + g2D.fillRoundRect( x, y, width, height, arcWidth, arcHeight ); + } + + public void finalize() { + System.out.println( "finalize():" ); + g2D.finalize(); + } + + public Shape getClip() + { + System.out.println( "getClip():" ); + return g2D.getClip(); + } + + public Rectangle getClipBounds() + { + System.out.println( "getClipBounds():" ); + return g2D.getClipBounds(); + } + + public Rectangle getClipBounds(Rectangle r) { + System.out.println( "getClipBounds(Rectangle):" ); + System.out.println( "r = " + r ); + return g2D.getClipBounds( r ); + } + + public Rectangle getClipRect() { + System.out.println( "getClipRect():" ); + return g2D.getClipRect(); + } + + public Color getColor() + { + System.out.println( "getColor():" ); + return g2D.getColor(); + } + + public Font getFont() + { + System.out.println( "getFont():" ); + return g2D.getFont(); + } + + public FontMetrics getFontMetrics() { + System.out.println( "getFontMetrics():" ); + return g2D.getFontMetrics(); + } + + public FontMetrics getFontMetrics(Font f) + { + System.out.println( "getFontMetrics():" ); + return g2D.getFontMetrics( f ); + } + + public boolean hitClip(int x, int y, int width, int height) { + System.out.println( "hitClip(int,int,int,int):" ); + System.out.println( "x = " + x ); + System.out.println( "y = " + y ); + System.out.println( "width = " + width ); + System.out.println( "height = " + height ); + return g2D.hitClip( x, y, width, height ); + } + + public void setClip(Shape clip) + { + System.out.println( "setClip(Shape):" ); + System.out.println( "clip = " + clip ); + g2D.setClip( clip ); + } + + public void setClip(int x, int y, int width, int height) + { + System.out.println( "setClip(int,int,int,int):" ); + System.out.println( "x = " + x ); + System.out.println( "y = " + y ); + System.out.println( "width = " + width ); + System.out.println( "height = " + height ); + g2D.setClip( x, y, width, height ); + } + + public void setColor(Color c) + { + System.out.println( "setColor():" ); + System.out.println( "c = " + c ); + g2D.setColor( c ); + } + + public void setFont(Font font) + { + System.out.println( "setFont(Font):" ); + System.out.println( "font = " + font ); + g2D.setFont( font ); + } + + public void setPaintMode() + { + System.out.println( "setPaintMode():" ); + g2D.setPaintMode(); + } + + public void setXORMode(Color c1) + { + System.out.println( "setXORMode(Color):" ); + System.out.println( "c1 = " + c1 ); + g2D.setXORMode( c1 ); + } + + public String toString() { + System.out.println( "toString():" ); + return g2D.toString(); + } + + public void translate(int x, int y) + { + System.out.println( "translate(int,int):" ); + System.out.println( "x = " + x ); + System.out.println( "y = " + y ); + g2D.translate( x, y ); + } +} diff --git a/src/java/org/apache/poi/hssf/usermodel/EscherGraphics.java b/src/java/org/apache/poi/hssf/usermodel/EscherGraphics.java new file mode 100644 index 0000000000..fa8065a44c --- /dev/null +++ b/src/java/org/apache/poi/hssf/usermodel/EscherGraphics.java @@ -0,0 +1,450 @@ +package org.apache.poi.hssf.usermodel; + +import org.apache.poi.util.POILogFactory; +import org.apache.poi.util.POILogger; +import org.apache.poi.hssf.util.HSSFColor; + +import java.awt.*; +import java.awt.image.ImageObserver; +import java.text.AttributedCharacterIterator; + +/** + * Translates Graphics calls into escher calls. The translation is lossy so + * many features are not supported and some just aren't implemented yet. If + * in doubt test the specific calls you wish to make. Graphics calls are + * always performed into an EscherGroup so one will need to be created. + * <p> + * <b>Important:</b> + * <blockquote> + * One important concept worth considering is that of font size. One of the + * difficulties in converting Graphics calls into escher drawing calls is that + * Excel does not have the concept of absolute pixel positions. It measures + * it's cell widths in 'characters' and the cell heights in points. + * Unfortunately it's not defined exactly what a type of character it's + * measuring. Presumably this is due to the fact that the Excel will be + * using different fonts on different platforms or even within the same + * platform. + * <p> + * Because of this constraint we've had to calculate the + * verticalPointsPerPixel. This the amount the font should be scaled by when + * you issue commands such as drawString(). A good way to calculate this + * is to use the follow formula: + * <p> + * <pre> + * multipler = groupHeightInPoints / heightOfGroup + * </pre> + * <p> + * The height of the group is calculated fairly simply by calculating the + * difference between the y coordinates of the bounding box of the shape. The + * height of the group can be calculated by using a convenience called + * <code>HSSFClientAnchor.getAnchorHeightInPoints()</code>. + * </blockquote> + * + * @author Glen Stampoultzis (glens at apache.org) + */ +public class EscherGraphics + extends Graphics +{ + private HSSFShapeGroup escherGroup; + private HSSFWorkbook workbook; + private float verticalPointsPerPixel = 1.0f; + private float verticalPixelsPerPoint; + private Color foreground; + private Color background = Color.white; + private Font font; + private static POILogger logger = POILogFactory.getLogger(EscherGraphics.class); + + /** + * Construct an escher graphics object. + * + * @param escherGroup The escher group to write the graphics calls into. + * @param workbook The workbook we are using. + * @param forecolor The foreground color to use as default. + * @param verticalPointsPerPixel The font multiplier. (See class description for information on how this works.). + */ + public EscherGraphics(HSSFShapeGroup escherGroup, HSSFWorkbook workbook, Color forecolor, float verticalPointsPerPixel ) + { + this.escherGroup = escherGroup; + this.workbook = workbook; + this.verticalPointsPerPixel = verticalPointsPerPixel; + this.verticalPixelsPerPoint = 1 / verticalPointsPerPixel; + this.font = new Font("Arial", 0, 10); + this.foreground = forecolor; +// background = backcolor; + } + + /** + * Constructs an escher graphics object. + * + * @param escherGroup The escher group to write the graphics calls into. + * @param workbook The workbook we are using. + * @param foreground The foreground color to use as default. + * @param verticalPointsPerPixel The font multiplier. (See class description for information on how this works.). + * @param font The font to use. + */ + EscherGraphics( HSSFShapeGroup escherGroup, HSSFWorkbook workbook, Color foreground, Font font, float verticalPointsPerPixel ) + { + this.escherGroup = escherGroup; + this.workbook = workbook; + this.foreground = foreground; +// this.background = background; + this.font = font; + this.verticalPointsPerPixel = verticalPointsPerPixel; + this.verticalPixelsPerPoint = 1 / verticalPointsPerPixel; + } + + /** + * Constructs an escher graphics object. + * + * @param escherGroup The escher group to write the graphics calls into. + * @param workbook The workbook we are using. + * @param forecolor The default foreground color. + */ +// public EscherGraphics( HSSFShapeGroup escherGroup, HSSFWorkbook workbook, Color forecolor) +// { +// this(escherGroup, workbook, forecolor, 1.0f); +// } + + + public void clearRect(int x, int y, int width, int height) + { + Color color = foreground; + setColor(background); + fillRect(x,y,width,height); + setColor(color); + } + + public void clipRect(int x, int y, int width, int height) + { + logger.log(POILogger.WARN,"clipRect not supported"); + } + + public void copyArea(int x, int y, int width, int height, int dx, int dy) + { + logger.log(POILogger.WARN,"copyArea not supported"); + } + + public Graphics create() + { + EscherGraphics g = new EscherGraphics(escherGroup, workbook, + foreground, font, verticalPointsPerPixel ); + return g; + } + + public void dispose() + { + } + + public void drawArc(int x, int y, int width, int height, + int startAngle, int arcAngle) + { + logger.log(POILogger.WARN,"drawArc not supported"); + } + + public boolean drawImage(Image img, + int dx1, int dy1, int dx2, int dy2, + int sx1, int sy1, int sx2, int sy2, + Color bgcolor, + ImageObserver observer) + { + logger.log(POILogger.WARN,"drawImage not supported"); + + return true; + } + + public boolean drawImage(Image img, + int dx1, int dy1, int dx2, int dy2, + int sx1, int sy1, int sx2, int sy2, + ImageObserver observer) + { + logger.log(POILogger.WARN,"drawImage not supported"); + return true; + } + + public boolean drawImage(Image image, int i, int j, int k, int l, Color color, ImageObserver imageobserver) + { + return drawImage(image, i, j, i + k, j + l, 0, 0, image.getWidth(imageobserver), image.getHeight(imageobserver), color, imageobserver); + } + + public boolean drawImage(Image image, int i, int j, int k, int l, ImageObserver imageobserver) + { + return drawImage(image, i, j, i + k, j + l, 0, 0, image.getWidth(imageobserver), image.getHeight(imageobserver), imageobserver); + } + + public boolean drawImage(Image image, int i, int j, Color color, ImageObserver imageobserver) + { + return drawImage(image, i, j, image.getWidth(imageobserver), image.getHeight(imageobserver), color, imageobserver); + } + + public boolean drawImage(Image image, int i, int j, ImageObserver imageobserver) + { + return drawImage(image, i, j, image.getWidth(imageobserver), image.getHeight(imageobserver), imageobserver); + } + + public void drawLine(int x1, int y1, int x2, int y2) + { + HSSFSimpleShape shape = escherGroup.createShape(new HSSFChildAnchor(x1, y1, x2, y2) ); + shape.setShapeType(HSSFSimpleShape.OBJECT_TYPE_LINE); + shape.setLineWidth(0); + shape.setLineStyleColor(foreground.getRed(), foreground.getGreen(), foreground.getBlue()); + } + + public void drawOval(int x, int y, int width, int height) + { + HSSFSimpleShape shape = escherGroup.createShape(new HSSFChildAnchor(x,y,x+width,y+height) ); + shape.setShapeType(HSSFSimpleShape.OBJECT_TYPE_OVAL); + shape.setLineWidth(0); + shape.setLineStyleColor(foreground.getRed(), foreground.getGreen(), foreground.getBlue()); + shape.setNoFill(true); + } + + public void drawPolygon(int xPoints[], int yPoints[], + int nPoints) + { + int right = findBiggest(xPoints); + int bottom = findBiggest(yPoints); + int left = findSmallest(xPoints); + int top = findSmallest(yPoints); + HSSFPolygon shape = escherGroup.createPolygon(new HSSFChildAnchor(left,top,right,bottom) ); + shape.setPolygonDrawArea(right - left, bottom - top); + shape.setPoints(addToAll(xPoints, -left), addToAll(yPoints, -top)); + shape.setLineStyleColor(foreground.getRed(), foreground.getGreen(), foreground.getBlue()); + shape.setLineWidth(0); + shape.setNoFill(true); + } + + private int[] addToAll( int[] values, int amount ) + { + int[] result = new int[values.length]; + for ( int i = 0; i < values.length; i++ ) + result[i] = values[i] + amount; + return result; + } + + public void drawPolyline(int xPoints[], int yPoints[], + int nPoints) + { + logger.log(POILogger.WARN,"drawPolyline not supported"); + } + + public void drawRect(int x, int y, int width, int height) + { + logger.log(POILogger.WARN,"drawRect not supported"); + } + + public void drawRoundRect(int x, int y, int width, int height, + int arcWidth, int arcHeight) + { + logger.log(POILogger.WARN,"drawRoundRect not supported"); + } + + public void drawString(String str, int x, int y) + { + if (str == null || str.equals("")) + return; + + Font excelFont = font; + if ( font.getName().equals( "SansSerif" ) ) + { + excelFont = new Font( "Arial", font.getStyle(), (int) ( font.getSize() / verticalPixelsPerPoint ) ); + } + else + { + excelFont = new Font( font.getName(), font.getStyle(), (int) ( font.getSize() / verticalPixelsPerPoint )); + } + FontDetails d = StaticFontMetrics.getFontDetails( excelFont ); + int width = (int) ( (d.getStringWidth( str ) * 2.5) + 12 ); + int height = (int) ( ( font.getSize() * 2.0 * verticalPixelsPerPoint ) + 6 ); + y -= ( font.getSize() * verticalPixelsPerPoint ); // we want to draw the shape from the top-left + HSSFTextbox textbox = escherGroup.createTextbox( new HSSFChildAnchor( x, y, x + width, y + height ) ); + textbox.setNoFill( true ); + textbox.setLineStyle( HSSFShape.LINESTYLE_NONE ); + HSSFRichTextString s = new HSSFRichTextString( str ); + HSSFFont hssfFont = matchFont( excelFont ); + s.applyFont( hssfFont ); + textbox.setString( s ); + } + + private HSSFFont matchFont( Font font ) + { + HSSFColor hssfColor = workbook.getCustomPalette() + .findColor((byte)foreground.getRed(), (byte)foreground.getGreen(), (byte)foreground.getBlue()); + if (hssfColor == null) + hssfColor = workbook.getCustomPalette().findSimilarColor((byte)foreground.getRed(), (byte)foreground.getGreen(), (byte)foreground.getBlue()); + boolean bold = (font.getStyle() & Font.BOLD) != 0; + boolean italic = (font.getStyle() & Font.ITALIC) != 0; + HSSFFont hssfFont = workbook.findFont(bold ? HSSFFont.BOLDWEIGHT_BOLD : 0, + hssfColor.getIndex(), + (short)(font.getSize() * 20), + font.getName(), + italic, + false, + (short)0, + (byte)0); + if (hssfFont == null) + { + hssfFont = workbook.createFont(); + hssfFont.setBoldweight(bold ? HSSFFont.BOLDWEIGHT_BOLD : 0); + hssfFont.setColor(hssfColor.getIndex()); + hssfFont.setFontHeight((short)(font.getSize() * 20)); + hssfFont.setFontName(font.getName()); + hssfFont.setItalic(italic); + hssfFont.setStrikeout(false); + hssfFont.setTypeOffset((short) 0); + hssfFont.setUnderline((byte) 0); + } + + return hssfFont; + } + + + public void drawString(AttributedCharacterIterator iterator, + int x, int y) + { + logger.log(POILogger.WARN,"drawString not supported"); + } + + public void fillArc(int x, int y, int width, int height, + int startAngle, int arcAngle) + { + logger.log(POILogger.WARN,"fillArc not supported"); + } + + public void fillOval(int x, int y, int width, int height) + { + HSSFSimpleShape shape = escherGroup.createShape(new HSSFChildAnchor( x, y, x + width, y + height ) ); + shape.setShapeType(HSSFSimpleShape.OBJECT_TYPE_OVAL); + shape.setLineStyle(HSSFShape.LINESTYLE_NONE); + shape.setFillColor(foreground.getRed(), foreground.getGreen(), foreground.getBlue()); + shape.setLineStyleColor(foreground.getRed(), foreground.getGreen(), foreground.getBlue()); + } + + public void fillPolygon(int xPoints[], int yPoints[], + int nPoints) + { + int right = findBiggest(xPoints); + int bottom = findBiggest(yPoints); + int left = findSmallest(xPoints); + int top = findSmallest(yPoints); + HSSFPolygon shape = escherGroup.createPolygon(new HSSFChildAnchor(left,top,right,bottom) ); + shape.setPolygonDrawArea(right - left, bottom - top); + shape.setPoints(addToAll(xPoints, -left), addToAll(yPoints, -top)); + shape.setLineStyleColor(foreground.getRed(), foreground.getGreen(), foreground.getBlue()); + shape.setFillColor(foreground.getRed(), foreground.getGreen(), foreground.getBlue()); + } + + private int findBiggest( int[] values ) + { + int result = Integer.MIN_VALUE; + for ( int i = 0; i < values.length; i++ ) + { + if (values[i] > result) + result = values[i]; + } + return result; + } + + private int findSmallest( int[] values ) + { + int result = Integer.MAX_VALUE; + for ( int i = 0; i < values.length; i++ ) + { + if (values[i] < result) + result = values[i]; + } + return result; + } + + public void fillRect(int x, int y, int width, int height) + { + HSSFSimpleShape shape = escherGroup.createShape(new HSSFChildAnchor( x, y, x + width, y + height ) ); + shape.setShapeType(HSSFSimpleShape.OBJECT_TYPE_RECTANGLE); + shape.setLineStyle(HSSFShape.LINESTYLE_NONE); + shape.setFillColor(foreground.getRed(), foreground.getGreen(), foreground.getBlue()); + shape.setLineStyleColor(foreground.getRed(), foreground.getGreen(), foreground.getBlue()); + } + + public void fillRoundRect(int x, int y, int width, int height, + int arcWidth, int arcHeight) + { + logger.log(POILogger.WARN,"fillRoundRect not supported"); + } + + public Shape getClip() + { + return getClipBounds(); + } + + public Rectangle getClipBounds() + { + return null; + } + + public Rectangle getClipRect() + { + return getClipBounds(); + } + + public Color getColor() + { + return foreground; + } + + public Font getFont() + { + return font; + } + + public FontMetrics getFontMetrics(Font f) + { + return Toolkit.getDefaultToolkit().getFontMetrics(f); + } + + public void setClip(int x, int y, int width, int height) + { + setClip(((Shape) (new Rectangle(x,y,width,height)))); + } + + public void setClip(Shape shape) + { + // ignore... not implemented + } + + public void setColor(Color color) + { + foreground = color; + } + + public void setFont(Font f) + { + font = f; + } + + public void setPaintMode() + { + logger.log(POILogger.WARN,"setPaintMode not supported"); + } + + public void setXORMode(Color color) + { + logger.log(POILogger.WARN,"setXORMode not supported"); + } + + public void translate(int x, int y) + { + logger.log(POILogger.WARN,"translate not supported"); + } + + public Color getBackground() + { + return background; + } + + public void setBackground( Color background ) + { + this.background = background; + } + + +} + diff --git a/src/java/org/apache/poi/hssf/usermodel/EscherGraphics2d.java b/src/java/org/apache/poi/hssf/usermodel/EscherGraphics2d.java new file mode 100644 index 0000000000..f3298d79e6 --- /dev/null +++ b/src/java/org/apache/poi/hssf/usermodel/EscherGraphics2d.java @@ -0,0 +1,567 @@ +package org.apache.poi.hssf.usermodel; + +import org.apache.poi.util.POILogFactory; +import org.apache.poi.util.POILogger; + +import java.awt.*; +import java.awt.font.FontRenderContext; +import java.awt.font.GlyphVector; +import java.awt.font.TextLayout; +import java.awt.geom.AffineTransform; +import java.awt.geom.Area; +import java.awt.geom.GeneralPath; +import java.awt.image.BufferedImage; +import java.awt.image.BufferedImageOp; +import java.awt.image.ImageObserver; +import java.awt.image.RenderedImage; +import java.awt.image.renderable.RenderableImage; +import java.text.AttributedCharacterIterator; +import java.util.Map; + +/** + * Translates Graphics2d calls into escher calls. The translation is lossy so + * many features are not supported and some just aren't implemented yet. If + * in doubt test the specific calls you wish to make. Graphics calls are + * always drawn into an EscherGroup so one will need to be created. + * <p> + * <b>Important:</b> + * <blockquote> + * One important concept worth considering is that of font size. One of the + * difficulties in converting Graphics calls into escher drawing calls is that + * Excel does not have the concept of absolute pixel positions. It measures + * it's cell widths in 'characters' and the cell heights in points. + * Unfortunately it's not defined exactly what a type of character it's + * measuring. Presumably this is due to the fact that the Excel will be + * using different fonts on different platforms or even within the same + * platform. + * <p> + * Because of this constraint you have to calculate the verticalPointsPerPixel. + * This the amount the font should be scaled by when + * you issue commands such as drawString(). A good way to calculate this + * is to use the follow formula: + * <p> + * <pre> + * multipler = groupHeightInPoints / heightOfGroup + * </pre> + * <p> + * The height of the group is calculated fairly simply by calculating the + * difference between the y coordinates of the bounding box of the shape. The + * height of the group can be calculated by using a convenience called + * <code>HSSFClientAnchor.getAnchorHeightInPoints()</code>. + * </blockquote> + * + * @author Glen Stampoultzis (glens at apache.org) + */ +public class EscherGraphics2d extends Graphics2D +{ + private EscherGraphics escherGraphics; + private BufferedImage img; + private AffineTransform trans; + private Stroke stroke; + private Paint paint; + private Shape deviceclip; + private POILogger logger = POILogFactory.getLogger(getClass()); + + /** + * Constructs one escher graphics object from an escher graphics object. + * + * @param escherGraphics the original EscherGraphics2d object to copy + */ + public EscherGraphics2d(EscherGraphics escherGraphics) + { + this.escherGraphics = escherGraphics; + setImg( new BufferedImage(1, 1, 2) ); + setColor(Color.black); + } + + public void addRenderingHints(Map map) + { + getG2D().addRenderingHints(map); + } + + public void clearRect(int i, int j, int k, int l) + { + Paint paint1 = getPaint(); + setColor(getBackground()); + fillRect(i, j, k, l); + setPaint(paint1); + } + + public void clip(Shape shape) + { + if(getDeviceclip() != null) + { + Area area = new Area(getClip()); + if(shape != null) + area.intersect(new Area(shape)); + shape = area; + } + setClip(shape); + } + + public void clipRect(int x, int y, int width, int height) + { + clip(new Rectangle(x,y,width,height)); + } + + public void copyArea(int x, int y, int width, int height, + int dx, int dy) + { + getG2D().copyArea(x,y,width,height,dx,dy); + } + + public Graphics create() + { + EscherGraphics2d g2d = new EscherGraphics2d(escherGraphics); + return g2d; + } + + public void dispose() + { + getEscherGraphics().dispose(); + getG2D().dispose(); + getImg().flush(); + } + + public void draw(Shape shape) + { + logger.log(POILogger.WARN,"copyArea not supported"); + } + + public void drawArc(int x, int y, int width, int height, + int startAngle, int arcAngle) + { + draw(new java.awt.geom.Arc2D.Float(x, y, width, height, startAngle, arcAngle, 0)); + } + + public void drawGlyphVector(GlyphVector g, float x, float y) + { + fill(g.getOutline(x, y)); + } + + public boolean drawImage(Image image, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, + int sx2, int sy2, Color bgColor, ImageObserver imageobserver) + { + logger.log(POILogger.WARN,"drawImage() not supported"); + return true; + } + + public boolean drawImage(Image image, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, + int sx2, int sy2, ImageObserver imageobserver) + { + logger.log(POILogger.WARN,"drawImage() not supported"); + return drawImage(image, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, null, imageobserver); + } + public boolean drawImage(Image image, int dx1, int dy1, int dx2, int dy2, Color bgColor, ImageObserver imageobserver) + { + logger.log(POILogger.WARN,"drawImage() not supported"); + return true; + } + + public boolean drawImage(Image img, int x, int y, + int width, int height, + ImageObserver observer) + { + return drawImage(img, x,y,width,height, null, observer); + } + + public boolean drawImage(Image image, int x, int y, Color bgColor, ImageObserver imageobserver) + { + return drawImage(image, x, y, image.getWidth(imageobserver), image.getHeight(imageobserver), bgColor, imageobserver); + } + + public boolean drawImage(Image image, int x, int y, ImageObserver imageobserver) + { + return drawImage(image, x, y, image.getWidth(imageobserver), image.getHeight(imageobserver), imageobserver); + } + + public boolean drawImage(Image image, AffineTransform affinetransform, ImageObserver imageobserver) + { + AffineTransform affinetransform1 = (AffineTransform)getTrans().clone(); + getTrans().concatenate(affinetransform); + drawImage(image, 0, 0, imageobserver); + setTrans( affinetransform1 ); + return true; + } + + public void drawImage(BufferedImage bufferedimage, BufferedImageOp op, int x, int y) + { + BufferedImage img = op.filter(bufferedimage, null); + drawImage(((Image) (img)), new AffineTransform(1.0F, 0.0F, 0.0F, 1.0F, x, y), null); + } + + public void drawLine(int x1, int y1, int x2, int y2) + { + getEscherGraphics().drawLine(x1,y1,x2,y2); +// draw(new GeneralPath(new java.awt.geom.Line2D.Float(x1, y1, x2, y2))); + } + + public void drawOval(int x, int y, int width, int height) + { + getEscherGraphics().drawOval(x,y,width,height); +// draw(new java.awt.geom.Ellipse2D.Float(x, y, width, height)); + } + + public void drawPolygon(int xPoints[], int yPoints[], + int nPoints) + { + getEscherGraphics().drawPolygon(xPoints, yPoints, nPoints); + } + + public void drawPolyline(int xPoints[], int yPoints[], int nPoints) + { + if(nPoints > 0) + { + GeneralPath generalpath = new GeneralPath(); + generalpath.moveTo(xPoints[0], yPoints[0]); + for(int j = 1; j < nPoints; j++) + generalpath.lineTo(xPoints[j], yPoints[j]); + + draw(generalpath); + } + } + + public void drawRect(int x, int y, int width, int height) + { + escherGraphics.drawRect(x,y,width,height); + } + + public void drawRenderableImage(RenderableImage renderableimage, AffineTransform affinetransform) + { + drawRenderedImage(renderableimage.createDefaultRendering(), affinetransform); + } + + public void drawRenderedImage(RenderedImage renderedimage, AffineTransform affinetransform) + { + BufferedImage bufferedimage = new BufferedImage(renderedimage.getColorModel(), renderedimage.getData().createCompatibleWritableRaster(), false, null); + bufferedimage.setData(renderedimage.getData()); + drawImage(bufferedimage, affinetransform, null); + } + + public void drawRoundRect(int i, int j, int k, int l, int i1, int j1) + { + draw(new java.awt.geom.RoundRectangle2D.Float(i, j, k, l, i1, j1)); + } + + public void drawString(String string, float x, float y) + { + getEscherGraphics().drawString(string, (int)x, (int)y); + } + + public void drawString(String string, int x, int y) + { + getEscherGraphics().drawString(string, x, y); + } + + public void drawString(AttributedCharacterIterator attributedcharacteriterator, float x, float y) + { + TextLayout textlayout = new TextLayout(attributedcharacteriterator, getFontRenderContext()); + Paint paint1 = getPaint(); + setColor(getColor()); + fill(textlayout.getOutline(AffineTransform.getTranslateInstance(x, y))); + setPaint(paint1); + } + + public void drawString(AttributedCharacterIterator attributedcharacteriterator, int x, int y) + { + drawString(attributedcharacteriterator, x, y); + } + + public void fill(Shape shape) + { + logger.log(POILogger.WARN,"fill(Shape) not supported"); + } + + public void fillArc(int i, int j, int k, int l, int i1, int j1) + { + fill(new java.awt.geom.Arc2D.Float(i, j, k, l, i1, j1, 2)); + } + + public void fillOval(int x, int y, int width, int height) + { + escherGraphics.fillOval(x,y,width,height); + } + + /** + * Fills a closed polygon defined by + * arrays of <i>x</i> and <i>y</i> coordinates. + * <p> + * This method draws the polygon defined by <code>nPoint</code> line + * segments, where the first <code>nPoint - 1</code> + * line segments are line segments from + * <code>(xPoints[i - 1], yPoints[i - 1])</code> + * to <code>(xPoints[i], yPoints[i])</code>, for + * 1 ≤ <i>i</i> ≤ <code>nPoints</code>. + * The figure is automatically closed by drawing a line connecting + * the final point to the first point, if those points are different. + * <p> + * The area inside the polygon is defined using an + * even-odd fill rule, also known as the alternating rule. + * @param xPoints a an array of <code>x</code> coordinates. + * @param yPoints a an array of <code>y</code> coordinates. + * @param nPoints a the total number of points. + * @see java.awt.Graphics#drawPolygon(int[], int[], int) + */ + public void fillPolygon(int xPoints[], int yPoints[], int nPoints) + { + escherGraphics.fillPolygon(xPoints, yPoints, nPoints); + } + + public void fillRect(int x, int y, int width, int height) + { + getEscherGraphics().fillRect(x,y,width,height); + } + + public void fillRoundRect(int x, int y, int width, int height, + int arcWidth, int arcHeight) + { + fill(new java.awt.geom.RoundRectangle2D.Float(x, y, width, height, arcWidth, arcHeight)); + } + + public Color getBackground() + { + return getEscherGraphics().getBackground(); + } + + public Shape getClip() + { + try + { + return getTrans().createInverse().createTransformedShape(getDeviceclip()); + } + catch(Exception _ex) + { + return null; + } + } + + public Rectangle getClipBounds() + { + if(getDeviceclip() != null) + return getClip().getBounds(); + else + return null; + } + + public Color getColor() + { + return escherGraphics.getColor(); + } + + public Composite getComposite() + { + return getG2D().getComposite(); + } + + public GraphicsConfiguration getDeviceConfiguration() + { + return getG2D().getDeviceConfiguration(); + } + + public Font getFont() + { + return getEscherGraphics().getFont(); + } + + public FontMetrics getFontMetrics(Font font) + { + return getEscherGraphics().getFontMetrics(font); + } + + public FontRenderContext getFontRenderContext() + { + getG2D().setTransform(getTrans()); + return getG2D().getFontRenderContext(); + } + + public Paint getPaint() + { + return paint; + } + + public Object getRenderingHint(java.awt.RenderingHints.Key key) + { + return getG2D().getRenderingHint(key); + } + + public RenderingHints getRenderingHints() + { + return getG2D().getRenderingHints(); + } + + public Stroke getStroke() + { + return stroke; + } + + public AffineTransform getTransform() + { + return (AffineTransform)getTrans().clone(); + } + + public boolean hit(Rectangle rectangle, Shape shape, boolean flag) + { + getG2D().setTransform(getTrans()); + getG2D().setStroke(getStroke()); + getG2D().setClip(getClip()); + return getG2D().hit(rectangle, shape, flag); + } + + public void rotate(double d) + { + getTrans().rotate(d); + } + + public void rotate(double d, double d1, double d2) + { + getTrans().rotate(d, d1, d2); + } + + public void scale(double d, double d1) + { + getTrans().scale(d, d1); + } + + public void setBackground(Color c) + { + getEscherGraphics().setBackground(c); + } + + public void setClip(int i, int j, int k, int l) + { + setClip(((Shape) (new Rectangle(i, j, k, l)))); + } + + public void setClip(Shape shape) + { + setDeviceclip( getTrans().createTransformedShape(shape) ); + } + + public void setColor(Color c) + { + escherGraphics.setColor(c); + } + + public void setComposite(Composite composite) + { + getG2D().setComposite(composite); + } + + public void setFont(Font font) + { + getEscherGraphics().setFont(font); + } + + public void setPaint(Paint paint1) + { + if(paint1 != null) + { + paint = paint1; + if(paint1 instanceof Color) + setColor( (Color)paint1 ); + } + } + + public void setPaintMode() + { + getEscherGraphics().setPaintMode(); + } + + public void setRenderingHint(java.awt.RenderingHints.Key key, Object obj) + { + getG2D().setRenderingHint(key, obj); + } + + public void setRenderingHints(Map map) + { + getG2D().setRenderingHints(map); + } + + public void setStroke(Stroke s) + { + stroke = s; + } + + public void setTransform(AffineTransform affinetransform) + { + setTrans( (AffineTransform)affinetransform.clone() ); + } + + public void setXORMode(Color color1) + { + getEscherGraphics().setXORMode(color1); + } + + public void shear(double d, double d1) + { + getTrans().shear(d, d1); + } + + public void transform(AffineTransform affinetransform) + { + getTrans().concatenate(affinetransform); + } + +// Image transformImage(Image image, Rectangle rectangle, Rectangle rectangle1, ImageObserver imageobserver, Color color1) +// { +// logger.log(POILogger.WARN,"transformImage() not supported"); +// return null; +// } +// +// Image transformImage(Image image, int ai[], Rectangle rectangle, ImageObserver imageobserver, Color color1) +// { +// logger.log(POILogger.WARN,"transformImage() not supported"); +// return null; +// } + + public void translate(double d, double d1) + { + getTrans().translate(d, d1); + } + + public void translate(int i, int j) + { + getTrans().translate(i, j); + } + + private EscherGraphics getEscherGraphics() + { + return escherGraphics; + } + + private BufferedImage getImg() + { + return img; + } + + private void setImg( BufferedImage img ) + { + this.img = img; + } + + private Graphics2D getG2D() + { + return (Graphics2D) img.getGraphics(); + } + + private AffineTransform getTrans() + { + return trans; + } + + private void setTrans( AffineTransform trans ) + { + this.trans = trans; + } + + private Shape getDeviceclip() + { + return deviceclip; + } + + private void setDeviceclip( Shape deviceclip ) + { + this.deviceclip = deviceclip; + } + +} diff --git a/src/java/org/apache/poi/hssf/usermodel/FontDetails.java b/src/java/org/apache/poi/hssf/usermodel/FontDetails.java new file mode 100644 index 0000000000..96b0d87a86 --- /dev/null +++ b/src/java/org/apache/poi/hssf/usermodel/FontDetails.java @@ -0,0 +1,143 @@ +package org.apache.poi.hssf.usermodel; + +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import java.util.StringTokenizer; + +/** + * Stores width and height details about a font. + * + * @author Glen Stampoultzis (glens at apache.org) + */ +public class FontDetails +{ + private String fontName; + private int height; + private Map charWidths = new HashMap(); + + /** + * Construct the font details with the given name and height. + * + * @param fontName The font name. + * @param height The height of the font. + */ + public FontDetails( String fontName, int height ) + { + this.fontName = fontName; + this.height = height; + } + + public String getFontName() + { + return fontName; + } + + public int getHeight() + { + return height; + } + + public void addChar( char c, int width ) + { + charWidths.put(new Character(c), new Integer(width)); + } + + /** + * Retrieves the width of the specified character. If the metrics for + * a particular character are not available it defaults to returning the + * width for the 'W' character. + */ + public int getCharWidth( char c ) + { + Integer widthInteger = (Integer)(charWidths.get(new Character(c))); + if (widthInteger == null && c != 'W') + return getCharWidth('W'); + else + return widthInteger.intValue(); + } + + public void addChars( char[] characters, int[] widths ) + { + for ( int i = 0; i < characters.length; i++ ) + { + charWidths.put( new Character(characters[i]), new Integer(widths[i])); + } + } + + /** + * Create an instance of <code>FontDetails</code> by loading them from the + * provided property object. + * @param fontName the font name + * @param fontMetricsProps the property object holding the details of this + * particular font. + * @return a new FontDetails instance. + */ + public static FontDetails create( String fontName, Properties fontMetricsProps ) + { + String heightStr = fontMetricsProps.getProperty( "font." + fontName + ".height"); + String widthsStr = fontMetricsProps.getProperty( "font." + fontName + ".widths"); + String charactersStr = fontMetricsProps.getProperty( "font." + fontName + ".characters"); + int height = Integer.parseInt(heightStr); + FontDetails d = new FontDetails(fontName, height); + String[] charactersStrArray = split(charactersStr, ",", -1); + String[] widthsStrArray = split(widthsStr, ",", -1); + if (charactersStrArray.length != widthsStrArray.length) + throw new RuntimeException("Number of characters does not number of widths for font " + fontName); + for ( int i = 0; i < widthsStrArray.length; i++ ) + { + if (charactersStrArray[i].length() != 0) + d.addChar(charactersStrArray[i].charAt(0), Integer.parseInt(widthsStrArray[i])); + } + return d; + } + + /** + * Gets the width of all characters in a string. + * + * @param str The string to measure. + * @return The width of the string for a 10 point font. + */ + public int getStringWidth(String str) + { + int width = 0; + for (int i = 0; i < str.length(); i++) + { + width += getCharWidth(str.charAt(i)); + } + return width; + } + + /** + * Split the given string into an array of strings using the given + * delimiter. + */ + private static String[] split(String text, String separator, int max) + { + StringTokenizer tok = new StringTokenizer(text, separator); + int listSize = tok.countTokens(); + if(max != -1 && listSize > max) + listSize = max; + String list[] = new String[listSize]; + for(int i = 0; tok.hasMoreTokens(); i++) + { + if(max != -1 && i == listSize - 1) + { + StringBuffer buf = new StringBuffer((text.length() * (listSize - i)) / listSize); + while(tok.hasMoreTokens()) + { + buf.append(tok.nextToken()); + if(tok.hasMoreTokens()) + buf.append(separator); + } + list[i] = buf.toString().trim(); + break; + } + list[i] = tok.nextToken().trim(); + } + + return list; + } + + +} diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFAnchor.java b/src/java/org/apache/poi/hssf/usermodel/HSSFAnchor.java new file mode 100644 index 0000000000..b9db3c78eb --- /dev/null +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFAnchor.java @@ -0,0 +1,40 @@ +package org.apache.poi.hssf.usermodel; + + +/** + * An anchor is what specifics the position of a shape within a client object + * or within another containing shape. + * + * @author Glen Stampoultzis (glens at apache.org) + */ +public abstract class HSSFAnchor +{ + int dx1; + int dy1; + int dx2; + int dy2; + + public HSSFAnchor() + { + } + + public HSSFAnchor( int dx1, int dy1, int dx2, int dy2 ) + { + this.dx1 = dx1; + this.dy1 = dy1; + this.dx2 = dx2; + this.dy2 = dy2; + } + + public int getDx1(){ return dx1; } + public void setDx1( int dx1 ){ this.dx1 = dx1; } + public int getDy1(){ return dy1; } + public void setDy1( int dy1 ){ this.dy1 = dy1; } + public int getDy2(){ return dy2; } + public void setDy2( int dy2 ){ this.dy2 = dy2; } + public int getDx2(){ return dx2; } + public void setDx2( int dx2 ){ this.dx2 = dx2; } + + public abstract boolean isHorizontallyFlipped(); + public abstract boolean isVerticallyFlipped(); +} diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFChildAnchor.java b/src/java/org/apache/poi/hssf/usermodel/HSSFChildAnchor.java new file mode 100644 index 0000000000..fd69f48cde --- /dev/null +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFChildAnchor.java @@ -0,0 +1,37 @@ +package org.apache.poi.hssf.usermodel; + +import org.apache.poi.ddf.EscherRecord; +import org.apache.poi.ddf.EscherClientAnchorRecord; +import org.apache.poi.ddf.EscherChildAnchorRecord; + +public class HSSFChildAnchor + extends HSSFAnchor +{ + public HSSFChildAnchor() + { + } + + public HSSFChildAnchor( int dx1, int dy1, int dx2, int dy2 ) + { + super( dx1, dy1, dx2, dy2 ); + } + + public void setAnchor(int dx1, int dy1, int dx2, int dy2) + { + this.dx1 = dx1; + this.dy1 = dy1; + this.dx2 = dx2; + this.dy2 = dy2; + } + + public boolean isHorizontallyFlipped() + { + return dx1 > dx2; + } + + public boolean isVerticallyFlipped() + { + return dy1 > dy2; + } + +} diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFClientAnchor.java b/src/java/org/apache/poi/hssf/usermodel/HSSFClientAnchor.java new file mode 100644 index 0000000000..cbbaf167f2 --- /dev/null +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFClientAnchor.java @@ -0,0 +1,207 @@ +package org.apache.poi.hssf.usermodel; + +import org.apache.poi.ddf.EscherClientAnchorRecord; +import org.apache.poi.ddf.EscherRecord; + + +/** + * A client anchor is attached to an excel worksheet. It anchors against a + * top-left and buttom-right cell. + * + * @author Glen Stampoultzis (glens at apache.org) + */ +public class HSSFClientAnchor + extends HSSFAnchor +{ + short col1; + int row1; + short col2; + int row2; + + /** + * Creates a new client anchor and defaults all the anchor positions to 0. + */ + public HSSFClientAnchor() + { + } + + /** + * Creates a new client anchor and sets the top-left and bottom-right + * coordinates of the anchor. + * + * @param dx1 the x coordinate within the first cell. + * @param dy1 the y coordinate within the first cell. + * @param dx2 the x coordinate within the second cell. + * @param dy2 the y coordinate within the second cell. + * @param col1 the column (0 based) of the first cell. + * @param row1 the row (0 based) of the first cell. + * @param col2 the column (0 based) of the second cell. + * @param row2 the row (0 based) of the second cell. + */ + public HSSFClientAnchor( int dx1, int dy1, int dx2, int dy2, short col1, int row1, short col2, int row2 ) + { + super( dx1, dy1, dx2, dy2 ); + + checkRange(dx1, 0, 1023, "dx1"); + checkRange(dx2, 0, 1023, "dx2"); + checkRange(dy1, 0, 255, "dy1"); + checkRange(dy2, 0, 255, "dy2"); + checkRange(col1, 0, 255, "col1"); + checkRange(col2, 0, 255, "col2"); + checkRange(row1, 0, 255 * 256, "row1"); + checkRange(row2, 0, 255 * 256, "row2"); + + this.col1 = col1; + this.row1 = row1; + this.col2 = col2; + this.row2 = row2; + } + + /** + * Calculates the height of a client anchor in points. + * + * @param sheet the sheet the anchor will be attached to + * @return the shape height. + */ + public float getAnchorHeightInPoints(HSSFSheet sheet ) + { + int y1 = Math.min( getDy1(), getDy2() ); + int y2 = Math.max( getDy1(), getDy2() ); + int row1 = Math.min( getRow1(), getRow2() ); + int row2 = Math.max( getRow1(), getRow2() ); + + float points = 0; + if (row1 == row2) + { + points = ((y2 - y1) / 256.0f) * getRowHeightInPoints(sheet, row2); + } + else + { + points += ((256.0f - y1) / 256.0f) * getRowHeightInPoints(sheet, row1); + for (int i = row1 + 1; i < row2; i++) + { + points += getRowHeightInPoints(sheet, i); + } + points += (y2 / 256.0f) * getRowHeightInPoints(sheet, row2); + } + + return points; + } + + private float getRowHeightInPoints(HSSFSheet sheet, int rowNum) + { + HSSFRow row = sheet.getRow(rowNum); + if (row == null) + return sheet.getDefaultRowHeightInPoints(); + else + return row.getHeightInPoints(); + } + + public short getCol1() + { + return col1; + } + + public void setCol1( short col1 ) + { + checkRange(col1, 0, 255, "col1"); + this.col1 = col1; + } + + public short getCol2() + { + return col2; + } + + public void setCol2( short col2 ) + { + checkRange(col2, 0, 255, "col2"); + this.col2 = col2; + } + + public int getRow1() + { + return row1; + } + + public void setRow1( int row1 ) + { + checkRange(row1, 0, 256 * 256, "row1"); + this.row1 = row1; + } + + public int getRow2() + { + return row2; + } + + public void setRow2( int row2 ) + { + checkRange(row2, 0, 256 * 256, "row2"); + this.row2 = row2; + } + + /** + * Dets the top-left and bottom-right + * coordinates of the anchor. + * + * @param x1 the x coordinate within the first cell. + * @param y1 the y coordinate within the first cell. + * @param x2 the x coordinate within the second cell. + * @param y2 the y coordinate within the second cell. + * @param col1 the column (0 based) of the first cell. + * @param row1 the row (0 based) of the first cell. + * @param col2 the column (0 based) of the second cell. + * @param row2 the row (0 based) of the second cell. + */ + public void setAnchor( short col1, int row1, int x1, int y1, short col2, int row2, int x2, int y2 ) + { + checkRange(dx1, 0, 1023, "dx1"); + checkRange(dx2, 0, 1023, "dx2"); + checkRange(dy1, 0, 255, "dy1"); + checkRange(dy2, 0, 255, "dy2"); + checkRange(col1, 0, 255, "col1"); + checkRange(col2, 0, 255, "col2"); + checkRange(row1, 0, 255 * 256, "row1"); + checkRange(row2, 0, 255 * 256, "row2"); + + this.col1 = col1; + this.row1 = row1; + this.dx1 = x1; + this.dy1 = y1; + this.col2 = col2; + this.row2 = row2; + this.dx2 = x2; + this.dy2 = y2; + } + + /** + * @return true if the anchor goes from right to left. + */ + public boolean isHorizontallyFlipped() + { + if (col1 == col2) + return dx1 > dx2; + else + return col1 > col2; + } + + /** + * @return true if the anchor goes from bottom to top. + */ + public boolean isVerticallyFlipped() + { + if (row1 == row2) + return dy1 > dy2; + else + return row1 > row2; + } + + private void checkRange( int value, int minRange, int maxRange, String varName ) + { + if (value < minRange || value > maxRange) + throw new IllegalArgumentException(varName + " must be between " + minRange + " and " + maxRange); + } + + +} diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFFont.java b/src/java/org/apache/poi/hssf/usermodel/HSSFFont.java index 016c0d42f1..1bc777fe8c 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFFont.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFFont.java @@ -386,4 +386,12 @@ public class HSSFFont { return font.getUnderline(); } + + public String toString() + { + return "org.apache.poi.hssf.usermodel.HSSFFont{" + + font + + "}"; + } + } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFPalette.java b/src/java/org/apache/poi/hssf/usermodel/HSSFPalette.java index 863533d455..d1eb55a08d 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFPalette.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFPalette.java @@ -110,7 +110,60 @@ public class HSSFPalette } return null; } - + + /** + * Finds the closest matching color in the custom palette. The + * method for finding the distance between the colors is fairly + * primative. + * + * @param red The red component of the color to match. + * @param green The green component of the color to match. + * @param blue The blue component of the color to match. + * @return The closest color or null if there are no custom + * colors currently defined. + */ + public HSSFColor findSimilarColor(byte red, byte green, byte blue) + { + HSSFColor result = null; + int minColorDistance = Integer.MAX_VALUE; + byte[] b = palette.getColor(PaletteRecord.FIRST_COLOR_INDEX); + for (short i = (short) PaletteRecord.FIRST_COLOR_INDEX; b != null; + b = palette.getColor(++i)) + { + int colorDistance = red - b[0] + green - b[1] + blue - b[2]; + if (colorDistance < minColorDistance) + { + result = getColor(i); + } + } + return result; + } + + /** + * Adds a new color into an empty color slot. + * @param red The red component + * @param green The green component + * @param blue The blue component + * + * @return The new custom color. + * + * @throws RuntimeException if there are more more free color indexes. + */ + public HSSFColor addColor( byte red, byte green, byte blue ) + { + byte[] b = palette.getColor(PaletteRecord.FIRST_COLOR_INDEX); + short i; + for (i = (short) PaletteRecord.FIRST_COLOR_INDEX; i < PaletteRecord.STANDARD_PALETTE_SIZE + PaletteRecord.FIRST_COLOR_INDEX; b = palette.getColor(++i)) + { + if (b == null) + { + setColorAtIndex( i, red, green, blue ); + return getColor(i); + } + } + throw new RuntimeException("Could not find free color index"); + } + /** * Sets the color at the given offset * diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFPatriarch.java b/src/java/org/apache/poi/hssf/usermodel/HSSFPatriarch.java new file mode 100644 index 0000000000..d0a376c0ec --- /dev/null +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFPatriarch.java @@ -0,0 +1,159 @@ +package org.apache.poi.hssf.usermodel; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * The patriarch is the toplevel container for shapes in a sheet. It does + * little other than act as a container for other shapes and groups. + * + * @author Glen Stampoultzis (glens at apache.org) + */ +public class HSSFPatriarch + implements HSSFShapeContainer +{ + List shapes = new ArrayList(); + HSSFSheet sheet; + int x1 = 0; + int y1 = 0 ; + int x2 = 1023; + int y2 = 255; + + /** + * Creates the patriarch. + * + * @param sheet the sheet this patriarch is stored in. + */ + HSSFPatriarch(HSSFSheet sheet) + { + this.sheet = sheet; + } + + /** + * Creates a new group record stored under this patriarch. + * + * @param anchor the client anchor describes how this group is attached + * to the sheet. + * @return the newly created group. + */ + public HSSFShapeGroup createGroup(HSSFClientAnchor anchor) + { + HSSFShapeGroup group = new HSSFShapeGroup(null, anchor); + group.anchor = anchor; + shapes.add(group); + return group; + } + + /** + * Creates a simple shape. This includes such shapes as lines, rectangles, + * and ovals. + * + * @param anchor the client anchor describes how this group is attached + * to the sheet. + * @return the newly created shape. + */ + public HSSFSimpleShape createSimpleShape(HSSFClientAnchor anchor) + { + HSSFSimpleShape shape = new HSSFSimpleShape(null, anchor); + shape.anchor = anchor; + shapes.add(shape); + return shape; + } + + /** + * Creates a polygon + * + * @param anchor the client anchor describes how this group is attached + * to the sheet. + * @return the newly created shape. + */ + public HSSFPolygon createPolygon(HSSFClientAnchor anchor) + { + HSSFPolygon shape = new HSSFPolygon(null, anchor); + shape.anchor = anchor; + shapes.add(shape); + return shape; + } + + /** + * Constructs a textbox under the patriarch. + * + * @param anchor the client anchor describes how this group is attached + * to the sheet. + * @return the newly created textbox. + */ + public HSSFTextbox createTextbox(HSSFClientAnchor anchor) + { + HSSFTextbox shape = new HSSFTextbox(null, anchor); + shape.anchor = anchor; + shapes.add(shape); + return shape; + } + + /** + * Returns a list of all shapes contained by the patriarch. + */ + public List getChildren() + { + return shapes; + } + + /** + * Total count of all children and their children's children. + */ + public int countOfAllChildren() + { + int count = shapes.size(); + for ( Iterator iterator = shapes.iterator(); iterator.hasNext(); ) + { + HSSFShape shape = (HSSFShape) iterator.next(); + count += shape.countOfAllChildren(); + } + return count; + } + /** + * Sets the coordinate space of this group. All children are contrained + * to these coordinates. + */ + public void setCoordinates( int x1, int y1, int x2, int y2 ) + { + this.x1 = x1; + this.y1 = y1; + this.x2 = x2; + this.y2 = y2; + } + + /** + * The top left x coordinate of this group. + */ + public int getX1() + { + return x1; + } + + /** + * The top left y coordinate of this group. + */ + public int getY1() + { + return y1; + } + + /** + * The bottom right x coordinate of this group. + */ + public int getX2() + { + return x2; + } + + /** + * The bottom right y coordinate of this group. + */ + public int getY2() + { + return y2; + } + +} diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFPolygon.java b/src/java/org/apache/poi/hssf/usermodel/HSSFPolygon.java new file mode 100644 index 0000000000..67f4532658 --- /dev/null +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFPolygon.java @@ -0,0 +1,66 @@ +package org.apache.poi.hssf.usermodel; + +/** + * @author Glen Stampoultzis (glens at superlinksoftware.com) + */ +public class HSSFPolygon + extends HSSFShape +{ + int[] xPoints; + int[] yPoints; + int drawAreaWidth = 100; + int drawAreaHeight = 100; + + HSSFPolygon( HSSFShape parent, HSSFAnchor anchor ) + { + super( parent, anchor ); + } + + public int[] getXPoints() + { + return xPoints; + } + + public int[] getYPoints() + { + return yPoints; + } + + public void setPoints(int[] xPoints, int[] yPoints) + { + this.xPoints = cloneArray(xPoints); + this.yPoints = cloneArray(yPoints); + } + + private int[] cloneArray( int[] a ) + { + int[] result = new int[a.length]; + for ( int i = 0; i < a.length; i++ ) + result[i] = a[i]; + + return result; + } + + /** + * Defines the width and height of the points in the polygon + * @param width + * @param height + */ + public void setPolygonDrawArea( int width, int height ) + { + this.drawAreaWidth = width; + this.drawAreaHeight = height; + } + + public int getDrawAreaWidth() + { + return drawAreaWidth; + } + + public int getDrawAreaHeight() + { + return drawAreaHeight; + } + + +} diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFRichTextString.java b/src/java/org/apache/poi/hssf/usermodel/HSSFRichTextString.java new file mode 100644 index 0000000000..59edf61d64 --- /dev/null +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFRichTextString.java @@ -0,0 +1,179 @@ +package org.apache.poi.hssf.usermodel; + +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; + +/** + * Rich text unicode string. These strings can have fonts applied to + * arbitary parts of the string. + * + * @author Glen Stampoultzis (glens at apache.org) + */ +public class HSSFRichTextString + implements Comparable +{ + /** Place holder for indicating that NO_FONT has been applied here */ + public static final short NO_FONT = -1; + + String string; + SortedMap formattingRuns = new TreeMap(); + + public HSSFRichTextString() + { + this(""); + } + + public HSSFRichTextString( String string ) + { + this.string = string; + this.formattingRuns.put(new Integer(0), new Short(NO_FONT)); + } + + /** + * Applies a font to the specified characters of a string. + * + * @param startIndex The start index to apply the font to (inclusive) + * @param endIndex The end index to apply the font to (exclusive) + * @param fontIndex The font to use. + */ + public void applyFont(int startIndex, int endIndex, short fontIndex) + { + if (startIndex > endIndex) + throw new IllegalArgumentException("Start index must be less than end index."); + if (startIndex < 0 || endIndex > length()) + throw new IllegalArgumentException("Start and end index not in range."); + if (startIndex == endIndex) + return; + + Integer from = new Integer(startIndex); + Integer to = new Integer(endIndex); + short fontAtIndex = NO_FONT; + if (endIndex != length()) + fontAtIndex = getFontAtIndex(endIndex); + formattingRuns.subMap(from, to).clear(); + formattingRuns.put(from, new Short(fontIndex)); + if (endIndex != length()) + { + if (fontIndex != fontAtIndex) + formattingRuns.put(to, new Short(fontAtIndex)); + } + } + + /** + * Applies a font to the specified characters of a string. + * + * @param startIndex The start index to apply the font to (inclusive) + * @param endIndex The end index to apply to font to (exclusive) + * @param font The index of the font to use. + */ + public void applyFont(int startIndex, int endIndex, HSSFFont font) + { + applyFont(startIndex, endIndex, font.getIndex()); + } + + /** + * Sets the font of the entire string. + * @param font The font to use. + */ + public void applyFont(HSSFFont font) + { + applyFont(0, string.length(), font); + } + + /** + * Returns the plain string representation. + */ + public String getString() + { + return string; + } + + /** + * @return the number of characters in the font. + */ + public int length() + { + return string.length(); + } + + /** + * Returns the font in use at a particular index. + * + * @param index The index. + * @return The font that's currently being applied at that + * index or null if no font is being applied or the + * index is out of range. + */ + public short getFontAtIndex( int index ) + { + if (index < 0 || index >= string.length()) + throw new ArrayIndexOutOfBoundsException("Font index " + index + " out of bounds of string"); + Integer key = new Integer(index + 1); + SortedMap head = formattingRuns.headMap(key); + if (head.isEmpty()) + throw new IllegalStateException("Should not reach here. No font found."); + else + return ((Short) head.get(head.lastKey())).shortValue(); + } + + /** + * @return The number of formatting runs used. There will always be at + * least one of font NO_FONT. + * + * @see #NO_FONT + */ + public int numFormattingRuns() + { + return formattingRuns.size(); + } + + /** + * The index within the string to which the specified formatting run applies. + * @param index the index of the formatting run + * @return the index within the string. + */ + public int getIndexOfFormattingRun(int index) + { + Map.Entry[] runs = (Map.Entry[]) formattingRuns.entrySet().toArray(new Map.Entry[formattingRuns.size()] ); + return ((Integer)runs[index].getKey()).intValue(); + } + + /** + * Gets the font used in a particular formatting run. + * + * @param index the index of the formatting run + * @return the font number used. + */ + public short getFontOfFormattingRun(int index) + { + Map.Entry[] runs = (Map.Entry[]) formattingRuns.entrySet().toArray(new Map.Entry[formattingRuns.size()] ); + return ((Short)(runs[index].getValue())).shortValue(); + } + + /** + * Compares one rich text string to another. + */ + public int compareTo( Object o ) + { + return 0; // todo + } + + /** + * @return the plain text representation of this string. + */ + public String toString() + { + return string; + } + + /** + * Applies the specified font to the entire string. + * + * @param fontIndex the font to apply. + */ + public void applyFont( short fontIndex ) + { + applyFont(0, string.length(), fontIndex); + } +} diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFShape.java b/src/java/org/apache/poi/hssf/usermodel/HSSFShape.java new file mode 100644 index 0000000000..61620f2351 --- /dev/null +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFShape.java @@ -0,0 +1,195 @@ +package org.apache.poi.hssf.usermodel; + +/** + * An abstract shape. + * + * @author Glen Stampoultzis (glens at apache.org) + */ +public abstract class HSSFShape +{ + public static final int LINEWIDTH_ONE_PT = 12700; + public static final int LINEWIDTH_DEFAULT = 9525; + + public static final int LINESTYLE_SOLID = 0; // Solid (continuous) pen + public static final int LINESTYLE_DASHSYS = 1; // PS_DASH system dash style + public static final int LINESTYLE_DOTSYS = 2; // PS_DOT system dash style + public static final int LINESTYLE_DASHDOTSYS = 3; // PS_DASHDOT system dash style + public static final int LINESTYLE_DASHDOTDOTSYS = 4; // PS_DASHDOTDOT system dash style + public static final int LINESTYLE_DOTGEL = 5; // square dot style + public static final int LINESTYLE_DASHGEL = 6; // dash style + public static final int LINESTYLE_LONGDASHGEL = 7; // long dash style + public static final int LINESTYLE_DASHDOTGEL = 8; // dash short dash + public static final int LINESTYLE_LONGDASHDOTGEL = 9; // long dash short dash + public static final int LINESTYLE_LONGDASHDOTDOTGEL = 10; // long dash short dash short dash + public static final int LINESTYLE_NONE = -1; + + HSSFShape parent; + HSSFAnchor anchor; + int lineStyleColor = 0x08000040; + int fillColor = 0x08000009; + int lineWidth = LINEWIDTH_DEFAULT; // 12700 = 1pt + int lineStyle = LINESTYLE_SOLID; + boolean noFill = false; + + /** + * Create a new shape with the specified parent and anchor. + */ + HSSFShape( HSSFShape parent, HSSFAnchor anchor ) + { + this.parent = parent; + this.anchor = anchor; + } + + /** + * Gets the parent shape. + */ + public HSSFShape getParent() + { + return parent; + } + + /** + * @return the anchor that is used by this shape. + */ + public HSSFAnchor getAnchor() + { + return anchor; + } + + /** + * Sets a particular anchor. A top-level shape must have an anchor of + * HSSFClientAnchor. A child anchor must have an anchor of HSSFChildAnchor + * + * @param anchor the anchor to use. + * @throws IllegalArgumentException when the wrong anchor is used for + * this particular shape. + * + * @see HSSFChildAnchor + * @see HSSFClientAnchor + */ + public void setAnchor( HSSFAnchor anchor ) + { + if ( parent == null ) + { + if ( anchor instanceof HSSFChildAnchor ) + throw new IllegalArgumentException( "Must use client anchors for shapes directly attached to sheet." ); + } + else + { + if ( anchor instanceof HSSFClientAnchor ) + throw new IllegalArgumentException( "Must use child anchors for shapes attached to groups." ); + } + + this.anchor = anchor; + } + + /** + * The color applied to the lines of this shape. + */ + public int getLineStyleColor() + { + return lineStyleColor; + } + + /** + * The color applied to the lines of this shape. + */ + public void setLineStyleColor( int lineStyleColor ) + { + this.lineStyleColor = lineStyleColor; + } + + /** + * The color applied to the lines of this shape. + */ + public void setLineStyleColor( int red, int green, int blue ) + { + this.lineStyleColor = ((blue) << 16) | ((green) << 8) | red; + } + + /** + * The color used to fill this shape. + */ + public int getFillColor() + { + return fillColor; + } + + /** + * The color used to fill this shape. + */ + public void setFillColor( int fillColor ) + { + this.fillColor = fillColor; + } + + /** + * The color used to fill this shape. + */ + public void setFillColor( int red, int green, int blue ) + { + this.fillColor = ((blue) << 16) | ((green) << 8) | red; + } + + /** + * @return returns with width of the line in EMUs. 12700 = 1 pt. + */ + public int getLineWidth() + { + return lineWidth; + } + + /** + * Sets the width of the line. 12700 = 1 pt. + * + * @param lineWidth width in EMU's. 12700EMU's = 1 pt + * + * @see HSSFShape#LINEWIDTH_ONE_PT + */ + public void setLineWidth( int lineWidth ) + { + this.lineWidth = lineWidth; + } + + /** + * @return One of the constants in LINESTYLE_* + */ + public int getLineStyle() + { + return lineStyle; + } + + /** + * Sets the line style. + * + * @param lineStyle One of the constants in LINESTYLE_* + */ + public void setLineStyle( int lineStyle ) + { + this.lineStyle = lineStyle; + } + + /** + * @return true if this shape is not filled with a color. + */ + public boolean isNoFill() + { + return noFill; + } + + /** + * Sets whether this shape is filled or transparent. + */ + public void setNoFill( boolean noFill ) + { + this.noFill = noFill; + } + + /** + * Count of all children and their childrens children. + */ + public int countOfAllChildren() + { + return 1; + } +} diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFShapeContainer.java b/src/java/org/apache/poi/hssf/usermodel/HSSFShapeContainer.java new file mode 100644 index 0000000000..f641fe9c0e --- /dev/null +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFShapeContainer.java @@ -0,0 +1,17 @@ +package org.apache.poi.hssf.usermodel; + +import java.util.List; + +/** + * An interface that indicates whether a class can contain children. + * + * @author Glen Stampoultzis (glens at apache.org) + */ +public interface HSSFShapeContainer +{ + /** + * @return Any children contained by this shape. + */ + List getChildren(); + +} diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFShapeGroup.java b/src/java/org/apache/poi/hssf/usermodel/HSSFShapeGroup.java new file mode 100644 index 0000000000..c33c8c6d34 --- /dev/null +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFShapeGroup.java @@ -0,0 +1,148 @@ +package org.apache.poi.hssf.usermodel; + +import java.util.ArrayList; +import java.util.List; +import java.util.Iterator; + +/** + * A shape group may contain other shapes. It was no actual form on the + * sheet. + * + * @author Glen Stampoultzis (glens at apache.org) + */ +public class HSSFShapeGroup + extends HSSFShape + implements HSSFShapeContainer +{ + List shapes = new ArrayList(); + int x1 = 0; + int y1 = 0 ; + int x2 = 1023; + int y2 = 255; + + + public HSSFShapeGroup( HSSFShape parent, HSSFAnchor anchor ) + { + super( parent, anchor ); + } + + /** + * Create another group under this group. + * @param anchor the position of the new group. + * @return the group + */ + public HSSFShapeGroup createGroup(HSSFChildAnchor anchor) + { + HSSFShapeGroup group = new HSSFShapeGroup(this, anchor); + group.anchor = anchor; + shapes.add(group); + return group; + } + + /** + * Create a new simple shape under this group. + * @param anchor the position of the shape. + * @return the shape + */ + public HSSFSimpleShape createShape(HSSFChildAnchor anchor) + { + HSSFSimpleShape shape = new HSSFSimpleShape(this, anchor); + shape.anchor = anchor; + shapes.add(shape); + return shape; + } + + /** + * Create a new textbox under this group. + * @param anchor the position of the shape. + * @return the textbox + */ + public HSSFTextbox createTextbox(HSSFChildAnchor anchor) + { + HSSFTextbox shape = new HSSFTextbox(this, anchor); + shape.anchor = anchor; + shapes.add(shape); + return shape; + } + + /** + * Creates a polygon + * + * @param anchor the client anchor describes how this group is attached + * to the sheet. + * @return the newly created shape. + */ + public HSSFPolygon createPolygon(HSSFChildAnchor anchor) + { + HSSFPolygon shape = new HSSFPolygon(this, anchor); + shape.anchor = anchor; + shapes.add(shape); + return shape; + } + + /** + * Return all children contained by this shape. + */ + public List getChildren() + { + return shapes; + } + + /** + * Sets the coordinate space of this group. All children are contrained + * to these coordinates. + */ + public void setCoordinates( int x1, int y1, int x2, int y2 ) + { + this.x1 = x1; + this.y1 = y1; + this.x2 = x2; + this.y2 = y2; + } + + /** + * The top left x coordinate of this group. + */ + public int getX1() + { + return x1; + } + + /** + * The top left y coordinate of this group. + */ + public int getY1() + { + return y1; + } + + /** + * The bottom right x coordinate of this group. + */ + public int getX2() + { + return x2; + } + + /** + * The bottom right y coordinate of this group. + */ + public int getY2() + { + return y2; + } + + /** + * Count of all children and their childrens children. + */ + public int countOfAllChildren() + { + int count = shapes.size(); + for ( Iterator iterator = shapes.iterator(); iterator.hasNext(); ) + { + HSSFShape shape = (HSSFShape) iterator.next(); + count += shape.countOfAllChildren(); + } + return count; + } +} diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java b/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java index c0fcfe0aa8..a48c87d43f 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java @@ -62,19 +62,15 @@ package org.apache.poi.hssf.usermodel; import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.io.PrintWriter; import org.apache.poi.hssf.model.Sheet; import org.apache.poi.hssf.model.Workbook; -import org.apache.poi.hssf.record.CellValueRecordInterface; -import org.apache.poi.hssf.record.HCenterRecord; -import org.apache.poi.hssf.record.RowRecord; -import org.apache.poi.hssf.record.SCLRecord; -import org.apache.poi.hssf.record.VCenterRecord; -import org.apache.poi.hssf.record.WSBoolRecord; -import org.apache.poi.hssf.record.WindowTwoRecord; +import org.apache.poi.hssf.record.*; import org.apache.poi.hssf.util.Region; import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogger; +import org.apache.poi.ddf.EscherRecord; /** * High level representation of a worksheet. @@ -979,6 +975,9 @@ public class HSSFSheet * <p> * Additionally shifts merged regions that are completely defined in these * rows (ie. merged 2 cells on a row to be shifted). + * <p> + * TODO Might want to add bounds checking here + * * @param startRow the row to start shifting * @param endRow the row to end shifting * @param n the number of rows to shift @@ -1001,8 +1000,9 @@ public class HSSFSheet inc = -1; } - shiftMerged(startRow, endRow, n, true); - + shiftMerged(startRow, endRow, n, true); + sheet.shiftRowBreaks(startRow, endRow, n); + for ( int rowNum = s; rowNum >= startRow && rowNum <= endRow && rowNum >= 0 && rowNum < 65536; rowNum += inc ) { HSSFRow row = getRow( rowNum ); @@ -1146,6 +1146,149 @@ public class HSSFSheet public boolean isDisplayRowColHeadings() { return sheet.isDisplayRowColHeadings(); } + + /** + * Sets a page break at the indicated row + * @param row + */ + public void setRowBreak(int row) { + validateRow(row); + sheet.setRowBreak(row, (short)0, (short)255); + } + + /** + * Determines if there is a page break at the indicated row + * @param row + * @return + */ + public boolean isRowBroken(int row) { + return sheet.isRowBroken(row); + } + + /** + * Removes the page break at the indicated row + * @param row + */ + public void removeRowBreak(int row) { + sheet.removeRowBreak(row); + } + + /** + * Retrieves all the horizontal page breaks + * @return + */ + public int[] getRowBreaks(){ + //we can probably cache this information, but this should be a sparsely used function + int[] returnValue = new int[sheet.getNumRowBreaks()]; + Iterator iterator = sheet.getRowBreaks(); + int i = 0; + while (iterator.hasNext()) { + PageBreakRecord.Break breakItem = (PageBreakRecord.Break)iterator.next(); + returnValue[i++] = (int)breakItem.main; + } + return returnValue; + } + + /** + * Retrieves all the vertical page breaks + * @return + */ + public short[] getColumnBreaks(){ + //we can probably cache this information, but this should be a sparsely used function + short[] returnValue = new short[sheet.getNumColumnBreaks()]; + Iterator iterator = sheet.getColumnBreaks(); + int i = 0; + while (iterator.hasNext()) { + PageBreakRecord.Break breakItem = (PageBreakRecord.Break)iterator.next(); + returnValue[i++] = breakItem.main; + } + return returnValue; + } + + + /** + * Sets a page break at the indicated column + * @param column + */ + public void setColumnBreak(short column) { + validateColumn(column); + sheet.setColumnBreak(column, (short)0, (short)65535); + } + + /** + * Determines if there is a page break at the indicated column + * @param column + * @return + */ + public boolean isColumnBroken(short column) { + return sheet.isColumnBroken(column); + } + + /** + * Removes a page break at the indicated column + * @param column + */ + public void removeColumnBreak(short column) { + sheet.removeColumnBreak(column); + } + + /** + * Runs a bounds check for row numbers + * @param row + */ + protected void validateRow(int row) { + if (row > 65535) throw new IllegalArgumentException("Maximum row number is 65535"); + if (row < 0) throw new IllegalArgumentException("Minumum row number is 0"); + } + + /** + * Runs a bounds check for column numbers + * @param column + */ + protected void validateColumn(short column) { + if (column > 255) throw new IllegalArgumentException("Maximum column number is 255"); + if (column < 0) throw new IllegalArgumentException("Minimum column number is 0"); + } + + /** + * Aggregates the drawing records and dumps the escher record hierarchy + * to the standard output. + */ + public void dumpDrawingRecords() + { + sheet.aggregateDrawingRecords(book.getDrawingManager()); + + EscherAggregate r = (EscherAggregate) getSheet().findFirstRecordBySid(EscherAggregate.sid); + List escherRecords = r.getEscherRecords(); + for ( Iterator iterator = escherRecords.iterator(); iterator.hasNext(); ) + { + EscherRecord escherRecord = (EscherRecord) iterator.next(); + PrintWriter w = new PrintWriter(System.out); + escherRecord.display(w, 0); + w.close(); + } + } + + /** + * Creates the toplevel drawing patriarch. This will have the effect of + * removing any existing drawings on this sheet. + * + * @return The new patriarch. + */ + public HSSFPatriarch createDrawingPatriarch() + { + // Create the drawing group if it doesn't already exist. + book.createDrawingGroup(); + + sheet.aggregateDrawingRecords(book.getDrawingManager()); + EscherAggregate agg = (EscherAggregate) sheet.findFirstRecordBySid(EscherAggregate.sid); + HSSFPatriarch patriarch = new HSSFPatriarch(this); + agg.clear(); // Initially the behaviour will be to clear out any existing shapes in the sheet when + // creating a new patriarch. + agg.setPatriarch(patriarch); + return patriarch; + } + } class SheetRowIterator implements Iterator { diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFSimpleShape.java b/src/java/org/apache/poi/hssf/usermodel/HSSFSimpleShape.java new file mode 100644 index 0000000000..c90fe6bb8c --- /dev/null +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFSimpleShape.java @@ -0,0 +1,64 @@ +package org.apache.poi.hssf.usermodel; + +/** + * Represents a simple shape such as a line, rectangle or oval. + * + * @author Glen Stampoultzis (glens at apache.org) + */ +public class HSSFSimpleShape + extends HSSFShape +{ + // The commented out ones haven't been tested yet or aren't supported + // by HSSFSimpleShape. + + public final static short OBJECT_TYPE_LINE = 1; + public final static short OBJECT_TYPE_RECTANGLE = 2; + public final static short OBJECT_TYPE_OVAL = 3; +// public final static short OBJECT_TYPE_ARC = 4; +// public final static short OBJECT_TYPE_CHART = 5; +// public final static short OBJECT_TYPE_TEXT = 6; +// public final static short OBJECT_TYPE_BUTTON = 7; +// public final static short OBJECT_TYPE_PICTURE = 8; +// public final static short OBJECT_TYPE_POLYGON = 9; +// public final static short OBJECT_TYPE_CHECKBOX = 11; +// public final static short OBJECT_TYPE_OPTION_BUTTON = 12; +// public final static short OBJECT_TYPE_EDIT_BOX = 13; +// public final static short OBJECT_TYPE_LABEL = 14; +// public final static short OBJECT_TYPE_DIALOG_BOX = 15; +// public final static short OBJECT_TYPE_SPINNER = 16; +// public final static short OBJECT_TYPE_SCROLL_BAR = 17; +// public final static short OBJECT_TYPE_LIST_BOX = 18; +// public final static short OBJECT_TYPE_GROUP_BOX = 19; +// public final static short OBJECT_TYPE_COMBO_BOX = 20; +// public final static short OBJECT_TYPE_COMMENT = 25; +// public final static short OBJECT_TYPE_MICROSOFT_OFFICE_DRAWING = 30; + + int shapeType = OBJECT_TYPE_LINE; + + HSSFSimpleShape( HSSFShape parent, HSSFAnchor anchor ) + { + super( parent, anchor ); + } + + /** + * Gets the shape type. + * @return One of the OBJECT_TYPE_* constants. + * + * @see #OBJECT_TYPE_LINE + * @see #OBJECT_TYPE_OVAL + * @see #OBJECT_TYPE_RECTANGLE + */ + public int getShapeType() { return shapeType; } + + /** + * Sets the shape types. + * + * @param shapeType One of the OBJECT_TYPE_* constants. + * + * @see #OBJECT_TYPE_LINE + * @see #OBJECT_TYPE_OVAL + * @see #OBJECT_TYPE_RECTANGLE + */ + public void setShapeType( int shapeType ){ this.shapeType = shapeType; } + +} diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFTextbox.java b/src/java/org/apache/poi/hssf/usermodel/HSSFTextbox.java new file mode 100644 index 0000000000..423e256fac --- /dev/null +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFTextbox.java @@ -0,0 +1,107 @@ +package org.apache.poi.hssf.usermodel; + +/** + * A textbox is a shape that may hold a rich text string. + * + * @author Glen Stampoultzis (glens at apache.org) + */ +public class HSSFTextbox + extends HSSFSimpleShape +{ + public final static short OBJECT_TYPE_TEXT = 6; + + int marginLeft, marginRight, marginTop, marginBottom; + + HSSFRichTextString string = new HSSFRichTextString(""); + + /** + * Construct a new textbox with the given parent and anchor. + * @param parent + * @param anchor One of HSSFClientAnchor or HSSFChildAnchor + */ + public HSSFTextbox( HSSFShape parent, HSSFAnchor anchor ) + { + super( parent, anchor ); + setShapeType(OBJECT_TYPE_TEXT); + } + + /** + * @return the rich text string for this textbox. + */ + public HSSFRichTextString getString() + { + return string; + } + + /** + * @param string Sets the rich text string used by this object. + */ + public void setString( HSSFRichTextString string ) + { + this.string = string; + } + + /** + * @return Returns the left margin within the textbox. + */ + public int getMarginLeft() + { + return marginLeft; + } + + /** + * Sets the left margin within the textbox. + */ + public void setMarginLeft( int marginLeft ) + { + this.marginLeft = marginLeft; + } + + /** + * @return returns the right margin within the textbox. + */ + public int getMarginRight() + { + return marginRight; + } + + /** + * Sets the right margin within the textbox. + */ + public void setMarginRight( int marginRight ) + { + this.marginRight = marginRight; + } + + /** + * @return returns the top margin within the textbox. + */ + public int getMarginTop() + { + return marginTop; + } + + /** + * Sets the top margin within the textbox. + */ + public void setMarginTop( int marginTop ) + { + this.marginTop = marginTop; + } + + /** + * Gets the bottom margin within the textbox. + */ + public int getMarginBottom() + { + return marginBottom; + } + + /** + * Sets the bottom margin within the textbox. + */ + public void setMarginBottom( int marginBottom ) + { + this.marginBottom = marginBottom; + } +} diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java b/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java index 02bdc81167..9fa70e7471 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java @@ -653,6 +653,40 @@ public class HSSFWorkbook } /** + * Finds a font that matches the one with the supplied attributes + */ + public HSSFFont findFont(short boldWeight, short color, short fontHeight, + String name, boolean italic, boolean strikeout, + short typeOffset, byte underline) + { +// System.out.println( boldWeight + ", " + color + ", " + fontHeight + ", " + name + ", " + italic + ", " + strikeout + ", " + typeOffset + ", " + underline ); + for (short i = 0; i < workbook.getNumberOfFontRecords(); i++) + { + if (i == 4) + continue; + + FontRecord font = workbook.getFontRecordAt(i); + HSSFFont hssfFont = new HSSFFont(i, font); +// System.out.println( hssfFont.getBoldweight() + ", " + hssfFont.getColor() + ", " + hssfFont.getFontHeight() + ", " + hssfFont.getFontName() + ", " + hssfFont.getItalic() + ", " + hssfFont.getStrikeout() + ", " + hssfFont.getTypeOffset() + ", " + hssfFont.getUnderline() ); + if (hssfFont.getBoldweight() == boldWeight + && hssfFont.getColor() == color + && hssfFont.getFontHeight() == fontHeight + && hssfFont.getFontName().equals(name) + && hssfFont.getItalic() == italic + && hssfFont.getStrikeout() == strikeout + && hssfFont.getTypeOffset() == typeOffset + && hssfFont.getUnderline() == underline) + { +// System.out.println( "Found font" ); + return hssfFont; + } + } + +// System.out.println( "No font found" ); + return null; + } + + /** * get the number of fonts in the font table * @return number of fonts */ diff --git a/src/java/org/apache/poi/hssf/usermodel/StaticFontMetrics.java b/src/java/org/apache/poi/hssf/usermodel/StaticFontMetrics.java new file mode 100644 index 0000000000..1c7b330cc3 --- /dev/null +++ b/src/java/org/apache/poi/hssf/usermodel/StaticFontMetrics.java @@ -0,0 +1,81 @@ +package org.apache.poi.hssf.usermodel; + +import java.util.*; +import java.awt.*; +import java.io.*; + +/** + * Allows the user to lookup the font metrics for a particular font without + * actually having the font on the system. The font details are loaded + * as a resource from the POI jar file (or classpath) and should be contained + * in path "/font_metrics.properties". The font widths are for a 10 point + * version of the font. Use a multiplier for other sizes. + * + * @author Glen Stampoultzis (glens at apache.org) + */ +class StaticFontMetrics +{ + private static Properties fontMetricsProps; + private static Map fontDetailsMap = new HashMap(); + + /** + * Retrieves the fake font details for a given font. + * @param font the font to lookup. + * @return the fake font. + */ + public static FontDetails getFontDetails(Font font) + { + if (fontMetricsProps == null) + { + InputStream metricsIn = null; + try + { + fontMetricsProps = new Properties(); + if (System.getProperty("font.metrics.filename") != null) + { + String filename = System.getProperty("font.metrics.filename"); + File file = new File(filename); + if (!file.exists()) + throw new FileNotFoundException("font_metrics.properties not found at path " + file.getAbsolutePath()); + metricsIn = new FileInputStream(file); + } + else + { + metricsIn = FontDetails.class.getResourceAsStream("/font_metrics.properties"); + if (metricsIn == null) + throw new FileNotFoundException("font_metrics.properties not found in classpath"); + } + fontMetricsProps.load(metricsIn); + } + catch ( IOException e ) + { + throw new RuntimeException("Could not load font metrics: " + e.getMessage()); + } + finally + { + if (metricsIn != null) + { + try + { + metricsIn.close(); + } + catch ( IOException ignore ) { } + } + } + } + + String fontName = font.getName(); + + if (fontDetailsMap.get(fontName) == null) + { + FontDetails fontDetails = FontDetails.create(fontName, fontMetricsProps); + fontDetailsMap.put( fontName, fontDetails ); + return fontDetails; + } + else + { + return (FontDetails) fontDetailsMap.get(fontName); + } + + } +} diff --git a/src/java/org/apache/poi/hssf/usermodel/TestEscherGraphics.java b/src/java/org/apache/poi/hssf/usermodel/TestEscherGraphics.java new file mode 100644 index 0000000000..336b961f42 --- /dev/null +++ b/src/java/org/apache/poi/hssf/usermodel/TestEscherGraphics.java @@ -0,0 +1,71 @@ +package org.apache.poi.hssf.usermodel; + +import junit.framework.TestCase; + +import java.awt.*; + +/** + * Tests the capabilities of the EscherGraphics class. + * + * @author Glen Stampoultzis (glens at apache.org) + */ +public class TestEscherGraphics extends TestCase +{ + private HSSFShapeGroup escherGroup; + private EscherGraphics graphics; + + protected void setUp() throws Exception + { + HSSFWorkbook workbook = new HSSFWorkbook(); + HSSFSheet sheet = workbook.createSheet("test"); + escherGroup = sheet.createDrawingPatriarch().createGroup(new HSSFClientAnchor(0,0,1023,255,(short)0,0,(short) 0,0)); + escherGroup = new HSSFShapeGroup(null, new HSSFChildAnchor()); + graphics = new EscherGraphics(this.escherGroup, workbook, Color.black, 1.0f); + super.setUp(); + } + + public void testGetFont() throws Exception + { + Font f = graphics.getFont(); + assertEquals("java.awt.Font[family=Arial,name=Arial,style=plain,size=10]", f.toString()); + } + + public void testGetFontMetrics() throws Exception + { + FontMetrics fontMetrics = graphics.getFontMetrics(graphics.getFont()); + assertEquals(7, fontMetrics.charWidth('X')); + assertEquals("java.awt.Font[family=Arial,name=Arial,style=plain,size=10]", fontMetrics.getFont().toString()); + } + + public void testSetFont() throws Exception + { + Font f = new Font("Helvetica", 0, 12); + graphics.setFont(f); + assertEquals(f, graphics.getFont()); + } + + public void testSetColor() throws Exception + { + graphics.setColor(Color.red); + assertEquals(Color.red, graphics.getColor()); + } + + public void testFillRect() throws Exception + { + graphics.fillRect( 10, 10, 20, 20 ); + HSSFSimpleShape s = (HSSFSimpleShape) escherGroup.getChildren().get(0); + assertEquals(HSSFSimpleShape.OBJECT_TYPE_RECTANGLE, s.getShapeType()); + assertEquals(10, s.getAnchor().getDx1()); + assertEquals(10, s.getAnchor().getDy1()); + assertEquals(30, s.getAnchor().getDy2()); + assertEquals(30, s.getAnchor().getDx2()); + } + + public void testDrawString() throws Exception + { + graphics.drawString("This is a test", 10, 10); + HSSFTextbox t = (HSSFTextbox) escherGroup.getChildren().get(0); + assertEquals("This is a test", t.getString().toString()); + } + +} diff --git a/src/java/org/apache/poi/hssf/usermodel/TestEscherGraphics2d.java b/src/java/org/apache/poi/hssf/usermodel/TestEscherGraphics2d.java new file mode 100644 index 0000000000..22f0e28c45 --- /dev/null +++ b/src/java/org/apache/poi/hssf/usermodel/TestEscherGraphics2d.java @@ -0,0 +1,74 @@ +package org.apache.poi.hssf.usermodel; + +import junit.framework.TestCase; + +import java.awt.*; +import java.io.FileOutputStream; + +/** + * Tests the Graphics2d drawing capability. + * + * @author Glen Stampoultzis (glens at apache.org) + */ +public class TestEscherGraphics2d extends TestCase +{ + private HSSFShapeGroup escherGroup; + private EscherGraphics2d graphics; + + protected void setUp() throws Exception + { + super.setUp(); + + HSSFWorkbook workbook = new HSSFWorkbook(); + HSSFSheet sheet = workbook.createSheet("test"); + escherGroup = sheet.createDrawingPatriarch().createGroup(new HSSFClientAnchor(0,0,1023,255,(short)0,0,(short) 0,0)); + escherGroup = new HSSFShapeGroup(null, new HSSFChildAnchor()); + EscherGraphics g = new EscherGraphics(this.escherGroup, workbook, Color.black, 1.0f); + graphics = new EscherGraphics2d(g); + + } + + public void testDrawString() throws Exception + { + graphics.drawString("This is a test", 10, 10); + HSSFTextbox t = (HSSFTextbox) escherGroup.getChildren().get(0); + assertEquals("This is a test", t.getString().toString()); + } + + public void testFillRect() throws Exception + { + graphics.fillRect( 10, 10, 20, 20 ); + HSSFSimpleShape s = (HSSFSimpleShape) escherGroup.getChildren().get(0); + assertEquals(HSSFSimpleShape.OBJECT_TYPE_RECTANGLE, s.getShapeType()); + assertEquals(10, s.getAnchor().getDx1()); + assertEquals(10, s.getAnchor().getDy1()); + assertEquals(30, s.getAnchor().getDy2()); + assertEquals(30, s.getAnchor().getDx2()); + } + + public void testGetFontMetrics() throws Exception + { + FontMetrics fontMetrics = graphics.getFontMetrics(graphics.getFont()); + assertEquals(7, fontMetrics.charWidth('X')); + assertEquals("java.awt.Font[family=Arial,name=Arial,style=plain,size=10]", fontMetrics.getFont().toString()); + } + + public void testSetFont() throws Exception + { + Font f = new Font("Helvetica", 0, 12); + graphics.setFont(f); + assertEquals(f, graphics.getFont()); + } + + public void testSetColor() throws Exception + { + graphics.setColor(Color.red); + assertEquals(Color.red, graphics.getColor()); + } + + public void testGetFont() throws Exception + { + Font f = graphics.getFont(); + assertEquals("java.awt.Font[family=Arial,name=Arial,style=plain,size=10]", f.toString()); + } +} diff --git a/src/java/org/apache/poi/hssf/usermodel/TestFontDetails.java b/src/java/org/apache/poi/hssf/usermodel/TestFontDetails.java new file mode 100644 index 0000000000..532e62568b --- /dev/null +++ b/src/java/org/apache/poi/hssf/usermodel/TestFontDetails.java @@ -0,0 +1,45 @@ +package org.apache.poi.hssf.usermodel; + +import junit.framework.TestCase; + +import java.util.Properties; + +/** + * Tests the implementation of the FontDetails class. + * + * @author Glen Stampoultzis (glens at apache.org) + */ +public class TestFontDetails extends TestCase +{ + private Properties properties; + private FontDetails fontDetails; + + protected void setUp() throws Exception + { + properties = new Properties(); + properties.setProperty("font.Arial.height", "13"); + properties.setProperty("font.Arial.characters", "a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "); + properties.setProperty("font.Arial.widths", "6, 6, 6, 6, 6, 3, 6, 6, 3, 4, 6, 3, 9, 6, 6, 6, 6, 4, 6, 3, 6, 7, 9, 6, 5, 5, 7, 7, 7, 7, 7, 6, 8, 7, 3, 6, 7, 6, 9, 7, 8, 7, 8, 7, 7, 5, 7, 7, 9, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, "); + fontDetails = FontDetails.create("Arial", properties); + + } + + public void testCreate() throws Exception + { + assertEquals(13, fontDetails.getHeight()); + assertEquals(6, fontDetails.getCharWidth('a')); + assertEquals(3, fontDetails.getCharWidth('f')); + } + + public void testGetStringWidth() throws Exception + { + assertEquals(9, fontDetails.getStringWidth("af")); + } + + public void testGetCharWidth() throws Exception + { + assertEquals(6, fontDetails.getCharWidth('a')); + assertEquals(9, fontDetails.getCharWidth('=')); + } + +} diff --git a/src/java/org/apache/poi/hssf/usermodel/TestHSSFClientAnchor.java b/src/java/org/apache/poi/hssf/usermodel/TestHSSFClientAnchor.java new file mode 100644 index 0000000000..ab3e06a3d6 --- /dev/null +++ b/src/java/org/apache/poi/hssf/usermodel/TestHSSFClientAnchor.java @@ -0,0 +1,44 @@ +package org.apache.poi.hssf.usermodel; + +import junit.framework.TestCase; + +/** + * Various tests for HSSFClientAnchor. + * + * @author Glen Stampoultzis (glens at apache.org) + */ +public class TestHSSFClientAnchor extends TestCase +{ + public void testGetAnchorHeightInPoints() throws Exception + { + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sheet = wb.createSheet("test"); + HSSFClientAnchor a = new HSSFClientAnchor(0,0,1023,255,(short)0,0,(short)0,0); + float p = a.getAnchorHeightInPoints(sheet); + assertEquals(11.953,p,0.001); + + sheet.createRow(0).setHeightInPoints(14); + a = new HSSFClientAnchor(0,0,1023,255,(short)0,0,(short)0,0); + p = a.getAnchorHeightInPoints(sheet); + assertEquals(13.945,p,0.001); + + a = new HSSFClientAnchor(0,0,1023,127,(short)0,0,(short)0,0); + p = a.getAnchorHeightInPoints(sheet); + assertEquals(6.945,p,0.001); + + a = new HSSFClientAnchor(0,126,1023,127,(short)0,0,(short)0,0); + p = a.getAnchorHeightInPoints(sheet); + assertEquals(0.054,p,0.001); + + a = new HSSFClientAnchor(0,0,1023,0,(short)0,0,(short)0,1); + p = a.getAnchorHeightInPoints(sheet); + assertEquals(14.0,p,0.001); + + sheet.createRow(0).setHeightInPoints(12); + a = new HSSFClientAnchor(0,127,1023,127,(short)0,0,(short)0,1); + p = a.getAnchorHeightInPoints(sheet); + assertEquals(12.0,p,0.001); + + } + +} diff --git a/src/java/org/apache/poi/hssf/usermodel/TestHSSFRichTextString.java b/src/java/org/apache/poi/hssf/usermodel/TestHSSFRichTextString.java new file mode 100644 index 0000000000..69dcb78e7e --- /dev/null +++ b/src/java/org/apache/poi/hssf/usermodel/TestHSSFRichTextString.java @@ -0,0 +1,50 @@ +package org.apache.poi.hssf.usermodel; + +import junit.framework.TestCase; + +public class TestHSSFRichTextString extends TestCase +{ + public void testApplyFont() throws Exception + { + + HSSFRichTextString r = new HSSFRichTextString("testing"); + assertEquals(1,r.numFormattingRuns()); + r.applyFont(2,4, new HSSFFont((short)1, null)); + assertEquals(3,r.numFormattingRuns()); + assertEquals(HSSFRichTextString.NO_FONT, r.getFontAtIndex(0)); + assertEquals(HSSFRichTextString.NO_FONT, r.getFontAtIndex(1)); + assertEquals(1, r.getFontAtIndex(2)); + assertEquals(1, r.getFontAtIndex(3)); + assertEquals(HSSFRichTextString.NO_FONT, r.getFontAtIndex(4)); + assertEquals(HSSFRichTextString.NO_FONT, r.getFontAtIndex(5)); + assertEquals(HSSFRichTextString.NO_FONT, r.getFontAtIndex(6)); + + r.applyFont(6,7, new HSSFFont((short)2, null)); + assertEquals(HSSFRichTextString.NO_FONT, r.getFontAtIndex(0)); + assertEquals(HSSFRichTextString.NO_FONT, r.getFontAtIndex(1)); + assertEquals(1, r.getFontAtIndex(2)); + assertEquals(1, r.getFontAtIndex(3)); + assertEquals(HSSFRichTextString.NO_FONT, r.getFontAtIndex(4)); + assertEquals(HSSFRichTextString.NO_FONT, r.getFontAtIndex(5)); + assertEquals(2, r.getFontAtIndex(6)); + + r.applyFont(HSSFRichTextString.NO_FONT); + assertEquals(HSSFRichTextString.NO_FONT, r.getFontAtIndex(0)); + assertEquals(HSSFRichTextString.NO_FONT, r.getFontAtIndex(1)); + assertEquals(HSSFRichTextString.NO_FONT, r.getFontAtIndex(2)); + assertEquals(HSSFRichTextString.NO_FONT, r.getFontAtIndex(3)); + assertEquals(HSSFRichTextString.NO_FONT, r.getFontAtIndex(4)); + assertEquals(HSSFRichTextString.NO_FONT, r.getFontAtIndex(5)); + + r.applyFont(new HSSFFont((short)1, null)); + assertEquals(1, r.getFontAtIndex(0)); + assertEquals(1, r.getFontAtIndex(1)); + assertEquals(1, r.getFontAtIndex(2)); + assertEquals(1, r.getFontAtIndex(3)); + assertEquals(1, r.getFontAtIndex(4)); + assertEquals(1, r.getFontAtIndex(5)); + assertEquals(1, r.getFontAtIndex(6)); + + } + +} diff --git a/src/java/org/apache/poi/util/DrawingDump.java b/src/java/org/apache/poi/util/DrawingDump.java new file mode 100644 index 0000000000..3c62f67368 --- /dev/null +++ b/src/java/org/apache/poi/util/DrawingDump.java @@ -0,0 +1,24 @@ +package org.apache.poi.util; + +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.hssf.usermodel.HSSFSheet; +import org.apache.poi.poifs.filesystem.POIFSFileSystem; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; + +/** + * Dump out the aggregated escher records + */ +public class DrawingDump +{ + public static void main( String[] args ) throws IOException + { + POIFSFileSystem fs = + new POIFSFileSystem(new FileInputStream(args[0])); + HSSFWorkbook wb = new HSSFWorkbook(fs); + HSSFSheet sheet = wb.getSheetAt(0); + sheet.dumpDrawingRecords(); + } +} diff --git a/src/records/definitions/common_object_data_record.xml b/src/records/definitions/common_object_data_record.xml new file mode 100644 index 0000000000..5e5046206d --- /dev/null +++ b/src/records/definitions/common_object_data_record.xml @@ -0,0 +1,52 @@ +<record id="0x15" name="CommonObjectData" excel-record-id="ftCmo" + package="org.apache.poi.hssf.record"> + <suffix>SubRecord</suffix> + <extends>SubRecord</extends> + <description>The common object data record is used to store all common preferences for an excel object.</description> + <author>Glen Stampoultzis (glens at apache.org)</author> + <fields> + <field type="int" size="2" name="object type"> + <const value="0" name="group" description="group"/> + <const value="1" name="line" description="line"/> + <const value="2" name="rectangle" description="rectangle"/> + <const value="3" name="oval" description="oval"/> + <const value="4" name="arc" description="arc"/> + <const value="5" name="chart" description="chart"/> + <const value="6" name="text" description="text"/> + <const value="7" name="button" description="button"/> + <const value="8" name="picture" description="picture"/> + <const value="9" name="polygon" description="polygon"/> + <const value="10" name="reserved1" description="reserved1"/> + <const value="11" name="checkbox" description="checkbox"/> + <const value="12" name="option button" description="option button"/> + <const value="13" name="edit box" description="edit box"/> + <const value="14" name="label" description="label"/> + <const value="15" name="dialog box" description="dialog box"/> + <const value="16" name="spinner" description="spinner"/> + <const value="17" name="scroll bar" description="scroll bar"/> + <const value="18" name="list box" description="list box"/> + <const value="19" name="group box" description="group box"/> + <const value="20" name="combo box" description="combo box"/> + <const value="21" name="reserved2" description="reserved2"/> + <const value="22" name="reserved3" description="reserved3"/> + <const value="23" name="reserved4" description="reserved4"/> + <const value="24" name="reserved5" description="reserved5"/> + <const value="25" name="comment" description="comment"/> + <const value="26" name="reserved6" description="reserved6"/> + <const value="27" name="reserved7" description="reserved7"/> + <const value="28" name="reserved8" description="reserved8"/> + <const value="29" name="reserved9" description="reserved9"/> + <const value="30" name="microsoft office drawing" description="microsoft office drawing"/> + </field> + <field type="int" size="2" name="object id"/> + <field type="int" size="2" name="option"> + <bit number="0" name="locked" description="true if object is locked when sheet has been protected"/> + <bit number="4" name="printable" description="object appears when printed"/> + <bit number="13" name="autofill" description="whether object uses an automatic fill style"/> + <bit number="14" name="autoline" description="whether object uses an automatic line style"/> + </field> + <field type="int" size="4" name="reserved1" description="Should always be 0"/> + <field type="int" size="4" name="reserved2" description="Should always be 0"/> + <field type="int" size="4" name="reserved3" description="Should always be 0"/> + </fields> +</record> diff --git a/src/records/definitions/ftend_record.xml b/src/records/definitions/ftend_record.xml new file mode 100644 index 0000000000..d516890e3c --- /dev/null +++ b/src/records/definitions/ftend_record.xml @@ -0,0 +1,9 @@ +<record id="0x00" name="End" excel-record-id="ftEnd" + package="org.apache.poi.hssf.record"> + <suffix>SubRecord</suffix> + <extends>SubRecord</extends> + <description>The end data record is used to denote the end of the subrecords.</description> + <author>Glen Stampoultzis (glens at apache.org)</author> + <fields> + </fields> +</record> diff --git a/src/records/definitions/subrecord.xsl b/src/records/definitions/subrecord.xsl new file mode 100644 index 0000000000..a925b27281 --- /dev/null +++ b/src/records/definitions/subrecord.xsl @@ -0,0 +1,326 @@ +<xsl:stylesheet version="1.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:recutil="org.apache.poi.generator.RecordUtil" + xmlns:field="org.apache.poi.generator.FieldIterator" + xmlns:java="java" > + +<xsl:template match="record"> +/* ==================================================================== + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2003 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Apache" and "Apache Software Foundation" and + * "Apache POI" must not be used to endorse or promote products + * derived from this software without prior written permission. For + * written permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * "Apache POI", nor may "Apache" appear in their name, without + * prior written permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + */ + +<xsl:if test="@package"> +package <xsl:value-of select="@package"/>; +</xsl:if> + + +import org.apache.poi.util.*; + +/** + * <xsl:value-of select="/record/description"/> + * NOTE: This source is automatically generated please do not modify this file. Either subclass or + * remove the record in src/records/definitions. +<xsl:apply-templates select="author"/> + */ +public class <xsl:value-of select="@name"/>SubRecord + extends SubRecord +{ + public final static short sid = <xsl:value-of select="@id"/>; +<xsl:for-each select="//fields/field"> private <xsl:value-of select="recutil:getType(@size,@type,10)"/><xsl:text> </xsl:text><xsl:value-of select="recutil:getFieldName(position(),@name,0)"/><xsl:value-of select="recutil:initializeText(@size,@type)"/>; +<xsl:apply-templates select="./bit|./const|./bit/const"/> +</xsl:for-each> + + public <xsl:value-of select="@name"/>SubRecord() + { +<xsl:for-each select="//fields/field"><xsl:if test="@default"> +<xsl:text> </xsl:text> +<xsl:value-of select="recutil:getFieldName(position(),@name,0)"/> = <xsl:value-of select="@default"/>; +</xsl:if></xsl:for-each> + } + + /** + * Constructs a <xsl:value-of select="@name"/> record and sets its fields appropriately. + * + * @param id id must be <xsl:value-of select="@id"/> or an exception + * will be throw upon validation + * @param size size the size of the data area of the record + * @param data data of the record (should not contain sid/len) + */ + + public <xsl:value-of select="@name"/>SubRecord(short id, short size, byte [] data) + { + super(id, size, data); + <xsl:for-each select="//fields/field"> + <xsl:if test="@default"> + <xsl:text> </xsl:text> + <xsl:value-of select="recutil:getFieldName(position(),@name,0)"/> = + <xsl:value-of select="@default"/>; + + </xsl:if> + </xsl:for-each> + } + + /** + * Constructs a <xsl:value-of select="@name"/> record and sets its fields appropriately. + * + * @param id id must be <xsl:value-of select="@id"/> or an exception + * will be throw upon validation + * @param size size the size of the data area of the record + * @param data data of the record (should not contain sid/len) + * @param offset of the record's data + */ + + public <xsl:value-of select="@name"/>SubRecord(short id, short size, byte [] data, int offset) + { + super(id, size, data, offset); + <xsl:for-each select="//fields/field"> + <xsl:if test="@default"> + <xsl:text> </xsl:text> + <xsl:value-of select="recutil:getFieldName(position(),@name,0)"/> = + <xsl:value-of select="@default"/>; + + </xsl:if> + </xsl:for-each> + } + + /** + * Checks the sid matches the expected side for this record + * + * @param id the expected sid. + */ + protected void validateSid(short id) + { + if (id != sid) + { + throw new RecordFormatException("Not a <xsl:value-of select="@name"/> record"); + } + } + + protected void fillFields(byte [] data, short size, int offset) + { + +<xsl:text> int pos = 0; +</xsl:text> + + <xsl:variable name="fieldIterator" select="field:new()"/> +<xsl:for-each select="//fields/field"> + <xsl:text> </xsl:text><xsl:value-of select="field:fillDecoder2($fieldIterator,position(),@name,@size,@type)"/>; +</xsl:for-each> + } + + public String toString() + { + StringBuffer buffer = new StringBuffer(); + + buffer.append("[<xsl:value-of select="recutil:getRecordId(@name,@excel-record-id)"/>]\n"); +<xsl:apply-templates select="//field" mode="tostring"/> + buffer.append("[/<xsl:value-of select="recutil:getRecordId(@name,@excel-record-id)"/>]\n"); + return buffer.toString(); + } + + public int serialize(int offset, byte[] data) + { + int pos = 0; + + LittleEndian.putShort(data, 0 + offset, sid); + LittleEndian.putShort(data, 2 + offset, (short)(getRecordSize() - 4)); +<xsl:variable name="fieldIterator" select="field:new()"/> +<xsl:for-each select="//fields/field"><xsl:text> + </xsl:text><xsl:value-of select="field:serialiseEncoder($fieldIterator,position(),@name,@size,@type)"/> +</xsl:for-each> + + return getRecordSize(); + } + + /** + * Size of record (exluding 4 byte header) + */ + public int getRecordSize() + { +<xsl:variable name="fieldIterator" select="field:new()"/> +<xsl:text> return 4 </xsl:text> +<xsl:for-each select="//fields/field"> + <xsl:value-of select="field:calcSize($fieldIterator,position(),@name,@size,@type)"/> +</xsl:for-each>; + } + + public short getSid() + { + return this.sid; + } + + public Object clone() { + <xsl:value-of select="@name"/>SubRecord rec = new <xsl:value-of select="@name"/>SubRecord(); + <xsl:for-each select="//fields/field"> + <xsl:text> + </xsl:text><xsl:value-of select="recutil:clone(@name,@type,position())"/><xsl:text>;</xsl:text> + </xsl:for-each> + return rec; + } + +<xsl:apply-templates select="//field" mode="getset"/> +<xsl:apply-templates select="//field" mode="bits"/> + +} // END OF CLASS + + +</xsl:template> + +<xsl:template match = "field" mode="bits"> +<xsl:variable name="fieldNum" select="position()"/> +<xsl:for-each select="bit"> +<xsl:if test="not (@mask)"> + /** + * Sets the <xsl:value-of select="@name"/> field value. + * <xsl:value-of select="@description"/> + */ + public void set<xsl:value-of select="recutil:getFieldName1stCap(@name,0)"/>(boolean value) + { + <xsl:value-of select="recutil:getFieldName($fieldNum,../@name,0)"/> = <xsl:value-of select="recutil:getFieldName(@name,0)"/>.set<xsl:value-of select="recutil:getType1stCap(../@size,../@type,0)"/>Boolean(<xsl:value-of select="recutil:getFieldName($fieldNum,../@name,0)"/>, value); + } + + /** + * <xsl:value-of select="@description"/> + * @return the <xsl:value-of select="@name"/> field value. + */ + public boolean is<xsl:value-of select="recutil:getFieldName1stCap(@name,0)"/>() + { + return <xsl:value-of select="recutil:getFieldName(@name,0)"/>.isSet(<xsl:value-of select="recutil:getFieldName($fieldNum,../@name,0)"/>); + } +</xsl:if> +<xsl:if test="@mask"> + /** + * Sets the <xsl:value-of select="@name"/> field value. + * <xsl:value-of select="@description"/> + */ + public void set<xsl:value-of select="recutil:getFieldName1stCap(@name,0)"/>(short value) + { + <xsl:value-of select="recutil:getFieldName($fieldNum,../@name,0)"/> = <xsl:value-of select="recutil:getFieldName(@name,0)"/>.set<xsl:value-of select="recutil:getType1stCap(../@size,../@type,0)"/>Value(<xsl:value-of select="recutil:getFieldName($fieldNum,../@name,0)"/>, value); + } + + /** + * <xsl:value-of select="@description"/> + * @return the <xsl:value-of select="@name"/> field value. + */ + public short get<xsl:value-of select="recutil:getFieldName1stCap(@name,0)"/>() + { + return <xsl:value-of select="recutil:getFieldName(@name,0)"/>.getShortValue(<xsl:value-of select="recutil:getFieldName($fieldNum,../@name,0)"/>); + } +</xsl:if> +</xsl:for-each> +</xsl:template> + +<xsl:template match = "bit" ><xsl:if test="not (@mask)"> private BitField <xsl:value-of select="recutil:getFieldName(@name,42)"/> = new BitField(<xsl:value-of select="recutil:getMask(@number)"/>); +</xsl:if><xsl:if test="@mask"> private BitField <xsl:value-of select="recutil:getFieldName(@name,42)"/> = new BitField(<xsl:value-of select="@mask"/>); +</xsl:if> +</xsl:template> +<xsl:template match = "const"> public final static <xsl:value-of select="recutil:getType(../@size,../@type,10)"/><xsl:text> </xsl:text><xsl:value-of select="recutil:getConstName(../@name,@name,30)"/> = <xsl:value-of select="@value"/>; +</xsl:template> + +<xsl:template match = "const" mode="listconsts"> +<xsl:text> + * </xsl:text> +<xsl:value-of select="recutil:getConstName(../@name,@name,0)"/></xsl:template> +<xsl:template match="field" mode="getset"> + /** + * Get the <xsl:value-of select="@name"/> field for the <xsl:value-of select="../../@name"/> record.<xsl:if test="./const"> + * + * @return One of <xsl:apply-templates select="./const" mode="listconsts"/></xsl:if> + */ + public <xsl:value-of select="recutil:getType(@size,@type,0)"/> get<xsl:value-of select="recutil:getFieldName1stCap(@name,0)"/>() + { + return <xsl:value-of select="recutil:getFieldName(position(),@name,0)"/>; + } + + /** + * Set the <xsl:value-of select="@name"/> field for the <xsl:value-of select="../../@name"/> record.<xsl:if test="./const"> + * + * @param <xsl:value-of select="recutil:getFieldName(position(),@name,0)"/> + * One of <xsl:apply-templates select="./const" mode="listconsts"/></xsl:if> + */ + public void set<xsl:value-of select="recutil:getFieldName1stCap(@name,0)"/>(<xsl:value-of select="recutil:getType(@size,@type,0)"/><xsl:text> </xsl:text><xsl:value-of select="recutil:getFieldName(position(),@name,0)"/>) + { + this.<xsl:value-of select="recutil:getFieldName(position(),@name,0)"/> = <xsl:value-of select="recutil:getFieldName(position(),@name,0)"/>; + } +</xsl:template> + +<xsl:template match="field" mode="tostring"> + <xsl:value-of select="recutil:getToString(@name,@type,@size)"/> + <xsl:text> + buffer.append(System.getProperty("line.separator")); </xsl:text> + <xsl:apply-templates select="bit" mode="bittostring"/> + <xsl:text> </xsl:text> +</xsl:template> + + <xsl:template match="bit" mode="bittostring"> + <xsl:if test="not (@mask)"> + <xsl:text> buffer.append(" .</xsl:text> + <xsl:value-of select="recutil:getFieldName(@name,20)"/> + <xsl:text> = ").append(is</xsl:text> + <xsl:value-of select="recutil:getFieldName1stCap(@name,0)"/> + <xsl:text>()).append('\n'); </xsl:text> + </xsl:if> + <xsl:if test="@mask"> + <xsl:text> buffer.append(" .</xsl:text> + <xsl:value-of select="recutil:getFieldName(@name,20)"/> + <xsl:text> = ").append(get</xsl:text> + <xsl:value-of select="recutil:getFieldName1stCap(@name,0)"/> + <xsl:text>()).append('\n'); </xsl:text> + </xsl:if> + </xsl:template> + +<xsl:template match="author"> + * @author <xsl:value-of select="."/> +</xsl:template> + +</xsl:stylesheet> diff --git a/src/records/definitions/subrecord_test.xsl b/src/records/definitions/subrecord_test.xsl new file mode 100644 index 0000000000..a98b77dac1 --- /dev/null +++ b/src/records/definitions/subrecord_test.xsl @@ -0,0 +1,155 @@ +<xsl:stylesheet version="1.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:recutil="org.apache.poi.generator.RecordUtil" + xmlns:field="org.apache.poi.generator.FieldIterator" + xmlns:java="java" > + +<xsl:template match="record"> +/* ==================================================================== + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2003 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Apache" and "Apache Software Foundation" and + * "Apache POI" must not be used to endorse or promote products + * derived from this software without prior written permission. For + * written permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * "Apache POI", nor may "Apache" appear in their name, without + * prior written permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + */ + +<xsl:if test="@package"> +package <xsl:value-of select="@package"/>; +</xsl:if> + +import junit.framework.TestCase; + +/** + * Tests the serialization and deserialization of the <xsl:value-of select="@name"/>SubRecord + * class works correctly. Test data taken directly from a real + * Excel file. + * +<xsl:apply-templates select="author"/> + */ +public class Test<xsl:value-of select="@name"/>SubRecord + extends TestCase +{ + byte[] data = new byte[] { + <xsl:value-of select="recutil:getByteArrayString(testdata)"/> + }; + + public Test<xsl:value-of select="@name"/>SubRecord(String name) + { + super(name); + } + + public void testLoad() + throws Exception + { + <xsl:value-of select="@name"/>SubRecord record = new <xsl:value-of select="@name"/>SubRecord((short)<xsl:value-of select="@id"/>, (short)data.length, data); + +<xsl:for-each select="//fields/field"> + <xsl:choose><xsl:when test="@type='string' or @type='hbstring'"> + assertEquals( "<xsl:value-of select="./testresult/@value"/>", record.get<xsl:value-of select="recutil:getFieldName1stCap(@name,0)"/>()); +</xsl:when><xsl:when test="@size='1'"> + assertEquals( (byte)<xsl:value-of select="./testresult/@value"/>, record.get<xsl:value-of select="recutil:getFieldName1stCap(@name,0)"/>()); +</xsl:when><xsl:when test="@size='2'"> + assertEquals( (short)<xsl:value-of select="./testresult/@value"/>, record.get<xsl:value-of select="recutil:getFieldName1stCap(@name,0)"/>()); +</xsl:when><xsl:when test="@size='4'"> + assertEquals( (int)<xsl:value-of select="./testresult/@value"/>, record.get<xsl:value-of select="recutil:getFieldName1stCap(@name,0)"/>()); +</xsl:when><xsl:otherwise> + assertEquals( (double)<xsl:value-of select="./testresult/@value"/>, record.get<xsl:value-of select="recutil:getFieldName1stCap(@name,0)"/>()); +</xsl:otherwise></xsl:choose> +<xsl:apply-templates select="./bit" mode="get"/> +</xsl:for-each> + + assertEquals( <xsl:value-of select="./testsize"/>, record.getRecordSize() ); + + record.validateSid((short)<xsl:value-of select="@id"/>); + } + + public void testStore() + { + <xsl:value-of select="@name"/>SubRecord record = new <xsl:value-of select="@name"/>SubRecord(); + + +<xsl:for-each select="//fields/field"> + <xsl:choose><xsl:when test="@type='string' or @type='hbstring'"> + record.set<xsl:value-of select="recutil:getFieldName1stCap(@name,0)"/>( "<xsl:value-of select="./testresult/@value"/>" ); +</xsl:when><xsl:when test="@size='1'"> + record.set<xsl:value-of select="recutil:getFieldName1stCap(@name,0)"/>( (byte)<xsl:value-of select="./testresult/@value"/> ); +</xsl:when><xsl:when test="@size='2'"> + record.set<xsl:value-of select="recutil:getFieldName1stCap(@name,0)"/>( (short)<xsl:value-of select="./testresult/@value"/> ); +</xsl:when><xsl:when test="@size='4'"> + record.set<xsl:value-of select="recutil:getFieldName1stCap(@name,0)"/>( (int)<xsl:value-of select="./testresult/@value"/> ); +</xsl:when><xsl:otherwise> + record.set<xsl:value-of select="recutil:getFieldName1stCap(@name,0)"/>( (double)<xsl:value-of select="./testresult/@value"/> ); +</xsl:otherwise></xsl:choose> + +<xsl:apply-templates select="./bit" mode="set"/> +</xsl:for-each> + + byte [] recordBytes = record.serialize(); + assertEquals(recordBytes.length - 4, data.length); + for (int i = 0; i < data.length; i++) + assertEquals("At offset " + i, data[i], recordBytes[i+4]); + } +} +</xsl:template> + +<xsl:template match="author"> + * @author <xsl:value-of select="."/> +</xsl:template> + +<xsl:template match="bit" mode="get"> +<xsl:text> </xsl:text>assertEquals( <xsl:value-of select="./testresult/@value"/>, record.is<xsl:value-of select="recutil:getFieldName1stCap(@name,0)"/>() );<xsl:text> +</xsl:text> +</xsl:template> + +<xsl:template match="bit" mode="set"> +<xsl:text> </xsl:text>record.set<xsl:value-of select="recutil:getFieldName1stCap(@name,0)"/>( <xsl:value-of select="./testresult/@value"/> );<xsl:text> +</xsl:text> +</xsl:template> + +</xsl:stylesheet> diff --git a/src/records/definitions/textobject_record.xml b/src/records/definitions/textobject_record.xml new file mode 100644 index 0000000000..6d4bc9c666 --- /dev/null +++ b/src/records/definitions/textobject_record.xml @@ -0,0 +1,41 @@ +<record id="0x1B6" name="TextObjectBase" excel-record-id="TXO" + package="org.apache.poi.hssf.record"> + <suffix>Record</suffix> + <extends>Record</extends> + <description>The TXO record is used to define the properties of a text box. It is followed + by two continue records unless there is no actual text. The first continue record contains + the text data and the next continue record contains the formatting runs.</description> + <author>Glen Stampoultzis (glens at apache.org)</author> + <fields> + <field type="int" size="2" name="options" description="option flags"> + <bit number="0" name="reserved1" description="reserved field"/> + <bit mask="0x000E" name="Horizontal text alignment"> + <const name="left aligned" value="1"/> + <const name="centered" value="2"/> + <const name="right aligned" value="3"/> + <const name="justified" value="4"/> + </bit> + <bit mask="0x0070" name="Vertical text alignment"> + <const name="top" value="1"/> + <const name="center" value="2"/> + <const name="bottom" value="3"/> + <const name="justify" value="4"/> + </bit> + <bit mask="0x0180" name="reserved2"/> + <bit number="9" name="text locked" description="Text has been locked"/> + <bit mask="0xFC00" name="reserved3"/> + </field> + <field type="int" size="2" name="text orientation" description="specifies whether the text is rotated"> + <const name="none" value="0"/> + <const name="top_to_bottom" value="1"/> + <const name="rot_right" value="2"/> + <const name="rot_left" value="3"/> + </field> + <field type="int" size="2" name="reserved4" description="reserved field"/> + <field type="int" size="2" name="reserved5" description="reserved field"/> + <field type="int" size="2" name="reserved6" description="reserved field"/> + <field type="int" size="2" name="text length" description="the left of the text string"/> + <field type="int" size="2" name="formatting run length" description="the length of the formatting runs."/> + <field type="int" size="4" name="reserved7" description="reserved field"/> + </fields> +</record> diff --git a/src/resources/fontmetrics/font_metrics.properties b/src/resources/fontmetrics/font_metrics.properties new file mode 100644 index 0000000000..24fb9918ea --- /dev/null +++ b/src/resources/fontmetrics/font_metrics.properties @@ -0,0 +1,912 @@ +#Font Metrics +#Sun Sep 07 21:54:47 EST 2003 +font.Lucida\ Sans\ Demibold\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Franklin\ Gothic\ Medium\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Jokerman.height=16 +font.Harrington.height=13 +font.Curlz\ MT.height=14 +font.Garamond\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Wide\ Latin.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Colonna\ MT.widths=5, 6, 5, 6, 6, 4, 7, 6, 4, 4, 6, 4, 9, 6, 6, 6, 6, 5, 5, 5, 6, 6, 8, 6, 6, 5, 8, 6, 7, 8, 6, 6, 7, 8, 4, 4, 7, 6, 9, 9, 7, 7, 7, 8, 5, 7, 8, 7, 9, 7, 8, 6, 6, 4, 5, 5, 6, 5, 6, 5, 5, 6, +font.Gill\ Sans\ MT\ Ext\ Condensed\ Bold.height=12 +font.Beesknees\ ITC.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Freestyle\ Script.height=13 +font.Palace\ Script\ MT.widths=4, 3, 3, 4, 3, 3, 4, 4, 3, 3, 4, 3, 6, 4, 3, 4, 3, 3, 3, 3, 4, 4, 5, 4, 4, 3, 8, 8, 6, 8, 5, 7, 6, 8, 6, 6, 8, 7, 8, 7, 5, 7, 6, 8, 6, 7, 5, 5, 6, 7, 5, 6, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, +font.Lucida\ Sans\ Demibold\ Roman.height=14 +font.Franklin\ Gothic\ Book\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Bitstream\ Vera\ Serif.widths=7, 7, 6, 7, 6, 4, 7, 7, 5, 5, 8, 5, 11, 7, 6, 6, 7, 6, 6, 5, 7, 6, 9, 6, 6, 6, 8, 9, 8, 9, 8, 8, 8, 10, 5, 5, 9, 7, 11, 9, 9, 8, 9, 8, 7, 7, 9, 7, 10, 7, 7, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Bitstream\ Vera\ Sans\ Mono.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Matisse\ ITC.height=14 +font.Arial\ Special\ G2\ Bold\ Italic.height=14 +font.Lucida\ Fax\ Demibold.height=14 +font.Arial\ Special\ G1\ Bold\ Italic.height=14 +font.Tempus\ Sans\ ITC.height=14 +font.Impact.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Algerian.widths=9, 7, 7, 7, 7, 7, 8, 7, 4, 6, 8, 6, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 9, 8, 7, 7, 9, 7, 7, 7, 7, 7, 8, 7, 4, 6, 8, 6, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 9, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Gill\ Sans\ MT\ Bold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Arial\ Narrow\ Special\ G1\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.OCR\ A\ Extended.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Cooper\ Black.widths=7, 7, 6, 8, 6, 5, 7, 8, 6, 6, 8, 6, 11, 8, 7, 8, 7, 6, 6, 6, 8, 7, 11, 7, 7, 6, 9, 8, 8, 9, 8, 8, 9, 9, 4, 7, 9, 7, 10, 9, 9, 8, 9, 9, 7, 8, 9, 9, 11, 9, 9, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Century\ Gothic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Bauhaus\ 93.widths=7, 7, 7, 7, 7, 4, 7, 6, 4, 5, 6, 4, 9, 6, 7, 7, 7, 4, 5, 4, 6, 5, 9, 6, 6, 6, 7, 7, 9, 8, 6, 6, 7, 7, 4, 4, 7, 4, 9, 7, 9, 7, 9, 7, 5, 6, 7, 6, 9, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Bernard\ MT\ Condensed.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Old\ English\ Text\ MT.widths=5, 5, 4, 5, 4, 3, 5, 5, 3, 3, 6, 3, 7, 5, 5, 5, 5, 4, 5, 3, 5, 5, 7, 6, 5, 4, 9, 10, 10, 10, 10, 10, 9, 10, 8, 8, 10, 9, 12, 11, 10, 10, 10, 9, 10, 9, 11, 9, 10, 8, 9, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, +font.Century\ Schoolbook.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Lucida\ Fax\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Arial\ Rounded\ MT\ Bold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Informal\ Roman.widths=5, 5, 4, 6, 5, 4, 5, 5, 3, 3, 5, 3, 7, 5, 4, 5, 5, 5, 4, 4, 5, 5, 7, 5, 5, 6, 7, 7, 6, 7, 6, 6, 7, 7, 4, 6, 6, 6, 8, 7, 6, 6, 6, 6, 5, 7, 7, 7, 9, 7, 7, 7, 5, 4, 6, 6, 6, 7, 6, 6, 6, 6, +font.Lucida\ Sans\ Demibold.height=14 +font.Ravie.height=14 +font.Perpetua.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.MT\ Extra.height=13 +font.Bitstream\ Vera\ Sans\ Mono\ Bold\ Oblique.height=14 +font.Book\ Antiqua\ Bold\ Italic.height=14 +font.Eras\ Medium\ ITC.height=12 +font.Rockwell\ Bold.height=14 +font.Snap\ ITC.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.dialog.italic.widths=7, 7, 6, 7, 7, 4, 7, 7, 4, 3, 6, 4, 8, 7, 7, 7, 7, 4, 6, 4, 7, 6, 9, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 3, 6, 8, 7, 9, 8, 9, 8, 9, 8, 8, 7, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Century\ Gothic\ Bold\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Franklin\ Gothic\ Demi\ Italic.height=14 +font.Franklin\ Gothic\ Book.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Arial\ Special\ G1\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Tw\ Cen\ MT\ Bold\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Desdemona.height=12 +font.Franklin\ Gothic\ Demi\ Cond.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Wingdings\ 3.height=13 +font.High\ Tower\ Text.height=13 +font.Edwardian\ Script\ ITC.widths=4, 4, 3, 4, 3, 2, 4, 4, 3, 3, 4, 3, 6, 5, 4, 4, 4, 4, 3, 3, 4, 4, 5, 4, 4, 4, 10, 10, 9, 9, 9, 8, 8, 10, 7, 7, 10, 9, 11, 10, 9, 9, 8, 10, 8, 7, 10, 8, 10, 11, 10, 8, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, +font.Gigi.widths=6, 5, 5, 5, 6, 5, 6, 5, 5, 4, 6, 6, 9, 6, 4, 6, 5, 5, 4, 5, 6, 5, 8, 6, 6, 4, 10, 8, 7, 10, 5, 9, 7, 9, 6, 6, 9, 8, 11, 9, 6, 8, 8, 7, 6, 7, 8, 9, 13, 9, 9, 7, 5, 5, 6, 5, 6, 5, 5, 5, 5, 6, +font.Engravers\ MT.height=13 +font.Californian\ FB\ Bold.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Lucida\ Calligraphy\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Arial\ Special\ G1\ Italic.height=14 +font.Book\ Antiqua\ Bold\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Franklin\ Gothic\ Heavy.widths=7, 7, 6, 7, 7, 5, 7, 7, 4, 4, 7, 4, 10, 7, 7, 7, 7, 5, 6, 5, 7, 6, 8, 7, 6, 6, 7, 7, 7, 8, 7, 6, 8, 8, 4, 5, 8, 6, 9, 8, 8, 7, 8, 8, 7, 6, 8, 7, 10, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Lucida\ Sans\ Typewriter\ Regular.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Ransom\ Bold.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.High\ Tower\ Text\ Italic.height=14 +font.dialog.bold.height=14 +font.Times\ New\ Roman\ Special\ G2\ Bold\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Trebuchet\ MS\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Times\ New\ Roman\ Special\ G1\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Gill\ Sans\ Ultra\ Bold\ Condensed.widths=5, 5, 4, 5, 5, 4, 5, 5, 3, 3, 5, 3, 8, 5, 5, 5, 5, 5, 4, 4, 5, 5, 7, 6, 5, 4, 7, 6, 5, 6, 5, 5, 6, 7, 3, 4, 6, 5, 7, 6, 6, 6, 6, 6, 5, 5, 6, 6, 8, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, +font.Monotype\ Sorts\ 2.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Rockwell\ Condensed.height=13 +font.Bell\ MT.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Times\ New\ Roman\ Special\ G2\ Bold\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Times\ New\ Roman\ Special\ G1\ Bold\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Gill\ Sans\ Ultra\ Bold.height=13 +font.American\ Uncial.height=13 +font.Kino\ MT.widths=5, 5, 5, 5, 5, 4, 5, 5, 3, 3, 5, 3, 7, 5, 5, 5, 5, 4, 4, 4, 5, 5, 7, 4, 5, 4, 6, 5, 6, 6, 5, 5, 6, 6, 4, 5, 5, 5, 8, 6, 7, 5, 7, 5, 5, 5, 6, 6, 7, 5, 6, 4, 7, 4, 5, 5, 5, 5, 5, 4, 5, 5, +font.Wingdings.height=12 +font.Mistral.widths=5, 4, 4, 5, 3, 3, 4, 5, 4, 4, 5, 4, 7, 6, 5, 4, 4, 4, 4, 4, 5, 4, 6, 5, 5, 4, 5, 6, 6, 6, 5, 5, 6, 6, 5, 4, 6, 4, 7, 6, 6, 5, 6, 6, 4, 4, 6, 5, 7, 5, 5, 5, 6, 5, 5, 5, 5, 5, 5, 4, 5, 5, +font.Bitstream\ Vera\ Sans\ Bold\ Oblique.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Harrington.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Lucida\ Console.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Bookshelf\ Symbol\ 1.widths=5, 5, 5, 7, 7, 9, 7, 7, 3, 3, 4, 3, 10, 5, 7, 9, 9, 4, 6, 4, 9, 6, 6, 4, 4, 5, 8, 9, 6, 7, 9, 9, 7, 8, 4, 9, 5, 3, 10, 7, 9, 9, 9, 4, 6, 4, 9, 9, 9, 9, 9, 6, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, +font.Lucida\ Fax\ Demibold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Eras\ Demi\ ITC.height=12 +font.Calisto\ MT.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Blackadder\ ITC.height=13 +font.Arial\ Narrow\ Special\ G2\ Italic.height=14 +font.Century\ Gothic.widths=7, 7, 6, 7, 6, 3, 7, 6, 2, 3, 6, 2, 10, 6, 6, 7, 7, 3, 4, 3, 6, 6, 8, 6, 6, 5, 7, 6, 8, 7, 5, 5, 8, 7, 3, 5, 6, 4, 9, 7, 8, 6, 8, 6, 5, 4, 6, 7, 9, 7, 6, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, +font.Ransom\ Bold\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Monotype\ Sorts.widths=9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, +font.Vivaldi\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Lucida\ Sans\ Typewriter\ Bold\ Oblique.height=14 +font.Lucida\ Handwriting\ Italic.height=14 +font.Castellar.widths=9, 8, 9, 10, 7, 7, 9, 10, 5, 6, 9, 7, 12, 10, 10, 7, 10, 9, 6, 8, 10, 9, 12, 10, 9, 8, 9, 8, 9, 10, 7, 7, 9, 10, 5, 6, 9, 7, 12, 10, 10, 7, 10, 9, 6, 8, 10, 9, 12, 10, 9, 8, 8, 5, 6, 6, 7, 6, 7, 6, 7, 7, +font.Forte.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Tw\ Cen\ MT\ Medium.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Arial\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Arial\ Narrow\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Abadi\ MT\ Condensed.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Lucida\ Sans\ Regular.height=14 +font.Bradley\ Hand\ ITC.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Eurostile.height=10 +font.Perpetua\ Bold.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Rockwell\ Condensed.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Bookshelf\ Symbol\ 2.height=13 +font.Calisto\ MT\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.serif.plain.height=14 +font.Lucida\ Bright\ Regular.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Bell\ MT\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Tw\ Cen\ MT\ Condensed.widths=4, 4, 3, 4, 4, 3, 4, 4, 2, 2, 4, 2, 7, 4, 4, 4, 4, 3, 3, 3, 4, 4, 6, 4, 4, 3, 5, 5, 4, 5, 4, 4, 5, 5, 3, 4, 5, 4, 6, 5, 5, 4, 5, 5, 4, 4, 5, 5, 7, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, +font.dialog.bolditalic.height=14 +font.Palatino\ Linotype\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.dialoginput.italic.widths=7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Georgia\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Rockwell\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Franklin\ Gothic\ Demi.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Goudy\ Old\ Style\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Perpetua\ Titling\ MT\ Light.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Parchment.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Symbol.height=14 +font.Abadi\ MT\ Condensed.widths=5, 5, 5, 5, 5, 3, 5, 5, 3, 3, 5, 3, 7, 5, 5, 5, 5, 4, 5, 3, 5, 5, 7, 5, 5, 5, 5, 6, 5, 6, 6, 5, 6, 6, 3, 4, 5, 5, 7, 6, 6, 6, 6, 5, 5, 5, 6, 5, 7, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, +font.Palatino\ Linotype\ Bold\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Arial\ Special\ G1.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Verdana\ Ref.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Century\ Gothic\ Bold.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Vladimir\ Script.widths=5, 5, 4, 5, 4, 4, 5, 5, 4, 3, 5, 4, 6, 5, 5, 5, 5, 4, 4, 4, 5, 5, 6, 5, 5, 4, 9, 9, 7, 8, 7, 7, 8, 9, 7, 8, 7, 7, 10, 8, 7, 7, 7, 8, 7, 7, 8, 8, 10, 7, 8, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, +font.Franklin\ Gothic\ Demi\ Cond.widths=5, 5, 4, 5, 5, 3, 5, 5, 2, 2, 5, 2, 7, 5, 5, 5, 5, 3, 4, 3, 5, 4, 6, 4, 4, 4, 5, 6, 5, 6, 5, 4, 6, 6, 3, 3, 5, 4, 8, 6, 6, 5, 6, 5, 5, 4, 6, 5, 7, 5, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, +font.OCR\ A\ Extended.height=11 +font.Arial\ Narrow\ Bold.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Runic\ MT\ Condensed.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Wide\ Latin.widths=11, 11, 10, 11, 10, 8, 10, 12, 7, 7, 13, 7, 18, 12, 11, 11, 11, 10, 10, 7, 12, 12, 16, 13, 11, 10, 15, 15, 14, 16, 15, 14, 16, 17, 10, 11, 16, 14, 18, 15, 14, 15, 14, 16, 13, 13, 16, 14, 19, 15, 14, 13, 13, 10, 13, 12, 13, 12, 13, 11, 13, 13, +font.Arial\ Special\ G2\ Bold.height=14 +font.Lucida\ Console.height=11 +font.Lucida\ Sans\ Typewriter\ Regular.height=14 +font.Bookman\ Old\ Style\ Bold.height=14 +font.Copperplate\ Gothic\ Light.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Tw\ Cen\ MT\ Bold.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Trebuchet\ MS\ Bold.height=14 +font.Garamond\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Bitstream\ Vera\ Serif\ Bold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Viner\ Hand\ ITC.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Franklin\ Gothic\ Book.widths=6, 6, 6, 6, 6, 4, 6, 6, 3, 3, 6, 3, 9, 6, 6, 6, 6, 4, 6, 4, 6, 6, 8, 5, 5, 5, 6, 7, 7, 7, 6, 6, 7, 7, 4, 5, 7, 6, 9, 8, 7, 7, 7, 7, 7, 6, 7, 6, 9, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Bookman\ Old\ Style\ Bold\ Italic.height=14 +font.Ransom\ Bold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Pacmania.height=12 +font.Arial\ Narrow\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Lucida\ Fax\ Demibold\ Italic.height=14 +font.monospaced.italic.widths=7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Book\ Antiqua\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Lucida\ Sans\ Demibold\ Roman.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Arial.height=13 +font.Lucida\ Bright\ Demibold\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Arial\ Special\ G2.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Mistral.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Verdana\ Ref.height=14 +font.Papyrus.height=17 +font.Lucida\ Sans\ Unicode.widths=7, 7, 6, 7, 6, 5, 7, 7, 4, 4, 7, 4, 9, 7, 7, 7, 7, 6, 6, 5, 7, 7, 9, 7, 7, 7, 8, 7, 8, 8, 6, 6, 8, 8, 4, 4, 8, 6, 10, 8, 9, 7, 9, 7, 6, 7, 8, 8, 10, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Lucida\ Sans\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Arial\ Bold\ Italic.height=14 +font.Century\ Schoolbook\ Bold.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Times\ New\ Roman\ Special\ G2\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Arial\ Special\ G1.widths=8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, +font.Arial\ Narrow\ Special\ G1.widths=10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, +font.Marlett.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Century\ Gothic\ Italic.height=14 +font.Century\ Gothic\ Bold\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Georgia\ Bold.height=14 +font.Lucida\ Sans\ Typewriter\ Bold.height=14 +font.MS\ Outlook.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Lucida\ Fax\ Demibold\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Rockwell\ Bold\ Italic.height=14 +font.Parchment.widths=3, 3, 3, 3, 3, 2, 3, 3, 2, 2, 3, 2, 4, 3, 3, 3, 3, 3, 3, 2, 3, 3, 4, 3, 3, 2, 10, 9, 8, 8, 8, 7, 11, 9, 7, 7, 9, 9, 10, 10, 10, 9, 9, 8, 8, 8, 9, 8, 10, 8, 8, 9, 3, 2, 3, 3, 3, 3, 3, 3, 3, 3, +font.Palatino\ Linotype\ Bold\ Italic.height=14 +font.Verdana\ Bold\ Italic.height=14 +font.Juice\ ITC.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Lucida\ Sans\ Typewriter\ Oblique.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Times\ New\ Roman\ Special\ G2\ Bold.height=14 +font.Ransom.height=16 +font.Rockwell\ Condensed\ Bold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Californian\ FB\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Verdana\ Italic.height=14 +font.French\ Script\ MT.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Garamond\ Bold.height=14 +font.Lucida\ Calligraphy\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Arial\ Narrow\ Special\ G2\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Eras\ Light\ ITC.height=12 +font.Franklin\ Gothic\ Book\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Broadway.widths=8, 8, 7, 8, 7, 5, 7, 9, 5, 5, 8, 5, 13, 9, 7, 8, 8, 6, 6, 5, 9, 7, 10, 8, 7, 8, 9, 8, 8, 9, 8, 8, 9, 9, 5, 7, 8, 8, 10, 8, 9, 9, 9, 9, 7, 8, 9, 9, 11, 9, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Californian\ FB\ Bold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Courier\ New\ Bold\ Italic.height=14 +font.Marlett.widths=6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, +font.Trebuchet\ MS.widths=5, 6, 5, 6, 6, 4, 6, 6, 3, 4, 6, 3, 9, 6, 6, 6, 6, 5, 4, 4, 6, 6, 9, 6, 5, 5, 7, 6, 6, 6, 6, 6, 7, 7, 3, 5, 6, 6, 8, 7, 7, 6, 7, 6, 5, 6, 7, 7, 10, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, +font.Tw\ Cen\ MT\ Medium\ Italic.height=14 +font.Microsoft\ Sans\ Serif.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Brush\ Script\ MT\ Italic.height=14 +font.Baskerville\ Old\ Face.widths=5, 6, 5, 6, 6, 4, 5, 6, 3, 3, 6, 3, 9, 6, 6, 6, 6, 4, 5, 4, 6, 5, 7, 5, 5, 5, 8, 8, 8, 9, 7, 6, 9, 9, 5, 4, 8, 7, 10, 9, 9, 7, 9, 7, 6, 8, 9, 8, 12, 8, 8, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, +font.Edda.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Bitstream\ Vera\ Sans\ Bold.height=14 +font.Rage\ Italic.widths=5, 4, 4, 6, 4, 3, 5, 5, 4, 3, 5, 4, 7, 6, 5, 6, 6, 5, 4, 4, 6, 5, 6, 5, 6, 4, 7, 8, 6, 8, 7, 6, 6, 8, 5, 6, 8, 7, 10, 8, 7, 7, 6, 8, 7, 6, 8, 7, 9, 7, 8, 7, 6, 4, 6, 6, 6, 5, 6, 5, 6, 6, +font.Franklin\ Gothic\ Medium\ Italic.height=14 +font.Century\ Schoolbook\ Bold\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Braggadocio.widths=9, 9, 7, 9, 8, 6, 9, 9, 5, 5, 10, 5, 13, 9, 9, 9, 9, 8, 7, 6, 9, 8, 11, 8, 10, 9, 10, 12, 10, 11, 10, 10, 10, 12, 7, 8, 13, 10, 13, 10, 11, 11, 11, 12, 9, 11, 10, 11, 13, 10, 11, 12, 11, 7, 10, 11, 12, 11, 10, 11, 11, 11, +font.Wingdings\ 2.height=12 +font.Britannic\ Bold.height=11 +font.Bitstream\ Vera\ Sans\ Mono\ Bold\ Oblique.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Bookshelf\ Symbol\ 5.widths=9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, +font.Playbill.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Arial\ Narrow\ Bold\ Italic.height=14 +font.Georgia\ Ref.widths=7, 7, 6, 7, 6, 5, 7, 8, 5, 4, 7, 5, 11, 8, 6, 7, 6, 6, 6, 5, 8, 7, 9, 7, 7, 6, 9, 8, 7, 8, 8, 7, 8, 9, 5, 6, 8, 7, 10, 10, 8, 8, 8, 9, 7, 7, 9, 9, 11, 9, 9, 7, 7, 6, 7, 7, 7, 7, 7, 7, 7, 7, +font.Georgia.widths=7, 7, 5, 7, 6, 5, 6, 8, 5, 4, 7, 5, 11, 8, 6, 7, 6, 5, 5, 5, 8, 7, 8, 7, 7, 5, 8, 8, 7, 8, 7, 7, 8, 9, 5, 6, 8, 7, 10, 8, 8, 7, 8, 8, 6, 7, 8, 8, 11, 8, 8, 7, 7, 6, 6, 6, 6, 6, 7, 6, 7, 7, +font.Arial\ Black\ Italic.height=14 +font.Franklin\ Gothic\ Medium\ Cond.widths=6, 6, 5, 6, 5, 4, 5, 6, 3, 3, 5, 3, 8, 6, 6, 6, 6, 4, 5, 4, 6, 5, 7, 5, 5, 4, 6, 6, 6, 7, 6, 5, 7, 6, 3, 4, 6, 5, 8, 7, 7, 6, 7, 6, 6, 5, 6, 6, 8, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, +font.Bauhaus\ 93.height=15 +font.Old\ English\ Text\ MT.height=11 +font.Verdana\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Augsburger\ Initials.widths=8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, +font.monospaced.bold.height=14 +font.Bell\ MT.widths=5, 6, 5, 6, 5, 4, 6, 6, 4, 4, 6, 4, 8, 6, 6, 6, 6, 5, 5, 4, 6, 6, 8, 6, 6, 5, 8, 7, 7, 9, 8, 7, 8, 9, 5, 5, 8, 7, 10, 9, 8, 7, 8, 8, 6, 8, 9, 8, 11, 8, 8, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, +font.Rockwell\ Italic.height=14 +font.Lucida\ Sans\ Unicode.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Castellar.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Stencil.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Chiller.widths=4, 4, 4, 4, 4, 3, 4, 4, 3, 3, 4, 3, 6, 4, 4, 4, 4, 3, 4, 4, 4, 4, 5, 4, 5, 5, 6, 6, 6, 5, 5, 4, 5, 5, 4, 5, 6, 5, 7, 6, 6, 5, 6, 5, 4, 5, 5, 5, 7, 6, 5, 6, 6, 4, 5, 5, 5, 5, 5, 5, 6, 5, +font.Gill\ Sans\ MT\ Bold\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.dialog.italic.height=14 +font.Perpetua\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.ProFont.widths=8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, +font.dialog.bolditalic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Trebuchet\ MS\ Bold\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Arial\ Bold.height=14 +font.Californian\ FB\ Bold.height=14 +font.Rockwell\ Condensed\ Bold.height=14 +font.Georgia\ Bold\ Italic.height=14 +font.Trebuchet\ MS\ Bold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Arial\ Special\ G1\ Bold.height=14 +font.Harlow\ Solid\ Italic\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Poor\ Richard.widths=6, 6, 5, 6, 5, 4, 6, 6, 4, 3, 6, 4, 8, 6, 5, 6, 6, 4, 4, 4, 6, 6, 7, 5, 6, 4, 8, 7, 8, 8, 6, 6, 8, 8, 4, 5, 7, 6, 9, 8, 9, 6, 9, 7, 6, 6, 8, 8, 11, 7, 8, 6, 6, 4, 6, 6, 6, 6, 6, 6, 6, 6, +font.serif.bolditalic.widths=5, 5, 4, 5, 4, 3, 5, 6, 3, 3, 5, 3, 8, 6, 5, 5, 5, 4, 4, 3, 6, 4, 7, 5, 4, 4, 7, 7, 7, 7, 7, 7, 7, 8, 4, 5, 7, 6, 9, 7, 7, 6, 7, 7, 6, 6, 7, 7, 9, 7, 7, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, +font.Gill\ Sans\ Ultra\ Bold\ Condensed.height=13 +font.Verdana.widths=7, 7, 6, 7, 7, 5, 7, 7, 3, 4, 7, 3, 11, 7, 7, 7, 7, 5, 6, 5, 7, 7, 9, 7, 7, 6, 9, 8, 7, 9, 7, 7, 8, 8, 6, 6, 8, 6, 9, 8, 9, 8, 9, 8, 7, 6, 8, 8, 12, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Gill\ Sans\ Ultra\ Bold\ Condensed.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Times\ New\ Roman\ Special\ G2\ Bold\ Italic.height=14 +font.Courier\ New\ Italic.height=14 +font.Goudy\ Old\ Style.widths=5, 6, 5, 6, 5, 4, 5, 6, 4, 4, 6, 4, 8, 7, 6, 6, 6, 4, 4, 4, 6, 5, 7, 5, 5, 4, 8, 7, 8, 8, 7, 6, 9, 9, 4, 4, 8, 6, 10, 8, 9, 6, 9, 8, 6, 7, 9, 8, 11, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, +font.dialog.plain.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Times\ New\ Roman\ Bold.height=14 +font.Bitstream\ Vera\ Sans\ Mono\ Oblique.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Trebuchet\ MS.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Modern\ No.\ 20.widths=6, 6, 5, 6, 5, 5, 5, 7, 4, 4, 6, 4, 9, 6, 5, 6, 6, 5, 4, 4, 6, 5, 8, 6, 6, 5, 8, 8, 6, 8, 8, 7, 8, 9, 5, 6, 8, 8, 10, 8, 7, 7, 7, 8, 6, 7, 8, 8, 11, 8, 8, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, +font.Kino\ MT.height=12 +font.Viner\ Hand\ ITC.widths=7, 6, 5, 6, 5, 5, 6, 7, 5, 5, 8, 5, 10, 8, 5, 6, 7, 5, 4, 6, 8, 7, 9, 6, 7, 6, 9, 9, 8, 9, 8, 7, 7, 9, 5, 6, 10, 8, 10, 9, 6, 8, 9, 8, 8, 7, 8, 8, 12, 7, 8, 8, 5, 4, 8, 6, 7, 5, 6, 6, 6, 6, +font.Forte.widths=7, 6, 5, 7, 5, 5, 7, 6, 4, 4, 6, 6, 9, 6, 6, 7, 6, 4, 5, 4, 6, 5, 8, 6, 6, 5, 8, 8, 7, 8, 7, 6, 7, 9, 7, 6, 8, 5, 12, 9, 7, 7, 8, 7, 6, 6, 7, 6, 9, 8, 8, 7, 6, 5, 5, 6, 6, 6, 6, 6, 6, 6, +font.dialoginput.bold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Impact.height=14 +font.Showcard\ Gothic.widths=7, 8, 7, 10, 7, 6, 8, 9, 5, 6, 8, 6, 10, 9, 9, 8, 9, 8, 7, 7, 9, 7, 11, 8, 7, 7, 7, 8, 7, 10, 7, 6, 8, 9, 5, 6, 8, 6, 10, 9, 9, 8, 9, 8, 7, 7, 9, 7, 11, 8, 7, 7, 7, 5, 6, 6, 7, 6, 6, 6, 7, 7, +font.Felix\ Titling.widths=8, 7, 9, 9, 6, 6, 9, 8, 4, 4, 9, 6, 10, 9, 9, 6, 9, 9, 6, 7, 9, 8, 11, 8, 7, 8, 8, 7, 9, 9, 6, 6, 9, 8, 4, 4, 9, 6, 10, 9, 9, 6, 9, 9, 6, 7, 9, 8, 11, 8, 7, 8, 7, 4, 6, 7, 6, 7, 7, 6, 7, 7, +font.Arial\ Black.height=15 +font.Comic\ Sans\ MS\ Bold.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Stencil.height=11 +font.Gill\ Sans\ MT\ Condensed.height=13 +font.Tw\ Cen\ MT\ Medium\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.French\ Script\ MT.widths=4, 4, 4, 4, 4, 3, 5, 5, 3, 3, 5, 3, 6, 4, 4, 4, 4, 3, 4, 3, 4, 4, 5, 4, 4, 4, 6, 8, 6, 8, 6, 7, 6, 9, 6, 6, 9, 7, 11, 10, 7, 6, 7, 8, 7, 6, 8, 8, 9, 7, 8, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, +font.Symbol.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Book\ Antiqua\ Bold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Times\ New\ Roman\ Special\ G1.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Felix\ Titling.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Gill\ Sans\ MT.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Perpetua\ Bold\ Italic.height=14 +font.Arial\ Special\ G1\ Bold\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Tw\ Cen\ MT\ Condensed\ Extra\ Bold.height=12 +font.Century\ Schoolbook\ Bold\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Calisto\ MT.widths=4, 6, 4, 5, 4, 3, 5, 6, 3, 3, 5, 3, 9, 6, 6, 6, 5, 4, 4, 3, 6, 6, 8, 5, 5, 5, 8, 7, 8, 8, 6, 6, 9, 8, 3, 5, 8, 6, 10, 8, 9, 5, 9, 7, 6, 5, 8, 7, 11, 7, 8, 6, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, +font.dialoginput.bolditalic.widths=6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, +font.Pristina.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Times\ New\ Roman\ Special\ G1\ Bold.height=14 +font.serif.bold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Kristen\ ITC.height=15 +font.Lucida\ Bright\ Demibold.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Times\ New\ Roman\ Special\ G2.widths=8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, +font.Palatino\ Linotype.height=14 +font.dialoginput.plain.height=14 +font.Times\ New\ Roman\ Special\ G2.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Kunstler\ Script.height=12 +font.Book\ Antiqua.widths=5, 6, 4, 6, 5, 4, 6, 6, 3, 3, 6, 3, 9, 6, 6, 6, 6, 4, 4, 3, 6, 6, 8, 5, 6, 5, 8, 7, 7, 8, 6, 6, 8, 8, 4, 4, 8, 6, 10, 8, 8, 6, 8, 7, 6, 7, 8, 8, 10, 7, 7, 7, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, +font.Times\ New\ Roman.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Tw\ Cen\ MT\ Medium.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Mercurius\ Script\ MT\ Bold.height=12 +font.Tw\ Cen\ MT\ Condensed.height=12 +font.Monotype\ Sorts\ 2.widths=11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, +font.sansserif.bold.height=14 +font.Elephant\ Italic.height=14 +font.dialoginput.italic.height=14 +font.Tahoma\ Bold.height=14 +font.Rockwell\ Bold\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Arial\ Narrow.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Gill\ Sans\ MT\ Italic.height=14 +font.serif.bold.widths=5, 6, 4, 6, 4, 4, 5, 6, 3, 3, 5, 3, 8, 6, 5, 6, 6, 4, 4, 3, 6, 5, 7, 6, 5, 3, 7, 7, 7, 7, 7, 6, 8, 8, 4, 5, 8, 7, 9, 7, 8, 6, 9, 7, 6, 7, 7, 7, 10, 7, 7, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, +font.Baskerville\ Old\ Face.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Abadi\ MT\ Condensed.height=12 +font.Copperplate\ Gothic\ Bold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Snap\ ITC.widths=7, 7, 7, 7, 7, 7, 8, 7, 6, 5, 8, 5, 9, 7, 7, 7, 7, 7, 7, 7, 8, 8, 11, 9, 8, 7, 10, 9, 9, 9, 9, 9, 10, 10, 6, 9, 9, 8, 12, 10, 9, 9, 9, 9, 9, 8, 9, 9, 12, 10, 10, 9, 9, 6, 9, 9, 10, 9, 9, 9, 10, 9, +font.Rockwell\ Extra\ Bold.widths=8, 8, 7, 8, 7, 5, 8, 8, 4, 4, 7, 4, 12, 8, 8, 8, 8, 5, 6, 5, 8, 7, 10, 7, 7, 7, 8, 9, 8, 9, 8, 7, 8, 9, 5, 4, 9, 7, 11, 9, 9, 8, 9, 9, 6, 8, 7, 8, 11, 10, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Bradley\ Hand\ ITC.height=13 +font.Imprint\ MT\ Shadow.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Arial\ Special\ G2.height=16 +font.Arial\ Narrow\ Special\ G2.height=16 +font.Bitstream\ Vera\ Sans\ Oblique.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Elephant.widths=7, 7, 6, 7, 6, 5, 6, 7, 4, 4, 7, 4, 10, 8, 7, 7, 7, 6, 6, 5, 8, 6, 9, 6, 6, 6, 8, 10, 9, 10, 10, 9, 10, 11, 6, 8, 10, 9, 12, 10, 10, 9, 10, 10, 8, 9, 10, 8, 13, 9, 8, 9, 9, 6, 8, 8, 8, 8, 8, 7, 8, 8, +font.Eurostile\ Bold.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Gigi.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Imprint\ MT\ Shadow.height=13 +font.Monotype\ Corsiva.widths=5, 5, 4, 5, 4, 4, 6, 5, 3, 3, 5, 3, 7, 6, 5, 5, 5, 4, 4, 4, 6, 5, 8, 5, 5, 5, 7, 7, 6, 8, 7, 7, 7, 8, 5, 5, 8, 7, 9, 8, 7, 7, 7, 7, 6, 6, 8, 7, 10, 7, 7, 7, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, +font.Matisse\ ITC.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Bookman\ Old\ Style.widths=6, 6, 6, 6, 6, 5, 6, 7, 4, 4, 7, 4, 10, 7, 7, 7, 6, 5, 5, 5, 7, 6, 10, 6, 6, 6, 7, 8, 7, 8, 7, 7, 8, 8, 4, 7, 8, 7, 11, 8, 8, 7, 8, 8, 7, 8, 8, 8, 11, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Perpetua.height=13 +font.Franklin\ Gothic\ Book.height=13 +font.Gloucester\ MT\ Extra\ Condensed.widths=4, 5, 4, 5, 4, 3, 4, 5, 3, 3, 5, 3, 6, 5, 5, 5, 5, 4, 4, 3, 5, 4, 6, 5, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 3, 4, 5, 4, 7, 5, 5, 5, 5, 5, 5, 5, 5, 5, 7, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, +font.Ransom\ Bold\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Bitstream\ Vera\ Sans\ Mono.widths=6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, +font.Garamond\ Italic.height=14 +font.Colonna\ MT.height=12 +font.Gill\ Sans\ MT\ Ext\ Condensed\ Bold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Calisto\ MT\ Bold.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Edda.widths=9, 10, 9, 10, 8, 9, 9, 10, 5, 5, 9, 8, 13, 9, 8, 10, 9, 9, 8, 8, 9, 9, 14, 9, 10, 9, 9, 9, 9, 10, 9, 9, 10, 10, 5, 6, 10, 8, 11, 10, 10, 9, 10, 9, 9, 9, 10, 9, 12, 9, 8, 9, 9, 5, 8, 8, 8, 8, 8, 7, 8, 8, +font.Palace\ Script\ MT.height=10 +font.Bitstream\ Vera\ Serif.height=13 +font.Onyx.widths=5, 5, 5, 5, 5, 3, 5, 5, 3, 3, 5, 3, 7, 5, 5, 5, 5, 5, 4, 4, 5, 5, 7, 5, 5, 4, 5, 5, 5, 5, 4, 4, 5, 5, 3, 4, 5, 4, 7, 6, 5, 5, 5, 6, 5, 5, 5, 5, 7, 5, 5, 5, 5, 3, 4, 4, 5, 5, 5, 4, 5, 5, +font.Playbill.widths=4, 4, 3, 4, 3, 3, 3, 4, 2, 2, 4, 2, 5, 4, 3, 4, 4, 3, 3, 3, 4, 4, 5, 4, 4, 3, 4, 4, 4, 4, 4, 4, 4, 4, 3, 4, 4, 4, 6, 5, 4, 4, 4, 5, 4, 4, 4, 4, 5, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, +font.Bookman\ Old\ Style\ Bold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Vladimir\ Script.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Tempus\ Sans\ ITC.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Algerian.height=14 +font.Arial\ Narrow\ Special\ G1\ Italic.height=14 +font.Cooper\ Black.height=13 +font.Chiller.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Lucida\ Fax\ Italic.height=14 +font.Bitstream\ Vera\ Sans\ Bold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Informal\ Roman.height=13 +font.Kunstler\ Script.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.monospaced.plain.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Script\ MT\ Bold.widths=5, 4, 4, 5, 4, 4, 5, 5, 2, 4, 5, 3, 7, 5, 4, 6, 5, 4, 4, 3, 5, 4, 7, 6, 5, 5, 7, 8, 6, 8, 5, 6, 6, 9, 6, 5, 8, 6, 10, 9, 6, 7, 6, 8, 6, 7, 8, 7, 10, 7, 8, 6, 5, 4, 5, 5, 5, 5, 5, 5, 5, 5, +font.Juice\ ITC.widths=5, 5, 4, 5, 4, 4, 4, 5, 3, 3, 4, 3, 6, 5, 4, 5, 5, 4, 4, 4, 5, 4, 6, 4, 5, 4, 5, 4, 4, 4, 4, 4, 4, 5, 3, 4, 4, 4, 5, 5, 5, 4, 5, 4, 4, 4, 5, 5, 5, 5, 4, 4, 5, 3, 4, 4, 4, 4, 5, 4, 4, 5, +font.Calisto\ MT\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Georgia\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Tahoma.widths=6, 6, 5, 6, 6, 4, 6, 6, 3, 4, 7, 3, 9, 6, 6, 6, 6, 5, 5, 4, 6, 6, 9, 6, 6, 5, 8, 7, 7, 8, 6, 6, 7, 8, 5, 5, 7, 6, 9, 7, 8, 7, 8, 7, 6, 7, 7, 7, 10, 7, 7, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Arial\ Special\ G2\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Bookshelf\ Symbol\ 4.widths=9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, +font.Tw\ Cen\ MT\ Bold\ Italic.height=14 +font.High\ Tower\ Text\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Lucida\ Sans\ Demibold\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Enviro.widths=5, 5, 6, 5, 6, 4, 6, 5, 3, 3, 5, 4, 9, 5, 5, 5, 6, 4, 5, 4, 6, 5, 7, 4, 5, 6, 7, 8, 7, 7, 8, 6, 7, 7, 3, 7, 8, 8, 8, 7, 8, 8, 8, 8, 9, 7, 7, 7, 9, 6, 8, 7, 7, 3, 8, 8, 8, 8, 8, 8, 8, 8, +font.Edwardian\ Script\ ITC.height=13 +font.Gigi.height=15 +font.Abadi\ MT\ Condensed\ Light.widths=5, 5, 5, 5, 5, 3, 5, 5, 3, 3, 5, 3, 7, 5, 5, 5, 5, 4, 5, 3, 5, 5, 6, 5, 5, 5, 5, 6, 5, 6, 5, 5, 5, 6, 3, 4, 5, 5, 7, 6, 6, 6, 6, 6, 5, 5, 6, 5, 7, 6, 6, 5, 6, 5, 6, 6, 6, 6, 6, 6, 6, 6, +font.Goudy\ Old\ Style\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Franklin\ Gothic\ Heavy.height=13 +font.Stop.widths=7, 6, 6, 7, 6, 6, 7, 6, 4, 5, 6, 5, 9, 7, 8, 6, 8, 6, 6, 6, 7, 7, 9, 7, 7, 6, 7, 6, 6, 7, 6, 6, 7, 6, 4, 5, 6, 5, 9, 7, 8, 6, 8, 6, 6, 6, 7, 7, 9, 7, 7, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Andy\ Bold.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Haettenschweiler.widths=5, 5, 5, 5, 5, 4, 5, 5, 3, 3, 5, 3, 7, 5, 5, 5, 5, 5, 5, 4, 5, 5, 7, 5, 5, 5, 5, 5, 5, 5, 4, 4, 5, 5, 3, 5, 5, 4, 7, 5, 5, 5, 5, 5, 5, 5, 5, 5, 7, 6, 5, 5, 5, 3, 5, 5, 5, 5, 5, 5, 5, 5, +font.Maiandra\ GD.widths=5, 6, 5, 6, 5, 4, 5, 6, 3, 3, 6, 3, 9, 6, 6, 6, 6, 4, 4, 4, 6, 6, 9, 5, 6, 5, 8, 7, 7, 8, 6, 6, 8, 9, 4, 5, 7, 5, 10, 8, 9, 6, 9, 7, 5, 7, 8, 7, 11, 7, 7, 6, 6, 4, 6, 6, 6, 6, 6, 6, 6, 6, +font.Franklin\ Gothic\ Heavy\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.sansserif.bolditalic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.dialoginput.italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Ransom\ Bold.height=14 +font.Britannic\ Bold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Edwardian\ Script\ ITC.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Trebuchet\ MS\ Italic.height=14 +font.Californian\ FB.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Times\ New\ Roman\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Baskerville\ Old\ Face.height=11 +font.Times\ New\ Roman\ Special\ G1\ Bold\ Italic.height=14 +font.Californian\ FB\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Book\ Antiqua\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Bernard\ MT\ Condensed.widths=6, 6, 5, 6, 5, 4, 6, 6, 4, 4, 6, 4, 8, 6, 6, 6, 6, 5, 4, 4, 6, 5, 7, 5, 5, 5, 6, 6, 5, 6, 5, 5, 6, 7, 4, 4, 6, 5, 7, 6, 6, 6, 6, 6, 5, 6, 5, 6, 8, 6, 6, 5, 6, 5, 6, 6, 6, 6, 6, 6, 6, 6, +font.Lucida\ Bright\ Demibold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Gill\ Sans\ MT\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Bitstream\ Vera\ Sans\ Mono\ Oblique.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Freestyle\ Script.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Century\ Schoolbook.widths=7, 7, 5, 7, 6, 4, 6, 7, 4, 4, 7, 4, 10, 7, 6, 7, 7, 5, 6, 5, 7, 6, 9, 6, 6, 6, 8, 8, 8, 9, 8, 8, 9, 9, 5, 7, 9, 8, 10, 9, 9, 8, 9, 8, 7, 8, 9, 8, 11, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Times\ New\ Roman\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Lucida\ Sans\ Typewriter\ Oblique.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Mistral.height=11 +font.Bitstream\ Vera\ Sans\ Bold\ Oblique.height=14 +font.Arial\ Special\ G2\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Georgia\ Ref.height=13 +font.Bitstream\ Vera\ Sans\ Mono\ Bold.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Ransom.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Bookshelf\ Symbol\ 1.height=13 +font.Century\ Gothic\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Century\ Gothic.height=14 +font.Wingdings\ 2.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Gill\ Sans\ MT.widths=5, 6, 5, 6, 6, 3, 5, 6, 3, 3, 6, 3, 10, 6, 6, 6, 6, 4, 4, 4, 6, 5, 8, 6, 5, 5, 8, 7, 8, 8, 6, 6, 8, 8, 3, 3, 7, 6, 9, 8, 9, 7, 9, 7, 6, 7, 8, 7, 12, 8, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, +font.Ransom\ Bold\ Italic.height=14 +font.Times\ New\ Roman\ Special\ G1\ Bold\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Monotype\ Sorts.height=10 +font.Book\ Antiqua.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Gill\ Sans\ MT\ Bold.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Maiandra\ GD\ Demi\ Bold.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Franklin\ Gothic\ Heavy\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Lucida\ Sans\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Castellar.height=13 +font.sansserif.plain.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.OpenSymbol.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Tw\ Cen\ MT\ Condensed.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Times\ New\ Roman\ Special\ G1\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Gradl.widths=4, 4, 3, 4, 3, 2, 4, 4, 2, 2, 4, 2, 5, 4, 4, 4, 4, 3, 3, 3, 4, 3, 4, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 2, 4, 4, 4, 6, 4, 5, 4, 4, 4, 4, 4, 5, 4, 6, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, +font.Arial\ Narrow\ Bold\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Parade.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.ProFont.height=14 +font.Perpetua\ Bold.height=14 +font.Wingdings\ 3.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Calisto\ MT\ Italic.height=14 +font.Lucida\ Bright\ Regular.height=14 +font.Bell\ MT\ Italic.height=14 +font.Kristen\ ITC.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Lucida\ Sans\ Typewriter\ Bold\ Oblique.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Franklin\ Gothic\ Demi\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Rage\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Georgia\ Italic.height=14 +font.MS\ Outlook.widths=6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, +font.Goudy\ Old\ Style\ Italic.height=14 +font.Lucida\ Sans\ Typewriter\ Bold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Perpetua\ Titling\ MT\ Light.height=14 +font.Footlight\ MT\ Light.widths=6, 6, 5, 6, 5, 4, 6, 7, 4, 4, 6, 4, 9, 7, 6, 6, 6, 5, 5, 4, 7, 6, 9, 6, 6, 5, 7, 6, 7, 8, 6, 5, 8, 8, 4, 4, 7, 6, 10, 8, 8, 6, 8, 6, 5, 6, 8, 7, 11, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Augsburger\ Initials.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Courier\ New\ Bold\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.monospaced.bolditalic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Bookman\ Old\ Style\ Bold\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Century\ Gothic\ Bold.height=14 +font.Vladimir\ Script.height=13 +font.Franklin\ Gothic\ Demi\ Cond.height=13 +font.Century\ Schoolbook\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Arial\ Narrow\ Bold.height=14 +font.Times\ New\ Roman\ Special\ G1.widths=8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, +font.OpenSymbol.widths=6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, +font.Wide\ Latin.height=11 +font.Showcard\ Gothic.height=14 +font.Californian\ FB.widths=4, 6, 5, 5, 5, 4, 6, 5, 3, 3, 6, 3, 7, 5, 5, 6, 5, 5, 4, 4, 5, 5, 8, 5, 5, 5, 7, 6, 7, 7, 7, 6, 7, 8, 4, 4, 7, 6, 10, 8, 7, 6, 7, 7, 5, 6, 8, 7, 11, 7, 6, 7, 5, 4, 5, 5, 6, 5, 5, 5, 5, 5, +font.Tw\ Cen\ MT\ Bold.height=14 +font.Bitstream\ Vera\ Sans.widths=7, 7, 6, 7, 7, 4, 7, 7, 3, 3, 7, 3, 11, 7, 7, 7, 7, 5, 6, 5, 7, 7, 9, 7, 7, 5, 9, 7, 7, 8, 6, 6, 8, 8, 3, 3, 7, 6, 9, 8, 8, 7, 8, 8, 7, 7, 8, 9, 11, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Palatino\ Linotype\ Bold.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.sansserif.italic.widths=7, 7, 6, 7, 7, 4, 7, 7, 4, 3, 6, 4, 8, 7, 7, 7, 7, 4, 6, 4, 7, 6, 9, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 3, 6, 8, 7, 9, 8, 9, 8, 9, 8, 8, 7, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Footlight\ MT\ Light.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Verdana.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Arial\ Narrow\ Italic.height=14 +font.monospaced.italic.height=14 +font.Book\ Antiqua\ Italic.height=14 +font.Arial\ Narrow.widths=5, 5, 5, 5, 5, 3, 5, 5, 2, 2, 5, 2, 7, 5, 5, 5, 5, 4, 5, 3, 5, 5, 6, 5, 5, 4, 6, 6, 6, 6, 5, 5, 6, 6, 2, 5, 6, 5, 7, 6, 6, 5, 6, 6, 5, 5, 6, 5, 8, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, +font.Lucida\ Bright\ Demibold\ Italic.height=14 +font.Times\ New\ Roman.widths=5, 6, 4, 6, 4, 4, 5, 6, 3, 3, 5, 3, 8, 6, 5, 6, 6, 4, 4, 3, 6, 5, 7, 6, 5, 3, 7, 7, 7, 7, 7, 6, 8, 8, 4, 5, 8, 7, 9, 7, 8, 6, 9, 7, 6, 7, 7, 7, 10, 7, 7, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, +font.Arial.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Lucida\ Handwriting\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Times\ New\ Roman\ Bold\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Lucida\ Sans\ Unicode.height=16 +font.Ravie.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.serif.italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Andy\ Bold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Century\ Schoolbook\ Bold.height=14 +font.Times\ New\ Roman\ Special\ G2\ Italic.height=14 +font.Arial\ Special\ G1.height=16 +font.Arial\ Narrow\ Special\ G1.height=16 +font.Bookman\ Old\ Style\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Times\ New\ Roman\ Special\ G2.height=16 +font.Rockwell\ Bold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Century\ Gothic\ Bold\ Italic.height=14 +font.sansserif.plain.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Tw\ Cen\ MT\ Medium.height=14 +font.Parchment.height=12 +font.Bitstream\ Vera\ Serif\ Bold.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Informal\ Roman.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Lucida\ Fax\ Regular.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Ransom\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Arial\ Narrow\ Special\ G1\ Bold.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Showcard\ Gothic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.OCRB.widths=7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Jokerman.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Colonna\ MT.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Eras\ Light\ ITC.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Lucida\ Fax\ Regular.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Copperplate\ Gothic\ Bold.widths=7, 7, 7, 7, 6, 6, 7, 7, 4, 5, 7, 6, 9, 7, 7, 7, 7, 7, 6, 4, 7, 7, 9, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 9, 4, 6, 8, 7, 9, 9, 9, 8, 9, 8, 7, 6, 8, 7, 13, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Pepita\ MT.widths=5, 5, 4, 6, 4, 4, 6, 5, 3, 4, 5, 4, 7, 5, 4, 6, 5, 4, 4, 4, 5, 5, 8, 6, 5, 6, 10, 9, 8, 11, 6, 9, 10, 9, 5, 5, 11, 8, 12, 9, 9, 9, 12, 9, 9, 9, 10, 9, 12, 9, 9, 11, 5, 4, 6, 5, 7, 6, 5, 6, 6, 7, +font.Eras\ Bold\ ITC.widths=6, 7, 5, 7, 6, 4, 7, 7, 3, 3, 6, 3, 9, 7, 6, 7, 7, 4, 5, 4, 7, 6, 9, 6, 6, 5, 8, 7, 7, 8, 7, 6, 8, 8, 3, 5, 8, 6, 9, 8, 9, 6, 9, 7, 6, 6, 8, 8, 11, 7, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Franklin\ Gothic\ Demi.widths=5, 5, 5, 5, 5, 3, 6, 5, 3, 3, 5, 3, 8, 5, 5, 5, 5, 3, 5, 4, 5, 5, 7, 5, 4, 4, 6, 7, 6, 6, 6, 6, 6, 6, 3, 4, 6, 5, 9, 6, 6, 6, 6, 7, 6, 5, 7, 6, 9, 6, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, +font.Rockwell\ Extra\ Bold.height=13 +font.monospaced.plain.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Centaur.widths=5, 6, 5, 6, 5, 4, 5, 6, 3, 3, 6, 3, 8, 6, 6, 6, 6, 4, 4, 4, 6, 5, 7, 5, 5, 5, 7, 6, 7, 8, 7, 6, 8, 9, 5, 5, 7, 7, 10, 9, 8, 6, 8, 8, 6, 8, 8, 8, 11, 8, 8, 7, 6, 5, 6, 6, 6, 6, 6, 6, 6, 6, +font.Tw\ Cen\ MT\ Condensed\ Bold.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Harlow\ Solid\ Italic\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Lucida\ Calligraphy\ Italic.height=14 +font.Palatino\ Linotype\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Courier\ New\ Bold.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.dialoginput.bold.widths=6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, +font.Broadway.height=13 +font.Franklin\ Gothic\ Book\ Italic.height=14 +font.Marlett.height=10 +font.Trebuchet\ MS.height=13 +font.Arial\ Narrow\ Special\ G2\ Bold.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Webdings.widths=6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, +font.Monotype\ Corsiva.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.High\ Tower\ Text.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Gloucester\ MT\ Extra\ Condensed.height=13 +font.Rage\ Italic.height=14 +font.Abadi\ MT\ Condensed\ Extra\ Bold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Bookshelf\ Symbol\ 3.widths=7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Braggadocio.height=10 +font.Microsoft\ Sans\ Serif.widths=7, 7, 6, 7, 7, 4, 7, 7, 3, 3, 6, 3, 9, 7, 7, 7, 7, 4, 6, 4, 7, 7, 9, 6, 6, 6, 8, 8, 8, 9, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 6, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Curlz\ MT.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Arial\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Goudy\ Stout.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.sansserif.bolditalic.widths=6, 6, 6, 6, 6, 3, 6, 6, 3, 3, 6, 3, 9, 6, 6, 6, 6, 4, 6, 3, 6, 6, 8, 6, 6, 5, 7, 7, 7, 7, 7, 6, 8, 7, 3, 6, 7, 6, 9, 7, 8, 7, 8, 7, 7, 6, 7, 7, 9, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, +font.Bookshelf\ Symbol\ 5.height=13 +font.Edda.height=16 +font.Vivaldi\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Bookshelf\ Symbol\ 1.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Georgia.height=13 +font.Bell\ MT\ Bold.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Franklin\ Gothic\ Medium\ Cond.height=13 +font.Bookman\ Old\ Style.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Augsburger\ Initials.height=12 +font.Beesknees\ ITC.widths=7, 7, 6, 7, 6, 6, 7, 7, 4, 4, 7, 5, 8, 7, 8, 6, 8, 7, 5, 5, 7, 7, 9, 7, 7, 7, 9, 8, 7, 8, 7, 7, 8, 8, 5, 5, 8, 6, 10, 8, 9, 7, 9, 8, 6, 6, 8, 9, 10, 9, 8, 8, 9, 5, 7, 6, 7, 6, 8, 6, 7, 8, +font.Perpetua\ Titling\ MT\ Light.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Bell\ MT.height=12 +font.Arial\ Narrow\ Special\ G1\ Bold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Times\ New\ Roman\ Special\ G2\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Juice\ ITC.height=13 +font.Trebuchet\ MS\ Bold\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Arial\ Rounded\ MT\ Bold.widths=6, 6, 6, 6, 6, 3, 6, 6, 3, 3, 6, 3, 9, 6, 6, 6, 6, 4, 5, 4, 6, 5, 8, 5, 5, 5, 7, 7, 7, 7, 7, 6, 8, 8, 3, 6, 7, 6, 8, 8, 8, 7, 8, 7, 7, 6, 8, 7, 9, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, +font.Arial\ Special\ G2\ Italic.height=14 +font.Chiller.height=13 +font.Gill\ Sans\ MT\ Bold\ Italic.height=14 +font.Bookshelf\ Symbol\ 2.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Abadi\ MT\ Condensed\ Light.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Lucida\ Sans\ Demibold\ Italic.height=14 +font.Ransom\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Goudy\ Stout.widths=14, 13, 11, 12, 13, 13, 13, 16, 11, 12, 16, 13, 18, 13, 13, 12, 11, 15, 11, 14, 14, 13, 18, 14, 14, 14, 14, 13, 11, 12, 13, 13, 13, 16, 11, 12, 16, 13, 18, 13, 13, 12, 11, 15, 11, 14, 14, 13, 18, 14, 14, 14, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, +font.Trebuchet\ MS\ Bold\ Italic.height=14 +font.Elephant\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Arial\ Narrow\ Special\ G2\ Bold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Pacmania.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Broadway.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Pristina.widths=5, 5, 4, 5, 4, 3, 5, 5, 3, 2, 5, 2, 7, 5, 4, 5, 5, 4, 4, 3, 5, 5, 6, 5, 5, 4, 7, 8, 7, 8, 8, 7, 7, 9, 6, 6, 9, 6, 11, 8, 8, 7, 8, 8, 6, 7, 9, 8, 11, 8, 8, 6, 5, 4, 5, 5, 5, 5, 5, 5, 5, 5, +font.Courier\ New.widths=6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, +font.Abadi\ MT\ Condensed\ Extra\ Bold.widths=4, 5, 4, 5, 5, 3, 5, 5, 3, 3, 4, 3, 7, 5, 5, 5, 5, 3, 4, 3, 5, 4, 6, 4, 4, 4, 5, 5, 4, 5, 5, 4, 5, 5, 3, 3, 5, 4, 6, 5, 6, 5, 6, 5, 5, 5, 5, 5, 7, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, +font.Copperplate\ Gothic\ Light.widths=7, 7, 8, 8, 7, 7, 8, 8, 4, 6, 7, 7, 8, 8, 9, 7, 9, 7, 7, 6, 8, 7, 10, 7, 6, 6, 8, 9, 9, 9, 8, 8, 9, 9, 4, 7, 8, 8, 10, 9, 10, 8, 10, 9, 8, 8, 9, 8, 12, 8, 8, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, +font.dialog.plain.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Poor\ Richard.height=13 +font.Franklin\ Gothic\ Medium.widths=6, 6, 6, 6, 6, 4, 6, 6, 3, 3, 6, 3, 9, 6, 6, 6, 6, 4, 6, 4, 6, 5, 8, 5, 5, 5, 7, 7, 7, 8, 6, 6, 7, 7, 4, 5, 7, 6, 9, 8, 7, 7, 7, 7, 7, 6, 7, 6, 10, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.serif.bolditalic.height=14 +font.Bookshelf\ Symbol\ 3.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Comic\ Sans\ MS\ Bold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Lucida\ Bright\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Runic\ MT\ Condensed.widths=5, 5, 5, 5, 5, 3, 5, 5, 3, 3, 4, 3, 7, 5, 5, 5, 5, 5, 5, 3, 5, 5, 7, 5, 5, 4, 5, 6, 6, 6, 6, 4, 6, 6, 3, 5, 5, 6, 7, 6, 6, 6, 6, 5, 5, 5, 6, 5, 7, 5, 5, 5, 5, 4, 5, 4, 5, 4, 5, 5, 5, 5, +font.Perpetua\ Titling\ MT\ Bold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Gill\ Sans\ Ultra\ Bold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Verdana.height=14 +font.Goudy\ Old\ Style.height=13 +font.monospaced.bolditalic.widths=6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, +font.Goudy\ Old\ Style.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Bitstream\ Vera\ Sans\ Mono\ Oblique.height=14 +font.Modern\ No.\ 20.height=12 +font.Matura\ MT\ Script\ Capitals.widths=6, 6, 5, 6, 5, 5, 6, 6, 4, 4, 6, 4, 8, 6, 6, 6, 6, 5, 6, 4, 6, 6, 9, 6, 6, 6, 14, 12, 10, 14, 9, 13, 9, 9, 9, 13, 13, 13, 16, 15, 13, 10, 12, 11, 10, 10, 12, 13, 15, 15, 12, 14, 8, 5, 6, 5, 7, 6, 6, 6, 6, 6, +font.Viner\ Hand\ ITC.height=17 +font.Forte.height=15 +font.Placard\ Condensed.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Franklin\ Gothic\ Medium.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Perpetua\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Placard\ Condensed.widths=5, 5, 5, 5, 5, 3, 5, 5, 3, 3, 5, 3, 6, 5, 5, 5, 5, 3, 4, 3, 5, 4, 6, 5, 4, 3, 5, 5, 5, 5, 4, 4, 5, 5, 3, 3, 5, 4, 6, 5, 5, 5, 5, 5, 5, 4, 5, 5, 6, 5, 5, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, +font.Jokerman.widths=7, 7, 6, 7, 7, 4, 7, 8, 4, 4, 7, 4, 9, 7, 7, 7, 7, 6, 5, 5, 7, 6, 8, 7, 7, 7, 9, 8, 8, 8, 7, 6, 9, 8, 4, 7, 8, 8, 10, 10, 9, 8, 9, 8, 7, 8, 9, 8, 12, 9, 8, 8, 8, 5, 7, 8, 8, 7, 7, 7, 7, 8, +font.Franklin\ Gothic\ Heavy.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Arial\ Black.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Harrington.widths=6, 6, 6, 7, 6, 4, 6, 6, 4, 4, 6, 4, 8, 6, 7, 7, 6, 5, 6, 4, 7, 6, 8, 5, 7, 6, 8, 8, 7, 8, 8, 7, 8, 8, 4, 4, 7, 7, 9, 8, 8, 8, 8, 8, 7, 6, 8, 7, 10, 7, 7, 7, 7, 4, 6, 6, 7, 6, 6, 5, 6, 6, +font.Felix\ Titling.height=13 +font.Comic\ Sans\ MS\ Bold.height=14 +font.Papyrus.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Curlz\ MT.widths=5, 5, 5, 5, 5, 4, 5, 6, 3, 3, 5, 3, 8, 6, 6, 6, 6, 5, 4, 4, 6, 5, 8, 5, 5, 5, 7, 7, 6, 8, 6, 6, 7, 8, 4, 5, 7, 6, 9, 9, 8, 7, 8, 7, 6, 6, 8, 7, 10, 7, 8, 6, 6, 4, 5, 5, 6, 6, 6, 5, 5, 6, +font.Century\ Schoolbook\ Bold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Gill\ Sans\ MT\ Ext\ Condensed\ Bold.widths=3, 3, 2, 3, 3, 2, 3, 3, 1, 2, 3, 2, 4, 3, 3, 3, 3, 2, 2, 2, 3, 3, 4, 3, 3, 2, 3, 3, 3, 3, 2, 2, 3, 3, 2, 2, 3, 2, 4, 3, 3, 3, 3, 3, 2, 3, 3, 3, 5, 3, 3, 2, 3, 2, 2, 2, 3, 2, 3, 2, 3, 3, +font.French\ Script\ MT.height=13 +font.Tw\ Cen\ MT\ Condensed\ Bold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Freestyle\ Script.widths=3, 3, 4, 4, 4, 5, 6, 6, 2, 3, 5, 4, 4, 4, 4, 3, 5, 3, 4, 4, 5, 4, 6, 3, 4, 3, 5, 4, 5, 6, 5, 5, 5, 6, 4, 5, 6, 4, 6, 6, 5, 5, 5, 6, 5, 6, 6, 6, 7, 5, 5, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 4, +font.Lucida\ Sans\ Demibold\ Roman.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Bookshelf\ Symbol\ 4.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Goudy\ Old\ Style\ Bold.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Braggadocio.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Lucida\ Sans\ Regular.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Gloucester\ MT\ Extra\ Condensed.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Arial\ Special\ G2\ Bold\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Lucida\ Fax\ Demibold.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Arial\ Special\ G1\ Bold\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Perpetua\ Titling\ MT\ Bold.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Century\ Schoolbook\ Bold\ Italic.height=14 +font.Calisto\ MT.height=13 +font.Lucida\ Sans\ Demibold.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.dialoginput.bolditalic.height=14 +font.Poor\ Richard.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Ravie.widths=9, 9, 8, 9, 8, 8, 9, 9, 6, 8, 9, 5, 13, 9, 8, 9, 9, 9, 8, 9, 9, 9, 12, 9, 10, 9, 10, 9, 8, 9, 10, 9, 9, 10, 6, 9, 11, 9, 14, 9, 9, 9, 9, 12, 9, 10, 10, 11, 14, 10, 10, 10, 8, 6, 9, 9, 11, 9, 9, 8, 8, 9, +font.Arial\ Bold\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.MT\ Extra.widths=6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, +font.Bitstream\ Vera\ Sans\ Mono\ Bold\ Oblique.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Eras\ Medium\ ITC.widths=6, 7, 6, 7, 6, 4, 7, 7, 3, 3, 6, 3, 10, 7, 7, 7, 7, 5, 5, 4, 7, 6, 9, 6, 6, 5, 8, 7, 7, 8, 7, 6, 8, 8, 3, 5, 7, 6, 10, 9, 9, 6, 9, 7, 6, 6, 8, 8, 11, 7, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Lucida\ Bright\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Lucida\ Bright\ Demibold.height=14 +font.Pepita\ MT.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Bitstream\ Vera\ Sans.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Wingdings.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Bookshelf\ Symbol\ 5.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.dialoginput.bolditalic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Book\ Antiqua.height=13 +font.Franklin\ Gothic\ Demi\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Comic\ Sans\ MS.widths=6, 6, 5, 6, 6, 5, 5, 6, 3, 4, 5, 3, 8, 5, 5, 5, 5, 5, 5, 5, 5, 5, 7, 6, 6, 5, 7, 6, 6, 7, 6, 6, 7, 8, 5, 7, 6, 6, 9, 8, 8, 5, 9, 6, 7, 7, 7, 7, 10, 7, 6, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, +font.Desdemona.widths=6, 6, 6, 6, 6, 6, 7, 7, 3, 5, 6, 6, 8, 7, 8, 6, 7, 6, 6, 6, 7, 6, 7, 6, 5, 6, 6, 6, 6, 6, 6, 6, 7, 7, 3, 5, 6, 6, 8, 7, 8, 6, 7, 6, 6, 6, 7, 6, 7, 6, 5, 6, 5, 3, 5, 6, 5, 5, 5, 5, 5, 6, +font.sansserif.italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Wingdings\ 3.widths=6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, +font.High\ Tower\ Text.widths=5, 7, 5, 6, 5, 5, 6, 7, 4, 4, 6, 4, 10, 7, 6, 7, 6, 5, 6, 4, 7, 6, 9, 6, 6, 6, 9, 7, 9, 9, 7, 6, 9, 8, 4, 5, 8, 7, 10, 9, 10, 7, 10, 7, 7, 8, 9, 9, 11, 8, 8, 9, 6, 4, 5, 5, 6, 5, 6, 6, 6, 6, +font.Monotype\ Sorts\ 2.height=13 +font.Bookman\ Old\ Style\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Engravers\ MT.widths=10, 10, 10, 11, 9, 9, 11, 11, 6, 8, 11, 9, 12, 11, 11, 9, 11, 11, 9, 10, 11, 10, 13, 11, 11, 9, 10, 10, 10, 11, 9, 9, 11, 11, 6, 8, 11, 9, 12, 11, 11, 9, 11, 11, 9, 10, 11, 10, 13, 11, 11, 9, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Times\ New\ Roman\ Special\ G1\ Bold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Maiandra\ GD.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Arial\ Special\ G1\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Eurostile.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.serif.bold.height=14 +font.High\ Tower\ Text\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.dialog.bold.widths=6, 6, 6, 6, 6, 3, 6, 6, 3, 4, 6, 3, 9, 6, 6, 6, 6, 4, 6, 3, 6, 7, 9, 6, 5, 5, 7, 7, 7, 7, 7, 6, 8, 7, 3, 6, 7, 6, 9, 7, 8, 7, 8, 7, 7, 5, 7, 7, 9, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, +font.Book\ Antiqua\ Bold.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Snap\ ITC.height=14 +font.Trebuchet\ MS\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Rockwell\ Condensed.widths=5, 5, 4, 5, 4, 3, 5, 5, 3, 3, 5, 3, 8, 5, 5, 5, 5, 3, 4, 3, 5, 4, 6, 4, 4, 4, 6, 5, 5, 6, 5, 5, 5, 6, 4, 3, 6, 5, 7, 6, 6, 5, 5, 5, 4, 5, 5, 6, 7, 6, 6, 5, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, +font.Cooper\ Black.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Times\ New\ Roman\ Special\ G2\ Bold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Gill\ Sans\ Ultra\ Bold.widths=7, 8, 6, 8, 7, 6, 8, 8, 4, 5, 8, 4, 12, 8, 7, 8, 8, 7, 6, 6, 8, 7, 10, 8, 7, 7, 10, 9, 8, 9, 7, 7, 9, 10, 4, 6, 9, 7, 11, 9, 9, 8, 9, 9, 7, 7, 9, 8, 12, 10, 9, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, +font.Bitstream\ Vera\ Serif.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Bitstream\ Vera\ Sans\ Oblique.height=14 +font.Elephant.height=13 +font.Eurostile\ Bold.height=14 +font.American\ Uncial.widths=8, 7, 7, 9, 8, 8, 8, 8, 4, 6, 7, 5, 13, 8, 8, 8, 8, 8, 6, 7, 8, 8, 12, 9, 8, 8, 8, 7, 7, 9, 8, 8, 8, 8, 4, 6, 7, 5, 13, 8, 8, 8, 8, 8, 6, 7, 8, 8, 12, 9, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, +font.Arial\ Narrow\ Bold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Centaur.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Monotype\ Corsiva.height=12 +font.Bookman\ Old\ Style.height=13 +font.Wingdings.widths=6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, +font.Lucida\ Sans\ Demibold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Times\ New\ Roman\ Bold\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Gill\ Sans\ MT\ Condensed.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Bitstream\ Vera\ Sans\ Mono.height=13 +font.Times\ New\ Roman.height=13 +font.Arial\ Narrow\ Special\ G2\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Matura\ MT\ Script\ Capitals.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Calisto\ MT\ Bold.height=14 +font.Bitstream\ Vera\ Sans\ Mono\ Bold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.serif.italic.widths=6, 6, 5, 6, 5, 4, 6, 6, 4, 4, 5, 4, 8, 6, 6, 6, 6, 5, 5, 4, 6, 5, 8, 5, 5, 5, 7, 7, 8, 8, 7, 7, 8, 8, 4, 5, 8, 7, 9, 8, 8, 7, 8, 7, 6, 7, 8, 7, 10, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, +font.Bauhaus\ 93.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.dialog.bold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Onyx.height=11 +font.Playbill.height=11 +font.Tahoma\ Bold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Lucida\ Handwriting\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Tahoma.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.monospaced.italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Calisto\ MT\ Bold\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Georgia\ Bold\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Verdana\ Bold.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Georgia\ Bold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Kino\ MT.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.monospaced.plain.height=14 +font.Eurostile.widths=6, 6, 6, 6, 6, 4, 6, 6, 3, 3, 5, 3, 9, 6, 6, 6, 6, 5, 6, 4, 6, 5, 8, 5, 5, 5, 7, 7, 7, 8, 6, 6, 7, 8, 3, 6, 7, 6, 10, 8, 7, 7, 8, 7, 7, 6, 8, 7, 11, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Lucida\ Sans\ Regular.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Script\ MT\ Bold.height=13 +font.Tahoma.height=13 +font.Bookshelf\ Symbol\ 4.height=13 +font.dialog.bolditalic.widths=6, 6, 6, 6, 6, 3, 6, 6, 3, 3, 6, 3, 9, 6, 6, 6, 6, 4, 6, 3, 6, 6, 8, 6, 6, 5, 7, 7, 7, 7, 7, 6, 8, 7, 3, 6, 7, 6, 9, 7, 8, 7, 8, 7, 7, 6, 7, 7, 9, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, +font.Rockwell\ Extra\ Bold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Maiandra\ GD\ Demi\ Bold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Enviro.height=13 +font.Abadi\ MT\ Condensed\ Light.height=12 +font.Centaur.height=12 +font.Tw\ Cen\ MT\ Bold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Garamond.widths=5, 6, 5, 5, 5, 3, 5, 6, 3, 3, 5, 3, 8, 6, 5, 6, 5, 3, 4, 3, 6, 5, 7, 5, 5, 5, 7, 7, 7, 8, 7, 6, 7, 8, 4, 4, 7, 6, 9, 7, 8, 6, 8, 7, 5, 6, 7, 6, 9, 6, 7, 7, 5, 4, 5, 5, 5, 5, 5, 5, 5, 5, +font.Bell\ MT\ Bold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Maiandra\ GD\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Stop.height=9 +font.Andy\ Bold.height=14 +font.Palatino\ Linotype.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Symbol.widths=7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Courier\ New\ Bold.height=14 +font.Haettenschweiler.height=12 +font.Maiandra\ GD.height=13 +font.Franklin\ Gothic\ Heavy\ Italic.height=14 +font.dialoginput.bold.height=14 +font.OCR\ A\ Extended.widths=7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Verdana\ Bold\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.MT\ Extra.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Times\ New\ Roman\ Italic.height=14 +font.Script\ MT\ Bold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Georgia\ Ref.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Lucida\ Sans\ Typewriter\ Regular.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Bookman\ Old\ Style\ Bold.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.sansserif.bold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Trebuchet\ MS\ Bold.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Californian\ FB\ Italic.height=14 +font.Bernard\ MT\ Condensed.height=13 +font.Arial\ Special\ G1\ Bold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Comic\ Sans\ MS.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Parade.widths=6, 7, 6, 7, 6, 4, 6, 7, 4, 4, 6, 4, 9, 7, 6, 6, 7, 5, 5, 4, 7, 7, 9, 7, 8, 6, 8, 7, 7, 8, 7, 7, 8, 8, 4, 7, 7, 6, 10, 8, 8, 7, 7, 7, 7, 7, 8, 9, 11, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Bookman\ Old\ Style\ Bold\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Jester\ Regular.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Century\ Schoolbook.height=13 +font.Lucida\ Sans\ Typewriter\ Oblique.height=14 +font.Eurostile\ Bold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Bitstream\ Vera\ Sans\ Mono\ Bold.height=14 +font.Garamond\ Bold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Franklin\ Gothic\ Medium\ Cond.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.dialoginput.plain.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Verdana\ Ref.widths=8, 8, 6, 8, 8, 5, 8, 8, 4, 4, 8, 4, 12, 8, 8, 8, 8, 5, 7, 5, 8, 7, 8, 8, 7, 7, 8, 8, 9, 9, 7, 7, 9, 9, 6, 6, 8, 7, 10, 8, 10, 8, 10, 9, 8, 8, 9, 8, 12, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, +font.Papyrus.widths=6, 8, 6, 7, 7, 5, 6, 7, 4, 4, 6, 4, 10, 7, 7, 6, 7, 5, 5, 5, 7, 5, 6, 5, 6, 5, 11, 10, 11, 11, 11, 8, 11, 11, 4, 8, 10, 8, 11, 10, 13, 8, 12, 9, 11, 11, 11, 9, 12, 9, 9, 9, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.OCRB.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Matisse\ ITC.widths=5, 5, 6, 5, 5, 5, 5, 5, 3, 5, 7, 5, 12, 8, 5, 5, 5, 5, 5, 5, 6, 9, 10, 5, 5, 5, 5, 5, 4, 5, 4, 5, 5, 5, 3, 5, 6, 5, 9, 7, 5, 6, 6, 6, 4, 6, 5, 5, 7, 7, 8, 5, 6, 4, 5, 5, 5, 5, 5, 5, 5, 5, +font.Arial\ Bold\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Arial\ Special\ G2\ Bold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Tempus\ Sans\ ITC.widths=5, 6, 5, 6, 6, 4, 6, 6, 4, 4, 6, 4, 10, 7, 7, 6, 6, 4, 4, 5, 7, 6, 8, 6, 6, 5, 9, 6, 7, 8, 6, 6, 8, 9, 4, 5, 8, 6, 10, 9, 9, 7, 9, 7, 5, 6, 8, 8, 11, 7, 7, 7, 8, 5, 7, 6, 7, 6, 6, 6, 6, 6, +font.Gill\ Sans\ MT.height=13 +font.Arial\ Narrow\ Special\ G1\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Gill\ Sans\ MT\ Bold.height=14 +font.Century\ Gothic\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Onyx.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Modern\ No.\ 20.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Maiandra\ GD\ Demi\ Bold.height=14 +font.Garamond.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Tw\ Cen\ MT\ Condensed\ Extra\ Bold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Georgia\ Bold.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Lucida\ Sans\ Italic.height=14 +font.Bitstream\ Vera\ Sans\ Bold\ Oblique.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.serif.plain.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Rockwell\ Bold\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Times\ New\ Roman\ Special\ G1\ Italic.height=14 +font.Gradl.height=11 +font.Book\ Antiqua\ Bold\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Calisto\ MT\ Bold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Rockwell\ Bold.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Palatino\ Linotype\ Bold\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Goudy\ Stout.height=15 +font.Algerian.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Eras\ Bold\ ITC.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Stop.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Engravers\ MT.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Courier\ New\ Bold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Ransom.widths=6, 7, 5, 7, 5, 4, 3, 5, 5, 3, 6, 3, 6, 6, 7, 6, 5, 4, 4, 3, 6, 4, 8, 16, 4, 9, 6, 6, 8, 8, 4, 11, 7, 8, 5, 6, 8, 7, 6, 9, 6, 3, 9, 5, 7, 8, 5, 5, 11, 8, 5, 9, 8, 4, 4, 5, 4, 4, 3, 8, 10, 7, +font.Gill\ Sans\ MT\ Bold\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Arial\ Bold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Pristina.height=13 +font.Old\ English\ Text\ MT.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Courier\ New.height=12 +font.Arial\ Narrow\ Special\ G1.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Verdana\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Eras\ Medium\ ITC.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Garamond\ Bold.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.MS\ Outlook.height=12 +font.serif.bolditalic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Gradl.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Eras\ Light\ ITC.widths=6, 7, 6, 7, 6, 4, 7, 7, 3, 3, 5, 3, 9, 7, 7, 7, 7, 4, 5, 4, 7, 6, 9, 5, 5, 5, 7, 7, 7, 8, 6, 6, 8, 8, 3, 5, 6, 6, 9, 8, 9, 6, 9, 6, 5, 6, 8, 7, 11, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, +font.Palace\ Script\ MT.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Footlight\ MT\ Light.height=10 +font.Courier\ New\ Bold\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Tw\ Cen\ MT\ Medium\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Brush\ Script\ MT\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Century\ Schoolbook\ Italic.height=14 +font.Times\ New\ Roman\ Special\ G1.height=16 +font.OpenSymbol.height=14 +font.Bitstream\ Vera\ Sans\ Bold.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Placard\ Condensed.height=14 +font.Californian\ FB.height=13 +font.Bell\ MT\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Century\ Gothic\ Bold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Wingdings\ 2.widths=6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, +font.Britannic\ Bold.widths=5, 5, 5, 5, 5, 3, 5, 6, 3, 3, 5, 3, 8, 6, 5, 5, 5, 4, 5, 3, 6, 4, 7, 5, 5, 4, 5, 6, 6, 7, 5, 5, 6, 7, 3, 4, 6, 5, 7, 6, 6, 6, 6, 6, 5, 5, 6, 5, 8, 6, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, +font.dialog.italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Bitstream\ Vera\ Sans.height=13 +font.Arial\ Narrow\ Bold\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Palatino\ Linotype\ Bold.height=14 +font.Goudy\ Old\ Style\ Bold.height=14 +font.sansserif.italic.height=14 +font.Arial\ Black\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Perpetua\ Bold\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Enviro.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Perpetua\ Titling\ MT\ Bold.height=14 +font.Eras\ Demi\ ITC.widths=6, 6, 5, 6, 6, 3, 6, 6, 3, 3, 5, 3, 9, 6, 6, 6, 6, 4, 4, 4, 6, 5, 9, 6, 5, 5, 8, 7, 6, 8, 6, 6, 8, 8, 3, 5, 7, 5, 9, 8, 8, 6, 8, 6, 5, 6, 8, 7, 11, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, +font.Arial\ Narrow.height=13 +font.Blackadder\ ITC.widths=5, 5, 4, 5, 4, 4, 5, 5, 3, 4, 5, 3, 6, 5, 4, 5, 5, 4, 4, 4, 6, 5, 6, 5, 5, 5, 9, 9, 9, 9, 9, 7, 9, 10, 6, 7, 11, 8, 13, 9, 9, 8, 11, 10, 8, 6, 11, 10, 12, 11, 9, 10, 5, 4, 4, 4, 5, 5, 5, 5, 5, 5, +font.Times\ New\ Roman\ Bold\ Italic.height=14 +font.monospaced.bold.widths=6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, +font.monospaced.bold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Rockwell\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Lucida\ Sans\ Typewriter\ Bold\ Oblique.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Haettenschweiler.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.American\ Uncial.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Bookman\ Old\ Style\ Italic.height=14 +font.Calisto\ MT\ Bold\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.sansserif.plain.height=14 +font.Comic\ Sans\ MS.height=14 +font.Bitstream\ Vera\ Serif\ Bold.height=14 +font.Arial\ Bold.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Tw\ Cen\ MT\ Bold\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Rockwell\ Condensed\ Bold.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Georgia\ Bold\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Bookshelf\ Symbol\ 2.widths=5, 5, 5, 7, 7, 9, 7, 7, 3, 3, 4, 3, 10, 5, 9, 9, 9, 9, 6, 4, 7, 9, 6, 4, 4, 5, 7, 9, 6, 7, 7, 9, 7, 8, 9, 9, 5, 7, 10, 7, 9, 9, 9, 4, 6, 8, 8, 9, 9, 9, 9, 7, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, +font.serif.plain.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Arial\ Narrow\ Special\ G1\ Bold.height=14 +font.Arial\ Special\ G1\ Bold.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.OCRB.height=15 +font.Lucida\ Fax\ Regular.height=14 +font.Copperplate\ Gothic\ Bold.height=12 +font.Maiandra\ GD\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Arial\ Special\ G2\ Bold\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Brush\ Script\ MT\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Book\ Antiqua\ Bold.height=14 +font.Pepita\ MT.height=12 +font.Eras\ Bold\ ITC.height=13 +font.Franklin\ Gothic\ Demi.height=13 +font.Courier\ New\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Perpetua\ Bold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Tw\ Cen\ MT\ Condensed\ Bold.height=14 +font.Harlow\ Solid\ Italic\ Italic.height=14 +font.Century\ Schoolbook\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Courier\ New\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Times\ New\ Roman\ Bold.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Palatino\ Linotype\ Italic.height=14 +font.Monotype\ Sorts.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Lucida\ Fax\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Franklin\ Gothic\ Medium\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Impact.widths=6, 6, 6, 6, 6, 4, 6, 6, 4, 4, 6, 4, 8, 6, 6, 6, 6, 5, 6, 4, 6, 6, 8, 4, 5, 5, 6, 7, 7, 7, 5, 5, 7, 7, 4, 4, 6, 5, 8, 6, 6, 6, 6, 6, 6, 6, 6, 6, 8, 6, 6, 5, 6, 5, 6, 6, 6, 6, 6, 5, 6, 6, +font.Arial\ Special\ G2\ Bold.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Courier\ New.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Arial\ Narrow\ Special\ G2\ Bold.height=14 +font.Times\ New\ Roman\ Bold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Lucida\ Bright\ Demibold\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Webdings.height=10 +font.Arial\ Black.widths=8, 8, 8, 8, 8, 5, 8, 8, 4, 4, 8, 4, 11, 8, 8, 8, 8, 5, 7, 5, 8, 7, 10, 8, 7, 7, 9, 9, 9, 9, 8, 8, 9, 9, 5, 8, 9, 8, 10, 9, 9, 8, 9, 9, 8, 8, 9, 9, 11, 9, 9, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, +font.Lucida\ Console.widths=7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Elephant.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Stencil.widths=7, 8, 7, 7, 7, 7, 7, 9, 5, 6, 8, 6, 9, 8, 7, 7, 7, 8, 7, 7, 8, 7, 9, 7, 7, 6, 7, 8, 7, 7, 7, 7, 7, 9, 5, 6, 8, 6, 9, 8, 7, 7, 7, 8, 7, 7, 8, 7, 9, 7, 7, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Gill\ Sans\ MT\ Condensed.widths=4, 5, 4, 5, 4, 3, 4, 5, 3, 3, 4, 3, 6, 5, 4, 5, 5, 4, 4, 3, 5, 4, 5, 4, 4, 4, 5, 5, 4, 5, 5, 4, 5, 5, 3, 3, 5, 4, 6, 5, 5, 5, 5, 5, 4, 4, 5, 4, 7, 5, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, +font.Georgia.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Eras\ Demi\ ITC.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Bookshelf\ Symbol\ 3.height=14 +font.Microsoft\ Sans\ Serif.height=13 +font.Arial\ Italic.height=14 +font.Mercurius\ Script\ MT\ Bold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.sansserif.bolditalic.height=14 +font.serif.italic.height=14 +font.Vivaldi\ Italic.height=14 +font.Pacmania.widths=9, 8, 8, 8, 8, 8, 8, 8, 4, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 9, 8, 8, 8, 8, 8, 8, 8, 4, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 4, 8, 8, 8, 8, 8, 8, 8, 8, +font.Lucida\ Fax\ Demibold\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.ProFont.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Perpetua\ Bold\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Tw\ Cen\ MT\ Condensed\ Extra\ Bold.widths=5, 5, 3, 5, 4, 3, 5, 5, 3, 3, 5, 3, 7, 5, 5, 5, 5, 3, 4, 3, 5, 4, 6, 5, 4, 4, 5, 5, 4, 5, 4, 4, 6, 6, 3, 4, 5, 4, 7, 6, 6, 5, 6, 5, 4, 4, 6, 6, 8, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, +font.Arial.widths=6, 6, 6, 6, 6, 3, 6, 6, 3, 4, 6, 3, 9, 6, 6, 6, 6, 4, 6, 3, 6, 7, 9, 6, 5, 5, 7, 7, 7, 7, 7, 6, 8, 7, 3, 6, 7, 6, 9, 7, 8, 7, 8, 7, 7, 5, 7, 7, 9, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, +font.Bell\ MT\ Bold.height=14 +font.Calisto\ MT\ Bold\ Italic.height=14 +font.Times\ New\ Roman\ Special\ G1\ Bold.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Beesknees\ ITC.height=13 +font.Verdana\ Bold.height=14 +font.Kristen\ ITC.widths=7, 7, 7, 7, 7, 6, 6, 7, 4, 4, 7, 4, 9, 7, 7, 7, 7, 5, 6, 6, 7, 6, 9, 7, 5, 6, 10, 8, 9, 10, 8, 7, 9, 10, 5, 7, 9, 8, 12, 10, 10, 8, 10, 8, 7, 8, 10, 8, 11, 8, 8, 10, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, +font.Palatino\ Linotype.widths=5, 6, 5, 6, 5, 4, 6, 6, 3, 3, 6, 3, 9, 6, 6, 6, 6, 4, 4, 3, 6, 5, 8, 5, 5, 5, 8, 7, 7, 8, 6, 6, 8, 8, 4, 4, 8, 6, 10, 8, 8, 7, 8, 7, 6, 8, 8, 8, 10, 7, 6, 7, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, +font.dialoginput.plain.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Kunstler\ Script.widths=4, 4, 3, 4, 3, 3, 4, 5, 3, 3, 4, 3, 7, 5, 3, 5, 4, 3, 3, 3, 4, 4, 6, 4, 5, 4, 9, 10, 9, 10, 8, 8, 11, 9, 7, 6, 9, 9, 9, 9, 8, 8, 8, 10, 7, 8, 9, 8, 10, 8, 8, 8, 4, 4, 5, 4, 4, 4, 4, 4, 4, 4, +font.Lucida\ Sans\ Typewriter\ Bold.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Arial\ Rounded\ MT\ Bold.height=13 +font.Arial\ Black\ Italic.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Mercurius\ Script\ MT\ Bold.widths=6, 6, 4, 6, 4, 4, 6, 5, 4, 4, 5, 4, 8, 5, 5, 6, 6, 4, 4, 4, 6, 5, 8, 6, 6, 5, 8, 8, 7, 8, 7, 7, 7, 9, 5, 7, 8, 7, 11, 9, 7, 8, 7, 8, 6, 8, 9, 7, 11, 8, 8, 7, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, +font.Webdings.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Jester\ Regular.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.sansserif.bold.widths=6, 6, 6, 6, 6, 3, 6, 6, 3, 4, 6, 3, 9, 6, 6, 6, 6, 4, 6, 3, 6, 7, 9, 6, 5, 5, 7, 7, 7, 7, 7, 6, 8, 7, 3, 6, 7, 6, 9, 7, 8, 7, 8, 7, 7, 5, 7, 7, 9, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, +font.Desdemona.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Ransom\ Italic.height=14 +font.Elephant\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Verdana\ Bold\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Tahoma\ Bold.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Palatino\ Linotype\ Bold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Times\ New\ Roman\ Special\ G2\ Bold.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Garamond.height=12 +font.Gill\ Sans\ MT\ Italic.widths=7, 7, 7, 7, 7, 4, 7, 7, 4, 4, 5, 4, 10, 7, 7, 7, 7, 4, 5, 4, 7, 6, 8, 6, 6, 6, 8, 8, 8, 8, 8, 7, 9, 8, 4, 6, 8, 7, 10, 8, 9, 8, 9, 8, 8, 8, 8, 8, 10, 8, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, +font.Bitstream\ Vera\ Sans\ Oblique.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Maiandra\ GD\ Italic.height=14 +font.Abadi\ MT\ Condensed\ Extra\ Bold.height=12 +font.Copperplate\ Gothic\ Light.height=12 +font.Lucida\ Bright\ Regular.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Bradley\ Hand\ ITC.widths=6, 6, 5, 6, 5, 5, 7, 6, 4, 4, 7, 4, 10, 7, 5, 6, 7, 5, 5, 4, 7, 6, 8, 6, 7, 6, 8, 8, 7, 7, 7, 6, 8, 8, 4, 4, 8, 7, 10, 9, 8, 7, 8, 8, 8, 7, 8, 7, 11, 7, 8, 10, 6, 6, 7, 7, 7, 7, 7, 7, 6, 6, +font.dialog.plain.height=14 +font.Arial\ Special\ G2.widths=8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, +font.Franklin\ Gothic\ Medium.height=13 +font.Arial\ Narrow\ Special\ G2.widths=10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, +font.Lucida\ Bright\ Italic.height=14 +font.Runic\ MT\ Condensed.height=12 +font.Imprint\ MT\ Shadow.widths=5, 7, 5, 7, 5, 4, 6, 7, 4, 4, 6, 4, 10, 7, 6, 7, 7, 5, 5, 4, 7, 6, 9, 6, 6, 5, 8, 8, 8, 10, 8, 8, 9, 10, 5, 5, 9, 8, 10, 9, 9, 8, 9, 9, 7, 8, 9, 8, 11, 9, 8, 8, 6, 5, 6, 6, 6, 6, 6, 6, 6, 6, +font.monospaced.bolditalic.height=14 +font.Verdana\ Bold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Perpetua.widths=5, 6, 5, 6, 5, 3, 5, 6, 3, 3, 6, 3, 8, 6, 6, 6, 6, 4, 4, 3, 6, 5, 8, 6, 5, 5, 7, 6, 6, 7, 5, 5, 7, 8, 4, 4, 7, 5, 9, 7, 7, 5, 7, 7, 5, 6, 7, 6, 9, 7, 6, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, +font.Matura\ MT\ Script\ Capitals.height=14 +font.Parade.height=12 +font.Jester\ Regular.height=14 +font.Blackadder\ ITC.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Goudy\ Old\ Style\ Bold.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Perpetua\ Italic.height=14 +font.Arial\ Narrow\ Special\ G2.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + +font.Something.height=13 +font.Something.characters=a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +font.Something.widths=6, 6, 6, 6, 6, 3, 6, 6, 3, 4, 6, 3, 9, 6, 6, 6, 6, 4, 6, 3, 6, 7, 9, 6, 5, 5, 7, 7, 7, 7, 7, 6, 8, 7, 3, 6, 7, 6, 9, 7, 8, 7, 8, 7, 7, 5, 7, 7, 9, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, diff --git a/src/testcases/org/apache/poi/hssf/model/SheetTest.java b/src/testcases/org/apache/poi/hssf/model/SheetTest.java index 3f604e9cf5..1f4bfaf78b 100644 --- a/src/testcases/org/apache/poi/hssf/model/SheetTest.java +++ b/src/testcases/org/apache/poi/hssf/model/SheetTest.java @@ -3,19 +3,11 @@ package org.apache.poi.hssf.model; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; +import java.util.Iterator; import junit.framework.TestCase; -import org.apache.poi.hssf.record.BOFRecord; -import org.apache.poi.hssf.record.BlankRecord; -import org.apache.poi.hssf.record.ColumnInfoRecord; -import org.apache.poi.hssf.record.EOFRecord; -import org.apache.poi.hssf.record.FormulaRecord; -import org.apache.poi.hssf.record.LabelSSTRecord; -import org.apache.poi.hssf.record.MergeCellsRecord; -import org.apache.poi.hssf.record.RowRecord; -import org.apache.poi.hssf.record.SharedFormulaRecord; -import org.apache.poi.hssf.record.StringRecord; +import org.apache.poi.hssf.record.*; import org.apache.poi.hssf.record.aggregates.FormulaRecordAggregate; import org.apache.poi.hssf.record.aggregates.ValueRecordsAggregate; import org.apache.poi.hssf.record.formula.ExpPtg; @@ -257,8 +249,126 @@ public class SheetTest extends TestCase } + /** + * Make sure page break functionality works (in memory) + * + */ + public void testRowPageBreaks(){ + short colFrom = 0; + short colTo = 255; -} + Sheet sheet = Sheet.createSheet(); + sheet.setRowBreak(0, colFrom, colTo); + + assertTrue("no row break at 0", sheet.isRowBroken(0)); + assertEquals("1 row break available", 1, sheet.getNumRowBreaks()); + + sheet.setRowBreak(0, colFrom, colTo); + sheet.setRowBreak(0, colFrom, colTo); + + assertTrue("no row break at 0", sheet.isRowBroken(0)); + assertEquals("1 row break available", 1, sheet.getNumRowBreaks()); + + sheet.setRowBreak(10, colFrom, colTo); + sheet.setRowBreak(11, colFrom, colTo); + + assertTrue("no row break at 10", sheet.isRowBroken(10)); + assertTrue("no row break at 11", sheet.isRowBroken(11)); + assertEquals("3 row break available", 3, sheet.getNumRowBreaks()); + + + boolean is10 = false; + boolean is0 = false; + boolean is11 = false; + Iterator iterator = sheet.getRowBreaks(); + while (iterator.hasNext()) { + PageBreakRecord.Break breakItem = (PageBreakRecord.Break)iterator.next(); + int main = (int)breakItem.main; + if (main != 0 && main != 10 && main != 11) fail("Invalid page break"); + if (main == 0) is0 = true; + if (main == 10) is10= true; + if (main == 11) is11 = true; + } + + assertTrue("one of the breaks didnt make it", is0 && is10 && is11); + + sheet.removeRowBreak(11); + assertFalse("row should be removed", sheet.isRowBroken(11)); + + sheet.removeRowBreak(0); + assertFalse("row should be removed", sheet.isRowBroken(0)); + + sheet.removeRowBreak(10); + assertFalse("row should be removed", sheet.isRowBroken(10)); + + assertEquals("no more breaks", 0, sheet.getNumRowBreaks()); + + + } + + /** + * Make sure column pag breaks works properly (in-memory) + * + */ + public void testColPageBreaks(){ + short rowFrom = 0; + short rowTo = (short)65535; + + Sheet sheet = Sheet.createSheet(); + sheet.setColumnBreak((short)0, rowFrom, rowTo); + + assertTrue("no col break at 0", sheet.isColumnBroken((short)0)); + assertEquals("1 col break available", 1, sheet.getNumColumnBreaks()); + + sheet.setColumnBreak((short)0, rowFrom, rowTo); + + assertTrue("no col break at 0", sheet.isColumnBroken((short)0)); + assertEquals("1 col break available", 1, sheet.getNumColumnBreaks()); + + sheet.setColumnBreak((short)1, rowFrom, rowTo); + sheet.setColumnBreak((short)10, rowFrom, rowTo); + sheet.setColumnBreak((short)15, rowFrom, rowTo); + + assertTrue("no col break at 1", sheet.isColumnBroken((short)1)); + assertTrue("no col break at 10", sheet.isColumnBroken((short)10)); + assertTrue("no col break at 15", sheet.isColumnBroken((short)15)); + assertEquals("4 col break available", 4, sheet.getNumColumnBreaks()); + + boolean is10 = false; + boolean is0 = false; + boolean is1 = false; + boolean is15 = false; + + Iterator iterator = sheet.getColumnBreaks(); + while (iterator.hasNext()) { + PageBreakRecord.Break breakItem = (PageBreakRecord.Break)iterator.next(); + int main = (int)breakItem.main; + if (main != 0 && main != 1 && main != 10 && main != 15) fail("Invalid page break"); + if (main == 0) is0 = true; + if (main == 1) is1 = true; + if (main == 10) is10= true; + if (main == 15) is15 = true; + } + + assertTrue("one of the breaks didnt make it", is0 && is1 && is10 && is15); + + sheet.removeColumnBreak((short)15); + assertFalse("column break should not be there", sheet.isColumnBroken((short)15)); + + sheet.removeColumnBreak((short)0); + assertFalse("column break should not be there", sheet.isColumnBroken((short)0)); + + sheet.removeColumnBreak((short)1); + assertFalse("column break should not be there", sheet.isColumnBroken((short)1)); + + sheet.removeColumnBreak((short)10); + assertFalse("column break should not be there", sheet.isColumnBroken((short)10)); + + assertEquals("no more breaks", 0, sheet.getNumColumnBreaks()); + } + + +} diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestCloneSheet.java b/src/testcases/org/apache/poi/hssf/usermodel/TestCloneSheet.java index 6ccf29ec27..a82e0d5f37 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestCloneSheet.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestCloneSheet.java @@ -56,8 +56,6 @@ package org.apache.poi.hssf.usermodel; import junit.framework.TestCase; -import org.apache.poi.hssf.usermodel.HSSFSheet; -import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.hssf.util.Region; /** @@ -82,4 +80,23 @@ public class TestCloneSheet extends TestCase { catch(Exception e){e.printStackTrace();fail(e.getMessage());} } + /** + * Ensures that pagebreak cloning works properly + * + */ + public void testPageBreakClones() { + HSSFWorkbook b = new HSSFWorkbook(); + HSSFSheet s = b.createSheet("Test"); + s.setRowBreak(3); + s.setColumnBreak((short)6); + + HSSFSheet clone = b.cloneSheet(0); + assertTrue("Row 3 not broken", clone.isRowBroken(3)); + assertTrue("Column 6 not broken", clone.isColumnBroken((short)6)); + + s.removeRowBreak(3); + + assertTrue("Row 3 still should be broken", clone.isRowBroken(3)); + } + } diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFPalette.java b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFPalette.java index 788eeef0f3..61a3f1d861 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFPalette.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFPalette.java @@ -178,7 +178,21 @@ public class TestHSSFPalette extends TestCase c.compare(expectedColor, paletteColor); } } - + + public void testAddColor() throws Exception + { + try + { + HSSFColor hssfColor = hssfPalette.addColor((byte)10,(byte)10,(byte)10); + fail(); + } + catch ( RuntimeException e ) + { + // Failing because by default there are no colours left in the palette. + } + } + + private static interface ColorComparator { void compare(HSSFColor expected, HSSFColor palette); diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheet.java b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheet.java index 3779f3a781..647ed8f4af 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheet.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheet.java @@ -382,6 +382,54 @@ public class TestHSSFSheet assertEquals(sheet.isDisplayFormulas(), true); } + /** + * Make sure the excel file loads work + * + */ + public void testPageBreakFiles() throws Exception{ + FileInputStream fis = null; + HSSFWorkbook wb = null; + + String filename = System.getProperty("HSSF.testdata.path"); + + filename = filename + "/SimpleWithPageBreaks.xls"; + fis = new FileInputStream(filename); + wb = new HSSFWorkbook(fis); + fis.close(); + + HSSFSheet sheet = wb.getSheetAt(0); + assertNotNull(sheet); + + assertEquals("1 row page break", 1, sheet.getRowBreaks().length); + assertEquals("1 column page break", 1, sheet.getColumnBreaks().length); + + assertTrue("No row page break", sheet.isRowBroken(22)); + assertTrue("No column page break", sheet.isColumnBroken((short)4)); + + sheet.setRowBreak(10); + sheet.setColumnBreak((short)13); + + assertEquals("row breaks number", 2, sheet.getRowBreaks().length); + assertEquals("column breaks number", 2, sheet.getColumnBreaks().length); + + File tempFile = File.createTempFile("display", "testPagebreaks.xls"); + FileOutputStream stream = new FileOutputStream(tempFile); + wb.write(stream); + stream.close(); + + wb = new HSSFWorkbook(new FileInputStream(tempFile)); + sheet = wb.getSheetAt(0); + + assertTrue("No row page break", sheet.isRowBroken(22)); + assertTrue("No column page break", sheet.isColumnBroken((short)4)); + + + assertEquals("row breaks number", 2, sheet.getRowBreaks().length); + assertEquals("column breaks number", 2, sheet.getColumnBreaks().length); + + + } + public static void main(java.lang.String[] args) { junit.textui.TestRunner.run(TestHSSFSheet.class); } diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestSheetShiftRows.java b/src/testcases/org/apache/poi/hssf/usermodel/TestSheetShiftRows.java index e91003e4da..e65bac11e9 100755 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestSheetShiftRows.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestSheetShiftRows.java @@ -188,5 +188,22 @@ public class TestSheetShiftRows extends TestCase { s.createRow(3).createCell((short)0).setCellValue("TEST2"); s.shiftRows(0,4,1); } + + /** + * When shifting rows, the page breaks should go with it + * + */ + public void testShiftRowBreaks() + { + HSSFWorkbook b = new HSSFWorkbook(); + HSSFSheet s = b.createSheet(); + HSSFRow row = s.createRow(4); + row.createCell((short)0).setCellValue("test"); + s.setRowBreak(4); + + s.shiftRows(4, 4, 2); + assertTrue("Row number 6 should have a pagebreak", s.isRowBroken(6)); + } + } |