1 // TextField.cpp: User-editable text regions, for Gnash.
3 // Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012
4 // Free Software Foundation, Inc
6 // This program is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 3 of the License, or
9 // (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 // Things implemented:
21 // - setTextFormat does not discard target, url, tabStops, display or
23 // - Above five fields are now implemented (except for target != blank)
24 // - Call movie_root getURL function to properly open url and target
27 // - For the url cases (url property and anchor tag in html) we should
28 // change the mouse cursor to the hand cursor standard for linkable
31 #include "TextField.h"
39 #include <boost/assign/list_of.hpp>
40 #include <boost/bind.hpp>
41 #include <boost/tuple/tuple.hpp>
45 #include "swf/DefineEditTextTag.h"
46 #include "MovieClip.h"
47 #include "movie_root.h" // for killing focus
48 #include "as_environment.h"
51 #include "namedStrings.h"
52 #include "StringPredicates.h"
53 #include "TextFormat_as.h"
55 #include "TextRecord.h"
57 #include "GnashNumeric.h"
58 #include "MouseButtonState.h"
59 #include "Global_as.h"
61 #include "Transform.h"
62 #include "ObjectURI.h"
64 // Text fields have a fixed 2 pixel padding for each side (regardless of border)
65 #define PADDING_TWIPS 40
67 // Define the following to get detailed log information about
68 // textfield bounds and HTML tags:
69 //#define GNASH_DEBUG_TEXTFIELDS 1
71 // Define this to get debugging info about text formatting
72 //#define GNASH_DEBUG_TEXT_FORMATTING 1
76 TextField::TextField(as_object
* object
, DisplayObject
* parent
,
77 const SWF::DefineEditTextTag
& def
)
79 InteractiveObject(object
, parent
),
85 _variable_name(def
.variableName()),
86 _backgroundColor(255,255,255,255),
87 _borderColor(0,0,0,255),
88 _textColor(def
.color()),
89 _alignment(def
.alignment()),
99 _maxChars(def
.maxChars()),
100 _autoSize(def
.autoSize() ? AUTOSIZE_LEFT
: AUTOSIZE_NONE
),
101 _type(def
.readOnly() ? typeDynamic
: typeInput
),
102 _bounds(def
.bounds()),
104 _leading(def
.leading()),
105 _indent(def
.indent()),
107 _leftMargin(def
.leftMargin()),
108 _rightMargin(def
.rightMargin()),
109 _fontHeight(def
.textHeight()),
110 _textDefined(def
.hasText()),
111 _restrictDefined(false),
115 _multiline(def
.multiline()),
116 _password(def
.password()),
117 _text_variable_registered(false),
118 _drawBackground(def
.border()),
119 _drawBorder(def
.border()),
120 _embedFonts(def
.getUseEmbeddedGlyphs()),
121 _wordWrap(def
.wordWrap()),
123 _selectable(!def
.noSelect())
127 // WARNING! remember to set the font *before* setting text value!
128 boost::intrusive_ptr
<const Font
> f
= def
.getFont();
129 if (!f
) f
= fontlib::get_default_font();
132 const int version
= getSWFVersion(*object
);
134 // set default text *before* calling registerTextVariable
135 // (if the textvariable already exist and has a value
136 // the text will be replaced with it)
138 setTextValue(utf8::decodeCanonicalString(def
.defaultText(), version
));
145 TextField::TextField(as_object
* object
, DisplayObject
* parent
,
146 const SWFRect
& bounds
)
148 InteractiveObject(object
, parent
),
153 _backgroundColor(255,255,255,255),
154 _borderColor(0, 0, 0, 255),
155 _textColor(0, 0, 0, 255),
156 _alignment(ALIGN_LEFT
),
167 _autoSize(AUTOSIZE_NONE
),
176 _fontHeight(12 * 20),
178 _restrictDefined(false),
184 _text_variable_registered(false),
185 _drawBackground(false),
193 // Use the default font (Times New Roman for Windows, Times for Mac
194 // according to docs. They don't say what it is for Linux.
195 boost::intrusive_ptr
<const Font
> f
= fontlib::get_default_font();
204 registerTextVariable();
206 reset_bounding_box(0, 0);
210 TextField::~TextField()
215 TextField::removeTextField()
217 int depth
= get_depth();
218 if ( depth
< 0 || depth
> 1048575 )
220 //IF_VERBOSE_ASCODING_ERRORS(
221 log_debug("CHECKME: removeTextField(%s): TextField depth (%d) "
222 "out of the 'dynamic' zone [0..1048575], won't remove",
228 DisplayObject
* p
= parent();
229 assert(p
); // every TextField must have a parent, right ?
231 MovieClip
* parentSprite
= p
->to_movie();
234 log_error(_("FIXME: attempt to remove a TextField being a child of a %s"),
239 // second argument is arbitrary, see comments above
240 // the function declaration in MovieClip.h
241 parentSprite
->remove_display_object(depth
, 0);
245 TextField::show_cursor(Renderer
& renderer
, const SWFMatrix
& mat
)
247 if (_textRecords
.empty()) {
253 size_t i
= cursorRecord();
254 SWF::TextRecord record
= _textRecords
[i
];
256 x
= record
.xOffset();
257 y
= record
.yOffset() - record
.textHeight() + getLeading();
258 h
= record
.textHeight();
260 if (!record
.glyphs().empty()) {
261 for (unsigned int p
= 0 ; p
< (m_cursor
- _recordStarts
[i
]); ++p
) {
262 x
+= record
.glyphs()[p
].advance
;
266 const std::vector
<point
> line
= boost::assign::list_of
270 renderer
.drawLine(line
, rgba(0, 0, 0, 255), mat
);
274 TextField::cursorRecord()
276 if (_textRecords
.empty()) return 0;
280 while (i
< _textRecords
.size() && m_cursor
>= _recordStarts
[i
]) {
283 // TODO: it seems like this could return (size_t) -1, but there's no
284 // evidence this is allowed or handled.
289 TextField::display(Renderer
& renderer
, const Transform
& base
)
291 const DisplayObject::MaskRenderer
mr(renderer
, *this);
293 registerTextVariable();
295 const bool drawBorder
= getDrawBorder();
296 const bool drawBackground
= getDrawBackground();
298 Transform xform
= base
* transform();
300 // This is a hack to handle device fonts, which are not affected by
302 if (!getEmbedFonts()) xform
.colorTransform
= SWFCxForm();
304 if ((drawBorder
|| drawBackground
) && !_bounds
.is_null()) {
306 boost::int32_t xmin
= _bounds
.get_x_min();
307 boost::int32_t xmax
= _bounds
.get_x_max();
308 boost::int32_t ymin
= _bounds
.get_y_min();
309 boost::int32_t ymax
= _bounds
.get_y_max();
311 const std::vector
<point
> coords
= boost::assign::list_of
317 rgba borderColor
= drawBorder
? getBorderColor() : rgba(0,0,0,0);
318 rgba backgroundColor
= drawBackground
? getBackgroundColor() :
321 SWFCxForm cx
= xform
.colorTransform
;
323 if (drawBorder
) borderColor
= cx
.transform(borderColor
);
325 if (drawBackground
) backgroundColor
= cx
.transform(backgroundColor
);
327 #ifdef GNASH_DEBUG_TEXTFIELDS
328 log_debug("rendering a Pol composed by corners %s", _bounds
);
331 renderer
.draw_poly(coords
, backgroundColor
,
332 borderColor
, xform
.matrix
, true);
336 // Draw our actual text.
337 // Using a SWFMatrix to translate to def bounds seems an hack to me.
338 // A cleaner implementation is likely correctly setting the
339 // _xOffset and _yOffset memebers in glyph records.
340 // Anyway, see bug #17954 for a testcase.
341 if (!_bounds
.is_null()) {
342 xform
.matrix
.concatenate_translation(_bounds
.get_x_min(),
343 _bounds
.get_y_min());
346 _displayRecords
.clear();
347 // TODO: work out how leading should be implemented.
348 const float fontLeading
= 0;
351 int yoffset
= (getFontHeight() + fontLeading
) + PADDING_TWIPS
;
353 for (size_t i
= 0; i
< _textRecords
.size(); ++i
) {
355 //find the line the record is on
356 while (recordline
< _line_starts
.size() &&
357 _line_starts
[recordline
] <= _recordStarts
[i
]) {
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
]);
369 SWF::TextRecord::displayRecords(renderer
, xform
, _displayRecords
,
372 if (m_has_focus
&& !isReadOnly()) show_cursor(renderer
, xform
.matrix
);
379 TextField::add_invalidated_bounds(InvalidatedRanges
& ranges
, bool force
)
381 if (!force
&& !invalidated()) return; // no need to redraw
383 ranges
.add(m_old_invalidated_ranges
);
385 const SWFMatrix
& wm
= getWorldMatrix(*this);
387 SWFRect bounds
= getBounds();
388 bounds
.expand_to_rect(m_text_bounding_box
);
389 wm
.transform(bounds
);
390 ranges
.add(bounds
.getRange());
394 TextField::setRestrict(const std::string
& restrict
)
396 _restrictDefined
= true;
398 std::string::const_iterator rit
= restrict
.begin();
399 std::string::const_iterator re
= restrict
.end();
400 std::set
<wchar_t>::const_iterator locate
;
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
));
406 } else { //then this is an ALLOW pattern, _restrictedchars should remain empty
407 _restrictedchars
.clear();
411 while (rit
!= re
&& *rit
!= '^') { //This loop allows chars
413 log_error(_("invalid restrict string"));
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
));
423 log_error(_("invalid restrict string"));
426 } else if (*rit
== '\\') {
428 _restrictedchars
.insert(*rit
);
431 _restrictedchars
.insert(*rit
);
438 while (rit
!= re
&& *rit
!= '^') { //This loop restricts chars
439 locate
= _restrictedchars
.find(*rit
);
441 log_error(_("invalid restrict string"));
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
);
456 log_error(_("invalid restrict string"));
459 } else if (*rit
== '\\') {
461 locate
= _restrictedchars
.find(*rit
);
462 if(locate
!= _restrictedchars
.end()) {
463 _restrictedchars
.erase(locate
);
467 if(locate
!= _restrictedchars
.end()) {
468 _restrictedchars
.erase(locate
);
477 _restrict
= restrict
;
481 TextField::replaceSelection(const std::string
& replace
)
483 const int version
= getSWFVersion(*getObject(this));
484 const std::wstring
& wstr
= utf8::decodeCanonicalString(replace
, version
);
486 assert(_selection
.second
>= _selection
.first
);
487 assert(_selection
.second
<= _text
.size());
488 assert(_selection
.first
<= _text
.size());
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
;
495 const size_t replaceLength
= wstr
.size();
497 _text
.replace(start
, end
- start
, wstr
);
498 _selection
= std::make_pair(start
+ replaceLength
, start
+ replaceLength
);
502 TextField::setSelection(int start
, int end
)
505 _selection
= std::make_pair(0, 0);
509 const size_t textLength
= _text
.size();
511 if (start
< 0) start
= 0;
512 else start
= std::min
<size_t>(start
, textLength
);
514 if (end
< 0) end
= 0;
515 else end
= std::min
<size_t>(end
, textLength
);
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
521 if (start
> end
) std::swap(start
, end
);
523 _selection
= std::make_pair(start
, end
);
527 TextField::keyInput(key::code c
)
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.
539 setHtml(false); //editable html fields are not yet implemented
540 std::wstring s
= _text
;
542 // maybe _text is changed in ActionScript
543 m_cursor
= std::min
<size_t>(m_cursor
, _text
.size());
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();
554 if (isReadOnly()) return;
557 s
.erase(m_cursor
- 1, 1);
564 if (isReadOnly()) return;
565 if (_glyphcount
> m_cursor
)
567 s
.erase(m_cursor
, 1);
572 case key::INSERT
: // TODO
573 if (isReadOnly()) return;
577 while ( linestartit
< linestartend
&& *linestartit
<= m_cursor
) {
578 cur_cursor
= *linestartit
;
581 m_cursor
= cur_cursor
;
585 // if going a page up is too far...
586 if(_scroll
< _linesindisplay
) {
589 } else { // go a page up
590 _scroll
-= _linesindisplay
;
591 m_cursor
= _line_starts
[_scroll
];
597 while ( linestartit
< linestartend
&& *linestartit
<= m_cursor
) {
598 cur_cursor
= *linestartit
;
601 //if there is no previous line
602 if ( linestartit
-_line_starts
.begin() - 2 < 0 ) {
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
;
611 m_cursor
= *(--(--linestartit
)) + (m_cursor
- cur_cursor
);
613 if (m_cursor
< _line_starts
[_scroll
] && _line_starts
[_scroll
] != 0) {
620 while ( linestartit
< linestartend
&& *linestartit
<= m_cursor
) {
623 m_cursor
= linestartit
!= linestartend
? *linestartit
- 1 : _text
.size();
627 //if going another page down is too far...
628 if(_scroll
+ _linesindisplay
>= manylines
) {
629 if(manylines
- _linesindisplay
<= 0) {
632 _scroll
= manylines
- _linesindisplay
;
634 if(m_cursor
< _line_starts
[_scroll
-1]) {
635 m_cursor
= _line_starts
[_scroll
-1];
637 m_cursor
= _text
.size();
639 } else { //go a page down
640 _scroll
+= _linesindisplay
;
641 m_cursor
= _line_starts
[_scroll
];
648 while (linestartit
< linestartend
&&
649 *linestartit
<= m_cursor
) {
650 cur_cursor
= *linestartit
;
654 // linestartit should never be before _line_starts.begin()
655 const size_t currentLine
= linestartit
-
656 _line_starts
.begin();
658 //if there is no next line
659 if (currentLine
>= manylines
) {
660 m_cursor
= _text
.size();
663 nextlinesize
= _textRecords
[currentLine
].glyphs().size();
665 //if the next line is smaller
666 if (m_cursor
- cur_cursor
> nextlinesize
) {
667 m_cursor
= *linestartit
+ nextlinesize
;
669 //put the cursor at the same character distance
670 m_cursor
= *(linestartit
) + (m_cursor
- cur_cursor
);
672 if (_line_starts
.size() > _linesindisplay
&&
673 m_cursor
>= _line_starts
[_scroll
+_linesindisplay
]) {
681 m_cursor
= m_cursor
> 0 ? m_cursor
- 1 : 0;
685 m_cursor
= m_cursor
< _glyphcount
? m_cursor
+ 1 :
690 if (isReadOnly()) return;
691 if (!multiline()) break;
695 if (maxChars() != 0) {
696 if (_maxChars
<= _glyphcount
) {
701 if (isReadOnly()) return;
702 wchar_t t
= static_cast<wchar_t>(
703 gnash::key::codeMap
[c
][key::ASCII
]);
706 if (!_restrictDefined
) {
707 // Insert one copy of the character
708 // at the cursor position.
709 s
.insert(m_cursor
, 1, t
);
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
);
716 } else if (_restrictedchars
.count(tolower(t
))) {
717 // restrict substitutes the opposite case
718 s
.insert(m_cursor
, 1, tolower(t
));
720 } else if (_restrictedchars
.count(toupper(t
))) {
721 // restrict substitutes the opposite case
722 s
.insert(m_cursor
, 1, toupper(t
));
733 TextField::mouseEvent(const event_id
& ev
)
737 case event_id::PRESS
:
739 movie_root
& root
= stage();
740 boost::int32_t x_mouse
, y_mouse
;
741 boost::tie(x_mouse
, y_mouse
) = root
.mousePosition();
743 SWFMatrix m
= getMatrix(*this);
745 x_mouse
-= m
.get_x_translation();
746 y_mouse
-= m
.get_y_translation();
750 for (size_t i
=0; i
< _textRecords
.size(); ++i
) {
751 if ((x_mouse
> _textRecords
[i
].xOffset()) &&
752 (x_mouse
< _textRecords
[i
].xOffset()+_textRecords
[i
].recordWidth()) &&
753 (y_mouse
> _textRecords
[i
].yOffset()-_textRecords
[i
].textHeight()) &&
754 (y_mouse
< _textRecords
[i
].yOffset())) {
755 rec
= _textRecords
[i
];
760 if (!rec
.getURL().empty()) {
761 root
.getURL(rec
.getURL(), rec
.getTarget(), "",
762 MovieClip::METHOD_NONE
);
773 TextField::topmostMouseEntity(boost::int32_t x
, boost::int32_t y
)
775 if (!visible()) return 0;
777 // Not selectable, so don't catch mouse events!
778 if (!_selectable
) return 0;
780 SWFMatrix m
= getMatrix(*this);
782 m
.invert().transform(p
);
784 if (_bounds
.point_test(p
.x
, p
.y
)) return this;
790 TextField::updateText(const std::string
& str
)
792 const int version
= getSWFVersion(*getObject(this));
793 const std::wstring
& wstr
= utf8::decodeCanonicalString(str
, version
);
798 TextField::updateText(const std::wstring
& wstr
)
801 if (_text
== wstr
) return;
807 _selection
.first
= std::min(_selection
.first
, _text
.size());
808 _selection
.second
= std::min(_selection
.second
, _text
.size());
814 TextField::updateHtmlText(const std::wstring
& wstr
)
816 if (_htmlText
== wstr
) return;
825 TextField::setTextValue(const std::wstring
& wstr
)
827 updateHtmlText(wstr
);
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
;
835 const int version
= getSWFVersion(*getObject(this));
836 // we shouldn't truncate, right?
837 tgt
->set_member(ref
.second
, utf8::encodeCanonicalString(wstr
,
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
);
851 TextField::get_text_value() const
853 // we need the const_cast here because registerTextVariable
854 // *might* change our text value, calling the non-const
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();
861 const int version
= getSWFVersion(*getObject(this));
863 return utf8::encodeCanonicalString(_text
, version
);
867 TextField::get_htmltext_value() const
869 const_cast<TextField
*>(this)->registerTextVariable();
870 const int version
= getSWFVersion(*getObject(this));
871 return utf8::encodeCanonicalString(_htmlText
, version
);
875 TextField::setTextFormat(TextFormat_as
& tf
)
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());
893 // NEED TO IMPLEMENT THESE TWO
894 if (tf
.url()) setURL(*tf
.url());
895 if (tf
.target()) setTarget(*tf
.target());
901 TextField::align_line(TextAlignment align
, int last_line_start_record
, float x
)
903 float width
= _bounds
.width();
904 float right_margin
= getRightMargin();
906 float extra_space
= (width
- right_margin
) - x
- PADDING_TWIPS
;
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
);
917 float shift_right
= 0.0f
;
921 // Nothing to do; already aligned left.
924 // Distribute the space evenly on both sides.
925 shift_right
= extra_space
/ 2;
928 // Shift all the way to the right.
929 shift_right
= extra_space
;
932 // What should we do here?
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
);
944 boost::intrusive_ptr
<const Font
>
945 TextField::setFont(boost::intrusive_ptr
<const Font
> newfont
)
947 if (newfont
== _font
) return _font
;
949 boost::intrusive_ptr
<const Font
> oldfont
= _font
;
958 TextField::insertTab(SWF::TextRecord
& rec
, boost::int32_t& x
, float scale
)
961 const int space
= 32;
962 int index
= rec
.getFont()->get_glyph_index(space
, _embedFonts
);
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());
972 // TODO: why is there a copy of the vector?
973 std::vector
<int> tabStops
= _tabStops
;
975 std::sort(_tabStops
.begin(), _tabStops
.end());
977 if (!_tabStops
.empty()) {
978 int tab
= _tabStops
.back() + 1;
980 for (size_t i
= 0; i
< tabStops
.size(); ++i
) {
981 if (tabStops
[i
] > x
) {
982 if((tabStops
[i
] - x
) < tab
) {
983 tab
= tabStops
[i
] - x
;
989 // This is necessary in case the number of tabs in the text
990 // are more than the actual number of tabStops inside the
992 if (tab
!= _tabStops
.back() + 1) {
993 SWF::TextRecord::GlyphEntry ge
;
994 ge
.index
= rec
.getFont()->get_glyph_index(32, _embedFonts
);
1001 SWF::TextRecord::GlyphEntry ge
;
1003 ge
.advance
= scale
* rec
.getFont()->get_advance(index
,
1006 const int tabstop
= 4;
1007 rec
.addGlyph(ge
, tabstop
);
1008 x
+= ge
.advance
* tabstop
;
1014 TextField::format_text()
1016 _textRecords
.clear();
1017 _line_starts
.clear();
1018 _recordStarts
.clear();
1021 _recordStarts
.push_back(0);
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);
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'
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
1044 log_error(_("No font for TextField!"));
1048 boost::uint16_t fontHeight
= getFontHeight();
1049 const float scale
= fontHeight
/
1050 static_cast<float>(_font
->unitsPerEM(_embedFonts
));
1052 // TODO: work out how leading affects things.
1053 const float fontLeading
= 0;
1055 const boost::uint16_t leftMargin
= getLeftMargin();
1056 const boost::uint16_t indent
= getIndent();
1057 const boost::uint16_t blockIndent
= getBlockIndent();
1058 const bool underlined
= getUnderlined();
1060 /// Remember the current bounds for autosize.
1061 SWFRect
oldBounds(_bounds
);
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
);
1072 // create in textrecord.h
1074 rec
.setTarget(_target
);
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()
1085 int space
= rec
.getFont()->get_glyph_index(32, _embedFonts
);
1087 SWF::TextRecord::GlyphEntry ge
;
1089 ge
.advance
= scale
* rec
.getFont()->get_advance(space
, _embedFonts
);
1090 rec
.addGlyph(ge
, 5);
1092 // We use an asterisk instead of a bullet
1093 int bullet
= rec
.getFont()->get_glyph_index(42, _embedFonts
);
1095 ge
.advance
= scale
* rec
.getFont()->get_advance(bullet
, _embedFonts
);
1098 space
= rec
.getFont()->get_glyph_index(32, _embedFonts
);
1100 ge
.advance
= scale
* rec
.getFont()->get_advance(space
, _embedFonts
);
1101 rec
.addGlyph(ge
, 4);
1104 boost::int32_t x
= static_cast<boost::int32_t>(rec
.xOffset());
1105 boost::int32_t y
= static_cast<boost::int32_t>(rec
.yOffset());
1107 // Start the bbox at the upper-left corner of the first glyph.
1108 //reset_bounding_box(x, y + fontHeight);
1110 int last_code
= -1; // only used if _embedFonts
1111 int last_space_glyph
= -1;
1112 size_t last_line_start_record
= 0;
1114 _line_starts
.push_back(0);
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();
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
);
1128 // Expand bounding box to include the whole text (if autoSize and wordWrap
1129 // is not in operation.
1130 if (_autoSize
!= AUTOSIZE_NONE
&& !doWordWrap())
1132 _bounds
.expand_to_point(x
+ PADDING_TWIPS
, y
+ PADDING_TWIPS
);
1134 if (_autoSize
== AUTOSIZE_RIGHT
) {
1135 /// Autosize right expands from the previous right margin.
1138 m
.set_x_translation(oldBounds
.get_x_max() - _bounds
.width());
1139 m
.transform(_bounds
);
1141 else if (_autoSize
== AUTOSIZE_CENTER
) {
1142 // Autosize center expands from the previous center.
1144 m
.set_x_translation(oldBounds
.get_x_min() + oldBounds
.width() / 2.0 -
1145 _bounds
.width() / 2.0);
1146 m
.transform(_bounds
);
1150 // Add the last line to our output.
1151 _textRecords
.push_back(rec
);
1153 // align the last (or single) line
1154 align_line(getTextAlignment(), last_line_start_record
, x
);
1158 set_invalidated(); //redraw
1163 TextField::scrollLines()
1165 boost::uint16_t fontHeight
= getFontHeight();
1166 const float fontLeading
= 0;
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
;
1174 // If there aren't as many lines as we have scrolled, display the
1176 if (manylines
< _scroll
) {
1177 _scroll
= manylines
- _linesindisplay
;
1181 // which line is the cursor on?
1182 while (line
< manylines
&& _line_starts
[line
] <= m_cursor
) {
1186 if (manylines
- _scroll
<= _linesindisplay
) {
1187 // This is for if we delete a line
1188 if (manylines
< _linesindisplay
) _scroll
= 0;
1190 _scroll
= manylines
- _linesindisplay
;
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
);
1205 TextField::newLine(boost::int32_t& x
, boost::int32_t& y
,
1206 SWF::TextRecord
& rec
, int& last_space_glyph
,
1207 LineStarts::value_type
& last_line_start_record
, float div
)
1210 LineStarts::iterator linestartit
= _line_starts
.begin();
1211 LineStarts::const_iterator linestartend
= _line_starts
.end();
1213 // TODO: work out how leading affects things.
1214 const float leading
= 0;
1216 // Close out this stretch of glyphs.
1218 _textRecords
.push_back(rec
);
1219 _recordStarts
.push_back(_glyphcount
);
1220 align_line(getTextAlignment(), last_line_start_record
, x
);
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
);
1227 // new paragraphs get the indent.
1228 x
= std::max(0, getLeftMargin() + getIndent() + getBlockIndent()) +
1230 y
+= div
* (getFontHeight() + leading
);
1231 if (y
>= _bounds
.height()) {
1235 // Start a new record on the next line. Other properties of the
1236 // TextRecord should be left unchanged.
1241 last_space_glyph
= -1;
1242 last_line_start_record
= _textRecords
.size();
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
;
1249 while (linestartit
< linestartend
&& *linestartit
< currentPos
)
1253 _line_starts
.insert(linestartit
, currentPos
);
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()
1265 int space
= rec
.getFont()->get_glyph_index(32, _embedFonts
);
1266 SWF::TextRecord::GlyphEntry ge
;
1269 const float scale
= getFontHeight() /
1270 static_cast<float>(_font
->unitsPerEM(_embedFonts
));
1272 ge
.advance
= scale
* rec
.getFont()->get_advance(space
, _embedFonts
);
1277 int bullet
= rec
.getFont()->get_glyph_index(42, _embedFonts
);
1279 ge
.advance
= scale
* rec
.getFont()->get_advance(bullet
, _embedFonts
);
1284 ge
.advance
= scale
* rec
.getFont()->get_advance(space
, _embedFonts
);
1292 TextField::handleChar(std::wstring::const_iterator
& it
,
1293 const std::wstring::const_iterator
& e
, boost::int32_t& x
,
1294 boost::int32_t& y
, SWF::TextRecord
& rec
, int& last_code
,
1295 int& last_space_glyph
, LineStarts::value_type
& last_line_start_record
)
1297 LineStarts::iterator linestartit
= _line_starts
.begin();
1298 LineStarts::const_iterator linestartend
= _line_starts
.end();
1300 float scale
= _fontHeight
/
1301 static_cast<float>(_font
->unitsPerEM(_embedFonts
));
1302 float fontDescent
= _font
->descent(_embedFonts
) * scale
;
1304 // TODO: work out how leading should be implemented.
1305 const float leading
= 0;
1306 const float fontLeading
= 0;
1308 boost::uint32_t code
= 0;
1316 x
+= rec
.getFont()->get_kerning_adjustment(last_code
,
1317 static_cast<int>(code
)) * scale
;
1318 last_code
= static_cast<int>(code
);
1321 // Expand the bounding-box to the lower-right corner of each glyph as
1323 m_text_bounding_box
.expand_to_point(x
, y
+ fontDescent
);
1330 insertTab(rec
, x
, scale
);
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.
1340 // ActionScript understands the '\b' escape sequence
1341 // for inserting a BS DisplayObject.
1343 // ONLY WORKS FOR BACKSPACING OVER ONE CHARACTER, WON'T BS
1344 // OVER NEWLINES, ETC.
1346 if (!rec
.glyphs().empty())
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
;
1359 newLine(x
,y
,rec
,last_space_glyph
,last_line_start_record
,1.0);
1365 //close out this stretch of glyphs
1366 _textRecords
.push_back(rec
);
1368 _recordStarts
.push_back(_glyphcount
);
1370 while (it
!= e
&& *it
!= '>') {
1376 LOG_ONCE(log_debug("HTML in a text field is unsupported, "
1377 "gnash will just ignore the tags and "
1378 "print their content"));
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());
1393 std::map
<std::string
,std::string
>::const_iterator attloc
;
1396 //parsing went wrong
1399 // Don't think this is the best way to match with
1401 // TODO: assumes tags are properly nested. This isn't
1405 newrec
.setUnderline(true);
1406 handleChar(it
, e
, x
, y
, newrec
, last_code
,
1407 last_space_glyph
, last_line_start_record
);
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
);
1418 attloc
= attributes
.find("TARGET");
1419 if (attloc
!=attributes
.end()) {
1420 newrec
.setTarget(attloc
->second
);
1422 handleChar(it
, e
, x
, y
, newrec
, last_code
,
1423 last_space_glyph
, last_line_start_record
);
1425 else if (s
== "B") {
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
);
1434 else if (s
== "FONT") {
1436 boost::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"),
1449 // font COLOR attribute
1451 colorFromHexString(hexval
);
1452 newrec
.setColor(color
);
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."))
1462 //font FACE attribute
1464 fontlib::get_font(attloc
->second
,
1465 rec
.getFont()->isBold(),
1466 rec
.getFont()->isItalic());
1467 newrec
.setFont(newfont
);
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() +
1477 (pixelsToTwips(std::strtol(
1478 attloc
->second
.substr(1,attloc
->second
.length()-1).data(),
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(),
1486 } else if (firstchar
== "-") {
1487 newrec
.setTextHeight(rec
.textHeight() -
1488 (pixelsToTwips(std::strtol(
1489 attloc
->second
.substr(1,attloc
->second
.length()-1).data(),
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(),
1498 newrec
.setTextHeight(pixelsToTwips(std::strtol(
1499 attloc
->second
.data(), NULL
, 10)));
1500 newrec
.setYOffset(PADDING_TWIPS
+ newrec
.textHeight() +
1501 (fontLeading
- fontDescent
));
1502 _fontHeight
= pixelsToTwips(std::strtol(
1503 attloc
->second
.data(), NULL
, 10));
1506 handleChar(it
, e
, x
, y
, newrec
, last_code
,
1507 last_space_glyph
, last_line_start_record
);
1508 _fontHeight
= originalsize
;
1509 y
= newrec
.yOffset();
1511 else if (s
== "IMG") {
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
);
1517 else if (s
== "I") {
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
;
1530 ge
.advance
= scale
* newrec
.getFont()->get_advance(space
, _embedFonts
);
1531 newrec
.addGlyph(ge
, 5);
1533 // We use an asterisk instead of a bullet
1534 int bullet
= newrec
.getFont()->get_glyph_index(42, _embedFonts
);
1536 ge
.advance
= scale
* newrec
.getFont()->get_advance(bullet
, _embedFonts
);
1537 newrec
.addGlyph(ge
);
1539 space
= newrec
.getFont()->get_glyph_index(32, _embedFonts
);
1541 ge
.advance
= scale
* newrec
.getFont()->get_advance(space
, _embedFonts
);
1542 newrec
.addGlyph(ge
, 4);
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);
1549 else if (s
== "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
);
1555 else if (s
== "TEXTFORMAT") {
1556 log_debug("in textformat");
1558 boost::uint16_t originalblockindent
= getBlockIndent();
1559 boost::uint16_t originalindent
= getIndent();
1560 boost::uint16_t originalleading
= getLeading();
1561 boost::uint16_t originalleftmargin
= getLeftMargin();
1562 boost::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(), NULL
, 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())
1575 newrec
.setXOffset(x
);
1578 attloc
= attributes
.find("INDENT");
1579 if (attloc
!= attributes
.end()) {
1580 //textformat INDENT attribute
1581 setIndent(pixelsToTwips(std::strtol(
1582 attloc
->second
.data(), NULL
, 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())
1589 newrec
.setXOffset(x
);
1592 attloc
= attributes
.find("LEADING");
1593 if (attloc
!= attributes
.end()) {
1594 //textformat LEADING attribute
1595 setLeading(pixelsToTwips(std::strtol(
1596 attloc
->second
.data(), NULL
, 10)));
1598 attloc
= attributes
.find("LEFTMARGIN");
1599 if (attloc
!= attributes
.end()) {
1600 //textformat LEFTMARGIN attribute
1601 setLeftMargin(pixelsToTwips(std::strtol(
1602 attloc
->second
.data(), NULL
, 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())
1609 newrec
.setXOffset(x
);
1612 attloc
= attributes
.find("RIGHTMARGIN");
1613 if (attloc
!= attributes
.end()) {
1614 //textformat RIGHTMARGIN attribute
1615 setRightMargin(pixelsToTwips(std::strtol(
1616 attloc
->second
.data(), NULL
, 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.
1620 attloc
= attributes
.find("TABSTOPS");
1621 if (attloc
!= attributes
.end()) {
1622 //textformat TABSTOPS attribute
1623 log_unimpl(_("HTML <textformat> tag tabstops attribute"));
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
);
1634 else if (s
== "P") {
1636 if (_display
== TEXTFORMAT_BLOCK
) {
1637 handleChar(it
, e
, x
, y
, newrec
, last_code
,
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);
1646 handleChar(it
, e
, x
, y
, newrec
, last_code
,
1648 last_line_start_record
);
1651 else if (s
== "BR" || s
== "SBR") {
1653 newLine(x
, y
, rec
, last_space_glyph
,
1654 last_line_start_record
, 1.0);
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
);
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...
1672 last_space_glyph
= rec
.glyphs().size();
1673 // Don't break, as we still need to insert the space glyph.
1679 SWF::TextRecord::GlyphEntry ge
;
1680 int bullet
= rec
.getFont()->get_glyph_index(42, _embedFonts
);
1682 ge
.advance
= scale
* rec
.getFont()->get_advance(bullet
,
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
1693 int index
= rec
.getFont()->get_glyph_index(
1694 static_cast<boost::uint16_t>(code
), _embedFonts
);
1696 IF_VERBOSE_MALFORMED_SWF (
1699 // Missing glyph! Log the first few errors.
1700 static int s_log_count
= 0;
1701 if (s_log_count
< 10)
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());
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());
1721 // Drop through and use index == -1; this will display
1722 // using the empty-box glyph
1726 SWF::TextRecord::GlyphEntry ge
;
1728 ge
.advance
= scale
* rec
.getFont()->get_advance(index
,
1738 float width
= _bounds
.width();
1739 if (x
>= width
- getRightMargin() - PADDING_TWIPS
)
1741 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1742 log_debug("Text in TextField %s exceeds width [ _bounds %s ]",
1743 getTarget(), _bounds
);
1746 // No wrap and no resize: truncate
1747 if (!doWordWrap() && getAutoSize() == AUTOSIZE_NONE
)
1749 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1750 log_debug(" wordWrap=false, autoSize=none");
1752 // Truncate long line, but keep expanding text box
1753 bool newlinefound
= false;
1759 x
+= rec
.getFont()->get_kerning_adjustment(last_code
,
1760 static_cast<int>(code
)) * scale
;
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());
1771 if (code
== 13 || code
== 10)
1773 newlinefound
= true;
1777 int index
= rec
.getFont()->get_glyph_index(
1778 static_cast<boost::uint16_t>(code
), _embedFonts
);
1779 x
+= scale
* rec
.getFont()->get_advance(index
, _embedFonts
);
1782 if (!newlinefound
) break;
1784 else if (doWordWrap()) {
1786 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1787 log_debug(" wordWrap=true");
1790 // Insert newline if there's space or autosize != none
1792 // Close out this stretch of glyphs.
1793 _textRecords
.push_back(rec
);
1795 float previous_x
= x
;
1796 x
= std::max(0, getLeftMargin() + getBlockIndent()) + PADDING_TWIPS
;
1797 y
+= _fontHeight
+ leading
;
1798 if (y
>= _bounds
.height()) {
1802 // Start a new record on the next line.
1807 // TODO : what if m_text_glyph_records is empty ?
1809 assert(!_textRecords
.empty());
1810 SWF::TextRecord
& last_line
= _textRecords
.back();
1812 linestartit
= _line_starts
.begin();
1813 linestartend
= _line_starts
.end();
1814 if (last_space_glyph
== -1)
1816 // Pull the previous glyph down onto the
1818 if (!last_line
.glyphs().empty())
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
1826 const size_t currentPos
= _glyphcount
;
1827 while (linestartit
!= linestartend
&&
1828 *linestartit
+ 1 <= currentPos
)
1832 _line_starts
.insert(linestartit
, currentPos
);
1833 _recordStarts
.push_back(currentPos
);
1836 // Move the previous word down onto the next line.
1838 previous_x
-= last_line
.glyphs()[last_space_glyph
].advance
;
1840 const SWF::TextRecord::Glyphs::size_type lineSize
=
1841 last_line
.glyphs().size();
1842 for (unsigned int i
= last_space_glyph
+ 1; i
< lineSize
;
1845 rec
.addGlyph(last_line
.glyphs()[i
]);
1846 x
+= last_line
.glyphs()[i
].advance
;
1847 previous_x
-= last_line
.glyphs()[i
].advance
;
1849 last_line
.clearGlyphs(lineSize
- last_space_glyph
);
1851 // record the position at the start of this line as
1853 const size_t linestartpos
= _glyphcount
-
1854 rec
.glyphs().size();
1856 while (linestartit
< linestartend
&&
1857 *linestartit
< linestartpos
)
1861 _line_starts
.insert(linestartit
, linestartpos
);
1862 _recordStarts
.push_back(linestartpos
);
1865 align_line(getTextAlignment(), last_line_start_record
, previous_x
);
1867 last_space_glyph
= -1;
1868 last_line_start_record
= _textRecords
.size();
1873 #ifdef GNASH_DEBUG_TEXT_FORMATTING
1874 log_debug(" wordWrap=%d, autoSize=%d", _wordWrap
, _autoSize
);
1882 TextField::getDefinitionVersion() const
1884 // TODO: work out if this correct.
1885 return get_root()->getDefinitionVersion();
1889 TextField::VariableRef
1890 TextField::parseTextVariableRef(const std::string
& variableName
) const
1895 #ifdef DEBUG_DYNTEXT_VARIABLES
1896 log_debug("VariableName: %s", variableName
);
1899 /// Why isn't get_environment const again ?
1900 const as_environment
& env
= const_cast<TextField
*>(this)->get_environment();
1902 as_object
* target
= getObject(env
.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
);
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
);
1922 // find target for the path component
1923 // we use our parent's environment for this
1924 target
= findObject(env
, path
);
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 "
1941 ret
.second
= getURI(getVM(*object()), parsedName
);
1947 TextField::registerTextVariable()
1949 //#define DEBUG_DYNTEXT_VARIABLES 1
1951 #ifdef DEBUG_DYNTEXT_VARIABLES
1952 log_debug("registerTextVariable() called");
1955 if (_text_variable_registered
) {
1959 if (_variable_name
.empty()) {
1960 _text_variable_registered
= true;
1964 VariableRef varRef
= parseTextVariableRef(_variable_name
);
1965 as_object
* target
= varRef
.first
;
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.",
1975 const ObjectURI
& key
= varRef
.second
;
1976 as_object
* obj
= getObject(this);
1977 const int version
= getSWFVersion(*obj
);
1979 // check if the VariableName already has a value,
1980 // in that case update text value
1982 if (target
->get_member(key
, &val
)) {
1983 // TODO: pass environment to to_string ?
1984 setTextValue(utf8::decodeCanonicalString(val
.to_string(), version
));
1986 else if (_textDefined
) {
1987 as_value newVal
= as_value(utf8::encodeCanonicalString(_text
, version
));
1988 target
->set_member(key
, newVal
);
1991 MovieClip
* sprite
= get
<MovieClip
>(target
);
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);
1999 _text_variable_registered
= true;
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.
2007 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
2013 while (it
!= e
&& *it
!= ' ') {
2024 log_error(_("invalid HTML tag"));
2033 // Check for NULL character
2035 log_error(_("found NULL character in htmlText"));
2038 tag
.push_back(std::toupper(*it
));
2041 while (it
!= e
&& *it
== ' ') {
2042 ++it
; //skip over spaces
2058 log_error(_("invalid HTML tag"));
2063 std::string attname
;
2064 std::string attvalue
;
2067 while (it
!= e
&& *it
!= '>') {
2068 while (it
!= e
&& *it
!= '=' && *it
!= ' ') {
2071 log_error(_("found NULL character in htmlText"));
2075 log_error(_("malformed HTML tag, invalid attribute name"));
2082 attname
.push_back(std::toupper(*it
));
2085 while (it
!= e
&& (*it
== ' ' || *it
== '=')) {
2086 ++it
; //skip over spaces and '='
2089 if (it
== e
) return false;
2091 if (q
!= '"' && q
!= '\'') {
2092 // This is not an attribute.
2093 while (it
!= e
) ++it
;
2097 // Advance past attribute opener
2099 while (it
!= e
&& *it
!= q
) {
2102 log_error(_("found NULL character in htmlText"));
2106 attvalue
.push_back(std::toupper(*it
));
2110 if (it
== e
) return false;
2113 while (it
!= e
) ++it
;
2117 // Skip attribute closer.
2120 attributes
.insert(std::make_pair(attname
, attvalue
));
2124 if ((*it
!= ' ') && (*it
!= '/') && (*it
!= '>')) {
2125 log_error(_("malformed HTML tag, invalid attribute value"));
2132 while (it
!= e
&& *it
== ' ') {
2133 ++it
; //skip over spaces
2139 } else if (*it
== '/') {
2149 log_error(_("invalid HTML tag"));
2155 #ifdef GNASH_DEBUG_TEXTFIELDS
2156 log_debug("HTML tag: %s", utf8::encodeCanonicalString(tag
, 7));
2158 log_error(_("I declare this a HTML syntax error"));
2159 return false; //since we did not return already, must be malformed...?
2163 TextField::set_variable_name(const std::string
& newname
)
2165 if (newname
!= _variable_name
) {
2166 _variable_name
= newname
;
2168 // The name was empty or undefined, so there's nothing more to do.
2169 if (_variable_name
.empty()) return;
2171 _text_variable_registered
= false;
2173 #ifdef DEBUG_DYNTEXT_VARIABLES
2174 log_debug("Calling updateText after change of variable name");
2177 // Use the original definition text if this isn't dynamically
2179 if (_tag
) updateText(_tag
->defaultText());
2181 #ifdef DEBUG_DYNTEXT_VARIABLES
2182 log_debug("Calling registerTextVariable after change of variable "
2183 "name and updateText call");
2185 registerTextVariable();
2190 TextField::pointInShape(boost::int32_t x
, boost::int32_t y
) const
2192 const SWFMatrix wm
= getWorldMatrix(*this).invert();
2195 return _bounds
.point_test(lp
.x
, lp
.y
);
2199 TextField::getDrawBorder() const
2205 TextField::setDrawBorder(bool val
)
2207 if (_drawBorder
!= val
) {
2214 TextField::getBorderColor() const
2216 return _borderColor
;
2220 TextField::setBorderColor(const rgba
& col
)
2222 if (_borderColor
!= col
) {
2229 TextField::getDrawBackground() const
2231 return _drawBackground
;
2235 TextField::setDrawBackground(bool val
)
2237 if (_drawBackground
!= val
) {
2239 _drawBackground
= val
;
2244 TextField::getBackgroundColor() const
2246 return _backgroundColor
;
2250 TextField::setBackgroundColor(const rgba
& col
)
2252 if (_backgroundColor
!= col
) {
2254 _backgroundColor
= col
;
2259 TextField::setTextColor(const rgba
& col
)
2261 if (_textColor
!= col
) {
2265 std::for_each(_displayRecords
.begin(), _displayRecords
.end(),
2266 boost::bind(&SWF::TextRecord::setColor
, _1
, _textColor
));
2271 TextField::setEmbedFonts(bool use
)
2273 if (_embedFonts
!= use
) {
2281 TextField::setWordWrap(bool wrap
)
2283 if (_wordWrap
!= wrap
) {
2291 TextField::setLeading(boost::int16_t h
)
2293 if (_leading
!= h
) {
2300 TextField::setUnderlined(bool v
)
2302 if (_underlined
!= v
) {
2309 TextField::setBullet(bool b
)
2317 TextField::setTabStops(const std::vector
<int>& tabStops
)
2319 _tabStops
.resize(tabStops
.size());
2321 for (size_t i
= 0; i
< tabStops
.size(); i
++) {
2322 _tabStops
[i
] = pixelsToTwips(tabStops
[i
]);
2329 TextField::setURL(std::string url
)
2338 TextField::setTarget(std::string target
)
2340 if (_target
!= target
) {
2347 TextField::setDisplay(TextFormatDisplay display
)
2349 if (_display
!= display
) {
2356 TextField::setAlignment(TextAlignment h
)
2358 if (_alignment
!= h
) {
2365 TextField::setIndent(boost::uint16_t h
)
2374 TextField::setBlockIndent(boost::uint16_t h
)
2376 if (_blockIndent
!= h
) {
2383 TextField::setRightMargin(boost::uint16_t h
)
2385 if (_rightMargin
!= h
) {
2392 TextField::setLeftMargin(boost::uint16_t h
)
2394 if (_leftMargin
!= h
) {
2401 TextField::setFontHeight(boost::uint16_t h
)
2403 if (_fontHeight
!= h
) {
2410 TextField::TypeValue
2411 TextField::parseTypeValue(const std::string
& val
)
2413 StringNoCaseEqual cmp
;
2415 if (cmp(val
, "input")) return typeInput
;
2416 if (cmp(val
, "dynamic")) return typeDynamic
;
2422 TextField::typeValueName(TypeValue val
)
2426 //log_debug("typeInput returned as 'input'");
2429 //log_debug("typeDynamic returned as 'dynamic'");
2432 //log_debug("invalid type %d returned as 'invalid'", (int)val);
2439 TextField::setAutoSize(AutoSize val
)
2441 if (val
== _autoSize
) return;
2448 TextField::TextAlignment
2449 TextField::getTextAlignment()
2451 TextAlignment textAlignment
= getAlignment();
2453 switch (_autoSize
) {
2454 case AUTOSIZE_CENTER
:
2455 textAlignment
= ALIGN_CENTER
;
2458 textAlignment
= ALIGN_LEFT
;
2460 case AUTOSIZE_RIGHT
:
2461 textAlignment
= ALIGN_RIGHT
;
2464 // Leave it as it was.
2468 return textAlignment
;
2472 TextField::onChanged()
2474 as_object
* obj
= getObject(this);
2475 callMethod(obj
, NSV::PROP_BROADCAST_MESSAGE
, "onChanged", obj
);
2478 /// This is called by movie_root when focus is applied to this TextField.
2480 /// The return value is true if the TextField can receive focus.
2481 /// The swfdec testsuite suggests that version 5 textfields cannot ever
2484 TextField::handleFocus()
2488 /// Select the entire text on focus.
2489 setSelection(0, _text
.length());
2493 m_cursor
= _text
.size();
2498 /// This is called by movie_root when focus is removed from the
2499 /// current TextField.
2501 TextField::killFocus()
2503 if (!m_has_focus
) return;
2505 m_has_focus
= false;
2506 format_text(); // is this needed ?
2510 TextField::setWidth(double newwidth
)
2512 const SWFRect
& bounds
= getBounds();
2513 _bounds
.set_to_rect(bounds
.get_x_min(),
2515 bounds
.get_x_min() + newwidth
,
2516 bounds
.get_y_max());
2520 TextField::setHeight(double newheight
)
2522 const SWFRect
& bounds
= getBounds();
2523 _bounds
.set_to_rect(bounds
.get_x_min(),
2526 bounds
.get_y_min() + newheight
);
2529 } // namespace gnash
2534 // indent-tabs-mode: t