2016-06-14 14:38:12 +05:30
|
|
|
|
/* -*- 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 .
|
|
|
|
|
*/
|
|
|
|
|
|
2017-04-14 08:42:15 +10:00
|
|
|
|
#include <memory>
|
2018-05-10 12:48:13 +02:00
|
|
|
|
|
|
|
|
|
#include <hb-icu.h>
|
2019-06-10 12:30:21 +02:00
|
|
|
|
#include <hb-ot.h>
|
2019-07-24 10:25:44 +07:00
|
|
|
|
#include <hb-graphite2.h>
|
2018-05-10 12:48:13 +02:00
|
|
|
|
|
|
|
|
|
#include <sallayout.hxx>
|
2016-06-14 14:38:12 +05:30
|
|
|
|
|
2018-07-28 15:57:23 +02:00
|
|
|
|
#include <sal/log.hxx>
|
2018-03-06 10:23:06 +00:00
|
|
|
|
#include <unotools/configmgr.hxx>
|
2016-06-14 14:38:12 +05:30
|
|
|
|
#include <vcl/unohelp.hxx>
|
2018-06-15 19:32:15 +02:00
|
|
|
|
#include <vcl/font/Feature.hxx>
|
|
|
|
|
#include <vcl/font/FeatureParser.hxx>
|
2016-06-14 14:38:12 +05:30
|
|
|
|
#include <scrptrun.h>
|
|
|
|
|
#include <com/sun/star/i18n/CharacterIteratorMode.hpp>
|
|
|
|
|
#include <salgdi.hxx>
|
2016-10-10 01:36:45 +02:00
|
|
|
|
#include <unicode/uchar.h>
|
2016-12-20 09:11:45 +01:00
|
|
|
|
|
2017-12-26 15:14:31 +00:00
|
|
|
|
#include <fontselect.hxx>
|
2016-09-07 19:40:11 +02:00
|
|
|
|
|
2016-10-10 01:36:45 +02:00
|
|
|
|
#if !HB_VERSION_ATLEAST(1, 1, 0)
|
2016-10-24 10:08:09 +02:00
|
|
|
|
// 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;
|
|
|
|
|
}
|
|
|
|
|
|
2016-06-14 14:38:12 +05:30
|
|
|
|
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;
|
|
|
|
|
}
|
2016-10-10 01:36:45 +02:00
|
|
|
|
#endif
|
2016-06-14 14:38:12 +05:30
|
|
|
|
|
2018-05-10 12:48:13 +02:00
|
|
|
|
GenericSalLayout::GenericSalLayout(LogicalFontInstance &rFont)
|
2018-10-29 13:32:03 +00:00
|
|
|
|
: mpVertGlyphs(nullptr)
|
2017-12-26 15:14:31 +00:00
|
|
|
|
, mbFuzzing(utl::ConfigManager::IsFuzzing())
|
|
|
|
|
{
|
2019-03-11 08:15:13 +02:00
|
|
|
|
new SalLayoutGlyphsImpl(m_GlyphItems, rFont);
|
2017-12-26 15:14:31 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-05-10 12:48:13 +02:00
|
|
|
|
GenericSalLayout::~GenericSalLayout()
|
2017-12-26 15:14:31 +00:00
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-10 12:48:13 +02:00
|
|
|
|
void GenericSalLayout::ParseFeatures(const OUString& aName)
|
2016-10-17 15:22:32 +01:00
|
|
|
|
{
|
2018-06-15 19:32:15 +02:00
|
|
|
|
vcl::font::FeatureParser aParser(aName);
|
2018-10-23 09:47:38 +02:00
|
|
|
|
const OUString& sLanguage = aParser.getLanguage();
|
2018-06-15 19:32:15 +02:00
|
|
|
|
if (!sLanguage.isEmpty())
|
|
|
|
|
msLanguage = OUStringToOString(sLanguage, RTL_TEXTENCODING_ASCII_US);
|
2016-12-14 17:51:33 +02:00
|
|
|
|
|
2019-03-11 20:55:24 +02:00
|
|
|
|
for (auto const &rFeat : aParser.getFeatures())
|
2016-10-17 15:22:32 +01:00
|
|
|
|
{
|
2019-03-11 20:55:24 +02:00
|
|
|
|
hb_feature_t aFeature { rFeat.m_nTag, rFeat.m_nValue, rFeat.m_nStart, rFeat.m_nEnd };
|
2018-06-15 19:32:15 +02:00
|
|
|
|
maFeatures.push_back(aFeature);
|
2016-10-17 15:22:32 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-11-09 23:50:53 +02:00
|
|
|
|
struct SubRun
|
2016-06-14 14:38:12 +05:30
|
|
|
|
{
|
|
|
|
|
int32_t mnMin;
|
|
|
|
|
int32_t mnEnd;
|
2016-11-10 03:59:28 +02:00
|
|
|
|
hb_script_t maScript;
|
2016-11-10 00:57:08 +02:00
|
|
|
|
hb_direction_t maDirection;
|
2016-06-14 14:38:12 +05:30
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
namespace vcl {
|
|
|
|
|
struct Run
|
|
|
|
|
{
|
2018-09-13 13:08:33 +02:00
|
|
|
|
int32_t const nStart;
|
|
|
|
|
int32_t const nEnd;
|
|
|
|
|
UScriptCode const nCode;
|
2016-06-14 14:38:12 +05:30
|
|
|
|
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())
|
|
|
|
|
{
|
2017-09-13 16:44:19 +02:00
|
|
|
|
runs.emplace_back(aScriptRun.getScriptStart(),
|
|
|
|
|
aScriptRun.getScriptEnd(), aScriptRun.getScriptCode());
|
2016-06-14 14:38:12 +05:30
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
2017-03-19 17:01:02 +02:00
|
|
|
|
} // namespace vcl
|
2016-11-09 23:39:44 +02:00
|
|
|
|
|
2017-03-19 17:01:02 +02:00
|
|
|
|
namespace {
|
2019-06-04 03:57:00 +02:00
|
|
|
|
#if U_ICU_VERSION_MAJOR_NUM >= 63
|
|
|
|
|
enum class VerticalOrientation {
|
|
|
|
|
Upright = U_VO_UPRIGHT,
|
|
|
|
|
Rotated = U_VO_ROTATED,
|
|
|
|
|
TransformedUpright = U_VO_TRANSFORMED_UPRIGHT,
|
|
|
|
|
TransformedRotated = U_VO_TRANSFORMED_ROTATED
|
|
|
|
|
};
|
|
|
|
|
#else
|
2016-11-09 23:39:44 +02:00
|
|
|
|
#include "VerticalOrientationData.cxx"
|
|
|
|
|
|
2017-03-19 17:01:02 +02:00
|
|
|
|
// These must match the values in the file included above.
|
|
|
|
|
enum class VerticalOrientation {
|
|
|
|
|
Upright = 0,
|
|
|
|
|
Rotated = 1,
|
|
|
|
|
TransformedUpright = 2,
|
|
|
|
|
TransformedRotated = 3
|
|
|
|
|
};
|
2019-06-04 03:57:00 +02:00
|
|
|
|
#endif
|
2017-03-19 17:01:02 +02:00
|
|
|
|
|
2017-03-17 20:00:03 +08:00
|
|
|
|
VerticalOrientation GetVerticalOrientation(sal_UCS4 cCh, const LanguageTag& rTag)
|
2016-11-09 23:39:44 +02:00
|
|
|
|
{
|
2017-10-01 21:16:06 +08:00
|
|
|
|
// Override orientation of fullwidth colon , semi-colon,
|
|
|
|
|
// and Bopomofo tonal marks.
|
|
|
|
|
if ((cCh == 0xff1a || cCh == 0xff1b
|
|
|
|
|
|| cCh == 0x2ca || cCh == 0x2cb || cCh == 0x2c7 || cCh == 0x2d9)
|
|
|
|
|
&& rTag.getLanguage() == "zh")
|
2017-03-18 08:00:04 +02:00
|
|
|
|
return VerticalOrientation::TransformedUpright;
|
|
|
|
|
|
2019-06-04 03:57:00 +02:00
|
|
|
|
#if U_ICU_VERSION_MAJOR_NUM >= 63
|
|
|
|
|
int32_t nRet = u_getIntPropertyValue(cCh, UCHAR_VERTICAL_ORIENTATION);
|
|
|
|
|
#else
|
2016-11-09 23:39:44 +02:00
|
|
|
|
uint8_t nRet = 1;
|
|
|
|
|
|
|
|
|
|
if (cCh < 0x10000)
|
|
|
|
|
{
|
|
|
|
|
nRet = sVerticalOrientationValues[sVerticalOrientationPages[0][cCh >> kVerticalOrientationCharBits]]
|
|
|
|
|
[cCh & ((1 << kVerticalOrientationCharBits) - 1)];
|
|
|
|
|
}
|
|
|
|
|
else if (cCh < (kVerticalOrientationMaxPlane + 1) * 0x10000)
|
|
|
|
|
{
|
|
|
|
|
nRet = sVerticalOrientationValues[sVerticalOrientationPages[sVerticalOrientationPlanes[(cCh >> 16) - 1]]
|
|
|
|
|
[(cCh & 0xffff) >> kVerticalOrientationCharBits]]
|
|
|
|
|
[cCh & ((1 << kVerticalOrientationCharBits) - 1)];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Default value for unassigned
|
|
|
|
|
SAL_WARN("vcl.gdi", "Getting VerticalOrientation for codepoint outside Unicode range");
|
|
|
|
|
}
|
2019-06-04 03:57:00 +02:00
|
|
|
|
#endif
|
2016-11-09 23:39:44 +02:00
|
|
|
|
|
|
|
|
|
return VerticalOrientation(nRet);
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-19 17:01:02 +02:00
|
|
|
|
} // namespace
|
2016-06-14 14:38:12 +05:30
|
|
|
|
|
2019-01-23 19:59:45 +01:00
|
|
|
|
std::shared_ptr<vcl::TextLayoutCache> GenericSalLayout::CreateTextLayoutCache(OUString const& rString)
|
2016-06-14 14:38:12 +05:30
|
|
|
|
{
|
|
|
|
|
return std::make_shared<vcl::TextLayoutCache>(rString.getStr(), rString.getLength());
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-16 17:34:49 +02:00
|
|
|
|
const SalLayoutGlyphs* GenericSalLayout::GetGlyphs() const
|
2018-08-16 12:13:18 +02:00
|
|
|
|
{
|
2018-08-16 17:34:49 +02:00
|
|
|
|
return &m_GlyphItems;
|
2018-08-16 12:13:18 +02:00
|
|
|
|
}
|
|
|
|
|
|
2018-05-10 12:48:13 +02:00
|
|
|
|
void GenericSalLayout::SetNeedFallback(ImplLayoutArgs& rArgs, sal_Int32 nCharPos, bool bRightToLeft)
|
2016-06-14 14:38:12 +05:30
|
|
|
|
{
|
2018-03-06 10:23:06 +00:00
|
|
|
|
if (nCharPos < 0 || mbFuzzing)
|
2016-06-14 14:38:12 +05:30
|
|
|
|
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 nGraphemeEndPos =
|
|
|
|
|
mxBreak->nextCharacters(rArgs.mrStr, nCharPos, aLocale,
|
2019-06-02 10:00:41 +08:00
|
|
|
|
i18n::CharacterIteratorMode::SKIPCELL, 1, nDone);
|
|
|
|
|
// Safely advance nCharPos in case it is a non-BMP character.
|
|
|
|
|
rArgs.mrStr.iterateCodePoints(&nCharPos);
|
|
|
|
|
sal_Int32 nGraphemeStartPos =
|
|
|
|
|
mxBreak->previousCharacters(rArgs.mrStr, nCharPos, aLocale,
|
2016-06-14 14:38:12 +05:30
|
|
|
|
i18n::CharacterIteratorMode::SKIPCELL, 1, nDone);
|
|
|
|
|
|
|
|
|
|
rArgs.NeedFallback(nGraphemeStartPos, nGraphemeEndPos, bRightToLeft);
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-10 12:48:13 +02:00
|
|
|
|
void GenericSalLayout::AdjustLayout(ImplLayoutArgs& rArgs)
|
2016-06-14 14:38:12 +05:30
|
|
|
|
{
|
2016-10-09 23:23:45 +02:00
|
|
|
|
SalLayout::AdjustLayout(rArgs);
|
|
|
|
|
|
|
|
|
|
if (rArgs.mpDXArray)
|
|
|
|
|
ApplyDXArray(rArgs);
|
|
|
|
|
else if (rArgs.mnLayoutWidth)
|
|
|
|
|
Justify(rArgs.mnLayoutWidth);
|
2016-06-14 14:38:12 +05:30
|
|
|
|
// apply asian kerning if the glyphs are not already formatted
|
2019-04-27 21:53:21 +02:00
|
|
|
|
else if ((rArgs.mnFlags & SalLayoutFlags::KerningAsian)
|
|
|
|
|
&& !(rArgs.mnFlags & SalLayoutFlags::Vertical))
|
|
|
|
|
ApplyAsianKerning(rArgs.mrStr);
|
2016-06-14 14:38:12 +05:30
|
|
|
|
}
|
|
|
|
|
|
2018-05-10 12:48:13 +02:00
|
|
|
|
void GenericSalLayout::DrawText(SalGraphics& rSalGraphics) const
|
2016-06-14 14:38:12 +05:30
|
|
|
|
{
|
|
|
|
|
//call platform dependent DrawText functions
|
2016-11-24 01:32:11 +02:00
|
|
|
|
rSalGraphics.DrawTextLayout( *this );
|
2016-06-14 14:38:12 +05:30
|
|
|
|
}
|
|
|
|
|
|
2017-03-05 00:04:35 +02:00
|
|
|
|
// Find if the nominal glyph of the character is an input to “vert” feature.
|
2016-11-19 21:32:19 +02:00
|
|
|
|
// We don’t check for a specific script or language as it shouldn’t matter
|
|
|
|
|
// here; if the glyph would be the result from applying “vert” for any
|
|
|
|
|
// script/language then we want to always treat it as upright glyph.
|
2018-05-10 12:48:13 +02:00
|
|
|
|
bool GenericSalLayout::HasVerticalAlternate(sal_UCS4 aChar, sal_UCS4 aVariationSelector)
|
2016-11-19 21:32:19 +02:00
|
|
|
|
{
|
2017-03-05 00:04:35 +02:00
|
|
|
|
hb_codepoint_t nGlyphIndex = 0;
|
2018-10-29 13:32:03 +00:00
|
|
|
|
hb_font_t *pHbFont = GetFont().GetHbFont();
|
2017-12-26 15:14:31 +00:00
|
|
|
|
if (!hb_font_get_glyph(pHbFont, aChar, aVariationSelector, &nGlyphIndex))
|
2017-03-05 00:04:35 +02:00
|
|
|
|
return false;
|
|
|
|
|
|
2016-11-19 21:32:19 +02:00
|
|
|
|
if (!mpVertGlyphs)
|
|
|
|
|
{
|
2017-12-26 15:14:31 +00:00
|
|
|
|
hb_face_t* pHbFace = hb_font_get_face(pHbFont);
|
2016-11-19 21:32:19 +02:00
|
|
|
|
mpVertGlyphs = hb_set_create();
|
|
|
|
|
|
|
|
|
|
// Find all GSUB lookups for “vert” feature.
|
|
|
|
|
hb_set_t* pLookups = hb_set_create();
|
2017-07-30 12:16:31 +02:00
|
|
|
|
hb_tag_t const pFeatures[] = { HB_TAG('v','e','r','t'), HB_TAG_NONE };
|
2016-11-19 21:32:19 +02:00
|
|
|
|
hb_ot_layout_collect_lookups(pHbFace, HB_OT_TAG_GSUB, nullptr, nullptr, pFeatures, pLookups);
|
|
|
|
|
if (!hb_set_is_empty(pLookups))
|
|
|
|
|
{
|
|
|
|
|
// Find the output glyphs in each lookup (i.e. the glyphs that
|
|
|
|
|
// would result from applying this lookup).
|
|
|
|
|
hb_codepoint_t nIdx = HB_SET_VALUE_INVALID;
|
|
|
|
|
while (hb_set_next(pLookups, &nIdx))
|
|
|
|
|
{
|
|
|
|
|
hb_set_t* pGlyphs = hb_set_create();
|
2017-03-05 00:04:35 +02:00
|
|
|
|
hb_ot_layout_lookup_collect_glyphs(pHbFace, HB_OT_TAG_GSUB, nIdx,
|
|
|
|
|
nullptr, // glyphs before
|
|
|
|
|
pGlyphs, // glyphs input
|
|
|
|
|
nullptr, // glyphs after
|
|
|
|
|
nullptr); // glyphs out
|
2016-11-19 21:32:19 +02:00
|
|
|
|
hb_set_union(mpVertGlyphs, pGlyphs);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-25 10:33:32 +02:00
|
|
|
|
return hb_set_has(mpVertGlyphs, nGlyphIndex) != 0;
|
2016-11-19 21:32:19 +02:00
|
|
|
|
}
|
|
|
|
|
|
2018-08-16 13:51:43 +02:00
|
|
|
|
bool GenericSalLayout::LayoutText(ImplLayoutArgs& rArgs, const SalLayoutGlyphs* pGlyphs)
|
2016-06-14 14:38:12 +05:30
|
|
|
|
{
|
2018-08-10 17:49:15 +02:00
|
|
|
|
// No need to touch m_GlyphItems at all for an empty string.
|
|
|
|
|
if (rArgs.mnEndCharPos - rArgs.mnMinCharPos <= 0)
|
|
|
|
|
return true;
|
|
|
|
|
|
2018-08-16 13:51:43 +02:00
|
|
|
|
if (pGlyphs)
|
|
|
|
|
{
|
|
|
|
|
// Work with pre-computed glyph items.
|
|
|
|
|
m_GlyphItems = *pGlyphs;
|
2019-03-22 17:57:46 +01:00
|
|
|
|
// Some flags are set as a side effect of text layout, restore them here.
|
|
|
|
|
rArgs.mnFlags |= pGlyphs->Impl()->mnFlags;
|
2018-08-16 13:51:43 +02:00
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-29 13:32:03 +00:00
|
|
|
|
hb_font_t *pHbFont = GetFont().GetHbFont();
|
2019-11-08 11:05:04 +02:00
|
|
|
|
bool isGraphite = GetFont().IsGraphiteFont();
|
2016-06-14 14:38:12 +05:30
|
|
|
|
|
|
|
|
|
int nGlyphCapacity = 2 * (rArgs.mnEndCharPos - rArgs.mnMinCharPos);
|
2018-10-27 21:44:39 +02:00
|
|
|
|
m_GlyphItems.Impl()->reserve(nGlyphCapacity);
|
2016-06-14 14:38:12 +05:30
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-20 16:37:22 +02:00
|
|
|
|
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
|
|
|
|
|
|
2018-10-29 13:32:03 +00:00
|
|
|
|
const FontSelectPattern& rFontSelData = GetFont().GetFontSelectPattern();
|
2017-02-12 11:08:59 +02:00
|
|
|
|
if (rArgs.mnFlags & SalLayoutFlags::DisableKerning)
|
|
|
|
|
{
|
2017-12-26 15:14:31 +00:00
|
|
|
|
SAL_INFO("vcl.harfbuzz", "Disabling kerning for font: " << rFontSelData.maTargetName);
|
2017-02-12 11:08:59 +02:00
|
|
|
|
maFeatures.push_back({ HB_TAG('k','e','r','n'), 0, 0, static_cast<unsigned int>(-1) });
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-26 15:14:31 +00:00
|
|
|
|
ParseFeatures(rFontSelData.maTargetName);
|
2016-10-30 18:50:01 +02:00
|
|
|
|
|
2016-11-01 02:15:23 +02:00
|
|
|
|
double nXScale = 0;
|
|
|
|
|
double nYScale = 0;
|
2018-10-29 13:32:03 +00:00
|
|
|
|
GetFont().GetScale(&nXScale, &nYScale);
|
2016-11-01 02:15:23 +02:00
|
|
|
|
|
2016-06-14 14:38:12 +05:30
|
|
|
|
Point aCurrPos(0, 0);
|
|
|
|
|
while (true)
|
|
|
|
|
{
|
|
|
|
|
int nBidiMinRunPos, nBidiEndRunPos;
|
|
|
|
|
bool bRightToLeft;
|
|
|
|
|
if (!rArgs.GetNextRun(&nBidiMinRunPos, &nBidiEndRunPos, &bRightToLeft))
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
// Find script subruns.
|
2016-11-09 23:50:53 +02:00
|
|
|
|
std::vector<SubRun> aSubRuns;
|
2019-07-24 10:25:44 +07:00
|
|
|
|
int nCurrentPos = nBidiMinRunPos;
|
2016-06-14 14:38:12 +05:30
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-24 10:25:44 +07:00
|
|
|
|
if (isGraphite)
|
2016-06-14 14:38:12 +05:30
|
|
|
|
{
|
2016-11-10 03:59:28 +02:00
|
|
|
|
hb_script_t aScript = hb_icu_script_to_script(pTextLayout->runs[k].nCode);
|
2019-07-24 10:25:44 +07:00
|
|
|
|
aSubRuns.push_back({ nBidiMinRunPos, nBidiEndRunPos, aScript, bRightToLeft ? HB_DIRECTION_RTL : HB_DIRECTION_LTR });
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
while (nCurrentPos < nBidiEndRunPos && k < pTextLayout->runs.size())
|
2016-11-10 00:57:08 +02:00
|
|
|
|
{
|
2019-07-24 10:25:44 +07:00
|
|
|
|
int32_t nMinRunPos = nCurrentPos;
|
|
|
|
|
int32_t nEndRunPos = std::min(pTextLayout->runs[k].nEnd, nBidiEndRunPos);
|
|
|
|
|
hb_direction_t aDirection = bRightToLeft ? HB_DIRECTION_RTL : HB_DIRECTION_LTR;
|
|
|
|
|
hb_script_t aScript = hb_icu_script_to_script(pTextLayout->runs[k].nCode);
|
|
|
|
|
// For vertical text, further divide the runs based on character
|
|
|
|
|
// orientation.
|
|
|
|
|
if (rArgs.mnFlags & SalLayoutFlags::Vertical)
|
2016-11-10 00:57:08 +02:00
|
|
|
|
{
|
2019-07-24 10:25:44 +07:00
|
|
|
|
sal_Int32 nIdx = nMinRunPos;
|
|
|
|
|
while (nIdx < nEndRunPos)
|
2017-03-05 00:04:35 +02:00
|
|
|
|
{
|
2019-07-24 10:25:44 +07:00
|
|
|
|
sal_Int32 nPrevIdx = nIdx;
|
|
|
|
|
sal_UCS4 aChar = rArgs.mrStr.iterateCodePoints(&nIdx);
|
|
|
|
|
VerticalOrientation aVo = GetVerticalOrientation(aChar, rArgs.maLanguageTag);
|
|
|
|
|
|
|
|
|
|
sal_UCS4 aVariationSelector = 0;
|
|
|
|
|
if (nIdx < nEndRunPos)
|
2017-03-05 00:04:35 +02:00
|
|
|
|
{
|
2019-07-24 10:25:44 +07:00
|
|
|
|
sal_Int32 nNextIdx = nIdx;
|
|
|
|
|
sal_UCS4 aNextChar = rArgs.mrStr.iterateCodePoints(&nNextIdx);
|
|
|
|
|
if (u_hasBinaryProperty(aNextChar, UCHAR_VARIATION_SELECTOR))
|
|
|
|
|
{
|
|
|
|
|
nIdx = nNextIdx;
|
|
|
|
|
aVariationSelector = aNextChar;
|
|
|
|
|
}
|
2017-03-05 00:04:35 +02:00
|
|
|
|
}
|
|
|
|
|
|
2019-07-24 10:25:44 +07:00
|
|
|
|
// Charters with U and Tu vertical orientation should
|
|
|
|
|
// be shaped in vertical direction. But characters
|
|
|
|
|
// with Tr should be shaped in vertical direction
|
|
|
|
|
// only if they have vertical alternates, otherwise
|
|
|
|
|
// they should be shaped in horizontal direction
|
|
|
|
|
// and then rotated.
|
|
|
|
|
// See http://unicode.org/reports/tr50/#vo
|
|
|
|
|
if (aVo == VerticalOrientation::Upright ||
|
|
|
|
|
aVo == VerticalOrientation::TransformedUpright ||
|
|
|
|
|
(aVo == VerticalOrientation::TransformedRotated &&
|
|
|
|
|
HasVerticalAlternate(aChar, aVariationSelector)))
|
|
|
|
|
{
|
|
|
|
|
aDirection = HB_DIRECTION_TTB;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
aDirection = bRightToLeft ? HB_DIRECTION_RTL : HB_DIRECTION_LTR;
|
|
|
|
|
}
|
2016-11-10 00:57:08 +02:00
|
|
|
|
|
2019-07-24 10:25:44 +07:00
|
|
|
|
if (aSubRuns.empty() || aSubRuns.back().maDirection != aDirection)
|
|
|
|
|
aSubRuns.push_back({ nPrevIdx, nIdx, aScript, aDirection });
|
|
|
|
|
else
|
|
|
|
|
aSubRuns.back().mnEnd = nIdx;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
aSubRuns.push_back({ nMinRunPos, nEndRunPos, aScript, aDirection });
|
2016-11-10 00:57:08 +02:00
|
|
|
|
}
|
2016-06-14 14:38:12 +05:30
|
|
|
|
|
2019-07-24 10:25:44 +07:00
|
|
|
|
nCurrentPos = nEndRunPos;
|
|
|
|
|
++k;
|
|
|
|
|
}
|
2016-06-14 14:38:12 +05:30
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// RTL subruns should be reversed to ensure that final glyph order is
|
|
|
|
|
// correct.
|
|
|
|
|
if (bRightToLeft)
|
2016-11-09 23:50:53 +02:00
|
|
|
|
std::reverse(aSubRuns.begin(), aSubRuns.end());
|
2016-06-14 14:38:12 +05:30
|
|
|
|
|
2016-11-09 23:50:53 +02:00
|
|
|
|
for (const auto& aSubRun : aSubRuns)
|
2016-06-14 14:38:12 +05:30
|
|
|
|
{
|
2016-10-20 16:37:22 +02:00
|
|
|
|
hb_buffer_clear_contents(pHbBuffer);
|
|
|
|
|
|
2019-01-23 19:59:45 +01:00
|
|
|
|
const int nMinRunPos = aSubRun.mnMin;
|
|
|
|
|
const int nEndRunPos = aSubRun.mnEnd;
|
|
|
|
|
const int nRunLen = nEndRunPos - nMinRunPos;
|
2016-09-26 19:09:52 +02:00
|
|
|
|
|
2016-10-17 19:59:23 +02:00
|
|
|
|
OString sLanguage = msLanguage;
|
|
|
|
|
if (sLanguage.isEmpty())
|
|
|
|
|
sLanguage = OUStringToOString(rArgs.maLanguageTag.getBcp47(), RTL_TEXTENCODING_ASCII_US);
|
2016-06-14 14:38:12 +05:30
|
|
|
|
|
|
|
|
|
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 */
|
|
|
|
|
|
2016-11-10 00:57:08 +02:00
|
|
|
|
hb_buffer_set_direction(pHbBuffer, aSubRun.maDirection);
|
2016-11-10 03:59:28 +02:00
|
|
|
|
hb_buffer_set_script(pHbBuffer, aSubRun.maScript);
|
2016-06-14 14:38:12 +05:30
|
|
|
|
hb_buffer_set_language(pHbBuffer, hb_language_from_string(sLanguage.getStr(), -1));
|
2018-01-15 09:01:15 +01:00
|
|
|
|
hb_buffer_set_flags(pHbBuffer, static_cast<hb_buffer_flags_t>(nHbFlags));
|
2016-06-14 14:38:12 +05:30
|
|
|
|
hb_buffer_add_utf16(
|
|
|
|
|
pHbBuffer, reinterpret_cast<uint16_t const *>(pStr), nLength,
|
|
|
|
|
nMinRunPos, nRunLen);
|
|
|
|
|
hb_buffer_set_cluster_level(pHbBuffer, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS);
|
2016-12-02 08:06:08 +02:00
|
|
|
|
|
2016-10-06 04:15:41 +02:00
|
|
|
|
// 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.
|
2017-07-30 12:16:31 +02:00
|
|
|
|
const char*const pHbShapers[] = { "graphite2", "coretext_aat", "ot", "fallback", nullptr };
|
2018-09-11 15:03:13 +02:00
|
|
|
|
bool ok = hb_shape_full(pHbFont, pHbBuffer, maFeatures.data(), maFeatures.size(), pHbShapers);
|
2016-10-06 04:15:41 +02:00
|
|
|
|
assert(ok);
|
|
|
|
|
(void) ok;
|
2016-06-14 14:38:12 +05:30
|
|
|
|
|
|
|
|
|
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;
|
2018-03-21 16:54:10 +02:00
|
|
|
|
int32_t nCharCount = 0;
|
2018-04-21 18:39:30 +02:00
|
|
|
|
bool bInCluster = false;
|
2018-04-26 12:55:26 +02:00
|
|
|
|
bool bClusterStart = false;
|
2018-03-21 16:54:10 +02:00
|
|
|
|
|
|
|
|
|
// Find the number of characters that make up this glyph.
|
|
|
|
|
if (!bRightToLeft)
|
|
|
|
|
{
|
|
|
|
|
// If the cluster is the same as previous glyph, then this
|
|
|
|
|
// already consumed, skip.
|
|
|
|
|
if (i > 0 && pHbGlyphInfos[i].cluster == pHbGlyphInfos[i - 1].cluster)
|
2018-04-21 18:39:30 +02:00
|
|
|
|
{
|
2018-03-21 16:54:10 +02:00
|
|
|
|
nCharCount = 0;
|
2018-04-21 18:39:30 +02:00
|
|
|
|
bInCluster = true;
|
|
|
|
|
}
|
2018-03-21 16:54:10 +02:00
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Find the next glyph with a different cluster, or the
|
|
|
|
|
// end of text.
|
|
|
|
|
int j = i;
|
|
|
|
|
int32_t nNextCharPos = nCharPos;
|
|
|
|
|
while (nNextCharPos == nCharPos && j < nRunGlyphCount)
|
|
|
|
|
nNextCharPos = pHbGlyphInfos[j++].cluster;
|
|
|
|
|
|
|
|
|
|
if (nNextCharPos == nCharPos)
|
2018-04-23 18:24:32 +02:00
|
|
|
|
nNextCharPos = nEndRunPos;
|
2018-03-21 16:54:10 +02:00
|
|
|
|
nCharCount = nNextCharPos - nCharPos;
|
2018-04-26 12:55:26 +02:00
|
|
|
|
if ((i == 0 || pHbGlyphInfos[i].cluster != pHbGlyphInfos[i - 1].cluster) &&
|
2018-04-29 01:00:20 +02:00
|
|
|
|
(i < nRunGlyphCount - 1 && pHbGlyphInfos[i].cluster == pHbGlyphInfos[i + 1].cluster))
|
2018-04-26 12:55:26 +02:00
|
|
|
|
bClusterStart = true;
|
2018-03-21 16:54:10 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// If the cluster is the same as previous glyph, then this
|
|
|
|
|
// will be consumed later, skip.
|
|
|
|
|
if (i < nRunGlyphCount - 1 && pHbGlyphInfos[i].cluster == pHbGlyphInfos[i + 1].cluster)
|
2018-04-21 18:39:30 +02:00
|
|
|
|
{
|
2018-03-21 16:54:10 +02:00
|
|
|
|
nCharCount = 0;
|
2018-04-21 18:39:30 +02:00
|
|
|
|
bInCluster = true;
|
|
|
|
|
}
|
2018-03-21 16:54:10 +02:00
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Find the previous glyph with a different cluster, or
|
|
|
|
|
// the end of text.
|
|
|
|
|
int j = i;
|
|
|
|
|
int32_t nNextCharPos = nCharPos;
|
|
|
|
|
while (nNextCharPos == nCharPos && j >= 0)
|
|
|
|
|
nNextCharPos = pHbGlyphInfos[j--].cluster;
|
|
|
|
|
|
|
|
|
|
if (nNextCharPos == nCharPos)
|
2018-04-23 18:24:32 +02:00
|
|
|
|
nNextCharPos = nEndRunPos;
|
2018-03-21 16:54:10 +02:00
|
|
|
|
nCharCount = nNextCharPos - nCharPos;
|
2018-04-26 12:55:26 +02:00
|
|
|
|
if ((i == nRunGlyphCount - 1 || pHbGlyphInfos[i].cluster != pHbGlyphInfos[i + 1].cluster) &&
|
|
|
|
|
(i > 0 && pHbGlyphInfos[i].cluster == pHbGlyphInfos[i - 1].cluster))
|
|
|
|
|
bClusterStart = true;
|
2018-03-21 16:54:10 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
2016-06-14 14:38:12 +05:30
|
|
|
|
|
|
|
|
|
// if needed request glyph fallback by updating LayoutArgs
|
|
|
|
|
if (!nGlyphIndex)
|
|
|
|
|
{
|
|
|
|
|
SetNeedFallback(rArgs, nCharPos, bRightToLeft);
|
|
|
|
|
if (SalLayoutFlags::ForFallback & rArgs.mnFlags)
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-05 22:12:39 +02:00
|
|
|
|
GlyphItemFlags nGlyphFlags = GlyphItemFlags::NONE;
|
2016-06-14 14:38:12 +05:30
|
|
|
|
if (bRightToLeft)
|
2019-07-05 22:12:39 +02:00
|
|
|
|
nGlyphFlags |= GlyphItemFlags::IS_RTL_GLYPH;
|
2016-06-14 14:38:12 +05:30
|
|
|
|
|
2018-04-26 12:55:26 +02:00
|
|
|
|
if (bClusterStart)
|
2019-07-05 22:12:39 +02:00
|
|
|
|
nGlyphFlags |= GlyphItemFlags::IS_CLUSTER_START;
|
2018-04-26 12:55:26 +02:00
|
|
|
|
|
2016-06-14 14:38:12 +05:30
|
|
|
|
if (bInCluster)
|
2019-07-05 22:12:39 +02:00
|
|
|
|
nGlyphFlags |= GlyphItemFlags::IS_IN_CLUSTER;
|
2016-06-14 14:38:12 +05:30
|
|
|
|
|
2016-11-19 21:32:19 +02:00
|
|
|
|
sal_Int32 indexUtf16 = nCharPos;
|
|
|
|
|
sal_UCS4 aChar = rArgs.mrStr.iterateCodePoints(&indexUtf16, 0);
|
|
|
|
|
|
2016-11-28 06:43:29 +02:00
|
|
|
|
if (u_getIntPropertyValue(aChar, UCHAR_GENERAL_CATEGORY) == U_NON_SPACING_MARK)
|
2019-07-05 22:12:39 +02:00
|
|
|
|
nGlyphFlags |= GlyphItemFlags::IS_DIACRITIC;
|
2016-06-14 14:38:12 +05:30
|
|
|
|
|
2016-12-08 07:00:02 +02:00
|
|
|
|
if (u_isUWhiteSpace(aChar))
|
2019-07-05 22:12:39 +02:00
|
|
|
|
nGlyphFlags |= GlyphItemFlags::IS_SPACING;
|
2016-12-08 07:00:02 +02:00
|
|
|
|
|
2018-03-13 16:14:47 +02:00
|
|
|
|
if (aSubRun.maScript == HB_SCRIPT_ARABIC &&
|
2016-11-28 06:43:29 +02:00
|
|
|
|
HB_DIRECTION_IS_BACKWARD(aSubRun.maDirection) &&
|
2019-07-05 22:12:39 +02:00
|
|
|
|
!(nGlyphFlags & GlyphItemFlags::IS_SPACING))
|
2016-11-11 19:38:16 +02:00
|
|
|
|
{
|
2019-07-05 22:12:39 +02:00
|
|
|
|
nGlyphFlags |= GlyphItemFlags::ALLOW_KASHIDA;
|
2016-11-11 19:38:16 +02:00
|
|
|
|
rArgs.mnFlags |= SalLayoutFlags::KashidaJustification;
|
|
|
|
|
}
|
|
|
|
|
|
2016-11-01 02:15:23 +02:00
|
|
|
|
DeviceCoordinate nAdvance, nXOffset, nYOffset;
|
2016-11-10 00:57:08 +02:00
|
|
|
|
if (aSubRun.maDirection == HB_DIRECTION_TTB)
|
2016-09-26 19:09:52 +02:00
|
|
|
|
{
|
2019-07-05 22:12:39 +02:00
|
|
|
|
nGlyphFlags |= GlyphItemFlags::IS_VERTICAL;
|
2016-11-19 21:32:19 +02:00
|
|
|
|
|
2017-11-10 00:24:26 +08:00
|
|
|
|
// We have glyph offsets that is relative to h origin now,
|
|
|
|
|
// add the origin back so it is relative to v origin.
|
2017-12-26 15:14:31 +00:00
|
|
|
|
hb_font_add_glyph_origin_for_direction(pHbFont,
|
2017-11-10 00:24:26 +08:00
|
|
|
|
nGlyphIndex,
|
|
|
|
|
HB_DIRECTION_TTB,
|
|
|
|
|
&pHbPositions[i].x_offset ,
|
|
|
|
|
&pHbPositions[i].y_offset );
|
2016-11-11 18:24:11 +02:00
|
|
|
|
nAdvance = -pHbPositions[i].y_advance;
|
2017-11-18 22:42:34 +08:00
|
|
|
|
nXOffset = -pHbPositions[i].y_offset;
|
|
|
|
|
nYOffset = -pHbPositions[i].x_offset;
|
2016-09-26 19:09:52 +02:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2016-11-11 18:24:11 +02:00
|
|
|
|
nAdvance = pHbPositions[i].x_advance;
|
|
|
|
|
nXOffset = pHbPositions[i].x_offset;
|
|
|
|
|
nYOffset = -pHbPositions[i].y_offset;
|
2016-09-26 19:09:52 +02:00
|
|
|
|
}
|
2016-06-14 14:38:12 +05:30
|
|
|
|
|
2016-12-20 01:36:34 +02:00
|
|
|
|
nAdvance = std::lround(nAdvance * nXScale);
|
|
|
|
|
nXOffset = std::lround(nXOffset * nXScale);
|
2016-12-22 20:31:39 +02:00
|
|
|
|
nYOffset = std::lround(nYOffset * nYScale);
|
2016-11-19 15:07:03 +02:00
|
|
|
|
|
|
|
|
|
Point aNewPos(aCurrPos.X() + nXOffset, aCurrPos.Y() + nYOffset);
|
2018-03-21 16:54:10 +02:00
|
|
|
|
const GlyphItem aGI(nCharPos, nCharCount, nGlyphIndex, aNewPos, nGlyphFlags,
|
2018-10-29 13:32:03 +00:00
|
|
|
|
nAdvance, nXOffset, &GetFont());
|
2018-10-27 21:44:39 +02:00
|
|
|
|
m_GlyphItems.Impl()->push_back(aGI);
|
2016-06-14 14:38:12 +05:30
|
|
|
|
|
2018-02-21 15:56:58 +02:00
|
|
|
|
aCurrPos.AdjustX(nAdvance );
|
2016-06-14 14:38:12 +05:30
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-20 16:37:22 +02:00
|
|
|
|
hb_buffer_destroy(pHbBuffer);
|
|
|
|
|
|
2019-03-22 17:57:46 +01:00
|
|
|
|
// Some flags are set as a side effect of text layout, save them here.
|
|
|
|
|
if (rArgs.mnFlags & SalLayoutFlags::GlyphItemsOnly)
|
|
|
|
|
m_GlyphItems.Impl()->mnFlags = rArgs.mnFlags;
|
|
|
|
|
|
2016-06-14 14:38:12 +05:30
|
|
|
|
return true;
|
|
|
|
|
}
|
2016-09-11 10:25:46 +02:00
|
|
|
|
|
2018-07-24 15:01:01 +01:00
|
|
|
|
void GenericSalLayout::GetCharWidths(DeviceCoordinate* pCharWidths) const
|
2016-09-11 10:25:46 +02:00
|
|
|
|
{
|
2018-07-24 14:47:14 +01:00
|
|
|
|
const int nCharCount = mnEndCharPos - mnMinCharPos;
|
2016-09-11 10:25:46 +02:00
|
|
|
|
|
|
|
|
|
for (int i = 0; i < nCharCount; ++i)
|
|
|
|
|
pCharWidths[i] = 0;
|
|
|
|
|
|
2018-10-27 21:44:39 +02:00
|
|
|
|
for (auto const& aGlyphItem : *m_GlyphItems.Impl())
|
2018-07-24 14:47:14 +01:00
|
|
|
|
{
|
2019-07-05 22:12:39 +02:00
|
|
|
|
const int nIndex = aGlyphItem.charPos() - mnMinCharPos;
|
2018-07-24 14:47:14 +01:00
|
|
|
|
if (nIndex >= nCharCount)
|
|
|
|
|
continue;
|
2018-09-30 14:00:54 +00:00
|
|
|
|
pCharWidths[nIndex] += aGlyphItem.m_nNewWidth;
|
2018-07-24 14:47:14 +01:00
|
|
|
|
}
|
2016-09-11 10:25:46 +02:00
|
|
|
|
}
|
|
|
|
|
|
2016-10-09 23:23:45 +02:00
|
|
|
|
// 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.
|
2016-10-10 00:54:00 +02:00
|
|
|
|
//
|
|
|
|
|
// Writer eventually calls IsKashidaPosValid() to check whether it can insert a
|
|
|
|
|
// Kashida between two characters or not.
|
|
|
|
|
//
|
2016-10-09 23:23:45 +02:00
|
|
|
|
// 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.
|
2016-10-23 12:56:00 +02:00
|
|
|
|
// * For any RTL glyph that has DX adjustment, insert enough Kashidas to
|
2016-10-09 23:23:45 +02:00
|
|
|
|
// fill in the added space.
|
|
|
|
|
|
2019-01-23 09:08:24 +02:00
|
|
|
|
void GenericSalLayout::ApplyDXArray(const ImplLayoutArgs& rArgs)
|
2016-09-11 10:25:46 +02:00
|
|
|
|
{
|
|
|
|
|
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];
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-09 23:23:45 +02:00
|
|
|
|
bool bKashidaJustify = false;
|
|
|
|
|
DeviceCoordinate nKashidaWidth = 0;
|
|
|
|
|
hb_codepoint_t nKashidaIndex = 0;
|
|
|
|
|
if (rArgs.mnFlags & SalLayoutFlags::KashidaJustification)
|
|
|
|
|
{
|
2018-10-29 13:32:03 +00:00
|
|
|
|
hb_font_t *pHbFont = GetFont().GetHbFont();
|
2016-10-09 23:23:45 +02:00
|
|
|
|
// Find Kashida glyph width and index.
|
2017-12-26 15:14:31 +00:00
|
|
|
|
if (hb_font_get_glyph(pHbFont, 0x0640, 0, &nKashidaIndex))
|
2018-10-29 13:32:03 +00:00
|
|
|
|
nKashidaWidth = GetFont().GetKashidaWidth();
|
2016-10-09 23:23:45 +02:00
|
|
|
|
bKashidaJustify = nKashidaWidth != 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Map of Kashida insertion points (in the glyph items vector) and the
|
|
|
|
|
// requested width.
|
|
|
|
|
std::map<size_t, DeviceCoordinate> pKashidas;
|
|
|
|
|
|
2016-09-11 10:25:46 +02:00
|
|
|
|
// The accumulated difference in X position.
|
|
|
|
|
DeviceCoordinate nDelta = 0;
|
|
|
|
|
|
|
|
|
|
// Apply the DX adjustments to glyph positions and widths.
|
|
|
|
|
size_t i = 0;
|
2018-10-27 21:44:39 +02:00
|
|
|
|
while (i < m_GlyphItems.Impl()->size())
|
2016-09-11 10:25:46 +02:00
|
|
|
|
{
|
2018-04-27 05:21:29 +02:00
|
|
|
|
// Accumulate the width difference for all characters corresponding to
|
|
|
|
|
// this glyph.
|
2019-07-05 22:12:39 +02:00
|
|
|
|
int nCharPos = (*m_GlyphItems.Impl())[i].charPos() - mnMinCharPos;
|
2018-04-27 05:21:29 +02:00
|
|
|
|
DeviceCoordinate nDiff = 0;
|
2019-07-05 22:12:39 +02:00
|
|
|
|
for (int j = 0; j < (*m_GlyphItems.Impl())[i].charCount(); j++)
|
2018-04-27 05:21:29 +02:00
|
|
|
|
nDiff += pNewCharWidths[nCharPos + j] - pOldCharWidths[nCharPos + j];
|
2016-09-11 10:25:46 +02:00
|
|
|
|
|
2018-10-27 21:44:39 +02:00
|
|
|
|
if (!(*m_GlyphItems.Impl())[i].IsRTLGlyph())
|
2018-04-27 05:21:29 +02:00
|
|
|
|
{
|
|
|
|
|
// Adjust the width and position of the first (leftmost) glyph in
|
|
|
|
|
// the cluster.
|
2018-10-27 21:44:39 +02:00
|
|
|
|
(*m_GlyphItems.Impl())[i].m_nNewWidth += nDiff;
|
|
|
|
|
(*m_GlyphItems.Impl())[i].m_aLinearPos.AdjustX(nDelta);
|
2016-10-30 00:36:07 +02:00
|
|
|
|
|
2018-04-27 05:21:29 +02:00
|
|
|
|
// Adjust the position of the rest of the glyphs in the cluster.
|
2018-10-27 21:44:39 +02:00
|
|
|
|
while (++i < m_GlyphItems.Impl()->size())
|
2018-04-27 05:21:29 +02:00
|
|
|
|
{
|
2018-10-27 21:44:39 +02:00
|
|
|
|
if (!(*m_GlyphItems.Impl())[i].IsInCluster())
|
2018-04-27 05:21:29 +02:00
|
|
|
|
break;
|
2018-10-27 21:44:39 +02:00
|
|
|
|
(*m_GlyphItems.Impl())[i].m_aLinearPos.AdjustX(nDelta);
|
2018-04-27 05:21:29 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
2018-10-27 21:44:39 +02:00
|
|
|
|
else if ((*m_GlyphItems.Impl())[i].IsInCluster())
|
2016-09-11 10:25:46 +02:00
|
|
|
|
{
|
2018-04-27 05:21:29 +02:00
|
|
|
|
// RTL glyph in the middle of the cluster, will be handled in the
|
|
|
|
|
// loop below.
|
|
|
|
|
i++;
|
2016-09-11 10:25:46 +02:00
|
|
|
|
}
|
2018-04-27 05:21:29 +02:00
|
|
|
|
else
|
2016-11-28 06:43:29 +02:00
|
|
|
|
{
|
2018-04-27 05:21:29 +02:00
|
|
|
|
// Adjust the width and position of the first (rightmost) glyph in
|
|
|
|
|
// the cluster.
|
|
|
|
|
// For RTL, we put all the adjustment to the left of the glyph.
|
2018-10-27 21:44:39 +02:00
|
|
|
|
(*m_GlyphItems.Impl())[i].m_nNewWidth += nDiff;
|
|
|
|
|
(*m_GlyphItems.Impl())[i].m_aLinearPos.AdjustX(nDelta + nDiff);
|
2018-04-27 05:21:29 +02:00
|
|
|
|
|
|
|
|
|
// Adjust the X position of all glyphs in the cluster.
|
|
|
|
|
size_t j = i;
|
2018-04-30 09:13:10 +01:00
|
|
|
|
while (j > 0)
|
2018-04-27 05:21:29 +02:00
|
|
|
|
{
|
2018-04-30 09:13:10 +01:00
|
|
|
|
--j;
|
2018-10-27 21:44:39 +02:00
|
|
|
|
if (!(*m_GlyphItems.Impl())[j].IsInCluster())
|
2018-04-27 05:21:29 +02:00
|
|
|
|
break;
|
2018-10-27 21:44:39 +02:00
|
|
|
|
(*m_GlyphItems.Impl())[j].m_aLinearPos.AdjustX(nDelta + nDiff);
|
2018-04-27 05:21:29 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If this glyph is Kashida-justifiable, then mark this as a
|
|
|
|
|
// Kashida position. Since this must be a RTL glyph, we mark the
|
|
|
|
|
// last glyph in the cluster not the first as this would be the
|
|
|
|
|
// base glyph.
|
2018-10-27 21:44:39 +02:00
|
|
|
|
if (bKashidaJustify && (*m_GlyphItems.Impl())[i].AllowKashida() &&
|
2019-07-05 22:12:39 +02:00
|
|
|
|
nDiff > (*m_GlyphItems.Impl())[i].charCount()) // Rounding errors, 1 pixel per character!
|
2016-11-28 06:43:29 +02:00
|
|
|
|
{
|
2018-04-27 05:21:29 +02:00
|
|
|
|
pKashidas[i] = nDiff;
|
|
|
|
|
// Move any non-spacing marks attached to this cluster as well.
|
|
|
|
|
// Looping backward because this is RTL glyph.
|
|
|
|
|
while (j > 0)
|
2016-11-28 06:43:29 +02:00
|
|
|
|
{
|
2018-10-27 21:44:39 +02:00
|
|
|
|
if (!(*m_GlyphItems.Impl())[j].IsDiacritic())
|
2018-04-27 05:21:29 +02:00
|
|
|
|
break;
|
2018-10-27 21:44:39 +02:00
|
|
|
|
(*m_GlyphItems.Impl())[j--].m_aLinearPos.AdjustX(nDiff);
|
2016-11-28 06:43:29 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
2018-04-27 05:21:29 +02:00
|
|
|
|
i++;
|
2016-11-28 06:43:29 +02:00
|
|
|
|
}
|
|
|
|
|
|
2016-09-11 10:25:46 +02:00
|
|
|
|
// Increment the delta, the loop above makes sure we do so only once
|
2016-11-28 06:43:29 +02:00
|
|
|
|
// for every character (cluster) 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).
|
2016-09-11 10:25:46 +02:00
|
|
|
|
nDelta += nDiff;
|
|
|
|
|
}
|
2016-10-09 23:23:45 +02:00
|
|
|
|
|
|
|
|
|
// Insert Kashida glyphs.
|
|
|
|
|
if (bKashidaJustify && !pKashidas.empty())
|
|
|
|
|
{
|
|
|
|
|
size_t nInserted = 0;
|
|
|
|
|
for (auto const& pKashida : pKashidas)
|
|
|
|
|
{
|
2018-10-27 21:44:39 +02:00
|
|
|
|
auto pGlyphIter = m_GlyphItems.Impl()->begin() + nInserted + pKashida.first;
|
2016-10-09 23:23:45 +02:00
|
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-08 18:54:20 +01:00
|
|
|
|
Point aPos(pGlyphIter->m_aLinearPos.getX() - nTotalWidth, 0);
|
2019-07-05 22:12:39 +02:00
|
|
|
|
int nCharPos = pGlyphIter->charPos();
|
|
|
|
|
GlyphItemFlags const nFlags = GlyphItemFlags::IS_IN_CLUSTER | GlyphItemFlags::IS_RTL_GLYPH;
|
2016-10-09 23:23:45 +02:00
|
|
|
|
while (nCopies--)
|
|
|
|
|
{
|
2018-10-29 13:32:03 +00:00
|
|
|
|
GlyphItem aKashida(nCharPos, 0, nKashidaIndex, aPos, nFlags, nKashidaWidth, 0, &GetFont());
|
2018-10-27 21:44:39 +02:00
|
|
|
|
pGlyphIter = m_GlyphItems.Impl()->insert(pGlyphIter, aKashida);
|
2018-02-21 15:56:58 +02:00
|
|
|
|
aPos.AdjustX(nKashidaWidth );
|
|
|
|
|
aPos.AdjustX( -nOverlap );
|
2016-10-09 23:23:45 +02:00
|
|
|
|
++pGlyphIter;
|
|
|
|
|
++nInserted;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-09-11 10:25:46 +02:00
|
|
|
|
}
|
2016-10-10 00:54:00 +02:00
|
|
|
|
|
2018-05-10 12:48:13 +02:00
|
|
|
|
bool GenericSalLayout::IsKashidaPosValid(int nCharPos) const
|
2016-10-10 00:54:00 +02:00
|
|
|
|
{
|
2018-10-27 21:44:39 +02:00
|
|
|
|
for (auto pIter = m_GlyphItems.Impl()->begin(); pIter != m_GlyphItems.Impl()->end(); ++pIter)
|
2016-10-10 00:54:00 +02:00
|
|
|
|
{
|
2019-07-05 22:12:39 +02:00
|
|
|
|
if (pIter->charPos() == nCharPos)
|
2016-10-10 00:54:00 +02:00
|
|
|
|
{
|
2018-04-27 05:21:29 +02:00
|
|
|
|
// The position is the first glyph, this would happen if we
|
2016-11-09 16:19:08 +02:00
|
|
|
|
// changed the text styling in the middle of a word. Since we don’t
|
2017-02-13 22:18:09 +01:00
|
|
|
|
// do ligatures across layout engine instances, this can’t be a
|
2016-11-09 16:19:08 +02:00
|
|
|
|
// ligature so it should be fine.
|
2018-10-27 21:44:39 +02:00
|
|
|
|
if (pIter == m_GlyphItems.Impl()->begin())
|
2016-11-09 16:19:08 +02:00
|
|
|
|
return true;
|
|
|
|
|
|
2018-04-27 05:21:29 +02:00
|
|
|
|
// If the character is not supported by this layout, return false
|
2016-11-05 05:19:31 +02:00
|
|
|
|
// so that fallback layouts would be checked for it.
|
2019-07-05 22:12:39 +02:00
|
|
|
|
if (pIter->glyphId() == 0)
|
2016-11-05 05:19:31 +02:00
|
|
|
|
break;
|
|
|
|
|
|
2016-10-10 00:54:00 +02:00
|
|
|
|
// 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.
|
2018-10-27 21:44:39 +02:00
|
|
|
|
for (auto pPrev = pIter - 1; pPrev != m_GlyphItems.Impl()->begin(); --pPrev)
|
2016-10-10 00:54:00 +02:00
|
|
|
|
{
|
2019-07-05 22:12:39 +02:00
|
|
|
|
if (pPrev->charPos() != nCharPos)
|
2016-10-10 00:54:00 +02:00
|
|
|
|
{
|
|
|
|
|
// Check if the found glyph belongs to the next character,
|
|
|
|
|
// otherwise the current glyph will be a ligature which is
|
|
|
|
|
// invalid kashida position.
|
2019-07-05 22:12:39 +02:00
|
|
|
|
if (pPrev->charPos() == (nCharPos + 1))
|
2016-10-10 00:54:00 +02:00
|
|
|
|
return true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2016-10-21 02:30:27 +02:00
|
|
|
|
|
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|