I have to create PDF file in which need to add lines at bottom left like footer.
Following code is working:
import StringIO
from reportlab.pdfgen import canvas
import uuid
def test(pdf_file_name="abc.pdf", pdf_size=(432, 648), font_details=("Times-Roman", 9)):
# create a new PDF with Reportla
text_to_add = "I am writing here.."
new_pdf = "test_%s.pdf"%(str(uuid.uuid4()))
packet = StringIO.StringIO()
packet.seek(0)
c = canvas.Canvas(pdf_file_name, pagesize = pdf_size)
#- Get the length of text in a PDF.
text_len = c.stringWidth(text_to_add, font_details[0], font_details[1])
#- take margin 20 and 20 in both axis
#- Adjust starting point on x axis according to text_len
x = pdf_size[0]-20 - text_len
y = 20
#- set font.
c.setFont(font_details[0], font_details[1])
#- write text,
c.drawString(x, y, text_to_add)
c.showPage()
c.save()
return pdf_file_name
Now if text have multiple lines then this is not working because length of text is greater than width of Page size. Understood.
I try with Frame and paragraph but still can not write text in correct position in a PDF
Following is code:
from reportlab.lib.pagesizes import letter
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.platypus import BaseDocTemplate, Frame, PageTemplate, Paragraph
styles = getSampleStyleSheet()
styleN = styles['Normal']
styleH = styles['Heading1']
def footer(canvas, doc):
canvas.saveState()
P = Paragraph("This is a multi-line footer. It goes on every page. " * 10, styleN)
w, h = P.wrap(doc.width, doc.bottomMargin)
print "w, h:", w, h
print "doc.leftMargin:", doc.leftMargin
P.drawOn(canvas, 10, 30)
canvas.restoreState()
def test():
doc = BaseDocTemplate('test.pdf', pagesize=(432, 648))
print "doc.leftMargin:", doc.leftMargin
print "doc.bottomMargin:", doc.bottomMargin
print "doc.width:", doc.width
print "doc.height:", doc.height
frame = Frame(10, 50, 432, 648, id='normal')
template = PageTemplate(id='test', frames=frame, onPage=footer)
doc.addPageTemplates([template])
text = []
for i in range(1):
text.append(Paragraph("", styleN))
doc.build(text)
Not understand why size of page change, because I set (432, 648) but is show (288.0, 504.0)
doc.leftMargin: 72.0
doc.bottomMargin: 72.0
doc.width: 288.0
doc.height: 504.0
Also Frame size:
w, h: 288.0 96
doc.leftMargin: 72.0
Do not know how to fix this issue.
I refer this link
First the mystery regarding the doc.width, the doc.width isn't the actual width of the document. It is the width of the area between the margins so in this case doc.width + doc.leftMargin + doc.rightMargin equals the width of the actual page.
Now back to why the footer did not span the entire width of the page as you wanted. This is because of the same issue as described above, namely doc.width isn't the actual paper width.
Assuming you want the footer to span the entire page
def footer(canvas, doc):
canvas.saveState()
P = Paragraph("This is a multi-line footer. It goes on every page. " * 10, styleN)
# Notice the letter[0] which is the width of the letter page size
w, h = P.wrap(letter[0] - 20, doc.bottomMargin)
P.drawOn(canvas, 10, 10)
canvas.restoreState()
Assuming you want the footer to span the width of the writable area
Note: the margins on the default setting are pretty big so that is why there is so much empty space on the sides.
def footer(canvas, doc):
canvas.saveState()
P = Paragraph("This is a multi-line footer. It goes on every page. " * 10, styleN)
w, h = P.wrap(doc.width, doc.bottomMargin)
print "w, h:", w, h
print "doc.leftMargin:", doc.leftMargin
P.drawOn(canvas, doc.leftMargin, 10)
canvas.restoreState()
EDIT:
As it might be useful to know where the normal text should start. We need to figure out the height of our footer. Under normal circumstances we cannot use P.height as it depends on the width of the text, calling it will raise a AttributeError.
In our case we actually are able to get the height of the footer either directly from P.wrap (the h) or by calling P.height after we have called P.wrap.
By starting our Frame at the height of the footer we will never have overlapping text. Yet it is important to remember to set the height of the Frame to doc.height - footer.height to ensure the text won't be placed outside the page.
Related
I have the report lab document, which is a mix of regular strings drawed with drawstring method and frames. I will get the table data from request, and i need to have the possibility to split the table to another page, because i don't know the size of the page. It works fine when the table is not big, but when the amount of rows is too big, it dust doesn't display anyting on the page. As per the reportlab lib, I simple need to use frame.split(), but it doesn't work. The code:
def generate_pdf(request: Request):
buffer = io.BytesIO()
pdfmetrics.registerFont(TTFont("Poppins-bold", f"static/fonts/poppins-800.ttf"))
pdfmetrics.registerFont(TTFont("Poppins-medium", "static/fonts/poppins-medium.ttf"))
pdfmetrics.registerFont(TTFont("Poppins-semibold", "static/fonts/poppins-semibold.ttf"))
pdfmetrics.registerFont(TTFont("Poppins-regular", "static/fonts/poppins-regular.ttf"))
doc = canvas.Canvas(buffer)
customColor = colors.Color(red=(39.0 / 255), green=(71.0 / 255), blue=(114.0 / 255))
doc.setFillColor(customColor)
doc.setFont(psfontname="Poppins-bold", size=20)
if request.data["type"] == "Purchase Order":
doc.drawString(50, 780, "Purchase Order")
else:
doc.drawString(50, 780, "Work Order")
doc = add_image(doc)
doc = draw_invoice_start_end(doc, request)
doc = draw_order_by_order_to(doc, request)
table_data = []
for i in range(50):
table_data.append(["item"])
frame1 = Frame(width=800, height=300, showBoundary=1, x1=50, y1=200)
story = []
table = Table(data=table_data, colWidths=None, rowHeights=None, style=None, splitByRow=1, repeatRows=0,
repeatCols=0,
rowSplitRange=None, spaceBefore=None, spaceAfter=None, cornerRadii=None)
styles = getSampleStyleSheet()
styles.add(ParagraphStyle(name="Rectangles",
fontName="Poppins-semibold",
textColor=customColor,
fontSize=10,
leading=12,
backColor="white",
borderRadius=5)
)
styleN = styles["Rectangles"]
story.append(table)
paragraph = Paragraph(text="Lorem impsum" * 5000, style=styleN)
story.append(paragraph)
for el in story:
if frame1.add(el, doc) == 0:
frame1.split(el, doc)
doc.showPage()
frame1 = Frame(0.5 * inch, inch, 7 * inch, 10.5 * inch, showBoundary=1)
doc.save()
pdf = buffer.getvalue()
file_data = ContentFile(pdf)
buffer.close()
return file_data
The only way I can think of, is using the frame.split() method, but it didn't work. Also, it looks like the SimpleDocTemplate could help me, but I will need to change the whole code to make it work.
This is what i get with this code: outcome
I am doing a report in python reportlab. And I want to align some text from the right side. But each line of text has a different length, and I have to set coordinates manually. How can I do it dynamically?
pdf.setFont('Helvetica', 9)
pdf.drawString(400,600,"Rechnungsdatum: "+today)
pdf.drawString(390,590,"Leistungserbringung: "+master_data.loc[master_data['company']==company]['Leistungserbringung'][0]) #cus_specific
pdf.drawString(343,580,"Leistungszeitraum: "+start+" - "+end)
pdf.drawString(372,570,"Rechnungsnummer: "+master_data.loc[master_data['company']==company]['Rechnungsnummer'][0]) #cus_specific
pdf.drawString(398,560,"Lieferantennummer: "+master_data.loc[master_data['company']==company]['Lieferantennummer'][0]) #cus_specific
pdf.drawString(432,550,"Zahlungsziel: " +str((date.today().replace(day=1) - timedelta(days=1)).day)+ " Tage")
That's how it looks like:
[]
I want all lines to be aligned from the right side for a beautiful view. I am using Canvas and drawing each string via drawString(). Any help will be appreciated)
You can get canvas width using Canvas._pagesize[0], Canvas object has drawRightString method for right-aligning lines.
from reportlab.pdfgen.canvas import Canvas
from reportlab.lib.units import inch, mm, cm, pica
from datetime import date, timedelta
if __name__ == "__main__":
pdf = Canvas("output.pdf")
pdf.setFont('Helvetica', 9)
master_data = ...
start = ...
end = ...
company = ...
today = ...
lines = [
"Rechnungsdatum: "+today,
"Leistungserbringung: "+master_data.loc[master_data['company']==company]['Leistungserbringung'][0],
"Leistungszeitraum: "+start+" - "+end,
"Rechnungsnummer: "+master_data.loc[master_data['company']==company]['Rechnungsnummer'][0],
"Lieferantennummer: "+master_data.loc[master_data['company']==company]['Lieferantennummer'][0],
"Zahlungsziel: " +str((date.today().replace(day=1) - timedelta(days=1)).day)+ " Tage",
]
ys = [600,590,580,570,560,550]
width = pdf._pagesize[0]
padding = 10 * mm
for y, line in zip(ys, lines):
pdf.drawRightString(width - padding, y, line)
pdf.save()
I am new to reportlab lib, I am learning it simultaneously working on a college project.
I have created a desktop application in wxpython, which result in saving data in PDF.
I want to add 2 lines in my pdf. Where line starts with a user input called name, then some words, again at 2nd line some words, user name and then again some words...
I tried use some of Paragraph and canvas methods and classes but I wasn't able to get the desired output.
Desired output:
Alex is working on college project.
reportlab is very good lib, Alex liked it.
My code:
import os
import reportlab
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.pdfmetrics import registerFontFamily
from reportlab.pdfbase.ttfonts import TTFont
# Registered font family
pdfmetrics.registerFont(TTFont('Vera', 'Vera.ttf'))
pdfmetrics.registerFont(TTFont('VeraBd', 'VeraBd.ttf'))
pdfmetrics.registerFont(TTFont('VeraIt', 'VeraIt.ttf'))
pdfmetrics.registerFont(TTFont('VeraBI', 'VeraBI.ttf'))
# Registered fontfamily
registerFontFamily('Vera',normal='Vera',bold='VeraBd',italic='VeraIt',boldItalic='VeraBI')
# Output pdf file name.
can = canvas.Canvas("Bold_Trail.pdf", pagesize=A4)
# Setfont for whole pdf.
can.setFont('Vera', 12)
# student name variable.
student_name ="Alex"
# Content.
line1 = " is working on college project."
line2 = "Reportlab is very good lib, "
line3 = " liked it.<br>"
# Joining whole content together.
content = "<strong>" + student_name + "</strong>" + line1
content2 = line2 + "<strong>"+student_name + "</strong>" + line3
# drawString location calculation.
x = 0; y = 8.5 * 72
# First string.
can.drawString(x,y, content)
y = y - 72
# Second String.
can.drawString(x,y, content2)
# Create PDF.
can.save()
Is there any other way except using XML method <strong> or <b> that do not work in the above program?
The words should remain on one line.
You could use the setFont method of the canvas object, to set the font to Bold when needed, and Normal otherwise.
* UPDATE *
In order to calculate the right value for x, you can use the stringWidth method, that calculates the length of the string given its content, the font name and the font size. You will have to import it from reportlab.pdfbase.pdfmetrics:
[...]
from reportlab.pdfbase.pdfmetrics import stringWidth
[...]
# student name variable.
student_name = 'Alex'
# Content.
line1 = " is working on college project."
line2 = "Reportlab is very good lib, "
line3 = " liked it"
# drawString location calculation.
x = 0
y = 8.5 * 72
# First string.
can.setFont('Helvetica-Bold', 8)
can.drawString(x, y, student_name)
can.setFont('Helvetica', 8)
textWidth = stringWidth(student_name, 'Helvetica-Bold', 8)
x += textWidth + 1
can.drawString(x, y, line1)
y = y - 72
# Second String.
x = 0
can.setFont('Helvetica', 8)
can.drawString(x, y, line2)
textWidth = stringWidth(line2, 'Helvetica', 8)
x += textWidth + 1
can.setFont('Helvetica-Bold', 8)
can.drawString(x, y, student_name)
textWidth = stringWidth(student_name, 'Helvetica-Bold', 8)
x += textWidth + 1
can.setFont('Helvetica', 8)
can.drawString(x, y, line3)
# Create PDF.
can.save()
Or you could have a look at ParagraphStyle and Paragraph (from reportlab.lib.styles import ParagraphStyle, from reportlab.platypus import Paragraph) but I am not sure if you can concatenate two different styles in the same string.
I found a way to format the Paragraph text with XML tags. Firstly you need to register the font family then it should work. Below I downloaded a group of .ttf files as an example and registered them, after that, the XML tags worked properly.
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.pdfbase.pdfmetrics import registerFontFamily
pdfmetrics.registerFont(TTFont('OpenSansR', 'OpenSans-Regular.ttf'))
pdfmetrics.registerFont(TTFont('OpenSansL', 'OpenSans-Light.ttf'))
pdfmetrics.registerFont(TTFont('OpenSansB', 'OpenSans-Bold.ttf'))
registerFontFamily('OpenSans', normal='OpenSansR', bold='OpenSansB', italic='OpenSansL', boldItalic='OpenSansB')
I'm trying to split a "full-width" table across 2 pages or even more. I use the Platypus library of ReportLab and the BaseDocTemplate class.
I've a "full width" table of elements and this should be drawn into a frame of the first page, if the table has enough rows It should be continued in the second page. My problem is that the frame of the first page has a different height and position than the others, because at the top of the first page I need to show more information (Yes... I'm talking about an invoice or order).
After thousands attempts, all that I've got is a pdf with a unique page with only 8 items/rows, It's exactly the space that they require at the first page, but if the table has more than 8 rows, then I get a pdf with only 1 page and without the table (that means an empty frame, although I see all data in the log).
I've used the methods split() and wrap() but probably in the wrong way, because I'm new with ReportLab. I show you the last version of my code:
from django.http import HttpResponse
from reportlab.pdfgen import canvas
from reportlab.lib.units import mm
from reportlab.lib import colors
from reportlab.lib.pagesizes import A4
from reportlab.platypus import BaseDocTemplate, PageTemplate, Table, Spacer, Frame, TableStyle,\
NextPageTemplate, PageBreak, FrameBreak
PAGE_WIDTH = A4[0]
PAGE_HEIGHT = A4[1]
MARGIN = 10*mm
class ThingPDF(BaseDocTemplate):
def header(self, canvas, subheader=True):
# print 'header()'
data = [('AAAA', 'Thing %s' % (self.thing.name)), ]
s = []
t = Table(data, colWidths=[95 * mm, 95 * mm], rowHeights=None, style=None, splitByRow=1,
repeatRows=0, repeatCols=0)
t.setStyle(TableStyle([
('BACKGROUND', (0, 0), (0, 0), colors.red),
('BACKGROUND', (1, 0), (1, 0), colors.blue),
('ALIGN', (1, 0), (1, 0), 'RIGHT'),
]))
s.append(t)
# if subheader:
# print 'subheader'
self.head.addFromList(s, canvas)
def data_table(self, canvas, items):
# print 'data_table()'
d = [[u'col0', u'col1', u'col2', u'col3', u'col4', ],]
for item in items:
d.append([item.col0, item.col1, item.col2, item.col3, item.col4])
s = []
t = Table(d, colWidths=[20*mm, 100*mm, 20*mm, 20*mm, 30*mm], rowHeights=20*mm, style=None,\
splitByRow=1, repeatRows=0, repeatCols=0)
t.setStyle([('BACKGROUND', (0,0), (-1,0), ('#eeeeee'))])
h=187*mm #TODO
w=A4[0] - (2*MARGIN)
splitter = t.split(w, h)
# print '\n\nresult of splitting: ', len(splitter)
for i in splitter:
print 'i: ', i
self.dataframeX.addFromList(s, canvas)
s.append(t)
self.dataframe0.addFromList(s, canvas)
def on_first_page(self, canvas, doc):
canvas.saveState()
self.header(canvas)
self.data_table(canvas, self.items)
canvas.restoreState()
def on_next_pages(self, canvas, doc):
canvas.saveState()
self.header(canvas, subheader=False)
canvas.restoreState()
def build_pdf(self, thing=None, items=None, user=None):
self.thing = thing
self.items = items
self.doc = BaseDocTemplate('%s.pdf' % (thing.name),
pagesize=A4,
pageTemplates=[self.first_page, self.next_pages,],
showBoundary=1,
rightMargin=MARGIN,
leftMargin=MARGIN,
bottomMargin=MARGIN,
topMargin=MARGIN,
allowSplitting=1,
title='%s' % 'title')
self.story.append(Spacer(0*mm, 2*mm))
self.doc.build(self.story)
response = HttpResponse(mimetype='application/pdf')
response['Content-Disposition'] = 'attachment; filename=%s.pdf' % (reference)
return response
def __init__(self):
self.thing = None
self.items = None
self.story = []
#========== FRAMES ==========
self.head = Frame(x1=MARGIN, y1=A4[1] - (2*MARGIN), width=A4[0] - (2*MARGIN), height=10*mm,
leftPadding=0, bottomPadding=0, rightPadding=0, topPadding=0, id='header',
showBoundary=1)#, overlapAttachedSpace=None, _debug=None)
self.dataframe0 = Frame(x1=MARGIN, y1=10*mm, width=A4[0] - (2*MARGIN), height=187*mm,
leftPadding=0, bottomPadding=0, rightPadding=0, topPadding=0,
id='body', showBoundary=1)
self.dataframeX = Frame(x1=MARGIN, y1=MARGIN, width=A4[0] - (2*MARGIN), height=257*mm,
leftPadding=0, bottomPadding=0, rightPadding=0, topPadding=0,
id='body', showBoundary=1)
#========== PAGES ==========
self.first_page = PageTemplate(id='firstpage', frames=[self.head, self.dataframe0], onPage=self.on_first_page)
self.next_pages = PageTemplate(id='nextpages', frames=[self.head, self.dataframeX], onPage=self.on_next_pages)
Thank you in advance!!
The code that you posted is lacking some data and is not by itself runnable, so i can't really tell if my answer will be correct. Please extend your code if this doesn't work!
First of all, you don't have to use the wrap and split methods at all! The table will split itself when doc.build consumes the story. Also, split doesn't split the table inline, but returns just a list of tables. So in your case splitter is a list of tables over which you iterate and then append an empty list to the frame. I would suggest that you skip that part. You are adding the different elements to individual frames. Because of this, you would add the splitted table to the dataframeX but dataframeX might never be used, because you are never using the next_pages PageTemplate. For this you have to add an NextPageTemplate() to the story, after you are finished with your first page. I would let platypus do that stuff for you: Just let your methods return lists with the generated elements and concatenate them before passing them to doc.build().
I'm using pyBarcode to generate PNGs and the number below the barcode is getting cut off on the right. How do I nudge it left a few pixels?
According to the documentation I need to do something like this:
barcode.writer.BaseWriter(paint_text=my_callback)
And define a callback like this:
my_callback(xpos, ypos)
and:
use self.text as text
How exactly do I apply all of that to my Django view (below)?
def barcode(request):
import barcode
from barcode.writer import ImageWriter
from cStringIO import StringIO
def mm2px(mm, dpi=300):
return (mm * dpi) / 25.4
class MyImageWriter(ImageWriter):
def calculate_size(self, modules_per_line, number_of_lines, dpi=300):
width = 2 * self.quiet_zone + modules_per_line * self.module_width
height = 1.0 + self.module_height * number_of_lines
if self.text:
height += (self.font_size + self.text_distance) / 3
return int(mm2px(width, dpi)), int(mm2px(height, dpi))
f = BarcodeForm(request.GET)
if f.is_valid():
try:
i = StringIO()
bc_factory = barcode.get_barcode_class(f.PYBARCODE_TYPE[f.cleaned_data['barcode_type']])
bc_factory.default_writer_options['quiet_zone'] = 1.0
bc_factory.default_writer_options['text_distance'] = 1.0
bc_factory.default_writer_options['module_height'] = 15.0
bc_factory.default_writer_options['module_width'] = 0.3
bc_factory.default_writer_options['font_size'] = 46
bc = bc_factory(f.cleaned_data['text'], writer=MyImageWriter())
bc.write(i)
return HttpResponse(i.getvalue(), mimetype='image/png')
except Exception, e:
return HttpResponseBadRequest(str(e))
else:
return HttpResponseBadRequest('Missing text or unsupported barcode type: %s' % f.errors)
Edit: After answering I noticed you've got a factory that's setting the quiet_zone to 1.0. Change that back to 6.5 and I'd imagine it will look fine.
Edit2: I misunderstood the exact problem you were experiencing.
For whatever reason the author of pyBarcode is putting the text centered in the middle of the bar code. When the render method calls _paint_text() it passes in xpos/2, which sets it in the middle of the barcode. I guess this is okay with the default font he's using, but when you increased the font, it no longer fits.
Instead I was able to place it on the left side, by overriding the _paint_text() method. In the last line below, the variable pos is just a tuple containing an (x,y) coordinate that tells PIL where to draw the text on the bar code. So I've made sure x is lined up with the bar code. If you need it right aligned, you could play around with the xpos variable to get it where you need.
Give this a shot:
class MyImageWriter(ImageWriter):
def calculate_size(self, modules_per_line, number_of_lines, dpi=300):
width = 2 * self.quiet_zone + modules_per_line * self.module_width
height = 1.0 + self.module_height * number_of_lines
if self.text:
height += (self.font_size + self.text_distance) / 3
return int(mm2px(width, dpi)), int(mm2px(height, dpi))
def _paint_text(self, xpos, ypos):
# this should align your font to the left side of the bar code:
xpos = self.quiet_zone
pos = (mm2px(xpos, self.dpi), mm2px(ypos, self.dpi))
font = ImageFont.truetype(FONT, self.font_size)
self._draw.text(pos, self.text, font=font, fill=self.foreground)