当前位置: 首页 > news >正文

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

代码介绍

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 PROPERTIESMACOSX_RPATH ONMACOSX_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)
http://www.lryc.cn/news/583602.html

相关文章:

  • JavaScript对象的深度拷贝
  • 全球发展币GDEV:从中国出发,走向全球的数字发展合作蓝图
  • 【RK3568+PG2L50H开发板实验例程】FPGA部分 | DDR3 读写实验例程
  • 【学习笔记】OkHttp源码架构解析:从设计模式到核心实现
  • 【Java】【力扣】【字节高频】3.无重复字符的最长字串
  • 便捷的电脑自动关机辅助工具
  • Deepseek搭建智能体个人知识库
  • yolo8实现目标检测
  • 操作系统核心技术剖析:从Android驱动模型到鸿蒙微内核的国产化实践
  • Day 56
  • EPLAN 电气制图(六):结构盒与设备管理器核心概念(基础知识选看)
  • Linux操作系统之进程间通信:管道概念
  • EF提高性能(查询禁用追踪)(关闭延迟加载)
  • 神经网络初步学习3——数据与损失
  • 如何选择时序数据库:关键因素与实用指南
  • HCIP(综合实验)
  • 备受期待的 MMORPG 游戏《侍魂R》移动端现已上线 Sui
  • 【教程】基于GNN的药物相互作用网络中的链接预测
  • 200nl2sql
  • 安全管理协议(SMP):配对流程、密钥生成与防中间人攻击——蓝牙面试核心考点精解
  • python 在运行时没有加载修改后的版本
  • 自动驾驶决策与规划
  • 华为动态路由配置
  • 【Linux | 网络】socket编程 - 使用UDP实现服务端向客户端提供简单的服务
  • 分库分表之实战-sharding-JDBC水平分库+分表后:查询与删除操作实战
  • Android Notification 通过增加addAction 跳转回Service重新执行逻辑
  • 海信IP501H_GK6323处理器免拆卡刷包和线刷救砖包_当贝纯净版
  • LLM 在预测下一个词的时候是怎么计算向量的,说明详细过程
  • 数据库技术体系及场景选型方案
  • RNN及其变体的概念和案例