Call it once where we will use its results. Change-Id: I63f3ceaf47e68bea9fab29d6836745416cf8ccb8
781 lines
27 KiB
C++
781 lines
27 KiB
C++
/* -*- 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 "CommonSalLayout.hxx"
|
||
|
||
#include <vcl/unohelp.hxx>
|
||
#include <scrptrun.h>
|
||
#include <com/sun/star/i18n/CharacterIteratorMode.hpp>
|
||
#include <i18nlangtag/mslangid.hxx>
|
||
#include <limits>
|
||
#include <salgdi.hxx>
|
||
#include <unicode/uchar.h>
|
||
|
||
|
||
static hb_blob_t* getFontTable(hb_face_t* /*face*/, hb_tag_t nTableTag, void* pUserData)
|
||
{
|
||
char pTagName[5];
|
||
pTagName[0] = (char)(nTableTag >> 24);
|
||
pTagName[1] = (char)(nTableTag >> 16);
|
||
pTagName[2] = (char)(nTableTag >> 8);
|
||
pTagName[3] = (char)(nTableTag);
|
||
pTagName[4] = 0;
|
||
|
||
sal_uLong nLength = 0;
|
||
#if defined(_WIN32)
|
||
unsigned char* pBuffer = nullptr;
|
||
HFONT hFont = static_cast<HFONT>(pUserData);
|
||
HDC hDC = GetDC(nullptr);
|
||
SelectObject(hDC, hFont);
|
||
nLength = ::GetFontData(hDC, OSL_NETDWORD(nTableTag), 0, nullptr, 0);
|
||
if (nLength > 0 && nLength != GDI_ERROR)
|
||
{
|
||
pBuffer = new unsigned char[nLength];
|
||
::GetFontData(hDC, OSL_NETDWORD(nTableTag), 0, pBuffer, nLength);
|
||
}
|
||
ReleaseDC(nullptr, hDC);
|
||
#elif defined(MACOSX) || defined(IOS)
|
||
unsigned char* pBuffer = nullptr;
|
||
CoreTextFontFace* pFont = static_cast<CoreTextFontFace*>(pUserData);
|
||
nLength = pFont->GetFontTable(pTagName, nullptr);
|
||
if (nLength > 0)
|
||
{
|
||
pBuffer = new unsigned char[nLength];
|
||
pFont->GetFontTable(pTagName, pBuffer);
|
||
}
|
||
#else
|
||
const unsigned char* pBuffer = nullptr;
|
||
FreetypeFont* pFont = static_cast<FreetypeFont*>(pUserData);
|
||
pBuffer = pFont->GetTable(pTagName, &nLength);
|
||
#endif
|
||
|
||
hb_blob_t* pBlob = nullptr;
|
||
if (pBuffer != nullptr)
|
||
#if defined(_WIN32) || defined(MACOSX) || defined(IOS)
|
||
pBlob = hb_blob_create(reinterpret_cast<const char*>(pBuffer), nLength, HB_MEMORY_MODE_READONLY,
|
||
pBuffer, [](void* data){ delete[] static_cast<unsigned char*>(data); });
|
||
#else
|
||
pBlob = hb_blob_create(reinterpret_cast<const char*>(pBuffer), nLength, HB_MEMORY_MODE_READONLY, nullptr, nullptr);
|
||
#endif
|
||
|
||
return pBlob;
|
||
}
|
||
|
||
static hb_font_t* createHbFont(hb_face_t* pHbFace)
|
||
{
|
||
hb_font_t* pHbFont = hb_font_create(pHbFace);
|
||
hb_ot_font_set_funcs(pHbFont);
|
||
|
||
return pHbFont;
|
||
}
|
||
|
||
// We cache and re-use the HarfBuzz font for different layout instances, so we
|
||
// need sure to set the correct scale (font size) before using the font.
|
||
hb_font_t* CommonSalLayout::getHbFont()
|
||
{
|
||
unsigned int nXScale = mrFontSelData.mnWidth << 6;
|
||
unsigned int nYScale = mrFontSelData.mnHeight << 6;
|
||
|
||
#if defined(_WIN32)
|
||
// HACK to get stretched/shrunken text. TODO: Get rid of HACK
|
||
if (nXScale)
|
||
nXScale = double(nXScale) * 1.812;
|
||
#endif
|
||
|
||
if (!nXScale)
|
||
nXScale = nYScale;
|
||
|
||
hb_font_set_ppem(mpHbFont, nXScale, nYScale);
|
||
hb_font_set_scale(mpHbFont, nXScale, nYScale);
|
||
|
||
return mpHbFont;
|
||
}
|
||
|
||
#if !HB_VERSION_ATLEAST(1, 1, 0)
|
||
// Disabled Unicode compatibility decomposition, see fdo#66715
|
||
static unsigned int unicodeDecomposeCompatibility(hb_unicode_funcs_t* /*ufuncs*/,
|
||
hb_codepoint_t /*u*/,
|
||
hb_codepoint_t* /*decomposed*/,
|
||
void* /*user_data*/)
|
||
{
|
||
return 0;
|
||
}
|
||
|
||
static hb_unicode_funcs_t* getUnicodeFuncs()
|
||
{
|
||
static hb_unicode_funcs_t* ufuncs = hb_unicode_funcs_create(hb_icu_get_unicode_funcs());
|
||
hb_unicode_funcs_set_decompose_compatibility_func(ufuncs, unicodeDecomposeCompatibility, nullptr, nullptr);
|
||
return ufuncs;
|
||
}
|
||
#endif
|
||
|
||
void CommonSalLayout::ParseFeatures(const OUString& name)
|
||
{
|
||
int nFeatures = 0;
|
||
int nStart = name.indexOf(':');
|
||
if (nStart < 0)
|
||
return;
|
||
OString oName = OUStringToOString(name, RTL_TEXTENCODING_ASCII_US);
|
||
for (int nNext = nStart; nNext > 0; nNext = name.indexOf('&', nNext + 1))
|
||
{
|
||
if (name.match("lang=", nNext + 1))
|
||
{
|
||
int endamp = name.indexOf('&', nNext+1);
|
||
int enddelim = name.indexOf(' ', nNext+1);
|
||
int end = name.getLength();
|
||
if (endamp < 0)
|
||
{
|
||
if (enddelim > 0)
|
||
end = enddelim;
|
||
}
|
||
else if (enddelim < 0 || endamp < enddelim)
|
||
end = endamp;
|
||
else
|
||
end = enddelim;
|
||
msLanguage = oName.copy(nNext + 6, end - nNext - 6);
|
||
}
|
||
else
|
||
++nFeatures;
|
||
}
|
||
if (nFeatures == 0)
|
||
return;
|
||
|
||
maFeatures.reserve(nFeatures);
|
||
for (int nThis = nStart, nNext = name.indexOf('&', nStart + 1); nThis > 0; nThis = nNext, nNext = name.indexOf('&', nNext + 1))
|
||
{
|
||
if (!name.match("lang=", nThis + 1))
|
||
{
|
||
int end = nNext > 0 ? nNext : name.getLength();
|
||
hb_feature_t aFeature;
|
||
if (hb_feature_from_string(oName.getStr() + nThis + 1, end - nThis - 1, &aFeature))
|
||
maFeatures.push_back(aFeature);
|
||
}
|
||
}
|
||
}
|
||
|
||
#if defined(_WIN32)
|
||
CommonSalLayout::CommonSalLayout(HDC hDC, WinFontInstance& rWinFontInstance, const WinFontFace& rWinFontFace)
|
||
: mrFontSelData(rWinFontInstance.maFontSelData)
|
||
{
|
||
mpHbFont = rWinFontFace.GetHbFont();
|
||
if (!mpHbFont)
|
||
{
|
||
HFONT hFont = static_cast<HFONT>(GetCurrentObject(hDC, OBJ_FONT));
|
||
hb_face_t* pHbFace = hb_face_create_for_tables(getFontTable, hFont, nullptr);
|
||
|
||
mpHbFont = createHbFont(pHbFace);
|
||
rWinFontFace.SetHbFont(mpHbFont);
|
||
|
||
hb_face_destroy(pHbFace);
|
||
}
|
||
}
|
||
|
||
#elif defined(MACOSX) || defined(IOS)
|
||
CommonSalLayout::CommonSalLayout(const CoreTextStyle& rCoreTextStyle)
|
||
: mrFontSelData(rCoreTextStyle.maFontSelData),
|
||
mrCoreTextStyle(rCoreTextStyle)
|
||
{
|
||
mpHbFont = rCoreTextStyle.GetHbFont();
|
||
if (!mpHbFont)
|
||
{
|
||
hb_face_t* pHbFace;
|
||
CTFontRef pCTFont = static_cast<CTFontRef>(CFDictionaryGetValue(rCoreTextStyle.GetStyleDict(), kCTFontAttributeName));
|
||
CGFontRef pCGFont = CTFontCopyGraphicsFont(pCTFont, nullptr);
|
||
if (pCGFont)
|
||
pHbFace = hb_coretext_face_create(pCGFont);
|
||
else
|
||
pHbFace = hb_face_create_for_tables(getFontTable, const_cast<CoreTextFontFace*>(rCoreTextStyle.mpFontData), nullptr);
|
||
CGFontRelease(pCGFont);
|
||
|
||
mpHbFont = createHbFont(pHbFace);
|
||
rCoreTextStyle.SetHbFont(mpHbFont);
|
||
|
||
hb_face_destroy(pHbFace);
|
||
}
|
||
}
|
||
|
||
#else
|
||
CommonSalLayout::CommonSalLayout(FreetypeFont& rFreetypeFont)
|
||
: mrFontSelData(rFreetypeFont.GetFontSelData()),
|
||
mrFreetypeFont(rFreetypeFont)
|
||
{
|
||
mpHbFont = rFreetypeFont.GetHbFont();
|
||
if (!mpHbFont)
|
||
{
|
||
hb_face_t* pHbFace = hb_face_create_for_tables(getFontTable, &rFreetypeFont, nullptr);
|
||
|
||
mpHbFont = createHbFont(pHbFace);
|
||
mrFreetypeFont.SetHbFont(mpHbFont);
|
||
|
||
hb_face_destroy(pHbFace);
|
||
}
|
||
}
|
||
#endif
|
||
|
||
struct HbScriptRun
|
||
{
|
||
int32_t mnMin;
|
||
int32_t mnEnd;
|
||
UScriptCode maScript;
|
||
|
||
HbScriptRun(int32_t nMin, int32_t nEnd, UScriptCode aScript)
|
||
: mnMin(nMin)
|
||
, mnEnd(nEnd)
|
||
, maScript(aScript)
|
||
{}
|
||
};
|
||
|
||
typedef std::vector<HbScriptRun> HbScriptRuns;
|
||
|
||
namespace vcl {
|
||
struct Run
|
||
{
|
||
int32_t nStart;
|
||
int32_t nEnd;
|
||
UScriptCode nCode;
|
||
Run(int32_t nStart_, int32_t nEnd_, UScriptCode nCode_)
|
||
: nStart(nStart_)
|
||
, nEnd(nEnd_)
|
||
, nCode(nCode_)
|
||
{}
|
||
};
|
||
|
||
class TextLayoutCache
|
||
{
|
||
public:
|
||
std::vector<vcl::Run> runs;
|
||
TextLayoutCache(sal_Unicode const* pStr, sal_Int32 const nEnd)
|
||
{
|
||
vcl::ScriptRun aScriptRun(
|
||
reinterpret_cast<const UChar *>(pStr),
|
||
nEnd);
|
||
while (aScriptRun.next())
|
||
{
|
||
runs.push_back(Run(aScriptRun.getScriptStart(),
|
||
aScriptRun.getScriptEnd(), aScriptRun.getScriptCode()));
|
||
}
|
||
}
|
||
};
|
||
}
|
||
|
||
std::shared_ptr<vcl::TextLayoutCache> CommonSalLayout::CreateTextLayoutCache(OUString const& rString) const
|
||
{
|
||
return std::make_shared<vcl::TextLayoutCache>(rString.getStr(), rString.getLength());
|
||
}
|
||
|
||
void CommonSalLayout::SetNeedFallback(ImplLayoutArgs& rArgs, sal_Int32 nCharPos, bool bRightToLeft)
|
||
{
|
||
if (nCharPos < 0)
|
||
return;
|
||
|
||
using namespace ::com::sun::star;
|
||
|
||
if (!mxBreak.is())
|
||
mxBreak = vcl::unohelper::CreateBreakIterator();
|
||
|
||
lang::Locale aLocale(rArgs.maLanguageTag.getLocale());
|
||
|
||
//if position nCharPos is missing in the font, grab the entire grapheme and
|
||
//mark all glyphs as missing so the whole thing is rendered with the same
|
||
//font
|
||
sal_Int32 nDone;
|
||
sal_Int32 nGraphemeStartPos =
|
||
mxBreak->previousCharacters(rArgs.mrStr, nCharPos + 1, aLocale,
|
||
i18n::CharacterIteratorMode::SKIPCELL, 1, nDone);
|
||
sal_Int32 nGraphemeEndPos =
|
||
mxBreak->nextCharacters(rArgs.mrStr, nCharPos, aLocale,
|
||
i18n::CharacterIteratorMode::SKIPCELL, 1, nDone);
|
||
|
||
rArgs.NeedFallback(nGraphemeStartPos, nGraphemeEndPos, bRightToLeft);
|
||
}
|
||
|
||
void CommonSalLayout::AdjustLayout(ImplLayoutArgs& rArgs)
|
||
{
|
||
SalLayout::AdjustLayout(rArgs);
|
||
|
||
if (rArgs.mpDXArray)
|
||
ApplyDXArray(rArgs);
|
||
else if (rArgs.mnLayoutWidth)
|
||
Justify(rArgs.mnLayoutWidth);
|
||
|
||
// apply asian kerning if the glyphs are not already formatted
|
||
if ((rArgs.mnFlags & SalLayoutFlags::KerningAsian)
|
||
&& !(rArgs.mnFlags & SalLayoutFlags::Vertical))
|
||
if ((rArgs.mpDXArray != nullptr) || (rArgs.mnLayoutWidth != 0))
|
||
ApplyAsianKerning(rArgs.mrStr);
|
||
}
|
||
|
||
void CommonSalLayout::DrawText(SalGraphics& rSalGraphics) const
|
||
{
|
||
//call platform dependent DrawText functions
|
||
rSalGraphics.DrawSalLayout( *this );
|
||
}
|
||
|
||
/* https://drafts.csswg.org/css-writing-modes-3/#script-orientations */
|
||
static int GetVerticalFlagsForScript(UScriptCode aScript)
|
||
{
|
||
int nFlag = GF_NONE;
|
||
|
||
switch (aScript)
|
||
{
|
||
/* ttb 0° */
|
||
case USCRIPT_BOPOMOFO:
|
||
case USCRIPT_EGYPTIAN_HIEROGLYPHS:
|
||
case USCRIPT_HAN:
|
||
case USCRIPT_HANGUL:
|
||
case USCRIPT_HIRAGANA:
|
||
case USCRIPT_KATAKANA:
|
||
case USCRIPT_MEROITIC_CURSIVE:
|
||
case USCRIPT_MEROITIC_HIEROGLYPHS:
|
||
case USCRIPT_YI:
|
||
nFlag = GF_ROTL;
|
||
break;
|
||
#if 0
|
||
/* ttb -90° */
|
||
case USCRIPT_ORKHON:
|
||
nFlag = ??;
|
||
break;
|
||
/* btt -90° */
|
||
case USCRIPT_OGHAM:
|
||
nFlag = ??;
|
||
break;
|
||
#endif
|
||
/* ttb 90°, no extra rotation needed */
|
||
case USCRIPT_MONGOLIAN:
|
||
case USCRIPT_PHAGS_PA:
|
||
/* horizontal scripts */
|
||
default:
|
||
break;
|
||
}
|
||
|
||
return nFlag;
|
||
}
|
||
|
||
bool CommonSalLayout::LayoutText(ImplLayoutArgs& rArgs)
|
||
{
|
||
hb_font_t* pHbFont = getHbFont();
|
||
hb_face_t* pHbFace = hb_font_get_face(pHbFont);
|
||
hb_script_t aHbScript = HB_SCRIPT_INVALID;
|
||
|
||
int nGlyphCapacity = 2 * (rArgs.mnEndCharPos - rArgs.mnMinCharPos);
|
||
Reserve(nGlyphCapacity);
|
||
|
||
const int nLength = rArgs.mrStr.getLength();
|
||
const sal_Unicode *pStr = rArgs.mrStr.getStr();
|
||
|
||
std::unique_ptr<vcl::TextLayoutCache> pNewScriptRun;
|
||
vcl::TextLayoutCache const* pTextLayout;
|
||
if (rArgs.m_pTextLayoutCache)
|
||
{
|
||
pTextLayout = rArgs.m_pTextLayoutCache; // use cache!
|
||
}
|
||
else
|
||
{
|
||
pNewScriptRun.reset(new vcl::TextLayoutCache(pStr, rArgs.mnEndCharPos));
|
||
pTextLayout = pNewScriptRun.get();
|
||
}
|
||
|
||
hb_buffer_t* pHbBuffer = hb_buffer_create();
|
||
hb_buffer_pre_allocate(pHbBuffer, nGlyphCapacity);
|
||
#if !HB_VERSION_ATLEAST(1, 1, 0)
|
||
static hb_unicode_funcs_t* pHbUnicodeFuncs = getUnicodeFuncs();
|
||
hb_buffer_set_unicode_funcs(pHbBuffer, pHbUnicodeFuncs);
|
||
#endif
|
||
|
||
ParseFeatures(mrFontSelData.maTargetName);
|
||
|
||
Point aCurrPos(0, 0);
|
||
while (true)
|
||
{
|
||
int nBidiMinRunPos, nBidiEndRunPos;
|
||
bool bRightToLeft;
|
||
if (!rArgs.GetNextRun(&nBidiMinRunPos, &nBidiEndRunPos, &bRightToLeft))
|
||
break;
|
||
|
||
// Find script subruns.
|
||
int nCurrentPos = nBidiMinRunPos;
|
||
HbScriptRuns aScriptSubRuns;
|
||
size_t k = 0;
|
||
for (; k < pTextLayout->runs.size(); ++k)
|
||
{
|
||
vcl::Run const& rRun(pTextLayout->runs[k]);
|
||
if (rRun.nStart <= nCurrentPos && nCurrentPos < rRun.nEnd)
|
||
{
|
||
break;
|
||
}
|
||
}
|
||
|
||
while (nCurrentPos < nBidiEndRunPos && k < pTextLayout->runs.size())
|
||
{
|
||
int32_t nMinRunPos = nCurrentPos;
|
||
int32_t nEndRunPos = std::min(pTextLayout->runs[k].nEnd, nBidiEndRunPos);
|
||
HbScriptRun aRun(nMinRunPos, nEndRunPos, pTextLayout->runs[k].nCode);
|
||
aScriptSubRuns.push_back(aRun);
|
||
|
||
nCurrentPos = nEndRunPos;
|
||
++k;
|
||
}
|
||
|
||
// RTL subruns should be reversed to ensure that final glyph order is
|
||
// correct.
|
||
if (bRightToLeft)
|
||
std::reverse(aScriptSubRuns.begin(), aScriptSubRuns.end());
|
||
|
||
for (const auto& aScriptRun : aScriptSubRuns)
|
||
{
|
||
hb_buffer_clear_contents(pHbBuffer);
|
||
|
||
int nMinRunPos = aScriptRun.mnMin;
|
||
int nEndRunPos = aScriptRun.mnEnd;
|
||
int nRunLen = nEndRunPos - nMinRunPos;
|
||
aHbScript = hb_icu_script_to_script(aScriptRun.maScript);
|
||
|
||
OString sLanguage = msLanguage;
|
||
if (sLanguage.isEmpty())
|
||
sLanguage = OUStringToOString(rArgs.maLanguageTag.getBcp47(), RTL_TEXTENCODING_ASCII_US);
|
||
|
||
bool bVertical = false;
|
||
if ((rArgs.mnFlags & SalLayoutFlags::Vertical) &&
|
||
GetVerticalFlagsForScript(aScriptRun.maScript) == GF_ROTL)
|
||
{
|
||
bVertical = true;
|
||
}
|
||
|
||
int nHbFlags = HB_BUFFER_FLAGS_DEFAULT;
|
||
if (nMinRunPos == 0)
|
||
nHbFlags |= HB_BUFFER_FLAG_BOT; /* Beginning-of-text */
|
||
if (nEndRunPos == nLength)
|
||
nHbFlags |= HB_BUFFER_FLAG_EOT; /* End-of-text */
|
||
|
||
if (bVertical)
|
||
hb_buffer_set_direction(pHbBuffer, HB_DIRECTION_TTB);
|
||
else
|
||
hb_buffer_set_direction(pHbBuffer, bRightToLeft ? HB_DIRECTION_RTL: HB_DIRECTION_LTR);
|
||
hb_buffer_set_script(pHbBuffer, aHbScript);
|
||
hb_buffer_set_language(pHbBuffer, hb_language_from_string(sLanguage.getStr(), -1));
|
||
hb_buffer_set_flags(pHbBuffer, (hb_buffer_flags_t) nHbFlags);
|
||
hb_buffer_add_utf16(
|
||
pHbBuffer, reinterpret_cast<uint16_t const *>(pStr), nLength,
|
||
nMinRunPos, nRunLen);
|
||
#if HB_VERSION_ATLEAST(0, 9, 42)
|
||
hb_buffer_set_cluster_level(pHbBuffer, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS);
|
||
#endif
|
||
// The shapers that we want HarfBuzz to use, in the order of
|
||
// preference. The coretext_aat shaper is available only on macOS,
|
||
// but there is no harm in always including it, HarfBuzz will
|
||
// ignore unavailable shapers.
|
||
const char* pHbShapers[] = { "coretext_aat", "graphite2", "ot", "fallback", nullptr };
|
||
hb_segment_properties_t aHbProps;
|
||
hb_buffer_get_segment_properties(pHbBuffer, &aHbProps);
|
||
hb_shape_plan_t* pHbPlan = hb_shape_plan_create_cached(pHbFace, &aHbProps, maFeatures.data(), maFeatures.size(), pHbShapers);
|
||
bool ok = hb_shape_plan_execute(pHbPlan, pHbFont, pHbBuffer, maFeatures.data(), maFeatures.size());
|
||
assert(ok);
|
||
(void) ok;
|
||
hb_buffer_set_content_type(pHbBuffer, HB_BUFFER_CONTENT_TYPE_GLYPHS);
|
||
SAL_INFO("vcl.harfbuzz", hb_shape_plan_get_shaper(pHbPlan) << " shaper used for " << mrFontSelData.GetFamilyName());
|
||
|
||
int nRunGlyphCount = hb_buffer_get_length(pHbBuffer);
|
||
hb_glyph_info_t *pHbGlyphInfos = hb_buffer_get_glyph_infos(pHbBuffer, nullptr);
|
||
hb_glyph_position_t *pHbPositions = hb_buffer_get_glyph_positions(pHbBuffer, nullptr);
|
||
|
||
for (int i = 0; i < nRunGlyphCount; ++i) {
|
||
int32_t nGlyphIndex = pHbGlyphInfos[i].codepoint;
|
||
int32_t nCharPos = pHbGlyphInfos[i].cluster;
|
||
|
||
// if needed request glyph fallback by updating LayoutArgs
|
||
if (!nGlyphIndex)
|
||
{
|
||
SetNeedFallback(rArgs, nCharPos, bRightToLeft);
|
||
if (SalLayoutFlags::ForFallback & rArgs.mnFlags)
|
||
continue;
|
||
}
|
||
|
||
sal_Int32 indexUtf16 = nCharPos;
|
||
sal_UCS4 aChar = rArgs.mrStr.iterateCodePoints(&indexUtf16, 0);
|
||
|
||
bool bInCluster = false;
|
||
if (i > 0 && pHbGlyphInfos[i].cluster == pHbGlyphInfos[i - 1].cluster)
|
||
bInCluster = true;
|
||
|
||
long nGlyphFlags = 0;
|
||
if (bRightToLeft)
|
||
nGlyphFlags |= GlyphItem::IS_RTL_GLYPH;
|
||
|
||
if (bInCluster)
|
||
nGlyphFlags |= GlyphItem::IS_IN_CLUSTER;
|
||
|
||
bool bDiacritic = false;
|
||
if (hb_ot_layout_has_glyph_classes(pHbFace))
|
||
{
|
||
// the font has GDEF table
|
||
if (pHbPositions[i].x_advance == 0)
|
||
bDiacritic = hb_ot_layout_get_glyph_class(pHbFace, nGlyphIndex) == HB_OT_LAYOUT_GLYPH_CLASS_MARK;
|
||
}
|
||
else
|
||
{
|
||
#if HB_VERSION_ATLEAST(0, 9, 42)
|
||
if (u_getIntPropertyValue(aChar, UCHAR_GENERAL_CATEGORY) == U_NON_SPACING_MARK)
|
||
bDiacritic = true;
|
||
#else
|
||
// the font lacks GDEF table
|
||
if (pHbPositions[i].x_advance == 0)
|
||
bDiacritic = true;
|
||
#endif
|
||
}
|
||
|
||
if (bDiacritic)
|
||
nGlyphFlags |= GlyphItem::IS_DIACRITIC;
|
||
|
||
int32_t nAdvance, nXOffset, nYOffset;
|
||
if (bVertical)
|
||
{
|
||
int nVertFlag;
|
||
#if 0 /* XXX: does not work as expected for Common script */
|
||
UErrorCode error = U_ZERO_ERROR;
|
||
nVertFlag = GetVerticalFlagsForScript(uscript_getScript(aChar, &error));
|
||
#else
|
||
nVertFlag = GetVerticalFlags(aChar);
|
||
if (nVertFlag == GF_ROTR)
|
||
nVertFlag = GF_ROTL;
|
||
#endif
|
||
nGlyphIndex |= nVertFlag;
|
||
nAdvance = -pHbPositions[i].y_advance >> 6;
|
||
nXOffset = pHbPositions[i].y_offset >> 6;
|
||
nYOffset = -pHbPositions[i].x_offset >> 6;
|
||
}
|
||
else
|
||
{
|
||
nAdvance = pHbPositions[i].x_advance >> 6;
|
||
nXOffset = pHbPositions[i].x_offset >> 6;
|
||
nYOffset = pHbPositions[i].y_offset >> 6;
|
||
}
|
||
|
||
Point aNewPos = Point(aCurrPos.X() + nXOffset, -(aCurrPos.Y() + nYOffset));
|
||
const GlyphItem aGI(nCharPos, nGlyphIndex, aNewPos, nGlyphFlags, nAdvance, nXOffset);
|
||
AppendGlyph(aGI);
|
||
|
||
aCurrPos.X() += nAdvance;
|
||
}
|
||
}
|
||
}
|
||
|
||
hb_buffer_destroy(pHbBuffer);
|
||
|
||
// sort glyphs in visual order
|
||
// and then in logical order (e.g. diacritics after cluster start)
|
||
// XXX: why?
|
||
SortGlyphItems();
|
||
|
||
// determine need for kashida justification
|
||
if ((rArgs.mpDXArray || rArgs.mnLayoutWidth)
|
||
&& ((aHbScript == HB_SCRIPT_ARABIC) || (aHbScript == HB_SCRIPT_SYRIAC)))
|
||
rArgs.mnFlags |= SalLayoutFlags::KashidaJustification;
|
||
|
||
return true;
|
||
}
|
||
|
||
bool CommonSalLayout::GetCharWidths(DeviceCoordinate* pCharWidths) const
|
||
{
|
||
int nCharCount = mnEndCharPos - mnMinCharPos;
|
||
|
||
for (int i = 0; i < nCharCount; ++i)
|
||
pCharWidths[i] = 0;
|
||
|
||
for (auto const& aGlyphItem : m_GlyphItems)
|
||
pCharWidths[aGlyphItem.mnCharPos - mnMinCharPos] += aGlyphItem.mnNewWidth;
|
||
|
||
return true;
|
||
}
|
||
|
||
// A note on how Kashida justification is implemented (because it took me 5
|
||
// years to figure it out):
|
||
// The decision to insert Kashidas, where and how much is taken by Writer.
|
||
// This decision is communicated to us in a very indirect way; by increasing
|
||
// the width of the character after which Kashidas should be inserted by the
|
||
// desired amount.
|
||
//
|
||
// Writer eventually calls IsKashidaPosValid() to check whether it can insert a
|
||
// Kashida between two characters or not.
|
||
//
|
||
// Here we do:
|
||
// - In LayoutText() set KashidaJustification flag based on text script.
|
||
// - In ApplyDXArray():
|
||
// * Check the above flag to decide whether to insert Kashidas or not.
|
||
// * For any RTL glyph that has DX adjustment, insert enough Kashidas to
|
||
// fill in the added space.
|
||
|
||
void CommonSalLayout::ApplyDXArray(ImplLayoutArgs& rArgs)
|
||
{
|
||
if (rArgs.mpDXArray == nullptr)
|
||
return;
|
||
|
||
int nCharCount = mnEndCharPos - mnMinCharPos;
|
||
std::unique_ptr<DeviceCoordinate[]> const pOldCharWidths(new DeviceCoordinate[nCharCount]);
|
||
std::unique_ptr<DeviceCoordinate[]> const pNewCharWidths(new DeviceCoordinate[nCharCount]);
|
||
|
||
// Get the natural character widths (i.e. before applying DX adjustments).
|
||
GetCharWidths(pOldCharWidths.get());
|
||
|
||
// Calculate the character widths after DX adjustments.
|
||
for (int i = 0; i < nCharCount; ++i)
|
||
{
|
||
if (i == 0)
|
||
pNewCharWidths[i] = rArgs.mpDXArray[i];
|
||
else
|
||
pNewCharWidths[i] = rArgs.mpDXArray[i] - rArgs.mpDXArray[i - 1];
|
||
}
|
||
|
||
|
||
bool bKashidaJustify = false;
|
||
DeviceCoordinate nKashidaWidth = 0;
|
||
hb_codepoint_t nKashidaIndex = 0;
|
||
if (rArgs.mnFlags & SalLayoutFlags::KashidaJustification)
|
||
{
|
||
// Find Kashida glyph width and index.
|
||
hb_font_t* pHbFont = getHbFont();
|
||
if (hb_font_get_glyph(pHbFont, 0x0640, 0, &nKashidaIndex))
|
||
nKashidaWidth = hb_font_get_glyph_h_advance(pHbFont, nKashidaIndex) / 64;
|
||
bKashidaJustify = nKashidaWidth != 0;
|
||
}
|
||
|
||
// Map of Kashida insertion points (in the glyph items vector) and the
|
||
// requested width.
|
||
std::map<size_t, DeviceCoordinate> pKashidas;
|
||
|
||
// The accumulated difference in X position.
|
||
DeviceCoordinate nDelta = 0;
|
||
|
||
// Apply the DX adjustments to glyph positions and widths.
|
||
size_t i = 0;
|
||
while (i < m_GlyphItems.size())
|
||
{
|
||
int nCharPos = m_GlyphItems[i].mnCharPos - mnMinCharPos;
|
||
DeviceCoordinate nDiff = pNewCharWidths[nCharPos] - pOldCharWidths[nCharPos];
|
||
|
||
// nDiff > 1 to ignore rounding errors.
|
||
if (bKashidaJustify && nDiff > 1)
|
||
pKashidas[i] = nDiff;
|
||
|
||
// Adjust the width of the first glyph belonging to current character.
|
||
m_GlyphItems[i].mnNewWidth += nDiff;
|
||
|
||
// Apply the X position of all glyphs belonging to current character.
|
||
size_t j = i;
|
||
while (j < m_GlyphItems.size())
|
||
{
|
||
if (m_GlyphItems[j].mnCharPos != m_GlyphItems[i].mnCharPos)
|
||
break;
|
||
m_GlyphItems[j].maLinearPos.X() += nDelta;
|
||
// For RTL, put all DX adjustment space to the left of the glyph.
|
||
if (m_GlyphItems[i].IsRTLGlyph())
|
||
m_GlyphItems[j].maLinearPos.X() += nDiff;
|
||
++j;
|
||
}
|
||
|
||
// Increment the delta, the loop above makes sure we do so only once
|
||
// for every character not for every glyph (otherwise we would apply it
|
||
// multiple times for each glyphs belonging to the same character which
|
||
// is wrong since DX adjustments are character based).
|
||
nDelta += nDiff;
|
||
i = j;
|
||
}
|
||
|
||
// Insert Kashida glyphs.
|
||
if (bKashidaJustify && !pKashidas.empty())
|
||
{
|
||
size_t nInserted = 0;
|
||
for (auto const& pKashida : pKashidas)
|
||
{
|
||
auto pGlyphIter = m_GlyphItems.begin() + nInserted + pKashida.first;
|
||
|
||
// Don’t insert Kashida after LTR glyphs.
|
||
if (!pGlyphIter->IsRTLGlyph())
|
||
continue;
|
||
|
||
// Don’t insert Kashida after space.
|
||
sal_Int32 indexUtf16 = pGlyphIter->mnCharPos;
|
||
sal_UCS4 aChar = rArgs.mrStr.iterateCodePoints(&indexUtf16, 0);
|
||
if (u_isUWhiteSpace(aChar))
|
||
continue;
|
||
|
||
// The total Kashida width.
|
||
DeviceCoordinate nTotalWidth = pKashida.second;
|
||
|
||
// Number of times to repeat each Kashida.
|
||
int nCopies = 1;
|
||
if (nTotalWidth > nKashidaWidth)
|
||
nCopies = nTotalWidth / nKashidaWidth;
|
||
|
||
// See if we can improve the fit by adding an extra Kashidas and
|
||
// squeezing them together a bit.
|
||
DeviceCoordinate nOverlap = 0;
|
||
DeviceCoordinate nShortfall = nTotalWidth - nKashidaWidth * nCopies;
|
||
if (nShortfall > 0)
|
||
{
|
||
++nCopies;
|
||
DeviceCoordinate nExcess = nCopies * nKashidaWidth - nTotalWidth;
|
||
if (nExcess > 0)
|
||
nOverlap = nExcess / (nCopies - 1);
|
||
}
|
||
|
||
Point aPos(pGlyphIter->maLinearPos.X() - nTotalWidth, 0);
|
||
int nCharPos = pGlyphIter->mnCharPos;
|
||
int nFlags = GlyphItem::IS_IN_CLUSTER | GlyphItem::IS_RTL_GLYPH;
|
||
while (nCopies--)
|
||
{
|
||
GlyphItem aKashida(nCharPos, nKashidaIndex, aPos, nFlags, nKashidaWidth);
|
||
pGlyphIter = m_GlyphItems.insert(pGlyphIter, aKashida);
|
||
aPos.X() += nKashidaWidth;
|
||
aPos.X() -= nOverlap;
|
||
++pGlyphIter;
|
||
++nInserted;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
bool CommonSalLayout::IsKashidaPosValid(int nCharPos) const
|
||
{
|
||
for (auto pIter = m_GlyphItems.begin(); pIter != m_GlyphItems.end(); ++pIter)
|
||
{
|
||
if (pIter->mnCharPos == nCharPos)
|
||
{
|
||
// Search backwards for previous glyph belonging to a different
|
||
// character. We are looking backwards because we are dealing with
|
||
// RTL glyphs, which will be in visual order.
|
||
for (auto pPrev = pIter - 1; pPrev != m_GlyphItems.begin(); --pPrev)
|
||
{
|
||
if (pPrev->mnCharPos != nCharPos)
|
||
{
|
||
// Check if the found glyph belongs to the next character,
|
||
// otherwise the current glyph will be a ligature which is
|
||
// invalid kashida position.
|
||
if (pPrev->mnCharPos == (nCharPos + 1))
|
||
return true;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|