├── 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 |
صومعهٔ عیسیست خوان اهل دل
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 |
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 |
--------------------------------------------------------------------------------