mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
Improved the vertical font hinting algorithm and removed some duplicated typeface code.
This commit is contained in:
parent
45b49f84d1
commit
42bff200ed
10 changed files with 89 additions and 123 deletions
|
|
@ -385,7 +385,7 @@ bool CustomTypeface::getOutlineForGlyph (int glyphNumber, Path& path)
|
|||
return false;
|
||||
}
|
||||
|
||||
EdgeTable* CustomTypeface::getEdgeTableForGlyph (int glyphNumber, const AffineTransform& transform)
|
||||
EdgeTable* CustomTypeface::getEdgeTableForGlyph (int glyphNumber, const AffineTransform& transform, float fontHeight)
|
||||
{
|
||||
if (const GlyphInfo* const glyph = findGlyph ((juce_wchar) glyphNumber, true))
|
||||
{
|
||||
|
|
@ -399,7 +399,7 @@ EdgeTable* CustomTypeface::getEdgeTableForGlyph (int glyphNumber, const AffineTr
|
|||
const Typeface::Ptr fallbackTypeface (getFallbackTypeface());
|
||||
|
||||
if (fallbackTypeface != nullptr && fallbackTypeface != this)
|
||||
return fallbackTypeface->getEdgeTableForGlyph (glyphNumber, transform);
|
||||
return fallbackTypeface->getEdgeTableForGlyph (glyphNumber, transform, fontHeight);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ public:
|
|||
float getStringWidth (const String&) override;
|
||||
void getGlyphPositions (const String&, Array <int>& glyphs, Array<float>& xOffsets) override;
|
||||
bool getOutlineForGlyph (int glyphNumber, Path&) override;
|
||||
EdgeTable* getEdgeTableForGlyph (int glyphNumber, const AffineTransform&) override;
|
||||
EdgeTable* getEdgeTableForGlyph (int glyphNumber, const AffineTransform&, float fontHeight) override;
|
||||
|
||||
protected:
|
||||
//==============================================================================
|
||||
|
|
|
|||
|
|
@ -116,13 +116,17 @@ Typeface::Ptr Typeface::getFallbackTypeface()
|
|||
return fallbackFont.getTypeface();
|
||||
}
|
||||
|
||||
EdgeTable* Typeface::getEdgeTableForGlyph (int glyphNumber, const AffineTransform& transform)
|
||||
EdgeTable* Typeface::getEdgeTableForGlyph (int glyphNumber, const AffineTransform& transform, float fontHeight)
|
||||
{
|
||||
Path path;
|
||||
|
||||
if (getOutlineForGlyph (glyphNumber, path) && ! path.isEmpty())
|
||||
{
|
||||
applyVerticalHintingTransform (fontHeight, path);
|
||||
|
||||
return new EdgeTable (path.getBoundsTransformed (transform).getSmallestIntegerContainer().expanded (1, 0),
|
||||
path, transform);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
|
@ -131,84 +135,78 @@ EdgeTable* Typeface::getEdgeTableForGlyph (int glyphNumber, const AffineTransfor
|
|||
struct Typeface::HintingParams
|
||||
{
|
||||
HintingParams (Typeface& t)
|
||||
: top (0), middle (0), bottom (0)
|
||||
: cachedSize (0), top (0), middle (0), bottom (0)
|
||||
{
|
||||
Font font (&t);
|
||||
font = font.withHeight ((float) standardHeight);
|
||||
|
||||
top = getAverageY (font, "BDEFPRTZOQC", true);
|
||||
top = getAverageY (font, "BDEFPRTZOQ", true);
|
||||
middle = getAverageY (font, "acegmnopqrsuvwxy", true);
|
||||
bottom = getAverageY (font, "BDELZOC", false);
|
||||
}
|
||||
|
||||
AffineTransform getVerticalHintingTransform (float fontSize) noexcept
|
||||
void applyVerticalHintingTransform (float fontSize, Path& path)
|
||||
{
|
||||
if (cachedSize == fontSize)
|
||||
return cachedTransform;
|
||||
if (cachedSize != fontSize)
|
||||
{
|
||||
cachedSize = fontSize;
|
||||
cachedScale = Scaling (top, middle, bottom, fontSize);
|
||||
}
|
||||
|
||||
const float t = fontSize * top;
|
||||
const float m = fontSize * middle;
|
||||
const float b = fontSize * bottom;
|
||||
if (bottom < top + 3.0f / fontSize)
|
||||
return;
|
||||
|
||||
if (b < t + 2.0f)
|
||||
return AffineTransform();
|
||||
Path result;
|
||||
|
||||
Scaling s[] = { Scaling (t, m, b, 0.0f, 0.0f),
|
||||
Scaling (t, m, b, 1.0f, 0.0f),
|
||||
Scaling (t, m, b, 0.0f, 1.0f),
|
||||
Scaling (t, m, b, 1.0f, 1.0f) };
|
||||
for (Path::Iterator i (path); i.next();)
|
||||
{
|
||||
switch (i.elementType)
|
||||
{
|
||||
case Path::Iterator::startNewSubPath: result.startNewSubPath (i.x1, cachedScale.apply (i.y1)); break;
|
||||
case Path::Iterator::lineTo: result.lineTo (i.x1, cachedScale.apply (i.y1)); break;
|
||||
case Path::Iterator::quadraticTo: result.quadraticTo (i.x1, cachedScale.apply (i.y1),
|
||||
i.x2, cachedScale.apply (i.y2)); break;
|
||||
case Path::Iterator::cubicTo: result.cubicTo (i.x1, cachedScale.apply (i.y1),
|
||||
i.x2, cachedScale.apply (i.y2),
|
||||
i.x3, cachedScale.apply (i.y3)); break;
|
||||
case Path::Iterator::closePath: result.closeSubPath(); break;
|
||||
default: jassertfalse; break;
|
||||
}
|
||||
}
|
||||
|
||||
int best = 0;
|
||||
|
||||
for (int i = 1; i < numElementsInArray (s); ++i)
|
||||
if (s[i].drift < s[best].drift)
|
||||
best = i;
|
||||
|
||||
cachedSize = fontSize;
|
||||
|
||||
AffineTransform result (s[best].getTransform());
|
||||
cachedTransform = result;
|
||||
return result;
|
||||
result.swapWithPath (path);
|
||||
}
|
||||
|
||||
private:
|
||||
float cachedSize;
|
||||
AffineTransform cachedTransform;
|
||||
|
||||
struct Scaling
|
||||
{
|
||||
Scaling (float t, float m, float b, float direction1, float direction2) noexcept
|
||||
Scaling() noexcept : middle(), upperScale(), upperOffset(), lowerScale(), lowerOffset() {}
|
||||
|
||||
Scaling (float t, float m, float b, float fontSize) noexcept : middle (m)
|
||||
{
|
||||
float newT = std::floor (t) + direction1;
|
||||
float newB = std::floor (b) + direction2;
|
||||
float newM = newT + (newB - newT) * (m - t) / (b - t);
|
||||
const float newT = std::floor (fontSize * t + 0.5f) / fontSize;
|
||||
const float newB = std::floor (fontSize * b + 0.5f) / fontSize;
|
||||
const float newM = std::floor (fontSize * m + 0.5f) / fontSize;
|
||||
|
||||
float middleOffset = newM - std::floor (newM);
|
||||
float nudge = middleOffset < 0.5f ? (middleOffset * -0.2f) : ((1.0f - middleOffset) * 0.2f);
|
||||
newT += nudge;
|
||||
newB += nudge;
|
||||
upperScale = jlimit (0.9f, 1.1f, (newM - newT) / (m - t));
|
||||
lowerScale = jlimit (0.9f, 1.1f, (newB - newM) / (b - m));
|
||||
|
||||
scale = (newB - newT) / (b - t);
|
||||
offset = (newB / scale) - b;
|
||||
|
||||
drift = getDrift (t) + getDrift (m) + getDrift (b) + offset + 20.0f * std::abs (scale - 1.0f);
|
||||
upperOffset = newM - m * upperScale;
|
||||
lowerOffset = newB - b * lowerScale;
|
||||
}
|
||||
|
||||
AffineTransform getTransform() const noexcept
|
||||
float apply (float y) const noexcept
|
||||
{
|
||||
return AffineTransform::translation (0, offset).scaled (1.0f, scale);
|
||||
return y < middle ? (y * upperScale + upperOffset)
|
||||
: (y * lowerScale + lowerOffset);
|
||||
}
|
||||
|
||||
float getDrift (float n) const noexcept
|
||||
{
|
||||
n = (n + offset) * scale;
|
||||
const float diff = n - std::floor (n);
|
||||
return jmin (diff, 1.0f - diff);
|
||||
}
|
||||
|
||||
float offset, scale, drift;
|
||||
float middle, upperScale, upperOffset, lowerScale, lowerOffset;
|
||||
};
|
||||
|
||||
float cachedSize;
|
||||
Scaling cachedScale;
|
||||
|
||||
static float getAverageY (const Font& font, const char* chars, bool getTop)
|
||||
{
|
||||
GlyphArrangement ga;
|
||||
|
|
@ -248,15 +246,15 @@ private:
|
|||
float top, middle, bottom;
|
||||
};
|
||||
|
||||
AffineTransform Typeface::getVerticalHintingTransform (float fontSize)
|
||||
void Typeface::applyVerticalHintingTransform (float fontSize, Path& path)
|
||||
{
|
||||
if (fontSize < 3.0f || fontSize > 25.0f)
|
||||
return AffineTransform();
|
||||
if (fontSize > 3.0f && fontSize < 25.0f)
|
||||
{
|
||||
ScopedLock sl (hintingLock);
|
||||
|
||||
ScopedLock sl (hintingLock);
|
||||
if (hintingParams == nullptr)
|
||||
hintingParams = new HintingParams (*this);
|
||||
|
||||
if (hintingParams == nullptr)
|
||||
hintingParams = new HintingParams (*this);
|
||||
|
||||
return hintingParams->getVerticalHintingTransform (fontSize);
|
||||
return hintingParams->applyVerticalHintingTransform (fontSize, path);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ public:
|
|||
virtual bool getOutlineForGlyph (int glyphNumber, Path& path) = 0;
|
||||
|
||||
/** Returns a new EdgeTable that contains the path for the givem glyph, with the specified transform applied. */
|
||||
virtual EdgeTable* getEdgeTableForGlyph (int glyphNumber, const AffineTransform& transform);
|
||||
virtual EdgeTable* getEdgeTableForGlyph (int glyphNumber, const AffineTransform& transform, float fontHeight);
|
||||
|
||||
/** Returns true if the typeface uses hinting. */
|
||||
virtual bool isHinted() const { return false; }
|
||||
|
|
@ -134,10 +134,11 @@ public:
|
|||
*/
|
||||
static void scanFolderForFonts (const File& folder);
|
||||
|
||||
/** Makes an attempt at estimating a good overall transform that will scale a font of
|
||||
the given size to align vertically with the pixel grid.
|
||||
/** Makes an attempt at performing a good overall distortion that will scale a font of
|
||||
the given size to align vertically with the pixel grid. The path should be an unscaled
|
||||
(i.e. normalised to height of 1.0) path for a glyph.
|
||||
*/
|
||||
AffineTransform getVerticalHintingTransform (float fontHeight);
|
||||
void applyVerticalHintingTransform (float fontHeight, Path& path);
|
||||
|
||||
protected:
|
||||
//==============================================================================
|
||||
|
|
|
|||
|
|
@ -284,8 +284,8 @@ public:
|
|||
|
||||
const float fontHeight = font.getHeight();
|
||||
edgeTable = typeface->getEdgeTableForGlyph (glyphNumber,
|
||||
AffineTransform::scale (fontHeight * font.getHorizontalScale(), fontHeight)
|
||||
.followedBy (font.getTypeface()->getVerticalHintingTransform (fontHeight)));
|
||||
AffineTransform::scale (fontHeight * font.getHorizontalScale(),
|
||||
fontHeight), fontHeight);
|
||||
}
|
||||
|
||||
Font font;
|
||||
|
|
@ -2487,7 +2487,7 @@ public:
|
|||
AffineTransform t (transform.getTransformWith (AffineTransform::scale (fontHeight * font.getHorizontalScale(), fontHeight)
|
||||
.followedBy (trans)));
|
||||
|
||||
const ScopedPointer<EdgeTable> et (font.getTypeface()->getEdgeTableForGlyph (glyphNumber, t));
|
||||
const ScopedPointer<EdgeTable> et (font.getTypeface()->getEdgeTableForGlyph (glyphNumber, t, fontHeight));
|
||||
|
||||
if (et != nullptr)
|
||||
fillShape (new EdgeTableRegionType (*et), false);
|
||||
|
|
|
|||
|
|
@ -170,11 +170,11 @@ public:
|
|||
heightToPointsFactor = referenceFontSize / totalHeight;
|
||||
}
|
||||
|
||||
float getAscent() const { return ascent; }
|
||||
float getDescent() const { return descent; }
|
||||
float getHeightToPointsFactor() const { return heightToPointsFactor; }
|
||||
float getAscent() const override { return ascent; }
|
||||
float getDescent() const override { return descent; }
|
||||
float getHeightToPointsFactor() const override { return heightToPointsFactor; }
|
||||
|
||||
float getStringWidth (const String& text)
|
||||
float getStringWidth (const String& text) override
|
||||
{
|
||||
JNIEnv* env = getEnv();
|
||||
const int numChars = text.length();
|
||||
|
|
@ -193,7 +193,7 @@ public:
|
|||
return x * referenceFontToUnits;
|
||||
}
|
||||
|
||||
void getGlyphPositions (const String& text, Array<int>& glyphs, Array<float>& xOffsets)
|
||||
void getGlyphPositions (const String& text, Array<int>& glyphs, Array<float>& xOffsets) override
|
||||
{
|
||||
JNIEnv* env = getEnv();
|
||||
const int numChars = text.length();
|
||||
|
|
@ -218,12 +218,12 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
bool getOutlineForGlyph (int /*glyphNumber*/, Path& /*destPath*/)
|
||||
bool getOutlineForGlyph (int /*glyphNumber*/, Path& /*destPath*/) override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
EdgeTable* getEdgeTableForGlyph (int glyphNumber, const AffineTransform& t)
|
||||
EdgeTable* getEdgeTableForGlyph (int glyphNumber, const AffineTransform& t, float /*fontHeight*/) override
|
||||
{
|
||||
JNIEnv* env = getEnv();
|
||||
|
||||
|
|
|
|||
|
|
@ -575,11 +575,11 @@ public:
|
|||
CFRelease (ctFontRef);
|
||||
}
|
||||
|
||||
float getAscent() const { return ascent; }
|
||||
float getDescent() const { return 1.0f - ascent; }
|
||||
float getHeightToPointsFactor() const { return fontHeightToPointsFactor; }
|
||||
float getAscent() const override { return ascent; }
|
||||
float getDescent() const override { return 1.0f - ascent; }
|
||||
float getHeightToPointsFactor() const override { return fontHeightToPointsFactor; }
|
||||
|
||||
float getStringWidth (const String& text)
|
||||
float getStringWidth (const String& text) override
|
||||
{
|
||||
float x = 0;
|
||||
|
||||
|
|
@ -612,7 +612,7 @@ public:
|
|||
return x;
|
||||
}
|
||||
|
||||
void getGlyphPositions (const String& text, Array <int>& resultGlyphs, Array <float>& xOffsets)
|
||||
void getGlyphPositions (const String& text, Array <int>& resultGlyphs, Array <float>& xOffsets) override
|
||||
{
|
||||
xOffsets.add (0);
|
||||
|
||||
|
|
@ -648,18 +648,7 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
EdgeTable* getEdgeTableForGlyph (int glyphNumber, const AffineTransform& transform)
|
||||
{
|
||||
Path path;
|
||||
|
||||
if (getOutlineForGlyph (glyphNumber, path) && ! path.isEmpty())
|
||||
return new EdgeTable (path.getBoundsTransformed (transform).getSmallestIntegerContainer().expanded (1, 0),
|
||||
path, transform);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool getOutlineForGlyph (int glyphNumber, Path& path)
|
||||
bool getOutlineForGlyph (int glyphNumber, Path& path) override
|
||||
{
|
||||
jassert (path.isEmpty()); // we might need to apply a transform to the path, so this must be empty
|
||||
|
||||
|
|
@ -906,11 +895,11 @@ public:
|
|||
#endif
|
||||
|
||||
|
||||
float getAscent() const { return ascent; }
|
||||
float getDescent() const { return 1.0f - ascent; }
|
||||
float getHeightToPointsFactor() const { return fontHeightToPointsFactor; }
|
||||
float getAscent() const override { return ascent; }
|
||||
float getDescent() const override { return 1.0f - ascent; }
|
||||
float getHeightToPointsFactor() const override { return fontHeightToPointsFactor; }
|
||||
|
||||
float getStringWidth (const String& text)
|
||||
float getStringWidth (const String& text) override
|
||||
{
|
||||
if (fontRef == 0 || text.isEmpty())
|
||||
return 0;
|
||||
|
|
@ -951,7 +940,7 @@ public:
|
|||
return x * unitsToHeightScaleFactor;
|
||||
}
|
||||
|
||||
void getGlyphPositions (const String& text, Array <int>& resultGlyphs, Array <float>& xOffsets)
|
||||
void getGlyphPositions (const String& text, Array <int>& resultGlyphs, Array <float>& xOffsets) override
|
||||
{
|
||||
xOffsets.add (0);
|
||||
|
||||
|
|
@ -1009,18 +998,7 @@ public:
|
|||
#endif
|
||||
}
|
||||
|
||||
EdgeTable* getEdgeTableForGlyph (int glyphNumber, const AffineTransform& transform)
|
||||
{
|
||||
Path path;
|
||||
|
||||
if (getOutlineForGlyph (glyphNumber, path) && ! path.isEmpty())
|
||||
return new EdgeTable (path.getBoundsTransformed (transform).getSmallestIntegerContainer().expanded (1, 0),
|
||||
path, transform);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool getOutlineForGlyph (int glyphNumber, Path& path)
|
||||
bool getOutlineForGlyph (int glyphNumber, Path& path) override
|
||||
{
|
||||
#if JUCE_IOS
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -237,17 +237,6 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
EdgeTable* getEdgeTableForGlyph (int glyphNumber, const AffineTransform& transform)
|
||||
{
|
||||
Path path;
|
||||
|
||||
if (getOutlineForGlyph (glyphNumber, path) && ! path.isEmpty())
|
||||
return new EdgeTable (path.getBoundsTransformed (transform).getSmallestIntegerContainer().expanded (1, 0),
|
||||
path, transform);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool getOutlineForGlyph (int glyphNumber, Path& path)
|
||||
{
|
||||
jassert (path.isEmpty()); // we might need to apply a transform to the path, so this must be empty
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@
|
|||
USE_FUNCTION (glUniformMatrix4fv, void, (GLint p1, GLsizei p2, GLboolean p3, const GLfloat* p4), (p1, p2, p3, p4))
|
||||
|
||||
#else
|
||||
#define JUCE_GL_EXTENSION_FUNCTIONS1(USE_FUNCTION) JUCE_GL_BASIC_EXTENSION_FUNCTIONS(USE_FUNCTION, EXT_FUNCTION)
|
||||
#define JUCE_GL_EXTENSION_FUNCTIONS1(USE_FUNCTION, EXT_FUNCTION) JUCE_GL_BASIC_EXTENSION_FUNCTIONS(USE_FUNCTION, EXT_FUNCTION)
|
||||
#endif
|
||||
|
||||
#if JUCE_USE_OPENGL_FIXED_FUNCTION
|
||||
|
|
|
|||
|
|
@ -1410,7 +1410,7 @@ public:
|
|||
AffineTransform t (transform.getTransformWith (AffineTransform::scale (fontHeight * font.getHorizontalScale(), fontHeight)
|
||||
.followedBy (trans)));
|
||||
|
||||
const ScopedPointer<EdgeTable> et (font.getTypeface()->getEdgeTableForGlyph (glyphNumber, t));
|
||||
const ScopedPointer<EdgeTable> et (font.getTypeface()->getEdgeTableForGlyph (glyphNumber, t, fontHeight));
|
||||
|
||||
if (et != nullptr)
|
||||
fillShape (new EdgeTableRegionType (*et), false);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue