riched20: Add uniscribe support.
This commit is contained in:
parent
57d5b6fcdd
commit
7a0a4ce7ab
@ -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 = \
|
||||
|
@ -47,6 +47,7 @@
|
||||
#include <richole.h>
|
||||
#include "imm.h"
|
||||
#include <textserv.h>
|
||||
#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
|
||||
|
@ -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);
|
||||
|
@ -331,6 +331,11 @@ 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 );
|
||||
|
||||
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 );
|
||||
|
@ -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);
|
||||
@ -593,7 +621,11 @@ SIZE ME_GetRunSizeCommon(ME_Context *c, const ME_Paragraph *para, ME_Run *run, i
|
||||
* 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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user