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 java.util.LinkedList;
021: import java.util.Vector;
022:
023: import org.apache.poi.hslf.model.textproperties.TextPropCollection;
024: import org.apache.poi.hslf.record.*;
025: import org.apache.poi.hslf.usermodel.RichTextRun;
026: import org.apache.poi.hslf.usermodel.SlideShow;
027: import org.apache.poi.util.StringUtil;
028:
029: /**
030: * This class represents a run of text in a powerpoint document. That
031: * run could be text on a sheet, or text in a note.
032: * It is only a very basic class for now
033: *
034: * @author Nick Burch
035: */
036:
037: public class TextRun {
038: // Note: These fields are protected to help with unit testing
039: // Other classes shouldn't really go playing with them!
040: protected TextHeaderAtom _headerAtom;
041: protected TextBytesAtom _byteAtom;
042: protected TextCharsAtom _charAtom;
043: protected StyleTextPropAtom _styleAtom;
044: protected boolean _isUnicode;
045: protected RichTextRun[] _rtRuns;
046: private SlideShow slideShow;
047: private Sheet sheet;
048: private int shapeId;
049: private int slwtIndex; //position in the owning SlideListWithText
050: /**
051: * all text run records that follow TextHeaderAtom.
052: * (there can be misc InteractiveInfo, TxInteractiveInfo and other records)
053: */
054: protected Record[] _records;
055:
056: /**
057: * Constructs a Text Run from a Unicode text block
058: *
059: * @param tha the TextHeaderAtom that defines what's what
060: * @param tca the TextCharsAtom containing the text
061: * @param sta the StyleTextPropAtom which defines the character stylings
062: */
063: public TextRun(TextHeaderAtom tha, TextCharsAtom tca,
064: StyleTextPropAtom sta) {
065: this (tha, null, tca, sta);
066: }
067:
068: /**
069: * Constructs a Text Run from a Ascii text block
070: *
071: * @param tha the TextHeaderAtom that defines what's what
072: * @param tba the TextBytesAtom containing the text
073: * @param sta the StyleTextPropAtom which defines the character stylings
074: */
075: public TextRun(TextHeaderAtom tha, TextBytesAtom tba,
076: StyleTextPropAtom sta) {
077: this (tha, tba, null, sta);
078: }
079:
080: /**
081: * Internal constructor and initializer
082: */
083: private TextRun(TextHeaderAtom tha, TextBytesAtom tba,
084: TextCharsAtom tca, StyleTextPropAtom sta) {
085: _headerAtom = tha;
086: _styleAtom = sta;
087: if (tba != null) {
088: _byteAtom = tba;
089: _isUnicode = false;
090: } else {
091: _charAtom = tca;
092: _isUnicode = true;
093: }
094: String runRawText = getText();
095:
096: // Figure out the rich text runs
097: LinkedList pStyles = new LinkedList();
098: LinkedList cStyles = new LinkedList();
099: if (_styleAtom != null) {
100: // Get the style atom to grok itself
101: _styleAtom.setParentTextSize(runRawText.length());
102: pStyles = _styleAtom.getParagraphStyles();
103: cStyles = _styleAtom.getCharacterStyles();
104: }
105:
106: // Handle case of no current style, with a default
107: if (pStyles.size() == 0 || cStyles.size() == 0) {
108: _rtRuns = new RichTextRun[1];
109: _rtRuns[0] = new RichTextRun(this , 0, runRawText.length());
110: } else {
111: // Build up Rich Text Runs, one for each
112: // character/paragraph style pair
113: Vector rtrs = new Vector();
114:
115: int pos = 0;
116:
117: int curP = 0;
118: int curC = 0;
119: int pLenRemain = -1;
120: int cLenRemain = -1;
121:
122: // Build one for each run with the same style
123: while (pos <= runRawText.length() && curP < pStyles.size()
124: && curC < cStyles.size()) {
125: // Get the Props to use
126: TextPropCollection pProps = (TextPropCollection) pStyles
127: .get(curP);
128: TextPropCollection cProps = (TextPropCollection) cStyles
129: .get(curC);
130:
131: int pLen = pProps.getCharactersCovered();
132: int cLen = cProps.getCharactersCovered();
133:
134: // Handle new pass
135: boolean freshSet = false;
136: if (pLenRemain == -1 && cLenRemain == -1) {
137: freshSet = true;
138: }
139: if (pLenRemain == -1) {
140: pLenRemain = pLen;
141: }
142: if (cLenRemain == -1) {
143: cLenRemain = cLen;
144: }
145:
146: // So we know how to build the eventual run
147: int runLen = -1;
148: boolean pShared = false;
149: boolean cShared = false;
150:
151: // Same size, new styles - neither shared
152: if (pLen == cLen && freshSet) {
153: runLen = cLen;
154: pShared = false;
155: cShared = false;
156: curP++;
157: curC++;
158: pLenRemain = -1;
159: cLenRemain = -1;
160: } else {
161: // Some sharing
162:
163: // See if we are already in a shared block
164: if (pLenRemain < pLen) {
165: // Existing shared p block
166: pShared = true;
167:
168: // Do we end with the c block, or either side of it?
169: if (pLenRemain == cLenRemain) {
170: // We end at the same time
171: cShared = false;
172: runLen = pLenRemain;
173: curP++;
174: curC++;
175: pLenRemain = -1;
176: cLenRemain = -1;
177: } else if (pLenRemain < cLenRemain) {
178: // We end before the c block
179: cShared = true;
180: runLen = pLenRemain;
181: curP++;
182: cLenRemain -= pLenRemain;
183: pLenRemain = -1;
184: } else {
185: // We end after the c block
186: cShared = false;
187: runLen = cLenRemain;
188: curC++;
189: pLenRemain -= cLenRemain;
190: cLenRemain = -1;
191: }
192: } else if (cLenRemain < cLen) {
193: // Existing shared c block
194: cShared = true;
195:
196: // Do we end with the p block, or either side of it?
197: if (pLenRemain == cLenRemain) {
198: // We end at the same time
199: pShared = false;
200: runLen = cLenRemain;
201: curP++;
202: curC++;
203: pLenRemain = -1;
204: cLenRemain = -1;
205: } else if (cLenRemain < pLenRemain) {
206: // We end before the p block
207: pShared = true;
208: runLen = cLenRemain;
209: curC++;
210: pLenRemain -= cLenRemain;
211: cLenRemain = -1;
212: } else {
213: // We end after the p block
214: pShared = false;
215: runLen = pLenRemain;
216: curP++;
217: cLenRemain -= pLenRemain;
218: pLenRemain = -1;
219: }
220: } else {
221: // Start of a shared block
222: if (pLenRemain < cLenRemain) {
223: // Shared c block
224: pShared = false;
225: cShared = true;
226: runLen = pLenRemain;
227: curP++;
228: cLenRemain -= pLenRemain;
229: pLenRemain = -1;
230: } else {
231: // Shared p block
232: pShared = true;
233: cShared = false;
234: runLen = cLenRemain;
235: curC++;
236: pLenRemain -= cLenRemain;
237: cLenRemain = -1;
238: }
239: }
240: }
241:
242: // Wind on
243: int prevPos = pos;
244: pos += runLen;
245: // Adjust for end-of-run extra 1 length
246: if (pos > runRawText.length()) {
247: runLen--;
248: }
249:
250: // Save
251: RichTextRun rtr = new RichTextRun(this , prevPos,
252: runLen, pProps, cProps, pShared, cShared);
253: rtrs.add(rtr);
254: }
255:
256: // Build the array
257: _rtRuns = new RichTextRun[rtrs.size()];
258: rtrs.copyInto(_rtRuns);
259: }
260: }
261:
262: // Update methods follow
263:
264: /**
265: * Saves the given string to the records. Doesn't touch the stylings.
266: */
267: private void storeText(String s) {
268: // Remove a single trailing \n, as there is an implicit one at the
269: // end of every record
270: if (s.endsWith("\n")) {
271: s = s.substring(0, s.length() - 1);
272: }
273:
274: // Store in the appropriate record
275: if (_isUnicode) {
276: // The atom can safely convert to unicode
277: _charAtom.setText(s);
278: } else {
279: // Will it fit in a 8 bit atom?
280: boolean hasMultibyte = StringUtil.hasMultibyte(s);
281: if (!hasMultibyte) {
282: // Fine to go into 8 bit atom
283: byte[] text = new byte[s.length()];
284: StringUtil.putCompressedUnicode(s, text, 0);
285: _byteAtom.setText(text);
286: } else {
287: // Need to swap a TextBytesAtom for a TextCharsAtom
288:
289: // Build the new TextCharsAtom
290: _charAtom = new TextCharsAtom();
291: _charAtom.setText(s);
292:
293: // Use the TextHeaderAtom to do the swap on the parent
294: RecordContainer parent = _headerAtom.getParentRecord();
295: Record[] cr = parent.getChildRecords();
296: for (int i = 0; i < cr.length; i++) {
297: // Look for TextBytesAtom
298: if (cr[i].equals(_byteAtom)) {
299: // Found it, so replace, then all done
300: cr[i] = _charAtom;
301: break;
302: }
303: }
304:
305: // Flag the change
306: _byteAtom = null;
307: _isUnicode = true;
308: }
309: }
310: }
311:
312: /**
313: * Handles an update to the text stored in one of the Rich Text Runs
314: * @param run
315: * @param s
316: */
317: public synchronized void changeTextInRichTextRun(RichTextRun run,
318: String s) {
319: // Figure out which run it is
320: int runID = -1;
321: for (int i = 0; i < _rtRuns.length; i++) {
322: if (run.equals(_rtRuns[i])) {
323: runID = i;
324: }
325: }
326: if (runID == -1) {
327: throw new IllegalArgumentException(
328: "Supplied RichTextRun wasn't from this TextRun");
329: }
330:
331: // Ensure a StyleTextPropAtom is present, adding if required
332: ensureStyleAtomPresent();
333:
334: // Update the text length for its Paragraph and Character stylings
335: // If it's shared:
336: // * calculate the new length based on the run's old text
337: // * this should leave in any +1's for the end of block if needed
338: // If it isn't shared:
339: // * reset the length, to the new string's length
340: // * add on +1 if the last block
341: // The last run needs its stylings to be 1 longer than the raw
342: // text is. This is to define the stylings that any new text
343: // that is added will inherit
344: TextPropCollection pCol = run._getRawParagraphStyle();
345: TextPropCollection cCol = run._getRawCharacterStyle();
346: int newSize = s.length();
347: if (runID == _rtRuns.length - 1) {
348: newSize++;
349: }
350:
351: if (run._isParagraphStyleShared()) {
352: pCol.updateTextSize(pCol.getCharactersCovered()
353: - run.getLength() + s.length());
354: } else {
355: pCol.updateTextSize(newSize);
356: }
357: if (run._isCharacterStyleShared()) {
358: cCol.updateTextSize(cCol.getCharactersCovered()
359: - run.getLength() + s.length());
360: } else {
361: cCol.updateTextSize(newSize);
362: }
363:
364: // Build up the new text
365: // As we go through, update the start position for all subsequent runs
366: // The building relies on the old text still being present
367: StringBuffer newText = new StringBuffer();
368: for (int i = 0; i < _rtRuns.length; i++) {
369: int newStartPos = newText.length();
370:
371: // Build up the new text
372: if (i != runID) {
373: // Not the affected run, so keep old text
374: newText.append(_rtRuns[i].getRawText());
375: } else {
376: // Affected run, so use new text
377: newText.append(s);
378: }
379:
380: // Do we need to update the start position of this run?
381: // (Need to get the text before we update the start pos)
382: if (i <= runID) {
383: // Change is after this, so don't need to change start position
384: } else {
385: // Change has occured, so update start position
386: _rtRuns[i].updateStartPosition(newStartPos);
387: }
388: }
389:
390: // Now we can save the new text
391: storeText(newText.toString());
392: }
393:
394: /**
395: * Changes the text, and sets it all to have the same styling
396: * as the the first character has.
397: * If you care about styling, do setText on a RichTextRun instead
398: */
399: public synchronized void setText(String s) {
400: // Save the new text to the atoms
401: storeText(s);
402: RichTextRun fst = _rtRuns[0];
403:
404: // Finally, zap and re-do the RichTextRuns
405: for (int i = 0; i < _rtRuns.length; i++) {
406: _rtRuns[i] = null;
407: }
408: _rtRuns = new RichTextRun[1];
409: _rtRuns[0] = fst;
410:
411: // Now handle record stylings:
412: // If there isn't styling
413: // no change, stays with no styling
414: // If there is styling:
415: // everthing gets the same style that the first block has
416: if (_styleAtom != null) {
417: LinkedList pStyles = _styleAtom.getParagraphStyles();
418: while (pStyles.size() > 1) {
419: pStyles.removeLast();
420: }
421:
422: LinkedList cStyles = _styleAtom.getCharacterStyles();
423: while (cStyles.size() > 1) {
424: cStyles.removeLast();
425: }
426:
427: _rtRuns[0].setText(s);
428: } else {
429: // Recreate rich text run with no styling
430: _rtRuns[0] = new RichTextRun(this , 0, s.length());
431: }
432: }
433:
434: /**
435: * Ensure a StyleTextPropAtom is present for this run,
436: * by adding if required. Normally for internal TextRun use.
437: */
438: public synchronized void ensureStyleAtomPresent() {
439: if (_styleAtom != null) {
440: // All there
441: return;
442: }
443:
444: // Create a new one at the right size
445: _styleAtom = new StyleTextPropAtom(getRawText().length() + 1);
446:
447: // Use the TextHeader atom to get at the parent
448: RecordContainer runAtomsParent = _headerAtom.getParentRecord();
449:
450: // Add the new StyleTextPropAtom after the TextCharsAtom / TextBytesAtom
451: Record addAfter = _byteAtom;
452: if (_byteAtom == null) {
453: addAfter = _charAtom;
454: }
455: runAtomsParent.addChildAfter(_styleAtom, addAfter);
456:
457: // Feed this to our sole rich text run
458: if (_rtRuns.length != 1) {
459: throw new IllegalStateException(
460: "Needed to add StyleTextPropAtom when had many rich text runs");
461: }
462: // These are the only styles for now
463: _rtRuns[0].supplyTextProps((TextPropCollection) _styleAtom
464: .getParagraphStyles().get(0),
465: (TextPropCollection) _styleAtom.getCharacterStyles()
466: .get(0), false, false);
467: }
468:
469: // Accesser methods follow
470:
471: /**
472: * Returns the text content of the run, which has been made safe
473: * for printing and other use.
474: */
475: public String getText() {
476: String rawText = getRawText();
477:
478: // PowerPoint seems to store files with \r as the line break
479: // The messes things up on everything but a Mac, so translate
480: // them to \n
481: String text = rawText.replace('\r', '\n');
482: return text;
483: }
484:
485: /**
486: * Returns the raw text content of the run. This hasn't had any
487: * changes applied to it, and so is probably unlikely to print
488: * out nicely.
489: */
490: public String getRawText() {
491: if (_isUnicode) {
492: return _charAtom.getText();
493: } else {
494: return _byteAtom.getText();
495: }
496: }
497:
498: /**
499: * Fetch the rich text runs (runs of text with the same styling) that
500: * are contained within this block of text
501: */
502: public RichTextRun[] getRichTextRuns() {
503: return _rtRuns;
504: }
505:
506: /**
507: * Returns the type of the text, from the TextHeaderAtom.
508: * Possible values can be seen from TextHeaderAtom
509: * @see org.apache.poi.hslf.record.TextHeaderAtom
510: */
511: public int getRunType() {
512: return _headerAtom.getTextType();
513: }
514:
515: /**
516: * Changes the type of the text. Values should be taken
517: * from TextHeaderAtom. No checking is done to ensure you
518: * set this to a valid value!
519: * @see org.apache.poi.hslf.record.TextHeaderAtom
520: */
521: public void setRunType(int type) {
522: _headerAtom.setTextType(type);
523: }
524:
525: /**
526: * Supply the SlideShow we belong to.
527: * Also passes it on to our child RichTextRuns
528: */
529: public void supplySlideShow(SlideShow ss) {
530: slideShow = ss;
531: if (_rtRuns != null) {
532: for (int i = 0; i < _rtRuns.length; i++) {
533: _rtRuns[i].supplySlideShow(slideShow);
534: }
535: }
536: }
537:
538: public void setSheet(Sheet sheet) {
539: this .sheet = sheet;
540: }
541:
542: public Sheet getSheet() {
543: return this .sheet;
544: }
545:
546: /**
547: * @return Shape ID
548: */
549: protected int getShapeId() {
550: return shapeId;
551: }
552:
553: /**
554: * @param id Shape ID
555: */
556: protected void setShapeId(int id) {
557: shapeId = id;
558: }
559:
560: /**
561: * @return 0-based index of the text run in the SLWT container
562: */
563: protected int getIndex() {
564: return slwtIndex;
565: }
566:
567: /**
568: * @param id 0-based index of the text run in the SLWT container
569: */
570: protected void setIndex(int id) {
571: slwtIndex = id;
572: }
573:
574: /**
575: * Returns the array of all hyperlinks in this text run
576: *
577: * @return the array of all hyperlinks in this text run
578: * or <code>null</code> if not found.
579: */
580: public Hyperlink[] getHyperlinks() {
581: return Hyperlink.find(this);
582: }
583: }
|