├── LICENSE ├── README.md ├── arabic.html ├── css ├── 1455_gutenberg_b42.otf ├── EncodeSans-VF.ttf ├── Estedad-VF.woff2 ├── NotoNastaliqUrdu-VF.ttf ├── arabic.css ├── base.css ├── gutenberg.css └── poetry.css ├── dombreak.ts ├── gutenberg.html ├── index.html ├── newbreak-rust ├── Cargo.toml └── src │ └── lib.rs ├── newbreak.ts ├── package.json ├── sile-tests.lua ├── src ├── bundle.js ├── dombreak.js ├── dombreak.js.map ├── entry.js ├── entry.js.map ├── hyphenator-en.js ├── hyphenator.js ├── jQuery-FontSpy.js ├── jquery.js ├── kashida.js ├── newbreak.js ├── newbreak.js.map ├── test.js └── test.js.map └── tsconfig.json /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Copyright (c) 2019 Simon Cozens 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, 6 | are permitted provided that the following conditions are met: 7 | 8 | - Redistributions of source code must retain the above copyright notice, 9 | this list of conditions and the following disclaimer. 10 | 11 | - Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | - Neither the name of Simon Cozens nor the names of its contributors may 16 | be used to endorse or promote products derived from this software without 17 | specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 20 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 23 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 26 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Newbreak: Another line breaking algorithm, for variable fonts 2 | 3 | The main algorithm lives in `newbreak.ts` and is implemented in TypeScript. 4 | It should be clearly documented. 5 | 6 | You may want to override the default options in `dombreak.ts` to change 7 | the allowable stretchiness and shrinkiness of the font. 8 | 9 | ## To see the demo 10 | 11 | Here it is: https://simoncozens.github.io/newbreak/ 12 | 13 | ## To run the demo yourself 14 | 15 | * Use `npm install` to get the Javascript modules you need. 16 | * Run `npm run-script build` to compile TypeScript to Javascript. 17 | 18 | This should produce `src/bundle.js`, which is needed by the `index.html`. 19 | 20 | ## To use it on your own site 21 | 22 | Either compile it yourself (using the instructions above) or grab the precompiled versions of [newbreak.js](https://github.com/simoncozens/newbreak/blob/gh-pages/src/newbreak.js) and [dombreak.js](https://github.com/simoncozens/newbreak/blob/gh-pages/src/dombreak.js), and add them to your site. (Get the hyphenator and dictionary as well if you want these.) 23 | 24 | Then, when your fonts are loaded, call: 25 | 26 | var dombreak = require("./dombreak") 27 | new dombreak.DomBreak($("#elemToJustify"), { 28 | // ... your DomBreak options here ... 29 | }) 30 | 31 | (You can use the [fontspy](https://github.com/patrickmarabeas/jQuery-FontSpy.js) library to run a callback when all CSS fonts are loaded.) 32 | 33 | See the comments in [dombreak.ts](https://github.com/simoncozens/newbreak/blob/master/dombreak.ts) for the available options or just accept the defaults. 34 | 35 | ## Using the interesting features 36 | 37 | ### Variable font extension 38 | 39 | If you have a variable font, then you can use the axes in your variable font to stretch or shrink some or all of your glyphs. Add the `data-method="wdth"` (or whatever axis name) attribute to your wrapper div to tell the system *how* to vary your fonts. 40 | 41 | To make all tokens stretchable/shrinkable, the easiest way is to add the `data-text-stretch="computed" data-text-shrink="computed"` attributes to the wrapper div. This will allow each token to be stretched/shrunk to the limits of the variable font axis. 42 | 43 | If you don't want to stretch/shrink them the whole way, you can instead specify a width ratio: `data-text-stretch="0.5"` will allow text to be stretched to a total of 50% more than their natural width. 44 | 45 | If you want to make only *some* tokens stretchable/shrinkable (such as Arabic kashidas) or to make some tokens more stretchy than others, then you need to alter the way that justification nodes are created. The way to do this is to write a function which takes a piece of text and a node, and spits out an *array* of nodes. This function is passed as an option to your `dombreak` instance. The following function will allow only the word "stretchy" to be stretched up to a total of three times its natural width. 46 | 47 | var nodeCustomizer = function (text, node) { 48 | if (text == "stretchy") { 49 | node.stretch = 2 * node.width 50 | } else { 51 | node.stretch = 0; 52 | } 53 | return [node]; 54 | } 55 | var d = new DomBreak.DomBreak(box, {customizeTextNode: nodeCustomizer}); 56 | 57 | > `node.stretch` and `node.shrink` specifies the amount of space (in points) that can be added to or taken away from the natural width. So if `node.stretch` is `2 * node.width`, then the node can be stretched up to a *total* of `node.width + node.stretch == 3 * node.width`. 58 | 59 | ### Contextual alternates 60 | 61 | In situations like Arabic justification, you may get a better line fit by choosing alternative forms of glyphs or activating ligatures. newbreak supports this, but making it happen takes a bit of work. Each justification node can have an array called `node.alternates`, which consists of more Node objects. Here's a node customizer which adds any alternate glyphs produced by the swash feature: 62 | 63 | function makeItSwashy(text, node) { 64 | d.cacheComputedStretch = {}; 65 | 66 | var nlalt = d.makeText(text, $("#testbox2")[0]) 67 | nlalt.text.addClass("swash") 68 | nlalt.text.css("font-feature-settings", "swsh") 69 | nlalt.substitutionPenalty = 10; 70 | if (nlalt.width != node.width) { 71 | node.alternates = [nlalt]; 72 | } 73 | return [node]; 74 | } 75 | 76 | To make this work you need to add another text box (`#testbox2` in this example) which has the same CSS properties as your main box *but with the swash feature activated*. This is so that when text nodes are created in this context, their width will be measured correctly. 77 | 78 | We have added a `substitutionPenalty` to this alternate node, so that the engine is somewhat penalised for choosing the swash form over the default one. 79 | 80 | ### Choosing the strategy 81 | 82 | Each justification node has a `stretchContribution` or `shrinkContribution`. This is an array of numbers which should sum to one, specifying different "levels" at which the stretching and shrinking takes places. The default value is `[1]`. By carefully setting the `stretchContribution` (and *mutatis mutantis* for shrink, so I'm not going to mention it again), you can customize the way that justification takes place for your language. 83 | 84 | Let's suppose we have a line whose natural width is 80pt and we want to stretch it to be 130pt long. We have four spaces, which can stretch up to 10pt each, and two kashidas, which can stretch by 40pt. 85 | 86 | If we set the `stretchContribution` of each space node to `[1,0]` and the `stretchContribution` of the kashidas to `[0,1]`, this is what happens: the first level of stretching is delegated completely to the spaces, which are stretched to their full 10pt each, and the kashidas are untouched. Now the line is 120pt long, so we need another 10pt. We move to the next level, in which the spaces will use 0% of their available stretch and the kashidas will use all of it. We don't need them to each go the whole 40pt, though: the shortfall is spread between them and they each stretch 5pt. Setting `space.stretchContribution = [1,0]; kashida.stretchContribution = [0,1]` means "stretch the spaces first, then fill up with kashidas next". 87 | 88 | If instead we set `space.stretchContribution = [1]; kashida.stretchContribution = [0.5, 0.5]` then the kashidas will be prepared to use up 50% of their stretchability on the first pass. Kashidas contribute 20pt each and spaces contribute 10pt, so we have a total usable stretch of 80pt. To make up the 50 point shortfall, each node is allocated 50/80 of their stretch: kashidas are stretched by 12.5pt and spaces are stretched by 6.25pt. What our setting means is "fill up the line with spaces and kashidas at the same time, but kashidas go towards their maximum width at half the speed that spaces to." 89 | 90 | Conversely, if we set `space.stretchContribution = [0,1]; kashida.stretchContribution = [1,0]`, we would stretch the line as far as we could with kashidas before adjusting the spaces. 91 | 92 | Combining this with the alternation penalties should give you full control over the order of justification techniques. -------------------------------------------------------------------------------- /arabic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Test line breaking 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 |
صومعهٔ عیسیست خوان اهل دل 20 | هان و هان ای مبتلا این در مهل 21 | جمع گشتندی ز هر اطراف خلق 22 | از ضریر و لنگ و شل و اهل دلق 23 | بر در آن صومعهٔ عیسی صباح 24 | تا بدم اوشان رهاند از جناح 25 | او چو فارغ گشتی از اوراد خویش 26 | چاشتگه بیرون شدی آن خوب‌کیش 27 | جوق جوقی مبتلا دیدی نزار 28 | شسته بر در در امید و انتظار 29 | گفتی ای اصحاب آفت از خدا 30 | حاجت این جملگانتان شد روا 31 | هین روان گردید بی رنج و عنا 32 | سوی غفاری و اکرام خدا 33 | جملگان چون اشتران بسته‌پای 34 | که گشایی زانوی ایشان برای 35 | خوش دوان و شادمانه سوی خان 36 | از دعای او شدندی پا دوان
37 |
38 |
39 |
40 |
41 | 42 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /css/1455_gutenberg_b42.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simoncozens/newbreak/8526994cf79af503ad934dfa4aad209c9cadfb96/css/1455_gutenberg_b42.otf -------------------------------------------------------------------------------- /css/EncodeSans-VF.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simoncozens/newbreak/8526994cf79af503ad934dfa4aad209c9cadfb96/css/EncodeSans-VF.ttf -------------------------------------------------------------------------------- /css/Estedad-VF.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simoncozens/newbreak/8526994cf79af503ad934dfa4aad209c9cadfb96/css/Estedad-VF.woff2 -------------------------------------------------------------------------------- /css/NotoNastaliqUrdu-VF.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simoncozens/newbreak/8526994cf79af503ad934dfa4aad209c9cadfb96/css/NotoNastaliqUrdu-VF.ttf -------------------------------------------------------------------------------- /css/arabic.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "XNassimGX"; 3 | src: url("./XNassimGX.ttf") 4 | } 5 | 6 | @font-face { 7 | font-family: "NNU-VF"; 8 | src: url("./NotoNastaliqUrdu-VF.ttf") 9 | } 10 | 11 | 12 | 13 | div#testbox, div#testbox2 { 14 | font-family: "NNU-VF"; line-height: 1.5em; 15 | font-size: 34px; 16 | width: 420px; 17 | } 18 | 19 | div#testbox2 { 20 | font-feature-settings: "swsh"; 21 | } 22 | 23 | .swash { 24 | font-feature-settings: "swsh"; 25 | } 26 | -------------------------------------------------------------------------------- /css/base.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | src: url('EncodeSans-VF.ttf'); 3 | font-family:'Encode Sans'; 4 | font-style: normal; 5 | } 6 | 7 | @font-face { 8 | src: url('Fit-VF.woff2') format("woff2"), 9 | url('Fit-VF.woff') format("woff"); 10 | font-family: 'Fit'; 11 | font-style: normal; 12 | font-weight: normal; 13 | font-stretch: 10% 1000%; 14 | } 15 | 16 | .controls { 17 | float:right; width:350px; display:block; 18 | line-height: 1.5em; 19 | font-family: sans-serif; 20 | background-color: #dfdfdf; 21 | padding: 15px; 22 | } 23 | small { color: #333; } 24 | 25 | span.glue { display: inline-block } 26 | div.nowrap { white-space: nowrap } 27 | div#testbox { 28 | font-family: "Encode Sans", "Skia"; line-height: 1.4em; 29 | font-size: 24px; 30 | width: 420px; border:2px solid #666; padding:5px; 31 | } 32 | 33 | #testbox.Fit { font-size: 90px !important; } 34 | label { display: inline-block; width: 170px; } -------------------------------------------------------------------------------- /css/gutenberg.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "Gutenberg"; 3 | src: url("./1455_gutenberg_b42.otf") 4 | } 5 | 6 | .dlig { 7 | font-feature-settings: "dlig"; 8 | } 9 | 10 | div#testbox { 11 | font-family: "Gutenberg"; line-height: 1.5em; 12 | font-size: 34px; 13 | width: 420px; border:2px solid #666; padding:5px; 14 | } 15 | 16 | 17 | div#testbox2 { 18 | font-family: "Gutenberg"; line-height: 1.5em; 19 | font-size: 34px; 20 | } 21 | -------------------------------------------------------------------------------- /css/poetry.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | src: url('EncodeSans-VF.ttf'); 3 | font-family:'Encode Sans'; 4 | font-style: normal; 5 | } 6 | 7 | @font-face { 8 | src: url('Fit-VF.woff2') format("woff2"), 9 | url('Fit-VF.woff') format("woff"); 10 | font-family: 'Fit'; 11 | font-style: normal; 12 | font-weight: normal; 13 | font-stretch: 10% 1000%; 14 | } 15 | 16 | span.glue { display: inline-block } 17 | div.nowrap { white-space: nowrap } 18 | div#leftcol { 19 | font-family: "Encode Sans", "Skia"; line-height: 1.4em; 20 | font-size: 24px; 21 | flex:50%; border:2px solid #666; padding:5px; 22 | max-width: 50%; 23 | line-height: 2em; 24 | 25 | } 26 | 27 | #flexwrapper { display: flex; max-width: 80% } 28 | div#rightcol { 29 | font-family: "Encode Sans", "Skia"; line-height: 1.4em; 30 | font-size: 24px; 31 | flex:50%; border:2px solid #666; padding:5px; 32 | line-height: 2em; 33 | max-width: 50%; 34 | } 35 | 36 | -------------------------------------------------------------------------------- /dombreak.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This is dombreak, which applies the newbreak algorithm to text 3 | * nodes in a HTML DOM. 4 | */ 5 | 6 | import * as $ from "jquery"; 7 | import { Node, Linebreaker, Line } from './newbreak'; 8 | 9 | /** You can provide a hash of options to the DomBreak object to 10 | * control various aspects of its behaviour: 11 | */ 12 | interface DomBreakOptions { 13 | /** 14 | * @property {number} spaceStretch 15 | * A measure of how "stretchy" a space is, defined as the ratio 16 | * between the extended width and the default width. i.e. if your 17 | * default space is 5 points wide and `spaceStretch` is 1.0, then 18 | * you can expand a space another 5 points up to a total of 10 points. 19 | **/ 20 | spaceStretch?: number, 21 | 22 | /** 23 | * @property {number} spaceShrink 24 | * Similarly for how much a space can shrink; a ratio of 0.5 with 25 | * a 5-point space can reduce the space to 2.5 points where needed. 26 | */ 27 | spaceShrink?: number, 28 | 29 | /** 30 | * @property {number} textStretch 31 | * A ratio of how stretchy a piece of text is. Placing a constant 32 | * ratio here assumes that all characters stretch to the same degree. 33 | * If they don't, then you can use the magic string "computed" instead 34 | * of a number. When you do this, DomBreak will try to stretch each bit 35 | * of text as far as the font allows it to go and compute the appropriate 36 | * value for each individual bit of text. 37 | */ 38 | textStretch?: number | string, 39 | 40 | /** 41 | * @property {number} textShrink 42 | * The same but for how much a piece of text can shrink. 43 | */ 44 | textShrink?: number | string, 45 | 46 | /** 47 | * @property {number} textLetterSpacing 48 | * The maximum amount of letterspacing (in pixels) you want to apply 49 | * when stretching a font. 50 | */ 51 | textLetterSpacing?: number, 52 | 53 | /** 54 | * @property {number} textLetterSpacingPriority 55 | * Ratio of how much of the stretching of a piece of text is done by 56 | * letterspacing and how much by applying variable font axes. 57 | * If this is 0, everything is done with variable fonts; if it is 1, 58 | * all stretching is done by letterspacing. 59 | */ 60 | textLetterSpacingPriority?: number, 61 | 62 | /** 63 | * @property {boolean} hyphenate 64 | * Should the text be hyphenated? 65 | */ 66 | hyphenate?: boolean, 67 | 68 | /** 69 | * @property {boolean} colorize 70 | * If this is true, then stretched text becomes more green as it 71 | * stretches while shrunk text becomes more red. 72 | */ 73 | colorize?: boolean, 74 | 75 | /** 76 | * @property {boolean} fullJustify 77 | * If false, the last line and any text with hard breaks are set ragged; 78 | * if true, they are set flush. 79 | */ 80 | 81 | fullJustify?: boolean, 82 | 83 | /** 84 | * @property {string} method 85 | * The CSS method used to stretch and shrink text. This can either be 86 | * the string "font-stretch", in which case the CSS parameter "font-stretch" 87 | * is used, or the name of a variable font axis such as "wdth" or "GEXT". 88 | */ 89 | method?: string, 90 | 91 | /** 92 | * @property {function} customizeTextNode 93 | * When a node is created from a piece of text, this callback is called to 94 | * allow the user to customize the node. For example, you can check the text 95 | * and decide that you don't want this particular bit of text to be stretched 96 | * at all. Normally this function should mutate the node and return nothing. 97 | * If it does return, it should return a list of nodes, which will be 98 | * *substituted* for the node passed in. 99 | */ 100 | customizeTextNode?: ((text: string, node: Node) => Node[]) | ((text: string, node: Node) => void), 101 | 102 | /** 103 | * @property {function} customNodeMaker 104 | * When a HTML element, rather than a piece of text or space, is found in the 105 | * box to be justified, it is passed to this function to be transformed into 106 | * one or more Nodes. By default, the text is extracted as normal. 107 | */ 108 | customNodeMaker?: (domnode: JQuery) => Node[] 109 | 110 | /** 111 | * @property {function} resizeMode 112 | * How the container node should be resized: "jquery-ui" uses jQuery UI 113 | * "resizable" method (which adds a handle for drag resizing). "resizeobserver" 114 | * uses browser-native ResizeObserver and no UI. 115 | */ 116 | resizeMode: string 117 | } 118 | 119 | var defaultOptions: DomBreakOptions = { 120 | spaceStretch: 1.00, 121 | spaceShrink: 0.20, 122 | textStretch: 0.10, 123 | textShrink: 0.40, 124 | textLetterSpacing: 0, 125 | textLetterSpacingPriority: 0, 126 | hyphenate: false, 127 | colorize: true, 128 | fullJustify: false, 129 | method: "font-stretch", 130 | resizeMode: "jquery-ui" 131 | } 132 | 133 | declare var Hyphenator: any; 134 | 135 | // Crappy function to measure the width of a bit of text. 136 | var fakeEl: JQuery; 137 | function textWidth (text:string, elProto: JQuery) :number { 138 | if (!fakeEl) { 139 | fakeEl = $("").appendTo(document.body).hide() 140 | } 141 | for (var c of ["font-style", "font-variant", "font-weight", "font-size", "font-family", "font-stretch", "font-variation-settings", "font-feature-settings"]) { 142 | fakeEl.css(c,elProto.css(c)); 143 | } 144 | fakeEl.text(text); 145 | return fakeEl.width(); 146 | }; 147 | 148 | export class DomBreak { 149 | public options: DomBreakOptions; 150 | public nodelist: Node[]; 151 | public origContents: JQuery; 152 | public domNode: JQuery; 153 | public cacheComputedShrink = {} 154 | public cacheComputedStretch = {} 155 | public cacheSpaceWidth = -1; 156 | public doneResizeObserver = false; 157 | 158 | constructor (domnode: JQuery, options: DomBreakOptions) { 159 | this.options = {...defaultOptions,...options}; 160 | this.domNode = domnode; 161 | if (domnode[0].hasAttribute("data-text-stretch")) { 162 | this.options.textStretch = domnode.data("text-stretch") 163 | } 164 | if (domnode[0].hasAttribute("data-text-shrink")) { 165 | this.options.textShrink = domnode.data("text-shrink") 166 | } 167 | if (domnode.data("method")) { 168 | this.options.method = domnode.data("method") 169 | } 170 | this.origContents = domnode.contents(); 171 | if (!this.options.customNodeMaker) { 172 | this.options.customNodeMaker = (el) => { 173 | return this.textToNodes(domnode, el.text()) 174 | } 175 | } 176 | this.rebuild(); 177 | } 178 | 179 | public rebuild () { 180 | this.cacheComputedStretch = {}; 181 | this.cacheComputedShrink = {}; 182 | this.nodelist = this.DOMToNodes(this.domNode, this.origContents); 183 | let doResize = (evt, ui) => { this.layout() } 184 | if (this.options.resizeMode == "jquery-ui") { 185 | if (this.domNode.resizable( "instance" )) { 186 | this.domNode.resizable("destroy"); 187 | } 188 | setTimeout(() => { 189 | this.domNode.resizable({resize: doResize }); 190 | doResize(null,null); 191 | },0.1); 192 | } else if (this.options.resizeMode == "resizeobserver" && ! this.doneResizeObserver) { 193 | let ro = new ResizeObserver(doResize) 194 | ro.observe(this.domNode[0]) 195 | console.log("Observing", this.domNode[0]) 196 | this.doneResizeObserver = true; 197 | } 198 | } 199 | 200 | public makeGlue(domnode) : Node { 201 | var sp = $("") 202 | sp.addClass("glue") 203 | // Because it's hard to measure a space directly we have to do a bit of 204 | // messing about to work out the width. 205 | if (this.cacheSpaceWidth == -1) { 206 | this.cacheSpaceWidth = textWidth("x x", domnode)-textWidth("xx",domnode) 207 | } 208 | sp.width(this.cacheSpaceWidth) 209 | return { 210 | text: sp, 211 | debugText: " ", 212 | penalty: 0, 213 | stretchContribution: [1,0], 214 | shrinkContribution: [1,0], 215 | breakable: true, 216 | width: sp.width(), 217 | stretch: this.options.spaceStretch * sp.width(), 218 | shrink: this.options.spaceShrink * sp.width() 219 | } as Node; 220 | } 221 | 222 | public makeForcedBreak(domnode) : Node[] { 223 | var rv: Node[] = [] 224 | if (!this.options.fullJustify) { 225 | var sp = $("") 226 | sp.addClass("glue") 227 | rv.push( 228 | { 229 | debugText: " ", 230 | text: sp, 231 | breakable: false, 232 | penalty: 0, 233 | width: sp.width(), 234 | stretch: 100000, 235 | shrink: 0 236 | } as Node) 237 | } 238 | var b = $("") 239 | rv.push( 240 | { 241 | debugText: "\n", 242 | text: b, 243 | breakable: true, 244 | penalty: -100000, 245 | stretch: 0, 246 | width: 0, 247 | } as Node) 248 | return rv; 249 | } 250 | 251 | public makeText(t :string, domnode) : Node[] { 252 | var sp = $(""); 253 | sp.addClass("text") 254 | sp.text(t); 255 | var length = t.length; 256 | var width = textWidth(t, domnode) 257 | var maximumLSavailable = (length-1) * this.options.textLetterSpacing 258 | var maximumVarfontStretchAvailable : number 259 | var shrink; 260 | if (this.options.textStretch == "computed") { 261 | maximumVarfontStretchAvailable = this.computeMaxWidth(sp) - width; 262 | // console.log(t+" can stretch by "+maximumVarfontStretchAvailable+"px") 263 | } else { 264 | var maximumVarfontStretchAvailable = (this.options.textStretch as number) * width 265 | } 266 | if (this.options.textShrink == "computed") { 267 | shrink = width - this.computeMinWidth(sp); 268 | } else { 269 | shrink = (this.options.textShrink as number) * width 270 | } 271 | this.setToWidth(sp, width) 272 | var stretch = maximumLSavailable * this.options.textLetterSpacingPriority + maximumVarfontStretchAvailable * (1-this.options.textLetterSpacingPriority) 273 | var node = { 274 | debugText: t, 275 | text: sp, 276 | breakable: false, 277 | penalty:0, 278 | stretchContribution: [0,1], 279 | shrinkContribution: [0,1], 280 | width: width, 281 | stretch: stretch, 282 | shrink: shrink 283 | } as Node; 284 | 285 | if (this.options.customizeTextNode) { 286 | // Avoid horrible recursive mess 287 | var saveThisFunction = this.options.customizeTextNode; 288 | delete this.options["customizeTextNode"]; 289 | var res = saveThisFunction(t, node) 290 | this.options.customizeTextNode = saveThisFunction; 291 | if (res) { return res } 292 | } 293 | sp.attr("width", node.width); 294 | sp.attr("stretch", node.stretch); 295 | sp.attr("shrink", node.shrink); 296 | return [node]; 297 | } 298 | 299 | public computeMaxWidth(sp: JQuery) : number { 300 | if (this.cacheComputedStretch[sp.text()]) { return this.cacheComputedStretch[sp.text()] } 301 | var measureEl = sp.clone().appendTo(this.domNode).hide(); 302 | this.setToWidth(measureEl, 1000) 303 | var w = measureEl.width() 304 | measureEl.remove() 305 | this.cacheComputedStretch[sp.text()] = w 306 | return w 307 | } 308 | 309 | public computeMinWidth(sp: JQuery) : number { 310 | if (this.cacheComputedShrink[sp.text()]) { return this.cacheComputedShrink[sp.text()] } 311 | var measureEl = sp.clone().appendTo(this.domNode).hide(); 312 | this.setToWidth(measureEl, 0) 313 | var w = measureEl.width() 314 | measureEl.remove() 315 | this.cacheComputedShrink[sp.text()] = w 316 | return w 317 | } 318 | 319 | private hyphenator: any; 320 | public hyphenate(t) { 321 | if (this.options.hyphenate) { 322 | if (!this.hyphenator) { this.hyphenator = new Hyphenator() } 323 | return this.hyphenator.hyphenate(t); 324 | } 325 | return [t]; 326 | } 327 | 328 | // The first job is to create nodes, both in the DOM and 329 | // newbreak `Node` objects, representing each word and space. 330 | public DOMToNodes(domnode: JQuery, contents: JQuery) : Node[] { 331 | domnode.empty() 332 | domnode.addClass("nowrap") 333 | var nodelist: Node[] = [] 334 | contents.each( (i,el) => { 335 | if (el.nodeType == 3) { 336 | nodelist = nodelist.concat(this.textToNodes(domnode, el.textContent)) 337 | } else if (el.nodeType == 1) { 338 | el = el as HTMLElement 339 | var nodes 340 | if (el.tagName == "BR") { 341 | nodes = this.makeForcedBreak(domnode); 342 | } else { 343 | nodes = this.options.customNodeMaker($(el)) 344 | } 345 | for (var n of nodes) { 346 | nodelist.push(n) 347 | domnode.append(n.text) 348 | } 349 | } 350 | }) 351 | return nodelist 352 | } 353 | 354 | public textToNodes(domnode: JQuery, text: string) : Node[] { 355 | // We'll empty the container and tell it that we're handling wrapping. 356 | 357 | var nodelist: Node[] = []; 358 | for (let t of text.split(/(\s+)/m)) { 359 | var n: Node; 360 | if (t.match(/\s+/)) { 361 | // This is just space. Turn it into a glue node. 362 | n = this.makeGlue(domnode); 363 | nodelist.push(n); 364 | domnode.append(n.text); 365 | } 366 | else { 367 | // This is text. Turn it into hyphenated fragments. 368 | // If hyphenation is off, we just get the text back. 369 | let fragments = this.hyphenate(t) as string[]; 370 | for (let idx=0; idx < fragments.length; idx++) { 371 | var frag = fragments[idx]; 372 | // Turn each fragment into a `Node`, pop it on the list 373 | // and put the word back into the DOM. 374 | var nl = this.makeText(frag, domnode); 375 | for (n of nl) { 376 | nodelist.push(n); 377 | domnode.append(n.text); 378 | } 379 | // if (idx != fragments.length-1) { 380 | // // Add hyphens between each fragment. 381 | // n = this.makeHyphen(domnode); 382 | // nodelist.push(n); 383 | // domnode.append(n.text); 384 | // } 385 | } 386 | } 387 | } 388 | 389 | if (!this.options.fullJustify) { 390 | // At the end of the paragraph we need super-stretchy glue. 391 | let stretchy = this.makeGlue(domnode); 392 | stretchy.stretch = 10000; 393 | nodelist.push(stretchy); 394 | } 395 | return nodelist; 396 | } 397 | 398 | public setToWidth(el:JQuery, width: number) { 399 | var tries = 20 400 | // console.log(`Setting ${el.text()} to width ${width}, currently ${el.width()}`) 401 | if (this.options.method == "font-stretch") { 402 | var guess = width / el.width() * 100 403 | var min = 0 // XXX 404 | var max = 200 // XXX 405 | } else { 406 | var guess = width / (0.001+el.width()) * 1000 407 | var min = 0 // XXX 408 | var max = 1000 // XXX 409 | } 410 | if (Math.abs(el.width() - width) < 1) { 411 | return; 412 | } 413 | while (tries--) { 414 | if (this.options.method == "font-stretch") { 415 | el.css("font-stretch", guess+"%") 416 | } else { 417 | el.css("font-variation-settings", `'${this.options.method}' ${guess}`) 418 | } 419 | var newWidth = textWidth(el.text(), el) 420 | // console.log(`Width is now ${newWidth}, desired is ${width}`) 421 | if (Math.abs(newWidth - width) < 1) { 422 | return; 423 | } else if (newWidth > width) { 424 | max = guess 425 | } else if (newWidth < width) { 426 | min = guess 427 | } 428 | guess = (min + max) / 2 429 | // console.log(`New guess is ${guess}`) 430 | } 431 | } 432 | 433 | public layout(desiredWidth?) { 434 | var nodelist = this.nodelist; 435 | var domnode = this.domNode; 436 | if (!desiredWidth) { 437 | desiredWidth = domnode.width(); 438 | } 439 | var breaker = new Linebreaker(nodelist, [domnode.width()]) 440 | var lines:Line[] = breaker.doBreak({ 441 | fullJustify: this.options.fullJustify, 442 | unacceptableRatio: (this.options.fullJustify ? 0 : 0.5) 443 | }); 444 | domnode.find("br").remove() 445 | domnode.children("span").remove() 446 | 447 | 448 | // Stretch and shrink each node as appropriate. We'll add linebreaks later. 449 | for (var l of lines) { 450 | for (var ix = 0; ix < l.nodes.length ; ix++) { 451 | var n = l.nodes[ix]; 452 | var el = (n.text as JQuery); 453 | el.attr("width", n.width); 454 | el.attr("desired-width", l.targetWidths[ix]); 455 | el.attr("stretch", n.stretch); 456 | el.attr("shrink", n.shrink); 457 | 458 | domnode.append(el) 459 | 460 | if (n.stretch > 0 || n.shrink > 0) { 461 | if (el.hasClass("text")) { 462 | // Text gets shrunk with the variable font CSS rule. 463 | this.setToWidth(el, l.targetWidths[ix]); 464 | el.css("letter-spacing", "normal"); 465 | if (this.options.colorize) { 466 | var stretchShrink = (n.width-l.targetWidths[ix]) / n.width 467 | var color; 468 | if (stretchShrink > 0) { 469 | var redness = (stretchShrink * 4 * 255).toFixed(0); 470 | color = "rgb("+redness+",0,0)" 471 | } else { 472 | var greenness = -(stretchShrink * 4 * 255).toFixed(0); 473 | color = "rgb(0,"+greenness+",0)" 474 | } 475 | el.css("color", color) 476 | } 477 | } else { 478 | // Glue gets shrunk by setting its width directly. 479 | el.css("width", l.targetWidths[ix]+"px") 480 | } 481 | } 482 | if (ix == l.nodes.length-1) { 483 | // el.next().after($("
")); 484 | domnode.append($("
")); 485 | } 486 | } 487 | } 488 | } 489 | } 490 | -------------------------------------------------------------------------------- /gutenberg.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Test line breaking 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 | 22 | 23 | 0.05 24 |
25 |
26 | 27 | 28 | 0.90 29 |
30 |
31 | 32 | 33 |
34 | 35 |
36 | 37 |
38 | in principio creavit Deus caelum et terram. terra autem erat inanis et vacua et tenebrae super faciem abyssi et spiritus Dei ferebatur super aquas. dixitque Deus fiat lux et facta est lux. 39 | et vidit Deus lucem quod esset bona et divisit lucem ac tenebras. appellavitque lucem diem et tenebras noctem factumque est vespere et mane dies unus. 40 |
41 | 42 |
43 |
44 | 45 | 46 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Test line breaking 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 |
20 | 21 | 26 |
27 |
28 | 29 | 30 | 1.5 31 |
32 |
33 | 34 | 35 | 0.20 36 |
37 |
38 | 39 | 40 | 0.10 41 |
42 |
43 | 44 | 45 | 0.10 46 |
47 |
48 | 49 | 50 | 0 51 |
52 |
53 | 54 | 55 | 0 56 |
57 | (0 = justify exclusively with width axis, 1 = justify exclusively with letterspacing) 58 |
59 | 65 |
66 | 67 | 68 |
69 | 70 |
71 | 72 |
73 | To Sherlock Holmes she is always "the woman". I have seldom heard him mention her under any other name. In his eyes she eclipses and predominates the whole of her sex. It was not that he felt any emotion akin to love for Irene Adler. All emotions, and that one particularly, were abhorrent to his cold, precise but admirably balanced mind. He was, I take it, the most perfect reasoning and observing machine that the world has seen, but as a lover he would have placed himself in a false position. 74 |
75 | 76 | 77 | 78 | 79 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /newbreak-rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "newbreak" 3 | version = "0.1.0" 4 | authors = ["Simon Cozens "] 5 | edition = "2018" 6 | 7 | [lib] 8 | crate-type = ["cdylib"] # Creates dynamic lib 9 | 10 | [dependencies] 11 | libc = "0.2" 12 | 13 | -------------------------------------------------------------------------------- /newbreak-rust/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate libc; 2 | use core::cmp::Ordering::Equal; 3 | use std::collections::HashMap; 4 | 5 | use std::ptr; 6 | 7 | use libc::c_void; 8 | 9 | #[derive(Debug, Clone)] 10 | pub struct Node { 11 | penalty: i32, 12 | width: f32, 13 | substitution_penalty: i32, 14 | stretch: f32, 15 | shrink: f32, 16 | breakable: bool, 17 | stretch_contribution: Vec, 18 | shrink_contribution: Vec, 19 | stretch_penalty: f32, 20 | text: *mut c_void, 21 | debug_text: String, 22 | original_index: usize, 23 | alternates: Vec, 24 | any_breakable: bool, 25 | any_negative_penalties: bool 26 | } 27 | 28 | #[derive(Debug, Hash, PartialEq, Eq, Clone)] 29 | struct BreakOptions { 30 | full_justify: bool, 31 | start: usize, 32 | end: usize, 33 | unacceptable_ratio: u32, 34 | line_penalty: i32 35 | } 36 | 37 | #[derive(Debug, Clone)] 38 | struct Line<'a> { 39 | ratio: f32, 40 | total_stretch: f32, 41 | total_shrink: f32, 42 | shortfall: f32, 43 | options: BreakOptions, 44 | target_widths: Vec, 45 | badness: i32, 46 | nodes: &'a [Node] 47 | } 48 | 49 | #[derive(Debug, Clone)] 50 | struct Solution<'a> { 51 | lines: Vec>, 52 | // nodes: &'a [Node], 53 | total_badness: i32 54 | } 55 | 56 | #[derive(Debug)] 57 | pub struct Linebreaker<'a> { 58 | nodes: Vec, 59 | hsize: Vec, 60 | memoize_cache: HashMap>, 61 | debugging: bool 62 | } 63 | 64 | impl Node { 65 | fn dummy () -> Node{ 66 | Node { 67 | width: 0.0, 68 | breakable: true, 69 | penalty: 0, 70 | alternates: vec![], 71 | any_breakable: true, 72 | any_negative_penalties: false, 73 | stretch: 0.0, 74 | shrink: 0.0, 75 | stretch_contribution: vec![], 76 | shrink_contribution: vec![], 77 | original_index: 0, 78 | stretch_penalty: 0.0, 79 | substitution_penalty: 0, 80 | debug_text: String::new(), 81 | text: ptr::null_mut() 82 | } 83 | } 84 | // add code here 85 | } 86 | 87 | impl Node { 88 | fn prepare(&mut self, ix: usize) { 89 | self.original_index = ix; 90 | if self.penalty < 0 { self.any_negative_penalties = true } 91 | if self.breakable { self.any_breakable = true } 92 | if self.stretch_contribution.is_empty() { self.stretch_contribution.push(1.0) } 93 | if self.shrink_contribution.is_empty() { self.shrink_contribution.push(1.0) } 94 | for nn in self.alternates.iter() { 95 | if nn.penalty < 0 { self.any_negative_penalties = true } 96 | if nn.breakable { self.breakable = true } 97 | } 98 | } 99 | } 100 | 101 | impl<'a> Linebreaker<'a> { 102 | fn prepare_nodes(&mut self) { 103 | for (ix,n) in self.nodes.iter_mut().enumerate() { 104 | n.prepare(ix); 105 | for nn in n.alternates.iter_mut() { 106 | nn.prepare(ix) 107 | } 108 | } 109 | } 110 | 111 | fn target_for(&self, line_no: usize) -> f32 { 112 | let max_index = self.hsize.len()-1; 113 | if line_no > max_index { 114 | self.hsize[max_index] 115 | } else { 116 | self.hsize[line_no] 117 | } 118 | } 119 | 120 | fn has_any_negative_penalties(&self, nodes: Vec) -> bool { 121 | for n in nodes { 122 | if n.any_negative_penalties { return true; } 123 | } 124 | false 125 | } 126 | 127 | fn badness(&self, line: &Line) -> i32 { 128 | let mut bad: i32; 129 | if line.shortfall == 0.0 { 130 | bad = 0 131 | } else if line.shortfall > 0.0 { 132 | bad = (100.0 * (line.shortfall/(0.001+line.total_stretch)).powf(3.0)).floor() as i32 133 | } else { 134 | bad = (100.0 * (-line.shortfall/(0.001+line.total_shrink)).powf(3.0)).floor() as i32 135 | } 136 | if let Some(last) = line.nodes.last() { bad += last.penalty } 137 | bad += line.options.line_penalty; 138 | for n in line.nodes { 139 | bad += n.substitution_penalty; 140 | } 141 | bad 142 | } 143 | 144 | fn find_breakpoints(&'a self, line_no: usize, options: BreakOptions) -> Solution<'a> { 145 | let target = self.target_for(line_no); 146 | if self.debugging { 147 | println!("Looking for breakpoints {:?}-{:?} to fill {:?} on line {:?}", options.start, options.end, target, line_no); 148 | } 149 | if self.memoize_cache.contains_key(&options) { 150 | return self.memoize_cache.get(&options).unwrap().clone() 151 | } 152 | 153 | let relevant : &[Node] = &self.nodes[options.start .. options.end]; 154 | let mut cur_width = 0.0; 155 | let mut cur_stretch = 0.0; 156 | let mut cur_shrink = 0.0; 157 | let mut considerations: Vec = vec![]; 158 | let mut best_badness = i32::MAX; 159 | // let mut seen_alternate = false; 160 | for (this_node_ix, this_node) in relevant.iter().enumerate() { 161 | // if !this_node.alternates.is_empty() { seen_alternate = true } 162 | if self.debugging { println!("Node {:?} ({:?}) line {:?}", this_node.debug_text, this_node.original_index, line_no);} 163 | if !this_node.any_breakable { 164 | if self.debugging { println!("Adding width {:?} for node {:?}", this_node.width, this_node.debug_text);} 165 | cur_width += this_node.width; cur_stretch += this_node.stretch; cur_shrink += this_node.shrink; 166 | continue; 167 | } 168 | if self.debugging { println!("Target: {:?} Current Width: {:?} Current Stretch: {:?}", target, cur_width, cur_stretch) } 169 | 170 | let last_line = this_node.original_index >= self.nodes.last().unwrap().original_index-2; 171 | if self.debugging { println!("Ratio: {:?} Unacceptable Ratio: {:?} Last line: {:?}", cur_width / target,(options.unacceptable_ratio as f32)/ 100.0, last_line) } 172 | 173 | if (cur_width / target < (options.unacceptable_ratio as f32)/ 1000.0 &&!last_line) || 174 | cur_width / target > (2.0-(options.unacceptable_ratio as f32)/1000.0) { 175 | if self.debugging { println!("Too far") } 176 | cur_width += this_node.width; cur_stretch += this_node.stretch; cur_shrink += this_node.shrink; 177 | continue; 178 | } 179 | // We now have a potential breakpoint 180 | let mut line = Line { 181 | ratio: cur_width / target, 182 | shortfall: target - cur_width, 183 | total_shrink: cur_shrink, 184 | total_stretch: cur_stretch, 185 | target_widths: vec![], 186 | options: options.clone(), 187 | badness: 0, 188 | nodes: &relevant[0..this_node_ix], 189 | }; 190 | line.badness = self.badness(&line); 191 | // if seen_alternate { self.try_to_improve(line, target) } 192 | // if !this_node.breakable && !(line.nodes[line.nodes.len()-1].breakable) { 193 | // cur_width += this_node.width; cur_stretch += this_node.stretch; cur_shrink += this_node.shrink; 194 | // continue; 195 | // } 196 | let badness = line.badness; // May have been improved 197 | let any_negative_penalties = self.has_any_negative_penalties(relevant.to_vec()); 198 | if (best_badness < badness && any_negative_penalties) 199 | || relevant.len() == 1 { 200 | // Won't find any others 201 | } else { 202 | let mut new_consideration = Solution { 203 | total_badness: badness, 204 | lines: vec![line] 205 | }; 206 | if this_node.original_index+1 < options.end { 207 | // Recurse 208 | let mut new_options = options.clone(); 209 | new_options.start = this_node.original_index + 1; 210 | let recursed = self.find_breakpoints(line_no+1, new_options); 211 | for l in recursed.lines { 212 | new_consideration.lines.push(l) 213 | } 214 | new_consideration.total_badness += recursed.total_badness; 215 | if new_consideration.total_badness < best_badness { 216 | best_badness = new_consideration.total_badness 217 | } 218 | considerations.push(new_consideration) 219 | } else { 220 | considerations.push(new_consideration) 221 | } 222 | } 223 | cur_width += this_node.width; cur_stretch += this_node.stretch; cur_shrink += this_node.shrink; 224 | } 225 | 226 | if considerations.is_empty() { 227 | return Solution { 228 | lines: vec![], 229 | total_badness: i32::MAX 230 | } 231 | } 232 | // Otherwise find the best of the bunch 233 | let best_ref = considerations.iter().min_by(|a, b| a.total_badness.partial_cmp(&b.total_badness).unwrap_or(Equal)).unwrap(); 234 | // self.memoize_cache.insert(options, best_ref.clone()); 235 | best_ref.clone() 236 | } 237 | } 238 | 239 | // C Interface 240 | 241 | #[no_mangle] 242 | pub extern "C" fn nb_new(first_line: f32) -> Linebreaker { 243 | let lb = Linebreaker { 244 | nodes: vec![], 245 | hsize: vec![first_line], 246 | breakpoints: vec![], 247 | debugging: false, 248 | memoize_cache: HashMap::new() 249 | }; 250 | unimplemented!() 251 | } 252 | 253 | // pub fn nb_add_node(mut lb: Linebreaker) { 254 | // lb.nodes.push(Node::dummy()); 255 | // } 256 | 257 | pub fn nb_finalize(lb: &mut Linebreaker) { 258 | lb.nodes.push(Node::dummy()); 259 | lb.prepare_nodes(); 260 | } 261 | 262 | // pub fn nb_dobreak<'a>(lb: Linebreaker<'a>, full_justify: bool, 263 | // start: usize, end: usize, unacceptable_ratio: u32, 264 | // line_penalty: i32) -> Solution<'a> { 265 | // let options = BreakOptions { full_justify, 266 | // start, 267 | // end: if end > 0 { end } else { lb.nodes.len()-1 }, 268 | // unacceptable_ratio: if unacceptable_ratio != 0 { unacceptable_ratio } else { 500 }, 269 | // line_penalty: if line_penalty != 0 { line_penalty } else { 10 }, 270 | // }; 271 | // lb.find_breakpoints(0, options) 272 | // } 273 | 274 | #[cfg(test)] 275 | fn make_some_stuff(count: usize) -> Vec { 276 | let mut nodelist = vec![]; 277 | for i in 0..count { 278 | nodelist.push(Node { 279 | width: 100.0, 280 | breakable: false, 281 | penalty: 0, 282 | alternates: vec![], 283 | any_breakable: false, 284 | any_negative_penalties: false, 285 | stretch: 0.0, 286 | shrink: 0.0, 287 | stretch_contribution: vec![], 288 | shrink_contribution: vec![], 289 | original_index: 0, 290 | stretch_penalty: 0.0, 291 | substitution_penalty: 0, 292 | debug_text: format!("laa{:?}", i), 293 | text: ptr::null_mut() 294 | }); 295 | nodelist.push(Node { 296 | width: 10.0, 297 | breakable: true, 298 | penalty: 0, 299 | alternates: vec![], 300 | any_breakable: false, 301 | any_negative_penalties: false, 302 | stretch: if i==count-1 { 1000000.0 } else { 15.0 }, 303 | shrink: 3.0, 304 | stretch_contribution: vec![], 305 | shrink_contribution: vec![], 306 | original_index: 0, 307 | stretch_penalty: 0.0, 308 | substitution_penalty: 0, 309 | debug_text: String::from(" "), 310 | text: ptr::null_mut() 311 | }); 312 | } 313 | nodelist 314 | } 315 | 316 | #[cfg(test)] 317 | fn check_all_breakables_returned(nl: &[Node], lines: Vec) { 318 | let mut nonbreakablecount = 0; 319 | let mut nodesout = 0; 320 | for n in nl { if !n.breakable { nonbreakablecount += 1 } } 321 | for l in lines { 322 | for n in l.nodes { 323 | if !n.breakable { nodesout += 1 } 324 | } 325 | } 326 | assert_eq!(nonbreakablecount, nodesout); 327 | } 328 | 329 | #[test] 330 | fn test_nb_1() { 331 | let mut lb = Linebreaker { nodes: make_some_stuff(2), 332 | hsize: vec![220.0], 333 | debugging: true, 334 | memoize_cache: HashMap::new() 335 | }; 336 | nb_finalize(&mut lb); 337 | let options = BreakOptions { full_justify: false, 338 | start: 0, 339 | end: lb.nodes.len()-1, 340 | unacceptable_ratio: 500, 341 | line_penalty: 10 342 | }; 343 | let sol = lb.find_breakpoints(0, options); 344 | assert_eq!(sol.lines.len(), 1); 345 | check_all_breakables_returned(&lb.nodes, sol.lines); 346 | } 347 | 348 | #[test] 349 | fn test_nb_2() { 350 | let mut lb = Linebreaker { nodes: make_some_stuff(4), 351 | hsize: vec![220.0], 352 | debugging: true, 353 | memoize_cache: HashMap::new() 354 | }; 355 | nb_finalize(&mut lb); 356 | let options = BreakOptions { full_justify: false, 357 | start: 0, 358 | end: lb.nodes.len()-1, 359 | unacceptable_ratio: 500, 360 | line_penalty: 10 361 | }; 362 | let sol = lb.find_breakpoints(0, options); 363 | assert_eq!(sol.lines.len(), 2); 364 | check_all_breakables_returned(&lb.nodes, sol.lines); 365 | } 366 | -------------------------------------------------------------------------------- /newbreak.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This is newbreak, a text justification algorithm designed to play nice 3 | * with variable fonts and non-Latin scripts. 4 | * 5 | * It is based vaguely on the classic Knuth-Plass algorithm as implemented 6 | * in TeX and SILE, but with a few interesting changes. (It's also a lot 7 | * simpler, which may also mean dumber.) 8 | */ 9 | 10 | /** 11 | * A node class to feed to the linebreaker 12 | * @class Node 13 | */ 14 | export class Node { 15 | /** 16 | * The linebreaker just deals with nodes. Everything's a node. There's 17 | * no difference between boxes, glue, and penalties any more. This was 18 | * slightly inspired by Knuth's idea of the "kerf", but more to the point, 19 | * everything needs to be able to stretch, at least a bit! 20 | * @property {number} penalty 21 | * @property {number} width 22 | * @property {number} stretch 23 | * @property {number} shrink 24 | */ 25 | public penalty: number; 26 | public substitutionPenalty?: number; 27 | public width: number; 28 | /** 29 | * This is the total amount of stretch and shrink available through all strategies 30 | */ 31 | public stretch?: number; 32 | public shrink?: number; 33 | public breakable: boolean; 34 | 35 | /** 36 | * stretchContribution is a normalized (sums to one) array which represents 37 | * how much of the stretch value is allocated to different "levels" of justification. 38 | * For example, if kashidas are set to stretchContribution [1,0] and spaces have 39 | * stretchContribution [0,1], then kashidas will be stretched to their limit first 40 | * to fill a line, and if more room is needed then the engine will start expanding 41 | * spaces. If kashidas have [0.5, 0.5] and spaces have [1,0], then spaces and 42 | * kashidas will stretch at the same time, but kashidas will only stretch to half 43 | * their maximum width initially, and will only stretch the other half once spaces 44 | * have fully stretched. 45 | **/ 46 | public stretchContribution: number[]; 47 | public shrinkContribution: number[]; 48 | public stretchPenalty?: number; 49 | 50 | public text?: any; 51 | public debugText?: string; 52 | originalIndex?: number; 53 | 54 | public alternates?: Node[]; 55 | 56 | anyBreakable?: boolean; 57 | anyNegativePenalties?: boolean; 58 | } 59 | 60 | export interface Line { 61 | nodes: Node[]; // Includes selected alternates 62 | ratio?: number; 63 | totalStretch: number; 64 | totalShrink: number; 65 | shortfall: number; 66 | options: BreakOptions; 67 | targetWidths?: number[]; 68 | badness?: number; 69 | } 70 | 71 | // Don't worry about this, it's just an internal thing to make the 72 | // type checking neat. 73 | interface Solution { 74 | lines: Line[]; 75 | totalBadness: number; 76 | } 77 | 78 | interface BreakOptions { 79 | fullJustify?: boolean; 80 | start?: number; 81 | end?: number; 82 | unacceptableRatio?: number; 83 | linePenalty?: number; 84 | } 85 | 86 | const INF_BAD = 10000; 87 | 88 | /** 89 | * The main line breaking algorithm 90 | * @class Linebreaker 91 | */ 92 | 93 | export class Linebreaker { 94 | public nodes: Node[]; 95 | public hsize: number[]; 96 | private memoizeCache: any; 97 | public debugging: boolean; 98 | 99 | /** 100 | * Create a new linebreaker. 101 | * @constructor 102 | * @param {Node[]} nodes - array of nodes to break 103 | * @param {number[]} hsize - array of target line widths 104 | * 105 | * As with TeX, the last width in the `hsize` array is propagated to all 106 | * future lines. For example, if `hsize` is `[70,50]` then the first line 107 | * of the paragraph will be set at 70 units wide and all further lines will 108 | * be 50 units. 109 | **/ 110 | constructor (nodes: Node[], hsize: number[]) { 111 | this.nodes = []; this.hsize = hsize; 112 | for (var n of nodes) { 113 | this.nodes.push({...n}) 114 | } 115 | // Add dummy node to end. 116 | this.nodes.push({ width:0, breakable: true, penalty: 0} as Node) 117 | this.prepareNodes() 118 | this.memoizeCache = {} 119 | } 120 | 121 | /** 122 | Sets up helpful shortcuts on node objects 123 | */ 124 | private prepareNodes() { 125 | for (var thisNodeIx = 0; thisNodeIx < this.nodes.length ; thisNodeIx++) { 126 | var n = this.nodes[thisNodeIx]; 127 | this.prepareNode(n, thisNodeIx); 128 | if (n.alternates) { for (var a of n.alternates) { 129 | this.prepareNode(a, thisNodeIx); 130 | } } 131 | } 132 | } 133 | 134 | private prepareNode(n: Node, ix: number) { 135 | n.originalIndex = ix; 136 | if (n.penalty < 0) { n.anyNegativePenalties = true } 137 | if (n.breakable) { n.anyBreakable = true } 138 | if (!n.stretchContribution) {n.stretchContribution = [1] } 139 | if (!n.shrinkContribution) {n.shrinkContribution = [1] } 140 | if (n.alternates) { for (var a of n.alternates) { 141 | if (a.penalty < 0) { n.anyNegativePenalties = true } 142 | if (a.breakable) { n.anyBreakable = true } 143 | } } 144 | } 145 | 146 | /** 147 | * Run the break algorithm. 148 | * You may want to feed the output of this into the `ratios` method below. 149 | * @returns {number[]} An array of node indexes at which to break. 150 | */ 151 | public doBreak (options:BreakOptions = {}) :Line[] { 152 | var defaultOptions :BreakOptions = { 153 | start: 0, 154 | end: this.nodes.length-1, 155 | fullJustify: false, 156 | unacceptableRatio: 0.5, 157 | linePenalty: 10 158 | } 159 | var best = this.findBreakpoints(0, { ...options, ...defaultOptions }); 160 | this.assignTargetWidths(best); 161 | this.debug("Final best consideration:") 162 | this.debugConsideration(best.lines); 163 | return best.lines; 164 | } 165 | 166 | // A shortcut for finding the target length of the given line number. 167 | public targetFor(lineNo: number) :number { 168 | return this.hsize[lineNo > this.hsize.length-1 ? this.hsize.length-1 : lineNo] 169 | } 170 | 171 | public hasAnyNegativePenalties(nodelist: Node[]) :boolean { 172 | for (var n of nodelist) { 173 | if (n.anyNegativePenalties) { return true } 174 | } 175 | return false; 176 | } 177 | 178 | /* 179 | Finally we arrive at the core of the algorithm. The basic principle is 180 | actually quite simple: find the optimum solution (minimize the number 181 | of demerits) given all the possible, feasible breakpoints in a paragraph. 182 | You do this by walking the tree of all possible breakpoints. Tree 183 | search algorithms are recursive algorithms. Here's another way to 184 | say it: To find the best way to break a paragraph, 185 | 186 | * Find the possible breakpoints for line one. 187 | * For each possible line-one breakpoint, find the best way to 188 | break the rest of the paragraph. 189 | * Compare the solutions and return the best one. 190 | 191 | This leads itself very naturally to a recursive implementation. 192 | 193 | The reason this is so complicated in the case of TeX is that Knuth was 194 | a great programmer trying to write a really neat algorithm that would 195 | run quickly on a small computer with little memory. So he had to do all 196 | kinds of clever linear programming to run in the optimum time and memory. 197 | I am not a great programmer and I don't give a damn. I'm quite happy to 198 | use the recursive implementation, trading off time and memory for 199 | simplicity. 200 | */ 201 | 202 | public findBreakpoints(lineNo: number, options: BreakOptions) : Solution { 203 | let target = this.targetFor(lineNo) 204 | 205 | /* 206 | One of the reasons for *not* using a recursive solution is that 207 | they tend to balloon time and memory, and also redo the same computation 208 | lots of times. We avoid both these problems by memoization. 209 | */ 210 | let key = JSON.stringify(options) 211 | this.debug(`Looking for breakpoints ${options.start}->${options.end} to fill ${target} on line ${lineNo}`,lineNo) 212 | if (key in this.memoizeCache) { 213 | this.debug(`Returned from cache`,lineNo); 214 | this.debug(this.memoizeCache[key],lineNo); 215 | return this.memoizeCache[key] 216 | } 217 | 218 | let relevant = this.nodes.slice(options.start, options.end+1); 219 | var anyNegativePenalties = this.hasAnyNegativePenalties(relevant) 220 | // This represents how far along the line we are towards the target width. 221 | let curWidth = 0; 222 | let curStretch = 0; 223 | let curShrink = 0; 224 | 225 | let considerations = [] as Solution[]; 226 | var bestBadness = Infinity; 227 | var seenAlternate = false; 228 | var that = this; 229 | var node 230 | let addNodeToTotals = function (n) { 231 | that.debug(`Adding width ${n.width} for node ${n.debugText||n.text||""}`, lineNo) 232 | curWidth += n.width; 233 | curStretch += n.stretch || 0; 234 | curShrink += n.shrink || 0; 235 | } 236 | 237 | /* 238 | Now we walk through the relevant nodes, looking for feasible breakpoints 239 | and keeping track of how close we are to the target. 240 | */ 241 | for (var thisNodeIx = 0; thisNodeIx < relevant.length ; thisNodeIx++) { 242 | let thisNode = relevant[thisNodeIx]; 243 | if (thisNode.alternates && thisNode.alternates.length > 0) { 244 | seenAlternate = true; 245 | } 246 | 247 | // If we can't break here... don't try to break here. 248 | this.debug(`Node ${thisNode.originalIndex} ${thisNode.debugText||thisNode.text||""}`, lineNo) 249 | if (!thisNode.anyBreakable) { 250 | addNodeToTotals(thisNode); 251 | continue; 252 | } 253 | this.debug(` Target: ${target}. Current width: ${curWidth}. Current stretch: ${curStretch}`, lineNo); 254 | var lastLine = thisNode.originalIndex >= this.nodes[this.nodes.length-1].originalIndex-2; 255 | // If we're a long way from the end and stretching to get there would be horrible, 256 | // don't even bother investigating this breakpoint. 257 | // console.log("Width",curWidth, "Target:", target, "Ratio: ",curWidth/target, "Unacceptable: ",options.unacceptableRatio) 258 | if ( (curWidth / target < options.unacceptableRatio &&!lastLine) || 259 | curWidth / target > (2-options.unacceptableRatio)) { 260 | this.debug(` Too far`, lineNo); 261 | addNodeToTotals(thisNode); 262 | continue; 263 | } 264 | 265 | // We have a potential breakpoint. Build a Line node 266 | // Find out how bad this potential breakpoint is. 267 | this.debug(`Possibility!`, lineNo) 268 | var line:Line = { 269 | nodes: relevant.slice(0,thisNodeIx), 270 | ratio: curWidth / target, 271 | shortfall: target - curWidth, 272 | totalShrink: curShrink, 273 | totalStretch: curStretch, 274 | options: options 275 | } 276 | 277 | line.badness = this.badness(line) 278 | if (seenAlternate) { 279 | line = this.tryToImprove(line, target); 280 | } 281 | 282 | // If we are at e.g. a hyphenation point (not breakable but has breakable 283 | // alternate) then only consider this is if the last node has become breakable 284 | // through considering the alternates 285 | if (!thisNode.breakable && !(line.nodes[line.nodes.length-1].breakable)) { 286 | that.debug(`Adding width ${thisNode.width} for node ${thisNode.debugText||thisNode.text||""}`, lineNo) 287 | addNodeToTotals(thisNode); 288 | continue; 289 | } 290 | 291 | let badness = line.badness; 292 | this.debug(` Badness was ${badness}`, lineNo) 293 | 294 | if (bestBadness < badness && !anyNegativePenalties) { 295 | // We have a better option already, and we have no chance 296 | // to improve this option, don't bother. 297 | } else if (relevant.length == 1) { 298 | // We aren't going to find any other nodes. Don't bother 299 | } else { 300 | // It's worth a further look at this breakpoint. 301 | // If we have nodes A...Z and we test a break at C, we need to work 302 | // out the best way to break the sub-paragraph D...Z. 303 | 304 | // Remembering that "Breakpoint path = Breakpoint for first line 305 | // + breakpoint path for remainder of paragraph", first we make 306 | // a line set which holds the first line... 307 | 308 | var newConsideration : Solution = { 309 | totalBadness: badness, 310 | lines: [ line ] 311 | }; 312 | 313 | if (thisNode.originalIndex+1 < options.end) { 314 | this.debug(`Recursing, now start at ${thisNode.originalIndex+1}`, lineNo) 315 | let recursed = this.findBreakpoints(lineNo+1, { 316 | ...options, 317 | start: thisNode.originalIndex+1, 318 | end: options.end, 319 | }) 320 | this.debug(`In that recursion, total badness = ${recursed.totalBadness}`) 321 | 322 | // ...and then we add to it the current solution 323 | newConsideration.lines = newConsideration.lines.concat(recursed.lines); 324 | newConsideration.totalBadness += recursed.totalBadness; 325 | 326 | // Save this option if it's better than we've seen already, 327 | // to save recursing into worse ones. 328 | if (newConsideration.totalBadness < bestBadness) { 329 | bestBadness = newConsideration.totalBadness 330 | } 331 | considerations.push(newConsideration); 332 | } else { 333 | considerations.push(newConsideration); 334 | } 335 | } 336 | addNodeToTotals(thisNode); 337 | } 338 | 339 | // If we found nothing, give up. 340 | if (considerations.length < 1) { 341 | return { totalBadness: INF_BAD * INF_BAD, lines: [] } as Solution; 342 | } 343 | 344 | // Otherwise, find the best of the bunch. 345 | this.debug("Choosing between considerations:") 346 | for (var c of considerations) { 347 | this.debug("With badness "+c.totalBadness+": ") 348 | this.debugConsideration(c.lines) 349 | } 350 | let best = considerations.reduce( (a, b) => a.totalBadness <= b.totalBadness ? a : b ); 351 | this.debug(`Best answer for ${key} was:`, lineNo) 352 | this.debugConsideration(best.lines) 353 | this.debug(` with badness ${best.totalBadness}`,lineNo) 354 | 355 | // Store it in the memoize cache for next time. 356 | this.memoizeCache[key] = best; 357 | return best; 358 | } 359 | 360 | private badness(line: Line): number { 361 | var bad = 0; 362 | if (line.shortfall == 0) { 363 | bad = 0 364 | } else if (line.shortfall > 0) { 365 | // XXX use stretch/shrink penalties here instead 366 | bad = Math.floor(100 * (line.shortfall/(0.001+line.totalStretch))**3) 367 | } else { 368 | bad = Math.floor(100 * (-line.shortfall/(0.001+line.totalShrink))**3) 369 | } 370 | // consider also penalties. Break penalty: 371 | bad += line.nodes[line.nodes.length-1].penalty 372 | // Line penalty 373 | bad += line.options.linePenalty 374 | // Invert negative penalties 375 | for (var n of line.nodes) { 376 | if (n.penalty < 0) { bad += n.penalty * n.penalty; } 377 | } 378 | // Any substitutions 379 | for (var n of line.nodes) { 380 | bad += n.substitutionPenalty || 0; 381 | } 382 | return bad; 383 | } 384 | 385 | 386 | private tryToImprove(line: Line, target:number): Line { 387 | var nodesWithAlternates = line.nodes.map( n => [ n, ...(n.alternates||[]) ]) 388 | var set:Node[]; 389 | var bestLine = line; 390 | this.debug("Trying to improve, base badness is "+ line.badness) 391 | for (set of this._cartesian_set(nodesWithAlternates)) { 392 | var newLine:Line = {nodes: set, totalShrink: 0, totalStretch: 0, shortfall: target, options: line.options } 393 | for (var n of set) { 394 | newLine.totalShrink += n.shrink; 395 | newLine.totalStretch += n.stretch; 396 | newLine.shortfall -= n.width; 397 | } 398 | newLine.badness = this.badness(newLine) 399 | this.debug("New line is "+ newLine.badness) 400 | if (newLine.badness < bestLine.badness) { 401 | bestLine = newLine; 402 | } 403 | } 404 | bestLine.ratio = (target-bestLine.shortfall) / target; 405 | return bestLine 406 | } 407 | 408 | private debugConsideration(lines: Line[]) { 409 | this.debug("---") 410 | for (let l of lines) { 411 | this.debug(l.ratio.toFixed(3)+ " " + l.nodes.map( (a) => a.debugText||a.text||"").join("")) 412 | } 413 | this.debug("---") 414 | } 415 | 416 | private debug(msg: any, lineNo=0) { 417 | if (this.debugging) { 418 | var spacer = new Array(lineNo+1).join(" + ") 419 | console.log(spacer + msg); 420 | } 421 | } 422 | 423 | private assignTargetWidths(solution: Solution) { 424 | for (var line of solution.lines) { 425 | this.assignTargetWidthsToLine(line) 426 | } 427 | } 428 | 429 | private assignTargetWidthsToLine(line: Line) { 430 | line.targetWidths = line.nodes.map(n => n.width); 431 | if (line.shortfall == 0) { 432 | return 433 | } 434 | var level = 0; 435 | if (line.shortfall > 0) { 436 | while (line.shortfall > 0) { // We need to expand 437 | var thisLevelStretch = line.nodes.map( n => n.stretch*(n.stretchContribution[level] || 0)); 438 | var thisLevelTotalStretch = thisLevelStretch.reduce( (a,c) => a+c, 0); // Sum 439 | if (thisLevelTotalStretch == 0) { break; } 440 | 441 | var ratio = line.shortfall / (0.001+thisLevelTotalStretch); 442 | if (ratio > 1) { ratio = 1 } 443 | 444 | line.targetWidths = line.targetWidths.map( (w,ix) => w + ratio * thisLevelStretch[ix]); 445 | line.shortfall -= thisLevelTotalStretch * ratio; 446 | level = level + 1; 447 | } 448 | } else { 449 | while (line.shortfall < 0) { // We need to expand 450 | var thisLevelShrink = line.nodes.map( n => n.shrink*(n.shrinkContribution[level] || 0)); 451 | var thisLevelTotalShrink = thisLevelShrink.reduce( (a,c) => a+c, 0); // Sum 452 | if (thisLevelTotalShrink == 0) { break; } 453 | 454 | var ratio = -line.shortfall / (0.001+thisLevelTotalShrink); 455 | if (ratio > 1) { ratio = 1 } 456 | 457 | line.targetWidths = line.targetWidths.map( (w,ix) => w - ratio * thisLevelShrink[ix]); 458 | line.shortfall += thisLevelTotalShrink * ratio; 459 | level = level + 1; 460 | } 461 | } 462 | this.debug("Final widths:"+ line.targetWidths.join(", ")) 463 | } 464 | 465 | public _cartesian_set(arg) { 466 | const r = []; 467 | const max = arg.length-1; 468 | let helper = (arr, i) => { 469 | for (let j=0, l=arg[i].length; j").appendTo(document.body).hide(); 38 | } 39 | for (var _i = 0, _a = ["font-style", "font-variant", "font-weight", "font-size", "font-family", "font-stretch", "font-variation-settings", "font-feature-settings"]; _i < _a.length; _i++) { 40 | var c = _a[_i]; 41 | fakeEl.css(c, elProto.css(c)); 42 | } 43 | fakeEl.text(text); 44 | return fakeEl.width(); 45 | } 46 | ; 47 | var DomBreak = /** @class */ (function () { 48 | function DomBreak(domnode, options) { 49 | var _this = this; 50 | this.cacheComputedShrink = {}; 51 | this.cacheComputedStretch = {}; 52 | this.cacheSpaceWidth = -1; 53 | this.doneResizeObserver = false; 54 | this.options = __assign({}, defaultOptions, options); 55 | this.domNode = domnode; 56 | if (domnode[0].hasAttribute("data-text-stretch")) { 57 | this.options.textStretch = domnode.data("text-stretch"); 58 | } 59 | if (domnode[0].hasAttribute("data-text-shrink")) { 60 | this.options.textShrink = domnode.data("text-shrink"); 61 | } 62 | if (domnode.data("method")) { 63 | this.options.method = domnode.data("method"); 64 | } 65 | this.origContents = domnode.contents(); 66 | if (!this.options.customNodeMaker) { 67 | this.options.customNodeMaker = function (el) { 68 | return _this.textToNodes(domnode, el.text()); 69 | }; 70 | } 71 | this.rebuild(); 72 | } 73 | DomBreak.prototype.rebuild = function () { 74 | var _this = this; 75 | this.cacheComputedStretch = {}; 76 | this.cacheComputedShrink = {}; 77 | this.nodelist = this.DOMToNodes(this.domNode, this.origContents); 78 | var doResize = function (evt, ui) { _this.layout(); }; 79 | if (this.options.resizeMode == "jquery-ui") { 80 | if (this.domNode.resizable("instance")) { 81 | this.domNode.resizable("destroy"); 82 | } 83 | setTimeout(function () { 84 | _this.domNode.resizable({ resize: doResize }); 85 | doResize(null, null); 86 | }, 0.1); 87 | } 88 | else if (this.options.resizeMode == "resizeobserver" && !this.doneResizeObserver) { 89 | var ro = new ResizeObserver(doResize); 90 | ro.observe(this.domNode[0]); 91 | console.log("Observing", this.domNode[0]); 92 | this.doneResizeObserver = true; 93 | } 94 | }; 95 | DomBreak.prototype.makeGlue = function (domnode) { 96 | var sp = $(""); 97 | sp.addClass("glue"); 98 | // Because it's hard to measure a space directly we have to do a bit of 99 | // messing about to work out the width. 100 | if (this.cacheSpaceWidth == -1) { 101 | this.cacheSpaceWidth = textWidth("x x", domnode) - textWidth("xx", domnode); 102 | } 103 | sp.width(this.cacheSpaceWidth); 104 | return { 105 | text: sp, 106 | debugText: " ", 107 | penalty: 0, 108 | stretchContribution: [1, 0], 109 | shrinkContribution: [1, 0], 110 | breakable: true, 111 | width: sp.width(), 112 | stretch: this.options.spaceStretch * sp.width(), 113 | shrink: this.options.spaceShrink * sp.width() 114 | }; 115 | }; 116 | DomBreak.prototype.makeForcedBreak = function (domnode) { 117 | var rv = []; 118 | if (!this.options.fullJustify) { 119 | var sp = $(""); 120 | sp.addClass("glue"); 121 | rv.push({ 122 | debugText: " ", 123 | text: sp, 124 | breakable: false, 125 | penalty: 0, 126 | width: sp.width(), 127 | stretch: 100000, 128 | shrink: 0 129 | }); 130 | } 131 | var b = $(""); 132 | rv.push({ 133 | debugText: "\n", 134 | text: b, 135 | breakable: true, 136 | penalty: -100000, 137 | stretch: 0, 138 | width: 0, 139 | }); 140 | return rv; 141 | }; 142 | DomBreak.prototype.makeText = function (t, domnode) { 143 | var sp = $(""); 144 | sp.addClass("text"); 145 | sp.text(t); 146 | var length = t.length; 147 | var width = textWidth(t, domnode); 148 | var maximumLSavailable = (length - 1) * this.options.textLetterSpacing; 149 | var maximumVarfontStretchAvailable; 150 | var shrink; 151 | if (this.options.textStretch == "computed") { 152 | maximumVarfontStretchAvailable = this.computeMaxWidth(sp) - width; 153 | // console.log(t+" can stretch by "+maximumVarfontStretchAvailable+"px") 154 | } 155 | else { 156 | var maximumVarfontStretchAvailable = this.options.textStretch * width; 157 | } 158 | if (this.options.textShrink == "computed") { 159 | shrink = width - this.computeMinWidth(sp); 160 | } 161 | else { 162 | shrink = this.options.textShrink * width; 163 | } 164 | this.setToWidth(sp, width); 165 | var stretch = maximumLSavailable * this.options.textLetterSpacingPriority + maximumVarfontStretchAvailable * (1 - this.options.textLetterSpacingPriority); 166 | var node = { 167 | debugText: t, 168 | text: sp, 169 | breakable: false, 170 | penalty: 0, 171 | stretchContribution: [0, 1], 172 | shrinkContribution: [0, 1], 173 | width: width, 174 | stretch: stretch, 175 | shrink: shrink 176 | }; 177 | if (this.options.customizeTextNode) { 178 | // Avoid horrible recursive mess 179 | var saveThisFunction = this.options.customizeTextNode; 180 | delete this.options["customizeTextNode"]; 181 | var res = saveThisFunction(t, node); 182 | this.options.customizeTextNode = saveThisFunction; 183 | if (res) { 184 | return res; 185 | } 186 | } 187 | sp.attr("width", node.width); 188 | sp.attr("stretch", node.stretch); 189 | sp.attr("shrink", node.shrink); 190 | return [node]; 191 | }; 192 | DomBreak.prototype.computeMaxWidth = function (sp) { 193 | if (this.cacheComputedStretch[sp.text()]) { 194 | return this.cacheComputedStretch[sp.text()]; 195 | } 196 | var measureEl = sp.clone().appendTo(this.domNode).hide(); 197 | this.setToWidth(measureEl, 1000); 198 | var w = measureEl.width(); 199 | measureEl.remove(); 200 | this.cacheComputedStretch[sp.text()] = w; 201 | return w; 202 | }; 203 | DomBreak.prototype.computeMinWidth = function (sp) { 204 | if (this.cacheComputedShrink[sp.text()]) { 205 | return this.cacheComputedShrink[sp.text()]; 206 | } 207 | var measureEl = sp.clone().appendTo(this.domNode).hide(); 208 | this.setToWidth(measureEl, 0); 209 | var w = measureEl.width(); 210 | measureEl.remove(); 211 | this.cacheComputedShrink[sp.text()] = w; 212 | return w; 213 | }; 214 | DomBreak.prototype.hyphenate = function (t) { 215 | if (this.options.hyphenate) { 216 | if (!this.hyphenator) { 217 | this.hyphenator = new Hyphenator(); 218 | } 219 | return this.hyphenator.hyphenate(t); 220 | } 221 | return [t]; 222 | }; 223 | // The first job is to create nodes, both in the DOM and 224 | // newbreak `Node` objects, representing each word and space. 225 | DomBreak.prototype.DOMToNodes = function (domnode, contents) { 226 | var _this = this; 227 | domnode.empty(); 228 | domnode.addClass("nowrap"); 229 | var nodelist = []; 230 | contents.each(function (i, el) { 231 | if (el.nodeType == 3) { 232 | nodelist = nodelist.concat(_this.textToNodes(domnode, el.textContent)); 233 | } 234 | else if (el.nodeType == 1) { 235 | el = el; 236 | var nodes; 237 | if (el.tagName == "BR") { 238 | nodes = _this.makeForcedBreak(domnode); 239 | } 240 | else { 241 | nodes = _this.options.customNodeMaker($(el)); 242 | } 243 | for (var _i = 0, nodes_1 = nodes; _i < nodes_1.length; _i++) { 244 | var n = nodes_1[_i]; 245 | nodelist.push(n); 246 | domnode.append(n.text); 247 | } 248 | } 249 | }); 250 | return nodelist; 251 | }; 252 | DomBreak.prototype.textToNodes = function (domnode, text) { 253 | // We'll empty the container and tell it that we're handling wrapping. 254 | var nodelist = []; 255 | for (var _i = 0, _a = text.split(/(\s+)/m); _i < _a.length; _i++) { 256 | var t = _a[_i]; 257 | var n; 258 | if (t.match(/\s+/)) { 259 | // This is just space. Turn it into a glue node. 260 | n = this.makeGlue(domnode); 261 | nodelist.push(n); 262 | domnode.append(n.text); 263 | } 264 | else { 265 | // This is text. Turn it into hyphenated fragments. 266 | // If hyphenation is off, we just get the text back. 267 | var fragments = this.hyphenate(t); 268 | for (var idx = 0; idx < fragments.length; idx++) { 269 | var frag = fragments[idx]; 270 | // Turn each fragment into a `Node`, pop it on the list 271 | // and put the word back into the DOM. 272 | var nl = this.makeText(frag, domnode); 273 | for (var _b = 0, nl_1 = nl; _b < nl_1.length; _b++) { 274 | n = nl_1[_b]; 275 | nodelist.push(n); 276 | domnode.append(n.text); 277 | } 278 | // if (idx != fragments.length-1) { 279 | // // Add hyphens between each fragment. 280 | // n = this.makeHyphen(domnode); 281 | // nodelist.push(n); 282 | // domnode.append(n.text); 283 | // } 284 | } 285 | } 286 | } 287 | if (!this.options.fullJustify) { 288 | // At the end of the paragraph we need super-stretchy glue. 289 | var stretchy = this.makeGlue(domnode); 290 | stretchy.stretch = 10000; 291 | nodelist.push(stretchy); 292 | } 293 | return nodelist; 294 | }; 295 | DomBreak.prototype.setToWidth = function (el, width) { 296 | var tries = 20; 297 | // console.log(`Setting ${el.text()} to width ${width}, currently ${el.width()}`) 298 | if (this.options.method == "font-stretch") { 299 | var guess = width / el.width() * 100; 300 | var min = 0; // XXX 301 | var max = 200; // XXX 302 | } 303 | else { 304 | var guess = width / (0.001 + el.width()) * 1000; 305 | var min = 0; // XXX 306 | var max = 1000; // XXX 307 | } 308 | if (Math.abs(el.width() - width) < 1) { 309 | return; 310 | } 311 | while (tries--) { 312 | if (this.options.method == "font-stretch") { 313 | el.css("font-stretch", guess + "%"); 314 | } 315 | else { 316 | el.css("font-variation-settings", "'" + this.options.method + "' " + guess); 317 | } 318 | var newWidth = textWidth(el.text(), el); 319 | // console.log(`Width is now ${newWidth}, desired is ${width}`) 320 | if (Math.abs(newWidth - width) < 1) { 321 | return; 322 | } 323 | else if (newWidth > width) { 324 | max = guess; 325 | } 326 | else if (newWidth < width) { 327 | min = guess; 328 | } 329 | guess = (min + max) / 2; 330 | // console.log(`New guess is ${guess}`) 331 | } 332 | }; 333 | DomBreak.prototype.layout = function (desiredWidth) { 334 | var nodelist = this.nodelist; 335 | var domnode = this.domNode; 336 | if (!desiredWidth) { 337 | desiredWidth = domnode.width(); 338 | } 339 | var breaker = new newbreak_1.Linebreaker(nodelist, [domnode.width()]); 340 | var lines = breaker.doBreak({ 341 | fullJustify: this.options.fullJustify, 342 | unacceptableRatio: (this.options.fullJustify ? 0 : 0.5) 343 | }); 344 | domnode.find("br").remove(); 345 | domnode.children("span").remove(); 346 | // Stretch and shrink each node as appropriate. We'll add linebreaks later. 347 | for (var _i = 0, lines_1 = lines; _i < lines_1.length; _i++) { 348 | var l = lines_1[_i]; 349 | for (var ix = 0; ix < l.nodes.length; ix++) { 350 | var n = l.nodes[ix]; 351 | var el = n.text; 352 | el.attr("width", n.width); 353 | el.attr("desired-width", l.targetWidths[ix]); 354 | el.attr("stretch", n.stretch); 355 | el.attr("shrink", n.shrink); 356 | domnode.append(el); 357 | if (n.stretch > 0 || n.shrink > 0) { 358 | if (el.hasClass("text")) { 359 | // Text gets shrunk with the variable font CSS rule. 360 | this.setToWidth(el, l.targetWidths[ix]); 361 | el.css("letter-spacing", "normal"); 362 | if (this.options.colorize) { 363 | var stretchShrink = (n.width - l.targetWidths[ix]) / n.width; 364 | var color; 365 | if (stretchShrink > 0) { 366 | var redness = (stretchShrink * 4 * 255).toFixed(0); 367 | color = "rgb(" + redness + ",0,0)"; 368 | } 369 | else { 370 | var greenness = -(stretchShrink * 4 * 255).toFixed(0); 371 | color = "rgb(0," + greenness + ",0)"; 372 | } 373 | el.css("color", color); 374 | } 375 | } 376 | else { 377 | // Glue gets shrunk by setting its width directly. 378 | el.css("width", l.targetWidths[ix] + "px"); 379 | } 380 | } 381 | if (ix == l.nodes.length - 1) { 382 | // el.next().after($("
")); 383 | domnode.append($("
")); 384 | } 385 | } 386 | } 387 | }; 388 | return DomBreak; 389 | }()); 390 | exports.DomBreak = DomBreak; 391 | //# sourceMappingURL=dombreak.js.map -------------------------------------------------------------------------------- /src/dombreak.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"dombreak.js","sourceRoot":"","sources":["../dombreak.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /src/entry.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | // Just an entry point to the typescript bundle. 3 | Object.defineProperty(exports, "__esModule", { value: true }); 4 | var dombreak_1 = require("./dombreak"); 5 | var d; 6 | function changeFont(font) { 7 | $("div#testbox").css("font-family", font); 8 | window["fontSpy"](font, { 9 | success: function () { 10 | if (d) { 11 | d.rebuild(); 12 | } 13 | else { 14 | d = new dombreak_1.DomBreak($("#testbox"), { textLetterSpacingPriority: 0.25 }); 15 | } 16 | } 17 | }); 18 | } 19 | changeFont("Encode Sans"); 20 | $("select").on("change", function (e) { 21 | changeFont($(e.target).val()); 22 | }); 23 | $(".slidecontainer input").on("input", function (e) { 24 | var input = $(e.target); 25 | var id = input[0].id; 26 | var v = input.val() / 100.0; 27 | $("#" + id + "Value").text(v); 28 | }); 29 | $(".slidecontainer input").change(function (e) { 30 | var input = $(e.target); 31 | var id = input[0].id; 32 | if (id == "hyphenate") { 33 | d.options.hyphenate = !!input.prop('checked'); 34 | } 35 | else if (id == "fulljustify") { 36 | $("#testbox").toggleClass("fulljustify"); 37 | } 38 | else { 39 | var v = input.val() / 100.0; 40 | $("#" + id + "Value").text(v); 41 | d.options[id] = v; 42 | } 43 | d.rebuild(); 44 | }); 45 | //# sourceMappingURL=entry.js.map -------------------------------------------------------------------------------- /src/entry.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"entry.js","sourceRoot":"","sources":["../entry.ts"],"names":[],"mappings":";AAAA,gDAAgD;;AAEhD,uCAAsC;AAEtC,IAAI,CAAW,CAAC;AAEhB,SAAS,UAAU,CAAC,IAAI;IACtB,CAAC,CAAC,aAAa,CAAC,CAAC,GAAG,CAAC,aAAa,EAAE,IAAI,CAAC,CAAA;IACzC,MAAM,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE;QACtB,OAAO,EAAE;YACP,IAAI,CAAC,EAAE;gBACL,CAAC,CAAC,OAAO,EAAE,CAAA;aACZ;iBAAM;gBACL,CAAC,GAAG,IAAI,mBAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,EAAE,EAAC,yBAAyB,EAAE,IAAI,EAAC,CAAC,CAAC;aACpE;QACH,CAAC;KACF,CAAC,CAAA;AACJ,CAAC;AAED,UAAU,CAAC,aAAa,CAAC,CAAC;AAE1B,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,QAAQ,EAAE,UAAC,CAAC;IACzB,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;AAChC,CAAC,CAAC,CAAA;AAEF,CAAC,CAAC,uBAAuB,CAAC,CAAC,EAAE,CAAC,OAAO,EAAE,UAAC,CAAC;IACvC,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IACxB,IAAI,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACrB,IAAI,CAAC,GAAI,KAAK,CAAC,GAAG,EAAa,GAAG,KAAK,CAAA;IACvC,CAAC,CAAC,MAAI,EAAE,UAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAC3B,CAAC,CAAC,CAAC;AAEH,CAAC,CAAC,uBAAuB,CAAC,CAAC,MAAM,CAAC,UAAC,CAAC;IAClC,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IACxB,IAAI,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACrB,IAAI,EAAE,IAAI,WAAW,EAAE;QACrB,CAAC,CAAC,OAAO,CAAC,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;KAC/C;SAAM,IAAI,EAAE,IAAI,aAAa,EAAE;QAC9B,CAAC,CAAC,UAAU,CAAC,CAAC,WAAW,CAAC,aAAa,CAAC,CAAA;KACzC;SAAM;QACL,IAAI,CAAC,GAAI,KAAK,CAAC,GAAG,EAAa,GAAG,KAAK,CAAA;QACvC,CAAC,CAAC,MAAI,EAAE,UAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACzB,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;KACnB;IACD,CAAC,CAAC,OAAO,EAAE,CAAC;AACd,CAAC,CAAC,CAAA"} -------------------------------------------------------------------------------- /src/hyphenator-en.js: -------------------------------------------------------------------------------- 1 | HyphenatorEnglish = {}; 2 | HyphenatorEnglish.patterns = [".ach4", ".ad4der", ".af1t", ".al3t", ".am5at", ".an5c", ".ang4", 3 | ".ani5m", ".ant4", ".an3te", ".anti5s", ".ar5s", ".ar4tie", ".ar4ty", 4 | ".as3c", ".as1p", ".as1s", ".aster5", ".atom5", ".au1d", ".av4i", 5 | ".awn4", ".ba4g", ".ba5na", ".bas4e", ".ber4", ".be5ra", ".be3sm", 6 | ".be5sto", ".bri2", ".but4ti", ".cam4pe", ".can5c", ".capa5b", 7 | ".car5ol", ".ca4t", ".ce4la", ".ch4", ".chill5i", ".ci2", ".cit5r", 8 | ".co3e", ".co4r", ".cor5ner", ".de4moi", ".de3o", ".de3ra", ".de3ri", 9 | ".des4c", ".dictio5", ".do4t", ".du4c", ".dumb5", ".earth5", ".eas3i", 10 | ".eb4", ".eer4", ".eg2", ".el5d", ".el3em", ".enam3", ".en3g", ".en3s", 11 | ".eq5ui5t", ".er4ri", ".es3", ".eu3", ".eye5", ".fes3", ".for5mer", 12 | ".ga2", ".ge2", ".gen3t4", ".ge5og", ".gi5a", ".gi4b", ".go4r", 13 | ".hand5i", ".han5k", ".he2", ".hero5i", ".hes3", ".het3", ".hi3b", 14 | ".hi3er", ".hon5ey", ".hon3o", ".hov5", ".id4l", ".idol3", ".im3m", 15 | ".im5pin", ".in1", ".in3ci", ".ine2", ".in2k", ".in3s", ".ir5r", 16 | ".is4i", ".ju3r", ".la4cy", ".la4m", ".lat5er", ".lath5", ".le2", 17 | ".leg5e", ".len4", ".lep5", ".lev1", ".li4g", ".lig5a", ".li2n", 18 | ".li3o", ".li4t", ".mag5a5", ".mal5o", ".man5a", ".mar5ti", ".me2", 19 | ".mer3c", ".me5ter", ".mis1", ".mist5i", ".mon3e", ".mo3ro", ".mu5ta", 20 | ".muta5b", ".ni4c", ".od2", ".odd5", ".of5te", ".or5ato", ".or3c", 21 | ".or1d", ".or3t", ".os3", ".os4tl", ".oth3", ".out3", ".ped5al", 22 | ".pe5te", ".pe5tit", ".pi4e", ".pio5n", ".pi2t", ".pre3m", ".ra4c", 23 | ".ran4t", ".ratio5na", ".ree2", ".re5mit", ".res2", ".re5stat", ".ri4g", 24 | ".rit5u", ".ro4q", ".ros5t", ".row5d", ".ru4d", ".sci3e", ".self5", 25 | ".sell5", ".se2n", ".se5rie", ".sh2", ".si2", ".sing4", ".st4", 26 | ".sta5bl", ".sy2", ".ta4", ".te4", ".ten5an", ".th2", ".ti2", ".til4", 27 | ".tim5o5", ".ting4", ".tin5k", ".ton4a", ".to4p", ".top5i", ".tou5s", 28 | ".trib5ut", ".un1a", ".un3ce", ".under5", ".un1e", ".un5k", ".un5o", 29 | ".un3u", ".up3", ".ure3", ".us5a", ".ven4de", ".ve5ra", ".wil5i", 30 | ".ye4", "4ab.", "a5bal", "a5ban", "abe2", "ab5erd", "abi5a", "ab5it5ab", 31 | "ab5lat", "ab5o5liz", "4abr", "ab5rog", "ab3ul", "a4car", "ac5ard", 32 | "ac5aro", "a5ceou", "ac1er", "a5chet", "4a2ci", "a3cie", "ac1in", 33 | "a3cio", "ac5rob", "act5if", "ac3ul", "ac4um", "a2d", "ad4din", 34 | "ad5er.", "2adi", "a3dia", "ad3ica", "adi4er", "a3dio", "a3dit", 35 | "a5diu", "ad4le", "ad3ow", "ad5ran", "ad4su", "4adu", "a3duc", "ad5um", 36 | "ae4r", "aeri4e", "a2f", "aff4", "a4gab", "aga4n", "ag5ell", "age4o", 37 | "4ageu", "ag1i", "4ag4l", "ag1n", "a2go", "3agog", "ag3oni", "a5guer", 38 | "ag5ul", "a4gy", "a3ha", "a3he", "ah4l", "a3ho", "ai2", "a5ia", "a3ic.", 39 | "ai5ly", "a4i4n", "ain5in", "ain5o", "ait5en", "a1j", "ak1en", "al5ab", 40 | "al3ad", "a4lar", "4aldi", "2ale", "al3end", "a4lenti", "a5le5o", 41 | "al1i", "al4ia.", "ali4e", "al5lev", "4allic", "4alm", "a5log.", 42 | "a4ly.", "4alys", "5a5lyst", "5alyt", "3alyz", "4ama", "am5ab", "am3ag", 43 | "ama5ra", "am5asc", "a4matis", "a4m5ato", "am5era", "am3ic", "am5if", 44 | "am5ily", "am1in", "ami4no", "a2mo", "a5mon", "amor5i", "amp5en", "a2n", 45 | "an3age", "3analy", "a3nar", "an3arc", "anar4i", "a3nati", "4and", 46 | "ande4s", "an3dis", "an1dl", "an4dow", "a5nee", "a3nen", "an5est.", 47 | "a3neu", "2ang", "ang5ie", "an1gl", "a4n1ic", "a3nies", "an3i3f", 48 | "an4ime", "a5nimi", "a5nine", "an3io", "a3nip", "an3ish", "an3it", 49 | "a3niu", "an4kli", "5anniz", "ano4", "an5ot", "anoth5", "an2sa", 50 | "an4sco", "an4sn", "an2sp", "ans3po", "an4st", "an4sur", "antal4", 51 | "an4tie", "4anto", "an2tr", "an4tw", "an3ua", "an3ul", "a5nur", "4ao", 52 | "apar4", "ap5at", "ap5ero", "a3pher", "4aphi", "a4pilla", "ap5illar", 53 | "ap3in", "ap3ita", "a3pitu", "a2pl", "apoc5", "ap5ola", "apor5i", 54 | "apos3t", "aps5es", "a3pu", "aque5", "2a2r", "ar3act", "a5rade", 55 | "ar5adis", "ar3al", "a5ramete", "aran4g", "ara3p", "ar4at", "a5ratio", 56 | "ar5ativ", "a5rau", "ar5av4", "araw4", "arbal4", "ar4chan", "ar5dine", 57 | "ar4dr", "ar5eas", "a3ree", "ar3ent", "a5ress", "ar4fi", "ar4fl", 58 | "ar1i", "ar5ial", "ar3ian", "a3riet", "ar4im", "ar5inat", "ar3io", 59 | "ar2iz", "ar2mi", "ar5o5d", "a5roni", "a3roo", "ar2p", "ar3q", "arre4", 60 | "ar4sa", "ar2sh", "4as.", "as4ab", "as3ant", "ashi4", "a5sia.", "a3sib", 61 | "a3sic", "5a5si4t", "ask3i", "as4l", "a4soc", "as5ph", "as4sh", 62 | "as3ten", "as1tr", "asur5a", "a2ta", "at3abl", "at5ac", "at3alo", 63 | "at5ap", "ate5c", "at5ech", "at3ego", "at3en.", "at3era", "ater5n", 64 | "a5terna", "at3est", "at5ev", "4ath", "ath5em", "a5then", "at4ho", 65 | "ath5om", "4ati.", "a5tia", "at5i5b", "at1ic", "at3if", "ation5ar", 66 | "at3itu", "a4tog", "a2tom", "at5omiz", "a4top", "a4tos", "a1tr", 67 | "at5rop", "at4sk", "at4tag", "at5te", "at4th", "a2tu", "at5ua", "at5ue", 68 | "at3ul", "at3ura", "a2ty", "au4b", "augh3", "au3gu", "au4l2", "aun5d", 69 | "au3r", "au5sib", "aut5en", "au1th", "a2va", "av3ag", "a5van", "ave4no", 70 | "av3era", "av5ern", "av5ery", "av1i", "avi4er", "av3ig", "av5oc", 71 | "a1vor", "3away", "aw3i", "aw4ly", "aws4", "ax4ic", "ax4id", "ay5al", 72 | "aye4", "ays4", "azi4er", "azz5i", "5ba.", "bad5ger", "ba4ge", "bal1a", 73 | "ban5dag", "ban4e", "ban3i", "barbi5", "bari4a", "bas4si", "1bat", 74 | "ba4z", "2b1b", "b2be", "b3ber", "bbi4na", "4b1d", "4be.", "beak4", 75 | "beat3", "4be2d", "be3da", "be3de", "be3di", "be3gi", "be5gu", "1bel", 76 | "be1li", "be3lo", "4be5m", "be5nig", "be5nu", "4bes4", "be3sp", 77 | "be5str", "3bet", "bet5iz", "be5tr", "be3tw", "be3w", "be5yo", "2bf", 78 | "4b3h", "bi2b", "bi4d", "3bie", "bi5en", "bi4er", "2b3if", "1bil", 79 | "bi3liz", "bina5r4", "bin4d", "bi5net", "bi3ogr", "bi5ou", "bi2t", 80 | "3bi3tio", "bi3tr", "3bit5ua", "b5itz", "b1j", "bk4", "b2l2", "blath5", 81 | "b4le.", "blen4", "5blesp", "b3lis", "b4lo", "blun4t", "4b1m", "4b3n", 82 | "bne5g", "3bod", "bod3i", "bo4e", "bol3ic", "bom4bi", "bon4a", "bon5at", 83 | "3boo", "5bor.", "4b1ora", "bor5d", "5bore", "5bori", "5bos4", "b5ota", 84 | "both5", "bo4to", "bound3", "4bp", "4brit", "broth3", "2b5s2", "bsor4", 85 | "2bt", "bt4l", "b4to", "b3tr", "buf4fer", "bu4ga", "bu3li", "bumi4", 86 | "bu4n", "bunt4i", "bu3re", "bus5ie", "buss4e", "5bust", "4buta", 87 | "3butio", "b5uto", "b1v", "4b5w", "5by.", "bys4", "1ca", "cab3in", 88 | "ca1bl", "cach4", "ca5den", "4cag4", "2c5ah", "ca3lat", "cal4la", 89 | "call5in", "4calo", "can5d", "can4e", "can4ic", "can5is", "can3iz", 90 | "can4ty", "cany4", "ca5per", "car5om", "cast5er", "cas5tig", "4casy", 91 | "ca4th", "4cativ", "cav5al", "c3c", "ccha5", "cci4a", "ccompa5", 92 | "ccon4", "ccou3t", "2ce.", "4ced.", "4ceden", "3cei", "5cel.", "3cell", 93 | "1cen", "3cenc", "2cen4e", "4ceni", "3cent", "3cep", "ce5ram", "4cesa", 94 | "3cessi", "ces5si5b", "ces5t", "cet4", "c5e4ta", "cew4", "2ch", "4ch.", 95 | "4ch3ab", "5chanic", "ch5a5nis", "che2", "cheap3", "4ched", "che5lo", 96 | "3chemi", "ch5ene", "ch3er.", "ch3ers", "4ch1in", "5chine.", "ch5iness", 97 | "5chini", "5chio", "3chit", "chi2z", "3cho2", "ch4ti", "1ci", "3cia", 98 | "ci2a5b", "cia5r", "ci5c", "4cier", "5cific.", "4cii", "ci4la", "3cili", 99 | "2cim", "2cin", "c4ina", "3cinat", "cin3em", "c1ing", "c5ing.", "5cino", 100 | "cion4", "4cipe", "ci3ph", "4cipic", "4cista", "4cisti", "2c1it", 101 | "cit3iz", "5ciz", "ck1", "ck3i", "1c4l4", "4clar", "c5laratio", 102 | "5clare", "cle4m", "4clic", "clim4", "cly4", "c5n", "1co", "co5ag", 103 | "coe2", "2cog", "co4gr", "coi4", "co3inc", "col5i", "5colo", "col3or", 104 | "com5er", "con4a", "c4one", "con3g", "con5t", "co3pa", "cop3ic", 105 | "co4pl", "4corb", "coro3n", "cos4e", "cov1", "cove4", "cow5a", "coz5e", 106 | "co5zi", "c1q", "cras5t", "5crat.", "5cratic", "cre3at", "5cred", 107 | "4c3reta", "cre4v", "cri2", "cri5f", "c4rin", "cris4", "5criti", 108 | "cro4pl", "crop5o", "cros4e", "cru4d", "4c3s2", "2c1t", "cta4b", 109 | "ct5ang", "c5tant", "c2te", "c3ter", "c4ticu", "ctim3i", "ctu4r", 110 | "c4tw", "cud5", "c4uf", "c4ui", "cu5ity", "5culi", "cul4tis", "3cultu", 111 | "cu2ma", "c3ume", "cu4mi", "3cun", "cu3pi", "cu5py", "cur5a4b", 112 | "cu5ria", "1cus", "cuss4i", "3c4ut", "cu4tie", "4c5utiv", "4cutr", 113 | "1cy", "cze4", "1d2a", "5da.", "2d3a4b", "dach4", "4daf", "2dag", 114 | "da2m2", "dan3g", "dard5", "dark5", "4dary", "3dat", "4dativ", "4dato", 115 | "5dav4", "dav5e", "5day", "d1b", "d5c", "d1d4", "2de.", "deaf5", 116 | "deb5it", "de4bon", "decan4", "de4cil", "de5com", "2d1ed", "4dee.", 117 | "de5if", "deli4e", "del5i5q", "de5lo", "d4em", "5dem.", "3demic", 118 | "dem5ic.", "de5mil", "de4mons", "demor5", "1den", "de4nar", "de3no", 119 | "denti5f", "de3nu", "de1p", "de3pa", "depi4", "de2pu", "d3eq", "d4erh", 120 | "5derm", "dern5iz", "der5s", "des2", "d2es.", "de1sc", "de2s5o", 121 | "des3ti", "de3str", "de4su", "de1t", "de2to", "de1v", "dev3il", "4dey", 122 | "4d1f", "d4ga", "d3ge4t", "dg1i", "d2gy", "d1h2", "5di.", "1d4i3a", 123 | "dia5b", "di4cam", "d4ice", "3dict", "3did", "5di3en", "d1if", "di3ge", 124 | "di4lato", "d1in", "1dina", "3dine.", "5dini", "di5niz", "1dio", 125 | "dio5g", "di4pl", "dir2", "di1re", "dirt5i", "dis1", "5disi", "d4is3t", 126 | "d2iti", "1di1v", "d1j", "d5k2", "4d5la", "3dle.", "3dled", "3dles.", 127 | "4dless", "2d3lo", "4d5lu", "2dly", "d1m", "4d1n4", "1do", "3do.", 128 | "do5de", "5doe", "2d5of", "d4og", "do4la", "doli4", "do5lor", "dom5iz", 129 | "do3nat", "doni4", "doo3d", "dop4p", "d4or", "3dos", "4d5out", "do4v", 130 | "3dox", "d1p", "1dr", "drag5on", "4drai", "dre4", "drea5r", "5dren", 131 | "dri4b", "dril4", "dro4p", "4drow", "5drupli", "4dry", "2d1s2", "ds4p", 132 | "d4sw", "d4sy", "d2th", "1du", "d1u1a", "du2c", "d1uca", "duc5er", 133 | "4duct.", "4ducts", "du5el", "du4g", "d3ule", "dum4be", "du4n", "4dup", 134 | "du4pe", "d1v", "d1w", "d2y", "5dyn", "dy4se", "dys5p", "e1a4b", 135 | "e3act", "ead1", "ead5ie", "ea4ge", "ea5ger", "ea4l", "eal5er", 136 | "eal3ou", "eam3er", "e5and", "ear3a", "ear4c", "ear5es", "ear4ic", 137 | "ear4il", "ear5k", "ear2t", "eart3e", "ea5sp", "e3ass", "east3", "ea2t", 138 | "eat5en", "eath3i", "e5atif", "e4a3tu", "ea2v", "eav3en", "eav5i", 139 | "eav5o", "2e1b", "e4bel.", "e4bels", "e4ben", "e4bit", "e3br", "e4cad", 140 | "ecan5c", "ecca5", "e1ce", "ec5essa", "ec2i", "e4cib", "ec5ificat", 141 | "ec5ifie", "ec5ify", "ec3im", "eci4t", "e5cite", "e4clam", "e4clus", 142 | "e2col", "e4comm", "e4compe", "e4conc", "e2cor", "ec3ora", "eco5ro", 143 | "e1cr", "e4crem", "ec4tan", "ec4te", "e1cu", "e4cul", "ec3ula", "2e2da", 144 | "4ed3d", "e4d1er", "ede4s", "4edi", "e3dia", "ed3ib", "ed3ica", "ed3im", 145 | "ed1it", "edi5z", "4edo", "e4dol", "edon2", "e4dri", "e4dul", "ed5ulo", 146 | "ee2c", "eed3i", "ee2f", "eel3i", "ee4ly", "ee2m", "ee4na", "ee4p1", 147 | "ee2s4", "eest4", "ee4ty", "e5ex", "e1f", "e4f3ere", "1eff", "e4fic", 148 | "5efici", "efil4", "e3fine", "ef5i5nite", "3efit", "efor5es", "e4fuse.", 149 | "4egal", "eger4", "eg5ib", "eg4ic", "eg5ing", "e5git5", "eg5n", "e4go.", 150 | "e4gos", "eg1ul", "e5gur", "5egy", "e1h4", "eher4", "ei2", "e5ic", 151 | "ei5d", "eig2", "ei5gl", "e3imb", "e3inf", "e1ing", "e5inst", "eir4d", 152 | "eit3e", "ei3th", "e5ity", "e1j", "e4jud", "ej5udi", "eki4n", "ek4la", 153 | "e1la", "e4la.", "e4lac", "elan4d", "el5ativ", "e4law", "elaxa4", 154 | "e3lea", "el5ebra", "5elec", "e4led", "el3ega", "e5len", "e4l1er", 155 | "e1les", "el2f", "el2i", "e3libe", "e4l5ic.", "el3ica", "e3lier", 156 | "el5igib", "e5lim", "e4l3ing", "e3lio", "e2lis", "el5ish", "e3liv3", 157 | "4ella", "el4lab", "ello4", "e5loc", "el5og", "el3op.", "el2sh", 158 | "el4ta", "e5lud", "el5ug", "e4mac", "e4mag", "e5man", "em5ana", "em5b", 159 | "e1me", "e2mel", "e4met", "em3ica", "emi4e", "em5igra", "em1in2", 160 | "em5ine", "em3i3ni", "e4mis", "em5ish", "e5miss", "em3iz", "5emniz", 161 | "emo4g", "emoni5o", "em3pi", "e4mul", "em5ula", "emu3n", "e3my", 162 | "en5amo", "e4nant", "ench4er", "en3dic", "e5nea", "e5nee", "en3em", 163 | "en5ero", "en5esi", "en5est", "en3etr", "e3new", "en5ics", "e5nie", 164 | "e5nil", "e3nio", "en3ish", "en3it", "e5niu", "5eniz", "4enn", "4eno", 165 | "eno4g", "e4nos", "en3ov", "en4sw", "ent5age", "4enthes", "en3ua", 166 | "en5uf", "e3ny.", "4en3z", "e5of", "eo2g", "e4oi4", "e3ol", "eop3ar", 167 | "e1or", "eo3re", "eo5rol", "eos4", "e4ot", "eo4to", "e5out", "e5ow", 168 | "e2pa", "e3pai", "ep5anc", "e5pel", "e3pent", "ep5etitio", "ephe4", 169 | "e4pli", "e1po", "e4prec", "ep5reca", "e4pred", "ep3reh", "e3pro", 170 | "e4prob", "ep4sh", "ep5ti5b", "e4put", "ep5uta", "e1q", "equi3l", 171 | "e4q3ui3s", "er1a", "era4b", "4erand", "er3ar", "4erati.", "2erb", 172 | "er4bl", "er3ch", "er4che", "2ere.", "e3real", "ere5co", "ere3in", 173 | "er5el.", "er3emo", "er5ena", "er5ence", "4erene", "er3ent", "ere4q", 174 | "er5ess", "er3est", "eret4", "er1h", "er1i", "e1ria4", "5erick", 175 | "e3rien", "eri4er", "er3ine", "e1rio", "4erit", "er4iu", "eri4v", 176 | "e4riva", "er3m4", "er4nis", "4ernit", "5erniz", "er3no", "2ero", 177 | "er5ob", "e5roc", "ero4r", "er1ou", "er1s", "er3set", "ert3er", "4ertl", 178 | "er3tw", "4eru", "eru4t", "5erwau", "e1s4a", "e4sage.", "e4sages", 179 | "es2c", "e2sca", "es5can", "e3scr", "es5cu", "e1s2e", "e2sec", "es5ecr", 180 | "es5enc", "e4sert.", "e4serts", "e4serva", "4esh", "e3sha", "esh5en", 181 | "e1si", "e2sic", "e2sid", "es5iden", "es5igna", "e2s5im", "es4i4n", 182 | "esis4te", "esi4u", "e5skin", "es4mi", "e2sol", "es3olu", "e2son", 183 | "es5ona", "e1sp", "es3per", "es5pira", "es4pre", "2ess", "es4si4b", 184 | "estan4", "es3tig", "es5tim", "4es2to", "e3ston", "2estr", "e5stro", 185 | "estruc5", "e2sur", "es5urr", "es4w", "eta4b", "eten4d", "e3teo", 186 | "ethod3", "et1ic", "e5tide", "etin4", "eti4no", "e5tir", "e5titio", 187 | "et5itiv", "4etn", "et5ona", "e3tra", "e3tre", "et3ric", "et5rif", 188 | "et3rog", "et5ros", "et3ua", "et5ym", "et5z", "4eu", "e5un", "e3up", 189 | "eu3ro", "eus4", "eute4", "euti5l", "eu5tr", "eva2p5", "e2vas", 190 | "ev5ast", "e5vea", "ev3ell", "evel3o", "e5veng", "even4i", "ev1er", 191 | "e5verb", "e1vi", "ev3id", "evi4l", "e4vin", "evi4v", "e5voc", "e5vu", 192 | "e1wa", "e4wag", "e5wee", "e3wh", "ewil5", "ew3ing", "e3wit", "1exp", 193 | "5eyc", "5eye.", "eys4", "1fa", "fa3bl", "fab3r", "fa4ce", "4fag", 194 | "fain4", "fall5e", "4fa4ma", "fam5is", "5far", "far5th", "fa3ta", 195 | "fa3the", "4fato", "fault5", "4f5b", "4fd", "4fe.", "feas4", "feath3", 196 | "fe4b", "4feca", "5fect", "2fed", "fe3li", "fe4mo", "fen2d", "fend5e", 197 | "fer1", "5ferr", "fev4", "4f1f", "f4fes", "f4fie", "f5fin.", "f2f5is", 198 | "f4fly", "f2fy", "4fh", "1fi", "fi3a", "2f3ic.", "4f3ical", "f3ican", 199 | "4ficate", "f3icen", "fi3cer", "fic4i", "5ficia", "5ficie", "4fics", 200 | "fi3cu", "fi5del", "fight5", "fil5i", "fill5in", "4fily", "2fin", 201 | "5fina", "fin2d5", "fi2ne", "f1in3g", "fin4n", "fis4ti", "f4l2", 202 | "f5less", "flin4", "flo3re", "f2ly5", "4fm", "4fn", "1fo", "5fon", 203 | "fon4de", "fon4t", "fo2r", "fo5rat", "for5ay", "fore5t", "for4i", 204 | "fort5a", "fos5", "4f5p", "fra4t", "f5rea", "fres5c", "fri2", "fril4", 205 | "frol5", "2f3s", "2ft", "f4to", "f2ty", "3fu", "fu5el", "4fug", 206 | "fu4min", "fu5ne", "fu3ri", "fusi4", "fus4s", "4futa", "1fy", "1ga", 207 | "gaf4", "5gal.", "3gali", "ga3lo", "2gam", "ga5met", "g5amo", "gan5is", 208 | "ga3niz", "gani5za", "4gano", "gar5n4", "gass4", "gath3", "4gativ", 209 | "4gaz", "g3b", "gd4", "2ge.", "2ged", "geez4", "gel4in", "ge5lis", 210 | "ge5liz", "4gely", "1gen", "ge4nat", "ge5niz", "4geno", "4geny", "1geo", 211 | "ge3om", "g4ery", "5gesi", "geth5", "4geto", "ge4ty", "ge4v", "4g1g2", 212 | "g2ge", "g3ger", "gglu5", "ggo4", "gh3in", "gh5out", "gh4to", "5gi.", 213 | "1gi4a", "gia5r", "g1ic", "5gicia", "g4ico", "gien5", "5gies.", "gil4", 214 | "g3imen", "3g4in.", "gin5ge", "5g4ins", "5gio", "3gir", "gir4l", 215 | "g3isl", "gi4u", "5giv", "3giz", "gl2", "gla4", "glad5i", "5glas", 216 | "1gle", "gli4b", "g3lig", "3glo", "glo3r", "g1m", "g4my", "gn4a", 217 | "g4na.", "gnet4t", "g1ni", "g2nin", "g4nio", "g1no", "g4non", "1go", 218 | "3go.", "gob5", "5goe", "3g4o4g", "go3is", "gon2", "4g3o3na", "gondo5", 219 | "go3ni", "5goo", "go5riz", "gor5ou", "5gos.", "gov1", "g3p", "1gr", 220 | "4grada", "g4rai", "gran2", "5graph.", "g5rapher", "5graphic", 221 | "4graphy", "4gray", "gre4n", "4gress.", "4grit", "g4ro", "gruf4", "gs2", 222 | "g5ste", "gth3", "gu4a", "3guard", "2gue", "5gui5t", "3gun", "3gus", 223 | "4gu4t", "g3w", "1gy", "2g5y3n", "gy5ra", "h3ab4l", "hach4", "hae4m", 224 | "hae4t", "h5agu", "ha3la", "hala3m", "ha4m", "han4ci", "han4cy", 225 | "5hand.", "han4g", "hang5er", "hang5o", "h5a5niz", "han4k", "han4te", 226 | "hap3l", "hap5t", "ha3ran", "ha5ras", "har2d", "hard3e", "har4le", 227 | "harp5en", "har5ter", "has5s", "haun4", "5haz", "haz3a", "h1b", "1head", 228 | "3hear", "he4can", "h5ecat", "h4ed", "he5do5", "he3l4i", "hel4lis", 229 | "hel4ly", "h5elo", "hem4p", "he2n", "hena4", "hen5at", "heo5r", "hep5", 230 | "h4era", "hera3p", "her4ba", "here5a", "h3ern", "h5erou", "h3ery", 231 | "h1es", "he2s5p", "he4t", "het4ed", "heu4", "h1f", "h1h", "hi5an", 232 | "hi4co", "high5", "h4il2", "himer4", "h4ina", "hion4e", "hi4p", "hir4l", 233 | "hi3ro", "hir4p", "hir4r", "his3el", "his4s", "hith5er", "hi2v", "4hk", 234 | "4h1l4", "hlan4", "h2lo", "hlo3ri", "4h1m", "hmet4", "2h1n", "h5odiz", 235 | "h5ods", "ho4g", "hoge4", "hol5ar", "3hol4e", "ho4ma", "home3", "hon4a", 236 | "ho5ny", "3hood", "hoon4", "hor5at", "ho5ris", "hort3e", "ho5ru", 237 | "hos4e", "ho5sen", "hos1p", "1hous", "house3", "hov5el", "4h5p", "4hr4", 238 | "hree5", "hro5niz", "hro3po", "4h1s2", "h4sh", "h4tar", "ht1en", 239 | "ht5es", "h4ty", "hu4g", "hu4min", "hun5ke", "hun4t", "hus3t4", "hu4t", 240 | "h1w", "h4wart", "hy3pe", "hy3ph", "hy2s", "2i1a", "i2al", "iam4", 241 | "iam5ete", "i2an", "4ianc", "ian3i", "4ian4t", "ia5pe", "iass4", 242 | "i4ativ", "ia4tric", "i4atu", "ibe4", "ib3era", "ib5ert", "ib5ia", 243 | "ib3in", "ib5it.", "ib5ite", "i1bl", "ib3li", "i5bo", "i1br", "i2b5ri", 244 | "i5bun", "4icam", "5icap", "4icar", "i4car.", "i4cara", "icas5", 245 | "i4cay", "iccu4", "4iceo", "4ich", "2ici", "i5cid", "ic5ina", "i2cip", 246 | "ic3ipa", "i4cly", "i2c5oc", "4i1cr", "5icra", "i4cry", "ic4te", 247 | "ictu2", "ic4t3ua", "ic3ula", "ic4um", "ic5uo", "i3cur", "2id", "i4dai", 248 | "id5anc", "id5d", "ide3al", "ide4s", "i2di", "id5ian", "idi4ar", 249 | "i5die", "id3io", "idi5ou", "id1it", "id5iu", "i3dle", "i4dom", "id3ow", 250 | "i4dr", "i2du", "id5uo", "2ie4", "ied4e", "5ie5ga", "ield3", "ien5a4", 251 | "ien4e", "i5enn", "i3enti", "i1er.", "i3esc", "i1est", "i3et", "4if.", 252 | "if5ero", "iff5en", "if4fr", "4ific.", "i3fie", "i3fl", "4ift", "2ig", 253 | "iga5b", "ig3era", "ight3i", "4igi", "i3gib", "ig3il", "ig3in", "ig3it", 254 | "i4g4l", "i2go", "ig3or", "ig5ot", "i5gre", "igu5i", "ig1ur", "i3h", 255 | "4i5i4", "i3j", "4ik", "i1la", "il3a4b", "i4lade", "i2l5am", "ila5ra", 256 | "i3leg", "il1er", "ilev4", "il5f", "il1i", "il3ia", "il2ib", "il3io", 257 | "il4ist", "2ilit", "il2iz", "ill5ab", "4iln", "il3oq", "il4ty", "il5ur", 258 | "il3v", "i4mag", "im3age", "ima5ry", "imenta5r", "4imet", "im1i", 259 | "im5ida", "imi5le", "i5mini", "4imit", "im4ni", "i3mon", "i2mu", 260 | "im3ula", "2in.", "i4n3au", "4inav", "incel4", "in3cer", "4ind", 261 | "in5dling", "2ine", "i3nee", "iner4ar", "i5ness", "4inga", "4inge", 262 | "in5gen", "4ingi", "in5gling", "4ingo", "4ingu", "2ini", "i5ni.", 263 | "i4nia", "in3io", "in1is", "i5nite.", "5initio", "in3ity", "4ink", 264 | "4inl", "2inn", "2i1no", "i4no4c", "ino4s", "i4not", "2ins", "in3se", 265 | "insur5a", "2int.", "2in4th", "in1u", "i5nus", "4iny", "2io", "4io.", 266 | "ioge4", "io2gr", "i1ol", "io4m", "ion3at", "ion4ery", "ion3i", "io5ph", 267 | "ior3i", "i4os", "io5th", "i5oti", "io4to", "i4our", "2ip", "ipe4", 268 | "iphras4", "ip3i", "ip4ic", "ip4re4", "ip3ul", "i3qua", "iq5uef", 269 | "iq3uid", "iq3ui3t", "4ir", "i1ra", "ira4b", "i4rac", "ird5e", "ire4de", 270 | "i4ref", "i4rel4", "i4res", "ir5gi", "ir1i", "iri5de", "ir4is", 271 | "iri3tu", "5i5r2iz", "ir4min", "iro4g", "5iron.", "ir5ul", "2is.", 272 | "is5ag", "is3ar", "isas5", "2is1c", "is3ch", "4ise", "is3er", "3isf", 273 | "is5han", "is3hon", "ish5op", "is3ib", "isi4d", "i5sis", "is5itiv", 274 | "4is4k", "islan4", "4isms", "i2so", "iso5mer", "is1p", "is2pi", "is4py", 275 | "4is1s", "is4sal", "issen4", "is4ses", "is4ta.", "is1te", "is1ti", 276 | "ist4ly", "4istral", "i2su", "is5us", "4ita.", "ita4bi", "i4tag", 277 | "4ita5m", "i3tan", "i3tat", "2ite", "it3era", "i5teri", "it4es", "2ith", 278 | "i1ti", "4itia", "4i2tic", "it3ica", "5i5tick", "it3ig", "it5ill", 279 | "i2tim", "2itio", "4itis", "i4tism", "i2t5o5m", "4iton", "i4tram", 280 | "it5ry", "4itt", "it3uat", "i5tud", "it3ul", "4itz.", "i1u", "2iv", 281 | "iv3ell", "iv3en.", "i4v3er.", "i4vers.", "iv5il.", "iv5io", "iv1it", 282 | "i5vore", "iv3o3ro", "i4v3ot", "4i5w", "ix4o", "4iy", "4izar", "izi4", 283 | "5izont", "5ja", "jac4q", "ja4p", "1je", "jer5s", "4jestie", "4jesty", 284 | "jew3", "jo4p", "5judg", "3ka.", "k3ab", "k5ag", "kais4", "kal4", "k1b", 285 | "k2ed", "1kee", "ke4g", "ke5li", "k3en4d", "k1er", "kes4", "k3est.", 286 | "ke4ty", "k3f", "kh4", "k1i", "5ki.", "5k2ic", "k4ill", "kilo5", "k4im", 287 | "k4in.", "kin4de", "k5iness", "kin4g", "ki4p", "kis4", "k5ish", "kk4", 288 | "k1l", "4kley", "4kly", "k1m", "k5nes", "1k2no", "ko5r", "kosh4", 289 | "k3ou", "kro5n", "4k1s2", "k4sc", "ks4l", "k4sy", "k5t", "k1w", 290 | "lab3ic", "l4abo", "laci4", "l4ade", "la3dy", "lag4n", "lam3o", "3land", 291 | "lan4dl", "lan5et", "lan4te", "lar4g", "lar3i", "las4e", "la5tan", 292 | "4lateli", "4lativ", "4lav", "la4v4a", "2l1b", "lbin4", "4l1c2", "lce4", 293 | "l3ci", "2ld", "l2de", "ld4ere", "ld4eri", "ldi4", "ld5is", "l3dr", 294 | "l4dri", "le2a", "le4bi", "left5", "5leg.", "5legg", "le4mat", 295 | "lem5atic", "4len.", "3lenc", "5lene.", "1lent", "le3ph", "le4pr", 296 | "lera5b", "ler4e", "3lerg", "3l4eri", "l4ero", "les2", "le5sco", 297 | "5lesq", "3less", "5less.", "l3eva", "lev4er.", "lev4era", "lev4ers", 298 | "3ley", "4leye", "2lf", "l5fr", "4l1g4", "l5ga", "lgar3", "l4ges", 299 | "lgo3", "2l3h", "li4ag", "li2am", "liar5iz", "li4as", "li4ato", "li5bi", 300 | "5licio", "li4cor", "4lics", "4lict.", "l4icu", "l3icy", "l3ida", 301 | "lid5er", "3lidi", "lif3er", "l4iff", "li4fl", "5ligate", "3ligh", 302 | "li4gra", "3lik", "4l4i4l", "lim4bl", "lim3i", "li4mo", "l4im4p", 303 | "l4ina", "1l4ine", "lin3ea", "lin3i", "link5er", "li5og", "4l4iq", 304 | "lis4p", "l1it", "l2it.", "5litica", "l5i5tics", "liv3er", "l1iz", 305 | "4lj", "lka3", "l3kal", "lka4t", "l1l", "l4law", "l2le", "l5lea", 306 | "l3lec", "l3leg", "l3lel", "l3le4n", "l3le4t", "ll2i", "l2lin4", 307 | "l5lina", "ll4o", "lloqui5", "ll5out", "l5low", "2lm", "l5met", 308 | "lm3ing", "l4mod", "lmon4", "2l1n2", "3lo.", "lob5al", "lo4ci", "4lof", 309 | "3logic", "l5ogo", "3logu", "lom3er", "5long", "lon4i", "l3o3niz", 310 | "lood5", "5lope.", "lop3i", "l3opm", "lora4", "lo4rato", "lo5rie", 311 | "lor5ou", "5los.", "los5et", "5losophiz", "5losophy", "los4t", "lo4ta", 312 | "loun5d", "2lout", "4lov", "2lp", "lpa5b", "l3pha", "l5phi", "lp5ing", 313 | "l3pit", "l4pl", "l5pr", "4l1r", "2l1s2", "l4sc", "l2se", "l4sie", 314 | "4lt", "lt5ag", "ltane5", "l1te", "lten4", "ltera4", "lth3i", "l5ties.", 315 | "ltis4", "l1tr", "ltu2", "ltur3a", "lu5a", "lu3br", "luch4", "lu3ci", 316 | "lu3en", "luf4", "lu5id", "lu4ma", "5lumi", "l5umn.", "5lumnia", "lu3o", 317 | "luo3r", "4lup", "luss4", "lus3te", "1lut", "l5ven", "l5vet4", "2l1w", 318 | "1ly", "4lya", "4lyb", "ly5me", "ly3no", "2lys4", "l5yse", "1ma", 319 | "2mab", "ma2ca", "ma5chine", "ma4cl", "mag5in", "5magn", "2mah", 320 | "maid5", "4mald", "ma3lig", "ma5lin", "mal4li", "mal4ty", "5mania", 321 | "man5is", "man3iz", "4map", "ma5rine.", "ma5riz", "mar4ly", "mar3v", 322 | "ma5sce", "mas4e", "mas1t", "5mate", "math3", "ma3tis", "4matiza", 323 | "4m1b", "mba4t5", "m5bil", "m4b3ing", "mbi4v", "4m5c", "4me.", "2med", 324 | "4med.", "5media", "me3die", "m5e5dy", "me2g", "mel5on", "mel4t", 325 | "me2m", "mem1o3", "1men", "men4a", "men5ac", "men4de", "4mene", "men4i", 326 | "mens4", "mensu5", "3ment", "men4te", "me5on", "m5ersa", "2mes", 327 | "3mesti", "me4ta", "met3al", "me1te", "me5thi", "m4etr", "5metric", 328 | "me5trie", "me3try", "me4v", "4m1f", "2mh", "5mi.", "mi3a", "mid4a", 329 | "mid4g", "mig4", "3milia", "m5i5lie", "m4ill", "min4a", "3mind", 330 | "m5inee", "m4ingl", "min5gli", "m5ingly", "min4t", "m4inu", "miot4", 331 | "m2is", "mis4er.", "mis5l", "mis4ti", "m5istry", "4mith", "m2iz", "4mk", 332 | "4m1l", "m1m", "mma5ry", "4m1n", "mn4a", "m4nin", "mn4o", "1mo", 333 | "4mocr", "5mocratiz", "mo2d1", "mo4go", "mois2", "moi5se", "4mok", 334 | "mo5lest", "mo3me", "mon5et", "mon5ge", "moni3a", "mon4ism", "mon4ist", 335 | "mo3niz", "monol4", "mo3ny.", "mo2r", "4mora.", "mos2", "mo5sey", 336 | "mo3sp", "moth3", "m5ouf", "3mous", "mo2v", "4m1p", "mpara5", "mpa5rab", 337 | "mpar5i", "m3pet", "mphas4", "m2pi", "mpi4a", "mp5ies", "m4p1in", 338 | "m5pir", "mp5is", "mpo3ri", "mpos5ite", "m4pous", "mpov5", "mp4tr", 339 | "m2py", "4m3r", "4m1s2", "m4sh", "m5si", "4mt", "1mu", "mula5r4", 340 | "5mult", "multi3", "3mum", "mun2", "4mup", "mu4u", "4mw", "1na", 341 | "2n1a2b", "n4abu", "4nac.", "na4ca", "n5act", "nag5er.", "nak4", 342 | "na4li", "na5lia", "4nalt", "na5mit", "n2an", "nanci4", "nan4it", 343 | "nank4", "nar3c", "4nare", "nar3i", "nar4l", "n5arm", "n4as", "nas4c", 344 | "nas5ti", "n2at", "na3tal", "nato5miz", "n2au", "nau3se", "3naut", 345 | "nav4e", "4n1b4", "ncar5", "n4ces.", "n3cha", "n5cheo", "n5chil", 346 | "n3chis", "nc1in", "nc4it", "ncour5a", "n1cr", "n1cu", "n4dai", "n5dan", 347 | "n1de", "nd5est.", "ndi4b", "n5d2if", "n1dit", "n3diz", "n5duc", 348 | "ndu4r", "nd2we", "2ne.", "n3ear", "ne2b", "neb3u", "ne2c", "5neck", 349 | "2ned", "ne4gat", "neg5ativ", "5nege", "ne4la", "nel5iz", "ne5mi", 350 | "ne4mo", "1nen", "4nene", "3neo", "ne4po", "ne2q", "n1er", "nera5b", 351 | "n4erar", "n2ere", "n4er5i", "ner4r", "1nes", "2nes.", "4nesp", "2nest", 352 | "4nesw", "3netic", "ne4v", "n5eve", "ne4w", "n3f", "n4gab", "n3gel", 353 | "nge4n4e", "n5gere", "n3geri", "ng5ha", "n3gib", "ng1in", "n5git", 354 | "n4gla", "ngov4", "ng5sh", "n1gu", "n4gum", "n2gy", "4n1h4", "nha4", 355 | "nhab3", "nhe4", "3n4ia", "ni3an", "ni4ap", "ni3ba", "ni4bl", "ni4d", 356 | "ni5di", "ni4er", "ni2fi", "ni5ficat", "n5igr", "nik4", "n1im", 357 | "ni3miz", "n1in", "5nine.", "nin4g", "ni4o", "5nis.", "nis4ta", "n2it", 358 | "n4ith", "3nitio", "n3itor", "ni3tr", "n1j", "4nk2", "n5kero", "n3ket", 359 | "nk3in", "n1kl", "4n1l", "n5m", "nme4", "nmet4", "4n1n2", "nne4", 360 | "nni3al", "nni4v", "nob4l", "no3ble", "n5ocl", "4n3o2d", "3noe", "4nog", 361 | "noge4", "nois5i", "no5l4i", "5nologis", "3nomic", "n5o5miz", "no4mo", 362 | "no3my", "no4n", "non4ag", "non5i", "n5oniz", "4nop", "5nop5o5li", 363 | "nor5ab", "no4rary", "4nosc", "nos4e", "nos5t", "no5ta", "1nou", 364 | "3noun", "nov3el3", "nowl3", "n1p4", "npi4", "npre4c", "n1q", "n1r", 365 | "nru4", "2n1s2", "ns5ab", "nsati4", "ns4c", "n2se", "n4s3es", "nsid1", 366 | "nsig4", "n2sl", "ns3m", "n4soc", "ns4pe", "n5spi", "nsta5bl", "n1t", 367 | "nta4b", "nter3s", "nt2i", "n5tib", "nti4er", "nti2f", "n3tine", 368 | "n4t3ing", "nti4p", "ntrol5li", "nt4s", "ntu3me", "nu1a", "nu4d", 369 | "nu5en", "nuf4fe", "n3uin", "3nu3it", "n4um", "nu1me", "n5umi", "3nu4n", 370 | "n3uo", "nu3tr", "n1v2", "n1w4", "nym4", "nyp4", "4nz", "n3za", "4oa", 371 | "oad3", "o5a5les", "oard3", "oas4e", "oast5e", "oat5i", "ob3a3b", 372 | "o5bar", "obe4l", "o1bi", "o2bin", "ob5ing", "o3br", "ob3ul", "o1ce", 373 | "och4", "o3chet", "ocif3", "o4cil", "o4clam", "o4cod", "oc3rac", 374 | "oc5ratiz", "ocre3", "5ocrit", "octor5a", "oc3ula", "o5cure", "od5ded", 375 | "od3ic", "odi3o", "o2do4", "odor3", "od5uct.", "od5ucts", "o4el", 376 | "o5eng", "o3er", "oe4ta", "o3ev", "o2fi", "of5ite", "ofit4t", "o2g5a5r", 377 | "og5ativ", "o4gato", "o1ge", "o5gene", "o5geo", "o4ger", "o3gie", 378 | "1o1gis", "og3it", "o4gl", "o5g2ly", "3ogniz", "o4gro", "ogu5i", "1ogy", 379 | "2ogyn", "o1h2", "ohab5", "oi2", "oic3es", "oi3der", "oiff4", "oig4", 380 | "oi5let", "o3ing", "oint5er", "o5ism", "oi5son", "oist5en", "oi3ter", 381 | "o5j", "2ok", "o3ken", "ok5ie", "o1la", "o4lan", "olass4", "ol2d", 382 | "old1e", "ol3er", "o3lesc", "o3let", "ol4fi", "ol2i", "o3lia", "o3lice", 383 | "ol5id.", "o3li4f", "o5lil", "ol3ing", "o5lio", "o5lis.", "ol3ish", 384 | "o5lite", "o5litio", "o5liv", "olli4e", "ol5ogiz", "olo4r", "ol5pl", 385 | "ol2t", "ol3ub", "ol3ume", "ol3un", "o5lus", "ol2v", "o2ly", "om5ah", 386 | "oma5l", "om5atiz", "om2be", "om4bl", "o2me", "om3ena", "om5erse", 387 | "o4met", "om5etry", "o3mia", "om3ic.", "om3ica", "o5mid", "om1in", 388 | "o5mini", "5ommend", "omo4ge", "o4mon", "om3pi", "ompro5", "o2n", 389 | "on1a", "on4ac", "o3nan", "on1c", "3oncil", "2ond", "on5do", "o3nen", 390 | "on5est", "on4gu", "on1ic", "o3nio", "on1is", "o5niu", "on3key", 391 | "on4odi", "on3omy", "on3s", "onspi4", "onspir5a", "onsu4", "onten4", 392 | "on3t4i", "ontif5", "on5um", "onva5", "oo2", "ood5e", "ood5i", "oo4k", 393 | "oop3i", "o3ord", "oost5", "o2pa", "ope5d", "op1er", "3opera", 394 | "4operag", "2oph", "o5phan", "o5pher", "op3ing", "o3pit", "o5pon", 395 | "o4posi", "o1pr", "op1u", "opy5", "o1q", "o1ra", "o5ra.", "o4r3ag", 396 | "or5aliz", "or5ange", "ore5a", "o5real", "or3ei", "ore5sh", "or5est.", 397 | "orew4", "or4gu", "4o5ria", "or3ica", "o5ril", "or1in", "o1rio", 398 | "or3ity", "o3riu", "or2mi", "orn2e", "o5rof", "or3oug", "or5pe", 399 | "3orrh", "or4se", "ors5en", "orst4", "or3thi", "or3thy", "or4ty", 400 | "o5rum", "o1ry", "os3al", "os2c", "os4ce", "o3scop", "4oscopi", "o5scr", 401 | "os4i4e", "os5itiv", "os3ito", "os3ity", "osi4u", "os4l", "o2so", 402 | "os4pa", "os4po", "os2ta", "o5stati", "os5til", "os5tit", "o4tan", 403 | "otele4g", "ot3er.", "ot5ers", "o4tes", "4oth", "oth5esi", "oth3i4", 404 | "ot3ic.", "ot5ica", "o3tice", "o3tif", "o3tis", "oto5s", "ou2", "ou3bl", 405 | "ouch5i", "ou5et", "ou4l", "ounc5er", "oun2d", "ou5v", "ov4en", 406 | "over4ne", "over3s", "ov4ert", "o3vis", "oviti4", "o5v4ol", "ow3der", 407 | "ow3el", "ow5est", "ow1i", "own5i", "o4wo", "oy1a", "1pa", "pa4ca", 408 | "pa4ce", "pac4t", "p4ad", "5pagan", "p3agat", "p4ai", "pain4", "p4al", 409 | "pan4a", "pan3el", "pan4ty", "pa3ny", "pa1p", "pa4pu", "para5bl", 410 | "par5age", "par5di", "3pare", "par5el", "p4a4ri", "par4is", "pa2te", 411 | "pa5ter", "5pathic", "pa5thy", "pa4tric", "pav4", "3pay", "4p1b", "pd4", 412 | "4pe.", "3pe4a", "pear4l", "pe2c", "2p2ed", "3pede", "3pedi", "pedia4", 413 | "ped4ic", "p4ee", "pee4d", "pek4", "pe4la", "peli4e", "pe4nan", "p4enc", 414 | "pen4th", "pe5on", "p4era.", "pera5bl", "p4erag", "p4eri", "peri5st", 415 | "per4mal", "perme5", "p4ern", "per3o", "per3ti", "pe5ru", "per1v", 416 | "pe2t", "pe5ten", "pe5tiz", "4pf", "4pg", "4ph.", "phar5i", "phe3no", 417 | "ph4er", "ph4es.", "ph1ic", "5phie", "ph5ing", "5phisti", "3phiz", 418 | "ph2l", "3phob", "3phone", "5phoni", "pho4r", "4phs", "ph3t", "5phu", 419 | "1phy", "pi3a", "pian4", "pi4cie", "pi4cy", "p4id", "p5ida", "pi3de", 420 | "5pidi", "3piec", "pi3en", "pi4grap", "pi3lo", "pi2n", "p4in.", "pind4", 421 | "p4ino", "3pi1o", "pion4", "p3ith", "pi5tha", "pi2tu", "2p3k2", "1p2l2", 422 | "3plan", "plas5t", "pli3a", "pli5er", "4plig", "pli4n", "ploi4", 423 | "plu4m", "plum4b", "4p1m", "2p3n", "po4c", "5pod.", "po5em", "po3et5", 424 | "5po4g", "poin2", "5point", "poly5t", "po4ni", "po4p", "1p4or", "po4ry", 425 | "1pos", "pos1s", "p4ot", "po4ta", "5poun", "4p1p", "ppa5ra", "p2pe", 426 | "p4ped", "p5pel", "p3pen", "p3per", "p3pet", "ppo5site", "pr2", 427 | "pray4e", "5preci", "pre5co", "pre3em", "pref5ac", "pre4la", "pre3r", 428 | "p3rese", "3press", "pre5ten", "pre3v", "5pri4e", "prin4t3", "pri4s", 429 | "pris3o", "p3roca", "prof5it", "pro3l", "pros3e", "pro1t", "2p1s2", 430 | "p2se", "ps4h", "p4sib", "2p1t", "pt5a4b", "p2te", "p2th", "pti3m", 431 | "ptu4r", "p4tw", "pub3", "pue4", "puf4", "pul3c", "pu4m", "pu2n", 432 | "pur4r", "5pus", "pu2t", "5pute", "put3er", "pu3tr", "put4ted", 433 | "put4tin", "p3w", "qu2", "qua5v", "2que.", "3quer", "3quet", "2rab", 434 | "ra3bi", "rach4e", "r5acl", "raf5fi", "raf4t", "r2ai", "ra4lo", 435 | "ram3et", "r2ami", "rane5o", "ran4ge", "r4ani", "ra5no", "rap3er", 436 | "3raphy", "rar5c", "rare4", "rar5ef", "4raril", "r2as", "ration4", 437 | "rau4t", "ra5vai", "rav3el", "ra5zie", "r1b", "r4bab", "r4bag", "rbi2", 438 | "rbi4f", "r2bin", "r5bine", "rb5ing.", "rb4o", "r1c", "r2ce", "rcen4", 439 | "r3cha", "rch4er", "r4ci4b", "rc4it", "rcum3", "r4dal", "rd2i", "rdi4a", 440 | "rdi4er", "rdin4", "rd3ing", "2re.", "re1al", "re3an", "re5arr", 441 | "5reav", "re4aw", "r5ebrat", "rec5oll", "rec5ompe", "re4cre", "2r2ed", 442 | "re1de", "re3dis", "red5it", "re4fac", "re2fe", "re5fer.", "re3fi", 443 | "re4fy", "reg3is", "re5it", "re1li", "re5lu", "r4en4ta", "ren4te", 444 | "re1o", "re5pin", "re4posi", "re1pu", "r1er4", "r4eri", "rero4", 445 | "re5ru", "r4es.", "re4spi", "ress5ib", "res2t", "re5stal", "re3str", 446 | "re4ter", "re4ti4z", "re3tri", "reu2", "re5uti", "rev2", "re4val", 447 | "rev3el", "r5ev5er.", "re5vers", "re5vert", "re5vil", "rev5olu", 448 | "re4wh", "r1f", "rfu4", "r4fy", "rg2", "rg3er", "r3get", "r3gic", 449 | "rgi4n", "rg3ing", "r5gis", "r5git", "r1gl", "rgo4n", "r3gu", "rh4", 450 | "4rh.", "4rhal", "ri3a", "ria4b", "ri4ag", "r4ib", "rib3a", "ric5as", 451 | "r4ice", "4rici", "5ricid", "ri4cie", "r4ico", "rid5er", "ri3enc", 452 | "ri3ent", "ri1er", "ri5et", "rig5an", "5rigi", "ril3iz", "5riman", 453 | "rim5i", "3rimo", "rim4pe", "r2ina", "5rina.", "rin4d", "rin4e", 454 | "rin4g", "ri1o", "5riph", "riph5e", "ri2pl", "rip5lic", "r4iq", "r2is", 455 | "r4is.", "ris4c", "r3ish", "ris4p", "ri3ta3b", "r5ited.", "rit5er.", 456 | "rit5ers", "rit3ic", "ri2tu", "rit5ur", "riv5el", "riv3et", "riv3i", 457 | "r3j", "r3ket", "rk4le", "rk4lin", "r1l", "rle4", "r2led", "r4lig", 458 | "r4lis", "rl5ish", "r3lo4", "r1m", "rma5c", "r2me", "r3men", "rm5ers", 459 | "rm3ing", "r4ming.", "r4mio", "r3mit", "r4my", "r4nar", "r3nel", 460 | "r4ner", "r5net", "r3ney", "r5nic", "r1nis4", "r3nit", "r3niv", "rno4", 461 | "r4nou", "r3nu", "rob3l", "r2oc", "ro3cr", "ro4e", "ro1fe", "ro5fil", 462 | "rok2", "ro5ker", "5role.", "rom5ete", "rom4i", "rom4p", "ron4al", 463 | "ron4e", "ro5n4is", "ron4ta", "1room", "5root", "ro3pel", "rop3ic", 464 | "ror3i", "ro5ro", "ros5per", "ros4s", "ro4the", "ro4ty", "ro4va", 465 | "rov5el", "rox5", "r1p", "r4pea", "r5pent", "rp5er.", "r3pet", "rp4h4", 466 | "rp3ing", "r3po", "r1r4", "rre4c", "rre4f", "r4reo", "rre4st", "rri4o", 467 | "rri4v", "rron4", "rros4", "rrys4", "4rs2", "r1sa", "rsa5ti", "rs4c", 468 | "r2se", "r3sec", "rse4cr", "rs5er.", "rs3es", "rse5v2", "r1sh", "r5sha", 469 | "r1si", "r4si4b", "rson3", "r1sp", "r5sw", "rtach4", "r4tag", "r3teb", 470 | "rten4d", "rte5o", "r1ti", "rt5ib", "rti4d", "r4tier", "r3tig", 471 | "rtil3i", "rtil4l", "r4tily", "r4tist", "r4tiv", "r3tri", "rtroph4", 472 | "rt4sh", "ru3a", "ru3e4l", "ru3en", "ru4gl", "ru3in", "rum3pl", "ru2n", 473 | "runk5", "run4ty", "r5usc", "ruti5n", "rv4e", "rvel4i", "r3ven", 474 | "rv5er.", "r5vest", "r3vey", "r3vic", "rvi4v", "r3vo", "r1w", "ry4c", 475 | "5rynge", "ry3t", "sa2", "2s1ab", "5sack", "sac3ri", "s3act", "5sai", 476 | "salar4", "sal4m", "sa5lo", "sal4t", "3sanc", "san4de", "s1ap", "sa5ta", 477 | "5sa3tio", "sat3u", "sau4", "sa5vor", "5saw", "4s5b", "scan4t5", 478 | "sca4p", "scav5", "s4ced", "4scei", "s4ces", "sch2", "s4cho", "3s4cie", 479 | "5scin4d", "scle5", "s4cli", "scof4", "4scopy", "scour5a", "s1cu", 480 | "4s5d", "4se.", "se4a", "seas4", "sea5w", "se2c3o", "3sect", "4s4ed", 481 | "se4d4e", "s5edl", "se2g", "seg3r", "5sei", "se1le", "5self", "5selv", 482 | "4seme", "se4mol", "sen5at", "4senc", "sen4d", "s5ened", "sen5g", 483 | "s5enin", "4sentd", "4sentl", "sep3a3", "4s1er.", "s4erl", "ser4o", 484 | "4servo", "s1e4s", "se5sh", "ses5t", "5se5um", "5sev", "sev3en", 485 | "sew4i", "5sex", "4s3f", "2s3g", "s2h", "2sh.", "sh1er", "5shev", 486 | "sh1in", "sh3io", "3ship", "shiv5", "sho4", "sh5old", "shon3", "shor4", 487 | "short5", "4shw", "si1b", "s5icc", "3side.", "5sides", "5sidi", 488 | "si5diz", "4signa", "sil4e", "4sily", "2s1in", "s2ina", "5sine.", 489 | "s3ing", "1sio", "5sion", "sion5a", "si2r", "sir5a", "1sis", "3sitio", 490 | "5siu", "1siv", "5siz", "sk2", "4ske", "s3ket", "sk5ine", "sk5ing", 491 | "s1l2", "s3lat", "s2le", "slith5", "2s1m", "s3ma", "small3", "sman3", 492 | "smel4", "s5men", "5smith", "smol5d4", "s1n4", "1so", "so4ce", "soft3", 493 | "so4lab", "sol3d2", "so3lic", "5solv", "3som", "3s4on.", "sona4", 494 | "son4g", "s4op", "5sophic", "s5ophiz", "s5ophy", "sor5c", "sor5d", 495 | "4sov", "so5vi", "2spa", "5spai", "spa4n", "spen4d", "2s5peo", "2sper", 496 | "s2phe", "3spher", "spho5", "spil4", "sp5ing", "4spio", "s4ply", 497 | "s4pon", "spor4", "4spot", "squal4l", "s1r", "2ss", "s1sa", "ssas3", 498 | "s2s5c", "s3sel", "s5seng", "s4ses.", "s5set", "s1si", "s4sie", 499 | "ssi4er", "ss5ily", "s4sl", "ss4li", "s4sn", "sspend4", "ss2t", 500 | "ssur5a", "ss5w", "2st.", "s2tag", "s2tal", "stam4i", "5stand", 501 | "s4ta4p", "5stat.", "s4ted", "stern5i", "s5tero", "ste2w", "stew5a", 502 | "s3the", "st2i", "s4ti.", "s5tia", "s1tic", "5stick", "s4tie", "s3tif", 503 | "st3ing", "5stir", "s1tle", "5stock", "stom3a", "5stone", "s4top", 504 | "3store", "st4r", "s4trad", "5stratu", "s4tray", "s4trid", "4stry", 505 | "4st3w", "s2ty", "1su", "su1al", "su4b3", "su2g3", "su5is", "suit3", 506 | "s4ul", "su2m", "sum3i", "su2n", "su2r", "4sv", "sw2", "4swo", "s4y", 507 | "4syc", "3syl", "syn5o", "sy5rin", "1ta", "3ta.", "2tab", "ta5bles", 508 | "5taboliz", "4taci", "ta5do", "4taf4", "tai5lo", "ta2l", "ta5la", 509 | "tal5en", "tal3i", "4talk", "tal4lis", "ta5log", "ta5mo", "tan4de", 510 | "tanta3", "ta5per", "ta5pl", "tar4a", "4tarc", "4tare", "ta3riz", 511 | "tas4e", "ta5sy", "4tatic", "ta4tur", "taun4", "tav4", "2taw", "tax4is", 512 | "2t1b", "4tc", "t4ch", "tch5et", "4t1d", "4te.", "tead4i", "4teat", 513 | "tece4", "5tect", "2t1ed", "te5di", "1tee", "teg4", "te5ger", "te5gi", 514 | "3tel.", "teli4", "5tels", "te2ma2", "tem3at", "3tenan", "3tenc", 515 | "3tend", "4tenes", "1tent", "ten4tag", "1teo", "te4p", "te5pe", "ter3c", 516 | "5ter3d", "1teri", "ter5ies", "ter3is", "teri5za", "5ternit", "ter5v", 517 | "4tes.", "4tess", "t3ess.", "teth5e", "3teu", "3tex", "4tey", "2t1f", 518 | "4t1g", "2th.", "than4", "th2e", "4thea", "th3eas", "the5at", "the3is", 519 | "3thet", "th5ic.", "th5ica", "4thil", "5think", "4thl", "th5ode", 520 | "5thodic", "4thoo", "thor5it", "tho5riz", "2ths", "1tia", "ti4ab", 521 | "ti4ato", "2ti2b", "4tick", "t4ico", "t4ic1u", "5tidi", "3tien", "tif2", 522 | "ti5fy", "2tig", "5tigu", "till5in", "1tim", "4timp", "tim5ul", "2t1in", 523 | "t2ina", "3tine.", "3tini", "1tio", "ti5oc", "tion5ee", "5tiq", "ti3sa", 524 | "3tise", "tis4m", "ti5so", "tis4p", "5tistica", "ti3tl", "ti4u", "1tiv", 525 | "tiv4a", "1tiz", "ti3za", "ti3zen", "2tl", "t5la", "tlan4", "3tle.", 526 | "3tled", "3tles.", "t5let.", "t5lo", "4t1m", "tme4", "2t1n2", "1to", 527 | "to3b", "to5crat", "4todo", "2tof", "to2gr", "to5ic", "to2ma", "tom4b", 528 | "to3my", "ton4ali", "to3nat", "4tono", "4tony", "to2ra", "to3rie", 529 | "tor5iz", "tos2", "5tour", "4tout", "to3war", "4t1p", "1tra", "tra3b", 530 | "tra5ch", "traci4", "trac4it", "trac4te", "tras4", "tra5ven", 531 | "trav5es5", "tre5f", "tre4m", "trem5i", "5tria", "tri5ces", "5tricia", 532 | "4trics", "2trim", "tri4v", "tro5mi", "tron5i", "4trony", "tro5phe", 533 | "tro3sp", "tro3v", "tru5i", "trus4", "4t1s2", "t4sc", "tsh4", "t4sw", 534 | "4t3t2", "t4tes", "t5to", "ttu4", "1tu", "tu1a", "tu3ar", "tu4bi", 535 | "tud2", "4tue", "4tuf4", "5tu3i", "3tum", "tu4nis", "2t3up.", "3ture", 536 | "5turi", "tur3is", "tur5o", "tu5ry", "3tus", "4tv", "tw4", "4t1wa", 537 | "twis4", "4two", "1ty", "4tya", "2tyl", "type3", "ty5ph", "4tz", "tz4e", 538 | "4uab", "uac4", "ua5na", "uan4i", "uar5ant", "uar2d", "uar3i", "uar3t", 539 | "u1at", "uav4", "ub4e", "u4bel", "u3ber", "u4bero", "u1b4i", "u4b5ing", 540 | "u3ble.", "u3ca", "uci4b", "uc4it", "ucle3", "u3cr", "u3cu", "u4cy", 541 | "ud5d", "ud3er", "ud5est", "udev4", "u1dic", "ud3ied", "ud3ies", 542 | "ud5is", "u5dit", "u4don", "ud4si", "u4du", "u4ene", "uens4", "uen4te", 543 | "uer4il", "3ufa", "u3fl", "ugh3en", "ug5in", "2ui2", "uil5iz", "ui4n", 544 | "u1ing", "uir4m", "uita4", "uiv3", "uiv4er.", "u5j", "4uk", "u1la", 545 | "ula5b", "u5lati", "ulch4", "5ulche", "ul3der", "ul4e", "u1len", 546 | "ul4gi", "ul2i", "u5lia", "ul3ing", "ul5ish", "ul4lar", "ul4li4b", 547 | "ul4lis", "4ul3m", "u1l4o", "4uls", "uls5es", "ul1ti", "ultra3", 548 | "4ultu", "u3lu", "ul5ul", "ul5v", "um5ab", "um4bi", "um4bly", "u1mi", 549 | "u4m3ing", "umor5o", "um2p", "unat4", "u2ne", "un4er", "u1ni", "un4im", 550 | "u2nin", "un5ish", "uni3v", "un3s4", "un4sw", "unt3ab", "un4ter.", 551 | "un4tes", "unu4", "un5y", "un5z", "u4ors", "u5os", "u1ou", "u1pe", 552 | "uper5s", "u5pia", "up3ing", "u3pl", "up3p", "upport5", "upt5ib", 553 | "uptu4", "u1ra", "4ura.", "u4rag", "u4ras", "ur4be", "urc4", "ur1d", 554 | "ure5at", "ur4fer", "ur4fr", "u3rif", "uri4fic", "ur1in", "u3rio", 555 | "u1rit", "ur3iz", "ur2l", "url5ing.", "ur4no", "uros4", "ur4pe", 556 | "ur4pi", "urs5er", "ur5tes", "ur3the", "urti4", "ur4tie", "u3ru", "2us", 557 | "u5sad", "u5san", "us4ap", "usc2", "us3ci", "use5a", "u5sia", "u3sic", 558 | "us4lin", "us1p", "us5sl", "us5tere", "us1tr", "u2su", "usur4", "uta4b", 559 | "u3tat", "4ute.", "4utel", "4uten", "uten4i", "4u1t2i", "uti5liz", 560 | "u3tine", "ut3ing", "ution5a", "u4tis", "5u5tiz", "u4t1l", "ut5of", 561 | "uto5g", "uto5matic", "u5ton", "u4tou", "uts4", "u3u", "uu4m", "u1v2", 562 | "uxu3", "uz4e", "1va", "5va.", "2v1a4b", "vac5il", "vac3u", "vag4", 563 | "va4ge", "va5lie", "val5o", "val1u", "va5mo", "va5niz", "va5pi", 564 | "var5ied", "3vat", "4ve.", "4ved", "veg3", "v3el.", "vel3li", "ve4lo", 565 | "v4ely", "ven3om", "v5enue", "v4erd", "5vere.", "v4erel", "v3eren", 566 | "ver5enc", "v4eres", "ver3ie", "vermi4n", "3verse", "ver3th", "v4e2s", 567 | "4ves.", "ves4te", "ve4te", "vet3er", "ve4ty", "vi5ali", "5vian", 568 | "5vide.", "5vided", "4v3iden", "5vides", "5vidi", "v3if", "vi5gn", 569 | "vik4", "2vil", "5vilit", "v3i3liz", "v1in", "4vi4na", "v2inc", "vin5d", 570 | "4ving", "vio3l", "v3io4r", "vi1ou", "vi4p", "vi5ro", "vis3it", "vi3so", 571 | "vi3su", "4viti", "vit3r", "4vity", "3viv", "5vo.", "voi4", "3vok", 572 | "vo4la", "v5ole", "5volt", "3volv", "vom5i", "vor5ab", "vori4", "vo4ry", 573 | "vo4ta", "4votee", "4vv4", "v4y", "w5abl", "2wac", "wa5ger", "wag5o", 574 | "wait5", "w5al.", "wam4", "war4t", "was4t", "wa1te", "wa5ver", "w1b", 575 | "wea5rie", "weath3", "wed4n", "weet3", "wee5v", "wel4l", "w1er", 576 | "west3", "w3ev", "whi4", "wi2", "wil2", "will5in", "win4de", "win4g", 577 | "wir4", "3wise", "with3", "wiz5", "w4k", "wl4es", "wl3in", "w4no", 578 | "1wo2", "wom1", "wo5ven", "w5p", "wra4", "wri4", "writa4", "w3sh", 579 | "ws4l", "ws4pe", "w5s4t", "4wt", "wy4", "x1a", "xac5e", "x4ago", "xam3", 580 | "x4ap", "xas5", "x3c2", "x1e", "xe4cuto", "x2ed", "xer4i", "xe5ro", 581 | "x1h", "xhi2", "xhil5", "xhu4", "x3i", "xi5a", "xi5c", "xi5di", "x4ime", 582 | "xi5miz", "x3o", "x4ob", "x3p", "xpan4d", "xpecto5", "xpe3d", "x1t2", 583 | "x3ti", "x1u", "xu3a", "xx4", "y5ac", "3yar4", "y5at", "y1b", "y1c", 584 | "y2ce", "yc5er", "y3ch", "ych4e", "ycom4", "ycot4", "y1d", "y5ee", 585 | "y1er", "y4erf", "yes4", "ye4t", "y5gi", "4y3h", "y1i", "y3la", 586 | "ylla5bl", "y3lo", "y5lu", "ymbol5", "yme4", "ympa3", "yn3chr", "yn5d", 587 | "yn5g", "yn5ic", "5ynx", "y1o4", "yo5d", "y4o5g", "yom4", "yo5net", 588 | "y4ons", "y4os", "y4ped", "yper5", "yp3i", "y3po", "y4poc", "yp2ta", 589 | "y5pu", "yra5m", "yr5ia", "y3ro", "yr4r", "ys4c", "y3s2e", "ys3ica", 590 | "ys3io", "3ysis", "y4so", "yss4", "ys1t", "ys3ta", "ysur4", "y3thin", 591 | "yt3ic", "y1w", "za1", "z5a2b", "zar2", "4zb", "2ze", "ze4n", "ze4p", 592 | "z1er", "ze3ro", "zet4", "2z1i", "z4il", "z4is", "5zl", "4zm", "1zo", 593 | "zo4m", "zo5ol", "zte4", "4z1z2", "z4zy"]; 594 | 595 | HyphenatorEnglish.exceptions = ["as-so-ciate", "as-so-ciates", 596 | "dec-li-na-tion", "oblig-a-tory", "phil-an-thropic", "present", 597 | "presents", "project", "projects", "reci-procity", "re-cog-ni-zance", 598 | "ref-or-ma-tion", "ret-ri-bu-tion", "ta-ble"] -------------------------------------------------------------------------------- /src/hyphenator.js: -------------------------------------------------------------------------------- 1 | Hyphenator = function() { 2 | var addPattern = function(h, p) { 3 | var chars = p.split("").filter(function(x) { return x.match(/\D/) }); 4 | var points = p.split(/\D/).map(function(x) { return parseInt(x) || 0 }); 5 | var t = h.trie; 6 | chars.map(function(x) { 7 | if (!t[x]) { t[x] = {} }; 8 | t = t[x]; 9 | }); 10 | t["_"] = points; 11 | }; 12 | var loadPatterns = function(h, languageset) { 13 | if(!languageset) languageset = HyphenatorEnglish; 14 | languageset.patterns.forEach(function(p) { addPattern(h, p) }); 15 | languageset.exceptions.forEach(function(x) { 16 | h.exceptions[x.replace(/-/g,"")] = [ 0 ].concat(x.split(/[^-]/).map(function(l) { return l == "-" ? 1 : 0})); 17 | }); 18 | }; 19 | 20 | var hyphenate = function(word, delim) { 21 | if (!delim) delim = "-"; 22 | if (word.length < this.minWord) return [word]; 23 | var points = this.exceptions[word.toLowerCase()]; 24 | word = word.split(""); 25 | if (!points) { 26 | var work = ["." ].concat( word.map(function(s) { return s.toLowerCase() })).concat(["."]); 27 | var points = [0].concat(work.map(function(x) { return 0 })); 28 | for (var i = 0; i < work.length; i++) { 29 | var t = this.trie; 30 | for (var j = i;j < work.length; j++) { 31 | if (!t[work[j]]) break; 32 | t = t[work[j]]; 33 | var p; 34 | if (p = t._) { 35 | for (k = 0; k < p.length; k++) { 36 | if (points[i+k] < p[k]) { 37 | points[i+k] = p[k]; 38 | } 39 | } 40 | } 41 | } 42 | } 43 | for (var i= 0; i < this.minPrefix + 1; i++) points[i] = 0; 44 | for (var i= points.length; i > points.length-this.maxPrefix; i--) points[i] = 0; 45 | } 46 | var pieces = ['']; 47 | var i; 48 | for (i =0; i < word.length; i++) { 49 | pieces[pieces.length-1] = pieces[pieces.length-1] + word[i]; 50 | if (points[2+i] % 2) { pieces.push("") } 51 | } 52 | return pieces; 53 | }; 54 | return function (patterns, exceptions) { 55 | this.minWord = 5; this.minPrefix = 2; this.minPrefix = 2; 56 | this.trie = {}; 57 | this.exceptions = {}; 58 | this.hyphenate = hyphenate; 59 | loadPatterns(this, patterns, exceptions); 60 | return this; 61 | } 62 | }(); 63 | -------------------------------------------------------------------------------- /src/jQuery-FontSpy.js: -------------------------------------------------------------------------------- 1 | /* jQuery-FontSpy.js v3.0.0 2 | * https://github.com/patrickmarabeas/jQuery-FontSpy.js 3 | * 4 | * Copyright 2013, Patrick Marabeas http://pulse-dev.com 5 | * Released under the MIT license 6 | * http://opensource.org/licenses/mit-license.php 7 | * 8 | * Date: 02/11/2015 9 | */ 10 | 11 | (function( w, $ ) { 12 | 13 | fontSpy = function ( fontName, conf ) { 14 | var $html = $('html'), 15 | $body = $('body'), 16 | fontFamilyName = fontName; 17 | 18 | // Throw error if fontName is not a string or not is left as an empty string 19 | if (typeof fontFamilyName !== 'string' || fontFamilyName === '') { 20 | throw 'A valid fontName is required. fontName must be a string and must not be an empty string.'; 21 | } 22 | 23 | var defaults = { 24 | font: fontFamilyName, 25 | fontClass: fontFamilyName.toLowerCase().replace( /\s/g, '' ), 26 | success: function() {}, 27 | failure: function() {}, 28 | testFont: 'Courier New', 29 | testString: 'QW@HhsXJ', 30 | glyphs: '', 31 | delay: 50, 32 | timeOut: 1000, 33 | callback: $.noop 34 | }; 35 | 36 | var config = $.extend( defaults, conf ); 37 | 38 | var $tester = $('' + config.testString+config.glyphs + '') 39 | .css('position', 'absolute') 40 | .css('top', '-9999px') 41 | .css('left', '-9999px') 42 | .css('visibility', 'hidden') 43 | .css('fontFamily', config.testFont) 44 | .css('fontSize', '250px'); 45 | 46 | $body.append($tester); 47 | 48 | var fallbackFontWidth = $tester.outerWidth(); 49 | 50 | $tester.css('fontFamily', config.font + ',' + config.testFont); 51 | 52 | var failure = function () { 53 | $html.addClass("no-"+config.fontClass); 54 | if( config && config.failure ) { 55 | config.failure(); 56 | } 57 | config.callback(new Error('FontSpy timeout')); 58 | $tester.remove(); 59 | }; 60 | 61 | var success = function () { 62 | config.callback(); 63 | $html.addClass(config.fontClass); 64 | if( config && config.success ) { 65 | config.success(); 66 | } 67 | $tester.remove(); 68 | }; 69 | 70 | var retry = function () { 71 | setTimeout(checkFont, config.delay); 72 | config.timeOut = config.timeOut - config.delay; 73 | }; 74 | 75 | var checkFont = function () { 76 | var loadedFontWidth = $tester.outerWidth(); 77 | 78 | if (fallbackFontWidth !== loadedFontWidth){ 79 | success(); 80 | } else if(config.timeOut < 0) { 81 | failure(); 82 | } else { 83 | retry(); 84 | } 85 | } 86 | 87 | checkFont(); 88 | } 89 | })( this, jQuery ); -------------------------------------------------------------------------------- /src/kashida.js: -------------------------------------------------------------------------------- 1 | // Kashida insertion rules 2 | 3 | var joiningData = { 4 | 0x0621: 'U', 0x0622: 'R', 0x0623: 'R', 0x0624: 'R', 0x0625: 'R', 5 | 0x0627: 'R', 0x0629: 'R', 0x062F: 'R', 0x0630: 'R', 0x0631: 'R', 6 | 0x0632: 'R', 0x0640: 'C', 0x0648: 'R', 0x0671: 'R', 0x0672: 'R', 7 | 0x0673: 'R', 0x0674: 'U', 0x0675: 'R', 0x0676: 'R', 0x0677: 'R', 8 | 0x0688: 'R', 0x0689: 'R', 0x068A: 'R', 0x068B: 'R', 0x068C: 'R', 9 | 0x068D: 'R', 0x068E: 'R', 0x068F: 'R', 0x0690: 'R', 0x0691: 'R', 10 | 0x0692: 'R', 0x0693: 'R', 0x0694: 'R', 0x0695: 'R', 0x0696: 'R', 11 | 0x0697: 'R', 0x0698: 'R', 0x0699: 'R', 0x06C0: 'R', 0x06C3: 'R', 12 | 0x06C4: 'R', 0x06C5: 'R', 0x06C6: 'R', 0x06C7: 'R', 0x06C8: 'R', 13 | 0x06C9: 'R', 0x06CA: 'R', 0x06CB: 'R', 0x06CD: 'R', 0x06CF: 'R', 14 | 0x06D2: 'R', 0x06D3: 'R', 0x06D5: 'R', 0x06DD: 'U', 0x06EE: 'R', 15 | 0x06EF: 'R' 16 | } 17 | 18 | var kashidaTable = { 19 | "با": 1, "بج": -1, "بد": -1, "بر": -1, "بط": 1, "بل": -1, "بم": 1, "بن": 1, "به":1, 20 | "جا": -1, "جج": -1, "جد": -1, "جر": -1, "جط": 1, "جع": -1, "جك": -1, "جل": -1, "جم": -1, "جن": -1, "جم": -1, "جن": -1, "جه": -1, "جو": -1, 21 | "سا": 1, "سن": 2, "سج": -1, "سد": -1, "سر": -1, "سس": 2, "سص": 2, "سط": 1, "سع": -1, "سف": -1, "سق": -1, "سك": 2, "سل": 2, "سم": -1, "سن": 2, "سه": -1, "سو": -1, 22 | "صا": 1, "صن": 2, "صج": -1, "صد": -1, "صر": -1, "صس": 2, "صص": 2, "صط": 1, "صع": -1, "صف": -1, "صق": -1, "صك": 2, "صل": 2, "صم": -1, "صن": 2, "صه": -1, "صو": -1, 23 | "طا": 1, "طن": 2, "طج": -1, "طد": -1, "طر": -1, "طس": 2, "طص": 2, "طط": 1, "طع": -1, "طف": -1, "طق": -1, "طك": 2, "طل": 2, "طم": -1, "طن": 2, "طه": -1, "طو": -1, 24 | "عا": -1, "عج": -1, "عد": -1, "عر": -1, "عط": 1, "عع": -1, "عل": -1, "عم": -1, "عن": -1, "عه": -1, 25 | // XXX 26 | "كل": 1, 27 | "لم": -1, 28 | } 29 | var zwj = '\u200d' 30 | 31 | for (var i = 0x620; i <= 0x6FF; i++) { if (!joiningData[i]) joiningData[i] = "D" } 32 | 33 | function needsJoiner(pre,post) { 34 | var pre2 = pre.replace(/[^\u0620-\u064A]/g, "") 35 | var post2 = post.replace(/[^\u0620-\u064A]/g, "") 36 | var prejoin = joiningData[pre2.charCodeAt(pre2.length-1)] 37 | var postjoin = joiningData[post2.charCodeAt(0)] 38 | if ((prejoin == "D") && (postjoin == "D" || postjoin == "R")) { 39 | return true; 40 | } 41 | return false; 42 | } 43 | 44 | var kashCache = {} 45 | 46 | function applyKashidaRules(s) { 47 | // Standard Arabic "letters" run from 0x620 to 0x64A. 48 | // For the purposes of counting letters in a word, we ignore all others. 49 | if (s in kashCache) { return kashCache[s] } 50 | var letters = s.replace(/[^\u0620-\u064A]/g, "") 51 | var length = letters.length 52 | var dontStretch = [ {'token': s, 'kashida': false} ] 53 | if (length < 2) { 54 | return (kashCache[s] = dontStretch) 55 | } 56 | 57 | if (length == 2) { 58 | // Initial Seen can be lengthened 59 | if (letters[0] == "س" || letters[0] == "ش") { 60 | return (kashCache[s] = [ {'token': s, 'kashida': true} ]); 61 | } 62 | else { 63 | return (kashCache[s] = dontStretch) 64 | } 65 | } 66 | 67 | if (length == 3) { // Play it safe 68 | return (kashCache[s] = dontStretch) 69 | } 70 | 71 | if (length == 4 || length == 5) { // Ibn Khalouf 72 | if (letters == "الله") { 73 | return (kashCache[s] = dontStretch) // This one is special 74 | } 75 | // Split into three chunks: one before the stretchable letter, one containing just the 76 | // second stretchable letter, and one containing the rest. Insert ZWJs as appropriate. 77 | var chunks = s.match(/^([^\u0620-\u064A]*[\u0620-\u064A][^\u0620-\u064A]*)([\u0620-\u064A])(.*)$/) 78 | 79 | var pre = chunks[1] 80 | var stretchy = chunks[2] 81 | var post = chunks[3] 82 | if (needsJoiner(pre,stretchy)) { 83 | pre = zwj + pre; stretchy = zwj + stretchy 84 | } 85 | if (needsJoiner(stretchy,post)) { 86 | // You would hope so... 87 | stretchy = zwj + stretchy + zwj; 88 | post = zwj + post; 89 | return (kashCache[s] = [ 90 | {'token': pre, 'kashida': false }, 91 | {'token': stretchy, 'kashida': true }, 92 | {'token': post, 'kashida': false }, 93 | ]); 94 | } else { 95 | return dontStretch; 96 | } 97 | } 98 | 99 | chunks = s.match(/([^\u0620-\u064A]*[\u0620-\u064A])/g) 100 | 101 | nodes = [ { 'token': chunks[0], 'kashida': false } ] 102 | var post; 103 | for (var i = 2; i < chunks.length; i++) { 104 | var pre = chunks[i-1]; var pre2 = pre.substr(-1) 105 | post = chunks[i]; var post2 = post.substr(-1); 106 | if (needsJoiner(nodes[nodes.length-1].token.substr(-1), pre2)) { 107 | pre = zwj + pre 108 | nodes[nodes.length-1].token = nodes[nodes.length-1].token + zwj 109 | } 110 | if (kashidaTable[pre2+post2]) { 111 | nodes.push({ 'token': pre, 'kashida': true }) 112 | } else { 113 | nodes.push({ 'token': pre, 'kashida': false }) 114 | } 115 | } 116 | nodes.push({ 'token': post, 'kashida': false }) 117 | return (kashCache[s] = nodes); 118 | } 119 | 120 | var testing = false; 121 | if (testing) { 122 | const assert = require('assert'); 123 | assert.deepEqual(applyKashidaRules("سر"), [ {'token': "سر", 'kashida': true}]) 124 | assert.deepEqual(applyKashidaRules("تر"), [ {'token': "تر", 'kashida': false}]) 125 | assert.deepEqual(applyKashidaRules("ترن"), [ {'token': "ترن", 'kashida': false}]) 126 | } 127 | -------------------------------------------------------------------------------- /src/newbreak.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /** 3 | * This is newbreak, a text justification algorithm designed to play nice 4 | * with variable fonts and non-Latin scripts. 5 | * 6 | * It is based vaguely on the classic Knuth-Plass algorithm as implemented 7 | * in TeX and SILE, but with a few interesting changes. (It's also a lot 8 | * simpler, which may also mean dumber.) 9 | */ 10 | var __assign = (this && this.__assign) || function () { 11 | __assign = Object.assign || function(t) { 12 | for (var s, i = 1, n = arguments.length; i < n; i++) { 13 | s = arguments[i]; 14 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) 15 | t[p] = s[p]; 16 | } 17 | return t; 18 | }; 19 | return __assign.apply(this, arguments); 20 | }; 21 | Object.defineProperty(exports, "__esModule", { value: true }); 22 | /** 23 | * A node class to feed to the linebreaker 24 | * @class Node 25 | */ 26 | var Node = /** @class */ (function () { 27 | function Node() { 28 | } 29 | return Node; 30 | }()); 31 | exports.Node = Node; 32 | var INF_BAD = 10000; 33 | /** 34 | * The main line breaking algorithm 35 | * @class Linebreaker 36 | */ 37 | var Linebreaker = /** @class */ (function () { 38 | /** 39 | * Create a new linebreaker. 40 | * @constructor 41 | * @param {Node[]} nodes - array of nodes to break 42 | * @param {number[]} hsize - array of target line widths 43 | * 44 | * As with TeX, the last width in the `hsize` array is propagated to all 45 | * future lines. For example, if `hsize` is `[70,50]` then the first line 46 | * of the paragraph will be set at 70 units wide and all further lines will 47 | * be 50 units. 48 | **/ 49 | function Linebreaker(nodes, hsize) { 50 | this.nodes = []; 51 | this.hsize = hsize; 52 | for (var _i = 0, nodes_1 = nodes; _i < nodes_1.length; _i++) { 53 | var n = nodes_1[_i]; 54 | this.nodes.push(__assign({}, n)); 55 | } 56 | // Add dummy node to end. 57 | this.nodes.push({ width: 0, breakable: true, penalty: 0 }); 58 | this.prepareNodes(); 59 | this.memoizeCache = {}; 60 | } 61 | /** 62 | Sets up helpful shortcuts on node objects 63 | */ 64 | Linebreaker.prototype.prepareNodes = function () { 65 | for (var thisNodeIx = 0; thisNodeIx < this.nodes.length; thisNodeIx++) { 66 | var n = this.nodes[thisNodeIx]; 67 | this.prepareNode(n, thisNodeIx); 68 | if (n.alternates) { 69 | for (var _i = 0, _a = n.alternates; _i < _a.length; _i++) { 70 | var a = _a[_i]; 71 | this.prepareNode(a, thisNodeIx); 72 | } 73 | } 74 | } 75 | }; 76 | Linebreaker.prototype.prepareNode = function (n, ix) { 77 | n.originalIndex = ix; 78 | if (n.penalty < 0) { 79 | n.anyNegativePenalties = true; 80 | } 81 | if (n.breakable) { 82 | n.anyBreakable = true; 83 | } 84 | if (!n.stretchContribution) { 85 | n.stretchContribution = [1]; 86 | } 87 | if (!n.shrinkContribution) { 88 | n.shrinkContribution = [1]; 89 | } 90 | if (n.alternates) { 91 | for (var _i = 0, _a = n.alternates; _i < _a.length; _i++) { 92 | var a = _a[_i]; 93 | if (a.penalty < 0) { 94 | n.anyNegativePenalties = true; 95 | } 96 | if (a.breakable) { 97 | n.anyBreakable = true; 98 | } 99 | } 100 | } 101 | }; 102 | /** 103 | * Run the break algorithm. 104 | * You may want to feed the output of this into the `ratios` method below. 105 | * @returns {number[]} An array of node indexes at which to break. 106 | */ 107 | Linebreaker.prototype.doBreak = function (options) { 108 | if (options === void 0) { options = {}; } 109 | var defaultOptions = { 110 | start: 0, 111 | end: this.nodes.length - 1, 112 | fullJustify: false, 113 | unacceptableRatio: 0.5, 114 | linePenalty: 10 115 | }; 116 | var best = this.findBreakpoints(0, __assign({}, options, defaultOptions)); 117 | this.assignTargetWidths(best); 118 | this.debug("Final best consideration:"); 119 | this.debugConsideration(best.lines); 120 | return best.lines; 121 | }; 122 | // A shortcut for finding the target length of the given line number. 123 | Linebreaker.prototype.targetFor = function (lineNo) { 124 | return this.hsize[lineNo > this.hsize.length - 1 ? this.hsize.length - 1 : lineNo]; 125 | }; 126 | Linebreaker.prototype.hasAnyNegativePenalties = function (nodelist) { 127 | for (var _i = 0, nodelist_1 = nodelist; _i < nodelist_1.length; _i++) { 128 | var n = nodelist_1[_i]; 129 | if (n.anyNegativePenalties) { 130 | return true; 131 | } 132 | } 133 | return false; 134 | }; 135 | /* 136 | Finally we arrive at the core of the algorithm. The basic principle is 137 | actually quite simple: find the optimum solution (minimize the number 138 | of demerits) given all the possible, feasible breakpoints in a paragraph. 139 | You do this by walking the tree of all possible breakpoints. Tree 140 | search algorithms are recursive algorithms. Here's another way to 141 | say it: To find the best way to break a paragraph, 142 | 143 | * Find the possible breakpoints for line one. 144 | * For each possible line-one breakpoint, find the best way to 145 | break the rest of the paragraph. 146 | * Compare the solutions and return the best one. 147 | 148 | This leads itself very naturally to a recursive implementation. 149 | 150 | The reason this is so complicated in the case of TeX is that Knuth was 151 | a great programmer trying to write a really neat algorithm that would 152 | run quickly on a small computer with little memory. So he had to do all 153 | kinds of clever linear programming to run in the optimum time and memory. 154 | I am not a great programmer and I don't give a damn. I'm quite happy to 155 | use the recursive implementation, trading off time and memory for 156 | simplicity. 157 | */ 158 | Linebreaker.prototype.findBreakpoints = function (lineNo, options) { 159 | var target = this.targetFor(lineNo); 160 | /* 161 | One of the reasons for *not* using a recursive solution is that 162 | they tend to balloon time and memory, and also redo the same computation 163 | lots of times. We avoid both these problems by memoization. 164 | */ 165 | var key = JSON.stringify(options); 166 | this.debug("Looking for breakpoints " + options.start + "->" + options.end + " to fill " + target + " on line " + lineNo, lineNo); 167 | if (key in this.memoizeCache) { 168 | this.debug("Returned from cache", lineNo); 169 | this.debug(this.memoizeCache[key], lineNo); 170 | return this.memoizeCache[key]; 171 | } 172 | var relevant = this.nodes.slice(options.start, options.end + 1); 173 | var anyNegativePenalties = this.hasAnyNegativePenalties(relevant); 174 | // This represents how far along the line we are towards the target width. 175 | var curWidth = 0; 176 | var curStretch = 0; 177 | var curShrink = 0; 178 | var considerations = []; 179 | var bestBadness = Infinity; 180 | var seenAlternate = false; 181 | var that = this; 182 | var node; 183 | var addNodeToTotals = function (n) { 184 | that.debug("Adding width " + n.width + " for node " + (n.debugText || n.text || ""), lineNo); 185 | curWidth += n.width; 186 | curStretch += n.stretch || 0; 187 | curShrink += n.shrink || 0; 188 | }; 189 | /* 190 | Now we walk through the relevant nodes, looking for feasible breakpoints 191 | and keeping track of how close we are to the target. 192 | */ 193 | for (var thisNodeIx = 0; thisNodeIx < relevant.length; thisNodeIx++) { 194 | var thisNode = relevant[thisNodeIx]; 195 | if (thisNode.alternates && thisNode.alternates.length > 0) { 196 | seenAlternate = true; 197 | } 198 | // If we can't break here... don't try to break here. 199 | this.debug("Node " + thisNode.originalIndex + " " + (thisNode.debugText || thisNode.text || ""), lineNo); 200 | if (!thisNode.anyBreakable) { 201 | addNodeToTotals(thisNode); 202 | continue; 203 | } 204 | this.debug(" Target: " + target + ". Current width: " + curWidth + ". Current stretch: " + curStretch, lineNo); 205 | var lastLine = thisNode.originalIndex >= this.nodes[this.nodes.length - 1].originalIndex - 2; 206 | // If we're a long way from the end and stretching to get there would be horrible, 207 | // don't even bother investigating this breakpoint. 208 | // console.log("Width",curWidth, "Target:", target, "Ratio: ",curWidth/target, "Unacceptable: ",options.unacceptableRatio) 209 | if ((curWidth / target < options.unacceptableRatio && !lastLine) || 210 | curWidth / target > (2 - options.unacceptableRatio)) { 211 | this.debug(" Too far", lineNo); 212 | addNodeToTotals(thisNode); 213 | continue; 214 | } 215 | // We have a potential breakpoint. Build a Line node 216 | // Find out how bad this potential breakpoint is. 217 | this.debug("Possibility!", lineNo); 218 | var line = { 219 | nodes: relevant.slice(0, thisNodeIx), 220 | ratio: curWidth / target, 221 | shortfall: target - curWidth, 222 | totalShrink: curShrink, 223 | totalStretch: curStretch, 224 | options: options 225 | }; 226 | line.badness = this.badness(line); 227 | if (seenAlternate) { 228 | line = this.tryToImprove(line, target); 229 | } 230 | // If we are at e.g. a hyphenation point (not breakable but has breakable 231 | // alternate) then only consider this is if the last node has become breakable 232 | // through considering the alternates 233 | if (!thisNode.breakable && !(line.nodes[line.nodes.length - 1].breakable)) { 234 | that.debug("Adding width " + thisNode.width + " for node " + (thisNode.debugText || thisNode.text || ""), lineNo); 235 | addNodeToTotals(thisNode); 236 | continue; 237 | } 238 | var badness = line.badness; 239 | this.debug(" Badness was " + badness, lineNo); 240 | if (bestBadness < badness && !anyNegativePenalties) { 241 | // We have a better option already, and we have no chance 242 | // to improve this option, don't bother. 243 | } 244 | else if (relevant.length == 1) { 245 | // We aren't going to find any other nodes. Don't bother 246 | } 247 | else { 248 | // It's worth a further look at this breakpoint. 249 | // If we have nodes A...Z and we test a break at C, we need to work 250 | // out the best way to break the sub-paragraph D...Z. 251 | // Remembering that "Breakpoint path = Breakpoint for first line 252 | // + breakpoint path for remainder of paragraph", first we make 253 | // a line set which holds the first line... 254 | var newConsideration = { 255 | totalBadness: badness, 256 | lines: [line] 257 | }; 258 | if (thisNode.originalIndex + 1 < options.end) { 259 | this.debug("Recursing, now start at " + (thisNode.originalIndex + 1), lineNo); 260 | var recursed = this.findBreakpoints(lineNo + 1, __assign({}, options, { start: thisNode.originalIndex + 1, end: options.end })); 261 | this.debug("In that recursion, total badness = " + recursed.totalBadness); 262 | // ...and then we add to it the current solution 263 | newConsideration.lines = newConsideration.lines.concat(recursed.lines); 264 | newConsideration.totalBadness += recursed.totalBadness; 265 | // Save this option if it's better than we've seen already, 266 | // to save recursing into worse ones. 267 | if (newConsideration.totalBadness < bestBadness) { 268 | bestBadness = newConsideration.totalBadness; 269 | } 270 | considerations.push(newConsideration); 271 | } 272 | else { 273 | considerations.push(newConsideration); 274 | } 275 | } 276 | addNodeToTotals(thisNode); 277 | } 278 | // If we found nothing, give up. 279 | if (considerations.length < 1) { 280 | return { totalBadness: INF_BAD * INF_BAD, lines: [] }; 281 | } 282 | // Otherwise, find the best of the bunch. 283 | this.debug("Choosing between considerations:"); 284 | for (var _i = 0, considerations_1 = considerations; _i < considerations_1.length; _i++) { 285 | var c = considerations_1[_i]; 286 | this.debug("With badness " + c.totalBadness + ": "); 287 | this.debugConsideration(c.lines); 288 | } 289 | var best = considerations.reduce(function (a, b) { return a.totalBadness <= b.totalBadness ? a : b; }); 290 | this.debug("Best answer for " + key + " was:", lineNo); 291 | this.debugConsideration(best.lines); 292 | this.debug(" with badness " + best.totalBadness, lineNo); 293 | // Store it in the memoize cache for next time. 294 | this.memoizeCache[key] = best; 295 | return best; 296 | }; 297 | Linebreaker.prototype.badness = function (line) { 298 | var bad = 0; 299 | if (line.shortfall == 0) { 300 | bad = 0; 301 | } 302 | else if (line.shortfall > 0) { 303 | // XXX use stretch/shrink penalties here instead 304 | bad = Math.floor(100 * Math.pow((line.shortfall / (0.001 + line.totalStretch)), 3)); 305 | } 306 | else { 307 | bad = Math.floor(100 * Math.pow((-line.shortfall / (0.001 + line.totalShrink)), 3)); 308 | } 309 | // consider also penalties. Break penalty: 310 | bad += line.nodes[line.nodes.length - 1].penalty; 311 | // Line penalty 312 | bad += line.options.linePenalty; 313 | // Invert negative penalties 314 | for (var _i = 0, _a = line.nodes; _i < _a.length; _i++) { 315 | var n = _a[_i]; 316 | if (n.penalty < 0) { 317 | bad += n.penalty * n.penalty; 318 | } 319 | } 320 | // Any substitutions 321 | for (var _b = 0, _c = line.nodes; _b < _c.length; _b++) { 322 | var n = _c[_b]; 323 | bad += n.substitutionPenalty || 0; 324 | } 325 | return bad; 326 | }; 327 | Linebreaker.prototype.tryToImprove = function (line, target) { 328 | var nodesWithAlternates = line.nodes.map(function (n) { return [n].concat((n.alternates || [])); }); 329 | var set; 330 | var bestLine = line; 331 | this.debug("Trying to improve, base badness is " + line.badness); 332 | for (var _i = 0, _a = this._cartesian_set(nodesWithAlternates); _i < _a.length; _i++) { 333 | set = _a[_i]; 334 | var newLine = { nodes: set, totalShrink: 0, totalStretch: 0, shortfall: target, options: line.options }; 335 | for (var _b = 0, set_1 = set; _b < set_1.length; _b++) { 336 | var n = set_1[_b]; 337 | newLine.totalShrink += n.shrink; 338 | newLine.totalStretch += n.stretch; 339 | newLine.shortfall -= n.width; 340 | } 341 | newLine.badness = this.badness(newLine); 342 | this.debug("New line is " + newLine.badness); 343 | if (newLine.badness < bestLine.badness) { 344 | bestLine = newLine; 345 | } 346 | } 347 | bestLine.ratio = (target - bestLine.shortfall) / target; 348 | return bestLine; 349 | }; 350 | Linebreaker.prototype.debugConsideration = function (lines) { 351 | this.debug("---"); 352 | for (var _i = 0, lines_1 = lines; _i < lines_1.length; _i++) { 353 | var l = lines_1[_i]; 354 | this.debug(l.ratio.toFixed(3) + " " + l.nodes.map(function (a) { return a.debugText || a.text || ""; }).join("")); 355 | } 356 | this.debug("---"); 357 | }; 358 | Linebreaker.prototype.debug = function (msg, lineNo) { 359 | if (lineNo === void 0) { lineNo = 0; } 360 | if (this.debugging) { 361 | var spacer = new Array(lineNo + 1).join(" + "); 362 | console.log(spacer + msg); 363 | } 364 | }; 365 | Linebreaker.prototype.assignTargetWidths = function (solution) { 366 | for (var _i = 0, _a = solution.lines; _i < _a.length; _i++) { 367 | var line = _a[_i]; 368 | this.assignTargetWidthsToLine(line); 369 | } 370 | }; 371 | Linebreaker.prototype.assignTargetWidthsToLine = function (line) { 372 | line.targetWidths = line.nodes.map(function (n) { return n.width; }); 373 | if (line.shortfall == 0) { 374 | return; 375 | } 376 | var level = 0; 377 | if (line.shortfall > 0) { 378 | while (line.shortfall > 0) { // We need to expand 379 | var thisLevelStretch = line.nodes.map(function (n) { return n.stretch * (n.stretchContribution[level] || 0); }); 380 | var thisLevelTotalStretch = thisLevelStretch.reduce(function (a, c) { return a + c; }, 0); // Sum 381 | if (thisLevelTotalStretch == 0) { 382 | break; 383 | } 384 | var ratio = line.shortfall / (0.001 + thisLevelTotalStretch); 385 | if (ratio > 1) { 386 | ratio = 1; 387 | } 388 | line.targetWidths = line.targetWidths.map(function (w, ix) { return w + ratio * thisLevelStretch[ix]; }); 389 | line.shortfall -= thisLevelTotalStretch * ratio; 390 | level = level + 1; 391 | } 392 | } 393 | else { 394 | while (line.shortfall < 0) { // We need to expand 395 | var thisLevelShrink = line.nodes.map(function (n) { return n.shrink * (n.shrinkContribution[level] || 0); }); 396 | var thisLevelTotalShrink = thisLevelShrink.reduce(function (a, c) { return a + c; }, 0); // Sum 397 | if (thisLevelTotalShrink == 0) { 398 | break; 399 | } 400 | var ratio = -line.shortfall / (0.001 + thisLevelTotalShrink); 401 | if (ratio > 1) { 402 | ratio = 1; 403 | } 404 | line.targetWidths = line.targetWidths.map(function (w, ix) { return w - ratio * thisLevelShrink[ix]; }); 405 | line.shortfall += thisLevelTotalShrink * ratio; 406 | level = level + 1; 407 | } 408 | } 409 | this.debug("Final widths:" + line.targetWidths.join(", ")); 410 | }; 411 | Linebreaker.prototype._cartesian_set = function (arg) { 412 | var r = []; 413 | var max = arg.length - 1; 414 | var helper = function (arr, i) { 415 | for (var j = 0, l = arg[i].length; j < l; j++) { 416 | var a = arr.slice(0); 417 | a.push(arg[i][j]); 418 | if (i == max) 419 | r.push(a); 420 | else 421 | helper(a, i + 1); 422 | } 423 | }; 424 | helper([], 0); 425 | return r; 426 | }; 427 | return Linebreaker; 428 | }()); 429 | exports.Linebreaker = Linebreaker; 430 | //# sourceMappingURL=newbreak.js.map -------------------------------------------------------------------------------- /src/newbreak.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"newbreak.js","sourceRoot":"","sources":["../newbreak.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /src/test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | var chai_1 = require("chai"); 4 | var mocha_1 = require("mocha"); 5 | var newbreak_1 = require("./newbreak"); 6 | function makeSomeStuff(count) { 7 | var nodelist = []; 8 | for (var i = 0; i < count; i++) { 9 | var node = { 10 | debugText: "laa" + i, 11 | width: 100, 12 | stretch: 0, 13 | shrink: 0, 14 | penalty: 0, 15 | breakable: false 16 | }; 17 | nodelist.push(node); 18 | var glue = { 19 | debugText: " ", 20 | width: 10, 21 | stretch: (i == count - 1 ? 1000000 : 15), 22 | penalty: 0, 23 | shrink: 3, 24 | breakable: true 25 | }; 26 | nodelist.push(glue); 27 | } 28 | return nodelist; 29 | } 30 | function checkAllNonBreakablesReturned(nodelist, lines) { 31 | var nonbreakablecount = nodelist.filter(function (x) { return !x.breakable; }).length; 32 | var nodesout = 0; 33 | for (var _i = 0, lines_1 = lines; _i < lines_1.length; _i++) { 34 | var l = lines_1[_i]; 35 | nodesout += l.nodes.filter(function (x) { return !x.breakable; }).length; 36 | } 37 | mocha_1.it('should return all the nodes', function () { 38 | chai_1.expect(nodesout).to.equal(nonbreakablecount); 39 | }); 40 | } 41 | mocha_1.describe("Single line", function () { 42 | var nodelist = makeSomeStuff(2); 43 | var breaker = new newbreak_1.Linebreaker(nodelist, [220]); 44 | var lines = breaker.doBreak({}); 45 | mocha_1.it('should have one line', function () { 46 | chai_1.expect(lines.length).to.equal(1); 47 | }); 48 | checkAllNonBreakablesReturned(nodelist, lines); 49 | }); 50 | mocha_1.describe("Two lines", function () { 51 | var nodelist = makeSomeStuff(4); 52 | console.log(nodelist); 53 | var breaker = new newbreak_1.Linebreaker(nodelist, [220]); 54 | var lines = breaker.doBreak({}); 55 | mocha_1.it('should have two lines', function () { 56 | chai_1.expect(lines.length).to.equal(2); 57 | }); 58 | console.log(lines); 59 | checkAllNonBreakablesReturned(nodelist, lines); 60 | }); 61 | //# sourceMappingURL=test.js.map -------------------------------------------------------------------------------- /src/test.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"test.js","sourceRoot":"","sources":["../test.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "sourceMap": true, 4 | "module": "commonjs", 5 | "target": "es5", 6 | "jsx": "react", 7 | "outDir": "src" 8 | }, 9 | "include": [ 10 | "." 11 | ], 12 | "compileOnSave": false 13 | } 14 | --------------------------------------------------------------------------------