1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
10 #include "rtfdocumentimpl.hxx"
12 #include <com/sun/star/io/WrongFormatException.hpp>
13 #include <svl/lngmisc.hxx>
15 #include <ooxml/resourceids.hxx>
17 #include <sal/log.hxx>
19 #include "rtfreferenceproperties.hxx"
20 #include "rtfskipdestination.hxx"
22 using namespace com::sun::star
;
24 namespace writerfilter::rtftok
26 RTFError
RTFDocumentImpl::dispatchSymbol(RTFKeyword nKeyword
)
29 if (nKeyword
!= RTFKeyword::HEXCHAR
)
30 checkUnicode(/*bUnicode =*/true, /*bHex =*/true);
32 checkUnicode(/*bUnicode =*/true, /*bHex =*/false);
33 RTFSkipDestination
aSkip(*this);
35 if (RTFKeyword::LINE
== nKeyword
)
37 // very special handling since text() will eat lone '\n'
38 singleChar('\n', /*bRunProps=*/true);
48 case RTFKeyword::BACKSLASH
:
51 case RTFKeyword::LBRACE
:
54 case RTFKeyword::RBRACE
:
57 case RTFKeyword::EMDASH
:
60 case RTFKeyword::ENDASH
:
63 case RTFKeyword::BULLET
:
66 case RTFKeyword::LQUOTE
:
69 case RTFKeyword::RQUOTE
:
72 case RTFKeyword::LDBLQUOTE
:
75 case RTFKeyword::RDBLQUOTE
:
83 OUString
aStr(OStringToOUString(OStringChar(char(cCh
)), RTL_TEXTENCODING_MS_1252
));
90 case RTFKeyword::IGNORE
:
92 m_bSkipUnknown
= true;
93 aSkip
.setReset(false);
99 if (m_aStates
.top().getDestination() == Destination::FOOTNOTESEPARATOR
)
100 break; // just ignore it - only thing we read in here is CHFTNSEP
103 runProps(); // tdf#152872 paragraph marker formatting
104 if (!m_aStates
.top().getCurrentBuffer())
107 // Not in table? Reset max width.
110 // Was in table, but not anymore -> tblEnd.
111 RTFSprms aAttributes
;
113 aSprms
.set(NS_ooxml::LN_tblEnd
, new RTFValue(1));
114 writerfilter::Reference
<Properties
>::Pointer_t pProperties
115 = new RTFReferenceProperties(std::move(aAttributes
), std::move(aSprms
));
116 Mapper().props(pProperties
);
120 else if (m_aStates
.top().getDestination() != Destination::SHAPETEXT
)
122 RTFValue::Pointer_t pValue
;
123 m_aStates
.top().getCurrentBuffer()->push_back(Buf_t(BUFFER_PAR
, pValue
, nullptr));
125 // but don't emit properties yet, since they may change till the first text token arrives
127 if (!m_aStates
.top().getFrame().inFrame())
129 m_bNeedFinalPar
= false;
132 case RTFKeyword::SECT
:
135 dispatchSymbol(RTFKeyword::PAR
);
138 if (m_bIgnoreNextContSectBreak
)
139 m_bIgnoreNextContSectBreak
= false;
143 if (m_nResetBreakOnSectBreak
!= RTFKeyword::invalid
)
145 // this should run on _second_ \sect after \page
146 dispatchSymbol(m_nResetBreakOnSectBreak
); // lazy reset
147 m_nResetBreakOnSectBreak
= RTFKeyword::invalid
;
148 m_bNeedSect
= false; // dispatchSymbol set it
153 case RTFKeyword::NOBREAK
:
155 OUString
aStr(SVT_HARD_SPACE
);
159 case RTFKeyword::NOBRKHYPH
:
161 OUString
aStr(SVT_HARD_HYPHEN
);
165 case RTFKeyword::OPTHYPH
:
167 OUString
aStr(SVT_SOFT_HYPHEN
);
171 case RTFKeyword::HEXCHAR
:
172 m_aStates
.top().setInternalState(RTFInternalState::HEX
);
174 case RTFKeyword::CELL
:
175 case RTFKeyword::NESTCELL
:
177 if (nKeyword
== RTFKeyword::CELL
)
178 m_bAfterCellBeforeRow
= true;
183 // There were no runs in the cell, so we need to send paragraph and character properties here.
184 auto pPValue
= new RTFValue(m_aStates
.top().getParagraphAttributes(),
185 m_aStates
.top().getParagraphSprms());
186 bufferProperties(m_aTableBufferStack
.back(), pPValue
, nullptr);
187 auto pCValue
= new RTFValue(m_aStates
.top().getCharacterAttributes(),
188 m_aStates
.top().getCharacterSprms());
189 bufferProperties(m_aTableBufferStack
.back(), pCValue
, nullptr);
192 RTFValue::Pointer_t pValue
;
193 m_aTableBufferStack
.back().emplace_back(Buf_t(BUFFER_CELLEND
, pValue
, nullptr));
197 case RTFKeyword::NESTROW
:
199 tools::SvRef
<TableRowBuffer
> const pBuffer(
200 new TableRowBuffer(m_aTableBufferStack
.back(), m_aNestedTableCellsSprms
,
201 m_aNestedTableCellsAttributes
, m_nNestedCells
));
202 prepareProperties(m_aStates
.top(), pBuffer
->GetParaProperties(),
203 pBuffer
->GetFrameProperties(), pBuffer
->GetRowProperties(),
204 m_nNestedCells
, m_nNestedCurrentCellX
- m_nNestedTRLeft
);
206 if (m_aTableBufferStack
.size() == 1 || !m_aStates
.top().getCurrentBuffer())
208 throw io::WrongFormatException("mismatch between \\itap and number of \\nestrow",
211 assert(m_aStates
.top().getCurrentBuffer() == &m_aTableBufferStack
.back());
212 // note: there may be several states pointing to table buffer!
213 for (std::size_t i
= 0; i
< m_aStates
.size(); ++i
)
215 if (m_aStates
[i
].getCurrentBuffer() == &m_aTableBufferStack
.back())
217 m_aStates
[i
].setCurrentBuffer(
218 &m_aTableBufferStack
[m_aTableBufferStack
.size() - 2]);
221 m_aTableBufferStack
.pop_back();
222 m_aTableBufferStack
.back().emplace_back(
223 Buf_t(BUFFER_NESTROW
, RTFValue::Pointer_t(), pBuffer
));
225 m_aNestedTableCellsSprms
.clear();
226 m_aNestedTableCellsAttributes
.clear();
231 case RTFKeyword::ROW
:
233 m_bAfterCellBeforeRow
= false;
234 if (m_aStates
.top().getTableRowWidthAfter() > 0)
236 // Add fake cellx / cell, RTF equivalent of
237 // OOXMLFastContextHandlerTextTableRow::handleGridAfter().
238 auto pXValue
= new RTFValue(m_aStates
.top().getTableRowWidthAfter());
239 m_aStates
.top().getTableRowSprms().set(NS_ooxml::LN_CT_TblGridBase_gridCol
, pXValue
,
240 RTFOverwrite::NO_APPEND
);
241 dispatchSymbol(RTFKeyword::CELL
);
243 // Adjust total width, which is done in the \cellx handler for normal cells.
244 m_nTopLevelCurrentCellX
+= m_aStates
.top().getTableRowWidthAfter();
246 m_aStates
.top().setTableRowWidthAfter(0);
249 bool bRestored
= false;
250 // Ending a row, but no cells defined?
251 // See if there was an invalid table row reset, so we can restore cell infos to help invalid documents.
252 if (!m_nTopLevelCurrentCellX
&& m_nBackupTopLevelCurrentCellX
)
254 restoreTableRowProperties();
258 // If the right edge of the last cell (row width) is smaller than the width of some other row, mimic WW8TabDesc::CalcDefaults(): resize the last cell
259 const int MINLAY
= 23; // sw/inc/swtypes.hxx, minimal possible size of frames.
260 if ((m_nCellxMax
- m_nTopLevelCurrentCellX
) >= MINLAY
)
262 auto pXValueLast
= m_aStates
.top().getTableRowSprms().find(
263 NS_ooxml::LN_CT_TblGridBase_gridCol
, false);
264 const int nXValueLast
= pXValueLast
? pXValueLast
->getInt() : 0;
265 auto pXValue
= new RTFValue(nXValueLast
+ m_nCellxMax
- m_nTopLevelCurrentCellX
);
266 m_aStates
.top().getTableRowSprms().eraseLast(NS_ooxml::LN_CT_TblGridBase_gridCol
);
267 m_aStates
.top().getTableRowSprms().set(NS_ooxml::LN_CT_TblGridBase_gridCol
, pXValue
,
268 RTFOverwrite::NO_APPEND
);
269 m_nTopLevelCurrentCellX
= m_nCellxMax
;
272 if (m_nTopLevelCells
)
274 // Make a backup before we start popping elements
275 m_aTableInheritingCellsSprms
= m_aTopLevelTableCellsSprms
;
276 m_aTableInheritingCellsAttributes
= m_aTopLevelTableCellsAttributes
;
277 m_nInheritingCells
= m_nTopLevelCells
;
281 // No table definition? Then inherit from the previous row
282 m_aTopLevelTableCellsSprms
= m_aTableInheritingCellsSprms
;
283 m_aTopLevelTableCellsAttributes
= m_aTableInheritingCellsAttributes
;
284 m_nTopLevelCells
= m_nInheritingCells
;
287 while (m_aTableBufferStack
.size() > 1)
289 SAL_WARN("writerfilter.rtf", "dropping extra table buffer");
290 // note: there may be several states pointing to table buffer!
291 for (std::size_t i
= 0; i
< m_aStates
.size(); ++i
)
293 if (m_aStates
[i
].getCurrentBuffer() == &m_aTableBufferStack
.back())
295 m_aStates
[i
].setCurrentBuffer(&m_aTableBufferStack
.front());
298 m_aTableBufferStack
.pop_back();
301 replayRowBuffer(m_aTableBufferStack
.back(), m_aTopLevelTableCellsSprms
,
302 m_aTopLevelTableCellsAttributes
, m_nTopLevelCells
);
304 // The scope of the table cell defaults is one row.
305 m_aDefaultState
.getTableCellSprms().clear();
306 m_aStates
.top().getTableCellSprms() = m_aDefaultState
.getTableCellSprms();
307 m_aStates
.top().getTableCellAttributes() = m_aDefaultState
.getTableCellAttributes();
309 writerfilter::Reference
<Properties
>::Pointer_t paraProperties
;
310 writerfilter::Reference
<Properties
>::Pointer_t frameProperties
;
311 writerfilter::Reference
<Properties
>::Pointer_t rowProperties
;
312 prepareProperties(m_aStates
.top(), paraProperties
, frameProperties
, rowProperties
,
313 m_nTopLevelCells
, m_nTopLevelCurrentCellX
- m_nTopLevelTRLeft
);
314 sendProperties(paraProperties
, frameProperties
, rowProperties
);
317 m_bNeedFinalPar
= true;
318 m_aTableBufferStack
.back().clear();
319 m_nTopLevelCells
= 0;
322 // We restored cell definitions, clear these now.
323 // This is necessary, as later cell definitions want to overwrite the restored ones.
324 resetTableRowProperties();
327 case RTFKeyword::COLUMN
:
329 bool bColumns
= false; // If we have multiple columns
330 RTFValue::Pointer_t pCols
331 = m_aStates
.top().getSectionSprms().find(NS_ooxml::LN_EG_SectPrContents_cols
);
334 RTFValue::Pointer_t pNum
= pCols
->getAttributes().find(NS_ooxml::LN_CT_Columns_num
);
335 if (pNum
&& pNum
->getInt() > 1)
341 sal_uInt8
const sBreak
[] = { 0xe };
342 Mapper().startCharacterGroup();
343 Mapper().text(sBreak
, 1);
344 Mapper().endCharacterGroup();
347 dispatchSymbol(RTFKeyword::PAGE
);
350 case RTFKeyword::CHFTN
:
352 if (m_aStates
.top().getCurrentBuffer() == &m_aSuperBuffer
)
353 // Stop buffering, there will be no custom mark for this footnote or endnote.
354 m_aStates
.top().setCurrentBuffer(nullptr);
357 case RTFKeyword::PAGE
:
359 // Ignore page breaks inside tables.
360 if (m_aStates
.top().getCurrentBuffer() == &m_aTableBufferStack
.back())
363 // If we're inside a continuous section, we should send a section break, not a page one.
364 RTFValue::Pointer_t pBreak
365 = m_aStates
.top().getSectionSprms().find(NS_ooxml::LN_EG_SectPrContents_type
);
366 // Unless we're on a title page.
367 RTFValue::Pointer_t pTitlePg
368 = m_aStates
.top().getSectionSprms().find(NS_ooxml::LN_EG_SectPrContents_titlePg
);
371 == static_cast<sal_Int32
>(NS_ooxml::LN_Value_ST_SectionMark_continuous
))
372 || m_nResetBreakOnSectBreak
== RTFKeyword::SBKNONE
)
373 && !(pTitlePg
&& pTitlePg
->getInt()))
377 dispatchSymbol(RTFKeyword::PAR
);
378 m_bWasInFrame
= false;
381 // note: this will not affect the following section break
382 // but the one just pushed
383 dispatchFlag(RTFKeyword::SBKPAGE
);
385 dispatchSymbol(RTFKeyword::PAR
);
386 m_bIgnoreNextContSectBreak
= true;
387 // arrange to clean up the synthetic RTFKeyword::SBKPAGE
388 m_nResetBreakOnSectBreak
= RTFKeyword::SBKNONE
;
392 bool bFirstRun
= m_bFirstRun
;
394 if (bFirstRun
|| m_bNeedCr
)
396 // Only send the paragraph properties early if we'll create a new paragraph in a
400 sal_uInt8
const sBreak
[] = { 0xc };
401 Mapper().text(sBreak
, 1);
402 if (bFirstRun
|| m_bNeedCr
)
404 // If we don't have content in the document yet (so the break-before can't move
405 // to a second layout page) or we already have characters sent (so the paragraph
406 // properties are already finalized), then continue inserting a fake paragraph.
417 case RTFKeyword::CHPGN
:
419 OUString
aStr("PAGE");
420 singleChar(cFieldStart
);
422 singleChar(cFieldSep
, true);
423 singleChar(cFieldEnd
);
426 case RTFKeyword::CHFTNSEP
:
428 static const sal_Unicode uFtnEdnSep
= 0x3;
429 Mapper().utext(reinterpret_cast<const sal_uInt8
*>(&uFtnEdnSep
), 1);
434 SAL_INFO("writerfilter.rtf",
435 "TODO handle symbol '" << keywordToString(nKeyword
) << "'");
436 aSkip
.setParsed(false);
443 } // namespace writerfilter::rtftok
445 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */