سؤال

I've got the following code that attempts to resize the font size of a reportlab Platypus flowable's text until it fits into the available height I have given it. But I'm finding that the Paragraph flowable is not saving its style.fontSize attribute in each loop of the recursion, and python baulks with an infinite recursion.

def fits_space(w,h,aW,aH,p):
    """
    Determines if inputted text fits in the space given for a paragraph
    and if it doesn't, reduces the font-size until it does.

    """
    if w<=aW and h<=aH:
        # return font size to apply it to the para again
        print "final font_size: %d" % p.style.fontSize
        return p # now render the paragraph in the doctemplate
    else: 
        p.style.fontSize -= 1
        w,h = p.wrap(aW, aH)
        fits_space(w,h,aW,aH,p)

def renderPage(name, text):
    doc = SimpleDocTemplate("%s.pdf" % name)
    parts = []
    style = ParagraphStyle(name='fancy')
    style.fontSize = 150
    p = Paragraph(text, style)
    aW = PAGE_WIDTH-4*inch  # available width and height 
    aH = PAGE_HEIGHT-4*inch 
    w,h = p.wrap(aW, aH) # find required space
    p = fits_space(w,h,aW,aH,p) # recursively fit the font size to the text
    parts.append(p)
    doc.build(parts)

Can anyone let me know why - in the fits_space() function, in the else clause, when I call p.wrap(aW, aH), the outputted value is the same as before I decremented the paragraph's fontSize by 1? Surely the wrapped height should be less if I reduce the font size?

Any ideas where I'm going wrong?

UPDATE Nitzie's code below almost worked just needed to add a resizer to the style.leading too in my case:

def shrink_font_size(aW, aH, text, style):
    """Shrinks font size by using pdfmetrics to calculate the height
    of a paragraph, given the font name, size, and available width."""
    def break_lines(text, aW):
        # simpleSplit calculates how reportlab will break up the lines for
        # display in a paragraph, by using width/fontsize.
        return simpleSplit(text, style.fontName, style.fontSize, aW)

    def line_wrap(lines, style):
        # Get overall width of text by getting stringWidth of longest line
        width = stringWidth(max(lines), style.fontName, style.fontSize)
        # Paragraph height can be calculated via line spacing and number of lines.
        height = style.leading * len(lines)
        return width, height

    lines = break_lines(text, aW)
    width, height = line_wrap(lines, style)

    while height > aH or width > aW:
        style.fontSize -= 1
        style.leading -= 1 # do this if you're leading is based on the fontSize
        lines = break_lines(text, aW)
        width, height = line_wrap(lines, style)

TIA

هل كانت مفيدة؟

المحلول

For some reason, p.wrap does not seem to truly recalculate height/width once you've changed the fontsize after calling it once already.

I was able to get a working version by calculating font size based on ReportLab's stringWidth and splitLines methods. Although this means it is no longer recursive:

from reportlab.platypus import SimpleDocTemplate
from reportlab.lib.styles import ParagraphStyle
from reportlab.lib.units import inch
from reportlab.lib.pagesizes import A4
from reportlab.platypus import Paragraph
from reportlab.lib.utils import simpleSplit
from reportlab.pdfbase.pdfmetrics import stringWidth

def shrink_font_size(aW, aH, text, style):
    """Shrinks font size by using pdfmetrics to calculate the height
    of a paragraph, given the font name, size, and available width."""
    def break_lines(text, aW):
        # simpleSplit calculates how reportlab will break up the lines for
        # display in a paragraph, by using width/fontsize.
        return simpleSplit(text, style.fontName, style.fontSize, aW)

    def line_wrap(lines, style):
        # Get overall width of text by getting stringWidth of longest line
        width = stringWidth(max(lines), style.fontName, style.fontSize)
        # Paragraph height can be calculated via line spacing and number of lines.
        height = style.leading * len(lines)
        return width, height

    lines = break_lines(text, aW)
    width, height = line_wrap(lines, style)

    while height > aH or width > aW:
        style.fontSize -= 1
        lines = break_lines(text, aW)
        width, height = line_wrap(lines, style)


def renderPage(name, text):
    doc = SimpleDocTemplate("%s.pdf" % name)
    parts = []
    # Wasn't sure where PAGE_WIDTH and PAGE_HEIGHT came from for OP,
    # so I just used a standard A4 page measurement.
    PAGE_WIDTH, PAGE_HEIGHT = A4
    aW = PAGE_WIDTH - 4*inch  # available width and height 
    aH = PAGE_HEIGHT - 4*inch

    style = ParagraphStyle(name='fancy')
    style.fontSize = 200
    style.leading = 20
    shrink_font_size(aW, aH, text, style)

    p = Paragraph(text, style)
    parts.append(p)
    doc.build(parts)

if __name__ == "__main__":
    renderPage('test', '12345689019283382848248284 842828428529392381472754 842828428529392381472754 842828428529392381472754\n' * 10)
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top