{
  "id": "P046",
  "slug": "text-formatter",
  "key": "P046-text-formatter",
  "title": "Text formatter",
  "summary": "A paragraph set at a width from 20 to 60: left, right, centred or justified, wrapped greedily as a terminal does, or balanced by dynamic programming so that the right edge is as even as it can be - with the raggedness of both wrappings shown side by side.",
  "entry": "main.eml",
  "ui": "terminal",
  "readme": "# P046 - Text formatter\n\nA paragraph set at a width from 20 to 60 characters: left, right, centred or\njustified, wrapped greedily as a terminal does, or balanced so that the right\nedge is as even as it can be. The lines are drawn inside a frame of the\nchosen width, and the raggedness of both wrappings is shown so that they can\nbe compared.\n\n- `main.eml` - the menu, the text and the width with their checks, and the\n  framed paragraph on screen\n- `layout.eml` - the two ways of breaking words into lines, the raggedness,\n  and laying a line out\n\nHow each part works:\n\n- Greedy wrapping is the corpus case `word-wrap`: a word goes on the current\n  line if it fits, otherwise it starts the next one.\n- Raggedness is the sum, over every line but the last, of the square of the\n  spaces left at its end; squaring makes one very short line cost more than\n  several slightly short ones.\n- Balanced wrapping finds the breaking with the least raggedness by dynamic\n  programming from the end of the paragraph: the cheapest way to break the\n  words from each position on is the cheapest choice of its first line plus\n  the cheapest way to break the rest. On a tie the longer first line wins.\n  At width 31 the sample takes eight lines either way, with raggedness 89\n  greedily and 51 balanced: balanced wrapping moves \"nor\" to the next line\n  early on, so that no line is left 8 spaces short as \"while it fits;\n  balanced\" is in the greedy version.\n- Justified lines spread the spare spaces over the gaps, the leftmost gaps\n  taking one more when they do not divide evenly; the last line and a line\n  of one word stay left-aligned.\n- A word longer than the width gets a line of its own and sticks out of the\n  frame; the program names it.\n\nWhat is checked: a width from 20 to 60; a typed text of at least one word,\nended by a line holding only a dot. An empty width answer cancels.\n\nSessions: `sessions/basic.in` sets the sample at the starting width of 36,\nthen at 31 - greedy (raggedness 89), balanced (51), justified, right-aligned\nand centred. `sessions/bad-input.in` gives menu choices 0 and x, widths 19,\n61 and x, a text with no words, and a text with a 45-letter word, shown left,\nbalanced and justified, before going back to the sample.\n\nBuilt on the verified corpus case `word-wrap` (greedy wrapping, checked for\nlines over the width and for words lost or added).\n",
  "modules": [
    {
      "name": "main.eml",
      "eml": "# P046 text formatter: a paragraph set at a chosen width - left, right,\n# centred or justified, wrapped greedily as a terminal does, or balanced so\n# that the right edge is as even as it can be. The raggedness of both\n# wrappings is shown, so they can be compared.\nimport layout\n\ndef trim(s):\n    0 => i\n    len(s) => j\n    while i < j and s[i] == \" \":\n        i + 1 => i\n    while j > i and s[j - 1] == \" \":\n        j - 1 => j\n    return s[i:j]\n\ndef number(s):\n    if s == \"\" or len(s) > 2:\n        return -1\n    0 => n\n    for c in s:\n        if not (c in \"0123456789\"):\n            return -1\n        n * 10 + int(c) => n\n    return n\n\ndef lines_text(n):\n    if n == 1:\n        return \"1 line\"\n    return str(n) + \" lines\"\n\ndef show(words, width, how, wrap):\n    if len(words) == 0:\n        \"There is no text yet.\" ^0\n        return 0\n    \"greedy\" => name\n    if wrap == \"balanced\":\n        layout.balanced(words, width) => lines\n        \"balanced\" => name\n    else:\n        layout.greedy(words, width) => lines\n    (\"Width \" + str(width) + \", \" + how + \", wrapped \" + name + \":\") ^0\n    (\"+\" + \"-\" * width + \"+\") ^0\n    [] => long\n    for k in [0:len(lines) - 1]:\n        layout.laid_out(lines[k], width, how, k == len(lines) - 1) => text\n        if len(text) > width:\n            (\"|\" + text) ^0\n            long + [lines[k][0]] => long\n        else:\n            (\"|\" + text + \" \" * (width - len(text)) + \"|\") ^0\n    (\"+\" + \"-\" * width + \"+\") ^0\n    (lines_text(len(lines)) + \"; raggedness \" + str(layout.raggedness(lines, width)) + \" (the squares of the spaces left at the end of every line but the last).\") ^0\n    if wrap == \"balanced\":\n        layout.greedy(words, width) => g\n        (\"Greedy wrapping at this width: \" + lines_text(len(g)) + \", raggedness \" + str(layout.raggedness(g, width)) + \".\") ^0\n    for w in long:\n        (\"The word \" + w + \" is longer than the width, so it sticks out.\") ^0\n    return 0\n\ndef read_text():\n    \"Type the text; it may run over several lines. A line with only a dot ends it.\" ^0\n    \"\" => text\n    while True:\n        input(\"> \") => line\n        if trim(line) == \".\":\n            return layout.words_of(text)\n        text + \" \" + line => text\n\n\"== Text formatter ==\" ^0\n\"Set a paragraph at a width: left, right, centred, justified, or balanced.\" ^0\n\"A line of text is easy to read when it is neither too long nor too short. Greedy wrapping puts each word on the current line while it fits; balanced wrapping looks at the whole paragraph first and evens out the ragged right edge.\" => sample\nlayout.words_of(sample) => words\n36 => width\nTrue => running\nwhile running:\n    \"\" ^0\n    \"1) text  2) sample  3) width  4) left  5) right  6) center  7) justify  8) balanced  9) quit\" ^0\n    trim(input(\"choice> \")) => choice\n    if choice == \"1\":\n        read_text() => typed\n        if len(typed) == 0:\n            \"No words: the text stays as it was.\" ^0\n        else:\n            typed => words\n            (str(len(words)) + \" words.\") ^0\n    elif choice == \"2\":\n        layout.words_of(sample) => words\n        (\"The sample: \" + str(len(words)) + \" words.\") ^0\n    elif choice == \"3\":\n        trim(input(\"width (20 to 60)> \")) => answer\n        number(answer) => w\n        if answer == \"\":\n            \"Cancelled.\" ^0\n        elif w >= 20 and w <= 60:\n            w => width\n            (\"The width is now \" + str(width) + \".\") ^0\n        else:\n            (\"Type a number from 20 to 60; the width stays \" + str(width) + \".\") ^0\n    elif choice == \"4\":\n        show(words, width, \"left\", \"greedy\")\n    elif choice == \"5\":\n        show(words, width, \"right\", \"greedy\")\n    elif choice == \"6\":\n        show(words, width, \"center\", \"greedy\")\n    elif choice == \"7\":\n        show(words, width, \"justify\", \"greedy\")\n    elif choice == \"8\":\n        show(words, width, \"left\", \"balanced\")\n    elif choice == \"9\":\n        False => running\n    else:\n        \"Pick a number from 1 to 9.\" ^0\n\"Bye.\" ^0\n",
      "python": "import layout\n\ndef trim(s):\n    i = 0\n    j = len(s)\n    while i < j and s[i] == \" \":\n        i = i + 1\n    while j > i and s[j - 1] == \" \":\n        j = j - 1\n    return s[i:j]\n\ndef number(s):\n    if s == \"\" or len(s) > 2:\n        return -1\n    n = 0\n    for c in s:\n        if not c in \"0123456789\":\n            return -1\n        n = n * 10 + int(c)\n    return n\n\ndef lines_text(n):\n    if n == 1:\n        return \"1 line\"\n    return str(n) + \" lines\"\n\ndef show(words, width, how, wrap):\n    if len(words) == 0:\n        print(\"There is no text yet.\")\n        return 0\n    name = \"greedy\"\n    if wrap == \"balanced\":\n        lines = layout.balanced(words, width)\n        name = \"balanced\"\n    else:\n        lines = layout.greedy(words, width)\n    print(\"Width \" + str(width) + \", \" + how + \", wrapped \" + name + \":\")\n    print(\"+\" + \"-\" * width + \"+\")\n    long = []\n    for k in range(0, len(lines)):\n        text = layout.laid_out(lines[k], width, how, k == len(lines) - 1)\n        if len(text) > width:\n            print(\"|\" + text)\n            long = long + [lines[k][0]]\n        else:\n            print(\"|\" + text + \" \" * (width - len(text)) + \"|\")\n    print(\"+\" + \"-\" * width + \"+\")\n    print(lines_text(len(lines)) + \"; raggedness \" + str(layout.raggedness(lines, width)) + \" (the squares of the spaces left at the end of every line but the last).\")\n    if wrap == \"balanced\":\n        g = layout.greedy(words, width)\n        print(\"Greedy wrapping at this width: \" + lines_text(len(g)) + \", raggedness \" + str(layout.raggedness(g, width)) + \".\")\n    for w in long:\n        print(\"The word \" + w + \" is longer than the width, so it sticks out.\")\n    return 0\n\ndef read_text():\n    print(\"Type the text; it may run over several lines. A line with only a dot ends it.\")\n    text = \"\"\n    while True:\n        line = input(\"> \")\n        if trim(line) == \".\":\n            return layout.words_of(text)\n        text = text + \" \" + line\n\nprint(\"== Text formatter ==\")\nprint(\"Set a paragraph at a width: left, right, centred, justified, or balanced.\")\nsample = \"A line of text is easy to read when it is neither too long nor too short. Greedy wrapping puts each word on the current line while it fits; balanced wrapping looks at the whole paragraph first and evens out the ragged right edge.\"\nwords = layout.words_of(sample)\nwidth = 36\nrunning = True\nwhile running:\n    print(\"\")\n    print(\"1) text  2) sample  3) width  4) left  5) right  6) center  7) justify  8) balanced  9) quit\")\n    choice = trim(input(\"choice> \"))\n    if choice == \"1\":\n        typed = read_text()\n        if len(typed) == 0:\n            print(\"No words: the text stays as it was.\")\n        else:\n            words = typed\n            print(str(len(words)) + \" words.\")\n    elif choice == \"2\":\n        words = layout.words_of(sample)\n        print(\"The sample: \" + str(len(words)) + \" words.\")\n    elif choice == \"3\":\n        answer = trim(input(\"width (20 to 60)> \"))\n        w = number(answer)\n        if answer == \"\":\n            print(\"Cancelled.\")\n        elif w >= 20 and w <= 60:\n            width = w\n            print(\"The width is now \" + str(width) + \".\")\n        else:\n            print(\"Type a number from 20 to 60; the width stays \" + str(width) + \".\")\n    elif choice == \"4\":\n        show(words, width, \"left\", \"greedy\")\n    elif choice == \"5\":\n        show(words, width, \"right\", \"greedy\")\n    elif choice == \"6\":\n        show(words, width, \"center\", \"greedy\")\n    elif choice == \"7\":\n        show(words, width, \"justify\", \"greedy\")\n    elif choice == \"8\":\n        show(words, width, \"left\", \"balanced\")\n    elif choice == \"9\":\n        running = False\n    else:\n        print(\"Pick a number from 1 to 9.\")\nprint(\"Bye.\")\n"
    },
    {
      "name": "layout.eml",
      "eml": "# P046 text formatter - breaking words into lines and laying the lines out.\n# A breaking is a list of lines, each a list of words.\n\ndef words_of(text):\n    # Words are runs of anything but spaces.\n    [] => out\n    \"\" => word\n    for c in text + \" \":\n        if c == \" \":\n            if word != \"\":\n                out + [word] => out\n            \"\" => word\n        else:\n            word + c => word\n    return out\n\ndef length(line):\n    # The length of the words joined by single spaces.\n    0 => n\n    for w in line:\n        n + len(w) => n\n    return n + len(line) - 1\n\ndef greedy(words, width):\n    # The corpus case word-wrap: a word goes on the current line if it fits,\n    # otherwise it starts the next one. A word longer than the width gets a\n    # line of its own.\n    [] => lines\n    [] => line\n    for w in words:\n        if len(line) == 0:\n            [w] => line\n        elif length(line) + 1 + len(w) <= width:\n            line + [w] => line\n        else:\n            lines + [line] => lines\n            [w] => line\n    if len(line) > 0:\n        lines + [line] => lines\n    return lines\n\ndef gap_cost(words, i, j, width):\n    # The cost of a line holding words i to j (not the last line): the square\n    # of the spaces left at its end; -1 if it does not fit. A single word too\n    # long for the width costs nothing - no breaking can help it.\n    length(words[i:j + 1]) => n\n    if n > width:\n        if i == j:\n            return 0\n        return -1\n    return (width - n) * (width - n)\n\ndef balanced(words, width):\n    # The breaking with the least raggedness - the sum, over every line but\n    # the last, of the square of the spaces left at its end - by dynamic\n    # programming from the end: best[i] is the least cost of breaking words\n    # i onwards. On a tie the longer first line wins.\n    len(words) => n\n    [0] * (n + 1) => best\n    [n] * (n + 1) => cut\n    n - 1 => i\n    while i >= 0:\n        -1 => least\n        i => j\n        True => fitting\n        while j < n and fitting:\n            if j == n - 1 and (length(words[i:n]) <= width or i == j):\n                0 => c\n            else:\n                gap_cost(words, i, j, width) => c\n                if c != -1:\n                    c + best[j + 1] => c\n            if c == -1:\n                False => fitting\n            elif least == -1 or c <= least:\n                c => least\n                j + 1 => cut[i]\n            j + 1 => j\n        least => best[i]\n        i - 1 => i\n    [] => lines\n    0 => i\n    while i < n:\n        lines + [words[i:cut[i]]] => lines\n        cut[i] => i\n    return lines\n\ndef raggedness(lines, width):\n    0 => total\n    for k in [0:len(lines) - 2]:\n        width - length(lines[k]) => left\n        if left > 0:\n            total + left * left => total\n    return total\n\ndef laid_out(line, width, how, last):\n    # One line as text, without the spaces after it: \"left\", \"right\",\n    # \"center\" or \"justify\" (the last line and a one-word line stay left).\n    length(line) => n\n    \"\" => plain\n    for w in line:\n        if plain != \"\":\n            plain + \" \" => plain\n        plain + w => plain\n    if how == \"right\" and n < width:\n        return \" \" * (width - n) + plain\n    if how == \"center\" and n < width:\n        return \" \" * int((width - n) / 2) + plain\n    if how == \"justify\" and not last and len(line) > 1 and n < width:\n        width - n => extra\n        len(line) - 1 => gaps\n        int(extra / gaps) => each\n        extra % gaps => more\n        line[0] => out\n        for k in [1:len(line) - 1]:\n            1 + each => spaces\n            if k <= more:\n                spaces + 1 => spaces\n            out + \" \" * spaces + line[k] => out\n        return out\n    return plain\n",
      "python": "def words_of(text):\n    out = []\n    word = \"\"\n    for c in text + \" \":\n        if c == \" \":\n            if word != \"\":\n                out = out + [word]\n            word = \"\"\n        else:\n            word = word + c\n    return out\n\ndef length(line):\n    n = 0\n    for w in line:\n        n = n + len(w)\n    return n + len(line) - 1\n\ndef greedy(words, width):\n    lines = []\n    line = []\n    for w in words:\n        if len(line) == 0:\n            line = [w]\n        elif length(line) + 1 + len(w) <= width:\n            line = line + [w]\n        else:\n            lines = lines + [line]\n            line = [w]\n    if len(line) > 0:\n        lines = lines + [line]\n    return lines\n\ndef gap_cost(words, i, j, width):\n    n = length(words[i:j + 1])\n    if n > width:\n        if i == j:\n            return 0\n        return -1\n    return (width - n) * (width - n)\n\ndef balanced(words, width):\n    n = len(words)\n    best = [0] * (n + 1)\n    cut = [n] * (n + 1)\n    i = n - 1\n    while i >= 0:\n        least = -1\n        j = i\n        fitting = True\n        while j < n and fitting:\n            if j == n - 1 and (length(words[i:n]) <= width or i == j):\n                c = 0\n            else:\n                c = gap_cost(words, i, j, width)\n                if c != -1:\n                    c = c + best[j + 1]\n            if c == -1:\n                fitting = False\n            elif least == -1 or c <= least:\n                least = c\n                cut[i] = j + 1\n            j = j + 1\n        best[i] = least\n        i = i - 1\n    lines = []\n    i = 0\n    while i < n:\n        lines = lines + [words[i:cut[i]]]\n        i = cut[i]\n    return lines\n\ndef raggedness(lines, width):\n    total = 0\n    for k in range(0, len(lines) - 2+1):\n        left = width - length(lines[k])\n        if left > 0:\n            total = total + left * left\n    return total\n\ndef laid_out(line, width, how, last):\n    n = length(line)\n    plain = \"\"\n    for w in line:\n        if plain != \"\":\n            plain = plain + \" \"\n        plain = plain + w\n    if how == \"right\" and n < width:\n        return \" \" * (width - n) + plain\n    if how == \"center\" and n < width:\n        return \" \" * int((width - n) / 2) + plain\n    if how == \"justify\" and not last and len(line) > 1 and n < width:\n        extra = width - n\n        gaps = len(line) - 1\n        each = int(extra / gaps)\n        more = extra % gaps\n        out = line[0]\n        for k in range(1, len(line)):\n            spaces = 1 + each\n            if k <= more:\n                spaces = spaces + 1\n            out = out + \" \" * spaces + line[k]\n        return out\n    return plain\n"
    }
  ],
  "sessions": [
    {
      "name": "bad-input",
      "input": "0\nx\n3\n19\n3\n61\n3\nx\n3\n\n1\n.\n1\nSome words, then a very long one:\nPneumonoultramicroscopicsilicovolcanoconiosis\nand a few more words after it.\n.\n4\n8\n7\n2\n9\n",
      "screen": "== Text formatter ==\nSet a paragraph at a width: left, right, centred, justified, or balanced.\n\n1) text  2) sample  3) width  4) left  5) right  6) center  7) justify  8) balanced  9) quit\nchoice> 0\nPick a number from 1 to 9.\n\n1) text  2) sample  3) width  4) left  5) right  6) center  7) justify  8) balanced  9) quit\nchoice> x\nPick a number from 1 to 9.\n\n1) text  2) sample  3) width  4) left  5) right  6) center  7) justify  8) balanced  9) quit\nchoice> 3\nwidth (20 to 60)> 19\nType a number from 20 to 60; the width stays 36.\n\n1) text  2) sample  3) width  4) left  5) right  6) center  7) justify  8) balanced  9) quit\nchoice> 3\nwidth (20 to 60)> 61\nType a number from 20 to 60; the width stays 36.\n\n1) text  2) sample  3) width  4) left  5) right  6) center  7) justify  8) balanced  9) quit\nchoice> 3\nwidth (20 to 60)> x\nType a number from 20 to 60; the width stays 36.\n\n1) text  2) sample  3) width  4) left  5) right  6) center  7) justify  8) balanced  9) quit\nchoice> 3\nwidth (20 to 60)> \nCancelled.\n\n1) text  2) sample  3) width  4) left  5) right  6) center  7) justify  8) balanced  9) quit\nchoice> 1\nType the text; it may run over several lines. A line with only a dot ends it.\n> .\nNo words: the text stays as it was.\n\n1) text  2) sample  3) width  4) left  5) right  6) center  7) justify  8) balanced  9) quit\nchoice> 1\nType the text; it may run over several lines. A line with only a dot ends it.\n> Some words, then a very long one:\n> Pneumonoultramicroscopicsilicovolcanoconiosis\n> and a few more words after it.\n> .\n15 words.\n\n1) text  2) sample  3) width  4) left  5) right  6) center  7) justify  8) balanced  9) quit\nchoice> 4\nWidth 36, left, wrapped greedy:\n+------------------------------------+\n|Some words, then a very long one:   |\n|Pneumonoultramicroscopicsilicovolcanoconiosis\n|and a few more words after it.      |\n+------------------------------------+\n3 lines; raggedness 9 (the squares of the spaces left at the end of every line but the last).\nThe word Pneumonoultramicroscopicsilicovolcanoconiosis is longer than the width, so it sticks out.\n\n1) text  2) sample  3) width  4) left  5) right  6) center  7) justify  8) balanced  9) quit\nchoice> 8\nWidth 36, left, wrapped balanced:\n+------------------------------------+\n|Some words, then a very long one:   |\n|Pneumonoultramicroscopicsilicovolcanoconiosis\n|and a few more words after it.      |\n+------------------------------------+\n3 lines; raggedness 9 (the squares of the spaces left at the end of every line but the last).\nGreedy wrapping at this width: 3 lines, raggedness 9.\nThe word Pneumonoultramicroscopicsilicovolcanoconiosis is longer than the width, so it sticks out.\n\n1) text  2) sample  3) width  4) left  5) right  6) center  7) justify  8) balanced  9) quit\nchoice> 7\nWidth 36, justify, wrapped greedy:\n+------------------------------------+\n|Some  words,  then  a very long one:|\n|Pneumonoultramicroscopicsilicovolcanoconiosis\n|and a few more words after it.      |\n+------------------------------------+\n3 lines; raggedness 9 (the squares of the spaces left at the end of every line but the last).\nThe word Pneumonoultramicroscopicsilicovolcanoconiosis is longer than the width, so it sticks out.\n\n1) text  2) sample  3) width  4) left  5) right  6) center  7) justify  8) balanced  9) quit\nchoice> 2\nThe sample: 44 words.\n\n1) text  2) sample  3) width  4) left  5) right  6) center  7) justify  8) balanced  9) quit\nchoice> 9\nBye.\n",
      "interpreter": "equal"
    },
    {
      "name": "basic",
      "input": "4\n3\n31\n4\n8\n7\n5\n6\n9\n",
      "screen": "== Text formatter ==\nSet a paragraph at a width: left, right, centred, justified, or balanced.\n\n1) text  2) sample  3) width  4) left  5) right  6) center  7) justify  8) balanced  9) quit\nchoice> 4\nWidth 36, left, wrapped greedy:\n+------------------------------------+\n|A line of text is easy to read when |\n|it is neither too long nor too      |\n|short. Greedy wrapping puts each    |\n|word on the current line while it   |\n|fits; balanced wrapping looks at the|\n|whole paragraph first and evens out |\n|the ragged right edge.              |\n+------------------------------------+\n7 lines; raggedness 63 (the squares of the spaces left at the end of every line but the last).\n\n1) text  2) sample  3) width  4) left  5) right  6) center  7) justify  8) balanced  9) quit\nchoice> 3\nwidth (20 to 60)> 31\nThe width is now 31.\n\n1) text  2) sample  3) width  4) left  5) right  6) center  7) justify  8) balanced  9) quit\nchoice> 4\nWidth 31, left, wrapped greedy:\n+-------------------------------+\n|A line of text is easy to read |\n|when it is neither too long nor|\n|too short. Greedy wrapping puts|\n|each word on the current line  |\n|while it fits; balanced        |\n|wrapping looks at the whole    |\n|paragraph first and evens out  |\n|the ragged right edge.         |\n+-------------------------------+\n8 lines; raggedness 89 (the squares of the spaces left at the end of every line but the last).\n\n1) text  2) sample  3) width  4) left  5) right  6) center  7) justify  8) balanced  9) quit\nchoice> 8\nWidth 31, left, wrapped balanced:\n+-------------------------------+\n|A line of text is easy to read |\n|when it is neither too long    |\n|nor too short. Greedy wrapping |\n|puts each word on the current  |\n|line while it fits; balanced   |\n|wrapping looks at the whole    |\n|paragraph first and evens out  |\n|the ragged right edge.         |\n+-------------------------------+\n8 lines; raggedness 51 (the squares of the spaces left at the end of every line but the last).\nGreedy wrapping at this width: 8 lines, raggedness 89.\n\n1) text  2) sample  3) width  4) left  5) right  6) center  7) justify  8) balanced  9) quit\nchoice> 7\nWidth 31, justify, wrapped greedy:\n+-------------------------------+\n|A  line of text is easy to read|\n|when it is neither too long nor|\n|too short. Greedy wrapping puts|\n|each  word  on the current line|\n|while    it    fits;   balanced|\n|wrapping  looks  at  the  whole|\n|paragraph  first  and evens out|\n|the ragged right edge.         |\n+-------------------------------+\n8 lines; raggedness 89 (the squares of the spaces left at the end of every line but the last).\n\n1) text  2) sample  3) width  4) left  5) right  6) center  7) justify  8) balanced  9) quit\nchoice> 5\nWidth 31, right, wrapped greedy:\n+-------------------------------+\n| A line of text is easy to read|\n|when it is neither too long nor|\n|too short. Greedy wrapping puts|\n|  each word on the current line|\n|        while it fits; balanced|\n|    wrapping looks at the whole|\n|  paragraph first and evens out|\n|         the ragged right edge.|\n+-------------------------------+\n8 lines; raggedness 89 (the squares of the spaces left at the end of every line but the last).\n\n1) text  2) sample  3) width  4) left  5) right  6) center  7) justify  8) balanced  9) quit\nchoice> 6\nWidth 31, center, wrapped greedy:\n+-------------------------------+\n|A line of text is easy to read |\n|when it is neither too long nor|\n|too short. Greedy wrapping puts|\n| each word on the current line |\n|    while it fits; balanced    |\n|  wrapping looks at the whole  |\n| paragraph first and evens out |\n|    the ragged right edge.     |\n+-------------------------------+\n8 lines; raggedness 89 (the squares of the spaces left at the end of every line but the last).\n\n1) text  2) sample  3) width  4) left  5) right  6) center  7) justify  8) balanced  9) quit\nchoice> 9\nBye.\n",
      "interpreter": "equal"
    }
  ],
  "builtOn": [
    {
      "slug": "word-wrap",
      "caseId": "154-word-wrap",
      "title": "Greedy word wrap"
    }
  ],
  "updated": "2026-10-10"
}
