「macOS 系统字体收集器 (C++17 实现)」

发布于:2025-07-10 ⋅ 阅读:(17) ⋅ 点赞:(0)

代码介绍

1. 核心功能

这是一个专为 macOS 设计的字体文件收集工具,能够:
✅ 智能扫描系统字体目录(如 /Library/Fonts)和用户指定目录
✅ 自动过滤 .ttf/.otf/.ttc 等字体文件
✅ 安全跳过无权限访问的目录(兼容 SIP 保护机制)
✅ 冲突处理自动重命名同名字体文件(追加 _1, _2 等后缀)
✅ 完整日志记录所有跳过的目录及原因

2. 技术亮点

🔧 现代 C++17 标准实现,跨平台兼容
🔧 使用 库实现高效目录遍历
🔧 多线程安全的错误记录机制(std::mutex)
🔧 双重访问策略:
• 优先尝试直接递归访问

• 失败后自动降级为逐个子目录探测

3. 典型使用场景

📁 设计师工作流:快速收集散落在各处的商用字体
💻 开发者工具:为字体管理软件提供基础服务
🔐 系统维护:备份关键字体文件

4. 使用方法示例

FontCopier copier({“.ttf”, “.otf”}, “~/FontBackup”);
copier.add_custom_font_dir(“/”, true); // 全盘扫描(自动跳过系统保护区)
copier.run();

5. 输出

示例

=== macOS 字体拷贝工具 (C++17) ===
搜索字体类型: .ttf .otf
目标文件夹: /Users/Alice/FontBackup

正在尝试访问目录: /Library/Fonts
跳过受保护目录: /System/Library/Fonts (Operation not permitted)
找到 142 个字体文件:

  1. 正在拷贝: /Library/Fonts/Arial.ttf -> ~/FontBackup/Arial_1.ttf

操作完成。成功拷贝 138/142 个字体文件
跳过的目录 (3个):

  1. /System/Library/Fonts (Operation not permitted)

  2. /private/var/db (Permission denied)

  3. 扩展建议

🛠 可扩展为 GUI 工具(Qt/QML 前端)
🛠 添加网络字体下载功能
🛠 集成字体预览能力

注意:运行时可能需要 sudo 权限访问系统目录,但会严格遵守 macOS 权限系统限制

代码主体

#include <iostream>
#include <vector>
#include <string>
#include <filesystem>
#include <algorithm>
#include <set>
#include <iomanip>
#include <mutex>

namespace fs = std::filesystem;

class FontCopier {
public:
    FontCopier(const std::vector<std::string>& extensions, const fs::path& destination)
        : target_extensions_(extensions.begin(), extensions.end()),
          destination_(destination) {
        // 初始化默认 macOS 字体目录
        font_dirs_ = {
            // "/Library/Fonts",
            // "/System/Library/Fonts",
            // home_dir() / "Library/Fonts",
            // "/Network/Library/Fonts"
        };
    }

    // 添加用户自定义目录(支持递归搜索)
    void add_custom_font_dir(const fs::path& dir, bool recursive = true) {
        if (fs::exists(dir)) {
            custom_dirs_.emplace_back(dir, recursive);
        } else {
            std::cerr << "警告: 目录不存在 - " << dir << std::endl;
            skipped_dirs_.push_back({dir, "Directory does not exist"});
        }
    }

    void run() {
        std::cout << "=== macOS 字体拷贝工具 (C++17) ===" << std::endl;
        print_config();
        
        auto font_files = find_font_files();
        if (font_files.empty()) {
            std::cout << "未找到匹配的字体文件\n";
            return;
        }

        copy_files(font_files);
        print_summary();
        print_skipped_dirs();
    }

    const std::vector<std::pair<fs::path, std::string>>& get_skipped_dirs() const {
        return skipped_dirs_;
    }

private:
    fs::path home_dir() const {
        return fs::path(getenv("HOME"));
    }

    void print_config() const {
        std::cout << "搜索字体类型: ";
        for (const auto& ext : target_extensions_) {
            std::cout << ext << " ";
        }
        std::cout << "\n目标文件夹: " << destination_ << "\n\n";
    }

    std::vector<fs::path> find_font_files() {
        std::vector<fs::path> results;
        std::mutex mtx;
        
        // 搜索系统字体目录
        for (const auto& dir : font_dirs_) {
            search_directory(dir, true, results, mtx);
        }
        
        // 搜索用户自定义目录
        for (const auto& [dir, recursive] : custom_dirs_) {
            search_directory(dir, recursive, results, mtx);
        }
        
        return results;
    }

     void search_directory(const fs::path& dir, bool recursive, 
                         std::vector<fs::path>& results, std::mutex& mtx) {
        try {
            if (!fs::exists(dir)) {
                add_skipped_dir(dir, "Directory does not exist");
                return;
            }

            std::cout << "正在尝试访问目录: " << dir << std::endl;

            // 先尝试直接访问目录
            try {
                if (recursive) {
                    for (const auto& entry : fs::recursive_directory_iterator(
                        dir, fs::directory_options::skip_permission_denied)) {
                        process_entry(entry, results, mtx);
                    }
                } else {
                    for (const auto& entry : fs::directory_iterator(
                        dir, fs::directory_options::skip_permission_denied)) {
                        process_entry(entry, results, mtx);
                    }
                }
                return; // 如果成功就直接返回
            } catch (const fs::filesystem_error& e) {
                // 如果直接访问失败,记录并继续尝试子目录
                add_skipped_dir(dir, e.what());
                std::cerr << "目录访问错误: " << dir << " (" << e.what() << ")\n";
            }

            // 特殊处理根目录或受限目录:尝试一级子目录
            if (dir == "/" || dir == "/System" || dir == "/Library") {
                std::cout << "尝试访问 " << dir << " 的子目录..." << std::endl;
                
                try {
                    for (const auto& entry : fs::directory_iterator(
                        dir, fs::directory_options::skip_permission_denied)) {
                        
                        if (fs::is_directory(entry.status())) {
                            try {
                                std::cout << "正在搜索子目录: " << entry.path() << std::endl;
                                if (recursive) {
                                    for (const auto& sub_entry : fs::recursive_directory_iterator(
                                        entry.path(), fs::directory_options::skip_permission_denied)) {
                                        process_entry(sub_entry, results, mtx);
                                    }
                                } else {
                                    for (const auto& sub_entry : fs::directory_iterator(
                                        entry.path(), fs::directory_options::skip_permission_denied)) {
                                        process_entry(sub_entry, results, mtx);
                                    }
                                }
                            } catch (const fs::filesystem_error& e) {
                                add_skipped_dir(entry.path(), e.what());
                                std::cerr << "子目录访问错误: " << entry.path() << " (" << e.what() << ")\n";
                            }
                        }
                    }
                } catch (const fs::filesystem_error& e) {
                    add_skipped_dir(dir, e.what());
                    std::cerr << "无法列出目录内容: " << dir << " (" << e.what() << ")\n";
                }
            }
        } catch (const std::exception& e) {
            add_skipped_dir(dir, e.what());
            std::cerr << "未知错误: " << e.what() << "\n";
        }
    }

    void process_entry(const fs::directory_entry& entry, 
                      std::vector<fs::path>& results, std::mutex& mtx) {
        try {
            if (is_target_file(entry.path())) {
                std::lock_guard<std::mutex> lock(mtx);
                results.push_back(entry.path());
            }
        } catch (const fs::filesystem_error& e) {
            add_skipped_dir(entry.path(), e.what());
        }
    }

    bool is_target_file(const fs::path& path) const {
        try {
            if (!fs::is_regular_file(path)) return false;
            
            auto ext = path.extension().string();
            std::transform(ext.begin(), ext.end(), ext.begin(), 
                          [](unsigned char c) { return std::tolower(c); });
            
            return target_extensions_.count(ext) > 0;
        } catch (...) {
            return false;
        }
    }

    void copy_files(const std::vector<fs::path>& files) {
        fs::create_directories(destination_);
        
        std::cout << "\n找到 " << files.size() << " 个字体文件:\n";
        for (size_t i = 0; i < files.size(); ++i) {
            const auto& src = files[i];
            auto dest = destination_ / src.filename();
            
            // 处理文件名冲突
            int counter = 1;
            while (fs::exists(dest)) {
                auto new_name = src.stem().string() + "_" + std::to_string(counter++) + src.extension().string();
                dest = destination_ / new_name;
            }
            
            std::cout << i + 1 << ". 正在拷贝: " << src << " -> " << dest << std::endl;
            
            try {
                fs::copy(src, dest, fs::copy_options::overwrite_existing);
                ++success_count_;
            } catch (const fs::filesystem_error& e) {
                std::cerr << "拷贝失败: " << src << " (" << e.what() << ")\n";
                add_skipped_file(src, e.what());
            }
        }
    }

    void add_skipped_dir(const fs::path& dir, const std::string& reason) {
        std::lock_guard<std::mutex> lock(skip_mutex_);
        skipped_dirs_.emplace_back(dir, reason);
    }

    void add_skipped_file(const fs::path& file, const std::string& reason) {
        std::lock_guard<std::mutex> lock(skip_mutex_);
        skipped_files_.emplace_back(file, reason);
    }

    void print_summary() const {
        std::cout << "\n操作完成。成功拷贝 " << success_count_ 
                  << "/" << (success_count_ + skipped_files_.size()) 
                  << " 个字体文件到: " << destination_ << std::endl;
    }

    void print_skipped_dirs() const {
        if (!skipped_dirs_.empty()) {
            std::cout << "\n跳过的目录 (" << skipped_dirs_.size() << " 个):\n";
            for (size_t i = 0; i < skipped_dirs_.size(); ++i) {
                std::cout << i + 1 << ". " << skipped_dirs_[i].first 
                          << " (原因: " << skipped_dirs_[i].second << ")\n";
            }
        }

        if (!skipped_files_.empty()) {
            std::cout << "\n跳过的文件 (" << skipped_files_.size() << " 个):\n";
            for (size_t i = 0; i < skipped_files_.size(); ++i) {
                std::cout << i + 1 << ". " << skipped_files_[i].first 
                          << " (原因: " << skipped_files_[i].second << ")\n";
            }
        }
    }

private:
    std::set<std::string> target_extensions_;
    std::vector<fs::path> font_dirs_;
    std::vector<std::pair<fs::path, bool>> custom_dirs_; // <目录路径, 是否递归>
    fs::path destination_;
    size_t success_count_ = 0;
    
    // 跳过的目录和文件记录
    std::vector<std::pair<fs::path, std::string>> skipped_dirs_;
    std::vector<std::pair<fs::path, std::string>> skipped_files_;
    mutable std::mutex skip_mutex_;
};

int main() {
    // 配置参数
    std::vector<std::string> extensions = {".ttf", ".otf", ".ttc"};
    auto destination = fs::path("/Users/admin/Desktop/CopiedFonts");
    
    // 创建拷贝工具实例
    FontCopier copier(extensions, destination);
    
    // 添加自定义目录(示例)
    copier.add_custom_font_dir("/Library/Fonts");
    copier.add_custom_font_dir("/System/Library/Fonts");
    copier.add_custom_font_dir("~/Library/Fonts");
    copier.add_custom_font_dir("/", true); // 递归搜索根目录
    
    // 运行拷贝工具
    copier.run();
    
    return 0;
}

CMakeList.txt

cmake_minimum_required(VERSION 3.15)  # 需要支持 C++17 filesystem
project(FontCopier LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

# 设置输出目录
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)

# # 根据平台配置 filesystem 库
# if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
#     set(FILESYSTEM_LIB stdc++fs)
# elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
#     set(FILESYSTEM_LIB)
# endif()

# 可执行文件配置
add_executable(font_copier
    ${CMAKE_CURRENT_SOURCE_DIR}/collect_sysfont.cpp
    # src/FontCopier.cpp
    # include/FontCopier.h
)

target_include_directories(font_copier PRIVATE include)

# 链接系统库
target_link_libraries(font_copier PRIVATE ${FILESYSTEM_LIB})

# macOS 特定设置
if(APPLE)
    find_library(CORESERVICES CoreServices)
    target_link_libraries(font_copier PRIVATE ${CORESERVICES})
    
    # 设置 macOS 部署目标版本
    set_target_properties(font_copier PROPERTIES
        MACOSX_RPATH ON
        MACOSX_DEPLOYMENT_TARGET "10.15"  # 支持 filesystem 的最低版本
    )
endif()

# # 安装配置
# install(TARGETS font_copier
#     RUNTIME DESTINATION bin
#     BUNDLE DESTINATION bin
# )

# # 单元测试配置(可选)
# if(BUILD_TESTING)
#     enable_testing()
#     add_subdirectory(tests)
# endif()

# # 打包配置
# include(InstallRequiredSystemLibraries)
# set(CPACK_PACKAGE_NAME "FontCopier")
# set(CPACK_PACKAGE_VERSION "1.0.0")
# set(CPACK_PACKAGE_DESCRIPTION "Cross-platform font collector tool")
# include(CPack)

网站公告

今日签到

点亮在社区的每一天
去签到