Hi.
The attached patch implements ExtTextOut() for "open" paths. It seems to be working correctly for my tests but... well, the code I copied from xrender.c is a total mess IMHO.
Calculating cell deltas and calculating the base line origin has to be done for all kind of TrueType rendering (and most of the stuff for all other text rendering too) so most of it should maybe be put into ExtTextOutW() in gdi/font.c instead of duplicating it all over the place. But I'm not sure if this would require the drv API to be changed (which looks like it's modelled after the Win32 API on purpose) or maybe tie gdi to x11drv.
Also from looking at the code it seems to me that the ExtTextOut() implementation in x11drv/text.c behaves - well - different than x11drv/xrender.c in several cases. (TA_*, rotation and break-char handling...)
I really don't like the attached code but I don't feel like messing around with it more so I'll just post it here - maybe someone else can use it as a starting-point.
-flx
? gdi32.spec.def Index: font.c =================================================================== RCS file: /home/wine/wine/dlls/gdi/font.c,v retrieving revision 1.10 diff -u -r1.10 font.c --- font.c 18 Apr 2005 10:30:56 -0000 1.10 +++ font.c 10 May 2005 23:28:05 -0000 @@ -1719,35 +1719,40 @@ { BOOL ret = FALSE; DC * dc = DC_GetDCUpdate( hdc ); - if (dc) + LPWSTR lpReorderedString = NULL; + + if (!dc) return FALSE; + + if (flags&(ETO_NUMERICSLOCAL|ETO_NUMERICSLATIN|ETO_PDY)) + FIXME("flags ETO_NUMERICSLOCAL|ETO_NUMERICSLATIN|ETO_PDY unimplemented\n"); + + if( !(flags&(ETO_GLYPH_INDEX|ETO_IGNORELANGUAGE)) && BidiAvail && count>0 ) { - if (flags&(ETO_NUMERICSLOCAL|ETO_NUMERICSLATIN|ETO_PDY)) - FIXME("flags ETO_NUMERICSLOCAL|ETO_NUMERICSLATIN|ETO_PDY unimplemented\n"); + /* The caller did not specify that language processing was already done. */ + DWORD dir = WINE_GCPW_FORCE_LTR;
- if(PATH_IsPathOpen(dc->path)) - FIXME("called on an open path\n"); - else if(dc->funcs->pExtTextOut) - { - if( !(flags&(ETO_GLYPH_INDEX|ETO_IGNORELANGUAGE)) && BidiAvail && count>0 ) - { - /* The caller did not specify that language processing was already done. - */ - LPWSTR lpReorderedString=HeapAlloc(GetProcessHeap(), 0, count*sizeof(WCHAR)); - - BIDI_Reorder( str, count, GCP_REORDER, - ((flags&ETO_RTLREADING)!=0 || (GetTextAlign(hdc)&TA_RTLREADING)!=0)? - WINE_GCPW_FORCE_RTL:WINE_GCPW_FORCE_LTR, - lpReorderedString, count, NULL ); - - ret = dc->funcs->pExtTextOut(dc->physDev,x,y,flags|ETO_IGNORELANGUAGE, - lprect,lpReorderedString,count,lpDx,dc->breakExtra); - HeapFree(GetProcessHeap(), 0, lpReorderedString); - } else - ret = dc->funcs->pExtTextOut(dc->physDev,x,y,flags,lprect,str,count, - lpDx,dc->breakExtra); - } - GDI_ReleaseObj( hdc ); + if((flags&ETO_RTLREADING)!=0 || (GetTextAlign(hdc)&TA_RTLREADING)!=0) + dir = WINE_GCPW_FORCE_RTL; + + lpReorderedString=HeapAlloc(GetProcessHeap(), 0, count*sizeof(WCHAR)); + + BIDI_Reorder( str, count, GCP_REORDER, dir, lpReorderedString, count, NULL ); + + flags |= ETO_IGNORELANGUAGE; + str = lpReorderedString; } + + if(PATH_IsPathOpen(dc->path)) + ret = PATH_ExtTextOut(dc,x,y,flags,lprect,str,count,lpDx); + else if(dc->funcs->pExtTextOut) + ret = dc->funcs->pExtTextOut(dc->physDev,x,y,flags,lprect,str,count, + lpDx,dc->breakExtra); + + if(lpReorderedString) + HeapFree(GetProcessHeap(), 0, lpReorderedString); + + GDI_ReleaseObj( hdc ); + return ret; }
Index: gdi_private.h =================================================================== RCS file: /home/wine/wine/dlls/gdi/gdi_private.h,v retrieving revision 1.25 diff -u -r1.25 gdi_private.h --- gdi_private.h 13 Apr 2005 16:11:18 -0000 1.25 +++ gdi_private.h 10 May 2005 23:28:10 -0000 @@ -405,6 +405,8 @@ extern BOOL PATH_LineTo(DC *dc, INT x, INT y); extern BOOL PATH_Rectangle(DC *dc, INT x1, INT y1, INT x2, INT y2); extern BOOL PATH_Ellipse(DC *dc, INT x1, INT y1, INT x2, INT y2); +extern BOOL PATH_ExtTextOut(DC *dc, INT x, INT y, UINT flags, const RECT *lprec, + LPCWSTR str, UINT count, const INT *lpDx); extern BOOL PATH_Arc(DC *dc, INT x1, INT y1, INT x2, INT y2, INT xStart, INT yStart, INT xEnd, INT yEnd, INT lines); extern BOOL PATH_PolyBezierTo(DC *dc, const POINT *pt, DWORD cbCount); Index: path.c =================================================================== RCS file: /home/wine/wine/dlls/gdi/path.c,v retrieving revision 1.5 diff -u -r1.5 path.c --- path.c 24 Mar 2005 21:01:38 -0000 1.5 +++ path.c 10 May 2005 23:28:13 -0000 @@ -101,6 +101,7 @@ double y, POINT *pPoint); static void PATH_NormalizePoint(FLOAT_POINT corners[], const FLOAT_POINT *pPoint, double *pX, double *pY); +static void PATH_PTFXtoPT(POINT *base_line, POINTFX *ptfx, POINT *pt); static BOOL PATH_CheckCorners(DC *dc, POINT corners[], INT x1, INT y1, INT x2, INT y2);
/* Performs a world-to-viewport transformation on the specified point (which @@ -717,6 +718,287 @@ CloseFigure(dc->hSelf) ); }
+/* PATH_ExtTextOut + * + * Should be called when a call to ExtTextOut is performed on a DC that has + * an open path. Returns TRUE if successful, else FALSE. + * + * TODO: Check if TA_* handling is correct + */ +BOOL PATH_ExtTextOut(DC *dc, INT x, INT y, UINT flags, const RECT *lprec, + LPCWSTR str, UINT count, const INT *lpDx) +{ + GdiPath *pPath = &dc->path; + HDC hdc = dc->hSelf; + TEXTMETRICW tm; + LOGFONTW lf; + POINT cell_origin, pt; + UINT align, ggo_format = GGO_BEZIER; + INT char_extra, adjust_origin = 0; + const WORD *glyphs; + double cosEsc, sinEsc; + int i, k, *deltas = NULL; + struct + { + POINT origin; + UINT width, height, length; + } baseline; + + /* Windows seems to do nothing and report success on ETO_CLIPPED */ + if(flags & ETO_CLIPPED) + return TRUE; + + /* This is also observed native behaviour */ + if(flags & ETO_OPAQUE) + PATH_Rectangle(dc, lprec->left, lprec->top, lprec->right, lprec->bottom); + + /* Now we do: + * - GetGlyphIndicesW() if !GGO_GLYPH_INDEX + * - Calculate cell deltas considering lpDx, + * GetTextExtentPointI() and breakExtra + * - Calculate width of the text to be rendered + * - Calculate cos/sin of rotation angle + * - Adjust text origin according to GetTextAlign(), + * the rotation angle and the text width + * + * FIXME: All this is also done in x11drv/xrender.c and much of + * it a third time in x11drv/text.c. And this is a mess. + */ + + GetObjectW(GetCurrentObject(hdc, OBJ_FONT), sizeof(lf), &lf); + GetTextMetricsW(hdc, &tm); + + if(flags & ETO_GLYPH_INDEX) + { + /* Nothing to do, input data is already in the right format */ + ggo_format |= GGO_GLYPH_INDEX; + glyphs = (const WORD *)str; + } + else + { + glyphs = HeapAlloc(GetProcessHeap(), 0, count * sizeof(WCHAR)); + GetGlyphIndicesW(hdc, str, count, (WORD*)glyphs, 0); + } + + char_extra = GetTextCharacterExtra(hdc); + if(char_extra || dc->breakExtra) + { + deltas = HeapAlloc(GetProcessHeap(), 0, count * sizeof(INT)); + for(i = 0; i < count; i++) + { + if(lpDx) + deltas[i] = lpDx[i] + char_extra; + else + { + SIZE sz; + GetTextExtentPointI(hdc, glyphs + i, 1, &sz); + deltas[i] = sz.cx; + } + + if(dc->breakExtra && str[i] == tm.tmBreakChar) + deltas[i] += dc->breakExtra; + } + } + else if(lpDx) + deltas = (INT*)lpDx; + + /* calculate length of the base line */ + if(deltas) + { + baseline.length = 0; + for(i = 0; i < count; i++) + baseline.length += deltas[i]; + } + else + { + SIZE sz; + GetTextExtentPointI(hdc, glyphs, count, &sz); + baseline.length = sz.cx; + } + + if(lf.lfEscapement != 0) + { + cosEsc = cos(lf.lfEscapement * M_PI / 1800); + sinEsc = sin(lf.lfEscapement * M_PI / 1800); + } + else + { + cosEsc = 1; + sinEsc = 0; + } + + pt.x = baseline.length * cosEsc; + pt.y = baseline.length * sinEsc; + LPtoDP(hdc, &pt, 1); + baseline.width = pt.x; + baseline.height = pt.y; + + /* GetGlyphOutline will rotate around the base line - we have to + calculate the base line origin from the text origin if we need + another reference point */ + + align = GetTextAlign(hdc); + + if(align & TA_UPDATECP) + FIXME("TA_UPDATECP not implemented\n"); + + baseline.origin.x = x; + baseline.origin.y = y; + LPtoDP(hdc, &baseline.origin, 1); + + switch(align & (TA_LEFT | TA_RIGHT | TA_CENTER)) + { + case TA_LEFT: + break; + + case TA_CENTER: + x -= baseline.width / 2; + y += baseline.height / 2; + break; + + case TA_RIGHT: + x -= baseline.width; + y += baseline.height; + break; + } + + if((align & TA_TOP) == TA_TOP) + adjust_origin = tm.tmAscent; + else if((align & TA_BOTTOM) == TA_BOTTOM) + adjust_origin = tm.tmDescent; + + if(adjust_origin) + { + baseline.origin.x += adjust_origin * sinEsc; + baseline.origin.y += adjust_origin * cosEsc; + + /* FIXME: when using TA_TOP or TA_BOTTOM and rotating by >0 degrees + windows shifts down the text by some px... we'll probably never + know how microsoft screwed up the stuff we did above but this + seems to come close to native behaviour: */ + + if(lf.lfEscapement) + baseline.origin.y += tm.tmHeight / 8; + } + /* -- end of code duplication */ + + cell_origin = baseline.origin; + + /* For each glyph */ + for(i = 0; i < count; i++) + { + char buf[65536], *p; + GLYPHMETRICS metrics; + MAT2 identity = { { 0, 1 }, { 0, 0 }, { 0, 0 }, { 0, 1 } }; /* no transformation */ + + DWORD n = GetGlyphOutlineW(hdc, str[i], ggo_format, &metrics, sizeof(buf), &buf, &identity); + + if(n == GDI_ERROR) + { + WARN("GetGlyphOutline() failed\n"); + return FALSE; + } + + p = buf; + + /* For each contour */ + while(p < buf + n) + { + TTPOLYGONHEADER *header = (void *)(p); + TTPOLYGONHEADER *next = (void *)(p + header->cb); + POINT start, pt; + + PATH_PTFXtoPT(&cell_origin, &header->pfxStart, &start); + PATH_AddEntry(pPath, &start, PT_MOVETO); + + p += sizeof(TTPOLYGONHEADER); + + /* For each curve */ + while(p < (char *)next) + { + TTPOLYCURVE *curve = (void *)p; + BYTE flags; + + if(curve->wType == TT_PRIM_LINE) + flags = PT_LINETO; + else if(curve->wType == TT_PRIM_CSPLINE) + flags = PT_BEZIERTO; + else + { + /* This should not happen since we passed GGO_BEZIER */ + ERR("GetGlyphOutlineW() retrieved TTPOLYCURVE type %d\n", curve->wType); + return FALSE; + } + + /* For each point */ + for(k = 0; k < curve->cpfx; k++) + { + PATH_PTFXtoPT(&cell_origin, &curve->apfx[k], &pt); + PATH_AddEntry(pPath, &pt, flags); + } + + p += sizeof(TTPOLYCURVE) + sizeof(POINTFX) * (curve->cpfx - 1); + } + + /* Close contour if necessary */ + if(pt.x != start.x || pt.y != start.y) + PATH_AddEntry(pPath, &start, PT_LINETO); + } + + if(!deltas) + { + cell_origin.x += metrics.gmCellIncX; + cell_origin.y += metrics.gmCellIncY; + } + else + { + cell_origin.x += deltas[i] * cosEsc; + cell_origin.y += deltas[i] * -sinEsc; + } + } + + if(tm.tmStruckOut || tm.tmUnderlined) + { + OUTLINETEXTMETRICW otm; + + GetOutlineTextMetricsW(hdc, sizeof(OUTLINETEXTMETRICW), &otm); + + if(tm.tmStruckOut) + { + POINT a = { sinEsc * otm.otmsStrikeoutPosition, + cosEsc * otm.otmsStrikeoutPosition }, + b = { a.x - sinEsc * otm.otmsStrikeoutSize, + a.y - cosEsc * otm.otmsStrikeoutSize }, + pts[4] = { { baseline.origin.x - a.x, baseline.origin.y - a.y }, + { baseline.origin.x - b.x, baseline.origin.y - b.y }, + { cell_origin.x - b.x, cell_origin.y - b.y }, + { cell_origin.x - a.x, cell_origin.y - a.y } }; + + PATH_Polygon(dc, pts, 4); + } + + if(tm.tmUnderlined) + { + POINT a = { sinEsc * otm.otmsUnderscorePosition, + cosEsc * otm.otmsUnderscorePosition }, + b = { a.x - sinEsc * otm.otmsUnderscoreSize, + a.y - cosEsc * otm.otmsUnderscoreSize }, + pts[4] = { { baseline.origin.x - a.x, baseline.origin.y - a.y }, + { baseline.origin.x - b.x, baseline.origin.y - b.y }, + { cell_origin.x - b.x, cell_origin.y - b.y }, + { cell_origin.x - a.x, cell_origin.y - a.y } }; + + PATH_Polygon(dc, pts, 4); + } + } + + if(deltas && !lpDx) + HeapFree(GetProcessHeap(), 0, deltas); + + return TRUE; +} + + /* PATH_Arc * * Should be called when a call to Arc is performed on a DC that has @@ -1403,6 +1685,18 @@ 2.0 - 1.0; }
+/* PATH_PTFXtoPT + * + * Convert TrueType glyph coordinates (relative to the left side of the + * glyph's base line) into absolute coordinates. + */ + +static void PATH_PTFXtoPT(POINT *base_line, POINTFX *ptfx, POINT *pt) +{ + pt->x = base_line->x + ptfx->x.value + (ptfx->x.fract >= 0x8000 ? 1 : 0); + pt->y = base_line->y - ptfx->y.value - (ptfx->y.fract >= 0x8000 ? 1 : 0); +} +
/******************************************************************* * FlattenPath [GDI32.@]