简体   繁体   中英

Python ReportLab Paragraph Count Lines Printed

Is there any way that I can get the number of lines for a Flowable Paragraph in reportlab? I have a very long string printed in different sizes and fonts. I need to know how many lines is utilized for the Whole Paragraph to be printed with TA_JUSTIFY alignment.

Can this be done?

Below is my sample python file

import os
import sys
import string
import pprint
import imp
import tempfile

from reportlab.pdfgen import canvas
from reportlab.platypus import Preformatted, XPreformatted, Paragraph, Frame, Image, \
     Table, TableStyle, Spacer
from reportlab.lib.enums import TA_LEFT, TA_RIGHT, TA_CENTER, TA_JUSTIFY
from reportlab.lib import styles
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.pagesizes import *
from reportlab.lib import colors
import reportlab.rl_config
# Import as may be needed if we require embedded true type fonts
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.lib.fonts import addMapping
from reportlab.graphics.barcode import code39, code128, code93
from reportlab.graphics.barcode import common
from reportlab.graphics.barcode import qr
from reportlab.graphics import renderPDF
from reportlab.graphics.shapes import Drawing
import string
import os
import imp
from reportlab.lib import colors


canv = canvas.Canvas('Output.pdf')


styles = getSampleStyleSheet()
parastyle = ParagraphStyle(name='Justify', alignment=TA_JUSTIFY)
parastyle.leading = 12
parastyle.fontSize = 11
styles.add(parastyle)

drawText = "The Avengers become divided, both over how to approach Loki and the revelation that S.H.I.E.L.D. plans to harness the Tesseract to develop weapons as a deterrent against hostile extraterrestrials. As the group argues, Barton and Loki's other possessed agents attack the Helicarrier, disabling one of its engines in flight and causing Banner to transform into the Hulk. Stark and Rogers work to restart the damaged engine, and Thor attempts to stop the Hulk's rampage. Romanoff reluctantly fights Barton, and knocks him unconscious, breaking Loki's mind control. Loki escapes after killing Coulson and ejecting Thor from the airship, while the Hulk falls to the ground after attacking a S.H.I.E.L.D. fighter jet. Fury uses Coulson's death to motivate the Avengers into working as a team. Stark and Rogers realize that for Loki, simply defeating them will not be enough; he needs to overpower them publicly to validate himself as ruler of Earth. Loki uses the Tesseract, in conjunction with a device Selvig built, to open a wormhole above Stark Tower to the Chitauri fleet in space, launching his invasion."

inch = INCH = 72
cm = CM = inch/2.54
mm = MM = cm/10


x=10*mm
y=240*mm
width=190*mm
height=10*mm

canv.saveState()
canv.translate(x,y)
canv.rotate(0)
canv.translate(-x,-y)

p = Paragraph(drawText, styles["Justify"])

p.wrapOn(canv, width, height)
p.drawOn(canv, x, y)

canv.showPage()
canv.save()

THis is the current Output Output

I need to get the number of lines printed in paragraph. In my example i must get 11.

If i change the font and the fontsize, i must get the value accordingly.

After you call Paragraph.wrap or Paragraph.wrapOn , you have a property called blPara available, which has a property called lines that's a list. You could use the length of it. Like so:

from reportlab.lib.enums import TA_JUSTIFY
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import ParagraphStyle
from reportlab.lib.units import mm
from reportlab.pdfgen.canvas import Canvas
from reportlab.platypus import Paragraph

canvas = Canvas("test.pdf")
drawText = "The Avengers become divided, both over how to approach Loki and the revelation that S.H.I.E.L.D. plans to harness the Tesseract to develop weapons as a deterrent against hostile extraterrestrials. As the group argues, Barton and Loki's other possessed agents attack the Helicarrier, disabling one of its engines in flight and causing Banner to transform into the Hulk. Stark and Rogers work to restart the damaged engine, and Thor attempts to stop the Hulk's rampage. Romanoff reluctantly fights Barton, and knocks him unconscious, breaking Loki's mind control. Loki escapes after killing Coulson and ejecting Thor from the airship, while the Hulk falls to the ground after attacking a S.H.I.E.L.D. fighter jet. Fury uses Coulson's death to motivate the Avengers into working as a team. Stark and Rogers realize that for Loki, simply defeating them will not be enough; he needs to overpower them publicly to validate himself as ruler of Earth. Loki uses the Tesseract, in conjunction with a device Selvig built, to open a wormhole above Stark Tower to the Chitauri fleet in space, launching his invasion."

availWidth, availHeight = 190*mm, A4[1]

style = ParagraphStyle("justifies", alignment=TA_JUSTIFY, fontSize=11, leading=12)
par = Paragraph(drawText, style=style)

par.wrap(availWidth, availHeight)
par.drawOn(canvas, 10*mm, A4[1]-10*mm-par.height)

print(len(par.blPara.lines))  # 11
canvas.save()

There's also simpleSplit which does the same job, with the added benefit that it's easier to get the text of each line.

from reportlab.lib.utils import simpleSplit
lines = simpleSplit(drawText, style.fontName, style.fontSize, availWidth)

I don't think that this is currently possible at all in Reportlab, since the flowable doesnt "know" about the content directly. See also this exchange on the reportlab mailing list .

I just happened across this question while also searching for something simple to do what you're asking. Unfortunately I didn't find an answer, so I tried to put something together to get this to work. It ain't pretty, but it works!

I just added this code right after your canv.save() method call:

pixelRatio = 1.333333 # 1 point = 1.333333 pixels
# https://websemantics.uk/articles/font-size-conversion/
# http://www.endmemo.com/sconvert/pixelpoint.php#targetText=Pixel%E2%86%94Point%201%20Point,Twip%201%20Pixel%20%3D%2015%20Twip

fontLineHeight = styles["Justify"].fontSize * pixelRatio
boxWidth = width
boxHeight = height
boxLines = int(72/fontLineHeight)
boxLRBuffer = 0 # Adjust this to whatever inside margin/padding you have in your box
boxPixelCapacity = boxWidth * boxLines - boxLRBuffer # Total pixels in the box

# A list to hold the lines of text
textLines = []
# A pointer to indicate which character is being evaluated
position = len(drawText)
# Make a copy of the original string so as to not modify it
tempString = drawText
# This is a dummy string used to test the width of the string
testString1 = tempString
# Another dummy string to hold what is not being split into a line
# testString2 will be build going backwards from the last character
# to the character right after the line split
testString2 = ''
# Boolean flag to get out of the while loop
done = False
while not done:
    # Use pdfmetrics to get the line width in points based upon the fontName and fontSize used
    if pdfmetrics.stringWidth(testString1, styles["Justify"].fontName, styles["Justify"].fontSize, 'utf8') > boxWidth-boxLRBuffer*2:
        # Since the line is too long, move backwards through the string until an appropriate size is found
        # by decreasing the pointer position.  Spaces also take space.
        position -= 1
        if position >= 0:
            #since the the pointer has not reached the beginning of testString1
            testString1 = testString1[:position] # remove the last character from testString1
            testString2 = tempString[position:] # add the next character to testString2 for later use
    # Since testString1 now fits within the stringWidth
    # it must be checked to see if this is the last line
    # being processed.
    elif testString1 == testString2:
        # This must be the last line since it equals testString2
            textLines.append(testString2.strip())
            break
    # This is not the last line of text, so now work backwards in
    # the string to find a space so the line can be split
    elif ' ' in testString1:
        while testString1[position:] != ' ' and position >= 0:
            testString1 = testString1[:position]
            testString2 = tempString[position:]
            position -= 1
        if len(testString1):
            textLines.append(testString1.strip())
        else:
            textLines.append(testString2.strip())
            break
        tempString = tempString[position:].strip()
        testString1 = tempString
        position = len(tempString)
    elif testString1 == "":
        # It's done
        break
    else:
        # The text does not exceed the stringWidth
        textLines.append(testString1)
        # testString2 must be checked and if empty then it's done
        if testString2 == "":
            done = True
        else:
           tempString = tempString[position:].strip()
           testString1 = tempString
           position = len(tempString)
print(len(textLines))

I'm open to suggestions to make this code better or more streamlined.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM