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 },
