root/libcore/asobj/TextFormat_as.cpp

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. alignSet
  2. displaySet
  3. registerTextFormatNative
  4. textformat_class_init
  5. textformat_new
  6. textformat_display
  7. textformat_tabStops
  8. textformat_color
  9. textformat_align
  10. textformat_getTextExtent
  11. attachTextFormatInterface
  12. parseDisplayString
  13. getAlignString
  14. getDisplayString

// textformat.cpp:  ActionScript text formatting decorators, for Gnash.
// 
//   Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010,
//   2011 Free Software Foundation, Inc
// 
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
// 

#include "TextFormat_as.h"

#include <boost/optional.hpp>

#include "log.h"
#include "fn_call.h"
#include "Global_as.h"
#include "NativeFunction.h" 
#include "namedStrings.h"
#include "VM.h"
#include "RGBA.h" 
#include "StringPredicates.h"
#include "smart_ptr.h" 
#include "GnashNumeric.h"
#include "Array_as.h"
#include "fontlib.h"
#include "Font.h"


namespace gnash {

// Functions and templates for reducing code duplication.
namespace {

/// Set functors may need access to fn_call resources, e.g. SWF version.
//
/// Currently only as_value::to_string() needs this, but in future other
/// conversions may follow.
struct SetBase
{
    SetBase(const fn_call& fn) : _fn(fn) {}
    const fn_call& fn() const {
        return _fn;
    }
private:
    const fn_call& _fn;
};

/// Convert pixels to twips, treating negative values as 0.
struct
PositiveTwips : SetBase
{
    PositiveTwips(const fn_call& fn) : SetBase(fn) {}
    int operator()(const as_value& val) const {
        return pixelsToTwips(std::max<int>(toInt(val, getVM(fn())), 0));
    }
};

/// Convert an argument from a number of pixels into twips.
struct
PixelsToTwips : SetBase
{
    PixelsToTwips(const fn_call& fn) : SetBase(fn) {}
    boost::int32_t operator()(const as_value& val) const {
        return pixelsToTwips(toNumber(val, getVM(fn())));
    }
};

/// Convert the as_value to a boolean.
struct
ToBool : SetBase
{
    ToBool(const fn_call& fn) : SetBase(fn) {}
    bool operator()(const as_value& val) const {
        return toBool(val, getVM(fn()));
    }
};

/// Convert the as_value to a string.
struct
ToString : SetBase
{
    ToString(const fn_call& fn) : SetBase(fn) {}
    std::string operator()(const as_value& val) const {
        return val.to_string(getSWFVersion(fn()));
    }
};


/// Get functors.
//
/// Conversions and processing are done when setting, so these functors should
/// be relatively simple.

/// Do nothing, i.e. return exactly the same argument that was passed.
struct
Nothing
{
    template<typename T> const T& operator()(const T& val) const {
        return val;
    }
};

/// Convert internal twip values to pixel values for ActionScript.
struct
TwipsToPixels
{
    template<typename T> double operator()(const T& t) const {
        return twipsToPixels(t);
    }
};

/// Produce a function to set a TextFormat property
//
/// @tparam T       The type of the Relay (should be TextFormat_as)
/// @tparam U       The return type of the function to be called (as C++ can't
///                 yet work out what it is)
/// @tparam F       The function to call to store the value.
/// @tparam P       A function object to be applied to the argument before
///                 storing the value.
template<typename T, typename U, void(T::*F)(const boost::optional<U>&), typename P>
struct Set
{
    static as_value set(const fn_call& fn) {

        T* relay = ensure<ThisIsNative<T> >(fn);

        if (!fn.nargs) return as_value();

        const as_value& arg = fn.arg(0);
        // Undefined doesn't do anything.

        if (arg.is_undefined() || arg.is_null()) {
            (relay->*F)(boost::optional<U>());
            return as_value();
        }

        // The function P takes care of converting the argument to the
        // required type.
        (relay->*F)(P(fn)(arg));
        return as_value();
    }

};

/// Produce a function to get a TextFormat property
//
/// @tparam T       The type of the Relay (should be TextFormat_as)
/// @tparam U       The return type of the function to be called (as C++ can't
///                 yet work out what it is)
/// @tparam F       The function to call to retrieve the value.
/// @tparam P       A function object to be applied to the argument before
///                 returning the value.
template<typename T, typename U, const boost::optional<U>&(T::*F)() const,
    typename P = Nothing>
struct Get
{
    static as_value get(const fn_call& fn) {
        T* relay = ensure<ThisIsNative<T> >(fn);
        const boost::optional<U>& opt = (relay->*F)();
                if (opt) return as_value(P()(*opt));
                
        as_value null;
        null.set_null();
        return null;
    }
};

/// Function object for Array handling.
class
PushToVector
{
public:
    PushToVector(std::vector<int>& v, const fn_call& fn) : _v(v), _fn(fn) {}
    void operator()(const as_value& val) {
        _v.push_back(toNumber(val, getVM(_fn)));
    }
private:
    std::vector<int>& _v;
    const fn_call& _fn;
};


}

namespace {

    as_value textformat_new(const fn_call& fn);
    void attachTextFormatInterface(as_object& o);
    const char* getAlignString(TextField::TextAlignment a);
        const char* getDisplayString(TextField::TextFormatDisplay a);
        TextField::TextFormatDisplay parseDisplayString(const std::string& display);

    /// Align works a bit differently, so is currently not a template.
    as_value textformat_align(const fn_call& fn);

    /// Display is never null, so not a template.
        as_value textformat_display(const fn_call& fn);
        as_value textformat_tabStops(const fn_call& fn);
        as_value textformat_color(const fn_call& fn);
        as_value textformat_getTextExtent(const fn_call& fn);

}

TextFormat_as::TextFormat_as()
    :
    _display(TextField::TEXTFORMAT_BLOCK)
{
}



void
TextFormat_as::alignSet(const std::string& align) 
{
    StringNoCaseEqual cmp;
    if (cmp(align, "left")) alignSet(TextField::ALIGN_LEFT);
    if (cmp(align, "center")) alignSet(TextField::ALIGN_CENTER);
    if (cmp(align, "right")) alignSet(TextField::ALIGN_RIGHT);
    if (cmp(align, "justify")) alignSet(TextField::ALIGN_JUSTIFY);
}

void
TextFormat_as::displaySet(const std::string& display)
{
        displaySet(parseDisplayString(display));
}

void
registerTextFormatNative(as_object& o)
{
    VM& vm = getVM(o);
    
    // TODO: find out native accessors for kerning, letterSpacing.
    // TODO: these functions are probably split into getters and setters
    // instead of one function for both. Needs testing.
    vm.registerNative(textformat_new, 110, 0);
    
    vm.registerNative(
            Get<const TextFormat_as, std::string, &TextFormat_as::font>::get,
            110, 1);
    vm.registerNative(
            Set<TextFormat_as, std::string, &TextFormat_as::fontSet,
            ToString>::set, 
            110, 2);
    
    vm.registerNative(
            Get<const TextFormat_as, boost::uint16_t,
            &TextFormat_as::size, TwipsToPixels>::get,
            110, 3);
    vm.registerNative(
            Set<TextFormat_as, boost::uint16_t, &TextFormat_as::sizeSet,
            PixelsToTwips>::set, 
            110, 4);
    
    vm.registerNative(textformat_color, 110, 5);
    vm.registerNative(textformat_color, 110, 6);
    
    vm.registerNative(
            Get<const TextFormat_as, std::string, &TextFormat_as::url>::get,
            110, 7);
    vm.registerNative(
            Set<TextFormat_as, std::string, &TextFormat_as::urlSet,
            ToString>::set, 
            110, 8);
    
    vm.registerNative(
            Get<const TextFormat_as, std::string, &TextFormat_as::target>::get,
            110, 9);
    vm.registerNative(
            Set<TextFormat_as, std::string, &TextFormat_as::targetSet,
            ToString>::set, 
            110, 10);
    
    vm.registerNative(
            Get<const TextFormat_as, bool, &TextFormat_as::bold>::get,
            110, 11);
    vm.registerNative(
            Set<TextFormat_as, bool, &TextFormat_as::boldSet,
            ToBool>::set, 
            110, 12);
    
    vm.registerNative(
            Get<const TextFormat_as, bool, &TextFormat_as::italic>::get,
            110, 13);
    vm.registerNative(
            Set<TextFormat_as, bool, &TextFormat_as::italicSet,
            ToBool>::set, 
            110, 14);
    
    vm.registerNative(
            Get<const TextFormat_as, bool, &TextFormat_as::underlined>::get,
            110, 15);
    vm.registerNative(
            Set<TextFormat_as, bool, &TextFormat_as::underlinedSet,
            ToBool>::set, 
            110, 16);
    
    vm.registerNative(textformat_align, 110, 17);
    vm.registerNative(textformat_align, 110, 18);

    vm.registerNative(
            Get<const TextFormat_as, boost::uint16_t,
            &TextFormat_as::leftMargin, TwipsToPixels>::get,
            110, 19);
    vm.registerNative(
            Set<TextFormat_as, boost::uint16_t, &TextFormat_as::leftMarginSet,
            PositiveTwips>::set, 
            110, 20);

    vm.registerNative(
            Get<const TextFormat_as, boost::uint16_t,
            &TextFormat_as::rightMargin, TwipsToPixels>::get,
            110, 21);
    vm.registerNative(
            Set<TextFormat_as, boost::uint16_t, &TextFormat_as::rightMarginSet,
            PositiveTwips>::set, 
            110, 22);

    vm.registerNative(
            Get<const TextFormat_as, boost::uint16_t,
            &TextFormat_as::indent,
            TwipsToPixels>::get, 110, 23);
    vm.registerNative(
            Set<TextFormat_as, boost::uint16_t, &TextFormat_as::indentSet,
            PositiveTwips>::set, 
            110, 24);
    
    vm.registerNative(
            Get<const TextFormat_as, boost::uint16_t,
            &TextFormat_as::leading,
            TwipsToPixels>::get, 110, 25);
    vm.registerNative(
            Set<TextFormat_as, boost::uint16_t, &TextFormat_as::leadingSet,
            PositiveTwips>::set, 
            110, 26);

    vm.registerNative(
            Get<const TextFormat_as, boost::uint32_t,
            &TextFormat_as::blockIndent,
            TwipsToPixels>::get, 110, 27);
    vm.registerNative(
            Set<TextFormat_as, boost::uint32_t, &TextFormat_as::blockIndentSet,
            PositiveTwips>::set,
            110, 28);

    vm.registerNative(textformat_tabStops, 110, 29);
    vm.registerNative(textformat_tabStops, 110, 30);

    vm.registerNative(
            Get<const TextFormat_as, bool, &TextFormat_as::bullet,
            Nothing>::get, 110, 31);
    vm.registerNative(
            Set<TextFormat_as, bool, &TextFormat_as::bulletSet,
            ToBool>::set, 
            110, 32);

    vm.registerNative(textformat_getTextExtent, 110, 33);
}

// extern (used by Global.cpp)
void
textformat_class_init(as_object& global, const ObjectURI& uri)
{

    Global_as& gl = getGlobal(global);
    as_object* proto = createObject(gl);;
    as_object* cl = gl.createClass(&textformat_new, proto);

        global.init_member(uri, cl, as_object::DefaultFlags);

}


namespace {

/// new TextFormat([font, [size, [color, [bold, [italic, [underline, [url, [target, [align,[leftMargin, [rightMargin, [indent, [leading]]]]]]]]]]]]])
//
/// This is a native function responsible for:
/// 1. attaching properties to TextFormat.prototype
/// 2. adding a getTextExtent member to the constructed object
/// 3. attaching the TextFormat_as relay object.
/// 4. setting the appropriate native properties of TextFormat_as
as_value
textformat_new(const fn_call& fn)
{

    as_object* obj = ensure<ValidThis>(fn);

    std::auto_ptr<TextFormat_as> tf(new TextFormat_as);

        const size_t args = fn.nargs;
        
        switch (args)
        {
            default:
                log_error(_("Too many args (%d) passed to TextFormat"), args);
            case 13:
                tf->leadingSet(pixelsToTwips(toInt(fn.arg(12), getVM(fn))));
            case 12:
                tf->indentSet(pixelsToTwips(toInt(fn.arg(11), getVM(fn))));
            case 11:
                tf->rightMarginSet(pixelsToTwips(toInt(fn.arg(10), getVM(fn))));
            case 10:
                tf->leftMarginSet(pixelsToTwips(toInt(fn.arg(9), getVM(fn))));
            case 9:
                tf->alignSet(fn.arg(8).to_string());
            case 8:
                tf->targetSet(fn.arg(7).to_string());
            case 7:
                tf->urlSet(fn.arg(6).to_string());
            case 6:
                tf->underlinedSet(toBool(fn.arg(5), getVM(fn)));
            case 5:
                tf->italicSet(toBool(fn.arg(4), getVM(fn)));
            case 4:
                tf->boldSet(toBool(fn.arg(3), getVM(fn)));
            case 3:
            {
                rgba col;
                col.parseRGB(toInt(fn.arg(2), getVM(fn)));
                tf->colorSet(col);
            }
            case 2:
                tf->sizeSet(pixelsToTwips(toInt(fn.arg(1), getVM(fn))));
            case 1:
                tf->fontSet(fn.arg(0).to_string());
                break;
            case 0:
                // What happens here?
                break;
        }
        
    obj->setRelay(tf.release());
    as_object* proto = obj->get_prototype();
    if (proto) attachTextFormatInterface(*proto);

    const int flags = 0;
    
    // This is a weird function with no children.
    VM& vm = getVM(fn);
    NativeFunction* gte = vm.getNative(110, 33);
    gte->clearProperties();
        obj->init_member("getTextExtent", gte, flags);

        return as_value();
}

as_value
textformat_display(const fn_call& fn)
{
    TextFormat_as* relay = ensure<ThisIsNative<TextFormat_as> >(fn);

        as_value ret;

        if ( fn.nargs == 0 ) // getter
        {
        ret.set_string(getDisplayString(relay->display()));
        }
        else // setter
        {
                relay->displaySet(fn.arg(0).to_string());
        }

        return ret;
}

as_value
textformat_tabStops(const fn_call& fn)
{
    TextFormat_as* relay = ensure<ThisIsNative<TextFormat_as> >(fn);
        
    if (!fn.nargs) {
                LOG_ONCE( log_unimpl("Getter for textformat_tabStops") );
        as_value null;
        null.set_null();
        return null;
        }
        
    as_object* arg = toObject(fn.arg(0), getVM(fn));
    if (!arg) return as_value();

        std::vector<int> tabStops;

    PushToVector pv(tabStops, fn);
    foreachArray(*arg, pv);

    relay->tabStopsSet(tabStops);
        
        return as_value();
}

as_value
textformat_color(const fn_call& fn)
{
    TextFormat_as* relay = ensure<ThisIsNative<TextFormat_as> >(fn);

        as_value ret;

        if (fn.nargs == 0) {
                if (relay->color()) ret.set_double(relay->color()->toRGB());
                else ret.set_null();
        }
        else {
                rgba newcolor;
                newcolor.parseRGB(toInt(fn.arg(0), getVM(fn)));
                relay->colorSet(newcolor);
        }

        return ret;
}

as_value
textformat_align(const fn_call& fn)
{
    TextFormat_as* relay = ensure<ThisIsNative<TextFormat_as> >(fn);

    as_value ret;

    if (fn.nargs == 0) {
        if (relay->align()) {
            ret.set_string(getAlignString(*relay->align()));
        }
        else ret.set_null();
    }
    else // setter
    {
        relay->alignSet(fn.arg(0).to_string());
    }

    return ret;
}


/// Return various dimensions of a theoretical run of text
//
/// The TextFormat's format values are used to calculate what the dimensions
/// of a TextField would be if it contained the given text.
//
/// This may never apply to embedded fonts. There is no way to instruct the
/// function to use embedded fonts, so it makes sense if it always chooses
/// the device font.
//
/// TODO: this duplicates other functionality in TextField; ideally both
/// should be fixed, tested, and merged.
as_value
textformat_getTextExtent(const fn_call& fn)
{

    TextFormat_as* relay = ensure<ThisIsNative<TextFormat_as> >(fn);
    
    if (!fn.nargs) {
        IF_VERBOSE_ASCODING_ERRORS(
            log_aserror("TextFormat.getTextExtent requires at least one"
                "argument");
        );
        return as_value();
    }

    const int version = getSWFVersion(fn);
    const std::string& s = fn.arg(0).to_string(version);


    const bool limitWidth = (fn.nargs > 1);
    
    // Everything must be in twips here.
    const double tfw = limitWidth ?
        pixelsToTwips(toNumber(fn.arg(1), getVM(fn))) : 0;

    const bool bold = relay->bold() ? *relay->bold() : false;
    const bool italic = relay->italic() ? *relay->italic() : false;
    const double size = relay->size() ? *relay->size() : 240;

    // Note: currently leading is never defined for device fonts, and since
    // getTextExtent currently only takes account of device fonts we don't
    // need it.

    Font* f = relay->font() ?
        fontlib::get_font(*relay->font(), bold, italic) :
        fontlib::get_default_font().get();
    
    // Whether to use embedded fonts if required.
    const bool em = false;

    /// Advance, descent, ascent given according to square of 1024.
    //
    /// An ascent of 1024 is equal to the whole size of the character, so
    /// 240 twips for a size 12.
    const double scale = size / static_cast<double>(f->unitsPerEM(em));

    double height = size;
    double width = 0;
    double curr = 0;
    
    const double ascent = f->ascent(em) * scale;
    const double descent = f->descent(em) * scale;

    for (std::string::const_iterator it = s.begin(), e = s.end();
            it != e; ++it) {

        const int index = f->get_glyph_index(*it, em);
        const double advance = f->get_advance(index, em) * scale;
        if (limitWidth && (curr + advance > tfw)) {
            curr = 0;
            height += size;
        }
        curr += advance;
        width = std::max(width, curr);

    }

    Global_as& gl = getGlobal(fn);
    as_object* obj = new as_object(gl);

    obj->init_member("textFieldHeight", twipsToPixels(height) + 4);
    obj->init_member("textFieldWidth",
            limitWidth ? twipsToPixels(tfw) : twipsToPixels(width) + 4);
    obj->init_member("width", twipsToPixels(width));
    obj->init_member("height", twipsToPixels(height));
    obj->init_member("ascent", twipsToPixels(ascent));
    obj->init_member("descent", twipsToPixels(descent));

    return as_value(obj);

}


void
attachTextFormatInterface(as_object& o)
{
        int flags = 0;

    NativeFunction* get;
    NativeFunction* set;

    VM& vm = getVM(o);

    get = vm.getNative(110, 1);
    set = vm.getNative(110, 2);
        o.init_property("font", *get, *set, flags);
    get = vm.getNative(110, 3);
    set = vm.getNative(110, 4);
        o.init_property("size", *get, *set, flags);
    get = vm.getNative(110, 5);
    set = vm.getNative(110, 6);
        o.init_property("color", *get, *set, flags);
    get = vm.getNative(110, 7);
    set = vm.getNative(110, 8);
        o.init_property("url", *get, *set, flags);
    get = vm.getNative(110, 9);
    set = vm.getNative(110, 10);
        o.init_property("target", *get, *set, flags);
    get = vm.getNative(110, 11);
    set = vm.getNative(110, 12);
        o.init_property("bold", *get, *set, flags);
    get = vm.getNative(110, 13);
    set = vm.getNative(110, 14);
        o.init_property("italic", *get, *set, flags);
    get = vm.getNative(110, 15);
    set = vm.getNative(110, 16);
        o.init_property("underline", *get, *set, flags);
    get = vm.getNative(110, 17);
    set = vm.getNative(110, 18);
        o.init_property("align", *get, *set, flags);
    get = vm.getNative(110, 19);
    set = vm.getNative(110, 20);
        o.init_property("leftMargin", *get, *set, flags);
    get = vm.getNative(110, 21);
    set = vm.getNative(110, 22);
        o.init_property("rightMargin", *get, *set, flags);
    get = vm.getNative(110, 23);
    set = vm.getNative(110, 24);
        o.init_property("indent", *get, *set, flags);
    get = vm.getNative(110, 25);
    set = vm.getNative(110, 26);
        o.init_property("leading", *get, *set, flags);
    get = vm.getNative(110, 27);
    set = vm.getNative(110, 28);
        o.init_property("blockIndent", *get, *set, flags);
    get = vm.getNative(110, 29);
    set = vm.getNative(110, 30);
        o.init_property("tabStops", *get, *set, flags);
    get = vm.getNative(110, 31);
    set = vm.getNative(110, 32);
        o.init_property("bullet", *get, *set, flags);

    o.init_property("display", textformat_display, textformat_display, flags);
}


TextField::TextFormatDisplay
parseDisplayString(const std::string& display)
{
        StringNoCaseEqual cmp;
        if (cmp(display, "inline")) return TextField::TEXTFORMAT_INLINE;
        if (cmp(display, "block")) return TextField::TEXTFORMAT_BLOCK;
        
    // Is this correct? We have to return something here...
        log_debug("Invalid display string %s ", display);
    return TextField::TEXTFORMAT_BLOCK;
}

const char* 
getAlignString(TextField::TextAlignment a)
{
        switch (a)
        {
                case TextField::ALIGN_LEFT:
                        return "left";
                case TextField::ALIGN_CENTER:
                        return "center";
                case TextField::ALIGN_RIGHT:
                        return "right";
                case TextField::ALIGN_JUSTIFY:
                        return "justify";
                default:
                        log_error("Uknown alignment value: %d, take as left", a);
                        return "left";
        }
}

const char*
getDisplayString(TextField::TextFormatDisplay a) 
{
        switch (a)
        {
                case TextField::TEXTFORMAT_INLINE:
                        return "inline";
                case TextField::TEXTFORMAT_BLOCK:
                        return "block";
                default:
                        log_error("Unknown display value: %d ", a);
            return "";
        }
}
        

} // anonymous namespace
} // end of gnash namespace

/* [<][>][^][v][top][bottom][index][help] */