Skip to content

Commit 8216d69

Browse files
committed
#2: Implement Text demo
1 parent a26b979 commit 8216d69

File tree

3 files changed

+286
-1
lines changed

3 files changed

+286
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
/*:
2+
# Text
3+
4+
**Text** is a program which show various way of text showing.
5+
6+
([Original example that uses LibHaru](https://github.com/libharu/libharu/wiki/Examples#text_democ))
7+
*/
8+
import SwiftyHaru
9+
import func Foundation.tan
10+
/*:
11+
Firstly, we need to define some helper functions.
12+
*/
13+
let sampleTextShort = "ABCabc123"
14+
let sampleTextLong = "abcdefgABCDEFG123!#$%&+-@?"
15+
let sampleTextPhrase = "The quick brown fox jumps over the lazy dog."
16+
17+
func showStripePattern(in context: DrawingContext, x: Float, y: Float) {
18+
19+
context.strokeColor = #colorLiteral(red: 0, green: 0, blue: 0.5, alpha: 1)
20+
context.lineWidth = 1
21+
22+
for iy in stride(from: 0 as Float, to: 50, by: 3) {
23+
context.stroke(
24+
Path()
25+
.moving(toX: x, y: y + iy)
26+
.appendingLine(toX: x + context.textWidth(for: sampleTextShort), y: y + iy)
27+
)
28+
}
29+
30+
context.lineWidth = 2.5
31+
}
32+
33+
func showDescription(in context: DrawingContext, x: Float, y: Float, text: String) throws {
34+
let fontSize = context.fontSize
35+
let fillColor = context.fillColor
36+
37+
context.fillColor = .black
38+
context.textRenderingMode = .fill
39+
context.fontSize = 10
40+
try context.show(text: text, atX: x, y: y - 12)
41+
42+
context.fontSize = fontSize
43+
context.fillColor = fillColor
44+
}
45+
/*:
46+
Then we setup our document.
47+
*/
48+
let document = PDFDocument()
49+
let pageTitle = "Text Demo"
50+
51+
try document.setCompressionMode(to: .all)
52+
53+
try document.addPage() { context in
54+
55+
/*:
56+
Let's draw the grid for convenience.
57+
*/
58+
let horizontalLabels = sequence(first: 50, next: { $0 + 50 }).lazy.map(String.init)
59+
let verticalLabels = sequence(first: 10, next: { $0 + 10 }).lazy.map(String.init)
60+
61+
let labels = Grid.Labels(top: Grid.LabelParameters(sequence: "" + horizontalLabels,
62+
offset: Vector(dx: 0, dy: -6)),
63+
bottom: Grid.LabelParameters(sequence: "" + horizontalLabels,
64+
offset: Vector(dx: 0, dy: 6)),
65+
left: Grid.LabelParameters(sequence: "" + verticalLabels,
66+
frequency: 1,
67+
offset: Vector(dx: 6, dy: 0)))
68+
69+
let serifs = Grid.Serifs(top: .default,
70+
bottom: .default,
71+
left: Grid.SerifParameters(frequency: 1),
72+
right: nil)
73+
74+
let grid = Grid(width: context.page.width,
75+
height: context.page.height,
76+
labels: labels,
77+
serifs: serifs)
78+
try context.draw(grid, position: .zero)
79+
80+
/*:
81+
Let's print the title of the page:
82+
*/
83+
84+
context.fontSize = 24
85+
let textWidth = context.textWidth(for: pageTitle)
86+
try context.show(text: pageTitle, atX: (context.page.width - textWidth) / 2, y: context.page.height - 50)
87+
/*:
88+
We are now ready to place some text samples.
89+
First, let's play with font size.
90+
*/
91+
var textPosition = Point(x: 60, y: context.page.height - 60)
92+
93+
for fontSize in sequence(first: 8 as Float, next: { $0 * 1.5 }) {
94+
95+
if fontSize >= 60 {
96+
break
97+
}
98+
99+
context.fontSize = fontSize
100+
textPosition.y -= 5 + fontSize
101+
102+
// Measure the number of characters which included in the page
103+
let utf8Length = try context
104+
.measureText(sampleTextLong, width: context.page.width - 120, wordwrap: false).utf8Length
105+
106+
try context.show(text: String(sampleTextLong.utf8.prefix(utf8Length))!, atPosition: textPosition)
107+
108+
// Print the description
109+
textPosition.y -= 10
110+
context.fontSize = 8
111+
try context.show(text: String(format: "Fontsize=%.0f", fontSize), atPosition: textPosition)
112+
}
113+
/*:
114+
Then let's experiment with font color.
115+
*/
116+
context.fontSize = 8
117+
textPosition.y -= 30
118+
try context.show(text: "Font color", atPosition: textPosition)
119+
120+
context.fontSize = 18
121+
textPosition.y -= 20
122+
for (i, char) in sampleTextLong.enumerated() {
123+
let char = String(char)
124+
let red = Float(i) / Float(sampleTextLong.utf8.count)
125+
let green = 1 - red
126+
context.fillColor = Color(red: red, green: green, blue: 0)!
127+
try context.show(text: char, atPosition: textPosition)
128+
textPosition.x += context.textWidth(for: char)
129+
}
130+
131+
textPosition.x = 60
132+
textPosition.y -= 25
133+
for (i, char) in sampleTextLong.enumerated() {
134+
let char = String(char)
135+
let red = Float(i) / Float(sampleTextLong.utf8.count)
136+
let blue = 1 - red
137+
context.fillColor = Color(red: red, green: 0, blue: blue)!
138+
try context.show(text: char, atPosition: textPosition)
139+
textPosition.x += context.textWidth(for: char)
140+
}
141+
142+
textPosition.x = 60
143+
textPosition.y -= 25
144+
for (i, char) in sampleTextLong.enumerated() {
145+
let char = String(char)
146+
let blue = Float(i) / Float(sampleTextLong.utf8.count)
147+
let green = 1 - blue
148+
context.fillColor = Color(red: 0, green: green, blue: blue)!
149+
try context.show(text: char, atPosition: textPosition)
150+
textPosition.x += context.textWidth(for: char)
151+
}
152+
/*:
153+
Explore different font rendering modes:
154+
*/
155+
let ypos: Float = 450
156+
157+
context.fontSize = 32
158+
context.fillColor = #colorLiteral(red: 0.5, green: 0.5, blue: 0, alpha: 1)
159+
context.lineWidth
160+
161+
try showDescription(in: context, x: 60, y: ypos, text: "textRenderingMode = .fill")
162+
context.textRenderingMode = .fill
163+
try context.show(text: sampleTextShort, atX: 60, y: ypos)
164+
165+
try showDescription(in: context, x: 60, y: ypos - 50, text: "textRenderingMode = .stroke")
166+
context.textRenderingMode = .stroke
167+
try context.show(text: sampleTextShort, atX: 60, y: ypos - 50)
168+
169+
try showDescription(in: context, x: 60, y: ypos - 100, text: "textRenderingMode = .fillThenStroke")
170+
context.textRenderingMode = .fillThenStroke
171+
try context.show(text: sampleTextShort, atX: 60, y: ypos - 100)
172+
173+
try showDescription(in: context, x: 60, y: ypos - 150, text: "textRenderingMode = .fillClipping")
174+
try context.withNewGState {
175+
context.textRenderingMode = .fillClipping
176+
try context.show(text: sampleTextShort, atX: 60, y: ypos - 150)
177+
showStripePattern(in: context, x: 60, y: ypos - 150)
178+
}
179+
180+
try showDescription(in: context, x: 60, y: ypos - 200, text: "textRenderingMode = .strokeClipping")
181+
try context.withNewGState {
182+
context.textRenderingMode = .strokeClipping
183+
try context.show(text: sampleTextShort, atX: 60, y: ypos - 200)
184+
showStripePattern(in: context, x: 60, y: ypos - 200)
185+
}
186+
187+
try showDescription(in: context, x: 60, y: ypos - 250, text: "textRenderingMode = .fillStrokeClipping")
188+
try context.withNewGState {
189+
context.textRenderingMode = .fillStrokeClipping
190+
try context.show(text: sampleTextShort, atX: 60, y: ypos - 250)
191+
showStripePattern(in: context, x: 60, y: ypos - 250)
192+
}
193+
194+
/*:
195+
Reset text attributes and try to transform text in different ways:
196+
*/
197+
context.textRenderingMode = .fill
198+
context.fillColor = .black
199+
context.fontSize = 30
200+
201+
try showDescription(in: context, x: 320, y: ypos - 60, text: "Rotating text")
202+
let rotatedTextPosition = Point(x: 330, y: ypos - 60)
203+
let rotatedTextMatrix = AffineTransform(rotationAngle: .pi / 6) *
204+
AffineTransform(translationX: rotatedTextPosition.x, y: rotatedTextPosition.y)
205+
try context.show(text: sampleTextShort,
206+
atPosition: rotatedTextPosition,
207+
textMatrix: rotatedTextMatrix)
208+
209+
try showDescription(in: context, x: 320, y: ypos - 120, text: "Skewing text")
210+
let skewedTextPosition = Point(x: 330, y: ypos - 120)
211+
let skewedTextMatrix = AffineTransform(a: 1, b: tan(.pi / 18),
212+
c: tan(.pi / 9), d: 1,
213+
tx: skewedTextPosition.x, ty: skewedTextPosition.y)
214+
try context.show(text: sampleTextShort,
215+
atPosition: skewedTextPosition,
216+
textMatrix: skewedTextMatrix)
217+
218+
try showDescription(in: context, x: 320, y: ypos - 175, text: "Scaling text (X direction)")
219+
let scaledXTextPosition = Point(x: 320, y: ypos - 175)
220+
let scaledXTextMatrix = AffineTransform(scaleX: 1.5, y: 1) *
221+
AffineTransform(translationX: scaledXTextPosition.x, y: scaledXTextPosition.y)
222+
try context.show(text: sampleTextShort,
223+
atPosition: scaledXTextPosition,
224+
textMatrix: scaledXTextMatrix)
225+
226+
try showDescription(in: context, x: 320, y: ypos - 250, text: "Scaling text (Y direction)")
227+
let scaledYTextPosition = Point(x: 320, y: ypos - 250)
228+
let scaledYTextMatrix = AffineTransform(scaleX: 1, y: 2) *
229+
AffineTransform(translationX: scaledYTextPosition.x, y: scaledYTextPosition.y)
230+
try context.show(text: sampleTextShort,
231+
atPosition: scaledYTextPosition,
232+
textMatrix: scaledYTextMatrix)
233+
234+
/*:
235+
Alter character spacing and word spacing:
236+
*/
237+
238+
try showDescription(in: context, x: 60, y: 140, text: "char-spacing 0")
239+
try showDescription(in: context, x: 60, y: 100, text: "char-spacing 1.5")
240+
try showDescription(in: context, x: 60, y: 60, text: "char-spacing 1.5, word-spacing 2.5")
241+
242+
context.fontSize = 20
243+
context.fillColor = #colorLiteral(red: 0.1, green: 0.3, blue: 0.1, alpha: 1)
244+
245+
context.characterSpacing = 0
246+
try context.show(text: sampleTextPhrase, atX: 60, y: 140)
247+
248+
context.characterSpacing = 1.5
249+
try context.show(text: sampleTextPhrase, atX: 60, y: 100)
250+
251+
context.wordSpacing = 2.5
252+
try context.show(text: sampleTextPhrase, atX: 60, y: 60)
253+
}
254+
255+
/*:
256+
We need to save our document.
257+
*/
258+
document.display()
259+
/*:
260+
[Previous page](@previous) • **[Table of contents](Table%20of%20contents)** • [Next page](@next)
261+
*/

Demo/Demo.playground/contents.xcplayground

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2-
<playground version='6.0' target-platform='macos' display-mode='rendered'>
2+
<playground version='6.0' target-platform='macos' display-mode='raw'>
33
<pages>
44
<page name='Table of contents'/>
55
<page name='Fonts'/>
66
<page name='Lines'/>
7+
<page name='Text'/>
78
<page name='Arcs'/>
89
<page name='TrueType Fonts'/>
910
<page name='Grid Sheet'/>

Sources/SwiftyHaru/Graphics/Geometry.swift

+23
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,17 @@ public struct Point: Hashable {
100100
public static func +(lhs: Point, rhs: Vector) -> Point {
101101
return Point(x: lhs.x + rhs.dx, y: lhs.y + rhs.dy)
102102
}
103+
104+
/// Translates the `lhs` point by the specified `rhs` vector and stores the result in `lhs`.
105+
///
106+
/// - Parameters:
107+
/// - lhs: The point to translate.
108+
/// - rhs: The difference vector.
109+
@inlinable
110+
public static func +=(lhs: inout Point, rhs: Vector) {
111+
lhs.x += rhs.dx
112+
lhs.y += rhs.dy
113+
}
103114

104115
/// Translates the `lhs` point by negation of the specified `rhs` vector.
105116
///
@@ -112,6 +123,18 @@ public struct Point: Hashable {
112123
return Point(x: lhs.x - rhs.dx, y: lhs.y - rhs.dy)
113124
}
114125

126+
/// Translates the `lhs` point by negation of the specified `rhs` vector and stores the result
127+
/// in `lhs`.
128+
///
129+
/// - Parameters:
130+
/// - lhs: The point to translate.
131+
/// - rhs: The difference vector.
132+
@inlinable
133+
public static func -=(lhs: inout Point, rhs: Vector) {
134+
lhs.x -= rhs.dx
135+
lhs.y -= rhs.dy
136+
}
137+
115138
/// Returns the vector that needs to be added to `rhs` to get `lhs`.
116139
///
117140
/// - Parameters:

0 commit comments

Comments
 (0)