fltk/src/fl_font_win32.cxx
Ian MacArthur 1d988db2db Under win32, text_extents() is not handling surrogate pairs either, at least on XP.
The problem seems to be in GetGlyphIndicesW() which is returning invalid indices for the surrogate pairs.
This causes subsequent measurements of the glyphs to fail, of course.

This patch does not fix the problem, it only makes sure it fails cleanly, causing a fallback to the default fl_measure like behaviour.
This is not nice, nor what I want, but at least it is consistent for now...



git-svn-id: file:///fltk/svn/fltk/branches/branch-1.3@8582 ea41ed52-d2ee-0310-a9c1-e6b18d33e121
2011-04-12 16:18:42 +00:00

423 lines
14 KiB
C++

//
// "$Id$"
//
// WIN32 font selection routines for the Fast Light Tool Kit (FLTK).
//
// Copyright 1998-2011 by Bill Spitzak and others.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
// USA.
//
// Please report all bugs and problems on the following page:
//
// http://www.fltk.org/str.php
//
#include <FL/Fl_Printer.H>
static int fl_angle_ = 0;
#ifndef FL_DOXYGEN
Fl_Font_Descriptor::Fl_Font_Descriptor(const char* name, Fl_Fontsize fsize) {
int weight = FW_NORMAL;
int italic = 0;
switch (*name++) {
case 'I': italic = 1; break;
case 'P': italic = 1;
case 'B': weight = FW_BOLD; break;
case ' ': break;
default: name--;
}
fid = CreateFont(
-fsize, // negative makes it use "char size"
0, // logical average character width
fl_angle_*10, // angle of escapement
fl_angle_*10, // base-line orientation angle
weight,
italic,
FALSE, // underline attribute flag
FALSE, // strikeout attribute flag
DEFAULT_CHARSET, // character set identifier
OUT_DEFAULT_PRECIS, // output precision
CLIP_DEFAULT_PRECIS,// clipping precision
DEFAULT_QUALITY, // output quality
DEFAULT_PITCH, // pitch and family
name // pointer to typeface name string
);
angle = fl_angle_;
if (!fl_gc) fl_GetDC(0);
SelectObject(fl_gc, fid);
GetTextMetrics(fl_gc, &metr);
// BOOL ret = GetCharWidthFloat(fl_gc, metr.tmFirstChar, metr.tmLastChar, font->width+metr.tmFirstChar);
// ...would be the right call, but is not implemented into Window95! (WinNT?)
//GetCharWidth(fl_gc, 0, 255, width);
int i;
for (i = 0; i < 64; i++) width[i] = NULL;
#if HAVE_GL
listbase = 0;
for (i = 0; i < 64; i++) glok[i] = 0;
#endif
size = fsize;
}
Fl_Font_Descriptor::~Fl_Font_Descriptor() {
#if HAVE_GL
// Delete list created by gl_draw(). This is not done by this code
// as it will link in GL unnecessarily. There should be some kind
// of "free" routine pointer, or a subclass?
// if (listbase) {
// int base = font->min_char_or_byte2;
// int size = font->max_char_or_byte2-base+1;
// int base = 0; int size = 256;
// glDeleteLists(listbase+base,size);
// }
#endif
if (this == fl_graphics_driver->font_descriptor()) fl_graphics_driver->font_descriptor(NULL);
DeleteObject(fid);
int i;
for (i = 0; i < 64; i++) free(width[i]);
}
////////////////////////////////////////////////////////////////
// WARNING: if you add to this table, you must redefine FL_FREE_FONT
// in Enumerations.H & recompile!!
static Fl_Fontdesc built_in_table[] = {
{" Arial"},
{"BArial"},
{"IArial"},
{"PArial"},
{" Courier New"},
{"BCourier New"},
{"ICourier New"},
{"PCourier New"},
{" Times New Roman"},
{"BTimes New Roman"},
{"ITimes New Roman"},
{"PTimes New Roman"},
{" Symbol"},
{" Terminal"},
{"BTerminal"},
{" Wingdings"},
};
Fl_Fontdesc* fl_fonts = built_in_table;
static Fl_Font_Descriptor* find(Fl_Font fnum, Fl_Fontsize size, int angle) {
Fl_Fontdesc* s = fl_fonts+fnum;
if (!s->name) s = fl_fonts; // use 0 if fnum undefined
Fl_Font_Descriptor* f;
for (f = s->first; f; f = f->next)
if (f->size == size && f->angle == angle) return f;
f = new Fl_Font_Descriptor(s->name, size);
f->next = s->first;
s->first = f;
return f;
}
////////////////////////////////////////////////////////////////
// Public interface:
static void fl_font(Fl_Graphics_Driver *driver, Fl_Font fnum, Fl_Fontsize size, int angle) {
if (fnum==-1) { // just make sure that we will load a new font next time
fl_angle_ = 0;
driver->Fl_Graphics_Driver::font(0, 0);
return;
}
if (fnum == driver->Fl_Graphics_Driver::font() && size == driver->size() && angle == fl_angle_) return;
fl_angle_ = angle;
driver->Fl_Graphics_Driver::font(fnum, size);
driver->font_descriptor( find(fnum, size, angle) );
}
void Fl_GDI_Graphics_Driver::font(Fl_Font fnum, Fl_Fontsize size) {
fl_font(this, fnum, size, 0);
}
int Fl_GDI_Graphics_Driver::height() {
Fl_Font_Descriptor *fl_fontsize = font_descriptor();
if (fl_fontsize) return (fl_fontsize->metr.tmAscent + fl_fontsize->metr.tmDescent);
else return -1;
}
int Fl_GDI_Graphics_Driver::descent() {
Fl_Font_Descriptor *fl_fontsize = font_descriptor();
if (fl_fontsize) return fl_fontsize->metr.tmDescent;
else return -1;
}
// Unicode string buffer
static unsigned short *wstr = NULL;
static int wstr_len = 0;
double Fl_GDI_Graphics_Driver::width(const char* c, int n) {
int i = 0;
if (!font_descriptor()) return -1.0;
double w = 0.0;
char *end = (char *)&c[n];
while (i < n) {
unsigned int ucs;
int l;
ucs = fl_utf8decode((const char*)(c + i), end, &l);
// if (l < 1) l = 1;
i += l;
if (!fl_nonspacing(ucs)) {
w += width(ucs);
}
}
return w;
}
double Fl_GDI_Graphics_Driver::width(unsigned int c) {
Fl_Font_Descriptor *fl_fontsize = font_descriptor();
unsigned int r;
SIZE s;
// Special Case Handling of Unicode points over U+FFFF
// The logic (below) computes a lookup table for char widths
// on-the-fly, but the table only covers codepoints up to
// U+FFFF, which covers the basic multilingual plane, but
// not any higher plane, or glyphs that require surrogate-pairs
// to encode them in WinXX which is UTF16.
// This code assumes that these glyphs are rarely used and simply
// measures them explicitly if they occur - Which may be slow...
if(c > 0x0000FFFF) { // UTF16 surrogate pair is needed
if (!fl_gc) { // We have no valid gc, so nothing to measure - bail out
return 0.0;
}
int cc; // cell count
char utf8[8]; // Array for UTF-8 representation of c
unsigned short ucs[4]; // Array for UTF16 representation of c
// This fl_utf8encode / fl_utf8toUtf16 dance creates a UTF16 string
// from a UCS code point.
cc = fl_utf8encode(c, utf8);
cc = fl_utf8toUtf16(utf8, cc, ucs, 4);
GetTextExtentPoint32W(fl_gc, (WCHAR*)ucs, cc, &s);
return (double)s.cx;
}
// else - this falls through to the lookup-table for glyph widths
// in the basic multilingual plane
r = (c & 0xFC00) >> 10;
if (!fl_fontsize->width[r]) {
fl_fontsize->width[r] = (int*) malloc(sizeof(int) * 0x0400);
unsigned short i = 0, ii = r * 0x400;
// The following code makes a best effort attempt to obtain a valid fl_gc.
// If no fl_gc is available at the time we call fl_width(), then we first
// try to obtain a gc from the first fltk window.
// If that is null then we attempt to obtain the gc from the current screen
// using (GetDC(NULL)).
// This should resolve STR #2086
HDC gc = fl_gc;
HWND hWnd = 0;
if (!gc) { // We have no valid gc, try and obtain one
// Use our first fltk window, or fallback to using the screen via GetDC(NULL)
hWnd = Fl::first_window() ? fl_xid(Fl::first_window()) : NULL;
gc = GetDC(hWnd);
}
if (!gc)
Fl::fatal("Invalid graphic context: fl_width() failed because no valid HDC was found!");
SelectObject(gc, fl_fontsize->fid);
for (; i < 0x400; i++) {
GetTextExtentPoint32W(gc, (WCHAR*)&ii, 1, &s);
fl_fontsize->width[r][i] = s.cx;
ii++;
}
if (gc && gc!=fl_gc) ReleaseDC(hWnd, gc);
}
return (double) fl_fontsize->width[r][c & 0x03FF];
}
/* Add function pointer to allow us to access GetGlyphIndicesW on systems that have it,
* without crashing on systems that do not. */
/* DWORD WINAPI GetGlyphIndicesW(HDC,LPCWSTR,int,LPWORD,DWORD) */
typedef DWORD (WINAPI* fl_GetGlyphIndices_func)(HDC,LPCWSTR,int,LPWORD,DWORD);
static fl_GetGlyphIndices_func fl_GetGlyphIndices = NULL; // used to hold a proc pointer for GetGlyphIndicesW
static int have_loaded_GetGlyphIndices = 0; // Set this non-zero once we have tried to load GetGlyphIndices
// Function that tries to dynamically load GetGlyphIndicesW at runtime
static void GetGlyphIndices_init() {
// Since not all versions of Windows include GetGlyphIndicesW support,
// we do a run-time check for the required function.
HMODULE hMod = GetModuleHandle("GDI32.DLL");
if (hMod) {
// check that GetGlyphIndicesW is available
fl_GetGlyphIndices = (fl_GetGlyphIndices_func)GetProcAddress(hMod, "GetGlyphIndicesW");
}
have_loaded_GetGlyphIndices = -1; // set this non-zero when we have attempted to load GetGlyphIndicesW
} // GetGlyphIndices_init function
static void on_printer_extents_update(int &dx, int &dy, int &w, int &h)
// converts text extents from device coords to logical coords
{
POINT pt[3] = { {0, 0}, {dx, dy}, {dx+w, dy+h} };
DPtoLP(fl_gc, pt, 3);
w = pt[2].x - pt[1].x;
h = pt[2].y - pt[1].y;
dx = pt[1].x - pt[0].x;
dy = pt[1].y - pt[0].y;
}
// if printer context, extents shd be converted to logical coords
#define EXTENTS_UPDATE(x,y,w,h) \
if (Fl_Surface_Device::surface()->class_name() == Fl_Printer::class_id) { on_printer_extents_update(x,y,w,h); }
static unsigned short *ext_buff = NULL; // UTF-16 converted version of input UTF-8 string
static unsigned wc_len = 0; // current string buffer dimension
static WORD *gi = NULL; // glyph indices array
// Function to determine the extent of the "inked" area of the glyphs in a string
void Fl_GDI_Graphics_Driver::text_extents(const char *c, int n, int &dx, int &dy, int &w, int &h) {
Fl_Font_Descriptor *fl_fontsize = font_descriptor();
if (!fl_fontsize) {
w = 0; h = 0;
dx = dy = 0;
return;
}
static const MAT2 matrix = { { 0, 1 }, { 0, 0 }, { 0, 0 }, { 0, 1 } };
GLYPHMETRICS metrics;
int maxw = 0, maxh = 0, dh;
int minx = 0, miny = -999999;
unsigned len = 0, idx = 0;
HWND hWnd = 0;
HDC gc = fl_gc; // local copy of current gc - make a copy in case we change it...
// Have we loaded the GetGlyphIndicesW function yet?
if (have_loaded_GetGlyphIndices == 0) {
GetGlyphIndices_init();
}
// Do we have a usable GetGlyphIndices function?
if(!fl_GetGlyphIndices) goto exit_error; // No GetGlyphIndices function, use fallback mechanism instead
// The following code makes a best effort attempt to obtain a valid fl_gc.
// See description in fl_width() above for an explanation.
if (!gc) { // We have no valid gc, try and obtain one
// Use our first fltk window, or fallback to using the screen via GetDC(NULL)
hWnd = Fl::first_window() ? fl_xid(Fl::first_window()) : NULL;
gc = GetDC(hWnd);
}
if (!gc) goto exit_error; // no valid gc, attempt to use fallback measure
// now convert the string to WCHAR and measure it
len = fl_utf8toUtf16(c, n, ext_buff, wc_len);
if(len >= wc_len) {
if(ext_buff) {delete [] ext_buff;}
if(gi) {delete [] gi;}
wc_len = len + 64;
ext_buff = new unsigned short[wc_len];
gi = new WORD[wc_len];
len = fl_utf8toUtf16(c, n, ext_buff, wc_len);
}
SelectObject(gc, fl_fontsize->fid);
if (fl_GetGlyphIndices(gc, (WCHAR*)ext_buff, len, gi, GGI_MARK_NONEXISTING_GLYPHS) == GDI_ERROR) {
// some error occured here - just return fl_measure values
goto exit_error;
}
// now we have the glyph array we measure each glyph in turn...
for(idx = 0; idx < len; idx++){
if (GetGlyphOutlineW (gc, gi[idx], GGO_METRICS | GGO_GLYPH_INDEX,
&metrics, 0, NULL, &matrix) == GDI_ERROR) {
goto exit_error;
}
maxw += metrics.gmCellIncX;
if(idx == 0) minx = metrics.gmptGlyphOrigin.x;
dh = metrics.gmBlackBoxY - metrics.gmptGlyphOrigin.y;
if(dh > maxh) maxh = dh;
if(miny < metrics.gmptGlyphOrigin.y) miny = metrics.gmptGlyphOrigin.y;
}
// for the last cell, we only want the bounding X-extent, not the glyphs increment step
maxw = maxw - metrics.gmCellIncX + metrics.gmBlackBoxX + metrics.gmptGlyphOrigin.x;
w = maxw - minx;
h = maxh + miny;
dx = minx;
dy = -miny;
EXTENTS_UPDATE(dx, dy, w, h);
return; // normal exit
exit_error:
// some error here - just return fl_measure values
w = (int)width(c, n);
h = height();
dx = 0;
dy = descent() - h;
EXTENTS_UPDATE(dx, dy, w, h);
return;
} // fl_text_extents
void Fl_GDI_Graphics_Driver::draw(const char* str, int n, int x, int y) {
COLORREF oldColor = SetTextColor(fl_gc, fl_RGB());
SelectObject(fl_gc, font_descriptor()->fid);
int wn = fl_utf8toUtf16(str, n, wstr, wstr_len);
if(wn >= wstr_len) {
wstr = (unsigned short*) realloc(wstr, sizeof(unsigned short) * (wn + 1));
wstr_len = wn + 1;
wn = fl_utf8toUtf16(str, n, wstr, wstr_len);
}
TextOutW(fl_gc, x, y, (WCHAR*)wstr, wn);
SetTextColor(fl_gc, oldColor); // restore initial state
}
void Fl_GDI_Graphics_Driver::draw(int angle, const char* str, int n, int x, int y) {
fl_font(this, Fl_Graphics_Driver::font(), size(), angle);
int wn = 0; // count of UTF16 cells to render full string
COLORREF oldColor = SetTextColor(fl_gc, fl_RGB());
SelectObject(fl_gc, font_descriptor()->fid);
wn = fl_utf8toUtf16(str, n, wstr, wstr_len);
if(wn >= wstr_len) { // Array too small
wstr = (unsigned short*) realloc(wstr, sizeof(unsigned short) * (wn + 1));
wstr_len = wn + 1;
wn = fl_utf8toUtf16(str, n, wstr, wstr_len); // respin the translation
}
TextOutW(fl_gc, x, y, (WCHAR*)wstr, wn);
SetTextColor(fl_gc, oldColor);
fl_font(this, Fl_Graphics_Driver::font(), size(), 0);
}
void Fl_GDI_Graphics_Driver::rtl_draw(const char* c, int n, int x, int y) {
int wn;
wn = fl_utf8toUtf16(c, n, wstr, wstr_len);
if(wn >= wstr_len) {
wstr = (unsigned short*) realloc(wstr, sizeof(unsigned short) * (wn + 1));
wstr_len = wn + 1;
wn = fl_utf8toUtf16(c, n, wstr, wstr_len);
}
COLORREF oldColor = SetTextColor(fl_gc, fl_RGB());
SelectObject(fl_gc, font_descriptor()->fid);
#ifdef RTL_CHAR_BY_CHAR
int i = 0;
int lx = 0;
while (i < wn) { // output char by char is very bad for Arabic but coherent with fl_width()
lx = (int) width(wstr[i]);
x -= lx;
TextOutW(fl_gc, x, y, (WCHAR*)wstr + i, 1);
if (fl_nonspacing(wstr[i])) {
x += lx;
}
i++;
}
#else
UINT old_align = SetTextAlign(fl_gc, TA_RIGHT | TA_RTLREADING);
TextOutW(fl_gc, x, y - height() + descent(), (WCHAR*)wstr, wn);
SetTextAlign(fl_gc, old_align);
#endif
SetTextColor(fl_gc, oldColor);
}
#endif
//
// End of "$Id$".
//