mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
Typeface: Add support for rendering COLRv0 glyphs and png-based glyphs
This is sufficient for initial support of the system emoji fonts on each
platform:
- Noto Color Emoji on Linux and Android, png-based
- Apple Color Emoji on macOS and iOS, png-based
- Segoe UI Emoji on Windows 10 and 11, COLRv0-based
- This font also provides COLRv1 support, at least on Windows 11,
but JUCE will ignore that and use the COLRv0 data instead
This commit is contained in:
parent
0da52a8e7b
commit
4e90ef831b
4 changed files with 506 additions and 47 deletions
|
|
@ -246,28 +246,25 @@ static HbDrawFuncs getPathDrawFuncs()
|
|||
return funcs;
|
||||
}
|
||||
|
||||
static Path getTypefaceGlyph (const Typeface& typeface, int glyphNumber)
|
||||
[[nodiscard]] static Path getGlyphPathInGlyphUnits (hb_codepoint_t glyph, hb_font_t* font)
|
||||
{
|
||||
static const auto funcs = getPathDrawFuncs();
|
||||
|
||||
auto* font = typeface.getNativeDetails().getFont();
|
||||
|
||||
Path result;
|
||||
hb_font_draw_glyph (font, (hb_codepoint_t) glyphNumber, funcs.get(), &result);
|
||||
|
||||
// Convert to em units
|
||||
result.applyTransform (AffineTransform::scale (1.0f / (float) hb_face_get_upem (hb_font_get_face (font))).scaled (1.0f, -1.0f));
|
||||
|
||||
hb_font_draw_glyph (font, glyph, funcs.get(), &result);
|
||||
return result;
|
||||
}
|
||||
|
||||
void Typeface::getOutlineForGlyph (int glyphNumber, Path& path)
|
||||
{
|
||||
const auto native = getNativeDetails();
|
||||
auto* font = native.getFont();
|
||||
const auto metrics = getNativeDetails().getLegacyMetrics();
|
||||
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
|
||||
path = getTypefaceGlyph (*this, glyphNumber);
|
||||
path.applyTransform (AffineTransform::scale (metrics.getHeightToPointsFactor()));
|
||||
path = getGlyphPathInGlyphUnits ((hb_codepoint_t) glyphNumber, getNativeDetails().getFont());
|
||||
path.applyTransform (AffineTransform::scale (scale, -scale));
|
||||
}
|
||||
|
||||
void Typeface::applyVerticalHintingTransform (float, Path&)
|
||||
|
|
@ -279,12 +276,402 @@ EdgeTable* Typeface::getEdgeTableForGlyph (int glyphNumber, const AffineTransfor
|
|||
{
|
||||
Path path;
|
||||
getOutlineForGlyph (glyphNumber, path);
|
||||
path.applyTransform (transform);
|
||||
|
||||
if (path.isEmpty())
|
||||
return nullptr;
|
||||
return new EdgeTable (path.getBounds().getSmallestIntegerContainer().expanded (1, 0), std::move (path), {});
|
||||
}
|
||||
|
||||
return new EdgeTable (path.getBoundsTransformed (transform).getSmallestIntegerContainer().expanded (1, 0),
|
||||
path, transform);
|
||||
static Colour makeColour (hb_color_t c)
|
||||
{
|
||||
return PixelARGB (hb_color_get_alpha (c),
|
||||
hb_color_get_red (c),
|
||||
hb_color_get_green (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();
|
||||
auto* face = hb_font_get_face (font);
|
||||
constexpr auto palette = 0;
|
||||
|
||||
auto numLayers = hb_ot_color_glyph_get_layers (face, (hb_codepoint_t) glyphNumber, 0, nullptr, nullptr);
|
||||
std::vector<hb_ot_color_layer_t> layers (numLayers);
|
||||
hb_ot_color_glyph_get_layers (face, (hb_codepoint_t) glyphNumber, 0, &numLayers, layers.data());
|
||||
|
||||
if (layers.empty())
|
||||
return {};
|
||||
|
||||
std::vector<GlyphLayer> result;
|
||||
|
||||
for (const auto& layer : layers)
|
||||
{
|
||||
const auto hbFillColour = layer.color_index == 0xffff ? std::optional<hb_color_t>() : [&]
|
||||
{
|
||||
hb_color_t colour{};
|
||||
unsigned int numColours = 1;
|
||||
hb_ot_color_palette_get_colors (face, palette, layer.color_index, &numColours, &colour);
|
||||
return colour;
|
||||
}();
|
||||
|
||||
const auto juceFillColour = hbFillColour.has_value() ? makeColour (*hbFillColour) : std::optional<Colour>();
|
||||
|
||||
auto path = getGlyphPathInGlyphUnits (layer.glyph, font);
|
||||
path.applyTransform (transform);
|
||||
result.push_back ({ ColourLayer
|
||||
{
|
||||
EdgeTable { path.getBounds().getSmallestIntegerContainer().expanded (1, 0), path, {} },
|
||||
juceFillColour
|
||||
} });
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<GlyphLayer> Typeface::getLayersForGlyph (int glyphNumber, const AffineTransform& transform, float) const
|
||||
{
|
||||
auto* font = getNativeDetails().getFont();
|
||||
const auto metrics = getNativeDetails().getLegacyMetrics();
|
||||
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
|
||||
// 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 (! basicLayers.empty())
|
||||
return basicLayers;
|
||||
|
||||
constexpr auto palette = 0;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
float Typeface::getAscent() const { return getNativeDetails().getLegacyMetrics().getScaledAscent(); }
|
||||
|
|
|
|||
|
|
@ -35,6 +35,30 @@
|
|||
namespace juce
|
||||
{
|
||||
|
||||
/** A single path-based layer of a colour glyph. Contains the glyph shape and the colour in which
|
||||
the shape should be painted.
|
||||
*/
|
||||
struct ColourLayer
|
||||
{
|
||||
EdgeTable clip;
|
||||
std::optional<Colour> colour; ///< nullopt indicates 'foreground'
|
||||
};
|
||||
|
||||
/** A bitmap representing (part of) a glyph, most commonly used to represent colour emoji glyphs.
|
||||
*/
|
||||
struct ImageLayer
|
||||
{
|
||||
Image image;
|
||||
AffineTransform transform;
|
||||
};
|
||||
|
||||
/** A single layer that makes up part of a glyph image.
|
||||
*/
|
||||
struct GlyphLayer
|
||||
{
|
||||
std::variant<ColourLayer, ImageLayer> layer;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A typeface represents a size-independent font.
|
||||
|
|
@ -166,8 +190,35 @@ public:
|
|||
*/
|
||||
void getOutlineForGlyph (int glyphNumber, Path& path);
|
||||
|
||||
/** Returns a new EdgeTable that contains the path for the given glyph, with the specified transform applied. */
|
||||
EdgeTable* getEdgeTableForGlyph (int glyphNumber, const AffineTransform& transform, float fontHeight);
|
||||
/** @deprecated
|
||||
|
||||
Returns a new EdgeTable that contains the path for the given glyph, with the specified transform applied.
|
||||
|
||||
This is only capable of returning monochromatic glyphs. In fonts that contain multiple glyph
|
||||
styles with fallbacks (COLRv1, COLRv0, monochromatic), this will always return the
|
||||
monochromatic variant.
|
||||
|
||||
The height is specified in JUCE font-height units.
|
||||
|
||||
getLayersForGlyph() has better support for multilayer and bitmap glyphs, so it should be
|
||||
preferred in new code.
|
||||
*/
|
||||
[[deprecated ("Prefer getLayersForGlyph")]]
|
||||
EdgeTable* getEdgeTableForGlyph (int glyphNumber, const AffineTransform& transform, float normalisedHeight);
|
||||
|
||||
/** Returns the layers that should be painted in order to display this glyph.
|
||||
|
||||
Layers should be painted in the same order as they are returned, i.e. layer[0], layer[1] etc.
|
||||
|
||||
This should generally be preferred to getEdgeTableForGlyph, as it is more flexible.
|
||||
Currently, this only supports COLRv0 and bitmap fonts (no SVG or COLRv1).
|
||||
Support for SVG and COLRv1 may be added in the future, depending on demand. However, this
|
||||
would require significant additions to JUCE's rendering code, so it has been omitted for
|
||||
now.
|
||||
|
||||
The height is specified in JUCE font-height units.
|
||||
*/
|
||||
std::vector<GlyphLayer> getLayersForGlyph (int glyphNumber, const AffineTransform&, float normalisedHeight) const;
|
||||
|
||||
//==============================================================================
|
||||
/** Changes the number of fonts that are cached in memory. */
|
||||
|
|
|
|||
|
|
@ -140,16 +140,16 @@ namespace juce
|
|||
#include "images/juce_ImageCache.h"
|
||||
#include "images/juce_ImageConvolutionKernel.h"
|
||||
#include "images/juce_ImageFileFormat.h"
|
||||
#include "contexts/juce_GraphicsContext.h"
|
||||
#include "images/juce_Image.h"
|
||||
#include "colour/juce_FillType.h"
|
||||
#include "fonts/juce_Typeface.h"
|
||||
#include "fonts/juce_Font.h"
|
||||
#include "fonts/juce_AttributedString.h"
|
||||
#include "fonts/juce_GlyphArrangement.h"
|
||||
#include "fonts/juce_TextLayout.h"
|
||||
#include "contexts/juce_GraphicsContext.h"
|
||||
#include "contexts/juce_LowLevelGraphicsContext.h"
|
||||
#include "images/juce_Image.h"
|
||||
#include "images/juce_ScaledImage.h"
|
||||
#include "colour/juce_FillType.h"
|
||||
#include "fonts/juce_LruCache.h"
|
||||
#include "native/juce_RenderingHelpers.h"
|
||||
#include "contexts/juce_LowLevelGraphicsSoftwareRenderer.h"
|
||||
|
|
|
|||
|
|
@ -182,22 +182,18 @@ public:
|
|||
cache = {};
|
||||
}
|
||||
|
||||
template <class RenderTargetType>
|
||||
void drawGlyph (RenderTargetType& target, const Font& font, const int glyphNumber, Point<float> pos)
|
||||
const auto& get (const Font& font, const int glyphNumber)
|
||||
{
|
||||
const ScopedLock sl { lock };
|
||||
const auto& table = cache.get (Key { font, glyphNumber }, [] (const auto& key)
|
||||
return cache.get (Key { font, glyphNumber }, [] (const auto& key)
|
||||
{
|
||||
auto fontHeight = key.font.getHeight();
|
||||
auto typeface = key.font.getTypefacePtr();
|
||||
return rawToUniquePtr (typeface->getEdgeTableForGlyph (key.glyph,
|
||||
AffineTransform::scale (fontHeight * key.font.getHorizontalScale(),
|
||||
fontHeight),
|
||||
fontHeight));
|
||||
return typeface->getLayersForGlyph (key.glyph,
|
||||
AffineTransform::scale (fontHeight * key.font.getHorizontalScale(),
|
||||
fontHeight),
|
||||
fontHeight);
|
||||
});
|
||||
|
||||
if (table != nullptr)
|
||||
target.fillEdgeTable (*table, pos.x, roundToInt (pos.y));
|
||||
}
|
||||
|
||||
private:
|
||||
|
|
@ -218,7 +214,7 @@ private:
|
|||
}
|
||||
};
|
||||
|
||||
LruCache<Key, std::unique_ptr<EdgeTable>> cache;
|
||||
LruCache<Key, std::vector<GlyphLayer>> cache;
|
||||
CriticalSection lock;
|
||||
|
||||
static GlyphCache*& getSingletonPointer() noexcept
|
||||
|
|
@ -2599,22 +2595,24 @@ public:
|
|||
void setFont (const Font& newFont) override { stack->font = newFont; }
|
||||
const Font& getFont() override { return stack->font; }
|
||||
|
||||
void drawGlyph (int glyphNumber, const AffineTransform& trans) override
|
||||
void drawGlyph (int i, const AffineTransform& t) override
|
||||
{
|
||||
if (stack->clip == nullptr)
|
||||
return;
|
||||
|
||||
if (trans.isOnlyTranslation() && ! stack->transform.isRotated)
|
||||
const auto [layers, drawPosition] = [&]
|
||||
{
|
||||
auto& cache = RenderingHelpers::GlyphCache::getInstance();
|
||||
const Point pos (trans.getTranslationX(), trans.getTranslationY());
|
||||
if (t.isOnlyTranslation() && ! stack->transform.isRotated)
|
||||
{
|
||||
auto& cache = RenderingHelpers::GlyphCache::getInstance();
|
||||
const Point pos (t.getTranslationX(), t.getTranslationY());
|
||||
|
||||
if (stack->transform.isOnlyTranslated)
|
||||
{
|
||||
const auto drawPos = pos + stack->transform.offset.toFloat();
|
||||
return std::tuple (cache.get (stack->font, i), drawPos);
|
||||
}
|
||||
|
||||
if (stack->transform.isOnlyTranslated)
|
||||
{
|
||||
cache.drawGlyph (*stack, stack->font, glyphNumber, pos + stack->transform.offset.toFloat());
|
||||
}
|
||||
else
|
||||
{
|
||||
auto f = stack->font;
|
||||
f.setHeight (f.getHeight() * stack->transform.complexTransform.mat11);
|
||||
|
||||
|
|
@ -2623,18 +2621,41 @@ public:
|
|||
if (std::abs (xScale - 1.0f) > 0.01f)
|
||||
f.setHorizontalScale (xScale);
|
||||
|
||||
cache.drawGlyph (*stack, f, glyphNumber, stack->transform.transformed (pos));
|
||||
const auto drawPos = stack->transform.transformed (pos);
|
||||
return std::tuple (cache.get (f, i), drawPos);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
const auto fontHeight = stack->font.getHeight();
|
||||
const auto fontTransform = AffineTransform::scale (fontHeight * stack->font.getHorizontalScale(),
|
||||
fontHeight).followedBy (trans);
|
||||
fontHeight).followedBy (t);
|
||||
const auto fullTransform = stack->transform.getTransformWith (fontTransform);
|
||||
return std::tuple (stack->font.getTypefacePtr()->getLayersForGlyph (i, fullTransform, fontHeight), Point<float>{});
|
||||
}();
|
||||
|
||||
if (auto et = rawToUniquePtr (stack->font.getTypefacePtr()->getEdgeTableForGlyph (glyphNumber, fullTransform, fontHeight)))
|
||||
stack->fillShape (*new ClipRegions::EdgeTableRegion<SavedStateType> (*et), false);
|
||||
const auto initialFill = stack->fillType;
|
||||
const ScopeGuard scope { [&] { stack->setFillType (initialFill); } };
|
||||
|
||||
for (const auto& layer : layers)
|
||||
{
|
||||
if (auto* colourLayer = std::get_if<ColourLayer> (&layer.layer))
|
||||
{
|
||||
if (auto fill = colourLayer->colour)
|
||||
stack->setFillType (*fill);
|
||||
|
||||
stack->fillEdgeTable (colourLayer->clip, drawPosition.x, (int) drawPosition.y);
|
||||
}
|
||||
else if (auto* imageLayer = std::get_if<ImageLayer> (&layer.layer))
|
||||
{
|
||||
// The position arguments to fillEdgeTable are in physical screen-space,
|
||||
// and do not take the current context transform into account.
|
||||
// However, drawImage *does* apply the context transform internally.
|
||||
// We apply the inverse context transform here so that after the
|
||||
// real context transform is applied, the image will be painted at the
|
||||
// physical position specified by drawPosition.
|
||||
const auto imageTransform = imageLayer->transform.translated (drawPosition)
|
||||
.followedBy (stack->transform.getTransform().inverted());
|
||||
stack->drawImage (imageLayer->image, imageTransform);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue