KMBatchManager.swift 50 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186
  1. //
  2. // KMBatchManager.swift
  3. // PDF Master
  4. //
  5. // Created by lizhe on 2023/2/17.
  6. //
  7. import Cocoa
  8. enum KMBatchManagerSate: Int, CaseIterable {
  9. case unknow = 0
  10. case begin
  11. case processing
  12. case complete
  13. case error
  14. }
  15. let kBacthFilesProcessNotification = "kBacthFilesProcessNotification"
  16. let kBacthProcessNotification = "kBacthProcessNotification"
  17. class KMBatchManager: NSObject {
  18. public static let manager = KMBatchManager()
  19. fileprivate(set) var state: KMBatchManagerSate = .unknow
  20. var outputFolderPath: String = ""
  21. var convert: KMPDFConvert = KMPDFConvert()
  22. var isCancel = false
  23. var filesData: [KMBatchProcessingTableViewModel] = []
  24. var batchFilesData: [KMBatchProcessingTableViewModel] {
  25. get {
  26. var resultArray:[KMBatchProcessingTableViewModel] = []
  27. for item in filesData {
  28. if !item.isLock {
  29. resultArray.append(item)
  30. }
  31. }
  32. return resultArray
  33. }
  34. }
  35. func batch(type: KMBatchCollectionViewType, data: KMBatchSettingItemViewModel) {
  36. self.isCancel = false
  37. if self.isContainLockFiles() {
  38. let window = NSWindow()
  39. let currentWindow = NSWindow.currentWindow()
  40. let controller = KMBatchProcessingPasswordAlertViewController.init(nibName: "KMBatchProcessingPasswordAlertViewController", bundle: nil)
  41. controller.cancelAction = {
  42. currentWindow.endSheet(window)
  43. }
  44. controller.continueAction = {
  45. currentWindow.endSheet(window)
  46. }
  47. window.contentViewController = controller
  48. currentWindow.beginSheet(window)
  49. return
  50. }
  51. let panel = NSOpenPanel()
  52. panel.canChooseFiles = false
  53. panel.canChooseDirectories = true
  54. panel.canCreateDirectories = true
  55. panel.beginSheetModal(for: NSWindow.currentWindow()) { response in
  56. if response == .cancel {
  57. return
  58. }
  59. let outputFolderPath = (panel.url?.path)!
  60. self.outputFolderPath = outputFolderPath
  61. //
  62. self.batchUnkown()
  63. self.batchBegin()
  64. switch type {
  65. case .convertPDF:
  66. self.convertPDFExport(data: data, outputFolderPath: outputFolderPath)
  67. break
  68. case .OCR:
  69. self.convertOCRExport(data: data, outputFolderPath: outputFolderPath)
  70. break
  71. case .compress:
  72. self.compressExport(data: data, outputFolderPath: outputFolderPath)
  73. break
  74. case .security:
  75. self.securityExport(data: data, outputFolderPath: outputFolderPath)
  76. break
  77. case .watermark:
  78. self.waterMarkApplay(data: data, outputFolderPath: outputFolderPath)
  79. break
  80. case .background:
  81. self.backgroundApplay(data: data, outputFolderPath: outputFolderPath)
  82. break
  83. case .headerAndFooter:
  84. self.headAndFooterApplay(data: data, outputFolderPath: outputFolderPath)
  85. break
  86. case .batesNumber:
  87. self.batesApplay(data: data, outputFolderPath: outputFolderPath)
  88. break
  89. case .batchRemove:
  90. self.removeApplay(data: data, outputFolderPath: outputFolderPath)
  91. break
  92. case .imageToPDF:
  93. self.imageToPDFExport(data: data, outputFolderPath: outputFolderPath)
  94. break
  95. default:
  96. KMPrint("找不到")
  97. break
  98. }
  99. }
  100. }
  101. func cancel(type: KMBatchCollectionViewType = .convertPDF) {
  102. self.isCancel = true
  103. switch type {
  104. case .convertPDF:
  105. KMPDFConvertManager.defaultManager.cancel(convert: self.convert)
  106. break
  107. case .OCR:
  108. KMOCRManager.manager.cancelRecognition()
  109. break
  110. case .compress:
  111. KMCompressManager.shared.cancel()
  112. break
  113. default:
  114. KMPrint("没有此功能")
  115. break
  116. }
  117. self.batchFailure()
  118. }
  119. }
  120. //MARK: 批量
  121. extension KMBatchManager {
  122. //MARK: 转档
  123. func convertPDFExport(data: KMBatchSettingItemViewModel, outputFolderPath: String) {
  124. self.convertFile(outputFolderPath: outputFolderPath, data: data, filesData: self.batchFilesData)
  125. }
  126. func convertFile(outputFolderPath: String, data: KMBatchSettingItemViewModel, filesData: [KMBatchProcessingTableViewModel]) {
  127. guard !filesData.isEmpty else { return }
  128. func processFile(at index: Int) {
  129. guard index < filesData.count else {
  130. self.batchSuccess()
  131. return
  132. }
  133. if isCancel {
  134. return
  135. }
  136. let item = filesData[index]
  137. let filePath = item.filePath
  138. let document = self.fetchDocument(filePath: filePath, model: item)
  139. let settingData = data as? KMBatchConvertPDFViewModel ?? KMBatchConvertPDFViewModel()
  140. let path = self.fetchFilePath(type: .convertPDF, filePath: filePath, outputFolderPath: outputFolderPath)
  141. let convert = self.addConvertParameter(settingData)
  142. self.convert = convert
  143. let pageCount = document.pageCount
  144. // 获取页面
  145. let pages: [Int] = Array(1...Int(pageCount))
  146. convert.outputFolderPath = outputFolderPath
  147. convert.filePath = filePath
  148. convert.password = item.password
  149. convert.outputFileName = path.deletingPathExtension.lastPathComponent
  150. convert.pages = pages
  151. convert.isAllowOCR = settingData.needRecognizeText
  152. convert.ocrLanguage = settingData.languageType
  153. item.state = .clock
  154. self.itemProgress(item: item, processValue: 0)
  155. KMPDFConvertManager.defaultManager.convert(convert: convert, progress: { [unowned self] progressValue in
  156. print("转档进度 - \(progressValue)")
  157. let progress = Float(progressValue) / Float(pageCount)
  158. self.itemProgress(item: item, processValue: progress)
  159. }, completion: { [unowned self] finished, error in
  160. if finished {
  161. self.itemSuccess(item: item)
  162. } else {
  163. self.itemFailure(item: item, error: error! as NSError)
  164. }
  165. processFile(at: index + 1)
  166. })
  167. }
  168. // 开始处理第一个文件
  169. processFile(at: 0)
  170. }
  171. func addConvertParameter(_ data: KMBatchConvertPDFViewModel) -> KMPDFConvert {
  172. let settingData = data
  173. var convert = KMPDFConvert()
  174. switch settingData.convertPDFType {
  175. case .word:
  176. convert = KMPDFConvertWord()
  177. if settingData.layoutSettingType == .flowingText {
  178. convert.isAllInOneSheet = false
  179. } else {
  180. convert.isAllInOneSheet = true
  181. }
  182. case .excel:
  183. convert = KMPDFConvertExcel()
  184. if settingData.excelContent == .onlyText {
  185. convert.isExtractText = true
  186. } else if settingData.excelContent == .allContent {
  187. convert.isAllInOneSheet = settingData.isAllInOneSheet
  188. } else if settingData.excelContent == .onlyTable {
  189. convert.isExtractTable = true
  190. if settingData.excelWorksheet == .forEachTable {
  191. convert.extractTableIndex = 0
  192. } else if settingData.excelWorksheet == .forEachPage {
  193. convert.extractTableIndex = 1
  194. } else if settingData.excelWorksheet == .forTheDocument {
  195. convert.extractTableIndex = 2
  196. }
  197. }
  198. case .ppt:
  199. convert = KMPDFConvertPPT()
  200. case .csv:
  201. convert = KMPDFConvertCSV()
  202. convert.isAllInOneSheet = settingData.isAllInOneSheet
  203. case .image, .jpeg, .jpg, .jpeg2000, .bmp, .tiff, .png, .tga:
  204. convert = KMPDFConvertImage()
  205. convert.convertType = data.imageType
  206. var dpi: Int = 150
  207. if data.imageDpiIndex == 0 {
  208. dpi = 50
  209. } else if data.imageDpiIndex == 1 {
  210. dpi = 72
  211. } else if data.imageDpiIndex == 2 {
  212. dpi = 96
  213. } else if data.imageDpiIndex == 3 {
  214. dpi = 150
  215. } else if data.imageDpiIndex == 4 {
  216. dpi = 300
  217. } else if data.imageDpiIndex == 5 {
  218. dpi = 600
  219. } else {
  220. dpi = 150
  221. }
  222. if (data.imageType == .jpeg) {
  223. (convert as! KMPDFConvertImage).imageType = .JPEG
  224. (convert as! KMPDFConvertImage).imageDpi = dpi
  225. } else if (data.imageType == .png) {
  226. (convert as! KMPDFConvertImage).imageType = .PNG
  227. (convert as! KMPDFConvertImage).imageDpi = dpi
  228. } else {
  229. (convert as! KMPDFConvertImage).imageDpi = 150
  230. }
  231. case .html:
  232. convert = KMPDFConvertHTML()
  233. case .rtf:
  234. convert = KMPDFConvertRTF()
  235. case .json:
  236. convert = KMPDFConvertJson()
  237. if settingData.jsonType == .extractText {
  238. convert.isAllInOneSheet = false
  239. } else {
  240. convert.isAllInOneSheet = true
  241. }
  242. case .text:
  243. convert = KMPDFConvertText()
  244. default:
  245. KMPrint("不清楚")
  246. }
  247. return convert
  248. }
  249. //MARK: OCR
  250. func convertOCRExport(data: KMBatchSettingItemViewModel, outputFolderPath: String) {
  251. self.convertOCR(outputFolderPath: outputFolderPath, data: data as! KMOCRModel, filesData: self.batchFilesData)
  252. }
  253. func convertOCR(outputFolderPath: String, data: KMOCRModel, filesData: [KMBatchProcessingTableViewModel]?) {
  254. // 如果文件数据为空,直接返回
  255. guard let filesData = filesData, !filesData.isEmpty else { return }
  256. // 递归处理文件
  257. func processFile(at index: Int) {
  258. // 如果索引超出范围,说明所有文件都已经处理完毕
  259. guard index < filesData.count else {
  260. self.batchSuccess() // 所有文件处理完毕,调用批处理成功方法
  261. return
  262. }
  263. if isCancel {
  264. return
  265. }
  266. // 获取当前文件项
  267. let item = filesData[index]
  268. let document = CPDFDocument.init(url: URL(fileURLWithPath: item.filePath))
  269. // 如果文档有效,开始 OCR 转换
  270. if document != nil {
  271. // 获取 OCR 输出文件路径
  272. let path = self.fetchFilePath(type: .OCR, filePath: item.filePath, outputFolderPath: outputFolderPath)
  273. // 获取需要处理的页面范围
  274. data.pageRange = self.fetchValidPageIndexs(document!, model: item) ?? []
  275. // 执行 OCR 转换
  276. self.itemProgress(item: item, processValue: 0)
  277. self.convertOCR(outputFolderPath: outputFolderPath, document: document!, fileName: path.deletingPathExtension.lastPathComponent, data: data) { [unowned self] progress in
  278. // 更新进度
  279. self.itemProgress(item: item, processValue: progress)
  280. } complete: { [unowned self] document, text, error in
  281. // 处理 OCR 转换结果
  282. if error == nil {
  283. self.itemSuccess(item: item)
  284. } else {
  285. self.itemFailure(item: item, error: error! as NSError)
  286. }
  287. // 递归调用处理下一个文件
  288. processFile(at: index + 1)
  289. }
  290. } else {
  291. // 如果文件无效,跳过并处理下一个文件
  292. processFile(at: index + 1)
  293. }
  294. }
  295. // 从第一个文件开始处理
  296. processFile(at: 0)
  297. }
  298. // func convertOCR(outputFolderPath: String, data: KMOCRModel, filesData: [KMBatchProcessingTableViewModel]?) {
  299. // guard let filesData = filesData else { return }
  300. // for i in 0..<filesData.count {
  301. // let item = (filesData[i])
  302. // let document = CPDFDocument.init(url: URL(fileURLWithPath: item.filePath))
  303. // if document != nil {
  304. // //计算需要处理的页面
  305. //
  306. // let path = self.fetchFilePath(type: .OCR, filePath: item.filePath, outputFolderPath: outputFolderPath)
  307. // data.pageRange = self.fetchValidPageIndexs(document!, model: item) ?? []
  308. //
  309. // self.convertOCR(outputFolderPath: outputFolderPath, document: document!, fileName: path.deletingPathExtension.lastPathComponent, data: data) { [unowned self] progress in
  310. // self.itemProgress(item: item, processValue: progress)
  311. // } complete: { [unowned self] document, text, error in
  312. // if error == nil {
  313. // self.itemSuccess(item: item)
  314. // } else {
  315. // self.itemFailure(item: item, error: error! as NSError)
  316. // }
  317. //
  318. // if i == filesData.count - 1 {
  319. // self.batchSuccess()
  320. // }
  321. // }
  322. // }
  323. // }
  324. // }
  325. func convertOCR(outputFolderPath: String,
  326. document: CPDFDocument,
  327. fileName: String,
  328. data: KMOCRModel,
  329. progress: @escaping KMOCRManagerOCRProgress,
  330. complete: @escaping KMOCRManagerOCRComplete) {
  331. //计算需要处理的页面
  332. let path = outputFolderPath + "/" + fileName + ".pdf"
  333. KMOCRManager.manager.convertBatchOCR(document: document, saveFilePath: path, model: data, progress: { [unowned self] progressValue in
  334. progress(progressValue)
  335. }) { [unowned self] document, text, error in
  336. complete(document, text, error)
  337. }
  338. }
  339. //MARK: 压缩
  340. func compressExport(data: KMBatchSettingItemViewModel, outputFolderPath: String) {
  341. self.compressFile(outputFolderPath: outputFolderPath, data: (data as? KMCompressSettingModel)!, filesData: self.batchFilesData)
  342. }
  343. func compressFile(outputFolderPath: String, data: KMCompressSettingModel, filesData: [KMBatchProcessingTableViewModel]) {
  344. // 如果文件数组为空,直接返回
  345. guard !filesData.isEmpty else { return }
  346. // 递归处理每个文件
  347. func processFile(at index: Int) {
  348. // 如果索引超出范围,说明所有文件都已经处理完毕
  349. guard index < filesData.count else {
  350. self.batchSuccess() // 所有文件处理完毕,调用批处理成功方法
  351. return
  352. }
  353. if isCancel {
  354. return
  355. }
  356. // 获取当前文件项
  357. let item = filesData[index]
  358. let document = CPDFDocument.init(url: URL(fileURLWithPath: item.filePath))
  359. // 如果文档存在,则进行压缩
  360. if document != nil {
  361. // 获取压缩后的目标路径
  362. let path = self.fetchFilePath(type: .compress, filePath: item.filePath, outputFolderPath: outputFolderPath)
  363. // 调用压缩管理器进行压缩
  364. self.itemProgress(item: item, processValue: 0)
  365. KMCompressManager.shared.compress(documentURL: URL(fileURLWithPath: item.filePath), fileURL: URL(fileURLWithPath: path), limit: false, model: data) { currentPage, totalPages in
  366. // 计算进度
  367. let progress = Float(currentPage) / Float(totalPages)
  368. self.itemProgress(item: item, processValue: progress)
  369. } cancelHandler: {
  370. return false // 如果需要处理取消逻辑,可以在这里返回取消条件
  371. } completionHandler: { [unowned self] isFinish in
  372. if isFinish {
  373. self.itemSuccess(item: item) // 如果压缩成功,标记文件成功
  374. } else {
  375. self.itemFailure(item: item, error: nil) // 如果失败,标记失败
  376. }
  377. // 递归调用处理下一个文件
  378. processFile(at: index + 1)
  379. }
  380. } else {
  381. // 如果文件无效,直接跳过并继续处理下一个文件
  382. processFile(at: index + 1)
  383. }
  384. }
  385. // 从第一个文件开始处理
  386. processFile(at: 0)
  387. }
  388. // func compressFile(outputFolderPath: String, data: KMCompressSettingModel, filesData: [KMBatchProcessingTableViewModel]) {
  389. // if filesData.count != 0 {
  390. // DispatchQueue.global().async {
  391. // for i in 0..<filesData.count {
  392. // let item = filesData[i]
  393. // let document = CPDFDocument.init(url: URL(fileURLWithPath: item.filePath))
  394. // if document != nil {
  395. // let path = self.fetchFilePath(type: .compress, filePath: item.filePath, outputFolderPath: outputFolderPath)
  396. //
  397. // KMCompressManager.shared.compress(documentURL: URL(fileURLWithPath: item.filePath), fileURL: URL(fileURLWithPath: path), limit: false, model: data) { currentPage, totalPages in
  398. // let progress = Float(currentPage) / Float(totalPages)
  399. // self.itemProgress(item: item, processValue: progress)
  400. // } cancelHandler: {
  401. // return false
  402. // } completionHandler: { [unowned self] isFinish in
  403. // if isFinish {
  404. // self.itemSuccess(item: item)
  405. // } else {
  406. // self.itemFailure(item: item, error: nil)
  407. // }
  408. //
  409. // if i == filesData.count - 1 {
  410. // self.batchSuccess()
  411. // }
  412. // }
  413. // }
  414. //
  415. // }
  416. // }
  417. // }
  418. // }
  419. //MARK: 安全
  420. func securityExport(data: KMBatchSettingItemViewModel, outputFolderPath: String) {
  421. self.securityFile(outputFolderPath: outputFolderPath, data: data as! KMBatchSecurityViewModel, filesData: self.batchFilesData)
  422. }
  423. func securityFile(outputFolderPath: String, data: KMBatchSecurityViewModel, filesData: [KMBatchProcessingTableViewModel]) {
  424. if filesData.count != 0 {
  425. for i in 0..<filesData.count {
  426. let item = filesData[i]
  427. let docuemt = CPDFDocument.init(url: URL(fileURLWithPath: item.filePath))
  428. if (docuemt != nil) {
  429. let path = self.fetchFilePath(type: .security, filePath: item.filePath, outputFolderPath: outputFolderPath)
  430. var options: [CPDFDocumentWriteOption : Any] = [:]
  431. //开启密码
  432. if data.isOpenPassword &&
  433. !data.openPasswordString.isEmpty {
  434. options.updateValue(data.openPasswordString, forKey: .userPasswordOption)
  435. }
  436. //
  437. //权限密码
  438. if data.isPermission &&
  439. !data.permissionString.isEmpty {
  440. options.updateValue(data.permissionString, forKey: .ownerPasswordOption)
  441. }
  442. // 限制打印
  443. if data.restrictOptions.contains(.print) {
  444. options.updateValue(false, forKey: .allowsPrintingOption)
  445. } else {
  446. options.updateValue(true, forKey: .allowsPrintingOption)
  447. }
  448. //限制复制
  449. if data.restrictOptions.contains(.copy) {
  450. options.updateValue(false, forKey: .allowsCopyingOption)
  451. } else {
  452. options.updateValue(true, forKey: .allowsCopyingOption)
  453. }
  454. let result = docuemt!.write(toFile: path, withOptions: options)
  455. // let result = docuemt!.write(to: URL(fileURLWithPath: path), withOptions: options)
  456. if result {
  457. KMPrint("成功")
  458. self.itemSuccess(item: item)
  459. } else {
  460. KMPrint("失败")
  461. self.itemFailure(item: item, error: nil)
  462. }
  463. if i == filesData.count - 1 {
  464. self.batchSuccess()
  465. }
  466. }
  467. }
  468. }
  469. }
  470. //MARK: 水印
  471. func waterMarkApplay(data: KMBatchSettingItemViewModel, outputFolderPath: String) {
  472. if let data = data as? KMBatchWatermarkModel {
  473. self.waterMarkFile(outputFolderPath: outputFolderPath, data: data, filesData: self.batchFilesData)
  474. }
  475. }
  476. func waterMarkFile(outputFolderPath: String, data: KMBatchWatermarkModel, filesData: [KMBatchProcessingTableViewModel]) {
  477. if filesData.count != 0 {
  478. for i in 0..<filesData.count {
  479. let item = filesData[i]
  480. let path = self.fetchFilePath(type: .watermark, filePath: item.filePath, outputFolderPath: outputFolderPath)
  481. let document = CPDFDocument.init(url: URL(fileURLWithPath: item.filePath))
  482. if (document?.allowsPrinting == false || document?.allowsCopying == false) {
  483. let alert = NSAlert()
  484. alert.alertStyle = .critical
  485. alert.messageText = "此文档不允许修改"
  486. alert.runModal()
  487. return
  488. }
  489. if let watermarks = document?.watermarks(), let model = data.watermarkModel, let document = document {
  490. let pageString = self.fetchValidPageIndexString(document, model: item)
  491. let watermark = KMWatermarkModel.returnWaterMarkWith(model, document)
  492. watermark.pageString = pageString
  493. document.addWatermark(watermark)
  494. }
  495. if (FileManager.default.fileExists(atPath: path)) {
  496. try?FileManager.default.removeItem(atPath: path)
  497. }
  498. let result = document?.write(to: URL(fileURLWithPath: path)) ?? false
  499. if (result) {
  500. KMPrint("removeFile成功")
  501. self.itemSuccess(item: item)
  502. } else {
  503. KMPrint("removeFile失败")
  504. self.itemFailure(item: item, error: nil)
  505. }
  506. if i == filesData.count - 1 {
  507. self.batchSuccess()
  508. }
  509. }
  510. }
  511. }
  512. //MARK: 背景
  513. func backgroundApplay(data: KMBatchSettingItemViewModel, outputFolderPath: String) {
  514. if let data = data as? KMBatchBackgroundModel {
  515. self.backgroundFile(outputFolderPath: outputFolderPath, data: data, filesData: self.batchFilesData)
  516. }
  517. }
  518. func backgroundFile(outputFolderPath: String, data: KMBatchBackgroundModel, filesData: [KMBatchProcessingTableViewModel]) {
  519. if filesData.count != 0 {
  520. for i in 0..<filesData.count {
  521. let item = filesData[i]
  522. let path = self.fetchFilePath(type: .background, filePath: item.filePath, outputFolderPath: outputFolderPath)
  523. let document = CPDFDocument.init(url: URL(fileURLWithPath: item.filePath))
  524. if (document?.allowsPrinting == false || document?.allowsCopying == false) {
  525. let alert = NSAlert()
  526. alert.alertStyle = .critical
  527. alert.messageText = "此文档不允许修改"
  528. alert.runModal()
  529. return
  530. }
  531. if let background = document?.background(), let model = data.backgroundModel, let document = document {
  532. KMBackgroundManager.defaultManager.updateBackground(background, withModel: model)
  533. let pageIndexString = self.fetchValidPageIndexString(document, model: item)
  534. background.pageString = pageIndexString
  535. background.update()
  536. }
  537. if (FileManager.default.fileExists(atPath: path)) {
  538. try?FileManager.default.removeItem(atPath: path)
  539. }
  540. let result = document?.write(to: URL(fileURLWithPath: path)) ?? false
  541. if (result) {
  542. KMPrint("removeFile成功")
  543. self.itemSuccess(item: item)
  544. } else {
  545. KMPrint("removeFile失败")
  546. self.itemFailure(item: item, error: nil)
  547. }
  548. if i == filesData.count - 1 {
  549. self.batchSuccess()
  550. }
  551. }
  552. }
  553. }
  554. //MARK: 页眉页脚
  555. func headAndFooterApplay(data: KMBatchSettingItemViewModel, outputFolderPath: String) {
  556. if let data = data as? KMBatchHeaderAndFooterModel {
  557. self.headAndFooterFile(outputFolderPath: outputFolderPath, data: data, filesData: self.batchFilesData)
  558. }
  559. }
  560. func headAndFooterFile(outputFolderPath: String, data: KMBatchHeaderAndFooterModel, filesData: [KMBatchProcessingTableViewModel]) {
  561. if filesData.count != 0 {
  562. for i in 0..<filesData.count {
  563. let item = filesData[i]
  564. let path = self.fetchFilePath(type: .headerAndFooter, filePath: item.filePath, outputFolderPath: outputFolderPath)
  565. let document = CPDFDocument.init(url: URL(fileURLWithPath: item.filePath))
  566. if (document?.allowsPrinting == false || document?.allowsCopying == false) {
  567. let alert = NSAlert()
  568. alert.alertStyle = .critical
  569. alert.messageText = "此文档不允许修改"
  570. alert.runModal()
  571. return
  572. }
  573. if let headerFooter = document?.headerFooter(), let model = data.headerFooterModel, let document = document {
  574. let pageString = self.fetchValidPageIndexString(document, model: item)
  575. KMHeaderFooterManager.defaultManager.updateCPDFHeaderFooter(headerFooter, withModel: model, Int(document.pageCount))
  576. headerFooter.pageString = pageString
  577. headerFooter.update()
  578. }
  579. if (FileManager.default.fileExists(atPath: path)) {
  580. try?FileManager.default.removeItem(atPath: path)
  581. }
  582. let result = document?.write(to: URL(fileURLWithPath: path)) ?? false
  583. if (result) {
  584. KMPrint("removeFile成功")
  585. self.itemSuccess(item: item)
  586. } else {
  587. KMPrint("removeFile失败")
  588. self.itemFailure(item: item, error: nil)
  589. }
  590. if i == filesData.count - 1 {
  591. self.batchSuccess()
  592. }
  593. }
  594. }
  595. }
  596. //MARK: 贝茨码
  597. func batesApplay(data: KMBatchSettingItemViewModel, outputFolderPath: String) {
  598. if let data = data as? KMBatchBatesModel {
  599. self.batesFile(outputFolderPath: outputFolderPath, data: data, filesData: self.batchFilesData)
  600. }
  601. }
  602. func batesFile(outputFolderPath: String, data: KMBatchBatesModel, filesData: [KMBatchProcessingTableViewModel]) {
  603. if filesData.count != 0 {
  604. for i in 0..<filesData.count {
  605. let item = filesData[i]
  606. let path = self.fetchFilePath(type: .batesNumber, filePath: item.filePath, outputFolderPath: outputFolderPath)
  607. let document = CPDFDocument.init(url: URL(fileURLWithPath: item.filePath))
  608. if (document?.allowsPrinting == false || document?.allowsCopying == false) {
  609. let alert = NSAlert()
  610. alert.alertStyle = .critical
  611. alert.messageText = "此文档不允许修改"
  612. alert.runModal()
  613. return
  614. }
  615. if let bates = document?.bates(), let model = data.batesModel, let document = document {
  616. let pageString = self.fetchValidPageIndexString(document, model: item)
  617. KMBatesManager.defaultManager.updateCPDFBates(bates, withModel: model, Int(document.pageCount))
  618. bates.pageString = pageString
  619. bates.update()
  620. }
  621. if (FileManager.default.fileExists(atPath: path)) {
  622. try?FileManager.default.removeItem(atPath: path)
  623. }
  624. let result = document?.write(to: URL(fileURLWithPath: path)) ?? false
  625. if (result) {
  626. KMPrint("removeFile成功")
  627. self.itemSuccess(item: item)
  628. } else {
  629. KMPrint("removeFile失败")
  630. self.itemFailure(item: item, error: nil)
  631. }
  632. if i == filesData.count - 1 {
  633. self.batchSuccess()
  634. }
  635. }
  636. }
  637. }
  638. //MARK: 移除
  639. func removeApplay(data: KMBatchSettingItemViewModel, outputFolderPath: String) {
  640. self.removeFile(outputFolderPath: outputFolderPath, data: data as! KMBatchRemoveViewModel, filesData: self.batchFilesData)
  641. }
  642. func removeFile(outputFolderPath: String, data: KMBatchRemoveViewModel, filesData: [KMBatchProcessingTableViewModel]) {
  643. if filesData.count != 0 {
  644. for i in 0..<filesData.count {
  645. let item = filesData[i]
  646. // DispatchQueue.global().async {
  647. let path = self.fetchFilePath(type: .batchRemove, filePath: item.filePath, outputFolderPath: outputFolderPath)
  648. let document = CPDFDocument.init(url: URL(fileURLWithPath: item.filePath))
  649. if document != nil {
  650. if (document!.allowsPrinting == false || document!.allowsCopying == false) {
  651. let alert = NSAlert()
  652. alert.alertStyle = .critical
  653. alert.messageText = "此文档不允许修改"
  654. alert.runModal()
  655. return
  656. }
  657. if (data.options.contains(.security)) {
  658. }
  659. if (data.options.contains(.batesNumber)) {
  660. let property = document!.bates()
  661. property?.clear()
  662. }
  663. if (data.options.contains(.headerAndFooter)) {
  664. let property = document!.headerFooter()
  665. property?.clear()
  666. }
  667. if (data.options.contains(.background)) {
  668. let property = document!.background()
  669. property?.clear()
  670. }
  671. if (data.options.contains(.watermark)) {
  672. let array: Array<CPDFWatermark> = document!.watermarks() ?? []
  673. for model in array {
  674. document!.removeWatermark(model)
  675. }
  676. }
  677. if (FileManager.default.fileExists(atPath: path)) {
  678. try?FileManager.default.removeItem(atPath: path)
  679. }
  680. let result = document!.write(to: URL(fileURLWithPath: path))
  681. if (result) {
  682. KMPrint("removeFile成功")
  683. self.itemSuccess(item: item)
  684. } else {
  685. KMPrint("removeFile失败")
  686. self.itemFailure(item: item, error: nil)
  687. }
  688. if i == filesData.count - 1 {
  689. self.batchSuccess()
  690. }
  691. }
  692. // }
  693. }
  694. }
  695. }
  696. //MARK: 图片转PDF
  697. func imageToPDFExport(data: KMBatchSettingItemViewModel, outputFolderPath: String) {
  698. self.imageToPDFFile(outputFolderPath: outputFolderPath, data: data as! KMBatchImageToPDFModel, filesData: self.batchFilesData)
  699. }
  700. func imageToPDFFile(outputFolderPath: String, data: KMBatchImageToPDFModel, filesData: [KMBatchProcessingTableViewModel]) {
  701. if filesData.count != 0 {
  702. self.batchProgress()
  703. if data.isNewPDF {
  704. if data.isMergeAll {
  705. let item = filesData[0]
  706. let path = self.fetchFilePath(type: .imageToPDF, filePath: item.filePath, outputFolderPath: outputFolderPath)
  707. let pdfDocument = CPDFDocument()
  708. for item in filesData {
  709. pdfDocument?.km_insert(image: item.image, at: pdfDocument?.pageCount ?? 0)
  710. }
  711. if data.isOCR {
  712. let model = KMOCRModel()
  713. model.showType = .page
  714. model.saveAsPDF = true
  715. model.ocrType = data.ocrType
  716. model.languageType = data.languageType
  717. model.needTxT = data.isExtractText
  718. model.pageRangeType = .all
  719. //计算需要处理的页面
  720. let pages:[Int] = KMOCRManager.fetchPageIndex(document: pdfDocument!, model: model)
  721. model.pageRange = pages
  722. self.batchProgress()
  723. self.convertOCR(outputFolderPath: outputFolderPath, document: pdfDocument!, fileName: path.deletingPathExtension.lastPathComponent, data: model) { progress in
  724. self.batchProgress()
  725. } complete: { document, text, error in
  726. self.batchSuccess()
  727. }
  728. } else {
  729. let success = pdfDocument?.write(toFile: path)
  730. if success != nil {
  731. for item in filesData {
  732. self.itemSuccess(item: item)
  733. }
  734. self.batchSuccess()
  735. } else {
  736. self.batchFailure()
  737. }
  738. }
  739. } else {
  740. processFile(at: 0, outputFolderPath: outputFolderPath, data: data)
  741. }
  742. } else {
  743. let selectFilePath = data.selectFilePath
  744. if selectFilePath.count == 0 {
  745. let alert = NSAlert()
  746. alert.alertStyle = .critical
  747. alert.messageText = KMLocalizedString("文件未选择")
  748. alert.runModal()
  749. return
  750. }
  751. var fileName = selectFilePath.deletingPathExtension.lastPathComponent
  752. let path = self.fetchFilePath(type: .imageToPDF, filePath: selectFilePath, outputFolderPath: outputFolderPath)
  753. var pdfDocument = CPDFDocument(url: NSURL(fileURLWithPath: selectFilePath) as URL)
  754. let count: Int = Int(pdfDocument?.pageCount ?? 0)
  755. for item in filesData {
  756. pdfDocument?.km_insert(image: item.image, at: UInt(count))
  757. }
  758. if data.isOCR {
  759. let model = KMOCRModel()
  760. model.showType = .page
  761. model.saveAsPDF = true
  762. model.ocrType = data.ocrType
  763. model.languageType = data.languageType
  764. model.needTxT = data.isExtractText
  765. model.pageRangeType = .all
  766. //计算需要处理的页面
  767. let pages:[Int] = KMOCRManager.fetchPageIndex(document: pdfDocument!, model: model)
  768. model.pageRange = pages
  769. self.convertOCR(outputFolderPath: outputFolderPath, document: pdfDocument!, fileName: fileName, data: model) { progress in
  770. } complete: { document, text, error in
  771. if (error != nil) {
  772. self.batchFailure()
  773. } else {
  774. for item in filesData {
  775. self.itemSuccess(item: item)
  776. }
  777. self.batchSuccess()
  778. }
  779. }
  780. } else {
  781. let success = pdfDocument?.write(toFile: path)
  782. if success != nil {
  783. for item in filesData {
  784. self.itemSuccess(item: item)
  785. }
  786. self.batchSuccess()
  787. } else {
  788. self.batchFailure()
  789. }
  790. }
  791. }
  792. }
  793. }
  794. func processFile(at index: Int, outputFolderPath: String, data: KMBatchImageToPDFModel) {
  795. guard index < filesData.count else {
  796. self.batchSuccess()
  797. return
  798. }
  799. let item = filesData[index]
  800. if data.isOCR {
  801. let path = self.fetchFilePath(type: .imageToPDF, filePath: item.filePath, outputFolderPath: outputFolderPath)
  802. let pdfDocument = CPDFDocument()
  803. pdfDocument?.km_insert(image: item.image, at: pdfDocument?.pageCount ?? 0)
  804. let model = KMOCRModel()
  805. model.showType = .page
  806. model.saveAsPDF = true
  807. model.ocrType = data.ocrType
  808. model.languageType = data.languageType
  809. model.needTxT = data.isExtractText
  810. model.pageRangeType = .all
  811. let pages: [Int] = KMOCRManager.fetchPageIndex(document: pdfDocument!, model: model)
  812. model.pageRange = pages
  813. self.itemProgress(item: item, processValue: 0)
  814. self.convertOCR(outputFolderPath: outputFolderPath, document: pdfDocument!, fileName: path.deletingPathExtension.lastPathComponent, data: model) { [unowned self] progress in
  815. self.itemProgress(item: item, processValue: progress)
  816. } complete: { [unowned self] document, text, error in
  817. self.itemSuccess(item: filesData[index])
  818. processFile(at: index + 1, outputFolderPath: outputFolderPath, data: data)
  819. }
  820. } else {
  821. let path = self.fetchFilePath(type: .imageToPDF, filePath: item.filePath, outputFolderPath: outputFolderPath)
  822. let pdfDocument = CPDFDocument()
  823. pdfDocument?.km_insert(image: item.image, at: pdfDocument?.pageCount ?? 0)
  824. let success = pdfDocument?.write(toFile: path)
  825. if success != nil {
  826. self.itemSuccess(item: item)
  827. processFile(at: index + 1, outputFolderPath: outputFolderPath, data: data)
  828. } else {
  829. self.itemFailure(item: item, error: nil)
  830. }
  831. }
  832. }
  833. }
  834. //MARK: private
  835. extension KMBatchManager {
  836. func fetchValidPageIndexString(_ document: CPDFDocument, model: KMBatchProcessingTableViewModel) -> String? {
  837. if model.pageRange == .all {
  838. let pages = Array(0..<Int(document.pageCount))
  839. let pageIndexString = pages.isEmpty ? "" : pages.map { "\($0)" }.joined(separator: ",")
  840. return pageIndexString
  841. } else {
  842. let data = KMOCRModel()
  843. data.pageRangeType = model.pageRange
  844. data.pageRangeString = model.pageRangeString
  845. let pages:[Int] = KMOCRManager.fetchPageIndex(document: document, model: data)
  846. let pageIndexString = pages.isEmpty ? "" : pages.map { "\($0)" }.joined(separator: ",")
  847. return pageIndexString
  848. }
  849. return nil
  850. }
  851. func fetchValidPageIndexs(_ document: CPDFDocument, model: KMBatchProcessingTableViewModel) -> [Int]? {
  852. if model.pageRange == .all {
  853. let pages = Array(0..<Int(document.pageCount))
  854. return pages
  855. } else {
  856. let data = KMOCRModel()
  857. data.pageRangeType = model.pageRange
  858. data.pageRangeString = model.pageRangeString
  859. let pages:[Int] = KMOCRManager.fetchPageIndex(document: document, model: data)
  860. return pages
  861. }
  862. return []
  863. }
  864. func fetchDocument(filePath: String, model: KMBatchProcessingTableViewModel) -> CPDFDocument {
  865. var document = CPDFDocument(url: URL(fileURLWithPath: filePath))
  866. if model.password.count != 0 {
  867. document?.unlock(withPassword: model.password)
  868. }
  869. if model.pageRange == .all {
  870. } else {
  871. let data = KMOCRModel()
  872. data.pageRangeType = model.pageRange
  873. data.pageRangeString = model.pageRangeString
  874. let pages:[Int] = KMOCRManager.fetchPageIndex(document: document!, model: data)
  875. var tempDocument = CPDFDocument()
  876. for i in 0..<pages.count {
  877. let page = document?.page(at: UInt(i))
  878. tempDocument?.insertPageObject(page, at: tempDocument?.pageCount ?? 0)
  879. }
  880. let fileName = filePath.deletingPathExtension.lastPathComponent
  881. let isSuccess = tempDocument?.write(toFile: self.fetchTempFilePath(fileName: fileName))
  882. if isSuccess != nil {
  883. document = tempDocument
  884. }
  885. }
  886. return document ?? CPDFDocument()
  887. }
  888. func fetchTempFilePath(fileName: String) -> String {
  889. let floderPath = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.applicationSupportDirectory, FileManager.SearchPathDomainMask.userDomainMask, true).last?.stringByAppendingPathComponent(Bundle.main.bundleIdentifier!).stringByAppendingPathComponent("BatchTemp")
  890. let filePath = floderPath?.stringByAppendingPathComponent("\(fileName).pdf")
  891. if let data = floderPath, !FileManager.default.fileExists(atPath: data) {
  892. try?FileManager.default.createDirectory(atPath: data, withIntermediateDirectories: false)
  893. }
  894. if let data = filePath, !FileManager.default.fileExists(atPath: data) {
  895. FileManager.default.createFile(atPath: data, contents: nil)
  896. }
  897. return filePath ?? ""
  898. }
  899. func removeTempFilePath(filePath: String) {
  900. let fileName = filePath.deletingPathExtension.lastPathComponent
  901. let path = self.fetchTempFilePath(fileName: fileName)
  902. if (FileManager.default.fileExists(atPath: path)) {
  903. try?FileManager.default.removeItem(atPath: path)
  904. }
  905. }
  906. func fetchFilePath(type: KMBatchCollectionViewType, filePath: String, outputFolderPath: String) -> String {
  907. var fileName = filePath.deletingPathExtension.lastPathComponent
  908. if fileName.isEmpty {
  909. fileName = NSLocalizedString("Untitled", comment: "")
  910. }
  911. let path = outputFolderPath + "/" + fileName + ".pdf"
  912. // // 检查文件是否已存在,如果存在,则添加数字后缀
  913. // var finalPath = path
  914. // var count = 1
  915. // while FileManager.default.fileExists(atPath: finalPath) {
  916. // let newFileName = "\(fileName) \(count)"
  917. // finalPath = outputFolderPath + "/" + newFileName + ".pdf"
  918. // count += 1
  919. // }
  920. // 使用最终路径进行保存或其他操作
  921. // path = finalPath
  922. return path
  923. }
  924. func isContainLockFiles() -> Bool {
  925. var isContainFiles = false
  926. for item in filesData {
  927. if !item.isLock {
  928. } else {
  929. isContainFiles = true
  930. break
  931. }
  932. }
  933. return isContainFiles
  934. }
  935. }
  936. //MARK: Alert
  937. extension KMBatchManager {
  938. func batchUnkown() {
  939. for item in filesData {
  940. self.itemUnkown(item: item)
  941. }
  942. self.state = .unknow
  943. NotificationCenter.default.post(name: NSNotification.Name(kBacthProcessNotification), object: nil)
  944. }
  945. func batchBegin() {
  946. for item in filesData {
  947. self.itemUnkown(item: item)
  948. }
  949. self.state = .begin
  950. NotificationCenter.default.post(name: NSNotification.Name(kBacthProcessNotification), object: nil)
  951. }
  952. func batchProgress() {
  953. for item in filesData {
  954. self.itemProgress(item: item, processValue: 0)
  955. }
  956. self.state = .processing
  957. NotificationCenter.default.post(name: NSNotification.Name(kBacthProcessNotification), object: nil)
  958. }
  959. func batchSuccess() {
  960. if FileManager.default.fileExists(atPath: self.outputFolderPath) {
  961. NSWorkspace.shared.activateFileViewerSelecting([URL(fileURLWithPath: self.outputFolderPath)])
  962. }
  963. self.state = .complete
  964. NotificationCenter.default.post(name: NSNotification.Name(kBacthProcessNotification), object: nil)
  965. }
  966. func batchFailure() {
  967. if FileManager.default.fileExists(atPath: self.outputFolderPath) {
  968. NSWorkspace.shared.activateFileViewerSelecting([URL(fileURLWithPath: self.outputFolderPath)])
  969. }
  970. self.state = .error
  971. NotificationCenter.default.post(name: NSNotification.Name(kBacthProcessNotification), object: nil)
  972. }
  973. func itemUnkown(item: KMBatchProcessingTableViewModel) {
  974. item.state = .clock
  975. NotificationCenter.default.post(name: NSNotification.Name(kBacthFilesProcessNotification), object: item)
  976. }
  977. func itemProgress(item: KMBatchProcessingTableViewModel, processValue: Float) {
  978. if processValue > 0.7 {
  979. item.state = .loading70
  980. } else {
  981. item.state = .loading
  982. }
  983. NotificationCenter.default.post(name: NSNotification.Name(kBacthFilesProcessNotification), object: item)
  984. }
  985. func itemSuccess(item: KMBatchProcessingTableViewModel) {
  986. self.removeTempFilePath(filePath: item.filePath)
  987. item.state = .success
  988. NotificationCenter.default.post(name: NSNotification.Name(kBacthFilesProcessNotification), object: item)
  989. }
  990. func itemFailure(item: KMBatchProcessingTableViewModel, error: NSError?) {
  991. self.removeTempFilePath(filePath: item.filePath)
  992. item.state = .error
  993. NotificationCenter.default.post(name: NSNotification.Name(kBacthFilesProcessNotification), object: item)
  994. guard let error = error else { return }
  995. var errorString = ""
  996. let myError: NSError = error as NSError
  997. if myError.code == 1 {
  998. errorString = NSLocalizedString("Password required or incorrect password. Please re-enter your password and try again", comment: "")
  999. } else if myError.code == 2 {
  1000. errorString = NSLocalizedString("The license doesn't allow the permission", comment: "")
  1001. } else if myError.code == 3 {
  1002. errorString = NSLocalizedString("Malloc failure", comment: "")
  1003. } else if myError.code == 4 {
  1004. errorString = NSLocalizedString("Unknown error in processing conversion. Please try again later", comment: "")
  1005. } else if myError.code == 5 {
  1006. errorString = NSLocalizedString("Unknown error in processing PDF. Please try again later", comment: "")
  1007. } else if myError.code == 6 {
  1008. errorString = NSLocalizedString("File not found or could not be opened. Check if your file exists or choose another file to convert", comment: "")
  1009. } else if myError.code == 7 {
  1010. errorString = NSLocalizedString("File not in PDF format or corruptead. Change a PDF file and try again", comment: "")
  1011. } else if myError.code == 8 {
  1012. errorString = NSLocalizedString("Unsupported security scheme", comment: "")
  1013. } else if myError.code == 9 {
  1014. errorString = NSLocalizedString("Page not found or content error", comment: "")
  1015. } else if myError.code == 404 {
  1016. errorString = NSLocalizedString("Please check if the information is wrong or the network is error.", comment: "")
  1017. } else {
  1018. errorString = NSLocalizedString("Table not found", comment: "")
  1019. }
  1020. let alert = NSAlert()
  1021. alert.alertStyle = .critical
  1022. alert.messageText = NSLocalizedString("Conversion Failed", comment: "")
  1023. alert.informativeText = errorString
  1024. alert.addButton(withTitle: NSLocalizedString("OK", comment: ""))
  1025. alert.runModal()
  1026. }
  1027. }