001: /* ====================================================================
002: Licensed to the Apache Software Foundation (ASF) under one or more
003: contributor license agreements. See the NOTICE file distributed with
004: this work for additional information regarding copyright ownership.
005: The ASF licenses this file to You under the Apache License, Version 2.0
006: (the "License"); you may not use this file except in compliance with
007: the License. You may obtain a copy of the License at
008:
009: http://www.apache.org/licenses/LICENSE-2.0
010:
011: Unless required by applicable law or agreed to in writing, software
012: distributed under the License is distributed on an "AS IS" BASIS,
013: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: See the License for the specific language governing permissions and
015: limitations under the License.
016: ==================================================================== */
017:
018: package org.apache.poi.hslf.model;
019:
020: import org.apache.poi.ddf.*;
021: import org.apache.poi.hslf.record.*;
022: import org.apache.poi.hslf.usermodel.RichTextRun;
023: import org.apache.poi.hslf.exceptions.HSLFException;
024: import org.apache.poi.util.POILogger;
025:
026: import java.awt.*;
027: import java.awt.font.FontRenderContext;
028: import java.awt.font.TextLayout;
029: import java.io.IOException;
030:
031: /**
032: * Represents a TextFrame shape in PowerPoint.
033: * <p>
034: * Contains the text in a text frame as well as the properties and methods
035: * that control alignment and anchoring of the text.
036: * </p>
037: *
038: * @author Yegor Kozlov
039: */
040: public class TextBox extends SimpleShape {
041:
042: /**
043: * How to anchor the text
044: */
045: public static final int AnchorTop = 0;
046: public static final int AnchorMiddle = 1;
047: public static final int AnchorBottom = 2;
048: public static final int AnchorTopCentered = 3;
049: public static final int AnchorMiddleCentered = 4;
050: public static final int AnchorBottomCentered = 5;
051: public static final int AnchorTopBaseline = 6;
052: public static final int AnchorBottomBaseline = 7;
053: public static final int AnchorTopCenteredBaseline = 8;
054: public static final int AnchorBottomCenteredBaseline = 9;
055:
056: /**
057: * How to wrap the text
058: */
059: public static final int WrapSquare = 0;
060: public static final int WrapByPoints = 1;
061: public static final int WrapNone = 2;
062: public static final int WrapTopBottom = 3;
063: public static final int WrapThrough = 4;
064:
065: /**
066: * How to align the text
067: */
068: public static final int AlignLeft = 0;
069: public static final int AlignCenter = 1;
070: public static final int AlignRight = 2;
071: public static final int AlignJustify = 3;
072:
073: /**
074: * Low-level object which holds actual text and format data
075: */
076: protected TextRun _txtrun;
077:
078: /**
079: * Escher container which holds text attributes such as
080: * TextHeaderAtom, TextBytesAtom ot TextCharsAtom, StyleTextPropAtom etc.
081: */
082: protected EscherTextboxWrapper _txtbox;
083:
084: /**
085: * Is the TextBox missing the text records which actually
086: * store the text?
087: */
088: private boolean _missingTextRecords = false;
089:
090: /**
091: * Create a TextBox object and initialize it from the supplied Record container.
092: *
093: * @param escherRecord <code>EscherSpContainer</code> container which holds information about this shape
094: * @param parent the parent of the shape
095: */
096: protected TextBox(EscherContainerRecord escherRecord, Shape parent) {
097: super (escherRecord, parent);
098:
099: EscherTextboxRecord textbox = (EscherTextboxRecord) Shape
100: .getEscherChild(_escherContainer,
101: EscherTextboxRecord.RECORD_ID);
102: _txtbox = new EscherTextboxWrapper(textbox);
103: }
104:
105: /**
106: * Create a new TextBox. This constructor is used when a new shape is created.
107: *
108: * @param parent the parent of this Shape. For example, if this text box is a cell
109: * in a table then the parent is Table.
110: */
111: public TextBox(Shape parent) {
112: super (null, parent);
113: _escherContainer = createSpContainer(parent instanceof ShapeGroup);
114: }
115:
116: /**
117: * Create a new TextBox. This constructor is used when a new shape is created.
118: *
119: */
120: public TextBox() {
121: this (null);
122: }
123:
124: /**
125: * Create a new textBox and initialize internal structures
126: *
127: * @return the created <code>EscherContainerRecord</code> which holds shape data
128: */
129: protected EscherContainerRecord createSpContainer(boolean isChild) {
130: EscherContainerRecord spcont = super .createSpContainer(isChild);
131:
132: EscherSpRecord spRecord = spcont
133: .getChildById(EscherSpRecord.RECORD_ID);
134: short type = (ShapeTypes.TextBox << 4) | 0x2;
135: spRecord.setOptions(type);
136:
137: //set default properties for a textbox
138: EscherOptRecord opt = (EscherOptRecord) getEscherChild(spcont,
139: EscherOptRecord.RECORD_ID);
140: setEscherProperty(opt, EscherProperties.TEXT__TEXTID, 0);
141:
142: setEscherProperty(opt, EscherProperties.FILL__FILLCOLOR,
143: 0x8000004);
144: setEscherProperty(opt, EscherProperties.FILL__FILLBACKCOLOR,
145: 0x8000000);
146: setEscherProperty(opt, EscherProperties.FILL__NOFILLHITTEST,
147: 0x100000);
148: setEscherProperty(opt, EscherProperties.LINESTYLE__COLOR,
149: 0x8000001);
150: setEscherProperty(opt,
151: EscherProperties.LINESTYLE__NOLINEDRAWDASH, 0x80000);
152: setEscherProperty(opt, EscherProperties.SHADOWSTYLE__COLOR,
153: 0x8000002);
154:
155: //create EscherTextboxWrapper
156: _txtbox = new EscherTextboxWrapper();
157:
158: TextHeaderAtom tha = new TextHeaderAtom();
159: tha.setParentRecord(_txtbox); // TextHeaderAtom is parent aware
160: _txtbox.appendChildRecord(tha);
161:
162: TextCharsAtom tca = new TextCharsAtom();
163: _txtbox.appendChildRecord(tca);
164:
165: StyleTextPropAtom sta = new StyleTextPropAtom(0);
166: _txtbox.appendChildRecord(sta);
167:
168: _txtrun = new TextRun(tha, tca, sta);
169: _txtrun.setText("");
170: spcont.addChildRecord(_txtbox.getEscherRecord());
171:
172: return spcont;
173: }
174:
175: /**
176: * Returns the text contained in this text frame.
177: *
178: * @return the text string for this textbox.
179: */
180: public String getText() {
181: return _txtrun == null ? null : _txtrun.getText();
182: }
183:
184: /**
185: * Sets the text contained in this text frame.
186: *
187: * @param text the text string used by this object.
188: */
189: public void setText(String text) {
190: _txtrun.setText(text);
191: }
192:
193: /**
194: * When a textbox is added to a sheet we need to tell upper-level
195: * <code>PPDrawing</code> about it.
196: *
197: * @param sh the sheet we are adding to
198: */
199: protected void afterInsert(Sheet sh) {
200: PPDrawing ppdrawing = sh.getPPDrawing();
201: ppdrawing.addTextboxWrapper(_txtbox);
202: // Ensure the escher layer knows about the added records
203: try {
204: _txtbox.writeOut(null);
205: } catch (IOException e) {
206: throw new HSLFException(e);
207: }
208: if (getAnchor().equals(new java.awt.Rectangle())
209: && !"".equals(getText()))
210: resizeToFitText();
211: }
212:
213: /**
214: * Adjust the size of the TextBox so it encompasses the text inside it.
215: */
216: public void resizeToFitText() {
217: try {
218: FontRenderContext frc = new FontRenderContext(null, true,
219: true);
220: RichTextRun rt = _txtrun.getRichTextRuns()[0];
221: int size = rt.getFontSize();
222: int style = 0;
223: if (rt.isBold())
224: style |= Font.BOLD;
225: if (rt.isItalic())
226: style |= Font.ITALIC;
227: String fntname = rt.getFontName();
228: Font font = new Font(fntname, style, size);
229:
230: TextLayout layout = new TextLayout(getText(), font, frc);
231: int width = Math.round(layout.getAdvance());
232: int height = Math.round(layout.getAscent());
233:
234: Dimension txsize = new Dimension(width, height);
235: java.awt.Rectangle anchor = getAnchor();
236: anchor.setSize(txsize);
237: setAnchor(anchor);
238: } catch (Exception e) {
239: e.printStackTrace();
240:
241: }
242: }
243:
244: /**
245: * Returns the type of vertical alignment for the text.
246: * One of the <code>Anchor*</code> constants defined in this class.
247: *
248: * @return the type of alignment
249: */
250: public int getVerticalAlignment() {
251: EscherOptRecord opt = (EscherOptRecord) getEscherChild(
252: _escherContainer, EscherOptRecord.RECORD_ID);
253: EscherSimpleProperty prop = (EscherSimpleProperty) getEscherProperty(
254: opt, EscherProperties.TEXT__ANCHORTEXT);
255: int valign;
256: if (prop == null) {
257: int type = getTextRun().getRunType();
258: switch (type) {
259: case TextHeaderAtom.TITLE_TYPE:
260: case TextHeaderAtom.CENTER_TITLE_TYPE:
261: valign = TextBox.AnchorMiddle;
262: break;
263: default:
264: valign = TextBox.AnchorTop;
265: break;
266: }
267: } else {
268: valign = prop.getPropertyValue();
269: }
270: return valign;
271: }
272:
273: /**
274: * Sets the type of vertical alignment for the text.
275: * One of the <code>Anchor*</code> constants defined in this class.
276: *
277: * @param align - the type of alignment
278: */
279: public void setVerticalAlignment(int align) {
280: EscherOptRecord opt = (EscherOptRecord) getEscherChild(
281: _escherContainer, EscherOptRecord.RECORD_ID);
282: setEscherProperty(opt, EscherProperties.TEXT__ANCHORTEXT, align);
283: }
284:
285: public void setHorizontalAlignment(int align) {
286: _txtrun.getRichTextRuns()[0].setAlignment(align);
287: }
288:
289: public int getHorizontalAlignment() {
290: return _txtrun.getRichTextRuns()[0].getAlignment();
291: }
292:
293: /**
294: * Returns the distance (in points) between the bottom of the text frame
295: * and the bottom of the inscribed rectangle of the shape that contains the text.
296: * Default value is 1/20 inch.
297: *
298: * @return the botom margin
299: */
300: public int getMarginBottom() {
301: EscherOptRecord opt = (EscherOptRecord) getEscherChild(
302: _escherContainer, EscherOptRecord.RECORD_ID);
303: EscherSimpleProperty prop = (EscherSimpleProperty) getEscherProperty(
304: opt, EscherProperties.TEXT__TEXTBOTTOM);
305: int val = prop == null ? EMU_PER_INCH / 20 : prop
306: .getPropertyValue();
307: return val / EMU_PER_POINT;
308: }
309:
310: /**
311: * Sets the botom margin.
312: * @see #getMarginBottom()
313: *
314: * @param margin the bottom margin
315: */
316: public void setMarginBottom(int margin) {
317: EscherOptRecord opt = (EscherOptRecord) getEscherChild(
318: _escherContainer, EscherOptRecord.RECORD_ID);
319: setEscherProperty(opt, EscherProperties.TEXT__TEXTBOTTOM,
320: margin * EMU_PER_POINT);
321: }
322:
323: /**
324: * Returns the distance (in EMUs) between the left edge of the text frame
325: * and the left edge of the inscribed rectangle of the shape that contains
326: * the text.
327: * Default value is 1/10 inch.
328: *
329: * @return the left margin
330: */
331: public int getMarginLeft() {
332: EscherOptRecord opt = (EscherOptRecord) getEscherChild(
333: _escherContainer, EscherOptRecord.RECORD_ID);
334: EscherSimpleProperty prop = (EscherSimpleProperty) getEscherProperty(
335: opt, EscherProperties.TEXT__TEXTBOTTOM);
336: int val = prop == null ? EMU_PER_INCH / 10 : prop
337: .getPropertyValue();
338: return val / EMU_PER_POINT;
339: }
340:
341: /**
342: * Sets the left margin.
343: * @see #getMarginLeft()
344: *
345: * @param margin the left margin
346: */
347: public void setMarginLeft(int margin) {
348: EscherOptRecord opt = (EscherOptRecord) getEscherChild(
349: _escherContainer, EscherOptRecord.RECORD_ID);
350: setEscherProperty(opt, EscherProperties.TEXT__TEXTLEFT, margin
351: * EMU_PER_POINT);
352: }
353:
354: /**
355: * Returns the distance (in EMUs) between the right edge of the
356: * text frame and the right edge of the inscribed rectangle of the shape
357: * that contains the text.
358: * Default value is 1/10 inch.
359: *
360: * @return the right margin
361: */
362: public int getMarginRight() {
363: EscherOptRecord opt = (EscherOptRecord) getEscherChild(
364: _escherContainer, EscherOptRecord.RECORD_ID);
365: EscherSimpleProperty prop = (EscherSimpleProperty) getEscherProperty(
366: opt, EscherProperties.TEXT__TEXTRIGHT);
367: int val = prop == null ? EMU_PER_INCH / 10 : prop
368: .getPropertyValue();
369: return val / EMU_PER_POINT;
370: }
371:
372: /**
373: * Sets the right margin.
374: * @see #getMarginRight()
375: *
376: * @param margin the right margin
377: */
378: public void setMarginRight(int margin) {
379: EscherOptRecord opt = (EscherOptRecord) getEscherChild(
380: _escherContainer, EscherOptRecord.RECORD_ID);
381: setEscherProperty(opt, EscherProperties.TEXT__TEXTRIGHT, margin
382: * EMU_PER_POINT);
383: }
384:
385: /**
386: * Returns the distance (in EMUs) between the top of the text frame
387: * and the top of the inscribed rectangle of the shape that contains the text.
388: * Default value is 1/20 inch.
389: *
390: * @return the top margin
391: */
392: public int getMarginTop() {
393: EscherOptRecord opt = (EscherOptRecord) getEscherChild(
394: _escherContainer, EscherOptRecord.RECORD_ID);
395: EscherSimpleProperty prop = (EscherSimpleProperty) getEscherProperty(
396: opt, EscherProperties.TEXT__TEXTTOP);
397: int val = prop == null ? EMU_PER_INCH / 20 : prop
398: .getPropertyValue();
399: return val / EMU_PER_POINT;
400: }
401:
402: /**
403: * Sets the top margin.
404: * @see #getMarginTop()
405: *
406: * @param margin the top margin
407: */
408: public void setMarginTop(int margin) {
409: EscherOptRecord opt = (EscherOptRecord) getEscherChild(
410: _escherContainer, EscherOptRecord.RECORD_ID);
411: setEscherProperty(opt, EscherProperties.TEXT__TEXTTOP, margin
412: * EMU_PER_POINT);
413: }
414:
415: /**
416: * Returns the value indicating word wrap.
417: * One of the <code>Wrap*</code> constants defined in this class.
418: *
419: * @return the value indicating word wrap
420: */
421: public int getWordWrap() {
422: EscherOptRecord opt = (EscherOptRecord) getEscherChild(
423: _escherContainer, EscherOptRecord.RECORD_ID);
424: EscherSimpleProperty prop = (EscherSimpleProperty) getEscherProperty(
425: opt, EscherProperties.TEXT__WRAPTEXT);
426: return prop == null ? WrapSquare : prop.getPropertyValue();
427: }
428:
429: /**
430: * Specifies how the text should be wrapped
431: *
432: * @param wrap the value indicating how the text should be wrapped
433: */
434: public void setWordWrap(int wrap) {
435: EscherOptRecord opt = (EscherOptRecord) getEscherChild(
436: _escherContainer, EscherOptRecord.RECORD_ID);
437: setEscherProperty(opt, EscherProperties.TEXT__WRAPTEXT, wrap);
438: }
439:
440: /**
441: * @return id for the text.
442: */
443: public int getTextId() {
444: EscherOptRecord opt = (EscherOptRecord) getEscherChild(
445: _escherContainer, EscherOptRecord.RECORD_ID);
446: EscherSimpleProperty prop = (EscherSimpleProperty) getEscherProperty(
447: opt, EscherProperties.TEXT__TEXTID);
448: return prop == null ? 0 : prop.getPropertyValue();
449: }
450:
451: /**
452: * Sets text ID
453: *
454: * @param id of the text
455: */
456: public void setTextId(int id) {
457: EscherOptRecord opt = (EscherOptRecord) getEscherChild(
458: _escherContainer, EscherOptRecord.RECORD_ID);
459: setEscherProperty(opt, EscherProperties.TEXT__TEXTID, id);
460: }
461:
462: /**
463: * The color used to fill this shape.
464: *
465: * @param color the background color
466: */
467: public void setBackgroundColor(Color color) {
468: EscherOptRecord opt = (EscherOptRecord) getEscherChild(
469: _escherContainer, EscherOptRecord.RECORD_ID);
470: int rgb = new Color(color.getBlue(), color.getGreen(), color
471: .getRed(), 0).getRGB();
472: setEscherProperty(opt, EscherProperties.FILL__FILLBACKCOLOR,
473: rgb);
474: }
475:
476: /**
477: * @return the TextRun object for this text box
478: */
479: public TextRun getTextRun() {
480: return _txtrun;
481: }
482:
483: public void setSheet(Sheet sheet) {
484: _sheet = sheet;
485:
486: // Initialize _txtrun object.
487: // (We can't do it in the constructor because the sheet
488: // is not assigned then, it's only built once we have
489: // all the records)
490: if (_txtrun == null)
491: initTextRun();
492: if (_txtrun == null) {
493: // No text records found, skip
494: _missingTextRecords = true;
495: return;
496: } else {
497: _missingTextRecords = false;
498: }
499:
500: // Supply the sheet to our child RichTextRuns
501: _txtrun.setSheet(sheet);
502: RichTextRun[] rt = _txtrun.getRichTextRuns();
503: for (int i = 0; i < rt.length; i++) {
504: rt[i].supplySlideShow(_sheet.getSlideShow());
505: }
506: }
507:
508: private void initTextRun() {
509: OutlineTextRefAtom ota = null;
510:
511: // Find the interesting child records
512: Record[] child = _txtbox.getChildRecords();
513: for (int i = 0; i < child.length; i++) {
514: if (child[i] instanceof OutlineTextRefAtom) {
515: ota = (OutlineTextRefAtom) child[i];
516: break;
517: }
518: }
519:
520: Sheet sheet = getSheet();
521: TextRun[] runs = sheet.getTextRuns();
522: if (ota != null) {
523: int idx = ota.getTextIndex();
524: for (int i = 0; i < runs.length; i++) {
525: if (runs[i].getIndex() == idx) {
526: _txtrun = runs[i];
527: }
528: }
529: if (_txtrun == null) {
530: logger.log(POILogger.WARN,
531: "text run not found for OutlineTextRefAtom.TextIndex="
532: + idx);
533: }
534: } else {
535: int shapeId = _escherContainer.getChildById(
536: EscherSpRecord.RECORD_ID).getShapeId();
537: if (runs != null)
538: for (int i = 0; i < runs.length; i++) {
539: if (runs[i].getShapeId() == shapeId) {
540: _txtrun = runs[i];
541: break;
542: }
543: }
544: }
545:
546: }
547: }
|