svgio/CppunitTest_svgio.mk                    |    1 
 svgio/CppunitTest_svgio_tools.mk              |    1 
 svgio/Library_svgio.mk                        |    2 
 svgio/inc/svgdocument.hxx                     |    6 -
 svgio/inc/svgnode.hxx                         |    7 +
 svgio/inc/svgswitchnode.hxx                   |   55 +++++++++++
 svgio/inc/svgtoken.hxx                        |    1 
 svgio/qa/cppunit/SvgImportTest.cxx            |   23 ++++
 svgio/qa/cppunit/data/tdf160386.svg           |   16 +++
 svgio/source/svgreader/svgdocumenthandler.cxx |    9 +
 svgio/source/svgreader/svgnode.cxx            |   44 +++++++-
 svgio/source/svgreader/svgswitchnode.cxx      |  129 ++++++++++++++++++++++++++
 svgio/source/svgreader/svgtoken.cxx           |    1 
 vcl/inc/font/LogicalFontInstance.hxx          |    3 
 vcl/inc/impfontcache.hxx                      |    8 -
 vcl/inc/impglyphitem.hxx                      |    6 -
 vcl/qa/cppunit/logicalfontinstance.cxx        |   43 ++++++--
 vcl/source/font/LogicalFontInstance.cxx       |   50 ++++------
 vcl/source/font/fontcache.cxx                 |    4 
 vcl/source/gdi/CommonSalLayout.cxx            |    6 -
 vcl/source/gdi/pdfwriter_impl.cxx             |    4 
 vcl/source/gdi/sallayout.cxx                  |   24 ++--
 vcl/source/outdev/font.cxx                    |    4 
 vcl/workben/listglyphs.cxx                    |    2 
 24 files changed, 367 insertions(+), 82 deletions(-)

New commits:
commit 6f4a949c07eb06345df08c722f8e59e97888a499
Author:     Mike Kaganski <[email protected]>
AuthorDate: Fri Mar 29 20:15:06 2024 +0500
Commit:     Xisco Fauli <[email protected]>
CommitDate: Mon Apr 8 16:56:31 2024 +0200

    tdf#160430: Fix glyph bounds calculation, and use basegfx::B2DRectangle
    
    ... instead of tools::Rectangle.
    
    Several problems were there:
    1. First, a horizontal bounding rectangle was calculated, with due
    rounding; and then the result was rotated, and after that, rounded
    again. That made the resulting rotated rectangle coordinates very
    imprecise.
    2. Also, ceil/floor was applied without normalization; and in case
    of rotated font, that meant, that sometimes the range could be not
    expanded to cover partially covered pixels, but instead collapsed.
    3. The rotation to angles other than 90 degree multiples was done
    incorrectly, resulting in cut off parts of characters.
    4. For 90 degrees, the imprecise result of sin/cos converted 0.0
    into values like 3e-16, which then could be ceil'ed up to 1.
    
    Using B2DRectangle and its transform allows to simplify and fix
    the calculations easily, and avoids premature rounding. Render of
    rotated text of small size is more stable with this change.
    
    Change-Id: Idffd74b9937feb2418ab76a8d325fdaf4ff841b7
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/165553
    Tested-by: Jenkins
    Reviewed-by: Tomaž Vajngerl <[email protected]>
    Reviewed-by: Mike Kaganski <[email protected]>
    (cherry picked from commit 8962141a12c966b2d891829925e6203bf8d51852)
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/165619
    Reviewed-by: Xisco Fauli <[email protected]>

diff --git a/vcl/inc/font/LogicalFontInstance.hxx 
b/vcl/inc/font/LogicalFontInstance.hxx
index 40d3c57c4e67..73ba2e26a2b1 100644
--- a/vcl/inc/font/LogicalFontInstance.hxx
+++ b/vcl/inc/font/LogicalFontInstance.hxx
@@ -22,6 +22,7 @@
 #include <sal/config.h>
 
 #include <basegfx/polygon/b2dpolypolygon.hxx>
+#include <basegfx/range/b2drectangle.hxx>
 #include <o3tl/hash_combine.hxx>
 #include <rtl/ref.hxx>
 #include <salhelper/simplereferenceobject.hxx>
@@ -100,7 +101,7 @@ public: // TODO: make data members private
     vcl::font::PhysicalFontFace* GetFontFace() { return m_pFontFace.get(); }
     const ImplFontCache* GetFontCache() const { return mpFontCache; }
 
-    bool GetGlyphBoundRect(sal_GlyphId, tools::Rectangle&, bool) const;
+    bool GetGlyphBoundRect(sal_GlyphId, basegfx::B2DRectangle&, bool) const;
     virtual bool GetGlyphOutline(sal_GlyphId, basegfx::B2DPolyPolygon&, bool) 
const = 0;
     basegfx::B2DPolyPolygon GetGlyphOutlineUntransformed(sal_GlyphId) const;
 
diff --git a/vcl/inc/impfontcache.hxx b/vcl/inc/impfontcache.hxx
index 5ea19b05d9a5..4d197003b279 100644
--- a/vcl/inc/impfontcache.hxx
+++ b/vcl/inc/impfontcache.hxx
@@ -21,10 +21,10 @@
 
 #include <sal/config.h>
 
+#include <basegfx/range/b2drectangle.hxx>
 #include <rtl/ref.hxx>
 #include <o3tl/lru_map.hxx>
 #include <o3tl/hash_combine.hxx>
-#include <tools/gen.hxx>
 
 #include "font/FontSelectPattern.hxx"
 #include "glyphid.hxx"
@@ -59,7 +59,7 @@ struct GlyphBoundRectCacheHash
     }
 };
 
-typedef o3tl::lru_map<GlyphBoundRectCacheKey, tools::Rectangle,
+typedef o3tl::lru_map<GlyphBoundRectCacheKey, basegfx::B2DRectangle,
                       GlyphBoundRectCacheHash> GlyphBoundRectCache;
 
 class ImplFontCache
@@ -86,8 +86,8 @@ public:
                             LogicalFontInstance* pLogicalFont,
                             int nFallbackLevel, OUString& rMissingCodes );
 
-    bool GetCachedGlyphBoundRect(const LogicalFontInstance *, sal_GlyphId, 
tools::Rectangle &);
-    void CacheGlyphBoundRect(const LogicalFontInstance *, sal_GlyphId, 
tools::Rectangle &);
+    bool GetCachedGlyphBoundRect(const LogicalFontInstance*, sal_GlyphId, 
basegfx::B2DRectangle&);
+    void CacheGlyphBoundRect(const LogicalFontInstance*, sal_GlyphId, 
basegfx::B2DRectangle&);
 
     void                Invalidate();
 };
diff --git a/vcl/inc/impglyphitem.hxx b/vcl/inc/impglyphitem.hxx
index 1fa8454e2ea8..bb08031f3ab6 100644
--- a/vcl/inc/impglyphitem.hxx
+++ b/vcl/inc/impglyphitem.hxx
@@ -20,8 +20,8 @@
 #ifndef INCLUDED_VCL_IMPGLYPHITEM_HXX
 #define INCLUDED_VCL_IMPGLYPHITEM_HXX
 
+#include <basegfx/range/b2drectangle.hxx>
 #include <o3tl/typed_flags_set.hxx>
-#include <tools/gen.hxx>
 #include <vcl/dllapi.h>
 #include <vcl/rendercontext/SalLayoutFlags.hxx>
 #include <rtl/math.hxx>
@@ -89,7 +89,7 @@ public:
         return bool(m_nFlags & GlyphItemFlags::IS_SAFE_TO_INSERT_KASHIDA);
     }
 
-    inline bool GetGlyphBoundRect(const LogicalFontInstance*, 
tools::Rectangle&) const;
+    inline bool GetGlyphBoundRect(const LogicalFontInstance*, 
basegfx::B2DRectangle&) const;
     inline bool GetGlyphOutline(const LogicalFontInstance*, 
basegfx::B2DPolyPolygon&) const;
     inline void dropGlyph();
 
@@ -121,7 +121,7 @@ public:
 };
 
 bool GlyphItem::GetGlyphBoundRect(const LogicalFontInstance* pFontInstance,
-                                  tools::Rectangle& rRect) const
+                                  basegfx::B2DRectangle& rRect) const
 {
     return pFontInstance->GetGlyphBoundRect(m_aGlyphId, rRect, IsVertical());
 }
diff --git a/vcl/qa/cppunit/logicalfontinstance.cxx 
b/vcl/qa/cppunit/logicalfontinstance.cxx
index 6d5bbc4dafda..2a0e30d50c34 100644
--- a/vcl/qa/cppunit/logicalfontinstance.cxx
+++ b/vcl/qa/cppunit/logicalfontinstance.cxx
@@ -39,25 +39,46 @@ void VclLogicalFontInstanceTest::testglyphboundrect()
 {
     ScopedVclPtr<VirtualDevice> device = 
VclPtr<VirtualDevice>::Create(DeviceFormat::WITHOUT_ALPHA);
     device->SetOutputSizePixel(Size(1000, 1000));
-    device->SetFont(vcl::Font("Liberation Sans", Size(0, 110)));
+    vcl::Font font("Liberation Sans", Size(0, 110));
+    device->SetFont(font);
 
     const LogicalFontInstance* pFontInstance = device->GetFontInstance();
 
-    tools::Rectangle aBoundRect;
+    basegfx::B2DRectangle aBoundRect;
     const auto LATIN_SMALL_LETTER_B = 0x0062;
     
pFontInstance->GetGlyphBoundRect(pFontInstance->GetGlyphIndex(LATIN_SMALL_LETTER_B),
 aBoundRect,
                                      false);
 
-    const tools::Long nExpectedX = 7;
-    const tools::Long nExpectedY = -80;
-    const tools::Long nExpectedWidth = 51;
-    const tools::Long nExpectedHeight = 83;
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(7.1, aBoundRect.getMinX(), 0.05);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(-79.7, aBoundRect.getMinY(), 0.05);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(49.5, aBoundRect.getWidth(), 0.05);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(80.8, aBoundRect.getHeight(), 0.05);
 
-    CPPUNIT_ASSERT_EQUAL_MESSAGE("x of glyph is wrong", nExpectedX, 
aBoundRect.getX());
-    CPPUNIT_ASSERT_EQUAL_MESSAGE("y of glyph is wrong", nExpectedY, 
aBoundRect.getY());
-    CPPUNIT_ASSERT_EQUAL_MESSAGE("height of glyph of wrong", nExpectedWidth, 
aBoundRect.GetWidth());
-    CPPUNIT_ASSERT_EQUAL_MESSAGE("width of glyph of wrong", nExpectedHeight,
-                                 aBoundRect.GetHeight());
+    font.SetOrientation(900_deg10);
+    device->SetFont(font);
+
+    pFontInstance = device->GetFontInstance();
+
+    
pFontInstance->GetGlyphBoundRect(pFontInstance->GetGlyphIndex(LATIN_SMALL_LETTER_B),
 aBoundRect,
+                                     false);
+
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(-79.7, aBoundRect.getMinX(), 0.05);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(-56.6, aBoundRect.getMinY(), 0.05);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(80.8, aBoundRect.getWidth(), 0.05);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(49.5, aBoundRect.getHeight(), 0.05);
+
+    font.SetOrientation(450_deg10);
+    device->SetFont(font);
+
+    pFontInstance = device->GetFontInstance();
+
+    
pFontInstance->GetGlyphBoundRect(pFontInstance->GetGlyphIndex(LATIN_SMALL_LETTER_B),
 aBoundRect,
+                                     false);
+
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(-51.3, aBoundRect.getMinX(), 0.05);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(-96.4, aBoundRect.getMinY(), 0.05);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(92.1, aBoundRect.getWidth(), 0.05);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(92.1, aBoundRect.getHeight(), 0.05);
 }
 
 CPPUNIT_TEST_SUITE_REGISTRATION(VclLogicalFontInstanceTest);
diff --git a/vcl/source/font/LogicalFontInstance.cxx 
b/vcl/source/font/LogicalFontInstance.cxx
index 0c21cba47548..fbb115825828 100644
--- a/vcl/source/font/LogicalFontInstance.cxx
+++ b/vcl/source/font/LogicalFontInstance.cxx
@@ -26,6 +26,8 @@
 #include <font/LogicalFontInstance.hxx>
 #include <impfontcache.hxx>
 
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+
 LogicalFontInstance::LogicalFontInstance(const vcl::font::PhysicalFontFace& 
rFontFace,
                                          const vcl::font::FontSelectPattern& 
rFontSelData)
     : mxFontMetric(new FontMetricData(rFontSelData))
@@ -166,47 +168,39 @@ void 
LogicalFontInstance::IgnoreFallbackForUnicode(sal_UCS4 cChar, FontWeight eW
         maUnicodeFallbackList.erase(it);
 }
 
-bool LogicalFontInstance::GetGlyphBoundRect(sal_GlyphId nID, tools::Rectangle& 
rRect,
+bool LogicalFontInstance::GetGlyphBoundRect(sal_GlyphId nID, 
basegfx::B2DRectangle& rRect,
                                             bool bVertical) const
 {
+    // TODO/FIXME: bVertical handling here is highly suspicious. When it's 
true, it may
+    // return different rectangle, depending on if this glyph was cached 
already or not.
+
     if (mpFontCache && mpFontCache->GetCachedGlyphBoundRect(this, nID, rRect))
         return true;
 
     auto* pHbFont = const_cast<LogicalFontInstance*>(this)->GetHbFont();
     hb_glyph_extents_t aExtents;
-    bool res = hb_font_get_glyph_extents(pHbFont, nID, &aExtents);
+    if (!hb_font_get_glyph_extents(pHbFont, nID, &aExtents))
+        return false;
 
-    if (res)
-    {
-        double nXScale = 0, nYScale = 0;
-        GetScale(&nXScale, &nYScale);
+    double nXScale = 0, nYScale = 0;
+    GetScale(&nXScale, &nYScale);
 
-        double fMinX = aExtents.x_bearing;
-        double fMinY = aExtents.y_bearing;
-        double fMaxX = aExtents.x_bearing + aExtents.width;
-        double fMaxY = aExtents.y_bearing + aExtents.height;
+    double fMinX = aExtents.x_bearing * nXScale;
+    double fMinY = -aExtents.y_bearing * nYScale;
+    double fMaxX = (aExtents.x_bearing + aExtents.width) * nXScale;
+    double fMaxY = -(aExtents.y_bearing + aExtents.height) * nYScale;
+    rRect = basegfx::B2DRectangle(fMinX, fMinY, fMaxX, fMaxY);
 
-        tools::Rectangle aRect(std::floor(fMinX * nXScale), -std::ceil(fMinY * 
nYScale),
-                               std::ceil(fMaxX * nXScale), -std::floor(fMaxY * 
nYScale));
-        if (mnOrientation && !bVertical)
-        {
-            // Apply font rotation.
-            const double fRad = toRadians(mnOrientation);
-            const double fCos = cos(fRad);
-            const double fSin = sin(fRad);
-
-            rRect.SetLeft(fCos * aRect.Left() + fSin * aRect.Top());
-            rRect.SetTop(-fSin * aRect.Left() - fCos * aRect.Top());
-            rRect.SetRight(fCos * aRect.Right() + fSin * aRect.Bottom());
-            rRect.SetBottom(-fSin * aRect.Right() - fCos * aRect.Bottom());
-        }
-        else
-            rRect = aRect;
+    if (mnOrientation && !bVertical)
+    {
+        // Apply font rotation.
+        
rRect.transform(basegfx::utils::createRotateB2DHomMatrix(-toRadians(mnOrientation)));
     }
 
-    if (mpFontCache && res)
+    if (mpFontCache)
         mpFontCache->CacheGlyphBoundRect(this, nID, rRect);
-    return res;
+
+    return true;
 }
 
 sal_GlyphId LogicalFontInstance::GetGlyphIndex(uint32_t nUnicode, uint32_t 
nVariationSelector) const
diff --git a/vcl/source/font/fontcache.cxx b/vcl/source/font/fontcache.cxx
index c0dba153502e..ce4ba6adf64c 100644
--- a/vcl/source/font/fontcache.cxx
+++ b/vcl/source/font/fontcache.cxx
@@ -252,7 +252,7 @@ void ImplFontCache::Invalidate()
     m_aBoundRectCache.clear();
 }
 
-bool ImplFontCache::GetCachedGlyphBoundRect(const LogicalFontInstance *pFont, 
sal_GlyphId nID, tools::Rectangle &rRect)
+bool ImplFontCache::GetCachedGlyphBoundRect(const LogicalFontInstance *pFont, 
sal_GlyphId nID, basegfx::B2DRectangle &rRect)
 {
     if (!pFont->GetFontCache())
         return false;
@@ -269,7 +269,7 @@ bool ImplFontCache::GetCachedGlyphBoundRect(const 
LogicalFontInstance *pFont, sa
     return false;
 }
 
-void ImplFontCache::CacheGlyphBoundRect(const LogicalFontInstance *pFont, 
sal_GlyphId nID, tools::Rectangle &rRect)
+void ImplFontCache::CacheGlyphBoundRect(const LogicalFontInstance *pFont, 
sal_GlyphId nID, basegfx::B2DRectangle &rRect)
 {
     if (!pFont->GetFontCache())
         return;
diff --git a/vcl/source/gdi/CommonSalLayout.cxx 
b/vcl/source/gdi/CommonSalLayout.cxx
index bcf6f54639e8..455428e7f396 100644
--- a/vcl/source/gdi/CommonSalLayout.cxx
+++ b/vcl/source/gdi/CommonSalLayout.cxx
@@ -534,12 +534,12 @@ bool 
GenericSalLayout::LayoutText(vcl::text::ImplLayoutArgs& rArgs, const SalLay
                     {
                         // We need glyph's advance, top bearing, and height to
                         // correct y offset.
-                        tools::Rectangle aRect;
+                        basegfx::B2DRectangle aRect;
                         // Get cached bound rect value for the font,
                         GetFont().GetGlyphBoundRect(nGlyphIndex, aRect, true);
 
-                        nXOffset = -(aRect.Top() / nXScale  + ( 
pHbPositions[i].y_advance
-                                    + ( aRect.GetHeight() / nXScale ) ) / 2.0 
);
+                        nXOffset = -(aRect.getMinX() / nXScale  + ( 
pHbPositions[i].y_advance
+                                    + ( aRect.getHeight() / nXScale ) ) / 2.0 
);
                     }
 
                 }
diff --git a/vcl/source/gdi/pdfwriter_impl.cxx 
b/vcl/source/gdi/pdfwriter_impl.cxx
index a5365e681b3b..fba7a85db430 100644
--- a/vcl/source/gdi/pdfwriter_impl.cxx
+++ b/vcl/source/gdi/pdfwriter_impl.cxx
@@ -7091,7 +7091,7 @@ void PDFWriterImpl::drawLayout( SalLayout& rLayout, const 
OUString& rText, bool
     else if ( eAlign == ALIGN_TOP )
         aOffset.AdjustY(GetFontInstance()->mxFontMetric->GetAscent() );
 
-    tools::Rectangle aRectangle;
+    basegfx::B2DRectangle aRectangle;
     nIndex = 0;
     while (rLayout.GetNextGlyph(&pGlyph, aPos, nIndex, &pGlyphFont))
     {
@@ -7109,7 +7109,7 @@ void PDFWriterImpl::drawLayout( SalLayout& rLayout, const 
OUString& rText, bool
             else
             {
                 aAdjOffset = basegfx::B2DPoint(aOffset.X(), aOffset.Y());
-                aAdjOffset.adjustX(aRectangle.Left() + (aRectangle.GetWidth() 
- aEmphasisMark.GetWidth()) / 2 );
+                aAdjOffset.adjustX(aRectangle.getMinX() + 
(aRectangle.getWidth() - aEmphasisMark.GetWidth()) / 2 );
             }
 
             aAdjOffset = aRotScale.transform( aAdjOffset );
diff --git a/vcl/source/gdi/sallayout.cxx b/vcl/source/gdi/sallayout.cxx
index d052ab53221b..756a427f1dda 100644
--- a/vcl/source/gdi/sallayout.cxx
+++ b/vcl/source/gdi/sallayout.cxx
@@ -214,12 +214,15 @@ bool SalLayout::GetOutline(basegfx::B2DPolyPolygonVector& 
rVector) const
     return (bAllOk && bOneOk);
 }
 
+// No need to expand to the next pixel, when the character only covers its 
tiny fraction
+static double trimInsignificant(double n) { return std::round(n * 1e5) / 1e5; }
+
 bool SalLayout::GetBoundRect(tools::Rectangle& rRect) const
 {
     bool bRet = false;
-    rRect.SetEmpty();
 
-    tools::Rectangle aRectangle;
+    basegfx::B2DRectangle aUnion;
+    basegfx::B2DRectangle aRectangle;
 
     basegfx::B2DPoint aPos;
     const GlyphItem* pGlyph;
@@ -230,22 +233,19 @@ bool SalLayout::GetBoundRect(tools::Rectangle& rRect) 
const
         // get bounding rectangle of individual glyph
         if (pGlyph->GetGlyphBoundRect(pGlyphFont, aRectangle))
         {
-            if (!aRectangle.IsEmpty())
+            if (!aRectangle.isEmpty())
             {
-                aRectangle.AdjustLeft(std::floor(aPos.getX()));
-                aRectangle.AdjustRight(std::ceil(aPos.getX()));
-                aRectangle.AdjustTop(std::floor(aPos.getY()));
-                aRectangle.AdjustBottom(std::ceil(aPos.getY()));
-
+                
aRectangle.transform(basegfx::utils::createTranslateB2DHomMatrix(aPos));
                 // merge rectangle
-                if (rRect.IsEmpty())
-                    rRect = aRectangle;
-                else
-                    rRect.Union(aRectangle);
+                aUnion.expand(aRectangle);
             }
             bRet = true;
         }
     }
+    rRect = 
tools::Rectangle(rtl::math::approxFloor(trimInsignificant(aUnion.getMinX())),
+                             
rtl::math::approxFloor(trimInsignificant(aUnion.getMinY())),
+                             
rtl::math::approxCeil(trimInsignificant(aUnion.getMaxX())),
+                             
rtl::math::approxCeil(trimInsignificant(aUnion.getMaxY())));
 
     return bRet;
 }
diff --git a/vcl/source/outdev/font.cxx b/vcl/source/outdev/font.cxx
index 2086db7f6341..9c043731591a 100644
--- a/vcl/source/outdev/font.cxx
+++ b/vcl/source/outdev/font.cxx
@@ -951,7 +951,7 @@ void OutputDevice::ImplDrawEmphasisMarks( SalLayout& 
rSalLayout )
     aOffset += Point( nEmphasisWidth2, nEmphasisHeight2 );
 
     basegfx::B2DPoint aOutPoint;
-    tools::Rectangle aRectangle;
+    basegfx::B2DRectangle aRectangle;
     const GlyphItem* pGlyph;
     const LogicalFontInstance* pGlyphFont;
     int nStart = 0;
@@ -971,7 +971,7 @@ void OutputDevice::ImplDrawEmphasisMarks( SalLayout& 
rSalLayout )
             else
             {
                 aAdjPoint = aOffset;
-                aAdjPoint.AdjustX(aRectangle.Left() + (aRectangle.GetWidth() - 
aEmphasisMark.GetWidth()) / 2 );
+                aAdjPoint.AdjustX(aRectangle.getMinX() + 
(aRectangle.getWidth() - aEmphasisMark.GetWidth()) / 2 );
             }
 
             if ( mpFontInstance->mnOrientation )
diff --git a/vcl/workben/listglyphs.cxx b/vcl/workben/listglyphs.cxx
index def2ff818122..341006d433dd 100644
--- a/vcl/workben/listglyphs.cxx
+++ b/vcl/workben/listglyphs.cxx
@@ -120,7 +120,7 @@ int ListGlyphs::Main()
              nChar = pCharMap->GetNextChar(nChar))
         {
             auto nGlyphIndex = pFontInstance->GetGlyphIndex(nChar);
-            tools::Rectangle aGlyphBounds;
+            basegfx::B2DRectangle aGlyphBounds;
             pFontInstance->GetGlyphBoundRect(nGlyphIndex, aGlyphBounds, false);
             std::cout << "Codepoint: " << pFontFace->GetGlyphName(nGlyphIndex)
                       << "; glyph bounds: " << aGlyphBounds << "
";
commit 8575b792c32613e44324606393cf7a6f136472b3
Author:     Xisco Fauli <[email protected]>
AuthorDate: Wed Mar 27 11:38:44 2024 +0100
Commit:     Xisco Fauli <[email protected]>
CommitDate: Mon Apr 8 16:56:25 2024 +0200

    tdf#160386: Add support for switch element
    
    For now, only use language tag, meaning if
    there is a file like in the unittest with
    
    <text systemLanguage="en-us">Howdy!</text>
    <text systemLanguage="en-gb">Wotcha!</text>
    <text systemLanguage="en-au">G'day!</text>
    <text systemLanguage="en">Hello!</text>
    
    "Hello!" with be displayed in a en_AU system locale
    
    This patch partially reverts 13a41e7a12598c7896d6dc8d34aba6af5b80b83c
    "tdf#150124: do nothing when parent is of unkown type"
    making 0dfd8288a87b58e503bb3a41be6137485fbf3f68
    "ofz#60384 Direct-leak" no longer necessary
    
    Change-Id: Ifc73bc69aa997088dc0a2b11d7d30446303fa3b3
    
    Change-Id: I885ef0f2c44b86196881fe55a963db2e5c7eb1be
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/165394
    Tested-by: Jenkins
    Reviewed-by: Xisco Fauli <[email protected]>
    Signed-off-by: Xisco Fauli <[email protected]>
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/165438

diff --git a/svgio/CppunitTest_svgio.mk b/svgio/CppunitTest_svgio.mk
index 9309f5dcb9b8..a179d6af30fa 100644
--- a/svgio/CppunitTest_svgio.mk
+++ b/svgio/CppunitTest_svgio.mk
@@ -34,6 +34,7 @@ $(eval $(call gb_CppunitTest_use_libraries,svgio,\
     cppu \
     cppuhelper \
     comphelper \
+    i18nlangtag \
     sal \
     salhelper \
     sax \
diff --git a/svgio/CppunitTest_svgio_tools.mk b/svgio/CppunitTest_svgio_tools.mk
index abb6bb6e0fc7..5f4d7adbe523 100644
--- a/svgio/CppunitTest_svgio_tools.mk
+++ b/svgio/CppunitTest_svgio_tools.mk
@@ -33,6 +33,7 @@ $(eval $(call gb_CppunitTest_use_libraries,svgio_tools,\
     comphelper \
     cppu \
     cppuhelper \
+    i18nlangtag \
     sal \
     salhelper \
     sax \
diff --git a/svgio/Library_svgio.mk b/svgio/Library_svgio.mk
index edd83ed57251..45b34a26b997 100644
--- a/svgio/Library_svgio.mk
+++ b/svgio/Library_svgio.mk
@@ -41,6 +41,7 @@ $(eval $(call gb_Library_use_libraries,svgio,\
     comphelper \
     cppu \
     cppuhelper \
+    i18nlangtag \
     sal \
     salhelper \
     tk \
@@ -83,6 +84,7 @@ $(eval $(call gb_Library_add_exception_objects,svgio,\
     svgio/source/svgreader/svgstyleattributes \
     svgio/source/svgreader/svgstylenode \
     svgio/source/svgreader/svgsvgnode \
+    svgio/source/svgreader/svgswitchnode \
     svgio/source/svgreader/svgsymbolnode \
     svgio/source/svgreader/svgtextnode \
     svgio/source/svgreader/svgtextposition \
diff --git a/svgio/inc/svgdocument.hxx b/svgio/inc/svgdocument.hxx
index 77b4d3891179..9f79342c0c55 100644
--- a/svgio/inc/svgdocument.hxx
+++ b/svgio/inc/svgdocument.hxx
@@ -34,9 +34,6 @@ namespace svgio::svgreader
             /// the document hierarchy with all root nodes
             SvgNodeVector           maNodes;
 
-            /// invalid nodes that have no parent
-            SvgNodeVector           maOrphanNodes;
-
             /// the absolute path of the Svg file in progress (if available)
             const OUString     maAbsolutePath;
 
@@ -75,9 +72,6 @@ namespace svgio::svgreader
             /// data read access
             const SvgNodeVector& getSvgNodeVector() const { return maNodes; }
             const OUString& getAbsolutePath() const { return maAbsolutePath; }
-
-            /// invalid nodes that have no parent
-            void addOrphanNode(SvgNode* pOrphan) { 
maOrphanNodes.emplace_back(pOrphan); }
         };
 
 } // end of namespace svgio::svgreader
diff --git a/svgio/inc/svgnode.hxx b/svgio/inc/svgnode.hxx
index 63abc4f8cb0a..16c1f50bc3db 100644
--- a/svgio/inc/svgnode.hxx
+++ b/svgio/inc/svgnode.hxx
@@ -95,6 +95,9 @@ namespace svgio::svgreader
             /// Class svan value
             std::optional<OUString>   mpClass;
 
+            /// systemLanguage values
+            std::vector<OUString>  maSystemLanguage;
+
             /// XmlSpace value
             XmlSpace                    maXmlSpace;
 
@@ -174,6 +177,10 @@ namespace svgio::svgreader
             std::optional<OUString> const & getClass() const { return mpClass; 
}
             void setClass(OUString const &);
 
+            /// SystemLanguage access
+            std::vector<OUString> const & getSystemLanguage() const { return 
maSystemLanguage; }
+            void setSystemLanguage(OUString const &);
+
             /// XmlSpace access
             XmlSpace getXmlSpace() const;
             void setXmlSpace(XmlSpace eXmlSpace) { maXmlSpace = eXmlSpace; }
diff --git a/svgio/inc/svgswitchnode.hxx b/svgio/inc/svgswitchnode.hxx
new file mode 100644
index 000000000000..b83ae0c4ac0f
--- /dev/null
+++ b/svgio/inc/svgswitchnode.hxx
@@ -0,0 +1,55 @@
+/* -*- 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 .
+ */
+
+#pragma once
+
+#include "svgstyleattributes.hxx"
+#include <basegfx/matrix/b2dhommatrix.hxx>
+
+namespace svgio::svgreader
+{
+class SvgSwitchNode final : public SvgNode
+{
+private:
+    /// use styles
+    SvgStyleAttributes maSvgStyleAttributes;
+
+    /// variable scan values, dependent of given XAttributeList
+    std::optional<basegfx::B2DHomMatrix> mpaTransform;
+
+public:
+    SvgSwitchNode(SvgDocument& rDocument, SvgNode* pParent);
+    virtual ~SvgSwitchNode() override;
+
+    virtual const SvgStyleAttributes* getSvgStyleAttributes() const override;
+    virtual void parseAttribute(SVGToken aSVGToken, const OUString& aContent) 
override;
+    virtual void 
decomposeSvgNode(drawinglayer::primitive2d::Primitive2DContainer& rTarget,
+                                  bool bReferenced) const override;
+
+    /// transform content
+    const std::optional<basegfx::B2DHomMatrix>& getTransform() const { return 
mpaTransform; }
+    void setTransform(const std::optional<basegfx::B2DHomMatrix>& pMatrix)
+    {
+        mpaTransform = pMatrix;
+    }
+};
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/inc/svgtoken.hxx b/svgio/inc/svgtoken.hxx
index 26a5d8f5f423..26ef86efc5f9 100644
--- a/svgio/inc/svgtoken.hxx
+++ b/svgio/inc/svgtoken.hxx
@@ -109,6 +109,7 @@ namespace svgio::svgreader
             PatternContentUnits,
             PatternTransform,
             Opacity,
+            SystemLanguage,
             Visibility,
             Title,
             Desc,
diff --git a/svgio/qa/cppunit/SvgImportTest.cxx 
b/svgio/qa/cppunit/SvgImportTest.cxx
index c7188dedd7c0..6969dc406a0e 100644
--- a/svgio/qa/cppunit/SvgImportTest.cxx
+++ b/svgio/qa/cppunit/SvgImportTest.cxx
@@ -38,6 +38,7 @@ protected:
     void checkRectPrimitive(Primitive2DSequence const & rPrimitive);
 
     Primitive2DSequence parseSvg(std::u16string_view aSource);
+    xmlDocUniquePtr dumpAndParseSvg(std::u16string_view aSource);
 };
 
 Primitive2DSequence Test::parseSvg(std::u16string_view aSource)
@@ -59,6 +60,17 @@ Primitive2DSequence Test::parseSvg(std::u16string_view 
aSource)
     return xSvgParser->getDecomposition(aInputStream, aPath);
 }
 
+xmlDocUniquePtr Test::dumpAndParseSvg(std::u16string_view aSource)
+{
+    Primitive2DSequence aSequence = parseSvg(aSource);
+
+    drawinglayer::Primitive2dXmlDump dumper;
+    xmlDocUniquePtr pDocument = dumper.dumpAndParse(aSequence);
+
+    CPPUNIT_ASSERT (pDocument);
+    return pDocument;
+}
+
 void Test::checkRectPrimitive(Primitive2DSequence const & rPrimitive)
 {
     drawinglayer::Primitive2dXmlDump dumper;
@@ -385,6 +397,17 @@ CPPUNIT_TEST_FIXTURE(Test, testFontsizeRelative)
     assertXPath(pDocument, "/primitive2D/transform/textsimpleportion[2]"_ostr, 
"familyname"_ostr, "DejaVu Serif");
 }
 
+CPPUNIT_TEST_FIXTURE(Test, testTdf160386)
+{
+    xmlDocUniquePtr pDocument = 
dumpAndParseSvg(u"/svgio/qa/cppunit/data/tdf160386.svg");
+
+    // Without the fix in place, this test would have failed with
+    // - Expected: 1
+    // - Actual  : 11
+    assertXPath(pDocument, "/primitive2D/transform/textsimpleportion"_ostr, 1);
+    assertXPath(pDocument, "/primitive2D/transform/textsimpleportion"_ostr, 
"text"_ostr, "Hello!");
+}
+
 CPPUNIT_TEST_FIXTURE(Test, testTdf145896)
 {
     Primitive2DSequence aSequence = 
parseSvg(u"/svgio/qa/cppunit/data/tdf145896.svg");
diff --git a/svgio/qa/cppunit/data/tdf160386.svg 
b/svgio/qa/cppunit/data/tdf160386.svg
new file mode 100644
index 000000000000..1644b0d15514
--- /dev/null
+++ b/svgio/qa/cppunit/data/tdf160386.svg
@@ -0,0 +1,16 @@
+<svg viewBox="0 -20 100 50" xmlns="http://www.w3.org/2000/svg";>
+  <switch font-family="DejaVu Sans">
+    <text systemLanguage="ar">مرحبا</text>
+    <text systemLanguage="de,nl">Hallo!</text>
+    <text systemLanguage="en-us">Howdy!</text>
+    <text systemLanguage="en-gb">Wotcha!</text>
+    <text systemLanguage="en-au">G'day!</text>
+    <text systemLanguage="en">Hello!</text>
+    <text systemLanguage="es">Hola!</text>
+    <text systemLanguage="fr">Bonjour!</text>
+    <text systemLanguage="ja">こんにちは</text>
+    <text systemLanguage="ru">Привет!</text>
+    <text>☺</text>
+  </switch>
+</svg>
+
diff --git a/svgio/source/svgreader/svgdocumenthandler.cxx 
b/svgio/source/svgreader/svgdocumenthandler.cxx
index 5e89edad6ca7..8d2cc8849c55 100644
--- a/svgio/source/svgreader/svgdocumenthandler.cxx
+++ b/svgio/source/svgreader/svgdocumenthandler.cxx
@@ -27,6 +27,7 @@
 #include <svgrectnode.hxx>
 #include <svggradientnode.hxx>
 #include <svggradientstopnode.hxx>
+#include <svgswitchnode.hxx>
 #include <svgsymbolnode.hxx>
 #include <svgusenode.hxx>
 #include <svgcirclenode.hxx>
@@ -199,7 +200,13 @@ namespace
                     mpTarget->parseAttributes(xAttribs);
                     break;
                 }
-                case SVGToken::Switch: //TODO: Support switch element
+                case SVGToken::Switch:
+                {
+                    /// new node for Switch
+                    mpTarget = new SvgSwitchNode(maDocument, mpTarget);
+                    mpTarget->parseAttributes(xAttribs);
+                    break;
+                }
                 case SVGToken::Defs:
                 case SVGToken::G:
                 {
diff --git a/svgio/source/svgreader/svgnode.cxx 
b/svgio/source/svgreader/svgnode.cxx
index 0ae4e80363c8..7a45c681f19c 100644
--- a/svgio/source/svgreader/svgnode.cxx
+++ b/svgio/source/svgreader/svgnode.cxx
@@ -367,17 +367,13 @@ namespace {
             mpParent(pParent),
             mpAlternativeParent(nullptr),
             maXmlSpace(XmlSpace::NotSet),
-            maDisplay(Display::Inline),
+            maDisplay(maType == SVGToken::Unknown ? Display::None : 
Display::Inline), // tdf#150124: do not display unknown nodes
             mbDecomposing(false),
             mbCssStyleVectorBuilt(false)
         {
             if (pParent)
             {
-                // tdf#150124 ignore when parent is unknown
-                if (pParent->getType() != SVGToken::Unknown)
-                    pParent->maChildren.emplace_back(this);
-                else
-                    mrDocument.addOrphanNode(this);
+                pParent->maChildren.emplace_back(this);
             }
         }
 
@@ -527,6 +523,14 @@ namespace {
                     }
                     break;
                 }
+                case SVGToken::SystemLanguage:
+                {
+                    if(!aContent.isEmpty())
+                    {
+                        setSystemLanguage(aContent);
+                    }
+                    break;
+                }
                 case SVGToken::XmlSpace:
                 {
                     if(!aContent.isEmpty())
@@ -752,6 +756,34 @@ namespace {
             mrDocument.addSvgNodeToMapper(*mpClass, *this);
         }
 
+        void SvgNode::setSystemLanguage(OUString const & rSystemClass)
+        {
+            const sal_Int32 nLen(rSystemClass.getLength());
+            sal_Int32 nPos(0);
+            OUStringBuffer aToken;
+
+            // split into single tokens (currently only comma separator)
+            while(nPos < nLen)
+            {
+                const sal_Int32 nInitPos(nPos);
+                copyToLimiter(rSystemClass, u',', nPos, aToken, nLen);
+                skip_char(rSystemClass, u',', nPos, nLen);
+                const OUString aLang(o3tl::trim(aToken));
+                aToken.setLength(0);
+
+                if(!aLang.isEmpty())
+                {
+                    maSystemLanguage.push_back(aLang);
+                }
+
+                if(nInitPos == nPos)
+                {
+                    OSL_ENSURE(false, "Could not interpret on current position 
(!)");
+                    nPos++;
+                }
+            }
+        }
+
         XmlSpace SvgNode::getXmlSpace() const
         {
             if(maXmlSpace != XmlSpace::NotSet)
diff --git a/svgio/source/svgreader/svgswitchnode.cxx 
b/svgio/source/svgreader/svgswitchnode.cxx
new file mode 100644
index 000000000000..bbad79a3b5d9
--- /dev/null
+++ b/svgio/source/svgreader/svgswitchnode.cxx
@@ -0,0 +1,129 @@
+/* -*- 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 <svgswitchnode.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <drawinglayer/primitive2d/transformprimitive2d.hxx>
+#include <unotools/syslocaleoptions.hxx>
+
+namespace svgio::svgreader
+{
+SvgSwitchNode::SvgSwitchNode(SvgDocument& rDocument, SvgNode* pParent)
+    : SvgNode(SVGToken::Switch, rDocument, pParent)
+    , maSvgStyleAttributes(*this)
+{
+}
+
+SvgSwitchNode::~SvgSwitchNode() {}
+
+const SvgStyleAttributes* SvgSwitchNode::getSvgStyleAttributes() const
+{
+    return checkForCssStyle(maSvgStyleAttributes);
+}
+
+void SvgSwitchNode::parseAttribute(SVGToken aSVGToken, const OUString& 
aContent)
+{
+    // call parent
+    SvgNode::parseAttribute(aSVGToken, aContent);
+
+    // read style attributes
+    maSvgStyleAttributes.parseStyleAttribute(aSVGToken, aContent);
+
+    // parse own
+    switch (aSVGToken)
+    {
+        case SVGToken::Style:
+        {
+            readLocalCssStyle(aContent);
+            break;
+        }
+        case SVGToken::Transform:
+        {
+            const basegfx::B2DHomMatrix aMatrix(readTransform(aContent, 
*this));
+
+            if (!aMatrix.isIdentity())
+            {
+                setTransform(aMatrix);
+            }
+            break;
+        }
+        default:
+        {
+            break;
+        }
+    }
+}
+
+void 
SvgSwitchNode::decomposeSvgNode(drawinglayer::primitive2d::Primitive2DContainer&
 rTarget,
+                                     bool bReferenced) const
+{
+    // #i125258# for SVGTokenG decompose children
+    const SvgStyleAttributes* pStyle = getSvgStyleAttributes();
+
+    if (pStyle)
+    {
+        drawinglayer::primitive2d::Primitive2DContainer aContent;
+
+        const auto& rChildren = getChildren();
+        const sal_uInt32 nCount(rChildren.size());
+        OUString 
sLanguage(SvtSysLocaleOptions().GetRealUILanguageTag().getLanguage());
+
+        SvgNode* pNodeToDecompose = nullptr;
+        for (sal_uInt32 a(0); a < nCount; a++)
+        {
+            SvgNode* pCandidate = rChildren[a].get();
+
+            if (pCandidate && Display::None != pCandidate->getDisplay())
+            {
+                std::vector<OUString> aSystemLanguage = 
pCandidate->getSystemLanguage();
+                if (!sLanguage.isEmpty() && !aSystemLanguage.empty())
+                {
+                    for (const OUString& sSystemLang : aSystemLanguage)
+                    {
+                        if (sSystemLang == sLanguage)
+                        {
+                            pNodeToDecompose = pCandidate;
+                            break;
+                        }
+                    }
+                }
+                else
+                {
+                    pNodeToDecompose = pCandidate;
+                }
+            }
+
+            if (pNodeToDecompose)
+            {
+                pNodeToDecompose->decomposeSvgNode(aContent, bReferenced);
+                // break once it's descomposed
+                break;
+            }
+        }
+
+        if (!aContent.empty())
+        {
+            pStyle->add_postProcess(rTarget, std::move(aContent), 
getTransform());
+        }
+    }
+}
+
+} // end of namespace svgio::svgreader
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/svgio/source/svgreader/svgtoken.cxx 
b/svgio/source/svgreader/svgtoken.cxx
index 968ead048312..642fba085f6f 100644
--- a/svgio/source/svgreader/svgtoken.cxx
+++ b/svgio/source/svgreader/svgtoken.cxx
@@ -107,6 +107,7 @@ constexpr auto aSVGTokenMap = 
frozen::make_unordered_map<std::u16string_view, SV
     { u"patternContentUnits", SVGToken::PatternContentUnits },
     { u"patternTransform", SVGToken::PatternTransform },
     { u"opacity", SVGToken::Opacity },
+    { u"systemLanguage", SVGToken::SystemLanguage },
     { u"visibility", SVGToken::Visibility },
     { u"title", SVGToken::Title },
     { u"desc", SVGToken::Desc },

Reply via email to