/* -*- 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 #include #include #include #include #include #include #include #include #include #include "atkwrapper.hxx" #include #include #include #include #include #include #include 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() ); } 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 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(); } 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(); /* * 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 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(blue) | ( static_cast(green) << 8 ) | ( static_cast(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() * 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(rAny.get()) ) { 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(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(); 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() ) { 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(); 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(fValue); return true; } /*****************************************************************************/ static const gchar * bool_values[] = { "true", "false" }; static gchar * Bool2String( const uno::Any& rAny ) { int n = 1; if( rAny.get() ) 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(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(dval * 100); return true; } /*****************************************************************************/ static gchar * CaseMap2String( const uno::Any& rAny ) { const gchar * value; switch( rAny.get() ) { 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(); 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 (); 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() ) return g_strdup(outline); } if( nReliefIndex != -1 ) { sal_Int16 n = rAttributeList[nReliefIndex].Value.get(); 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() ) value_list[count++] = const_cast (decorations[DECORATION_BLINK]); } if( nUnderlineIndex != -1 ) { sal_Int16 n = rAttributeList[nUnderlineIndex].Value.get (); if( n != awt::FontUnderline::NONE ) value_list[count++] = const_cast (decorations[DECORATION_UNDERLINE]); } if( nStrikeoutIndex != -1 ) { sal_Int16 n = rAttributeList[nStrikeoutIndex].Value.get (); if( n != awt::FontStrikeout::NONE && n != awt::FontStrikeout::DONTKNOW ) value_list[count++] = const_cast (decorations[DECORATION_LINE_THROUGH]); } if( count == 0 ) value_list[count++] = const_cast (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() ) n = 1; return g_strdup( shadow_values[n] ); } /*****************************************************************************/ static gchar * Short2Degree( const uno::Any& rAny ) { float f = rAny.get() / 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(); 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(); 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(); 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(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(p1); const char * pc = *static_cast(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(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(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(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 = aAttributeList; return true; } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */