| /* ==================================================================== |
| 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.hssf.record; |
| |
| import org.apache.poi.hssf.record.cont.ContinuableRecord; |
| import org.apache.poi.hssf.record.cont.ContinuableRecordOutput; |
| import org.apache.poi.ss.formula.ptg.OperandPtg; |
| import org.apache.poi.ss.formula.ptg.Ptg; |
| import org.apache.poi.hssf.usermodel.HSSFRichTextString; |
| import org.apache.poi.util.BitField; |
| import org.apache.poi.util.BitFieldFactory; |
| import org.apache.poi.util.HexDump; |
| |
| /** |
| * The TXO record (0x01B6) is used to define the properties of a text box. It is |
| * followed by two or more continue records unless there is no actual text. The |
| * first continue records contain the text data and the last continue record |
| * contains the formatting runs. |
| */ |
| public final class TextObjectRecord extends ContinuableRecord { |
| public final static short sid = 0x01B6; |
| |
| private static final int FORMAT_RUN_ENCODED_SIZE = 8; // 2 shorts and 4 bytes reserved |
| |
| private static final BitField HorizontalTextAlignment = BitFieldFactory.getInstance(0x000E); |
| private static final BitField VerticalTextAlignment = BitFieldFactory.getInstance(0x0070); |
| private static final BitField textLocked = BitFieldFactory.getInstance(0x0200); |
| |
| public final static short HORIZONTAL_TEXT_ALIGNMENT_LEFT_ALIGNED = 1; |
| public final static short HORIZONTAL_TEXT_ALIGNMENT_CENTERED = 2; |
| public final static short HORIZONTAL_TEXT_ALIGNMENT_RIGHT_ALIGNED = 3; |
| public final static short HORIZONTAL_TEXT_ALIGNMENT_JUSTIFIED = 4; |
| public final static short VERTICAL_TEXT_ALIGNMENT_TOP = 1; |
| public final static short VERTICAL_TEXT_ALIGNMENT_CENTER = 2; |
| public final static short VERTICAL_TEXT_ALIGNMENT_BOTTOM = 3; |
| public final static short VERTICAL_TEXT_ALIGNMENT_JUSTIFY = 4; |
| |
| public final static short TEXT_ORIENTATION_NONE = 0; |
| public final static short TEXT_ORIENTATION_TOP_TO_BOTTOM = 1; |
| public final static short TEXT_ORIENTATION_ROT_RIGHT = 2; |
| public final static short TEXT_ORIENTATION_ROT_LEFT = 3; |
| |
| private int field_1_options; |
| private int field_2_textOrientation; |
| private int field_3_reserved4; |
| private int field_4_reserved5; |
| private int field_5_reserved6; |
| private int field_8_reserved7; |
| |
| private HSSFRichTextString _text; |
| |
| /* |
| * Note - the next three fields are very similar to those on |
| * EmbededObjectRefSubRecord(ftPictFmla 0x0009) |
| * |
| * some observed values for the 4 bytes preceding the formula: C0 5E 86 03 |
| * C0 11 AC 02 80 F1 8A 03 D4 F0 8A 03 |
| */ |
| private int _unknownPreFormulaInt; |
| /** expect tRef, tRef3D, tArea, tArea3D or tName */ |
| private OperandPtg _linkRefPtg; |
| /** |
| * Not clear if needed . Excel seems to be OK if this byte is not present. |
| * Value is often the same as the earlier firstColumn byte. */ |
| private Byte _unknownPostFormulaByte; |
| |
| public TextObjectRecord() { |
| // |
| } |
| |
| public TextObjectRecord(RecordInputStream in) { |
| field_1_options = in.readUShort(); |
| field_2_textOrientation = in.readUShort(); |
| field_3_reserved4 = in.readUShort(); |
| field_4_reserved5 = in.readUShort(); |
| field_5_reserved6 = in.readUShort(); |
| int field_6_textLength = in.readUShort(); |
| int field_7_formattingDataLength = in.readUShort(); |
| field_8_reserved7 = in.readInt(); |
| |
| if (in.remaining() > 0) { |
| // Text Objects can have simple reference formulas |
| // (This bit not mentioned in the MS document) |
| if (in.remaining() < 11) { |
| throw new RecordFormatException("Not enough remaining data for a link formula"); |
| } |
| int formulaSize = in.readUShort(); |
| _unknownPreFormulaInt = in.readInt(); |
| Ptg[] ptgs = Ptg.readTokens(formulaSize, in); |
| if (ptgs.length != 1) { |
| throw new RecordFormatException("Read " + ptgs.length |
| + " tokens but expected exactly 1"); |
| } |
| _linkRefPtg = (OperandPtg) ptgs[0]; |
| if (in.remaining() > 0) { |
| _unknownPostFormulaByte = Byte.valueOf(in.readByte()); |
| } else { |
| _unknownPostFormulaByte = null; |
| } |
| } else { |
| _linkRefPtg = null; |
| } |
| if (in.remaining() > 0) { |
| throw new RecordFormatException("Unused " + in.remaining() + " bytes at end of record"); |
| } |
| |
| String text; |
| if (field_6_textLength > 0) { |
| text = readRawString(in, field_6_textLength); |
| } else { |
| text = ""; |
| } |
| _text = new HSSFRichTextString(text); |
| |
| if (field_7_formattingDataLength > 0) { |
| processFontRuns(in, _text, field_7_formattingDataLength); |
| } |
| } |
| |
| private static String readRawString(RecordInputStream in, int textLength) { |
| byte compressByte = in.readByte(); |
| boolean isCompressed = (compressByte & 0x01) == 0; |
| if (isCompressed) { |
| return in.readCompressedUnicode(textLength); |
| } |
| return in.readUnicodeLEString(textLength); |
| } |
| |
| private static void processFontRuns(RecordInputStream in, HSSFRichTextString str, |
| int formattingRunDataLength) { |
| if (formattingRunDataLength % FORMAT_RUN_ENCODED_SIZE != 0) { |
| throw new RecordFormatException("Bad format run data length " + formattingRunDataLength |
| + ")"); |
| } |
| int nRuns = formattingRunDataLength / FORMAT_RUN_ENCODED_SIZE; |
| for (int i = 0; i < nRuns; i++) { |
| short index = in.readShort(); |
| short iFont = in.readShort(); |
| in.readInt(); // skip reserved. |
| str.applyFont(index, str.length(), iFont); |
| } |
| } |
| |
| public short getSid() { |
| return sid; |
| } |
| |
| private void serializeTXORecord(ContinuableRecordOutput out) { |
| |
| out.writeShort(field_1_options); |
| out.writeShort(field_2_textOrientation); |
| out.writeShort(field_3_reserved4); |
| out.writeShort(field_4_reserved5); |
| out.writeShort(field_5_reserved6); |
| out.writeShort(_text.length()); |
| out.writeShort(getFormattingDataLength()); |
| out.writeInt(field_8_reserved7); |
| |
| if (_linkRefPtg != null) { |
| int formulaSize = _linkRefPtg.getSize(); |
| out.writeShort(formulaSize); |
| out.writeInt(_unknownPreFormulaInt); |
| _linkRefPtg.write(out); |
| if (_unknownPostFormulaByte != null) { |
| out.writeByte(_unknownPostFormulaByte.byteValue()); |
| } |
| } |
| } |
| |
| private void serializeTrailingRecords(ContinuableRecordOutput out) { |
| out.writeContinue(); |
| out.writeStringData(_text.getString()); |
| out.writeContinue(); |
| writeFormatData(out, _text); |
| } |
| |
| protected void serialize(ContinuableRecordOutput out) { |
| |
| serializeTXORecord(out); |
| if (_text.getString().length() > 0) { |
| serializeTrailingRecords(out); |
| } |
| } |
| |
| private int getFormattingDataLength() { |
| if (_text.length() < 1) { |
| // important - no formatting data if text is empty |
| return 0; |
| } |
| return (_text.numFormattingRuns() + 1) * FORMAT_RUN_ENCODED_SIZE; |
| } |
| |
| private static void writeFormatData(ContinuableRecordOutput out , HSSFRichTextString str) { |
| int nRuns = str.numFormattingRuns(); |
| for (int i = 0; i < nRuns; i++) { |
| out.writeShort(str.getIndexOfFormattingRun(i)); |
| int fontIndex = str.getFontOfFormattingRun(i); |
| out.writeShort(fontIndex == HSSFRichTextString.NO_FONT ? 0 : fontIndex); |
| out.writeInt(0); // skip reserved |
| } |
| out.writeShort(str.length()); |
| out.writeShort(0); |
| out.writeInt(0); // skip reserved |
| } |
| |
| /** |
| * Sets the Horizontal text alignment field value. |
| */ |
| public void setHorizontalTextAlignment(int value) { |
| field_1_options = HorizontalTextAlignment.setValue(field_1_options, value); |
| } |
| |
| /** |
| * @return the Horizontal text alignment field value. |
| */ |
| public int getHorizontalTextAlignment() { |
| return HorizontalTextAlignment.getValue(field_1_options); |
| } |
| |
| /** |
| * Sets the Vertical text alignment field value. |
| */ |
| public void setVerticalTextAlignment(int value) { |
| field_1_options = VerticalTextAlignment.setValue(field_1_options, value); |
| } |
| |
| /** |
| * @return the Vertical text alignment field value. |
| */ |
| public int getVerticalTextAlignment() { |
| return VerticalTextAlignment.getValue(field_1_options); |
| } |
| |
| /** |
| * Sets the text locked field value. |
| */ |
| public void setTextLocked(boolean value) { |
| field_1_options = textLocked.setBoolean(field_1_options, value); |
| } |
| |
| /** |
| * @return the text locked field value. |
| */ |
| public boolean isTextLocked() { |
| return textLocked.isSet(field_1_options); |
| } |
| |
| /** |
| * Get the text orientation field for the TextObjectBase record. |
| * |
| * @return One of TEXT_ORIENTATION_NONE TEXT_ORIENTATION_TOP_TO_BOTTOM |
| * TEXT_ORIENTATION_ROT_RIGHT TEXT_ORIENTATION_ROT_LEFT |
| */ |
| public int getTextOrientation() { |
| return field_2_textOrientation; |
| } |
| |
| /** |
| * Set the text orientation field for the TextObjectBase record. |
| * |
| * @param textOrientation |
| * One of TEXT_ORIENTATION_NONE TEXT_ORIENTATION_TOP_TO_BOTTOM |
| * TEXT_ORIENTATION_ROT_RIGHT TEXT_ORIENTATION_ROT_LEFT |
| */ |
| public void setTextOrientation(int textOrientation) { |
| this.field_2_textOrientation = textOrientation; |
| } |
| |
| public HSSFRichTextString getStr() { |
| return _text; |
| } |
| |
| public void setStr(HSSFRichTextString str) { |
| _text = str; |
| } |
| |
| public Ptg getLinkRefPtg() { |
| return _linkRefPtg; |
| } |
| |
| public String toString() { |
| StringBuffer sb = new StringBuffer(); |
| |
| sb.append("[TXO]\n"); |
| sb.append(" .options = ").append(HexDump.shortToHex(field_1_options)).append("\n"); |
| sb.append(" .isHorizontal = ").append(getHorizontalTextAlignment()).append('\n'); |
| sb.append(" .isVertical = ").append(getVerticalTextAlignment()).append('\n'); |
| sb.append(" .textLocked = ").append(isTextLocked()).append('\n'); |
| sb.append(" .textOrientation= ").append(HexDump.shortToHex(getTextOrientation())).append("\n"); |
| sb.append(" .reserved4 = ").append(HexDump.shortToHex(field_3_reserved4)).append("\n"); |
| sb.append(" .reserved5 = ").append(HexDump.shortToHex(field_4_reserved5)).append("\n"); |
| sb.append(" .reserved6 = ").append(HexDump.shortToHex(field_5_reserved6)).append("\n"); |
| sb.append(" .textLength = ").append(HexDump.shortToHex(_text.length())).append("\n"); |
| sb.append(" .reserved7 = ").append(HexDump.intToHex(field_8_reserved7)).append("\n"); |
| |
| sb.append(" .string = ").append(_text).append('\n'); |
| |
| for (int i = 0; i < _text.numFormattingRuns(); i++) { |
| sb.append(" .textrun = ").append(_text.getFontOfFormattingRun(i)).append('\n'); |
| |
| } |
| sb.append("[/TXO]\n"); |
| return sb.toString(); |
| } |
| |
| public Object clone() { |
| |
| TextObjectRecord rec = new TextObjectRecord(); |
| rec._text = _text; |
| |
| rec.field_1_options = field_1_options; |
| rec.field_2_textOrientation = field_2_textOrientation; |
| rec.field_3_reserved4 = field_3_reserved4; |
| rec.field_4_reserved5 = field_4_reserved5; |
| rec.field_5_reserved6 = field_5_reserved6; |
| rec.field_8_reserved7 = field_8_reserved7; |
| |
| rec._text = _text; // clone needed? |
| |
| if (_linkRefPtg != null) { |
| rec._unknownPreFormulaInt = _unknownPreFormulaInt; |
| rec._linkRefPtg = _linkRefPtg.copy(); |
| rec._unknownPostFormulaByte = _unknownPostFormulaByte; |
| } |
| return rec; |
| } |
| } |