/* This file is part of Zutty. * Copyright (C) 2020 Tom Szilagyi * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * See the file LICENSE for the full license. */ #include "fontpack.h" #include "log.h" #include //#include // DEBUG #include #include namespace { struct SearchState { // input const char* fontname = nullptr; size_t fontnamelen = 0; // output int level = 0; std::string ext; std::string regular; std::string bold; std::string italic; std::string boldItalic; }; SearchState sstate; int saveCandidate (const char* fpath, const char* ext, int level, const char* variant, std::string& dest) { logT << variant << ": " << fpath << std::endl; if (sstate.ext != "" && sstate.ext != ext) { logT << "Rejecting candidate because its extension: '" << ext << "' does not match the other(s): '" << sstate.ext << "'" << std::endl; return 1; } dest = fpath; sstate.ext = ext; sstate.level = level; return 0; } int fontFileFilter (const char* fpath, const struct stat* sb, int tflag, struct FTW* ftwbuf) { // If we have just emerged from a directory where fonts were found, // time to call it a day. if (tflag == FTW_D && sstate.level > 0 && ftwbuf->level == sstate.level - 1 && sstate.regular.size () > 0) return 1; // In a less ideal case, where some candidates were found in this dir, // but no regular variant, clear partial results and proceed. if (tflag == FTW_D && sstate.level > 0) { logT << "Some candidates found but no regular variant; continuing" << std::endl; sstate.level = 0; sstate.ext = ""; sstate.regular = ""; sstate.bold = ""; sstate.italic = ""; sstate.boldItalic = ""; return 0; } // Filter by file type - only regular files and symlinks if (tflag != FTW_F && tflag != FTW_SL) return 0; // Filter by extension const char* fname = fpath + ftwbuf->base; const char* ext = strrchr (fname, '.'); if (!ext) return 0; if (strcasecmp (ext, ".gz") == 0 && ext > fname) do --ext; while (ext > fname && ext [0] != '.'); if (strcasecmp (ext, ".ttc") != 0 && strcasecmp (ext, ".ttf") != 0 && strcasecmp (ext, ".otf") != 0 && strcasecmp (ext, ".pcf") != 0 && strcasecmp (ext, ".pcf.gz") != 0) return 0; // Filter by font name if (strncasecmp (fname, sstate.fontname, sstate.fontnamelen) != 0) return 0; // At this point we only need to consider the mid part between font name // and extension. This is either matched to one of the face variants, // or we reject the file. const char* mid = fname + sstate.fontnamelen; size_t midlen = ext - mid; // Discard optional hyphen, underscore or space between name and midpart if (midlen > 0 && (mid [0] == '-' || mid [0] == '_' || mid [0] == ' ')) { ++mid; --midlen; } // Apply some simple heuristics to identify font variants if (midlen == 0 || strncasecmp (mid, "R", midlen) == 0 || strncasecmp (mid, "Regular", midlen) == 0) { if (saveCandidate (fpath, ext, ftwbuf->level, "Regular", sstate.regular)) return 0; } else if (strncasecmp (mid, "B", midlen) == 0 || strncasecmp (mid, "Bold", midlen) == 0) { if (saveCandidate (fpath, ext, ftwbuf->level, "Bold", sstate.bold)) return 0; } else if (strncasecmp (mid, "I", midlen) == 0 || strncasecmp (mid, "It", midlen) == 0 || strncasecmp (mid, "Italic", midlen) == 0 || strncasecmp (mid, "O", midlen) == 0 || strncasecmp (mid, "Ob", midlen) == 0 || strncasecmp (mid, "Oblique", midlen) == 0) { if (saveCandidate (fpath, ext, ftwbuf->level, "Italic", sstate.italic)) return 0; } else if (strncasecmp (mid, "BI", midlen) == 0 || strncasecmp (mid, "BoldIt", midlen) == 0 || strncasecmp (mid, "BoldItalic", midlen) == 0) { if (saveCandidate (fpath, ext, ftwbuf->level, "BoldItalic", sstate.boldItalic)) return 0; } #if 0 // TODO remove the below printouts along with the include of stdio.h printf("%-3s %2d ", (tflag == FTW_D) ? "d" : (tflag == FTW_DNR) ? "dnr" : (tflag == FTW_DP) ? "dp" : (tflag == FTW_F) ? "f" : (tflag == FTW_NS) ? "ns" : (tflag == FTW_SL) ? "sl" : (tflag == FTW_SLN) ? "sln" : "???", ftwbuf->level); if (tflag == FTW_NS) printf("-------"); else printf("%7jd", (intmax_t) sb->st_size); printf(" %-40s %d %s\n", fpath, ftwbuf->base, fpath + ftwbuf->base); #endif return 0; } } // namespace namespace zutty { Fontpack::Fontpack (const std::string& fontpath, const std::string& fontname, const std::string& dwfontname) { logT << "Fontpack: fontpath=" << fontpath << "; fontname=" << fontname << "; dwfontname=" << dwfontname << std::endl; // Look for & initialize the regular font (with variants) sstate.fontname = fontname.data (); sstate.fontnamelen = fontname.size (); size_t pos = 0; size_t nextpos = 0; do { nextpos = fontpath.find (':', pos); size_t len = (nextpos == std::string::npos) ? std::string::npos : nextpos - pos; std::string fontpath1 = fontpath.substr (pos, len); logT << "Looking for candidates under " << fontpath1 << std::endl; pos = nextpos + 1; int flags = FTW_DEPTH; if (nftw (fontpath1.c_str (), fontFileFilter, 32, flags) == -1) { SYS_WARN ("Cannot walk file tree at ", fontpath1); } } while (!sstate.regular.size () && nextpos != std::string::npos); if (! sstate.regular.size ()) { logE << "No Regular variant of the requested font '" << fontname << "' could be identified." << std::endl; throw std::runtime_error (std::string ("No suitable files for '") + fontname + "' found!"); } fontRegular = std::make_unique (sstate.regular); px = fontRegular->getPx (); py = fontRegular->getPy (); try { if (sstate.bold.size ()) fontBold = std::make_unique ( sstate.bold, * fontRegular.get (), Font::Overlay); } catch (const std::runtime_error& e) { fontBold = nullptr; logW << "Failed to load bold variant: " << e.what () << std::endl; } try { if (sstate.italic.size ()) fontItalic = std::make_unique ( sstate.italic, * fontRegular.get (), Font::Overlay); } catch (const std::runtime_error& e) { fontItalic = nullptr; logW << "Failed to load italic variant: " << e.what () << std::endl; } try { if (sstate.boldItalic.size ()) fontBoldItalic = std::make_unique ( sstate.boldItalic, * fontRegular.get (), Font::Overlay); } catch (const std::runtime_error& e) { fontBoldItalic = nullptr; logW << "Failed to load boldItalic variant: " << e.what () << std::endl; } // Initialize the underline metrics ulMetrics [0] = getRegular().getUlTop (); ulMetrics [1] = getRegular().getUlThick (); ulMetrics [2] = getBold().getUlTop (); ulMetrics [3] = getBold().getUlThick (); ulMetrics [4] = getItalic().getUlTop (); ulMetrics [5] = getItalic().getUlThick (); ulMetrics [6] = getBoldItalic().getUlTop (); ulMetrics [7] = getBoldItalic().getUlThick (); // Look for & initialize the double-width font sstate.level = 0; sstate.ext = ""; sstate.regular = ""; sstate.bold = ""; sstate.italic = ""; sstate.boldItalic = ""; sstate.fontname = dwfontname.data (); sstate.fontnamelen = dwfontname.size (); pos = 0; nextpos = 0; do { nextpos = fontpath.find (':', pos); size_t len = (nextpos == std::string::npos) ? std::string::npos : nextpos - pos; std::string fontpath1 = fontpath.substr (pos, len); logT << "Looking for double-width candidates under " << fontpath1 << std::endl; pos = nextpos + 1; int flags = FTW_DEPTH; if (nftw (fontpath1.c_str (), fontFileFilter, 32, flags) == -1) { SYS_WARN ("Cannot walk file tree at ", fontpath1); } } while (!sstate.regular.size () && nextpos != std::string::npos); try { if (sstate.regular.size ()) fontDoubleWidth = std::make_unique ( sstate.regular, * fontRegular.get (), Font::DoubleWidth); else if (dwfontname != "") { logW << "Failed to locate requested double-width font: " << dwfontname << std::endl; } } catch (const std::runtime_error& e) { fontDoubleWidth = nullptr; logW << "Failed to load double-width font: " << e.what () << std::endl; } } } // namespace zutty