1 // TextField.cpp:  User-editable text regions, for Gnash.
2 //
3 //   Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012
4 //   Free Software 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.
15 //
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
19 //
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
25 
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
30 
31 #include "TextField.h"
32 
33 #include <algorithm>
34 #include <string>
35 #include <cstdlib>
36 #include <cctype>
37 #include <utility>
38 #include <map>
39 #include <functional>
40 
41 #include "utf8.h"
42 #include "log.h"
43 #include "swf/DefineEditTextTag.h"
44 #include "MovieClip.h"
45 #include "movie_root.h"     // for killing focus
46 #include "as_environment.h"
47 #include "Font.h"
48 #include "fontlib.h"
49 #include "namedStrings.h"
50 #include "StringPredicates.h"
51 #include "TextFormat_as.h"
52 #include "GnashKey.h"
53 #include "TextRecord.h"
54 #include "Point2d.h"
55 #include "GnashNumeric.h"
56 #include "MouseButtonState.h"
57 #include "Global_as.h"
58 #include "Renderer.h"
59 #include "Transform.h"
60 #include "ObjectURI.h"
61 #include "Movie.h"
62 
63 // Text fields have a fixed 2 pixel padding for each side (regardless of border)
64 #define PADDING_TWIPS 40
65 
66 // Define the following to get detailed log information about
67 // textfield bounds and HTML tags:
68 //#define GNASH_DEBUG_TEXTFIELDS 1
69 
70 // Define this to get debugging info about text formatting
71 //#define GNASH_DEBUG_TEXT_FORMATTING 1
72 
73 
74 namespace gnash {
75 
TextField(as_object * object,DisplayObject * parent,const SWF::DefineEditTextTag & def)76 TextField::TextField(as_object* object, DisplayObject* parent,
77         const SWF::DefineEditTextTag& def)
78     :
79     InteractiveObject(object, parent),
80     _tag(&def),
81     _url(""),
82     _target(""),
83     _display(),
84     _tabStops(),
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()),
90     _font(nullptr),
91     m_cursor(0u),
92     _glyphcount(0u),
93     _scroll(0u),
94     _maxScroll(1u),
95     _hScroll(0u),
96     _maxHScroll(0u),
97     _bottomScroll(0u),
98     _linesindisplay(0u),
99     _maxChars(def.maxChars()),
100     _autoSize(def.autoSize() ? AUTOSIZE_LEFT : AUTOSIZE_NONE),
101     _type(def.readOnly() ? typeDynamic : typeInput),
102     _bounds(def.bounds()),
103     _selection(0, 0),
104     _leading(def.leading()),
105     _indent(def.indent()),
106     _blockIndent(0),
107     _leftMargin(def.leftMargin()),
108     _rightMargin(def.rightMargin()),
109     _fontHeight(def.textHeight()),
110     _textDefined(def.hasText()),
111     _restrictDefined(false),
112     _underlined(false),
113     _bullet(false),
114     m_has_focus(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()),
122     _html(def.html()),
123     _selectable(!def.noSelect())
124 {
125     assert(object);
126 
127     // WARNING! remember to set the font *before* setting text value!
128     boost::intrusive_ptr<const Font> f = def.getFont();
129     if (!f) f = fontlib::get_default_font();
130     setFont(f);
131 
132     const int version = getSWFVersion(*object);
133 
134     // set default text *before* calling registerTextVariable
135     // (if the textvariable already exist and has a value
136     // the text will be replaced with it)
137     if (_textDefined) {
138         setTextValue(utf8::decodeCanonicalString(def.defaultText(), version));
139     }
140 
141     init();
142 
143 }
144 
TextField(as_object * object,DisplayObject * parent,SWFRect bounds)145 TextField::TextField(as_object* object, DisplayObject* parent,
146         SWFRect bounds)
147     :
148     InteractiveObject(object, parent),
149     _url(""),
150     _target(""),
151     _display(),
152     _tabStops(),
153     _backgroundColor(255,255,255,255),
154     _borderColor(0, 0, 0, 255),
155     _textColor(0, 0, 0, 255),
156     _alignment(ALIGN_LEFT),
157     _font(nullptr),
158     m_cursor(0u),
159     _glyphcount(0u),
160     _scroll(0u),
161     _maxScroll(1u),
162     _hScroll(0u),
163     _maxHScroll(0u),
164     _bottomScroll(0u),
165     _linesindisplay(0u),
166     _maxChars(0),
167     _autoSize(AUTOSIZE_NONE),
168     _type(typeDynamic),
169     _bounds(std::move(bounds)),
170     _selection(0, 0),
171     _leading(0),
172     _indent(0),
173     _blockIndent(0),
174     _leftMargin(0),
175     _rightMargin(0),
176     _fontHeight(12 * 20),
177     _textDefined(false),
178     _restrictDefined(false),
179     _underlined(false),
180     _bullet(false),
181     m_has_focus(false),
182     _multiline(false),
183     _password(false),
184     _text_variable_registered(false),
185     _drawBackground(false),
186     _drawBorder(false),
187     _embedFonts(false),
188     _wordWrap(false),
189     _html(false),
190     _selectable(true)
191 {
192     assert(object);
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();
196     setFont(f);
197 
198     init();
199 }
200 
201 void
init()202 TextField::init()
203 {
204     registerTextVariable();
205 
206     reset_bounding_box(0, 0);
207 }
208 
209 
~TextField()210 TextField::~TextField()
211 {
212 }
213 
214 void
removeTextField()215 TextField::removeTextField()
216 {
217     int depth = get_depth();
218     if ( depth < 0 || depth > 1048575 )
219     {
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",
223             getTarget(), depth);
224         //);
225         return;
226     }
227 
228     DisplayObject* p = parent();
229     assert(p); // every TextField must have a parent, right ?
230 
231     MovieClip* parentSprite = p->to_movie();
232 
233     if (!parentSprite) {
234         log_error(_("FIXME: attempt to remove a TextField being a child of a %s"),
235                 typeName(*p));
236         return;
237     }
238 
239     // second argument is arbitrary, see comments above
240     // the function declaration in MovieClip.h
241     parentSprite->remove_display_object(depth, 0);
242 }
243 
244 void
show_cursor(Renderer & renderer,const SWFMatrix & mat)245 TextField::show_cursor(Renderer& renderer, const SWFMatrix& mat)
246 {
247     if (_textRecords.empty()) {
248         return;
249     }
250     std::uint16_t x;
251     std::uint16_t y;
252     std::uint16_t h;
253     size_t i = cursorRecord();
254     SWF::TextRecord record = _textRecords[i];
255 
256     x = record.xOffset();
257     y = record.yOffset() - record.textHeight() + getLeading();
258     h = record.textHeight();
259 
260 	if (!record.glyphs().empty()) {
261         for (unsigned int p = 0 ; p < (m_cursor - _recordStarts[i]); ++p) {
262             x += record.glyphs()[p].advance;
263         }
264     }
265 
266     const std::vector<point> line = {
267         point(x, y),
268         point(x, y + h)};
269 
270     renderer.drawLine(line, rgba(0, 0, 0, 255), mat);
271 }
272 
273 size_t
cursorRecord()274 TextField::cursorRecord()
275 {
276     if (_textRecords.empty()) return 0;
277 
278     size_t i = 0;
279 
280     while (i < _textRecords.size() && m_cursor >= _recordStarts[i]) {
281         ++i;
282     }
283     // TODO: it seems like this could return (size_t) -1, but there's no
284     // evidence this is allowed or handled.
285     return i - 1;
286 }
287 
288 void
display(Renderer & renderer,const Transform & base)289 TextField::display(Renderer& renderer, const Transform& base)
290 {
291     const DisplayObject::MaskRenderer mr(renderer, *this);
292 
293     registerTextVariable();
294 
295     const bool drawBorder = getDrawBorder();
296     const bool drawBackground = getDrawBackground();
297 
298     Transform xform = base * transform();
299 
300     // This is a hack to handle device fonts, which are not affected by
301     // color transform.
302     if (!getEmbedFonts()) xform.colorTransform = SWFCxForm();
303 
304     if ((drawBorder || drawBackground) && !_bounds.is_null()) {
305 
306         std::int32_t xmin = _bounds.get_x_min();
307         std::int32_t xmax = _bounds.get_x_max();
308         std::int32_t ymin = _bounds.get_y_min();
309         std::int32_t ymax = _bounds.get_y_max();
310 
311         const std::vector<point> coords = {
312             point(xmin, ymin),
313             point(xmax, ymin),
314             point(xmax, ymax),
315             point(xmin, ymax)};
316 
317         rgba borderColor = drawBorder ? getBorderColor() : rgba(0,0,0,0);
318         rgba backgroundColor = drawBackground ? getBackgroundColor() :
319                                                 rgba(0,0,0,0);
320 
321         SWFCxForm cx = xform.colorTransform;
322 
323         if (drawBorder) borderColor = cx.transform(borderColor);
324 
325         if (drawBackground) backgroundColor = cx.transform(backgroundColor);
326 
327 #ifdef GNASH_DEBUG_TEXTFIELDS
328 	log_debug("rendering a Pol composed by corners %s", _bounds);
329 #endif
330 
331         renderer.draw_poly(coords, backgroundColor,
332                 borderColor, xform.matrix, true);
333 
334     }
335 
336     // Draw our actual text.
337     // Using a SWFMatrix to translate to def bounds seems an hack to me.
338     // A cleaner implementation is likely correctly setting the
339     // _xOffset and _yOffset memebers in glyph records.
340     // Anyway, see bug #17954 for a testcase.
341     if (!_bounds.is_null()) {
342         xform.matrix.concatenate_translation(_bounds.get_x_min(),
343                 _bounds.get_y_min());
344     }
345 
346     _displayRecords.clear();
347     // TODO: work out how leading should be implemented.
348     const float fontLeading = 0;
349 
350     //offset the lines
351     int yoffset = (getFontHeight() + fontLeading) + PADDING_TWIPS;
352     size_t recordline;
353     for (size_t i = 0; i < _textRecords.size(); ++i) {
354         recordline = 0;
355         //find the line the record is on
356         while (recordline < _line_starts.size() &&
357                 _line_starts[recordline] <= _recordStarts[i]) {
358             ++recordline;
359         }
360         //offset the line
361         _textRecords[i].setYOffset((recordline-_scroll)*yoffset);
362         //add the lines we want to the display record
363         if (_textRecords[i].yOffset() > 0 &&
364             _textRecords[i].yOffset() < _bounds.height()) {
365             _displayRecords.push_back(_textRecords[i]);
366         }
367     }
368 
369     SWF::TextRecord::displayRecords(renderer, xform, _displayRecords,
370             _embedFonts);
371 
372     if (m_has_focus && !isReadOnly()) show_cursor(renderer, xform.matrix);
373 
374     clear_invalidated();
375 }
376 
377 
378 void
add_invalidated_bounds(InvalidatedRanges & ranges,bool force)379 TextField::add_invalidated_bounds(InvalidatedRanges& ranges, bool force)
380 {
381     if (!force && !invalidated()) return; // no need to redraw
382 
383     ranges.add(m_old_invalidated_ranges);
384 
385     const SWFMatrix& wm = getWorldMatrix(*this);
386 
387     SWFRect bounds = getBounds();
388     bounds.expand_to_rect(m_text_bounding_box);
389     wm.transform(bounds);
390     ranges.add(bounds.getRange());
391 }
392 
393 void
setRestrict(const std::string & restrict)394 TextField::setRestrict(const std::string& restrict)
395 {
396     _restrictDefined = true;
397 
398     std::string::const_iterator rit = restrict.begin();
399     std::string::const_iterator re = restrict.end();
400     std::set<wchar_t>::const_iterator locate;
401 
402     if (*rit == '^') { //then this is a true RESTRICT pattern, add all chars to _restrictedchars
403         for (unsigned int i = 0; i <= 255; ++i) {
404             _restrictedchars.insert(char(i));
405         }
406     } else { //then this is an ALLOW pattern, _restrictedchars should remain empty
407         _restrictedchars.clear();
408     }
409 
410     while (rit != re) {
411         while (rit != re && *rit != '^') { //This loop allows chars
412             if (*rit == '-') {
413                 log_error(_("invalid restrict string"));
414                 return;
415             } else if (*(rit+1) == '-') {
416                 if (re - (rit+2) != 0) {
417                     unsigned int q = *(rit+2);
418                     for (unsigned int p = *rit; p <= q; (++p)){
419                         _restrictedchars.insert(char(p));
420                     }
421                     rit += 3;
422                 } else {
423                     log_error(_("invalid restrict string"));
424                     return;
425                 }
426             } else if (*rit == '\\') {
427                 ++rit;
428                 _restrictedchars.insert(*rit);
429                 ++rit;
430             } else {
431                 _restrictedchars.insert(*rit);
432                 ++rit;
433             }
434         }
435         if (rit != re) {
436             ++rit;
437         }
438         while (rit != re && *rit != '^') { //This loop restricts chars
439             locate = _restrictedchars.find(*rit);
440             if (*rit == '-') {
441                 log_error(_("invalid restrict string"));
442                 return;
443             } else if (*(rit+1) == '-') {
444                 if (re - (rit+2) != 0) {
445                     unsigned int q = *(rit+2);
446                     for (unsigned int p = *rit; p <= q; ++p){
447                         locate = _restrictedchars.find(p);
448                         if(locate != _restrictedchars.end()) {
449                             _restrictedchars.erase(locate);
450                         }
451                     }
452                     ++rit;
453                     ++rit;
454                     ++rit;
455                 } else {
456                     log_error(_("invalid restrict string"));
457                     return;
458                 }
459             } else if (*rit == '\\') {
460                 ++rit;
461                 locate = _restrictedchars.find(*rit);
462                 if(locate != _restrictedchars.end()) {
463                     _restrictedchars.erase(locate);
464                 }
465                 ++rit;
466             } else {
467                 if(locate != _restrictedchars.end()) {
468                     _restrictedchars.erase(locate);
469                 }
470                 ++rit;
471             }
472         }
473         if (rit != re) {
474             ++rit;
475         }
476     }
477     _restrict = restrict;
478 }
479 
480 void
replaceSelection(const std::string & replace)481 TextField::replaceSelection(const std::string& replace)
482 {
483     const int version = getSWFVersion(*getObject(this));
484     const std::wstring& wstr = utf8::decodeCanonicalString(replace, version);
485 
486     assert(_selection.second >= _selection.first);
487     assert(_selection.second <= _text.size());
488     assert(_selection.first <= _text.size());
489 
490     // If the text has changed but the selection hasn't, make sure we
491     // don't access it out of bounds.
492     const size_t start = _selection.first;
493     const size_t end = _selection.second;
494 
495     const size_t replaceLength = wstr.size();
496 
497     _text.replace(start, end - start, wstr);
498     _selection = std::make_pair(start + replaceLength, start + replaceLength);
499 }
500 
501 void
setSelection(int start,int end)502 TextField::setSelection(int start, int end)
503 {
504     if (_text.empty()) {
505         _selection = std::make_pair(0, 0);
506         return;
507     }
508 
509     const size_t textLength = _text.size();
510 
511     if (start < 0) start = 0;
512     else start = std::min<size_t>(start, textLength);
513 
514     if (end < 0) end = 0;
515     else end = std::min<size_t>(end, textLength);
516 
517     // The cursor position is always set to the end value, even if the
518     // two values are swapped to obtain the selection. Equal values are
519     // fine.
520     m_cursor = end;
521     if (start > end) std::swap(start, end);
522 
523     _selection = std::make_pair(start, end);
524 }
525 
526 void
keyInput(key::code c)527 TextField::keyInput(key::code c)
528 {
529     // c is the unique gnash::key::code for a DisplayObject/key.
530     // The maximum value is about 265, including function keys.
531     // It seems that typing in DisplayObjects outside the Latin-1 set
532     // (256 DisplayObject codes, identical to the first 256 of UTF-8)
533     // is not supported, though a much greater number UTF-8 codes can be
534     // stored and displayed. See utf.h for more information.
535     // This is a limit on the number of key codes, not on the
536     // capacity of strings.
537 
538 
539     setHtml(false); //editable html fields are not yet implemented
540     std::wstring s = _text;
541 
542     // maybe _text is changed in ActionScript
543     m_cursor = std::min<size_t>(m_cursor, _text.size());
544 
545     size_t cur_cursor = m_cursor;
546     size_t previouslinesize = 0;
547     size_t nextlinesize = 0;
548     size_t manylines = _line_starts.size();
549     LineStarts::iterator linestartit = _line_starts.begin();
550     LineStarts::const_iterator linestartend = _line_starts.end();
551 
552     switch (c) {
553         case key::BACKSPACE:
554             if (isReadOnly()) return;
555             if (m_cursor > 0)
556             {
557                 s.erase(m_cursor - 1, 1);
558                 m_cursor--;
559                 setTextValue(s);
560             }
561             break;
562 
563         case key::DELETEKEY:
564             if (isReadOnly()) return;
565             if (_glyphcount > m_cursor)
566             {
567                 s.erase(m_cursor, 1);
568                 setTextValue(s);
569             }
570             break;
571 
572         case key::INSERT:        // TODO
573             if (isReadOnly()) return;
574             break;
575 
576         case key::HOME:
577             while ( linestartit < linestartend && *linestartit <= m_cursor ) {
578                 cur_cursor = *linestartit;
579                 ++linestartit;
580             }
581             m_cursor = cur_cursor;
582             break;
583 
584         case key::PGUP:
585             // if going a page up is too far...
586             if(_scroll < _linesindisplay) {
587                 _scroll = 0;
588                 m_cursor = 0;
589             } else { // go a page up
590                 _scroll -= _linesindisplay;
591                 m_cursor = _line_starts[_scroll];
592             }
593             scrollLines();
594             break;
595 
596         case key::UP:
597             while ( linestartit < linestartend && *linestartit <= m_cursor ) {
598                 cur_cursor = *linestartit;
599                 ++linestartit;
600             }
601             //if there is no previous line
602             if ( linestartit-_line_starts.begin() - 2 < 0 ) {
603                 m_cursor = 0;
604                 break;
605             }
606             previouslinesize = _textRecords[linestartit-_line_starts.begin() - 2].glyphs().size();
607             //if the previous line is smaller
608             if (m_cursor - cur_cursor > previouslinesize) {
609                 m_cursor = *(--(--linestartit)) + previouslinesize;
610             } else {
611                 m_cursor = *(--(--linestartit)) + (m_cursor - cur_cursor);
612             }
613             if (m_cursor < _line_starts[_scroll] && _line_starts[_scroll] != 0) {
614                 --_scroll;
615             }
616             scrollLines();
617             break;
618 
619         case key::END:
620             while ( linestartit < linestartend && *linestartit <= m_cursor ) {
621                 ++linestartit;
622             }
623             m_cursor = linestartit != linestartend ? *linestartit - 1 : _text.size();
624             break;
625 
626         case key::PGDN:
627             //if going another page down is too far...
628             if(_scroll + _linesindisplay >= manylines) {
629                 if(manylines - _linesindisplay <= 0) {
630                     _scroll = 0;
631                 } else {
632                     _scroll = manylines - _linesindisplay;
633                 }
634                 if(m_cursor < _line_starts[_scroll-1]) {
635                     m_cursor = _line_starts[_scroll-1];
636                 } else {
637                     m_cursor = _text.size();
638                 }
639             } else { //go a page down
640                 _scroll += _linesindisplay;
641                 m_cursor = _line_starts[_scroll];
642             }
643             scrollLines();
644             break;
645 
646         case key::DOWN:
647         {
648             while (linestartit < linestartend &&
649                     *linestartit <= m_cursor ) {
650                 cur_cursor = *linestartit;
651                 ++linestartit;
652             }
653 
654             // linestartit should never be before _line_starts.begin()
655             const size_t currentLine = linestartit -
656                 _line_starts.begin();
657 
658             //if there is no next line
659             if (currentLine >= manylines ) {
660                 m_cursor = _text.size();
661                 break;
662             }
663             nextlinesize = _textRecords[currentLine].glyphs().size();
664 
665             //if the next line is smaller
666             if (m_cursor - cur_cursor > nextlinesize) {
667                 m_cursor = *linestartit + nextlinesize;
668             } else {
669                 //put the cursor at the same character distance
670                 m_cursor = *(linestartit) + (m_cursor - cur_cursor);
671             }
672             if (_line_starts.size() > _linesindisplay &&
673                 m_cursor >= _line_starts[_scroll+_linesindisplay]) {
674                 ++_scroll;
675             }
676             scrollLines();
677             break;
678         }
679 
680         case key::LEFT:
681             m_cursor = m_cursor > 0 ? m_cursor - 1 : 0;
682             break;
683 
684         case key::RIGHT:
685             m_cursor = m_cursor < _glyphcount ? m_cursor + 1 :
686                                                 _glyphcount;
687             break;
688 
689         case key::ENTER:
690             if (isReadOnly()) return;
691             if (!multiline()) break;
692 
693         default:
694 
695             if (maxChars() != 0) {
696                 if (_maxChars <= _glyphcount) {
697                     break;
698                 }
699             }
700 
701             if (isReadOnly()) return;
702             wchar_t t = static_cast<wchar_t>(
703                     gnash::key::codeMap[c][key::ASCII]);
704             if (t != 0) {
705 
706                 if (!_restrictDefined) {
707                     // Insert one copy of the character
708                     // at the cursor position.
709                     s.insert(m_cursor, 1, t);
710                     m_cursor++;
711                 } else if (_restrictedchars.count(t)) {
712                     // Insert one copy of the character
713                     // at the cursor position.
714                     s.insert(m_cursor, 1, t);
715                     m_cursor++;
716                 } else if (_restrictedchars.count(tolower(t))) {
717                     // restrict substitutes the opposite case
718                     s.insert(m_cursor, 1, tolower(t));
719                     m_cursor++;
720                 } else if (_restrictedchars.count(toupper(t))) {
721                     // restrict substitutes the opposite case
722                     s.insert(m_cursor, 1, toupper(t));
723                     m_cursor++;
724                 }
725             }
726             setTextValue(s);
727     }
728     onChanged();
729     set_invalidated();
730 }
731 
732 void
mouseEvent(const event_id & ev)733 TextField::mouseEvent(const event_id& ev)
734 {
735     switch (ev.id())
736     {
737 		case event_id::PRESS:
738 		{
739 			movie_root& root = stage();
740             std::int32_t x_mouse, y_mouse;
741             boost::tie(x_mouse, y_mouse) = root.mousePosition();
742 
743 			SWFMatrix m = getMatrix(*this);
744 
745 			x_mouse -= m.get_x_translation();
746 			y_mouse -= m.get_y_translation();
747 
748 			SWF::TextRecord rec;
749 
750 			for (auto& record: _textRecords) {
751 				if 	((x_mouse >  record.xOffset()) &&
752 					(x_mouse < record.xOffset()+record.recordWidth()) &&
753 					(y_mouse > record.yOffset()-record.textHeight()) &&
754 					(y_mouse < record.yOffset())) {
755 						rec = record;
756 						break;
757 					}
758 			}
759 
760 			if (!rec.getURL().empty()) {
761 				   root.getURL(rec.getURL(), rec.getTarget(), "",
762 								MovieClip::METHOD_NONE);
763 			}
764 
765 			break;
766 		}
767         default:
768             return;
769     };
770 }
771 
772 InteractiveObject*
topmostMouseEntity(std::int32_t x,std::int32_t y)773 TextField::topmostMouseEntity(std::int32_t x, std::int32_t y)
774 {
775     if (!visible()) return nullptr;
776 
777     // Not selectable, so don't catch mouse events!
778     if (!_selectable) return nullptr;
779 
780     SWFMatrix m = getMatrix(*this);
781     point p(x, y);
782     m.invert().transform(p);
783 
784     if (_bounds.point_test(p.x, p.y)) return this;
785 
786     return nullptr;
787 }
788 
789 void
updateText(const std::string & str)790 TextField::updateText(const std::string& str)
791 {
792     const int version = getSWFVersion(*getObject(this));
793     const std::wstring& wstr = utf8::decodeCanonicalString(str, version);
794     updateText(wstr);
795 }
796 
797 void
updateText(const std::wstring & wstr)798 TextField::updateText(const std::wstring& wstr)
799 {
800     _textDefined = true;
801     if (_text == wstr) return;
802 
803     set_invalidated();
804 
805     _text = wstr;
806 
807     _selection.first = std::min(_selection.first, _text.size());
808     _selection.second = std::min(_selection.second, _text.size());
809 
810     format_text();
811 }
812 
813 void
updateHtmlText(const std::wstring & wstr)814 TextField::updateHtmlText(const std::wstring& wstr)
815 {
816     if (_htmlText == wstr) return;
817 
818     set_invalidated();
819 
820     _htmlText = wstr;
821     format_text();
822 }
823 
824 void
setTextValue(const std::wstring & wstr)825 TextField::setTextValue(const std::wstring& wstr)
826 {
827     updateHtmlText(wstr);
828     updateText(wstr);
829 
830     if (!_variable_name.empty() && _text_variable_registered) {
831         // TODO: notify MovieClip if we have a variable name !
832         VariableRef ref = parseTextVariableRef(_variable_name);
833         as_object* tgt = ref.first;
834         if (tgt) {
835             const int version = getSWFVersion(*getObject(this));
836             // we shouldn't truncate, right?
837             tgt->set_member(ref.second, utf8::encodeCanonicalString(wstr,
838                         version));
839         }
840         else {
841             // nothing to do (too early ?)
842             log_debug("setTextValue: variable name %s points to a non-existent"
843 		      "target, I guess we would not be registered if this was"
844 		      "true, or the sprite we've registered our variable name"
845 		      "has been unloaded", _variable_name);
846         }
847     }
848 }
849 
850 std::string
get_text_value() const851 TextField::get_text_value() const
852 {
853     // we need the const_cast here because registerTextVariable
854     // *might* change our text value, calling the non-const
855     // setTextValue().
856     // This happens if the TextVariable has not been already registered
857     // and during registration comes out to name an existing variable
858     // with a pre-existing value.
859     const_cast<TextField*>(this)->registerTextVariable();
860 
861     const int version = getSWFVersion(*getObject(this));
862 
863     return utf8::encodeCanonicalString(_text, version);
864 }
865 
866 std::string
get_htmltext_value() const867 TextField::get_htmltext_value() const
868 {
869     const_cast<TextField*>(this)->registerTextVariable();
870     const int version = getSWFVersion(*getObject(this));
871     return utf8::encodeCanonicalString(_htmlText, version);
872 }
873 
874 void
setTextFormat(TextFormat_as & tf)875 TextField::setTextFormat(TextFormat_as& tf)
876 {
877     //TODO: this is lazy. we should set all the TextFormat variables HERE, i think
878     //This is just so we can set individual variables without having to call format_text()
879     //This calls format_text() at the end of setting TextFormat
880     if (tf.align()) setAlignment(*tf.align());
881     if (tf.size()) setFontHeight(*tf.size()); // keep twips
882     if (tf.indent()) setIndent(*tf.indent());
883     if (tf.blockIndent()) setBlockIndent(*tf.blockIndent());
884     if (tf.leading()) setLeading(*tf.leading());
885     if (tf.leftMargin()) setLeftMargin(*tf.leftMargin());
886     if (tf.rightMargin()) setRightMargin(*tf.rightMargin());
887     if (tf.color()) setTextColor(*tf.color());
888     if (tf.underlined()) setUnderlined(*tf.underlined());
889     if (tf.bullet()) setBullet(*tf.bullet());
890     setDisplay(tf.display());
891 	if (tf.tabStops()) setTabStops(*tf.tabStops());
892 
893 	// NEED TO IMPLEMENT THESE TWO
894 	if (tf.url()) setURL(*tf.url());
895 	if (tf.target()) setTarget(*tf.target());
896 
897     format_text();
898 }
899 
900 float
align_line(TextAlignment align,int last_line_start_record,float x)901 TextField::align_line(TextAlignment align, int last_line_start_record, float x)
902 {
903     float width = _bounds.width();
904     float right_margin = getRightMargin();
905 
906     float extra_space = (width - right_margin) - x - PADDING_TWIPS;
907 
908     if (extra_space <= 0.0f) {
909 #ifdef GNASH_DEBUG_TEXTFIELDS
910         log_debug("TextField text doesn't fit in its boundaries: "
911                 "width %g, margin %g - nothing to align",
912                 width, right_margin);
913 #endif
914         return 0.0f;
915     }
916 
917     float shift_right = 0.0f;
918 
919     switch (align) {
920         case ALIGN_LEFT:
921             // Nothing to do; already aligned left.
922             return 0.0f;
923         case ALIGN_CENTER:
924             // Distribute the space evenly on both sides.
925             shift_right = extra_space / 2;
926             break;
927         case ALIGN_RIGHT:
928             // Shift all the way to the right.
929             shift_right = extra_space;
930             break;
931         case ALIGN_JUSTIFY:
932             // What should we do here?
933             break;
934     }
935 
936     // Shift the beginnings of the records on this line.
937     for (size_t i = last_line_start_record; i < _textRecords.size(); ++i) {
938         SWF::TextRecord& rec = _textRecords[i];
939         rec.setXOffset(rec.xOffset() + shift_right);
940     }
941     return shift_right;
942 }
943 
944 boost::intrusive_ptr<const Font>
setFont(boost::intrusive_ptr<const Font> newfont)945 TextField::setFont(boost::intrusive_ptr<const Font> newfont)
946 {
947     if (newfont == _font) return _font;
948 
949     boost::intrusive_ptr<const Font> oldfont = _font;
950     set_invalidated();
951     _font = newfont;
952     format_text();
953     return oldfont;
954 }
955 
956 
957 void
insertTab(SWF::TextRecord & rec,std::int32_t & x,float scale)958 TextField::insertTab(SWF::TextRecord& rec, std::int32_t& x, float scale)
959 {
960      // tab (ASCII HT)
961     const int space = 32;
962     int index = rec.getFont()->get_glyph_index(space, _embedFonts);
963     if (index == -1) {
964         IF_VERBOSE_MALFORMED_SWF (
965           log_error(_("TextField: missing glyph for space char (needed "
966                   "for TAB). Make sure DisplayObject shapes for font "
967                   "%s are being exported into your SWF file."),
968                 rec.getFont()->name());
969         );
970     }
971     else {
972         // TODO: why is there a copy of the vector?
973 		std::vector<int> tabStops = _tabStops;
974 
975         std::sort(_tabStops.begin(), _tabStops.end());
976 
977         if (!_tabStops.empty()) {
978             int tab = _tabStops.back() + 1;
979 
980             for (auto& tabStop : tabStops) {
981                 if (tabStop > x) {
982                     if((tabStop - x) < tab) {
983                         tab = tabStop - x;
984                     }
985 				}
986 
987             }
988 
989 			// This is necessary in case the number of tabs in the text
990 			// are more than the actual number of tabStops inside the
991 			// vector
992 			if (tab != _tabStops.back() + 1) {
993 				SWF::TextRecord::GlyphEntry ge;
994 				ge.index = rec.getFont()->get_glyph_index(32, _embedFonts);
995 				ge.advance = tab;
996 				rec.addGlyph(ge);
997 				x+=ge.advance;
998 			}
999         }
1000         else {
1001             SWF::TextRecord::GlyphEntry ge;
1002             ge.index = index;
1003             ge.advance = scale * rec.getFont()->get_advance(index,
1004                     _embedFonts);
1005 
1006             const int tabstop = 4;
1007             rec.addGlyph(ge, tabstop);
1008             x += ge.advance * tabstop;
1009         }
1010     }
1011 }
1012 
1013 void
format_text()1014 TextField::format_text()
1015 {
1016     _textRecords.clear();
1017     _line_starts.clear();
1018     _recordStarts.clear();
1019     _glyphcount = 0;
1020 
1021     _recordStarts.push_back(0);
1022 
1023     // nothing more to do if text is empty
1024     if (_text.empty()) {
1025         // TODO: should we still reset _bounds if autoSize != AUTOSIZE_NONE ?
1026         //       not sure we should...
1027         reset_bounding_box(0, 0);
1028         return;
1029     }
1030 
1031     AutoSize autoSize = getAutoSize();
1032     if (autoSize != AUTOSIZE_NONE) {
1033         // When doing WordWrap we don't want to change
1034         // the boundaries. See bug #24348
1035         if (!doWordWrap()) {
1036             _bounds.set_to_rect(0, 0, 0, 0); // this is correct for 'true'
1037         }
1038     }
1039 
1040     // FIXME: I don't think we should query the definition
1041     // to find the appropriate font to use, as ActionScript
1042     // code should be able to change the font of a TextField
1043     if (!_font) {
1044         log_error(_("No font for TextField!"));
1045         return;
1046     }
1047 
1048     std::uint16_t fontHeight = getFontHeight();
1049     const float scale = fontHeight /
1050         static_cast<float>(_font->unitsPerEM(_embedFonts));
1051 
1052     // TODO: work out how leading affects things.
1053     const float fontLeading = 0;
1054 
1055     const std::uint16_t leftMargin = getLeftMargin();
1056     const std::uint16_t indent = getIndent();
1057     const std::uint16_t blockIndent = getBlockIndent();
1058     const bool underlined = getUnderlined();
1059 
1060     /// Remember the current bounds for autosize.
1061     SWFRect oldBounds(_bounds);
1062 
1063     SWF::TextRecord rec;    // one to work on
1064     rec.setFont(_font.get());
1065     rec.setUnderline(underlined);
1066     rec.setColor(getTextColor());
1067     rec.setXOffset(PADDING_TWIPS +
1068             std::max(0, leftMargin + indent + blockIndent));
1069     rec.setYOffset(PADDING_TWIPS + fontHeight + fontLeading);
1070     rec.setTextHeight(fontHeight);
1071 
1072 	// create in textrecord.h
1073 	rec.setURL(_url);
1074 	rec.setTarget(_target);
1075 
1076     // BULLET CASE:
1077 
1078     // First, we indent 10 spaces, and then place the bullet
1079     // character (in this case, an asterisk), then we pad it
1080     // again with 10 spaces
1081     // Note: this works only for additional lines of a
1082     // bulleted list, so that is why there is a bullet format
1083     // in the beginning of format_text()
1084     if (_bullet) {
1085         int space = rec.getFont()->get_glyph_index(32, _embedFonts);
1086 
1087         SWF::TextRecord::GlyphEntry ge;
1088         ge.index = space;
1089         ge.advance = scale * rec.getFont()->get_advance(space, _embedFonts);
1090         rec.addGlyph(ge, 5);
1091 
1092         // We use an asterisk instead of a bullet
1093         int bullet = rec.getFont()->get_glyph_index(42, _embedFonts);
1094         ge.index = bullet;
1095         ge.advance = scale * rec.getFont()->get_advance(bullet, _embedFonts);
1096         rec.addGlyph(ge);
1097 
1098         space = rec.getFont()->get_glyph_index(32, _embedFonts);
1099         ge.index = space;
1100         ge.advance = scale * rec.getFont()->get_advance(space, _embedFonts);
1101         rec.addGlyph(ge, 4);
1102     }
1103 
1104     std::int32_t x = static_cast<std::int32_t>(rec.xOffset());
1105     std::int32_t y = static_cast<std::int32_t>(rec.yOffset());
1106 
1107     // Start the bbox at the upper-left corner of the first glyph.
1108     //reset_bounding_box(x, y + fontHeight);
1109 
1110     int last_code = -1; // only used if _embedFonts
1111     int last_space_glyph = -1;
1112     size_t last_line_start_record = 0;
1113 
1114     _line_starts.push_back(0);
1115 
1116     // String iterators are very sensitive to
1117     // potential changes to the string (to allow for copy-on-write).
1118     // So there must be no external changes to the string or
1119     // calls to most non-const member functions during this loop.
1120     // Especially not c_str() or data().
1121     std::wstring::const_iterator it = _text.begin();
1122     const std::wstring::const_iterator e = _text.end();
1123 
1124     ///handleChar takes care of placing the glyphs
1125     handleChar(it, e, x, y, rec, last_code, last_space_glyph,
1126             last_line_start_record);
1127 
1128     // Expand bounding box to include the whole text (if autoSize and wordWrap
1129     // is not in operation.
1130     if (_autoSize != AUTOSIZE_NONE && !doWordWrap())
1131     {
1132         _bounds.expand_to_point(x + PADDING_TWIPS, y + PADDING_TWIPS);
1133 
1134         if (_autoSize == AUTOSIZE_RIGHT) {
1135             /// Autosize right expands from the previous right margin.
1136             SWFMatrix m;
1137 
1138             m.set_x_translation(oldBounds.get_x_max() - _bounds.width());
1139             m.transform(_bounds);
1140         }
1141         else if (_autoSize == AUTOSIZE_CENTER) {
1142             // Autosize center expands from the previous center.
1143             SWFMatrix m;
1144             m.set_x_translation(oldBounds.get_x_min() + oldBounds.width() / 2.0 -
1145                     _bounds.width() / 2.0);
1146             m.transform(_bounds);
1147         }
1148     }
1149 
1150     // Add the last line to our output.
1151     _textRecords.push_back(rec);
1152 
1153     // align the last (or single) line
1154     align_line(getTextAlignment(), last_line_start_record, x);
1155 
1156     scrollLines();
1157 
1158     set_invalidated(); //redraw
1159 
1160 }
1161 
1162 void
scrollLines()1163 TextField::scrollLines()
1164 {
1165     std::uint16_t fontHeight = getFontHeight();
1166     const float fontLeading = 0;
1167 
1168     _linesindisplay = _bounds.height() / (fontHeight + fontLeading + PADDING_TWIPS);
1169     if (_linesindisplay > 0) { //no need to place lines if we can't fit any
1170         size_t manylines = _line_starts.size();
1171         size_t lastvisibleline = _scroll + _linesindisplay;
1172         size_t line = 0;
1173 
1174         // If there aren't as many lines as we have scrolled, display the
1175         // end of the text.
1176         if (manylines < _scroll) {
1177             _scroll = manylines - _linesindisplay;
1178             return;
1179         }
1180 
1181         // which line is the cursor on?
1182         while (line < manylines && _line_starts[line] <= m_cursor) {
1183             ++line;
1184         }
1185 
1186         if (manylines - _scroll <= _linesindisplay) {
1187             // This is for if we delete a line
1188             if (manylines < _linesindisplay) _scroll = 0;
1189             else {
1190                 _scroll = manylines - _linesindisplay;
1191             }
1192         } else if (line < _scroll) {
1193             //if we are at a higher position, scroll the lines down
1194             _scroll -= _scroll - line;
1195         } else if (manylines > _scroll + _linesindisplay) {
1196             //if we are at a lower position, scroll the lines up
1197             if (line >= (_scroll+_linesindisplay)) {
1198                 _scroll += line - (lastvisibleline);
1199             }
1200         }
1201     }
1202 }
1203 
1204 void
newLine(std::int32_t & x,std::int32_t & y,SWF::TextRecord & rec,int & last_space_glyph,LineStarts::value_type & last_line_start_record,float div)1205 TextField::newLine(std::int32_t& x, std::int32_t& y,
1206 				   SWF::TextRecord& rec, int& last_space_glyph,
1207 				LineStarts::value_type& last_line_start_record, float div)
1208 {
1209     // newline.
1210     LineStarts::iterator linestartit = _line_starts.begin();
1211     LineStarts::const_iterator linestartend = _line_starts.end();
1212 
1213     // TODO: work out how leading affects things.
1214     const float leading = 0;
1215 
1216     // Close out this stretch of glyphs.
1217     ++_glyphcount;
1218     _textRecords.push_back(rec);
1219     _recordStarts.push_back(_glyphcount);
1220     align_line(getTextAlignment(), last_line_start_record, x);
1221 
1222     // Expand bounding box to include last column of text ...
1223     if (!doWordWrap() && _autoSize != AUTOSIZE_NONE) {
1224         _bounds.expand_to_point(x + PADDING_TWIPS, y + PADDING_TWIPS);
1225     }
1226 
1227     // new paragraphs get the indent.
1228     x = std::max(0, getLeftMargin() + getIndent() + getBlockIndent()) +
1229         PADDING_TWIPS;
1230     y += div * (getFontHeight() + leading);
1231     if (y >= _bounds.height()) {
1232         ++_maxScroll;
1233     }
1234 
1235     // Start a new record on the next line. Other properties of the
1236     // TextRecord should be left unchanged.
1237     rec.clearGlyphs();
1238     rec.setXOffset(x);
1239     rec.setYOffset(y);
1240 
1241     last_space_glyph = -1;
1242     last_line_start_record = _textRecords.size();
1243 
1244     linestartit = _line_starts.begin();
1245     linestartend = _line_starts.end();
1246     //Fit a line_start in the correct place
1247     const size_t currentPos = _glyphcount;
1248 
1249     while (linestartit < linestartend && *linestartit < currentPos)
1250     {
1251         ++linestartit;
1252     }
1253     _line_starts.insert(linestartit, currentPos);
1254 
1255     // BULLET CASE:
1256 
1257     // First, we indent 10 spaces, and then place the bullet
1258     // character (in this case, an asterisk), then we pad it
1259     // again with 10 spaces
1260     // Note: this works only for additional lines of a
1261     // bulleted list, so that is why there is a bullet format
1262     // in the beginning of format_text()
1263     if (_bullet)
1264     {
1265         int space = rec.getFont()->get_glyph_index(32, _embedFonts);
1266         SWF::TextRecord::GlyphEntry ge;
1267         ge.index = space;
1268 
1269         const float scale = getFontHeight() /
1270             static_cast<float>(_font->unitsPerEM(_embedFonts));
1271 
1272         ge.advance = scale * rec.getFont()->get_advance(space, _embedFonts);
1273 
1274         rec.addGlyph(ge,5);
1275         _glyphcount += 5;
1276 
1277         int bullet = rec.getFont()->get_glyph_index(42, _embedFonts);
1278         ge.index = bullet;
1279         ge.advance = scale * rec.getFont()->get_advance(bullet, _embedFonts);
1280         rec.addGlyph(ge);
1281         ++_glyphcount;
1282 
1283         ge.index = space;
1284         ge.advance = scale * rec.getFont()->get_advance(space, _embedFonts);
1285 
1286         rec.addGlyph(ge,4);
1287         _glyphcount += 4;
1288     }
1289 }
1290 
1291 void
handleChar(std::wstring::const_iterator & it,const std::wstring::const_iterator & e,std::int32_t & x,std::int32_t & y,SWF::TextRecord & rec,int & last_code,int & last_space_glyph,LineStarts::value_type & last_line_start_record)1292 TextField::handleChar(std::wstring::const_iterator& it,
1293         const std::wstring::const_iterator& e, std::int32_t& x,
1294         std::int32_t& y, SWF::TextRecord& rec, int& last_code,
1295         int& last_space_glyph, LineStarts::value_type& last_line_start_record)
1296 {
1297     LineStarts::iterator linestartit = _line_starts.begin();
1298     LineStarts::const_iterator linestartend = _line_starts.end();
1299 
1300     float scale = _fontHeight /
1301         static_cast<float>(_font->unitsPerEM(_embedFonts));
1302     float fontDescent = _font->descent(_embedFonts) * scale;
1303 
1304     // TODO: work out how leading should be implemented.
1305     const float leading = 0;
1306     const float fontLeading = 0;
1307 
1308     std::uint32_t code = 0;
1309     while (it != e)
1310     {
1311         code = *it++;
1312         if (!code) break;
1313 
1314         if ( _embedFonts )
1315         {
1316             x += rec.getFont()->get_kerning_adjustment(last_code,
1317                     static_cast<int>(code)) * scale;
1318             last_code = static_cast<int>(code);
1319         }
1320 
1321         // Expand the bounding-box to the lower-right corner of each glyph as
1322         // we generate it.
1323         m_text_bounding_box.expand_to_point(x, y + fontDescent);
1324         switch (code)
1325         {
1326             case 27:
1327                 // Ignore escape
1328                 break;
1329             case 9:
1330                 insertTab(rec, x, scale);
1331                 break;
1332             case 8:
1333                 // Backspace
1334 
1335                 // This is a limited hack to enable overstrike effects.
1336                 // It backs the cursor up by one DisplayObject and then continues
1337                 // the layout.  E.g. you can use this to display an underline
1338                 // cursor inside a simulated text-entry box.
1339                 //
1340                 // ActionScript understands the '\b' escape sequence
1341                 // for inserting a BS DisplayObject.
1342                 //
1343                 // ONLY WORKS FOR BACKSPACING OVER ONE CHARACTER, WON'T BS
1344                 // OVER NEWLINES, ETC.
1345 
1346                 if (!rec.glyphs().empty())
1347                 {
1348                     // Peek at the previous glyph, and zero out its advance
1349                     // value, so the next char overwrites it.
1350                     float advance = rec.glyphs().back().advance;
1351                     x -= advance;
1352                     // Remove one glyph
1353                     rec.clearGlyphs(1);
1354                 }
1355                 continue;
1356             case 13:
1357             case 10:
1358             {
1359                 newLine(x,y,rec,last_space_glyph,last_line_start_record,1.0);
1360                 break;
1361             }
1362             case '<':
1363                 if (doHtml())
1364                 {
1365                     //close out this stretch of glyphs
1366                     _textRecords.push_back(rec);
1367                     rec.clearGlyphs();
1368                     _recordStarts.push_back(_glyphcount);
1369                     if (*it == '/') {
1370                         while (it != e && *it != '>') {
1371                             ++it;
1372                         }
1373                         ++it;
1374                         return;
1375                     }
1376                     LOG_ONCE(log_debug("HTML in a text field is unsupported, "
1377                                          "gnash will just ignore the tags and "
1378                                          "print their content"));
1379 
1380                     std::wstring discard;
1381                     std::map<std::string,std::string> attributes;
1382                     SWF::TextRecord newrec;
1383                     newrec.setFont(rec.getFont());
1384                     newrec.setUnderline(rec.underline());
1385                     newrec.setColor(rec.color());
1386                     newrec.setTextHeight(rec.textHeight());
1387                     newrec.setXOffset(x);
1388                     newrec.setYOffset(y);
1389                     bool selfclosing = false;
1390                     bool complete = parseHTML(discard, attributes, it, e, selfclosing);
1391                     std::string s(discard.begin(), discard.end());
1392 
1393                     std::map<std::string,std::string>::const_iterator attloc;
1394 
1395                     if (!complete) {
1396                         //parsing went wrong
1397                         continue;
1398                     } else {
1399                         // Don't think this is the best way to match with
1400                         // tags...
1401                         // TODO: assumes tags are properly nested. This isn't
1402                         // correct.
1403                         if (s == "U") {
1404                             //underline
1405                             newrec.setUnderline(true);
1406                             handleChar(it, e, x, y, newrec, last_code,
1407                                     last_space_glyph, last_line_start_record);
1408                         }
1409                         else if (s == "A") {
1410                             // anchor (blue text).
1411 							rgba color(0, 0, 0xff, 0xff);
1412 							newrec.setColor(color);
1413 							newrec.setUnderline(true);
1414 							attloc = attributes.find("HREF");
1415 							if (attloc != attributes.end()) {
1416 								newrec.setURL(attloc->second);
1417 							}
1418 							attloc = attributes.find("TARGET");
1419 							if (attloc !=attributes.end()) {
1420 								newrec.setTarget(attloc->second);
1421 							}
1422                             handleChar(it, e, x, y, newrec, last_code,
1423                                     last_space_glyph, last_line_start_record);
1424                         }
1425                         else if (s == "B") {
1426                             //bold
1427                             Font* boldfont =
1428                                 fontlib::get_font(rec.getFont()->name(),
1429                                     true, rec.getFont()->isItalic());
1430                             newrec.setFont(boldfont);
1431                             handleChar(it, e, x, y, newrec, last_code,
1432                                     last_space_glyph, last_line_start_record);
1433                         }
1434                         else if (s == "FONT") {
1435                             //font
1436                             std::uint16_t originalsize = _fontHeight;
1437                             attloc = attributes.find("COLOR");
1438                             if (attloc != attributes.end()) {
1439                                 std::string hexval(attloc->second);
1440                                 if (hexval.empty() || hexval[0] != '#') {
1441                                     // FIXME: should this be a log_aserror
1442                                     //        or log_unimpl ? It is triggered
1443                                     //        by TextFieldHTML.as
1444                                     log_error(_("Unexpected value '%s' in TextField font color attribute"),
1445                                         hexval);
1446                                 }
1447                                 else {
1448                                     hexval.erase(0, 1);
1449                                     // font COLOR attribute
1450                                     const rgba color =
1451                                         colorFromHexString(hexval);
1452                                     newrec.setColor(color);
1453                                 }
1454                             }
1455                             attloc = attributes.find("FACE");
1456                             if (attloc != attributes.end()) {
1457                                 if (attloc->second.empty()) {
1458                                     IF_VERBOSE_ASCODING_ERRORS(
1459                                          log_aserror(_("Expected a font name in FACE attribute."))
1460                                     );
1461                                 } else {
1462                                     //font FACE attribute
1463                                     Font* newfont =
1464                                         fontlib::get_font(attloc->second,
1465                                         rec.getFont()->isBold(),
1466                                         rec.getFont()->isItalic());
1467                                     newrec.setFont(newfont);
1468                                 }
1469                             }
1470                             attloc = attributes.find("SIZE");
1471                             if (attloc != attributes.end()) {
1472                                 //font SIZE attribute
1473                                 std::string firstchar = attloc->second.substr(0,1);
1474                                 if (firstchar == "+") {
1475                                     newrec.setTextHeight(rec.textHeight() +
1476 
1477 										(pixelsToTwips(std::strtol(
1478                                         attloc->second.substr(1,attloc->second.length()-1).data(),
1479                                         nullptr,10))));
1480                                     newrec.setYOffset(PADDING_TWIPS +
1481                                         newrec.textHeight() +
1482                                         (fontLeading - fontDescent));
1483                                     _fontHeight += pixelsToTwips(std::strtol(
1484                                         attloc->second.substr(1,attloc->second.length()-1).data(),
1485                                         nullptr,10));
1486                                 } else if (firstchar == "-") {
1487                                     newrec.setTextHeight(rec.textHeight() -
1488                                         (pixelsToTwips(std::strtol(
1489                                         attloc->second.substr(1,attloc->second.length()-1).data(),
1490                                         nullptr,10))));
1491                                     newrec.setYOffset(PADDING_TWIPS +
1492                                         newrec.textHeight() +
1493                                         (fontLeading - fontDescent));
1494                                     _fontHeight -= pixelsToTwips(std::strtol(
1495                                         attloc->second.substr(1,attloc->second.length()-1).data(),
1496                                         nullptr,10));
1497                                 } else {
1498                                     newrec.setTextHeight(pixelsToTwips(std::strtol(
1499                                         attloc->second.data(), nullptr, 10)));
1500                                     newrec.setYOffset(PADDING_TWIPS + newrec.textHeight() +
1501                                         (fontLeading - fontDescent));
1502                                     _fontHeight = pixelsToTwips(std::strtol(
1503                                         attloc->second.data(), nullptr, 10));
1504                                 }
1505                             }
1506                             handleChar(it, e, x, y, newrec, last_code,
1507                                     last_space_glyph, last_line_start_record);
1508                             _fontHeight = originalsize;
1509                             y = newrec.yOffset();
1510                         }
1511                         else if (s == "IMG") {
1512                             //image
1513                             log_unimpl(_("<img> HTML tag in TextField"));
1514                             handleChar(it, e, x, y, newrec, last_code,
1515                                     last_space_glyph, last_line_start_record);
1516                         }
1517                         else if (s == "I") {
1518                             //italic
1519                             Font* italicfont =
1520                                 fontlib::get_font(rec.getFont()->name(),
1521                                     rec.getFont()->isBold(), true);
1522                             newrec.setFont(italicfont);
1523                             handleChar(it, e, x, y, newrec, last_code,
1524                                     last_space_glyph, last_line_start_record);
1525                         } else if (s == "LI") {
1526                             //list item (bullet)
1527 							int space = newrec.getFont()->get_glyph_index(32, _embedFonts);
1528 							SWF::TextRecord::GlyphEntry ge;
1529 							ge.index = space;
1530 							ge.advance = scale * newrec.getFont()->get_advance(space, _embedFonts);
1531 							newrec.addGlyph(ge, 5);
1532 
1533 							// We use an asterisk instead of a bullet
1534 							int bullet = newrec.getFont()->get_glyph_index(42, _embedFonts);
1535 							ge.index = bullet;
1536 							ge.advance = scale * newrec.getFont()->get_advance(bullet, _embedFonts);
1537 							newrec.addGlyph(ge);
1538 
1539 							space = newrec.getFont()->get_glyph_index(32, _embedFonts);
1540 							ge.index = space;
1541 							ge.advance = scale * newrec.getFont()->get_advance(space, _embedFonts);
1542 							newrec.addGlyph(ge, 4);
1543 
1544 							handleChar(it, e, x, y, newrec, last_code,
1545                                     last_space_glyph, last_line_start_record);
1546 							newLine(x, y, newrec, last_space_glyph,
1547                                     last_line_start_record, 1.0);
1548                         }
1549                         else if (s == "SPAN") {
1550                             //span
1551                             log_unimpl(_("<span> HTML tag in TextField"));
1552                             handleChar(it, e, x, y, newrec, last_code,
1553                                     last_space_glyph, last_line_start_record);
1554                         }
1555                         else if (s == "TEXTFORMAT") {
1556                             log_debug("in textformat");
1557                             //textformat
1558                             std::uint16_t originalblockindent = getBlockIndent();
1559                             std::uint16_t originalindent = getIndent();
1560                             std::uint16_t originalleading = getLeading();
1561                             std::uint16_t originalleftmargin = getLeftMargin();
1562                             std::uint16_t originalrightmargin = getRightMargin();
1563                             std::vector<int> originaltabstops = getTabStops();
1564                             attloc = attributes.find("BLOCKINDENT");
1565                             if (attloc != attributes.end()) {
1566                                 //textformat BLOCKINDENT attribute
1567                                 setBlockIndent(pixelsToTwips(std::strtol(
1568                                         attloc->second.data(), nullptr, 10)));
1569                                 if (newrec.xOffset() == std::max(0, originalleftmargin +
1570                                     originalindent + originalblockindent) + PADDING_TWIPS) {
1571                                     //if beginning of line, indent
1572                                     x = std::max(0, getLeftMargin() +
1573                                         getIndent() + getBlockIndent())
1574                                          + PADDING_TWIPS;
1575                                     newrec.setXOffset(x);
1576                                 }
1577                             }
1578                             attloc = attributes.find("INDENT");
1579                             if (attloc != attributes.end()) {
1580                                 //textformat INDENT attribute
1581                                 setIndent(pixelsToTwips(std::strtol(
1582                                     attloc->second.data(), nullptr, 10)));
1583                                 if (newrec.xOffset() == std::max(0, originalleftmargin +
1584                                     originalindent + getBlockIndent()) + PADDING_TWIPS) {
1585                                     //if beginning of line, indent
1586                                     x = std::max(0, getLeftMargin() +
1587                                         getIndent() + getBlockIndent())
1588                                          + PADDING_TWIPS;
1589                                     newrec.setXOffset(x);
1590                                 }
1591                             }
1592                             attloc = attributes.find("LEADING");
1593                             if (attloc != attributes.end()) {
1594                                 //textformat LEADING attribute
1595                                 setLeading(pixelsToTwips(std::strtol(
1596                                         attloc->second.data(), nullptr, 10)));
1597                             }
1598                             attloc = attributes.find("LEFTMARGIN");
1599                             if (attloc != attributes.end()) {
1600                                 //textformat LEFTMARGIN attribute
1601                                 setLeftMargin(pixelsToTwips(std::strtol(
1602                                         attloc->second.data(), nullptr, 10)));
1603                                 if (newrec.xOffset() == std::max(0, originalleftmargin +
1604                                     getIndent() + getBlockIndent()) + PADDING_TWIPS) {
1605                                     //if beginning of line, indent
1606                                     x = std::max(0, getLeftMargin() +
1607                                         getIndent() + getBlockIndent())
1608                                          + PADDING_TWIPS;
1609                                     newrec.setXOffset(x);
1610                                 }
1611                             }
1612                             attloc = attributes.find("RIGHTMARGIN");
1613                             if (attloc != attributes.end()) {
1614                                 //textformat RIGHTMARGIN attribute
1615                                 setRightMargin(pixelsToTwips(std::strtol(
1616                                         attloc->second.data(), nullptr, 10)));
1617                                 //FIXME:Should not apply this to this line if we are not at
1618                                 //beginning of line. Not sure how to do that.
1619                             }
1620                             attloc = attributes.find("TABSTOPS");
1621                             if (attloc != attributes.end()) {
1622                                 //textformat TABSTOPS attribute
1623                                 log_unimpl(_("HTML <textformat> tag tabstops attribute"));
1624                             }
1625                             handleChar(it, e, x, y, newrec, last_code,
1626                                     last_space_glyph, last_line_start_record);
1627                             setBlockIndent(originalblockindent);
1628                             setIndent(originalindent);
1629                             setLeading(originalleading);
1630                             setLeftMargin(originalleftmargin);
1631                             setRightMargin(originalrightmargin);
1632                             setTabStops(originaltabstops);
1633                         }
1634                         else if (s == "P") {
1635                             //paragraph
1636                             if (_display == TEXTFORMAT_BLOCK) {
1637                                 handleChar(it, e, x, y, newrec, last_code,
1638                                         last_space_glyph,
1639                                         last_line_start_record);
1640                                 newLine(x, y, rec, last_space_glyph,
1641                                         last_line_start_record, 1.0);
1642 								newLine(x, y, rec, last_space_glyph,
1643                                         last_line_start_record, 1.5);
1644                             }
1645                             else {
1646                                 handleChar(it, e, x, y, newrec, last_code,
1647                                         last_space_glyph,
1648                                         last_line_start_record);
1649                             }
1650                         }
1651                         else if (s == "BR" || s == "SBR") {
1652                             //line break
1653 							newLine(x, y, rec, last_space_glyph,
1654 										last_line_start_record, 1.0);
1655                         }
1656                         else {
1657                             log_debug("<%s> tag is unsupported", s);
1658                             if (!selfclosing) { //then recurse, look for closing tag
1659                             handleChar(it, e, x, y, newrec, last_code,
1660                                                 last_space_glyph, last_line_start_record);
1661                             }
1662                         }
1663                     }
1664                     rec.setXOffset(x);
1665                     rec.setYOffset(y);
1666                     continue;
1667                 }
1668                 // If HTML isn't enabled, carry on and insert the glyph.
1669                 // FIXME: do we also want to be changing last_space_glyph?
1670                 //        ...because we are...
1671             case 32:
1672                 last_space_glyph = rec.glyphs().size();
1673                 // Don't break, as we still need to insert the space glyph.
1674 
1675             default:
1676             {
1677                 if ( password() )
1678                 {
1679                     SWF::TextRecord::GlyphEntry ge;
1680                     int bullet = rec.getFont()->get_glyph_index(42, _embedFonts);
1681                     ge.index = bullet;
1682                     ge.advance = scale * rec.getFont()->get_advance(bullet,
1683                         _embedFonts);
1684                     rec.addGlyph(ge);
1685                     ++_glyphcount;
1686                     break;
1687                 }
1688                 // The font table holds up to 65535 glyphs. Casting
1689                 // from uint32_t would, in the event that the code
1690                 // is higher than 65535, result in the wrong DisplayObject
1691                 // being chosen. Flash can currently only handle 16-bit
1692                 // values.
1693                 int index = rec.getFont()->get_glyph_index(
1694                         static_cast<std::uint16_t>(code), _embedFonts);
1695 
1696                 IF_VERBOSE_MALFORMED_SWF (
1697                     if (index == -1)
1698                     {
1699                         // Missing glyph! Log the first few errors.
1700                         static int s_log_count = 0;
1701                         if (s_log_count < 10)
1702                         {
1703                             s_log_count++;
1704                             if (_embedFonts)
1705                             {
1706                                 log_swferror(_("TextField: missing embedded "
1707                                     "glyph for char %d. Make sure DisplayObject "
1708                                     "shapes for font %s are being exported "
1709                                     "into your SWF file"),
1710                                     code, _font->name());
1711                             }
1712                             else
1713                             {
1714                                 log_swferror(_("TextField: missing device "
1715                                     "glyph for char %d. Maybe you don't have "
1716                                     "font '%s' installed in your system."),
1717                                     code, _font->name());
1718                             }
1719                         }
1720 
1721                         // Drop through and use index == -1; this will display
1722                         // using the empty-box glyph
1723                     }
1724                 );
1725 
1726                 SWF::TextRecord::GlyphEntry ge;
1727                 ge.index = index;
1728                 ge.advance = scale * rec.getFont()->get_advance(index,
1729                         _embedFonts);
1730 
1731                 rec.addGlyph(ge);
1732 
1733                 x += ge.advance;
1734                 ++_glyphcount;
1735             }
1736         }
1737 
1738         float width = _bounds.width();
1739         if (x >= width - getRightMargin() - PADDING_TWIPS)
1740         {
1741 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1742             log_debug("Text in TextField %s exceeds width [ _bounds %s ]",
1743                     getTarget(), _bounds);
1744 #endif
1745 
1746             // No wrap and no resize: truncate
1747             if (!doWordWrap() && getAutoSize() == AUTOSIZE_NONE)
1748             {
1749 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1750                 log_debug(" wordWrap=false, autoSize=none");
1751 #endif
1752                 // Truncate long line, but keep expanding text box
1753                 bool newlinefound = false;
1754                 while (it != e)
1755                 {
1756                     code = *it++;
1757                     if (_embedFonts)
1758                     {
1759                         x += rec.getFont()->get_kerning_adjustment(last_code,
1760                                 static_cast<int>(code)) * scale;
1761                         last_code = code;
1762                     }
1763                     // Expand the bounding-box to the lower-right corner
1764                     // of each glyph, even if we don't display it
1765                     m_text_bounding_box.expand_to_point(x, y + fontDescent);
1766 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1767                     log_debug("Text box expanded to %s (width: %f)",
1768                             m_text_bounding_box, m_text_bounding_box.width());
1769 #endif
1770 
1771                     if (code == 13 || code == 10)
1772                     {
1773                         newlinefound = true;
1774                         break;
1775                     }
1776 
1777                     int index = rec.getFont()->get_glyph_index(
1778                             static_cast<std::uint16_t>(code), _embedFonts);
1779                     x += scale * rec.getFont()->get_advance(index, _embedFonts);
1780 
1781                 }
1782                 if (!newlinefound) break;
1783             }
1784             else if (doWordWrap()) {
1785 
1786 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1787                 log_debug(" wordWrap=true");
1788 #endif
1789 
1790                 // Insert newline if there's space or autosize != none
1791 
1792                 // Close out this stretch of glyphs.
1793                 _textRecords.push_back(rec);
1794 
1795                 float previous_x = x;
1796                 x = std::max(0, getLeftMargin() + getBlockIndent()) + PADDING_TWIPS;
1797                 y += _fontHeight + leading;
1798                 if (y >= _bounds.height()) {
1799                     ++_maxScroll;
1800                 }
1801 
1802                 // Start a new record on the next line.
1803                 rec.clearGlyphs();
1804                 rec.setXOffset(x);
1805                 rec.setYOffset(y);
1806 
1807                 // TODO : what if m_text_glyph_records is empty ?
1808                 // Is it possible ?
1809                 assert(!_textRecords.empty());
1810                 SWF::TextRecord& last_line = _textRecords.back();
1811 
1812                 linestartit = _line_starts.begin();
1813                 linestartend = _line_starts.end();
1814                 if (last_space_glyph == -1)
1815                 {
1816                     // Pull the previous glyph down onto the
1817                     // new line.
1818                     if (!last_line.glyphs().empty())
1819                     {
1820                         rec.addGlyph(last_line.glyphs().back());
1821                         x += last_line.glyphs().back().advance;
1822                         previous_x -= last_line.glyphs().back().advance;
1823                         last_line.clearGlyphs(1);
1824                         //record the new line start
1825                         //
1826                         const size_t currentPos = _glyphcount;
1827                         while (linestartit != linestartend &&
1828                                 *linestartit + 1 <= currentPos)
1829                         {
1830                             ++linestartit;
1831                         }
1832                         _line_starts.insert(linestartit, currentPos);
1833                         _recordStarts.push_back(currentPos);
1834                     }
1835                 } else {
1836                     // Move the previous word down onto the next line.
1837 
1838                     previous_x -= last_line.glyphs()[last_space_glyph].advance;
1839 
1840                     const SWF::TextRecord::Glyphs::size_type lineSize =
1841                         last_line.glyphs().size();
1842                     for (unsigned int i = last_space_glyph + 1; i < lineSize;
1843                             ++i)
1844                     {
1845                         rec.addGlyph(last_line.glyphs()[i]);
1846                         x += last_line.glyphs()[i].advance;
1847                         previous_x -= last_line.glyphs()[i].advance;
1848                     }
1849                     last_line.clearGlyphs(lineSize - last_space_glyph);
1850 
1851                     // record the position at the start of this line as
1852                     // a line_start
1853                     const size_t linestartpos = _glyphcount -
1854                             rec.glyphs().size();
1855 
1856                     while (linestartit < linestartend &&
1857                             *linestartit < linestartpos)
1858                     {
1859                         ++linestartit;
1860                     }
1861                     _line_starts.insert(linestartit, linestartpos);
1862                     _recordStarts.push_back(linestartpos);
1863                 }
1864 
1865                 align_line(getTextAlignment(), last_line_start_record, previous_x);
1866 
1867                 last_space_glyph = -1;
1868                 last_line_start_record = _textRecords.size();
1869 
1870             }
1871             else
1872             {
1873 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1874                 log_debug(" wordWrap=%d, autoSize=%d", _wordWrap, _autoSize);
1875 #endif
1876             }
1877         }
1878     }
1879 }
1880 
1881 int
getDefinitionVersion() const1882 TextField::getDefinitionVersion() const
1883 {
1884     // TODO: work out if this correct.
1885     return get_root()->getDefinitionVersion();
1886 }
1887 
1888 
1889 TextField::VariableRef
parseTextVariableRef(const std::string & variableName) const1890 TextField::parseTextVariableRef(const std::string& variableName) const
1891 {
1892     VariableRef ret;
1893     ret.first = nullptr;
1894 
1895 #ifdef DEBUG_DYNTEXT_VARIABLES
1896     log_debug("VariableName: %s", variableName);
1897 #endif
1898 
1899     /// Why isn't get_environment const again ?
1900     const as_environment& env = const_cast<TextField*>(this)->get_environment();
1901 
1902     as_object* target = getObject(env.target());
1903     if (!target) {
1904         IF_VERBOSE_MALFORMED_SWF(
1905             log_swferror(_("Current environment has no target, "
1906                 "can't bind VariableName (%s) associated to "
1907                 "text field. Gnash will try to register "
1908                 "again on next access."), variableName);
1909         );
1910         return ret;
1911     }
1912 
1913     // If the variable string contains a path, we extract
1914     // the appropriate target from it and update the variable
1915     // name. We copy the string so we can assign to it if necessary.
1916     std::string parsedName = variableName;
1917     std::string path, var;
1918     if (parsePath(variableName, path, var)) {
1919 #ifdef DEBUG_DYNTEXT_VARIABLES
1920         log_debug("Variable text Path: %s, Var: %s", path, var);
1921 #endif
1922         // find target for the path component
1923         // we use our parent's environment for this
1924         target = findObject(env, path);
1925 
1926         parsedName = var;
1927     }
1928 
1929     if (!target) {
1930         IF_VERBOSE_MALFORMED_SWF(
1931             log_swferror(_("VariableName associated to text field refers "
1932                     "to an unknown target (%s). It is possible that the "
1933                     "DisplayObject will be instantiated later in the SWF "
1934                     "stream. Gnash will try to register again on next "
1935                     "access."), path);
1936         );
1937         return ret;
1938     }
1939 
1940     ret.first = target;
1941     ret.second = getURI(getVM(*object()), parsedName);
1942 
1943     return ret;
1944 }
1945 
1946 void
registerTextVariable()1947 TextField::registerTextVariable()
1948 {
1949 //#define DEBUG_DYNTEXT_VARIABLES 1
1950 
1951 #ifdef DEBUG_DYNTEXT_VARIABLES
1952     log_debug("registerTextVariable() called");
1953 #endif
1954 
1955     if (_text_variable_registered) {
1956         return;
1957     }
1958 
1959     if (_variable_name.empty()) {
1960         _text_variable_registered = true;
1961         return;
1962     }
1963 
1964     VariableRef varRef = parseTextVariableRef(_variable_name);
1965     as_object* target = varRef.first;
1966     if (!target) {
1967         log_debug("VariableName associated to text field (%s) refer to "
1968                     "an unknown target. It is possible that the DisplayObject "
1969                     "will be instantiated later in the SWF stream. "
1970                     "Gnash will try to register again on next access.",
1971                 _variable_name);
1972         return;
1973     }
1974 
1975     const ObjectURI& key = varRef.second;
1976     as_object* obj = getObject(this);
1977     const int version = getSWFVersion(*obj);
1978 
1979     // check if the VariableName already has a value,
1980     // in that case update text value
1981     as_value val;
1982     if (target->get_member(key, &val)) {
1983         // TODO: pass environment to to_string ?
1984         setTextValue(utf8::decodeCanonicalString(val.to_string(), version));
1985     }
1986     else if (_textDefined) {
1987         as_value newVal = as_value(utf8::encodeCanonicalString(_text, version));
1988         target->set_member(key, newVal);
1989     }
1990 
1991     MovieClip* sprite = get<MovieClip>(target);
1992 
1993     if (sprite) {
1994         // add the textfield variable to the target sprite
1995         // TODO: have set_textfield_variable take a string_table::key instead ?
1996         sprite->set_textfield_variable(key, this);
1997 
1998     }
1999     _text_variable_registered = true;
2000 }
2001 
2002 /// Parses an HTML tag (between < and >) and puts
2003 /// the contents into tag. Returns false if the
2004 /// tag was incomplete. The iterator is moved to after
2005 /// the closing tag or the end of the string.
2006 bool
parseHTML(std::wstring & tag,std::map<std::string,std::string> & attributes,std::wstring::const_iterator & it,const std::wstring::const_iterator & e,bool & selfclosing) const2007 TextField::parseHTML(std::wstring& tag,
2008         std::map<std::string, std::string>& attributes,
2009         std::wstring::const_iterator& it,
2010         const std::wstring::const_iterator& e,
2011         bool& selfclosing) const
2012 {
2013     while (it != e && *it != ' ') {
2014         if (*it == '/') {
2015             ++it;
2016             if (*it == '>') {
2017                 ++it;
2018                 selfclosing = true;
2019                 return true;
2020             } else {
2021                 while (it != e) {
2022                     ++it;
2023                 }
2024                 log_error(_("invalid HTML tag"));
2025                 return false;
2026             }
2027         }
2028         if (*it == '>') {
2029             ++it;
2030             return true;
2031         }
2032 
2033         // Check for NULL character
2034         if (*it == 0) {
2035             log_error(_("found NULL character in htmlText"));
2036             return false;
2037         }
2038         tag.push_back(std::toupper(*it));
2039         ++it;
2040     }
2041     while (it != e && *it == ' ') {
2042         ++it; //skip over spaces
2043     }
2044     if (*it == '>') {
2045         ++it;
2046         return true;
2047     }
2048     if (*it == '/') {
2049         ++it;
2050         if (*it == '>') {
2051             ++it;
2052             selfclosing = true;
2053             return true;
2054         } else {
2055             while (it != e) {
2056                 ++it;
2057             }
2058             log_error(_("invalid HTML tag"));
2059             return false;
2060         }
2061     }
2062 
2063     std::string attname;
2064     std::string attvalue;
2065 
2066     //attributes
2067     while (it != e && *it != '>') {
2068         while (it != e && *it != '=' && *it != ' ') {
2069 
2070             if (*it == 0) {
2071                 log_error(_("found NULL character in htmlText"));
2072                 return false;
2073             }
2074             if (*it == '>') {
2075                 log_error(_("malformed HTML tag, invalid attribute name"));
2076                 while (it != e) {
2077                     ++it;
2078                 }
2079                 return false;
2080             }
2081 
2082             attname.push_back(std::toupper(*it));
2083             ++it;
2084         }
2085         while (it != e && (*it == ' ' || *it == '=')) {
2086             ++it; //skip over spaces and '='
2087         }
2088 
2089         if (it == e) return false;
2090         const char q = *it;
2091         if (q != '"' && q != '\'') {
2092             // This is not an attribute.
2093             while (it != e) ++it;
2094             return false;
2095         }
2096 
2097         // Advance past attribute opener
2098         ++it;
2099         while (it != e && *it != q) {
2100 
2101             if (*it == 0) {
2102                 log_error(_("found NULL character in htmlText"));
2103                 return false;
2104             }
2105 
2106             attvalue.push_back(std::toupper(*it));
2107             ++it;
2108         }
2109 
2110         if (it == e) return false;
2111 
2112         if (*it != q) {
2113             while (it != e) ++it;
2114             return false;
2115         }
2116 
2117         // Skip attribute closer.
2118         ++it;
2119 
2120         attributes.insert(std::make_pair(attname, attvalue));
2121         attname.clear();
2122         attvalue.clear();
2123 
2124         if ((*it != ' ') && (*it != '/') && (*it != '>')) {
2125             log_error(_("malformed HTML tag, invalid attribute value"));
2126             while (it != e) {
2127                 ++it;
2128             }
2129             return false;
2130         }
2131         if (*it == ' ') {
2132             while (it != e && *it == ' ') {
2133                 ++it; //skip over spaces
2134             }
2135         }
2136         if (*it == '>') {
2137             ++it;
2138             return true;
2139         } else if (*it == '/') {
2140             ++it;
2141             if (*it == '>') {
2142                 ++it;
2143                 selfclosing = true;
2144                 return true;
2145             } else {
2146                 while (it != e) {
2147                     ++it;
2148                 }
2149                 log_error(_("invalid HTML tag"));
2150                 return false;
2151             }
2152         }
2153     }
2154 
2155 #ifdef GNASH_DEBUG_TEXTFIELDS
2156     log_debug("HTML tag: %s", utf8::encodeCanonicalString(tag, 7));
2157 #endif
2158     log_error(_("I declare this a HTML syntax error"));
2159     return false; //since we did not return already, must be malformed...?
2160 }
2161 
2162 void
set_variable_name(const std::string & newname)2163 TextField::set_variable_name(const std::string& newname)
2164 {
2165     if (newname != _variable_name) {
2166         _variable_name = newname;
2167 
2168         // The name was empty or undefined, so there's nothing more to do.
2169         if (_variable_name.empty()) return;
2170 
2171         _text_variable_registered = false;
2172 
2173 #ifdef DEBUG_DYNTEXT_VARIABLES
2174         log_debug("Calling updateText after change of variable name");
2175 #endif
2176 
2177         // Use the original definition text if this isn't dynamically
2178         // created.
2179         if (_tag) updateText(_tag->defaultText());
2180 
2181 #ifdef DEBUG_DYNTEXT_VARIABLES
2182         log_debug("Calling registerTextVariable after change of variable "
2183 		    "name and updateText call");
2184 #endif
2185         registerTextVariable();
2186     }
2187 }
2188 
2189 bool
pointInShape(std::int32_t x,std::int32_t y) const2190 TextField::pointInShape(std::int32_t x, std::int32_t y) const
2191 {
2192     const SWFMatrix wm = getWorldMatrix(*this).invert();
2193     point lp(x, y);
2194     wm.transform(lp);
2195     return _bounds.point_test(lp.x, lp.y);
2196 }
2197 
2198 bool
getDrawBorder() const2199 TextField::getDrawBorder() const
2200 {
2201     return _drawBorder;
2202 }
2203 
2204 void
setDrawBorder(bool val)2205 TextField::setDrawBorder(bool val)
2206 {
2207     if (_drawBorder != val) {
2208         set_invalidated();
2209         _drawBorder = val;
2210     }
2211 }
2212 
2213 rgba
getBorderColor() const2214 TextField::getBorderColor() const
2215 {
2216     return _borderColor;
2217 }
2218 
2219 void
setBorderColor(const rgba & col)2220 TextField::setBorderColor(const rgba& col)
2221 {
2222     if (_borderColor != col) {
2223         set_invalidated();
2224         _borderColor = col;
2225     }
2226 }
2227 
2228 bool
getDrawBackground() const2229 TextField::getDrawBackground() const
2230 {
2231     return _drawBackground;
2232 }
2233 
2234 void
setDrawBackground(bool val)2235 TextField::setDrawBackground(bool val)
2236 {
2237     if (_drawBackground != val) {
2238         set_invalidated();
2239         _drawBackground = val;
2240     }
2241 }
2242 
2243 rgba
getBackgroundColor() const2244 TextField::getBackgroundColor() const
2245 {
2246     return _backgroundColor;
2247 }
2248 
2249 void
setBackgroundColor(const rgba & col)2250 TextField::setBackgroundColor(const rgba& col)
2251 {
2252     if (_backgroundColor != col) {
2253         set_invalidated();
2254         _backgroundColor = col;
2255     }
2256 }
2257 
2258 void
setTextColor(const rgba & col)2259 TextField::setTextColor(const rgba& col)
2260 {
2261     if (_textColor != col) {
2262 
2263         set_invalidated();
2264         _textColor = col;
2265         std::for_each(_displayRecords.begin(), _displayRecords.end(),
2266                 std::bind(&SWF::TextRecord::setColor, std::placeholders::_1,
2267                     _textColor));
2268     }
2269 }
2270 
2271 void
setEmbedFonts(bool use)2272 TextField::setEmbedFonts(bool use)
2273 {
2274     if (_embedFonts != use) {
2275         set_invalidated();
2276         _embedFonts=use;
2277         format_text();
2278     }
2279 }
2280 
2281 void
setWordWrap(bool wrap)2282 TextField::setWordWrap(bool wrap)
2283 {
2284     if (_wordWrap != wrap) {
2285         set_invalidated();
2286         _wordWrap = wrap;
2287         format_text();
2288     }
2289 }
2290 
2291 void
setLeading(std::int16_t h)2292 TextField::setLeading(std::int16_t h)
2293 {
2294     if (_leading != h) {
2295         set_invalidated();
2296         _leading = h;
2297     }
2298 }
2299 
2300 void
setUnderlined(bool v)2301 TextField::setUnderlined(bool v)
2302 {
2303     if (_underlined != v) {
2304         set_invalidated();
2305         _underlined = v;
2306     }
2307 }
2308 
2309 void
setBullet(bool b)2310 TextField::setBullet(bool b)
2311 {
2312     if (_bullet != b) {
2313         _bullet = b;
2314     }
2315 }
2316 
2317 void
setTabStops(const std::vector<int> & tabStops)2318 TextField::setTabStops(const std::vector<int>& tabStops)
2319 {
2320 	_tabStops.resize(tabStops.size());
2321 
2322 	for (size_t i = 0; i < tabStops.size(); i ++) {
2323 		_tabStops[i] = pixelsToTwips(tabStops[i]);
2324 	}
2325 
2326     set_invalidated();
2327 }
2328 
2329 void
setURL(std::string url)2330 TextField::setURL(std::string url)
2331 {
2332     if (_url != url) {
2333         set_invalidated();
2334         _url = url;
2335     }
2336 }
2337 
2338 void
setTarget(std::string target)2339 TextField::setTarget(std::string target)
2340 {
2341     if (_target != target) {
2342         set_invalidated();
2343         _target = target;
2344     }
2345 }
2346 
2347 void
setDisplay(TextFormatDisplay display)2348 TextField::setDisplay(TextFormatDisplay display)
2349 {
2350     if (_display != display) {
2351         set_invalidated();
2352         _display = display;
2353     }
2354 }
2355 
2356 void
setAlignment(TextAlignment h)2357 TextField::setAlignment(TextAlignment h)
2358 {
2359     if (_alignment != h) {
2360         set_invalidated();
2361         _alignment = h;
2362     }
2363 }
2364 
2365 void
setIndent(std::uint16_t h)2366 TextField::setIndent(std::uint16_t h)
2367 {
2368     if (_indent != h) {
2369         set_invalidated();
2370         _indent = h;
2371     }
2372 }
2373 
2374 void
setBlockIndent(std::uint16_t h)2375 TextField::setBlockIndent(std::uint16_t h)
2376 {
2377     if (_blockIndent != h) {
2378         set_invalidated();
2379         _blockIndent = h;
2380     }
2381 }
2382 
2383 void
setRightMargin(std::uint16_t h)2384 TextField::setRightMargin(std::uint16_t h)
2385 {
2386     if (_rightMargin != h) {
2387         set_invalidated();
2388         _rightMargin = h;
2389     }
2390 }
2391 
2392 void
setLeftMargin(std::uint16_t h)2393 TextField::setLeftMargin(std::uint16_t h)
2394 {
2395     if (_leftMargin != h) {
2396         set_invalidated();
2397         _leftMargin = h;
2398     }
2399 }
2400 
2401 void
setFontHeight(std::uint16_t h)2402 TextField::setFontHeight(std::uint16_t h)
2403 {
2404     if (_fontHeight != h) {
2405         set_invalidated();
2406         _fontHeight = h;
2407     }
2408 }
2409 
2410 
2411 TextField::TypeValue
parseTypeValue(const std::string & val)2412 TextField::parseTypeValue(const std::string& val)
2413 {
2414     StringNoCaseEqual cmp;
2415 
2416     if (cmp(val, "input")) return typeInput;
2417     if (cmp(val, "dynamic")) return typeDynamic;
2418     return typeInvalid;
2419 }
2420 
2421 
2422 const char*
typeValueName(TypeValue val)2423 TextField::typeValueName(TypeValue val)
2424 {
2425     switch (val) {
2426         case typeInput:
2427             //log_debug("typeInput returned as 'input'");
2428             return "input";
2429         case typeDynamic:
2430             //log_debug("typeDynamic returned as 'dynamic'");
2431             return "dynamic";
2432         default:
2433             //log_debug("invalid type %d returned as 'invalid'", (int)val);
2434             return "invalid";
2435     }
2436 
2437 }
2438 
2439 void
setAutoSize(AutoSize val)2440 TextField::setAutoSize(AutoSize val)
2441 {
2442     if (val == _autoSize) return;
2443 
2444     set_invalidated();
2445     _autoSize = val;
2446     format_text();
2447 }
2448 
2449 TextField::TextAlignment
getTextAlignment()2450 TextField::getTextAlignment()
2451 {
2452     TextAlignment textAlignment = getAlignment();
2453 
2454     switch (_autoSize) {
2455         case AUTOSIZE_CENTER:
2456             textAlignment = ALIGN_CENTER;
2457             break;
2458         case AUTOSIZE_LEFT:
2459             textAlignment = ALIGN_LEFT;
2460             break;
2461         case AUTOSIZE_RIGHT:
2462             textAlignment = ALIGN_RIGHT;
2463             break;
2464         default:
2465             // Leave it as it was.
2466             break;
2467     }
2468 
2469     return textAlignment;
2470 }
2471 
2472 void
onChanged()2473 TextField::onChanged()
2474 {
2475     as_object* obj = getObject(this);
2476     callMethod(obj, NSV::PROP_BROADCAST_MESSAGE, "onChanged", obj);
2477 }
2478 
2479 /// This is called by movie_root when focus is applied to this TextField.
2480 //
2481 /// The return value is true if the TextField can receive focus.
2482 /// The swfdec testsuite suggests that version 5 textfields cannot ever
2483 /// handle focus.
2484 bool
handleFocus()2485 TextField::handleFocus()
2486 {
2487     set_invalidated();
2488 
2489     /// Select the entire text on focus.
2490     setSelection(0, _text.length());
2491 
2492     m_has_focus = true;
2493 
2494     m_cursor = _text.size();
2495     format_text();
2496     return true;
2497 }
2498 
2499 /// This is called by movie_root when focus is removed from the
2500 /// current TextField.
2501 void
killFocus()2502 TextField::killFocus()
2503 {
2504     if (!m_has_focus) return;
2505     set_invalidated();
2506     m_has_focus = false;
2507     format_text(); // is this needed ?
2508 }
2509 
2510 void
setWidth(double newwidth)2511 TextField::setWidth(double newwidth)
2512 {
2513 	const SWFRect& bounds = getBounds();
2514     _bounds.set_to_rect(bounds.get_x_min(),
2515             bounds.get_y_min(),
2516             bounds.get_x_min() + newwidth,
2517             bounds.get_y_max());
2518 }
2519 
2520 void
setHeight(double newheight)2521 TextField::setHeight(double newheight)
2522 {
2523 	const SWFRect& bounds = getBounds();
2524     _bounds.set_to_rect(bounds.get_x_min(),
2525             bounds.get_y_min(),
2526             bounds.get_x_max(),
2527             bounds.get_y_min() + newheight);
2528 }
2529 
2530 } // namespace gnash
2531 
2532 
2533 // Local Variables:
2534 // mode: C++
2535 // indent-tabs-mode: t
2536 // End:
2537 
2538