--- /dev/null
+node_modules
--- /dev/null
+
+var Render = require("./render");
+var makeColumn = require("./column");
+
+exports.transcribe = transcribe;
+function transcribe(text) {
+ var tengwarObjects = parse(text);
+ return Render.transcribe(tengwarObjects);
+}
+
+exports.encode = encode;
+function encode(text) {
+ var tengwarObjects = parse(text);
+ return Render.encode(tengwarObjects);
+}
+
+exports.parse = parse;
+function parse(text, options) {
+ options = options || makeOptions();
+ 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(contiguous, options));
+ } catch (exception) {
+ word.push(makeColumn().addError("Cannot transcribe " + JSON.stringify(word) + " 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 + vilya? + aha? + longHalla?)
+// third age: (iuRising?) default
+
+function makeOptions() {
+ return {
+ vilya: false,
+ // false: (v: vala, w: wilya)
+ // true: (v: vilya, w: ERROR)
+ aha: false,
+ // between the original formation of the language,
+ // but before the third age,
+ // harma was renamed aha,
+ // and meant breath-h in initial position
+ longHalla: false,
+ // TODO indicates that halla should be used before medial L and W to
+ // indicate that these are pronounced with length.
+ // initial hl and hw remain short.
+ classical: false,
+ // before the third age
+ // affects use of "r" and "h"
+ // without classic, we default to the mode from the namarie poem.
+ // in the classical period, "r" was transcribed as "ore" only between
+ // vowels.
+ // in the third age, through the namarie poem, "r" is only "ore" before
+ // consontants and at the end of words.
+ // TODO figure out "h"
+ iuRising: false
+ // iuRising thirdAge: anna:y,u
+ // otherwise: ure:i
+ // in the third age, "iu" is a rising diphthong,
+ // whereas all others are falling. rising means
+ // that they are stressed on the second sound, as
+ // in "yule". whether to use yanta or anna is
+ // not attested.
+ // TODO doubled dots for í
+ // TODO triple dots for y
+ // TODO simplification of a, noting non-a
+ };
+};
+
+function parseWord(latin, options) {
+
+ // normalize
+ latin = latin.replace(substitutionsRe, function ($, key) {
+ return substitutions[key];
+ });
+
+ // 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
+ // result) and return a state.
+ var result;
+ var state = parseWordTail(function (transcription) {
+ result = transcription;
+ return expectEof();
+ }, options, []);
+ // drive the state machine
+ Array.prototype.forEach.call(latin, function (letter, i) {
+ state = state(letter);
+ });
+ // break break break
+ while (!result) {
+ state = state(""); // EOF
+ }
+ return result;
+
+}
+
+var substitutions = {
+ "k": "c",
+ "x": "cs",
+ "qu": "cw",
+ "q": "cw",
+ "ph": "f",
+ "ë": "e",
+ "â": "á",
+ "ê": "é",
+ "î": "í",
+ "ô": "ó",
+ "û": "ú"
+};
+
+var substitutionsRe = new RegExp("(" +
+ Object.keys(substitutions).join("|") +
+")", "ig");
+
+var vowels = "aeiouyáéíóú";
+var punctuation = {
+ "-": "comma",
+ ",": "comma",
+ ":": "comma",
+ ";": "full-stop",
+ ".": "full-stop",
+ "!": "exclamation-point",
+ "?": "question-mark",
+ "(": "open-paren",
+ ")": "close-paren",
+ ">": "flourish-left",
+ "<": "flourish-right"
+};
+
+// state machine
+
+function parseWordTail(callback, options, columns, previous) {
+ return parseColumn(function (moreColumns) {
+ if (!moreColumns.length) {
+ return callback(columns);
+ } else {
+ return parseWordTail(
+ callback,
+ options,
+ columns.concat(moreColumns),
+ moreColumns[moreColumns.length - 1] // previous
+ );
+ }
+ }, options, previous);
+}
+
+function parseColumn(callback, options, previous) {
+ return parseTengwa(function (columns) {
+ var previous = columns.pop();
+ return parseTehta(function (next) {
+ return callback(columns.concat(next).filter(Boolean));
+ }, options, previous);
+ }, options, previous);
+}
+
+function parseTengwa(callback, options, previous) {
+ return function (character) {
+ if (character === "n") { // n
+ return function (character) {
+ if (character === "n") { // nn
+ return callback([makeColumn("numen").addBarBelow()]);
+ } else if (character === "t") { // nt
+ return callback([makeColumn("tinco")]);
+ } else if (character === "d") { // nd
+ return callback([makeColumn("ando")]);
+ } else if (character === "g") { // ng
+ return function (character) {
+ if (character === "w") { // ngw
+ return callback([makeColumn("ungwe")]);
+ } else { // ng
+ return callback([makeColumn("anga")])(character);
+ }
+ };
+ } else if (character === "c") { // nc
+ return function (character) {
+ if (character === "w") { // ncw
+ return callback([makeColumn("unque")]);
+ } else { // nc
+ return callback([makeColumn("anca")])(character);
+ }
+ };
+ } else {
+ return callback([makeColumn("numen")])(character);
+ }
+ };
+ } else if (character === "m") {
+ return function (character) {
+ if (character === "m") { // mm
+ return callback([makeColumn("malta").addBarBelow()]);
+ } else if (character === "p") { // mp
+ return callback([makeColumn("ampa")]);
+ } else if (character === "b") { // mb
+ return callback([makeColumn("umbar")]);
+ } else {
+ return callback([makeColumn("malta")])(character);
+ }
+ };
+ } else if (character === "ñ") { // ñ
+ return function (character) {
+ if (character === "g") { // ñg
+ return function (character) {
+ if (character === "w") { // ñgw
+ return callback([makeColumn("ungwe")]);
+ } else { // ñg
+ return callback([makeColumn("anga")])(character);
+ }
+ }
+ } else if (character === "c") { // ñc
+ return function (character) {
+ if (character === "w") { // ñcw
+ return callback([makeColumn("unque")]);
+ } else { // ñc
+ return callback([makeColumn("anca")]);
+ }
+ }
+ } else {
+ return callback([makeColumn("noldo")])(character);
+ }
+ };
+ } else if (character === "t") {
+ return function (character) {
+ if (character === "t") { // tt
+ return function (character) {
+ if (character === "y") { // tty
+ return callback([makeColumn("tinco").addBelow("y").addBarBelow()]);
+ } else { // tt
+ return callback([makeColumn("tinco").addBarBelow()])(character);
+ }
+ };
+ } else if (character === "y") { // ty
+ return callback([makeColumn("tinco").addBelow("y")]);
+ } else if (character === "h") {
+ return callback([makeColumn("thule")]);
+ } else if (character === "s") {
+ return function (character) {
+ // TODO s-inverse, s-extended, s-flourish
+ if (character === "") { // ts final
+ return callback([makeColumn("tinco").addFollowing("s")])(character);
+ } else { // ts medial
+ return callback([
+ makeColumn("tinco"),
+ makeColumn("silme")
+ ])(character);
+ }
+ };
+ } else { // t
+ return callback([makeColumn("tinco")])(character);
+ }
+ };
+ } else if (character === "p") {
+ return function (character) {
+ if (character === "p") {
+ return function (character) {
+ if (character === "y") {
+ return callback([makeColumn("parma").addBelow("y").addBarBelow()]);
+ } else {
+ return callback([makeColumn("parma").addBarBelow()])(character);
+ }
+ };
+ } else if (character === "y") { // py
+ return callback([makeColumn("parma").addBelow("y")]);
+ } else if (character === "s") { // ps
+ return function (character) {
+ if (character === "") { // ps final
+ return callback([makeColumn("parma").addFollowing("s")])(character);
+ } else { // ps medial
+ return callback([
+ makeColumn("parma"),
+ makeColumn("silme")
+ ])(character);
+ }
+ };
+ } else { // t
+ return callback([makeColumn("parma")])(character);
+ }
+ };
+ } else if (character === "c") {
+ return function (character) {
+ if (character === "c") {
+ return callback([makeColumn("calma").addBarBelow()]);
+ } else if (character === "s") {
+ return callback([makeColumn("calma").addBelow("s")]);
+ } else if (character === "h") {
+ return callback([makeColumn("harma")]);
+ } else if (character === "w") {
+ return callback([makeColumn("quesse")]);
+ } else {
+ return callback([makeColumn("calma")])(character);
+ }
+ };
+ } else if (character === "f") {
+ return callback([makeColumn("formen")]);
+ } else if (character === "v") {
+ if (options.vilya) {
+ return callback([makeColumn("wilya")]); // vilya
+ } else {
+ return callback([makeColumn("vala")]);
+ }
+ } else if (character === "w") {
+ if (options.vilya) {
+ return callback([
+ makeColumn("short-carrier").addAbove("u")
+ .addError("Before the introduction of vala, wilya was called vilya and represented the v sound. There is no tengwa to represent consonantal w.")
+ ]);
+ } else {
+ return callback([makeColumn("vala")]);
+ }
+ } else if (character === "r") {
+ return function (character) {
+ if (character === "d") { // rd
+ return callback([makeColumn("arda")]);
+ } else if (character === "h") { // hr
+ return callback([makeColumn("halla"), makeColumn("romen")]);
+ } else if (options.classical) {
+ // pre-namarie style, ore when r between vowels
+ if (
+ previous &&
+ previous.above &&
+ character !== "" &&
+ vowels.indexOf(character) !== -1
+ ) {
+ return callback([makeColumn("ore")])(character);
+ } else {
+ return callback([makeColumn("romen")])(character);
+ }
+ } else {
+ // pre-consonant and word-final
+ if (character === "" || vowels.indexOf(character) === -1) { // ore
+ return callback([makeColumn("ore")])(character);
+ } else { // romen
+ return callback([makeColumn("romen")])(character);
+ }
+ }
+ };
+ } else if (character === "l") {
+ return function (character) {
+ if (character === "l") {
+ return function (character) {
+ if (character === "y") {
+ return callback([makeColumn("lambe").addBelow("y").addBarBelow()]);
+ } else {
+ return callback([makeColumn("lambe").addBarBelow()])(character);
+ }
+ }
+ } else if (character === "y") {
+ return callback([makeColumn("lambe").addBelow("y")]);
+ } else if (character === "h") { // hl
+ return callback([makeColumn("halla"), makeColumn("lambe")]);
+ } else if (character === "d") {
+ return callback([makeColumn("alda")]);
+ } else if (character === "b") {
+ return callback([makeColumn("lambe"), makeColumn("umbar")]);
+ } else {
+ return callback([makeColumn("lambe")])(character);
+ }
+ };
+ } else if (character === "s") {
+ return function (character) {
+ if (character === "s") { // ss
+ return callback([makeColumn("esse")]);
+ } else {
+ return callback([makeColumn("silme")])(character);
+ }
+ };
+ } else if (character === "h") {
+ return function (character) {
+ if (character === "l") {
+ return callback([
+ makeColumn("halla"),
+ makeColumn("lambe")
+ ]);
+ } else if (character === "r") {
+ return callback([
+ makeColumn("halla"),
+ makeColumn("romen")
+ ]);
+ } else if (character === "w") {
+ return callback([makeColumn("hwesta")]);
+ } else if (character === "t") {
+ return callback([makeColumn("harma")]);
+ } else if (character === "y") {
+ if (options.classical && !options.aha) { // initial
+ return callback([makeColumn("hyarmen")]);
+ } else { // post-aha, through to the third-age
+ return callback([makeColumn("hyarmen").addBelow("y")]);
+ }
+ } else if (!previous) { // initial
+ if (options.classical && !options.aha) {
+ return callback([makeColumn("halla")])(character);
+ } else { // post-aha
+ return callback([makeColumn("harma")])(character);
+ }
+ } else { // medial
+ if (options.classical && !options.aha) { // initial
+ return callback([makeColumn("harma")])(character);
+ } else if (options.classical) { // post-aha
+ return callback([makeColumn("hyarmen")])(character);
+ } else { // namarie, third-age
+ return callback([makeColumn("harma")])(character);
+ }
+ }
+ };
+ } else if (character === "d") {
+ return callback([makeColumn("ando").addError("D cannot appear except after N, L, or R")]);
+ } else if (character === "b") {
+ return callback([makeColumn("umbar").addError("B cannot appear except after M or L")]);
+ } else if (character === "g") {
+ return callback([makeColumn("anga").addError("G cannot appear except after N or Ñ")]);
+ } else if (character === "j") {
+ return callback([makeColumn().addError("J cannot be transcribed in Classical Mode")]);
+ } else {
+ return callback([])(character);
+ }
+ };
+}
+
+function parseTehta(callback, options, previous) {
+ return function (character) {
+ if (character === "a") {
+ return function (character) {
+ if (character === "i") {
+ return callback([previous, makeColumn("yanta", "a")]);
+ } else if (character === "u") {
+ return callback([previous, makeColumn("ure", "a")]);
+ } else if (previous && previous.canAddAbove()) {
+ previous.addAbove("a");
+ return callback([previous])(character);
+ } else {
+ return callback([previous, makeColumn("short-carrier", "a")])(character);
+ }
+ };
+ } else if (character === "e") {
+ return function (character) {
+ if (character === "u") {
+ return callback([previous, makeColumn("ure", "e")]);
+ } else if (previous && previous.canAddAbove()) {
+ previous.addAbove("e");
+ return callback([previous])(character);
+ } else {
+ return callback([previous, makeColumn("short-carrier", "e")])(character);
+ }
+ };
+ } else if (character === "i") {
+ return function (character) {
+ if (character === "u") {
+ if (options.iuRising) {
+ return callback([previous, makeColumn("anna", "u").addBelow("y")]);
+ } else {
+ return callback([previous, makeColumn("ure", "i")]);
+ }
+ } else if (previous && previous.canAddAbove()) {
+ previous.addAbove("i");
+ return callback([previous])(character);
+ } else {
+ return callback([previous, makeColumn("short-carrier", "i")])(character);
+ }
+ };
+ } else if (character === "o") {
+ return function (character) {
+ if (character === "i") {
+ return callback([previous, makeColumn("yanta", "o")]);
+ } else if (previous && previous.canAddAbove()) {
+ previous.addAbove("o");
+ return callback([previous])(character);
+ } else {
+ return callback([previous, makeColumn("short-carrier", "o")])(character);
+ }
+ };
+ } else if (character === "u") {
+ return function (character) {
+ if (character === "i") {
+ return callback([previous, makeColumn("yanta", "u")]);
+ } else if (previous && previous.canAddAbove()) {
+ previous.addAbove("u");
+ return callback([previous])(character);
+ } else {
+ return callback([previous, makeColumn("short-carrier", "u")])(character);
+ }
+ };
+ } else if (character === "y") {
+ if (previous && previous.canAddBelow()) {
+ return callback([previous.addBelow("y")]);
+ } else {
+ var next = makeColumn("anna").addBelow("y");
+ return parseTehta(function (moreColumns) {
+ return callback([previous].concat(moreColumns));
+ }, options, next);
+ }
+ } else if (character === "á") {
+ return callback([previous, makeColumn("long-carrier", "a")]);
+ } else if (character === "é") {
+ return callback([previous, makeColumn("long-carrier", "e")]);
+ } else if (character === "í") {
+ return callback([previous, makeColumn("long-carrier", "i")]);
+ } else if (character === "ó") {
+ if (previous && previous.canAddAbove()) {
+ previous.addAbove('ó');
+ return callback([previous]);
+ } else {
+ return callback([previous, makeColumn("long-carrier", "o")]);
+ }
+ } else if (character === "ú") {
+ if (previous && previous.canAddAbove()) {
+ previous.addAbove('ú');
+ return callback([previous]);
+ } else {
+ return callback([previous, makeColumn("long-carrier", "u")]);
+ }
+ } else {
+ return callback([previous])(character);
+ }
+ };
+}
+
+// generic parser utilities
+
+function expect(expected, callback) {
+ var displayExpected = expected ? JSON.stringify(expected) : "end of word";
+ return function (character) {
+ if (character !== expected) {
+ var displayCharacter = character ? JSON.stringify(character) : "end of word";
+ throw new Error("Expected " + displayExpected + " but got " + displayCharacter);
+ } else {
+ return callback(expected);
+ }
+ }
+}
+
+function expectEof() {
+ return expect("", function () {
+ return function () {
+ return parseEof();
+ };
+ });
+}
+
+// Notes regarding "h":
+//
+// http://at.mansbjorkman.net/teng_quenya.htm#note_harma
+// originally:
+// h represented ach-laut and was written with harma.
+// h initial transcribed as halla
+// h medial transcribed as harma
+// hy transcribed as hyarmen
+// then harma became aha:
+// then h in initial position became a breath-h, still spelled with harma, but
+// renamed aha.
+// h initial transcribed as harma
+// h medial transcribed as hyarmen
+// hy transcribed as hyarmen with underposed y
+// then, in the third age:
+// the h in every position became a breath-h
+// except before t, where it remained pronounced as ach-laut
+// h initial ???
+// h medial transcribed as harma
+// h transcribed as halla or hyarmen in other positions (needs clarification)
+//
+// ach-laut (_ch_, /x/ phonetically, {h} by tolkien)
+// original: harma in all positions
+// altered: harma initially, halla in all other positions
+// third-age: halla in all other positions
+// hy (/ç/ phonetically)
+// original: hyarmen in all positions
+// altered: hyarmen with y below
+// third-age:
+// h (breath h)
+// original: halla in all positions
+// altered: hyarmen medially
+// third-age:
+//
+// harma:
+// original: ach-laut found in all positions
+// altered: breath h initially (renamed aha), ach-laut medial
+// third-age: ach-laut before t, breath h all other places
+// hyarmen:
+// original: represented {hy}, palatalized h, in all positions
+// altered: breath h medial, palatalized with y below
+// third-age: same
+// halla:
+// original: breath-h, presuming existed only initially
+// altered: breath h initial
+// third-age: only used for hl and hr
+//
+// hr: halla romen
+// hl: halla lambe
+// ht: harma
+// hy:
+// original: hyarmen
+// altered:
+// initial: ERROR
+// medial: hyarmen lower-y
+// third age: hyarmen lower-y
+// ch: harma
+// h initial:
+// original: halla
+// altered: XXX
+// third-age: harma
+// h medial: hyarmen
+
--- /dev/null
+
+module.exports = function makeColumn(tengwa, above, below) {
+ return new Column(tengwa, above, below);
+};
+
+var Column = function (tengwa, above, below) {
+ this.above = above;
+ this.barAbove = void 0;
+ this.tengwa = tengwa;
+ this.barBelow = void 0;
+ this.below = below;
+ this.following = void 0;
+ this.error = void 0;
+};
+
+Column.prototype.canAddAbove = function () {
+ return !this.above || (
+ (this.tengwa === "silme" || this.tengwa === "esse")
+ && !this.below
+ );
+};
+
+Column.prototype.addAbove = function (above) {
+ if (this.tengwa === "silme") {
+ this.tengwa = "silme-nuquerna";
+ }
+ if (this.tengwa === "esse") {
+ this.tengwa = "esse-nuquerna";
+ }
+ this.above = above;
+ return this;
+};
+
+Column.prototype.canAddBelow = function () {
+ return !this.below && this.tengwa !== "silme-nuquerna";
+};
+
+Column.prototype.addBelow = function (below) {
+ this.below = below;
+ return this;
+};
+
+Column.prototype.addBarAbove = function () {
+ this.barAbove = true;
+ return this;
+};
+
+Column.prototype.addBarBelow = function () {
+ this.barBelow = true;
+ return this;
+};
+
+Column.prototype.addFollowing = function (following) {
+ this.following = following;
+ return this;
+};
+
+Column.prototype.addError = function (error) {
+ this.errors = this.errors || [];
+ this.errors.push(error);
+ return this;
+};
+
font-family: tengwar;
src: url('custom-webfont.eot');
src: url('custom-webfont.eot#iefix'),
- url('custom-webfont.woff') format('woff'),
- url('custom-webfont.ttf') format('truetype'),
+ url('custom-webfont.woff') format('woff'),
+ url('custom-webfont.ttf') format('truetype'),
url('custom-webfont.svg#TengwarAnnatarItalic') format('svg');
font-weight: normal;
font-style: normal;
}
-table {
- padding: 4ex;
-}
-td {
- width: 4em;
-}
+
.tengwar {
font-family: tengwar;
font-size: 30px;
--- /dev/null
+
+var Render = require("./render");
+var makeColumn = require("./column");
+
+// TODO rewrite using the parser technique from classical.js
+
+exports.transcribe = transcribe;
+function transcribe(text) {
+ return Render.transcribe(parse(text));
+}
+
+exports.encode = encode;
+function encode(text) {
+ return Render.encode(parse(text));
+}
+
+exports.parse = parse;
+function parse(latin) {
+ latin = latin.replace(/[,:] +/g, ",");
+ return latin.split(/\n\n\n+/).map(function (section) {
+ return section.split(/\n\n/).map(function (paragraph) {
+ return paragraph.split(/\n/).map(function (line) {
+ return line.split(/\s+/).map(function (word) {
+ var columns = []
+ word.replace(/([\wáéíóúÁÉÍÓÚëËâêîôûÂÊÎÔÛ']+)|(\W+)/g, function ($, word, others) {
+ try {
+ columns.push.apply(columns, parseWord(word || others));
+ } catch (x) {
+ console.log(parseWord(word || others), word || others);
+ }
+ });
+ return columns;
+ });
+ });
+ });
+ });
+}
+
+function parseWord(latin) {
+ latin = latin
+ .toLowerCase()
+ .replace(substitutionsRe, function ($, key) {
+ return Render.decode(Mode.substitutions[key]);
+ });
+ if (Mode.words[latin])
+ return Render.decode(Mode.words[latin]);
+ var columns = [];
+ var length;
+ var first = true;
+ var maybeFinal;
+ while (latin.length) {
+ if (latin[0] != "s")
+ maybeFinal = undefined;
+ length = latin.length;
+ latin = latin
+ .replace(transcriptionsRe, function ($, vowel, tengwa, w, y, s, prime) {
+ //console.log(latin, [vowel, tengwa, w, y, s]);
+ w = w || ""; s = s || ""; y = y || "";
+ var value = Mode.transcriptions[tengwa];
+ tengwa = value.split(":")[0];
+ var tehtar = value.split(":").slice(1).join(":");
+ var voweled = value.split(":").filter(function (term) {
+ return Mode.vowelTranscriptions[term];
+ }).length;
+ if (vowel) {
+ if (!voweled) {
+ // flip if necessary
+ if (
+ Render.tehtaForTengwa(tengwa, vowel) === null &&
+ Render.tehtaForTengwa(tengwa + "-nuquerna", vowel) !== null
+ ) {
+ value = [tengwa + "-nuquerna"]
+ .concat(tehtar)
+ .concat([vowel])
+ .filter(function (part) {
+ return part;
+ }).join(":");
+ } else {
+ value += ":" + vowel;
+ }
+ } else {
+ columns.push(parseWord(vowel));
+ }
+ voweled = true;
+ }
+ if (w && !voweled) {
+ value += ":w";
+ w = "";
+ voweled = true;
+ }
+ if (y) {
+ value += ":y";
+ y = "";
+ }
+ // must go last because it has a non-zero width
+ if (s && !w) {
+ var length = prime.length;
+ var possibilities = [
+ "s",
+ "s-inverse",
+ "s-extended",
+ "s-flourish"
+ ].filter(function (tehta) {
+ return Render.tehtaForTengwa(tengwa, tehta);
+ });
+ while (possibilities.length && length) {
+ possibilities.shift();
+ length--;
+ }
+ if (possibilities.length) {
+ if (value.split(":").indexOf("quesse") >= 0) {
+ value = value + ":" + possibilities.shift();
+ s = "";
+ } else {
+ maybeFinal = value + ":" + possibilities.shift();
+ }
+ }
+ }
+ columns.push(value);
+ first = false;
+ return w + y + s;
+ });
+ if (length === latin.length) {
+ length = latin.length;
+ latin = latin.replace(vowelTranscriptionsRe, function ($, vowel) {
+ var value = Mode.vowelTranscriptions[vowel];
+ columns.push(value);
+ return "";
+ });
+ if (length === latin.length) {
+ //throw new Error("Can't transcribe " + latin.slice(1));
+ if (Mode.punctuation[latin[0]])
+ columns.push(Mode.punctuation[latin[0]]);
+ latin = latin.slice(1);
+ }
+ }
+ }
+ if (columns.length) {
+ if (maybeFinal && columns[columns.length - 1] == "silme") {
+ columns.pop();
+ columns.pop();
+ columns.push(maybeFinal);
+ }
+ columns.push(columns.pop().replace("romen", "ore"));
+ }
+ /*
+ * failed attempt to distinguish yanta spelling of consonantal i
+ * automatically in iant, iaur, and ioreth but not in galadriel,
+ * moria
+ columns = columns.map(function (part, i) {
+ if (i === columns.length - 1)
+ return part;
+ if (part !== "short-carrier:i")
+ return part;
+ if (columns[i + 1].split(":").filter(function (term) {
+ return term === "a";
+ }).length) {
+ return "yanta";
+ } else {
+ return part;
+ }
+ });
+ */
+ /*
+ // abandoned trick to replace "is" with short-carrier with s hook
+ columns = columns.map(function (part, i) {
+ //console.log(part);
+ if (part === "silme-nuquerna:i")
+ return "short-carrier:s";
+ return part;
+ });
+ */
+ return Render.decodeWord(columns.join(";"));
+}
+
+// king's letter, general use
+var Mode = {
+ "substitutions": {
+ "k": "c",
+ "x": "cs",
+ "qu": "cw",
+ "q": "cw",
+ "ë": "e",
+ "â": "á",
+ "ê": "é",
+ "î": "í",
+ "ô": "ó",
+ "û": "ú"
+ },
+ "transcriptions": {
+
+ // consonants
+ "t": "tinco",
+ "nt": "tinco:tilde-above",
+ "tt": "tinco:tilde-below",
+
+ "p": "parma",
+ "mp": "parma:tilde-above",
+ "pp": "parma:tilde-below",
+
+ "ch'": "calma", // ch is palatal fricative, as in bach
+ "nch'": "calma:tilde-above",
+
+ "c": "quesse",
+ "nc": "quesse:tilde-above",
+
+ "d": "ando",
+ "nd": "ando:tilde-above",
+ "dd": "ando:tilde-below",
+
+ "b": "umbar",
+ "mb": "umbar:tilde-above",
+ "bb": "umbar:tilde-below",
+
+ "j": "anca",
+ "nj": "anca:tilde-above",
+
+ "g": "ungwe",
+ "ng": "ungwe:tilde-above",
+ "gg": "ungwe:tilde-below",
+
+ "th": "thule",
+ "nth": "thule:tilde-above",
+
+ "f": "formen",
+ "ph": "formen",
+ "mf": "formen:tilde-above",
+ "mph": "formen:tilde-above",
+
+ "sh": "harma",
+
+ "h": "hyarmen",
+ "ch": "hwesta",
+ "hw": "hwesta-sindarinwa",
+ "wh": "hwesta-sindarinwa",
+
+ "gh": "unque",
+ "ngh": "unque:tilde-above",
+
+ "dh": "anto",
+ "ndh": "anto:tilde-above",
+
+ "v": "ampa",
+ "bh": "ampa",
+ "mv": "ampa:tilde-above",
+ "mbh": "ampa:tilde-above",
+
+ "n": "numen",
+ "nn": "numen:tilde-above",
+
+ "m": "malta",
+ "mm": "malta:tilde-above",
+
+ "ng": "nwalme",
+ "ñ": "nwalme",
+ "nwal": "nwalme:w;lambe:a",
+
+ "r": "romen",
+ "rr": "romen:tilde-below",
+ "rh": "arda",
+
+ "l": "lambe",
+ "ll": "lambe:tilde-below",
+ "lh": "alda",
+
+ "s": "silme",
+ "ss": "silme:tilde-below",
+
+ "z": "esse",
+
+ "á": "wilya:a",
+ "é": "long-carrier:e",
+ "í": "long-carrier:i",
+ "ó": "long-carrier:o",
+ "ú": "long-carrier:u",
+ "w": "vala",
+
+ "ai": "anna:a",
+ "oi": "anna:o",
+ "ui": "anna:u",
+ "au": "vala:a",
+ "eu": "vala:e",
+ "iu": "vala:i",
+ "ae": "yanta:a",
+
+ },
+ "vowelTranscriptions": {
+
+ "a": "short-carrier:a",
+ "e": "short-carrier:e",
+ "i": "short-carrier:i",
+ "o": "short-carrier:o",
+ "u": "short-carrier:u",
+
+ "á": "wilya:a",
+ "é": "long-carrier:e",
+ "í": "long-carrier:i",
+ "ó": "short-carrier:ó",
+ "ú": "short-carrier:ú",
+
+ "w": "vala",
+ "y": "short-carrier:í"
+
+ },
+
+ "words": {
+ "iant": "yanta;tinco:tilde-above:a",
+ "iaur": "yanta;vala:a;ore",
+ "baranduiniant": "umbar;romen:a;ando:tilde-above:a;anna:u;yanta;anto:tilde-above:a",
+ "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"
+ },
+
+ "punctuation": {
+ "-": "comma",
+ ",": "comma",
+ ":": "comma",
+ ";": "full-stop",
+ ".": "full-stop",
+ "!": "exclamation-point",
+ "?": "question-mark",
+ "(": "open-paren",
+ ")": "close-paren",
+ ">": "flourish-left",
+ "<": "flourish-right"
+ },
+
+ "annotations": {
+ "tinco": {"tengwa": "t"},
+ "parma": {"tengwa": "p"},
+ "calma": {"tengwa": "c"},
+ "quesse": {"tengwa": "c"},
+ "ando": {"tengwa": "d"},
+ "umbar": {"tengwa": "b"},
+ "anga": {"tengwa": "ch"},
+ "ungwe": {"tengwa": "g"},
+ "thule": {"tengwa": "th"},
+ "formen": {"tengwa": "f"},
+ "hyarmen": {"tengwa": "h"},
+ "hwesta": {"tengwa": "kh"},
+ "unque": {"tengwa": "gh"},
+ "anto": {"tengwa": "dh"},
+ "anca": {"tengwa": "j"},
+ "ampa": {"tengwa": "v"},
+ "numen": {"tengwa": "n"},
+ "malta": {"tengwa": "m"},
+ "nwalme": {"tengwa": "ñ"},
+ "romen": {"tengwa": "r"},
+ "ore": {"tengwa": "-r"},
+ "lambe": {"tengwa": "l"},
+ "silme": {"tengwa": "s"},
+ "silme-nuquerna": {"tengwa": "s"},
+ "esse": {"tengwa": "z"},
+ "esse-nuquerna": {"tengwa": "z"},
+ "harma": {"tengwa": "sh"},
+ "alda": {"tengwa": "lh"},
+ "arda": {"tengwa": "rh"},
+ "wilya": {"tengwa": "a"},
+ "vala": {"tengwa": "w"},
+ "anna": {"tengwa": "i"},
+ "vala": {"tengwa": "w"},
+ "yanta": {"tengwa": "e"},
+ "hwesta-sindarinwa": {"tengwa": "wh"},
+ "s": {"following": "s"},
+ "s-inverse": {"following": "s<sub>2</sub>"},
+ "s-extended": {"following": "s<sub>3</sub>"},
+ "s-flourish": {"following": "s<sub>4</sub>"},
+ "long-carrier": {"tengwa": "´"},
+ "short-carrier": {},
+ "tilde-above": {"above": "nmñ-"},
+ "tilde-below": {"below": "2"},
+ "a": {"tehta-above": "a"},
+ "e": {"tehta-above": "e"},
+ "i": {"tehta-above": "i"},
+ "o": {"tehta-above": "o"},
+ "u": {"tehta-above": "u"},
+ "ó": {"tehta-above": "ó"},
+ "ú": {"tehta-above": "ú"},
+ "í": {"tehta-above": "y"},
+ "y": {"tehta-below": "y"},
+ "w": {"tehta-above": "w"},
+ "full-stop": {"tengwa": "."},
+ "exclamation-point": {"tengwa": "!"},
+ "question-mark": {"tengwa": "?"},
+ "comma": {"tengwa": "-"},
+ "open-paren": {"tengwa": "("},
+ "close-paren": {"tengwa": ")"},
+ "flourish-left": {"tengwa": "“"},
+ "flourish-right": {"tengwa": "”"}
+ }
+};
+
+var transcriptionsRe = new RegExp("^([aeiouóú]?'?)(" +
+ Object.keys(Mode.transcriptions).sort(function (a, b) {
+ return b.length - a.length;
+ }).join("|") +
+")(w?)(y?)(s?)('*)", "ig");
+var vowelTranscriptionsRe = new RegExp("^(" +
+ Object.keys(Mode.vowelTranscriptions).join("|") +
+")", "ig");
+var substitutionsRe = new RegExp("(" +
+ Object.keys(Mode.substitutions).join("|") +
+")", "ig");
+
+@font-face {
+ font-family: tengwar;
+ src: url('custom-webfont.eot');
+ src: url('custom-webfont.eot#iefix'),
+ url('custom-webfont.woff') format('woff'),
+ url('custom-webfont.ttf') format('truetype'),
+ url('custom-webfont.svg#TengwarAnnatarItalic') format('svg');
+ font-weight: normal;
+ font-style: normal;
+}
+
+.tengwar {
+ font-family: tengwar;
+ font-size: 30px;
+}
+
body {
color: #ff0;
--- /dev/null
+<?php
+
+$wgHooks["ParserFirstCallInit"][] = "wfTengwarInit";
+
+function wfTengwarInit(Parser $parser) {
+ $parser->setHook("tengwar", "wfTengwar");
+ return true;
+}
+function wfTengwar($input, array $args) {
+ $mode = array_key_exists("mode", $args) ? $args["mode"] : "";
+ $encoded = array_key_exists("encoded", $args) ? $args["encoded"] : "";
+ $bindings = array_key_exists("bindings", $args) ? $args["bindings"] : "";
+ return "<span class=\"tengwar\" data-tengwar=\"" .
+ htmlspecialchars($input) .
+ "\" data-encoded=\"" .
+ htmlspecialchars($encoded) .
+ "\" data-bindings=\"" .
+ htmlspecialchars($bindings) .
+ "\" data-mode=\"" .
+ htmlspecialchars($mode) .
+ "\" ></span>";
+}
+
+?>
"url": "http://github.com/kriskowal/tengwarjs.git"
},
"dependencies": {
+ "mr": "0.0.x"
},
"devDependencies": {
}
--- /dev/null
+
+var makeColumn = require("./column");
+
+var Font = {
+ "names": [
+ ["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"],
+ ],
+ "aliases": {
+ "vilya": "wilya",
+ "aha": "harma"
+ },
+ // classical
+ "tengwar": {
+ // 1
+ "tinco": "1", // t
+ "parma": "q", // p
+ "calma": "a", // c
+ "quesse": "z", // qu
+ // 2
+ "ando" : "2", // nd
+ "umbar": "w", // mb
+ "anga" : "s", // ng
+ "ungwe": "x", // ngw
+ // 3
+ "thule" : "3", // th
+ "formen": "e", // ph / f
+ "harma" : "d", // h / ch
+ "hwesta": "c", // hw / chw
+ // 4
+ "anto" : "4", // nt
+ "ampa" : "r", // mp
+ "anca" : "f", // nc
+ "unque": "v", // nqu
+ // 5
+ "numen" : "5", // n
+ "malta" : "t", // m
+ "noldo" : "g", // ng
+ "nwalme": "b", // ngw / nw
+ // 6
+ "ore" : "6", // r
+ "vala" : "y", // v
+ "anna" : "h", // -
+ "wilya": "n", // w / v
+ // 7
+ "romen": "7", // medial r
+ "arda" : "u", // rd / rh
+ "lambe": "j", // l
+ "alda" : "m", // ld / lh
+ // 8
+ "silme": "8", // s
+ "silme-nuquerna": "i", // s
+ "esse": "k", // z
+ "esse-nuquerna": ",", // z
+ // 9
+ "hyarmen": "9", // hyarmen
+ "hwesta-sindarinwa": "o", // hwesta sindarinwa
+ "yanta": "l", // yanta
+ "ure": ".", // ure
+ // 10
+ "halla": "½", // halla
+ "short-carrier": "`",
+ "long-carrier": "~",
+ "round-carrier": "]",
+ // I
+ "tinco-extended": "!",
+ "parma-extended": "Q",
+ "calma-extended": "A",
+ "quesse-extended": "Z",
+ // punctuation
+ "comma": "=",
+ "full-stop": "-",
+ "exclamation-point": "Á",
+ "question-mark": "À",
+ "open-paren": "Œ",
+ "close-paren": "œ",
+ "flourish-left": "Ğ",
+ "flourish-right": "ğ",
+ },
+ "tehtar": {
+ "a": "#EDC",
+ "e": "$RFV",
+ "i": "%TGB",
+ "o": "^YHN",
+ "u": [
+ "&",
+ "U",
+ "J",
+ "M",
+ "Ā", // backward hooks, from the alt font to the custom font
+ "ā",
+ "Ă",
+ "ă"
+ ],
+ //"á": "",
+ "ó": [
+ "Ą",
+ "ą",
+ "Ć",
+ "ć"
+ ],
+ "ú": [
+ "Ĉ",
+ "ĉ",
+ "Ċ",
+ "ċ"
+ ],
+ "í": [
+ "Ô",
+ "Õ",
+ "Ö",
+ "×",
+ ],
+ "w": "èéêë",
+ "y": "ÌÍÎÏ´",
+ /*
+ "o-under": [
+ "ä",
+ "å", // a ring above
+ "æ",
+ "ç",
+ "|"
+ ],
+ */
+ // TODO deal with the fact that all of these
+ // should only be final (for word spacing) except
+ // for the first S-hook for "calma" and "quesse"
+ // since they appear within the tengwa
+ "s": {
+ "special": true,
+ "tinco": "+",
+ "ando": "+",
+ "numen": "+",
+ "lambe": "_",
+ "calma": "|",
+ "quesse": "|",
+ "short-carrier": "}",
+ },
+ "s-inverse": {
+ "special": true,
+ "tinco": "¡"
+ },
+ "s-extended": {
+ "special": true,
+ "tinco": "Ç"
+ },
+ "s-flourish": {
+ "special": true,
+ "tinco": "£",
+ "lambe": "¥"
+ },
+ "tilde-above": "Pp",
+ "tilde-below": [
+ ":",
+ ";",
+ "°",
+ ],
+ "tilde-high-above": ")0",
+ "tilde-far-below": "?/",
+ "bar-above": "{[",
+ "bar-below": [
+ '"',
+ "'",
+ "¸" // cedilla
+ ],
+ "bar-high-above": "ìî",
+ "bar-far-below": "íï"
+ },
+ "barsAndTildes": [
+ "tilde-above",
+ "tilde-below",
+ "tilde-high-above",
+ "tilde-far-below",
+ "bar-above",
+ "bar-below",
+ "bar-high-above",
+ "bar-high-below"
+ ],
+ "tehtaPositions": {
+ "tinco": {
+ "o": 3,
+ "w": 3,
+ "others": 2
+ },
+ "parma": {
+ "o": 3,
+ "w": 3,
+ "others": 2
+ },
+ "calma": {
+ "o": 3,
+ "w": 3,
+ "u": 3,
+ "others": 2
+ },
+ "quesse": {
+ "o": 3,
+ "w": 3,
+ "others": 2
+ },
+ "ando": {
+ "wide": true,
+ "e": 1,
+ "o": 2,
+ "ó": 1,
+ "ú": 1,
+ "others": 0
+ },
+ "umbar": {
+ "wide": true,
+ "e": 1,
+ "o": 2,
+ "ó": 1,
+ "ú": 1,
+ "others": 0
+ },
+ "anga": {
+ "wide": true,
+ "e": 1,
+ "ó": 1,
+ "ú": 1,
+ "others": 0
+ },
+ "ungwe": {
+ "wide": true,
+ "e": 1,
+ "o": 1,
+ "ó": 1,
+ "ú": 1,
+ "others": 0
+ },
+ "thule": {
+ "others": 3
+ },
+ "formen": 3,
+ "harma": {
+ "e": 0,
+ "o": 3,
+ "u": 7,
+ "ó": 2,
+ "ú": 2,
+ "w": 0,
+ "others": 1
+ },
+ "hwesta": {
+ "e": 0,
+ "o": 3,
+ "u": 7,
+ "w": 0,
+ "others": 1
+ },
+ "anto": {
+ "wide": true,
+ "ó": 1,
+ "ú": 1,
+ "others": 0
+ },
+ "ampa": {
+ "wide": true,
+ "ó": 1,
+ "ú": 1,
+ "others": 0
+ },
+ "anca": {
+ "wide": true,
+ "u": 7,
+ "ó": 1,
+ "ú": 1,
+ "others": 0
+ },
+ "unque": {
+ "wide": true,
+ "u": 7,
+ "others": 0
+ },
+ "numen": {
+ "wide": true,
+ "ó": 1,
+ "ú": 1,
+ "others": 0
+ },
+ "malta": {
+ "wide": true,
+ "ó": 1,
+ "ú": 1,
+ "others": 0
+ },
+ "noldo": {
+ "wide": true,
+ "ó": 1,
+ "ú": 1,
+ "others": 0
+ },
+ "nwalme": {
+ "wide": true,
+ "ó": 1,
+ "ú": 1,
+ "others": 0
+ },
+ "ore": {
+ "e": 3,
+ "o": 3,
+ "u": 3,
+ "ó": 3,
+ "ú": 3,
+ "others": 1
+ },
+ "vala": {
+ "e": 3,
+ "o": 3,
+ "u": 3,
+ "ó": 3,
+ "ú": 3,
+ "others": 1
+ },
+ "anna": {
+ "e": 3,
+ "o": 3,
+ "u": 3,
+ "ó": 2,
+ "ú": 2,
+ "others": 1
+ },
+ "wilya": {
+ "e": 3,
+ "o": 3,
+ "u": 3,
+ "ó": 3,
+ "ú": 3,
+ "others": 1
+ },
+ "romen": {
+ "e": 3,
+ "o": 3,
+ "u": 3,
+ "ó": 2,
+ "ú": 2,
+ "y": null,
+ "others": 1
+ },
+ "arda": {
+ "a": 1,
+ "e": 3,
+ "i": 1,
+ "o": 3,
+ "u": 3,
+ "í": 1,
+ "ó": 2,
+ "ú": 2,
+ "y": null,
+ "others": 0
+ },
+ "lambe": {
+ "wide": true,
+ "e": 1,
+ "y": 4,
+ "ó": 1,
+ "ú": 1,
+ "others": 0
+ },
+ "alda": {
+ "wide": true,
+ "others": 1
+ },
+ "silme": {
+ "y": 3,
+ "others": null
+ },
+ "silme-nuquerna": {
+ "e": 3,
+ "o": 3,
+ "u": 3,
+ "ó": 3,
+ "ú": 3,
+ "y": null,
+ "others": 1
+ },
+ "esse": {
+ "y": null,
+ "others": null
+ },
+ "esse-nuquerna": {
+ "e": 3,
+ "o": 3,
+ "u": 3,
+ "ó": 3,
+ "ú": 3,
+ "others": 1
+ },
+ "hyarmen": 3,
+ "hwesta-sindarinwa": {
+ "o": 2,
+ "u": 2,
+ "ó": 1,
+ "ú": 2,
+ "others": 0
+ },
+ "yanta": {
+ "e": 3,
+ "o": 3,
+ "u": 3,
+ "ó": 2,
+ "ú": 2,
+ "others": 1
+ },
+ "ure": {
+ "e": 3,
+ "o": 3,
+ "u": 3,
+ "ó": 3,
+ "ú": 3,
+ "others": 1
+ },
+ // should not occur:
+ "halla": {
+ "others": null
+ },
+ "short-carrier": 3,
+ "long-carrier": {
+ "y": null,
+ "others": 3
+ },
+ "round-carrier": 3,
+ "tinco-extended": 3,
+ "parma-extended": 3,
+ "calma-extended": {
+ "o": 3,
+ "u": 7,
+ "ó": 2,
+ "ú": 2,
+ "others": 1
+ },
+ "quesse-extended": {
+ "o": 0,
+ "u": 7,
+ "others": 1
+ }
+ },
+ "punctuation": {
+ "-": "comma",
+ ",": "comma",
+ ":": "comma",
+ ";": "full-stop",
+ ".": "full-stop",
+ "!": "exclamation-point",
+ "?": "question-mark",
+ "(": "open-paren",
+ ")": "close-paren",
+ ">": "flourish-left",
+ "<": "flourish-right"
+ },
+ "annotations": {
+ "tinco": {"tengwa": "t"},
+ "parma": {"tengwa": "p"},
+ "calma": {"tengwa": "c"},
+ "quesse": {"tengwa": "c"},
+ "ando": {"tengwa": "d"},
+ "umbar": {"tengwa": "b"},
+ "anga": {"tengwa": "ch"},
+ "ungwe": {"tengwa": "g"},
+ "thule": {"tengwa": "th"},
+ "formen": {"tengwa": "f"},
+ "hyarmen": {"tengwa": "h"},
+ "hwesta": {"tengwa": "kh"},
+ "unque": {"tengwa": "gh"},
+ "anto": {"tengwa": "dh"},
+ "anca": {"tengwa": "j"},
+ "ampa": {"tengwa": "v"},
+ "numen": {"tengwa": "n"},
+ "malta": {"tengwa": "m"},
+ "nwalme": {"tengwa": "ñ"},
+ "romen": {"tengwa": "r"},
+ "ore": {"tengwa": "-r"},
+ "lambe": {"tengwa": "l"},
+ "silme": {"tengwa": "s"},
+ "silme-nuquerna": {"tengwa": "s"},
+ "esse": {"tengwa": "z"},
+ "esse-nuquerna": {"tengwa": "z"},
+ "harma": {"tengwa": "sh"},
+ "alda": {"tengwa": "lh"},
+ "arda": {"tengwa": "rh"},
+ "wilya": {"tengwa": "a"},
+ "vala": {"tengwa": "w"},
+ "anna": {"tengwa": "i"},
+ "vala": {"tengwa": "w"},
+ "yanta": {"tengwa": "e"},
+ "hwesta-sindarinwa": {"tengwa": "wh"},
+ "s": {"following": "s"},
+ "s-inverse": {"following": "s<sub>2</sub>"},
+ "s-extended": {"following": "s<sub>3</sub>"},
+ "s-flourish": {"following": "s<sub>4</sub>"},
+ "long-carrier": {"tengwa": "´"},
+ "short-carrier": {},
+ "tilde-above": {"above": "nmñ-"},
+ "tilde-below": {"below": "2"},
+ "a": {"tehta-above": "a"},
+ "e": {"tehta-above": "e"},
+ "i": {"tehta-above": "i"},
+ "o": {"tehta-above": "o"},
+ "u": {"tehta-above": "u"},
+ "ó": {"tehta-above": "ó"},
+ "ú": {"tehta-above": "ú"},
+ "í": {"tehta-above": "y"},
+ "y": {"tehta-below": "y"},
+ "w": {"tehta-above": "w"},
+ "full-stop": {"tengwa": "."},
+ "exclamation-point": {"tengwa": "!"},
+ "question-mark": {"tengwa": "?"},
+ "comma": {"tengwa": "-"},
+ "open-paren": {"tengwa": "("},
+ "close-paren": {"tengwa": ")"},
+ "flourish-left": {"tengwa": "“"},
+ "flourish-right": {"tengwa": "”"}
+ }
+};
+
+exports.encode = encode;
+function encode(sections) {
+ return sections.map(function (section) {
+ return section.map(function (paragraph) {
+ return paragraph.map(function (line) {
+ return line.map(function (word) {
+ return word.map(function (column) {
+ var parts = [];
+ if (column.below)
+ parts.push(column.below);
+ if (column.above)
+ parts.push(column.above);
+ if (column.barAbove)
+ parts.push("bar-above");
+ if (column.barBelow)
+ parts.push("bar-below");
+ if (column.following)
+ parts.push(column.following);
+ if (parts.length) {
+ return column.tengwa + ":" + parts.join(",");
+ } else {
+ return column.tengwa;
+ }
+ }).join(";");
+ }).join(" ");;
+ }).join("\n");
+ }).join("\n\n");
+ }).join("\n\n\n");
+}
+
+exports.decode = decode;
+function decode(encoding) {
+ return encoding.split("\n\n\n").map(function (section) {
+ return section.split("\n\n").map(function (paragraph) {
+ return paragraph.split("\n").map(function (line) {
+ return line.split(" ").map(decodeWord);
+ });
+ });
+ });
+}
+
+exports.decodeWord = decodeWord;
+function decodeWord(word) {
+ return word.split(";").map(function (column) {
+ var parts = column.split(":");
+ var tengwa = parts.shift();
+ var tehtar = parts.length ? parts.shift().split(",") : [];
+ var result = makeColumn(tengwa);
+ tehtar.forEach(function (tehta) {
+ if (tehta === "bar-above") {
+ result.addBarAbove();
+ } else if (tehta === "bar-below") {
+ result.addBarBelow();
+ } else if (tehta === "y") {
+ result.addBelow("y");
+ } else if (
+ tehta === "s" ||
+ tehta === "s-inverse" ||
+ tehta === "s-extended" ||
+ tehta === "s-flourish"
+ ) {
+ if (
+ tehta === "s" &&
+ (tengwa === "calma" || tengwa === "quesse")
+ ) {
+ result.addBelow(tehta);
+ } else {
+ result.addFollowing(tehta);
+ }
+ } else {
+ result.addAbove(tehta);
+ }
+ });
+ return result;
+ });
+}
+
+exports.transcribe = transcribe;
+function transcribe(sections) {
+ return sections.map(function (section) {
+ return section.map(function (paragraph) {
+ return paragraph.map(function (line) {
+ return line.map(function (word) {
+ return word.map(function (column) {
+ var tengwa = column.tengwa || "anna";
+ var tehtar = [];
+ if (column.above) tehtar.push(column.above);
+ if (column.below) tehtar.push(column.below);
+ if (column.barBelow) tehtar.push("bar-below");
+ if (column.barAbove) tehtar.push("bar-above");
+ if (column.following) tehtar.push(column.following);
+ var html = Font.tengwar[tengwa] + tehtar.map(function (tehta) {
+ return tehtaForTengwa(tengwa, tehta);
+ }).join("");
+ if (column.errors) {
+ html = "<abbr class=\"error\" title=\"" + column.errors.join("\n").replace(/"/g, """) + "\">" + html + "</abbr>";
+ }
+ return html;
+ }).join("");
+ }).join(" ");;
+ }).join("\n");
+ }).join("\n\n");
+ }).join("\n\n\n");
+}
+
+exports.tehtaForTengwa = tehtaForTengwa;
+function tehtaForTengwa(tengwa, tehta) {
+ var tehtaKey = tehtaKeyForTengwa(tengwa, tehta);
+ if (tehtaKey === null)
+ return null;
+ return (
+ Font.tehtar[tehta][tengwa] ||
+ Font.tehtar[tehta][tehtaKey] ||
+ ""
+ );
+}
+
+function tehtaKeyForTengwa(tengwa, tehta) {
+ var positions = Font.tehtaPositions;
+ if (!Font.tehtar[tehta])
+ throw new Error("No tehta for: " + JSON.stringify(tehta));
+ if (Font.tehtar[tehta].special && !Font.tehtar[tehta][tengwa])
+ return null;
+ if (Font.barsAndTildes.indexOf(tehta) >= 0) {
+ if (["lambe", "alda"].indexOf(tengwa) >= 0 && Font.tehtar[tehta].length >= 2)
+ return 2;
+ return positions[tengwa].wide ? 0 : 1;
+ } else if (positions[tengwa] !== undefined) {
+ if (positions[tengwa][tehta] !== undefined) {
+ return positions[tengwa][tehta];
+ } else if (positions[tengwa].others !== undefined) {
+ return positions[tengwa].others;
+ } else {
+ return positions[tengwa];
+ }
+ }
+ return 0;
+}
+
--- /dev/null
+
+@font-face {
+ font-family: tengwar;
+ src: url('custom-webfont.eot');
+ src: url('custom-webfont.eot#iefix'),
+ url('custom-webfont.woff') format('woff'),
+ url('custom-webfont.ttf') format('truetype'),
+ url('custom-webfont.svg#TengwarAnnatarItalic') format('svg');
+ font-weight: normal;
+ font-style: normal;
+}
+
+.tengwar {
+ font-family: tengwar;
+ font-size: 30px;
+}
+
+body {
+ color: #ff0;
+
+ background-repeat: no-repeat;
+ background: #fefcea; /* Old browsers */
+ background: -moz-linear-gradient(top, #fefcea 0%, #f9e63b 7%, #e0a928 53%, #d69a2c 94%, #c97e16 99%); /* FF3.6+ */
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#fefcea), color-stop(7%,#f9e63b), color-stop(53%,#e0a928), color-stop(94%,#d69a2c), color-stop(99%,#c97e16)); /* Chrome,Safari4+ */
+ background: -webkit-linear-gradient(top, #fefcea 0%,#f9e63b 7%,#e0a928 53%,#d69a2c 94%,#c97e16 99%); /* Chrome10+,Safari5.1+ */
+ background: -o-linear-gradient(top, #fefcea 0%,#f9e63b 7%,#e0a928 53%,#d69a2c 94%,#c97e16 99%); /* Opera11.10+ */
+ background: -ms-linear-gradient(top, #fefcea 0%,#f9e63b 7%,#e0a928 53%,#d69a2c 94%,#c97e16 99%); /* IE10+ */
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#fefcea', endColorstr='#c97e16',GradientType=0 ); /* IE6-9 */
+ background: linear-gradient(top, #fefcea 0%,#f9e63b 7%,#e0a928 53%,#d69a2c 94%,#c97e16 99%); /* W3C */
+
+}
+
+body, textarea {
+ font-family: serif;
+ font-size: 25px;
+}
+
+textarea, .copy {
+ font-family: monospace;
+ color: black;
+}
+
+a {
+ color: #a00;
+}
+
+a:visited {
+ color: #c00;
+}
+
+a:hover {
+}
+
+a:focus {
+}
+
+#annotation table {
+ border-collapse: collapse;
+ margin-right: 1em;
+}
+
+#annotation td {
+ border: solid 1px;
+ width: 1em;
+ text-align: left;
+ font-size: 20px;
+ padding: 2px;
+ line-height: 100%;
+ text-shadow: 0px 0px 10px #730 ;
+ white-space: nowrap;
+ font-family: monospace;
+}
+
+#output {
+ text-align: center;
+ line-height: 150px;
+ font-size: 60px;
+ width: 100%;
+ text-shadow: 0px 0px 10px #730 ;
+}
+
+#output-box {
+ margin-bottom: 10px;
+ overflow: auto;
+}
+
+#input {
+ text-align: center;
+ width: 100%;
+}
+
+#about-box {
+ position: absolute;
+ bottom: 0;
+ right: 0;
+ padding-right: 1ex;
+ padding-bottom: .5ex;
+}
+
+#mode-box {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ padding-left: 1ex;
+ padding-bottom: .5ex;
+}
+
+.hbox {
+ display: -webkit-box;
+ display: -moz-box;
+ display: box;
+ -webkit-box-orient: horizontal;
+ -moz-box-orient: horizontal;
+ box-orient: horizontal;
+ -webkit-box-align: baseline;
+ -moz-box-align: baseline;
+ box-align: baseline;
+}
+.vbox {
+ display: -webkit-box;
+ display: -moz-box;
+ display: box;
+ -webkit-box-orient: vertical;
+ -moz-box-orient: vertical;
+ box-orient: vertical;
+}
+.strut {
+ -webkit-box-flex: 0;
+ -moz-box-flex: 0;
+ box-flex: 0;
+}
+.spring {
+ -webkit-box-flex: 1;
+ -moz-box-flex: 1;
+ box-flex: 1;
+}
+
+table {
+ padding: 4ex;
+}
+
+td {
+ width: 4em;
+}
+
--- /dev/null
+<html>
+ <head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8">
+ <title>Tengwar Transcriber</title>
+ <link rel="stylesheet" type="text/css" href="test.css">
+ </head>
+ <body>
+
+ <noscript>
+ <br>This transcriber makes extensive use
+ of JavaScript and very new CSS tricks.
+ You might try using the much older
+ <a href="http://tengwar.art.pl/tengwar/ott/start.php?l=en">
+ tengwar transcriber
+ </a> if your browser does not support
+ these features.
+ </noscript>
+
+ <div class="vbox" style="height: 100%; width: 100%">
+ <div class="vbox spring" id="output-box">
+ <div class="spring"></div>
+ <div id="output" class="strut tengwar"></div>
+ <div class="spring"></div>
+ <div class="strut hbox">
+ <div class="spring"></div>
+ <div id="annotation"></div>
+ <div class="spring"></div>
+ </div>
+ </div>
+ <div class="strut hbox">
+ <div class="strut" style="width: 30ex"></div>
+ <div class="spring">
+ <textarea id="input"></textarea>
+ </div>
+ <div class="strut" style="width: 30ex"></div>
+ </div>
+ </div>
+
+ <div id="mode-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>
+ </div>
+
+ <div id="about-box">
+ <a href="about.html">about</a>
+ </div>
+
+ <script src="node_modules/mr/bootstrap.js" data-module="test" charset="utf-8"></script>
+
+ </body>
+</html>
--- /dev/null
+
+var GeneralUse = require("./general-use");
+var Classical = require("./classical");
+
+var input = document.querySelector("#input");
+var generalUse = document.querySelector("#input-general-use");
+var classical = document.querySelector("#input-classical");
+var output = document.querySelector("#output");
+
+function update() {
+ var value = input.value;
+ var mode = generalUse.checked ? GeneralUse : Classical;
+ output.innerHTML = mode.transcribe(value);
+}
+
+function onupdate(event) {
+ update();
+}
+
+input.addEventListener("keyup", onupdate);
+input.addEventListener("keydown", onupdate);
+input.select();
+
+generalUse.addEventListener("change", onupdate);
+classical.addEventListener("change", onupdate);
+
if (require.main === module)
require("test").run(exports);
+