Coverage Report

Created: 2026-10-14 09:33

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/svtools/source/misc/langtab.cxx
Line
Count
Source
1
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
/*
3
 * This file is part of the LibreOffice project.
4
 *
5
 * This Source Code Form is subject to the terms of the Mozilla Public
6
 * License, v. 2.0. If a copy of the MPL was not distributed with this
7
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8
 *
9
 * This file incorporates work covered by the following license notice:
10
 *
11
 *   Licensed to the Apache Software Foundation (ASF) under one or more
12
 *   contributor license agreements. See the NOTICE file distributed
13
 *   with this work for additional information regarding copyright
14
 *   ownership. The ASF licenses this file to you under the Apache
15
 *   License, Version 2.0 (the "License"); you may not use this file
16
 *   except in compliance with the License. You may obtain a copy of
17
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18
 */
19
20
#include <sal/config.h>
21
22
#include <string_view>
23
24
#include <com/sun/star/container/XNameAccess.hpp>
25
#include <com/sun/star/i18n/DirectionProperty.hpp>
26
#include <com/sun/star/i18n/ScriptType.hpp>
27
#include <com/sun/star/uno/Sequence.hxx>
28
#include <com/sun/star/uno/Any.h>
29
30
#include <comphelper/lok.hxx>
31
#include <i18nlangtag/lang.h>
32
#include <i18nlangtag/mslangid.hxx>
33
#include <i18nlangtag/languagetag.hxx>
34
#include <i18nlangtag/languagetagicu.hxx>
35
36
#include <i18nutil/unicode.hxx>
37
38
#include <sal/log.hxx>
39
#include <vcl/svapp.hxx>
40
#include <vcl/settings.hxx>
41
#include <svtools/svtresid.hxx>
42
#include <svtools/langtab.hxx>
43
#include <unotools/syslocale.hxx>
44
#include <unotools/charclass.hxx>
45
#include <tools/resary.hxx>
46
#include <officecfg/VCL.hxx>
47
#include <langtab.hrc>
48
49
using namespace ::com::sun::star;
50
51
namespace {
52
53
class SvtLanguageTableImpl
54
{
55
private:
56
    std::vector<std::pair<OUString, LanguageType>> m_aStrings;
57
    OUString m_aUILanguage; // UI language used to build the table
58
    const OUString& AddItem(const OUString& rLanguage, const LanguageType eType)
59
0
    {
60
0
        return m_aStrings.emplace_back(rLanguage, eType).first;
61
0
    }
62
    void            Build();
63
64
public:
65
66
    SvtLanguageTableImpl();
67
68
    // In LOKit mode, rebuild the table if the UI language has changed.
69
    void            RebuildIfNeeded();
70
71
    bool            HasType( const LanguageType eType ) const;
72
    const OUString& GetString( const LanguageType eType ) const;
73
    LanguageType    GetType( std::u16string_view rStr ) const;
74
    sal_uInt32      GetEntryCount() const;
75
    LanguageType    GetTypeAtIndex( sal_uInt32 nIndex ) const;
76
    LanguageType    GetValue(sal_uInt32 nIndex) const
77
0
    {
78
0
        return (nIndex < m_aStrings.size()) ? m_aStrings[nIndex].second : LANGUAGE_DONTKNOW;
79
0
    }
80
    sal_uInt32      FindIndex(LanguageType nValue) const
81
0
    {
82
0
        const size_t nItems = m_aStrings.size();
83
0
        for (size_t i = 0; i < nItems; ++i)
84
0
        {
85
0
            if (m_aStrings[i].second == nValue)
86
0
                return i;
87
0
        }
88
0
        return RESARRAY_INDEX_NOTFOUND;
89
0
    }
90
    const OUString& AddEntry( const OUString& rString, const LanguageType eType);
91
};
92
93
SvtLanguageTableImpl& theLanguageTable()
94
0
{
95
0
    static SvtLanguageTableImpl aTable;
96
0
    aTable.RebuildIfNeeded();
97
0
    return aTable;
98
0
}
99
}
100
101
OUString ApplyLreOrRleEmbedding( const OUString &rText )
102
0
{
103
0
    const sal_Int32 nLen = rText.getLength();
104
0
    if (nLen == 0)
105
0
        return OUString();
106
107
0
    constexpr sal_Unicode cLRE_Embedding  = 0x202A;      // the start char of an LRE embedding
108
0
    constexpr sal_Unicode cRLE_Embedding  = 0x202B;      // the start char of an RLE embedding
109
0
    constexpr sal_Unicode cPopDirectionalFormat = 0x202C;   // the unicode PDF (POP_DIRECTIONAL_FORMAT) char that terminates an LRE/RLE embedding
110
111
    // check if there are already embedding characters at the strings start
112
    // if so change nothing
113
0
    const sal_Unicode cChar = rText[0];
114
0
    if (cChar == cLRE_Embedding || cChar == cRLE_Embedding)
115
0
        return rText;
116
117
    // since we only call the function getCharacterDirection
118
    // it does not matter which locale the CharClass is for.
119
    // Thus we can readily make use of SvtSysLocale::GetCharClass()
120
    // which should come at no cost...
121
0
    SvtSysLocale aSysLocale;
122
0
    const CharClass &rCharClass = aSysLocale.GetCharClass();
123
124
    // we should look for the first non-neutral LTR or RTL character
125
    // and use that to determine the embedding of the whole text...
126
    // Thus we can avoid to check every character of the text.
127
0
    bool bFound     = false;
128
0
    bool bIsRtlText = false;
129
0
    for (sal_Int32 i = 0;  i < nLen && !bFound;  ++i)
130
0
    {
131
0
        i18n::DirectionProperty nDirection = rCharClass.getCharacterDirection( rText, i );
132
0
        switch (nDirection)
133
0
        {
134
0
            case i18n::DirectionProperty_LEFT_TO_RIGHT :
135
0
            case i18n::DirectionProperty_LEFT_TO_RIGHT_EMBEDDING :
136
0
            case i18n::DirectionProperty_LEFT_TO_RIGHT_OVERRIDE :
137
0
            case i18n::DirectionProperty_EUROPEAN_NUMBER :
138
0
            case i18n::DirectionProperty_ARABIC_NUMBER :        // yes! arabic numbers are written from left to right
139
0
            {
140
0
                bIsRtlText  = false;
141
0
                bFound      = true;
142
0
                break;
143
0
            }
144
145
0
            case i18n::DirectionProperty_RIGHT_TO_LEFT :
146
0
            case i18n::DirectionProperty_RIGHT_TO_LEFT_ARABIC :
147
0
            case i18n::DirectionProperty_RIGHT_TO_LEFT_EMBEDDING :
148
0
            case i18n::DirectionProperty_RIGHT_TO_LEFT_OVERRIDE :
149
0
            {
150
0
                bIsRtlText  = true;
151
0
                bFound      = true;
152
0
                break;
153
0
            }
154
155
0
            default:
156
0
            {
157
                // nothing to be done, character is considered to be neutral we need to look further ...
158
0
            }
159
0
        }
160
0
    }
161
162
0
    sal_Unicode cStart  = cLRE_Embedding;   // default is to use LRE embedding characters
163
0
    if (bIsRtlText)
164
0
        cStart = cRLE_Embedding;            // then use RLE embedding
165
166
    // add embedding start and end chars to the text if the direction could be determined
167
0
    OUString aRes( rText );
168
0
    if (bFound)
169
0
    {
170
0
        aRes = OUStringChar(cStart) + aRes
171
0
            + OUStringChar(cPopDirectionalFormat);
172
0
    }
173
174
0
    return aRes;
175
0
}
176
177
static OUString lcl_getDescription( const LanguageTag& rTag )
178
0
{
179
0
    OUString aStr( LanguageTagIcu::getDisplayName( rTag, Application::GetSettings().GetUILanguageTag()));
180
0
    if (aStr.isEmpty() || aStr == rTag.getBcp47())
181
0
    {
182
        // Place in curly brackets, so all on-the-fly tags without display name
183
        // are grouped together at the top of a listbox (but behind the
184
        // "[None]" entry), and not sprinkled all over, which alphabetically
185
        // might make sense in an English UI only anyway. Also a visual
186
        // indicator that it is a programmatical name, IMHO.
187
0
        return OUString::Concat("{") + aStr + "}";
188
0
    }
189
0
    else
190
0
    {
191
        // The ICU display name might be identical to a predefined name or even
192
        // to another tag's ICU name; clarify that this is a generated name and
193
        // append the language tag in curly brackets to distinguish.
194
0
        return aStr + " {" + rTag.getBcp47() + "}";
195
0
    }
196
0
}
197
198
SvtLanguageTableImpl::SvtLanguageTableImpl()
199
0
{
200
0
    Build();
201
0
}
202
203
void SvtLanguageTableImpl::Build()
204
0
{
205
0
    m_aStrings.clear();
206
207
0
    for (const auto& [rResId, rType] : STR_ARR_SVT_LANGUAGE_TABLE)
208
0
    {
209
0
        m_aStrings.emplace_back(SvtResId(rResId), rType);
210
0
    }
211
212
0
    auto xNA = officecfg::VCL::ExtraLanguages::get();
213
0
    const uno::Sequence <OUString> rElementNames = xNA->getElementNames();
214
0
    for (const OUString& rBcp47 : rElementNames)
215
0
    {
216
0
        OUString aName;
217
0
        sal_Int32 nType = 0;
218
0
        uno::Reference <container::XNameAccess> xNB;
219
0
        xNA->getByName(rBcp47) >>= xNB;
220
0
        bool bSuccess = (xNB->getByName(u"Name"_ustr) >>= aName) &&
221
0
                        (xNB->getByName(u"ScriptType"_ustr) >>= nType);
222
0
        if (bSuccess)
223
0
        {
224
0
            LanguageTag aLang(rBcp47);
225
0
            LanguageType nLangType = aLang.getLanguageType();
226
0
            if (nType <= sal_Int32(LanguageTag::ScriptType::RTL) && nType > sal_Int32(LanguageTag::ScriptType::UNKNOWN))
227
0
                aLang.setScriptType(LanguageTag::ScriptType(nType));
228
0
            sal_uInt32 nPos = FindIndex(nLangType);
229
0
            if (nPos == RESARRAY_INDEX_NOTFOUND)
230
0
                AddEntry( (aName.isEmpty() ? lcl_getDescription(aLang) : aName), nLangType);
231
0
        }
232
0
    }
233
234
0
    m_aUILanguage = SvtSysLocale().GetUILanguageTag().getBcp47();
235
0
}
236
237
void SvtLanguageTableImpl::RebuildIfNeeded()
238
0
{
239
0
    if (!comphelper::LibreOfficeKit::isActive())
240
0
        return;
241
242
0
    OUString aCurrLang = SvtSysLocale().GetUILanguageTag().getBcp47();
243
0
    if (m_aUILanguage != aCurrLang)
244
0
        Build();
245
0
}
246
247
bool SvtLanguageTableImpl::HasType( const LanguageType eType ) const
248
0
{
249
0
    LanguageType eLang = MsLangId::getReplacementForObsoleteLanguage( eType );
250
0
    sal_uInt32 nPos = FindIndex(eLang);
251
252
0
    return RESARRAY_INDEX_NOTFOUND != nPos && nPos < GetEntryCount();
253
0
}
254
255
bool SvtLanguageTable::HasLanguageType( const LanguageType eType )
256
0
{
257
0
    return theLanguageTable().HasType( eType );
258
0
}
259
260
const OUString& SvtLanguageTableImpl::GetString( const LanguageType eType ) const
261
0
{
262
0
    const LanguageType nLang = MsLangId::getReplacementForObsoleteLanguage( eType);
263
0
    const sal_uInt32 nPos = (eType == LANGUAGE_PROCESS_OR_USER_DEFAULT ?
264
0
            FindIndex(LANGUAGE_SYSTEM) : FindIndex( nLang));
265
266
0
    if ( RESARRAY_INDEX_NOTFOUND != nPos && nPos < GetEntryCount() )
267
0
        return m_aStrings[nPos].first;
268
269
    // Obtain from ICU, or a geeky but usable-in-a-pinch lang-tag.
270
0
    OUString sLangTag( lcl_getDescription( LanguageTag(nLang)));
271
0
    SAL_WARN("svtools.misc", "Language: 0x"
272
0
        << std::hex << nLang
273
0
        << " with unknown name, so returning generated: "
274
0
        << sLangTag);
275
276
    // And add it to the table, so it is available in all subsequent language boxes.
277
0
    return const_cast<SvtLanguageTableImpl*>(this)->AddEntry( sLangTag, nLang);
278
0
}
279
280
const OUString& SvtLanguageTable::GetLanguageString( const LanguageType eType )
281
0
{
282
0
    return theLanguageTable().GetString( eType );
283
0
}
284
285
LanguageType SvtLanguageTableImpl::GetType( std::u16string_view rStr ) const
286
0
{
287
0
    LanguageType eType = LANGUAGE_DONTKNOW;
288
0
    sal_uInt32 nCount = GetEntryCount();
289
290
0
    for ( sal_uInt32 i = 0; i < nCount; ++i )
291
0
    {
292
0
        if (m_aStrings[i].first == rStr)
293
0
        {
294
0
            eType = GetValue(i);
295
0
            break;
296
0
        }
297
0
    }
298
0
    return eType;
299
0
}
300
301
LanguageType SvtLanguageTable::GetLanguageType( std::u16string_view rStr )
302
0
{
303
0
    return theLanguageTable().GetType( rStr );
304
0
}
305
306
sal_uInt32 SvtLanguageTableImpl::GetEntryCount() const
307
0
{
308
0
    return m_aStrings.size();
309
0
}
310
311
sal_uInt32 SvtLanguageTable::GetLanguageEntryCount()
312
0
{
313
0
    return theLanguageTable().GetEntryCount();
314
0
}
315
316
317
LanguageType SvtLanguageTableImpl::GetTypeAtIndex( sal_uInt32 nIndex ) const
318
0
{
319
0
    LanguageType nType = LANGUAGE_DONTKNOW;
320
0
    if (nIndex < GetEntryCount())
321
0
        nType = GetValue(nIndex);
322
0
    return nType;
323
0
}
324
325
LanguageType SvtLanguageTable::GetLanguageTypeAtIndex( sal_uInt32 nIndex )
326
0
{
327
0
    return theLanguageTable().GetTypeAtIndex( nIndex);
328
0
}
329
330
const OUString& SvtLanguageTableImpl::AddEntry( const OUString& rString, const LanguageType eType )
331
0
{
332
0
    if (LanguageTag::isOnTheFlyID(eType)
333
0
            && LanguageTag::getOnTheFlyScriptType(eType) == LanguageTag::ScriptType::UNKNOWN)
334
0
    {
335
        // Classify the script type to distribute the entry into the proper
336
        // language list later.
337
0
        LanguageTag aLanguageTag(eType);
338
0
        const sal_Int16 nScriptClass = unicode::getScriptClassFromLanguageTag( aLanguageTag);
339
0
        LanguageTag::ScriptType eScriptType;
340
0
        switch (nScriptClass)
341
0
        {
342
0
            default:
343
0
                eScriptType = LanguageTag::ScriptType::WESTERN;
344
0
                assert(!"unexpected ScriptType");
345
0
            break;
346
0
            case css::i18n::ScriptType::WEAK:
347
0
            case css::i18n::ScriptType::LATIN:
348
0
                eScriptType = LanguageTag::ScriptType::WESTERN;
349
0
            break;
350
0
            case css::i18n::ScriptType::ASIAN:
351
0
                eScriptType = LanguageTag::ScriptType::CJK;
352
0
            break;
353
0
            case css::i18n::ScriptType::COMPLEX:
354
                /* TODO: determine if it would be LanguageTag::ScriptType::RTL
355
                 * instead; could that be done by
356
                 * getScriptClassFromLanguageTag() as well by asking Unicode
357
                 * properties? */
358
0
                eScriptType = LanguageTag::ScriptType::CTL;
359
0
            break;
360
0
        }
361
0
        aLanguageTag.setScriptType( eScriptType);
362
0
    }
363
0
    return AddItem( rString, eType);
364
0
}
365
366
void SvtLanguageTable::AddLanguageTag( const LanguageTag& rLanguageTag )
367
0
{
368
0
    theLanguageTable().AddEntry( lcl_getDescription(rLanguageTag), rLanguageTag.getLanguageType());
369
0
}
370
371
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */