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)