diff --git a/modules/juce_graphics/fonts/juce_Typeface.cpp b/modules/juce_graphics/fonts/juce_Typeface.cpp index 032ee318dd..85df87eb89 100644 --- a/modules/juce_graphics/fonts/juce_Typeface.cpp +++ b/modules/juce_graphics/fonts/juce_Typeface.cpp @@ -101,8 +101,8 @@ struct FontStyleHelpers }; //============================================================================== -Typeface::Typeface (const String& name_, const String& style_) noexcept - : name (name_), style (style_) +Typeface::Typeface (const String& faceName, const String& styleName) noexcept + : name (faceName), style (styleName) { } @@ -126,3 +126,134 @@ EdgeTable* Typeface::getEdgeTableForGlyph (int glyphNumber, const AffineTransfor return nullptr; } + +//============================================================================== +struct Typeface::HintingParams +{ + HintingParams (Typeface& t) + : top (0), middle (0), bottom (0) + { + Font font (&t); + font = font.withHeight ((float) standardHeight); + + top = getAverageY (font, "BDEFPRTZOQC", true); + middle = getAverageY (font, "acegmnopqrsuvwxy", true); + bottom = getAverageY (font, "BDELZOC", false); + } + + AffineTransform getVerticalHintingTransform (float fontSize) noexcept + { + 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) }; + + 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; + } + +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); + + 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; + + drift = getDrift (t) + getDrift (m) + getDrift (b) + offset + 20.0f * std::abs (scale - 1.0f); + } + + AffineTransform getTransform() const noexcept + { + return AffineTransform::translation (0, offset).scaled (1.0f, scale); + } + + 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; + }; + + static float getAverageY (const Font& font, const char* chars, bool getTop) + { + GlyphArrangement ga; + ga.addLineOfText (font, chars, 0, 0); + + Array y; + DefaultElementComparator sorter; + + for (int i = 0; i < ga.getNumGlyphs(); ++i) + { + Path p; + ga.getGlyph (i).createPath (p); + Rectangle bounds (p.getBounds()); + + if (! p.isEmpty()) + y.addSorted (sorter, getTop ? bounds.getY() : bounds.getBottom()); + } + + float median = y[y.size() / 2]; + + int total = 0; + int num = 0; + + for (int i = 0; i < y.size(); ++i) + { + if (std::abs (median - y.getUnchecked(i)) < 0.05f * (float) standardHeight) + { + total += y.getUnchecked(i); + ++num; + } + } + + return num < 4 ? 0.0f : total / (num * (float) standardHeight); + } + + enum { standardHeight = 100 }; + float top, middle, bottom; +}; + +AffineTransform Typeface::getVerticalHintingTransform (float fontSize) +{ + ScopedLock sl (hintingLock); + + if (hintingParams == nullptr) + hintingParams = new HintingParams (*this); + + return hintingParams->getVerticalHintingTransform (fontSize); +} diff --git a/modules/juce_graphics/fonts/juce_Typeface.h b/modules/juce_graphics/fonts/juce_Typeface.h index 9c2ae31ef3..ed42a48210 100644 --- a/modules/juce_graphics/fonts/juce_Typeface.h +++ b/modules/juce_graphics/fonts/juce_Typeface.h @@ -134,6 +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. + */ + AffineTransform getVerticalHintingTransform (float fontHeight); + protected: //============================================================================== String name, style; @@ -143,6 +148,11 @@ protected: static Ptr getFallbackTypeface(); private: + struct HintingParams; + friend struct ContainerDeletePolicy; + ScopedPointer hintingParams; + CriticalSection hintingLock; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Typeface) }; diff --git a/modules/juce_graphics/native/juce_RenderingHelpers.h b/modules/juce_graphics/native/juce_RenderingHelpers.h index dae758cf80..cb8e9d6cc8 100644 --- a/modules/juce_graphics/native/juce_RenderingHelpers.h +++ b/modules/juce_graphics/native/juce_RenderingHelpers.h @@ -283,11 +283,9 @@ public: glyph = glyphNumber; const float fontHeight = font.getHeight(); - edgeTable = typeface->getEdgeTableForGlyph (glyphNumber, AffineTransform::scale (fontHeight * font.getHorizontalScale(), - fontHeight)); - - if (edgeTable != nullptr) - edgeTable->multiplyLevels (1.5f); + edgeTable = typeface->getEdgeTableForGlyph (glyphNumber, + AffineTransform::scale (fontHeight * font.getHorizontalScale(), fontHeight) + .followedBy (font.getTypeface()->getVerticalHintingTransform (fontHeight))); } Font font; @@ -295,7 +293,7 @@ public: bool snapToIntegerCoordinate; private: - ScopedPointer edgeTable; + ScopedPointer edgeTable; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CachedGlyphEdgeTable) }; @@ -1351,7 +1349,7 @@ namespace EdgeTableFillers const int maxX, maxY; int y; DestPixelType* linePixels; - HeapBlock scratchBuffer; + HeapBlock scratchBuffer; size_t scratchSize; JUCE_DECLARE_NON_COPYABLE (TransformedImageFill) @@ -1369,16 +1367,16 @@ namespace EdgeTableFillers switch (srcData.pixelFormat) { case Image::ARGB: - if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } - else { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } + if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } + else { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } break; case Image::RGB: - if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } - else { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } + if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } + else { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } break; default: - if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } - else { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } + if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } + else { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } break; } break; @@ -1387,16 +1385,16 @@ namespace EdgeTableFillers switch (srcData.pixelFormat) { case Image::ARGB: - if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } - else { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } + if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } + else { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } break; case Image::RGB: - if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } - else { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } + if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } + else { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } break; default: - if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } - else { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } + if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } + else { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } break; } break; @@ -1405,16 +1403,16 @@ namespace EdgeTableFillers switch (srcData.pixelFormat) { case Image::ARGB: - if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } - else { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } + if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } + else { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } break; case Image::RGB: - if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } - else { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } + if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } + else { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } break; default: - if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } - else { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } + if (tiledFill) { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } + else { TransformedImageFill r (destData, srcData, transform, alpha, quality); iter.iterate (r); } break; } break; @@ -1430,16 +1428,16 @@ namespace EdgeTableFillers switch (srcData.pixelFormat) { case Image::ARGB: - if (tiledFill) { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } - else { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } + if (tiledFill) { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } + else { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } break; case Image::RGB: - if (tiledFill) { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } - else { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } + if (tiledFill) { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } + else { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } break; default: - if (tiledFill) { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } - else { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } + if (tiledFill) { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } + else { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } break; } break; @@ -1448,16 +1446,16 @@ namespace EdgeTableFillers switch (srcData.pixelFormat) { case Image::ARGB: - if (tiledFill) { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } - else { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } + if (tiledFill) { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } + else { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } break; case Image::RGB: - if (tiledFill) { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } - else { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } + if (tiledFill) { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } + else { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } break; default: - if (tiledFill) { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } - else { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } + if (tiledFill) { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } + else { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } break; } break; @@ -1466,16 +1464,16 @@ namespace EdgeTableFillers switch (srcData.pixelFormat) { case Image::ARGB: - if (tiledFill) { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } - else { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } + if (tiledFill) { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } + else { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } break; case Image::RGB: - if (tiledFill) { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } - else { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } + if (tiledFill) { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } + else { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } break; default: - if (tiledFill) { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } - else { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } + if (tiledFill) { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } + else { ImageFill r (destData, srcData, alpha, x, y); iter.iterate (r); } break; } break; @@ -1487,12 +1485,12 @@ namespace EdgeTableFillers { if (replaceContents) { - EdgeTableFillers::SolidColour r (destData, fillColour); + EdgeTableFillers::SolidColour r (destData, fillColour); iter.iterate (r); } else { - EdgeTableFillers::SolidColour r (destData, fillColour); + EdgeTableFillers::SolidColour r (destData, fillColour); iter.iterate (r); } } @@ -1505,18 +1503,18 @@ namespace EdgeTableFillers { if (isIdentity) { - EdgeTableFillers::Gradient renderer (destData, g, transform, lookupTable, numLookupEntries); + EdgeTableFillers::Gradient renderer (destData, g, transform, lookupTable, numLookupEntries); iter.iterate (renderer); } else { - EdgeTableFillers::Gradient renderer (destData, g, transform, lookupTable, numLookupEntries); + EdgeTableFillers::Gradient renderer (destData, g, transform, lookupTable, numLookupEntries); iter.iterate (renderer); } } else { - EdgeTableFillers::Gradient renderer (destData, g, transform, lookupTable, numLookupEntries); + EdgeTableFillers::Gradient renderer (destData, g, transform, lookupTable, numLookupEntries); iter.iterate (renderer); } } @@ -1721,7 +1719,7 @@ struct ClipRegions template void transformedClipImage (const Image::BitmapData& srcData, const AffineTransform& transform, const Graphics::ResamplingQuality quality, const SrcPixelType*) { - EdgeTableFillers::TransformedImageFill renderer (srcData, srcData, transform, 255, quality); + EdgeTableFillers::TransformedImageFill renderer (srcData, srcData, transform, 255, quality); for (int y = 0; y < edgeTable.getMaximumBounds().getHeight(); ++y) renderer.clipEdgeTableLine (edgeTable, edgeTable.getMaximumBounds().getX(), y + edgeTable.getMaximumBounds().getY(), @@ -1734,7 +1732,7 @@ struct ClipRegions Rectangle r (imageX, imageY, srcData.width, srcData.height); edgeTable.clipToRectangle (r); - EdgeTableFillers::ImageFill renderer (srcData, srcData, 255, imageX, imageY); + EdgeTableFillers::ImageFill renderer (srcData, srcData, 255, imageX, imageY); for (int y = 0; y < r.getHeight(); ++y) renderer.clipEdgeTableLine (edgeTable, r.getX(), y + r.getY(), r.getWidth()); @@ -2002,7 +2000,7 @@ public: { } - SavedStateType& getThis() noexcept { return *static_cast (this); } + SavedStateType& getThis() noexcept { return *static_cast (this); } bool clipToRectangle (const Rectangle& r) { @@ -2253,11 +2251,20 @@ public: { EdgeTableRegionType* edgeTableClip = new EdgeTableRegionType (edgeTable); edgeTableClip->edgeTable.translate (x, y); + + if (fillType.isColour()) + { + float brightness = fillType.colour.getBrightness() - 0.5f; + + if (brightness > 0.0f) + edgeTableClip->edgeTable.multiplyLevels (1.0f + 1.6f * brightness); + } + fillShape (edgeTableClip, false); } } - void drawLine (const Line & line) + void drawLine (const Line& line) { Path p; p.addLineSegment (line, 1.0f); @@ -2437,7 +2444,7 @@ public: } } - typedef GlyphCache, SoftwareRendererSavedState> GlyphCacheType; + typedef GlyphCache, SoftwareRendererSavedState> GlyphCacheType; static void clearGlyphCache() { @@ -2523,7 +2530,7 @@ public: template void fillWithGradient (IteratorType& iter, ColourGradient& gradient, const AffineTransform& trans, bool isIdentity) const { - HeapBlock lookupTable; + HeapBlock lookupTable; const int numLookupEntries = gradient.createLookupTable (trans, lookupTable); jassert (numLookupEntries > 0); @@ -2632,7 +2639,7 @@ public: void fillPath (const Path& path, const AffineTransform& t) override { stack->fillPath (path, t); } void drawImage (const Image& im, const AffineTransform& t) override { stack->drawImage (im, t); } void drawGlyph (int glyphNumber, const AffineTransform& t) override { stack->drawGlyph (glyphNumber, t); } - void drawLine (const Line & line) override { stack->drawLine (line); } + void drawLine (const Line& line) override { stack->drawLine (line); } void setFont (const Font& newFont) override { stack->font = newFont; } const Font& getFont() override { return stack->font; }