cppcanvas/source/mtfrenderer/implrenderer.cxx | 20 +------- editeng/source/editeng/impedit.hxx | 1 editeng/source/editeng/impedit3.cxx | 18 +------ include/vcl/svapp.hxx | 8 ++- vcl/CppunitTest_vcl_localize_digits.mk | 26 ++++++++++ vcl/Module_vcl.mk | 1 vcl/qa/cppunit/LocalizeDigits.cxx | 64 ++++++++++++++++++++++++++ vcl/source/gdi/sallayout.cxx | 47 +++++++++++++++++++ vcl/source/outdev/text.cxx | 26 +--------- 9 files changed, 154 insertions(+), 57 deletions(-)
New commits: commit 95621a71f9ff4257b5c6f4be1eb11fc7328a2fa4 Author: Neil Roberts <[email protected]> AuthorDate: Wed Nov 5 14:05:08 2025 +0100 Commit: Noel Grandin <[email protected]> CommitDate: Wed Nov 5 19:21:18 2025 +0100 Add a unit test for LocalizeDigitsInString Change-Id: I51fb04dbc542b4052a31e8c96552406a1d6947e7 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/193461 Tested-by: Jenkins Reviewed-by: Noel Grandin <[email protected]> diff --git a/vcl/CppunitTest_vcl_localize_digits.mk b/vcl/CppunitTest_vcl_localize_digits.mk new file mode 100644 index 000000000000..b077dbca060e --- /dev/null +++ b/vcl/CppunitTest_vcl_localize_digits.mk @@ -0,0 +1,26 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t; fill-column: 100 -*- +# +# 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/. +# + +$(eval $(call gb_CppunitTest_CppunitTest,vcl_localize_digits)) + +$(eval $(call gb_CppunitTest_set_include,vcl_localize_digits,\ + $$(INCLUDE) \ + -I$(SRCDIR)/vcl/inc \ +)) + +$(eval $(call gb_CppunitTest_add_exception_objects,vcl_localize_digits, \ + vcl/qa/cppunit/LocalizeDigits \ +)) + +$(eval $(call gb_CppunitTest_use_libraries,vcl_localize_digits, \ + sal \ + vcl \ +)) + +# vim: set noet sw=4 ts=4: diff --git a/vcl/Module_vcl.mk b/vcl/Module_vcl.mk index a80744aad17a..517bdf723d39 100644 --- a/vcl/Module_vcl.mk +++ b/vcl/Module_vcl.mk @@ -254,6 +254,7 @@ $(eval $(call gb_Module_add_check_targets,vcl,\ CppunitTest_vcl_skia) \ CppunitTest_vcl_filter_igif \ CppunitTest_vcl_unit_conversion \ + CppunitTest_vcl_localize_digits \ )) ifeq ($(USING_X11),TRUE) diff --git a/vcl/qa/cppunit/LocalizeDigits.cxx b/vcl/qa/cppunit/LocalizeDigits.cxx new file mode 100644 index 000000000000..6062bacd0286 --- /dev/null +++ b/vcl/qa/cppunit/LocalizeDigits.cxx @@ -0,0 +1,64 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#include <sal/config.h> + +#include <cppunit/TestFixture.h> +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/plugin/TestPlugIn.h> + +#include <vcl/svapp.hxx> + +namespace +{ +CPPUNIT_TEST_FIXTURE(CppUnit::TestFixture, LocalDigits) +{ + // Test the case where there are no digits to convert + OUString sNoDigits = "hello"; + sal_Int32 nLen = sNoDigits.getLength(); + // It should return the same string + CPPUNIT_ASSERT_EQUAL(reinterpret_cast<sal_uIntPtr>(sNoDigits.getStr()), + reinterpret_cast<sal_uIntPtr>( + LocalizeDigitsInString(sNoDigits, LANGUAGE_FARSI, 0, nLen).getStr())); + // The length should not change + CPPUNIT_ASSERT_EQUAL(sNoDigits.getLength(), nLen); + + // Test the case where there are digits but they are already correct for the locale. + OUString sDozen = "There are 12 eggs in a dozen."; + nLen = sDozen.getLength(); + // It should return the same string + CPPUNIT_ASSERT_EQUAL(reinterpret_cast<sal_uIntPtr>(sDozen.getStr()), + reinterpret_cast<sal_uIntPtr>( + LocalizeDigitsInString(sDozen, LANGUAGE_SYSTEM, 0, nLen).getStr())); + // The length should not change + CPPUNIT_ASSERT_EQUAL(sDozen.getLength(), nLen); + + // Test an actual conversion + CPPUNIT_ASSERT_EQUAL(u"There are Ϋ±Ϋ² eggs in a dozen."_ustr, + LocalizeDigitsInString(sDozen, LANGUAGE_FARSI, 0, nLen)); + CPPUNIT_ASSERT_EQUAL(sDozen.getLength(), nLen); + + // Test converting a subrange + nLen = 12; + CPPUNIT_ASSERT_EQUAL( + u"There are Ϋ±Ϋ² eggs in a dozen but 13 in a baker's dozen."_ustr, + LocalizeDigitsInString("There are 12 eggs in a dozen but 13 in a baker's dozen.", + LANGUAGE_FARSI, 2, nLen)); + CPPUNIT_ASSERT_EQUAL(sal_Int32(12), nLen); + + // Test with characters outside of the bmp + CPPUNIT_ASSERT_EQUAL( + u"ππΊ πΈ Ϋ±Ϋ² π§ππ π¦π― π© ππ±ππΌπ ππ³ππ©π―."_ustr, + LocalizeDigitsInString(u"ππΊ πΈ 12 π§ππ π¦π― π© ππ±ππΌπ ππ³ππ©π―."_ustr, LANGUAGE_FARSI)); +} +} // namespace + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ commit 117d9624a3b861ead575394f6dfc974b4f4eeee6 Author: Neil Roberts <[email protected]> AuthorDate: Wed Nov 5 11:08:25 2025 +0100 Commit: Noel Grandin <[email protected]> CommitDate: Wed Nov 5 19:21:07 2025 +0100 Consolidate calls to GetLocalizedChar into one helper function The three places that call GetLocalizedChar were all functions that were doing the same thing, ie, localizing the digits in a string. Instead of having three separate copies of this similar code, this patch stops exporting GetLocalizedChar and instead exports a utility function that operates on a string. One of the three versions of the code avoided allocating a new string if nothing changes. This new consolidated helper uses the same approach so that the other two places can take advantage of this very likely optimisation. The main motivation to do this was to avoid truncating the sal_UCS4 returned by GetLocalizedChar into a sal_Unicode. The consolidated implementation adds the converted characters with appendUtf32 instead so that if GetLocalizedChar ever returns characters outside of the BMP then it would work. In practice this doesnβt currently happen so itβs not an actual problem. However the commit below makes the compiler warn when sal_UCS4 is truncated, so this patch fixes a truncation that was marked as a TODO in that commit. commit 82f055e5cb35501db06b4996909759f686de9631 Author: Noel Grandin <[email protected]> Date: Tue Nov 4 10:12:18 2025 +0200 new loplugin:narrow Change-Id: I628a29520a0896cd0f4c7c2ff131ac763e07925b Reviewed-on: https://gerrit.libreoffice.org/c/core/+/193460 Tested-by: Jenkins Reviewed-by: Noel Grandin <[email protected]> diff --git a/cppcanvas/source/mtfrenderer/implrenderer.cxx b/cppcanvas/source/mtfrenderer/implrenderer.cxx index d11f9cdada40..e7b04397507b 100644 --- a/cppcanvas/source/mtfrenderer/implrenderer.cxx +++ b/cppcanvas/source/mtfrenderer/implrenderer.cxx @@ -215,20 +215,6 @@ namespace return Bitmap( aSolid, aMask ); } - - OUString convertToLocalizedNumerals(std::u16string_view rStr, - LanguageType eTextLanguage) - { - OUStringBuffer aBuf(rStr); - for (sal_Int32 i = 0; i < aBuf.getLength(); ++i) - { - sal_Unicode nChar = aBuf[i]; - if (nChar >= '0' && nChar <= '9') - // TODO: are the localized digit surrogates? - aBuf[i] = static_cast<sal_Unicode>(GetLocalizedChar(nChar, eTextLanguage)); - } - return aBuf.makeStringAndClear(); - } } namespace cppcanvas::internal @@ -2438,7 +2424,7 @@ namespace cppcanvas::internal OUString sText = pAct->GetText(); if (rVDev.GetDigitLanguage()) - sText = convertToLocalizedNumerals(sText, rVDev.GetDigitLanguage()); + sText = LocalizeDigitsInString(sText, rVDev.GetDigitLanguage()); const sal_Int32 nLen = std::min(pAct->GetLen(), pAct->GetText().getLength() - pAct->GetIndex()); @@ -2460,7 +2446,7 @@ namespace cppcanvas::internal OUString sText = pAct->GetText(); if (rVDev.GetDigitLanguage()) - sText = convertToLocalizedNumerals(sText, rVDev.GetDigitLanguage()); + sText = LocalizeDigitsInString(sText, rVDev.GetDigitLanguage()); const sal_Int32 nLen = std::min(pAct->GetLen(), pAct->GetText().getLength() - pAct->GetIndex()); @@ -2540,7 +2526,7 @@ namespace cppcanvas::internal OUString sText = pAct->GetText(); if (rVDev.GetDigitLanguage()) - sText = convertToLocalizedNumerals(sText, rVDev.GetDigitLanguage()); + sText = LocalizeDigitsInString(sText, rVDev.GetDigitLanguage()); const sal_Int32 nLen = std::min(pAct->GetLen(), pAct->GetText().getLength() - pAct->GetIndex()); diff --git a/editeng/source/editeng/impedit.hxx b/editeng/source/editeng/impedit.hxx index 338145db8973..72eadc1b79ac 100644 --- a/editeng/source/editeng/impedit.hxx +++ b/editeng/source/editeng/impedit.hxx @@ -771,7 +771,6 @@ private: void ImplInitLayoutMode(OutputDevice& rOutDev, sal_Int32 nPara, sal_Int32 nIndex); static LanguageType ImplCalcDigitLang(LanguageType eCurLang); static void ImplInitDigitMode(OutputDevice& rOutDev, LanguageType eLang); - static OUString convertDigits(std::u16string_view rString, sal_Int32 nStt, sal_Int32 nLen, LanguageType eDigitLang); EditPaM ReadText( SvStream& rInput, EditSelection aSel ); EditPaM ReadRTF( SvStream& rInput, EditSelection aSel ); diff --git a/editeng/source/editeng/impedit3.cxx b/editeng/source/editeng/impedit3.cxx index 1298eb314b91..000d959c7b89 100644 --- a/editeng/source/editeng/impedit3.cxx +++ b/editeng/source/editeng/impedit3.cxx @@ -3906,8 +3906,9 @@ void ImpEditEngine::StripAllPortions( OutputDevice& rOutDev, tools::Rectangle aC const Color aTextLineColor(rOutDev.GetTextLineColor()); // Unicode code points conversion according to ctl text numeral setting - aText = convertDigits(aText, nTextStart, nTextLen, - ImplCalcDigitLang(aTmpFont.GetLanguage())); + aText = LocalizeDigitsInString(aText, + ImplCalcDigitLang(aTmpFont.GetLanguage()), + nTextStart, nTextLen); // StripPortions() data callback const DrawPortionInfo aInfo( @@ -4668,19 +4669,6 @@ LanguageType ImpEditEngine::ImplCalcDigitLang(LanguageType eCurLang) return eLang; } -OUString ImpEditEngine::convertDigits(std::u16string_view rString, sal_Int32 nStt, sal_Int32 nLen, LanguageType eDigitLang) -{ - OUStringBuffer aBuf(rString); - for (sal_Int32 nIdx = nStt, nEnd = nStt + nLen; nIdx < nEnd; ++nIdx) - { - sal_Unicode cChar = aBuf[nIdx]; - if (cChar >= '0' && cChar <= '9') - // TODO: are the localized digit surrogates? - aBuf[nIdx] = static_cast<sal_Unicode>(GetLocalizedChar(cChar, eDigitLang)); - } - return aBuf.makeStringAndClear(); -} - // Either sets the digit mode at the output device void ImpEditEngine::ImplInitDigitMode(OutputDevice& rOutDev, LanguageType eCurLang) { diff --git a/include/vcl/svapp.hxx b/include/vcl/svapp.hxx index ef908f61931b..43cb3f9535b0 100644 --- a/include/vcl/svapp.hxx +++ b/include/vcl/svapp.hxx @@ -84,7 +84,13 @@ namespace com::sun::star::awt { class XWindow; } -VCL_DLLPUBLIC sal_UCS4 GetLocalizedChar( sal_UCS4, LanguageType ); +VCL_DLLPUBLIC OUString LocalizeDigitsInString( const OUString&, LanguageType, + sal_Int32 nStart, sal_Int32& nLen ); +inline OUString LocalizeDigitsInString( const OUString& sStr, LanguageType eTextLanguage ) +{ + sal_Int32 nLen = sStr.getLength(); + return LocalizeDigitsInString(sStr, eTextLanguage, 0, nLen); +} enum class SystemWindowFlags { NOAUTOMODE = 0x0001, diff --git a/vcl/source/gdi/sallayout.cxx b/vcl/source/gdi/sallayout.cxx index aa9dca1d6e06..67b52fba8d39 100644 --- a/vcl/source/gdi/sallayout.cxx +++ b/vcl/source/gdi/sallayout.cxx @@ -47,6 +47,8 @@ #define GF_FONTMASK 0xF0000000 #define GF_FONTSHIFT 28 +namespace +{ sal_UCS4 GetLocalizedChar( sal_UCS4 nChar, LanguageType eLang ) { @@ -121,6 +123,51 @@ sal_UCS4 GetLocalizedChar( sal_UCS4 nChar, LanguageType eLang ) return nChar; } +} + +OUString LocalizeDigitsInString( const OUString& sStr, LanguageType eTextLanguage, + sal_Int32 nStart, sal_Int32& nLen ) +{ + sal_Int32 nextPos, nEnd = nStart + nLen; + + for (sal_Int32 i = nStart; i < nEnd; i = nextPos) + { + nextPos = i; + sal_uInt32 nChar = sStr.iterateCodePoints(&nextPos); + + sal_UCS4 nReplacementChar = GetLocalizedChar(nChar, eTextLanguage); + + // The first time we encounter a character that needs to change weβll make a copy of the + // string so we can return a new modified one + if (nReplacementChar != nChar) + { + // The new string is very likely to have the same length as the old one + OUStringBuffer xTmpStr(sStr.getLength()); + xTmpStr.append(sStr.subView(0, i)); + xTmpStr.appendUtf32(nReplacementChar); + + // Convert the remainder of the range + for (i = nextPos; i < nEnd;) + { + nReplacementChar = GetLocalizedChar(sStr.iterateCodePoints(&i), eTextLanguage); + xTmpStr.appendUtf32(nReplacementChar); + } + + // Add the rest of the string outside of the range + xTmpStr.append(sStr.subView(nEnd)); + + // The length of the string might have changed if GetLocalizedChar converts between BMP + // and surrogate pairs + nLen += xTmpStr.getLength() - sStr.getLength(); + + return xTmpStr.makeStringAndClear(); + } + } + + // Nothing changed so we can just return the original string + return sStr; +} + SalLayout::SalLayout() : mnMinCharPos( -1 ), mnEndCharPos( -1 ), diff --git a/vcl/source/outdev/text.cxx b/vcl/source/outdev/text.cxx index ba190b047cfe..4b1afddcfc14 100644 --- a/vcl/source/outdev/text.cxx +++ b/vcl/source/outdev/text.cxx @@ -994,29 +994,9 @@ vcl::text::ImplLayoutArgs OutputDevice::ImplPrepareLayoutArgs( OUString& rStr, if( meTextLanguage ) //TODO: (mnTextLayoutMode & vcl::text::ComplexTextLayoutFlags::SubstituteDigits) { - // disable character localization when no digits used - const sal_Unicode* pBase = rStr.getStr(); - const sal_Unicode* pStr = pBase + nMinIndex; - const sal_Unicode* pEnd = pBase + nEndIndex; - std::optional<OUStringBuffer> xTmpStr; - for( ; pStr < pEnd; ++pStr ) - { - // TODO: are there non-digit localizations? - if( (*pStr >= '0') && (*pStr <= '9') ) - { - // translate characters to local preference - sal_UCS4 cChar = GetLocalizedChar( *pStr, meTextLanguage ); - if( cChar != *pStr ) - { - if (!xTmpStr) - xTmpStr = OUStringBuffer(rStr); - // TODO: are the localized digit surrogates? - (*xTmpStr)[pStr - pBase] = static_cast<sal_Unicode>(cChar); - } - } - } - if (xTmpStr) - rStr = (*xTmpStr).makeStringAndClear(); + sal_Int32 nSubstringLen = nEndIndex - nMinIndex; + rStr = LocalizeDigitsInString(rStr, meTextLanguage, nMinIndex, nSubstringLen); + nEndIndex = nMinIndex + nSubstringLen; } // right align for RTL text, DRAWPOS_REVERSED, RTL window style
