Skip to content

Commit

Permalink
Initial import.
Browse files Browse the repository at this point in the history
  • Loading branch information
cocoatoucher committed Jan 5, 2022
1 parent ebb021d commit 8c892f2
Show file tree
Hide file tree
Showing 12 changed files with 537 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file added Docs/examples.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Docs/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
27 changes: 27 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// swift-tools-version:5.5
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
name: "StyledMarkdown",
platforms: [
.iOS(.v15), .macOS(.v12), .tvOS(.v15), .watchOS(.v8)
],
products: [
.library(
name: "StyledMarkdown",
targets: ["StyledMarkdown"]
),
],
targets: [
.target(
name: "StyledMarkdown",
path: "Sources"
),
.testTarget(
name: "StyledMarkdownTests",
dependencies: ["StyledMarkdown"]
),
]
)
67 changes: 67 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<p align="center">
<img src="Docs/logo.png" width="300" max-width="80%" alt="glide"/>
</p>

<p align="center">
iOS 15.0 / macOS 12.0 / tvOS 15.0 / watchOS 8.0
</p>

StyledMarkdown is a mini library that lets you define custom styles in code and use them in your localized markdown strings.

```
let normalStyle = Style { style in
style.font = .subheadline
style.foregroundColor = .red
}
let boldStyle = Style { style in
style.font = Font.italic(.system(size: 20))()
style.foregroundColor = .blue
}
let myStyleGroup = StyleGroup(
base: normalStyle,
[
"bold": boldStyle
]
)
Text(
"Hey ^[buddy](style: 'bold')",
styleGroup: myStyleGroup
)
```

A custom `Text` initializer with custom `AttributedStringKey`s are used to achieve this outcome.

***The idea of StyleGroup and named Styles comes directly from [`SwiftRichString` library by `Daniele Margutti` on GitHub](https://github.com/malcommac/SwiftRichString). Some of the code from there is also used in this package.***

### Limitation

Currently there is a bug with `.init(markdown:including:)` initialiser of `AttributedString`. This initializer ignores custom `AttributedStringKey`s used in this library while creating the final string. Due to this, right now it is not possible to style any markdown string, but only localized keys. Radar reported, a new initialiser will be added once that is fixed.

## Examples
<p align="center">
<img src="Docs/examples.png" width="400" max-width="80%" alt="glide devices"/>
</p>

## Supported modifiers

#### font(*SwiftUI.Font*)
#### foregroundColor(*Color*)
#### strikethrough(*Color*)
#### strikethroughStyle(*NSUnderlineStyle*)
#### underline(*Color*)
#### underlineStyle(*NSUnderlineStyle*)
#### kerning(*CGFloat*)
#### tracking(*CGFloat*)
#### baselineOffset(*CGFloat*)

### 🔗 Links

You can add links inside your strings using the custom `link` `AttributedStringKey`:
`^[styled link](link: {url: 'http://www.example.com', style: 'linkStyle'})`

### 🎆 Images (not supported)

It is currently not supported to include `Image` elements within `AttributedString`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//
// AttributeScopes.StyledMarkdownAttributes.swift
// StyledMarkdown
//
// Created by cocoatoucher on 2022-01-04.
//

import Foundation

extension AttributeScopes {
struct StyledMarkdownAttributes: AttributeScope {
let styleName: StyleNameAttribute
let linkWithStyleName: LinkWithStyleNameAttribute
let swiftUI: SwiftUIAttributes
}

var styledMarkdown: StyledMarkdownAttributes.Type { StyledMarkdownAttributes.self }
}

extension AttributeDynamicLookup {
subscript<T: AttributedStringKey>(
dynamicMember keyPath: KeyPath<AttributeScopes.StyledMarkdownAttributes,
T>
) -> T {
self[T.self]
}
}

enum StyleNameAttribute: CodableAttributedStringKey, MarkdownDecodableAttributedStringKey {
typealias Value = String

static var name = "style"
}

enum LinkWithStyleNameAttribute: CodableAttributedStringKey, MarkdownDecodableAttributedStringKey {
struct Value: Codable, Hashable {
let url: URL
let style: String
}

static var name = "link"
}
65 changes: 65 additions & 0 deletions Sources/StyledMarkdown/Style/Style.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
//
// Style.swift
// StyledMarkdown
//
// Created by cocoatoucher on 2022-01-04.
//

import SwiftUI

public class Style: StyleProtocol {

public var font: SwiftUI.Font?

public var foregroundColor: Color?

public var strikethroughColor: Color?

public var strikethroughStyle: NSUnderlineStyle?

public var underlineColor: Color?

public var underlineStyle: NSUnderlineStyle?

public var kerning: CGFloat?

public var tracking: CGFloat?

public var baselineOffset: CGFloat?

public init(_ handler: ((Style) -> Void)? = nil) {
handler?(self)
}

public var modifiers: [StyleModifier] {
var result: [StyleModifier] = []
if let font = font {
result.append(.font(font))
}
if let foregroundColor = foregroundColor {
result.append(.foregroundColor(foregroundColor))
}
if let strikethroughColor = strikethroughColor {
result.append(.strikethroughColor(strikethroughColor))
}
if let strikethroughStyle = strikethroughStyle {
result.append(.strikethroughStyle(strikethroughStyle))
}
if let underlineColor = underlineColor {
result.append(.underline(underlineColor))
}
if let underlineStyle = underlineStyle {
result.append(.underlineStyle(underlineStyle))
}
if let kerning = kerning {
result.append(.kerning(kerning))
}
if let tracking = tracking {
result.append(.tracking(tracking))
}
if let baselineOffset = baselineOffset {
result.append(.baselineOffset(baselineOffset))
}
return result
}
}
25 changes: 25 additions & 0 deletions Sources/StyledMarkdown/Style/StyleGroup.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// StyleGroup.swift
// StyledMarkdown
//
// Created by cocoatoucher on 2022-01-04.
//

import Foundation

public class StyleGroup: StyleProtocol {

public var modifiers: [StyleModifier] = []

public private(set) var styles: [String: StyleProtocol]

public var baseStyle: StyleProtocol?

public init(
base: StyleProtocol? = nil,
_ styles: [String: StyleProtocol] = [:]
) {
self.styles = styles
self.baseStyle = base
}
}
20 changes: 20 additions & 0 deletions Sources/StyledMarkdown/Style/StyleModifier.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// StyleModifier.swift
// StyledMarkdown
//
// Created by cocoatoucher on 2022-01-04.
//

import SwiftUI

public enum StyleModifier {
case font(SwiftUI.Font)
case foregroundColor(Color)
case strikethroughColor(Color)
case strikethroughStyle(NSUnderlineStyle)
case underline(Color)
case underlineStyle(NSUnderlineStyle)
case kerning(CGFloat)
case tracking(CGFloat)
case baselineOffset(CGFloat)
}
43 changes: 43 additions & 0 deletions Sources/StyledMarkdown/Style/StyleProtocol.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//
// StyleProtocol.swift
// StyledMarkdown
//
// Created by cocoatoucher on 2022-01-04.
//

import Foundation

public protocol StyleProtocol {
var modifiers: [StyleModifier] { get }

func add(to source: inout AttributedSubstring)
}

public extension StyleProtocol {

func add(to source: inout AttributedSubstring) {
for modifier in self.modifiers {
switch modifier {
case .font(let font):
source.font = font
case .foregroundColor(let color):
source.foregroundColor = color
case .strikethroughColor(let color):
source.strikethroughColor = .init(color)
case .strikethroughStyle(let style):
source.strikethroughStyle = style
case .underline(let color):
source.underlineColor = .init(color)
case .underlineStyle(let style):
source.underlineStyle = style
case .kerning(let kerning):
source.kern = kerning
case .tracking(let tracking):
source.tracking = tracking
case .baselineOffset(let baselineOffset):
source.baselineOffset = baselineOffset
}
}
}

}
Loading

0 comments on commit 8c892f2

Please sign in to comment.