Introduce, and implement as usefully as I can in all front ends, a
authorsimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Sun, 27 Dec 2009 10:01:16 +0000 (10:01 +0000)
committersimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Sun, 27 Dec 2009 10:01:16 +0000 (10:01 +0000)
new function in the drawing API which permits the display of text
from outside basic ASCII. A fallback mechanism is provided so that
puzzles can give a list of strings they'd like to display in order
of preference and the system will return the best one it can manage;
puzzles are required to cope with ASCII-only front ends.

git-svn-id: svn://svn.tartarus.org/sgt/puzzles@8793 cda61777-01e9-0310-a592-d414129be87e

PuzzleApplet.java
devel.but
drawing.c
gtk.c
nestedvm.c
nullfe.c
osx.m
ps.c
puzzles.h
windows.c

index f401ecf..b796aa4 100644 (file)
@@ -373,7 +373,7 @@ public class PuzzleApplet extends JApplet implements Runtime.CallJavaCB {
             case 7: // string
                 gg.setColor(colors[arg2]);
                 {
-                    String text = runtime.cstring(arg3);
+                    String text = runtime.utfstring(arg3);
                     Font ft = new Font((xarg3 & 0x10) != 0 ? "Monospaced" : "Dialog",
                             Font.PLAIN, 100);
                     int height100 = this.getFontMetrics(ft).getHeight();
index 4783b06..c5a2c43 100644 (file)
--- a/devel.but
+++ b/devel.but
@@ -1848,6 +1848,54 @@ the back end function \cw{colours()} (\k{backend-colours}).
 
 This function may be used for both drawing and printing.
 
+The character set used to encode the text passed to this function is
+specified \e{by the drawing object}, although it must be a superset
+of ASCII. If a puzzle wants to display text that is not contained in
+ASCII, it should use the \cw{text_fallback()} function
+(\k{drawing-text-fallback}) to query the drawing object for an
+appropriate representation of the characters it wants.
+
+\S{drawing-text-fallback} \cw{text_fallback()}
+
+\c char *text_fallback(drawing *dr, const char *const *strings,
+\c                     int nstrings);
+
+This function is used to request a translation of UTF-8 text into
+whatever character encoding is expected by the drawing object's
+implementation of \cw{draw_text()}.
+
+The input is a list of strings encoded in UTF-8: \cw{nstrings} gives
+the number of strings in the list, and \cw{strings[0]},
+\cw{strings[1]}, ..., \cw{strings[nstrings-1]} are the strings
+themselves.
+
+The returned string (which is dynamically allocated and must be
+freed when finished with) is derived from the first string in the
+list that the drawing object expects to be able to display reliably;
+it will consist of that string translated into the character set
+expected by \cw{draw_text()}.
+
+Drawing implementations are not required to handle anything outside
+ASCII, but are permitted to assume that \e{some} string will be
+successfully translated. So every call to this function must include
+a string somewhere in the list (presumably the last element) which
+consists of nothing but ASCII, to be used by any front end which
+cannot handle anything else.
+
+For example, if a puzzle wished to display a string including a
+multiplication sign (U+00D7 in Unicode, represented by the bytes C3
+97 in UTF-8), it might do something like this:
+
+\c static const char *const times_signs[] = { "\xC3\x97", "x" };
+\c char *times_sign = text_fallback(dr, times_signs, 2);
+\c sprintf(buffer, "%d%s%d", width, times_sign, height);
+\c draw_text(dr, x, y, font, size, align, colour, buffer);
+\c sfree(buffer);
+
+which would draw a string with a times sign in the middle on
+platforms that support it, and fall back to a simple ASCII \cq{x}
+where there was no alternative.
+
 \S{drawing-clip} \cw{clip()}
 
 \c void clip(drawing *dr, int x, int y, int w, int h);
@@ -2442,6 +2490,19 @@ Implementations of this API which do not provide printing services
 may define this function pointer to be \cw{NULL}; it will never be
 called unless printing is attempted.
 
+\S{drawingapi-text-fallback} \cw{text_fallback()}
+
+\c char *(*text_fallback)(void *handle, const char *const *strings,
+\c                        int nstrings);
+
+This function behaves exactly like the back end \cw{text_fallback()}
+function; see \k{drawing-text-fallback}.
+
+Implementations of this API which do not support any characters
+outside ASCII may define this function pointer to be \cw{NULL}, in
+which case the central code in \cw{drawing.c} will provide a default
+implementation.
+
 \H{drawingapi-frontend} The drawing API as called by the front end
 
 There are a small number of functions provided in \cw{drawing.c}
index 26df1ff..4cbb46d 100644 (file)
--- a/drawing.c
+++ b/drawing.c
@@ -127,6 +127,39 @@ void end_draw(drawing *dr)
     dr->api->end_draw(dr->handle);
 }
 
+char *text_fallback(drawing *dr, const char *const *strings, int nstrings)
+{
+    int i;
+
+    /*
+     * If the drawing implementation provides one of these, use it.
+     */
+    if (dr && dr->api->text_fallback)
+       return dr->api->text_fallback(dr->handle, strings, nstrings);
+
+    /*
+     * Otherwise, do the simple thing and just pick the first string
+     * that fits in plain ASCII. It will then need no translation
+     * out of UTF-8.
+     */
+    for (i = 0; i < nstrings; i++) {
+       const char *p;
+
+       for (p = strings[i]; *p; p++)
+           if (*p & 0x80)
+               break;
+       if (!*p)
+           return dupstr(strings[i]);
+    }
+
+    /*
+     * The caller was responsible for making sure _some_ string in
+     * the list was in plain ASCII.
+     */
+    assert(!"Should never get here");
+    return NULL;                      /* placate optimiser */
+}
+
 void status_bar(drawing *dr, char *text)
 {
     char *rewritten;
diff --git a/gtk.c b/gtk.c
index 9c19ab6..0fd351b 100644 (file)
--- a/gtk.c
+++ b/gtk.c
@@ -499,6 +499,17 @@ void gtk_end_draw(void *handle)
     }
 }
 
+#ifdef USE_PANGO
+char *gtk_text_fallback(void *handle, const char *const *strings, int nstrings)
+{
+    /*
+     * We assume Pango can cope with any UTF-8 likely to be emitted
+     * by a puzzle.
+     */
+    return dupstr(strings[0]);
+}
+#endif
+
 const struct drawing_api gtk_drawing = {
     gtk_draw_text,
     gtk_draw_rect,
@@ -516,7 +527,12 @@ const struct drawing_api gtk_drawing = {
     gtk_blitter_save,
     gtk_blitter_load,
     NULL, NULL, NULL, NULL, NULL, NULL, /* {begin,end}_{doc,page,puzzle} */
-    NULL,                             /* line_width */
+    NULL, NULL,                               /* line_width, line_dotted */
+#ifdef USE_PANGO
+    gtk_text_fallback,
+#else
+    NULL,
+#endif
 };
 
 static void destroy(GtkWidget *widget, gpointer data)
index 2bbf4e3..9381b6c 100644 (file)
@@ -167,6 +167,16 @@ void nestedvm_end_draw(void *handle)
     _call_java(4,2,0,0);
 }
 
+char *nestedvm_text_fallback(void *handle, const char *const *strings,
+                            int nstrings)
+{
+    /*
+     * We assume Java can cope with any UTF-8 likely to be emitted
+     * by a puzzle.
+     */
+    return dupstr(strings[0]);
+}
+
 const struct drawing_api nestedvm_drawing = {
     nestedvm_draw_text,
     nestedvm_draw_rect,
@@ -185,6 +195,7 @@ const struct drawing_api nestedvm_drawing = {
     nestedvm_blitter_load,
     NULL, NULL, NULL, NULL, NULL, NULL, /* {begin,end}_{doc,page,puzzle} */
     NULL, NULL,                               /* line_width, line_dotted */
+    nestedvm_text_fallback,
 };
 
 int jcallback_key_event(int x, int y, int keyval)
index 325fd5a..f80a50c 100644 (file)
--- a/nullfe.c
+++ b/nullfe.c
@@ -17,6 +17,8 @@ void draw_polygon(drawing *dr, int *coords, int npoints,
                   int fillcolour, int outlinecolour) {}
 void draw_circle(drawing *dr, int cx, int cy, int radius,
                  int fillcolour, int outlinecolour) {}
+char *text_fallback(drawing *dr, const char *const *strings, int nstrings)
+{ return dupstr(strings[0]); }
 void clip(drawing *dr, int x, int y, int w, int h) {}
 void unclip(drawing *dr) {}
 void start_draw(drawing *dr) {}
diff --git a/osx.m b/osx.m
index c59ff56..4c3b132 100644 (file)
--- a/osx.m
+++ b/osx.m
@@ -1344,7 +1344,8 @@ static void osx_draw_text(void *handle, int x, int y, int fonttype,
                          int fontsize, int align, int colour, char *text)
 {
     frontend *fe = (frontend *)handle;
-    NSString *string = [NSString stringWithCString:text];
+    NSString *string = [NSString stringWithCString:text
+                       encoding:NSUTF8StringEncoding];
     NSDictionary *attr;
     NSFont *font;
     NSSize size;
@@ -1378,6 +1379,15 @@ static void osx_draw_text(void *handle, int x, int y, int fonttype,
 
     [string drawAtPoint:point withAttributes:attr];
 }
+static char *osx_text_fallback(void *handle, const char *const *strings,
+                              int nstrings)
+{
+    /*
+     * We assume OS X can cope with any UTF-8 likely to be emitted
+     * by a puzzle.
+     */
+    return dupstr(strings[0]);
+}
 struct blitter {
     int w, h;
     int x, y;
@@ -1478,7 +1488,8 @@ const struct drawing_api osx_drawing = {
     osx_blitter_save,
     osx_blitter_load,
     NULL, NULL, NULL, NULL, NULL, NULL, /* {begin,end}_{doc,page,puzzle} */
-    NULL,                             /* line_width */
+    NULL, NULL,                               /* line_width, line_dotted */
+    osx_text_fallback,
 };
 
 void deactivate_timer(frontend *fe)
diff --git a/ps.c b/ps.c
index f6a71bb..2394cc5 100644 (file)
--- a/ps.c
+++ b/ps.c
@@ -109,7 +109,7 @@ static void ps_draw_text(void *handle, int x, int y, int fonttype,
     y = ps->ytop - y;
     ps_setcolour(ps, colour);
     ps_printf(ps, "/%s findfont %d scalefont setfont\n",
-             fonttype == FONT_FIXED ? "Courier" : "Helvetica",
+             fonttype == FONT_FIXED ? "Courier-L1" : "Helvetica-L1",
              fontsize);
     if (align & ALIGN_VCENTRE) {
        ps_printf(ps, "newpath 0 0 moveto (X) true charpath flattenpath"
@@ -242,6 +242,49 @@ static void ps_line_dotted(void *handle, int dotted)
     }
 }
 
+static char *ps_text_fallback(void *handle, const char *const *strings,
+                             int nstrings)
+{
+    /*
+     * We can handle anything in ISO 8859-1, and we'll manually
+     * translate it out of UTF-8 for the purpose.
+     */
+    int i, maxlen;
+    char *ret;
+
+    maxlen = 0;
+    for (i = 0; i < nstrings; i++) {
+       int len = strlen(strings[i]);
+       if (maxlen < len) maxlen = len;
+    }
+
+    ret = snewn(maxlen + 1, char);
+
+    for (i = 0; i < nstrings; i++) {
+       const char *p = strings[i];
+       char *q = ret;
+
+       while (*p) {
+           int c = (unsigned char)*p++;
+           if (c < 0x80) {
+               *q++ = c;              /* ASCII */
+           } else if ((c == 0xC2 || c == 0xC3) && (*p & 0xC0) == 0x80) {
+               *q++ = (c << 6) | (*p++ & 0x3F);   /* top half of 8859-1 */
+           } else {
+               break;
+           }
+       }
+
+       if (!*p) {
+           *q = '\0';
+           return ret;
+       }
+    }
+
+    assert(!"Should never reach here");
+    return NULL;
+}
+
 static void ps_begin_doc(void *handle, int pages)
 {
     psdata *ps = (psdata *)handle;
@@ -259,6 +302,34 @@ static void ps_begin_doc(void *handle, int pages)
     fputs("%%IncludeResource: font Helvetica\n", ps->fp);
     fputs("%%IncludeResource: font Courier\n", ps->fp);
     fputs("%%EndSetup\n", ps->fp);
+    fputs("%%BeginProlog\n", ps->fp);
+    /*
+     * Re-encode Helvetica and Courier into ISO-8859-1, which gives
+     * us times and divide signs - and also (according to the
+     * Language Reference Manual) a bonus in that the ASCII '-' code
+     * point now points to a minus sign instead of a hyphen.
+     */
+    fputs("/Helvetica findfont " /* get the font dictionary */
+         "dup maxlength dict dup begin " /* create and open a new dict */
+         "exch " /* move the original font to top of stack */
+         "{1 index /FID ne {def} {pop pop} ifelse} forall "
+                                      /* copy everything except FID */
+         "/Encoding ISOLatin1Encoding def "
+                             /* set the thing we actually wanted to change */
+         "/FontName /Helvetica-L1 def " /* set a new font name */
+         "FontName end exch definefont" /* and define the font */
+         "\n", ps->fp);
+    fputs("/Courier findfont " /* get the font dictionary */
+         "dup maxlength dict dup begin " /* create and open a new dict */
+         "exch " /* move the original font to top of stack */
+         "{1 index /FID ne {def} {pop pop} ifelse} forall "
+                                      /* copy everything except FID */
+         "/Encoding ISOLatin1Encoding def "
+                             /* set the thing we actually wanted to change */
+         "/FontName /Courier-L1 def " /* set a new font name */
+         "FontName end exch definefont" /* and define the font */
+         "\n", ps->fp);
+    fputs("%%EndProlog\n", ps->fp);
 }
 
 static void ps_begin_page(void *handle, int number)
@@ -333,6 +404,7 @@ static const struct drawing_api ps_drawing = {
     ps_end_doc,
     ps_line_width,
     ps_line_dotted,
+    ps_text_fallback,
 };
 
 psdata *ps_init(FILE *outfile, int colour)
index bb9bc6d..a97746c 100644 (file)
--- a/puzzles.h
+++ b/puzzles.h
@@ -189,6 +189,7 @@ void unclip(drawing *dr);
 void start_draw(drawing *dr);
 void draw_update(drawing *dr, int x, int y, int w, int h);
 void end_draw(drawing *dr);
+char *text_fallback(drawing *dr, const char *const *strings, int nstrings);
 void status_bar(drawing *dr, char *text);
 blitter *blitter_new(drawing *dr, int w, int h);
 void blitter_free(drawing *dr, blitter *bl);
@@ -516,6 +517,8 @@ struct drawing_api {
     void (*end_doc)(void *handle);
     void (*line_width)(void *handle, float width);
     void (*line_dotted)(void *handle, int dotted);
+    char *(*text_fallback)(void *handle, const char *const *strings,
+                          int nstrings);
 };
 
 /*
index 7fe11f3..1fb14b9 100644 (file)
--- a/windows.c
+++ b/windows.c
@@ -601,10 +601,8 @@ static void win_draw_text(void *handle, int x, int y, int fonttype,
        HFONT oldfont;
        TEXTMETRIC tm;
        SIZE size;
-#ifdef _WIN32_WCE
        TCHAR wText[256];
-       MultiByteToWideChar (CP_ACP, 0, text, -1, wText, 256);
-#endif
+       MultiByteToWideChar (CP_UTF8, 0, text, -1, wText, 256);
 
        oldfont = SelectObject(fe->hdc, fe->fonts[i].font);
        if (GetTextMetrics(fe->hdc, &tm)) {
@@ -613,11 +611,7 @@ static void win_draw_text(void *handle, int x, int y, int fonttype,
            else
                xy.y -= tm.tmAscent;
        }
-#ifndef _WIN32_WCE
-       if (GetTextExtentPoint32(fe->hdc, text, strlen(text), &size))
-#else
-       if (GetTextExtentPoint32(fe->hdc, wText, wcslen(wText), &size))
-#endif
+       if (GetTextExtentPoint32W(fe->hdc, wText, wcslen(wText), &size))
        {
            if (align & ALIGN_HCENTRE)
                xy.x -= size.cx / 2;
@@ -626,11 +620,7 @@ static void win_draw_text(void *handle, int x, int y, int fonttype,
        }
        SetBkMode(fe->hdc, TRANSPARENT);
        win_text_colour(fe, colour);
-#ifndef _WIN32_WCE
-       TextOut(fe->hdc, xy.x, xy.y, text, strlen(text));
-#else
-       ExtTextOut(fe->hdc, xy.x, xy.y, 0, NULL, wText, wcslen(wText), NULL);
-#endif
+       ExtTextOutW(fe->hdc, xy.x, xy.y, 0, NULL, wText, wcslen(wText), NULL);
        SelectObject(fe->hdc, oldfont);
     }
 }
@@ -956,6 +946,15 @@ static void win_end_doc(void *handle)
     }
 }
 
+char *win_text_fallback(void *handle, const char *const *strings, int nstrings)
+{
+    /*
+     * We assume Windows can cope with any UTF-8 likely to be
+     * emitted by a puzzle.
+     */
+    return dupstr(strings[0]);
+}
+
 const struct drawing_api win_drawing = {
     win_draw_text,
     win_draw_rect,
@@ -980,6 +979,7 @@ const struct drawing_api win_drawing = {
     win_end_doc,
     win_line_width,
     win_line_dotted,
+    win_text_fallback,
 };
 
 void print(frontend *fe)