1 // TextSnapshot_as.cpp: ActionScript "TextSnapshot" class, for Gnash.
3 // Copyright (C) 2009, 2010, 2011 Free Software Foundation, Inc.
5 // This program is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation; either version 3 of the License, or
8 // (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21 #include "TextSnapshot_as.h"
23 #include <boost/algorithm/string/compare.hpp>
24 #include <boost/dynamic_bitset.hpp>
27 #include "as_object.h" // for inheritance
30 #include "Global_as.h"
31 #include "NativeFunction.h"
32 #include "StaticText.h"
33 #include "DisplayList.h"
34 #include "MovieClip.h"
36 #include "swf/TextRecord.h"
38 #include "GnashNumeric.h"
39 #include "namedStrings.h"
43 class TextSnapshot_as
: public Relay
48 typedef std::vector
<const SWF::TextRecord
*> Records
;
50 /// Should remain in the order of insertion
51 /// We should only ever iterate from begin to end, so there's no
52 /// performance issue.
53 typedef std::vector
<std::pair
<StaticText
*, Records
> > TextFields
;
55 /// Construct a TextSnapshot_as from a MovieClip.
57 /// @param mc The MovieClip to search for static text. If 0, the
58 /// TextSnapshot is invalid, which should be reflected in
60 TextSnapshot_as(const MovieClip
* mc
);
62 std::string
getText(boost::int32_t start
, boost::int32_t end
,
65 boost::int32_t findText(boost::int32_t start
, const std::string
& text
,
66 bool ignoreCase
) const;
68 bool valid() const { return _valid
; }
70 size_t getCount() const { return _count
; }
72 void setSelected(size_t start
, size_t end
, bool selected
);
74 bool getSelected(size_t start
, size_t end
) const;
76 std::string
getSelectedText(bool newlines
) const;
78 void getTextRunInfo(size_t start
, size_t end
, as_object
& ri
) const;
82 virtual void setReachable() const;
86 /// Generate a string from the TextRecords in this TextSnapshot.
88 /// @param to The string to write to
89 /// @param newline If true, newlines are written after every
90 /// StaticText in this TextSnapshot
91 /// @param selectedOnly Only write DisplayObject that are selected to.
92 /// @param start The start index
93 /// @param len The number of StaticText DisplayObjects to traverse.
94 /// This includes non-selected DisplayObjects.
95 void makeString(std::string
& to
, bool newline
= false,
96 bool selectedOnly
= false,
97 std::string::size_type start
= 0,
98 std::string::size_type len
= std::string::npos
) const;
100 TextFields _textFields
;
102 /// Whether the object is valid, i.e. it was constructed with a MovieClip.
104 /// This should be deducible from another member, but since there seems
105 /// to be no point in storing the MovieClip this bool will do instead.
108 /// The number of DisplayObjects
110 /// There is no need to store this, but it is quicker than counting
111 /// afresh every time.
115 // Forward declarations
118 void attachTextSnapshotStaticInterface(as_object
& o
);
120 as_value
textsnapshot_findText(const fn_call
& fn
);
121 as_value
textsnapshot_getCount(const fn_call
& fn
);
122 as_value
textsnapshot_getSelected(const fn_call
& fn
);
123 as_value
textsnapshot_getSelectedText(const fn_call
& fn
);
124 as_value
textsnapshot_getTextRunInfo(const fn_call
& fn
);
125 as_value
textsnapshot_getText(const fn_call
& fn
);
126 as_value
textsnapshot_hitTestTextNearPos(const fn_call
& fn
);
127 as_value
textsnapshot_setSelectColor(const fn_call
& fn
);
128 as_value
textsnapshot_setSelected(const fn_call
& fn
);
129 as_value
textsnapshot_ctor(const fn_call
& fn
);
131 void attachTextSnapshotInterface(as_object
& o
);
133 size_t getTextFields(const MovieClip
* mc
,
134 TextSnapshot_as::TextFields
& fields
);
136 void setTextReachable(const TextSnapshot_as::TextFields::value_type
& vt
);
141 // extern (used by Global.cpp)
143 textsnapshot_class_init(as_object
& where
, const ObjectURI
& uri
)
145 registerBuiltinClass(where
, textsnapshot_ctor
, attachTextSnapshotInterface
,
146 attachTextSnapshotStaticInterface
, uri
);
149 /// The member _textFields is initialized here unnecessarily to show
150 /// that it is constructed before it is used.
151 TextSnapshot_as::TextSnapshot_as(const MovieClip
* mc
)
155 _count(getTextFields(mc
, _textFields
))
160 TextSnapshot_as::setSelected(size_t start
, size_t end
, bool selected
)
162 /// If there are no TextFields, there is nothing to do.
163 if (_textFields
.empty()) return;
165 start
= std::min(start
, _count
);
166 end
= std::min(end
, _count
);
168 TextFields::const_iterator field
= _textFields
.begin();
170 size_t totalChars
= field
->first
->getSelected().size();
171 size_t fieldStartIndex
= 0;
173 for (size_t i
= start
; i
< end
; ++i
) {
175 /// Find the field containing the start index.
176 while (totalChars
<= i
) {
177 fieldStartIndex
= totalChars
;
180 if (field
== _textFields
.end()) return;
182 const boost::dynamic_bitset
<>& sel
= field
->first
->getSelected();
183 totalChars
+= sel
.size();
186 field
->first
->setSelected(i
- fieldStartIndex
, selected
);
191 TextSnapshot_as::getSelected(size_t start
, size_t end
) const
194 if (_textFields
.empty()) return false;
196 start
= std::min(start
, _count
);
197 end
= std::min(end
, _count
);
199 TextFields::const_iterator field
= _textFields
.begin();
201 size_t totalChars
= field
->first
->getSelected().size();
202 size_t fieldStartIndex
= 0;
204 for (size_t i
= start
; i
< end
; ++i
) {
206 /// Find the field containing the start index.
207 while (totalChars
<= i
) {
208 fieldStartIndex
= totalChars
;
210 if (field
== _textFields
.end()) return false;
212 const boost::dynamic_bitset
<>& sel
= field
->first
->getSelected();
213 totalChars
+= sel
.size();
217 if (field
->first
->getSelected().test(i
- fieldStartIndex
)) return true;
224 TextSnapshot_as::setReachable() const
226 std::for_each(_textFields
.begin(), _textFields
.end(), setTextReachable
);
230 TextSnapshot_as::getTextRunInfo(size_t start
, size_t end
, as_object
& ri
) const
232 std::string::size_type pos
= 0;
234 std::string::size_type len
= end
- start
;
236 for (TextFields::const_iterator field
= _textFields
.begin(),
237 e
= _textFields
.end(); field
!= e
; ++field
) {
239 const Records
& rec
= field
->second
;
240 const SWFMatrix
& mat
= getMatrix(*field
->first
);
241 const boost::dynamic_bitset
<>& selected
= field
->first
->getSelected();
243 const std::string::size_type fieldStartIndex
= pos
;
245 for (Records::const_iterator j
= rec
.begin(), end
= rec
.end();
248 const SWF::TextRecord
* tr
= *j
;
251 const SWF::TextRecord::Glyphs
& glyphs
= tr
->glyphs();
252 const SWF::TextRecord::Glyphs::size_type numGlyphs
= glyphs
.size();
254 if (pos
+ numGlyphs
< start
) {
259 const Font
* font
= tr
->getFont();
262 double x
= tr
->xOffset();
263 for (SWF::TextRecord::Glyphs::const_iterator k
= glyphs
.begin(),
264 e
= glyphs
.end(); k
!= e
; ++k
) {
272 as_object
* el
= new as_object(getGlobal(ri
));
274 el
->init_member("indexInRun", pos
);
275 el
->init_member("selected",
276 selected
.test(pos
- fieldStartIndex
));
277 el
->init_member("font", font
->name());
278 el
->init_member("color", tr
->color().toRGBA());
279 el
->init_member("height", twipsToPixels(tr
->textHeight()));
281 const double factor
= 65536.0;
282 el
->init_member("matrix_a", mat
.a() / factor
);
283 el
->init_member("matrix_b", mat
.b() / factor
);
284 el
->init_member("matrix_c", mat
.c() / factor
);
285 el
->init_member("matrix_d", mat
.d() / factor
);
287 const double xpos
= twipsToPixels(mat
.tx() + x
);
288 const double ypos
= twipsToPixels(mat
.ty() + tr
->yOffset());
289 el
->init_member("matrix_tx", xpos
);
290 el
->init_member("matrix_ty", ypos
);
292 callMethod(&ri
, NSV::PROP_PUSH
, el
);
296 if (pos
- start
> len
) return;
304 TextSnapshot_as::makeString(std::string
& to
, bool newline
, bool selectedOnly
,
305 std::string::size_type start
, std::string::size_type len
) const
308 std::string::size_type pos
= 0;
310 for (TextFields::const_iterator field
= _textFields
.begin(),
311 e
= _textFields
.end(); field
!= e
; ++field
)
313 // When newlines are requested, insert one after each individual
314 // text field is processed.
315 if (newline
&& pos
> start
) to
+= '\n';
317 const Records
& records
= field
->second
;
318 const boost::dynamic_bitset
<>& selected
= field
->first
->getSelected();
320 /// Remember the position at the beginning of the StaticText.
321 const std::string::size_type fieldStartIndex
= pos
;
323 for (Records::const_iterator j
= records
.begin(), end
= records
.end();
326 const SWF::TextRecord
* tr
= *j
;
329 const SWF::TextRecord::Glyphs
& glyphs
= tr
->glyphs();
330 const SWF::TextRecord::Glyphs::size_type numGlyphs
= glyphs
.size();
332 if (pos
+ numGlyphs
< start
) {
337 const Font
* font
= tr
->getFont();
340 for (SWF::TextRecord::Glyphs::const_iterator k
= glyphs
.begin(),
341 e
= glyphs
.end(); k
!= e
; ++k
) {
348 if (!selectedOnly
|| selected
.test(pos
- fieldStartIndex
)) {
349 to
+= font
->codeTableLookup(k
->index
, true);
352 if (pos
- start
== len
) return;
359 TextSnapshot_as::getText(boost::int32_t start
, boost::int32_t end
, bool nl
)
363 // Start is always moved to between 0 and len - 1.
364 start
= std::max
<boost::int32_t>(start
, 0);
365 start
= std::min
<boost::int32_t>(start
, _count
- 1);
367 // End is always moved to between start and end. We don't really care
368 // about the end of the string.
369 end
= std::max(start
+ 1, end
);
371 std::string snapshot
;
372 makeString(snapshot
, nl
, false, start
, end
- start
);
379 TextSnapshot_as::getSelectedText(bool newline
) const
383 makeString(sel
, newline
, true);
388 TextSnapshot_as::findText(boost::int32_t start
, const std::string
& text
,
389 bool ignoreCase
) const
392 if (start
< 0 || text
.empty()) return -1;
394 std::string snapshot
;
395 makeString(snapshot
);
397 const std::string::size_type len
= snapshot
.size();
399 // Don't try to search if start is past the end of the string.
400 if (len
< static_cast<size_t>(start
)) return -1;
403 std::string::const_iterator it
= std::search(snapshot
.begin() + start
,
404 snapshot
.end(), text
.begin(), text
.end(), boost::is_iequal());
405 return (it
== snapshot
.end()) ? -1 : it
- snapshot
.begin();
408 std::string::size_type pos
= snapshot
.find(text
, start
);
409 return (pos
== std::string::npos
) ? -1 : pos
;
414 registerTextSnapshotNative(as_object
& global
)
416 VM
& vm
= getVM(global
);
417 vm
.registerNative(textsnapshot_ctor
, 1067, 0);
418 vm
.registerNative(textsnapshot_getCount
, 1067, 1);
419 vm
.registerNative(textsnapshot_setSelected
, 1067, 2);
420 vm
.registerNative(textsnapshot_getSelected
, 1067, 3);
421 vm
.registerNative(textsnapshot_getText
, 1067, 4);
422 vm
.registerNative(textsnapshot_getSelectedText
, 1067, 5);
423 vm
.registerNative(textsnapshot_hitTestTextNearPos
, 1067, 6);
424 vm
.registerNative(textsnapshot_findText
, 1067, 7);
425 vm
.registerNative(textsnapshot_setSelectColor
, 1067, 8);
426 vm
.registerNative(textsnapshot_getTextRunInfo
, 1067, 9);
435 TextFinder(TextSnapshot_as::TextFields
& fields
)
441 void operator()(DisplayObject
* ch
) {
443 /// This is not tested.
444 if (ch
->unloaded()) return;
446 TextSnapshot_as::Records text
;
450 if ((tf
= ch
->getStaticText(text
, numChars
))) {
451 _fields
.push_back(std::make_pair(tf
, text
));
456 size_t getCount() const { return _count
; }
459 TextSnapshot_as::TextFields
& _fields
;
464 attachTextSnapshotStaticInterface(as_object
& /*o*/)
470 attachTextSnapshotInterface(as_object
& o
)
473 const int flags
= PropFlags::onlySWF6Up
;
476 o
.init_member("getCount", vm
.getNative(1067, 1), flags
);
477 o
.init_member("setSelected", vm
.getNative(1067, 2), flags
);
478 o
.init_member("getSelected", vm
.getNative(1067, 3), flags
);
479 o
.init_member("getText", vm
.getNative(1067, 4), flags
);
480 o
.init_member("getSelectedText", vm
.getNative(1067, 5), flags
);
481 o
.init_member("hitTestTextNearPos", vm
.getNative(1067, 6), flags
);
482 o
.init_member("findText", vm
.getNative(1067, 7), flags
);
483 o
.init_member("setSelectColor", vm
.getNative(1067, 8), flags
);
484 o
.init_member("getTextRunInfo", vm
.getNative(1067, 9), flags
);
489 textsnapshot_getTextRunInfo(const fn_call
& fn
)
491 TextSnapshot_as
* ts
= ensure
<ThisIsNative
<TextSnapshot_as
> >(fn
);
493 if (!ts
->valid()) return as_value();
499 const size_t start
= std::max
<boost::int32_t>(0,
500 toInt(fn
.arg(0), getVM(fn
)));
501 const size_t end
= std::max
<boost::int32_t>(start
+ 1,
502 toInt(fn
.arg(1), getVM(fn
)));
504 Global_as
& gl
= getGlobal(fn
);
505 as_object
* ri
= gl
.createArray();
507 ts
->getTextRunInfo(start
, end
, *ri
);
513 textsnapshot_findText(const fn_call
& fn
)
515 TextSnapshot_as
* ts
= ensure
<ThisIsNative
<TextSnapshot_as
> >(fn
);
517 if (!ts
->valid()) return as_value();
520 IF_VERBOSE_ASCODING_ERRORS(
521 log_aserror(_("TextSnapshot.findText() requires 3 arguments"));
526 boost::int32_t start
= toInt(fn
.arg(0), getVM(fn
));
527 const std::string
& text
= fn
.arg(1).to_string();
529 /// Yes, the pp is case-insensitive by default. We don't write
530 /// functions like that here.
531 const bool ignoreCase
= !toBool(fn
.arg(2), getVM(fn
));
533 return ts
->findText(start
, text
, ignoreCase
);
537 textsnapshot_getCount(const fn_call
& fn
)
539 TextSnapshot_as
* ts
= ensure
<ThisIsNative
<TextSnapshot_as
> >(fn
);
541 if (!ts
->valid()) return as_value();
544 IF_VERBOSE_ASCODING_ERRORS(
545 log_aserror(_("TextSnapshot.getCount() takes no arguments"));
550 return ts
->getCount();
553 /// Returns a boolean value, or undefined if not valid.
555 textsnapshot_getSelected(const fn_call
& fn
)
557 TextSnapshot_as
* ts
= ensure
<ThisIsNative
<TextSnapshot_as
> >(fn
);
559 if (!ts
->valid()) return as_value();
565 const size_t start
= std::max
<boost::int32_t>(0,
566 toInt(fn
.arg(0), getVM(fn
)));
567 const size_t end
= std::max
<boost::int32_t>(start
+ 1,
568 toInt(fn
.arg(1), getVM(fn
)));
570 return as_value(ts
->getSelected(start
, end
));
573 /// Returns a string, or undefined if not valid.
575 textsnapshot_getSelectedText(const fn_call
& fn
)
577 TextSnapshot_as
* ts
= ensure
<ThisIsNative
<TextSnapshot_as
> >(fn
);
579 if (!ts
->valid()) return as_value();
585 const bool newlines
= fn
.nargs
? toBool(fn
.arg(0), getVM(fn
)) : false;
587 return as_value(ts
->getSelectedText(newlines
));
590 /// Returns a string, or undefined if not valid.
592 textsnapshot_getText(const fn_call
& fn
)
594 TextSnapshot_as
* ts
= ensure
<ThisIsNative
<TextSnapshot_as
> >(fn
);
596 if (!ts
->valid()) return as_value();
598 if (fn
.nargs
< 2 || fn
.nargs
> 3)
600 IF_VERBOSE_ASCODING_ERRORS(
601 log_aserror(_("TextSnapshot.getText requires exactly 2 arguments"));
606 const boost::int32_t start
= toInt(fn
.arg(0), getVM(fn
));
607 const boost::int32_t end
= toInt(fn
.arg(1), getVM(fn
));
609 const bool newline
= (fn
.nargs
> 2) ? toBool(fn
.arg(2), getVM(fn
)) : false;
611 return ts
->getText(start
, end
, newline
);
616 /// Returns bool, or undefined if not valid.
618 textsnapshot_hitTestTextNearPos(const fn_call
& fn
)
620 TextSnapshot_as
* ts
= ensure
<ThisIsNative
<TextSnapshot_as
> >(fn
);
622 if (!ts
->valid()) return as_value();
624 log_unimpl(__FUNCTION__
);
630 textsnapshot_setSelectColor(const fn_call
& fn
)
633 TextSnapshot_as
* ts
= ensure
<ThisIsNative
<TextSnapshot_as
> >(fn
);
636 log_unimpl(__FUNCTION__
);
643 textsnapshot_setSelected(const fn_call
& fn
)
645 TextSnapshot_as
* ts
= ensure
<ThisIsNative
<TextSnapshot_as
> >(fn
);
647 if (fn
.nargs
< 2 || fn
.nargs
> 3) {
651 const size_t start
= std::max
<boost::int32_t>(0,
652 toInt(fn
.arg(0), getVM(fn
)));
653 const size_t end
= std::max
<boost::int32_t>(start
,
654 toInt(fn
.arg(1), getVM(fn
)));
656 const bool selected
= (fn
.nargs
> 2) ? toBool(fn
.arg(2), getVM(fn
)) : true;
658 ts
->setSelected(start
, end
, selected
);
664 textsnapshot_ctor(const fn_call
& fn
)
666 as_object
* ptr
= ensure
<ValidThis
>(fn
);
668 MovieClip
* mc
= (fn
.nargs
== 1) ? fn
.arg(0).toMovieClip() : 0;
670 ptr
->setRelay(new TextSnapshot_as(mc
));
675 getTextFields(const MovieClip
* mc
, TextSnapshot_as::TextFields
& fields
)
678 const DisplayList
& dl
= mc
->getDisplayList();
680 TextFinder
finder(fields
);
682 return finder
.getCount();
688 setTextReachable(const TextSnapshot_as::TextFields::value_type
& vt
)
690 vt
.first
->setReachable();
693 } // anonymous namespace
698 // indent-tabs-mode: t