|
| 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 | + */ |
0 commit comments