more progress with PPTX2PNG

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1188091 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Yegor Kozlov 2011-10-24 11:07:59 +00:00
parent a15034b281
commit f1a2673950
8 changed files with 316 additions and 88 deletions

View File

@ -33,11 +33,9 @@ import org.openxmlformats.schemas.presentationml.x2006.main.CTConnector;
import org.openxmlformats.schemas.presentationml.x2006.main.CTConnectorNonVisual; import org.openxmlformats.schemas.presentationml.x2006.main.CTConnectorNonVisual;
import java.awt.*; import java.awt.*;
import java.awt.geom.GeneralPath; import java.awt.geom.*;
import java.awt.geom.Rectangle2D;
/** /**
*
* Specifies a connection shape. * Specifies a connection shape.
* *
* @author Yegor Kozlov * @author Yegor Kozlov
@ -83,7 +81,7 @@ public class XSLFConnectorShape extends XSLFSimpleShape {
public LineDecoration getLineHeadDecoration() { public LineDecoration getLineHeadDecoration() {
CTLineProperties ln = getSpPr().getLn(); CTLineProperties ln = getSpPr().getLn();
if(!ln.isSetHeadEnd()) return LineDecoration.NONE; if (ln == null || !ln.isSetHeadEnd()) return LineDecoration.NONE;
STLineEndType.Enum end = ln.getHeadEnd().getType(); STLineEndType.Enum end = ln.getHeadEnd().getType();
return end == null ? LineDecoration.NONE : LineDecoration.values()[end.intValue() - 1]; return end == null ? LineDecoration.NONE : LineDecoration.values()[end.intValue() - 1];
@ -104,10 +102,10 @@ public class XSLFConnectorShape extends XSLFSimpleShape {
public LineEndWidth getLineHeadWidth() { public LineEndWidth getLineHeadWidth() {
CTLineProperties ln = getSpPr().getLn(); CTLineProperties ln = getSpPr().getLn();
if(!ln.isSetHeadEnd()) return null; if (ln == null || !ln.isSetHeadEnd()) return LineEndWidth.MEDIUM;
STLineEndWidth.Enum w = ln.getHeadEnd().getW(); STLineEndWidth.Enum w = ln.getHeadEnd().getW();
return w == null ? null : LineEndWidth.values()[w.intValue() - 1]; return w == null ? LineEndWidth.MEDIUM : LineEndWidth.values()[w.intValue() - 1];
} }
/** /**
@ -126,10 +124,10 @@ public class XSLFConnectorShape extends XSLFSimpleShape {
public LineEndLength getLineHeadLength() { public LineEndLength getLineHeadLength() {
CTLineProperties ln = getSpPr().getLn(); CTLineProperties ln = getSpPr().getLn();
if(!ln.isSetHeadEnd()) return null; if (ln == null || !ln.isSetHeadEnd()) return LineEndLength.MEDIUM;
STLineEndLength.Enum len = ln.getHeadEnd().getLen(); STLineEndLength.Enum len = ln.getHeadEnd().getLen();
return len == null ? null : LineEndLength.values()[len.intValue() - 1]; return len == null ? LineEndLength.MEDIUM : LineEndLength.values()[len.intValue() - 1];
} }
/** /**
@ -147,7 +145,7 @@ public class XSLFConnectorShape extends XSLFSimpleShape {
public LineDecoration getLineTailDecoration() { public LineDecoration getLineTailDecoration() {
CTLineProperties ln = getSpPr().getLn(); CTLineProperties ln = getSpPr().getLn();
if(!ln.isSetTailEnd()) return LineDecoration.NONE; if (ln == null || !ln.isSetTailEnd()) return LineDecoration.NONE;
STLineEndType.Enum end = ln.getTailEnd().getType(); STLineEndType.Enum end = ln.getTailEnd().getType();
return end == null ? LineDecoration.NONE : LineDecoration.values()[end.intValue() - 1]; return end == null ? LineDecoration.NONE : LineDecoration.values()[end.intValue() - 1];
@ -168,10 +166,10 @@ public class XSLFConnectorShape extends XSLFSimpleShape {
public LineEndWidth getLineTailWidth() { public LineEndWidth getLineTailWidth() {
CTLineProperties ln = getSpPr().getLn(); CTLineProperties ln = getSpPr().getLn();
if(!ln.isSetTailEnd()) return null; if (ln == null || !ln.isSetTailEnd()) return LineEndWidth.MEDIUM;
STLineEndWidth.Enum w = ln.getTailEnd().getW(); STLineEndWidth.Enum w = ln.getTailEnd().getW();
return w == null ? null : LineEndWidth.values()[w.intValue() - 1]; return w == null ? LineEndWidth.MEDIUM : LineEndWidth.values()[w.intValue() - 1];
} }
/** /**
@ -190,10 +188,10 @@ public class XSLFConnectorShape extends XSLFSimpleShape {
public LineEndLength getLineTailLength() { public LineEndLength getLineTailLength() {
CTLineProperties ln = getSpPr().getLn(); CTLineProperties ln = getSpPr().getLn();
if(!ln.isSetTailEnd()) return null; if (ln == null || !ln.isSetTailEnd()) return LineEndLength.MEDIUM;
STLineEndLength.Enum len = ln.getTailEnd().getLen(); STLineEndLength.Enum len = ln.getTailEnd().getLen();
return len == null ? null : LineEndLength.values()[len.intValue() - 1]; return len == null ? LineEndLength.MEDIUM : LineEndLength.values()[len.intValue() - 1];
} }
@Override @Override
@ -202,14 +200,26 @@ public class XSLFConnectorShape extends XSLFSimpleShape {
// shadow // shadow
XSLFShadow shadow = getShadow(); XSLFShadow shadow = getShadow();
if(shadow != null) shadow.draw(graphics);
//border //border
Color lineColor = getLineColor(); Color lineColor = getLineColor();
if (lineColor != null) { if (lineColor != null) {
if (shadow != null) shadow.draw(graphics);
graphics.setColor(lineColor); graphics.setColor(lineColor);
applyStroke(graphics); applyStroke(graphics);
graphics.draw(outline); graphics.draw(outline);
Shape tailDecoration = getTailDecoration();
if (tailDecoration != null) {
graphics.draw(tailDecoration);
}
Shape headDecoration = getHeadDecoration();
if (headDecoration != null) {
graphics.draw(headDecoration);
}
} }
} }
@ -221,11 +231,116 @@ public class XSLFConnectorShape extends XSLFSimpleShape {
x2 = anchor.getX() + anchor.getWidth(), x2 = anchor.getX() + anchor.getWidth(),
y2 = anchor.getY() + anchor.getHeight(); y2 = anchor.getY() + anchor.getHeight();
GeneralPath line = new GeneralPath();
line.moveTo((float)x1, (float)y1);
line.lineTo((float)x2, (float)y2);
return line; return new Line2D.Double(x1, y1, x2, y2);
}
Shape getTailDecoration() {
LineEndLength tailLength = getLineTailLength();
LineEndWidth tailWidth = getLineTailWidth();
double lineWidth = getLineWidth();
Rectangle2D anchor = getAnchor();
double x2 = anchor.getX() + anchor.getWidth(),
y2 = anchor.getY() + anchor.getHeight();
double alpha = Math.atan(anchor.getHeight() / anchor.getWidth());
AffineTransform at = new AffineTransform();
Shape shape = null;
Rectangle2D bounds;
double scaleY = Math.pow(2, tailWidth.ordinal());
double scaleX = Math.pow(2, tailLength.ordinal());
switch (getLineHeadDecoration()) {
case OVAL:
shape = new Ellipse2D.Double(0, 0, lineWidth * scaleX, lineWidth * scaleY);
bounds = shape.getBounds2D();
at.translate(x2 - bounds.getWidth() / 2, y2 - bounds.getHeight() / 2);
at.rotate(alpha, bounds.getX() + bounds.getWidth() / 2, bounds.getY() + bounds.getHeight() / 2);
break;
case ARROW:
GeneralPath arrow = new GeneralPath();
arrow.moveTo(-lineWidth * 3, -lineWidth * 2);
arrow.lineTo(0, 0);
arrow.lineTo(-lineWidth * 3, lineWidth * 2);
shape = arrow;
at.translate(x2, y2);
at.rotate(alpha);
break;
case TRIANGLE:
scaleY = tailWidth.ordinal() + 1;
scaleX = tailLength.ordinal() + 1;
GeneralPath triangle = new GeneralPath();
triangle.moveTo(-lineWidth * scaleX, -lineWidth * scaleY/2);
triangle.lineTo(0, 0);
triangle.lineTo(-lineWidth * scaleX, lineWidth * scaleY/2);
triangle.closePath();
shape = triangle;
at.translate(x2, y2);
at.rotate(alpha);
break;
default:
break;
}
if (shape != null) {
shape = at.createTransformedShape(shape);
}
return shape;
}
Shape getHeadDecoration() {
LineEndLength headLength = getLineHeadLength();
LineEndWidth headWidth = getLineHeadWidth();
double lineWidth = getLineWidth();
Rectangle2D anchor = getAnchor();
double x1 = anchor.getX(),
y1 = anchor.getY();
double alpha = Math.atan(anchor.getHeight() / anchor.getWidth());
AffineTransform at = new AffineTransform();
Shape shape = null;
Rectangle2D bounds;
double scaleY = 1;
double scaleX = 1;
switch (getLineHeadDecoration()) {
case OVAL:
scaleY = Math.pow(2, headWidth.ordinal());
scaleX = Math.pow(2, headLength.ordinal());
shape = new Ellipse2D.Double(0, 0, lineWidth * scaleX, lineWidth * scaleY);
break;
case STEALTH:
case ARROW:
GeneralPath arrow = new GeneralPath();
arrow.moveTo(lineWidth * 3 * scaleX, -lineWidth * scaleY * 2);
arrow.lineTo(0, 0);
arrow.lineTo(lineWidth * 3 * scaleX, lineWidth * scaleY * 2);
shape = arrow;
at.translate(x1, y1);
at.rotate(alpha);
break;
case TRIANGLE:
scaleY = headWidth.ordinal() + 1;
scaleX = headLength.ordinal() + 1;
GeneralPath triangle = new GeneralPath();
triangle.moveTo(lineWidth * scaleX, -lineWidth * scaleY/2);
triangle.lineTo(0, 0);
triangle.lineTo(lineWidth * scaleX, lineWidth * scaleY/2);
triangle.closePath();
shape = triangle;
at.translate(x1, y1);
at.rotate(alpha);
break;
default:
break;
}
if (shape != null) {
shape = at.createTransformedShape(shape);
}
return shape;
} }
} }

View File

@ -0,0 +1,87 @@
/*
* ====================================================================
* 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.xslf.usermodel;
import org.apache.poi.util.Beta;
import javax.imageio.ImageIO;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
/**
* For now this class renders only images supported by the javax.imageio.ImageIO
* framework. Subclasses can override this class to support other formats, for
* example, Use Apache batik to render WMF:
*
* <pre>
* <code>
* @Override
* public class MyImageRendener extends XSLFImageRendener{
* public boolean drawImage(Graphics2D graphics, XSLFPictureData data, Rectangle2D anchor){
* boolean ok = super.drawImage(graphics, data, anchor);
* if(!ok){
* // see what type of image we are
* String contentType = data.getPackagePart().getContentType();
* if(contentType.equals("image/wmf")){
* // use Apache Batik to handle WMF
* // see http://xmlgraphics.apache.org/batik/
* }
*
* }
* return ok;
* }
* }
* </code>
* </pre>
*
* and then pass this class to your instance of java.awt.Graphics2D:
*
* <pre>
* <code>
* graphics.setRenderingHint(XSLFRenderingHint.IMAGE_RENDERER, new MyImageRendener());
* </code>
* </pre>
*
* @author Yegor Kozlov
*/
@Beta
public class XSLFImageRendener {
/**
* Render picture data into the supplied graphics
*
* @return true if the picture data was succesfully renderered
*/
public boolean drawImage(Graphics2D graphics, XSLFPictureData data,
Rectangle2D anchor) {
try {
BufferedImage img = ImageIO.read(new ByteArrayInputStream(data
.getData()));
graphics.drawImage(img, (int) anchor.getX(), (int) anchor.getY(),
(int) anchor.getWidth(), (int) anchor.getHeight(), null);
return true;
} catch (Exception e) {
return false;
}
}
}

View File

@ -117,30 +117,26 @@ public class XSLFPictureShape extends XSLFSimpleShape {
public void draw(Graphics2D graphics){ public void draw(Graphics2D graphics){
java.awt.Shape outline = getOutline(); java.awt.Shape outline = getOutline();
// shadow
XSLFShadow shadow = getShadow();
//fill //fill
Color fillColor = getFillColor(); Color fillColor = getFillColor();
if (fillColor != null) { if (fillColor != null) {
if(shadow != null) shadow.draw(graphics);
graphics.setColor(fillColor); graphics.setColor(fillColor);
applyFill(graphics); applyFill(graphics);
graphics.fill(outline); graphics.fill(outline);
} }
// text
XSLFPictureData data = getPictureData(); XSLFPictureData data = getPictureData();
if(data == null) return; if(data == null) return;
BufferedImage img; XSLFImageRendener renderer = (XSLFImageRendener)graphics.getRenderingHint(XSLFRenderingHint.IMAGE_RENDERER);
try { if(renderer == null) renderer = new XSLFImageRendener();
img = ImageIO.read(new ByteArrayInputStream(data.getData()));
}
catch (Exception e){
return;
}
Rectangle2D anchor = getAnchor();
graphics.drawImage(img, (int)anchor.getX(), (int)anchor.getY(),
(int)anchor.getWidth(), (int)anchor.getHeight(), null);
renderer.drawImage(graphics, data, getAnchor());
//border overlays the image //border overlays the image
Color lineColor = getLineColor(); Color lineColor = getLineColor();

View File

@ -38,5 +38,5 @@ public class XSLFRenderingHint extends RenderingHints.Key {
public static final XSLFRenderingHint GSAVE = new XSLFRenderingHint(1); public static final XSLFRenderingHint GSAVE = new XSLFRenderingHint(1);
public static final XSLFRenderingHint GRESTORE = new XSLFRenderingHint(2); public static final XSLFRenderingHint GRESTORE = new XSLFRenderingHint(2);
public static final XSLFRenderingHint SKIP_PLACEHOLDERS = new XSLFRenderingHint(3); public static final XSLFRenderingHint IMAGE_RENDERER = new XSLFRenderingHint(3);
} }

View File

@ -45,9 +45,13 @@ public class XSLFShadow extends XSLFSimpleShape {
_parent = parentShape; _parent = parentShape;
} }
@Override
public void draw(Graphics2D graphics) { public void draw(Graphics2D graphics) {
Shape outline = _parent.getOutline(); Shape outline = _parent.getOutline();
Color parentFillColor = _parent.getFillColor();
Color parentLineColor = _parent.getLineColor();
double angle = getAngle(); double angle = getAngle();
double dist = getDistance(); double dist = getDistance();
double dx = dist * Math.cos( Math.toRadians(angle)); double dx = dist * Math.cos( Math.toRadians(angle));
@ -55,12 +59,18 @@ public class XSLFShadow extends XSLFSimpleShape {
graphics.translate(dx, dy); graphics.translate(dx, dy);
//fill
Color fillColor = getFillColor(); Color fillColor = getFillColor();
if (fillColor != null) { if (fillColor != null) {
graphics.setColor(fillColor); graphics.setColor(fillColor);
}
if(parentFillColor != null) {
graphics.fill(outline); graphics.fill(outline);
} }
if(parentLineColor != null) {
_parent.applyStroke(graphics);
graphics.draw(outline);
}
graphics.translate(-dx, -dy); graphics.translate(-dx, -dy);
} }
@ -75,21 +85,37 @@ public class XSLFShadow extends XSLFSimpleShape {
throw new IllegalStateException("You can't set anchor of a shadow"); throw new IllegalStateException("You can't set anchor of a shadow");
} }
/**
* @return the offset of this shadow in points
*/
public double getDistance(){ public double getDistance(){
CTOuterShadowEffect ct = (CTOuterShadowEffect)getXmlObject(); CTOuterShadowEffect ct = (CTOuterShadowEffect)getXmlObject();
return ct.isSetDist() ? Units.toPoints(ct.getDist()) : 0; return ct.isSetDist() ? Units.toPoints(ct.getDist()) : 0;
} }
/**
*
* @return the direction to offset the shadow in angles
*/
public double getAngle(){ public double getAngle(){
CTOuterShadowEffect ct = (CTOuterShadowEffect)getXmlObject(); CTOuterShadowEffect ct = (CTOuterShadowEffect)getXmlObject();
return ct.isSetDir() ? (double)ct.getDir() / 60000 : 0; return ct.isSetDir() ? (double)ct.getDir() / 60000 : 0;
} }
/**
*
* @return the blur radius of the shadow
* TODO: figure out how to make sense of this property when rendering shadows
*/
public double getBlur(){ public double getBlur(){
CTOuterShadowEffect ct = (CTOuterShadowEffect)getXmlObject(); CTOuterShadowEffect ct = (CTOuterShadowEffect)getXmlObject();
return ct.isSetBlurRad() ? Units.toPoints(ct.getBlurRad()) : 0; return ct.isSetBlurRad() ? Units.toPoints(ct.getBlurRad()) : 0;
} }
/**
* @return the color of this shadow.
* Depending whether the parent shape is filled or stroked, this color is used to fill or stroke this shadow
*/
@Override @Override
public Color getFillColor() { public Color getFillColor() {
XSLFTheme theme = getSheet().getTheme(); XSLFTheme theme = getSheet().getTheme();
@ -100,7 +126,10 @@ public class XSLFShadow extends XSLFSimpleShape {
else if (ct.isSetPrstClr()) { else if (ct.isSetPrstClr()) {
return theme.getPresetColor(ct.getPrstClr()); return theme.getPresetColor(ct.getPrstClr());
} }
else if (ct.isSetSrgbClr()) {
return theme.getSrgbColor(ct.getSrgbClr());
}
return Color.black; return null;
} }
} }

View File

@ -279,14 +279,14 @@ public abstract class XSLFSimpleShape extends XSLFShape {
public Color getLineColor() { public Color getLineColor() {
final XSLFTheme theme = _sheet.getTheme(); final XSLFTheme theme = _sheet.getTheme();
final Color noline = new Color(0,0,0,0);
PropertyFetcher<Color> fetcher = new PropertyFetcher<Color>(){ PropertyFetcher<Color> fetcher = new PropertyFetcher<Color>(){
public boolean fetch(XSLFSimpleShape shape){ public boolean fetch(XSLFSimpleShape shape){
CTShapeProperties spPr = shape.getSpPr(); CTShapeProperties spPr = shape.getSpPr();
CTLineProperties ln = spPr.getLn(); CTLineProperties ln = spPr.getLn();
if (ln != null) { if (ln != null) {
if (ln.isSetNoFill()) { if (ln.isSetNoFill()) {
setValue(null); setValue(noline);
return true; return true;
} }
CTSolidColorFillProperties solidLine = ln.getSolidFill(); CTSolidColorFillProperties solidLine = ln.getSolidFill();
@ -311,7 +311,7 @@ public abstract class XSLFSimpleShape extends XSLFShape {
} }
} }
} }
return color; return color == noline ? null : color;
} }
public void setLineWidth(double width) { public void setLineWidth(double width) {
@ -480,12 +480,12 @@ public abstract class XSLFSimpleShape extends XSLFShape {
*/ */
public Color getFillColor() { public Color getFillColor() {
final XSLFTheme theme = _sheet.getTheme(); final XSLFTheme theme = _sheet.getTheme();
final Color nofill = new Color(0,0,0,0);
PropertyFetcher<Color> fetcher = new PropertyFetcher<Color>(){ PropertyFetcher<Color> fetcher = new PropertyFetcher<Color>(){
public boolean fetch(XSLFSimpleShape shape){ public boolean fetch(XSLFSimpleShape shape){
CTShapeProperties spPr = shape.getSpPr(); CTShapeProperties spPr = shape.getSpPr();
if (spPr.isSetNoFill()) { if (spPr.isSetNoFill()) {
setValue(null); setValue(nofill); // use it as 'nofill' value
return true; return true;
} }
if (spPr.isSetSolidFill()) { if (spPr.isSetSolidFill()) {
@ -508,7 +508,7 @@ public abstract class XSLFSimpleShape extends XSLFShape {
} }
} }
} }
return color; return color == nofill ? null : color;
} }
public XSLFShadow getShadow(){ public XSLFShadow getShadow(){
@ -608,7 +608,7 @@ public abstract class XSLFSimpleShape extends XSLFShape {
int meter = BasicStroke.JOIN_ROUND; int meter = BasicStroke.JOIN_ROUND;
Stroke stroke = new BasicStroke(lineWidth, cap, meter, 0.0f, dash, Stroke stroke = new BasicStroke(lineWidth, cap, meter, Math.max(1, lineWidth), dash,
dash_phase); dash_phase);
graphics.setStroke(stroke); graphics.setStroke(stroke);
} }

View File

@ -387,18 +387,19 @@ public abstract class XSLFTextShape extends XSLFSimpleShape {
// shadow // shadow
XSLFShadow shadow = getShadow(); XSLFShadow shadow = getShadow();
if(shadow != null) shadow.draw(graphics);
//fill
Color fillColor = getFillColor(); Color fillColor = getFillColor();
Color lineColor = getLineColor();
if(shadow != null) {
shadow.draw(graphics);
}
if (fillColor != null) { if (fillColor != null) {
graphics.setColor(fillColor); graphics.setColor(fillColor);
applyFill(graphics); applyFill(graphics);
graphics.fill(outline); graphics.fill(outline);
} }
//border
Color lineColor = getLineColor();
if (lineColor != null){ if (lineColor != null){
graphics.setColor(lineColor); graphics.setColor(lineColor);
applyStroke(graphics); applyStroke(graphics);

View File

@ -69,12 +69,12 @@ public class TestXSLFConnectorShape extends TestCase {
assertEquals(STLineEndType.ARROW, shape.getSpPr().getLn().getTailEnd().getType()); assertEquals(STLineEndType.ARROW, shape.getSpPr().getLn().getTailEnd().getType());
// line end width // line end width
assertEquals(null, shape.getLineHeadWidth()); assertEquals(LineEndWidth.MEDIUM, shape.getLineHeadWidth());
assertEquals(null, shape.getLineTailWidth()); assertEquals(LineEndWidth.MEDIUM, shape.getLineTailWidth());
shape.setLineHeadWidth(null); shape.setLineHeadWidth(null);
shape.setLineHeadWidth(null); shape.setLineHeadWidth(null);
assertEquals(null, shape.getLineHeadWidth()); assertEquals(LineEndWidth.MEDIUM, shape.getLineHeadWidth());
assertEquals(null, shape.getLineTailWidth()); assertEquals(LineEndWidth.MEDIUM, shape.getLineTailWidth());
assertFalse(shape.getSpPr().getLn().getHeadEnd().isSetW()); assertFalse(shape.getSpPr().getLn().getHeadEnd().isSetW());
assertFalse(shape.getSpPr().getLn().getTailEnd().isSetW()); assertFalse(shape.getSpPr().getLn().getTailEnd().isSetW());
shape.setLineHeadWidth(LineEndWidth.LARGE); shape.setLineHeadWidth(LineEndWidth.LARGE);
@ -91,12 +91,12 @@ public class TestXSLFConnectorShape extends TestCase {
assertEquals(STLineEndWidth.LG, shape.getSpPr().getLn().getTailEnd().getW()); assertEquals(STLineEndWidth.LG, shape.getSpPr().getLn().getTailEnd().getW());
// line end length // line end length
assertEquals(null, shape.getLineHeadLength()); assertEquals(LineEndLength.MEDIUM, shape.getLineHeadLength());
assertEquals(null, shape.getLineTailLength()); assertEquals(LineEndLength.MEDIUM, shape.getLineTailLength());
shape.setLineHeadLength(null); shape.setLineHeadLength(null);
shape.setLineTailLength(null); shape.setLineTailLength(null);
assertEquals(null, shape.getLineHeadLength()); assertEquals(LineEndLength.MEDIUM, shape.getLineHeadLength());
assertEquals(null, shape.getLineTailLength()); assertEquals(LineEndLength.MEDIUM, shape.getLineTailLength());
assertFalse(shape.getSpPr().getLn().getHeadEnd().isSetLen()); assertFalse(shape.getSpPr().getLn().getHeadEnd().isSetLen());
assertFalse(shape.getSpPr().getLn().getTailEnd().isSetLen()); assertFalse(shape.getSpPr().getLn().getTailEnd().isSetLen());
shape.setLineHeadLength(LineEndLength.LARGE); shape.setLineHeadLength(LineEndLength.LARGE);