add macro so new strings get found by xgettext, so they can be translated.
[gnash.git] / libcore / asobj / TextSnapshot_as.cpp
blob02c96a10297cc6b0d7fdbd2b4da8255b3ec6fbd7
1 // TextSnapshot_as.cpp: ActionScript "TextSnapshot" class, for Gnash.
2 //
3 // Copyright (C) 2009, 2010, 2011 Free Software Foundation, Inc.
4 //
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.
9 //
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>
25 #include <algorithm>
27 #include "as_object.h" // for inheritance
28 #include "log.h"
29 #include "fn_call.h"
30 #include "Global_as.h"
31 #include "NativeFunction.h"
32 #include "StaticText.h"
33 #include "DisplayList.h"
34 #include "MovieClip.h"
35 #include "Font.h"
36 #include "swf/TextRecord.h"
37 #include "RGBA.h"
38 #include "GnashNumeric.h"
39 #include "namedStrings.h"
41 namespace gnash {
43 class TextSnapshot_as : public Relay
46 public:
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
59 /// AS return values.
60 TextSnapshot_as(const MovieClip* mc);
62 std::string getText(boost::int32_t start, boost::int32_t end,
63 bool nl) const;
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;
80 protected:
82 virtual void setReachable() const;
84 private:
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.
106 const bool _valid;
108 /// The number of DisplayObjects
110 /// There is no need to store this, but it is quicker than counting
111 /// afresh every time.
112 const size_t _count;
115 // Forward declarations
116 namespace {
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)
142 void
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)
153 _textFields(),
154 _valid(mc),
155 _count(getTextFields(mc, _textFields))
159 void
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;
178 ++field;
180 if (field == _textFields.end()) return;
182 const boost::dynamic_bitset<>& sel = field->first->getSelected();
183 totalChars += sel.size();
184 continue;
186 field->first->setSelected(i - fieldStartIndex, selected);
190 bool
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;
209 ++field;
210 if (field == _textFields.end()) return false;
212 const boost::dynamic_bitset<>& sel = field->first->getSelected();
213 totalChars += sel.size();
214 continue;
217 if (field->first->getSelected().test(i - fieldStartIndex)) return true;
220 return false;
223 void
224 TextSnapshot_as::setReachable() const
226 std::for_each(_textFields.begin(), _textFields.end(), setTextReachable);
229 void
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();
246 j != end; ++j) {
248 const SWF::TextRecord* tr = *j;
249 assert(tr);
251 const SWF::TextRecord::Glyphs& glyphs = tr->glyphs();
252 const SWF::TextRecord::Glyphs::size_type numGlyphs = glyphs.size();
254 if (pos + numGlyphs < start) {
255 pos += numGlyphs;
256 continue;
259 const Font* font = tr->getFont();
260 assert(font);
262 double x = tr->xOffset();
263 for (SWF::TextRecord::Glyphs::const_iterator k = glyphs.begin(),
264 e = glyphs.end(); k != e; ++k) {
266 if (pos < start) {
267 x += k->advance;
268 ++pos;
269 continue;
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);
294 ++pos;
295 x += k->advance;
296 if (pos - start > len) return;
303 void
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();
324 j != end; ++j) {
326 const SWF::TextRecord* tr = *j;
327 assert(tr);
329 const SWF::TextRecord::Glyphs& glyphs = tr->glyphs();
330 const SWF::TextRecord::Glyphs::size_type numGlyphs = glyphs.size();
332 if (pos + numGlyphs < start) {
333 pos += numGlyphs;
334 continue;
337 const Font* font = tr->getFont();
338 assert(font);
340 for (SWF::TextRecord::Glyphs::const_iterator k = glyphs.begin(),
341 e = glyphs.end(); k != e; ++k) {
343 if (pos < start) {
344 ++pos;
345 continue;
348 if (!selectedOnly || selected.test(pos - fieldStartIndex)) {
349 to += font->codeTableLookup(k->index, true);
351 ++pos;
352 if (pos - start == len) return;
358 std::string
359 TextSnapshot_as::getText(boost::int32_t start, boost::int32_t end, bool nl)
360 const
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);
374 return snapshot;
378 std::string
379 TextSnapshot_as::getSelectedText(bool newline) const
381 std::string sel;
383 makeString(sel, newline, true);
384 return sel;
387 boost::int32_t
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;
402 if (ignoreCase) {
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;
413 void
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);
430 namespace {
432 class TextFinder
434 public:
435 TextFinder(TextSnapshot_as::TextFields& fields)
437 _fields(fields),
438 _count(0)
441 void operator()(DisplayObject* ch) {
443 /// This is not tested.
444 if (ch->unloaded()) return;
446 TextSnapshot_as::Records text;
447 StaticText* tf;
448 size_t numChars;
450 if ((tf = ch->getStaticText(text, numChars))) {
451 _fields.push_back(std::make_pair(tf, text));
452 _count += numChars;
456 size_t getCount() const { return _count; }
458 private:
459 TextSnapshot_as::TextFields& _fields;
460 size_t _count;
463 void
464 attachTextSnapshotStaticInterface(as_object& /*o*/)
469 void
470 attachTextSnapshotInterface(as_object& o)
473 const int flags = PropFlags::onlySWF6Up;
475 VM& vm = getVM(o);
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);
488 as_value
489 textsnapshot_getTextRunInfo(const fn_call& fn)
491 TextSnapshot_as* ts = ensure<ThisIsNative<TextSnapshot_as> >(fn);
493 if (!ts->valid()) return as_value();
495 if (fn.nargs != 2) {
496 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);
509 return as_value(ri);
512 as_value
513 textsnapshot_findText(const fn_call& fn)
515 TextSnapshot_as* ts = ensure<ThisIsNative<TextSnapshot_as> >(fn);
517 if (!ts->valid()) return as_value();
519 if (fn.nargs != 3) {
520 IF_VERBOSE_ASCODING_ERRORS(
521 log_aserror(_("TextSnapshot.findText() requires 3 arguments"));
523 return as_value();
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);
536 as_value
537 textsnapshot_getCount(const fn_call& fn)
539 TextSnapshot_as* ts = ensure<ThisIsNative<TextSnapshot_as> >(fn);
541 if (!ts->valid()) return as_value();
543 if (fn.nargs) {
544 IF_VERBOSE_ASCODING_ERRORS(
545 log_aserror(_("TextSnapshot.getCount() takes no arguments"));
547 return as_value();
550 return ts->getCount();
553 /// Returns a boolean value, or undefined if not valid.
554 as_value
555 textsnapshot_getSelected(const fn_call& fn)
557 TextSnapshot_as* ts = ensure<ThisIsNative<TextSnapshot_as> >(fn);
559 if (!ts->valid()) return as_value();
561 if (fn.nargs != 2) {
562 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.
574 as_value
575 textsnapshot_getSelectedText(const fn_call& fn)
577 TextSnapshot_as* ts = ensure<ThisIsNative<TextSnapshot_as> >(fn);
579 if (!ts->valid()) return as_value();
581 if (fn.nargs > 1) {
582 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.
591 as_value
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"));
603 return as_value();
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.
617 as_value
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__);
625 return as_value();
628 /// Returns void.
629 as_value
630 textsnapshot_setSelectColor(const fn_call& fn)
633 TextSnapshot_as* ts = ensure<ThisIsNative<TextSnapshot_as> >(fn);
634 UNUSED(ts);
636 log_unimpl(__FUNCTION__);
637 return as_value();
641 /// Returns void.
642 as_value
643 textsnapshot_setSelected(const fn_call& fn)
645 TextSnapshot_as* ts = ensure<ThisIsNative<TextSnapshot_as> >(fn);
647 if (fn.nargs < 2 || fn.nargs > 3) {
648 return as_value();
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);
660 return as_value();
663 as_value
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));
671 return as_value();
674 size_t
675 getTextFields(const MovieClip* mc, TextSnapshot_as::TextFields& fields)
677 if (mc) {
678 const DisplayList& dl = mc->getDisplayList();
680 TextFinder finder(fields);
681 dl.visitAll(finder);
682 return finder.getCount();
684 return 0;
687 void
688 setTextReachable(const TextSnapshot_as::TextFields::value_type& vt)
690 vt.first->setReachable();
693 } // anonymous namespace
694 } // gnash namespace
696 // local Variables:
697 // mode: C++
698 // indent-tabs-mode: t
699 // End: