/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   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 .
 */

#include <stylesbuffer.hxx>
#include <patterncache.hxx>

#include <com/sun/star/awt/FontDescriptor.hpp>
#include <com/sun/star/awt/FontFamily.hpp>
#include <com/sun/star/awt/FontPitch.hpp>
#include <com/sun/star/awt/FontSlant.hpp>
#include <com/sun/star/awt/FontStrikeout.hpp>
#include <com/sun/star/awt/FontType.hpp>
#include <com/sun/star/awt/FontWeight.hpp>
#include <com/sun/star/awt/FontUnderline.hpp>
#include <com/sun/star/awt/XDevice.hpp>
#include <com/sun/star/awt/XFont2.hpp>
#include <com/sun/star/container/XIndexAccess.hpp>
#include <com/sun/star/sheet/XSpreadsheetDocument.hpp>
#include <com/sun/star/style/XStyle.hpp>
#include <com/sun/star/text/WritingMode2.hpp>
#include <com/sun/star/table/BorderLineStyle.hpp>
#include <com/sun/star/table/CellVertJustify2.hpp>
#include <com/sun/star/table/CellJustifyMethod.hpp>
#include <editeng/justifyitem.hxx>
#include <editeng/frmdiritem.hxx>
#include <editeng/fontitem.hxx>
#include <editeng/postitem.hxx>
#include <editeng/fhgtitem.hxx>
#include <editeng/wghtitem.hxx>
#include <editeng/udlnitem.hxx>
#include <editeng/colritem.hxx>
#include <editeng/crossedoutitem.hxx>
#include <editeng/contouritem.hxx>
#include <editeng/escapementitem.hxx>
#include <editeng/shdditem.hxx>
#include <editeng/eeitem.hxx>
#include <editeng/borderline.hxx>
#include <editeng/boxitem.hxx>
#include <editeng/lineitem.hxx>
#include <editeng/brushitem.hxx>
#include <svx/rotmodit.hxx>
#include <tools/fontenum.hxx>
#include <tools/UnitConversion.hxx>
#include <utility>
#include <vcl/unohelp.hxx>
#include <rtl/tencinfo.h>
#include <rtl/ustrbuf.hxx>
#include <sal/log.hxx>
#include <osl/diagnose.h>
#include <oox/core/filterbase.hxx>
#include <oox/helper/attributelist.hxx>
#include <oox/helper/binaryinputstream.hxx>
#include <oox/helper/containerhelper.hxx>
#include <oox/helper/propertymap.hxx>
#include <oox/helper/propertyset.hxx>
#include <oox/token/namespaces.hxx>
#include <oox/token/properties.hxx>
#include <oox/token/tokens.hxx>
#include <themebuffer.hxx>
#include <unitconverter.hxx>
#include <document.hxx>
#include <stlpool.hxx>
#include <docpool.hxx>
#include <ftools.hxx>
#include <scitems.hxx>
#include <attrib.hxx>
#include <globstr.hrc>
#include <scresid.hxx>
#include <documentimport.hxx>
#include <patattr.hxx>
#include <stlsheet.hxx>
#include <biffhelper.hxx>
#include <docuno.hxx>

namespace oox::xls {

using namespace ::com::sun::star::awt;
using namespace ::com::sun::star::container;
using namespace ::com::sun::star::style;
using namespace ::com::sun::star::table;
using namespace ::com::sun::star::text;
using namespace ::com::sun::star::uno;


namespace {

// OOXML constants ------------------------------------------------------------

// OOXML predefined color indexes (also used in BIFF3-BIFF8)
const sal_Int32 OOX_COLOR_USEROFFSET        = 0;        /// First user defined color in palette (OOXML/BIFF12).

// OOXML font family (also used in BIFF)
const sal_Int32 OOX_FONTFAMILY_NONE         = 0;
const sal_Int32 OOX_FONTFAMILY_ROMAN        = 1;
const sal_Int32 OOX_FONTFAMILY_SWISS        = 2;
const sal_Int32 OOX_FONTFAMILY_MODERN       = 3;
const sal_Int32 OOX_FONTFAMILY_SCRIPT       = 4;
const sal_Int32 OOX_FONTFAMILY_DECORATIVE   = 5;

// OOXML cell text direction (also used in BIFF)
const sal_Int32 OOX_XF_TEXTDIR_CONTEXT      = 0;
const sal_Int32 OOX_XF_TEXTDIR_LTR          = 1;
const sal_Int32 OOX_XF_TEXTDIR_RTL          = 2;

// OOXML cell rotation (also used in BIFF)
const sal_Int32 OOX_XF_ROTATION_NONE        = 0;
const sal_Int32 OOX_XF_ROTATION_STACKED     = 255;

// OOXML cell indentation
const sal_Int32 OOX_XF_INDENT_NONE          = 0;

// OOXML built-in cell styles (also used in BIFF)
const sal_Int32 OOX_STYLE_NORMAL            = 0;        /// Default cell style.
const sal_Int32 OOX_STYLE_ROWLEVEL          = 1;        /// RowLevel_x cell style.
const sal_Int32 OOX_STYLE_COLLEVEL          = 2;        /// ColLevel_x cell style.

// BIFF12 constants -----------------------------------------------------------

// BIFF12 color types
const sal_uInt8 BIFF12_COLOR_AUTO           = 0;
const sal_uInt8 BIFF12_COLOR_INDEXED        = 1;
const sal_uInt8 BIFF12_COLOR_RGB            = 2;
const sal_uInt8 BIFF12_COLOR_THEME          = 3;

// BIFF12 diagonal borders
const sal_uInt8 BIFF12_BORDER_DIAG_TLBR     = 0x01;     /// Top-left to bottom-right.
const sal_uInt8 BIFF12_BORDER_DIAG_BLTR     = 0x02;     /// Bottom-left to top-right.

// BIFF12 gradient fill
const sal_Int32 BIFF12_FILL_GRADIENT        = 40;

// BIFF12 XF flags
const sal_uInt32 BIFF12_XF_WRAPTEXT         = 0x00400000;
const sal_uInt32 BIFF12_XF_JUSTLASTLINE     = 0x00800000;
const sal_uInt32 BIFF12_XF_SHRINK           = 0x01000000;
const sal_uInt32 BIFF12_XF_LOCKED           = 0x10000000;
const sal_uInt32 BIFF12_XF_HIDDEN           = 0x20000000;

// BIFF12 XF attribute used flags
const sal_uInt16 BIFF12_XF_NUMFMT_USED      = 0x0001;
const sal_uInt16 BIFF12_XF_FONT_USED        = 0x0002;
const sal_uInt16 BIFF12_XF_ALIGN_USED       = 0x0004;
const sal_uInt16 BIFF12_XF_BORDER_USED      = 0x0008;
const sal_uInt16 BIFF12_XF_AREA_USED        = 0x0010;
const sal_uInt16 BIFF12_XF_PROT_USED        = 0x0020;

// BIFF12 DXF constants
const sal_uInt16 BIFF12_DXF_FILL_PATTERN    = 0;
const sal_uInt16 BIFF12_DXF_FILL_FGCOLOR    = 1;
const sal_uInt16 BIFF12_DXF_FILL_BGCOLOR    = 2;
const sal_uInt16 BIFF12_DXF_FILL_GRADIENT   = 3;
const sal_uInt16 BIFF12_DXF_FILL_STOP       = 4;
const sal_uInt16 BIFF12_DXF_FONT_COLOR      = 5;
const sal_uInt16 BIFF12_DXF_BORDER_TOP      = 6;
const sal_uInt16 BIFF12_DXF_BORDER_BOTTOM   = 7;
const sal_uInt16 BIFF12_DXF_BORDER_LEFT     = 8;
const sal_uInt16 BIFF12_DXF_BORDER_RIGHT    = 9;
const sal_uInt16 BIFF12_DXF_FONT_NAME       = 24;
const sal_uInt16 BIFF12_DXF_FONT_WEIGHT     = 25;
const sal_uInt16 BIFF12_DXF_FONT_UNDERLINE  = 26;
const sal_uInt16 BIFF12_DXF_FONT_ESCAPEMENT = 27;
const sal_uInt16 BIFF12_DXF_FONT_ITALIC     = 28;
const sal_uInt16 BIFF12_DXF_FONT_STRIKE     = 29;
const sal_uInt16 BIFF12_DXF_FONT_OUTLINE    = 30;
const sal_uInt16 BIFF12_DXF_FONT_SHADOW     = 31;
const sal_uInt16 BIFF12_DXF_FONT_HEIGHT     = 36;
const sal_uInt16 BIFF12_DXF_FONT_SCHEME     = 37;
const sal_uInt16 BIFF12_DXF_NUMFMT_CODE     = 38;
const sal_uInt16 BIFF12_DXF_NUMFMT_ID       = 41;
const sal_uInt16 BIFF12_DXF_CELL_LOCKED     = 43;
const sal_uInt16 BIFF12_DXF_CELL_HIDDEN     = 44;

// BIFF12 CELLSTYLE flags
const sal_uInt16 BIFF12_CELLSTYLE_BUILTIN   = 0x0001;
const sal_uInt16 BIFF12_CELLSTYLE_HIDDEN    = 0x0002;
const sal_uInt16 BIFF12_CELLSTYLE_CUSTOM    = 0x0004;

// BIFF constants -------------------------------------------------------------

// BIFF font flags, also used in BIFF12
const sal_uInt16 BIFF_FONTFLAG_ITALIC       = 0x0002;
const sal_uInt16 BIFF_FONTFLAG_STRIKEOUT    = 0x0008;
const sal_uInt16 BIFF_FONTFLAG_OUTLINE      = 0x0010;
const sal_uInt16 BIFF_FONTFLAG_SHADOW       = 0x0020;

// BIFF font weight
const sal_uInt16 BIFF_FONTWEIGHT_BOLD       = 450;

// BIFF font underline, also used in BIFF12
const sal_uInt8 BIFF_FONTUNDERL_NONE        = 0;
const sal_uInt8 BIFF_FONTUNDERL_SINGLE      = 1;
const sal_uInt8 BIFF_FONTUNDERL_DOUBLE      = 2;
const sal_uInt8 BIFF_FONTUNDERL_SINGLE_ACC  = 33;
const sal_uInt8 BIFF_FONTUNDERL_DOUBLE_ACC  = 34;

::Color lclReadRgbColor( BinaryInputStream& rStrm )
{
    sal_uInt8 nR, nG, nB, nA;
    nR = rStrm.readuChar();
    nG = rStrm.readuChar();
    nB = rStrm.readuChar();
    nA = rStrm.readuChar();
    sal_Int32 nValue = nA;
    nValue <<= 8;
    nValue |= nR;
    nValue <<= 8;
    nValue |= nG;
    nValue <<= 8;
    nValue |= nB;
    return ::Color(ColorTransparency, nValue);
}

} // namespace

ExcelGraphicHelper::ExcelGraphicHelper( const WorkbookHelper& rHelper ) :
    GraphicHelper( rHelper.getBaseFilter().getComponentContext(), rHelper.getBaseFilter().getTargetFrame(), rHelper.getBaseFilter().getStorage() ),
    WorkbookHelper( rHelper )
{
}

::Color ExcelGraphicHelper::getSchemeColor( sal_Int32 nToken ) const
{
    return getTheme().getColorByToken( nToken );
}

::Color ExcelGraphicHelper::getPaletteColor( sal_Int32 nPaletteIdx ) const
{
    return getStyles().getPaletteColor( nPaletteIdx );
}

void XlsColor::setAuto()
{
    clearTransformations();
    setSchemeClr( XML_phClr );
}

void XlsColor::setRgb( ::Color nRgbValue, double fTint )
{
    clearTransformations();
    setSrgbClr( sal_uInt32(nRgbValue) & 0xFFFFFF );
    if (fTint != 0.0)
        addExcelTintTransformation(fTint);
}

void XlsColor::setTheme( sal_Int32 nThemeIdx, double fTint )
{
    clearTransformations();
    static const sal_Int32 spnColorTokens[] = {
        XML_lt1, XML_dk1, XML_lt2, XML_dk2, XML_accent1, XML_accent2,
        XML_accent3, XML_accent4, XML_accent5, XML_accent6, XML_hlink, XML_folHlink };
    setSchemeClr( STATIC_ARRAY_SELECT( spnColorTokens, nThemeIdx, XML_TOKEN_INVALID ) );
    if (fTint != 0.0)
        addExcelTintTransformation( fTint );
}

void XlsColor::setIndexed( sal_Int32 nPaletteIdx, double fTint )
{
    clearTransformations();
    setPaletteClr( nPaletteIdx );
    if (fTint != 0.0)
        addExcelTintTransformation(fTint);
}

void XlsColor::importColor( const AttributeList& rAttribs )
{
    // tdf#113271 The order of import color is very important in case of more than one color attributes was provided.
    // This order (theme -> rgb -> indexed -> auto) is not documented and was gathered experimentally based on MS Excel 2013.
    if (rAttribs.hasAttribute(XML_theme))
    {
        sal_Int32 nTheme = rAttribs.getInteger(XML_theme, -1);
        double fTint = rAttribs.getDouble(XML_tint, 0.0);
        setTheme(nTheme , fTint);
    }
    else if( rAttribs.hasAttribute( XML_rgb ) )
        setRgb( ::Color(ColorTransparency, rAttribs.getIntegerHex( XML_rgb, sal_Int32(API_RGB_TRANSPARENT) ) ), rAttribs.getDouble( XML_tint, 0.0 ) );
    else if( rAttribs.hasAttribute( XML_indexed ) )
        setIndexed( rAttribs.getInteger( XML_indexed, -1 ), rAttribs.getDouble( XML_tint, 0.0 ) );
    else if( rAttribs.getBool( XML_auto, false ) )
        setAuto();
    else
    {
        OSL_FAIL( "Color::importColor - unknown color type" );
        setAuto();
    }
}

void XlsColor::importColor( SequenceInputStream& rStrm )
{
    sal_uInt8 nFlags, nIndex;
    sal_Int16 nTint;
    nFlags = rStrm.readuChar();
    nIndex = rStrm.readuChar();
    nTint = rStrm.readInt16();

    // scale tint from signed 16-bit to double range -1.0 ... 1.0
    double fTint = nTint;
    if( nTint < 0 )
        fTint /= -SAL_MIN_INT16;
    else if( nTint > 0 )
        fTint /= SAL_MAX_INT16;

    switch( extractValue< sal_uInt8 >( nFlags, 1, 7 ) )
    {
        case BIFF12_COLOR_AUTO:
            setAuto();
            rStrm.skip( 4 );
        break;
        case BIFF12_COLOR_INDEXED:
            setIndexed( nIndex, fTint );
            rStrm.skip( 4 );
        break;
        case BIFF12_COLOR_RGB:
            setRgb( lclReadRgbColor( rStrm ), fTint );
        break;
        case BIFF12_COLOR_THEME:
            setTheme( nIndex, fTint );
            rStrm.skip( 4 );
        break;
        default:
            OSL_FAIL( "Color::importColor - unknown color type" );
            setAuto();
            rStrm.skip( 4 );
    }
}

void XlsColor::importColorId( SequenceInputStream& rStrm )
{
    setIndexed( rStrm.readInt32() );
}

SequenceInputStream& operator>>( SequenceInputStream& rStrm, XlsColor& orColor )
{
    orColor.importColor( rStrm );
    return rStrm;
}

namespace {

/** Standard EGA colors, bright. */
#define PALETTE_EGA_COLORS_LIGHT \
            ::Color(0x000000), ::Color(0xFFFFFF), ::Color(0xFF0000), ::Color(0x00FF00), ::Color(0x0000FF), ::Color(0xFFFF00), ::Color(0xFF00FF), ::Color(0x00FFFF)
/** Standard EGA colors), dark. */
#define PALETTE_EGA_COLORS_DARK \
            ::Color(0x800000), ::Color(0x008000), ::Color(0x000080), ::Color(0x808000), ::Color(0x800080), ::Color(0x008080), ::Color(0xC0C0C0), ::Color(0x808080)

/** Default color table for BIFF8/BIFF12/OOXML. */
const ::Color spnDefColors8[] =
{
/*  0 */    PALETTE_EGA_COLORS_LIGHT,
/*  8 */    PALETTE_EGA_COLORS_LIGHT,
/* 16 */    PALETTE_EGA_COLORS_DARK,
/* 24 */    ::Color(0x9999FF), ::Color(0x993366), ::Color(0xFFFFCC), ::Color(0xCCFFFF), ::Color(0x660066), ::Color(0xFF8080), ::Color(0x0066CC), ::Color(0xCCCCFF),
/* 32 */    ::Color(0x000080), ::Color(0xFF00FF), ::Color(0xFFFF00), ::Color(0x00FFFF), ::Color(0x800080), ::Color(0x800000), ::Color(0x008080), ::Color(0x0000FF),
/* 40 */    ::Color(0x00CCFF), ::Color(0xCCFFFF), ::Color(0xCCFFCC), ::Color(0xFFFF99), ::Color(0x99CCFF), ::Color(0xFF99CC), ::Color(0xCC99FF), ::Color(0xFFCC99),
/* 48 */    ::Color(0x3366FF), ::Color(0x33CCCC), ::Color(0x99CC00), ::Color(0xFFCC00), ::Color(0xFF9900), ::Color(0xFF6600), ::Color(0x666699), ::Color(0x969696),
/* 56 */    ::Color(0x003366), ::Color(0x339966), ::Color(0x003300), ::Color(0x333300), ::Color(0x993300), ::Color(0x993366), ::Color(0x333399), ::Color(0x333333)
};

#undef PALETTE_EGA_COLORS_LIGHT
#undef PALETTE_EGA_COLORS_DARK

} // namespace

ColorPalette::ColorPalette( const WorkbookHelper& rHelper )
    : WorkbookHelper(rHelper)
    , mnAppendIndex(0)
{
    // default colors
    maColors.insert( maColors.begin(), spnDefColors8, spnDefColors8 + SAL_N_ELEMENTS(spnDefColors8) );
    mnAppendIndex = OOX_COLOR_USEROFFSET;
}

void ColorPalette::importPaletteColor( const AttributeList& rAttribs )
{
    appendColor( ::Color(ColorTransparency, rAttribs.getIntegerHex( XML_rgb, sal_Int32(API_RGB_WHITE) ) ) );
}

void ColorPalette::importPaletteColor( SequenceInputStream& rStrm )
{
    ::Color nRgb = lclReadRgbColor( rStrm );
    appendColor( nRgb );
}

::Color ColorPalette::getColor( sal_Int32 nPaletteIdx ) const
{
    ::Color nColor = API_RGB_TRANSPARENT;
    if( const ::Color* pnPaletteColor = ContainerHelper::getVectorElement( maColors, nPaletteIdx ) )
    {
        nColor = *pnPaletteColor;
    }
    else switch( nPaletteIdx )
    {
        case OOX_COLOR_WINDOWTEXT3:
        case OOX_COLOR_WINDOWTEXT:
        case OOX_COLOR_CHWINDOWTEXT:    nColor = getBaseFilter().getGraphicHelper().getSystemColor( XML_windowText );   break;
        case OOX_COLOR_WINDOWBACK3:
        case OOX_COLOR_WINDOWBACK:
        case OOX_COLOR_CHWINDOWBACK:    nColor = getBaseFilter().getGraphicHelper().getSystemColor( XML_window );       break;
        case OOX_COLOR_BUTTONBACK:      nColor = getBaseFilter().getGraphicHelper().getSystemColor( XML_btnFace );      break;
        case OOX_COLOR_CHBORDERAUTO:    nColor = API_RGB_BLACK; /* really always black? */                              break;
        case OOX_COLOR_NOTEBACK:        nColor = getBaseFilter().getGraphicHelper().getSystemColor( XML_infoBk );       break;
        case OOX_COLOR_NOTETEXT:        nColor = getBaseFilter().getGraphicHelper().getSystemColor( XML_infoText );     break;
        case OOX_COLOR_FONTAUTO:        nColor = API_RGB_TRANSPARENT;                                                   break;
        default:                        OSL_FAIL( "ColorPalette::getColor - unknown color index" );
    }
    return nColor;
}

void ColorPalette::appendColor( ::Color nRGBValue )
{
    if( mnAppendIndex < maColors.size() )
        maColors[ mnAppendIndex ] = nRGBValue;
    else
        maColors.push_back( nRGBValue );
    ++mnAppendIndex;
}

namespace {

void lclSetFontName( ApiScriptFontName& rFontName, const FontDescriptor& rFontDesc, bool bHasGlyphs )
{
    if( bHasGlyphs )
    {
        rFontName.maName = rFontDesc.Name;
        rFontName.mnFamily = rFontDesc.Family;
        // API font descriptor contains rtl_TextEncoding constants
        rFontName.mnTextEnc = rFontDesc.CharSet;
    }
    else
    {
        rFontName = ApiScriptFontName();
    }
}

} // namespace

FontModel::FontModel() :
    mnScheme( XML_none ),
    mnFamily( OOX_FONTFAMILY_NONE ),
    mnCharSet( WINDOWS_CHARSET_DEFAULT ),
    mfHeight( 0.0 ),
    mnUnderline( XML_none ),
    mnEscapement( XML_baseline ),
    mbBold( false ),
    mbItalic( false ),
    mbStrikeout( false ),
    mbOutline( false ),
    mbShadow( false )
{
}

void FontModel::setBiff12Scheme( sal_uInt8 nScheme )
{
    static const sal_Int32 spnSchemes[] = { XML_none, XML_major, XML_minor };
    mnScheme = STATIC_ARRAY_SELECT( spnSchemes, nScheme, XML_none );
}

void FontModel::setBiffHeight( sal_uInt16 nHeight )
{
    mfHeight = nHeight / 20.0;  // convert twips to points
}

void FontModel::setBiffWeight( sal_uInt16 nWeight )
{
    mbBold = nWeight >= BIFF_FONTWEIGHT_BOLD;
}

void FontModel::setBiffUnderline( sal_uInt16 nUnderline )
{
    switch( nUnderline )
    {
        case BIFF_FONTUNDERL_NONE:          mnUnderline = XML_none;             break;
        case BIFF_FONTUNDERL_SINGLE:        mnUnderline = XML_single;           break;
        case BIFF_FONTUNDERL_DOUBLE:        mnUnderline = XML_double;           break;
        case BIFF_FONTUNDERL_SINGLE_ACC:    mnUnderline = XML_singleAccounting; break;
        case BIFF_FONTUNDERL_DOUBLE_ACC:    mnUnderline = XML_doubleAccounting; break;
        default:                            mnUnderline = XML_none;
    }
}

void FontModel::setBiffEscapement( sal_uInt16 nEscapement )
{
    static const sal_Int32 spnEscapes[] = { XML_baseline, XML_superscript, XML_subscript };
    mnEscapement = STATIC_ARRAY_SELECT( spnEscapes, nEscapement, XML_baseline );
}

bool FontModel::operator==(const FontModel& rhs) const
{
    if (maName != rhs.maName)
        return false;
    if (maColor != rhs.maColor)
        return false;
    if (mnScheme != rhs.mnScheme)
        return false;
    if (mnFamily != rhs.mnFamily)
        return false;
    if (mnCharSet != rhs.mnCharSet)
        return false;
    if (mfHeight != rhs.mfHeight)
        return false;
    if (mnUnderline != rhs.mnUnderline)
        return false;
    if (mnEscapement != rhs.mnEscapement)
        return false;
    if (mbBold != rhs.mbBold)
        return false;
    if (mbItalic != rhs.mbItalic)
        return false;
    if (mbStrikeout != rhs.mbStrikeout)
        return false;
    if (mbOutline != rhs.mbOutline)
        return false;
    if (mbShadow != rhs.mbShadow)
        return false;
    return true;
}

ApiFontUsedFlags::ApiFontUsedFlags( bool bAllUsed ) :
    mbNameUsed( bAllUsed ),
    mbColorUsed( bAllUsed ),
    mbSchemeUsed( bAllUsed ),
    mbHeightUsed( bAllUsed ),
    mbUnderlineUsed( bAllUsed ),
    mbEscapementUsed( bAllUsed ),
    mbWeightUsed( bAllUsed ),
    mbPostureUsed( bAllUsed ),
    mbStrikeoutUsed( bAllUsed ),
    mbOutlineUsed( bAllUsed ),
    mbShadowUsed( bAllUsed )
{
}

ApiScriptFontName::ApiScriptFontName() :
    mnFamily( css::awt::FontFamily::DONTKNOW ),
    mnTextEnc( RTL_TEXTENCODING_DONTKNOW )
{
}

ApiFontData::ApiFontData() :
    maDesc(
        u"Calibri"_ustr,
        220,                                            // height 11 points
        0,
        OUString(),
        css::awt::FontFamily::DONTKNOW,
        RTL_TEXTENCODING_DONTKNOW,
        css::awt::FontPitch::DONTKNOW,
        100.0,
        css::awt::FontWeight::NORMAL,
        css::awt::FontSlant_NONE,
        css::awt::FontUnderline::NONE,
        css::awt::FontStrikeout::NONE,
        0.0,
        false,
        false,
        css::awt::FontType::DONTKNOW ),
    mnColor( API_RGB_TRANSPARENT ),
    mnEscapement( API_ESCAPE_NONE ),
    mnEscapeHeight( API_ESCAPEHEIGHT_NONE ),
    mbOutline( false ),
    mbShadow( false )
{
    maLatinFont.maName = maDesc.Name;
}

Font::Font( const WorkbookHelper& rHelper, bool bDxf ) :
    WorkbookHelper( rHelper ),
    maModel( rHelper.getTheme().getDefaultFontModel() ),
    maUsedFlags( !bDxf ),
    mbDxf( bDxf )
{
}

Font::Font( const WorkbookHelper& rHelper, FontModel aModel ) :
    WorkbookHelper( rHelper ),
    maModel(std::move( aModel )),
    maUsedFlags( true ),
    mbDxf( false )
{
}

void Font::importAttribs( sal_Int32 nElement, const AttributeList& rAttribs )
{
    const FontModel& rDefModel = getTheme().getDefaultFontModel();
    switch( nElement )
    {
        case XLS_TOKEN( name ):     // when in <font> element
        case XLS_TOKEN( rFont ):    // when in <rPr> element
            if( rAttribs.hasAttribute( XML_val ) )
            {
                maModel.maName = rAttribs.getXString( XML_val, OUString() );
                maUsedFlags.mbNameUsed = true;
            }
        break;
        case XLS_TOKEN( scheme ):
            maModel.mnScheme = rAttribs.getToken( XML_val, rDefModel.mnScheme );
        break;
        case XLS_TOKEN( family ):
            maModel.mnFamily = rAttribs.getInteger( XML_val, rDefModel.mnFamily );
        break;
        case XLS_TOKEN( charset ):
            maModel.mnCharSet = rAttribs.getInteger( XML_val, rDefModel.mnCharSet );
        break;
        case XLS_TOKEN( sz ):
            maModel.mfHeight = rAttribs.getDouble( XML_val, rDefModel.mfHeight );
            maUsedFlags.mbHeightUsed = true;
        break;
        case XLS_TOKEN( color ):
            maModel.maColor.importColor( rAttribs );
            maUsedFlags.mbColorUsed = true;
        break;
        case XLS_TOKEN( u ):
            maModel.mnUnderline = rAttribs.getToken( XML_val, XML_single );
            maUsedFlags.mbUnderlineUsed = true;
        break;
        case XLS_TOKEN( vertAlign ):
            maModel.mnEscapement = rAttribs.getToken( XML_val, XML_baseline );
            maUsedFlags.mbEscapementUsed = true;
        break;
        case XLS_TOKEN( b ):
            maModel.mbBold = rAttribs.getBool( XML_val, true );
            maUsedFlags.mbWeightUsed = true;
        break;
        case XLS_TOKEN( i ):
            maModel.mbItalic = rAttribs.getBool( XML_val, true );
            maUsedFlags.mbPostureUsed = true;
        break;
        case XLS_TOKEN( strike ):
            maModel.mbStrikeout = rAttribs.getBool( XML_val, true );
            maUsedFlags.mbStrikeoutUsed = true;
        break;
        case XLS_TOKEN( outline ):
            maModel.mbOutline = rAttribs.getBool( XML_val, true );
            maUsedFlags.mbOutlineUsed = true;
        break;
        case XLS_TOKEN( shadow ):
            maModel.mbShadow = rAttribs.getBool( XML_val, true );
            maUsedFlags.mbShadowUsed = true;
        break;
    }
}

void Font::importFont( SequenceInputStream& rStrm )
{
    SAL_WARN_IF( mbDxf, "sc", "Font::importFont - unexpected conditional formatting flag" );

    sal_uInt16 nHeight, nFlags, nWeight, nEscapement;
    sal_uInt8 nUnderline, nFamily, nCharSet, nScheme;
    nHeight = rStrm.readuInt16();
    nFlags = rStrm.readuInt16();
    nWeight = rStrm.readuInt16();
    nEscapement = rStrm.readuInt16();
    nUnderline = rStrm.readuChar();
    nFamily = rStrm.readuChar();
    nCharSet = rStrm.readuChar();
    rStrm.skip( 1 );
    rStrm >> maModel.maColor;
    nScheme = rStrm.readuChar();
    rStrm >> maModel.maName;

    // equal constants in all BIFFs for weight, underline, and escapement
    maModel.setBiff12Scheme( nScheme );
    maModel.setBiffHeight( nHeight );
    maModel.setBiffWeight( nWeight );
    maModel.setBiffUnderline( nUnderline );
    maModel.setBiffEscapement( nEscapement );
    maModel.mnFamily    = nFamily;
    maModel.mnCharSet   = nCharSet;
    // equal flags in all BIFFs
    maModel.mbItalic    = getFlag( nFlags, BIFF_FONTFLAG_ITALIC );
    maModel.mbStrikeout = getFlag( nFlags, BIFF_FONTFLAG_STRIKEOUT );
    maModel.mbOutline   = getFlag( nFlags, BIFF_FONTFLAG_OUTLINE );
    maModel.mbShadow    = getFlag( nFlags, BIFF_FONTFLAG_SHADOW );
}

void Font::importDxfName( SequenceInputStream& rStrm )
{
    SAL_WARN_IF( !mbDxf, "sc", "Font::importDxfName - missing conditional formatting flag" );
    maModel.maName = BiffHelper::readString( rStrm, false );
    maUsedFlags.mbColorUsed = true;
}

void Font::importDxfColor( SequenceInputStream& rStrm )
{
    SAL_WARN_IF( !mbDxf, "sc", "Font::importDxfColor - missing conditional formatting flag" );
    rStrm >> maModel.maColor;
    maUsedFlags.mbColorUsed = true;
}

void Font::importDxfScheme( SequenceInputStream& rStrm )
{
    SAL_WARN_IF( !mbDxf, "sc", "Font::importDxfScheme - missing conditional formatting flag" );
    maModel.setBiff12Scheme( rStrm.readuInt8() );
    maUsedFlags.mbSchemeUsed = true;
}

void Font::importDxfHeight( SequenceInputStream& rStrm )
{
    SAL_WARN_IF( !mbDxf, "sc", "Font::importDxfHeight - missing conditional formatting flag" );
    maModel.setBiffHeight( rStrm.readuInt16() );
    maUsedFlags.mbHeightUsed = true;
}

void Font::importDxfWeight( SequenceInputStream& rStrm )
{
    SAL_WARN_IF( !mbDxf, "sc", "Font::importDxfWeight - missing conditional formatting flag" );
    maModel.setBiffWeight( rStrm.readuInt16() );
    maUsedFlags.mbWeightUsed = true;
}

void Font::importDxfUnderline( SequenceInputStream& rStrm )
{
    SAL_WARN_IF( !mbDxf, "sc", "Font::importDxfUnderline - missing conditional formatting flag" );
    maModel.setBiffUnderline( rStrm.readuInt16() );
    maUsedFlags.mbUnderlineUsed = true;
}

void Font::importDxfEscapement( SequenceInputStream& rStrm )
{
    SAL_WARN_IF( !mbDxf, "sc", "Font::importDxfEscapement - missing conditional formatting flag" );
    maModel.setBiffEscapement( rStrm.readuInt16() );
    maUsedFlags.mbEscapementUsed = true;
}

void Font::importDxfFlag( sal_Int32 nElement, SequenceInputStream& rStrm )
{
    SAL_WARN_IF( !mbDxf, "sc", "Font::importDxfFlag - missing conditional formatting flag" );
    bool bFlag = rStrm.readuInt8() != 0;
    switch( nElement )
    {
        case XML_i:
            maModel.mbItalic = bFlag;
            maUsedFlags.mbPostureUsed = true;
        break;
        case XML_strike:
            maModel.mbStrikeout = bFlag;
            maUsedFlags.mbStrikeoutUsed = true;
        break;
        case XML_outline:
            maModel.mbOutline = bFlag;
            maUsedFlags.mbOutlineUsed = true;
        break;
        case XML_shadow:
            maModel.mbShadow = bFlag;
            maUsedFlags.mbShadowUsed = true;
        break;
        default:
            OSL_FAIL( "Font::importDxfFlag - unexpected element identifier" );
    }
}

void Font::finalizeImport()
{
    // font name
    maApiData.maDesc.Name = maModel.maName;

    // font family
    switch( maModel.mnFamily )
    {
        case OOX_FONTFAMILY_NONE:           maApiData.maDesc.Family = css::awt::FontFamily::DONTKNOW;     break;
        case OOX_FONTFAMILY_ROMAN:          maApiData.maDesc.Family = css::awt::FontFamily::ROMAN;        break;
        case OOX_FONTFAMILY_SWISS:          maApiData.maDesc.Family = css::awt::FontFamily::SWISS;        break;
        case OOX_FONTFAMILY_MODERN:         maApiData.maDesc.Family = css::awt::FontFamily::MODERN;       break;
        case OOX_FONTFAMILY_SCRIPT:         maApiData.maDesc.Family = css::awt::FontFamily::SCRIPT;       break;
        case OOX_FONTFAMILY_DECORATIVE:     maApiData.maDesc.Family = css::awt::FontFamily::DECORATIVE;   break;
    }

    // character set (API font descriptor uses rtl_TextEncoding in member CharSet!)
    if( (0 <= maModel.mnCharSet) && (maModel.mnCharSet <= SAL_MAX_UINT8) )
        maApiData.maDesc.CharSet = static_cast< sal_Int16 >(
            rtl_getTextEncodingFromWindowsCharset( static_cast< sal_uInt8 >( maModel.mnCharSet ) ) );

    // color, height, weight, slant, strikeout, outline, shadow
    maApiData.maComplexColor   = maModel.maColor.createComplexColor(getBaseFilter().getGraphicHelper(), -1);
    maApiData.mnColor          = maModel.maColor.getColor( getBaseFilter().getGraphicHelper() );
    maApiData.maDesc.Height    = static_cast< sal_Int16 >( maModel.mfHeight * 20.0 );
    maApiData.maDesc.Weight    = maModel.mbBold ? css::awt::FontWeight::BOLD : css::awt::FontWeight::NORMAL;
    maApiData.maDesc.Slant     = maModel.mbItalic ? css::awt::FontSlant_ITALIC : css::awt::FontSlant_NONE;
    maApiData.maDesc.Strikeout = maModel.mbStrikeout ? css::awt::FontStrikeout::SINGLE : css::awt::FontStrikeout::NONE;
    maApiData.mbOutline        = maModel.mbOutline;
    maApiData.mbShadow         = maModel.mbShadow;

    // underline
    switch( maModel.mnUnderline )
    {
        case XML_double:            maApiData.maDesc.Underline = css::awt::FontUnderline::DOUBLE; break;
        case XML_doubleAccounting:  maApiData.maDesc.Underline = css::awt::FontUnderline::DOUBLE; break;
        case XML_none:              maApiData.maDesc.Underline = css::awt::FontUnderline::NONE;   break;
        case XML_single:            maApiData.maDesc.Underline = css::awt::FontUnderline::SINGLE; break;
        case XML_singleAccounting:  maApiData.maDesc.Underline = css::awt::FontUnderline::SINGLE; break;
    }

    // escapement
    switch( maModel.mnEscapement )
    {
        case XML_baseline:
            maApiData.mnEscapement = API_ESCAPE_NONE;
            maApiData.mnEscapeHeight = API_ESCAPEHEIGHT_NONE;
        break;
        case XML_superscript:
            maApiData.mnEscapement = API_ESCAPE_SUPERSCRIPT;
            maApiData.mnEscapeHeight = API_ESCAPEHEIGHT_DEFAULT;
        break;
        case XML_subscript:
            maApiData.mnEscapement = API_ESCAPE_SUBSCRIPT;
            maApiData.mnEscapeHeight = API_ESCAPEHEIGHT_DEFAULT;
        break;
    }

    // supported script types
    if( !maUsedFlags.mbNameUsed )
        return;

    // For caching if the font has glyphs then the underline and strike-through don't matter.
    // Those aren't differences in the actual font, so just zero those out for the cache.
    // The weight and pitch are different faces though, and there are some fonts with glyphs
    // missing in one or other variant.
    css::awt::FontDescriptor aGlyphDesc = maApiData.maDesc;
    aGlyphDesc.Strikeout = css::awt::FontStrikeout::NONE;
    aGlyphDesc.Underline = css::awt::FontUnderline::NONE;
    // Never seen that to be the case for font *sizes* however, so we can use a uniform 10pt size
    aGlyphDesc.Height = 200;

    bool bHasAsian(false), bHasCmplx(false), bHasLatin(false);
    FontClassificationMap& rFontClassificationCache = getFontClassificationCache();
    if (auto found = rFontClassificationCache.find(aGlyphDesc); found != rFontClassificationCache.end())
    {
        FontClassification eClassification = found->second;
        bHasAsian = bool(eClassification & FontClassification::Asian);
        bHasCmplx = bool(eClassification & FontClassification::Cmplx);
        bHasLatin = bool(eClassification & FontClassification::Latin);
    }
    else
    {
        PropertySet aDocProps(( Reference< css::beans::XPropertySet >(getDocument()) ));
        Reference< XDevice > xDevice( aDocProps.getAnyProperty( PROP_ReferenceDevice ), UNO_QUERY );
        if( !xDevice.is() )
            return;

        Reference< XFont2 > xFont( xDevice->getFont(aGlyphDesc), UNO_QUERY );
        if( !xFont.is() )
            return;

        // #91658# CJK fonts
        bHasAsian =
            xFont->hasGlyphs( OUString( u'\x3041' ) ) ||    // 3040-309F: Hiragana
            xFont->hasGlyphs( OUString( u'\x30A1' ) ) ||    // 30A0-30FF: Katakana
            xFont->hasGlyphs( OUString( u'\x3111' ) ) ||    // 3100-312F: Bopomofo
            xFont->hasGlyphs( OUString( u'\x3131' ) ) ||    // 3130-318F: Hangul Compatibility Jamo
            xFont->hasGlyphs( OUString( u'\x3301' ) ) ||    // 3300-33FF: CJK Compatibility
            xFont->hasGlyphs( OUString( u'\x3401' ) ) ||    // 3400-4DBF: CJK Unified Ideographs Extension A
            xFont->hasGlyphs( OUString( u'\x4E01' ) ) ||    // 4E00-9FFF: CJK Unified Ideographs
            xFont->hasGlyphs( OUString( u'\x7E01' ) ) ||    // 4E00-9FFF: CJK Unified Ideographs
            xFont->hasGlyphs( OUString( u'\xA001' ) ) ||    // A001-A48F: Yi Syllables
            xFont->hasGlyphs( OUString( u'\xAC01' ) ) ||    // AC00-D7AF: Hangul Syllables
            xFont->hasGlyphs( OUString( u'\xCC01' ) ) ||    // AC00-D7AF: Hangul Syllables
            xFont->hasGlyphs( OUString( u'\xF901' ) ) ||    // F900-FAFF: CJK Compatibility Ideographs
            xFont->hasGlyphs( OUString( u'\xFF71' ) );      // FF00-FFEF: Halfwidth/Fullwidth Forms
        // #113783# CTL fonts
        bHasCmplx =
            xFont->hasGlyphs( OUString( u'\x05D1' ) ) ||    // 0590-05FF: Hebrew
            xFont->hasGlyphs( OUString( u'\x0631' ) ) ||    // 0600-06FF: Arabic
            xFont->hasGlyphs( OUString( u'\x0721' ) ) ||    // 0700-074F: Syriac
            xFont->hasGlyphs( OUString( u'\x0911' ) ) ||    // 0900-0DFF: Indic scripts
            xFont->hasGlyphs( OUString( u'\x0E01' ) ) ||    // 0E00-0E7F: Thai
            xFont->hasGlyphs( OUString( u'\xFB21' ) ) ||    // FB1D-FB4F: Hebrew Presentation Forms
            xFont->hasGlyphs( OUString( u'\xFB51' ) ) ||    // FB50-FDFF: Arabic Presentation Forms-A
            xFont->hasGlyphs( OUString( u'\xFE71' ) );      // FE70-FEFF: Arabic Presentation Forms-B
        // Western fonts
        bHasLatin =
            (!bHasAsian && !bHasCmplx) ||
            xFont->hasGlyphs( OUString( 'A' ) );

        FontClassification eClassification(FontClassification::None);
        if (bHasAsian)
            eClassification = eClassification | FontClassification::Asian;
        if (bHasCmplx)
            eClassification = eClassification | FontClassification::Cmplx;
        if (bHasLatin)
            eClassification = eClassification | FontClassification::Latin;
        rFontClassificationCache.emplace(aGlyphDesc, eClassification);
    }

    lclSetFontName( maApiData.maLatinFont, maApiData.maDesc, bHasLatin );
    lclSetFontName( maApiData.maAsianFont, maApiData.maDesc, bHasAsian );
    lclSetFontName( maApiData.maCmplxFont, maApiData.maDesc, bHasCmplx );
}

bool Font::needsRichTextFormat() const
{
    return maApiData.mnEscapement != API_ESCAPE_NONE;
}

static ::FontFamily lcl_getFontFamily( sal_Int32 nFamily )
{
    ::FontFamily eScFamily = FAMILY_DONTKNOW;
    switch( nFamily )
    {
        case css::awt::FontFamily::DONTKNOW:
            eScFamily = FAMILY_DONTKNOW;
            break;
        case css::awt::FontFamily::ROMAN:
            eScFamily = FAMILY_ROMAN;
            break;
        case css::awt::FontFamily::SWISS:
            eScFamily = FAMILY_SWISS;
            break;
        case css::awt::FontFamily::MODERN:
            eScFamily = FAMILY_MODERN;
            break;
        case css::awt::FontFamily::SCRIPT:
            eScFamily = FAMILY_SCRIPT;
            break;
        case css::awt::FontFamily::DECORATIVE:
            eScFamily = FAMILY_DECORATIVE;
            break;
    }
    return eScFamily;
}

void Font::fillToItemSet( SfxItemSet& rItemSet, bool bEditEngineText, bool bSkipPoolDefs ) const
{
    if ( maUsedFlags.mbNameUsed )
    {
        if( !maApiData.maLatinFont.maName.isEmpty() )
        {
            rtl_TextEncoding eFontEnc = maApiData.maLatinFont.mnTextEnc;
            // taken from binary importer
            rtl_TextEncoding eTempTextEnc = (bEditEngineText && (eFontEnc == getTextEncoding())) ?
                ScfTools::GetSystemTextEncoding() : eFontEnc;

            SvxFontItem aFontItem( lcl_getFontFamily( maApiData.maLatinFont.mnFamily ), maApiData.maLatinFont.maName, OUString(),
                PITCH_DONTKNOW, eTempTextEnc, ATTR_FONT );
            ScfTools::PutItem( rItemSet, aFontItem, bEditEngineText ? static_cast<sal_uInt16>(EE_CHAR_FONTINFO) : ATTR_FONT, bSkipPoolDefs );
        }
        if( !maApiData.maAsianFont.maName.isEmpty() )
        {
            rtl_TextEncoding eFontEnc = maApiData.maAsianFont.mnTextEnc;
            // taken from binary importer
            rtl_TextEncoding eTempTextEnc = (bEditEngineText && (eFontEnc == getTextEncoding())) ?
                ScfTools::GetSystemTextEncoding() : eFontEnc;
            SvxFontItem aFontItem( lcl_getFontFamily( maApiData.maAsianFont.mnFamily ), maApiData.maAsianFont.maName, OUString(),
                PITCH_DONTKNOW, eTempTextEnc, ATTR_FONT );
            ScfTools::PutItem( rItemSet, aFontItem, bEditEngineText ? static_cast<sal_uInt16>(EE_CHAR_FONTINFO_CJK) : ATTR_CJK_FONT, bSkipPoolDefs );
        }
        if( !maApiData.maCmplxFont.maName.isEmpty() )
        {
            rtl_TextEncoding eFontEnc = maApiData.maCmplxFont.mnTextEnc;
            // taken from binary importer
            rtl_TextEncoding eTempTextEnc = (bEditEngineText && (eFontEnc == getTextEncoding())) ?
                ScfTools::GetSystemTextEncoding() : eFontEnc;
            SvxFontItem aFontItem( lcl_getFontFamily( maApiData.maCmplxFont.mnFamily ), maApiData.maCmplxFont.maName, OUString(),
                PITCH_DONTKNOW, eTempTextEnc, ATTR_FONT );
            ScfTools::PutItem( rItemSet, aFontItem, bEditEngineText ? static_cast<sal_uInt16>(EE_CHAR_FONTINFO_CTL) : ATTR_CTL_FONT, bSkipPoolDefs );
        }
    }
    // font height
    if( maUsedFlags.mbHeightUsed )
    {
        sal_Int32 nHeight = maApiData.maDesc.Height;
        // do we use XclFontItemType::HeaderFooter ( or is it just relevant for the binary filter )
        if( bEditEngineText/* && (eType != XclFontItemType::HeaderFooter) */)     // do not convert header/footer height
            nHeight = convertTwipToMm100(nHeight);
        SvxFontHeightItem aHeightItem( nHeight, 100, ATTR_FONT_HEIGHT );
        ScfTools::PutItem( rItemSet, aHeightItem, bEditEngineText ? static_cast<sal_uInt16>(EE_CHAR_FONTHEIGHT) :  ATTR_FONT_HEIGHT, bSkipPoolDefs );
        ScfTools::PutItem( rItemSet, aHeightItem, bEditEngineText ? static_cast<sal_uInt16>(EE_CHAR_FONTHEIGHT_CJK) : ATTR_CJK_FONT_HEIGHT, bSkipPoolDefs );
        ScfTools::PutItem( rItemSet, aHeightItem, bEditEngineText ? static_cast<sal_uInt16>(EE_CHAR_FONTHEIGHT_CTL) : ATTR_CTL_FONT_HEIGHT, bSkipPoolDefs );
    }
    // font weight
    if( maUsedFlags.mbWeightUsed )
    {
        ::FontWeight fWeight = vcl::unohelper::ConvertFontWeight( maApiData.maDesc.Weight );
        SvxWeightItem aWeightItem( fWeight, ATTR_FONT_WEIGHT );
        ScfTools::PutItem( rItemSet, aWeightItem, bEditEngineText ? static_cast<sal_uInt16>(EE_CHAR_WEIGHT) : ATTR_FONT_WEIGHT, bSkipPoolDefs );
        ScfTools::PutItem( rItemSet, aWeightItem, bEditEngineText ? static_cast<sal_uInt16>(EE_CHAR_WEIGHT_CTL) : ATTR_CTL_FONT_WEIGHT, bSkipPoolDefs );
        ScfTools::PutItem( rItemSet, aWeightItem, bEditEngineText ? static_cast<sal_uInt16>(EE_CHAR_WEIGHT_CJK) : ATTR_CJK_FONT_WEIGHT, bSkipPoolDefs );
    }
    // font posture
    if( maUsedFlags.mbPostureUsed )
    {
        SvxPostureItem aPostItem( ( maApiData.maDesc.Slant == css::awt::FontSlant_ITALIC ) ? ITALIC_NORMAL :  ITALIC_NONE,  ATTR_FONT_POSTURE);
        ScfTools::PutItem( rItemSet, aPostItem, bEditEngineText ? static_cast<sal_uInt16>(EE_CHAR_ITALIC) : ATTR_FONT_POSTURE, bSkipPoolDefs );
        ScfTools::PutItem( rItemSet, aPostItem, bEditEngineText ? static_cast<sal_uInt16>(EE_CHAR_ITALIC_CJK) : ATTR_CJK_FONT_POSTURE, bSkipPoolDefs );
        ScfTools::PutItem( rItemSet, aPostItem, bEditEngineText ? static_cast<sal_uInt16>(EE_CHAR_ITALIC_CTL) : ATTR_CTL_FONT_POSTURE, bSkipPoolDefs );
    }
    // character color
    if( maUsedFlags.mbColorUsed )
    {
        ScfTools::PutItem( rItemSet,SvxColorItem( maApiData.mnColor, maApiData.maComplexColor, bEditEngineText ? static_cast<sal_uInt16>(EE_CHAR_COLOR) : ATTR_FONT_COLOR), bSkipPoolDefs );
    }
    // underline style
    if( maUsedFlags.mbUnderlineUsed )
    {
        FontLineStyle eScUnderl;
        if ( maApiData.maDesc.Underline == css::awt::FontUnderline::DOUBLE )
            eScUnderl = LINESTYLE_DOUBLE;
        else if ( maApiData.maDesc.Underline == css::awt::FontUnderline::SINGLE )
            eScUnderl = LINESTYLE_SINGLE;
        else
            eScUnderl = LINESTYLE_NONE;
        SvxUnderlineItem aUnderlItem( eScUnderl, ATTR_FONT_UNDERLINE );
        ScfTools::PutItem( rItemSet, aUnderlItem, bEditEngineText ? static_cast<sal_uInt16>(EE_CHAR_UNDERLINE) : ATTR_FONT_UNDERLINE, bSkipPoolDefs );
    }
    // strike out style
    if( maUsedFlags.mbStrikeoutUsed )
    {
        ScfTools::PutItem( rItemSet, SvxCrossedOutItem( maModel.mbStrikeout ? STRIKEOUT_SINGLE : STRIKEOUT_NONE, bEditEngineText ? static_cast<sal_uInt16>(EE_CHAR_STRIKEOUT) : ATTR_FONT_CROSSEDOUT ), bEditEngineText ? static_cast<sal_uInt16>(EE_CHAR_STRIKEOUT) : ATTR_FONT_CROSSEDOUT, bSkipPoolDefs );
    }

    // outline style
    if( maUsedFlags.mbOutlineUsed )
    {
        ScfTools::PutItem( rItemSet, SvxContourItem( maApiData.mbOutline, ATTR_FONT_CONTOUR ), bEditEngineText ? static_cast<sal_uInt16>(EE_CHAR_OUTLINE) : ATTR_FONT_CONTOUR, bSkipPoolDefs );
    }

    // shadow style
    if( maUsedFlags.mbShadowUsed )
    {
        ScfTools::PutItem( rItemSet, SvxShadowedItem( maApiData.mbShadow, ATTR_FONT_SHADOWED ), bEditEngineText ? static_cast<sal_uInt16>(EE_CHAR_SHADOW) : ATTR_FONT_SHADOWED, bSkipPoolDefs );
    }
    if( !maUsedFlags.mbEscapementUsed )
        return;

    SvxEscapement eScEscapem = SvxEscapement::Off;
    if ( maApiData.mnEscapement == API_ESCAPE_SUPERSCRIPT )
        eScEscapem = SvxEscapement::Superscript;
    else if ( maApiData.mnEscapement == API_ESCAPE_SUBSCRIPT )
        eScEscapem = SvxEscapement::Subscript;
    if( bEditEngineText )
    {
       // #TODO handle EscapementHeight
        rItemSet.Put( SvxEscapementItem( eScEscapem, EE_CHAR_ESCAPEMENT ) );
    }
}

void Font::writeToPropertyMap( PropertyMap& rPropMap ) const
{
    // font name properties
    if( maUsedFlags.mbNameUsed )
    {
        if( !maApiData.maLatinFont.maName.isEmpty() )
        {
            rPropMap.setProperty( PROP_CharFontName, maApiData.maLatinFont.maName);
            rPropMap.setProperty( PROP_CharFontFamily, maApiData.maLatinFont.mnFamily);
            rPropMap.setProperty( PROP_CharFontCharSet, maApiData.maLatinFont.mnTextEnc);
        }
        if( !maApiData.maAsianFont.maName.isEmpty() )
        {
            rPropMap.setProperty( PROP_CharFontNameAsian, maApiData.maAsianFont.maName);
            rPropMap.setProperty( PROP_CharFontFamilyAsian, maApiData.maAsianFont.mnFamily);
            rPropMap.setProperty( PROP_CharFontCharSetAsian, maApiData.maAsianFont.mnTextEnc);
        }
        if( !maApiData.maCmplxFont.maName.isEmpty() )
        {
            rPropMap.setProperty( PROP_CharFontNameComplex, maApiData.maCmplxFont.maName);
            rPropMap.setProperty( PROP_CharFontFamilyComplex, maApiData.maCmplxFont.mnFamily);
            rPropMap.setProperty( PROP_CharFontCharSetComplex, maApiData.maCmplxFont.mnTextEnc);
        }
    }
    // font height
    if( maUsedFlags.mbHeightUsed )
    {
        float fHeight = static_cast< float >( maApiData.maDesc.Height / 20.0 ); // twips to points
        rPropMap.setProperty( PROP_CharHeight, fHeight);
        rPropMap.setProperty( PROP_CharHeightAsian, fHeight);
        rPropMap.setProperty( PROP_CharHeightComplex, fHeight);
    }
    // font weight
    if( maUsedFlags.mbWeightUsed )
    {
        float fWeight = maApiData.maDesc.Weight;
        rPropMap.setProperty( PROP_CharWeight, fWeight);
        rPropMap.setProperty( PROP_CharWeightAsian, fWeight);
        rPropMap.setProperty( PROP_CharWeightComplex, fWeight);
    }
    // font posture
    if( maUsedFlags.mbPostureUsed )
    {
        rPropMap.setProperty( PROP_CharPosture, maApiData.maDesc.Slant);
        rPropMap.setProperty( PROP_CharPostureAsian, maApiData.maDesc.Slant);
        rPropMap.setProperty( PROP_CharPostureComplex, maApiData.maDesc.Slant);
    }
    // character color
    if( maUsedFlags.mbColorUsed )
        rPropMap.setProperty( PROP_CharColor, maApiData.mnColor);
    // underline style
    if( maUsedFlags.mbUnderlineUsed )
        rPropMap.setProperty( PROP_CharUnderline, maApiData.maDesc.Underline);
    // strike out style
    if( maUsedFlags.mbStrikeoutUsed )
        rPropMap.setProperty( PROP_CharStrikeout, maApiData.maDesc.Strikeout);
    // outline style
    if( maUsedFlags.mbOutlineUsed )
        rPropMap.setProperty( PROP_CharContoured, maApiData.mbOutline);
    // shadow style
    if( maUsedFlags.mbShadowUsed )
        rPropMap.setProperty( PROP_CharShadowed, maApiData.mbShadow);
    // escapement
    if( maUsedFlags.mbEscapementUsed )
    {
        rPropMap.setProperty( PROP_CharEscapement, maApiData.mnEscapement);
        rPropMap.setProperty( PROP_CharEscapementHeight, maApiData.mnEscapeHeight);
    }
}

void Font::writeToPropertySet( PropertySet& rPropSet ) const
{
    PropertyMap aPropMap;
    writeToPropertyMap( aPropMap );
    rPropSet.setProperties( aPropMap );
}

AlignmentModel::AlignmentModel() :
    mnHorAlign( XML_general ),
    mnVerAlign( XML_bottom ),
    mnTextDir( OOX_XF_TEXTDIR_CONTEXT ),
    mnRotation( OOX_XF_ROTATION_NONE ),
    mnIndent( OOX_XF_INDENT_NONE ),
    mbWrapText( false ),
    mbShrink( false ),
    mbJustLastLine( false )
{
}

void AlignmentModel::setBiffHorAlign( sal_uInt8 nHorAlign )
{
    static const sal_Int32 spnHorAligns[] = {
        XML_general, XML_left, XML_center, XML_right,
        XML_fill, XML_justify, XML_centerContinuous, XML_distributed };
    mnHorAlign = STATIC_ARRAY_SELECT( spnHorAligns, nHorAlign, XML_general );
}

void AlignmentModel::setBiffVerAlign( sal_uInt8 nVerAlign )
{
    static const sal_Int32 spnVerAligns[] = {
        XML_top, XML_center, XML_bottom, XML_justify, XML_distributed };
    mnVerAlign = STATIC_ARRAY_SELECT( spnVerAligns, nVerAlign, XML_bottom );
}

ApiAlignmentData::ApiAlignmentData() :
    meHorJustify( css::table::CellHoriJustify_STANDARD ),
    mnHorJustifyMethod( css::table::CellJustifyMethod::AUTO ),
    mnVerJustify( css::table::CellVertJustify2::STANDARD ),
    mnVerJustifyMethod( css::table::CellJustifyMethod::AUTO ),
    meOrientation( css::table::CellOrientation_STANDARD ),
    mnRotation( 0 ),
    mnWritingMode( css::text::WritingMode2::PAGE ),
    mnIndent( 0 ),
    mbWrapText( false ),
    mbShrink( false )
{
}

bool operator==( const ApiAlignmentData& rLeft, const ApiAlignmentData& rRight )
{
    return
        (rLeft.meHorJustify  == rRight.meHorJustify) &&
        (rLeft.mnHorJustifyMethod == rRight.mnHorJustifyMethod) &&
        (rLeft.mnVerJustify  == rRight.mnVerJustify) &&
        (rLeft.mnVerJustifyMethod == rRight.mnVerJustifyMethod) &&
        (rLeft.meOrientation == rRight.meOrientation) &&
        (rLeft.mnRotation    == rRight.mnRotation) &&
        (rLeft.mnWritingMode == rRight.mnWritingMode) &&
        (rLeft.mnIndent      == rRight.mnIndent) &&
        (rLeft.mbWrapText    == rRight.mbWrapText) &&
        (rLeft.mbShrink      == rRight.mbShrink);
}

Alignment::Alignment( const WorkbookHelper& rHelper ) :
    WorkbookHelper( rHelper )
{
}

void Alignment::importAlignment( const AttributeList& rAttribs )
{
    maModel.mnVerAlign     = rAttribs.getToken( XML_vertical, XML_bottom );
    maModel.mnTextDir      = rAttribs.getInteger( XML_readingOrder, OOX_XF_TEXTDIR_CONTEXT );
    maModel.mnRotation     = rAttribs.getInteger( XML_textRotation, OOX_XF_ROTATION_NONE );
    sal_Int32 nDefaultHorAlign = XML_general;
    if (maModel.mnRotation != OOX_XF_ROTATION_NONE)
    {
        if (maModel.mnRotation < 90 || maModel.mnRotation == 180)
        {
            nDefaultHorAlign = XML_left;
        }
        else
        {
            nDefaultHorAlign = XML_right;
        }
    }
    maModel.mnHorAlign     = rAttribs.getToken( XML_horizontal, nDefaultHorAlign );
    maModel.mnIndent       = rAttribs.getInteger( XML_indent, OOX_XF_INDENT_NONE );
    maModel.mbWrapText     = rAttribs.getBool( XML_wrapText, false );
    maModel.mbShrink       = rAttribs.getBool( XML_shrinkToFit, false );
    maModel.mbJustLastLine = rAttribs.getBool( XML_justifyLastLine, false );
}

void Alignment::setBiff12Data( sal_uInt32 nFlags )
{
    maModel.setBiffHorAlign( extractValue< sal_uInt8 >( nFlags, 16, 3 ) );
    maModel.setBiffVerAlign( extractValue< sal_uInt8 >( nFlags, 19, 3 ) );
    maModel.mnTextDir      = extractValue< sal_Int32 >( nFlags, 26, 2 );
    maModel.mnRotation     = extractValue< sal_Int32 >( nFlags, 0, 8 );
    maModel.mnIndent       = extractValue< sal_uInt8 >( nFlags, 8, 8 );
    maModel.mbWrapText     = getFlag( nFlags, BIFF12_XF_WRAPTEXT );
    maModel.mbShrink       = getFlag( nFlags, BIFF12_XF_SHRINK );
    maModel.mbJustLastLine = getFlag( nFlags, BIFF12_XF_JUSTLASTLINE );
}

void Alignment::finalizeImport()
{
    // horizontal alignment
    switch( maModel.mnHorAlign )
    {
        case XML_center:            maApiData.meHorJustify = css::table::CellHoriJustify_CENTER;    break;
        case XML_centerContinuous:  maApiData.meHorJustify = css::table::CellHoriJustify_CENTER;    break;
        case XML_distributed:       maApiData.meHorJustify = css::table::CellHoriJustify_BLOCK;     break;
        case XML_fill:              maApiData.meHorJustify = css::table::CellHoriJustify_REPEAT;    break;
        case XML_general:           maApiData.meHorJustify = css::table::CellHoriJustify_STANDARD;  break;
        case XML_justify:           maApiData.meHorJustify = css::table::CellHoriJustify_BLOCK;     break;
        case XML_left:              maApiData.meHorJustify = css::table::CellHoriJustify_LEFT;      break;
        case XML_right:             maApiData.meHorJustify = css::table::CellHoriJustify_RIGHT;     break;
    }

    if (maModel.mnHorAlign == XML_distributed)
        maApiData.mnHorJustifyMethod = css::table::CellJustifyMethod::DISTRIBUTE;

    // vertical alignment
    switch( maModel.mnVerAlign )
    {
        case XML_bottom:        maApiData.mnVerJustify = css::table::CellVertJustify2::BOTTOM;    break;
        case XML_center:        maApiData.mnVerJustify = css::table::CellVertJustify2::CENTER;    break;
        case XML_distributed:   maApiData.mnVerJustify = css::table::CellVertJustify2::BLOCK;     break;
        case XML_justify:       maApiData.mnVerJustify = css::table::CellVertJustify2::BLOCK;     break;
        case XML_top:           maApiData.mnVerJustify = css::table::CellVertJustify2::TOP;       break;
    }

    if (maModel.mnVerAlign == XML_distributed)
        maApiData.mnVerJustifyMethod = css::table::CellJustifyMethod::DISTRIBUTE;

    /*  indentation: expressed as number of blocks of 3 space characters in
        OOXML. */
    UnitConverter& rUnitConverter = getUnitConverter();
    // Note: indents are stored in twips
    sal_Int32 nIndent = rUnitConverter.scaleValue( 3.0 * maModel.mnIndent, Unit::Space, Unit::Twip);
    if( (0 <= nIndent) && (nIndent <= SAL_MAX_INT16) )
        maApiData.mnIndent = static_cast< sal_Int16 >( nIndent );

    // complex text direction
    switch( maModel.mnTextDir )
    {
        case OOX_XF_TEXTDIR_CONTEXT:    maApiData.mnWritingMode = css::text::WritingMode2::PAGE;   break;
        case OOX_XF_TEXTDIR_LTR:        maApiData.mnWritingMode = css::text::WritingMode2::LR_TB;  break;
        case OOX_XF_TEXTDIR_RTL:        maApiData.mnWritingMode = css::text::WritingMode2::RL_TB;  break;
    }

    // rotation: 0-90 means 0 to 90 degrees ccw, 91-180 means 1 to 90 degrees cw, 255 means stacked
    sal_Int32 nOoxRot = maModel.mnRotation;
    maApiData.mnRotation = Degree100(((0 <= nOoxRot) && (nOoxRot <= 90)) ?
        (100 * nOoxRot) :
        (((91 <= nOoxRot) && (nOoxRot <= 180)) ? (100 * (450 - nOoxRot)) : 0));

    // "Orientation" property used for character stacking
    maApiData.meOrientation = (nOoxRot == OOX_XF_ROTATION_STACKED) ?
        css::table::CellOrientation_STACKED : css::table::CellOrientation_STANDARD;

    // alignment flags (#i84960 automatic line break, if vertically justified/distributed)
    maApiData.mbWrapText = maModel.mbWrapText || (maModel.mnVerAlign == XML_distributed) || (maModel.mnVerAlign == XML_justify);
    maApiData.mbShrink = maModel.mbShrink;

}

::SvxCellVerJustify Alignment::GetScVerAlign() const
{
    ::SvxCellVerJustify nVert = ::SvxCellVerJustify::Standard;
    switch ( maApiData.mnVerJustify )
    {
        case css::table::CellVertJustify2::BOTTOM:
            nVert = ::SvxCellVerJustify::Bottom;
            break;
        case css::table::CellVertJustify2::CENTER:
            nVert = ::SvxCellVerJustify::Center;
            break;
        case css::table::CellVertJustify2::TOP:
            nVert = ::SvxCellVerJustify::Top;
            break;
        case css::table::CellVertJustify2::BLOCK:
            nVert = ::SvxCellVerJustify::Block;
            break;
        case css::table::CellVertJustify2::STANDARD:
        default:
            nVert = ::SvxCellVerJustify::Standard;
            break;
    }
    return nVert;
}

::SvxCellHorJustify Alignment::GetScHorAlign() const
{
    ::SvxCellHorJustify nHori = ::SvxCellHorJustify::Standard;
    switch( maApiData.meHorJustify )
    {
        case css::table::CellHoriJustify_LEFT:
            nHori = ::SvxCellHorJustify::Left;
            break;
        case css::table::CellHoriJustify_CENTER:
            nHori = ::SvxCellHorJustify::Center;
            break;
        case css::table::CellHoriJustify_RIGHT:
            nHori = ::SvxCellHorJustify::Right;
            break;
        case css::table::CellHoriJustify_BLOCK:
            nHori = ::SvxCellHorJustify::Block;
            break;
        case css::table::CellHoriJustify_REPEAT:
            nHori = ::SvxCellHorJustify::Repeat;
            break;
        case css::table::CellHoriJustify_STANDARD:
        default:
            nHori = ::SvxCellHorJustify::Standard;
            break;
    }
    return nHori;
}

SvxFrameDirection Alignment::GetScFrameDir() const
{
    SvxFrameDirection eFrameDir = SvxFrameDirection::Environment;
    switch( maApiData.mnWritingMode )
    {
        case css::text::WritingMode2::PAGE:
            eFrameDir = SvxFrameDirection::Environment;
            break;
        case css::text::WritingMode2::LR_TB:
            eFrameDir = SvxFrameDirection::Horizontal_LR_TB;
            break;
        case css::text::WritingMode2::RL_TB:
            eFrameDir = SvxFrameDirection::Horizontal_RL_TB;
            break;
        default:
            OSL_FAIL( "GetScFrameDir - unknown CTL text direction" );
    }
    return eFrameDir;
}

void Alignment::fillToItemSet( SfxItemSet& rItemSet, bool bSkipPoolDefs ) const
{
    // horizontal alignment
    ScfTools::PutItem( rItemSet, SvxHorJustifyItem( GetScHorAlign(), ATTR_HOR_JUSTIFY ), bSkipPoolDefs );
    ScfTools::PutItem( rItemSet, SvxJustifyMethodItem( ( maApiData.mnHorJustifyMethod == css::table::CellJustifyMethod::DISTRIBUTE ) ? ::SvxCellJustifyMethod::Distribute : ::SvxCellJustifyMethod::Auto, ATTR_HOR_JUSTIFY_METHOD ), bSkipPoolDefs );
    ScfTools::PutItem( rItemSet, SvxVerJustifyItem( GetScVerAlign(), ATTR_VER_JUSTIFY ), bSkipPoolDefs );
    // vertical alignment
    ScfTools::PutItem( rItemSet, SvxJustifyMethodItem( ( maApiData.mnVerJustifyMethod == css::table::CellJustifyMethod::DISTRIBUTE ) ? ::SvxCellJustifyMethod::Distribute : ::SvxCellJustifyMethod::Auto, ATTR_VER_JUSTIFY_METHOD ), bSkipPoolDefs );

    // CTL text direction
    ScfTools::PutItem( rItemSet, SvxFrameDirectionItem( GetScFrameDir(), ATTR_WRITINGDIR ), bSkipPoolDefs );
    // set an angle in the range from -90 to 90 degrees
    ScfTools::PutItem( rItemSet, ScRotateValueItem( maApiData.mnRotation ), bSkipPoolDefs );
    // Orientation
    ScfTools::PutItem( rItemSet, ScVerticalStackCell( maApiData.meOrientation == css::table::CellOrientation_STACKED ), bSkipPoolDefs );
    // indent
    ScfTools::PutItem( rItemSet, ScIndentItem( maApiData.mnIndent ), bSkipPoolDefs );
    // line wrap
    ScfTools::PutItem( rItemSet, ScLineBreakCell( maApiData.mbWrapText ), bSkipPoolDefs );
    ScfTools::PutItem( rItemSet, ScShrinkToFitCell( maApiData.mbShrink ), bSkipPoolDefs );
}

ProtectionModel::ProtectionModel() :
    mbLocked( true ),   // default in Excel and Calc
    mbHidden( false )
{
}

ApiProtectionData::ApiProtectionData() :
    maCellProt( true, false, false, false )
{
}

bool operator==( const ApiProtectionData& rLeft, const ApiProtectionData& rRight )
{
    return
        (rLeft.maCellProt.IsLocked        == rRight.maCellProt.IsLocked) &&
        (rLeft.maCellProt.IsFormulaHidden == rRight.maCellProt.IsFormulaHidden) &&
        (rLeft.maCellProt.IsHidden        == rRight.maCellProt.IsHidden) &&
        (rLeft.maCellProt.IsPrintHidden   == rRight.maCellProt.IsPrintHidden);
}

Protection::Protection( const WorkbookHelper& rHelper, bool bDxf ) :
    WorkbookHelper( rHelper ),
    mbDxf( bDxf )
{
}

void Protection::importProtection( const AttributeList& rAttribs )
{
    maModel.mbLocked = rAttribs.getBool( XML_locked, true );
    maModel.mbHidden = rAttribs.getBool( XML_hidden, false );
}

void Protection::importDxfProtection( sal_Int32 nElement, SequenceInputStream& rStrm )
{
    SAL_WARN_IF( !mbDxf, "sc", "Protection::importDxfProtection - missing protection flag" );
    bool bFlag = rStrm.readuInt8() != 0;
    switch( nElement )
    {
        case XML_locked:
            maModel.mbLocked = bFlag;
        break;
        case XML_hidden:
            maModel.mbHidden = bFlag;
        break;
        default:
            OSL_FAIL( "Protection::importDxfProtection - unexpected element identifier" );
    }
}

void Protection::setBiff12Data( sal_uInt32 nFlags )
{
    maModel.mbLocked = getFlag( nFlags, BIFF12_XF_LOCKED );
    maModel.mbHidden = getFlag( nFlags, BIFF12_XF_HIDDEN );
}

void Protection::finalizeImport()
{
    maApiData.maCellProt.IsLocked = maModel.mbLocked;
    maApiData.maCellProt.IsFormulaHidden = maModel.mbHidden;
}

void Protection::fillToItemSet( SfxItemSet& rItemSet, bool bSkipPoolDefs ) const
{
    ScfTools::PutItem( rItemSet, ScProtectionAttr( maApiData.maCellProt.IsLocked, maApiData.maCellProt.IsFormulaHidden ), bSkipPoolDefs );
}

namespace {

bool lcl_isBorder(const css::table::BorderLine& rBorder)
{
    return (rBorder.InnerLineWidth > 0) || (rBorder.OuterLineWidth > 0);
}

}

BorderLineModel::BorderLineModel( bool bDxf ) :
    mnStyle( XML_none ),
    mbUsed( !bDxf )
{
    maColor.setIndexed( OOX_COLOR_WINDOWTEXT );
}

void BorderLineModel::setBiffStyle( sal_Int32 nLineStyle )
{
    static const sal_Int32 spnStyleIds[] = {
        XML_none, XML_thin, XML_medium, XML_dashed,
        XML_dotted, XML_thick, XML_double, XML_hair,
        XML_mediumDashed, XML_dashDot, XML_mediumDashDot, XML_dashDotDot,
        XML_mediumDashDotDot, XML_slantDashDot };
    mnStyle = STATIC_ARRAY_SELECT( spnStyleIds, nLineStyle, XML_none );
}

bool BorderLineModel::operator==(const BorderLineModel& rhs) const
{
    if (maColor != rhs.maColor)
        return false;
    if (mnStyle != rhs.mnStyle)
        return false;
    if (mbUsed != rhs.mbUsed)
        return false;
    return true;
}

BorderModel::BorderModel( bool bDxf ) :
    maLeft( bDxf ),
    maRight( bDxf ),
    maTop( bDxf ),
    maBottom( bDxf ),
    maDiagonal( bDxf ),
    mbDiagTLtoBR( false ),
    mbDiagBLtoTR( false )
{
}

ApiBorderData::ApiBorderData() :
    mbBorderUsed( false ),
    mbDiagUsed( false )
{
}

bool ApiBorderData::hasAnyOuterBorder() const
{
    return
        ( lcl_isBorder( maTop )    &&  maTop.OuterLineWidth > 0   ) ||
        ( lcl_isBorder( maBottom ) && maBottom.OuterLineWidth > 0 ) ||
        ( lcl_isBorder( maLeft )   && maLeft.OuterLineWidth > 0   ) ||
        ( lcl_isBorder( maRight )  && maRight.OuterLineWidth > 0  );
}

namespace {

void lclSetBorderLineWidth( BorderLine& rBorderLine,
        sal_Int16 nOuter, sal_Int16 nDist = API_LINE_NONE, sal_Int16 nInner = API_LINE_NONE )
{
    rBorderLine.OuterLineWidth = nOuter;
    rBorderLine.LineDistance = nDist;
    rBorderLine.InnerLineWidth = nInner;
}

} // namespace

Border::Border( const WorkbookHelper& rHelper, bool bDxf ) :
    WorkbookHelper( rHelper ),
    maModel( bDxf ),
    mbDxf( bDxf )
{
}

void Border::importBorder( const AttributeList& rAttribs )
{
    maModel.mbDiagTLtoBR = rAttribs.getBool( XML_diagonalDown, false );
    maModel.mbDiagBLtoTR = rAttribs.getBool( XML_diagonalUp, false );
}

void Border::importStyle( sal_Int32 nElement, const AttributeList& rAttribs )
{
    if( BorderLineModel* pBorderLine = getBorderLine( nElement ) )
    {
        pBorderLine->mnStyle = rAttribs.getToken( XML_style, XML_none );
        pBorderLine->mbUsed = true;
    }
}

void Border::importColor( sal_Int32 nElement, const AttributeList& rAttribs )
{
    if( BorderLineModel* pBorderLine = getBorderLine( nElement ) )
        pBorderLine->maColor.importColor( rAttribs );
}

void Border::importBorder( SequenceInputStream& rStrm )
{
    sal_uInt8 nFlags = rStrm.readuInt8();
    maModel.mbDiagTLtoBR = getFlag( nFlags, BIFF12_BORDER_DIAG_TLBR );
    maModel.mbDiagBLtoTR = getFlag( nFlags, BIFF12_BORDER_DIAG_BLTR );
    maModel.maTop.setBiffStyle( rStrm.readuInt16() );
    rStrm >> maModel.maTop.maColor;
    maModel.maBottom.setBiffStyle( rStrm.readuInt16() );
    rStrm >> maModel.maBottom.maColor;
    maModel.maLeft.setBiffStyle( rStrm.readuInt16() );
    rStrm >> maModel.maLeft.maColor;
    maModel.maRight.setBiffStyle( rStrm.readuInt16() );
    rStrm >> maModel.maRight.maColor;
    maModel.maDiagonal.setBiffStyle( rStrm.readuInt16() );
    rStrm >> maModel.maDiagonal.maColor;
}

void Border::importDxfBorder( sal_Int32 nElement, SequenceInputStream& rStrm )
{
    SAL_WARN_IF( !mbDxf, "sc", "Border::importDxfBorder - missing conditional formatting flag" );
    if( BorderLineModel* pBorderLine = getBorderLine( nElement ) )
    {
        sal_uInt16 nStyle;
        rStrm >> pBorderLine->maColor;
        nStyle = rStrm.readuInt16();
        pBorderLine->setBiffStyle( nStyle );
        pBorderLine->mbUsed = true;
    }
}

void Border::finalizeImport( bool bRTL )
{
    if (bRTL)
        std::swap(maModel.maLeft, maModel.maRight);

    maApiData.mbBorderUsed = maModel.maLeft.mbUsed || maModel.maRight.mbUsed || maModel.maTop.mbUsed || maModel.maBottom.mbUsed;
    maApiData.mbDiagUsed   = maModel.maDiagonal.mbUsed;

    convertBorderLine( maApiData.maLeft,   maModel.maLeft );
    convertBorderLine( maApiData.maRight,  maModel.maRight );
    convertBorderLine( maApiData.maTop,    maModel.maTop );
    convertBorderLine( maApiData.maBottom, maModel.maBottom );

    maApiData.maComplexColorLeft = maModel.maLeft.maColor.createComplexColor(getBaseFilter().getGraphicHelper(), -1);
    maApiData.maComplexColorRight = maModel.maRight.maColor.createComplexColor(getBaseFilter().getGraphicHelper(), -1);
    maApiData.maComplexColorTop = maModel.maTop.maColor.createComplexColor(getBaseFilter().getGraphicHelper(), -1);
    maApiData.maComplexColorBottom = maModel.maBottom.maColor.createComplexColor(getBaseFilter().getGraphicHelper(), -1);

    if( maModel.mbDiagTLtoBR )
        convertBorderLine( maApiData.maTLtoBR, maModel.maDiagonal );
    if( maModel.mbDiagBLtoTR )
        convertBorderLine( maApiData.maBLtoTR, maModel.maDiagonal );
}

void Border::fillToItemSet( SfxItemSet& rItemSet, bool bSkipPoolDefs ) const
{
    if( maApiData.mbBorderUsed )
    {
         SvxBoxItem aBoxItem( ATTR_BORDER );
         editeng::SvxBorderLine aLine;

         if (SvxBoxItem::LineToSvxLine(maApiData.maLeft, aLine, false))
         {
             aLine.setComplexColor(maApiData.maComplexColorLeft);
             aBoxItem.SetLine( &aLine, SvxBoxItemLine::LEFT );
         }
         if (SvxBoxItem::LineToSvxLine(maApiData.maRight, aLine, false))
         {
             aLine.setComplexColor(maApiData.maComplexColorRight);
             aBoxItem.SetLine( &aLine, SvxBoxItemLine::RIGHT );
         }
         if (SvxBoxItem::LineToSvxLine(maApiData.maTop, aLine, false))
         {
             aLine.setComplexColor(maApiData.maComplexColorTop);
             aBoxItem.SetLine( &aLine, SvxBoxItemLine::TOP );
         }
         if (SvxBoxItem::LineToSvxLine(maApiData.maBottom, aLine, false))
         {
             aLine.setComplexColor(maApiData.maComplexColorBottom);
             aBoxItem.SetLine( &aLine, SvxBoxItemLine::BOTTOM );
         }
         ScfTools::PutItem( rItemSet, aBoxItem, bSkipPoolDefs );
    }
    if ( !maApiData.mbDiagUsed )
        return;

    SvxLineItem aTLBRItem( ATTR_BORDER_TLBR );
    SvxLineItem aBLTRItem( ATTR_BORDER_BLTR );
    ::editeng::SvxBorderLine aLine;
    if (SvxBoxItem::LineToSvxLine(maApiData.maTLtoBR, aLine, false))
    {
        aTLBRItem.SetLine( &aLine );
    }
    if (SvxBoxItem::LineToSvxLine(maApiData.maBLtoTR, aLine, false))
    {
        aBLTRItem.SetLine( &aLine );
    }
    ScfTools::PutItem( rItemSet, aTLBRItem, bSkipPoolDefs );
    ScfTools::PutItem( rItemSet, aBLTRItem, bSkipPoolDefs );
}

BorderLineModel* Border::getBorderLine( sal_Int32 nElement )
{
    switch( nElement )
    {
        case XLS_TOKEN( left ):     return &maModel.maLeft;
        case XLS_TOKEN( start ):     return &maModel.maLeft;
        case XLS_TOKEN( right ):    return &maModel.maRight;
        case XLS_TOKEN( end ):    return &maModel.maRight;
        case XLS_TOKEN( top ):      return &maModel.maTop;
        case XLS_TOKEN( bottom ):   return &maModel.maBottom;
        case XLS_TOKEN( diagonal ): return &maModel.maDiagonal;
    }
    return nullptr;
}

bool Border::convertBorderLine( BorderLine2& rBorderLine, const BorderLineModel& rModel )
{
    // Document: sc/qa/unit/data/README.cellborders

    rBorderLine.Color = sal_Int32(rModel.maColor.getColor( getBaseFilter().getGraphicHelper(), API_RGB_BLACK ));
    switch( rModel.mnStyle )
    {
        case XML_dashDot:
            lclSetBorderLineWidth( rBorderLine, API_LINE_THIN );
            rBorderLine.LineStyle = BorderLineStyle::DASH_DOT;
        break;
        case XML_dashDotDot:
            lclSetBorderLineWidth( rBorderLine, API_LINE_THIN );
            rBorderLine.LineStyle = BorderLineStyle::DASH_DOT_DOT;
        break;
        case XML_dashed:
            lclSetBorderLineWidth( rBorderLine, API_LINE_THIN );
            rBorderLine.LineStyle = BorderLineStyle::FINE_DASHED;
        break;
        case XML_dotted:
            lclSetBorderLineWidth( rBorderLine, API_LINE_THIN );
            rBorderLine.LineStyle = BorderLineStyle::DOTTED;
        break;
        case XML_double:
            lclSetBorderLineWidth( rBorderLine, 10, 15, 10 );
            rBorderLine.LineStyle = BorderLineStyle::DOUBLE_THIN;
        break;
        case XML_hair:              lclSetBorderLineWidth( rBorderLine, API_LINE_HAIR );    break;
        case XML_medium:            lclSetBorderLineWidth( rBorderLine, API_LINE_MEDIUM );  break;
        case XML_mediumDashDot:
            lclSetBorderLineWidth( rBorderLine, API_LINE_MEDIUM );
            rBorderLine.LineStyle = BorderLineStyle::DASH_DOT;
            break;
        case XML_mediumDashDotDot:
            lclSetBorderLineWidth( rBorderLine, API_LINE_MEDIUM );
            rBorderLine.LineStyle = BorderLineStyle::DASH_DOT_DOT;
            break;
        case XML_mediumDashed:
            lclSetBorderLineWidth( rBorderLine, API_LINE_MEDIUM );
            rBorderLine.LineStyle = BorderLineStyle::DASHED;
        break;
        case XML_none:              lclSetBorderLineWidth( rBorderLine, API_LINE_NONE );    break;
        case XML_slantDashDot:
            lclSetBorderLineWidth( rBorderLine, API_LINE_MEDIUM );
            rBorderLine.LineStyle = BorderLineStyle::FINE_DASHED;
            break;
        case XML_thick:             lclSetBorderLineWidth( rBorderLine, API_LINE_THICK );   break;
        case XML_thin:              lclSetBorderLineWidth( rBorderLine, API_LINE_THIN );    break;
        default:                    lclSetBorderLineWidth( rBorderLine, API_LINE_NONE );    break;
    }
    return rModel.mbUsed;
}

PatternFillModel::PatternFillModel( bool bDxf ) :
    mnPattern( XML_none ),
    mbPattColorUsed( !bDxf ),
    mbFillColorUsed( !bDxf ),
    mbPatternUsed( !bDxf )
{
    maPatternColor.setIndexed( OOX_COLOR_WINDOWTEXT );
    maFilterPatternColor.setIndexed( OOX_COLOR_WINDOWTEXT );
    maFillColor.setIndexed( OOX_COLOR_WINDOWBACK );
}

void PatternFillModel::setBiffPattern( sal_Int32 nPattern )
{
    static const sal_Int32 spnPatternIds[] = {
        XML_none, XML_solid, XML_mediumGray, XML_darkGray,
        XML_lightGray, XML_darkHorizontal, XML_darkVertical, XML_darkDown,
        XML_darkUp, XML_darkGrid, XML_darkTrellis, XML_lightHorizontal,
        XML_lightVertical, XML_lightDown, XML_lightUp, XML_lightGrid,
        XML_lightTrellis, XML_gray125, XML_gray0625 };
    mnPattern = STATIC_ARRAY_SELECT( spnPatternIds, nPattern, XML_none );
}

bool PatternFillModel::operator==(const PatternFillModel& rhs) const
{
    if (maPatternColor != rhs.maPatternColor)
        return false;
    if (maFilterPatternColor != rhs.maFilterPatternColor)
        return false;
    if (maFillColor != rhs.maFillColor)
        return false;
    if (mnPattern != rhs.mnPattern)
        return false;
    if (mbPattColorUsed != rhs.mbPattColorUsed)
        return false;
    if (mbFillColorUsed != rhs.mbFillColorUsed)
        return false;
    if (mbPatternUsed != rhs.mbPatternUsed)
        return false;
    return true;
}

GradientFillModel::GradientFillModel() :
    mnType( XML_linear ),
    mfAngle( 0.0 ),
    mfLeft( 0.0 ),
    mfRight( 0.0 ),
    mfTop( 0.0 ),
    mfBottom( 0.0 )
{
}

void GradientFillModel::readGradient( SequenceInputStream& rStrm )
{
    sal_Int32 nType;
    nType = rStrm.readInt32();
    mfAngle = rStrm.readDouble();
    mfLeft = rStrm.readDouble();
    mfRight = rStrm.readDouble();
    mfTop = rStrm.readDouble();
    mfBottom = rStrm.readDouble();
    static const sal_Int32 spnTypes[] = { XML_linear, XML_path };
    mnType = STATIC_ARRAY_SELECT( spnTypes, nType, XML_TOKEN_INVALID );
}

void GradientFillModel::readGradientStop( SequenceInputStream& rStrm, bool bDxf )
{
    XlsColor aColor;
    double fPosition;
    if( bDxf )
    {
        rStrm.skip( 2 );
        fPosition = rStrm.readDouble();
        rStrm >> aColor;
    }
    else
    {
        rStrm >> aColor;
        fPosition = rStrm.readDouble();
    }
    if( !rStrm.isEof() && (fPosition >= 0.0) )
        maColors[ fPosition ] = std::move(aColor);
}

ApiSolidFillData::ApiSolidFillData() :
    mnColor( API_RGB_TRANSPARENT ),
    mnFilterColor( API_RGB_TRANSPARENT ),
    mbTransparent( true ),
    mbUsed( false )
{
}

namespace {

sal_Int32 lclGetMixedColorComp( sal_Int32 nPatt, sal_Int32 nFill, sal_Int32 nAlpha )
{
    return ((nPatt - nFill) * nAlpha) / 0x80 + nFill;
}

::Color lclGetMixedColor( ::Color nPattColor, ::Color nFillColor, sal_Int32 nAlpha )
{
    return ::Color(
        lclGetMixedColorComp( nPattColor.GetRed(), nFillColor.GetRed(), nAlpha ),
        lclGetMixedColorComp( nPattColor.GetGreen(), nFillColor.GetGreen(), nAlpha ),
        lclGetMixedColorComp( nPattColor.GetBlue(), nFillColor.GetBlue(), nAlpha ) );
}

} // namespace

Fill::Fill( const WorkbookHelper& rHelper, bool bDxf ) :
    WorkbookHelper( rHelper ),
    mbDxf( bDxf )
{
}

void Fill::importPatternFill( const AttributeList& rAttribs )
{
    mxPatternModel = std::make_shared<PatternFillModel>( mbDxf );
    mxPatternModel->mnPattern = rAttribs.getToken( XML_patternType, XML_none );
    if( mbDxf )
        mxPatternModel->mbPatternUsed = rAttribs.hasAttribute( XML_patternType );
}

void Fill::importFgColor( const AttributeList& rAttribs )
{
    OSL_ENSURE( mxPatternModel, "Fill::importFgColor - missing pattern data" );
    if( mxPatternModel )
    {
        mxPatternModel->maPatternColor.importColor(rAttribs);
        mxPatternModel->mbPattColorUsed = true;
    }
}

void Fill::importBgColor( const AttributeList& rAttribs )
{
    OSL_ENSURE( mxPatternModel, "Fill::importBgColor - missing pattern data" );
    if( mxPatternModel )
    {
        mxPatternModel->maFillColor.importColor(rAttribs);
        mxPatternModel->mbFillColorUsed = true;
    }
}

void Fill::importGradientFill( const AttributeList& rAttribs )
{
    mxGradientModel = std::make_shared<GradientFillModel>();
    mxGradientModel->mnType = rAttribs.getToken( XML_type, XML_linear );
    mxGradientModel->mfAngle = rAttribs.getDouble( XML_degree, 0.0 );
    mxGradientModel->mfLeft = rAttribs.getDouble( XML_left, 0.0 );
    mxGradientModel->mfRight = rAttribs.getDouble( XML_right, 0.0 );
    mxGradientModel->mfTop = rAttribs.getDouble( XML_top, 0.0 );
    mxGradientModel->mfBottom = rAttribs.getDouble( XML_bottom, 0.0 );
}

void Fill::importColor( const AttributeList& rAttribs, double fPosition )
{
    OSL_ENSURE( mxGradientModel, "Fill::importColor - missing gradient data" );
    if( mxGradientModel && (fPosition >= 0.0) )
        mxGradientModel->maColors[ fPosition ].importColor( rAttribs );
}

void Fill::importFill( SequenceInputStream& rStrm )
{
    SAL_WARN_IF( mbDxf, "sc", "Fill::importFill - unexpected conditional formatting flag" );
    sal_Int32 nPattern = rStrm.readInt32();
    if( nPattern == BIFF12_FILL_GRADIENT )
    {
        mxGradientModel = std::make_shared<GradientFillModel>();
        sal_Int32 nStopCount;
        rStrm.skip( 16 );
        mxGradientModel->readGradient( rStrm );
        nStopCount = rStrm.readInt32();
        for( sal_Int32 nStop = 0; (nStop < nStopCount) && !rStrm.isEof(); ++nStop )
            mxGradientModel->readGradientStop( rStrm, false );
    }
    else
    {
        mxPatternModel = std::make_shared<PatternFillModel>( mbDxf );
        mxPatternModel->setBiffPattern( nPattern );
        rStrm >> mxPatternModel->maPatternColor >> mxPatternModel->maFillColor;
    }
}

void Fill::importDxfPattern( SequenceInputStream& rStrm )
{
    SAL_WARN_IF( !mbDxf, "sc", "Fill::importDxfPattern - missing conditional formatting flag" );
    if( !mxPatternModel )
        mxPatternModel = std::make_shared<PatternFillModel>( mbDxf );
    mxPatternModel->setBiffPattern( rStrm.readuInt8() );
    mxPatternModel->mbPatternUsed = true;
}

void Fill::importDxfFgColor( SequenceInputStream& rStrm )
{
    SAL_WARN_IF( !mbDxf, "sc", "Fill::importDxfFgColor - missing conditional formatting flag" );
    if( !mxPatternModel )
        mxPatternModel = std::make_shared<PatternFillModel>( mbDxf );
    mxPatternModel->maPatternColor.importColor( rStrm );
    mxPatternModel->mbPattColorUsed = true;
}

void Fill::importDxfBgColor( SequenceInputStream& rStrm )
{
    SAL_WARN_IF( !mbDxf, "sc", "Fill::importDxfBgColor - missing conditional formatting flag" );
    if( !mxPatternModel )
        mxPatternModel = std::make_shared<PatternFillModel>( mbDxf );
    mxPatternModel->maFillColor.importColor( rStrm );
    mxPatternModel->mbFillColorUsed = true;
}

void Fill::importDxfGradient( SequenceInputStream& rStrm )
{
    SAL_WARN_IF( !mbDxf, "sc", "Fill::importDxfGradient - missing conditional formatting flag" );
    if( !mxGradientModel )
        mxGradientModel = std::make_shared<GradientFillModel>();
    mxGradientModel->readGradient( rStrm );
}

void Fill::importDxfStop( SequenceInputStream& rStrm )
{
    SAL_WARN_IF( !mbDxf, "sc", "Fill::importDxfStop - missing conditional formatting flag" );
    if( !mxGradientModel )
        mxGradientModel = std::make_shared<GradientFillModel>();
    mxGradientModel->readGradientStop( rStrm, true );
}

void Fill::finalizeImport()
{
    const GraphicHelper& rGraphicHelper = getBaseFilter().getGraphicHelper();

    if( mxPatternModel )
    {
        // finalize the OOXML data struct
        PatternFillModel& rModel = *mxPatternModel;
        if( mbDxf )
        {
            if( rModel.mbFillColorUsed && (!rModel.mbPatternUsed || (rModel.mnPattern == XML_solid)) )
            {
                rModel.maFilterPatternColor = rModel.maPatternColor;
                rModel.maPatternColor = rModel.maFillColor;
                rModel.mnPattern = XML_solid;
                rModel.mbPattColorUsed = rModel.mbPatternUsed = true;
            }
            else if(
                !rModel.mbFillColorUsed && !rModel.mbPattColorUsed &&
                rModel.mbPatternUsed && rModel.mnPattern == XML_solid )
            {
                rModel.mbPatternUsed = false;
            }
            else
                rModel.maFilterPatternColor = rModel.maPatternColor;
        }

        // convert to API fill settings
        maApiData.mbUsed = rModel.mbPatternUsed;
        if( rModel.mnPattern == XML_none )
        {
            maApiData.mnColor = API_RGB_TRANSPARENT;
            maApiData.mbTransparent = true;
        }
        else
        {
            sal_Int32 nAlpha = 0x80;
            switch( rModel.mnPattern )
            {
                case XML_darkDown:          nAlpha = 0x40;  break;
                case XML_darkGray:          nAlpha = 0x60;  break;
                case XML_darkGrid:          nAlpha = 0x40;  break;
                case XML_darkHorizontal:    nAlpha = 0x40;  break;
                case XML_darkTrellis:       nAlpha = 0x60;  break;
                case XML_darkUp:            nAlpha = 0x40;  break;
                case XML_darkVertical:      nAlpha = 0x40;  break;
                case XML_gray0625:          nAlpha = 0x08;  break;
                case XML_gray125:           nAlpha = 0x10;  break;
                case XML_lightDown:         nAlpha = 0x20;  break;
                case XML_lightGray:         nAlpha = 0x20;  break;
                case XML_lightGrid:         nAlpha = 0x38;  break;
                case XML_lightHorizontal:   nAlpha = 0x20;  break;
                case XML_lightTrellis:      nAlpha = 0x30;  break;
                case XML_lightUp:           nAlpha = 0x20;  break;
                case XML_lightVertical:     nAlpha = 0x20;  break;
                case XML_mediumGray:        nAlpha = 0x40;  break;
                case XML_solid:             nAlpha = 0x80;  break;
            }

            ::Color nWinTextColor = rGraphicHelper.getSystemColor( XML_windowText );
            ::Color nWinColor = rGraphicHelper.getSystemColor( XML_window );

            if (!rModel.mbPattColorUsed)
            {
                rModel.maPatternColor.setAuto();
                rModel.maFilterPatternColor.setAuto();
            }
            ::Color nPattColor = rModel.maPatternColor.getColor( rGraphicHelper, nWinTextColor );
            ::Color nFiltPattColor = rModel.maFilterPatternColor.getColor( rGraphicHelper, nWinTextColor );

            if( !rModel.mbFillColorUsed )
                rModel.maFillColor.setAuto();
            ::Color nFillColor = rModel.maFillColor.getColor( rGraphicHelper, nWinColor );

            maApiData.mnColor = lclGetMixedColor( nPattColor, nFillColor, nAlpha );
            maApiData.maComplexColor = rModel.maPatternColor.createComplexColor(rGraphicHelper, -1);
            maApiData.mnFilterColor = lclGetMixedColor( nFiltPattColor, nFillColor, nAlpha );
            maApiData.mbTransparent = false;
        }
    }
    else if( mxGradientModel && !mxGradientModel->maColors.empty() )
    {
        GradientFillModel& rModel = *mxGradientModel;
        maApiData.mbUsed = true;    // no support for differential attributes
        GradientFillModel::ColorMap::const_iterator aIt = rModel.maColors.begin();
        OSL_ENSURE( !aIt->second.isAuto(), "Fill::finalizeImport - automatic gradient color" );
        maApiData.mnColor = aIt->second.getColor( rGraphicHelper, API_RGB_WHITE );
        if( ++aIt != rModel.maColors.end() )
        {
            OSL_ENSURE( !aIt->second.isAuto(), "Fill::finalizeImport - automatic gradient color" );
            ::Color nEndColor = aIt->second.getColor( rGraphicHelper, API_RGB_WHITE );
            maApiData.mnColor = lclGetMixedColor( maApiData.mnColor, nEndColor, 0x40 );
            maApiData.mbTransparent = false;
        }
    }
}

void Fill::fillToItemSet( SfxItemSet& rItemSet, bool bSkipPoolDefs ) const
{
    if( !maApiData.mbUsed )
        return;

    SvxBrushItem aBrushItem( ATTR_BACKGROUND );
    if ( maApiData.mbTransparent )
    {
        aBrushItem.SetColor( COL_TRANSPARENT );
        aBrushItem.SetFiltColor( COL_TRANSPARENT );
    }
    else
    {
        aBrushItem.SetColor( maApiData.mnColor  );
        aBrushItem.setComplexColor(maApiData.maComplexColor);
        aBrushItem.SetFiltColor( maApiData.mnFilterColor  );
    }
    ScfTools::PutItem( rItemSet, aBrushItem, bSkipPoolDefs );
}

XfModel::XfModel() :
    mnStyleXfId( -1 ),
    mnFontId( -1 ),
    mnNumFmtId( -1 ),
    mnBorderId( -1 ),
    mnFillId( -1 ),
    mbCellXf( true ),
    mbFontUsed( false ),
    mbNumFmtUsed( false ),
    mbAlignUsed( false ),
    mbProtUsed( false ),
    mbBorderUsed( false ),
    mbAreaUsed( false )
{
}

Xf::AttrList::AttrList(const ScPatternAttr* pDefPattern):
    mbLatinNumFmtOnly(true),
    mpDefPattern(pDefPattern)
{}

Xf::Xf( const WorkbookHelper& rHelper ) :
    WorkbookHelper( rHelper ),
    mnScNumFmt(0),
    maAlignment( rHelper ),
    maProtection( rHelper, false ),
    meRotationRef( css::table::CellVertJustify2::STANDARD ),
    mpStyleSheet( nullptr )
{
}

void Xf::importXf( const AttributeList& rAttribs, bool bCellXf )
{
    maModel.mbCellXf = bCellXf;
    // tdf#70565 Set proper default value to "0" of xfId attribute
    // When xfId is not exist during .xlsx import
    // it must have values set to "0".
    // This doesn't impact spreadsheets created with MS Excel,
    // as xfId attribute is always created during export to .xlsx
    // Not setting "0" value is causing wrong .xlsx import by LibreOffice,
    // for spreadsheets created by external applications (ex. SAP BI).
    bool bApplyDefault;
    if ( maModel.mbCellXf )
    {
        const sal_Int32 xfId = rAttribs.getInteger( XML_xfId, -1 );
        // No xfId => no cellStyleXfs that could overwrite this on change, thus
        // has to be applied.
        bApplyDefault = (xfId < 0);
        maModel.mnStyleXfId = std::max<sal_Int32>(0, xfId);
    }
    else
    {
        maModel.mnStyleXfId = rAttribs.getInteger( XML_xfId, -1 );
        bApplyDefault = true;
    }
    maModel.mnFontId = rAttribs.getInteger( XML_fontId, -1 );
    maModel.mnNumFmtId = rAttribs.getInteger( XML_numFmtId, -1 );
    maModel.mnBorderId = rAttribs.getInteger( XML_borderId, -1 );
    maModel.mnFillId = rAttribs.getInteger( XML_fillId, -1 );

    // Default value of the apply*** attributes is dependent on context:
    // true in cellStyleXfs element, false in cellXfs element...
    // But it's not as easy as it sounds, for docs see
    // https://learn.microsoft.com/en-us/openspecs/office_standards/ms-oe376/59922f8b-0edc-4e93-a822-9f22254aec46
    // and apparently in reality cellStyleXfs xf and cellXfs xf are not merged
    // at all, see
    // https://learn.microsoft.com/en-us/openspecs/office_standards/ms-oe376/bcf98682-e8d3-44b8-b8f8-0bf696878ba1
    // "b. The standard states that both the cell style xf records and cell xf
    // records must be read to understand the full set of formatting applied to
    // a cell."
    // "In Office, only the cell xf record defines the formatting applied to a cell."

    // So for reading documents this is all crap and effectively xf records
    // apply their explicit properties by default unless denied.
    // bApplyDefault==false only for cellXf xf with xfId.

    // For cellXf xf, mbAlignUsed and mbProtUsed will be set when actually
    // importing the element.
    maModel.mbAlignUsed  = rAttribs.getBool( XML_applyAlignment,    bApplyDefault);
    maModel.mbProtUsed   = rAttribs.getBool( XML_applyProtection,   bApplyDefault);

    maModel.mbFontUsed   = rAttribs.getBool( XML_applyFont,         bApplyDefault || maModel.mnFontId > 0);
    maModel.mbNumFmtUsed = rAttribs.getBool( XML_applyNumberFormat, bApplyDefault || maModel.mnNumFmtId > 0);
    maModel.mbBorderUsed = rAttribs.getBool( XML_applyBorder,       bApplyDefault || maModel.mnBorderId > 0);
    maModel.mbAreaUsed   = rAttribs.getBool( XML_applyFill,         bApplyDefault || maModel.mnFillId > 0);
}

void Xf::importAlignment( const AttributeList& rAttribs )
{
    maAlignment.importAlignment( rAttribs );
    if (maModel.mbCellXf)
        maModel.mbAlignUsed = true;
}

void Xf::importProtection( const AttributeList& rAttribs )
{
    maProtection.importProtection( rAttribs );
    if (maModel.mbCellXf)
        maModel.mbProtUsed = true;
}

void Xf::importXf( SequenceInputStream& rStrm, bool bCellXf )
{
    maModel.mbCellXf = bCellXf;
    maModel.mnStyleXfId = rStrm.readuInt16();
    maModel.mnNumFmtId = rStrm.readuInt16();
    maModel.mnFontId = rStrm.readuInt16();
    maModel.mnFillId = rStrm.readuInt16();
    maModel.mnBorderId = rStrm.readuInt16();
    sal_uInt32 nFlags = rStrm.readuInt32();
    maAlignment.setBiff12Data( nFlags );
    maProtection.setBiff12Data( nFlags );
    // used flags, see comments in Xf::setBiffUsedFlags()
    sal_uInt16 nUsedFlags = rStrm.readuInt16();
    maModel.mbFontUsed   = maModel.mbCellXf == getFlag( nUsedFlags, BIFF12_XF_FONT_USED );
    maModel.mbNumFmtUsed = maModel.mbCellXf == getFlag( nUsedFlags, BIFF12_XF_NUMFMT_USED );
    maModel.mbAlignUsed  = maModel.mbCellXf == getFlag( nUsedFlags, BIFF12_XF_ALIGN_USED );
    maModel.mbProtUsed   = maModel.mbCellXf == getFlag( nUsedFlags, BIFF12_XF_PROT_USED );
    maModel.mbBorderUsed = maModel.mbCellXf == getFlag( nUsedFlags, BIFF12_XF_BORDER_USED );
    maModel.mbAreaUsed   = maModel.mbCellXf == getFlag( nUsedFlags, BIFF12_XF_AREA_USED );
}

void Xf::finalizeImport()
{
    // alignment and protection
    maAlignment.finalizeImport();
    maProtection.finalizeImport();
}

FontRef Xf::getFont() const
{
    return getStyles().getFont( maModel.mnFontId );
}

void Xf::applyPatternToAttrList( AttrList& rAttrs, SCROW nRow1, SCROW nRow2, sal_Int32 nXfId, sal_Int32 nNumFmtId, ScPatternCache& rCache )
{
    ScPatternAttr* pCachedPattern = rCache.query(nXfId, nNumFmtId);
    if (!pCachedPattern)
        createPattern();

    ScPatternAttr& rPat = pCachedPattern ? *pCachedPattern : *mpPattern;
    ScDocumentImport& rDocImport = getDocImport();
    ScDocument& rDoc = getScDocument();
    if ( !pCachedPattern && isCellXf() )
    {
        StylesBuffer& rStyles = getStyles();
        rStyles.createCellStyle( maModel.mnStyleXfId );

        mpStyleSheet = rStyles.getCellStyleSheet( maModel.mnStyleXfId );
        if ( mpStyleSheet )
        {
            //rDoc.ApplySelectionStyle( static_cast<ScStyleSheet&>(*mpStyleSheet), rMarkData );
            rPat.SetStyleSheet(mpStyleSheet, false);
        }
        else
        {
            ScStyleSheetPool* pStylePool = rDoc.GetStyleSheetPool();
            if (pStylePool)
            {
                ScStyleSheet* pStyleSheet = static_cast<ScStyleSheet*>(
                    pStylePool->Find(
                        ScResId(STR_STYLENAME_STANDARD), SfxStyleFamily::Para));

                if (pStyleSheet)
                    rPat.SetStyleSheet( pStyleSheet, false );
            }
        }
    }
    if ( !pCachedPattern && nNumFmtId >= 0 )
    {
        ScPatternAttr aNumPat(rDoc.getCellAttributeHelper());
        mnScNumFmt = getStyles().writeNumFmtToItemSet( aNumPat.GetItemSetWritable(), nNumFmtId, false );
        rPat.GetItemSetWritable().Put(aNumPat.GetItemSet());
    }

    if (!pCachedPattern && !rDocImport.isLatinScript(mnScNumFmt))
        rAttrs.mbLatinNumFmtOnly = false;

    if (!pCachedPattern && !rPat.GetStyleName())
        return;


    // Check for a gap between the last entry and this one.
    bool bHasGap = false;
    if (rAttrs.maAttrs.empty() && nRow1 > 0)
        // First attribute range doesn't start at row 0.
        bHasGap = true;

    if (!rAttrs.maAttrs.empty() && rAttrs.maAttrs.back().nEndRow + 1 < nRow1)
        bHasGap = true;

    if (bHasGap)
    {
        // Fill this gap with the default pattern.
        ScAttrEntry aEntry;
        aEntry.nEndRow = nRow1 - 1;
        aEntry.setScPatternAttr(rAttrs.mpDefPattern, false);
        rAttrs.maAttrs.push_back(aEntry);

        // Check if the default pattern is 'General'.
        if (!rDocImport.isLatinScript(*aEntry.getScPatternAttr()))
            rAttrs.mbLatinNumFmtOnly = false;
    }

    ScAttrEntry aEntry;
    aEntry.nEndRow = nRow2;
    aEntry.setScPatternAttr(&rPat, false);
    // Put the allocated pattern to cache
    if (!pCachedPattern)
        rCache.add(nXfId, nNumFmtId, const_cast<ScPatternAttr*>(aEntry.getScPatternAttr()));

    if (!rDocImport.isLatinScript(*aEntry.getScPatternAttr()))
        rAttrs.mbLatinNumFmtOnly = false;

    rAttrs.maAttrs.push_back(std::move(aEntry));
}

void Xf::writeToDoc( ScDocumentImport& rDoc, const ScRange& rRange )
{
    const StylesBuffer& rStyles = getStyles();

    if (isCellXf())
    {
        // Cell style name.
        OUString aStyleName = rStyles.createCellStyle(maModel.mnStyleXfId);

        ScStyleSheet* pStyleSheet =
            static_cast<ScStyleSheet*>(
                rDoc.getDoc().GetStyleSheetPool()->Find(aStyleName, SfxStyleFamily::Para));

        if (pStyleSheet)
        {
            rDoc.getDoc().ApplyStyleAreaTab(
                rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row(), rRange.aStart.Tab(),
                *pStyleSheet);
        }
    }

    const ScPatternAttr& rAttr = createPattern();
    rDoc.getDoc().ApplyPatternAreaTab(
        rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row(), rRange.aStart.Tab(), rAttr);
}

const ::ScPatternAttr&
Xf::createPattern( bool bSkipPoolDefs )
{
    if( mpPattern )
        return *mpPattern;
    mpPattern.reset( new ::ScPatternAttr(getScDocument().getCellAttributeHelper()) );
    SfxItemSet& rItemSet = mpPattern->GetItemSetWritable();
    /*  Enables the used flags, if the formatting attributes differ from the
        style XF. In cell XFs Excel uses the cell attributes, if they differ
        from the parent style XF (even if the used flag is switched off).
        #109899# ...or if the respective flag is not set in parent style XF.
     */
    StylesBuffer& rStyles = getStyles();

    const Xf* pStyleXf = isCellXf() ? rStyles.getStyleXf( maModel.mnStyleXfId ).get() : nullptr;
    if( pStyleXf && !mpStyleSheet )
    {
        rStyles.createCellStyle( maModel.mnStyleXfId );
        mpStyleSheet = rStyles.getCellStyleSheet(  maModel.mnStyleXfId );
        OSL_ENSURE( mpStyleSheet, "Xf::createPattern - no parentStyle created" );

        const XfModel& rStyleData = pStyleXf->maModel;
        if( !maModel.mbFontUsed )
            maModel.mbFontUsed = !rStyleData.mbFontUsed || (maModel.mnFontId != rStyleData.mnFontId);
        if( !maModel.mbNumFmtUsed )
            maModel.mbNumFmtUsed = !rStyleData.mbNumFmtUsed || (maModel.mnNumFmtId != rStyleData.mnNumFmtId);
        if( !maModel.mbAlignUsed )
            maModel.mbAlignUsed = !rStyleData.mbAlignUsed || !(maAlignment.getApiData() == pStyleXf->maAlignment.getApiData());
        if( !maModel.mbProtUsed )
            maModel.mbProtUsed = !rStyleData.mbProtUsed || !(maProtection.getApiData() == pStyleXf->maProtection.getApiData());
        if( !maModel.mbBorderUsed )
            maModel.mbBorderUsed = !rStyleData.mbBorderUsed || !StylesBuffer::equalBorders( maModel.mnBorderId, rStyleData.mnBorderId );
        if( !maModel.mbAreaUsed )
            maModel.mbAreaUsed = !rStyleData.mbAreaUsed || !StylesBuffer::equalFills( maModel.mnFillId, rStyleData.mnFillId );
    }
    // cell protection
    if( maModel.mbProtUsed )
    {
        maProtection.fillToItemSet( rItemSet, bSkipPoolDefs );
    }

    // font
    if( maModel.mbFontUsed )
    {
        rStyles.writeFontToItemSet( rItemSet, maModel.mnFontId, bSkipPoolDefs );
    }

    // value format
    if( maModel.mbNumFmtUsed )
    {
        mnScNumFmt = rStyles.writeNumFmtToItemSet( rItemSet, maModel.mnNumFmtId, bSkipPoolDefs );
    }
    // alignment
    if( maModel.mbAlignUsed )
    {
        maAlignment.fillToItemSet( rItemSet, bSkipPoolDefs );
    }

    // border
    if( maModel.mbBorderUsed )
    {
        rStyles.writeBorderToItemSet( rItemSet, maModel.mnBorderId, bSkipPoolDefs );
    }

    // area
    if( maModel.mbAreaUsed )
    {
        rStyles.writeFillToItemSet( rItemSet, maModel.mnFillId, bSkipPoolDefs );
    }

    /*  #i38709# Decide which rotation reference mode to use. If any outer
        border line of the cell is set (either explicitly or via cell style),
        and the cell contents are rotated, set rotation reference to bottom of
        cell. This causes the borders to be painted rotated with the text. */
    if( const Alignment* pAlignment = maModel.mbAlignUsed ? &maAlignment : (pStyleXf ? &pStyleXf->maAlignment : nullptr) )
    {
        SvxRotateMode eRotateMode = SVX_ROTATE_MODE_STANDARD;
        sal_Int32 nBorderId = maModel.mbBorderUsed ? maModel.mnBorderId : (pStyleXf ? pStyleXf->maModel.mnBorderId : -1);
        if( const Border* pBorder = rStyles.getBorder( nBorderId ).get() )
        {
            if( (pAlignment->getApiData().mnRotation) && pBorder->getApiData().hasAnyOuterBorder() )
            {
                meRotationRef = css::table::CellVertJustify2::BOTTOM;
                eRotateMode = SVX_ROTATE_MODE_BOTTOM;
            }
        }
        ScfTools::PutItem( rItemSet, SvxRotateModeItem( eRotateMode, ATTR_ROTATE_MODE ), bSkipPoolDefs );
    }

    return *mpPattern;
}

Dxf::Dxf( const WorkbookHelper& rHelper ) :
    WorkbookHelper( rHelper )
{
}

FontRef const & Dxf::createFont( bool bAlwaysNew )
{
    if( bAlwaysNew || !mxFont )
        mxFont = std::make_shared<Font>( *this, true );
    return mxFont;
}

BorderRef const & Dxf::createBorder( bool bAlwaysNew )
{
    if( bAlwaysNew || !mxBorder )
        mxBorder = std::make_shared<Border>( *this, true );
    return mxBorder;
}

FillRef const & Dxf::createFill( bool bAlwaysNew )
{
    if( bAlwaysNew || !mxFill )
        mxFill = std::make_shared<Fill>( *this, true );
    return mxFill;
}

ProtectionRef const & Dxf::createProtection( bool bAlwaysNew )
{
    if ( bAlwaysNew || !mxProtection )
        mxProtection = std::make_shared<Protection>( *this, true );
    return mxProtection;
}

void Dxf::importNumFmt( const AttributeList& rAttribs )
{
    // don't propagate number formats defined in Dxf entries
    // they can have the same id ( but different format codes ) as those
    // defined globally earlier. We discard the id defined in XML_numFmtId
    // and generate one ourselves ( this assumes that the normal numberformat
    // import has already taken place )
    sal_Int32 nNumFmtId  = getStyles().nextFreeNumFmtId();
    OUString aFmtCode = rAttribs.getXString( XML_formatCode, OUString() );
    mxNumFmt = getStyles().createNumFmt( nNumFmtId, aFmtCode );
}

void Dxf::importDxf( SequenceInputStream& rStrm )
{
    sal_Int32 nNumFmtId = -1;
    OUString aFmtCode;
    sal_uInt16 nRecCount;
    rStrm.skip( 4 );    // flags
    nRecCount = rStrm.readuInt16();
    for( sal_uInt16 nRec = 0; !rStrm.isEof() && (nRec < nRecCount); ++nRec )
    {
        sal_uInt16 nSubRecId, nSubRecSize;
        sal_Int64 nRecEnd = rStrm.tell();
        nSubRecId = rStrm.readuInt16();
        nSubRecSize = rStrm.readuInt16();
        nRecEnd += nSubRecSize;
        switch( nSubRecId )
        {
            case BIFF12_DXF_FILL_PATTERN:       createFill( false )->importDxfPattern( rStrm );                         break;
            case BIFF12_DXF_FILL_FGCOLOR:       createFill( false )->importDxfFgColor( rStrm );                         break;
            case BIFF12_DXF_FILL_BGCOLOR:       createFill( false )->importDxfBgColor( rStrm );                         break;
            case BIFF12_DXF_FILL_GRADIENT:      createFill( false )->importDxfGradient( rStrm );                        break;
            case BIFF12_DXF_FILL_STOP:          createFill( false )->importDxfStop( rStrm );                            break;
            case BIFF12_DXF_FONT_COLOR:         createFont( false )->importDxfColor( rStrm );                           break;
            case BIFF12_DXF_BORDER_TOP:         createBorder( false )->importDxfBorder( XLS_TOKEN( top ), rStrm );      break;
            case BIFF12_DXF_BORDER_BOTTOM:      createBorder( false )->importDxfBorder( XLS_TOKEN( bottom ), rStrm );   break;
            case BIFF12_DXF_BORDER_LEFT:        createBorder( false )->importDxfBorder( XLS_TOKEN( left ), rStrm );     break;
            case BIFF12_DXF_BORDER_RIGHT:       createBorder( false )->importDxfBorder( XLS_TOKEN( right ), rStrm );    break;
            case BIFF12_DXF_FONT_NAME:          createFont( false )->importDxfName( rStrm );                            break;
            case BIFF12_DXF_FONT_WEIGHT:        createFont( false )->importDxfWeight( rStrm );                          break;
            case BIFF12_DXF_FONT_UNDERLINE:     createFont( false )->importDxfUnderline( rStrm );                       break;
            case BIFF12_DXF_FONT_ESCAPEMENT:    createFont( false )->importDxfEscapement( rStrm );                      break;
            case BIFF12_DXF_FONT_ITALIC:        createFont( false )->importDxfFlag( XML_i, rStrm );                     break;
            case BIFF12_DXF_FONT_STRIKE:        createFont( false )->importDxfFlag( XML_strike, rStrm );                break;
            case BIFF12_DXF_FONT_OUTLINE:       createFont( false )->importDxfFlag( XML_outline, rStrm );               break;
            case BIFF12_DXF_FONT_SHADOW:        createFont( false )->importDxfFlag( XML_shadow, rStrm );                break;
            case BIFF12_DXF_FONT_HEIGHT:        createFont( false )->importDxfHeight( rStrm );                          break;
            case BIFF12_DXF_FONT_SCHEME:        createFont( false )->importDxfScheme( rStrm );                          break;
            case BIFF12_DXF_NUMFMT_CODE:        aFmtCode = BiffHelper::readString( rStrm, false );                      break;
            case BIFF12_DXF_NUMFMT_ID:          nNumFmtId = rStrm.readuInt16();                                         break;
            case BIFF12_DXF_CELL_LOCKED:        createProtection( false )->importDxfProtection( XML_locked, rStrm );    break;
            case BIFF12_DXF_CELL_HIDDEN:        createProtection( false )->importDxfProtection( XML_hidden, rStrm );    break;
        }
        rStrm.seek( nRecEnd );
    }
    OSL_ENSURE( !rStrm.isEof() && (rStrm.getRemaining() == 0), "Dxf::importDxf - unexpected remaining data" );
    mxNumFmt = getStyles().createNumFmt( nNumFmtId, aFmtCode );
}

void Dxf::finalizeImport()
{
    if( mxFont )
        mxFont->finalizeImport();
    bool bRTL = false;
    // number format already finalized by the number formats buffer
    if( mxAlignment )
    {
        mxAlignment->finalizeImport();
        // how do we detect RTL when text dir is OOX_XF_CONTEXT? ( seems you
        // would need access to the cell content, which we don't here )
        if ( mxAlignment->getModel().mnTextDir == OOX_XF_TEXTDIR_RTL )
            bRTL = true;
    }
    if( mxProtection )
        mxProtection->finalizeImport();
    if( mxBorder )
    {
        mxBorder->finalizeImport( bRTL );
    }
    if( mxFill )
        mxFill->finalizeImport();
}

void Dxf::fillToItemSet( SfxItemSet& rSet ) const
{
    if (mxFont)
        mxFont->fillToItemSet(rSet, false);
    if (mxNumFmt)
        mxNumFmt->fillToItemSet(rSet);
    if (mxAlignment)
        mxAlignment->fillToItemSet(rSet);
    if (mxProtection)
        mxProtection->fillToItemSet(rSet);
    if (mxBorder)
        mxBorder->fillToItemSet(rSet);
    if (mxFill)
        mxFill->fillToItemSet(rSet);
}

namespace {

const char* const sppcStyleNames[] =
{
    "Normal",
    "RowLevel_",            // outline level will be appended
    "ColLevel_",            // outline level will be appended
    "Comma",
    "Currency",
    "Percent",
    "Comma [0]",            // new in BIFF4
    "Currency [0]",
    "Hyperlink",            // new in BIFF8
    "Followed Hyperlink",
    "Note",                 // new in OOX
    "Warning Text",
    nullptr,
    nullptr,
    nullptr,
    "Title",
    "Heading 1",
    "Heading 2",
    "Heading 3",
    "Heading 4",
    "Input",
    "Output",
    "Calculation",
    "Check Cell",
    "Linked Cell",
    "Total",
    "Good",
    "Bad",
    "Neutral",
    "Accent1",
    "20% - Accent1",
    "40% - Accent1",
    "60% - Accent1",
    "Accent2",
    "20% - Accent2",
    "40% - Accent2",
    "60% - Accent2",
    "Accent3",
    "20% - Accent3",
    "40% - Accent3",
    "60% - Accent3",
    "Accent4",
    "20% - Accent4",
    "40% - Accent4",
    "60% - Accent4",
    "Accent5",
    "20% - Accent5",
    "40% - Accent5",
    "60% - Accent5",
    "Accent6",
    "20% - Accent6",
    "40% - Accent6",
    "60% - Accent6",
    "Explanatory Text"
};
const sal_Int32 snStyleNamesCount = static_cast< sal_Int32 >( SAL_N_ELEMENTS( sppcStyleNames ) );

OUString lclGetBuiltinStyleName( sal_Int32 nBuiltinId, std::u16string_view rName, sal_Int32 nLevel = 0 )
{
    OSL_ENSURE( (0 <= nBuiltinId) && (nBuiltinId < snStyleNamesCount), "lclGetBuiltinStyleName - unknown built-in style" );
    OUStringBuffer aStyleName("Excel Built-in ");
    if( (0 <= nBuiltinId) && (nBuiltinId < snStyleNamesCount) && (sppcStyleNames[ nBuiltinId ] != nullptr) )
        aStyleName.appendAscii( sppcStyleNames[ nBuiltinId ] );
    else if( !rName.empty() )
        aStyleName.append( rName );
    else
        aStyleName.append( nBuiltinId );
    if( (nBuiltinId == OOX_STYLE_ROWLEVEL) || (nBuiltinId == OOX_STYLE_COLLEVEL) )
        aStyleName.append( nLevel );
    return aStyleName.makeStringAndClear();
}

OUString lclCreateStyleName( const CellStyleModel& rModel )
{
    return rModel.mbBuiltin ? lclGetBuiltinStyleName( rModel.mnBuiltinId, rModel.maName, rModel.mnLevel ) : rModel.maName;
}

} // namespace

CellStyleModel::CellStyleModel() :
    mnXfId( -1 ),
    mnBuiltinId( -1 ),
    mnLevel( 0 ),
    mbBuiltin( false ),
    mbCustom( false ),
    mbHidden( false )
{
}

bool CellStyleModel::isBuiltin() const
{
    return mbBuiltin && (mnBuiltinId >= 0);
}

bool CellStyleModel::isDefaultStyle() const
{
    return mbBuiltin && (mnBuiltinId == OOX_STYLE_NORMAL);
}

CellStyle::CellStyle( const WorkbookHelper& rHelper ) :
    WorkbookHelper( rHelper ),
    mbCreated( false ),
    mpStyleSheet( nullptr )
{
}

void CellStyle::importCellStyle( const AttributeList& rAttribs )
{
    maModel.maName      = rAttribs.getXString( XML_name, OUString() );
    maModel.mnXfId      = rAttribs.getInteger( XML_xfId, -1 );
    maModel.mnBuiltinId = rAttribs.getInteger( XML_builtinId, -1 );
    maModel.mnLevel     = rAttribs.getInteger( XML_iLevel, 0 );
    maModel.mbBuiltin   = rAttribs.hasAttribute( XML_builtinId );
    maModel.mbCustom    = rAttribs.getBool( XML_customBuiltin, false );
    maModel.mbHidden    = rAttribs.getBool( XML_hidden, false );
}

void CellStyle::importCellStyle( SequenceInputStream& rStrm )
{
    sal_uInt16 nFlags;
    maModel.mnXfId = rStrm.readInt32();
    nFlags = rStrm.readuInt16();
    maModel.mnBuiltinId = rStrm.readInt8();
    maModel.mnLevel = rStrm.readInt8();
    rStrm >> maModel.maName;
    maModel.mbBuiltin = getFlag( nFlags, BIFF12_CELLSTYLE_BUILTIN );
    maModel.mbCustom = getFlag( nFlags, BIFF12_CELLSTYLE_CUSTOM );
    maModel.mbHidden = getFlag( nFlags, BIFF12_CELLSTYLE_HIDDEN );
}

void CellStyle::createCellStyle()
{

    // #i1624# #i1768# ignore unnamed user styles
    bool bDefStyle = maModel.isDefaultStyle();
    if( !mbCreated )
    {
        if ( bDefStyle && maFinalName.isEmpty() )
            maFinalName = ScResId( STR_STYLENAME_STANDARD );
        mbCreated = maFinalName.isEmpty();
    }

    if( mbCreated || mpStyleSheet )
        return;

    bool bCreatePattern = false;
    Xf* pXF = getStyles().getStyleXf( maModel.mnXfId ).get();
    ::ScDocument& rDoc = getScDocument();

    if( bDefStyle )
    {
        // use existing "Default" style sheet
        mpStyleSheet = static_cast< ScStyleSheet* >( rDoc.GetStyleSheetPool()->Find(
            ScResId( STR_STYLENAME_STANDARD ), SfxStyleFamily::Para ) );
        OSL_ENSURE( mpStyleSheet, "CellStyle::createStyle - Default style not found" );
        bCreatePattern = true;
    }
    else
    {
        mpStyleSheet = static_cast< ScStyleSheet* >( rDoc.GetStyleSheetPool()->Find( maFinalName, SfxStyleFamily::Para ) );
        if( !mpStyleSheet )
        {
            mpStyleSheet = &static_cast< ScStyleSheet& >( rDoc.GetStyleSheetPool()->Make( maFinalName, SfxStyleFamily::Para, SfxStyleSearchBits::UserDefined ) );
            bCreatePattern = true;
        }
    }

    // bDefStyle==true omits default pool items in CreatePattern()
    if( bCreatePattern && mpStyleSheet && pXF )
        mpStyleSheet->GetItemSet().Put( pXF->createPattern( bDefStyle ).GetItemSet() );
}

void CellStyle::finalizeImport( const OUString& rFinalName )
{
    maFinalName = rFinalName;
    if( !maModel.isBuiltin() || maModel.mbCustom )
        createCellStyle();
}

CellStyleBuffer::CellStyleBuffer( const WorkbookHelper& rHelper ) :
    WorkbookHelper( rHelper )
{
}

CellStyleRef CellStyleBuffer::importCellStyle( const AttributeList& rAttribs )
{
    CellStyleRef xCellStyle = std::make_shared<CellStyle>( *this );
    xCellStyle->importCellStyle( rAttribs );
    insertCellStyle( xCellStyle );
    return xCellStyle;
}

CellStyleRef CellStyleBuffer::importCellStyle( SequenceInputStream& rStrm )
{
    CellStyleRef xCellStyle = std::make_shared<CellStyle>( *this );
    xCellStyle->importCellStyle( rStrm );
    insertCellStyle( xCellStyle );
    return xCellStyle;
}

void CellStyleBuffer::finalizeImport()
{
    // calculate final names of all styles
    typedef RefMap< OUString, CellStyle, IgnoreCaseCompare > CellStyleNameMap;
    CellStyleNameMap aCellStyles;
    CellStyleVector aConflictNameStyles;

    /*  First, reserve style names that are built-in in Calc. This causes that
        imported cell styles get different unused names and thus do not try to
        overwrite these built-in styles. */
    try
    {
        // unfortunately, com.sun.star.style.StyleFamily does not implement XEnumerationAccess...
        Reference< XIndexAccess > xStyleFamilyIA( getCellStyleFamily(), UNO_QUERY_THROW );
        for( sal_Int32 nIndex = 0, nCount = xStyleFamilyIA->getCount(); nIndex < nCount; ++nIndex )
        {
            Reference< XStyle > xStyle( xStyleFamilyIA->getByIndex( nIndex ), UNO_QUERY_THROW );
            if( !xStyle->isUserDefined() )
            {
                // create an empty entry by using ::std::map<>::operator[]
                aCellStyles[ xStyle->getName() ];
            }
        }
    }
    catch( Exception& )
    {
    }

    /*  Calculate names of built-in styles. Store styles with reserved names
        in the aConflictNameStyles list. */
    for( const auto& rxStyle : maBuiltinStyles )
    {
        const CellStyleModel& rModel = rxStyle->getModel();
        if (rModel.isDefaultStyle())
            continue;

        OUString aStyleName = lclCreateStyleName( rModel );
        /*  If a builtin style entry already exists,
            we just stick with the last definition and ignore
            the preceding ones. */
        aCellStyles[ aStyleName ] = rxStyle;
    }

    /*  Calculate names of user defined styles. Store styles with reserved
        names in the aConflictNameStyles list. */
    for( const auto& rxStyle : maUserStyles )
    {
        const CellStyleModel& rModel = rxStyle->getModel();
        OUString aStyleName = lclCreateStyleName( rModel );
        // #i1624# #i1768# ignore unnamed user styles
        if( aStyleName.getLength() > 0 )
        {
            if( aCellStyles.find( aStyleName ) != aCellStyles.end() )
                aConflictNameStyles.push_back( rxStyle );
            else
                aCellStyles[ aStyleName ] = rxStyle;
        }
    }

    // find unused names for all styles with conflicting names
    // having the index counter outside the loop prevents performance problems with opening some pathological documents (tdf#62095)
    sal_Int32 nIndex = 0;
    for( const auto& rxStyle : aConflictNameStyles )
    {
        const CellStyleModel& rModel = rxStyle->getModel();
        OUString aStyleName = lclCreateStyleName( rModel );
        OUString aUnusedName;
        do
        {
            aUnusedName = aStyleName + OUStringChar(' ') + OUString::number( ++nIndex );
        }
        while( !aCellStyles.try_emplace( aUnusedName, rxStyle ).second );
    }

    // set final names and create user-defined and modified built-in cell styles
    aCellStyles.forEachMemWithKey( &CellStyle::finalizeImport );
}

sal_Int32 CellStyleBuffer::getDefaultXfId() const
{
    return mxDefStyle ? mxDefStyle->getModel().mnXfId : -1;
}

OUString CellStyleBuffer::getDefaultStyleName() const
{
    return createCellStyle( mxDefStyle );
}

OUString CellStyleBuffer::createCellStyle( sal_Int32 nXfId ) const
{
    return createCellStyle( maStylesByXf.get( nXfId ) );
}

::ScStyleSheet*   CellStyleBuffer::getCellStyleSheet( sal_Int32 nXfId ) const
{
    return getCellStyleSheet( maStylesByXf.get( nXfId ) );
}

// private --------------------------------------------------------------------

void CellStyleBuffer::insertCellStyle( CellStyleRef const & xCellStyle )
{
    const CellStyleModel& rModel = xCellStyle->getModel();
    if( rModel.mnXfId < 0 )
        return;

    // insert into the built-in map or user defined map
    (rModel.isBuiltin() ? maBuiltinStyles : maUserStyles).push_back( xCellStyle );

    // insert into the XF identifier map
    OSL_ENSURE( maStylesByXf.count( rModel.mnXfId ) == 0, "CellStyleBuffer::insertCellStyle - multiple styles with equal XF identifier" );
    maStylesByXf[ rModel.mnXfId ] = xCellStyle;

    // remember default cell style
    if( rModel.isDefaultStyle() )
        mxDefStyle = xCellStyle;
}

::ScStyleSheet* CellStyleBuffer::getCellStyleSheet( const CellStyleRef& rxCellStyle )
{
    ::ScStyleSheet* pStyleSheet = nullptr;
    if ( rxCellStyle )
        pStyleSheet = rxCellStyle->getStyleSheet();
    return pStyleSheet;
}

OUString CellStyleBuffer::createCellStyle( const CellStyleRef& rxCellStyle )
{
    if( rxCellStyle )
    {
        rxCellStyle->createCellStyle();
        const OUString& rStyleName = rxCellStyle->getFinalStyleName();
        if( !rStyleName.isEmpty() )
            return rStyleName;
    }
    // on error: fallback to default style
    return lclGetBuiltinStyleName( OOX_STYLE_NORMAL, u"" );
}

AutoFormatModel::AutoFormatModel() :
    mnAutoFormatId( 0 ),
    mbApplyNumFmt( false ),
    mbApplyFont( false ),
    mbApplyAlignment( false ),
    mbApplyBorder( false ),
    mbApplyFill( false ),
    mbApplyProtection( false )
{
}

StylesBuffer::StylesBuffer( const WorkbookHelper& rHelper ) :
    WorkbookHelper( rHelper ),
    maPalette( rHelper ),
    maNumFmts( rHelper ),
    maCellStyles( rHelper )
{
}

FontRef StylesBuffer::createFont()
{
    FontRef xFont = std::make_shared<Font>( *this, false );
    maFonts.push_back( xFont );
    return xFont;
}

NumberFormatRef StylesBuffer::createNumFmt( sal_Int32 nNumFmtId, std::u16string_view aFmtCode )
{
    return maNumFmts.createNumFmt( nNumFmtId, aFmtCode );
}

sal_Int32 StylesBuffer::nextFreeNumFmtId()
{
    return maNumFmts.nextFreeId();
}

BorderRef StylesBuffer::createBorder()
{
    BorderRef xBorder = std::make_shared<Border>( *this, false );
    maBorders.push_back( xBorder );
    return xBorder;
}

FillRef StylesBuffer::createFill()
{
    FillRef xFill = std::make_shared<Fill>( *this, false );
    maFills.push_back( xFill );
    return xFill;
}

XfRef StylesBuffer::createCellXf()
{
    XfRef xXf = std::make_shared<Xf>( *this );
    maCellXfs.push_back( xXf );
    return xXf;
}

XfRef StylesBuffer::createStyleXf()
{
    XfRef xXf = std::make_shared<Xf>( *this );
    maStyleXfs.push_back( xXf );
    return xXf;
}

DxfRef StylesBuffer::createDxf()
{
    DxfRef xDxf = std::make_shared<Dxf>( *this );
    maDxfs.push_back( xDxf );
    return xDxf;
}

DxfRef StylesBuffer::createExtDxf()
{
    DxfRef xDxf = std::make_shared<Dxf>( *this );
    maExtDxfs.push_back( xDxf );
    return xDxf;
}

void StylesBuffer::importPaletteColor( const AttributeList& rAttribs )
{
    maPalette.importPaletteColor( rAttribs );
}

NumberFormatRef StylesBuffer::importNumFmt( const AttributeList& rAttribs )
{
    return maNumFmts.importNumFmt( rAttribs );
}

CellStyleRef StylesBuffer::importCellStyle( const AttributeList& rAttribs )
{
    return maCellStyles.importCellStyle( rAttribs );
}

void StylesBuffer::importPaletteColor( SequenceInputStream& rStrm )
{
    maPalette.importPaletteColor( rStrm );
}

void StylesBuffer::importNumFmt( SequenceInputStream& rStrm )
{
    maNumFmts.importNumFmt( rStrm );
}

void StylesBuffer::importCellStyle( SequenceInputStream& rStrm )
{
    maCellStyles.importCellStyle( rStrm );
}

void StylesBuffer::finalizeImport()
{
    // fonts first, are needed to finalize unit converter and XFs below
    maFonts.forEachMem( &Font::finalizeImport );
    // finalize unit coefficients after default font is known
    getUnitConverter().finalizeImport();
    // number formats
    maNumFmts.finalizeImport();
    // borders and fills
    // is there a document wide RTL setting that we
    // would/could need to pass to finalizeImport here ?
    maBorders.forEachMem( &Border::finalizeImport, false );
    maFills.forEachMem( &Fill::finalizeImport );
    // style XFs and cell XFs
    maStyleXfs.forEachMem( &Xf::finalizeImport );
    maCellXfs.forEachMem( &Xf::finalizeImport );
    // built-in and user defined cell styles
    maCellStyles.finalizeImport();
    // differential formatting (for conditional formatting)
    maDxfs.forEachMem( &Dxf::finalizeImport );
}

::Color StylesBuffer::getPaletteColor( sal_Int32 nPaletteIdx ) const
{
    return maPalette.getColor( nPaletteIdx );
}

FontRef StylesBuffer::getFont( sal_Int32 nFontId ) const
{
    return maFonts.get( nFontId );
}

BorderRef StylesBuffer::getBorder( sal_Int32 nBorderId ) const
{
    return maBorders.get( nBorderId );
}

XfRef StylesBuffer::getCellXf( sal_Int32 nXfId ) const
{
    return maCellXfs.get( nXfId );
}

XfRef StylesBuffer::getStyleXf( sal_Int32 nXfId ) const
{
    return maStyleXfs.get( nXfId );
}

FontRef StylesBuffer::getFontFromCellXf( sal_Int32 nXfId ) const
{
    FontRef xFont;
    if( const Xf* pXf = getCellXf( nXfId ).get() )
        xFont = pXf->getFont();
    return xFont;
}

FontRef StylesBuffer::getDefaultFont() const
{
    FontRef xDefFont;
    if( const Xf* pXf = getStyleXf( maCellStyles.getDefaultXfId() ).get() )
        xDefFont = pXf->getFont();
    // no font from styles - try first loaded font (e.g. BIFF2)
    if( !xDefFont )
        xDefFont = maFonts.get( 0 );
    OSL_ENSURE( xDefFont, "StylesBuffer::getDefaultFont - no default font found" );
    return xDefFont;
}

const FontModel& StylesBuffer::getDefaultFontModel() const
{
    FontRef xDefFont = getDefaultFont();
    return xDefFont ? xDefFont->getModel() : getTheme().getDefaultFontModel();
}

bool StylesBuffer::equalBorders( sal_Int32 nBorderId1, sal_Int32 nBorderId2 )
{
    // in OOXML, borders are assumed to be unique
    return nBorderId1 == nBorderId2;
}

bool StylesBuffer::equalFills( sal_Int32 nFillId1, sal_Int32 nFillId2 )
{
    // in OOXML, fills are assumed to be unique
    return nFillId1 == nFillId2;
}

OUString StylesBuffer::getDefaultStyleName() const
{
    return maCellStyles.getDefaultStyleName();
}

OUString StylesBuffer::createCellStyle( sal_Int32 nXfId ) const
{
    return maCellStyles.createCellStyle( nXfId );
}

::ScStyleSheet* StylesBuffer::getCellStyleSheet( sal_Int32 nXfId ) const
{
    return maCellStyles.getCellStyleSheet( nXfId );
}

OUString StylesBuffer::createDxfStyle( sal_Int32 nDxfId ) const
{
    OUString& rStyleName = maDxfStyles[ nDxfId ];
    if (!rStyleName.isEmpty())
        return rStyleName;

    if (Dxf* pDxf = maDxfs.get(nDxfId).get())
    {
        // FIXME: How can we know whether this dxf is for conditional formatting,
        // not for color filter? Currently this style is created for each dxf
        // (which might only be used by color filter)
        rStyleName = "ConditionalStyle_" + OUString::number(nDxfId + 1);

        // Create a cell style. This may overwrite an existing style if
        // one with the same name exists.
        ScStyleSheet& rStyleSheet = ScfTools::MakeCellStyleSheet(
                *getScDocument().GetStyleSheetPool(), rStyleName, true);

        rStyleSheet.ResetParent();
        SfxItemSet& rStyleItemSet =
            rStyleSheet.GetItemSet();

        pDxf->fillToItemSet(rStyleItemSet);

    }

    // on error: fallback to default style
    if (rStyleName.isEmpty())
        rStyleName = maCellStyles.getDefaultStyleName();

    return rStyleName;
}

OUString StylesBuffer::createExtDxfStyle( sal_Int32 nDxfId ) const
{
    OUString rStyleName;

    if (Dxf* pDxf = maExtDxfs.get(nDxfId).get())
    {
        rStyleName = "ExtConditionalStyle_" + OUString::number(nDxfId + 1);

        // Create a cell style. This may overwrite an existing style if
        // one with the same name exists.
        ScStyleSheet& rStyleSheet = ScfTools::MakeCellStyleSheet(
                *getScDocument().GetStyleSheetPool(), rStyleName, true);

        rStyleSheet.ResetParent();
        SfxItemSet& rStyleItemSet =
            rStyleSheet.GetItemSet();

        pDxf->fillToItemSet(rStyleItemSet);
    }

    // on error: fallback to default style
    if (rStyleName.isEmpty())
        rStyleName = maCellStyles.getDefaultStyleName();

    return rStyleName;
}

DxfRef StylesBuffer::getDxf(sal_Int32 nDxfId) const
{
    return maDxfs.get(nDxfId);
}

void StylesBuffer::writeFontToItemSet( SfxItemSet& rItemSet, sal_Int32 nFontId, bool bSkipPoolDefs ) const
{
    if( Font* pFont = maFonts.get( nFontId ).get() )
        pFont->fillToItemSet( rItemSet, false, bSkipPoolDefs );
}

sal_uInt32 StylesBuffer::writeNumFmtToItemSet( SfxItemSet& rItemSet, sal_uInt32 nNumFmtId, bool bSkipPoolDefs ) const
{
    return maNumFmts.fillToItemSet( rItemSet, nNumFmtId, bSkipPoolDefs );
}

void StylesBuffer::writeBorderToItemSet( SfxItemSet& rItemSet, sal_Int32 nBorderId, bool bSkipPoolDefs ) const
{
    if( Border* pBorder = maBorders.get( nBorderId ).get() )
        pBorder->fillToItemSet( rItemSet, bSkipPoolDefs );
}

void StylesBuffer::writeFillToItemSet( SfxItemSet& rItemSet, sal_Int32 nFillId, bool bSkipPoolDefs ) const
{
    if( Fill* pFill = maFills.get( nFillId ).get() )
        pFill->fillToItemSet( rItemSet, bSkipPoolDefs );
}

bool operator==( const XfModel& rXfModel1,  const XfModel& rXfModel2 )
{
    return ( rXfModel1.mbCellXf == rXfModel2.mbCellXf &&
        rXfModel1.mnStyleXfId == rXfModel2.mnStyleXfId &&
        rXfModel1.mbFontUsed == rXfModel2.mbFontUsed &&
        rXfModel1.mnFontId == rXfModel2.mnFontId &&
        rXfModel1.mbNumFmtUsed == rXfModel2.mbNumFmtUsed &&
        rXfModel1.mnNumFmtId == rXfModel2.mnNumFmtId &&
        rXfModel1.mbAlignUsed == rXfModel2.mbAlignUsed &&
        rXfModel1.mbBorderUsed == rXfModel2.mbBorderUsed &&
        rXfModel1.mnBorderId == rXfModel2.mnBorderId &&
        rXfModel1.mbAreaUsed == rXfModel2.mbAreaUsed &&
        rXfModel1.mnFillId == rXfModel2.mnFillId &&
        rXfModel1.mbProtUsed == rXfModel2.mbProtUsed );
}

bool operator==( const Xf& rXf1, const Xf& rXf2 )
{
    if ( rXf1.maModel == rXf2.maModel )
    {
        if ( rXf1.maModel.mbAlignUsed )
        {
            if ( !( rXf1.maAlignment.getApiData() == rXf2.maAlignment.getApiData() ) )
                return false;
        }
        if ( rXf1.maModel.mbProtUsed )
        {
            if ( !( rXf1.maProtection.getApiData() == rXf2.maProtection.getApiData() ) )
                return false;
        }
        return true;
    }
    return false;
}

void StylesBuffer::writeCellXfToDoc(
    ScDocumentImport& rDoc, const ScRange& rRange, sal_Int32 nXfId ) const
{
    Xf* pXf = maCellXfs.get(nXfId).get();
    if (!pXf)
        return;

    pXf->writeToDoc(rDoc, rRange);
}

} // namespace oox

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
