2109 lines
77 KiB
C
2109 lines
77 KiB
C
/*
|
|
* Text analyzing tests
|
|
*
|
|
* Copyright 2012-2014 Nikolay Sivov for CodeWeavers
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 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
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
|
|
*/
|
|
|
|
#define COBJMACROS
|
|
|
|
#include <assert.h>
|
|
#include <stdio.h>
|
|
#include <limits.h>
|
|
#include <math.h>
|
|
|
|
#include "initguid.h"
|
|
#include "windows.h"
|
|
#include "dwrite_3.h"
|
|
|
|
#include "wine/test.h"
|
|
|
|
static IDWriteFactory *factory;
|
|
static const WCHAR test_fontfile[] = {'w','i','n','e','_','t','e','s','t','_','f','o','n','t','.','t','t','f',0};
|
|
|
|
enum analysis_kind {
|
|
ScriptAnalysis,
|
|
LastKind
|
|
};
|
|
|
|
static const char *get_analysis_kind_name(enum analysis_kind kind)
|
|
{
|
|
switch (kind)
|
|
{
|
|
case ScriptAnalysis:
|
|
return "ScriptAnalysis";
|
|
default:
|
|
return "unknown";
|
|
}
|
|
}
|
|
|
|
struct script_analysis {
|
|
UINT32 pos;
|
|
UINT32 len;
|
|
DWRITE_SCRIPT_SHAPES shapes;
|
|
};
|
|
|
|
struct call_entry {
|
|
enum analysis_kind kind;
|
|
struct script_analysis sa;
|
|
};
|
|
|
|
struct testcontext {
|
|
enum analysis_kind kind;
|
|
BOOL todo;
|
|
int *failcount;
|
|
const char *file;
|
|
int line;
|
|
};
|
|
|
|
struct call_sequence
|
|
{
|
|
int count;
|
|
int size;
|
|
struct call_entry *sequence;
|
|
};
|
|
|
|
#define NUM_CALL_SEQUENCES 1
|
|
#define ANALYZER_ID 0
|
|
static struct call_sequence *sequences[NUM_CALL_SEQUENCES];
|
|
static struct call_sequence *expected_seq[1];
|
|
|
|
static void add_call(struct call_sequence **seq, int sequence_index, const struct call_entry *call)
|
|
{
|
|
struct call_sequence *call_seq = seq[sequence_index];
|
|
|
|
if (!call_seq->sequence)
|
|
{
|
|
call_seq->size = 10;
|
|
call_seq->sequence = HeapAlloc(GetProcessHeap(), 0,
|
|
call_seq->size * sizeof (struct call_entry));
|
|
}
|
|
|
|
if (call_seq->count == call_seq->size)
|
|
{
|
|
call_seq->size *= 2;
|
|
call_seq->sequence = HeapReAlloc(GetProcessHeap(), 0,
|
|
call_seq->sequence,
|
|
call_seq->size * sizeof (struct call_entry));
|
|
}
|
|
|
|
assert(call_seq->sequence);
|
|
|
|
call_seq->sequence[call_seq->count++] = *call;
|
|
}
|
|
|
|
static inline void flush_sequence(struct call_sequence **seg, int sequence_index)
|
|
{
|
|
struct call_sequence *call_seq = seg[sequence_index];
|
|
|
|
HeapFree(GetProcessHeap(), 0, call_seq->sequence);
|
|
call_seq->sequence = NULL;
|
|
call_seq->count = call_seq->size = 0;
|
|
}
|
|
|
|
static inline void flush_sequences(struct call_sequence **seq, int n)
|
|
{
|
|
int i;
|
|
for (i = 0; i < n; i++)
|
|
flush_sequence(seq, i);
|
|
}
|
|
|
|
static void init_call_sequences(struct call_sequence **seq, int n)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < n; i++)
|
|
seq[i] = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(struct call_sequence));
|
|
}
|
|
|
|
static void test_uint(UINT32 actual, UINT32 expected, const char *name, const struct testcontext *ctxt)
|
|
{
|
|
if (expected != actual && ctxt->todo)
|
|
{
|
|
(*ctxt->failcount)++;
|
|
ok_(ctxt->file, ctxt->line) (0, "%s: \"%s\" expecting %u, got %u\n", get_analysis_kind_name(ctxt->kind), name, expected, actual);
|
|
}
|
|
else
|
|
ok_(ctxt->file, ctxt->line) (expected == actual, "%s: \"%s\" expecting %u, got %u\n", get_analysis_kind_name(ctxt->kind), name,
|
|
expected, actual);
|
|
}
|
|
|
|
static void ok_sequence_(struct call_sequence **seq, int sequence_index,
|
|
const struct call_entry *expected, const char *context, BOOL todo,
|
|
const char *file, int line)
|
|
{
|
|
struct call_sequence *call_seq = seq[sequence_index];
|
|
static const struct call_entry end_of_sequence = { LastKind };
|
|
const struct call_entry *actual, *sequence;
|
|
int failcount = 0;
|
|
struct testcontext ctxt;
|
|
|
|
add_call(seq, sequence_index, &end_of_sequence);
|
|
|
|
sequence = call_seq->sequence;
|
|
actual = sequence;
|
|
|
|
ctxt.failcount = &failcount;
|
|
ctxt.todo = todo;
|
|
ctxt.file = file;
|
|
ctxt.line = line;
|
|
|
|
while (expected->kind != LastKind && actual->kind != LastKind)
|
|
{
|
|
if (expected->kind == actual->kind)
|
|
{
|
|
ctxt.kind = expected->kind;
|
|
|
|
switch (actual->kind)
|
|
{
|
|
case ScriptAnalysis:
|
|
test_uint(actual->sa.pos, expected->sa.pos, "position", &ctxt);
|
|
test_uint(actual->sa.len, expected->sa.len, "length", &ctxt);
|
|
test_uint(actual->sa.shapes, expected->sa.shapes, "shapes", &ctxt);
|
|
break;
|
|
default:
|
|
ok(0, "%s: callback not handled, %s\n", context, get_analysis_kind_name(actual->kind));
|
|
}
|
|
expected++;
|
|
actual++;
|
|
}
|
|
else if (todo)
|
|
{
|
|
failcount++;
|
|
todo_wine
|
|
{
|
|
ok_(file, line) (0, "%s: call %s was expected, but got call %s instead\n",
|
|
context, get_analysis_kind_name(expected->kind), get_analysis_kind_name(actual->kind));
|
|
}
|
|
|
|
flush_sequence(seq, sequence_index);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
ok_(file, line) (0, "%s: call %s was expected, but got call %s instead\n",
|
|
context, get_analysis_kind_name(expected->kind), get_analysis_kind_name(actual->kind));
|
|
expected++;
|
|
actual++;
|
|
}
|
|
}
|
|
|
|
if (todo)
|
|
{
|
|
todo_wine
|
|
{
|
|
if (expected->kind != LastKind || actual->kind != LastKind)
|
|
{
|
|
failcount++;
|
|
ok_(file, line) (0, "%s: the call sequence is not complete: expected %s - actual %s\n",
|
|
context, get_analysis_kind_name(expected->kind), get_analysis_kind_name(actual->kind));
|
|
}
|
|
}
|
|
}
|
|
else if (expected->kind != LastKind || actual->kind != LastKind)
|
|
{
|
|
ok_(file, line) (0, "%s: the call sequence is not complete: expected %s - actual %s\n",
|
|
context, get_analysis_kind_name(expected->kind), get_analysis_kind_name(actual->kind));
|
|
}
|
|
|
|
if (todo && !failcount) /* succeeded yet marked todo */
|
|
{
|
|
todo_wine
|
|
{
|
|
ok_(file, line)(1, "%s: marked \"todo_wine\" but succeeds\n", context);
|
|
}
|
|
}
|
|
|
|
flush_sequence(seq, sequence_index);
|
|
}
|
|
|
|
#define ok_sequence(seq, index, exp, contx, todo) \
|
|
ok_sequence_(seq, index, (exp), (contx), (todo), __FILE__, __LINE__)
|
|
|
|
static HRESULT WINAPI analysissink_QueryInterface(IDWriteTextAnalysisSink *iface, REFIID riid, void **obj)
|
|
{
|
|
if (IsEqualIID(riid, &IID_IDWriteTextAnalysisSink) || IsEqualIID(riid, &IID_IUnknown))
|
|
{
|
|
*obj = iface;
|
|
return S_OK;
|
|
}
|
|
|
|
*obj = NULL;
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
static ULONG WINAPI analysissink_AddRef(IDWriteTextAnalysisSink *iface)
|
|
{
|
|
return 2;
|
|
}
|
|
|
|
static ULONG WINAPI analysissink_Release(IDWriteTextAnalysisSink *iface)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
static HRESULT WINAPI analysissink_SetScriptAnalysis(IDWriteTextAnalysisSink *iface,
|
|
UINT32 position, UINT32 length, DWRITE_SCRIPT_ANALYSIS const* sa)
|
|
{
|
|
struct call_entry entry;
|
|
entry.kind = ScriptAnalysis;
|
|
entry.sa.pos = position;
|
|
entry.sa.len = length;
|
|
entry.sa.shapes = sa->shapes;
|
|
add_call(sequences, ANALYZER_ID, &entry);
|
|
return S_OK;
|
|
}
|
|
|
|
static DWRITE_SCRIPT_ANALYSIS g_sa;
|
|
static HRESULT WINAPI analysissink_SetScriptAnalysis2(IDWriteTextAnalysisSink *iface,
|
|
UINT32 position, UINT32 length, DWRITE_SCRIPT_ANALYSIS const* sa)
|
|
{
|
|
g_sa = *sa;
|
|
return S_OK;
|
|
}
|
|
|
|
#define BREAKPOINT_COUNT 20
|
|
static DWRITE_LINE_BREAKPOINT g_actual_bp[BREAKPOINT_COUNT];
|
|
|
|
static HRESULT WINAPI analysissink_SetLineBreakpoints(IDWriteTextAnalysisSink *iface,
|
|
UINT32 position,
|
|
UINT32 length,
|
|
DWRITE_LINE_BREAKPOINT const* breakpoints)
|
|
{
|
|
if (position + length > BREAKPOINT_COUNT) {
|
|
ok(0, "SetLineBreakpoints: reported pos=%u, len=%u overflows expected length %d\n", position, length, BREAKPOINT_COUNT);
|
|
return E_FAIL;
|
|
}
|
|
memcpy(&g_actual_bp[position], breakpoints, length*sizeof(DWRITE_LINE_BREAKPOINT));
|
|
return S_OK;
|
|
}
|
|
|
|
static HRESULT WINAPI analysissink_SetBidiLevel(IDWriteTextAnalysisSink *iface,
|
|
UINT32 position,
|
|
UINT32 length,
|
|
UINT8 explicitLevel,
|
|
UINT8 resolvedLevel)
|
|
{
|
|
ok(0, "unexpected\n");
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
static HRESULT WINAPI analysissink_SetNumberSubstitution(IDWriteTextAnalysisSink *iface,
|
|
UINT32 position,
|
|
UINT32 length,
|
|
IDWriteNumberSubstitution* substitution)
|
|
{
|
|
ok(0, "unexpected\n");
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
static IDWriteTextAnalysisSinkVtbl analysissinkvtbl = {
|
|
analysissink_QueryInterface,
|
|
analysissink_AddRef,
|
|
analysissink_Release,
|
|
analysissink_SetScriptAnalysis,
|
|
analysissink_SetLineBreakpoints,
|
|
analysissink_SetBidiLevel,
|
|
analysissink_SetNumberSubstitution
|
|
};
|
|
|
|
static IDWriteTextAnalysisSinkVtbl analysissinkvtbl2 = {
|
|
analysissink_QueryInterface,
|
|
analysissink_AddRef,
|
|
analysissink_Release,
|
|
analysissink_SetScriptAnalysis2,
|
|
analysissink_SetLineBreakpoints,
|
|
analysissink_SetBidiLevel,
|
|
analysissink_SetNumberSubstitution
|
|
};
|
|
|
|
static IDWriteTextAnalysisSink analysissink = { &analysissinkvtbl };
|
|
static IDWriteTextAnalysisSink analysissink2 = { &analysissinkvtbl2 };
|
|
|
|
static HRESULT WINAPI analysissource_QueryInterface(IDWriteTextAnalysisSource *iface,
|
|
REFIID riid, void **obj)
|
|
{
|
|
ok(0, "QueryInterface not expected\n");
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
static ULONG WINAPI analysissource_AddRef(IDWriteTextAnalysisSource *iface)
|
|
{
|
|
ok(0, "AddRef not expected\n");
|
|
return 2;
|
|
}
|
|
|
|
static ULONG WINAPI analysissource_Release(IDWriteTextAnalysisSource *iface)
|
|
{
|
|
ok(0, "Release not expected\n");
|
|
return 1;
|
|
}
|
|
|
|
static const WCHAR *g_source;
|
|
|
|
static HRESULT WINAPI analysissource_GetTextAtPosition(IDWriteTextAnalysisSource *iface,
|
|
UINT32 position, WCHAR const** text, UINT32* text_len)
|
|
{
|
|
if (position >= lstrlenW(g_source))
|
|
{
|
|
*text = NULL;
|
|
*text_len = 0;
|
|
}
|
|
else
|
|
{
|
|
*text = &g_source[position];
|
|
*text_len = lstrlenW(g_source) - position;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
static HRESULT WINAPI analysissource_GetTextBeforePosition(IDWriteTextAnalysisSource *iface,
|
|
UINT32 position, WCHAR const** text, UINT32* text_len)
|
|
{
|
|
ok(0, "unexpected\n");
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
static DWRITE_READING_DIRECTION WINAPI analysissource_GetParagraphReadingDirection(
|
|
IDWriteTextAnalysisSource *iface)
|
|
{
|
|
ok(0, "unexpected\n");
|
|
return DWRITE_READING_DIRECTION_RIGHT_TO_LEFT;
|
|
}
|
|
|
|
static HRESULT WINAPI analysissource_GetLocaleName(IDWriteTextAnalysisSource *iface,
|
|
UINT32 position, UINT32* text_len, WCHAR const** locale)
|
|
{
|
|
*locale = NULL;
|
|
*text_len = 0;
|
|
return S_OK;
|
|
}
|
|
|
|
static HRESULT WINAPI analysissource_GetNumberSubstitution(IDWriteTextAnalysisSource *iface,
|
|
UINT32 position, UINT32* text_len, IDWriteNumberSubstitution **substitution)
|
|
{
|
|
ok(0, "unexpected\n");
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
static IDWriteTextAnalysisSourceVtbl analysissourcevtbl = {
|
|
analysissource_QueryInterface,
|
|
analysissource_AddRef,
|
|
analysissource_Release,
|
|
analysissource_GetTextAtPosition,
|
|
analysissource_GetTextBeforePosition,
|
|
analysissource_GetParagraphReadingDirection,
|
|
analysissource_GetLocaleName,
|
|
analysissource_GetNumberSubstitution
|
|
};
|
|
|
|
static IDWriteTextAnalysisSource analysissource = { &analysissourcevtbl };
|
|
|
|
static IDWriteFontFace *create_fontface(void)
|
|
{
|
|
static const WCHAR tahomaW[] = {'T','a','h','o','m','a',0};
|
|
IDWriteGdiInterop *interop;
|
|
IDWriteFontFace *fontface;
|
|
IDWriteFont *font;
|
|
LOGFONTW logfont;
|
|
HRESULT hr;
|
|
|
|
hr = IDWriteFactory_GetGdiInterop(factory, &interop);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
|
|
memset(&logfont, 0, sizeof(logfont));
|
|
logfont.lfHeight = 12;
|
|
logfont.lfWidth = 12;
|
|
logfont.lfWeight = FW_NORMAL;
|
|
logfont.lfItalic = 1;
|
|
lstrcpyW(logfont.lfFaceName, tahomaW);
|
|
|
|
hr = IDWriteGdiInterop_CreateFontFromLOGFONT(interop, &logfont, &font);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
|
|
hr = IDWriteFont_CreateFontFace(font, &fontface);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
|
|
IDWriteFont_Release(font);
|
|
IDWriteGdiInterop_Release(interop);
|
|
|
|
return fontface;
|
|
}
|
|
|
|
static WCHAR *create_testfontfile(const WCHAR *filename)
|
|
{
|
|
static WCHAR pathW[MAX_PATH];
|
|
DWORD written;
|
|
HANDLE file;
|
|
HRSRC res;
|
|
void *ptr;
|
|
|
|
GetTempPathW(sizeof(pathW)/sizeof(WCHAR), pathW);
|
|
lstrcatW(pathW, filename);
|
|
|
|
file = CreateFileW(pathW, GENERIC_READ|GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, 0);
|
|
ok(file != INVALID_HANDLE_VALUE, "file creation failed, at %s, error %d\n", wine_dbgstr_w(pathW),
|
|
GetLastError());
|
|
|
|
res = FindResourceA(GetModuleHandleA(NULL), (LPCSTR)MAKEINTRESOURCE(1), (LPCSTR)RT_RCDATA);
|
|
ok(res != 0, "couldn't find resource\n");
|
|
ptr = LockResource(LoadResource(GetModuleHandleA(NULL), res));
|
|
WriteFile(file, ptr, SizeofResource(GetModuleHandleA(NULL), res), &written, NULL);
|
|
ok(written == SizeofResource(GetModuleHandleA(NULL), res), "couldn't write resource\n");
|
|
CloseHandle(file);
|
|
|
|
return pathW;
|
|
}
|
|
|
|
#define DELETE_FONTFILE(filename) _delete_testfontfile(filename, __LINE__)
|
|
static void _delete_testfontfile(const WCHAR *filename, int line)
|
|
{
|
|
BOOL ret = DeleteFileW(filename);
|
|
ok_(__FILE__,line)(ret, "failed to delete file %s, error %d\n", wine_dbgstr_w(filename), GetLastError());
|
|
}
|
|
|
|
static IDWriteFontFace *create_testfontface(const WCHAR *filename)
|
|
{
|
|
IDWriteFontFace *face;
|
|
IDWriteFontFile *file;
|
|
HRESULT hr;
|
|
|
|
hr = IDWriteFactory_CreateFontFileReference(factory, filename, NULL, &file);
|
|
ok(hr == S_OK, "got 0x%08x\n",hr);
|
|
|
|
hr = IDWriteFactory_CreateFontFace(factory, DWRITE_FONT_FACE_TYPE_TRUETYPE, 1, &file, 0,
|
|
DWRITE_FONT_SIMULATIONS_NONE, &face);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
IDWriteFontFile_Release(file);
|
|
|
|
return face;
|
|
}
|
|
|
|
struct sa_test {
|
|
const WCHAR string[50];
|
|
int item_count;
|
|
struct script_analysis sa[10];
|
|
};
|
|
|
|
static struct sa_test sa_tests[] = {
|
|
{
|
|
/* just 1 char string */
|
|
{'t',0}, 1,
|
|
{ { 0, 1, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
{'t','e','s','t',0}, 1,
|
|
{ { 0, 4, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
{' ',' ',' ',' ','!','$','[','^','{','~',0}, 1,
|
|
{ { 0, 10, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
{' ',' ',' ','1','2',' ',0}, 1,
|
|
{ { 0, 6, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* digits only */
|
|
{'1','2',0}, 1,
|
|
{ { 0, 2, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Arabic */
|
|
{0x064a,0x064f,0x0633,0x0627,0x0648,0x0650,0x064a,0x0661,0}, 1,
|
|
{ { 0, 8, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Arabic */
|
|
{0x0627,0x0644,0x0635,0x0651,0x0650,0x062d,0x0629,0x064f,' ',0x062a,0x064e,
|
|
0x0627,0x062c,0x064c,' ',0x0639,0x064e,0x0644,0x0649,' ',
|
|
0x0631,0x064f,0x0624,0x0648,0x0633,0x0650,' ',0x0627,0x0644,
|
|
0x0623,0x0635,0x0650,0x062d,0x0651,0x064e,0x0627,0x0621,0x0650,0x06f0,0x06f5,0}, 1,
|
|
{ { 0, 40, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Arabic, Latin */
|
|
{'1','2','3','-','5','2',0x064a,0x064f,0x0633,0x0627,0x0648,0x0650,0x064a,'7','1','.',0}, 1,
|
|
{ { 0, 16, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Arabic, English */
|
|
{'A','B','C','-','D','E','F',' ',0x0621,0x0623,0x0624,0}, 2,
|
|
{ { 0, 8, DWRITE_SCRIPT_SHAPES_DEFAULT },
|
|
{ 8, 3, DWRITE_SCRIPT_SHAPES_DEFAULT },
|
|
}
|
|
},
|
|
{
|
|
/* leading space, Arabic, English */
|
|
{' ',0x0621,0x0623,0x0624,'A','B','C','-','D','E','F',0}, 2,
|
|
{ { 0, 4, DWRITE_SCRIPT_SHAPES_DEFAULT },
|
|
{ 4, 7, DWRITE_SCRIPT_SHAPES_DEFAULT },
|
|
}
|
|
},
|
|
{
|
|
/* English, Arabic, trailing space */
|
|
{'A','B','C','-','D','E','F',0x0621,0x0623,0x0624,' ',0}, 2,
|
|
{ { 0, 7, DWRITE_SCRIPT_SHAPES_DEFAULT },
|
|
{ 7, 4, DWRITE_SCRIPT_SHAPES_DEFAULT },
|
|
}
|
|
},
|
|
{
|
|
/* C1 Controls, Latin-1 Supplement */
|
|
{0x80,0x90,0x9f,0xa0,0xc0,0xb8,0xbf,0xc0,0xff,0}, 2,
|
|
{ { 0, 3, DWRITE_SCRIPT_SHAPES_NO_VISUAL },
|
|
{ 3, 6, DWRITE_SCRIPT_SHAPES_DEFAULT },
|
|
}
|
|
},
|
|
{
|
|
/* Latin Extended-A */
|
|
{0x100,0x120,0x130,0x140,0x150,0x160,0x170,0x17f,0}, 1,
|
|
{ { 0, 8, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Latin Extended-B */
|
|
{0x180,0x190,0x1bf,0x1c0,0x1c3,0x1c4,0x1cc,0x1dc,0x1ff,0x217,0x21b,0x24f,0}, 1,
|
|
{ { 0, 12, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* IPA Extensions */
|
|
{0x250,0x260,0x270,0x290,0x2af,0}, 1,
|
|
{ { 0, 5, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Spacing Modifier Letters */
|
|
{0x2b0,0x2ba,0x2d7,0x2dd,0x2ef,0x2ff,0}, 1,
|
|
{ { 0, 6, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Combining Diacritical Marks */
|
|
{0x300,0x320,0x340,0x345,0x350,0x36f,0}, 1,
|
|
{ { 0, 6, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Greek and Coptic */
|
|
{0x370,0x388,0x3d8,0x3e1,0x3e2,0x3fa,0x3ff,0}, 3,
|
|
{ { 0, 4, DWRITE_SCRIPT_SHAPES_DEFAULT },
|
|
{ 4, 1, DWRITE_SCRIPT_SHAPES_DEFAULT },
|
|
{ 5, 2, DWRITE_SCRIPT_SHAPES_DEFAULT }
|
|
}
|
|
},
|
|
{
|
|
/* Cyrillic and Cyrillic Supplement */
|
|
{0x400,0x40f,0x410,0x44f,0x450,0x45f,0x460,0x481,0x48a,0x4f0,0x4fa,0x4ff,0x500,0x510,0x520,0}, 1,
|
|
{ { 0, 15, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Armenian */
|
|
{0x531,0x540,0x559,0x55f,0x570,0x589,0x58a,0}, 1,
|
|
{ { 0, 7, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Hebrew */
|
|
{0x5e9,0x5dc,0x5d5,0x5dd,0}, 1,
|
|
{ { 0, 4, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Latin, Hebrew, Latin */
|
|
{'p','a','r','t',' ','o','n','e',' ',0x5d7,0x5dc,0x5e7,' ',0x5e9,0x5ea,0x5d9,0x5d9,0x5dd,' ','p','a','r','t',' ','t','h','r','e','e',0}, 3,
|
|
{ { 0, 9, DWRITE_SCRIPT_SHAPES_DEFAULT },
|
|
{ 9, 10, DWRITE_SCRIPT_SHAPES_DEFAULT },
|
|
{ 19, 10, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Syriac */
|
|
{0x710,0x712,0x712,0x714,'.',0}, 1,
|
|
{ { 0, 5, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Arabic Supplement */
|
|
{0x750,0x760,0x76d,'.',0}, 1,
|
|
{ { 0, 4, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Thaana */
|
|
{0x780,0x78e,0x798,0x7a6,0x7b0,'.',0}, 1,
|
|
{ { 0, 6, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* N'Ko */
|
|
{0x7c0,0x7ca,0x7e8,0x7eb,0x7f6,'.',0}, 1,
|
|
{ { 0, 6, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Thaana */
|
|
{0x780,0x798,0x7a5,0x7a6,0x7b0,'.',0}, 1,
|
|
{ { 0, 6, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Devanagari */
|
|
{0x926,0x947,0x935,0x928,0x93e,0x917,0x930,0x940,'.',0}, 1,
|
|
{ { 0, 9, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Bengali */
|
|
{0x9ac,0x9be,0x982,0x9b2,0x9be,'.',0}, 1,
|
|
{ { 0, 6, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Gurmukhi */
|
|
{0xa17,0xa41,0xa30,0xa2e,0xa41,0xa16,0xa40,'.',0}, 1,
|
|
{ { 0, 8, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Gujarati */
|
|
{0xa97,0xac1,0xa9c,0xab0,0xabe,0xaa4,0xac0,'.',0}, 1,
|
|
{ { 0, 8, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Oriya */
|
|
{0xb13,0xb21,0xb3c,0xb3f,0xb06,'.',0}, 1,
|
|
{ { 0, 6, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Tamil */
|
|
{0xba4,0xbae,0xbbf,0xbb4,0xbcd,'.',0}, 1,
|
|
{ { 0, 6, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Telugu */
|
|
{0xc24,0xc46,0xc32,0xc41,0xc17,0xc41,'.',0}, 1,
|
|
{ { 0, 7, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Kannada */
|
|
{0xc95,0xca8,0xccd,0xca8,0xca1,'.',0}, 1,
|
|
{ { 0, 6, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Malayalam */
|
|
{0xd2e,0xd32,0xd2f,0xd3e,0xd33,0xd02,'.',0}, 1,
|
|
{ { 0, 7, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Sinhala */
|
|
{0xd82,0xd85,0xd9a,0xdcf,'.',0}, 1,
|
|
{ { 0, 5, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Thai */
|
|
{0x0e04,0x0e27,0x0e32,0x0e21,0x0e1e,0x0e22,0x0e32,0x0e22,0x0e32,0x0e21,
|
|
0x0e2d,0x0e22,0x0e39,0x0e48,0x0e17,0x0e35,0x0e48,0x0e44,0x0e2b,0x0e19,
|
|
0x0e04,0x0e27,0x0e32,0x0e21,0x0e2a, 0x0e33,0x0e40,0x0e23,0x0e47,0x0e08,
|
|
0x0e2d,0x0e22,0x0e39,0x0e48,0x0e17,0x0e35,0x0e48,0x0e19,0x0e31,0x0e48,0x0e19,'.',0}, 1,
|
|
{ { 0, 42, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Lao */
|
|
{0xead,0xeb1,0xe81,0xeaa,0xead,0xe99,0xea5,0xeb2,0xea7,'.',0}, 1,
|
|
{ { 0, 10, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Tibetan */
|
|
{0xf04,0xf05,0xf0e,0x020,0xf51,0xf7c,0xf53,0xf0b,0xf5a,0xf53,0xf0b,
|
|
0xf51,0xf44,0xf0b,0xf54,0xf7c,0xf0d,'.',0}, 1,
|
|
{ { 0, 18, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Myanmar */
|
|
{0x1019,0x103c,0x1014,0x103a,0x1019,0x102c,0x1021,0x1000,0x1039,0x1001,0x101b,0x102c,'.',0}, 1,
|
|
{ { 0, 13, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Georgian */
|
|
{0x10a0,0x10d0,0x10da,0x10f1,0x10fb,0x2d00,'.',0}, 1,
|
|
{ { 0, 7, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Hangul */
|
|
{0x1100,0x1110,0x1160,0x1170,0x11a8,'.',0}, 1,
|
|
{ { 0, 6, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Ethiopic */
|
|
{0x130d,0x12d5,0x12dd,0}, 1,
|
|
{ { 0, 3, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Cherokee */
|
|
{0x13e3,0x13b3,0x13a9,0x0020,0x13a6,0x13ec,0x13c2,0x13af,0x13cd,0x13d7,0}, 1,
|
|
{ { 0, 10, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Canadian */
|
|
{0x1403,0x14c4,0x1483,0x144e,0x1450,0x1466,0}, 1,
|
|
{ { 0, 6, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Ogham */
|
|
{0x169b,0x1691,0x168c,0x1690,0x168b,0x169c,0}, 1,
|
|
{ { 0, 6, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Runic */
|
|
{0x16a0,0x16a1,0x16a2,0x16a3,0x16a4,0x16a5,0}, 1,
|
|
{ { 0, 6, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Khmer */
|
|
{0x1781,0x17c1,0x1798,0x179a,0x1797,0x17b6,0x179f,0x17b6,0x19e0,0}, 1,
|
|
{ { 0, 9, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Mongolian */
|
|
{0x182e,0x1823,0x1829,0x182d,0x1823,0x182f,0x0020,0x182a,0x1822,0x1834,0x1822,0x182d,0x180c,0}, 1,
|
|
{ { 0, 13, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Limbu */
|
|
{0x1900,0x1910,0x1920,0x1930,0}, 1,
|
|
{ { 0, 4, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Tai Le */
|
|
{0x1956,0x196d,0x1970,0x1956,0x196c,0x1973,0x1951,0x1968,0x1952,0x1970,0}, 1,
|
|
{ { 0, 10, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* New Tai Lue */
|
|
{0x1992,0x19c4,0}, 1,
|
|
{ { 0, 2, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Buginese */
|
|
{0x1a00,0x1a10,0}, 1,
|
|
{ { 0, 2, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Tai Tham */
|
|
{0x1a20,0x1a40,0x1a50,0}, 1,
|
|
{ { 0, 3, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Balinese */
|
|
{0x1b00,0x1b05,0x1b20,0}, 1,
|
|
{ { 0, 3, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Sundanese */
|
|
{0x1b80,0x1b85,0x1ba0,0}, 1,
|
|
{ { 0, 3, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Batak */
|
|
{0x1bc0,0x1be5,0x1bfc,0}, 1,
|
|
{ { 0, 3, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Lepcha */
|
|
{0x1c00,0x1c20,0x1c40,0}, 1,
|
|
{ { 0, 3, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Ol Chiki */
|
|
{0x1c50,0x1c5a,0x1c77,0}, 1,
|
|
{ { 0, 3, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Sundanese Supplement */
|
|
{0x1cc0,0x1cc5,0x1cc7,0}, 1,
|
|
{ { 0, 3, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Phonetic Extensions */
|
|
{0x1d00,0x1d40,0x1d70,0}, 1,
|
|
{ { 0, 3, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Combining diacritical marks */
|
|
{0x1dc0,0x300,0x1ddf,0}, 1,
|
|
{ { 0, 3, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Latin Extended Additional, Extended-C */
|
|
{0x1e00,0x1d00,0x2c60,0}, 1,
|
|
{ { 0, 3, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Greek Extended */
|
|
{0x3f0,0x1f00,0}, 1,
|
|
{ { 0, 2, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* General Punctuation */
|
|
{0x1dc0,0x2000,0}, 1,
|
|
{ { 0, 2, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Superscripts and Subscripts */
|
|
{0x2070,0x2086,0x2000,0}, 1,
|
|
{ { 0, 3, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Currency, Combining Diacritical Marks for Symbols. Letterlike Symbols.. */
|
|
{0x20a0,0x20b8,0x2000,0x20d0,0x2100,0x2150,0x2190,0x2200,0x2300,0x2400,0x2440,0x2460,0x2500,0x2580,0x25a0,0x2600,
|
|
0x2700,0x27c0,0x27f0,0x2900,0x2980,0x2a00,0x2b00,0}, 1,
|
|
{ { 0, 23, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Braille */
|
|
{0x2800,0x2070,0x2000,0}, 1,
|
|
{ { 0, 3, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Glagolitic */
|
|
{0x2c00,0x2c12,0}, 1,
|
|
{ { 0, 2, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* Coptic */
|
|
{0x2c80,0x3e2,0x1f00,0}, 2,
|
|
{ { 0, 2, DWRITE_SCRIPT_SHAPES_DEFAULT },
|
|
{ 2, 1, DWRITE_SCRIPT_SHAPES_DEFAULT } }
|
|
},
|
|
{
|
|
/* Tifinagh */
|
|
{0x2d30,0x2d4a,0}, 1,
|
|
{ { 0, 2, DWRITE_SCRIPT_SHAPES_DEFAULT }}
|
|
},
|
|
{
|
|
/* LRE/PDF */
|
|
{0x202a,0x202c,'a','b','c','\r',0}, 3,
|
|
{ { 0, 2, DWRITE_SCRIPT_SHAPES_NO_VISUAL },
|
|
{ 2, 3, DWRITE_SCRIPT_SHAPES_DEFAULT },
|
|
{ 5, 1, DWRITE_SCRIPT_SHAPES_NO_VISUAL } }
|
|
},
|
|
{
|
|
/* LRE/PDF and other visual and non-visual codes from Common script range */
|
|
{0x202a,0x202c,'r','!',0x200b,'\r',0}, 3,
|
|
{ { 0, 2, DWRITE_SCRIPT_SHAPES_NO_VISUAL },
|
|
{ 2, 2, DWRITE_SCRIPT_SHAPES_DEFAULT },
|
|
{ 4, 2, DWRITE_SCRIPT_SHAPES_NO_VISUAL } }
|
|
},
|
|
/* keep this as end marker */
|
|
{ {0} }
|
|
};
|
|
|
|
static void init_expected_sa(struct call_sequence **seq, const struct sa_test *test)
|
|
{
|
|
static const struct call_entry end_of_sequence = { LastKind };
|
|
int i;
|
|
|
|
flush_sequence(seq, 0);
|
|
|
|
/* add expected calls */
|
|
for (i = 0; i < test->item_count; i++)
|
|
{
|
|
struct call_entry call;
|
|
|
|
call.kind = ScriptAnalysis;
|
|
call.sa.pos = test->sa[i].pos;
|
|
call.sa.len = test->sa[i].len;
|
|
call.sa.shapes = test->sa[i].shapes;
|
|
add_call(seq, 0, &call);
|
|
}
|
|
|
|
/* and stop marker */
|
|
add_call(seq, 0, &end_of_sequence);
|
|
}
|
|
|
|
static void get_script_analysis(const WCHAR *str, DWRITE_SCRIPT_ANALYSIS *sa)
|
|
{
|
|
IDWriteTextAnalyzer *analyzer;
|
|
HRESULT hr;
|
|
|
|
g_source = str;
|
|
|
|
hr = IDWriteFactory_CreateTextAnalyzer(factory, &analyzer);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
|
|
hr = IDWriteTextAnalyzer_AnalyzeScript(analyzer, &analysissource, 0, lstrlenW(g_source), &analysissink2);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
|
|
*sa = g_sa;
|
|
}
|
|
|
|
static void test_AnalyzeScript(void)
|
|
{
|
|
const struct sa_test *ptr = sa_tests;
|
|
IDWriteTextAnalyzer *analyzer;
|
|
HRESULT hr;
|
|
|
|
hr = IDWriteFactory_CreateTextAnalyzer(factory, &analyzer);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
|
|
while (*ptr->string)
|
|
{
|
|
g_source = ptr->string;
|
|
|
|
init_expected_sa(expected_seq, ptr);
|
|
hr = IDWriteTextAnalyzer_AnalyzeScript(analyzer, &analysissource, 0, lstrlenW(g_source), &analysissink);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
ok_sequence(sequences, ANALYZER_ID, expected_seq[0]->sequence, wine_dbgstr_w(ptr->string), FALSE);
|
|
ptr++;
|
|
}
|
|
|
|
IDWriteTextAnalyzer_Release(analyzer);
|
|
}
|
|
|
|
struct linebreaks_test {
|
|
const WCHAR text[BREAKPOINT_COUNT+1];
|
|
DWRITE_LINE_BREAKPOINT bp[BREAKPOINT_COUNT];
|
|
};
|
|
|
|
static struct linebreaks_test linebreaks_tests[] = {
|
|
{ {'A','-','B',' ','C',0x58a,'D',0x2010,'E',0x2012,'F',0x2013,'\t',0xc,0xb,0x2028,0x2029,0x200b,0},
|
|
{
|
|
{ DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, 0, 0 },
|
|
{ DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, DWRITE_BREAK_CONDITION_CAN_BREAK, 0, 0 },
|
|
{ DWRITE_BREAK_CONDITION_CAN_BREAK, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, 0, 0 },
|
|
{ DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, DWRITE_BREAK_CONDITION_CAN_BREAK, 1, 0 },
|
|
{ DWRITE_BREAK_CONDITION_CAN_BREAK, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, 0, 0 },
|
|
{ DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, DWRITE_BREAK_CONDITION_CAN_BREAK, 0, 0 },
|
|
{ DWRITE_BREAK_CONDITION_CAN_BREAK, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, 0, 0 },
|
|
{ DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, DWRITE_BREAK_CONDITION_CAN_BREAK, 0, 0 },
|
|
{ DWRITE_BREAK_CONDITION_CAN_BREAK, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, 0, 0 },
|
|
{ DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, DWRITE_BREAK_CONDITION_CAN_BREAK, 0, 0 },
|
|
{ DWRITE_BREAK_CONDITION_CAN_BREAK, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, 0, 0 },
|
|
{ DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, 0, 0 },
|
|
{ DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, 1, 0 },
|
|
{ DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, DWRITE_BREAK_CONDITION_MUST_BREAK, 1, 0 },
|
|
{ DWRITE_BREAK_CONDITION_MUST_BREAK, DWRITE_BREAK_CONDITION_MUST_BREAK, 1, 0 },
|
|
{ DWRITE_BREAK_CONDITION_MUST_BREAK, DWRITE_BREAK_CONDITION_MUST_BREAK, 1, 0 },
|
|
{ DWRITE_BREAK_CONDITION_MUST_BREAK, DWRITE_BREAK_CONDITION_MUST_BREAK, 1, 0 },
|
|
{ DWRITE_BREAK_CONDITION_MUST_BREAK, DWRITE_BREAK_CONDITION_CAN_BREAK, 0, 0 },
|
|
}
|
|
},
|
|
/* Soft hyphen, visible word dividers */
|
|
{ {'A',0xad,'B',0x5be,'C',0xf0b,'D',0x1361,'E',0x17d8,'F',0x17da,'G',0},
|
|
{
|
|
{ DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, 0, 0 },
|
|
{ DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, DWRITE_BREAK_CONDITION_CAN_BREAK, 0, 1 },
|
|
{ DWRITE_BREAK_CONDITION_CAN_BREAK, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, 0, 0 },
|
|
{ DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, DWRITE_BREAK_CONDITION_CAN_BREAK, 0, 0 },
|
|
{ DWRITE_BREAK_CONDITION_CAN_BREAK, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, 0, 0 },
|
|
{ DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, DWRITE_BREAK_CONDITION_CAN_BREAK, 0, 0 },
|
|
{ DWRITE_BREAK_CONDITION_CAN_BREAK, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, 0, 0 },
|
|
{ DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, DWRITE_BREAK_CONDITION_CAN_BREAK, 0, 0 },
|
|
{ DWRITE_BREAK_CONDITION_CAN_BREAK, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, 0, 0 },
|
|
{ DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, DWRITE_BREAK_CONDITION_CAN_BREAK, 0, 0 },
|
|
{ DWRITE_BREAK_CONDITION_CAN_BREAK, DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, 0, 0 },
|
|
{ DWRITE_BREAK_CONDITION_MAY_NOT_BREAK, DWRITE_BREAK_CONDITION_CAN_BREAK, 0, 0 },
|
|
{ DWRITE_BREAK_CONDITION_CAN_BREAK, DWRITE_BREAK_CONDITION_CAN_BREAK, 0, 0 },
|
|
}
|
|
},
|
|
{ { 0 } }
|
|
};
|
|
|
|
static void compare_breakpoints(const struct linebreaks_test *test, DWRITE_LINE_BREAKPOINT *actual)
|
|
{
|
|
static const char *conditions[] = {"N","CB","NB","B"};
|
|
const WCHAR *text = test->text;
|
|
int cmp = memcmp(test->bp, actual, sizeof(*actual)*BREAKPOINT_COUNT);
|
|
ok(!cmp, "%s: got wrong breakpoint data\n", wine_dbgstr_w(test->text));
|
|
if (cmp) {
|
|
int i = 0;
|
|
while (*text) {
|
|
ok(!memcmp(&test->bp[i], &actual[i], sizeof(*actual)),
|
|
"%s: got [%s, %s] (%s, %s), expected [%s, %s] (%s, %s)\n",
|
|
wine_dbgstr_wn(&test->text[i], 1),
|
|
conditions[g_actual_bp[i].breakConditionBefore],
|
|
conditions[g_actual_bp[i].breakConditionAfter],
|
|
g_actual_bp[i].isWhitespace ? "WS" : "0",
|
|
g_actual_bp[i].isSoftHyphen ? "SHY" : "0",
|
|
conditions[test->bp[i].breakConditionBefore],
|
|
conditions[test->bp[i].breakConditionAfter],
|
|
test->bp[i].isWhitespace ? "WS" : "0",
|
|
test->bp[i].isSoftHyphen ? "SHY" : "0");
|
|
if (g_actual_bp[i].isSoftHyphen)
|
|
ok(!g_actual_bp[i].isWhitespace, "%s: soft hyphen marked as a whitespace\n",
|
|
wine_dbgstr_wn(&test->text[i], 1));
|
|
text++;
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void test_AnalyzeLineBreakpoints(void)
|
|
{
|
|
static const WCHAR emptyW[] = {0};
|
|
const struct linebreaks_test *ptr = linebreaks_tests;
|
|
IDWriteTextAnalyzer *analyzer;
|
|
UINT32 i = 0;
|
|
HRESULT hr;
|
|
|
|
hr = IDWriteFactory_CreateTextAnalyzer(factory, &analyzer);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
|
|
g_source = emptyW;
|
|
hr = IDWriteTextAnalyzer_AnalyzeLineBreakpoints(analyzer, &analysissource, 0, 0, &analysissink);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
|
|
while (*ptr->text)
|
|
{
|
|
UINT32 len;
|
|
|
|
g_source = ptr->text;
|
|
len = lstrlenW(g_source);
|
|
|
|
if (len > BREAKPOINT_COUNT) {
|
|
ok(0, "test %u: increase BREAKPOINT_COUNT to at least %u\n", i, len);
|
|
i++;
|
|
ptr++;
|
|
continue;
|
|
}
|
|
|
|
memset(g_actual_bp, 0, sizeof(g_actual_bp));
|
|
hr = IDWriteTextAnalyzer_AnalyzeLineBreakpoints(analyzer, &analysissource, 0, len, &analysissink);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
compare_breakpoints(ptr, g_actual_bp);
|
|
|
|
i++;
|
|
ptr++;
|
|
}
|
|
|
|
IDWriteTextAnalyzer_Release(analyzer);
|
|
}
|
|
|
|
static void test_GetScriptProperties(void)
|
|
{
|
|
IDWriteTextAnalyzer1 *analyzer1;
|
|
IDWriteTextAnalyzer *analyzer;
|
|
DWRITE_SCRIPT_ANALYSIS sa;
|
|
DWRITE_SCRIPT_PROPERTIES props;
|
|
HRESULT hr;
|
|
|
|
hr = IDWriteFactory_CreateTextAnalyzer(factory, &analyzer);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
|
|
hr = IDWriteTextAnalyzer_QueryInterface(analyzer, &IID_IDWriteTextAnalyzer1, (void**)&analyzer1);
|
|
IDWriteTextAnalyzer_Release(analyzer);
|
|
if (hr != S_OK) {
|
|
win_skip("GetScriptProperties() is not supported.\n");
|
|
return;
|
|
}
|
|
|
|
sa.script = 1000;
|
|
hr = IDWriteTextAnalyzer1_GetScriptProperties(analyzer1, sa, &props);
|
|
ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);
|
|
|
|
if (0) /* crashes on native */
|
|
hr = IDWriteTextAnalyzer1_GetScriptProperties(analyzer1, sa, NULL);
|
|
|
|
sa.script = 0;
|
|
hr = IDWriteTextAnalyzer1_GetScriptProperties(analyzer1, sa, &props);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
|
|
IDWriteTextAnalyzer1_Release(analyzer1);
|
|
}
|
|
|
|
struct textcomplexity_test {
|
|
const WCHAR text[5];
|
|
UINT32 length;
|
|
BOOL simple;
|
|
UINT32 len_read;
|
|
};
|
|
|
|
static const struct textcomplexity_test textcomplexity_tests[] = {
|
|
{ {0}, 1, FALSE, 1 },
|
|
{ {0}, 0, TRUE, 0 },
|
|
{ {0x610,0}, 0, TRUE, 0 },
|
|
{ {'A','B','C','D',0}, 3, TRUE, 3 },
|
|
{ {'A','B','C','D',0}, 5, TRUE, 4 },
|
|
{ {'A','B','C','D',0}, 10, TRUE, 4 },
|
|
{ {'A',0x610,'C','D',0}, 1, TRUE, 1 },
|
|
{ {'A',0x610,'C','D',0}, 2, FALSE, 2 },
|
|
{ {0x610,'A','C','D',0}, 1, FALSE, 1 },
|
|
{ {0x610,'A','C','D',0}, 2, FALSE, 1 },
|
|
{ {0x610,0x610,'C','D',0}, 2, FALSE, 2 },
|
|
{ {0xd800,'A','B',0}, 1, FALSE, 1 },
|
|
{ {0xd800,'A','B',0}, 2, FALSE, 1 },
|
|
{ {0xdc00,'A','B',0}, 2, FALSE, 1 }
|
|
};
|
|
|
|
static void test_GetTextComplexity(void)
|
|
{
|
|
static const WCHAR textW[] = {'A','B','C',0};
|
|
IDWriteTextAnalyzer1 *analyzer1;
|
|
IDWriteTextAnalyzer *analyzer;
|
|
IDWriteFontFace *fontface;
|
|
UINT16 indices[10];
|
|
BOOL simple;
|
|
HRESULT hr;
|
|
UINT32 len;
|
|
int i;
|
|
|
|
hr = IDWriteFactory_CreateTextAnalyzer(factory, &analyzer);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
|
|
hr = IDWriteTextAnalyzer_QueryInterface(analyzer, &IID_IDWriteTextAnalyzer1, (void**)&analyzer1);
|
|
IDWriteTextAnalyzer_Release(analyzer);
|
|
if (hr != S_OK) {
|
|
win_skip("GetTextComplexity() is not supported.\n");
|
|
return;
|
|
}
|
|
|
|
if (0) { /* crashes on native */
|
|
hr = IDWriteTextAnalyzer1_GetTextComplexity(analyzer1, NULL, 0, NULL, NULL, NULL, NULL);
|
|
hr = IDWriteTextAnalyzer1_GetTextComplexity(analyzer1, NULL, 0, NULL, NULL, &len, NULL);
|
|
hr = IDWriteTextAnalyzer1_GetTextComplexity(analyzer1, textW, 3, NULL, NULL, NULL, NULL);
|
|
hr = IDWriteTextAnalyzer1_GetTextComplexity(analyzer1, textW, 3, NULL, NULL, &len, NULL);
|
|
hr = IDWriteTextAnalyzer1_GetTextComplexity(analyzer1, textW, 3, NULL, &simple, NULL, NULL);
|
|
}
|
|
|
|
len = 1;
|
|
simple = TRUE;
|
|
hr = IDWriteTextAnalyzer1_GetTextComplexity(analyzer1, NULL, 0, NULL, &simple, &len, NULL);
|
|
ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);
|
|
ok(len == 0, "got %d\n", len);
|
|
ok(simple == FALSE, "got %d\n", simple);
|
|
|
|
len = 1;
|
|
simple = TRUE;
|
|
indices[0] = 1;
|
|
hr = IDWriteTextAnalyzer1_GetTextComplexity(analyzer1, textW, 3, NULL, &simple, &len, NULL);
|
|
ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);
|
|
ok(len == 0, "got %d\n", len);
|
|
ok(simple == FALSE, "got %d\n", simple);
|
|
ok(indices[0] == 1, "got %d\n", indices[0]);
|
|
|
|
fontface = create_fontface();
|
|
|
|
for (i = 0; i < sizeof(textcomplexity_tests)/sizeof(struct textcomplexity_test); i++) {
|
|
const struct textcomplexity_test *ptr = &textcomplexity_tests[i];
|
|
len = 1;
|
|
simple = !ptr->simple;
|
|
indices[0] = 0;
|
|
hr = IDWriteTextAnalyzer1_GetTextComplexity(analyzer1, ptr->text, ptr->length, fontface, &simple, &len, indices);
|
|
ok(hr == S_OK, "%d: got 0x%08x\n", i, hr);
|
|
ok(len == ptr->len_read, "%d: read length: got %d, expected %d\n", i, len, ptr->len_read);
|
|
ok(simple == ptr->simple, "%d: simple: got %d, expected %d\n", i, simple, ptr->simple);
|
|
if (simple && ptr->length)
|
|
ok(indices[0] > 0, "%d: got %d\n", i, indices[0]);
|
|
else
|
|
ok(indices[0] == 0, "%d: got %d\n", i, indices[0]);
|
|
}
|
|
|
|
IDWriteFontFace_Release(fontface);
|
|
IDWriteTextAnalyzer1_Release(analyzer1);
|
|
}
|
|
|
|
static void test_numbersubstitution(void)
|
|
{
|
|
static const WCHAR dummyW[] = {'d','u','m','m','y',0};
|
|
IDWriteNumberSubstitution *substitution;
|
|
HRESULT hr;
|
|
|
|
hr = IDWriteFactory_CreateNumberSubstitution(factory, DWRITE_NUMBER_SUBSTITUTION_METHOD_NONE, NULL, FALSE, &substitution);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
IDWriteNumberSubstitution_Release(substitution);
|
|
|
|
/* invalid method */
|
|
hr = IDWriteFactory_CreateNumberSubstitution(factory, DWRITE_NUMBER_SUBSTITUTION_METHOD_TRADITIONAL+1, NULL, FALSE, &substitution);
|
|
ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);
|
|
|
|
/* invalid method */
|
|
hr = IDWriteFactory_CreateNumberSubstitution(factory, -1, NULL, FALSE, &substitution);
|
|
ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);
|
|
|
|
/* invalid locale */
|
|
hr = IDWriteFactory_CreateNumberSubstitution(factory, DWRITE_NUMBER_SUBSTITUTION_METHOD_TRADITIONAL, dummyW, FALSE, &substitution);
|
|
ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);
|
|
|
|
hr = IDWriteFactory_CreateNumberSubstitution(factory, DWRITE_NUMBER_SUBSTITUTION_METHOD_CONTEXTUAL, dummyW, FALSE, &substitution);
|
|
ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);
|
|
|
|
hr = IDWriteFactory_CreateNumberSubstitution(factory, DWRITE_NUMBER_SUBSTITUTION_METHOD_NATIONAL, dummyW, FALSE, &substitution);
|
|
ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);
|
|
|
|
/* invalid locale, but it's not needed for this method */
|
|
hr = IDWriteFactory_CreateNumberSubstitution(factory, DWRITE_NUMBER_SUBSTITUTION_METHOD_NONE, dummyW, FALSE, &substitution);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
IDWriteNumberSubstitution_Release(substitution);
|
|
}
|
|
|
|
static void get_fontface_glyphs(IDWriteFontFace *fontface, const WCHAR *str, UINT16 *glyphs)
|
|
{
|
|
while (*str) {
|
|
UINT32 codepoint = *str;
|
|
HRESULT hr;
|
|
|
|
hr = IDWriteFontFace_GetGlyphIndices(fontface, &codepoint, 1, glyphs++);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
str++;
|
|
}
|
|
}
|
|
|
|
static void get_fontface_advances(IDWriteFontFace *fontface, FLOAT emsize, const UINT16 *glyphs, FLOAT *advances, UINT32 count)
|
|
{
|
|
DWRITE_FONT_METRICS fontmetrics;
|
|
UINT32 i;
|
|
|
|
IDWriteFontFace_GetMetrics(fontface, &fontmetrics);
|
|
for (i = 0; i < count; i++) {
|
|
DWRITE_GLYPH_METRICS metrics;
|
|
HRESULT hr;
|
|
|
|
hr = IDWriteFontFace_GetDesignGlyphMetrics(fontface, glyphs + i, 1, &metrics, FALSE);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
|
|
advances[i] = (FLOAT)metrics.advanceWidth * emsize / (FLOAT)fontmetrics.designUnitsPerEm;
|
|
}
|
|
}
|
|
|
|
static void test_GetGlyphs(void)
|
|
{
|
|
static const WCHAR test1W[] = {'<','B',' ','C',0};
|
|
static const WCHAR test2W[] = {'<','B','\t','C',0};
|
|
static const WCHAR test3W[] = {0x202a,0x202c,0};
|
|
DWRITE_SHAPING_GLYPH_PROPERTIES shapingprops[20];
|
|
DWRITE_SHAPING_TEXT_PROPERTIES props[20];
|
|
UINT32 maxglyphcount, actual_count;
|
|
FLOAT advances[10], advances2[10];
|
|
IDWriteTextAnalyzer *analyzer;
|
|
IDWriteFontFace *fontface;
|
|
DWRITE_SCRIPT_ANALYSIS sa;
|
|
DWRITE_GLYPH_OFFSET offsets[10];
|
|
UINT16 clustermap[10];
|
|
UINT16 glyphs1[10];
|
|
UINT16 glyphs2[10];
|
|
HRESULT hr;
|
|
|
|
hr = IDWriteFactory_CreateTextAnalyzer(factory, &analyzer);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
|
|
fontface = create_fontface();
|
|
|
|
maxglyphcount = 1;
|
|
sa.script = 0;
|
|
sa.shapes = DWRITE_SCRIPT_SHAPES_DEFAULT;
|
|
hr = IDWriteTextAnalyzer_GetGlyphs(analyzer, test1W, lstrlenW(test1W), fontface, FALSE, FALSE, &sa, NULL,
|
|
NULL, NULL, NULL, 0, maxglyphcount, clustermap, props, glyphs1, shapingprops, &actual_count);
|
|
ok(hr == E_NOT_SUFFICIENT_BUFFER, "got 0x%08x\n", hr);
|
|
|
|
if (0) {
|
|
/* NULL fontface - crashes on Windows */
|
|
hr = IDWriteTextAnalyzer_GetGlyphs(analyzer, test1W, lstrlenW(test1W), NULL, FALSE, FALSE, &sa, NULL,
|
|
NULL, NULL, NULL, 0, maxglyphcount, clustermap, props, glyphs1, shapingprops, &actual_count);
|
|
}
|
|
|
|
/* invalid script id */
|
|
maxglyphcount = 10;
|
|
actual_count = 0;
|
|
sa.script = 999;
|
|
sa.shapes = DWRITE_SCRIPT_SHAPES_DEFAULT;
|
|
hr = IDWriteTextAnalyzer_GetGlyphs(analyzer, test1W, lstrlenW(test1W), fontface, FALSE, FALSE, &sa, NULL,
|
|
NULL, NULL, NULL, 0, maxglyphcount, clustermap, props, glyphs1, shapingprops, &actual_count);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
ok(actual_count == 4, "got %d\n", actual_count);
|
|
ok(sa.script == 999, "got %u\n", sa.script);
|
|
|
|
/* no '\t' -> ' ' replacement */
|
|
maxglyphcount = 10;
|
|
actual_count = 0;
|
|
hr = IDWriteTextAnalyzer_GetGlyphs(analyzer, test1W, lstrlenW(test1W), fontface, FALSE, FALSE, &sa, NULL,
|
|
NULL, NULL, NULL, 0, maxglyphcount, clustermap, props, glyphs1, shapingprops, &actual_count);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
ok(actual_count == 4, "got %d\n", actual_count);
|
|
|
|
actual_count = 0;
|
|
hr = IDWriteTextAnalyzer_GetGlyphs(analyzer, test2W, lstrlenW(test2W), fontface, FALSE, FALSE, &sa, NULL,
|
|
NULL, NULL, NULL, 0, maxglyphcount, clustermap, props, glyphs2, shapingprops, &actual_count);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
ok(actual_count == 4, "got %d\n", actual_count);
|
|
ok(glyphs1[2] != glyphs2[2], "got %d\n", glyphs1[2]);
|
|
|
|
/* check that mirroring works */
|
|
maxglyphcount = 10;
|
|
actual_count = 0;
|
|
hr = IDWriteTextAnalyzer_GetGlyphs(analyzer, test1W, lstrlenW(test1W), fontface, FALSE, FALSE, &sa, NULL,
|
|
NULL, NULL, NULL, 0, maxglyphcount, clustermap, props, glyphs1, shapingprops, &actual_count);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
ok(actual_count == 4, "got %d\n", actual_count);
|
|
|
|
actual_count = 0;
|
|
hr = IDWriteTextAnalyzer_GetGlyphs(analyzer, test1W, lstrlenW(test1W), fontface, FALSE, TRUE, &sa, NULL,
|
|
NULL, NULL, NULL, 0, maxglyphcount, clustermap, props, glyphs2, shapingprops, &actual_count);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
ok(actual_count == 4, "got %d\n", actual_count);
|
|
ok(glyphs1[0] != glyphs2[0], "got %d\n", glyphs1[0]);
|
|
|
|
/* embedded control codes, with unknown script id 0 */
|
|
get_fontface_glyphs(fontface, test3W, glyphs2);
|
|
get_fontface_advances(fontface, 10.0, glyphs2, advances2, 2);
|
|
|
|
actual_count = 0;
|
|
hr = IDWriteTextAnalyzer_GetGlyphs(analyzer, test3W, lstrlenW(test3W), fontface, FALSE, TRUE, &sa, NULL,
|
|
NULL, NULL, NULL, 0, maxglyphcount, clustermap, props, glyphs1, shapingprops, &actual_count);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
ok(actual_count == 2, "got %d\n", actual_count);
|
|
ok(glyphs1[0] == glyphs2[0], "got %u, expected %u\n", glyphs1[0], glyphs2[0]);
|
|
ok(glyphs1[1] == glyphs2[1], "got %u, expected %u\n", glyphs1[1], glyphs2[1]);
|
|
ok(shapingprops[0].isClusterStart == 1, "got %d\n", shapingprops[0].isClusterStart);
|
|
ok(shapingprops[0].isZeroWidthSpace == 0, "got %d\n", shapingprops[0].isZeroWidthSpace);
|
|
ok(shapingprops[1].isClusterStart == 1, "got %d\n", shapingprops[1].isClusterStart);
|
|
ok(shapingprops[1].isZeroWidthSpace == 0, "got %d\n", shapingprops[1].isZeroWidthSpace);
|
|
ok(clustermap[0] == 0, "got %d\n", clustermap[0]);
|
|
ok(clustermap[1] == 1, "got %d\n", clustermap[1]);
|
|
|
|
memset(advances, 0, sizeof(advances));
|
|
hr = IDWriteTextAnalyzer_GetGlyphPlacements(analyzer, test3W, clustermap, props, lstrlenW(test3W),
|
|
glyphs1, shapingprops, actual_count, fontface, 10.0, FALSE, FALSE, &sa, NULL, NULL,
|
|
NULL, 0, advances, offsets);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
ok(advances[0] == advances2[0], "got %.2f, expected %.2f\n", advances[0], advances2[0]);
|
|
ok(advances[1] == advances2[1], "got %.2f, expected %.2f\n", advances[1], advances2[1]);
|
|
|
|
/* embedded control codes with proper script */
|
|
sa.script = 0;
|
|
get_script_analysis(test3W, &sa);
|
|
ok(sa.script != 0, "got %d\n", sa.script);
|
|
actual_count = 0;
|
|
hr = IDWriteTextAnalyzer_GetGlyphs(analyzer, test3W, lstrlenW(test3W), fontface, FALSE, FALSE, &sa, NULL,
|
|
NULL, NULL, NULL, 0, maxglyphcount, clustermap, props, glyphs1, shapingprops, &actual_count);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
ok(actual_count == 2, "got %d\n", actual_count);
|
|
ok(glyphs1[0] == glyphs2[0], "got %u, expected %u\n", glyphs1[0], glyphs2[0]);
|
|
ok(glyphs1[1] == glyphs2[1], "got %u, expected %u\n", glyphs1[1], glyphs2[1]);
|
|
ok(shapingprops[0].isClusterStart == 1, "got %d\n", shapingprops[0].isClusterStart);
|
|
ok(shapingprops[0].isZeroWidthSpace == 0, "got %d\n", shapingprops[0].isZeroWidthSpace);
|
|
ok(shapingprops[1].isClusterStart == 1, "got %d\n", shapingprops[1].isClusterStart);
|
|
ok(shapingprops[1].isZeroWidthSpace == 0, "got %d\n", shapingprops[1].isZeroWidthSpace);
|
|
ok(clustermap[0] == 0, "got %d\n", clustermap[0]);
|
|
ok(clustermap[1] == 1, "got %d\n", clustermap[1]);
|
|
|
|
memset(advances, 0, sizeof(advances));
|
|
hr = IDWriteTextAnalyzer_GetGlyphPlacements(analyzer, test3W, clustermap, props, lstrlenW(test3W),
|
|
glyphs1, shapingprops, actual_count, fontface, 10.0, FALSE, FALSE, &sa, NULL, NULL,
|
|
NULL, 0, advances, offsets);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
ok(advances[0] == advances2[0], "got %.2f, expected %.2f\n", advances[0], advances2[0]);
|
|
ok(advances[1] == advances2[1], "got %.2f, expected %.2f\n", advances[1], advances2[1]);
|
|
|
|
/* DWRITE_SCRIPT_SHAPES_NO_VISUAL run */
|
|
maxglyphcount = 10;
|
|
actual_count = 0;
|
|
sa.script = 0;
|
|
sa.shapes = DWRITE_SCRIPT_SHAPES_NO_VISUAL;
|
|
hr = IDWriteTextAnalyzer_GetGlyphs(analyzer, test1W, lstrlenW(test1W), fontface, FALSE, FALSE, &sa, NULL,
|
|
NULL, NULL, NULL, 0, maxglyphcount, clustermap, props, glyphs1, shapingprops, &actual_count);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
ok(actual_count == 4, "got %d\n", actual_count);
|
|
ok(sa.script == 0, "got %u\n", sa.script);
|
|
ok(!shapingprops[0].isZeroWidthSpace, "got %d\n", shapingprops[0].isZeroWidthSpace);
|
|
|
|
IDWriteTextAnalyzer_Release(analyzer);
|
|
IDWriteFontFace_Release(fontface);
|
|
}
|
|
|
|
static BOOL has_feature(const DWRITE_FONT_FEATURE_TAG *tags, UINT32 count, DWRITE_FONT_FEATURE_TAG feature)
|
|
{
|
|
UINT32 i;
|
|
|
|
for (i = 0; i < count; i++)
|
|
if (tags[i] == feature) return TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
static void test_GetTypographicFeatures(void)
|
|
{
|
|
static const WCHAR localeW[] = {'c','a','d','a','b','r','a',0};
|
|
static const WCHAR arabicW[] = {0x064a,0x064f,0x0633,0};
|
|
static const WCHAR abcW[] = {'a','b','c',0};
|
|
DWRITE_FONT_FEATURE_TAG tags[20];
|
|
IDWriteTextAnalyzer2 *analyzer2;
|
|
IDWriteTextAnalyzer *analyzer;
|
|
IDWriteFontFace *fontface;
|
|
DWRITE_SCRIPT_ANALYSIS sa;
|
|
UINT32 count;
|
|
HRESULT hr;
|
|
BOOL ret;
|
|
|
|
hr = IDWriteFactory_CreateTextAnalyzer(factory, &analyzer);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
|
|
hr = IDWriteTextAnalyzer_QueryInterface(analyzer, &IID_IDWriteTextAnalyzer2, (void**)&analyzer2);
|
|
IDWriteTextAnalyzer_Release(analyzer);
|
|
if (hr != S_OK) {
|
|
win_skip("GetTypographicFeatures() is not supported.\n");
|
|
return;
|
|
}
|
|
|
|
fontface = create_fontface();
|
|
|
|
get_script_analysis(abcW, &sa);
|
|
count = 0;
|
|
hr = IDWriteTextAnalyzer2_GetTypographicFeatures(analyzer2, fontface, sa, NULL, 0, &count, NULL);
|
|
todo_wine {
|
|
ok(hr == E_NOT_SUFFICIENT_BUFFER, "got 0x%08x\n", hr);
|
|
ok(count > 0, "got %u\n", count);
|
|
}
|
|
/* invalid locale name is ignored */
|
|
get_script_analysis(abcW, &sa);
|
|
count = 0;
|
|
hr = IDWriteTextAnalyzer2_GetTypographicFeatures(analyzer2, fontface, sa, localeW, 0, &count, NULL);
|
|
todo_wine {
|
|
ok(hr == E_NOT_SUFFICIENT_BUFFER, "got 0x%08x\n", hr);
|
|
ok(count > 0, "got %u\n", count);
|
|
}
|
|
/* both GSUB and GPOS features are reported */
|
|
get_script_analysis(arabicW, &sa);
|
|
memset(tags, 0, sizeof(tags));
|
|
count = 0;
|
|
hr = IDWriteTextAnalyzer2_GetTypographicFeatures(analyzer2, fontface, sa, NULL, sizeof(tags)/sizeof(tags[0]), &count, tags);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
todo_wine {
|
|
ok(count > 0, "got %u\n", count);
|
|
ret = has_feature(tags, count, DWRITE_FONT_FEATURE_TAG_CONTEXTUAL_ALTERNATES);
|
|
ok(ret, "expected 'calt' feature\n");
|
|
ret = has_feature(tags, count, DWRITE_FONT_FEATURE_TAG_MARK_TO_MARK_POSITIONING);
|
|
ok(ret, "expected 'mkmk' feature\n");
|
|
}
|
|
get_script_analysis(abcW, &sa);
|
|
memset(tags, 0, sizeof(tags));
|
|
count = 0;
|
|
hr = IDWriteTextAnalyzer2_GetTypographicFeatures(analyzer2, fontface, sa, NULL, sizeof(tags)/sizeof(tags[0]), &count, tags);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
todo_wine {
|
|
ok(count > 0, "got %u\n", count);
|
|
ret = has_feature(tags, count, DWRITE_FONT_FEATURE_TAG_GLYPH_COMPOSITION_DECOMPOSITION);
|
|
ok(ret, "expected 'ccmp' feature\n");
|
|
ret = has_feature(tags, count, DWRITE_FONT_FEATURE_TAG_MARK_TO_MARK_POSITIONING);
|
|
ok(ret, "expected 'mkmk' feature\n");
|
|
}
|
|
ret = has_feature(tags, count, DWRITE_FONT_FEATURE_TAG_CONTEXTUAL_ALTERNATES);
|
|
ok(!ret, "unexpected 'calt' feature\n");
|
|
|
|
IDWriteFontFace_Release(fontface);
|
|
IDWriteTextAnalyzer2_Release(analyzer2);
|
|
}
|
|
|
|
static void test_GetGlyphPlacements(void)
|
|
{
|
|
DWRITE_SHAPING_GLYPH_PROPERTIES glyphprops[2];
|
|
DWRITE_SHAPING_TEXT_PROPERTIES textprops[2];
|
|
static const WCHAR aW[] = {'A','D',0};
|
|
UINT16 clustermap[2], glyphs[2];
|
|
DWRITE_GLYPH_OFFSET offsets[2];
|
|
IDWriteTextAnalyzer *analyzer;
|
|
IDWriteFontFace *fontface;
|
|
DWRITE_SCRIPT_ANALYSIS sa;
|
|
FLOAT advances[2];
|
|
UINT32 count, len;
|
|
WCHAR *path;
|
|
HRESULT hr;
|
|
|
|
hr = IDWriteFactory_CreateTextAnalyzer(factory, &analyzer);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
|
|
path = create_testfontfile(test_fontfile);
|
|
fontface = create_testfontface(path);
|
|
|
|
get_script_analysis(aW, &sa);
|
|
count = 0;
|
|
len = lstrlenW(aW);
|
|
hr = IDWriteTextAnalyzer_GetGlyphs(analyzer, aW, len, fontface, FALSE, FALSE, &sa, NULL,
|
|
NULL, NULL, NULL, 0, len, clustermap, textprops, glyphs, glyphprops, &count);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
ok(count == 2, "got %u\n", count);
|
|
|
|
/* just return on zero glyphs */
|
|
advances[0] = advances[1] = 1.0;
|
|
offsets[0].advanceOffset = offsets[0].ascenderOffset = 2.0;
|
|
hr = IDWriteTextAnalyzer_GetGlyphPlacements(analyzer, aW, clustermap, textprops,
|
|
len, glyphs, glyphprops, 0, fontface, 0.0, FALSE, FALSE, &sa, NULL, NULL,
|
|
NULL, 0, advances, offsets);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
ok(advances[0] == 1.0, "got %.2f\n", advances[0]);
|
|
ok(offsets[0].advanceOffset == 2.0 && offsets[0].ascenderOffset == 2.0, "got %.2f,%.2f\n",
|
|
offsets[0].advanceOffset, offsets[0].ascenderOffset);
|
|
|
|
/* advances/offsets are scaled with provided font emSize and designed eM box size */
|
|
advances[0] = advances[1] = 1.0;
|
|
memset(offsets, 0xcc, sizeof(offsets));
|
|
hr = IDWriteTextAnalyzer_GetGlyphPlacements(analyzer, aW, clustermap, textprops,
|
|
len, glyphs, glyphprops, len, fontface, 0.0, FALSE, FALSE, &sa, NULL, NULL,
|
|
NULL, 0, advances, offsets);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
ok(advances[0] == 0.0, "got %.2f\n", advances[0]);
|
|
ok(offsets[0].advanceOffset == 0.0 && offsets[0].ascenderOffset == 0.0, "got %.2f,%.2f\n",
|
|
offsets[0].advanceOffset, offsets[0].ascenderOffset);
|
|
|
|
advances[0] = advances[1] = 1.0;
|
|
memset(offsets, 0xcc, sizeof(offsets));
|
|
hr = IDWriteTextAnalyzer_GetGlyphPlacements(analyzer, aW, clustermap, textprops,
|
|
len, glyphs, glyphprops, len, fontface, 2048.0, FALSE, FALSE, &sa, NULL, NULL,
|
|
NULL, 0, advances, offsets);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
ok(advances[0] == 1000.0, "got %.2f\n", advances[0]);
|
|
ok(offsets[0].advanceOffset == 0.0 && offsets[0].ascenderOffset == 0.0, "got %.2f,%.2f\n",
|
|
offsets[0].advanceOffset, offsets[0].ascenderOffset);
|
|
|
|
advances[0] = advances[1] = 1.0;
|
|
memset(offsets, 0xcc, sizeof(offsets));
|
|
hr = IDWriteTextAnalyzer_GetGlyphPlacements(analyzer, aW, clustermap, textprops,
|
|
len, glyphs, glyphprops, len, fontface, 1024.0, FALSE, FALSE, &sa, NULL, NULL,
|
|
NULL, 0, advances, offsets);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
ok(advances[0] == 500.0, "got %.2f\n", advances[0]);
|
|
ok(advances[1] == 500.0, "got %.2f\n", advances[1]);
|
|
ok(offsets[0].advanceOffset == 0.0 && offsets[0].ascenderOffset == 0.0, "got %.2f,%.2f\n",
|
|
offsets[0].advanceOffset, offsets[0].ascenderOffset);
|
|
|
|
advances[0] = advances[1] = 1.0;
|
|
memset(offsets, 0xcc, sizeof(offsets));
|
|
hr = IDWriteTextAnalyzer_GetGlyphPlacements(analyzer, aW, clustermap, textprops,
|
|
len, glyphs, glyphprops, len, fontface, 20.48, FALSE, FALSE, &sa, NULL, NULL,
|
|
NULL, 0, advances, offsets);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
ok(advances[0] == 10.0, "got %.2f\n", advances[0]);
|
|
ok(advances[1] == 10.0, "got %.2f\n", advances[1]);
|
|
ok(offsets[0].advanceOffset == 0.0 && offsets[0].ascenderOffset == 0.0, "got %.2f,%.2f\n",
|
|
offsets[0].advanceOffset, offsets[0].ascenderOffset);
|
|
|
|
/* without clustermap */
|
|
advances[0] = advances[1] = 1.0;
|
|
hr = IDWriteTextAnalyzer_GetGlyphPlacements(analyzer, aW, NULL, textprops,
|
|
len, glyphs, glyphprops, len, fontface, 1024.0, FALSE, FALSE, &sa, NULL, NULL,
|
|
NULL, 0, advances, offsets);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
ok(advances[0] == 500.0, "got %.2f\n", advances[0]);
|
|
ok(advances[1] == 500.0, "got %.2f\n", advances[1]);
|
|
|
|
/* it's happy to use negative size too */
|
|
advances[0] = advances[1] = 1.0;
|
|
memset(offsets, 0xcc, sizeof(offsets));
|
|
hr = IDWriteTextAnalyzer_GetGlyphPlacements(analyzer, aW, clustermap, textprops,
|
|
len, glyphs, glyphprops, len, fontface, -10.24, FALSE, FALSE, &sa, NULL, NULL,
|
|
NULL, 0, advances, offsets);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
ok(advances[0] == -5.0, "got %.2f\n", advances[0]);
|
|
ok(offsets[0].advanceOffset == 0.0 && offsets[0].ascenderOffset == 0.0, "got %.2f,%.2f\n",
|
|
offsets[0].advanceOffset, offsets[0].ascenderOffset);
|
|
|
|
/* DWRITE_SCRIPT_SHAPES_NO_VISUAL has no effect on placement */
|
|
sa.shapes = DWRITE_SCRIPT_SHAPES_NO_VISUAL;
|
|
advances[0] = advances[1] = 1.0f;
|
|
memset(offsets, 0xcc, sizeof(offsets));
|
|
hr = IDWriteTextAnalyzer_GetGlyphPlacements(analyzer, aW, clustermap, textprops,
|
|
len, glyphs, glyphprops, len, fontface, 2048.0f, FALSE, FALSE, &sa, NULL, NULL,
|
|
NULL, 0, advances, offsets);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
ok(advances[0] == 1000.0f, "got %.2f\n", advances[0]);
|
|
ok(advances[1] == 1000.0f, "got %.2f\n", advances[1]);
|
|
ok(offsets[0].advanceOffset == 0.0f && offsets[0].ascenderOffset == 0.0f, "got %.2f,%.2f\n",
|
|
offsets[0].advanceOffset, offsets[0].ascenderOffset);
|
|
|
|
/* isZeroWidthSpace */
|
|
sa.shapes = DWRITE_SCRIPT_SHAPES_DEFAULT;
|
|
advances[0] = advances[1] = 1.0f;
|
|
memset(offsets, 0xcc, sizeof(offsets));
|
|
glyphprops[0].isZeroWidthSpace = 1;
|
|
hr = IDWriteTextAnalyzer_GetGlyphPlacements(analyzer, aW, clustermap, textprops,
|
|
len, glyphs, glyphprops, len, fontface, 2048.0f, FALSE, FALSE, &sa, NULL, NULL,
|
|
NULL, 0, advances, offsets);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
ok(advances[0] == 0.0f, "got %.2f\n", advances[0]);
|
|
ok(advances[1] == 1000.0f, "got %.2f\n", advances[1]);
|
|
ok(offsets[0].advanceOffset == 0.0f && offsets[0].ascenderOffset == 0.0f, "got %.2f,%.2f\n",
|
|
offsets[0].advanceOffset, offsets[0].ascenderOffset);
|
|
|
|
IDWriteTextAnalyzer_Release(analyzer);
|
|
IDWriteFontFace_Release(fontface);
|
|
DELETE_FONTFILE(path);
|
|
}
|
|
|
|
struct spacing_test {
|
|
FLOAT leading;
|
|
FLOAT trailing;
|
|
FLOAT min_advance;
|
|
FLOAT advances[3];
|
|
FLOAT offsets[3];
|
|
FLOAT modified_advances[3];
|
|
FLOAT modified_offsets[3];
|
|
BOOL single_cluster;
|
|
BOOL is_ZWS[3];
|
|
};
|
|
|
|
static const struct spacing_test spacing_tests[] = {
|
|
{ 0.0, 0.0, 0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0, 11.0 }, { 2.0, 3.0 } }, /* 0 */
|
|
{ 0.0, 0.0, 2.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0, 11.0 }, { 2.0, 3.0 } },
|
|
{ 1.0, 0.0, 0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 11.0, 12.0 }, { 3.0, 4.0 } },
|
|
{ 1.0, 1.0, 0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 12.0, 13.0 }, { 3.0, 4.0 } },
|
|
{ 1.0, -1.0, 0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0, 11.0 }, { 3.0, 4.0 } },
|
|
{ 0.0, -10.0, 0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 0.0, 1.0 }, { 2.0, 3.0 } }, /* 5 */
|
|
{ -5.0, -4.0, 5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 5.0, 5.0 }, { -1.0, -0.5 } },
|
|
{ -5.0, -5.0, 5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 5.0, 5.0 }, { -0.5, 0.0 } },
|
|
{ 2.0, 0.0, 5.0, { 1.0, 2.0 }, { 2.0, 3.0 }, { 7.0, 7.0 }, { 6.0, 6.5 } },
|
|
{ 2.0, 1.0, 5.0, { 1.0, 2.0 }, { 2.0, 3.0 }, { 8.0, 8.0 }, { 6.0, 6.5 } },
|
|
{ 2.0, -10.0, 5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 7.0, 7.0 }, { 4.0, 5.0 } }, /* 10 */
|
|
{ 1.0, -10.0, 5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 6.0, 6.0 }, { 3.0, 4.0 } },
|
|
{ -10.0, 1.0, 5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 6.0, 6.0 }, { -3.0, -3.0 } },
|
|
{ 0.0, -10.0, 5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 5.0, 5.0 }, { 2.0, 3.0 } },
|
|
{ 1.0, -10.0, -5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 0.0, 0.0 }, { 2.0, 3.0 } },
|
|
{ -10.0, 1.0, 5.0, { 8.0, 11.0 }, { 2.0, 3.0 }, { 6.0, 6.0 }, { -1.0, -3.0 } }, /* 15 */
|
|
/* cluster of more than 1 glyph */
|
|
{ 0.0, 0.0, 0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0, 11.0 }, { 2.0, 3.0 }, TRUE },
|
|
{ 1.0, 0.0, 0.0, { 10.0, 11.0 }, { 2.0, 3.5 }, { 11.0, 11.0 }, { 3.0, 3.5 }, TRUE },
|
|
{ 1.0, 1.0, 0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 11.0, 12.0 }, { 3.0, 3.0 }, TRUE },
|
|
{ 1.0, -1.0, 0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 11.0, 10.0 }, { 3.0, 3.0 }, TRUE },
|
|
{ 0.0, -10.0, 0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0, 1.0 }, { 2.0, 3.0 }, TRUE }, /* 20 */
|
|
{ 0.0, -10.0, 5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0, 1.0 }, { 2.0, 3.0 }, TRUE },
|
|
{ 1.0, -10.0, -5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 0.0, 0.0 }, { 2.0, 3.0 }, TRUE },
|
|
{ -5.0, -10.0, 4.0, { 10.0, 11.0, 12.0 }, { 2.0, 3.0, 4.0 }, { 5.0, 11.0, 2.0 }, { -3.0, 3.0, 4.0 }, TRUE },
|
|
{ -10.0, -10.0, 4.0, { 10.0, 11.0, 12.0 }, { 2.0, 3.0, 4.0 }, { 0.0, 11.0, 2.0 }, { -8.0, 3.0, 4.0 }, TRUE },
|
|
{ -10.0, -10.0, 5.0, { 10.0, 1.0, 12.0 }, { 2.0, 3.0, 4.0 }, { 1.0, 1.0, 3.0 }, { -7.0, 3.0, 4.0 }, TRUE }, /* 25 */
|
|
{ -10.0, 1.0, 5.0, { 10.0, 1.0, 2.0 }, { 2.0, 3.0, 4.0 }, { 2.0, 1.0, 3.0 }, { -6.0, 3.0, 4.0 }, TRUE },
|
|
{ 1.0, -10.0, 5.0, { 2.0, 1.0, 10.0 }, { 2.0, 3.0, 4.0 }, { 3.0, 1.0, 2.0 }, { 3.0, 3.0, 4.0 }, TRUE },
|
|
{ -10.0, -10.0, 5.0, { 11.0, 1.0, 11.0 }, { 2.0, 3.0, 4.0 }, { 2.0, 1.0, 2.0 }, { -7.0, 3.0, 4.0 }, TRUE },
|
|
/* isZeroWidthSpace set */
|
|
{ 1.0, 0.0, 0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0, 12.0 }, { 2.0, 4.0 }, FALSE, { TRUE, FALSE } },
|
|
{ 1.0, 1.0, 0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0, 13.0 }, { 2.0, 4.0 }, FALSE, { TRUE, FALSE } }, /* 30 */
|
|
{ 1.0, -1.0, 0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0, 11.0 }, { 3.0, 3.0 }, FALSE, { FALSE, TRUE } },
|
|
{ 0.0, -10.0, 0.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0, 1.0 }, { 2.0, 3.0 }, FALSE, { TRUE, FALSE } },
|
|
{ -5.0, -4.0, 5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 5.0, 11.0 }, { -1.0, 3.0 }, FALSE, { FALSE, TRUE } },
|
|
{ -5.0, -5.0, 5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0, 11.0 }, { 2.0, 3.0 }, FALSE, { TRUE, TRUE } },
|
|
{ 2.0, 0.0, 5.0, { 1.0, 2.0 }, { 2.0, 3.0 }, { 7.0, 2.0 }, { 6.0, 3.0 }, FALSE, { FALSE, TRUE } }, /* 35 */
|
|
{ 2.0, 1.0, 5.0, { 1.0, 2.0 }, { 2.0, 3.0 }, { 8.0, 2.0 }, { 6.0, 3.0 }, FALSE, { FALSE, TRUE } },
|
|
{ 2.0, -10.0, 5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0, 11.0 }, { 2.0, 3.0 }, FALSE, { TRUE, TRUE } },
|
|
{ 1.0, -10.0, 5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 6.0, 11.0 }, { 3.0, 3.0 }, FALSE, { FALSE, TRUE } },
|
|
{ -10.0, 1.0, 5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 10.0, 11.0 }, { 2.0, 3.0 }, FALSE, { TRUE, TRUE } },
|
|
{ 0.0, -10.0, 5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 5.0, 11.0 }, { 2.0, 3.0 }, FALSE, { FALSE, TRUE } }, /* 40 */
|
|
{ 1.0, -10.0, -5.0, { 10.0, 11.0 }, { 2.0, 3.0 }, { 0.0, 0.0 }, { 2.0, 3.0 }, FALSE, { TRUE, FALSE } },
|
|
{ -10.0, 1.0, 5.0, { 8.0, 11.0 }, { 2.0, 3.0 }, { 6.0, 11.0 }, { -1.0, 3.0 }, FALSE, { FALSE, TRUE } },
|
|
};
|
|
|
|
static void test_ApplyCharacterSpacing(void)
|
|
{
|
|
DWRITE_SHAPING_GLYPH_PROPERTIES props[3];
|
|
IDWriteTextAnalyzer1 *analyzer1;
|
|
IDWriteTextAnalyzer *analyzer;
|
|
UINT16 clustermap[2];
|
|
HRESULT hr;
|
|
int i;
|
|
|
|
hr = IDWriteFactory_CreateTextAnalyzer(factory, &analyzer);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
|
|
hr = IDWriteTextAnalyzer_QueryInterface(analyzer, &IID_IDWriteTextAnalyzer1, (void**)&analyzer1);
|
|
IDWriteTextAnalyzer_Release(analyzer);
|
|
if (hr != S_OK) {
|
|
win_skip("ApplyCharacterSpacing() is not supported.\n");
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < sizeof(spacing_tests)/sizeof(spacing_tests[0]); i++) {
|
|
const struct spacing_test *ptr = spacing_tests + i;
|
|
DWRITE_GLYPH_OFFSET offsets[3];
|
|
UINT32 glyph_count;
|
|
FLOAT advances[3];
|
|
|
|
offsets[0].advanceOffset = ptr->offsets[0];
|
|
offsets[1].advanceOffset = ptr->offsets[1];
|
|
offsets[2].advanceOffset = ptr->offsets[2];
|
|
/* Ascender offsets are never thouched as spacing applies in reading direction only,
|
|
we'll only test them to see if they are not changed */
|
|
offsets[0].ascenderOffset = 23.0;
|
|
offsets[1].ascenderOffset = 32.0;
|
|
offsets[2].ascenderOffset = 31.0;
|
|
|
|
glyph_count = ptr->advances[2] > 0.0 ? 3 : 2;
|
|
if (ptr->single_cluster) {
|
|
clustermap[0] = 0;
|
|
clustermap[1] = 0;
|
|
}
|
|
else {
|
|
/* trivial case with one glyph per cluster */
|
|
clustermap[0] = 0;
|
|
clustermap[1] = 1;
|
|
}
|
|
|
|
advances[0] = advances[1] = 123.45;
|
|
memset(props, 0, sizeof(props));
|
|
props[0].isZeroWidthSpace = ptr->is_ZWS[0];
|
|
props[1].isZeroWidthSpace = ptr->is_ZWS[1];
|
|
props[2].isZeroWidthSpace = ptr->is_ZWS[2];
|
|
|
|
hr = IDWriteTextAnalyzer1_ApplyCharacterSpacing(analyzer1,
|
|
ptr->leading,
|
|
ptr->trailing,
|
|
ptr->min_advance,
|
|
sizeof(clustermap)/sizeof(clustermap[0]),
|
|
glyph_count,
|
|
clustermap,
|
|
ptr->advances,
|
|
offsets,
|
|
props,
|
|
advances,
|
|
offsets);
|
|
/* invalid argument cases */
|
|
if (ptr->min_advance < 0.0)
|
|
ok(hr == E_INVALIDARG, "%d: got 0x%08x\n", i, hr);
|
|
else
|
|
ok(hr == S_OK, "%d: got 0x%08x\n", i, hr);
|
|
if (hr == S_OK) {
|
|
ok(ptr->modified_advances[0] == advances[0], "%d: got advance[0] %.2f, expected %.2f\n", i, advances[0], ptr->modified_advances[0]);
|
|
ok(ptr->modified_advances[1] == advances[1], "%d: got advance[1] %.2f, expected %.2f\n", i, advances[1], ptr->modified_advances[1]);
|
|
if (glyph_count > 2)
|
|
ok(ptr->modified_advances[2] == advances[2], "%d: got advance[2] %.2f, expected %.2f\n", i, advances[2], ptr->modified_advances[2]);
|
|
|
|
ok(ptr->modified_offsets[0] == offsets[0].advanceOffset, "%d: got offset[0] %.2f, expected %.2f\n", i,
|
|
offsets[0].advanceOffset, ptr->modified_offsets[0]);
|
|
ok(ptr->modified_offsets[1] == offsets[1].advanceOffset, "%d: got offset[1] %.2f, expected %.2f\n", i,
|
|
offsets[1].advanceOffset, ptr->modified_offsets[1]);
|
|
if (glyph_count > 2)
|
|
ok(ptr->modified_offsets[2] == offsets[2].advanceOffset, "%d: got offset[2] %.2f, expected %.2f\n", i,
|
|
offsets[2].advanceOffset, ptr->modified_offsets[2]);
|
|
|
|
ok(offsets[0].ascenderOffset == 23.0, "%d: unexpected ascenderOffset %.2f\n", i, offsets[0].ascenderOffset);
|
|
ok(offsets[1].ascenderOffset == 32.0, "%d: unexpected ascenderOffset %.2f\n", i, offsets[1].ascenderOffset);
|
|
ok(offsets[2].ascenderOffset == 31.0, "%d: unexpected ascenderOffset %.2f\n", i, offsets[2].ascenderOffset);
|
|
}
|
|
else {
|
|
ok(ptr->modified_advances[0] == advances[0], "%d: got advance[0] %.2f, expected %.2f\n", i, advances[0], ptr->modified_advances[0]);
|
|
ok(ptr->modified_advances[1] == advances[1], "%d: got advance[1] %.2f, expected %.2f\n", i, advances[1], ptr->modified_advances[1]);
|
|
ok(ptr->offsets[0] == offsets[0].advanceOffset, "%d: got offset[0] %.2f, expected %.2f\n", i,
|
|
offsets[0].advanceOffset, ptr->modified_offsets[0]);
|
|
ok(ptr->offsets[1] == offsets[1].advanceOffset, "%d: got offset[1] %.2f, expected %.2f\n", i,
|
|
offsets[1].advanceOffset, ptr->modified_offsets[1]);
|
|
ok(offsets[0].ascenderOffset == 23.0, "%d: unexpected ascenderOffset %.2f\n", i, offsets[0].ascenderOffset);
|
|
ok(offsets[1].ascenderOffset == 32.0, "%d: unexpected ascenderOffset %.2f\n", i, offsets[1].ascenderOffset);
|
|
}
|
|
}
|
|
|
|
IDWriteTextAnalyzer1_Release(analyzer1);
|
|
}
|
|
|
|
struct orientation_transf_test {
|
|
DWRITE_GLYPH_ORIENTATION_ANGLE angle;
|
|
BOOL is_sideways;
|
|
DWRITE_MATRIX m;
|
|
};
|
|
|
|
static const struct orientation_transf_test ot_tests[] = {
|
|
{ DWRITE_GLYPH_ORIENTATION_ANGLE_0_DEGREES, FALSE, { 1.0, 0.0, 0.0, 1.0, 0.0, 0.0 } },
|
|
{ DWRITE_GLYPH_ORIENTATION_ANGLE_90_DEGREES, FALSE, { 0.0, 1.0, -1.0, 0.0, 0.0, 0.0 } },
|
|
{ DWRITE_GLYPH_ORIENTATION_ANGLE_180_DEGREES, FALSE, { -1.0, 0.0, 0.0, -1.0, 0.0, 0.0 } },
|
|
{ DWRITE_GLYPH_ORIENTATION_ANGLE_270_DEGREES, FALSE, { 0.0, -1.0, 1.0, 0.0, 0.0, 0.0 } },
|
|
{ DWRITE_GLYPH_ORIENTATION_ANGLE_0_DEGREES, TRUE, { 0.0, 1.0, -1.0, 0.0, 0.0, 0.0 } },
|
|
{ DWRITE_GLYPH_ORIENTATION_ANGLE_90_DEGREES, TRUE, { -1.0, 0.0, 0.0, -1.0, 0.0, 0.0 } },
|
|
{ DWRITE_GLYPH_ORIENTATION_ANGLE_180_DEGREES, TRUE, { 0.0, -1.0, 1.0, 0.0, 0.0, 0.0 } },
|
|
{ DWRITE_GLYPH_ORIENTATION_ANGLE_270_DEGREES, TRUE, { 1.0, 0.0, 0.0, 1.0, 0.0, 0.0 } }
|
|
};
|
|
|
|
static inline const char *dbgstr_matrix(const DWRITE_MATRIX *m)
|
|
{
|
|
static char buff[64];
|
|
sprintf(buff, "{%.2f, %.2f, %.2f, %.2f, %.2f, %.2f}", m->m11, m->m12,
|
|
m->m21, m->m22, m->dx, m->dy);
|
|
return buff;
|
|
}
|
|
|
|
static void test_GetGlyphOrientationTransform(void)
|
|
{
|
|
IDWriteTextAnalyzer2 *analyzer2;
|
|
IDWriteTextAnalyzer1 *analyzer1;
|
|
IDWriteTextAnalyzer *analyzer;
|
|
FLOAT originx, originy;
|
|
DWRITE_MATRIX m;
|
|
HRESULT hr;
|
|
int i;
|
|
|
|
hr = IDWriteFactory_CreateTextAnalyzer(factory, &analyzer);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
|
|
hr = IDWriteTextAnalyzer_QueryInterface(analyzer, &IID_IDWriteTextAnalyzer1, (void**)&analyzer1);
|
|
IDWriteTextAnalyzer_Release(analyzer);
|
|
if (hr != S_OK) {
|
|
win_skip("GetGlyphOrientationTransform() is not supported.\n");
|
|
return;
|
|
}
|
|
|
|
/* invalid angle value */
|
|
memset(&m, 0xcc, sizeof(m));
|
|
hr = IDWriteTextAnalyzer1_GetGlyphOrientationTransform(analyzer1,
|
|
DWRITE_GLYPH_ORIENTATION_ANGLE_270_DEGREES + 1, FALSE, &m);
|
|
ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);
|
|
ok(m.m11 == 0.0, "got %.2f\n", m.m11);
|
|
|
|
for (i = 0; i < sizeof(ot_tests)/sizeof(ot_tests[0]); i++) {
|
|
memset(&m, 0, sizeof(m));
|
|
hr = IDWriteTextAnalyzer1_GetGlyphOrientationTransform(analyzer1, ot_tests[i].angle,
|
|
ot_tests[i].is_sideways, &m);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
ok(!memcmp(&ot_tests[i].m, &m, sizeof(m)), "%d: wrong matrix %s\n", i, dbgstr_matrix(&m));
|
|
}
|
|
|
|
hr = IDWriteTextAnalyzer1_QueryInterface(analyzer1, &IID_IDWriteTextAnalyzer2, (void**)&analyzer2);
|
|
IDWriteTextAnalyzer1_Release(analyzer1);
|
|
if (hr != S_OK) {
|
|
win_skip("IDWriteTextAnalyzer2::GetGlyphOrientationTransform() is not supported.\n");
|
|
return;
|
|
}
|
|
|
|
/* invalid angle value */
|
|
memset(&m, 0xcc, sizeof(m));
|
|
hr = IDWriteTextAnalyzer2_GetGlyphOrientationTransform(analyzer2,
|
|
DWRITE_GLYPH_ORIENTATION_ANGLE_270_DEGREES + 1, FALSE, 0.0, 0.0, &m);
|
|
ok(hr == E_INVALIDARG, "got 0x%08x\n", hr);
|
|
ok(m.m11 == 0.0, "got %.2f\n", m.m11);
|
|
|
|
originx = 50.0;
|
|
originy = 60.0;
|
|
for (i = 0; i < sizeof(ot_tests)/sizeof(ot_tests[0]); i++) {
|
|
DWRITE_GLYPH_ORIENTATION_ANGLE angle = DWRITE_GLYPH_ORIENTATION_ANGLE_0_DEGREES;
|
|
DWRITE_MATRIX m_exp;
|
|
|
|
memset(&m, 0, sizeof(m));
|
|
|
|
/* zero offset gives same result as a call from IDWriteTextAnalyzer1 */
|
|
hr = IDWriteTextAnalyzer2_GetGlyphOrientationTransform(analyzer2, ot_tests[i].angle,
|
|
ot_tests[i].is_sideways, 0.0, 0.0, &m);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
ok(!memcmp(&ot_tests[i].m, &m, sizeof(m)), "%d: wrong matrix %s\n", i, dbgstr_matrix(&m));
|
|
|
|
m_exp = ot_tests[i].m;
|
|
hr = IDWriteTextAnalyzer2_GetGlyphOrientationTransform(analyzer2, ot_tests[i].angle,
|
|
ot_tests[i].is_sideways, originx, originy, &m);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
|
|
/* 90 degrees more for sideways */
|
|
if (ot_tests[i].is_sideways) {
|
|
switch (ot_tests[i].angle)
|
|
{
|
|
case DWRITE_GLYPH_ORIENTATION_ANGLE_0_DEGREES:
|
|
angle = DWRITE_GLYPH_ORIENTATION_ANGLE_90_DEGREES;
|
|
break;
|
|
case DWRITE_GLYPH_ORIENTATION_ANGLE_90_DEGREES:
|
|
angle = DWRITE_GLYPH_ORIENTATION_ANGLE_180_DEGREES;
|
|
break;
|
|
case DWRITE_GLYPH_ORIENTATION_ANGLE_180_DEGREES:
|
|
angle = DWRITE_GLYPH_ORIENTATION_ANGLE_270_DEGREES;
|
|
break;
|
|
case DWRITE_GLYPH_ORIENTATION_ANGLE_270_DEGREES:
|
|
angle = DWRITE_GLYPH_ORIENTATION_ANGLE_0_DEGREES;
|
|
break;
|
|
default:
|
|
;
|
|
}
|
|
}
|
|
else
|
|
angle = ot_tests[i].angle;
|
|
|
|
/* set expected offsets */
|
|
switch (angle)
|
|
{
|
|
case DWRITE_GLYPH_ORIENTATION_ANGLE_0_DEGREES:
|
|
break;
|
|
case DWRITE_GLYPH_ORIENTATION_ANGLE_90_DEGREES:
|
|
m_exp.dx = originx + originy;
|
|
m_exp.dy = originy - originx;
|
|
break;
|
|
case DWRITE_GLYPH_ORIENTATION_ANGLE_180_DEGREES:
|
|
m_exp.dx = originx + originx;
|
|
m_exp.dy = originy + originy;
|
|
break;
|
|
case DWRITE_GLYPH_ORIENTATION_ANGLE_270_DEGREES:
|
|
m_exp.dx = originx - originy;
|
|
m_exp.dy = originy + originx;
|
|
break;
|
|
default:
|
|
;
|
|
}
|
|
|
|
ok(!memcmp(&m_exp, &m, sizeof(m)), "%d: wrong matrix %s\n", i, dbgstr_matrix(&m));
|
|
}
|
|
|
|
IDWriteTextAnalyzer2_Release(analyzer2);
|
|
}
|
|
|
|
static void test_GetBaseline(void)
|
|
{
|
|
DWRITE_SCRIPT_ANALYSIS sa = { 0 };
|
|
IDWriteTextAnalyzer1 *analyzer1;
|
|
IDWriteTextAnalyzer *analyzer;
|
|
IDWriteFontFace *fontface;
|
|
INT32 baseline;
|
|
BOOL exists;
|
|
HRESULT hr;
|
|
|
|
hr = IDWriteFactory_CreateTextAnalyzer(factory, &analyzer);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
|
|
hr = IDWriteTextAnalyzer_QueryInterface(analyzer, &IID_IDWriteTextAnalyzer1, (void**)&analyzer1);
|
|
IDWriteTextAnalyzer_Release(analyzer);
|
|
if (hr != S_OK) {
|
|
win_skip("GetBaseline() is not supported.\n");
|
|
return;
|
|
}
|
|
|
|
fontface = create_fontface();
|
|
|
|
/* Tahoma doesn't have BASE table, it doesn't work even with simulation enabled */
|
|
exists = TRUE;
|
|
baseline = 456;
|
|
hr = IDWriteTextAnalyzer1_GetBaseline(analyzer1,
|
|
fontface,
|
|
DWRITE_BASELINE_DEFAULT,
|
|
FALSE,
|
|
TRUE,
|
|
sa,
|
|
NULL,
|
|
&baseline,
|
|
&exists);
|
|
todo_wine {
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
ok(baseline == 0, "got %d\n", baseline);
|
|
ok(exists == FALSE, "got %d\n", exists);
|
|
}
|
|
exists = TRUE;
|
|
baseline = 456;
|
|
hr = IDWriteTextAnalyzer1_GetBaseline(analyzer1,
|
|
fontface,
|
|
DWRITE_BASELINE_ROMAN,
|
|
FALSE,
|
|
TRUE,
|
|
sa,
|
|
NULL,
|
|
&baseline,
|
|
&exists);
|
|
todo_wine {
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
ok(baseline == 0, "got %d\n", baseline);
|
|
ok(exists == FALSE, "got %d\n", exists);
|
|
}
|
|
IDWriteFontFace_Release(fontface);
|
|
IDWriteTextAnalyzer1_Release(analyzer1);
|
|
}
|
|
|
|
static inline BOOL float_eq(FLOAT left, FLOAT right)
|
|
{
|
|
int x = *(int *)&left;
|
|
int y = *(int *)&right;
|
|
|
|
if (x < 0)
|
|
x = INT_MIN - x;
|
|
if (y < 0)
|
|
y = INT_MIN - y;
|
|
|
|
return abs(x - y) <= 8;
|
|
}
|
|
|
|
static void test_GetGdiCompatibleGlyphPlacements(void)
|
|
{
|
|
static const WCHAR strW[] = {'A',0};
|
|
DWRITE_SHAPING_GLYPH_PROPERTIES glyphprops[1];
|
|
DWRITE_SHAPING_TEXT_PROPERTIES textprops[1];
|
|
DWRITE_SCRIPT_ANALYSIS sa = { 0 };
|
|
IDWriteTextAnalyzer *analyzer;
|
|
IDWriteFontFace *fontface;
|
|
UINT16 clustermap[1];
|
|
HRESULT hr;
|
|
UINT32 count;
|
|
UINT16 glyphs[1];
|
|
FLOAT advance;
|
|
DWRITE_GLYPH_OFFSET offsets[1];
|
|
DWRITE_FONT_METRICS fontmetrics;
|
|
FLOAT emsize;
|
|
|
|
hr = IDWriteFactory_CreateTextAnalyzer(factory, &analyzer);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
|
|
fontface = create_fontface();
|
|
|
|
IDWriteFontFace_GetMetrics(fontface, &fontmetrics);
|
|
|
|
count = 0;
|
|
hr = IDWriteTextAnalyzer_GetGlyphs(analyzer, strW, 1, fontface,
|
|
FALSE, FALSE, &sa, NULL, NULL, NULL, NULL, 0, 1, clustermap,
|
|
textprops, glyphs, glyphprops, &count);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
ok(count == 1, "got %u\n", count);
|
|
|
|
for (emsize = 12.0; emsize <= 20.0; emsize += 1.0) {
|
|
FLOAT compatadvance, expected, ppdip;
|
|
DWRITE_GLYPH_METRICS metrics;
|
|
|
|
hr = IDWriteTextAnalyzer_GetGlyphPlacements(analyzer, strW, clustermap,
|
|
textprops, 1, glyphs, glyphprops, count, fontface, emsize, FALSE, FALSE,
|
|
&sa, NULL, NULL, NULL, 0, &advance, offsets);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
ok(advance > 0.0, "got %f\n", advance);
|
|
|
|
/* 1 ppdip, no transform */
|
|
ppdip = 1.0;
|
|
hr = IDWriteFontFace_GetGdiCompatibleGlyphMetrics(fontface, emsize, ppdip, NULL, FALSE,
|
|
glyphs, 1, &metrics, FALSE);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
|
|
expected = floorf(metrics.advanceWidth * emsize * ppdip / fontmetrics.designUnitsPerEm + 0.5f) / ppdip;
|
|
hr = IDWriteTextAnalyzer_GetGdiCompatibleGlyphPlacements(analyzer, strW,
|
|
clustermap, textprops, 1, glyphs, glyphprops, count, fontface, emsize,
|
|
ppdip, NULL, FALSE, FALSE, FALSE, &sa, NULL, NULL, NULL, 0, &compatadvance, offsets);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
ok(compatadvance == expected, "%.0f: got advance %f, expected %f, natural %f\n", emsize,
|
|
compatadvance, expected, advance);
|
|
|
|
/* 1.2 ppdip, no transform */
|
|
ppdip = 1.2;
|
|
hr = IDWriteFontFace_GetGdiCompatibleGlyphMetrics(fontface, emsize, ppdip, NULL, FALSE,
|
|
glyphs, 1, &metrics, FALSE);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
|
|
expected = floorf(metrics.advanceWidth * emsize * ppdip / fontmetrics.designUnitsPerEm + 0.5f) / ppdip;
|
|
hr = IDWriteTextAnalyzer_GetGdiCompatibleGlyphPlacements(analyzer, strW,
|
|
clustermap, textprops, 1, glyphs, glyphprops, count, fontface, emsize,
|
|
ppdip, NULL, FALSE, FALSE, FALSE, &sa, NULL, NULL, NULL, 0, &compatadvance, offsets);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
ok(float_eq(compatadvance, expected), "%.0f: got advance %f, expected %f, natural %f\n", emsize,
|
|
compatadvance, expected, advance);
|
|
}
|
|
|
|
IDWriteFontFace_Release(fontface);
|
|
IDWriteTextAnalyzer_Release(analyzer);
|
|
}
|
|
|
|
START_TEST(analyzer)
|
|
{
|
|
HRESULT hr;
|
|
|
|
hr = DWriteCreateFactory(DWRITE_FACTORY_TYPE_ISOLATED, &IID_IDWriteFactory, (IUnknown**)&factory);
|
|
ok(hr == S_OK, "got 0x%08x\n", hr);
|
|
if (hr != S_OK)
|
|
{
|
|
win_skip("failed to create factory\n");
|
|
return;
|
|
}
|
|
|
|
init_call_sequences(sequences, NUM_CALL_SEQUENCES);
|
|
init_call_sequences(expected_seq, 1);
|
|
|
|
test_AnalyzeScript();
|
|
test_AnalyzeLineBreakpoints();
|
|
test_GetScriptProperties();
|
|
test_GetTextComplexity();
|
|
test_GetGlyphs();
|
|
test_numbersubstitution();
|
|
test_GetTypographicFeatures();
|
|
test_GetGlyphPlacements();
|
|
test_ApplyCharacterSpacing();
|
|
test_GetGlyphOrientationTransform();
|
|
test_GetBaseline();
|
|
test_GetGdiCompatibleGlyphPlacements();
|
|
|
|
IDWriteFactory_Release(factory);
|
|
}
|