vcl: Better kashida insertion position validation
Use the new HarfBuzz glyph flag HB_GLYPH_FLAG_SAFE_TO_INSERT_TATWEEL if
available to prevent kashida insertion where is would interrupt shaping
(between contextual glyphs). Previously we only prevented it when there
is a ligature across the insertion position, with this change fonts that
use contextual alternate instead of ligatures will be handled better as
well.
Change-Id: Ibb42a310c1e7dbcb225a1ba3acac82154b4edb3a
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/137649
Tested-by: Jenkins
Reviewed-by: Caolán McNamara <[email protected]>
diff --git a/vcl/inc/impglyphitem.hxx b/vcl/inc/impglyphitem.hxx
index ea3b2f3..f0e4e70 100644
--- a/vcl/inc/impglyphitem.hxx
+++ b/vcl/inc/impglyphitem.hxx
@@ -40,11 +40,12 @@
ALLOW_KASHIDA = 0x20,
IS_DROPPED = 0x40,
IS_CLUSTER_START = 0x80,
IS_UNSAFE_TO_BREAK = 0x100 // HB_GLYPH_FLAG_UNSAFE_TO_BREAK from harfbuzz
IS_UNSAFE_TO_BREAK = 0x100, // HB_GLYPH_FLAG_UNSAFE_TO_BREAK from harfbuzz
IS_SAFE_TO_INSERT_KASHIDA = 0x200 // HB_GLYPH_FLAG_SAFE_TO_INSERT_TATWEEL from harfbuzz
};
namespace o3tl
{
template <> struct typed_flags<GlyphItemFlags> : is_typed_flags<GlyphItemFlags, 0x1ff>
template <> struct typed_flags<GlyphItemFlags> : is_typed_flags<GlyphItemFlags, 0x3ff>
{
};
};
@@ -85,6 +86,10 @@
bool IsDropped() const { return bool(m_nFlags & GlyphItemFlags::IS_DROPPED); }
bool IsClusterStart() const { return bool(m_nFlags & GlyphItemFlags::IS_CLUSTER_START); }
bool IsUnsafeToBreak() const { return bool(m_nFlags & GlyphItemFlags::IS_UNSAFE_TO_BREAK); }
bool IsSafeToInsertKashida() const
{
return bool(m_nFlags & GlyphItemFlags::IS_SAFE_TO_INSERT_KASHIDA);
}
inline bool GetGlyphBoundRect(const LogicalFontInstance*, tools::Rectangle&) const;
inline bool GetGlyphOutline(const LogicalFontInstance*, basegfx::B2DPolyPolygon&) const;
diff --git a/vcl/source/gdi/CommonSalLayout.cxx b/vcl/source/gdi/CommonSalLayout.cxx
index 7e2bcd6..98364d4 100644
--- a/vcl/source/gdi/CommonSalLayout.cxx
+++ b/vcl/source/gdi/CommonSalLayout.cxx
@@ -438,6 +438,10 @@
const int nRunLen = nEndRunPos - nMinRunPos;
int nHbFlags = HB_BUFFER_FLAGS_DEFAULT;
#if HB_VERSION_ATLEAST(5, 1, 0)
// Produce HB_GLYPH_FLAG_SAFE_TO_INSERT_TATWEEL that we use below.
nHbFlags |= HB_BUFFER_FLAG_PRODUCE_SAFE_TO_INSERT_TATWEEL;
#endif
if (nMinRunPos == 0)
nHbFlags |= HB_BUFFER_FLAG_BOT; /* Beginning-of-text */
if (nEndRunPos == nLength)
@@ -577,6 +581,14 @@
nGlyphFlags |= GlyphItemFlags::IS_UNSAFE_TO_BREAK;
#endif
#if HB_VERSION_ATLEAST(5, 1, 0)
if (hb_glyph_info_get_glyph_flags(&pHbGlyphInfos[i]) & HB_GLYPH_FLAG_SAFE_TO_INSERT_TATWEEL)
nGlyphFlags |= GlyphItemFlags::IS_SAFE_TO_INSERT_KASHIDA;
#else
// If support is not present, then allow kashida anywhere.
nGlyphFlags |= GlyphItemFlags::IS_SAFE_TO_INSERT_KASHIDA;
#endif
DeviceCoordinate nAdvance, nXOffset, nYOffset;
if (aSubRun.maDirection == HB_DIRECTION_TTB)
{
@@ -837,18 +849,31 @@
if (pIter->glyphId() == 0)
break;
int nClusterEndPos = 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->charPos() != nCharPos)
#if HB_VERSION_ATLEAST(5, 1, 0)
// This is a combining mark, keep moving until we find a base,
// since HarfBuzz will tell as we can’t insert kashida after a
// mark, but we know know enough to move the marks when
// inserting kashida.
if (pPrev->IsDiacritic())
{
nClusterEndPos = pPrev->charPos();
continue;
}
#endif
if (pPrev->charPos() > nClusterEndPos)
{
// Check if the found glyph belongs to the next character,
// and return if it is safe to insert kashida before it,
// otherwise the current glyph will be a ligature which is
// invalid kashida position.
if (pPrev->charPos() == (nCharPos + 1))
return true;
if (pPrev->charPos() == (nClusterEndPos + 1))
return pPrev->IsSafeToInsertKashida();
break;
}
}