@@ -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