android: Update app-specific/MIME type icons
[LibreOffice.git] / writerfilter / source / rtftok / rtfdispatchsymbol.cxx
blob9aa9a2ce4a2e24dcbaa2ac5b374f1cee58a05b15
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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/.
8 */
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)
28 setNeedSect(true);
29 if (nKeyword != RTFKeyword::HEXCHAR)
30 checkUnicode(/*bUnicode =*/true, /*bHex =*/true);
31 else
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);
39 return RTFError::OK;
41 // Trivial symbols
42 sal_uInt8 cCh = 0;
43 switch (nKeyword)
45 case RTFKeyword::TAB:
46 cCh = '\t';
47 break;
48 case RTFKeyword::BACKSLASH:
49 cCh = '\\';
50 break;
51 case RTFKeyword::LBRACE:
52 cCh = '{';
53 break;
54 case RTFKeyword::RBRACE:
55 cCh = '}';
56 break;
57 case RTFKeyword::EMDASH:
58 cCh = 151;
59 break;
60 case RTFKeyword::ENDASH:
61 cCh = 150;
62 break;
63 case RTFKeyword::BULLET:
64 cCh = 149;
65 break;
66 case RTFKeyword::LQUOTE:
67 cCh = 145;
68 break;
69 case RTFKeyword::RQUOTE:
70 cCh = 146;
71 break;
72 case RTFKeyword::LDBLQUOTE:
73 cCh = 147;
74 break;
75 case RTFKeyword::RDBLQUOTE:
76 cCh = 148;
77 break;
78 default:
79 break;
81 if (cCh > 0)
83 OUString aStr(OStringToOUString(OStringChar(char(cCh)), RTL_TEXTENCODING_MS_1252));
84 text(aStr);
85 return RTFError::OK;
88 switch (nKeyword)
90 case RTFKeyword::IGNORE:
92 m_bSkipUnknown = true;
93 aSkip.setReset(false);
94 return RTFError::OK;
96 break;
97 case RTFKeyword::PAR:
99 if (m_aStates.top().getDestination() == Destination::FOOTNOTESEPARATOR)
100 break; // just ignore it - only thing we read in here is CHFTNSEP
101 checkFirstRun();
102 checkNeedPap();
103 runProps(); // tdf#152872 paragraph marker formatting
104 if (!m_aStates.top().getCurrentBuffer())
106 parBreak();
107 // Not in table? Reset max width.
108 if (m_nCellxMax)
110 // Was in table, but not anymore -> tblEnd.
111 RTFSprms aAttributes;
112 RTFSprms aSprms;
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);
118 m_nCellxMax = 0;
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
126 m_bNeedPap = true;
127 if (!m_aStates.top().getFrame().inFrame())
128 m_bNeedPar = false;
129 m_bNeedFinalPar = false;
131 break;
132 case RTFKeyword::SECT:
134 if (m_bNeedCr)
135 dispatchSymbol(RTFKeyword::PAR);
137 m_bHadSect = true;
138 if (m_bIgnoreNextContSectBreak)
139 m_bIgnoreNextContSectBreak = false;
140 else
142 sectBreak();
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
152 break;
153 case RTFKeyword::NOBREAK:
155 OUString aStr(SVT_HARD_SPACE);
156 text(aStr);
158 break;
159 case RTFKeyword::NOBRKHYPH:
161 OUString aStr(SVT_HARD_HYPHEN);
162 text(aStr);
164 break;
165 case RTFKeyword::OPTHYPH:
167 OUString aStr(SVT_SOFT_HYPHEN);
168 text(aStr);
170 break;
171 case RTFKeyword::HEXCHAR:
172 m_aStates.top().setInternalState(RTFInternalState::HEX);
173 break;
174 case RTFKeyword::CELL:
175 case RTFKeyword::NESTCELL:
177 if (nKeyword == RTFKeyword::CELL)
178 m_bAfterCellBeforeRow = true;
180 checkFirstRun();
181 if (m_bNeedPap)
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));
194 m_bNeedPap = true;
196 break;
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",
209 nullptr);
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();
227 m_nNestedCells = 0;
228 m_bNeedPap = true;
230 break;
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();
255 bRestored = true;
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;
279 else
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);
316 m_bNeedPap = true;
317 m_bNeedFinalPar = true;
318 m_aTableBufferStack.back().clear();
319 m_nTopLevelCells = 0;
321 if (bRestored)
322 // We restored cell definitions, clear these now.
323 // This is necessary, as later cell definitions want to overwrite the restored ones.
324 resetTableRowProperties();
326 break;
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);
332 if (pCols)
334 RTFValue::Pointer_t pNum = pCols->getAttributes().find(NS_ooxml::LN_CT_Columns_num);
335 if (pNum && pNum->getInt() > 1)
336 bColumns = true;
338 checkFirstRun();
339 if (bColumns)
341 sal_uInt8 const sBreak[] = { 0xe };
342 Mapper().startCharacterGroup();
343 Mapper().text(sBreak, 1);
344 Mapper().endCharacterGroup();
346 else
347 dispatchSymbol(RTFKeyword::PAGE);
349 break;
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);
355 break;
357 case RTFKeyword::PAGE:
359 // Ignore page breaks inside tables.
360 if (m_aStates.top().getCurrentBuffer() == &m_aTableBufferStack.back())
361 break;
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);
369 if (((pBreak
370 && pBreak->getInt()
371 == static_cast<sal_Int32>(NS_ooxml::LN_Value_ST_SectionMark_continuous))
372 || m_nResetBreakOnSectBreak == RTFKeyword::SBKNONE)
373 && !(pTitlePg && pTitlePg->getInt()))
375 if (m_bWasInFrame)
377 dispatchSymbol(RTFKeyword::PAR);
378 m_bWasInFrame = false;
380 sectBreak();
381 // note: this will not affect the following section break
382 // but the one just pushed
383 dispatchFlag(RTFKeyword::SBKPAGE);
384 if (m_bNeedPar)
385 dispatchSymbol(RTFKeyword::PAR);
386 m_bIgnoreNextContSectBreak = true;
387 // arrange to clean up the synthetic RTFKeyword::SBKPAGE
388 m_nResetBreakOnSectBreak = RTFKeyword::SBKNONE;
390 else
392 bool bFirstRun = m_bFirstRun;
393 checkFirstRun();
394 if (bFirstRun || m_bNeedCr)
396 // Only send the paragraph properties early if we'll create a new paragraph in a
397 // bit anyway.
398 checkNeedPap();
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.
407 if (!m_bNeedPap)
409 parBreak();
410 m_bNeedPap = true;
413 m_bNeedCr = true;
416 break;
417 case RTFKeyword::CHPGN:
419 OUString aStr("PAGE");
420 singleChar(cFieldStart);
421 text(aStr);
422 singleChar(cFieldSep, true);
423 singleChar(cFieldEnd);
425 break;
426 case RTFKeyword::CHFTNSEP:
428 static const sal_Unicode uFtnEdnSep = 0x3;
429 Mapper().utext(reinterpret_cast<const sal_uInt8*>(&uFtnEdnSep), 1);
431 break;
432 default:
434 SAL_INFO("writerfilter.rtf",
435 "TODO handle symbol '" << keywordToString(nKeyword) << "'");
436 aSkip.setParsed(false);
438 break;
440 return RTFError::OK;
443 } // namespace writerfilter::rtftok
445 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */