// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen // SPDX-License-Identifier: BSD-3-Clause #include "vtkFontConfigFreeTypeTools.h" #include "vtkMath.h" #include "vtkObjectFactory.h" #include "vtkSmartPointer.h" #include "vtkTextProperty.h" #include #include VTK_ABI_NAMESPACE_BEGIN vtkStandardNewMacro(vtkFontConfigFreeTypeTools); namespace { // The FreeType face requester callback: FT_Error vtkFontConfigFreeTypeToolsFaceRequester( FTC_FaceID face_id, FT_Library lib, FT_Pointer request_data, FT_Face* face) { // Get a pointer to the current vtkFontConfigFreeTypeTools object vtkFontConfigFreeTypeTools* self = reinterpret_cast(request_data); // Map the ID to a text property vtkSmartPointer tprop = vtkSmartPointer::New(); self->MapIdToTextProperty(reinterpret_cast(face_id), tprop); bool faceIsSet = self->GetForceCompiledFonts() || tprop->GetFontFamily() == VTK_FONT_FILE ? false : vtkFontConfigFreeTypeTools::LookupFaceFontConfig(tprop, lib, face); // Fall back to compiled fonts if lookup fails/compiled fonts are forced: if (!faceIsSet) { faceIsSet = vtkFontConfigFreeTypeTools::Superclass::LookupFace(tprop, lib, face); } if (!faceIsSet) { return static_cast(1); } if (tprop->GetOrientation() != 0.0) { // FreeType documentation says that the transform should not be set // but we cache faces also by transform, so that there is a unique // (face, orientation) cache entry FT_Matrix matrix; float angle = vtkMath::RadiansFromDegrees(tprop->GetOrientation()); matrix.xx = (FT_Fixed)(cos(angle) * 0x10000L); matrix.xy = (FT_Fixed)(-sin(angle) * 0x10000L); matrix.yx = (FT_Fixed)(sin(angle) * 0x10000L); matrix.yy = (FT_Fixed)(cos(angle) * 0x10000L); FT_Set_Transform(*face, &matrix, nullptr); } return static_cast(0); } } // end anon namespace void vtkFontConfigFreeTypeTools::PrintSelf(ostream& os, vtkIndent indent) { this->Superclass::PrintSelf(os, indent); } vtkFontConfigFreeTypeTools::vtkFontConfigFreeTypeTools() = default; vtkFontConfigFreeTypeTools::~vtkFontConfigFreeTypeTools() = default; FT_Error vtkFontConfigFreeTypeTools::CreateFTCManager() { return FTC_Manager_New(*this->GetLibrary(), this->MaximumNumberOfFaces, this->MaximumNumberOfSizes, this->MaximumNumberOfBytes, vtkFontConfigFreeTypeToolsFaceRequester, static_cast(this), this->CacheManager); } bool vtkFontConfigFreeTypeTools::LookupFaceFontConfig( vtkTextProperty* tprop, FT_Library lib, FT_Face* face) { if (!FcInit()) { return false; } // Query tprop const FcChar8* family = reinterpret_cast(tprop->GetFontFamilyAsString()); const double pointSize = static_cast(tprop->GetFontSize()); const int weight = tprop->GetBold() ? FC_WEIGHT_BOLD : FC_WEIGHT_MEDIUM; const int slant = tprop->GetItalic() ? FC_SLANT_ITALIC : FC_SLANT_ROMAN; // Build pattern FcPattern* pattern = FcPatternCreate(); FcPatternAddString(pattern, FC_FAMILY, family); FcPatternAddDouble(pattern, FC_SIZE, pointSize); FcPatternAddInteger(pattern, FC_WEIGHT, weight); FcPatternAddInteger(pattern, FC_SLANT, slant); FcPatternAddBool(pattern, FC_SCALABLE, true); // Prefer fonts that have at least greek characters: FcCharSet* charSet = FcCharSetCreate(); FcCharSetAddChar(charSet, static_cast(948)); // lowercase delta FcPatternAddCharSet(pattern, FC_CHARSET, charSet); // Replace common font names, e.g. arial, times, etc -> sans, serif, etc FcConfigSubstitute(nullptr, pattern, FcMatchPattern); // Fill in any missing defaults: FcDefaultSubstitute(pattern); // Match pattern FcResult result; FcFontSet* fontMatches = FcFontSort(nullptr, pattern, false, nullptr, &result); FcPatternDestroy(pattern); pattern = nullptr; if (!fontMatches || fontMatches->nfont == 0) { if (fontMatches) { FcFontSetDestroy(fontMatches); } return false; } // Grab the first match that is scalable -- even though we've requested // scalable fonts in the match, FC seems to not weigh that option very heavily FcPattern* match = nullptr; for (int i = 0; i < fontMatches->nfont; ++i) { match = fontMatches->fonts[i]; // Ensure that the match is scalable FcBool isScalable; if (FcPatternGetBool(match, FC_SCALABLE, 0, &isScalable) != FcResultMatch || !isScalable) { continue; } FcCharSet* currentFontCharSet; if (FcPatternGetCharSet(match, FC_CHARSET, 0, ¤tFontCharSet) != FcResultMatch || FcCharSetIntersectCount(charSet, currentFontCharSet) == 0) { continue; } break; } if (!match) { FcFontSetDestroy(fontMatches); FcCharSetDestroy(charSet); return false; } // Get filename. Do not free the filename string -- it is owned by FcPattern // "match". Likewise, do not use the filename after match is freed. FcChar8* filename; result = FcPatternGetString(match, FC_FILE, 0, &filename); FT_Error error = FT_New_Face(lib, reinterpret_cast(filename), 0, face); if (!error) { vtkDebugWithObjectMacro(vtkFreeTypeTools::GetInstance(), << "Loading system font: " << reinterpret_cast(filename)); } FcCharSetDestroy(charSet); charSet = nullptr; FcFontSetDestroy(fontMatches); fontMatches = nullptr; return !error; } VTK_ABI_NAMESPACE_END