-1

I have a chunk of text and I want to print it so that every line is exactly n characters in width.

I'm using python's textwrap library but the problem I've had with that is that sometimes the line of text will be less than n characters in length with no whitespace padding.

My code looks like this

from tabulate import tabulate
import textwrap

tabulate.PRESERVE_WHITESPACE = True


s = [long line here]

width = 64

s = textwrap.fill(
    s,
    width,
    replace_whitespace=False,
    break_long_words=True,
    break_on_hyphens=False,
)


print(tabulate([[s]], tablefmt="grid"))

I'm printing out a series of these textboxes and it would look awkward if they were of different width. Depending on what s is sometimes I get a box that is of a width less than width

Is there an option I'm not seeing in the textwrap library or maybe a way I can extend the library to get it to do what I want?

Here are the strings I'm setting s to:

"Meta 335 to 95\n\nAmzn 188 to 96\n\nNflx 700 to 286\n\nSpot 305 to 80\n\nCRM 311 to 160\n\nSnap 57 to 10\n\nAmd 164 to 60\n\nARKK 126 to 39\n\nThese are all stocks off their Nov 2021 highs. Most of the market was poor phrasing, but this is some good perspective (instead of comparing vs the indices)"

"Meta 335 to 95\n\nAmzn 188 to 96\n\nNflx 700 to 286\n\nSpot 305 to 80\n\nCRM 311 to 160\n 57 to 10\n\nAmd 164 to 60\n\nARKK 126 to 39\n\nThese are all stocks off their Nov 2021 highs. Most of the market was poor phrasing, but this is some good perspective (instead of comparing vs the indices)"

"Meta 335Spot0  0nnR 1 o10n5 o1\\Ad14t 0nnRK16t rl tocks off their Nov 2021 highs. Most of the market was poor phrasing, but this is some good perspective (instead of comparing vs the indices)"

If you set s to these strings you'll see that you get tables of different widths.

Jon
  • 5
  • 3
  • Please clarify your specific problem or provide additional details to highlight exactly what you need. As it's currently written, it's hard to tell exactly what you're asking. – Community Nov 02 '22 at 08:52
  • @Community added some clarity. I think if you read the post or try the code with the different strings you'd get it – Jon Nov 02 '22 at 21:01
  • `textwrap` produces lines that do not exceed your length. It does not do justification, which is the process of inserting blanks to fill the space exactly. That's up to you. – Tim Roberts Nov 02 '22 at 21:05
  • @TimRoberts Appreciate the comment, thanks. Is there a library you know of that does something closer to what I want? – Jon Nov 02 '22 at 21:08
  • textwrap is already iterating over the whole string and not doing what I want, seems like it would unnecessarily waste CPU time to iterate over the whole string again. – Jon Nov 02 '22 at 21:11
  • Well, `textwrap` has chopped it up into lines that do not exceed your size. If you use `wrap` instead of `fill`, you'll get a list of lines. Now, for each line, if the line is less than the max, you know how many spaces you need to distribute. Really, you need to think about whether it is worth the trouble. Justified text is rarely pretty on a computer. – Tim Roberts Nov 02 '22 at 21:16
  • Did not realize wrap returns a list of lines! That is much easier to work with. I had actually tried this with wrap but just thought the output was weird, didn't realize it was it was a list of lines. Thanks! I think you might be right about the justified text, but I want to put it all into a nice box haha – Jon Nov 02 '22 at 21:21

1 Answers1

1

See what you think about this. This post-processes textwrap.wrap to do justification:

import textwrap

def wrapme(s):

    width = 64
    lines = s.splitlines()
    result = []
    for l in lines:
        if len(l) < 64:
            result.append(l)
        else:
            breaks = textwrap.wrap( l, width, replace_whitespace=False, break_long_words=True, break_on_hyphens=False )
            for b in breaks[:-1]:
                if len(b) == 64:
                    result.append(b)
                    continue
                insert = 64-len(b)
                words = b.split()
                every = insert // (len(words)-1) + 1
                extra = insert % (len(words)-1)
                for i in range(extra):
                    words[i] += ' '
                result.append( (' '*every).join(words) )
            result.append( breaks[-1] )
    return result


for s in (
"Meta 335 to 95\n\nAmzn 188 to 96\n\nNflx 700 to 286\n\nSpot 305 to 80\n\nCRM 311 to 160\n\nSnap 57 to 10\n\nAmd 164 to 60\n\nARKK 126 to 39\n\nThese are all stocks off their Nov 2021 highs. Most of the market was poor phrasing, but this is some good perspective (instead of comparing vs the indices)"
,
"Meta 335 to 95\n\nAmzn 188 to 96\n\nNflx 700 to 286\n\nSpot 305 to 80\n\nCRM 311 to 160\n 57 to 10\n\nAmd 164 to 60\n\nARKK 126 to 39\n\nThese are all stocks off their Nov 2021 highs. Most of the market was poor phrasing, but this is some good perspective (instead of comparing vs the indices)"
,
"Meta 335Spot0  0nnR 1 o10n5 o1\\Ad14t 0nnRK16t rl tocks off their Nov 2021 highs. Most of the market was poor phrasing, but this is some good perspective (instead of comparing vs the indices)"
):
    for line in wrapme(s):
        print(f"|{line}|")

Output:

|Meta 335 to 95|
||
|Amzn 188 to 96|
||
|Nflx 700 to 286|
||
|Spot 305 to 80|
||
|CRM 311 to 160|
||
|Snap 57 to 10|
||
|Amd 164 to 60|
||
|ARKK 126 to 39|
||
|These  are  all  stocks  off  their  Nov 2021 highs. Most of the|
|market  was  poor  phrasing,  but  this is some good perspective|
|(instead of comparing vs the indices)|
|Meta 335 to 95|
||
|Amzn 188 to 96|
||
|Nflx 700 to 286|
||
|Spot 305 to 80|
||
|CRM 311 to 160|
| 57 to 10|
||
|Amd 164 to 60|
||
|ARKK 126 to 39|
||
|These  are  all  stocks  off  their  Nov 2021 highs. Most of the|
|market  was  poor  phrasing,  but  this is some good perspective|
|(instead of comparing vs the indices)|
|Meta 335Spot0  0nnR 1 o10n5 o1\Ad14t 0nnRK16t rl tocks off their|
|Nov  2021  highs. Most of the market was poor phrasing, but this|
|is some good perspective (instead of comparing vs the indices)|
Tim Roberts
  • 48,973
  • 4
  • 21
  • 30
  • Thanks Tim, I made a few changes and this works perfectly! I changed the contents of the for loop to `width = 64 for line in wrapme(s,width): print("|" + line + " "*(64-len(line)) + "|")` and changed the `wrapme` function to take in a width `def wrapme(s, width)` as well. Sorry I only saw this message about a half hours ago or I would've responded sooner. Thanks again you rock! – Jon Nov 03 '22 at 02:01