summaryrefslogtreecommitdiff
path: root/Build/source/texk/web2c/luatexdir/lang/texlang.c
diff options
context:
space:
mode:
Diffstat (limited to 'Build/source/texk/web2c/luatexdir/lang/texlang.c')
-rw-r--r--Build/source/texk/web2c/luatexdir/lang/texlang.c825
1 files changed, 825 insertions, 0 deletions
diff --git a/Build/source/texk/web2c/luatexdir/lang/texlang.c b/Build/source/texk/web2c/luatexdir/lang/texlang.c
new file mode 100644
index 00000000000..12390ae2fcf
--- /dev/null
+++ b/Build/source/texk/web2c/luatexdir/lang/texlang.c
@@ -0,0 +1,825 @@
+/*
+Copyright (c) 2007 Taco Hoekwater <taco@latex.org>
+
+This file is part of luatex.
+
+luatex is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+luatex is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with luatex; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+This is texlang.c
+*/
+
+#include "luatex-api.h"
+#include <ptexlib.h>
+
+#include <string.h>
+
+#include "nodes.h"
+#include "hyphen.h"
+
+/* functions from the fontforge unicode library */
+
+extern unsigned int *utf82u_strcpy(unsigned int *ubuf,const char *utf8buf);
+extern unsigned int u_strlen(unsigned int *ubuf);
+extern char *utf8_idpb(char *w,unsigned int i);
+
+#define noVERBOSE
+
+#define MAX_TEX_LANGUAGES 32767
+
+static struct tex_language *tex_languages[MAX_TEX_LANGUAGES] = {NULL};
+static int next_lang_id = 0;
+
+struct tex_language *
+new_language (void) {
+ struct tex_language* lang;
+ if (next_lang_id<MAX_TEX_LANGUAGES) {
+ lang = xmalloc(sizeof(struct tex_language));
+ tex_languages[next_lang_id] = lang;
+ lang->id = next_lang_id++;
+ lang->exceptions = 0;
+ lang->patterns = NULL;
+ lang->pre_hyphen_char = '-';
+ lang->post_hyphen_char = 0;
+ return lang;
+ } else {
+ return NULL;
+ }
+}
+
+struct tex_language *
+get_language (int n) {
+ if (n>=0 && n<=MAX_TEX_LANGUAGES ) {
+ if (tex_languages[n]!=NULL) {
+ return tex_languages[n];
+ } else {
+ return new_language();
+ }
+ } else {
+ return NULL;
+ }
+}
+
+void
+set_pre_hyphen_char (integer n, integer v) {
+ struct tex_language *l = get_language((int)n);
+ l->pre_hyphen_char = (int)v;
+}
+
+void
+set_post_hyphen_char (integer n, integer v) {
+ struct tex_language *l = get_language((int)n);
+ l->post_hyphen_char = (int)v;
+}
+
+integer
+get_pre_hyphen_char (integer n) {
+ struct tex_language *l = get_language((int)n);
+ return (integer)l->pre_hyphen_char;
+}
+
+integer
+get_post_hyphen_char (integer n) {
+ struct tex_language *l = get_language((int)n);
+ return (integer)l->post_hyphen_char;
+}
+
+void
+load_patterns (struct tex_language *lang, unsigned char *buffer) {
+ if (lang==NULL)
+ return;
+ if (lang->patterns==NULL) {
+ lang->patterns = hnj_hyphen_new();
+ }
+ hnj_hyphen_load (lang->patterns,buffer);
+}
+
+void
+clear_patterns (struct tex_language *lang) {
+ if (lang==NULL)
+ return;
+ if (lang->patterns!=NULL) {
+ hnj_hyphen_clear(lang->patterns);
+ }
+}
+
+void
+load_tex_patterns(int curlang, halfword head) {
+ char *s = tokenlist_to_cstring (head,1, NULL);
+ load_patterns(get_language(curlang),(unsigned char *)s);
+}
+
+
+#define STORE_CHAR(x) { word[w] = x ; if (w<MAX_WORD_LEN) w++; }
+
+/* todo change this! */
+
+char *
+clean_hyphenation (char *buffer, char **cleaned) {
+ int items;
+ unsigned char word [MAX_WORD_LEN+1];
+ int w = 0;
+ char *s = buffer;
+ while (*s && !isspace(*s)) {
+ if (*s == '-') { /* skip */
+ } else if (*s == '=') {
+ STORE_CHAR('-');
+ } else if (*s == '{') {
+ s++;
+ items=0;
+ while (*s && *s!='}') { s++; }
+ if (*s=='}') { items++; s++; }
+ while (*s && *s!='}') { s++; }
+ if (*s=='}') { items++; s++; }
+ if (*s=='{') { s++; }
+ while (*s && *s!='}') {STORE_CHAR(*s); s++; }
+ if (*s=='}') { items++; } else { s--; }
+ if (items!=3) { /* syntax error */
+ *cleaned = NULL;
+ while (*s && !isspace(*s)) { s++; }
+ return s;
+ }
+ } else {
+ STORE_CHAR(*s);
+ }
+ s++;
+ }
+ word[w] = 0;
+ *cleaned = xstrdup((char *)word);
+ return s;
+}
+
+void
+load_hyphenation (struct tex_language *lang, unsigned char *buffer) {
+ char *s, *value, *cleaned;
+ lua_State *L = Luas[0];
+ if (lang==NULL)
+ return;
+ if (lang->exceptions==0) {
+ lua_newtable(L);
+ lang->exceptions = luaL_ref(L,LUA_REGISTRYINDEX);
+ }
+ lua_rawgeti(L, LUA_REGISTRYINDEX, lang->exceptions);
+ s = (char *)buffer;
+ while (*s) {
+ while (isspace(*s)) s++;
+ if (*s) {
+ value = s;
+ s = clean_hyphenation(s, &cleaned);
+ if (cleaned!=NULL) {
+ if ((s-value)>0) {
+ lua_pushstring(L,cleaned);
+ lua_pushlstring(L,value,(s-value));
+ lua_rawset(L,-3);
+ }
+ free(cleaned);
+ } else {
+#ifdef VERBOSE
+ fprintf(stderr,"skipping invalid hyphenation exception: %s\n",value);
+#endif
+ }
+ }
+ }
+}
+
+void
+clear_hyphenation (struct tex_language *lang) {
+ if (lang==NULL)
+ return;
+ if (lang->exceptions!=0) {
+ luaL_unref(Luas[0],LUA_REGISTRYINDEX,lang->exceptions);
+ lang->exceptions = 0;
+ }
+}
+
+
+void
+load_tex_hyphenation(int curlang, halfword head) {
+ char *s = tokenlist_to_cstring (head,1, NULL);
+ load_hyphenation(get_language(curlang),(unsigned char *)s);
+}
+
+/* TODO: clean this up. The delete_attribute_ref() statements are not very
+ nice, but needed. Also, in the post-break, it would be nicer to get the
+ attribute list from vlink(n). No rush, as it is currently not used much. */
+
+halfword insert_discretionary ( halfword t, halfword pre, halfword post, halfword replace) {
+ halfword g, n;
+ n = new_node(disc_node,syllable_disc);
+ try_couple_nodes(n,vlink(t));
+ couple_nodes(t,n);
+ for (g=pre;g!=null;g=vlink(g)) {
+ font(g)=font(replace);
+ if (node_attr(t)!=null) {
+ delete_attribute_ref(node_attr(g));
+ node_attr(g) = node_attr(t);
+ attr_list_ref(node_attr(t)) += 1;
+ }
+ }
+ for (g=post;g!=null;g =vlink(g)) {
+ font(g)=font(replace);
+ if (node_attr(t)!=null) {
+ delete_attribute_ref(node_attr(g));
+ node_attr(g) = node_attr(t);
+ attr_list_ref(node_attr(t)) += 1;
+ }
+ }
+ for (g=replace;g!=null;g =vlink(g)) {
+ if (node_attr(t)!=null) {
+ delete_attribute_ref(node_attr(g));
+ node_attr(g) = node_attr(t);
+ attr_list_ref(node_attr(t)) += 1;
+ }
+ }
+ if (node_attr(t)!=null) {
+ delete_attribute_ref(node_attr(vlink(t)));
+ node_attr(vlink(t)) = node_attr(t);
+ attr_list_ref(node_attr(t)) += 1;
+ }
+ t = vlink(t);
+ set_disc_field(pre_break(t),pre);
+ set_disc_field(post_break(t),post);
+ set_disc_field(no_break(t),replace);
+ return t;
+}
+
+halfword
+insert_syllable_discretionary ( halfword t, lang_variables *lan) {
+ halfword g, n;
+ n = new_node(disc_node,syllable_disc);
+ couple_nodes(n,vlink(t));
+ couple_nodes(t,n);
+ delete_attribute_ref(node_attr(n));
+ if (node_attr(t)!=null) {
+ node_attr(n) = node_attr(t);
+ attr_list_ref(node_attr(t))++ ;
+ } else {
+ node_attr(n) = null;
+ }
+ if (lan->pre_hyphen_char >0) {
+ g = raw_glyph_node();
+ set_to_character(g);
+ character(g)=lan->pre_hyphen_char;
+ font(g)=font(t);
+ lang_data(g)=lang_data(t);
+ if (node_attr(t)!=null) {
+ node_attr(g) = node_attr(t);
+ attr_list_ref(node_attr(t)) ++;
+ }
+ set_disc_field(pre_break(n),g);
+ }
+
+ if (lan->post_hyphen_char >0) {
+ t = vlink(n);
+ g = raw_glyph_node();
+ set_to_character(g);
+ character(g)=lan->post_hyphen_char;
+ font(g)=font(t);
+ lang_data(g)=lang_data(t);
+ if (node_attr(t)!=null) {
+ node_attr(g) = node_attr(t);
+ attr_list_ref(node_attr(t)) += 1;
+ }
+ set_disc_field(post_break(n),g);
+ }
+ return n;
+}
+
+halfword insert_word_discretionary ( halfword t, lang_variables *lan) {
+ halfword pre = null, pos = null;
+ if (lan->pre_hyphen_char >0) pre = insert_character ( null, lan->pre_hyphen_char);
+ if (lan->post_hyphen_char>0) pos = insert_character ( null, lan->post_hyphen_char);
+ return insert_discretionary ( t, pre, pos, null);
+}
+
+halfword insert_complex_discretionary ( halfword t, lang_variables *lan,
+ halfword pre, halfword pos, halfword replace) {
+ return insert_discretionary ( t, pre, pos, replace);
+}
+
+
+halfword insert_character ( halfword t, int c) {
+ halfword p;
+ p=new_node(glyph_node,0);
+ set_to_character(p);
+ character(p)=c;
+ if (t!=null) {
+ couple_nodes(t,p);
+ }
+ return p;
+}
+
+
+void
+set_disc_field (halfword f, halfword t) {
+ if (t!=null) {
+ couple_nodes(f,t);
+ tlink(f) = tail_of_list(t);
+ }
+}
+
+
+
+char *hyphenation_exception(int exceptions, char *w) {
+ char *ret = NULL;
+ lua_State *L = Luas[0];
+ lua_checkstack(L,2);
+ lua_rawgeti(L,LUA_REGISTRYINDEX,exceptions);
+ if (lua_istable(L,-1)) { /* ?? */
+ lua_pushstring(L,w); /* word table */
+ lua_rawget(L,-2);
+ if (lua_isstring(L,-1)) {
+ ret = xstrdup((char *)lua_tostring(L,-1));
+ }
+ lua_pop(L,2);
+ } else {
+ lua_pop(L,1);
+ }
+ return ret;
+}
+
+
+char *exception_strings(struct tex_language *lang) {
+ char *value;
+ int size = 0, current =0;
+ size_t l =0;
+ char *ret = NULL;
+ lua_State *L = Luas[0];
+ if (lang->exceptions==0)
+ return NULL;
+ lua_checkstack(L,2);
+ lua_rawgeti(L,LUA_REGISTRYINDEX,lang->exceptions);
+ if (lua_istable(L,-1)) {
+ /* iterate and join */
+ lua_pushnil(L); /* first key */
+ while (lua_next(L,-2) != 0) {
+ value = (char *)lua_tolstring(L, -1, &l);
+ if (current + 2 + l > size ) {
+ ret = xrealloc(ret, (1.2*size)+current+l+1024);
+ size = (1.2*size)+current+l+1024;
+ }
+ *(ret+current) = ' ';
+ strcpy(ret+current+1,value);
+ current += l+1;
+ lua_pop(L, 1);
+ }
+ }
+ return ret;
+}
+
+
+/* the sequence from |wordstart| to |r| can contain only normal characters */
+/* it could be faster to modify a halfword pointer and return an integer */
+
+halfword find_exception_part(int *j, int *uword, int len) {
+ halfword g = null, gg = null;
+ register int i = *j;
+ i++; /* this puts uword[i] on the '{' */
+ while (i<len && uword[i+1] != '}') {
+ if (g==null) {
+ gg = new_char(0,uword[i+1]);
+ g = gg;
+ } else {
+ halfword s = new_char(0,uword[i+1]);
+ couple_nodes(g,s);
+ g = vlink(g);
+ }
+ i++;
+ }
+ *j = ++i;
+ return gg;
+}
+
+int count_exception_part(int *j, int *uword, int len) {
+ int ret=0;
+ register int i = *j;
+ i++; /* this puts uword[i] on the '{' */
+ while (i<len && uword[i+1] != '}') {
+ ret++;
+ i++;
+ }
+ *j = ++i;
+ return ret;
+}
+
+
+static char *PAT_ERROR[] = {
+ "Exception discretionaries should contain three pairs of braced items.",
+ "No intervening spaces are allowed.",
+ NULL };
+
+void do_exception (halfword wordstart, halfword r, char *replacement) {
+ int i;
+ halfword t;
+ unsigned len;
+ int clang;
+ lang_variables langdata;
+ int uword[MAX_WORD_LEN+1] = {0};
+ (void)utf82u_strcpy((unsigned int *)uword,replacement);
+ len = u_strlen((unsigned int *)uword);
+ i = 0;
+ t=wordstart;
+ clang = char_lang(wordstart);
+ langdata.pre_hyphen_char = get_pre_hyphen_char(clang);
+ langdata.post_hyphen_char = get_post_hyphen_char(clang);
+
+ for (i=0;i<len;i++) {
+ if (uword[i+1] == '-') { /* a hyphen follows */
+ while (vlink(t)!=r && (type(t)!=glyph_node || !is_simple_character(t)))
+ t = vlink(t);
+ if (vlink(t)==r)
+ break;
+ insert_syllable_discretionary(t, &langdata);
+ t = vlink(t); /* skip the new disc */
+ } else if (uword[i+1] == '=') {
+ /* do nothing ? */
+ t = vlink(t);
+ } else if (uword[i+1] == '{') {
+ halfword gg, hh, replace = null;
+ int repl;
+ gg = find_exception_part(&i,uword,len);
+ if (i==len || uword[i+1] != '{') {
+ tex_error ("broken pattern 1", PAT_ERROR);
+ }
+ hh = find_exception_part(&i,uword,len);
+ if (i==len || uword[i+1] != '{') {
+ tex_error ("broken pattern 2", PAT_ERROR);
+ }
+ repl = count_exception_part(&i,uword,len);
+ if (i==len) {
+ tex_error ("broken pattern 3", PAT_ERROR);
+ }
+ /*i++; */ /* jump over the last right brace */
+ if (vlink(t)==r)
+ break;
+ if (repl>0) {
+ halfword q = t;
+ replace = vlink(q);
+ while(repl>0 && q!=null) {
+ q=vlink(q);
+ if (type(q)==glyph_node) {
+ repl--;
+ }
+ }
+ try_couple_nodes(t,vlink(q));
+ vlink(q)=null;
+ }
+ t = insert_discretionary(t,gg,hh,replace);
+ } else {
+ t = vlink(t);
+ }
+ }
+}
+
+/* This is a documentation section from the pascal web file. It is not
+true any more, but I do not have time right now to rewrite it -- Taco
+
+When the line-breaking routine is unable to find a feasible sequence of
+breakpoints, it makes a second pass over the paragraph, attempting to
+hyphenate the hyphenatable words. The goal of hyphenation is to insert
+discretionary material into the paragraph so that there are more
+potential places to break.
+
+The general rules for hyphenation are somewhat complex and technical,
+because we want to be able to hyphenate words that are preceded or
+followed by punctuation marks, and because we want the rules to work
+for languages other than English. We also must contend with the fact
+that hyphens might radically alter the ligature and kerning structure
+of a word.
+
+A sequence of characters will be considered for hyphenation only if it
+belongs to a ``potentially hyphenatable part'' of the current paragraph.
+This is a sequence of nodes $p_0p_1\ldots p_m$ where $p_0$ is a glue node,
+$p_1\ldots p_{m-1}$ are either character or ligature or whatsit or
+implicit kern nodes, and $p_m$ is a glue or penalty or insertion or adjust
+or mark or whatsit or explicit kern node. (Therefore hyphenation is
+disabled by boxes, math formulas, and discretionary nodes already inserted
+by the user.) The ligature nodes among $p_1\ldots p_{m-1}$ are effectively
+expanded into the original non-ligature characters; the kern nodes and
+whatsits are ignored. Each character |c| is now classified as either a
+nonletter (if |lc_code(c)=0|), a lowercase letter (if
+|lc_code(c)=c|), or an uppercase letter (otherwise); an uppercase letter
+is treated as if it were |lc_code(c)| for purposes of hyphenation. The
+characters generated by $p_1\ldots p_{m-1}$ may begin with nonletters; let
+$c_1$ be the first letter that is not in the middle of a ligature. Whatsit
+nodes preceding $c_1$ are ignored; a whatsit found after $c_1$ will be the
+terminating node $p_m$. All characters that do not have the same font as
+$c_1$ will be treated as nonletters. The |hyphen_char| for that font
+must be between 0 and 255, otherwise hyphenation will not be attempted.
+\TeX\ looks ahead for as many consecutive letters $c_1\ldots c_n$ as
+possible; however, |n| must be less than 64, so a character that would
+otherwise be $c_{64}$ is effectively not a letter. Furthermore $c_n$ must
+not be in the middle of a ligature. In this way we obtain a string of
+letters $c_1\ldots c_n$ that are generated by nodes $p_a\ldots p_b$, where
+|1<=a<=b+1<=m|. If |n>=l_hyf+r_hyf|, this string qualifies for hyphenation;
+however, |uc_hyph| must be positive, if $c_1$ is uppercase.
+
+The hyphenation process takes place in three stages. First, the candidate
+sequence $c_1\ldots c_n$ is found; then potential positions for hyphens
+are determined by referring to hyphenation tables; and finally, the nodes
+$p_a\ldots p_b$ are replaced by a new sequence of nodes that includes the
+discretionary breaks found.
+
+Fortunately, we do not have to do all this calculation very often, because
+of the way it has been taken out of \TeX's inner loop. For example, when
+the second edition of the author's 700-page book {\sl Seminumerical
+Algorithms} was typeset by \TeX, only about 1.2 hyphenations needed to be
+@^Knuth, Donald Ervin@>
+tried per paragraph, since the line breaking algorithm needed to use two
+passes on only about 5 per cent of the paragraphs.
+
+
+When a word been set up to contain a candidate for hyphenation,
+\TeX\ first looks to see if it is in the user's exception dictionary. If not,
+hyphens are inserted based on patterns that appear within the given word,
+using an algorithm due to Frank~M. Liang.
+@^Liang, Franklin Mark@>
+*/
+
+/*
+ * This is incompatible with TEX because the first word of a paragraph
+ * can be hyphenated, but most european users seem to agree that
+ * prohibiting hyphenation there was not a the best idea ever.
+ */
+
+halfword find_next_wordstart(halfword r) {
+ register int l;
+ register int start_ok = 1;
+ int mathlevel = 1;
+ while (r!=null) {
+ switch (type(r)) {
+ case glue_node:
+ start_ok = 1;
+ break;
+ case math_node:
+ while (mathlevel>0 ){
+ r = vlink(r);
+ if (r==null)
+ return r;
+ if (type(r)==math_node) {
+ if (subtype(r)==before) {
+ mathlevel++;
+ } else {
+ mathlevel--;
+ }
+ }
+ }
+ break;
+ case glyph_node:
+ if (start_ok &&
+ is_simple_character(r) &&
+ (l = get_lc_code(character(r)))>0 &&
+ (char_uchyph(r) || l == character(r)))
+ return r;
+ /* fall through */
+ default:
+ start_ok = 0;
+ break;
+ }
+ r = vlink(r);
+ }
+ return r;
+}
+
+int valid_wordend( halfword s) {
+ register halfword r = s;
+ register int clang = char_lang(s);
+ if (r==null)
+ return 1;
+ while ((r!=null) &&
+ ((type(r)==glyph_node && is_simple_character(r) && clang == char_lang(r)) ||
+ (type(r)==kern_node && subtype(r)==normal))) {
+ r = vlink(r);
+ }
+ if (r==null ||
+ (type(r)==glyph_node && is_simple_character(r) && clang != char_lang(r)) ||
+ type(r)==glue_node ||
+ type(r)==whatsit_node ||
+ type(r)==ins_node ||
+ type(r)==adjust_node ||
+ type(r)==penalty_node ||
+ (type(r)==kern_node && subtype(r)==explicit))
+ return 1;
+ return 0;
+}
+
+void
+hnj_hyphenation (halfword head, halfword tail) {
+ int lchar, i;
+ struct tex_language* lang;
+ lang_variables langdata;
+ char utf8word[(4*MAX_WORD_LEN)+1] = {0};
+ int wordlen = 0;
+ char *hy = utf8word;
+ char *replacement = NULL;
+ halfword s, r = head, wordstart = null, save_tail = null, left = null, right = null;
+
+ /* this first movement assures two things:
+ * a) that we won't waste lots of time on something that has been
+ * handled already (in that case, none of the glyphs match |simple_character|).
+ * b) that the first word can be hyphenated. if the movement was
+ * not explicit, then the indentation at the start of a paragraph
+ * list would make find_next_wordstart() look too far ahead.
+ */
+
+ while (r!=null && (type(r)!=glyph_node || !is_simple_character(r)))
+ r =vlink(r);
+ /* this will make |r| a glyph node with subtype_character */
+ r = find_next_wordstart(r);
+ if (r==null)
+ return;
+
+ assert (tail!=null);
+ save_tail = vlink(tail);
+ s = new_penalty(0);
+ couple_nodes(tail, s);
+
+ while (r!=null) { /* could be while(1), but let's be paranoid */
+ wordstart = r;
+ assert (is_simple_character(wordstart));
+ int clang = char_lang(wordstart);
+ int lhmin = char_lhmin(wordstart);
+ int rhmin = char_rhmin(wordstart);
+ langdata.pre_hyphen_char = get_pre_hyphen_char(clang);
+ langdata.post_hyphen_char = get_post_hyphen_char(clang);
+ while (r!=null &&
+ type(r)==glyph_node &&
+ is_simple_character(r) &&
+ clang == char_lang(r) &&
+ (lchar = get_lc_code(character(r)))>0) {
+ wordlen++;
+ hy = utf8_idpb(hy,character(r));
+ /* this should not be needed any more */
+ /*if (vlink(r)!=null) alink(vlink(r))=r;*/
+ r = vlink(r);
+ }
+ if (valid_wordend(r) && wordlen>=lhmin+rhmin && (lang=tex_languages[clang])!=NULL) {
+ *hy=0;
+ if (lang->exceptions!=0 &&
+ (replacement = hyphenation_exception(lang->exceptions,utf8word))!=NULL) {
+#ifdef VERBOSE
+ fprintf(stderr,"replacing %s (c=%d) by %s\n",utf8word,clang,replacement);
+#endif
+ do_exception(wordstart,r,replacement);
+ free(replacement);
+ } else if (lang->patterns!=NULL) {
+
+ left = wordstart;
+ for (i=lhmin;i>1;i--) {
+ left = vlink(left);
+ while (!is_simple_character(left))
+ left = vlink(left);
+ }
+ right = r;
+ for (i=rhmin;i>0;i--) {
+ right = alink(right);
+ while (!is_simple_character(right))
+ right = alink(right);
+ }
+
+#ifdef VERBOSE
+ fprintf(stderr,"hyphenate %s (c=%d,l=%d,r=%d) from %c to %c\n",utf8word,
+ clang,lhmin,rhmin,
+ character(left), character(right));
+#endif
+ (void)hnj_hyphen_hyphenate(lang->patterns,wordstart,r,wordlen,left,right, &langdata);
+ }
+ }
+ wordlen = 0;
+ hy = utf8word;
+ if (r==null)
+ break;
+ r = find_next_wordstart(r);
+ }
+ flush_node(vlink(tail));
+ vlink(tail) = save_tail;
+}
+
+
+void
+new_hyphenation (halfword head, halfword tail) {
+ register int callback_id = 0;
+ if (head==null || vlink(head)==null)
+ return;
+ fix_node_list (head); /* TODO: use couple_nodes() in append_tail()!*/
+ callback_id = callback_defined(hyphenate_callback);
+ if (callback_id>0) {
+ lua_State *L = Luas[0];
+ lua_rawgeti(L,LUA_REGISTRYINDEX,callback_callbacks_id);
+ lua_rawgeti(L,-1, callback_id);
+ if (!lua_isfunction(L,-1)) {
+ lua_pop(L,2);
+ return;
+ }
+ nodelist_to_lua(L,head);
+ nodelist_to_lua(L,tail);
+ if (lua_pcall(L,2,0,0) != 0) {
+ fprintf(stdout,"error: %s\n",lua_tostring(L,-1));
+ lua_pop(L,2);
+ lua_error(L);
+ return;
+ }
+ lua_pop(L,1);
+ } else {
+ hnj_hyphenation(head,tail);
+ }
+}
+
+/* dumping and undumping fonts */
+
+#define dump_string(a) \
+ if (a!=NULL) { \
+ x = strlen(a)+1; \
+ dump_int(x); dump_things(*a, x); \
+ } else { \
+ x = 0; dump_int(x); \
+ }
+
+
+void dump_one_language (int i) {
+ char *s = NULL;
+ unsigned x = 0;
+ struct tex_language *lang;
+ lang = tex_languages[i];
+ dump_int(lang->id);
+ dump_int(lang->pre_hyphen_char);
+ dump_int(lang->post_hyphen_char);
+ if (lang->patterns!=NULL) {
+ s = (char *)hnj_serialize(lang->patterns);
+ }
+ dump_string(s);
+ if (s!=NULL) {
+ free(s);
+ s = NULL;
+ }
+ if (lang->exceptions!=0)
+ s = exception_strings(lang);
+ dump_string(s);
+ if (s!=NULL) {
+ free(s);
+ }
+ free (lang);
+}
+
+void dump_language_data (void) {
+ int i;
+ dump_int(next_lang_id);
+ for (i=0;i<next_lang_id;i++) {
+ if (tex_languages[i]) {
+ dump_int(1);
+ dump_one_language(i);
+ } else {
+ dump_int(0);
+ }
+ }
+}
+
+
+void undump_one_language (int i) {
+ char *s = NULL;
+ unsigned x = 0;
+ struct tex_language *lang = get_language(i);
+ undump_int(x); lang->id = x;
+ undump_int(x); lang->pre_hyphen_char = x;
+ undump_int(x); lang->post_hyphen_char = x;
+ /* patterns */
+ undump_int (x);
+ if (x>0) {
+ s = xmalloc(x);
+ undump_things(*s,x);
+ load_patterns(lang,(unsigned char *)s);
+ free(s);
+ }
+ /* exceptions */
+ undump_int (x);
+ if (x>0) {
+ s = xmalloc(x);
+ undump_things(*s,x);
+ load_hyphenation(lang,(unsigned char *)s);
+ free(s);
+ }
+}
+
+void undump_language_data (void) {
+ int i;
+ unsigned x, numlangs;
+ undump_int(numlangs);
+ for (i=0;i<numlangs;i++) {
+ undump_int(x);
+ if (x==1) {
+ undump_one_language(i);
+ }
+ }
+}
+