You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

TextRun.java 22KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712
  1. /* ====================================================================
  2. Licensed to the Apache Software Foundation (ASF) under one or more
  3. contributor license agreements. See the NOTICE file distributed with
  4. this work for additional information regarding copyright ownership.
  5. The ASF licenses this file to You under the Apache License, Version 2.0
  6. (the "License"); you may not use this file except in compliance with
  7. the License. You may obtain a copy of the License at
  8. http://www.apache.org/licenses/LICENSE-2.0
  9. Unless required by applicable law or agreed to in writing, software
  10. distributed under the License is distributed on an "AS IS" BASIS,
  11. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. See the License for the specific language governing permissions and
  13. limitations under the License.
  14. ==================================================================== */
  15. package org.apache.poi.hslf.model;
  16. import java.util.LinkedList;
  17. import java.util.Vector;
  18. import org.apache.poi.hslf.model.textproperties.TextPropCollection;
  19. import org.apache.poi.hslf.record.*;
  20. import org.apache.poi.hslf.usermodel.RichTextRun;
  21. import org.apache.poi.hslf.usermodel.SlideShow;
  22. import org.apache.poi.util.StringUtil;
  23. /**
  24. * This class represents a run of text in a powerpoint document. That
  25. * run could be text on a sheet, or text in a note.
  26. * It is only a very basic class for now
  27. *
  28. * @author Nick Burch
  29. */
  30. public final class TextRun
  31. {
  32. // Note: These fields are protected to help with unit testing
  33. // Other classes shouldn't really go playing with them!
  34. protected TextHeaderAtom _headerAtom;
  35. protected TextBytesAtom _byteAtom;
  36. protected TextCharsAtom _charAtom;
  37. protected StyleTextPropAtom _styleAtom;
  38. protected TextRulerAtom _ruler;
  39. protected boolean _isUnicode;
  40. protected RichTextRun[] _rtRuns;
  41. private SlideShow slideShow;
  42. private Sheet _sheet;
  43. private int shapeId;
  44. private int slwtIndex = -1; //position in the owning SlideListWithText
  45. /**
  46. * all text run records that follow TextHeaderAtom.
  47. * (there can be misc InteractiveInfo, TxInteractiveInfo and other records)
  48. */
  49. protected Record[] _records;
  50. /**
  51. * Constructs a Text Run from a Unicode text block
  52. *
  53. * @param tha the TextHeaderAtom that defines what's what
  54. * @param tca the TextCharsAtom containing the text
  55. * @param sta the StyleTextPropAtom which defines the character stylings
  56. */
  57. public TextRun(TextHeaderAtom tha, TextCharsAtom tca, StyleTextPropAtom sta) {
  58. this(tha,null,tca,sta);
  59. }
  60. /**
  61. * Constructs a Text Run from a Ascii text block
  62. *
  63. * @param tha the TextHeaderAtom that defines what's what
  64. * @param tba the TextBytesAtom containing the text
  65. * @param sta the StyleTextPropAtom which defines the character stylings
  66. */
  67. public TextRun(TextHeaderAtom tha, TextBytesAtom tba, StyleTextPropAtom sta) {
  68. this(tha,tba,null,sta);
  69. }
  70. /**
  71. * Internal constructor and initializer
  72. */
  73. private TextRun(TextHeaderAtom tha, TextBytesAtom tba, TextCharsAtom tca, StyleTextPropAtom sta) {
  74. _headerAtom = tha;
  75. _styleAtom = sta;
  76. if(tba != null) {
  77. _byteAtom = tba;
  78. _isUnicode = false;
  79. } else {
  80. _charAtom = tca;
  81. _isUnicode = true;
  82. }
  83. String runRawText = getText();
  84. // Figure out the rich text runs
  85. LinkedList pStyles = new LinkedList();
  86. LinkedList cStyles = new LinkedList();
  87. if(_styleAtom != null) {
  88. // Get the style atom to grok itself
  89. _styleAtom.setParentTextSize(runRawText.length());
  90. pStyles = _styleAtom.getParagraphStyles();
  91. cStyles = _styleAtom.getCharacterStyles();
  92. }
  93. buildRichTextRuns(pStyles, cStyles, runRawText);
  94. }
  95. public void buildRichTextRuns(LinkedList pStyles, LinkedList cStyles, String runRawText){
  96. // Handle case of no current style, with a default
  97. if(pStyles.size() == 0 || cStyles.size() == 0) {
  98. _rtRuns = new RichTextRun[1];
  99. _rtRuns[0] = new RichTextRun(this, 0, runRawText.length());
  100. } else {
  101. // Build up Rich Text Runs, one for each
  102. // character/paragraph style pair
  103. Vector rtrs = new Vector();
  104. int pos = 0;
  105. int curP = 0;
  106. int curC = 0;
  107. int pLenRemain = -1;
  108. int cLenRemain = -1;
  109. // Build one for each run with the same style
  110. while(pos <= runRawText.length() && curP < pStyles.size() && curC < cStyles.size()) {
  111. // Get the Props to use
  112. TextPropCollection pProps = (TextPropCollection)pStyles.get(curP);
  113. TextPropCollection cProps = (TextPropCollection)cStyles.get(curC);
  114. int pLen = pProps.getCharactersCovered();
  115. int cLen = cProps.getCharactersCovered();
  116. // Handle new pass
  117. boolean freshSet = false;
  118. if(pLenRemain == -1 && cLenRemain == -1) { freshSet = true; }
  119. if(pLenRemain == -1) { pLenRemain = pLen; }
  120. if(cLenRemain == -1) { cLenRemain = cLen; }
  121. // So we know how to build the eventual run
  122. int runLen = -1;
  123. boolean pShared = false;
  124. boolean cShared = false;
  125. // Same size, new styles - neither shared
  126. if(pLen == cLen && freshSet) {
  127. runLen = cLen;
  128. pShared = false;
  129. cShared = false;
  130. curP++;
  131. curC++;
  132. pLenRemain = -1;
  133. cLenRemain = -1;
  134. } else {
  135. // Some sharing
  136. // See if we are already in a shared block
  137. if(pLenRemain < pLen) {
  138. // Existing shared p block
  139. pShared = true;
  140. // Do we end with the c block, or either side of it?
  141. if(pLenRemain == cLenRemain) {
  142. // We end at the same time
  143. cShared = false;
  144. runLen = pLenRemain;
  145. curP++;
  146. curC++;
  147. pLenRemain = -1;
  148. cLenRemain = -1;
  149. } else if(pLenRemain < cLenRemain) {
  150. // We end before the c block
  151. cShared = true;
  152. runLen = pLenRemain;
  153. curP++;
  154. cLenRemain -= pLenRemain;
  155. pLenRemain = -1;
  156. } else {
  157. // We end after the c block
  158. cShared = false;
  159. runLen = cLenRemain;
  160. curC++;
  161. pLenRemain -= cLenRemain;
  162. cLenRemain = -1;
  163. }
  164. } else if(cLenRemain < cLen) {
  165. // Existing shared c block
  166. cShared = true;
  167. // Do we end with the p block, or either side of it?
  168. if(pLenRemain == cLenRemain) {
  169. // We end at the same time
  170. pShared = false;
  171. runLen = cLenRemain;
  172. curP++;
  173. curC++;
  174. pLenRemain = -1;
  175. cLenRemain = -1;
  176. } else if(cLenRemain < pLenRemain) {
  177. // We end before the p block
  178. pShared = true;
  179. runLen = cLenRemain;
  180. curC++;
  181. pLenRemain -= cLenRemain;
  182. cLenRemain = -1;
  183. } else {
  184. // We end after the p block
  185. pShared = false;
  186. runLen = pLenRemain;
  187. curP++;
  188. cLenRemain -= pLenRemain;
  189. pLenRemain = -1;
  190. }
  191. } else {
  192. // Start of a shared block
  193. if(pLenRemain < cLenRemain) {
  194. // Shared c block
  195. pShared = false;
  196. cShared = true;
  197. runLen = pLenRemain;
  198. curP++;
  199. cLenRemain -= pLenRemain;
  200. pLenRemain = -1;
  201. } else {
  202. // Shared p block
  203. pShared = true;
  204. cShared = false;
  205. runLen = cLenRemain;
  206. curC++;
  207. pLenRemain -= cLenRemain;
  208. cLenRemain = -1;
  209. }
  210. }
  211. }
  212. // Wind on
  213. int prevPos = pos;
  214. pos += runLen;
  215. // Adjust for end-of-run extra 1 length
  216. if(pos > runRawText.length()) {
  217. runLen--;
  218. }
  219. // Save
  220. RichTextRun rtr = new RichTextRun(this, prevPos, runLen, pProps, cProps, pShared, cShared);
  221. rtrs.add(rtr);
  222. }
  223. // Build the array
  224. _rtRuns = new RichTextRun[rtrs.size()];
  225. rtrs.copyInto(_rtRuns);
  226. }
  227. }
  228. // Update methods follow
  229. /**
  230. * Adds the supplied text onto the end of the TextRun,
  231. * creating a new RichTextRun (returned) for it to
  232. * sit in.
  233. * In many cases, before calling this, you'll want to add
  234. * a newline onto the end of your last RichTextRun
  235. */
  236. public RichTextRun appendText(String s) {
  237. // We will need a StyleTextProp atom
  238. ensureStyleAtomPresent();
  239. // First up, append the text to the
  240. // underlying text atom
  241. int oldSize = getRawText().length();
  242. storeText(
  243. getRawText() + s
  244. );
  245. // If either of the previous styles overran
  246. // the text by one, we need to shuffle that
  247. // extra character onto the new ones
  248. int pOverRun = _styleAtom.getParagraphTextLengthCovered() - oldSize;
  249. int cOverRun = _styleAtom.getCharacterTextLengthCovered() - oldSize;
  250. if(pOverRun > 0) {
  251. TextPropCollection tpc = (TextPropCollection)
  252. _styleAtom.getParagraphStyles().getLast();
  253. tpc.updateTextSize(
  254. tpc.getCharactersCovered() - pOverRun
  255. );
  256. }
  257. if(cOverRun > 0) {
  258. TextPropCollection tpc = (TextPropCollection)
  259. _styleAtom.getCharacterStyles().getLast();
  260. tpc.updateTextSize(
  261. tpc.getCharactersCovered() - cOverRun
  262. );
  263. }
  264. // Next, add the styles for its paragraph and characters
  265. TextPropCollection newPTP =
  266. _styleAtom.addParagraphTextPropCollection(s.length()+pOverRun);
  267. TextPropCollection newCTP =
  268. _styleAtom.addCharacterTextPropCollection(s.length()+cOverRun);
  269. // Now, create the new RichTextRun
  270. RichTextRun nr = new RichTextRun(
  271. this, oldSize, s.length(),
  272. newPTP, newCTP, false, false
  273. );
  274. // Add the new RichTextRun onto our list
  275. RichTextRun[] newRuns = new RichTextRun[_rtRuns.length+1];
  276. System.arraycopy(_rtRuns, 0, newRuns, 0, _rtRuns.length);
  277. newRuns[newRuns.length-1] = nr;
  278. _rtRuns = newRuns;
  279. // And return the new run to the caller
  280. return nr;
  281. }
  282. /**
  283. * Saves the given string to the records. Doesn't
  284. * touch the stylings.
  285. */
  286. private void storeText(String s) {
  287. // Remove a single trailing \r, as there is an implicit one at the
  288. // end of every record
  289. if(s.endsWith("\r")) {
  290. s = s.substring(0, s.length()-1);
  291. }
  292. // Store in the appropriate record
  293. if(_isUnicode) {
  294. // The atom can safely convert to unicode
  295. _charAtom.setText(s);
  296. } else {
  297. // Will it fit in a 8 bit atom?
  298. boolean hasMultibyte = StringUtil.hasMultibyte(s);
  299. if(! hasMultibyte) {
  300. // Fine to go into 8 bit atom
  301. byte[] text = new byte[s.length()];
  302. StringUtil.putCompressedUnicode(s,text,0);
  303. _byteAtom.setText(text);
  304. } else {
  305. // Need to swap a TextBytesAtom for a TextCharsAtom
  306. // Build the new TextCharsAtom
  307. _charAtom = new TextCharsAtom();
  308. _charAtom.setText(s);
  309. // Use the TextHeaderAtom to do the swap on the parent
  310. RecordContainer parent = _headerAtom.getParentRecord();
  311. Record[] cr = parent.getChildRecords();
  312. for(int i=0; i<cr.length; i++) {
  313. // Look for TextBytesAtom
  314. if(cr[i].equals(_byteAtom)) {
  315. // Found it, so replace, then all done
  316. cr[i] = _charAtom;
  317. break;
  318. }
  319. }
  320. // Flag the change
  321. _byteAtom = null;
  322. _isUnicode = true;
  323. }
  324. }
  325. /**
  326. * If TextSpecInfoAtom is present, we must update the text size in it,
  327. * otherwise the ppt will be corrupted
  328. */
  329. if(_records != null) for (int i = 0; i < _records.length; i++) {
  330. if(_records[i] instanceof TextSpecInfoAtom){
  331. TextSpecInfoAtom specAtom = (TextSpecInfoAtom)_records[i];
  332. if((s.length() + 1) != specAtom.getCharactersCovered()){
  333. specAtom.reset(s.length() + 1);
  334. }
  335. }
  336. }
  337. }
  338. /**
  339. * Handles an update to the text stored in one of the Rich Text Runs
  340. * @param run
  341. * @param s
  342. */
  343. public void changeTextInRichTextRun(RichTextRun run, String s) {
  344. // Figure out which run it is
  345. int runID = -1;
  346. for(int i=0; i<_rtRuns.length; i++) {
  347. if(run.equals(_rtRuns[i])) {
  348. runID = i;
  349. }
  350. }
  351. if(runID == -1) {
  352. throw new IllegalArgumentException("Supplied RichTextRun wasn't from this TextRun");
  353. }
  354. // Ensure a StyleTextPropAtom is present, adding if required
  355. ensureStyleAtomPresent();
  356. // Update the text length for its Paragraph and Character stylings
  357. // If it's shared:
  358. // * calculate the new length based on the run's old text
  359. // * this should leave in any +1's for the end of block if needed
  360. // If it isn't shared:
  361. // * reset the length, to the new string's length
  362. // * add on +1 if the last block
  363. // The last run needs its stylings to be 1 longer than the raw
  364. // text is. This is to define the stylings that any new text
  365. // that is added will inherit
  366. TextPropCollection pCol = run._getRawParagraphStyle();
  367. TextPropCollection cCol = run._getRawCharacterStyle();
  368. int newSize = s.length();
  369. if(runID == _rtRuns.length-1) {
  370. newSize++;
  371. }
  372. if(run._isParagraphStyleShared()) {
  373. pCol.updateTextSize( pCol.getCharactersCovered() - run.getLength() + s.length() );
  374. } else {
  375. pCol.updateTextSize(newSize);
  376. }
  377. if(run._isCharacterStyleShared()) {
  378. cCol.updateTextSize( cCol.getCharactersCovered() - run.getLength() + s.length() );
  379. } else {
  380. cCol.updateTextSize(newSize);
  381. }
  382. // Build up the new text
  383. // As we go through, update the start position for all subsequent runs
  384. // The building relies on the old text still being present
  385. StringBuffer newText = new StringBuffer();
  386. for(int i=0; i<_rtRuns.length; i++) {
  387. int newStartPos = newText.length();
  388. // Build up the new text
  389. if(i != runID) {
  390. // Not the affected run, so keep old text
  391. newText.append(_rtRuns[i].getRawText());
  392. } else {
  393. // Affected run, so use new text
  394. newText.append(s);
  395. }
  396. // Do we need to update the start position of this run?
  397. // (Need to get the text before we update the start pos)
  398. if(i <= runID) {
  399. // Change is after this, so don't need to change start position
  400. } else {
  401. // Change has occured, so update start position
  402. _rtRuns[i].updateStartPosition(newStartPos);
  403. }
  404. }
  405. // Now we can save the new text
  406. storeText(newText.toString());
  407. }
  408. /**
  409. * Changes the text, and sets it all to have the same styling
  410. * as the the first character has.
  411. * If you care about styling, do setText on a RichTextRun instead
  412. */
  413. public void setRawText(String s) {
  414. // Save the new text to the atoms
  415. storeText(s);
  416. RichTextRun fst = _rtRuns[0];
  417. // Finally, zap and re-do the RichTextRuns
  418. for(int i=0; i<_rtRuns.length; i++) { _rtRuns[i] = null; }
  419. _rtRuns = new RichTextRun[1];
  420. _rtRuns[0] = fst;
  421. // Now handle record stylings:
  422. // If there isn't styling
  423. // no change, stays with no styling
  424. // If there is styling:
  425. // everthing gets the same style that the first block has
  426. if(_styleAtom != null) {
  427. LinkedList pStyles = _styleAtom.getParagraphStyles();
  428. while(pStyles.size() > 1) { pStyles.removeLast(); }
  429. LinkedList cStyles = _styleAtom.getCharacterStyles();
  430. while(cStyles.size() > 1) { cStyles.removeLast(); }
  431. _rtRuns[0].setText(s);
  432. } else {
  433. // Recreate rich text run with no styling
  434. _rtRuns[0] = new RichTextRun(this,0,s.length());
  435. }
  436. }
  437. /**
  438. * Changes the text.
  439. * Converts '\r' into '\n'
  440. */
  441. public void setText(String s) {
  442. String text = normalize(s);
  443. setRawText(text);
  444. }
  445. /**
  446. * Ensure a StyleTextPropAtom is present for this run,
  447. * by adding if required. Normally for internal TextRun use.
  448. */
  449. public void ensureStyleAtomPresent() {
  450. if(_styleAtom != null) {
  451. // All there
  452. return;
  453. }
  454. // Create a new one at the right size
  455. _styleAtom = new StyleTextPropAtom(getRawText().length() + 1);
  456. // Use the TextHeader atom to get at the parent
  457. RecordContainer runAtomsParent = _headerAtom.getParentRecord();
  458. // Add the new StyleTextPropAtom after the TextCharsAtom / TextBytesAtom
  459. Record addAfter = _byteAtom;
  460. if(_byteAtom == null) { addAfter = _charAtom; }
  461. runAtomsParent.addChildAfter(_styleAtom, addAfter);
  462. // Feed this to our sole rich text run
  463. if(_rtRuns.length != 1) {
  464. throw new IllegalStateException("Needed to add StyleTextPropAtom when had many rich text runs");
  465. }
  466. // These are the only styles for now
  467. _rtRuns[0].supplyTextProps(
  468. (TextPropCollection)_styleAtom.getParagraphStyles().get(0),
  469. (TextPropCollection)_styleAtom.getCharacterStyles().get(0),
  470. false,
  471. false
  472. );
  473. }
  474. // Accesser methods follow
  475. /**
  476. * Returns the text content of the run, which has been made safe
  477. * for printing and other use.
  478. */
  479. public String getText() {
  480. String rawText = getRawText();
  481. // PowerPoint seems to store files with \r as the line break
  482. // The messes things up on everything but a Mac, so translate
  483. // them to \n
  484. String text = rawText.replace('\r','\n');
  485. int type = _headerAtom == null ? 0 : _headerAtom.getTextType();
  486. if(type == TextHeaderAtom.TITLE_TYPE || type == TextHeaderAtom.CENTER_TITLE_TYPE){
  487. //0xB acts like cariage return in page titles and like blank in the others
  488. text = text.replace((char) 0x0B, '\n');
  489. } else {
  490. text = text.replace((char) 0x0B, ' ');
  491. }
  492. return text;
  493. }
  494. /**
  495. * Returns the raw text content of the run. This hasn't had any
  496. * changes applied to it, and so is probably unlikely to print
  497. * out nicely.
  498. */
  499. public String getRawText() {
  500. if(_isUnicode) {
  501. return _charAtom.getText();
  502. }
  503. return _byteAtom.getText();
  504. }
  505. /**
  506. * Fetch the rich text runs (runs of text with the same styling) that
  507. * are contained within this block of text
  508. */
  509. public RichTextRun[] getRichTextRuns() {
  510. return _rtRuns;
  511. }
  512. /**
  513. * Returns the type of the text, from the TextHeaderAtom.
  514. * Possible values can be seen from TextHeaderAtom
  515. * @see org.apache.poi.hslf.record.TextHeaderAtom
  516. */
  517. public int getRunType() {
  518. return _headerAtom.getTextType();
  519. }
  520. /**
  521. * Changes the type of the text. Values should be taken
  522. * from TextHeaderAtom. No checking is done to ensure you
  523. * set this to a valid value!
  524. * @see org.apache.poi.hslf.record.TextHeaderAtom
  525. */
  526. public void setRunType(int type) {
  527. _headerAtom.setTextType(type);
  528. }
  529. /**
  530. * Supply the SlideShow we belong to.
  531. * Also passes it on to our child RichTextRuns
  532. */
  533. public void supplySlideShow(SlideShow ss) {
  534. slideShow = ss;
  535. if(_rtRuns != null) {
  536. for(int i=0; i<_rtRuns.length; i++) {
  537. _rtRuns[i].supplySlideShow(slideShow);
  538. }
  539. }
  540. }
  541. public void setSheet(Sheet sheet){
  542. this._sheet = sheet;
  543. }
  544. public Sheet getSheet(){
  545. return this._sheet;
  546. }
  547. /**
  548. * @return Shape ID
  549. */
  550. protected int getShapeId(){
  551. return shapeId;
  552. }
  553. /**
  554. * @param id Shape ID
  555. */
  556. protected void setShapeId(int id){
  557. shapeId = id;
  558. }
  559. /**
  560. * @return 0-based index of the text run in the SLWT container
  561. */
  562. protected int getIndex(){
  563. return slwtIndex;
  564. }
  565. /**
  566. * @param id 0-based index of the text run in the SLWT container
  567. */
  568. protected void setIndex(int id){
  569. slwtIndex = id;
  570. }
  571. /**
  572. * Returns the array of all hyperlinks in this text run
  573. *
  574. * @return the array of all hyperlinks in this text run
  575. * or <code>null</code> if not found.
  576. */
  577. public Hyperlink[] getHyperlinks(){
  578. return Hyperlink.find(this);
  579. }
  580. /**
  581. * Fetch RichTextRun at a given position
  582. *
  583. * @param pos 0-based index in the text
  584. * @return RichTextRun or null if not found
  585. */
  586. public RichTextRun getRichTextRunAt(int pos){
  587. for (int i = 0; i < _rtRuns.length; i++) {
  588. int start = _rtRuns[i].getStartIndex();
  589. int end = _rtRuns[i].getEndIndex();
  590. if(pos >= start && pos < end) return _rtRuns[i];
  591. }
  592. return null;
  593. }
  594. public TextRulerAtom getTextRuler(){
  595. if(_ruler == null){
  596. if(_records != null) for (int i = 0; i < _records.length; i++) {
  597. if(_records[i] instanceof TextRulerAtom) {
  598. _ruler = (TextRulerAtom)_records[i];
  599. break;
  600. }
  601. }
  602. }
  603. return _ruler;
  604. }
  605. public TextRulerAtom createTextRuler(){
  606. _ruler = getTextRuler();
  607. if(_ruler == null){
  608. _ruler = TextRulerAtom.getParagraphInstance();
  609. _headerAtom.getParentRecord().appendChildRecord(_ruler);
  610. }
  611. return _ruler;
  612. }
  613. /**
  614. * Returns a new string with line breaks converted into internal ppt representation
  615. */
  616. public String normalize(String s){
  617. String ns = s.replaceAll("\\r?\\n", "\r");
  618. return ns;
  619. }
  620. /**
  621. * Returns records that make up this text run
  622. *
  623. * @return text run records
  624. */
  625. public Record[] getRecords(){
  626. return _records;
  627. }
  628. }