├── Previews ├── Logo.png ├── example.jpg ├── Flowchart.png ├── founders.jpg ├── custom_font_1_preview.jpg ├── custom_font_2_preview.jpg ├── custom_font_3_preview.jpg └── original_font_preview.jpg ├── Tests └── PersianJustifyTests │ ├── Bundle.test.swift │ ├── Resources │ └── Fonts │ │ ├── paeez.ttf │ │ ├── shokuh.ttf │ │ ├── dastnevis.otf │ │ ├── khodkar.ttf │ │ ├── shahgoosh.ttf │ │ └── Yekan Boom.ttf │ ├── SnapshotTests │ ├── __Snapshots__ │ │ ├── UIKitSnapshotTests │ │ │ ├── testSampleViewController.1.png │ │ │ ├── testLongMultilineTextOnUILabel.Font-BTitrBold.png │ │ │ ├── testLongMultilineTextOnUILabel.Font-DastNevis.png │ │ │ ├── testLongMultilineTextOnUILabel.Font-shahgosh.png │ │ │ ├── testLongMultilineTextOnUILabel.Font-Shokoh-Bold.png │ │ │ ├── testLongMultilineTextOnUILabel.Font-Digi-Paeez-Regular.png │ │ │ └── testLongMultilineTextOnUILabel.Font-MT_YekanSquareBoom-Bold.png │ │ └── AppKitSnapshotTests │ │ │ ├── testLongMultilineTextOnNSTextField.Font-BTitrBold.png │ │ │ ├── testLongMultilineTextOnNSTextField.Font-DastNevis.png │ │ │ ├── testLongMultilineTextOnNSTextField.Font-shahgosh.png │ │ │ ├── testLongMultilineTextOnNSTextField.Font-Shokoh-Bold.png │ │ │ ├── testLongMultilineTextOnNSTextField.Font-Digi-Paeez-Regular.png │ │ │ └── testLongMultilineTextOnNSTextField.Font-MT_YekanSquareBoom-Bold.png │ ├── AppKitSnapshotTests.swift │ └── UIKitSnapshotTests.swift │ ├── DemoTexts.swift │ ├── GetFontTests.swift │ ├── MainFunctionalityTests.swift │ └── Sample │ └── SampleViewController.swift ├── Example ├── perian_justify_example │ ├── Assets.xcassets │ │ ├── Contents.json │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Fonts │ │ ├── paeez.ttf │ │ ├── shokuh.ttf │ │ ├── dastnevis.otf │ │ ├── khodkar.ttf │ │ ├── shahgoosh.ttf │ │ └── Yekan Boom.ttf │ ├── Helpers │ │ └── UILabelExtensions.swift │ ├── Info.plist │ ├── AppDelegate.swift │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── SceneDelegate.swift │ └── ViewController.swift └── perian_justify_example.xcodeproj │ ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved │ └── project.pbxproj ├── .gitignore ├── Sources └── PersianJustify │ ├── printConditioner.swift │ ├── Extensions │ ├── Typealias.swift │ ├── View.getFont.swift │ ├── String.range(of,options).swift │ ├── String.toPJString(in).swift │ └── UILabel.fixDirectionForiOS26.swift │ └── PersianJustify.swift ├── .swiftpm └── xcode │ ├── package.xcworkspace │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ └── xcschemes │ └── PersianJustify.xcscheme ├── PersianJustify.iml ├── .github └── workflows │ └── swift.yml ├── LICENSE ├── Package.swift ├── Package.resolved ├── .swiftlint.yml ├── README.md └── CODE_OF_CONDUCT.md /Previews/Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HappyIosDeveloper/PersianJustify/HEAD/Previews/Logo.png -------------------------------------------------------------------------------- /Previews/example.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HappyIosDeveloper/PersianJustify/HEAD/Previews/example.jpg -------------------------------------------------------------------------------- /Previews/Flowchart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HappyIosDeveloper/PersianJustify/HEAD/Previews/Flowchart.png -------------------------------------------------------------------------------- /Previews/founders.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HappyIosDeveloper/PersianJustify/HEAD/Previews/founders.jpg -------------------------------------------------------------------------------- /Previews/custom_font_1_preview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HappyIosDeveloper/PersianJustify/HEAD/Previews/custom_font_1_preview.jpg -------------------------------------------------------------------------------- /Previews/custom_font_2_preview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HappyIosDeveloper/PersianJustify/HEAD/Previews/custom_font_2_preview.jpg -------------------------------------------------------------------------------- /Previews/custom_font_3_preview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HappyIosDeveloper/PersianJustify/HEAD/Previews/custom_font_3_preview.jpg -------------------------------------------------------------------------------- /Previews/original_font_preview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HappyIosDeveloper/PersianJustify/HEAD/Previews/original_font_preview.jpg -------------------------------------------------------------------------------- /Tests/PersianJustifyTests/Bundle.test.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension Bundle { 4 | static var test: Bundle { .module } 5 | } 6 | -------------------------------------------------------------------------------- /Example/perian_justify_example/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Example/perian_justify_example/Fonts/paeez.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HappyIosDeveloper/PersianJustify/HEAD/Example/perian_justify_example/Fonts/paeez.ttf -------------------------------------------------------------------------------- /Example/perian_justify_example/Fonts/shokuh.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HappyIosDeveloper/PersianJustify/HEAD/Example/perian_justify_example/Fonts/shokuh.ttf -------------------------------------------------------------------------------- /Example/perian_justify_example/Fonts/dastnevis.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HappyIosDeveloper/PersianJustify/HEAD/Example/perian_justify_example/Fonts/dastnevis.otf -------------------------------------------------------------------------------- /Example/perian_justify_example/Fonts/khodkar.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HappyIosDeveloper/PersianJustify/HEAD/Example/perian_justify_example/Fonts/khodkar.ttf -------------------------------------------------------------------------------- /Example/perian_justify_example/Fonts/shahgoosh.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HappyIosDeveloper/PersianJustify/HEAD/Example/perian_justify_example/Fonts/shahgoosh.ttf -------------------------------------------------------------------------------- /Example/perian_justify_example/Fonts/Yekan Boom.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HappyIosDeveloper/PersianJustify/HEAD/Example/perian_justify_example/Fonts/Yekan Boom.ttf -------------------------------------------------------------------------------- /Tests/PersianJustifyTests/Resources/Fonts/paeez.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HappyIosDeveloper/PersianJustify/HEAD/Tests/PersianJustifyTests/Resources/Fonts/paeez.ttf -------------------------------------------------------------------------------- /Tests/PersianJustifyTests/Resources/Fonts/shokuh.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HappyIosDeveloper/PersianJustify/HEAD/Tests/PersianJustifyTests/Resources/Fonts/shokuh.ttf -------------------------------------------------------------------------------- /Tests/PersianJustifyTests/Resources/Fonts/dastnevis.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HappyIosDeveloper/PersianJustify/HEAD/Tests/PersianJustifyTests/Resources/Fonts/dastnevis.otf -------------------------------------------------------------------------------- /Tests/PersianJustifyTests/Resources/Fonts/khodkar.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HappyIosDeveloper/PersianJustify/HEAD/Tests/PersianJustifyTests/Resources/Fonts/khodkar.ttf -------------------------------------------------------------------------------- /Tests/PersianJustifyTests/Resources/Fonts/shahgoosh.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HappyIosDeveloper/PersianJustify/HEAD/Tests/PersianJustifyTests/Resources/Fonts/shahgoosh.ttf -------------------------------------------------------------------------------- /Tests/PersianJustifyTests/Resources/Fonts/Yekan Boom.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HappyIosDeveloper/PersianJustify/HEAD/Tests/PersianJustifyTests/Resources/Fonts/Yekan Boom.ttf -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | xcuserdata/ 5 | DerivedData/ 6 | .swiftpm/configuration/registries.json 7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 8 | .netrc 9 | -------------------------------------------------------------------------------- /Example/perian_justify_example.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Sources/PersianJustify/printConditioner.swift: -------------------------------------------------------------------------------- 1 | internal func print(_ items: Any..., separator: String = " ", terminator: String = "\n") { 2 | #if DEBUG 3 | Swift.print(items, separator: separator, terminator: terminator) 4 | #endif 5 | } 6 | -------------------------------------------------------------------------------- /Example/perian_justify_example/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 | -------------------------------------------------------------------------------- /Tests/PersianJustifyTests/SnapshotTests/__Snapshots__/UIKitSnapshotTests/testSampleViewController.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HappyIosDeveloper/PersianJustify/HEAD/Tests/PersianJustifyTests/SnapshotTests/__Snapshots__/UIKitSnapshotTests/testSampleViewController.1.png -------------------------------------------------------------------------------- /Sources/PersianJustify/Extensions/Typealias.swift: -------------------------------------------------------------------------------- 1 | #if canImport(UIKit) 2 | import UIKit 3 | public typealias Font = UIFont 4 | public typealias View = UIView 5 | #elseif canImport(AppKit) 6 | import AppKit 7 | public typealias Font = NSFont 8 | public typealias View = NSView 9 | #endif 10 | -------------------------------------------------------------------------------- /Sources/PersianJustify/Extensions/View.getFont.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | internal extension View { 4 | func getFont() -> Font? { 5 | let key = "font" 6 | guard responds(to: Selector(key)) else { return nil } 7 | return value(forKey: key) as? Font 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Tests/PersianJustifyTests/SnapshotTests/__Snapshots__/UIKitSnapshotTests/testLongMultilineTextOnUILabel.Font-BTitrBold.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HappyIosDeveloper/PersianJustify/HEAD/Tests/PersianJustifyTests/SnapshotTests/__Snapshots__/UIKitSnapshotTests/testLongMultilineTextOnUILabel.Font-BTitrBold.png -------------------------------------------------------------------------------- /Tests/PersianJustifyTests/SnapshotTests/__Snapshots__/UIKitSnapshotTests/testLongMultilineTextOnUILabel.Font-DastNevis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HappyIosDeveloper/PersianJustify/HEAD/Tests/PersianJustifyTests/SnapshotTests/__Snapshots__/UIKitSnapshotTests/testLongMultilineTextOnUILabel.Font-DastNevis.png -------------------------------------------------------------------------------- /Tests/PersianJustifyTests/SnapshotTests/__Snapshots__/UIKitSnapshotTests/testLongMultilineTextOnUILabel.Font-shahgosh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HappyIosDeveloper/PersianJustify/HEAD/Tests/PersianJustifyTests/SnapshotTests/__Snapshots__/UIKitSnapshotTests/testLongMultilineTextOnUILabel.Font-shahgosh.png -------------------------------------------------------------------------------- /Tests/PersianJustifyTests/SnapshotTests/__Snapshots__/UIKitSnapshotTests/testLongMultilineTextOnUILabel.Font-Shokoh-Bold.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HappyIosDeveloper/PersianJustify/HEAD/Tests/PersianJustifyTests/SnapshotTests/__Snapshots__/UIKitSnapshotTests/testLongMultilineTextOnUILabel.Font-Shokoh-Bold.png -------------------------------------------------------------------------------- /Example/perian_justify_example/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | } 8 | ], 9 | "info" : { 10 | "author" : "xcode", 11 | "version" : 1 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Tests/PersianJustifyTests/SnapshotTests/__Snapshots__/AppKitSnapshotTests/testLongMultilineTextOnNSTextField.Font-BTitrBold.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HappyIosDeveloper/PersianJustify/HEAD/Tests/PersianJustifyTests/SnapshotTests/__Snapshots__/AppKitSnapshotTests/testLongMultilineTextOnNSTextField.Font-BTitrBold.png -------------------------------------------------------------------------------- /Tests/PersianJustifyTests/SnapshotTests/__Snapshots__/AppKitSnapshotTests/testLongMultilineTextOnNSTextField.Font-DastNevis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HappyIosDeveloper/PersianJustify/HEAD/Tests/PersianJustifyTests/SnapshotTests/__Snapshots__/AppKitSnapshotTests/testLongMultilineTextOnNSTextField.Font-DastNevis.png -------------------------------------------------------------------------------- /Tests/PersianJustifyTests/SnapshotTests/__Snapshots__/AppKitSnapshotTests/testLongMultilineTextOnNSTextField.Font-shahgosh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HappyIosDeveloper/PersianJustify/HEAD/Tests/PersianJustifyTests/SnapshotTests/__Snapshots__/AppKitSnapshotTests/testLongMultilineTextOnNSTextField.Font-shahgosh.png -------------------------------------------------------------------------------- /Tests/PersianJustifyTests/SnapshotTests/__Snapshots__/AppKitSnapshotTests/testLongMultilineTextOnNSTextField.Font-Shokoh-Bold.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HappyIosDeveloper/PersianJustify/HEAD/Tests/PersianJustifyTests/SnapshotTests/__Snapshots__/AppKitSnapshotTests/testLongMultilineTextOnNSTextField.Font-Shokoh-Bold.png -------------------------------------------------------------------------------- /Tests/PersianJustifyTests/SnapshotTests/__Snapshots__/UIKitSnapshotTests/testLongMultilineTextOnUILabel.Font-Digi-Paeez-Regular.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HappyIosDeveloper/PersianJustify/HEAD/Tests/PersianJustifyTests/SnapshotTests/__Snapshots__/UIKitSnapshotTests/testLongMultilineTextOnUILabel.Font-Digi-Paeez-Regular.png -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Tests/PersianJustifyTests/SnapshotTests/__Snapshots__/AppKitSnapshotTests/testLongMultilineTextOnNSTextField.Font-Digi-Paeez-Regular.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HappyIosDeveloper/PersianJustify/HEAD/Tests/PersianJustifyTests/SnapshotTests/__Snapshots__/AppKitSnapshotTests/testLongMultilineTextOnNSTextField.Font-Digi-Paeez-Regular.png -------------------------------------------------------------------------------- /Tests/PersianJustifyTests/SnapshotTests/__Snapshots__/UIKitSnapshotTests/testLongMultilineTextOnUILabel.Font-MT_YekanSquareBoom-Bold.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HappyIosDeveloper/PersianJustify/HEAD/Tests/PersianJustifyTests/SnapshotTests/__Snapshots__/UIKitSnapshotTests/testLongMultilineTextOnUILabel.Font-MT_YekanSquareBoom-Bold.png -------------------------------------------------------------------------------- /Tests/PersianJustifyTests/SnapshotTests/__Snapshots__/AppKitSnapshotTests/testLongMultilineTextOnNSTextField.Font-MT_YekanSquareBoom-Bold.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HappyIosDeveloper/PersianJustify/HEAD/Tests/PersianJustifyTests/SnapshotTests/__Snapshots__/AppKitSnapshotTests/testLongMultilineTextOnNSTextField.Font-MT_YekanSquareBoom-Bold.png -------------------------------------------------------------------------------- /Example/perian_justify_example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Sources/PersianJustify/Extensions/String.range(of,options).swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension String { 4 | 5 | /// Finds and returns the range of the first occurrence of a given string within the string, subject to given options. 6 | func range(of searchString: String, options mask: NSString.CompareOptions = []) -> NSRange { 7 | (self as NSString).range(of: searchString, options: mask) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Example/perian_justify_example.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "originHash" : "fbd9878878133309570b047b11f57a737e995fb2bc7eba51a820ab14bf03a929", 3 | "pins" : [ 4 | { 5 | "identity" : "persianjustify", 6 | "kind" : "remoteSourceControl", 7 | "location" : "https://github.com/HappyIosDeveloper/PersianJustify", 8 | "state" : { 9 | "revision" : "d92423f2fce26821b877c66c5c3846a7b455a52f", 10 | "version" : "0.6.1" 11 | } 12 | } 13 | ], 14 | "version" : 3 15 | } 16 | -------------------------------------------------------------------------------- /Sources/PersianJustify/Extensions/String.toPJString(in).swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension String { 4 | 5 | @available( 6 | *, deprecated, 7 | renamed: "toPJString(fittingWidth:font:)", 8 | message: "This method requires too much information and will not be available from v1.0" 9 | ) 10 | public func toPJString(in view: View) -> NSAttributedString { 11 | let defaultFont = Font() 12 | let font = view.getFont() ?? defaultFont 13 | let parentWidth = view.frame.width 14 | return toPJString(fittingWidth: parentWidth, font: font) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Example/perian_justify_example/Helpers/UILabelExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UILabelExtensions.swift 3 | // perian_justify 4 | // 5 | // Created by Ahmadreza on 2/17/24. 6 | // 7 | 8 | import UIKit 9 | 10 | extension UILabel { 11 | 12 | // MARK: This will mess with the applied attributions 13 | func markExtenders() { 14 | let attributedText = self.text!.reduce(NSMutableAttributedString()) { 15 | $0.append(NSAttributedString(string: String($1),attributes: [.foregroundColor: $1 == "ـ" ? UIColor.systemPink : .black])) 16 | return $0 17 | } 18 | self.attributedText = attributedText 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /PersianJustify.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | ... 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Sources/PersianJustify/Extensions/UILabel.fixDirectionForiOS26.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UILabel.fixDirectionForiOS26.swift 3 | // PersianJustify 4 | // 5 | // Created by Ahmadreza on 10/13/25. 6 | // 7 | 8 | #if canImport(UIKit) 9 | import UIKit 10 | public extension UILabel { 11 | 12 | @available(iOS 26.0, *) 13 | public func fixDirectionForiOS26() { 14 | if let traitOverrides = value(forKey: "traitOverrides") as? NSObject { 15 | if traitOverrides.responds(to: NSSelectorFromString("setResolvesNaturalAlignmentWithBaseWritingDirection:")) { 16 | traitOverrides.setValue(true, forKey: "resolvesNaturalAlignmentWithBaseWritingDirection") 17 | } 18 | } 19 | } 20 | } 21 | #endif 22 | -------------------------------------------------------------------------------- /.github/workflows/swift.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Swift project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-swift 3 | 4 | name: Automation Test 5 | 6 | on: 7 | push: 8 | branches: [ "main" ] 9 | pull_request: 10 | branches: [ "main" ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: macos-14 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: maxim-lobanov/setup-xcode@v1 20 | with: 21 | xcode-version: '15.2' 22 | - name: Build with the release configurations 23 | run: swift build -c release -v 24 | - name: Tests on iPhone 15 Pro (iOS 17.2) Simulator with release configurations 25 | run: xcodebuild 26 | -scheme 'PersianJustify' 27 | -sdk iphonesimulator 28 | -destination 'platform=iOS Simulator,name=iPhone 15 Pro,OS=17.2' 29 | test -------------------------------------------------------------------------------- /Tests/PersianJustifyTests/DemoTexts.swift: -------------------------------------------------------------------------------- 1 | let shortDemoText = """ 2 | خودروهایی که نام‌شان در این لیست قیمت وجود ندارد، محصولاتی هستند که در اردیبهشت‌ماه سال 1402، یا تولید نشده‌اند یا هنوز زمان عرضه و تحویل آنها فرا نرسیده است. 3 | """ 4 | 5 | let longDemoText = """ 6 | داشتن خواب باکیفیت برای سلامتی انسان اهمیت بسیار زیادی دارد. همه‌ی افراد در طول روز ساعت‌های زیادی را در حال فعالیت هستند و استرس‌های مختلفی را تحمل می‌کنند، بنابراین اتاق خواب باید یک جای راحت و آرامبخش باشد. هیچ‌کس از اهمیت داشتن یک تخت خواب و تشک راحت بی‌خیر نیست، اما نکته‌ی پر اهمیت بعدی روتختی، رو بالشی، پتو، لحاف و کاور و در واقع ست سرویس خوابی است که استفاده می‌کنید. رنگ‌هایی که در اتاق خواب استفاده می‌کنید باید آرامبخش باشند و یک سرویس خواب طوسی یا آبی می‌تواند بسیار مناسب باشد. باتوجه به خواب آور بودن رنگ بنفش یک سرویس خواب بنفش شیک هم گزینه‌ی خوبی است. پارچه‌ی به کار رفته برای دوخت روتختی هم بسیار مهم است، زیرا باید به‌راحتی شسته شود و عرق بدن را هم جذب کند. یک روبالشتی خوب هم باید نرم و لطیف باشد تا موها آسیب نبیننند. پارچه‌های نخی و کتان برای روتختی بسیار مناسب‌اند و از ساتن و ابریشم می‌توانید برای روبالشتی استفاده کنید. 7 | """ 8 | -------------------------------------------------------------------------------- /Example/perian_justify_example/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIApplicationSceneManifest 6 | 7 | UIApplicationSupportsMultipleScenes 8 | 9 | UISceneConfigurations 10 | 11 | UIWindowSceneSessionRoleApplication 12 | 13 | 14 | UISceneConfigurationName 15 | Default Configuration 16 | UISceneDelegateClassName 17 | $(PRODUCT_MODULE_NAME).SceneDelegate 18 | UISceneStoryboardFile 19 | Main 20 | 21 | 22 | 23 | 24 | UIAppFonts 25 | 26 | dastnevis.otf 27 | khodkar.ttf 28 | paeez.ttf 29 | shahgoosh.ttf 30 | shokuh.ttf 31 | Yekan Boom.ttf 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Ahmadreza 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Tests/PersianJustifyTests/SnapshotTests/AppKitSnapshotTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import FontBlaster 3 | import SnapshotTesting 4 | @testable import PersianJustify 5 | 6 | #if canImport(AppKit) 7 | final class UIKitSnapshotTests: XCTestCase { 8 | override class func setUp() { 9 | FontBlaster.blast(bundle: .test) 10 | } 11 | 12 | func testFonts() { 13 | let fonts = FontBlaster.loadedFonts 14 | XCTAssertFalse(fonts.isEmpty) 15 | } 16 | 17 | func testLongMultilineTextOnNSTextField() throws { 18 | let width: CGFloat = 360 19 | for fontName in FontBlaster.loadedFonts { 20 | let font = try XCTUnwrap(Font(name: fontName, size: 17)) 21 | let justifiedText = longDemoText.toPJString(fittingWidth: width, font: font) 22 | let sut = NSTextField(labelWithAttributedString: justifiedText) 23 | sut.textColor = .black 24 | 25 | assertSnapshot( 26 | of: sut, 27 | as: .image(size: CGSize(width: width, height: 780)), 28 | named: "Font:\(fontName)" 29 | ) 30 | } 31 | } 32 | } 33 | #endif 34 | -------------------------------------------------------------------------------- /Tests/PersianJustifyTests/SnapshotTests/UIKitSnapshotTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import FontBlaster 3 | import SnapshotTesting 4 | @testable import PersianJustify 5 | 6 | #if canImport(UIKit) 7 | final class UIKitSnapshotTests: XCTestCase { 8 | override class func setUp() { 9 | FontBlaster.blast(bundle: .test) 10 | } 11 | 12 | func testFonts() { 13 | let fonts = FontBlaster.loadedFonts 14 | XCTAssertFalse(fonts.isEmpty) 15 | } 16 | 17 | func testSampleViewController() throws { 18 | let sut = SampleViewController() 19 | 20 | assertSnapshot(of: sut, as: .image(size: CGSize(width: 360, height: 780))) 21 | } 22 | 23 | func testLongMultilineTextOnUILabel() throws { 24 | let width: CGFloat = 360 25 | for fontName in FontBlaster.loadedFonts { 26 | let font = try XCTUnwrap(Font(name: fontName, size: 17)) 27 | let justifiedText = shortDemoText.toPJString(fittingWidth: width, font: font) 28 | let sut = UILabel() 29 | sut.attributedText = justifiedText 30 | sut.numberOfLines = 0 31 | sut.textColor = .black 32 | assertSnapshot( 33 | of: sut, 34 | as: .image(size: CGSize(width: width, height: 780)), 35 | named: "Font:\(fontName)" 36 | ) 37 | } 38 | } 39 | } 40 | #endif 41 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.5 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "PersianJustify", 8 | platforms: [.iOS(.v15), .macOS(.v11), .tvOS(.v12)], 9 | products: [ 10 | // Products define the executables and libraries a package produces, making them visible to other packages. 11 | .library( 12 | name: "PersianJustify", 13 | targets: ["PersianJustify"]), 14 | ], 15 | dependencies: [ 16 | .package(url: "https://github.com/ArtSabintsev/FontBlaster", from: "5.3.0"), 17 | .package(url: "https://github.com/pointfreeco/swift-snapshot-testing", from: "1.18.7"), 18 | ], 19 | targets: [ 20 | // Targets are the basic building blocks of a package, defining a module or a test suite. 21 | // Targets can depend on other targets in this package and products from dependencies. 22 | .target( 23 | name: "PersianJustify"), 24 | .testTarget( 25 | name: "PersianJustifyTests", 26 | dependencies: [ 27 | "PersianJustify", 28 | "FontBlaster", 29 | .product(name: "SnapshotTesting", package: "swift-snapshot-testing"), 30 | ], 31 | exclude: ["SnapshotTests/__Snapshots__"], 32 | resources: [.process("Resources")] 33 | ), 34 | ] 35 | ) 36 | -------------------------------------------------------------------------------- /Example/perian_justify_example/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // PersianJustify 4 | // 5 | // Created by Ahmadreza on 2/17/24. 6 | // 7 | 8 | import UIKit 9 | 10 | @main 11 | class AppDelegate: UIResponder, UIApplicationDelegate { 12 | 13 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 14 | // Override point for customization after application launch. 15 | return true 16 | } 17 | 18 | // MARK: UISceneSession Lifecycle 19 | @available(iOS 13.0, *) 20 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 21 | // Called when a new scene session is being created. 22 | // Use this method to select a configuration to create the new scene with. 23 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 24 | } 25 | 26 | @available(iOS 13.0, *) 27 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 28 | // Called when the user discards a scene session. 29 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 30 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "FontBlaster", 6 | "repositoryURL": "https://github.com/ArtSabintsev/FontBlaster", 7 | "state": { 8 | "branch": null, 9 | "revision": "09fffe9cf20293fce81ce7154b46f8e60291baa8", 10 | "version": "5.3.0" 11 | } 12 | }, 13 | { 14 | "package": "swift-custom-dump", 15 | "repositoryURL": "https://github.com/pointfreeco/swift-custom-dump", 16 | "state": { 17 | "branch": null, 18 | "revision": "82645ec760917961cfa08c9c0c7104a57a0fa4b1", 19 | "version": "1.3.3" 20 | } 21 | }, 22 | { 23 | "package": "swift-snapshot-testing", 24 | "repositoryURL": "https://github.com/pointfreeco/swift-snapshot-testing", 25 | "state": { 26 | "branch": null, 27 | "revision": "a8b7c5e0ed33d8ab8887d1654d9b59f2cbad529b", 28 | "version": "1.18.7" 29 | } 30 | }, 31 | { 32 | "package": "swift-syntax", 33 | "repositoryURL": "https://github.com/swiftlang/swift-syntax", 34 | "state": { 35 | "branch": null, 36 | "revision": "4799286537280063c85a32f09884cfbca301b1a1", 37 | "version": "602.0.0" 38 | } 39 | }, 40 | { 41 | "package": "xctest-dynamic-overlay", 42 | "repositoryURL": "https://github.com/pointfreeco/xctest-dynamic-overlay", 43 | "state": { 44 | "branch": null, 45 | "revision": "4c27acf5394b645b70d8ba19dc249c0472d5f618", 46 | "version": "1.7.0" 47 | } 48 | } 49 | ] 50 | }, 51 | "version": 1 52 | } 53 | -------------------------------------------------------------------------------- /Example/perian_justify_example/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Tests/PersianJustifyTests/GetFontTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import PersianJustify 3 | 4 | #if canImport(UIKit) 5 | final class GetFontTests: XCTestCase { 6 | 7 | func testUILabelGetFont() { 8 | let sut = UILabel() 9 | let expFont = UIFont.systemFont(ofSize: 100) 10 | sut.font = expFont 11 | 12 | XCTAssertEqual(sut.font, expFont) 13 | XCTAssertEqual(sut.getFont(), expFont) 14 | XCTAssertEqual(sut.getFont(), sut.font) 15 | } 16 | 17 | func testUITextFieldGetFont() { 18 | let sut = UITextField() 19 | let expFont = UIFont.systemFont(ofSize: 100) 20 | sut.font = expFont 21 | 22 | XCTAssertEqual(sut.font, expFont) 23 | XCTAssertEqual(sut.getFont(), expFont) 24 | XCTAssertEqual(sut.getFont(), sut.font) 25 | } 26 | 27 | func testUITextViewGetFont() { 28 | let sut = UITextView() 29 | let expFont = UIFont.systemFont(ofSize: 100) 30 | sut.font = expFont 31 | 32 | XCTAssertEqual(sut.font, expFont) 33 | XCTAssertEqual(sut.getFont(), expFont) 34 | XCTAssertEqual(sut.getFont(), sut.font) 35 | } 36 | 37 | func testUIViewGetFont() { 38 | let sut = UIView() 39 | XCTAssertNil(sut.getFont()) 40 | } 41 | } 42 | #endif 43 | 44 | #if canImport(AppKit) 45 | final class GetFontTests: XCTestCase { 46 | 47 | // There is no such a thing as `NSLabel` in the `AppKit` 48 | 49 | func testNSTextFieldGetFont() { 50 | let sut = NSTextField() 51 | let expFont = NSFont.systemFont(ofSize: 100) 52 | sut.font = expFont 53 | 54 | XCTAssertEqual(sut.font, expFont) 55 | XCTAssertEqual(sut.getFont(), expFont) 56 | XCTAssertEqual(sut.getFont(), sut.font) 57 | } 58 | 59 | func testUITextViewGetFont() { 60 | let sut = NSTextView() 61 | let expFont = NSFont.systemFont(ofSize: 100) 62 | sut.font = expFont 63 | 64 | XCTAssertEqual(sut.font, expFont) 65 | XCTAssertEqual(sut.getFont(), expFont) 66 | XCTAssertEqual(sut.getFont(), sut.font) 67 | } 68 | } 69 | #endif 70 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | # By default, SwiftLint uses a set of sensible default rules you can adjust: 2 | disabled_rules: # rule identifiers turned on by default to exclude from running 3 | - colon 4 | - comma 5 | - control_statement 6 | opt_in_rules: # some rules are turned off by default, so you need to opt-in 7 | - empty_count # find all the available rules by running: `swiftlint rules` 8 | 9 | # Alternatively, specify all rules explicitly by uncommenting this option: 10 | # only_rules: # delete `disabled_rules` & `opt_in_rules` if using this 11 | # - empty_parameters 12 | # - vertical_whitespace 13 | 14 | analyzer_rules: # rules run by `swiftlint analyze` 15 | - explicit_self 16 | 17 | included: # case-sensitive paths to include during linting. `--path` is ignored if present 18 | - PersianJustify 19 | excluded: # case-sensitive paths to ignore during linting. Takes precedence over `included` 20 | - Carthage 21 | - Pods 22 | - Sources/ExcludedFolder 23 | - Sources/ExcludedFile.swift 24 | - Sources/*/ExcludedFile.swift # exclude files with a wildcard 25 | 26 | # If true, SwiftLint will not fail if no lintable files are found. 27 | allow_zero_lintable_files: false 28 | 29 | # If true, SwiftLint will treat all warnings as errors. 30 | strict: false 31 | 32 | # configurable rules can be customized from this configuration file 33 | # binary rules can set their severity level 34 | force_cast: warning # implicitly 35 | force_try: 36 | severity: warning # explicitly 37 | # rules that have both warning and error levels, can set just the warning level 38 | # implicitly 39 | line_length: 110 40 | # they can set both implicitly with an array 41 | type_body_length: 42 | - 300 # warning 43 | - 400 # error 44 | # or they can set both explicitly 45 | file_length: 46 | warning: 500 47 | error: 1200 48 | # naming rules can set warnings/errors for min_length and max_length 49 | # additionally they can set excluded names 50 | type_name: 51 | min_length: 4 # only warning 52 | max_length: # warning and error 53 | warning: 40 54 | error: 50 55 | excluded: iPhone # excluded via string 56 | allowed_symbols: ["_"] # these are allowed in type names 57 | identifier_name: 58 | min_length: # only min_length 59 | error: 4 # only error 60 | excluded: # excluded via string array 61 | - id 62 | - URL 63 | - GlobalAPIKey 64 | reporter: "xcode" # reporter type (xcode, json, csv, checkstyle, codeclimate, junit, html, emoji, sonarqube, markdown, github-actions-logging, summary) 65 | -------------------------------------------------------------------------------- /Example/perian_justify_example/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // PersianJustify 4 | // 5 | // Created by Ahmadreza on 2/17/24. 6 | // 7 | 8 | import UIKit 9 | 10 | @available(iOS 13.0, *) 11 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 12 | 13 | var window: UIWindow? 14 | 15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 16 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 17 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 18 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 19 | guard let _ = (scene as? UIWindowScene) else { return } 20 | } 21 | 22 | func sceneDidDisconnect(_ scene: UIScene) { 23 | // Called as the scene is being released by the system. 24 | // This occurs shortly after the scene enters the background, or when its session is discarded. 25 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 26 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). 27 | } 28 | 29 | func sceneDidBecomeActive(_ scene: UIScene) { 30 | // Called when the scene has moved from an inactive state to an active state. 31 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 32 | } 33 | 34 | func sceneWillResignActive(_ scene: UIScene) { 35 | // Called when the scene will move from an active state to an inactive state. 36 | // This may occur due to temporary interruptions (ex. an incoming phone call). 37 | } 38 | 39 | func sceneWillEnterForeground(_ scene: UIScene) { 40 | // Called as the scene transitions from the background to the foreground. 41 | // Use this method to undo the changes made on entering the background. 42 | } 43 | 44 | func sceneDidEnterBackground(_ scene: UIScene) { 45 | // Called as the scene transitions from the foreground to the background. 46 | // Use this method to save data, release shared resources, and store enough scene-specific state information 47 | // to restore the scene back to its current state. 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Tests/PersianJustifyTests/MainFunctionalityTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Ahmadreza on 3/15/24. 6 | // 7 | 8 | import XCTest 9 | @testable import PersianJustify 10 | 11 | #if canImport(UIKit) 12 | final class MainFunctionalityTests: XCTestCase { 13 | 14 | func testPerformance() { 15 | let sut = UILabel() 16 | measure { 17 | sut.attributedText = longDemoText.toPJString(fittingWidth: sut.bounds.width, font: sut.font) 18 | } 19 | } 20 | 21 | func testMainFunctionIsGettingOutput() { 22 | let text1 = "" 23 | let text2 = "blah blah" 24 | let text3 = "السلام اللعیکم\nو رحمت الله و برکاتو" 25 | 26 | let sut = UILabel() 27 | sut.attributedText = text1.toPJString(fittingWidth: sut.bounds.width, font: sut.font) 28 | XCTAssertEqual(sut.attributedText?.string.count, text1.count) 29 | 30 | sut.attributedText = text2.toPJString(fittingWidth: sut.bounds.width, font: sut.font) 31 | XCTAssertEqual(sut.attributedText?.string.count, text2.count) 32 | 33 | sut.attributedText = text3.toPJString(fittingWidth: sut.bounds.width, font: sut.font) 34 | XCTAssertEqual(sut.attributedText?.string.count, text3.count) 35 | } 36 | 37 | func testMainFunctionIsGettingExactNextLineCharacter() { 38 | let noEnter = "a" 39 | let twoEnters = "a\na" 40 | let threeEnters = "a\na\na\na" 41 | 42 | let sut = UILabel() 43 | sut.attributedText = noEnter.toPJString(fittingWidth: sut.bounds.width, font: sut.font) 44 | XCTAssertEqual(sut.attributedText?.string.getNextLineCount(), noEnter.getNextLineCount()) 45 | 46 | sut.attributedText = twoEnters.toPJString(fittingWidth: sut.bounds.width, font: sut.font) 47 | XCTAssertEqual(sut.attributedText?.string.getNextLineCount(), twoEnters.getNextLineCount()) 48 | 49 | sut.attributedText = threeEnters.toPJString(fittingWidth: sut.bounds.width, font: sut.font) 50 | XCTAssertEqual(sut.attributedText?.string.getNextLineCount(), threeEnters.getNextLineCount()) 51 | } 52 | 53 | func testMainFunctionIsGettingExactSpaceCharacter() { 54 | let noSpace = "a" 55 | let twoSpaces = "a a" 56 | let threeSpaces = "a a a a" 57 | 58 | let sut = UILabel() 59 | sut.attributedText = noSpace.toPJString(fittingWidth: sut.bounds.width, font: sut.font) 60 | XCTAssertEqual(sut.attributedText?.string.getSpaceCount(), noSpace.getSpaceCount()) 61 | 62 | sut.attributedText = twoSpaces.toPJString(fittingWidth: sut.bounds.width, font: sut.font) 63 | XCTAssertEqual(sut.attributedText?.string.getSpaceCount(), twoSpaces.getSpaceCount()) 64 | 65 | sut.attributedText = threeSpaces.toPJString(fittingWidth: sut.bounds.width, font: sut.font) 66 | XCTAssertEqual(sut.attributedText?.string.getSpaceCount(), threeSpaces.getSpaceCount()) 67 | } 68 | } 69 | #endif 70 | 71 | private extension String { 72 | 73 | func getNextLineCount()-> Int { 74 | return filter({$0 == "\n"}).count 75 | } 76 | 77 | func getSpaceCount()-> Int { 78 | return filter({$0 == " "}).count 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /.swiftpm/xcode/xcshareddata/xcschemes/PersianJustify.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 9 | 10 | 16 | 22 | 23 | 24 | 25 | 26 | 32 | 33 | 35 | 41 | 42 | 43 | 44 | 45 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Persian Justify 4 | 5 | Justify Hebrew languages in iOS using the Swift language and CoreText. 6 | 7 | Image 1 8 | 9 | [![Automation Test](https://github.com/HappyIosDeveloper/PersianJustify/actions/workflows/swift.yml/badge.svg)](https://github.com/HappyIosDeveloper/PersianJustify/actions/workflows/swift.yml) 10 | | ⬇️ | ⬇️ | 11 | | --- | --- | 12 | ||| 13 | ||| 14 | 15 | 16 | ### Usage: 17 | ##### ✅ Add PersianJustify using SPM 18 | ##### ✅ Import PersianJustify in your class and use it like so: 19 | ```ruby 20 | yourLabel.numberOfLines = 0 21 | yourLabel.attributedText = yourText.toPJString(in: yourLabel) 22 | ``` 23 | 24 | 25 | ### Example Project: 26 | Image 1 27 | Navigate to the "Example" folder with finder and open the example project while the main project is not open in XCode. 28 | 29 | 30 | ### How it works? 31 | Simple - It breaks the words into lines base of the provided width and then justifies the lines individually using CoreText. 32 | 33 | flowchart 34 | 35 | 36 | ### Problems: 37 | - Not optimized (yet) and consumes a lot of energy. 38 | - Some weird UI issues in text with some fonts. 39 | - Not tested with all Hebrew languages. 40 | - It needs more tests. 41 | 42 | 43 | #### Contribution: 44 | ##### I accept the helps to fix the bugs and improve the functionality with open arms, but please consider these conventions: 45 | - Don't change the project structure and reconstruct everything. 46 | - To streamline the review process and make it easier to merge, consider breaking down the changes into smaller, more focused pull requests. 47 | - Each PR should be small in scope, addressing only one task (e.g., feature, refactor, documentation) to allow maintainers to choose what to merge and where. 48 | - Commits should follow a logical flow, making the commit history easy to follow and enabling project builds for each individual commit. 49 | - Avoid unnecessary back-and-forth commits (such as add -> delete -> add) that can occur during development. 50 | - Tests should pass locally and in continuous integration. 51 | - Code should adhere to linting and styling rules according to the repository's configuration. 52 | - Ensure that each PR includes clear and concise documentation explaining the changes made and any relevant context for reviewers. 53 | - Follow a consistent naming convention for branches, commits, and PR titles to facilitate navigation and understanding of the project's history. 54 | - Prioritize backward compatibility and consider the impact of changes on existing functionality, dependencies, and users. 55 | - Strive for simplicity and readability in code, favoring clarity and maintainability over complexity. 56 | - Maintain a clean and organized project structure, with clear separation of concerns and minimal coupling between components. 57 | - Foster an inclusive and respectful environment, welcoming contributions from developers of all backgrounds and skill levels. 58 | 59 | 60 | ### Credits: 61 | I'm very thankful to my dear brother and teacher [MR.Mojtaba Hosseini](https://github.com/MojtabaHs) to be a big part of this.' 62 | 63 | 64 | 65 | ### License: 66 | PersianJustify is released under the MIT license. See [LICENSE](https://github.com/HappyIosDeveloper/PersianJustify/blob/main/LICENSE) for details. 67 | -------------------------------------------------------------------------------- /Example/perian_justify_example/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // PersianJustify 4 | // 5 | // Created by Ahmadreza on 2/17/24. 6 | // 7 | 8 | import UIKit 9 | import PersianJustify 10 | 11 | class ViewController: UIViewController { 12 | 13 | @IBOutlet weak var fullWidthLabel1: UILabel! 14 | @IBOutlet weak var fullWidthLabel2: UILabel! 15 | @IBOutlet weak var halfWidthLabel1: UILabel! 16 | @IBOutlet weak var halfWidthLabel2: UILabel! 17 | 18 | let text1 = """ 19 | خودروهایی که نام‌شان در این لیست قیمت وجود ندارد، محصولاتی هستند که در اردیبهشت‌ماه سال 1402، یا تولید نشده‌اند یا هنوز زمان عرضه و تحویل آنها فرا نرسیده است. 20 | """ 21 | let text2 = """ 22 | داشتن خواب باکیفیت برای سلامتی انسان اهمیت بسیار زیادی دارد. همه‌ی افراد در طول روز ساعت‌های زیادی را در حال فعالیت هستند و استرس‌های مختلفی را تحمل می‌کنند، بنابراین اتاق خواب باید یک جای راحت و آرامبخش باشد. هیچ‌کس از اهمیت داشتن یک تخت خواب و تشک راحت بی‌خیر نیست، اما نکته‌ی پر اهمیت بعدی روتختی، رو بالشی، پتو، لحاف و کاور و در واقع ست سرویس خوابی است که استفاده می‌کنید. رنگ‌هایی که در اتاق خواب استفاده می‌کنید باید آرامبخش باشند و یک سرویس خواب طوسی یا آبی می‌تواند بسیار مناسب باشد. باتوجه به خواب آور بودن رنگ بنفش یک سرویس خواب بنفش شیک هم گزینه‌ی خوبی است. پارچه‌ی به کار رفته برای دوخت روتختی هم بسیار مهم است، زیرا باید به‌راحتی شسته شود و عرق بدن را هم جذب کند. یک روبالشتی خوب هم باید نرم و لطیف باشد تا موها آسیب نبیننند. پارچه‌های نخی و کتان برای روتختی بسیار مناسب‌اند و از ساتن و ابریشم می‌توانید برای روبالشتی استفاده کنید. 23 | 24 | """ 25 | 26 | enum FontNames: String { 27 | case khodkar = "B Titr Bold" 28 | case dastNevis = "Dast Nevis" 29 | case paeez = "Digi Paeez Regular" 30 | case shahgoosh = "shahgosh" 31 | case shokuh = "Shokoh Bold" 32 | case yekan = "MT_Yekan Square Boom Bold" 33 | } 34 | 35 | override func viewDidLoad() { 36 | super.viewDidLoad() 37 | 38 | setupRTLDirection() 39 | setFont(name: .paeez) 40 | fillLabels() 41 | } 42 | } 43 | 44 | // MARK: - Setup Functions 45 | extension ViewController { 46 | 47 | func setupRTLDirection() { 48 | fullWidthLabel1.textAlignment = .right 49 | fullWidthLabel2.textAlignment = .right 50 | halfWidthLabel1.textAlignment = .right 51 | halfWidthLabel2.textAlignment = .right 52 | fullWidthLabel1.numberOfLines = 0 53 | fullWidthLabel2.numberOfLines = 0 54 | halfWidthLabel1.numberOfLines = 0 55 | halfWidthLabel2.numberOfLines = 0 56 | if #available(iOS 26.0, *) { 57 | fullWidthLabel1.fixDirectionForiOS26() 58 | fullWidthLabel2.fixDirectionForiOS26() 59 | halfWidthLabel1.fixDirectionForiOS26() 60 | halfWidthLabel2.fixDirectionForiOS26() 61 | } 62 | } 63 | 64 | func fillLabels() { 65 | fullWidthLabel1.attributedText = text1.toPJString(in: fullWidthLabel1) 66 | fullWidthLabel2.attributedText = text2.toPJString(in: fullWidthLabel2) 67 | halfWidthLabel1.attributedText = text1.toPJString(in: halfWidthLabel1) 68 | halfWidthLabel2.attributedText = text2.toPJString(in: halfWidthLabel2) 69 | fullWidthLabel1.sizeToFit() 70 | fullWidthLabel2.sizeToFit() 71 | halfWidthLabel1.sizeToFit() 72 | halfWidthLabel2.sizeToFit() 73 | // addBorder() 74 | } 75 | 76 | func addBorder() { 77 | fullWidthLabel1.addBorder() 78 | fullWidthLabel2.addBorder() 79 | halfWidthLabel1.addBorder() 80 | halfWidthLabel2.addBorder() 81 | } 82 | 83 | func setFont(name: FontNames) { 84 | fullWidthLabel1.font = UIFont(name: name.rawValue, size: 17) 85 | fullWidthLabel2.font = UIFont(name: name.rawValue, size: 17) 86 | halfWidthLabel1.font = UIFont(name: name.rawValue, size: 17) 87 | halfWidthLabel2.font = UIFont(name: name.rawValue, size: 17) 88 | } 89 | } 90 | 91 | // MARK: - UIView Extensions 92 | extension UIView { 93 | 94 | func addBorder() { 95 | layer.borderWidth = 1 96 | layer.borderColor = UIColor.red.cgColor 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Tests/PersianJustifyTests/Sample/SampleViewController.swift: -------------------------------------------------------------------------------- 1 | // Based on the Example project 2 | 3 | #if canImport(UIKit) 4 | import UIKit 5 | import PersianJustify 6 | 7 | class SampleViewController: UIViewController { 8 | 9 | var fullWidthLabel1 = UILabel() 10 | var fullWidthLabel2 = UILabel() 11 | var halfWidthLabel1 = UILabel() 12 | var halfWidthLabel2 = UILabel() 13 | 14 | private func setup() { 15 | /// Add views 16 | let scrollView = UIScrollView(frame: view.frame) 17 | view.addSubview(scrollView) 18 | 19 | let horizontalStack = UIStackView(arrangedSubviews: [halfWidthLabel1, halfWidthLabel2]) 20 | horizontalStack.axis = .horizontal 21 | horizontalStack.spacing = 20 22 | 23 | let verticalStack = UIStackView(arrangedSubviews: [fullWidthLabel1, fullWidthLabel2, horizontalStack]) 24 | verticalStack.axis = .vertical 25 | verticalStack.spacing = 20 26 | scrollView.addSubview(verticalStack) 27 | 28 | /// Layout views 29 | scrollView.translatesAutoresizingMaskIntoConstraints = false 30 | scrollView.topAnchor.constraint(equalTo: view.topAnchor, constant: 0).isActive = true 31 | scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0).isActive = true 32 | scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0).isActive = true 33 | scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0).isActive = true 34 | 35 | verticalStack.translatesAutoresizingMaskIntoConstraints = false 36 | 37 | verticalStack.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 0).isActive = true 38 | verticalStack.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: 0).isActive = true 39 | verticalStack.widthAnchor.constraint(equalTo: scrollView.widthAnchor, constant: 0).isActive = true 40 | verticalStack.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor, constant: 0).isActive = true 41 | verticalStack.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor, constant: 0).isActive = true 42 | 43 | setupRTLDirection() 44 | setFont(name: .paeez) 45 | fillLabels() 46 | setColor(.black) 47 | } 48 | 49 | override func loadView() { 50 | super.loadView() 51 | setup() 52 | } 53 | 54 | enum FontNames: String { 55 | case khodkar = "B Titr Bold" 56 | case dastNevis = "Dast Nevis" 57 | case paeez = "Digi Paeez Regular" 58 | case shahgoosh = "shahgosh" 59 | case shokuh = "Shokoh Bold" 60 | case yekan = "MT_Yekan Square Boom Bold" 61 | } 62 | } 63 | 64 | // MARK: - Setup Functions 65 | extension SampleViewController { 66 | 67 | func setupRTLDirection() { 68 | fullWidthLabel1.textAlignment = .right 69 | fullWidthLabel2.textAlignment = .right 70 | halfWidthLabel1.textAlignment = .right 71 | halfWidthLabel2.textAlignment = .right 72 | fullWidthLabel1.numberOfLines = 0 73 | fullWidthLabel2.numberOfLines = 0 74 | halfWidthLabel1.numberOfLines = 0 75 | halfWidthLabel2.numberOfLines = 0 76 | } 77 | 78 | func fillLabels() { 79 | let font = fullWidthLabel1.font! 80 | let width = view.bounds.width 81 | fullWidthLabel1.attributedText = shortDemoText.toPJString(fittingWidth: width, font: font) 82 | fullWidthLabel2.attributedText = longDemoText.toPJString(fittingWidth: width, font: font) 83 | halfWidthLabel1.attributedText = shortDemoText.toPJString(fittingWidth: width, font: font) 84 | halfWidthLabel2.attributedText = longDemoText.toPJString(fittingWidth: width, font: font) 85 | fullWidthLabel1.sizeToFit() 86 | fullWidthLabel2.sizeToFit() 87 | halfWidthLabel1.sizeToFit() 88 | halfWidthLabel2.sizeToFit() 89 | } 90 | 91 | func setFont(name: FontNames) { 92 | fullWidthLabel1.font = UIFont(name: name.rawValue, size: 17) 93 | fullWidthLabel2.font = UIFont(name: name.rawValue, size: 17) 94 | halfWidthLabel1.font = UIFont(name: name.rawValue, size: 17) 95 | halfWidthLabel2.font = UIFont(name: name.rawValue, size: 17) 96 | } 97 | 98 | func setColor(_ color: UIColor) { 99 | fullWidthLabel1.textColor = color 100 | fullWidthLabel2.textColor = color 101 | halfWidthLabel1.textColor = color 102 | halfWidthLabel2.textColor = color 103 | } 104 | } 105 | #endif 106 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | . 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /Sources/PersianJustify/PersianJustify.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainLogics.swift 3 | // PersianJustify 4 | // 5 | // Created by Ahmadreza on 3/14/24. 6 | // 7 | 8 | #if canImport(UIKit) 9 | import UIKit 10 | #elseif canImport(AppKit) 11 | import AppKit 12 | #endif 13 | 14 | import CoreText 15 | 16 | // MARK: - Variables 17 | private let nextLineCharacter: Character = "\n" 18 | private let spaceCharacter: Character = " " 19 | private let miniSpaceCharacter: Character = "‌" 20 | private let extenderCharacter: Character = "ـ" // Persian underline 21 | private let attributedSpace = NSAttributedString(string: spaceCharacter.description) 22 | private let attributedNextLine = NSMutableAttributedString(string: nextLineCharacter.description) 23 | private let forbiddenExtendableCharacters = ["ا", "د", "ذ", "ر", "ز", "و", "آ", "ژ"] 24 | 25 | // MARK: - Usage using toPJString function 26 | extension String { 27 | 28 | public func toPJString(fittingWidth parentWidth: CGFloat, font: Font = Font()) -> NSAttributedString { 29 | let defaultAttributedTest = NSAttributedString(string: self) 30 | // return defaultAttributedTest // MARK: Uncomment to see the unjustified text 31 | if isEmpty { return defaultAttributedTest } 32 | let final = NSMutableAttributedString(string: "") 33 | let doubleNextLine = nextLineCharacter.description + nextLineCharacter.description 34 | let allLines = replacingOccurrences(of: doubleNextLine, with: nextLineCharacter.description).getWords(separator: nextLineCharacter) 35 | for i in 0.. NSMutableAttributedString { 63 | let words = getWords(separator: spaceCharacter) 64 | let totalWordsWidth = words.compactMap({$0.getWordWidth(font: font)}).reduce(0, +) 65 | let emptySpace = parentWidth - totalWordsWidth 66 | let singleExtenderWidth = extenderCharacter.description.getWordWidth(font: font, isRequiredSpace: false) 67 | let requiredExtender = Swift.max((emptySpace / singleExtenderWidth), 0) 68 | let supportedExtenderWords = words.filter({$0.isSupportExtender()}) 69 | print("words: ", self) 70 | print("parent width: \(parentWidth)") 71 | print("totalWordsWidth \(totalWordsWidth)") 72 | print("emptySpace: \(emptySpace)") 73 | if isLastLineInParagraph { // MARK: May not required justify. 74 | return attributedStringWithFont(font: font) 75 | } else { 76 | let isManyExtendersRequired = CGFloat(supportedExtenderWords.count) < requiredExtender 77 | if isManyExtendersRequired { 78 | print("many extenders required") 79 | let requiredExtend = emptySpace / CGFloat(supportedExtenderWords.count) 80 | return getExtendedWords(words: supportedExtenderWords, requiredExtend: requiredExtend * 0.2, font: font) 81 | } else if requiredExtender > 0 && supportedExtenderWords.count > 0 { 82 | print("little extenders required") 83 | return getExtendedWords(words: supportedExtenderWords, requiredExtend: max(requiredExtender * 0.1, 0), font: font) 84 | } else { 85 | print("no extender added") 86 | return attributedStringWithFont(font: font) 87 | } 88 | } 89 | } 90 | 91 | func getExtendedWords(words: [String], requiredExtend: CGFloat, font: Font) -> NSMutableAttributedString { 92 | print("------------------------------------------") 93 | let style = NSMutableParagraphStyle() 94 | style.alignment = NSTextAlignment.justified 95 | style.baseWritingDirection = .rightToLeft 96 | let attributedText = attributedStringWithFont(font: font) 97 | for word in words { 98 | let range = range(of: word, options: .widthInsensitive) 99 | attributedText.addAttribute(NSAttributedString.Key.kern, value: requiredExtend, range: range) 100 | attributedText.addAttributes([NSAttributedString.Key.paragraphStyle: style], range: range) 101 | print("applying extend | \(requiredExtend) to \(word)") 102 | } 103 | print("------------------------------------------") 104 | return attributedText 105 | } 106 | 107 | func attributedStringWithFont(font: Font) -> NSMutableAttributedString { 108 | let totalRange = NSRange(location: 0, length: self.utf16.count) 109 | let attributedText = NSMutableAttributedString(string: self) 110 | attributedText.setAttributes([NSAttributedString.Key.font: font], range: totalRange) 111 | return attributedText 112 | } 113 | 114 | var isArabic: Bool { 115 | let predicate = NSPredicate(format: "SELF MATCHES %@", "(?s).*\\p{Arabic}.*") 116 | return predicate.evaluate(with: self) 117 | } 118 | 119 | func getWords(separator: Character) -> [String] { 120 | return split(separator: separator).compactMap({$0.description}) 121 | } 122 | 123 | func getWordWidth(font: Font, isRequiredSpace: Bool = true) -> CGFloat { 124 | let text = isRequiredSpace ? (self + spaceCharacter.description) : self 125 | let attributedString = NSAttributedString(string: text, attributes: [.font: font]) 126 | let line = CTLineCreateWithAttributedString(attributedString) 127 | var ascent: CGFloat = 0 128 | var descent: CGFloat = 0 129 | var leading: CGFloat = 0 130 | let width = CTLineGetTypographicBounds(line, &ascent, &descent, &leading) 131 | return CGFloat(width) 132 | } 133 | 134 | func isSupportExtender() -> Bool { 135 | guard count > 1 else { return false } 136 | let array = Array(self) 137 | for i in stride(from: count-1, to: 0, by: -1) where (i > 0) && i < count { 138 | let char = array[i].description 139 | let rightChar = array[i-1].description 140 | if !forbiddenExtendableCharacters.contains(rightChar) && rightChar.isArabic && char.isArabic { 141 | return true 142 | } 143 | } 144 | return false 145 | } 146 | } 147 | 148 | private extension [String] { 149 | 150 | func hasRoomForNextWord(nextWord: String, parentWidth: CGFloat, font: Font) -> Bool { 151 | let requiredWidth = nextWord.getWordWidth(font: font) 152 | let currentWidth = compactMap({$0.getWordWidth(font: font)}).reduce(0, +) 153 | return (currentWidth + requiredWidth) <= parentWidth 154 | } 155 | 156 | func joinWithSpace() -> String { 157 | return joined(separator: spaceCharacter.description) 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /Example/perian_justify_example/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 32 | 38 | 39 | 40 | 41 | 47 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /Example/perian_justify_example.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 56; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 0B1847112BA05F5D00C0BAA5 /* Yekan Boom.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 0B18470B2BA05F5D00C0BAA5 /* Yekan Boom.ttf */; }; 11 | 0B1847122BA05F5D00C0BAA5 /* paeez.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 0B18470C2BA05F5D00C0BAA5 /* paeez.ttf */; }; 12 | 0B1847132BA05F5D00C0BAA5 /* khodkar.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 0B18470D2BA05F5D00C0BAA5 /* khodkar.ttf */; }; 13 | 0B1847142BA05F5D00C0BAA5 /* shahgoosh.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 0B18470E2BA05F5D00C0BAA5 /* shahgoosh.ttf */; }; 14 | 0B1847152BA05F5D00C0BAA5 /* dastnevis.otf in Resources */ = {isa = PBXBuildFile; fileRef = 0B18470F2BA05F5D00C0BAA5 /* dastnevis.otf */; }; 15 | 0B1847162BA05F5D00C0BAA5 /* shokuh.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 0B1847102BA05F5D00C0BAA5 /* shokuh.ttf */; }; 16 | 0B71E4CE2BA3580800CA53FA /* PersianJustify in Frameworks */ = {isa = PBXBuildFile; productRef = 0B71E4CD2BA3580800CA53FA /* PersianJustify */; }; 17 | 0BFFF9DD2B809FC10088D24F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BFFF9DC2B809FC10088D24F /* AppDelegate.swift */; }; 18 | 0BFFF9DF2B809FC10088D24F /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BFFF9DE2B809FC10088D24F /* SceneDelegate.swift */; }; 19 | 0BFFF9E12B809FC10088D24F /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BFFF9E02B809FC10088D24F /* ViewController.swift */; }; 20 | 0BFFF9E42B809FC10088D24F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0BFFF9E22B809FC10088D24F /* Main.storyboard */; }; 21 | 0BFFF9E62B809FC20088D24F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0BFFF9E52B809FC20088D24F /* Assets.xcassets */; }; 22 | 0BFFF9E92B809FC20088D24F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0BFFF9E72B809FC20088D24F /* LaunchScreen.storyboard */; }; 23 | 0BFFF9F52B80E5350088D24F /* UILabelExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BFFF9F42B80E5350088D24F /* UILabelExtensions.swift */; }; 24 | /* End PBXBuildFile section */ 25 | 26 | /* Begin PBXFileReference section */ 27 | 0B18470B2BA05F5D00C0BAA5 /* Yekan Boom.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Yekan Boom.ttf"; sourceTree = ""; }; 28 | 0B18470C2BA05F5D00C0BAA5 /* paeez.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = paeez.ttf; sourceTree = ""; }; 29 | 0B18470D2BA05F5D00C0BAA5 /* khodkar.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = khodkar.ttf; sourceTree = ""; }; 30 | 0B18470E2BA05F5D00C0BAA5 /* shahgoosh.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = shahgoosh.ttf; sourceTree = ""; }; 31 | 0B18470F2BA05F5D00C0BAA5 /* dastnevis.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = dastnevis.otf; sourceTree = ""; }; 32 | 0B1847102BA05F5D00C0BAA5 /* shokuh.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = shokuh.ttf; sourceTree = ""; }; 33 | 0BFFF9D92B809FC10088D24F /* perian_justify_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = perian_justify_example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 34 | 0BFFF9DC2B809FC10088D24F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 35 | 0BFFF9DE2B809FC10088D24F /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 36 | 0BFFF9E02B809FC10088D24F /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 37 | 0BFFF9E32B809FC10088D24F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 38 | 0BFFF9E52B809FC20088D24F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 39 | 0BFFF9E82B809FC20088D24F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 40 | 0BFFF9EA2B809FC20088D24F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 41 | 0BFFF9F42B80E5350088D24F /* UILabelExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UILabelExtensions.swift; sourceTree = ""; }; 42 | /* End PBXFileReference section */ 43 | 44 | /* Begin PBXFrameworksBuildPhase section */ 45 | 0BFFF9D62B809FC10088D24F /* Frameworks */ = { 46 | isa = PBXFrameworksBuildPhase; 47 | buildActionMask = 2147483647; 48 | files = ( 49 | 0B71E4CE2BA3580800CA53FA /* PersianJustify in Frameworks */, 50 | ); 51 | runOnlyForDeploymentPostprocessing = 0; 52 | }; 53 | /* End PBXFrameworksBuildPhase section */ 54 | 55 | /* Begin PBXGroup section */ 56 | 0B18470A2BA05F4D00C0BAA5 /* Fonts */ = { 57 | isa = PBXGroup; 58 | children = ( 59 | 0B18470F2BA05F5D00C0BAA5 /* dastnevis.otf */, 60 | 0B18470D2BA05F5D00C0BAA5 /* khodkar.ttf */, 61 | 0B18470C2BA05F5D00C0BAA5 /* paeez.ttf */, 62 | 0B18470E2BA05F5D00C0BAA5 /* shahgoosh.ttf */, 63 | 0B1847102BA05F5D00C0BAA5 /* shokuh.ttf */, 64 | 0B18470B2BA05F5D00C0BAA5 /* Yekan Boom.ttf */, 65 | ); 66 | path = Fonts; 67 | sourceTree = ""; 68 | }; 69 | 0BFFF9D02B809FC10088D24F = { 70 | isa = PBXGroup; 71 | children = ( 72 | 0BFFF9DB2B809FC10088D24F /* perian_justify_example */, 73 | 0BFFF9DA2B809FC10088D24F /* Products */, 74 | ); 75 | sourceTree = ""; 76 | }; 77 | 0BFFF9DA2B809FC10088D24F /* Products */ = { 78 | isa = PBXGroup; 79 | children = ( 80 | 0BFFF9D92B809FC10088D24F /* perian_justify_example.app */, 81 | ); 82 | name = Products; 83 | sourceTree = ""; 84 | }; 85 | 0BFFF9DB2B809FC10088D24F /* perian_justify_example */ = { 86 | isa = PBXGroup; 87 | children = ( 88 | 0B18470A2BA05F4D00C0BAA5 /* Fonts */, 89 | 0BFFF9F32B80E5250088D24F /* Helpers */, 90 | 0BFFF9DC2B809FC10088D24F /* AppDelegate.swift */, 91 | 0BFFF9DE2B809FC10088D24F /* SceneDelegate.swift */, 92 | 0BFFF9E02B809FC10088D24F /* ViewController.swift */, 93 | 0BFFF9E22B809FC10088D24F /* Main.storyboard */, 94 | 0BFFF9E52B809FC20088D24F /* Assets.xcassets */, 95 | 0BFFF9E72B809FC20088D24F /* LaunchScreen.storyboard */, 96 | 0BFFF9EA2B809FC20088D24F /* Info.plist */, 97 | ); 98 | path = perian_justify_example; 99 | sourceTree = ""; 100 | }; 101 | 0BFFF9F32B80E5250088D24F /* Helpers */ = { 102 | isa = PBXGroup; 103 | children = ( 104 | 0BFFF9F42B80E5350088D24F /* UILabelExtensions.swift */, 105 | ); 106 | path = Helpers; 107 | sourceTree = ""; 108 | }; 109 | /* End PBXGroup section */ 110 | 111 | /* Begin PBXNativeTarget section */ 112 | 0BFFF9D82B809FC10088D24F /* perian_justify_example */ = { 113 | isa = PBXNativeTarget; 114 | buildConfigurationList = 0BFFF9ED2B809FC20088D24F /* Build configuration list for PBXNativeTarget "perian_justify_example" */; 115 | buildPhases = ( 116 | 0BFFF9D52B809FC10088D24F /* Sources */, 117 | 0BFFF9D62B809FC10088D24F /* Frameworks */, 118 | 0BFFF9D72B809FC10088D24F /* Resources */, 119 | ); 120 | buildRules = ( 121 | ); 122 | dependencies = ( 123 | ); 124 | name = perian_justify_example; 125 | packageProductDependencies = ( 126 | 0B71E4CD2BA3580800CA53FA /* PersianJustify */, 127 | ); 128 | productName = perian_justify; 129 | productReference = 0BFFF9D92B809FC10088D24F /* perian_justify_example.app */; 130 | productType = "com.apple.product-type.application"; 131 | }; 132 | /* End PBXNativeTarget section */ 133 | 134 | /* Begin PBXProject section */ 135 | 0BFFF9D12B809FC10088D24F /* Project object */ = { 136 | isa = PBXProject; 137 | attributes = { 138 | BuildIndependentTargetsInParallel = 1; 139 | LastSwiftUpdateCheck = 1520; 140 | LastUpgradeCheck = 1520; 141 | TargetAttributes = { 142 | 0BFFF9D82B809FC10088D24F = { 143 | CreatedOnToolsVersion = 15.2; 144 | }; 145 | }; 146 | }; 147 | buildConfigurationList = 0BFFF9D42B809FC10088D24F /* Build configuration list for PBXProject "perian_justify_example" */; 148 | compatibilityVersion = "Xcode 14.0"; 149 | developmentRegion = en; 150 | hasScannedForEncodings = 0; 151 | knownRegions = ( 152 | en, 153 | Base, 154 | ); 155 | mainGroup = 0BFFF9D02B809FC10088D24F; 156 | packageReferences = ( 157 | 0B71E4CC2BA3580800CA53FA /* XCRemoteSwiftPackageReference "PersianJustify" */, 158 | ); 159 | productRefGroup = 0BFFF9DA2B809FC10088D24F /* Products */; 160 | projectDirPath = ""; 161 | projectRoot = ""; 162 | targets = ( 163 | 0BFFF9D82B809FC10088D24F /* perian_justify_example */, 164 | ); 165 | }; 166 | /* End PBXProject section */ 167 | 168 | /* Begin PBXResourcesBuildPhase section */ 169 | 0BFFF9D72B809FC10088D24F /* Resources */ = { 170 | isa = PBXResourcesBuildPhase; 171 | buildActionMask = 2147483647; 172 | files = ( 173 | 0BFFF9E92B809FC20088D24F /* LaunchScreen.storyboard in Resources */, 174 | 0B1847142BA05F5D00C0BAA5 /* shahgoosh.ttf in Resources */, 175 | 0B1847122BA05F5D00C0BAA5 /* paeez.ttf in Resources */, 176 | 0BFFF9E62B809FC20088D24F /* Assets.xcassets in Resources */, 177 | 0B1847132BA05F5D00C0BAA5 /* khodkar.ttf in Resources */, 178 | 0B1847152BA05F5D00C0BAA5 /* dastnevis.otf in Resources */, 179 | 0BFFF9E42B809FC10088D24F /* Main.storyboard in Resources */, 180 | 0B1847112BA05F5D00C0BAA5 /* Yekan Boom.ttf in Resources */, 181 | 0B1847162BA05F5D00C0BAA5 /* shokuh.ttf in Resources */, 182 | ); 183 | runOnlyForDeploymentPostprocessing = 0; 184 | }; 185 | /* End PBXResourcesBuildPhase section */ 186 | 187 | /* Begin PBXSourcesBuildPhase section */ 188 | 0BFFF9D52B809FC10088D24F /* Sources */ = { 189 | isa = PBXSourcesBuildPhase; 190 | buildActionMask = 2147483647; 191 | files = ( 192 | 0BFFF9E12B809FC10088D24F /* ViewController.swift in Sources */, 193 | 0BFFF9DD2B809FC10088D24F /* AppDelegate.swift in Sources */, 194 | 0BFFF9F52B80E5350088D24F /* UILabelExtensions.swift in Sources */, 195 | 0BFFF9DF2B809FC10088D24F /* SceneDelegate.swift in Sources */, 196 | ); 197 | runOnlyForDeploymentPostprocessing = 0; 198 | }; 199 | /* End PBXSourcesBuildPhase section */ 200 | 201 | /* Begin PBXVariantGroup section */ 202 | 0BFFF9E22B809FC10088D24F /* Main.storyboard */ = { 203 | isa = PBXVariantGroup; 204 | children = ( 205 | 0BFFF9E32B809FC10088D24F /* Base */, 206 | ); 207 | name = Main.storyboard; 208 | sourceTree = ""; 209 | }; 210 | 0BFFF9E72B809FC20088D24F /* LaunchScreen.storyboard */ = { 211 | isa = PBXVariantGroup; 212 | children = ( 213 | 0BFFF9E82B809FC20088D24F /* Base */, 214 | ); 215 | name = LaunchScreen.storyboard; 216 | sourceTree = ""; 217 | }; 218 | /* End PBXVariantGroup section */ 219 | 220 | /* Begin XCBuildConfiguration section */ 221 | 0BFFF9EB2B809FC20088D24F /* Debug */ = { 222 | isa = XCBuildConfiguration; 223 | buildSettings = { 224 | ALWAYS_SEARCH_USER_PATHS = NO; 225 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 226 | CLANG_ANALYZER_NONNULL = YES; 227 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 228 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 229 | CLANG_ENABLE_MODULES = YES; 230 | CLANG_ENABLE_OBJC_ARC = YES; 231 | CLANG_ENABLE_OBJC_WEAK = YES; 232 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 233 | CLANG_WARN_BOOL_CONVERSION = YES; 234 | CLANG_WARN_COMMA = YES; 235 | CLANG_WARN_CONSTANT_CONVERSION = YES; 236 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 237 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 238 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 239 | CLANG_WARN_EMPTY_BODY = YES; 240 | CLANG_WARN_ENUM_CONVERSION = YES; 241 | CLANG_WARN_INFINITE_RECURSION = YES; 242 | CLANG_WARN_INT_CONVERSION = YES; 243 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 244 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 245 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 246 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 247 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 248 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 249 | CLANG_WARN_STRICT_PROTOTYPES = YES; 250 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 251 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 252 | CLANG_WARN_UNREACHABLE_CODE = YES; 253 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 254 | COPY_PHASE_STRIP = NO; 255 | DEBUG_INFORMATION_FORMAT = dwarf; 256 | ENABLE_STRICT_OBJC_MSGSEND = YES; 257 | ENABLE_TESTABILITY = YES; 258 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 259 | GCC_C_LANGUAGE_STANDARD = gnu17; 260 | GCC_DYNAMIC_NO_PIC = NO; 261 | GCC_NO_COMMON_BLOCKS = YES; 262 | GCC_OPTIMIZATION_LEVEL = 0; 263 | GCC_PREPROCESSOR_DEFINITIONS = ( 264 | "DEBUG=1", 265 | "$(inherited)", 266 | ); 267 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 268 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 269 | GCC_WARN_UNDECLARED_SELECTOR = YES; 270 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 271 | GCC_WARN_UNUSED_FUNCTION = YES; 272 | GCC_WARN_UNUSED_VARIABLE = YES; 273 | IPHONEOS_DEPLOYMENT_TARGET = 17.2; 274 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 275 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 276 | MTL_FAST_MATH = YES; 277 | ONLY_ACTIVE_ARCH = YES; 278 | SDKROOT = iphoneos; 279 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; 280 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 281 | }; 282 | name = Debug; 283 | }; 284 | 0BFFF9EC2B809FC20088D24F /* Release */ = { 285 | isa = XCBuildConfiguration; 286 | buildSettings = { 287 | ALWAYS_SEARCH_USER_PATHS = NO; 288 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 289 | CLANG_ANALYZER_NONNULL = YES; 290 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 291 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 292 | CLANG_ENABLE_MODULES = YES; 293 | CLANG_ENABLE_OBJC_ARC = YES; 294 | CLANG_ENABLE_OBJC_WEAK = YES; 295 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 296 | CLANG_WARN_BOOL_CONVERSION = YES; 297 | CLANG_WARN_COMMA = YES; 298 | CLANG_WARN_CONSTANT_CONVERSION = YES; 299 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 300 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 301 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 302 | CLANG_WARN_EMPTY_BODY = YES; 303 | CLANG_WARN_ENUM_CONVERSION = YES; 304 | CLANG_WARN_INFINITE_RECURSION = YES; 305 | CLANG_WARN_INT_CONVERSION = YES; 306 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 307 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 308 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 309 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 310 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 311 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 312 | CLANG_WARN_STRICT_PROTOTYPES = YES; 313 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 314 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 315 | CLANG_WARN_UNREACHABLE_CODE = YES; 316 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 317 | COPY_PHASE_STRIP = NO; 318 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 319 | ENABLE_NS_ASSERTIONS = NO; 320 | ENABLE_STRICT_OBJC_MSGSEND = YES; 321 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 322 | GCC_C_LANGUAGE_STANDARD = gnu17; 323 | GCC_NO_COMMON_BLOCKS = YES; 324 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 325 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 326 | GCC_WARN_UNDECLARED_SELECTOR = YES; 327 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 328 | GCC_WARN_UNUSED_FUNCTION = YES; 329 | GCC_WARN_UNUSED_VARIABLE = YES; 330 | IPHONEOS_DEPLOYMENT_TARGET = 17.2; 331 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 332 | MTL_ENABLE_DEBUG_INFO = NO; 333 | MTL_FAST_MATH = YES; 334 | SDKROOT = iphoneos; 335 | SWIFT_COMPILATION_MODE = wholemodule; 336 | VALIDATE_PRODUCT = YES; 337 | }; 338 | name = Release; 339 | }; 340 | 0BFFF9EE2B809FC20088D24F /* Debug */ = { 341 | isa = XCBuildConfiguration; 342 | buildSettings = { 343 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 344 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 345 | CODE_SIGN_STYLE = Automatic; 346 | CURRENT_PROJECT_VERSION = 1; 347 | GENERATE_INFOPLIST_FILE = YES; 348 | INFOPLIST_FILE = perian_justify_example/Info.plist; 349 | INFOPLIST_KEY_CFBundleDisplayName = PersianJustifyExample; 350 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 351 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; 352 | INFOPLIST_KEY_UIMainStoryboardFile = Main; 353 | INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait"; 354 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; 355 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 356 | LD_RUNPATH_SEARCH_PATHS = ( 357 | "$(inherited)", 358 | "@executable_path/Frameworks", 359 | ); 360 | MARKETING_VERSION = 1.0.0; 361 | PRODUCT_BUNDLE_IDENTIFIER = "com.ebcom.perian-justify-exmaple"; 362 | PRODUCT_NAME = "$(TARGET_NAME)"; 363 | SWIFT_EMIT_LOC_STRINGS = YES; 364 | SWIFT_VERSION = 5.0; 365 | TARGETED_DEVICE_FAMILY = "1,2"; 366 | }; 367 | name = Debug; 368 | }; 369 | 0BFFF9EF2B809FC20088D24F /* Release */ = { 370 | isa = XCBuildConfiguration; 371 | buildSettings = { 372 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 373 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 374 | CODE_SIGN_STYLE = Automatic; 375 | CURRENT_PROJECT_VERSION = 1; 376 | GENERATE_INFOPLIST_FILE = YES; 377 | INFOPLIST_FILE = perian_justify_example/Info.plist; 378 | INFOPLIST_KEY_CFBundleDisplayName = PersianJustifyExample; 379 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 380 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; 381 | INFOPLIST_KEY_UIMainStoryboardFile = Main; 382 | INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait"; 383 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; 384 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 385 | LD_RUNPATH_SEARCH_PATHS = ( 386 | "$(inherited)", 387 | "@executable_path/Frameworks", 388 | ); 389 | MARKETING_VERSION = 1.0.0; 390 | PRODUCT_BUNDLE_IDENTIFIER = "com.ebcom.perian-justify-exmaple"; 391 | PRODUCT_NAME = "$(TARGET_NAME)"; 392 | SWIFT_EMIT_LOC_STRINGS = YES; 393 | SWIFT_VERSION = 5.0; 394 | TARGETED_DEVICE_FAMILY = "1,2"; 395 | }; 396 | name = Release; 397 | }; 398 | /* End XCBuildConfiguration section */ 399 | 400 | /* Begin XCConfigurationList section */ 401 | 0BFFF9D42B809FC10088D24F /* Build configuration list for PBXProject "perian_justify_example" */ = { 402 | isa = XCConfigurationList; 403 | buildConfigurations = ( 404 | 0BFFF9EB2B809FC20088D24F /* Debug */, 405 | 0BFFF9EC2B809FC20088D24F /* Release */, 406 | ); 407 | defaultConfigurationIsVisible = 0; 408 | defaultConfigurationName = Release; 409 | }; 410 | 0BFFF9ED2B809FC20088D24F /* Build configuration list for PBXNativeTarget "perian_justify_example" */ = { 411 | isa = XCConfigurationList; 412 | buildConfigurations = ( 413 | 0BFFF9EE2B809FC20088D24F /* Debug */, 414 | 0BFFF9EF2B809FC20088D24F /* Release */, 415 | ); 416 | defaultConfigurationIsVisible = 0; 417 | defaultConfigurationName = Release; 418 | }; 419 | /* End XCConfigurationList section */ 420 | 421 | /* Begin XCRemoteSwiftPackageReference section */ 422 | 0B71E4CC2BA3580800CA53FA /* XCRemoteSwiftPackageReference "PersianJustify" */ = { 423 | isa = XCRemoteSwiftPackageReference; 424 | repositoryURL = "https://github.com/HappyIosDeveloper/PersianJustify"; 425 | requirement = { 426 | kind = upToNextMajorVersion; 427 | minimumVersion = 0.3.0; 428 | }; 429 | }; 430 | /* End XCRemoteSwiftPackageReference section */ 431 | 432 | /* Begin XCSwiftPackageProductDependency section */ 433 | 0B71E4CD2BA3580800CA53FA /* PersianJustify */ = { 434 | isa = XCSwiftPackageProductDependency; 435 | package = 0B71E4CC2BA3580800CA53FA /* XCRemoteSwiftPackageReference "PersianJustify" */; 436 | productName = PersianJustify; 437 | }; 438 | /* End XCSwiftPackageProductDependency section */ 439 | }; 440 | rootObject = 0BFFF9D12B809FC10088D24F /* Project object */; 441 | } 442 | --------------------------------------------------------------------------------