1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-10 23:44:24 +00:00
JUCE/modules/juce_graphics/native/juce_linux_Fonts.cpp

562 lines
18 KiB
C++

/*
==============================================================================
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 <FTLibWrapper> 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 <FTFaceWrapper> 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<XmlElement> 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<DirectoryIterator> iter;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LinuxFontFileIterator);
};
//==============================================================================
class FTTypefaceList : private 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),
style (face.face->style_name),
faceIndex (faceIndex_),
isMonospaced ((face.face->face_flags & FT_FACE_FLAG_FIXED_WIDTH) != 0),
isSansSerif (isFaceSansSerif (family))
{
}
const File file;
const String family, style;
const int faceIndex;
const bool isMonospaced, isSansSerif;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (KnownTypeface);
};
//==============================================================================
FTFaceWrapper::Ptr createFace (const String& fontName, const String& fontStyle)
{
const KnownTypeface* ftFace = matchTypeface (fontName, fontStyle);
if (ftFace == nullptr)
ftFace = matchTypeface (fontName, "Regular");
if (ftFace == nullptr)
ftFace = matchTypeface (fontName, String::empty);
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;
}
//==============================================================================
StringArray findAllFamilyNames() const
{
StringArray s;
for (int i = 0; i < faces.size(); i++)
s.addIfNotAlreadyThere (faces.getUnchecked(i)->family);
return s;
}
StringArray findAllTypefaceStyles (const String& family) const
{
StringArray s;
for (int i = 0; i < faces.size(); i++)
{
const KnownTypeface* const face = faces.getUnchecked(i);
if (face->family == family)
s.addIfNotAlreadyThere (face->style);
}
return s;
}
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<KnownTypeface> faces;
const KnownTypeface* matchTypeface (const String& familyName, const String& style) const noexcept
{
for (int i = 0; i < faces.size(); ++i)
{
const KnownTypeface* const face = faces.getUnchecked(i);
if (face->family == familyName
&& (face->style.equalsIgnoreCase (style) || style.isEmpty()))
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_WITH_LEAK_DETECTOR (FTTypefaceList);
};
juce_ImplementSingleton_SingleThreaded (FTTypefaceList)
//==============================================================================
class FreeTypeTypeface : public CustomTypeface
{
public:
FreeTypeTypeface (const Font& font)
: faceWrapper (FTTypefaceList::getInstance()
->createFace (font.getTypefaceName(), font.getTypefaceStyle()))
{
if (faceWrapper != nullptr)
{
setCharacteristics (font.getTypefaceName(),
font.getTypefaceStyle(),
faceWrapper->face->ascender / (float) (faceWrapper->face->ascender - faceWrapper->face->descender),
L' ');
}
else
{
DBG ("Failed to create typeface: " << font.toString());
}
}
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()
{
return FTTypefaceList::getInstance()->findAllFamilyNames();
}
StringArray Font::findAllTypefaceStyles (const String& family)
{
return FTTypefaceList::getInstance()->findAllTypefaceStyles (family);
}
//==============================================================================
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);
for (int j = 0; j < choices.size(); ++j)
if (names.contains (choices[j], true))
return choices[j];
for (int j = 0; j < choices.size(); ++j)
for (int i = 0; i < names.size(); ++i)
if (names[i].startsWithIgnoreCase (choices[j]))
return names[i];
for (int 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",
"Liberation Sans", "DejaVu 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",
"Liberation Serif", "DejaVu Serif", "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",
"Liberation Mono", "DejaVu 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);
}
bool TextLayout::createNativeLayout (const AttributedString&)
{
return false;
}