Skip to content

Commit e20030c

Browse files
committed
fix: enhance html parsing
1 parent cd67f79 commit e20030c

3 files changed

Lines changed: 356 additions & 191 deletions

File tree

Outspire.swiftpm/Features/SchoolArrangement/Utils/PDFGenerator.swift

Lines changed: 219 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -11,99 +11,96 @@ class PDFGenerator {
1111
let format = UIGraphicsPDFRendererFormat()
1212
format.documentInfo = pdfMetaData as [String: Any]
1313

14+
// Standard letter size
1415
let pageWidth = 8.5 * 72.0
1516
let pageHeight = 11 * 72.0
1617
let pageRect = CGRect(x: 0, y: 0, width: pageWidth, height: pageHeight)
1718

19+
// Margins
20+
let margin: CGFloat = 50.0
21+
let contentWidth = pageWidth - (margin * 2)
22+
1823
let renderer = UIGraphicsPDFRenderer(bounds: pageRect, format: format)
1924

2025
do {
2126
let data = try renderer.pdfData { (context) in
22-
context.beginPage()
23-
24-
// Title configuration
25-
let titleFont = UIFont.boldSystemFont(ofSize: 24.0)
26-
let titleAttributes: [NSAttributedString.Key: Any] = [
27-
.font: titleFont
28-
]
29-
30-
// Draw title
31-
let titleStringSize = title.size(withAttributes: titleAttributes)
32-
let titleRect = CGRect(x: 50, y: 50, width: pageRect.width - 100, height: titleStringSize.height)
33-
title.draw(in: titleRect, withAttributes: titleAttributes)
3427

35-
// Draw date
36-
let dateFont = UIFont.systemFont(ofSize: 14.0)
37-
let dateAttributes: [NSAttributedString.Key: Any] = [
38-
.font: dateFont,
39-
.foregroundColor: UIColor.darkGray
40-
]
41-
let dateRect = CGRect(x: 50, y: titleRect.maxY + 10, width: pageRect.width - 100, height: 20)
42-
date.draw(in: dateRect, withAttributes: dateAttributes)
28+
// First page - title and initial content
29+
context.beginPage()
30+
var currentY = drawHeader(
31+
title: title,
32+
date: date,
33+
rect: pageRect,
34+
margin: margin
35+
)
4336

44-
var yPos = dateRect.maxY + 30
37+
// Add horizontal rule
38+
currentY += 10
39+
let path = UIBezierPath()
40+
path.move(to: CGPoint(x: margin, y: currentY))
41+
path.addLine(to: CGPoint(x: pageWidth - margin, y: currentY))
42+
UIColor.lightGray.setStroke()
43+
path.lineWidth = 0.5
44+
path.stroke()
45+
currentY += 20
4546

46-
// Add content if not empty
47+
// Process content to plain text
4748
if !content.isEmpty {
48-
// Process HTML content to plain text
49-
let processedContent = content.replacingOccurrences(of: "<[^>]+>", with: "", options: .regularExpression)
50-
51-
let contentFont = UIFont.systemFont(ofSize: 12.0)
52-
let contentAttributes: [NSAttributedString.Key: Any] = [
53-
.font: contentFont
54-
]
55-
56-
let contentStringSize = processedContent.size(withAttributes: contentAttributes)
57-
let contentHeight = min(200, contentStringSize.height) // Limit content height
49+
currentY = drawContent(
50+
content: content,
51+
startY: currentY,
52+
pageRect: pageRect,
53+
margin: margin,
54+
context: context
55+
)
5856

59-
let contentRect = CGRect(x: 50, y: yPos, width: pageRect.width - 100, height: contentHeight)
60-
processedContent.draw(in: contentRect, withAttributes: contentAttributes)
61-
62-
yPos = contentRect.maxY + 30
57+
// Add space after content
58+
currentY += 20
6359
}
6460

65-
// Add images
66-
for (index, image) in images.enumerated() {
67-
// Start a new page if not enough space
68-
if yPos + 250 > pageRect.height {
69-
context.beginPage()
70-
yPos = 50 // Reset Y position for new page
71-
}
72-
73-
// Calculate image size while maintaining aspect ratio
74-
let maxWidth: CGFloat = pageRect.width - 100
75-
let maxHeight: CGFloat = 250
76-
77-
let aspectRatio = image.size.width / image.size.height
78-
let imageWidth: CGFloat
79-
let imageHeight: CGFloat
80-
81-
if aspectRatio > 1 { // Landscape
82-
imageWidth = min(maxWidth, image.size.width)
83-
imageHeight = imageWidth / aspectRatio
84-
} else { // Portrait
85-
imageHeight = min(maxHeight, image.size.height)
86-
imageWidth = imageHeight * aspectRatio
87-
}
61+
// Add images if available
62+
if !images.isEmpty {
63+
// Add a section title for images
64+
let imageSectionFont = UIFont.boldSystemFont(ofSize: 16.0)
65+
let imageSectionTitle = "Attachments (\(images.count) images)"
8866

89-
// Center the image
90-
let xPos = (pageRect.width - imageWidth) / 2
91-
92-
let imageRect = CGRect(x: xPos, y: yPos, width: imageWidth, height: imageHeight)
93-
image.draw(in: imageRect)
94-
95-
// Add image number text below the image
96-
let imageNumberFont = UIFont.systemFont(ofSize: 10.0)
97-
let imageNumberAttributes: [NSAttributedString.Key: Any] = [
98-
.font: imageNumberFont,
67+
let imageSectionAttributes: [NSAttributedString.Key: Any] = [
68+
.font: imageSectionFont,
9969
.foregroundColor: UIColor.darkGray
10070
]
10171

102-
let imageNumberText = "Image \(index + 1) of \(images.count)"
103-
let imageNumberRect = CGRect(x: 50, y: yPos + imageHeight + 5, width: pageRect.width - 100, height: 15)
104-
imageNumberText.draw(in: imageNumberRect, withAttributes: imageNumberAttributes)
72+
// Draw section title
73+
let sectionTitleRect = CGRect(x: margin, y: currentY, width: contentWidth, height: 20)
74+
imageSectionTitle.draw(in: sectionTitleRect, withAttributes: imageSectionAttributes)
75+
currentY += 25
76+
77+
// Check if we need a new page for images
78+
if currentY > pageHeight - 250 {
79+
context.beginPage()
80+
currentY = margin
81+
}
10582

106-
yPos = imageNumberRect.maxY + 30
83+
// Draw each image
84+
for (index, image) in images.enumerated() {
85+
// Check if we need a new page
86+
if currentY + 280 > pageHeight {
87+
context.beginPage()
88+
currentY = margin
89+
}
90+
91+
// Draw the image
92+
currentY = drawImage(
93+
image: image,
94+
index: index,
95+
total: images.count,
96+
startY: currentY,
97+
pageRect: pageRect,
98+
margin: margin
99+
)
100+
101+
// Add spacing between images
102+
currentY += 30
103+
}
107104
}
108105
}
109106

@@ -113,4 +110,153 @@ class PDFGenerator {
113110
return nil
114111
}
115112
}
113+
114+
// Helper method to draw the header
115+
private static func drawHeader(title: String, date: String, rect: CGRect, margin: CGFloat) -> CGFloat {
116+
var currentY = margin
117+
118+
// Title configuration
119+
let titleFont = UIFont.boldSystemFont(ofSize: 20.0)
120+
let titleAttributes: [NSAttributedString.Key: Any] = [
121+
.font: titleFont
122+
]
123+
124+
// Calculate title height based on available width
125+
let titleWidth = rect.width - (margin * 2)
126+
let titleSize = title.boundingRect(
127+
with: CGSize(width: titleWidth, height: 200),
128+
options: [.usesLineFragmentOrigin, .usesFontLeading],
129+
attributes: titleAttributes,
130+
context: nil
131+
).size
132+
133+
// Draw title
134+
let titleRect = CGRect(x: margin, y: currentY, width: titleWidth, height: titleSize.height)
135+
title.draw(in: titleRect, withAttributes: titleAttributes)
136+
currentY += titleSize.height + 10
137+
138+
// Draw date
139+
let dateFont = UIFont.systemFont(ofSize: 12.0)
140+
let dateAttributes: [NSAttributedString.Key: Any] = [
141+
.font: dateFont,
142+
.foregroundColor: UIColor.darkGray
143+
]
144+
145+
let dateRect = CGRect(x: margin, y: currentY, width: titleWidth, height: 20)
146+
date.draw(in: dateRect, withAttributes: dateAttributes)
147+
currentY += 20
148+
149+
return currentY
150+
}
151+
152+
// Helper method to draw content
153+
private static func drawContent(content: String, startY: CGFloat, pageRect: CGRect,
154+
margin: CGFloat, context: UIGraphicsPDFRendererContext) -> CGFloat {
155+
var currentY = startY
156+
let contentWidth = pageRect.width - (margin * 2)
157+
158+
// Process HTML to plain text
159+
let processedContent = content.replacingOccurrences(of: "<[^>]+>", with: "", options: .regularExpression)
160+
.trimmingCharacters(in: .whitespacesAndNewlines)
161+
162+
// Parse content into paragraphs
163+
let paragraphs = processedContent.components(separatedBy: "\n")
164+
165+
// Content font configuration
166+
let contentFont = UIFont.systemFont(ofSize: 12.0)
167+
let contentAttributes: [NSAttributedString.Key: Any] = [
168+
.font: contentFont,
169+
.foregroundColor: UIColor.black
170+
]
171+
172+
for paragraph in paragraphs {
173+
if paragraph.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
174+
currentY += 10 // Add space for empty paragraph
175+
continue
176+
}
177+
178+
// Calculate paragraph height based on text
179+
let paragraphSize = paragraph.boundingRect(
180+
with: CGSize(width: contentWidth, height: 1000),
181+
options: [.usesLineFragmentOrigin, .usesFontLeading],
182+
attributes: contentAttributes,
183+
context: nil
184+
).size
185+
186+
// Check if we need a new page
187+
if currentY + paragraphSize.height > pageRect.height - margin {
188+
context.beginPage()
189+
currentY = margin
190+
}
191+
192+
// Draw paragraph
193+
let paragraphRect = CGRect(x: margin, y: currentY, width: contentWidth, height: paragraphSize.height)
194+
paragraph.draw(in: paragraphRect, withAttributes: contentAttributes)
195+
196+
currentY += paragraphSize.height + 8 // Add space after paragraph
197+
}
198+
199+
return currentY
200+
}
201+
202+
// Helper method to draw an image with caption
203+
private static func drawImage(image: UIImage, index: Int, total: Int, startY: CGFloat,
204+
pageRect: CGRect, margin: CGFloat) -> CGFloat {
205+
var currentY = startY
206+
let contentWidth = pageRect.width - (margin * 2)
207+
208+
// Calculate image size while maintaining aspect ratio
209+
let maxHeight: CGFloat = 220
210+
211+
let aspectRatio = image.size.width / image.size.height
212+
let imageWidth: CGFloat
213+
let imageHeight: CGFloat
214+
215+
if aspectRatio > 1 { // Landscape
216+
imageWidth = min(contentWidth, image.size.width)
217+
imageHeight = imageWidth / aspectRatio
218+
} else { // Portrait
219+
imageHeight = min(maxHeight, image.size.height)
220+
imageWidth = imageHeight * aspectRatio
221+
}
222+
223+
// Ensure we're not exceeding max height
224+
let finalHeight = min(imageHeight, maxHeight)
225+
let finalWidth = aspectRatio > 1 ? finalHeight * aspectRatio : imageWidth
226+
227+
// Center the image
228+
let xPos = margin + (contentWidth - finalWidth) / 2
229+
230+
// Draw image
231+
let imageRect = CGRect(x: xPos, y: currentY, width: finalWidth, height: finalHeight)
232+
image.draw(in: imageRect)
233+
currentY += finalHeight + 5
234+
235+
// Add caption below image
236+
let captionFont = UIFont.systemFont(ofSize: 10.0)
237+
let captionAttributes: [NSAttributedString.Key: Any] = [
238+
.font: captionFont,
239+
.foregroundColor: UIColor.darkGray
240+
]
241+
242+
let caption = "Image \(index + 1) of \(total)"
243+
let captionSize = caption.boundingRect(
244+
with: CGSize(width: contentWidth, height: 50),
245+
options: [.usesLineFragmentOrigin],
246+
attributes: captionAttributes,
247+
context: nil
248+
).size
249+
250+
let captionRect = CGRect(
251+
x: margin,
252+
y: currentY,
253+
width: contentWidth,
254+
height: captionSize.height
255+
)
256+
257+
caption.draw(in: captionRect, withAttributes: captionAttributes)
258+
currentY += captionSize.height
259+
260+
return currentY
261+
}
116262
}

0 commit comments

Comments
 (0)