1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-10 23:44:24 +00:00

Typeface: Switch to using harfbuzz for computing simple advances on all platforms

This commit is contained in:
reuk 2024-02-27 15:05:39 +00:00
parent ec2d221b08
commit ce4f5377dd
No known key found for this signature in database
GPG key ID: FCB43929F012EE5C
7 changed files with 75 additions and 252 deletions

View file

@ -312,6 +312,79 @@ std::optional<uint32_t> Typeface::getNominalGlyphForCodepoint (juce_wchar cp) co
return result;
}
static constexpr auto hbTag (const char (&arr)[5])
{
return HB_TAG (arr[0], arr[1], arr[2], arr[3]);
}
template <typename Consumer>
static void doSimpleShape (const Typeface& typeface, const String& text, Consumer&& consumer)
{
HbBuffer buffer { hb_buffer_create() };
hb_buffer_add_utf8 (buffer.get(), text.toRawUTF8(), -1, 0, -1);
hb_buffer_set_cluster_level (buffer.get(), HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS);
hb_buffer_guess_segment_properties (buffer.get());
const auto& native = typeface.getNativeDetails();
auto* font = native.getFont();
// Disable ligatures, because TextEditor requires a 1:1 codepoint/glyph mapping for caret
// positioning to work as expected.
// Use an alternative method if you require shaping with ligature support.
static const std::vector<hb_feature_t> features = []
{
std::vector<hb_feature_t> result;
for (const auto key : { hbTag ("liga"), hbTag ("clig"), hbTag ("hlig"), hbTag ("dlig"), hbTag ("calt") })
result.push_back (hb_feature_t { key, 0, HB_FEATURE_GLOBAL_START, HB_FEATURE_GLOBAL_END });
return result;
}();
hb_shape (font, buffer.get(), features.data(), (unsigned int) features.size());
unsigned int numGlyphs{};
auto* infos = hb_buffer_get_glyph_infos (buffer.get(), &numGlyphs);
auto* positions = hb_buffer_get_glyph_positions (buffer.get(), &numGlyphs);
const auto heightToPoints = native.getLegacyMetrics().getHeightToPointsFactor();
const auto upem = hb_face_get_upem (hb_font_get_face (font));
Point<hb_position_t> cursor{};
for (auto i = decltype (numGlyphs){}; i < numGlyphs; ++i)
{
const auto& info = infos[i];
const auto& position = positions[i];
consumer (std::make_optional (info.codepoint),
heightToPoints * ((float) cursor.x + (float) position.x_offset) / (float) upem);
cursor += Point { position.x_advance, position.y_advance };
}
consumer (std::optional<hb_codepoint_t>{}, heightToPoints * (float) cursor.x / (float) upem);
}
float Typeface::getStringWidth (const String& text)
{
float x{};
doSimpleShape (*this, text, [&] (auto, auto xOffset)
{
x = xOffset;
});
return x;
}
void Typeface::getGlyphPositions (const String& text, Array<int>& glyphs, Array<float>& xOffsets)
{
doSimpleShape (*this, text, [&] (auto codepoint, auto xOffset)
{
if (codepoint.has_value())
glyphs.add ((int) *codepoint);
xOffsets.add (xOffset);
});
}
//==============================================================================
//==============================================================================
#if JUCE_UNIT_TESTS

View file

@ -136,7 +136,7 @@ public:
The distance returned is based on the font having an normalised height of 1.0.
You should never need to call this!
*/
virtual float getStringWidth (const String& text) = 0;
float getStringWidth (const String& text);
/** @deprecated
This function has several shortcomings:
@ -159,7 +159,7 @@ public:
The distances returned are based on the font having an normalised height of 1.0.
You should never need to call this!
*/
virtual void getGlyphPositions (const String& text, Array<int>& glyphs, Array<float>& xOffsets) = 0;
void getGlyphPositions (const String& text, Array<int>& glyphs, Array<float>& xOffsets);
/** Returns the outline for a glyph.
The path returned will be normalised to a font height of 1.0.

View file

@ -482,49 +482,6 @@ public:
factories->getFonts().removeCollection (collection);
}
float getStringWidth (const String& text) override
{
auto utf32 = text.toUTF32();
auto numChars = utf32.length();
std::vector<UINT16> results (numChars);
if (FAILED (dwFontFace->GetGlyphIndices (utf32, (UINT32) numChars, results.data())))
return {};
float x = 0;
for (size_t i = 0; i < numChars; ++i)
x += getKerning (results[i], (i + 1) < numChars ? results[i + 1] : -1);
const auto heightToPoints = getNativeDetails().getLegacyMetrics().getHeightToPointsFactor();
return x * heightToPoints;
}
void getGlyphPositions (const String& text, Array<int>& resultGlyphs, Array<float>& xOffsets) override
{
auto utf32 = text.toUTF32();
auto numChars = utf32.length();
std::vector<UINT16> results (numChars);
float x = 0;
const auto heightToPoints = getNativeDetails().getLegacyMetrics().getHeightToPointsFactor();
if (SUCCEEDED (dwFontFace->GetGlyphIndices (utf32, (UINT32) numChars, results.data())))
{
resultGlyphs.ensureStorageAllocated ((int) numChars);
xOffsets.ensureStorageAllocated ((int) numChars + 1);
for (size_t i = 0; i < numChars; ++i)
{
resultGlyphs.add (results[i]);
xOffsets.add (x * heightToPoints);
x += getKerning (results[i], (i + 1) < numChars ? results[i + 1] : -1);
}
}
xOffsets.add (x * heightToPoints);
}
static Typeface::Ptr from (const Font& f)
{
const auto name = f.getTypefaceName();

View file

@ -267,54 +267,7 @@ public:
c->remove ({ getName(), getStyle() });
}
float getStringWidth (const String& text) override
{
const auto heightToPoints = getNativeDetails().getLegacyMetrics().getHeightToPointsFactor();
const auto upem = hb_face_get_upem (hb_font_get_face (hbFont.get()));
hb_position_t x{};
doSimpleShape (text, [&] (const auto&, const auto& position)
{
x += position.x_advance;
});
return heightToPoints * (float) x / (float) upem;
}
void getGlyphPositions (const String& text, Array<int>& glyphs, Array<float>& xOffsets) override
{
const auto heightToPoints = getNativeDetails().getLegacyMetrics().getHeightToPointsFactor();
const auto upem = hb_face_get_upem (hb_font_get_face (hbFont.get()));
Point<hb_position_t> cursor{};
doSimpleShape (text, [&] (const auto& info, const auto& position)
{
glyphs.add ((int) info.codepoint);
xOffsets.add (heightToPoints * ((float) cursor.x + (float) position.x_offset) / (float) upem);
cursor += Point { position.x_advance, position.y_advance };
});
xOffsets.add (heightToPoints * (float) cursor.x / (float) upem);
}
private:
template <typename Consumer>
void doSimpleShape (const String& text, Consumer&& consumer)
{
HbBuffer buffer { hb_buffer_create() };
hb_buffer_add_utf8 (buffer.get(), text.toRawUTF8(), -1, 0, -1);
hb_buffer_guess_segment_properties (buffer.get());
hb_shape (hbFont.get(), buffer.get(), nullptr, 0);
unsigned int numGlyphs{};
auto* infos = hb_buffer_get_glyph_infos (buffer.get(), &numGlyphs);
auto* positions = hb_buffer_get_glyph_positions (buffer.get(), &numGlyphs);
for (auto i = decltype (numGlyphs){}; i < numGlyphs; ++i)
consumer (infos[i], positions[i]);
}
static Typeface::Ptr fromMemory (DoCache cache, Span<const std::byte> blob, unsigned int index = 0)
{
auto face = FontStyleHelpers::getFaceForBlob ({ reinterpret_cast<const char*> (blob.data()), blob.size() }, index);

View file

@ -431,44 +431,6 @@ public:
list->removeMemoryFace (ftFace);
}
float getStringWidth (const String& text) override
{
const auto heightToPoints = getNativeDetails().getLegacyMetrics().getHeightToPointsFactor();
float x = 0;
for (auto iter = text.begin(), end = text.end(); iter != end; ++iter)
{
const auto currentChar = *iter;
const auto nextIter = iter + 1;
const auto nextChar = nextIter == end ? 0 : *nextIter;
x += getSpacingForGlyphs (getNominalGlyphForCharacter (currentChar),
getNominalGlyphForCharacter (nextChar));
}
return x * heightToPoints;
}
void getGlyphPositions (const String& text, Array<int>& resultGlyphs, Array<float>& xOffsets) override
{
for (auto c : text)
resultGlyphs.add ((int) getNominalGlyphForCharacter (c));
const auto heightToPoints = getNativeDetails().getLegacyMetrics().getHeightToPointsFactor();
xOffsets.add (0);
for (auto iter = resultGlyphs.begin(); iter != resultGlyphs.end(); ++iter)
{
const auto currentGlyph = *iter;
const auto nextIter = iter + 1;
const auto nextGlyph = nextIter == resultGlyphs.end() ? 0 : *nextIter;
xOffsets.add (xOffsets.getLast() + getSpacingForGlyphs ((FT_UInt) currentGlyph, (FT_UInt) nextGlyph) * heightToPoints);
}
}
private:
FreeTypeTypeface (DoCache cache,
FTFaceWrapper::Ptr ftFaceIn,
@ -485,31 +447,6 @@ private:
list->addMemoryFace (ftFace);
}
float getKerningForGlyphs (FT_UInt a, FT_UInt b) const
{
FT_Vector kerning{};
if (FT_Get_Kerning (ftFace->face, a, b, FT_KERNING_UNSCALED, &kerning) != 0)
return {};
return ((float) kerning.x / (float) ftFace->face->units_per_EM);
}
float getSpacingForGlyphs (FT_UInt a, FT_UInt b) const
{
FT_Fixed advance{};
if (FT_Get_Advance (ftFace->face, a, FT_LOAD_ADVANCE_ONLY | FT_LOAD_NO_SCALE, &advance) != 0)
return {};
return getKerningForGlyphs (a, b) + ((float) advance / (float) ftFace->face->units_per_EM);
}
FT_UInt getNominalGlyphForCharacter (juce_wchar c) const
{
return FT_Get_Char_Index (ftFace->face, (FT_ULong) c);
}
FTFaceWrapper::Ptr ftFace;
HbFont hb;
DoCache doCache;

View file

@ -623,71 +623,6 @@ public:
getRegistered().remove (ctFont.get());
}
float getStringWidth (const String& text) override
{
float x = 0;
if (ctFont != nullptr && text.isNotEmpty())
{
CFUniquePtr<CFStringRef> cfText (text.toCFString());
CFUniquePtr<CFAttributedStringRef> attribString (CFAttributedStringCreate (kCFAllocatorDefault, cfText.get(), getAttributedStringAtts().get()));
CFUniquePtr<CTLineRef> line (CTLineCreateWithAttributedString (attribString.get()));
auto runArray = CTLineGetGlyphRuns (line.get());
const auto heightToPoints = getNativeDetails().getLegacyMetrics().getHeightToPointsFactor();
for (CFIndex i = 0; i < CFArrayGetCount (runArray); ++i)
{
auto run = (CTRunRef) CFArrayGetValueAtIndex (runArray, i);
auto length = CTRunGetGlyphCount (run);
const CoreTextTypeLayout::Advances advances (run, length);
for (int j = 0; j < length; ++j)
x += (float) advances.advances[j].width;
}
x *= heightToPoints;
}
return x;
}
void getGlyphPositions (const String& text, Array<int>& resultGlyphs, Array<float>& xOffsets) override
{
xOffsets.add (0);
if (ctFont != nullptr && text.isNotEmpty())
{
float x = 0;
CFUniquePtr<CFStringRef> cfText (text.toCFString());
CFUniquePtr<CFAttributedStringRef> attribString (CFAttributedStringCreate (kCFAllocatorDefault, cfText.get(), getAttributedStringAtts().get()));
CFUniquePtr<CTLineRef> line (CTLineCreateWithAttributedString (attribString.get()));
auto runArray = CTLineGetGlyphRuns (line.get());
const auto heightToPointsFactor = getNativeDetails().getLegacyMetrics().getHeightToPointsFactor();
for (CFIndex i = 0; i < CFArrayGetCount (runArray); ++i)
{
auto run = (CTRunRef) CFArrayGetValueAtIndex (runArray, i);
auto length = CTRunGetGlyphCount (run);
const CoreTextTypeLayout::Advances advances (run, length);
const CoreTextTypeLayout::Glyphs glyphs (run, (size_t) length);
for (int j = 0; j < length; ++j)
{
x += (float) advances.advances[j].width;
xOffsets.add (x * heightToPointsFactor);
resultGlyphs.add (glyphs.glyphs[j]);
}
}
}
}
CTFontRef getFontRef() const
{
return ctFont.get();

View file

@ -35,38 +35,6 @@
namespace juce
{
namespace GraphicsHelpers
{
static LocalRef<jobject> createPaint (Graphics::ResamplingQuality quality)
{
jint constructorFlags = 1 /*ANTI_ALIAS_FLAG*/
| 4 /*DITHER_FLAG*/
| 128 /*SUBPIXEL_TEXT_FLAG*/;
if (quality > Graphics::lowResamplingQuality)
constructorFlags |= 2; /*FILTER_BITMAP_FLAG*/
return LocalRef<jobject> (getEnv()->NewObject (AndroidPaint, AndroidPaint.constructor, constructorFlags));
}
static LocalRef<jobject> createMatrix (JNIEnv* env, const AffineTransform& t)
{
auto m = LocalRef<jobject> (env->NewObject (AndroidMatrix, AndroidMatrix.constructor));
jfloat values[9] = { t.mat00, t.mat01, t.mat02,
t.mat10, t.mat11, t.mat12,
0.0f, 0.0f, 1.0f };
jfloatArray javaArray = env->NewFloatArray (9);
env->SetFloatArrayRegion (javaArray, 0, 9, values);
env->CallVoidMethod (m, AndroidMatrix.setValues, javaArray);
env->DeleteLocalRef (javaArray);
return m;
}
} // namespace GraphicsHelpers
ImagePixelData::Ptr NativeImageType::create (Image::PixelFormat format, int width, int height, bool clearImage) const
{
return SoftwareImageType().create (format, width, height, clearImage);