Add Mode of Beleriand
authorKris Kowal <kris.kowal@cixar.com>
Tue, 4 Sep 2012 03:15:06 +0000 (20:15 -0700)
committerKris Kowal <kris.kowal@cixar.com>
Tue, 4 Sep 2012 03:15:09 +0000 (20:15 -0700)
-   Support options in the data-mode attribute, like data-mode="general-use
    reverse-curls no-ach-laut"
-   Add reverseCurls option to Classical and General Use modes.

-   Consolidate document parse tree generation.
-   Convert normalizer to a parser adapter.
-   Create Trie Parsing framework.
-   Add double dot above tehta to Tengwar Parmaite bindings.

23 files changed:
README.md
alphabet.js
beleriand.js [new file with mode: 0644]
beleriand.txt [new file with mode: 0644]
classical.js
document-parser.js [new file with mode: 0644]
editor/index.html
editor/index.js
fonts.js [new file with mode: 0644]
general-use.js
modes.js [new file with mode: 0644]
normalize.js
parser.js
samples/index.html
spec/classical.js
spec/general-use.js
tengwar-parmaite.js
tests.js [deleted file]
trie-parser.js [new file with mode: 0644]
trie.js [new file with mode: 0644]
vanilla-tengwar.build.sh
vanilla-tengwar.js
vanilla-tengwar.min.js

index 7bcf6ba..ed90ee2 100644 (file)
--- a/README.md
+++ b/README.md
@@ -12,8 +12,12 @@ Using the Script
 ================
 
 The script searches the document for elements with the `tengwar` class.
-The body of a `tengwar` class must be rendered with the included Tengar
-Annatar variant webfont, using the included `tengwar-annatar.css`.
+The class must also include either `parmaite` or `annatar` to select the
+rendering font.  This is not merely for the purpose of applying the
+appropriate web font, but also instructs the script on which bindings to
+use for kerning tehtar.  The body of a `tengwar` class must be rendered
+with the included Tengar Annatar variant webfont or Tengwar Parmaitë
+using the included `tengwar-annatar.css` or `tengwar-parmaite.css`.
 
 If the element has a `data-tengwar` property, that property is expected
 to contain phonetic letters from the latin alphabet and gets transcribed
@@ -22,14 +26,23 @@ popular for Sindarin and English.  The script populates the element's
 inner HTML with the font bindings, rendering the desired tengwar text
 visible.
 
-If the element has a `data-mode=classical` property, the latin letters
-are instead transcribed into Tengwar Annatar key bindings through the
-Classical mode, popular for Quenya.
+    class="tengwar annatar"
+
+If the element has a `data-mode` property, the latin letters
+are instead transcribed into key bindings through the
+Classical mode, popular for Quenya, or the mode of Beleriand.  Various
+options can also be applied.
+
+    data-mode="general-use no-ach-laut reverse-curls"
+    data-mode="classical reverse-curls"
+    data-mode="beleriand"
 
 If the element has a `data-encoded` property, the value is expected to
 be a description of the tengwar and tehtar to display like
 `romen:a;ungwe:a;romen:o;numen` for "Aragorn" in the General Use mode.
 
+    data-encoded="romen:a;ungwe:a;romen:o;numen"
+
 Of course, a page can bypass the whole automated transcription process
 by statically populating the element with the desired key bindings and
 using neither of these data properties.
@@ -43,11 +56,16 @@ Using the Modules
 -   `tengwar/general-use` transcribes phonetic latin letters, as Tolkien
     wrote it, into Tengwar Notation in the General Use mode, suitable
     for Sindarin and many other languages.
-    -   `transcribe(text, options)` to Tengwar Annatar key bindings
+    -   `transcribe(text, options)` to key bindings for the font.
+        Tengwar Annatar by default.
     -   `encode(text, options)` to Tengwar Notation
     -   `parse(text, options)` to Tengwar Object Notation
     -   `makeOptions(options)`
         -   `font` defaults to the TengwarAnnatar module.
+        -   `block` whether to include HTML tags for paragraphs and line
+            breaks.
+        -   `plain` whether to exclude all HTML from the output,
+            making it suitable for plain text..
         -   `doubleNasalsWithTildeBelow`: Many tengwa can be doubled in
             General Use mode by placing a tilde above the tengwa, and
             many tengwa can be prefixed with the sound of the
@@ -57,6 +75,11 @@ Using the Modules
             their value.
             -   `false`: by default, a tilde above doubles a nasal
             -   `true`: a tilde below doubles a nasal
+        -   `reverseCurls`: In the Black Speech of the ring inscription,
+            among other samples, the "o" and "u" tehtar are reversed.
+            -   `false`: by default, the "o" tehta curls forward, and
+                "u" backward.
+            -   `true`: "o" curls backward, "u" forward.
         -   `noAchLaut`
             -   `false`: by default, "ch" is transcribed as ach-laut,
                 the "ch" as in "Bach".  "cc" is transcribed as "ch" as
@@ -67,13 +90,19 @@ Using the Modules
                 tehta.
             -   `true`: "is" is a short carrier with an I tehta and S
                 hook.
+
 -   `tengwar/classical` transcribes phonetic latin letters into Tengwar
     Notation in the Classical mode, most commonly used for Quenya.
-    -   `transcribe(text, options)` to Tengwar Annatar key bindings
+    -   `transcribe(text, options)` to key bindings for the font.
+        Tengwar Annatar by default.
     -   `encode(text, options)` to Tengwar Notation
     -   `parse(text, options)` to Tengwar Object Notation
     -   `makeOptions(options)`
         -   `font` defaults to the TengwarAnnatar module.
+        -   `block` whether to include HTML tags for paragraphs and line
+            breaks.
+        -   `plain` whether to exclude all HTML from the output,
+            making it suitable for plain text..
         -   `viyla`: In the earlier forms of the mode, the tengwa
             "vilya" represented the sound of the letter V.  The tengwa
             "vala" eventually replaced its role and "vilya" was renamed
@@ -82,6 +111,11 @@ Using the Modules
                 V.
             -   `true`: "vilya" serves for V, and W is interpreted as
                 the vowel U.
+        -   `reverseCurls`: In the Black Speech of the ring inscription,
+            among other samples, the "o" and "u" tehtar are reversed.
+            -   `false`: by default, the "o" tehta curls forward, and
+                "u" backward.
+            -   `true`: "o" curls backward, "u" forward.
         -   `iuRising`: In the Third Age, IU is a rising diphthong,
             meaning that the stress is on the second sound.  Whether to
             represent a rising diphthong in the same fashion as other
@@ -128,6 +162,21 @@ Using the Modules
                 -   HY is interpreted as "hyarmen"
                 -   HT still implies CHT so treated as "harma" as above.
                 -   CH, HL, HR, and HW (and WH) are not affected.
+
+-   `tengwar/beleriand`: transcribes phonetic latin letters into Tengwar
+    Notation in the mode of Beleriand, which is suitable for Sindarin
+    and uses full tengwar for most vowels, instead of tehtar.
+    -   `transcribe(text, options)` to key bindings for the font.
+        Tengwar Annatar by default.
+    -   `encode(text, options)` to Tengwar Notation
+    -   `parse(text, options)` to Tengwar Object Notation
+    -   `makeOptions(options)`
+        -   `font` defaults to the TengwarAnnatar module.
+        -   `block` whether to include HTML tags for paragraphs and line
+            breaks.
+        -   `plain` whether to exclude all HTML from the output,
+            making it suitable for plain text..
+
 -   `tengwar/tengwar-annatar`: Translates Tengwar Object Notation into
     key bindings for Johan Winge’s Tengwar Annatar font.  Provides the
     `makeColumn` primitive which is aware of how a column of tengwar and
index c0cf697..6dc6d4f 100644 (file)
@@ -47,6 +47,7 @@ exports.tehtar = [].concat(
 
 exports.aliases = {
     "vilya": "wilya",
-    "aha": "harma"
+    "aha": "harma",
+    "gasdil": "halla"
 };
 
diff --git a/beleriand.js b/beleriand.js
new file mode 100644 (file)
index 0000000..a7b8dea
--- /dev/null
@@ -0,0 +1,349 @@
+
+var TengwarParmaite = require("./tengwar-parmaite");
+var Parser = require("./parser");
+var makeDocumentParser = require("./document-parser");
+var normalize = require("./normalize");
+var punctuation = require("./punctuation");
+
+exports.name = "Mode of Beleriand";
+
+var defaults = {};
+exports.makeOptions = makeOptions;
+function makeOptions(options) {
+    options = options || defaults;
+    return {
+        font: options.font || TengwarParmaite,
+        block: options.block,
+        plain: options.plain
+    };
+}
+
+exports.transcribe = transcribe;
+function transcribe(text, options) {
+    options = makeOptions(options);
+    var font = options.font;
+    return font.transcribe(parse(text, options), options);
+}
+
+exports.encode = encode;
+function encode(text, options) {
+    options = makeOptions(options);
+    return Notation.encode(parse(text, options), options);
+}
+
+var parse = exports.parse = makeDocumentParser(parseNormalWord, makeOptions);
+
+function parseNormalWord(callback, options) {
+    return normalize(parseWord(callback, options));
+}
+
+function parseWord(callback, options, columns) {
+    columns = columns || [];
+    return parseColumn(function (column) {
+        if (column) {
+            return parseWord(
+                callback,
+                options,
+                columns.concat([column])
+            );
+        } else {
+            return callback(columns);
+        }
+    }, options);
+}
+
+function parseColumn(callback, options) {
+    return parseTengwa(function (column) {
+        if (column) {
+            return parseFollowingS(callback, column);
+        } else {
+            return callback();
+        }
+    }, options);
+}
+
+function parseTengwa(callback, options) {
+    var font = options.font;
+    var makeColumn = font.makeColumn;
+    return function (character) {
+        if (character === "n") { // n
+            return function (character) {
+                if (character === "t" || character === "d") { // n{t,d}
+                    return parseTengwa(function (column) {
+                        return callback(column.addTildeAbove());
+                    }, options)(character);
+                } else if (character === "c" || character === "g") { // n{c,g}
+                    return parseTengwa(callback, options)("ñ")(character);
+                } else if (character === "n") { // nn
+                    return callback(makeColumn("numen"));
+                } else { // n.
+                    return callback(makeColumn("ore"))(character);
+                }
+            };
+        } else if (character === "t") { // t
+            return function (character) {
+                if (character === "h") { // th
+                    return callback(makeColumn("thule"));
+                } else { // t.
+                    return callback(makeColumn("tinco"))(character);
+                }
+            };
+        } else if (character === "d") { // d
+            return function (character) {
+                if (character === "h") { // dh
+                    return callback(makeColumn("anto"));
+                } else { // d.
+                    return callback(makeColumn("ando"))(character);
+                }
+            };
+        } else if (character === "m") { // m
+            return function (character) {
+                if (
+                    character === "p" || character === "b" ||
+                    character === "f" || character === "v"
+                ) {
+                    return parseTengwa(function (column) {
+                        return callback(column.addTildeAbove());
+                    }, options)(character);
+                } else if (character === "m") { // mm
+                    return callback(makeColumn("malta"));
+                } else { // m.
+                    return callback(makeColumn("vala"))(character);
+                }
+            };
+        } else if (character === "p") { // p
+            return callback(makeColumn("parma"));
+        } else if (character === "b") { // b
+            return callback(makeColumn("umbar"));
+        } else if (character === "f") { // f
+            return function (character) {
+                if (Parser.isFinal(character)) { // f final
+                    return callback(makeColumn("ampa"))(character);
+                } else {
+                    return callback(makeColumn("formen"))(character);
+                }
+            };
+        } else if (character === "v") { // v
+            return callback(makeColumn("ampa"));
+        } else if (character === "ñ") { // ñ
+            return function (character) {
+                if (character === "c" || character === "g") {
+                    return parseTengwa(function (column) {
+                        if (column.tengwa === "halla") {
+                            column.addError("Lenited G (halla) should not be nasalized with prefix N");
+                        }
+                        return callback(column.addTildeAbove());
+                    }, options)(character);
+                } else { // ñ.
+                    return callback(makeColumn("noldo"))(character);
+                }
+            };
+        } else if (character === "c") { // c
+            return function (character) {
+                if (character === "h") { // ch
+                    return function (character) {
+                        if (character === "w") { // chw
+                            return callback(makeColumn("hwesta"));
+                        } else { // ch.
+                            return callback(makeColumn("harma"))(character);
+                        }
+                    };
+                } else if (character === "w") { // cw
+                    return callback(makeColumn("quesse"));
+                } else { // c.
+                    return callback(makeColumn("calma"))(character);
+                }
+            };
+        } else if (character === "g") {
+            return function (character) {
+                if (character === "h") { // gh
+                    return function (character) {
+                        if (character === "w") { // ghw
+                            return callback(makeColumn("unque"));
+                        } else { // gh.
+                            return callback(makeColumn("anca"))(character);
+                        }
+                    };
+                } else if (character === "w") { // gw
+                    return callback(makeColumn("ungwe"));
+                } else if (character === "'") { // g'
+                    return callback(makeColumn("halla")); // gasdil
+                } else { // g.
+                    return callback(makeColumn("anga"))(character);
+                }
+            };
+        } else if (character === "r") { // r
+            return function (character) {
+                if (character === "h") { // rh
+                    return callback(makeColumn("arda"));
+                } else {
+                    return callback(makeColumn("romen"))(character);
+                }
+            };
+        } else if (character === "l") { // l
+            return function (character) {
+                if (character === "h") { // lh
+                    return callback(makeColumn("alda"));
+                } else {
+                    return callback(makeColumn("lambe"))(character);
+                }
+            };
+        } else if (character === "s") { // s
+            return callback(makeColumn("silme"));
+        } else if (character === "a") { // a
+            return function (character) {
+                if (character === "i") { // ai
+                    return callback(makeColumn("round-carrier").addAbove("í"));
+                } else if (character === "u") { // au
+                    return callback(makeColumn("round-carrier").addAbove("w"));
+                } else if (character === "'") { // a'
+                    return callback(makeColumn("round-carrier").addAbove("i"));
+                } else if (character === "a") { // aa
+                    return callback(makeColumn("round-carrier").addAbove("e"));
+                } else { // a.
+                    return callback(makeColumn("round-carrier"))(character);
+                }
+            };
+        } else if (character === "e") { // e
+            return function (character) {
+                if (character === "i") { // ei
+                    return callback(makeColumn("yanta").addAbove("í"));
+                } else if (character === "e") {
+                    return callback(makeColumn("yanta").addAbove("e"));
+                } else { // e.
+                    return callback(makeColumn("yanta"))(character);
+                }
+            };
+        } else if (character === "i") { // i
+            return function (character) {
+                if (character === "i") { // ii -> í
+                    return parseColumn(callback, options)("í");
+                } else {
+                    return Parser.countPrimes(function (primes) {
+                        if (primes === 0) {
+                            return callback(makeColumn("short-carrier"));
+                        } else if (primes === 1) {
+                            return callback(makeColumn("short-carrier").addAbove("i"));
+                        } else if (primes === 3) {
+                            return callback(makeColumn("long-carrier"));
+                        } else if (primes === 4) {
+                            return callback(makeColumn("long-carrier").addAbove("i"));
+                        } else {
+                            return callback(makeColumn("long-carrier").addAbove("i").addError("I only has four variants between short or long and dotted or not."));
+                        }
+                    })(character);
+                }
+            };
+        } else if (character === "o") {
+            return function (character) {
+                if (character === "o") { // oo
+                    return callback(makeColumn("anna").addAbove("e"));
+                } else {
+                    return callback(makeColumn("anna"))(character);
+                }
+            };
+        } else if (character === "u") {
+            return function (character) {
+                if (character === "i") {
+                    return callback(makeColumn("ure").addAbove("í"));
+                } else if (character === "u") {
+                    return callback(makeColumn("ure").addAbove("e"));
+                } else {
+                    return callback(makeColumn("ure"))(character);
+                }
+            };
+        } else if (character === "w") { // w
+            return function (character) {
+                if (character === "w") { // ww
+                    return callback(makeColumn("wilya").addAbove("e"));
+                } else { // w.
+                    return callback(makeColumn("wilya"))(character);
+                }
+            };
+        } else if (character === "y") {
+            return function (character) {
+                if (character === "y") { // yy
+                    return callback(makeColumn("silme-nuquerna").addAbove("e"));
+                } else { // y.
+                    return callback(makeColumn("silme-nuquerna"))(character);
+                }
+            };
+        } else if (character === "á") {
+            return callback(makeColumn("round-carrier").addAbove("e"));
+        } else if (character === "é") {
+            return callback(makeColumn("yanta").addAbove("e"));
+        } else if (character === "í") {
+            return Parser.countPrimes(function (primes) {
+                if (primes === 0) {
+                    return callback(makeColumn("short-carrier").addAbove("e"));
+                } else if (primes === 1) {
+                    return callback(makeColumn("long-carrier").addAbove("e"));
+                } else {
+                    return callback(makeColumn("long-carrier").addAbove("e").addError("Í only has one variant."));
+                }
+            });
+        } else if (character === "ó") {
+            return callback(makeColumn("anna").addAbove("e"));
+        } else if (character === "ú") {
+            return callback(makeColumn("ure").addAbove("e"));
+        } else if (character === "h") {
+            return function (character) {
+                //if (character === "m") {
+                //    return callback(makeColumn("ore-nasalized"));
+                if (character === "w") {
+                    return callback(makeColumn("hwesta-sindarinwa"));
+                } else {
+                    return callback(makeColumn("hyarmen"))(character);
+                }
+            };
+        } else if (character === "z") {
+            return callback(makeColumn("silme").addError("Z does not appear in the mode of Beleriand"));
+        } else if (punctuation[character]) {
+            return callback(makeColumn(punctuation[character]));
+        } else if (Parser.isBreak(character)) {
+            return callback()(character);
+        } else {
+            return callback(makeColumn("anna").addError("Unexpected character: " + JSON.stringify(character)));
+        }
+    };
+}
+
+function parseFollowingS(callback, column) {
+    return function (character) {
+        if (character === "s") {
+            if (column.canAddBelow("s")) {
+                return callback(column.addBelow("s"));
+            } else {
+                return Parser.countPrimes(function (primes) {
+                    return function (character) {
+                        if (Parser.isFinal(character)) { // end of word
+                            if (column.canAddFollowing("s-final") && primes-- === 0) {
+                                column.addFollowing("s-final");
+                            } else if (column.canAddFollowing("s-inverse") && primes -- === 0) {
+                                column.addFollowing("s-inverse");
+                            } else if (column.canAddFollowing("s-extended") && primes-- === 0) {
+                                column.addFollowing("s-extended");
+                            } else if (column.canAddFollowing("s-flourish")) {
+                                column.addFollowing("s-flourish");
+                                if (primes > 0) {
+                                    column.addError(
+                                        "Following S only has 3 alternate " +
+                                        "flourishes."
+                                    );
+                                }
+                            } else {
+                                return callback(column)("s")(character);
+                            }
+                            return callback(column)(character);
+                        } else {
+                            return callback(column)("s")(character);
+                        }
+                    };
+                });
+            }
+        } else {
+            return callback(column)(character);
+        }
+    };
+}
+
diff --git a/beleriand.txt b/beleriand.txt
new file mode 100644 (file)
index 0000000..c5e058e
--- /dev/null
@@ -0,0 +1,96 @@
+
+n -> ore
+nn -> numen
+nt -> tinco tilde-above
+nth -> thule tilde-above
+nc -> calma tilde-above
+nch -> harma tilde-above
+nchw -> unque tilde-above
+ncw -> quesse tilde-above
+ng -> 
+ngh ->
+nghw ->
+ngw -> 
+nd -> ando tilde-above
+ndh -> anto tilde-above
+ng -> anga tilde-above
+ng (final) -> noldo
+
+t -> tinco
+th -> thule
+d -> ando
+dh -> anto
+
+m -> vala
+mm -> malta
+mp -> parma tilde-above
+mb -> umbar tilde-above
+mf -> formen tilde-above
+mf (final) -> ampa tilde-above
+mv -> ampa tilde-above
+
+p -> parma
+b -> umbar
+f (final) -> ampa
+f -> formen
+v -> ampa
+
+ñ -> noldo
+ñc -> calma tilde-above
+ñch -> harma tilde-above
+ñcw -> quesse tilde-above
+ñg -> anga tilde-above
+ñgh -> anca tilde-above
+ñghw -> unque tilde-above
+ñgw -> ungwe tilde-above
+ñw -> nwalme tilde-above
+
+c -> calma
+cw -> quesse
+ch -> harma
+chw -> hwesta
+g -> anga
+gw -> ungwe
+gh -> anca
+ghw -> unque
+g' -> halla (indicating lenition)
+
+r -> romen
+rh -> arda
+l -> lambe
+lh -> alda
+
+s -> silme
+
+a -> round-carrier
+a' -> round-carrier i-above
+ai -> round-carrier y-above
+au -> round-carrier w
+e -> yanta
+ei -> yanta y-above
+i -> short-carrier
+i' -> short-carrier i-above
+i'' -> long-carrier
+ii -> short-carrier e-above
+ii' -> long-carrier e-above
+o -> anna
+u -> ure
+ui -> ure y-above
+w -> wilya
+y -> silme-nuquerna
+yy -> silme-nuquerna e-tehta
+
+á -> round-carrier e-tehta
+é -> yanta e-tehta
+í -> short or long carrier e-tehta
+ó -> anna e-tehta
+ú -> ure e-tehta
+
+h -> hyarmen
+hm -> XXX nasalized ore
+hw -> hwesta-sindarinwa
+hl -> use lh
+hr -> use rh
+
+z -> do not use
+
index b7cc5c2..50a1d32 100644 (file)
@@ -1,11 +1,15 @@
 
 var TengwarAnnatar = require("./tengwar-annatar");
 var Notation = require("./notation");
-var makeParser = require("./parser");
+var Parser = require("./parser");
+var makeDocumentParser = require("./document-parser");
 var normalize = require("./normalize");
 var punctuation = require("./punctuation");
 
+exports.name = "Classical Mode";
+
 var defaults = {};
+exports.makeOptions = makeOptions;
 function makeOptions(options) {
     options = options || defaults;
     return {
@@ -29,6 +33,9 @@ function makeOptions(options) {
         // in the third age, through the namarie poem, "r" is only "ore" before
         // consontants and at the end of words.
         // TODO figure out "h"
+        reverseCurls: options.reverseCurls,
+        // false: by default, o is forward, u is backward
+        // true: o is backward, u is forward
         iuRising: options.isRising,
         // iuRising thirdAge: anna:y,u
         // otherwise: ure:i
@@ -63,61 +70,19 @@ function encode(text, options) {
     return Notation.encode(parse(text, options), options);
 }
 
-// TODO convert to parse tree
-exports.parse = parse;
-function parse(text, options) {
-    options = makeOptions(options);
-    var font = options.font;
-    var makeColumn = font.makeColumn;
-    return text.split(/\n\n\n+/).map(function (section) {
-        return section.split(/\n\n/).map(function (paragraph) {
-            return paragraph.split(/\n/).map(function (line) {
-                var words = [];
-                var word = [];
-                line.toLowerCase().replace(
-                    /([\wáéíóúëâêîôûñ']+)|(.)/g,
-                    function ($, contiguous, other) {
-                        if (contiguous) {
-                            try {
-                                word.push.apply(word, parseWord(normalize(contiguous), options));
-                            } catch (exception) {
-                                word.push(makeColumn().addError("Cannot transcribe " + JSON.stringify(contiguous) + " because " + exception.message));
-                            }
-                        } else if (punctuation[other]) {
-                            word.push(makeColumn(punctuation[other]));
-                        } else if (other === " ") {
-                            words.push(word);
-                            word = [];
-                        } else {
-                            word.push(makeColumn().addError("Cannot transcribe " + JSON.stringify(other)));
-                        }
-                    }
-                );
-                if (word.length) {
-                    words.push(word);
-                }
-                return words;
-            });
-        });
-    });
-}
-
-// original mode: (classical)
-// interim: (classical + wilya? + aha? + longHalla?)
-// third age: (iuRising?) default
+var parse = exports.parse = makeDocumentParser(parseNormalWord, makeOptions);
 
-var parseWord = makeParser(function (callback, options) {
-    return parseWordTail(callback, options, []);
-});
-
-// state machine
+function parseNormalWord(callback, options) {
+    return normalize(parseWord(callback, options));
+}
 
-function parseWordTail(callback, options, columns, previous) {
+function parseWord(callback, options, columns, previous) {
+    columns = columns || [];
     return parseColumn(function (moreColumns) {
         if (!moreColumns.length) {
             return callback(columns);
         } else {
-            return parseWordTail(
+            return parseWord(
                 callback,
                 options,
                 columns.concat(moreColumns),
@@ -128,10 +93,28 @@ function parseWordTail(callback, options, columns, previous) {
 }
 
 function parseColumn(callback, options, previous) {
+    var font = options.font;
+    var makeColumn = font.makeColumn;
     return parseTengwa(function (columns) {
         var previous = columns.pop();
         return parseTehta(function (next) {
-            return callback(columns.concat(next).filter(Boolean));
+            var next = columns.concat(next).filter(Boolean)
+            if (next.length) {
+                return callback(next);
+            } else {
+                return function (character) {
+                    if (Parser.isBreak(character)) {
+                        return callback([])(character);
+                    } else if (punctuation[character]) {
+                        return callback([makeColumn(punctuation[character])]);
+                    } else {
+                        return callback([makeColumn("anna").addError(
+                            "Cannot transcribe " + JSON.stringify(character) +
+                            " in Classical Mode"
+                        )]);
+                    }
+                };
+            }
         }, options, previous);
     }, options, previous);
 }
@@ -221,7 +204,7 @@ function parseTengwa(callback, options, previous) {
                 } else if (character === "s") {
                     return function (character) {
                         // TODO s-inverse, s-extended, s-flourish
-                        if (character === "") { // ts final
+                        if (Parser.isFinal(character)) { // ts final
                             return callback([makeColumn("tinco").addFollowing("s")])(character);
                         } else { // ts medial
                             return callback([
@@ -248,7 +231,7 @@ function parseTengwa(callback, options, previous) {
                     return callback([makeColumn("parma").addBelow("y")]);
                 } else if (character === "s") { // ps
                     return function (character) {
-                        if (character === "") { // ps final
+                        if (Parser.isFinal(character)) { // ps final
                             return callback([makeColumn("parma").addFollowing("s")])(character);
                         } else { // ps medial
                             return callback([
@@ -313,7 +296,7 @@ function parseTengwa(callback, options, previous) {
                     }
                 } else {
                     // pre-consonant and word-final
-                    if (character === "" || vowels.indexOf(character) === -1) { // ore
+                    if (Parser.isFinal(character) || vowels.indexOf(character) === -1) { // ore
                         return callback([makeColumn("ore")])(character);
                     } else { // romen
                         return callback([makeColumn("romen")])(character);
@@ -417,8 +400,7 @@ function parseTehta(callback, options, previous) {
                 } else if (character === "u") {
                     return callback([previous, makeColumn("ure", "a")]);
                 } else if (previous && previous.canAddAbove("a")) {
-                    previous.addAbove("a");
-                    return callback([previous])(character);
+                    return callback([previous.addAbove("a")])(character);
                 } else {
                     return callback([previous, makeColumn("short-carrier", "a")])(character);
                 }
@@ -437,7 +419,7 @@ function parseTehta(callback, options, previous) {
             return function (character) {
                 if (character === "u") {
                     if (options.iuRising) {
-                        return callback([previous, makeColumn("anna", "u").addBelow("y")]);
+                        return callback([previous, makeColumn("anna").addAbove(reverseCurls("u", options)).addBelow("y")]);
                     } else {
                         return callback([previous, makeColumn("ure", "i")]);
                     }
@@ -450,11 +432,11 @@ function parseTehta(callback, options, previous) {
         } else if (character === "o") {
             return function (character) {
                 if (character === "i") {
-                    return callback([previous, makeColumn("yanta", "o")]);
+                    return callback([previous, makeColumn("yanta").addAbove(reverseCurls("o", options))]);
                 } else if (previous && previous.canAddAbove("o")) {
-                    return callback([previous.addAbove("o")])(character);
+                    return callback([previous.addAbove(reverseCurls("o", options))])(character);
                 } else {
-                    return callback([previous, makeColumn("short-carrier", "o")])(character);
+                    return callback([previous, makeColumn("short-carrier").addAbove(reverseCurls("o", options))])(character);
                 }
             };
         } else if (character === "u") {
@@ -462,9 +444,9 @@ function parseTehta(callback, options, previous) {
                 if (character === "i") {
                     return callback([previous, makeColumn("yanta", "u")]);
                 } else if (previous && previous.canAddAbove("u")) {
-                    return callback([previous.addAbove("u")])(character);
+                    return callback([previous.addAbove(reverseCurls("u", options))])(character);
                 } else {
-                    return callback([previous, makeColumn("short-carrier", "u")])(character);
+                    return callback([previous, makeColumn("short-carrier").addAbove(reverseCurls("u", options))])(character);
                 }
             };
         } else if (character === "y") {
@@ -484,15 +466,15 @@ function parseTehta(callback, options, previous) {
             return callback([previous, makeColumn("long-carrier", "i")]);
         } else if (character === "ó") {
             if (previous && previous.canAddAbove("ó")) {
-                return callback([previous.addAbove("ó")]);
+                return callback([previous.addAbove(reverseCurls("ó", options))]);
             } else {
-               return callback([previous, makeColumn("long-carrier", "o")]);
+               return callback([previous, makeColumn("long-carrier").addAbove(reverseCurls("o", options))]);
             }
         } else if (character === "ú") {
             if (previous && previous.canAddAbove("ú")) {
-                return callback([previous.addAbove("ú")]);
+                return callback([previous.addAbove(reverseCurls("ú", options))]);
             } else {
-                return callback([previous, makeColumn("long-carrier", "u")]);
+                return callback([previous, makeColumn("long-carrier").addAbove(reverseCurls("u", options))]);
             }
         } else {
             return callback([previous])(character);
@@ -500,6 +482,14 @@ function parseTehta(callback, options, previous) {
     };
 }
 
+var curlReversals = {"o": "u", "u": "o", "ó": "ú", "ú": "ó"};
+function reverseCurls(tehta, options) {
+    if (options.reverseCurls) {
+        tehta = curlReversals[tehta] || tehta;
+    }
+    return tehta;
+}
+
 // Notes regarding "h":
 //
 // http://at.mansbjorkman.net/teng_quenya.htm#note_harma
diff --git a/document-parser.js b/document-parser.js
new file mode 100644 (file)
index 0000000..044a34f
--- /dev/null
@@ -0,0 +1,37 @@
+
+var Parser = require("./parser");
+
+// produces a document parser from a word parser in an arbitrary mode
+module.exports = makeDocumentParser;
+function makeDocumentParser(parseWord, makeOptions) {
+    var parseLine = Parser.makeDelimitedParser(parseWord, parseSomeSpaces);
+    var parseParagraph = Parser.makeDelimitedParser(parseLine, parseNewlineSpace);
+    var parseSection = Parser.makeDelimitedParser(parseParagraph, parseNewlineSpace);
+    var parseDocument = Parser.makeDelimitedParser(parseSection, parseNewlineSpaces);
+    return Parser.makeParser(function (callback, options) {
+        options = makeOptions(options);
+        var state = parseDocument(callback, options);
+        return state;
+    });
+}
+
+var parseAnySpaces = Parser.makeParseAny(" ");
+var parseSomeSpaces = Parser.makeParseSome(" ");
+var parseNewline = Parser.makeExpect("\n");
+var parseNewlines = Parser.makeParseSome("\n");
+
+function parseNewlineSpace(callback) {
+    return parseAnySpaces(function () {
+        return parseNewline(function () {
+            return parseAnySpaces(callback);
+        });
+    });
+}
+
+function parseNewlineSpaces(callback) {
+    return parseAnySpaces(function () {
+        return parseNewlines(function () {
+            return parseAnySpaces(callback);
+        });
+    });
+}
index 5a4344d..5b5300a 100644 (file)
             <div id="divider"></div>
             <div id="input-box">
                 <div id="form-box">
+
                     <input type="radio" name="mode" id="input-general-use" value="general-use" checked>
                     <label for="input-general-use">General Use</label><br>
                     <input type="radio" name="mode" id="input-classical" value="classical">
                     <label for="input-classical">Classical</label><br>
+                    <input type="radio" name="mode" id="input-beleriand" value="beleriand">
+                    <label for="input-beleriand">Beleriand</label><br>
+
                     <input type="radio" name="font" id="input-tengwar-annatar" value="annatar" checked>
                     <label for="input-tengwar-annatar">Tengwar Annatar</label><br>
                     <input type="radio" name="font" id="input-tengwar-parmaite" value="parmaite">
                     <label for="input-tengwar-parmaite">Tengwar Parmaitë</label><br>
+
                     <!--
                     <input type="checkbox" name="transliteration" id="input-transliteration">
                     <label for="input-transliteration">Show transliteration</label>
                     -->
+
                 </div>
                 <div id="content-box" style="">
                     <textarea id="input" spellcheck="false"></textarea>
index 3dcd3be..2138aa1 100644 (file)
@@ -1,14 +1,13 @@
 
 var QS = require("qs");
-var GeneralUse = require("../general-use");
-var Classical = require("../classical");
-var TengwarAnnatar = require("../tengwar-annatar");
-var TengwarParmaite = require("../tengwar-parmaite");
+var modes = require("../modes");
+var fonts = require("../fonts");
 
 var body = document.body;
 var input = document.querySelector("#input");
 var generalUse = document.querySelector("#input-general-use");
 var classical = document.querySelector("#input-classical");
+var beleriand = document.querySelector("#input-beleriand");
 var annatar = document.querySelector("#input-tengwar-annatar");
 var parmaite = document.querySelector("#input-tengwar-parmaite");
 var output = document.querySelector("#output");
@@ -20,6 +19,7 @@ if (window.location.search) {
     input.value = query.q || "";
     generalUse.checked = query.mode === "general-use";
     classical.checked = query.mode === "classical";
+    beleriand.checked = query.mode === "beleriand";
     parmaite.checked = query.font === "parmaite";
     annatar.checked = query.font === "annatar";
 } else {
@@ -42,8 +42,8 @@ function update() {
         input.value = value;
     }
     history.replaceState(query, value, "?" + QS.stringify(query));
-    var mode = query.mode === "classical" ? Classical : GeneralUse;
-    var font = query.font === "annatar" ? TengwarAnnatar : TengwarParmaite;
+    var mode = modes[query.mode] || TengwarParmaite;
+    var font = fonts[query.font] || TengwarAnnatar;
     output.classList.remove(query.font !== "parmaite" ? "parmaite" : "annatar");
     output.classList.add(query.font === "parmaite" ? "parmaite" : "annatar");
     output.innerHTML = mode.transcribe(value, {font: font, block: true});
@@ -54,7 +54,7 @@ function onupdate(event) {
     var change;
     if (query.q !== input.value) change = true;
     query.q = input.value;
-    var newMode = generalUse.checked ? "general-use" : "classical";
+    var newMode = generalUse.checked ? "general-use" : classical.checked ? "classical" : "beleriand";
     if (query.mode !== newMode) change = true;
     query.mode = newMode;
     var newFont = annatar.checked ? "annatar" : "parmaite";
@@ -67,23 +67,10 @@ input.addEventListener("keyup", onupdate);
 input.addEventListener("keydown", onupdate);
 
 input.select();
-/*
-input.focus();
-var range = document.createRange();
-var node;
-if (!input.lastChild) {
-    input.appendChild(document.createTextNode(""));
-}
-node = input.lastChild;
-range.selectNode(node);
-range.collapse();
-var selection = window.getSelection();
-selection.removeAllRanges();
-selection.addRange(range);
-*/
 
 generalUse.addEventListener("change", onupdate);
 classical.addEventListener("change", onupdate);
+beleriand.addEventListener("change", onupdate);
 parmaite.addEventListener("change", onupdate);
 annatar.addEventListener("change", onupdate);
 
diff --git a/fonts.js b/fonts.js
new file mode 100644 (file)
index 0000000..265e1f5
--- /dev/null
+++ b/fonts.js
@@ -0,0 +1,4 @@
+module.exports = {
+    "parmaite": require("./tengwar-parmaite"),
+    "annatar": require("./tengwar-annatar")
+};
index f69f97e..260e701 100644 (file)
@@ -1,12 +1,16 @@
 
 var TengwarAnnatar = require("./tengwar-annatar");
 var Notation = require("./notation");
-var makeParser = require("./parser");
+var Parser = require("./parser");
+var makeDocumentParser = require("./document-parser");
 var normalize = require("./normalize");
 var punctuation = require("./punctuation");
 var parseNumber = require("./numerals");
 
+exports.name = "General Use Mode";
+
 var defaults = {};
+exports.makeOptions = makeOptions;
 function makeOptions(options) {
     options = options || defaults;
     return {
@@ -21,6 +25,9 @@ function makeOptions(options) {
         // or below.
         // false: by default, place a tilde above doubled nasals.
         // true: place the tilde below doubled nasals.
+        reverseCurls: options.reverseCurls,
+        // false: by default, o is forward, u is backward
+        // true: o is backward, u is forward
         noAchLaut: options.noAchLaut,
         // false: "ch" is interpreted as ach-laut, "cc" as "ch" as in "chew"
         // true: "ch" is interpreted as "ch" as in chew
@@ -43,52 +50,22 @@ function encode(text, options) {
     return Notation.encode(parse(text, options), options);
 }
 
-// TODO convert to parse tree
-exports.parse = parse;
-function parse(text, options) {
-    options = makeOptions(options);
+var parse = exports.parse = makeDocumentParser(parseNormalWord, makeOptions);
+
+function parseNormalWord(callback, options) {
+    return normalize(parseWord(callback, options));
+}
+
+function parseWord(callback, options) {
     var font = options.font;
     var makeColumn = font.makeColumn;
-    return text.split(/\n\n\n+/).map(function (section) {
-        return section.split(/\n\n/).map(function (paragraph) {
-            return paragraph.split(/\n/).map(function (line) {
-                var words = [];
-                var word = [];
-                line.toLowerCase().replace(
-                    /(0[\dab]+)|(\d+)|([\wáéíóúëâêîôûñ']+)|(.)/g,
-                    function ($, dudecimal, decimal, contiguous, other) {
-                        if (contiguous) {
-                            contiguous = normalize(contiguous);
-                            try {
-                                if (book[contiguous]) {
-                                    word.push.apply(word, Notation.decodeWord(book[contiguous], makeColumn));
-                                } else {
-                                    word.push.apply(word, parseWord(contiguous, options));
-                                }
-                            } catch (exception) {
-                                word.push(makeColumn().addError("Cannot transcribe " + JSON.stringify(word) + " because " + exception.message));
-                            }
-                        } else if (dudecimal) {
-                            word.push.apply(word, parseNumber(dudecimal, 12, options));
-                        } else if (decimal) {
-                            word.push.apply(word, parseNumber(decimal, 10, options));
-                        } else if (punctuation[other]) {
-                            word.push(makeColumn(punctuation[other]));
-                        } else if (other === " ") {
-                            words.push(word);
-                            word = [];
-                        } else {
-                            word.push(makeColumn().addError("Cannot transcribe " + JSON.stringify(other)));
-                        }
-                    }
-                );
-                if (word.length) {
-                    words.push(word);
-                }
-                return words;
-            });
-        });
-    });
+    return scanWord(function (word) {
+        if (book[word]) {
+            return callback(Notation.decodeWord(book[word], makeColumn));
+        } else {
+            return callback(parseWordPiecewise(word, options));
+        }
+    }, options);
 }
 
 var book = {
@@ -101,7 +78,18 @@ var book = {
     "is": "short-carrier:i,s"
 };
 
-var parseWord = makeParser(function (callback, options) {
+function scanWord(callback, options, word) {
+    word = word || "";
+    return function (character) {
+        if (Parser.isBreak(character)) {
+            return callback(word)(character);
+        } else {
+            return scanWord(callback, options, word + character);
+        }
+    };
+}
+
+var parseWordPiecewise = Parser.makeParser(function (callback, options) {
     return parseWordTail(callback, options, []);
 });
 
@@ -135,6 +123,9 @@ function parseColumn(callback, options, previous) {
                             .addBelow("s")
                         ]);
                     } else if (canAddAboveTengwa(tehta) && tengwa.canAddAbove(tehta)) {
+                        if (options.reverseCurls) {
+                            tehta = reverseCurls[tehta] || tehta;
+                        }
                         tengwa.addAbove(tehta);
                         return parseTengwaAnnotations(function (tengwa) {
                             return callback([tengwa]);
@@ -160,7 +151,15 @@ function parseColumn(callback, options, previous) {
                     return callback([carrier]);
                 }, makeCarrier(tehta));
             } else {
-                return callback([]);
+                return function (character) {
+                    if (Parser.isBreak(character)) {
+                        return callback([]);
+                    } else if (punctuation[character]) {
+                        return callback([makeColumn(punctuation[character])]);
+                    } else {
+                        return callback([makeColumn("anna").addError("Unexpected character: " + JSON.stringify(character))]);
+                    }
+                };
             }
         }, options, tehta);
     });
@@ -187,6 +186,7 @@ function parseTehta(callback) {
 var tengwaTehtar = "aeiouóú";
 var vowels = "aeiouáéíóú";
 var shorterVowels = {"á": "a", "é": "e", "í": "i", "ó": "o", "ú": "u"};
+var reverseCurls = {"o": "u", "u": "o", "ó": "ú", "ú": "ó"};
 
 function canAddAboveTengwa(tehta) {
     return tengwaTehtar.indexOf(tehta) !== -1;
@@ -235,7 +235,6 @@ function parseTengwa(callback, options, tehta) {
                         } else { // nw.
                             return callback(makeColumn("numen").addAbove("w"))(character);
                         }
-                    return callback(makeColumn("nwalme").addAbove("w"));
                     };
                 } else { // n.
                     return callback(makeColumn("numen"))(character);
@@ -265,7 +264,7 @@ function parseTengwa(callback, options, tehta) {
             };
         } else if (character === "ñ") { // ñ
             return function (character) {
-                // ññ does not really exist
+                // ññ does not exist to the best of my knowledge
                 // ñw is handled naturally by following w
                 if (character === "c") { // ñc
                     return callback(makeColumn("quesse").addTildeAbove());
@@ -281,6 +280,14 @@ function parseTengwa(callback, options, tehta) {
                     return callback(makeColumn("tinco").addTildeBelow());
                 } else if (character === "h") { // th
                     return callback(makeColumn("thule"));
+                } else if (character === "c") { // tc
+                    return function (character) {
+                        if (character === "h") { // tch -> tinco calma
+                            return callback(makeColumn("tinco"))("c")("h")("'");
+                        } else {
+                            return callback(makeColumn("tinco"))("c")(character);
+                        }
+                    };
                 } else { // t.
                     return callback(makeColumn("tinco"))(character);
                 }
@@ -314,6 +321,8 @@ function parseTengwa(callback, options, tehta) {
             return function (character) {
                 if (character === "d") { // dd
                     return callback(makeColumn("ando").addTildeBelow());
+                } else if (character === "j") { // dj
+                    return callback(makeColumn("anga"));
                 } else if (character === "h") { // dh
                     return callback(makeColumn("anto"));
                 } else { // d.
@@ -369,7 +378,7 @@ function parseTengwa(callback, options, tehta) {
                     return callback(makeColumn("esse"))(character);
                 }
             };
-        } else if (character === "h") { //
+        } else if (character === "h") { // h
             return function (character) {
                 if (character === "w") { // hw
                     return callback(makeColumn("hwesta-sindarinwa"));
@@ -467,7 +476,7 @@ function parseFollowingS(callback, column) {
             if (column.canAddBelow("s")) {
                 return callback(column.addBelow("s"));
             } else {
-                return countPrimes(function (primes) {
+                return Parser.countPrimes(function (primes) {
                     return function (character) {
                         if (character === "") { // end of word
                             if (column.canAddFollowing("s-final") && primes-- === 0) {
@@ -500,14 +509,3 @@ function parseFollowingS(callback, column) {
     };
 }
 
-function countPrimes(callback, primes) {
-    primes = primes || 0;
-    return function (character) {
-        if (character === "'") {
-            return countPrimes(callback, primes + 1);
-        } else {
-            return callback(primes)(character);
-        }
-    };
-}
-
diff --git a/modes.js b/modes.js
new file mode 100644 (file)
index 0000000..fafd7b0
--- /dev/null
+++ b/modes.js
@@ -0,0 +1,5 @@
+module.exports = {
+    "general-use": require("./general-use"),
+    "classical": require("./classical"),
+    "beleriand": require("./beleriand")
+};
index 7263937..cc3a731 100644 (file)
@@ -1,17 +1,29 @@
 
-module.exports = function normalize(text) {
-    // normalize
-    return text.replace(normalFormsRe, function ($, key) {
-        return normalForms[key];
-    });
+var Parser = require("./parser");
+var makeTrie = require("./trie");
+var makeParserFromTrie = require("./trie-parser");
+var array_ = Array.prototype;
+
+module.exports = normalize;
+function normalize(callback) {
+    return toLowerCase(simplify(callback));
 };
 
-var normalForms = {
+function toLowerCase(callback) {
+    return function passthrough(character) {
+        callback = callback(character.toLowerCase());
+        return passthrough;
+    };
+}
+
+var table = {
     "k": "c",
     "x": "cs",
-    "qu": "cw",
     "q": "cw",
+    "qu": "cw",
+    "p": "p",
     "ph": "f",
+    "b": "b",
     "bh": "v",
     "ë": "e",
     "â": "á",
@@ -21,8 +33,24 @@ var normalForms = {
     "û": "ú"
 };
 
-var normalFormsRe = new RegExp("(" +
-    Object.keys(normalForms).join("|") +
-")", "ig");
+var trie = makeTrie(table);
 
+var simplify = makeParserFromTrie(
+    trie,
+    function makeProducer(string) {
+        return function (callback) {
+            return Array.prototype.reduce.call(string, function (callback, character) {
+                return callback(character);
+            }, callback);
+        };
+    },
+    function callback(callback) {
+        return simplify(callback);
+    },
+    function fallback(callback) {
+        return function (character) {
+            return simplify(callback(character));
+        };
+    }
+);
 
index 40f4106..5cfe7d1 100644 (file)
--- a/parser.js
+++ b/parser.js
@@ -1,10 +1,13 @@
 
-module.exports = function makeParser(production, errorHandler) {
-    var errorHandler = errorHandler || function (error) {
-        throw new Error(error);
+var punctuation = require("./punctuation");
+
+// builds a string parser from a streaming character parser
+exports.makeParser = makeParser;
+function makeParser(production, errorHandler) {
+    var errorHandler = errorHandler || function (error, text) {
+        throw new Error(error + " while parsing " + JSON.stringify(text));
     };
     return function (text /*, ...args*/) {
-
         // the parser is a monadic state machine.
         // each state is represented by a function that accepts
         // a character.  parse functions accept a callback (for forwarding the
@@ -12,7 +15,9 @@ module.exports = function makeParser(production, errorHandler) {
         var result;
         var state = production.apply(null, [function (_result) {
             result = _result;
-            return expectEof(errorHandler);
+            return expectEof(function (error) {
+                return errorHandler(error, text);
+            });
         }].concat(Array.prototype.slice.call(arguments, 1)));
         // drive the state machine
         Array.prototype.forEach.call(text, function (letter, i) {
@@ -23,7 +28,6 @@ module.exports = function makeParser(production, errorHandler) {
             state = state(""); // EOF
         }
         return result;
-
     };
 }
 
@@ -38,3 +42,89 @@ function expectEof(errback) {
     };
 }
 
+exports.makeExpect = makeExpect;
+function makeExpect(expected) {
+    return function (callback) {
+        return function (character) {
+            if (character === expected) {
+                return callback(character);
+            } else {
+                return callback()(character);
+            }
+        };
+    };
+}
+
+exports.makeParseSome = makeParseSome;
+function makeParseSome(expected) {
+    var parseSome = function (callback) {
+        return function (character) {
+            if (character === expected) {
+                return parseRemaining(callback);
+            } else {
+                return callback()(character);
+            }
+        };
+    };
+    var parseRemaining = makeParseAny(expected);
+    return parseSome;
+}
+
+exports.makeParseAny = makeParseAny;
+function makeParseAny(expected) {
+    return function parseRemaining(callback) {
+        return function (character) {
+            if (character === expected) {
+                return parseRemaining(callback);
+            } else {
+                return callback(expected)(character);
+            }
+        };
+    };
+}
+
+exports.makeDelimitedParser = makeDelimitedParser;
+function makeDelimitedParser(parsePrevious, parseDelimiter) {
+    return function parseSelf(callback, options, terms) {
+        terms = terms || [];
+        return parsePrevious(function (term) {
+            if (!term.length) {
+                return callback(terms);
+            } else {
+                terms = terms.concat([term]);
+                return parseDelimiter(function (delimiter) {
+                    if (delimiter) {
+                        return parseSelf(callback, options, terms);
+                    } else {
+                        return callback(terms);
+                    }
+                });
+            }
+        }, options);
+    }
+}
+
+// used by parsers to determine whether the cursor is on a word break
+exports.isBreak = isBreak;
+function isBreak(character) {
+    return character === " " || character === "\n" || character === "";
+}
+
+exports.isFinal = isFinal;
+function isFinal(character) {
+    return isBreak(character) || punctuation[character];
+}
+
+// used by multiple modes
+exports.countPrimes = countPrimes;
+function countPrimes(callback, primes) {
+    primes = primes || 0;
+    return function (character) {
+        if (character === "'") {
+            return countPrimes(callback, primes + 1);
+        } else {
+            return callback(primes)(character);
+        }
+    };
+}
+
index 39d9085..8def2c6 100644 (file)
@@ -15,7 +15,7 @@
         <div class="tengwar annatar"
             data-tengwar=">Ash nazg durbatulûk, ash nazg gimbatul,<
             Ash nazg thrakatulûk agh burzum-ishi krimpatul."
-            data-mode="general-use"></div>
+            data-mode="general-use reverse-curls"></div>
         <p>Compare to the mode on the actual ring inscription.</p>
         <img src="ring-inscription.jpg">
 
index 5e24477..a6aa8fa 100644 (file)
@@ -1,5 +1,6 @@
 module.exports = {
     "ainaldo": "yanta:a;numen:a;alda:o",
+    "ainaldo i-luin:\nheru aldost.": "yanta:a;numen:a;alda:o short-carrier:i;comma;lambe;yanta:u;numen;comma\nhyarmen:e;romen:u short-carrier:a;alda:o;silme;tinco;full-stop",
     "aldost": "short-carrier:a;alda:o;silme;tinco",
     "hlóce": "halla;lambe:ó;calma:e", // attested
     "hríve": "halla;romen;long-carrier:i;vala:e", // attested
index 65786ee..3d30c0d 100644 (file)
@@ -4,6 +4,7 @@ module.exports = {
     "aldost": "lambe:a;ando;silme-nuquerna:o;tinco",
     "amon sûl": "malta:a;numen:o silme;lambe:ú",
     "aragorn": "romen:a;ungwe:a;romen:o;numen",
+    "Aragorn Arathorn:\nTelcontar, Elessar": "romen:a;ungwe:a;romen:o;numen romen:a;thule:a;romen:o;numen;comma\ntinco;lambe:e;quesse;tinco:o,tilde-above;romen:a;comma lambe:e;silme-nuquerna:e,tilde-below;ore:a",
     "atto": "tinco:a,tilde-below;short-carrier:o",
     "baranduiniant": "umbar;romen:a;ando:a,tilde-above;anna:u;yanta;anto:a,tilde-above",
     "dagor bragolach": "ando;ungwe:a;ore:o umbar;romen;ungwe:a;lambe:o;hwesta:a",
index 3b013a0..ce60396 100644 (file)
@@ -45,6 +45,7 @@ var positions = exports.positions = {
     "formen": {
         "a": 3,
         "w": 3,
+        "í": 3,
         "others": 2
     },
     "harma": {
@@ -54,6 +55,7 @@ var positions = exports.positions = {
         "o": 1,
         "u": 1,
         "w": 0,
+        "í": 0,
         "others": 1
     },
     "hwesta": {
@@ -115,6 +117,7 @@ var positions = exports.positions = {
         "o": 2,
         "w": 1,
         "y": 1,
+        "í": 2,
         "others": 3
     },
     "anna": {
@@ -123,11 +126,8 @@ var positions = exports.positions = {
         "others": 2
     },
     "wilya": {
-        "a": 1,
-        "e": 1,
         "i": 2,
-        "o": 1,
-        "u": 1,
+        "í": 2,
         "others": 1
     },
 
@@ -148,6 +148,7 @@ var positions = exports.positions = {
         "o": 1,
         "u": 1,
         "w": 1,
+        "í": 2,
         "y": null,
         "o-under": null,
         "others": 0
@@ -225,24 +226,28 @@ var positions = exports.positions = {
         "a": 3,
         "w": 3,
         "y": 3,
+        "í": 3,
         "others": 2
     },
     "parma-extended": {
         "a": 3,
         "w": 3,
         "y": 3,
+        "í": 3,
         "others": 2
     },
     "calma-extended": {
         "i": 1,
         "w": 1,
         "y": 0,
+        "í": 0,
         "others": 0
     },
     "quesse-extended": {
         "i": 1,
         "w": 1,
         "y": 0,
+        "í": 0,
         "others": 0
     }
 
@@ -294,7 +299,7 @@ function tehtaForTengwa(tengwa, tehta) {
     );
 }
 
-var longVowels = "áéíóú";
+var longVowels = "áéóú";
 function tehtaKeyForTengwa(tengwa, tehta) {
     if (!tehtar[tehta])
         return null;
diff --git a/tests.js b/tests.js
deleted file mode 100644 (file)
index 614c3c6..0000000
--- a/tests.js
+++ /dev/null
@@ -1,87 +0,0 @@
-
-var GeneralUse = require("./general-use");
-var Classical = require("./classical");
-
-var tests = {
-    classical: {
-
-        "ainaldo": "yanta:a;numen:a;alda:o",
-        "aldost": "short-carrier:a;alda:o;silme;tinco",
-        "hlóce": "halla;lambe:ó;calma:e", // attested
-        "hríve": "halla;romen;long-carrier:i;vala:e", // attested
-        "hyarmen": "hyarmen:y,a;romen;malta:e;numen",
-        "hyarmen(classical)": "hyarmen:a;romen;malta:e;numen",
-        "hyarmen(classical,aha)": "hyarmen:y,a;romen;malta:e;numen",
-        "lambe": "lambe:a;umbar:e",
-        "moria": "malta:o;romen:i;short-carrier:a"
-        "silme": "silme-nuquerna:i;lambe;malta:e",
-        "syi": "silme:y;short-carrier:i", //*
-        "thúlë": "thule:ú;lambe:e",
-        "tyelpe": "tinco:y,e;lambe;parma:e",
-        "vanwa": "vala:a;numen;wilya:a",
-        "yuldar": "anna:y,u;alda:a;ore",
-
-    },
-    generalUse: {
-
-        // sindarin (sorted)
-        "ainur": "anna:a;numen;ore:u",
-        "aldost": "lambe:a;ando;silme-nuquerna:o;tinco",
-        "amon sûl": "malta:a;numen:o silme;lambe:ú",
-        "aragorn": "romen:a;ungwe:a;romen:o;numen",
-        "atto": "tinco:a,tilde-below;short-carrier:o",
-        "baranduiniant": "umbar;romen:a;ando:a,tilde-above;anna:u;yanta;anto:a,tilde-above",
-        "dagor bragolach": "ando;ungwe:a;ore:o umbar;romen;ungwe:a;lambe:o;hwesta:a"
-        "galadhrim": "ungwe;lambe:a;anto:a;romen;malta:i",
-        "galadriel": "ungwe;lambe:a;ando:a;romen;short-carrier:i;lambe:e",
-        "gandalf": "ungwe;ando:a,tilde-above;lambe:a;formen",
-        "glorfindel": "ungwe;lambe;romen:o;formen;ando:i,tilde-above;lambe:e",
-        "gwaith iaur arnor": "ungwe:w;anna:a;thule yanta;vala:a;ore romen:a;numen;ore:o",
-        "gwathló": "ungwe:w;thule:a;lambe;long-carrier:o",
-        "hwesta sindarinwa": "hwesta-sindarinwa;silme-nuquerna:e;tinco;short-carrier:a silme;ando:i,tilde-above;romen:a;numen:i;vala;short-carrier:a",
-        "iant": "yanta;tinco:a,tilde-above",
-        "iaur": "yanta;vala:a;ore",
-        "isildur": "silme-nuquerna:i;lambe:i;ando;ore:u",
-        "lhûn": "alda;numen:ú",
-        "lothlórien": "lambe;thule;lambe;romen:ó;short-carrier:i;numen:e",
-        "mae govannen": "malta;yanta:a ungwe;ampa:o;numen:a,tilde-above;numen:e",
-        "mellon": "malta;lambe:e,tilde-below;numen:o",
-        "mordor": "malta;romen:o;ando:ore:o",
-        "moria": "malta;romen:o;short-carrier:i;short-carrier:a"
-        "noldor": "nwalme;lambe:o;ando;ore:o",
-        "periannath": "parma;romen:e;short-carrier:i;numen:a,tilde-above;thule:a"
-        "rhûn": "arda;numen:ú",
-        "tyelpe": "tinco:y;lambe:e;parma;short-carrier:e",
-        "varda": "ampa;romen:a;ando;short-carrier:a",
-        "á": "wilya:a",
-        "ñoldor": "nwalme;lambe:o;ando;ore:o",
-
-        // quenya (improper mode) (sorted)
-        "ardalambion": "romen:a;ando;lambe:a;umbar:a,tilde-above;short-carrier:i;numen:o",
-        "helcaraxë": "hyarmen;lambe:e;quesse;romen:a;quesse:a,s;short-carrier:e",
-        "hyarmen": "hyarmen:y;romen:a;malta;numen:e",
-        "istari": "silme-nuqeurna:i;tinco;romen:a;short-carrier:i",
-        "sinome maruvan": "silme;numen:i;malta:o;short-carrier:e malta;romen:a;ampa:u;numen:a",
-        "telperion": "tinco;lambe:e;parma;romen:e;short-carrier:i;numen:o",
-        "yuldar": "short-carrier:í;lambe:u;ando;ore:a",
-
-        // english (appropriate mode) (sorted)
-        "hobbits": "hyarmen;umbar:o,tilde-below;tinco:i,s",
-        "hobbits'": "hyarmen;umbar:o,tilde-below;tinco:i,s-inverse",
-        "hobbits''": "hyarmen;umbar:o,tilde-below;tinco:i,s-extended",
-
-        // old english
-        "írensaga": "long-carrier:i;romen;numen:e;silme;ungwe:a;short-carrier:a",
-
-        // interesting clusters
-        "xx": "quesse/s;quesse:s",
-        "tsts": "tinco;silme;tinco:s",
-        "iqs": "quesse:i;vala;silme",
-        "aty": "tinco:a,y",
-        "is": "short-carrier:i,s",
-        "allys": "lambe:a,tilde-below,y:s",
-        "alyssa": "lambe:a,y;silme:tilde-below;short-carrier:a"
-
-    }
-};
-
diff --git a/trie-parser.js b/trie-parser.js
new file mode 100644 (file)
index 0000000..7eb720e
--- /dev/null
@@ -0,0 +1,38 @@
+
+var Parser = require("./parser");
+
+module.exports = makeParserFromTrie;
+function makeParserFromTrie(trie, makeProducer, callback, fallback) {
+    var children = {};
+    var characters = Object.keys(trie.children);
+    characters.forEach(function (character) {
+        children[character] = makeParserFromTrie(
+            trie.children[character],
+            makeProducer,
+            callback,
+            fallback
+        );
+    });
+    var producer;
+    if (trie.value) {
+        producer = makeProducer(trie.value);
+    }
+    return characters.reduceRight(function (next, expected) {
+        return function (state) {
+            return function (character) {
+                if (character === expected) {
+                    return callback(children[character](state));
+                } else {
+                    return next(state)(character);
+                }
+            };
+        };
+    }, function (state) {
+        if (producer) {
+            return callback(producer(state));
+        } else {
+            return fallback(state);
+        }
+    });
+}
+
diff --git a/trie.js b/trie.js
new file mode 100644 (file)
index 0000000..35c6e0b
--- /dev/null
+++ b/trie.js
@@ -0,0 +1,25 @@
+
+module.exports = makeTrie;
+function makeTrie(table) {
+    var strings = Object.keys(table);
+    var trie = {value: void 0, children: {}};
+    var tables = {};
+    strings.forEach(function (string) {
+        if (string.length === 0) {
+            trie.value = table[string];
+        } else {
+            var character = string[0];
+            if (!tables[character]) {
+                tables[character] = {};
+            }
+            var tail = string.slice(1);
+            tables[character][tail] = table[string];
+        }
+    });
+    var characters = Object.keys(tables);
+    characters.forEach(function (character) {
+        trie.children[character] = makeTrie(tables[character]);
+    });
+    return trie;
+}
+
index 42413a8..fe4f373 100644 (file)
@@ -5,17 +5,24 @@
         --common_js_entry_module vanilla-tengwar.js \
         --js \
             vanilla-tengwar.js \
+            modes.js \
             general-use.js \
             classical.js \
+            beleriand.js \
+            fonts.js \
             tengwar-annatar.js \
             tengwar-parmaite.js \
-            parser.js \
-            normalize.js \
-            punctuation.js \
-            notation.js \
+            dan-smith.js \
             alphabet.js \
+            punctuation.js \
             numerals.js \
-            dan-smith.js
+            notation.js \
+            normalize.js \
+            parser.js \
+            document-parser.js \
+            trie.js \
+            trie-parser.js
+
     echo '})();'
 ) | closure > vanilla-tengwar.min.js
 
index 61dc6f4..b05fa1e 100644 (file)
@@ -1,8 +1,10 @@
 
 var GeneralUse = require("./general-use");
 var Classical = require("./classical");
+var Beleriand = require("./beleriand");
 var TengwarAnnatar = require("./tengwar-annatar");
 var TengwarParmaite = require("./tengwar-parmaite");
+var modes = require("./modes");
 
 var array_ = Array.prototype;
 
@@ -32,13 +34,20 @@ function onload() {
         if (encoding) {
             element.innerText = TengwarAnnatar.transcribe(encoding, {block: block});
         } else if (tengwar) {
-            mode = mode || 'general-use';
             font = element.classList.contains("parmaite") ? TengwarParmaite : TengwarAnnatar;
-            var Mode = mode === 'general-use' ? GeneralUse : Classical;
-            element.innerHTML = Mode.transcribe(tengwar, {
-                block: block,
-                font: font
+            var flags = mode.split(/\s+/);
+            var mode = flags.shift();
+            var Mode = modes[mode] || GeneralUse;
+            var options = Mode.makeOptions();
+            flags.forEach(function (flag) {
+                flag = flag.replace(/\-(\w)/g, function ($, $1) {
+                    return $1.toUpperCase();
+                });
+                options[flag] = true;
             });
+            options.block = block;
+            options.font = font;
+            element.innerHTML = Mode.transcribe(tengwar, options);
         }
     });
 }
index 054be7a..32fcaa2 100644 (file)
@@ -1,47 +1,59 @@
-(function(){function F(a,e){return a.split(";").map(function(a){var a=a.split(":"),f=a.shift(),a=a.length?a.shift().split(","):[],d=e(f);a.forEach(function(a){"tilde-above"===a?d.addTildeAbove():"tilde-below"===a?d.addBarBelow():"y"===a?d.addBelow("y"):"s"===a||"s-inverse"===a||"s-extended"===a||"s-flourish"===a?"s"===a&&("calma"===f||"quesse"===f)?d.addBelow(a):d.addFollowing(a):d.addAbove(a)});return d})}function q(a,e){var c=!s[e]?null:s[e].special?s[e][a]||null:-1!==X.barsAndTildes.indexOf(e)?
-"lambe"===a||"alda"===a&&2<=s[e].length?2:m[a].wide?0:1:null==m[a]||null===m[a][e]?null:null!=m[a][e]?m[a][e]:null!=m[a].others?m[a].others:m[a];return null==c?null:s[e][a]||s[e][c]||""}function z(a){a=a||Y;return{font:a.font||Z,block:a.block,plain:a.plain,doubleNasalsWithTildeBelow:a.doubleNasalsWithTildeBelow,noAchLaut:a.noAchLaut}}function A(a,e){var e=z(e),c=e.font.makeColumn;return a.split(/\n\n\n+/).map(function(a){return a.split(/\n\n/).map(function(a){return a.split(/\n/).map(function(a){var d=
-[],g=[];a.toLowerCase().replace(/(0[\dab]+)|(\d+)|([\w\u00e1\u00e9\u00ed\u00f3\u00fa\u00eb\u00e2\u00ea\u00ee\u00f4\u00fb\u00f1']+)|(.)/g,function(a,b,f,n,h){if(n){n=aa(n);try{H[n]?g.push.apply(g,I.decodeWord(H[n],c)):g.push.apply(g,ba(n,e))}catch(ca){g.push(c().addError("Cannot transcribe "+JSON.stringify(g)+" because "+ca.message))}}else b?g.push.apply(g,J(b,12,e)):f?g.push.apply(g,J(f,10,e)):K[h]?g.push(c(K[h])):" "===h?(d.push(g),g=[]):g.push(c().addError("Cannot transcribe "+JSON.stringify(h)))});
-g.length&&d.push(g);return d})})})}function L(a,e,c){var f=function(b){return b.length?L(a,e,c.concat(b),b[b.length-1]):a(c)},d=function(a){return y[a]?b("long-carrier").addAbove(y[a]):b("short-carrier").addAbove(a)},b=e.font.makeColumn,G=function(a){var b=function(b){return b?a?-1!==B.indexOf(a)&&b.canAddAbove(a)?(b.addAbove(a),u(function(b){return f([b])},b)):u(function(b){return f([d(a),b])},b):u(function(b){return f([b])},b):a?u(function(b){return f([b])},d(a)):f([])},c=e.font.makeColumn;return function(d){return"n"===
-d?function(a){return"n"===a?e.doubleNasalsWithTildeBelow?b(c("numen").addTildeBelow()):b(c("numen").addTildeAbove()):"t"===a?function(a){return"h"===a?b(c("thule").addTildeAbove()):b(c("tinco").addTildeAbove())(a)}:"d"===a?b(c("ando").addTildeAbove()):"c"===a?b(c("quesse").addTildeAbove()):"g"===a?b(c("ungwe").addTildeAbove()):"j"===a?b(c("anca").addTildeAbove()):"w"===a?b(c("nwalme").addAbove("w")):b(c("numen"))(a)}:"m"===d?function(a){return"m"===a?e.doubleNasalsWithTildeBelow?b(c("malta").addTildeBelow()):
-b(c("malta").addTildeAbove()):"p"===a?b(c("parma").addTildeAbove()):"b"===a?b(c("umbar").addTildeAbove()):"f"===a?b(c("formen").addTildeAbove()):"v"===a?b(c("ampa").addTildeAbove()):b(c("malta"))(a)}:"\u00f1"===d?function(a){return"c"===a?b(c("quesse").addTildeAbove()):"g"===a?b(c("ungwe").addTildeAbove()):b(c("nwalme"))(a)}:"t"===d?function(a){return"t"===a?b(c("tinco").addTildeBelow()):"h"===a?b(c("thule")):b(c("tinco"))(a)}:"p"===d?function(a){return"p"===a?b(c("parma").addTildeBelow()):b(c("parma"))(a)}:
-"c"===d?function(a){return"c"===a?b(c("calma")):"h"===a?e.noAchLaut?b(c("calma")):b(c("hwesta")):b(c("quesse"))(a)}:"d"===d?function(a){return"d"===a?b(c("ando").addTildeBelow()):"h"===a?b(c("anto")):b(c("ando"))(a)}:"b"===d?function(a){return"b"===a?b(c("umbar").addTildeBelow()):b(c("umbar"))(a)}:"g"===d?function(a){return"g"===a?b(c("ungwe").addTildeBelow()):"h"===a?b(c("unque")):b(c("ungwe"))(a)}:"f"===d?function(a){return"f"===a?b(c("formen").addTildeBelow()):b(c("formen"))(a)}:"v"===d?b(c("ampa")):
-"j"===d?b(c("anca")):"s"===d?function(a){return"s"===a?b(c("silme").addTildeBelow()):"h"===a?b(c("harma")):b(c("silme"))(a)}:"z"===d?function(a){return"z"===a?b(c("esse").addTildeBelow()):b(c("esse"))(a)}:"h"===d?function(a){return"w"===a?b(c("hwesta-sindarinwa")):b(c("hyarmen"))(a)}:"r"===d?function(a){return"r"===a?b(c("romen").addTildeBelow()):"h"===a?b(c("arda")):""===a?b(c("ore"))(a):b(c("romen"))(a)}:"l"===d?function(a){return"l"===a?b(c("lambe").addTildeBelow()):"h"===a?b(c("alda")):b(c("lambe"))(a)}:
-"i"===d?b(c("anna")):"u"===d?b(c("vala")):"w"===d?function(a){return"h"===a?b(c("hwesta-sindarinwa")):b(c("vala"))(a)}:"e"===d&&(!a||"a"===a)?b(c("yanta")):"y"===d?b(c("wilya").addBelow("y")):"\u00e1"===d?b(c("wilya").addAbove("a")):y[d]&&-1==B.indexOf(d)?b(c("long-carrier").addAbove(y[d])):b()(d)}};return function(a){return-1!==B.indexOf(a)?G(a):G()(a)}}function u(a,e){var c=function(c){var d=function(b){return function(c){return"s"===c?b.canAddBelow("s")?a(b.addBelow("s")):M(function(c){return function(d){if(""===
-d){if(b.canAddFollowing("s-final")&&0===c--)b.addFollowing("s-final");else if(b.canAddFollowing("s-inverse")&&0===c--)b.addFollowing("s-inverse");else if(b.canAddFollowing("s-extended")&&0===c--)b.addFollowing("s-extended");else if(b.canAddFollowing("s-flourish"))b.addFollowing("s-flourish"),0<c&&b.addError("Following S only has 3 alternate flourishes.");else return a(b)("s")(d);return a(b)(d)}return a(b)("s")(d)}}):a(b)(c)}};return function(a){return"y"===a&&c.canAddBelow("y")?d(c.addBelow("y")):
-d(c)(a)}};return e.canAddAbove("w")?function(a){return"w"===a?c(e.addAbove("w")):c(e)(a)}:c(e)}function M(a,e){e=e||0;return function(c){return"'"===c?M(a,e+1):a(e)(c)}}function C(a){a=a||da;return{font:a.font||ea,block:a.block,plain:a.plain,vilya:a.vilya,harma:a.harma,classical:a.classical,iuRising:a.isRising,longHalla:a.longHalla}}function D(a,e){var e=C(e),c=e.font.makeColumn;return a.split(/\n\n\n+/).map(function(a){return a.split(/\n\n/).map(function(a){return a.split(/\n/).map(function(a){var d=
-[],g=[];a.toLowerCase().replace(/([\w\u00e1\u00e9\u00ed\u00f3\u00fa\u00eb\u00e2\u00ea\u00ee\u00f4\u00fb\u00f1']+)|(.)/g,function(a,b,f){if(b)try{g.push.apply(g,fa(ga(b),e))}catch(n){g.push(c().addError("Cannot transcribe "+JSON.stringify(b)+" because "+n.message))}else N[f]?g.push(c(N[f])):" "===f?(d.push(g),g=[]):g.push(c().addError("Cannot transcribe "+JSON.stringify(f)))});g.length&&d.push(g);return d})})})}function O(a,e,c,f){var d=function(b){var d=b.pop();return P(function(d){d=b.concat(d).filter(Boolean);
-return d.length?O(a,e,c.concat(d),d[d.length-1]):a(c)},e,d)},b=e.font.makeColumn;return function(a){return"n"===a?function(a){return"n"===a?d([b("numen").addTildeBelow()]):"t"===a?d([b("anto")]):"d"===a?d([b("ando")]):"g"===a?function(a){return"w"===a?d([b("ungwe")]):d([b("anga")])(a)}:"c"===a?function(a){return"w"===a?d([b("unque")]):d([b("anca")])(a)}:d([b("numen")])(a)}:"m"===a?function(a){return"m"===a?d([b("malta").addTildeBelow()]):"p"===a?d([b("ampa")]):"b"===a?d([b("umbar")]):d([b("malta")])(a)}:
-"\u00f1"===a?function(a){return"g"===a?function(a){return"w"===a?d([b("ungwe")]):d([b("anga")])(a)}:"c"===a?function(a){return"w"===a?d([b("unque")]):d([b("anca")])}:d([b("noldo")])(a)}:"t"===a?function(a){return"t"===a?function(a){return"y"===a?d([b("tinco").addBelow("y").addTildeBelow()]):d([b("tinco").addTildeBelow()])(a)}:"y"===a?d([b("tinco").addBelow("y")]):"h"===a?d([b("thule")]):"s"===a?function(a){return""===a?d([b("tinco").addFollowing("s")])(a):d([b("tinco"),b("silme")])(a)}:d([b("tinco")])(a)}:
-"p"===a?function(a){return"p"===a?function(a){return"y"===a?d([b("parma").addBelow("y").addTildeBelow()]):d([b("parma").addTildeBelow()])(a)}:"y"===a?d([b("parma").addBelow("y")]):"s"===a?function(a){return""===a?d([b("parma").addFollowing("s")])(a):d([b("parma"),b("silme")])(a)}:d([b("parma")])(a)}:"c"===a?function(a){return"c"===a?d([b("calma").addTildeBelow()]):"s"===a?d([b("calma").addBelow("s")]):"h"===a?d([b("harma")]):"w"===a?d([b("quesse")]):d([b("calma")])(a)}:"f"===a?d([b("formen")]):"v"===
-a?e.vilya?d([b("wilya")]):d([b("vala")]):"w"===a?e.vilya?d([])("u"):d([b("wilya")]):"r"===a?function(a){return"d"===a?d([b("arda")]):"h"===a?d([b("halla").addError("R should preceed H in the HR diagraph in Classical mode."),b("romen").addError("R should preceed H in the HR diagraph in Classical mode.")]):e.classical?f&&f.above&&""!==a&&-1!==Q.indexOf(a)?d([b("ore")])(a):d([b("romen")])(a):""===a||-1===Q.indexOf(a)?d([b("ore")])(a):d([b("romen")])(a)}:"l"===a?function(a){return"l"===a?function(a){return"y"===
-a?d([b("lambe").addBelow("y").addTildeBelow()]):d([b("lambe").addTildeBelow()])(a)}:"y"===a?d([b("lambe").addBelow("y")]):"h"===a?d([b("halla").addError("L should preceed H in the HL diagraph in Classical mode."),b("lambe").addError("L should preceed H in the HL diagraph in Classical mode.")]):"d"===a?d([b("alda")]):"b"===a?d([b("lambe"),b("umbar")]):d([b("lambe")])(a)}:"s"===a?function(a){return"s"===a?d([b("esse")]):d([b("silme")])(a)}:"h"===a?function(a){return"l"===a?d([b("halla"),b("lambe")]):
-"r"===a?d([b("halla"),b("romen")]):"w"===a?d([b("hwesta")]):"t"===a?d([b("harma")]):"y"===a?e.classical&&e.harma?d([b("hyarmen")]):d([b("hyarmen").addBelow("y")]):e.classical?e.harma?d([b("harma")])(a):f?d([b("hyarmen")])(a):d([b("harma")])(a):d([b("hyarmen")])(a)}:"d"===a?d([b("ando").addError("D cannot appear except after N, L, or R")]):"b"===a?d([b("umbar").addError("B cannot appear except after M or L")]):"g"===a?d([b("anga").addError("G cannot appear except after N or \u00d1")]):"j"===a?d([b().addError("J cannot be transcribed in Classical Mode")]):
-d([])(a)}}function P(a,e,c){var f=e.font.makeColumn;return function(d){if("a"===d)return function(b){return"i"===b?a([c,f("yanta","a")]):"u"===b?a([c,f("ure","a")]):c&&c.canAddAbove("a")?(c.addAbove("a"),a([c])(b)):a([c,f("short-carrier","a")])(b)};if("e"===d)return function(b){return"u"===b?a([c,f("ure","e")]):c&&c.canAddAbove("e")?a([c.addAbove("e")])(b):a([c,f("short-carrier","e")])(b)};if("i"===d)return function(b){return"u"===b?e.iuRising?a([c,f("anna","u").addBelow("y")]):a([c,f("ure","i")]):
-c&&c.canAddAbove("i")?a([c.addAbove("i")])(b):a([c,f("short-carrier","i")])(b)};if("o"===d)return function(b){return"i"===b?a([c,f("yanta","o")]):c&&c.canAddAbove("o")?a([c.addAbove("o")])(b):a([c,f("short-carrier","o")])(b)};if("u"===d)return function(b){return"i"===b?a([c,f("yanta","u")]):c&&c.canAddAbove("u")?a([c.addAbove("u")])(b):a([c,f("short-carrier","u")])(b)};if("y"===d){if(c&&c.canAddBelow("y"))return a([c.addBelow("y")]);d=f("anna").addBelow("y");return P(function(b){return a([c].concat(b))},
-e,d)}return"\u00e1"===d?a([c,f("long-carrier","a")]):"\u00e9"===d?a([c,f("long-carrier","e")]):"\u00ed"===d?a([c,f("long-carrier","i")]):"\u00f3"===d?c&&c.canAddAbove("\u00f3")?a([c.addAbove("\u00f3")]):a([c,f("long-carrier","o")]):"\u00fa"===d?c&&c.canAddAbove("\u00fa")?a([c.addAbove("\u00fa")]):a([c,f("long-carrier","u")]):a([c])(d)}}function r(a,e){var c=!t[e]||-1!==ha.indexOf(e)?null:t[e].special?t[e][a]||null:-1!==ia.barsAndTildes.indexOf(e)?"lambe"===a||"alda"===a&&2<=t[e].length?2:o[a].wide?
-0:1:null==o[a]||null===o[a][e]?null:null!=o[a][e]?o[a][e]:null!=o[a].others?o[a].others:o[a];return null==c?null:t[e][a]||t[e][c]||null}function R(){if(document.body.classList&&document.querySelectorAll&&S.forEach){var a=document.querySelectorAll(".tengwar");S.forEach.call(a,function(a){var c=a.dataset,f,d,b;b="div"===a.tagName.toLowerCase();c?(f=c.tengwar,d=c.mode,c=c.encoding):(f=a.getAttribute("data-tengwar"),d=a.getAttribute("data-mode"),c=a.getAttribute("data-encoding"));c?a.innerText=T.transcribe(c,
-{block:b}):f&&(d=d||"general-use",c=a.classList.contains("parmaite")?ja:T,a.innerHTML=("general-use"===d?ka:la).transcribe(f,{block:b,font:c}))})}}var h={module$exports:function(a,e){e=e||function(a){throw Error(a);};return function(c){var f,d=a.apply(null,[function(a){f=a;var c=e;return function(a){""!==a&&c("Unexpected "+JSON.stringify(a));return function $(){return $}}}].concat(Array.prototype.slice.call(arguments,1)));for(Array.prototype.forEach.call(c,function(a){d=d(a)});!f;)d=d("");return f}}};
-h.module$exports&&(h=h.module$exports);var v={module$exports:function(a){return a.replace(ma,function(a,c){return U[c]})}},U={k:"c",x:"cs",qu:"cw",q:"cw",ph:"f",bh:"v","\u00eb":"e","\u00e2":"\u00e1","\u00ea":"\u00e9","\u00ee":"\u00ed","\u00f4":"\u00f3","\u00fb":"\u00fa"},ma=RegExp("("+Object.keys(U).join("|")+")","ig");v.module$exports&&(v=v.module$exports);var w={module$exports:{"-":"comma",",":"comma",":":"comma",";":"full-stop",".":"full-stop","!":"exclamation-point","?":"question-mark","(":"open-paren",
-")":"close-paren",">":"flourish-left","<":"flourish-right"}};w.module$exports&&(w=w.module$exports);var E={encode:function(a){return a.map(function(a){return a.map(function(a){return a.map(function(a){return a.map(function(a){return a.map(function(a){var c=[];a.above&&c.push(a.above);a.below&&c.push(a.below);a.following&&c.push(a.following);a.tildeAbove&&c.push("tilde-above");a.tildeBelow&&c.push("tilde-below");return c.length?a.tengwa+":"+c.join(","):a.tengwa}).join(";")}).join(" ")}).join("\n")}).join("\n\n")}).join("\n\n\n")},
-decode:function(a,e){return a.split("\n\n\n").map(function(a){return a.split("\n\n").map(function(a){return a.split("\n").map(function(a){return a.split(" ").map(function(a){return F(a,e)})})})})}};E.decodeWord=F;var p={tengwar:[["tinco","parma","calma","quesse"],["ando","umbar","anga","ungwe"],["thule","formen","harma","hwesta"],["anto","ampa","anca","unque"],["numen","malta","noldo","nwalme"],["ore","vala","anna","wilya"],["romen","arda","lambe","alda"],["silme","silme-nuquerna","esse","esse-nuquerna"],
-["hyarmen","hwesta-sindarinwa","yanta","ure"],["halla","short-carrier","long-carrier","round-carrier"],["tinco-extended","parma-extended","calma-extended","quesse-extended"]],tehtarAbove:"aeiou\u00e1\u00e9\u00ed\u00f3\u00faw".split(""),tehtarBelow:["y","s","o-under"],tehtarFollowing:["s-final","s-inverse","s-extended","s-flourish"],barsAndTildes:"tilde-above tilde-below tilde-high-above tilde-far-below bar-above bar-below bar-high-above bar-far-below".split(" ")};p.tehtar=[].concat(p.tehtarAbove,
-p.tehtarBelow,p.tehtarFollowing,p.barsAndTildes);p.aliases={vilya:"wilya",aha:"harma"};var i={},na=Array.prototype;i.module$exports=function(a,e,c){var f=c.font.makeColumn;return na.map.call(parseInt(a,e).toString(e),function(a){return f("#"+a)})};i.module$exports&&(i=i.module$exports);var V={tinco:"1",parma:"q",calma:"a",quesse:"z",ando:"2",umbar:"w",anga:"s",ungwe:"x",thule:"3",formen:"e",harma:"d",hwesta:"c",anto:"4",ampa:"r",anca:"f",unque:"v",numen:"5",malta:"t",noldo:"g",nwalme:"b",ore:"6",
-vala:"y",anna:"h",wilya:"n",romen:"7",arda:"u",lambe:"j",alda:"m",silme:"8","silme-nuquerna":"i",esse:"k","esse-nuquerna":",",hyarmen:"9","hwesta-sindarinwa":"o",yanta:"l",ure:".",halla:"\u00bd","short-carrier":"`","long-carrier":"~","round-carrier":"]","tinco-extended":"!","parma-extended":"Q","calma-extended":"A","quesse-extended":"Z",comma:"=","full-stop":"-","exclamation-point":"\u00c1","question-mark":"\u00c0","open-paren":"&#140;","close-paren":"&#156;","flourish-left":"&#286;","flourish-right":"&#287;",
-"#0":"\uf8ff","#1":"\u00d2","#2":"\u00da","#3":"\u00db","#4":"\u00d9","#5":"\u0131","#6":"\u02c6","#7":"\u02dc","#8":"\u00af","#9":"\u02d8","#10":"\u02d9","#11":"\u02da"},W={a:"#EDC",e:"$RFV",i:"%TGB",o:"^YHN",u:"& U J M &#256; &#257; &#258; &#259;".split(" "),"\u00f3":["&#260;","&#261;","&#262;","&#263;"],"\u00fa":["&#264;","&#265;","&#266;","&#267;"],"\u00ed":["&#212;","&#213;","&#214;","&#215;"],w:"\u00e8\u00e9\u00ea\u00eb",y:"\u00cc\u00cd\u00ce\u00cf\u00b4","o-under":["\u00e4","&#229;","\u00e6",
-"\u00e7","|"],s:{special:!0,calma:"|",quesse:"|","short-carrier":"}"},"s-final":{special:!0,tinco:"+",ando:"+",numen:"+",lambe:"_"},"s-inverse":{special:!0,tinco:"\u00a1"},"s-extended":{special:!0,tinco:"&#199;"},"s-flourish":{special:!0,tinco:"&#163;",lambe:"&#165;"},"tilde-above":"Pp","tilde-below":[":",";","&#176;"],"tilde-high-above":")0","tilde-far-below":"?/","bar-above":"{[","bar-below":['"',"'","&#184;"],"bar-high-above":"\u00ec\u00ee","bar-far-below":"\u00ed\u00ef"},l={},X=p,oa=l.tengwar=
-V,s=l.tehtar=W,m=l.positions={tinco:{o:3,w:3,others:2},parma:{o:3,w:3,others:2},calma:{o:3,w:3,u:3,"o-under":1,others:2},quesse:{o:3,w:3,"o-under":1,others:2},ando:{wide:!0,e:1,o:2,"\u00f3":1,"\u00fa":1,others:0},umbar:{wide:!0,e:1,o:2,"\u00f3":1,"\u00fa":1,others:0},anga:{wide:!0,e:1,"\u00f3":1,"\u00fa":1,others:0},ungwe:{wide:!0,e:1,o:1,"\u00f3":1,"\u00fa":1,others:0},thule:{others:3},formen:3,harma:{e:0,o:3,u:7,"\u00f3":2,"\u00fa":2,w:0,others:1},hwesta:{e:0,o:3,u:7,w:0,others:1},anto:{wide:!0,
-"\u00f3":1,"\u00fa":1,others:0},ampa:{wide:!0,"\u00f3":1,"\u00fa":1,others:0},anca:{wide:!0,u:7,"\u00f3":1,"\u00fa":1,others:0},unque:{wide:!0,u:7,others:0},numen:{wide:!0,"\u00f3":1,"\u00fa":1,others:0},malta:{wide:!0,"\u00f3":1,"\u00fa":1,others:0},noldo:{wide:!0,"\u00f3":1,"\u00fa":1,others:0},nwalme:{wide:!0,"\u00f3":1,"\u00fa":1,others:0},ore:{e:3,o:3,u:3,"\u00f3":3,"\u00fa":3,others:1},vala:{e:3,o:3,u:3,"\u00f3":3,"\u00fa":3,others:1},anna:{e:3,o:3,u:3,"\u00f3":2,"\u00fa":2,others:1},wilya:{e:3,
-o:3,u:3,"\u00f3":3,"\u00fa":3,others:1},romen:{e:3,o:3,u:3,"\u00f3":2,"\u00fa":2,y:null,"o-under":null,others:1},arda:{a:1,e:3,i:1,o:3,u:3,"\u00ed":1,"\u00f3":2,"\u00fa":2,y:null,"o-under":null,others:0},lambe:{wide:!0,e:1,y:4,"\u00f3":1,"\u00fa":1,"o-under":null,others:0},alda:{wide:!0,"o-under":null,others:1},silme:{y:3,"o-under":2,others:null},"silme-nuquerna":{e:3,o:3,u:3,"\u00f3":3,"\u00fa":3,y:null,"o-under":null,others:1},esse:{y:null,others:null},"esse-nuquerna":{e:3,o:3,u:3,"\u00f3":3,"\u00fa":3,
-others:1},hyarmen:3,"hwesta-sindarinwa":{o:2,u:2,"\u00f3":1,"\u00fa":2,others:0},yanta:{e:3,o:3,u:3,"\u00f3":2,"\u00fa":2,others:1},ure:{e:3,o:3,u:3,"\u00f3":3,"\u00fa":3,others:1},halla:{others:null},"short-carrier":3,"long-carrier":{y:null,"o-under":null,others:3},"round-carrier":3,"tinco-extended":3,"parma-extended":3,"calma-extended":{o:3,u:7,"\u00f3":2,"\u00fa":2,others:1},"quesse-extended":{o:0,u:7,others:1}};l.transcribe=function(a,e){var e=e||{},c=e.plain||!1,f=e.block||!1,d=f?"<p>":"",b=
-f?"<br>":"",h=f?"</p>":"";return a.map(function(a){return a.map(function(a){return d+a.map(function(a){return a.map(function(a){return a.map(function(a){var b=a.tengwa||"anna",d=[];a.above&&d.push(a.above);a.below&&d.push(a.below);a.tildeBelow&&d.push("tilde-below");a.tildeAbove&&d.push("tilde-above");a.following&&d.push(a.following);d=oa[b]+d.map(function(a){return q(b,a)}).join("");a.errors&&!c&&(d='<abbr class="error" title="'+a.errors.join("\n").replace(/"/g,"&quot;")+'">'+d+"</abbr>");return d}).join("")}).join(" ")}).join(b+
-"\n")+h}).join("\n\n")}).join("\n\n\n")};l.tehtaForTengwa=q;l.makeColumn=function(a,e,c){return new j(a,e,c)};var j=function(a,e,c){this.above=e;this.tildeAbove=void 0;this.tengwa=a;this.tildeBelow=void 0;this.below=c;this.error=this.following=void 0};j.prototype.canAddAbove=function(a){return!this.above&&!!q(this.tengwa,a)||!this.below&&("silme"===this.tengwa&&q("silme-nuquerna",a)||"esse"===this.tengwa&&q("esse-nuquerna",a))};j.prototype.addAbove=function(a){"silme"===this.tengwa&&(this.tengwa=
-"silme-nuquerna");"esse"===this.tengwa&&(this.tengwa="esse-nuquerna");this.above=a;return this};j.prototype.canAddBelow=function(a){return!this.below&&!!q(this.tengwa,a)};j.prototype.addBelow=function(a){this.below=a;return this};j.prototype.addTildeAbove=function(){this.tildeAbove=!0;return this};j.prototype.addTildeBelow=function(){this.tildeBelow=!0;return this};j.prototype.canAddFollowing=function(a){return!this.following&&!!q(this.tengwa,a)};j.prototype.addFollowing=function(a){this.following=
-a;return this};j.prototype.addError=function(a){this.errors=this.errors||[];this.errors.push(a);return this};var x={},Z=l,I=E,aa=v,K=w,J=i,Y={};x.transcribe=function(a,e){e=z(e);return e.font.transcribe(A(a,e),e)};x.encode=function(a,e){e=z(e);return I.encode(A(a,e),e)};x.parse=A;var H={iant:"yanta;tinco:a,tilde-above",iaur:"yanta;vala:a;ore",baranduiniant:"umbar;romen:a;ando:a,tilde-above;anna:u;yanta;anto:a,tilde-above",ioreth:"yanta;romen:o;thule:e",noldo:"nwalme;lambe:o;ando;short-carrier:o",
-noldor:"nwalme;lambe:o;ando;ore:o",is:"short-carrier:i,s"},ba=h(function(a,e){return L(a,e,[])}),B="aeiou\u00f3\u00fa",y={"\u00e1":"a","\u00e9":"e","\u00ed":"i","\u00f3":"o","\u00fa":"u"};x.parseTengwaAnnotations=u;var i={},ea=l,ga=v,N=w,da={};i.transcribe=function(a,e){e=C(e);return e.font.transcribe(D(a,e),e)};i.encode=function(a,e){e=C(e);return E.encode(D(a,e),e)};i.parse=D;var fa=h(function(a,e){return O(a,e,[])}),Q="aeiouy\u00e1\u00e9\u00ed\u00f3\u00fa",h={},ia=p,pa=h.tengwar=V,t=h.tehtar=W,
-o=h.positions={tinco:2,parma:2,calma:{y:1,"o-under":1,others:2},quesse:{y:1,"o-under":1,others:2},ando:{wide:!0,others:0},umbar:{wide:!0,others:0},anga:{wide:!0,others:0},ungwe:{wide:!0,others:0},thule:{a:3,w:3,others:2},formen:{a:3,w:3,others:2},harma:{a:0,e:0,i:1,o:1,u:1,w:0,others:1},hwesta:{a:0,e:0,i:1,o:1,u:1,w:0,others:1},anto:{wide:!0,others:0},ampa:{wide:!0,others:0},anca:{wide:!0,others:0},unque:{wide:!0,others:0},numen:{wide:!0,others:0},malta:{wide:!0,others:0},noldo:{wide:!0,others:0},
-nwalme:{wide:!0,others:0},ore:{a:1,e:2,i:1,o:2,u:3,others:1},vala:{a:1,e:2,i:2,o:2,w:1,y:1,others:3},anna:{a:1,e:2,i:2,o:2,u:3,w:3,others:1},wilya:{a:1,e:1,i:2,o:1,u:1,others:1},romen:{a:1,e:1,i:2,o:1,u:1,y:null,"o-under":null,others:1},arda:{a:1,e:1,i:2,o:1,u:1,w:1,y:null,"o-under":null,others:0},lambe:{wide:!0,e:1,y:4,w:0,"o-under":null,others:0},alda:{wide:!0,w:0,y:null,"o-under":null,others:1},silme:{y:2,"o-under":2,others:null},"silme-nuquerna":{e:2,y:null,"o-under":null,others:1},esse:{others:null},
-"esse-nuquerna":{e:2,y:null,"o-under":null,others:1},hyarmen:{y:1,others:3},"hwesta-sindarinwa":{w:1,y:1,others:0},yanta:{a:1,others:2},ure:{a:1,others:2},halla:{others:null},"short-carrier":{y:null,others:3},"long-carrier":{y:null,"o-under":null,others:3},"round-carrier":2,"tinco-extended":{a:3,w:3,y:3,others:2},"parma-extended":{a:3,w:3,y:3,others:2},"calma-extended":{i:1,w:1,y:0,others:0},"quesse-extended":{i:1,w:1,y:0,others:0}};h.transcribe=function(a,e){var e=e||{},c=e.plain||!1,f=e.block||
-!1,d=f?"<p>":"",b=f?"<br>":"",h=f?"</p>":"";return a.map(function(a){return a.map(function(a){return d+a.map(function(a){return a.map(function(a){return a.map(function(a){var b=a.tengwa||"anna",d=[];a.above&&d.push(a.above);a.below&&d.push(a.below);a.tildeBelow&&d.push("tilde-below");a.tildeAbove&&d.push("tilde-above");a.following&&d.push(a.following);d=pa[b]+d.map(function(a){return r(b,a)}).join("");a.errors&&!c&&(d='<abbr class="error" title="'+a.errors.join("\n").replace(/"/g,"&quot;")+'">'+d+
-"</abbr>");return d}).join("")}).join(" ")}).join(b+"\n")+h}).join("\n\n")}).join("\n\n\n")};h.tehtaForTengwa=r;var ha="\u00e1\u00e9\u00ed\u00f3\u00fa";h.makeColumn=function(a,e,c){return new k(a,e,c)};var k=function(a,e,c){this.above=e;this.tildeAbove=void 0;this.tengwa=a;this.tildeBelow=void 0;this.below=c;this.error=this.following=void 0};k.prototype.canAddAbove=function(a){return!this.above&&!!r(this.tengwa,a)||!this.below&&("silme"===this.tengwa&&r("silme-nuquerna",a)||"esse"===this.tengwa&&
-r("esse-nuquerna",a))};k.prototype.addAbove=function(a){"silme"===this.tengwa&&(this.tengwa="silme-nuquerna");"esse"===this.tengwa&&(this.tengwa="esse-nuquerna");this.above=a;return this};k.prototype.canAddBelow=function(a){return!this.below&&!!r(this.tengwa,a)};k.prototype.addBelow=function(a){this.below=a;return this};k.prototype.addTildeAbove=function(){this.tildeAbove=!0;return this};k.prototype.addTildeBelow=function(){this.tildeBelow=!0;return this};k.prototype.canAddFollowing=function(a){return!this.following&&
-!!r(this.tengwa,a)};k.prototype.addFollowing=function(a){this.following=a;return this};k.prototype.addError=function(a){this.errors=this.errors||[];this.errors.push(a);return this};var ka=x,la=i,T=l,ja=h,S=Array.prototype;"complete"===document.readyState?R():document.addEventListener("DOMContentLoaded",R,!0)})();
+(function(){function w(a,c){var b=!y[c]?null:y[c].special?y[c][a]||null:-1!==la.barsAndTildes.indexOf(c)?"lambe"===a||"alda"===a&&2<=y[c].length?2:t[a].wide?0:1:null==t[a]||null===t[a][c]?null:null!=t[a][c]?t[a][c]:null!=t[a].others?t[a].others:t[a];return null==b?null:y[c][a]||y[c][b]||""}function x(a,c){var b=!z[c]||-1!==ma.indexOf(c)?null:z[c].special?z[c][a]||null:-1!==na.barsAndTildes.indexOf(c)?"lambe"===a||"alda"===a&&2<=z[c].length?2:u[a].wide?0:1:null==u[a]||null===u[a][c]?null:null!=u[a][c]?
+u[a][c]:null!=u[a].others?u[a].others:u[a];return null==b?null:z[c][a]||z[c][b]||null}function O(a,c){return a.split(";").map(function(a){var a=a.split(":"),d=a.shift(),a=a.length?a.shift().split(","):[],e=c(d);a.forEach(function(a){"tilde-above"===a?e.addTildeAbove():"tilde-below"===a?e.addBarBelow():"y"===a?e.addBelow("y"):"s"===a||"s-inverse"===a||"s-extended"===a||"s-flourish"===a?"s"===a&&("calma"===d||"quesse"===d)?e.addBelow(a):e.addFollowing(a):e.addAbove(a)});return e})}function P(a){return function b(d){return function(e){return e===
+a?b(d):d(a)(e)}}}function Q(a){return" "===a||"\n"===a||""===a}function R(a,c){c=c||0;return function(b){return"'"===b?R(a,c+1):a(c)(b)}}function S(a){return E(function(){return oa(function(){return E(a)})})}function pa(a){return E(function(){return qa(function(){return E(a)})})}function T(a){var c={value:void 0,children:{}},b={};Object.keys(a).forEach(function(d){if(0===d.length)c.value=a[d];else{var e=d[0];b[e]||(b[e]={});var i=d.slice(1);b[e][i]=a[d]}});Object.keys(b).forEach(function(a){c.children[a]=
+T(b[a])});return c}function U(a,c,b,d){var e={},i=Object.keys(a.children);i.forEach(function(g){e[g]=U(a.children[g],c,b,d)});var f;a.value&&(f=c(a.value));return i.reduceRight(function(a,c){return function(d){return function(h){return h===c?b(e[h](d)):a(d)(h)}}},function(a){return f?b(f(a)):d(a)})}function F(a){a=a||sa;return{font:a.font||ta,block:a.block,plain:a.plain,doubleNasalsWithTildeBelow:a.doubleNasalsWithTildeBelow,reverseCurls:a.reverseCurls,noAchLaut:a.noAchLaut,isHook:a.isHook}}function V(a,
+c,b){b=b||"";return function(d){return G.isBreak(d)?a(b)(d):V(a,c,b+d)}}function W(a,c,b){var d=function(e){return e.length?W(a,c,b.concat(e),e[e.length-1]):a(b)},e=function(a){return H[a]?i("long-carrier").addAbove(H[a]):i("short-carrier").addAbove(a)},i=c.font.makeColumn,f=function(a){var b=function(b){return b?a?"silme"===b.tengwa&&"i"===a&&c.isHook?d([i("short-carrier").addAbove("i").addBelow("s")]):-1!==M.indexOf(a)&&b.canAddAbove(a)?(c.reverseCurls&&(a=ua[a]||a),b.addAbove(a),C(function(a){return d([a])},
+b)):C(function(b){return d([e(a),b])},b):C(function(a){return d([a])},b):a?C(function(a){return d([a])},e(a)):function(a){return G.isBreak(a)?d([]):X[a]?d([i(X[a])]):d([i("anna").addError("Unexpected character: "+JSON.stringify(a))])}},f=a,h=c.font.makeColumn;return function(a){return"n"===a?function(a){return"n"===a?c.doubleNasalsWithTildeBelow?b(h("numen").addTildeBelow()):b(h("numen").addTildeAbove()):"t"===a?function(a){return"h"===a?b(h("thule").addTildeAbove()):b(h("tinco").addTildeAbove())(a)}:
+"d"===a?b(h("ando").addTildeAbove()):"c"===a?b(h("quesse").addTildeAbove()):"g"===a?b(h("ungwe").addTildeAbove()):"j"===a?b(h("anca").addTildeAbove()):"w"===a?function(a){return"a"===a?function(a){return"l"===a?b(h("nwalme").addAbove("w"))("a")(a):b(h("numen").addAbove("w"))("a")(a)}:"nw'"===a?b(h("nwalme").addAbove("w")):b(h("numen").addAbove("w"))(a)}:b(h("numen"))(a)}:"m"===a?function(a){return"m"===a?c.doubleNasalsWithTildeBelow?b(h("malta").addTildeBelow()):b(h("malta").addTildeAbove()):"p"===
+a?b(h("parma").addTildeAbove()):"b"===a?b(h("umbar").addTildeAbove()):"f"===a?b(h("formen").addTildeAbove()):"v"===a?b(h("ampa").addTildeAbove()):b(h("malta"))(a)}:"\u00f1"===a?function(a){return"c"===a?b(h("quesse").addTildeAbove()):"g"===a?b(h("ungwe").addTildeAbove()):b(h("nwalme"))(a)}:"t"===a?function(a){return"t"===a?b(h("tinco").addTildeBelow()):"h"===a?b(h("thule")):"c"===a?function(a){return"h"===a?b(h("tinco"))("c")("h")("'"):b(h("tinco"))("c")(a)}:b(h("tinco"))(a)}:"p"===a?function(a){return"p"===
+a?b(h("parma").addTildeBelow()):b(h("parma"))(a)}:"c"===a?function(a){return"c"===a?b(h("calma")):"h"===a?c.noAchLaut?b(h("calma")):b(h("hwesta")):b(h("quesse"))(a)}:"d"===a?function(a){return"d"===a?b(h("ando").addTildeBelow()):"j"===a?b(h("anga")):"h"===a?b(h("anto")):b(h("ando"))(a)}:"b"===a?function(a){return"b"===a?b(h("umbar").addTildeBelow()):b(h("umbar"))(a)}:"g"===a?function(a){return"g"===a?b(h("ungwe").addTildeBelow()):"h"===a?b(h("unque")):b(h("ungwe"))(a)}:"f"===a?function(a){return"f"===
+a?b(h("formen").addTildeBelow()):b(h("formen"))(a)}:"v"===a?b(h("ampa")):"j"===a?b(h("anca")):"s"===a?function(a){return"s"===a?b(h("silme").addTildeBelow()):"h"===a?b(h("harma")):b(h("silme"))(a)}:"z"===a?function(a){return"z"===a?b(h("esse").addTildeBelow()):b(h("esse"))(a)}:"h"===a?function(a){return"w"===a?b(h("hwesta-sindarinwa")):b(h("hyarmen"))(a)}:"r"===a?function(a){return"r"===a?b(h("romen").addTildeBelow()):"h"===a?b(h("arda")):""===a?b(h("ore"))(a):b(h("romen"))(a)}:"l"===a?function(a){return"l"===
+a?b(h("lambe").addTildeBelow()):"h"===a?b(h("alda")):b(h("lambe"))(a)}:"i"===a?b(h("anna")):"u"===a?b(h("vala")):"w"===a?function(a){return"h"===a?b(h("hwesta-sindarinwa")):b(h("vala"))(a)}:"e"===a&&(!f||"a"===f)?b(h("yanta")):"y"===a?b(h("wilya").addBelow("y")):"\u00e1"===a?b(h("wilya").addAbove("a")):H[a]&&-1==M.indexOf(a)?b(h("long-carrier").addAbove(H[a])):b()(a)}};return function(a){return-1!==M.indexOf(a)?f(a):f()(a)}}function C(a,c){var b=function(b){var c=function(b){return function(c){return"s"===
+c?b.canAddBelow("s")?a(b.addBelow("s")):G.countPrimes(function(c){return function(e){if(""===e){if(b.canAddFollowing("s-final")&&0===c--)b.addFollowing("s-final");else if(b.canAddFollowing("s-inverse")&&0===c--)b.addFollowing("s-inverse");else if(b.canAddFollowing("s-extended")&&0===c--)b.addFollowing("s-extended");else if(b.canAddFollowing("s-flourish"))b.addFollowing("s-flourish"),0<c&&b.addError("Following S only has 3 alternate flourishes.");else return a(b)("s")(e);return a(b)(e)}return a(b)("s")(e)}}):
+a(b)(c)}};return function(a){return"y"===a&&b.canAddBelow("y")?c(b.addBelow("y")):c(b)(a)}};return c.canAddAbove("w")?function(a){return"w"===a?b(c.addAbove("w")):b(c)(a)}:b(c)}function I(a){a=a||va;return{font:a.font||wa,block:a.block,plain:a.plain,vilya:a.vilya,harma:a.harma,classical:a.classical,reverseCurls:a.reverseCurls,iuRising:a.isRising,longHalla:a.longHalla}}function Y(a,c,b,d){var b=b||[],e=function(e){return e.length?Y(a,c,b.concat(e),e[e.length-1]):a(b)},i=c.font.makeColumn,f=function(a){var b=
+a.pop();return Z(function(b){b=a.concat(b).filter(Boolean);return b.length?e(b):function(a){return J.isBreak(a)?e([])(a):$[a]?e([i($[a])]):e([i("anna").addError("Cannot transcribe "+JSON.stringify(a)+" in Classical Mode")])}},c,b)},g=c.font.makeColumn;return function(a){return"n"===a?function(a){return"n"===a?f([g("numen").addTildeBelow()]):"t"===a?f([g("anto")]):"d"===a?f([g("ando")]):"g"===a?function(a){return"w"===a?f([g("ungwe")]):f([g("anga")])(a)}:"c"===a?function(a){return"w"===a?f([g("unque")]):
+f([g("anca")])(a)}:f([g("numen")])(a)}:"m"===a?function(a){return"m"===a?f([g("malta").addTildeBelow()]):"p"===a?f([g("ampa")]):"b"===a?f([g("umbar")]):f([g("malta")])(a)}:"\u00f1"===a?function(a){return"g"===a?function(a){return"w"===a?f([g("ungwe")]):f([g("anga")])(a)}:"c"===a?function(a){return"w"===a?f([g("unque")]):f([g("anca")])}:f([g("noldo")])(a)}:"t"===a?function(a){return"t"===a?function(a){return"y"===a?f([g("tinco").addBelow("y").addTildeBelow()]):f([g("tinco").addTildeBelow()])(a)}:"y"===
+a?f([g("tinco").addBelow("y")]):"h"===a?f([g("thule")]):"s"===a?function(a){return J.isFinal(a)?f([g("tinco").addFollowing("s")])(a):f([g("tinco"),g("silme")])(a)}:f([g("tinco")])(a)}:"p"===a?function(a){return"p"===a?function(a){return"y"===a?f([g("parma").addBelow("y").addTildeBelow()]):f([g("parma").addTildeBelow()])(a)}:"y"===a?f([g("parma").addBelow("y")]):"s"===a?function(a){return J.isFinal(a)?f([g("parma").addFollowing("s")])(a):f([g("parma"),g("silme")])(a)}:f([g("parma")])(a)}:"c"===a?function(a){return"c"===
+a?f([g("calma").addTildeBelow()]):"s"===a?f([g("calma").addBelow("s")]):"h"===a?f([g("harma")]):"w"===a?f([g("quesse")]):f([g("calma")])(a)}:"f"===a?f([g("formen")]):"v"===a?c.vilya?f([g("wilya")]):f([g("vala")]):"w"===a?c.vilya?f([])("u"):f([g("wilya")]):"r"===a?function(a){return"d"===a?f([g("arda")]):"h"===a?f([g("halla").addError("R should preceed H in the HR diagraph in Classical mode."),g("romen").addError("R should preceed H in the HR diagraph in Classical mode.")]):c.classical?d&&d.above&&
+""!==a&&-1!==aa.indexOf(a)?f([g("ore")])(a):f([g("romen")])(a):J.isFinal(a)||-1===aa.indexOf(a)?f([g("ore")])(a):f([g("romen")])(a)}:"l"===a?function(a){return"l"===a?function(a){return"y"===a?f([g("lambe").addBelow("y").addTildeBelow()]):f([g("lambe").addTildeBelow()])(a)}:"y"===a?f([g("lambe").addBelow("y")]):"h"===a?f([g("halla").addError("L should preceed H in the HL diagraph in Classical mode."),g("lambe").addError("L should preceed H in the HL diagraph in Classical mode.")]):"d"===a?f([g("alda")]):
+"b"===a?f([g("lambe"),g("umbar")]):f([g("lambe")])(a)}:"s"===a?function(a){return"s"===a?f([g("esse")]):f([g("silme")])(a)}:"h"===a?function(a){return"l"===a?f([g("halla"),g("lambe")]):"r"===a?f([g("halla"),g("romen")]):"w"===a?f([g("hwesta")]):"t"===a?f([g("harma")]):"y"===a?c.classical&&c.harma?f([g("hyarmen")]):f([g("hyarmen").addBelow("y")]):c.classical?c.harma?f([g("harma")])(a):d?f([g("hyarmen")])(a):f([g("harma")])(a):f([g("hyarmen")])(a)}:"d"===a?f([g("ando").addError("D cannot appear except after N, L, or R")]):
+"b"===a?f([g("umbar").addError("B cannot appear except after M or L")]):"g"===a?f([g("anga").addError("G cannot appear except after N or \u00d1")]):"j"===a?f([g().addError("J cannot be transcribed in Classical Mode")]):f([])(a)}}function Z(a,c,b){var d=c.font.makeColumn;return function(e){if("a"===e)return function(c){return"i"===c?a([b,d("yanta","a")]):"u"===c?a([b,d("ure","a")]):b&&b.canAddAbove("a")?a([b.addAbove("a")])(c):a([b,d("short-carrier","a")])(c)};if("e"===e)return function(c){return"u"===
+c?a([b,d("ure","e")]):b&&b.canAddAbove("e")?a([b.addAbove("e")])(c):a([b,d("short-carrier","e")])(c)};if("i"===e)return function(e){return"u"===e?c.iuRising?a([b,d("anna").addAbove(p("u",c)).addBelow("y")]):a([b,d("ure","i")]):b&&b.canAddAbove("i")?a([b.addAbove("i")])(e):a([b,d("short-carrier","i")])(e)};if("o"===e)return function(e){return"i"===e?a([b,d("yanta").addAbove(p("o",c))]):b&&b.canAddAbove("o")?a([b.addAbove(p("o",c))])(e):a([b,d("short-carrier").addAbove(p("o",c))])(e)};if("u"===e)return function(e){return"i"===
+e?a([b,d("yanta","u")]):b&&b.canAddAbove("u")?a([b.addAbove(p("u",c))])(e):a([b,d("short-carrier").addAbove(p("u",c))])(e)};if("y"===e){if(b&&b.canAddBelow("y"))return a([b.addBelow("y")]);e=d("anna").addBelow("y");return Z(function(c){return a([b].concat(c))},c,e)}return"\u00e1"===e?a([b,d("long-carrier","a")]):"\u00e9"===e?a([b,d("long-carrier","e")]):"\u00ed"===e?a([b,d("long-carrier","i")]):"\u00f3"===e?b&&b.canAddAbove("\u00f3")?a([b.addAbove(p("\u00f3",c))]):a([b,d("long-carrier").addAbove(p("o",
+c))]):"\u00fa"===e?b&&b.canAddAbove("\u00fa")?a([b.addAbove(p("\u00fa",c))]):a([b,d("long-carrier").addAbove(p("u",c))]):a([b])(e)}}function p(a,c){c.reverseCurls&&(a=xa[a]||a);return a}function K(a){a=a||ya;return{font:a.font||za,block:a.block,plain:a.plain}}function ba(a,c,b){b=b||[];return ca(function(d){return d?ba(a,c,b.concat([d])):a(b)},c)}function ca(a,c){return D(function(b){return b?function(c){return"s"===c?b.canAddBelow("s")?a(b.addBelow("s")):A.countPrimes(function(c){return function(d){if(A.isFinal(d)){if(b.canAddFollowing("s-final")&&
+0===c--)b.addFollowing("s-final");else if(b.canAddFollowing("s-inverse")&&0===c--)b.addFollowing("s-inverse");else if(b.canAddFollowing("s-extended")&&0===c--)b.addFollowing("s-extended");else if(b.canAddFollowing("s-flourish"))b.addFollowing("s-flourish"),0<c&&b.addError("Following S only has 3 alternate flourishes.");else return a(b)("s")(d);return a(b)(d)}return a(b)("s")(d)}}):a(b)(c)}:a()},c)}function D(a,c){var b=c.font.makeColumn;return function(d){return"n"===d?function(e){return"t"===e||
+"d"===e?D(function(b){return a(b.addTildeAbove())},c)(e):"c"===e||"g"===e?D(a,c)("\u00f1")(e):"n"===e?a(b("numen")):a(b("ore"))(e)}:"t"===d?function(c){return"h"===c?a(b("thule")):a(b("tinco"))(c)}:"d"===d?function(c){return"h"===c?a(b("anto")):a(b("ando"))(c)}:"m"===d?function(e){return"p"===e||"b"===e||"f"===e||"v"===e?D(function(b){return a(b.addTildeAbove())},c)(e):"m"===e?a(b("malta")):a(b("vala"))(e)}:"p"===d?a(b("parma")):"b"===d?a(b("umbar")):"f"===d?function(c){return A.isFinal(c)?a(b("ampa"))(c):
+a(b("formen"))(c)}:"v"===d?a(b("ampa")):"\u00f1"===d?function(e){return"c"===e||"g"===e?D(function(b){"halla"===b.tengwa&&b.addError("Lenited G (halla) should not be nasalized with prefix N");return a(b.addTildeAbove())},c)(e):a(b("noldo"))(e)}:"c"===d?function(c){return"h"===c?function(c){return"w"===c?a(b("hwesta")):a(b("harma"))(c)}:"w"===c?a(b("quesse")):a(b("calma"))(c)}:"g"===d?function(c){return"h"===c?function(c){return"w"===c?a(b("unque")):a(b("anca"))(c)}:"w"===c?a(b("ungwe")):"'"===c?a(b("halla")):
+a(b("anga"))(c)}:"r"===d?function(c){return"h"===c?a(b("arda")):a(b("romen"))(c)}:"l"===d?function(c){return"h"===c?a(b("alda")):a(b("lambe"))(c)}:"s"===d?a(b("silme")):"a"===d?function(c){return"i"===c?a(b("round-carrier").addAbove("\u00ed")):"u"===c?a(b("round-carrier").addAbove("w")):"'"===c?a(b("round-carrier").addAbove("i")):"a"===c?a(b("round-carrier").addAbove("e")):a(b("round-carrier"))(c)}:"e"===d?function(c){return"i"===c?a(b("yanta").addAbove("\u00ed")):"e"===c?a(b("yanta").addAbove("e")):
+a(b("yanta"))(c)}:"i"===d?function(e){return"i"===e?ca(a,c)("\u00ed"):A.countPrimes(function(c){return 0===c?a(b("short-carrier")):1===c?a(b("short-carrier").addAbove("i")):3===c?a(b("long-carrier")):4===c?a(b("long-carrier").addAbove("i")):a(b("long-carrier").addAbove("i").addError("I only has four variants between short or long and dotted or not."))})(e)}:"o"===d?function(c){return"o"===c?a(b("anna").addAbove("e")):a(b("anna"))(c)}:"u"===d?function(c){return"i"===c?a(b("ure").addAbove("\u00ed")):
+"u"===c?a(b("ure").addAbove("e")):a(b("ure"))(c)}:"w"===d?function(c){return"w"===c?a(b("wilya").addAbove("e")):a(b("wilya"))(c)}:"y"===d?function(c){return"y"===c?a(b("silme-nuquerna").addAbove("e")):a(b("silme-nuquerna"))(c)}:"\u00e1"===d?a(b("round-carrier").addAbove("e")):"\u00e9"===d?a(b("yanta").addAbove("e")):"\u00ed"===d?A.countPrimes(function(c){return 0===c?a(b("short-carrier").addAbove("e")):1===c?a(b("long-carrier").addAbove("e")):a(b("long-carrier").addAbove("e").addError("\u00cd only has one variant."))}):
+"\u00f3"===d?a(b("anna").addAbove("e")):"\u00fa"===d?a(b("ure").addAbove("e")):"h"===d?function(c){return"w"===c?a(b("hwesta-sindarinwa")):a(b("hyarmen"))(c)}:"z"===d?a(b("silme").addError("Z does not appear in the mode of Beleriand")):da[d]?a(b(da[d])):A.isBreak(d)?a()(d):a(b("anna").addError("Unexpected character: "+JSON.stringify(d)))}}function ea(){if(document.body.classList&&document.querySelectorAll&&fa.forEach){var a=document.querySelectorAll(".tengwar");fa.forEach.call(a,function(a){var b=
+a.dataset,d,e,i;i="div"===a.tagName.toLowerCase();b?(d=b.tengwar,e=b.mode,b=b.encoding):(d=a.getAttribute("data-tengwar"),e=a.getAttribute("data-mode"),b=a.getAttribute("data-encoding"));if(b)a.innerText=ga.transcribe(b,{block:i});else if(d){var b=a.classList.contains("parmaite")?Aa:ga,f=e.split(/\s+/);e=f.shift();e=Ba[e]||Ca;var g=e.makeOptions();f.forEach(function(a){a=a.replace(/\-(\w)/g,function(a,b){return b.toUpperCase()});g[a]=!0});g.block=i;g.font=b;a.innerHTML=e.transcribe(d,g)}})}}var k=
+{tinco:"1",parma:"q",calma:"a",quesse:"z",ando:"2",umbar:"w",anga:"s",ungwe:"x",thule:"3",formen:"e",harma:"d",hwesta:"c",anto:"4",ampa:"r",anca:"f",unque:"v",numen:"5",malta:"t",noldo:"g",nwalme:"b",ore:"6",vala:"y",anna:"h",wilya:"n",romen:"7",arda:"u",lambe:"j",alda:"m",silme:"8","silme-nuquerna":"i",esse:"k","esse-nuquerna":",",hyarmen:"9","hwesta-sindarinwa":"o",yanta:"l",ure:".",halla:"\u00bd","short-carrier":"`","long-carrier":"~","round-carrier":"]","tinco-extended":"!","parma-extended":"Q",
+"calma-extended":"A","quesse-extended":"Z",comma:"=","full-stop":"-","exclamation-point":"\u00c1","question-mark":"\u00c0","open-paren":"&#140;","close-paren":"&#156;","flourish-left":"&#286;","flourish-right":"&#287;","#0":"\uf8ff","#1":"\u00d2","#2":"\u00da","#3":"\u00db","#4":"\u00d9","#5":"\u0131","#6":"\u02c6","#7":"\u02dc","#8":"\u00af","#9":"\u02d8","#10":"\u02d9","#11":"\u02da"},n={a:"#EDC",e:"$RFV",i:"%TGB",o:"^YHN",u:"& U J M &#256; &#257; &#258; &#259;".split(" "),"\u00f3":["&#260;","&#261;",
+"&#262;","&#263;"],"\u00fa":["&#264;","&#265;","&#266;","&#267;"],"\u00ed":["&#212;","&#213;","&#214;","&#215;"],w:"\u00e8\u00e9\u00ea\u00eb",y:"\u00cc\u00cd\u00ce\u00cf\u00b4","o-under":["\u00e4","&#229;","\u00e6","\u00e7","|"],s:{special:!0,calma:"|",quesse:"|","short-carrier":"}"},"s-final":{special:!0,tinco:"+",ando:"+",numen:"+",lambe:"_"},"s-inverse":{special:!0,tinco:"\u00a1"},"s-extended":{special:!0,tinco:"&#199;"},"s-flourish":{special:!0,tinco:"&#163;",lambe:"&#165;"},"tilde-above":"Pp",
+"tilde-below":[":",";","&#176;"],"tilde-high-above":")0","tilde-far-below":"?/","bar-above":"{[","bar-below":['"',"'","&#184;"],"bar-high-above":"\u00ec\u00ee","bar-far-below":"\u00ed\u00ef"},m={tengwar:[["tinco","parma","calma","quesse"],["ando","umbar","anga","ungwe"],["thule","formen","harma","hwesta"],["anto","ampa","anca","unque"],["numen","malta","noldo","nwalme"],["ore","vala","anna","wilya"],["romen","arda","lambe","alda"],["silme","silme-nuquerna","esse","esse-nuquerna"],["hyarmen","hwesta-sindarinwa",
+"yanta","ure"],["halla","short-carrier","long-carrier","round-carrier"],["tinco-extended","parma-extended","calma-extended","quesse-extended"]],tehtarAbove:"aeiou\u00e1\u00e9\u00ed\u00f3\u00faw".split(""),tehtarBelow:["y","s","o-under"],tehtarFollowing:["s-final","s-inverse","s-extended","s-flourish"],barsAndTildes:"tilde-above tilde-below tilde-high-above tilde-far-below bar-above bar-below bar-high-above bar-far-below".split(" ")};m.tehtar=[].concat(m.tehtarAbove,m.tehtarBelow,m.tehtarFollowing,
+m.barsAndTildes);m.aliases={vilya:"wilya",aha:"harma",gasdil:"halla"};var s={},la=m,Da=s.tengwar=k,y=s.tehtar=n,t=s.positions={tinco:{o:3,w:3,others:2},parma:{o:3,w:3,others:2},calma:{o:3,w:3,u:3,"o-under":1,others:2},quesse:{o:3,w:3,"o-under":1,others:2},ando:{wide:!0,e:1,o:2,"\u00f3":1,"\u00fa":1,others:0},umbar:{wide:!0,e:1,o:2,"\u00f3":1,"\u00fa":1,others:0},anga:{wide:!0,e:1,"\u00f3":1,"\u00fa":1,others:0},ungwe:{wide:!0,e:1,o:1,"\u00f3":1,"\u00fa":1,others:0},thule:{others:3},formen:3,harma:{e:0,
+o:3,u:7,"\u00f3":2,"\u00fa":2,w:0,others:1},hwesta:{e:0,o:3,u:7,w:0,others:1},anto:{wide:!0,"\u00f3":1,"\u00fa":1,others:0},ampa:{wide:!0,"\u00f3":1,"\u00fa":1,others:0},anca:{wide:!0,u:7,"\u00f3":1,"\u00fa":1,others:0},unque:{wide:!0,u:7,others:0},numen:{wide:!0,"\u00f3":1,"\u00fa":1,others:0},malta:{wide:!0,"\u00f3":1,"\u00fa":1,others:0},noldo:{wide:!0,"\u00f3":1,"\u00fa":1,others:0},nwalme:{wide:!0,"\u00f3":1,"\u00fa":1,others:0},ore:{e:3,o:3,u:3,"\u00f3":3,"\u00fa":3,others:1},vala:{e:3,o:3,
+u:3,"\u00f3":3,"\u00fa":3,others:1},anna:{e:3,o:3,u:3,"\u00f3":2,"\u00fa":2,others:1},wilya:{e:3,o:3,u:3,"\u00f3":3,"\u00fa":3,others:1},romen:{e:3,o:3,u:3,"\u00f3":2,"\u00fa":2,y:null,"o-under":null,others:1},arda:{a:1,e:3,i:1,o:3,u:3,"\u00ed":1,"\u00f3":2,"\u00fa":2,y:null,"o-under":null,others:0},lambe:{wide:!0,e:1,y:4,"\u00f3":1,"\u00fa":1,"o-under":null,others:0},alda:{wide:!0,"o-under":null,others:1},silme:{y:3,"o-under":2,others:null},"silme-nuquerna":{e:3,o:3,u:3,"\u00f3":3,"\u00fa":3,y:null,
+"o-under":null,others:1},esse:{y:null,others:null},"esse-nuquerna":{e:3,o:3,u:3,"\u00f3":3,"\u00fa":3,others:1},hyarmen:3,"hwesta-sindarinwa":{o:2,u:2,"\u00f3":1,"\u00fa":2,others:0},yanta:{e:3,o:3,u:3,"\u00f3":2,"\u00fa":2,others:1},ure:{e:3,o:3,u:3,"\u00f3":3,"\u00fa":3,others:1},halla:{others:null},"short-carrier":3,"long-carrier":{y:null,"o-under":null,others:3},"round-carrier":3,"tinco-extended":3,"parma-extended":3,"calma-extended":{o:3,u:7,"\u00f3":2,"\u00fa":2,others:1},"quesse-extended":{o:0,
+u:7,others:1}};s.transcribe=function(a,c){var c=c||{},b=c.plain||!1,d=c.block||!1,e=d?"<p>":"",i=d?"<br>":"",f=d?"</p>":"";return a.map(function(a){return a.map(function(a){return e+a.map(function(a){return a.map(function(a){return a.map(function(a){var c=a.tengwa||"anna",d=[];a.above&&d.push(a.above);a.below&&d.push(a.below);a.tildeBelow&&d.push("tilde-below");a.tildeAbove&&d.push("tilde-above");a.following&&d.push(a.following);d=Da[c]+d.map(function(a){return w(c,a)}).join("");a.errors&&!b&&(d=
+'<abbr class="error" title="'+a.errors.join("\n").replace(/"/g,"&quot;")+'">'+d+"</abbr>");return d}).join("")}).join(" ")}).join(i+"\n")+f}).join("\n\n")}).join("\n\n\n")};s.tehtaForTengwa=w;s.makeColumn=function(a,c,b){return new q(a,c,b)};var q=function(a,c,b){this.above=c;this.tildeAbove=void 0;this.tengwa=a;this.tildeBelow=void 0;this.below=b;this.error=this.following=void 0};q.prototype.canAddAbove=function(a){return!this.above&&!!w(this.tengwa,a)||!this.below&&("silme"===this.tengwa&&w("silme-nuquerna",
+a)||"esse"===this.tengwa&&w("esse-nuquerna",a))};q.prototype.addAbove=function(a){"silme"===this.tengwa&&(this.tengwa="silme-nuquerna");"esse"===this.tengwa&&(this.tengwa="esse-nuquerna");this.above=a;return this};q.prototype.canAddBelow=function(a){return!this.below&&!!w(this.tengwa,a)};q.prototype.addBelow=function(a){this.below=a;return this};q.prototype.addTildeAbove=function(){this.tildeAbove=!0;return this};q.prototype.addTildeBelow=function(){this.tildeBelow=!0;return this};q.prototype.canAddFollowing=
+function(a){return!this.following&&!!w(this.tengwa,a)};q.prototype.addFollowing=function(a){this.following=a;return this};q.prototype.addError=function(a){this.errors=this.errors||[];this.errors.push(a);return this};var v={},na=m,Ea=v.tengwar=k,z=v.tehtar=n,u=v.positions={tinco:2,parma:2,calma:{y:1,"o-under":1,others:2},quesse:{y:1,"o-under":1,others:2},ando:{wide:!0,others:0},umbar:{wide:!0,others:0},anga:{wide:!0,others:0},ungwe:{wide:!0,others:0},thule:{a:3,w:3,others:2},formen:{a:3,w:3,"\u00ed":3,
+others:2},harma:{a:0,e:0,i:1,o:1,u:1,w:0,"\u00ed":0,others:1},hwesta:{a:0,e:0,i:1,o:1,u:1,w:0,others:1},anto:{wide:!0,others:0},ampa:{wide:!0,others:0},anca:{wide:!0,others:0},unque:{wide:!0,others:0},numen:{wide:!0,others:0},malta:{wide:!0,others:0},noldo:{wide:!0,others:0},nwalme:{wide:!0,others:0},ore:{a:1,e:2,i:1,o:2,u:3,others:1},vala:{a:1,e:2,i:2,o:2,w:1,y:1,"\u00ed":2,others:3},anna:{a:1,w:3,others:2},wilya:{i:2,"\u00ed":2,others:1},romen:{a:1,e:1,i:2,o:1,u:1,y:3,"o-under":null,others:1},arda:{a:1,
+e:1,i:2,o:1,u:1,w:1,"\u00ed":2,y:null,"o-under":null,others:0},lambe:{wide:!0,e:1,y:4,w:0,"o-under":null,others:0},alda:{wide:!0,w:0,y:null,"o-under":null,others:1},silme:{y:2,"o-under":2,others:null},"silme-nuquerna":{e:2,y:null,"o-under":null,others:1},esse:{others:null},"esse-nuquerna":{e:2,y:null,"o-under":null,others:1},hyarmen:{y:1,others:3},"hwesta-sindarinwa":{w:1,y:1,others:0},yanta:{a:1,others:2},ure:{a:1,others:2},halla:{others:null},"short-carrier":{y:null,others:3},"long-carrier":{y:null,
+"o-under":null,others:3},"round-carrier":2,"tinco-extended":{a:3,w:3,y:3,"\u00ed":3,others:2},"parma-extended":{a:3,w:3,y:3,"\u00ed":3,others:2},"calma-extended":{i:1,w:1,y:0,"\u00ed":0,others:0},"quesse-extended":{i:1,w:1,y:0,"\u00ed":0,others:0}};v.transcribe=function(a,c){var c=c||{},b=c.plain||!1,d=c.block||!1,e=d?"<p>":"",i=d?"<br>":"",f=d?"</p>":"";return a.map(function(a){return a.map(function(a){return e+a.map(function(a){return a.map(function(a){return a.map(function(a){var c=a.tengwa||"anna",
+d=[];a.above&&d.push(a.above);a.below&&d.push(a.below);a.tildeBelow&&d.push("tilde-below");a.tildeAbove&&d.push("tilde-above");a.following&&d.push(a.following);d=Ea[c]+d.map(function(a){return x(c,a)}).join("");a.errors&&!b&&(d='<abbr class="error" title="'+a.errors.join("\n").replace(/"/g,"&quot;")+'">'+d+"</abbr>");return d}).join("")}).join(" ")}).join(i+"\n")+f}).join("\n\n")}).join("\n\n\n")};v.tehtaForTengwa=x;var ma="\u00e1\u00e9\u00f3\u00fa";v.makeColumn=function(a,c,b){return new r(a,c,b)};
+var r=function(a,c,b){this.above=c;this.tildeAbove=void 0;this.tengwa=a;this.tildeBelow=void 0;this.below=b;this.error=this.following=void 0};r.prototype.canAddAbove=function(a){return!this.above&&!!x(this.tengwa,a)||!this.below&&("silme"===this.tengwa&&x("silme-nuquerna",a)||"esse"===this.tengwa&&x("esse-nuquerna",a))};r.prototype.addAbove=function(a){"silme"===this.tengwa&&(this.tengwa="silme-nuquerna");"esse"===this.tengwa&&(this.tengwa="esse-nuquerna");this.above=a;return this};r.prototype.canAddBelow=
+function(a){return!this.below&&!!x(this.tengwa,a)};r.prototype.addBelow=function(a){this.below=a;return this};r.prototype.addTildeAbove=function(){this.tildeAbove=!0;return this};r.prototype.addTildeBelow=function(){this.tildeBelow=!0;return this};r.prototype.canAddFollowing=function(a){return!this.following&&!!x(this.tengwa,a)};r.prototype.addFollowing=function(a){this.following=a;return this};r.prototype.addError=function(a){this.errors=this.errors||[];this.errors.push(a);return this};k={module$exports:{"-":"comma",
+",":"comma",":":"comma",";":"full-stop",".":"full-stop","!":"exclamation-point","?":"question-mark","(":"open-paren",")":"close-paren",">":"flourish-left","<":"flourish-right"}};k.module$exports&&(k=k.module$exports);var n={},Fa=Array.prototype;n.module$exports=function(a,c,b){var d=b.font.makeColumn;return Fa.map.call(parseInt(a,c).toString(c),function(a){return d("#"+a)})};n.module$exports&&(n=n.module$exports);var L={encode:function(a){return a.map(function(a){return a.map(function(a){return a.map(function(a){return a.map(function(a){return a.map(function(a){var b=
+[];a.above&&b.push(a.above);a.below&&b.push(a.below);a.following&&b.push(a.following);a.tildeAbove&&b.push("tilde-above");a.tildeBelow&&b.push("tilde-below");return b.length?a.tengwa+":"+b.join(","):a.tengwa}).join(";")}).join(" ")}).join("\n")}).join("\n\n")}).join("\n\n\n")},decode:function(a,c){return a.split("\n\n\n").map(function(a){return a.split("\n\n").map(function(a){return a.split("\n").map(function(a){return a.split(" ").map(function(a){return O(a,c)})})})})}};L.decodeWord=O;var j={},Ga=
+k;j.makeParser=function(a,c){c=c||function(a,c){throw Error(a+" while parsing "+JSON.stringify(c));};return function(b){var d,e=a.apply(null,[function(a){d=a;return function(a){""!==a&&(a="Unexpected "+JSON.stringify(a),c(a,b));return function ra(){return ra}}}].concat(Array.prototype.slice.call(arguments,1)));for(Array.prototype.forEach.call(b,function(a){e=e(a)});!d;)e=e("");return d}};j.makeExpect=function(a){return function(c){return function(b){return b===a?c(b):c()(b)}}};j.makeParseSome=function(a){var c=
+P(a);return function(b){return function(d){return d===a?c(b):b()(d)}}};j.makeParseAny=P;j.makeDelimitedParser=function(a,c){return function d(e,i,f){f=f||[];return a(function(a){return a.length?(f=f.concat([a]),c(function(a){return a?d(e,i,f):e(f)})):e(f)},i)}};j.isBreak=Q;j.isFinal=function(a){return Q(a)||Ga[a]};j.countPrimes=R;var n={module$exports:function(a,c){var b=j.makeDelimitedParser(a,Ha),b=j.makeDelimitedParser(b,S),b=j.makeDelimitedParser(b,S),d=j.makeDelimitedParser(b,pa);return j.makeParser(function(a,
+b){b=c(b);return d(a,b)})}},E=j.makeParseAny(" "),Ha=j.makeParseSome(" "),oa=j.makeExpect("\n"),qa=j.makeParseSome("\n");n.module$exports&&(n=n.module$exports);var l={};l.module$exports=T;l.module$exports&&(l=l.module$exports);var o={};o.module$exports=U;o.module$exports&&(o=o.module$exports);var m={module$exports:function(a){var c=N(a);return function d(a){c=c(a.toLowerCase());return d}}},l=l({k:"c",x:"cs",q:"cw",qu:"cw",p:"p",ph:"f",b:"b",bh:"v","\u00eb":"e","\u00e2":"\u00e1","\u00ea":"\u00e9",
+"\u00ee":"\u00ed","\u00f4":"\u00f3","\u00fb":"\u00fa"}),N=o(l,function(a){return function(c){return Array.prototype.reduce.call(a,function(a,c){return a(c)},c)}},function(a){return N(a)},function(a){return function(c){return N(a(c))}});m.module$exports&&(m=m.module$exports);var l={},ta=s,G=j,Ia=m,X=k;l.name="General Use Mode";var sa={};l.makeOptions=F;l.transcribe=function(a,c){c=F(c);return c.font.transcribe(ha(a,c),c)};l.encode=function(a,c){c=F(c);return L.encode(ha(a,c),c)};var ha=l.parse=n(function(a,
+c){var b,d=c.font.makeColumn;b=V(function(b){return ia[b]?a(L.decodeWord(ia[b],d)):a(Ja(b,c))},c);return Ia(b)},F),ia={iant:"yanta;tinco:a,tilde-above",iaur:"yanta;vala:a;ore",baranduiniant:"umbar;romen:a;ando:a,tilde-above;anna:u;yanta;anto:a,tilde-above",ioreth:"yanta;romen:o;thule:e",noldo:"nwalme;lambe:o;ando;short-carrier:o",noldor:"nwalme;lambe:o;ando;ore:o",is:"short-carrier:i,s"},Ja=G.makeParser(function(a,c){return W(a,c,[])}),M="aeiou\u00f3\u00fa",H={"\u00e1":"a","\u00e9":"e","\u00ed":"i",
+"\u00f3":"o","\u00fa":"u"},ua={o:"u",u:"o","\u00f3":"\u00fa","\u00fa":"\u00f3"};l.parseTengwaAnnotations=C;var o={},wa=s,J=j,Ka=m,$=k;o.name="Classical Mode";var va={};o.makeOptions=I;o.transcribe=function(a,c){c=I(c);return c.font.transcribe(ja(a,c),c)};o.encode=function(a,c){c=I(c);return L.encode(ja(a,c),c)};var ja=o.parse=n(function(a,c){return Ka(Y(a,c))},I),aa="aeiouy\u00e1\u00e9\u00ed\u00f3\u00fa",xa={o:"u",u:"o","\u00f3":"\u00fa","\u00fa":"\u00f3"},B={},za=v,A=j,La=m,da=k;B.name="Mode of Beleriand";
+var ya={};B.makeOptions=K;B.transcribe=function(a,c){c=K(c);return c.font.transcribe(ka(a,c),c)};B.encode=function(a,c){c=K(c);return Notation.encode(ka(a,c),c)};var ka=B.parse=n(function(a,c){return La(ba(a,c))},K),k={};k.module$exports={"general-use":l,classical:o,beleriand:B};k.module$exports&&(k=k.module$exports);var Ca=l,ga=s,Aa=v,Ba=k,fa=Array.prototype;"complete"===document.readyState?ea():document.addEventListener("DOMContentLoaded",ea,!0)})();