more work on rendering ppt slides

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@652298 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Yegor Kozlov 2008-04-30 06:29:11 +00:00
parent 598fbaef20
commit ee9a91198b
23 changed files with 631 additions and 175 deletions

View File

@ -369,5 +369,12 @@ public class AutoShapes {
}
};
shapes[ShapeTypes.StraightConnector1] = new ShapeOutline(){
public java.awt.Shape getOutline(Shape shape){
return new Line2D.Float(0, 0, 21600, 21600);
}
};
}
}

View File

@ -19,6 +19,7 @@ package org.apache.poi.hslf.model;
import org.apache.poi.ddf.*;
import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.POILogger;
import org.apache.poi.util.HexDump;
import java.awt.geom.*;
import java.util.ArrayList;
@ -185,10 +186,6 @@ public class Freeform extends AutoShape {
return null;
}
Rectangle2D bounds = getAnchor2D();
float right = (float)bounds.getX();
float bottom = (float)bounds.getY();
GeneralPath path = new GeneralPath();
int numPoints = verticesProp.getNumberOfElementsInArray();
int numSegments = segmentsProp.getNumberOfElementsInArray();
@ -199,8 +196,8 @@ public class Freeform extends AutoShape {
short x = LittleEndian.getShort(p, 0);
short y = LittleEndian.getShort(p, 2);
path.moveTo(
((float)x*POINT_DPI/MASTER_DPI + right),
((float)y*POINT_DPI/MASTER_DPI + bottom));
((float)x*POINT_DPI/MASTER_DPI),
((float)y*POINT_DPI/MASTER_DPI));
} else if (Arrays.equals(elem, SEGMENTINFO_CUBICTO) || Arrays.equals(elem, SEGMENTINFO_CUBICTO2)){
i++;
byte[] p1 = verticesProp.getElement(j++);
@ -213,9 +210,9 @@ public class Freeform extends AutoShape {
short x3 = LittleEndian.getShort(p3, 0);
short y3 = LittleEndian.getShort(p3, 2);
path.curveTo(
((float)x1*POINT_DPI/MASTER_DPI + right), ((float)y1*POINT_DPI/MASTER_DPI + bottom),
((float)x2*POINT_DPI/MASTER_DPI + right), ((float)y2*POINT_DPI/MASTER_DPI + bottom),
((float)x3*POINT_DPI/MASTER_DPI + right), ((float)y3*POINT_DPI/MASTER_DPI + bottom));
((float)x1*POINT_DPI/MASTER_DPI), ((float)y1*POINT_DPI/MASTER_DPI),
((float)x2*POINT_DPI/MASTER_DPI), ((float)y2*POINT_DPI/MASTER_DPI),
((float)x3*POINT_DPI/MASTER_DPI), ((float)y3*POINT_DPI/MASTER_DPI));
} else if (Arrays.equals(elem, SEGMENTINFO_LINETO)){
i++;
@ -226,18 +223,26 @@ public class Freeform extends AutoShape {
short x = LittleEndian.getShort(p, 0);
short y = LittleEndian.getShort(p, 2);
path.lineTo(
((float)x*POINT_DPI/MASTER_DPI + right), ((float)y*POINT_DPI/MASTER_DPI + bottom));
((float)x*POINT_DPI/MASTER_DPI), ((float)y*POINT_DPI/MASTER_DPI));
}
} else if (Arrays.equals(pnext, SEGMENTINFO_CLOSE)){
path.closePath();
}
}
}
return path;
}
public java.awt.Shape getOutline(){
return getPath();
GeneralPath path = getPath();
Rectangle2D anchor = getAnchor2D();
Rectangle2D bounds = path.getBounds2D();
AffineTransform at = new AffineTransform();
at.translate(anchor.getX(), anchor.getY());
at.scale(
anchor.getWidth()/bounds.getWidth(),
anchor.getHeight()/bounds.getHeight()
);
return at.createTransformedShape(path);
}
}

View File

@ -67,4 +67,21 @@ public abstract class MasterSheet extends Sheet {
}
return false;
}
/**
* Return placeholder by text type
*/
public TextShape getPlaceholder(int type){
Shape[] shape = getShapes();
for (int i = 0; i < shape.length; i++) {
if(shape[i] instanceof TextShape){
TextShape tx = (TextShape)shape[i];
TextRun run = tx.getTextRun();
if(run != null && run.getRunType() == type){
return tx;
}
}
}
return null;
}
}

View File

@ -28,6 +28,7 @@ import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.awt.*;
import java.awt.geom.Rectangle2D;
import java.awt.geom.AffineTransform;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
@ -244,6 +245,9 @@ public class Picture extends SimpleShape {
}
public void draw(Graphics2D graphics){
AffineTransform at = graphics.getTransform();
ShapePainter.paint(this, graphics);
PictureData data = getPictureData();
if (data instanceof Bitmap){
BufferedImage img = null;
@ -260,5 +264,6 @@ public class Picture extends SimpleShape {
} else {
logger.log(POILogger.WARN, "Rendering of metafiles is not yet supported. image.type: " + (data == null ? "NA" : data.getClass().getName()));
}
graphics.setTransform(at);
}
}

View File

@ -341,58 +341,7 @@ public abstract class Shape {
* @param sh - owning shape
*/
protected void afterInsert(Sheet sh){
PPDrawing ppdrawing = sh.getPPDrawing();
EscherContainerRecord dgContainer = (EscherContainerRecord) ppdrawing.getEscherRecords()[0];
EscherDgRecord dg = (EscherDgRecord) Shape.getEscherChild(dgContainer, EscherDgRecord.RECORD_ID);
int id = allocateShapeId(dg);
setShapeId(id);
}
/**
* Allocates new shape id for the new drawing group id.
*
* @param dg EscherDgRecord of the sheet that owns the shape being created
*
* @return a new shape id.
*/
protected int allocateShapeId(EscherDgRecord dg)
{
EscherDggRecord dgg = _sheet.getSlideShow().getDocumentRecord().getPPDrawingGroup().getEscherDggRecord();
if(dgg == null){
logger.log(POILogger.ERROR, "EscherDggRecord not found");
return 0;
}
dgg.setNumShapesSaved( dgg.getNumShapesSaved() + 1 );
// Add to existing cluster if space available
for (int i = 0; i < dgg.getFileIdClusters().length; i++)
{
EscherDggRecord.FileIdCluster c = dgg.getFileIdClusters()[i];
if (c.getDrawingGroupId() == dg.getDrawingGroupId() && c.getNumShapeIdsUsed() != 1024)
{
int result = c.getNumShapeIdsUsed() + (1024 * (i+1));
c.incrementShapeId();
dg.setNumShapes( dg.getNumShapes() + 1 );
dg.setLastMSOSPID( result );
if (result >= dgg.getShapeIdMax())
dgg.setShapeIdMax( result + 1 );
return result;
}
}
// Create new cluster
dgg.addCluster( dg.getDrawingGroupId(), 0 );
dgg.getFileIdClusters()[dgg.getFileIdClusters().length-1].incrementShapeId();
dg.setNumShapes( dg.getNumShapes() + 1 );
int result = (1024 * dgg.getFileIdClusters().length);
dg.setLastMSOSPID( result );
if (result >= dgg.getShapeIdMax())
dgg.setShapeIdMax( result + 1 );
return result;
}
/**

View File

@ -196,13 +196,8 @@ public class ShapeGroup extends Shape{
Sheet sheet = getSheet();
shape.setSheet(sheet);
shape.setShapeId(sheet.allocateShapeId());
shape.afterInsert(sheet);
if (shape instanceof TextShape) {
TextShape tbox = (TextShape) shape;
EscherTextboxWrapper txWrapper = tbox.getEscherTextboxWrapper();
if(txWrapper != null) getSheet().getPPDrawing().addTextboxWrapper(txWrapper);
}
}
/**
@ -277,20 +272,9 @@ public class ShapeGroup extends Shape{
}
public void draw(Graphics2D graphics){
Rectangle2D anchor = getAnchor2D();
Rectangle2D coords = getCoordinates();
//transform coordinates
AffineTransform at = graphics.getTransform();
/*
if(!anchor.equals(coords)){
graphics.scale(anchor.getWidth()/coords.getWidth(), anchor.getHeight()/coords.getHeight());
graphics.translate(
anchor.getX()*coords.getWidth()/anchor.getWidth() - coords.getX(),
anchor.getY()*coords.getHeight()/anchor.getHeight() - coords.getY());
}
*/
Shape[] sh = getShapes();
for (int i = 0; i < sh.length; i++) {
sh[i].draw(graphics);

View File

@ -18,12 +18,10 @@
package org.apache.poi.hslf.model;
import org.apache.poi.ddf.EscherContainerRecord;
import org.apache.poi.ddf.EscherDgRecord;
import org.apache.poi.ddf.EscherRecord;
import org.apache.poi.ddf.EscherSpRecord;
import org.apache.poi.ddf.*;
import org.apache.poi.hslf.record.*;
import org.apache.poi.hslf.usermodel.SlideShow;
import org.apache.poi.util.POILogger;
import java.util.ArrayList;
import java.util.Iterator;
@ -248,15 +246,47 @@ public abstract class Sheet {
spgr.addChildRecord(shape.getSpContainer());
shape.setSheet(this);
shape.setShapeId(allocateShapeId());
shape.afterInsert(this);
}
// If it's a TextShape, we need to tell the PPDrawing, as it has to
// track TextboxWrappers specially
if (shape instanceof TextShape) {
TextShape tbox = (TextShape) shape;
EscherTextboxWrapper txWrapper = tbox.getEscherTextboxWrapper();
if(txWrapper != null) ppdrawing.addTextboxWrapper(txWrapper);
/**
* Allocates new shape id for the new drawing group id.
*
* @return a new shape id.
*/
public int allocateShapeId()
{
EscherDggRecord dgg = _slideShow.getDocumentRecord().getPPDrawingGroup().getEscherDggRecord();
EscherDgRecord dg = _container.getPPDrawing().getEscherDgRecord();
dgg.setNumShapesSaved( dgg.getNumShapesSaved() + 1 );
// Add to existing cluster if space available
for (int i = 0; i < dgg.getFileIdClusters().length; i++)
{
EscherDggRecord.FileIdCluster c = dgg.getFileIdClusters()[i];
if (c.getDrawingGroupId() == dg.getDrawingGroupId() && c.getNumShapeIdsUsed() != 1024)
{
int result = c.getNumShapeIdsUsed() + (1024 * (i+1));
c.incrementShapeId();
dg.setNumShapes( dg.getNumShapes() + 1 );
dg.setLastMSOSPID( result );
if (result >= dgg.getShapeIdMax())
dgg.setShapeIdMax( result + 1 );
return result;
}
}
// Create new cluster
dgg.addCluster( dg.getDrawingGroupId(), 0, false );
dgg.getFileIdClusters()[dgg.getFileIdClusters().length-1].incrementShapeId();
dg.setNumShapes( dg.getNumShapes() + 1 );
int result = (1024 * dgg.getFileIdClusters().length);
dg.setLastMSOSPID( result );
if (result >= dgg.getShapeIdMax())
dgg.setShapeIdMax( result + 1 );
return result;
}
/**
@ -284,6 +314,13 @@ public abstract class Sheet {
return lst.remove(shape.getSpContainer());
}
/**
* Called by SlideShow ater a new sheet is created
*/
public void onCreate(){
}
/**
* Return the master sheet .
*/

View File

@ -126,8 +126,8 @@ public class SimpleShape extends Shape {
EscherSimpleProperty p2 = (EscherSimpleProperty)getEscherProperty(opt, EscherProperties.LINESTYLE__NOLINEDRAWDASH);
int p2val = p2 == null ? 0 : p2.getPropertyValue();
Color clr = null;
if (p1 != null && (p2val & 0x8) != 0){
int rgb = p1.getPropertyValue();
if ((p2val & 0x8) != 0 || (p2val & 0x10) != 0){
int rgb = p1 == null ? 0 : p1.getPropertyValue();
if (rgb >= 0x8000000) {
int idx = rgb % 0x8000000;
if(getSheet() != null) {

View File

@ -21,12 +21,17 @@
package org.apache.poi.hslf.model;
import java.util.Vector;
import java.util.Iterator;
import java.awt.*;
import org.apache.poi.hslf.record.SlideAtom;
import org.apache.poi.hslf.record.TextHeaderAtom;
import org.apache.poi.hslf.record.ColorSchemeAtom;
import org.apache.poi.hslf.record.SlideListWithText.SlideAtomsSet;
import org.apache.poi.ddf.EscherDggRecord;
import org.apache.poi.ddf.EscherContainerRecord;
import org.apache.poi.ddf.EscherDgRecord;
import org.apache.poi.ddf.EscherSpRecord;
/**
* This class represents a slide in a PowerPoint Document. It allows
@ -126,6 +131,42 @@ public class Slide extends Sheet
_slideNo = newSlideNumber;
}
/**
* Called by SlideShow ater a new slide is created.
* <p>
* For Slide we need to do the following:
* <li> set id of the drawing group.
* <li> set shapeId for the container descriptor and background
* </p>
*/
public void onCreate(){
//initialize drawing group id
EscherDggRecord dgg = getSlideShow().getDocumentRecord().getPPDrawingGroup().getEscherDggRecord();
EscherContainerRecord dgContainer = (EscherContainerRecord)getSheetContainer().getPPDrawing().getEscherRecords()[0];
EscherDgRecord dg = (EscherDgRecord) Shape.getEscherChild(dgContainer, EscherDgRecord.RECORD_ID);
int dgId = dgg.getMaxDrawingGroupId() + 1;
dg.setOptions((short)(dgId << 4));
dgg.setDrawingsSaved(dgg.getDrawingsSaved() + 1);
for (Iterator it = dgContainer.getChildContainers().iterator(); it.hasNext(); ) {
EscherContainerRecord c = (EscherContainerRecord)it.next();
EscherSpRecord spr = null;
switch(c.getRecordId()){
case EscherContainerRecord.SPGR_CONTAINER:
EscherContainerRecord dc = (EscherContainerRecord)c.getChildRecords().get(0);
spr = dc.getChildById(EscherSpRecord.RECORD_ID);
break;
case EscherContainerRecord.SP_CONTAINER:
spr = c.getChildById(EscherSpRecord.RECORD_ID);
break;
}
if(spr != null) spr.setShapeId(allocateShapeId());
}
//PPT doen't increment the number of saved shapes for group descriptor and background
dg.setNumShapes(1);
}
/**
* Create a <code>TextBox</code> object that represents the slide's title.
*

View File

@ -92,6 +92,7 @@ public class SlideMaster extends MasterSheet {
} else {
switch (txtype) {
case TextHeaderAtom.CENTRE_BODY_TYPE:
case TextHeaderAtom.HALF_BODY_TYPE:
case TextHeaderAtom.QUARTER_BODY_TYPE:
txtype = TextHeaderAtom.BODY_TYPE;
break;

View File

@ -17,6 +17,7 @@
package org.apache.poi.hslf.model;
import org.apache.poi.hslf.usermodel.RichTextRun;
import org.apache.poi.hslf.record.TextRulerAtom;
import org.apache.poi.util.POILogger;
import org.apache.poi.util.POILogFactory;
@ -38,6 +39,13 @@ import java.util.ArrayList;
public class TextPainter {
protected POILogger logger = POILogFactory.getLogger(this.getClass());
/**
* Display unicode square if a bullet char can't be displayed,
* for example, if Wingdings font is used.
* TODO: map Wingdngs and Symbol to unicode Arial
*/
protected static final char DEFAULT_BULLET_CHAR = '\u25a0';
protected TextShape _shape;
public TextPainter(TextShape shape){
@ -49,6 +57,10 @@ public class TextPainter {
*/
public AttributedString getAttributedString(TextRun txrun){
String text = txrun.getText();
//TODO: properly process tabs
text = text.replace('\t', ' ');
text = text.replace((char)160, ' ');
AttributedString at = new AttributedString(text);
RichTextRun[] rt = txrun.getRichTextRuns();
for (int i = 0; i < rt.length; i++) {
@ -109,7 +121,24 @@ public class TextPainter {
}
float wrappingWidth = (float)anchor.getWidth() - _shape.getMarginLeft() - _shape.getMarginRight();
wrappingWidth -= rt.getTextOffset();
int bulletOffset = rt.getBulletOffset();
int textOffset = rt.getTextOffset();
int indent = rt.getIndentLevel();
TextRulerAtom ruler = run.getTextRuler();
if(ruler != null) {
int bullet_val = ruler.getBulletOffsets()[indent]*Shape.POINT_DPI/Shape.MASTER_DPI;
int text_val = ruler.getTextOffsets()[indent]*Shape.POINT_DPI/Shape.MASTER_DPI;
if(bullet_val > text_val){
int a = bullet_val;
bullet_val = text_val;
text_val = a;
}
if(bullet_val != 0 ) bulletOffset = bullet_val;
if(text_val != 0) textOffset = text_val;
}
wrappingWidth -= textOffset;
if (_shape.getWordWrap() == TextShape.WrapNone) {
wrappingWidth = _shape.getSheet().getSlideShow().getPageSize().width;
@ -141,8 +170,9 @@ public class TextPainter {
}
el._align = rt.getAlignment();
el._text = textLayout;
el._textOffset = rt.getTextOffset();
el.advance = textLayout.getAdvance();
el._textOffset = textOffset;
el._text = new AttributedString(it, startIndex, endIndex);
if (prStart){
int sp = rt.getSpaceBefore();
@ -182,13 +212,25 @@ public class TextPainter {
Color clr = rt.getBulletColor();
if (clr != null) bat.addAttribute(TextAttribute.FOREGROUND, clr);
else bat.addAttribute(TextAttribute.FOREGROUND, it.getAttribute(TextAttribute.FOREGROUND));
bat.addAttribute(TextAttribute.FAMILY, it.getAttribute(TextAttribute.FAMILY));
bat.addAttribute(TextAttribute.SIZE, it.getAttribute(TextAttribute.SIZE));
TextLayout bulletLayout = new TextLayout(bat.getIterator(), graphics.getFontRenderContext());
int fontIdx = rt.getBulletFont();
if(fontIdx == -1) fontIdx = rt.getFontIndex();
PPFont bulletFont = _shape.getSheet().getSlideShow().getFont(fontIdx);
bat.addAttribute(TextAttribute.FAMILY, bulletFont.getFontName());
int bulletSize = rt.getBulletSize();
int fontSize = rt.getFontSize();
if(bulletSize != -1) fontSize = Math.round(fontSize*bulletSize*0.01f);
bat.addAttribute(TextAttribute.SIZE, new Float(fontSize));
if(!new Font(bulletFont.getFontName(), Font.PLAIN, 1).canDisplay(rt.getBulletChar())){
bat.addAttribute(TextAttribute.FAMILY, "Arial");
bat = new AttributedString("" + DEFAULT_BULLET_CHAR, bat.getIterator().getAttributes());
}
if(text.substring(startIndex, endIndex).length() > 1){
el._bullet = bulletLayout;
el._bulletOffset = rt.getBulletOffset();
el._bullet = bat;
el._bulletOffset = bulletOffset;
}
}
lines.add(el);
@ -225,29 +267,32 @@ public class TextPainter {
break;
case TextShape.AlignCenter:
pen.x = anchor.getX() + _shape.getMarginLeft() +
(anchor.getWidth() - elem._text.getAdvance() - _shape.getMarginLeft() - _shape.getMarginRight()) / 2;
(anchor.getWidth() - elem.advance - _shape.getMarginLeft() - _shape.getMarginRight()) / 2;
break;
case TextShape.AlignRight:
pen.x = anchor.getX() + _shape.getMarginLeft() +
(anchor.getWidth() - elem._text.getAdvance() - _shape.getMarginLeft() - _shape.getMarginRight());
(anchor.getWidth() - elem.advance - _shape.getMarginLeft() - _shape.getMarginRight());
break;
}
if(elem._bullet != null){
elem._bullet.draw(graphics, (float)(pen.x + elem._bulletOffset), (float)pen.y);
graphics.drawString(elem._bullet.getIterator(), (float)(pen.x + elem._bulletOffset), (float)pen.y);
}
AttributedCharacterIterator chIt = elem._text.getIterator();
if(chIt.getEndIndex() > chIt.getBeginIndex()) {
graphics.drawString(chIt, (float)(pen.x + elem._textOffset), (float)pen.y);
}
elem._text.draw(graphics, (float)(pen.x + elem._textOffset), (float)pen.y);
y0 += elem.descent;
}
}
static class TextElement {
public TextLayout _text;
public static class TextElement {
public AttributedString _text;
public int _textOffset;
public TextLayout _bullet;
public AttributedString _bullet;
public int _bulletOffset;
public int _align;
public float ascent, descent;
public float advance;
}
}

View File

@ -535,9 +535,13 @@ public class TextRun
// them to \n
String text = rawText.replace('\r','\n');
//0xB acts like cariage return in page titles
text = text.replace((char) 0x0B, '\n');
int type = _headerAtom == null ? 0 : _headerAtom.getTextType();
if(type == TextHeaderAtom.TITLE_TYPE || type == TextHeaderAtom.CENTER_TITLE_TYPE){
//0xB acts like cariage return in page titles and like blank in the others
text = text.replace((char) 0x0B, '\n');
} else {
text = text.replace((char) 0x0B, ' ');
}
return text;
}
@ -655,4 +659,11 @@ public class TextRun
return null;
}
public TextRulerAtom getTextRuler(){
for (int i = 0; i < _records.length; i++) {
if(_records[i] instanceof TextRulerAtom) return (TextRulerAtom)_records[i];
}
return null;
}
}

View File

@ -261,17 +261,28 @@ public abstract class TextShape extends SimpleShape {
public int getVerticalAlignment(){
EscherOptRecord opt = (EscherOptRecord)getEscherChild(_escherContainer, EscherOptRecord.RECORD_ID);
EscherSimpleProperty prop = (EscherSimpleProperty)getEscherProperty(opt, EscherProperties.TEXT__ANCHORTEXT);
int valign;
int valign = TextShape.AnchorTop;
if (prop == null){
/**
* If vertical alignment was not found in the shape properties then try to
* fetch the master shape and search for the align property there.
*/
int type = getTextRun().getRunType();
switch (type){
case TextHeaderAtom.TITLE_TYPE:
case TextHeaderAtom.CENTER_TITLE_TYPE:
valign = TextShape.AnchorMiddle;
break;
default:
valign = TextShape.AnchorTop;
break;
MasterSheet master = getSheet().getMasterSheet();
if(master != null){
TextShape masterShape = master.getPlaceholder(type);
if(masterShape != null) valign = masterShape.getVerticalAlignment();
} else {
//not found in the master sheet. Use the hardcoded defaults.
switch (type){
case TextHeaderAtom.TITLE_TYPE:
case TextHeaderAtom.CENTER_TITLE_TYPE:
valign = TextShape.AnchorMiddle;
break;
default:
valign = TextShape.AnchorTop;
break;
}
}
} else {
valign = prop.getPropertyValue();

View File

@ -24,11 +24,13 @@ import org.apache.poi.util.POILogger;
import org.apache.poi.ddf.*;
import org.apache.poi.hslf.model.ShapeTypes;
import org.apache.poi.hslf.model.Shape;
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
import java.util.Vector;
import java.util.Iterator;
/**
* These are actually wrappers onto Escher drawings. Make use of
@ -52,6 +54,8 @@ public class PPDrawing extends RecordAtom
private EscherRecord[] childRecords;
private EscherTextboxWrapper[] textboxWrappers;
//cached EscherDgRecord
private EscherDgRecord dg;
/**
* Get access to the underlying Escher Records
@ -296,4 +300,24 @@ public class PPDrawing extends RecordAtom
tw[textboxWrappers.length] = txtbox;
textboxWrappers = tw;
}
/**
* Return EscherDgRecord which keeps track of the number of shapes and shapeId in this drawing group
*
* @return EscherDgRecord
*/
public EscherDgRecord getEscherDgRecord(){
if(dg == null){
EscherContainerRecord dgContainer = (EscherContainerRecord)childRecords[0];
for(Iterator it = dgContainer.getChildRecords().iterator(); it.hasNext();){
EscherRecord r = (EscherRecord) it.next();
if(r instanceof EscherDgRecord){
dg = (EscherDgRecord)r;
break;
}
}
}
return dg;
}
}

View File

@ -89,7 +89,7 @@ public class RecordTypes {
public static final Type TxMasterStyleAtom = new Type(4003,TxMasterStyleAtom.class);
public static final Type TxCFStyleAtom = new Type(4004,null);
public static final Type TxPFStyleAtom = new Type(4005,null);
public static final Type TextRulerAtom = new Type(4006,null);
public static final Type TextRulerAtom = new Type(4006,TextRulerAtom.class);
public static final Type TextBookmarkAtom = new Type(4007,null);
public static final Type TextBytesAtom = new Type(4008,TextBytesAtom.class);
public static final Type TxSIStyleAtom = new Type(4009,null);

View File

@ -127,8 +127,8 @@ public class StyleTextPropAtom extends RecordAtom
new ParagraphFlagsTextProp(),
new TextProp(2, 0x80, "bullet.char"),
new TextProp(2, 0x10, "bullet.font"),
new TextProp(2, 0x40, "bullet.size"),
new TextProp(4, 0x20, "bullet.color"),
new TextProp(2, 0x40, "bullet.size"),
new AlignmentTextProp(),
new TextProp(2, 0x100, "text.offset"),
new TextProp(2, 0x200, "para_unknown_2"),

View File

@ -0,0 +1,194 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
package org.apache.poi.hslf.record;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.zip.InflaterInputStream;
import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.POILogger;
/**
* Ruler of a text as it differs from the style's ruler settings.
*
* @author Yegor Kozlov
*/
public class TextRulerAtom extends RecordAtom {
/**
* Record header.
*/
private byte[] _header;
/**
* Record data.
*/
private byte[] _data;
//ruler internals
private int defaultTabSize;
private int numLevels;
private int[] tabStops;
private int[] bulletOffsets = new int[5];
private int[] textOffsets = new int[5];
/**
* Constructs a new empty ruler atom.
*/
protected TextRulerAtom() {
_header = new byte[8];
_data = new byte[0];
LittleEndian.putShort(_header, 2, (short)getRecordType());
LittleEndian.putInt(_header, 4, _data.length);
}
/**
* Constructs the ruler atom record from its
* source data.
*
* @param source the source data as a byte array.
* @param start the start offset into the byte array.
* @param len the length of the slice in the byte array.
*/
protected TextRulerAtom(byte[] source, int start, int len) {
// Get the header.
_header = new byte[8];
System.arraycopy(source,start,_header,0,8);
// Get the record data.
_data = new byte[len-8];
System.arraycopy(source,start+8,_data,0,len-8);
try {
read();
} catch (Exception e){
logger.log(POILogger.ERROR, "Failed to parse TextRulerAtom: " + e.getMessage());
e.printStackTrace();
}
}
/**
* Gets the record type.
*
* @return the record type.
*/
public long getRecordType() {
return RecordTypes.TextRulerAtom.typeID;
}
/**
* Write the contents of the record back, so it can be written
* to disk.
*
* @param out the output stream to write to.
* @throws java.io.IOException if an error occurs.
*/
public void writeOut(OutputStream out) throws IOException {
out.write(_header);
out.write(_data);
}
/**
* Read the record bytes and initialize the internal variables
*/
private void read(){
int pos = 0;
short mask = LittleEndian.getShort(_data); pos += 4;
short val;
int[] bits = {1, 0, 2, 3, 8, 4, 9, 5, 10, 6, 11, 7, 12};
for (int i = 0; i < bits.length; i++) {
if((mask & 1 << bits[i]) != 0){
switch (bits[i]){
case 0:
//defaultTabSize
defaultTabSize = LittleEndian.getShort(_data, pos); pos += 2;
break;
case 1:
//numLevels
numLevels = LittleEndian.getShort(_data, pos); pos += 2;
break;
case 2:
//tabStops
val = LittleEndian.getShort(_data, pos); pos += 2;
tabStops = new int[val*2];
for (int j = 0; j < tabStops.length; j++) {
tabStops[j] = LittleEndian.getUShort(_data, pos); pos += 2;
}
break;
case 3:
case 4:
case 5:
case 6:
case 7:
//bullet.offset
val = LittleEndian.getShort(_data, pos); pos += 2;
bulletOffsets[bits[i]-3] = val;
break;
case 8:
case 9:
case 10:
case 11:
case 12:
//text.offset
val = LittleEndian.getShort(_data, pos); pos += 2;
textOffsets[bits[i]-8] = val;
break;
}
}
}
}
/**
* Default distance between tab stops, in master coordinates (576 dpi).
*/
public int getDefaultTabSize(){
return defaultTabSize;
}
/**
* Number of indent levels (maximum 5).
*/
public int getNumberOfLevels(){
return numLevels;
}
/**
* Default distance between tab stops, in master coordinates (576 dpi).
*/
public int[] getTabStops(){
return tabStops;
}
/**
* Paragraph's distance from shape's left margin, in master coordinates (576 dpi).
*/
public int[] getTextOffsets(){
return textOffsets;
}
/**
* First line of paragraph's distance from shape's left margin, in master coordinates (576 dpi).
*/
public int[] getBulletOffsets(){
return bulletOffsets;
}
}

View File

@ -32,6 +32,8 @@ import org.apache.poi.hslf.model.textproperties.ParagraphFlagsTextProp;
import org.apache.poi.hslf.model.textproperties.TextProp;
import org.apache.poi.hslf.model.textproperties.TextPropCollection;
import org.apache.poi.hslf.record.ColorSchemeAtom;
import org.apache.poi.util.POILogger;
import org.apache.poi.util.POILogFactory;
/**
@ -39,6 +41,8 @@ import org.apache.poi.hslf.record.ColorSchemeAtom;
*
*/
public class RichTextRun {
protected POILogger logger = POILogFactory.getLogger(this.getClass());
/** The TextRun we belong to */
private TextRun parentRun;
/** The SlideShow we belong to */
@ -199,10 +203,15 @@ public class RichTextRun {
}
if (prop == null){
Sheet sheet = parentRun.getSheet();
int txtype = parentRun.getRunType();
MasterSheet master = sheet.getMasterSheet();
if (master != null)
prop = (BitMaskTextProp)master.getStyleAttribute(txtype, getIndentLevel(), propname, isCharacter);
if(sheet != null){
int txtype = parentRun.getRunType();
MasterSheet master = sheet.getMasterSheet();
if (master != null){
prop = (BitMaskTextProp)master.getStyleAttribute(txtype, getIndentLevel(), propname, isCharacter);
}
} else {
logger.log(POILogger.WARN, "MasterSheet is not available");
}
}
return prop == null ? false : prop.getSubValue(index);
@ -213,7 +222,7 @@ public class RichTextRun {
* it if required.
*/
private void setCharFlagsTextPropVal(int index, boolean value) {
setFlag(true, index, value);
if(getFlag(true, index) != value) setFlag(true, index, value);
}
public void setFlag(boolean isCharacter, int index, boolean value) {
@ -281,10 +290,14 @@ public class RichTextRun {
*/
private int getParaTextPropVal(String propName) {
TextProp prop = null;
boolean hardAttribute = false;
if (paragraphStyle != null){
prop = paragraphStyle.findByName(propName);
BitMaskTextProp maskProp = (BitMaskTextProp)paragraphStyle.findByName(ParagraphFlagsTextProp.NAME);
hardAttribute = maskProp != null && maskProp.getValue() == 0;
}
if (prop == null){
if (prop == null && !hardAttribute){
Sheet sheet = parentRun.getSheet();
int txtype = parentRun.getRunType();
MasterSheet master = sheet.getMasterSheet();
@ -574,6 +587,13 @@ public class RichTextRun {
return getFlag(false, ParagraphFlagsTextProp.BULLET_IDX);
}
/**
* Returns whether this rich text run has bullets
*/
public boolean isBulletHard() {
return getFlag(false, ParagraphFlagsTextProp.BULLET_IDX);
}
/**
* Sets the bullet character
*/

View File

@ -24,10 +24,7 @@ import java.util.*;
import java.awt.Dimension;
import java.io.*;
import org.apache.poi.ddf.EscherBSERecord;
import org.apache.poi.ddf.EscherContainerRecord;
import org.apache.poi.ddf.EscherOptRecord;
import org.apache.poi.ddf.EscherRecord;
import org.apache.poi.ddf.*;
import org.apache.poi.hslf.*;
import org.apache.poi.hslf.model.*;
import org.apache.poi.hslf.model.Notes;
@ -66,9 +63,7 @@ public class SlideShow
// Lookup between the PersitPtr "sheet" IDs, and the position
// in the mostRecentCoreRecords array
private Hashtable _sheetIdToCoreRecordsLookup;
// Used when adding new core records
private int _highestSheetId;
// Records that are interesting
private Document _documentRecord;
@ -203,8 +198,6 @@ public class SlideShow
for(int i=0; i<allIDs.length; i++) {
_sheetIdToCoreRecordsLookup.put(new Integer(allIDs[i]), new Integer(i));
}
// Capture the ID of the highest sheet
_highestSheetId = allIDs[(allIDs.length-1)];
// Now convert the byte offsets back into record offsets
for(int i=0; i<_records.length; i++) {
@ -612,38 +605,35 @@ public class SlideShow
}
}
}
// Set up a new SlidePersistAtom for this slide
// Set up a new SlidePersistAtom for this slide
SlidePersistAtom sp = new SlidePersistAtom();
// Reference is the 1-based index of the slide container in
// the PersistPtr root.
// It always starts with 3 (1 is Document, 2 is MainMaster, 3 is
// the first slide), but quicksaves etc can leave gaps
_highestSheetId++;
sp.setRefID(_highestSheetId);
// First slideId is always 256
sp.setSlideIdentifier(prev == null ? 256 : (prev.getSlideIdentifier() + 1));
// Add this new SlidePersistAtom to the SlideListWithText
slist.addSlidePersistAtom(sp);
// Create a new Slide
Slide slide = new Slide(sp.getSlideIdentifier(), sp.getRefID(), _slides.length+1);
slide.setSlideShow(this);
slide.onCreate();
// Add in to the list of Slides
Slide[] s = new Slide[_slides.length+1];
System.arraycopy(_slides, 0, s, 0, _slides.length);
s[_slides.length] = slide;
_slides = s;
logger.log(POILogger.INFO, "Added slide " + _slides.length + " with ref " + sp.getRefID() + " and identifier " + sp.getSlideIdentifier());
// Add the core records for this new Slide to the record tree
org.apache.poi.hslf.record.Slide slideRecord = slide.getSlideRecord();
slideRecord.setSheetId(sp.getRefID());
int slideRecordPos = _hslfSlideShow.appendRootLevelRecord(slideRecord);
_records = _hslfSlideShow.getRecords();
// Add the new Slide into the PersistPtr stuff
int offset = 0;
int slideOffset = 0;
@ -653,7 +643,7 @@ public class SlideShow
Record record = _records[i];
ByteArrayOutputStream out = new ByteArrayOutputStream();
record.writeOut(out);
// Grab interesting records as they come past
if(_records[i].getRecordType() == RecordTypes.PersistPtrIncrementalBlock.typeID){
ptr = (PersistPtrHolder)_records[i];
@ -661,25 +651,29 @@ public class SlideShow
if(_records[i].getRecordType() == RecordTypes.UserEditAtom.typeID) {
usr = (UserEditAtom)_records[i];
}
if(i == slideRecordPos) {
slideOffset = offset;
}
offset += out.size();
}
// persist ID is UserEditAtom.maxPersistWritten + 1
int psrId = usr.getMaxPersistWritten() + 1;
sp.setRefID(psrId);
slideRecord.setSheetId(psrId);
// Last view is now of the slide
usr.setLastViewType((short)UserEditAtom.LAST_VIEW_SLIDE_VIEW);
usr.setMaxPersistWritten(psrId); //increment the number of persit objects
// Add the new slide into the last PersistPtr
// (Also need to tell it where it is)
slideRecord.setLastOnDiskOffset(slideOffset);
ptr.addSlideLookup(sp.getRefID(), slideOffset);
logger.log(POILogger.INFO, "New slide ended up at " + slideOffset);
// Last view is now of the slide
usr.setLastViewType((short)UserEditAtom.LAST_VIEW_SLIDE_VIEW);
usr.setMaxPersistWritten(_highestSheetId);
// All done and added
slide.setSlideShow(this);
return slide;
}

View File

@ -52,7 +52,7 @@ public class TestFreeform extends TestCase {
Freeform p = new Freeform();
p.setPath(path1);
GeneralPath path2 = p.getPath();
java.awt.Shape path2 = p.getOutline();
assertTrue(new Area(path1).equals(new Area(path2)));
}
@ -63,7 +63,7 @@ public class TestFreeform extends TestCase {
Freeform p = new Freeform();
p.setPath(path1);
GeneralPath path2 = p.getPath();
java.awt.Shape path2 = p.getOutline();
assertTrue(new Area(path1).equals(new Area(path2)));
}
@ -74,7 +74,7 @@ public class TestFreeform extends TestCase {
Freeform p = new Freeform();
p.setPath(path1);
GeneralPath path2 = p.getPath();
java.awt.Shape path2 = p.getOutline();
assertTrue(new Area(path1).equals(new Area(path2)));
}
}
}

View File

@ -20,6 +20,8 @@ import junit.framework.TestCase;
import org.apache.poi.hslf.usermodel.SlideShow;
import org.apache.poi.hslf.usermodel.RichTextRun;
import org.apache.poi.hslf.HSLFSlideShow;
import org.apache.poi.ddf.EscherDggRecord;
import org.apache.poi.ddf.EscherDgRecord;
import java.awt.*;
import java.awt.Rectangle;
@ -311,18 +313,49 @@ public class TestShapes extends TestCase {
public void testShapeId() throws IOException {
SlideShow ppt = new SlideShow();
Slide slide = ppt.createSlide();
Shape shape;
Shape shape = null;
shape = new Line();
assertEquals(0, shape.getShapeId());
slide.addShape(shape);
assertTrue(shape.getShapeId() > 0);
//EscherDgg is a document-level record which keeps track of the drawing groups
EscherDggRecord dgg = ppt.getDocumentRecord().getPPDrawingGroup().getEscherDggRecord();
EscherDgRecord dg = slide.getSheetContainer().getPPDrawing().getEscherDgRecord();
int shapeId = shape.getShapeId();
int dggShapesUsed = dgg.getNumShapesSaved(); //total number of shapes in the ppt
int dggMaxId = dgg.getShapeIdMax(); //max number of shapeId
shape = new Line();
assertEquals(0, shape.getShapeId());
slide.addShape(shape);
assertEquals(shapeId + 1, shape.getShapeId());
int dgMaxId = dg.getLastMSOSPID(); //max shapeId in the slide
int dgShapesUsed = dg.getNumShapes(); // number of shapes in the slide
//insert 3 shapes and make sure the Ids are properly incremented
for (int i = 0; i < 3; i++) {
shape = new Line();
assertEquals(0, shape.getShapeId());
slide.addShape(shape);
assertTrue(shape.getShapeId() > 0);
//check that EscherDgRecord is updated
assertEquals(shape.getShapeId(), dg.getLastMSOSPID());
assertEquals(dgMaxId + 1, dg.getLastMSOSPID());
assertEquals(dgShapesUsed + 1, dg.getNumShapes());
//check that EscherDggRecord is updated
assertEquals(shape.getShapeId() + 1, dgg.getShapeIdMax());
assertEquals(dggMaxId + 1, dgg.getShapeIdMax());
assertEquals(dggShapesUsed + 1, dgg.getNumShapesSaved());
dggShapesUsed = dgg.getNumShapesSaved();
dggMaxId = dgg.getShapeIdMax();
dgMaxId = dg.getLastMSOSPID();
dgShapesUsed = dg.getNumShapes();
}
//For each drawing group PPT allocates clusters with size=1024
//if the number of shapes is greater that 1024 a new cluster is allocated
//make sure it is so
int numClusters = dgg.getNumIdClusters();
for (int i = 0; i < 1025; i++) {
shape = new Line();
slide.addShape(shape);
}
assertEquals(numClusters + 1, dgg.getNumIdClusters());
}
}

View File

@ -0,0 +1,76 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
package org.apache.poi.hslf.record;
import org.apache.poi.hslf.HSLFSlideShow;
import org.apache.poi.hslf.model.textproperties.CharFlagsTextProp;
import org.apache.poi.hslf.model.textproperties.TextProp;
import org.apache.poi.hslf.model.textproperties.TextPropCollection;
import org.apache.poi.hslf.record.StyleTextPropAtom.*;
import org.apache.poi.hslf.usermodel.SlideShow;
import org.apache.poi.util.HexDump;
import junit.framework.TestCase;
import java.io.ByteArrayOutputStream;
import java.util.LinkedList;
import java.util.Arrays;
/**
* Tests TextRulerAtom
*
* @author Yegor Kozlov
*/
public class TestTextRulerAtom extends TestCase {
//from a real file
private byte[] data_1 = new byte[] {
0x00, 0x00, (byte)0xA6, 0x0F, 0x18, 0x00, 0x00, 0x00,
(byte)0xF8, 0x1F, 0x00, 0x00, 0x75, 0x00, (byte)0xE2, 0x00, 0x59,
0x01, (byte)0xC3, 0x01, 0x1A, 0x03, (byte)0x87, 0x03, (byte)0xF8,
0x03, 0x69, 0x04, (byte)0xF6, 0x05, (byte)0xF6, 0x05
};
public void testReadRuler() throws Exception {
TextRulerAtom ruler = new TextRulerAtom(data_1, 0, data_1.length);
assertEquals(ruler.getNumberOfLevels(), 0);
assertEquals(ruler.getDefaultTabSize(), 0);
int[] tabStops = ruler.getTabStops();
assertNull(tabStops);
int[] textOffsets = ruler.getTextOffsets();
assertTrue(Arrays.equals(new int[]{226, 451, 903, 1129, 1526}, textOffsets));
int[] bulletOffsets = ruler.getBulletOffsets();
assertTrue(Arrays.equals(new int[]{117, 345, 794, 1016, 1526}, bulletOffsets));
}
public void testWriteRuler() throws Exception {
TextRulerAtom ruler = new TextRulerAtom(data_1, 0, data_1.length);
ByteArrayOutputStream out = new ByteArrayOutputStream();
ruler.writeOut(out);
byte[] result = out.toByteArray();
assertTrue(Arrays.equals(result, data_1));
}
}

View File

@ -90,9 +90,11 @@ public class TestRichTextRun extends TestCase {
// Now set it to not bold
rtr.setBold(false);
assertNotNull(rtr._getRawCharacterStyle());
assertNotNull(rtr._getRawParagraphStyle());
assertFalse(rtr.isBold());
//setting bold=false doesn't change the internal state
assertNull(rtr._getRawCharacterStyle());
assertNull(rtr._getRawParagraphStyle());
assertFalse(rtr.isBold());
// And now make it bold
rtr.setBold(true);