/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   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 .
 */
 
#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 <i18nlangtag/languagetag.hxx>
#include <tools/UnitConversion.hxx>
#include <o3tl/safeint.hxx>
#include <o3tl/string_view.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;
// #i92232#
static AtkTextAttribute atk_text_attribute_tracked_change = ATK_TEXT_ATTR_INVALID;
// #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.
  */
 
namespace {
 
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,
    // #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
    // #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 nullptr;
}
 
#define get_bool_value( list, index ) get_value( list, index, Bool2String )
#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 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 <<= fval;
    return true;
}
 
/*****************************************************************************/
 
/// @throws uno::RuntimeException
static css::uno::Reference<css::accessibility::XAccessibleComponent>
    getComponent( AtkText *pText )
{
    AtkObjectWrapper *pWrap = ATK_OBJECT_WRAPPER( pText );
    if( pWrap )
    {
        if( !pWrap->mpComponent.is() )
        {
            pWrap->mpComponent.set(pWrap->mpContext, css::uno::UNO_QUERY);
        }
 
        return pWrap->mpComponent;
    }
 
    return css::uno::Reference<css::accessibility::XAccessibleComponent>();
}
 
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
        {
            css::uno::Reference<css::accessibility::XAccessibleComponent>
                pComponent = getComponent( text );
            if( pComponent.is() )
            {
                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&) {
            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 nullptr;
}
 
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 = static_cast<sal_Int32>(blue) | ( static_cast<sal_Int32>(green) << 8 ) | ( static_cast<sal_Int32>(red) << 16 );
    rAny <<= nColor;
    return true;
}
 
/*****************************************************************************/
 
static gchar*
FontSlant2Style(const uno::Any& rAny)
{
    const gchar * value = nullptr;
 
    awt::FontSlant aFontSlant;
    if(!(rAny >>= aFontSlant))
        return nullptr;
 
    switch( aFontSlant )
    {
        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 nullptr;
}
 
static bool
Style2FontSlant( uno::Any& rAny, const gchar * value )
{
    awt::FontSlant aFontSlant;
 
    if( strncmp( value, STRNCMP_PARAM( "normal" ) ) == 0 )
        aFontSlant = awt::FontSlant_NONE;
    else if( strncmp( value, STRNCMP_PARAM( "oblique" ) ) == 0 )
        aFontSlant = awt::FontSlant_OBLIQUE;
    else if( strncmp( value, STRNCMP_PARAM( "italic" ) ) == 0 )
        aFontSlant = awt::FontSlant_ITALIC;
    else if( strncmp( value, STRNCMP_PARAM( "reverse oblique" ) ) == 0 )
        aFontSlant = awt::FontSlant_REVERSE_OBLIQUE;
    else if( strncmp( value, STRNCMP_PARAM( "reverse italic" ) ) == 0 )
        aFontSlant = awt::FontSlant_REVERSE_ITALIC;
    else
        return false;
 
    rAny <<= 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 <<= weight / 4;
    return true;
}
 
/*****************************************************************************/
 
static gchar*
Adjust2Justification(const uno::Any& rAny)
{
    const gchar * value = nullptr;
 
    switch( static_cast<style::ParagraphAdjust>(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 nullptr;
}
 
static bool
Justification2Adjust( uno::Any& rAny, const gchar * value )
{
    style::ParagraphAdjust nParagraphAdjust;
 
    if( strncmp( value, STRNCMP_PARAM( "left" ) ) == 0 )
        nParagraphAdjust = style::ParagraphAdjust_LEFT;
    else if( strncmp( value, STRNCMP_PARAM( "right" ) ) == 0 )
        nParagraphAdjust = style::ParagraphAdjust_RIGHT;
    else if( strncmp( value, STRNCMP_PARAM( "fill" ) ) == 0 )
        nParagraphAdjust = style::ParagraphAdjust_BLOCK;
    else if( strncmp( value, STRNCMP_PARAM( "center" ) ) == 0 )
        nParagraphAdjust = style::ParagraphAdjust_CENTER;
    else
        return false;
 
    rAny <<= static_cast<short>(nParagraphAdjust);
    return true;
}
 
/*****************************************************************************/
 
const gchar * const font_strikethrough[] = {
    "none",   // FontStrikeout::NONE
    "single", // FontStrikeout::SINGLE
    "double", // FontStrikeout::DOUBLE
    nullptr,     // FontStrikeout::DONTKNOW
    "bold",   // FontStrikeout::BOLD
    "with /", // FontStrikeout::SLASH
    "with X"  // FontStrikeout::X
};
 
static gchar*
Strikeout2String(const uno::Any& rAny)
{
    sal_Int16 n = rAny.get<sal_Int16>();
 
    if( n >= 0 && o3tl::make_unsigned(n) < SAL_N_ELEMENTS(font_strikethrough) )
        return g_strdup( font_strikethrough[n] );
 
    return nullptr;
}
 
static bool
String2Strikeout( uno::Any& rAny, const gchar * value )
{
    for( sal_Int16 n=0; n < sal_Int16(SAL_N_ELEMENTS(font_strikethrough)); ++n )
    {
        if( ( nullptr != font_strikethrough[n] ) &&
            0 == strncmp( value, font_strikethrough[n], strlen( font_strikethrough[n] ) ) )
        {
            rAny <<= n;
            return true;
        }
    }
 
    return false;
}
 
/*****************************************************************************/
 
static gchar*
Underline2String(const uno::Any& rAny)
{
    const gchar * value = nullptr;
 
    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 nullptr;
}
 
static bool
String2Underline( uno::Any& rAny, const gchar * value )
{
    short nUnderline;
 
    if( strncmp( value, STRNCMP_PARAM( "none" ) ) == 0 )
        nUnderline = awt::FontUnderline::NONE;
    else if( strncmp( value, STRNCMP_PARAM( "single" ) ) == 0 )
        nUnderline = awt::FontUnderline::SINGLE;
    else if( strncmp( value, STRNCMP_PARAM( "double" ) ) == 0 )
        nUnderline = awt::FontUnderline::DOUBLE;
    else
        return false;
 
    rAny <<= nUnderline;
    return true;
}
 
/*****************************************************************************/
 
static gchar*
GetString(const uno::Any& rAny)
{
    OString aFontName = OUStringToOString( rAny.get< OUString > (), RTL_TEXTENCODING_UTF8 );
 
    if( !aFontName.isEmpty() )
        return g_strdup( aFontName.getStr() );
 
    return nullptr;
}
 
static bool
SetString( uno::Any& rAny, const gchar * value )
{
    OString aFontName( value );
 
    if( !aFontName.isEmpty() )
    {
        rAny <<= 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: don't use double here because of warning on linux
 
    if( 1 != sscanf( value, "%gmm", &fValue ) )
        return false;
 
    fValue = fValue * 100;
 
    rAny <<= static_cast<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<bool>() )
        n = 0;
 
    return g_strdup( bool_values[n] );
}
 
static bool
String2Bool( uno::Any& rAny, const gchar * value )
{
    bool bValue;
 
    if( strncmp( value, STRNCMP_PARAM( "true" ) ) == 0 )
        bValue = true;
    else if( strncmp( value, STRNCMP_PARAM( "false" ) ) == 0 )
        bValue = false;
    else
        return false;
 
    rAny <<= bValue;
    return true;
}
 
/*****************************************************************************/
 
static gchar*
Scale2String( const uno::Any& rAny )
{
    return g_strdup_printf( "%g", static_cast<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 <<= static_cast<sal_Int16>(dval * 100);
    return true;
}
 
/*****************************************************************************/
 
static gchar *
CaseMap2String( const uno::Any& rAny )
{
    const gchar * value;
 
    switch( rAny.get<short>() )
    {
        case style::CaseMap::SMALLCAPS:
            value = "small_caps";
            break;
 
        default:
            value = "normal";
            break;
    }
 
    return g_strdup(value);
}
 
static bool
String2CaseMap( uno::Any& rAny, const gchar * value )
{
    short nCaseMap;
 
    if( strncmp( value, STRNCMP_PARAM( "normal" ) ) == 0 )
        nCaseMap = style::CaseMap::NONE;
    else if( strncmp( value, STRNCMP_PARAM( "small_caps" ) ) == 0 )
        nCaseMap = style::CaseMap::SMALLCAPS;
    else
        return false;
 
    rAny <<= nCaseMap;
    return true;
}
 
/*****************************************************************************/
 
const gchar * const 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)
{
    /* FIXME-BCP47: support language tags? And why is country lowercase? */
    lang::Locale aLocale = rAny.get<lang::Locale> ();
    LanguageTag aLanguageTag( aLocale);
    return g_strdup_printf( "%s-%s",
        OUStringToOString( aLanguageTag.getLanguage(), RTL_TEXTENCODING_ASCII_US).getStr(),
        OUStringToOString( aLanguageTag.getCountry(), RTL_TEXTENCODING_ASCII_US).toAsciiLowerCase().getStr() );
}
 
static bool
String2Locale( uno::Any& rAny, const gchar * value )
{
    /* FIXME-BCP47: support language tags? */
    bool ret = false;
 
    gchar ** str_array = g_strsplit_set( value, "-.@", -1 );
    if( str_array[0] != nullptr )
    {
        ret = true;
 
        lang::Locale aLocale;
 
        aLocale.Language = OUString::createFromAscii(str_array[0]);
        if( str_array[1] != nullptr )
        {
            gchar * country = g_ascii_strup(str_array[1], -1);
            aLocale.Country = OUString::createFromAscii(country);
            g_free(country);
        }
 
        rAny <<= 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" };
const gchar * const 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<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 nullptr;
}
 
/*****************************************************************************/
 
// @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] = { nullptr, nullptr, nullptr, nullptr };
    gint count = 0;
 
    // no property value found
    if( ( nBlinkIndex == -1 ) && (nUnderlineIndex == -1 ) && (nStrikeoutIndex == -1))
        return nullptr;
 
    if( nBlinkIndex != -1 )
    {
        if( rAttributeList[nBlinkIndex].Value.get<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<bool>() )
        n = 1;
 
    return g_strdup( shadow_values[n] );
}
 
/*****************************************************************************/
 
static gchar *
Short2Degree( const uno::Any& rAny )
{
    float f = rAny.get<sal_Int16>() / 10.0;
    return g_strdup_printf( "%g", f );
}
 
/*****************************************************************************/
 
const gchar * const 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 nullptr;
}
 
// @see http://www.w3.org/TR/2001/WD-css3-text-20010517/#PrimaryTextAdvanceDirection
 
const gchar * const 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 nullptr;
}
 
/*****************************************************************************/
 
const char * const 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 = nullptr;
 
    // 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 = nullptr;
 
    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", convertMm100ToPoint<double>(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 = nullptr;
 
    if( rAny >>= theTabStops)
    {
        sal_Unicode lastFillChar = ' ';
 
        for (const auto& rTabStop : theTabStops)
        {
            bool is_default_tab = (style::TabAlign_DEFAULT == rTabStop.Alignment);
 
            if( is_default_tab != default_tabs )
                continue;
 
            double fValue = rTabStop.Position;
            fValue = fValue * 0.01;
 
            const gchar * tab_align = "";
            switch( rTabStop.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( rTabStop.FillChar != lastFillChar )
            {
                lastFillChar = rTabStop.FillChar;
                switch (lastFillChar)
                {
                    case ' ':
                        lead_char = "blank ";
                        break;
 
                    case '.':
                        lead_char = "dotted ";
                        break;
 
                    case '-':
                        lead_char = "dashed ";
                        break;
 
                    case '_':
                        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, nullptr);
                g_free( tab_str );
                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" {
 
static int
attr_compare(const void *p1,const void *p2)
{
    const rtl_uString * pustr = static_cast<const rtl_uString *>(p1);
    const char * pc = *static_cast<const char * const *>(p2);
 
    return rtl_ustr_ascii_compare_WithLength(pustr->buffer, pustr->length, pc);
}
 
}
 
static void
find_exported_attributes( sal_Int32 *pArray,
    const css::uno::Sequence< css::beans::PropertyValue >& rAttributeList )
{
    for( sal_Int32 i = 0; i < rAttributeList.getLength(); i++ )
    {
        const char ** pAttr = static_cast<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 = static_cast<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 = nullptr;
 
    sal_Int32 aIndexList[TEXT_ATTRIBUTE_LAST] = { -1 };
 
    // Initialize index array with -1
    for(sal_Int32 & rn : aIndexList)
        rn = -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 ? nullptr : 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 ? nullptr : 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));
 
    // #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_new_from_extended_attributes(
    const css::uno::Reference< css::accessibility::XAccessibleExtendedAttributes >& rExtendedAttributes )
{
    AtkAttributeSet *pSet = nullptr;
 
    // extended attributes is a string of colon-separated pairs of property and value,
    // with pairs separated by semicolons. Example: "heading-level:2;weight:bold;"
    uno::Any anyVal = rExtendedAttributes->getExtendedAttributes();
    OUString sExtendedAttrs;
    anyVal >>= sExtendedAttrs;
    sal_Int32 nIndex = 0;
    do
    {
        OUString sProperty = sExtendedAttrs.getToken( 0, ';', nIndex );
 
        sal_Int32 nColonPos = 0;
        OString sPropertyName = OUStringToOString( o3tl::getToken(sProperty, 0, ':', nColonPos ),
                                                   RTL_TEXTENCODING_UTF8 );
        OString sPropertyValue = OUStringToOString( o3tl::getToken(sProperty, 0, ':', nColonPos ),
                                                    RTL_TEXTENCODING_UTF8 );
 
        pSet = attribute_set_prepend( pSet,
                                      atk_text_attribute_register( sPropertyName.getStr() ),
                                      g_strdup_printf( "%s", sPropertyValue.getStr() ) );
    }
    while ( nIndex >= 0 && nIndex < sExtendedAttrs.getLength() );
 
    return pSet;
}
 
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;
}
 
// #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;
}
 
/*****************************************************************************/
 
namespace {
 
struct AtkTextAttrMapping
{
    OUString          name;
    TextPropertyValueFunc toPropertyValue;
};
 
}
 
constexpr AtkTextAttrMapping g_TextAttrMap[]
{
    { u""_ustr, InvalidValue },                       // ATK_TEXT_ATTR_INVALID = 0
    { u"ParaLeftMargin"_ustr, UnitString2CMM },       // ATK_TEXT_ATTR_LEFT_MARGIN
    { u"ParaRightMargin"_ustr, UnitString2CMM },      // ATK_TEXT_ATTR_RIGHT_MARGIN
    { u"ParaFirstLineIndent"_ustr, UnitString2CMM },  // ATK_TEXT_ATTR_INDENT
    { u"CharHidden"_ustr, String2Bool },              // ATK_TEXT_ATTR_INVISIBLE
    { u""_ustr, InvalidValue },                       // ATK_TEXT_ATTR_EDITABLE
    { u"ParaTopMargin"_ustr, UnitString2CMM },        // ATK_TEXT_ATTR_PIXELS_ABOVE_LINES
    { u"ParaBottomMargin"_ustr, UnitString2CMM },     // ATK_TEXT_ATTR_PIXELS_BELOW_LINES
    { u""_ustr, InvalidValue },                       // ATK_TEXT_ATTR_PIXELS_INSIDE_WRAP
    { u""_ustr, InvalidValue },                       // ATK_TEXT_ATTR_BG_FULL_HEIGHT
    { u""_ustr, InvalidValue },                       // ATK_TEXT_ATTR_RISE
    { u"CharUnderline"_ustr, String2Underline },      // ATK_TEXT_ATTR_UNDERLINE
    { u"CharStrikeout"_ustr, String2Strikeout },      // ATK_TEXT_ATTR_STRIKETHROUGH
    { u"CharHeight"_ustr, String2Float },             // ATK_TEXT_ATTR_SIZE
    { u"CharScaleWidth"_ustr, String2Scale },         // ATK_TEXT_ATTR_SCALE
    { u"CharWeight"_ustr, String2Weight },            // ATK_TEXT_ATTR_WEIGHT
    { u"CharLocale"_ustr, String2Locale },            // ATK_TEXT_ATTR_LANGUAGE
    { u"CharFontName"_ustr,  SetString },             // ATK_TEXT_ATTR_FAMILY_NAME
    { u"CharBackColor"_ustr, String2Color },          // ATK_TEXT_ATTR_BG_COLOR
    { u"CharColor"_ustr, String2Color },              // ATK_TEXT_ATTR_FG_COLOR
    { u""_ustr, InvalidValue },                       // ATK_TEXT_ATTR_BG_STIPPLE
    { u""_ustr, InvalidValue },                       // ATK_TEXT_ATTR_FG_STIPPLE
    { u""_ustr, InvalidValue },                       // ATK_TEXT_ATTR_WRAP_MODE
    { u""_ustr, InvalidValue },                       // ATK_TEXT_ATTR_DIRECTION
    { u"ParaAdjust"_ustr, Justification2Adjust },     // ATK_TEXT_ATTR_JUSTIFICATION
    { u""_ustr, InvalidValue },                       // ATK_TEXT_ATTR_STRETCH
    { u"CharCaseMap"_ustr, String2CaseMap },          // ATK_TEXT_ATTR_VARIANT
    { u"CharPosture"_ustr, Style2FontSlant }          // ATK_TEXT_ATTR_STYLE
};
 
/*****************************************************************************/
 
bool
attribute_set_map_to_property_values(
    AtkAttributeSet* attribute_set,
    uno::Sequence< beans::PropertyValue >& rValueList )
{
    // Ensure enough space ..
    uno::Sequence< beans::PropertyValue > aAttributeList (SAL_N_ELEMENTS(g_TextAttrMap));
    auto pAttributeList = aAttributeList.getArray();
 
    sal_Int32 nIndex = 0;
    for( GSList * item = attribute_set; item != nullptr; item = g_slist_next( item ) )
    {
        AtkAttribute* attribute = reinterpret_cast<AtkAttribute *>(item);
 
        AtkTextAttribute text_attr = atk_text_attribute_for_name( attribute->name );
        if( text_attr < SAL_N_ELEMENTS(g_TextAttrMap) )
        {
            if( g_TextAttrMap[text_attr].name[0] != '\0' )
            {
                if( ! g_TextAttrMap[text_attr].toPropertyValue( pAttributeList[nIndex].Value, attribute->value) )
                    return false;
 
                pAttributeList[nIndex].Name = g_TextAttrMap[text_attr].name;
                pAttributeList[nIndex].State = beans::PropertyState_DIRECT_VALUE;
                ++nIndex;
            }
        }
        else
        {
            // Unsupported text attribute
            return false;
        }
    }
 
    aAttributeList.realloc( nIndex );
    rValueList = std::move(aAttributeList);
    return true;
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V1009 Check the array initialization. Only the first element is initialized explicitly. The rest elements are initialized with zeros.