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
> box
= boost::assign::list_of
270 renderer
.drawLine(box
, 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 std::vector
<point
> coords(4);
308 boost::int32_t xmin
= _bounds
.get_x_min();
309 boost::int32_t xmax
= _bounds
.get_x_max();
310 boost::int32_t ymin
= _bounds
.get_y_min();
311 boost::int32_t ymax
= _bounds
.get_y_max();
313 coords
[0].setTo(xmin
, ymin
);
314 coords
[1].setTo(xmax
, ymin
);
315 coords
[2].setTo(xmax
, ymax
);
316 coords
[3].setTo(xmin
, ymax
);
318 rgba borderColor
= drawBorder
? getBorderColor() : rgba(0,0,0,0);
319 rgba backgroundColor
= drawBackground
? getBackgroundColor() :
322 SWFCxForm cx
= xform
.colorTransform
;
324 if (drawBorder
) borderColor
= cx
.transform(borderColor
);
326 if (drawBackground
) backgroundColor
= cx
.transform(backgroundColor
);
328 #ifdef GNASH_DEBUG_TEXTFIELDS
329 log_debug("rendering a Pol composed by corners %s", _bounds
);
332 renderer
.draw_poly(&coords
.front(), 4, backgroundColor
,
333 borderColor
, xform
.matrix
, true);
337 // Draw our actual text.
338 // Using a SWFMatrix to translate to def bounds seems an hack to me.
339 // A cleaner implementation is likely correctly setting the
340 // _xOffset and _yOffset memebers in glyph records.
341 // Anyway, see bug #17954 for a testcase.
342 if (!_bounds
.is_null()) {
343 xform
.matrix
.concatenate_translation(_bounds
.get_x_min(),
344 _bounds
.get_y_min());
347 _displayRecords
.clear();
348 float scale
= getFontHeight() /
349 static_cast<float>(_font
->unitsPerEM(_embedFonts
));
350 float fontLeading
= _font
->leading() * scale
;
353 int yoffset
= (getFontHeight() + fontLeading
) + PADDING_TWIPS
;
355 for (size_t i
= 0; i
< _textRecords
.size(); ++i
) {
357 //find the line the record is on
358 while (recordline
< _line_starts
.size() &&
359 _line_starts
[recordline
] <= _recordStarts
[i
]) {
363 _textRecords
[i
].setYOffset((recordline
-_scroll
)*yoffset
);
364 //add the lines we want to the display record
365 if (_textRecords
[i
].yOffset() > 0 &&
366 _textRecords
[i
].yOffset() < _bounds
.height()) {
367 _displayRecords
.push_back(_textRecords
[i
]);
371 SWF::TextRecord::displayRecords(renderer
, xform
, _displayRecords
,
374 if (m_has_focus
&& !isReadOnly()) show_cursor(renderer
, xform
.matrix
);
381 TextField::add_invalidated_bounds(InvalidatedRanges
& ranges
, bool force
)
383 if (!force
&& !invalidated()) return; // no need to redraw
385 ranges
.add(m_old_invalidated_ranges
);
387 const SWFMatrix
& wm
= getWorldMatrix(*this);
389 SWFRect bounds
= getBounds();
390 bounds
.expand_to_rect(m_text_bounding_box
);
391 wm
.transform(bounds
);
392 ranges
.add(bounds
.getRange());
396 TextField::setRestrict(const std::string
& restrict
)
398 _restrictDefined
= true;
400 std::string::const_iterator rit
= restrict
.begin();
401 std::string::const_iterator re
= restrict
.end();
402 std::set
<wchar_t>::const_iterator locate
;
404 if (*rit
== '^') { //then this is a true RESTRICT pattern, add all chars to _restrictedchars
405 for (unsigned int i
= 0; i
<= 255; ++i
) {
406 _restrictedchars
.insert(char(i
));
408 } else { //then this is an ALLOW pattern, _restrictedchars should remain empty
409 _restrictedchars
.clear();
413 while (rit
!= re
&& *rit
!= '^') { //This loop allows chars
415 log_error("invalid restrict string");
417 } else if (*(rit
+1) == '-') {
418 if (re
- (rit
+2) != 0) {
419 unsigned int q
= *(rit
+2);
420 for (unsigned int p
= *rit
; p
<= q
; (++p
)){
421 _restrictedchars
.insert(char(p
));
425 log_error("invalid restrict string");
428 } else if (*rit
== '\\') {
430 _restrictedchars
.insert(*rit
);
433 _restrictedchars
.insert(*rit
);
440 while (rit
!= re
&& *rit
!= '^') { //This loop restricts chars
441 locate
= _restrictedchars
.find(*rit
);
443 log_error("invalid restrict string");
445 } else if (*(rit
+1) == '-') {
446 if (re
- (rit
+2) != 0) {
447 unsigned int q
= *(rit
+2);
448 for (unsigned int p
= *rit
; p
<= q
; ++p
){
449 locate
= _restrictedchars
.find(p
);
450 if(locate
!= _restrictedchars
.end()) {
451 _restrictedchars
.erase(locate
);
458 log_error("invalid restrict string");
461 } else if (*rit
== '\\') {
463 locate
= _restrictedchars
.find(*rit
);
464 if(locate
!= _restrictedchars
.end()) {
465 _restrictedchars
.erase(locate
);
469 if(locate
!= _restrictedchars
.end()) {
470 _restrictedchars
.erase(locate
);
479 _restrict
= restrict
;
483 TextField::replaceSelection(const std::string
& replace
)
485 const int version
= getSWFVersion(*getObject(this));
486 const std::wstring
& wstr
= utf8::decodeCanonicalString(replace
, version
);
488 assert(_selection
.second
>= _selection
.first
);
489 assert(_selection
.second
<= _text
.size());
490 assert(_selection
.first
<= _text
.size());
492 // If the text has changed but the selection hasn't, make sure we
493 // don't access it out of bounds.
494 const size_t start
= _selection
.first
;
495 const size_t end
= _selection
.second
;
497 const size_t replaceLength
= wstr
.size();
499 _text
.replace(start
, end
- start
, wstr
);
500 _selection
= std::make_pair(start
+ replaceLength
, start
+ replaceLength
);
504 TextField::setSelection(int start
, int end
)
507 _selection
= std::make_pair(0, 0);
511 const size_t textLength
= _text
.size();
513 if (start
< 0) start
= 0;
514 else start
= std::min
<size_t>(start
, textLength
);
516 if (end
< 0) end
= 0;
517 else end
= std::min
<size_t>(end
, textLength
);
519 // The cursor position is always set to the end value, even if the
520 // two values are swapped to obtain the selection. Equal values are
523 if (start
> end
) std::swap(start
, end
);
525 _selection
= std::make_pair(start
, end
);
529 TextField::notifyEvent(const event_id
& ev
)
533 case event_id::PRESS
:
535 movie_root
& root
= stage();
536 boost::int32_t x_mouse
, y_mouse
;
537 boost::tie(x_mouse
, y_mouse
) = root
.mousePosition();
539 SWFMatrix m
= getMatrix(*this);
541 x_mouse
-= m
.get_x_translation();
542 y_mouse
-= m
.get_y_translation();
546 for (size_t i
=0; i
< _textRecords
.size(); ++i
) {
547 if ((x_mouse
> _textRecords
[i
].xOffset()) &&
548 (x_mouse
< _textRecords
[i
].xOffset()+_textRecords
[i
].recordWidth()) &&
549 (y_mouse
> _textRecords
[i
].yOffset()-_textRecords
[i
].textHeight()) &&
550 (y_mouse
< _textRecords
[i
].yOffset())) {
551 rec
= _textRecords
[i
];
556 if (!rec
.getURL().empty()) {
557 root
.getURL(rec
.getURL(), rec
.getTarget(), "",
558 MovieClip::METHOD_NONE
);
563 case event_id::KEY_PRESS
:
565 setHtml(false); //editable html fields are not yet implemented
566 std::wstring s
= _text
;
568 // id.keyCode is the unique gnash::key::code for a DisplayObject/key.
569 // The maximum value is about 265, including function keys.
570 // It seems that typing in DisplayObjects outside the Latin-1 set
571 // (256 DisplayObject codes, identical to the first 256 of UTF-8)
572 // is not supported, though a much greater number UTF-8 codes can be
573 // stored and displayed. See utf.h for more information.
574 // This is a limit on the number of key codes, not on the
575 // capacity of strings.
576 gnash::key::code c
= ev
.keyCode();
579 // maybe _text is changed in ActionScript
580 m_cursor
= std::min
<size_t>(m_cursor
, _text
.size());
582 size_t cur_cursor
= m_cursor
;
583 size_t previouslinesize
= 0;
584 size_t nextlinesize
= 0;
585 size_t manylines
= _line_starts
.size();
586 LineStarts::iterator linestartit
= _line_starts
.begin();
587 LineStarts::const_iterator linestartend
= _line_starts
.end();
592 if (isReadOnly()) return;
595 s
.erase(m_cursor
- 1, 1);
602 if (isReadOnly()) return;
603 if (_glyphcount
> m_cursor
)
605 s
.erase(m_cursor
, 1);
610 case key::INSERT
: // TODO
611 if (isReadOnly()) return;
615 while ( linestartit
< linestartend
&& *linestartit
<= m_cursor
) {
616 cur_cursor
= *linestartit
;
619 m_cursor
= cur_cursor
;
623 // if going a page up is too far...
624 if(_scroll
< _linesindisplay
) {
627 } else { // go a page up
628 _scroll
-= _linesindisplay
;
629 m_cursor
= _line_starts
[_scroll
];
635 while ( linestartit
< linestartend
&& *linestartit
<= m_cursor
) {
636 cur_cursor
= *linestartit
;
639 //if there is no previous line
640 if ( linestartit
-_line_starts
.begin() - 2 < 0 ) {
644 previouslinesize
= _textRecords
[linestartit
-_line_starts
.begin() - 2].glyphs().size();
645 //if the previous line is smaller
646 if (m_cursor
- cur_cursor
> previouslinesize
) {
647 m_cursor
= *(--(--linestartit
)) + previouslinesize
;
649 m_cursor
= *(--(--linestartit
)) + (m_cursor
- cur_cursor
);
651 if (m_cursor
< _line_starts
[_scroll
] && _line_starts
[_scroll
] != 0) {
658 while ( linestartit
< linestartend
&& *linestartit
<= m_cursor
) {
661 m_cursor
= linestartit
!= linestartend
? *linestartit
- 1 : _text
.size();
665 //if going another page down is too far...
666 if(_scroll
+ _linesindisplay
>= manylines
) {
667 if(manylines
- _linesindisplay
<= 0) {
670 _scroll
= manylines
- _linesindisplay
;
672 if(m_cursor
< _line_starts
[_scroll
-1]) {
673 m_cursor
= _line_starts
[_scroll
-1];
675 m_cursor
= _text
.size();
677 } else { //go a page down
678 _scroll
+= _linesindisplay
;
679 m_cursor
= _line_starts
[_scroll
];
686 while (linestartit
< linestartend
&&
687 *linestartit
<= m_cursor
) {
688 cur_cursor
= *linestartit
;
692 // linestartit should never be before _line_starts.begin()
693 const size_t currentLine
= linestartit
-
694 _line_starts
.begin();
696 //if there is no next line
697 if (currentLine
>= manylines
) {
698 m_cursor
= _text
.size();
701 nextlinesize
= _textRecords
[currentLine
].glyphs().size();
703 //if the next line is smaller
704 if (m_cursor
- cur_cursor
> nextlinesize
) {
705 m_cursor
= *linestartit
+ nextlinesize
;
707 //put the cursor at the same character distance
708 m_cursor
= *(linestartit
) + (m_cursor
- cur_cursor
);
710 if (_line_starts
.size() > _linesindisplay
&&
711 m_cursor
>= _line_starts
[_scroll
+_linesindisplay
]) {
719 m_cursor
= m_cursor
> 0 ? m_cursor
- 1 : 0;
723 m_cursor
= m_cursor
< _glyphcount
? m_cursor
+ 1 :
728 if (isReadOnly()) return;
729 if (!multiline()) break;
733 if (maxChars() != 0) {
734 if (_maxChars
<= _glyphcount
) {
739 if (isReadOnly()) return;
740 wchar_t t
= static_cast<wchar_t>(
741 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
)
778 if (!visible()) return 0;
780 // Not selectable, so don't catch mouse events!
781 if (!_selectable
) return 0;
783 SWFMatrix m
= getMatrix(*this);
785 m
.invert().transform(p
);
787 if (_bounds
.point_test(p
.x
, p
.y
)) return this;
793 TextField::updateText(const std::string
& str
)
795 const int version
= getSWFVersion(*getObject(this));
796 const std::wstring
& wstr
= utf8::decodeCanonicalString(str
, version
);
801 TextField::updateText(const std::wstring
& wstr
)
804 if (_text
== wstr
) return;
810 _selection
.first
= std::min(_selection
.first
, _text
.size());
811 _selection
.second
= std::min(_selection
.second
, _text
.size());
817 TextField::updateHtmlText(const std::wstring
& wstr
)
819 if (_htmlText
== wstr
) return;
828 TextField::setTextValue(const std::wstring
& wstr
)
830 updateHtmlText(wstr
);
833 if (!_variable_name
.empty() && _text_variable_registered
) {
834 // TODO: notify MovieClip if we have a variable name !
835 VariableRef ref
= parseTextVariableRef(_variable_name
);
836 as_object
* tgt
= ref
.first
;
838 const int version
= getSWFVersion(*getObject(this));
839 // we shouldn't truncate, right?
840 tgt
->set_member(ref
.second
, utf8::encodeCanonicalString(wstr
,
844 // nothing to do (too early ?)
845 log_debug("setTextValue: variable name %s points to a non-existent"
846 " target, I guess we would not be registered if this was "
847 "true, or the sprite we've registered our variable name "
848 "has been unloaded", _variable_name
);
854 TextField::get_text_value() const
856 // we need the const_cast here because registerTextVariable
857 // *might* change our text value, calling the non-const
859 // This happens if the TextVariable has not been already registered
860 // and during registration comes out to name an existing variable
861 // with a pre-existing value.
862 const_cast<TextField
*>(this)->registerTextVariable();
864 const int version
= getSWFVersion(*getObject(this));
866 return utf8::encodeCanonicalString(_text
, version
);
870 TextField::get_htmltext_value() const
872 const_cast<TextField
*>(this)->registerTextVariable();
873 const int version
= getSWFVersion(*getObject(this));
874 return utf8::encodeCanonicalString(_htmlText
, version
);
878 TextField::setTextFormat(TextFormat_as
& tf
)
880 //TODO: this is lazy. we should set all the TextFormat variables HERE, i think
881 //This is just so we can set individual variables without having to call format_text()
882 //This calls format_text() at the end of setting TextFormat
883 if (tf
.align()) setAlignment(*tf
.align());
884 if (tf
.size()) setFontHeight(*tf
.size()); // keep twips
885 if (tf
.indent()) setIndent(*tf
.indent());
886 if (tf
.blockIndent()) setBlockIndent(*tf
.blockIndent());
887 if (tf
.leading()) setLeading(*tf
.leading());
888 if (tf
.leftMargin()) setLeftMargin(*tf
.leftMargin());
889 if (tf
.rightMargin()) setRightMargin(*tf
.rightMargin());
890 if (tf
.color()) setTextColor(*tf
.color());
891 if (tf
.underlined()) setUnderlined(*tf
.underlined());
892 if (tf
.bullet()) setBullet(*tf
.bullet());
893 setDisplay(tf
.display());
894 if (tf
.tabStops()) setTabStops(*tf
.tabStops());
896 // NEED TO IMPLEMENT THESE TWO
897 if (tf
.url()) setURL(*tf
.url());
898 if (tf
.target()) setTarget(*tf
.target());
904 TextField::align_line(TextAlignment align
, int last_line_start_record
, float x
)
906 float width
= _bounds
.width();
907 float right_margin
= getRightMargin();
909 float extra_space
= (width
- right_margin
) - x
- PADDING_TWIPS
;
911 if (extra_space
<= 0.0f
) {
912 #ifdef GNASH_DEBUG_TEXTFIELDS
913 log_debug(_("TextField text doesn't fit in its boundaries: "
914 "width %g, margin %g - nothing to align"),
915 width
, right_margin
);
920 float shift_right
= 0.0f
;
924 // Nothing to do; already aligned left.
927 // Distribute the space evenly on both sides.
928 shift_right
= extra_space
/ 2;
931 // Shift all the way to the right.
932 shift_right
= extra_space
;
935 // What should we do here?
939 // Shift the beginnings of the records on this line.
940 for (size_t i
= last_line_start_record
; i
< _textRecords
.size(); ++i
) {
941 SWF::TextRecord
& rec
= _textRecords
[i
];
942 rec
.setXOffset(rec
.xOffset() + shift_right
);
947 boost::intrusive_ptr
<const Font
>
948 TextField::setFont(boost::intrusive_ptr
<const Font
> newfont
)
950 if (newfont
== _font
) return _font
;
952 boost::intrusive_ptr
<const Font
> oldfont
= _font
;
961 TextField::insertTab(SWF::TextRecord
& rec
, boost::int32_t& x
, float scale
)
964 const int space
= 32;
965 int index
= rec
.getFont()->get_glyph_index(space
, _embedFonts
);
967 IF_VERBOSE_MALFORMED_SWF (
968 log_error(_("TextField: missing glyph for space char (needed "
969 "for TAB). Make sure DisplayObject shapes for font "
970 "%s are being exported into your SWF file."),
971 rec
.getFont()->name());
975 // TODO: why is there a copy of the vector?
976 std::vector
<int> tabStops
= _tabStops
;
978 std::sort(_tabStops
.begin(), _tabStops
.end());
981 if (!_tabStops
.empty()) {
982 tab
= _tabStops
.back() + 1;
984 for (size_t i
= 0; i
< tabStops
.size(); ++i
) {
985 if (tabStops
[i
] > x
) {
986 if((tabStops
[i
] - x
) < tab
) {
987 tab
= tabStops
[i
] - x
;
993 // This is necessary in case the number of tabs in the text
994 // are more than the actual number of tabStops inside the
996 if (tab
!= _tabStops
.back() + 1) {
997 SWF::TextRecord::GlyphEntry ge
;
998 ge
.index
= rec
.getFont()->get_glyph_index(32, _embedFonts
);
1005 SWF::TextRecord::GlyphEntry ge
;
1007 ge
.advance
= scale
* rec
.getFont()->get_advance(index
,
1010 const int tabstop
= 4;
1011 rec
.addGlyph(ge
, tabstop
);
1012 x
+= ge
.advance
* tabstop
;
1018 TextField::format_text()
1020 _textRecords
.clear();
1021 _line_starts
.clear();
1022 _recordStarts
.clear();
1025 _recordStarts
.push_back(0);
1027 // nothing more to do if text is empty
1028 if (_text
.empty()) {
1029 // TODO: should we still reset _bounds if autoSize != AUTOSIZE_NONE ?
1030 // not sure we should...
1031 reset_bounding_box(0, 0);
1035 LineStarts::iterator linestartit
= _line_starts
.begin();
1036 LineStarts::const_iterator linestartend
= _line_starts
.end();
1038 AutoSize autoSize
= getAutoSize();
1039 if (autoSize
!= AUTOSIZE_NONE
) {
1040 // When doing WordWrap we don't want to change
1041 // the boundaries. See bug #24348
1042 if (!doWordWrap()) {
1043 _bounds
.set_to_rect(0, 0, 0, 0); // this is correct for 'true'
1047 // FIXME: I don't think we should query the definition
1048 // to find the appropriate font to use, as ActionScript
1049 // code should be able to change the font of a TextField
1051 log_error(_("No font for TextField!"));
1055 boost::uint16_t fontHeight
= getFontHeight();
1056 float scale
= fontHeight
/
1057 static_cast<float>(_font
->unitsPerEM(_embedFonts
));
1058 const float fontLeading
= _font
->leading() * scale
;
1059 const boost::uint16_t leftMargin
= getLeftMargin();
1060 const boost::uint16_t indent
= getIndent();
1061 const boost::uint16_t blockIndent
= getBlockIndent();
1062 const bool underlined
= getUnderlined();
1064 /// Remember the current bounds for autosize.
1065 SWFRect
oldBounds(_bounds
);
1067 SWF::TextRecord rec
; // one to work on
1068 rec
.setFont(_font
.get());
1069 rec
.setUnderline(underlined
);
1070 rec
.setColor(getTextColor());
1071 rec
.setXOffset(PADDING_TWIPS
+
1072 std::max(0, leftMargin
+ indent
+ blockIndent
));
1073 rec
.setYOffset(PADDING_TWIPS
+ fontHeight
+ fontLeading
);
1074 rec
.setTextHeight(fontHeight
);
1076 // create in textrecord.h
1078 rec
.setTarget(_target
);
1082 // First, we indent 10 spaces, and then place the bullet
1083 // character (in this case, an asterisk), then we pad it
1084 // again with 10 spaces
1085 // Note: this works only for additional lines of a
1086 // bulleted list, so that is why there is a bullet format
1087 // in the beginning of format_text()
1089 int space
= rec
.getFont()->get_glyph_index(32, _embedFonts
);
1091 SWF::TextRecord::GlyphEntry ge
;
1093 ge
.advance
= scale
* rec
.getFont()->get_advance(space
, _embedFonts
);
1094 rec
.addGlyph(ge
, 5);
1096 // We use an asterisk instead of a bullet
1097 int bullet
= rec
.getFont()->get_glyph_index(42, _embedFonts
);
1099 ge
.advance
= scale
* rec
.getFont()->get_advance(bullet
, _embedFonts
);
1102 space
= rec
.getFont()->get_glyph_index(32, _embedFonts
);
1104 ge
.advance
= scale
* rec
.getFont()->get_advance(space
, _embedFonts
);
1105 rec
.addGlyph(ge
, 4);
1108 boost::int32_t x
= static_cast<boost::int32_t>(rec
.xOffset());
1109 boost::int32_t y
= static_cast<boost::int32_t>(rec
.yOffset());
1111 // Start the bbox at the upper-left corner of the first glyph.
1112 //reset_bounding_box(x, y + fontHeight);
1114 int last_code
= -1; // only used if _embedFonts
1115 int last_space_glyph
= -1;
1116 size_t last_line_start_record
= 0;
1118 _line_starts
.push_back(0);
1120 // String iterators are very sensitive to
1121 // potential changes to the string (to allow for copy-on-write).
1122 // So there must be no external changes to the string or
1123 // calls to most non-const member functions during this loop.
1124 // Especially not c_str() or data().
1125 std::wstring::const_iterator it
= _text
.begin();
1126 const std::wstring::const_iterator e
= _text
.end();
1128 ///handleChar takes care of placing the glyphs
1129 handleChar(it
, e
, x
, y
, rec
, last_code
, last_space_glyph
,
1130 last_line_start_record
);
1132 // Expand bounding box to include the whole text (if autoSize and wordWrap
1133 // is not in operation.
1134 if (_autoSize
!= AUTOSIZE_NONE
&& !doWordWrap())
1136 _bounds
.expand_to_point(x
+ PADDING_TWIPS
, y
+ PADDING_TWIPS
);
1138 if (_autoSize
== AUTOSIZE_RIGHT
) {
1139 /// Autosize right expands from the previous right margin.
1142 m
.set_x_translation(oldBounds
.get_x_max() - _bounds
.width());
1143 m
.transform(_bounds
);
1145 else if (_autoSize
== AUTOSIZE_CENTER
) {
1146 // Autosize center expands from the previous center.
1148 m
.set_x_translation(oldBounds
.get_x_min() + oldBounds
.width() / 2.0 -
1149 _bounds
.width() / 2.0);
1150 m
.transform(_bounds
);
1154 // Add the last line to our output.
1155 _textRecords
.push_back(rec
);
1157 // align the last (or single) line
1158 align_line(getTextAlignment(), last_line_start_record
, x
);
1162 set_invalidated(); //redraw
1167 TextField::scrollLines()
1169 boost::uint16_t fontHeight
= getFontHeight();
1170 float scale
= fontHeight
/
1171 static_cast<float>(_font
->unitsPerEM(_embedFonts
));
1172 float fontLeading
= _font
->leading() * scale
;
1173 _linesindisplay
= _bounds
.height() / (fontHeight
+ fontLeading
+ PADDING_TWIPS
);
1174 if (_linesindisplay
> 0) { //no need to place lines if we can't fit any
1175 size_t manylines
= _line_starts
.size();
1176 size_t lastvisibleline
= _scroll
+ _linesindisplay
;
1179 // If there aren't as many lines as we have scrolled, display the
1181 if (manylines
< _scroll
) {
1182 _scroll
= manylines
- _linesindisplay
;
1186 // which line is the cursor on?
1187 while (line
< manylines
&& _line_starts
[line
] <= m_cursor
) {
1191 if (manylines
- _scroll
<= _linesindisplay
) {
1192 // This is for if we delete a line
1193 if (manylines
< _linesindisplay
) _scroll
= 0;
1195 _scroll
= manylines
- _linesindisplay
;
1197 } else if (line
< _scroll
) {
1198 //if we are at a higher position, scroll the lines down
1199 _scroll
-= _scroll
- line
;
1200 } else if (manylines
> _scroll
+ _linesindisplay
) {
1201 //if we are at a lower position, scroll the lines up
1202 if (line
>= (_scroll
+_linesindisplay
)) {
1203 _scroll
+= line
- (lastvisibleline
);
1210 TextField::newLine(boost::int32_t& x
, boost::int32_t& y
,
1211 SWF::TextRecord
& rec
, int& last_space_glyph
,
1212 LineStarts::value_type
& last_line_start_record
, float div
)
1215 LineStarts::iterator linestartit
= _line_starts
.begin();
1216 LineStarts::const_iterator linestartend
= _line_starts
.end();
1218 float scale
= _fontHeight
/
1219 static_cast<float>(_font
->unitsPerEM(_embedFonts
));
1220 float fontLeading
= _font
->leading() * scale
;
1221 float leading
= getLeading();
1222 leading
+= fontLeading
* scale
; // not sure this is correct...
1224 // Close out this stretch of glyphs.
1226 _textRecords
.push_back(rec
);
1227 _recordStarts
.push_back(_glyphcount
);
1228 align_line(getTextAlignment(), last_line_start_record
, x
);
1230 // Expand bounding box to include last column of text ...
1231 if (!doWordWrap() && _autoSize
!= AUTOSIZE_NONE
) {
1232 _bounds
.expand_to_point(x
+ PADDING_TWIPS
, y
+ PADDING_TWIPS
);
1235 // new paragraphs get the indent.
1236 x
= std::max(0, getLeftMargin() + getIndent() + getBlockIndent()) +
1238 y
+= div
* (getFontHeight() + leading
);
1239 if (y
>= _bounds
.height()) {
1243 // Start a new record on the next line. Other properties of the
1244 // TextRecord should be left unchanged.
1249 last_space_glyph
= -1;
1250 last_line_start_record
= _textRecords
.size();
1252 linestartit
= _line_starts
.begin();
1253 linestartend
= _line_starts
.end();
1254 //Fit a line_start in the correct place
1255 const size_t currentPos
= _glyphcount
;
1257 while (linestartit
< linestartend
&& *linestartit
< currentPos
)
1261 _line_starts
.insert(linestartit
, currentPos
);
1265 // First, we indent 10 spaces, and then place the bullet
1266 // character (in this case, an asterisk), then we pad it
1267 // again with 10 spaces
1268 // Note: this works only for additional lines of a
1269 // bulleted list, so that is why there is a bullet format
1270 // in the beginning of format_text()
1273 int space
= rec
.getFont()->get_glyph_index(32, _embedFonts
);
1274 SWF::TextRecord::GlyphEntry ge
;
1276 ge
.advance
= scale
* rec
.getFont()->get_advance(space
, _embedFonts
);
1281 int bullet
= rec
.getFont()->get_glyph_index(42, _embedFonts
);
1283 ge
.advance
= scale
* rec
.getFont()->get_advance(bullet
, _embedFonts
);
1288 ge
.advance
= scale
* rec
.getFont()->get_advance(space
, _embedFonts
);
1296 TextField::handleChar(std::wstring::const_iterator
& it
,
1297 const std::wstring::const_iterator
& e
, boost::int32_t& x
,
1298 boost::int32_t& y
, SWF::TextRecord
& rec
, int& last_code
,
1299 int& last_space_glyph
, LineStarts::value_type
& last_line_start_record
)
1301 LineStarts::iterator linestartit
= _line_starts
.begin();
1302 LineStarts::const_iterator linestartend
= _line_starts
.end();
1304 float scale
= _fontHeight
/
1305 static_cast<float>(_font
->unitsPerEM(_embedFonts
));
1306 float fontDescent
= _font
->descent(_embedFonts
) * scale
;
1307 float fontLeading
= _font
->leading() * scale
;
1308 float leading
= getLeading();
1309 leading
+= fontLeading
* scale
; // not sure this is correct...
1311 boost::uint32_t code
= 0;
1319 x
+= rec
.getFont()->get_kerning_adjustment(last_code
,
1320 static_cast<int>(code
)) * scale
;
1321 last_code
= static_cast<int>(code
);
1324 // Expand the bounding-box to the lower-right corner of each glyph as
1326 m_text_bounding_box
.expand_to_point(x
, y
+ fontDescent
);
1333 insertTab(rec
, x
, scale
);
1338 // This is a limited hack to enable overstrike effects.
1339 // It backs the cursor up by one DisplayObject and then continues
1340 // the layout. E.g. you can use this to display an underline
1341 // cursor inside a simulated text-entry box.
1343 // ActionScript understands the '\b' escape sequence
1344 // for inserting a BS DisplayObject.
1346 // ONLY WORKS FOR BACKSPACING OVER ONE CHARACTER, WON'T BS
1347 // OVER NEWLINES, ETC.
1349 if (!rec
.glyphs().empty())
1351 // Peek at the previous glyph, and zero out its advance
1352 // value, so the next char overwrites it.
1353 float advance
= rec
.glyphs().back().advance
;
1362 newLine(x
,y
,rec
,last_space_glyph
,last_line_start_record
,1.0);
1368 //close out this stretch of glyphs
1369 _textRecords
.push_back(rec
);
1371 _recordStarts
.push_back(_glyphcount
);
1373 while (it
!= e
&& *it
!= '>') {
1379 LOG_ONCE(log_debug(_("HTML in a text field is unsupported, "
1380 "gnash will just ignore the tags and "
1381 "print their content")));
1383 std::wstring discard
;
1384 std::map
<std::string
,std::string
> attributes
;
1385 SWF::TextRecord newrec
;
1386 newrec
.setFont(rec
.getFont());
1387 newrec
.setUnderline(rec
.underline());
1388 newrec
.setColor(rec
.color());
1389 newrec
.setTextHeight(rec
.textHeight());
1390 newrec
.setXOffset(x
);
1391 newrec
.setYOffset(y
);
1392 bool selfclosing
= false;
1393 bool complete
= parseHTML(discard
, attributes
, it
, e
, selfclosing
);
1394 std::string
s(discard
.begin(), discard
.end());
1396 std::map
<std::string
,std::string
>::const_iterator attloc
;
1399 //parsing went wrong
1402 // Don't think this is the best way to match with
1404 // TODO: assumes tags are properly nested. This isn't
1408 newrec
.setUnderline(true);
1409 handleChar(it
, e
, x
, y
, newrec
, last_code
,
1410 last_space_glyph
, last_line_start_record
);
1412 else if (s
== "A") {
1413 // anchor (blue text).
1414 rgba
color(0, 0, 0xff, 0xff);
1415 newrec
.setColor(color
);
1416 newrec
.setUnderline(true);
1417 attloc
= attributes
.find("HREF");
1418 if (attloc
!= attributes
.end()) {
1419 newrec
.setURL(attloc
->second
);
1421 attloc
= attributes
.find("TARGET");
1422 if (attloc
!=attributes
.end()) {
1423 newrec
.setTarget(attloc
->second
);
1425 handleChar(it
, e
, x
, y
, newrec
, last_code
,
1426 last_space_glyph
, last_line_start_record
);
1428 else if (s
== "B") {
1430 Font
* boldfont
= new Font(rec
.getFont()->name(),
1431 true, rec
.getFont()->isItalic());
1432 newrec
.setFont(boldfont
);
1433 handleChar(it
, e
, x
, y
, newrec
, last_code
,
1434 last_space_glyph
, last_line_start_record
);
1436 else if (s
== "FONT") {
1438 boost::uint16_t originalsize
= _fontHeight
;
1439 attloc
= attributes
.find("COLOR");
1440 if (attloc
!= attributes
.end()) {
1441 std::string
hexval(attloc
->second
);
1442 if (hexval
.empty() || hexval
[0] != '#') {
1443 // FIXME: should this be a log_aserror
1444 // or log_unimpl ? It is triggered
1445 // by TextFieldHTML.as
1446 log_error("Unexpected value '%s' in "
1447 "TextField font color attribute",
1452 // font COLOR attribute
1454 colorFromHexString(hexval
);
1455 newrec
.setColor(color
);
1458 attloc
= attributes
.find("FACE");
1459 if (attloc
!= attributes
.end()) {
1460 //font FACE attribute
1461 Font
* newfont
= new Font(attloc
->second
,
1462 rec
.getFont()->isBold(), rec
.getFont()->isItalic());
1463 newrec
.setFont(newfont
);
1465 attloc
= attributes
.find("SIZE");
1466 if (attloc
!= attributes
.end()) {
1467 //font SIZE attribute
1468 std::string firstchar
= attloc
->second
.substr(0,1);
1469 if (firstchar
== "+") {
1470 newrec
.setTextHeight(rec
.textHeight() +
1472 (pixelsToTwips(std::strtol(
1473 attloc
->second
.substr(1,attloc
->second
.length()-1).data(),
1475 newrec
.setYOffset(PADDING_TWIPS
+
1476 newrec
.textHeight() +
1477 (fontLeading
- fontDescent
));
1478 _fontHeight
+= pixelsToTwips(std::strtol(
1479 attloc
->second
.substr(1,attloc
->second
.length()-1).data(),
1481 } else if (firstchar
== "-") {
1482 newrec
.setTextHeight(rec
.textHeight() -
1483 (pixelsToTwips(std::strtol(
1484 attloc
->second
.substr(1,attloc
->second
.length()-1).data(),
1486 newrec
.setYOffset(PADDING_TWIPS
+
1487 newrec
.textHeight() +
1488 (fontLeading
- fontDescent
));
1489 _fontHeight
-= pixelsToTwips(std::strtol(
1490 attloc
->second
.substr(1,attloc
->second
.length()-1).data(),
1493 newrec
.setTextHeight(pixelsToTwips(std::strtol(
1494 attloc
->second
.data(), NULL
, 10)));
1495 newrec
.setYOffset(PADDING_TWIPS
+ newrec
.textHeight() +
1496 (fontLeading
- fontDescent
));
1497 _fontHeight
= pixelsToTwips(std::strtol(
1498 attloc
->second
.data(), NULL
, 10));
1501 handleChar(it
, e
, x
, y
, newrec
, last_code
,
1502 last_space_glyph
, last_line_start_record
);
1503 _fontHeight
= originalsize
;
1504 y
= newrec
.yOffset();
1506 else if (s
== "IMG") {
1508 log_unimpl("<img> html tag in TextField");
1509 handleChar(it
, e
, x
, y
, newrec
, last_code
,
1510 last_space_glyph
, last_line_start_record
);
1512 else if (s
== "I") {
1514 Font
* italicfont
= new Font(rec
.getFont()->name(),
1515 rec
.getFont()->isBold(), true);
1516 newrec
.setFont(italicfont
);
1517 handleChar(it
, e
, x
, y
, newrec
, last_code
,
1518 last_space_glyph
, last_line_start_record
);
1519 } else if (s
== "LI") {
1520 //list item (bullet)
1521 int space
= newrec
.getFont()->get_glyph_index(32, _embedFonts
);
1522 SWF::TextRecord::GlyphEntry ge
;
1524 ge
.advance
= scale
* newrec
.getFont()->get_advance(space
, _embedFonts
);
1525 newrec
.addGlyph(ge
, 5);
1527 // We use an asterisk instead of a bullet
1528 int bullet
= newrec
.getFont()->get_glyph_index(42, _embedFonts
);
1530 ge
.advance
= scale
* newrec
.getFont()->get_advance(bullet
, _embedFonts
);
1531 newrec
.addGlyph(ge
);
1533 space
= newrec
.getFont()->get_glyph_index(32, _embedFonts
);
1535 ge
.advance
= scale
* newrec
.getFont()->get_advance(space
, _embedFonts
);
1536 newrec
.addGlyph(ge
, 4);
1538 handleChar(it
, e
, x
, y
, newrec
, last_code
,
1539 last_space_glyph
, last_line_start_record
);
1540 newLine(x
, y
, newrec
, last_space_glyph
,
1541 last_line_start_record
, 1.0);
1543 else if (s
== "SPAN") {
1545 log_unimpl("<span> html tag in TextField");
1546 handleChar(it
, e
, x
, y
, newrec
, last_code
,
1547 last_space_glyph
, last_line_start_record
);
1549 else if (s
== "TEXTFORMAT") {
1550 log_debug("in textformat");
1552 boost::uint16_t originalblockindent
= getBlockIndent();
1553 boost::uint16_t originalindent
= getIndent();
1554 boost::uint16_t originalleading
= getLeading();
1555 boost::uint16_t originalleftmargin
= getLeftMargin();
1556 boost::uint16_t originalrightmargin
= getRightMargin();
1557 std::vector
<int> originaltabstops
= getTabStops();
1558 attloc
= attributes
.find("BLOCKINDENT");
1559 if (attloc
!= attributes
.end()) {
1560 //textformat BLOCKINDENT attribute
1561 setBlockIndent(pixelsToTwips(std::strtol(
1562 attloc
->second
.data(), NULL
, 10)));
1563 if (newrec
.xOffset() == std::max(0, originalleftmargin
+
1564 originalindent
+ originalblockindent
) + PADDING_TWIPS
) {
1565 //if beginning of line, indent
1566 x
= std::max(0, getLeftMargin() +
1567 getIndent() + getBlockIndent())
1569 newrec
.setXOffset(x
);
1572 attloc
= attributes
.find("INDENT");
1573 if (attloc
!= attributes
.end()) {
1574 //textformat INDENT attribute
1575 setIndent(pixelsToTwips(std::strtol(
1576 attloc
->second
.data(), NULL
, 10)));
1577 if (newrec
.xOffset() == std::max(0, originalleftmargin
+
1578 originalindent
+ getBlockIndent()) + PADDING_TWIPS
) {
1579 //if beginning of line, indent
1580 x
= std::max(0, getLeftMargin() +
1581 getIndent() + getBlockIndent())
1583 newrec
.setXOffset(x
);
1586 attloc
= attributes
.find("LEADING");
1587 if (attloc
!= attributes
.end()) {
1588 //textformat LEADING attribute
1589 setLeading(pixelsToTwips(std::strtol(
1590 attloc
->second
.data(), NULL
, 10)));
1592 attloc
= attributes
.find("LEFTMARGIN");
1593 if (attloc
!= attributes
.end()) {
1594 //textformat LEFTMARGIN attribute
1595 setLeftMargin(pixelsToTwips(std::strtol(
1596 attloc
->second
.data(), NULL
, 10)));
1597 if (newrec
.xOffset() == std::max(0, originalleftmargin
+
1598 getIndent() + getBlockIndent()) + PADDING_TWIPS
) {
1599 //if beginning of line, indent
1600 x
= std::max(0, getLeftMargin() +
1601 getIndent() + getBlockIndent())
1603 newrec
.setXOffset(x
);
1606 attloc
= attributes
.find("RIGHTMARGIN");
1607 if (attloc
!= attributes
.end()) {
1608 //textformat RIGHTMARGIN attribute
1609 setRightMargin(pixelsToTwips(std::strtol(
1610 attloc
->second
.data(), NULL
, 10)));
1611 //FIXME:Should not apply this to this line if we are not at
1612 //beginning of line. Not sure how to do that.
1614 attloc
= attributes
.find("TABSTOPS");
1615 if (attloc
!= attributes
.end()) {
1616 //textformat TABSTOPS attribute
1617 log_unimpl("html <textformat> tag tabstops attribute");
1619 handleChar(it
, e
, x
, y
, newrec
, last_code
,
1620 last_space_glyph
, last_line_start_record
);
1621 setBlockIndent(originalblockindent
);
1622 setIndent(originalindent
);
1623 setLeading(originalleading
);
1624 setLeftMargin(originalleftmargin
);
1625 setRightMargin(originalrightmargin
);
1626 setTabStops(originaltabstops
);
1628 else if (s
== "P") {
1630 if (_display
== TEXTFORMAT_BLOCK
) {
1631 handleChar(it
, e
, x
, y
, newrec
, last_code
,
1633 last_line_start_record
);
1634 newLine(x
, y
, rec
, last_space_glyph
,
1635 last_line_start_record
, 1.0);
1636 newLine(x
, y
, rec
, last_space_glyph
,
1637 last_line_start_record
, 1.5);
1640 handleChar(it
, e
, x
, y
, newrec
, last_code
,
1642 last_line_start_record
);
1645 else if (s
== "BR" || s
== "SBR") {
1647 newLine(x
, y
, rec
, last_space_glyph
,
1648 last_line_start_record
, 1.0);
1651 log_debug("<%s> tag is unsupported", s
);
1652 if (!selfclosing
) { //then recurse, look for closing tag
1653 handleChar(it
, e
, x
, y
, newrec
, last_code
,
1654 last_space_glyph
, last_line_start_record
);
1662 // If HTML isn't enabled, carry on and insert the glyph.
1663 // FIXME: do we also want to be changing last_space_glyph?
1664 // ...because we are...
1666 last_space_glyph
= rec
.glyphs().size();
1667 // Don't break, as we still need to insert the space glyph.
1676 SWF::TextRecord::GlyphEntry ge
;
1677 int bullet
= rec
.getFont()->get_glyph_index(42, _embedFonts
);
1679 ge
.advance
= scale
* rec
.getFont()->get_advance(bullet
,
1685 // The font table holds up to 65535 glyphs. Casting
1686 // from uint32_t would, in the event that the code
1687 // is higher than 65535, result in the wrong DisplayObject
1688 // being chosen. Flash can currently only handle 16-bit
1690 int index
= rec
.getFont()->get_glyph_index(
1691 static_cast<boost::uint16_t>(code
), _embedFonts
);
1693 IF_VERBOSE_MALFORMED_SWF (
1696 // Missing glyph! Log the first few errors.
1697 static int s_log_count
= 0;
1698 if (s_log_count
< 10)
1703 log_swferror(_("TextField: missing embedded "
1704 "glyph for char %d. Make sure DisplayObject "
1705 "shapes for font %s are being exported "
1706 "into your SWF file"),
1707 code
, _font
->name());
1711 log_swferror(_("TextField: missing device "
1712 "glyph for char %d. Maybe you don't have "
1713 "font '%s' installed in your system."),
1714 code
, _font
->name());
1718 // Drop through and use index == -1; this will display
1719 // using the empty-box glyph
1723 SWF::TextRecord::GlyphEntry ge
;
1725 ge
.advance
= scale
* rec
.getFont()->get_advance(index
,
1735 float width
= _bounds
.width();
1736 if (x
>= width
- getRightMargin() - PADDING_TWIPS
)
1738 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1739 log_debug("Text in TextField %s exceeds width [ _bounds %s ]",
1740 getTarget(), _bounds
);
1743 // No wrap and no resize: truncate
1744 if (!doWordWrap() && getAutoSize() == AUTOSIZE_NONE
)
1746 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1747 log_debug(" wordWrap=false, autoSize=none");
1749 // Truncate long line, but keep expanding text box
1750 bool newlinefound
= false;
1756 x
+= rec
.getFont()->get_kerning_adjustment(last_code
,
1757 static_cast<int>(code
)) * scale
;
1760 // Expand the bounding-box to the lower-right corner
1761 // of each glyph, even if we don't display it
1762 m_text_bounding_box
.expand_to_point(x
, y
+ fontDescent
);
1763 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1764 log_debug("Text bbox expanded to %s (width: %f)",
1765 m_text_bounding_box
, m_text_bounding_box
.width());
1768 if (code
== 13 || code
== 10)
1770 newlinefound
= true;
1774 int index
= rec
.getFont()->get_glyph_index(
1775 static_cast<boost::uint16_t>(code
), _embedFonts
);
1776 x
+= scale
* rec
.getFont()->get_advance(index
, _embedFonts
);
1779 if (!newlinefound
) break;
1781 else if (doWordWrap()) {
1783 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1784 log_debug(" wordWrap=true");
1787 // Insert newline if there's space or autosize != none
1789 // Close out this stretch of glyphs.
1790 _textRecords
.push_back(rec
);
1792 float previous_x
= x
;
1793 x
= std::max(0, getLeftMargin() + getBlockIndent()) + PADDING_TWIPS
;
1794 y
+= _fontHeight
+ leading
;
1795 if (y
>= _bounds
.height()) {
1799 // Start a new record on the next line.
1804 // TODO : what if m_text_glyph_records is empty ?
1806 assert(!_textRecords
.empty());
1807 SWF::TextRecord
& last_line
= _textRecords
.back();
1809 linestartit
= _line_starts
.begin();
1810 linestartend
= _line_starts
.end();
1811 if (last_space_glyph
== -1)
1813 // Pull the previous glyph down onto the
1815 if (!last_line
.glyphs().empty())
1817 rec
.addGlyph(last_line
.glyphs().back());
1818 x
+= last_line
.glyphs().back().advance
;
1819 previous_x
-= last_line
.glyphs().back().advance
;
1820 last_line
.clearGlyphs(1);
1821 //record the new line start
1823 const size_t currentPos
= _glyphcount
;
1824 while (linestartit
!= linestartend
&&
1825 *linestartit
+ 1 <= currentPos
)
1829 _line_starts
.insert(linestartit
, currentPos
);
1830 _recordStarts
.push_back(currentPos
);
1833 // Move the previous word down onto the next line.
1835 previous_x
-= last_line
.glyphs()[last_space_glyph
].advance
;
1837 const SWF::TextRecord::Glyphs::size_type lineSize
=
1838 last_line
.glyphs().size();
1839 for (unsigned int i
= last_space_glyph
+ 1; i
< lineSize
;
1842 rec
.addGlyph(last_line
.glyphs()[i
]);
1843 x
+= last_line
.glyphs()[i
].advance
;
1844 previous_x
-= last_line
.glyphs()[i
].advance
;
1846 last_line
.clearGlyphs(lineSize
- last_space_glyph
);
1848 // record the position at the start of this line as
1850 const size_t linestartpos
= _glyphcount
-
1851 rec
.glyphs().size();
1853 while (linestartit
< linestartend
&&
1854 *linestartit
< linestartpos
)
1858 _line_starts
.insert(linestartit
, linestartpos
);
1859 _recordStarts
.push_back(linestartpos
);
1862 align_line(getTextAlignment(), last_line_start_record
, previous_x
);
1864 last_space_glyph
= -1;
1865 last_line_start_record
= _textRecords
.size();
1870 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1871 log_debug(" wordWrap=%d, autoSize=%d", _wordWrap
, _autoSize
);
1879 TextField::getDefinitionVersion() const
1881 // TODO: work out if this correct.
1882 return get_root()->getDefinitionVersion();
1886 TextField::VariableRef
1887 TextField::parseTextVariableRef(const std::string
& variableName
) const
1892 #ifdef DEBUG_DYNTEXT_VARIABLES
1893 log_debug(_("VariableName: %s"), variableName
);
1896 /// Why isn't get_environment const again ?
1897 const as_environment
& env
= const_cast<TextField
*>(this)->get_environment();
1899 as_object
* target
= getObject(env
.target());
1901 IF_VERBOSE_MALFORMED_SWF(
1902 log_swferror(_("Current environment has no target, "
1903 "can't bind VariableName (%s) associated to "
1904 "text field. Gnash will try to register "
1905 "again on next access."), variableName
);
1910 // If the variable string contains a path, we extract
1911 // the appropriate target from it and update the variable
1912 // name. We copy the string so we can assign to it if necessary.
1913 std::string parsedName
= variableName
;
1914 std::string path
, var
;
1915 if (parsePath(variableName
, path
, var
)) {
1916 #ifdef DEBUG_DYNTEXT_VARIABLES
1917 log_debug(_("Variable text Path: %s, Var: %s"), path
, var
);
1919 // find target for the path component
1920 // we use our parent's environment for this
1921 target
= findObject(env
, path
);
1927 IF_VERBOSE_MALFORMED_SWF(
1928 log_swferror(_("VariableName associated to text field refers "
1929 "to an unknown target (%s). It is possible that the "
1930 "DisplayObject will be instantiated later in the SWF "
1931 "stream. Gnash will try to register again on next "
1938 ret
.second
= getURI(getVM(*object()), parsedName
);
1944 TextField::registerTextVariable()
1946 //#define DEBUG_DYNTEXT_VARIABLES 1
1948 #ifdef DEBUG_DYNTEXT_VARIABLES
1949 log_debug(_("registerTextVariable() called"));
1952 if (_text_variable_registered
) {
1956 if (_variable_name
.empty()) {
1957 _text_variable_registered
= true;
1961 VariableRef varRef
= parseTextVariableRef(_variable_name
);
1962 as_object
* target
= varRef
.first
;
1964 log_debug(_("VariableName associated to text field (%s) refer to "
1965 "an unknown target. It is possible that the DisplayObject "
1966 "will be instantiated later in the SWF stream. "
1967 "Gnash will try to register again on next access."),
1972 const ObjectURI
& key
= varRef
.second
;
1973 as_object
* obj
= getObject(this);
1974 const int version
= getSWFVersion(*obj
);
1976 // check if the VariableName already has a value,
1977 // in that case update text value
1979 if (target
->get_member(key
, &val
)) {
1980 // TODO: pass environment to to_string ?
1981 // as_environment& env = get_environment();
1982 setTextValue(utf8::decodeCanonicalString(val
.to_string(), version
));
1984 else if (_textDefined
) {
1985 as_value newVal
= as_value(utf8::encodeCanonicalString(_text
, version
));
1986 target
->set_member(key
, newVal
);
1989 MovieClip
* sprite
= get
<MovieClip
>(target
);
1992 // add the textfield variable to the target sprite
1993 // TODO: have set_textfield_variable take a string_table::key instead ?
1994 sprite
->set_textfield_variable(key
, this);
1997 _text_variable_registered
= true;
2000 /// Parses an HTML tag (between < and >) and puts
2001 /// the contents into tag. Returns false if the
2002 /// tag was incomplete. The iterator is moved to after
2003 /// the closing tag or the end of the string.
2005 TextField::parseHTML(std::wstring
& tag
,
2006 std::map
<std::string
, std::string
>& attributes
,
2007 std::wstring::const_iterator
& it
,
2008 const std::wstring::const_iterator
& e
,
2009 bool& selfclosing
) const
2011 while (it
!= e
&& *it
!= ' ') {
2022 log_error("invalid html tag");
2031 // Check for NULL character
2033 log_error("found NULL character in htmlText");
2036 tag
.push_back(std::toupper(*it
));
2039 while (it
!= e
&& *it
== ' ') {
2040 ++it
; //skip over spaces
2056 log_error("invalid html tag");
2061 std::string attname
;
2062 std::string attvalue
;
2065 while (it
!= e
&& *it
!= '>') {
2066 while (it
!= e
&& *it
!= '=' && *it
!= ' ') {
2069 log_error("found NULL character in htmlText");
2073 log_error("malformed HTML tag, invalid attribute name");
2080 attname
.push_back(std::toupper(*it
));
2083 while (it
!= e
&& (*it
== ' ' || *it
== '=')) {
2084 ++it
; //skip over spaces and '='
2087 if (it
== e
) return false;
2089 if (q
!= '"' && q
!= '\'') {
2090 // This is not an attribute.
2091 while (it
!= e
) ++it
;
2095 // Advance past attribute opener
2097 while (it
!= e
&& *it
!= q
) {
2100 log_error("found NULL character in htmlText");
2104 attvalue
.push_back(std::toupper(*it
));
2108 if (it
== e
) return false;
2111 while (it
!= e
) ++it
;
2115 // Skip attribute closer.
2118 attributes
.insert(std::make_pair(attname
, attvalue
));
2122 if ((*it
!= ' ') && (*it
!= '/') && (*it
!= '>')) {
2123 log_error("malformed HTML tag, invalid attribute value");
2130 while (it
!= e
&& *it
== ' ') {
2131 ++it
; //skip over spaces
2137 } else if (*it
== '/') {
2147 log_error("invalid html tag");
2153 #ifdef GNASH_DEBUG_TEXTFIELDS
2154 log_debug ("HTML tag: %s", utf8::encodeCanonicalString(tag
, 7));
2156 log_error("I declare this a HTML syntax error");
2157 return false; //since we did not return already, must be malformed...?
2161 TextField::set_variable_name(const std::string
& newname
)
2163 if (newname
!= _variable_name
) {
2164 _variable_name
= newname
;
2166 // The name was empty or undefined, so there's nothing more to do.
2167 if (_variable_name
.empty()) return;
2169 _text_variable_registered
= false;
2171 #ifdef DEBUG_DYNTEXT_VARIABLES
2172 log_debug("Calling updateText after change of variable name");
2175 // Use the original definition text if this isn't dynamically
2177 if (_tag
) updateText(_tag
->defaultText());
2179 #ifdef DEBUG_DYNTEXT_VARIABLES
2180 log_debug("Calling registerTextVariable after change of variable "
2181 "name and updateText call");
2183 registerTextVariable();
2188 TextField::pointInShape(boost::int32_t x
, boost::int32_t y
) const
2190 const SWFMatrix wm
= getWorldMatrix(*this).invert();
2193 return _bounds
.point_test(lp
.x
, lp
.y
);
2197 TextField::getDrawBorder() const
2203 TextField::setDrawBorder(bool val
)
2205 if (_drawBorder
!= val
) {
2212 TextField::getBorderColor() const
2214 return _borderColor
;
2218 TextField::setBorderColor(const rgba
& col
)
2220 if (_borderColor
!= col
) {
2227 TextField::getDrawBackground() const
2229 return _drawBackground
;
2233 TextField::setDrawBackground(bool val
)
2235 if (_drawBackground
!= val
) {
2237 _drawBackground
= val
;
2242 TextField::getBackgroundColor() const
2244 return _backgroundColor
;
2248 TextField::setBackgroundColor(const rgba
& col
)
2250 if (_backgroundColor
!= col
) {
2252 _backgroundColor
= col
;
2257 TextField::setTextColor(const rgba
& col
)
2259 if (_textColor
!= col
) {
2263 std::for_each(_displayRecords
.begin(), _displayRecords
.end(),
2264 boost::bind(&SWF::TextRecord::setColor
, _1
, _textColor
));
2269 TextField::setEmbedFonts(bool use
)
2271 if (_embedFonts
!= use
) {
2279 TextField::setWordWrap(bool wrap
)
2281 if (_wordWrap
!= wrap
) {
2289 TextField::setLeading(boost::int16_t h
)
2291 if (_leading
!= h
) {
2298 TextField::setUnderlined(bool v
)
2300 if (_underlined
!= v
) {
2307 TextField::setBullet(bool b
)
2315 TextField::setTabStops(const std::vector
<int>& tabStops
)
2317 _tabStops
.resize(tabStops
.size());
2319 for (size_t i
= 0; i
< tabStops
.size(); i
++) {
2320 _tabStops
[i
] = pixelsToTwips(tabStops
[i
]);
2327 TextField::setURL(std::string url
)
2336 TextField::setTarget(std::string target
)
2338 if (_target
!= target
) {
2345 TextField::setDisplay(TextFormatDisplay display
)
2347 if (_display
!= display
) {
2354 TextField::setAlignment(TextAlignment h
)
2356 if (_alignment
!= h
) {
2363 TextField::setIndent(boost::uint16_t h
)
2372 TextField::setBlockIndent(boost::uint16_t h
)
2374 if (_blockIndent
!= h
) {
2381 TextField::setRightMargin(boost::uint16_t h
)
2383 if (_rightMargin
!= h
) {
2390 TextField::setLeftMargin(boost::uint16_t h
)
2392 if (_leftMargin
!= h
) {
2399 TextField::setFontHeight(boost::uint16_t h
)
2401 if (_fontHeight
!= h
) {
2408 TextField::TypeValue
2409 TextField::parseTypeValue(const std::string
& val
)
2411 StringNoCaseEqual cmp
;
2413 if (cmp(val
, "input")) return typeInput
;
2414 if (cmp(val
, "dynamic")) return typeDynamic
;
2420 TextField::typeValueName(TypeValue val
)
2424 //log_debug("typeInput returned as 'input'");
2427 //log_debug("typeDynamic returned as 'dynamic'");
2430 //log_debug("invalid type %d returned as 'invalid'", (int)val);
2437 TextField::setAutoSize(AutoSize val
)
2439 if (val
== _autoSize
) return;
2446 TextField::TextAlignment
2447 TextField::getTextAlignment()
2449 TextAlignment textAlignment
= getAlignment();
2451 switch (_autoSize
) {
2452 case AUTOSIZE_CENTER
:
2453 textAlignment
= ALIGN_CENTER
;
2456 textAlignment
= ALIGN_LEFT
;
2458 case AUTOSIZE_RIGHT
:
2459 textAlignment
= ALIGN_RIGHT
;
2462 // Leave it as it was.
2466 return textAlignment
;
2470 TextField::onChanged()
2472 as_object
* obj
= getObject(this);
2473 callMethod(obj
, NSV::PROP_BROADCAST_MESSAGE
, "onChanged", obj
);
2476 /// This is called by movie_root when focus is applied to this TextField.
2478 /// The return value is true if the TextField can receive focus.
2479 /// The swfdec testsuite suggests that version 5 textfields cannot ever
2482 TextField::handleFocus()
2486 /// Select the entire text on focus.
2487 setSelection(0, _text
.length());
2491 m_cursor
= _text
.size();
2496 /// This is called by movie_root when focus is removed from the
2497 /// current TextField.
2499 TextField::killFocus()
2501 if (!m_has_focus
) return;
2503 m_has_focus
= false;
2504 format_text(); // is this needed ?
2508 TextField::setWidth(double newwidth
)
2510 const SWFRect
& bounds
= getBounds();
2511 _bounds
.set_to_rect(bounds
.get_x_min(),
2513 bounds
.get_x_min() + newwidth
,
2514 bounds
.get_y_max());
2518 TextField::setHeight(double newheight
)
2520 const SWFRect
& bounds
= getBounds();
2521 _bounds
.set_to_rect(bounds
.get_x_min(),
2524 bounds
.get_y_min() + newheight
);
2527 } // namespace gnash
2532 // indent-tabs-mode: t