@@ -39,11 +39,13 @@ function PDFGenerator.new()
3939 current_y = 0 ,
4040 resources = {},
4141 font_metrics = {},
42+ last_font = nil ,
4243 current_table = {
4344 current_row = {
4445 height = nil
4546 },
46- padding = 5 ,
47+ padding_x = 5 ,
48+ padding_y = 5 ,
4749 header_columns = nil ,
4850 data_columns = nil ,
4951 header_options = nil ,
@@ -112,13 +114,16 @@ function PDFGenerator:addPage(width, height)
112114
113115 -- self:drawHeader()
114116 -- self:drawFooter()
117+ if self .last_font then
118+ self :useFont (self .last_font .fontFamily , self .last_font .factor )
119+ end
115120
116121 self :setY (0 )
117122 self :setX (0 )
118123
119124 -- Display table header
120125 if self .current_table .header_columns then
121- self :drawRowTable (self .current_table .header_columns , { fillColor = " eee " } )
126+ self :drawRowTable (self .current_table .header_columns , self . current_table . header_options )
122127 end
123128
124129 return self
@@ -135,18 +140,15 @@ function PDFGenerator:addBasicFont()
135140end
136141
137142-- Add custom font (TrueType)
138- function PDFGenerator :addCustomFont (fontPath , fontName )
143+ function PDFGenerator :addCustomFont (fontPath , fontName , fontWeight )
139144 local fontObj = getNewObjNum ()
140145 local fontFileObj = getNewObjNum ()
141146 local fontDescObj = getNewObjNum ()
142147
143148 -- Read font file
144- local file = io.open (fontPath , " rb" )
145- if not file then
146- error (" Could not open font file: " .. fontPath )
147- end
148- local fontData = file :read (" *all" )
149- file :close ()
149+
150+ local fontData = LoadAsset (fontPath )
151+ local fontMetrics = LoadAsset (fontPath :gsub (" %.ttf$" , " .json" ))
150152
151153 -- Font descriptor object
152154 self .objects [fontDescObj ] = string.format (
@@ -178,12 +180,14 @@ function PDFGenerator:addCustomFont(fontPath, fontName)
178180 self .custom_fonts = {}
179181 end
180182 self .custom_fonts [fontName ] = fontObj
181-
182- return fontObj
183+ self . font_metrics [ fontName ] = self . font_metrics [ fontName ] or {}
184+ self . font_metrics [ fontName ][ fontWeight ] = DecodeJson ( fontMetrics )
183185end
184186
185187-- Use custom font for text
186- function PDFGenerator :useFont (fontName )
188+ function PDFGenerator :useFont (fontName , factor )
189+ factor = factor or 1
190+
187191 if not self .custom_fonts or not self .custom_fonts [fontName ] then
188192 error (" Font not loaded: " .. fontName )
189193 end
@@ -202,6 +206,8 @@ function PDFGenerator:useFont(fontName)
202206 )
203207 )
204208
209+ self .last_font = { fontFamily = fontName , factor = factor }
210+
205211 return self
206212end
207213-- Get text width for current font and size using font metrics
@@ -212,7 +218,7 @@ function PDFGenerator:getTextWidth(text, fontSize, fontWeight)
212218 -- Helvetica character widths (in 1/1000 units of font size)
213219 -- These metrics are from the Adobe Font Metrics (AFM) file for Helvetica
214220 -- Values represent character widths in 1/1000 units of the font size
215- local helveticaMetrics = {
221+ local fontMetrics = {
216222 normal = {
217223 [32 ]= 278 , [33 ]= 278 , [34 ]= 355 , [35 ]= 556 , [36 ]= 556 , [37 ]= 889 , [38 ]= 667 , [39 ]= 191 , [40 ]= 333 , [41 ]= 333 ,
218224 [42 ]= 389 , [43 ]= 584 , [44 ]= 278 , [45 ]= 333 , [46 ]= 278 , [47 ]= 278 , [48 ]= 556 , [49 ]= 556 , [50 ]= 556 , [51 ]= 556 ,
@@ -273,11 +279,16 @@ function PDFGenerator:getTextWidth(text, fontSize, fontWeight)
273279 }
274280 }
275281
282+ if self .font_metrics [self .last_font .fontFamily ] then
283+ fontMetrics = self .font_metrics [self .last_font .fontFamily ]
284+ end
285+
276286 local width = 0
277287 for i = 1 , # text do
278288 local charCode = string.byte (text , i )
279- local metrics = helveticaMetrics [fontWeight ]
280- width = width + (metrics [charCode ] or 556 ) -- default to 556 for unknown chars
289+ local metrics = fontMetrics [fontWeight ]
290+
291+ width = width + (metrics [" " .. charCode ] or 556 ) -- default to 556 for unknown chars
281292 end
282293
283294 -- Convert from font units (1/1000) to points
@@ -397,24 +408,53 @@ function PDFGenerator:addText(text, fontSize, color, alignment, width)
397408 return self
398409end
399410
411+ -- Add paragraph to current page
412+ function PDFGenerator :addParagraph (text , options )
413+ options = options or {}
414+ options .fontSize = options .fontSize or 12
415+ options .alignment = options .alignment or " left"
416+ options .width = options .width or (self .page_width - self .margin_x [1 ] - self .margin_x [2 ])
417+ options .color = options .color or " 000000"
418+ options .width = options .width
419+
420+ local lines = self :splitTextToLines (text , options .fontSize , options .width )
421+ for i , line in ipairs (lines ) do
422+ self .current_y = self .current_y + options .fontSize * 1.2
423+ if self .out_of_page == false and self .page_height - self .current_y - self .header_height < self .margin_y [1 ] + self .margin_y [2 ] then
424+ self :addPage ()
425+ end
426+ self :addText (line , options .fontSize , options .color , options .alignment , options .width )
427+ end
428+ return self
429+ end
430+
400431-- Draw a table
401- function PDFGenerator :drawTable (options )
432+ function PDFGenerator :drawTable (options , table_options )
402433 options = options or {}
434+ self .current_table = table .merge (self .current_table , table_options or {})
403435 self .current_table .header_columns = options .header_columns
404436 self .current_table .data_columns = options .data_columns
405437 self .current_table .header_options = options .header_options
406438 self .current_table .data_options = options .data_options
407439
408440 self :drawRowTable (options .header_columns , options .header_options )
409- for _ , column in ipairs (options .data_columns ) do
441+ for line , column in ipairs (options .data_columns ) do
442+ if options .data_options .oddFillColor and line % 2 == 0 then
443+ options .data_options .fillColor = options .data_options .oddFillColor
444+ end
445+
446+ if options .data_options .evenFillColor and line % 2 == 1 then
447+ options .data_options .fillColor = options .data_options .evenFillColor
448+ end
449+
410450 self :drawRowTable (column , options .data_options )
411451 end
412452
413453 self .current_table .header_columns = nil
414454 self .current_table .data_columns = nil
415455 self .current_table .header_options = nil
416456 self .current_table .data_options = nil
417- self .current_table .current_row = { height = nil , padding = 5 }
457+ self .current_table .current_row = { height = nil , padding_x = 5 , padding_y = 5 }
418458end
419459
420460-- Calculate maximum height needed for a collection of text items
@@ -426,14 +466,15 @@ function PDFGenerator:calculateMaxHeight(items)
426466 local text = item .text or " "
427467 local fontSize = item .fontSize or 12
428468 local width = item .width or (self .page_width - self .margin_x [1 ] - self .margin_x [2 ])
429- local padding = item .padding or self .current_table .padding or 5
469+ local padding_x = item .padding_x or self .current_table .padding_x or 5
470+ local padding_y = item .padding_y or self .current_table .padding_y or 5
430471
431472 -- Split text into lines considering the available width
432- local lines = self :splitTextToLines (text , fontSize , width - (padding * 2 ))
473+ local lines = self :splitTextToLines (text , fontSize , width - (padding_x * 2 ))
433474
434475 -- Calculate height for this item
435476 local line_height = fontSize * 1.5 -- Standard line height
436- local text_height = # lines * line_height + 2 -- + (padding * 2) -- Include padding
477+ local text_height = # lines * line_height + 2 + (padding_y * 2 ) -- Include padding
437478 -- Update max_height if this item is taller
438479 if text_height > max_height then
439480 max_height = text_height
@@ -485,25 +526,28 @@ function PDFGenerator:drawTableCell(text, options)
485526 options .borderColor = options .borderColor or " 000"
486527
487528 -- Draw cell border using existing rectangle method
488- self :drawRectangle (
489- options .width ,
490- self .current_table .current_row .height ,
491- options .borderWidth ,
492- " solid" ,
493- options .borderColor ,
494- options .fillColor
495- )
529+ self :drawRectangle ({
530+ width = options .width ,
531+ height = self .current_table .current_row .height ,
532+ borderWidth = options .borderWidth ,
533+ borderStyle = " solid" ,
534+ borderColor = options .borderColor ,
535+ fillColor = options .fillColor ,
536+ borderSides = options .borderSides ;
537+ })
496538
497539 -- Save current position before drawing text
498540 local saved_x = self .current_x
499541 local saved_y = self .current_y
500542
501543 if options .alignment == " left" then
502- self :moveX (self .current_table .padding )
544+ self :moveX (self .current_table .padding_x )
503545 elseif options .alignment == " right" then
504- self :moveX (- self .current_table .padding )
546+ self :moveX (- self .current_table .padding_x )
505547 end
506548
549+ self :moveY (self .current_table .padding_y )
550+
507551 self :addParagraph (text , { fontSize = options .fontSize , alignment = options .alignment , width = options .width })
508552
509553 -- Restore cursor position after drawing text
@@ -542,26 +586,6 @@ function PDFGenerator:currentYPos()
542586 return self .page_height - self .margin_y [1 ] - self .current_y - self .header_height
543587end
544588
545- -- Add paragraph to current page
546- function PDFGenerator :addParagraph (text , options )
547- options = options or {}
548- options .fontSize = options .fontSize or 12
549- options .alignment = options .alignment or " left"
550- options .width = options .width or (self .page_width - self .margin_x [1 ] - self .margin_x [2 ])
551- options .color = options .color or " 000000"
552- options .width = options .width
553-
554- local lines = self :splitTextToLines (text , options .fontSize , options .width )
555- for i , line in ipairs (lines ) do
556- self .current_y = self .current_y + options .fontSize * 1.2
557- if self .out_of_page == false and self .page_height - self .current_y - self .header_height < self .margin_y [1 ] + self .margin_y [2 ] then
558- self :addPage ()
559- end
560- self :addText (line , options .fontSize , options .color , options .alignment , options .width )
561- end
562- return self
563- end
564-
565589-- Draw line on current page
566590function PDFGenerator :drawLine (x1 , y1 , x2 , y2 , width )
567591 width = width or 1
@@ -686,13 +710,22 @@ end
686710-- Draw rectangle on current page
687711-- borderStyle can be "solid" or "dashed"
688712-- borderColor and fillColor should be in format {r, g, b} where each value is between 0 and 1
689- function PDFGenerator :drawRectangle (width , height , borderWidth , borderStyle , borderColor , fillColor )
690- borderWidth = borderWidth or 1
691- borderStyle = borderStyle or " solid"
692- borderColor = borderColor or " 000000" -- default gray
693- borderColor = PDFGenerator :hexToRGB (borderColor )
694- fillColor = fillColor or " ffffff" -- default gray
695- fillColor = PDFGenerator :hexToRGB (fillColor )
713+ function PDFGenerator :drawRectangle (options )
714+ Logger (EncodeJson (options .borderSides ))
715+
716+ options = options or {}
717+ options .borderWidth = options .borderWidth or 1
718+ options .borderStyle = options .borderStyle or " solid"
719+ options .borderColor = options .borderColor or " 000000" -- default gray
720+ options .borderColor = PDFGenerator :hexToRGB (options .borderColor )
721+ options .fillColor = options .fillColor or " ffffff" -- default gray
722+ options .fillColor = PDFGenerator :hexToRGB (options .fillColor )
723+
724+ options .borderSides = options .borderSides or {}
725+ options .borderSides .left = options .borderSides .left or true
726+ options .borderSides .right = options .borderSides .right or true
727+ options .borderSides .top = options .borderSides .top or true
728+ options .borderSides .bottom = options .borderSides .bottom or true
696729
697730 local content = self .contents [self .current_page_obj ]
698731
@@ -702,35 +735,81 @@ function PDFGenerator:drawRectangle(width, height, borderWidth, borderStyle, bor
702735 -- Set border color
703736 content .stream = content .stream .. string.format (
704737 " %s %s %s RG\n " ,
705- numberToString (borderColor [1 ]),
706- numberToString (borderColor [2 ]),
707- numberToString (borderColor [3 ])
738+ numberToString (options . borderColor [1 ]),
739+ numberToString (options . borderColor [2 ]),
740+ numberToString (options . borderColor [3 ])
708741 )
709742
710743 -- Set line width
711- content .stream = content .stream .. string.format (" %s w\n " , numberToString (borderWidth ))
744+ content .stream = content .stream .. string.format (" %s w\n " , numberToString (options . borderWidth ))
712745
713746 -- Set dash pattern if needed
714- if borderStyle == " dashed" then
747+ if options . borderStyle == " dashed" then
715748 content .stream = content .stream .. " [3 3] 0 d\n "
716749 end
717750
718751 -- If fill color is provided, set it and draw filled rectangle
719752 content .stream = content .stream .. string.format (
720753 " %s %s %s rg\n " ,
721- numberToString (fillColor [1 ]),
722- numberToString (fillColor [2 ]),
723- numberToString (fillColor [3 ])
754+ numberToString (options . fillColor [1 ]),
755+ numberToString (options . fillColor [2 ]),
756+ numberToString (options . fillColor [3 ])
724757 )
725758 -- Draw filled and stroked rectangle
726759 content .stream = content .stream .. string.format (
727- " %s %s %s %s re\n B \n " ,
760+ " %s %s %s %s re\n f \n " ,
728761 numberToString (self .margin_x [1 ] + self .current_x ),
729- numberToString (self .currentYPos (self ) - height ),
730- numberToString (width ),
731- numberToString (height )
762+ numberToString (self .currentYPos (self ) - options . height ),
763+ numberToString (options . width ),
764+ numberToString (options . height )
732765 )
733766
767+ -- Draw left border
768+ Logger (EncodeJson (options .borderSides ))
769+ if options .borderSides .left == true then
770+ content .stream = content .stream .. string.format (
771+ " %s w\n %s %s m\n %s %s l\n S\n " ,
772+ numberToString (options .borderWidth ),
773+ numberToString (self .margin_x [1 ] + self .current_x ),
774+ numberToString (self .currentYPos (self ) - options .height ),
775+ numberToString (self .margin_x [1 ] + self .current_x ),
776+ numberToString (self .currentYPos (self ))
777+ )
778+ end
779+
780+ if options .borderSides .right == true then
781+ content .stream = content .stream .. string.format (
782+ " %s w\n %s %s m\n %s %s l\n S\n " ,
783+ numberToString (options .borderWidth ),
784+ numberToString (self .margin_x [1 ] + self .current_x + options .width ),
785+ numberToString (self .currentYPos (self ) - options .height ),
786+ numberToString (self .margin_x [1 ] + self .current_x + options .width ),
787+ numberToString (self .currentYPos (self ))
788+ )
789+ end
790+
791+ if options .borderSides .top == true then
792+ content .stream = content .stream .. string.format (
793+ " %s w\n %s %s m\n %s %s l\n S\n " ,
794+ numberToString (options .borderWidth ),
795+ numberToString (self .margin_x [1 ] + self .current_x ),
796+ numberToString (self .currentYPos (self )),
797+ numberToString (self .margin_x [1 ] + self .current_x + options .width ),
798+ numberToString (self .currentYPos (self ))
799+ )
800+ end
801+
802+ if options .borderSides .bottom == true then
803+ content .stream = content .stream .. string.format (
804+ " %s w\n %s %s m\n %s %s l\n S\n " ,
805+ numberToString (options .borderWidth ),
806+ numberToString (self .margin_x [1 ] + self .current_x ),
807+ numberToString (self .currentYPos (self ) - options .height ),
808+ numberToString (self .margin_x [1 ] + self .current_x + options .width ),
809+ numberToString (self .currentYPos (self ) - options .height )
810+ )
811+ end
812+
734813 -- Restore graphics state
735814 content .stream = content .stream .. " Q\n "
736815
0 commit comments