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.

HSLFTextParagraph.java 59KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603
  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.usermodel;
  16. import static org.apache.poi.hslf.record.RecordTypes.OutlineTextRefAtom;
  17. import java.awt.Color;
  18. import java.io.IOException;
  19. import java.util.ArrayList;
  20. import java.util.Iterator;
  21. import java.util.List;
  22. import org.apache.poi.hslf.exceptions.HSLFException;
  23. import org.apache.poi.hslf.model.PPFont;
  24. import org.apache.poi.hslf.model.textproperties.BitMaskTextProp;
  25. import org.apache.poi.hslf.model.textproperties.FontAlignmentProp;
  26. import org.apache.poi.hslf.model.textproperties.IndentProp;
  27. import org.apache.poi.hslf.model.textproperties.ParagraphFlagsTextProp;
  28. import org.apache.poi.hslf.model.textproperties.TextAlignmentProp;
  29. import org.apache.poi.hslf.model.textproperties.TextPFException9;
  30. import org.apache.poi.hslf.model.textproperties.TextProp;
  31. import org.apache.poi.hslf.model.textproperties.TextPropCollection;
  32. import org.apache.poi.hslf.model.textproperties.TextPropCollection.TextPropType;
  33. import org.apache.poi.hslf.record.ColorSchemeAtom;
  34. import org.apache.poi.hslf.record.EscherTextboxWrapper;
  35. import org.apache.poi.hslf.record.FontCollection;
  36. import org.apache.poi.hslf.record.InteractiveInfo;
  37. import org.apache.poi.hslf.record.MasterTextPropAtom;
  38. import org.apache.poi.hslf.record.OEPlaceholderAtom;
  39. import org.apache.poi.hslf.record.OutlineTextRefAtom;
  40. import org.apache.poi.hslf.record.PPDrawing;
  41. import org.apache.poi.hslf.record.Record;
  42. import org.apache.poi.hslf.record.RecordContainer;
  43. import org.apache.poi.hslf.record.RecordTypes;
  44. import org.apache.poi.hslf.record.RoundTripHFPlaceholder12;
  45. import org.apache.poi.hslf.record.SlideListWithText;
  46. import org.apache.poi.hslf.record.SlidePersistAtom;
  47. import org.apache.poi.hslf.record.StyleTextProp9Atom;
  48. import org.apache.poi.hslf.record.StyleTextPropAtom;
  49. import org.apache.poi.hslf.record.TextBytesAtom;
  50. import org.apache.poi.hslf.record.TextCharsAtom;
  51. import org.apache.poi.hslf.record.TextHeaderAtom;
  52. import org.apache.poi.hslf.record.TextRulerAtom;
  53. import org.apache.poi.hslf.record.TextSpecInfoAtom;
  54. import org.apache.poi.hslf.record.TxInteractiveInfoAtom;
  55. import org.apache.poi.sl.draw.DrawPaint;
  56. import org.apache.poi.sl.usermodel.AutoNumberingScheme;
  57. import org.apache.poi.sl.usermodel.PaintStyle;
  58. import org.apache.poi.sl.usermodel.PaintStyle.SolidPaint;
  59. import org.apache.poi.sl.usermodel.TextParagraph;
  60. import org.apache.poi.util.Internal;
  61. import org.apache.poi.util.LocaleUtil;
  62. import org.apache.poi.util.POILogFactory;
  63. import org.apache.poi.util.POILogger;
  64. import org.apache.poi.util.StringUtil;
  65. import org.apache.poi.util.Units;
  66. /**
  67. * This class represents a run of text in a powerpoint document. That
  68. * run could be text on a sheet, or text in a note.
  69. * It is only a very basic class for now
  70. *
  71. * @author Nick Burch
  72. */
  73. public final class HSLFTextParagraph implements TextParagraph<HSLFShape,HSLFTextParagraph,HSLFTextRun> {
  74. protected static final POILogger logger = POILogFactory.getLogger(HSLFTextParagraph.class);
  75. /**
  76. * How to align the text
  77. */
  78. /* package */static final int AlignLeft = 0;
  79. /* package */static final int AlignCenter = 1;
  80. /* package */static final int AlignRight = 2;
  81. /* package */static final int AlignJustify = 3;
  82. // Note: These fields are protected to help with unit testing
  83. // Other classes shouldn't really go playing with them!
  84. private final TextHeaderAtom _headerAtom;
  85. private TextBytesAtom _byteAtom;
  86. private TextCharsAtom _charAtom;
  87. private TextPropCollection _paragraphStyle = new TextPropCollection(1, TextPropType.paragraph);
  88. protected TextRulerAtom _ruler;
  89. protected final List<HSLFTextRun> _runs = new ArrayList<HSLFTextRun>();
  90. protected HSLFTextShape _parentShape;
  91. private HSLFSheet _sheet;
  92. private int shapeId;
  93. private StyleTextProp9Atom styleTextProp9Atom;
  94. private boolean _dirty = false;
  95. private final List<HSLFTextParagraph> parentList;
  96. /**
  97. * Constructs a Text Run from a Unicode text block.
  98. * Either a {@link TextCharsAtom} or a {@link TextBytesAtom} needs to be provided.
  99. *
  100. * @param tha the TextHeaderAtom that defines what's what
  101. * @param tba the TextBytesAtom containing the text or null if {@link TextCharsAtom} is provided
  102. * @param tca the TextCharsAtom containing the text or null if {@link TextBytesAtom} is provided
  103. * @param parentList the list which contains this paragraph
  104. */
  105. /* package */ HSLFTextParagraph(
  106. TextHeaderAtom tha,
  107. TextBytesAtom tba,
  108. TextCharsAtom tca,
  109. List<HSLFTextParagraph> parentList
  110. ) {
  111. if (tha == null) {
  112. throw new IllegalArgumentException("TextHeaderAtom must be set.");
  113. }
  114. _headerAtom = tha;
  115. _byteAtom = tba;
  116. _charAtom = tca;
  117. this.parentList = parentList;
  118. }
  119. /* package */HSLFTextParagraph(HSLFTextParagraph other) {
  120. _headerAtom = other._headerAtom;
  121. _byteAtom = other._byteAtom;
  122. _charAtom = other._charAtom;
  123. _parentShape = other._parentShape;
  124. _sheet = other._sheet;
  125. _ruler = other._ruler;
  126. shapeId = other.shapeId;
  127. _paragraphStyle.copy(other._paragraphStyle);
  128. parentList = other.parentList;
  129. }
  130. public void addTextRun(HSLFTextRun run) {
  131. _runs.add(run);
  132. }
  133. @Override
  134. public List<HSLFTextRun> getTextRuns() {
  135. return _runs;
  136. }
  137. public TextPropCollection getParagraphStyle() {
  138. return _paragraphStyle;
  139. }
  140. public void setParagraphStyle(TextPropCollection paragraphStyle) {
  141. _paragraphStyle.copy(paragraphStyle);
  142. }
  143. /**
  144. * Setting a master style reference
  145. *
  146. * @param paragraphStyle the master style reference
  147. *
  148. * @since POI 3.14-Beta1
  149. */
  150. @Internal
  151. /* package */ void setMasterStyleReference(TextPropCollection paragraphStyle) {
  152. _paragraphStyle = paragraphStyle;
  153. }
  154. /**
  155. * Supply the Sheet we belong to, which might have an assigned SlideShow
  156. * Also passes it on to our child RichTextRuns
  157. */
  158. public static void supplySheet(List<HSLFTextParagraph> paragraphs, HSLFSheet sheet) {
  159. if (paragraphs == null) {
  160. return;
  161. }
  162. for (HSLFTextParagraph p : paragraphs) {
  163. p.supplySheet(sheet);
  164. }
  165. assert(sheet.getSlideShow() != null);
  166. }
  167. /**
  168. * Supply the Sheet we belong to, which might have an assigned SlideShow
  169. * Also passes it on to our child RichTextRuns
  170. */
  171. private void supplySheet(HSLFSheet sheet) {
  172. this._sheet = sheet;
  173. if (_runs == null) return;
  174. for (HSLFTextRun rt : _runs) {
  175. rt.updateSheet();
  176. }
  177. }
  178. public HSLFSheet getSheet() {
  179. return this._sheet;
  180. }
  181. /**
  182. * @return Shape ID
  183. */
  184. protected int getShapeId() {
  185. return shapeId;
  186. }
  187. /**
  188. * @param id Shape ID
  189. */
  190. protected void setShapeId(int id) {
  191. shapeId = id;
  192. }
  193. /**
  194. * @return 0-based index of the text run in the SLWT container
  195. */
  196. protected int getIndex() {
  197. return (_headerAtom != null) ? _headerAtom.getIndex() : -1;
  198. }
  199. /**
  200. * Sets the index of the paragraph in the SLWT container
  201. *
  202. * @param index
  203. */
  204. protected void setIndex(int index) {
  205. if (_headerAtom != null) _headerAtom.setIndex(index);
  206. }
  207. /**
  208. * Returns the type of the text, from the TextHeaderAtom.
  209. * Possible values can be seen from TextHeaderAtom
  210. * @see org.apache.poi.hslf.record.TextHeaderAtom
  211. */
  212. public int getRunType() {
  213. return (_headerAtom != null) ? _headerAtom.getTextType() : -1;
  214. }
  215. public void setRunType(int runType) {
  216. if (_headerAtom != null) _headerAtom.setTextType(runType);
  217. }
  218. /**
  219. * Is this Text Run one from a {@link PPDrawing}, or is it
  220. * one from the {@link SlideListWithText}?
  221. */
  222. public boolean isDrawingBased() {
  223. return (getIndex() == -1);
  224. }
  225. public TextRulerAtom getTextRuler() {
  226. return _ruler;
  227. }
  228. public TextRulerAtom createTextRuler() {
  229. _ruler = getTextRuler();
  230. if (_ruler == null) {
  231. _ruler = TextRulerAtom.getParagraphInstance();
  232. Record childAfter = _byteAtom;
  233. if (childAfter == null) childAfter = _charAtom;
  234. if (childAfter == null) childAfter = _headerAtom;
  235. _headerAtom.getParentRecord().addChildAfter(_ruler, childAfter);
  236. }
  237. return _ruler;
  238. }
  239. /**
  240. * Returns records that make up the list of text paragraphs
  241. * (there can be misc InteractiveInfo, TxInteractiveInfo and other records)
  242. *
  243. * @return text run records
  244. */
  245. public Record[] getRecords() {
  246. Record r[] = _headerAtom.getParentRecord().getChildRecords();
  247. return getRecords(r, new int[] { 0 }, _headerAtom);
  248. }
  249. private static Record[] getRecords(Record[] records, int[] startIdx, TextHeaderAtom headerAtom) {
  250. if (records == null) {
  251. throw new NullPointerException("records need to be set.");
  252. }
  253. for (; startIdx[0] < records.length; startIdx[0]++) {
  254. Record r = records[startIdx[0]];
  255. if (r instanceof TextHeaderAtom && (headerAtom == null || r == headerAtom)) break;
  256. }
  257. if (startIdx[0] >= records.length) {
  258. logger.log(POILogger.INFO, "header atom wasn't found - container might contain only an OutlineTextRefAtom");
  259. return new Record[0];
  260. }
  261. int length;
  262. for (length = 1; startIdx[0] + length < records.length; length++) {
  263. Record r = records[startIdx[0]+length];
  264. if (r instanceof TextHeaderAtom || r instanceof SlidePersistAtom) break;
  265. }
  266. Record result[] = new Record[length];
  267. System.arraycopy(records, startIdx[0], result, 0, length);
  268. startIdx[0] += length;
  269. return result;
  270. }
  271. /** Numbered List info */
  272. public void setStyleTextProp9Atom(final StyleTextProp9Atom styleTextProp9Atom) {
  273. this.styleTextProp9Atom = styleTextProp9Atom;
  274. }
  275. /** Numbered List info */
  276. public StyleTextProp9Atom getStyleTextProp9Atom() {
  277. return this.styleTextProp9Atom;
  278. }
  279. @Override
  280. public Iterator<HSLFTextRun> iterator() {
  281. return _runs.iterator();
  282. }
  283. @Override
  284. public Double getLeftMargin() {
  285. TextProp val = getPropVal(_paragraphStyle, "text.offset", this);
  286. return (val == null) ? null : Units.masterToPoints(val.getValue());
  287. }
  288. @Override
  289. public void setLeftMargin(Double leftMargin) {
  290. Integer val = (leftMargin == null) ? null : Units.pointsToMaster(leftMargin);
  291. setParagraphTextPropVal("text.offset", val);
  292. }
  293. @Override
  294. public Double getRightMargin() {
  295. // TODO: find out, how to determine this value
  296. return null;
  297. }
  298. @Override
  299. public void setRightMargin(Double rightMargin) {
  300. // TODO: find out, how to set this value
  301. }
  302. @Override
  303. public Double getIndent() {
  304. TextProp val = getPropVal(_paragraphStyle, "bullet.offset", this);
  305. return (val == null) ? null : Units.masterToPoints(val.getValue());
  306. }
  307. @Override
  308. public void setIndent(Double indent) {
  309. Integer val = (indent == null) ? null : Units.pointsToMaster(indent);
  310. setParagraphTextPropVal("bullet.offset", val);
  311. }
  312. @Override
  313. public String getDefaultFontFamily() {
  314. String typeface = null;
  315. if (!_runs.isEmpty()) {
  316. typeface = _runs.get(0).getFontFamily();
  317. }
  318. return (typeface != null) ? typeface : "Arial";
  319. }
  320. @Override
  321. public Double getDefaultFontSize() {
  322. Double d = null;
  323. if (!_runs.isEmpty()) {
  324. d = _runs.get(0).getFontSize();
  325. }
  326. return (d != null) ? d : 12d;
  327. }
  328. @Override
  329. public void setTextAlign(TextAlign align) {
  330. Integer alignInt = null;
  331. if (align != null) switch (align) {
  332. default:
  333. case LEFT: alignInt = TextAlignmentProp.LEFT;break;
  334. case CENTER: alignInt = TextAlignmentProp.CENTER; break;
  335. case RIGHT: alignInt = TextAlignmentProp.RIGHT; break;
  336. case DIST: alignInt = TextAlignmentProp.DISTRIBUTED; break;
  337. case JUSTIFY: alignInt = TextAlignmentProp.JUSTIFY; break;
  338. case JUSTIFY_LOW: alignInt = TextAlignmentProp.JUSTIFYLOW; break;
  339. case THAI_DIST: alignInt = TextAlignmentProp.THAIDISTRIBUTED; break;
  340. }
  341. setParagraphTextPropVal("alignment", alignInt);
  342. }
  343. @Override
  344. public TextAlign getTextAlign() {
  345. TextProp tp = getPropVal(_paragraphStyle, "alignment", this);
  346. if (tp == null) return null;
  347. switch (tp.getValue()) {
  348. default:
  349. case TextAlignmentProp.LEFT: return TextAlign.LEFT;
  350. case TextAlignmentProp.CENTER: return TextAlign.CENTER;
  351. case TextAlignmentProp.RIGHT: return TextAlign.RIGHT;
  352. case TextAlignmentProp.JUSTIFY: return TextAlign.JUSTIFY;
  353. case TextAlignmentProp.JUSTIFYLOW: return TextAlign.JUSTIFY_LOW;
  354. case TextAlignmentProp.DISTRIBUTED: return TextAlign.DIST;
  355. case TextAlignmentProp.THAIDISTRIBUTED: return TextAlign.THAI_DIST;
  356. }
  357. }
  358. @Override
  359. public FontAlign getFontAlign() {
  360. TextProp tp = getPropVal(_paragraphStyle, FontAlignmentProp.NAME, this);
  361. if (tp == null) return null;
  362. switch (tp.getValue()) {
  363. case FontAlignmentProp.BASELINE: return FontAlign.BASELINE;
  364. case FontAlignmentProp.TOP: return FontAlign.TOP;
  365. case FontAlignmentProp.CENTER: return FontAlign.CENTER;
  366. case FontAlignmentProp.BOTTOM: return FontAlign.BOTTOM;
  367. default: return FontAlign.AUTO;
  368. }
  369. }
  370. public AutoNumberingScheme getAutoNumberingScheme() {
  371. if (styleTextProp9Atom == null) return null;
  372. TextPFException9[] ant = styleTextProp9Atom.getAutoNumberTypes();
  373. int level = getIndentLevel();
  374. if (ant == null || level == -1 || level >= ant.length) return null;
  375. return ant[level].getAutoNumberScheme();
  376. }
  377. public Integer getAutoNumberingStartAt() {
  378. if (styleTextProp9Atom == null) return null;
  379. TextPFException9[] ant = styleTextProp9Atom.getAutoNumberTypes();
  380. int level = getIndentLevel();
  381. if (ant == null || level >= ant.length) return null;
  382. Short startAt = ant[level].getAutoNumberStartNumber();
  383. assert(startAt != null);
  384. return startAt.intValue();
  385. }
  386. @Override
  387. public BulletStyle getBulletStyle() {
  388. if (!isBullet() && getAutoNumberingScheme() == null) return null;
  389. return new BulletStyle() {
  390. @Override
  391. public String getBulletCharacter() {
  392. Character chr = HSLFTextParagraph.this.getBulletChar();
  393. return (chr == null || chr == 0) ? "" : "" + chr;
  394. }
  395. @Override
  396. public String getBulletFont() {
  397. return HSLFTextParagraph.this.getBulletFont();
  398. }
  399. @Override
  400. public Double getBulletFontSize() {
  401. return HSLFTextParagraph.this.getBulletSize();
  402. }
  403. @Override
  404. public void setBulletFontColor(Color color) {
  405. setBulletFontColor(DrawPaint.createSolidPaint(color));
  406. }
  407. @Override
  408. public void setBulletFontColor(PaintStyle color) {
  409. if (!(color instanceof SolidPaint)) {
  410. throw new IllegalArgumentException("HSLF only supports SolidPaint");
  411. }
  412. SolidPaint sp = (SolidPaint)color;
  413. Color col = DrawPaint.applyColorTransform(sp.getSolidColor());
  414. HSLFTextParagraph.this.setBulletColor(col);
  415. }
  416. @Override
  417. public PaintStyle getBulletFontColor() {
  418. Color col = HSLFTextParagraph.this.getBulletColor();
  419. return DrawPaint.createSolidPaint(col);
  420. }
  421. @Override
  422. public AutoNumberingScheme getAutoNumberingScheme() {
  423. return HSLFTextParagraph.this.getAutoNumberingScheme();
  424. }
  425. @Override
  426. public Integer getAutoNumberingStartAt() {
  427. return HSLFTextParagraph.this.getAutoNumberingStartAt();
  428. }
  429. };
  430. }
  431. @Override
  432. public void setBulletStyle(Object... styles) {
  433. if (styles.length == 0) {
  434. setBullet(false);
  435. } else {
  436. setBullet(true);
  437. for (Object ostyle : styles) {
  438. if (ostyle instanceof Number) {
  439. setBulletSize(((Number)ostyle).doubleValue());
  440. } else if (ostyle instanceof Color) {
  441. setBulletColor((Color)ostyle);
  442. } else if (ostyle instanceof Character) {
  443. setBulletChar((Character)ostyle);
  444. } else if (ostyle instanceof String) {
  445. setBulletFont((String)ostyle);
  446. } else if (ostyle instanceof AutoNumberingScheme) {
  447. throw new HSLFException("setting bullet auto-numberin scheme for HSLF not supported ... yet");
  448. }
  449. }
  450. }
  451. }
  452. @Override
  453. public HSLFTextShape getParentShape() {
  454. return _parentShape;
  455. }
  456. public void setParentShape(HSLFTextShape parentShape) {
  457. _parentShape = parentShape;
  458. }
  459. @Override
  460. public int getIndentLevel() {
  461. return _paragraphStyle == null ? 0 : _paragraphStyle.getIndentLevel();
  462. }
  463. @Override
  464. public void setIndentLevel(int level) {
  465. if( _paragraphStyle != null ) _paragraphStyle.setIndentLevel((short)level);
  466. }
  467. /**
  468. * Sets whether this rich text run has bullets
  469. */
  470. public void setBullet(boolean flag) {
  471. setFlag(ParagraphFlagsTextProp.BULLET_IDX, flag);
  472. }
  473. /**
  474. * Returns whether this rich text run has bullets
  475. */
  476. public boolean isBullet() {
  477. return getFlag(ParagraphFlagsTextProp.BULLET_IDX);
  478. }
  479. /**
  480. * Sets the bullet character
  481. */
  482. public void setBulletChar(Character c) {
  483. Integer val = (c == null) ? null : (int)c.charValue();
  484. setParagraphTextPropVal("bullet.char", val);
  485. }
  486. /**
  487. * Returns the bullet character
  488. */
  489. public Character getBulletChar() {
  490. TextProp tp = getPropVal(_paragraphStyle, "bullet.char", this);
  491. return (tp == null) ? null : (char)tp.getValue();
  492. }
  493. /**
  494. * Sets the bullet size
  495. */
  496. public void setBulletSize(Double size) {
  497. setPctOrPoints("bullet.size", size);
  498. }
  499. /**
  500. * Returns the bullet size, null if unset
  501. */
  502. public Double getBulletSize() {
  503. return getPctOrPoints("bullet.size");
  504. }
  505. /**
  506. * Sets the bullet color
  507. */
  508. public void setBulletColor(Color color) {
  509. Integer val = (color == null) ? null : new Color(color.getBlue(), color.getGreen(), color.getRed(), 254).getRGB();
  510. setParagraphTextPropVal("bullet.color", val);
  511. setFlag(ParagraphFlagsTextProp.BULLET_HARDCOLOR_IDX, (color != null));
  512. }
  513. /**
  514. * Returns the bullet color
  515. */
  516. public Color getBulletColor() {
  517. TextProp tp = getPropVal(_paragraphStyle, "bullet.color", this);
  518. boolean hasColor = getFlag(ParagraphFlagsTextProp.BULLET_HARDCOLOR_IDX);
  519. if (tp == null || !hasColor) {
  520. // if bullet color is undefined, return color of first run
  521. if (_runs.isEmpty()) {
  522. return null;
  523. }
  524. SolidPaint sp = _runs.get(0).getFontColor();
  525. if(sp == null) {
  526. return null;
  527. }
  528. return DrawPaint.applyColorTransform(sp.getSolidColor());
  529. }
  530. return getColorFromColorIndexStruct(tp.getValue(), _sheet);
  531. }
  532. /**
  533. * Sets the bullet font
  534. */
  535. public void setBulletFont(String typeface) {
  536. if (typeface == null) {
  537. setPropVal(_paragraphStyle, "bullet.font", null);
  538. setFlag(ParagraphFlagsTextProp.BULLET_HARDFONT_IDX, false);
  539. }
  540. FontCollection fc = getSheet().getSlideShow().getFontCollection();
  541. int idx = fc.addFont(typeface);
  542. setParagraphTextPropVal("bullet.font", idx);
  543. setFlag(ParagraphFlagsTextProp.BULLET_HARDFONT_IDX, true);
  544. }
  545. /**
  546. * Returns the bullet font
  547. */
  548. public String getBulletFont() {
  549. TextProp tp = getPropVal(_paragraphStyle, "bullet.font", this);
  550. boolean hasFont = getFlag(ParagraphFlagsTextProp.BULLET_HARDFONT_IDX);
  551. if (tp == null || !hasFont) return getDefaultFontFamily();
  552. PPFont ppFont = getSheet().getSlideShow().getFont(tp.getValue());
  553. assert(ppFont != null);
  554. return ppFont.getFontName();
  555. }
  556. @Override
  557. public void setLineSpacing(Double lineSpacing) {
  558. setPctOrPoints("linespacing", lineSpacing);
  559. }
  560. @Override
  561. public Double getLineSpacing() {
  562. return getPctOrPoints("linespacing");
  563. }
  564. @Override
  565. public void setSpaceBefore(Double spaceBefore) {
  566. setPctOrPoints("spacebefore", spaceBefore);
  567. }
  568. @Override
  569. public Double getSpaceBefore() {
  570. return getPctOrPoints("spacebefore");
  571. }
  572. @Override
  573. public void setSpaceAfter(Double spaceAfter) {
  574. setPctOrPoints("spaceafter", spaceAfter);
  575. }
  576. @Override
  577. public Double getSpaceAfter() {
  578. return getPctOrPoints("spaceafter");
  579. }
  580. @Override
  581. public Double getDefaultTabSize() {
  582. // TODO: implement
  583. return null;
  584. }
  585. private Double getPctOrPoints(String propName) {
  586. TextProp tp = getPropVal(_paragraphStyle, propName, this);
  587. if (tp == null) return null;
  588. int val = tp.getValue();
  589. return (val < 0) ? Units.masterToPoints(val) : val;
  590. }
  591. private void setPctOrPoints(String propName, Double dval) {
  592. Integer ival = null;
  593. if (dval != null) {
  594. ival = (dval < 0) ? Units.pointsToMaster(dval) : dval.intValue();
  595. }
  596. setParagraphTextPropVal(propName, ival);
  597. }
  598. private boolean getFlag(int index) {
  599. BitMaskTextProp tp = (BitMaskTextProp)getPropVal(_paragraphStyle, ParagraphFlagsTextProp.NAME, this);
  600. return (tp == null) ? false : tp.getSubValue(index);
  601. }
  602. private void setFlag(int index, boolean value) {
  603. BitMaskTextProp tp = (BitMaskTextProp)_paragraphStyle.addWithName(ParagraphFlagsTextProp.NAME);
  604. tp.setSubValue(value, index);
  605. setDirty();
  606. }
  607. /**
  608. * Fetch the value of the given Paragraph related TextProp. Returns null if
  609. * that TextProp isn't present. If the TextProp isn't present, the value
  610. * from the appropriate Master Sheet will apply.
  611. *
  612. * The propName can be a comma-separated list, in case multiple equivalent values
  613. * are queried
  614. */
  615. protected static TextProp getPropVal(TextPropCollection props, String propName, HSLFTextParagraph paragraph) {
  616. String propNames[] = propName.split(",");
  617. for (String pn : propNames) {
  618. TextProp prop = props.findByName(pn);
  619. if (prop == null) {
  620. continue;
  621. }
  622. // Font properties (maybe other too???) can have an index of -1
  623. // so we check the master for this font index then
  624. if (pn.contains("font") && prop.getValue() == -1) {
  625. return getMasterPropVal(props, pn, paragraph);
  626. }
  627. return prop;
  628. }
  629. return getMasterPropVal(props, propName, paragraph);
  630. }
  631. private static TextProp getMasterPropVal(TextPropCollection props, String propName, HSLFTextParagraph paragraph) {
  632. String propNames[] = propName.split(",");
  633. BitMaskTextProp maskProp = (BitMaskTextProp) props.findByName(ParagraphFlagsTextProp.NAME);
  634. boolean hardAttribute = (maskProp != null && maskProp.getValue() == 0);
  635. if (hardAttribute) return null;
  636. HSLFSheet sheet = paragraph.getSheet();
  637. int txtype = paragraph.getRunType();
  638. HSLFMasterSheet master = sheet.getMasterSheet();
  639. if (master == null) {
  640. logger.log(POILogger.WARN, "MasterSheet is not available");
  641. return null;
  642. }
  643. boolean isChar = props.getTextPropType() == TextPropType.character;
  644. for (String pn : propNames) {
  645. TextProp prop = master.getStyleAttribute(txtype, paragraph.getIndentLevel(), pn, isChar);
  646. if (prop != null) return prop;
  647. }
  648. return null;
  649. }
  650. /**
  651. * Returns the named TextProp, either by fetching it (if it exists) or
  652. * adding it (if it didn't)
  653. *
  654. * @param props the TextPropCollection to fetch from / add into
  655. * @param name the name of the TextProp to fetch/add
  656. * @param val the value, null if unset
  657. */
  658. protected static void setPropVal(TextPropCollection props, String name, Integer val) {
  659. if (val == null) {
  660. props.removeByName(name);
  661. return;
  662. }
  663. // Fetch / Add the TextProp
  664. TextProp tp = props.addWithName(name);
  665. tp.setValue(val);
  666. }
  667. /**
  668. * Check and add linebreaks to text runs leading other paragraphs
  669. *
  670. * @param paragraphs
  671. */
  672. protected static void fixLineEndings(List<HSLFTextParagraph> paragraphs) {
  673. HSLFTextRun lastRun = null;
  674. for (HSLFTextParagraph p : paragraphs) {
  675. if (lastRun != null && !lastRun.getRawText().endsWith("\r")) {
  676. lastRun.setText(lastRun.getRawText() + "\r");
  677. }
  678. List<HSLFTextRun> ltr = p.getTextRuns();
  679. if (ltr.isEmpty()) {
  680. throw new RuntimeException("paragraph without textruns found");
  681. }
  682. lastRun = ltr.get(ltr.size() - 1);
  683. assert (lastRun.getRawText() != null);
  684. }
  685. }
  686. /**
  687. * Search for a StyleTextPropAtom is for this text header (list of paragraphs)
  688. *
  689. * @param header the header
  690. * @param textLen the length of the rawtext, or -1 if the length is not known
  691. */
  692. private static StyleTextPropAtom findStyleAtomPresent(TextHeaderAtom header, int textLen) {
  693. boolean afterHeader = false;
  694. StyleTextPropAtom style = null;
  695. for (Record record : header.getParentRecord().getChildRecords()) {
  696. long rt = record.getRecordType();
  697. if (afterHeader && rt == RecordTypes.TextHeaderAtom.typeID) {
  698. // already on the next header, quit searching
  699. break;
  700. }
  701. afterHeader |= (header == record);
  702. if (afterHeader && rt == RecordTypes.StyleTextPropAtom.typeID) {
  703. // found it
  704. style = (StyleTextPropAtom) record;
  705. }
  706. }
  707. if (style == null) {
  708. logger.log(POILogger.INFO, "styles atom doesn't exist. Creating dummy record for later saving.");
  709. style = new StyleTextPropAtom((textLen < 0) ? 1 : textLen);
  710. } else {
  711. if (textLen >= 0) {
  712. style.setParentTextSize(textLen);
  713. }
  714. }
  715. return style;
  716. }
  717. /**
  718. * Saves the modified paragraphs/textrun to the records.
  719. * Also updates the styles to the correct text length.
  720. */
  721. protected static void storeText(List<HSLFTextParagraph> paragraphs) {
  722. fixLineEndings(paragraphs);
  723. updateTextAtom(paragraphs);
  724. updateStyles(paragraphs);
  725. updateHyperlinks(paragraphs);
  726. refreshRecords(paragraphs);
  727. for (HSLFTextParagraph p : paragraphs) {
  728. p._dirty = false;
  729. }
  730. }
  731. /**
  732. * Set the correct text atom depending on the multibyte usage
  733. */
  734. private static void updateTextAtom(List<HSLFTextParagraph> paragraphs) {
  735. final String rawText = toInternalString(getRawText(paragraphs));
  736. // Will it fit in a 8 bit atom?
  737. boolean isUnicode = StringUtil.hasMultibyte(rawText);
  738. // isUnicode = true;
  739. TextHeaderAtom headerAtom = paragraphs.get(0)._headerAtom;
  740. TextBytesAtom byteAtom = paragraphs.get(0)._byteAtom;
  741. TextCharsAtom charAtom = paragraphs.get(0)._charAtom;
  742. StyleTextPropAtom styleAtom = findStyleAtomPresent(headerAtom, rawText.length());
  743. // Store in the appropriate record
  744. Record oldRecord = null, newRecord = null;
  745. if (isUnicode) {
  746. if (byteAtom != null || charAtom == null) {
  747. oldRecord = byteAtom;
  748. charAtom = new TextCharsAtom();
  749. }
  750. newRecord = charAtom;
  751. charAtom.setText(rawText);
  752. } else {
  753. if (charAtom != null || byteAtom == null) {
  754. oldRecord = charAtom;
  755. byteAtom = new TextBytesAtom();
  756. }
  757. newRecord = byteAtom;
  758. byte[] byteText = new byte[rawText.length()];
  759. StringUtil.putCompressedUnicode(rawText, byteText, 0);
  760. byteAtom.setText(byteText);
  761. }
  762. assert (newRecord != null);
  763. RecordContainer _txtbox = headerAtom.getParentRecord();
  764. Record[] cr = _txtbox.getChildRecords();
  765. int /* headerIdx = -1, */ textIdx = -1, styleIdx = -1;
  766. for (int i = 0; i < cr.length; i++) {
  767. Record r = cr[i];
  768. if (r == headerAtom) ; // headerIdx = i;
  769. else if (r == oldRecord || r == newRecord) textIdx = i;
  770. else if (r == styleAtom) styleIdx = i;
  771. }
  772. if (textIdx == -1) {
  773. // the old record was never registered, ignore it
  774. _txtbox.addChildAfter(newRecord, headerAtom);
  775. // textIdx = headerIdx + 1;
  776. } else {
  777. // swap not appropriated records - noop if unchanged
  778. cr[textIdx] = newRecord;
  779. }
  780. if (styleIdx == -1) {
  781. // Add the new StyleTextPropAtom after the TextCharsAtom / TextBytesAtom
  782. _txtbox.addChildAfter(styleAtom, newRecord);
  783. }
  784. for (HSLFTextParagraph p : paragraphs) {
  785. if (newRecord == byteAtom) {
  786. p._byteAtom = byteAtom;
  787. p._charAtom = null;
  788. } else {
  789. p._byteAtom = null;
  790. p._charAtom = charAtom;
  791. }
  792. }
  793. }
  794. /**
  795. * Update paragraph and character styles - merges them when subsequential styles match
  796. */
  797. private static void updateStyles(List<HSLFTextParagraph> paragraphs) {
  798. final String rawText = toInternalString(getRawText(paragraphs));
  799. TextHeaderAtom headerAtom = paragraphs.get(0)._headerAtom;
  800. StyleTextPropAtom styleAtom = findStyleAtomPresent(headerAtom, rawText.length());
  801. // Update the text length for its Paragraph and Character stylings
  802. // * reset the length, to the new string's length
  803. // * add on +1 if the last block
  804. styleAtom.clearStyles();
  805. TextPropCollection lastPTPC = null, lastRTPC = null, ptpc = null, rtpc = null;
  806. for (HSLFTextParagraph para : paragraphs) {
  807. ptpc = para.getParagraphStyle();
  808. ptpc.updateTextSize(0);
  809. if (!ptpc.equals(lastPTPC)) {
  810. lastPTPC = styleAtom.addParagraphTextPropCollection(0);
  811. lastPTPC.copy(ptpc);
  812. }
  813. for (HSLFTextRun tr : para.getTextRuns()) {
  814. rtpc = tr.getCharacterStyle();
  815. rtpc.updateTextSize(0);
  816. if (!rtpc.equals(lastRTPC)) {
  817. lastRTPC = styleAtom.addCharacterTextPropCollection(0);
  818. lastRTPC.copy(rtpc);
  819. }
  820. int len = tr.getLength();
  821. ptpc.updateTextSize(ptpc.getCharactersCovered() + len);
  822. rtpc.updateTextSize(len);
  823. lastPTPC.updateTextSize(lastPTPC.getCharactersCovered() + len);
  824. lastRTPC.updateTextSize(lastRTPC.getCharactersCovered() + len);
  825. }
  826. }
  827. assert (lastPTPC != null && lastRTPC != null && ptpc != null && rtpc != null);
  828. ptpc.updateTextSize(ptpc.getCharactersCovered() + 1);
  829. rtpc.updateTextSize(rtpc.getCharactersCovered() + 1);
  830. lastPTPC.updateTextSize(lastPTPC.getCharactersCovered() + 1);
  831. lastRTPC.updateTextSize(lastRTPC.getCharactersCovered() + 1);
  832. /**
  833. * If TextSpecInfoAtom is present, we must update the text size in it,
  834. * otherwise the ppt will be corrupted
  835. */
  836. for (Record r : paragraphs.get(0).getRecords()) {
  837. if (r instanceof TextSpecInfoAtom) {
  838. ((TextSpecInfoAtom) r).setParentSize(rawText.length() + 1);
  839. break;
  840. }
  841. }
  842. }
  843. private static void updateHyperlinks(List<HSLFTextParagraph> paragraphs) {
  844. TextHeaderAtom headerAtom = paragraphs.get(0)._headerAtom;
  845. RecordContainer _txtbox = headerAtom.getParentRecord();
  846. // remove existing hyperlink records
  847. for (Record r : _txtbox.getChildRecords()) {
  848. if (r instanceof InteractiveInfo || r instanceof TxInteractiveInfoAtom) {
  849. _txtbox.removeChild(r);
  850. }
  851. }
  852. // now go through all the textruns and check for hyperlinks
  853. HSLFHyperlink lastLink = null;
  854. for (HSLFTextParagraph para : paragraphs) {
  855. for (HSLFTextRun run : para) {
  856. HSLFHyperlink thisLink = run.getHyperlink();
  857. if (thisLink != null && thisLink == lastLink) {
  858. // the hyperlink extends over this text run, increase its length
  859. // TODO: the text run might be longer than the hyperlink
  860. thisLink.setEndIndex(thisLink.getEndIndex()+run.getLength());
  861. } else {
  862. if (lastLink != null) {
  863. InteractiveInfo info = lastLink.getInfo();
  864. TxInteractiveInfoAtom txinfo = lastLink.getTextRunInfo();
  865. assert(info != null && txinfo != null);
  866. _txtbox.appendChildRecord(info);
  867. _txtbox.appendChildRecord(txinfo);
  868. }
  869. }
  870. lastLink = thisLink;
  871. }
  872. }
  873. if (lastLink != null) {
  874. InteractiveInfo info = lastLink.getInfo();
  875. TxInteractiveInfoAtom txinfo = lastLink.getTextRunInfo();
  876. assert(info != null && txinfo != null);
  877. _txtbox.appendChildRecord(info);
  878. _txtbox.appendChildRecord(txinfo);
  879. }
  880. }
  881. /**
  882. * Writes the textbox records back to the document record
  883. */
  884. private static void refreshRecords(List<HSLFTextParagraph> paragraphs) {
  885. TextHeaderAtom headerAtom = paragraphs.get(0)._headerAtom;
  886. RecordContainer _txtbox = headerAtom.getParentRecord();
  887. if (_txtbox instanceof EscherTextboxWrapper) {
  888. try {
  889. ((EscherTextboxWrapper) _txtbox).writeOut(null);
  890. } catch (IOException e) {
  891. throw new RuntimeException("failed dummy write", e);
  892. }
  893. }
  894. }
  895. /**
  896. * Adds the supplied text onto the end of the TextParagraphs,
  897. * creating a new RichTextRun for it to sit in.
  898. *
  899. * @param text the text string used by this object.
  900. */
  901. protected static HSLFTextRun appendText(List<HSLFTextParagraph> paragraphs, String text, boolean newParagraph) {
  902. text = toInternalString(text);
  903. // check paragraphs
  904. assert(!paragraphs.isEmpty() && !paragraphs.get(0).getTextRuns().isEmpty());
  905. HSLFTextParagraph htp = paragraphs.get(paragraphs.size() - 1);
  906. HSLFTextRun htr = htp.getTextRuns().get(htp.getTextRuns().size() - 1);
  907. boolean addParagraph = newParagraph;
  908. for (String rawText : text.split("(?<=\r)")) {
  909. // special case, if last text paragraph or run is empty, we will reuse it
  910. boolean lastRunEmpty = (htr.getLength() == 0);
  911. boolean lastParaEmpty = lastRunEmpty && (htp.getTextRuns().size() == 1);
  912. if (addParagraph && !lastParaEmpty) {
  913. TextPropCollection tpc = htp.getParagraphStyle();
  914. HSLFTextParagraph prevHtp = htp;
  915. htp = new HSLFTextParagraph(htp._headerAtom, htp._byteAtom, htp._charAtom, paragraphs);
  916. htp.getParagraphStyle().copy(tpc);
  917. htp.setParentShape(prevHtp.getParentShape());
  918. htp.setShapeId(prevHtp.getShapeId());
  919. htp.supplySheet(prevHtp.getSheet());
  920. paragraphs.add(htp);
  921. }
  922. addParagraph = true;
  923. if (!lastRunEmpty) {
  924. TextPropCollection tpc = htr.getCharacterStyle();
  925. htr = new HSLFTextRun(htp);
  926. htr.getCharacterStyle().copy(tpc);
  927. htp.addTextRun(htr);
  928. }
  929. htr.setText(rawText);
  930. }
  931. storeText(paragraphs);
  932. return htr;
  933. }
  934. /**
  935. * Sets (overwrites) the current text.
  936. * Uses the properties of the first paragraph / textrun
  937. *
  938. * @param text the text string used by this object.
  939. */
  940. public static HSLFTextRun setText(List<HSLFTextParagraph> paragraphs, String text) {
  941. // check paragraphs
  942. assert(!paragraphs.isEmpty() && !paragraphs.get(0).getTextRuns().isEmpty());
  943. Iterator<HSLFTextParagraph> paraIter = paragraphs.iterator();
  944. HSLFTextParagraph htp = paraIter.next(); // keep first
  945. assert (htp != null);
  946. while (paraIter.hasNext()) {
  947. paraIter.next();
  948. paraIter.remove();
  949. }
  950. Iterator<HSLFTextRun> runIter = htp.getTextRuns().iterator();
  951. if (runIter.hasNext()) {
  952. HSLFTextRun htr = runIter.next();
  953. htr.setText("");
  954. while (runIter.hasNext()) {
  955. runIter.next();
  956. runIter.remove();
  957. }
  958. } else {
  959. HSLFTextRun trun = new HSLFTextRun(htp);
  960. htp.addTextRun(trun);
  961. }
  962. return appendText(paragraphs, text, false);
  963. }
  964. public static String getText(List<HSLFTextParagraph> paragraphs) {
  965. assert (!paragraphs.isEmpty());
  966. String rawText = getRawText(paragraphs);
  967. return toExternalString(rawText, paragraphs.get(0).getRunType());
  968. }
  969. public static String getRawText(List<HSLFTextParagraph> paragraphs) {
  970. StringBuilder sb = new StringBuilder();
  971. for (HSLFTextParagraph p : paragraphs) {
  972. for (HSLFTextRun r : p.getTextRuns()) {
  973. sb.append(r.getRawText());
  974. }
  975. }
  976. return sb.toString();
  977. }
  978. @Override
  979. public String toString() {
  980. StringBuilder sb = new StringBuilder();
  981. for (HSLFTextRun r : getTextRuns()) {
  982. sb.append(r.getRawText());
  983. }
  984. return toExternalString(sb.toString(), getRunType());
  985. }
  986. /**
  987. * Returns a new string with line breaks converted into internal ppt
  988. * representation
  989. */
  990. protected static String toInternalString(String s) {
  991. String ns = s.replaceAll("\\r?\\n", "\r");
  992. return ns;
  993. }
  994. /**
  995. * Converts raw text from the text paragraphs to a formatted string,
  996. * i.e. it converts certain control characters used in the raw txt
  997. *
  998. * @param rawText the raw text
  999. * @param runType the run type of the shape, paragraph or headerAtom.
  1000. * use -1 if unknown
  1001. * @return the formatted string
  1002. */
  1003. public static String toExternalString(String rawText, int runType) {
  1004. // PowerPoint seems to store files with \r as the line break
  1005. // The messes things up on everything but a Mac, so translate
  1006. // them to \n
  1007. String text = rawText.replace('\r', '\n');
  1008. switch (runType) {
  1009. // 0xB acts like cariage return in page titles and like blank in the
  1010. // others
  1011. case -1:
  1012. case org.apache.poi.hslf.record.TextHeaderAtom.TITLE_TYPE:
  1013. case org.apache.poi.hslf.record.TextHeaderAtom.CENTER_TITLE_TYPE:
  1014. text = text.replace((char) 0x0B, '\n');
  1015. break;
  1016. default:
  1017. text = text.replace((char) 0x0B, ' ');
  1018. break;
  1019. }
  1020. return text;
  1021. }
  1022. /**
  1023. * For a given PPDrawing, grab all the TextRuns
  1024. */
  1025. public static List<List<HSLFTextParagraph>> findTextParagraphs(PPDrawing ppdrawing, HSLFSheet sheet) {
  1026. List<List<HSLFTextParagraph>> runsV = new ArrayList<List<HSLFTextParagraph>>();
  1027. for (EscherTextboxWrapper wrapper : ppdrawing.getTextboxWrappers()) {
  1028. List<HSLFTextParagraph> p = findTextParagraphs(wrapper, sheet);
  1029. if (p != null) runsV.add(p);
  1030. }
  1031. return runsV;
  1032. }
  1033. /**
  1034. * Scans through the supplied record array, looking for
  1035. * a TextHeaderAtom followed by one of a TextBytesAtom or
  1036. * a TextCharsAtom. Builds up TextRuns from these
  1037. *
  1038. * @param wrapper an EscherTextboxWrapper
  1039. */
  1040. protected static List<HSLFTextParagraph> findTextParagraphs(EscherTextboxWrapper wrapper, HSLFSheet sheet) {
  1041. // propagate parents to parent-aware records
  1042. RecordContainer.handleParentAwareRecords(wrapper);
  1043. int shapeId = wrapper.getShapeId();
  1044. List<HSLFTextParagraph> rv = null;
  1045. OutlineTextRefAtom ota = (OutlineTextRefAtom)wrapper.findFirstOfType(OutlineTextRefAtom.typeID);
  1046. if (ota != null) {
  1047. // if we are based on an outline, there are no further records to be parsed from the wrapper
  1048. if (sheet == null) {
  1049. throw new RuntimeException("Outline atom reference can't be solved without a sheet record");
  1050. }
  1051. List<List<HSLFTextParagraph>> sheetRuns = sheet.getTextParagraphs();
  1052. assert (sheetRuns != null);
  1053. int idx = ota.getTextIndex();
  1054. for (List<HSLFTextParagraph> r : sheetRuns) {
  1055. if (r.isEmpty()) continue;
  1056. int ridx = r.get(0).getIndex();
  1057. if (ridx > idx) break;
  1058. if (ridx == idx) {
  1059. if (rv == null) {
  1060. rv = r;
  1061. } else {
  1062. // create a new container
  1063. // TODO: ... is this case really happening?
  1064. rv = new ArrayList<HSLFTextParagraph>(rv);
  1065. rv.addAll(r);
  1066. }
  1067. }
  1068. }
  1069. if (rv == null || rv.isEmpty()) {
  1070. logger.log(POILogger.WARN, "text run not found for OutlineTextRefAtom.TextIndex=" + idx);
  1071. }
  1072. } else {
  1073. if (sheet != null) {
  1074. // check sheet runs first, so we get exactly the same paragraph list
  1075. List<List<HSLFTextParagraph>> sheetRuns = sheet.getTextParagraphs();
  1076. assert (sheetRuns != null);
  1077. for (List<HSLFTextParagraph> paras : sheetRuns) {
  1078. if (!paras.isEmpty() && paras.get(0)._headerAtom.getParentRecord() == wrapper) {
  1079. rv = paras;
  1080. break;
  1081. }
  1082. }
  1083. }
  1084. if (rv == null) {
  1085. // if we haven't found the wrapper in the sheet runs, create a new paragraph list from its record
  1086. List<List<HSLFTextParagraph>> rvl = findTextParagraphs(wrapper.getChildRecords());
  1087. switch (rvl.size()) {
  1088. case 0: break; // nothing found
  1089. case 1: rv = rvl.get(0); break; // normal case
  1090. default:
  1091. throw new RuntimeException("TextBox contains more than one list of paragraphs.");
  1092. }
  1093. }
  1094. }
  1095. if (rv != null) {
  1096. StyleTextProp9Atom styleTextProp9Atom = wrapper.getStyleTextProp9Atom();
  1097. for (HSLFTextParagraph htp : rv) {
  1098. htp.setShapeId(shapeId);
  1099. htp.setStyleTextProp9Atom(styleTextProp9Atom);
  1100. }
  1101. }
  1102. return rv;
  1103. }
  1104. /**
  1105. * Scans through the supplied record array, looking for
  1106. * a TextHeaderAtom followed by one of a TextBytesAtom or
  1107. * a TextCharsAtom. Builds up TextRuns from these
  1108. *
  1109. * @param records the records to build from
  1110. */
  1111. protected static List<List<HSLFTextParagraph>> findTextParagraphs(Record[] records) {
  1112. List<List<HSLFTextParagraph>> paragraphCollection = new ArrayList<List<HSLFTextParagraph>>();
  1113. int[] recordIdx = { 0 };
  1114. for (int slwtIndex = 0; recordIdx[0] < records.length; slwtIndex++) {
  1115. TextHeaderAtom header = null;
  1116. TextBytesAtom tbytes = null;
  1117. TextCharsAtom tchars = null;
  1118. TextRulerAtom ruler = null;
  1119. MasterTextPropAtom indents = null;
  1120. for (Record r : getRecords(records, recordIdx, null)) {
  1121. long rt = r.getRecordType();
  1122. if (RecordTypes.TextHeaderAtom.typeID == rt) {
  1123. header = (TextHeaderAtom) r;
  1124. } else if (RecordTypes.TextBytesAtom.typeID == rt) {
  1125. tbytes = (TextBytesAtom) r;
  1126. } else if (RecordTypes.TextCharsAtom.typeID == rt) {
  1127. tchars = (TextCharsAtom) r;
  1128. } else if (RecordTypes.TextRulerAtom.typeID == rt) {
  1129. ruler = (TextRulerAtom) r;
  1130. } else if (RecordTypes.MasterTextPropAtom.typeID == rt) {
  1131. indents = (MasterTextPropAtom) r;
  1132. }
  1133. // don't search for RecordTypes.StyleTextPropAtom.typeID here ... see findStyleAtomPresent below
  1134. }
  1135. if (header == null) break;
  1136. if (header.getParentRecord() instanceof SlideListWithText) {
  1137. // runs found in PPDrawing are not linked with SlideListWithTexts
  1138. header.setIndex(slwtIndex);
  1139. }
  1140. if (tbytes == null && tchars == null) {
  1141. tbytes = new TextBytesAtom();
  1142. // don't add record yet - set it in storeText
  1143. logger.log(POILogger.INFO, "bytes nor chars atom doesn't exist. Creating dummy record for later saving.");
  1144. }
  1145. String rawText = (tchars != null) ? tchars.getText() : tbytes.getText();
  1146. StyleTextPropAtom styles = findStyleAtomPresent(header, rawText.length());
  1147. List<HSLFTextParagraph> paragraphs = new ArrayList<HSLFTextParagraph>();
  1148. paragraphCollection.add(paragraphs);
  1149. // split, but keep delimiter
  1150. for (String para : rawText.split("(?<=\r)")) {
  1151. HSLFTextParagraph tpara = new HSLFTextParagraph(header, tbytes, tchars, paragraphs);
  1152. paragraphs.add(tpara);
  1153. tpara._ruler = ruler;
  1154. tpara.getParagraphStyle().updateTextSize(para.length());
  1155. HSLFTextRun trun = new HSLFTextRun(tpara);
  1156. tpara.addTextRun(trun);
  1157. trun.setText(para);
  1158. }
  1159. applyCharacterStyles(paragraphs, styles.getCharacterStyles());
  1160. applyParagraphStyles(paragraphs, styles.getParagraphStyles());
  1161. if (indents != null) {
  1162. applyParagraphIndents(paragraphs, indents.getIndents());
  1163. }
  1164. }
  1165. if (paragraphCollection.isEmpty()) {
  1166. logger.log(POILogger.DEBUG, "No text records found.");
  1167. }
  1168. return paragraphCollection;
  1169. }
  1170. protected static void applyHyperlinks(List<HSLFTextParagraph> paragraphs) {
  1171. List<HSLFHyperlink> links = HSLFHyperlink.find(paragraphs);
  1172. for (HSLFHyperlink h : links) {
  1173. int csIdx = 0;
  1174. for (HSLFTextParagraph p : paragraphs) {
  1175. if (csIdx > h.getEndIndex()) break;
  1176. List<HSLFTextRun> runs = p.getTextRuns();
  1177. for (int rlen=0,rIdx=0; rIdx < runs.size(); csIdx+=rlen, rIdx++) {
  1178. HSLFTextRun run = runs.get(rIdx);
  1179. rlen = run.getLength();
  1180. if (csIdx < h.getEndIndex() && h.getStartIndex() < csIdx+rlen) {
  1181. String rawText = run.getRawText();
  1182. int startIdx = h.getStartIndex()-csIdx;
  1183. if (startIdx > 0) {
  1184. // hyperlink starts within current textrun
  1185. HSLFTextRun newRun = new HSLFTextRun(p);
  1186. newRun.setCharacterStyle(run.getCharacterStyle());
  1187. newRun.setText(rawText.substring(startIdx));
  1188. run.setText(rawText.substring(0, startIdx));
  1189. runs.add(rIdx+1, newRun);
  1190. rlen = startIdx;
  1191. continue;
  1192. }
  1193. int endIdx = Math.min(rlen, h.getEndIndex()-h.getStartIndex());
  1194. if (endIdx < rlen) {
  1195. // hyperlink ends before end of current textrun
  1196. HSLFTextRun newRun = new HSLFTextRun(p);
  1197. newRun.setCharacterStyle(run.getCharacterStyle());
  1198. newRun.setText(rawText.substring(0, endIdx));
  1199. run.setText(rawText.substring(endIdx));
  1200. runs.add(rIdx, newRun);
  1201. rlen = endIdx;
  1202. run = newRun;
  1203. }
  1204. run.setHyperlink(h);
  1205. }
  1206. }
  1207. }
  1208. }
  1209. }
  1210. protected static void applyCharacterStyles(List<HSLFTextParagraph> paragraphs, List<TextPropCollection> charStyles) {
  1211. int paraIdx = 0, runIdx = 0;
  1212. HSLFTextRun trun;
  1213. for (int csIdx = 0; csIdx < charStyles.size(); csIdx++) {
  1214. TextPropCollection p = charStyles.get(csIdx);
  1215. for (int ccRun = 0, ccStyle = p.getCharactersCovered(); ccRun < ccStyle;) {
  1216. HSLFTextParagraph para = paragraphs.get(paraIdx);
  1217. List<HSLFTextRun> runs = para.getTextRuns();
  1218. trun = runs.get(runIdx);
  1219. final int len = trun.getLength();
  1220. if (ccRun + len <= ccStyle) {
  1221. ccRun += len;
  1222. } else {
  1223. String text = trun.getRawText();
  1224. trun.setText(text.substring(0, ccStyle - ccRun));
  1225. HSLFTextRun nextRun = new HSLFTextRun(para);
  1226. nextRun.setText(text.substring(ccStyle - ccRun));
  1227. runs.add(runIdx + 1, nextRun);
  1228. ccRun += ccStyle - ccRun;
  1229. }
  1230. trun.setCharacterStyle(p);
  1231. if (paraIdx == paragraphs.size()-1 && runIdx == runs.size()-1) {
  1232. if (csIdx < charStyles.size() - 1) {
  1233. // special case, empty trailing text run
  1234. HSLFTextRun nextRun = new HSLFTextRun(para);
  1235. nextRun.setText("");
  1236. runs.add(nextRun);
  1237. ccRun++;
  1238. } else {
  1239. // need to add +1 to the last run of the last paragraph
  1240. trun.getCharacterStyle().updateTextSize(trun.getLength()+1);
  1241. ccRun++;
  1242. }
  1243. }
  1244. // need to compare it again, in case a run has been added after
  1245. if (++runIdx == runs.size()) {
  1246. paraIdx++;
  1247. runIdx = 0;
  1248. }
  1249. }
  1250. }
  1251. }
  1252. protected static void applyParagraphStyles(List<HSLFTextParagraph> paragraphs, List<TextPropCollection> paraStyles) {
  1253. int paraIdx = 0;
  1254. for (TextPropCollection p : paraStyles) {
  1255. for (int ccPara = 0, ccStyle = p.getCharactersCovered(); ccPara < ccStyle; paraIdx++) {
  1256. if (paraIdx >= paragraphs.size()) return;
  1257. HSLFTextParagraph htp = paragraphs.get(paraIdx);
  1258. TextPropCollection pCopy = new TextPropCollection(0, TextPropType.paragraph);
  1259. pCopy.copy(p);
  1260. htp.setParagraphStyle(pCopy);
  1261. int len = 0;
  1262. for (HSLFTextRun trun : htp.getTextRuns()) {
  1263. len += trun.getLength();
  1264. }
  1265. if (paraIdx == paragraphs.size()-1) len++;
  1266. pCopy.updateTextSize(len);
  1267. ccPara += len;
  1268. }
  1269. }
  1270. }
  1271. protected static void applyParagraphIndents(List<HSLFTextParagraph> paragraphs, List<IndentProp> paraStyles) {
  1272. int paraIdx = 0;
  1273. for (IndentProp p : paraStyles) {
  1274. for (int ccPara = 0, ccStyle = p.getCharactersCovered(); ccPara < ccStyle; paraIdx++) {
  1275. if (paraIdx >= paragraphs.size() || ccPara >= ccStyle-1) return;
  1276. HSLFTextParagraph para = paragraphs.get(paraIdx);
  1277. int len = 0;
  1278. for (HSLFTextRun trun : para.getTextRuns()) {
  1279. len += trun.getLength();
  1280. }
  1281. para.setIndentLevel(p.getIndentLevel());
  1282. ccPara += len + 1;
  1283. }
  1284. }
  1285. }
  1286. protected static List<HSLFTextParagraph> createEmptyParagraph() {
  1287. EscherTextboxWrapper wrapper = new EscherTextboxWrapper();
  1288. return createEmptyParagraph(wrapper);
  1289. }
  1290. protected static List<HSLFTextParagraph> createEmptyParagraph(EscherTextboxWrapper wrapper) {
  1291. TextHeaderAtom tha = new TextHeaderAtom();
  1292. tha.setParentRecord(wrapper);
  1293. wrapper.appendChildRecord(tha);
  1294. TextBytesAtom tba = new TextBytesAtom();
  1295. tba.setText("".getBytes(LocaleUtil.CHARSET_1252));
  1296. wrapper.appendChildRecord(tba);
  1297. StyleTextPropAtom sta = new StyleTextPropAtom(1);
  1298. TextPropCollection paraStyle = sta.addParagraphTextPropCollection(1);
  1299. TextPropCollection charStyle = sta.addCharacterTextPropCollection(1);
  1300. wrapper.appendChildRecord(sta);
  1301. List<HSLFTextParagraph> paragraphs = new ArrayList<HSLFTextParagraph>(1);
  1302. HSLFTextParagraph htp = new HSLFTextParagraph(tha, tba, null, paragraphs);
  1303. htp.setParagraphStyle(paraStyle);
  1304. paragraphs.add(htp);
  1305. HSLFTextRun htr = new HSLFTextRun(htp);
  1306. htr.setCharacterStyle(charStyle);
  1307. htr.setText("");
  1308. htp.addTextRun(htr);
  1309. return paragraphs;
  1310. }
  1311. public EscherTextboxWrapper getTextboxWrapper() {
  1312. return (EscherTextboxWrapper) _headerAtom.getParentRecord();
  1313. }
  1314. protected static Color getColorFromColorIndexStruct(int rgb, HSLFSheet sheet) {
  1315. int cidx = rgb >>> 24;
  1316. Color tmp;
  1317. switch (cidx) {
  1318. // Background ... Accent 3 color
  1319. case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
  1320. if (sheet == null) return null;
  1321. ColorSchemeAtom ca = sheet.getColorScheme();
  1322. tmp = new Color(ca.getColor(cidx), true);
  1323. break;
  1324. // Color is an sRGB value specified by red, green, and blue fields.
  1325. case 0xFE:
  1326. tmp = new Color(rgb, true);
  1327. break;
  1328. // Color is undefined.
  1329. default:
  1330. case 0xFF:
  1331. return null;
  1332. }
  1333. return new Color(tmp.getBlue(), tmp.getGreen(), tmp.getRed());
  1334. }
  1335. /**
  1336. * Sets the value of the given Paragraph TextProp, add if required
  1337. * @param propName The name of the Paragraph TextProp
  1338. * @param val The value to set for the TextProp
  1339. */
  1340. public void setParagraphTextPropVal(String propName, Integer val) {
  1341. setPropVal(_paragraphStyle, propName, val);
  1342. setDirty();
  1343. }
  1344. /**
  1345. * marks this paragraph dirty, so its records will be renewed on save
  1346. */
  1347. public void setDirty() {
  1348. _dirty = true;
  1349. }
  1350. public boolean isDirty() {
  1351. return _dirty;
  1352. }
  1353. /**
  1354. * Calculates the start index of the given text run
  1355. *
  1356. * @param textrun the text run to search for
  1357. * @return the start index with the paragraph collection or -1 if not found
  1358. */
  1359. /* package */ int getStartIdxOfTextRun(HSLFTextRun textrun) {
  1360. int idx = 0;
  1361. for (HSLFTextParagraph p : parentList) {
  1362. for (HSLFTextRun r : p) {
  1363. if (r == textrun) {
  1364. return idx;
  1365. }
  1366. idx += r.getLength();
  1367. }
  1368. }
  1369. return -1;
  1370. }
  1371. /**
  1372. * {@inheritDoc}
  1373. *
  1374. * @see RoundTripHFPlaceholder12
  1375. */
  1376. @Override
  1377. public boolean isHeaderOrFooter() {
  1378. HSLFShape s = getParentShape();
  1379. if (s == null) {
  1380. return false;
  1381. }
  1382. RoundTripHFPlaceholder12 hfPl = s.getClientDataRecord(RecordTypes.RoundTripHFPlaceholder12.typeID);
  1383. if (hfPl == null) {
  1384. return false;
  1385. }
  1386. int plId = hfPl.getPlaceholderId();
  1387. switch (plId) {
  1388. case OEPlaceholderAtom.MasterDate:
  1389. case OEPlaceholderAtom.MasterSlideNumber:
  1390. case OEPlaceholderAtom.MasterFooter:
  1391. case OEPlaceholderAtom.MasterHeader:
  1392. return true;
  1393. default:
  1394. return false;
  1395. }
  1396. }
  1397. }