tdf#87535: Preview styles using CTL/CJK fonts in the sidebar

Code is similar to code in svx/source/dialog/fntctrl.cxx and
source/ui/chrdlg/drpcps.cxx, etc. Instead of using only the “western”
font, it splits the text based on script type and uses the appropriate
font for each script type.

Change-Id: Iacdf92829d4568f2d580b39ca14ffe1218ceaa9c
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/138600
Tested-by: Jenkins
Reviewed-by: Caolán McNamara <caolanm@redhat.com>
diff --git a/svx/inc/CommonStylePreviewRenderer.hxx b/svx/inc/CommonStylePreviewRenderer.hxx
index b613f48..c4d87abf 100644
--- a/svx/inc/CommonStylePreviewRenderer.hxx
+++ b/svx/inc/CommonStylePreviewRenderer.hxx
@@ -10,6 +10,7 @@
#pragma once

#include <optional>
#include <vector>

#include <editeng/svxfont.hxx>
#include <sfx2/objsh.hxx>
@@ -18,21 +19,45 @@
#include <tools/color.hxx>
#include <tools/gen.hxx>

#include <com/sun/star/uno/Reference.h>
#include <com/sun/star/i18n/BreakIterator.hpp>

class OutputDevice;
class SfxStyleSheetBase;

using namespace css;

namespace svx
{
class CommonStylePreviewRenderer final : public sfx2::StylePreviewRenderer
{
    std::optional<SvxFont> m_oFont;
    std::optional<SvxFont> m_oCJKFont;
    std::optional<SvxFont> m_oCTLFont;
    Color maFontColor;
    Color maHighlightColor;
    Color maBackgroundColor;
    Size maPixelSize;
    tools::Long mnHeight;
    OUString maStyleName;
    OUString maScriptText;
    css::uno::Reference<css::i18n::XBreakIterator> mxBreak;
    struct ScriptInfo
    {
        tools::Long textWidth;
        sal_uInt16 scriptType;
        sal_Int32 changePos;
        ScriptInfo(sal_uInt16 scrptType, sal_Int32 position)
            : textWidth(0)
            , scriptType(scrptType)
            , changePos(position)
        {
        }
    };
    std::vector<ScriptInfo> maScriptChanges;

    Size getRenderSize() const;
    bool SetFontSize(const SfxItemSet& rSet, sal_uInt16 nSlot, SvxFont& rFont);
    void CalcRenderSize();
    void CheckScript();

public:
    CommonStylePreviewRenderer(const SfxObjectShell& rShell, OutputDevice& rOutputDev,
diff --git a/svx/source/styles/CommonStylePreviewRenderer.cxx b/svx/source/styles/CommonStylePreviewRenderer.cxx
index df0e353..66fbb9e 100644
--- a/svx/source/styles/CommonStylePreviewRenderer.cxx
+++ b/svx/source/styles/CommonStylePreviewRenderer.cxx
@@ -14,6 +14,7 @@
#include <sfx2/objsh.hxx>
#include <svl/style.hxx>
#include <svl/itemset.hxx>
#include <svl/itempool.hxx>
#include <vcl/outdev.hxx>

#include <com/sun/star/drawing/FillStyle.hpp>
@@ -37,6 +38,9 @@

#include <editeng/editids.hrc>

#include <comphelper/processfactory.hxx>
#include <com/sun/star/i18n/ScriptType.hpp>

using namespace css;

namespace svx
@@ -56,57 +60,127 @@ CommonStylePreviewRenderer::CommonStylePreviewRenderer(
CommonStylePreviewRenderer::~CommonStylePreviewRenderer()
{}

static bool GetWhich(const SfxItemSet& rSet, sal_uInt16 nSlot, sal_uInt16& rWhich)
{
    rWhich = rSet.GetPool()->GetWhich(nSlot);
    return rSet.GetItemState(rWhich) >= SfxItemState::DEFAULT;
}

static bool SetFont(const SfxItemSet& rSet, sal_uInt16 nSlot, SvxFont& rFont)
{
    sal_uInt16 nWhich;
    if (GetWhich(rSet, nSlot, nWhich))
    {
        const auto& rFontItem = static_cast<const SvxFontItem&>(rSet.Get(nWhich));
        rFont.SetFamily(rFontItem.GetFamily());
        rFont.SetFamilyName(rFontItem.GetFamilyName());
        rFont.SetPitch(rFontItem.GetPitch());
        rFont.SetCharSet(rFontItem.GetCharSet());
        rFont.SetStyleName(rFontItem.GetStyleName());
        return true;
    }
    return false;
}

bool CommonStylePreviewRenderer::SetFontSize(const SfxItemSet& rSet, sal_uInt16 nSlot, SvxFont& rFont)
{
    sal_uInt16 nWhich;
    if (GetWhich(rSet, nSlot, nWhich))
    {
        const auto& rFontHeightItem = static_cast<const SvxFontHeightItem&>(rSet.Get(nWhich));
        Size aFontSize(0, rFontHeightItem.GetHeight());
        aFontSize = mrOutputDev.LogicToPixel(aFontSize, MapMode(mrShell.GetMapUnit()));
        rFont.SetFontSize(aFontSize);
        return true;
    }
    return false;
}

bool CommonStylePreviewRenderer::recalculate()
{
    m_oFont.reset();
    m_oCJKFont.reset();
    m_oCTLFont.reset();

    std::optional<SfxItemSet> pItemSet(mpStyle->GetItemSetForPreview());

    if (!pItemSet) return false;

    SvxFont aFont;
    SvxFont aCJKFont;
    SvxFont aCTLFont;

    const SfxPoolItem* pItem;

    if ((pItem = pItemSet->GetItem(SID_ATTR_CHAR_WEIGHT)) != nullptr)
    {
        aFont.SetWeight(static_cast<const SvxWeightItem*>(pItem)->GetWeight());
    }
    if ((pItem = pItemSet->GetItem(SID_ATTR_CHAR_CJK_WEIGHT)) != nullptr)
        aCJKFont.SetWeight(static_cast<const SvxWeightItem*>(pItem)->GetWeight());
    if ((pItem = pItemSet->GetItem(SID_ATTR_CHAR_CTL_WEIGHT)) != nullptr)
        aCTLFont.SetWeight(static_cast<const SvxWeightItem*>(pItem)->GetWeight());

    if ((pItem = pItemSet->GetItem(SID_ATTR_CHAR_POSTURE)) != nullptr)
    {
        aFont.SetItalic(static_cast<const SvxPostureItem*>(pItem)->GetPosture());
    }
    if ((pItem = pItemSet->GetItem(SID_ATTR_CHAR_CJK_POSTURE)) != nullptr)
        aCJKFont.SetItalic(static_cast<const SvxPostureItem*>(pItem)->GetPosture());
    if ((pItem = pItemSet->GetItem(SID_ATTR_CHAR_CTL_POSTURE)) != nullptr)
        aCTLFont.SetItalic(static_cast<const SvxPostureItem*>(pItem)->GetPosture());

    if ((pItem = pItemSet->GetItem(SID_ATTR_CHAR_CONTOUR)) != nullptr)
    {
        aFont.SetOutline(static_cast< const SvxContourItem*>(pItem)->GetValue());
        auto aVal = static_cast<const SvxContourItem*>(pItem)->GetValue();
        aFont.SetOutline(aVal);
        aCJKFont.SetOutline(aVal);
        aCTLFont.SetOutline(aVal);
    }
    if ((pItem = pItemSet->GetItem(SID_ATTR_CHAR_SHADOWED)) != nullptr)
    {
        aFont.SetShadow(static_cast<const SvxShadowedItem*>(pItem)->GetValue());
        auto aVal = static_cast<const SvxShadowedItem*>(pItem)->GetValue();
        aFont.SetShadow(aVal);
        aCJKFont.SetShadow(aVal);
        aCTLFont.SetShadow(aVal);
    }
    if ((pItem = pItemSet->GetItem(SID_ATTR_CHAR_RELIEF)) != nullptr)
    {
        aFont.SetRelief(static_cast<const SvxCharReliefItem*>(pItem)->GetValue());
        auto aVal = static_cast<const SvxCharReliefItem*>(pItem)->GetValue();
        aFont.SetRelief(aVal);
        aCJKFont.SetRelief(aVal);
        aCTLFont.SetRelief(aVal);
    }
    if ((pItem = pItemSet->GetItem(SID_ATTR_CHAR_UNDERLINE)) != nullptr)
    {
        aFont.SetUnderline(static_cast< const SvxUnderlineItem*>(pItem)->GetLineStyle());
        auto aVal = static_cast<const SvxUnderlineItem*>(pItem)->GetLineStyle();
        aFont.SetUnderline(aVal);
        aCJKFont.SetUnderline(aVal);
        aCTLFont.SetUnderline(aVal);
    }
    if ((pItem = pItemSet->GetItem(SID_ATTR_CHAR_OVERLINE)) != nullptr)
    {
        aFont.SetOverline(static_cast<const SvxOverlineItem*>(pItem)->GetValue());
        auto aVal = static_cast<const SvxOverlineItem*>(pItem)->GetValue();
        aFont.SetOverline(aVal);
        aCJKFont.SetOverline(aVal);
        aCTLFont.SetOverline(aVal);
    }
    if ((pItem = pItemSet->GetItem(SID_ATTR_CHAR_STRIKEOUT)) != nullptr)
    {
        aFont.SetStrikeout(static_cast<const SvxCrossedOutItem*>(pItem)->GetStrikeout());
        auto aVal = static_cast<const SvxCrossedOutItem*>(pItem)->GetStrikeout();
        aFont.SetStrikeout(aVal);
        aCJKFont.SetStrikeout(aVal);
        aCTLFont.SetStrikeout(aVal);
    }
    if ((pItem = pItemSet->GetItem(SID_ATTR_CHAR_CASEMAP)) != nullptr)
    {
        aFont.SetCaseMap(static_cast<const SvxCaseMapItem*>(pItem)->GetCaseMap());
        auto aVal = static_cast<const SvxCaseMapItem*>(pItem)->GetCaseMap();
        aFont.SetCaseMap(aVal);
        aCJKFont.SetCaseMap(aVal);
        aCTLFont.SetCaseMap(aVal);
    }
    if ((pItem = pItemSet->GetItem(SID_ATTR_CHAR_EMPHASISMARK)) != nullptr)
    {
        aFont.SetEmphasisMark(static_cast<const SvxEmphasisMarkItem*>(pItem)->GetEmphasisMark());
        auto aVal = static_cast<const SvxEmphasisMarkItem*>(pItem)->GetEmphasisMark();
        aFont.SetEmphasisMark(aVal);
        aCJKFont.SetEmphasisMark(aVal);
        aCTLFont.SetEmphasisMark(aVal);
    }
    if ((pItem = pItemSet->GetItem(SID_ATTR_CHAR_COLOR)) != nullptr)
    {
@@ -132,59 +206,117 @@ bool CommonStylePreviewRenderer::recalculate()
        }
    }

    if ((pItem = pItemSet->GetItem(SID_ATTR_CHAR_FONT)) != nullptr)
    {
        const SvxFontItem* pFontItem = static_cast<const SvxFontItem*>(pItem);
        if (IsStarSymbol(pFontItem->GetFamilyName()))
            return false;
        aFont.SetFamilyName(pFontItem->GetFamilyName());
        aFont.SetStyleName(pFontItem->GetStyleName());
    }
    else
    {
        return false;
    }
    if (SetFont(*pItemSet, SID_ATTR_CHAR_FONT, aFont) &&
        SetFontSize(*pItemSet, SID_ATTR_CHAR_FONTHEIGHT, aFont))
        m_oFont = aFont;

    if ((pItem = pItemSet->GetItem(SID_ATTR_CHAR_FONTHEIGHT)) != nullptr)
    {
        const SvxFontHeightItem* pFontHeightItem = static_cast<const SvxFontHeightItem*>(pItem);
        Size aFontSize(0, pFontHeightItem->GetHeight());
        maPixelSize = mrOutputDev.LogicToPixel(aFontSize, MapMode(mrShell.GetMapUnit()));
        aFont.SetFontSize(maPixelSize);
    if (SetFont(*pItemSet, SID_ATTR_CHAR_CJK_FONT, aCJKFont) &&
        SetFontSize(*pItemSet, SID_ATTR_CHAR_CJK_FONTHEIGHT, aCJKFont))
        m_oCJKFont = aCJKFont;

        vcl::Font aOldFont(mrOutputDev.GetFont());
    if (SetFont(*pItemSet, SID_ATTR_CHAR_CTL_FONT, aCTLFont) &&
        SetFontSize(*pItemSet, SID_ATTR_CHAR_CTL_FONTHEIGHT, aCTLFont))
        m_oCTLFont = aCTLFont;

        mrOutputDev.SetFont(aFont);
        tools::Rectangle aTextRect;
        mrOutputDev.GetTextBoundRect(aTextRect, mpStyle->GetName());
        if (aTextRect.Bottom() > mnMaxHeight)
        {
            double ratio = double(mnMaxHeight) / aTextRect.Bottom();
            maPixelSize.setWidth( maPixelSize.Width() * ratio );
            maPixelSize.setHeight( maPixelSize.Height() * ratio );
            aFont.SetFontSize(maPixelSize);
        }
        mrOutputDev.SetFont(aOldFont);
    }
    else
    {
        return false;
    }

    m_oFont = aFont;
    maPixelSize = getRenderSize();
    CheckScript();
    CalcRenderSize();
    return true;
}

Size CommonStylePreviewRenderer::getRenderSize() const
void CommonStylePreviewRenderer::CalcRenderSize()
{
    assert(m_oFont);
    Size aPixelSize = m_oFont->GetTextSize(mrOutputDev, maStyleName);
    const OUString& rText = maStyleName;

    if (aPixelSize.Height() > mnMaxHeight)
        aPixelSize.setHeight( mnMaxHeight );
    tools::Rectangle aTextRect;
    tools::Long nHeight = 0;

    return aPixelSize;
    sal_uInt16 nScript;
    sal_uInt16 nIdx = 0;
    sal_Int32 nStart = 0;
    sal_Int32 nEnd;
    size_t nCnt = maScriptChanges.size();

    if (nCnt)
    {
        nEnd = maScriptChanges[nIdx].changePos;
        nScript = maScriptChanges[nIdx].scriptType;
    }
    else
    {
        nEnd = rText.getLength();
        nScript = css::i18n::ScriptType::LATIN;
    }

    do
    {
        auto oFont = (nScript == css::i18n::ScriptType::ASIAN) ?
                         m_oCJKFont :
                         ((nScript == css::i18n::ScriptType::COMPLEX) ?
                             m_oCTLFont :
                             m_oFont);

        mrOutputDev.Push(vcl::PushFlags::FONT);

        Size aSize;
        if (oFont)
        {
            mrOutputDev.SetFont(*oFont);
            aSize = oFont->GetTextSize(mrOutputDev, rText, nStart, nEnd - nStart);
        }
        else
            aSize = Size(mrOutputDev.GetTextWidth(rText, nStart, nEnd - nStart), mrOutputDev.GetFont().GetFontHeight());

        tools::Rectangle aRect;
        mrOutputDev.GetTextBoundRect(aRect, rText, nStart, nStart, nEnd - nStart);
        aTextRect = aTextRect.Union(aRect);

        mrOutputDev.Pop();

        auto nWidth = aSize.Width();
        nHeight = std::max(nHeight, aSize.Height());
        if (nIdx >= maScriptChanges.size())
            break;

        maScriptChanges[nIdx++].textWidth = nWidth;

        if (nEnd < rText.getLength() && nIdx < nCnt)
        {
            nStart = nEnd;
            nEnd = maScriptChanges[nIdx].changePos;
            nScript = maScriptChanges[nIdx].scriptType;
        }
        else
            break;
    }
    while(true);

    double fRatio = 1;
    if (aTextRect.Bottom() > mnMaxHeight)
        fRatio = double(mnMaxHeight) / aTextRect.Bottom();

    mnHeight = std::min(tools::Long(nHeight * fRatio), mnMaxHeight);
    if (fRatio != 1)
    {
        Size aFontSize;
        if (m_oFont)
        {
            aFontSize = m_oFont->GetFontSize();
            m_oFont->SetFontSize(Size(aFontSize.Height() * fRatio, aFontSize.Width() * fRatio));
        }
        if (m_oCJKFont)
        {
            aFontSize = m_oCJKFont->GetFontSize();
            m_oCJKFont->SetFontSize(Size(aFontSize.Height() * fRatio, aFontSize.Width() * fRatio));
        }
        if (m_oCTLFont)
        {
            aFontSize = m_oCTLFont->GetFontSize();
            m_oCTLFont->SetFontSize(Size(aFontSize.Height() * fRatio, aFontSize.Width() * fRatio));
        }

        for (auto& aChange : maScriptChanges)
            aChange.textWidth *= fRatio;
    }
}

bool CommonStylePreviewRenderer::render(const tools::Rectangle& aRectangle, RenderAlign eRenderAlign)
@@ -200,34 +332,108 @@ bool CommonStylePreviewRenderer::render(const tools::Rectangle& aRectangle, Rend
        mrOutputDev.DrawRect(aRectangle);
    }

    if (m_oFont)
        mrOutputDev.SetFont(*m_oFont);

    if (maFontColor != COL_AUTO)
        mrOutputDev.SetTextColor(maFontColor);

    if (maHighlightColor != COL_AUTO)
        mrOutputDev.SetTextFillColor(maHighlightColor);

    Size aPixelSize(m_oFont ? maPixelSize : mrOutputDev.GetFont().GetFontSize());

    Point aFontDrawPosition = aRectangle.TopLeft();
    if (eRenderAlign == RenderAlign::CENTER)
    {
        if (aRectangle.GetHeight() > aPixelSize.Height())
            aFontDrawPosition.AdjustY((aRectangle.GetHeight() - aPixelSize.Height()) / 2 );
        if (aRectangle.GetHeight() > mnHeight)
            aFontDrawPosition.AdjustY((aRectangle.GetHeight() - mnHeight) / 2 );
    }

    if (m_oFont)
        m_oFont->QuickDrawText( &mrOutputDev, aFontDrawPosition, rText, 0, rText.getLength(), {} );
    sal_uInt16 nScript;
    sal_uInt16 nIdx = 0;
    sal_Int32 nStart = 0;
    sal_Int32 nEnd;
    size_t nCnt = maScriptChanges.size();
    if (nCnt)
    {
        nEnd = maScriptChanges[nIdx].changePos;
        nScript = maScriptChanges[nIdx].scriptType;
    }
    else
        mrOutputDev.DrawText(aFontDrawPosition, rText);
    {
        nEnd = rText.getLength();
        nScript = css::i18n::ScriptType::LATIN;
    }

    do
    {
        auto oFont = (nScript == css::i18n::ScriptType::ASIAN)
                         ? m_oCJKFont
                         : ((nScript == css::i18n::ScriptType::COMPLEX)
                             ? m_oCTLFont
                             : m_oFont);

        mrOutputDev.Push(vcl::PushFlags::FONT);

        if (oFont)
        {
            mrOutputDev.SetFont(*oFont);
            oFont->QuickDrawText(&mrOutputDev, aFontDrawPosition, rText, nStart, nEnd - nStart, {});
        }
        else
            mrOutputDev.DrawText(aFontDrawPosition, rText, nStart, nEnd - nStart);

        mrOutputDev.Pop();

        aFontDrawPosition.AdjustX(maScriptChanges[nIdx++].textWidth);
        if (nEnd < rText.getLength() && nIdx < nCnt)
        {
            nStart = nEnd;
            nEnd = maScriptChanges[nIdx].changePos;
            nScript = maScriptChanges[nIdx].scriptType;
        }
        else
            break;
    }
    while(true);

    mrOutputDev.Pop();

    return true;
}

void CommonStylePreviewRenderer::CheckScript()
{
    assert(!maStyleName.isEmpty()); // must have a preview text here!
    if (maStyleName == maScriptText)
        return; // already initialized

    maScriptText = maStyleName;
    maScriptChanges.clear();

    if (!mxBreak.is())
    {
        auto xContext = comphelper::getProcessComponentContext();
        mxBreak = css::i18n::BreakIterator::create(xContext);
    }

    sal_Int16 nScript = mxBreak->getScriptType(maStyleName, 0);
    sal_Int32 nChg = 0;
    if (css::i18n::ScriptType::WEAK == nScript)
    {
        nChg = mxBreak->endOfScript(maStyleName, nChg, nScript);
        if (nChg < maStyleName.getLength())
            nScript = mxBreak->getScriptType(maStyleName, nChg);
        else
            nScript = css::i18n::ScriptType::LATIN;
    }

    while (true)
    {
        nChg = mxBreak->endOfScript(maStyleName, nChg, nScript);
        maScriptChanges.emplace_back(nScript, nChg);
        if (nChg >= maStyleName.getLength() || nChg < 0)
            break;
        nScript = mxBreak->getScriptType(maStyleName, nChg);
    }
}

} // end svx namespace

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