Line data Source code
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 "richstring.hxx"
21 :
22 : #include <com/sun/star/text/XText.hpp>
23 : #include <rtl/ustrbuf.hxx>
24 : #include <editeng/editobj.hxx>
25 : #include "oox/helper/attributelist.hxx"
26 : #include "oox/helper/propertyset.hxx"
27 : #include "biffinputstream.hxx"
28 : #include "editutil.hxx"
29 :
30 : namespace oox {
31 : namespace xls {
32 :
33 : using namespace ::com::sun::star::text;
34 : using namespace ::com::sun::star::uno;
35 :
36 :
37 : namespace {
38 :
39 : const sal_uInt8 BIFF12_STRINGFLAG_FONTS = 0x01;
40 : const sal_uInt8 BIFF12_STRINGFLAG_PHONETICS = 0x02;
41 :
42 277 : inline bool lclNeedsRichTextFormat( const Font* pFont )
43 : {
44 277 : return pFont && pFont->needsRichTextFormat();
45 : }
46 :
47 : } // namespace
48 :
49 250 : RichStringPortion::RichStringPortion( const WorkbookHelper& rHelper ) :
50 : WorkbookHelper( rHelper ),
51 250 : mnFontId( -1 )
52 : {
53 250 : }
54 :
55 249 : void RichStringPortion::setText( const OUString& rText )
56 : {
57 249 : maText = rText;
58 249 : }
59 :
60 1 : FontRef RichStringPortion::createFont()
61 : {
62 1 : mxFont.reset( new Font( *this, false ) );
63 1 : return mxFont;
64 : }
65 :
66 0 : void RichStringPortion::setFontId( sal_Int32 nFontId )
67 : {
68 0 : mnFontId = nFontId;
69 0 : }
70 :
71 250 : void RichStringPortion::finalizeImport()
72 : {
73 250 : if( mxFont.get() )
74 1 : mxFont->finalizeImport();
75 249 : else if( mnFontId >= 0 )
76 0 : mxFont = getStyles().getFont( mnFontId );
77 250 : }
78 :
79 0 : void RichStringPortion::convert( const Reference< XText >& rxText, const Font* pFont, bool bReplace )
80 : {
81 0 : Reference< XTextRange > xRange;
82 0 : if( bReplace )
83 0 : xRange.set( rxText, UNO_QUERY );
84 : else
85 0 : xRange = rxText->getEnd();
86 : OSL_ENSURE( xRange.is(), "RichStringPortion::convert - cannot get text range interface" );
87 :
88 0 : if( xRange.is() )
89 : {
90 0 : xRange->setString( maText );
91 0 : if( mxFont.get() )
92 : {
93 0 : PropertySet aPropSet( xRange );
94 0 : mxFont->writeToPropertySet( aPropSet, FONT_PROPTYPE_TEXT );
95 : }
96 :
97 : /* Some font attributes cannot be set to cell formatting in Calc but
98 : require to use rich formatting, e.g. font escapement. But do not
99 : use the passed font if this portion has its own font. */
100 0 : else if( lclNeedsRichTextFormat( pFont ) )
101 : {
102 0 : PropertySet aPropSet( xRange );
103 0 : pFont->writeToPropertySet( aPropSet, FONT_PROPTYPE_TEXT );
104 : }
105 0 : }
106 0 : }
107 :
108 1 : void RichStringPortion::convert( ScEditEngineDefaulter& rEE, ESelection& rSelection, const Font* pFont )
109 : {
110 1 : rSelection.nStartPos = rSelection.nEndPos;
111 1 : rSelection.nStartPara = rSelection.nEndPara;
112 1 : SfxItemSet aItemSet( rEE.GetEmptyItemSet() );
113 :
114 1 : const Font* pFontToUse = mxFont.get() ? mxFont.get() : lclNeedsRichTextFormat( pFont ) ? pFont : NULL;
115 :
116 1 : if ( pFontToUse )
117 0 : pFontToUse->fillToItemSet( aItemSet, FONT_PROPTYPE_TEXT );
118 :
119 : // #TODO need to manually adjust nEndPos ( and nEndPara ) to cater for any paragraphs
120 1 : sal_Int32 nLastParaLoc = -1;
121 1 : sal_Int32 nSearchIndex = maText.indexOf( '\n' );
122 1 : sal_Int32 nParaOccurence = 0;
123 4 : while ( nSearchIndex != -1 )
124 : {
125 2 : nLastParaLoc = nSearchIndex;
126 2 : ++nParaOccurence;
127 2 : rSelection.nEndPos = 0;
128 2 : nSearchIndex = maText.indexOf( '\n', nSearchIndex + 1);
129 : }
130 :
131 1 : rSelection.nEndPara += nParaOccurence;
132 1 : if ( nLastParaLoc != -1 )
133 : {
134 1 : rSelection.nEndPos = maText.getLength() - 1 - nLastParaLoc;
135 : }
136 : else
137 : {
138 0 : rSelection.nEndPos = rSelection.nStartPos + maText.getLength();
139 : }
140 1 : rEE.QuickSetAttribs( aItemSet, rSelection );
141 1 : }
142 :
143 1 : void RichStringPortion::writeFontProperties( const Reference<XText>& rxText, const Font* pFont ) const
144 : {
145 1 : PropertySet aPropSet(rxText);
146 :
147 1 : if (mxFont.get())
148 1 : mxFont->writeToPropertySet(aPropSet, FONT_PROPTYPE_TEXT);
149 :
150 1 : if (lclNeedsRichTextFormat(pFont))
151 0 : pFont->writeToPropertySet(aPropSet, FONT_PROPTYPE_TEXT);
152 1 : }
153 :
154 0 : void FontPortionModel::read( SequenceInputStream& rStrm )
155 : {
156 0 : mnPos = rStrm.readuInt16();
157 0 : mnFontId = rStrm.readuInt16();
158 0 : }
159 :
160 0 : void FontPortionModelList::appendPortion( const FontPortionModel& rPortion )
161 : {
162 : // #i33341# real life -- same character index may occur several times
163 : OSL_ENSURE( empty() || (back().mnPos <= rPortion.mnPos), "FontPortionModelList::appendPortion - wrong char order" );
164 0 : if( empty() || (back().mnPos < rPortion.mnPos) )
165 0 : push_back( rPortion );
166 : else
167 0 : back().mnFontId = rPortion.mnFontId;
168 0 : }
169 :
170 0 : void FontPortionModelList::importPortions( SequenceInputStream& rStrm )
171 : {
172 0 : sal_Int32 nCount = rStrm.readInt32();
173 0 : clear();
174 0 : if( nCount > 0 )
175 : {
176 0 : reserve( getLimitedValue< size_t, sal_Int64 >( nCount, 0, rStrm.getRemaining() / 4 ) );
177 : /* #i33341# real life -- same character index may occur several times
178 : -> use appendPortion() to validate string position. */
179 0 : FontPortionModel aPortion;
180 0 : for( sal_Int32 nIndex = 0; !rStrm.isEof() && (nIndex < nCount); ++nIndex )
181 : {
182 0 : aPortion.read( rStrm );
183 0 : appendPortion( aPortion );
184 : }
185 : }
186 0 : }
187 :
188 335 : PhoneticDataModel::PhoneticDataModel() :
189 : mnFontId( -1 ),
190 : mnType( XML_fullwidthKatakana ),
191 335 : mnAlignment( XML_left )
192 : {
193 335 : }
194 :
195 0 : void PhoneticDataModel::setBiffData( sal_Int32 nType, sal_Int32 nAlignment )
196 : {
197 : static const sal_Int32 spnTypeIds[] = { XML_halfwidthKatakana, XML_fullwidthKatakana, XML_hiragana, XML_noConversion };
198 0 : mnType = STATIC_ARRAY_SELECT( spnTypeIds, nType, XML_fullwidthKatakana );
199 :
200 : static const sal_Int32 spnAlignments[] = { XML_noControl, XML_left, XML_center, XML_distributed };
201 0 : mnAlignment = STATIC_ARRAY_SELECT( spnAlignments, nAlignment, XML_left );
202 0 : }
203 :
204 335 : PhoneticSettings::PhoneticSettings( const WorkbookHelper& rHelper ) :
205 335 : WorkbookHelper( rHelper )
206 : {
207 335 : }
208 :
209 6 : void PhoneticSettings::importPhoneticPr( const AttributeList& rAttribs )
210 : {
211 6 : maModel.mnFontId = rAttribs.getInteger( XML_fontId, -1 );
212 6 : maModel.mnType = rAttribs.getToken( XML_type, XML_fullwidthKatakana );
213 6 : maModel.mnAlignment = rAttribs.getToken( XML_alignment, XML_left );
214 6 : }
215 :
216 0 : void PhoneticSettings::importPhoneticPr( SequenceInputStream& rStrm )
217 : {
218 : sal_uInt16 nFontId;
219 : sal_Int32 nType, nAlignment;
220 0 : rStrm >> nFontId >> nType >> nAlignment;
221 0 : maModel.mnFontId = nFontId;
222 0 : maModel.setBiffData( nType, nAlignment );
223 0 : }
224 :
225 0 : void PhoneticSettings::importStringData( SequenceInputStream& rStrm )
226 : {
227 : sal_uInt16 nFontId, nFlags;
228 0 : rStrm >> nFontId >> nFlags;
229 0 : maModel.mnFontId = nFontId;
230 0 : maModel.setBiffData( extractValue< sal_Int32 >( nFlags, 0, 2 ), extractValue< sal_Int32 >( nFlags, 2, 2 ) );
231 0 : }
232 :
233 0 : RichStringPhonetic::RichStringPhonetic( const WorkbookHelper& rHelper ) :
234 : WorkbookHelper( rHelper ),
235 : mnBasePos( -1 ),
236 0 : mnBaseEnd( -1 )
237 : {
238 0 : }
239 :
240 0 : void RichStringPhonetic::setText( const OUString& rText )
241 : {
242 0 : maText = rText;
243 0 : }
244 :
245 0 : void RichStringPhonetic::importPhoneticRun( const AttributeList& rAttribs )
246 : {
247 0 : mnBasePos = rAttribs.getInteger( XML_sb, -1 );
248 0 : mnBaseEnd = rAttribs.getInteger( XML_eb, -1 );
249 0 : }
250 :
251 0 : void RichStringPhonetic::setBaseRange( sal_Int32 nBasePos, sal_Int32 nBaseEnd )
252 : {
253 0 : mnBasePos = nBasePos;
254 0 : mnBaseEnd = nBaseEnd;
255 0 : }
256 :
257 0 : void PhoneticPortionModel::read( SequenceInputStream& rStrm )
258 : {
259 0 : mnPos = rStrm.readuInt16();
260 0 : mnBasePos = rStrm.readuInt16();
261 0 : mnBaseLen = rStrm.readuInt16();
262 0 : }
263 :
264 0 : void PhoneticPortionModelList::appendPortion( const PhoneticPortionModel& rPortion )
265 : {
266 : // same character index may occur several times
267 : OSL_ENSURE( empty() || ((back().mnPos <= rPortion.mnPos) &&
268 : (back().mnBasePos + back().mnBaseLen <= rPortion.mnBasePos)),
269 : "PhoneticPortionModelList::appendPortion - wrong char order" );
270 0 : if( empty() || (back().mnPos < rPortion.mnPos) )
271 : {
272 0 : push_back( rPortion );
273 : }
274 0 : else if( back().mnPos == rPortion.mnPos )
275 : {
276 0 : back().mnBasePos = rPortion.mnBasePos;
277 0 : back().mnBaseLen = rPortion.mnBaseLen;
278 : }
279 0 : }
280 :
281 0 : void PhoneticPortionModelList::importPortions( SequenceInputStream& rStrm )
282 : {
283 0 : sal_Int32 nCount = rStrm.readInt32();
284 0 : clear();
285 0 : if( nCount > 0 )
286 : {
287 0 : reserve( getLimitedValue< size_t, sal_Int64 >( nCount, 0, rStrm.getRemaining() / 6 ) );
288 0 : PhoneticPortionModel aPortion;
289 0 : for( sal_Int32 nIndex = 0; !rStrm.isEof() && (nIndex < nCount); ++nIndex )
290 : {
291 0 : aPortion.read( rStrm );
292 0 : appendPortion( aPortion );
293 : }
294 : }
295 0 : }
296 :
297 250 : RichString::RichString( const WorkbookHelper& rHelper ) :
298 : WorkbookHelper( rHelper ),
299 250 : maPhonSettings( rHelper )
300 : {
301 250 : }
302 :
303 249 : RichStringPortionRef RichString::importText( const AttributeList& )
304 : {
305 249 : return createPortion();
306 : }
307 :
308 1 : RichStringPortionRef RichString::importRun( const AttributeList& )
309 : {
310 1 : return createPortion();
311 : }
312 :
313 0 : RichStringPhoneticRef RichString::importPhoneticRun( const AttributeList& rAttribs )
314 : {
315 0 : RichStringPhoneticRef xPhonetic = createPhonetic();
316 0 : xPhonetic->importPhoneticRun( rAttribs );
317 0 : return xPhonetic;
318 : }
319 :
320 0 : void RichString::importPhoneticPr( const AttributeList& rAttribs )
321 : {
322 0 : maPhonSettings.importPhoneticPr( rAttribs );
323 0 : }
324 :
325 0 : void RichString::importString( SequenceInputStream& rStrm, bool bRich )
326 : {
327 0 : sal_uInt8 nFlags = bRich ? rStrm.readuInt8() : 0;
328 0 : OUString aBaseText = BiffHelper::readString( rStrm );
329 :
330 0 : if( !rStrm.isEof() && getFlag( nFlags, BIFF12_STRINGFLAG_FONTS ) )
331 : {
332 0 : FontPortionModelList aPortions;
333 0 : aPortions.importPortions( rStrm );
334 0 : createTextPortions( aBaseText, aPortions );
335 : }
336 : else
337 : {
338 0 : createPortion()->setText( aBaseText );
339 : }
340 :
341 0 : if( !rStrm.isEof() && getFlag( nFlags, BIFF12_STRINGFLAG_PHONETICS ) )
342 : {
343 0 : OUString aPhoneticText = BiffHelper::readString( rStrm );
344 0 : PhoneticPortionModelList aPortions;
345 0 : aPortions.importPortions( rStrm );
346 0 : maPhonSettings.importStringData( rStrm );
347 0 : createPhoneticPortions( aPhoneticText, aPortions, aBaseText.getLength() );
348 0 : }
349 0 : }
350 :
351 250 : void RichString::finalizeImport()
352 : {
353 250 : maTextPortions.forEachMem( &RichStringPortion::finalizeImport );
354 250 : }
355 :
356 275 : bool RichString::extractPlainString( OUString& orString, const Font* pFirstPortionFont ) const
357 : {
358 275 : if( !maPhonPortions.empty() )
359 0 : return false;
360 275 : if( maTextPortions.empty() )
361 : {
362 0 : orString = OUString();
363 0 : return true;
364 : }
365 275 : if( (maTextPortions.size() == 1) && !maTextPortions.front()->hasFont() && !lclNeedsRichTextFormat( pFirstPortionFont ) )
366 : {
367 275 : orString = maTextPortions.front()->getText();
368 275 : return orString.indexOf( '\x0A' ) < 0;
369 : }
370 0 : return false;
371 : }
372 :
373 1 : void RichString::convert( const Reference< XText >& rxText, bool bReplaceOld, const Font* pFirstPortionFont ) const
374 : {
375 1 : if (maTextPortions.size() == 1)
376 : {
377 : // Set text directly to the cell when the string has only one portion.
378 : // It's much faster this way.
379 1 : RichStringPortion& rPtn = *maTextPortions.front();
380 1 : rxText->setString(rPtn.getText());
381 1 : rPtn.writeFontProperties(rxText, pFirstPortionFont);
382 2 : return;
383 : }
384 :
385 0 : for( PortionVector::const_iterator aIt = maTextPortions.begin(), aEnd = maTextPortions.end(); aIt != aEnd; ++aIt )
386 : {
387 0 : (*aIt)->convert( rxText, pFirstPortionFont, bReplaceOld );
388 0 : pFirstPortionFont = 0; // use passed font for first portion only
389 0 : bReplaceOld = false; // do not replace first portion text with following portions
390 : }
391 : }
392 :
393 1 : ::EditTextObject* RichString::convert( ScEditEngineDefaulter& rEE, const Font* pFirstPortionFont ) const
394 : {
395 1 : ESelection aSelection;
396 :
397 1 : OUString sString;
398 2 : for( PortionVector::const_iterator aIt = maTextPortions.begin(), aEnd = maTextPortions.end(); aIt != aEnd; ++aIt )
399 1 : sString += (*aIt)->getText();
400 :
401 1 : rEE.SetText( sString );
402 :
403 2 : for( PortionVector::const_iterator aIt = maTextPortions.begin(), aEnd = maTextPortions.end(); aIt != aEnd; ++aIt )
404 : {
405 1 : (*aIt)->convert( rEE, aSelection, pFirstPortionFont );
406 1 : pFirstPortionFont = 0;
407 : }
408 :
409 1 : return rEE.CreateTextObject();
410 : }
411 :
412 : // private --------------------------------------------------------------------
413 :
414 250 : RichStringPortionRef RichString::createPortion()
415 : {
416 250 : RichStringPortionRef xPortion( new RichStringPortion( *this ) );
417 250 : maTextPortions.push_back( xPortion );
418 250 : return xPortion;
419 : }
420 :
421 0 : RichStringPhoneticRef RichString::createPhonetic()
422 : {
423 0 : RichStringPhoneticRef xPhonetic( new RichStringPhonetic( *this ) );
424 0 : maPhonPortions.push_back( xPhonetic );
425 0 : return xPhonetic;
426 : }
427 :
428 0 : void RichString::createTextPortions( const OUString& rText, FontPortionModelList& rPortions )
429 : {
430 0 : maTextPortions.clear();
431 0 : if( !rText.isEmpty() )
432 : {
433 0 : sal_Int32 nStrLen = rText.getLength();
434 : // add leading and trailing string position to ease the following loop
435 0 : if( rPortions.empty() || (rPortions.front().mnPos > 0) )
436 0 : rPortions.insert( rPortions.begin(), FontPortionModel( 0, -1 ) );
437 0 : if( rPortions.back().mnPos < nStrLen )
438 0 : rPortions.push_back( FontPortionModel( nStrLen, -1 ) );
439 :
440 : // create all string portions according to the font id vector
441 0 : for( FontPortionModelList::const_iterator aIt = rPortions.begin(); aIt->mnPos < nStrLen; ++aIt )
442 : {
443 0 : sal_Int32 nPortionLen = (aIt + 1)->mnPos - aIt->mnPos;
444 0 : if( (0 < nPortionLen) && (aIt->mnPos + nPortionLen <= nStrLen) )
445 : {
446 0 : RichStringPortionRef xPortion = createPortion();
447 0 : xPortion->setText( rText.copy( aIt->mnPos, nPortionLen ) );
448 0 : xPortion->setFontId( aIt->mnFontId );
449 : }
450 : }
451 : }
452 0 : }
453 :
454 0 : void RichString::createPhoneticPortions( const OUString& rText, PhoneticPortionModelList& rPortions, sal_Int32 nBaseLen )
455 : {
456 0 : maPhonPortions.clear();
457 0 : if( !rText.isEmpty())
458 : {
459 0 : sal_Int32 nStrLen = rText.getLength();
460 : // no portions - assign phonetic text to entire base text
461 0 : if( rPortions.empty() )
462 0 : rPortions.push_back( PhoneticPortionModel( 0, 0, nBaseLen ) );
463 : // add trailing string position to ease the following loop
464 0 : if( rPortions.back().mnPos < nStrLen )
465 0 : rPortions.push_back( PhoneticPortionModel( nStrLen, nBaseLen, 0 ) );
466 :
467 : // create all phonetic portions according to the portions vector
468 0 : for( PhoneticPortionModelList::const_iterator aIt = rPortions.begin(); aIt->mnPos < nStrLen; ++aIt )
469 : {
470 0 : sal_Int32 nPortionLen = (aIt + 1)->mnPos - aIt->mnPos;
471 0 : if( (0 < nPortionLen) && (aIt->mnPos + nPortionLen <= nStrLen) )
472 : {
473 0 : RichStringPhoneticRef xPhonetic = createPhonetic();
474 0 : xPhonetic->setText( rText.copy( aIt->mnPos, nPortionLen ) );
475 0 : xPhonetic->setBaseRange( aIt->mnBasePos, aIt->mnBasePos + aIt->mnBaseLen );
476 : }
477 : }
478 : }
479 0 : }
480 :
481 : } // namespace xls
482 18 : } // namespace oox
483 :
484 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|