1
2 /* ====================================================================
3 Licensed to the Apache Software Foundation (ASF) under one or more
4 contributor license agreements. See the NOTICE file distributed with
5 this work for additional information regarding copyright ownership.
6 The ASF licenses this file to You under the Apache License, Version 2.0
7 (the "License"); you may not use this file except in compliance with
8 the License. You may obtain a copy of the License at
9
10 http://www.apache.org/licenses/LICENSE-2.0
11
12 Unless required by applicable law or agreed to in writing, software
13 distributed under the License is distributed on an "AS IS" BASIS,
14 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 See the License for the specific language governing permissions and
16 limitations under the License.
17 ==================================================================== */
18
19
20
21 package org.apache.poi.hslf.usermodel;
22
23 import java.awt.Color;
24
25 import org.apache.poi.hslf.model.MasterSheet;
26 import org.apache.poi.hslf.model.Shape;
27 import org.apache.poi.hslf.model.Sheet;
28 import org.apache.poi.hslf.model.TextRun;
29 import org.apache.poi.hslf.model.textproperties.BitMaskTextProp;
30 import org.apache.poi.hslf.model.textproperties.CharFlagsTextProp;
31 import org.apache.poi.hslf.model.textproperties.ParagraphFlagsTextProp;
32 import org.apache.poi.hslf.model.textproperties.TextProp;
33 import org.apache.poi.hslf.model.textproperties.TextPropCollection;
34 import org.apache.poi.hslf.record.ColorSchemeAtom;
35 import org.apache.poi.util.POILogger;
36 import org.apache.poi.util.POILogFactory;
37
38
39 /**
40 * Represents a run of text, all with the same style
41 *
42 */
43 public class RichTextRun {
44 protected POILogger logger = POILogFactory.getLogger(this.getClass());
45
46 /** The TextRun we belong to */
47 private TextRun parentRun;
48 /** The SlideShow we belong to */
49 private SlideShow slideShow;
50
51 /** Where in the parent TextRun we start from */
52 private int startPos;
53
54 /** How long a string (in the parent TextRun) we represent */
55 private int length;
56
57 private String _fontname;
58 /**
59 * Our paragraph and character style.
60 * Note - we may share these styles with other RichTextRuns
61 */
62 private TextPropCollection paragraphStyle;
63 private TextPropCollection characterStyle;
64 private boolean sharingParagraphStyle;
65 private boolean sharingCharacterStyle;
66
67 /**
68 * Create a new wrapper around a (currently not)
69 * rich text string
70 * @param parent
71 * @param startAt
72 * @param len
73 */
74 public RichTextRun(TextRun parent, int startAt, int len) {
75 this(parent, startAt, len, null, null, false, false);
76 }
77 /**
78 * Create a new wrapper around a rich text string
79 * @param parent The parent TextRun
80 * @param startAt The start position of this run
81 * @param len The length of this run
82 * @param pStyle The paragraph style property collection
83 * @param cStyle The character style property collection
84 * @param pShared The paragraph styles are shared with other runs
85 * @param cShared The character styles are shared with other runs
86 */
87 public RichTextRun(TextRun parent, int startAt, int len,
88 TextPropCollection pStyle, TextPropCollection cStyle,
89 boolean pShared, boolean cShared) {
90 parentRun = parent;
91 startPos = startAt;
92 length = len;
93 paragraphStyle = pStyle;
94 characterStyle = cStyle;
95 sharingParagraphStyle = pShared;
96 sharingCharacterStyle = cShared;
97 }
98
99 /**
100 * Supply (normally default) textprops, and if they're shared,
101 * when a run gets them
102 */
103 public void supplyTextProps(TextPropCollection pStyle, TextPropCollection cStyle, boolean pShared, boolean cShared) {
104 if(paragraphStyle != null || characterStyle != null) {
105 throw new IllegalStateException("Can't call supplyTextProps if run already has some");
106 }
107 paragraphStyle = pStyle;
108 characterStyle = cStyle;
109 sharingParagraphStyle = pShared;
110 sharingCharacterStyle = cShared;
111 }
112 /**
113 * Supply the SlideShow we belong to
114 */
115 public void supplySlideShow(SlideShow ss) {
116 slideShow = ss;
117 if (_fontname != null) {
118 setFontName(_fontname);
119 _fontname = null;
120 }
121 }
122
123 /**
124 * Get the length of the text
125 */
126 public int getLength() {
127 return length;
128 }
129
130 /**
131 * The beginning index, inclusive.
132 *
133 * @return the beginning index, inclusive.
134 */
135 public int getStartIndex(){
136 return startPos;
137 }
138
139 /**
140 * The ending index, exclusive.
141 *
142 * @return the ending index, exclusive.
143 */
144 public int getEndIndex(){
145 return startPos + length;
146 }
147
148 /**
149 * Fetch the text, in output suitable form
150 */
151 public String getText() {
152 return parentRun.getText().substring(startPos, startPos+length);
153 }
154 /**
155 * Fetch the text, in raw storage form
156 */
157 public String getRawText() {
158 return parentRun.getRawText().substring(startPos, startPos+length);
159 }
160
161 /**
162 * Change the text
163 */
164 public void setText(String text) {
165 String s = parentRun.normalize(text);
166 setRawText(s);
167 }
168
169 /**
170 * Change the text
171 */
172 public void setRawText(String text) {
173 length = text.length();
174 parentRun.changeTextInRichTextRun(this,text);
175 }
176
177 /**
178 * Tells the RichTextRun its new position in the parent TextRun
179 * @param startAt
180 */
181 public void updateStartPosition(int startAt) {
182 startPos = startAt;
183 }
184
185
186 // --------------- Internal helpers on rich text properties -------
187
188 /**
189 * Fetch the value of the given flag in the CharFlagsTextProp.
190 * Returns false if the CharFlagsTextProp isn't present, since the
191 * text property won't be set if there's no CharFlagsTextProp.
192 */
193 private boolean isCharFlagsTextPropVal(int index) {
194 return getFlag(true, index);
195 }
196
197 private boolean getFlag(boolean isCharacter, int index) {
198 TextPropCollection props;
199 String propname;
200 if (isCharacter){
201 props = characterStyle;
202 propname = CharFlagsTextProp.NAME;
203 } else {
204 props = paragraphStyle;
205 propname = ParagraphFlagsTextProp.NAME;
206 }
207
208 BitMaskTextProp prop = null;
209 if (props != null){
210 prop = (BitMaskTextProp)props.findByName(propname);
211 }
212 if (prop == null){
213 Sheet sheet = parentRun.getSheet();
214 if(sheet != null){
215 int txtype = parentRun.getRunType();
216 MasterSheet master = sheet.getMasterSheet();
217 if (master != null){
218 prop = (BitMaskTextProp)master.getStyleAttribute(txtype, getIndentLevel(), propname, isCharacter);
219 }
220 } else {
221 logger.log(POILogger.WARN, "MasterSheet is not available");
222 }
223 }
224
225 return prop == null ? false : prop.getSubValue(index);
226 }
227
228 /**
229 * Set the value of the given flag in the CharFlagsTextProp, adding
230 * it if required.
231 */
232 private void setCharFlagsTextPropVal(int index, boolean value) {
233 if(getFlag(true, index) != value) setFlag(true, index, value);
234 }
235
236 public void setFlag(boolean isCharacter, int index, boolean value) {
237 TextPropCollection props;
238 String propname;
239 if (isCharacter){
240 props = characterStyle;
241 propname = CharFlagsTextProp.NAME;
242 } else {
243 props = paragraphStyle;
244 propname = ParagraphFlagsTextProp.NAME;
245 }
246
247 // Ensure we have the StyleTextProp atom we're going to need
248 if(props == null) {
249 parentRun.ensureStyleAtomPresent();
250 props = isCharacter ? characterStyle : paragraphStyle;
251 }
252
253 BitMaskTextProp prop = (BitMaskTextProp) fetchOrAddTextProp(props, propname);
254 prop.setSubValue(value,index);
255 }
256
257 /**
258 * Returns the named TextProp, either by fetching it (if it exists) or adding it
259 * (if it didn't)
260 * @param textPropCol The TextPropCollection to fetch from / add into
261 * @param textPropName The name of the TextProp to fetch/add
262 */
263 private TextProp fetchOrAddTextProp(TextPropCollection textPropCol, String textPropName) {
264 // Fetch / Add the TextProp
265 TextProp tp = textPropCol.findByName(textPropName);
266 if(tp == null) {
267 tp = textPropCol.addWithName(textPropName);
268 }
269 return tp;
270 }
271
272 /**
273 * Fetch the value of the given Character related TextProp.
274 * Returns -1 if that TextProp isn't present.
275 * If the TextProp isn't present, the value from the appropriate
276 * Master Sheet will apply.
277 */
278 private int getCharTextPropVal(String propName) {
279 TextProp prop = null;
280 if (characterStyle != null){
281 prop = characterStyle.findByName(propName);
282 }
283
284 if (prop == null){
285 Sheet sheet = parentRun.getSheet();
286 int txtype = parentRun.getRunType();
287 MasterSheet master = sheet.getMasterSheet();
288 if (master != null)
289 prop = master.getStyleAttribute(txtype, getIndentLevel(), propName, true);
290 }
291 return prop == null ? -1 : prop.getValue();
292 }
293 /**
294 * Fetch the value of the given Paragraph related TextProp.
295 * Returns -1 if that TextProp isn't present.
296 * If the TextProp isn't present, the value from the appropriate
297 * Master Sheet will apply.
298 */
299 private int getParaTextPropVal(String propName) {
300 TextProp prop = null;
301 boolean hardAttribute = false;
302 if (paragraphStyle != null){
303 prop = paragraphStyle.findByName(propName);
304
305 BitMaskTextProp maskProp = (BitMaskTextProp)paragraphStyle.findByName(ParagraphFlagsTextProp.NAME);
306 hardAttribute = maskProp != null && maskProp.getValue() == 0;
307 }
308 if (prop == null && !hardAttribute){
309 Sheet sheet = parentRun.getSheet();
310 int txtype = parentRun.getRunType();
311 MasterSheet master = sheet.getMasterSheet();
312 if (master != null)
313 prop = master.getStyleAttribute(txtype, getIndentLevel(), propName, false);
314 }
315
316 return prop == null ? -1 : prop.getValue();
317 }
318
319 /**
320 * Sets the value of the given Character TextProp, add if required
321 * @param propName The name of the Character TextProp
322 * @param val The value to set for the TextProp
323 */
324 public void setParaTextPropVal(String propName, int val) {
325 // Ensure we have the StyleTextProp atom we're going to need
326 if(paragraphStyle == null) {
327 parentRun.ensureStyleAtomPresent();
328 // paragraphStyle will now be defined
329 }
330
331 TextProp tp = fetchOrAddTextProp(paragraphStyle, propName);
332 tp.setValue(val);
333 }
334 /**
335 * Sets the value of the given Paragraph TextProp, add if required
336 * @param propName The name of the Paragraph TextProp
337 * @param val The value to set for the TextProp
338 */
339 public void setCharTextPropVal(String propName, int val) {
340 // Ensure we have the StyleTextProp atom we're going to need
341 if(characterStyle == null) {
342 parentRun.ensureStyleAtomPresent();
343 // characterStyle will now be defined
344 }
345
346 TextProp tp = fetchOrAddTextProp(characterStyle, propName);
347 tp.setValue(val);
348 }
349
350
351 // --------------- Friendly getters / setters on rich text properties -------
352
353 /**
354 * Is the text bold?
355 */
356 public boolean isBold() {
357 return isCharFlagsTextPropVal(CharFlagsTextProp.BOLD_IDX);
358 }
359
360 /**
361 * Is the text bold?
362 */
363 public void setBold(boolean bold) {
364 setCharFlagsTextPropVal(CharFlagsTextProp.BOLD_IDX, bold);
365 }
366
367 /**
368 * Is the text italic?
369 */
370 public boolean isItalic() {
371 return isCharFlagsTextPropVal(CharFlagsTextProp.ITALIC_IDX);
372 }
373
374 /**
375 * Is the text italic?
376 */
377 public void setItalic(boolean italic) {
378 setCharFlagsTextPropVal(CharFlagsTextProp.ITALIC_IDX, italic);
379 }
380
381 /**
382 * Is the text underlined?
383 */
384 public boolean isUnderlined() {
385 return isCharFlagsTextPropVal(CharFlagsTextProp.UNDERLINE_IDX);
386 }
387
388 /**
389 * Is the text underlined?
390 */
391 public void setUnderlined(boolean underlined) {
392 setCharFlagsTextPropVal(CharFlagsTextProp.UNDERLINE_IDX, underlined);
393 }
394
395 /**
396 * Does the text have a shadow?
397 */
398 public boolean isShadowed() {
399 return isCharFlagsTextPropVal(CharFlagsTextProp.SHADOW_IDX);
400 }
401
402 /**
403 * Does the text have a shadow?
404 */
405 public void setShadowed(boolean flag) {
406 setCharFlagsTextPropVal(CharFlagsTextProp.SHADOW_IDX, flag);
407 }
408
409 /**
410 * Is this text embossed?
411 */
412 public boolean isEmbossed() {
413 return isCharFlagsTextPropVal(CharFlagsTextProp.RELIEF_IDX);
414 }
415
416 /**
417 * Is this text embossed?
418 */
419 public void setEmbossed(boolean flag) {
420 setCharFlagsTextPropVal(CharFlagsTextProp.RELIEF_IDX, flag);
421 }
422
423 /**
424 * Gets the strikethrough flag
425 */
426 public boolean isStrikethrough() {
427 return isCharFlagsTextPropVal(CharFlagsTextProp.STRIKETHROUGH_IDX);
428 }
429
430 /**
431 * Sets the strikethrough flag
432 */
433 public void setStrikethrough(boolean flag) {
434 setCharFlagsTextPropVal(CharFlagsTextProp.STRIKETHROUGH_IDX, flag);
435 }
436
437 /**
438 * Gets the subscript/superscript option
439 *
440 * @return the percentage of the font size. If the value is positive, it is superscript, otherwise it is subscript
441 */
442 public int getSuperscript() {
443 int val = getCharTextPropVal("superscript");
444 return val == -1 ? 0 : val;
445 }
446
447 /**
448 * Sets the subscript/superscript option
449 *
450 * @param val the percentage of the font size. If the value is positive, it is superscript, otherwise it is subscript
451 */
452 public void setSuperscript(int val) {
453 setCharTextPropVal("superscript", val);
454 }
455
456 /**
457 * Gets the font size
458 */
459 public int getFontSize() {
460 return getCharTextPropVal("font.size");
461 }
462
463
464 /**
465 * Sets the font size
466 */
467 public void setFontSize(int fontSize) {
468 setCharTextPropVal("font.size", fontSize);
469 }
470
471 /**
472 * Gets the font index
473 */
474 public int getFontIndex() {
475 return getCharTextPropVal("font.index");
476 }
477
478 /**
479 * Sets the font index
480 */
481 public void setFontIndex(int idx) {
482 setCharTextPropVal("font.index", idx);
483 }
484
485
486 /**
487 * Sets the font name to use
488 */
489 public void setFontName(String fontName) {
490 if (slideShow == null) {
491 //we can't set font since slideshow is not assigned yet
492 _fontname = fontName;
493 } else {
494 // Get the index for this font (adding if needed)
495 int fontIdx = slideShow.getFontCollection().addFont(fontName);
496 setCharTextPropVal("font.index", fontIdx);
497 }
498 }
499
500 /**
501 * Gets the font name
502 */
503 public String getFontName() {
504 if (slideShow == null) {
505 return _fontname;
506 } else {
507 int fontIdx = getCharTextPropVal("font.index");
508 if(fontIdx == -1) { return null; }
509 return slideShow.getFontCollection().getFontWithId(fontIdx);
510 }
511 }
512
513 /**
514 * @return font color as RGB value
515 * @see java.awt.Color
516 */
517 public Color getFontColor() {
518 int rgb = getCharTextPropVal("font.color");
519
520 int cidx = rgb >> 24;
521 if (rgb % 0x1000000 == 0){
522 ColorSchemeAtom ca = parentRun.getSheet().getColorScheme();
523 if(cidx >= 0 && cidx <= 7) rgb = ca.getColor(cidx);
524 }
525 Color tmp = new Color(rgb, true);
526 return new Color(tmp.getBlue(), tmp.getGreen(), tmp.getRed());
527 }
528
529 /**
530 * Sets color of the text, as a int bgr.
531 * (PowerPoint stores as BlueGreenRed, not the more
532 * usual RedGreenBlue)
533 * @see java.awt.Color
534 */
535 public void setFontColor(int bgr) {
536 setCharTextPropVal("font.color", bgr);
537 }
538
539 /**
540 * Sets color of the text, as a java.awt.Color
541 */
542 public void setFontColor(Color color) {
543 // In PowerPont RGB bytes are swapped, as BGR
544 int rgb = new Color(color.getBlue(), color.getGreen(), color.getRed(), 254).getRGB();
545 setFontColor(rgb);
546 }
547
548 /**
549 * Sets the type of horizontal alignment for the text.
550 * One of the <code>Align*</code> constants defined in the <code>TextBox</code> class.
551 *
552 * @param align - the type of alignment
553 */
554 public void setAlignment(int align) {
555 setParaTextPropVal("alignment", align);
556 }
557 /**
558 * Returns the type of horizontal alignment for the text.
559 * One of the <code>Align*</code> constants defined in the <code>TextBox</class> class.
560 *
561 * @return the type of alignment
562 */
563 public int getAlignment() {
564 return getParaTextPropVal("alignment");
565 }
566
567 /**
568 *
569 * @return indentation level
570 */
571 public int getIndentLevel() {
572 return paragraphStyle == null ? 0 : paragraphStyle.getReservedField();
573 }
574
575 /**
576 * Sets indentation level
577 *
578 * @param level indentation level. Must be in the range [0, 4]
579 */
580 public void setIndentLevel(int level) {
581 if(paragraphStyle != null ) paragraphStyle.setReservedField((short)level);
582 }
583
584 /**
585 * Sets whether this rich text run has bullets
586 */
587 public void setBullet(boolean flag) {
588 setFlag(false, ParagraphFlagsTextProp.BULLET_IDX, flag);
589 }
590
591 /**
592 * Returns whether this rich text run has bullets
593 */
594 public boolean isBullet() {
595 return getFlag(false, ParagraphFlagsTextProp.BULLET_IDX);
596 }
597
598 /**
599 * Returns whether this rich text run has bullets
600 */
601 public boolean isBulletHard() {
602 return getFlag(false, ParagraphFlagsTextProp.BULLET_IDX);
603 }
604
605 /**
606 * Sets the bullet character
607 */
608 public void setBulletChar(char c) {
609 setParaTextPropVal("bullet.char", c);
610 }
611
612 /**
613 * Returns the bullet character
614 */
615 public char getBulletChar() {
616 return (char)getParaTextPropVal("bullet.char");
617 }
618
619 /**
620 * Sets the bullet offset
621 */
622 public void setBulletOffset(int offset) {
623 setParaTextPropVal("bullet.offset", offset*Shape.MASTER_DPI/Shape.POINT_DPI);
624 }
625
626 /**
627 * Returns the bullet offset
628 */
629 public int getBulletOffset() {
630 return getParaTextPropVal("bullet.offset")*Shape.POINT_DPI/Shape.MASTER_DPI;
631 }
632
633 /**
634 * Sets the text offset
635 */
636 public void setTextOffset(int offset) {
637 setParaTextPropVal("text.offset", offset*Shape.MASTER_DPI/Shape.POINT_DPI);
638 }
639
640 /**
641 * Returns the text offset
642 */
643 public int getTextOffset() {
644 return getParaTextPropVal("text.offset")*Shape.POINT_DPI/Shape.MASTER_DPI;
645 }
646
647 /**
648 * Sets the bullet size
649 */
650 public void setBulletSize(int size) {
651 setParaTextPropVal("bullet.size", size);
652 }
653
654 /**
655 * Returns the bullet size
656 */
657 public int getBulletSize() {
658 return getParaTextPropVal("bullet.size");
659 }
660
661 /**
662 * Sets the bullet color
663 */
664 public void setBulletColor(Color color) {
665 int rgb = new Color(color.getBlue(), color.getGreen(), color.getRed(), 254).getRGB();
666 setParaTextPropVal("bullet.color", rgb);
667 }
668
669 /**
670 * Returns the bullet color
671 */
672 public Color getBulletColor() {
673 int rgb = getParaTextPropVal("bullet.color");
674 if(rgb == -1) return getFontColor();
675
676 int cidx = rgb >> 24;
677 if (rgb % 0x1000000 == 0){
678 ColorSchemeAtom ca = parentRun.getSheet().getColorScheme();
679 if(cidx >= 0 && cidx <= 7) rgb = ca.getColor(cidx);
680 }
681 Color tmp = new Color(rgb, true);
682 return new Color(tmp.getBlue(), tmp.getGreen(), tmp.getRed());
683 }
684
685 /**
686 * Sets the bullet font
687 */
688 public void setBulletFont(int idx) {
689 setParaTextPropVal("bullet.font", idx);
690 }
691
692 /**
693 * Returns the bullet font
694 */
695 public int getBulletFont() {
696 return getParaTextPropVal("bullet.font");
697 }
698
699 /**
700 * Sets the line spacing.
701 * <p>
702 * If linespacing >= 0, then linespacing is a percentage of normal line height.
703 * If linespacing < 0, the absolute value of linespacing is the spacing in master coordinates.
704 * </p>
705 */
706 public void setLineSpacing(int val) {
707 setParaTextPropVal("linespacing", val);
708 }
709
710 /**
711 * Returns the line spacing
712 * <p>
713 * If linespacing >= 0, then linespacing is a percentage of normal line height.
714 * If linespacing < 0, the absolute value of linespacing is the spacing in master coordinates.
715 * </p>
716 *
717 * @return the spacing between lines
718 */
719 public int getLineSpacing() {
720 int val = getParaTextPropVal("linespacing");
721 return val == -1 ? 0 : val;
722 }
723
724 /**
725 * Sets spacing before a paragraph.
726 * <p>
727 * If spacebefore >= 0, then spacebefore is a percentage of normal line height.
728 * If spacebefore < 0, the absolute value of spacebefore is the spacing in master coordinates.
729 * </p>
730 */
731 public void setSpaceBefore(int val) {
732 setParaTextPropVal("spacebefore", val);
733 }
734
735 /**
736 * Returns spacing before a paragraph
737 * <p>
738 * If spacebefore >= 0, then spacebefore is a percentage of normal line height.
739 * If spacebefore < 0, the absolute value of spacebefore is the spacing in master coordinates.
740 * </p>
741 *
742 * @return the spacing before a paragraph
743 */
744 public int getSpaceBefore() {
745 int val = getParaTextPropVal("spacebefore");
746 return val == -1 ? 0 : val;
747 }
748
749 /**
750 * Sets spacing after a paragraph.
751 * <p>
752 * If spaceafter >= 0, then spaceafter is a percentage of normal line height.
753 * If spaceafter < 0, the absolute value of spaceafter is the spacing in master coordinates.
754 * </p>
755 */
756 public void setSpaceAfter(int val) {
757 setParaTextPropVal("spaceafter", val);
758 }
759
760 /**
761 * Returns spacing after a paragraph
762 * <p>
763 * If spaceafter >= 0, then spaceafter is a percentage of normal line height.
764 * If spaceafter < 0, the absolute value of spaceafter is the spacing in master coordinates.
765 * </p>
766 *
767 * @return the spacing before a paragraph
768 */
769 public int getSpaceAfter() {
770 int val = getParaTextPropVal("spaceafter");
771 return val == -1 ? 0 : val;
772 }
773 // --------------- Internal HSLF methods, not intended for end-user use! -------
774
775 /**
776 * Internal Use Only - get the underlying paragraph style collection.
777 * For normal use, use the friendly setters and getters
778 */
779 public TextPropCollection _getRawParagraphStyle() { return paragraphStyle; }
780 /**
781 * Internal Use Only - get the underlying character style collection.
782 * For normal use, use the friendly setters and getters
783 */
784 public TextPropCollection _getRawCharacterStyle() { return characterStyle; }
785 /**
786 * Internal Use Only - are the Paragraph styles shared?
787 */
788 public boolean _isParagraphStyleShared() { return sharingParagraphStyle; }
789 /**
790 * Internal Use Only - are the Character styles shared?
791 */
792 public boolean _isCharacterStyleShared() { return sharingCharacterStyle; }
793 }