shithub: puzzles

Download patch

ref: 9fbb365684ef662fc183ebd45c2eeb50f40589b5
parent: 72922b307822874862d943893b10ac75d01689d2
author: Simon Tatham <anakin@pobox.com>
date: Sun Dec 27 05:01:16 EST 2009

Introduce, and implement as usefully as I can in all front ends, a
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.

[originally from svn r8793]

--- a/PuzzleApplet.java
+++ b/PuzzleApplet.java
@@ -373,7 +373,7 @@
             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();
--- a/devel.but
+++ b/devel.but
@@ -1848,6 +1848,54 @@
 
 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);
@@ -2441,6 +2489,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
 
--- a/drawing.c
+++ b/drawing.c
@@ -127,6 +127,39 @@
     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;
--- a/gtk.c
+++ b/gtk.c
@@ -499,6 +499,17 @@
     }
 }
 
+#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 @@
     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)
--- a/nestedvm.c
+++ b/nestedvm.c
@@ -167,6 +167,16 @@
     _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 @@
     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)
--- a/nullfe.c
+++ b/nullfe.c
@@ -17,6 +17,8 @@
                   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) {}
--- a/osx.m
+++ b/osx.m
@@ -1344,7 +1344,8 @@
 			  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 @@
 
     [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 @@
     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)
--- a/ps.c
+++ b/ps.c
@@ -109,7 +109,7 @@
     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 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 @@
     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 @@
     ps_end_doc,
     ps_line_width,
     ps_line_dotted,
+    ps_text_fallback,
 };
 
 psdata *ps_init(FILE *outfile, int colour)
--- a/puzzles.h
+++ b/puzzles.h
@@ -189,6 +189,7 @@
 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 @@
     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);
 };
 
 /*
--- a/windows.c
+++ b/windows.c
@@ -601,10 +601,8 @@
 	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 @@
 	    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 @@
 	}
 	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 @@
     }
 }
 
+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 @@
     win_end_doc,
     win_line_width,
     win_line_dotted,
+    win_text_fallback,
 };
 
 void print(frontend *fe)