/**************************************************************
 * 
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * 
 *************************************************************/



// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_vcl.hxx"

#include "atktextattributes.hxx"

#include <com/sun/star/awt/FontSlant.hpp>
#include <com/sun/star/awt/FontStrikeout.hpp>
#include <com/sun/star/awt/FontUnderline.hpp>

#include <com/sun/star/style/CaseMap.hpp>
#include <com/sun/star/style/LineSpacing.hpp>
#include <com/sun/star/style/LineSpacingMode.hpp>
#include <com/sun/star/style/ParagraphAdjust.hpp>
#include <com/sun/star/style/TabAlign.hpp>
#include <com/sun/star/style/TabStop.hpp>

#include <com/sun/star/text/WritingMode2.hpp>

#include "atkwrapper.hxx"

#include <com/sun/star/accessibility/XAccessibleComponent.hpp>

#include <vcl/svapp.hxx>
#include <vcl/outdev.hxx>

#include <stdio.h>
#include <string.h>

using namespace ::com::sun::star;

typedef gchar* (* AtkTextAttrFunc)       ( const uno::Any& rAny );
typedef bool   (* TextPropertyValueFunc) ( uno::Any& rAny, const gchar * value );

#define STRNCMP_PARAM( s )  s,sizeof( s )-1


/*****************************************************************************/

static AtkTextAttribute atk_text_attribute_paragraph_style = ATK_TEXT_ATTR_INVALID;
static AtkTextAttribute atk_text_attribute_font_effect = ATK_TEXT_ATTR_INVALID;
static AtkTextAttribute atk_text_attribute_decoration = ATK_TEXT_ATTR_INVALID;
static AtkTextAttribute atk_text_attribute_line_height = ATK_TEXT_ATTR_INVALID;
static AtkTextAttribute atk_text_attribute_rotation = ATK_TEXT_ATTR_INVALID;
static AtkTextAttribute atk_text_attribute_shadow = ATK_TEXT_ATTR_INVALID;
static AtkTextAttribute atk_text_attribute_tab_interval = ATK_TEXT_ATTR_INVALID;
static AtkTextAttribute atk_text_attribute_tab_stops = ATK_TEXT_ATTR_INVALID;
static AtkTextAttribute atk_text_attribute_writing_mode = ATK_TEXT_ATTR_INVALID;
static AtkTextAttribute atk_text_attribute_vertical_align = ATK_TEXT_ATTR_INVALID;
static AtkTextAttribute atk_text_attribute_misspelled = ATK_TEXT_ATTR_INVALID;
// --> OD 2010-03-01 #i92232#
static AtkTextAttribute atk_text_attribute_tracked_change = ATK_TEXT_ATTR_INVALID;
// <--
// --> OD 2010-03-05 #i92233#
static AtkTextAttribute atk_text_attribute_mm_to_pixel_ratio = ATK_TEXT_ATTR_INVALID;
// <--

/*****************************************************************************/

/**
  * !! IMPORTANT NOTE !! : when adding items to this list, KEEP THE LIST SORTED
  *                        and re-arrange the enum values accordingly.
  */

enum ExportedAttribute
{
    TEXT_ATTRIBUTE_BACKGROUND_COLOR = 0,
    TEXT_ATTRIBUTE_CASEMAP,
    TEXT_ATTRIBUTE_FOREGROUND_COLOR,
    TEXT_ATTRIBUTE_CONTOURED,
    TEXT_ATTRIBUTE_CHAR_ESCAPEMENT,
    TEXT_ATTRIBUTE_BLINKING,
    TEXT_ATTRIBUTE_FONT_NAME,
    TEXT_ATTRIBUTE_HEIGHT,
    TEXT_ATTRIBUTE_HIDDEN,
    TEXT_ATTRIBUTE_KERNING,
    TEXT_ATTRIBUTE_LOCALE,
    TEXT_ATTRIBUTE_POSTURE,
    TEXT_ATTRIBUTE_RELIEF,
    TEXT_ATTRIBUTE_ROTATION,
    TEXT_ATTRIBUTE_SCALE,
    TEXT_ATTRIBUTE_SHADOWED,
    TEXT_ATTRIBUTE_STRIKETHROUGH,
    TEXT_ATTRIBUTE_UNDERLINE,
    TEXT_ATTRIBUTE_WEIGHT,
    // --> OD 2010-03-05 #i92233#
    TEXT_ATTRIBUTE_MM_TO_PIXEL_RATIO,
    // <--
    TEXT_ATTRIBUTE_JUSTIFICATION,
    TEXT_ATTRIBUTE_BOTTOM_MARGIN,
    TEXT_ATTRIBUTE_FIRST_LINE_INDENT,
    TEXT_ATTRIBUTE_LEFT_MARGIN,
    TEXT_ATTRIBUTE_LINE_SPACING,
    TEXT_ATTRIBUTE_RIGHT_MARGIN,
    TEXT_ATTRIBUTE_STYLE_NAME,
    TEXT_ATTRIBUTE_TAB_STOPS,
    TEXT_ATTRIBUTE_TOP_MARGIN,
    TEXT_ATTRIBUTE_WRITING_MODE,
    TEXT_ATTRIBUTE_LAST
};

static const char * ExportedTextAttributes[TEXT_ATTRIBUTE_LAST] =
{
    "CharBackColor",        // TEXT_ATTRIBUTE_BACKGROUND_COLOR
    "CharCaseMap",          // TEXT_ATTRIBUTE_CASEMAP
    "CharColor",            // TEXT_ATTRIBUTE_FOREGROUND_COLOR
    "CharContoured",        // TEXT_ATTRIBUTE_CONTOURED
    "CharEscapement",       // TEXT_ATTRIBUTE_CHAR_ESCAPEMENT
    "CharFlash",            // TEXT_ATTRIBUTE_BLINKING
    "CharFontName",         // TEXT_ATTRIBUTE_FONT_NAME
    "CharHeight",           // TEXT_ATTRIBUTE_HEIGHT
    "CharHidden",           // TEXT_ATTRIBUTE_HIDDEN
    "CharKerning",          // TEXT_ATTRIBUTE_KERNING
    "CharLocale",           // TEXT_ATTRIBUTE_LOCALE
    "CharPosture",          // TEXT_ATTRIBUTE_POSTURE
    "CharRelief",           // TEXT_ATTRIBUTE_RELIEF
    "CharRotation",         // TEXT_ATTRIBUTE_ROTATION
    "CharScaleWidth",       // TEXT_ATTRIBUTE_SCALE
    "CharShadowed",         // TEXT_ATTRIBUTE_SHADOWED
    "CharStrikeout",        // TEXT_ATTRIBUTE_STRIKETHROUGH
    "CharUnderline",        // TEXT_ATTRIBUTE_UNDERLINE
    "CharWeight",           // TEXT_ATTRIBUTE_WEIGHT
    // --> OD 2010-03-05 #i92233#
    "MMToPixelRatio",       // TEXT_ATTRIBUTE_MM_TO_PIXEL_RATIO
    // <--
    "ParaAdjust",           // TEXT_ATTRIBUTE_JUSTIFICATION
    "ParaBottomMargin",     // TEXT_ATTRIBUTE_BOTTOM_MARGIN
    "ParaFirstLineIndent",  // TEXT_ATTRIBUTE_FIRST_LINE_INDENT
    "ParaLeftMargin",       // TEXT_ATTRIBUTE_LEFT_MARGIN
    "ParaLineSpacing",      // TEXT_ATTRIBUTE_LINE_SPACING
    "ParaRightMargin",      // TEXT_ATTRIBUTE_RIGHT_MARGIN
    "ParaStyleName",        // TEXT_ATTRIBUTE_STYLE_NAME
    "ParaTabStops",         // TEXT_ATTRIBUTE_TAB_STOPS
    "ParaTopMargin",        // TEXT_ATTRIBUTE_TOP_MARGIN
    "WritingMode"           // TEXT_ATTRIBUTE_WRITING_MODE
};


/*****************************************************************************/

static gchar*
get_value( const uno::Sequence< beans::PropertyValue >& rAttributeList,
           sal_Int32 nIndex, AtkTextAttrFunc func )
{
    if( nIndex != -1 )
        return func(rAttributeList[nIndex].Value);

    return NULL;
}

#define get_bool_value( list, index ) get_value( list, index, Bool2String )
#define get_short_value( list, index ) get_value( list, index, Short2String )
//#define get_long_value( list, index ) get_value( list, index, Long2String ) pb: not used (warning on linux)
#define get_height_value( list, index ) get_value( list, index, Float2String )
#define get_justification_value( list, index ) get_value( list, index, Adjust2Justification )
#define get_cmm_value( list, index ) get_value( list, index, CMM2UnitString )
#define get_scale_width( list, index ) get_value( list, index, Scale2String )
#define get_strikethrough_value( list, index ) get_value( list, index, Strikeout2String )
#define get_string_value( list, index ) get_value( list, index, GetString )
#define get_style_value( list, index ) get_value( list, index, FontSlant2Style )
#define get_underline_value( list, index ) get_value( list, index, Underline2String )
#define get_variant_value( list, index ) get_value( list, index, CaseMap2String )
#define get_weight_value( list, index ) get_value( list, index, Weight2String )
#define get_language_string( list, index ) get_value( list, index, Locale2String )

/*
static gchar*
dump_value( const uno::Sequence< beans::PropertyValue >& rAttributeList, sal_Int32 nIndex )
{
    if( nIndex != -1 )
    {
        rtl::OString aName = rtl::OUStringToOString(rAttributeList[nIndex].Name, RTL_TEXTENCODING_UTF8);

        if( rAttributeList[nIndex].Value.has<sal_Int16> () )
            OSL_TRACE( "%s = %d (short value)", aName.getStr(),
                rAttributeList[nIndex].Value.get<sal_Int16> () );

        else if( rAttributeList[nIndex].Value.has<sal_Int8> () )
            OSL_TRACE( "%s = %d (byte value)", aName.getStr(),
                rAttributeList[nIndex].Value.get<sal_Int8> () );

        else if( rAttributeList[nIndex].Value.has<sal_Bool> () )
            OSL_TRACE( "%s = %s (bool value)", aName.getStr(),
                rAttributeList[nIndex].Value.get<sal_Bool> () ? "true" : "false" );

        else if( rAttributeList[nIndex].Value.has<rtl::OUString> () )
            OSL_TRACE( "%s = %s", aName.getStr(),
                rtl::OUStringToOString(rAttributeList[nIndex].Value.get<rtl::OUString> (),
                    RTL_TEXTENCODING_UTF8).getStr() );
    }

    return NULL;
}
*/

static inline
double toPoint(sal_Int16 n)
{
    // 100th mm -> pt
    return (double) (n * 72) / 2540;
}


/*****************************************************************************/

/*
static gchar*
NullString(const uno::Any&)
{
    return NULL;
}
*/

static bool
InvalidValue( uno::Any&, const gchar * )
{
    return false;
}

/*****************************************************************************/

static gchar*
Float2String(const uno::Any& rAny)
{
    return g_strdup_printf( "%g", rAny.get<float>() );
}

static bool
String2Float( uno::Any& rAny, const gchar * value )
{
    float fval;

    if( 1 != sscanf( value, "%g", &fval ) )
        return false;

    rAny = uno::makeAny( fval );
    return true;
}

/*****************************************************************************/

/*
static gchar*
Short2String(const uno::Any& rAny)
{
    return g_strdup_printf( "%d", rAny.get<sal_Int16>() );
}

static bool
String2Short( uno::Any& rAny, const gchar * value )
{
    sal_Int32 lval;

    if( 1 != sscanf( value, "%d", &lval ) )
        return false;

    rAny = uno::makeAny( (sal_Int16) lval );
    return true;
}
*/

/*****************************************************************************/
/* pb: not used (warning on linux)
static gchar*
Long2String(const uno::Any& rAny)
{
    return g_strdup_printf( "%ld", rAny.get<sal_Int32>() );
}

static bool
String2Long( uno::Any& rAny, const gchar * value )
{
    sal_Int32 lval;

    if( 1 != sscanf( value, "%ld", &lval ) )
        return false;

    rAny = uno::makeAny( lval );
    return true;
}
*/
/*****************************************************************************/

static accessibility::XAccessibleComponent*
    getComponent( AtkText *pText ) throw (uno::RuntimeException)
{
    AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pText );
    if( pWrap )
    {
        if( !pWrap->mpComponent && pWrap->mpContext )
        {
            uno::Any any = pWrap->mpContext->queryInterface( accessibility::XAccessibleComponent::static_type(NULL) );
            pWrap->mpComponent = reinterpret_cast< accessibility::XAccessibleComponent * > (any.pReserved);
            pWrap->mpComponent->acquire();
        }

        return pWrap->mpComponent;
    }

    return NULL;
}

static gchar*
get_color_value(const uno::Sequence< beans::PropertyValue >& rAttributeList,
                const sal_Int32 * pIndexArray,
                ExportedAttribute attr,
                AtkText * text)
{
    sal_Int32 nColor = -1; // AUTOMATIC
    sal_Int32 nIndex = pIndexArray[attr];

    if( nIndex != -1 )
        nColor = rAttributeList[nIndex].Value.get<sal_Int32>();

    /*
     * Check for color value for 100% alpha white, which means
     * "automatic". Grab the RGB value from XAccessibleComponent
     * in this case.
     */

    if( (nColor == -1) && text )
    {
        try
        {
            accessibility::XAccessibleComponent *pComponent = getComponent( text );
            if( pComponent )
            {
                switch( attr )
                {
                    case TEXT_ATTRIBUTE_BACKGROUND_COLOR:
                        nColor = pComponent->getBackground();
                        break;
                    case TEXT_ATTRIBUTE_FOREGROUND_COLOR:
                        nColor = pComponent->getForeground();
                        break;
                    default:
                        break;
                }
            }
        }

        catch(const uno::Exception& e) {
            g_warning( "Exception in get[Fore|Back]groundColor()" );
        }
    }

    if( nColor != -1 )
    {
        sal_uInt8 blue  = nColor & 0xFF;
        sal_uInt8 green = (nColor >> 8) & 0xFF;
        sal_uInt8 red   = (nColor >> 16) & 0xFF;

        return g_strdup_printf( "%u,%u,%u", red, green, blue );
    }

    return NULL;
}

static bool
String2Color( uno::Any& rAny, const gchar * value )
{
    int red, green, blue;

    if( 3 != sscanf( value, "%d,%d,%d", &red, &green, &blue ) )
        return false;

    sal_Int32 nColor = (sal_Int32) blue | ( (sal_Int32) green << 8 ) | ( ( sal_Int32 ) red << 16 );
    rAny = uno::makeAny( nColor );
    return true;
}

/*****************************************************************************/

static gchar*
FontSlant2Style(const uno::Any& rAny)
{
    const gchar * value = NULL;

    switch( rAny.get<awt::FontSlant>() )
    {
        case awt::FontSlant_NONE:
            value = "normal";
            break;

        case awt::FontSlant_OBLIQUE:
            value = "oblique";
            break;

        case awt::FontSlant_ITALIC:
            value = "italic";
            break;

        case awt::FontSlant_REVERSE_OBLIQUE:
            value = "reverse oblique";
            break;

        case awt::FontSlant_REVERSE_ITALIC:
            value = "reverse italic";
            break;

        default:
            break;
    }

    if( value )
         return g_strdup( value );

    return NULL;
}

static bool
Style2FontSlant( uno::Any& rAny, const gchar * value )
{
    awt::FontSlant aFontSlant;

    if( strncmp( value, STRNCMP_PARAM( "normal" ) ) )
        aFontSlant = awt::FontSlant_NONE;
    else if( strncmp( value, STRNCMP_PARAM( "oblique" ) ) )
        aFontSlant = awt::FontSlant_OBLIQUE;
    else if( strncmp( value, STRNCMP_PARAM( "italic" ) ) )
        aFontSlant = awt::FontSlant_ITALIC;
    else if( strncmp( value, STRNCMP_PARAM( "reverse oblique" ) ) )
        aFontSlant = awt::FontSlant_REVERSE_OBLIQUE;
    else if( strncmp( value, STRNCMP_PARAM( "reverse italic" ) ) )
        aFontSlant = awt::FontSlant_REVERSE_ITALIC;
    else
        return false;

    rAny = uno::makeAny( aFontSlant );
    return true;
}

/*****************************************************************************/

static gchar*
Weight2String(const uno::Any& rAny)
{
    return g_strdup_printf( "%g", rAny.get<float>() * 4 );
}

static bool
String2Weight( uno::Any& rAny, const gchar * value )
{
    float weight;

    if( 1 != sscanf( value, "%g", &weight ) )
        return false;

    rAny = uno::makeAny( weight / 4 );
    return true;
}


/*****************************************************************************/

static gchar*
Adjust2Justification(const uno::Any& rAny)
{
    const gchar * value = NULL;

    switch( rAny.get<short>() )
    {
        case style::ParagraphAdjust_LEFT:
            value = "left";
            break;

        case style::ParagraphAdjust_RIGHT:
            value = "right";
            break;

        case style::ParagraphAdjust_BLOCK:
        case style::ParagraphAdjust_STRETCH:
            value = "fill";
            break;

        case style::ParagraphAdjust_CENTER:
            value = "center";
            break;

        default:
            break;
    }

    if( value )
        return g_strdup( value );

    return NULL;
}

static bool
Justification2Adjust( uno::Any& rAny, const gchar * value )
{
    short nParagraphAdjust;

    if( strncmp( value, STRNCMP_PARAM( "left" ) ) )
        nParagraphAdjust = style::ParagraphAdjust_LEFT;
    else if( strncmp( value, STRNCMP_PARAM( "right" ) ) )
        nParagraphAdjust = style::ParagraphAdjust_RIGHT;
    else if( strncmp( value, STRNCMP_PARAM( "fill" ) ) )
        nParagraphAdjust = style::ParagraphAdjust_BLOCK;
    else if( strncmp( value, STRNCMP_PARAM( "center" ) ) )
        nParagraphAdjust = style::ParagraphAdjust_CENTER;
    else
        return false;

    rAny = uno::makeAny( nParagraphAdjust );
    return true;
}

/*****************************************************************************/

const gchar * font_strikethrough[] = {
    "none",   // FontStrikeout::NONE
    "single", // FontStrikeout::SINGLE
    "double", // FontStrikeout::DOUBLE
    NULL,     // FontStrikeout::DONTKNOW
    "bold",   // FontStrikeout::BOLD
    "with /", // FontStrikeout::SLASH
    "with X"  // FontStrikeout::X
};

const sal_Int16 n_strikeout_constants = sizeof(font_strikethrough) / sizeof(gchar*);

static gchar*
Strikeout2String(const uno::Any& rAny)
{
    sal_Int16 n = rAny.get<sal_Int16>();

    if( n >= 0 && n < n_strikeout_constants )
        return g_strdup( font_strikethrough[n] );

    return NULL;
}

static bool
String2Strikeout( uno::Any& rAny, const gchar * value )
{
    for( sal_Int16 n=0; n < n_strikeout_constants; ++n )
    {
        if( ( NULL != font_strikethrough[n] ) &&
            0 == strncmp( value, font_strikethrough[n], strlen( font_strikethrough[n] ) ) )
        {
            rAny = uno::makeAny( n );
            return true;
        }
    }

    return false;
}

/*****************************************************************************/

static gchar*
Underline2String(const uno::Any& rAny)
{
    const gchar * value = NULL;

    switch( rAny.get<sal_Int16>() )
    {
        case awt::FontUnderline::NONE:
            value = "none";
            break;

        case awt::FontUnderline::SINGLE:
            value = "single";
            break;

        case awt::FontUnderline::DOUBLE:
            value = "double";
            break;

        default:
            break;
    }

    if( value )
        return g_strdup( value );

    return NULL;
}

static bool
String2Underline( uno::Any& rAny, const gchar * value )
{
    short nUnderline;

    if( strncmp( value, STRNCMP_PARAM( "none" ) ) )
        nUnderline = awt::FontUnderline::NONE;
    else if( strncmp( value, STRNCMP_PARAM( "single" ) ) )
        nUnderline = awt::FontUnderline::SINGLE;
    else if( strncmp( value, STRNCMP_PARAM( "double" ) ) )
        nUnderline = awt::FontUnderline::DOUBLE;
    else
        return false;

    rAny = uno::makeAny( nUnderline );
    return true;
}

/*****************************************************************************/

static gchar*
GetString(const uno::Any& rAny)
{
    rtl::OString aFontName = rtl::OUStringToOString( rAny.get< rtl::OUString > (), RTL_TEXTENCODING_UTF8 );

    if( aFontName.getLength() )
        return g_strdup( aFontName.getStr() );

    return NULL;
}

static bool
SetString( uno::Any& rAny, const gchar * value )
{
    rtl::OString aFontName( value );

    if( aFontName.getLength() )
    {
        rAny = uno::makeAny( rtl::OStringToOUString( aFontName, RTL_TEXTENCODING_UTF8 ) );
        return true;
    }

    return false;
}

/*****************************************************************************/

// @see http://developer.gnome.org/doc/API/2.0/atk/AtkText.html#AtkTextAttribute

// CMM = 100th of mm
static gchar*
CMM2UnitString(const uno::Any& rAny)
{
    double fValue = rAny.get<sal_Int32>();
    fValue = fValue * 0.01;

    return g_strdup_printf( "%gmm", fValue );
}

static bool
UnitString2CMM( uno::Any& rAny, const gchar * value )
{
    float fValue = 0.0; // pb: dont use double here because of warning on linux

    if( 1 != sscanf( value, "%gmm", &fValue ) )
        return false;

    fValue = fValue * 100;

    rAny = uno::makeAny( (sal_Int32) fValue);
    return true;
}

/*****************************************************************************/

static const gchar * bool_values[] = { "true", "false" };

static gchar *
Bool2String( const uno::Any& rAny )
{
    int n = 1;

    if( rAny.get<sal_Bool>() )
        n = 0;

    return g_strdup( bool_values[n] );
}

static bool
String2Bool( uno::Any& rAny, const gchar * value )
{
    sal_Bool bValue;

    if( strncmp( value, STRNCMP_PARAM( "true" ) ) )
        bValue = sal_True;
    else if( strncmp( value, STRNCMP_PARAM( "false" ) ) )
        bValue = sal_False;
    else
        return false;

    rAny = uno::makeAny(bValue);
    return true;
}

/*****************************************************************************/

static gchar*
Scale2String( const uno::Any& rAny )
{
    return g_strdup_printf( "%g", (double) (rAny.get< sal_Int16 > ()) / 100 );
}

static bool
String2Scale( uno::Any& rAny, const gchar * value )
{
    double dval;

    if( 1 != sscanf( value, "%lg", &dval ) )
        return false;

    rAny = uno::makeAny((sal_Int16) (dval * 100));
    return true;
}

/*****************************************************************************/

static gchar *
CaseMap2String( const uno::Any& rAny )
{
    const gchar * value = NULL;

    switch( rAny.get<short>() )
    {
        case style::CaseMap::SMALLCAPS:
            value = "small_caps";
            break;

        default:
            value = "normal";
            break;
    }

    if( value )
        return g_strdup( value );

    return NULL;
}

static bool
String2CaseMap( uno::Any& rAny, const gchar * value )
{
    short nCaseMap;

    if( strncmp( value, STRNCMP_PARAM( "normal" ) ) )
        nCaseMap = style::CaseMap::NONE;
    else if( strncmp( value, STRNCMP_PARAM( "small_caps" ) ) )
        nCaseMap = style::CaseMap::SMALLCAPS;
    else
        return false;

    rAny = uno::makeAny( nCaseMap );
    return true;
}

/*****************************************************************************/

const gchar * font_stretch[] = {
    "ultra_condensed",
    "extra_condensed",
    "condensed",
    "semi_condensed",
    "normal",
    "semi_expanded",
    "expanded",
    "extra_expanded",
    "ultra_expanded"
};

static gchar*
Kerning2Stretch(const uno::Any& rAny)
{
    sal_Int16 n = rAny.get<sal_Int16>();
    int i = 4;

    // No good idea for a mapping - just return the basic info
    if( n < 0 )
        i=2;
    else if( n > 0 )
        i=6;

    return g_strdup(font_stretch[i]);
}

/*****************************************************************************/

static gchar*
Locale2String(const uno::Any& rAny)
{
    lang::Locale aLocale = rAny.get<lang::Locale> ();
    return g_strdup_printf( "%s-%s",
        rtl::OUStringToOString( aLocale.Language, RTL_TEXTENCODING_ASCII_US).getStr(),
        rtl::OUStringToOString( aLocale.Country, RTL_TEXTENCODING_ASCII_US).toAsciiLowerCase().getStr() );
}

static bool
String2Locale( uno::Any& rAny, const gchar * value )
{
    bool ret = false;

    gchar ** str_array = g_strsplit_set( value, "-.@", -1 );
    if( str_array[0] != NULL )
    {
        ret = true;

        lang::Locale aLocale;

        aLocale.Language = rtl::OUString::createFromAscii(str_array[0]);
        if( str_array[1] != NULL )
        {
            gchar * country = g_ascii_strup(str_array[1], -1);
            aLocale.Country = rtl::OUString::createFromAscii(country);
            g_free(country);
        }

        rAny = uno::makeAny(aLocale);
    }

    g_strfreev(str_array);
    return ret;
}

/*****************************************************************************/

// @see http://www.w3.org/TR/2002/WD-css3-fonts-20020802/#font-effect-prop
static const gchar * relief[] = { "none", "emboss", "engrave" };
static const gchar * outline  = "outline";

static gchar *
get_font_effect(const uno::Sequence< beans::PropertyValue >& rAttributeList,
                sal_Int32 nContourIndex, sal_Int32 nReliefIndex)
{
    if( nContourIndex != -1 )
    {
        if( rAttributeList[nContourIndex].Value.get<sal_Bool>() )
            return g_strdup(outline);
    }

    if( nReliefIndex != -1 )
    {
        sal_Int16 n = rAttributeList[nReliefIndex].Value.get<sal_Int16>();
        if( n <  3)
            return g_strdup(relief[n]);
    }

    return NULL;
}

/*****************************************************************************/

// @see http://www.w3.org/TR/REC-CSS2/text.html#lining-striking-props


enum
{
    DECORATION_NONE = 0,
    DECORATION_BLINK,
    DECORATION_UNDERLINE,
    DECORATION_LINE_THROUGH
};


static const gchar * decorations[] = { "none", "blink", "underline", "line-through" };

static gchar *
get_text_decoration(const uno::Sequence< beans::PropertyValue >& rAttributeList,
                    sal_Int32 nBlinkIndex, sal_Int32 nUnderlineIndex,
                    sal_Int16 nStrikeoutIndex)
{
    gchar * value_list[4] = { NULL, NULL, NULL, NULL };
    gint count = 0;

    // no property value found
    if( ( nBlinkIndex == -1 ) && (nUnderlineIndex == -1 ) && (nStrikeoutIndex == -1))
        return NULL;

    if( nBlinkIndex != -1 )
    {
        if( rAttributeList[nBlinkIndex].Value.get<sal_Bool>() )
            value_list[count++] = const_cast <gchar *> (decorations[DECORATION_BLINK]);
    }
    if( nUnderlineIndex != -1 )
    {
        sal_Int16 n = rAttributeList[nUnderlineIndex].Value.get<sal_Int16> ();
        if( n != awt::FontUnderline::NONE )
            value_list[count++] = const_cast <gchar *> (decorations[DECORATION_UNDERLINE]);
    }
    if( nStrikeoutIndex != -1 )
    {
        sal_Int16 n = rAttributeList[nStrikeoutIndex].Value.get<sal_Int16> ();
        if( n != awt::FontStrikeout::NONE && n != awt::FontStrikeout::DONTKNOW )
            value_list[count++] = const_cast <gchar *> (decorations[DECORATION_LINE_THROUGH]);
    }

    if( count == 0 )
        value_list[count++] = const_cast <gchar *> (decorations[DECORATION_NONE]);

    return g_strjoinv(" ", value_list);
}


/*****************************************************************************/

// @see http://www.w3.org/TR/REC-CSS2/text.html#propdef-text-shadow

static const gchar * shadow_values[] = { "none", "black" };

static gchar *
Bool2Shadow( const uno::Any& rAny )
{
    int n = 0;

    if( rAny.get<sal_Bool>() )
        n = 1;

    return g_strdup( shadow_values[n] );
}

/*****************************************************************************/

static gchar *
Short2Degree( const uno::Any& rAny )
{
    float f = rAny.get<sal_Int16>() / 10;
    return g_strdup_printf( "%g", f );
}

/*****************************************************************************/

const gchar * directions[] = { "ltr", "rtl", "rtl", "ltr", "none" };

static gchar *
WritingMode2Direction( const uno::Any& rAny )
{
    sal_Int16 n = rAny.get<sal_Int16>();

    if( 0 <= n && n <= text::WritingMode2::PAGE )
        return g_strdup(directions[n]);

    return NULL;
}

// @see http://www.w3.org/TR/2001/WD-css3-text-20010517/#PrimaryTextAdvanceDirection

const gchar * writing_modes[] = { "lr-tb", "rl-tb", "tb-rl", "tb-lr", "none" };
static gchar *
WritingMode2String( const uno::Any& rAny )
{
    sal_Int16 n = rAny.get<sal_Int16>();

    if( 0 <= n && n <= text::WritingMode2::PAGE )
        return g_strdup(writing_modes[n]);

    return NULL;
}

/*****************************************************************************/

const char * baseline_values[] = { "baseline", "sub", "super" };

// @see http://www.w3.org/TR/REC-CSS2/visudet.html#propdef-vertical-align
static gchar *
Escapement2VerticalAlign( const uno::Any& rAny )
{
    sal_Int16 n = rAny.get<sal_Int16>();
    gchar * ret = NULL;

    // Values are in %, 101% means "automatic"
    if( n == 0 )
        ret = g_strdup(baseline_values[0]);
    else if( n == 101 )
        ret = g_strdup(baseline_values[2]);
    else if( n == -101 )
        ret = g_strdup(baseline_values[1]);
    else
        ret = g_strdup_printf( "%d%%", n );

    return ret;
}

/*****************************************************************************/

// @see http://www.w3.org/TR/REC-CSS2/visudet.html#propdef-line-height
static gchar *
LineSpacing2LineHeight( const uno::Any& rAny )
{
    style::LineSpacing ls;
    gchar * ret = NULL;

    if( rAny >>= ls )
    {
        if( ls.Mode == style::LineSpacingMode::PROP )
            ret = g_strdup_printf( "%d%%", ls.Height );
        else if( ls.Mode == style::LineSpacingMode::FIX )
            ret = g_strdup_printf( "%.3gpt", toPoint(ls.Height) );
    }

    return ret;
}

/*****************************************************************************/

// @see http://www.w3.org/People/howcome/t/970224HTMLERB-CSS/WD-tabs-970117.html
static gchar *
TabStopList2String( const uno::Any& rAny, bool default_tabs )
{
    uno::Sequence< style::TabStop > theTabStops;
    gchar * ret = NULL;

    if( rAny >>= theTabStops)
    {
        sal_Int32 indexOfTab = 0;
        sal_Int32 numberOfTabs = theTabStops.getLength();
        sal_Unicode lastFillChar = (sal_Unicode) ' ';

        for( ; indexOfTab < numberOfTabs; ++indexOfTab )
        {
            bool is_default_tab = (style::TabAlign_DEFAULT == theTabStops[indexOfTab].Alignment);

            if( is_default_tab != default_tabs )
                continue;

            double fValue = theTabStops[indexOfTab].Position;
            fValue = fValue * 0.01;

            const gchar * tab_align = "";
            switch( theTabStops[indexOfTab].Alignment )
            {
                case style::TabAlign_LEFT :
                    tab_align = "left ";
                    break;
                case style::TabAlign_CENTER :
                    tab_align = "center ";
                    break;
                case style::TabAlign_RIGHT :
                    tab_align = "right ";
                    break;
                case style::TabAlign_DECIMAL :
                    tab_align = "decimal ";
                    break;
                default:
                    break;
            }

            const gchar * lead_char = "";

            if( theTabStops[indexOfTab].FillChar != lastFillChar )
            {
                lastFillChar = theTabStops[indexOfTab].FillChar;
                switch (lastFillChar)
                {
                    case (sal_Unicode) ' ':
                        lead_char = "blank ";
                        break;

                    case (sal_Unicode) '.':
                        lead_char = "dotted ";
                        break;

                    case (sal_Unicode) '-':
                        lead_char = "dashed ";
                        break;

                    case (sal_Unicode) '_':
                        lead_char = "lined ";
                        break;

                    default:
                        lead_char = "custom ";
                        break;
                }
            }

            gchar * tab_str = g_strdup_printf( "%s%s%gmm", lead_char, tab_align, fValue );

            if( ret )
            {
                gchar * old_tab_str = ret;
                ret = g_strconcat(old_tab_str, " ", tab_str, NULL /* terminated */);
                g_free( old_tab_str );
            }
            else
                ret = tab_str;
        }
    }

    return ret;
}

static gchar *
TabStops2String( const uno::Any& rAny )
{
    return TabStopList2String(rAny, false);
}

static gchar *
DefaultTabStops2String( const uno::Any& rAny )
{
    return TabStopList2String(rAny, true);
}

/*****************************************************************************/

extern "C" int
attr_compare(const void *p1,const void *p2)
{
    const rtl_uString * pustr = (const rtl_uString *) p1;
    const char * pc = *((const char **) p2);

    return rtl_ustr_ascii_compare_WithLength(pustr->buffer, pustr->length, pc);
}

static void
find_exported_attributes( sal_Int32 *pArray,
    const com::sun::star::uno::Sequence< com::sun::star::beans::PropertyValue >& rAttributeList )
{
    for( sal_Int32 i = 0; i < rAttributeList.getLength(); i++ )
    {
        const char ** pAttr = (const char **) bsearch(rAttributeList[i].Name.pData,
            ExportedTextAttributes, TEXT_ATTRIBUTE_LAST, sizeof(const char *),
            attr_compare);

        if( pAttr )
        {
            sal_Int32 nIndex = pAttr - ExportedTextAttributes;
            pArray[nIndex] = i;
        }
    }
}

/*****************************************************************************/

static AtkAttributeSet*
attribute_set_prepend( AtkAttributeSet* attribute_set,
                       AtkTextAttribute attribute,
                       gchar * value )
{
    if( value )
    {
        AtkAttribute *at = (AtkAttribute *) g_malloc( sizeof (AtkAttribute) );
        at->name  = g_strdup( atk_text_attribute_get_name( attribute ) );
        at->value = value;

        return g_slist_prepend(attribute_set, at);
    }

    return attribute_set;
}

/*****************************************************************************/

AtkAttributeSet*
attribute_set_new_from_property_values(
    const uno::Sequence< beans::PropertyValue >& rAttributeList,
    bool run_attributes_only,
    AtkText *text)
{
    AtkAttributeSet* attribute_set = NULL;

    sal_Int32 aIndexList[TEXT_ATTRIBUTE_LAST] = { -1 };

    // Initialize index array with -1
    for( sal_Int32 attr = 0; attr < TEXT_ATTRIBUTE_LAST; ++attr )
        aIndexList[attr] = -1;

    find_exported_attributes(aIndexList, rAttributeList);

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_BG_COLOR,
        get_color_value(rAttributeList, aIndexList, TEXT_ATTRIBUTE_BACKGROUND_COLOR, run_attributes_only ? NULL : text ) );

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_FG_COLOR,
        get_color_value(rAttributeList, aIndexList, TEXT_ATTRIBUTE_FOREGROUND_COLOR, run_attributes_only ? NULL : text) );

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_INVISIBLE,
        get_bool_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_HIDDEN]));

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_UNDERLINE,
        get_underline_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_UNDERLINE]));

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_STRIKETHROUGH,
        get_strikethrough_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_STRIKETHROUGH]));

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_SIZE,
        get_height_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_HEIGHT]));

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_WEIGHT,
        get_weight_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_WEIGHT]));

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_FAMILY_NAME,
        get_string_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_FONT_NAME]));

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_VARIANT,
        get_variant_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_CASEMAP]));

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_STYLE,
        get_style_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_POSTURE]));

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_SCALE,
        get_scale_width(rAttributeList, aIndexList[TEXT_ATTRIBUTE_SCALE]));

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_LANGUAGE,
        get_language_string(rAttributeList, aIndexList[TEXT_ATTRIBUTE_LOCALE]));

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_DIRECTION,
        get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_WRITING_MODE], WritingMode2Direction));

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_STRETCH,
        get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_KERNING], Kerning2Stretch));

    if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_font_effect )
        atk_text_attribute_font_effect = atk_text_attribute_register("font-effect");

    attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_font_effect,
        get_font_effect(rAttributeList, aIndexList[TEXT_ATTRIBUTE_CONTOURED], aIndexList[TEXT_ATTRIBUTE_RELIEF]));

    if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_decoration )
        atk_text_attribute_decoration = atk_text_attribute_register("text-decoration");

    attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_decoration,
        get_text_decoration(rAttributeList, aIndexList[TEXT_ATTRIBUTE_BLINKING],
            aIndexList[TEXT_ATTRIBUTE_UNDERLINE], aIndexList[TEXT_ATTRIBUTE_STRIKETHROUGH]));

    if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_rotation )
        atk_text_attribute_rotation = atk_text_attribute_register("text-rotation");

    attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_rotation,
        get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_ROTATION], Short2Degree));

    if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_shadow )
        atk_text_attribute_shadow = atk_text_attribute_register("text-shadow");

    attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_shadow,
        get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_SHADOWED], Bool2Shadow));

    if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_writing_mode )
        atk_text_attribute_writing_mode = atk_text_attribute_register("writing-mode");

    attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_writing_mode,
        get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_WRITING_MODE], WritingMode2String));

    if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_vertical_align )
        atk_text_attribute_vertical_align = atk_text_attribute_register("vertical-align");

    attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_vertical_align,
        get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_CHAR_ESCAPEMENT], Escapement2VerticalAlign));

    if( run_attributes_only )
        return attribute_set;

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_LEFT_MARGIN,
        get_cmm_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_LEFT_MARGIN]));

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_RIGHT_MARGIN,
        get_cmm_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_RIGHT_MARGIN]));

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_INDENT,
        get_cmm_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_FIRST_LINE_INDENT]));

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_PIXELS_ABOVE_LINES,
        get_cmm_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_TOP_MARGIN]));

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_PIXELS_BELOW_LINES,
        get_cmm_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_BOTTOM_MARGIN]));

    attribute_set = attribute_set_prepend(attribute_set, ATK_TEXT_ATTR_JUSTIFICATION,
        get_justification_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_JUSTIFICATION]));

    if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_paragraph_style )
        atk_text_attribute_paragraph_style = atk_text_attribute_register("paragraph-style");

    attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_paragraph_style,
        get_string_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_STYLE_NAME]));

    if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_line_height )
        atk_text_attribute_line_height = atk_text_attribute_register("line-height");

    attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_line_height,
        get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_LINE_SPACING], LineSpacing2LineHeight));

    if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_tab_interval )
        atk_text_attribute_tab_interval = atk_text_attribute_register("tab-interval");

    attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_tab_interval,
        get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_TAB_STOPS], DefaultTabStops2String));

    if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_tab_stops )
        atk_text_attribute_tab_stops = atk_text_attribute_register("tab-stops");

    attribute_set = attribute_set_prepend(attribute_set, atk_text_attribute_tab_stops,
        get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_TAB_STOPS], TabStops2String));

    // --> OD 2010-03-05 #i92233#
    if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_mm_to_pixel_ratio )
        atk_text_attribute_mm_to_pixel_ratio = atk_text_attribute_register("mm-to-pixel-ratio");

    attribute_set = attribute_set_prepend( attribute_set, atk_text_attribute_mm_to_pixel_ratio,
        get_value(rAttributeList, aIndexList[TEXT_ATTRIBUTE_MM_TO_PIXEL_RATIO], Float2String));
    // <--

    return attribute_set;
}


AtkAttributeSet* attribute_set_prepend_misspelled( AtkAttributeSet* attribute_set )
{
	if( ATK_TEXT_ATTR_INVALID == atk_text_attribute_misspelled )
		atk_text_attribute_misspelled = atk_text_attribute_register( "text-spelling" );

	attribute_set = attribute_set_prepend( attribute_set, atk_text_attribute_misspelled,
		g_strdup_printf( "misspelled" ) );

	return attribute_set;
}

// --> OD 2010-03-01 #i92232#
AtkAttributeSet* attribute_set_prepend_tracked_change_insertion( AtkAttributeSet* attribute_set )
{
    if ( ATK_TEXT_ATTR_INVALID == atk_text_attribute_tracked_change )
    {
        atk_text_attribute_tracked_change = atk_text_attribute_register( "text-tracked-change" );
    }

    attribute_set = attribute_set_prepend( attribute_set,
                                           atk_text_attribute_tracked_change,
                                           g_strdup_printf( "insertion" ) );

    return attribute_set;
}

AtkAttributeSet* attribute_set_prepend_tracked_change_deletion( AtkAttributeSet* attribute_set )
{
    if ( ATK_TEXT_ATTR_INVALID == atk_text_attribute_tracked_change )
    {
        atk_text_attribute_tracked_change = atk_text_attribute_register( "text-tracked-change" );
    }

    attribute_set = attribute_set_prepend( attribute_set,
                                           atk_text_attribute_tracked_change,
                                           g_strdup_printf( "deletion" ) );

    return attribute_set;
}

AtkAttributeSet* attribute_set_prepend_tracked_change_formatchange( AtkAttributeSet* attribute_set )
{
    if ( ATK_TEXT_ATTR_INVALID == atk_text_attribute_tracked_change )
    {
        atk_text_attribute_tracked_change = atk_text_attribute_register( "text-tracked-change" );
    }

    attribute_set = attribute_set_prepend( attribute_set,
                                           atk_text_attribute_tracked_change,
                                           g_strdup_printf( "attribute-change" ) );

    return attribute_set;
}
// <--

/*****************************************************************************/

struct AtkTextAttrMapping
{
    const char *          name;
    TextPropertyValueFunc toPropertyValue;
};

const AtkTextAttrMapping g_TextAttrMap[] =
{
    { "", InvalidValue },                       // ATK_TEXT_ATTR_INVALID = 0
    { "ParaLeftMargin", UnitString2CMM },       // ATK_TEXT_ATTR_LEFT_MARGIN
    { "ParaRightMargin", UnitString2CMM },      // ATK_TEXT_ATTR_RIGHT_MARGIN
    { "ParaFirstLineIndent", UnitString2CMM },  // ATK_TEXT_ATTR_INDENT
    { "CharHidden", String2Bool },              // ATK_TEXT_ATTR_INVISIBLE
    { "", InvalidValue },                       // ATK_TEXT_ATTR_EDITABLE
    { "ParaTopMargin", UnitString2CMM },        // ATK_TEXT_ATTR_PIXELS_ABOVE_LINES
    { "ParaBottomMargin", UnitString2CMM },     // ATK_TEXT_ATTR_PIXELS_BELOW_LINES
    { "", InvalidValue },                       // ATK_TEXT_ATTR_PIXELS_INSIDE_WRAP
    { "", InvalidValue },                       // ATK_TEXT_ATTR_BG_FULL_HEIGHT
    { "", InvalidValue },                       // ATK_TEXT_ATTR_RISE
    { "CharUnderline", String2Underline },      // ATK_TEXT_ATTR_UNDERLINE
    { "CharStrikeout", String2Strikeout },      // ATK_TEXT_ATTR_STRIKETHROUGH
    { "CharHeight", String2Float },             // ATK_TEXT_ATTR_SIZE
    { "CharScaleWidth", String2Scale },         // ATK_TEXT_ATTR_SCALE
    { "CharWeight", String2Weight },            // ATK_TEXT_ATTR_WEIGHT
    { "CharLocale", String2Locale },            // ATK_TEXT_ATTR_LANGUAGE
    { "CharFontName",  SetString },             // ATK_TEXT_ATTR_FAMILY_NAME
    { "CharBackColor", String2Color },          // ATK_TEXT_ATTR_BG_COLOR
    { "CharColor", String2Color },              // ATK_TEXT_ATTR_FG_COLOR
    { "", InvalidValue },                       // ATK_TEXT_ATTR_BG_STIPPLE
    { "", InvalidValue },                       // ATK_TEXT_ATTR_FG_STIPPLE
    { "", InvalidValue },                       // ATK_TEXT_ATTR_WRAP_MODE
    { "", InvalidValue },                       // ATK_TEXT_ATTR_DIRECTION
    { "ParaAdjust", Justification2Adjust },     // ATK_TEXT_ATTR_JUSTIFICATION
    { "", InvalidValue },                       // ATK_TEXT_ATTR_STRETCH
    { "CharCaseMap", String2CaseMap },          // ATK_TEXT_ATTR_VARIANT
    { "CharPosture", Style2FontSlant }          // ATK_TEXT_ATTR_STYLE
};

static const sal_Int32 g_TextAttrMapSize = sizeof( g_TextAttrMap ) / sizeof( AtkTextAttrMapping );

/*****************************************************************************/

bool
attribute_set_map_to_property_values(
    AtkAttributeSet* attribute_set,
    uno::Sequence< beans::PropertyValue >& rValueList )
{
    // Ensure enough space ..
    uno::Sequence< beans::PropertyValue > aAttributeList (g_TextAttrMapSize);

    sal_Int32 nIndex = 0;
    for( GSList * item = attribute_set; item != NULL; item = g_slist_next( item ) )
    {
        AtkAttribute* attribute = (AtkAttribute *) item;

        AtkTextAttribute text_attr = atk_text_attribute_for_name( attribute->name );
        if( text_attr < g_TextAttrMapSize )
        {
            if( g_TextAttrMap[text_attr].name[0] != '\0' )
            {
                if( ! g_TextAttrMap[text_attr].toPropertyValue( aAttributeList[nIndex].Value, attribute->value) )
                    return false;

                aAttributeList[nIndex].Name = rtl::OUString::createFromAscii( g_TextAttrMap[text_attr].name );
                aAttributeList[nIndex].State = beans::PropertyState_DIRECT_VALUE;
                ++nIndex;
            }
        }
        else
        {
            // Unsupported text attribute
            return false;
        }
    }

    aAttributeList.realloc( nIndex );
    rValueList = aAttributeList;
    return true;
}

