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:
parent
ec2d221b08
commit
ce4f5377dd
7 changed files with 75 additions and 252 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue