| /* |
| * 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); |
| } |
| } |
| } |