diff options
author | Mike Kaganski <mike.kaganski@collabora.com> | 2024-04-30 15:40:41 +0500 |
---|---|---|
committer | Mike Kaganski <mike.kaganski@collabora.com> | 2024-04-30 19:11:32 +0200 |
commit | acbb6c98ae1335da5aca4f1a55de5f3ae35a5d02 (patch) | |
tree | fe21b7daf69fde6c759e6e726ec5dc50529dba62 | |
parent | 6104316fd50076d41039c33a5338b016e93259fe (diff) |
tdf#160867: restore HTML map export for text hyperlinks in frames
The most exciting was to discover that this functionality was actually
already implemented prior to 2001, and then accidentally dropped, and
nobody noticed, until Noel did his great cleanups, and made an amazing
investigation in commit ed2ae3c3bb0a708cafc3de6a01adc9ddc43fb859 (remove
dead SwNoteURL, 2018-03-14). The detailed commit message made my task
so much easier: I knew where and what to restore.
So this change restores relevant pieces removed over the time in commits
* 1b666235f6b0b0f0b13f473bf3b639f4f5f0b12f (loplugin:singlevalfields
improve copy constructor check, 2018-01-03),
* be8c414567f49242164b1fdfb12764b16be355c1 (loplugin:unusedmethods also
check for functions returning bool, 2018-01-19),
* 73139fe600fc1399ae828077981a2498cb0a0b0c (loplugin:unusedmethods,
2018-01-20)
* bb7ade140df807b6a0f12766a1365b8f8d0fd342 (loplugin:unusedmethods,
2018-03-08),
* ed2ae3c3bb0a708cafc3de6a01adc9ddc43fb859 (remove dead SwNoteURL,
2018-03-14),
* fd1cfd25b48cb4bd5c87e9cb317b37699ca3a1d6 (PortionType::Url is unused,
2019-01-18).
It re-implements the functionality accidentally removed in commit
da7671e4f7482110ecd0cfbfd7dbd9e0b873c81c (Opt.(FME): The new attribute
handler makes a lot of code superfluous, 2001-03-15), moving it into
SwAttrHandler, which replaced the ChgFnt in SwTxtAttr.
It also fixes the code writing the HTML image map, to output valid HTML.
And finally, it adds a unit test, to avoid repeating the story :-)
Change-Id: I72ae3cf30f0e9689f50a2c877e1622e4ae46de49
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/166924
Tested-by: Jenkins
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
-rw-r--r-- | include/vcl/imaprect.hxx | 2 | ||||
-rw-r--r-- | svtools/source/svhtml/htmlout.cxx | 4 | ||||
-rw-r--r-- | sw/qa/extras/htmlexport/data/tdf160867_image_with_link.fodt | 25 | ||||
-rw-r--r-- | sw/qa/extras/htmlexport/htmlexport.cxx | 21 | ||||
-rw-r--r-- | sw/source/core/inc/noteurl.hxx | 34 | ||||
-rw-r--r-- | sw/source/core/inc/swfont.hxx | 3 | ||||
-rw-r--r-- | sw/source/core/layout/paintfrm.cxx | 1 | ||||
-rw-r--r-- | sw/source/core/text/atrhndl.hxx | 2 | ||||
-rw-r--r-- | sw/source/core/text/atrstck.cxx | 15 | ||||
-rw-r--r-- | sw/source/core/text/inftxt.cxx | 21 | ||||
-rw-r--r-- | sw/source/core/text/inftxt.hxx | 7 | ||||
-rw-r--r-- | sw/source/core/text/itrform2.cxx | 2 | ||||
-rw-r--r-- | sw/source/core/text/itrpaint.cxx | 3 | ||||
-rw-r--r-- | sw/source/core/text/noteurl.cxx | 35 | ||||
-rw-r--r-- | sw/source/core/text/pormulti.cxx | 3 | ||||
-rw-r--r-- | sw/source/core/txtnode/swfont.cxx | 2 |
16 files changed, 176 insertions, 4 deletions
diff --git a/include/vcl/imaprect.hxx b/include/vcl/imaprect.hxx index 89cb52d80bbe..8abe1ed39960 100644 --- a/include/vcl/imaprect.hxx +++ b/include/vcl/imaprect.hxx @@ -25,7 +25,7 @@ class Fraction; -class UNLESS_MERGELIBS(VCL_DLLPUBLIC) IMapRectangleObject final : public IMapObject +class VCL_DLLPUBLIC IMapRectangleObject final : public IMapObject { tools::Rectangle aRect; diff --git a/svtools/source/svhtml/htmlout.cxx b/svtools/source/svhtml/htmlout.cxx index 956546269708..d165a8922f02 100644 --- a/svtools/source/svhtml/htmlout.cxx +++ b/svtools/source/svhtml/htmlout.cxx @@ -702,7 +702,7 @@ SvStream& HTMLOutFuncs::Out_ImageMap( SvStream& rStream, sOut.append(OString::Concat("<") + OOO_STRING_SVTOOLS_HTML_area " " OOO_STRING_SVTOOLS_HTML_O_shape - "=" + pShape + " " + "=\"" + pShape + "\" " OOO_STRING_SVTOOLS_HTML_O_coords "=\"" + aCoords + "\" "); rStream.WriteOString( sOut ); @@ -756,7 +756,7 @@ SvStream& HTMLOutFuncs::Out_ImageMap( SvStream& rStream, Out_Events( rStream, rMacroTab, pEventTable, bOutStarBasic ); - rStream.WriteChar( '>' ); + rStream.WriteOString("/>"); } } diff --git a/sw/qa/extras/htmlexport/data/tdf160867_image_with_link.fodt b/sw/qa/extras/htmlexport/data/tdf160867_image_with_link.fodt new file mode 100644 index 000000000000..43c35cdff13e --- /dev/null +++ b/sw/qa/extras/htmlexport/data/tdf160867_image_with_link.fodt @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<office:document xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text"> + <office:styles> + <style:style style:name="Frame" style:family="graphic"> + <style:graphic-properties text:anchor-type="as-char" svg:x="0" svg:y="0" style:number-wrapped-paragraphs="no-limit" style:wrap-contour="false" fo:background-color="transparent" draw:fill="none" fo:margin-left="0" fo:margin-right="0" fo:margin-top="0" fo:margin-bottom="0" style:wrap="none" style:vertical-pos="middle" style:vertical-rel="baseline" style:horizontal-pos="from-left" style:horizontal-rel="paragraph" fo:padding="0" fo:border="none" loext:rel-width-rel="paragraph"/> + </style:style> + </office:styles> + <office:body> + <office:text> + <text:p><draw:a xlink:type="simple" xlink:href="foo/bar"><draw:frame draw:style-name="Frame" draw:name="image1" svg:width="17cm" svg:height="25mm" style:rel-height="scale"><draw:image draw:mime-type="image/png"> + <office:binary-data>iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsIAAA7CARUoSoAAAAAMSURBVBhXY/jPwAAAAwEBAGMkVdMAAAAASUVORK5C</office:binary-data> + </draw:image> + </draw:frame></draw:a>image1 with a hyperlink, and this text with <text:a xlink:type="simple" xlink:href="baz" text:style-name="Internet_20_link" text:visited-style-name="Visited_20_Internet_20_Link">another hyperlink</text:a></text:p> + <text:p><draw:frame draw:style-name="Frame" draw:name="frame" svg:width="17cm"> + <draw:text-box fo:min-height="1pt"> + <text:p><draw:a xlink:type="simple" xlink:href="foo/bar"><draw:frame draw:style-name="Frame" draw:name="image2" svg:width="17cm" svg:height="25mm"><draw:image draw:mime-type="image/png"> + <office:binary-data>iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsIAAA7CARUoSoAAAAAMSURBVBhXY/jPwAAAAwEBAGMkVdMAAAAASUVORK5C</office:binary-data> + </draw:image> + </draw:frame></draw:a>image2 with a hyperlink, and this text with <text:a xlink:type="simple" xlink:href="baz" text:style-name="Internet_20_link" text:visited-style-name="Visited_20_Internet_20_Link">another hyperlink</text:a>, in a frame</text:p> + </draw:text-box> + </draw:frame></text:p> + </office:text> + </office:body> +</office:document>
\ No newline at end of file diff --git a/sw/qa/extras/htmlexport/htmlexport.cxx b/sw/qa/extras/htmlexport/htmlexport.cxx index 6a23599184a5..329be9231bb9 100644 --- a/sw/qa/extras/htmlexport/htmlexport.cxx +++ b/sw/qa/extras/htmlexport/htmlexport.cxx @@ -3082,6 +3082,27 @@ CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testHTML_Tdf160390) ExportToHTML(); } +CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testHTML_160867) +{ + // Given a document with an image with hyperlink, and text with hyperlink, both in a frame: + createSwDoc("tdf160867_image_with_link.fodt"); + // When exporting to HTML: + ExportToHTML(); + // Parse it as XML (strict!) + xmlDocUniquePtr pDoc = parseXml(maTempFile); + CPPUNIT_ASSERT(pDoc); + assertXPath(pDoc, "/html/body/p"_ostr, 2); + + // Test export of text hyperlink in the image map. TODO: implement export of image hyperlink. + // Without the fix, the test would fail with + // - Expected: 1 + // - Actual : 0 + // - In <>, XPath '/html/body/p[2]/map' number of nodes is incorrect + const OUString mapName = getXPath(pDoc, "/html/body/p[2]/map"_ostr, "name"_ostr); + assertXPath(pDoc, "/html/body/p[2]/map/area"_ostr, "shape"_ostr, u"rect"_ustr); + assertXPath(pDoc, "/html/body/p[2]/img"_ostr, "usemap"_ostr, "#" + mapName); +} + } // end of anonymous namespace CPPUNIT_PLUGIN_IMPLEMENT(); diff --git a/sw/source/core/inc/noteurl.hxx b/sw/source/core/inc/noteurl.hxx index 1e5775d3c099..eeae15642a43 100644 --- a/sw/source/core/inc/noteurl.hxx +++ b/sw/source/core/inc/noteurl.hxx @@ -20,8 +20,42 @@ #ifndef INCLUDED_SW_SOURCE_CORE_INC_NOTEURL_HXX #define INCLUDED_SW_SOURCE_CORE_INC_NOTEURL_HXX +#include <swrect.hxx> + +#include <rtl/ustring.hxx> + +#include <vector> + +class ImageMap; +class MapMode; + +class SwURLNote +{ + OUString aURL; + OUString aTarget; + SwRect aRect; + +public: + SwURLNote(const OUString& rURL, const OUString& rTarget, const SwRect& rRect) + : aURL(rURL) + , aTarget(rTarget) + , aRect(rRect) + { + } + const OUString& GetURL() const { return aURL; } + const OUString& GetTarget() const { return aTarget; } + const SwRect& GetRect() const { return aRect; } +}; + class SwNoteURL { +private: + std::vector<SwURLNote> m_List; + +public: + SwNoteURL() {} + void InsertURLNote(const OUString& rURL, const OUString& rTarget, const SwRect& rRect); + void FillImageMap(ImageMap* pMap, const Point& rPos, const MapMode& rMap); }; // globale Variable, in NoteURL.Cxx angelegt diff --git a/sw/source/core/inc/swfont.hxx b/sw/source/core/inc/swfont.hxx index a8c3320d1cb8..25453744bd3f 100644 --- a/sw/source/core/inc/swfont.hxx +++ b/sw/source/core/inc/swfont.hxx @@ -174,6 +174,7 @@ class SwFont bool m_bFontChg :1; bool m_bOrgChg :1; // nOrgHeight/Ascent are invalid bool m_bGreyWave :1; // for the extended TextInput: gray waveline + bool m_bURL : 1 = false; public: SwFont( const SwAttrSet* pSet, const IDocumentSettingAccess* pIDocumentSettingAccess ); @@ -264,6 +265,8 @@ public: inline void SetGreyWave( const bool bNew ); bool IsGreyWave() const { return m_bGreyWave; } bool IsPaintBlank() const { return m_bPaintBlank; } + void SetURL(const bool bURL) { m_bURL = bURL; } + bool IsURL() const { return m_bURL; } // setting of the base class font for SwTextCharFormat void SetDiffFnt( const SfxItemSet* pSet, diff --git a/sw/source/core/layout/paintfrm.cxx b/sw/source/core/layout/paintfrm.cxx index 8aa4c3738934..1e8600d83094 100644 --- a/sw/source/core/layout/paintfrm.cxx +++ b/sw/source/core/layout/paintfrm.cxx @@ -8136,6 +8136,7 @@ Graphic SwFlyFrameFormat::MakeGraphic( ImageMap* pMap, const sal_uInt32 /*nMaxim if( bNoteURL ) { OSL_ENSURE( pNoteURL, "MakeGraphic: Good Bye, NoteURL." ); + pNoteURL->FillImageMap(pMap, pFly->getFrameArea().Pos(), aMap); delete pNoteURL; pNoteURL = nullptr; } diff --git a/sw/source/core/text/atrhndl.hxx b/sw/source/core/text/atrhndl.hxx index 851615325a06..efe1ae954958 100644 --- a/sw/source/core/text/atrhndl.hxx +++ b/sw/source/core/text/atrhndl.hxx @@ -46,6 +46,8 @@ private: // a template, if we have to restart the attribute evaluation std::optional<SwFont> m_oFnt; + int m_nINETFMT = 0; // for font's SetURL + bool m_bVertLayout; bool m_bVertLayoutLRBT; diff --git a/sw/source/core/text/atrstck.cxx b/sw/source/core/text/atrstck.cxx index 0eb8d74e89c6..82a3da7fbf17 100644 --- a/sw/source/core/text/atrstck.cxx +++ b/sw/source/core/text/atrstck.cxx @@ -367,6 +367,13 @@ void SwAttrHandler::PushAndChg( const SwTextAttr& rAttr, SwFont& rFnt ) } } } + + if (rAttr.Which() == RES_TXTATR_INETFMT) + { + if (m_nINETFMT == 0) + rFnt.SetURL(true); + ++m_nINETFMT; + } } // this is the usual case, we have a basic attribute, push it onto the // stack and change the font @@ -433,6 +440,14 @@ void SwAttrHandler::PopAndChg( const SwTextAttr& rAttr, SwFont& rFnt ) const SfxItemSet* pSet = CharFormat::GetItemSet( rAttr.GetAttr() ); if ( !pSet ) return; + if (rAttr.Which() == RES_TXTATR_INETFMT) + { + assert(m_nINETFMT > 0); + --m_nINETFMT; + if (m_nINETFMT == 0) + rFnt.SetURL(false); + } + for ( sal_uInt16 i = RES_CHRATR_BEGIN; i < RES_CHRATR_END; i++) { const SfxPoolItem* pItem; diff --git a/sw/source/core/text/inftxt.cxx b/sw/source/core/text/inftxt.cxx index b293f18f824a..a1a3bb89a87b 100644 --- a/sw/source/core/text/inftxt.cxx +++ b/sw/source/core/text/inftxt.cxx @@ -21,7 +21,9 @@ #include <unotools/linguprops.hxx> #include <unotools/lingucfg.hxx> +#include <fmtinfmt.hxx> #include <hintids.hxx> +#include <txatbase.hxx> #include <svl/ctloptions.hxx> #include <sfx2/infobar.hxx> #include <sfx2/printer.hxx> @@ -1461,6 +1463,25 @@ void SwTextPaintInfo::DrawViewOpt( const SwLinePortion &rPor, DrawBackground( rPor, pColor ); } +void SwTextPaintInfo::NotifyURL_(const SwLinePortion& rPor) const +{ + assert(pNoteURL); + + SwRect aIntersect; + CalcRect(rPor, nullptr, &aIntersect); + + if (aIntersect.HasArea()) + { + SwTextNode* pNd = const_cast<SwTextNode*>(GetTextFrame()->GetTextNodeFirst()); + SwTextAttr* const pAttr = pNd->GetTextAttrAt(sal_Int32(GetIdx()), RES_TXTATR_INETFMT); + if (pAttr) + { + const SwFormatINetFormat& rFormat = pAttr->GetINetFormat(); + pNoteURL->InsertURLNote(rFormat.GetValue(), rFormat.GetTargetFrame(), aIntersect); + } + } +} + static void lcl_InitHyphValues( PropertyValues &rVals, sal_Int16 nMinLeading, sal_Int16 nMinTrailing, bool bNoCapsHyphenation, bool bNoLastWordHyphenation, diff --git a/sw/source/core/text/inftxt.hxx b/sw/source/core/text/inftxt.hxx index cb11d02b1e8d..9c4126a7a6e9 100644 --- a/sw/source/core/text/inftxt.hxx +++ b/sw/source/core/text/inftxt.hxx @@ -358,6 +358,7 @@ class SwTextPaintInfo : public SwTextSizeInfo const bool bGrammarCheck = false ); SwTextPaintInfo &operator=(const SwTextPaintInfo&) = delete; + void NotifyURL_(const SwLinePortion& rPor) const; protected: SwTextPaintInfo() @@ -418,6 +419,12 @@ public: void DrawCSDFHighlighting(const SwLinePortion &rPor) const; + void NotifyURL(const SwLinePortion& rPor) const + { + if (URLNotify()) + NotifyURL_(rPor); + } + /** * Calculate the rectangular area where the portion takes place. * @param[in] rPor portion for which the method specify the painting area diff --git a/sw/source/core/text/itrform2.cxx b/sw/source/core/text/itrform2.cxx index 2d24e103957a..046aa6bde1ea 100644 --- a/sw/source/core/text/itrform2.cxx +++ b/sw/source/core/text/itrform2.cxx @@ -1318,7 +1318,7 @@ SwTextPortion *SwTextFormatter::WhichTextPor( SwTextFormatInfo &rInf ) const } if( !pPor ) { - if( !rInf.X() && !m_pCurr->GetNextPortion() && !m_pCurr->GetLen() ) + if( !rInf.X() && !m_pCurr->GetNextPortion() && !m_pCurr->GetLen() && !GetFnt()->IsURL() ) pPor = m_pCurr; else pPor = new SwTextPortion; diff --git a/sw/source/core/text/itrpaint.cxx b/sw/source/core/text/itrpaint.cxx index 5b6bb1288d57..8fa9ca45f5fd 100644 --- a/sw/source/core/text/itrpaint.cxx +++ b/sw/source/core/text/itrpaint.cxx @@ -459,6 +459,9 @@ void SwTextPainter::DrawTextLine( const SwRect &rPaint, SwSaveClip &rClip, // reset (for special vertical alignment) GetInfo().Y( nOldY ); + if (GetFnt()->IsURL() && pPor->InTextGrp()) + GetInfo().NotifyURL(*pPor); + bFirst &= !pPor->GetLen(); if( pNext || !pPor->IsMarginPortion() ) pPor->Move( GetInfo() ); diff --git a/sw/source/core/text/noteurl.cxx b/sw/source/core/text/noteurl.cxx index fa91ea252d5f..ae52e1c29a91 100644 --- a/sw/source/core/text/noteurl.cxx +++ b/sw/source/core/text/noteurl.cxx @@ -19,7 +19,42 @@ #include <noteurl.hxx> +#include <vcl/imap.hxx> +#include <vcl/imaprect.hxx> +#include <vcl/mapmod.hxx> +#include <vcl/outdev.hxx> + // Global variable SwNoteURL* pNoteURL = nullptr; +void SwNoteURL::InsertURLNote(const OUString& rURL, const OUString& rTarget, const SwRect& rRect) +{ + const size_t nCount = m_List.size(); + for (size_t i = 0; i < nCount; ++i) + if (rRect == m_List[i].GetRect()) + return; + + m_List.emplace_back(rURL, rTarget, rRect); +} + +void SwNoteURL::FillImageMap(ImageMap* pMap, const Point& rPos, const MapMode& rMap) +{ + assert(pMap && "FillImageMap: No ImageMap, no cookies!"); + const size_t nCount = m_List.size(); + if (nCount) + { + MapMode aMap(MapUnit::Map100thMM); + for (size_t i = 0; i < nCount; ++i) + { + const SwURLNote& rNote = m_List[i]; + SwRect aSwRect(rNote.GetRect()); + aSwRect -= rPos; + tools::Rectangle aRect(OutputDevice::LogicToLogic(aSwRect.SVRect(), rMap, aMap)); + IMapRectangleObject aObj(aRect, rNote.GetURL(), OUString(), OUString(), + rNote.GetTarget(), OUString(), true, false); + pMap->InsertIMapObject(aObj); + } + } +} + /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/core/text/pormulti.cxx b/sw/source/core/text/pormulti.cxx index 9fb6011f0db5..aaf8339b37ef 100644 --- a/sw/source/core/text/pormulti.cxx +++ b/sw/source/core/text/pormulti.cxx @@ -1783,6 +1783,9 @@ void SwTextPainter::PaintMultiPortion( const SwRect &rPaint, pPor->Paint( GetInfo() ); } + if (GetFnt()->IsURL() && pPor->InTextGrp()) + GetInfo().NotifyURL(*pPor); + bFirst &= !pPor->GetLen(); if( pNext || !pPor->IsMarginPortion() ) pPor->Move( GetInfo() ); diff --git a/sw/source/core/txtnode/swfont.cxx b/sw/source/core/txtnode/swfont.cxx index 98fec0f153e2..20b3062267f7 100644 --- a/sw/source/core/txtnode/swfont.cxx +++ b/sw/source/core/txtnode/swfont.cxx @@ -684,6 +684,7 @@ SwFont::SwFont( const SwFont &rFont ) m_bOrgChg = rFont.m_bOrgChg; m_bPaintBlank = rFont.m_bPaintBlank; m_bGreyWave = rFont.m_bGreyWave; + m_bURL = rFont.m_bURL; } SwFont::SwFont( const SwAttrSet* pAttrSet, @@ -873,6 +874,7 @@ SwFont& SwFont::operator=( const SwFont &rFont ) m_bOrgChg = rFont.m_bOrgChg; m_bPaintBlank = rFont.m_bPaintBlank; m_bGreyWave = rFont.m_bGreyWave; + m_bURL = rFont.m_bURL; } return *this; } |