37#include <com/sun/star/i18n/CharacterIteratorMode.hpp>
39#include <unicode/uchar.h>
41#include <hb-graphite2.h>
47 , mpVertGlyphs(nullptr)
48 , mbFuzzing(
utl::ConfigManager::IsFuzzing())
62 if (!sLanguage.isEmpty())
67 hb_feature_t aFeature { rFeat.m_nTag, rFeat.m_nValue, rFeat.m_nStart, rFeat.m_nEnd };
79 hb_direction_t maDirection;
85#if U_ICU_VERSION_MAJOR_NUM >= 63
86 enum class VerticalOrientation {
88 Rotated = U_VO_ROTATED,
89 TransformedUpright = U_VO_TRANSFORMED_UPRIGHT,
90 TransformedRotated = U_VO_TRANSFORMED_ROTATED
96 enum class VerticalOrientation {
99 TransformedUpright = 2,
100 TransformedRotated = 3
108 if ((cCh == 0xff1a || cCh == 0xff1b
109 || cCh == 0x2ca || cCh == 0x2cb || cCh == 0x2c7 || cCh == 0x2d9)
111 return VerticalOrientation::TransformedUpright;
113#if U_ICU_VERSION_MAJOR_NUM >= 63
114 int32_t nRet = u_getIntPropertyValue(cCh, UCHAR_VERTICAL_ORIENTATION);
132 SAL_WARN(
"vcl.gdi",
"Getting VerticalOrientation for codepoint outside Unicode range");
136 return VerticalOrientation(nRet);
164 int nGraphemeEndPos =
166 i18n::CharacterIteratorMode::SKIPCELL, 1, nDone);
168 rArgs.
mrStr.iterateCodePoints(&nCharPos);
169 int nGraphemeStartPos =
170 mxBreak->previousCharacters(rArgs.
mrStr, nCharPos, aLocale,
171 i18n::CharacterIteratorMode::SKIPCELL, 1, nDone);
174 nGraphemeStartPos = std::max(rArgs.
mnMinCharPos, nGraphemeStartPos);
175 nGraphemeEndPos = std::min(rArgs.
mnEndCharPos, nGraphemeEndPos);
177 rArgs.
AddFallbackRun(nGraphemeStartPos, nGraphemeEndPos, bRightToLeft);
212 hb_face_t* pHbFace = hb_font_get_face(
GetFont().GetHbFont());
216 hb_set_t* pLookups = hb_set_create();
217 hb_tag_t
const pFeatures[] = { HB_TAG(
'v',
'e',
'r',
't'), HB_TAG_NONE };
218 hb_ot_layout_collect_lookups(pHbFace, HB_OT_TAG_GSUB,
nullptr,
nullptr, pFeatures, pLookups);
219 if (!hb_set_is_empty(pLookups))
223 hb_codepoint_t nIdx = HB_SET_VALUE_INVALID;
224 while (hb_set_next(pLookups, &nIdx))
226 hb_set_t* pGlyphs = hb_set_create();
227 hb_ot_layout_lookup_collect_glyphs(pHbFace, HB_OT_TAG_GSUB, nIdx,
235 hb_set_destroy(pLookups);
268 std::optional<vcl::text::TextLayoutCache> oNewScriptRun;
277 pTextLayout = &*oNewScriptRun;
287 hb_font_extents_t extents;
288 if (hb_font_get_h_extents(pHbFont, &extents))
289 nBaseOffset = ( extents.ascender + extents.descender ) / 2;
292 hb_buffer_t* pHbBuffer = hb_buffer_create();
293 hb_buffer_pre_allocate(pHbBuffer, nGlyphCapacity);
299 maFeatures.push_back({ HB_TAG(
'k',
'e',
'r',
'n'), 0, 0,
static_cast<unsigned int>(-1) });
308 maFeatures.push_back({ HB_TAG(
'l',
'i',
'g',
'a'), 0, 0,
static_cast<unsigned int>(-1) });
309 maFeatures.push_back({ HB_TAG(
'c',
'l',
'i',
'g'), 0, 0,
static_cast<unsigned int>(-1) });
321 int nBidiMinRunPos, nBidiEndRunPos;
323 if (!rArgs.
GetNextRun(&nBidiMinRunPos, &nBidiEndRunPos, &bRightToLeft))
327 std::vector<SubRun> aSubRuns;
328 int nCurrentPos = nBidiMinRunPos;
330 for (; k < pTextLayout->
runs.size(); ++k)
333 if (rRun.
nStart <= nCurrentPos && nCurrentPos < rRun.
nEnd)
341 hb_script_t aScript = hb_icu_script_to_script(pTextLayout->
runs[k].nCode);
342 aSubRuns.push_back({ nBidiMinRunPos, nBidiEndRunPos, aScript, bRightToLeft ? HB_DIRECTION_RTL : HB_DIRECTION_LTR });
346 while (nCurrentPos < nBidiEndRunPos && k < pTextLayout->runs.size())
348 int32_t nMinRunPos = nCurrentPos;
349 int32_t nEndRunPos = std::min(pTextLayout->
runs[k].nEnd, nBidiEndRunPos);
350 hb_direction_t
aDirection = bRightToLeft ? HB_DIRECTION_RTL : HB_DIRECTION_LTR;
351 hb_script_t aScript = hb_icu_script_to_script(pTextLayout->
runs[k].nCode);
356 sal_Int32 nIdx = nMinRunPos;
357 while (nIdx < nEndRunPos)
359 sal_Int32 nPrevIdx = nIdx;
361 VerticalOrientation aVo = GetVerticalOrientation(aChar, rArgs.
maLanguageTag);
364 if (nIdx < nEndRunPos)
366 sal_Int32 nNextIdx = nIdx;
367 sal_UCS4 aNextChar = rArgs.
mrStr.iterateCodePoints(&nNextIdx);
368 if (u_hasBinaryProperty(aNextChar, UCHAR_VARIATION_SELECTOR))
371 aVariationSelector = aNextChar;
382 if (aVo == VerticalOrientation::Upright ||
383 aVo == VerticalOrientation::TransformedUpright ||
384 (aVo == VerticalOrientation::TransformedRotated &&
391 aDirection = bRightToLeft ? HB_DIRECTION_RTL : HB_DIRECTION_LTR;
394 if (aSubRuns.empty() || aSubRuns.back().maDirection !=
aDirection)
395 aSubRuns.push_back({ nPrevIdx, nIdx, aScript,
aDirection });
397 aSubRuns.back().mnEnd = nIdx;
402 aSubRuns.push_back({ nMinRunPos, nEndRunPos, aScript,
aDirection });
405 nCurrentPos = nEndRunPos;
413 std::reverse(aSubRuns.begin(), aSubRuns.end());
415 for (
const auto& aSubRun : aSubRuns)
417 hb_buffer_clear_contents(pHbBuffer);
419 const int nMinRunPos = aSubRun.mnMin;
420 const int nEndRunPos = aSubRun.mnEnd;
421 const int nRunLen = nEndRunPos - nMinRunPos;
423 int nHbFlags = HB_BUFFER_FLAGS_DEFAULT;
426 nHbFlags |= HB_BUFFER_FLAG_PRODUCE_SAFE_TO_INSERT_TATWEEL;
429 nHbFlags |= HB_BUFFER_FLAG_BOT;
431 nHbFlags |= HB_BUFFER_FLAG_EOT;
433 hb_buffer_set_direction(pHbBuffer, aSubRun.maDirection);
434 hb_buffer_set_script(pHbBuffer, aSubRun.maScript);
437 hb_buffer_set_language(pHbBuffer, hb_language_from_string(
msLanguage.getStr(),
msLanguage.getLength()));
442 hb_buffer_set_language(pHbBuffer, hb_language_from_string(sLanguage.getStr(), sLanguage.getLength()));
444 hb_buffer_set_flags(pHbBuffer,
static_cast<hb_buffer_flags_t
>(nHbFlags));
446 pHbBuffer,
reinterpret_cast<uint16_t
const *
>(pStr),
nLength,
447 nMinRunPos, nRunLen);
451 const char*
const pHbShapers[] = {
"graphite2",
"ot",
"fallback",
nullptr };
456 int nRunGlyphCount = hb_buffer_get_length(pHbBuffer);
457 hb_glyph_info_t *pHbGlyphInfos = hb_buffer_get_glyph_infos(pHbBuffer,
nullptr);
458 hb_glyph_position_t *pHbPositions = hb_buffer_get_glyph_positions(pHbBuffer,
nullptr);
460 for (
int i = 0;
i < nRunGlyphCount; ++
i) {
461 int32_t nGlyphIndex = pHbGlyphInfos[
i].codepoint;
462 int32_t nCharPos = pHbGlyphInfos[
i].cluster;
463 int32_t nCharCount = 0;
464 bool bInCluster =
false;
465 bool bClusterStart =
false;
472 if (
i > 0 && pHbGlyphInfos[
i].cluster == pHbGlyphInfos[
i - 1].cluster)
482 int32_t nNextCharPos = nCharPos;
483 while (nNextCharPos == nCharPos && j < nRunGlyphCount)
484 nNextCharPos = pHbGlyphInfos[j++].cluster;
486 if (nNextCharPos == nCharPos)
487 nNextCharPos = nEndRunPos;
488 nCharCount = nNextCharPos - nCharPos;
489 if ((
i == 0 || pHbGlyphInfos[
i].cluster != pHbGlyphInfos[
i - 1].cluster) &&
490 (
i < nRunGlyphCount - 1 && pHbGlyphInfos[
i].cluster == pHbGlyphInfos[
i + 1].cluster))
491 bClusterStart =
true;
498 if (
i < nRunGlyphCount - 1 && pHbGlyphInfos[
i].cluster == pHbGlyphInfos[
i + 1].cluster)
508 int32_t nNextCharPos = nCharPos;
509 while (nNextCharPos == nCharPos && j >= 0)
510 nNextCharPos = pHbGlyphInfos[j--].cluster;
512 if (nNextCharPos == nCharPos)
513 nNextCharPos = nEndRunPos;
514 nCharCount = nNextCharPos - nCharPos;
515 if ((
i == nRunGlyphCount - 1 || pHbGlyphInfos[
i].cluster != pHbGlyphInfos[
i + 1].cluster) &&
516 (
i > 0 && pHbGlyphInfos[
i].cluster == pHbGlyphInfos[
i - 1].cluster))
517 bClusterStart =
true;
542 if (u_isUWhiteSpace(aChar))
545 if (hb_glyph_info_get_glyph_flags(&pHbGlyphInfos[
i]) & HB_GLYPH_FLAG_UNSAFE_TO_BREAK)
548 if (hb_glyph_info_get_glyph_flags(&pHbGlyphInfos[
i]) & HB_GLYPH_FLAG_SAFE_TO_INSERT_TATWEEL)
552 if (aSubRun.maDirection == HB_DIRECTION_TTB)
556 nAdvance = -pHbPositions[
i].y_advance;
557 nXOffset = -pHbPositions[
i].y_offset;
558 nYOffset = -pHbPositions[
i].x_offset - nBaseOffset;
560 if (
GetFont().NeedOffsetCorrection(pHbPositions[
i].y_offset))
568 nXOffset = -(aRect.
Top() / nXScale + ( pHbPositions[
i].y_advance
569 + ( aRect.
GetHeight() / nXScale ) ) / 2 );
575 nAdvance = pHbPositions[
i].x_advance;
576 nXOffset = pHbPositions[
i].x_offset;
577 nYOffset = -pHbPositions[
i].y_offset;
580 nAdvance = std::lround(nAdvance * nXScale);
581 nXOffset = std::lround(nXOffset * nXScale);
582 nYOffset = std::lround(nYOffset * nYScale);
585 const GlyphItem aGI(nCharPos, nCharCount, nGlyphIndex, aNewPos, nGlyphFlags,
586 nAdvance, nXOffset, nYOffset);
594 hb_buffer_destroy(pHbBuffer);
608 rCharWidths.resize(nCharCount, 0);
610 css::uno::Reference<css::i18n::XBreakIterator> xBreak;
618 unsigned int nGraphemeCount = 0;
619 if (aGlyphItem.charCount() > 1 && aGlyphItem.newWidth() != 0 && !rStr.isEmpty())
628 sal_Int32
nPos = aGlyphItem.charPos();
629 while (
nPos < aGlyphItem.charPos() + aGlyphItem.charCount())
631 nPos = xBreak->nextCharacters(rStr,
nPos, aLocale,
632 css::i18n::CharacterIteratorMode::SKIPCELL, 1, nDone);
637 if (nGraphemeCount > 1)
641 std::vector<DeviceCoordinate> aWidths(nGraphemeCount);
644 unsigned int nCarets = nGraphemeCount;
645 std::vector<hb_position_t> aCarets(nGraphemeCount);
646 hb_ot_layout_get_ligature_carets(
GetFont().GetHbFont(),
647 aGlyphItem.IsRTLGlyph() ? HB_DIRECTION_RTL : HB_DIRECTION_LTR,
648 aGlyphItem.glyphId(), 0, &nCarets, aCarets.data());
653 if (nCarets == nGraphemeCount - 1)
659 for (
size_t i = 0;
i < nCarets;
i++)
660 aCarets[
i] = (aCarets[
i] * fScale) + aGlyphItem.xOffset();
663 aCarets[nCarets] = aGlyphItem.newWidth();
667 for (
size_t i = 0;
i < nGraphemeCount;
i++)
668 aWidths[
i] = aCarets[
i] - (
i == 0 ? 0 : aCarets[
i - 1]);
672 if (aGlyphItem.IsRTLGlyph())
673 std::reverse(aWidths.begin(), aWidths.end());
678 auto nWidth = aGlyphItem.newWidth() / nGraphemeCount;
679 std::fill(aWidths.begin(), aWidths.end(), nWidth);
683 aWidths[nGraphemeCount - 1] += aGlyphItem.newWidth() - (nWidth * nGraphemeCount);
688 sal_Int32
nPos = aGlyphItem.charPos();
689 for (
auto nWidth : aWidths)
692 nPos = xBreak->nextCharacters(rStr,
nPos, aLocale,
693 css::i18n::CharacterIteratorMode::SKIPCELL, 1, nDone);
697 rCharWidths[aGlyphItem.charPos() -
mnMinCharPos] += aGlyphItem.newWidth();
708 std::vector<DeviceCoordinate> aOldCharWidths;
709 std::unique_ptr<double[]>
const pNewCharWidths(
new double[nCharCount]);
715 for (
int i = 0;
i < nCharCount; ++
i)
718 pNewCharWidths[
i] = pDXArray[
i];
720 pNewCharWidths[
i] = pDXArray[
i] - pDXArray[
i - 1];
725 std::map<size_t, std::pair<DeviceCoordinate, DeviceCoordinate>> pKashidas;
739 nDiff += pNewCharWidths[nCharPos + j] - aOldCharWidths[nCharPos + j];
772 for (
int j =
i - 1; j >= 0 &&
m_GlyphItems[j].IsInCluster(); j--)
777 if (pKashidaArray && pKashidaArray[nCharPos])
778 pKashidas[
i] = { nDiff, pNewCharWidths[nCharPos] };
791 if (pKashidas.empty())
798 if (nKashidaWidth <= 0)
800 SAL_WARN(
"vcl.gdi",
"Asked to insert Kashidas in a font with bogus Kashida width");
804 size_t nInserted = 0;
805 for (
auto const& pKashida : pKashidas)
807 auto pGlyphIter =
m_GlyphItems.begin() + nInserted + pKashida.first;
810 auto const& [nTotalWidth, nClusterWidth] = pKashida.second;
814 if (nTotalWidth > nKashidaWidth)
815 nCopies = nTotalWidth / nKashidaWidth;
820 double nShortfall = nTotalWidth - nKashidaWidth * nCopies;
824 double nExcess = nCopies * nKashidaWidth - nTotalWidth;
826 nOverlap = nExcess / (nCopies - 1);
830 int nCharPos = pGlyphIter->charPos();
834 aPos.
adjustX(-nClusterWidth + pGlyphIter->origWidth());
837 GlyphItem aKashida(nCharPos, 0, nKashidaIndex, aPos, nFlags, 0, 0, 0);
839 aPos.
adjustX(nKashidaWidth - nOverlap);
851 [&](
const GlyphItem& g) { return g.charPos() == nCharPos; });
853 [&](
const GlyphItem& g) { return g.charPos() == nNextCharPos; });
862 if (rGlyph->glyphId() == 0 || rNextGlyph->glyphId() == 0)
866 return rNextGlyph->IsSafeToInsertKashida();
static const uint8_t sVerticalOrientationValues[34][128]
#define kVerticalOrientationCharBits
static const uint8_t sVerticalOrientationPages[4][512]
static const uint8_t sVerticalOrientationPlanes[16]
#define kVerticalOrientationMaxPlane
LogicalFontInstance & GetFont() const
SalLayoutGlyphsImpl m_GlyphItems
void Justify(DeviceCoordinate nNewWidth)
void SetNeedFallback(vcl::text::ImplLayoutArgs &, sal_Int32, bool)
bool IsKashidaPosValid(int nCharPos, int nNextCharPos) const final override
SalLayoutGlyphs GetGlyphs() const final override
void GetCharWidths(std::vector< DeviceCoordinate > &rCharWidths, const OUString &rStr) const
std::vector< hb_feature_t > maFeatures
GenericSalLayout(LogicalFontInstance &)
bool LayoutText(vcl::text::ImplLayoutArgs &, const SalLayoutGlyphsImpl *) final override
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
int 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 &)
std::vector< FeatureSetting > const & getFeatures() const
OUString const & getLanguage() const
vcl::text::TextLayoutCache const * m_pTextLayoutCache
bool GetNextRun(int *nMinRunPos, int *nEndRunPos, bool *bRTL)
DeviceCoordinate mnLayoutWidth
const sal_Bool * mpKashidaArray
const double * mpNaturalDXArray
void AddFallbackRun(int nMinRunPos, int nEndRunPos, bool bRTL)
LanguageTag maLanguageTag
std::vector< vcl::text::Run > runs
sal_Int32 DeviceCoordinate
@ 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