From 4011952cc6b338217e904e4a38d9a2414258fa36 Mon Sep 17 00:00:00 2001 From: simon Date: Sun, 27 Dec 2009 10:01:16 +0000 Subject: [PATCH] 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. git-svn-id: svn://svn.tartarus.org/sgt/puzzles@8793 cda61777-01e9-0310-a592-d414129be87e --- PuzzleApplet.java | 2 +- devel.but | 61 +++++++++++++++++++++++++++++++++++++++++++++ drawing.c | 33 +++++++++++++++++++++++++ gtk.c | 18 +++++++++++++- nestedvm.c | 11 +++++++++ nullfe.c | 2 ++ osx.m | 15 +++++++++-- ps.c | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- puzzles.h | 3 +++ windows.c | 26 +++++++++---------- 10 files changed, 227 insertions(+), 18 deletions(-) diff --git a/PuzzleApplet.java b/PuzzleApplet.java index f401ecf..b796aa4 100644 --- a/PuzzleApplet.java +++ b/PuzzleApplet.java @@ -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(); diff --git a/devel.but b/devel.but index 4783b06..c5a2c43 100644 --- 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} diff --git a/drawing.c b/drawing.c index 26df1ff..4cbb46d 100644 --- 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 --- 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) diff --git a/nestedvm.c b/nestedvm.c index 2bbf4e3..9381b6c 100644 --- a/nestedvm.c +++ b/nestedvm.c @@ -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) diff --git a/nullfe.c b/nullfe.c index 325fd5a..f80a50c 100644 --- 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 --- 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 --- 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) diff --git a/puzzles.h b/puzzles.h index bb9bc6d..a97746c 100644 --- 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); }; /* diff --git a/windows.c b/windows.c index 7fe11f3..1fb14b9 100644 --- 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) -- 2.11.0