文章目录
- 逻辑字体类
- 头文件
- 实现文件
- 使用文件主程序
- CMakeLists文件
- 脚本文件
逻辑字体类
#ifndef LOGICAL_FONT_H
#define LOGICAL_FONT_H#include <string>
#include <memory>
#include <CoreText/CoreText.h>
#include <CoreFoundation/CoreFoundation.h>class LogicalFont {
public:LogicalFont() = default;~LogicalFont() {if (character_set_) {CFRelease(character_set_);}}const std::string& en_family_name() const { return family_name_; }const std::string& en_face_name() const { return style_name_; }const std::string& en_postscript_name() const { return postscript_name_; }const std::string& localized_family_name() const { return localized_family_name_; }const std::string& localized_style_name() const { return localized_style_name_; }const std::string& localized_postscript_name() const { return localized_postscript_name_; }const std::string& full_name() const { return full_name_; }const std::string& font_path() const { return font_path_; }void set_family_name(const std::string& name) { family_name_ = name; }void set_style_name(const std::string& name) { style_name_ = name; }void set_postscript_name(const std::string& name) { postscript_name_ = name; }void set_localized_family_name(const std::string& name) { localized_family_name_ = name; }void set_localized_style_name(const std::string& name) { localized_style_name_ = name; }void set_localized_postscript_name(const std::string& name) { localized_postscript_name_ = name; }void set_full_name(const std::string& name) { full_name_ = name; }void set_font_path(const std::string& path) { font_path_ = path; }int32_t ascent() const { return ascent_; }int32_t descent() const { return descent_; }int32_t line_gap() const { return line_gap_; }int32_t cap_height() const { return cap_height_; }int32_t x_height() const { return x_height_; }int32_t underline_position() const { return underline_position_; }int32_t underline_thickness() const { return underline_thickness_; }int32_t strikethrough_position() const { return strikethrough_position_; }int32_t strikethrough_thickness() const { return strikethrough_thickness_; }int32_t slant_angle() const { return slant_angle_; }float weight() const { return weight_; }float width() const { return width_; }void set_ascent(int32_t value) { ascent_ = value; }void set_descent(int32_t value) { descent_ = value; }void set_line_gap(int32_t value) { line_gap_ = value; }void set_cap_height(int32_t value) { cap_height_ = value; }void set_x_height(int32_t value) { x_height_ = value; }void set_underline_position(int32_t value) { underline_position_ = value; }void set_underline_thickness(int32_t value) { underline_thickness_ = value; }void set_strikethrough_position(int32_t value) { strikethrough_position_ = value; }void set_strikethrough_thickness(int32_t value) { strikethrough_thickness_ = value; }void set_slant_angle(int32_t value) { slant_angle_ = value; }void set_weight(float value) { weight_ = value; }void set_width(float value) { width_ = value; }bool bold() const { return bold_; }bool italic() const { return italic_; }void set_bold(bool value) { bold_ = value; }void set_italic(bool value) { italic_ = value; }CFCharacterSetRef character_set() const { return character_set_; }void set_character_set(CFCharacterSetRef charset) {if (character_set_) {CFRelease(character_set_);}character_set_ = (CFCharacterSetRef)CFRetain(charset);}CTFontRef logical_font() const { return logical_font_; }void set_logical_font(CTFontRef font) { logical_font_ = font; }private:std::string family_name_; std::string style_name_; std::string postscript_name_; std::string localized_family_name_; std::string localized_style_name_; std::string localized_postscript_name_; std::string full_name_; std::string font_path_; int32_t ascent_ = 0;int32_t descent_ = 0;int32_t line_gap_ = 0;int32_t cap_height_ = 0;int32_t x_height_ = 0;int32_t underline_position_ = 0;int32_t underline_thickness_ = 0;int32_t strikethrough_position_ = 0;int32_t strikethrough_thickness_ = 0;int32_t slant_angle_ = 0;float weight_ = 0.0f;float width_ = 0.0f;bool bold_ = false;bool italic_ = false;CFCharacterSetRef character_set_ = nullptr;CTFontRef logical_font_ = nullptr;
};#endif
头文件
#pragma once
#include <memory>
#include <CoreText/CoreText.h>
#include <type_traits>
#include <CoreText/CoreText.h>
#include <CoreGraphics/CoreGraphics.h>
#include <iostream>#include<memory>
#include <CoreText/CoreText.h>#include <iostream>
#include <fstream>
#include <map>
#include <string>
#include<unordered_set>
#include "LogicalFont.hpp"namespace FontUtils {std::string CFStringToStdString(CFStringRef cfStr);struct UnicodeRange {uint32_t start; uint32_t end; };std::string CFURLToPath(CFURLRef url);std::string NormalizeString(std::string input);} class FontManagerMac
{
public:FontManagerMac() {};~FontManagerMac() {};void EnumerateSystemFonts();private:void ProcessFontFamilies(CFArrayRef fontFamilies);void ProcessSingleFontFamily(CFStringRef familyName);void ProcessFontDescriptors(CFArrayRef fontDescriptors, CFStringRef familyName);void ProcessSingleFont(CTFontDescriptorRef fontDescriptor, CFStringRef familyName, CFIndex index);void StoreFontInfo(const std::shared_ptr<LogicalFont>& log_font);bool GetFontInfo(std::shared_ptr<LogicalFont>& font, CTFontRef ctFont);bool GetExtendedFontMetrics(std::shared_ptr<LogicalFont> &font, CTFontRef ctf_font);std::vector<FontUtils::UnicodeRange> GetSupportedUnicodeRanges(CTFontRef font);public: std::unordered_map<std::string, std::shared_ptr<LogicalFont>> postscript_to_font_;std::unordered_map<std::string, std::shared_ptr<LogicalFont>> local_postscript_to_font_;std::unordered_map<std::string, std::shared_ptr<LogicalFont>> full_to_font_;std::unordered_set<std::string> family_set_;std::unordered_set<std::string> postscript_set_;std::unordered_set<std::string> local_family_set_;std::unordered_set<std::string> local_postscript_set_;std::unordered_map<std::string, std::string> postscript_to_family_;std::unordered_map<std::string, std::string> en_to_local_family_;std::unordered_map<std::string, std::string> local_to_en_ps_;std::map<std::string, std::vector<FontUtils::UnicodeRange>> font_unicode_ranges_map_ ; void PrintAllFontData() const {std::cout << "\n========== Font Container Data ==========\n";PrintFontMaps();PrintNameSets();PrintNameMappings();std::cout << "========================================\n";}
private:void PrintFontMaps() const {std::cout << "\n[Font Object Mappings]\n";PrintMap("PostScript to Font", postscript_to_font_);PrintMap("Localized PostScript to Font", local_postscript_to_font_);PrintMap("Full Name to Font", full_to_font_);}void PrintNameSets() const {std::cout << "\n[Name Sets]\n";PrintSet("Family Names", family_set_);PrintSet("PostScript Names", postscript_set_);PrintSet("Localized Family Names", local_family_set_);PrintSet("Localized PostScript Names", local_postscript_set_);}void PrintNameMappings() const {std::cout << "\n[Name Mappings]\n";PrintStringMap("PostScript to Family", postscript_to_family_);PrintStringMap("English to Local Family", en_to_local_family_);PrintStringMap("Local to English PostScript", local_to_en_ps_);}void PrintUnicodeRanges() const {std::cout << "\n[Unicode Ranges by Font]\n";for (const auto& [fontName, ranges] : font_unicode_ranges_map_) {std::cout << " " << fontName << ":\n";for (const auto& range : ranges) {std::cout << " U+" << std::hex << std::uppercase << std::setw(4) << std::setfill('0') << range.start << "-U+" << std::setw(4) << range.end << std::dec << "\n";}}}template<typename T>void PrintMap(const std::string& title, const std::unordered_map<std::string, std::shared_ptr<T>>& map) const {std::cout << " " << title << " (" << map.size() << " items):\n";for (const auto& [key, value] : map) {std::cout << " " << std::setw(40) << std::left << key << " -> " << (value ? value->en_postscript_name() : "nullptr") << "\n";}}void PrintSet(const std::string& title, const std::unordered_set<std::string>& set) const {std::cout << " " << title << " (" << set.size() << " items):\n ";size_t count = 0;for (const auto& item : set) {std::cout << item;if (++count % 5 == 0 && count != set.size()) std::cout << "\n ";else if (count != set.size()) std::cout << ", ";}std::cout << "\n";}void PrintStringMap(const std::string& title, const std::unordered_map<std::string, std::string>& map) const {std::cout << " " << title << " (" << map.size() << " items):\n";for (const auto& [key, value] : map) {std::cout << " " << std::setw(40) << std::left << key << " -> " << value << "\n";}}
};
实现文件
#include "mac_font_traversal.h"
using std::shared_ptr;namespace FontUtils {std::string CFStringToStdString(CFStringRef cfStr) {if (!cfStr) return "";CFIndex length = CFStringGetLength(cfStr);CFIndex maxSize = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1;std::unique_ptr<char[]> buffer(new char[maxSize]);if (CFStringGetCString(cfStr, buffer.get(), maxSize, kCFStringEncodingUTF8)) {return std::string(buffer.get());}return "";}std::string CFURLToPath(CFURLRef url) {if (!url) {return {}; }char path[PATH_MAX];if (!CFURLGetFileSystemRepresentation(url, true, (UInt8*)path, PATH_MAX)) {return {}; }return std::string(path); }std::string NormalizeString(std::string input) {std::string result;result.reserve(input.size());for (const unsigned char ch : input) {if (ch != ' ' && ch != '-' && ch != '_' && ch != ',' && !std::isspace(ch)) {result += std::tolower(ch);}}return result;}}
std::vector<FontUtils::UnicodeRange> FontManagerMac::GetSupportedUnicodeRanges(CTFontRef font) {std::vector<FontUtils::UnicodeRange> tempRanges; if (!font) { return tempRanges; }CFCharacterSetRef charset = CTFontCopyCharacterSet(font); CFDataRef bitmapData = CFCharacterSetCreateBitmapRepresentation(kCFAllocatorDefault, charset);const UInt8 *bitmap = CFDataGetBytePtr(bitmapData); CFIndex length = CFDataGetLength(bitmapData); bool inRange = false; uint32_t start = 0; uint32_t maxChar = 0; for (uint32_t byteIndex = 0; byteIndex < length; byteIndex++) {UInt8 byte = bitmap[byteIndex]; if (byte == 0) { if (inRange) { tempRanges.push_back({start, (byteIndex << 3) - 1});inRange = false;}continue; }for (uint32_t bit = 0; bit < 8; bit++) {uint32_t currentChar = (byteIndex << 3) + bit; bool isSupported = (byte & (1 << bit)) != 0; if (isSupported) { if (!inRange) { start = currentChar;inRange = true;}maxChar = currentChar; } else if (inRange) { tempRanges.push_back({start, currentChar - 1}); inRange = false;}}}if (inRange) {tempRanges.push_back({start, maxChar});}if (!tempRanges.empty()) {size_t compressedCount = 0; for (size_t i = 1; i < tempRanges.size(); i++) {if (tempRanges[i].start <= tempRanges[compressedCount].end + 1) {if (tempRanges[i].end > tempRanges[compressedCount].end) {tempRanges[compressedCount].end = tempRanges[i].end;}} else {compressedCount++;tempRanges[compressedCount] = tempRanges[i];}}tempRanges.resize(compressedCount + 1);}CFRelease(bitmapData);CFRelease(charset);return tempRanges;
}
void FontManagerMac::StoreFontInfo(const std::shared_ptr<LogicalFont>& font) {const auto norm_face_name = FontUtils::NormalizeString(font->en_face_name());const auto norm_family_name = FontUtils::NormalizeString(font->en_family_name());const auto norm_ps_name = FontUtils::NormalizeString(font->en_postscript_name());const auto norm_local_ps_name = FontUtils::NormalizeString(font->localized_postscript_name());const auto norm_local_family_name = FontUtils::NormalizeString(font->localized_family_name());const auto norm_full_name = FontUtils::NormalizeString(font->full_name());postscript_to_font_.emplace(norm_ps_name, font);local_postscript_to_font_.emplace(norm_local_ps_name, font);full_to_font_.emplace(norm_full_name, font);family_set_.insert(norm_family_name);postscript_set_.insert(norm_ps_name);postscript_to_family_.emplace(norm_ps_name, norm_family_name);local_family_set_.insert(font->localized_family_name());local_postscript_set_.insert(font->localized_postscript_name());en_to_local_family_.emplace(norm_family_name, norm_local_family_name);local_to_en_ps_.emplace(norm_local_ps_name, norm_ps_name);font_unicode_ranges_map_.try_emplace(norm_family_name,GetSupportedUnicodeRanges(font->logical_font()));}void FontManagerMac::EnumerateSystemFonts() {CFArrayRef fontFamilies = CTFontManagerCopyAvailableFontFamilyNames();if (!fontFamilies) {std::cerr << "Error: Unable to get system font family list" << std::endl;return;}ProcessFontFamilies(fontFamilies);CFRelease(fontFamilies);}void FontManagerMac::ProcessFontFamilies(CFArrayRef fontFamilies) {CFIndex familyCount = CFArrayGetCount(fontFamilies);for (CFIndex i = 0; i < familyCount; ++i) {CFStringRef familyName = (CFStringRef)CFArrayGetValueAtIndex(fontFamilies, i);if (!familyName) {std::cerr << "Warning: Failed to get font family name at index " << i << std::endl;continue;}ProcessSingleFontFamily(familyName);}}void FontManagerMac::ProcessSingleFontFamily(CFStringRef familyName) {CTFontDescriptorRef familyDescriptor = CTFontDescriptorCreateWithNameAndSize(familyName, 0);if (!familyDescriptor) {std::cerr << "Warning: Failed to create descriptor for family "<< FontUtils::CFStringToStdString(familyName) << std::endl;return;}CFArrayRef fontDescriptors = CTFontDescriptorCreateMatchingFontDescriptors(familyDescriptor, NULL);CFRelease(familyDescriptor);if (!fontDescriptors) {std::cerr << "Warning: No fonts found for family "<< FontUtils::CFStringToStdString(familyName) << std::endl;return;}ProcessFontDescriptors(fontDescriptors, familyName);CFRelease(fontDescriptors);}void FontManagerMac::ProcessFontDescriptors(CFArrayRef fontDescriptors, CFStringRef familyName) {CFIndex fontCount = CFArrayGetCount(fontDescriptors);for (CFIndex j = 0; j < fontCount; ++j) {CTFontDescriptorRef fontDescriptor = (CTFontDescriptorRef)CFArrayGetValueAtIndex(fontDescriptors, j);ProcessSingleFont(fontDescriptor, familyName, j);}}void FontManagerMac::ProcessSingleFont(CTFontDescriptorRef fontDescriptor, CFStringRef familyName, CFIndex index) {CTFontRef font = CTFontCreateWithFontDescriptor(fontDescriptor, 0.0, NULL);if (!font) {std::cerr << "Warning: Failed to create font for descriptor " << index<< " in family " << FontUtils::CFStringToStdString(familyName) << std::endl;return;}auto log_font = std::make_shared<LogicalFont>();if (GetFontInfo(log_font, font)) {StoreFontInfo(log_font);}CFRelease(font);}bool FontManagerMac::GetExtendedFontMetrics(std::shared_ptr<LogicalFont>& font, CTFontRef ctf_font) {if (!ctf_font || !font) return false;font->set_ascent(static_cast<int32_t>(CTFontGetAscent(ctf_font)));font->set_descent(static_cast<int32_t>(CTFontGetDescent(ctf_font)));font->set_line_gap(static_cast<int32_t>(CTFontGetLeading(ctf_font)));font->set_cap_height(static_cast<int32_t>(CTFontGetCapHeight(ctf_font)));font->set_x_height(static_cast<int32_t>(CTFontGetXHeight(ctf_font)));font->set_underline_position(static_cast<int32_t>(CTFontGetUnderlinePosition(ctf_font)));font->set_underline_thickness(static_cast<int32_t>(CTFontGetUnderlineThickness(ctf_font)));font->set_strikethrough_position(static_cast<int32_t>(font->strikethrough_position() * 0.5f));font->set_strikethrough_thickness(font->underline_thickness());if (&CTFontGetSlantAngle != NULL) {font->set_slant_angle(static_cast<int32_t>(CTFontGetSlantAngle(ctf_font)));}CGFloat fontWeight = 0.0;CFDictionaryRef fontTraits = CTFontCopyTraits(ctf_font);if (fontTraits) {CFNumberRef weightTrait = (CFNumberRef)CFDictionaryGetValue(fontTraits, kCTFontWeightTrait);if (weightTrait) {CFNumberGetValue(weightTrait, kCFNumberCGFloatType, &fontWeight);fontWeight = (fontWeight - 0.4) / 0.6; }CFRelease(fontTraits);}font->set_weight(fontWeight);CGGlyph glyph;UniChar testChar = 'x';if (CTFontGetGlyphsForCharacters(ctf_font, &testChar, &glyph, 1)) {CGSize advance;CTFontGetAdvancesForGlyphs(ctf_font, kCTFontOrientationHorizontal, &glyph, &advance, 1);font->set_width(advance.width);}return true;
}
bool FontManagerMac::GetFontInfo(std::shared_ptr<LogicalFont>& font, CTFontRef ctf_font) {if (!font || !ctf_font) return false;CTFontDescriptorRef descriptor = CTFontCopyFontDescriptor(ctf_font);if (!descriptor) return false;CFStringRef family = nullptr;CFStringRef styleName = nullptr;
if ((family = static_cast<CFStringRef>(CTFontDescriptorCopyAttribute(descriptor, kCTFontFamilyNameAttribute)))) {font->set_family_name(FontUtils::CFStringToStdString(family));}if ((styleName = static_cast<CFStringRef>(CTFontDescriptorCopyAttribute(descriptor, kCTFontStyleNameAttribute)))) {font->set_style_name(FontUtils::CFStringToStdString(styleName));}if (family && styleName) {font->set_full_name(FontUtils::CFStringToStdString(family) + " " +FontUtils::CFStringToStdString(styleName));}if (CFStringRef localizedFamily = CTFontCopyLocalizedName(ctf_font, kCTFontFamilyNameKey, nullptr)) {font->set_localized_family_name(FontUtils::CFStringToStdString(localizedFamily));CFRelease(localizedFamily);}if (styleName) { if (CFStringRef localizedStyle = CTFontCopyLocalizedName(ctf_font, kCTFontStyleNameKey, nullptr)) {font->set_localized_style_name(FontUtils::CFStringToStdString(localizedStyle));CFRelease(localizedStyle);}}if (CFStringRef psName = CTFontCopyPostScriptName(ctf_font)) {font->set_postscript_name(FontUtils::CFStringToStdString(psName));CFRelease(psName);if (CFStringRef localizedPsName = CTFontCopyLocalizedName(ctf_font, kCTFontPostScriptNameKey, nullptr)) {font->set_localized_postscript_name(FontUtils::CFStringToStdString(localizedPsName));CFRelease(localizedPsName);}}if (CFCharacterSetRef fontChars = CTFontCopyCharacterSet(ctf_font)) {font->set_character_set(fontChars);CFRelease(fontChars);}if (CFURLRef fontURL = static_cast<CFURLRef>(CTFontDescriptorCopyAttribute(descriptor, kCTFontURLAttribute))) {font->set_font_path(FontUtils::CFURLToPath(fontURL));CFRelease(fontURL);}if (family) CFRelease(family);if (styleName) CFRelease(styleName);GetExtendedFontMetrics(font, ctf_font);font->set_logical_font(ctf_font);CFRelease(descriptor);return true;
}
使用文件主程序
#include "mac_font_traversal.h"int main()
{FontManagerMac font_manager;font_manager.EnumerateSystemFonts();font_manager.PrintAllFontData();
}
CMakeLists文件
cmake_minimum_required(VERSION 3.15)
project(FontManagerMac)# 设置C++标准
set(CMAKE_CXX_STANDARD 17)# 查找macOS系统框架
find_library(COREFOUNDATION CoreFoundation REQUIRED)
find_library(CORETEXT CoreText REQUIRED)# 添加库目标
add_executable(FontManagerMac${CMAKE_CURRENT_SOURCE_DIR}/LogicalFont.hpp${CMAKE_CURRENT_SOURCE_DIR}/mac_font_traversal.h${CMAKE_CURRENT_SOURCE_DIR}/mac_font_traversal.cpp${CMAKE_CURRENT_SOURCE_DIR}/main.cpp
)# 链接系统框架
target_link_libraries(FontManagerMac PRIVATE${COREFOUNDATION}${CORETEXT}
)
# 找到并链接 CoreGraphics 和 ImageIO 框架
if(APPLE)find_library(COREGRAPHICS_FRAMEWORK CoreGraphics)find_library(IMAGEIO_FRAMEWORK ImageIO)target_link_libraries(FontManagerMac PRIVATE ${COREGRAPHICS_FRAMEWORK} ${IMAGEIO_FRAMEWORK})
endif()# 包含当前目录
target_include_directories(FontManagerMac PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})# 针对Xcode额外设置
if(CMAKE_GENERATOR STREQUAL "Xcode")set_target_properties(FontManagerMac PROPERTIESXCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC "YES"XCODE_ATTRIBUTE_GCC_INPUT_FILETYPE "sourcecode.cpp.utf-8")
endif()
脚本文件
#!/bin/bash
root_dir=$(pwd)
build() {local arch="$1"local build_type="$2"build_dir=${root_dir}/build/mainmodule_build/MACOSX/${arch}install_dir=${root_dir}/installecho "Build dir: ${build_dir}"if [ -e $build_dir ]; thenrm -rf $build_dirficmake \-DBUILD_SHARED_LIBS:BOOL=TRUE \-DCMAKE_EXPORT_COMPILE_COMMANDS:BOOL=TRUE \-DCMAKE_BUILD_TYPE:STRING=${build_type} \-DCMAKE_INSTALL_PREFIX=${install_dir} \-DCMAKE_OSX_ARCHITECTURES=${arch} \-DCMAKE_OSX_DEPLOYMENT_TARGET=10.13 \-DSYSTEM:STRING=Darwin \-DAFS_TARGET_PLATFORM=mac \-DAFS_BUILD_MODE:STRING=build \-G "Xcode" \-S ${root_dir} \-B ${build_dir}cmake --build ${build_dir} \--config ${build_type} \-j 14 \--
}
build x86_64 Debug