/* ============================================================================== This file is part of the JUCE library - "Jules' Utility Class Extensions" Copyright 2004-11 by Raw Material Software Ltd. ------------------------------------------------------------------------------ JUCE can be redistributed and/or modified under the terms of the GNU General Public License (Version 2), as published by the Free Software Foundation. A copy of the license is included in the JUCE distribution, or can be found online at www.gnu.org/licenses. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. ------------------------------------------------------------------------------ To release a closed-source product which uses JUCE, commercial licenses are available: visit www.rawmaterialsoftware.com/juce for more information. ============================================================================== */ struct FTLibWrapper : public ReferenceCountedObject { FTLibWrapper() : library (0) { if (FT_Init_FreeType (&library) != 0) { library = 0; DBG ("Failed to initialize FreeType"); } } ~FTLibWrapper() { if (library != 0) FT_Done_FreeType (library); } FT_Library library; typedef ReferenceCountedObjectPtr Ptr; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FTLibWrapper); }; //============================================================================== struct FTFaceWrapper : public ReferenceCountedObject { FTFaceWrapper (const FTLibWrapper::Ptr& ftLib, const File& file, int faceIndex) : face (0), library (ftLib) { if (FT_New_Face (ftLib->library, file.getFullPathName().toUTF8(), faceIndex, &face) != 0) face = 0; } ~FTFaceWrapper() { if (face != 0) FT_Done_Face (face); } FT_Face face; FTLibWrapper::Ptr library; typedef ReferenceCountedObjectPtr Ptr; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FTFaceWrapper); }; //============================================================================== class LinuxFontFileIterator { public: LinuxFontFileIterator() : index (0) { fontDirs.addTokens (CharPointer_UTF8 (getenv ("JUCE_FONT_PATH")), ";,", String::empty); fontDirs.removeEmptyStrings (true); if (fontDirs.size() == 0) { const ScopedPointer fontsInfo (XmlDocument::parse (File ("/etc/fonts/fonts.conf"))); if (fontsInfo != nullptr) { forEachXmlChildElementWithTagName (*fontsInfo, e, "dir") { fontDirs.add (e->getAllSubText().trim()); } } } if (fontDirs.size() == 0) fontDirs.add ("/usr/X11R6/lib/X11/fonts"); fontDirs.removeEmptyStrings (true); } bool next() { if (iter != nullptr) { while (iter->next()) if (getFile().hasFileExtension ("ttf;pfb;pcf")) return true; } if (index >= fontDirs.size()) return false; iter = new DirectoryIterator (fontDirs [index++], true); return next(); } File getFile() const { jassert (iter != nullptr); return iter->getFile(); } private: StringArray fontDirs; int index; ScopedPointer iter; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LinuxFontFileIterator); }; //============================================================================== class FTTypefaceList : public DeletedAtShutdown { public: FTTypefaceList() : library (new FTLibWrapper()) { LinuxFontFileIterator fontFileIterator; while (fontFileIterator.next()) { int faceIndex = 0; int numFaces = 0; do { FTFaceWrapper face (library, fontFileIterator.getFile(), faceIndex); if (face.face != 0) { if (faceIndex == 0) numFaces = face.face->num_faces; if ((face.face->face_flags & FT_FACE_FLAG_SCALABLE) != 0) faces.add (new KnownTypeface (fontFileIterator.getFile(), faceIndex, face)); } ++faceIndex; } while (faceIndex < numFaces); } } ~FTTypefaceList() { clearSingletonInstance(); } //============================================================================== struct KnownTypeface { KnownTypeface (const File& file_, const int faceIndex_, const FTFaceWrapper& face) : file (file_), family (face.face->family_name), faceIndex (faceIndex_), isBold ((face.face->style_flags & FT_STYLE_FLAG_BOLD) != 0), isItalic ((face.face->style_flags & FT_STYLE_FLAG_ITALIC) != 0), isMonospaced ((face.face->face_flags & FT_FACE_FLAG_FIXED_WIDTH) != 0), isSansSerif (isFaceSansSerif (family)) { } const File file; const String family; const int faceIndex; const bool isBold, isItalic, isMonospaced, isSansSerif; JUCE_DECLARE_NON_COPYABLE (KnownTypeface); }; //============================================================================== FTFaceWrapper::Ptr createFace (const String& fontName, const bool bold, const bool italic) { const KnownTypeface* ftFace = matchTypeface (fontName, bold, italic); if (ftFace == nullptr) { ftFace = matchTypeface (fontName, ! bold, italic); if (ftFace == nullptr) { ftFace = matchTypeface (fontName, bold, ! italic); if (ftFace == nullptr) ftFace = matchTypeface (fontName, ! bold, ! italic); } } if (ftFace != nullptr) { FTFaceWrapper::Ptr face (new FTFaceWrapper (library, ftFace->file, ftFace->faceIndex)); if (face->face != 0) { // If there isn't a unicode charmap then select the first one. if (FT_Select_Charmap (face->face, ft_encoding_unicode) != 0) FT_Set_Charmap (face->face, face->face->charmaps[0]); return face; } } return nullptr; } //============================================================================== void getFamilyNames (StringArray& familyNames) const { for (int i = 0; i < faces.size(); i++) familyNames.addIfNotAlreadyThere (faces.getUnchecked(i)->family); } void getMonospacedNames (StringArray& monoSpaced) const { for (int i = 0; i < faces.size(); i++) if (faces.getUnchecked(i)->isMonospaced) monoSpaced.addIfNotAlreadyThere (faces.getUnchecked(i)->family); } void getSerifNames (StringArray& serif) const { for (int i = 0; i < faces.size(); i++) if (! faces.getUnchecked(i)->isSansSerif) serif.addIfNotAlreadyThere (faces.getUnchecked(i)->family); } void getSansSerifNames (StringArray& sansSerif) const { for (int i = 0; i < faces.size(); i++) if (faces.getUnchecked(i)->isSansSerif) sansSerif.addIfNotAlreadyThere (faces.getUnchecked(i)->family); } juce_DeclareSingleton_SingleThreaded_Minimal (FTTypefaceList); private: FTLibWrapper::Ptr library; OwnedArray faces; const KnownTypeface* matchTypeface (const String& familyName, const bool wantBold, const bool wantItalic) const noexcept { for (int i = 0; i < faces.size(); ++i) { const KnownTypeface* const face = faces.getUnchecked(i); if (face->family == familyName && face->isBold == wantBold && face->isItalic == wantItalic) return face; } return nullptr; } static bool isFaceSansSerif (const String& family) { const char* sansNames[] = { "Sans", "Verdana", "Arial", "Ubuntu" }; for (int i = 0; i < numElementsInArray (sansNames); ++i) if (family.containsIgnoreCase (sansNames[i])) return true; return false; } JUCE_DECLARE_NON_COPYABLE (FTTypefaceList); }; juce_ImplementSingleton_SingleThreaded (FTTypefaceList) //============================================================================== class FreeTypeTypeface : public CustomTypeface { public: FreeTypeTypeface (const Font& font) : faceWrapper (FTTypefaceList::getInstance() ->createFace (font.getTypefaceName(), font.isBold(), font.isItalic())) { if (faceWrapper != nullptr) { setCharacteristics (font.getTypefaceName(), faceWrapper->face->ascender / (float) (faceWrapper->face->ascender - faceWrapper->face->descender), font.isBold(), font.isItalic(), L' '); } else { DBG ("Failed to create typeface: " << font.getTypefaceName() << " " << (font.isBold() ? 'B' : ' ') << (font.isItalic() ? 'I' : ' ')); } } bool loadGlyphIfPossible (const juce_wchar character) { if (faceWrapper != nullptr) { FT_Face face = faceWrapper->face; const unsigned int glyphIndex = FT_Get_Char_Index (face, character); if (FT_Load_Glyph (face, glyphIndex, FT_LOAD_NO_SCALE | FT_LOAD_NO_BITMAP | FT_LOAD_IGNORE_TRANSFORM) == 0 && face->glyph->format == ft_glyph_format_outline) { const float scale = 1.0f / (float) (face->ascender - face->descender); Path destShape; if (getGlyphShape (destShape, face->glyph->outline, scale)) { addGlyph (character, destShape, face->glyph->metrics.horiAdvance * scale); if ((face->face_flags & FT_FACE_FLAG_KERNING) != 0) addKerning (face, character, glyphIndex); return true; } } } return false; } private: FTFaceWrapper::Ptr faceWrapper; bool getGlyphShape (Path& destShape, const FT_Outline& outline, const float scaleX) { const float scaleY = -scaleX; const short* const contours = outline.contours; const char* const tags = outline.tags; const FT_Vector* const points = outline.points; for (int c = 0; c < outline.n_contours; ++c) { const int startPoint = (c == 0) ? 0 : contours [c - 1] + 1; const int endPoint = contours[c]; for (int p = startPoint; p <= endPoint; ++p) { const float x = scaleX * points[p].x; const float y = scaleY * points[p].y; if (p == startPoint) { if (FT_CURVE_TAG (tags[p]) == FT_Curve_Tag_Conic) { float x2 = scaleX * points [endPoint].x; float y2 = scaleY * points [endPoint].y; if (FT_CURVE_TAG (tags[endPoint]) != FT_Curve_Tag_On) { x2 = (x + x2) * 0.5f; y2 = (y + y2) * 0.5f; } destShape.startNewSubPath (x2, y2); } else { destShape.startNewSubPath (x, y); } } if (FT_CURVE_TAG (tags[p]) == FT_Curve_Tag_On) { if (p != startPoint) destShape.lineTo (x, y); } else if (FT_CURVE_TAG (tags[p]) == FT_Curve_Tag_Conic) { const int nextIndex = (p == endPoint) ? startPoint : p + 1; float x2 = scaleX * points [nextIndex].x; float y2 = scaleY * points [nextIndex].y; if (FT_CURVE_TAG (tags [nextIndex]) == FT_Curve_Tag_Conic) { x2 = (x + x2) * 0.5f; y2 = (y + y2) * 0.5f; } else { ++p; } destShape.quadraticTo (x, y, x2, y2); } else if (FT_CURVE_TAG (tags[p]) == FT_Curve_Tag_Cubic) { const int next1 = p + 1; const int next2 = (p == (endPoint - 1)) ? startPoint : (p + 2); if (p >= endPoint || FT_CURVE_TAG (tags[next1]) != FT_Curve_Tag_Cubic || FT_CURVE_TAG (tags[next2]) != FT_Curve_Tag_On) return false; const float x2 = scaleX * points [next1].x; const float y2 = scaleY * points [next1].y; const float x3 = scaleX * points [next2].x; const float y3 = scaleY * points [next2].y; destShape.cubicTo (x, y, x2, y2, x3, y3); p += 2; } } destShape.closeSubPath(); } return true; } void addKerning (FT_Face face, const uint32 character, const uint32 glyphIndex) { const float height = (float) (face->ascender - face->descender); uint32 rightGlyphIndex; uint32 rightCharCode = FT_Get_First_Char (face, &rightGlyphIndex); while (rightGlyphIndex != 0) { FT_Vector kerning; if (FT_Get_Kerning (face, glyphIndex, rightGlyphIndex, ft_kerning_unscaled, &kerning) == 0 && kerning.x != 0) addKerningPair (character, rightCharCode, kerning.x / height); rightCharCode = FT_Get_Next_Char (face, rightCharCode, &rightGlyphIndex); } } JUCE_DECLARE_NON_COPYABLE (FreeTypeTypeface); }; //============================================================================== Typeface::Ptr Typeface::createSystemTypefaceFor (const Font& font) { return new FreeTypeTypeface (font); } StringArray Font::findAllTypefaceNames() { StringArray s; FTTypefaceList::getInstance()->getFamilyNames (s); s.sort (true); return s; } //============================================================================== struct DefaultFontNames { DefaultFontNames() : defaultSans (getDefaultSansSerifFontName()), defaultSerif (getDefaultSerifFontName()), defaultFixed (getDefaultMonospacedFontName()) { } String defaultSans, defaultSerif, defaultFixed; private: static String pickBestFont (const StringArray& names, const char* const* choicesArray) { const StringArray choices (choicesArray); int j; for (j = 0; j < choices.size(); ++j) if (names.contains (choices[j], true)) return choices[j]; for (j = 0; j < choices.size(); ++j) for (int i = 0; i < names.size(); ++i) if (names[i].startsWithIgnoreCase (choices[j])) return names[i]; for (j = 0; j < choices.size(); ++j) for (int i = 0; i < names.size(); ++i) if (names[i].containsIgnoreCase (choices[j])) return names[i]; return names[0]; } static String getDefaultSansSerifFontName() { StringArray allFonts; FTTypefaceList::getInstance()->getSansSerifNames (allFonts); const char* targets[] = { "Verdana", "Bitstream Vera Sans", "Luxi Sans", "Sans", 0 }; return pickBestFont (allFonts, targets); } static String getDefaultSerifFontName() { StringArray allFonts; FTTypefaceList::getInstance()->getSerifNames (allFonts); const char* targets[] = { "Bitstream Vera Serif", "Times", "Nimbus Roman", "Serif", 0 }; return pickBestFont (allFonts, targets); } static String getDefaultMonospacedFontName() { StringArray allFonts; FTTypefaceList::getInstance()->getMonospacedNames (allFonts); const char* targets[] = { "Bitstream Vera Sans Mono", "Courier", "Sans Mono", "Mono", 0 }; return pickBestFont (allFonts, targets); } JUCE_DECLARE_NON_COPYABLE (DefaultFontNames); }; Typeface::Ptr Font::getDefaultTypefaceForFont (const Font& font) { static DefaultFontNames defaultNames; String faceName (font.getTypefaceName()); if (faceName == getDefaultSansSerifFontName()) faceName = defaultNames.defaultSans; else if (faceName == getDefaultSerifFontName()) faceName = defaultNames.defaultSerif; else if (faceName == getDefaultMonospacedFontName()) faceName = defaultNames.defaultFixed; Font f (font); f.setTypefaceName (faceName); return Typeface::createSystemTypefaceFor (f); }