From ee9a91198b785374c7d861213b323fe977b4a649 Mon Sep 17 00:00:00 2001 From: Yegor Kozlov Date: Wed, 30 Apr 2008 06:29:11 +0000 Subject: [PATCH] more work on rendering ppt slides git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@652298 13f79535-47bb-0310-9956-ffa450edef68 --- .../org/apache/poi/hslf/model/AutoShapes.java | 7 + .../org/apache/poi/hslf/model/Freeform.java | 29 +-- .../apache/poi/hslf/model/MasterSheet.java | 17 ++ .../org/apache/poi/hslf/model/Picture.java | 5 + .../src/org/apache/poi/hslf/model/Shape.java | 51 ----- .../org/apache/poi/hslf/model/ShapeGroup.java | 18 +- .../src/org/apache/poi/hslf/model/Sheet.java | 57 ++++- .../apache/poi/hslf/model/SimpleShape.java | 4 +- .../src/org/apache/poi/hslf/model/Slide.java | 41 ++++ .../apache/poi/hslf/model/SlideMaster.java | 1 + .../apache/poi/hslf/model/TextPainter.java | 77 +++++-- .../org/apache/poi/hslf/model/TextRun.java | 17 +- .../org/apache/poi/hslf/model/TextShape.java | 29 ++- .../org/apache/poi/hslf/record/PPDrawing.java | 24 +++ .../apache/poi/hslf/record/RecordTypes.java | 2 +- .../poi/hslf/record/StyleTextPropAtom.java | 2 +- .../apache/poi/hslf/record/TextRulerAtom.java | 194 ++++++++++++++++++ .../poi/hslf/usermodel/RichTextRun.java | 32 ++- .../apache/poi/hslf/usermodel/SlideShow.java | 54 +++-- .../apache/poi/hslf/model/TestFreeform.java | 8 +- .../org/apache/poi/hslf/model/TestShapes.java | 53 ++++- .../poi/hslf/record/TestTextRulerAtom.java | 76 +++++++ .../poi/hslf/usermodel/TestRichTextRun.java | 8 +- 23 files changed, 631 insertions(+), 175 deletions(-) create mode 100755 src/scratchpad/src/org/apache/poi/hslf/record/TextRulerAtom.java create mode 100755 src/scratchpad/testcases/org/apache/poi/hslf/record/TestTextRulerAtom.java diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/AutoShapes.java b/src/scratchpad/src/org/apache/poi/hslf/model/AutoShapes.java index 5d345e6de..1fc4ec993 100755 --- a/src/scratchpad/src/org/apache/poi/hslf/model/AutoShapes.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/AutoShapes.java @@ -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); + } + }; + + } } diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/Freeform.java b/src/scratchpad/src/org/apache/poi/hslf/model/Freeform.java index d31237f8d..fb3980a45 100755 --- a/src/scratchpad/src/org/apache/poi/hslf/model/Freeform.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/Freeform.java @@ -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); } } diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/MasterSheet.java b/src/scratchpad/src/org/apache/poi/hslf/model/MasterSheet.java index 5b1b1016e..d01136d87 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/MasterSheet.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/MasterSheet.java @@ -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; + } } diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/Picture.java b/src/scratchpad/src/org/apache/poi/hslf/model/Picture.java index e10986966..375249b51 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/Picture.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/Picture.java @@ -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); } } diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/Shape.java b/src/scratchpad/src/org/apache/poi/hslf/model/Shape.java index e96e34900..fbea17e88 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/Shape.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/Shape.java @@ -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; } /** diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/ShapeGroup.java b/src/scratchpad/src/org/apache/poi/hslf/model/ShapeGroup.java index b57a6e233..cfa829671 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/ShapeGroup.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/ShapeGroup.java @@ -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); diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/Sheet.java b/src/scratchpad/src/org/apache/poi/hslf/model/Sheet.java index d9c8903d5..6eb84ca2e 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/Sheet.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/Sheet.java @@ -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 . */ diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/SimpleShape.java b/src/scratchpad/src/org/apache/poi/hslf/model/SimpleShape.java index ea0719a7f..e15454d65 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/SimpleShape.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/SimpleShape.java @@ -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) { diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/Slide.java b/src/scratchpad/src/org/apache/poi/hslf/model/Slide.java index e618ae230..bb99c1bca 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/Slide.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/Slide.java @@ -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. + *

+ * For Slide we need to do the following: + *

  • set id of the drawing group. + *
  • set shapeId for the container descriptor and background + *

    + */ + 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 TextBox object that represents the slide's title. * diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/SlideMaster.java b/src/scratchpad/src/org/apache/poi/hslf/model/SlideMaster.java index 26870dbdb..b48edfc1a 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/SlideMaster.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/SlideMaster.java @@ -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; diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/TextPainter.java b/src/scratchpad/src/org/apache/poi/hslf/model/TextPainter.java index c1eadc336..6eff54329 100755 --- a/src/scratchpad/src/org/apache/poi/hslf/model/TextPainter.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/TextPainter.java @@ -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; } } diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/TextRun.java b/src/scratchpad/src/org/apache/poi/hslf/model/TextRun.java index 2f77ac5ff..5dac65025 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/TextRun.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/TextRun.java @@ -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; + + } } diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/TextShape.java b/src/scratchpad/src/org/apache/poi/hslf/model/TextShape.java index 7249817be..53f8ef97d 100755 --- a/src/scratchpad/src/org/apache/poi/hslf/model/TextShape.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/TextShape.java @@ -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(); diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/PPDrawing.java b/src/scratchpad/src/org/apache/poi/hslf/record/PPDrawing.java index e42b358b8..3a1ed8dbe 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/record/PPDrawing.java +++ b/src/scratchpad/src/org/apache/poi/hslf/record/PPDrawing.java @@ -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; + } + } diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/RecordTypes.java b/src/scratchpad/src/org/apache/poi/hslf/record/RecordTypes.java index 45038f7a2..cedc99ce0 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/record/RecordTypes.java +++ b/src/scratchpad/src/org/apache/poi/hslf/record/RecordTypes.java @@ -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); diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/StyleTextPropAtom.java b/src/scratchpad/src/org/apache/poi/hslf/record/StyleTextPropAtom.java index 187dec12a..2f3b898a7 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/record/StyleTextPropAtom.java +++ b/src/scratchpad/src/org/apache/poi/hslf/record/StyleTextPropAtom.java @@ -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"), diff --git a/src/scratchpad/src/org/apache/poi/hslf/record/TextRulerAtom.java b/src/scratchpad/src/org/apache/poi/hslf/record/TextRulerAtom.java new file mode 100755 index 000000000..71ded8540 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hslf/record/TextRulerAtom.java @@ -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; + } +} diff --git a/src/scratchpad/src/org/apache/poi/hslf/usermodel/RichTextRun.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/RichTextRun.java index 2a09f2224..7458df7e6 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/usermodel/RichTextRun.java +++ b/src/scratchpad/src/org/apache/poi/hslf/usermodel/RichTextRun.java @@ -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 */ diff --git a/src/scratchpad/src/org/apache/poi/hslf/usermodel/SlideShow.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/SlideShow.java index 828255087..d969c5b88 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/usermodel/SlideShow.java +++ b/src/scratchpad/src/org/apache/poi/hslf/usermodel/SlideShow.java @@ -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 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()); } } diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/record/TestTextRulerAtom.java b/src/scratchpad/testcases/org/apache/poi/hslf/record/TestTextRulerAtom.java new file mode 100755 index 000000000..0b610cb64 --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hslf/record/TestTextRulerAtom.java @@ -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)); + } +} diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestRichTextRun.java b/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestRichTextRun.java index 0fecdab97..eda6589c4 100644 --- a/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestRichTextRun.java +++ b/src/scratchpad/testcases/org/apache/poi/hslf/usermodel/TestRichTextRun.java @@ -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);