drop libxv, add lsb-release
[gnash.git] / libcore / TextField.cpp
blob41517622e2678ae8e4316bda42f17fecfa32e47a
1 // TextField.cpp: User-editable text regions, for Gnash.
2 //
3 // Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Free Software
4 // Foundation, Inc
5 //
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.
10 //
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
22 // bullets
23 // - Above five fields are now implemented (except for target != blank)
24 // - Call movie_root getURL function to properly open url and target
26 // Things to work on:
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
29 // text
31 #include "utf8.h"
32 #include "log.h"
33 #include "swf/DefineEditTextTag.h"
34 #include "MovieClip.h"
35 #include "TextField.h"
36 #include "movie_root.h" // for killing focus
37 #include "as_environment.h"
38 #include "Font.h"
39 #include "fontlib.h"
40 #include "namedStrings.h"
41 #include "StringPredicates.h"
42 #include "TextFormat_as.h"
43 #include "GnashKey.h"
44 #include "TextRecord.h"
45 #include "Point2d.h"
46 #include "GnashNumeric.h"
47 #include "MouseButtonState.h"
48 #include "Global_as.h"
49 #include "Renderer.h"
50 #include "Transform.h"
52 #include <algorithm>
53 #include <string>
54 #include <boost/assign/list_of.hpp>
55 #include <boost/bind.hpp>
56 #include <cstdlib>
57 #include <cctype>
58 #include <utility>
59 #include <map>
61 // Text fields have a fixed 2 pixel padding for each side (regardless of border)
62 #define PADDING_TWIPS 40
64 // Define the following to get detailed log information about
65 // textfield bounds and HTML tags:
66 //#define GNASH_DEBUG_TEXTFIELDS 1
68 // Define this to get debugging info about text formatting
69 //#define GNASH_DEBUG_TEXT_FORMATTING 1
71 namespace gnash {
73 TextField::TextField(as_object* object, DisplayObject* parent,
74 const SWF::DefineEditTextTag& def)
76 InteractiveObject(object, parent),
77 _tag(&def),
78 _url(""),
79 _target(""),
80 _display(),
81 _tabStops(),
82 _variable_name(def.variableName()),
83 _backgroundColor(255,255,255,255),
84 _borderColor(0,0,0,255),
85 _textColor(def.color()),
86 _alignment(def.alignment()),
87 _font(0),
88 m_cursor(0u),
89 _glyphcount(0u),
90 _scroll(0u),
91 _maxScroll(1u),
92 _hScroll(0u),
93 _maxHScroll(0u),
94 _bottomScroll(0u),
95 _linesindisplay(0u),
96 _maxChars(def.maxChars()),
97 _autoSize(def.autoSize() ? AUTOSIZE_LEFT : AUTOSIZE_NONE),
98 _type(def.readOnly() ? typeDynamic : typeInput),
99 _bounds(def.bounds()),
100 _selection(0, 0),
101 _leading(def.leading()),
102 _indent(def.indent()),
103 _blockIndent(0),
104 _leftMargin(def.leftMargin()),
105 _rightMargin(def.rightMargin()),
106 _fontHeight(def.textHeight()),
107 _textDefined(def.hasText()),
108 _htmlTextDefined(def.hasText()),
109 _restrictDefined(false),
110 _underlined(false),
111 _bullet(false),
112 m_has_focus(false),
113 _multiline(def.multiline()),
114 _password(def.password()),
115 _text_variable_registered(false),
116 _drawBackground(def.border()),
117 _drawBorder(def.border()),
118 _embedFonts(def.getUseEmbeddedGlyphs()),
119 _wordWrap(def.wordWrap()),
120 _html(def.html()),
121 _selectable(!def.noSelect())
124 assert(object);
126 // WARNING! remember to set the font *before* setting text value!
127 boost::intrusive_ptr<const Font> f = def.getFont();
128 if (!f) f = fontlib::get_default_font();
129 setFont(f);
131 const int version = getSWFVersion(*object);
133 // set default text *before* calling registerTextVariable
134 // (if the textvariable already exist and has a value
135 // the text will be replaced with it)
136 if (_textDefined)
138 setTextValue(utf8::decodeCanonicalString(def.defaultText(), version));
139 setHtmlTextValue(utf8::decodeCanonicalString(def.defaultText(), version));
142 init();
146 TextField::TextField(as_object* object, DisplayObject* parent,
147 const SWFRect& bounds)
149 InteractiveObject(object, parent),
150 _url(""),
151 _target(""),
152 _display(),
153 _tabStops(),
154 _backgroundColor(255,255,255,255),
155 _borderColor(0, 0, 0, 255),
156 _textColor(0, 0, 0, 255),
157 _alignment(ALIGN_LEFT),
158 _font(0),
159 m_cursor(0u),
160 _glyphcount(0u),
161 _scroll(0u),
162 _maxScroll(1u),
163 _hScroll(0u),
164 _maxHScroll(0u),
165 _bottomScroll(0u),
166 _linesindisplay(0u),
167 _maxChars(0),
168 _autoSize(AUTOSIZE_NONE),
169 _type(typeDynamic),
170 _bounds(bounds),
171 _selection(0, 0),
172 _leading(0),
173 _indent(0),
174 _blockIndent(0),
175 _leftMargin(0),
176 _rightMargin(0),
177 _fontHeight(12 * 20),
178 _textDefined(false),
179 _htmlTextDefined(false),
180 _restrictDefined(false),
181 _underlined(false),
182 _bullet(false),
183 m_has_focus(false),
184 _multiline(false),
185 _password(false),
186 _text_variable_registered(false),
187 _drawBackground(false),
188 _drawBorder(false),
189 _embedFonts(false),
190 _wordWrap(false),
191 _html(false),
192 _selectable(true)
194 // Use the default font (Times New Roman for Windows, Times for Mac
195 // according to docs. They don't say what it is for Linux.
196 boost::intrusive_ptr<const Font> f = fontlib::get_default_font();
197 setFont(f);
199 init();
202 void
203 TextField::init()
205 registerTextVariable();
207 reset_bounding_box(0, 0);
211 TextField::~TextField()
215 void
216 TextField::removeTextField()
218 int depth = get_depth();
219 if ( depth < 0 || depth > 1048575 )
221 //IF_VERBOSE_ASCODING_ERRORS(
222 log_debug(_("CHECKME: removeTextField(%s): TextField depth (%d) "
223 "out of the 'dynamic' zone [0..1048575], won't remove"),
224 getTarget(), depth);
225 //);
226 return;
229 DisplayObject* p = parent();
230 assert(p); // every TextField must have a parent, right ?
232 MovieClip* parentSprite = p->to_movie();
234 if (!parentSprite) {
235 log_error("FIXME: attempt to remove a TextField being a child of a %s",
236 typeName(*p));
237 return;
240 // second argument is arbitrary, see comments above
241 // the function declaration in MovieClip.h
242 parentSprite->remove_display_object(depth, 0);
245 void
246 TextField::show_cursor(Renderer& renderer, const SWFMatrix& mat)
248 if (_textRecords.empty()) {
249 return;
251 boost::uint16_t x;
252 boost::uint16_t y;
253 boost::uint16_t h;
254 size_t i = cursorRecord();
255 SWF::TextRecord record = _textRecords[i];
257 x = record.xOffset();
258 y = record.yOffset() - record.textHeight() + getLeading();
259 h = record.textHeight();
261 if (!record.glyphs().empty()) {
262 for (unsigned int p = 0 ; p < (m_cursor - _recordStarts[i]); ++p) {
263 x += record.glyphs()[p].advance;
267 const std::vector<point> box = boost::assign::list_of
268 (point(x, y))
269 (point(x, y + h));
271 renderer.drawLine(box, rgba(0, 0, 0, 255), mat);
274 size_t
275 TextField::cursorRecord()
277 SWF::TextRecord record;
278 size_t i = 0;
280 if (_textRecords.size() != 0) {
281 while (i < _textRecords.size() && m_cursor >= _recordStarts[i]) {
282 ++i;
284 return i-1;
286 return 0;
289 void
290 TextField::display(Renderer& renderer, const Transform& base)
292 const DisplayObject::MaskRenderer mr(renderer, *this);
294 registerTextVariable();
296 const bool drawBorder = getDrawBorder();
297 const bool drawBackground = getDrawBackground();
299 Transform xform = base * transform();
301 // This is a hack to handle device fonts, which are not affected by
302 // color transform.
303 if (!getEmbedFonts()) xform.colorTransform = SWFCxForm();
305 if ((drawBorder || drawBackground) && !_bounds.is_null()) {
307 std::vector<point> coords(4);
309 boost::int32_t xmin = _bounds.get_x_min();
310 boost::int32_t xmax = _bounds.get_x_max();
311 boost::int32_t ymin = _bounds.get_y_min();
312 boost::int32_t ymax = _bounds.get_y_max();
314 coords[0].setTo(xmin, ymin);
315 coords[1].setTo(xmax, ymin);
316 coords[2].setTo(xmax, ymax);
317 coords[3].setTo(xmin, ymax);
319 rgba borderColor = drawBorder ? getBorderColor() : rgba(0,0,0,0);
320 rgba backgroundColor = drawBackground ? getBackgroundColor() :
321 rgba(0,0,0,0);
323 SWFCxForm cx = xform.colorTransform;
325 if (drawBorder) borderColor = cx.transform(borderColor);
327 if (drawBackground) backgroundColor = cx.transform(backgroundColor);
329 #ifdef GNASH_DEBUG_TEXTFIELDS
330 log_debug("rendering a Pol composed by corners %s", _bounds);
331 #endif
333 renderer.draw_poly(&coords.front(), 4, backgroundColor,
334 borderColor, xform.matrix, true);
338 // Draw our actual text.
339 // Using a SWFMatrix to translate to def bounds seems an hack to me.
340 // A cleaner implementation is likely correctly setting the
341 // _xOffset and _yOffset memebers in glyph records.
342 // Anyway, see bug #17954 for a testcase.
343 if (!_bounds.is_null()) {
344 xform.matrix.concatenate_translation(_bounds.get_x_min(),
345 _bounds.get_y_min());
348 _displayRecords.clear();
349 float scale = getFontHeight() /
350 static_cast<float>(_font->unitsPerEM(_embedFonts));
351 float fontLeading = _font->leading() * scale;
353 //offset the lines
354 int yoffset = (getFontHeight() + fontLeading) + PADDING_TWIPS;
355 size_t recordline;
356 for (size_t i = 0; i < _textRecords.size(); ++i) {
357 recordline = 0;
358 //find the line the record is on
359 while (recordline < _line_starts.size() &&
360 _line_starts[recordline] <= _recordStarts[i]) {
361 ++recordline;
363 //offset the line
364 _textRecords[i].setYOffset((recordline-_scroll)*yoffset);
365 //add the lines we want to the display record
366 if (_textRecords[i].yOffset() > 0 &&
367 _textRecords[i].yOffset() < _bounds.height()) {
368 _displayRecords.push_back(_textRecords[i]);
372 SWF::TextRecord::displayRecords(renderer, xform, _displayRecords,
373 _embedFonts);
375 if (m_has_focus && !isReadOnly()) show_cursor(renderer, xform.matrix);
377 clear_invalidated();
381 void
382 TextField::add_invalidated_bounds(InvalidatedRanges& ranges, bool force)
384 if (!force && !invalidated()) return; // no need to redraw
386 ranges.add(m_old_invalidated_ranges);
388 const SWFMatrix& wm = getWorldMatrix(*this);
390 SWFRect bounds = getBounds();
391 bounds.expand_to_rect(m_text_bounding_box);
392 wm.transform(bounds);
393 ranges.add(bounds.getRange());
396 void
397 TextField::setRestrict(const std::string& restrict)
399 _restrictDefined = true;
401 std::string::const_iterator rit = restrict.begin();
402 std::string::const_iterator re = restrict.end();
403 std::set<wchar_t>::const_iterator locate;
405 if (*rit == '^') { //then this is a true RESTRICT pattern, add all chars to _restrictedchars
406 for (unsigned int i = 0; i <= 255; ++i) {
407 _restrictedchars.insert(char(i));
409 } else { //then this is an ALLOW pattern, _restrictedchars should remain empty
410 _restrictedchars.clear();
413 while (rit != re) {
414 while (rit != re && *rit != '^') { //This loop allows chars
415 if (*rit == '-') {
416 log_error("invalid restrict string");
417 return;
418 } else if (*(rit+1) == '-') {
419 if (re - (rit+2) != 0) {
420 unsigned int q = *(rit+2);
421 for (unsigned int p = *rit; p <= q; (++p)){
422 _restrictedchars.insert(char(p));
424 rit += 3;
425 } else {
426 log_error("invalid restrict string");
427 return;
429 } else if (*rit == '\\') {
430 ++rit;
431 _restrictedchars.insert(*rit);
432 ++rit;
433 } else {
434 _restrictedchars.insert(*rit);
435 ++rit;
438 if (rit != re) {
439 ++rit;
441 while (rit != re && *rit != '^') { //This loop restricts chars
442 locate = _restrictedchars.find(*rit);
443 if (*rit == '-') {
444 log_error("invalid restrict string");
445 return;
446 } else if (*(rit+1) == '-') {
447 if (re - (rit+2) != 0) {
448 unsigned int q = *(rit+2);
449 for (unsigned int p = *rit; p <= q; ++p){
450 locate = _restrictedchars.find(p);
451 if(locate != _restrictedchars.end()) {
452 _restrictedchars.erase(locate);
455 ++rit;
456 ++rit;
457 ++rit;
458 } else {
459 log_error("invalid restrict string");
460 return;
462 } else if (*rit == '\\') {
463 ++rit;
464 locate = _restrictedchars.find(*rit);
465 if(locate != _restrictedchars.end()) {
466 _restrictedchars.erase(locate);
468 ++rit;
469 } else {
470 if(locate != _restrictedchars.end()) {
471 _restrictedchars.erase(locate);
473 ++rit;
476 if (rit != re) {
477 ++rit;
480 _restrict = restrict;
483 void
484 TextField::replaceSelection(const std::string& replace)
487 const int version = getSWFVersion(*getObject(this));
488 const std::wstring& wstr = utf8::decodeCanonicalString(replace, version);
490 const size_t start = _selection.first;
491 const size_t replaceLength = wstr.size();
493 _text.replace(start, _selection.second - start, wstr);
494 _selection = std::make_pair(start + replaceLength, start + replaceLength);
497 void
498 TextField::setSelection(int start, int end)
501 if (_text.empty()) {
502 _selection = std::make_pair(0, 0);
503 return;
506 const size_t textLength = _text.size();
508 if (start < 0) start = 0;
509 else start = std::min<size_t>(start, textLength);
511 if (end < 0) end = 0;
512 else end = std::min<size_t>(end, textLength);
514 // The cursor position is always set to the end value, even if the
515 // two values are swapped to obtain the selection. Equal values are
516 // fine.
517 m_cursor = end;
518 if (start > end) std::swap(start, end);
520 _selection = std::make_pair(start, end);
523 void
524 TextField::notifyEvent(const event_id& ev)
526 switch (ev.id())
528 case event_id::PRESS:
530 movie_root& root = stage();
531 boost::int32_t x_mouse, y_mouse;
532 root.get_mouse_state(x_mouse, y_mouse);
534 SWFMatrix m = getMatrix(*this);
536 x_mouse -= m.get_x_translation();
537 y_mouse -= m.get_y_translation();
539 SWF::TextRecord rec;
541 for (size_t i=0; i < _textRecords.size(); ++i) {
542 if ((x_mouse > _textRecords[i].xOffset()) &&
543 (x_mouse < _textRecords[i].xOffset()+_textRecords[i].recordWidth()) &&
544 (y_mouse > _textRecords[i].yOffset()-_textRecords[i].textHeight()) &&
545 (y_mouse < _textRecords[i].yOffset())) {
546 rec = _textRecords[i];
547 break;
551 if (!rec.getURL().empty()) {
552 root.getURL(rec.getURL(), rec.getTarget(), "",
553 MovieClip::METHOD_NONE);
556 break;
558 case event_id::KEY_PRESS:
560 setHtml(false); //editable html fields are not yet implemented
561 std::wstring s = _text;
563 // id.keyCode is the unique gnash::key::code for a DisplayObject/key.
564 // The maximum value is about 265, including function keys.
565 // It seems that typing in DisplayObjects outside the Latin-1 set
566 // (256 DisplayObject codes, identical to the first 256 of UTF-8)
567 // is not supported, though a much greater number UTF-8 codes can be
568 // stored and displayed. See utf.h for more information.
569 // This is a limit on the number of key codes, not on the
570 // capacity of strings.
571 gnash::key::code c = ev.keyCode();
574 // maybe _text is changed in ActionScript
575 m_cursor = std::min<size_t>(m_cursor, _text.size());
577 size_t cur_cursor = m_cursor;
578 size_t previouslinesize = 0;
579 size_t nextlinesize = 0;
580 size_t manylines = _line_starts.size();
581 LineStarts::iterator linestartit = _line_starts.begin();
582 LineStarts::const_iterator linestartend = _line_starts.end();
584 switch (c)
586 case key::BACKSPACE:
587 if (isReadOnly()) return;
588 if (m_cursor > 0)
590 s.erase(m_cursor - 1, 1);
591 m_cursor--;
592 setTextValue(s);
594 break;
596 case key::DELETEKEY:
597 if (isReadOnly()) return;
598 if (_glyphcount > m_cursor)
600 s.erase(m_cursor, 1);
601 setTextValue(s);
603 break;
605 case key::INSERT: // TODO
606 if (isReadOnly()) return;
607 break;
609 case key::HOME:
610 while ( linestartit < linestartend && *linestartit <= m_cursor ) {
611 cur_cursor = *linestartit;
612 linestartit++;
614 m_cursor = cur_cursor;
615 break;
617 case key::PGUP:
618 // if going a page up is too far...
619 if(_scroll < _linesindisplay) {
620 _scroll = 0;
621 m_cursor = 0;
622 } else { // go a page up
623 _scroll -= _linesindisplay;
624 m_cursor = _line_starts[_scroll];
626 scrollLines();
627 break;
629 case key::UP:
630 while ( linestartit < linestartend && *linestartit <= m_cursor ) {
631 cur_cursor = *linestartit;
632 linestartit++;
634 //if there is no previous line
635 if ( linestartit-_line_starts.begin() - 2 < 0 ) {
636 m_cursor = 0;
637 break;
639 previouslinesize = _textRecords[linestartit-_line_starts.begin() - 2].glyphs().size();
640 //if the previous line is smaller
641 if (m_cursor - cur_cursor > previouslinesize) {
642 m_cursor = *(--(--linestartit)) + previouslinesize;
643 } else {
644 m_cursor = *(--(--linestartit)) + (m_cursor - cur_cursor);
646 if (m_cursor < _line_starts[_scroll] && _line_starts[_scroll] != 0) {
647 --_scroll;
649 scrollLines();
650 break;
652 case key::END:
653 while ( linestartit < linestartend && *linestartit <= m_cursor ) {
654 linestartit++;
656 m_cursor = linestartit != linestartend ? *linestartit - 1 : _text.size();
657 break;
659 case key::PGDN:
660 //if going another page down is too far...
661 if(_scroll + _linesindisplay >= manylines) {
662 if(manylines - _linesindisplay <= 0) {
663 _scroll = 0;
664 } else {
665 _scroll = manylines - _linesindisplay;
667 if(m_cursor < _line_starts[_scroll-1]) {
668 m_cursor = _line_starts[_scroll-1];
669 } else {
670 m_cursor = _text.size();
672 } else { //go a page down
673 _scroll += _linesindisplay;
674 m_cursor = _line_starts[_scroll];
676 scrollLines();
677 break;
679 case key::DOWN:
681 while (linestartit < linestartend &&
682 *linestartit <= m_cursor ) {
683 cur_cursor = *linestartit;
684 linestartit++;
687 // linestartit should never be before _line_starts.begin()
688 const size_t currentLine = linestartit -
689 _line_starts.begin();
691 //if there is no next line
692 if (currentLine >= manylines ) {
693 m_cursor = _text.size();
694 break;
696 nextlinesize = _textRecords[currentLine].glyphs().size();
698 //if the next line is smaller
699 if (m_cursor - cur_cursor > nextlinesize) {
700 m_cursor = *linestartit + nextlinesize;
701 } else {
702 //put the cursor at the same character distance
703 m_cursor = *(linestartit) + (m_cursor - cur_cursor);
705 if (_line_starts.size() > _linesindisplay &&
706 m_cursor >= _line_starts[_scroll+_linesindisplay]) {
707 ++_scroll;
709 scrollLines();
710 break;
713 case key::LEFT:
714 m_cursor = m_cursor > 0 ? m_cursor - 1 : 0;
715 break;
717 case key::RIGHT:
718 m_cursor = m_cursor < _glyphcount ? m_cursor + 1 :
719 _glyphcount;
720 break;
722 case key::ENTER:
723 if (isReadOnly()) return;
724 if ( !multiline() )
725 break;
727 default:
729 if ( maxChars()!=0 )
731 if ( _maxChars <= _glyphcount )
733 break;
737 if (isReadOnly()) return;
738 wchar_t t = static_cast<wchar_t>(
739 gnash::key::codeMap[c][key::ASCII]);
740 if (t != 0)
743 if (!_restrictDefined) {
744 // Insert one copy of the character
745 // at the cursor position.
746 s.insert(m_cursor, 1, t);
747 m_cursor++;
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);
752 m_cursor++;
753 } else if (_restrictedchars.count(tolower(t))) {
754 // restrict substitutes the opposite case
755 s.insert(m_cursor, 1, tolower(t));
756 m_cursor++;
757 } else if (_restrictedchars.count(toupper(t))) {
758 // restrict substitutes the opposite case
759 s.insert(m_cursor, 1, toupper(t));
760 m_cursor++;
763 setTextValue(s);
765 onChanged();
766 set_invalidated();
769 default:
770 return;
774 InteractiveObject*
775 TextField::topmostMouseEntity(boost::int32_t x, boost::int32_t y)
778 if (!visible()) return 0;
780 // Not selectable, so don't catch mouse events!
781 if (!_selectable) return 0;
783 SWFMatrix m = getMatrix(*this);
784 point p(x, y);
785 m.invert().transform(p);
787 if (_bounds.point_test(p.x, p.y)) return this;
789 return 0;
792 void
793 TextField::updateText(const std::string& str)
795 const int version = getSWFVersion(*getObject(this));
796 const std::wstring& wstr = utf8::decodeCanonicalString(str, version);
797 updateText(wstr);
800 void
801 TextField::updateText(const std::wstring& wstr)
803 _textDefined = true;
805 if (_text == wstr) return;
807 set_invalidated();
809 _text = wstr;
810 format_text();
813 void
814 TextField::updateHtmlText(const std::string& str)
816 int version = getSWFVersion(*getObject(this));
817 const std::wstring& wstr = utf8::decodeCanonicalString(str, version);
818 updateHtmlText(wstr);
821 void
822 TextField::updateHtmlText(const std::wstring& wstr)
824 _htmlTextDefined = true;
826 if (_htmlText == wstr) return;
828 set_invalidated();
830 _htmlText = wstr;
831 format_text();
834 void
835 TextField::setHtmlTextValue(const std::wstring& wstr)
837 if (!doHtml()) {
838 updateText(wstr);
839 } else {
840 //updateText with no HTML tags
841 //for now, it is better to make the display correct
842 updateText(wstr);
844 updateHtmlText(wstr);
846 if ( ! _variable_name.empty() && _text_variable_registered )
848 // TODO: notify MovieClip if we have a variable name !
849 VariableRef ref = parseTextVariableRef(_variable_name);
850 as_object* tgt = ref.first;
851 if ( tgt )
853 const int version = getSWFVersion(*getObject(this));
854 // we shouldn't truncate, right?
855 tgt->set_member(ref.second, utf8::encodeCanonicalString(wstr,
856 version));
858 else
860 // nothing to do (too early ?)
861 log_debug("setHtmlTextValue: variable name %s points to a non-existent"
862 " target, I guess we would not be registered if this was "
863 "true, or the sprite we've registered our variable name "
864 "has been unloaded", _variable_name);
869 void
870 TextField::setTextValue(const std::wstring& wstr)
872 if (!doHtml()) {
873 updateHtmlText(wstr);
874 } else {
875 //updateHtmlText and insert necessary html tags
877 updateText(wstr);
879 if ( ! _variable_name.empty() && _text_variable_registered )
881 // TODO: notify MovieClip if we have a variable name !
882 VariableRef ref = parseTextVariableRef(_variable_name);
883 as_object* tgt = ref.first;
884 if ( tgt )
886 const int version = getSWFVersion(*getObject(this));
887 // we shouldn't truncate, right?
888 tgt->set_member(ref.second, utf8::encodeCanonicalString(wstr,
889 version));
891 else
893 // nothing to do (too early ?)
894 log_debug("setTextValue: variable name %s points to a non-existent"
895 " target, I guess we would not be registered if this was "
896 "true, or the sprite we've registered our variable name "
897 "has been unloaded", _variable_name);
902 std::string
903 TextField::get_text_value() const
905 // we need the const_cast here because registerTextVariable
906 // *might* change our text value, calling the non-const
907 // setTextValue().
908 // This happens if the TextVariable has not been already registered
909 // and during registration comes out to name an existing variable
910 // with a pre-existing value.
911 const_cast<TextField*>(this)->registerTextVariable();
913 const int version = getSWFVersion(*getObject(this));
915 return utf8::encodeCanonicalString(_text, version);
918 std::string
919 TextField::get_htmltext_value() const
921 const_cast<TextField*>(this)->registerTextVariable();
922 const int version = getSWFVersion(*getObject(const_cast<TextField*>(this)));
923 return utf8::encodeCanonicalString(_htmlText, version);
926 void
927 TextField::setTextFormat(TextFormat_as& tf)
929 //TODO: this is lazy. we should set all the TextFormat variables HERE, i think
930 //This is just so we can set individual variables without having to call format_text()
931 //This calls format_text() at the end of setting TextFormat
932 if (tf.align()) setAlignment(*tf.align());
933 if (tf.size()) setFontHeight(*tf.size()); // keep twips
934 if (tf.indent()) setIndent(*tf.indent());
935 if (tf.blockIndent()) setBlockIndent(*tf.blockIndent());
936 if (tf.leading()) setLeading(*tf.leading());
937 if (tf.leftMargin()) setLeftMargin(*tf.leftMargin());
938 if (tf.rightMargin()) setRightMargin(*tf.rightMargin());
939 if (tf.color()) setTextColor(*tf.color());
940 if (tf.underlined()) setUnderlined(*tf.underlined());
941 if (tf.bullet()) setBullet(*tf.bullet());
942 setDisplay(tf.display());
943 if (tf.tabStops()) setTabStops(*tf.tabStops());
945 // NEED TO IMPLEMENT THESE TWO
946 if (tf.url()) setURL(*tf.url());
947 if (tf.target()) setTarget(*tf.target());
949 format_text();
952 float
953 TextField::align_line(TextAlignment align, int last_line_start_record, float x)
956 float width = _bounds.width();
957 float right_margin = getRightMargin();
959 float extra_space = (width - right_margin) - x - PADDING_TWIPS;
961 if (extra_space <= 0.0f) {
962 #ifdef GNASH_DEBUG_TEXTFIELDS
963 log_debug(_("TextField text doesn't fit in its boundaries: "
964 "width %g, margin %g - nothing to align"),
965 width, right_margin);
966 #endif
967 return 0.0f;
970 float shift_right = 0.0f;
972 switch (align) {
973 case ALIGN_LEFT:
974 // Nothing to do; already aligned left.
975 return 0.0f;
976 case ALIGN_CENTER:
977 // Distribute the space evenly on both sides.
978 shift_right = extra_space / 2;
979 break;
980 case ALIGN_RIGHT:
981 // Shift all the way to the right.
982 shift_right = extra_space;
983 break;
984 case ALIGN_JUSTIFY:
985 // What should we do here?
986 break;
989 // Shift the beginnings of the records on this line.
990 for (size_t i = last_line_start_record; i < _textRecords.size(); ++i) {
991 SWF::TextRecord& rec = _textRecords[i];
992 rec.setXOffset(rec.xOffset() + shift_right);
994 return shift_right;
997 boost::intrusive_ptr<const Font>
998 TextField::setFont(boost::intrusive_ptr<const Font> newfont)
1000 if ( newfont == _font ) return _font;
1002 boost::intrusive_ptr<const Font> oldfont = _font;
1003 set_invalidated();
1004 _font = newfont;
1005 format_text();
1006 return oldfont;
1010 void
1011 TextField::insertTab(SWF::TextRecord& rec, boost::int32_t& x, float scale)
1013 // tab (ASCII HT)
1014 const int space = 32;
1015 int index = rec.getFont()->get_glyph_index(space, _embedFonts);
1016 if ( index == -1 )
1018 IF_VERBOSE_MALFORMED_SWF (
1019 log_error(_("TextField: missing glyph for space char (needed "
1020 "for TAB). Make sure DisplayObject shapes for font "
1021 "%s are being exported into your SWF file."),
1022 rec.getFont()->name());
1025 else
1027 std::vector<int> tabStops;
1028 tabStops = _tabStops;
1030 std::sort(_tabStops.begin(), _tabStops.end());
1032 int tab = 0;
1033 if ( !_tabStops.empty() )
1035 tab = _tabStops.back()+1;
1037 for (size_t i = 0; i < tabStops.size(); ++i)
1039 if (tabStops[i] > x)
1041 if((tabStops[i] - x) < tab)
1043 tab = tabStops[i] - x;
1049 // This is necessary in case the number of tabs in the text
1050 // are more than the actual number of tabStops inside the
1051 // vector
1052 if ( !(tab == _tabStops.back()+1) )
1054 SWF::TextRecord::GlyphEntry ge;
1055 ge.index = rec.getFont()->get_glyph_index(32, _embedFonts);
1056 ge.advance = tab;
1057 rec.addGlyph(ge);
1058 x+=ge.advance;
1061 else
1063 SWF::TextRecord::GlyphEntry ge;
1064 ge.index = index;
1065 ge.advance = scale * rec.getFont()->get_advance(index,
1066 _embedFonts);
1068 const int tabstop = 4;
1069 rec.addGlyph(ge, tabstop);
1070 x += ge.advance * tabstop;
1075 void
1076 TextField::format_text()
1078 _textRecords.clear();
1079 _line_starts.clear();
1080 _recordStarts.clear();
1081 _glyphcount = 0;
1083 _recordStarts.push_back(0);
1085 // nothing more to do if text is empty
1086 if ( _text.empty() )
1088 // TODO: should we still reset _bounds if autoSize != AUTOSIZE_NONE ?
1089 // not sure we should...
1090 reset_bounding_box(0, 0);
1091 return;
1094 LineStarts::iterator linestartit = _line_starts.begin();
1095 LineStarts::const_iterator linestartend = _line_starts.end();
1097 AutoSize autoSize = getAutoSize();
1098 if (autoSize != AUTOSIZE_NONE) {
1099 // When doing WordWrap we don't want to change
1100 // the boundaries. See bug #24348
1101 if (!doWordWrap()) {
1102 _bounds.set_to_rect(0, 0, 0, 0); // this is correct for 'true'
1106 // FIXME: I don't think we should query the definition
1107 // to find the appropriate font to use, as ActionScript
1108 // code should be able to change the font of a TextField
1109 if (!_font) {
1110 log_error(_("No font for TextField!"));
1111 return;
1114 boost::uint16_t fontHeight = getFontHeight();
1115 float scale = fontHeight /
1116 static_cast<float>(_font->unitsPerEM(_embedFonts));
1117 const float fontLeading = _font->leading() * scale;
1118 const boost::uint16_t leftMargin = getLeftMargin();
1119 const boost::uint16_t indent = getIndent();
1120 const boost::uint16_t blockIndent = getBlockIndent();
1121 const bool underlined = getUnderlined();
1123 /// Remember the current bounds for autosize.
1124 SWFRect oldBounds(_bounds);
1126 SWF::TextRecord rec; // one to work on
1127 rec.setFont(_font.get());
1128 rec.setUnderline(underlined);
1129 rec.setColor(getTextColor());
1130 rec.setXOffset(PADDING_TWIPS +
1131 std::max(0, leftMargin + indent + blockIndent));
1132 rec.setYOffset(PADDING_TWIPS + fontHeight + fontLeading);
1133 rec.setTextHeight(fontHeight);
1135 // create in textrecord.h
1136 rec.setURL(_url);
1137 rec.setTarget(_target);
1139 // BULLET CASE:
1141 // First, we indent 10 spaces, and then place the bullet
1142 // character (in this case, an asterisk), then we pad it
1143 // again with 10 spaces
1144 // Note: this works only for additional lines of a
1145 // bulleted list, so that is why there is a bullet format
1146 // in the beginning of format_text()
1147 if ( _bullet )
1149 int space = rec.getFont()->get_glyph_index(32, _embedFonts);
1151 SWF::TextRecord::GlyphEntry ge;
1152 ge.index = space;
1153 ge.advance = scale * rec.getFont()->get_advance(space, _embedFonts);
1154 rec.addGlyph(ge, 5);
1156 // We use an asterisk instead of a bullet
1157 int bullet = rec.getFont()->get_glyph_index(42, _embedFonts);
1158 ge.index = bullet;
1159 ge.advance = scale * rec.getFont()->get_advance(bullet, _embedFonts);
1160 rec.addGlyph(ge);
1162 space = rec.getFont()->get_glyph_index(32, _embedFonts);
1163 ge.index = space;
1164 ge.advance = scale * rec.getFont()->get_advance(space, _embedFonts);
1165 rec.addGlyph(ge, 4);
1168 boost::int32_t x = static_cast<boost::int32_t>(rec.xOffset());
1169 boost::int32_t y = static_cast<boost::int32_t>(rec.yOffset());
1171 // Start the bbox at the upper-left corner of the first glyph.
1172 //reset_bounding_box(x, y + fontHeight);
1174 int last_code = -1; // only used if _embedFonts
1175 int last_space_glyph = -1;
1176 size_t last_line_start_record = 0;
1178 float leading = getLeading();
1179 leading += fontLeading * scale; // not sure this is correct...
1181 _line_starts.push_back(0);
1183 // String iterators are very sensitive to
1184 // potential changes to the string (to allow for copy-on-write).
1185 // So there must be no external changes to the string or
1186 // calls to most non-const member functions during this loop.
1187 // Especially not c_str() or data().
1188 std::wstring::const_iterator it = _text.begin();
1189 const std::wstring::const_iterator e = _text.end();
1191 ///handleChar takes care of placing the glyphs
1192 handleChar(it, e, x, y, rec, last_code, last_space_glyph,
1193 last_line_start_record);
1195 // Expand bounding box to include the whole text (if autoSize and wordWrap
1196 // is not in operation.
1197 if (_autoSize != AUTOSIZE_NONE && !doWordWrap())
1199 _bounds.expand_to_point(x + PADDING_TWIPS, y + PADDING_TWIPS);
1201 if (_autoSize == AUTOSIZE_RIGHT) {
1202 /// Autosize right expands from the previous right margin.
1203 SWFMatrix m;
1205 m.tx = oldBounds.get_x_max() - _bounds.width();
1206 m.transform(_bounds);
1208 else if (_autoSize == AUTOSIZE_CENTER) {
1209 // Autosize center expands from the previous center.
1210 SWFMatrix m;
1211 m.tx = oldBounds.get_x_min() + oldBounds.width() / 2.0 -
1212 _bounds.width() / 2.0;
1213 m.transform(_bounds);
1217 // Add the last line to our output.
1218 _textRecords.push_back(rec);
1220 // align the last (or single) line
1221 align_line(getTextAlignment(), last_line_start_record, x);
1224 scrollLines();
1226 set_invalidated(); //redraw
1230 void
1231 TextField::scrollLines()
1233 boost::uint16_t fontHeight = getFontHeight();
1234 float scale = fontHeight /
1235 static_cast<float>(_font->unitsPerEM(_embedFonts));
1236 float fontLeading = _font->leading() * scale;
1237 _linesindisplay = _bounds.height() / (fontHeight + fontLeading + PADDING_TWIPS);
1238 if (_linesindisplay > 0) { //no need to place lines if we can't fit any
1239 size_t manylines = _line_starts.size();
1240 size_t lastvisibleline = _scroll + _linesindisplay;
1241 size_t line = 0;
1243 // If there aren't as many lines as we have scrolled, display the
1244 // end of the text.
1245 if (manylines < _scroll) {
1246 _scroll = manylines - _linesindisplay;
1247 return;
1250 // which line is the cursor on?
1251 while (line < manylines && _line_starts[line] <= m_cursor) {
1252 ++line;
1255 if (manylines - _scroll <= _linesindisplay) {
1256 // This is for if we delete a line
1257 if (manylines < _linesindisplay) _scroll = 0;
1258 else {
1259 _scroll = manylines - _linesindisplay;
1261 } else if (line < _scroll) {
1262 //if we are at a higher position, scroll the lines down
1263 _scroll -= _scroll - line;
1264 } else if (manylines > _scroll + _linesindisplay) {
1265 //if we are at a lower position, scroll the lines up
1266 if (line >= (_scroll+_linesindisplay)) {
1267 _scroll += line - (lastvisibleline);
1273 void
1274 TextField::newLine(boost::int32_t& x, boost::int32_t& y,
1275 SWF::TextRecord& rec, int& last_space_glyph,
1276 LineStarts::value_type& last_line_start_record, float div)
1278 // newline.
1279 LineStarts::iterator linestartit = _line_starts.begin();
1280 LineStarts::const_iterator linestartend = _line_starts.end();
1282 float scale = _fontHeight /
1283 static_cast<float>(_font->unitsPerEM(_embedFonts));
1284 float fontLeading = _font->leading() * scale;
1285 float leading = getLeading();
1286 leading += fontLeading * scale; // not sure this is correct...
1288 // Close out this stretch of glyphs.
1289 ++_glyphcount;
1290 _textRecords.push_back(rec);
1291 _recordStarts.push_back(_glyphcount);
1292 align_line(getTextAlignment(), last_line_start_record, x);
1294 // Expand bounding box to include last column of text ...
1295 if (!doWordWrap() && _autoSize != AUTOSIZE_NONE) {
1296 _bounds.expand_to_point(x + PADDING_TWIPS, y + PADDING_TWIPS);
1299 // new paragraphs get the indent.
1300 x = std::max(0, getLeftMargin() + getIndent() + getBlockIndent()) +
1301 PADDING_TWIPS;
1302 y += div * (getFontHeight() + leading);
1303 if (y >= _bounds.height()) {
1304 ++_maxScroll;
1307 // Start a new record on the next line. Other properties of the
1308 // TextRecord should be left unchanged.
1309 rec.clearGlyphs();
1310 rec.setXOffset(x);
1311 rec.setYOffset(y);
1313 last_space_glyph = -1;
1314 last_line_start_record = _textRecords.size();
1316 linestartit = _line_starts.begin();
1317 linestartend = _line_starts.end();
1318 //Fit a line_start in the correct place
1319 const size_t currentPos = _glyphcount;
1321 while (linestartit < linestartend && *linestartit < currentPos)
1323 ++linestartit;
1325 _line_starts.insert(linestartit, currentPos);
1327 // BULLET CASE:
1329 // First, we indent 10 spaces, and then place the bullet
1330 // character (in this case, an asterisk), then we pad it
1331 // again with 10 spaces
1332 // Note: this works only for additional lines of a
1333 // bulleted list, so that is why there is a bullet format
1334 // in the beginning of format_text()
1335 if (_bullet)
1337 int space = rec.getFont()->get_glyph_index(32, _embedFonts);
1338 SWF::TextRecord::GlyphEntry ge;
1339 ge.index = space;
1340 ge.advance = scale * rec.getFont()->get_advance(space, _embedFonts);
1342 rec.addGlyph(ge,5);
1343 _glyphcount += 5;
1345 int bullet = rec.getFont()->get_glyph_index(42, _embedFonts);
1346 ge.index = bullet;
1347 ge.advance = scale * rec.getFont()->get_advance(bullet, _embedFonts);
1348 rec.addGlyph(ge);
1349 ++_glyphcount;
1351 ge.index = space;
1352 ge.advance = scale * rec.getFont()->get_advance(space, _embedFonts);
1354 rec.addGlyph(ge,4);
1355 _glyphcount += 4;
1359 void
1360 TextField::handleChar(std::wstring::const_iterator& it,
1361 const std::wstring::const_iterator& e, boost::int32_t& x,
1362 boost::int32_t& y, SWF::TextRecord& rec, int& last_code,
1363 int& last_space_glyph, LineStarts::value_type& last_line_start_record)
1365 LineStarts::iterator linestartit = _line_starts.begin();
1366 LineStarts::const_iterator linestartend = _line_starts.end();
1368 float scale = _fontHeight /
1369 static_cast<float>(_font->unitsPerEM(_embedFonts));
1370 float fontDescent = _font->descent(_embedFonts) * scale;
1371 float fontLeading = _font->leading() * scale;
1372 float leading = getLeading();
1373 leading += fontLeading * scale; // not sure this is correct...
1375 boost::uint32_t code = 0;
1376 while (it != e)
1378 code = *it++;
1379 if (!code) break;
1381 if ( _embedFonts )
1383 x += rec.getFont()->get_kerning_adjustment(last_code,
1384 static_cast<int>(code)) * scale;
1385 last_code = static_cast<int>(code);
1388 // Expand the bounding-box to the lower-right corner of each glyph as
1389 // we generate it.
1390 m_text_bounding_box.expand_to_point(x, y + fontDescent);
1391 switch (code)
1393 case 27:
1394 // Ignore escape
1395 break;
1396 case 9:
1397 insertTab(rec, x, scale);
1398 break;
1399 case 8:
1400 // Backspace
1402 // This is a limited hack to enable overstrike effects.
1403 // It backs the cursor up by one DisplayObject and then continues
1404 // the layout. E.g. you can use this to display an underline
1405 // cursor inside a simulated text-entry box.
1407 // ActionScript understands the '\b' escape sequence
1408 // for inserting a BS DisplayObject.
1410 // ONLY WORKS FOR BACKSPACING OVER ONE CHARACTER, WON'T BS
1411 // OVER NEWLINES, ETC.
1413 if (!rec.glyphs().empty())
1415 // Peek at the previous glyph, and zero out its advance
1416 // value, so the next char overwrites it.
1417 float advance = rec.glyphs().back().advance;
1418 x -= advance;
1419 // Remove one glyph
1420 rec.clearGlyphs(1);
1422 continue;
1423 case 13:
1424 case 10:
1426 newLine(x,y,rec,last_space_glyph,last_line_start_record,1.0);
1427 break;
1429 case '<':
1430 if (doHtml())
1432 //close out this stretch of glyphs
1433 _textRecords.push_back(rec);
1434 rec.clearGlyphs();
1435 _recordStarts.push_back(_glyphcount);
1436 if (*it == '/') {
1437 while (it != e && *it != '>') {
1438 ++it;
1440 ++it;
1441 return;
1443 LOG_ONCE(log_debug(_("HTML in a text field is unsupported, "
1444 "gnash will just ignore the tags and "
1445 "print their content")));
1447 std::wstring discard;
1448 std::map<std::string,std::string> attributes;
1449 SWF::TextRecord newrec;
1450 newrec.setFont(rec.getFont());
1451 newrec.setUnderline(rec.underline());
1452 newrec.setColor(rec.color());
1453 newrec.setTextHeight(rec.textHeight());
1454 newrec.setXOffset(x);
1455 newrec.setYOffset(y);
1456 bool selfclosing = false;
1457 bool complete = parseHTML(discard, attributes, it, e, selfclosing);
1458 std::string s(discard.begin(), discard.end());
1460 std::map<std::string,std::string>::const_iterator attloc;
1462 if (!complete) {
1463 //parsing went wrong
1464 continue;
1465 } else {
1466 // Don't think this is the best way to match with
1467 // tags...
1468 // TODO: assumes tags are properly nested. This isn't
1469 // correct.
1470 if (s == "U") {
1471 //underline
1472 newrec.setUnderline(true);
1473 handleChar(it, e, x, y, newrec, last_code,
1474 last_space_glyph, last_line_start_record);
1476 else if (s == "A") {
1477 // anchor (blue text).
1478 rgba color(0, 0, 0xff, 0xff);
1479 newrec.setColor(color);
1480 newrec.setUnderline(true);
1481 attloc = attributes.find("HREF");
1482 if (attloc != attributes.end()) {
1483 newrec.setURL(attloc->second);
1485 attloc = attributes.find("TARGET");
1486 if (attloc !=attributes.end()) {
1487 newrec.setTarget(attloc->second);
1489 handleChar(it, e, x, y, newrec, last_code,
1490 last_space_glyph, last_line_start_record);
1492 else if (s == "B") {
1493 //bold
1494 Font* boldfont = new Font(rec.getFont()->name(),
1495 true, rec.getFont()->isItalic());
1496 newrec.setFont(boldfont);
1497 handleChar(it, e, x, y, newrec, last_code,
1498 last_space_glyph, last_line_start_record);
1500 else if (s == "FONT") {
1501 //font
1502 boost::uint16_t originalsize = _fontHeight;
1503 attloc = attributes.find("COLOR");
1504 if (attloc != attributes.end()) {
1505 std::string hexval(attloc->second);
1506 if (hexval.empty() || hexval[0] != '#') {
1507 log_error("Unexpected color value");
1509 else {
1510 hexval.erase(0, 1);
1511 // font COLOR attribute
1512 const rgba color =
1513 colorFromHexString(hexval);
1514 newrec.setColor(color);
1517 attloc = attributes.find("FACE");
1518 if (attloc != attributes.end()) {
1519 //font FACE attribute
1520 Font* newfont = new Font(attloc->second,
1521 rec.getFont()->isBold(), rec.getFont()->isItalic());
1522 newrec.setFont(newfont);
1524 attloc = attributes.find("SIZE");
1525 if (attloc != attributes.end()) {
1526 //font SIZE attribute
1527 std::string firstchar = attloc->second.substr(0,1);
1528 if (firstchar == "+") {
1529 newrec.setTextHeight(rec.textHeight() +
1531 (pixelsToTwips(std::strtol(
1532 attloc->second.substr(1,attloc->second.length()-1).data(),
1533 NULL,10))));
1534 newrec.setYOffset(PADDING_TWIPS +
1535 newrec.textHeight() +
1536 (fontLeading - fontDescent));
1537 _fontHeight += pixelsToTwips(std::strtol(
1538 attloc->second.substr(1,attloc->second.length()-1).data(),
1539 NULL,10));
1540 } else if (firstchar == "-") {
1541 newrec.setTextHeight(rec.textHeight() -
1542 (pixelsToTwips(std::strtol(
1543 attloc->second.substr(1,attloc->second.length()-1).data(),
1544 NULL,10))));
1545 newrec.setYOffset(PADDING_TWIPS +
1546 newrec.textHeight() +
1547 (fontLeading - fontDescent));
1548 _fontHeight -= pixelsToTwips(std::strtol(
1549 attloc->second.substr(1,attloc->second.length()-1).data(),
1550 NULL,10));
1551 } else {
1552 newrec.setTextHeight(pixelsToTwips(std::strtol(
1553 attloc->second.data(), NULL, 10)));
1554 newrec.setYOffset(PADDING_TWIPS + newrec.textHeight() +
1555 (fontLeading - fontDescent));
1556 _fontHeight = pixelsToTwips(std::strtol(
1557 attloc->second.data(), NULL, 10));
1560 handleChar(it, e, x, y, newrec, last_code,
1561 last_space_glyph, last_line_start_record);
1562 _fontHeight = originalsize;
1563 y = newrec.yOffset();
1565 else if (s == "IMG") {
1566 //image
1567 log_unimpl("<img> html tag in TextField");
1568 handleChar(it, e, x, y, newrec, last_code,
1569 last_space_glyph, last_line_start_record);
1571 else if (s == "I") {
1572 //italic
1573 Font* italicfont = new Font(rec.getFont()->name(),
1574 rec.getFont()->isBold(), true);
1575 newrec.setFont(italicfont);
1576 handleChar(it, e, x, y, newrec, last_code,
1577 last_space_glyph, last_line_start_record);
1578 } else if (s == "LI") {
1579 //list item (bullet)
1580 int space = newrec.getFont()->get_glyph_index(32, _embedFonts);
1581 SWF::TextRecord::GlyphEntry ge;
1582 ge.index = space;
1583 ge.advance = scale * newrec.getFont()->get_advance(space, _embedFonts);
1584 newrec.addGlyph(ge, 5);
1586 // We use an asterisk instead of a bullet
1587 int bullet = newrec.getFont()->get_glyph_index(42, _embedFonts);
1588 ge.index = bullet;
1589 ge.advance = scale * newrec.getFont()->get_advance(bullet, _embedFonts);
1590 newrec.addGlyph(ge);
1592 space = newrec.getFont()->get_glyph_index(32, _embedFonts);
1593 ge.index = space;
1594 ge.advance = scale * newrec.getFont()->get_advance(space, _embedFonts);
1595 newrec.addGlyph(ge, 4);
1597 handleChar(it, e, x, y, newrec, last_code,
1598 last_space_glyph, last_line_start_record);
1599 newLine(x, y, newrec, last_space_glyph,
1600 last_line_start_record, 1.0);
1602 else if (s == "SPAN") {
1603 //span
1604 log_unimpl("<span> html tag in TextField");
1605 handleChar(it, e, x, y, newrec, last_code,
1606 last_space_glyph, last_line_start_record);
1608 else if (s == "TEXTFORMAT") {
1609 log_debug("in textformat");
1610 //textformat
1611 boost::uint16_t originalblockindent = getBlockIndent();
1612 boost::uint16_t originalindent = getIndent();
1613 boost::uint16_t originalleading = getLeading();
1614 boost::uint16_t originalleftmargin = getLeftMargin();
1615 boost::uint16_t originalrightmargin = getRightMargin();
1616 std::vector<int> originaltabstops = getTabStops();
1617 attloc = attributes.find("BLOCKINDENT");
1618 if (attloc != attributes.end()) {
1619 //textformat BLOCKINDENT attribute
1620 setBlockIndent(pixelsToTwips(std::strtol(
1621 attloc->second.data(), NULL, 10)));
1622 if (newrec.xOffset() == std::max(0, originalleftmargin +
1623 originalindent + originalblockindent) + PADDING_TWIPS) {
1624 //if beginning of line, indent
1625 x = std::max(0, getLeftMargin() +
1626 getIndent() + getBlockIndent())
1627 + PADDING_TWIPS;
1628 newrec.setXOffset(x);
1631 attloc = attributes.find("INDENT");
1632 if (attloc != attributes.end()) {
1633 //textformat INDENT attribute
1634 setIndent(pixelsToTwips(std::strtol(
1635 attloc->second.data(), NULL, 10)));
1636 if (newrec.xOffset() == std::max(0, originalleftmargin +
1637 originalindent + getBlockIndent()) + PADDING_TWIPS) {
1638 //if beginning of line, indent
1639 x = std::max(0, getLeftMargin() +
1640 getIndent() + getBlockIndent())
1641 + PADDING_TWIPS;
1642 newrec.setXOffset(x);
1645 attloc = attributes.find("LEADING");
1646 if (attloc != attributes.end()) {
1647 //textformat LEADING attribute
1648 setLeading(pixelsToTwips(std::strtol(
1649 attloc->second.data(), NULL, 10)));
1651 attloc = attributes.find("LEFTMARGIN");
1652 if (attloc != attributes.end()) {
1653 //textformat LEFTMARGIN attribute
1654 setLeftMargin(pixelsToTwips(std::strtol(
1655 attloc->second.data(), NULL, 10)));
1656 if (newrec.xOffset() == std::max(0, originalleftmargin +
1657 getIndent() + getBlockIndent()) + PADDING_TWIPS) {
1658 //if beginning of line, indent
1659 x = std::max(0, getLeftMargin() +
1660 getIndent() + getBlockIndent())
1661 + PADDING_TWIPS;
1662 newrec.setXOffset(x);
1665 attloc = attributes.find("RIGHTMARGIN");
1666 if (attloc != attributes.end()) {
1667 //textformat RIGHTMARGIN attribute
1668 setRightMargin(pixelsToTwips(std::strtol(
1669 attloc->second.data(), NULL, 10)));
1670 //FIXME:Should not apply this to this line if we are not at
1671 //beginning of line. Not sure how to do that.
1673 attloc = attributes.find("TABSTOPS");
1674 if (attloc != attributes.end()) {
1675 //textformat TABSTOPS attribute
1676 log_unimpl("html <textformat> tag tabstops attribute");
1678 handleChar(it, e, x, y, newrec, last_code,
1679 last_space_glyph, last_line_start_record);
1680 setBlockIndent(originalblockindent);
1681 setIndent(originalindent);
1682 setLeading(originalleading);
1683 setLeftMargin(originalleftmargin);
1684 setRightMargin(originalrightmargin);
1685 setTabStops(originaltabstops);
1687 else if (s == "P") {
1688 //paragraph
1689 if (_display == TEXTFORMAT_BLOCK) {
1690 handleChar(it, e, x, y, newrec, last_code,
1691 last_space_glyph,
1692 last_line_start_record);
1693 newLine(x, y, rec, last_space_glyph,
1694 last_line_start_record, 1.0);
1695 newLine(x, y, rec, last_space_glyph,
1696 last_line_start_record, 1.5);
1698 else {
1699 handleChar(it, e, x, y, newrec, last_code,
1700 last_space_glyph,
1701 last_line_start_record);
1704 else if (s == "BR" || s == "SBR") {
1705 //line break
1706 newLine(x, y, rec, last_space_glyph,
1707 last_line_start_record, 1.0);
1709 else {
1710 log_debug("<%s> tag is unsupported", s);
1711 if (!selfclosing) { //then recurse, look for closing tag
1712 handleChar(it, e, x, y, newrec, last_code,
1713 last_space_glyph, last_line_start_record);
1717 rec.setXOffset(x);
1718 rec.setYOffset(y);
1719 continue;
1721 // If HTML isn't enabled, carry on and insert the glyph.
1722 // FIXME: do we also want to be changing last_space_glyph?
1723 // ...because we are...
1724 case 32:
1725 last_space_glyph = rec.glyphs().size();
1726 // Don't break, as we still need to insert the space glyph.
1728 default:
1733 if ( password() )
1735 SWF::TextRecord::GlyphEntry ge;
1736 int bullet = rec.getFont()->get_glyph_index(42, _embedFonts);
1737 ge.index = bullet;
1738 ge.advance = scale * rec.getFont()->get_advance(bullet,
1739 _embedFonts);
1740 rec.addGlyph(ge);
1741 ++_glyphcount;
1742 break;
1744 // The font table holds up to 65535 glyphs. Casting
1745 // from uint32_t would, in the event that the code
1746 // is higher than 65535, result in the wrong DisplayObject
1747 // being chosen. Flash can currently only handle 16-bit
1748 // values.
1749 int index = rec.getFont()->get_glyph_index(
1750 static_cast<boost::uint16_t>(code), _embedFonts);
1752 IF_VERBOSE_MALFORMED_SWF (
1753 if (index == -1)
1755 // Missing glyph! Log the first few errors.
1756 static int s_log_count = 0;
1757 if (s_log_count < 10)
1759 s_log_count++;
1760 if (_embedFonts)
1762 log_swferror(_("TextField: missing embedded "
1763 "glyph for char %d. Make sure DisplayObject "
1764 "shapes for font %s are being exported "
1765 "into your SWF file"),
1766 code, _font->name());
1768 else
1770 log_swferror(_("TextField: missing device "
1771 "glyph for char %d. Maybe you don't have "
1772 "font '%s' installed in your system."),
1773 code, _font->name());
1777 // Drop through and use index == -1; this will display
1778 // using the empty-box glyph
1782 SWF::TextRecord::GlyphEntry ge;
1783 ge.index = index;
1784 ge.advance = scale * rec.getFont()->get_advance(index,
1785 _embedFonts);
1787 rec.addGlyph(ge);
1789 x += ge.advance;
1790 ++_glyphcount;
1794 float width = _bounds.width();
1795 if (x >= width - getRightMargin() - PADDING_TWIPS)
1797 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1798 log_debug("Text in TextField %s exceeds width [ _bounds %s ]",
1799 getTarget(), _bounds);
1800 #endif
1802 // No wrap and no resize: truncate
1803 if (!doWordWrap() && getAutoSize() == AUTOSIZE_NONE)
1805 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1806 log_debug(" wordWrap=false, autoSize=none");
1807 #endif
1808 // Truncate long line, but keep expanding text box
1809 bool newlinefound = false;
1810 while (it != e)
1812 code = *it++;
1813 if (_embedFonts)
1815 x += rec.getFont()->get_kerning_adjustment(last_code,
1816 static_cast<int>(code)) * scale;
1817 last_code = code;
1819 // Expand the bounding-box to the lower-right corner
1820 // of each glyph, even if we don't display it
1821 m_text_bounding_box.expand_to_point(x, y + fontDescent);
1822 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1823 log_debug("Text bbox expanded to %s (width: %f)",
1824 m_text_bounding_box, m_text_bounding_box.width());
1825 #endif
1827 if (code == 13 || code == 10)
1829 newlinefound = true;
1830 break;
1833 int index = rec.getFont()->get_glyph_index(
1834 static_cast<boost::uint16_t>(code), _embedFonts);
1835 x += scale * rec.getFont()->get_advance(index, _embedFonts);
1838 if (!newlinefound) break;
1840 else if (doWordWrap()) {
1842 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1843 log_debug(" wordWrap=true");
1844 #endif
1846 // Insert newline if there's space or autosize != none
1848 // Close out this stretch of glyphs.
1849 _textRecords.push_back(rec);
1851 float previous_x = x;
1852 x = std::max(0, getLeftMargin() + getBlockIndent()) + PADDING_TWIPS;
1853 y += _fontHeight + leading;
1854 if (y >= _bounds.height()) {
1855 ++_maxScroll;
1858 // Start a new record on the next line.
1859 rec.clearGlyphs();
1860 rec.setXOffset(x);
1861 rec.setYOffset(y);
1863 // TODO : what if m_text_glyph_records is empty ?
1864 // Is it possible ?
1865 assert(!_textRecords.empty());
1866 SWF::TextRecord& last_line = _textRecords.back();
1868 linestartit = _line_starts.begin();
1869 linestartend = _line_starts.end();
1870 if (last_space_glyph == -1)
1872 // Pull the previous glyph down onto the
1873 // new line.
1874 if (!last_line.glyphs().empty())
1876 rec.addGlyph(last_line.glyphs().back());
1877 x += last_line.glyphs().back().advance;
1878 previous_x -= last_line.glyphs().back().advance;
1879 last_line.clearGlyphs(1);
1880 //record the new line start
1882 const size_t currentPos = _glyphcount;
1883 while (linestartit != linestartend &&
1884 *linestartit + 1 <= currentPos)
1886 linestartit++;
1888 _line_starts.insert(linestartit, currentPos);
1889 _recordStarts.push_back(currentPos);
1891 } else {
1892 // Move the previous word down onto the next line.
1894 previous_x -= last_line.glyphs()[last_space_glyph].advance;
1896 const SWF::TextRecord::Glyphs::size_type lineSize =
1897 last_line.glyphs().size();
1898 for (unsigned int i = last_space_glyph + 1; i < lineSize;
1899 ++i)
1901 rec.addGlyph(last_line.glyphs()[i]);
1902 x += last_line.glyphs()[i].advance;
1903 previous_x -= last_line.glyphs()[i].advance;
1905 last_line.clearGlyphs(lineSize - last_space_glyph);
1907 // record the position at the start of this line as
1908 // a line_start
1909 const size_t linestartpos = _glyphcount -
1910 rec.glyphs().size();
1912 while (linestartit < linestartend &&
1913 *linestartit < linestartpos)
1915 ++linestartit;
1917 _line_starts.insert(linestartit, linestartpos);
1918 _recordStarts.push_back(linestartpos);
1921 align_line(getTextAlignment(), last_line_start_record, previous_x);
1923 last_space_glyph = -1;
1924 last_line_start_record = _textRecords.size();
1927 else
1929 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1930 log_debug(" wordWrap=%d, autoSize=%d", _wordWrap, _autoSize);
1931 #endif
1938 TextField::getDefinitionVersion() const
1940 // TODO: work out if this correct.
1941 return get_root()->getDefinitionVersion();
1945 TextField::VariableRef
1946 TextField::parseTextVariableRef(const std::string& variableName) const
1948 VariableRef ret;
1949 ret.first = 0;
1951 #ifdef DEBUG_DYNTEXT_VARIABLES
1952 log_debug(_("VariableName: %s"), variableName);
1953 #endif
1955 /// Why isn't get_environment const again ?
1956 as_environment& env = const_cast<TextField*>(this)->get_environment();
1958 as_object* target = getObject(env.get_target());
1959 if ( ! target )
1961 IF_VERBOSE_MALFORMED_SWF(
1962 log_swferror(_("Current environment has no target, "
1963 "can't bind VariableName (%s) associated to "
1964 "text field. Gnash will try to register "
1965 "again on next access."), variableName);
1967 return ret;
1970 // If the variable string contains a path, we extract
1971 // the appropriate target from it and update the variable
1972 // name. We copy the string so we can assign to it if necessary.
1973 std::string parsedName = variableName;
1974 std::string path, var;
1975 if (parsePath(variableName, path, var))
1977 #ifdef DEBUG_DYNTEXT_VARIABLES
1978 log_debug(_("Variable text Path: %s, Var: %s"), path, var);
1979 #endif
1980 // find target for the path component
1981 // we use our parent's environment for this
1982 target = env.find_object(path);
1984 parsedName = var;
1987 if ( ! target )
1989 IF_VERBOSE_MALFORMED_SWF(
1990 log_swferror(_("VariableName associated to text field refers "
1991 "to an unknown target (%s). It is possible that the "
1992 "DisplayObject will be instantiated later in the SWF "
1993 "stream. Gnash will try to register again on next "
1994 "access."), path);
1996 return ret;
1999 ret.first = target;
2000 ret.second = getStringTable(*object()).find(parsedName);
2002 return ret;
2005 void
2006 TextField::registerTextVariable()
2008 //#define DEBUG_DYNTEXT_VARIABLES 1
2010 #ifdef DEBUG_DYNTEXT_VARIABLES
2011 log_debug(_("registerTextVariable() called"));
2012 #endif
2014 if (_text_variable_registered) {
2015 #ifdef DEBUG_DYNTEXT_VARIABLES
2016 log_debug(_("registerTextVariable() no-op call (already registered)"));
2017 #endif
2018 return;
2021 if (_variable_name.empty()) {
2022 #ifdef DEBUG_DYNTEXT_VARIABLES
2023 log_debug(_("string is empty, consider as registered"));
2024 #endif
2025 _text_variable_registered=true;
2026 return;
2029 VariableRef varRef = parseTextVariableRef(_variable_name);
2030 as_object* target = varRef.first;
2031 if (!target) {
2032 log_debug(_("VariableName associated to text field (%s) refer to "
2033 "an unknown target. It is possible that the DisplayObject "
2034 "will be instantiated later in the SWF stream. "
2035 "Gnash will try to register again on next access."),
2036 _variable_name);
2037 return;
2040 const string_table::key key = varRef.second;
2041 as_object* obj = getObject(this);
2042 const int version = getSWFVersion(*obj);
2043 string_table& st = getStringTable(*obj);
2045 // check if the VariableName already has a value,
2046 // in that case update text value
2047 as_value val;
2048 if (target->get_member(key, &val) ) {
2049 #ifdef DEBUG_DYNTEXT_VARIABLES
2050 log_debug(_("target object (%s @ %p) does have a member named %s"),
2051 typeName(*target), (void*)target, st.value(key));
2052 #endif
2053 // TODO: pass environment to to_string ?
2054 // as_environment& env = get_environment();
2055 setTextValue(utf8::decodeCanonicalString(val.to_string(), version));
2057 else if (_textDefined) {
2058 as_value newVal = as_value(utf8::encodeCanonicalString(_text, version));
2059 #ifdef DEBUG_DYNTEXT_VARIABLES
2060 log_debug(_("target sprite (%s @ %p) does NOT have a member "
2061 "named %s (no problem, we'll add it with value %s)"),
2062 typeName(*target), (void*)target,
2063 st.value(key), newVal);
2064 #endif
2065 target->set_member(key, newVal);
2067 else {
2068 #ifdef DEBUG_DYNTEXT_VARIABLES
2069 log_debug(_("target sprite (%s @ %p) does NOT have a member "
2070 "named %s, and we don't have text defined"),
2071 typeName(*target), (void*)target, st.value(key));
2072 #endif
2075 MovieClip* sprite = get<MovieClip>(target);
2077 if (sprite) {
2078 // add the textfield variable to the target sprite
2079 // TODO: have set_textfield_variable take a string_table::key instead ?
2080 #ifdef DEBUG_DYNTEXT_VARIABLES
2081 log_debug("Calling set_textfield_variable(%s) against sprite %s",
2082 st.value(key), sprite->getTarget());
2083 #endif
2084 sprite->set_textfield_variable(st.value(key), this);
2087 _text_variable_registered=true;
2091 /// Parses an HTML tag (between < and >) and puts
2092 /// the contents into tag. Returns false if the
2093 /// tag was incomplete. The iterator is moved to after
2094 /// the closing tag or the end of the string.
2095 bool
2096 TextField::parseHTML(std::wstring& tag,
2097 std::map<std::string, std::string>& attributes,
2098 std::wstring::const_iterator& it,
2099 const std::wstring::const_iterator& e,
2100 bool& selfclosing) const
2102 while (it != e && *it != ' ') {
2103 if (*it == '/') {
2104 ++it;
2105 if (*it == '>') {
2106 ++it;
2107 selfclosing = true;
2108 return true;
2109 } else {
2110 while (it != e) {
2111 ++it;
2113 log_error("invalid html tag");
2114 return false;
2117 if (*it == '>') {
2118 ++it;
2119 return true;
2122 // Check for NULL character
2123 if (*it == 0) {
2124 log_error("found NULL character in htmlText");
2125 return false;
2127 tag.push_back(std::toupper(*it));
2128 ++it;
2130 while (it != e && *it == ' ') {
2131 ++it; //skip over spaces
2133 if (*it == '>') {
2134 ++it;
2135 return true;
2137 if (*it == '/') {
2138 ++it;
2139 if (*it == '>') {
2140 ++it;
2141 selfclosing = true;
2142 return true;
2143 } else {
2144 while (it != e) {
2145 ++it;
2147 log_error("invalid html tag");
2148 return false;
2152 std::string attname;
2153 std::string attvalue;
2155 //attributes
2156 while (it != e && *it != '>') {
2157 while (it != e && *it != '=' && *it != ' ') {
2159 if (*it == 0) {
2160 log_error("found NULL character in htmlText");
2161 return false;
2163 if (*it == '>') {
2164 log_error("malformed HTML tag, invalid attribute name");
2165 while (it != e) {
2166 ++it;
2168 return false;
2171 attname.push_back(std::toupper(*it));
2172 ++it;
2174 while (it != e && (*it == ' ' || *it == '=')) {
2175 ++it; //skip over spaces and '='
2178 if (it == e) return false;
2179 const char q = *it;
2180 if (q != '"' && q != '\'') {
2181 // This is not an attribute.
2182 while (it != e) ++it;
2183 return false;
2186 // Advance past attribute opener
2187 ++it;
2188 while (it != e && *it != q) {
2190 if (*it == 0) {
2191 log_error("found NULL character in htmlText");
2192 return false;
2195 attvalue.push_back(std::toupper(*it));
2196 ++it;
2199 if (it == e) return false;
2201 if (*it != q) {
2202 while (it != e) ++it;
2203 return false;
2206 // Skip attribute closer.
2207 ++it;
2209 attributes.insert(std::make_pair(attname, attvalue));
2210 attname.clear();
2211 attvalue.clear();
2213 if ((*it != ' ') && (*it != '/') && (*it != '>')) {
2214 log_error("malformed HTML tag, invalid attribute value");
2215 while (it != e) {
2216 ++it;
2218 return false;
2220 if (*it == ' ') {
2221 while (it != e && *it == ' ') {
2222 ++it; //skip over spaces
2225 if (*it == '>') {
2226 ++it;
2227 return true;
2228 } else if (*it == '/') {
2229 ++it;
2230 if (*it == '>') {
2231 ++it;
2232 selfclosing = true;
2233 return true;
2234 } else {
2235 while (it != e) {
2236 ++it;
2238 log_error("invalid html tag");
2239 return false;
2244 #ifdef GNASH_DEBUG_TEXTFIELDS
2245 log_debug ("HTML tag: %s", utf8::encodeCanonicalString(tag, 7));
2246 #endif
2247 log_error("I declare this a HTML syntax error");
2248 return false; //since we did not return already, must be malformed...?
2251 void
2252 TextField::set_variable_name(const std::string& newname)
2254 if ( newname != _variable_name )
2256 _variable_name = newname;
2258 // The name was empty or undefined, so there's nothing more to do.
2259 if (_variable_name.empty()) return;
2261 _text_variable_registered = false;
2263 #ifdef DEBUG_DYNTEXT_VARIABLES
2264 log_debug("Calling updateText after change of variable name");
2265 #endif
2267 // Use the original definition text if this isn't dynamically
2268 // created.
2269 if (_tag) updateText(_tag->defaultText());
2271 #ifdef DEBUG_DYNTEXT_VARIABLES
2272 log_debug("Calling registerTextVariable after change of variable "
2273 "name and updateText call");
2274 #endif
2275 registerTextVariable();
2279 bool
2280 TextField::pointInShape(boost::int32_t x, boost::int32_t y) const
2282 const SWFMatrix wm = getWorldMatrix(*this).invert();
2283 point lp(x, y);
2284 wm.transform(lp);
2285 return _bounds.point_test(lp.x, lp.y);
2288 bool
2289 TextField::getDrawBorder() const
2291 return _drawBorder;
2294 void
2295 TextField::setDrawBorder(bool val)
2297 if ( _drawBorder != val )
2299 set_invalidated();
2300 _drawBorder = val;
2304 rgba
2305 TextField::getBorderColor() const
2307 return _borderColor;
2310 void
2311 TextField::setBorderColor(const rgba& col)
2313 if ( _borderColor != col )
2315 set_invalidated();
2316 _borderColor = col;
2320 bool
2321 TextField::getDrawBackground() const
2323 return _drawBackground;
2326 void
2327 TextField::setDrawBackground(bool val)
2329 if ( _drawBackground != val )
2331 set_invalidated();
2332 _drawBackground = val;
2336 rgba
2337 TextField::getBackgroundColor() const
2339 return _backgroundColor;
2342 void
2343 TextField::setBackgroundColor(const rgba& col)
2345 if ( _backgroundColor != col )
2347 set_invalidated();
2348 _backgroundColor = col;
2352 void
2353 TextField::setTextColor(const rgba& col)
2355 if (_textColor != col) {
2357 set_invalidated();
2358 _textColor = col;
2359 std::for_each(_displayRecords.begin(), _displayRecords.end(),
2360 boost::bind(&SWF::TextRecord::setColor, _1, _textColor));
2364 void
2365 TextField::setEmbedFonts(bool use)
2367 if ( _embedFonts != use )
2369 set_invalidated();
2370 _embedFonts=use;
2371 format_text();
2375 void
2376 TextField::setWordWrap(bool wrap)
2378 if (_wordWrap != wrap) {
2380 set_invalidated();
2381 _wordWrap = wrap;
2382 format_text();
2386 void
2387 TextField::setLeading(boost::int16_t h)
2389 if ( _leading != h )
2391 set_invalidated();
2392 _leading = h;
2396 void
2397 TextField::setUnderlined(bool v)
2399 if ( _underlined != v )
2401 set_invalidated();
2402 _underlined = v;
2406 void
2407 TextField::setBullet(bool b)
2409 if (_bullet != b) {
2410 _bullet = b;
2414 void
2415 TextField::setTabStops(const std::vector<int>& tabStops)
2417 _tabStops.resize(tabStops.size());
2419 for (size_t i = 0; i < tabStops.size(); i ++) {
2420 _tabStops[i] = pixelsToTwips(tabStops[i]);
2423 set_invalidated();
2426 void
2427 TextField::setURL(std::string url)
2429 if ( _url != url ) {
2430 set_invalidated();
2431 _url = url;
2435 void
2436 TextField::setTarget(std::string target)
2438 if ( _target != target)
2440 set_invalidated();
2441 _target = target;
2445 void
2446 TextField::setDisplay(TextFormatDisplay display)
2448 if ( _display != display )
2450 set_invalidated();
2451 _display = display;
2455 void
2456 TextField::setAlignment(TextAlignment h)
2458 if ( _alignment != h )
2460 set_invalidated();
2461 _alignment = h;
2465 void
2466 TextField::setIndent(boost::uint16_t h)
2468 if ( _indent != h )
2470 set_invalidated();
2471 _indent = h;
2475 void
2476 TextField::setBlockIndent(boost::uint16_t h)
2478 if ( _blockIndent != h )
2480 set_invalidated();
2481 _blockIndent = h;
2485 void
2486 TextField::setRightMargin(boost::uint16_t h)
2488 if ( _rightMargin != h )
2490 set_invalidated();
2491 _rightMargin = h;
2495 void
2496 TextField::setLeftMargin(boost::uint16_t h)
2498 if (_leftMargin != h)
2500 set_invalidated();
2501 _leftMargin = h;
2505 void
2506 TextField::setFontHeight(boost::uint16_t h)
2508 if ( _fontHeight != h )
2510 set_invalidated();
2511 _fontHeight = h;
2516 TextField::TypeValue
2517 TextField::parseTypeValue(const std::string& val)
2519 StringNoCaseEqual cmp;
2521 if (cmp(val, "input")) return typeInput;
2522 if (cmp(val, "dynamic")) return typeDynamic;
2523 return typeInvalid;
2528 const char*
2529 TextField::typeValueName(TypeValue val)
2531 switch (val)
2533 case typeInput:
2534 //log_debug("typeInput returned as 'input'");
2535 return "input";
2536 case typeDynamic:
2537 //log_debug("typeDynamic returned as 'dynamic'");
2538 return "dynamic";
2539 default:
2540 //log_debug("invalid type %d returned as 'invalid'", (int)val);
2541 return "invalid";
2546 void
2547 TextField::setAutoSize(AutoSize val)
2549 if ( val == _autoSize ) return;
2551 set_invalidated();
2553 _autoSize = val;
2554 format_text();
2557 TextField::TextAlignment
2558 TextField::getTextAlignment()
2560 TextAlignment textAlignment = getAlignment();
2562 switch (_autoSize) {
2563 case AUTOSIZE_CENTER:
2564 textAlignment = ALIGN_CENTER;
2565 break;
2566 case AUTOSIZE_LEFT:
2567 textAlignment = ALIGN_LEFT;
2568 break;
2569 case AUTOSIZE_RIGHT:
2570 textAlignment = ALIGN_RIGHT;
2571 break;
2572 default:
2573 // Leave it as it was.
2574 break;
2577 return textAlignment;
2580 void
2581 TextField::onChanged()
2583 as_object* obj = getObject(this);
2584 callMethod(obj, NSV::PROP_BROADCAST_MESSAGE, "onChanged", obj);
2587 /// This is called by movie_root when focus is applied to this TextField.
2589 /// The return value is true if the TextField can receive focus.
2590 /// The swfdec testsuite suggests that version 5 textfields cannot ever
2591 /// handle focus.
2592 bool
2593 TextField::handleFocus()
2596 set_invalidated();
2598 /// Select the entire text on focus.
2599 setSelection(0, _text.length());
2601 m_has_focus = true;
2603 m_cursor = _text.size();
2604 format_text();
2605 return true;
2608 /// This is called by movie_root when focus is removed from the
2609 /// current TextField.
2610 void
2611 TextField::killFocus()
2613 if ( ! m_has_focus ) return; // nothing to do
2615 set_invalidated();
2616 m_has_focus = false;
2618 format_text(); // is this needed ?
2622 void
2623 TextField::setWidth(double newwidth)
2625 const SWFRect& bounds = getBounds();
2626 _bounds.set_to_rect(bounds.get_x_min(),
2627 bounds.get_y_min(),
2628 bounds.get_x_min() + newwidth,
2629 bounds.get_y_max());
2632 void
2633 TextField::setHeight(double newheight)
2635 const SWFRect& bounds = getBounds();
2636 _bounds.set_to_rect(bounds.get_x_min(),
2637 bounds.get_y_min(),
2638 bounds.get_x_max(),
2639 bounds.get_y_min() + newheight);
2642 /// TextField interface functions
2645 } // namespace gnash
2648 // Local Variables:
2649 // mode: C++
2650 // indent-tabs-mode: t
2651 // End: