* Copyright 2001-2016, Haiku.
* Distributed under the terms of the MIT License.
*
* Authors:
* DarkWyrm <bpmagic@columbus.rr.com>
* Axel Dörfler, axeld@pinc-software.de
*/
#include "FontManager.h"
#include <new>
#include <Autolock.h>
#include <Directory.h>
#include <Entry.h>
#include <File.h>
#include <FindDirectory.h>
#include <Message.h>
#include <NodeMonitor.h>
#include <Path.h>
#include <String.h>
#include "FontFamily.h"
#include "ServerConfig.h"
#include "ServerFont.h"
#ifdef TRACE_FONT_MANAGER
# define FTRACE(x) printf x
#else
# define FTRACE(x) ;
#endif
FT_Library gFreeTypeLibrary;
FontManager *gFontManager = NULL;
struct FontManager::font_directory {
node_ref directory;
uid_t user;
gid_t group;
uint32 revision;
BObjectList<FontStyle> styles;
bool AlreadyScanned() const { return revision != 0; }
FontStyle* FindStyle(const node_ref& nodeRef) const;
};
struct FontManager::font_mapping {
BString family;
BString style;
entry_ref ref;
};
FontStyle*
FontManager::font_directory::FindStyle(const node_ref& nodeRef) const
{
for (int32 i = styles.CountItems(); i-- > 0;) {
FontStyle* style = styles.ItemAt(i);
if (nodeRef == style->NodeRef())
return style;
}
return NULL;
}
static status_t
set_entry(node_ref& nodeRef, const char* name, BEntry& entry)
{
entry_ref ref;
ref.device = nodeRef.device;
ref.directory = nodeRef.node;
status_t status = ref.set_name(name);
if (status != B_OK)
return status;
return entry.SetTo(&ref);
}
static int
compare_font_families(const FontFamily* a, const FontFamily* b)
{
return strcmp(a->Name(), b->Name());
}
FontManager::FontManager()
: BLooper("Font Manager"),
fDirectories(10, true),
fMappings(10, true),
fFamilies(20),
fDefaultPlainFont(NULL),
fDefaultBoldFont(NULL),
fDefaultFixedFont(NULL),
fScanned(false),
fNextID(0)
{
fInitStatus = FT_Init_FreeType(&gFreeTypeLibrary) == 0 ? B_OK : B_ERROR;
if (fInitStatus == B_OK) {
_AddSystemPaths();
_LoadRecentFontMappings();
fInitStatus = _SetDefaultFonts();
if (fInitStatus == B_OK) {
_PrecacheFontFile(fDefaultPlainFont);
_PrecacheFontFile(fDefaultBoldFont);
}
}
}
FontManager::~FontManager()
{
delete fDefaultPlainFont;
delete fDefaultBoldFont;
delete fDefaultFixedFont;
for (int32 i = fFamilies.CountItems(); i-- > 0;) {
delete fFamilies.ItemAt(i);
}
FT_Done_FreeType(gFreeTypeLibrary);
}
void
FontManager::MessageReceived(BMessage* message)
{
switch (message->what) {
case B_NODE_MONITOR:
{
int32 opcode;
if (message->FindInt32("opcode", &opcode) != B_OK)
return;
switch (opcode) {
case B_ENTRY_CREATED:
{
const char* name;
node_ref nodeRef;
if (message->FindInt32("device", &nodeRef.device) != B_OK
|| message->FindInt64("directory", &nodeRef.node) != B_OK
|| message->FindString("name", &name) != B_OK)
break;
snooze(100000);
BEntry entry;
if (set_entry(nodeRef, name, entry) != B_OK)
break;
if (entry.IsDirectory()) {
_AddPath(entry);
} else {
font_directory* directory = _FindDirectory(nodeRef);
if (directory == NULL) {
break;
}
_AddFont(*directory, entry);
}
break;
}
case B_ENTRY_MOVED:
{
const char* name;
node_ref nodeRef;
uint64 fromNode;
uint64 node;
if (message->FindInt32("device", &nodeRef.device) != B_OK
|| message->FindInt64("to directory", &nodeRef.node) != B_OK
|| message->FindInt64("from directory", (int64 *)&fromNode) != B_OK
|| message->FindInt64("node", (int64 *)&node) != B_OK
|| message->FindString("name", &name) != B_OK)
break;
font_directory* directory = _FindDirectory(nodeRef);
BEntry entry;
if (set_entry(nodeRef, name, entry) != B_OK)
break;
if (directory != NULL) {
nodeRef.node = fromNode;
font_directory* fromDirectory = _FindDirectory(nodeRef);
if (entry.IsDirectory()) {
if (fromDirectory == NULL) {
_AddPath(entry);
FTRACE(("new directory moved in"));
} else {
nodeRef.node = node;
directory = _FindDirectory(nodeRef);
if (directory != NULL) {
for (int32 i = 0; i < directory->styles.CountItems(); i++) {
FontStyle* style = directory->styles.ItemAt(i);
style->UpdatePath(directory->directory);
}
}
FTRACE(("directory renamed"));
}
} else {
if (fromDirectory != NULL) {
nodeRef.node = node;
FontStyle* style = fromDirectory->FindStyle(nodeRef);
if (style != NULL) {
fromDirectory->styles.RemoveItem(style, false);
directory->styles.AddItem(style);
style->UpdatePath(directory->directory);
}
FTRACE(("font moved"));
} else {
FTRACE(("font added: %s\n", name));
_AddFont(*directory, entry);
}
}
} else {
if (entry.IsDirectory()) {
if (entry.GetNodeRef(&nodeRef) == B_OK
&& (directory = _FindDirectory(nodeRef)) != NULL)
_RemoveDirectory(directory);
} else {
_RemoveStyle(nodeRef.device, fromNode, node);
}
}
break;
}
case B_ENTRY_REMOVED:
{
node_ref nodeRef;
uint64 directoryNode;
if (message->FindInt32("device", &nodeRef.device) != B_OK
|| message->FindInt64("directory", (int64 *)&directoryNode) != B_OK
|| message->FindInt64("node", &nodeRef.node) != B_OK)
break;
font_directory* directory = _FindDirectory(nodeRef);
if (directory != NULL) {
_RemoveDirectory(directory);
} else {
_RemoveStyle(nodeRef.device, directoryNode, nodeRef.node);
}
break;
}
}
break;
}
}
}
void
FontManager::SaveRecentFontMappings()
{
}
void
FontManager::_AddDefaultMapping(const char* family, const char* style,
const char* path)
{
font_mapping* mapping = new (std::nothrow) font_mapping;
if (mapping == NULL)
return;
mapping->family = family;
mapping->style = style;
BEntry entry(path);
if (entry.GetRef(&mapping->ref) != B_OK
|| !entry.Exists()
|| !fMappings.AddItem(mapping))
delete mapping;
}
bool
FontManager::_LoadRecentFontMappings()
{
BPath ttfontsPath;
if (find_directory(B_BEOS_FONTS_DIRECTORY, &ttfontsPath) == B_OK) {
ttfontsPath.Append("ttfonts");
BPath veraFontPath = ttfontsPath;
veraFontPath.Append("NotoSans-Regular.ttf");
_AddDefaultMapping("Noto Sans", "Book", veraFontPath.Path());
veraFontPath.SetTo(ttfontsPath.Path());
veraFontPath.Append("NotoSans-Bold.ttf");
_AddDefaultMapping("Noto Sans", "Bold", veraFontPath.Path());
veraFontPath.SetTo(ttfontsPath.Path());
veraFontPath.Append("NotoMono-Regular.ttf");
_AddDefaultMapping("Noto Mono", "Regular", veraFontPath.Path());
return true;
}
return false;
}
status_t
FontManager::_AddMappedFont(const char* familyName, const char* styleName)
{
FTRACE(("_AddMappedFont(family = \"%s\", style = \"%s\")\n",
familyName, styleName));
for (int32 i = 0; i < fMappings.CountItems(); i++) {
font_mapping* mapping = fMappings.ItemAt(i);
if (mapping->family == familyName) {
if (styleName != NULL && mapping->style != styleName)
continue;
BEntry entry(&mapping->ref);
if (entry.InitCheck() != B_OK)
continue;
node_ref nodeRef;
nodeRef.device = mapping->ref.device;
nodeRef.node = mapping->ref.directory;
font_directory* directory = _FindDirectory(nodeRef);
if (directory == NULL) {
BPath path(&entry);
if (path.GetParent(&path) != B_OK
|| _CreateDirectories(path.Path()) != B_OK
|| (directory = _FindDirectory(nodeRef)) == NULL)
continue;
}
return _AddFont(*directory, entry);
}
}
return B_ENTRY_NOT_FOUND;
}
It doesn't necessary delete the font style, if it's still
in use, though.
*/
void
FontManager::_RemoveStyle(font_directory& directory, FontStyle* style)
{
FTRACE(("font removed: %s\n", style->Name()));
directory.styles.RemoveItem(style);
directory.revision++;
fStyleHashTable.RemoveItem(*style);
style->Release();
}
void
FontManager::_RemoveStyle(dev_t device, uint64 directoryNode, uint64 node)
{
node_ref nodeRef;
nodeRef.device = device;
nodeRef.node = directoryNode;
font_directory* directory = _FindDirectory(nodeRef);
if (directory != NULL) {
nodeRef.node = node;
FontStyle* style = directory->FindStyle(nodeRef);
if (style != NULL)
_RemoveStyle(*directory, style);
}
}
FontStyle*
FontManager::_GetDefaultStyle(const char *familyName, const char *styleName,
const char *fallbackFamily, const char *fallbackStyle,
uint16 fallbackFace)
{
FontStyle* style = GetStyle(familyName, styleName);
if (style == NULL) {
style = GetStyle(fallbackFamily, fallbackStyle);
if (style == NULL) {
style = FindStyleMatchingFace(fallbackFace);
if (style == NULL && FamilyAt(0) != NULL)
style = FamilyAt(0)->StyleAt(0);
}
}
return style;
}
ServerFont without specifying a style, as well as the default
Desktop fonts if there are no settings available.
*/
status_t
FontManager::_SetDefaultFonts()
{
FontStyle* style = _GetDefaultStyle(DEFAULT_PLAIN_FONT_FAMILY,
DEFAULT_PLAIN_FONT_STYLE, FALLBACK_PLAIN_FONT_FAMILY,
DEFAULT_PLAIN_FONT_STYLE,
B_REGULAR_FACE);
if (style == NULL)
return B_ERROR;
fDefaultPlainFont = new (std::nothrow) ServerFont(*style,
DEFAULT_PLAIN_FONT_SIZE);
if (fDefaultPlainFont == NULL)
return B_NO_MEMORY;
style = _GetDefaultStyle(DEFAULT_BOLD_FONT_FAMILY, DEFAULT_BOLD_FONT_STYLE,
FALLBACK_BOLD_FONT_FAMILY, DEFAULT_BOLD_FONT_STYLE, B_BOLD_FACE);
fDefaultBoldFont = new (std::nothrow) ServerFont(*style,
DEFAULT_BOLD_FONT_SIZE);
if (fDefaultBoldFont == NULL)
return B_NO_MEMORY;
style = _GetDefaultStyle(DEFAULT_FIXED_FONT_FAMILY, DEFAULT_FIXED_FONT_STYLE,
FALLBACK_FIXED_FONT_FAMILY, DEFAULT_FIXED_FONT_STYLE, B_REGULAR_FACE);
fDefaultFixedFont = new (std::nothrow) ServerFont(*style,
DEFAULT_FIXED_FONT_SIZE);
if (fDefaultFixedFont == NULL)
return B_NO_MEMORY;
fDefaultFixedFont->SetSpacing(B_FIXED_SPACING);
return B_OK;
}
void
FontManager::_PrecacheFontFile(const ServerFont* font)
{
if (font == NULL)
return;
size_t bufferSize = 32768;
uint8* buffer = new (std::nothrow) uint8[bufferSize];
if (buffer == NULL) {
return;
}
BFile file(font->Path(), B_READ_ONLY);
if (file.InitCheck() != B_OK) {
delete[] buffer;
return;
}
while (true) {
ssize_t read = file.Read(buffer, bufferSize);
if (read < (ssize_t)bufferSize)
break;
}
delete[] buffer;
}
void
FontManager::_AddSystemPaths()
{
BPath path;
if (find_directory(B_SYSTEM_FONTS_DIRECTORY, &path, true) == B_OK)
_AddPath(path.Path());
#if !TEST_MODE
if (find_directory(B_SYSTEM_NONPACKAGED_FONTS_DIRECTORY, &path, true) == B_OK)
_AddPath(path.Path());
#endif
}
void
FontManager::_ScanFontsIfNecessary()
{
if (!fScanned)
_ScanFonts();
}
void
FontManager::_ScanFonts()
{
if (fScanned)
return;
for (int32 i = fDirectories.CountItems(); i-- > 0;) {
font_directory* directory = fDirectories.ItemAt(i);
if (directory->AlreadyScanned())
continue;
_ScanFontDirectory(*directory);
}
fScanned = true;
}
*/
status_t
FontManager::_AddFont(font_directory& directory, BEntry& entry)
{
node_ref nodeRef;
status_t status = entry.GetNodeRef(&nodeRef);
if (status < B_OK)
return status;
BPath path;
status = entry.GetPath(&path);
if (status < B_OK)
return status;
FT_Face face;
FT_Error error = FT_New_Face(gFreeTypeLibrary, path.Path(), 0, &face);
if (error != 0)
return B_ERROR;
FontFamily *family = _FindFamily(face->family_name);
if (family != NULL && family->HasStyle(face->style_name)) {
FT_Done_Face(face);
return B_OK;
}
if (family == NULL) {
family = new (std::nothrow) FontFamily(face->family_name, fNextID++);
if (family == NULL
|| !fFamilies.BinaryInsert(family, compare_font_families)) {
delete family;
FT_Done_Face(face);
return B_NO_MEMORY;
}
}
FTRACE(("\tadd style: %s, %s\n", face->family_name, face->style_name));
FontStyle *style = new (std::nothrow) FontStyle(nodeRef, path.Path(), face);
if (style == NULL || !family->AddStyle(style)) {
delete style;
delete family;
return B_NO_MEMORY;
}
directory.styles.AddItem(style);
fStyleHashTable.AddItem(style);
if (directory.AlreadyScanned())
directory.revision++;
return B_OK;
}
FontManager::font_directory*
FontManager::_FindDirectory(node_ref& nodeRef)
{
for (int32 i = fDirectories.CountItems(); i-- > 0;) {
font_directory* directory = fDirectories.ItemAt(i);
if (directory->directory == nodeRef)
return directory;
}
return NULL;
}
void
FontManager::_RemoveDirectory(font_directory* directory)
{
FTRACE(("FontManager: Remove directory (%" B_PRIdINO ")!\n",
directory->directory.node));
fDirectories.RemoveItem(directory, false);
watch_node(&directory->directory, B_STOP_WATCHING, this);
delete directory;
}
status_t
FontManager::_AddPath(const char* path)
{
BEntry entry;
status_t status = entry.SetTo(path);
if (status != B_OK)
return status;
return _AddPath(entry);
}
status_t
FontManager::_AddPath(BEntry& entry, font_directory** _newDirectory)
{
node_ref nodeRef;
status_t status = entry.GetNodeRef(&nodeRef);
if (status != B_OK)
return status;
font_directory* directory = _FindDirectory(nodeRef);
if (directory != NULL) {
if (_newDirectory)
*_newDirectory = directory;
return B_OK;
}
directory = new (std::nothrow) font_directory;
if (directory == NULL)
return B_NO_MEMORY;
struct stat stat;
status = entry.GetStat(&stat);
if (status != B_OK) {
delete directory;
return status;
}
directory->directory = nodeRef;
directory->user = stat.st_uid;
directory->group = stat.st_gid;
directory->revision = 0;
status = watch_node(&nodeRef, B_WATCH_DIRECTORY, this);
if (status != B_OK) {
printf("could not watch directory %" B_PRIdDEV ":%" B_PRIdINO "\n",
nodeRef.device, nodeRef.node);
} else {
BPath path(&entry);
FTRACE(("FontManager: now watching: %s\n", path.Path()));
}
fDirectories.AddItem(directory);
if (_newDirectory)
*_newDirectory = directory;
fScanned = false;
return B_OK;
}
only if one of its parent directories is already known.
This method is used to create the font_directories for font_mappings.
It recursively walks upwards in the directory hierarchy until it finds
a known font_directory (or hits the root directory, in which case it
bails out).
*/
status_t
FontManager::_CreateDirectories(const char* path)
{
FTRACE(("_CreateDirectories(path = %s)\n", path));
if (!strcmp(path, "/")) {
return B_ENTRY_NOT_FOUND;
}
BEntry entry;
status_t status = entry.SetTo(path);
if (status != B_OK)
return status;
node_ref nodeRef;
status = entry.GetNodeRef(&nodeRef);
if (status != B_OK)
return status;
font_directory* directory = _FindDirectory(nodeRef);
if (directory != NULL)
return B_OK;
BPath parent(path);
status = parent.GetParent(&parent);
if (status != B_OK)
return status;
status = _CreateDirectories(parent.Path());
if (status != B_OK)
return status;
return _AddPath(path);
}
\param directoryPath Path of the folder to scan.
*/
status_t
FontManager::_ScanFontDirectory(font_directory& fontDirectory)
{
BDirectory directory;
status_t status = directory.SetTo(&fontDirectory.directory);
if (status != B_OK)
return status;
BEntry entry;
while (directory.GetNextEntry(&entry) == B_OK) {
if (entry.IsDirectory()) {
font_directory* newDirectory;
if (_AddPath(entry, &newDirectory) == B_OK && newDirectory != NULL)
_ScanFontDirectory(*newDirectory);
continue;
}
#if 0
FT_CharMap charmap = _GetSupportedCharmap(face);
if (!charmap) {
FT_Done_Face(face);
continue;
}
face->charmap = charmap;
#endif
_AddFont(fontDirectory, entry);
}
fontDirectory.revision = 1;
return B_OK;
}
\param face Font handle obtained from FT_Load_Face()
\return An FT_CharMap or NULL if unsuccessful
*/
FT_CharMap
FontManager::_GetSupportedCharmap(const FT_Face& face)
{
for (int32 i = 0; i < face->num_charmaps; i++) {
FT_CharMap charmap = face->charmaps[i];
switch (charmap->platform_id) {
case 3:
if (charmap->encoding_id == 0 || charmap->encoding_id == 1)
return charmap;
break;
case 1:
if (charmap->encoding_id == 0)
return charmap;
break;
case 0:
if (charmap->encoding_id == 0)
return charmap;
break;
default:
break;
}
}
return NULL;
}
int32
FontManager::CheckRevision(uid_t user)
{
BAutolock locker(this);
int32 revision = 0;
_ScanFontsIfNecessary();
for (int32 i = 0; i < fDirectories.CountItems(); i++) {
font_directory* directory = fDirectories.ItemAt(i);
revision += directory->revision;
}
return revision;
}
\return The number of unique font families currently available
*/
int32
FontManager::CountFamilies()
{
_ScanFontsIfNecessary();
return fFamilies.CountItems();
}
\param family Name of the font family to scan
\return The number of font styles currently available for the font family
*/
int32
FontManager::CountStyles(const char *familyName)
{
_ScanFontsIfNecessary();
FontFamily *family = GetFamily(familyName);
if (family)
return family->CountStyles();
return 0;
}
\param family Name of the font family to scan
\return The number of font styles currently available for the font family
*/
int32
FontManager::CountStyles(uint16 familyID)
{
_ScanFontsIfNecessary();
FontFamily *family = GetFamily(familyID);
if (family)
return family->CountStyles();
return 0;
}
FontFamily*
FontManager::FamilyAt(int32 index) const
{
return fFamilies.ItemAt(index);
}
FontFamily*
FontManager::_FindFamily(const char* name) const
{
if (name == NULL)
return NULL;
FontFamily family(name, 0);
return const_cast<FontFamily*>(fFamilies.BinarySearch(family,
compare_font_families));
}
\param name The family to find
\return Pointer to the specified family or NULL if not found.
*/
FontFamily*
FontManager::GetFamily(const char* name)
{
if (name == NULL)
return NULL;
FontFamily* family = _FindFamily(name);
if (family != NULL)
return family;
if (fScanned)
return NULL;
if (_AddMappedFont(name) == B_OK)
return _FindFamily(name);
_ScanFonts();
return _FindFamily(name);
}
FontFamily*
FontManager::GetFamily(uint16 familyID) const
{
FontKey key(familyID, 0);
FontStyle* style = (FontStyle*)fStyleHashTable.GetValue(key);
if (style != NULL)
return style->Family();
return NULL;
}
FontStyle*
FontManager::GetStyleByIndex(const char* familyName, int32 index)
{
FontFamily* family = GetFamily(familyName);
if (family != NULL)
return family->StyleAt(index);
return NULL;
}
FontStyle*
FontManager::GetStyleByIndex(uint16 familyID, int32 index)
{
FontFamily* family = GetFamily(familyID);
if (family != NULL)
return family->StyleAt(index);
return NULL;
}
specified.
\param family The font's family or NULL in which case \a familyID is used
\param style The font's style or NULL in which case \a styleID is used
\param familyID will only be used if \a family is NULL (or empty)
\param styleID will only be used if \a style is NULL (or empty)
\param face is used to specify the style if both \a style is NULL or empty
and styleID is 0xffff.
\return The FontStyle having those attributes or NULL if not available
*/
FontStyle*
FontManager::GetStyle(const char* familyName, const char* styleName,
uint16 familyID, uint16 styleID, uint16 face)
{
FontFamily* family;
if (familyName != NULL && familyName[0])
family = GetFamily(familyName);
else
family = GetFamily(familyID);
if (family == NULL)
return NULL;
if (styleName != NULL && styleName[0]) {
FontStyle* fontStyle = family->GetStyle(styleName);
if (fontStyle != NULL)
return fontStyle;
if (_AddMappedFont(family->Name(), styleName) == B_OK) {
fontStyle = family->GetStyle(styleName);
if (fontStyle != NULL)
return fontStyle;
}
_ScanFonts();
return family->GetStyle(styleName);
}
if (styleID != 0xffff)
return family->GetStyleByID(styleID);
return family->GetStyleMatchingFace(face);
}
\param family ID for the font's family
\param style ID of the font's style
\return The FontStyle having those attributes or NULL if not available
*/
FontStyle*
FontManager::GetStyle(uint16 familyID, uint16 styleID) const
{
FontKey key(familyID, styleID);
return (FontStyle*)fStyleHashTable.GetValue(key);
}
to have one fitting your needs, you may want to use this method.
*/
FontStyle*
FontManager::FindStyleMatchingFace(uint16 face) const
{
int32 count = fFamilies.CountItems();
for (int32 i = 0; i < count; i++) {
FontFamily* family = fFamilies.ItemAt(i);
FontStyle* style = family->GetStyleMatchingFace(face);
if (style != NULL)
return style;
}
return NULL;
}
only - to remove itself from the font manager.
At this point, the style is already no longer available to the user.
*/
void
FontManager::RemoveStyle(FontStyle* style)
{
FontFamily* family = style->Family();
if (family == NULL)
debugger("family is NULL!");
FontStyle* check = GetStyle(family->ID(), style->ID());
if (check != NULL)
debugger("style removed but still available!");
if (family->RemoveStyle(style)
&& family->CountStyles() == 0)
fFamilies.RemoveItem(family);
}
const ServerFont*
FontManager::DefaultPlainFont() const
{
return fDefaultPlainFont;
}
const ServerFont*
FontManager::DefaultBoldFont() const
{
return fDefaultBoldFont;
}
const ServerFont*
FontManager::DefaultFixedFont() const
{
return fDefaultFixedFont;
}
void
FontManager::AttachUser(uid_t userID)
{
BAutolock locker(this);
#if !TEST_MODE
BPath path;
if (find_directory(B_USER_FONTS_DIRECTORY, &path, true) == B_OK)
_AddPath(path.Path());
if (find_directory(B_USER_NONPACKAGED_FONTS_DIRECTORY, &path, true)
== B_OK) {
_AddPath(path.Path());
}
#endif
}
void
FontManager::DetachUser(uid_t userID)
{
BAutolock locker(this);
}