blob: 8c77746477bf76a8e22b763f3efee29fce25df5c [file] [log] [blame]
/**************************************************************
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*************************************************************/
#include "oox/xls/richstring.hxx"
#include <com/sun/star/text/XText.hpp>
#include <rtl/ustrbuf.hxx>
#include "oox/helper/attributelist.hxx"
#include "oox/helper/propertyset.hxx"
#include "oox/xls/biffinputstream.hxx"
namespace oox {
namespace xls {
// ============================================================================
using namespace ::com::sun::star::text;
using namespace ::com::sun::star::uno;
using ::rtl::OString;
using ::rtl::OUString;
using ::rtl::OUStringBuffer;
// ============================================================================
namespace {
const sal_uInt8 BIFF12_STRINGFLAG_FONTS = 0x01;
const sal_uInt8 BIFF12_STRINGFLAG_PHONETICS = 0x02;
inline bool lclNeedsRichTextFormat( const Font* pFont )
{
return pFont && pFont->needsRichTextFormat();
}
} // namespace
// ============================================================================
RichStringPortion::RichStringPortion( const WorkbookHelper& rHelper ) :
WorkbookHelper( rHelper ),
mnFontId( -1 )
{
}
void RichStringPortion::setText( const OUString& rText )
{
maText = rText;
}
FontRef RichStringPortion::createFont()
{
mxFont.reset( new Font( *this, false ) );
return mxFont;
}
void RichStringPortion::setFontId( sal_Int32 nFontId )
{
mnFontId = nFontId;
}
void RichStringPortion::finalizeImport()
{
if( mxFont.get() )
mxFont->finalizeImport();
else if( mnFontId >= 0 )
mxFont = getStyles().getFont( mnFontId );
}
void RichStringPortion::convert( const Reference< XText >& rxText, const Font* pFont, bool bReplace )
{
Reference< XTextRange > xRange;
if( bReplace )
xRange.set( rxText, UNO_QUERY );
else
xRange = rxText->getEnd();
OSL_ENSURE( xRange.is(), "RichStringPortion::convert - cannot get text range interface" );
if( xRange.is() )
{
xRange->setString( maText );
if( mxFont.get() )
{
PropertySet aPropSet( xRange );
mxFont->writeToPropertySet( aPropSet, FONT_PROPTYPE_TEXT );
}
/* Some font attributes cannot be set to cell formatting in Calc but
require to use rich formatting, e.g. font escapement. But do not
use the passed font if this portion has its own font. */
else if( lclNeedsRichTextFormat( pFont ) )
{
PropertySet aPropSet( xRange );
pFont->writeToPropertySet( aPropSet, FONT_PROPTYPE_TEXT );
}
}
}
// ----------------------------------------------------------------------------
void FontPortionModel::read( SequenceInputStream& rStrm )
{
mnPos = rStrm.readuInt16();
mnFontId = rStrm.readuInt16();
}
void FontPortionModel::read( BiffInputStream& rStrm, BiffFontPortionMode eMode )
{
switch( eMode )
{
case BIFF_FONTPORTION_8BIT:
mnPos = rStrm.readuInt8();
mnFontId = rStrm.readuInt8();
break;
case BIFF_FONTPORTION_16BIT:
mnPos = rStrm.readuInt16();
mnFontId = rStrm.readuInt16();
break;
case BIFF_FONTPORTION_OBJ:
mnPos = rStrm.readuInt16();
mnFontId = rStrm.readuInt16();
rStrm.skip( 4 );
break;
}
}
// ----------------------------------------------------------------------------
void FontPortionModelList::appendPortion( const FontPortionModel& rPortion )
{
// #i33341# real life -- same character index may occur several times
OSL_ENSURE( empty() || (back().mnPos <= rPortion.mnPos), "FontPortionModelList::appendPortion - wrong char order" );
if( empty() || (back().mnPos < rPortion.mnPos) )
push_back( rPortion );
else
back().mnFontId = rPortion.mnFontId;
}
void FontPortionModelList::importPortions( SequenceInputStream& rStrm )
{
sal_Int32 nCount = rStrm.readInt32();
clear();
if( nCount > 0 )
{
reserve( getLimitedValue< size_t, sal_Int64 >( nCount, 0, rStrm.getRemaining() / 4 ) );
/* #i33341# real life -- same character index may occur several times
-> use appendPortion() to validate string position. */
FontPortionModel aPortion;
for( sal_Int32 nIndex = 0; !rStrm.isEof() && (nIndex < nCount); ++nIndex )
{
aPortion.read( rStrm );
appendPortion( aPortion );
}
}
}
void FontPortionModelList::importPortions( BiffInputStream& rStrm, sal_uInt16 nCount, BiffFontPortionMode eMode )
{
clear();
reserve( nCount );
/* #i33341# real life -- same character index may occur several times
-> use appendPortion() to validate string position. */
FontPortionModel aPortion;
for( sal_uInt16 nIndex = 0; !rStrm.isEof() && (nIndex < nCount); ++nIndex )
{
aPortion.read( rStrm, eMode );
appendPortion( aPortion );
}
}
void FontPortionModelList::importPortions( BiffInputStream& rStrm, bool b16Bit )
{
sal_uInt16 nCount = b16Bit ? rStrm.readuInt16() : rStrm.readuInt8();
importPortions( rStrm, nCount, b16Bit ? BIFF_FONTPORTION_16BIT : BIFF_FONTPORTION_8BIT );
}
// ============================================================================
PhoneticDataModel::PhoneticDataModel() :
mnFontId( -1 ),
mnType( XML_fullwidthKatakana ),
mnAlignment( XML_left )
{
}
void PhoneticDataModel::setBiffData( sal_Int32 nType, sal_Int32 nAlignment )
{
static const sal_Int32 spnTypeIds[] = { XML_halfwidthKatakana, XML_fullwidthKatakana, XML_hiragana, XML_noConversion };
mnType = STATIC_ARRAY_SELECT( spnTypeIds, nType, XML_fullwidthKatakana );
static const sal_Int32 spnAlignments[] = { XML_noControl, XML_left, XML_center, XML_distributed };
mnAlignment = STATIC_ARRAY_SELECT( spnAlignments, nAlignment, XML_left );
}
// ----------------------------------------------------------------------------
PhoneticSettings::PhoneticSettings( const WorkbookHelper& rHelper ) :
WorkbookHelper( rHelper )
{
}
void PhoneticSettings::importPhoneticPr( const AttributeList& rAttribs )
{
maModel.mnFontId = rAttribs.getInteger( XML_fontId, -1 );
maModel.mnType = rAttribs.getToken( XML_type, XML_fullwidthKatakana );
maModel.mnAlignment = rAttribs.getToken( XML_alignment, XML_left );
}
void PhoneticSettings::importPhoneticPr( SequenceInputStream& rStrm )
{
sal_uInt16 nFontId;
sal_Int32 nType, nAlignment;
rStrm >> nFontId >> nType >> nAlignment;
maModel.mnFontId = nFontId;
maModel.setBiffData( nType, nAlignment );
}
void PhoneticSettings::importPhoneticPr( BiffInputStream& rStrm )
{
sal_uInt16 nFontId, nFlags;
rStrm >> nFontId >> nFlags;
maModel.mnFontId = nFontId;
maModel.setBiffData( extractValue< sal_Int32 >( nFlags, 0, 2 ), extractValue< sal_Int32 >( nFlags, 2, 2 ) );
// following: range list with cells showing phonetic text
}
void PhoneticSettings::importStringData( SequenceInputStream& rStrm )
{
sal_uInt16 nFontId, nFlags;
rStrm >> nFontId >> nFlags;
maModel.mnFontId = nFontId;
maModel.setBiffData( extractValue< sal_Int32 >( nFlags, 0, 2 ), extractValue< sal_Int32 >( nFlags, 2, 2 ) );
}
void PhoneticSettings::importStringData( BiffInputStream& rStrm )
{
sal_uInt16 nFontId, nFlags;
rStrm >> nFontId >> nFlags;
maModel.mnFontId = nFontId;
maModel.setBiffData( extractValue< sal_Int32 >( nFlags, 0, 2 ), extractValue< sal_Int32 >( nFlags, 2, 2 ) );
}
// ============================================================================
RichStringPhonetic::RichStringPhonetic( const WorkbookHelper& rHelper ) :
WorkbookHelper( rHelper ),
mnBasePos( -1 ),
mnBaseEnd( -1 )
{
}
void RichStringPhonetic::setText( const OUString& rText )
{
maText = rText;
}
void RichStringPhonetic::importPhoneticRun( const AttributeList& rAttribs )
{
mnBasePos = rAttribs.getInteger( XML_sb, -1 );
mnBaseEnd = rAttribs.getInteger( XML_eb, -1 );
}
void RichStringPhonetic::setBaseRange( sal_Int32 nBasePos, sal_Int32 nBaseEnd )
{
mnBasePos = nBasePos;
mnBaseEnd = nBaseEnd;
}
// ----------------------------------------------------------------------------
void PhoneticPortionModel::read( SequenceInputStream& rStrm )
{
mnPos = rStrm.readuInt16();
mnBasePos = rStrm.readuInt16();
mnBaseLen = rStrm.readuInt16();
}
void PhoneticPortionModel::read( BiffInputStream& rStrm )
{
mnPos = rStrm.readuInt16();
mnBasePos = rStrm.readuInt16();
mnBaseLen = rStrm.readuInt16();
}
// ----------------------------------------------------------------------------
void PhoneticPortionModelList::appendPortion( const PhoneticPortionModel& rPortion )
{
// same character index may occur several times
OSL_ENSURE( empty() || ((back().mnPos <= rPortion.mnPos) &&
(back().mnBasePos + back().mnBaseLen <= rPortion.mnBasePos)),
"PhoneticPortionModelList::appendPortion - wrong char order" );
if( empty() || (back().mnPos < rPortion.mnPos) )
{
push_back( rPortion );
}
else if( back().mnPos == rPortion.mnPos )
{
back().mnBasePos = rPortion.mnBasePos;
back().mnBaseLen = rPortion.mnBaseLen;
}
}
void PhoneticPortionModelList::importPortions( SequenceInputStream& rStrm )
{
sal_Int32 nCount = rStrm.readInt32();
clear();
if( nCount > 0 )
{
reserve( getLimitedValue< size_t, sal_Int64 >( nCount, 0, rStrm.getRemaining() / 6 ) );
PhoneticPortionModel aPortion;
for( sal_Int32 nIndex = 0; !rStrm.isEof() && (nIndex < nCount); ++nIndex )
{
aPortion.read( rStrm );
appendPortion( aPortion );
}
}
}
OUString PhoneticPortionModelList::importPortions( BiffInputStream& rStrm, sal_Int32 nPhoneticSize )
{
OUString aPhoneticText;
sal_uInt16 nPortionCount, nTextLen1, nTextLen2;
rStrm >> nPortionCount >> nTextLen1 >> nTextLen2;
OSL_ENSURE( nTextLen1 == nTextLen2, "PhoneticPortionModelList::importPortions - wrong phonetic text length" );
if( (nTextLen1 == nTextLen2) && (nTextLen1 > 0) )
{
sal_Int32 nMinSize = 2 * nTextLen1 + 6 * nPortionCount + 14;
OSL_ENSURE( nMinSize <= nPhoneticSize, "PhoneticPortionModelList::importPortions - wrong size of phonetic data" );
if( nMinSize <= nPhoneticSize )
{
aPhoneticText = rStrm.readUnicodeArray( nTextLen1 );
clear();
reserve( nPortionCount );
PhoneticPortionModel aPortion;
for( sal_uInt16 nPortion = 0; nPortion < nPortionCount; ++nPortion )
{
aPortion.read( rStrm );
appendPortion( aPortion );
}
}
}
return aPhoneticText;
}
// ============================================================================
RichString::RichString( const WorkbookHelper& rHelper ) :
WorkbookHelper( rHelper ),
maPhonSettings( rHelper )
{
}
RichStringPortionRef RichString::importText( const AttributeList& )
{
return createPortion();
}
RichStringPortionRef RichString::importRun( const AttributeList& )
{
return createPortion();
}
RichStringPhoneticRef RichString::importPhoneticRun( const AttributeList& rAttribs )
{
RichStringPhoneticRef xPhonetic = createPhonetic();
xPhonetic->importPhoneticRun( rAttribs );
return xPhonetic;
}
void RichString::importPhoneticPr( const AttributeList& rAttribs )
{
maPhonSettings.importPhoneticPr( rAttribs );
}
void RichString::importString( SequenceInputStream& rStrm, bool bRich )
{
sal_uInt8 nFlags = bRich ? rStrm.readuInt8() : 0;
OUString aBaseText = BiffHelper::readString( rStrm );
if( !rStrm.isEof() && getFlag( nFlags, BIFF12_STRINGFLAG_FONTS ) )
{
FontPortionModelList aPortions;
aPortions.importPortions( rStrm );
createTextPortions( aBaseText, aPortions );
}
else
{
createPortion()->setText( aBaseText );
}
if( !rStrm.isEof() && getFlag( nFlags, BIFF12_STRINGFLAG_PHONETICS ) )
{
OUString aPhoneticText = BiffHelper::readString( rStrm );
PhoneticPortionModelList aPortions;
aPortions.importPortions( rStrm );
maPhonSettings.importStringData( rStrm );
createPhoneticPortions( aPhoneticText, aPortions, aBaseText.getLength() );
}
}
void RichString::importCharArray( BiffInputStream& rStrm, sal_uInt16 nChars, rtl_TextEncoding eTextEnc )
{
createPortion()->setText( rStrm.readCharArrayUC( nChars, eTextEnc ) );
}
void RichString::importByteString( BiffInputStream& rStrm, rtl_TextEncoding eTextEnc, BiffStringFlags nFlags )
{
OSL_ENSURE( !getFlag( nFlags, BIFF_STR_KEEPFONTS ), "RichString::importString - keep fonts not implemented" );
OSL_ENSURE( !getFlag( nFlags, static_cast< BiffStringFlags >( ~(BIFF_STR_8BITLENGTH | BIFF_STR_EXTRAFONTS) ) ), "RichString::importByteString - unknown flag" );
bool b8BitLength = getFlag( nFlags, BIFF_STR_8BITLENGTH );
OString aBaseText = rStrm.readByteString( !b8BitLength );
if( !rStrm.isEof() && getFlag( nFlags, BIFF_STR_EXTRAFONTS ) )
{
FontPortionModelList aPortions;
aPortions.importPortions( rStrm, false );
createTextPortions( aBaseText, eTextEnc, aPortions );
}
else
{
createPortion()->setText( OStringToOUString( aBaseText, eTextEnc ) );
}
}
void RichString::importUniString( BiffInputStream& rStrm, BiffStringFlags nFlags )
{
OSL_ENSURE( !getFlag( nFlags, BIFF_STR_KEEPFONTS ), "RichString::importUniString - keep fonts not implemented" );
OSL_ENSURE( !getFlag( nFlags, static_cast< BiffStringFlags >( ~(BIFF_STR_8BITLENGTH | BIFF_STR_SMARTFLAGS) ) ), "RichString::importUniString - unknown flag" );
bool b8BitLength = getFlag( nFlags, BIFF_STR_8BITLENGTH );
// --- string header ---
sal_uInt16 nChars = b8BitLength ? rStrm.readuInt8() : rStrm.readuInt16();
sal_uInt8 nFlagField = 0;
if( (nChars > 0) || !getFlag( nFlags, BIFF_STR_SMARTFLAGS ) )
rStrm >> nFlagField;
bool b16Bit = getFlag( nFlagField, BIFF_STRF_16BIT );
bool bFonts = getFlag( nFlagField, BIFF_STRF_RICH );
bool bPhonetic = getFlag( nFlagField, BIFF_STRF_PHONETIC );
sal_uInt16 nFontCount = bFonts ? rStrm.readuInt16() : 0;
sal_Int32 nPhoneticSize = bPhonetic ? rStrm.readInt32() : 0;
// --- character array ---
OUString aBaseText = rStrm.readUniStringChars( nChars, b16Bit );
// --- formatting ---
// #122185# bRich flag may be set, but format runs may be missing
if( !rStrm.isEof() && (nFontCount > 0) )
{
FontPortionModelList aPortions;
aPortions.importPortions( rStrm, nFontCount, BIFF_FONTPORTION_16BIT );
createTextPortions( aBaseText, aPortions );
}
else
{
createPortion()->setText( aBaseText );
}
// --- Asian phonetic information ---
// #122185# bPhonetic flag may be set, but phonetic info may be missing
if( !rStrm.isEof() && (nPhoneticSize > 0) )
{
sal_Int64 nPhoneticEnd = rStrm.tell() + nPhoneticSize;
OSL_ENSURE( nPhoneticSize > 14, "RichString::importUniString - wrong size of phonetic data" );
if( nPhoneticSize > 14 )
{
sal_uInt16 nId, nSize;
rStrm >> nId >> nSize;
OSL_ENSURE( nId == 1, "RichString::importUniString - unknown phonetic data identifier" );
sal_Int32 nMinSize = nSize + 4;
OSL_ENSURE( nMinSize <= nPhoneticSize, "RichString::importUniString - wrong size of phonetic data" );
if( (nId == 1) && (nMinSize <= nPhoneticSize) )
{
maPhonSettings.importStringData( rStrm );
PhoneticPortionModelList aPortions;
OUString aPhoneticText = aPortions.importPortions( rStrm, nPhoneticSize );
createPhoneticPortions( aPhoneticText, aPortions, aBaseText.getLength() );
}
}
rStrm.seek( nPhoneticEnd );
}
}
void RichString::finalizeImport()
{
maTextPortions.forEachMem( &RichStringPortion::finalizeImport );
}
bool RichString::extractPlainString( OUString& orString, const Font* pFirstPortionFont ) const
{
if( !maPhonPortions.empty() )
return false;
if( maTextPortions.empty() )
{
orString = OUString();
return true;
}
if( (maTextPortions.size() == 1) && !maTextPortions.front()->hasFont() && !lclNeedsRichTextFormat( pFirstPortionFont ) )
{
orString = maTextPortions.front()->getText();
return orString.indexOf( '\x0A' ) < 0;
}
return false;
}
void RichString::convert( const Reference< XText >& rxText, bool bReplaceOld, const Font* pFirstPortionFont ) const
{
for( PortionVector::const_iterator aIt = maTextPortions.begin(), aEnd = maTextPortions.end(); aIt != aEnd; ++aIt )
{
(*aIt)->convert( rxText, pFirstPortionFont, bReplaceOld );
pFirstPortionFont = 0; // use passed font for first portion only
bReplaceOld = false; // do not replace first portion text with following portions
}
}
// private --------------------------------------------------------------------
RichStringPortionRef RichString::createPortion()
{
RichStringPortionRef xPortion( new RichStringPortion( *this ) );
maTextPortions.push_back( xPortion );
return xPortion;
}
RichStringPhoneticRef RichString::createPhonetic()
{
RichStringPhoneticRef xPhonetic( new RichStringPhonetic( *this ) );
maPhonPortions.push_back( xPhonetic );
return xPhonetic;
}
void RichString::createTextPortions( const OString& rText, rtl_TextEncoding eTextEnc, FontPortionModelList& rPortions )
{
maTextPortions.clear();
sal_Int32 nStrLen = rText.getLength();
if( nStrLen > 0 )
{
// add leading and trailing string position to ease the following loop
if( rPortions.empty() || (rPortions.front().mnPos > 0) )
rPortions.insert( rPortions.begin(), FontPortionModel( 0, -1 ) );
if( rPortions.back().mnPos < nStrLen )
rPortions.push_back( FontPortionModel( nStrLen, -1 ) );
// create all string portions according to the font id vector
for( FontPortionModelList::const_iterator aIt = rPortions.begin(); aIt->mnPos < nStrLen; ++aIt )
{
sal_Int32 nPortionLen = (aIt + 1)->mnPos - aIt->mnPos;
if( (0 < nPortionLen) && (aIt->mnPos + nPortionLen <= nStrLen) )
{
// convert byte string to unicode string, using current font encoding
FontRef xFont = getStyles().getFont( aIt->mnFontId );
rtl_TextEncoding eFontEnc = xFont.get() ? xFont->getFontEncoding() : eTextEnc;
OUString aUniStr = OStringToOUString( rText.copy( aIt->mnPos, nPortionLen ), eFontEnc );
// create string portion
RichStringPortionRef xPortion = createPortion();
xPortion->setText( aUniStr );
xPortion->setFontId( aIt->mnFontId );
}
}
}
}
void RichString::createTextPortions( const OUString& rText, FontPortionModelList& rPortions )
{
maTextPortions.clear();
sal_Int32 nStrLen = rText.getLength();
if( nStrLen > 0 )
{
// add leading and trailing string position to ease the following loop
if( rPortions.empty() || (rPortions.front().mnPos > 0) )
rPortions.insert( rPortions.begin(), FontPortionModel( 0, -1 ) );
if( rPortions.back().mnPos < nStrLen )
rPortions.push_back( FontPortionModel( nStrLen, -1 ) );
// create all string portions according to the font id vector
for( FontPortionModelList::const_iterator aIt = rPortions.begin(); aIt->mnPos < nStrLen; ++aIt )
{
sal_Int32 nPortionLen = (aIt + 1)->mnPos - aIt->mnPos;
if( (0 < nPortionLen) && (aIt->mnPos + nPortionLen <= nStrLen) )
{
RichStringPortionRef xPortion = createPortion();
xPortion->setText( rText.copy( aIt->mnPos, nPortionLen ) );
xPortion->setFontId( aIt->mnFontId );
}
}
}
}
void RichString::createPhoneticPortions( const ::rtl::OUString& rText, PhoneticPortionModelList& rPortions, sal_Int32 nBaseLen )
{
maPhonPortions.clear();
sal_Int32 nStrLen = rText.getLength();
if( nStrLen > 0 )
{
// no portions - assign phonetic text to entire base text
if( rPortions.empty() )
rPortions.push_back( PhoneticPortionModel( 0, 0, nBaseLen ) );
// add trailing string position to ease the following loop
if( rPortions.back().mnPos < nStrLen )
rPortions.push_back( PhoneticPortionModel( nStrLen, nBaseLen, 0 ) );
// create all phonetic portions according to the portions vector
for( PhoneticPortionModelList::const_iterator aIt = rPortions.begin(); aIt->mnPos < nStrLen; ++aIt )
{
sal_Int32 nPortionLen = (aIt + 1)->mnPos - aIt->mnPos;
if( (0 < nPortionLen) && (aIt->mnPos + nPortionLen <= nStrLen) )
{
RichStringPhoneticRef xPhonetic = createPhonetic();
xPhonetic->setText( rText.copy( aIt->mnPos, nPortionLen ) );
xPhonetic->setBaseRange( aIt->mnBasePos, aIt->mnBasePos + aIt->mnBaseLen );
}
}
}
}
// ============================================================================
} // namespace xls
} // namespace oox