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

Font: Allow switching between legacy non-portable metrics, and new portable metrics

This commit is contained in:
reuk 2024-03-19 23:33:55 +00:00
parent 4f2c287f9b
commit c2fce879c5
No known key found for this signature in database
GPG key ID: FCB43929F012EE5C
12 changed files with 291 additions and 464 deletions

View file

@ -9,14 +9,24 @@ new TypefaceMetricsKind argument. The getAscent(), getDescent(), and
getHeightToPointsFactor() members have been replaced by getMetrics(), which
returns the same metrics information all at once.
Font instances now store a metrics kind internally. Calls to Font::getAscent()
and other functions that query font metrics will always use the Font's stored
metrics kind. Calls to Font::operator== will take the metrics kinds into
account, so two fonts that differ only in their stored metrics kind will
be considered non-equal.
**Possible Issues**
Code that calls any of the affected functions will fail to compile.
Code that calls any of the affected Typeface functions will fail to compile.
Code that compares Font instances may behave differently if the compared font
instances use mismatched metrics kinds.
**Workaround**
Specify the kind of metrics you require when calling Typeface member functions.
Call getMetrics() instead of the old individual getters for metrics.
Call getMetrics() instead of the old individual getters for metrics. Review
calls to Font::operator==, especially where comparing against a
default-constructed Font.
**Rationale**
@ -27,8 +37,9 @@ layout changes in existing projects), and portable metrics (more suitable for
new or cross-platform projects).
Most users will fetch metrics from Font objects rather than from the Typeface.
Font will continue to return non-portable metrics when constructed using the
existing (deprecated) constructors. Portable metrics can be enabled by
switching to the new Font constructor that takes a FontOptions argument.
old (deprecated) constructors. Portable metrics can be enabled by switching to
the new Font constructor that takes a FontOptions argument. See the
documentation for TypefaceMetricsKind for more details.
## Change

View file

@ -533,7 +533,7 @@ void LowLevelGraphicsPostScriptRenderer::drawGlyph (int glyphNumber, const Affin
{
Path p;
Font& font = stateStack.getLast()->font;
font.getTypefacePtr()->getOutlineForGlyph (glyphNumber, p);
font.getTypefacePtr()->getOutlineForGlyph (font.getMetricsKind(), glyphNumber, p);
fillPath (p, AffineTransform::scale (font.getHeight() * font.getHorizontalScale(), font.getHeight()).followedBy (transform));
}

View file

@ -216,8 +216,20 @@ public:
HbFont getFontPtr (const Font& f)
{
const ScopedLock lock (mutex);
if (auto ptr = getTypefacePtr (f))
return ptr->getNativeDetails().getFontAtSizeAndScale (f.getHeight(), f.getHorizontalScale());
return ptr->getNativeDetails().getFontAtSizeAndScale (f.getMetricsKind(), f.getHeight(), f.getHorizontalScale());
return {};
}
TypefaceMetrics getMetrics (const Font& f)
{
const ScopedLock lock (mutex);
if (auto ptr = getTypefacePtr (f))
return ptr->getMetrics (f.getMetricsKind());
return {};
}
@ -228,16 +240,6 @@ public:
typeface = nullptr;
}
float getAscent (const Font& f)
{
const ScopedLock lock (mutex);
if (approximatelyEqual (ascent, 0.0f))
ascent = getTypefacePtr (f)->getAscent();
return getHeight() * ascent;
}
/* We do not need to lock in these functions, as it's guaranteed
that these data members can only change if there is a single Font
instance referencing the shared state.
@ -256,6 +258,7 @@ public:
float getKerning() const { return options.getKerningFactor(); }
bool getUnderline() const { return options.getUnderline(); }
bool getFallbackEnabled() const { return options.getFallbackEnabled(); }
TypefaceMetricsKind getMetricsKind() const { return options.getMetricsKind(); }
/* This shared state may be shared between two or more Font instances that are being
read/modified from multiple threads.
@ -269,7 +272,7 @@ public:
jassert (getReferenceCount() == 1);
typeface = newTypeface;
if (typeface != nullptr)
if (newTypeface != nullptr)
{
options = options.withTypeface (typeface)
.withName (typeface->getName())
@ -307,12 +310,6 @@ public:
options = options.withKerningFactor (x);
}
void setAscent (float x)
{
jassert (getReferenceCount() == 1);
ascent = x;
}
void setUnderline (bool x)
{
jassert (getReferenceCount() == 1);
@ -348,7 +345,6 @@ private:
}
Typeface::Ptr typeface;
float ascent{};
FontOptions options;
CriticalSection mutex;
};
@ -359,22 +355,28 @@ Font::Font (FontOptions opt)
{
}
Font::Font() : font (new SharedFontInternal (FontOptions{})) {}
Font::Font (const Typeface::Ptr& typeface) : font (new SharedFontInternal (FontOptions { typeface })) {}
template <typename... Args>
auto legacyArgs (Args&&... args)
{
return FontOptions { std::forward<Args> (args)... }.withMetricsKind (TypefaceMetricsKind::legacy);
}
Font::Font() : font (new SharedFontInternal (legacyArgs())) {}
Font::Font (const Typeface::Ptr& typeface) : font (new SharedFontInternal (legacyArgs (typeface))) {}
Font::Font (const Font& other) noexcept : font (other.font) {}
Font::Font (float fontHeight, int styleFlags)
: font (new SharedFontInternal (FontOptions { fontHeight, styleFlags }))
: font (new SharedFontInternal (legacyArgs (fontHeight, styleFlags)))
{
}
Font::Font (const String& typefaceName, float fontHeight, int styleFlags)
: font (new SharedFontInternal (FontOptions { typefaceName, fontHeight, styleFlags }))
: font (new SharedFontInternal (legacyArgs (typefaceName, fontHeight, styleFlags)))
{
}
Font::Font (const String& typefaceName, const String& typefaceStyle, float fontHeight)
: font (new SharedFontInternal (FontOptions { typefaceName, typefaceStyle, fontHeight }))
: font (new SharedFontInternal (legacyArgs (typefaceName, typefaceStyle, fontHeight)))
{
}
@ -461,7 +463,6 @@ void Font::setTypefaceName (const String& faceName)
dupeInternalIfShared();
font->setTypefaceName (faceName);
font->setTypeface (nullptr);
font->setAscent (0);
}
}
@ -472,7 +473,6 @@ void Font::setTypefaceStyle (const String& typefaceStyle)
dupeInternalIfShared();
font->setTypefaceStyle (typefaceStyle);
font->setTypeface (nullptr);
font->setAscent (0);
}
}
@ -531,7 +531,7 @@ Font Font::withHeight (const float newHeight) const
float Font::getHeightToPointsFactor() const
{
return getTypefacePtr()->getHeightToPointsFactor();
return getTypefacePtr()->getMetrics (getMetricsKind()).heightToPoints;
}
Font Font::withPointHeight (float heightInPoints) const
@ -591,7 +591,6 @@ void Font::setStyleFlags (const int newFlags)
font->setTypeface (nullptr);
font->setTypefaceStyle (FontStyleHelpers::getStyleName (newFlags));
font->setUnderline ((newFlags & underlined) != 0);
font->setAscent (0);
}
}
@ -682,6 +681,8 @@ bool Font::isBold() const noexcept { return FontStyleHelpers::isBold
bool Font::isItalic() const noexcept { return FontStyleHelpers::isItalic (font->getTypefaceStyle()); }
bool Font::isUnderlined() const noexcept { return font->getUnderline(); }
TypefaceMetricsKind Font::getMetricsKind() const noexcept { return font->getMetricsKind(); }
void Font::setBold (const bool shouldBeBold)
{
auto flags = getStyleFlags();
@ -705,7 +706,7 @@ void Font::setUnderline (const bool shouldBeUnderlined)
float Font::getAscent() const
{
return font->getAscent (*this);
return font->getMetrics (*this).ascent * getHeight();
}
float Font::getHeight() const noexcept { return font->getHeight(); }
@ -722,13 +723,13 @@ int Font::getStringWidth (const String& text) const
float Font::getStringWidthFloat (const String& text) const
{
const auto w = getTypefacePtr()->getStringWidth (text, getHeight(), getHorizontalScale());
const auto w = getTypefacePtr()->getStringWidth (font->getMetricsKind(), text, getHeight(), getHorizontalScale());
return w + (font->getHeight() * font->getHorizontalScale() * font->getKerning() * (float) text.length());
}
void Font::getGlyphPositions (const String& text, Array<int>& glyphs, Array<float>& xOffsets) const
{
getTypefacePtr()->getGlyphPositions (text, glyphs, xOffsets, getHeight(), getHorizontalScale());
getTypefacePtr()->getGlyphPositions (font->getMetricsKind(), text, glyphs, xOffsets, getHeight(), getHorizontalScale());
if (auto num = xOffsets.size())
{

View file

@ -349,6 +349,9 @@ public:
/** Returns true if the font is underlined. */
bool isUnderlined() const noexcept;
/** Returns the kind of metrics used by this Font. */
TypefaceMetricsKind getMetricsKind() const noexcept;
//==============================================================================
/** Returns the font's horizontal scale.
A value of 1.0 is the normal scale, less than this will be narrower, greater

View file

@ -92,6 +92,7 @@ auto FontOptions::tie() const
style,
typeface.get(),
fallbacks,
metricsKind,
height,
tracking,
horizontalScale,

View file

@ -121,6 +121,9 @@ public:
/** Returns a copy of these options with underline enabled or disabled, defaults to disabled. */
[[nodiscard]] FontOptions withUnderline (bool x = true) const { return withMember (*this, &FontOptions::underlined, x); }
/** Returns a copy of these options the specified metrics kind. */
[[nodiscard]] FontOptions withMetricsKind (TypefaceMetricsKind x) const { return withMember (*this, &FontOptions::metricsKind, x); }
/** @see withName() */
[[nodiscard]] auto getName() const { return name; }
/** @see withStyle() */
@ -139,6 +142,8 @@ public:
[[nodiscard]] auto getFallbackEnabled() const { return fallbackEnabled; }
/** @see withUnderline() */
[[nodiscard]] auto getUnderline() const { return underlined; }
/** @see withMetricsKind() */
[[nodiscard]] auto getMetricsKind() const { return metricsKind; }
/** Equality operator. */
[[nodiscard]] bool operator== (const FontOptions& other) const;
@ -159,6 +164,7 @@ private:
String name, style;
Typeface::Ptr typeface;
std::vector<String> fallbacks;
TypefaceMetricsKind metricsKind { TypefaceMetricsKind::portable };
float height{};
float tracking{};
float horizontalScale = 1.0f;

View file

@ -81,7 +81,7 @@ void PositionedGlyph::createPath (Path& path) const
if (auto t = font.getTypefacePtr())
{
Path p;
t->getOutlineForGlyph (glyph, p);
t->getOutlineForGlyph (font.getMetricsKind(), glyph, p);
path.addPath (p, AffineTransform::scale (font.getHeight() * font.getHorizontalScale(), font.getHeight())
.translated (x, y));
@ -96,7 +96,7 @@ bool PositionedGlyph::hitTest (float px, float py) const
if (auto t = font.getTypefacePtr())
{
Path p;
t->getOutlineForGlyph (glyph, p);
t->getOutlineForGlyph (font.getMetricsKind(), glyph, p);
AffineTransform::translation (-x, -y)
.scaled (1.0f / (font.getHeight() * font.getHorizontalScale()), 1.0f / font.getHeight())

View file

@ -153,7 +153,7 @@ static void overrideCTFontAdvances (hb_font_t* hb, CTFontRef fontRef)
}
#endif
struct TypefaceLegacyMetrics
struct TypefaceAscentDescent
{
float ascent{}; // in em units
float descent{}; // in em units
@ -163,29 +163,46 @@ struct TypefaceLegacyMetrics
float getPointsToHeightFactor() const { return ascent + descent; }
float getHeightToPointsFactor() const { return 1.0f / getPointsToHeightFactor(); }
TypefaceMetrics getTypefaceMetrics() const
{
return { getScaledAscent(), getHeightToPointsFactor() };
}
};
using HbFont = std::unique_ptr<hb_font_t, FunctionPointerDestructor<hb_font_destroy>>;
using HbFace = std::unique_ptr<hb_face_t, FunctionPointerDestructor<hb_face_destroy>>;
using HbBuffer = std::unique_ptr<hb_buffer_t, FunctionPointerDestructor<hb_buffer_destroy>>;
using HbBlob = std::unique_ptr<hb_blob_t, FunctionPointerDestructor<hb_blob_destroy>>;
class Typeface::Native
{
public:
explicit Native (hb_font_t* fontRef)
: Native (fontRef, findLegacyMetrics (fontRef)) {}
Native (hb_font_t* fontRef, TypefaceLegacyMetrics metrics)
: font (fontRef), legacyMetrics (metrics) {}
Native (hb_font_t* fontRef, TypefaceAscentDescent nonPortableMetricsIn)
: font (fontRef), nonPortable (nonPortableMetricsIn)
{
}
auto* getFont() const { return font; }
auto getLegacyMetrics() const { return legacyMetrics; }
TypefaceAscentDescent getMetrics (TypefaceMetricsKind kind) const
{
switch (kind)
{
case TypefaceMetricsKind::legacy:
return nonPortable;
HbFont getFontAtSizeAndScale (float height, float horizontalScale) const
case TypefaceMetricsKind::portable:
return portable;
}
return {};
}
HbFont getFontAtSizeAndScale (TypefaceMetricsKind kind, float height, float horizontalScale) const
{
HbFont subFont { hb_font_create_sub_font (font) };
const auto points = height * getLegacyMetrics().getHeightToPointsFactor();
const auto points = height * getMetrics (kind).getHeightToPointsFactor();
hb_font_set_ptem (subFont.get(), points);
hb_font_set_scale (subFont.get(), HbScale::juceToHb (points * horizontalScale), HbScale::juceToHb (points));
@ -198,28 +215,27 @@ public:
}
private:
static TypefaceLegacyMetrics findLegacyMetrics (hb_font_t* f)
static TypefaceAscentDescent findPortableMetrics (hb_font_t* f, TypefaceAscentDescent fallback)
{
hb_font_extents_t extents{};
if (! hb_font_get_h_extents (f, &extents))
{
// jassertfalse;
return { 0.5f, 0.5f };
}
return fallback;
const auto ascent = std::abs ((float) extents.ascender);
const auto descent = std::abs ((float) extents.descender);
const auto upem = (float) hb_face_get_upem (hb_font_get_face (f));
TypefaceLegacyMetrics result;
TypefaceAscentDescent result;
result.ascent = ascent / upem;
result.descent = descent / upem;
return result;
}
hb_font_t* font = nullptr;
TypefaceLegacyMetrics legacyMetrics;
TypefaceAscentDescent nonPortable;
TypefaceAscentDescent portable = findPortableMetrics (font, nonPortable);
};
struct FontStyleHelpers
@ -292,7 +308,7 @@ struct FontStyleHelpers
private:
static String findName (const String& placeholder)
{
const Font f (FontOptions (placeholder, 15.0f, Font::plain));
const Font f (FontOptions { placeholder, 15.0f, Font::plain });
return Font::getDefaultTypefaceForFont (f)->getName();
}
@ -315,24 +331,21 @@ struct FontStyleHelpers
static HbFace getFaceForBlob (Span<const char> bytes, unsigned int index)
{
auto* blob = hb_blob_create_or_fail (bytes.data(),
HbBlob blob { hb_blob_create_or_fail (bytes.data(),
(unsigned int) bytes.size(),
HB_MEMORY_MODE_DUPLICATE,
nullptr,
nullptr);
const ScopeGuard scope { [&] { hb_blob_destroy (blob); } };
nullptr) };
const auto count = hb_face_count (blob);
const auto count = hb_face_count (blob.get());
if (index < count)
return HbFace { hb_face_create (blob.get(), index) };
if (count < 1)
{
// Attempted to create a font from invalid data. Perhaps the font format was unrecognised.
jassertfalse;
return {};
}
return HbFace { hb_face_create (blob, index) };
}
};
//==============================================================================
@ -388,11 +401,11 @@ static HbDrawFuncs getPathDrawFuncs()
return result;
}
void Typeface::getOutlineForGlyph (int glyphNumber, Path& path)
void Typeface::getOutlineForGlyph (TypefaceMetricsKind kind, int glyphNumber, Path& path) const
{
const auto native = getNativeDetails();
auto* font = native.getFont();
const auto metrics = getNativeDetails().getLegacyMetrics();
const auto metrics = native.getMetrics (kind);
const auto scale = metrics.getHeightToPointsFactor() / (float) hb_face_get_upem (hb_font_get_face (font));
// getTypefaceGlyph returns glyphs in em space, getOutlineForGlyph returns glyphs in "special JUCE units" space
@ -405,10 +418,10 @@ void Typeface::applyVerticalHintingTransform (float, Path&)
jassertfalse;
}
EdgeTable* Typeface::getEdgeTableForGlyph (int glyphNumber, const AffineTransform& transform, float)
EdgeTable* Typeface::getEdgeTableForGlyph (TypefaceMetricsKind kind, int glyphNumber, const AffineTransform& transform, float)
{
Path path;
getOutlineForGlyph (glyphNumber, path);
getOutlineForGlyph (kind, glyphNumber, path);
path.applyTransform (transform);
return new EdgeTable (path.getBounds().getSmallestIntegerContainer().expanded (1, 0), std::move (path), {});
@ -422,323 +435,6 @@ static Colour makeColour (hb_color_t c)
hb_color_get_blue (c));
}
class HbPaintGroup
{
public:
void pushClipGlyph (const AffineTransform& t, hb_codepoint_t glyph, hb_font_t* font)
{
auto path = getGlyphPathInGlyphUnits (glyph, font);
path.applyTransform (t);
pushClip (std::move (path));
}
void pushClipRect (const AffineTransform& t, Rectangle<float> rect)
{
Path path;
path.addRectangle (rect);
path.applyTransform (t);
pushClip (std::move (path));
}
void popClip()
{
clip.pop_back();
}
void fill (hb_bool_t foreground, hb_color_t c)
{
addLayerChecked (foreground, c);
}
void linearGradient (hb_color_line_t&, Point<float>, Point<float>, Point<float>)
{
// Support for COLRv1 glyphs is not fully implemented.
jassertfalse;
}
void radialGradient (hb_color_line_t&, Point<float>, float, Point<float>, float)
{
// Support for COLRv1 glyphs is not fully implemented.
jassertfalse;
}
void sweepGradient (hb_color_line_t&, Point<float>, float, float)
{
// Support for COLRv1 glyphs is not fully implemented.
jassertfalse;
}
bool image (const AffineTransform& t, hb_blob_t* image, unsigned int width, unsigned int height, hb_tag_t format, float, hb_glyph_extents_t* extents)
{
switch (format)
{
case HB_PAINT_IMAGE_FORMAT_BGRA:
// Raw bitmap-based glyphs are not currently supported.
// If you hit this assertion, please let the JUCE team know which font you're
// attempting to use.
// Depending on demand, support for this feature may be added in the future.
jassertfalse;
return false;
case HB_PAINT_IMAGE_FORMAT_PNG:
{
unsigned int imageDataSize{};
const char* imageData = hb_blob_get_data (image, &imageDataSize);
const auto juceImage = PNGImageFormat::loadFrom (imageData, imageDataSize);
if (juceImage.isNull())
return false;
const auto transform = AffineTransform::scale ((float) extents->width / (float) width,
(float) extents->height / (float) height)
.translated ((float) extents->x_bearing,
(float) extents->y_bearing)
.followedBy (t);
ImageLayer imageLayer { juceImage, transform };
layers.push_back ({ std::move (imageLayer) });
return true;
}
case HB_PAINT_IMAGE_FORMAT_SVG:
// SVG-based glyphs are not currently supported.
// If you hit this assertion, please let the JUCE team know which font you're
// attempting to use.
// Depending on demand, support for this feature may be added in the future.
jassertfalse;
return false;
}
jassertfalse;
return false;
}
std::vector<GlyphLayer> getLayers() &&
{
return std::move (layers);
}
void appendLayers (Span<GlyphLayer> l)
{
for (auto& layer : l)
layers.emplace_back (std::move (layer));
}
private:
GlyphLayer makeLayer (hb_bool_t foreground, hb_color_t c) const
{
return { ColourLayer { clip.back(), foreground ? std::optional<Colour>() : makeColour (c) } };
}
void pushClip (Path path)
{
pushClip ({ path.getBounds().getSmallestIntegerContainer().expanded (1, 0), path, {} });
}
template <typename... Args>
void addLayerChecked (Args&&... args)
{
if (clip.empty())
{
jassertfalse;
return;
}
layers.push_back (makeLayer (std::forward<Args> (args)...));
}
void pushClip (const EdgeTable& et)
{
if (! clip.empty())
{
clip.push_back (clip.back());
clip.back().clipToEdgeTable (et);
}
else
{
clip.push_back (et);
}
}
std::vector<EdgeTable> clip;
std::vector<GlyphLayer> layers;
};
class HbPaintContext
{
public:
explicit HbPaintContext (const AffineTransform& transformIn)
: baseTransform (transformIn)
{
}
void addTransform (const AffineTransform& transform)
{
transforms.push_back (transforms.empty() ? transform : transform.followedBy (transforms.back()));
}
void popTransform()
{
transforms.pop_back();
}
void pushClipGlyph (hb_codepoint_t glyph, hb_font_t* font)
{
groups.back().pushClipGlyph (getTransform(), glyph, font);
}
void pushClipRect (Rectangle<float> rect)
{
groups.back().pushClipRect (getTransform(), rect);
}
void popClip()
{
groups.back().popClip();
}
void fill (hb_bool_t foreground, hb_color_t c)
{
groups.back().fill (foreground, c);
}
void linearGradient (hb_color_line_t& line, Point<float> p0, Point<float> p1, Point<float> p2)
{
groups.back().linearGradient (line, p0, p1, p2);
}
void radialGradient (hb_color_line_t& line, Point<float> p0, float r0, Point<float> p1, float r1)
{
groups.back().radialGradient (line, p0, r0, p1, r1);
}
void sweepGradient (hb_color_line_t& line, Point<float> p, float begin, float end)
{
groups.back().sweepGradient (line, p, begin, end);
}
bool image (hb_blob_t* image, unsigned int width, unsigned int height, hb_tag_t format, float slant, hb_glyph_extents_t* extents)
{
return groups.back().image (getTransform(), image, width, height, format, slant, extents);
}
void pushGroup()
{
groups.emplace_back();
}
void popGroup ([[maybe_unused]] hb_paint_composite_mode_t mode)
{
// There is currently extremely limited support for colour glyph blend modes
jassert (mode == HB_PAINT_COMPOSITE_MODE_SRC_OVER);
auto newLayers = std::move (groups.back()).getLayers();
groups.pop_back();
groups.back().appendLayers (newLayers);
}
std::vector<GlyphLayer> getLayers() &&
{
return std::move (groups.back()).getLayers();
}
private:
AffineTransform getTransform() const
{
const auto glyphSpaceTransform = transforms.empty() ? AffineTransform{} : transforms.back();
return glyphSpaceTransform.followedBy (baseTransform);
}
AffineTransform baseTransform;
std::vector<AffineTransform> transforms;
std::vector<HbPaintGroup> groups = std::vector<HbPaintGroup> (1);
};
using HbPaintFuncs = std::unique_ptr<hb_paint_funcs_t, FunctionPointerDestructor<hb_paint_funcs_destroy>>;
static HbPaintFuncs getPathPaintFuncs()
{
HbPaintFuncs funcs { hb_paint_funcs_create() };
hb_paint_funcs_set_push_transform_func (funcs.get(), [] (auto*, auto* data, auto xx, auto yx, auto xy, auto yy, auto dx, auto dy, auto*)
{
auto& context = *static_cast<HbPaintContext*> (data);
context.addTransform ({ xx, xy, dx, yx, yy, dy });
}, nullptr, nullptr);
hb_paint_funcs_set_pop_transform_func (funcs.get(), [] (auto*, void* data, auto*)
{
auto& context = *static_cast<HbPaintContext*> (data);
context.popTransform();
}, nullptr, nullptr);
hb_paint_funcs_set_push_clip_glyph_func (funcs.get(), [] (auto*, void* data, auto glyph, auto* font, auto*)
{
auto& context = *static_cast<HbPaintContext*> (data);
context.pushClipGlyph (glyph, font);
}, nullptr, nullptr);
hb_paint_funcs_set_push_clip_rectangle_func (funcs.get(), [] (auto*, void* data, auto xmin, auto ymin, auto xmax, auto ymax, auto*)
{
auto& context = *static_cast<HbPaintContext*> (data);
context.pushClipRect (Rectangle<float>::leftTopRightBottom (xmin, ymin, xmax, ymax));
}, nullptr, nullptr);
hb_paint_funcs_set_pop_clip_func (funcs.get(), [] (auto*, void* data, auto*)
{
auto& context = *static_cast<HbPaintContext*> (data);
context.popClip();
}, nullptr, nullptr);
hb_paint_funcs_set_color_func (funcs.get(), [] (auto*, void* data, auto foreground, auto colour, auto*)
{
auto& context = *static_cast<HbPaintContext*> (data);
context.fill (foreground, colour);
}, nullptr, nullptr);
hb_paint_funcs_set_image_func (funcs.get(), [] (auto*, void* data, auto* image, auto w, auto h, auto format, auto slant, auto* extents, auto*) -> hb_bool_t
{
auto& context = *static_cast<HbPaintContext*> (data);
return context.image (image, w, h, format, slant, extents);
}, nullptr, nullptr);
hb_paint_funcs_set_linear_gradient_func (funcs.get(), [] (auto*, auto* data, auto* colourLine, auto x0, auto y0, auto x1, auto y1, auto x2, auto y2, auto*)
{
auto& context = *static_cast<HbPaintContext*> (data);
context.linearGradient (*colourLine, { x0, y0 }, { x1, y1 }, { x2, y2 });
}, nullptr, nullptr);
hb_paint_funcs_set_radial_gradient_func (funcs.get(), [] (auto*, auto* data, auto* colourLine, auto x0, auto y0, auto r0, auto x1, auto y1, auto r1, auto*)
{
auto& context = *static_cast<HbPaintContext*> (data);
context.radialGradient (*colourLine, { x0, y0 }, r0, { x1, y1 }, r1);
}, nullptr, nullptr);
hb_paint_funcs_set_sweep_gradient_func (funcs.get(), [] (auto*, auto* data, auto* colourLine, auto x0, auto y0, auto begin, auto end, auto*)
{
auto& context = *static_cast<HbPaintContext*> (data);
context.sweepGradient (*colourLine, { x0, y0 }, begin, end);
}, nullptr, nullptr);
hb_paint_funcs_set_push_group_func (funcs.get(), [] (auto*, auto* data, auto*)
{
auto& context = *static_cast<HbPaintContext*> (data);
context.pushGroup();
}, nullptr, nullptr);
hb_paint_funcs_set_pop_group_func (funcs.get(), [] (auto*, auto* data, auto mode, auto*)
{
auto& context = *static_cast<HbPaintContext*> (data);
context.popGroup (mode);
}, nullptr, nullptr);
hb_paint_funcs_set_custom_palette_color_func (funcs.get(), [] (auto*, auto*, auto, auto*, auto*) -> hb_bool_t
{
return false;
}, nullptr, nullptr);
return funcs;
}
static std::vector<GlyphLayer> getCOLRv0Layers (const Typeface& typeface, int glyphNumber, const AffineTransform& transform)
{
auto* font = typeface.getNativeDetails().getFont();
@ -778,33 +474,60 @@ static std::vector<GlyphLayer> getCOLRv0Layers (const Typeface& typeface, int gl
return result;
}
std::vector<GlyphLayer> Typeface::getLayersForGlyph (int glyphNumber, const AffineTransform& transform, float) const
static std::vector<GlyphLayer> getBitmapLayer (const Typeface& typeface, int glyphNumber, const AffineTransform& t)
{
if ((typeface.getColourGlyphFormats() & Typeface::colourGlyphFormatBitmap) == 0)
return {};
auto* font = typeface.getNativeDetails().getFont();
const HbBlob blob { hb_ot_color_glyph_reference_png (font, (hb_codepoint_t) glyphNumber) };
unsigned int imageDataSize{};
const char* imageData = hb_blob_get_data (blob.get(), &imageDataSize);
const auto juceImage = PNGImageFormat::loadFrom (imageData, imageDataSize);
if (juceImage.isNull())
return {};
hb_glyph_extents_t extents{};
hb_font_get_glyph_extents (font, (hb_codepoint_t) glyphNumber, &extents);
const auto wDenom = std::max (1, juceImage.getWidth());
const auto hDenom = std::max (1, juceImage.getHeight());
const auto transform = AffineTransform::scale ((float) extents.width / (float) wDenom,
(float) extents.height / (float) hDenom)
.translated ((float) extents.x_bearing,
(float) extents.y_bearing)
.followedBy (t);
return { GlyphLayer { ImageLayer { juceImage, transform } } };
}
std::vector<GlyphLayer> Typeface::getLayersForGlyph (TypefaceMetricsKind kind, int glyphNumber, const AffineTransform& transform, float) const
{
auto* font = getNativeDetails().getFont();
const auto metrics = getNativeDetails().getLegacyMetrics();
const auto metrics = getNativeDetails().getMetrics (kind);
const auto scale = metrics.getHeightToPointsFactor() / (float) hb_face_get_upem (hb_font_get_face (font));
const auto combinedTransform = AffineTransform::scale (scale, -scale).followedBy (transform);
// Before calling through to the 'paint' API, which JUCE can't easily support due to complex
if (auto bitmapLayer = getBitmapLayer (*this, glyphNumber, combinedTransform); ! bitmapLayer.empty())
return bitmapLayer;
// Instead of calling through to the 'paint' API, which JUCE can't easily support due to complex
// gradients and blend modes, attempt to load COLRv0 layers for the glyph, which we'll be able
// to render more successfully.
auto basicLayers = getCOLRv0Layers (*this, glyphNumber, combinedTransform);
if (auto layers = getCOLRv0Layers (*this, glyphNumber, combinedTransform); ! layers.empty())
return layers;
if (! basicLayers.empty())
return basicLayers;
// No bitmap or COLRv0 for this glyph, so just get a simple monochromatic outline
auto path = getGlyphPathInGlyphUnits ((hb_codepoint_t) glyphNumber, font);
constexpr auto palette = 0;
if (path.isEmpty())
return {};
static const auto funcs = getPathPaintFuncs();
HbPaintContext context { combinedTransform };
hb_font_paint_glyph (font,
(hb_codepoint_t) glyphNumber,
funcs.get(),
&context,
palette,
{});
return std::move (context).getLayers();
path.applyTransform (combinedTransform);
return { GlyphLayer { ColourLayer { EdgeTable { path.getBounds().getSmallestIntegerContainer().expanded (1, 0), path, {} }, {} } } };
}
int Typeface::getColourGlyphFormats() const
@ -816,9 +539,10 @@ int Typeface::getColourGlyphFormats() const
| (hb_ot_color_has_paint (face) ? colourGlyphFormatCOLRv1 : 0);
}
float Typeface::getAscent() const { return getNativeDetails().getLegacyMetrics().getScaledAscent(); }
float Typeface::getDescent() const { return getNativeDetails().getLegacyMetrics().getScaledDescent(); }
float Typeface::getHeightToPointsFactor() const { return getNativeDetails().getLegacyMetrics().getHeightToPointsFactor(); }
TypefaceMetrics Typeface::getMetrics (TypefaceMetricsKind kind) const
{
return getNativeDetails().getMetrics (kind).getTypefaceMetrics();
}
Typeface::Ptr Typeface::createSystemTypefaceFor (const void* fontFileData, size_t fontFileDataSize)
{
@ -847,7 +571,8 @@ static constexpr auto hbTag (const char (&arr)[5])
}
template <typename Consumer>
static void doSimpleShape (const Typeface& typeface,
static float doSimpleShapeWithNoBreaks (const Typeface& typeface,
TypefaceMetricsKind kind,
const String& text,
float height,
float horizontalScale,
@ -859,7 +584,7 @@ static void doSimpleShape (const Typeface& typeface,
hb_buffer_guess_segment_properties (buffer.get());
const auto& native = typeface.getNativeDetails();
const auto sized = native.getFontAtSizeAndScale (height, horizontalScale);
const auto sized = native.getFontAtSizeAndScale (kind, height, horizontalScale);
auto* font = sized.get();
// Disable ligatures, because TextEditor requires a 1:1 codepoint/glyph mapping for caret
@ -887,32 +612,66 @@ static void doSimpleShape (const Typeface& typeface,
{
const auto& info = infos[i];
const auto& position = positions[i];
consumer (std::make_optional (info.codepoint), HbScale::hbToJuce (cursor.x + position.x_offset));
consumer (info.codepoint, HbScale::hbToJuce (cursor.x + position.x_offset));
cursor += Point { position.x_advance, position.y_advance };
}
consumer (std::optional<hb_codepoint_t>{}, HbScale::hbToJuce (cursor.x));
return HbScale::hbToJuce (cursor.x);
}
float Typeface::getStringWidth (const String& text, float height, float horizontalScale)
template <typename Consumer>
static float doSimpleShape (const Typeface& typeface,
TypefaceMetricsKind kind,
const String& originalText,
float height,
float horizontalScale,
Consumer&& consumer)
{
float x{};
doSimpleShape (*this, text, height, horizontalScale, [&] (auto, auto xOffset)
const juce_wchar zeroWidthSpace = 0x200b;
const auto text = originalText.replaceCharacter ('\n', zeroWidthSpace);
float lastX{};
for (auto iter = text.begin(), end = text.end(); iter != end;)
{
x = xOffset;
const auto next = [&]
{
for (auto i = iter; i != end; ++i)
if (*i == zeroWidthSpace)
return i + 1;
return end;
}();
lastX += doSimpleShapeWithNoBreaks (typeface, kind, String (iter, next), height, horizontalScale, [&] (auto codepoint, auto x)
{
consumer (codepoint, lastX + x);
});
return x;
iter = next;
}
return lastX;
}
void Typeface::getGlyphPositions (const String& text, Array<int>& glyphs, Array<float>& xOffsets, float height, float horizontalScale)
float Typeface::getStringWidth (TypefaceMetricsKind kind, const String& text, float height, float horizontalScale)
{
doSimpleShape (*this, text, height, horizontalScale, [&] (auto codepoint, auto xOffset)
{
if (codepoint.has_value())
glyphs.add ((int) *codepoint);
return doSimpleShape (*this, kind, text, height, horizontalScale, [&] (auto, auto) {});
}
void Typeface::getGlyphPositions (TypefaceMetricsKind kind,
const String& text,
Array<int>& glyphs,
Array<float>& xOffsets,
float height,
float horizontalScale)
{
const auto width = doSimpleShape (*this, kind, text, height, horizontalScale, [&] (auto codepoint, auto xOffset)
{
glyphs.add ((int) codepoint);
xOffsets.add (xOffset);
});
xOffsets.add (width);
}
//==============================================================================
@ -957,7 +716,7 @@ public:
beginTest ("Typefaces loaded from memory are found when creating font instances by name");
{
Font font (FontOptions (ptr->getName(), ptr->getStyle(), 12.0f));
Font font (FontOptions { ptr->getName(), ptr->getStyle(), 12.0f });
expect (font.getTypefacePtr() != nullptr);
expect (font.getTypefacePtr()->getName() == ptr->getName());

View file

@ -59,6 +59,50 @@ struct GlyphLayer
std::variant<ColourLayer, ImageLayer> layer;
};
/** Identifiers for different styles of typeface metrics.
In new projects, it's recommended to use the 'portable' metrics kind, so that fonts display
at a similar size on all platforms.
Portable metrics are enabled by default when constructing a Font using FontOptions. The old,
deprecated Font constructors will all request the legacy metrics kind instead.
JUCE components that display text will query LookAndFeel::getDefaultMetricsKind() to find the
kind of metrics that should be used. This function can be overridden to switch the metrics kind
for the entire LookAndFeel.
The 'legacy' metrics kind uses the platform font facilities to retrieve font metrics.
Each platform has its own idiosyncratic behaviour when computing metrics - depending on the
typeface data, it's possible that the 'portable' metrics will differ from the 'legacy' metrics
on every platform. The biggest differences between legacy and portable metrics are likely to be
seen on Windows, so it may be helpful to check this platform first.
*/
enum class TypefaceMetricsKind
{
legacy, ///< Old-style metrics that may differ for the same font file when running on different platforms.
///< This was the default behaviour prior to JUCE 8.
portable ///< Where possible, will return the same font metrics on all platforms.
///< It's a good idea to use this for new JUCE projects, to keep text layout and
///< sizing consistent on all platforms.
};
/** Font metrics using JUCE conventions.
*/
struct TypefaceMetrics
{
/** The proportion of the typeface's height that it above the baseline, as a value between 0 and 1.
Note that 'height' here refers to the result of adding the absolute ascent and descent values.
That is, the sum of the ascent and descent will equal 1.
The sum of the ascent and descent will normally differ from the em size of the font.
That is, for a font size of 14pt, there will be 14 points per em, but the sum of the ascent
and descent in points is unlikely to be equal to 14.
*/
float ascent{};
/** The factor by which a JUCE font height should be multiplied in order to convert to a font
size in points.
*/
float heightToPoints{};
};
//==============================================================================
/**
A typeface represents a size-independent font.
@ -121,24 +165,8 @@ public:
/** Destructor. */
~Typeface() override;
/** Returns the ascent of the font, as a proportion of its height.
The height is considered to always be normalised as 1.0, so this will be a
value less that 1.0, indicating the proportion of the font that lies above
its baseline.
*/
float getAscent() const;
/** Returns the descent of the font, as a proportion of its height.
The height is considered to always be normalised as 1.0, so this will be a
value less that 1.0, indicating the proportion of the font that lies below
its baseline.
*/
float getDescent() const;
/** Returns the value by which you should multiply a JUCE font-height value to
convert it to the equivalent point-size.
*/
float getHeightToPointsFactor() const;
/** Returns information about the horizontal metrics of this font. */
[[nodiscard]] TypefaceMetrics getMetrics (TypefaceMetricsKind) const;
/** @deprecated
This function has several shortcomings:
@ -154,7 +182,10 @@ public:
Measures the width of a line of text.
You should never need to call this!
*/
float getStringWidth (const String& text, float normalisedHeight = 1.0f, float horizontalScale = 1.0f);
float getStringWidth (TypefaceMetricsKind,
const String& text,
float normalisedHeight = 1.0f,
float horizontalScale = 1.0f);
/** @deprecated
This function has several shortcomings:
@ -171,7 +202,8 @@ public:
Converts a line of text into its glyph numbers and their positions.
You should never need to call this!
*/
void getGlyphPositions (const String& text,
void getGlyphPositions (TypefaceMetricsKind,
const String& text,
Array<int>& glyphs,
Array<float>& xOffsets,
float normalisedHeight = 1.0f,
@ -180,7 +212,7 @@ public:
/** Returns the outline for a glyph.
The path returned will be normalised to a font height of 1.0.
*/
void getOutlineForGlyph (int glyphNumber, Path& path);
void getOutlineForGlyph (TypefaceMetricsKind, int glyphNumber, Path& path) const;
/** @deprecated
@ -196,7 +228,10 @@ public:
preferred in new code.
*/
[[deprecated ("Prefer getLayersForGlyph")]]
EdgeTable* getEdgeTableForGlyph (int glyphNumber, const AffineTransform& transform, float normalisedHeight);
EdgeTable* getEdgeTableForGlyph (TypefaceMetricsKind,
int glyphNumber,
const AffineTransform& transform,
float normalisedHeight);
/** Returns the layers that should be painted in order to display this glyph.
@ -210,7 +245,10 @@ public:
The height is specified in JUCE font-height units.
*/
std::vector<GlyphLayer> getLayersForGlyph (int glyphNumber, const AffineTransform&, float normalisedHeight) const;
std::vector<GlyphLayer> getLayersForGlyph (TypefaceMetricsKind,
int glyphNumber,
const AffineTransform&,
float normalisedHeight) const;
/** Kinds of colour glyph format that may be implemented by a particular typeface.
Most typefaces are monochromatic, and do not support any colour formats.

View file

@ -696,7 +696,7 @@ void CoreGraphicsContext::drawGlyph (int glyphNumber, const AffineTransform& tra
{
Path p;
auto& f = state->font;
f.getTypefacePtr()->getOutlineForGlyph (glyphNumber, p);
f.getTypefacePtr()->getOutlineForGlyph (f.getMetricsKind(), glyphNumber, p);
const auto scale = f.getHeight();
fillPath (p, AffineTransform::scale (scale * f.getHorizontalScale(), scale).followedBy (transform));

View file

@ -616,7 +616,7 @@ public:
Native getNativeDetails() const override
{
return Native { hb.get() };
return Native { hb.get(), nonPortableMetrics };
}
Typeface::Ptr createSystemFallback (const String& c, const String& language) const override
@ -698,6 +698,13 @@ private:
CFUniquePtr<CTFontRef> ctFont;
HbFont hb;
MemoryBlock storage;
TypefaceAscentDescent nonPortableMetrics = [&]
{
const CFUniquePtr<CGFontRef> cgFont { CTFontCopyGraphicsFont (ctFont.get(), nullptr) };
const auto upem = (float) CGFontGetUnitsPerEm (cgFont.get());
return TypefaceAscentDescent { (float) std::abs (CGFontGetAscent (cgFont.get()) / upem),
(float) std::abs (CGFontGetDescent (cgFont.get()) / upem) };
}();
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CoreTextTypeface)
};

View file

@ -189,7 +189,8 @@ public:
{
auto fontHeight = key.font.getHeight();
auto typeface = key.font.getTypefacePtr();
return typeface->getLayersForGlyph (key.glyph,
return typeface->getLayersForGlyph (key.font.getMetricsKind(),
key.glyph,
AffineTransform::scale (fontHeight * key.font.getHorizontalScale(),
fontHeight),
fontHeight);
@ -2629,7 +2630,7 @@ public:
const auto fontTransform = AffineTransform::scale (fontHeight * stack->font.getHorizontalScale(),
fontHeight).followedBy (t);
const auto fullTransform = stack->transform.getTransformWith (fontTransform);
return std::tuple (stack->font.getTypefacePtr()->getLayersForGlyph (i, fullTransform, fontHeight), Point<float>{});
return std::tuple (stack->font.getTypefacePtr()->getLayersForGlyph (stack->font.getMetricsKind(), i, fullTransform, fontHeight), Point<float>{});
}();
const auto initialFill = stack->fillType;