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/.
9 * This file incorporates work covered by the following license notice:
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 .
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>
31 #include <rtfparse.hxx>
33 #define SC_RTFTWIPTOL 10 // 10 Twips tolerance when determining columns
35 ScRTFParser::ScRTFParser( EditEngine
* pEditP
) :
38 pActDefault( nullptr ),
40 nStartAdjust( sal_uLong(~0) ),
44 // RTF default FontSize 12Pt
45 tools::Long nMM
= o3tl::convert(12, o3tl::Length::pt
, o3tl::Length::mm100
);
47 pPool
->SetUserDefaultItem( SvxFontHeightItem( nMM
, 100, EE_CHAR_FONTHEIGHT
) );
48 // Free-flying pInsDefault
49 pInsDefault
.reset( new ScRTFCellDefault( pPool
.get() ) );
52 ScRTFParser::~ScRTFParser()
55 maDefaultList
.clear();
58 ErrCode
ScRTFParser::Read( SvStream
& rStream
, const OUString
& rBaseURL
)
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
)
65 if ( !maList
.empty() )
67 auto& pE
= maList
.back();
68 if ( // Completely empty
69 ( !pE
->aSel
.HasRange() )
71 ( pE
->aSel
.start
.nPara
+ 1 == pE
->aSel
.end
.nPara
72 && pE
->aSel
.start
.nIndex
== pEdit
->GetTextLen( pE
->aSel
.start
.nPara
)
73 && pE
->aSel
.end
.nIndex
== 0
76 { // Don't take over the last paragraph
82 pEdit
->SetRtfImportHdl( aOldLink
);
86 void ScRTFParser::EntryEnd( ScEEParseEntry
* pE
, const ESelection
& aSel
)
88 // Paragraph -2 strips the attached empty paragraph
89 pE
->aSel
.end
.nPara
= aSel
.end
.nPara
- 2;
90 // Although it's called nEndPos, the last one is position + 1
91 pE
->aSel
.end
.nIndex
= pEdit
->GetTextLen(aSel
.end
.nPara
- 1);
94 inline void ScRTFParser::NextRow()
96 if ( nRowMax
< ++nRowCnt
)
100 bool ScRTFParser::SeekTwips( sal_uInt16 nTwips
, SCCOL
* pCol
)
102 ScRTFColTwips::const_iterator it
= aColTwips
.lower_bound( nTwips
);
103 bool bFound
= it
!= aColTwips
.end() && *it
== nTwips
;
104 sal_uInt16 nPos
= it
- aColTwips
.begin();
105 *pCol
= static_cast<SCCOL
>(nPos
);
108 sal_uInt16 nCount
= aColTwips
.size();
112 // nCol is insertion position; the next one higher up is there (or not)
113 if ( nCol
< static_cast<SCCOL
>(nCount
) && ((aColTwips
[nCol
] - SC_RTFTWIPTOL
) <= nTwips
) )
115 // Not smaller than everything else? Then compare with the next lower one
116 else if ( nCol
!= 0 && ((aColTwips
[nCol
-1] + SC_RTFTWIPTOL
) >= nTwips
) )
124 void ScRTFParser::ColAdjust()
126 if ( nStartAdjust
== sal_uLong(~0) )
130 for (size_t i
= nStartAdjust
, nListSize
= maList
.size(); i
< nListSize
; ++i
)
132 auto& pE
= maList
[i
];
136 if ( pE
->nColOverlap
> 1 )
137 nCol
= nCol
+ pE
->nColOverlap
; // Merged cells with \clmrg
140 SeekTwips( pE
->nTwips
, &nCol
);
141 if ( ++nCol
<= pE
->nCol
)
142 nCol
= pE
->nCol
+ 1; // Moved cell X
143 pE
->nColOverlap
= nCol
- pE
->nCol
; // Merged cells without \clmrg
145 if ( nCol
> nColMax
)
148 nStartAdjust
= sal_uLong(~0);
152 IMPL_LINK( ScRTFParser
, RTFImportHdl
, RtfImportInfo
&, rInfo
, void )
154 switch ( rInfo
.eState
)
156 case RtfImportState::NextToken
:
159 case RtfImportState::UnknownAttr
:
162 case RtfImportState::Start
:
164 SvxRTFParser
* pParser
= static_cast<SvxRTFParser
*>(rInfo
.pParser
);
165 pParser
->SetAttrPool( pPool
.get() );
166 pParser
->SetPardMap(SID_ATTR_BRUSH
, ATTR_BACKGROUND
);
167 pParser
->SetPardMap(SID_ATTR_BORDER_OUTER
, ATTR_BORDER
);
168 pParser
->SetPardMap(SID_ATTR_BORDER_SHADOW
, ATTR_SHADOW
);
171 case RtfImportState::End
:
172 if ( rInfo
.aSelection
.end
.nIndex
)
173 { // If still text: create last paragraph
174 pActDefault
= nullptr;
175 rInfo
.nToken
= RTF_PAR
;
176 // EditEngine did not attach an empty paragraph anymore
177 // which EntryEnd could strip
178 rInfo
.aSelection
.end
.nPara
++;
182 case RtfImportState::SetAttr
:
184 case RtfImportState::InsertText
:
186 case RtfImportState::InsertPara
:
189 OSL_FAIL("unknown ImportInfo.eState");
194 // For RTF_INTBL or respectively at the start of the first RTF_CELL
195 // after RTF_CELLX if there was no RTF_INTBL
196 void ScRTFParser::NewCellRow()
201 // Not flush on the right? => new table
202 if ( nLastWidth
&& !maDefaultList
.empty() )
204 const ScRTFCellDefault
& rD
= *maDefaultList
.back();
205 if (rD
.nTwips
!= nLastWidth
)
208 if ( !( SeekTwips( nLastWidth
, &n1
)
209 && SeekTwips( rD
.nTwips
, &n2
)
218 // Build up TwipCols only after nLastWidth comparison!
219 for (const std::unique_ptr
<ScRTFCellDefault
> & pCellDefault
: maDefaultList
)
221 const ScRTFCellDefault
& rD
= *pCellDefault
;
223 if ( !SeekTwips(rD
.nTwips
, &nCol
) )
224 aColTwips
.insert( rD
.nTwips
);
228 pActDefault
= maDefaultList
.empty() ? nullptr : maDefaultList
[0].get();
230 OSL_ENSURE( pActDefault
, "NewCellRow: pActDefault==0" );
237 \trowd \cellx \cellx ...
238 \intbl \cell \cell ...
241 [\trowd \cellx \cellx ...]
242 \intbl \cell \cell ...
249 \trowd \cellx \cellx ...
250 \intbl \cell \cell ...
253 [\trowd \cellx \cellx ...]
254 \intbl \cell \cell ...
260 void ScRTFParser::ProcToken( RtfImportInfo
* pInfo
)
262 switch ( pInfo
->nToken
)
264 case RTF_TROWD
: // denotes table row default, before RTF_CELLX
266 if (!maDefaultList
.empty())
267 nLastWidth
= maDefaultList
.back()->nTwips
;
270 if (pActDefault
!= pInsDefault
.get())
271 pActDefault
= nullptr;
272 maDefaultList
.clear();
274 nRtfLastToken
= pInfo
->nToken
;
278 case RTF_CLMGF
: // The first cell of cells to be merged
280 pDefMerge
= pInsDefault
.get();
281 nRtfLastToken
= pInfo
->nToken
;
284 case RTF_CLMRG
: // A cell to be merged with the preceding cell
286 if (!pDefMerge
&& !maDefaultList
.empty())
288 pDefMerge
= maDefaultList
.back().get();
289 mnCurPos
= maDefaultList
.size() - 1;
291 OSL_ENSURE( pDefMerge
, "RTF_CLMRG: pDefMerge==0" );
292 if ( pDefMerge
) // Else broken RTF
293 pDefMerge
->nColOverlap
++; // multiple successive ones possible
294 pInsDefault
->nColOverlap
= 0; // Flag: ignore these
295 nRtfLastToken
= pInfo
->nToken
;
298 case RTF_CELLX
: // closes cell default
301 pInsDefault
->nCol
= nColCnt
;
302 pInsDefault
->nTwips
= pInfo
->nTokenValue
; // Right cell border
303 maDefaultList
.push_back( std::move(pInsDefault
) );
304 // New free-flying pInsDefault
305 pInsDefault
.reset( new ScRTFCellDefault( pPool
.get() ) );
306 if ( ++nColCnt
> nColMax
)
308 nRtfLastToken
= pInfo
->nToken
;
311 case RTF_INTBL
: // before the first RTF_CELL
313 // Once over NextToken and once over UnknownAttrToken
314 // or e.g. \intbl ... \cell \pard \intbl ... \cell
315 if ( nRtfLastToken
!= RTF_INTBL
&& nRtfLastToken
!= RTF_CELL
&& nRtfLastToken
!= RTF_PAR
)
318 nRtfLastToken
= pInfo
->nToken
;
322 case RTF_CELL
: // denotes the end of a cell.
324 OSL_ENSURE( pActDefault
, "RTF_CELL: pActDefault==0" );
325 if ( bNewDef
|| !pActDefault
)
326 NewCellRow(); // before was no \intbl, bad behavior
327 // Broken RTF? Let's save what we can
329 pActDefault
= pInsDefault
.get();
330 if ( pActDefault
->nColOverlap
> 0 )
331 { // Not merged with preceding
332 mxActEntry
->nCol
= pActDefault
->nCol
;
333 mxActEntry
->nColOverlap
= pActDefault
->nColOverlap
;
334 mxActEntry
->nTwips
= pActDefault
->nTwips
;
335 mxActEntry
->nRow
= nRowCnt
;
336 mxActEntry
->aItemSet
.Set(pActDefault
->aItemSet
);
337 EntryEnd(mxActEntry
.get(), pInfo
->aSelection
);
339 if ( nStartAdjust
== sal_uLong(~0) )
340 nStartAdjust
= maList
.size();
341 maList
.push_back(mxActEntry
);
342 NewActEntry(mxActEntry
.get()); // New free-flying mxActEntry
345 { // Assign current Twips to MergeCell
346 if ( !maList
.empty() )
348 auto& pE
= maList
.back();
349 pE
->nTwips
= pActDefault
->nTwips
;
351 // Adjust selection of free-flying mxActEntry
352 // Paragraph -1 due to separated text in EditEngine during parsing
353 mxActEntry
->aSel
.start
.nPara
= pInfo
->aSelection
.end
.nPara
- 1;
356 pActDefault
= nullptr;
357 if (!maDefaultList
.empty() && (mnCurPos
+1) < maDefaultList
.size())
358 pActDefault
= maDefaultList
[++mnCurPos
].get();
360 nRtfLastToken
= pInfo
->nToken
;
363 case RTF_ROW
: // denotes the end of a row
366 nRtfLastToken
= pInfo
->nToken
;
369 case RTF_PAR
: // Paragraph
372 { // text not in table
373 ColAdjust(); // close the processing table
374 mxActEntry
->nCol
= 0;
375 mxActEntry
->nRow
= nRowCnt
;
376 EntryEnd(mxActEntry
.get(), pInfo
->aSelection
);
377 maList
.push_back(mxActEntry
);
378 NewActEntry(mxActEntry
.get()); // new mxActEntry
381 nRtfLastToken
= pInfo
->nToken
;
385 { // do not set nRtfLastToken
386 switch ( pInfo
->nToken
& ~(0xff | RTF_TABLEDEF
) )
389 static_cast<SvxRTFParser
*>(pInfo
->pParser
)->ReadBackgroundAttr(
390 pInfo
->nToken
, pInsDefault
->aItemSet
, true );
393 static_cast<SvxRTFParser
*>(pInfo
->pParser
)->ReadBorderAttr(
394 pInfo
->nToken
, pInsDefault
->aItemSet
, true );
401 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */