tdf#173649 sw: don't wrap docx text into a narrow gap beside a shape

Writer laid text into a 0.2 cm gap left of a text box, where Word
leaves the gap empty. Word gives up a gap under a quarter inch wide,
measured from the column edge to the object's wrap boundary, when the
other side has at least that much room. Follow the same rule for
documents the Word filters read.

Change-Id: Ifdacbf0469296ade1665fb0b56526301cd19f973
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211280
Reviewed-by: Samuel Mehrbrodt <samuel.mehrbrodt@collabora.com>
Tested-by: Jenkins
This commit is contained in:
Samuel Mehrbrodt
2026-09-21 20:38:58 +02:00
parent eb909d6847
commit 5b36e890ec
9 changed files with 72 additions and 0 deletions
+1
View File
@@ -71,6 +71,7 @@ enum class DocumentSettingId
MS_WORD_COMP_TRAILING_BLANKS,
// tdf#128197 MS Word in some modes can have line height based on shape height, not on font
MS_WORD_COMP_MIN_LINE_HEIGHT_BY_FLY,
MS_WORD_COMP_MIN_TEXT_WRAP_GAP,
UNIX_FORCE_ZERO_EXT_LEADING,
TABS_RELATIVE_TO_INDENT,
PROTECT_FORM,
Binary file not shown.
+19
View File
@@ -29,6 +29,25 @@ public:
}
};
CPPUNIT_TEST_FIXTURE(Test, testMinWrapGap)
{
// Given a document with a square-wrapped shape
createSwDoc("min-wrap-gap.docx");
// When layouting that document
calcLayout();
// Then make sure the line starts with the fly portion
xmlDocUniquePtr pXmlDoc = parseLayoutDump();
// Without the fix, this test would have failed with:
// - Expected: PortionType::Fly
// - Actual: PortionType::Text
assertXPath(pXmlDoc, "/root/page[1]/body/txt[2]/SwParaPortion/SwLineLayout/*[1]", "type",
u"PortionType::Fly");
// The paragraph below the shape should not be affected
assertXPath(pXmlDoc, "/root/page[1]/body/txt[5]/SwParaPortion/SwLineLayout/SwFixPortion", 0);
}
CPPUNIT_TEST_FIXTURE(Test, testClearingBreakWrapThrough)
{
// Given a document with a clearing break, then a shape in the next paragraph:
@@ -80,6 +80,7 @@ sw::DocumentSettingManager::DocumentSettingManager(SwDoc &rDoc)
mbTabRelativeToIndent(true),
mbProtectForm(false), // i#78591#
mbMsWordCompTrailingBlanks(false), // tdf#104349 tdf#104668
mbMsWordCompMinTextWrapGap(false),
mbMsWordCompMinLineHeightByFly(false),
mbInvertBorderSpacing (false),
mbCollapseEmptyCellPara(true),
@@ -221,6 +222,7 @@ bool sw::DocumentSettingManager::get(/*[in]*/ DocumentSettingId id) const
case DocumentSettingId::PROTECT_FORM: return mbProtectForm;
// tdf#104349 tdf#104668
case DocumentSettingId::MS_WORD_COMP_TRAILING_BLANKS: return mbMsWordCompTrailingBlanks;
case DocumentSettingId::MS_WORD_COMP_MIN_TEXT_WRAP_GAP: return mbMsWordCompMinTextWrapGap;
case DocumentSettingId::MS_WORD_COMP_MIN_LINE_HEIGHT_BY_FLY: return mbMsWordCompMinLineHeightByFly;
// #i89181#
case DocumentSettingId::TAB_AT_LEFT_INDENT_FOR_PARA_IN_LIST: return mbTabAtLeftIndentForParagraphsInList;
@@ -416,6 +418,10 @@ void sw::DocumentSettingManager::set(/*[in]*/ DocumentSettingId id, /*[in]*/ boo
mbMsWordCompTrailingBlanks = value;
break;
case DocumentSettingId::MS_WORD_COMP_MIN_TEXT_WRAP_GAP:
mbMsWordCompMinTextWrapGap = value;
break;
case DocumentSettingId::MS_WORD_COMP_MIN_LINE_HEIGHT_BY_FLY:
mbMsWordCompMinLineHeightByFly = value;
break;
@@ -806,6 +812,7 @@ void sw::DocumentSettingManager::ReplaceCompatibilityOptions(const DocumentSetti
mbTabRelativeToIndent = rSource.mbTabRelativeToIndent;
mbProtectForm = rSource.mbProtectForm;
mbMsWordCompTrailingBlanks = rSource.mbMsWordCompTrailingBlanks;
mbMsWordCompMinTextWrapGap = rSource.mbMsWordCompMinTextWrapGap;
mbMsWordCompMinLineHeightByFly = rSource.mbMsWordCompMinLineHeightByFly;
mbInvertBorderSpacing = rSource.mbInvertBorderSpacing;
mbCollapseEmptyCellPara = rSource.mbCollapseEmptyCellPara;
@@ -1068,6 +1075,11 @@ void sw::DocumentSettingManager::dumpAsXml(xmlTextWriterPtr pWriter) const
BAD_CAST(OString::boolean(mbMsWordCompTrailingBlanks).getStr()));
(void)xmlTextWriterEndElement(pWriter);
(void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbMsWordCompMinTextWrapGap"));
(void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"),
BAD_CAST(OString::boolean(mbMsWordCompMinTextWrapGap).getStr()));
(void)xmlTextWriterEndElement(pWriter);
(void)xmlTextWriterStartElement(pWriter, BAD_CAST("mbMsWordCompMinLineHeightByFly"));
(void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("value"),
BAD_CAST(OString::boolean(mbMsWordCompMinLineHeightByFly).getStr()));
@@ -143,6 +143,7 @@ class DocumentSettingManager final :
bool mbTabRelativeToIndent : 1; // #i24363# tab stops relative to indent
bool mbProtectForm : 1;
bool mbMsWordCompTrailingBlanks : 1; // tdf#104349 tdf#104668
bool mbMsWordCompMinTextWrapGap : 1;
bool mbMsWordCompMinLineHeightByFly : 1;
bool mbInvertBorderSpacing : 1;
bool mbCollapseEmptyCellPara : 1;
+23
View File
@@ -1479,6 +1479,10 @@ SwRect SwTextFly::AnchoredObjToRect( const SwAnchoredObject* pAnchoredObj,
// Wrap on both sides up to a frame width of 1.5cm
#define FRAME_MAX 850
// Word gives up a side narrower than 1/4 inch, measured from the column edge, when the
// other side has that much room. With both sides narrow it wraps on both.
#define WORD_TEXT_MIN 360
css::text::WrapTextMode SwTextFly::GetSurroundForTextWrap( const SwAnchoredObject* pAnchoredObj ) const
{
const SwFrameFormat* pFormat = pAnchoredObj->GetFrameFormat();
@@ -1553,6 +1557,25 @@ css::text::WrapTextMode SwTextFly::GetSurroundForTextWrap( const SwAnchoredObjec
}
}
// Ask the format rather than eSurroundForTextWrap, because an optimal wrap
// which the branch above turned into a parallel one measures its sides
// against the paragraph and keeps that result.
if (css::text::WrapTextMode_PARALLEL == rFlyFormat.GetSurround()
&& GetMaster()->GetDoc().getIDocumentSettingAccess().get(
DocumentSettingId::MS_WORD_COMP_MIN_TEXT_WRAP_GAP))
{
SwRectFnSet aRectFnSet(*m_pCurrFrame);
const SwRect& rObj(pAnchoredObj->GetObjRectWithSpaces());
const SwRect& rCol(m_pCurrFrame->getFrameArea());
const tools::Long nLeft = aRectFnSet.GetLeft(rObj) - aRectFnSet.GetLeft(rCol);
const tools::Long nRight = aRectFnSet.GetRight(rCol) - aRectFnSet.GetRight(rObj);
// If neither side has enough space, keep the wrap mode as PARALLEL
if (nLeft < WORD_TEXT_MIN && nRight >= WORD_TEXT_MIN)
eSurroundForTextWrap = css::text::WrapTextMode_RIGHT;
else if (nRight < WORD_TEXT_MIN && nLeft >= WORD_TEXT_MIN)
eSurroundForTextWrap = css::text::WrapTextMode_LEFT;
}
return eSurroundForTextWrap;
}
+2
View File
@@ -1954,6 +1954,8 @@ void SwWW8ImplReader::ImportDop()
DocumentSettingId::APPLY_PARAGRAPH_MARK_FORMAT_TO_NUMBERING, true);
m_rDoc.getIDocumentSettingAccess().set(
DocumentSettingId::MS_WORD_COMP_TRAILING_BLANKS, true);
m_rDoc.getIDocumentSettingAccess().set(
DocumentSettingId::MS_WORD_COMP_MIN_TEXT_WRAP_GAP, true);
// tdf#128195
m_rDoc.getIDocumentSettingAccess().set(
DocumentSettingId::HEADER_SPACING_BELOW_LAST_PARA, true);
@@ -114,6 +114,7 @@ enum SwDocumentSettingsPropertyHandles
HANDLE_USE_OLD_PRINTER_METRICS,
HANDLE_PROTECT_FORM,
HANDLE_MS_WORD_COMP_TRAILING_BLANKS,
HANDLE_MS_WORD_COMP_MIN_TEXT_WRAP_GAP,
HANDLE_MS_WORD_COMP_MIN_LINE_HEIGHT_BY_FLY,
HANDLE_TABS_RELATIVE_TO_INDENT,
HANDLE_RSID,
@@ -237,6 +238,7 @@ static rtl::Reference<MasterPropertySetInfo> lcl_createSettingsInfo()
{ u"RsidRoot"_ustr, HANDLE_RSID_ROOT, cppu::UnoType<sal_Int32>::get(), 0},
{ u"ProtectForm"_ustr, HANDLE_PROTECT_FORM, cppu::UnoType<bool>::get(), 0},
{ u"MsWordCompTrailingBlanks"_ustr, HANDLE_MS_WORD_COMP_TRAILING_BLANKS, cppu::UnoType<bool>::get(), 0 },
{ u"MsWordCompMinTextWrapGap"_ustr, HANDLE_MS_WORD_COMP_MIN_TEXT_WRAP_GAP, cppu::UnoType<bool>::get(), 0 },
{ u"MsWordCompMinLineHeightByFly"_ustr, HANDLE_MS_WORD_COMP_MIN_LINE_HEIGHT_BY_FLY, cppu::UnoType<bool>::get(), 0 },
{ u"TabAtLeftIndentForParagraphsInList"_ustr, HANDLE_TAB_AT_LEFT_INDENT_FOR_PARA_IN_LIST, cppu::UnoType<bool>::get(), 0},
{ u"ModifyPasswordInfo"_ustr, HANDLE_MODIFYPASSWORDINFO, cppu::UnoType< cppu::UnoSequenceType<css::beans::PropertyValue> >::get(), 0},
@@ -825,6 +827,12 @@ void SwXDocumentSettings::_setSingleValue( const comphelper::PropertyInfo & rInf
mpDoc->getIDocumentSettingAccess().set(DocumentSettingId::MS_WORD_COMP_TRAILING_BLANKS, bTmp);
}
break;
case HANDLE_MS_WORD_COMP_MIN_TEXT_WRAP_GAP:
{
bool bTmp = *o3tl::doAccess<bool>(rValue);
mpDoc->getIDocumentSettingAccess().set(DocumentSettingId::MS_WORD_COMP_MIN_TEXT_WRAP_GAP, bTmp);
}
break;
case HANDLE_MS_WORD_COMP_MIN_LINE_HEIGHT_BY_FLY:
{
bool bTmp = *o3tl::doAccess<bool>(rValue);
@@ -1618,6 +1626,11 @@ void SwXDocumentSettings::_getSingleValue( const comphelper::PropertyInfo & rInf
rValue <<= mpDoc->getIDocumentSettingAccess().get(DocumentSettingId::MS_WORD_COMP_TRAILING_BLANKS);
}
break;
case HANDLE_MS_WORD_COMP_MIN_TEXT_WRAP_GAP:
{
rValue <<= mpDoc->getIDocumentSettingAccess().get(DocumentSettingId::MS_WORD_COMP_MIN_TEXT_WRAP_GAP);
}
break;
case HANDLE_MS_WORD_COMP_MIN_LINE_HEIGHT_BY_FLY:
{
rValue <<= mpDoc->getIDocumentSettingAccess().get(DocumentSettingId::MS_WORD_COMP_MIN_LINE_HEIGHT_BY_FLY);
@@ -675,6 +675,7 @@ void SettingsTable::ApplyProperties(rtl::Reference<SwXTextDocument> const& xDoc)
// Shared between DOCX and RTF, unconditional flags.
xDocumentSettings->setPropertyValue(u"TableRowKeep"_ustr, uno::Any(true));
xDocumentSettings->setPropertyValue(u"MsWordCompMinTextWrapGap"_ustr, uno::Any(true));
xDocumentSettings->setPropertyValue(u"AddVerticalFrameOffsets"_ustr, uno::Any(true));
xDocumentSettings->setPropertyValue(u"ApplyParagraphMarkFormatToEmptyLineAtEndOfParagraph"_ustr,
uno::Any(true));