├── LICENSE ├── README.md ├── examples ├── article │ ├── browser-assist.js │ ├── browser.js │ ├── index.html │ ├── main.css │ └── typeset-with-ratio.png └── flatland │ ├── Figure-1.png │ ├── Figure-2.png │ ├── flatland-single.css │ └── index.html ├── lib ├── en-us.js ├── hypher.js └── jquery-1.4.2.js ├── src ├── formatter.js ├── linebreak.js └── linked-list.js ├── typeset-browser.png ├── typeset-centered.png ├── typeset-circle.png ├── typeset-flow.png ├── typeset-right.png ├── typeset-triangle.png └── typeset-with-ratio.png /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2009-2010, Bram Stein All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 8 | 9 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 10 | 11 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## TeX line breaking algorithm in JavaScript 2 | 3 | This is an implementation of the [Knuth and Plass line breaking algorithm](http://www3.interscience.wiley.com/journal/113445055/abstract) using JavaScript. The goal of this project is to optimally set justified text in the browser, and ultimately provide a library for various line breaking algorithms in JavaScript. 4 | 5 | The paragraph below is set using a JavaScript implementation of the classic Knuth and Plass algorithm as used in TeX. The numbers on the right of each line are the stretching or shrinking ratio compared to the optimal line width. This example uses a default space of 1/3 em, with a stretchability and shrink-ability of 1/6 em and 1/9 em respectively. 6 | 7 | ![](https://github.com/bramstein/typeset/raw/master/typeset-with-ratio.png) 8 | 9 | The following paragraph is set by a browser using `text-align: justify`. Notice the lines in the paragraph have, on average, greater inter-word spacing than the Knuth and Plass version, which is successful at minimizing the inter-word spacing over all lines. 10 | 11 | ![](https://github.com/bramstein/typeset/raw/master/typeset-browser.png) 12 | 13 | The browser also ends up with ten lines instead of the nine lines found by the Knuth and Plass line breaking algorithm. This comparison might not be completely fair since we don't know the default inter-word space used by the browser (nor its stretching and shrinking parameters.) Experimental results however indicate the values used in most browsers are either identical or very similar. The next section explains how the ratio values for the browser were calculated. 14 | 15 | ### Measuring the quality of browser line breaks 16 | 17 | Unfortunately there is no API to retrieve the positions of the line breaks the browser inserted, so we'll have to resort to some trickery. By wrapping each word in an invisible `` element and retrieving its `y` position we can find out when a new line starts. If the `y` position of the current word is different from the previous word we know a new line has started. This way a paragraph is split up in several individual lines. 18 | 19 | The ratios are then calculated by measuring the difference between the width of each line when `text-align` is set to `justify` and when it is set to `left`. This difference is then divided by the amount of stretchability of the line: i.e. the number of spaces multiplied by the stretch width for spaces. Although we don't know the actual stretchability we can use 1/6 em, just like the Knuth and Plass algorithm, if we only use it for comparison. 20 | 21 | ### Assisted browser line breaks 22 | 23 | The line breaking algorithm can also be used to correct the line breaks made by the browser. The easiest way to do is to split a text up into lines and adjust the CSS `word-spacing` property. Unfortunately, Webkit based browsers do not support sub-pixel word-spacing. Alternatively, we can absolute position each word or split the line into segmants with integer word spacing. You can see the latter approach in action on the [Flatland line breaking example.](examples/flatland/) 24 | 25 | ### Examples 26 | 27 | The line breaking algorithm is not only capable of justifying text, it can perform all sorts of alignment with an appropriate selection of boxes, glue and penalties. It is also possible to give it varying line widths to flow text around illustrations, asides or quotes. Alternatively, varying line widths can be used to create interesting text shapes as demonstrated below. 28 | 29 | #### Ragged right and centered alignment 30 | 31 | The following example is set ragged right. Ragged right is not simply justified text with fixed width inter-word spacing. Instead the algorithm tries to minimize the amount of white space at the end of each sentence over the whole paragraph. It also attempts to reduce the number of words that are "sticking out" of the margin. 32 | 33 | ![](https://github.com/bramstein/typeset/raw/master/typeset-right.png) 34 | 35 | Ragged left text can be achieved by using a ragged right text and aligning its line endings with the left border. The example below is set centered. Again this is not simply a centering of justified text, but instead an attempt at minimizing the line lengths over the whole paragraph. 36 | 37 | ![](https://github.com/bramstein/typeset/raw/master/typeset-centered.png) 38 | 39 | #### Variable line width 40 | 41 | By varying the line width for a paragraph it is possible to flow the text around illustrations, asides, quotes and such. The example below leaves a gap for an illustration by setting the line widths temporarily shorter and then reverting. You can also see that the algorithm chose to hyphenate certain words to achieve acceptable line breaking. 42 | 43 | ![](https://github.com/bramstein/typeset/raw/master/typeset-flow.png) 44 | 45 | It is also possible to make some non-rectangular shapes, as shown in the examples below. In the first example, the text is laid out using an increasing line width and center aligning each line. This creates a triangular shape. 46 | 47 | ![](https://github.com/bramstein/typeset/raw/master/typeset-triangle.png) 48 | 49 | Using some basic math it is also possible to set text in circles or even arbitrary polygons. Below is an example of text set inside a circle. 50 | 51 | ![](https://github.com/bramstein/typeset/raw/master/typeset-circle.png) 52 | 53 | ### To-do 54 | 55 | The following are some extensions to the algorithm discussed in the original paper, which I intend to implement (at some point.) 56 | 57 | * [Hanging punctuation](http://en.wikipedia.org/wiki/Hanging_punctuation). The following quote from the original paper explains how to implement it using the box, glue and penalty model: 58 | 59 | > Some people prefer to have the right edge of their text look ‘solid’, 60 | > by setting periods, commas, and other punctuation marks (including 61 | > inserted hyphens) in the right-hand margin. For example, this practice 62 | > is occasionally used in contemporary advertising. 63 | 64 | > It is easy to get inserted hyphens into the margin: We simply let the 65 | > width of the corresponding penalty item be zero. And it is almost as 66 | > easy to do the same for periods and other symbols, by putting every such 67 | > character in a box of width zero and adding the actual symbol width to 68 | > the glue that follows. If no break occurs at this glue, the accumulated 69 | > width is the same as before; and if a break does occur, the line will be 70 | > justified as if the period or other symbol were not present. 71 | 72 | * Compare quality against line-breaking implemented by [Internet Explorer's `text-justify` CSS property](http://msdn.microsoft.com/en-us/library/ms534671%28VS.85%29.aspx). 73 | * Figure out how to deal with dynamic paragraphs (i.e. paragraphs being edited) as their ratios will change during editing and thus visibly move around. 74 | 75 | ### References 76 | 77 | These are the resources I found most useful while implementing the line breaking algorithm. 78 | 79 | * [Digital Typography, Donald E. Knuth](http://www.amazon.com/Digital-Typography-Center-Language-Information/dp/1575860104/) 80 | * [Breaking paragraphs into lines, Donald E. Knuth, Michael F. Plass](http://www3.interscience.wiley.com/journal/113445055/abstract) 81 | * [Knuth & Plass line-breaking Revisited](http://defoe.sourceforge.net/folio/knuth-plass.html) 82 | * [Wikipedia: Word wrap](http://en.wikipedia.org/w/index.php?title=Word_wrap) 83 | -------------------------------------------------------------------------------- /examples/article/browser-assist.js: -------------------------------------------------------------------------------- 1 | jQuery(function ($) { 2 | var ruler = $('
').css({ 3 | visibility: 'hidden', 4 | position: 'absolute', 5 | top: '-8000px', 6 | width: 'auto', 7 | display: 'inline', 8 | left: '-8000px' 9 | }), 10 | format; 11 | 12 | $('body').append(ruler); 13 | 14 | format = Typeset.formatter(function (str) { 15 | if (str !== ' ') { 16 | return ruler.text(str).width(); 17 | } else { 18 | return ruler.html(' ').width(); 19 | } 20 | }); 21 | 22 | function browserAssistTypeset(identifier, text, type, lineLengths, tolerance) { 23 | var nodes = format[type](text), 24 | breaks = Typeset.linebreak(nodes, lineLengths, {tolerance: tolerance}), 25 | lines = [], 26 | i, point, r, lineStart, 27 | browserAssist = $(identifier).after(''), 28 | browserAssistRatio = $(identifier + ' + ul'); 29 | 30 | // Iterate through the line breaks, and split the nodes at the 31 | // correct point. 32 | for (i = 1; i < breaks.length; i += 1) { 33 | point = breaks[i].position, 34 | r = breaks[i].ratio; 35 | 36 | for (var j = lineStart; j < nodes.length; j += 1) { 37 | // After a line break, we skip any nodes unless they are boxes or forced breaks. 38 | if (nodes[j].type === 'box' || (nodes[j].type === 'penalty' && nodes[j].penalty === -Typeset.linebreak.infinity)) { 39 | lineStart = j; 40 | break; 41 | } 42 | } 43 | lines.push({ratio: r, nodes: nodes.slice(lineStart, point + 1), position: point}); 44 | lineStart = point; 45 | } 46 | 47 | lines = lines.map(function (line) { 48 | var spaceShrink = 1 / 9 * 12, 49 | spaceStretch = 1 / 6 * 12, 50 | ratio = line.ratio * (line.ratio < 0 ? spaceShrink : spaceStretch); 51 | 52 | var output = '' + line.nodes.filter(function (n) { 53 | return n.type === 'box'; 54 | }).map(function (n) { 55 | return n.value; 56 | }).join(' ') + ''; 57 | browserAssist.append(output); 58 | browserAssistRatio.append('
  • ' + line.ratio.toFixed(3) + '
  • '); 59 | }); 60 | } 61 | browserAssistTypeset('#browser-assist', text, 'justify', [350], 3); 62 | }); 63 | -------------------------------------------------------------------------------- /examples/article/browser.js: -------------------------------------------------------------------------------- 1 | jQuery(function ($) { 2 | function browserTypeset() { 3 | var original = $('#browser'), 4 | width = original.width(), 5 | copy = original.clone(), 6 | text = copy.text(), 7 | lines = [], 8 | ratios = [], 9 | words = text.split(/\s/), 10 | position = 0, 11 | stretchWidth = 0, 12 | spaceStretch = 0, 13 | html = []; 14 | 15 | $('body').append(copy); 16 | 17 | // This piece of code calculates the positions of the line breaks added 18 | // by the browser by adding an invisible wrapper element to each word 19 | // and checking when its y-position changes. 20 | words.forEach(function (word, index) { 21 | var html = words.slice(0, index), 22 | currentPosition = 0; 23 | 24 | html.push('' + word + ''); 25 | Array.prototype.push.apply(html, words.slice(index + 1, words.length)); 26 | 27 | copy.html(html.join(' ')); 28 | 29 | currentPosition = copy.find('span').position().top; 30 | 31 | if (currentPosition != position) { 32 | lines.push([]); 33 | position = currentPosition; 34 | } 35 | 36 | lines[lines.length - 1].push(word); 37 | }); 38 | 39 | lines = lines.map(function (line) { 40 | return line.join(' '); 41 | }); 42 | 43 | 44 | // We measure the width if the text is not justified and only a 45 | // single line (i.e. the optimal line length.) 46 | copy.empty(); 47 | copy.css({width: 'auto', display: 'inline', textAlign: 'left'}); 48 | 49 | // This works under the assumption that a space is 1/3 of an em, and 50 | // the stretch value is 1/6. Although the actual browser value may be 51 | // different, the assumption is valid as it is only used to compare 52 | // to the ratios calculated earlier. 53 | stretchWidth = copy.html(' ').width() / 2; 54 | 55 | lines.forEach(function (line, index) { 56 | // This conditional is to ensure we don't calculate the ratio 57 | // for the last line as it is not justified. 58 | if (index !== lines.length - 1) { 59 | copy.text(line); 60 | ratios.push((width - copy.width()) / ((line.split(/\s/).length - 1) * stretchWidth)); 61 | } else { 62 | ratios.push(0); 63 | } 64 | }); 65 | 66 | copy.remove(); 67 | 68 | 69 | html.push(''); 76 | 77 | $('#browser').parent().append(html.join('')); 78 | } 79 | 80 | $('#browser').text(text); 81 | browserTypeset(); 82 | }); 83 | -------------------------------------------------------------------------------- /examples/article/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | TypeSet 6 | 7 | 37 | 38 | 39 |

    TeX line breaking algorithm in JavaScript

    40 |

    Bram Stein (http://www.bramstein.com - b.l.stein@gmail.com)

    41 | 42 |

    This is an implementation of the Knuth and Plass line breaking algorithm using JavaScript and the HTML5 canvas element. The goal of this implementation is to optimally set justified text in the new HTML5 canvas element, and ultimately provide a library for various line breaking algorithms in JavaScript.

    43 |

    The paragraph below is set using a JavaScript implementation of the classic Knuth and Plass algorithm as used in TeX. The numbers on the right of each line are the stretching or shrinking ratio compared to the optimal line width. This example uses a default space of 13 em, with a stretchability and shrink-ability of 16 em and 19 em respectively.

    44 |
    45 |

    The following paragraph is set by your browser using text-align: justify. Notice the lines in the paragraph set by your browser have, on average, greater inter-word spacing than the canvas version, which is successful at minimizing the inter-word spacing over all lines.

    46 |
    47 |

    Your browser also ends up with ten lines instead of the nine lines found by the Knuth and Plass line breaking algorithm. This comparison might not be completely fair since we don't know the default inter-word space used by the browser (nor its stretching and shrinking parameters.) Experimental results however indicate the values used in most browsers are either identical or very similar. The next section explains how the ratio values for your browser were calculated.

    48 | 49 |

    Measuring the quality of browser line breaks

    50 |

    Unfortunately there is no API to retrieve the positions of the line breaks the browser inserted, so we'll have to resort to some trickery. By wrapping each word in an invisible <span> element and retrieving its y position we can find out when a new line starts. If the y position of the current word is different from the previous word we know a new line has started. This way a paragraph is split up in several individual lines.

    51 | 52 |

    The ratios are then calculated by measuring the difference between the width of each line when text-align is set to justify and when it is set to left. This difference is then divided by the amount of stretchability of the line: i.e. the number of spaces multiplied by the stretch width for spaces. Although we don't know the actual stretchability we can use 16 em, just like the Knuth and Plass algorithm, if we only use it for comparison.

    53 | 54 | 55 |

    Assisted browser line breaks

    56 |

    The following paragraph is set according to the line breaks found by the Knuth and Plass algorithm, but instead of using the HTML5 Canvas element it is rendered by your browser. By adjusting the CSS word-spacing property we have achieved the same paragraph as in the Canvas example.

    57 |
    58 | 59 |

    Examples

    60 |

    The line breaking algorithm is not only capable of justifying text, it can perform all sorts of alignment with an appropriate selection of boxes, glue and penalties. It is also possible to give it varying line widths to flow text around illustrations, asides or quotes. Alternatively, varying line widths can be used to create interesting text shapes as demonstrated below.

    61 |

    Ragged right and centered alignment

    62 |

    The following example is set ragged right. Ragged right is not simply justified text with fixed width inter-word spacing. Instead the algorithm tries to minimize the amount of white space at the end of each sentence over the whole paragraph. It also attempts to reduce the number of words that are "sticking out" of the margin. It is very noticeable here that hyphenation could improve the ragged right setting greatly by, for example, hyphenating the word "lime-tree".

    63 | 64 |

    Ragged left text can be achieved by using a ragged right text and aligning its line endings with the left border. The example below is set centered. Again this is not simply a centering of justified text, but instead an attempt at minimizing the line lengths over the whole paragraph.

    65 | 66 |

    Variable line width

    67 |

    By varying the line width for a paragraph it is possible to flow the text around illustrations, asides, quotes and such. The example below leaves a gap for an illustration by setting the line widths temporarily shorter and then reverting.

    68 | 69 |

    It is also possible to make some non-rectangular shapes, as shown in the example below. This text is laid out using an increasing line width and center aligning each line.

    70 | 71 | 72 | 73 |

    To-do

    74 |

    The following are some extensions to the algorithm discussed in the original paper, which I intend to implement (at some point.)

    75 | 92 | 93 |

    Source code

    94 | 98 |

    References

    99 |

    These are the resources I found most useful while implementing the line breaking algorithm.

    100 | 106 | 107 |   108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 207 | 208 | 209 | 213 | 220 | 221 | 222 | -------------------------------------------------------------------------------- /examples/article/main.css: -------------------------------------------------------------------------------- 1 | /** 2 | * TeX logo typesetting courtesy of Edward O'Conner (http://edward.oconnor.cx/2007/08/tex-poshlet) 3 | */ 4 | .tex sub { 5 | text-transform: uppercase; 6 | vertical-align: -0.5ex; 7 | margin-left: -0.1667em; 8 | margin-right: -0.125em; 9 | } 10 | .tex sub { 11 | font-size: 1em; 12 | } 13 | 14 | body { 15 | font-size:100%; 16 | line-height:1.5; 17 | font-family: sans-serif; 18 | width: 50%; 19 | margin-left: auto; 20 | margin-right: auto; 21 | background-color: #F9F9F4; 22 | } 23 | 24 | canvas { 25 | margin-left: auto; 26 | margin-right: auto; 27 | display: block; 28 | margin: 1.25em auto; 29 | } 30 | 31 | h1, h2, h3 { 32 | margin-top:0; 33 | margin-bottom:0; 34 | font-weight: normal; 35 | } 36 | h1 { 37 | font-size: 1.5em; 38 | padding: 1.45833em 0 0; 39 | text-align: center; 40 | font-family: serif; 41 | } 42 | h2 { 43 | font-size: 1.33333em; 44 | padding: 1.25em 0 0; 45 | } 46 | h3 { 47 | font-size: 1.16667em; 48 | padding: 1.51785em 0 0; 49 | text-transform: lowercase; 50 | font-variant: small-caps; 51 | font-weight: bold; 52 | } 53 | 54 | p sub, p sup { 55 | font-size: 0.7em; 56 | } 57 | 58 | p sup { 59 | vertical-align: 1ex; 60 | } 61 | 62 | p sub { 63 | vertical-align: -0.5ex; 64 | } 65 | 66 | code { 67 | font-size: 1.1em; 68 | } 69 | 70 | p { 71 | } 72 | 73 | p.subtitle { 74 | padding:0; 75 | margin: 0; 76 | font-family: sans-serif; 77 | text-align: center; 78 | margin-bottom: 2em; 79 | font-size: 0.8em; 80 | } 81 | 82 | blockquote { 83 | font-style: italic; 84 | } 85 | -------------------------------------------------------------------------------- /examples/article/typeset-with-ratio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bramstein/typeset/619fadd8b66db6b32ec810a2a51035e772042223/examples/article/typeset-with-ratio.png -------------------------------------------------------------------------------- /examples/flatland/Figure-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bramstein/typeset/619fadd8b66db6b32ec810a2a51035e772042223/examples/flatland/Figure-1.png -------------------------------------------------------------------------------- /examples/flatland/Figure-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bramstein/typeset/619fadd8b66db6b32ec810a2a51035e772042223/examples/flatland/Figure-2.png -------------------------------------------------------------------------------- /examples/flatland/flatland-single.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-size: 100%; 3 | line-height: 1.45em; 4 | background-color: rgb(180, 180, 180); 5 | padding: 0; 6 | margin: 0; 7 | font-family: "Minion Pro", Gentium, "Gentium Basic", "Hoefler Text", Constantia, "Palatino Linotype", Georgia, serif; 8 | } 9 | 10 | #info { 11 | font-family: Verdana, Arial, sans-serif; 12 | font-size: 0.85em; 13 | right: 0; 14 | position: absolute; 15 | background-color: rgb(40, 40, 40); 16 | color: rgb(240, 240, 240); 17 | padding: 1.5em 2.5em; 18 | margin: 1em; 19 | width: 15em; 20 | line-height: 1.4em; 21 | } 22 | 23 | #info h1 { 24 | font-size: 1em; 25 | font-weight: bold; 26 | } 27 | 28 | #info p { 29 | font-size: 0.88em; 30 | line-height: 1.4em; 31 | } 32 | 33 | #info p a { 34 | color: rgb(240, 0, 0); 35 | } 36 | 37 | #typeset h1 { 38 | font-size: 1.5em; 39 | font-style: italic; 40 | text-align: center; 41 | font-weight: normal; 42 | text-transform: uppercase; 43 | } 44 | #typeset h2 { 45 | font-size: 1.33333em; 46 | text-align: center; 47 | font-weight: normal; 48 | } 49 | 50 | #typeset h3 { 51 | font-size: 1.16667em; 52 | text-align: center; 53 | font-weight: normal; 54 | margin-top: 2em; 55 | } 56 | 57 | #typeset h3 span { 58 | font-style: italic; 59 | } 60 | 61 | #typeset { 62 | width: 34em; 63 | padding: 8em; 64 | padding-top: 2.5em; 65 | background-color: white; 66 | margin-left: auto; 67 | margin-right: auto; 68 | } 69 | 70 | #typeset hr { 71 | margin-top: 2em; 72 | width: 6em; 73 | } 74 | 75 | #typeset img { 76 | float: right; 77 | margin: 1.5em 0em 1.5em 1.5em; 78 | } 79 | 80 | #typeset p { 81 | padding: 0; 82 | margin: 0; 83 | /* outline: 1px solid red;*/ 84 | text-align: left; 85 | } -------------------------------------------------------------------------------- /examples/flatland/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Flatland 6 | 7 | 8 | 9 |
    10 |

    TeX Hyphenation & Justification on the Web

    11 |

    This text has been set using Typeset as an implementation of the Knuth and Plass line breaking algorithm. Uses Hypher for hyphenation. Text and images are from Edwin Abbott's Flatland.

    12 |
    13 |
    14 |
    15 |

    Flatland.

    16 |
    17 |

    PART I.

    18 |

    THIS WORLD.

    19 |

    § I. — Of the Nature of Flatland.

    20 | 21 |

    I call our world Flatland, not because we call it so, but to make its nature clearer to you, my happy readers, who are privileged to live in Space.

    22 | 23 |

    Imagine a vast sheet of paper on which straight Lines, Triangles, Squares, Pentagons, Hexagons, and other figures, instead of remaining fixed in their places, move freely about, on or in the surface, but without the power of rising above or sinking below it, very much like shadows — only hard and with luminous edges — and you will then have a pretty correct notion of my country and countrymen. Alas, a few years ago, I should have said "my universe;" but now my mind has been opened to higher views of things.

    24 | 25 |

    In such a country, you will perceive at once that it is impossible that there should be anything of what you call a "solid" kind; but I dare say you will suppose that we could at least distinguish by sight the Triangles, Squares, and other figures, moving about as I have described them. On the contrary, we could see nothing of the kind, not at least so as to distinguish one figure from another. Nothing was visible, nor could be visible, to us, except Straight Lines; and the necessity of this I will speedily demonstrate.

    26 | 27 |

    Place a penny on the middle of one of your tables in Space; and leaning over it, look down upon it. It will appear a circle.

    28 | 29 |

    But now, drawing back to the edge of the table, gradually lower your eye (thus bringing yourself more and more into the condition of the inhabitants of Flatland), and you will find the penny becoming more and more oval to your view; and at last when you have placed your eye exactly on the edge of the table (so that you are, as it were, actually a Flatlander) the penny will then have ceased to appear oval at all, and will have become, so far as you can see, a straight line.

    30 | 31 |

    The same thing would happen if you were to treat in the same way a Triangle, or Square, or any other figure cut out of pasteboard. As soon as you look at it with your eye on the edge on the table, you will find that it ceases to appear to you a figure, and that it becomes in appearance a straight line. Take for example an equilateral Triangle — who represents with us a Tradesman of the respectable class. Fig. 1 represents the Tradesman as you would see him while you were bending over him from above; figs. 2 and 3 represent the Tradesman, as you would see him if your eye were close to the level, or all but on the level of the table; and if your eye were quite on the level of the table (and that is how we see him in Flatland) you would see nothing but a straight line.

    32 | 33 | 34 | views of a triangle 35 | 36 |

    When I was in Spaceland I heard that your sailors have very similar experiences while they traverse your seas and discern some distant island or coast lying on the horizon. The far-off land may have bays, forelands, angles in and out to any number and extent; yet at a distance you see none of these (unless indeed your sun shines bright upon them revealing the projections and retirements by means of light and shade), nothing but a grey unbroken line upon the water.

    37 | 38 |

    Well, that is just what we see when one of our triangular or other acquaintances comes toward us in Flatland. As there is neither sun with us, nor any light of such a kind as to make shadows, we have none of the helps to the sight that you have in Spaceland. If our friend comes closer to us we see his line becomes larger; if he leaves us it becomes smaller: but still he looks like a straight line; be he a Triangle, Square, Pentagon, Hexagon, Circle, what you will — a straight Line he looks and nothing else. You may perhaps ask how under these disadvantageous circumstances we are able to distinguish our friends from one another: but the answer to this very natural question will be more fitly and easily given when I come to describe the inhabitants of Flatland. For the present let me defer this subject, and say a word or two about the climate and houses in our country.

    39 |
    40 |
    41 |

    § 2. — Of the Climate and Houses in Flatland

    42 | 43 |

    As with you, so also with us, there are four points of the compass North, South, East, and West.

    44 | 45 |

    There being no sun nor other heavenly bodies, it is impossible for us to determine the North in the usual way; but we have a method of our own. By a Law of Nature with us, there is a constant attraction to the South; and, although in temperate climates this is very slight — so that even a Woman in reasonable health can journey several furlongs northward without much difficulty — yet the hampering effect of the southward attraction is quite sufficient to serve as a compass in most parts of our earth. Moreover, the rain (which falls at stated intervals) coming always from the North, is an additional assistance; and in the towns we have the guidance of the houses, which of course have their side-walls running for The most part North and South, so that the roofs may keep off the rain from the North. In the country, where there are no houses, the trunks of the trees serve as some sort of guide. Altogether, we have not so much difficulty as might be expected in determining our bearings.

    46 | 47 |

    Yet in our more temperate regions, in which the southward attraction is hardly felt, walking sometimes in a perfectly desolate plain where there have been no houses nor trees to guide me, I have been occasionally compelled to remain stationary for hours together, waiting till the rain came before continuing my journey. On the weak and aged, and especially on delicate Females, the force of attraction tells much more heavily than on the robust of the Male Sex, so that it is a point of breeding, if you meet a Lady in the street, always to give her the North side of the way — by no means an easy thing to do always at short notice when you are in rude health and in a climate where it is difficult to tell your North from your South.

    48 | 49 |

    Windows there are none in our houses: for the light comes to us alike in our homes and out of them, by day and by night, equally at all times and in all places, whence we know not. It was in old days, with our learned men, an interesting and oft-investigated question, "What is the origin of light?" and the solution of it has been repeatedly attempted, with no other result than to crowd our lunatic asylums with the would-be solvers. Hence, after fruitless attempts to suppress such investigations indirectly by making them liable to a heavy tax, the Legislature, in comparatively recent times, absolutely prohibited them. I — alas; I alone in Flatland — know now only too well the true solution of this mysterious problem; but my knowledge cannot be made intelligible to a single one of my countrymen; and I am mocked at — I, the sole possessor of the truths of Space and of the theory of the introduction of Light from the world of three Dimensions — as if I were the maddest of the mad! But a truce to these painful digressions: let me return to our houses.

    50 | 51 |

    The most common form for the construction of a house is five-sided or pentagonal, as in the annexed figure. The two Northern sides RO, OF, constitute the roof, and for the most part have no doors; on the East is a small door for the Women; on the West a much larger one for the Men; the South side or floor is usually doorless.

    52 | 53 | Pentagonal house 54 | 55 |

    Square and triangular houses are not allowed, and for this reason. The angles of a Square (and still more those of an equilateral Triangle,) being much more pointed than those of a Pentagon, and the lines of inanimate objects (such as houses) being dimmer than the lines of Men and Women, it follows that there is no little danger lest the points of a square or triangular house residence might do serious injury to an inconsiderate or perhaps absent-minded traveller suddenly therefore, running against them: and as early as the eleventh century of our era, triangular houses were universally forbidden by Law, the only exceptions being fortifications, powder-magazines, barracks, and other state buildings, which it is not desirable that the general public should approach without circumspection.

    56 | 57 |

    At this period, square houses were still everywhere permitted, though discouraged by a special tax. But, about three centuries afterwards, the Law decided that in all towns containing a population above ten thousand, the angle of a Pentagon was the smallest house-angle that could be allowed consistently with the public safety. The good sense of the community has seconded the efforts of the Legislature; and now, even in the country, the pentagonal construction has superseded every other. It is only now and then in some very remote and backward agricultural district that an antiquarian may still discover a square house.

    58 |
    59 |
    60 | 61 | 62 | 63 | 64 | 65 | 263 | 264 | 265 | -------------------------------------------------------------------------------- /lib/en-us.js: -------------------------------------------------------------------------------- 1 | // The en-US hyphenation patterns are retrieved from 2 | // http://tug_org/svn/texhyphen/trunk/collaboration/repository/hyphenator/ 3 | Hypher.en = { 4 | leftmin : 2, 5 | rightmin : 2, 6 | patterns : { 7 | 3 : "x1qei2e1je1f1to2tlou2w3c1tue1q4tvtw41tyo1q4tz4tcd2yd1wd1v1du1ta4eu1pas4y1droo2d1psw24sv1dod1m1fad1j1su4fdo2n4fh1fi4fm4fn1fopd42ft3fu1fy1ga2sss1ru5jd5cd1bg3bgd44uk2ok1cyo5jgl2g1m4pf4pg1gog3p1gr1soc1qgs2oi2g3w1gysk21coc5nh1bck1h1fh1h4hk1zo1ci4zms2hh1w2ch5zl2idc3c2us2igi3hi3j4ik1cab1vsa22btr1w4bp2io2ipu3u4irbk4b1j1va2ze2bf4oar1p4nz4zbi1u2iv4iy5ja1jeza1y1wk1bk3fkh4k1ikk4k1lk1mk5tk1w2ldr1mn1t2lfr1lr3j4ljl1l2lm2lp4ltn1rrh4v4yn1q1ly1maw1brg2r1fwi24ao2mhw4kr1cw5p4mkm1m1mo4wtwy4x1ar1ba2nn5mx1ex1h4mtx3i1muqu2p3wx3o4mwa1jx3p1naai2x1ua2fxx4y1ba2dn1jy1cn3fpr2y1dy1i", 8 | 4 : "4dryn2itni4on1inn1im_up3nik4ni4dy5giye4tyes4_ye44ab_nhe4nha4abe2n2gyn1guy1ery5eep2pe4abry3lay3lone4wne4v1nesy3chn1erne2q3neo1nenp2seps4hy2cey5lu2nedne2cyme44nk2y5at2adine2b2ne_y5ac2p1tp2ten1den1cun1cryn5dp2th4adup4twpub3ae4rxu3ayn5gaff4pue4n2au4p1ppuf4n2atag1ipu4mag1na2gon4asx3tix1t2pu2na4gya3haa3heah4la3ho_ti2a5ian2an5puspu2tnak4_th2n1kl_te4_ta4mu4u4mupmun23mum2alex4ob_sy25ynxal1i_st4y1o4xi5cxi5a4alm_si2_sh2m5sixhu4m4sh4m3r4amam2py2rabm2pixhi2yo5dr2ai4m1pmo2vmos2x2edmo2r4n1la2mor2asx3c2xas5yom4x4apxam3nme44mokrbi2nne44andy4osp4ot3noemn4omn4a4m1n4nog4m1l2angws4l1posw3shwri4wra4yp3iwom11wo2m2izrb4ow4nopo4pr2cem2isrd2iano4mig4y3pomi3awiz55mi_no4n4m1fme4v2re_wir42mes1menme2mme2gre1o2med4me_4nop4m5c4m1bwil21noureu2whi4w3ev4maprev2w1era2plpo4crfu4r4fyy5pu2maha3pu2mab2a2rn1p4npi44lyb4lya2p3nwam42l1w1lut4luplu3or1glluf4lu5a2wacltu2y3rol1tr4vv4r3guyr4rl1te4rh_nru4ar1il2sel4sc4l1rl5prl4plys4c4lovri3ar4ib4lof3lo_ar2par3q_os3ll4oll2i4as_ri1o3vokl2levoi44p1mlka35vo_ns4cas4ll1izr4iqr2is3vivl1it3lika2tan2sen2slrle42l3hlgo3l5gal5frns3mvi4p3ley_od2r2meles24athr4myle2al3drv1inldi4l2de2vilnt2il3civik4lce42l1b4lavv3ifrno4r3nua1trr2ocnt4sy4sok4syks4la2tuk4sck3ouko5ryss4a2tyau4b4klyys1tnu1akis4au3rki4pro4ek4ima2va5ki_nu4dn4umn3uokes4k1erav1irok2ke4g1keek2ed_me2aw3ikal4aws4k5agk3ab3ka_aye4ays4veg3jo4p5ba_4vedjew3n1v24ve_ja4pzar23vatizi4n1w41batba4z2b1bb2beix4o4i5w4b1d4be_rox5nym4nyp4n3za4ittr3por1r4i1ti1bel2ith2itei2su4rs2r1sars4cr2seis1p3betvag4i2sor1shbe3wr1sioad34b3hbi2bbi4d3bie3isf4ise2is_1bilr1sp5va_r5sw_le2uz4eir1ibi2tuxu3r1tiu1v2i1raze4nze4pb2l2uu4mo1biip3iz1eripe4b4louts44b1m4b3no3br3bodi4osbo4eru3aio4mi1ol4io_3booo1ce4inyin1u2insru2n2inn4inl4inkrv4e2inioch42iner3vo4indpi2np4idbt4lb4tob3trry4cry3t2in_o4elbu4ni2muim1i5saiil3v4ilnil1iil5fs1apo3er4b5w5by_bys4_in1sau4i1lazet4u2suo3ev2z1ii2go4igius1p5saw4s5bo2fi4ifti3fl4if_i3etsch2usc22ie4i2dui4dri2diid5dpi3au3ruz4ils1cuz4is4s5d4se_se4a2ce_2ici4ich3ceii1bri5bo1ceni1blse2g5seiibe43cepi2aniam4ur2li2al2i1acet4hy2scew41phy4ch_5phuhu4thu4gche2h4tyh4shur1durc44hr44h5p5sev5sexu1ra4s3fup3p2s3gph3t2sh_ho4g2h1n_he23ciau3pl4h1mci5ch2lozo4m4ciihi2vhi4p2cim2cin4phsu1peu1ouo1geu5osheu4sho4he4th1es4shwun5zun5ysi1bunu45cizo4glck3ihep5he2nh4ed1sioph2l5hazsi2rcly4zte4_ge21siscoe22cog5siu1siv5siz_ga24skes1l2s2leha4m2s1ms3ma1ogyo1h2u1ni3gus3gun2guegu4acov1gth3_eu3g4ros1n4_es3u2nez4zyum2pu1mi3som_ev2oig4cri2gov15goos4opgon2ul5v5goeu3lugob53go_2c1t4ph_g1nog1nic2te4sov4ulsgn4ag4myc4twcud5c4ufc4uipe2t3glo1gleul2igla4_eg23giz3cun5givgi4u3gir5gio1cusul4e2spagil4g1ic5gi__eb4cze41d2a5da_u1laggo44daf2dagg2gege4v1geo1gen2ged3dato1la2ge_ol2dol2i5daypek4p4eed1d42de_4gazol2tuiv3ol2vo2lys1sa2gamgaf4o2meui4n2ui2pe2cd4em4fugi4jku3fl3ufaf2tyf4to1denu4du4pe_2f3sfri2de1ps1si4f5pfos5d3eqs4sls4snfo2rss2tdes25fon4p1b_ci23payss5w2st_de1tf4l2de1v2fin4dey4d1fd4gast2idg1id2gyd1h25di_ud5dfi3au4cy_ch4pav43didu3cud1iff2fyu3crd1inst4r4f1ffev4fer11dio2fedfe4bdir2s2ty4fe_dis1on1au3ca4f5bon1c2ondd5k25far4fagpa1peys45eyc1exps4ul2dlyp4ale3whon3s3do_e1wa5doee5vud4oge1visu2msu2nub4euav4su2rp4ai6rk_d4or3dosu1atdo4v3doxp4adoo4k4swoo2padre4eus4e3upe5un2ophet5z4syc3syl4y3hoy1ads4pd4swd4syd2tho4wo3ta_du2c4etn2tabta2luac4es4wdu4g2ess4uabdu4n4duptav4st5bow1io1pr5dyn2tawe1sp2t1bop1uead1tz4et4chopy5ea4l4t1d4te_2tyle1si4esh1tee4tyat1cr4twoteg4es2c4eru1teoer1s2eroea2tte4po1rat1wh3tusea2v3teu3texer1i2e1ber1h4tey2t1f4t1ge3br2th_th2e4thle1ce3tumec2i2ths2erb1tia4tueer1aou5vtud2tif22tige1potu1aou4lttu41timt5toos4le1cre2pat4swe5owe1cue4ottsh4eos4e1ort4sce3ol4edieo2ge5of1tio4eno4enn5tiq4edoti4u1tive3my1tiz4othee2ct5laee2ft5lo4t1mee2mtme4e1meem5bcoi4to3be5exo1ry2tof1effel2iel2ftos24t1pe1la1traos2ceig2ei5de5ico2soe1h45egyeg5n", 9 | 5 : "_ach4e4go_e4goseg1ule5gurtre5feg4iceher4eg5ibeger44egaltre4mei5gle3imbe3infe1ingtra3beir4deit3eei3the5ity5triae4jud3efiteki4nek4la2trime4la_e4lactri4v4toute4law5toure3leaefil45elece4ledto2rae5len4tonye1lestro3ve4fic4tonoto3mytom4bto2mato5ice5limto2gre3lioe2listru5i4todo4ellaee4tyello4e5locel5ogeest4el2shel4tae5ludel5uge4mace4mage5man2t1n2ee2s4ee4p1e2mele4metee4naemi4eee4lyeel3i3tled3tle_e4mistlan4eed3iem3iztrus4emo4gti3zaem3pie4mule4dulemu3ne4dritiv4aedon2e4dolti3tle5neae5neeen3emtis4pti5sotis4m3tisee3newti3sae5niee5nile3nioedi5zen3ite5niu5enized1ited3imeno4ge4nosen3oven4swti5oc4t1s2en3uaen5ufe3ny_4en3zed3ibe3diae4oi4ede4s3tini4ed3deo3ret2ina2e2dae4culeo4toe5outec4te4t3t2t4tes2t1ine5pel4timpe2corephe4e4plie2col5tigutu3arti5fytu4bie3pro3tienep4sh5tidie4putt4icoeci4t4tick2ti2bec3imera4bti4aber3ar4tuf45tu3ier4bler3che4cib2ere_4thooecca54thil3thet4thea3turethan4e4cade4bitere4qe4ben5turieret4tur5oeav5oeav5itu5ry4tess4tes_ter5ve1rio4eriter4iueri4v1terier3m4ter3cte5pe4t1waer3noeast3er5obe5rocero4rer1oue3assea5sp1tent4ertler3twtwis4eru4t3tende1s4a3tenc5telsear2te2scateli4e3scres5cue1s2ee2sec3tel_te5giear5kear4cte5diear3ae3sha2t1ede5ande2sice2sid5tecttece44teattype3ty5phesi4uea4gees4mie2sole3acte2sone1a4bdys5pdy4sedu4petaun4d3uleta5sytas4e4tare4tarctar4ata5pl2estrta5mo4talke2surtal3idu5eleta4bta5lae3teoua5naet1ic4taf4etin4ta5doe5tir4taciuan4id1ucad1u1ae3trae3tre2d1s2syn5ouar2d4drowet3uaet5ymdro4pdril4dri4b5dreneu3rouar3ieute44draieu5truar3te2vasdop4pe5veadoo3ddoni4u4belsum3iev1erdoli4do4laev3idevi4le4vinevi4ve5voc2d5ofdo5dee4wage5wee4d1n4ewil54d5lue3wit2d3lou3ber5eye_u1b4i3dledfa3blfab3rfa4ce3dle_fain4suit3su5issu2g34d5lasu4b3fa3tasu1al4fato1di1vd2iti5disiuci4bfeas4di1redi4pl4feca5fectdio5gfe3life4mofen2d4st3wuc4it5ferr5diniucle3f4fesf4fie4stry1dinaf4flydi3ge3dictd4icedia5bs4tops1tle5stirs3tifs4ties1ticfic4is5tias4ti_4ficsfi3cuud3ers3thefil5iste2w4filyudev45finas4tedfi2nes2talfin4ns2tagde2tode4suflin4u1dicf2ly5ud5isu5ditde1scd2es_der5sfon4tu4don5dermss4lid4erhfor4is4siede2pudepi4fra4tf5reade3pade3nufril4frol5ud4side3nou4eneuens4ug5infu5el5dem_s5setfu5nefu3rifusi4fus4s4futade5lode5if4dee_5gal_3galiga3lo2d1eds3selg5amos2s5cssas3u1ing4ganouir4mgass4gath3uita4deaf5dav5e5dav44dato4darygeez44spotspor4s4pon4gelydark5s4ply4spio4geno4genydard5ge3omg4ery5gesigeth54getoge4tydan3g4g1g2da2m2g3gergglu5dach4gh3inspil4gh4to4cutr1gi4agia5rula5bspho5g4icogien5s2pheulch42sperspa4n5spai3c4utu1lenul4gigir4lg3islcu5pycu3picu4mic3umecu2maso5vi5glasu5liagli4bg3lig5culiglo3r4ul3mctu4ru1l4og4na_c3terul1tig2ning4nio4ultug4noncta4b4c3s2cru4dul5ulsor5dgo3isum5absor5ccris4go3nic4rinson4gsona45gos_cri5fcre4vum4bi5credg4raigran25solvsoft3so4ceunat44graygre4nco5zi4gritcoz5egruf4cow5ag5stecove4cos4es5menun4ersmel44corbco4pl4gu4tco3pacon5tsman3gy5racon3ghach4hae4mhae4th5aguha3lac4onecon4aun4ims3latu2ninhan4gs3ket5colocol5ihan4kuni3vhap3lhap5ttras4co4grhar2dco5agsir5aclim45sionhas5shaun44clichaz3acle4m1head3hearun3s4s3ingun4sws2ina2s1in4silysil4eh5elohem4p4clarhena45sidiheo5r1c4l4h4eras5icc2c1itu4orsh3ernshor4h3eryci3phshon34cipecion45cinoc1ingc4inahi5anhi4cohigh5h4il2shiv5h4ina3ship3cilihir4lhi3rohir4phir4rsh3iohis4ssh1inci4lau5pia4h1l4hlan44cier5shevcia5rhmet4ch4tish1erh5ods3cho2hoge4chi2z3chitho4mahome3hon4aho5ny3hoodhoon45chiouptu44ura_ho5ruhos4esew4ihos1p1housu4ragses5tu4rasur4behree5se5shs1e4s4h1s24chedh4tarht1enht5esur4fru3rifser4os4erlhun4tsen5gur1inu3riosen4dhy3pehy3phu1ritces5tur3iz4cesa4sencur4no4iancian3i4semeia5peiass45selv5selfi4atu3centse1le4ceniib5iaib3inseg3ruros43cencib3li3cell5cel_s5edli5bun4icam5icap4icar4s4ed3secticas5i4cayiccu44iceour4pe4ced_i5cidsea5wi2cipseas4i4clyur4pi4i1cr5icrai4cryic4teictu2ccon4urti4ic4umic5uoi3curcci4ai4daiccha5ca4thscof4ide4s4casys4cliscle5i5dieid3ios4choid1itid5iui3dlei4domid3owu5sadu5sanid5uous4apied4ecany4ield3s4cesien4ei5enn4sceii1er_i3esci1estus3ciuse5as4cedscav5if4frsca4pi3fieu5siau3siccan4eiga5bcan5d4calous5sli3gibig3ilig3inig3iti4g4lus1trig3orig5oti5greigu5iig1ur2c5ah4i5i44cag4cach4ca1blusur4sat3usa5tab5utoi3legil1erilev4uta4b4butail3iail2ibil3io3sanc2ilitil2izsal4t5bustil3oqil4tyil5uru3tati4magsa5losal4m4ute_4imetbu3res3act5sack2s1ab4imitim4nii3mon4utelbumi4bu3libu4ga4inav4utenbsor42b5s2u4tis4briti3neervi4vr3vic4inga4inger3vey4ingir3ven4ingo4inguu4t1li5ni_i4niain3ioin1isbo4tor5uscrunk5both5b5ota5bos42i1no5boriino4si4not5borein3seru3in2int_ru4glbor5di5nusut5of5bor_uto5gioge4io2grbon4au5tonru3enu4touion3iio5phior3ibod3iio5thi5otiio4toi4ourbne5gb3lisrt4shblen4ip4icr3triip3uli3quar4tivr3tigrti4db4le_b5itzira4bi4racird5ert5ibi4refbi3tri4resir5gibi5ourte5oir4isr3tebr4tagbin4diro4gvac3uir5ul2b3ifis5agis3arisas52is1cis3chbi4eris3erbi5enrson3be5yor5shais3ibisi4di5sisbe3tw4is4krs3es4ismsbe5trr3secva4geis2piis4py4is1sbe3sp4bes4be5nuval5ois1teis1tirrys4rros44be5mis5us4ita_rron4i4tagrri4vi3tani3tatbe3lorri4or4reoit4esbe1libe5gu4itiarre4frre4cbe3giit3igbe3dii2tim2itio4itisrp4h4r3pet4itonr4peait5rybe3debe3dai5tudit3ul4itz_4be2dbeat3beak4ro4varo4tyros4sro5roiv5ioiv1itror3i5root1roomval1ub3berva5mo4izarva5piron4eban3ijac4qban4ebal1ajer5srom4prom4iba4geazz5i5judgay5alax4idax4ickais4aw4ly3awaya1vorav5ocav3igke5liv3el_ve4lov4elyro1feke4tyv4erdv4e2sa5vanav3ag5k2ick4illkilo5au1thk4in_4ves_ro3crkin4gve4teaun5dk5ishau4l2au3gu4kleyaugh3ve4tyk5nes1k2noat3ulkosh4at5uekro5n4k1s2at5uaat4that5te5vianat4sk5vidil4abolaci4l4adela3dylag4nlam3o3landrob3la4tosr4noular4glar3ilas4ea4topr3nivr3nita2tomr5nica4toglbin44l1c2vi5gnat3ifat1ica5tiar3neyr5net4ati_ld5isat4hol4driv2incle4bileft55leg_5leggr4nerr3nel4len_3lencr4nar1lentle3phle4prvin5dler4e3lergr3mitl4eroat5evr4mio5lesq3lessr3menl3eva4vingrma5cvio3lvi1ou4leyevi5rovi3so4l1g4vi3sulgar3l4gesate5cat5apli4agli2amr3lo4li4asr4lisli5bir4ligr2led4lics4vitil4icul3icyl3idaat5ac3lidirk4lel4iffli4flr3ket3lighvit3r4vityriv3iri2tulim3ili4moris4pl4inar3ishris4clin3ir4is_li5og4l4iqlis4pas1trl2it_as4shas5phri2pla4socask3ia3sicl3kallka4ta3sibl4lawashi4l5leal3lecl3legl3lel5riphas4abar2shrin4grin4ear4sarin4dr2inal5lowarre4l5met3rimol4modlmon42l1n2a3roorim5ilo4civo4la5rigil5ogo3loguri5et5longlon4iri1erlood5r4icolop3il3opmlora44ricir4icerib3a5los_v5oleri4agria4blos4tlo4taar2mi2loutar2izar3iolpa5bl3phal5phi4rhall3pit5voltar4im3volv2l1s2vom5ivori4l4siear4fllt5agar4fivo4rylten4vo4talth3ia3reeltis4ar4drw5ablrgo4naraw4lu3brluch4lu3cilu3enwag5olu5idlu4ma5lumia5raur5gitwait5luo3rw5al_luss4r5gisar4atl5venrgi4nara3pwar4tar3alwas4tly5mely3no2lys4l5ysewa1teaque5ma2car3gicma4clr3get5magnwed4nmaid54maldrg3erweet3wee5vwel4lapoc5re4whwest3ap3in4aphires2tr4es_mar3vre5rumas4emas1t5matemath3rero4r4eriap5atr1er4m5bilre1pumbi4vapar4a5nuran3ul4med_an3uare5lure1lian4twre5itmel4tan2trre4fy4antomen4are3fire2fe4menemen4imens4re1de3ment2r2edme5onre4awwin4g5reavme4tare3anme1tere1alm4etr3wiserdin4rdi4aan4stwith3an2span4snan2samid4amid4gan5otwl4esr4dalm4illmin4a3mindrcum3rc4itr3charcen4min4tm4inumiot4wl3ina3niumis5lan3ita3nip4mithan3ioan1gla3neuws4per2bina3nena5neem4ninw5s4tan1dl4mocrrbi4fmo2d1mo4gomois2xac5ex4agor4bagmo3mer4baba3narrau4ta5monrare4rar5cra5nor4aniam1inr2amiam5ifra4lomo3spmoth3m5ouf3mousam3icxer4ixe5roraf4tr5aclm3petra3bixhil5mpi4aam3ag3quetm5pirmp5is3quer2que_qua5vmpov5mp4tram5ab3alyz4m1s25alyt4alysa4ly_ali4exi5di5multx4ime4aldia4laral3adal5abak1enain5opu3trn4abu4nac_na4can5act5putexpe3dna4lia4i4n4naltai5lya3ic_pur4rag5ulnank4nar3c4narenar3inar4ln5arm3agognas4c4ag4l4ageupul3cage4oaga4na4gab3nautnav4e4n1b4ncar5ad5umn3chaa3ducptu4rpti3mnc1innc4itad4suad3owad4len4dain5dana5diua3ditndi4ba3dion1ditn3dizn5ducndu4rnd2we3yar4n3eara3dianeb3uac4um5neckac3ulp4siba3cio5negene4laac1inne5mine4moa3cie4nene4a2cine4poyc5erac1er2p1s2pro1tn2erepro3lner4rych4e2nes_4nesp2nest4neswpri4sycom4n5evea4carab3uln4gabn3gelpre3vpre3rycot4ng5han3gibng1inn5gitn4glangov4ng5shabi5an4gumy4erf4n1h4a5bannhab3a5bal3n4iani3anni4apni3bani4bl_us5ani5dini4erni2fip3petn5igr_ure3_un3up3per_un5op3pennin4g_un5k5nis_p5pel_un1en4ithp4ped_un1ani3tr_to4pympa3_til4n3ketnk3inyn5ic_se2ny4o5gy4onsnmet44n1n2_ru4d5pounnni4vnob4lpo4tan5ocly4ped_ro4qyper5noge4pos1s_ri4gpo4ry1p4or_res2no4mono3my_ree2po4ninon5ipoin2y4poc5po4gpo5em5pod_4noscnos4enos5tno5tayp2ta3noun_ra4cnowl3_pi2tyra5m_pi4eyr5ia_out3_oth32n1s2ns5ab_or3t_or1d_or3cplu4mnsid1nsig4y3s2eys3ion4socns4pen5spiploi4_odd5nta4bpli4n_ni4cn5tib4plignti2fpli3a3plannti4p1p2l23ysis2p3k2ys3ta_mis1nu5enpi2tun3uinp3ithysur4nu1men5umi3nu4nyt3icnu3trz5a2b_li4t_li3o_li2n_li4g_lev1_lep5_len4pion4oard3oas4e3pi1ooat5ip4inoo5barobe4l_la4mo2binpind4_ju3rob3ul_is4i_ir5rp4in_ocif3o4cil_in3so4codpi3lopi3enocre33piec5pidipi3dep5ida_in2kod3icodi3oo2do4odor3pi4cypian4_ine2o5engze3rooe4ta_im3m_id4l_hov5_hi3b_het3_hes3_go4r_gi4bpho4ro5geoo4gero3gie3phobog3it_gi5azo5ol3phizo4groogu5i4z1z22ogyn_fes3ohab5_eye55phieph1icoiff4_en3sph4ero3ing_en3go5ism_to2qans3v_el5d_eer4bbi4to3kenok5iebio5mo4lanper1v4chs_old1eol3erpe5ruo3letol4fi_du4co3liaper3op4ernp4erio5lilpe5ono5liop4encpe4la_do4tpee4do5livcin2q3pediolo4rol5pld3tabol3ub3pedeol3uno5lusedg1le1loaom5ahoma5l2p2edom2beom4bl_de3o3fich3pe4ao4met_co4ro3mia_co3ek3shao5midom1inll1fll3teapa2teo4monom3pi3pare_ca4tlue1pon4aco3nanm2an_pa4pum2en_on5doo3nenng1hoon4guon1ico3nioon1iso5niupa3nypan4ao3nou_bri2pain4ra1oronsu4rk1hopac4tpa4ceon5umonva5_ber4ood5eood5i6rks_oop3io3ordoost5rz1scope5dop1erpa4ca_ba4g_awn4_av4i_au1down5io3pito5pon1sync_as1s_as1p_as3ctch1c_ar5so5ra_ow3elo3visov4enore5auea1mor3eioun2d_ant4orew4or4guou5etou3blo5rilor1ino1rio_ang4o3riuor2miorn2eo5rofoto5sor5pe3orrhor4seo3tisorst4o3tif_an5cor4tyo5rum_al3tos3al_af1tos4ceo4teso4tano5scros2taos4poos4paz2z3wosi4ue3pai", 10 | 6 : "os3ityos3itoz3ian_os4i4ey1stroos5tilos5titxquis3_am5atot3er_ot5erso3scopor3thyweek1noth3i4ot3ic_ot5icao3ticeor3thiors5enor3ougor3ityor3icaouch5i4o5ria_ani5mv1ativore5sho5realus2er__an3teover3sov4erttot3icoviti4o5v4olow3dero4r3agow5esto4posiop3ingo5phero5phanthy3sc3operaontif5on3t4ionten45paganp3agattele2gonspi4on3omyon4odipan3elpan4tyon3keyon5est3oncil_ar4tyswimm6par5diompro5par5elp4a4ripar4isomo4gepa5terst5scrpa5thy_atom5sta1tio5miniom3icaom3ic_ss3hatsky1scpear4lom3ena_ba5naol3umer1veilpedia4ped4icolli4er1treuo5liteol3ishpeli4epe4nano5lis_pen4thol3ingp4era_r1thoup4erago3li4f_bas4er1krauperme5ol5id_o3liceper3tio3lescolass4oi3terpe5tenpe5tiz_be5raoi5son_be3smphar5iphe3nooi5letph4es_oi3deroic3esph5ingr3ial_3ognizo5g2ly1o1gis3phone5phonio5geneo4gatora3mour2amenofit4tof5itera3chupi4ciepoly1eod5dedo5cureoc3ula1pole_5ocritpee2v1param4oc3raco4clamo3chetob5ingob3a3boast5eoke1st3nu3itpi5thanuf4fentu3meoerst2o3chasplas5tn3tinepli5ernti4ernter3sntre1pn4s3esplum4bnsati4npre4cns4moonon1eqnor5abpo3et5n5lessn5oniz5pointpoly5tnon4agnk3rup3nomicng1sprno5l4inois5i4n3o2dno3blenni3aln5keroppa5ran3itor3nitionis4ta5nine_ni3miznd3thrmu2dron3geripray4e5precipre5copre3emm3ma1bpre4lan5gerep3rese3press_can5cmedi2c5pri4e_ce4la3neticpris3op3rocal3chain4er5ipros3en4erarnera5bnel5iz_cit5rne4gatn5d2ifpt5a4bjanu3aign4itn3chisn5chiln5cheon4ces_nau3seid4iosna3talnas5tinan4itnanci4na5mitna5liahnau3zput3er2n1a2bhex2a3hatch1multi3hair1sm4pousg1utanmpo3rim4p1inmp5iesmphas4rach4empar5iraf5figriev1mpara5mo5seyram3et4mora_rane5oran4gemo3ny_monol4rap3er3raphymo3nizgno5morar5ef4raril1g2nacg1leadmoni3ara5vairav3elra5ziemon5gemon5etght1wemoi5sege3o1dmma5ryr5bine3fluoren1dixmis4ti_de3ra_de3rie3chasrch4err4ci4bm4inglm5ineedu2al_3miliame3tryrdi4er_des4crd3ingdi2rerme5thimet3alre5arr3mestim5ersadi2rende2ticdes3icre4cremen4temensu5re3disred5itre4facmen4dede2mosmen5acmem1o3reg3ismel5onm5e5dyme3died2d5ibren4te5mediare5pindd5a5bdata1bmba4t5cle4arma3tisma5scemar4lyre4spichs3huma5riz_dumb5re3strre4terbrus4qre3tribio1rhre5utiman3izre4valrev3elbi1orbbe2vie_eas3ire5vilba1thyman5is5maniamal4tymal4lima5linma3ligmag5inav3ioul5vet4rg3inglus3teanti1dl5umn_ltur3a_el3emltera4ltane5lp5ingloun5dans5gra2cabllos5etlor5ouric5aslo5rie_enam35ricidri4cie5lope_rid5erri3encri3ent_semi5lom3errig5an3logicril3iz5rimanlob5allm3ingrim4pell5out5rina__er4ril5linal2lin4l3le4tl3le4nriph5eliv3er_ge5og_han5k_hi3er_hon3olin3ea1l4inel4im4p_idol3_in3ci_la4cy_lath5rit3iclim4blrit5urriv5elriv3et4l4i4lli4gra_leg5elif3errk4linlid5er4lict_li4cor5licioli4atorl5ish_lig5a_mal5o_man5a_mer3c5less_rm5ersrm3ingy3thinle5sco3l4erilera5b5lene__mon3ele4matld4erild4erela4v4ar1nis44lativ_mo3rola5tanlan4telan5etlan4dllab3ic_mu5takin4dek3est_ro5filk3en4dro5ker5role__of5te4jestyys3icaron4al5izont_os4tlron4tai4v3ot_pe5tero3pelrop3ici5voreiv5il__pio5n_pre3mro4the_ran4tiv3en_rov5eliv3ellit3uati4tramr5pentrp5er__rit5ui4tismrp3ingit5ill_ros5tit3ica4i2tici5terirre4stit3era4ita5mita4bi_row5dist4lyis4ta_is4sesrsa5tiissen4is4sal_sci3erse4crrs5er_islan4rse5v2yo5netish5opis3honr4si4bis5han5iron_ir4minrtach4_self5iri3turten4diri5dei4rel4ire4de_sell5r4tieriq3uidrtil3irtil4lr4tilyr4tistiq5uefip4re4_sing4_ting4yn3chrru3e4lion3at2in4th_tin5krum3pli4no4cin3ityrun4ty_ton4aruti5nymbol5rvel4i_top5irv5er_r5vestin5geni5ness_tou5s_un3cein3cerincel45ryngei4n3auim3ulai5miniimi5lesac3riim5ida_ve5rasalar4ima5ryim3ageill5abil4istsan4deila5rai2l5am_wil5ii4ladeil3a4bsa5voright3iig3eraab5erd4ific_iff5enif5eroi3entiien5a45ie5gaidi5ou3s4cieab5latidi4arid5ianide3al4scopyab5rogid5ancic3ulaac5ardi2c5ocic3ipaic5inase2c3oi4carai4car_se4d4ei2b5riib5iteib5it_ib5ertib3eraac5aroi4ativ4ian4tse4molsen5ata5ceouh4warts5enedhus3t4s5enin4sentd4sentlsep3a34s1er_hun5kehu4min4servohro3poa5chethov5el5se5umhouse3sev3enho5senhort3eho5rishor5at3hol4ehol5arh5odizhlo3riac5robhis3elhion4ehimer4het4edsh5oldhe2s5ph5eroushort5here5aher4bahera3p3side_5sideshen5atsi5diz4signahel4lyact5ifhe3l4ihe5do55sine_h5ecathe4canad4dinsion5aad5er_har4lehard3e3sitioha5rasha3ranhan4tead3icahang5oadi4ersk5inesk5ing5hand_han4cyhan4cislith5hala3mh3ab4lsmall32g5y3n5gui5t3guard5smithad5ranaeri4eag5ellag3onia5guerso4labsol3d2so3licain5in4grada3s4on_gor5ougo5rizgondo5xpan4dait5ens5ophyal3end3g4o4ggnet4tglad5i5g4insgin5ge3g4in_spen4d2s5peog3imen5gies_3spher5giciagh5outsp5ingge5nizge4natge5lizge5lisgel4inxi5miz4gativgar5n4a5le5oga3nizgan5isga5mets5sengs4ses_fu4minfres5cfort5assi4erss5ilyfore5tfor5ayfo5ratal4ia_fon4dessur5aflo3ref5lessfis4tif1in3gstam4i5stands4ta4p5stat_fin2d5al5levs5tero4allicstew5afight5fi5del5ficie5ficiafi3cer5stickf3icena5log_st3ingf3icanama5ra5stockstom3a5stone2f3ic_3storef2f5iss4tradam5ascs4trays4tridf5fin_fend5efeath3fault5fa3thefar5thfam5is4fa4mafall5eew3inge5verbeven4ie5vengevel3oev3ellev5asteva2p5euti5let5roset3roget5rifsy5rinet3ricet5onaam5eraam5ilyami4noamor5ieti4noe5tidetai5loethod3eten4dtal5enes5urramp5enan3ageta5loge5strotan4detanta3ta5pere3ston4es2toes5times3tigta3rizestan43analy4taticta4tures4prean3arces3pertax4ises5onaes3olue5skintch5etanar4ies4i4ntead4ie2s5ima3natiande4sesh5enan3disan4dowang5iete5geres5ences5ecres5cana4n1icte2ma2tem3at3tenanwrita45erwau4tenesert3era3nieser3set5erniz4erniter4nis5ter3de4rivaan3i3fter3isan4imewo5vener3ineeri4ere3rient3ess_teth5e5ericke1ria4er3ester5esser3ent4erenea5nimier5enaer3emoth3easthe5atthe3iser5el_th5ic_th5icaere3in5thinkere5coth5odea5ninee3realan3ishan4klier4che5anniz4erandti4atoanoth5equi3lep5utat4ic1uan4scoe4probep3rehe4predans3poe4precan4surantal4e3penttim5ulep5anceo5rol3tine_eop3aran4tiewin4deap5eroen3ishen5icsen3etren5esten5esien5eroa3pheren3dicap3itae4nanten5amoem5ulaa3pituti3zen5emnize5missem5ishap5olaem5ine3tles_t5let_em1in2apor5iem3icaem5anael3op_el4labapos3te3liv3el5ishaps5esweath3e3lierel3icaar3actwa5verto3nate3libee4l1erel3egato3rietor5iza5radeelaxa4aran4gto3warelan4dej5udie5insttra5chtraci4ar5av4wa5gere5git5arbal4ar5easeg5ing4voteetrem5iar3enta5ressar5ial4tricsvor5abe3finetro5mitron5i4tronyar3iantro3sp5eficia3rieted5uloed3icae4d1erec3ulaec4tane4cremeco5roec3orae4concar5o5de4comme4cluse4clame5citeec5ifya5ronias3anta5sia_tu4nis2t3up_ecan5ce4belstur3ise4bel_eav3ene4a3tue5atifeath3ieat5eneart3eear4ilear4icear5eseam3ereal3oueal5erea5geread5iedum4be4ducts4duct_duc5eras3tenasur5adrea5rat3abl4d5outdo3natdom5izdo5lor4dlessu4bero3dles_at3alou3ble_d4is3tdirt5idi5niz3dine_at5ech5di3endi4cam1d4i3ad3ge4tud5estdev3ilde3strud3iedud3iesdes3tide2s5oat3egovis3itde4nardemor5at3en_uen4teuer4ilde5milat3eraugh3en3demicater5nuil5izdeli4ede5comde4cildecan4de4bonv3io4rdeb5it4dativ2d3a4bat3estu5laticu4tie5ulcheul3dercuss4icu5riaath5em3cultua5thenul3ingul5ishul4lar4vi4naul4liscu5ityctim3ic4ticuuls5esc5tantultra3ct5angcros4ecrop5ocro4pl5critiath5omum4blycre3at5vilitumor5oat5i5b5crat_cras5tcoro3ncop3iccom5ercol3orun5ishco3inc5clareat3ituunt3abat5ropun4tescit3iz4cisti4cista4cipicc5ing_cin3em3cinatuper5s5videsup3ingci2a5b5chini5videdupt5ib5vide_at4tag4ch1inch3ersch3er_ch5ene3chemiche5loure5atur4fercheap3vi5aliat3uravet3er4ch3abc5e4taau5sib3cessives4tece5ram2cen4e4cedenccou3turs5erur5tesur3theaut5enur4tiecav5al4cativave4nover3thcar5omca5percan4tycan3izcan5iscan4icus4lin3versecal4laver3ieca3latca5dencab3in3butiobuss4ebus5iebunt4iv4eresuten4i4u1t2iv3erenu3tineut3ingv4erelbroth35u5tizbound34b1orabon5at5vere_bom4bibol3icblun4t5blespblath5av3erav5enuebi3ogrbi5netven3om2v1a4bvac5ilbi3lizbet5izbe5strva5liebe5nigbbi4nabas4siva5nizbari4aav5ernbarbi5av5eryvel3liazi4eravi4er", 11 | 7 : "_dri5v4ban5dagvar5iedbina5r43bi3tio3bit5ua_ad4derution5auti5lizver5encbuf4ferus5terevermi4ncall5incast5ercas5tigccompa5z3o1phros5itiv5chanicuri4fico5stati5chine_y5che3dupport54v3iden5cific_un4ter_at5omiz4oscopiotele4g5craticu4m3ingv3i3liz4c3retaul4li4bcul4tiscur5a4b4c5utiva5ternauiv4er_del5i5qdem5ic_de4monsdenti5fdern5izdi4latou4b5ingdrag5on5drupliuar5ant5a5si4tec5essawo4k1enec5ifiee4compear5inate4f3eretro5phewide5sp5triciatri5cesefor5ese4fuse_oth5esiar5dinear4chantra5ventrac4tetrac4itar5ativa5ratioel5ativor5est_ar5adisel5ebraton4alie4l5ic_wea5rieel5igibe4l3ingto5cratem5igraem3i3niemoni5oench4erwave1g4a4pillavoice1ption5eewill5inent5age4enthesvaude3vtill5inep5recaep5ti5bva6guer4erati_tho5rizthor5it5thodicer5ence5ternitteri5zater5iesten4tage4sage_e4sagese4sert_an5est_e4sertse4servaes5idenes5ignaesis4tees5piraes4si4btal4lisestruc5e5titioounc5erxe4cutota5bleset5itiva4m5atoa4matis5stratu4f3ical5a5lyst4ficatefill5instern5isspend4gani5zasqual4la4lenti4g3o3nas5ophiz5sophicxpecto55graph_or5angeuri4al_4graphy4gress_smol5d4hang5erh5a5nizharp5enhar5terhel4lishith5erhro5niziam5eteia4tricic4t3uascour5au2r1al_5scin4dover4nescan4t55sa3tiou5do3ny_ven4de_under5ty2p5al_anti5sylla5bliner4arturn3ari5nite_5initioinsur5aion4eryiphras4_tim5o5_ten5an_sta5blrtroph4_se5rieiq3ui3t5i5r2izis5itiviso5mer4istral5i5ticki2t5o5mtsch3ie_re5mittro3fiti4v3er_i4vers_ros5per_pe5titiv3o3ro_ped5alro5n4is_or5ato4jestierom5ete_muta5bk5iness4latelitr4ial__mist5i_me5terr4ming_lev4er__mar5tilev4eralev4ers_mag5a5liar5iz5ligaterit5ers_lat5errit5er_r5ited__im5pinri3ta3blink5er_hon5ey5litica_hero5ior5aliz_hand5irip5lic_gen3t4tolo2gylloqui5_con5grt1li2erbad5ger4operag_eu4lertho3donter2ic__ar4tie_ge4ome_ge5ot1_he3mo1_he3p6a_he3roe_in5u2tpara5bl5tar2rht1a1mintalk1a5ta3gon_par5age_aster5_ne6o3f_noe1thstyl1is_poly1s5pathic_pre1ampa4tricl3o3niz_sem4ic_semid6_semip4_semir45ommend_semiv4lea4s1a_spin1oom5etryspher1o_to6poglo4ratospe3cio3s2paceso2lute_we2b1l_re1e4ca5bolicom5erseaf6fishside5swanal6ysano5a2cside5stl5ties_5lumniasid2ed_anti1reshoe1stscy4th1s4chitzsales5wsales3cat6tes_augh4tlau5li5fom5atizol5ogizo5litiorev5olure5vertre5versbi5d2ifbil2lab_earth5pera5blro1tronro3meshblan2d1blin2d1blon2d2bor1no5ro1bot1re4ti4zr5le5quperi5stper4malbut2ed_but4tedcad5e1moist5enre5stalress5ibchie5vocig3a3roint5er4matizariv1o1lcous2ticri3tie5phisti_be5stoog5ativo2g5a5rr3a3digm4b3ingre4posir4en4tade4als_od5uctsquasis6quasir6re5fer_p5trol3rec5olldic1aiddif5fra3pseu2dr5ebrat5metric2d1lead2d1li2epro2g1epre1neuod5uct_octor5apoin3came5triem5i5liepli5narpara3memin5glim5inglypi4grappal6matmis4er_m5istryeo3graporth1riop1ism__but4tio3normaonom1icfeb1ruafermi1o_de4moio5a5lesodit1icodel3lirb5ing_gen2cy_n4t3ingmo5lestration4get2ic_4g1lishobli2g1mon4ismnsta5blmon4istg2n1or_nov3el3ns5ceivno1vembmpa5rabno4rarymula5r4nom1a6lput4tinput4tedn5o5miz_cam4penag5er_nge5nesh2t1eoun1dieck2ne1skiifac1etncour5ane3backmono1s6mono3chmol1e5cpref5ac3militapre5tenith5i2lnge4n4end5est__capa5bje1re1mma1la1ply5styr1kovian_car5olprin4t3lo2ges_l2l3ishprof5it1s2tamp", 12 | 8 : "lead6er_url5ing_ces5si5bch5a5nis1le1noidlith1o5g_chill5ilar5ce1nym5e5trych5inessation5arload4ed_load6er_la4c3i5elth5i2lyneg5ativ1lunk3erwrit6er_wrap3arotrav5es51ke6linga5rameteman3u1scmar1gin1ap5illar5tisticamedio6c1me3gran3i1tesima3mi3da5bves1titemil2l1agv1er1eigmi6n3is_1verely_e4q3ui3s5tabolizg5rapher5graphicmo5e2lasinfra1s2mon4ey1lim3ped3amo4no1enab5o5liz_cor5nermoth4et2m1ou3sinm5shack2ppo5sitemul2ti5uab5it5abimenta5rignit1ernato5mizhypo1thani5ficatuad1ratu4n5i4an_ho6r1ic_ua3drati5nologishite3sidin5dling_trib5utin5glingnom5e1non1o1mistmpos5itenon1i4so_re5stattro1p2istrof4ic_g2norespgnet1ism5glo5binlem5aticflow2er_fla1g6elntrol5lifit5ted_treach1etra1versl5i5ticso3mecha6_for5mer_de5rivati2n3o1me3spac6i2t3i4an_thy4l1antho1k2er_eq5ui5to4s3phertha4l1amt3ess2es3ter1geiou3ba3dotele1r6ooxi6d1iceli2t1isonspir5apar4a1leed1ulingea4n3iesoc5ratiztch3i1er_kil2n3ipi2c1a3dpli2c1abt6ap6athdrom3e5d_le6icesdrif2t1a_me4ga1l1prema3cdren1a5lpres2plipro2cess_met4ala3do5word1syth3i2_non1e2m_post1ampto3mat4rec5ompepu5bes5cstrib5utqu6a3si31stor1ab_sem6is4star3tliqui3v4arr1abolic_sph6in1de5clar12d3aloneradi1o6gs3qui3tosports3wsports3cra5n2hascro5e2cor3bin1gespokes5wspi2c1il_te3legrcroc1o1d_un3at5t_dictio5cat1a1s2buss4ingbus6i2esbus6i2erbo2t1u1lro5e2las1s2pacinb1i3tivema5rine_r3pau5li_un5err5r5ev5er__vi2c3arback2er_ma5chinesi5resid5losophyan3ti1n2sca6p1ersca2t1olar2rangesep3temb1sci2uttse3mes1tar3che5tsem1a1ph", 13 | 9 : "re4t1ribuuto5maticl3chil6d1a4pe5able1lec3ta6bas5ymptotyes5ter1yl5mo3nell5losophizlo1bot1o1c5laratioba6r1onierse1rad1iro5epide1co6ph1o3nscrap4er_rec5t6angre2c3i1prlai6n3ess1lum5bia_3lyg1a1miec5ificatef5i5nites2s3i4an_1ki5neticjapan1e2smed3i3cinirre6v3ocde2c5linao3les3termil5li5listrat1a1gquain2t1eep5etitiostu1pi4d1v1oir5du1su2per1e6_mi1s4ers3di1methy_mim5i2c1i5nitely_5maph1ro15moc1ra1tmoro6n5isdu1op1o1l_ko6r1te1n3ar4chs_phi2l3ant_ga4s1om1teach4er_parag6ra4o6v3i4an_oth3e1o1sn3ch2es1to5tes3toro5test1eror5tively5nop5o5liha2p3ar5rttrib1ut1_eth1y6l1e2r3i4an_5nop1oly_graph5er_5eu2clid1o1lo3n4omtrai3tor1_ratio5na5mocratiz_rav5en1o", 14 | 10 : "se1mi6t5ic3tro1le1um5sa3par5iloli3gop1o1am1en3ta5bath3er1o1s3slova1kia3s2og1a1myo3no2t1o3nc2tro3me6c1cu2r1ance5noc3er1osth1o5gen1ih3i5pel1a4nfi6n3ites_ever5si5bs2s1a3chu1d1ri3pleg5_ta5pes1trproc3i3ty_s5sign5a3b3rab1o1loiitin5er5arwaste3w6a2mi1n2ut1erde3fin3itiquin5tes5svi1vip3a3r", 15 | 11 : "pseu3d6o3f2s2t1ant5shimi1n2ut1estpseu3d6o3d25tab1o1lismpo3lyph1onophi5lat1e3ltravers3a3bschro1ding12g1o4n3i1zat1ro1pol3it3trop1o5lis3trop1o5lesle3g6en2dreeth1y6l1eneor4tho3ni4t", 16 | 12 : "3ra4m5e1triz1e6p3i3neph1" 17 | } 18 | }; -------------------------------------------------------------------------------- /lib/hypher.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @constructor 3 | * @param {!{patterns: !Object, leftmin: !number, rightmin: !number}} language The language pattern file. Compatible with Hyphenator.js. 4 | * @param {?Object=} options Options to alter Hypher's hyphenation behaviour. 5 | */ 6 | function Hypher(language) { 7 | 8 | /** 9 | * @type {!Hypher.TrieNode} 10 | */ 11 | this.trie = this.createTrie(language['patterns']); 12 | 13 | /** 14 | * @type {!number} 15 | * @const 16 | */ 17 | this.leftMin = language['leftmin']; 18 | 19 | /** 20 | * @type {!number} 21 | * @const 22 | */ 23 | this.rightMin = language['rightmin']; 24 | 25 | /** 26 | * @type {!Object.>} 27 | */ 28 | this.exceptions = {}; 29 | 30 | if (language['exceptions']) { 31 | language['exceptions'].split(/,\s?/g).forEach(function (exception) { 32 | var hyphenationMarker = new RegExp(exception.indexOf('=') !== -1 ? '=' : '-', 'g'); 33 | this.exceptions[exception.replace(hyphenationMarker, '')] = exception.split(hyphenationMarker); 34 | }, this); 35 | } 36 | } 37 | 38 | /** 39 | * @typedef {{_points: !Array.}} 40 | */ 41 | Hypher.TrieNode; 42 | 43 | /** 44 | * Creates a trie from a language pattern. 45 | * @private 46 | * @param {!Object} patternObject An object with language patterns. 47 | * @return {!Hypher.TrieNode} An object trie. 48 | */ 49 | Hypher.prototype.createTrie = function (patternObject) { 50 | var size = 0, 51 | tree = { 52 | _points: [] 53 | }, 54 | patterns; 55 | 56 | for (size in patternObject) { 57 | if (patternObject.hasOwnProperty(size)) { 58 | patterns = patternObject[size].match(new RegExp('.{1,' + (+size) + '}', 'g')); 59 | 60 | patterns.forEach(function (pattern) { 61 | var chars = pattern.replace(/[0-9]/g, '').split(''), 62 | points = pattern.split(/\D/), 63 | t = tree; 64 | 65 | chars.forEach(function (c) { 66 | var codePoint = c.charCodeAt(0); 67 | 68 | if (!t[codePoint]) { 69 | t[codePoint] = {}; 70 | } 71 | t = t[codePoint]; 72 | }); 73 | 74 | t._points = points.map(function (p) { 75 | return p || 0; 76 | }); 77 | }); 78 | } 79 | } 80 | return tree; 81 | }; 82 | 83 | /** 84 | * Hyphenates a text. 85 | * 86 | * @param {!string} str The text to hyphenate. 87 | * @return {!string} The same text with soft hyphens inserted in the right positions. 88 | */ 89 | Hypher.prototype.hyphenateText = function (str, minLength) { 90 | minLength = minLength || 4; 91 | 92 | // Regexp("\b", "g") splits on word boundaries, 93 | // compound separators and ZWNJ so we don't need 94 | // any special cases for those characters. 95 | var words = str.split(/\b/g); 96 | return words.map(function (word, i) { 97 | if (word.indexOf('/') !== -1) { 98 | // Don't insert a zero width space if the slash is at the beginning or end 99 | // of the text, or right after or before a space. 100 | if (i === 0 || i === words.length -1 || /\s+\/|\/\s+/.test(word)) { 101 | return word; 102 | } else { 103 | return word + '\u200B'; 104 | } 105 | } else if (word.length <= minLength) { 106 | return word; 107 | } else { 108 | return this.hyphenate(word).join('\u00AD'); 109 | } 110 | }, this).join(''); 111 | }; 112 | 113 | /** 114 | * Hyphenates a word. 115 | * 116 | * @param {!string} word The word to hyphenate 117 | * @return {!Array.} An array of word fragments indicating valid hyphenation points. 118 | */ 119 | Hypher.prototype.hyphenate = function (word) { 120 | var characters, 121 | characterPoints = [], 122 | originalCharacters, 123 | i, 124 | j, 125 | k, 126 | node, 127 | points = [], 128 | wordLength, 129 | nodePoints, 130 | nodePointsLength, 131 | m = Math.max, 132 | trie = this.trie, 133 | result = ['']; 134 | 135 | if (this.exceptions.hasOwnProperty(word)) { 136 | return this.exceptions[word]; 137 | } 138 | 139 | if (word.indexOf('\u00AD') !== -1) { 140 | return [word]; 141 | } 142 | 143 | word = '_' + word + '_'; 144 | 145 | characters = word.toLowerCase().split(''); 146 | originalCharacters = word.split(''); 147 | wordLength = characters.length; 148 | 149 | for (i = 0; i < wordLength; i += 1) { 150 | points[i] = 0; 151 | characterPoints[i] = characters[i].charCodeAt(0); 152 | } 153 | 154 | for (i = 0; i < wordLength; i += 1) { 155 | node = trie; 156 | for (j = i; j < wordLength; j += 1) { 157 | node = node[characterPoints[j]]; 158 | 159 | if (node) { 160 | nodePoints = node._points; 161 | if (nodePoints) { 162 | for (k = 0, nodePointsLength = nodePoints.length; k < nodePointsLength; k += 1) { 163 | points[i + k] = m(points[i + k], nodePoints[k]); 164 | } 165 | } 166 | } else { 167 | break; 168 | } 169 | } 170 | } 171 | 172 | for (i = 1; i < wordLength - 1; i += 1) { 173 | if (i > this.leftMin && i < (wordLength - this.rightMin) && points[i] % 2) { 174 | result.push(originalCharacters[i]); 175 | } else { 176 | result[result.length - 1] += originalCharacters[i]; 177 | } 178 | } 179 | 180 | return result; 181 | }; -------------------------------------------------------------------------------- /src/formatter.js: -------------------------------------------------------------------------------- 1 | /*global Typeset.linebreak*/ 2 | 3 | /*! 4 | * Knuth and Plass line breaking algorithm in JavaScript 5 | * 6 | * Licensed under the new BSD License. 7 | * Copyright 2009-2010, Bram Stein 8 | * All rights reserved. 9 | */ 10 | Typeset.formatter = function (measureText, options) { 11 | var linebreak = Typeset.linebreak; 12 | 13 | var spaceWidth = measureText(' '), 14 | o = { 15 | space: { 16 | width: options && options.space.width || 3, 17 | stretch: options && options.space.stretch || 6, 18 | shrink: options && options.space.shrink || 9 19 | } 20 | }, 21 | h = new Hypher(Hypher.en), 22 | hyphenWidth = measureText('-'), 23 | hyphenPenalty = 100; 24 | 25 | return { 26 | center: function (text) { 27 | var nodes = [], 28 | words = text.split(/\s/), 29 | spaceStretch = (spaceWidth * o.space.width) / o.space.stretch, 30 | spaceShrink = (spaceWidth * o.space.width) / o.space.shrink; 31 | 32 | // Although not specified in the Knuth and Plass whitepaper, this box is necessary 33 | // to keep the glue from disappearing. 34 | nodes.push(linebreak.box(0, '')); 35 | nodes.push(linebreak.glue(0, 12, 0)); 36 | 37 | words.forEach(function (word, index, array) { 38 | var hyphenated = h.hyphenate(word); 39 | if (hyphenated.length > 1 && word.length > 4) { 40 | hyphenated.forEach(function (part, partIndex, partArray) { 41 | nodes.push(linebreak.box(measureText(part), part)); 42 | if (partIndex !== partArray.length - 1) { 43 | nodes.push(linebreak.penalty(hyphenWidth, hyphenPenalty, 1)); 44 | } 45 | }); 46 | } else { 47 | nodes.push(linebreak.box(measureText(word), word)); 48 | } 49 | 50 | if (index === array.length - 1) { 51 | nodes.push(linebreak.glue(0, 12, 0)); 52 | nodes.push(linebreak.penalty(0, -linebreak.infinity, 0)); 53 | } else { 54 | nodes.push(linebreak.glue(0, 12, 0)); 55 | nodes.push(linebreak.penalty(0, 0, 0)); 56 | nodes.push(linebreak.glue(spaceWidth, -24, 0)); 57 | nodes.push(linebreak.box(0, '')); 58 | nodes.push(linebreak.penalty(0, linebreak.infinity, 0)); 59 | nodes.push(linebreak.glue(0, 12, 0)); 60 | } 61 | }); 62 | return nodes; 63 | }, 64 | justify: function (text) { 65 | var nodes = [], 66 | words = text.split(/\s/), 67 | spaceStretch = (spaceWidth * o.space.width) / o.space.stretch, 68 | spaceShrink = (spaceWidth * o.space.width) / o.space.shrink; 69 | 70 | words.forEach(function (word, index, array) { 71 | var hyphenated = h.hyphenate(word); 72 | if (hyphenated.length > 1 && word.length > 4) { 73 | hyphenated.forEach(function (part, partIndex, partArray) { 74 | nodes.push(linebreak.box(measureText(part), part)); 75 | if (partIndex !== partArray.length - 1) { 76 | nodes.push(linebreak.penalty(hyphenWidth, hyphenPenalty, 1)); 77 | } 78 | }); 79 | } else { 80 | nodes.push(linebreak.box(measureText(word), word)); 81 | } 82 | if (index === array.length - 1) { 83 | nodes.push(linebreak.glue(0, linebreak.infinity, 0)); 84 | nodes.push(linebreak.penalty(0, -linebreak.infinity, 1)); 85 | } else { 86 | nodes.push(linebreak.glue(spaceWidth, spaceStretch, spaceShrink)); 87 | } 88 | }); 89 | return nodes; 90 | }, 91 | left: function (text) { 92 | var nodes = [], 93 | words = text.split(/\s/), 94 | spaceStretch = (spaceWidth * o.space.width) / o.space.stretch, 95 | spaceShrink = (spaceWidth * o.space.width) / o.space.shrink; 96 | 97 | words.forEach(function (word, index, array) { 98 | var hyphenated = h.hyphenate(word); 99 | if (hyphenated.length > 1 && word.length > 4) { 100 | hyphenated.forEach(function (part, partIndex, partArray) { 101 | nodes.push(linebreak.box(measureText(part), part)); 102 | if (partIndex !== partArray.length - 1) { 103 | nodes.push(linebreak.penalty(hyphenWidth, hyphenPenalty, 1)); 104 | } 105 | }); 106 | } else { 107 | nodes.push(linebreak.box(measureText(word), word)); 108 | } 109 | 110 | if (index === array.length - 1) { 111 | nodes.push(linebreak.glue(0, linebreak.infinity, 0)); 112 | nodes.push(linebreak.penalty(0, -linebreak.infinity, 1)); 113 | } else { 114 | nodes.push(linebreak.glue(0, 12, 0)); 115 | nodes.push(linebreak.penalty(0, 0, 0)); 116 | nodes.push(linebreak.glue(spaceWidth, -12, 0)); 117 | } 118 | }); 119 | return nodes; 120 | } 121 | }; 122 | }; 123 | 124 | Typeset.formatter.defaults = { 125 | space: { 126 | width: 3, 127 | stretch: 6, 128 | shrink: 9 129 | } 130 | }; -------------------------------------------------------------------------------- /src/linebreak.js: -------------------------------------------------------------------------------- 1 | /*global Typeset.LinkedList*/ 2 | 3 | Typeset.linebreak = (function() { 4 | 5 | /** 6 | * @preserve Knuth and Plass line breaking algorithm in JavaScript 7 | * 8 | * Licensed under the new BSD License. 9 | * Copyright 2009-2010, Bram Stein 10 | * All rights reserved. 11 | */ 12 | var linebreak = function (nodes, lines, settings) { 13 | var options = { 14 | demerits: { 15 | line: settings && settings.demerits && settings.demerits.line || 10, 16 | flagged: settings && settings.demerits && settings.demerits.flagged || 100, 17 | fitness: settings && settings.demerits && settings.demerits.fitness || 3000 18 | }, 19 | tolerance: settings && settings.tolerance || 2 20 | }, 21 | activeNodes = new Typeset.LinkedList(), 22 | sum = { 23 | width: 0, 24 | stretch: 0, 25 | shrink: 0 26 | }, 27 | lineLengths = lines, 28 | breaks = [], 29 | tmp = { 30 | data: { 31 | demerits: Infinity 32 | } 33 | }; 34 | 35 | function breakpoint(position, demerits, ratio, line, fitnessClass, totals, previous) { 36 | return { 37 | position: position, 38 | demerits: demerits, 39 | ratio: ratio, 40 | line: line, 41 | fitnessClass: fitnessClass, 42 | totals: totals || { 43 | width: 0, 44 | stretch: 0, 45 | shrink: 0 46 | }, 47 | previous: previous 48 | }; 49 | } 50 | 51 | function computeCost(start, end, active, currentLine) { 52 | var width = sum.width - active.totals.width, 53 | stretch = 0, 54 | shrink = 0, 55 | // If the current line index is within the list of linelengths, use it, otherwise use 56 | // the last line length of the list. 57 | lineLength = currentLine < lineLengths.length ? lineLengths[currentLine - 1] : lineLengths[lineLengths.length - 1]; 58 | 59 | if (nodes[end].type === 'penalty') { 60 | width += nodes[end].width; 61 | } 62 | 63 | if (width < lineLength) { 64 | // Calculate the stretch ratio 65 | stretch = sum.stretch - active.totals.stretch; 66 | 67 | if (stretch > 0) { 68 | return (lineLength - width) / stretch; 69 | } else { 70 | return linebreak.infinity; 71 | } 72 | 73 | } else if (width > lineLength) { 74 | // Calculate the shrink ratio 75 | shrink = sum.shrink - active.totals.shrink; 76 | 77 | if (shrink > 0) { 78 | return (lineLength - width) / shrink; 79 | } else { 80 | return linebreak.infinity; 81 | } 82 | } else { 83 | // perfect match 84 | return 0; 85 | } 86 | } 87 | 88 | 89 | // Add width, stretch and shrink values from the current 90 | // break point up to the next box or forced penalty. 91 | function computeSum(breakPointIndex) { 92 | var result = { 93 | width: sum.width, 94 | stretch: sum.stretch, 95 | shrink: sum.shrink 96 | }, 97 | i = 0; 98 | 99 | for (i = breakPointIndex; i < nodes.length; i += 1) { 100 | if (nodes[i].type === 'glue') { 101 | result.width += nodes[i].width; 102 | result.stretch += nodes[i].stretch; 103 | result.shrink += nodes[i].shrink; 104 | } else if (nodes[i].type === 'box' || (nodes[i].type === 'penalty' && nodes[i].penalty === -linebreak.infinity && i > breakPointIndex)) { 105 | break; 106 | } 107 | } 108 | return result; 109 | } 110 | 111 | // The main loop of the algorithm 112 | function mainLoop(node, index, nodes) { 113 | var active = activeNodes.first(), 114 | next = null, 115 | ratio = 0, 116 | demerits = 0, 117 | candidates = [], 118 | badness, 119 | currentLine = 0, 120 | tmpSum, 121 | currentClass = 0, 122 | fitnessClass, 123 | candidate, 124 | newNode; 125 | 126 | // The inner loop iterates through all the active nodes with line < currentLine and then 127 | // breaks out to insert the new active node candidates before looking at the next active 128 | // nodes for the next lines. The result of this is that the active node list is always 129 | // sorted by line number. 130 | while (active !== null) { 131 | 132 | candidates = [{ 133 | demerits: Infinity 134 | }, { 135 | demerits: Infinity 136 | }, { 137 | demerits: Infinity 138 | }, { 139 | demerits: Infinity 140 | }]; 141 | 142 | // Iterate through the linked list of active nodes to find new potential active nodes 143 | // and deactivate current active nodes. 144 | while (active !== null) { 145 | next = active.next; 146 | currentLine = active.data.line + 1; 147 | ratio = computeCost(active.data.position, index, active.data, currentLine); 148 | 149 | // Deactive nodes when the distance between the current active node and the 150 | // current node becomes too large (i.e. it exceeds the stretch limit and the stretch 151 | // ratio becomes negative) or when the current node is a forced break (i.e. the end 152 | // of the paragraph when we want to remove all active nodes, but possibly have a final 153 | // candidate active node---if the paragraph can be set using the given tolerance value.) 154 | if (ratio < -1 || (node.type === 'penalty' && node.penalty === -linebreak.infinity)) { 155 | activeNodes.remove(active); 156 | } 157 | 158 | // If the ratio is within the valid range of -1 <= ratio <= tolerance calculate the 159 | // total demerits and record a candidate active node. 160 | if (-1 <= ratio && ratio <= options.tolerance) { 161 | badness = 100 * Math.pow(Math.abs(ratio), 3); 162 | 163 | // Positive penalty 164 | if (node.type === 'penalty' && node.penalty >= 0) { 165 | demerits = Math.pow(options.demerits.line + badness, 2) + Math.pow(node.penalty, 2); 166 | // Negative penalty but not a forced break 167 | } else if (node.type === 'penalty' && node.penalty !== -linebreak.infinity) { 168 | demerits = Math.pow(options.demerits.line + badness, 2) - Math.pow(node.penalty, 2); 169 | // All other cases 170 | } else { 171 | demerits = Math.pow(options.demerits.line + badness, 2); 172 | } 173 | 174 | if (node.type === 'penalty' && nodes[active.data.position].type === 'penalty') { 175 | demerits += options.demerits.flagged * node.flagged * nodes[active.data.position].flagged; 176 | } 177 | 178 | // Calculate the fitness class for this candidate active node. 179 | if (ratio < -0.5) { 180 | currentClass = 0; 181 | } else if (ratio <= 0.5) { 182 | currentClass = 1; 183 | } else if (ratio <= 1) { 184 | currentClass = 2; 185 | } else { 186 | currentClass = 3; 187 | } 188 | 189 | // Add a fitness penalty to the demerits if the fitness classes of two adjacent lines 190 | // differ too much. 191 | if (Math.abs(currentClass - active.data.fitnessClass) > 1) { 192 | demerits += options.demerits.fitness; 193 | } 194 | 195 | // Add the total demerits of the active node to get the total demerits of this candidate node. 196 | demerits += active.data.demerits; 197 | 198 | // Only store the best candidate for each fitness class 199 | if (demerits < candidates[currentClass].demerits) { 200 | candidates[currentClass] = { 201 | active: active, 202 | demerits: demerits, 203 | ratio: ratio 204 | }; 205 | } 206 | } 207 | 208 | active = next; 209 | 210 | // Stop iterating through active nodes to insert new candidate active nodes in the active list 211 | // before moving on to the active nodes for the next line. 212 | // TODO: The Knuth and Plass paper suggests a conditional for currentLine < j0. This means paragraphs 213 | // with identical line lengths will not be sorted by line number. Find out if that is a desirable outcome. 214 | // For now I left this out, as it only adds minimal overhead to the algorithm and keeping the active node 215 | // list sorted has a higher priority. 216 | if (active !== null && active.data.line >= currentLine) { 217 | break; 218 | } 219 | } 220 | 221 | tmpSum = computeSum(index); 222 | 223 | for (fitnessClass = 0; fitnessClass < candidates.length; fitnessClass += 1) { 224 | candidate = candidates[fitnessClass]; 225 | 226 | if (candidate.demerits < Infinity) { 227 | newNode = new Typeset.LinkedList.Node(breakpoint(index, candidate.demerits, candidate.ratio, 228 | candidate.active.data.line + 1, fitnessClass, tmpSum, candidate.active)); 229 | if (active !== null) { 230 | activeNodes.insertBefore(active, newNode); 231 | } else { 232 | activeNodes.push(newNode); 233 | } 234 | } 235 | } 236 | } 237 | } 238 | 239 | // Add an active node for the start of the paragraph. 240 | activeNodes.push(new Typeset.LinkedList.Node(breakpoint(0, 0, 0, 0, 0, undefined, null))); 241 | 242 | nodes.forEach(function (node, index, nodes) { 243 | if (node.type === 'box') { 244 | sum.width += node.width; 245 | } else if (node.type === 'glue') { 246 | if (index > 0 && nodes[index - 1].type === 'box') { 247 | mainLoop(node, index, nodes); 248 | } 249 | sum.width += node.width; 250 | sum.stretch += node.stretch; 251 | sum.shrink += node.shrink; 252 | } else if (node.type === 'penalty' && node.penalty !== linebreak.infinity) { 253 | mainLoop(node, index, nodes); 254 | } 255 | }); 256 | 257 | 258 | if (activeNodes.size() !== 0) { 259 | // Find the best active node (the one with the least total demerits.) 260 | activeNodes.forEach(function (node) { 261 | if (node.data.demerits < tmp.data.demerits) { 262 | tmp = node; 263 | } 264 | }); 265 | 266 | while (tmp !== null) { 267 | breaks.push({ 268 | position: tmp.data.position, 269 | ratio: tmp.data.ratio 270 | }); 271 | tmp = tmp.data.previous; 272 | } 273 | return breaks.reverse(); 274 | } 275 | return []; 276 | }; 277 | 278 | linebreak.infinity = 10000; 279 | 280 | linebreak.glue = function (width, stretch, shrink) { 281 | return { 282 | type: 'glue', 283 | width: width, 284 | stretch: stretch, 285 | shrink: shrink 286 | }; 287 | }; 288 | 289 | linebreak.box = function (width, value) { 290 | return { 291 | type: 'box', 292 | width: width, 293 | value: value 294 | }; 295 | }; 296 | 297 | linebreak.penalty = function (width, penalty, flagged) { 298 | return { 299 | type: 'penalty', 300 | width: width, 301 | penalty: penalty, 302 | flagged: flagged 303 | }; 304 | }; 305 | 306 | return linebreak; 307 | 308 | })(); 309 | -------------------------------------------------------------------------------- /src/linked-list.js: -------------------------------------------------------------------------------- 1 | if ("undefined" === typeof Typeset) { 2 | var Typeset = {}; 3 | } 4 | 5 | Typeset.LinkedList = (function(undefined) { 6 | 7 | function LinkedList() { 8 | this.head = null; 9 | this.tail = null; 10 | this.listSize = 0; 11 | }; 12 | 13 | LinkedList.Node = function (data) { 14 | this.prev = null; 15 | this.next = null; 16 | this.data = data; 17 | }; 18 | 19 | LinkedList.Node.prototype.toString = function () { 20 | return this.data.toString(); 21 | }; 22 | 23 | LinkedList.prototype.isLinked = function (node) { 24 | return !((node && node.prev === null && node.next === null && this.tail !== node && this.head !== node) || this.isEmpty()); 25 | }; 26 | 27 | LinkedList.prototype.size = function () { 28 | return this.listSize; 29 | }; 30 | 31 | LinkedList.prototype.isEmpty = function () { 32 | return this.listSize === 0; 33 | }; 34 | 35 | LinkedList.prototype.first = function () { 36 | return this.head; 37 | }; 38 | 39 | LinkedList.prototype.last = function () { 40 | return this.last; 41 | }; 42 | 43 | LinkedList.prototype.toString = function () { 44 | return this.toArray().toString(); 45 | }; 46 | 47 | LinkedList.prototype.toArray = function () { 48 | var node = this.head, 49 | result = []; 50 | while (node !== null) { 51 | result.push(node); 52 | node = node.next; 53 | } 54 | return result; 55 | }; 56 | 57 | // Note that modifying the list during 58 | // iteration is not safe. 59 | LinkedList.prototype.forEach = function (fun) { 60 | var node = this.head; 61 | while (node !== null) { 62 | fun(node); 63 | node = node.next; 64 | } 65 | }; 66 | 67 | LinkedList.prototype.contains = function (n) { 68 | var node = this.head; 69 | if (!this.isLinked(n)) { 70 | return false; 71 | } 72 | while (node !== null) { 73 | if (node === n) { 74 | return true; 75 | } 76 | node = node.next; 77 | } 78 | return false; 79 | }; 80 | 81 | LinkedList.prototype.at = function (i) { 82 | var node = this.head, index = 0; 83 | 84 | if (i >= this.listLength || i < 0) { 85 | return null; 86 | } 87 | 88 | while (node !== null) { 89 | if (i === index) { 90 | return node; 91 | } 92 | node = node.next; 93 | index += 1; 94 | } 95 | return null; 96 | }; 97 | 98 | LinkedList.prototype.insertAfter = function (node, newNode) { 99 | if (!this.isLinked(node)) { 100 | return this; 101 | } 102 | newNode.prev = node; 103 | newNode.next = node.next; 104 | if (node.next === null) { 105 | this.tail = newNode; 106 | } else { 107 | node.next.prev = newNode; 108 | } 109 | node.next = newNode; 110 | this.listSize += 1; 111 | return this; 112 | }; 113 | 114 | LinkedList.prototype.insertBefore = function (node, newNode) { 115 | if (!this.isLinked(node)) { 116 | return this; 117 | } 118 | newNode.prev = node.prev; 119 | newNode.next = node; 120 | if (node.prev === null) { 121 | this.head = newNode; 122 | } else { 123 | node.prev.next = newNode; 124 | } 125 | node.prev = newNode; 126 | this.listSize += 1; 127 | return this; 128 | }; 129 | 130 | LinkedList.prototype.push = function (node) { 131 | if (this.head === null) { 132 | this.unshift(node); 133 | } else { 134 | this.insertAfter(this.tail, node); 135 | } 136 | return this; 137 | }; 138 | 139 | LinkedList.prototype.unshift = function (node) { 140 | if (this.head === null) { 141 | this.head = node; 142 | this.tail = node; 143 | node.prev = null; 144 | node.next = null; 145 | this.listSize += 1; 146 | } else { 147 | this.insertBefore(this.head, node); 148 | } 149 | return this; 150 | }; 151 | 152 | LinkedList.prototype.remove = function (node) { 153 | if (!this.isLinked(node)) { 154 | return this; 155 | } 156 | if (node.prev === null) { 157 | this.head = node.next; 158 | } else { 159 | node.prev.next = node.next; 160 | } 161 | if (node.next === null) { 162 | this.tail = node.prev; 163 | } else { 164 | node.next.prev = node.prev; 165 | } 166 | this.listSize -= 1; 167 | return this; 168 | }; 169 | 170 | LinkedList.prototype.pop = function () { 171 | var node = this.tail; 172 | this.tail.prev.next = null; 173 | this.tail = this.tail.prev; 174 | this.listSize -= 1; 175 | node.prev = null; 176 | node.next = null; 177 | return node; 178 | }; 179 | 180 | LinkedList.prototype.shift = function () { 181 | var node = this.head; 182 | this.head.next.prev = null; 183 | this.head = this.head.next; 184 | this.listSize -= 1; 185 | node.prev = null; 186 | node.next = null; 187 | return node; 188 | }; 189 | 190 | return LinkedList; 191 | })(); -------------------------------------------------------------------------------- /typeset-browser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bramstein/typeset/619fadd8b66db6b32ec810a2a51035e772042223/typeset-browser.png -------------------------------------------------------------------------------- /typeset-centered.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bramstein/typeset/619fadd8b66db6b32ec810a2a51035e772042223/typeset-centered.png -------------------------------------------------------------------------------- /typeset-circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bramstein/typeset/619fadd8b66db6b32ec810a2a51035e772042223/typeset-circle.png -------------------------------------------------------------------------------- /typeset-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bramstein/typeset/619fadd8b66db6b32ec810a2a51035e772042223/typeset-flow.png -------------------------------------------------------------------------------- /typeset-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bramstein/typeset/619fadd8b66db6b32ec810a2a51035e772042223/typeset-right.png -------------------------------------------------------------------------------- /typeset-triangle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bramstein/typeset/619fadd8b66db6b32ec810a2a51035e772042223/typeset-triangle.png -------------------------------------------------------------------------------- /typeset-with-ratio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bramstein/typeset/619fadd8b66db6b32ec810a2a51035e772042223/typeset-with-ratio.png --------------------------------------------------------------------------------