blob: fdcda090d9a3ec27ad5e4d3ceac6d7708a53cdf5 [file] [log] [blame]
/*
* 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.
*/
/* $Id$ */
package org.apache.fop.render.rtf.rtflib.rtfdoc;
/*
* This file is part of the RTF library of the FOP project, which was originally
* created by Bertrand Delacretaz bdelacretaz@codeconsult.ch and by other
* contributors to the jfor project (www.jfor.org), who agreed to donate jfor to
* the FOP project.
*/
import java.io.IOException;
import java.io.Writer;
/**
* <p>A cell in an RTF table, container for paragraphs, lists, etc.</p>
*
* <p>This work was authored by Bertrand Delacretaz (bdelacretaz@codeconsult.ch).</p>
*/
public class RtfTableCell
extends RtfContainer
implements IRtfParagraphContainer, IRtfListContainer, IRtfTableContainer,
IRtfExternalGraphicContainer, IRtfTextrunContainer {
private RtfParagraph paragraph;
private RtfList list;
private RtfTable table;
private RtfExternalGraphic externalGraphic;
private final RtfTableRow parentRow;
private boolean setCenter;
private boolean setRight;
private int id;
private RtfParagraphBreak lastBreak;
private static final String TABLE_CELL_PARAGRAPH = "cell";
private static final String TABLE_CELL_NESTED_PARAGRAPH = "nestcell";
/** default cell width (in twips ??) */
public static final int DEFAULT_CELL_WIDTH = 2000;
/** cell width in twips */
private int cellWidth;
private int widthOffset;
/** cell merging has three states */
private int vMerge = NO_MERGE;
private int hMerge = NO_MERGE;
/** cell merging: this cell is not merged */
public static final int NO_MERGE = 0;
/** cell merging: this cell is the start of a range of merged cells */
public static final int MERGE_START = 1;
/** cell merging: this cell is part of (but not the start of) a range of merged cells */
public static final int MERGE_WITH_PREVIOUS = 2;
/** Create an RTF element as a child of given container */
RtfTableCell(RtfTableRow parent, Writer w, int cellWidth, int idNum) throws IOException {
super(parent, w);
id = idNum;
parentRow = parent;
this.cellWidth = cellWidth;
setCenter = false;
setRight = false;
}
/** Create an RTF element as a child of given container */
RtfTableCell(RtfTableRow parent, Writer w, int cellWidth, RtfAttributes attrs,
int idNum) throws IOException {
super(parent, w, attrs);
id = idNum;
parentRow = parent;
this.cellWidth = cellWidth;
}
/**
* Start a new paragraph after closing current current paragraph, list and table
* @param attrs attributes of new RtfParagraph
* @return new RtfParagraph object
* @throws IOException for I/O problems
*/
public RtfParagraph newParagraph(RtfAttributes attrs) throws IOException {
closeAll();
// in tables, RtfParagraph must have the intbl attribute
if (attrs == null) {
attrs = new RtfAttributes();
}
attrs.set("intbl");
paragraph = new RtfParagraph(this, writer, attrs);
if (paragraph.attrib.isSet("qc")) {
setCenter = true;
attrs.set("qc");
} else if (paragraph.attrib.isSet("qr")) {
setRight = true;
attrs.set("qr");
} else {
attrs.set("ql");
}
attrs.set("intbl");
//lines modified by Chris Scott, Westinghouse
return paragraph;
}
/**
* Start a new external graphic after closing current paragraph, list and table
* @throws IOException for I/O problems
* @return new RtfExternalGraphic object
*/
public RtfExternalGraphic newImage() throws IOException {
closeAll();
externalGraphic = new RtfExternalGraphic(this, writer);
return externalGraphic;
}
/**
* Start a new paragraph with default attributes after closing current
* paragraph, list and table
* @return new RtfParagraph object
* @throws IOException for I/O problems
*/
public RtfParagraph newParagraph() throws IOException {
return newParagraph(null);
}
/**
* Start a new list after closing current paragraph, list and table
* @param attrib attributes for new RtfList
* @return new RtfList object
* @throws IOException for I/O problems
*/
public RtfList newList(RtfAttributes attrib) throws IOException {
closeAll();
list = new RtfList(this, writer, attrib);
return list;
}
/**
* Start a new nested table after closing current paragraph, list and table
* @param tc table column info for new RtfTable
* @return new RtfTable object
* @throws IOException for I/O problems
*/
public RtfTable newTable(ITableColumnsInfo tc) throws IOException {
closeAll();
table = new RtfTable(this, writer, tc);
return table;
}
/**
* Start a new nested table after closing current paragraph, list and table
* @param attrs attributes of new RtfTable
* @param tc table column info for new RtfTable
* @return new RtfTable object
* @throws IOException for I/O problems
*/
// Modified by Boris Poudérous on 07/22/2002
public RtfTable newTable(RtfAttributes attrs, ITableColumnsInfo tc) throws IOException {
closeAll();
table = new RtfTable(this, writer, attrs, tc); // Added tc Boris Poudérous 07/22/2002
return table;
}
/** used by RtfTableRow to write the <celldef> cell definition control words
* @param offset sum of the widths of preceeding cells in same row
* @return offset + width of this cell
*/
int writeCellDef(int offset) throws IOException {
/*
* Don't write \clmgf or \clmrg. Instead add the widths
* of all spanned columns and create a single wider cell,
* because \clmgf and \clmrg won't work in last row of a
* table (Word2000 seems to do the same).
* Cause of this, dont't write horizontally merged cells.
* They just exist as placeholders in TableContext class,
* and are never written to RTF file.
*/
// horizontal cell merge codes
if (hMerge == MERGE_WITH_PREVIOUS) {
return offset;
}
newLine();
this.widthOffset = offset;
// vertical cell merge codes
if (vMerge == MERGE_START) {
writeControlWord("clvmgf");
} else if (vMerge == MERGE_WITH_PREVIOUS) {
writeControlWord("clvmrg");
}
/**
* Added by Boris POUDEROUS on 2002/06/26
*/
// Cell background color processing :
writeAttributes(attrib, ITableAttributes.CELL_COLOR);
/** - end - */
writeAttributes(attrib, ITableAttributes.ATTRIB_CELL_PADDING);
writeAttributes(attrib, ITableAttributes.CELL_BORDER);
writeAttributes(attrib, IBorderAttributes.BORDERS);
// determine cell width
int iCurrentWidth = this.cellWidth;
if (attrib.getValue("number-columns-spanned") != null) {
// Get the number of columns spanned
int nbMergedCells = (Integer) attrib.getValue("number-columns-spanned");
RtfTable tab = getRow().getTable();
// Get the context of the current table in order to get the width of each column
ITableColumnsInfo tableColumnsInfo
= tab.getITableColumnsInfo();
tableColumnsInfo.selectFirstColumn();
// Reach the column index in table context corresponding to the current column cell
// id is the index of the current cell (it begins at 1)
// getColumnIndex() is the index of the current column in table context (it begins at 0)
// => so we must withdraw 1 when comparing these two variables.
while ((this.id - 1) != tableColumnsInfo.getColumnIndex()) {
tableColumnsInfo.selectNextColumn();
}
// We withdraw one cell because the first cell is already created
// (it's the current cell) !
int i = nbMergedCells - 1;
while (i > 0) {
tableColumnsInfo.selectNextColumn();
iCurrentWidth += (int)tableColumnsInfo.getColumnWidth();
i--;
}
}
final int xPos = offset + iCurrentWidth;
//these lines added by Chris Scott, Westinghouse
//some attributes need to be written before opening block
if (setCenter) {
writeControlWord("trqc");
} else if (setRight) {
writeControlWord("trqr");
} else {
writeControlWord("trql");
}
writeAttributes(attrib, ITableAttributes.CELL_VERT_ALIGN);
writeControlWord("cellx" + xPos);
return xPos;
}
/**
* Overriden to avoid writing any it's a merged cell.
* @throws IOException for I/O problems
*/
protected void writeRtfContent() throws IOException {
// Never write horizontally merged cells.
if (hMerge == MERGE_WITH_PREVIOUS) {
return;
}
super.writeRtfContent();
}
/**
* Called before writeRtfContent; overriden to avoid writing
* any it's a merged cell.
* @throws IOException for I/O problems
*/
protected void writeRtfPrefix() throws IOException {
// Never write horizontally merged cells.
if (hMerge == MERGE_WITH_PREVIOUS) {
return;
}
super.writeRtfPrefix();
}
/**
* The "cell" control word marks the end of a cell
* @throws IOException for I/O problems
*/
protected void writeRtfSuffix() throws IOException {
// Never write horizontally merged cells.
if (hMerge == MERGE_WITH_PREVIOUS) {
return;
}
if (getRow().getTable().isNestedTable()) {
//nested table
if (lastBreak == null) {
writeControlWordNS("nestcell");
}
writeGroupMark(true);
writeControlWord("nonesttables");
writeControlWord("par");
writeGroupMark(false);
} else {
// word97 hangs if cell does not contain at least one "par" control word
// TODO this is what causes the extra spaces in nested table of test
// 004-spacing-in-tables.fo,
// but if is not here we generate invalid RTF for word97
if (setCenter) {
writeControlWord("qc");
} else if (setRight) {
writeControlWord("qr");
} else {
RtfElement lastChild = null;
if (getChildren().size() > 0) {
lastChild = (RtfElement) getChildren().get(getChildren().size() - 1);
}
if (lastChild != null
&& lastChild instanceof RtfTextrun) {
//Don't write \ql in order to allow for example a right aligned paragraph
//in a not right aligned table-cell to write its \qr.
} else {
writeControlWord("ql");
}
}
if (!containsText()) {
writeControlWord("intbl");
//R.Marra this create useless paragraph
//Seem working into Word97 with the "intbl" only
//writeControlWord("par");
}
if (lastBreak == null) {
writeControlWord("cell");
}
}
}
//modified by Chris Scott, Westinghouse
private void closeCurrentParagraph() throws IOException {
if (paragraph != null) {
paragraph.close();
}
}
private void closeCurrentList() throws IOException {
if (list != null) {
list.close();
}
}
private void closeCurrentTable() throws IOException {
if (table != null) {
table.close();
}
}
private void closeCurrentExternalGraphic() throws IOException {
if (externalGraphic != null) {
externalGraphic.close();
}
}
private void closeAll()
throws IOException {
closeCurrentTable();
closeCurrentParagraph();
closeCurrentList();
closeCurrentExternalGraphic();
}
/**
* @param mergeStatus vertical cell merging status to set
*/
public void setVMerge(int mergeStatus) {
this.vMerge = mergeStatus;
}
/**
* @return vertical cell merging status
*/
public int getVMerge() {
return this.vMerge;
}
/**
* Set horizontal cell merging status
* @param mergeStatus mergeStatus to set
*/
public void setHMerge(int mergeStatus) {
this.hMerge = mergeStatus;
}
/**
* @return horizontal cell merging status
*/
public int getHMerge() {
return this.hMerge;
}
/** get cell width */
int getCellWidth() {
return this.cellWidth;
}
/**
* Overridden so that nested tables cause extra rows to be added after the row
* that contains this cell
* disabled for V0.3 - nested table support is not done yet
* @throws IOException for I/O problems
*/
/*
protected void writeRtfContent()
throws IOException {
int extraRowIndex = 0;
RtfTableCell extraCell = null;
for (Iterator it = getChildren().iterator(); it.hasNext();) {
final RtfElement e = (RtfElement)it.next();
if (e instanceof RtfTable) {
// nested table - render its cells in supplementary rows after current row,
// and put the remaining content of this cell in a new cell after nested table
// Line added by Boris Poudérous
parentRow.getExtraRowSet().setParentITableColumnsInfo(
((RtfTable)this.getParentOfClass(e.getClass())).getITableColumnsInfo());
extraRowIndex = parentRow.getExtraRowSet().addTable((RtfTable)e,
extraRowIndex, widthOffset);
// Boris Poudérous added the passing of the current cell
// attributes to the new cells (in order not to have cell without
// border for example)
extraCell = parentRow.getExtraRowSet().createExtraCell(extraRowIndex,
widthOffset, this.getCellWidth(), attrib);
extraRowIndex++;
} else if (extraCell != null) {
// we are after a nested table, add elements to the extra cell created for them
extraCell.addChild(e);
} else {
// before a nested table, normal rendering
e.writeRtf();
}
}
}*/
/**
* A table cell always contains "useful" content, as it is here to take some
* space in a row.
* Use containsText() to find out if there is really some useful content in the cell.
* TODO: containsText could use the original isEmpty implementation?
* @return false (always)
*/
public boolean isEmpty() {
return false;
}
/** true if the "par" control word must be written for given RtfParagraph
* (which is not the case for the last non-empty paragraph of the cell)
*/
boolean paragraphNeedsPar(RtfParagraph p) {
// true if there is at least one non-empty paragraph after p in our children
boolean pFound = false;
boolean result = false;
for (final Object o : getChildren()) {
if (!pFound) {
// set pFound when p is found in the list
pFound = (o == p);
} else {
if (o instanceof RtfParagraph) {
final RtfParagraph p2 = (RtfParagraph) o;
if (!p2.isEmpty()) {
// found a non-empty paragraph after p
result = true;
break;
}
} else if (o instanceof RtfTable) {
break;
}
}
}
return result;
}
/**
* Returns the current RtfTextrun object.
* Opens a new one if necessary.
* @return The RtfTextrun object
* @throws IOException Thrown when an IO-problem occurs
*/
public RtfTextrun getTextrun() throws IOException {
RtfAttributes attrs = new RtfAttributes();
if (!getRow().getTable().isNestedTable()) {
attrs.set("intbl");
}
RtfTextrun textrun = RtfTextrun.getTextrun(this, writer, attrs);
//Suppress the very last \par, because the closing \cell applies the
//paragraph attributes.
textrun.setSuppressLastPar(true);
return textrun;
}
/**
* Get the parent row.
* @return The parent row.
*/
public RtfTableRow getRow() {
RtfElement e = this;
while (e.parent != null) {
if (e.parent instanceof RtfTableRow) {
return (RtfTableRow) e.parent;
}
e = e.parent;
}
return null;
}
/**
* The table cell decides whether or not a newly added paragraph break
* will be used to write the cell-end control word.
* For nested tables it is not necessary.
*
* @param parBreak the paragraph break element
*/
public void setLastParagraph(RtfParagraphBreak parBreak) {
if (parBreak != null) {
lastBreak = parBreak;
}
}
/**
* The last paragraph break was just stored before,
* now the control word is really switched
*/
public void finish() {
//If it is nested and contains another table do not set it
if (getRow().getTable().isNestedTable() && table != null) {
lastBreak = null;
} else if (lastBreak != null) {
lastBreak.switchControlWord(
getRow().getTable().isNestedTable()
? TABLE_CELL_NESTED_PARAGRAPH
: TABLE_CELL_PARAGRAPH);
}
}
}