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"
51 #include "ObjectURI.h"
55 #include <boost/assign/list_of.hpp>
56 #include <boost/bind.hpp>
62 // Text fields have a fixed 2 pixel padding for each side (regardless of border)
63 #define PADDING_TWIPS 40
65 // Define the following to get detailed log information about
66 // textfield bounds and HTML tags:
67 //#define GNASH_DEBUG_TEXTFIELDS 1
69 // Define this to get debugging info about text formatting
70 //#define GNASH_DEBUG_TEXT_FORMATTING 1
74 TextField::TextField(as_object
* object
, DisplayObject
* parent
,
75 const SWF::DefineEditTextTag
& def
)
77 InteractiveObject(object
, parent
),
83 _variable_name(def
.variableName()),
84 _backgroundColor(255,255,255,255),
85 _borderColor(0,0,0,255),
86 _textColor(def
.color()),
87 _alignment(def
.alignment()),
97 _maxChars(def
.maxChars()),
98 _autoSize(def
.autoSize() ? AUTOSIZE_LEFT
: AUTOSIZE_NONE
),
99 _type(def
.readOnly() ? typeDynamic
: typeInput
),
100 _bounds(def
.bounds()),
102 _leading(def
.leading()),
103 _indent(def
.indent()),
105 _leftMargin(def
.leftMargin()),
106 _rightMargin(def
.rightMargin()),
107 _fontHeight(def
.textHeight()),
108 _textDefined(def
.hasText()),
109 _htmlTextDefined(def
.hasText()),
110 _restrictDefined(false),
114 _multiline(def
.multiline()),
115 _password(def
.password()),
116 _text_variable_registered(false),
117 _drawBackground(def
.border()),
118 _drawBorder(def
.border()),
119 _embedFonts(def
.getUseEmbeddedGlyphs()),
120 _wordWrap(def
.wordWrap()),
122 _selectable(!def
.noSelect())
127 // WARNING! remember to set the font *before* setting text value!
128 boost::intrusive_ptr
<const Font
> f
= def
.getFont();
129 if (!f
) f
= fontlib::get_default_font();
132 const int version
= getSWFVersion(*object
);
134 // set default text *before* calling registerTextVariable
135 // (if the textvariable already exist and has a value
136 // the text will be replaced with it)
139 setTextValue(utf8::decodeCanonicalString(def
.defaultText(), version
));
140 setHtmlTextValue(utf8::decodeCanonicalString(def
.defaultText(), version
));
147 TextField::TextField(as_object
* object
, DisplayObject
* parent
,
148 const SWFRect
& bounds
)
150 InteractiveObject(object
, parent
),
155 _backgroundColor(255,255,255,255),
156 _borderColor(0, 0, 0, 255),
157 _textColor(0, 0, 0, 255),
158 _alignment(ALIGN_LEFT
),
169 _autoSize(AUTOSIZE_NONE
),
178 _fontHeight(12 * 20),
180 _htmlTextDefined(false),
181 _restrictDefined(false),
187 _text_variable_registered(false),
188 _drawBackground(false),
195 // Use the default font (Times New Roman for Windows, Times for Mac
196 // according to docs. They don't say what it is for Linux.
197 boost::intrusive_ptr
<const Font
> f
= fontlib::get_default_font();
206 registerTextVariable();
208 reset_bounding_box(0, 0);
212 TextField::~TextField()
217 TextField::removeTextField()
219 int depth
= get_depth();
220 if ( depth
< 0 || depth
> 1048575 )
222 //IF_VERBOSE_ASCODING_ERRORS(
223 log_debug(_("CHECKME: removeTextField(%s): TextField depth (%d) "
224 "out of the 'dynamic' zone [0..1048575], won't remove"),
230 DisplayObject
* p
= parent();
231 assert(p
); // every TextField must have a parent, right ?
233 MovieClip
* parentSprite
= p
->to_movie();
236 log_error("FIXME: attempt to remove a TextField being a child of a %s",
241 // second argument is arbitrary, see comments above
242 // the function declaration in MovieClip.h
243 parentSprite
->remove_display_object(depth
, 0);
247 TextField::show_cursor(Renderer
& renderer
, const SWFMatrix
& mat
)
249 if (_textRecords
.empty()) {
255 size_t i
= cursorRecord();
256 SWF::TextRecord record
= _textRecords
[i
];
258 x
= record
.xOffset();
259 y
= record
.yOffset() - record
.textHeight() + getLeading();
260 h
= record
.textHeight();
262 if (!record
.glyphs().empty()) {
263 for (unsigned int p
= 0 ; p
< (m_cursor
- _recordStarts
[i
]); ++p
) {
264 x
+= record
.glyphs()[p
].advance
;
268 const std::vector
<point
> box
= boost::assign::list_of
272 renderer
.drawLine(box
, rgba(0, 0, 0, 255), mat
);
276 TextField::cursorRecord()
278 SWF::TextRecord record
;
281 if (_textRecords
.size() != 0) {
282 while (i
< _textRecords
.size() && m_cursor
>= _recordStarts
[i
]) {
291 TextField::display(Renderer
& renderer
, const Transform
& base
)
293 const DisplayObject::MaskRenderer
mr(renderer
, *this);
295 registerTextVariable();
297 const bool drawBorder
= getDrawBorder();
298 const bool drawBackground
= getDrawBackground();
300 Transform xform
= base
* transform();
302 // This is a hack to handle device fonts, which are not affected by
304 if (!getEmbedFonts()) xform
.colorTransform
= SWFCxForm();
306 if ((drawBorder
|| drawBackground
) && !_bounds
.is_null()) {
308 std::vector
<point
> coords(4);
310 boost::int32_t xmin
= _bounds
.get_x_min();
311 boost::int32_t xmax
= _bounds
.get_x_max();
312 boost::int32_t ymin
= _bounds
.get_y_min();
313 boost::int32_t ymax
= _bounds
.get_y_max();
315 coords
[0].setTo(xmin
, ymin
);
316 coords
[1].setTo(xmax
, ymin
);
317 coords
[2].setTo(xmax
, ymax
);
318 coords
[3].setTo(xmin
, ymax
);
320 rgba borderColor
= drawBorder
? getBorderColor() : rgba(0,0,0,0);
321 rgba backgroundColor
= drawBackground
? getBackgroundColor() :
324 SWFCxForm cx
= xform
.colorTransform
;
326 if (drawBorder
) borderColor
= cx
.transform(borderColor
);
328 if (drawBackground
) backgroundColor
= cx
.transform(backgroundColor
);
330 #ifdef GNASH_DEBUG_TEXTFIELDS
331 log_debug("rendering a Pol composed by corners %s", _bounds
);
334 renderer
.draw_poly(&coords
.front(), 4, backgroundColor
,
335 borderColor
, xform
.matrix
, true);
339 // Draw our actual text.
340 // Using a SWFMatrix to translate to def bounds seems an hack to me.
341 // A cleaner implementation is likely correctly setting the
342 // _xOffset and _yOffset memebers in glyph records.
343 // Anyway, see bug #17954 for a testcase.
344 if (!_bounds
.is_null()) {
345 xform
.matrix
.concatenate_translation(_bounds
.get_x_min(),
346 _bounds
.get_y_min());
349 _displayRecords
.clear();
350 float scale
= getFontHeight() /
351 static_cast<float>(_font
->unitsPerEM(_embedFonts
));
352 float fontLeading
= _font
->leading() * scale
;
355 int yoffset
= (getFontHeight() + fontLeading
) + PADDING_TWIPS
;
357 for (size_t i
= 0; i
< _textRecords
.size(); ++i
) {
359 //find the line the record is on
360 while (recordline
< _line_starts
.size() &&
361 _line_starts
[recordline
] <= _recordStarts
[i
]) {
365 _textRecords
[i
].setYOffset((recordline
-_scroll
)*yoffset
);
366 //add the lines we want to the display record
367 if (_textRecords
[i
].yOffset() > 0 &&
368 _textRecords
[i
].yOffset() < _bounds
.height()) {
369 _displayRecords
.push_back(_textRecords
[i
]);
373 SWF::TextRecord::displayRecords(renderer
, xform
, _displayRecords
,
376 if (m_has_focus
&& !isReadOnly()) show_cursor(renderer
, xform
.matrix
);
383 TextField::add_invalidated_bounds(InvalidatedRanges
& ranges
, bool force
)
385 if (!force
&& !invalidated()) return; // no need to redraw
387 ranges
.add(m_old_invalidated_ranges
);
389 const SWFMatrix
& wm
= getWorldMatrix(*this);
391 SWFRect bounds
= getBounds();
392 bounds
.expand_to_rect(m_text_bounding_box
);
393 wm
.transform(bounds
);
394 ranges
.add(bounds
.getRange());
398 TextField::setRestrict(const std::string
& restrict
)
400 _restrictDefined
= true;
402 std::string::const_iterator rit
= restrict
.begin();
403 std::string::const_iterator re
= restrict
.end();
404 std::set
<wchar_t>::const_iterator locate
;
406 if (*rit
== '^') { //then this is a true RESTRICT pattern, add all chars to _restrictedchars
407 for (unsigned int i
= 0; i
<= 255; ++i
) {
408 _restrictedchars
.insert(char(i
));
410 } else { //then this is an ALLOW pattern, _restrictedchars should remain empty
411 _restrictedchars
.clear();
415 while (rit
!= re
&& *rit
!= '^') { //This loop allows chars
417 log_error("invalid restrict string");
419 } else if (*(rit
+1) == '-') {
420 if (re
- (rit
+2) != 0) {
421 unsigned int q
= *(rit
+2);
422 for (unsigned int p
= *rit
; p
<= q
; (++p
)){
423 _restrictedchars
.insert(char(p
));
427 log_error("invalid restrict string");
430 } else if (*rit
== '\\') {
432 _restrictedchars
.insert(*rit
);
435 _restrictedchars
.insert(*rit
);
442 while (rit
!= re
&& *rit
!= '^') { //This loop restricts chars
443 locate
= _restrictedchars
.find(*rit
);
445 log_error("invalid restrict string");
447 } else if (*(rit
+1) == '-') {
448 if (re
- (rit
+2) != 0) {
449 unsigned int q
= *(rit
+2);
450 for (unsigned int p
= *rit
; p
<= q
; ++p
){
451 locate
= _restrictedchars
.find(p
);
452 if(locate
!= _restrictedchars
.end()) {
453 _restrictedchars
.erase(locate
);
460 log_error("invalid restrict string");
463 } else if (*rit
== '\\') {
465 locate
= _restrictedchars
.find(*rit
);
466 if(locate
!= _restrictedchars
.end()) {
467 _restrictedchars
.erase(locate
);
471 if(locate
!= _restrictedchars
.end()) {
472 _restrictedchars
.erase(locate
);
481 _restrict
= restrict
;
485 TextField::replaceSelection(const std::string
& replace
)
488 const int version
= getSWFVersion(*getObject(this));
489 const std::wstring
& wstr
= utf8::decodeCanonicalString(replace
, version
);
491 const size_t start
= _selection
.first
;
492 const size_t replaceLength
= wstr
.size();
494 _text
.replace(start
, _selection
.second
- start
, wstr
);
495 _selection
= std::make_pair(start
+ replaceLength
, start
+ replaceLength
);
499 TextField::setSelection(int start
, int end
)
503 _selection
= std::make_pair(0, 0);
507 const size_t textLength
= _text
.size();
509 if (start
< 0) start
= 0;
510 else start
= std::min
<size_t>(start
, textLength
);
512 if (end
< 0) end
= 0;
513 else end
= std::min
<size_t>(end
, textLength
);
515 // The cursor position is always set to the end value, even if the
516 // two values are swapped to obtain the selection. Equal values are
519 if (start
> end
) std::swap(start
, end
);
521 _selection
= std::make_pair(start
, end
);
525 TextField::notifyEvent(const event_id
& ev
)
529 case event_id::PRESS
:
531 movie_root
& root
= stage();
532 boost::int32_t x_mouse
, y_mouse
;
533 root
.get_mouse_state(x_mouse
, y_mouse
);
535 SWFMatrix m
= getMatrix(*this);
537 x_mouse
-= m
.get_x_translation();
538 y_mouse
-= m
.get_y_translation();
542 for (size_t i
=0; i
< _textRecords
.size(); ++i
) {
543 if ((x_mouse
> _textRecords
[i
].xOffset()) &&
544 (x_mouse
< _textRecords
[i
].xOffset()+_textRecords
[i
].recordWidth()) &&
545 (y_mouse
> _textRecords
[i
].yOffset()-_textRecords
[i
].textHeight()) &&
546 (y_mouse
< _textRecords
[i
].yOffset())) {
547 rec
= _textRecords
[i
];
552 if (!rec
.getURL().empty()) {
553 root
.getURL(rec
.getURL(), rec
.getTarget(), "",
554 MovieClip::METHOD_NONE
);
559 case event_id::KEY_PRESS
:
561 setHtml(false); //editable html fields are not yet implemented
562 std::wstring s
= _text
;
564 // id.keyCode is the unique gnash::key::code for a DisplayObject/key.
565 // The maximum value is about 265, including function keys.
566 // It seems that typing in DisplayObjects outside the Latin-1 set
567 // (256 DisplayObject codes, identical to the first 256 of UTF-8)
568 // is not supported, though a much greater number UTF-8 codes can be
569 // stored and displayed. See utf.h for more information.
570 // This is a limit on the number of key codes, not on the
571 // capacity of strings.
572 gnash::key::code c
= ev
.keyCode();
575 // maybe _text is changed in ActionScript
576 m_cursor
= std::min
<size_t>(m_cursor
, _text
.size());
578 size_t cur_cursor
= m_cursor
;
579 size_t previouslinesize
= 0;
580 size_t nextlinesize
= 0;
581 size_t manylines
= _line_starts
.size();
582 LineStarts::iterator linestartit
= _line_starts
.begin();
583 LineStarts::const_iterator linestartend
= _line_starts
.end();
588 if (isReadOnly()) return;
591 s
.erase(m_cursor
- 1, 1);
598 if (isReadOnly()) return;
599 if (_glyphcount
> m_cursor
)
601 s
.erase(m_cursor
, 1);
606 case key::INSERT
: // TODO
607 if (isReadOnly()) return;
611 while ( linestartit
< linestartend
&& *linestartit
<= m_cursor
) {
612 cur_cursor
= *linestartit
;
615 m_cursor
= cur_cursor
;
619 // if going a page up is too far...
620 if(_scroll
< _linesindisplay
) {
623 } else { // go a page up
624 _scroll
-= _linesindisplay
;
625 m_cursor
= _line_starts
[_scroll
];
631 while ( linestartit
< linestartend
&& *linestartit
<= m_cursor
) {
632 cur_cursor
= *linestartit
;
635 //if there is no previous line
636 if ( linestartit
-_line_starts
.begin() - 2 < 0 ) {
640 previouslinesize
= _textRecords
[linestartit
-_line_starts
.begin() - 2].glyphs().size();
641 //if the previous line is smaller
642 if (m_cursor
- cur_cursor
> previouslinesize
) {
643 m_cursor
= *(--(--linestartit
)) + previouslinesize
;
645 m_cursor
= *(--(--linestartit
)) + (m_cursor
- cur_cursor
);
647 if (m_cursor
< _line_starts
[_scroll
] && _line_starts
[_scroll
] != 0) {
654 while ( linestartit
< linestartend
&& *linestartit
<= m_cursor
) {
657 m_cursor
= linestartit
!= linestartend
? *linestartit
- 1 : _text
.size();
661 //if going another page down is too far...
662 if(_scroll
+ _linesindisplay
>= manylines
) {
663 if(manylines
- _linesindisplay
<= 0) {
666 _scroll
= manylines
- _linesindisplay
;
668 if(m_cursor
< _line_starts
[_scroll
-1]) {
669 m_cursor
= _line_starts
[_scroll
-1];
671 m_cursor
= _text
.size();
673 } else { //go a page down
674 _scroll
+= _linesindisplay
;
675 m_cursor
= _line_starts
[_scroll
];
682 while (linestartit
< linestartend
&&
683 *linestartit
<= m_cursor
) {
684 cur_cursor
= *linestartit
;
688 // linestartit should never be before _line_starts.begin()
689 const size_t currentLine
= linestartit
-
690 _line_starts
.begin();
692 //if there is no next line
693 if (currentLine
>= manylines
) {
694 m_cursor
= _text
.size();
697 nextlinesize
= _textRecords
[currentLine
].glyphs().size();
699 //if the next line is smaller
700 if (m_cursor
- cur_cursor
> nextlinesize
) {
701 m_cursor
= *linestartit
+ nextlinesize
;
703 //put the cursor at the same character distance
704 m_cursor
= *(linestartit
) + (m_cursor
- cur_cursor
);
706 if (_line_starts
.size() > _linesindisplay
&&
707 m_cursor
>= _line_starts
[_scroll
+_linesindisplay
]) {
715 m_cursor
= m_cursor
> 0 ? m_cursor
- 1 : 0;
719 m_cursor
= m_cursor
< _glyphcount
? m_cursor
+ 1 :
724 if (isReadOnly()) return;
732 if ( _maxChars
<= _glyphcount
)
738 if (isReadOnly()) return;
739 wchar_t t
= static_cast<wchar_t>(
740 gnash::key::codeMap
[c
][key::ASCII
]);
744 if (!_restrictDefined
) {
745 // Insert one copy of the character
746 // at the cursor position.
747 s
.insert(m_cursor
, 1, t
);
749 } else if (_restrictedchars
.count(t
)) {
750 // Insert one copy of the character
751 // at the cursor position.
752 s
.insert(m_cursor
, 1, t
);
754 } else if (_restrictedchars
.count(tolower(t
))) {
755 // restrict substitutes the opposite case
756 s
.insert(m_cursor
, 1, tolower(t
));
758 } else if (_restrictedchars
.count(toupper(t
))) {
759 // restrict substitutes the opposite case
760 s
.insert(m_cursor
, 1, toupper(t
));
776 TextField::topmostMouseEntity(boost::int32_t x
, boost::int32_t y
)
779 if (!visible()) return 0;
781 // Not selectable, so don't catch mouse events!
782 if (!_selectable
) return 0;
784 SWFMatrix m
= getMatrix(*this);
786 m
.invert().transform(p
);
788 if (_bounds
.point_test(p
.x
, p
.y
)) return this;
794 TextField::updateText(const std::string
& str
)
796 const int version
= getSWFVersion(*getObject(this));
797 const std::wstring
& wstr
= utf8::decodeCanonicalString(str
, version
);
802 TextField::updateText(const std::wstring
& wstr
)
806 if (_text
== wstr
) return;
815 TextField::updateHtmlText(const std::string
& str
)
817 int version
= getSWFVersion(*getObject(this));
818 const std::wstring
& wstr
= utf8::decodeCanonicalString(str
, version
);
819 updateHtmlText(wstr
);
823 TextField::updateHtmlText(const std::wstring
& wstr
)
825 _htmlTextDefined
= true;
827 if (_htmlText
== wstr
) return;
836 TextField::setHtmlTextValue(const std::wstring
& wstr
)
841 //updateText with no HTML tags
842 //for now, it is better to make the display correct
845 updateHtmlText(wstr
);
847 if ( ! _variable_name
.empty() && _text_variable_registered
)
849 // TODO: notify MovieClip if we have a variable name !
850 VariableRef ref
= parseTextVariableRef(_variable_name
);
851 as_object
* tgt
= ref
.first
;
854 const int version
= getSWFVersion(*getObject(this));
855 // we shouldn't truncate, right?
856 tgt
->set_member(ref
.second
, utf8::encodeCanonicalString(wstr
,
861 // nothing to do (too early ?)
862 log_debug("setHtmlTextValue: variable name %s points to a non-existent"
863 " target, I guess we would not be registered if this was "
864 "true, or the sprite we've registered our variable name "
865 "has been unloaded", _variable_name
);
871 TextField::setTextValue(const std::wstring
& wstr
)
874 updateHtmlText(wstr
);
876 //updateHtmlText and insert necessary html tags
880 if ( ! _variable_name
.empty() && _text_variable_registered
)
882 // TODO: notify MovieClip if we have a variable name !
883 VariableRef ref
= parseTextVariableRef(_variable_name
);
884 as_object
* tgt
= ref
.first
;
887 const int version
= getSWFVersion(*getObject(this));
888 // we shouldn't truncate, right?
889 tgt
->set_member(ref
.second
, utf8::encodeCanonicalString(wstr
,
894 // nothing to do (too early ?)
895 log_debug("setTextValue: variable name %s points to a non-existent"
896 " target, I guess we would not be registered if this was "
897 "true, or the sprite we've registered our variable name "
898 "has been unloaded", _variable_name
);
904 TextField::get_text_value() const
906 // we need the const_cast here because registerTextVariable
907 // *might* change our text value, calling the non-const
909 // This happens if the TextVariable has not been already registered
910 // and during registration comes out to name an existing variable
911 // with a pre-existing value.
912 const_cast<TextField
*>(this)->registerTextVariable();
914 const int version
= getSWFVersion(*getObject(this));
916 return utf8::encodeCanonicalString(_text
, version
);
920 TextField::get_htmltext_value() const
922 const_cast<TextField
*>(this)->registerTextVariable();
923 const int version
= getSWFVersion(*getObject(const_cast<TextField
*>(this)));
924 return utf8::encodeCanonicalString(_htmlText
, version
);
928 TextField::setTextFormat(TextFormat_as
& tf
)
930 //TODO: this is lazy. we should set all the TextFormat variables HERE, i think
931 //This is just so we can set individual variables without having to call format_text()
932 //This calls format_text() at the end of setting TextFormat
933 if (tf
.align()) setAlignment(*tf
.align());
934 if (tf
.size()) setFontHeight(*tf
.size()); // keep twips
935 if (tf
.indent()) setIndent(*tf
.indent());
936 if (tf
.blockIndent()) setBlockIndent(*tf
.blockIndent());
937 if (tf
.leading()) setLeading(*tf
.leading());
938 if (tf
.leftMargin()) setLeftMargin(*tf
.leftMargin());
939 if (tf
.rightMargin()) setRightMargin(*tf
.rightMargin());
940 if (tf
.color()) setTextColor(*tf
.color());
941 if (tf
.underlined()) setUnderlined(*tf
.underlined());
942 if (tf
.bullet()) setBullet(*tf
.bullet());
943 setDisplay(tf
.display());
944 if (tf
.tabStops()) setTabStops(*tf
.tabStops());
946 // NEED TO IMPLEMENT THESE TWO
947 if (tf
.url()) setURL(*tf
.url());
948 if (tf
.target()) setTarget(*tf
.target());
954 TextField::align_line(TextAlignment align
, int last_line_start_record
, float x
)
957 float width
= _bounds
.width();
958 float right_margin
= getRightMargin();
960 float extra_space
= (width
- right_margin
) - x
- PADDING_TWIPS
;
962 if (extra_space
<= 0.0f
) {
963 #ifdef GNASH_DEBUG_TEXTFIELDS
964 log_debug(_("TextField text doesn't fit in its boundaries: "
965 "width %g, margin %g - nothing to align"),
966 width
, right_margin
);
971 float shift_right
= 0.0f
;
975 // Nothing to do; already aligned left.
978 // Distribute the space evenly on both sides.
979 shift_right
= extra_space
/ 2;
982 // Shift all the way to the right.
983 shift_right
= extra_space
;
986 // What should we do here?
990 // Shift the beginnings of the records on this line.
991 for (size_t i
= last_line_start_record
; i
< _textRecords
.size(); ++i
) {
992 SWF::TextRecord
& rec
= _textRecords
[i
];
993 rec
.setXOffset(rec
.xOffset() + shift_right
);
998 boost::intrusive_ptr
<const Font
>
999 TextField::setFont(boost::intrusive_ptr
<const Font
> newfont
)
1001 if ( newfont
== _font
) return _font
;
1003 boost::intrusive_ptr
<const Font
> oldfont
= _font
;
1012 TextField::insertTab(SWF::TextRecord
& rec
, boost::int32_t& x
, float scale
)
1015 const int space
= 32;
1016 int index
= rec
.getFont()->get_glyph_index(space
, _embedFonts
);
1019 IF_VERBOSE_MALFORMED_SWF (
1020 log_error(_("TextField: missing glyph for space char (needed "
1021 "for TAB). Make sure DisplayObject shapes for font "
1022 "%s are being exported into your SWF file."),
1023 rec
.getFont()->name());
1028 std::vector
<int> tabStops
;
1029 tabStops
= _tabStops
;
1031 std::sort(_tabStops
.begin(), _tabStops
.end());
1034 if ( !_tabStops
.empty() )
1036 tab
= _tabStops
.back()+1;
1038 for (size_t i
= 0; i
< tabStops
.size(); ++i
)
1040 if (tabStops
[i
] > x
)
1042 if((tabStops
[i
] - x
) < tab
)
1044 tab
= tabStops
[i
] - x
;
1050 // This is necessary in case the number of tabs in the text
1051 // are more than the actual number of tabStops inside the
1053 if ( !(tab
== _tabStops
.back()+1) )
1055 SWF::TextRecord::GlyphEntry ge
;
1056 ge
.index
= rec
.getFont()->get_glyph_index(32, _embedFonts
);
1064 SWF::TextRecord::GlyphEntry ge
;
1066 ge
.advance
= scale
* rec
.getFont()->get_advance(index
,
1069 const int tabstop
= 4;
1070 rec
.addGlyph(ge
, tabstop
);
1071 x
+= ge
.advance
* tabstop
;
1077 TextField::format_text()
1079 _textRecords
.clear();
1080 _line_starts
.clear();
1081 _recordStarts
.clear();
1084 _recordStarts
.push_back(0);
1086 // nothing more to do if text is empty
1087 if ( _text
.empty() )
1089 // TODO: should we still reset _bounds if autoSize != AUTOSIZE_NONE ?
1090 // not sure we should...
1091 reset_bounding_box(0, 0);
1095 LineStarts::iterator linestartit
= _line_starts
.begin();
1096 LineStarts::const_iterator linestartend
= _line_starts
.end();
1098 AutoSize autoSize
= getAutoSize();
1099 if (autoSize
!= AUTOSIZE_NONE
) {
1100 // When doing WordWrap we don't want to change
1101 // the boundaries. See bug #24348
1102 if (!doWordWrap()) {
1103 _bounds
.set_to_rect(0, 0, 0, 0); // this is correct for 'true'
1107 // FIXME: I don't think we should query the definition
1108 // to find the appropriate font to use, as ActionScript
1109 // code should be able to change the font of a TextField
1111 log_error(_("No font for TextField!"));
1115 boost::uint16_t fontHeight
= getFontHeight();
1116 float scale
= fontHeight
/
1117 static_cast<float>(_font
->unitsPerEM(_embedFonts
));
1118 const float fontLeading
= _font
->leading() * scale
;
1119 const boost::uint16_t leftMargin
= getLeftMargin();
1120 const boost::uint16_t indent
= getIndent();
1121 const boost::uint16_t blockIndent
= getBlockIndent();
1122 const bool underlined
= getUnderlined();
1124 /// Remember the current bounds for autosize.
1125 SWFRect
oldBounds(_bounds
);
1127 SWF::TextRecord rec
; // one to work on
1128 rec
.setFont(_font
.get());
1129 rec
.setUnderline(underlined
);
1130 rec
.setColor(getTextColor());
1131 rec
.setXOffset(PADDING_TWIPS
+
1132 std::max(0, leftMargin
+ indent
+ blockIndent
));
1133 rec
.setYOffset(PADDING_TWIPS
+ fontHeight
+ fontLeading
);
1134 rec
.setTextHeight(fontHeight
);
1136 // create in textrecord.h
1138 rec
.setTarget(_target
);
1142 // First, we indent 10 spaces, and then place the bullet
1143 // character (in this case, an asterisk), then we pad it
1144 // again with 10 spaces
1145 // Note: this works only for additional lines of a
1146 // bulleted list, so that is why there is a bullet format
1147 // in the beginning of format_text()
1150 int space
= rec
.getFont()->get_glyph_index(32, _embedFonts
);
1152 SWF::TextRecord::GlyphEntry ge
;
1154 ge
.advance
= scale
* rec
.getFont()->get_advance(space
, _embedFonts
);
1155 rec
.addGlyph(ge
, 5);
1157 // We use an asterisk instead of a bullet
1158 int bullet
= rec
.getFont()->get_glyph_index(42, _embedFonts
);
1160 ge
.advance
= scale
* rec
.getFont()->get_advance(bullet
, _embedFonts
);
1163 space
= rec
.getFont()->get_glyph_index(32, _embedFonts
);
1165 ge
.advance
= scale
* rec
.getFont()->get_advance(space
, _embedFonts
);
1166 rec
.addGlyph(ge
, 4);
1169 boost::int32_t x
= static_cast<boost::int32_t>(rec
.xOffset());
1170 boost::int32_t y
= static_cast<boost::int32_t>(rec
.yOffset());
1172 // Start the bbox at the upper-left corner of the first glyph.
1173 //reset_bounding_box(x, y + fontHeight);
1175 int last_code
= -1; // only used if _embedFonts
1176 int last_space_glyph
= -1;
1177 size_t last_line_start_record
= 0;
1179 float leading
= getLeading();
1180 leading
+= fontLeading
* scale
; // not sure this is correct...
1182 _line_starts
.push_back(0);
1184 // String iterators are very sensitive to
1185 // potential changes to the string (to allow for copy-on-write).
1186 // So there must be no external changes to the string or
1187 // calls to most non-const member functions during this loop.
1188 // Especially not c_str() or data().
1189 std::wstring::const_iterator it
= _text
.begin();
1190 const std::wstring::const_iterator e
= _text
.end();
1192 ///handleChar takes care of placing the glyphs
1193 handleChar(it
, e
, x
, y
, rec
, last_code
, last_space_glyph
,
1194 last_line_start_record
);
1196 // Expand bounding box to include the whole text (if autoSize and wordWrap
1197 // is not in operation.
1198 if (_autoSize
!= AUTOSIZE_NONE
&& !doWordWrap())
1200 _bounds
.expand_to_point(x
+ PADDING_TWIPS
, y
+ PADDING_TWIPS
);
1202 if (_autoSize
== AUTOSIZE_RIGHT
) {
1203 /// Autosize right expands from the previous right margin.
1206 m
.tx
= oldBounds
.get_x_max() - _bounds
.width();
1207 m
.transform(_bounds
);
1209 else if (_autoSize
== AUTOSIZE_CENTER
) {
1210 // Autosize center expands from the previous center.
1212 m
.tx
= oldBounds
.get_x_min() + oldBounds
.width() / 2.0 -
1213 _bounds
.width() / 2.0;
1214 m
.transform(_bounds
);
1218 // Add the last line to our output.
1219 _textRecords
.push_back(rec
);
1221 // align the last (or single) line
1222 align_line(getTextAlignment(), last_line_start_record
, x
);
1227 set_invalidated(); //redraw
1232 TextField::scrollLines()
1234 boost::uint16_t fontHeight
= getFontHeight();
1235 float scale
= fontHeight
/
1236 static_cast<float>(_font
->unitsPerEM(_embedFonts
));
1237 float fontLeading
= _font
->leading() * scale
;
1238 _linesindisplay
= _bounds
.height() / (fontHeight
+ fontLeading
+ PADDING_TWIPS
);
1239 if (_linesindisplay
> 0) { //no need to place lines if we can't fit any
1240 size_t manylines
= _line_starts
.size();
1241 size_t lastvisibleline
= _scroll
+ _linesindisplay
;
1244 // If there aren't as many lines as we have scrolled, display the
1246 if (manylines
< _scroll
) {
1247 _scroll
= manylines
- _linesindisplay
;
1251 // which line is the cursor on?
1252 while (line
< manylines
&& _line_starts
[line
] <= m_cursor
) {
1256 if (manylines
- _scroll
<= _linesindisplay
) {
1257 // This is for if we delete a line
1258 if (manylines
< _linesindisplay
) _scroll
= 0;
1260 _scroll
= manylines
- _linesindisplay
;
1262 } else if (line
< _scroll
) {
1263 //if we are at a higher position, scroll the lines down
1264 _scroll
-= _scroll
- line
;
1265 } else if (manylines
> _scroll
+ _linesindisplay
) {
1266 //if we are at a lower position, scroll the lines up
1267 if (line
>= (_scroll
+_linesindisplay
)) {
1268 _scroll
+= line
- (lastvisibleline
);
1275 TextField::newLine(boost::int32_t& x
, boost::int32_t& y
,
1276 SWF::TextRecord
& rec
, int& last_space_glyph
,
1277 LineStarts::value_type
& last_line_start_record
, float div
)
1280 LineStarts::iterator linestartit
= _line_starts
.begin();
1281 LineStarts::const_iterator linestartend
= _line_starts
.end();
1283 float scale
= _fontHeight
/
1284 static_cast<float>(_font
->unitsPerEM(_embedFonts
));
1285 float fontLeading
= _font
->leading() * scale
;
1286 float leading
= getLeading();
1287 leading
+= fontLeading
* scale
; // not sure this is correct...
1289 // Close out this stretch of glyphs.
1291 _textRecords
.push_back(rec
);
1292 _recordStarts
.push_back(_glyphcount
);
1293 align_line(getTextAlignment(), last_line_start_record
, x
);
1295 // Expand bounding box to include last column of text ...
1296 if (!doWordWrap() && _autoSize
!= AUTOSIZE_NONE
) {
1297 _bounds
.expand_to_point(x
+ PADDING_TWIPS
, y
+ PADDING_TWIPS
);
1300 // new paragraphs get the indent.
1301 x
= std::max(0, getLeftMargin() + getIndent() + getBlockIndent()) +
1303 y
+= div
* (getFontHeight() + leading
);
1304 if (y
>= _bounds
.height()) {
1308 // Start a new record on the next line. Other properties of the
1309 // TextRecord should be left unchanged.
1314 last_space_glyph
= -1;
1315 last_line_start_record
= _textRecords
.size();
1317 linestartit
= _line_starts
.begin();
1318 linestartend
= _line_starts
.end();
1319 //Fit a line_start in the correct place
1320 const size_t currentPos
= _glyphcount
;
1322 while (linestartit
< linestartend
&& *linestartit
< currentPos
)
1326 _line_starts
.insert(linestartit
, currentPos
);
1330 // First, we indent 10 spaces, and then place the bullet
1331 // character (in this case, an asterisk), then we pad it
1332 // again with 10 spaces
1333 // Note: this works only for additional lines of a
1334 // bulleted list, so that is why there is a bullet format
1335 // in the beginning of format_text()
1338 int space
= rec
.getFont()->get_glyph_index(32, _embedFonts
);
1339 SWF::TextRecord::GlyphEntry ge
;
1341 ge
.advance
= scale
* rec
.getFont()->get_advance(space
, _embedFonts
);
1346 int bullet
= rec
.getFont()->get_glyph_index(42, _embedFonts
);
1348 ge
.advance
= scale
* rec
.getFont()->get_advance(bullet
, _embedFonts
);
1353 ge
.advance
= scale
* rec
.getFont()->get_advance(space
, _embedFonts
);
1361 TextField::handleChar(std::wstring::const_iterator
& it
,
1362 const std::wstring::const_iterator
& e
, boost::int32_t& x
,
1363 boost::int32_t& y
, SWF::TextRecord
& rec
, int& last_code
,
1364 int& last_space_glyph
, LineStarts::value_type
& last_line_start_record
)
1366 LineStarts::iterator linestartit
= _line_starts
.begin();
1367 LineStarts::const_iterator linestartend
= _line_starts
.end();
1369 float scale
= _fontHeight
/
1370 static_cast<float>(_font
->unitsPerEM(_embedFonts
));
1371 float fontDescent
= _font
->descent(_embedFonts
) * scale
;
1372 float fontLeading
= _font
->leading() * scale
;
1373 float leading
= getLeading();
1374 leading
+= fontLeading
* scale
; // not sure this is correct...
1376 boost::uint32_t code
= 0;
1384 x
+= rec
.getFont()->get_kerning_adjustment(last_code
,
1385 static_cast<int>(code
)) * scale
;
1386 last_code
= static_cast<int>(code
);
1389 // Expand the bounding-box to the lower-right corner of each glyph as
1391 m_text_bounding_box
.expand_to_point(x
, y
+ fontDescent
);
1398 insertTab(rec
, x
, scale
);
1403 // This is a limited hack to enable overstrike effects.
1404 // It backs the cursor up by one DisplayObject and then continues
1405 // the layout. E.g. you can use this to display an underline
1406 // cursor inside a simulated text-entry box.
1408 // ActionScript understands the '\b' escape sequence
1409 // for inserting a BS DisplayObject.
1411 // ONLY WORKS FOR BACKSPACING OVER ONE CHARACTER, WON'T BS
1412 // OVER NEWLINES, ETC.
1414 if (!rec
.glyphs().empty())
1416 // Peek at the previous glyph, and zero out its advance
1417 // value, so the next char overwrites it.
1418 float advance
= rec
.glyphs().back().advance
;
1427 newLine(x
,y
,rec
,last_space_glyph
,last_line_start_record
,1.0);
1433 //close out this stretch of glyphs
1434 _textRecords
.push_back(rec
);
1436 _recordStarts
.push_back(_glyphcount
);
1438 while (it
!= e
&& *it
!= '>') {
1444 LOG_ONCE(log_debug(_("HTML in a text field is unsupported, "
1445 "gnash will just ignore the tags and "
1446 "print their content")));
1448 std::wstring discard
;
1449 std::map
<std::string
,std::string
> attributes
;
1450 SWF::TextRecord newrec
;
1451 newrec
.setFont(rec
.getFont());
1452 newrec
.setUnderline(rec
.underline());
1453 newrec
.setColor(rec
.color());
1454 newrec
.setTextHeight(rec
.textHeight());
1455 newrec
.setXOffset(x
);
1456 newrec
.setYOffset(y
);
1457 bool selfclosing
= false;
1458 bool complete
= parseHTML(discard
, attributes
, it
, e
, selfclosing
);
1459 std::string
s(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 // FIXME: should this be a log_aserror
1509 // or log_unimpl ? It is triggered
1510 // by TextFieldHTML.as
1511 log_error("Unexpected value '%s' in "
1512 "TextField font color attribute",
1517 // font COLOR attribute
1519 colorFromHexString(hexval
);
1520 newrec
.setColor(color
);
1523 attloc
= attributes
.find("FACE");
1524 if (attloc
!= attributes
.end()) {
1525 //font FACE attribute
1526 Font
* newfont
= new Font(attloc
->second
,
1527 rec
.getFont()->isBold(), rec
.getFont()->isItalic());
1528 newrec
.setFont(newfont
);
1530 attloc
= attributes
.find("SIZE");
1531 if (attloc
!= attributes
.end()) {
1532 //font SIZE attribute
1533 std::string firstchar
= attloc
->second
.substr(0,1);
1534 if (firstchar
== "+") {
1535 newrec
.setTextHeight(rec
.textHeight() +
1537 (pixelsToTwips(std::strtol(
1538 attloc
->second
.substr(1,attloc
->second
.length()-1).data(),
1540 newrec
.setYOffset(PADDING_TWIPS
+
1541 newrec
.textHeight() +
1542 (fontLeading
- fontDescent
));
1543 _fontHeight
+= pixelsToTwips(std::strtol(
1544 attloc
->second
.substr(1,attloc
->second
.length()-1).data(),
1546 } else if (firstchar
== "-") {
1547 newrec
.setTextHeight(rec
.textHeight() -
1548 (pixelsToTwips(std::strtol(
1549 attloc
->second
.substr(1,attloc
->second
.length()-1).data(),
1551 newrec
.setYOffset(PADDING_TWIPS
+
1552 newrec
.textHeight() +
1553 (fontLeading
- fontDescent
));
1554 _fontHeight
-= pixelsToTwips(std::strtol(
1555 attloc
->second
.substr(1,attloc
->second
.length()-1).data(),
1558 newrec
.setTextHeight(pixelsToTwips(std::strtol(
1559 attloc
->second
.data(), NULL
, 10)));
1560 newrec
.setYOffset(PADDING_TWIPS
+ newrec
.textHeight() +
1561 (fontLeading
- fontDescent
));
1562 _fontHeight
= pixelsToTwips(std::strtol(
1563 attloc
->second
.data(), NULL
, 10));
1566 handleChar(it
, e
, x
, y
, newrec
, last_code
,
1567 last_space_glyph
, last_line_start_record
);
1568 _fontHeight
= originalsize
;
1569 y
= newrec
.yOffset();
1571 else if (s
== "IMG") {
1573 log_unimpl("<img> html tag in TextField");
1574 handleChar(it
, e
, x
, y
, newrec
, last_code
,
1575 last_space_glyph
, last_line_start_record
);
1577 else if (s
== "I") {
1579 Font
* italicfont
= new Font(rec
.getFont()->name(),
1580 rec
.getFont()->isBold(), true);
1581 newrec
.setFont(italicfont
);
1582 handleChar(it
, e
, x
, y
, newrec
, last_code
,
1583 last_space_glyph
, last_line_start_record
);
1584 } else if (s
== "LI") {
1585 //list item (bullet)
1586 int space
= newrec
.getFont()->get_glyph_index(32, _embedFonts
);
1587 SWF::TextRecord::GlyphEntry ge
;
1589 ge
.advance
= scale
* newrec
.getFont()->get_advance(space
, _embedFonts
);
1590 newrec
.addGlyph(ge
, 5);
1592 // We use an asterisk instead of a bullet
1593 int bullet
= newrec
.getFont()->get_glyph_index(42, _embedFonts
);
1595 ge
.advance
= scale
* newrec
.getFont()->get_advance(bullet
, _embedFonts
);
1596 newrec
.addGlyph(ge
);
1598 space
= newrec
.getFont()->get_glyph_index(32, _embedFonts
);
1600 ge
.advance
= scale
* newrec
.getFont()->get_advance(space
, _embedFonts
);
1601 newrec
.addGlyph(ge
, 4);
1603 handleChar(it
, e
, x
, y
, newrec
, last_code
,
1604 last_space_glyph
, last_line_start_record
);
1605 newLine(x
, y
, newrec
, last_space_glyph
,
1606 last_line_start_record
, 1.0);
1608 else if (s
== "SPAN") {
1610 log_unimpl("<span> html tag in TextField");
1611 handleChar(it
, e
, x
, y
, newrec
, last_code
,
1612 last_space_glyph
, last_line_start_record
);
1614 else if (s
== "TEXTFORMAT") {
1615 log_debug("in textformat");
1617 boost::uint16_t originalblockindent
= getBlockIndent();
1618 boost::uint16_t originalindent
= getIndent();
1619 boost::uint16_t originalleading
= getLeading();
1620 boost::uint16_t originalleftmargin
= getLeftMargin();
1621 boost::uint16_t originalrightmargin
= getRightMargin();
1622 std::vector
<int> originaltabstops
= getTabStops();
1623 attloc
= attributes
.find("BLOCKINDENT");
1624 if (attloc
!= attributes
.end()) {
1625 //textformat BLOCKINDENT attribute
1626 setBlockIndent(pixelsToTwips(std::strtol(
1627 attloc
->second
.data(), NULL
, 10)));
1628 if (newrec
.xOffset() == std::max(0, originalleftmargin
+
1629 originalindent
+ originalblockindent
) + PADDING_TWIPS
) {
1630 //if beginning of line, indent
1631 x
= std::max(0, getLeftMargin() +
1632 getIndent() + getBlockIndent())
1634 newrec
.setXOffset(x
);
1637 attloc
= attributes
.find("INDENT");
1638 if (attloc
!= attributes
.end()) {
1639 //textformat INDENT attribute
1640 setIndent(pixelsToTwips(std::strtol(
1641 attloc
->second
.data(), NULL
, 10)));
1642 if (newrec
.xOffset() == std::max(0, originalleftmargin
+
1643 originalindent
+ getBlockIndent()) + PADDING_TWIPS
) {
1644 //if beginning of line, indent
1645 x
= std::max(0, getLeftMargin() +
1646 getIndent() + getBlockIndent())
1648 newrec
.setXOffset(x
);
1651 attloc
= attributes
.find("LEADING");
1652 if (attloc
!= attributes
.end()) {
1653 //textformat LEADING attribute
1654 setLeading(pixelsToTwips(std::strtol(
1655 attloc
->second
.data(), NULL
, 10)));
1657 attloc
= attributes
.find("LEFTMARGIN");
1658 if (attloc
!= attributes
.end()) {
1659 //textformat LEFTMARGIN attribute
1660 setLeftMargin(pixelsToTwips(std::strtol(
1661 attloc
->second
.data(), NULL
, 10)));
1662 if (newrec
.xOffset() == std::max(0, originalleftmargin
+
1663 getIndent() + getBlockIndent()) + PADDING_TWIPS
) {
1664 //if beginning of line, indent
1665 x
= std::max(0, getLeftMargin() +
1666 getIndent() + getBlockIndent())
1668 newrec
.setXOffset(x
);
1671 attloc
= attributes
.find("RIGHTMARGIN");
1672 if (attloc
!= attributes
.end()) {
1673 //textformat RIGHTMARGIN attribute
1674 setRightMargin(pixelsToTwips(std::strtol(
1675 attloc
->second
.data(), NULL
, 10)));
1676 //FIXME:Should not apply this to this line if we are not at
1677 //beginning of line. Not sure how to do that.
1679 attloc
= attributes
.find("TABSTOPS");
1680 if (attloc
!= attributes
.end()) {
1681 //textformat TABSTOPS attribute
1682 log_unimpl("html <textformat> tag tabstops attribute");
1684 handleChar(it
, e
, x
, y
, newrec
, last_code
,
1685 last_space_glyph
, last_line_start_record
);
1686 setBlockIndent(originalblockindent
);
1687 setIndent(originalindent
);
1688 setLeading(originalleading
);
1689 setLeftMargin(originalleftmargin
);
1690 setRightMargin(originalrightmargin
);
1691 setTabStops(originaltabstops
);
1693 else if (s
== "P") {
1695 if (_display
== TEXTFORMAT_BLOCK
) {
1696 handleChar(it
, e
, x
, y
, newrec
, last_code
,
1698 last_line_start_record
);
1699 newLine(x
, y
, rec
, last_space_glyph
,
1700 last_line_start_record
, 1.0);
1701 newLine(x
, y
, rec
, last_space_glyph
,
1702 last_line_start_record
, 1.5);
1705 handleChar(it
, e
, x
, y
, newrec
, last_code
,
1707 last_line_start_record
);
1710 else if (s
== "BR" || s
== "SBR") {
1712 newLine(x
, y
, rec
, last_space_glyph
,
1713 last_line_start_record
, 1.0);
1716 log_debug("<%s> tag is unsupported", s
);
1717 if (!selfclosing
) { //then recurse, look for closing tag
1718 handleChar(it
, e
, x
, y
, newrec
, last_code
,
1719 last_space_glyph
, last_line_start_record
);
1727 // If HTML isn't enabled, carry on and insert the glyph.
1728 // FIXME: do we also want to be changing last_space_glyph?
1729 // ...because we are...
1731 last_space_glyph
= rec
.glyphs().size();
1732 // Don't break, as we still need to insert the space glyph.
1741 SWF::TextRecord::GlyphEntry ge
;
1742 int bullet
= rec
.getFont()->get_glyph_index(42, _embedFonts
);
1744 ge
.advance
= scale
* rec
.getFont()->get_advance(bullet
,
1750 // The font table holds up to 65535 glyphs. Casting
1751 // from uint32_t would, in the event that the code
1752 // is higher than 65535, result in the wrong DisplayObject
1753 // being chosen. Flash can currently only handle 16-bit
1755 int index
= rec
.getFont()->get_glyph_index(
1756 static_cast<boost::uint16_t>(code
), _embedFonts
);
1758 IF_VERBOSE_MALFORMED_SWF (
1761 // Missing glyph! Log the first few errors.
1762 static int s_log_count
= 0;
1763 if (s_log_count
< 10)
1768 log_swferror(_("TextField: missing embedded "
1769 "glyph for char %d. Make sure DisplayObject "
1770 "shapes for font %s are being exported "
1771 "into your SWF file"),
1772 code
, _font
->name());
1776 log_swferror(_("TextField: missing device "
1777 "glyph for char %d. Maybe you don't have "
1778 "font '%s' installed in your system."),
1779 code
, _font
->name());
1783 // Drop through and use index == -1; this will display
1784 // using the empty-box glyph
1788 SWF::TextRecord::GlyphEntry ge
;
1790 ge
.advance
= scale
* rec
.getFont()->get_advance(index
,
1800 float width
= _bounds
.width();
1801 if (x
>= width
- getRightMargin() - PADDING_TWIPS
)
1803 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1804 log_debug("Text in TextField %s exceeds width [ _bounds %s ]",
1805 getTarget(), _bounds
);
1808 // No wrap and no resize: truncate
1809 if (!doWordWrap() && getAutoSize() == AUTOSIZE_NONE
)
1811 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1812 log_debug(" wordWrap=false, autoSize=none");
1814 // Truncate long line, but keep expanding text box
1815 bool newlinefound
= false;
1821 x
+= rec
.getFont()->get_kerning_adjustment(last_code
,
1822 static_cast<int>(code
)) * scale
;
1825 // Expand the bounding-box to the lower-right corner
1826 // of each glyph, even if we don't display it
1827 m_text_bounding_box
.expand_to_point(x
, y
+ fontDescent
);
1828 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1829 log_debug("Text bbox expanded to %s (width: %f)",
1830 m_text_bounding_box
, m_text_bounding_box
.width());
1833 if (code
== 13 || code
== 10)
1835 newlinefound
= true;
1839 int index
= rec
.getFont()->get_glyph_index(
1840 static_cast<boost::uint16_t>(code
), _embedFonts
);
1841 x
+= scale
* rec
.getFont()->get_advance(index
, _embedFonts
);
1844 if (!newlinefound
) break;
1846 else if (doWordWrap()) {
1848 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1849 log_debug(" wordWrap=true");
1852 // Insert newline if there's space or autosize != none
1854 // Close out this stretch of glyphs.
1855 _textRecords
.push_back(rec
);
1857 float previous_x
= x
;
1858 x
= std::max(0, getLeftMargin() + getBlockIndent()) + PADDING_TWIPS
;
1859 y
+= _fontHeight
+ leading
;
1860 if (y
>= _bounds
.height()) {
1864 // Start a new record on the next line.
1869 // TODO : what if m_text_glyph_records is empty ?
1871 assert(!_textRecords
.empty());
1872 SWF::TextRecord
& last_line
= _textRecords
.back();
1874 linestartit
= _line_starts
.begin();
1875 linestartend
= _line_starts
.end();
1876 if (last_space_glyph
== -1)
1878 // Pull the previous glyph down onto the
1880 if (!last_line
.glyphs().empty())
1882 rec
.addGlyph(last_line
.glyphs().back());
1883 x
+= last_line
.glyphs().back().advance
;
1884 previous_x
-= last_line
.glyphs().back().advance
;
1885 last_line
.clearGlyphs(1);
1886 //record the new line start
1888 const size_t currentPos
= _glyphcount
;
1889 while (linestartit
!= linestartend
&&
1890 *linestartit
+ 1 <= currentPos
)
1894 _line_starts
.insert(linestartit
, currentPos
);
1895 _recordStarts
.push_back(currentPos
);
1898 // Move the previous word down onto the next line.
1900 previous_x
-= last_line
.glyphs()[last_space_glyph
].advance
;
1902 const SWF::TextRecord::Glyphs::size_type lineSize
=
1903 last_line
.glyphs().size();
1904 for (unsigned int i
= last_space_glyph
+ 1; i
< lineSize
;
1907 rec
.addGlyph(last_line
.glyphs()[i
]);
1908 x
+= last_line
.glyphs()[i
].advance
;
1909 previous_x
-= last_line
.glyphs()[i
].advance
;
1911 last_line
.clearGlyphs(lineSize
- last_space_glyph
);
1913 // record the position at the start of this line as
1915 const size_t linestartpos
= _glyphcount
-
1916 rec
.glyphs().size();
1918 while (linestartit
< linestartend
&&
1919 *linestartit
< linestartpos
)
1923 _line_starts
.insert(linestartit
, linestartpos
);
1924 _recordStarts
.push_back(linestartpos
);
1927 align_line(getTextAlignment(), last_line_start_record
, previous_x
);
1929 last_space_glyph
= -1;
1930 last_line_start_record
= _textRecords
.size();
1935 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1936 log_debug(" wordWrap=%d, autoSize=%d", _wordWrap
, _autoSize
);
1944 TextField::getDefinitionVersion() const
1946 // TODO: work out if this correct.
1947 return get_root()->getDefinitionVersion();
1951 TextField::VariableRef
1952 TextField::parseTextVariableRef(const std::string
& variableName
) const
1957 #ifdef DEBUG_DYNTEXT_VARIABLES
1958 log_debug(_("VariableName: %s"), variableName
);
1961 /// Why isn't get_environment const again ?
1962 const as_environment
& env
= const_cast<TextField
*>(this)->get_environment();
1964 as_object
* target
= getObject(env
.target());
1966 IF_VERBOSE_MALFORMED_SWF(
1967 log_swferror(_("Current environment has no target, "
1968 "can't bind VariableName (%s) associated to "
1969 "text field. Gnash will try to register "
1970 "again on next access."), variableName
);
1975 // If the variable string contains a path, we extract
1976 // the appropriate target from it and update the variable
1977 // name. We copy the string so we can assign to it if necessary.
1978 std::string parsedName
= variableName
;
1979 std::string path
, var
;
1980 if (parsePath(variableName
, path
, var
))
1982 #ifdef DEBUG_DYNTEXT_VARIABLES
1983 log_debug(_("Variable text Path: %s, Var: %s"), path
, var
);
1985 // find target for the path component
1986 // we use our parent's environment for this
1987 target
= findObject(env
, path
);
1994 IF_VERBOSE_MALFORMED_SWF(
1995 log_swferror(_("VariableName associated to text field refers "
1996 "to an unknown target (%s). It is possible that the "
1997 "DisplayObject will be instantiated later in the SWF "
1998 "stream. Gnash will try to register again on next "
2005 ret
.second
= getURI(getVM(*object()), parsedName
);
2011 TextField::registerTextVariable()
2013 //#define DEBUG_DYNTEXT_VARIABLES 1
2015 #ifdef DEBUG_DYNTEXT_VARIABLES
2016 log_debug(_("registerTextVariable() called"));
2019 if (_text_variable_registered
) {
2020 #ifdef DEBUG_DYNTEXT_VARIABLES
2021 log_debug(_("registerTextVariable() no-op call (already registered)"));
2026 if (_variable_name
.empty()) {
2027 #ifdef DEBUG_DYNTEXT_VARIABLES
2028 log_debug(_("string is empty, consider as registered"));
2030 _text_variable_registered
=true;
2034 VariableRef varRef
= parseTextVariableRef(_variable_name
);
2035 as_object
* target
= varRef
.first
;
2037 log_debug(_("VariableName associated to text field (%s) refer to "
2038 "an unknown target. It is possible that the DisplayObject "
2039 "will be instantiated later in the SWF stream. "
2040 "Gnash will try to register again on next access."),
2045 const ObjectURI
& key
= varRef
.second
;
2046 as_object
* obj
= getObject(this);
2047 const int version
= getSWFVersion(*obj
);
2048 string_table
& st
= getStringTable(*obj
);
2050 // check if the VariableName already has a value,
2051 // in that case update text value
2053 if (target
->get_member(key
, &val
) ) {
2054 #ifdef DEBUG_DYNTEXT_VARIABLES
2055 log_debug(_("target object (%s @ %p) does have a member named %s"),
2056 typeName(*target
), (void*)target
, st
.value(key
));
2058 // TODO: pass environment to to_string ?
2059 // as_environment& env = get_environment();
2060 setTextValue(utf8::decodeCanonicalString(val
.to_string(), version
));
2062 else if (_textDefined
) {
2063 as_value newVal
= as_value(utf8::encodeCanonicalString(_text
, version
));
2064 #ifdef DEBUG_DYNTEXT_VARIABLES
2065 log_debug(_("target sprite (%s @ %p) does NOT have a member "
2066 "named %s (no problem, we'll add it with value %s)"),
2067 typeName(*target
), (void*)target
,
2068 st
.value(key
), newVal
);
2070 target
->set_member(key
, newVal
);
2073 #ifdef DEBUG_DYNTEXT_VARIABLES
2074 log_debug(_("target sprite (%s @ %p) does NOT have a member "
2075 "named %s, and we don't have text defined"),
2076 typeName(*target
), (void*)target
, st
.value(key
));
2080 MovieClip
* sprite
= get
<MovieClip
>(target
);
2083 // add the textfield variable to the target sprite
2084 // TODO: have set_textfield_variable take a string_table::key instead ?
2085 #ifdef DEBUG_DYNTEXT_VARIABLES
2086 log_debug("Calling set_textfield_variable(%s) against sprite %s",
2087 st
.value(key
), sprite
->getTarget());
2089 sprite
->set_textfield_variable(key
.toString(st
), this);
2092 _text_variable_registered
=true;
2096 /// Parses an HTML tag (between < and >) and puts
2097 /// the contents into tag. Returns false if the
2098 /// tag was incomplete. The iterator is moved to after
2099 /// the closing tag or the end of the string.
2101 TextField::parseHTML(std::wstring
& tag
,
2102 std::map
<std::string
, std::string
>& attributes
,
2103 std::wstring::const_iterator
& it
,
2104 const std::wstring::const_iterator
& e
,
2105 bool& selfclosing
) const
2107 while (it
!= e
&& *it
!= ' ') {
2118 log_error("invalid html tag");
2127 // Check for NULL character
2129 log_error("found NULL character in htmlText");
2132 tag
.push_back(std::toupper(*it
));
2135 while (it
!= e
&& *it
== ' ') {
2136 ++it
; //skip over spaces
2152 log_error("invalid html tag");
2157 std::string attname
;
2158 std::string attvalue
;
2161 while (it
!= e
&& *it
!= '>') {
2162 while (it
!= e
&& *it
!= '=' && *it
!= ' ') {
2165 log_error("found NULL character in htmlText");
2169 log_error("malformed HTML tag, invalid attribute name");
2176 attname
.push_back(std::toupper(*it
));
2179 while (it
!= e
&& (*it
== ' ' || *it
== '=')) {
2180 ++it
; //skip over spaces and '='
2183 if (it
== e
) return false;
2185 if (q
!= '"' && q
!= '\'') {
2186 // This is not an attribute.
2187 while (it
!= e
) ++it
;
2191 // Advance past attribute opener
2193 while (it
!= e
&& *it
!= q
) {
2196 log_error("found NULL character in htmlText");
2200 attvalue
.push_back(std::toupper(*it
));
2204 if (it
== e
) return false;
2207 while (it
!= e
) ++it
;
2211 // Skip attribute closer.
2214 attributes
.insert(std::make_pair(attname
, attvalue
));
2218 if ((*it
!= ' ') && (*it
!= '/') && (*it
!= '>')) {
2219 log_error("malformed HTML tag, invalid attribute value");
2226 while (it
!= e
&& *it
== ' ') {
2227 ++it
; //skip over spaces
2233 } else if (*it
== '/') {
2243 log_error("invalid html tag");
2249 #ifdef GNASH_DEBUG_TEXTFIELDS
2250 log_debug ("HTML tag: %s", utf8::encodeCanonicalString(tag
, 7));
2252 log_error("I declare this a HTML syntax error");
2253 return false; //since we did not return already, must be malformed...?
2257 TextField::set_variable_name(const std::string
& newname
)
2259 if ( newname
!= _variable_name
)
2261 _variable_name
= newname
;
2263 // The name was empty or undefined, so there's nothing more to do.
2264 if (_variable_name
.empty()) return;
2266 _text_variable_registered
= false;
2268 #ifdef DEBUG_DYNTEXT_VARIABLES
2269 log_debug("Calling updateText after change of variable name");
2272 // Use the original definition text if this isn't dynamically
2274 if (_tag
) updateText(_tag
->defaultText());
2276 #ifdef DEBUG_DYNTEXT_VARIABLES
2277 log_debug("Calling registerTextVariable after change of variable "
2278 "name and updateText call");
2280 registerTextVariable();
2285 TextField::pointInShape(boost::int32_t x
, boost::int32_t y
) const
2287 const SWFMatrix wm
= getWorldMatrix(*this).invert();
2290 return _bounds
.point_test(lp
.x
, lp
.y
);
2294 TextField::getDrawBorder() const
2300 TextField::setDrawBorder(bool val
)
2302 if ( _drawBorder
!= val
)
2310 TextField::getBorderColor() const
2312 return _borderColor
;
2316 TextField::setBorderColor(const rgba
& col
)
2318 if ( _borderColor
!= col
)
2326 TextField::getDrawBackground() const
2328 return _drawBackground
;
2332 TextField::setDrawBackground(bool val
)
2334 if ( _drawBackground
!= val
)
2337 _drawBackground
= val
;
2342 TextField::getBackgroundColor() const
2344 return _backgroundColor
;
2348 TextField::setBackgroundColor(const rgba
& col
)
2350 if ( _backgroundColor
!= col
)
2353 _backgroundColor
= col
;
2358 TextField::setTextColor(const rgba
& col
)
2360 if (_textColor
!= col
) {
2364 std::for_each(_displayRecords
.begin(), _displayRecords
.end(),
2365 boost::bind(&SWF::TextRecord::setColor
, _1
, _textColor
));
2370 TextField::setEmbedFonts(bool use
)
2372 if ( _embedFonts
!= use
)
2381 TextField::setWordWrap(bool wrap
)
2383 if (_wordWrap
!= wrap
) {
2392 TextField::setLeading(boost::int16_t h
)
2394 if ( _leading
!= h
)
2402 TextField::setUnderlined(bool v
)
2404 if ( _underlined
!= v
)
2412 TextField::setBullet(bool b
)
2420 TextField::setTabStops(const std::vector
<int>& tabStops
)
2422 _tabStops
.resize(tabStops
.size());
2424 for (size_t i
= 0; i
< tabStops
.size(); i
++) {
2425 _tabStops
[i
] = pixelsToTwips(tabStops
[i
]);
2432 TextField::setURL(std::string url
)
2434 if ( _url
!= url
) {
2441 TextField::setTarget(std::string target
)
2443 if ( _target
!= target
)
2451 TextField::setDisplay(TextFormatDisplay display
)
2453 if ( _display
!= display
)
2461 TextField::setAlignment(TextAlignment h
)
2463 if ( _alignment
!= h
)
2471 TextField::setIndent(boost::uint16_t h
)
2481 TextField::setBlockIndent(boost::uint16_t h
)
2483 if ( _blockIndent
!= h
)
2491 TextField::setRightMargin(boost::uint16_t h
)
2493 if ( _rightMargin
!= h
)
2501 TextField::setLeftMargin(boost::uint16_t h
)
2503 if (_leftMargin
!= h
)
2511 TextField::setFontHeight(boost::uint16_t h
)
2513 if ( _fontHeight
!= h
)
2521 TextField::TypeValue
2522 TextField::parseTypeValue(const std::string
& val
)
2524 StringNoCaseEqual cmp
;
2526 if (cmp(val
, "input")) return typeInput
;
2527 if (cmp(val
, "dynamic")) return typeDynamic
;
2534 TextField::typeValueName(TypeValue val
)
2539 //log_debug("typeInput returned as 'input'");
2542 //log_debug("typeDynamic returned as 'dynamic'");
2545 //log_debug("invalid type %d returned as 'invalid'", (int)val);
2552 TextField::setAutoSize(AutoSize val
)
2554 if ( val
== _autoSize
) return;
2562 TextField::TextAlignment
2563 TextField::getTextAlignment()
2565 TextAlignment textAlignment
= getAlignment();
2567 switch (_autoSize
) {
2568 case AUTOSIZE_CENTER
:
2569 textAlignment
= ALIGN_CENTER
;
2572 textAlignment
= ALIGN_LEFT
;
2574 case AUTOSIZE_RIGHT
:
2575 textAlignment
= ALIGN_RIGHT
;
2578 // Leave it as it was.
2582 return textAlignment
;
2586 TextField::onChanged()
2588 as_object
* obj
= getObject(this);
2589 callMethod(obj
, NSV::PROP_BROADCAST_MESSAGE
, "onChanged", obj
);
2592 /// This is called by movie_root when focus is applied to this TextField.
2594 /// The return value is true if the TextField can receive focus.
2595 /// The swfdec testsuite suggests that version 5 textfields cannot ever
2598 TextField::handleFocus()
2603 /// Select the entire text on focus.
2604 setSelection(0, _text
.length());
2608 m_cursor
= _text
.size();
2613 /// This is called by movie_root when focus is removed from the
2614 /// current TextField.
2616 TextField::killFocus()
2618 if ( ! m_has_focus
) return; // nothing to do
2621 m_has_focus
= false;
2623 format_text(); // is this needed ?
2628 TextField::setWidth(double newwidth
)
2630 const SWFRect
& bounds
= getBounds();
2631 _bounds
.set_to_rect(bounds
.get_x_min(),
2633 bounds
.get_x_min() + newwidth
,
2634 bounds
.get_y_max());
2638 TextField::setHeight(double newheight
)
2640 const SWFRect
& bounds
= getBounds();
2641 _bounds
.set_to_rect(bounds
.get_x_min(),
2644 bounds
.get_y_min() + newheight
);
2647 /// TextField interface functions
2650 } // namespace gnash
2655 // indent-tabs-mode: t