| @@ -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; | |||
| const float t = fontSize * top; | |||
| const float m = fontSize * middle; | |||
| const float b = fontSize * bottom; | |||
| if (b < t + 2.0f) | |||
| return AffineTransform(); | |||
| 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) }; | |||
| if (cachedSize != fontSize) | |||
| { | |||
| cachedSize = fontSize; | |||
| cachedScale = Scaling (top, middle, bottom, fontSize); | |||
| } | |||
| int best = 0; | |||
| if (bottom < top + 3.0f / fontSize) | |||
| return; | |||
| for (int i = 1; i < numElementsInArray (s); ++i) | |||
| if (s[i].drift < s[best].drift) | |||
| best = i; | |||
| Path result; | |||
| cachedSize = fontSize; | |||
| 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; | |||
| } | |||
| } | |||
| 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 | |||
| { | |||
| float newT = std::floor (t) + direction1; | |||
| float newB = std::floor (b) + direction2; | |||
| float newM = newT + (newB - newT) * (m - t) / (b - t); | |||
| Scaling() noexcept : middle(), upperScale(), upperOffset(), lowerScale(), lowerOffset() {} | |||
| float middleOffset = newM - std::floor (newM); | |||
| float nudge = middleOffset < 0.5f ? (middleOffset * -0.2f) : ((1.0f - middleOffset) * 0.2f); | |||
| newT += nudge; | |||
| newB += nudge; | |||
| scale = (newB - newT) / (b - t); | |||
| offset = (newB / scale) - b; | |||
| Scaling (float t, float m, float b, float fontSize) noexcept : middle (m) | |||
| { | |||
| 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; | |||
| drift = getDrift (t) + getDrift (m) + getDrift (b) + offset + 20.0f * std::abs (scale - 1.0f); | |||
| } | |||
| upperScale = jlimit (0.9f, 1.1f, (newM - newT) / (m - t)); | |||
| lowerScale = jlimit (0.9f, 1.1f, (newB - newM) / (b - m)); | |||
| AffineTransform getTransform() const noexcept | |||
| { | |||
| return AffineTransform::translation (0, offset).scaled (1.0f, scale); | |||
| upperOffset = newM - m * upperScale; | |||
| lowerOffset = newB - b * lowerScale; | |||
| } | |||
| float getDrift (float n) const noexcept | |||
| float apply (float y) const noexcept | |||
| { | |||
| n = (n + offset) * scale; | |||
| const float diff = n - std::floor (n); | |||
| return jmin (diff, 1.0f - diff); | |||
| return y < middle ? (y * upperScale + upperOffset) | |||
| : (y * lowerScale + lowerOffset); | |||
| } | |||
| 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(); | |||
| ScopedLock sl (hintingLock); | |||
| if (fontSize > 3.0f && fontSize < 25.0f) | |||
| { | |||
| 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); | |||