Skip to content

Commit 42b9a8b

Browse files
committed
Initial commit
0 parents  commit 42b9a8b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+312559
-0
lines changed

.github/workflows/jazzy.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
name: Jazzy Docs
2+
3+
on:
4+
release:
5+
types: [published]
6+
7+
jobs:
8+
publish_docs:
9+
name: Publish Jazzy Docs
10+
runs-on: [macos-latest]
11+
steps:
12+
- uses: actions/checkout@v2
13+
- name: Copy Images
14+
run: mkdir -p .docs/img && cp img/* .docs/img/
15+
- name: Publish Jazzy Docs
16+
uses: steven0351/publish-jazzy-docs@v1
17+
with:
18+
personal_access_token: ${{ secrets.ACCESS_TOKEN }}
19+
config: .jazzy.yaml

.github/workflows/swiftlint.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
name: SwiftLint
2+
on:
3+
push:
4+
branches:
5+
- master
6+
pull_request:
7+
paths:
8+
- '.github/workflows/swiftlint.yml'
9+
- '.swiftlint.yml'
10+
- '**/*.swift'
11+
jobs:
12+
swiftlint:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: actions/checkout@v2
16+
- name: SwiftLint
17+
uses: norio-nomura/action-swiftlint@3.1.0
18+
with:
19+
args: --strict

.github/workflows/tests.yml

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
name: Tests
2+
on:
3+
push:
4+
branches:
5+
- master
6+
pull_request:
7+
branches:
8+
- master
9+
jobs:
10+
macOS:
11+
name: macOS
12+
runs-on: macos-latest
13+
steps:
14+
- uses: actions/checkout@v2
15+
- name: Version
16+
run: swift --version
17+
- name: Build
18+
run: swift build
19+
- name: Test
20+
run: make test
21+
linux:
22+
name: Linux
23+
runs-on: ubuntu-latest
24+
steps:
25+
- uses: actions/checkout@v2
26+
- name: Setup
27+
run: |
28+
export API_URL="https://swiftenv-api.fuller.li/versions?snapshot=false&platform=ubuntu18.04"
29+
export SWIFT_VERSION="$(curl -H 'Accept: text/plain' "$API_URL" | tail -n1)"
30+
31+
git clone --depth 1 https://github.com/kylef/swiftenv.git ~/.swiftenv
32+
export SWIFTENV_ROOT="$HOME/.swiftenv"
33+
export PATH="$SWIFTENV_ROOT/bin:$SWIFTENV_ROOT/shims:$PATH"
34+
swiftenv install -s
35+
36+
echo "::set-env name=SWIFT_VERSION::$SWIFT_VERSION"
37+
echo "::add-path::$SWIFTENV_ROOT/shims"
38+
- name: Version
39+
run: |
40+
swift --version
41+
- name: Build
42+
run: |
43+
swift build
44+
- name: Test
45+
run: |
46+
make test

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.DS_Store
2+
/.build
3+
/.swiftpm
4+
/.docs
5+
/Packages
6+
/*.xcodeproj
7+
benchmark.txt

.jazzy.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
output: .docs
2+
swift_build_tool: spm
3+
author: John Mueller
4+
author_url: https://john-mueller.github.io/
5+
module: Hyphenation
6+
module_version: v0.1.0
7+
copyright: © 2020 John Mueller
8+
github_url: https://github.com/john-mueller/Hyphenation/
9+
min_acl: public
10+
theme: fullwidth

.swiftlint.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
opt_in_rules:
2+
- explicit_top_level_acl
3+
4+
disabled_rules:
5+
- trailing_comma
6+
7+
excluded:
8+
- .build
9+
- Tests/*/XCTestManifests.swift
10+
- Tests/LinuxMain.swift

LICENSE.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
© 2020 John Mueller
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.

Makefile

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
export SHELL := /bin/bash
2+
3+
.PHONY: test bench test-correctness test-performance test-thread-safety generate-linuxmain publish
4+
5+
test: test-correctness test-thread-safety
6+
7+
bench: test-performance
8+
9+
test-correctness:
10+
HYPHENATION_TEST_TYPE=hyphenation swift test -c debug
11+
12+
test-performance:
13+
rm benchmark.txt
14+
HYPHENATION_TEST_TYPE=performance swift test -c release > >(tee -a benchmark.txt) 2> >(tee -a benchmark.txt >&2)
15+
grep -oE 'test.+?average: [0-9]+\.[0-9]+' benchmark.txt | sed -E "s/]?'.+av/ av/g"
16+
17+
test-thread-safety:
18+
HYPHENATION_TEST_TYPE=thread-safety swift test -c debug --sanitize=thread
19+
20+
generate-linuxmain:
21+
HYPHENATION_TEST_TYPE=hyphenation swift test --generate-linuxmain
22+
HYPHENATION_TEST_TYPE=preformance swift test --generate-linuxmain
23+
HYPHENATION_TEST_TYPE=thread-safety swift test --generate-linuxmain
24+
25+
publish: generate-linuxmain test
26+
rm -rf .docs
27+
jazzy
28+
cp img/* .docs/img/
29+
swiftlint --strict

Package.swift

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// swift-tools-version:5.1
2+
3+
// Hyphenation
4+
// © 2020 John Mueller
5+
// MIT license, see LICENSE.md for details
6+
7+
// swiftlint:disable explicit_top_level_acl
8+
9+
import PackageDescription
10+
11+
let package = Package(
12+
name: "Hyphenation",
13+
products: [
14+
.library(name: "Hyphenation", targets: ["Hyphenation"]),
15+
],
16+
targets: [
17+
.target(name: "Hyphenation"),
18+
.testTarget(name: "HyphenationTests", dependencies: ["Hyphenation"]),
19+
.testTarget(name: "PerformanceTests", dependencies: ["Hyphenation"]),
20+
.testTarget(name: "ThreadSafetyTests", dependencies: ["Hyphenation"]),
21+
]
22+
)
23+
24+
import class Foundation.ProcessInfo
25+
26+
// conditional application of test targets based on environment variable
27+
switch ProcessInfo.processInfo.environment["HYPHENATION_TEST_TYPE"]?.lowercased() {
28+
case "hyphenation":
29+
package.targets.removeAll(where: { $0.type == .test && $0.name != "HyphenationTests" })
30+
case "performance":
31+
package.targets.removeAll(where: { $0.type == .test && $0.name != "HyphenationTests" })
32+
package.targets.first(where: { $0.type == .test })?.path = "Tests/PerformanceTests"
33+
case "thread-safety":
34+
package.targets.removeAll(where: { $0.type == .test && $0.name != "HyphenationTests" })
35+
package.targets.first(where: { $0.type == .test })?.path = "Tests/ThreadSafetyTests"
36+
default:
37+
break
38+
}

README.md

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
# Hyphenation
2+
3+
Efficient and flexible automatic hyphenation using the Knuth-Liang algorithm.
4+
5+
See full documentation at [https://john-mueller.github.io/Hyphenation](https://john-mueller.github.io/Hyphenation).
6+
7+
## Table of Contents
8+
9+
- [Introduction](#introduction)
10+
- [Installation](#installation)
11+
- [Usage](#usage)
12+
- [Exceptions](#exceptions)
13+
- [Custom Patterns](#custom-patterns)
14+
- [Thread Safety](#thread-safety)
15+
- [HTML/Code](#htmlcode)
16+
- [Contributing](#contributing)
17+
- [License](#license)
18+
- [Credits](#credits)
19+
20+
## Introduction
21+
22+
The primary purpose of this library is to automatically insert soft hyphens into text to improve its layout flow. Consider the following example from [Butterick's Practical Typography](https://practicaltypography.com/justified-text.html):
23+
24+
![Text without hyphenation](img/no-hyphenation.png)
25+
26+
As line length decreases, justified text often displays large gaps between words, while left- or right-aligned text displays increasingly uneven margins. By inserting soft hyphens into words (which are only displayed when they fall at the end of a line), the text can be split more naturally across multiple lines, improving the text's aesthetic appearance:
27+
28+
![Text with hyphenation](img/with-hyphenation.png)
29+
30+
This proves useful in HTML (since the `hyphens` CSS property is [not universally supported](https://caniuse.com/#search=hyphen)), and will even work in UIKit and SwiftUI text components.
31+
32+
## Installation
33+
34+
Hyphenation is installed via the [Swift Package Manager](https://swift.org/package-manager/). First, add it to your dependencies:
35+
36+
```swift
37+
let package = Package(
38+
...
39+
dependencies: [
40+
.package(url: "https://github.com/john-mueller/Hyphenation.git", from: "0.1.0")
41+
],
42+
...
43+
)
44+
```
45+
46+
Then import the Hyphenation module wherever it is needed:
47+
48+
```swift
49+
import Hyphenation
50+
```
51+
52+
## Usage
53+
54+
Basic usage just requires creating a `Hyphenator` instance and calling its `hyphenate(text:)` method:
55+
56+
```swift
57+
let hyphenator = Hyphenator()
58+
hyphenator.separator = "-"
59+
60+
let text = "This algorithm identifies likely hyphenation points."
61+
print(hyphenator.hyphenate(text: text))
62+
// This al-go-rithm iden-ti-fies like-ly hy-phen-ation points.
63+
```
64+
65+
You can also remove the `separator` character from a string by calling `unhyphenate(text:)`:
66+
67+
```swift
68+
let hyphenatedText = "This al-go-rithm iden-ti-fies like-ly hy-phen-ation points."
69+
print(hyphenator.unhyphenate(text: hyphenatedText))
70+
// This algorithm identifies likely hyphenation points.
71+
```
72+
73+
Note that if the original string contained the `separator` character, the `unhyphenate(text:)` method will remove those instances as well, so hyphenating and unhyphenating a string may not recover the original string.
74+
75+
### Exceptions
76+
77+
The algorithm is designed to prioritize the prevention of incorrect hyphenations over finding every correct hyphenation—missing a single hyphenation rarely effects text flow meaningfully, but bad hyphenation can be rather noticable. Because the patterns were derived from a English dictionary, it can make good guesses about many words that do not appear in a dictionary.
78+
79+
```swift
80+
let hyphenator = Hyphenator()
81+
hyphenator.separator = "-"
82+
83+
print(hyphenator.hyphenate(text: "swiftlang supercalifragilisticexpialidocious"))
84+
// swift-lang su-per-cal-ifrag-ilis-tic-ex-pi-ali-do-cious
85+
```
86+
87+
Nevertheless, the algorithm may occasionally produce unexpected results for brand names or other unusual words. In this case, you may manually specify a desired hyphenation using exceptions.
88+
89+
```swift
90+
print(hyphenator.hyphenate(text: "Microsoft sesquipedalian"))
91+
// Mi-crosoft sesquipedalian
92+
93+
hyphenator.addCustomExceptions(["Micro-soft", "ses-qui-pe-da-li-an"])
94+
95+
print(hyphenator.hyphenate(text: "Microsoft sesquipedalian"))
96+
// Micro-soft ses-qui-pe-da-li-an
97+
```
98+
99+
To remove custom exceptions, use the following methods:
100+
101+
```swift
102+
hyphenator.removeCustomExceptions(["sesquipedalian"])
103+
hyphenator.removeAllCustomExceptions()
104+
```
105+
106+
### Customizable Properties
107+
108+
There are several properties you can modify to adjust how words are hyphenated.
109+
110+
The `separator` property sets the character inserted at hyphenation points. By default, this is U+00AD (soft hyphen).
111+
112+
The `minLength`, `minLeading`, and `minTrailing` properties adjust where the separator character may be inserted in a word.
113+
114+
- Words shorter than `minLength` will not be hyphenated.
115+
- Hyphenation will not occur within the first `minLeading` characters.
116+
- Hyphenation will not occur within the last `minTrailing` characters.
117+
118+
### Custom Patterns
119+
120+
This library includes American English hyphenation patterns by default, but you can easily initialize a `Hyphenator` instance using patterns for many other languages. The patterns can be passed via `String` or `URL`:
121+
122+
```swift
123+
let hyphenator1 = Hyphenator(patterns: patternsString, exceptions: exceptionsString)
124+
let hyphenator2 = Hyphenator(patternFile: patternsURL, exceptions: exceptionsURL)
125+
```
126+
127+
Patterns for a wide variety of languages can be found in the [TeX hyphenation repo](https://github.com/hyphenation/tex-hyphen/tree/master/hyph-utf8/tex/generic/hyph-utf8/patterns/txt). *Please check the license under which each set of patterns is released* at the [TeX hyphenation patterns website](http://www.hyphenation.org/tex#languages). This page also lists the proper `minLeading` and `minTrailing` for each language.
128+
129+
When initializing a new `Hyphenator` instance, it is assumed that patterns are separated by newlines or whitespace.
130+
131+
### Thread Safety
132+
133+
The Hyphenator class is thread-safe, and can be used to hyphenate on multiple threads simultaneously (although the performance benefits over using two instances are negligible).
134+
135+
The `copy()` method provides an efficient way to create a new `Hyphenator` instance with the same properties and patterns as an existing instance.
136+
137+
### HTML/Code
138+
139+
You should not apply the `hyphenate(text:)` method directly to strings containing HTML or code, as the code elements may be erroneously hyphenated. A safer approach is to use another tool capable of identifying HTML or code elements and applying hyphenation only to plain text content within the markup.
140+
141+
See [HyphenationPublishPlugin](https://github.com/john-mueller/HyphenationPublishPlugin) for an example hyphenating HTML using [SwiftSoup](https://github.com/scinfu/SwiftSoup).
142+
143+
## Contributing
144+
145+
If you encounter an issue using Hyphenation, please let me know by filing an issue or submitting a pull request!
146+
147+
When filing an issue, please do your best to provide reproducable steps and an explanation of the expected behavior.
148+
149+
In the case of a pull request, please take note of the following steps:
150+
151+
1. `swiftlint` should produce no warnings when run in the project directory. This is checked by CI, but I also recommend linting locally if possible (instructions for installation in the [SwiftLint repo](https://github.com/realm/SwiftLint#installation)).
152+
2. If you have added or renamed test cases, run `make generate-linuxmain` in the project directory. This will ensure all tests are run on both macOS and Linux.
153+
3. Make sure `make test` results in no errors. This runs the tests in the `HyphenationTests` and `ThreadSafetyTests` targets.
154+
4. If changing any internal implementations, please run `make bench` both with and without your changes, to check for any speed regressions. This runs the tests in the `PerformanceTests` target.
155+
156+
## License
157+
158+
Hyphenation is provided under the MIT license (see [LICENSE.md](LICENSE.md)).
159+
160+
The English hyphenation patterns are provided under the [original custom license](https://github.com/hyphenation/tex-hyphen/blob/master/hyph-utf8/tex/generic/hyph-utf8/patterns/tex/hyph-en-us.tex), and are sourced from the [TeX hyphenation repo](https://github.com/hyphenation/tex-hyphen/tree/master/hyph-utf8/tex/generic/hyph-utf8/patterns/txt).
161+
162+
[The texts](Tests/PerformanceTests/TestHelpers/) used for performance testing are in the public domain, and are attributed in their headers.
163+
164+
The example paragraphs in the Introduction are from [Butterick's Practical Typography](https://practicaltypography.com), and are used with permission.
165+
166+
## Credits
167+
168+
This library was inspired by the pages on [justified text](https://practicaltypography.com/justified-text.html) and [optional hyphens](https://practicaltypography.com/optional-hyphens.html) in [Butterick's Practical Typography](https://practicaltypography.com) and the author's [implementation in Racket](https://github.com/mbutterick/hyphenate). It's worth a read!

0 commit comments

Comments
 (0)