import Foundation public struct ArabicPronunciationProcessor: Sendable { public init() {} public func process( lyrics: String, settings: ArabicPronunciationSettings ) -> ArabicPronunciationProcessingResult { guard settings.isEnabled else { return ArabicPronunciationProcessingResult( text: lyrics, notes: [.processingDisabled] ) } var processedText = lyrics.precomposedStringWithCanonicalMapping var notes = processingNotes(for: processedText, settings: settings) if settings.tanweenPolicy == .removeWhenUnwanted { processedText = removeTanween(from: processedText) } if settings.tanweenPolicy == .addWhenPronunciationRequires, containsArabicLetter(in: processedText), !containsTanween(in: processedText) { notes.append(.tanweenAdditionNeedsReview) } return ArabicPronunciationProcessingResult( text: processedText, notes: notes ) } private func processingNotes( for text: String, settings: ArabicPronunciationSettings ) -> [ArabicPronunciationProcessingNote] { guard containsArabicLetter(in: text) else { return [] } switch settings.diacritizationPolicy { case .unspecified: return [.diacritizationPolicyUnspecified] case .pronunciationTargeted where !containsDiacritics(in: text): return [.pronunciationTargetedDiacriticsNeedReview] case .fullTashkeel where !containsDiacritics(in: text): return [.fullTashkeelNeedsReview] default: return [] } } private func removeTanween(from text: String) -> String { String(text.unicodeScalars.filter { scalar in !isTanween(scalar) }) } private func containsArabicLetter(in text: String) -> Bool { text.unicodeScalars.contains { scalar in let isArabicBlock = (0x0600...0x06FF).contains(scalar.value) || (0x0750...0x077F).contains(scalar.value) || (0x08A0...0x08FF).contains(scalar.value) return isArabicBlock && CharacterSet.letters.contains(scalar) } } private func containsDiacritics(in text: String) -> Bool { text.unicodeScalars.contains(where: isArabicDiacritic) } private func containsTanween(in text: String) -> Bool { text.unicodeScalars.contains(where: isTanween) } private func isArabicDiacritic(_ scalar: Unicode.Scalar) -> Bool { (0x064B...0x065F).contains(scalar.value) || scalar.value == 0x0670 } private func isTanween(_ scalar: Unicode.Scalar) -> Bool { (0x064B...0x064D).contains(scalar.value) } } public struct ArabicPronunciationProcessingResult: Equatable, Sendable { public var text: String public var notes: [ArabicPronunciationProcessingNote] public init(text: String, notes: [ArabicPronunciationProcessingNote] = []) { self.text = text self.notes = notes } } public enum ArabicPronunciationProcessingNote: Equatable, Sendable { case processingDisabled case diacritizationPolicyUnspecified case pronunciationTargetedDiacriticsNeedReview case fullTashkeelNeedsReview case tanweenAdditionNeedsReview }