#62096 - Add support for tabstops

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1823893 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Andreas Beeker 2018-02-11 20:39:18 +00:00
parent 5f71c80131
commit 9968e86b14
22 changed files with 1033 additions and 469 deletions

View File

@ -21,5 +21,12 @@ public interface MasterSheet<
S extends Shape<S,P>, S extends Shape<S,P>,
P extends TextParagraph<S,P,? extends TextRun> P extends TextParagraph<S,P,? extends TextRun>
> extends Sheet<S,P> { > extends Sheet<S,P> {
/**
* Return the placeholder shape for the specified type
*
* @return the shape or {@code null} if it is not defined in this mastersheet
*
* @since POI 4.0.0
*/
SimpleShape<S,P> getPlaceholder(Placeholder type);
} }

View File

@ -0,0 +1,66 @@
/* ====================================================================
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.sl.usermodel;
public interface TabStop {
enum TabStopType {
LEFT(0,1), CENTER(1,2), RIGHT(2,3), DECIMAL(3,4);
public final int nativeId;
public final int ooxmlId;
TabStopType(int nativeId, int ooxmlId) {
this.nativeId = nativeId;
this.ooxmlId = ooxmlId;
}
public static TabStopType fromNativeId(final int nativeId) {
for (TabStopType tst : values()) {
if (tst.nativeId == nativeId) {
return tst;
}
}
return null;
}
public static TabStopType fromOoxmlId(final int ooxmlId) {
for (TabStopType tst : values()) {
if (tst.ooxmlId == ooxmlId) {
return tst;
}
}
return null;
}
}
/**
* Gets the position in points relative to the left side of the paragraph.
*
* @return position in points
*/
double getPositionInPoints();
/**
* Sets the position in points relative to the left side of the paragraph
*
* @param position position in points
*/
void setPositionInPoints(double position);
TabStopType getType();
void setType(TabStopType type);
}

View File

@ -374,4 +374,34 @@ public interface TextParagraph<
* @since POI 3.15-beta2 * @since POI 3.15-beta2
*/ */
boolean isHeaderOrFooter(); boolean isHeaderOrFooter();
/**
* Get the {@link TabStop TabStops} - the list can't be and it's entries shouldn't be modified.
* Opposed to other properties, this method is not cascading to the master sheet,
* if the property is not defined on the normal slide level, i.e. the tabstops on
* different levels aren't merged.
*
* @return the tabstop collection or {@code null} if no tabstops are defined
*
* @since POI 4.0.0
*/
List<? extends TabStop> getTabStops();
/**
* Set the {@link TabStop} collection
*
* @param tabStops the {@link TabStop} collection
*
* @since POI 4.0.0
*/
void addTabStops(double positionInPoints, TabStop.TabStopType tabStopType);
/**
* Removes the tabstops of this paragraphs.
* This doesn't affect inherited tabstops, e.g. inherited by the slide master
*
* @since POI 4.0.0
*/
void clearTabStops();
} }

View File

@ -472,6 +472,10 @@ implements XSLFShapeContainer, Sheet<XSLFShape,XSLFTextParagraph> {
return null; return null;
} }
public XSLFSimpleShape getPlaceholder(Placeholder ph) {
return getPlaceholderByType(ph.ooxmlId);
}
XSLFSimpleShape getPlaceholder(CTPlaceholder ph) { XSLFSimpleShape getPlaceholder(CTPlaceholder ph) {
XSLFSimpleShape shape = null; XSLFSimpleShape shape = null;
if(ph.isSetIdx()) { if(ph.isSetIdx()) {

View File

@ -0,0 +1,60 @@
/* ====================================================================
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.sl.usermodel.TabStop;
import org.apache.poi.util.Units;
import org.openxmlformats.schemas.drawingml.x2006.main.CTTextTabStop;
import org.openxmlformats.schemas.drawingml.x2006.main.STTextTabAlignType;
public class XSLFTabStop implements TabStop {
final CTTextTabStop tabStop;
XSLFTabStop(CTTextTabStop tabStop) {
this.tabStop = tabStop;
}
/** position in EMUs */
public int getPosition() {
return tabStop.getPos();
}
/** position in EMUs */
public void setPosition(final int position) {
tabStop.setPos(position);
}
@Override
public double getPositionInPoints() {
return Units.toPoints(getPosition());
}
@Override
public void setPositionInPoints(final double points) {
setPosition(Units.toEMU(points));
}
public TabStopType getType() {
return TabStopType.fromOoxmlId(tabStop.getAlgn().intValue());
}
public void setType(final TabStopType tabStopType) {
tabStop.setAlgn(STTextTabAlignType.Enum.forInt(tabStopType.ooxmlId) );
}
}

View File

@ -25,6 +25,7 @@ import org.apache.poi.sl.draw.DrawPaint;
import org.apache.poi.sl.usermodel.AutoNumberingScheme; import org.apache.poi.sl.usermodel.AutoNumberingScheme;
import org.apache.poi.sl.usermodel.PaintStyle; import org.apache.poi.sl.usermodel.PaintStyle;
import org.apache.poi.sl.usermodel.PaintStyle.SolidPaint; import org.apache.poi.sl.usermodel.PaintStyle.SolidPaint;
import org.apache.poi.sl.usermodel.TabStop.TabStopType;
import org.apache.poi.sl.usermodel.TextParagraph; import org.apache.poi.sl.usermodel.TextParagraph;
import org.apache.poi.util.Beta; import org.apache.poi.util.Beta;
import org.apache.poi.util.Internal; import org.apache.poi.util.Internal;
@ -32,26 +33,7 @@ import org.apache.poi.util.Units;
import org.apache.poi.xslf.model.ParagraphPropertyFetcher; import org.apache.poi.xslf.model.ParagraphPropertyFetcher;
import org.apache.xmlbeans.XmlCursor; import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlObject; import org.apache.xmlbeans.XmlObject;
import org.openxmlformats.schemas.drawingml.x2006.main.CTColor; import org.openxmlformats.schemas.drawingml.x2006.main.*;
import org.openxmlformats.schemas.drawingml.x2006.main.CTRegularTextRun;
import org.openxmlformats.schemas.drawingml.x2006.main.CTSRgbColor;
import org.openxmlformats.schemas.drawingml.x2006.main.CTTextAutonumberBullet;
import org.openxmlformats.schemas.drawingml.x2006.main.CTTextBulletSizePercent;
import org.openxmlformats.schemas.drawingml.x2006.main.CTTextBulletSizePoint;
import org.openxmlformats.schemas.drawingml.x2006.main.CTTextCharBullet;
import org.openxmlformats.schemas.drawingml.x2006.main.CTTextCharacterProperties;
import org.openxmlformats.schemas.drawingml.x2006.main.CTTextField;
import org.openxmlformats.schemas.drawingml.x2006.main.CTTextFont;
import org.openxmlformats.schemas.drawingml.x2006.main.CTTextLineBreak;
import org.openxmlformats.schemas.drawingml.x2006.main.CTTextNormalAutofit;
import org.openxmlformats.schemas.drawingml.x2006.main.CTTextParagraph;
import org.openxmlformats.schemas.drawingml.x2006.main.CTTextParagraphProperties;
import org.openxmlformats.schemas.drawingml.x2006.main.CTTextSpacing;
import org.openxmlformats.schemas.drawingml.x2006.main.CTTextTabStop;
import org.openxmlformats.schemas.drawingml.x2006.main.CTTextTabStopList;
import org.openxmlformats.schemas.drawingml.x2006.main.STTextAlignType;
import org.openxmlformats.schemas.drawingml.x2006.main.STTextAutonumberScheme;
import org.openxmlformats.schemas.drawingml.x2006.main.STTextFontAlignType;
import org.openxmlformats.schemas.presentationml.x2006.main.CTPlaceholder; import org.openxmlformats.schemas.presentationml.x2006.main.CTPlaceholder;
import org.openxmlformats.schemas.presentationml.x2006.main.STPlaceholderType; import org.openxmlformats.schemas.presentationml.x2006.main.STPlaceholderType;
@ -800,25 +782,27 @@ public class XSLFTextParagraph implements TextParagraph<XSLFShape,XSLFTextParagr
private <T> boolean fetchParagraphProperty(ParagraphPropertyFetcher<T> visitor){ private <T> boolean fetchParagraphProperty(ParagraphPropertyFetcher<T> visitor){
boolean ok = false; boolean ok = false;
XSLFTextShape shape = getParentShape(); final XSLFTextShape shape = getParentShape();
XSLFSheet sheet = shape.getSheet(); final XSLFSheet sheet = shape.getSheet();
if(_p.isSetPPr()) ok = visitor.fetch(_p.getPPr()); if (!(sheet instanceof XSLFSlideMaster)) {
if (ok) return true; if(_p.isSetPPr()) ok = visitor.fetch(_p.getPPr());
if (ok) return true;
ok = shape.fetchShapeProperty(visitor);
if (ok) return true; ok = shape.fetchShapeProperty(visitor);
if (ok) return true;
CTPlaceholder ph = shape.getCTPlaceholder();
if(ph == null){ CTPlaceholder ph = shape.getCTPlaceholder();
// if it is a plain text box then take defaults from presentation.xml if(ph == null){
@SuppressWarnings("resource") // if it is a plain text box then take defaults from presentation.xml
XMLSlideShow ppt = sheet.getSlideShow(); @SuppressWarnings("resource")
CTTextParagraphProperties themeProps = ppt.getDefaultParagraphStyle(getIndentLevel()); XMLSlideShow ppt = sheet.getSlideShow();
if (themeProps != null) ok = visitor.fetch(themeProps); CTTextParagraphProperties themeProps = ppt.getDefaultParagraphStyle(getIndentLevel());
if (themeProps != null) ok = visitor.fetch(themeProps);
}
if (ok) return true;
} }
if (ok) return true;
// defaults for placeholders are defined in the slide master // defaults for placeholders are defined in the slide master
CTTextParagraphProperties defaultProps = getDefaultMasterStyle(); CTTextParagraphProperties defaultProps = getDefaultMasterStyle();
@ -1011,7 +995,51 @@ public class XSLFTextParagraph implements TextParagraph<XSLFShape,XSLFTextParagr
} }
} }
} }
@Override
public List<XSLFTabStop> getTabStops() {
ParagraphPropertyFetcher<List<XSLFTabStop>> fetcher = new ParagraphPropertyFetcher<List<XSLFTabStop>>(getIndentLevel()){
public boolean fetch(CTTextParagraphProperties props) {
if (props.isSetTabLst()) {
final List<XSLFTabStop> list = new ArrayList<>();
for (final CTTextTabStop ta : props.getTabLst().getTabArray()) {
list.add(new XSLFTabStop(ta));
}
setValue(list);
return true;
}
return false;
}
};
fetchParagraphProperty(fetcher);
return fetcher.getValue();
}
@Override
public void addTabStops(double positionInPoints, TabStopType tabStopType) {
final XSLFSheet sheet = getParentShape().getSheet();
final CTTextParagraphProperties tpp;
if (sheet instanceof XSLFSlideMaster) {
tpp = getDefaultMasterStyle();
} else {
final CTTextParagraph xo = getXmlObject();
tpp = (xo.isSetPPr()) ? xo.getPPr() : xo.addNewPPr();
}
final CTTextTabStopList stl = (tpp.isSetTabLst()) ? tpp.getTabLst() : tpp.addNewTabLst();
XSLFTabStop tab = new XSLFTabStop(stl.addNewTab());
tab.setPositionInPoints(positionInPoints);
tab.setType(tabStopType);
}
@Override
public void clearTabStops() {
final XSLFSheet sheet = getParentShape().getSheet();
CTTextParagraphProperties tpp = (sheet instanceof XSLFSlideMaster) ? getDefaultMasterStyle() : getXmlObject().getPPr();
if (tpp != null && tpp.isSetTabLst()) {
tpp.unsetTabLst();
}
}
/** /**
* Helper method for appending text and keeping paragraph and character properties. * Helper method for appending text and keeping paragraph and character properties.
* The character properties are moved to the end paragraph marker * The character properties are moved to the end paragraph marker

View File

@ -20,12 +20,16 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import org.apache.poi.openxml4j.opc.OPCPackage; import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.openxml4j.opc.PackagePart; import org.apache.poi.openxml4j.opc.PackagePart;
import org.apache.poi.sl.usermodel.BaseTestSlideShow; import org.apache.poi.sl.usermodel.BaseTestSlideShow;
import org.apache.poi.sl.usermodel.SlideShow;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -179,4 +183,25 @@ public class TestXMLSlideShow extends BaseTestSlideShow {
xmlComments.close(); xmlComments.close();
xml.close(); xml.close();
} }
public SlideShow<?, ?> reopen(SlideShow<?, ?> show) {
return reopen((XMLSlideShow)show);
}
public static XMLSlideShow reopen(XMLSlideShow show) {
try {
BufAccessBAOS bos = new BufAccessBAOS();
show.write(bos);
return new XMLSlideShow(new ByteArrayInputStream(bos.getBuf()));
} catch (IOException e) {
fail(e.getMessage());
return null;
}
}
private static class BufAccessBAOS extends ByteArrayOutputStream {
public byte[] getBuf() {
return buf;
}
}
} }

View File

@ -0,0 +1,116 @@
/* ====================================================================
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.model.textproperties;
import org.apache.poi.sl.usermodel.TabStop;
import org.apache.poi.util.Internal;
import org.apache.poi.util.Units;
@Internal
public class HSLFTabStop implements TabStop, Cloneable {
/**
* A signed integer that specifies an offset, in master units, of the tab stop.
*
* If the TextPFException record that contains this TabStop structure also contains a
* leftMargin, then the value of position is relative to the left margin of the paragraph;
* otherwise, the value is relative to the left side of the paragraph.
*
* If a TextRuler record contains this TabStop structure, the value is relative to the
* left side of the text ruler.
*/
private int position;
/**
* A enumeration that specifies how text aligns at the tab stop.
*/
private TabStopType type;
public HSLFTabStop(int position, TabStopType type) {
this.position = position;
this.type = type;
}
public int getPosition() {
return position;
}
public void setPosition(final int position) {
this.position = position;
}
@Override
public double getPositionInPoints() {
return Units.masterToPoints(getPosition());
}
@Override
public void setPositionInPoints(final double points) {
setPosition(Units.pointsToMaster(points));
}
@Override
public TabStopType getType() {
return type;
}
@Override
public void setType(TabStopType type) {
this.type = type;
}
@Override
public HSLFTabStop clone() {
try {
return (HSLFTabStop)super.clone();
} catch (CloneNotSupportedException e) {
throw new IllegalStateException(e);
}
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + position;
result = prime * result + ((type == null) ? 0 : type.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof HSLFTabStop)) {
return false;
}
HSLFTabStop other = (HSLFTabStop) obj;
if (position != other.position) {
return false;
}
if (type != other.type) {
return false;
}
return true;
}
@Override
public String toString() {
return type + " @ " + position;
}
}

View File

@ -0,0 +1,158 @@
/* ====================================================================
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.model.textproperties;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import org.apache.poi.sl.usermodel.TabStop.TabStopType;
import org.apache.poi.util.Internal;
import org.apache.poi.util.LittleEndianByteArrayInputStream;
import org.apache.poi.util.LittleEndianConsts;
import org.apache.poi.util.LittleEndianInput;
import org.apache.poi.util.LittleEndianOutput;
import org.apache.poi.util.LittleEndianOutputStream;
/**
* Container for tabstop lists
*/
@Internal
public class HSLFTabStopPropCollection extends TextProp {
public static final String NAME = "tabStops";
private final List<HSLFTabStop> tabStops = new ArrayList<>();
public HSLFTabStopPropCollection() {
super(0, 0x100000, NAME);
}
public HSLFTabStopPropCollection(final HSLFTabStopPropCollection copy) {
super(0, copy.getMask(), copy.getName());
for (HSLFTabStop ts : copy.tabStops) {
tabStops.add(ts.clone());
}
}
/**
* Parses the tabstops from TxMasterStyle record
*
* @param data the data stream
* @param offset the offset within the data
*/
public void parseProperty(byte data[], int offset) {
tabStops.addAll(readTabStops(new LittleEndianByteArrayInputStream(data, offset)));
}
public static List<HSLFTabStop> readTabStops(final LittleEndianInput lei) {
final int count = lei.readUShort();
final List<HSLFTabStop> tabs = new ArrayList<>(count);
for (int i=0; i<count; i++) {
final int position = lei.readShort();
final TabStopType type = TabStopType.fromNativeId(lei.readShort());
tabs.add(new HSLFTabStop(position, type));
}
return tabs;
}
public void writeProperty(OutputStream out) {
writeTabStops(new LittleEndianOutputStream(out), tabStops);
}
public static void writeTabStops(final LittleEndianOutput leo, List<HSLFTabStop> tabStops) {
final int count = tabStops.size();
leo.writeShort(count);
for (HSLFTabStop ts : tabStops) {
leo.writeShort(ts.getPosition());
leo.writeShort(ts.getType().nativeId);
}
}
@Override
public int getValue() { return tabStops.size(); }
@Override
public int getSize() {
return LittleEndianConsts.SHORT_SIZE + tabStops.size()*LittleEndianConsts.INT_SIZE;
}
public List<HSLFTabStop> getTabStops() {
return tabStops;
}
public void clearTabs() {
tabStops.clear();
}
public void addTabStop(HSLFTabStop ts) {
tabStops.add(ts);
}
@Override
public HSLFTabStopPropCollection clone() {
return new HSLFTabStopPropCollection(this);
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result
+ ((tabStops == null) ? 0 : tabStops.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof HSLFTabStopPropCollection)) {
return false;
}
HSLFTabStopPropCollection other = (HSLFTabStopPropCollection) obj;
if (!super.equals(other)) {
return false;
}
return tabStops.equals(other.tabStops);
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder(super.toString());
sb.append(" [ ");
boolean isFirst = true;
for (HSLFTabStop tabStop : tabStops) {
if (!isFirst) {
sb.append(", ");
}
sb.append(tabStop.getType());
sb.append(" @ ");
sb.append(tabStop.getPosition());
isFirst = false;
}
sb.append(" ]");
return sb.toString();
}
}

View File

@ -1,123 +0,0 @@
/* ====================================================================
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.model.textproperties;
import java.util.ArrayList;
import java.util.List;
import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.LittleEndianConsts;
/**
* Container for tabstop lists
*/
public class TabStopPropCollection extends TextProp {
public enum TabStopType {
LEFT(0), CENTER(1), RIGHT(2), DECIMAL(3);
private final int val;
TabStopType(int val) {
this.val = val;
}
public static TabStopType fromRecordVal(int val) {
for (TabStopType tst : values()) {
if (tst.val == val) return tst;
}
return LEFT;
}
}
public static class TabStop {
/**
* If the TextPFException record that contains this TabStop structure also contains a
* leftMargin, then the value of position is relative to the left margin of the paragraph;
* otherwise, the value is relative to the left side of the paragraph.
*
* If a TextRuler record contains this TabStop structure, the value is relative to the
* left side of the text ruler.
*/
private int position;
/**
* A enumeration that specifies how text aligns at the tab stop.
*/
private TabStopType type;
public TabStop(int position, TabStopType type) {
this.position = position;
this.type = type;
}
public int getPosition() {
return position;
}
public void setPosition(int position) {
this.position = position;
}
public TabStopType getType() {
return type;
}
public void setType(TabStopType type) {
this.type = type;
}
}
private List<TabStop> tabStops = new ArrayList<>();
public TabStopPropCollection() {
super(0, 0x100000, "tabStops");
}
/**
* Parses the tabstops from TxMasterStyle record
*
* @param data the data stream
* @param offset the offset within the data
*/
public void parseProperty(byte data[], int offset) {
int count = LittleEndian.getUShort(data, offset);
int off = offset + LittleEndianConsts.SHORT_SIZE;
for (int i=0; i<count; i++) {
int position = LittleEndian.getShort(data, off);
off += LittleEndianConsts.SHORT_SIZE;
int recVal = LittleEndian.getShort(data, off);
TabStopType type = TabStopType.fromRecordVal(recVal);
off += LittleEndianConsts.SHORT_SIZE;
tabStops.add(new TabStop(position, type));
}
}
@Override
public int getSize() {
return LittleEndianConsts.SHORT_SIZE + tabStops.size()*LittleEndianConsts.INT_SIZE;
}
@Override
public TabStopPropCollection clone() {
TabStopPropCollection other = (TabStopPropCollection)super.clone();
other.tabStops = new ArrayList<>();
for (TabStop ts : tabStops) {
TabStop tso = new TabStop(ts.getPosition(), ts.getType());
other.tabStops.add(tso);
}
return other;
}
}

View File

@ -96,7 +96,7 @@ public class TextProp implements Cloneable {
try { try {
return (TextProp)super.clone(); return (TextProp)super.clone();
} catch(CloneNotSupportedException e) { } catch(CloneNotSupportedException e) {
throw new InternalError(e.getMessage()); throw new IllegalStateException(e);
} }
} }
@ -145,11 +145,11 @@ public class TextProp implements Cloneable {
@Override @Override
public String toString() { public String toString() {
int len; int len;
switch (sizeOfDataBlock) { switch (getSize()) {
case 1: len = 4; break; case 1: len = 4; break;
case 2: len = 6; break; case 2: len = 6; break;
default: len = 10; break; default: len = 10; break;
} }
return String.format(Locale.ROOT, "%s = %d (%0#"+len+"X mask / %d bytes)", propName, dataValue, maskInHeader, sizeOfDataBlock); return String.format(Locale.ROOT, "%s = %d (%0#"+len+"X mask / %d bytes)", getName(), getValue(), getMask(), getSize());
} }
} }

View File

@ -58,7 +58,7 @@ public class TextPropCollection {
// 0x200 - Undefined and MUST be ignored // 0x200 - Undefined and MUST be ignored
new TextProp(2, 0x400, "bullet.offset"), // indent new TextProp(2, 0x400, "bullet.offset"), // indent
new TextProp(2, 0x8000, "defaultTabSize"), new TextProp(2, 0x8000, "defaultTabSize"),
new TabStopPropCollection(), // tabstops size is variable! new HSLFTabStopPropCollection(), // tabstops size is variable!
new FontAlignmentProp(), new FontAlignmentProp(),
new WrapFlagsTextProp(), new WrapFlagsTextProp(),
new TextProp(2, 0x200000, "textDirection"), new TextProp(2, 0x200000, "textDirection"),
@ -130,12 +130,14 @@ public class TextPropCollection {
} }
/** Fetch the TextProp with this name, or null if it isn't present */ /** Fetch the TextProp with this name, or null if it isn't present */
public final TextProp findByName(String textPropName) { @SuppressWarnings("unchecked")
return textProps.get(textPropName); public final <T extends TextProp> T findByName(String textPropName) {
return (T)textProps.get(textPropName);
} }
public final TextProp removeByName(String name) { @SuppressWarnings("unchecked")
return textProps.remove(name); public final <T extends TextProp> T removeByName(String name) {
return (T)textProps.remove(name);
} }
public final TextPropType getTextPropType() { public final TextPropType getTextPropType() {
@ -153,10 +155,11 @@ public class TextPropCollection {
* @param name the property name * @param name the property name
* @return if found, the property template to copy from * @return if found, the property template to copy from
*/ */
private TextProp validatePropName(String name) { @SuppressWarnings("unchecked")
private <T extends TextProp> T validatePropName(final String name) {
for (TextProp tp : getPotentialProperties()) { for (TextProp tp : getPotentialProperties()) {
if (tp.getName().equals(name)) { if (tp.getName().equals(name)) {
return tp; return (T)tp;
} }
} }
String errStr = String errStr =
@ -166,13 +169,14 @@ public class TextPropCollection {
} }
/** Add the TextProp with this name to the list */ /** Add the TextProp with this name to the list */
public final TextProp addWithName(String name) { @SuppressWarnings("unchecked")
public final <T extends TextProp> T addWithName(final String name) {
// Find the base TextProp to base on // Find the base TextProp to base on
TextProp existing = findByName(name); T existing = findByName(name);
if (existing != null) return existing; if (existing != null) return existing;
// Add a copy of this property // Add a copy of this property
TextProp textProp = validatePropName(name).clone(); T textProp = (T)validatePropName(name).clone();
textProps.put(name,textProp); textProps.put(name,textProp);
return textProp; return textProp;
} }
@ -218,11 +222,13 @@ public class TextPropCollection {
// Bingo, data contains this property // Bingo, data contains this property
TextProp prop = tp.clone(); TextProp prop = tp.clone();
int val = 0; int val = 0;
if (prop.getSize() == 2) { if (prop instanceof HSLFTabStopPropCollection) {
((HSLFTabStopPropCollection)prop).parseProperty(data, dataOffset+bytesPassed);
} else if (prop.getSize() == 2) {
val = LittleEndian.getShort(data,dataOffset+bytesPassed); val = LittleEndian.getShort(data,dataOffset+bytesPassed);
} else if(prop.getSize() == 4) { } else if(prop.getSize() == 4) {
val = LittleEndian.getInt(data,dataOffset+bytesPassed); val = LittleEndian.getInt(data,dataOffset+bytesPassed);
} else if (prop.getSize() == 0 && !(prop instanceof TabStopPropCollection)) { } else if (prop.getSize() == 0) {
//remember "special" bits. //remember "special" bits.
maskSpecial |= tp.getMask(); maskSpecial |= tp.getMask();
continue; continue;
@ -230,9 +236,7 @@ public class TextPropCollection {
if (prop instanceof BitMaskTextProp) { if (prop instanceof BitMaskTextProp) {
((BitMaskTextProp)prop).setValueWithMask(val, containsField); ((BitMaskTextProp)prop).setValueWithMask(val, containsField);
} else if (prop instanceof TabStopPropCollection) { } else if (!(prop instanceof HSLFTabStopPropCollection)) {
((TabStopPropCollection)prop).parseProperty(data, dataOffset+bytesPassed);
} else {
prop.setValue(val); prop.setValue(val);
} }
bytesPassed += prop.getSize(); bytesPassed += prop.getSize();
@ -311,6 +315,8 @@ public class TextPropCollection {
StyleTextPropAtom.writeLittleEndian((short)val,o); StyleTextPropAtom.writeLittleEndian((short)val,o);
} else if (textProp.getSize() == 4) { } else if (textProp.getSize() == 4) {
StyleTextPropAtom.writeLittleEndian(val,o); StyleTextPropAtom.writeLittleEndian(val,o);
} else if (textProp instanceof HSLFTabStopPropCollection) {
((HSLFTabStopPropCollection)textProp).writeProperty(o);
} }
} }
} }
@ -359,8 +365,9 @@ public class TextPropCollection {
out.append(" indent level: "+getIndentLevel()+"\n"); out.append(" indent level: "+getIndentLevel()+"\n");
} }
for(TextProp p : getTextPropList()) { for(TextProp p : getTextPropList()) {
out.append(" " + p.getName() + " = " + p.getValue() ); out.append(" ");
out.append(" (0x" + HexDump.toHex(p.getValue()) + ")\n"); out.append(p.toString());
out.append("\n");
if (p instanceof BitMaskTextProp) { if (p instanceof BitMaskTextProp) {
BitMaskTextProp bm = (BitMaskTextProp)p; BitMaskTextProp bm = (BitMaskTextProp)p;
int i = 0; int i = 0;

View File

@ -17,11 +17,21 @@
package org.apache.poi.hslf.record; package org.apache.poi.hslf.record;
import static org.apache.poi.util.BitFieldFactory.getInstance;
import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.poi.util.IOUtils; import org.apache.poi.hslf.model.textproperties.HSLFTabStop;
import org.apache.poi.hslf.model.textproperties.HSLFTabStopPropCollection;
import org.apache.poi.util.BitField;
import org.apache.poi.util.LittleEndian; import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.LittleEndianByteArrayInputStream;
import org.apache.poi.util.LittleEndianOutputStream;
import org.apache.poi.util.POILogger; import org.apache.poi.util.POILogger;
/** /**
@ -31,33 +41,38 @@ public final class TextRulerAtom extends RecordAtom {
//arbitrarily selected; may need to increase //arbitrarily selected; may need to increase
private static final int MAX_RECORD_LENGTH = 100_000; private static final int MAX_RECORD_LENGTH = 100_000;
private static final BitField DEFAULT_TAB_SIZE = getInstance(0x0001);
private static final BitField C_LEVELS = getInstance(0x0002);
private static final BitField TAB_STOPS = getInstance(0x0004);
private static final BitField[] LEFT_MARGIN = {
getInstance(0x0008), getInstance(0x0010), getInstance(0x0020),
getInstance(0x0040), getInstance(0x0080),
};
private static final BitField[] INDENT = {
getInstance(0x0100), getInstance(0x0200), getInstance(0x0400),
getInstance(0x0800), getInstance(0x1000),
};
/** /**
* Record header. * Record header.
*/ */
private byte[] _header; private final byte[] _header = new byte[8];
/**
* Record data.
*/
private byte[] _data;
//ruler internals //ruler internals
private int defaultTabSize; private Integer defaultTabSize;
private int numLevels; private Integer numLevels;
private int[] tabStops; private final List<HSLFTabStop> tabStops = new ArrayList<>();
private int[] bulletOffsets = new int[5]; //bullet.offset
private int[] textOffsets = new int[5]; private final Integer[] leftMargin = new Integer[5];
//text.offset
private final Integer[] indent = new Integer[5];
/** /**
* Constructs a new empty ruler atom. * Constructs a new empty ruler atom.
*/ */
public TextRulerAtom() { public TextRulerAtom() {
_header = new byte[8];
_data = new byte[0];
LittleEndian.putShort(_header, 2, (short)getRecordType()); LittleEndian.putShort(_header, 2, (short)getRecordType());
LittleEndian.putInt(_header, 4, _data.length);
} }
/** /**
@ -68,18 +83,17 @@ public final class TextRulerAtom extends RecordAtom {
* @param start the start offset into the byte array. * @param start the start offset into the byte array.
* @param len the length of the slice in the byte array. * @param len the length of the slice in the byte array.
*/ */
protected TextRulerAtom(byte[] source, int start, int len) { protected TextRulerAtom(final byte[] source, final int start, final int len) {
// Get the header. final LittleEndianByteArrayInputStream leis = new LittleEndianByteArrayInputStream(source, start, Math.min(len, MAX_RECORD_LENGTH));
_header = new byte[8];
System.arraycopy(source,start,_header,0,8);
// Get the record data.
_data = IOUtils.safelyAllocate(len-8, MAX_RECORD_LENGTH);
System.arraycopy(source,start+8,_data,0,len-8);
try { try {
read(); // Get the header.
} catch (Exception e){ leis.read(_header);
// Get the record data.
read(leis);
} catch (IOException e){
logger.log(POILogger.ERROR, "Failed to parse TextRulerAtom: " + e.getMessage()); logger.log(POILogger.ERROR, "Failed to parse TextRulerAtom: " + e.getMessage());
} }
} }
@ -89,6 +103,7 @@ public final class TextRulerAtom extends RecordAtom {
* *
* @return the record type. * @return the record type.
*/ */
@Override
public long getRecordType() { public long getRecordType() {
return RecordTypes.TextRulerAtom.typeID; return RecordTypes.TextRulerAtom.typeID;
} }
@ -100,109 +115,110 @@ public final class TextRulerAtom extends RecordAtom {
* @param out the output stream to write to. * @param out the output stream to write to.
* @throws java.io.IOException if an error occurs. * @throws java.io.IOException if an error occurs.
*/ */
public void writeOut(OutputStream out) throws IOException { @Override
public void writeOut(final OutputStream out) throws IOException {
final ByteArrayOutputStream bos = new ByteArrayOutputStream(200);
final LittleEndianOutputStream lbos = new LittleEndianOutputStream(bos);
int mask = 0;
mask |= writeIf(lbos, numLevels, C_LEVELS);
mask |= writeIf(lbos, defaultTabSize, DEFAULT_TAB_SIZE);
mask |= writeIf(lbos, tabStops, TAB_STOPS);
for (int i=0; i<5; i++) {
mask |= writeIf(lbos, leftMargin[i], LEFT_MARGIN[i]);
mask |= writeIf(lbos, indent[i], INDENT[i]);
}
LittleEndian.putInt(_header, 4, bos.size()+4);
out.write(_header); out.write(_header);
out.write(_data); LittleEndian.putUShort(mask, out);
LittleEndian.putUShort(0, out);
bos.writeTo(out);
} }
private static int writeIf(final LittleEndianOutputStream lbos, Integer value, BitField bit) {
boolean isSet = false;
if (value != null) {
lbos.writeShort(value);
isSet = true;
}
return bit.setBoolean(0, isSet);
}
private static int writeIf(final LittleEndianOutputStream lbos, List<HSLFTabStop> value, BitField bit) {
boolean isSet = false;
if (value != null && !value.isEmpty()) {
HSLFTabStopPropCollection.writeTabStops(lbos, value);
isSet = true;
}
return bit.setBoolean(0, isSet);
}
/** /**
* Read the record bytes and initialize the internal variables * Read the record bytes and initialize the internal variables
*/ */
private void read(){ private void read(final LittleEndianByteArrayInputStream leis) {
int pos = 0; final int mask = leis.readInt();
short mask = LittleEndian.getShort(_data); pos += 4; numLevels = readIf(leis, mask, C_LEVELS);
short val; defaultTabSize = readIf(leis, mask, DEFAULT_TAB_SIZE);
int[] bits = {1, 0, 2, 3, 8, 4, 9, 5, 10, 6, 11, 7, 12}; if (TAB_STOPS.isSet(mask)) {
for (int i = 0; i < bits.length; i++) { tabStops.addAll(HSLFTabStopPropCollection.readTabStops(leis));
if((mask & 1 << bits[i]) != 0){ }
switch (bits[i]){ for (int i=0; i<5; i++) {
case 0: leftMargin[i] = readIf(leis, mask, LEFT_MARGIN[i]);
//defaultTabSize indent[i] = readIf(leis, mask, INDENT[i]);
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:
break;
}
}
} }
} }
private static Integer readIf(final LittleEndianByteArrayInputStream leis, final int mask, final BitField bit) {
return (bit.isSet(mask)) ? (int)leis.readShort() : null;
}
/** /**
* Default distance between tab stops, in master coordinates (576 dpi). * Default distance between tab stops, in master coordinates (576 dpi).
*/ */
public int getDefaultTabSize(){ public int getDefaultTabSize(){
return defaultTabSize; return defaultTabSize == null ? 0 : defaultTabSize;
} }
/** /**
* Number of indent levels (maximum 5). * Number of indent levels (maximum 5).
*/ */
public int getNumberOfLevels(){ public int getNumberOfLevels(){
return numLevels; return numLevels == null ? 0 : numLevels;
} }
/** /**
* Default distance between tab stops, in master coordinates (576 dpi). * Default distance between tab stops, in master coordinates (576 dpi).
*/ */
public int[] getTabStops(){ public List<HSLFTabStop> getTabStops(){
return tabStops; return tabStops;
} }
/** /**
* Paragraph's distance from shape's left margin, in master coordinates (576 dpi). * Paragraph's distance from shape's left margin, in master coordinates (576 dpi).
*/ */
public int[] getTextOffsets(){ public Integer[] getTextOffsets(){
return textOffsets; return indent;
} }
/** /**
* First line of paragraph's distance from shape's left margin, in master coordinates (576 dpi). * First line of paragraph's distance from shape's left margin, in master coordinates (576 dpi).
*/ */
public int[] getBulletOffsets(){ public Integer[] getBulletOffsets(){
return bulletOffsets; return leftMargin;
} }
public static TextRulerAtom getParagraphInstance(){ public static TextRulerAtom getParagraphInstance(){
byte[] data = new byte[] { final TextRulerAtom tra = new TextRulerAtom();
0x00, 0x00, (byte)0xA6, 0x0F, 0x0A, 0x00, 0x00, 0x00, tra.indent[0] = 249;
0x10, 0x03, 0x00, 0x00, (byte)0xF9, 0x00, 0x41, 0x01, 0x41, 0x01 tra.indent[1] = tra.leftMargin[1] = 321;
}; return tra;
return new TextRulerAtom(data, 0, data.length);
} }
public void setParagraphIndent(short tetxOffset, short bulletOffset){ public void setParagraphIndent(short leftMargin, short indent) {
LittleEndian.putShort(_data, 4, tetxOffset); Arrays.fill(this.leftMargin, null);
LittleEndian.putShort(_data, 6, bulletOffset); Arrays.fill(this.indent, null);
LittleEndian.putShort(_data, 8, bulletOffset); this.leftMargin[0] = (int)leftMargin;
this.indent[0] = (int)indent;
this.indent[1] = (int)indent;
} }
} }

View File

@ -17,16 +17,13 @@
package org.apache.poi.hslf.usermodel; package org.apache.poi.hslf.usermodel;
import org.apache.poi.hslf.model.textproperties.TextPropCollection;
import org.apache.poi.hslf.record.SheetContainer; import org.apache.poi.hslf.record.SheetContainer;
import org.apache.poi.hslf.model.textproperties.TextProp; import org.apache.poi.hslf.record.TextHeaderAtom;
import org.apache.poi.sl.usermodel.MasterSheet; import org.apache.poi.sl.usermodel.MasterSheet;
/** /**
* The superclass of all master sheets - Slide masters, Notes masters, etc. * The superclass of all master sheets - Slide masters, Notes masters, etc.
*
* For now it's empty. When we understand more about masters in ppt we will add the common functionality here.
*
* @author Yegor Kozlov
*/ */
public abstract class HSLFMasterSheet extends HSLFSheet implements MasterSheet<HSLFShape,HSLFTextParagraph> { public abstract class HSLFMasterSheet extends HSLFSheet implements MasterSheet<HSLFShape,HSLFTextParagraph> {
public HSLFMasterSheet(SheetContainer container, int sheetNo){ public HSLFMasterSheet(SheetContainer container, int sheetNo){
@ -34,10 +31,19 @@ public abstract class HSLFMasterSheet extends HSLFSheet implements MasterSheet<H
} }
/** /**
* Pickup a style attribute from the master. * Find the master collection for the given txtype/level/name.
* This is the "workhorse" which returns the default style attrubutes. * This is the "workhorse" which returns the default style attributes.
* If {@code name = "*"} return the current collection, otherwise if the name is not found
* in the current selection of txtype/level/name, first try lower levels then try parent types,
* if it wasn't found there return {@code null}.
*
* @param txtype the {@link TextHeaderAtom} type
* @param level the indent level of the paragraph, if the level is not defined for the found
* collection, the highest existing level will be used
* @param name the property name,
* @param isCharacter if {@code true} use character styles, otherwise use paragraph styles
*/ */
public abstract TextProp getStyleAttribute(int txtype, int level, String name, boolean isCharacter) ; public abstract TextPropCollection getPropCollection(int txtype, int level, String name, boolean isCharacter);
/** /**

View File

@ -74,42 +74,50 @@ public final class HSLFSlideMaster extends HSLFMasterSheet {
} }
/** /**
* Pickup a style attribute from the master. * Find the master collection for the given txtype/level/name.
* This is the "workhorse" which returns the default style attributes. * This is the "workhorse" which returns the default style attributes.
* If {@code name = "*"} return the current collection, otherwise if the name is not found
* in the current selection of txtype/level/name, first try lower levels then try parent types,
* if it wasn't found there return {@code null}.
*
* @param txtype the {@link TextHeaderAtom} type
* @param level the indent level of the paragraph, if the level is not defined for the found
* collection, the highest existing level will be used
* @param name the property name,
* @param isCharacter if {@code true} use character styles, otherwise use paragraph styles
*/ */
@Override @Override
public TextProp getStyleAttribute(int txtype, int level, String name, boolean isCharacter) { public TextPropCollection getPropCollection(final int txtype, final int level, final String name, final boolean isCharacter) {
if (_txmaster.length <= txtype) { if (txtype < _txmaster.length) {
return null; final TxMasterStyleAtom t = _txmaster[txtype];
} final List<TextPropCollection> styles = isCharacter ? t.getCharacterStyles() : t.getParagraphStyles();
TxMasterStyleAtom t = _txmaster[txtype]; // TODO: what is the reaction for readOnly=false and styles.isEmpty()?
List<TextPropCollection> styles = isCharacter ? t.getCharacterStyles() : t.getParagraphStyles(); final int minLevel = Math.min(level, styles.size()-1);
if ("*".equals(name)) {
TextProp prop = null; return styles.get(minLevel);
for (int i = Math.min(level, styles.size()-1); prop == null && i >= 0; i--) { }
prop = styles.get(i).findByName(name);
for (int i=minLevel; i >= 0; i--) {
final TextPropCollection col = styles.get(i);
final TextProp tp = col.findByName(name);
if (tp != null) {
return col;
}
}
} }
if (prop != null) {
return prop;
}
switch (txtype) { switch (txtype) {
case TextHeaderAtom.CENTRE_BODY_TYPE: case TextHeaderAtom.CENTRE_BODY_TYPE:
case TextHeaderAtom.HALF_BODY_TYPE: case TextHeaderAtom.HALF_BODY_TYPE:
case TextHeaderAtom.QUARTER_BODY_TYPE: case TextHeaderAtom.QUARTER_BODY_TYPE:
txtype = TextHeaderAtom.BODY_TYPE; return getPropCollection(TextHeaderAtom.BODY_TYPE, level, name, isCharacter);
break;
case TextHeaderAtom.CENTER_TITLE_TYPE: case TextHeaderAtom.CENTER_TITLE_TYPE:
txtype = TextHeaderAtom.TITLE_TYPE; return getPropCollection(TextHeaderAtom.TITLE_TYPE, level, name, isCharacter);
break;
default: default:
return null; return null;
} }
return getStyleAttribute(txtype, level, name, isCharacter);
} }
/** /**
* Assign SlideShow for this slide master. * Assign SlideShow for this slide master.
*/ */
@ -132,7 +140,7 @@ public final class HSLFSlideMaster extends HSLFMasterSheet {
_txmaster[txType] = txrec[i]; _txmaster[txType] = txrec[i];
} }
} }
for (List<HSLFTextParagraph> paras : getTextParagraphs()) { for (List<HSLFTextParagraph> paras : getTextParagraphs()) {
for (HSLFTextParagraph htp : paras) { for (HSLFTextParagraph htp : paras) {
int txType = htp.getRunType(); int txType = htp.getRunType();
@ -148,11 +156,6 @@ public final class HSLFSlideMaster extends HSLFMasterSheet {
charStyles.size() <= level || paragraphStyles.size() <= level) { charStyles.size() <= level || paragraphStyles.size() <= level) {
throw new HSLFException("Master styles not initialized"); throw new HSLFException("Master styles not initialized");
} }
htp.setMasterStyleReference(paragraphStyles.get(level));
for (HSLFTextRun htr : htp.getTextRuns()) {
htr.setMasterStyleReference(charStyles.get(level));
}
} }
} }
} }

View File

@ -24,12 +24,16 @@ import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.apache.poi.common.usermodel.fonts.FontGroup; import org.apache.poi.common.usermodel.fonts.FontGroup;
import org.apache.poi.common.usermodel.fonts.FontInfo; import org.apache.poi.common.usermodel.fonts.FontInfo;
import org.apache.poi.hslf.exceptions.HSLFException; import org.apache.poi.hslf.exceptions.HSLFException;
import org.apache.poi.hslf.model.textproperties.BitMaskTextProp; import org.apache.poi.hslf.model.textproperties.BitMaskTextProp;
import org.apache.poi.hslf.model.textproperties.FontAlignmentProp; import org.apache.poi.hslf.model.textproperties.FontAlignmentProp;
import org.apache.poi.hslf.model.textproperties.HSLFTabStop;
import org.apache.poi.hslf.model.textproperties.HSLFTabStopPropCollection;
import org.apache.poi.hslf.model.textproperties.IndentProp; import org.apache.poi.hslf.model.textproperties.IndentProp;
import org.apache.poi.hslf.model.textproperties.ParagraphFlagsTextProp; import org.apache.poi.hslf.model.textproperties.ParagraphFlagsTextProp;
import org.apache.poi.hslf.model.textproperties.TextAlignmentProp; import org.apache.poi.hslf.model.textproperties.TextAlignmentProp;
@ -37,34 +41,15 @@ import org.apache.poi.hslf.model.textproperties.TextPFException9;
import org.apache.poi.hslf.model.textproperties.TextProp; import org.apache.poi.hslf.model.textproperties.TextProp;
import org.apache.poi.hslf.model.textproperties.TextPropCollection; import org.apache.poi.hslf.model.textproperties.TextPropCollection;
import org.apache.poi.hslf.model.textproperties.TextPropCollection.TextPropType; import org.apache.poi.hslf.model.textproperties.TextPropCollection.TextPropType;
import org.apache.poi.hslf.record.ColorSchemeAtom; import org.apache.poi.hslf.record.*;
import org.apache.poi.hslf.record.EscherTextboxWrapper;
import org.apache.poi.hslf.record.InteractiveInfo;
import org.apache.poi.hslf.record.MasterTextPropAtom;
import org.apache.poi.hslf.record.OutlineTextRefAtom;
import org.apache.poi.hslf.record.PPDrawing;
import org.apache.poi.hslf.record.Record;
import org.apache.poi.hslf.record.RecordContainer;
import org.apache.poi.hslf.record.RecordTypes;
import org.apache.poi.hslf.record.RoundTripHFPlaceholder12;
import org.apache.poi.hslf.record.SlideListWithText;
import org.apache.poi.hslf.record.SlidePersistAtom;
import org.apache.poi.hslf.record.StyleTextProp9Atom;
import org.apache.poi.hslf.record.StyleTextPropAtom;
import org.apache.poi.hslf.record.TextBytesAtom;
import org.apache.poi.hslf.record.TextCharsAtom;
import org.apache.poi.hslf.record.TextHeaderAtom;
import org.apache.poi.hslf.record.TextRulerAtom;
import org.apache.poi.hslf.record.TextSpecInfoAtom;
import org.apache.poi.hslf.record.TxInteractiveInfoAtom;
import org.apache.poi.sl.draw.DrawPaint; import org.apache.poi.sl.draw.DrawPaint;
import org.apache.poi.sl.usermodel.AutoNumberingScheme; import org.apache.poi.sl.usermodel.AutoNumberingScheme;
import org.apache.poi.sl.usermodel.MasterSheet;
import org.apache.poi.sl.usermodel.PaintStyle; import org.apache.poi.sl.usermodel.PaintStyle;
import org.apache.poi.sl.usermodel.PaintStyle.SolidPaint; import org.apache.poi.sl.usermodel.PaintStyle.SolidPaint;
import org.apache.poi.sl.usermodel.Placeholder; import org.apache.poi.sl.usermodel.Placeholder;
import org.apache.poi.sl.usermodel.TabStop;
import org.apache.poi.sl.usermodel.TabStop.TabStopType;
import org.apache.poi.sl.usermodel.TextParagraph; import org.apache.poi.sl.usermodel.TextParagraph;
import org.apache.poi.util.Internal;
import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger; import org.apache.poi.util.POILogger;
import org.apache.poi.util.StringUtil; import org.apache.poi.util.StringUtil;
@ -93,7 +78,6 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText
private TextBytesAtom _byteAtom; private TextBytesAtom _byteAtom;
private TextCharsAtom _charAtom; private TextCharsAtom _charAtom;
private TextPropCollection _paragraphStyle = new TextPropCollection(1, TextPropType.paragraph); private TextPropCollection _paragraphStyle = new TextPropCollection(1, TextPropType.paragraph);
private TextPropCollection _masterStyle;
protected TextRulerAtom _ruler; protected TextRulerAtom _ruler;
protected final List<HSLFTextRun> _runs = new ArrayList<>(); protected final List<HSLFTextRun> _runs = new ArrayList<>();
@ -107,6 +91,33 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText
private final List<HSLFTextParagraph> parentList; private final List<HSLFTextParagraph> parentList;
private class HSLFTabStopDecorator implements TabStop {
final HSLFTabStop tabStop;
HSLFTabStopDecorator(final HSLFTabStop tabStop) {
this.tabStop = tabStop;
}
public double getPositionInPoints() {
return tabStop.getPositionInPoints();
}
public void setPositionInPoints(double position) {
tabStop.setPositionInPoints(position);
setDirty();
}
public TabStopType getType() {
return tabStop.getType();
}
public void setType(TabStopType type) {
tabStop.setType(type);
setDirty();
}
}
/** /**
* Constructs a Text Run from a Unicode text block. * Constructs a Text Run from a Unicode text block.
* Either a {@link TextCharsAtom} or a {@link TextBytesAtom} needs to be provided. * Either a {@link TextCharsAtom} or a {@link TextBytesAtom} needs to be provided.
@ -160,18 +171,6 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText
_paragraphStyle.copy(paragraphStyle); _paragraphStyle.copy(paragraphStyle);
} }
/**
* Setting a master style reference
*
* @param paragraphStyle the master style reference
*
* @since POI 3.14-Beta1
*/
@Internal
/* package */ void setMasterStyleReference(TextPropCollection masterStyle) {
_masterStyle = masterStyle;
}
/** /**
* Supply the Sheet we belong to, which might have an assigned SlideShow * Supply the Sheet we belong to, which might have an assigned SlideShow
* Also passes it on to our child RichTextRuns * Also passes it on to our child RichTextRuns
@ -341,7 +340,7 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText
@Override @Override
public Double getLeftMargin() { public Double getLeftMargin() {
TextProp tp = getPropVal(_paragraphStyle, _masterStyle, "text.offset"); TextProp tp = getPropVal(_paragraphStyle, "text.offset");
return (tp == null) ? null : Units.masterToPoints(tp.getValue()); return (tp == null) ? null : Units.masterToPoints(tp.getValue());
} }
@ -364,7 +363,7 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText
@Override @Override
public Double getIndent() { public Double getIndent() {
TextProp tp = getPropVal(_paragraphStyle, _masterStyle, "bullet.offset"); TextProp tp = getPropVal(_paragraphStyle, "bullet.offset");
return (tp == null) ? null : Units.masterToPoints(tp.getValue()); return (tp == null) ? null : Units.masterToPoints(tp.getValue());
} }
@ -388,7 +387,7 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText
if (fontInfo == null) { if (fontInfo == null) {
fontInfo = HSLFFontInfoPredefined.ARIAL; fontInfo = HSLFFontInfoPredefined.ARIAL;
} }
return fontInfo.getTypeface(); return fontInfo.getTypeface();
} }
@ -422,7 +421,7 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText
@Override @Override
public TextAlign getTextAlign() { public TextAlign getTextAlign() {
TextProp tp = getPropVal(_paragraphStyle, _masterStyle, "alignment"); TextProp tp = getPropVal(_paragraphStyle, "alignment");
if (tp == null) { if (tp == null) {
return null; return null;
} }
@ -440,7 +439,7 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText
@Override @Override
public FontAlign getFontAlign() { public FontAlign getFontAlign() {
TextProp tp = getPropVal(_paragraphStyle, _masterStyle, FontAlignmentProp.NAME); TextProp tp = getPropVal(_paragraphStyle, FontAlignmentProp.NAME);
if (tp == null) { if (tp == null) {
return null; return null;
} }
@ -606,7 +605,7 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText
* Returns the bullet character * Returns the bullet character
*/ */
public Character getBulletChar() { public Character getBulletChar() {
TextProp tp = getPropVal(_paragraphStyle, _masterStyle, "bullet.char"); TextProp tp = getPropVal(_paragraphStyle, "bullet.char");
return (tp == null) ? null : (char)tp.getValue(); return (tp == null) ? null : (char)tp.getValue();
} }
@ -637,7 +636,7 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText
* Returns the bullet color * Returns the bullet color
*/ */
public Color getBulletColor() { public Color getBulletColor() {
TextProp tp = getPropVal(_paragraphStyle, _masterStyle, "bullet.color"); TextProp tp = getPropVal(_paragraphStyle, "bullet.color");
boolean hasColor = getFlag(ParagraphFlagsTextProp.BULLET_HARDCOLOR_IDX); boolean hasColor = getFlag(ParagraphFlagsTextProp.BULLET_HARDCOLOR_IDX);
if (tp == null || !hasColor) { if (tp == null || !hasColor) {
// if bullet color is undefined, return color of first run // if bullet color is undefined, return color of first run
@ -661,7 +660,7 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText
*/ */
public void setBulletFont(String typeface) { public void setBulletFont(String typeface) {
if (typeface == null) { if (typeface == null) {
setPropVal(_paragraphStyle, _masterStyle, "bullet.font", null); setPropVal(_paragraphStyle, "bullet.font", null);
setFlag(ParagraphFlagsTextProp.BULLET_HARDFONT_IDX, false); setFlag(ParagraphFlagsTextProp.BULLET_HARDFONT_IDX, false);
return; return;
} }
@ -677,7 +676,7 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText
* Returns the bullet font * Returns the bullet font
*/ */
public String getBulletFont() { public String getBulletFont() {
TextProp tp = getPropVal(_paragraphStyle, _masterStyle, "bullet.font"); TextProp tp = getPropVal(_paragraphStyle, "bullet.font");
boolean hasFont = getFlag(ParagraphFlagsTextProp.BULLET_HARDFONT_IDX); boolean hasFont = getFlag(ParagraphFlagsTextProp.BULLET_HARDFONT_IDX);
if (tp == null || !hasFont) { if (tp == null || !hasFont) {
return getDefaultFontFamily(); return getDefaultFontFamily();
@ -723,8 +722,64 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText
return null; return null;
} }
@Override
public List<? extends TabStop> getTabStops() {
final List<HSLFTabStop> tabStops;
final TextRulerAtom textRuler;
if (getSheet() instanceof HSLFSlideMaster) {
final HSLFTabStopPropCollection tpc = getMasterPropVal(_paragraphStyle, HSLFTabStopPropCollection.NAME);
if (tpc == null) {
return null;
}
tabStops = tpc.getTabStops();
textRuler = null;
} else {
textRuler = (TextRulerAtom)_headerAtom.getParentRecord().findFirstOfType(RecordTypes.TextRulerAtom.typeID);
if (textRuler == null) {
return null;
}
tabStops = textRuler.getTabStops();
}
return tabStops.stream().map((tabStop) -> new HSLFTabStopDecorator(tabStop)).collect(Collectors.toList());
}
@Override
public void addTabStops(final double positionInPoints, final TabStopType tabStopType) {
final HSLFTabStop ts = new HSLFTabStop(0, tabStopType);
ts.setPositionInPoints(positionInPoints);
if (getSheet() instanceof HSLFSlideMaster) {
final Consumer<HSLFTabStopPropCollection> con = (tp) -> tp.addTabStop(ts);
setPropValInner(_paragraphStyle, HSLFTabStopPropCollection.NAME, con);
} else {
final RecordContainer cont = _headerAtom.getParentRecord();
TextRulerAtom textRuler = (TextRulerAtom)cont.findFirstOfType(RecordTypes.TextRulerAtom.typeID);
if (textRuler == null) {
textRuler = TextRulerAtom.getParagraphInstance();
cont.appendChildRecord(textRuler);
}
textRuler.getTabStops().add(ts);
}
}
@Override
public void clearTabStops() {
if (getSheet() instanceof HSLFSlideMaster) {
setPropValInner(_paragraphStyle, HSLFTabStopPropCollection.NAME, null);
} else {
final RecordContainer cont = _headerAtom.getParentRecord();
final TextRulerAtom textRuler = (TextRulerAtom)cont.findFirstOfType(RecordTypes.TextRulerAtom.typeID);
if (textRuler == null) {
return;
}
textRuler.getTabStops().clear();
}
}
private Double getPctOrPoints(String propName) { private Double getPctOrPoints(String propName) {
TextProp tp = getPropVal(_paragraphStyle, _masterStyle, propName); TextProp tp = getPropVal(_paragraphStyle, propName);
if (tp == null) { if (tp == null) {
return null; return null;
} }
@ -741,7 +796,7 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText
} }
private boolean getFlag(int index) { private boolean getFlag(int index) {
BitMaskTextProp tp = (BitMaskTextProp)getPropVal(_paragraphStyle, _masterStyle, ParagraphFlagsTextProp.NAME); BitMaskTextProp tp = getPropVal(_paragraphStyle, ParagraphFlagsTextProp.NAME);
return (tp == null) ? false : tp.getSubValue(index); return (tp == null) ? false : tp.getSubValue(index);
} }
@ -759,56 +814,54 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText
* The propName can be a comma-separated list, in case multiple equivalent values * The propName can be a comma-separated list, in case multiple equivalent values
* are queried * are queried
*/ */
protected TextProp getPropVal(TextPropCollection props, TextPropCollection masterProps, String propName) { protected <T extends TextProp> T getPropVal(TextPropCollection props, String propName) {
String propNames[] = propName.split(","); String propNames[] = propName.split(",");
for (String pn : propNames) { for (String pn : propNames) {
TextProp prop = props.findByName(pn); T prop = props.findByName(pn);
if (isValidProp(prop)) { if (isValidProp(prop)) {
return prop; return prop;
} }
} }
return getMasterPropVal(props, masterProps, propName); return getMasterPropVal(props, propName);
} }
private TextProp getMasterPropVal(TextPropCollection props, TextPropCollection masterProps, String propName) { private <T extends TextProp> T getMasterPropVal(final TextPropCollection props, final String propName) {
boolean isChar = props.getTextPropType() == TextPropType.character; boolean isChar = props.getTextPropType() == TextPropType.character;
// check if we can delegate to master for the property // check if we can delegate to master for the property
if (!isChar) { if (!isChar) {
BitMaskTextProp maskProp = (BitMaskTextProp) props.findByName(ParagraphFlagsTextProp.NAME); BitMaskTextProp maskProp = props.findByName(ParagraphFlagsTextProp.NAME);
boolean hardAttribute = (maskProp != null && maskProp.getValue() == 0); boolean hardAttribute = (maskProp != null && maskProp.getValue() == 0);
if (hardAttribute) { if (hardAttribute) {
return null; return null;
} }
} }
String propNames[] = propName.split(","); final String propNames[] = propName.split(",");
if (masterProps == null) { final HSLFSheet sheet = getSheet();
HSLFSheet sheet = getSheet(); final int txtype = getRunType();
int txtype = getRunType(); final HSLFMasterSheet master;
HSLFMasterSheet master = sheet.getMasterSheet(); if (sheet instanceof HSLFMasterSheet) {
master = (HSLFMasterSheet)sheet;
} else {
master = sheet.getMasterSheet();
if (master == null) { if (master == null) {
logger.log(POILogger.WARN, "MasterSheet is not available"); logger.log(POILogger.WARN, "MasterSheet is not available");
return null; return null;
} }
}
for (String pn : propNames) { for (String pn : propNames) {
TextProp prop = master.getStyleAttribute(txtype, getIndentLevel(), pn, isChar); TextPropCollection masterProps = master.getPropCollection(txtype, getIndentLevel(), pn, isChar);
if (isValidProp(prop)) { if (masterProps != null) {
return prop; T prop = masterProps.findByName(pn);
}
}
} else {
for (String pn : propNames) {
TextProp prop = masterProps.findByName(pn);
if (isValidProp(prop)) { if (isValidProp(prop)) {
return prop; return prop;
} }
} }
} }
return null; return null;
} }
@ -826,22 +879,33 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText
* @param name the name of the TextProp to fetch/add * @param name the name of the TextProp to fetch/add
* @param val the value, null if unset * @param val the value, null if unset
*/ */
protected void setPropVal(TextPropCollection props, TextPropCollection masterProps, String name, Integer val) { protected void setPropVal(final TextPropCollection props, final String name, final Integer val) {
TextPropCollection pc = props; setPropValInner(props, name, val == null ? null : tp -> tp.setValue(val));
if (getSheet() instanceof MasterSheet && masterProps != null) {
pc = masterProps;
}
if (val == null) {
pc.removeByName(name);
return;
}
// Fetch / Add the TextProp
TextProp tp = pc.addWithName(name);
tp.setValue(val);
} }
private void setPropValInner(final TextPropCollection props, final String name, Consumer<? extends TextProp> handler) {
final boolean isChar = props.getTextPropType() == TextPropType.character;
final TextPropCollection pc;
if (_sheet instanceof HSLFMasterSheet) {
pc = ((HSLFMasterSheet)_sheet).getPropCollection(getRunType(), getIndentLevel(), "*", isChar);
if (pc == null) {
throw new HSLFException("Master text property collection can't be determined.");
}
} else {
pc = props;
}
if (handler == null) {
pc.removeByName(name);
} else {
// Fetch / Add the TextProp
handler.accept(pc.addWithName(name));
}
setDirty();
}
/** /**
* Check and add linebreaks to text runs leading other paragraphs * Check and add linebreaks to text runs leading other paragraphs
* *
@ -1595,7 +1659,7 @@ public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFText
* @param val The value to set for the TextProp * @param val The value to set for the TextProp
*/ */
public void setParagraphTextPropVal(String propName, Integer val) { public void setParagraphTextPropVal(String propName, Integer val) {
setPropVal(_paragraphStyle, _masterStyle, propName, val); setPropVal(_paragraphStyle, propName, val);
setDirty(); setDirty();
} }

View File

@ -36,7 +36,6 @@ import org.apache.poi.sl.usermodel.Placeholder;
import org.apache.poi.sl.usermodel.TextParagraph; import org.apache.poi.sl.usermodel.TextParagraph;
import org.apache.poi.sl.usermodel.TextRun; import org.apache.poi.sl.usermodel.TextRun;
import org.apache.poi.sl.usermodel.TextShape; import org.apache.poi.sl.usermodel.TextShape;
import org.apache.poi.util.Internal;
import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger; import org.apache.poi.util.POILogger;
@ -61,8 +60,6 @@ public final class HSLFTextRun implements TextRun {
*/ */
private TextPropCollection characterStyle = new TextPropCollection(1, TextPropType.character); private TextPropCollection characterStyle = new TextPropCollection(1, TextPropType.character);
private TextPropCollection masterStyle;
/** /**
* Create a new wrapper around a rich text string * Create a new wrapper around a rich text string
* @param parentParagraph the parent paragraph * @param parentParagraph the parent paragraph
@ -80,19 +77,6 @@ public final class HSLFTextRun implements TextRun {
this.characterStyle.updateTextSize(_runText.length()); this.characterStyle.updateTextSize(_runText.length());
} }
/**
* Setting a master style reference
*
* @param characterStyle the master style reference
*
* @since POI 3.14-Beta1
*/
@Internal
/* package */ void setMasterStyleReference(TextPropCollection masterStyle) {
this.masterStyle = masterStyle;
}
/** /**
* Supply the SlideShow we belong to * Supply the SlideShow we belong to
*/ */
@ -149,28 +133,34 @@ public final class HSLFTextRun implements TextRun {
} }
protected boolean getFlag(int index) { protected boolean getFlag(int index) {
if (characterStyle == null) { BitMaskTextProp prop = (characterStyle == null) ? null : characterStyle.findByName(CharFlagsTextProp.NAME);
return false;
}
BitMaskTextProp prop = (BitMaskTextProp)characterStyle.findByName(CharFlagsTextProp.NAME);
if (prop == null || !prop.getSubPropMatches()[index]) { if (prop == null || !prop.getSubPropMatches()[index]) {
int txtype = parentParagraph.getRunType(); prop = getMasterProp(CharFlagsTextProp.NAME);
HSLFSheet sheet = parentParagraph.getSheet();
if (sheet != null) {
HSLFMasterSheet master = sheet.getMasterSheet();
if (master != null){
prop = (BitMaskTextProp)master.getStyleAttribute(txtype, parentParagraph.getIndentLevel(), CharFlagsTextProp.NAME, true);
}
} else {
logger.log(POILogger.WARN, "MasterSheet is not available");
}
} }
return prop == null ? false : prop.getSubValue(index); return prop == null ? false : prop.getSubValue(index);
} }
private <T extends TextProp> T getMasterProp(final String name) {
final int txtype = parentParagraph.getRunType();
final HSLFSheet sheet = parentParagraph.getSheet();
if (sheet == null) {
logger.log(POILogger.ERROR, "Sheet is not available");
return null;
}
final HSLFMasterSheet master = sheet.getMasterSheet();
if (master == null) {
logger.log(POILogger.WARN, "MasterSheet is not available");
return null;
}
final TextPropCollection col = master.getPropCollection(txtype, parentParagraph.getIndentLevel(), name, true);
return (col == null) ? null : col.findByName(name);
}
/** /**
* Set the value of the given flag in the CharFlagsTextProp, adding * Set the value of the given flag in the CharFlagsTextProp, adding
* it if required. * it if required.
@ -189,8 +179,7 @@ public final class HSLFTextRun implements TextRun {
* @param val The value to set for the TextProp * @param val The value to set for the TextProp
*/ */
public void setCharTextPropVal(String propName, Integer val) { public void setCharTextPropVal(String propName, Integer val) {
getTextParagraph().setPropVal(characterStyle, masterStyle, propName, val); getTextParagraph().setPropVal(characterStyle, propName, val);
getTextParagraph().setDirty();
} }
@ -270,7 +259,7 @@ public final class HSLFTextRun implements TextRun {
* @return the percentage of the font size. If the value is positive, it is superscript, otherwise it is subscript * @return the percentage of the font size. If the value is positive, it is superscript, otherwise it is subscript
*/ */
public int getSuperscript() { public int getSuperscript() {
TextProp tp = getTextParagraph().getPropVal(characterStyle, masterStyle, "superscript"); TextProp tp = getTextParagraph().getPropVal(characterStyle, "superscript");
return tp == null ? 0 : tp.getValue(); return tp == null ? 0 : tp.getValue();
} }
@ -285,7 +274,7 @@ public final class HSLFTextRun implements TextRun {
@Override @Override
public Double getFontSize() { public Double getFontSize() {
TextProp tp = getTextParagraph().getPropVal(characterStyle, masterStyle, "font.size"); TextProp tp = getTextParagraph().getPropVal(characterStyle, "font.size");
return tp == null ? null : (double)tp.getValue(); return tp == null ? null : (double)tp.getValue();
} }
@ -300,7 +289,7 @@ public final class HSLFTextRun implements TextRun {
* Gets the font index * Gets the font index
*/ */
public int getFontIndex() { public int getFontIndex() {
TextProp tp = getTextParagraph().getPropVal(characterStyle, masterStyle, "font.index"); TextProp tp = getTextParagraph().getPropVal(characterStyle, "font.index");
return tp == null ? -1 : tp.getValue(); return tp == null ? -1 : tp.getValue();
} }
@ -401,7 +390,7 @@ public final class HSLFTextRun implements TextRun {
break; break;
} }
TextProp tp = getTextParagraph().getPropVal(characterStyle, masterStyle, propName); TextProp tp = getTextParagraph().getPropVal(characterStyle, propName);
return (tp != null) ? slideShow.getFont(tp.getValue()) : null; return (tp != null) ? slideShow.getFont(tp.getValue()) : null;
} }
@ -410,7 +399,7 @@ public final class HSLFTextRun implements TextRun {
*/ */
@Override @Override
public SolidPaint getFontColor() { public SolidPaint getFontColor() {
TextProp tp = getTextParagraph().getPropVal(characterStyle, masterStyle, "font.color"); TextProp tp = getTextParagraph().getPropVal(characterStyle, "font.color");
if (tp == null) { if (tp == null) {
return null; return null;
} }

View File

@ -20,7 +20,7 @@ package org.apache.poi.hslf.usermodel;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.apache.poi.hslf.model.textproperties.TextProp; import org.apache.poi.hslf.model.textproperties.TextPropCollection;
import org.apache.poi.hslf.record.SlideAtom; import org.apache.poi.hslf.record.SlideAtom;
/** /**
@ -55,11 +55,11 @@ public final class HSLFTitleMaster extends HSLFMasterSheet {
* Delegate the call to the underlying slide master. * Delegate the call to the underlying slide master.
*/ */
@Override @Override
public TextProp getStyleAttribute(int txtype, int level, String name, boolean isCharacter) { public TextPropCollection getPropCollection(final int txtype, final int level, final String name, final boolean isCharacter) {
HSLFMasterSheet master = getMasterSheet(); final HSLFMasterSheet master = getMasterSheet();
return (master == null) ? null : master.getStyleAttribute(txtype, level, name, isCharacter); return (master == null) ? null : master.getPropCollection(txtype, level, name, isCharacter);
} }
/** /**
* Returns the slide master for this title master. * Returns the slide master for this title master.
*/ */

View File

@ -17,6 +17,10 @@
package org.apache.poi.hslf.model; package org.apache.poi.hslf.model;
import static org.apache.poi.hslf.record.TextHeaderAtom.BODY_TYPE;
import static org.apache.poi.hslf.record.TextHeaderAtom.CENTER_TITLE_TYPE;
import static org.apache.poi.hslf.record.TextHeaderAtom.CENTRE_BODY_TYPE;
import static org.apache.poi.hslf.record.TextHeaderAtom.TITLE_TYPE;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
@ -27,6 +31,7 @@ import java.util.List;
import org.apache.poi.POIDataSamples; import org.apache.poi.POIDataSamples;
import org.apache.poi.hslf.model.textproperties.CharFlagsTextProp; import org.apache.poi.hslf.model.textproperties.CharFlagsTextProp;
import org.apache.poi.hslf.model.textproperties.TextProp;
import org.apache.poi.hslf.record.Environment; import org.apache.poi.hslf.record.Environment;
import org.apache.poi.hslf.record.TextHeaderAtom; import org.apache.poi.hslf.record.TextHeaderAtom;
import org.apache.poi.hslf.usermodel.HSLFMasterSheet; import org.apache.poi.hslf.usermodel.HSLFMasterSheet;
@ -51,62 +56,70 @@ public final class TestSlideMaster {
*/ */
@Test @Test
public void testSlideMaster() throws IOException { public void testSlideMaster() throws IOException {
HSLFSlideShow ppt = new HSLFSlideShow(_slTests.openResourceAsStream("slide_master.ppt")); final HSLFSlideShow ppt = new HSLFSlideShow(_slTests.openResourceAsStream("slide_master.ppt"));
Environment env = ppt.getDocumentRecord().getEnvironment(); final Environment env = ppt.getDocumentRecord().getEnvironment();
List<HSLFSlideMaster> master = ppt.getSlideMasters(); assertEquals(2, ppt.getSlideMasters().size());
assertEquals(2, master.size());
//character attributes //character attributes
assertEquals(40, master.get(0).getStyleAttribute(TextHeaderAtom.TITLE_TYPE, 0, "font.size", true).getValue()); assertEquals(40, getMasterVal(ppt, 0, TITLE_TYPE, "font.size", true));
assertEquals(48, master.get(1).getStyleAttribute(TextHeaderAtom.TITLE_TYPE, 0, "font.size", true).getValue()); assertEquals(48, getMasterVal(ppt, 1, TITLE_TYPE, "font.size", true));
int font1 = master.get(0).getStyleAttribute(TextHeaderAtom.TITLE_TYPE, 0, "font.index", true).getValue(); int font1 = getMasterVal(ppt, 0, TITLE_TYPE, "font.index", true);
int font2 = master.get(1).getStyleAttribute(TextHeaderAtom.TITLE_TYPE, 0, "font.index", true).getValue(); int font2 = getMasterVal(ppt, 1, TITLE_TYPE, "font.index", true);
assertEquals("Arial", env.getFontCollection().getFontInfo(font1).getTypeface()); assertEquals("Arial", env.getFontCollection().getFontInfo(font1).getTypeface());
assertEquals("Georgia", env.getFontCollection().getFontInfo(font2).getTypeface()); assertEquals("Georgia", env.getFontCollection().getFontInfo(font2).getTypeface());
CharFlagsTextProp prop1 = (CharFlagsTextProp)master.get(0).getStyleAttribute(TextHeaderAtom.TITLE_TYPE, 0, "char_flags", true); CharFlagsTextProp prop1 = getMasterProp(ppt, 0, TITLE_TYPE, "char_flags", true);
assertEquals(false, prop1.getSubValue(CharFlagsTextProp.BOLD_IDX)); assertEquals(false, prop1.getSubValue(CharFlagsTextProp.BOLD_IDX));
assertEquals(false, prop1.getSubValue(CharFlagsTextProp.ITALIC_IDX)); assertEquals(false, prop1.getSubValue(CharFlagsTextProp.ITALIC_IDX));
assertEquals(true, prop1.getSubValue(CharFlagsTextProp.UNDERLINE_IDX)); assertEquals(true, prop1.getSubValue(CharFlagsTextProp.UNDERLINE_IDX));
CharFlagsTextProp prop2 = (CharFlagsTextProp)master.get(1).getStyleAttribute(TextHeaderAtom.TITLE_TYPE, 0, "char_flags", true); CharFlagsTextProp prop2 = getMasterProp(ppt, 1, TITLE_TYPE, "char_flags", true);
assertEquals(false, prop2.getSubValue(CharFlagsTextProp.BOLD_IDX)); assertEquals(false, prop2.getSubValue(CharFlagsTextProp.BOLD_IDX));
assertEquals(true, prop2.getSubValue(CharFlagsTextProp.ITALIC_IDX)); assertEquals(true, prop2.getSubValue(CharFlagsTextProp.ITALIC_IDX));
assertEquals(false, prop2.getSubValue(CharFlagsTextProp.UNDERLINE_IDX)); assertEquals(false, prop2.getSubValue(CharFlagsTextProp.UNDERLINE_IDX));
//now paragraph attributes //now paragraph attributes
assertEquals(0x266B, master.get(0).getStyleAttribute(TextHeaderAtom.BODY_TYPE, 0, "bullet.char", false).getValue()); assertEquals(0x266B, getMasterVal(ppt, 0, BODY_TYPE, "bullet.char", false));
assertEquals(0x2022, master.get(1).getStyleAttribute(TextHeaderAtom.BODY_TYPE, 0, "bullet.char", false).getValue()); assertEquals(0x2022, getMasterVal(ppt, 1, BODY_TYPE, "bullet.char", false));
int b1 = master.get(0).getStyleAttribute(TextHeaderAtom.BODY_TYPE, 0, "bullet.font", false).getValue(); int b1 = getMasterVal(ppt, 0, BODY_TYPE, "bullet.font", false);
int b2 = master.get(1).getStyleAttribute(TextHeaderAtom.BODY_TYPE, 0, "bullet.font", false).getValue(); int b2 = getMasterVal(ppt, 1, BODY_TYPE, "bullet.font", false);
assertEquals("Arial", env.getFontCollection().getFontInfo(b1).getTypeface()); assertEquals("Arial", env.getFontCollection().getFontInfo(b1).getTypeface());
assertEquals("Georgia", env.getFontCollection().getFontInfo(b2).getTypeface()); assertEquals("Georgia", env.getFontCollection().getFontInfo(b2).getTypeface());
ppt.close(); ppt.close();
} }
@SuppressWarnings("unchecked")
private static <T extends TextProp> T getMasterProp(HSLFSlideShow ppt, int masterIdx, int txtype, String propName, boolean isCharacter) {
return (T)ppt.getSlideMasters().get(masterIdx).getPropCollection(txtype, 0, propName, isCharacter).findByName(propName);
}
private static int getMasterVal(HSLFSlideShow ppt, int masterIdx, int txtype, String propName, boolean isCharacter) {
return getMasterProp(ppt, masterIdx, txtype, propName, isCharacter).getValue();
}
/** /**
* Test we can read default text attributes for a title master sheet * Test we can read default text attributes for a title master sheet
*/ */
@Test @Test
public void testTitleMasterTextAttributes() throws IOException { public void testTitleMasterTextAttributes() throws IOException {
HSLFSlideShow ppt = new HSLFSlideShow(_slTests.openResourceAsStream("slide_master.ppt")); HSLFSlideShow ppt = new HSLFSlideShow(_slTests.openResourceAsStream("slide_master.ppt"));
List<HSLFTitleMaster> master = ppt.getTitleMasters(); assertEquals(1, ppt.getTitleMasters().size());
assertEquals(1, master.size());
assertEquals(32, master.get(0).getStyleAttribute(TextHeaderAtom.CENTER_TITLE_TYPE, 0, "font.size", true).getValue()); assertEquals(40, getMasterVal(ppt, 0, CENTER_TITLE_TYPE, "font.size", true));
CharFlagsTextProp prop1 = (CharFlagsTextProp)master.get(0).getStyleAttribute(TextHeaderAtom.CENTER_TITLE_TYPE, 0, "char_flags", true); CharFlagsTextProp prop1 = getMasterProp(ppt, 0, CENTER_TITLE_TYPE, "char_flags", true);
assertEquals(true, prop1.getSubValue(CharFlagsTextProp.BOLD_IDX)); assertEquals(false, prop1.getSubValue(CharFlagsTextProp.BOLD_IDX));
assertEquals(false, prop1.getSubValue(CharFlagsTextProp.ITALIC_IDX)); assertEquals(false, prop1.getSubValue(CharFlagsTextProp.ITALIC_IDX));
assertEquals(true, prop1.getSubValue(CharFlagsTextProp.UNDERLINE_IDX)); assertEquals(true, prop1.getSubValue(CharFlagsTextProp.UNDERLINE_IDX));
assertEquals(20, master.get(0).getStyleAttribute(TextHeaderAtom.CENTRE_BODY_TYPE, 0, "font.size", true).getValue()); assertEquals(32, getMasterVal(ppt, 0, CENTRE_BODY_TYPE, "font.size", true));
CharFlagsTextProp prop2 = (CharFlagsTextProp)master.get(0).getStyleAttribute(TextHeaderAtom.CENTRE_BODY_TYPE, 0, "char_flags", true); CharFlagsTextProp prop2 = getMasterProp(ppt, 0, CENTRE_BODY_TYPE, "char_flags", true);
assertEquals(true, prop2.getSubValue(CharFlagsTextProp.BOLD_IDX)); assertEquals(false, prop2.getSubValue(CharFlagsTextProp.BOLD_IDX));
assertEquals(false, prop2.getSubValue(CharFlagsTextProp.ITALIC_IDX)); assertEquals(false, prop2.getSubValue(CharFlagsTextProp.ITALIC_IDX));
assertEquals(false, prop2.getSubValue(CharFlagsTextProp.UNDERLINE_IDX)); assertEquals(false, prop2.getSubValue(CharFlagsTextProp.UNDERLINE_IDX));

View File

@ -18,17 +18,17 @@
package org.apache.poi.hslf.record; package org.apache.poi.hslf.record;
import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.util.List;
import junit.framework.TestCase; import org.apache.poi.hslf.model.textproperties.HSLFTabStop;
import org.junit.Test;
/** public final class TestTextRulerAtom {
* Tests TextRulerAtom
*
* @author Yegor Kozlov
*/
public final class TestTextRulerAtom extends TestCase {
//from a real file //from a real file
private final byte[] data_1 = new byte[] { private final byte[] data_1 = new byte[] {
@ -40,25 +40,27 @@ public final class TestTextRulerAtom extends TestCase {
private final byte[] data_2 = new byte[] { private final byte[] data_2 = new byte[] {
0x00, 0x00, (byte)0xA6, 0x0F, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, (byte)0xA6, 0x0F, 0x0A, 0x00, 0x00, 0x00,
0x10, 0x03, 0x00, 0x00, (byte)0xF9, 0x00, 0x41, 0x01, 0x41, 0x01 0x08, 0x03, 0x00, 0x00, (byte)0xF9, 0x00, 0x41, 0x01, 0x41, 0x01
}; };
@Test
public void testReadRuler() { public void testReadRuler() {
TextRulerAtom ruler = new TextRulerAtom(data_1, 0, data_1.length); TextRulerAtom ruler = new TextRulerAtom(data_1, 0, data_1.length);
assertEquals(ruler.getNumberOfLevels(), 0); assertEquals(ruler.getNumberOfLevels(), 0);
assertEquals(ruler.getDefaultTabSize(), 0); assertEquals(ruler.getDefaultTabSize(), 0);
int[] tabStops = ruler.getTabStops(); List<HSLFTabStop> tabStops = ruler.getTabStops();
assertNull(tabStops); assertNotNull(tabStops);
int[] textOffsets = ruler.getTextOffsets(); Integer[] textOffsets = ruler.getTextOffsets();
assertArrayEquals(new int[]{226, 451, 903, 1129, 1526}, textOffsets); assertArrayEquals(new Integer[]{226, 451, 903, 1129, 1526}, textOffsets);
int[] bulletOffsets = ruler.getBulletOffsets(); Integer[] bulletOffsets = ruler.getBulletOffsets();
assertArrayEquals(new int[]{117, 345, 794, 1016, 1526}, bulletOffsets); assertArrayEquals(new Integer[]{117, 345, 794, 1016, 1526}, bulletOffsets);
} }
@Test
public void testWriteRuler() throws Exception { public void testWriteRuler() throws Exception {
TextRulerAtom ruler = new TextRulerAtom(data_1, 0, data_1.length); TextRulerAtom ruler = new TextRulerAtom(data_1, 0, data_1.length);
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();
@ -68,6 +70,7 @@ public final class TestTextRulerAtom extends TestCase {
assertArrayEquals(result, data_1); assertArrayEquals(result, data_1);
} }
@Test
public void testRead2() throws Exception { public void testRead2() throws Exception {
TextRulerAtom ruler = TextRulerAtom.getParagraphInstance(); TextRulerAtom ruler = TextRulerAtom.getParagraphInstance();
ruler.setParagraphIndent((short)249, (short)321); ruler.setParagraphIndent((short)249, (short)321);
@ -75,6 +78,6 @@ public final class TestTextRulerAtom extends TestCase {
ruler.writeOut(out); ruler.writeOut(out);
byte[] result = out.toByteArray(); byte[] result = out.toByteArray();
assertArrayEquals(result, data_2); assertArrayEquals(data_2, result);
} }
} }

View File

@ -17,8 +17,14 @@
package org.apache.poi.hslf.usermodel; package org.apache.poi.hslf.usermodel;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import org.apache.poi.sl.usermodel.BaseTestSlideShow; import org.apache.poi.sl.usermodel.BaseTestSlideShow;
import org.apache.poi.sl.usermodel.SlideShow;
import org.junit.Test; import org.junit.Test;
public class TestHSLFSlideShow extends BaseTestSlideShow { public class TestHSLFSlideShow extends BaseTestSlideShow {
@ -32,4 +38,25 @@ public class TestHSLFSlideShow extends BaseTestSlideShow {
public void dummy() { public void dummy() {
assertNotNull(createSlideShow()); assertNotNull(createSlideShow());
} }
public SlideShow<?, ?> reopen(SlideShow<?, ?> show) {
return reopen((HSLFSlideShow)show);
}
public static HSLFSlideShow reopen(HSLFSlideShow show) {
try {
BufAccessBAOS bos = new BufAccessBAOS();
show.write(bos);
return new HSLFSlideShow(new ByteArrayInputStream(bos.getBuf()));
} catch (IOException e) {
fail(e.getMessage());
return null;
}
}
private static class BufAccessBAOS extends ByteArrayOutputStream {
public byte[] getBuf() {
return buf;
}
}
} }

View File

@ -21,18 +21,24 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame; import static org.junit.Assert.assertSame;
import java.awt.Color;
import java.awt.geom.Rectangle2D;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.List;
import org.apache.poi.POIDataSamples; import org.apache.poi.POIDataSamples;
import org.apache.poi.sl.usermodel.PictureData.PictureType; import org.apache.poi.sl.usermodel.PictureData.PictureType;
import org.apache.poi.sl.usermodel.TabStop.TabStopType;
import org.junit.Test; import org.junit.Test;
public abstract class BaseTestSlideShow { public abstract class BaseTestSlideShow {
protected static final POIDataSamples slTests = POIDataSamples.getSlideShowInstance(); protected static final POIDataSamples slTests = POIDataSamples.getSlideShowInstance();
public abstract SlideShow<?, ?> createSlideShow(); public abstract SlideShow<?, ?> createSlideShow();
public abstract SlideShow<?, ?> reopen(SlideShow<?, ?> show);
@Test @Test
public void addPicture_File() throws IOException { public void addPicture_File() throws IOException {
@ -92,4 +98,63 @@ public abstract class BaseTestSlideShow {
show.close(); show.close();
} }
@Test
public void addTabStops() throws IOException {
try (final SlideShow<?,?> show1 = createSlideShow()) {
// first set the TabStops in the Master sheet
final MasterSheet<?, ?> master1 = show1.getSlideMasters().get(0);
final AutoShape<?, ?> master1_as = (AutoShape<?,?>)master1.getPlaceholder(Placeholder.BODY);
final TextParagraph<?, ?, ? extends TextRun> master1_tp = master1_as.getTextParagraphs().get(0);
master1_tp.clearTabStops();
int i1 = 0;
for (final TabStopType tst : TabStopType.values()) {
master1_tp.addTabStops(10+i1*10, tst);
i1++;
}
// then set it on a normal slide
final Slide<?,?> slide1 = show1.createSlide();
final AutoShape<?, ?> slide1_as = slide1.createAutoShape();
slide1_as.setText("abc");
slide1_as.setAnchor(new Rectangle2D.Double(100,100,100,100));
final TextParagraph<?, ?, ? extends TextRun> slide1_tp = slide1_as.getTextParagraphs().get(0);
slide1_tp.getTextRuns().get(0).setFontColor(new Color(0x563412));
slide1_tp.clearTabStops();
int i2 = 0;
for (final TabStopType tst : TabStopType.values()) {
slide1_tp.addTabStops(15+i2*5, tst);
i2++;
}
try (final SlideShow<?, ?> show2 = reopen(show1)) {
final MasterSheet<?, ?> master2 = show2.getSlideMasters().get(0);
final AutoShape<?, ?> master2_as = (AutoShape<?,?>)master2.getPlaceholder(Placeholder.BODY);
final TextParagraph<?, ?, ? extends TextRun> master2_tp = master2_as.getTextParagraphs().get(0);
final List<? extends TabStop> master2_tabStops = master2_tp.getTabStops();
assertNotNull(master2_tabStops);
int i3 = 0;
for (final TabStopType tst : TabStopType.values()) {
final TabStop ts = master2_tabStops.get(i3);
assertEquals(10+i3*10, ts.getPositionInPoints(), 0.0);
assertEquals(tst, ts.getType());
i3++;
}
final Slide<?,?> slide2 = show2.getSlides().get(0);
final AutoShape<?,?> slide2_as = (AutoShape<?,?>)slide2.getShapes().get(0);
final TextParagraph<?, ?, ? extends TextRun> slide2_tp = slide2_as.getTextParagraphs().get(0);
final List<? extends TabStop> slide2_tabStops = slide2_tp.getTabStops();
assertNotNull(slide2_tabStops);
int i4 = 0;
for (final TabStopType tst : TabStopType.values()) {
final TabStop ts = slide2_tabStops.get(i4);
assertEquals(15+i4*5, ts.getPositionInPoints(), 0.0);
assertEquals(tst, ts.getType());
i4++;
}
}
}
}
} }