diff --git a/dlls/riched20/Makefile.in b/dlls/riched20/Makefile.in index c32328ba55e..698378f6401 100644 --- a/dlls/riched20/Makefile.in +++ b/dlls/riched20/Makefile.in @@ -1,6 +1,6 @@ MODULE = riched20.dll IMPORTLIB = riched20 -IMPORTS = uuid ole32 oleaut32 imm32 user32 gdi32 +IMPORTS = uuid usp10 ole32 oleaut32 imm32 user32 gdi32 EXTRADLLFLAGS = -Wl,--image-base,0x7ac00000 C_SRCS = \ diff --git a/dlls/riched20/editstr.h b/dlls/riched20/editstr.h index 02c509d724a..8ce56c3c712 100644 --- a/dlls/riched20/editstr.h +++ b/dlls/riched20/editstr.h @@ -47,6 +47,7 @@ #include #include "imm.h" #include +#include "usp10.h" #include "wine/debug.h" #include "wine/list.h" @@ -68,6 +69,7 @@ typedef struct tagME_Style HFONT hFont; /* cached font for the style */ TEXTMETRICW tm; /* cached font metrics for the style */ int nRefs; /* reference count */ + SCRIPT_CACHE script_cache; } ME_Style; typedef enum { @@ -134,6 +136,7 @@ typedef enum { #define MEPF_CELL 0x04 /* The paragraph is nested in a cell */ #define MEPF_ROWSTART 0x08 /* Hidden empty paragraph at the start of the row */ #define MEPF_ROWEND 0x10 /* Visible empty paragraph at the end of the row */ +#define MEPF_COMPLEX 0x20 /* Use uniscribe */ /******************************** structures *************************/ @@ -150,6 +153,15 @@ typedef struct tagME_Run int nAscent, nDescent; /* pixels above/below baseline */ POINT pt; /* relative to para's position */ REOBJECT *ole_obj; /* FIXME: should be a union with strText (at least) */ + + SCRIPT_ANALYSIS script_analysis; + int num_glyphs, max_glyphs; + WORD *glyphs; + SCRIPT_VISATTR *vis_attrs; + int *advances; + GOFFSET *offsets; + int max_clusters; + WORD *clusters; } ME_Run; typedef struct tagME_Border diff --git a/dlls/riched20/list.c b/dlls/riched20/list.c index a6b3c6569b7..10732e523dd 100644 --- a/dlls/riched20/list.c +++ b/dlls/riched20/list.c @@ -166,6 +166,8 @@ void ME_DestroyDisplayItem(ME_DisplayItem *item) if (item->type==diRun) { if (item->member.run.ole_obj) ME_DeleteReObject(item->member.run.ole_obj); + heap_free( item->member.run.glyphs ); + heap_free( item->member.run.clusters ); ME_ReleaseStyle(item->member.run.style); } FREE_OBJ(item); diff --git a/dlls/riched20/paint.c b/dlls/riched20/paint.c index e728b47432a..f89306113b5 100644 --- a/dlls/riched20/paint.c +++ b/dlls/riched20/paint.c @@ -331,7 +331,12 @@ static void draw_text( ME_Context *c, ME_Run *run, int x, int y, BOOL selected, old_text = SetTextColor( c->hDC, text_color ); if (selected) old_back = SetBkColor( c->hDC, back_color ); - ExtTextOutW( c->hDC, x, y, selected ? ETO_OPAQUE : 0, sel_rect, text, run->len, NULL ); + if (run->para->nFlags & MEPF_COMPLEX) + ScriptTextOut( c->hDC, &run->style->script_cache, x, y, selected ? ETO_OPAQUE : 0, sel_rect, + &run->script_analysis, NULL, 0, run->glyphs, run->num_glyphs, run->advances, + NULL, run->offsets ); + else + ExtTextOutW( c->hDC, x, y, selected ? ETO_OPAQUE : 0, sel_rect, text, run->len, NULL ); if (selected) SetBkColor( c->hDC, old_back ); SetTextColor( c->hDC, old_text ); diff --git a/dlls/riched20/run.c b/dlls/riched20/run.c index 204431ea08c..38d027003fb 100644 --- a/dlls/riched20/run.c +++ b/dlls/riched20/run.c @@ -295,6 +295,14 @@ ME_DisplayItem *ME_MakeRun(ME_Style *s, int nFlags) item->member.run.nCharOfs = -1; item->member.run.len = 0; item->member.run.para = NULL; + item->member.run.num_glyphs = 0; + item->member.run.max_glyphs = 0; + item->member.run.glyphs = NULL; + item->member.run.vis_attrs = NULL; + item->member.run.advances = NULL; + item->member.run.offsets = NULL; + item->member.run.max_clusters = 0; + item->member.run.clusters = NULL; ME_AddRefStyle(s); return item; } @@ -468,6 +476,18 @@ int ME_CharFromPointContext(ME_Context *c, int cx, ME_Run *run, BOOL closest, BO return 1; } + if (run->para->nFlags & MEPF_COMPLEX) + { + int cp, trailing; + if (visual_order && run->script_analysis.fRTL) cx = run->nWidth - cx - 1; + + ScriptXtoCP( cx, run->len, run->num_glyphs, run->clusters, run->vis_attrs, run->advances, &run->script_analysis, + &cp, &trailing ); + TRACE("x %d cp %d trailing %d (run width %d) rtl %d log order %d\n", cx, cp, trailing, run->nWidth, + run->script_analysis.fRTL, run->script_analysis.fLogicalOrder); + return closest ? cp + trailing : cp; + } + if (c->editor->cPasswordMask) { mask_text = ME_MakeStringR( c->editor->cPasswordMask, run->len ); @@ -543,6 +563,14 @@ int ME_PointFromCharContext(ME_Context *c, ME_Run *pRun, int nOffset, BOOL visua nOffset = 0; } + if (pRun->para->nFlags & MEPF_COMPLEX) + { + int x; + ScriptCPtoX( nOffset, FALSE, pRun->len, pRun->num_glyphs, pRun->clusters, + pRun->vis_attrs, pRun->advances, &pRun->script_analysis, &x ); + if (visual_order && pRun->script_analysis.fRTL) x = pRun->nWidth - x - 1; + return x; + } if (c->editor->cPasswordMask) { mask_text = ME_MakeStringR(c->editor->cPasswordMask, pRun->len); @@ -592,8 +620,12 @@ SIZE ME_GetRunSizeCommon(ME_Context *c, const ME_Paragraph *para, ME_Run *run, i * this is wasteful for MERF_NONTEXT runs, but that shouldn't matter * in practice */ - - if (c->editor->cPasswordMask) + + if (para->nFlags & MEPF_COMPLEX) + { + size.cx = run->nWidth; + } + else if (c->editor->cPasswordMask) { ME_String *szMasked = ME_MakeStringR(c->editor->cPasswordMask,nLen); ME_GetTextExtent(c, szMasked->szData, nLen,run->style, &size); diff --git a/dlls/riched20/style.c b/dlls/riched20/style.c index 62927149074..0817ea80004 100644 --- a/dlls/riched20/style.c +++ b/dlls/riched20/style.c @@ -149,6 +149,7 @@ ME_Style *ME_MakeStyle(CHARFORMAT2W *style) s->hFont = NULL; memset(&s->tm, 0, sizeof(s->tm)); s->tm.tmAscent = -1; + s->script_cache = NULL; all_refs++; TRACE_(richedit_style)("ME_MakeStyle %p, total refs=%d\n", s, all_refs); return s; @@ -435,6 +436,7 @@ static void ME_DestroyStyle(ME_Style *s) { DeleteObject(s->hFont); s->hFont = NULL; } + ScriptFreeCache( &s->script_cache ); FREE_OBJ(s); } diff --git a/dlls/riched20/wrap.c b/dlls/riched20/wrap.c index 73edb98ca1c..6c9f2e3d5dd 100644 --- a/dlls/riched20/wrap.c +++ b/dlls/riched20/wrap.c @@ -32,6 +32,66 @@ WINE_DEFAULT_DEBUG_CHANNEL(richedit); * - no tabs */ + +static BOOL get_run_glyph_buffers( ME_Run *run ) +{ + heap_free( run->glyphs ); + run->glyphs = heap_alloc( run->max_glyphs * (sizeof(WORD) + sizeof(SCRIPT_VISATTR) + sizeof(int) + sizeof(GOFFSET)) ); + if (!run->glyphs) return FALSE; + + run->vis_attrs = (SCRIPT_VISATTR*)((char*)run->glyphs + run->max_glyphs * sizeof(WORD)); + run->advances = (int*)((char*)run->glyphs + run->max_glyphs * (sizeof(WORD) + sizeof(SCRIPT_VISATTR))); + run->offsets = (GOFFSET*)((char*)run->glyphs + run->max_glyphs * (sizeof(WORD) + sizeof(SCRIPT_VISATTR) + sizeof(int))); + + return TRUE; +} + +static HRESULT shape_run( ME_Context *c, ME_Run *run ) +{ + HRESULT hr; + HFONT old_font; + int i; + + if (!run->glyphs) + { + run->max_glyphs = 1.5 * run->len + 16; /* This is suggested in the uniscribe documentation */ + run->max_glyphs = (run->max_glyphs + 7) & ~7; /* Keep alignment simple */ + get_run_glyph_buffers( run ); + } + + if (run->max_clusters < run->len) + { + heap_free( run->clusters ); + run->max_clusters = run->len * 2; + run->clusters = heap_alloc( run->max_clusters * sizeof(WORD) ); + } + + old_font = ME_SelectStyleFont( c, run->style ); + while (1) + { + hr = ScriptShape( c->hDC, &run->style->script_cache, get_text( run, 0 ), run->len, run->max_glyphs, + &run->script_analysis, run->glyphs, run->clusters, run->vis_attrs, &run->num_glyphs ); + if (hr != E_OUTOFMEMORY) break; + if (run->max_glyphs > 10 * run->len) break; /* something has clearly gone wrong */ + run->max_glyphs *= 2; + get_run_glyph_buffers( run ); + } + + if (SUCCEEDED(hr)) + hr = ScriptPlace( c->hDC, &run->style->script_cache, run->glyphs, run->num_glyphs, run->vis_attrs, + &run->script_analysis, run->advances, run->offsets, NULL ); + + if (SUCCEEDED(hr)) + { + for (i = 0, run->nWidth = 0; i < run->num_glyphs; i++) + run->nWidth += run->advances[i]; + } + + ME_UnselectStyleFont( c, run->style, old_font ); + + return hr; +} + /****************************************************************************** * calc_run_extent * @@ -78,7 +138,10 @@ static ME_DisplayItem *split_run_extents(ME_WrapContext *wc, ME_DisplayItem *ite ME_SplitRunSimple(editor, &cursor); run2 = &cursor.pRun->member.run; + run2->script_analysis = run->script_analysis; + shape_run( wc->context, run ); + shape_run( wc->context, run2 ); calc_run_extent(wc->context, para, wc->nRow ? wc->nLeftMargin : wc->nFirstMargin, run); run2->pt.x = run->pt.x+run->nWidth; @@ -166,6 +229,65 @@ static void ME_BeginRow(ME_WrapContext *wc) wc->pt.y++; } +static void layout_row( ME_DisplayItem *start, const ME_DisplayItem *end ) +{ + ME_DisplayItem *p; + int i, num_runs = 0; + int buf[16 * 5]; /* 5 arrays - 4 of int & 1 of BYTE, alloc space for 5 of ints */ + int *vis_to_log = buf, *log_to_vis, *widths, *pos; + BYTE *levels; + BOOL found_black = FALSE; + + for (p = end->prev; p != start->prev; p = p->prev) + { + if (p->type == diRun) + { + if (!found_black) found_black = !(p->member.run.nFlags & (MERF_WHITESPACE | MERF_ENDPARA)); + if (found_black) num_runs++; + } + } + + TRACE("%d runs\n", num_runs); + if (!num_runs) return; + + if (num_runs > sizeof(buf) / (sizeof(buf[0]) * 5)) + vis_to_log = heap_alloc( num_runs * sizeof(int) * 5 ); + + log_to_vis = vis_to_log + num_runs; + widths = vis_to_log + 2 * num_runs; + pos = vis_to_log + 3 * num_runs; + levels = (BYTE*)(vis_to_log + 4 * num_runs); + + for (i = 0, p = start; i < num_runs; p = p->next) + { + if (p->type == diRun) + { + levels[i] = p->member.run.script_analysis.s.uBidiLevel; + widths[i] = p->member.run.nWidth; + TRACE( "%d: level %d width %d\n", i, levels[i], widths[i] ); + i++; + } + } + + ScriptLayout( num_runs, levels, vis_to_log, log_to_vis ); + + pos[0] = start->member.run.para->pt.x; + for (i = 1; i < num_runs; i++) + pos[i] = pos[i - 1] + widths[ vis_to_log[ i - 1 ] ]; + + for (i = 0, p = start; i < num_runs; p = p->next) + { + if (p->type == diRun) + { + p->member.run.pt.x = pos[ log_to_vis[ i ] ]; + TRACE( "%d: x = %d\n", i, p->member.run.pt.x ); + i++; + } + } + + if (vis_to_log != buf) heap_free( vis_to_log ); +} + static void ME_InsertRowStart(ME_WrapContext *wc, const ME_DisplayItem *pEnd) { ME_DisplayItem *p, *row; @@ -227,6 +349,9 @@ static void ME_InsertRowStart(ME_WrapContext *wc, const ME_DisplayItem *pEnd) shift = max((wc->nAvailWidth-width)/2, 0); if (align == PFA_RIGHT) shift = max(wc->nAvailWidth-width, 0); + + if (para->nFlags & MEPF_COMPLEX) layout_row( wc->pRowStart, pEnd ); + row->member.row.pt.x = row->member.row.nLMargin + shift; for (p = wc->pRowStart; p!=pEnd; p = p->next) { @@ -613,6 +738,111 @@ static void ME_PrepareParagraphForWrapping(ME_Context *c, ME_DisplayItem *tp) { } } +static HRESULT itemize_para( ME_Context *c, ME_DisplayItem *p ) +{ + ME_Paragraph *para = &p->member.para; + ME_Run *run; + ME_DisplayItem *di; + SCRIPT_ITEM buf[16], *items = buf; + int items_passed = sizeof( buf ) / sizeof( buf[0] ), num_items, cur_item; + SCRIPT_CONTROL control = { LANG_USER_DEFAULT, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, + FALSE, FALSE, 0 }; + SCRIPT_STATE state = { 0, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, 0, 0 }; + HRESULT hr; + + assert( p->type == diParagraph ); + + while (1) + { + hr = ScriptItemize( para->text->szData, para->text->nLen, items_passed, &control, + &state, items, &num_items ); + if (hr != E_OUTOFMEMORY) break; /* may not be enough items if hr == E_OUTOFMEMORY */ + if (items_passed > para->text->nLen + 1) break; /* something else has gone wrong */ + items_passed *= 2; + if (items == buf) + items = heap_alloc( items_passed * sizeof( *items ) ); + else + items = heap_realloc( items, items_passed * sizeof( *items ) ); + if (!items) break; + } + if (FAILED( hr )) goto end; + + if (TRACE_ON( richedit )) + { + TRACE( "got items:\n" ); + for (cur_item = 0; cur_item < num_items; cur_item++) + { + TRACE( "\t%d - %d RTL %d bidi level %d\n", items[cur_item].iCharPos, items[cur_item+1].iCharPos - 1, + items[cur_item].a.fRTL, items[cur_item].a.s.uBidiLevel ); + } + + TRACE( "before splitting runs into ranges\n" ); + for (di = p->next; di != p->member.para.next_para; di = di->next) + { + if (di->type != diRun) continue; + TRACE( "\t%d: %s\n", di->member.run.nCharOfs, debugstr_run( &di->member.run ) ); + } + } + + /* split runs into ranges at item boundaries */ + for (di = p->next, cur_item = 0; di != p->member.para.next_para; di = di->next) + { + if (di->type != diRun) continue; + run = &di->member.run; + + if (run->nCharOfs == items[cur_item+1].iCharPos) cur_item++; + + items[cur_item].a.fLogicalOrder = TRUE; + run->script_analysis = items[cur_item].a; + + if (run->nFlags & MERF_ENDPARA) break; /* don't split eop runs */ + + if (run->nCharOfs + run->len > items[cur_item+1].iCharPos) + { + ME_Cursor cursor = {p, di, items[cur_item+1].iCharPos - run->nCharOfs}; + ME_SplitRunSimple( c->editor, &cursor ); + } + } + + if (TRACE_ON( richedit )) + { + TRACE( "after splitting into ranges\n" ); + for (di = p->next; di != p->member.para.next_para; di = di->next) + { + if (di->type != diRun) continue; + TRACE( "\t%d: %s\n", di->member.run.nCharOfs, debugstr_run( &di->member.run ) ); + } + } + + para->nFlags |= MEPF_COMPLEX; + +end: + if (items != buf) heap_free( items ); + return hr; +} + + +static HRESULT shape_para( ME_Context *c, ME_DisplayItem *p ) +{ + ME_DisplayItem *di; + ME_Run *run; + HRESULT hr; + + for (di = p->next; di != p->member.para.next_para; di = di->next) + { + if (di->type != diRun) continue; + run = &di->member.run; + + hr = shape_run( c, run ); + if (FAILED( hr )) + { + run->para->nFlags &= ~MEPF_COMPLEX; + return hr; + } + } + return hr; +} + static void ME_WrapTextParagraph(ME_Context *c, ME_DisplayItem *tp) { ME_DisplayItem *p; ME_WrapContext wc; @@ -625,6 +855,15 @@ static void ME_WrapTextParagraph(ME_Context *c, ME_DisplayItem *tp) { return; } ME_PrepareParagraphForWrapping(c, tp); + + /* For now treating all non-password text as complex for better testing */ + if (!c->editor->cPasswordMask /* && + ScriptIsComplex( tp->member.para.text->szData, tp->member.para.text->nLen, SIC_COMPLEX ) == S_OK */) + { + if (SUCCEEDED( itemize_para( c, tp ) )) + shape_para( c, tp ); + } + pFmt = tp->member.para.pFmt; wc.context = c;