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
31 #include "TextField.h"
39 #include <boost/assign/list_of.hpp>
40 #include <boost/bind.hpp>
41 #include <boost/tuple/tuple.hpp>
45 #include "swf/DefineEditTextTag.h"
46 #include "MovieClip.h"
47 #include "movie_root.h" // for killing focus
48 #include "as_environment.h"
51 #include "namedStrings.h"
52 #include "StringPredicates.h"
53 #include "TextFormat_as.h"
55 #include "TextRecord.h"
57 #include "GnashNumeric.h"
58 #include "MouseButtonState.h"
59 #include "Global_as.h"
61 #include "Transform.h"
62 #include "ObjectURI.h"
64 // Text fields have a fixed 2 pixel padding for each side (regardless of border)
65 #define PADDING_TWIPS 40
67 // Define the following to get detailed log information about
68 // textfield bounds and HTML tags:
69 //#define GNASH_DEBUG_TEXTFIELDS 1
71 // Define this to get debugging info about text formatting
72 //#define GNASH_DEBUG_TEXT_FORMATTING 1
76 TextField::TextField(as_object
* object
, DisplayObject
* parent
,
77 const SWF::DefineEditTextTag
& def
)
79 InteractiveObject(object
, parent
),
85 _variable_name(def
.variableName()),
86 _backgroundColor(255,255,255,255),
87 _borderColor(0,0,0,255),
88 _textColor(def
.color()),
89 _alignment(def
.alignment()),
99 _maxChars(def
.maxChars()),
100 _autoSize(def
.autoSize() ? AUTOSIZE_LEFT
: AUTOSIZE_NONE
),
101 _type(def
.readOnly() ? typeDynamic
: typeInput
),
102 _bounds(def
.bounds()),
104 _leading(def
.leading()),
105 _indent(def
.indent()),
107 _leftMargin(def
.leftMargin()),
108 _rightMargin(def
.rightMargin()),
109 _fontHeight(def
.textHeight()),
110 _textDefined(def
.hasText()),
111 _restrictDefined(false),
115 _multiline(def
.multiline()),
116 _password(def
.password()),
117 _text_variable_registered(false),
118 _drawBackground(def
.border()),
119 _drawBorder(def
.border()),
120 _embedFonts(def
.getUseEmbeddedGlyphs()),
121 _wordWrap(def
.wordWrap()),
123 _selectable(!def
.noSelect())
128 // WARNING! remember to set the font *before* setting text value!
129 boost::intrusive_ptr
<const Font
> f
= def
.getFont();
130 if (!f
) f
= fontlib::get_default_font();
133 const int version
= getSWFVersion(*object
);
135 // set default text *before* calling registerTextVariable
136 // (if the textvariable already exist and has a value
137 // the text will be replaced with it)
139 setTextValue(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 _restrictDefined(false),
185 _text_variable_registered(false),
186 _drawBackground(false),
193 // Use the default font (Times New Roman for Windows, Times for Mac
194 // according to docs. They don't say what it is for Linux.
195 boost::intrusive_ptr
<const Font
> f
= fontlib::get_default_font();
204 registerTextVariable();
206 reset_bounding_box(0, 0);
210 TextField::~TextField()
215 TextField::removeTextField()
217 int depth
= get_depth();
218 if ( depth
< 0 || depth
> 1048575 )
220 //IF_VERBOSE_ASCODING_ERRORS(
221 log_debug(_("CHECKME: removeTextField(%s): TextField depth (%d) "
222 "out of the 'dynamic' zone [0..1048575], won't remove"),
228 DisplayObject
* p
= parent();
229 assert(p
); // every TextField must have a parent, right ?
231 MovieClip
* parentSprite
= p
->to_movie();
234 log_error("FIXME: attempt to remove a TextField being a child of a %s",
239 // second argument is arbitrary, see comments above
240 // the function declaration in MovieClip.h
241 parentSprite
->remove_display_object(depth
, 0);
245 TextField::show_cursor(Renderer
& renderer
, const SWFMatrix
& mat
)
247 if (_textRecords
.empty()) {
253 size_t i
= cursorRecord();
254 SWF::TextRecord record
= _textRecords
[i
];
256 x
= record
.xOffset();
257 y
= record
.yOffset() - record
.textHeight() + getLeading();
258 h
= record
.textHeight();
260 if (!record
.glyphs().empty()) {
261 for (unsigned int p
= 0 ; p
< (m_cursor
- _recordStarts
[i
]); ++p
) {
262 x
+= record
.glyphs()[p
].advance
;
266 const std::vector
<point
> line
= boost::assign::list_of
270 renderer
.drawLine(line
, rgba(0, 0, 0, 255), mat
);
274 TextField::cursorRecord()
276 SWF::TextRecord record
;
279 if (_textRecords
.size() != 0) {
280 while (i
< _textRecords
.size() && m_cursor
>= _recordStarts
[i
]) {
289 TextField::display(Renderer
& renderer
, const Transform
& base
)
291 const DisplayObject::MaskRenderer
mr(renderer
, *this);
293 registerTextVariable();
295 const bool drawBorder
= getDrawBorder();
296 const bool drawBackground
= getDrawBackground();
298 Transform xform
= base
* transform();
300 // This is a hack to handle device fonts, which are not affected by
302 if (!getEmbedFonts()) xform
.colorTransform
= SWFCxForm();
304 if ((drawBorder
|| drawBackground
) && !_bounds
.is_null()) {
306 boost::int32_t xmin
= _bounds
.get_x_min();
307 boost::int32_t xmax
= _bounds
.get_x_max();
308 boost::int32_t ymin
= _bounds
.get_y_min();
309 boost::int32_t ymax
= _bounds
.get_y_max();
311 const std::vector
<point
> coords
= boost::assign::list_of
317 rgba borderColor
= drawBorder
? getBorderColor() : rgba(0,0,0,0);
318 rgba backgroundColor
= drawBackground
? getBackgroundColor() :
321 SWFCxForm cx
= xform
.colorTransform
;
323 if (drawBorder
) borderColor
= cx
.transform(borderColor
);
325 if (drawBackground
) backgroundColor
= cx
.transform(backgroundColor
);
327 #ifdef GNASH_DEBUG_TEXTFIELDS
328 log_debug("rendering a Pol composed by corners %s", _bounds
);
331 renderer
.draw_poly(coords
, backgroundColor
,
332 borderColor
, xform
.matrix
, true);
336 // Draw our actual text.
337 // Using a SWFMatrix to translate to def bounds seems an hack to me.
338 // A cleaner implementation is likely correctly setting the
339 // _xOffset and _yOffset memebers in glyph records.
340 // Anyway, see bug #17954 for a testcase.
341 if (!_bounds
.is_null()) {
342 xform
.matrix
.concatenate_translation(_bounds
.get_x_min(),
343 _bounds
.get_y_min());
346 _displayRecords
.clear();
347 float scale
= getFontHeight() /
348 static_cast<float>(_font
->unitsPerEM(_embedFonts
));
349 float fontLeading
= _font
->leading() * scale
;
352 int yoffset
= (getFontHeight() + fontLeading
) + PADDING_TWIPS
;
354 for (size_t i
= 0; i
< _textRecords
.size(); ++i
) {
356 //find the line the record is on
357 while (recordline
< _line_starts
.size() &&
358 _line_starts
[recordline
] <= _recordStarts
[i
]) {
362 _textRecords
[i
].setYOffset((recordline
-_scroll
)*yoffset
);
363 //add the lines we want to the display record
364 if (_textRecords
[i
].yOffset() > 0 &&
365 _textRecords
[i
].yOffset() < _bounds
.height()) {
366 _displayRecords
.push_back(_textRecords
[i
]);
370 SWF::TextRecord::displayRecords(renderer
, xform
, _displayRecords
,
373 if (m_has_focus
&& !isReadOnly()) show_cursor(renderer
, xform
.matrix
);
380 TextField::add_invalidated_bounds(InvalidatedRanges
& ranges
, bool force
)
382 if (!force
&& !invalidated()) return; // no need to redraw
384 ranges
.add(m_old_invalidated_ranges
);
386 const SWFMatrix
& wm
= getWorldMatrix(*this);
388 SWFRect bounds
= getBounds();
389 bounds
.expand_to_rect(m_text_bounding_box
);
390 wm
.transform(bounds
);
391 ranges
.add(bounds
.getRange());
395 TextField::setRestrict(const std::string
& restrict
)
397 _restrictDefined
= true;
399 std::string::const_iterator rit
= restrict
.begin();
400 std::string::const_iterator re
= restrict
.end();
401 std::set
<wchar_t>::const_iterator locate
;
403 if (*rit
== '^') { //then this is a true RESTRICT pattern, add all chars to _restrictedchars
404 for (unsigned int i
= 0; i
<= 255; ++i
) {
405 _restrictedchars
.insert(char(i
));
407 } else { //then this is an ALLOW pattern, _restrictedchars should remain empty
408 _restrictedchars
.clear();
412 while (rit
!= re
&& *rit
!= '^') { //This loop allows chars
414 log_error("invalid restrict string");
416 } else if (*(rit
+1) == '-') {
417 if (re
- (rit
+2) != 0) {
418 unsigned int q
= *(rit
+2);
419 for (unsigned int p
= *rit
; p
<= q
; (++p
)){
420 _restrictedchars
.insert(char(p
));
424 log_error("invalid restrict string");
427 } else if (*rit
== '\\') {
429 _restrictedchars
.insert(*rit
);
432 _restrictedchars
.insert(*rit
);
439 while (rit
!= re
&& *rit
!= '^') { //This loop restricts chars
440 locate
= _restrictedchars
.find(*rit
);
442 log_error("invalid restrict string");
444 } else if (*(rit
+1) == '-') {
445 if (re
- (rit
+2) != 0) {
446 unsigned int q
= *(rit
+2);
447 for (unsigned int p
= *rit
; p
<= q
; ++p
){
448 locate
= _restrictedchars
.find(p
);
449 if(locate
!= _restrictedchars
.end()) {
450 _restrictedchars
.erase(locate
);
457 log_error("invalid restrict string");
460 } else if (*rit
== '\\') {
462 locate
= _restrictedchars
.find(*rit
);
463 if(locate
!= _restrictedchars
.end()) {
464 _restrictedchars
.erase(locate
);
468 if(locate
!= _restrictedchars
.end()) {
469 _restrictedchars
.erase(locate
);
478 _restrict
= restrict
;
482 TextField::replaceSelection(const std::string
& replace
)
484 const int version
= getSWFVersion(*getObject(this));
485 const std::wstring
& wstr
= utf8::decodeCanonicalString(replace
, version
);
487 assert(_selection
.second
>= _selection
.first
);
488 assert(_selection
.second
<= _text
.size());
489 assert(_selection
.first
<= _text
.size());
491 // If the text has changed but the selection hasn't, make sure we
492 // don't access it out of bounds.
493 const size_t start
= _selection
.first
;
494 const size_t end
= _selection
.second
;
496 const size_t replaceLength
= wstr
.size();
498 _text
.replace(start
, end
- start
, wstr
);
499 _selection
= std::make_pair(start
+ replaceLength
, start
+ replaceLength
);
503 TextField::setSelection(int start
, int end
)
506 _selection
= std::make_pair(0, 0);
510 const size_t textLength
= _text
.size();
512 if (start
< 0) start
= 0;
513 else start
= std::min
<size_t>(start
, textLength
);
515 if (end
< 0) end
= 0;
516 else end
= std::min
<size_t>(end
, textLength
);
518 // The cursor position is always set to the end value, even if the
519 // two values are swapped to obtain the selection. Equal values are
522 if (start
> end
) std::swap(start
, end
);
524 _selection
= std::make_pair(start
, end
);
528 TextField::notifyEvent(const event_id
& ev
)
532 case event_id::PRESS
:
534 movie_root
& root
= stage();
535 boost::int32_t x_mouse
, y_mouse
;
536 boost::tie(x_mouse
, y_mouse
) = root
.mousePosition();
538 SWFMatrix m
= getMatrix(*this);
540 x_mouse
-= m
.get_x_translation();
541 y_mouse
-= m
.get_y_translation();
545 for (size_t i
=0; i
< _textRecords
.size(); ++i
) {
546 if ((x_mouse
> _textRecords
[i
].xOffset()) &&
547 (x_mouse
< _textRecords
[i
].xOffset()+_textRecords
[i
].recordWidth()) &&
548 (y_mouse
> _textRecords
[i
].yOffset()-_textRecords
[i
].textHeight()) &&
549 (y_mouse
< _textRecords
[i
].yOffset())) {
550 rec
= _textRecords
[i
];
555 if (!rec
.getURL().empty()) {
556 root
.getURL(rec
.getURL(), rec
.getTarget(), "",
557 MovieClip::METHOD_NONE
);
562 case event_id::KEY_PRESS
:
564 setHtml(false); //editable html fields are not yet implemented
565 std::wstring s
= _text
;
567 // id.keyCode is the unique gnash::key::code for a DisplayObject/key.
568 // The maximum value is about 265, including function keys.
569 // It seems that typing in DisplayObjects outside the Latin-1 set
570 // (256 DisplayObject codes, identical to the first 256 of UTF-8)
571 // is not supported, though a much greater number UTF-8 codes can be
572 // stored and displayed. See utf.h for more information.
573 // This is a limit on the number of key codes, not on the
574 // capacity of strings.
575 gnash::key::code c
= ev
.keyCode();
578 // maybe _text is changed in ActionScript
579 m_cursor
= std::min
<size_t>(m_cursor
, _text
.size());
581 size_t cur_cursor
= m_cursor
;
582 size_t previouslinesize
= 0;
583 size_t nextlinesize
= 0;
584 size_t manylines
= _line_starts
.size();
585 LineStarts::iterator linestartit
= _line_starts
.begin();
586 LineStarts::const_iterator linestartend
= _line_starts
.end();
591 if (isReadOnly()) return;
594 s
.erase(m_cursor
- 1, 1);
601 if (isReadOnly()) return;
602 if (_glyphcount
> m_cursor
)
604 s
.erase(m_cursor
, 1);
609 case key::INSERT
: // TODO
610 if (isReadOnly()) return;
614 while ( linestartit
< linestartend
&& *linestartit
<= m_cursor
) {
615 cur_cursor
= *linestartit
;
618 m_cursor
= cur_cursor
;
622 // if going a page up is too far...
623 if(_scroll
< _linesindisplay
) {
626 } else { // go a page up
627 _scroll
-= _linesindisplay
;
628 m_cursor
= _line_starts
[_scroll
];
634 while ( linestartit
< linestartend
&& *linestartit
<= m_cursor
) {
635 cur_cursor
= *linestartit
;
638 //if there is no previous line
639 if ( linestartit
-_line_starts
.begin() - 2 < 0 ) {
643 previouslinesize
= _textRecords
[linestartit
-_line_starts
.begin() - 2].glyphs().size();
644 //if the previous line is smaller
645 if (m_cursor
- cur_cursor
> previouslinesize
) {
646 m_cursor
= *(--(--linestartit
)) + previouslinesize
;
648 m_cursor
= *(--(--linestartit
)) + (m_cursor
- cur_cursor
);
650 if (m_cursor
< _line_starts
[_scroll
] && _line_starts
[_scroll
] != 0) {
657 while ( linestartit
< linestartend
&& *linestartit
<= m_cursor
) {
660 m_cursor
= linestartit
!= linestartend
? *linestartit
- 1 : _text
.size();
664 //if going another page down is too far...
665 if(_scroll
+ _linesindisplay
>= manylines
) {
666 if(manylines
- _linesindisplay
<= 0) {
669 _scroll
= manylines
- _linesindisplay
;
671 if(m_cursor
< _line_starts
[_scroll
-1]) {
672 m_cursor
= _line_starts
[_scroll
-1];
674 m_cursor
= _text
.size();
676 } else { //go a page down
677 _scroll
+= _linesindisplay
;
678 m_cursor
= _line_starts
[_scroll
];
685 while (linestartit
< linestartend
&&
686 *linestartit
<= m_cursor
) {
687 cur_cursor
= *linestartit
;
691 // linestartit should never be before _line_starts.begin()
692 const size_t currentLine
= linestartit
-
693 _line_starts
.begin();
695 //if there is no next line
696 if (currentLine
>= manylines
) {
697 m_cursor
= _text
.size();
700 nextlinesize
= _textRecords
[currentLine
].glyphs().size();
702 //if the next line is smaller
703 if (m_cursor
- cur_cursor
> nextlinesize
) {
704 m_cursor
= *linestartit
+ nextlinesize
;
706 //put the cursor at the same character distance
707 m_cursor
= *(linestartit
) + (m_cursor
- cur_cursor
);
709 if (_line_starts
.size() > _linesindisplay
&&
710 m_cursor
>= _line_starts
[_scroll
+_linesindisplay
]) {
718 m_cursor
= m_cursor
> 0 ? m_cursor
- 1 : 0;
722 m_cursor
= m_cursor
< _glyphcount
? m_cursor
+ 1 :
727 if (isReadOnly()) return;
728 if (!multiline()) break;
732 if (maxChars() != 0) {
733 if (_maxChars
<= _glyphcount
) {
738 if (isReadOnly()) return;
739 wchar_t t
= static_cast<wchar_t>(
740 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
)
777 if (!visible()) return 0;
779 // Not selectable, so don't catch mouse events!
780 if (!_selectable
) return 0;
782 SWFMatrix m
= getMatrix(*this);
784 m
.invert().transform(p
);
786 if (_bounds
.point_test(p
.x
, p
.y
)) return this;
792 TextField::updateText(const std::string
& str
)
794 const int version
= getSWFVersion(*getObject(this));
795 const std::wstring
& wstr
= utf8::decodeCanonicalString(str
, version
);
800 TextField::updateText(const std::wstring
& wstr
)
803 if (_text
== wstr
) return;
809 _selection
.first
= std::min(_selection
.first
, _text
.size());
810 _selection
.second
= std::min(_selection
.second
, _text
.size());
816 TextField::updateHtmlText(const std::wstring
& wstr
)
818 if (_htmlText
== wstr
) return;
827 TextField::setTextValue(const std::wstring
& wstr
)
829 updateHtmlText(wstr
);
832 if (!_variable_name
.empty() && _text_variable_registered
) {
833 // TODO: notify MovieClip if we have a variable name !
834 VariableRef ref
= parseTextVariableRef(_variable_name
);
835 as_object
* tgt
= ref
.first
;
837 const int version
= getSWFVersion(*getObject(this));
838 // we shouldn't truncate, right?
839 tgt
->set_member(ref
.second
, utf8::encodeCanonicalString(wstr
,
843 // nothing to do (too early ?)
844 log_debug("setTextValue: variable name %s points to a non-existent"
845 " target, I guess we would not be registered if this was "
846 "true, or the sprite we've registered our variable name "
847 "has been unloaded", _variable_name
);
853 TextField::get_text_value() const
855 // we need the const_cast here because registerTextVariable
856 // *might* change our text value, calling the non-const
858 // This happens if the TextVariable has not been already registered
859 // and during registration comes out to name an existing variable
860 // with a pre-existing value.
861 const_cast<TextField
*>(this)->registerTextVariable();
863 const int version
= getSWFVersion(*getObject(this));
865 return utf8::encodeCanonicalString(_text
, version
);
869 TextField::get_htmltext_value() const
871 const_cast<TextField
*>(this)->registerTextVariable();
872 const int version
= getSWFVersion(*getObject(this));
873 return utf8::encodeCanonicalString(_htmlText
, version
);
877 TextField::setTextFormat(TextFormat_as
& tf
)
879 //TODO: this is lazy. we should set all the TextFormat variables HERE, i think
880 //This is just so we can set individual variables without having to call format_text()
881 //This calls format_text() at the end of setting TextFormat
882 if (tf
.align()) setAlignment(*tf
.align());
883 if (tf
.size()) setFontHeight(*tf
.size()); // keep twips
884 if (tf
.indent()) setIndent(*tf
.indent());
885 if (tf
.blockIndent()) setBlockIndent(*tf
.blockIndent());
886 if (tf
.leading()) setLeading(*tf
.leading());
887 if (tf
.leftMargin()) setLeftMargin(*tf
.leftMargin());
888 if (tf
.rightMargin()) setRightMargin(*tf
.rightMargin());
889 if (tf
.color()) setTextColor(*tf
.color());
890 if (tf
.underlined()) setUnderlined(*tf
.underlined());
891 if (tf
.bullet()) setBullet(*tf
.bullet());
892 setDisplay(tf
.display());
893 if (tf
.tabStops()) setTabStops(*tf
.tabStops());
895 // NEED TO IMPLEMENT THESE TWO
896 if (tf
.url()) setURL(*tf
.url());
897 if (tf
.target()) setTarget(*tf
.target());
903 TextField::align_line(TextAlignment align
, int last_line_start_record
, float x
)
905 float width
= _bounds
.width();
906 float right_margin
= getRightMargin();
908 float extra_space
= (width
- right_margin
) - x
- PADDING_TWIPS
;
910 if (extra_space
<= 0.0f
) {
911 #ifdef GNASH_DEBUG_TEXTFIELDS
912 log_debug(_("TextField text doesn't fit in its boundaries: "
913 "width %g, margin %g - nothing to align"),
914 width
, right_margin
);
919 float shift_right
= 0.0f
;
923 // Nothing to do; already aligned left.
926 // Distribute the space evenly on both sides.
927 shift_right
= extra_space
/ 2;
930 // Shift all the way to the right.
931 shift_right
= extra_space
;
934 // What should we do here?
938 // Shift the beginnings of the records on this line.
939 for (size_t i
= last_line_start_record
; i
< _textRecords
.size(); ++i
) {
940 SWF::TextRecord
& rec
= _textRecords
[i
];
941 rec
.setXOffset(rec
.xOffset() + shift_right
);
946 boost::intrusive_ptr
<const Font
>
947 TextField::setFont(boost::intrusive_ptr
<const Font
> newfont
)
949 if (newfont
== _font
) return _font
;
951 boost::intrusive_ptr
<const Font
> oldfont
= _font
;
960 TextField::insertTab(SWF::TextRecord
& rec
, boost::int32_t& x
, float scale
)
963 const int space
= 32;
964 int index
= rec
.getFont()->get_glyph_index(space
, _embedFonts
);
966 IF_VERBOSE_MALFORMED_SWF (
967 log_error(_("TextField: missing glyph for space char (needed "
968 "for TAB). Make sure DisplayObject shapes for font "
969 "%s are being exported into your SWF file."),
970 rec
.getFont()->name());
974 // TODO: why is there a copy of the vector?
975 std::vector
<int> tabStops
= _tabStops
;
977 std::sort(_tabStops
.begin(), _tabStops
.end());
980 if (!_tabStops
.empty()) {
981 tab
= _tabStops
.back() + 1;
983 for (size_t i
= 0; i
< tabStops
.size(); ++i
) {
984 if (tabStops
[i
] > x
) {
985 if((tabStops
[i
] - x
) < tab
) {
986 tab
= tabStops
[i
] - x
;
992 // This is necessary in case the number of tabs in the text
993 // are more than the actual number of tabStops inside the
995 if (tab
!= _tabStops
.back() + 1) {
996 SWF::TextRecord::GlyphEntry ge
;
997 ge
.index
= rec
.getFont()->get_glyph_index(32, _embedFonts
);
1004 SWF::TextRecord::GlyphEntry ge
;
1006 ge
.advance
= scale
* rec
.getFont()->get_advance(index
,
1009 const int tabstop
= 4;
1010 rec
.addGlyph(ge
, tabstop
);
1011 x
+= ge
.advance
* tabstop
;
1017 TextField::format_text()
1019 _textRecords
.clear();
1020 _line_starts
.clear();
1021 _recordStarts
.clear();
1024 _recordStarts
.push_back(0);
1026 // nothing more to do if text is empty
1027 if (_text
.empty()) {
1028 // TODO: should we still reset _bounds if autoSize != AUTOSIZE_NONE ?
1029 // not sure we should...
1030 reset_bounding_box(0, 0);
1034 LineStarts::iterator linestartit
= _line_starts
.begin();
1035 LineStarts::const_iterator linestartend
= _line_starts
.end();
1037 AutoSize autoSize
= getAutoSize();
1038 if (autoSize
!= AUTOSIZE_NONE
) {
1039 // When doing WordWrap we don't want to change
1040 // the boundaries. See bug #24348
1041 if (!doWordWrap()) {
1042 _bounds
.set_to_rect(0, 0, 0, 0); // this is correct for 'true'
1046 // FIXME: I don't think we should query the definition
1047 // to find the appropriate font to use, as ActionScript
1048 // code should be able to change the font of a TextField
1050 log_error(_("No font for TextField!"));
1054 boost::uint16_t fontHeight
= getFontHeight();
1055 float scale
= fontHeight
/
1056 static_cast<float>(_font
->unitsPerEM(_embedFonts
));
1057 const float fontLeading
= _font
->leading() * scale
;
1058 const boost::uint16_t leftMargin
= getLeftMargin();
1059 const boost::uint16_t indent
= getIndent();
1060 const boost::uint16_t blockIndent
= getBlockIndent();
1061 const bool underlined
= getUnderlined();
1063 /// Remember the current bounds for autosize.
1064 SWFRect
oldBounds(_bounds
);
1066 SWF::TextRecord rec
; // one to work on
1067 rec
.setFont(_font
.get());
1068 rec
.setUnderline(underlined
);
1069 rec
.setColor(getTextColor());
1070 rec
.setXOffset(PADDING_TWIPS
+
1071 std::max(0, leftMargin
+ indent
+ blockIndent
));
1072 rec
.setYOffset(PADDING_TWIPS
+ fontHeight
+ fontLeading
);
1073 rec
.setTextHeight(fontHeight
);
1075 // create in textrecord.h
1077 rec
.setTarget(_target
);
1081 // First, we indent 10 spaces, and then place the bullet
1082 // character (in this case, an asterisk), then we pad it
1083 // again with 10 spaces
1084 // Note: this works only for additional lines of a
1085 // bulleted list, so that is why there is a bullet format
1086 // in the beginning of format_text()
1088 int space
= rec
.getFont()->get_glyph_index(32, _embedFonts
);
1090 SWF::TextRecord::GlyphEntry ge
;
1092 ge
.advance
= scale
* rec
.getFont()->get_advance(space
, _embedFonts
);
1093 rec
.addGlyph(ge
, 5);
1095 // We use an asterisk instead of a bullet
1096 int bullet
= rec
.getFont()->get_glyph_index(42, _embedFonts
);
1098 ge
.advance
= scale
* rec
.getFont()->get_advance(bullet
, _embedFonts
);
1101 space
= rec
.getFont()->get_glyph_index(32, _embedFonts
);
1103 ge
.advance
= scale
* rec
.getFont()->get_advance(space
, _embedFonts
);
1104 rec
.addGlyph(ge
, 4);
1107 boost::int32_t x
= static_cast<boost::int32_t>(rec
.xOffset());
1108 boost::int32_t y
= static_cast<boost::int32_t>(rec
.yOffset());
1110 // Start the bbox at the upper-left corner of the first glyph.
1111 //reset_bounding_box(x, y + fontHeight);
1113 int last_code
= -1; // only used if _embedFonts
1114 int last_space_glyph
= -1;
1115 size_t last_line_start_record
= 0;
1117 _line_starts
.push_back(0);
1119 // String iterators are very sensitive to
1120 // potential changes to the string (to allow for copy-on-write).
1121 // So there must be no external changes to the string or
1122 // calls to most non-const member functions during this loop.
1123 // Especially not c_str() or data().
1124 std::wstring::const_iterator it
= _text
.begin();
1125 const std::wstring::const_iterator e
= _text
.end();
1127 ///handleChar takes care of placing the glyphs
1128 handleChar(it
, e
, x
, y
, rec
, last_code
, last_space_glyph
,
1129 last_line_start_record
);
1131 // Expand bounding box to include the whole text (if autoSize and wordWrap
1132 // is not in operation.
1133 if (_autoSize
!= AUTOSIZE_NONE
&& !doWordWrap())
1135 _bounds
.expand_to_point(x
+ PADDING_TWIPS
, y
+ PADDING_TWIPS
);
1137 if (_autoSize
== AUTOSIZE_RIGHT
) {
1138 /// Autosize right expands from the previous right margin.
1141 m
.set_x_translation(oldBounds
.get_x_max() - _bounds
.width());
1142 m
.transform(_bounds
);
1144 else if (_autoSize
== AUTOSIZE_CENTER
) {
1145 // Autosize center expands from the previous center.
1147 m
.set_x_translation(oldBounds
.get_x_min() + oldBounds
.width() / 2.0 -
1148 _bounds
.width() / 2.0);
1149 m
.transform(_bounds
);
1153 // Add the last line to our output.
1154 _textRecords
.push_back(rec
);
1156 // align the last (or single) line
1157 align_line(getTextAlignment(), last_line_start_record
, x
);
1161 set_invalidated(); //redraw
1166 TextField::scrollLines()
1168 boost::uint16_t fontHeight
= getFontHeight();
1169 float scale
= fontHeight
/
1170 static_cast<float>(_font
->unitsPerEM(_embedFonts
));
1171 float fontLeading
= _font
->leading() * scale
;
1172 _linesindisplay
= _bounds
.height() / (fontHeight
+ fontLeading
+ PADDING_TWIPS
);
1173 if (_linesindisplay
> 0) { //no need to place lines if we can't fit any
1174 size_t manylines
= _line_starts
.size();
1175 size_t lastvisibleline
= _scroll
+ _linesindisplay
;
1178 // If there aren't as many lines as we have scrolled, display the
1180 if (manylines
< _scroll
) {
1181 _scroll
= manylines
- _linesindisplay
;
1185 // which line is the cursor on?
1186 while (line
< manylines
&& _line_starts
[line
] <= m_cursor
) {
1190 if (manylines
- _scroll
<= _linesindisplay
) {
1191 // This is for if we delete a line
1192 if (manylines
< _linesindisplay
) _scroll
= 0;
1194 _scroll
= manylines
- _linesindisplay
;
1196 } else if (line
< _scroll
) {
1197 //if we are at a higher position, scroll the lines down
1198 _scroll
-= _scroll
- line
;
1199 } else if (manylines
> _scroll
+ _linesindisplay
) {
1200 //if we are at a lower position, scroll the lines up
1201 if (line
>= (_scroll
+_linesindisplay
)) {
1202 _scroll
+= line
- (lastvisibleline
);
1209 TextField::newLine(boost::int32_t& x
, boost::int32_t& y
,
1210 SWF::TextRecord
& rec
, int& last_space_glyph
,
1211 LineStarts::value_type
& last_line_start_record
, float div
)
1214 LineStarts::iterator linestartit
= _line_starts
.begin();
1215 LineStarts::const_iterator linestartend
= _line_starts
.end();
1217 float scale
= _fontHeight
/
1218 static_cast<float>(_font
->unitsPerEM(_embedFonts
));
1219 float fontLeading
= _font
->leading() * scale
;
1220 float leading
= getLeading();
1221 leading
+= fontLeading
* scale
; // not sure this is correct...
1223 // Close out this stretch of glyphs.
1225 _textRecords
.push_back(rec
);
1226 _recordStarts
.push_back(_glyphcount
);
1227 align_line(getTextAlignment(), last_line_start_record
, x
);
1229 // Expand bounding box to include last column of text ...
1230 if (!doWordWrap() && _autoSize
!= AUTOSIZE_NONE
) {
1231 _bounds
.expand_to_point(x
+ PADDING_TWIPS
, y
+ PADDING_TWIPS
);
1234 // new paragraphs get the indent.
1235 x
= std::max(0, getLeftMargin() + getIndent() + getBlockIndent()) +
1237 y
+= div
* (getFontHeight() + leading
);
1238 if (y
>= _bounds
.height()) {
1242 // Start a new record on the next line. Other properties of the
1243 // TextRecord should be left unchanged.
1248 last_space_glyph
= -1;
1249 last_line_start_record
= _textRecords
.size();
1251 linestartit
= _line_starts
.begin();
1252 linestartend
= _line_starts
.end();
1253 //Fit a line_start in the correct place
1254 const size_t currentPos
= _glyphcount
;
1256 while (linestartit
< linestartend
&& *linestartit
< currentPos
)
1260 _line_starts
.insert(linestartit
, currentPos
);
1264 // First, we indent 10 spaces, and then place the bullet
1265 // character (in this case, an asterisk), then we pad it
1266 // again with 10 spaces
1267 // Note: this works only for additional lines of a
1268 // bulleted list, so that is why there is a bullet format
1269 // in the beginning of format_text()
1272 int space
= rec
.getFont()->get_glyph_index(32, _embedFonts
);
1273 SWF::TextRecord::GlyphEntry ge
;
1275 ge
.advance
= scale
* rec
.getFont()->get_advance(space
, _embedFonts
);
1280 int bullet
= rec
.getFont()->get_glyph_index(42, _embedFonts
);
1282 ge
.advance
= scale
* rec
.getFont()->get_advance(bullet
, _embedFonts
);
1287 ge
.advance
= scale
* rec
.getFont()->get_advance(space
, _embedFonts
);
1295 TextField::handleChar(std::wstring::const_iterator
& it
,
1296 const std::wstring::const_iterator
& e
, boost::int32_t& x
,
1297 boost::int32_t& y
, SWF::TextRecord
& rec
, int& last_code
,
1298 int& last_space_glyph
, LineStarts::value_type
& last_line_start_record
)
1300 LineStarts::iterator linestartit
= _line_starts
.begin();
1301 LineStarts::const_iterator linestartend
= _line_starts
.end();
1303 float scale
= _fontHeight
/
1304 static_cast<float>(_font
->unitsPerEM(_embedFonts
));
1305 float fontDescent
= _font
->descent(_embedFonts
) * scale
;
1306 float fontLeading
= _font
->leading() * scale
;
1307 float leading
= getLeading();
1308 leading
+= fontLeading
* scale
; // not sure this is correct...
1310 boost::uint32_t code
= 0;
1318 x
+= rec
.getFont()->get_kerning_adjustment(last_code
,
1319 static_cast<int>(code
)) * scale
;
1320 last_code
= static_cast<int>(code
);
1323 // Expand the bounding-box to the lower-right corner of each glyph as
1325 m_text_bounding_box
.expand_to_point(x
, y
+ fontDescent
);
1332 insertTab(rec
, x
, scale
);
1337 // This is a limited hack to enable overstrike effects.
1338 // It backs the cursor up by one DisplayObject and then continues
1339 // the layout. E.g. you can use this to display an underline
1340 // cursor inside a simulated text-entry box.
1342 // ActionScript understands the '\b' escape sequence
1343 // for inserting a BS DisplayObject.
1345 // ONLY WORKS FOR BACKSPACING OVER ONE CHARACTER, WON'T BS
1346 // OVER NEWLINES, ETC.
1348 if (!rec
.glyphs().empty())
1350 // Peek at the previous glyph, and zero out its advance
1351 // value, so the next char overwrites it.
1352 float advance
= rec
.glyphs().back().advance
;
1361 newLine(x
,y
,rec
,last_space_glyph
,last_line_start_record
,1.0);
1367 //close out this stretch of glyphs
1368 _textRecords
.push_back(rec
);
1370 _recordStarts
.push_back(_glyphcount
);
1372 while (it
!= e
&& *it
!= '>') {
1378 LOG_ONCE(log_debug(_("HTML in a text field is unsupported, "
1379 "gnash will just ignore the tags and "
1380 "print their content")));
1382 std::wstring discard
;
1383 std::map
<std::string
,std::string
> attributes
;
1384 SWF::TextRecord newrec
;
1385 newrec
.setFont(rec
.getFont());
1386 newrec
.setUnderline(rec
.underline());
1387 newrec
.setColor(rec
.color());
1388 newrec
.setTextHeight(rec
.textHeight());
1389 newrec
.setXOffset(x
);
1390 newrec
.setYOffset(y
);
1391 bool selfclosing
= false;
1392 bool complete
= parseHTML(discard
, attributes
, it
, e
, selfclosing
);
1393 std::string
s(discard
.begin(), discard
.end());
1395 std::map
<std::string
,std::string
>::const_iterator attloc
;
1398 //parsing went wrong
1401 // Don't think this is the best way to match with
1403 // TODO: assumes tags are properly nested. This isn't
1407 newrec
.setUnderline(true);
1408 handleChar(it
, e
, x
, y
, newrec
, last_code
,
1409 last_space_glyph
, last_line_start_record
);
1411 else if (s
== "A") {
1412 // anchor (blue text).
1413 rgba
color(0, 0, 0xff, 0xff);
1414 newrec
.setColor(color
);
1415 newrec
.setUnderline(true);
1416 attloc
= attributes
.find("HREF");
1417 if (attloc
!= attributes
.end()) {
1418 newrec
.setURL(attloc
->second
);
1420 attloc
= attributes
.find("TARGET");
1421 if (attloc
!=attributes
.end()) {
1422 newrec
.setTarget(attloc
->second
);
1424 handleChar(it
, e
, x
, y
, newrec
, last_code
,
1425 last_space_glyph
, last_line_start_record
);
1427 else if (s
== "B") {
1429 Font
* boldfont
= new Font(rec
.getFont()->name(),
1430 true, rec
.getFont()->isItalic());
1431 newrec
.setFont(boldfont
);
1432 handleChar(it
, e
, x
, y
, newrec
, last_code
,
1433 last_space_glyph
, last_line_start_record
);
1435 else if (s
== "FONT") {
1437 boost::uint16_t originalsize
= _fontHeight
;
1438 attloc
= attributes
.find("COLOR");
1439 if (attloc
!= attributes
.end()) {
1440 std::string
hexval(attloc
->second
);
1441 if (hexval
.empty() || hexval
[0] != '#') {
1442 // FIXME: should this be a log_aserror
1443 // or log_unimpl ? It is triggered
1444 // by TextFieldHTML.as
1445 log_error("Unexpected value '%s' in "
1446 "TextField font color attribute",
1451 // font COLOR attribute
1453 colorFromHexString(hexval
);
1454 newrec
.setColor(color
);
1457 attloc
= attributes
.find("FACE");
1458 if (attloc
!= attributes
.end()) {
1459 //font FACE attribute
1460 Font
* newfont
= new Font(attloc
->second
,
1461 rec
.getFont()->isBold(), rec
.getFont()->isItalic());
1462 newrec
.setFont(newfont
);
1464 attloc
= attributes
.find("SIZE");
1465 if (attloc
!= attributes
.end()) {
1466 //font SIZE attribute
1467 std::string firstchar
= attloc
->second
.substr(0,1);
1468 if (firstchar
== "+") {
1469 newrec
.setTextHeight(rec
.textHeight() +
1471 (pixelsToTwips(std::strtol(
1472 attloc
->second
.substr(1,attloc
->second
.length()-1).data(),
1474 newrec
.setYOffset(PADDING_TWIPS
+
1475 newrec
.textHeight() +
1476 (fontLeading
- fontDescent
));
1477 _fontHeight
+= pixelsToTwips(std::strtol(
1478 attloc
->second
.substr(1,attloc
->second
.length()-1).data(),
1480 } else if (firstchar
== "-") {
1481 newrec
.setTextHeight(rec
.textHeight() -
1482 (pixelsToTwips(std::strtol(
1483 attloc
->second
.substr(1,attloc
->second
.length()-1).data(),
1485 newrec
.setYOffset(PADDING_TWIPS
+
1486 newrec
.textHeight() +
1487 (fontLeading
- fontDescent
));
1488 _fontHeight
-= pixelsToTwips(std::strtol(
1489 attloc
->second
.substr(1,attloc
->second
.length()-1).data(),
1492 newrec
.setTextHeight(pixelsToTwips(std::strtol(
1493 attloc
->second
.data(), NULL
, 10)));
1494 newrec
.setYOffset(PADDING_TWIPS
+ newrec
.textHeight() +
1495 (fontLeading
- fontDescent
));
1496 _fontHeight
= pixelsToTwips(std::strtol(
1497 attloc
->second
.data(), NULL
, 10));
1500 handleChar(it
, e
, x
, y
, newrec
, last_code
,
1501 last_space_glyph
, last_line_start_record
);
1502 _fontHeight
= originalsize
;
1503 y
= newrec
.yOffset();
1505 else if (s
== "IMG") {
1507 log_unimpl("<img> html tag in TextField");
1508 handleChar(it
, e
, x
, y
, newrec
, last_code
,
1509 last_space_glyph
, last_line_start_record
);
1511 else if (s
== "I") {
1513 Font
* italicfont
= new Font(rec
.getFont()->name(),
1514 rec
.getFont()->isBold(), true);
1515 newrec
.setFont(italicfont
);
1516 handleChar(it
, e
, x
, y
, newrec
, last_code
,
1517 last_space_glyph
, last_line_start_record
);
1518 } else if (s
== "LI") {
1519 //list item (bullet)
1520 int space
= newrec
.getFont()->get_glyph_index(32, _embedFonts
);
1521 SWF::TextRecord::GlyphEntry ge
;
1523 ge
.advance
= scale
* newrec
.getFont()->get_advance(space
, _embedFonts
);
1524 newrec
.addGlyph(ge
, 5);
1526 // We use an asterisk instead of a bullet
1527 int bullet
= newrec
.getFont()->get_glyph_index(42, _embedFonts
);
1529 ge
.advance
= scale
* newrec
.getFont()->get_advance(bullet
, _embedFonts
);
1530 newrec
.addGlyph(ge
);
1532 space
= newrec
.getFont()->get_glyph_index(32, _embedFonts
);
1534 ge
.advance
= scale
* newrec
.getFont()->get_advance(space
, _embedFonts
);
1535 newrec
.addGlyph(ge
, 4);
1537 handleChar(it
, e
, x
, y
, newrec
, last_code
,
1538 last_space_glyph
, last_line_start_record
);
1539 newLine(x
, y
, newrec
, last_space_glyph
,
1540 last_line_start_record
, 1.0);
1542 else if (s
== "SPAN") {
1544 log_unimpl("<span> html tag in TextField");
1545 handleChar(it
, e
, x
, y
, newrec
, last_code
,
1546 last_space_glyph
, last_line_start_record
);
1548 else if (s
== "TEXTFORMAT") {
1549 log_debug("in textformat");
1551 boost::uint16_t originalblockindent
= getBlockIndent();
1552 boost::uint16_t originalindent
= getIndent();
1553 boost::uint16_t originalleading
= getLeading();
1554 boost::uint16_t originalleftmargin
= getLeftMargin();
1555 boost::uint16_t originalrightmargin
= getRightMargin();
1556 std::vector
<int> originaltabstops
= getTabStops();
1557 attloc
= attributes
.find("BLOCKINDENT");
1558 if (attloc
!= attributes
.end()) {
1559 //textformat BLOCKINDENT attribute
1560 setBlockIndent(pixelsToTwips(std::strtol(
1561 attloc
->second
.data(), NULL
, 10)));
1562 if (newrec
.xOffset() == std::max(0, originalleftmargin
+
1563 originalindent
+ originalblockindent
) + PADDING_TWIPS
) {
1564 //if beginning of line, indent
1565 x
= std::max(0, getLeftMargin() +
1566 getIndent() + getBlockIndent())
1568 newrec
.setXOffset(x
);
1571 attloc
= attributes
.find("INDENT");
1572 if (attloc
!= attributes
.end()) {
1573 //textformat INDENT attribute
1574 setIndent(pixelsToTwips(std::strtol(
1575 attloc
->second
.data(), NULL
, 10)));
1576 if (newrec
.xOffset() == std::max(0, originalleftmargin
+
1577 originalindent
+ getBlockIndent()) + PADDING_TWIPS
) {
1578 //if beginning of line, indent
1579 x
= std::max(0, getLeftMargin() +
1580 getIndent() + getBlockIndent())
1582 newrec
.setXOffset(x
);
1585 attloc
= attributes
.find("LEADING");
1586 if (attloc
!= attributes
.end()) {
1587 //textformat LEADING attribute
1588 setLeading(pixelsToTwips(std::strtol(
1589 attloc
->second
.data(), NULL
, 10)));
1591 attloc
= attributes
.find("LEFTMARGIN");
1592 if (attloc
!= attributes
.end()) {
1593 //textformat LEFTMARGIN attribute
1594 setLeftMargin(pixelsToTwips(std::strtol(
1595 attloc
->second
.data(), NULL
, 10)));
1596 if (newrec
.xOffset() == std::max(0, originalleftmargin
+
1597 getIndent() + getBlockIndent()) + PADDING_TWIPS
) {
1598 //if beginning of line, indent
1599 x
= std::max(0, getLeftMargin() +
1600 getIndent() + getBlockIndent())
1602 newrec
.setXOffset(x
);
1605 attloc
= attributes
.find("RIGHTMARGIN");
1606 if (attloc
!= attributes
.end()) {
1607 //textformat RIGHTMARGIN attribute
1608 setRightMargin(pixelsToTwips(std::strtol(
1609 attloc
->second
.data(), NULL
, 10)));
1610 //FIXME:Should not apply this to this line if we are not at
1611 //beginning of line. Not sure how to do that.
1613 attloc
= attributes
.find("TABSTOPS");
1614 if (attloc
!= attributes
.end()) {
1615 //textformat TABSTOPS attribute
1616 log_unimpl("html <textformat> tag tabstops attribute");
1618 handleChar(it
, e
, x
, y
, newrec
, last_code
,
1619 last_space_glyph
, last_line_start_record
);
1620 setBlockIndent(originalblockindent
);
1621 setIndent(originalindent
);
1622 setLeading(originalleading
);
1623 setLeftMargin(originalleftmargin
);
1624 setRightMargin(originalrightmargin
);
1625 setTabStops(originaltabstops
);
1627 else if (s
== "P") {
1629 if (_display
== TEXTFORMAT_BLOCK
) {
1630 handleChar(it
, e
, x
, y
, newrec
, last_code
,
1632 last_line_start_record
);
1633 newLine(x
, y
, rec
, last_space_glyph
,
1634 last_line_start_record
, 1.0);
1635 newLine(x
, y
, rec
, last_space_glyph
,
1636 last_line_start_record
, 1.5);
1639 handleChar(it
, e
, x
, y
, newrec
, last_code
,
1641 last_line_start_record
);
1644 else if (s
== "BR" || s
== "SBR") {
1646 newLine(x
, y
, rec
, last_space_glyph
,
1647 last_line_start_record
, 1.0);
1650 log_debug("<%s> tag is unsupported", s
);
1651 if (!selfclosing
) { //then recurse, look for closing tag
1652 handleChar(it
, e
, x
, y
, newrec
, last_code
,
1653 last_space_glyph
, last_line_start_record
);
1661 // If HTML isn't enabled, carry on and insert the glyph.
1662 // FIXME: do we also want to be changing last_space_glyph?
1663 // ...because we are...
1665 last_space_glyph
= rec
.glyphs().size();
1666 // Don't break, as we still need to insert the space glyph.
1675 SWF::TextRecord::GlyphEntry ge
;
1676 int bullet
= rec
.getFont()->get_glyph_index(42, _embedFonts
);
1678 ge
.advance
= scale
* rec
.getFont()->get_advance(bullet
,
1684 // The font table holds up to 65535 glyphs. Casting
1685 // from uint32_t would, in the event that the code
1686 // is higher than 65535, result in the wrong DisplayObject
1687 // being chosen. Flash can currently only handle 16-bit
1689 int index
= rec
.getFont()->get_glyph_index(
1690 static_cast<boost::uint16_t>(code
), _embedFonts
);
1692 IF_VERBOSE_MALFORMED_SWF (
1695 // Missing glyph! Log the first few errors.
1696 static int s_log_count
= 0;
1697 if (s_log_count
< 10)
1702 log_swferror(_("TextField: missing embedded "
1703 "glyph for char %d. Make sure DisplayObject "
1704 "shapes for font %s are being exported "
1705 "into your SWF file"),
1706 code
, _font
->name());
1710 log_swferror(_("TextField: missing device "
1711 "glyph for char %d. Maybe you don't have "
1712 "font '%s' installed in your system."),
1713 code
, _font
->name());
1717 // Drop through and use index == -1; this will display
1718 // using the empty-box glyph
1722 SWF::TextRecord::GlyphEntry ge
;
1724 ge
.advance
= scale
* rec
.getFont()->get_advance(index
,
1734 float width
= _bounds
.width();
1735 if (x
>= width
- getRightMargin() - PADDING_TWIPS
)
1737 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1738 log_debug("Text in TextField %s exceeds width [ _bounds %s ]",
1739 getTarget(), _bounds
);
1742 // No wrap and no resize: truncate
1743 if (!doWordWrap() && getAutoSize() == AUTOSIZE_NONE
)
1745 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1746 log_debug(" wordWrap=false, autoSize=none");
1748 // Truncate long line, but keep expanding text box
1749 bool newlinefound
= false;
1755 x
+= rec
.getFont()->get_kerning_adjustment(last_code
,
1756 static_cast<int>(code
)) * scale
;
1759 // Expand the bounding-box to the lower-right corner
1760 // of each glyph, even if we don't display it
1761 m_text_bounding_box
.expand_to_point(x
, y
+ fontDescent
);
1762 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1763 log_debug("Text bbox expanded to %s (width: %f)",
1764 m_text_bounding_box
, m_text_bounding_box
.width());
1767 if (code
== 13 || code
== 10)
1769 newlinefound
= true;
1773 int index
= rec
.getFont()->get_glyph_index(
1774 static_cast<boost::uint16_t>(code
), _embedFonts
);
1775 x
+= scale
* rec
.getFont()->get_advance(index
, _embedFonts
);
1778 if (!newlinefound
) break;
1780 else if (doWordWrap()) {
1782 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1783 log_debug(" wordWrap=true");
1786 // Insert newline if there's space or autosize != none
1788 // Close out this stretch of glyphs.
1789 _textRecords
.push_back(rec
);
1791 float previous_x
= x
;
1792 x
= std::max(0, getLeftMargin() + getBlockIndent()) + PADDING_TWIPS
;
1793 y
+= _fontHeight
+ leading
;
1794 if (y
>= _bounds
.height()) {
1798 // Start a new record on the next line.
1803 // TODO : what if m_text_glyph_records is empty ?
1805 assert(!_textRecords
.empty());
1806 SWF::TextRecord
& last_line
= _textRecords
.back();
1808 linestartit
= _line_starts
.begin();
1809 linestartend
= _line_starts
.end();
1810 if (last_space_glyph
== -1)
1812 // Pull the previous glyph down onto the
1814 if (!last_line
.glyphs().empty())
1816 rec
.addGlyph(last_line
.glyphs().back());
1817 x
+= last_line
.glyphs().back().advance
;
1818 previous_x
-= last_line
.glyphs().back().advance
;
1819 last_line
.clearGlyphs(1);
1820 //record the new line start
1822 const size_t currentPos
= _glyphcount
;
1823 while (linestartit
!= linestartend
&&
1824 *linestartit
+ 1 <= currentPos
)
1828 _line_starts
.insert(linestartit
, currentPos
);
1829 _recordStarts
.push_back(currentPos
);
1832 // Move the previous word down onto the next line.
1834 previous_x
-= last_line
.glyphs()[last_space_glyph
].advance
;
1836 const SWF::TextRecord::Glyphs::size_type lineSize
=
1837 last_line
.glyphs().size();
1838 for (unsigned int i
= last_space_glyph
+ 1; i
< lineSize
;
1841 rec
.addGlyph(last_line
.glyphs()[i
]);
1842 x
+= last_line
.glyphs()[i
].advance
;
1843 previous_x
-= last_line
.glyphs()[i
].advance
;
1845 last_line
.clearGlyphs(lineSize
- last_space_glyph
);
1847 // record the position at the start of this line as
1849 const size_t linestartpos
= _glyphcount
-
1850 rec
.glyphs().size();
1852 while (linestartit
< linestartend
&&
1853 *linestartit
< linestartpos
)
1857 _line_starts
.insert(linestartit
, linestartpos
);
1858 _recordStarts
.push_back(linestartpos
);
1861 align_line(getTextAlignment(), last_line_start_record
, previous_x
);
1863 last_space_glyph
= -1;
1864 last_line_start_record
= _textRecords
.size();
1869 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1870 log_debug(" wordWrap=%d, autoSize=%d", _wordWrap
, _autoSize
);
1878 TextField::getDefinitionVersion() const
1880 // TODO: work out if this correct.
1881 return get_root()->getDefinitionVersion();
1885 TextField::VariableRef
1886 TextField::parseTextVariableRef(const std::string
& variableName
) const
1891 #ifdef DEBUG_DYNTEXT_VARIABLES
1892 log_debug(_("VariableName: %s"), variableName
);
1895 /// Why isn't get_environment const again ?
1896 const as_environment
& env
= const_cast<TextField
*>(this)->get_environment();
1898 as_object
* target
= getObject(env
.target());
1900 IF_VERBOSE_MALFORMED_SWF(
1901 log_swferror(_("Current environment has no target, "
1902 "can't bind VariableName (%s) associated to "
1903 "text field. Gnash will try to register "
1904 "again on next access."), variableName
);
1909 // If the variable string contains a path, we extract
1910 // the appropriate target from it and update the variable
1911 // name. We copy the string so we can assign to it if necessary.
1912 std::string parsedName
= variableName
;
1913 std::string path
, var
;
1914 if (parsePath(variableName
, path
, var
)) {
1915 #ifdef DEBUG_DYNTEXT_VARIABLES
1916 log_debug(_("Variable text Path: %s, Var: %s"), path
, var
);
1918 // find target for the path component
1919 // we use our parent's environment for this
1920 target
= findObject(env
, path
);
1926 IF_VERBOSE_MALFORMED_SWF(
1927 log_swferror(_("VariableName associated to text field refers "
1928 "to an unknown target (%s). It is possible that the "
1929 "DisplayObject will be instantiated later in the SWF "
1930 "stream. Gnash will try to register again on next "
1937 ret
.second
= getURI(getVM(*object()), parsedName
);
1943 TextField::registerTextVariable()
1945 //#define DEBUG_DYNTEXT_VARIABLES 1
1947 #ifdef DEBUG_DYNTEXT_VARIABLES
1948 log_debug(_("registerTextVariable() called"));
1951 if (_text_variable_registered
) {
1955 if (_variable_name
.empty()) {
1956 _text_variable_registered
= true;
1960 VariableRef varRef
= parseTextVariableRef(_variable_name
);
1961 as_object
* target
= varRef
.first
;
1963 log_debug(_("VariableName associated to text field (%s) refer to "
1964 "an unknown target. It is possible that the DisplayObject "
1965 "will be instantiated later in the SWF stream. "
1966 "Gnash will try to register again on next access."),
1971 const ObjectURI
& key
= varRef
.second
;
1972 as_object
* obj
= getObject(this);
1973 const int version
= getSWFVersion(*obj
);
1975 // check if the VariableName already has a value,
1976 // in that case update text value
1978 if (target
->get_member(key
, &val
)) {
1979 // TODO: pass environment to to_string ?
1980 // as_environment& env = get_environment();
1981 setTextValue(utf8::decodeCanonicalString(val
.to_string(), version
));
1983 else if (_textDefined
) {
1984 as_value newVal
= as_value(utf8::encodeCanonicalString(_text
, version
));
1985 target
->set_member(key
, newVal
);
1988 MovieClip
* sprite
= get
<MovieClip
>(target
);
1991 // add the textfield variable to the target sprite
1992 // TODO: have set_textfield_variable take a string_table::key instead ?
1993 sprite
->set_textfield_variable(key
, this);
1996 _text_variable_registered
= true;
1999 /// Parses an HTML tag (between < and >) and puts
2000 /// the contents into tag. Returns false if the
2001 /// tag was incomplete. The iterator is moved to after
2002 /// the closing tag or the end of the string.
2004 TextField::parseHTML(std::wstring
& tag
,
2005 std::map
<std::string
, std::string
>& attributes
,
2006 std::wstring::const_iterator
& it
,
2007 const std::wstring::const_iterator
& e
,
2008 bool& selfclosing
) const
2010 while (it
!= e
&& *it
!= ' ') {
2021 log_error("invalid html tag");
2030 // Check for NULL character
2032 log_error("found NULL character in htmlText");
2035 tag
.push_back(std::toupper(*it
));
2038 while (it
!= e
&& *it
== ' ') {
2039 ++it
; //skip over spaces
2055 log_error("invalid html tag");
2060 std::string attname
;
2061 std::string attvalue
;
2064 while (it
!= e
&& *it
!= '>') {
2065 while (it
!= e
&& *it
!= '=' && *it
!= ' ') {
2068 log_error("found NULL character in htmlText");
2072 log_error("malformed HTML tag, invalid attribute name");
2079 attname
.push_back(std::toupper(*it
));
2082 while (it
!= e
&& (*it
== ' ' || *it
== '=')) {
2083 ++it
; //skip over spaces and '='
2086 if (it
== e
) return false;
2088 if (q
!= '"' && q
!= '\'') {
2089 // This is not an attribute.
2090 while (it
!= e
) ++it
;
2094 // Advance past attribute opener
2096 while (it
!= e
&& *it
!= q
) {
2099 log_error("found NULL character in htmlText");
2103 attvalue
.push_back(std::toupper(*it
));
2107 if (it
== e
) return false;
2110 while (it
!= e
) ++it
;
2114 // Skip attribute closer.
2117 attributes
.insert(std::make_pair(attname
, attvalue
));
2121 if ((*it
!= ' ') && (*it
!= '/') && (*it
!= '>')) {
2122 log_error("malformed HTML tag, invalid attribute value");
2129 while (it
!= e
&& *it
== ' ') {
2130 ++it
; //skip over spaces
2136 } else if (*it
== '/') {
2146 log_error("invalid html tag");
2152 #ifdef GNASH_DEBUG_TEXTFIELDS
2153 log_debug ("HTML tag: %s", utf8::encodeCanonicalString(tag
, 7));
2155 log_error("I declare this a HTML syntax error");
2156 return false; //since we did not return already, must be malformed...?
2160 TextField::set_variable_name(const std::string
& newname
)
2162 if (newname
!= _variable_name
) {
2163 _variable_name
= newname
;
2165 // The name was empty or undefined, so there's nothing more to do.
2166 if (_variable_name
.empty()) return;
2168 _text_variable_registered
= false;
2170 #ifdef DEBUG_DYNTEXT_VARIABLES
2171 log_debug("Calling updateText after change of variable name");
2174 // Use the original definition text if this isn't dynamically
2176 if (_tag
) updateText(_tag
->defaultText());
2178 #ifdef DEBUG_DYNTEXT_VARIABLES
2179 log_debug("Calling registerTextVariable after change of variable "
2180 "name and updateText call");
2182 registerTextVariable();
2187 TextField::pointInShape(boost::int32_t x
, boost::int32_t y
) const
2189 const SWFMatrix wm
= getWorldMatrix(*this).invert();
2192 return _bounds
.point_test(lp
.x
, lp
.y
);
2196 TextField::getDrawBorder() const
2202 TextField::setDrawBorder(bool val
)
2204 if (_drawBorder
!= val
) {
2211 TextField::getBorderColor() const
2213 return _borderColor
;
2217 TextField::setBorderColor(const rgba
& col
)
2219 if (_borderColor
!= col
) {
2226 TextField::getDrawBackground() const
2228 return _drawBackground
;
2232 TextField::setDrawBackground(bool val
)
2234 if (_drawBackground
!= val
) {
2236 _drawBackground
= val
;
2241 TextField::getBackgroundColor() const
2243 return _backgroundColor
;
2247 TextField::setBackgroundColor(const rgba
& col
)
2249 if (_backgroundColor
!= col
) {
2251 _backgroundColor
= col
;
2256 TextField::setTextColor(const rgba
& col
)
2258 if (_textColor
!= col
) {
2262 std::for_each(_displayRecords
.begin(), _displayRecords
.end(),
2263 boost::bind(&SWF::TextRecord::setColor
, _1
, _textColor
));
2268 TextField::setEmbedFonts(bool use
)
2270 if (_embedFonts
!= use
) {
2278 TextField::setWordWrap(bool wrap
)
2280 if (_wordWrap
!= wrap
) {
2288 TextField::setLeading(boost::int16_t h
)
2290 if (_leading
!= h
) {
2297 TextField::setUnderlined(bool v
)
2299 if (_underlined
!= v
) {
2306 TextField::setBullet(bool b
)
2314 TextField::setTabStops(const std::vector
<int>& tabStops
)
2316 _tabStops
.resize(tabStops
.size());
2318 for (size_t i
= 0; i
< tabStops
.size(); i
++) {
2319 _tabStops
[i
] = pixelsToTwips(tabStops
[i
]);
2326 TextField::setURL(std::string url
)
2335 TextField::setTarget(std::string target
)
2337 if (_target
!= target
) {
2344 TextField::setDisplay(TextFormatDisplay display
)
2346 if (_display
!= display
) {
2353 TextField::setAlignment(TextAlignment h
)
2355 if (_alignment
!= h
) {
2362 TextField::setIndent(boost::uint16_t h
)
2371 TextField::setBlockIndent(boost::uint16_t h
)
2373 if (_blockIndent
!= h
) {
2380 TextField::setRightMargin(boost::uint16_t h
)
2382 if (_rightMargin
!= h
) {
2389 TextField::setLeftMargin(boost::uint16_t h
)
2391 if (_leftMargin
!= h
) {
2398 TextField::setFontHeight(boost::uint16_t h
)
2400 if (_fontHeight
!= h
) {
2407 TextField::TypeValue
2408 TextField::parseTypeValue(const std::string
& val
)
2410 StringNoCaseEqual cmp
;
2412 if (cmp(val
, "input")) return typeInput
;
2413 if (cmp(val
, "dynamic")) return typeDynamic
;
2419 TextField::typeValueName(TypeValue val
)
2423 //log_debug("typeInput returned as 'input'");
2426 //log_debug("typeDynamic returned as 'dynamic'");
2429 //log_debug("invalid type %d returned as 'invalid'", (int)val);
2436 TextField::setAutoSize(AutoSize val
)
2438 if (val
== _autoSize
) return;
2445 TextField::TextAlignment
2446 TextField::getTextAlignment()
2448 TextAlignment textAlignment
= getAlignment();
2450 switch (_autoSize
) {
2451 case AUTOSIZE_CENTER
:
2452 textAlignment
= ALIGN_CENTER
;
2455 textAlignment
= ALIGN_LEFT
;
2457 case AUTOSIZE_RIGHT
:
2458 textAlignment
= ALIGN_RIGHT
;
2461 // Leave it as it was.
2465 return textAlignment
;
2469 TextField::onChanged()
2471 as_object
* obj
= getObject(this);
2472 callMethod(obj
, NSV::PROP_BROADCAST_MESSAGE
, "onChanged", obj
);
2475 /// This is called by movie_root when focus is applied to this TextField.
2477 /// The return value is true if the TextField can receive focus.
2478 /// The swfdec testsuite suggests that version 5 textfields cannot ever
2481 TextField::handleFocus()
2485 /// Select the entire text on focus.
2486 setSelection(0, _text
.length());
2490 m_cursor
= _text
.size();
2495 /// This is called by movie_root when focus is removed from the
2496 /// current TextField.
2498 TextField::killFocus()
2500 if (!m_has_focus
) return;
2502 m_has_focus
= false;
2503 format_text(); // is this needed ?
2507 TextField::setWidth(double newwidth
)
2509 const SWFRect
& bounds
= getBounds();
2510 _bounds
.set_to_rect(bounds
.get_x_min(),
2512 bounds
.get_x_min() + newwidth
,
2513 bounds
.get_y_max());
2517 TextField::setHeight(double newheight
)
2519 const SWFRect
& bounds
= getBounds();
2520 _bounds
.set_to_rect(bounds
.get_x_min(),
2523 bounds
.get_y_min() + newheight
);
2526 } // namespace gnash
2531 // indent-tabs-mode: t