37#include <com/sun/star/i18n/CharacterIteratorMode.hpp>
39#include <unicode/uchar.h>
41#include <hb-graphite2.h>
48 , mpVertGlyphs(nullptr)
49 , mbFuzzing(
utl::ConfigManager::IsFuzzing())
63 if (!sLanguage.isEmpty())
68 hb_feature_t aFeature { rFeat.m_nTag, rFeat.m_nValue, rFeat.m_nStart, rFeat.m_nEnd };
80 hb_direction_t maDirection;
90 if ((cCh == 0xff1a || cCh == 0xff1b
91 || cCh == 0x2ca || cCh == 0x2cb || cCh == 0x2c7 || cCh == 0x2d9)
93 return U_VO_TRANSFORMED_UPRIGHT;
95 return u_getIntPropertyValue(cCh, UCHAR_VERTICAL_ORIENTATION);
122 int nGraphemeEndPos =
124 i18n::CharacterIteratorMode::SKIPCELL, 1, nDone);
126 rArgs.
mrStr.iterateCodePoints(&nCharPos);
127 int nGraphemeStartPos =
128 mxBreak->previousCharacters(rArgs.
mrStr, nCharPos, aLocale,
129 i18n::CharacterIteratorMode::SKIPCELL, 1, nDone);
132 nGraphemeStartPos = std::max(rArgs.
mnMinCharPos, nGraphemeStartPos);
133 nGraphemeEndPos = std::min(rArgs.
mnEndCharPos, nGraphemeEndPos);
135 rArgs.
AddFallbackRun(nGraphemeStartPos, nGraphemeEndPos, bRightToLeft);
170 hb_face_t* pHbFace = hb_font_get_face(
GetFont().GetHbFont());
174 hb_set_t* pLookups = hb_set_create();
175 hb_tag_t
const pFeatures[] = { HB_TAG(
'v',
'e',
'r',
't'), HB_TAG_NONE };
176 hb_ot_layout_collect_lookups(pHbFace, HB_OT_TAG_GSUB,
nullptr,
nullptr, pFeatures, pLookups);
177 if (!hb_set_is_empty(pLookups))
181 hb_codepoint_t nIdx = HB_SET_VALUE_INVALID;
182 while (hb_set_next(pLookups, &nIdx))
184 hb_set_t* pGlyphs = hb_set_create();
185 hb_ot_layout_lookup_collect_glyphs(pHbFace, HB_OT_TAG_GSUB, nIdx,
193 hb_set_destroy(pLookups);
226 std::optional<vcl::text::TextLayoutCache> oNewScriptRun;
235 pTextLayout = &*oNewScriptRun;
242 double nBaseOffset = 0;
245 hb_font_extents_t extents;
246 if (hb_font_get_h_extents(pHbFont, &extents))
247 nBaseOffset = ( extents.ascender + extents.descender ) / 2.0;
250 hb_buffer_t* pHbBuffer = hb_buffer_create();
251 hb_buffer_pre_allocate(pHbBuffer, nGlyphCapacity);
257 maFeatures.push_back({ HB_TAG(
'k',
'e',
'r',
'n'), 0, 0,
static_cast<unsigned int>(-1) });
266 maFeatures.push_back({ HB_TAG(
'l',
'i',
'g',
'a'), 0, 0,
static_cast<unsigned int>(-1) });
267 maFeatures.push_back({ HB_TAG(
'c',
'l',
'i',
'g'), 0, 0,
static_cast<unsigned int>(-1) });
279 int nBidiMinRunPos, nBidiEndRunPos;
281 if (!rArgs.
GetNextRun(&nBidiMinRunPos, &nBidiEndRunPos, &bRightToLeft))
285 std::vector<SubRun> aSubRuns;
286 int nCurrentPos = nBidiMinRunPos;
288 for (; k < pTextLayout->
runs.size(); ++k)
291 if (rRun.
nStart <= nCurrentPos && nCurrentPos < rRun.
nEnd)
299 hb_script_t aScript = hb_icu_script_to_script(pTextLayout->
runs[k].nCode);
300 aSubRuns.push_back({ nBidiMinRunPos, nBidiEndRunPos, aScript, bRightToLeft ? HB_DIRECTION_RTL : HB_DIRECTION_LTR });
304 while (nCurrentPos < nBidiEndRunPos && k < pTextLayout->runs.size())
306 int32_t nMinRunPos = nCurrentPos;
307 int32_t nEndRunPos = std::min(pTextLayout->
runs[k].nEnd, nBidiEndRunPos);
308 hb_direction_t
aDirection = bRightToLeft ? HB_DIRECTION_RTL : HB_DIRECTION_LTR;
309 hb_script_t aScript = hb_icu_script_to_script(pTextLayout->
runs[k].nCode);
314 sal_Int32 nIdx = nMinRunPos;
315 while (nIdx < nEndRunPos)
317 sal_Int32 nPrevIdx = nIdx;
319 int32_t aVo = GetVerticalOrientation(aChar, rArgs.
maLanguageTag);
322 if (nIdx < nEndRunPos)
324 sal_Int32 nNextIdx = nIdx;
325 sal_UCS4 aNextChar = rArgs.
mrStr.iterateCodePoints(&nNextIdx);
326 if (u_hasBinaryProperty(aNextChar, UCHAR_VARIATION_SELECTOR))
329 aVariationSelector = aNextChar;
340 if (aVo == U_VO_UPRIGHT || aVo == U_VO_TRANSFORMED_UPRIGHT ||
341 (aVo == U_VO_TRANSFORMED_ROTATED &&
348 aDirection = bRightToLeft ? HB_DIRECTION_RTL : HB_DIRECTION_LTR;
351 if (aSubRuns.empty() || aSubRuns.back().maDirection !=
aDirection || aSubRuns.back().maScript != aScript)
352 aSubRuns.push_back({ nPrevIdx, nIdx, aScript,
aDirection });
354 aSubRuns.back().mnEnd = nIdx;
359 aSubRuns.push_back({ nMinRunPos, nEndRunPos, aScript,
aDirection });
362 nCurrentPos = nEndRunPos;
370 std::reverse(aSubRuns.begin(), aSubRuns.end());
372 for (
const auto& aSubRun : aSubRuns)
374 hb_buffer_clear_contents(pHbBuffer);
376 const int nMinRunPos = aSubRun.mnMin;
377 const int nEndRunPos = aSubRun.mnEnd;
378 const int nRunLen = nEndRunPos - nMinRunPos;
380 int nHbFlags = HB_BUFFER_FLAGS_DEFAULT;
383 nHbFlags |= HB_BUFFER_FLAG_PRODUCE_SAFE_TO_INSERT_TATWEEL;
386 nHbFlags |= HB_BUFFER_FLAG_BOT;
388 nHbFlags |= HB_BUFFER_FLAG_EOT;
390 hb_buffer_set_direction(pHbBuffer, aSubRun.maDirection);
391 hb_buffer_set_script(pHbBuffer, aSubRun.maScript);
394 hb_buffer_set_language(pHbBuffer, hb_language_from_string(
msLanguage.getStr(),
msLanguage.getLength()));
399 hb_buffer_set_language(pHbBuffer, hb_language_from_string(sLanguage.getStr(), sLanguage.getLength()));
401 hb_buffer_set_flags(pHbBuffer,
static_cast<hb_buffer_flags_t
>(nHbFlags));
403 pHbBuffer,
reinterpret_cast<uint16_t
const *
>(pStr),
nLength,
404 nMinRunPos, nRunLen);
408 const char*
const pHbShapers[] = {
"graphite2",
"ot",
"fallback",
nullptr };
413 int nRunGlyphCount = hb_buffer_get_length(pHbBuffer);
414 hb_glyph_info_t *pHbGlyphInfos = hb_buffer_get_glyph_infos(pHbBuffer,
nullptr);
415 hb_glyph_position_t *pHbPositions = hb_buffer_get_glyph_positions(pHbBuffer,
nullptr);
417 for (
int i = 0;
i < nRunGlyphCount; ++
i) {
418 int32_t nGlyphIndex = pHbGlyphInfos[
i].codepoint;
419 int32_t nCharPos = pHbGlyphInfos[
i].cluster;
420 int32_t nCharCount = 0;
421 bool bInCluster =
false;
422 bool bClusterStart =
false;
429 if (
i > 0 && pHbGlyphInfos[
i].cluster == pHbGlyphInfos[
i - 1].cluster)
439 int32_t nNextCharPos = nCharPos;
440 while (nNextCharPos == nCharPos && j < nRunGlyphCount)
441 nNextCharPos = pHbGlyphInfos[j++].cluster;
443 if (nNextCharPos == nCharPos)
444 nNextCharPos = nEndRunPos;
445 nCharCount = nNextCharPos - nCharPos;
446 if ((
i == 0 || pHbGlyphInfos[
i].cluster != pHbGlyphInfos[
i - 1].cluster) &&
447 (
i < nRunGlyphCount - 1 && pHbGlyphInfos[
i].cluster == pHbGlyphInfos[
i + 1].cluster))
448 bClusterStart =
true;
455 if (
i < nRunGlyphCount - 1 && pHbGlyphInfos[
i].cluster == pHbGlyphInfos[
i + 1].cluster)
465 int32_t nNextCharPos = nCharPos;
466 while (nNextCharPos == nCharPos && j >= 0)
467 nNextCharPos = pHbGlyphInfos[j--].cluster;
469 if (nNextCharPos == nCharPos)
470 nNextCharPos = nEndRunPos;
471 nCharCount = nNextCharPos - nCharPos;
472 if ((
i == nRunGlyphCount - 1 || pHbGlyphInfos[
i].cluster != pHbGlyphInfos[
i + 1].cluster) &&
473 (
i > 0 && pHbGlyphInfos[
i].cluster == pHbGlyphInfos[
i - 1].cluster))
474 bClusterStart =
true;
499 if (u_isUWhiteSpace(aChar))
502 if (hb_glyph_info_get_glyph_flags(&pHbGlyphInfos[
i]) & HB_GLYPH_FLAG_UNSAFE_TO_BREAK)
505 if (hb_glyph_info_get_glyph_flags(&pHbGlyphInfos[
i]) & HB_GLYPH_FLAG_SAFE_TO_INSERT_TATWEEL)
508 double nAdvance, nXOffset, nYOffset;
509 if (aSubRun.maDirection == HB_DIRECTION_TTB)
513 nAdvance = -pHbPositions[
i].y_advance;
514 nXOffset = -pHbPositions[
i].y_offset;
515 nYOffset = -pHbPositions[
i].x_offset - nBaseOffset;
517 if (
GetFont().NeedOffsetCorrection(pHbPositions[
i].y_offset))
525 nXOffset = -(aRect.
Top() / nXScale + ( pHbPositions[
i].y_advance
526 + ( aRect.
GetHeight() / nXScale ) ) / 2.0 );
532 nAdvance = pHbPositions[
i].x_advance;
533 nXOffset = pHbPositions[
i].x_offset;
534 nYOffset = -pHbPositions[
i].y_offset;
537 nAdvance = nAdvance * nXScale;
538 nXOffset = nXOffset * nXScale;
539 nYOffset = nYOffset * nYScale;
542 nAdvance = std::lround(nAdvance);
543 nXOffset = std::lround(nXOffset);
544 nYOffset = std::lround(nYOffset);
548 const GlyphItem aGI(nCharPos, nCharCount, nGlyphIndex, aNewPos, nGlyphFlags,
549 nAdvance, nXOffset, nYOffset);
557 hb_buffer_destroy(pHbBuffer);
571 rCharWidths.resize(nCharCount, 0);
573 css::uno::Reference<css::i18n::XBreakIterator> xBreak;
581 unsigned int nGraphemeCount = 0;
582 if (aGlyphItem.charCount() > 1 && aGlyphItem.newWidth() != 0 && !rStr.isEmpty())
591 sal_Int32
nPos = aGlyphItem.charPos();
592 while (
nPos < aGlyphItem.charPos() + aGlyphItem.charCount())
594 nPos = xBreak->nextCharacters(rStr,
nPos, aLocale,
595 css::i18n::CharacterIteratorMode::SKIPCELL, 1, nDone);
600 if (nGraphemeCount > 1)
604 std::vector<double> aWidths(nGraphemeCount);
607 unsigned int nCarets = nGraphemeCount;
608 std::vector<hb_position_t> aCarets(nGraphemeCount);
609 hb_ot_layout_get_ligature_carets(
GetFont().GetHbFont(),
610 aGlyphItem.IsRTLGlyph() ? HB_DIRECTION_RTL : HB_DIRECTION_LTR,
611 aGlyphItem.glyphId(), 0, &nCarets, aCarets.data());
616 if (nCarets == nGraphemeCount - 1)
622 for (
size_t i = 0;
i < nCarets;
i++)
623 aCarets[
i] = (aCarets[
i] * fScale) + aGlyphItem.xOffset();
626 aCarets[nCarets] = aGlyphItem.newWidth();
630 for (
size_t i = 0;
i < nGraphemeCount;
i++)
631 aWidths[
i] = aCarets[
i] - (
i == 0 ? 0 : aCarets[
i - 1]);
635 if (aGlyphItem.IsRTLGlyph())
636 std::reverse(aWidths.begin(), aWidths.end());
641 auto nWidth = aGlyphItem.newWidth() / nGraphemeCount;
642 std::fill(aWidths.begin(), aWidths.end(), nWidth);
646 aWidths[nGraphemeCount - 1] += aGlyphItem.newWidth() - (nWidth * nGraphemeCount);
651 sal_Int32
nPos = aGlyphItem.charPos();
652 for (
auto nWidth : aWidths)
655 nPos = xBreak->nextCharacters(rStr,
nPos, aLocale,
656 css::i18n::CharacterIteratorMode::SKIPCELL, 1, nDone);
660 rCharWidths[aGlyphItem.charPos() -
mnMinCharPos] += aGlyphItem.newWidth();
671 std::vector<double> aOldCharWidths;
672 std::unique_ptr<double[]>
const pNewCharWidths(
new double[nCharCount]);
678 for (
int i = 0;
i < nCharCount; ++
i)
681 pNewCharWidths[
i] = pDXArray[
i];
683 pNewCharWidths[
i] = pDXArray[
i] - pDXArray[
i - 1];
688 std::map<size_t, std::pair<double, double>> pKashidas;
702 nDiff += pNewCharWidths[nCharPos + j] - aOldCharWidths[nCharPos + j];
735 for (
int j =
i - 1; j >= 0 &&
m_GlyphItems[j].IsInCluster(); j--)
740 if (pKashidaArray && pKashidaArray[nCharPos])
741 pKashidas[
i] = { nDiff, pNewCharWidths[nCharPos] };
754 if (pKashidas.empty())
761 nKashidaWidth = std::ceil(nKashidaWidth);
763 if (nKashidaWidth <= 0)
765 SAL_WARN(
"vcl.gdi",
"Asked to insert Kashidas in a font with bogus Kashida width");
769 size_t nInserted = 0;
770 for (
auto const& pKashida : pKashidas)
772 auto pGlyphIter =
m_GlyphItems.begin() + nInserted + pKashida.first;
775 auto const& [nTotalWidth, nClusterWidth] = pKashida.second;
779 if (nTotalWidth > nKashidaWidth)
780 nCopies = nTotalWidth / nKashidaWidth;
785 double nShortfall = nTotalWidth - nKashidaWidth * nCopies;
789 double nExcess = nCopies * nKashidaWidth - nTotalWidth;
791 nOverlap = nExcess / (nCopies - 1);
795 int nCharPos = pGlyphIter->charPos();
799 aPos.
adjustX(-nClusterWidth + pGlyphIter->origWidth());
802 GlyphItem aKashida(nCharPos, 0, nKashidaIndex, aPos, nFlags, 0, 0, 0);
804 aPos.
adjustX(nKashidaWidth - nOverlap);
816 [&](
const GlyphItem& g) { return g.charPos() == nCharPos; });
818 [&](
const GlyphItem& g) { return g.charPos() == nNextCharPos; });
827 if (rGlyph->glyphId() == 0 || rNextGlyph->glyphId() == 0)
831 return rNextGlyph->IsSafeToInsertKashida();
LogicalFontInstance & GetFont() const
SalLayoutGlyphsImpl m_GlyphItems
void GetCharWidths(std::vector< double > &rCharWidths, const OUString &rStr) const
void SetNeedFallback(vcl::text::ImplLayoutArgs &, sal_Int32, bool)
bool IsKashidaPosValid(int nCharPos, int nNextCharPos) const final override
SalLayoutGlyphs GetGlyphs() const final override
std::vector< hb_feature_t > maFeatures
GenericSalLayout(LogicalFontInstance &)
bool LayoutText(vcl::text::ImplLayoutArgs &, const SalLayoutGlyphsImpl *) final override
void Justify(double nNewWidth)
void DrawText(SalGraphics &) const final override
void ApplyAsianKerning(std::u16string_view rStr)
void ParseFeatures(std::u16string_view name)
~GenericSalLayout() override
void AdjustLayout(vcl::text::ImplLayoutArgs &) final override
bool HasVerticalAlternate(sal_UCS4 aChar, sal_UCS4 aNextChar)
css::uno::Reference< css::i18n::XBreakIterator > mxBreak
void ApplyDXArray(const double *, const sal_Bool *)
const css::lang::Locale & getLocale(bool bResolveSystem=true) const
OUString getLanguage() const
const OUString & getBcp47(bool bResolveSystem=true) const
void GetScale(double *nXScale, double *nYScale) const
bool GetGlyphBoundRect(sal_GlyphId, tools::Rectangle &, bool) const
const vcl::font::FontSelectPattern & GetFontSelectPattern() const
double GetKashidaWidth() const
sal_GlyphId GetGlyphIndex(uint32_t, uint32_t=0) const
virtual void DrawTextLayout(const GenericSalLayout &)=0
void SetFlags(SalLayoutFlags flags)
SalLayoutFlags GetFlags() const
SalLayoutGlyphsImpl * clone() const
void AppendImpl(SalLayoutGlyphsImpl *pImpl)
LanguageTag maLanguageTag
virtual void AdjustLayout(vcl::text::ImplLayoutArgs &)
bool GetSubpixelPositioning() const
std::vector< FeatureSetting > const & getFeatures() const
OUString const & getLanguage() const
vcl::text::TextLayoutCache const * m_pTextLayoutCache
bool GetNextRun(int *nMinRunPos, int *nEndRunPos, bool *bRTL)
const sal_Bool * mpKashidaArray
void AddFallbackRun(int nMinRunPos, int nEndRunPos, bool bRTL)
LanguageTag maLanguageTag
std::vector< vcl::text::Run > runs
@ IS_SAFE_TO_INSERT_KASHIDA
#define SAL_WARN(area, stream)
#define SAL_INFO(area, stream)
constexpr T & temporary(T &&x)
OString OUStringToOString(std::u16string_view str, ConnectionSettings const *settings)
VCL_DLLPUBLIC css::uno::Reference< css::i18n::XBreakIterator > CreateBreakIterator()
css::drawing::Direction3D aDirection