Follow-up to 92d78c3fa385f794b63cf6b328b41222f9e4c8b2 (Table Styles: Move Calc table style generation from OOXML filter to core). ComplexColor::applyTransformations() applies LumMod and LumOff as two separate RGB-HSL-RGB round-trips, which can produce off-by-one color values due to intermediate uint8 rounding. The OOXML filter's Color::getColor() avoided this by staying in HSL between the two transforms. Add ColorSet::resolveOOXMLColor() which collects LumMod and LumOff from the ComplexColor transforms and applies them in a single ApplyLumModOff() call. Also fixes the ComplexColor transform scale in TableStyleGenerator from 100'000 (OOXML scale) to 10'000 (ComplexColor convention), matching what the oox filter's createComplexColor() produces. Change-Id: Id0173f44ffa3663893e3012d7aefbeabbe264241 Reviewed-on: https://gerrit.collaboraoffice.com/c/core/+/232 Tested-by: Caolán McNamara <caolan.mcnamara@collabora.com> Reviewed-by: Caolán McNamara <caolan.mcnamara@collabora.com> (cherry picked from commit b44399314130a10698a18ffdab482a68bfdcff1a) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/204568 Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org> Tested-by: Jenkins
361 lines
13 KiB
C++
361 lines
13 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
|
|
/*
|
|
* 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/.
|
|
*/
|
|
|
|
#include <TableStyleGenerator.hxx>
|
|
|
|
#include <docmodel/theme/ColorSet.hxx>
|
|
#include <docmodel/color/ComplexColor.hxx>
|
|
#include <com/sun/star/table/BorderLine2.hpp>
|
|
#include <com/sun/star/table/BorderLineStyle.hpp>
|
|
#include <editeng/boxitem.hxx>
|
|
#include <editeng/brushitem.hxx>
|
|
#include <editeng/borderline.hxx>
|
|
#include <editeng/colritem.hxx>
|
|
#include <editeng/wghtitem.hxx>
|
|
#include <svl/itemset.hxx>
|
|
|
|
#include <document.hxx>
|
|
#include <patattr.hxx>
|
|
#include <scresid.hxx>
|
|
#include <strings.hrc>
|
|
#include <tablestyle.hxx>
|
|
#include <sc.hrc>
|
|
|
|
#include "defaulttablestyles.inc"
|
|
|
|
namespace
|
|
{
|
|
// OOXML theme index to model::ThemeColorType mapping
|
|
// (from spnColorTokens in stylesbuffer.cxx:266-268)
|
|
// Index: 0=lt1, 1=dk1, 2=lt2, 3=dk2, 4-9=accent1-6, 10=hlink, 11=folHlink
|
|
constexpr model::ThemeColorType aOoxmlIndexToThemeType[] = {
|
|
model::ThemeColorType::Light1, // 0
|
|
model::ThemeColorType::Dark1, // 1
|
|
model::ThemeColorType::Light2, // 2
|
|
model::ThemeColorType::Dark2, // 3
|
|
model::ThemeColorType::Accent1, // 4
|
|
model::ThemeColorType::Accent2, // 5
|
|
model::ThemeColorType::Accent3, // 6
|
|
model::ThemeColorType::Accent4, // 7
|
|
model::ThemeColorType::Accent5, // 8
|
|
model::ThemeColorType::Accent6, // 9
|
|
model::ThemeColorType::Hyperlink, // 10
|
|
model::ThemeColorType::FollowedHyperlink, // 11
|
|
};
|
|
|
|
struct ResolvedColor
|
|
{
|
|
::Color maColor;
|
|
model::ComplexColor maComplexColor;
|
|
};
|
|
|
|
struct FontData
|
|
{
|
|
bool bBold = false;
|
|
::Color maColor;
|
|
model::ComplexColor maComplexColor;
|
|
};
|
|
|
|
/// Build a ComplexColor from OOXML theme index + tint and resolve it.
|
|
/// Tint conversion replicates addExcelTintTransformation() from oox/source/drawingml/color.cxx.
|
|
ResolvedColor resolveThemeColor(const model::ColorSet& rColorSet, const ThemeColor& rThemeColor)
|
|
{
|
|
ResolvedColor aResult;
|
|
|
|
assert(rThemeColor.nThemeId >= 0
|
|
&& rThemeColor.nThemeId < static_cast<int>(std::size(aOoxmlIndexToThemeType)));
|
|
model::ThemeColorType eType = aOoxmlIndexToThemeType[rThemeColor.nThemeId];
|
|
|
|
// Build ComplexColor with transforms in 10'000 scale (ComplexColor convention),
|
|
// converting from OOXML 100'000 scale used by addExcelTintTransformation().
|
|
model::ComplexColor aComplexColor;
|
|
aComplexColor.setThemeColor(eType);
|
|
|
|
double fTint = rThemeColor.fTint;
|
|
if (fTint > 0.0)
|
|
{
|
|
sal_Int32 nValue = std::round(std::abs(fTint) * 10'000.0);
|
|
aComplexColor.addTransformation({ model::TransformationType::LumMod, 10'000 - nValue });
|
|
aComplexColor.addTransformation({ model::TransformationType::LumOff, nValue });
|
|
}
|
|
else if (fTint < 0.0)
|
|
{
|
|
sal_Int32 nValue = std::round(std::abs(fTint) * 10'000.0);
|
|
aComplexColor.addTransformation({ model::TransformationType::LumMod, 10'000 - nValue });
|
|
}
|
|
|
|
aResult.maComplexColor = aComplexColor;
|
|
aResult.maColor = rColorSet.resolveOOXMLColor(aComplexColor);
|
|
|
|
return aResult;
|
|
}
|
|
|
|
// Use the exact same path as the oox filter: create a BorderLine2 and call
|
|
// SvxBoxItem::LineToSvxLine(). This guarantees identical SvxBorderLine results.
|
|
void setBorderLine(editeng::SvxBorderLine& rLine, const ResolvedColor& rColor,
|
|
BorderElementStyle eStyle)
|
|
{
|
|
css::table::BorderLine2 bl2;
|
|
bl2.Color = sal_Int32(rColor.maColor);
|
|
bl2.OuterLineWidth = 0;
|
|
bl2.InnerLineWidth = 0;
|
|
bl2.LineDistance = 0;
|
|
bl2.LineStyle = 0; // css::table::BorderLineStyle::SOLID
|
|
bl2.LineWidth = 0;
|
|
|
|
switch (eStyle)
|
|
{
|
|
case BorderElementStyle::THIN:
|
|
bl2.OuterLineWidth = SvxBorderLineWidth::Thin;
|
|
break;
|
|
case BorderElementStyle::MEDIUM:
|
|
bl2.OuterLineWidth = SvxBorderLineWidth::Medium;
|
|
break;
|
|
case BorderElementStyle::THICK:
|
|
bl2.OuterLineWidth = SvxBorderLineWidth::Thick;
|
|
break;
|
|
case BorderElementStyle::DOUBLE:
|
|
bl2.OuterLineWidth = SvxBorderLineWidth::VeryThin;
|
|
bl2.LineDistance = SvxBorderLineWidth::Thin;
|
|
bl2.InnerLineWidth = SvxBorderLineWidth::VeryThin;
|
|
bl2.LineStyle = sal_Int16(css::table::BorderLineStyle::DOUBLE_THIN);
|
|
break;
|
|
}
|
|
|
|
SvxBoxItem::LineToSvxLine(bl2, rLine, false);
|
|
rLine.setComplexColor(rColor.maComplexColor);
|
|
}
|
|
|
|
std::vector<ResolvedColor> resolveColors(const model::ColorSet& rColorSet)
|
|
{
|
|
size_t nColors = std::size(aThemeColors);
|
|
std::vector<ResolvedColor> aColors(nColors);
|
|
for (size_t i = 0; i < nColors; ++i)
|
|
aColors[i] = resolveThemeColor(rColorSet, aThemeColors[i]);
|
|
return aColors;
|
|
}
|
|
|
|
std::vector<SvxBrushItem> buildFills(const std::vector<ResolvedColor>& rColors)
|
|
{
|
|
size_t nFills = std::size(aFills);
|
|
std::vector<SvxBrushItem> aFillItems;
|
|
aFillItems.reserve(nFills);
|
|
for (size_t i = 0; i < nFills; ++i)
|
|
{
|
|
aFillItems.emplace_back(ATTR_BACKGROUND);
|
|
const ::Fill& rFill = aFills[i];
|
|
aFillItems[i].SetColor(rColors[rFill.nFgColorId].maColor);
|
|
aFillItems[i].setComplexColor(rColors[rFill.nFgColorId].maComplexColor);
|
|
}
|
|
return aFillItems;
|
|
}
|
|
|
|
void buildBorders(const std::vector<ResolvedColor>& rColors, std::vector<SvxBoxItem>& rBoxItems,
|
|
std::vector<SvxBoxInfoItem>& rBoxInfoItems)
|
|
{
|
|
size_t nBorders = std::size(aBorders);
|
|
rBoxItems.clear();
|
|
rBoxItems.reserve(nBorders);
|
|
rBoxInfoItems.clear();
|
|
rBoxInfoItems.reserve(nBorders);
|
|
for (size_t i = 0; i < nBorders; ++i)
|
|
{
|
|
rBoxItems.emplace_back(ATTR_BORDER);
|
|
rBoxInfoItems.emplace_back(ATTR_BORDER_INNER);
|
|
}
|
|
|
|
for (size_t i = 0; i < nBorders; ++i)
|
|
{
|
|
const ::Border& rBorder = aBorders[i];
|
|
editeng::SvxBorderLine aLine;
|
|
|
|
if (rBorder.nTopId >= 0)
|
|
{
|
|
const BorderElement& rElem = aBorderElements[rBorder.nTopId];
|
|
setBorderLine(aLine, rColors[rElem.nColorId], rElem.eBorderStyle);
|
|
rBoxItems[i].SetLine(&aLine, SvxBoxItemLine::TOP);
|
|
}
|
|
if (rBorder.nBottomId >= 0)
|
|
{
|
|
const BorderElement& rElem = aBorderElements[rBorder.nBottomId];
|
|
setBorderLine(aLine, rColors[rElem.nColorId], rElem.eBorderStyle);
|
|
rBoxItems[i].SetLine(&aLine, SvxBoxItemLine::BOTTOM);
|
|
}
|
|
if (rBorder.nLeftId >= 0)
|
|
{
|
|
const BorderElement& rElem = aBorderElements[rBorder.nLeftId];
|
|
setBorderLine(aLine, rColors[rElem.nColorId], rElem.eBorderStyle);
|
|
rBoxItems[i].SetLine(&aLine, SvxBoxItemLine::LEFT);
|
|
}
|
|
if (rBorder.nRightId >= 0)
|
|
{
|
|
const BorderElement& rElem = aBorderElements[rBorder.nRightId];
|
|
setBorderLine(aLine, rColors[rElem.nColorId], rElem.eBorderStyle);
|
|
rBoxItems[i].SetLine(&aLine, SvxBoxItemLine::RIGHT);
|
|
}
|
|
if (rBorder.nVerticalId >= 0)
|
|
{
|
|
const BorderElement& rElem = aBorderElements[rBorder.nVerticalId];
|
|
setBorderLine(aLine, rColors[rElem.nColorId], rElem.eBorderStyle);
|
|
rBoxInfoItems[i].SetLine(&aLine, SvxBoxInfoItemLine::VERT);
|
|
}
|
|
if (rBorder.nHorizontalId >= 0)
|
|
{
|
|
const BorderElement& rElem = aBorderElements[rBorder.nHorizontalId];
|
|
setBorderLine(aLine, rColors[rElem.nColorId], rElem.eBorderStyle);
|
|
rBoxInfoItems[i].SetLine(&aLine, SvxBoxInfoItemLine::HORI);
|
|
}
|
|
}
|
|
}
|
|
|
|
std::vector<FontData> buildFonts(const std::vector<ResolvedColor>& rColors)
|
|
{
|
|
size_t nFonts = std::size(aFonts);
|
|
std::vector<FontData> aFontData(nFonts);
|
|
for (size_t i = 0; i < nFonts; ++i)
|
|
{
|
|
const ::Font& rFont = aFonts[i];
|
|
aFontData[i].bBold = rFont.bBold;
|
|
aFontData[i].maColor = rColors[rFont.nThemeColorId].maColor;
|
|
aFontData[i].maComplexColor = rColors[rFont.nThemeColorId].maComplexColor;
|
|
}
|
|
return aFontData;
|
|
}
|
|
|
|
std::vector<std::unique_ptr<ScPatternAttr>>
|
|
buildDxfPatterns(ScDocument& rDoc, const std::vector<SvxBrushItem>& rFillItems,
|
|
const std::vector<SvxBoxItem>& rBoxItems,
|
|
const std::vector<SvxBoxInfoItem>& rBoxInfoItems,
|
|
const std::vector<FontData>& rFontData)
|
|
{
|
|
size_t nDxfs = std::size(aDxfs);
|
|
std::vector<std::unique_ptr<ScPatternAttr>> aDxfPatterns(nDxfs);
|
|
for (size_t i = 0; i < nDxfs; ++i)
|
|
{
|
|
const ::Dxf& rDxf = aDxfs[i];
|
|
auto pPattern = std::make_unique<ScPatternAttr>(rDoc.getCellAttributeHelper());
|
|
SfxItemSet& rItemSet = pPattern->GetItemSetWritable();
|
|
|
|
if (rDxf.nFillId >= 0)
|
|
rItemSet.Put(rFillItems[rDxf.nFillId]);
|
|
|
|
if (rDxf.nBorderId >= 0)
|
|
{
|
|
const SvxBoxItem& rBox = rBoxItems[rDxf.nBorderId];
|
|
bool bHasOuter
|
|
= rBox.GetLine(SvxBoxItemLine::TOP) || rBox.GetLine(SvxBoxItemLine::BOTTOM)
|
|
|| rBox.GetLine(SvxBoxItemLine::LEFT) || rBox.GetLine(SvxBoxItemLine::RIGHT);
|
|
|
|
if (bHasOuter)
|
|
rItemSet.Put(rBox);
|
|
|
|
const SvxBoxInfoItem& rInfo = rBoxInfoItems[rDxf.nBorderId];
|
|
if (rInfo.GetVert() || rInfo.GetHori())
|
|
{
|
|
rItemSet.Put(rInfo);
|
|
}
|
|
}
|
|
|
|
if (rDxf.nFontId >= 0)
|
|
{
|
|
const FontData& rFontInfo = rFontData[rDxf.nFontId];
|
|
if (rFontInfo.bBold)
|
|
{
|
|
SvxWeightItem aWeight(WEIGHT_BOLD, ATTR_FONT_WEIGHT);
|
|
rItemSet.Put(aWeight);
|
|
rItemSet.Put(SvxWeightItem(WEIGHT_BOLD, ATTR_CJK_FONT_WEIGHT));
|
|
rItemSet.Put(SvxWeightItem(WEIGHT_BOLD, ATTR_CTL_FONT_WEIGHT));
|
|
}
|
|
SvxColorItem aColorItem(rFontInfo.maColor, rFontInfo.maComplexColor, ATTR_FONT_COLOR);
|
|
rItemSet.Put(aColorItem);
|
|
}
|
|
|
|
aDxfPatterns[i] = std::move(pPattern);
|
|
}
|
|
return aDxfPatterns;
|
|
}
|
|
|
|
TranslateId getCategoryTranslateId(TableStyleCategory eCategory)
|
|
{
|
|
switch (eCategory)
|
|
{
|
|
case TableStyleCategory::Light:
|
|
return STR_TABLE_STYLE_LIGHT;
|
|
case TableStyleCategory::Medium:
|
|
return STR_TABLE_STYLE_MEDIUM;
|
|
case TableStyleCategory::Dark:
|
|
return STR_TABLE_STYLE_DARK;
|
|
}
|
|
SAL_WARN("sc", "getCategoryTranslateId: unknown TableStyleCategory");
|
|
return {};
|
|
}
|
|
|
|
void assembleTableStyles(ScTableStyles& rTableStyles,
|
|
const std::vector<std::unique_ptr<ScPatternAttr>>& rDxfPatterns)
|
|
{
|
|
size_t nTableStyles = std::size(aTableStyles);
|
|
for (size_t i = 0; i < nTableStyles; ++i)
|
|
{
|
|
const ::TableStyle& rStyleInfo = aTableStyles[i];
|
|
OUString aUIName = ScResId(getCategoryTranslateId(rStyleInfo.eCategory)) + " "
|
|
+ OUString::number(rStyleInfo.nNumber);
|
|
auto pStyle = std::make_unique<ScTableStyle>(OUString::createFromAscii(rStyleInfo.pName),
|
|
std::optional<OUString>(aUIName));
|
|
pStyle->SetOOXMLDefault(true);
|
|
|
|
for (size_t j = 0; j < rStyleInfo.nElements; ++j)
|
|
{
|
|
const ::TableStyleElement& rElem
|
|
= aTableStyleElements[rStyleInfo.pTableStyleElementIds[j]];
|
|
if (rDxfPatterns[rElem.nDxfId])
|
|
{
|
|
auto pPatternCopy = std::make_unique<ScPatternAttr>(*rDxfPatterns[rElem.nDxfId]);
|
|
pStyle->SetPattern(rElem.eElement, std::move(pPatternCopy));
|
|
}
|
|
}
|
|
|
|
rTableStyles.AddTableStyle(std::move(pStyle));
|
|
}
|
|
}
|
|
|
|
} // end anonymous namespace
|
|
|
|
void ScTableStyleGenerator::generateDefaultStyles(ScDocument& rDoc,
|
|
const model::ColorSet& rColorSet)
|
|
{
|
|
ScTableStyles* pTableStyles = rDoc.GetTableStyles();
|
|
if (!pTableStyles)
|
|
return;
|
|
|
|
pTableStyles->ClearOOXMLDefaultStyles();
|
|
|
|
// Step 1: Resolve all theme colors
|
|
std::vector<ResolvedColor> aColors = resolveColors(rColorSet);
|
|
|
|
// Step 2: Build fill items (SvxBrushItem)
|
|
std::vector<SvxBrushItem> aFillItems = buildFills(aColors);
|
|
|
|
// Step 3: Build border items (SvxBoxItem + SvxBoxInfoItem)
|
|
std::vector<SvxBoxItem> aBoxItems;
|
|
std::vector<SvxBoxInfoItem> aBoxInfoItems;
|
|
buildBorders(aColors, aBoxItems, aBoxInfoItems);
|
|
|
|
// Step 4: Build font data (bold + color)
|
|
std::vector<FontData> aFontData = buildFonts(aColors);
|
|
|
|
// Step 5: Build DXF patterns (ScPatternAttr)
|
|
std::vector<std::unique_ptr<ScPatternAttr>> aDxfPatterns
|
|
= buildDxfPatterns(rDoc, aFillItems, aBoxItems, aBoxInfoItems, aFontData);
|
|
|
|
// Step 6: Assemble table styles
|
|
assembleTableStyles(*pTableStyles, aDxfPatterns);
|
|
}
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
|