1 // TextField.cpp: User-editable text regions, for Gnash.
3 // Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Free Software
6 // This program is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 3 of the License, or
9 // (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 // Things implemented:
21 // - setTextFormat does not discard target, url, tabStops, display or
23 // - Above five fields are now implemented (except for target != blank)
24 // - Call movie_root getURL function to properly open url and target
27 // - For the url cases (url property and anchor tag in html) we should
28 // change the mouse cursor to the hand cursor standard for linkable
33 #include "swf/DefineEditTextTag.h"
34 #include "MovieClip.h"
35 #include "TextField.h"
36 #include "movie_root.h" // for killing focus
37 #include "as_environment.h"
40 #include "namedStrings.h"
41 #include "StringPredicates.h"
42 #include "TextFormat_as.h"
44 #include "TextRecord.h"
46 #include "GnashNumeric.h"
47 #include "MouseButtonState.h"
48 #include "Global_as.h"
50 #include "Transform.h"
54 #include <boost/assign/list_of.hpp>
55 #include <boost/bind.hpp>
61 // Text fields have a fixed 2 pixel padding for each side (regardless of border)
62 #define PADDING_TWIPS 40
64 // Define the following to get detailed log information about
65 // textfield bounds and HTML tags:
66 //#define GNASH_DEBUG_TEXTFIELDS 1
68 // Define this to get debugging info about text formatting
69 //#define GNASH_DEBUG_TEXT_FORMATTING 1
73 TextField::TextField(as_object
* object
, DisplayObject
* parent
,
74 const SWF::DefineEditTextTag
& def
)
76 InteractiveObject(object
, parent
),
82 _variable_name(def
.variableName()),
83 _backgroundColor(255,255,255,255),
84 _borderColor(0,0,0,255),
85 _textColor(def
.color()),
86 _alignment(def
.alignment()),
96 _maxChars(def
.maxChars()),
97 _autoSize(def
.autoSize() ? AUTOSIZE_LEFT
: AUTOSIZE_NONE
),
98 _type(def
.readOnly() ? typeDynamic
: typeInput
),
99 _bounds(def
.bounds()),
101 _leading(def
.leading()),
102 _indent(def
.indent()),
104 _leftMargin(def
.leftMargin()),
105 _rightMargin(def
.rightMargin()),
106 _fontHeight(def
.textHeight()),
107 _textDefined(def
.hasText()),
108 _htmlTextDefined(def
.hasText()),
109 _restrictDefined(false),
113 _multiline(def
.multiline()),
114 _password(def
.password()),
115 _text_variable_registered(false),
116 _drawBackground(def
.border()),
117 _drawBorder(def
.border()),
118 _embedFonts(def
.getUseEmbeddedGlyphs()),
119 _wordWrap(def
.wordWrap()),
121 _selectable(!def
.noSelect())
126 // WARNING! remember to set the font *before* setting text value!
127 boost::intrusive_ptr
<const Font
> f
= def
.getFont();
128 if (!f
) f
= fontlib::get_default_font();
131 int version
= getSWFVersion(*object
);
133 // set default text *before* calling registerTextVariable
134 // (if the textvariable already exist and has a value
135 // the text will be replaced with it)
138 setTextValue(utf8::decodeCanonicalString(def
.defaultText(), version
));
139 setHtmlTextValue(utf8::decodeCanonicalString(def
.defaultText(), version
));
146 TextField::TextField(as_object
* object
, DisplayObject
* parent
,
147 const SWFRect
& bounds
)
149 InteractiveObject(object
, parent
),
154 _backgroundColor(255,255,255,255),
155 _borderColor(0, 0, 0, 255),
156 _textColor(0, 0, 0, 255),
157 _alignment(ALIGN_LEFT
),
168 _autoSize(AUTOSIZE_NONE
),
177 _fontHeight(12 * 20),
179 _htmlTextDefined(false),
180 _restrictDefined(false),
186 _text_variable_registered(false),
187 _drawBackground(false),
194 // Use the default font (Times New Roman for Windows, Times for Mac
195 // according to docs. They don't say what it is for Linux.
196 boost::intrusive_ptr
<const Font
> f
= fontlib::get_default_font();
205 registerTextVariable();
207 reset_bounding_box(0, 0);
211 TextField::~TextField()
216 TextField::removeTextField()
218 int depth
= get_depth();
219 if ( depth
< 0 || depth
> 1048575 )
221 //IF_VERBOSE_ASCODING_ERRORS(
222 log_debug(_("CHECKME: removeTextField(%s): TextField depth (%d) "
223 "out of the 'dynamic' zone [0..1048575], won't remove"),
229 DisplayObject
* p
= parent();
230 assert(p
); // every TextField must have a parent, right ?
232 MovieClip
* parentSprite
= p
->to_movie();
235 log_error("FIXME: attempt to remove a TextField being a child of a %s",
240 // second argument is arbitrary, see comments above
241 // the function declaration in MovieClip.h
242 parentSprite
->remove_display_object(depth
, 0);
246 TextField::show_cursor(Renderer
& renderer
, const SWFMatrix
& mat
)
248 if (_textRecords
.empty()) {
254 size_t i
= cursorRecord();
255 SWF::TextRecord record
= _textRecords
[i
];
257 x
= record
.xOffset();
258 y
= record
.yOffset() - record
.textHeight() + getLeading();
259 h
= record
.textHeight();
261 if (!record
.glyphs().empty()) {
262 for (unsigned int p
= 0 ; p
< (m_cursor
- _recordStarts
[i
]); ++p
) {
263 x
+= record
.glyphs()[p
].advance
;
267 const std::vector
<point
> box
= boost::assign::list_of
271 renderer
.drawLine(box
, rgba(0, 0, 0, 255), mat
);
275 TextField::cursorRecord()
277 SWF::TextRecord record
;
280 if (_textRecords
.size() != 0) {
281 while (i
< _textRecords
.size() && m_cursor
>= _recordStarts
[i
]) {
290 TextField::display(Renderer
& renderer
, const Transform
& base
)
292 const DisplayObject::MaskRenderer
mr(renderer
, *this);
294 registerTextVariable();
296 const bool drawBorder
= getDrawBorder();
297 const bool drawBackground
= getDrawBackground();
299 Transform xform
= base
* transform();
301 // This is a hack to handle device fonts, which are not affected by
303 if (getEmbedFonts()) xform
.colorTransform
= SWFCxForm();
305 if ((drawBorder
|| drawBackground
) && !_bounds
.is_null()) {
307 std::vector
<point
> coords(4);
309 boost::int32_t xmin
= _bounds
.get_x_min();
310 boost::int32_t xmax
= _bounds
.get_x_max();
311 boost::int32_t ymin
= _bounds
.get_y_min();
312 boost::int32_t ymax
= _bounds
.get_y_max();
314 coords
[0].setTo(xmin
, ymin
);
315 coords
[1].setTo(xmax
, ymin
);
316 coords
[2].setTo(xmax
, ymax
);
317 coords
[3].setTo(xmin
, ymax
);
319 rgba borderColor
= drawBorder
? getBorderColor() : rgba(0,0,0,0);
320 rgba backgroundColor
= drawBackground
? getBackgroundColor() :
323 SWFCxForm cx
= xform
.colorTransform
;
325 if (drawBorder
) borderColor
= cx
.transform(borderColor
);
327 if (drawBackground
) backgroundColor
= cx
.transform(backgroundColor
);
329 #ifdef GNASH_DEBUG_TEXTFIELDS
330 log_debug("rendering a Pol composed by corners %s", _bounds
);
333 renderer
.draw_poly(&coords
.front(), 4, backgroundColor
,
334 borderColor
, xform
.matrix
, true);
338 // Draw our actual text.
339 // Using a SWFMatrix to translate to def bounds seems an hack to me.
340 // A cleaner implementation is likely correctly setting the
341 // _xOffset and _yOffset memebers in glyph records.
342 // Anyway, see bug #17954 for a testcase.
343 if (!_bounds
.is_null()) {
344 xform
.matrix
.concatenate_translation(_bounds
.get_x_min(),
345 _bounds
.get_y_min());
348 _displayRecords
.clear();
349 float scale
= getFontHeight() /
350 static_cast<float>(_font
->unitsPerEM(_embedFonts
));
351 float fontLeading
= _font
->leading() * scale
;
354 int yoffset
= (getFontHeight() + fontLeading
) + PADDING_TWIPS
;
356 for (size_t i
= 0; i
< _textRecords
.size(); ++i
) {
358 //find the line the record is on
359 while (recordline
< _line_starts
.size() &&
360 _line_starts
[recordline
] <= _recordStarts
[i
]) {
364 _textRecords
[i
].setYOffset((recordline
-_scroll
)*yoffset
);
365 //add the lines we want to the display record
366 if (_textRecords
[i
].yOffset() > 0 &&
367 _textRecords
[i
].yOffset() < _bounds
.height()) {
368 _displayRecords
.push_back(_textRecords
[i
]);
372 SWF::TextRecord::displayRecords(renderer
, xform
, _displayRecords
,
375 if (m_has_focus
&& !isReadOnly()) show_cursor(renderer
, xform
.matrix
);
382 TextField::add_invalidated_bounds(InvalidatedRanges
& ranges
, bool force
)
384 if (!force
&& !invalidated()) return; // no need to redraw
386 ranges
.add(m_old_invalidated_ranges
);
388 const SWFMatrix
& wm
= getWorldMatrix(*this);
390 SWFRect bounds
= getBounds();
391 bounds
.expand_to_rect(m_text_bounding_box
);
392 wm
.transform(bounds
);
393 ranges
.add(bounds
.getRange());
397 TextField::setRestrict(const std::string
& restrict
)
399 _restrictDefined
= true;
401 std::string::const_iterator rit
= restrict
.begin();
402 std::string::const_iterator re
= restrict
.end();
403 std::set
<wchar_t>::const_iterator locate
;
405 if (*rit
== '^') { //then this is a true RESTRICT pattern, add all chars to _restrictedchars
406 for (unsigned int i
= 0; i
<= 255; ++i
) {
407 _restrictedchars
.insert(char(i
));
409 } else { //then this is an ALLOW pattern, _restrictedchars should remain empty
410 _restrictedchars
.clear();
414 while (rit
!= re
&& *rit
!= '^') { //This loop allows chars
416 log_error("invalid restrict string");
418 } else if (*(rit
+1) == '-') {
419 if (re
- (rit
+2) != 0) {
420 unsigned int q
= *(rit
+2);
421 for (unsigned int p
= *rit
; p
<= q
; (++p
)){
422 _restrictedchars
.insert(char(p
));
426 log_error("invalid restrict string");
429 } else if (*rit
== '\\') {
431 _restrictedchars
.insert(*rit
);
434 _restrictedchars
.insert(*rit
);
441 while (rit
!= re
&& *rit
!= '^') { //This loop restricts chars
442 locate
= _restrictedchars
.find(*rit
);
444 log_error("invalid restrict string");
446 } else if (*(rit
+1) == '-') {
447 if (re
- (rit
+2) != 0) {
448 unsigned int q
= *(rit
+2);
449 for (unsigned int p
= *rit
; p
<= q
; ++p
){
450 locate
= _restrictedchars
.find(p
);
451 if(locate
!= _restrictedchars
.end()) {
452 _restrictedchars
.erase(locate
);
459 log_error("invalid restrict string");
462 } else if (*rit
== '\\') {
464 locate
= _restrictedchars
.find(*rit
);
465 if(locate
!= _restrictedchars
.end()) {
466 _restrictedchars
.erase(locate
);
470 if(locate
!= _restrictedchars
.end()) {
471 _restrictedchars
.erase(locate
);
480 _restrict
= restrict
;
484 TextField::replaceSelection(const std::string
& replace
)
487 const int version
= getSWFVersion(*getObject(this));
488 const std::wstring
& wstr
= utf8::decodeCanonicalString(replace
, version
);
490 const size_t start
= _selection
.first
;
491 const size_t replaceLength
= wstr
.size();
493 _text
.replace(start
, _selection
.second
- start
, wstr
);
494 _selection
= std::make_pair(start
+ replaceLength
, start
+ replaceLength
);
498 TextField::setSelection(int start
, int end
)
502 _selection
= std::make_pair(0, 0);
506 const size_t textLength
= _text
.size();
508 if (start
< 0) start
= 0;
509 else start
= std::min
<size_t>(start
, textLength
);
511 if (end
< 0) end
= 0;
512 else end
= std::min
<size_t>(end
, textLength
);
514 // The cursor position is always set to the end value, even if the
515 // two values are swapped to obtain the selection. Equal values are
518 if (start
> end
) std::swap(start
, end
);
520 _selection
= std::make_pair(start
, end
);
524 TextField::notifyEvent(const event_id
& ev
)
528 case event_id::PRESS
:
530 movie_root
& root
= stage();
531 boost::int32_t x_mouse
, y_mouse
;
532 root
.get_mouse_state(x_mouse
, y_mouse
);
534 SWFMatrix m
= getMatrix(*this);
536 x_mouse
-= m
.get_x_translation();
537 y_mouse
-= m
.get_y_translation();
541 for (size_t i
=0; i
< _textRecords
.size(); ++i
) {
542 if ((x_mouse
> _textRecords
[i
].xOffset()) &&
543 (x_mouse
< _textRecords
[i
].xOffset()+_textRecords
[i
].recordWidth()) &&
544 (y_mouse
> _textRecords
[i
].yOffset()-_textRecords
[i
].textHeight()) &&
545 (y_mouse
< _textRecords
[i
].yOffset())) {
546 rec
= _textRecords
[i
];
551 if (!rec
.getURL().empty()) {
552 root
.getURL(rec
.getURL(), rec
.getTarget(), "",
553 MovieClip::METHOD_NONE
);
558 case event_id::KEY_PRESS
:
560 setHtml(false); //editable html fields are not yet implemented
561 std::wstring s
= _text
;
563 // id.keyCode is the unique gnash::key::code for a DisplayObject/key.
564 // The maximum value is about 265, including function keys.
565 // It seems that typing in DisplayObjects outside the Latin-1 set
566 // (256 DisplayObject codes, identical to the first 256 of UTF-8)
567 // is not supported, though a much greater number UTF-8 codes can be
568 // stored and displayed. See utf.h for more information.
569 // This is a limit on the number of key codes, not on the
570 // capacity of strings.
571 gnash::key::code c
= ev
.keyCode();
574 // maybe _text is changed in ActionScript
575 m_cursor
= std::min
<size_t>(m_cursor
, _text
.size());
577 size_t cur_cursor
= m_cursor
;
578 size_t previouslinesize
= 0;
579 size_t nextlinesize
= 0;
580 size_t manylines
= _line_starts
.size();
581 LineStarts::iterator linestartit
= _line_starts
.begin();
582 LineStarts::const_iterator linestartend
= _line_starts
.end();
587 if (isReadOnly()) return;
590 s
.erase(m_cursor
- 1, 1);
597 if (isReadOnly()) return;
598 if (_glyphcount
> m_cursor
)
600 s
.erase(m_cursor
, 1);
605 case key::INSERT
: // TODO
606 if (isReadOnly()) return;
610 while ( linestartit
< linestartend
&& *linestartit
<= m_cursor
) {
611 cur_cursor
= *linestartit
;
614 m_cursor
= cur_cursor
;
618 // if going a page up is too far...
619 if(_scroll
< _linesindisplay
) {
622 } else { // go a page up
623 _scroll
-= _linesindisplay
;
624 m_cursor
= _line_starts
[_scroll
];
630 while ( linestartit
< linestartend
&& *linestartit
<= m_cursor
) {
631 cur_cursor
= *linestartit
;
634 //if there is no previous line
635 if ( linestartit
-_line_starts
.begin() - 2 < 0 ) {
639 previouslinesize
= _textRecords
[linestartit
-_line_starts
.begin() - 2].glyphs().size();
640 //if the previous line is smaller
641 if (m_cursor
- cur_cursor
> previouslinesize
) {
642 m_cursor
= *(--(--linestartit
)) + previouslinesize
;
644 m_cursor
= *(--(--linestartit
)) + (m_cursor
- cur_cursor
);
646 if (m_cursor
< _line_starts
[_scroll
] && _line_starts
[_scroll
] != 0) {
653 while ( linestartit
< linestartend
&& *linestartit
<= m_cursor
) {
656 m_cursor
= linestartit
!= linestartend
? *linestartit
- 1 : _text
.size();
660 //if going another page down is too far...
661 if(_scroll
+ _linesindisplay
>= manylines
) {
662 if(manylines
- _linesindisplay
<= 0) {
665 _scroll
= manylines
- _linesindisplay
;
667 if(m_cursor
< _line_starts
[_scroll
-1]) {
668 m_cursor
= _line_starts
[_scroll
-1];
670 m_cursor
= _text
.size();
672 } else { //go a page down
673 _scroll
+= _linesindisplay
;
674 m_cursor
= _line_starts
[_scroll
];
681 while (linestartit
< linestartend
&&
682 *linestartit
<= m_cursor
) {
683 cur_cursor
= *linestartit
;
687 // linestartit should never be before _line_starts.begin()
688 const size_t currentLine
= linestartit
-
689 _line_starts
.begin();
691 //if there is no next line
692 if (currentLine
>= manylines
) {
693 m_cursor
= _text
.size();
696 nextlinesize
= _textRecords
[currentLine
].glyphs().size();
698 //if the next line is smaller
699 if (m_cursor
- cur_cursor
> nextlinesize
) {
700 m_cursor
= *linestartit
+ nextlinesize
;
702 //put the cursor at the same character distance
703 m_cursor
= *(linestartit
) + (m_cursor
- cur_cursor
);
705 if (_line_starts
.size() > _linesindisplay
&&
706 m_cursor
>= _line_starts
[_scroll
+_linesindisplay
]) {
714 m_cursor
= m_cursor
> 0 ? m_cursor
- 1 : 0;
718 m_cursor
= m_cursor
< _glyphcount
? m_cursor
+ 1 :
723 if (isReadOnly()) return;
731 if ( _maxChars
<= _glyphcount
)
737 if (isReadOnly()) return;
738 wchar_t t
= static_cast<wchar_t>(
739 gnash::key::codeMap
[c
][key::ASCII
]);
743 if (!_restrictDefined
) {
744 // Insert one copy of the character
745 // at the cursor position.
746 s
.insert(m_cursor
, 1, t
);
748 } else if (_restrictedchars
.count(t
)) {
749 // Insert one copy of the character
750 // at the cursor position.
751 s
.insert(m_cursor
, 1, t
);
753 } else if (_restrictedchars
.count(tolower(t
))) {
754 // restrict substitutes the opposite case
755 s
.insert(m_cursor
, 1, tolower(t
));
757 } else if (_restrictedchars
.count(toupper(t
))) {
758 // restrict substitutes the opposite case
759 s
.insert(m_cursor
, 1, toupper(t
));
775 TextField::topmostMouseEntity(boost::int32_t x
, boost::int32_t y
)
778 if (!visible()) return 0;
780 // Not selectable, so don't catch mouse events!
781 if (!_selectable
) return 0;
783 SWFMatrix m
= getMatrix(*this);
785 m
.invert().transform(p
);
787 if (_bounds
.point_test(p
.x
, p
.y
)) return this;
793 TextField::updateText(const std::string
& str
)
795 int version
= getSWFVersion(*getObject(this));
796 const std::wstring
& wstr
= utf8::decodeCanonicalString(str
, version
);
801 TextField::updateText(const std::wstring
& wstr
)
805 if (_text
== wstr
) return;
814 TextField::updateHtmlText(const std::string
& str
)
816 int version
= getSWFVersion(*getObject(this));
817 const std::wstring
& wstr
= utf8::decodeCanonicalString(str
, version
);
818 updateHtmlText(wstr
);
822 TextField::updateHtmlText(const std::wstring
& wstr
)
824 _htmlTextDefined
= true;
826 if (_htmlText
== wstr
) return;
835 TextField::setHtmlTextValue(const std::wstring
& wstr
)
840 //updateText with no HTML tags
841 //for now, it is better to make the display correct
844 updateHtmlText(wstr
);
846 if ( ! _variable_name
.empty() && _text_variable_registered
)
848 // TODO: notify MovieClip if we have a variable name !
849 VariableRef ref
= parseTextVariableRef(_variable_name
);
850 as_object
* tgt
= ref
.first
;
853 int version
= getSWFVersion(*getObject(this));
854 // we shouldn't truncate, right?
855 tgt
->set_member(ref
.second
, utf8::encodeCanonicalString(wstr
,
860 // nothing to do (too early ?)
861 log_debug("setHtmlTextValue: variable name %s points to a non-existent"
862 " target, I guess we would not be registered if this was "
863 "true, or the sprite we've registered our variable name "
864 "has been unloaded", _variable_name
);
870 TextField::setTextValue(const std::wstring
& wstr
)
873 updateHtmlText(wstr
);
875 //updateHtmlText and insert necessary html tags
879 if ( ! _variable_name
.empty() && _text_variable_registered
)
881 // TODO: notify MovieClip if we have a variable name !
882 VariableRef ref
= parseTextVariableRef(_variable_name
);
883 as_object
* tgt
= ref
.first
;
886 int version
= getSWFVersion(*getObject(this));
887 // we shouldn't truncate, right?
888 tgt
->set_member(ref
.second
, utf8::encodeCanonicalString(wstr
,
893 // nothing to do (too early ?)
894 log_debug("setTextValue: variable name %s points to a non-existent"
895 " target, I guess we would not be registered if this was "
896 "true, or the sprite we've registered our variable name "
897 "has been unloaded", _variable_name
);
903 TextField::get_text_value() const
905 // we need the const_cast here because registerTextVariable
906 // *might* change our text value, calling the non-const
908 // This happens if the TextVariable has not been already registered
909 // and during registration comes out to name an existing variable
910 // with a pre-existing value.
911 const_cast<TextField
*>(this)->registerTextVariable();
913 const int version
= getSWFVersion(*getObject(this));
915 return utf8::encodeCanonicalString(_text
, version
);
919 TextField::get_htmltext_value() const
921 const_cast<TextField
*>(this)->registerTextVariable();
922 int version
= getSWFVersion(*getObject(const_cast<TextField
*>(this)));
923 return utf8::encodeCanonicalString(_htmlText
, version
);
927 TextField::setTextFormat(TextFormat_as
& tf
)
929 //TODO: this is lazy. we should set all the TextFormat variables HERE, i think
930 //This is just so we can set individual variables without having to call format_text()
931 //This calls format_text() at the end of setting TextFormat
932 if (tf
.align()) setAlignment(*tf
.align());
933 if (tf
.size()) setFontHeight(*tf
.size()); // keep twips
934 if (tf
.indent()) setIndent(*tf
.indent());
935 if (tf
.blockIndent()) setBlockIndent(*tf
.blockIndent());
936 if (tf
.leading()) setLeading(*tf
.leading());
937 if (tf
.leftMargin()) setLeftMargin(*tf
.leftMargin());
938 if (tf
.rightMargin()) setRightMargin(*tf
.rightMargin());
939 if (tf
.color()) setTextColor(*tf
.color());
940 if (tf
.underlined()) setUnderlined(*tf
.underlined());
941 if (tf
.bullet()) setBullet(*tf
.bullet());
942 setDisplay(tf
.display());
943 if (tf
.tabStops()) setTabStops(*tf
.tabStops());
945 // NEED TO IMPLEMENT THESE TWO
946 if (tf
.url()) setURL(*tf
.url());
947 if (tf
.target()) setTarget(*tf
.target());
953 TextField::align_line(TextAlignment align
, int last_line_start_record
, float x
)
956 float width
= _bounds
.width();
957 float right_margin
= getRightMargin();
959 float extra_space
= (width
- right_margin
) - x
- PADDING_TWIPS
;
961 if (extra_space
<= 0.0f
) {
962 #ifdef GNASH_DEBUG_TEXTFIELDS
963 log_debug(_("TextField text doesn't fit in its boundaries: "
964 "width %g, margin %g - nothing to align"),
965 width
, right_margin
);
970 float shift_right
= 0.0f
;
974 // Nothing to do; already aligned left.
977 // Distribute the space evenly on both sides.
978 shift_right
= extra_space
/ 2;
981 // Shift all the way to the right.
982 shift_right
= extra_space
;
985 // What should we do here?
989 // Shift the beginnings of the records on this line.
990 for (size_t i
= last_line_start_record
; i
< _textRecords
.size(); ++i
) {
991 SWF::TextRecord
& rec
= _textRecords
[i
];
992 rec
.setXOffset(rec
.xOffset() + shift_right
);
997 boost::intrusive_ptr
<const Font
>
998 TextField::setFont(boost::intrusive_ptr
<const Font
> newfont
)
1000 if ( newfont
== _font
) return _font
;
1002 boost::intrusive_ptr
<const Font
> oldfont
= _font
;
1011 TextField::insertTab(SWF::TextRecord
& rec
, boost::int32_t& x
, float scale
)
1014 const int space
= 32;
1015 int index
= rec
.getFont()->get_glyph_index(space
, _embedFonts
);
1018 IF_VERBOSE_MALFORMED_SWF (
1019 log_error(_("TextField: missing glyph for space char (needed "
1020 "for TAB). Make sure DisplayObject shapes for font "
1021 "%s are being exported into your SWF file."),
1022 rec
.getFont()->name());
1027 std::vector
<int> tabStops
;
1028 tabStops
= _tabStops
;
1030 std::sort(_tabStops
.begin(), _tabStops
.end());
1033 if ( !_tabStops
.empty() )
1035 tab
= _tabStops
.back()+1;
1037 for (size_t i
= 0; i
< tabStops
.size(); ++i
)
1039 if (tabStops
[i
] > x
)
1041 if((tabStops
[i
] - x
) < tab
)
1043 tab
= tabStops
[i
] - x
;
1049 // This is necessary in case the number of tabs in the text
1050 // are more than the actual number of tabStops inside the
1052 if ( !(tab
== _tabStops
.back()+1) )
1054 SWF::TextRecord::GlyphEntry ge
;
1055 ge
.index
= rec
.getFont()->get_glyph_index(32, _embedFonts
);
1063 SWF::TextRecord::GlyphEntry ge
;
1065 ge
.advance
= scale
* rec
.getFont()->get_advance(index
,
1068 const int tabstop
= 4;
1069 rec
.addGlyph(ge
, tabstop
);
1070 x
+= ge
.advance
* tabstop
;
1076 TextField::format_text()
1078 _textRecords
.clear();
1079 _line_starts
.clear();
1080 _recordStarts
.clear();
1083 _recordStarts
.push_back(0);
1085 // nothing more to do if text is empty
1086 if ( _text
.empty() )
1088 // TODO: should we still reset _bounds if autoSize != AUTOSIZE_NONE ?
1089 // not sure we should...
1090 reset_bounding_box(0, 0);
1094 LineStarts::iterator linestartit
= _line_starts
.begin();
1095 LineStarts::const_iterator linestartend
= _line_starts
.end();
1097 AutoSize autoSize
= getAutoSize();
1098 if (autoSize
!= AUTOSIZE_NONE
) {
1099 // When doing WordWrap we don't want to change
1100 // the boundaries. See bug #24348
1101 if (!doWordWrap()) {
1102 _bounds
.set_to_rect(0, 0, 0, 0); // this is correct for 'true'
1106 // FIXME: I don't think we should query the definition
1107 // to find the appropriate font to use, as ActionScript
1108 // code should be able to change the font of a TextField
1110 log_error(_("No font for TextField!"));
1114 boost::uint16_t fontHeight
= getFontHeight();
1115 float scale
= fontHeight
/
1116 static_cast<float>(_font
->unitsPerEM(_embedFonts
));
1117 const float fontLeading
= _font
->leading() * scale
;
1118 const boost::uint16_t leftMargin
= getLeftMargin();
1119 const boost::uint16_t indent
= getIndent();
1120 const boost::uint16_t blockIndent
= getBlockIndent();
1121 const bool underlined
= getUnderlined();
1123 /// Remember the current bounds for autosize.
1124 SWFRect
oldBounds(_bounds
);
1126 SWF::TextRecord rec
; // one to work on
1127 rec
.setFont(_font
.get());
1128 rec
.setUnderline(underlined
);
1129 rec
.setColor(getTextColor());
1130 rec
.setXOffset(PADDING_TWIPS
+
1131 std::max(0, leftMargin
+ indent
+ blockIndent
));
1132 rec
.setYOffset(PADDING_TWIPS
+ fontHeight
+ fontLeading
);
1133 rec
.setTextHeight(fontHeight
);
1135 // create in textrecord.h
1137 rec
.setTarget(_target
);
1141 // First, we indent 10 spaces, and then place the bullet
1142 // character (in this case, an asterisk), then we pad it
1143 // again with 10 spaces
1144 // Note: this works only for additional lines of a
1145 // bulleted list, so that is why there is a bullet format
1146 // in the beginning of format_text()
1149 int space
= rec
.getFont()->get_glyph_index(32, _embedFonts
);
1151 SWF::TextRecord::GlyphEntry ge
;
1153 ge
.advance
= scale
* rec
.getFont()->get_advance(space
, _embedFonts
);
1154 rec
.addGlyph(ge
, 5);
1156 // We use an asterisk instead of a bullet
1157 int bullet
= rec
.getFont()->get_glyph_index(42, _embedFonts
);
1159 ge
.advance
= scale
* rec
.getFont()->get_advance(bullet
, _embedFonts
);
1162 space
= rec
.getFont()->get_glyph_index(32, _embedFonts
);
1164 ge
.advance
= scale
* rec
.getFont()->get_advance(space
, _embedFonts
);
1165 rec
.addGlyph(ge
, 4);
1168 boost::int32_t x
= static_cast<boost::int32_t>(rec
.xOffset());
1169 boost::int32_t y
= static_cast<boost::int32_t>(rec
.yOffset());
1171 // Start the bbox at the upper-left corner of the first glyph.
1172 //reset_bounding_box(x, y + fontHeight);
1174 int last_code
= -1; // only used if _embedFonts
1175 int last_space_glyph
= -1;
1176 size_t last_line_start_record
= 0;
1178 float leading
= getLeading();
1179 leading
+= fontLeading
* scale
; // not sure this is correct...
1181 _line_starts
.push_back(0);
1183 // String iterators are very sensitive to
1184 // potential changes to the string (to allow for copy-on-write).
1185 // So there must be no external changes to the string or
1186 // calls to most non-const member functions during this loop.
1187 // Especially not c_str() or data().
1188 std::wstring::const_iterator it
= _text
.begin();
1189 const std::wstring::const_iterator e
= _text
.end();
1191 ///handleChar takes care of placing the glyphs
1192 handleChar(it
, e
, x
, y
, rec
, last_code
, last_space_glyph
,
1193 last_line_start_record
);
1195 // Expand bounding box to include the whole text (if autoSize and wordWrap
1196 // is not in operation.
1197 if (_autoSize
!= AUTOSIZE_NONE
&& !doWordWrap())
1199 _bounds
.expand_to_point(x
+ PADDING_TWIPS
, y
+ PADDING_TWIPS
);
1201 if (_autoSize
== AUTOSIZE_RIGHT
) {
1202 /// Autosize right expands from the previous right margin.
1205 m
.tx
= oldBounds
.get_x_max() - _bounds
.width();
1206 m
.transform(_bounds
);
1208 else if (_autoSize
== AUTOSIZE_CENTER
) {
1209 // Autosize center expands from the previous center.
1211 m
.tx
= oldBounds
.get_x_min() + oldBounds
.width() / 2.0 -
1212 _bounds
.width() / 2.0;
1213 m
.transform(_bounds
);
1217 // Add the last line to our output.
1218 _textRecords
.push_back(rec
);
1220 // align the last (or single) line
1221 align_line(getTextAlignment(), last_line_start_record
, x
);
1226 set_invalidated(); //redraw
1231 TextField::scrollLines()
1233 boost::uint16_t fontHeight
= getFontHeight();
1234 float scale
= fontHeight
/
1235 static_cast<float>(_font
->unitsPerEM(_embedFonts
));
1236 float fontLeading
= _font
->leading() * scale
;
1237 _linesindisplay
= _bounds
.height() / (fontHeight
+ fontLeading
+ PADDING_TWIPS
);
1238 if (_linesindisplay
> 0) { //no need to place lines if we can't fit any
1239 size_t manylines
= _line_starts
.size();
1240 size_t lastvisibleline
= _scroll
+ _linesindisplay
;
1243 // If there aren't as many lines as we have scrolled, display the
1245 if (manylines
< _scroll
) {
1246 _scroll
= manylines
- _linesindisplay
;
1250 // which line is the cursor on?
1251 while (line
< manylines
&& _line_starts
[line
] <= m_cursor
) {
1255 if (manylines
- _scroll
<= _linesindisplay
) {
1256 // This is for if we delete a line
1257 if (manylines
< _linesindisplay
) _scroll
= 0;
1259 _scroll
= manylines
- _linesindisplay
;
1261 } else if (line
< _scroll
) {
1262 //if we are at a higher position, scroll the lines down
1263 _scroll
-= _scroll
- line
;
1264 } else if (manylines
> _scroll
+ _linesindisplay
) {
1265 //if we are at a lower position, scroll the lines up
1266 if (line
>= (_scroll
+_linesindisplay
)) {
1267 _scroll
+= line
- (lastvisibleline
);
1274 TextField::newLine(boost::int32_t& x
, boost::int32_t& y
,
1275 SWF::TextRecord
& rec
, int& last_space_glyph
,
1276 LineStarts::value_type
& last_line_start_record
, float div
)
1279 LineStarts::iterator linestartit
= _line_starts
.begin();
1280 LineStarts::const_iterator linestartend
= _line_starts
.end();
1282 float scale
= _fontHeight
/
1283 static_cast<float>(_font
->unitsPerEM(_embedFonts
));
1284 float fontLeading
= _font
->leading() * scale
;
1285 float leading
= getLeading();
1286 leading
+= fontLeading
* scale
; // not sure this is correct...
1288 // Close out this stretch of glyphs.
1290 _textRecords
.push_back(rec
);
1291 _recordStarts
.push_back(_glyphcount
);
1292 align_line(getTextAlignment(), last_line_start_record
, x
);
1294 // Expand bounding box to include last column of text ...
1295 if (!doWordWrap() && _autoSize
!= AUTOSIZE_NONE
) {
1296 _bounds
.expand_to_point(x
+ PADDING_TWIPS
, y
+ PADDING_TWIPS
);
1299 // new paragraphs get the indent.
1300 x
= std::max(0, getLeftMargin() + getIndent() + getBlockIndent()) +
1302 y
+= div
* (getFontHeight() + leading
);
1303 if (y
>= _bounds
.height()) {
1307 // Start a new record on the next line. Other properties of the
1308 // TextRecord should be left unchanged.
1313 last_space_glyph
= -1;
1314 last_line_start_record
= _textRecords
.size();
1316 linestartit
= _line_starts
.begin();
1317 linestartend
= _line_starts
.end();
1318 //Fit a line_start in the correct place
1319 const size_t currentPos
= _glyphcount
;
1321 while (linestartit
< linestartend
&& *linestartit
< currentPos
)
1325 _line_starts
.insert(linestartit
, currentPos
);
1329 // First, we indent 10 spaces, and then place the bullet
1330 // character (in this case, an asterisk), then we pad it
1331 // again with 10 spaces
1332 // Note: this works only for additional lines of a
1333 // bulleted list, so that is why there is a bullet format
1334 // in the beginning of format_text()
1337 int space
= rec
.getFont()->get_glyph_index(32, _embedFonts
);
1338 SWF::TextRecord::GlyphEntry ge
;
1340 ge
.advance
= scale
* rec
.getFont()->get_advance(space
, _embedFonts
);
1345 int bullet
= rec
.getFont()->get_glyph_index(42, _embedFonts
);
1347 ge
.advance
= scale
* rec
.getFont()->get_advance(bullet
, _embedFonts
);
1352 ge
.advance
= scale
* rec
.getFont()->get_advance(space
, _embedFonts
);
1360 TextField::handleChar(std::wstring::const_iterator
& it
,
1361 const std::wstring::const_iterator
& e
, boost::int32_t& x
,
1362 boost::int32_t& y
, SWF::TextRecord
& rec
, int& last_code
,
1363 int& last_space_glyph
, LineStarts::value_type
& last_line_start_record
)
1365 LineStarts::iterator linestartit
= _line_starts
.begin();
1366 LineStarts::const_iterator linestartend
= _line_starts
.end();
1368 float scale
= _fontHeight
/
1369 static_cast<float>(_font
->unitsPerEM(_embedFonts
));
1370 float fontDescent
= _font
->descent(_embedFonts
) * scale
;
1371 float fontLeading
= _font
->leading() * scale
;
1372 float leading
= getLeading();
1373 leading
+= fontLeading
* scale
; // not sure this is correct...
1375 boost::uint32_t code
= 0;
1383 x
+= rec
.getFont()->get_kerning_adjustment(last_code
,
1384 static_cast<int>(code
)) * scale
;
1385 last_code
= static_cast<int>(code
);
1388 // Expand the bounding-box to the lower-right corner of each glyph as
1390 m_text_bounding_box
.expand_to_point(x
, y
+ fontDescent
);
1397 insertTab(rec
, x
, scale
);
1402 // This is a limited hack to enable overstrike effects.
1403 // It backs the cursor up by one DisplayObject and then continues
1404 // the layout. E.g. you can use this to display an underline
1405 // cursor inside a simulated text-entry box.
1407 // ActionScript understands the '\b' escape sequence
1408 // for inserting a BS DisplayObject.
1410 // ONLY WORKS FOR BACKSPACING OVER ONE CHARACTER, WON'T BS
1411 // OVER NEWLINES, ETC.
1413 if (!rec
.glyphs().empty())
1415 // Peek at the previous glyph, and zero out its advance
1416 // value, so the next char overwrites it.
1417 float advance
= rec
.glyphs().back().advance
;
1426 newLine(x
,y
,rec
,last_space_glyph
,last_line_start_record
,1.0);
1432 //close out this stretch of glyphs
1433 _textRecords
.push_back(rec
);
1435 _recordStarts
.push_back(_glyphcount
);
1437 while (it
!= e
&& *it
!= '>') {
1443 LOG_ONCE(log_debug(_("HTML in a text field is unsupported, "
1444 "gnash will just ignore the tags and "
1445 "print their content")));
1447 std::wstring discard
;
1448 std::map
<std::string
,std::string
> attributes
;
1449 SWF::TextRecord newrec
;
1450 newrec
.setFont(rec
.getFont());
1451 newrec
.setUnderline(rec
.underline());
1452 newrec
.setColor(rec
.color());
1453 newrec
.setTextHeight(rec
.textHeight());
1454 newrec
.setXOffset(x
);
1455 newrec
.setYOffset(y
);
1456 bool selfclosing
= false;
1457 bool complete
= parseHTML(discard
, attributes
, it
, e
, selfclosing
);
1458 std::string
s(discard
.begin(), discard
.end());
1459 s
.assign(discard
.begin(), discard
.end());
1461 std::map
<std::string
,std::string
>::const_iterator attloc
;
1464 //parsing went wrong
1467 // Don't think this is the best way to match with
1469 // TODO: assumes tags are properly nested. This isn't
1473 newrec
.setUnderline(true);
1474 handleChar(it
, e
, x
, y
, newrec
, last_code
,
1475 last_space_glyph
, last_line_start_record
);
1477 else if (s
== "A") {
1478 // anchor (blue text).
1479 rgba
color(0, 0, 0xff, 0xff);
1480 newrec
.setColor(color
);
1481 newrec
.setUnderline(true);
1482 attloc
= attributes
.find("HREF");
1483 if (attloc
!= attributes
.end()) {
1484 newrec
.setURL(attloc
->second
);
1486 attloc
= attributes
.find("TARGET");
1487 if (attloc
!=attributes
.end()) {
1488 newrec
.setTarget(attloc
->second
);
1490 handleChar(it
, e
, x
, y
, newrec
, last_code
,
1491 last_space_glyph
, last_line_start_record
);
1493 else if (s
== "B") {
1495 Font
* boldfont
= new Font(rec
.getFont()->name(),
1496 true, rec
.getFont()->isItalic());
1497 newrec
.setFont(boldfont
);
1498 handleChar(it
, e
, x
, y
, newrec
, last_code
,
1499 last_space_glyph
, last_line_start_record
);
1501 else if (s
== "FONT") {
1503 boost::uint16_t originalsize
= _fontHeight
;
1504 attloc
= attributes
.find("COLOR");
1505 if (attloc
!= attributes
.end()) {
1506 std::string
hexval(attloc
->second
);
1507 if (hexval
.empty() || hexval
[0] != '#') {
1508 log_error("Unexpected color value");
1512 // font COLOR attribute
1514 colorFromHexString(hexval
);
1515 newrec
.setColor(color
);
1518 attloc
= attributes
.find("FACE");
1519 if (attloc
!= attributes
.end()) {
1520 //font FACE attribute
1521 Font
* newfont
= new Font(attloc
->second
,
1522 rec
.getFont()->isBold(), rec
.getFont()->isItalic());
1523 newrec
.setFont(newfont
);
1525 attloc
= attributes
.find("SIZE");
1526 if (attloc
!= attributes
.end()) {
1527 //font SIZE attribute
1528 std::string firstchar
= attloc
->second
.substr(0,1);
1529 if (firstchar
== "+") {
1530 newrec
.setTextHeight(rec
.textHeight() +
1532 (pixelsToTwips(std::strtol(
1533 attloc
->second
.substr(1,attloc
->second
.length()-1).data(),
1535 newrec
.setYOffset(PADDING_TWIPS
+
1536 newrec
.textHeight() +
1537 (fontLeading
- fontDescent
));
1538 _fontHeight
+= pixelsToTwips(std::strtol(
1539 attloc
->second
.substr(1,attloc
->second
.length()-1).data(),
1541 } else if (firstchar
== "-") {
1542 newrec
.setTextHeight(rec
.textHeight() -
1543 (pixelsToTwips(std::strtol(
1544 attloc
->second
.substr(1,attloc
->second
.length()-1).data(),
1546 newrec
.setYOffset(PADDING_TWIPS
+
1547 newrec
.textHeight() +
1548 (fontLeading
- fontDescent
));
1549 _fontHeight
-= pixelsToTwips(std::strtol(
1550 attloc
->second
.substr(1,attloc
->second
.length()-1).data(),
1553 newrec
.setTextHeight(pixelsToTwips(std::strtol(
1554 attloc
->second
.data(), NULL
, 10)));
1555 newrec
.setYOffset(PADDING_TWIPS
+ newrec
.textHeight() +
1556 (fontLeading
- fontDescent
));
1557 _fontHeight
= pixelsToTwips(std::strtol(
1558 attloc
->second
.data(), NULL
, 10));
1561 handleChar(it
, e
, x
, y
, newrec
, last_code
,
1562 last_space_glyph
, last_line_start_record
);
1563 _fontHeight
= originalsize
;
1564 y
= newrec
.yOffset();
1566 else if (s
== "IMG") {
1568 log_unimpl("<img> html tag in TextField");
1569 handleChar(it
, e
, x
, y
, newrec
, last_code
,
1570 last_space_glyph
, last_line_start_record
);
1572 else if (s
== "I") {
1574 Font
* italicfont
= new Font(rec
.getFont()->name(),
1575 rec
.getFont()->isBold(), true);
1576 newrec
.setFont(italicfont
);
1577 handleChar(it
, e
, x
, y
, newrec
, last_code
,
1578 last_space_glyph
, last_line_start_record
);
1579 } else if (s
== "LI") {
1580 //list item (bullet)
1581 int space
= newrec
.getFont()->get_glyph_index(32, _embedFonts
);
1582 SWF::TextRecord::GlyphEntry ge
;
1584 ge
.advance
= scale
* newrec
.getFont()->get_advance(space
, _embedFonts
);
1585 newrec
.addGlyph(ge
, 5);
1587 // We use an asterisk instead of a bullet
1588 int bullet
= newrec
.getFont()->get_glyph_index(42, _embedFonts
);
1590 ge
.advance
= scale
* newrec
.getFont()->get_advance(bullet
, _embedFonts
);
1591 newrec
.addGlyph(ge
);
1593 space
= newrec
.getFont()->get_glyph_index(32, _embedFonts
);
1595 ge
.advance
= scale
* newrec
.getFont()->get_advance(space
, _embedFonts
);
1596 newrec
.addGlyph(ge
, 4);
1598 handleChar(it
, e
, x
, y
, newrec
, last_code
,
1599 last_space_glyph
, last_line_start_record
);
1600 newLine(x
, y
, newrec
, last_space_glyph
,
1601 last_line_start_record
, 1.0);
1603 else if (s
== "SPAN") {
1605 log_unimpl("<span> html tag in TextField");
1606 handleChar(it
, e
, x
, y
, newrec
, last_code
,
1607 last_space_glyph
, last_line_start_record
);
1609 else if (s
== "TEXTFORMAT") {
1610 log_debug("in textformat");
1612 boost::uint16_t originalblockindent
= getBlockIndent();
1613 boost::uint16_t originalindent
= getIndent();
1614 boost::uint16_t originalleading
= getLeading();
1615 boost::uint16_t originalleftmargin
= getLeftMargin();
1616 boost::uint16_t originalrightmargin
= getRightMargin();
1617 std::vector
<int> originaltabstops
= getTabStops();
1618 attloc
= attributes
.find("BLOCKINDENT");
1619 if (attloc
!= attributes
.end()) {
1620 //textformat BLOCKINDENT attribute
1621 setBlockIndent(pixelsToTwips(std::strtol(
1622 attloc
->second
.data(), NULL
, 10)));
1623 if (newrec
.xOffset() == std::max(0, originalleftmargin
+
1624 originalindent
+ originalblockindent
) + PADDING_TWIPS
) {
1625 //if beginning of line, indent
1626 x
= std::max(0, getLeftMargin() +
1627 getIndent() + getBlockIndent())
1629 newrec
.setXOffset(x
);
1632 attloc
= attributes
.find("INDENT");
1633 if (attloc
!= attributes
.end()) {
1634 //textformat INDENT attribute
1635 setIndent(pixelsToTwips(std::strtol(
1636 attloc
->second
.data(), NULL
, 10)));
1637 if (newrec
.xOffset() == std::max(0, originalleftmargin
+
1638 originalindent
+ getBlockIndent()) + PADDING_TWIPS
) {
1639 //if beginning of line, indent
1640 x
= std::max(0, getLeftMargin() +
1641 getIndent() + getBlockIndent())
1643 newrec
.setXOffset(x
);
1646 attloc
= attributes
.find("LEADING");
1647 if (attloc
!= attributes
.end()) {
1648 //textformat LEADING attribute
1649 setLeading(pixelsToTwips(std::strtol(
1650 attloc
->second
.data(), NULL
, 10)));
1652 attloc
= attributes
.find("LEFTMARGIN");
1653 if (attloc
!= attributes
.end()) {
1654 //textformat LEFTMARGIN attribute
1655 setLeftMargin(pixelsToTwips(std::strtol(
1656 attloc
->second
.data(), NULL
, 10)));
1657 if (newrec
.xOffset() == std::max(0, originalleftmargin
+
1658 getIndent() + getBlockIndent()) + PADDING_TWIPS
) {
1659 //if beginning of line, indent
1660 x
= std::max(0, getLeftMargin() +
1661 getIndent() + getBlockIndent())
1663 newrec
.setXOffset(x
);
1666 attloc
= attributes
.find("RIGHTMARGIN");
1667 if (attloc
!= attributes
.end()) {
1668 //textformat RIGHTMARGIN attribute
1669 setRightMargin(pixelsToTwips(std::strtol(
1670 attloc
->second
.data(), NULL
, 10)));
1671 //FIXME:Should not apply this to this line if we are not at
1672 //beginning of line. Not sure how to do that.
1674 attloc
= attributes
.find("TABSTOPS");
1675 if (attloc
!= attributes
.end()) {
1676 //textformat TABSTOPS attribute
1677 log_unimpl("html <textformat> tag tabstops attribute");
1679 handleChar(it
, e
, x
, y
, newrec
, last_code
,
1680 last_space_glyph
, last_line_start_record
);
1681 setBlockIndent(originalblockindent
);
1682 setIndent(originalindent
);
1683 setLeading(originalleading
);
1684 setLeftMargin(originalleftmargin
);
1685 setRightMargin(originalrightmargin
);
1686 setTabStops(originaltabstops
);
1688 else if (s
== "P") {
1690 if (_display
== TEXTFORMAT_BLOCK
) {
1691 handleChar(it
, e
, x
, y
, newrec
, last_code
,
1693 last_line_start_record
);
1694 newLine(x
, y
, rec
, last_space_glyph
,
1695 last_line_start_record
, 1.0);
1696 newLine(x
, y
, rec
, last_space_glyph
,
1697 last_line_start_record
, 1.5);
1700 handleChar(it
, e
, x
, y
, newrec
, last_code
,
1702 last_line_start_record
);
1705 else if (s
== "BR" || s
== "SBR") {
1707 newLine(x
, y
, rec
, last_space_glyph
,
1708 last_line_start_record
, 1.0);
1711 log_debug("<%s> tag is unsupported", s
);
1712 if (!selfclosing
) { //then recurse, look for closing tag
1713 handleChar(it
, e
, x
, y
, newrec
, last_code
,
1714 last_space_glyph
, last_line_start_record
);
1722 // If HTML isn't enabled, carry on and insert the glyph.
1723 // FIXME: do we also want to be changing last_space_glyph?
1724 // ...because we are...
1726 last_space_glyph
= rec
.glyphs().size();
1727 // Don't break, as we still need to insert the space glyph.
1736 SWF::TextRecord::GlyphEntry ge
;
1737 int bullet
= rec
.getFont()->get_glyph_index(42, _embedFonts
);
1739 ge
.advance
= scale
* rec
.getFont()->get_advance(bullet
,
1745 // The font table holds up to 65535 glyphs. Casting
1746 // from uint32_t would, in the event that the code
1747 // is higher than 65535, result in the wrong DisplayObject
1748 // being chosen. Flash can currently only handle 16-bit
1750 int index
= rec
.getFont()->get_glyph_index(
1751 static_cast<boost::uint16_t>(code
), _embedFonts
);
1753 IF_VERBOSE_MALFORMED_SWF (
1756 // Missing glyph! Log the first few errors.
1757 static int s_log_count
= 0;
1758 if (s_log_count
< 10)
1763 log_swferror(_("TextField: missing embedded "
1764 "glyph for char %d. Make sure DisplayObject "
1765 "shapes for font %s are being exported "
1766 "into your SWF file"),
1767 code
, _font
->name());
1771 log_swferror(_("TextField: missing device "
1772 "glyph for char %d. Maybe you don't have "
1773 "font '%s' installed in your system."),
1774 code
, _font
->name());
1778 // Drop through and use index == -1; this will display
1779 // using the empty-box glyph
1783 SWF::TextRecord::GlyphEntry ge
;
1785 ge
.advance
= scale
* rec
.getFont()->get_advance(index
,
1795 float width
= _bounds
.width();
1796 if (x
>= width
- getRightMargin() - PADDING_TWIPS
)
1798 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1799 log_debug("Text in TextField %s exceeds width [ _bounds %s ]",
1800 getTarget(), _bounds
);
1803 // No wrap and no resize: truncate
1804 if (!doWordWrap() && getAutoSize() == AUTOSIZE_NONE
)
1806 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1807 log_debug(" wordWrap=false, autoSize=none");
1809 // Truncate long line, but keep expanding text box
1810 bool newlinefound
= false;
1816 x
+= rec
.getFont()->get_kerning_adjustment(last_code
,
1817 static_cast<int>(code
)) * scale
;
1820 // Expand the bounding-box to the lower-right corner
1821 // of each glyph, even if we don't display it
1822 m_text_bounding_box
.expand_to_point(x
, y
+ fontDescent
);
1823 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1824 log_debug("Text bbox expanded to %s (width: %f)",
1825 m_text_bounding_box
, m_text_bounding_box
.width());
1828 if (code
== 13 || code
== 10)
1830 newlinefound
= true;
1834 int index
= rec
.getFont()->get_glyph_index(
1835 static_cast<boost::uint16_t>(code
), _embedFonts
);
1836 x
+= scale
* rec
.getFont()->get_advance(index
, _embedFonts
);
1839 if (!newlinefound
) break;
1841 else if (doWordWrap()) {
1843 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1844 log_debug(" wordWrap=true");
1847 // Insert newline if there's space or autosize != none
1849 // Close out this stretch of glyphs.
1850 _textRecords
.push_back(rec
);
1852 float previous_x
= x
;
1853 x
= std::max(0, getLeftMargin() + getBlockIndent()) + PADDING_TWIPS
;
1854 y
+= _fontHeight
+ leading
;
1855 if (y
>= _bounds
.height()) {
1859 // Start a new record on the next line.
1864 // TODO : what if m_text_glyph_records is empty ?
1866 assert(!_textRecords
.empty());
1867 SWF::TextRecord
& last_line
= _textRecords
.back();
1869 linestartit
= _line_starts
.begin();
1870 linestartend
= _line_starts
.end();
1871 if (last_space_glyph
== -1)
1873 // Pull the previous glyph down onto the
1875 if (!last_line
.glyphs().empty())
1877 rec
.addGlyph(last_line
.glyphs().back());
1878 x
+= last_line
.glyphs().back().advance
;
1879 previous_x
-= last_line
.glyphs().back().advance
;
1880 last_line
.clearGlyphs(1);
1881 //record the new line start
1883 const size_t currentPos
= _glyphcount
;
1884 while (linestartit
!= linestartend
&&
1885 *linestartit
+ 1 <= currentPos
)
1889 _line_starts
.insert(linestartit
, currentPos
);
1890 _recordStarts
.push_back(currentPos
);
1893 // Move the previous word down onto the next line.
1895 previous_x
-= last_line
.glyphs()[last_space_glyph
].advance
;
1897 const SWF::TextRecord::Glyphs::size_type lineSize
=
1898 last_line
.glyphs().size();
1899 for (unsigned int i
= last_space_glyph
+ 1; i
< lineSize
;
1902 rec
.addGlyph(last_line
.glyphs()[i
]);
1903 x
+= last_line
.glyphs()[i
].advance
;
1904 previous_x
-= last_line
.glyphs()[i
].advance
;
1906 last_line
.clearGlyphs(lineSize
- last_space_glyph
);
1908 // record the position at the start of this line as
1910 const size_t linestartpos
= _glyphcount
-
1911 rec
.glyphs().size();
1913 while (linestartit
< linestartend
&&
1914 *linestartit
< linestartpos
)
1918 _line_starts
.insert(linestartit
, linestartpos
);
1919 _recordStarts
.push_back(linestartpos
);
1922 align_line(getTextAlignment(), last_line_start_record
, previous_x
);
1924 last_space_glyph
= -1;
1925 last_line_start_record
= _textRecords
.size();
1930 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1931 log_debug(" wordWrap=%d, autoSize=%d", _wordWrap
, _autoSize
);
1939 TextField::getDefinitionVersion() const
1941 // TODO: work out if this correct.
1942 return get_root()->getDefinitionVersion();
1946 TextField::VariableRef
1947 TextField::parseTextVariableRef(const std::string
& variableName
) const
1952 #ifdef DEBUG_DYNTEXT_VARIABLES
1953 log_debug(_("VariableName: %s"), variableName
);
1956 /// Why isn't get_environment const again ?
1957 as_environment
& env
= const_cast<TextField
*>(this)->get_environment();
1959 as_object
* target
= getObject(env
.get_target());
1962 IF_VERBOSE_MALFORMED_SWF(
1963 log_swferror(_("Current environment has no target, "
1964 "can't bind VariableName (%s) associated to "
1965 "text field. Gnash will try to register "
1966 "again on next access."), variableName
);
1971 // If the variable string contains a path, we extract
1972 // the appropriate target from it and update the variable
1973 // name. We copy the string so we can assign to it if necessary.
1974 std::string parsedName
= variableName
;
1975 std::string path
, var
;
1976 if (parsePath(variableName
, path
, var
))
1978 #ifdef DEBUG_DYNTEXT_VARIABLES
1979 log_debug(_("Variable text Path: %s, Var: %s"), path
, var
);
1981 // find target for the path component
1982 // we use our parent's environment for this
1983 target
= env
.find_object(path
);
1990 IF_VERBOSE_MALFORMED_SWF(
1991 log_swferror(_("VariableName associated to text field refers "
1992 "to an unknown target (%s). It is possible that the "
1993 "DisplayObject will be instantiated later in the SWF "
1994 "stream. Gnash will try to register again on next "
2001 ret
.second
= getStringTable(*object()).find(parsedName
);
2007 TextField::registerTextVariable()
2009 //#define DEBUG_DYNTEXT_VARIABLES 1
2011 #ifdef DEBUG_DYNTEXT_VARIABLES
2012 log_debug(_("registerTextVariable() called"));
2015 if (_text_variable_registered
) {
2016 #ifdef DEBUG_DYNTEXT_VARIABLES
2017 log_debug(_("registerTextVariable() no-op call (already registered)"));
2022 if (_variable_name
.empty()) {
2023 #ifdef DEBUG_DYNTEXT_VARIABLES
2024 log_debug(_("string is empty, consider as registered"));
2026 _text_variable_registered
=true;
2030 VariableRef varRef
= parseTextVariableRef(_variable_name
);
2031 as_object
* target
= varRef
.first
;
2033 log_debug(_("VariableName associated to text field (%s) refer to "
2034 "an unknown target. It is possible that the DisplayObject "
2035 "will be instantiated later in the SWF stream. "
2036 "Gnash will try to register again on next access."),
2041 const string_table::key key
= varRef
.second
;
2042 as_object
* obj
= getObject(this);
2043 const int version
= getSWFVersion(*obj
);
2044 string_table
& st
= getStringTable(*obj
);
2046 // check if the VariableName already has a value,
2047 // in that case update text value
2049 if (target
->get_member(key
, &val
) ) {
2050 #ifdef DEBUG_DYNTEXT_VARIABLES
2051 log_debug(_("target object (%s @ %p) does have a member named %s"),
2052 typeName(*target
), (void*)target
, st
.value(key
));
2054 // TODO: pass environment to to_string ?
2055 // as_environment& env = get_environment();
2056 setTextValue(utf8::decodeCanonicalString(val
.to_string(), version
));
2058 else if (_textDefined
) {
2059 as_value newVal
= as_value(utf8::encodeCanonicalString(_text
, version
));
2060 #ifdef DEBUG_DYNTEXT_VARIABLES
2061 log_debug(_("target sprite (%s @ %p) does NOT have a member "
2062 "named %s (no problem, we'll add it with value %s)"),
2063 typeName(*target
), (void*)target
,
2064 st
.value(key
), newVal
);
2066 target
->set_member(key
, newVal
);
2069 #ifdef DEBUG_DYNTEXT_VARIABLES
2070 log_debug(_("target sprite (%s @ %p) does NOT have a member "
2071 "named %s, and we don't have text defined"),
2072 typeName(*target
), (void*)target
, st
.value(key
));
2076 MovieClip
* sprite
= get
<MovieClip
>(target
);
2079 // add the textfield variable to the target sprite
2080 // TODO: have set_textfield_variable take a string_table::key instead ?
2081 #ifdef DEBUG_DYNTEXT_VARIABLES
2082 log_debug("Calling set_textfield_variable(%s) against sprite %s",
2083 st
.value(key
), sprite
->getTarget());
2085 sprite
->set_textfield_variable(st
.value(key
), this);
2088 _text_variable_registered
=true;
2092 /// Parses an HTML tag (between < and >) and puts
2093 /// the contents into tag. Returns false if the
2094 /// tag was incomplete. The iterator is moved to after
2095 /// the closing tag or the end of the string.
2097 TextField::parseHTML(std::wstring
& tag
,
2098 std::map
<std::string
, std::string
>& attributes
,
2099 std::wstring::const_iterator
& it
,
2100 const std::wstring::const_iterator
& e
,
2101 bool& selfclosing
) const
2103 std::string attname
;
2104 std::string attvalue
;
2105 while (it
!= e
&& *it
!= ' ') {
2116 log_error("invalid html tag");
2125 // Check for NULL character
2127 log_error("found NULL character in htmlText");
2130 tag
.push_back(std::toupper(*it
));
2133 while (it
!= e
&& *it
== ' ') {
2134 ++it
; //skip over spaces
2150 log_error("invalid html tag");
2155 while (it
!= e
&& *it
!= '>') {
2156 while (it
!= e
&& *it
!= '=' && *it
!= ' ') {
2159 log_error("found NULL character in htmlText");
2163 log_error("malformed HTML tag, invalid attribute name");
2170 attname
.push_back(std::toupper(*it
));
2173 while (it
!= e
&& (*it
== ' ' || *it
== '=')) {
2174 ++it
; //skip over spaces and '='
2177 if (*it
!= '"') { //make sure attribute value is opened with '"'
2178 log_error("attribute value must be opened with \'\"\' "
2179 "(did you remember escape char?)");
2188 while (it
!= e
&& *it
!= '"') { //get attribute value
2191 log_error("found NULL character in htmlText");
2195 attvalue
.push_back(std::toupper(*it
));
2199 if (*it
!= '"') { //make sure attribute value is closed with '"'
2200 log_error("attribute value must be closed with \'\"\' "
2201 "(did you remember escape char?)");
2210 attributes
.insert(std::make_pair(attname
, attvalue
));
2213 if ((*it
!= ' ') && (*it
!= '/') && (*it
!= '>')) {
2214 log_error("malformed HTML tag, invalid attribute value");
2221 while (it
!= e
&& *it
== ' ') {
2222 ++it
; //skip over spaces
2228 } else if (*it
== '/') {
2238 log_error("invalid html tag");
2244 #ifdef GNASH_DEBUG_TEXTFIELDS
2245 log_debug ("HTML tag: %s", utf8::encodeCanonicalString(tag
, 7));
2247 log_error("I declare this a HTML syntax error");
2248 return false; //since we did not return already, must be malformed...?
2252 TextField::set_variable_name(const std::string
& newname
)
2254 if ( newname
!= _variable_name
)
2256 _variable_name
= newname
;
2258 // The name was empty or undefined, so there's nothing more to do.
2259 if (_variable_name
.empty()) return;
2261 _text_variable_registered
= false;
2263 #ifdef DEBUG_DYNTEXT_VARIABLES
2264 log_debug("Calling updateText after change of variable name");
2267 // Use the original definition text if this isn't dynamically
2269 if (_tag
) updateText(_tag
->defaultText());
2271 #ifdef DEBUG_DYNTEXT_VARIABLES
2272 log_debug("Calling registerTextVariable after change of variable "
2273 "name and updateText call");
2275 registerTextVariable();
2280 TextField::pointInShape(boost::int32_t x
, boost::int32_t y
) const
2282 const SWFMatrix wm
= getWorldMatrix(*this).invert();
2285 return _bounds
.point_test(lp
.x
, lp
.y
);
2289 TextField::getDrawBorder() const
2295 TextField::setDrawBorder(bool val
)
2297 if ( _drawBorder
!= val
)
2305 TextField::getBorderColor() const
2307 return _borderColor
;
2311 TextField::setBorderColor(const rgba
& col
)
2313 if ( _borderColor
!= col
)
2321 TextField::getDrawBackground() const
2323 return _drawBackground
;
2327 TextField::setDrawBackground(bool val
)
2329 if ( _drawBackground
!= val
)
2332 _drawBackground
= val
;
2337 TextField::getBackgroundColor() const
2339 return _backgroundColor
;
2343 TextField::setBackgroundColor(const rgba
& col
)
2345 if ( _backgroundColor
!= col
)
2348 _backgroundColor
= col
;
2353 TextField::setTextColor(const rgba
& col
)
2355 if (_textColor
!= col
) {
2359 std::for_each(_displayRecords
.begin(), _displayRecords
.end(),
2360 boost::bind(&SWF::TextRecord::setColor
, _1
, _textColor
));
2365 TextField::setEmbedFonts(bool use
)
2367 if ( _embedFonts
!= use
)
2376 TextField::setWordWrap(bool wrap
)
2378 if (_wordWrap
!= wrap
) {
2387 TextField::setLeading(boost::int16_t h
)
2389 if ( _leading
!= h
)
2397 TextField::setUnderlined(bool v
)
2399 if ( _underlined
!= v
)
2407 TextField::setBullet(bool b
)
2415 TextField::setTabStops(const std::vector
<int>& tabStops
)
2417 _tabStops
.resize(tabStops
.size());
2419 for (size_t i
= 0; i
< tabStops
.size(); i
++) {
2420 _tabStops
[i
] = pixelsToTwips(tabStops
[i
]);
2427 TextField::setURL(std::string url
)
2429 if ( _url
!= url
) {
2436 TextField::setTarget(std::string target
)
2438 if ( _target
!= target
)
2446 TextField::setDisplay(TextFormatDisplay display
)
2448 if ( _display
!= display
)
2456 TextField::setAlignment(TextAlignment h
)
2458 if ( _alignment
!= h
)
2466 TextField::setIndent(boost::uint16_t h
)
2476 TextField::setBlockIndent(boost::uint16_t h
)
2478 if ( _blockIndent
!= h
)
2486 TextField::setRightMargin(boost::uint16_t h
)
2488 if ( _rightMargin
!= h
)
2496 TextField::setLeftMargin(boost::uint16_t h
)
2498 if (_leftMargin
!= h
)
2506 TextField::setFontHeight(boost::uint16_t h
)
2508 if ( _fontHeight
!= h
)
2516 TextField::TypeValue
2517 TextField::parseTypeValue(const std::string
& val
)
2519 StringNoCaseEqual cmp
;
2521 if (cmp(val
, "input")) return typeInput
;
2522 if (cmp(val
, "dynamic")) return typeDynamic
;
2529 TextField::typeValueName(TypeValue val
)
2534 //log_debug("typeInput returned as 'input'");
2537 //log_debug("typeDynamic returned as 'dynamic'");
2540 //log_debug("invalid type %d returned as 'invalid'", (int)val);
2547 TextField::setAutoSize(AutoSize val
)
2549 if ( val
== _autoSize
) return;
2557 TextField::TextAlignment
2558 TextField::getTextAlignment()
2560 TextAlignment textAlignment
= getAlignment();
2562 switch (_autoSize
) {
2563 case AUTOSIZE_CENTER
:
2564 textAlignment
= ALIGN_CENTER
;
2567 textAlignment
= ALIGN_LEFT
;
2569 case AUTOSIZE_RIGHT
:
2570 textAlignment
= ALIGN_RIGHT
;
2573 // Leave it as it was.
2577 return textAlignment
;
2581 TextField::onChanged()
2583 as_object
* obj
= getObject(this);
2584 callMethod(obj
, NSV::PROP_BROADCAST_MESSAGE
, "onChanged", obj
);
2587 /// This is called by movie_root when focus is applied to this TextField.
2589 /// The return value is true if the TextField can receive focus.
2590 /// The swfdec testsuite suggests that version 5 textfields cannot ever
2593 TextField::handleFocus()
2598 /// Select the entire text on focus.
2599 setSelection(0, _text
.length());
2603 m_cursor
= _text
.size();
2608 /// This is called by movie_root when focus is removed from the
2609 /// current TextField.
2611 TextField::killFocus()
2613 if ( ! m_has_focus
) return; // nothing to do
2616 m_has_focus
= false;
2618 format_text(); // is this needed ?
2623 TextField::setWidth(double newwidth
)
2625 const SWFRect
& bounds
= getBounds();
2626 _bounds
.set_to_rect(bounds
.get_x_min(),
2628 bounds
.get_x_min() + newwidth
,
2629 bounds
.get_y_max());
2633 TextField::setHeight(double newheight
)
2635 const SWFRect
& bounds
= getBounds();
2636 _bounds
.set_to_rect(bounds
.get_x_min(),
2639 bounds
.get_y_min() + newheight
);
2642 /// TextField interface functions
2645 } // namespace gnash
2650 // indent-tabs-mode: t