/* ============================================================================== This file is part of the JUCE library - "Jules' Utility Class Extensions" Copyright 2004-7 by Raw Material Software ltd. ------------------------------------------------------------------------------ JUCE can be redistributed and/or modified under the terms of the GNU General Public License, as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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. You should have received a copy of the GNU General Public License along with JUCE; if not, visit www.gnu.org/licenses or write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ------------------------------------------------------------------------------ If you'd like to release a closed-source product which uses JUCE, commercial licenses are also available: visit www.rawmaterialsoftware.com/juce for more information. ============================================================================== */ #include "../../../juce_Config.h" #include "linuxincludes.h" /* Got a build error here? You'll need to install the freetype library... The name of the package to install is "libfreetype6-dev". */ #include #include FT_FREETYPE_H #include "../../../src/juce_core/basics/juce_StandardHeader.h" BEGIN_JUCE_NAMESPACE #include "../../../src/juce_appframework/gui/graphics/fonts/juce_Font.h" #include "../../../src/juce_core/basics/juce_Singleton.h" #include "../../../src/juce_core/io/streams/juce_MemoryInputStream.h" #include "../../../src/juce_core/io/files/juce_DirectoryIterator.h" #include "../../../src/juce_core/text/juce_XmlDocument.h" #include "../../../src/juce_appframework/application/juce_DeletedAtShutdown.h" //============================================================================== class FreeTypeFontFace { public: //============================================================================== enum FontStyle { Plain = 0, Bold = 1, Italic = 2 }; struct FontNameIndex { String fileName; int faceIndex; }; //============================================================================== FreeTypeFontFace (const String& familyName) : hasSerif (false), monospaced (false) { family = familyName; } void setFileName (const String& name, const int faceIndex, FontStyle style) { if (names[(int) style].fileName.isEmpty()) { names[(int) style].fileName = name; names[(int) style].faceIndex = faceIndex; } } const String& getFamilyName() const throw() { return family; } const String& getFileName (int style, int* faceIndex) const throw() { *faceIndex = names [style].faceIndex; return names[style].fileName; } void setMonospaced (bool mono) { monospaced = mono; } bool getMonospaced () const throw() { return monospaced; } void setSerif (const bool serif) { hasSerif = serif; } bool getSerif () const throw() { return hasSerif; } private: //============================================================================== String family; FontNameIndex names[4]; bool hasSerif, monospaced; }; //============================================================================== class FreeTypeInterface : public DeletedAtShutdown { public: //============================================================================== FreeTypeInterface() throw() : lastFace (0), lastBold (false), lastItalic (false) { if (FT_Init_FreeType (&ftLib) != 0) { ftLib = 0; DBG (T("Failed to initialize FreeType")); } StringArray fontDirs; fontDirs.addTokens (String (getenv ("JUCE_FONT_PATH")), T(";,"), 0); fontDirs.removeEmptyStrings (true); if (fontDirs.size() == 0) { XmlDocument fontsConfig (File ("/etc/fonts/fonts.conf")); XmlElement* const fontsInfo = fontsConfig.getDocumentElement(); if (fontsInfo != 0) { forEachXmlChildElementWithTagName (*fontsInfo, e, T("dir")) { fontDirs.add (e->getAllSubText().trim()); } delete fontsInfo; } } if (fontDirs.size() == 0) fontDirs.add ("/usr/X11R6/lib/X11/fonts"); for (int i = 0; i < fontDirs.size(); ++i) enumerateFaces (fontDirs[i]); } ~FreeTypeInterface() throw() { if (lastFace != 0) FT_Done_Face (lastFace); if (ftLib != 0) FT_Done_FreeType (ftLib); clearSingletonInstance(); } //============================================================================== FreeTypeFontFace* findOrCreate (const String& familyName, const bool create = false) throw() { for (int i = 0; i < faces.size(); i++) if (faces[i]->getFamilyName() == familyName) return faces[i]; if (! create) return NULL; FreeTypeFontFace* newFace = new FreeTypeFontFace (familyName); faces.add (newFace); return newFace; } // Enumerate all font faces available in a given directory void enumerateFaces (const String& path) throw() { File dirPath (path); if (path.isEmpty() || ! dirPath.isDirectory()) return; DirectoryIterator di (dirPath, true); while (di.next()) { File possible (di.getFile()); if (possible.hasFileExtension (T("ttf")) || possible.hasFileExtension (T("pfb")) || possible.hasFileExtension (T("pcf"))) { FT_Face face; int faceIndex = 0; int numFaces = 0; do { if (FT_New_Face (ftLib, possible.getFullPathName(), faceIndex, &face) == 0) { if (faceIndex == 0) numFaces = face->num_faces; if ((face->face_flags & FT_FACE_FLAG_SCALABLE) != 0) { FreeTypeFontFace* const newFace = findOrCreate (face->family_name, true); int style = (int) FreeTypeFontFace::Plain; if ((face->style_flags & FT_STYLE_FLAG_BOLD) != 0) style |= (int) FreeTypeFontFace::Bold; if ((face->style_flags & FT_STYLE_FLAG_ITALIC) != 0) style |= (int) FreeTypeFontFace::Italic; newFace->setFileName (possible.getFullPathName(), faceIndex, (FreeTypeFontFace::FontStyle) style); if ((face->face_flags & FT_FACE_FLAG_FIXED_WIDTH) != 0) newFace->setMonospaced (true); else newFace->setMonospaced (false); // Surely there must be a better way to do this? if (String (face->family_name).containsIgnoreCase (T("Sans")) || String (face->family_name).containsIgnoreCase (T("Verdana")) || String (face->family_name).containsIgnoreCase (T("Arial"))) { newFace->setSerif (false); } else { newFace->setSerif (true); } } FT_Done_Face (face); } ++faceIndex; } while (faceIndex < numFaces); } } } // Create a FreeType face object for a given font FT_Face createFT_Face (const String& fontName, const bool bold, const bool italic) throw() { FT_Face face = NULL; if (fontName == lastFontName && bold == lastBold && italic == lastItalic) { face = lastFace; } else { if (lastFace) { FT_Done_Face (lastFace); lastFace = NULL; } lastFontName = fontName; lastBold = bold; lastItalic = italic; FreeTypeFontFace* const ftFace = findOrCreate (fontName); if (ftFace != 0) { int style = (int) FreeTypeFontFace::Plain; if (bold) style |= (int) FreeTypeFontFace::Bold; if (italic) style |= (int) FreeTypeFontFace::Italic; int faceIndex; String fileName (ftFace->getFileName (style, &faceIndex)); if (fileName.isEmpty()) { style ^= (int) FreeTypeFontFace::Bold; fileName = ftFace->getFileName (style, &faceIndex); if (fileName.isEmpty()) { style ^= (int) FreeTypeFontFace::Bold; style ^= (int) FreeTypeFontFace::Italic; fileName = ftFace->getFileName (style, &faceIndex); if (! fileName.length()) { style ^= (int) FreeTypeFontFace::Bold; fileName = ftFace->getFileName (style, &faceIndex); } } } if (! FT_New_Face (ftLib, (const char*) fileName, faceIndex, &lastFace)) { face = lastFace; // If there isn't a unicode charmap then select the first one. if (FT_Select_Charmap (face, ft_encoding_unicode)) FT_Set_Charmap (face, face->charmaps[0]); } } } return face; } bool addGlyph (FT_Face face, Typeface& dest, uint32 character) throw() { const unsigned int glyphIndex = FT_Get_Char_Index (face, character); const float height = (float) (face->ascender - face->descender); const float scaleX = 1.0f / height; const float scaleY = -1.0f / height; Path destShape; #define CONVERTX(val) (scaleX * (val).x) #define CONVERTY(val) (scaleY * (val).y) 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) { return false; } const FT_Outline* const outline = &face->glyph->outline; const short* const contours = outline->contours; const char* const tags = outline->tags; 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 = CONVERTX (points[p]); const float y = CONVERTY (points[p]); if (p == startPoint) { if (FT_CURVE_TAG (tags[p]) == FT_Curve_Tag_Conic) { float x2 = CONVERTX (points [endPoint]); float y2 = CONVERTY (points [endPoint]); 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 = CONVERTX (points [nextIndex]); float y2 = CONVERTY (points [nextIndex]); 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) { if (p >= endPoint) return false; const int next1 = p + 1; const int next2 = (p == (endPoint - 1)) ? startPoint : p + 2; const float x2 = CONVERTX (points [next1]); const float y2 = CONVERTY (points [next1]); const float x3 = CONVERTX (points [next2]); const float y3 = CONVERTY (points [next2]); if (FT_CURVE_TAG (tags[next1]) != FT_Curve_Tag_Cubic || FT_CURVE_TAG (tags[next2]) != FT_Curve_Tag_On) return false; destShape.cubicTo (x, y, x2, y2, x3, y3); p += 2; } } destShape.closeSubPath(); } dest.addGlyph (character, destShape, face->glyph->metrics.horiAdvance/height); if ((face->face_flags & FT_FACE_FLAG_KERNING) != 0) addKerning (face, dest, character, glyphIndex); return true; } void addKerning (FT_Face face, Typeface& dest, const uint32 character, const uint32 glyphIndex) throw() { 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) { if (kerning.x != 0) dest.addKerningPair (character, rightCharCode, kerning.x / height); } rightCharCode = FT_Get_Next_Char (face, rightCharCode, &rightGlyphIndex); } } // Add a glyph to a font bool addGlyphToFont (const uint32 character, const tchar* fontName, bool bold, bool italic, Typeface& dest) throw() { FT_Face face = createFT_Face (fontName, bold, italic); if (face != 0) return addGlyph (face, dest, character); return false; } // Create a Typeface object for given name/style bool createTypeface (const String& fontName, const bool bold, const bool italic, Typeface& dest, const bool addAllGlyphs) throw() { dest.clear(); dest.setName (fontName); dest.setBold (bold); dest.setItalic (italic); FT_Face face = createFT_Face (fontName, bold, italic); if (face == 0) { #ifdef JUCE_DEBUG String msg (T("Failed to create typeface: ")); msg << fontName << " " << (bold ? 'B' : ' ') << (italic ? 'I' : ' '); DBG (msg); #endif return face; } const float height = (float) (face->ascender - face->descender); dest.setAscent (face->ascender / height); dest.setDefaultCharacter (L' '); if (addAllGlyphs) { uint32 glyphIndex; uint32 charCode = FT_Get_First_Char (face, &glyphIndex); while (glyphIndex != 0) { addGlyph (face, dest, charCode); charCode = FT_Get_Next_Char (face, charCode, &glyphIndex); } } return true; } //============================================================================== void getFamilyNames (StringArray& familyNames) const throw() { for (int i = 0; i < faces.size(); i++) familyNames.add (faces[i]->getFamilyName()); } void getMonospacedNames (StringArray& monoSpaced) const throw() { for (int i = 0; i < faces.size(); i++) if (faces[i]->getMonospaced()) monoSpaced.add (faces[i]->getFamilyName()); } void getSerifNames (StringArray& serif) const throw() { for (int i = 0; i < faces.size(); i++) if (faces[i]->getSerif()) serif.add (faces[i]->getFamilyName()); } void getSansSerifNames (StringArray& sansSerif) const throw() { for (int i = 0; i < faces.size(); i++) if (! faces[i]->getSerif()) sansSerif.add (faces[i]->getFamilyName()); } juce_DeclareSingleton_SingleThreaded_Minimal (FreeTypeInterface) private: //============================================================================== FT_Library ftLib; FT_Face lastFace; String lastFontName; bool lastBold, lastItalic; OwnedArray faces; }; juce_ImplementSingleton_SingleThreaded (FreeTypeInterface) //============================================================================== void Typeface::initialiseTypefaceCharacteristics (const String& fontName, bool bold, bool italic, bool addAllGlyphsToFont) throw() { FreeTypeInterface::getInstance() ->createTypeface (fontName, bold, italic, *this, addAllGlyphsToFont); } bool Typeface::findAndAddSystemGlyph (juce_wchar character) throw() { return FreeTypeInterface::getInstance() ->addGlyphToFont (character, getName(), isBold(), isItalic(), *this); } const StringArray Font::findAllTypefaceNames() throw() { StringArray s; FreeTypeInterface::getInstance()->getFamilyNames (s); s.sort (true); return s; } static const String pickBestFont (const StringArray& names, const char* const choicesString) { StringArray choices; choices.addTokens (String (choicesString), T(","), 0); choices.trim(); choices.removeEmptyStrings(); int i, 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 (i = 0; i < names.size(); i++) if (names[i].startsWithIgnoreCase (choices[j])) return names[i]; for (j = 0; j < choices.size(); ++j) for (i = 0; i < names.size(); i++) if (names[i].containsIgnoreCase (choices[j])) return names[i]; return names[0]; } static const String linux_getDefaultSansSerifFontName() { StringArray allFonts; FreeTypeInterface::getInstance()->getSansSerifNames (allFonts); return pickBestFont (allFonts, "Verdana, Bitstream Vera Sans, Luxi Sans, Sans"); } static const String linux_getDefaultSerifFontName() { StringArray allFonts; FreeTypeInterface::getInstance()->getSerifNames (allFonts); return pickBestFont (allFonts, "Bitstream Vera Serif, Times, Nimbus Roman, Serif"); } static const String linux_getDefaultMonospacedFontName() { StringArray allFonts; FreeTypeInterface::getInstance()->getMonospacedNames (allFonts); return pickBestFont (allFonts, "Bitstream Vera Sans Mono, Courier, Sans Mono, Mono"); } void Typeface::getDefaultFontNames (String& defaultSans, String& defaultSerif, String& defaultFixed) throw() { defaultSans = linux_getDefaultSansSerifFontName(); defaultSerif = linux_getDefaultSerifFontName(); defaultFixed = linux_getDefaultMonospacedFontName(); } END_JUCE_NAMESPACE