xref: /core/sc/source/filter/rtf/rtfparse.cxx (revision 4790ef5c)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
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  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include <memory>
21 #include <scitems.hxx>
22 #include <editeng/eeitem.hxx>
23 #include <editeng/editeng.hxx>
24 #include <editeng/editids.hrc>
25 #include <editeng/fhgtitem.hxx>
26 #include <editeng/svxrtf.hxx>
27 #include <svtools/rtftoken.h>
28 #include <osl/diagnose.h>
29 #include <svl/itempool.hxx>
30 
31 #include <rtfparse.hxx>
32 
33 #define SC_RTFTWIPTOL 10        // 10 Twips tolerance when determining columns
34 
35 ScRTFParser::ScRTFParser( EditEngine* pEditP ) :
36         ScEEParser( pEditP ),
37         mnCurPos(0),
38         pActDefault( nullptr ),
39         pDefMerge( nullptr ),
40         nStartAdjust( sal_uLong(~0) ),
41         nLastWidth(0),
42         bNewDef( false )
43 {
44     // RTF default FontSize 12Pt
45     tools::Long nMM = o3tl::convert(12, o3tl::Length::pt, o3tl::Length::mm100);
46 
47     pPool->SetUserDefaultItem( SvxFontHeightItem( nMM, 100, EE_CHAR_FONTHEIGHT ) );
48     // Free-flying pInsDefault
49     pInsDefault.reset( new ScRTFCellDefault( pPool.get() ) );
50 }
51 
52 ScRTFParser::~ScRTFParser()
53 {
54     pInsDefault.reset();
55     maDefaultList.clear();
56 }
57 
58 ErrCode ScRTFParser::Read( SvStream& rStream, const OUString& rBaseURL )
59 {
60     Link<RtfImportInfo&,void> aOldLink = pEdit->GetRtfImportHdl();
61     pEdit->SetRtfImportHdl( LINK( this, ScRTFParser, RTFImportHdl ) );
62     ErrCode nErr = pEdit->Read( rStream, rBaseURL, EETextFormat::Rtf );
63     if ( nRtfLastToken == RTF_PAR )
64     {
65         if ( !maList.empty() )
66         {
67             auto& pE = maList.back();
68             if (    // Completely empty
69                 (  pE->aSel.nStartPara == pE->aSel.nEndPara
70                    && pE->aSel.nStartPos  == pE->aSel.nEndPos
71                 )
72                 ||  // Empty paragraph
73                 (  pE->aSel.nStartPara + 1 == pE->aSel.nEndPara
74                    && pE->aSel.nStartPos      == pEdit->GetTextLen( pE->aSel.nStartPara )
75                    && pE->aSel.nEndPos        == 0
76                 )
77                )
78             {   // Don't take over the last paragraph
79                 maList.pop_back();
80             }
81         }
82     }
83     ColAdjust();
84     pEdit->SetRtfImportHdl( aOldLink );
85     return nErr;
86 }
87 
88 void ScRTFParser::EntryEnd( ScEEParseEntry* pE, const ESelection& aSel )
89 {
90     // Paragraph -2 strips the attached empty paragraph
91     pE->aSel.nEndPara = aSel.nEndPara - 2;
92     // Although it's called nEndPos, the last one is position + 1
93     pE->aSel.nEndPos = pEdit->GetTextLen( aSel.nEndPara - 1 );
94 }
95 
96 inline void ScRTFParser::NextRow()
97 {
98     if ( nRowMax < ++nRowCnt )
99         nRowMax = nRowCnt;
100 }
101 
102 bool ScRTFParser::SeekTwips( sal_uInt16 nTwips, SCCOL* pCol )
103 {
104     ScRTFColTwips::const_iterator it = aColTwips.find( nTwips );
105     bool bFound = it != aColTwips.end();
106     sal_uInt16 nPos = it - aColTwips.begin();
107     *pCol = static_cast<SCCOL>(nPos);
108     if ( bFound )
109         return true;
110     sal_uInt16 nCount = aColTwips.size();
111     if ( !nCount )
112         return false;
113     SCCOL nCol = *pCol;
114     // nCol is insertion position; the next one higher up is there (or not)
115     if ( nCol < static_cast<SCCOL>(nCount) && ((aColTwips[nCol] - SC_RTFTWIPTOL) <= nTwips) )
116         return true;
117     // Not smaller than everything else? Then compare with the next lower one
118     else if ( nCol != 0 && ((aColTwips[nCol-1] + SC_RTFTWIPTOL) >= nTwips) )
119     {
120         (*pCol)--;
121         return true;
122     }
123     return false;
124 }
125 
126 void ScRTFParser::ColAdjust()
127 {
128     if ( nStartAdjust == sal_uLong(~0) )
129         return;
130 
131     SCCOL nCol = 0;
132     for (size_t i = nStartAdjust, nListSize = maList.size(); i < nListSize; ++i)
133     {
134         auto& pE = maList[i];
135         if ( pE->nCol == 0 )
136             nCol = 0;
137         pE->nCol = nCol;
138         if ( pE->nColOverlap > 1 )
139             nCol = nCol + pE->nColOverlap; // Merged cells with \clmrg
140         else
141         {
142             SeekTwips( pE->nTwips, &nCol );
143             if ( ++nCol <= pE->nCol )
144                 nCol = pE->nCol + 1; // Moved cell X
145             pE->nColOverlap = nCol - pE->nCol; // Merged cells without \clmrg
146         }
147         if ( nCol > nColMax )
148             nColMax = nCol;
149     }
150     nStartAdjust = sal_uLong(~0);
151     aColTwips.clear();
152 }
153 
154 IMPL_LINK( ScRTFParser, RTFImportHdl, RtfImportInfo&, rInfo, void )
155 {
156     switch ( rInfo.eState )
157     {
158         case RtfImportState::NextToken:
159             ProcToken( &rInfo );
160             break;
161         case RtfImportState::UnknownAttr:
162             ProcToken( &rInfo );
163             break;
164         case RtfImportState::Start:
165         {
166             SvxRTFParser* pParser = static_cast<SvxRTFParser*>(rInfo.pParser);
167             pParser->SetAttrPool( pPool.get() );
168             pParser->SetPardMap(SID_ATTR_BRUSH, ATTR_BACKGROUND);
169             pParser->SetPardMap(SID_ATTR_BORDER_OUTER, ATTR_BORDER);
170             pParser->SetPardMap(SID_ATTR_BORDER_SHADOW, ATTR_SHADOW);
171         }
172             break;
173         case RtfImportState::End:
174             if ( rInfo.aSelection.nEndPos )
175             {   // If still text: create last paragraph
176                 pActDefault = nullptr;
177                 rInfo.nToken = RTF_PAR;
178                 // EditEngine did not attach an empty paragraph anymore
179                 // which EntryEnd could strip
180                 rInfo.aSelection.nEndPara++;
181                 ProcToken( &rInfo );
182             }
183             break;
184         case RtfImportState::SetAttr:
185             break;
186         case RtfImportState::InsertText:
187             break;
188         case RtfImportState::InsertPara:
189             break;
190         default:
191             OSL_FAIL("unknown ImportInfo.eState");
192     }
193 }
194 
195 // Bad behavior:
196 // For RTF_INTBL or respectively at the start of the first RTF_CELL
197 // after RTF_CELLX if there was no RTF_INTBL
198 void ScRTFParser::NewCellRow()
199 {
200     if ( bNewDef )
201     {
202         bNewDef = false;
203         // Not flush on the right? => new table
204         if ( nLastWidth && !maDefaultList.empty() )
205         {
206             const ScRTFCellDefault& rD = *maDefaultList.back();
207             if (rD.nTwips != nLastWidth)
208             {
209                 SCCOL n1, n2;
210                 if ( !(  SeekTwips( nLastWidth, &n1 )
211                       && SeekTwips( rD.nTwips, &n2 )
212                       && n1 == n2
213                       )
214                 )
215                 {
216                     ColAdjust();
217                 }
218             }
219         }
220         // Build up TwipCols only after nLastWidth comparison!
221         for (const std::unique_ptr<ScRTFCellDefault> & pCellDefault : maDefaultList)
222         {
223             const ScRTFCellDefault& rD = *pCellDefault;
224             SCCOL nCol;
225             if ( !SeekTwips(rD.nTwips, &nCol) )
226                 aColTwips.insert( rD.nTwips );
227         }
228     }
229     pDefMerge = nullptr;
230     pActDefault = maDefaultList.empty() ? nullptr : maDefaultList[0].get();
231     mnCurPos = 0;
232     OSL_ENSURE( pActDefault, "NewCellRow: pActDefault==0" );
233 }
234 
235 /*
236     SW:
237     ~~~
238     [\par]
239     \trowd \cellx \cellx ...
240     \intbl \cell \cell ...
241     \row
242     [\par]
243     [\trowd \cellx \cellx ...]
244     \intbl \cell \cell ...
245     \row
246     [\par]
247 
248     M$-Word:
249     ~~~~~~~~
250     [\par]
251     \trowd \cellx \cellx ...
252     \intbl \cell \cell ...
253     \intbl \row
254     [\par]
255     [\trowd \cellx \cellx ...]
256     \intbl \cell \cell ...
257     \intbl \row
258     [\par]
259 
260  */
261 
262 void ScRTFParser::ProcToken( RtfImportInfo* pInfo )
263 {
264     switch ( pInfo->nToken )
265     {
266         case RTF_TROWD:         // denotes table row default, before RTF_CELLX
267         {
268             if (!maDefaultList.empty())
269                 nLastWidth = maDefaultList.back()->nTwips;
270 
271             nColCnt = 0;
272             if (pActDefault != pInsDefault.get())
273                 pActDefault = nullptr;
274             maDefaultList.clear();
275             pDefMerge = nullptr;
276             nRtfLastToken = pInfo->nToken;
277             mnCurPos = 0;
278         }
279         break;
280         case RTF_CLMGF:         // The first cell of cells to be merged
281         {
282             pDefMerge = pInsDefault.get();
283             nRtfLastToken = pInfo->nToken;
284         }
285         break;
286         case RTF_CLMRG:         // A cell to be merged with the preceding cell
287         {
288             if (!pDefMerge && !maDefaultList.empty())
289             {
290                 pDefMerge = maDefaultList.back().get();
291                 mnCurPos = maDefaultList.size() - 1;
292             }
293             OSL_ENSURE( pDefMerge, "RTF_CLMRG: pDefMerge==0" );
294             if ( pDefMerge ) // Else broken RTF
295                 pDefMerge->nColOverlap++;   // multiple successive ones possible
296             pInsDefault->nColOverlap = 0;   // Flag: ignore these
297             nRtfLastToken = pInfo->nToken;
298         }
299         break;
300         case RTF_CELLX:         // closes cell default
301         {
302             bNewDef = true;
303             pInsDefault->nCol = nColCnt;
304             pInsDefault->nTwips = pInfo->nTokenValue; // Right cell border
305             maDefaultList.push_back( std::move(pInsDefault) );
306             // New free-flying pInsDefault
307             pInsDefault.reset( new ScRTFCellDefault( pPool.get() ) );
308             if ( ++nColCnt > nColMax )
309                 nColMax = nColCnt;
310             nRtfLastToken = pInfo->nToken;
311         }
312         break;
313         case RTF_INTBL:         // before the first RTF_CELL
314         {
315             // Once over NextToken and once over UnknownAttrToken
316             // or e.g. \intbl ... \cell \pard \intbl ... \cell
317             if ( nRtfLastToken != RTF_INTBL && nRtfLastToken != RTF_CELL && nRtfLastToken != RTF_PAR )
318             {
319                 NewCellRow();
320                 nRtfLastToken = pInfo->nToken;
321             }
322         }
323         break;
324         case RTF_CELL:          // denotes the end of a cell.
325         {
326             OSL_ENSURE( pActDefault, "RTF_CELL: pActDefault==0" );
327             if ( bNewDef || !pActDefault )
328                 NewCellRow();    // before was no \intbl, bad behavior
329             // Broken RTF? Let's save what we can
330             if ( !pActDefault )
331                 pActDefault = pInsDefault.get();
332             if ( pActDefault->nColOverlap > 0 )
333             {   // Not merged with preceding
334                 mxActEntry->nCol = pActDefault->nCol;
335                 mxActEntry->nColOverlap = pActDefault->nColOverlap;
336                 mxActEntry->nTwips = pActDefault->nTwips;
337                 mxActEntry->nRow = nRowCnt;
338                 mxActEntry->aItemSet.Set(pActDefault->aItemSet);
339                 EntryEnd(mxActEntry.get(), pInfo->aSelection);
340 
341                 if ( nStartAdjust == sal_uLong(~0) )
342                     nStartAdjust = maList.size();
343                 maList.push_back(mxActEntry);
344                 NewActEntry(mxActEntry.get()); // New free-flying mxActEntry
345             }
346             else
347             {   // Assign current Twips to MergeCell
348                 if ( !maList.empty() )
349                 {
350                     auto& pE = maList.back();
351                     pE->nTwips = pActDefault->nTwips;
352                 }
353                 // Adjust selection of free-flying mxActEntry
354                 // Paragraph -1 due to separated text in EditEngine during parsing
355                 mxActEntry->aSel.nStartPara = pInfo->aSelection.nEndPara - 1;
356             }
357 
358             pActDefault = nullptr;
359             if (!maDefaultList.empty() && (mnCurPos+1) < maDefaultList.size())
360                 pActDefault = maDefaultList[++mnCurPos].get();
361 
362             nRtfLastToken = pInfo->nToken;
363         }
364         break;
365         case RTF_ROW:           // denotes the end of a row
366         {
367             NextRow();
368             nRtfLastToken = pInfo->nToken;
369         }
370         break;
371         case RTF_PAR:           // Paragraph
372         {
373             if ( !pActDefault )
374             {   // text not in table
375                 ColAdjust();    // close the processing table
376                 mxActEntry->nCol = 0;
377                 mxActEntry->nRow = nRowCnt;
378                 EntryEnd(mxActEntry.get(), pInfo->aSelection);
379                 maList.push_back(mxActEntry);
380                 NewActEntry(mxActEntry.get());   // new mxActEntry
381                 NextRow();
382             }
383             nRtfLastToken = pInfo->nToken;
384         }
385         break;
386         default:
387         {   // do not set nRtfLastToken
388             switch ( pInfo->nToken & ~(0xff | RTF_TABLEDEF) )
389             {
390                 case RTF_SHADINGDEF:
391                     static_cast<SvxRTFParser*>(pInfo->pParser)->ReadBackgroundAttr(
392                         pInfo->nToken, pInsDefault->aItemSet, true );
393                 break;
394                 case RTF_BRDRDEF:
395                     static_cast<SvxRTFParser*>(pInfo->pParser)->ReadBorderAttr(
396                         pInfo->nToken, pInsDefault->aItemSet, true );
397                 break;
398             }
399         }
400     }
401 }
402 
403 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
404