tdf#45589 sw: create and paint text portions for bookmarks
Add a new SwBookmarkPortion, derived from SwControlCharPortion.
There is no character for the bookmark in the text so the portion has
length of 0, which makes things quite a bit more tricky.
Formatting:
* SwBookmarkPortion is created last in WhichFirstPortion().
* In an empty paragraph the SwTextFrame::FormatEmpty() must be disabled.
* If there's a bookmark at the end of a paragraph,
SwTextFormatter::CalcAscent() must use the font of the previous
character, not the paragraph font, because that could grow the line
if it's higher.
* The SwMultiPortion complicates matters, because it uses a nested
SwTextFormatInfo and thus we need some extra steps to prevent
duplicate SwBookmarkPortions; this is particluarly a problem for
rotated text portions.
- SwTextFormatter::BuildPortions() must advance the outer
SwTextFormatInfo's bookmark position because BuildMultiPortion() has
already created the SwBookmarkPortion.
- If a SwBookmarkPortion is at the start of a SwMultiPortion, it will
be created before the SwMultiPortion but must be painted inside the
SwMultiPortion because its font is going to be initialised as inside
the SwMultiPortion (e.g. it will be rotated) so its position must
also be adapted to be inside, and only
SwTextPainter::PaintMultiPortion() does the setup for that; add a
hack to move it in SwTextFormatter::BuildMultiPortion().
Painting:
* Using the original font seems rather difficult, hard to predict what
some character is going to look like, and how it scales if the size is
increased; use OpenSymbol instead. Unfortunately OpenSymbol doesn't
have a good glyph that could be used for both the end of a bookmark
and start of another bookmark at the same position.
* SwLinePortion::PrePaint() wants to avoid moving the portion
half-outside the frame but often it looks better that way (previously
it was misaligned, now it's half-outside).
* Not sure if it makes sense to draw a field shading in
SwTextPaintInfo::DrawViewOpt() too; let's try it out, but dependent on
the same IsViewMetaChars() setting as the bookmark character itself,
not on the field shading setting.
Change-Id: I1ab94afb417221e278dbb3afd6c978a05fc78497
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/87364
Reviewed-by: Michael Stahl <michael.stahl@cib.de>
Tested-by: Michael Stahl <michael.stahl@cib.de>
diff --git a/sw/qa/extras/ooxmlimport/ooxmlimport.cxx b/sw/qa/extras/ooxmlimport/ooxmlimport.cxx
index a9edd2b..fb6fbde 100644
--- a/sw/qa/extras/ooxmlimport/ooxmlimport.cxx
+++ b/sw/qa/extras/ooxmlimport/ooxmlimport.cxx
@@ -318,8 +318,14 @@
* The problem was that direct formatting of the paragraph was not applied
* to the numbering. This is easier to test using a layout dump.
*/
OUString aHeight = parseDump("/root/page/body/txt/Special", "nHeight");
CPPUNIT_ASSERT_EQUAL(sal_Int32(220), aHeight.toInt32()); // It was 280
xmlDocPtr pXmlDoc = parseLayoutDump();
assertXPath(pXmlDoc, "/root/page/body/txt/Special[1]", "nHeight", "220");
// check the bookmark portions are of the expected height
assertXPath(pXmlDoc, "/root/page/body/txt/Special[2]", "nType", "PortionType::Bookmark");
assertXPath(pXmlDoc, "/root/page/body/txt/Special[2]", "nHeight", "253");
assertXPath(pXmlDoc, "/root/page/body/txt/Special[3]", "nType", "PortionType::Bookmark");
assertXPath(pXmlDoc, "/root/page/body/txt/Special[3]", "nHeight", "253");
/*
* Next problem was that the page margin contained the width of the page border as well.
diff --git a/sw/source/core/access/accportions.cxx b/sw/source/core/access/accportions.cxx
index 00cabbe..b929bc5 100644
--- a/sw/source/core/access/accportions.cxx
+++ b/sw/source/core/access/accportions.cxx
@@ -170,6 +170,9 @@
case PortionType::ControlChar:
sDisplay = rText + OUStringChar(m_pTextFrame->GetText()[sal_Int32(m_nViewPosition)]);
break;
case PortionType::Bookmark:
// TODO
break;
default:
sDisplay = rText;
break;
diff --git a/sw/source/core/inc/scriptinfo.hxx b/sw/source/core/inc/scriptinfo.hxx
index 6d7ad0f..bb91ded 100644
--- a/sw/source/core/inc/scriptinfo.hxx
+++ b/sw/source/core/inc/scriptinfo.hxx
@@ -24,6 +24,7 @@
#include <deque>
#include <unordered_set>
#include <rtl/ustrbuf.hxx>
#include <o3tl/typed_flags_set.hxx>
#include <i18nlangtag/lang.h>
#include "TextFrameIndex.hxx"
@@ -42,7 +43,7 @@
{
public:
enum CompType { KANA, SPECIAL_LEFT, SPECIAL_RIGHT, NONE, SPECIAL_MIDDLE};
enum class MarkKind { Start, End, Point };
enum class MarkKind { Start = (1<<0), End = (1<<1), Point = (1<<2) };
private:
//! Records a single change in script type.
@@ -182,6 +183,7 @@
}
TextFrameIndex NextHiddenChg(TextFrameIndex nPos) const;
TextFrameIndex NextBookmark(TextFrameIndex nPos) const;
MarkKind GetBookmark(TextFrameIndex nPos) const;
static void CalcHiddenRanges(const SwTextNode& rNode,
MultiSelection& rHiddenMulti,
std::vector<std::pair<sw::mark::IBookmark const*, MarkKind>> * pBookmarks);
@@ -385,6 +387,13 @@
static SwFontScript WhichFont(sal_Int32 nIdx, OUString const & rText);
};
namespace o3tl
{
template<> struct typed_flags<SwScriptInfo::MarkKind> : is_typed_flags<SwScriptInfo::MarkKind, 0x07> {};
}
#endif
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/inc/txttypes.hxx b/sw/source/core/inc/txttypes.hxx
index 2030fff..16f433e 100644
--- a/sw/source/core/inc/txttypes.hxx
+++ b/sw/source/core/inc/txttypes.hxx
@@ -33,6 +33,7 @@
Multi = 0x0085,
HiddenText = 0x0086,
ControlChar = 0x0087,
Bookmark = 0x0088,
Text = 0x8000,
Lay = 0x8001,
diff --git a/sw/source/core/text/inftxt.cxx b/sw/source/core/text/inftxt.cxx
index 83e8bde..8538d6e 100644
--- a/sw/source/core/text/inftxt.cxx
+++ b/sw/source/core/text/inftxt.cxx
@@ -1375,6 +1375,14 @@
bDraw = true;
}
break;
case PortionType::Bookmark:
if (!GetOpt().IsPagePreview()
&& !GetOpt().IsReadonly()
&& GetOpt().IsViewMetaChars())
{
bDraw = true;
}
break;
case PortionType::InputField:
// input field shading also in read-only mode
if ( !GetOpt().IsPagePreview()
@@ -1582,6 +1590,7 @@
m_nForcedLeftMargin = 0;
m_nSoftHyphPos = TextFrameIndex(0);
m_nUnderScorePos = TextFrameIndex(COMPLETE_STRING);
m_nLastBookmarkPos = TextFrameIndex(-1);
m_cHookChar = 0;
SetIdx(TextFrameIndex(0));
SetLen(TextFrameIndex(GetText().getLength()));
@@ -2007,4 +2016,18 @@
return bOld;
}
bool SwTextFormatInfo::CheckCurrentPosBookmark()
{
if (m_nLastBookmarkPos != GetIdx())
{
m_nLastBookmarkPos = GetIdx();
return true;
}
else
{
return false;
}
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/inftxt.hxx b/sw/source/core/text/inftxt.hxx
index 3088884..63d030e 100644
--- a/sw/source/core/text/inftxt.hxx
+++ b/sw/source/core/text/inftxt.hxx
@@ -477,6 +477,7 @@
TextFrameIndex m_nSoftHyphPos; ///< SoftHyphPos for Hyphenation
TextFrameIndex m_nLineStart; ///< Current line start in rText
TextFrameIndex m_nUnderScorePos; ///< enlarge repaint if underscore has been found
TextFrameIndex m_nLastBookmarkPos; ///< need to check for bookmarks at every portion
// #i34348# Changed type from sal_uInt16 to SwTwips
SwTwips m_nLeft; // Left margin
SwTwips m_nRight; // Right margin
@@ -636,6 +637,8 @@
bool IsArrowDone() const { return m_bArrowDone; }
void SetArrowDone( const bool bNew ) { m_bArrowDone = bNew; }
bool CheckCurrentPosBookmark();
// For SwTextPortion::Hyphenate
bool ChgHyph( const bool bNew );
diff --git a/sw/source/core/text/itrform2.cxx b/sw/source/core/text/itrform2.cxx
index 3285f7f..6edf7e0 100644
--- a/sw/source/core/text/itrform2.cxx
+++ b/sw/source/core/text/itrform2.cxx
@@ -691,6 +691,10 @@
CalcAscent( rInf, pPor );
InsertPortion( rInf, pPor );
if (pPor->IsMultiPortion() && (!m_pMulti || m_pMulti->IsBidi()))
{
(void) rInf.CheckCurrentPosBookmark(); // bookmark was already created inside MultiPortion!
}
pPor = NewPortion( rInf );
}
@@ -753,6 +757,19 @@
pPor->Height( pLast->Height() );
pPor->SetAscent( pLast->GetAscent() );
}
else if (pPor->GetWhichPor() == PortionType::Bookmark
&& rInf.GetIdx() == TextFrameIndex(rInf.GetText().getLength()))
{
// bookmark at end of paragraph: *don't* advance iterator, use the
// current font instead; it's possible that there's a font size on the
// paragraph and it's overridden on the last line of the paragraph and
// we don't want to apply it via SwBookmarkPortion and grow the line
// height (example: n758883.docx)
SwLinePortion const*const pLast = rInf.GetLast();
assert(pLast);
pPor->Height( pLast->Height() );
pPor->SetAscent( pLast->GetAscent() );
}
else
{
const SwLinePortion *pLast = rInf.GetLast();
@@ -1012,6 +1029,7 @@
return pPor;
}
// first portions have no length
SwLinePortion *SwTextFormatter::WhichFirstPortion(SwTextFormatInfo &rInf)
{
SwLinePortion *pPor = nullptr;
@@ -1144,6 +1162,39 @@
pPor = TryNewNoLengthPortion(rInf);
}
// 12. bookmarks
// check this *last* so that BuildMultiPortion() can find it!
if (!pPor && rInf.CheckCurrentPosBookmark())
{
auto const bookmark(m_pScriptInfo->GetBookmark(rInf.GetIdx()));
if (static_cast<bool>(bookmark))
{
sal_Unicode mark;
if ((bookmark & (SwScriptInfo::MarkKind::Start|SwScriptInfo::MarkKind::End))
== (SwScriptInfo::MarkKind::Start|SwScriptInfo::MarkKind::End))
{
//mark = u'\u2336'; // not in OpenSymbol :(
mark = '|';
// hmm ... paint U+2345 over U+2346 should be same width?
// and U+237F // or U+2E20/U+2E21
}
else if (bookmark & SwScriptInfo::MarkKind::Start)
{
mark = '[';
}
else if (bookmark & SwScriptInfo::MarkKind::End)
{
mark = ']';
}
else
{
assert(bookmark & SwScriptInfo::MarkKind::Point);
mark = '|';
}
pPor = new SwBookmarkPortion(rInf.GetLast(), mark);
}
}
return pPor;
}
diff --git a/sw/source/core/text/porlay.cxx b/sw/source/core/text/porlay.cxx
index aa0bbf6..9fa74a0 100644
--- a/sw/source/core/text/porlay.cxx
+++ b/sw/source/core/text/porlay.cxx
@@ -1686,6 +1686,23 @@
return TextFrameIndex(COMPLETE_STRING);
}
auto SwScriptInfo::GetBookmark(TextFrameIndex const nPos) const -> MarkKind
{
MarkKind ret{0};
for (auto const& it : m_Bookmarks)
{
if (nPos == it.first)
{
ret |= it.second;
}
else if (nPos < it.first)
{
break;
}
}
return ret;
}
// Takes a string and replaced the hidden ranges with cChar.
sal_Int32 SwScriptInfo::MaskHiddenRanges( const SwTextNode& rNode, OUStringBuffer & rText,
const sal_Int32 nStt, const sal_Int32 nEnd,
diff --git a/sw/source/core/text/porlin.cxx b/sw/source/core/text/porlin.cxx
index b5adccb..3d5cb26 100644
--- a/sw/source/core/text/porlin.cxx
+++ b/sw/source/core/text/porlin.cxx
@@ -103,7 +103,9 @@
1800 :
rInf.GetFont()->GetOrientation( rInf.GetTextFrame()->IsVertical() );
if (nLastWidth > nHalfView)
// pLast == this *only* for the 1st portion in the line so nLastWidth is 0;
// allow this too, will paint outside the frame but might look better...
if (nLastWidth > nHalfView || pLast == this)
{
switch (nDir)
{
diff --git a/sw/source/core/text/porlin.hxx b/sw/source/core/text/porlin.hxx
index 3fe08cb..ca7bc1a3 100644
--- a/sw/source/core/text/porlin.hxx
+++ b/sw/source/core/text/porlin.hxx
@@ -133,7 +133,7 @@
bool IsArrowPortion() const { return nWhichPor == PortionType::Arrow; }
bool IsMultiPortion() const { return nWhichPor == PortionType::Multi; }
bool IsNumberPortion() const { return nWhichPor == PortionType::Number; } // #i23726#
bool IsControlCharPortion() const { return nWhichPor == PortionType::ControlChar; }
bool IsControlCharPortion() const { return nWhichPor == PortionType::ControlChar || nWhichPor == PortionType::Bookmark; }
// Positioning
SwLinePortion *FindPrevPortion( const SwLinePortion *pRoot );
diff --git a/sw/source/core/text/pormulti.cxx b/sw/source/core/text/pormulti.cxx
index 1ef0c04e..5e991cc 100644
--- a/sw/source/core/text/pormulti.cxx
+++ b/sw/source/core/text/pormulti.cxx
@@ -38,6 +38,7 @@
#include "itrform2.hxx"
#include "porfld.hxx"
#include "porglue.hxx"
#include "porrst.hxx"
#include <pagefrm.hxx>
#include <rowfrm.hxx>
#include <tgrditem.hxx>
@@ -2029,6 +2030,19 @@
aInf.SetNumDone( rInf.IsNumDone() );
aInf.SetFootnoteDone( rInf.IsFootnoteDone() );
// if there's a bookmark at the start of the MultiPortion, it will be
// painted with the rotation etc. of the MultiPortion; move it *inside*
// so it gets positioned correctly; currently there's no other portion
// inserted between the end of WhichFirstPortion() and
// BuildMultiPortion()
if (rInf.GetLast()->GetWhichPor() == PortionType::Bookmark)
{
auto const pBookmark(static_cast<SwBookmarkPortion*>(rInf.GetLast()));
rInf.SetLast(pBookmark->Unchain());
assert(m_pCurr->GetNextPortion() == nullptr);
m_pCurr->SetNextPortion(pBookmark);
}
if( pFirstRest )
{
OSL_ENSURE( pFirstRest->InFieldGrp(), "BuildMulti: Fieldrest expected");
diff --git a/sw/source/core/text/porrst.cxx b/sw/source/core/text/porrst.cxx
index 6869596..44d2288 100644
--- a/sw/source/core/text/porrst.cxx
+++ b/sw/source/core/text/porrst.cxx
@@ -306,6 +306,17 @@
aTextFly.IsOn() && aTextFly.IsAnyObj( aRect ) )
return false;
// only need to check one node because of early return on GetMerged()
for (SwIndex const* pIndex = GetTextNodeFirst()->GetFirstIndex();
pIndex; pIndex = pIndex->GetNext())
{
sw::mark::IMark const*const pMark = pIndex->GetMark();
if (dynamic_cast<const sw::mark::IBookmark*>(pMark) != nullptr)
{ // need bookmark portions!
return false;
}
}
SwTwips nHeight = EmptyHeight();
if (aSet.GetParaGrid().GetValue() &&
@@ -488,35 +499,92 @@
return false;
};
bool SwControlCharPortion::DoPaint(SwTextPaintInfo const&,
OUString & rOutString, SwFont & rTmpFont, int &) const
{
if (mcChar == CHAR_ZWNBSP || !SwViewOption::IsFieldShadings())
{
return false;
}
switch (mcChar)
{
case CHAR_ZWSP:
rOutString = "/"; break;
// case CHAR_LRM :
// rText = sal_Unicode(0x2514); break;
// case CHAR_RLM :
// rText = sal_Unicode(0x2518); break;
default:
assert(false);
break;
}
rTmpFont.SetEscapement( CHAR_ZWSP == mcChar ? DFLT_ESC_AUTO_SUB : -25 );
const sal_uInt16 nProp = 40;
rTmpFont.SetProportion( nProp ); // a smaller font
return true;
}
bool SwBookmarkPortion::DoPaint(SwTextPaintInfo const& rInf,
OUString & rOutString, SwFont & rTmpFont, int & rDeltaY) const
{
if (!rInf.GetOpt().IsViewMetaChars())
{
return false;
}
rOutString = OUStringChar(mcChar);
// init font: we want OpenSymbol to ensure it doesn't look too crazy;
// thin and a bit higher than the surrounding text
auto const nOrigAscent(rTmpFont.GetAscent(rInf.GetVsh(), *rInf.GetOut()));
rTmpFont.SetName("OpenSymbol", rTmpFont.GetActual());
Size size(rTmpFont.GetSize(rTmpFont.GetActual()));
// use also the external leading (line gap) of the portion, but don't use
// 100% of it because i can't figure out how to baseline align that
auto const nFactor = (Height() * 95) / size.Height();
rTmpFont.SetProportion(nFactor);
rTmpFont.SetWeight(WEIGHT_THIN, rTmpFont.GetActual());
rTmpFont.SetColor(NON_PRINTING_CHARACTER_COLOR);
// reset these to default...
rTmpFont.SetAlign(ALIGN_BASELINE);
rTmpFont.SetUnderline(LINESTYLE_NONE);
rTmpFont.SetOverline(LINESTYLE_NONE);
rTmpFont.SetStrikeout(STRIKEOUT_NONE);
rTmpFont.SetOutline(false);
rTmpFont.SetShadow(false);
rTmpFont.SetTransparent(false);
rTmpFont.SetEmphasisMark(FontEmphasisMark::NONE);
rTmpFont.SetEscapement(0);
rTmpFont.SetPitch(PITCH_DONTKNOW, rTmpFont.GetActual());
rTmpFont.SetRelief(FontRelief::NONE);
// adjust Y position to account for different baselines of the fonts
auto const nOSAscent(rTmpFont.GetAscent(rInf.GetVsh(), *rInf.GetOut()));
rDeltaY = nOSAscent - nOrigAscent;
return true;
}
void SwControlCharPortion::Paint( const SwTextPaintInfo &rInf ) const
{
if ( Width() ) // is only set during prepaint mode
{
rInf.DrawViewOpt( *this, PortionType::ControlChar );
rInf.DrawViewOpt(*this, GetWhichPor());
if ( !rInf.GetOpt().IsPagePreview() &&
!rInf.GetOpt().IsReadonly() &&
SwViewOption::IsFieldShadings() &&
CHAR_ZWNBSP != mcChar )
int deltaY(0);
SwFont aTmpFont( *rInf.GetFont() );
OUString aOutString;
if (rInf.OnWin()
&& !rInf.GetOpt().IsPagePreview()
&& !rInf.GetOpt().IsReadonly()
&& DoPaint(rInf, aOutString, aTmpFont, deltaY))
{
SwFont aTmpFont( *rInf.GetFont() );
aTmpFont.SetEscapement( CHAR_ZWSP == mcChar ? DFLT_ESC_AUTO_SUB : -25 );
const sal_uInt16 nProp = 40;
aTmpFont.SetProportion( nProp ); // a smaller font
SwFontSave aFontSave( rInf, &aTmpFont );
OUString aOutString;
switch ( mcChar )
{
case CHAR_ZWSP :
aOutString = "/"; break;
// case CHAR_LRM :
// rText = sal_Unicode(0x2514); break;
// case CHAR_RLM :
// rText = sal_Unicode(0x2518); break;
}
if ( !mnHalfCharWidth )
mnHalfCharWidth = rInf.GetTextSize( aOutString ).Width() / 2;
@@ -527,12 +595,15 @@
{
case 0:
aNewPos.AdjustX(deltaX);
aNewPos.AdjustY(deltaY);
break;
case 900:
aNewPos.AdjustY(-deltaX);
aNewPos.AdjustX(deltaY);
break;
case 2700:
aNewPos.AdjustY(deltaX);
aNewPos.AdjustX(-deltaY);
break;
default:
assert(false);
@@ -565,4 +636,13 @@
return mnViewWidth;
}
SwLinePortion * SwBookmarkPortion::Unchain()
{
assert(!m_pPrevious || m_pPrevious->GetNextPortion() == this);
m_pPrevious->SetNextPortion(nullptr);
auto const pTmp(m_pPrevious);
m_pPrevious = nullptr;
return pTmp;
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/porrst.hxx b/sw/source/core/text/porrst.hxx
index 85e85d1..dcb2cfc 100644
--- a/sw/source/core/text/porrst.hxx
+++ b/sw/source/core/text/porrst.hxx
@@ -31,6 +31,7 @@
class SwPortionHandler;
class SwTextPaintInfo;
class SwTextSizeInfo;
class SwFont;
#define LINE_BREAK_WIDTH 150
#define SPECIAL_FONT_HEIGHT 200
@@ -133,6 +134,7 @@
private:
mutable sal_uInt16 mnViewWidth; // used to cache a calculated value
mutable sal_uInt16 mnHalfCharWidth; // used to cache a calculated value
protected:
sal_Unicode const mcChar;
public:
@@ -143,11 +145,35 @@
SetWhichPor( PortionType::ControlChar ); SetLen( TextFrameIndex(1) );
}
virtual bool DoPaint(SwTextPaintInfo const& rInf,
OUString & rOutString, SwFont & rTmpFont, int & rDeltaY) const;
virtual void Paint( const SwTextPaintInfo &rInf ) const override;
virtual bool Format( SwTextFormatInfo &rInf ) override;
virtual sal_uInt16 GetViewWidth( const SwTextSizeInfo& rInf ) const override;
};
/// for showing bookmark starts and ends; note that in contrast to
/// SwControlCharPortion these do not have a character in the text.
class SwBookmarkPortion : public SwControlCharPortion
{
private:
SwLinePortion * m_pPrevious;
public:
explicit SwBookmarkPortion(SwLinePortion *const pPrevious, sal_Unicode const cChar)
: SwControlCharPortion(cChar)
, m_pPrevious(pPrevious)
{
SetWhichPor(PortionType::Bookmark);
SetLen(TextFrameIndex(0));
}
virtual bool DoPaint(SwTextPaintInfo const& rInf,
OUString & rOutString, SwFont & rTmpFont, int & rDeltaY) const override;
virtual SwLinePortion * Compress() override { return this; }
SwLinePortion * Unchain();
};
#endif
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sw/source/core/text/xmldump.cxx b/sw/source/core/text/xmldump.cxx
index 510615b..3db640c 100644
--- a/sw/source/core/text/xmldump.cxx
+++ b/sw/source/core/text/xmldump.cxx
@@ -52,6 +52,7 @@
case PortionType::Multi: return "PortionType::Multi";
case PortionType::HiddenText: return "PortionType::HiddenText";
case PortionType::ControlChar: return "PortionType::ControlChar";
case PortionType::Bookmark: return "PortionType::Bookmark";
case PortionType::Text: return "PortionType::Text";
case PortionType::Lay: return "PortionType::Lay";