├── 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 |
8 |
9 |
[](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 |
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 |
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 |
--------------------------------------------------------------------------------