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]
This commit is contained in:
Simon Tatham
2009-12-27 10:01:16 +00:00
parent 72922b3078
commit 9fbb365684
10 changed files with 227 additions and 18 deletions

View 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();

View File

@ -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}

View File

@ -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;

18
gtk.c
View File

@ -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)

View 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)

View File

@ -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) {}

15
osx.m
View File

@ -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)

74
ps.c
View File

@ -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)

View File

@ -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);
};
/*

View File

@ -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)