/* * 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 int lastBreakDepth = Integer.MIN_VALUE; 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 * @param breakDepth The depth is necessary for picking the correct break element. * If it is deeper inside the whole cell it will be used, and if there is something on * the same level (depth) it is also set because the method is called for all breaks * in the correct order. */ public void setLastParagraph(RtfParagraphBreak parBreak, int breakDepth) { if (parBreak != null && breakDepth >= lastBreakDepth) { lastBreak = parBreak; lastBreakDepth = breakDepth; } } /** * 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); } } }