├── Info.plist ├── README.md ├── TextPix.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ └── swiftpm │ │ │ └── Package.resolved │ └── xcuserdata │ │ └── kivvi.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ └── kivvi.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── TextPix ├── AppState.swift ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── icon_1024x1024x32.png │ │ ├── icon_16x16x32.png │ │ ├── icon_256x256x32 1.png │ │ ├── icon_256x256x32.png │ │ ├── icon_32x32x32 1.png │ │ ├── icon_32x32x32.png │ │ ├── icon_512x512x32 1.png │ │ └── icon_512x512x32.png │ └── Contents.json ├── Resources │ ├── css │ │ └── katex.min.css │ └── js │ │ ├── auto-render.min.js │ │ ├── katex.min.js │ │ └── marked.min.js ├── TextPix.entitlements ├── TextPixApp.swift ├── Tools │ ├── GPTImageRequestManager.swift │ ├── KeyboardManager.swift │ ├── NotificationManager.swift │ ├── OCRService.swift │ └── ScreenshotManager.swift └── Views │ ├── MainTabView.swift │ ├── OCRResultView.swift │ ├── ScreenshotView.swift │ └── SettingView.swift └── images ├── ocr_code.png └── ocr_markdown_latex.png /Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | LSUIElement 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TextPix 2 | 3 | - TextPix 是一款轻量级的 macOS 菜单栏应用, 利用大语言模型对屏幕上的图片进行 OCR 识别, 并将结果以 Markdown (支持数学公式) 格式输出 4 | - 项目建立之初就是为了以 **低成本取代 mathpix** 5 | ## 核心功能 6 | 7 | - 一键截图: 支持系统截图交互, 快捷键 (默认 ⌘+⇧+2) 快速唤出 8 | 9 | - 智能识别: 将图片中的文字、列表、块方程、行内公式等完整识别并转为 Markdown 10 | 11 | - 实时预览: 内置 Markdown+KaTeX 渲染视图, 渲染效果与最终输出一致 12 | 13 | - 文本编辑: 下方可直接编辑识别结果 14 | 15 | - 复制分享: 手动点击「Copy to Clipboard」, 或开启自动复制, 一键粘贴到任意应用 16 | 17 | - 自定义配置: 在「Setting」页可设置 AI 接口地址、模型、API Key、系统提示词及快捷键 18 | 19 | > 模型建议使用 gemini-flash API 地址 : `https://generativelanguage.googleapis.com/v1beta/openai/chat/completions` 20 | > 国内模型使用 qwen-2.5-vl 效果还可以,API获取入口 [阿里云百炼](https://bailian.console.aliyun.com/) 21 | 22 | - ![](images/ocr_markdown_latex.png) 23 | 24 | - ![](images/ocr_code.png) 25 | 26 | 27 | ## 系统要求 28 | 29 | - macOS 13.5 及以上 30 | 31 | ## 快速上手 32 | 33 | 1. 前往「Setting」页面, 填写或修改: 34 | - API 地址 (Endpoint) 35 | - 模型名称 (Model) 36 | - API 密钥 (API Key) 37 | - 系统提示词 (System Prompt) 38 | 2. 按下截图快捷键, 即可体验从截图到 Markdown 输出的全流程便捷操作。 39 | -------------------------------------------------------------------------------- /TextPix.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 77; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 550E2E2A2DC3B77500561DEE /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 550E2E292DC3B76C00561DEE /* README.md */; }; 11 | 557CABDE2DC10D0200F1C613 /* KeyboardShortcuts in Frameworks */ = {isa = PBXBuildFile; productRef = 557CABDD2DC10D0200F1C613 /* KeyboardShortcuts */; }; 12 | 557CABEF2DC1D20A00F1C613 /* Ink in Frameworks */ = {isa = PBXBuildFile; productRef = 557CABEE2DC1D20A00F1C613 /* Ink */; }; 13 | /* End PBXBuildFile section */ 14 | 15 | /* Begin PBXFileReference section */ 16 | 550E2E292DC3B76C00561DEE /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 17 | 557CABCD2DC0FDC200F1C613 /* TextPix.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TextPix.app; sourceTree = BUILT_PRODUCTS_DIR; }; 18 | 557CAC132DC2A2C800F1C613 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 19 | /* End PBXFileReference section */ 20 | 21 | /* Begin PBXFileSystemSynchronizedRootGroup section */ 22 | 550E2E2B2DC3B7A900561DEE /* images */ = { 23 | isa = PBXFileSystemSynchronizedRootGroup; 24 | path = images; 25 | sourceTree = ""; 26 | }; 27 | 557CABCF2DC0FDC200F1C613 /* TextPix */ = { 28 | isa = PBXFileSystemSynchronizedRootGroup; 29 | path = TextPix; 30 | sourceTree = ""; 31 | }; 32 | /* End PBXFileSystemSynchronizedRootGroup section */ 33 | 34 | /* Begin PBXFrameworksBuildPhase section */ 35 | 557CABCA2DC0FDC200F1C613 /* Frameworks */ = { 36 | isa = PBXFrameworksBuildPhase; 37 | buildActionMask = 2147483647; 38 | files = ( 39 | 557CABDE2DC10D0200F1C613 /* KeyboardShortcuts in Frameworks */, 40 | 557CABEF2DC1D20A00F1C613 /* Ink in Frameworks */, 41 | ); 42 | runOnlyForDeploymentPostprocessing = 0; 43 | }; 44 | /* End PBXFrameworksBuildPhase section */ 45 | 46 | /* Begin PBXGroup section */ 47 | 557CABC42DC0FDC200F1C613 = { 48 | isa = PBXGroup; 49 | children = ( 50 | 550E2E2B2DC3B7A900561DEE /* images */, 51 | 550E2E292DC3B76C00561DEE /* README.md */, 52 | 557CABCF2DC0FDC200F1C613 /* TextPix */, 53 | 557CABCE2DC0FDC200F1C613 /* Products */, 54 | 557CAC132DC2A2C800F1C613 /* Info.plist */, 55 | ); 56 | sourceTree = ""; 57 | }; 58 | 557CABCE2DC0FDC200F1C613 /* Products */ = { 59 | isa = PBXGroup; 60 | children = ( 61 | 557CABCD2DC0FDC200F1C613 /* TextPix.app */, 62 | ); 63 | name = Products; 64 | sourceTree = ""; 65 | }; 66 | /* End PBXGroup section */ 67 | 68 | /* Begin PBXNativeTarget section */ 69 | 557CABCC2DC0FDC200F1C613 /* TextPix */ = { 70 | isa = PBXNativeTarget; 71 | buildConfigurationList = 557CABD92DC0FDC400F1C613 /* Build configuration list for PBXNativeTarget "TextPix" */; 72 | buildPhases = ( 73 | 557CABC92DC0FDC200F1C613 /* Sources */, 74 | 557CABCA2DC0FDC200F1C613 /* Frameworks */, 75 | 557CABCB2DC0FDC200F1C613 /* Resources */, 76 | ); 77 | buildRules = ( 78 | ); 79 | dependencies = ( 80 | ); 81 | fileSystemSynchronizedGroups = ( 82 | 550E2E2B2DC3B7A900561DEE /* images */, 83 | 557CABCF2DC0FDC200F1C613 /* TextPix */, 84 | ); 85 | name = TextPix; 86 | packageProductDependencies = ( 87 | 557CABDD2DC10D0200F1C613 /* KeyboardShortcuts */, 88 | 557CABEE2DC1D20A00F1C613 /* Ink */, 89 | ); 90 | productName = TextPix; 91 | productReference = 557CABCD2DC0FDC200F1C613 /* TextPix.app */; 92 | productType = "com.apple.product-type.application"; 93 | }; 94 | /* End PBXNativeTarget section */ 95 | 96 | /* Begin PBXProject section */ 97 | 557CABC52DC0FDC200F1C613 /* Project object */ = { 98 | isa = PBXProject; 99 | attributes = { 100 | BuildIndependentTargetsInParallel = 1; 101 | LastSwiftUpdateCheck = 1630; 102 | LastUpgradeCheck = 1630; 103 | TargetAttributes = { 104 | 557CABCC2DC0FDC200F1C613 = { 105 | CreatedOnToolsVersion = 16.3; 106 | }; 107 | }; 108 | }; 109 | buildConfigurationList = 557CABC82DC0FDC200F1C613 /* Build configuration list for PBXProject "TextPix" */; 110 | developmentRegion = en; 111 | hasScannedForEncodings = 0; 112 | knownRegions = ( 113 | en, 114 | Base, 115 | ); 116 | mainGroup = 557CABC42DC0FDC200F1C613; 117 | minimizedProjectReferenceProxies = 1; 118 | packageReferences = ( 119 | 557CABDC2DC10D0200F1C613 /* XCRemoteSwiftPackageReference "KeyboardShortcuts" */, 120 | 557CABED2DC1D20A00F1C613 /* XCRemoteSwiftPackageReference "Ink" */, 121 | ); 122 | preferredProjectObjectVersion = 77; 123 | productRefGroup = 557CABCE2DC0FDC200F1C613 /* Products */; 124 | projectDirPath = ""; 125 | projectRoot = ""; 126 | targets = ( 127 | 557CABCC2DC0FDC200F1C613 /* TextPix */, 128 | ); 129 | }; 130 | /* End PBXProject section */ 131 | 132 | /* Begin PBXResourcesBuildPhase section */ 133 | 557CABCB2DC0FDC200F1C613 /* Resources */ = { 134 | isa = PBXResourcesBuildPhase; 135 | buildActionMask = 2147483647; 136 | files = ( 137 | 550E2E2A2DC3B77500561DEE /* README.md in Resources */, 138 | ); 139 | runOnlyForDeploymentPostprocessing = 0; 140 | }; 141 | /* End PBXResourcesBuildPhase section */ 142 | 143 | /* Begin PBXSourcesBuildPhase section */ 144 | 557CABC92DC0FDC200F1C613 /* Sources */ = { 145 | isa = PBXSourcesBuildPhase; 146 | buildActionMask = 2147483647; 147 | files = ( 148 | ); 149 | runOnlyForDeploymentPostprocessing = 0; 150 | }; 151 | /* End PBXSourcesBuildPhase section */ 152 | 153 | /* Begin XCBuildConfiguration section */ 154 | 557CABD72DC0FDC400F1C613 /* Debug */ = { 155 | isa = XCBuildConfiguration; 156 | buildSettings = { 157 | ALWAYS_SEARCH_USER_PATHS = NO; 158 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 159 | CLANG_ANALYZER_NONNULL = YES; 160 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 161 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 162 | CLANG_ENABLE_MODULES = YES; 163 | CLANG_ENABLE_OBJC_ARC = YES; 164 | CLANG_ENABLE_OBJC_WEAK = YES; 165 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 166 | CLANG_WARN_BOOL_CONVERSION = YES; 167 | CLANG_WARN_COMMA = YES; 168 | CLANG_WARN_CONSTANT_CONVERSION = YES; 169 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 170 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 171 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 172 | CLANG_WARN_EMPTY_BODY = YES; 173 | CLANG_WARN_ENUM_CONVERSION = YES; 174 | CLANG_WARN_INFINITE_RECURSION = YES; 175 | CLANG_WARN_INT_CONVERSION = YES; 176 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 177 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 178 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 179 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 180 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 181 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 182 | CLANG_WARN_STRICT_PROTOTYPES = YES; 183 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 184 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 185 | CLANG_WARN_UNREACHABLE_CODE = YES; 186 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 187 | COPY_PHASE_STRIP = NO; 188 | DEBUG_INFORMATION_FORMAT = dwarf; 189 | DEVELOPMENT_TEAM = 33499L739V; 190 | ENABLE_STRICT_OBJC_MSGSEND = YES; 191 | ENABLE_TESTABILITY = YES; 192 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 193 | GCC_C_LANGUAGE_STANDARD = gnu17; 194 | GCC_DYNAMIC_NO_PIC = NO; 195 | GCC_NO_COMMON_BLOCKS = YES; 196 | GCC_OPTIMIZATION_LEVEL = 0; 197 | GCC_PREPROCESSOR_DEFINITIONS = ( 198 | "DEBUG=1", 199 | "$(inherited)", 200 | ); 201 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 202 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 203 | GCC_WARN_UNDECLARED_SELECTOR = YES; 204 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 205 | GCC_WARN_UNUSED_FUNCTION = YES; 206 | GCC_WARN_UNUSED_VARIABLE = YES; 207 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 208 | MACOSX_DEPLOYMENT_TARGET = 15.4; 209 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 210 | MTL_FAST_MATH = YES; 211 | ONLY_ACTIVE_ARCH = YES; 212 | SDKROOT = macosx; 213 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; 214 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 215 | }; 216 | name = Debug; 217 | }; 218 | 557CABD82DC0FDC400F1C613 /* Release */ = { 219 | isa = XCBuildConfiguration; 220 | buildSettings = { 221 | ALWAYS_SEARCH_USER_PATHS = NO; 222 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 223 | CLANG_ANALYZER_NONNULL = YES; 224 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 225 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 226 | CLANG_ENABLE_MODULES = YES; 227 | CLANG_ENABLE_OBJC_ARC = YES; 228 | CLANG_ENABLE_OBJC_WEAK = YES; 229 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 230 | CLANG_WARN_BOOL_CONVERSION = YES; 231 | CLANG_WARN_COMMA = YES; 232 | CLANG_WARN_CONSTANT_CONVERSION = YES; 233 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 234 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 235 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 236 | CLANG_WARN_EMPTY_BODY = YES; 237 | CLANG_WARN_ENUM_CONVERSION = YES; 238 | CLANG_WARN_INFINITE_RECURSION = YES; 239 | CLANG_WARN_INT_CONVERSION = YES; 240 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 241 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 242 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 243 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 244 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 245 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 246 | CLANG_WARN_STRICT_PROTOTYPES = YES; 247 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 248 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 249 | CLANG_WARN_UNREACHABLE_CODE = YES; 250 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 251 | COPY_PHASE_STRIP = NO; 252 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 253 | DEVELOPMENT_TEAM = 33499L739V; 254 | ENABLE_NS_ASSERTIONS = NO; 255 | ENABLE_STRICT_OBJC_MSGSEND = YES; 256 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 257 | GCC_C_LANGUAGE_STANDARD = gnu17; 258 | GCC_NO_COMMON_BLOCKS = YES; 259 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 260 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 261 | GCC_WARN_UNDECLARED_SELECTOR = YES; 262 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 263 | GCC_WARN_UNUSED_FUNCTION = YES; 264 | GCC_WARN_UNUSED_VARIABLE = YES; 265 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 266 | MACOSX_DEPLOYMENT_TARGET = 15.4; 267 | MTL_ENABLE_DEBUG_INFO = NO; 268 | MTL_FAST_MATH = YES; 269 | SDKROOT = macosx; 270 | SWIFT_COMPILATION_MODE = wholemodule; 271 | }; 272 | name = Release; 273 | }; 274 | 557CABDA2DC0FDC400F1C613 /* Debug */ = { 275 | isa = XCBuildConfiguration; 276 | buildSettings = { 277 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 278 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 279 | CODE_SIGN_ENTITLEMENTS = TextPix/TextPix.entitlements; 280 | CODE_SIGN_STYLE = Automatic; 281 | COMBINE_HIDPI_IMAGES = YES; 282 | CURRENT_PROJECT_VERSION = 1; 283 | DEVELOPMENT_TEAM = 33499L739V; 284 | ENABLE_HARDENED_RUNTIME = YES; 285 | ENABLE_PREVIEWS = YES; 286 | GENERATE_INFOPLIST_FILE = YES; 287 | INFOPLIST_FILE = Info.plist; 288 | INFOPLIST_KEY_NSHumanReadableCopyright = ""; 289 | LD_RUNPATH_SEARCH_PATHS = ( 290 | "$(inherited)", 291 | "@executable_path/../Frameworks", 292 | ); 293 | MACOSX_DEPLOYMENT_TARGET = 13.5; 294 | MARKETING_VERSION = 1.3; 295 | PRODUCT_BUNDLE_IDENTIFIER = kivvi.TextPix; 296 | PRODUCT_NAME = "$(TARGET_NAME)"; 297 | REGISTER_APP_GROUPS = YES; 298 | SWIFT_EMIT_LOC_STRINGS = YES; 299 | SWIFT_VERSION = 5.0; 300 | }; 301 | name = Debug; 302 | }; 303 | 557CABDB2DC0FDC400F1C613 /* Release */ = { 304 | isa = XCBuildConfiguration; 305 | buildSettings = { 306 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 307 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 308 | CODE_SIGN_ENTITLEMENTS = TextPix/TextPix.entitlements; 309 | CODE_SIGN_STYLE = Automatic; 310 | COMBINE_HIDPI_IMAGES = YES; 311 | CURRENT_PROJECT_VERSION = 1; 312 | DEVELOPMENT_TEAM = 33499L739V; 313 | ENABLE_HARDENED_RUNTIME = YES; 314 | ENABLE_PREVIEWS = YES; 315 | GENERATE_INFOPLIST_FILE = YES; 316 | INFOPLIST_FILE = Info.plist; 317 | INFOPLIST_KEY_NSHumanReadableCopyright = ""; 318 | LD_RUNPATH_SEARCH_PATHS = ( 319 | "$(inherited)", 320 | "@executable_path/../Frameworks", 321 | ); 322 | MACOSX_DEPLOYMENT_TARGET = 13.5; 323 | MARKETING_VERSION = 1.3; 324 | PRODUCT_BUNDLE_IDENTIFIER = kivvi.TextPix; 325 | PRODUCT_NAME = "$(TARGET_NAME)"; 326 | REGISTER_APP_GROUPS = YES; 327 | SWIFT_EMIT_LOC_STRINGS = YES; 328 | SWIFT_VERSION = 5.0; 329 | }; 330 | name = Release; 331 | }; 332 | /* End XCBuildConfiguration section */ 333 | 334 | /* Begin XCConfigurationList section */ 335 | 557CABC82DC0FDC200F1C613 /* Build configuration list for PBXProject "TextPix" */ = { 336 | isa = XCConfigurationList; 337 | buildConfigurations = ( 338 | 557CABD72DC0FDC400F1C613 /* Debug */, 339 | 557CABD82DC0FDC400F1C613 /* Release */, 340 | ); 341 | defaultConfigurationIsVisible = 0; 342 | defaultConfigurationName = Release; 343 | }; 344 | 557CABD92DC0FDC400F1C613 /* Build configuration list for PBXNativeTarget "TextPix" */ = { 345 | isa = XCConfigurationList; 346 | buildConfigurations = ( 347 | 557CABDA2DC0FDC400F1C613 /* Debug */, 348 | 557CABDB2DC0FDC400F1C613 /* Release */, 349 | ); 350 | defaultConfigurationIsVisible = 0; 351 | defaultConfigurationName = Release; 352 | }; 353 | /* End XCConfigurationList section */ 354 | 355 | /* Begin XCRemoteSwiftPackageReference section */ 356 | 557CABDC2DC10D0200F1C613 /* XCRemoteSwiftPackageReference "KeyboardShortcuts" */ = { 357 | isa = XCRemoteSwiftPackageReference; 358 | repositoryURL = "https://github.com/sindresorhus/KeyboardShortcuts"; 359 | requirement = { 360 | kind = upToNextMajorVersion; 361 | minimumVersion = 2.3.0; 362 | }; 363 | }; 364 | 557CABED2DC1D20A00F1C613 /* XCRemoteSwiftPackageReference "Ink" */ = { 365 | isa = XCRemoteSwiftPackageReference; 366 | repositoryURL = "https://github.com/JohnSundell/Ink.git"; 367 | requirement = { 368 | kind = upToNextMajorVersion; 369 | minimumVersion = 0.6.0; 370 | }; 371 | }; 372 | /* End XCRemoteSwiftPackageReference section */ 373 | 374 | /* Begin XCSwiftPackageProductDependency section */ 375 | 557CABDD2DC10D0200F1C613 /* KeyboardShortcuts */ = { 376 | isa = XCSwiftPackageProductDependency; 377 | package = 557CABDC2DC10D0200F1C613 /* XCRemoteSwiftPackageReference "KeyboardShortcuts" */; 378 | productName = KeyboardShortcuts; 379 | }; 380 | 557CABEE2DC1D20A00F1C613 /* Ink */ = { 381 | isa = XCSwiftPackageProductDependency; 382 | package = 557CABED2DC1D20A00F1C613 /* XCRemoteSwiftPackageReference "Ink" */; 383 | productName = Ink; 384 | }; 385 | /* End XCSwiftPackageProductDependency section */ 386 | }; 387 | rootObject = 557CABC52DC0FDC200F1C613 /* Project object */; 388 | } 389 | -------------------------------------------------------------------------------- /TextPix.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /TextPix.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "originHash" : "1b964a703f1e3cdde0aa88b03d10d3e2d146822ea83fe85e576e39cfe6710717", 3 | "pins" : [ 4 | { 5 | "identity" : "ink", 6 | "kind" : "remoteSourceControl", 7 | "location" : "https://github.com/JohnSundell/Ink.git", 8 | "state" : { 9 | "revision" : "bcc9f219900a62c4210e6db726035d7f03ae757b", 10 | "version" : "0.6.0" 11 | } 12 | }, 13 | { 14 | "identity" : "keyboardshortcuts", 15 | "kind" : "remoteSourceControl", 16 | "location" : "https://github.com/sindresorhus/KeyboardShortcuts", 17 | "state" : { 18 | "revision" : "045cf174010beb335fa1d2567d18c057b8787165", 19 | "version" : "2.3.0" 20 | } 21 | } 22 | ], 23 | "version" : 3 24 | } 25 | -------------------------------------------------------------------------------- /TextPix.xcodeproj/project.xcworkspace/xcuserdata/kivvi.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kivvi3412/TextPix/3d890bb13470d448f9d5c0f7af227cb44cb37472/TextPix.xcodeproj/project.xcworkspace/xcuserdata/kivvi.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /TextPix.xcodeproj/xcuserdata/kivvi.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | TextPix.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /TextPix/AppState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppState.swift 3 | // TextPix 4 | // 5 | // Created by HAIRONG ZHU on 2025/4/30. 6 | // 7 | 8 | import SwiftUI 9 | import Combine 10 | 11 | @MainActor 12 | class AppState: ObservableObject { 13 | // 核心数据, 不做持久化 14 | @Published var screenshotImage: NSImage? 15 | @Published var markdownText: String = "" 16 | @Published var isProcessing: Bool = false 17 | 18 | // 界面状态 19 | @Published var selectedTab: Int = UserDefaults.standard.integer(forKey: "selectedTab") { 20 | didSet { 21 | UserDefaults.standard.set(selectedTab, forKey: "selectedTab") 22 | } 23 | } 24 | 25 | // 自动复制到剪切板 26 | @Published var autoCopy: Bool = UserDefaults.standard.bool(forKey: "autoCopy") { 27 | didSet { 28 | UserDefaults.standard.set(autoCopy, forKey: "autoCopy") 29 | } 30 | } 31 | 32 | // LLM 参数 33 | @Published var apiKey: String = UserDefaults.standard.string(forKey: "apiKey") 34 | ?? "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" { 35 | didSet { 36 | UserDefaults.standard.set(apiKey, forKey: "apiKey") 37 | } 38 | } 39 | @Published var model: String = UserDefaults.standard.string(forKey: "model") 40 | ?? "qwen2.5-vl-7b-instruct" { 41 | didSet { 42 | UserDefaults.standard.set(model, forKey: "model") 43 | } 44 | } 45 | @Published var endpoint: String = UserDefaults.standard.string(forKey: "endpoint") 46 | ?? "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions" { 47 | didSet { 48 | UserDefaults.standard.set(endpoint, forKey: "endpoint") 49 | } 50 | } 51 | @Published var inferenceEnabled: Bool = UserDefaults.standard.bool(forKey: "inferenceEnabled") { 52 | didSet { 53 | UserDefaults.standard.set(inferenceEnabled, forKey: "inferenceEnabled") 54 | } 55 | } 56 | 57 | @Published var inferenceLevel: String = UserDefaults.standard.string(forKey: "inferenceLevel") 58 | ?? "medium" { 59 | didSet { 60 | UserDefaults.standard.set(inferenceLevel, forKey: "inferenceLevel") 61 | } 62 | } 63 | 64 | 65 | @Published var systemPrompt: String = UserDefaults.standard.string(forKey: "systemPrompt") 66 | ?? """ 67 | 1. OCR输出为markdown文本 (如果有序和无序列表使用markdown标准语法) 68 | 2. 输出格式也要像图片中的完全一致, 禁止自己换行和自行使用$$, 输出文本的空格换行必须和图片完全一致 69 | 3. 如果它是一个块方程用 $$ 符号包裹, 例: 70 | $$ 71 | \\int \\sec x \\mathrm{~d} x=\\ln |\\sec x+\\tan x|+C 72 | $$ 73 | 4. 内联方程, 使用 $...$ 的行内数学模式输出, 使用 $ 符号包裹 , 例: $f^{\\prime}\\left(x_0\\right)$ 74 | 5. 如果是表格, 请直接使用 markdown 表格语法输出 75 | 6. 注意缩进层次, 输出呈现的样子必须与原图保持一致 76 | 7. 禁止使用 \\[...\\] 输出, 不要漏东西, 完整整齐的输出图片所有内容 77 | """ { 78 | didSet { 79 | UserDefaults.standard.set(systemPrompt, forKey: "systemPrompt") 80 | } 81 | } 82 | 83 | // 单例 84 | static let shared = AppState() 85 | private init() {} 86 | } 87 | -------------------------------------------------------------------------------- /TextPix/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /TextPix/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icon_16x16x32.png", 5 | "idiom" : "mac", 6 | "scale" : "1x", 7 | "size" : "16x16" 8 | }, 9 | { 10 | "filename" : "icon_32x32x32 1.png", 11 | "idiom" : "mac", 12 | "scale" : "2x", 13 | "size" : "16x16" 14 | }, 15 | { 16 | "filename" : "icon_32x32x32.png", 17 | "idiom" : "mac", 18 | "scale" : "1x", 19 | "size" : "32x32" 20 | }, 21 | { 22 | "idiom" : "mac", 23 | "scale" : "2x", 24 | "size" : "32x32" 25 | }, 26 | { 27 | "idiom" : "mac", 28 | "scale" : "1x", 29 | "size" : "128x128" 30 | }, 31 | { 32 | "filename" : "icon_256x256x32 1.png", 33 | "idiom" : "mac", 34 | "scale" : "2x", 35 | "size" : "128x128" 36 | }, 37 | { 38 | "filename" : "icon_256x256x32.png", 39 | "idiom" : "mac", 40 | "scale" : "1x", 41 | "size" : "256x256" 42 | }, 43 | { 44 | "filename" : "icon_512x512x32 1.png", 45 | "idiom" : "mac", 46 | "scale" : "2x", 47 | "size" : "256x256" 48 | }, 49 | { 50 | "filename" : "icon_512x512x32.png", 51 | "idiom" : "mac", 52 | "scale" : "1x", 53 | "size" : "512x512" 54 | }, 55 | { 56 | "filename" : "icon_1024x1024x32.png", 57 | "idiom" : "mac", 58 | "scale" : "2x", 59 | "size" : "512x512" 60 | } 61 | ], 62 | "info" : { 63 | "author" : "xcode", 64 | "version" : 1 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /TextPix/Assets.xcassets/AppIcon.appiconset/icon_1024x1024x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kivvi3412/TextPix/3d890bb13470d448f9d5c0f7af227cb44cb37472/TextPix/Assets.xcassets/AppIcon.appiconset/icon_1024x1024x32.png -------------------------------------------------------------------------------- /TextPix/Assets.xcassets/AppIcon.appiconset/icon_16x16x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kivvi3412/TextPix/3d890bb13470d448f9d5c0f7af227cb44cb37472/TextPix/Assets.xcassets/AppIcon.appiconset/icon_16x16x32.png -------------------------------------------------------------------------------- /TextPix/Assets.xcassets/AppIcon.appiconset/icon_256x256x32 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kivvi3412/TextPix/3d890bb13470d448f9d5c0f7af227cb44cb37472/TextPix/Assets.xcassets/AppIcon.appiconset/icon_256x256x32 1.png -------------------------------------------------------------------------------- /TextPix/Assets.xcassets/AppIcon.appiconset/icon_256x256x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kivvi3412/TextPix/3d890bb13470d448f9d5c0f7af227cb44cb37472/TextPix/Assets.xcassets/AppIcon.appiconset/icon_256x256x32.png -------------------------------------------------------------------------------- /TextPix/Assets.xcassets/AppIcon.appiconset/icon_32x32x32 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kivvi3412/TextPix/3d890bb13470d448f9d5c0f7af227cb44cb37472/TextPix/Assets.xcassets/AppIcon.appiconset/icon_32x32x32 1.png -------------------------------------------------------------------------------- /TextPix/Assets.xcassets/AppIcon.appiconset/icon_32x32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kivvi3412/TextPix/3d890bb13470d448f9d5c0f7af227cb44cb37472/TextPix/Assets.xcassets/AppIcon.appiconset/icon_32x32x32.png -------------------------------------------------------------------------------- /TextPix/Assets.xcassets/AppIcon.appiconset/icon_512x512x32 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kivvi3412/TextPix/3d890bb13470d448f9d5c0f7af227cb44cb37472/TextPix/Assets.xcassets/AppIcon.appiconset/icon_512x512x32 1.png -------------------------------------------------------------------------------- /TextPix/Assets.xcassets/AppIcon.appiconset/icon_512x512x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kivvi3412/TextPix/3d890bb13470d448f9d5c0f7af227cb44cb37472/TextPix/Assets.xcassets/AppIcon.appiconset/icon_512x512x32.png -------------------------------------------------------------------------------- /TextPix/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /TextPix/Resources/css/katex.min.css: -------------------------------------------------------------------------------- 1 | @font-face{font-family:KaTeX_AMS;font-style:normal;font-weight:400;src:url(fonts/KaTeX_AMS-Regular.woff2) format("woff2"),url(fonts/KaTeX_AMS-Regular.woff) format("woff"),url(fonts/KaTeX_AMS-Regular.ttf) format("truetype")}@font-face{font-family:KaTeX_Caligraphic;font-style:normal;font-weight:700;src:url(fonts/KaTeX_Caligraphic-Bold.woff2) format("woff2"),url(fonts/KaTeX_Caligraphic-Bold.woff) format("woff"),url(fonts/KaTeX_Caligraphic-Bold.ttf) format("truetype")}@font-face{font-family:KaTeX_Caligraphic;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Caligraphic-Regular.woff2) format("woff2"),url(fonts/KaTeX_Caligraphic-Regular.woff) format("woff"),url(fonts/KaTeX_Caligraphic-Regular.ttf) format("truetype")}@font-face{font-family:KaTeX_Fraktur;font-style:normal;font-weight:700;src:url(fonts/KaTeX_Fraktur-Bold.woff2) format("woff2"),url(fonts/KaTeX_Fraktur-Bold.woff) format("woff"),url(fonts/KaTeX_Fraktur-Bold.ttf) format("truetype")}@font-face{font-family:KaTeX_Fraktur;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Fraktur-Regular.woff2) format("woff2"),url(fonts/KaTeX_Fraktur-Regular.woff) format("woff"),url(fonts/KaTeX_Fraktur-Regular.ttf) format("truetype")}@font-face{font-family:KaTeX_Main;font-style:normal;font-weight:700;src:url(fonts/KaTeX_Main-Bold.woff2) format("woff2"),url(fonts/KaTeX_Main-Bold.woff) format("woff"),url(fonts/KaTeX_Main-Bold.ttf) format("truetype")}@font-face{font-family:KaTeX_Main;font-style:italic;font-weight:700;src:url(fonts/KaTeX_Main-BoldItalic.woff2) format("woff2"),url(fonts/KaTeX_Main-BoldItalic.woff) format("woff"),url(fonts/KaTeX_Main-BoldItalic.ttf) format("truetype")}@font-face{font-family:KaTeX_Main;font-style:italic;font-weight:400;src:url(fonts/KaTeX_Main-Italic.woff2) format("woff2"),url(fonts/KaTeX_Main-Italic.woff) format("woff"),url(fonts/KaTeX_Main-Italic.ttf) format("truetype")}@font-face{font-family:KaTeX_Main;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Main-Regular.woff2) format("woff2"),url(fonts/KaTeX_Main-Regular.woff) format("woff"),url(fonts/KaTeX_Main-Regular.ttf) format("truetype")}@font-face{font-family:KaTeX_Math;font-style:italic;font-weight:700;src:url(fonts/KaTeX_Math-BoldItalic.woff2) format("woff2"),url(fonts/KaTeX_Math-BoldItalic.woff) format("woff"),url(fonts/KaTeX_Math-BoldItalic.ttf) format("truetype")}@font-face{font-family:KaTeX_Math;font-style:italic;font-weight:400;src:url(fonts/KaTeX_Math-Italic.woff2) format("woff2"),url(fonts/KaTeX_Math-Italic.woff) format("woff"),url(fonts/KaTeX_Math-Italic.ttf) format("truetype")}@font-face{font-family:"KaTeX_SansSerif";font-style:normal;font-weight:700;src:url(fonts/KaTeX_SansSerif-Bold.woff2) format("woff2"),url(fonts/KaTeX_SansSerif-Bold.woff) format("woff"),url(fonts/KaTeX_SansSerif-Bold.ttf) format("truetype")}@font-face{font-family:"KaTeX_SansSerif";font-style:italic;font-weight:400;src:url(fonts/KaTeX_SansSerif-Italic.woff2) format("woff2"),url(fonts/KaTeX_SansSerif-Italic.woff) format("woff"),url(fonts/KaTeX_SansSerif-Italic.ttf) format("truetype")}@font-face{font-family:"KaTeX_SansSerif";font-style:normal;font-weight:400;src:url(fonts/KaTeX_SansSerif-Regular.woff2) format("woff2"),url(fonts/KaTeX_SansSerif-Regular.woff) format("woff"),url(fonts/KaTeX_SansSerif-Regular.ttf) format("truetype")}@font-face{font-family:KaTeX_Script;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Script-Regular.woff2) format("woff2"),url(fonts/KaTeX_Script-Regular.woff) format("woff"),url(fonts/KaTeX_Script-Regular.ttf) format("truetype")}@font-face{font-family:KaTeX_Size1;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Size1-Regular.woff2) format("woff2"),url(fonts/KaTeX_Size1-Regular.woff) format("woff"),url(fonts/KaTeX_Size1-Regular.ttf) format("truetype")}@font-face{font-family:KaTeX_Size2;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Size2-Regular.woff2) format("woff2"),url(fonts/KaTeX_Size2-Regular.woff) format("woff"),url(fonts/KaTeX_Size2-Regular.ttf) format("truetype")}@font-face{font-family:KaTeX_Size3;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Size3-Regular.woff2) format("woff2"),url(fonts/KaTeX_Size3-Regular.woff) format("woff"),url(fonts/KaTeX_Size3-Regular.ttf) format("truetype")}@font-face{font-family:KaTeX_Size4;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Size4-Regular.woff2) format("woff2"),url(fonts/KaTeX_Size4-Regular.woff) format("woff"),url(fonts/KaTeX_Size4-Regular.ttf) format("truetype")}@font-face{font-family:KaTeX_Typewriter;font-style:normal;font-weight:400;src:url(fonts/KaTeX_Typewriter-Regular.woff2) format("woff2"),url(fonts/KaTeX_Typewriter-Regular.woff) format("woff"),url(fonts/KaTeX_Typewriter-Regular.ttf) format("truetype")}.katex{text-rendering:auto;font:normal 1.21em KaTeX_Main,Times New Roman,serif;line-height:1.2;text-indent:0}.katex *{-ms-high-contrast-adjust:none!important;border-color:currentColor}.katex .katex-version:after{content:"0.16.0"}.katex .katex-mathml{clip:rect(1px,1px,1px,1px);border:0;height:1px;overflow:hidden;padding:0;position:absolute;width:1px}.katex .katex-html>.newline{display:block}.katex .base{position:relative;white-space:nowrap;width:-webkit-min-content;width:-moz-min-content;width:min-content}.katex .base,.katex .strut{display:inline-block}.katex .textbf{font-weight:700}.katex .textit{font-style:italic}.katex .textrm{font-family:KaTeX_Main}.katex .textsf{font-family:KaTeX_SansSerif}.katex .texttt{font-family:KaTeX_Typewriter}.katex .mathnormal{font-family:KaTeX_Math;font-style:italic}.katex .mathit{font-family:KaTeX_Main;font-style:italic}.katex .mathrm{font-style:normal}.katex .mathbf{font-family:KaTeX_Main;font-weight:700}.katex .boldsymbol{font-family:KaTeX_Math;font-style:italic;font-weight:700}.katex .amsrm,.katex .mathbb,.katex .textbb{font-family:KaTeX_AMS}.katex .mathcal{font-family:KaTeX_Caligraphic}.katex .mathfrak,.katex .textfrak{font-family:KaTeX_Fraktur}.katex .mathtt{font-family:KaTeX_Typewriter}.katex .mathscr,.katex .textscr{font-family:KaTeX_Script}.katex .mathsf,.katex .textsf{font-family:KaTeX_SansSerif}.katex .mathboldsf,.katex .textboldsf{font-family:KaTeX_SansSerif;font-weight:700}.katex .mathitsf,.katex .textitsf{font-family:KaTeX_SansSerif;font-style:italic}.katex .mainrm{font-family:KaTeX_Main;font-style:normal}.katex .vlist-t{border-collapse:collapse;display:inline-table;table-layout:fixed}.katex .vlist-r{display:table-row}.katex .vlist{display:table-cell;position:relative;vertical-align:bottom}.katex .vlist>span{display:block;height:0;position:relative}.katex .vlist>span>span{display:inline-block}.katex .vlist>span>.pstrut{overflow:hidden;width:0}.katex .vlist-t2{margin-right:-2px}.katex .vlist-s{display:table-cell;font-size:1px;min-width:2px;vertical-align:bottom;width:2px}.katex .vbox{align-items:baseline;display:inline-flex;flex-direction:column}.katex .hbox{width:100%}.katex .hbox,.katex .thinbox{display:inline-flex;flex-direction:row}.katex .thinbox{max-width:0;width:0}.katex .msupsub{text-align:left}.katex .mfrac>span>span{text-align:center}.katex .mfrac .frac-line{border-bottom-style:solid;display:inline-block;width:100%}.katex .hdashline,.katex .hline,.katex .mfrac .frac-line,.katex .overline .overline-line,.katex .rule,.katex .underline .underline-line{min-height:1px}.katex .mspace{display:inline-block}.katex .clap,.katex .llap,.katex .rlap{position:relative;width:0}.katex .clap>.inner,.katex .llap>.inner,.katex .rlap>.inner{position:absolute}.katex .clap>.fix,.katex .llap>.fix,.katex .rlap>.fix{display:inline-block}.katex .llap>.inner{right:0}.katex .clap>.inner,.katex .rlap>.inner{left:0}.katex .clap>.inner>span{margin-left:-50%;margin-right:50%}.katex .rule{border:0 solid;display:inline-block;position:relative}.katex .hline,.katex .overline .overline-line,.katex .underline .underline-line{border-bottom-style:solid;display:inline-block;width:100%}.katex .hdashline{border-bottom-style:dashed;display:inline-block;width:100%}.katex .sqrt>.root{margin-left:.27777778em;margin-right:-.55555556em}.katex .fontsize-ensurer.reset-size1.size1,.katex .sizing.reset-size1.size1{font-size:1em}.katex .fontsize-ensurer.reset-size1.size2,.katex .sizing.reset-size1.size2{font-size:1.2em}.katex .fontsize-ensurer.reset-size1.size3,.katex .sizing.reset-size1.size3{font-size:1.4em}.katex .fontsize-ensurer.reset-size1.size4,.katex .sizing.reset-size1.size4{font-size:1.6em}.katex .fontsize-ensurer.reset-size1.size5,.katex .sizing.reset-size1.size5{font-size:1.8em}.katex .fontsize-ensurer.reset-size1.size6,.katex .sizing.reset-size1.size6{font-size:2em}.katex .fontsize-ensurer.reset-size1.size7,.katex .sizing.reset-size1.size7{font-size:2.4em}.katex .fontsize-ensurer.reset-size1.size8,.katex .sizing.reset-size1.size8{font-size:2.88em}.katex .fontsize-ensurer.reset-size1.size9,.katex .sizing.reset-size1.size9{font-size:3.456em}.katex .fontsize-ensurer.reset-size1.size10,.katex .sizing.reset-size1.size10{font-size:4.148em}.katex .fontsize-ensurer.reset-size1.size11,.katex .sizing.reset-size1.size11{font-size:4.976em}.katex .fontsize-ensurer.reset-size2.size1,.katex .sizing.reset-size2.size1{font-size:.83333333em}.katex .fontsize-ensurer.reset-size2.size2,.katex .sizing.reset-size2.size2{font-size:1em}.katex .fontsize-ensurer.reset-size2.size3,.katex .sizing.reset-size2.size3{font-size:1.16666667em}.katex .fontsize-ensurer.reset-size2.size4,.katex .sizing.reset-size2.size4{font-size:1.33333333em}.katex .fontsize-ensurer.reset-size2.size5,.katex .sizing.reset-size2.size5{font-size:1.5em}.katex .fontsize-ensurer.reset-size2.size6,.katex .sizing.reset-size2.size6{font-size:1.66666667em}.katex .fontsize-ensurer.reset-size2.size7,.katex .sizing.reset-size2.size7{font-size:2em}.katex .fontsize-ensurer.reset-size2.size8,.katex .sizing.reset-size2.size8{font-size:2.4em}.katex .fontsize-ensurer.reset-size2.size9,.katex .sizing.reset-size2.size9{font-size:2.88em}.katex .fontsize-ensurer.reset-size2.size10,.katex .sizing.reset-size2.size10{font-size:3.45666667em}.katex .fontsize-ensurer.reset-size2.size11,.katex .sizing.reset-size2.size11{font-size:4.14666667em}.katex .fontsize-ensurer.reset-size3.size1,.katex .sizing.reset-size3.size1{font-size:.71428571em}.katex .fontsize-ensurer.reset-size3.size2,.katex .sizing.reset-size3.size2{font-size:.85714286em}.katex .fontsize-ensurer.reset-size3.size3,.katex .sizing.reset-size3.size3{font-size:1em}.katex .fontsize-ensurer.reset-size3.size4,.katex .sizing.reset-size3.size4{font-size:1.14285714em}.katex .fontsize-ensurer.reset-size3.size5,.katex .sizing.reset-size3.size5{font-size:1.28571429em}.katex .fontsize-ensurer.reset-size3.size6,.katex .sizing.reset-size3.size6{font-size:1.42857143em}.katex .fontsize-ensurer.reset-size3.size7,.katex .sizing.reset-size3.size7{font-size:1.71428571em}.katex .fontsize-ensurer.reset-size3.size8,.katex .sizing.reset-size3.size8{font-size:2.05714286em}.katex .fontsize-ensurer.reset-size3.size9,.katex .sizing.reset-size3.size9{font-size:2.46857143em}.katex .fontsize-ensurer.reset-size3.size10,.katex .sizing.reset-size3.size10{font-size:2.96285714em}.katex .fontsize-ensurer.reset-size3.size11,.katex .sizing.reset-size3.size11{font-size:3.55428571em}.katex .fontsize-ensurer.reset-size4.size1,.katex .sizing.reset-size4.size1{font-size:.625em}.katex .fontsize-ensurer.reset-size4.size2,.katex .sizing.reset-size4.size2{font-size:.75em}.katex .fontsize-ensurer.reset-size4.size3,.katex .sizing.reset-size4.size3{font-size:.875em}.katex .fontsize-ensurer.reset-size4.size4,.katex .sizing.reset-size4.size4{font-size:1em}.katex .fontsize-ensurer.reset-size4.size5,.katex .sizing.reset-size4.size5{font-size:1.125em}.katex .fontsize-ensurer.reset-size4.size6,.katex .sizing.reset-size4.size6{font-size:1.25em}.katex .fontsize-ensurer.reset-size4.size7,.katex .sizing.reset-size4.size7{font-size:1.5em}.katex .fontsize-ensurer.reset-size4.size8,.katex .sizing.reset-size4.size8{font-size:1.8em}.katex .fontsize-ensurer.reset-size4.size9,.katex .sizing.reset-size4.size9{font-size:2.16em}.katex .fontsize-ensurer.reset-size4.size10,.katex .sizing.reset-size4.size10{font-size:2.5925em}.katex .fontsize-ensurer.reset-size4.size11,.katex .sizing.reset-size4.size11{font-size:3.11em}.katex .fontsize-ensurer.reset-size5.size1,.katex .sizing.reset-size5.size1{font-size:.55555556em}.katex .fontsize-ensurer.reset-size5.size2,.katex .sizing.reset-size5.size2{font-size:.66666667em}.katex .fontsize-ensurer.reset-size5.size3,.katex .sizing.reset-size5.size3{font-size:.77777778em}.katex .fontsize-ensurer.reset-size5.size4,.katex .sizing.reset-size5.size4{font-size:.88888889em}.katex .fontsize-ensurer.reset-size5.size5,.katex .sizing.reset-size5.size5{font-size:1em}.katex .fontsize-ensurer.reset-size5.size6,.katex .sizing.reset-size5.size6{font-size:1.11111111em}.katex .fontsize-ensurer.reset-size5.size7,.katex .sizing.reset-size5.size7{font-size:1.33333333em}.katex .fontsize-ensurer.reset-size5.size8,.katex .sizing.reset-size5.size8{font-size:1.6em}.katex .fontsize-ensurer.reset-size5.size9,.katex .sizing.reset-size5.size9{font-size:1.92em}.katex .fontsize-ensurer.reset-size5.size10,.katex .sizing.reset-size5.size10{font-size:2.30444444em}.katex .fontsize-ensurer.reset-size5.size11,.katex .sizing.reset-size5.size11{font-size:2.76444444em}.katex .fontsize-ensurer.reset-size6.size1,.katex .sizing.reset-size6.size1{font-size:.5em}.katex .fontsize-ensurer.reset-size6.size2,.katex .sizing.reset-size6.size2{font-size:.6em}.katex .fontsize-ensurer.reset-size6.size3,.katex .sizing.reset-size6.size3{font-size:.7em}.katex .fontsize-ensurer.reset-size6.size4,.katex .sizing.reset-size6.size4{font-size:.8em}.katex .fontsize-ensurer.reset-size6.size5,.katex .sizing.reset-size6.size5{font-size:.9em}.katex .fontsize-ensurer.reset-size6.size6,.katex .sizing.reset-size6.size6{font-size:1em}.katex .fontsize-ensurer.reset-size6.size7,.katex .sizing.reset-size6.size7{font-size:1.2em}.katex .fontsize-ensurer.reset-size6.size8,.katex .sizing.reset-size6.size8{font-size:1.44em}.katex .fontsize-ensurer.reset-size6.size9,.katex .sizing.reset-size6.size9{font-size:1.728em}.katex .fontsize-ensurer.reset-size6.size10,.katex .sizing.reset-size6.size10{font-size:2.074em}.katex .fontsize-ensurer.reset-size6.size11,.katex .sizing.reset-size6.size11{font-size:2.488em}.katex .fontsize-ensurer.reset-size7.size1,.katex .sizing.reset-size7.size1{font-size:.41666667em}.katex .fontsize-ensurer.reset-size7.size2,.katex .sizing.reset-size7.size2{font-size:.5em}.katex .fontsize-ensurer.reset-size7.size3,.katex .sizing.reset-size7.size3{font-size:.58333333em}.katex .fontsize-ensurer.reset-size7.size4,.katex .sizing.reset-size7.size4{font-size:.66666667em}.katex .fontsize-ensurer.reset-size7.size5,.katex .sizing.reset-size7.size5{font-size:.75em}.katex .fontsize-ensurer.reset-size7.size6,.katex .sizing.reset-size7.size6{font-size:.83333333em}.katex .fontsize-ensurer.reset-size7.size7,.katex .sizing.reset-size7.size7{font-size:1em}.katex .fontsize-ensurer.reset-size7.size8,.katex .sizing.reset-size7.size8{font-size:1.2em}.katex .fontsize-ensurer.reset-size7.size9,.katex .sizing.reset-size7.size9{font-size:1.44em}.katex .fontsize-ensurer.reset-size7.size10,.katex .sizing.reset-size7.size10{font-size:1.72833333em}.katex .fontsize-ensurer.reset-size7.size11,.katex .sizing.reset-size7.size11{font-size:2.07333333em}.katex .fontsize-ensurer.reset-size8.size1,.katex .sizing.reset-size8.size1{font-size:.34722222em}.katex .fontsize-ensurer.reset-size8.size2,.katex .sizing.reset-size8.size2{font-size:.41666667em}.katex .fontsize-ensurer.reset-size8.size3,.katex .sizing.reset-size8.size3{font-size:.48611111em}.katex .fontsize-ensurer.reset-size8.size4,.katex .sizing.reset-size8.size4{font-size:.55555556em}.katex .fontsize-ensurer.reset-size8.size5,.katex .sizing.reset-size8.size5{font-size:.625em}.katex .fontsize-ensurer.reset-size8.size6,.katex .sizing.reset-size8.size6{font-size:.69444444em}.katex .fontsize-ensurer.reset-size8.size7,.katex .sizing.reset-size8.size7{font-size:.83333333em}.katex .fontsize-ensurer.reset-size8.size8,.katex .sizing.reset-size8.size8{font-size:1em}.katex .fontsize-ensurer.reset-size8.size9,.katex .sizing.reset-size8.size9{font-size:1.2em}.katex .fontsize-ensurer.reset-size8.size10,.katex .sizing.reset-size8.size10{font-size:1.44027778em}.katex .fontsize-ensurer.reset-size8.size11,.katex .sizing.reset-size8.size11{font-size:1.72777778em}.katex .fontsize-ensurer.reset-size9.size1,.katex .sizing.reset-size9.size1{font-size:.28935185em}.katex .fontsize-ensurer.reset-size9.size2,.katex .sizing.reset-size9.size2{font-size:.34722222em}.katex .fontsize-ensurer.reset-size9.size3,.katex .sizing.reset-size9.size3{font-size:.40509259em}.katex .fontsize-ensurer.reset-size9.size4,.katex .sizing.reset-size9.size4{font-size:.46296296em}.katex .fontsize-ensurer.reset-size9.size5,.katex .sizing.reset-size9.size5{font-size:.52083333em}.katex .fontsize-ensurer.reset-size9.size6,.katex .sizing.reset-size9.size6{font-size:.5787037em}.katex .fontsize-ensurer.reset-size9.size7,.katex .sizing.reset-size9.size7{font-size:.69444444em}.katex .fontsize-ensurer.reset-size9.size8,.katex .sizing.reset-size9.size8{font-size:.83333333em}.katex .fontsize-ensurer.reset-size9.size9,.katex .sizing.reset-size9.size9{font-size:1em}.katex .fontsize-ensurer.reset-size9.size10,.katex .sizing.reset-size9.size10{font-size:1.20023148em}.katex .fontsize-ensurer.reset-size9.size11,.katex .sizing.reset-size9.size11{font-size:1.43981481em}.katex .fontsize-ensurer.reset-size10.size1,.katex .sizing.reset-size10.size1{font-size:.24108004em}.katex .fontsize-ensurer.reset-size10.size2,.katex .sizing.reset-size10.size2{font-size:.28929605em}.katex .fontsize-ensurer.reset-size10.size3,.katex .sizing.reset-size10.size3{font-size:.33751205em}.katex .fontsize-ensurer.reset-size10.size4,.katex .sizing.reset-size10.size4{font-size:.38572806em}.katex .fontsize-ensurer.reset-size10.size5,.katex .sizing.reset-size10.size5{font-size:.43394407em}.katex .fontsize-ensurer.reset-size10.size6,.katex .sizing.reset-size10.size6{font-size:.48216008em}.katex .fontsize-ensurer.reset-size10.size7,.katex .sizing.reset-size10.size7{font-size:.57859209em}.katex .fontsize-ensurer.reset-size10.size8,.katex .sizing.reset-size10.size8{font-size:.69431051em}.katex .fontsize-ensurer.reset-size10.size9,.katex .sizing.reset-size10.size9{font-size:.83317261em}.katex .fontsize-ensurer.reset-size10.size10,.katex .sizing.reset-size10.size10{font-size:1em}.katex .fontsize-ensurer.reset-size10.size11,.katex .sizing.reset-size10.size11{font-size:1.19961427em}.katex .fontsize-ensurer.reset-size11.size1,.katex .sizing.reset-size11.size1{font-size:.20096463em}.katex .fontsize-ensurer.reset-size11.size2,.katex .sizing.reset-size11.size2{font-size:.24115756em}.katex .fontsize-ensurer.reset-size11.size3,.katex .sizing.reset-size11.size3{font-size:.28135048em}.katex .fontsize-ensurer.reset-size11.size4,.katex .sizing.reset-size11.size4{font-size:.32154341em}.katex .fontsize-ensurer.reset-size11.size5,.katex .sizing.reset-size11.size5{font-size:.36173633em}.katex .fontsize-ensurer.reset-size11.size6,.katex .sizing.reset-size11.size6{font-size:.40192926em}.katex .fontsize-ensurer.reset-size11.size7,.katex .sizing.reset-size11.size7{font-size:.48231511em}.katex .fontsize-ensurer.reset-size11.size8,.katex .sizing.reset-size11.size8{font-size:.57877814em}.katex .fontsize-ensurer.reset-size11.size9,.katex .sizing.reset-size11.size9{font-size:.69453376em}.katex .fontsize-ensurer.reset-size11.size10,.katex .sizing.reset-size11.size10{font-size:.83360129em}.katex .fontsize-ensurer.reset-size11.size11,.katex .sizing.reset-size11.size11{font-size:1em}.katex .delimsizing.size1{font-family:KaTeX_Size1}.katex .delimsizing.size2{font-family:KaTeX_Size2}.katex .delimsizing.size3{font-family:KaTeX_Size3}.katex .delimsizing.size4{font-family:KaTeX_Size4}.katex .delimsizing.mult .delim-size1>span{font-family:KaTeX_Size1}.katex .delimsizing.mult .delim-size4>span{font-family:KaTeX_Size4}.katex .nulldelimiter{display:inline-block;width:.12em}.katex .delimcenter,.katex .op-symbol{position:relative}.katex .op-symbol.small-op{font-family:KaTeX_Size1}.katex .op-symbol.large-op{font-family:KaTeX_Size2}.katex .accent>.vlist-t,.katex .op-limits>.vlist-t{text-align:center}.katex .accent .accent-body{position:relative}.katex .accent .accent-body:not(.accent-full){width:0}.katex .overlay{display:block}.katex .mtable .vertical-separator{display:inline-block;min-width:1px}.katex .mtable .arraycolsep{display:inline-block}.katex .mtable .col-align-c>.vlist-t{text-align:center}.katex .mtable .col-align-l>.vlist-t{text-align:left}.katex .mtable .col-align-r>.vlist-t{text-align:right}.katex .svg-align{text-align:left}.katex svg{fill:currentColor;stroke:currentColor;fill-rule:nonzero;fill-opacity:1;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;display:block;height:inherit;position:absolute;width:100%}.katex svg path{stroke:none}.katex img{border-style:none;max-height:none;max-width:none;min-height:0;min-width:0}.katex .stretchy{display:block;overflow:hidden;position:relative;width:100%}.katex .stretchy:after,.katex .stretchy:before{content:""}.katex .hide-tail{overflow:hidden;position:relative;width:100%}.katex .halfarrow-left{left:0;overflow:hidden;position:absolute;width:50.2%}.katex .halfarrow-right{overflow:hidden;position:absolute;right:0;width:50.2%}.katex .brace-left{left:0;overflow:hidden;position:absolute;width:25.1%}.katex .brace-center{left:25%;overflow:hidden;position:absolute;width:50%}.katex .brace-right{overflow:hidden;position:absolute;right:0;width:25.1%}.katex .x-arrow-pad{padding:0 .5em}.katex .cd-arrow-pad{padding:0 .55556em 0 .27778em}.katex .mover,.katex .munder,.katex .x-arrow{text-align:center}.katex .boxpad{padding:0 .3em}.katex .fbox,.katex .fcolorbox{border:.04em solid;box-sizing:border-box}.katex .cancel-pad{padding:0 .2em}.katex .cancel-lap{margin-left:-.2em;margin-right:-.2em}.katex .sout{border-bottom-style:solid;border-bottom-width:.08em}.katex .angl{border-right:.049em solid;border-top:.049em solid;box-sizing:border-box;margin-right:.03889em}.katex .anglpad{padding:0 .03889em}.katex .eqn-num:before{content:"(" counter(katexEqnNo) ")";counter-increment:katexEqnNo}.katex .mml-eqn-num:before{content:"(" counter(mmlEqnNo) ")";counter-increment:mmlEqnNo}.katex .mtr-glue{width:50%}.katex .cd-vert-arrow{display:inline-block;position:relative}.katex .cd-label-left{display:inline-block;position:absolute;right:calc(50% + .3em);text-align:left}.katex .cd-label-right{display:inline-block;left:calc(50% + .3em);position:absolute;text-align:right}.katex-display{display:block;margin:1em 0;text-align:center}.katex-display>.katex{display:block;text-align:center;white-space:nowrap}.katex-display>.katex>.katex-html{display:block;position:relative}.katex-display>.katex>.katex-html>.tag{position:absolute;right:0}.katex-display.leqno>.katex>.katex-html>.tag{left:0;right:auto}.katex-display.fleqn>.katex{padding-left:2em;text-align:left}body{counter-reset:katexEqnNo mmlEqnNo} 2 | -------------------------------------------------------------------------------- /TextPix/Resources/js/auto-render.min.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("katex")):"function"==typeof define&&define.amd?define(["katex"],t):"object"==typeof exports?exports.renderMathInElement=t(require("katex")):e.renderMathInElement=t(e.katex)}("undefined"!=typeof self?self:this,(function(e){return function(){"use strict";var t={771:function(t){t.exports=e}},r={};function n(e){var a=r[e];if(void 0!==a)return a.exports;var i=r[e]={exports:{}};return t[e](i,i.exports,n),i.exports}n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,{a:t}),t},n.d=function(e,t){for(var r in t)n.o(t,r)&&!n.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)};var a={};return function(){n.d(a,{default:function(){return s}});var e=n(771),t=n.n(e),r=function(e,t,r){for(var n=r,a=0,i=e.length;n0&&(a.push({type:"text",data:e.slice(0,n)}),e=e.slice(n));var l=t.findIndex((function(t){return e.startsWith(t.left)}));if(-1===(n=r(t[l].right,e,t[l].left.length)))break;var d=e.slice(0,n+t[l].right.length),s=i.test(d)?d:e.slice(t[l].left.length,n);a.push({type:"math",data:s,rawData:d,display:t[l].display}),e=e.slice(n+t[l].right.length)}return""!==e&&a.push({type:"text",data:e}),a},l=function(e,r){var n=o(e,r.delimiters);if(1===n.length&&"text"===n[0].type)return null;for(var a=document.createDocumentFragment(),i=0;inull};function r(e,t=""){let n="string"==typeof e?e:e.source;const s={replace:(e,t)=>{let r="string"==typeof t?t:t.source;return r=r.replace(i.caret,"$1"),n=n.replace(e,r),s},getRegex:()=>new RegExp(n,t)};return s}const i={codeRemoveIndent:/^(?: {1,4}| {0,3}\t)/gm,outputLinkReplace:/\\([\[\]])/g,indentCodeCompensation:/^(\s+)(?:```)/,beginningSpace:/^\s+/,endingHash:/#$/,startingSpaceChar:/^ /,endingSpaceChar:/ $/,nonSpaceChar:/[^ ]/,newLineCharGlobal:/\n/g,tabCharGlobal:/\t/g,multipleSpaceGlobal:/\s+/g,blankLine:/^[ \t]*$/,doubleBlankLine:/\n[ \t]*\n[ \t]*$/,blockquoteStart:/^ {0,3}>/,blockquoteSetextReplace:/\n {0,3}((?:=+|-+) *)(?=\n|$)/g,blockquoteSetextReplace2:/^ {0,3}>[ \t]?/gm,listReplaceTabs:/^\t+/,listReplaceNesting:/^ {1,4}(?=( {4})*[^ ])/g,listIsTask:/^\[[ xX]\] /,listReplaceTask:/^\[[ xX]\] +/,anyLine:/\n.*\n/,hrefBrackets:/^<(.*)>$/,tableDelimiter:/[:|]/,tableAlignChars:/^\||\| *$/g,tableRowBlankLine:/\n[ \t]*$/,tableAlignRight:/^ *-+: *$/,tableAlignCenter:/^ *:-+: *$/,tableAlignLeft:/^ *:-+ *$/,startATag:/^/i,startPreScriptTag:/^<(pre|code|kbd|script)(\s|>)/i,endPreScriptTag:/^<\/(pre|code|kbd|script)(\s|>)/i,startAngleBracket:/^$/,pedanticHrefTitle:/^([^'"]*[^\s])\s+(['"])(.*)\2/,unicodeAlphaNumeric:/[\p{L}\p{N}]/u,escapeTest:/[&<>"']/,escapeReplace:/[&<>"']/g,escapeTestNoEncode:/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/,escapeReplaceNoEncode:/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/g,unescapeTest:/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/gi,caret:/(^|[^\[])\^/g,percentDecode:/%25/g,findPipe:/\|/g,splitPipe:/ \|/,slashPipe:/\\\|/g,carriageReturn:/\r\n|\r/g,spaceLine:/^ +$/gm,notSpaceStart:/^\S*/,endingNewline:/\n$/,listItemRegex:e=>new RegExp(`^( {0,3}${e})((?:[\t ][^\\n]*)?(?:\\n|$))`),nextBulletRegex:e=>new RegExp(`^ {0,${Math.min(3,e-1)}}(?:[*+-]|\\d{1,9}[.)])((?:[ \t][^\\n]*)?(?:\\n|$))`),hrRegex:e=>new RegExp(`^ {0,${Math.min(3,e-1)}}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)`),fencesBeginRegex:e=>new RegExp(`^ {0,${Math.min(3,e-1)}}(?:\`\`\`|~~~)`),headingBeginRegex:e=>new RegExp(`^ {0,${Math.min(3,e-1)}}#`),htmlBeginRegex:e=>new RegExp(`^ {0,${Math.min(3,e-1)}}<(?:[a-z].*>|!--)`,"i")},l=/^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/,o=/(?:[*+-]|\d{1,9}[.)])/,a=/^(?!bull |blockCode|fences|blockquote|heading|html|table)((?:.|\n(?!\s*?\n|bull |blockCode|fences|blockquote|heading|html|table))+?)\n {0,3}(=+|-+) *(?:\n+|$)/,c=r(a).replace(/bull/g,o).replace(/blockCode/g,/(?: {4}| {0,3}\t)/).replace(/fences/g,/ {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g,/ {0,3}>/).replace(/heading/g,/ {0,3}#{1,6}/).replace(/html/g,/ {0,3}<[^\n>]+>\n/).replace(/\|table/g,"").getRegex(),h=r(a).replace(/bull/g,o).replace(/blockCode/g,/(?: {4}| {0,3}\t)/).replace(/fences/g,/ {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g,/ {0,3}>/).replace(/heading/g,/ {0,3}#{1,6}/).replace(/html/g,/ {0,3}<[^\n>]+>\n/).replace(/table/g,/ {0,3}\|?(?:[:\- ]*\|)+[\:\- ]*\n/).getRegex(),p=/^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table| +\n)[^\n]+)*)/,u=/(?!\s*\])(?:\\.|[^\[\]\\])+/,g=r(/^ {0,3}\[(label)\]: *(?:\n[ \t]*)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n[ \t]*)?| *\n[ \t]*)(title))? *(?:\n+|$)/).replace("label",u).replace("title",/(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/).getRegex(),k=r(/^( {0,3}bull)([ \t][^\n]+?)?(?:\n|$)/).replace(/bull/g,o).getRegex(),d="address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|search|section|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul",f=/|$))/,x=r("^ {0,3}(?:<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:[^\\n]*\\n+|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?(?:\\?>\\n*|$)|\\n*|$)|\\n*|$)|)[\\s\\S]*?(?:(?:\\n[ \t]*)+\\n|$)|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ \t]*)+\\n|$)|(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ \t]*)+\\n|$))","i").replace("comment",f).replace("tag",d).replace("attribute",/ +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex(),b=r(p).replace("hr",l).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("|lheading","").replace("|table","").replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html",")|<(?:script|pre|style|textarea|!--)").replace("tag",d).getRegex(),m={blockquote:r(/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/).replace("paragraph",b).getRegex(),code:/^((?: {4}| {0,3}\t)[^\n]+(?:\n(?:[ \t]*(?:\n|$))*)?)+/,def:g,fences:/^ {0,3}(`{3,}(?=[^`\n]*(?:\n|$))|~{3,})([^\n]*)(?:\n|$)(?:|([\s\S]*?)(?:\n|$))(?: {0,3}\1[~`]* *(?=\n|$)|$)/,heading:/^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/,hr:l,html:x,lheading:c,list:k,newline:/^(?:[ \t]*(?:\n|$))+/,paragraph:b,table:s,text:/^[^\n]+/},w=r("^ *([^\\n ].*)\\n {0,3}((?:\\| *)?:?-+:? *(?:\\| *:?-+:? *)*(?:\\| *)?)(?:\\n((?:(?! *\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)").replace("hr",l).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("blockquote"," {0,3}>").replace("code","(?: {4}| {0,3}\t)[^\\n]").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html",")|<(?:script|pre|style|textarea|!--)").replace("tag",d).getRegex(),y={...m,lheading:h,table:w,paragraph:r(p).replace("hr",l).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("|lheading","").replace("table",w).replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html",")|<(?:script|pre|style|textarea|!--)").replace("tag",d).getRegex()},$={...m,html:r("^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+? *(?:\\n{2,}|\\s*$)|\\s]*)*?/?> *(?:\\n{2,}|\\s*$))").replace("comment",f).replace(/tag/g,"(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(),def:/^ *\[([^\]]+)\]: *]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/,heading:/^(#{1,6})(.*)(?:\n+|$)/,fences:s,lheading:/^(.+?)\n {0,3}(=+|-+) *(?:\n+|$)/,paragraph:r(p).replace("hr",l).replace("heading"," *#{1,6} *[^\n]").replace("lheading",c).replace("|table","").replace("blockquote"," {0,3}>").replace("|fences","").replace("|list","").replace("|html","").replace("|tag","").getRegex()},R=/^( {2,}|\\)\n(?!\s*$)/,S=/[\p{P}\p{S}]/u,T=/[\s\p{P}\p{S}]/u,z=/[^\s\p{P}\p{S}]/u,A=r(/^((?![*_])punctSpace)/,"u").replace(/punctSpace/g,T).getRegex(),_=/(?!~)[\p{P}\p{S}]/u,P=/^(?:\*+(?:((?!\*)punct)|[^\s*]))|^_+(?:((?!_)punct)|([^\s_]))/,I=r(P,"u").replace(/punct/g,S).getRegex(),L=r(P,"u").replace(/punct/g,_).getRegex(),B="^[^_*]*?__[^_*]*?\\*[^_*]*?(?=__)|[^*]+(?=[^*])|(?!\\*)punct(\\*+)(?=[\\s]|$)|notPunctSpace(\\*+)(?!\\*)(?=punctSpace|$)|(?!\\*)punctSpace(\\*+)(?=notPunctSpace)|[\\s](\\*+)(?!\\*)(?=punct)|(?!\\*)punct(\\*+)(?!\\*)(?=punct)|notPunctSpace(\\*+)(?=notPunctSpace)",C=r(B,"gu").replace(/notPunctSpace/g,z).replace(/punctSpace/g,T).replace(/punct/g,S).getRegex(),q=r(B,"gu").replace(/notPunctSpace/g,/(?:[^\s\p{P}\p{S}]|~)/u).replace(/punctSpace/g,/(?!~)[\s\p{P}\p{S}]/u).replace(/punct/g,_).getRegex(),E=r("^[^_*]*?\\*\\*[^_*]*?_[^_*]*?(?=\\*\\*)|[^_]+(?=[^_])|(?!_)punct(_+)(?=[\\s]|$)|notPunctSpace(_+)(?!_)(?=punctSpace|$)|(?!_)punctSpace(_+)(?=notPunctSpace)|[\\s](_+)(?!_)(?=punct)|(?!_)punct(_+)(?!_)(?=punct)","gu").replace(/notPunctSpace/g,z).replace(/punctSpace/g,T).replace(/punct/g,S).getRegex(),Z=r(/\\(punct)/,"gu").replace(/punct/g,S).getRegex(),v=r(/^<(scheme:[^\s\x00-\x1f<>]*|email)>/).replace("scheme",/[a-zA-Z][a-zA-Z0-9+.-]{1,31}/).replace("email",/[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/).getRegex(),D=r(f).replace("(?:--\x3e|$)","--\x3e").getRegex(),M=r("^comment|^|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^|^").replace("comment",D).replace("attribute",/\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/).getRegex(),O=/(?:\[(?:\\.|[^\[\]\\])*\]|\\.|`[^`]*`|[^\[\]\\`])*?/,Q=r(/^!?\[(label)\]\(\s*(href)(?:(?:[ \t]*(?:\n[ \t]*)?)(title))?\s*\)/).replace("label",O).replace("href",/<(?:\\.|[^\n<>\\])+>|[^ \t\n\x00-\x1f]*/).replace("title",/"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/).getRegex(),j=r(/^!?\[(label)\]\[(ref)\]/).replace("label",O).replace("ref",u).getRegex(),N=r(/^!?\[(ref)\](?:\[\])?/).replace("ref",u).getRegex(),G={_backpedal:s,anyPunctuation:Z,autolink:v,blockSkip:/\[[^[\]]*?\]\((?:\\.|[^\\\(\)]|\((?:\\.|[^\\\(\)])*\))*\)|`[^`]*?`|<[^<>]*?>/g,br:R,code:/^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/,del:s,emStrongLDelim:I,emStrongRDelimAst:C,emStrongRDelimUnd:E,escape:/^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/,link:Q,nolink:N,punctuation:A,reflink:j,reflinkSearch:r("reflink|nolink(?!\\()","g").replace("reflink",j).replace("nolink",N).getRegex(),tag:M,text:/^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\":">",'"':""","'":"'"},V=e=>K[e];function W(e,t){if(t){if(i.escapeTest.test(e))return e.replace(i.escapeReplace,V)}else if(i.escapeTestNoEncode.test(e))return e.replace(i.escapeReplaceNoEncode,V);return e}function Y(e){try{e=encodeURI(e).replace(i.percentDecode,"%")}catch{return null}return e}function ee(e,t){const n=e.replace(i.findPipe,((e,t,n)=>{let s=!1,r=t;for(;--r>=0&&"\\"===n[r];)s=!s;return s?"|":" |"})).split(i.splitPipe);let s=0;if(n[0].trim()||n.shift(),n.length>0&&!n.at(-1)?.trim()&&n.pop(),t)if(n.length>t)n.splice(t);else for(;n.length0)return{type:"space",raw:t[0]}}code(e){const t=this.rules.block.code.exec(e);if(t){const e=t[0].replace(this.rules.other.codeRemoveIndent,"");return{type:"code",raw:t[0],codeBlockStyle:"indented",text:this.options.pedantic?e:te(e,"\n")}}}fences(e){const t=this.rules.block.fences.exec(e);if(t){const e=t[0],n=function(e,t,n){const s=e.match(n.other.indentCodeCompensation);if(null===s)return t;const r=s[1];return t.split("\n").map((e=>{const t=e.match(n.other.beginningSpace);if(null===t)return e;const[s]=t;return s.length>=r.length?e.slice(r.length):e})).join("\n")}(e,t[3]||"",this.rules);return{type:"code",raw:e,lang:t[2]?t[2].trim().replace(this.rules.inline.anyPunctuation,"$1"):t[2],text:n}}}heading(e){const t=this.rules.block.heading.exec(e);if(t){let e=t[2].trim();if(this.rules.other.endingHash.test(e)){const t=te(e,"#");this.options.pedantic?e=t.trim():t&&!this.rules.other.endingSpaceChar.test(t)||(e=t.trim())}return{type:"heading",raw:t[0],depth:t[1].length,text:e,tokens:this.lexer.inline(e)}}}hr(e){const t=this.rules.block.hr.exec(e);if(t)return{type:"hr",raw:te(t[0],"\n")}}blockquote(e){const t=this.rules.block.blockquote.exec(e);if(t){let e=te(t[0],"\n").split("\n"),n="",s="";const r=[];for(;e.length>0;){let t=!1;const i=[];let l;for(l=0;l1,r={type:"list",raw:"",ordered:s,start:s?+n.slice(0,-1):"",loose:!1,items:[]};n=s?`\\d{1,9}\\${n.slice(-1)}`:`\\${n}`,this.options.pedantic&&(n=s?n:"[*+-]");const i=this.rules.other.listItemRegex(n);let l=!1;for(;e;){let n=!1,s="",o="";if(!(t=i.exec(e)))break;if(this.rules.block.hr.test(e))break;s=t[0],e=e.substring(s.length);let a=t[2].split("\n",1)[0].replace(this.rules.other.listReplaceTabs,(e=>" ".repeat(3*e.length))),c=e.split("\n",1)[0],h=!a.trim(),p=0;if(this.options.pedantic?(p=2,o=a.trimStart()):h?p=t[1].length+1:(p=t[2].search(this.rules.other.nonSpaceChar),p=p>4?1:p,o=a.slice(p),p+=t[1].length),h&&this.rules.other.blankLine.test(c)&&(s+=c+"\n",e=e.substring(c.length+1),n=!0),!n){const t=this.rules.other.nextBulletRegex(p),n=this.rules.other.hrRegex(p),r=this.rules.other.fencesBeginRegex(p),i=this.rules.other.headingBeginRegex(p),l=this.rules.other.htmlBeginRegex(p);for(;e;){const u=e.split("\n",1)[0];let g;if(c=u,this.options.pedantic?(c=c.replace(this.rules.other.listReplaceNesting," "),g=c):g=c.replace(this.rules.other.tabCharGlobal," "),r.test(c))break;if(i.test(c))break;if(l.test(c))break;if(t.test(c))break;if(n.test(c))break;if(g.search(this.rules.other.nonSpaceChar)>=p||!c.trim())o+="\n"+g.slice(p);else{if(h)break;if(a.replace(this.rules.other.tabCharGlobal," ").search(this.rules.other.nonSpaceChar)>=4)break;if(r.test(a))break;if(i.test(a))break;if(n.test(a))break;o+="\n"+c}h||c.trim()||(h=!0),s+=u+"\n",e=e.substring(u.length+1),a=g.slice(p)}}r.loose||(l?r.loose=!0:this.rules.other.doubleBlankLine.test(s)&&(l=!0));let u,g=null;this.options.gfm&&(g=this.rules.other.listIsTask.exec(o),g&&(u="[ ] "!==g[0],o=o.replace(this.rules.other.listReplaceTask,""))),r.items.push({type:"list_item",raw:s,task:!!g,checked:u,loose:!1,text:o,tokens:[]}),r.raw+=s}const o=r.items.at(-1);if(!o)return;o.raw=o.raw.trimEnd(),o.text=o.text.trimEnd(),r.raw=r.raw.trimEnd();for(let e=0;e"space"===e.type)),n=t.length>0&&t.some((e=>this.rules.other.anyLine.test(e.raw)));r.loose=n}if(r.loose)for(let e=0;e({text:e,tokens:this.lexer.inline(e),header:!1,align:i.align[t]}))));return i}}lheading(e){const t=this.rules.block.lheading.exec(e);if(t)return{type:"heading",raw:t[0],depth:"="===t[2].charAt(0)?1:2,text:t[1],tokens:this.lexer.inline(t[1])}}paragraph(e){const t=this.rules.block.paragraph.exec(e);if(t){const e="\n"===t[1].charAt(t[1].length-1)?t[1].slice(0,-1):t[1];return{type:"paragraph",raw:t[0],text:e,tokens:this.lexer.inline(e)}}}text(e){const t=this.rules.block.text.exec(e);if(t)return{type:"text",raw:t[0],text:t[0],tokens:this.lexer.inline(t[0])}}escape(e){const t=this.rules.inline.escape.exec(e);if(t)return{type:"escape",raw:t[0],text:t[1]}}tag(e){const t=this.rules.inline.tag.exec(e);if(t)return!this.lexer.state.inLink&&this.rules.other.startATag.test(t[0])?this.lexer.state.inLink=!0:this.lexer.state.inLink&&this.rules.other.endATag.test(t[0])&&(this.lexer.state.inLink=!1),!this.lexer.state.inRawBlock&&this.rules.other.startPreScriptTag.test(t[0])?this.lexer.state.inRawBlock=!0:this.lexer.state.inRawBlock&&this.rules.other.endPreScriptTag.test(t[0])&&(this.lexer.state.inRawBlock=!1),{type:"html",raw:t[0],inLink:this.lexer.state.inLink,inRawBlock:this.lexer.state.inRawBlock,block:!1,text:t[0]}}link(e){const t=this.rules.inline.link.exec(e);if(t){const e=t[2].trim();if(!this.options.pedantic&&this.rules.other.startAngleBracket.test(e)){if(!this.rules.other.endAngleBracket.test(e))return;const t=te(e.slice(0,-1),"\\");if((e.length-t.length)%2==0)return}else{const e=function(e,t){if(-1===e.indexOf(t[1]))return-1;let n=0;for(let s=0;s0?-2:-1}(t[2],"()");if(-2===e)return;if(e>-1){const n=(0===t[0].indexOf("!")?5:4)+t[1].length+e;t[2]=t[2].substring(0,e),t[0]=t[0].substring(0,n).trim(),t[3]=""}}let n=t[2],s="";if(this.options.pedantic){const e=this.rules.other.pedanticHrefTitle.exec(n);e&&(n=e[1],s=e[3])}else s=t[3]?t[3].slice(1,-1):"";return n=n.trim(),this.rules.other.startAngleBracket.test(n)&&(n=this.options.pedantic&&!this.rules.other.endAngleBracket.test(e)?n.slice(1):n.slice(1,-1)),ne(t,{href:n?n.replace(this.rules.inline.anyPunctuation,"$1"):n,title:s?s.replace(this.rules.inline.anyPunctuation,"$1"):s},t[0],this.lexer,this.rules)}}reflink(e,t){let n;if((n=this.rules.inline.reflink.exec(e))||(n=this.rules.inline.nolink.exec(e))){const e=t[(n[2]||n[1]).replace(this.rules.other.multipleSpaceGlobal," ").toLowerCase()];if(!e){const e=n[0].charAt(0);return{type:"text",raw:e,text:e}}return ne(n,e,n[0],this.lexer,this.rules)}}emStrong(e,t,n=""){let s=this.rules.inline.emStrongLDelim.exec(e);if(!s)return;if(s[3]&&n.match(this.rules.other.unicodeAlphaNumeric))return;if(!(s[1]||s[2]||"")||!n||this.rules.inline.punctuation.exec(n)){const n=[...s[0]].length-1;let r,i,l=n,o=0;const a="*"===s[0][0]?this.rules.inline.emStrongRDelimAst:this.rules.inline.emStrongRDelimUnd;for(a.lastIndex=0,t=t.slice(-1*e.length+n);null!=(s=a.exec(t));){if(r=s[1]||s[2]||s[3]||s[4]||s[5]||s[6],!r)continue;if(i=[...r].length,s[3]||s[4]){l+=i;continue}if((s[5]||s[6])&&n%3&&!((n+i)%3)){o+=i;continue}if(l-=i,l>0)continue;i=Math.min(i,i+l+o);const t=[...s[0]][0].length,a=e.slice(0,n+s.index+t+i);if(Math.min(n,i)%2){const e=a.slice(1,-1);return{type:"em",raw:a,text:e,tokens:this.lexer.inlineTokens(e)}}const c=a.slice(2,-2);return{type:"strong",raw:a,text:c,tokens:this.lexer.inlineTokens(c)}}}}codespan(e){const t=this.rules.inline.code.exec(e);if(t){let e=t[2].replace(this.rules.other.newLineCharGlobal," ");const n=this.rules.other.nonSpaceChar.test(e),s=this.rules.other.startingSpaceChar.test(e)&&this.rules.other.endingSpaceChar.test(e);return n&&s&&(e=e.substring(1,e.length-1)),{type:"codespan",raw:t[0],text:e}}}br(e){const t=this.rules.inline.br.exec(e);if(t)return{type:"br",raw:t[0]}}del(e){const t=this.rules.inline.del.exec(e);if(t)return{type:"del",raw:t[0],text:t[2],tokens:this.lexer.inlineTokens(t[2])}}autolink(e){const t=this.rules.inline.autolink.exec(e);if(t){let e,n;return"@"===t[2]?(e=t[1],n="mailto:"+e):(e=t[1],n=e),{type:"link",raw:t[0],text:e,href:n,tokens:[{type:"text",raw:e,text:e}]}}}url(e){let t;if(t=this.rules.inline.url.exec(e)){let e,n;if("@"===t[2])e=t[0],n="mailto:"+e;else{let s;do{s=t[0],t[0]=this.rules.inline._backpedal.exec(t[0])?.[0]??""}while(s!==t[0]);e=t[0],n="www."===t[1]?"http://"+t[0]:t[0]}return{type:"link",raw:t[0],text:e,href:n,tokens:[{type:"text",raw:e,text:e}]}}}inlineText(e){const t=this.rules.inline.text.exec(e);if(t){const e=this.lexer.state.inRawBlock;return{type:"text",raw:t[0],text:t[0],escaped:e}}}}class re{tokens;options;state;tokenizer;inlineQueue;constructor(t){this.tokens=[],this.tokens.links=Object.create(null),this.options=t||e.defaults,this.options.tokenizer=this.options.tokenizer||new se,this.tokenizer=this.options.tokenizer,this.tokenizer.options=this.options,this.tokenizer.lexer=this,this.inlineQueue=[],this.state={inLink:!1,inRawBlock:!1,top:!0};const n={other:i,block:U.normal,inline:J.normal};this.options.pedantic?(n.block=U.pedantic,n.inline=J.pedantic):this.options.gfm&&(n.block=U.gfm,this.options.breaks?n.inline=J.breaks:n.inline=J.gfm),this.tokenizer.rules=n}static get rules(){return{block:U,inline:J}}static lex(e,t){return new re(t).lex(e)}static lexInline(e,t){return new re(t).inlineTokens(e)}lex(e){e=e.replace(i.carriageReturn,"\n"),this.blockTokens(e,this.tokens);for(let e=0;e!!(s=n.call({lexer:this},e,t))&&(e=e.substring(s.raw.length),t.push(s),!0))))continue;if(s=this.tokenizer.space(e)){e=e.substring(s.raw.length);const n=t.at(-1);1===s.raw.length&&void 0!==n?n.raw+="\n":t.push(s);continue}if(s=this.tokenizer.code(e)){e=e.substring(s.raw.length);const n=t.at(-1);"paragraph"===n?.type||"text"===n?.type?(n.raw+="\n"+s.raw,n.text+="\n"+s.text,this.inlineQueue.at(-1).src=n.text):t.push(s);continue}if(s=this.tokenizer.fences(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.heading(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.hr(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.blockquote(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.list(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.html(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.def(e)){e=e.substring(s.raw.length);const n=t.at(-1);"paragraph"===n?.type||"text"===n?.type?(n.raw+="\n"+s.raw,n.text+="\n"+s.raw,this.inlineQueue.at(-1).src=n.text):this.tokens.links[s.tag]||(this.tokens.links[s.tag]={href:s.href,title:s.title});continue}if(s=this.tokenizer.table(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.lheading(e)){e=e.substring(s.raw.length),t.push(s);continue}let r=e;if(this.options.extensions?.startBlock){let t=1/0;const n=e.slice(1);let s;this.options.extensions.startBlock.forEach((e=>{s=e.call({lexer:this},n),"number"==typeof s&&s>=0&&(t=Math.min(t,s))})),t<1/0&&t>=0&&(r=e.substring(0,t+1))}if(this.state.top&&(s=this.tokenizer.paragraph(r))){const i=t.at(-1);n&&"paragraph"===i?.type?(i.raw+="\n"+s.raw,i.text+="\n"+s.text,this.inlineQueue.pop(),this.inlineQueue.at(-1).src=i.text):t.push(s),n=r.length!==e.length,e=e.substring(s.raw.length)}else if(s=this.tokenizer.text(e)){e=e.substring(s.raw.length);const n=t.at(-1);"text"===n?.type?(n.raw+="\n"+s.raw,n.text+="\n"+s.text,this.inlineQueue.pop(),this.inlineQueue.at(-1).src=n.text):t.push(s)}else if(e){const t="Infinite loop on byte: "+e.charCodeAt(0);if(this.options.silent){console.error(t);break}throw new Error(t)}}return this.state.top=!0,t}inline(e,t=[]){return this.inlineQueue.push({src:e,tokens:t}),t}inlineTokens(e,t=[]){let n=e,s=null;if(this.tokens.links){const e=Object.keys(this.tokens.links);if(e.length>0)for(;null!=(s=this.tokenizer.rules.inline.reflinkSearch.exec(n));)e.includes(s[0].slice(s[0].lastIndexOf("[")+1,-1))&&(n=n.slice(0,s.index)+"["+"a".repeat(s[0].length-2)+"]"+n.slice(this.tokenizer.rules.inline.reflinkSearch.lastIndex))}for(;null!=(s=this.tokenizer.rules.inline.anyPunctuation.exec(n));)n=n.slice(0,s.index)+"++"+n.slice(this.tokenizer.rules.inline.anyPunctuation.lastIndex);for(;null!=(s=this.tokenizer.rules.inline.blockSkip.exec(n));)n=n.slice(0,s.index)+"["+"a".repeat(s[0].length-2)+"]"+n.slice(this.tokenizer.rules.inline.blockSkip.lastIndex);let r=!1,i="";for(;e;){let s;if(r||(i=""),r=!1,this.options.extensions?.inline?.some((n=>!!(s=n.call({lexer:this},e,t))&&(e=e.substring(s.raw.length),t.push(s),!0))))continue;if(s=this.tokenizer.escape(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.tag(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.link(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.reflink(e,this.tokens.links)){e=e.substring(s.raw.length);const n=t.at(-1);"text"===s.type&&"text"===n?.type?(n.raw+=s.raw,n.text+=s.text):t.push(s);continue}if(s=this.tokenizer.emStrong(e,n,i)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.codespan(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.br(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.del(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.autolink(e)){e=e.substring(s.raw.length),t.push(s);continue}if(!this.state.inLink&&(s=this.tokenizer.url(e))){e=e.substring(s.raw.length),t.push(s);continue}let l=e;if(this.options.extensions?.startInline){let t=1/0;const n=e.slice(1);let s;this.options.extensions.startInline.forEach((e=>{s=e.call({lexer:this},n),"number"==typeof s&&s>=0&&(t=Math.min(t,s))})),t<1/0&&t>=0&&(l=e.substring(0,t+1))}if(s=this.tokenizer.inlineText(l)){e=e.substring(s.raw.length),"_"!==s.raw.slice(-1)&&(i=s.raw.slice(-1)),r=!0;const n=t.at(-1);"text"===n?.type?(n.raw+=s.raw,n.text+=s.text):t.push(s)}else if(e){const t="Infinite loop on byte: "+e.charCodeAt(0);if(this.options.silent){console.error(t);break}throw new Error(t)}}return t}}class ie{options;parser;constructor(t){this.options=t||e.defaults}space(e){return""}code({text:e,lang:t,escaped:n}){const s=(t||"").match(i.notSpaceStart)?.[0],r=e.replace(i.endingNewline,"")+"\n";return s?'
'+(n?r:W(r,!0))+"
\n":"
"+(n?r:W(r,!0))+"
\n"}blockquote({tokens:e}){return`
\n${this.parser.parse(e)}
\n`}html({text:e}){return e}heading({tokens:e,depth:t}){return`${this.parser.parseInline(e)}\n`}hr(e){return"
\n"}list(e){const t=e.ordered,n=e.start;let s="";for(let t=0;t\n"+s+"\n"}listitem(e){let t="";if(e.task){const n=this.checkbox({checked:!!e.checked});e.loose?"paragraph"===e.tokens[0]?.type?(e.tokens[0].text=n+" "+e.tokens[0].text,e.tokens[0].tokens&&e.tokens[0].tokens.length>0&&"text"===e.tokens[0].tokens[0].type&&(e.tokens[0].tokens[0].text=n+" "+W(e.tokens[0].tokens[0].text),e.tokens[0].tokens[0].escaped=!0)):e.tokens.unshift({type:"text",raw:n+" ",text:n+" ",escaped:!0}):t+=n+" "}return t+=this.parser.parse(e.tokens,!!e.loose),`
  • ${t}
  • \n`}checkbox({checked:e}){return"'}paragraph({tokens:e}){return`

    ${this.parser.parseInline(e)}

    \n`}table(e){let t="",n="";for(let t=0;t${s}`),"\n\n"+t+"\n"+s+"
    \n"}tablerow({text:e}){return`\n${e}\n`}tablecell(e){const t=this.parser.parseInline(e.tokens),n=e.header?"th":"td";return(e.align?`<${n} align="${e.align}">`:`<${n}>`)+t+`\n`}strong({tokens:e}){return`${this.parser.parseInline(e)}`}em({tokens:e}){return`${this.parser.parseInline(e)}`}codespan({text:e}){return`${W(e,!0)}`}br(e){return"
    "}del({tokens:e}){return`${this.parser.parseInline(e)}`}link({href:e,title:t,tokens:n}){const s=this.parser.parseInline(n),r=Y(e);if(null===r)return s;let i='
    ",i}image({href:e,title:t,text:n,tokens:s}){s&&(n=this.parser.parseInline(s,this.parser.textRenderer));const r=Y(e);if(null===r)return W(n);let i=`${n}{const r=e[s].flat(1/0);n=n.concat(this.walkTokens(r,t))})):e.tokens&&(n=n.concat(this.walkTokens(e.tokens,t)))}}return n}use(...e){const t=this.defaults.extensions||{renderers:{},childTokens:{}};return e.forEach((e=>{const n={...e};if(n.async=this.defaults.async||n.async||!1,e.extensions&&(e.extensions.forEach((e=>{if(!e.name)throw new Error("extension name required");if("renderer"in e){const n=t.renderers[e.name];t.renderers[e.name]=n?function(...t){let s=e.renderer.apply(this,t);return!1===s&&(s=n.apply(this,t)),s}:e.renderer}if("tokenizer"in e){if(!e.level||"block"!==e.level&&"inline"!==e.level)throw new Error("extension level must be 'block' or 'inline'");const n=t[e.level];n?n.unshift(e.tokenizer):t[e.level]=[e.tokenizer],e.start&&("block"===e.level?t.startBlock?t.startBlock.push(e.start):t.startBlock=[e.start]:"inline"===e.level&&(t.startInline?t.startInline.push(e.start):t.startInline=[e.start]))}"childTokens"in e&&e.childTokens&&(t.childTokens[e.name]=e.childTokens)})),n.extensions=t),e.renderer){const t=this.defaults.renderer||new ie(this.defaults);for(const n in e.renderer){if(!(n in t))throw new Error(`renderer '${n}' does not exist`);if(["options","parser"].includes(n))continue;const s=n,r=e.renderer[s],i=t[s];t[s]=(...e)=>{let n=r.apply(t,e);return!1===n&&(n=i.apply(t,e)),n||""}}n.renderer=t}if(e.tokenizer){const t=this.defaults.tokenizer||new se(this.defaults);for(const n in e.tokenizer){if(!(n in t))throw new Error(`tokenizer '${n}' does not exist`);if(["options","rules","lexer"].includes(n))continue;const s=n,r=e.tokenizer[s],i=t[s];t[s]=(...e)=>{let n=r.apply(t,e);return!1===n&&(n=i.apply(t,e)),n}}n.tokenizer=t}if(e.hooks){const t=this.defaults.hooks||new ae;for(const n in e.hooks){if(!(n in t))throw new Error(`hook '${n}' does not exist`);if(["options","block"].includes(n))continue;const s=n,r=e.hooks[s],i=t[s];ae.passThroughHooks.has(n)?t[s]=e=>{if(this.defaults.async)return Promise.resolve(r.call(t,e)).then((e=>i.call(t,e)));const n=r.call(t,e);return i.call(t,n)}:t[s]=(...e)=>{let n=r.apply(t,e);return!1===n&&(n=i.apply(t,e)),n}}n.hooks=t}if(e.walkTokens){const t=this.defaults.walkTokens,s=e.walkTokens;n.walkTokens=function(e){let n=[];return n.push(s.call(this,e)),t&&(n=n.concat(t.call(this,e))),n}}this.defaults={...this.defaults,...n}})),this}setOptions(e){return this.defaults={...this.defaults,...e},this}lexer(e,t){return re.lex(e,t??this.defaults)}parser(e,t){return oe.parse(e,t??this.defaults)}parseMarkdown(e){return(t,n)=>{const s={...n},r={...this.defaults,...s},i=this.onError(!!r.silent,!!r.async);if(!0===this.defaults.async&&!1===s.async)return i(new Error("marked(): The async option was set to true by an extension. Remove async: false from the parse options object to return a Promise."));if(null==t)return i(new Error("marked(): input parameter is undefined or null"));if("string"!=typeof t)return i(new Error("marked(): input parameter is of type "+Object.prototype.toString.call(t)+", string expected"));r.hooks&&(r.hooks.options=r,r.hooks.block=e);const l=r.hooks?r.hooks.provideLexer():e?re.lex:re.lexInline,o=r.hooks?r.hooks.provideParser():e?oe.parse:oe.parseInline;if(r.async)return Promise.resolve(r.hooks?r.hooks.preprocess(t):t).then((e=>l(e,r))).then((e=>r.hooks?r.hooks.processAllTokens(e):e)).then((e=>r.walkTokens?Promise.all(this.walkTokens(e,r.walkTokens)).then((()=>e)):e)).then((e=>o(e,r))).then((e=>r.hooks?r.hooks.postprocess(e):e)).catch(i);try{r.hooks&&(t=r.hooks.preprocess(t));let e=l(t,r);r.hooks&&(e=r.hooks.processAllTokens(e)),r.walkTokens&&this.walkTokens(e,r.walkTokens);let n=o(e,r);return r.hooks&&(n=r.hooks.postprocess(n)),n}catch(e){return i(e)}}}onError(e,t){return n=>{if(n.message+="\nPlease report this to https://github.com/markedjs/marked.",e){const e="

    An error occurred:

    "+W(n.message+"",!0)+"
    ";return t?Promise.resolve(e):e}if(t)return Promise.reject(n);throw n}}}const he=new ce;function pe(e,t){return he.parse(e,t)}pe.options=pe.setOptions=function(e){return he.setOptions(e),pe.defaults=he.defaults,n(pe.defaults),pe},pe.getDefaults=t,pe.defaults=e.defaults,pe.use=function(...e){return he.use(...e),pe.defaults=he.defaults,n(pe.defaults),pe},pe.walkTokens=function(e,t){return he.walkTokens(e,t)},pe.parseInline=he.parseInline,pe.Parser=oe,pe.parser=oe.parse,pe.Renderer=ie,pe.TextRenderer=le,pe.Lexer=re,pe.lexer=re.lex,pe.Tokenizer=se,pe.Hooks=ae,pe.parse=pe;const ue=pe.options,ge=pe.setOptions,ke=pe.use,de=pe.walkTokens,fe=pe.parseInline,xe=pe,be=oe.parse,me=re.lex;e.Hooks=ae,e.Lexer=re,e.Marked=ce,e.Parser=oe,e.Renderer=ie,e.TextRenderer=le,e.Tokenizer=se,e.getDefaults=t,e.lexer=me,e.marked=pe,e.options=ue,e.parse=xe,e.parseInline=fe,e.parser=be,e.setOptions=ge,e.use=ke,e.walkTokens=de})); 7 | -------------------------------------------------------------------------------- /TextPix/TextPix.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | com.apple.security.network.client 10 | 11 | com.apple.security.network.server 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /TextPix/TextPixApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextPixApp.swift 3 | // TextPix 4 | // 5 | // Created by HAIRONG ZHU on 2025/4/29. 6 | // 7 | 8 | import SwiftUI 9 | import KeyboardShortcuts 10 | import UserNotifications 11 | 12 | @main 13 | struct TextPixApp: App { 14 | @StateObject private var appState = AppState.shared 15 | private let screenshotManager = ScreenshotManager() 16 | private let keyboardManager: KeyboardManager 17 | 18 | init() { 19 | keyboardManager = KeyboardManager(screenshotManager: screenshotManager) 20 | _ = NotificationManager.shared 21 | } 22 | 23 | var body: some Scene { 24 | MenuBarExtra("TextPix", systemImage: "text.viewfinder") { 25 | MainTabView() 26 | .frame(minWidth: 600, minHeight: 450) 27 | } 28 | .menuBarExtraStyle(.window) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /TextPix/Tools/GPTImageRequestManager.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import SwiftUI 3 | 4 | @MainActor 5 | class GPTImageRequestManager { 6 | private let appState = AppState.shared 7 | 8 | // 将NSImage转换为Base64字符串 9 | private func convertImageToBase64(_ image: NSImage) -> String? { 10 | guard let cgImage = image.cgImage(forProposedRect: nil, context: nil, hints: nil) else { 11 | return nil 12 | } 13 | 14 | let bitmapRep = NSBitmapImageRep(cgImage: cgImage) 15 | guard let data = bitmapRep.representation(using: .jpeg, properties: [.compressionFactor: 0.7]) else { 16 | return nil 17 | } 18 | 19 | return data.base64EncodedString() 20 | } 21 | 22 | // 使用图片和系统提示词发送API请求 23 | @MainActor 24 | func requestCompletion(image: NSImage, systemPrompt: String) async throws { 25 | guard let base64Image = convertImageToBase64(image) else { 26 | throw NSError(domain: "GPTImageRequestManager", code: 1, userInfo: [NSLocalizedDescriptionKey: "无法将图片转换为base64格式"]) 27 | } 28 | 29 | appState.isProcessing = true 30 | 31 | // 使用defer确保函数退出时isLoading设为false 32 | defer { 33 | appState.isProcessing = false 34 | } 35 | 36 | // 创建API请求URL 37 | let url = URL(string: appState.endpoint)! 38 | var request = URLRequest(url: url) 39 | request.httpMethod = "POST" 40 | request.addValue("Bearer \(appState.apiKey)", forHTTPHeaderField: "Authorization") 41 | request.addValue("application/json", forHTTPHeaderField: "Content-Type") 42 | 43 | // 创建请求体 44 | let messages: [[String: Any]] = [ 45 | [ 46 | "role": "system", 47 | "content": appState.systemPrompt 48 | ], 49 | [ 50 | "role": "user", 51 | "content": [ 52 | [ 53 | "type": "image_url", 54 | "image_url": [ 55 | "url": "data:image/jpeg;base64,\(base64Image)" 56 | ] 57 | ] 58 | ] 59 | ] 60 | ] 61 | 62 | // 先生成一个通用的 requestBody 63 | var requestBody: [String: Any] = [ 64 | "model": appState.model, 65 | "messages": messages 66 | ] 67 | 68 | // 只有在 inferenceEnabled==true 时再补充 reasoning_effort 字段 69 | if appState.inferenceEnabled { 70 | requestBody["reasoning_effort"] = appState.inferenceLevel 71 | } 72 | 73 | do { 74 | request.httpBody = try JSONSerialization.data(withJSONObject: requestBody) 75 | } catch { 76 | throw NSError(domain: "GPTImageRequestManager", code: 3, userInfo: [NSLocalizedDescriptionKey: "请求体序列化失败"]) 77 | } 78 | 79 | // 发送请求 80 | do { 81 | let (data, response) = try await URLSession.shared.data(for: request) 82 | 83 | // 检查响应是否有效 84 | guard let httpResponse = response as? HTTPURLResponse else { 85 | throw NSError(domain: "GPTImageRequestManager", code: 2, userInfo: [NSLocalizedDescriptionKey: "无效的HTTP响应"]) 86 | } 87 | 88 | if httpResponse.statusCode != 200 { 89 | let errorString = String(data: data, encoding: .utf8) ?? "未知错误" 90 | throw NSError(domain: "GPTImageRequestManager", code: httpResponse.statusCode, userInfo: [NSLocalizedDescriptionKey: errorString]) 91 | } 92 | 93 | // 解析响应 94 | let decoder = JSONDecoder() 95 | let apiResponse = try decoder.decode(APIResponse.self, from: data) 96 | 97 | if let content = apiResponse.choices.first?.message.content { 98 | await MainActor.run { 99 | appState.markdownText = content 100 | } 101 | } else { 102 | await MainActor.run { 103 | appState.markdownText = "响应中没有内容" 104 | } 105 | } 106 | 107 | } catch { 108 | await MainActor.run { 109 | appState.markdownText = "错误: \(error.localizedDescription)" 110 | } 111 | throw error 112 | } 113 | } 114 | 115 | // 用于解码API响应的结构体 116 | struct APIResponse: Codable { 117 | let id: String 118 | let object: String 119 | let created: Int 120 | let model: String 121 | let choices: [Choice] 122 | let usage: Usage 123 | } 124 | 125 | struct Choice: Codable { 126 | let index: Int 127 | let message: Message 128 | let finishReason: String? 129 | 130 | enum CodingKeys: String, CodingKey { 131 | case index 132 | case message 133 | case finishReason = "finish_reason" 134 | } 135 | } 136 | 137 | struct Message: Codable { 138 | let role: String 139 | let content: String 140 | } 141 | 142 | struct Usage: Codable { 143 | let promptTokens: Int 144 | let completionTokens: Int 145 | let totalTokens: Int 146 | 147 | enum CodingKeys: String, CodingKey { 148 | case promptTokens = "prompt_tokens" 149 | case completionTokens = "completion_tokens" 150 | case totalTokens = "total_tokens" 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /TextPix/Tools/KeyboardManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeyboardManager.swift 3 | // TextPix 4 | // 5 | // Created by HAIRONG ZHU on 2025/4/30. 6 | // 7 | 8 | import KeyboardShortcuts 9 | import SwiftUI 10 | 11 | extension KeyboardShortcuts.Name { 12 | static let capture = Self( 13 | "capture", 14 | default: .init(.two, modifiers: [.command, .shift]) 15 | ) 16 | } 17 | 18 | class KeyboardManager { 19 | private let screenshotManager: ScreenshotManager 20 | 21 | init(screenshotManager: ScreenshotManager) { 22 | self.screenshotManager = screenshotManager 23 | setupShortcuts() 24 | } 25 | 26 | private func setupShortcuts() { 27 | KeyboardShortcuts.onKeyUp(for: .capture) { [weak self] in 28 | guard let self = self else { return } 29 | Task { 30 | await self.screenshotManager.captureWithSystemPicker() 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /TextPix/Tools/NotificationManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NotificationManager.swift 3 | // TextPix 4 | // 5 | // Created by HAIRONG ZHU on 2025/6/13. 6 | // 7 | 8 | import Foundation 9 | import UserNotifications 10 | 11 | /// 统一的本地通知封装 12 | @MainActor 13 | final class NotificationManager: NSObject, UNUserNotificationCenterDelegate { 14 | 15 | static let shared = NotificationManager() // 单例 16 | private override init() { 17 | super.init() 18 | let center = UNUserNotificationCenter.current() 19 | center.delegate = self // 让前台也能弹横幅 20 | Task { await requestAuthorization() } 21 | } 22 | 23 | /// 通知权限申请(只在第一次调用时弹窗) 24 | private func requestAuthorization() async { 25 | let center = UNUserNotificationCenter.current() 26 | do { 27 | try await center.requestAuthorization(options: [.alert, .sound, .badge]) 28 | } catch { 29 | // 权限弹窗被系统阻止时也不影响主流程 30 | print("Notification authorization error: \(error.localizedDescription)") 31 | } 32 | } 33 | 34 | /// 发送 OCR 结果通知 35 | func sendOCRResult(success: Bool, errorMessage: String? = nil) { 36 | let content = UNMutableNotificationContent() 37 | content.title = success ? "识别成功" : "识别失败" 38 | content.body = success ? "文本已生成并可复制使用。" : (errorMessage ?? "未知错误") 39 | content.sound = success ? .default : UNNotificationSound(named: UNNotificationSoundName("Glass.aiff")) 40 | 41 | let request = UNNotificationRequest( 42 | identifier: UUID().uuidString, 43 | content: content, 44 | trigger: nil // 立即送达 45 | ) 46 | UNUserNotificationCenter.current().add(request) 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /TextPix/Tools/OCRService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OCRService.swift 3 | // TextPix 4 | // 5 | // Created by Trae AI on 2025/5/2. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | @MainActor 12 | class OCRService { 13 | private let gptManager = GPTImageRequestManager() 14 | private let appState = AppState.shared 15 | 16 | func performOCR(on image: NSImage) async { 17 | await MainActor.run { 18 | appState.markdownText = "正在识别中..." 19 | appState.isProcessing = true 20 | appState.selectedTab = 0 // 切换到OCR结果标签页 21 | } 22 | 23 | do { 24 | // 使用 GPTImageRequestManager 发送请求 25 | // 注意:GPTImageRequestManager 内部已经处理了 appState.isProcessing 和 appState.ocrText 的更新 26 | // 这里我们只需要调用它,它会在完成或失败时更新 AppState 27 | try await gptManager.requestCompletion(image: image, systemPrompt: appState.systemPrompt) 28 | // 成功时,gptManager 内部会更新 appState.markdownText 29 | NotificationManager.shared.sendOCRResult(success: true) 30 | 31 | if (appState.autoCopy) { 32 | // 自动复制到剪切板 33 | let pasteboard = NSPasteboard.general 34 | pasteboard.clearContents() 35 | pasteboard.setString(appState.markdownText, forType: .string) 36 | } 37 | } catch { 38 | // 失败时,gptManager 内部会更新 appState.markdownText 为错误信息 39 | NotificationManager.shared.sendOCRResult(success: false, errorMessage: error.localizedDescription) 40 | // 确保即使出错也更新状态 41 | appState.isProcessing = false 42 | // 保留 gptManager 设置的错误信息 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /TextPix/Tools/ScreenshotManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScreenshotManager.swift 3 | // TextPix 4 | // 5 | // Created by HAIRONG ZHU on 2025/4/29. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @MainActor 11 | class ScreenshotManager: ObservableObject { 12 | private let appState = AppState.shared 13 | private let ocrService = OCRService() // 添加 OCR 服务实例 14 | 15 | func captureWithSystemPicker() async { 16 | let tmp = URL(fileURLWithPath: NSTemporaryDirectory()) 17 | .appendingPathComponent(UUID().uuidString + ".png") 18 | 19 | let task = Process() 20 | task.launchPath = "/usr/sbin/screencapture" 21 | task.arguments = ["-i", "-x", tmp.path] // -x 静默、-i 交互 22 | task.launch() 23 | task.waitUntilExit() 24 | 25 | if FileManager.default.fileExists(atPath: tmp.path), 26 | let img = NSImage(contentsOf: tmp) { 27 | appState.screenshotImage = img 28 | // 截图成功后,调用 OCR 服务 29 | Task { 30 | await ocrService.performOCR(on: img) 31 | } 32 | } 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /TextPix/Views/MainTabView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainTabView.swift 3 | // TextPix 4 | // 5 | // Created by HAIRONG ZHU on 2025/5/1. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct MainTabView: View { 11 | @ObservedObject private var appState = AppState.shared 12 | 13 | var body: some View { 14 | TabView(selection: $appState.selectedTab) { 15 | OCRResultView() 16 | .tag(0) 17 | .tabItem { 18 | Label("OCR", systemImage: "text.viewfinder") 19 | } 20 | 21 | ScreenshotView() 22 | .tag(1) 23 | .tabItem { 24 | Label("Original", systemImage: "photo") 25 | } 26 | 27 | SettingsView() 28 | .tag(2) 29 | .tabItem { 30 | Label("Setting", systemImage: "gear") 31 | } 32 | } 33 | .padding() 34 | } 35 | } 36 | 37 | #Preview { 38 | MainTabView() 39 | } 40 | -------------------------------------------------------------------------------- /TextPix/Views/OCRResultView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MarkDownDisplay.swift 3 | // TextPix 4 | // 5 | // Created by HAIRONG ZHU on 2025/5/1. 6 | // 7 | 8 | import SwiftUI 9 | import WebKit 10 | import Combine 11 | 12 | struct OCRResultView: View { 13 | @ObservedObject private var appState = AppState.shared 14 | 15 | var body: some View { 16 | Group { 17 | if appState.isProcessing { 18 | VStack { 19 | ProgressView() 20 | .progressViewStyle(CircularProgressViewStyle()) 21 | .scaleEffect(1.5) 22 | Text("正在识别中...") 23 | .font(.headline) 24 | .padding(.top) 25 | } 26 | .frame(maxWidth: .infinity, maxHeight: .infinity) 27 | } else { 28 | VStack { 29 | MarkdownLatexView(markdownText: appState.markdownText) 30 | .frame(minHeight: 100) 31 | .border(Color.gray.opacity(0.3)) 32 | .padding(.horizontal) 33 | 34 | Divider() 35 | 36 | TextEditor(text: $appState.markdownText) 37 | .font(.system(size: 14, design: .monospaced)) 38 | .padding(4) 39 | .border(Color.gray.opacity(0.3)) 40 | .padding(.horizontal) 41 | 42 | Button("Copy to Clipboard") { 43 | let pasteboard = NSPasteboard.general 44 | pasteboard.clearContents() 45 | pasteboard.setString(appState.markdownText, forType: .string) 46 | } 47 | .padding(.bottom) 48 | } 49 | } 50 | } 51 | } 52 | 53 | struct MarkdownLatexView: NSViewRepresentable { 54 | let markdownText: String 55 | 56 | func makeNSView(context: NSViewRepresentableContext) -> WKWebView { 57 | let configuration = WKWebViewConfiguration() 58 | let preferences = WKPreferences() 59 | 60 | // 现代方式启用 JavaScript 61 | let webpagePreferences = WKWebpagePreferences() 62 | webpagePreferences.allowsContentJavaScript = true 63 | configuration.defaultWebpagePreferences = webpagePreferences 64 | 65 | configuration.preferences = preferences 66 | configuration.userContentController.add(context.coordinator, name: "heightUpdate") 67 | 68 | let webView = WKWebView(frame: .zero, configuration: configuration) 69 | webView.navigationDelegate = context.coordinator 70 | 71 | // macOS版本特有设置 72 | webView.allowsMagnification = true 73 | 74 | // 初始化时加载HTML模板 75 | loadHTMLTemplate(webView: webView) 76 | 77 | return webView 78 | } 79 | 80 | func updateNSView(_ webView: WKWebView, context: NSViewRepresentableContext) { 81 | // 只有在页面已加载完成后才执行更新 82 | if context.coordinator.isHTMLLoaded { 83 | updateMarkdownContent(webView: webView) 84 | } 85 | } 86 | 87 | private func loadHTMLTemplate(webView: WKWebView) { 88 | guard let base = Bundle.main.resourceURL else { 89 | print("找不到主资源目录") 90 | return 91 | } 92 | 93 | let html = """ 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 126 | 127 | 128 |
    129 | 146 | 147 | 148 | """ 149 | 150 | webView.loadHTMLString(html, baseURL: base) 151 | } 152 | 153 | private func updateMarkdownContent(webView: WKWebView) { 154 | // 转义特殊字符 155 | let escapedMarkdown = markdownText.replacingOccurrences(of: "\\", with: "\\\\") 156 | .replacingOccurrences(of: "\"", with: "\\\"") 157 | .replacingOccurrences(of: "\n", with: "\\n") 158 | 159 | // 使用JavaScript更新内容而不是重新加载整个页面 160 | let updateScript = "updateMarkdown(\"\(escapedMarkdown)\");" 161 | webView.evaluateJavaScript(updateScript) { (result, error) in 162 | if let error = error { 163 | print("更新Markdown内容时出错: \(error.localizedDescription)") 164 | } 165 | } 166 | } 167 | 168 | func makeCoordinator() -> Coordinator { 169 | Coordinator(self) 170 | } 171 | 172 | class Coordinator: NSObject, WKNavigationDelegate, WKScriptMessageHandler { 173 | var parent: MarkdownLatexView 174 | var isHTMLLoaded = false 175 | 176 | init(_ parent: MarkdownLatexView) { 177 | self.parent = parent 178 | super.init() 179 | } 180 | 181 | func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { 182 | // 网页加载完成后, 标记状态并更新初始内容 183 | isHTMLLoaded = true 184 | parent.updateMarkdownContent(webView: webView) 185 | } 186 | 187 | func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { 188 | // 处理JavaScript消息 189 | if message.name == "heightUpdate", let height = message.body as? CGFloat { 190 | print("内容高度: \(height)") 191 | // 这里可以执行更多基于高度的逻辑, 如调整视图大小 192 | } 193 | } 194 | } 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /TextPix/Views/ScreenshotView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScreenshotView.swift 3 | // TextPix 4 | // 5 | // Created by HAIRONG ZHU on 2025/5/1. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ScreenshotView: View { 11 | @ObservedObject private var appState = AppState.shared 12 | 13 | var body: some View { 14 | VStack { 15 | if let image = appState.screenshotImage { 16 | Image(nsImage: image) 17 | .resizable() 18 | .aspectRatio(contentMode: .fit) 19 | .frame(maxWidth: .infinity, maxHeight: .infinity) 20 | .padding() 21 | .background(Color.gray.opacity(0.1)) 22 | .cornerRadius(8) 23 | } else { 24 | VStack(spacing: 20) { 25 | Image(systemName: "photo") 26 | .font(.system(size: 60)) 27 | .foregroundColor(.gray) 28 | 29 | Text("试着截一张图吧") 30 | .font(.headline) 31 | } 32 | .frame(maxWidth: .infinity, maxHeight: .infinity) 33 | } 34 | } 35 | .padding() 36 | .frame(minWidth: 400, minHeight: 300) 37 | } 38 | } 39 | 40 | #Preview { 41 | ScreenshotView() 42 | } 43 | -------------------------------------------------------------------------------- /TextPix/Views/SettingView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SettingView.swift 3 | // TextPix 4 | // 5 | // Created by HAIRONG ZHU on 2025/4/29. 6 | // 7 | 8 | import SwiftUI 9 | import KeyboardShortcuts 10 | 11 | struct SettingsView: View { 12 | @ObservedObject private var appState = AppState.shared 13 | 14 | var body: some View { 15 | ScrollView { 16 | VStack(alignment: .leading, spacing: 10) { 17 | // 快捷键设置 18 | GroupBox(label: Text("快捷键设置").font(.headline)) { 19 | HStack { 20 | Text("截图快捷键:") 21 | Spacer() 22 | KeyboardShortcuts.Recorder("", name: .capture) 23 | } 24 | } 25 | 26 | // 大语言模型设置 27 | GroupBox(label: Text("LLM设置").font(.headline)) { 28 | 29 | VStack(alignment: .leading, spacing: 2) { 30 | Text("模型") 31 | .font(.caption) 32 | .foregroundColor(.secondary) 33 | TextField("输入模型名称", text: $appState.model) 34 | .textFieldStyle(.roundedBorder) 35 | .font(.subheadline) 36 | .cornerRadius(8) 37 | } 38 | 39 | VStack(alignment: .leading, spacing: 2) { 40 | Text("API 地址") 41 | .font(.caption) 42 | .foregroundColor(.secondary) 43 | TextField("输入API地址", text: $appState.endpoint) 44 | .textFieldStyle(.roundedBorder) 45 | .font(.subheadline) 46 | .cornerRadius(8) 47 | } 48 | 49 | VStack(alignment: .leading, spacing: 2) { 50 | Text("API 密钥") 51 | .font(.caption) 52 | .foregroundColor(.secondary) 53 | 54 | SecureField("输入您的 API 密钥", text: $appState.apiKey) 55 | .textFieldStyle(.roundedBorder) 56 | .font(.subheadline) 57 | .cornerRadius(8) 58 | } 59 | 60 | // 是否开启推理(按钮),以及推理强度列表 low medium high none 61 | HStack(spacing: 2) { 62 | Toggle(isOn: $appState.inferenceEnabled) { 63 | Text("启用推理") 64 | .font(.caption) 65 | .foregroundColor(.secondary) 66 | } 67 | .toggleStyle(SwitchToggleStyle()) 68 | 69 | Spacer() 70 | Picker("推理强度", selection: $appState.inferenceLevel) { 71 | Text("low").tag("low") 72 | Text("medium").tag("medium") 73 | Text("high").tag("high") 74 | Text("none").tag("none") 75 | } 76 | .pickerStyle(.segmented) 77 | } 78 | } 79 | 80 | // OCR提示词设置 81 | GroupBox(label: Text("OCR系统提示词").font(.headline)) { 82 | TextEditor(text: $appState.systemPrompt) 83 | .font(.system(size: 12, design: .monospaced)) 84 | .textFieldStyle(.roundedBorder) 85 | .cornerRadius(8) 86 | .frame(minHeight: 100) 87 | } 88 | 89 | // 其他 90 | HStack { 91 | // 选择框,是否自动copy 92 | Toggle("OCR结果自动复制到剪切板", isOn: $appState.autoCopy) 93 | 94 | Spacer() // 将按钮推到右侧 95 | Button("Quit") { 96 | NSApplication.shared.terminate(nil) 97 | } 98 | } 99 | .padding() 100 | 101 | 102 | 103 | } 104 | .padding() 105 | } 106 | .frame(minWidth: 500, minHeight: 400) 107 | } 108 | } 109 | 110 | #Preview { 111 | SettingsView() 112 | } 113 | -------------------------------------------------------------------------------- /images/ocr_code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kivvi3412/TextPix/3d890bb13470d448f9d5c0f7af227cb44cb37472/images/ocr_code.png -------------------------------------------------------------------------------- /images/ocr_markdown_latex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kivvi3412/TextPix/3d890bb13470d448f9d5c0f7af227cb44cb37472/images/ocr_markdown_latex.png --------------------------------------------------------------------------------