From ce4f5377dd7c066f7f860d86a55080c6f0cdb9d4 Mon Sep 17 00:00:00 2001 From: reuk Date: Tue, 27 Feb 2024 15:05:39 +0000 Subject: [PATCH] Typeface: Switch to using harfbuzz for computing simple advances on all platforms --- modules/juce_graphics/fonts/juce_Typeface.cpp | 73 +++++++++++++++++++ modules/juce_graphics/fonts/juce_Typeface.h | 4 +- .../juce_DirectWriteTypeface_windows.cpp | 43 ----------- .../native/juce_Fonts_android.cpp | 47 ------------ .../native/juce_Fonts_freetype.cpp | 63 ---------------- .../juce_graphics/native/juce_Fonts_mac.mm | 65 ----------------- .../native/juce_GraphicsContext_android.cpp | 32 -------- 7 files changed, 75 insertions(+), 252 deletions(-) diff --git a/modules/juce_graphics/fonts/juce_Typeface.cpp b/modules/juce_graphics/fonts/juce_Typeface.cpp index e5afacf04c..06dfb7f155 100644 --- a/modules/juce_graphics/fonts/juce_Typeface.cpp +++ b/modules/juce_graphics/fonts/juce_Typeface.cpp @@ -312,6 +312,79 @@ std::optional 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 +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 features = [] + { + std::vector 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 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{}, 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& glyphs, Array& xOffsets) +{ + doSimpleShape (*this, text, [&] (auto codepoint, auto xOffset) + { + if (codepoint.has_value()) + glyphs.add ((int) *codepoint); + + xOffsets.add (xOffset); + }); +} + //============================================================================== //============================================================================== #if JUCE_UNIT_TESTS diff --git a/modules/juce_graphics/fonts/juce_Typeface.h b/modules/juce_graphics/fonts/juce_Typeface.h index 626fd73227..046b0e8dc2 100644 --- a/modules/juce_graphics/fonts/juce_Typeface.h +++ b/modules/juce_graphics/fonts/juce_Typeface.h @@ -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& glyphs, Array& xOffsets) = 0; + void getGlyphPositions (const String& text, Array& glyphs, Array& xOffsets); /** Returns the outline for a glyph. The path returned will be normalised to a font height of 1.0. diff --git a/modules/juce_graphics/native/juce_DirectWriteTypeface_windows.cpp b/modules/juce_graphics/native/juce_DirectWriteTypeface_windows.cpp index e006c38ede..398d989d5c 100644 --- a/modules/juce_graphics/native/juce_DirectWriteTypeface_windows.cpp +++ b/modules/juce_graphics/native/juce_DirectWriteTypeface_windows.cpp @@ -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 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& resultGlyphs, Array& xOffsets) override - { - auto utf32 = text.toUTF32(); - auto numChars = utf32.length(); - std::vector 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(); diff --git a/modules/juce_graphics/native/juce_Fonts_android.cpp b/modules/juce_graphics/native/juce_Fonts_android.cpp index 2339a14020..b2105d59d3 100644 --- a/modules/juce_graphics/native/juce_Fonts_android.cpp +++ b/modules/juce_graphics/native/juce_Fonts_android.cpp @@ -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& glyphs, Array& xOffsets) override - { - const auto heightToPoints = getNativeDetails().getLegacyMetrics().getHeightToPointsFactor(); - const auto upem = hb_face_get_upem (hb_font_get_face (hbFont.get())); - - Point 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 - 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 blob, unsigned int index = 0) { auto face = FontStyleHelpers::getFaceForBlob ({ reinterpret_cast (blob.data()), blob.size() }, index); diff --git a/modules/juce_graphics/native/juce_Fonts_freetype.cpp b/modules/juce_graphics/native/juce_Fonts_freetype.cpp index 47345edff5..5496cc847c 100644 --- a/modules/juce_graphics/native/juce_Fonts_freetype.cpp +++ b/modules/juce_graphics/native/juce_Fonts_freetype.cpp @@ -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& resultGlyphs, Array& 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; diff --git a/modules/juce_graphics/native/juce_Fonts_mac.mm b/modules/juce_graphics/native/juce_Fonts_mac.mm index 317c3adf34..d549b160e8 100644 --- a/modules/juce_graphics/native/juce_Fonts_mac.mm +++ b/modules/juce_graphics/native/juce_Fonts_mac.mm @@ -623,71 +623,6 @@ public: getRegistered().remove (ctFont.get()); } - float getStringWidth (const String& text) override - { - float x = 0; - - if (ctFont != nullptr && text.isNotEmpty()) - { - CFUniquePtr cfText (text.toCFString()); - CFUniquePtr attribString (CFAttributedStringCreate (kCFAllocatorDefault, cfText.get(), getAttributedStringAtts().get())); - - CFUniquePtr 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& resultGlyphs, Array& xOffsets) override - { - xOffsets.add (0); - - if (ctFont != nullptr && text.isNotEmpty()) - { - float x = 0; - - CFUniquePtr cfText (text.toCFString()); - CFUniquePtr attribString (CFAttributedStringCreate (kCFAllocatorDefault, cfText.get(), getAttributedStringAtts().get())); - - CFUniquePtr 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(); diff --git a/modules/juce_graphics/native/juce_GraphicsContext_android.cpp b/modules/juce_graphics/native/juce_GraphicsContext_android.cpp index 73efca06e2..29f5a24236 100644 --- a/modules/juce_graphics/native/juce_GraphicsContext_android.cpp +++ b/modules/juce_graphics/native/juce_GraphicsContext_android.cpp @@ -35,38 +35,6 @@ namespace juce { -namespace GraphicsHelpers -{ - static LocalRef 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 (getEnv()->NewObject (AndroidPaint, AndroidPaint.constructor, constructorFlags)); - } - - static LocalRef createMatrix (JNIEnv* env, const AffineTransform& t) - { - auto m = LocalRef (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);