[PATCH v3 0/2] MR10952: riched20: Properly capture \pntext content
riched20 previously ignored \\pntext instuctions in RTF, which caused erroneous Paragraph numbers to appear in some Applications. It now properly captures the \\pntext content and properly applies numbering when it's missing. Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=59392 -- v3: riched20: Expand editor tests to verify \pntext behaviour riched20: Properly capture \pntext content https://gitlab.winehq.org/wine/wine/-/merge_requests/10952
From: Rose Hellsing <rose@pinkro.se> riched20 previously ignored \pntext instuctions in RTF, which caused errornous Paragraph numbers to appear in some Applications. It now properly captures the \pntext content and properly applies numbering when it's missing. Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=59392 --- dlls/riched20/editor.c | 56 ++++++++++++++++++++++++++++++++++++++++++ dlls/riched20/reader.c | 31 +++++++++++++++++++++++ dlls/riched20/rtf.h | 1 + 3 files changed, 88 insertions(+) diff --git a/dlls/riched20/editor.c b/dlls/riched20/editor.c index e704508f2a5..72d25d3946d 100644 --- a/dlls/riched20/editor.c +++ b/dlls/riched20/editor.c @@ -1513,6 +1513,61 @@ static void ME_RTFReadParnumGroup( RTF_Info *info ) RTFRouteToken( info ); /* feed "}" back to router */ } +static void ME_RTFReadPntextGroup( RTF_Info *info ) +{ + int level = 1; + WCHAR buf[256]; + int buf_len = 0; + + ME_DestroyString( info->pntext ); + info->pntext = NULL; + + for (;;) + { + RTFGetToken( info ); + + if (info->rtfClass == rtfEOF) + return; + + if (RTFCheckCM( info, rtfGroup, rtfBeginGroup )) + { + level++; + continue; + } + + if (RTFCheckCM( info, rtfGroup, rtfEndGroup )) + { + level--; + if (level == 0) break; + continue; + } + + if (buf_len >= ARRAY_SIZE(buf) - 1) + continue; + + if (info->rtfClass == rtfText) + { + buf[buf_len++] = (WCHAR)(unsigned char)info->rtfMajor; + } + else if (info->rtfClass == rtfControl && info->rtfMajor == rtfSpecialChar) + { + switch (info->rtfMinor) + { + case rtfTab: buf[buf_len++] = '\t'; break; + case rtfBullet: buf[buf_len++] = 0x2022; break; + case rtfNoBrkSpace: buf[buf_len++] = 0x00A0; break; + case rtfNoBrkHyphen:buf[buf_len++] = 0x2011; break; + case rtfOptDest: + default: break; + } + } + } + + info->pntext = ME_MakeStringN( buf, buf_len ); + + RTFRouteToken( info ); +} + static void ME_RTFReadHook(RTF_Info *info) { switch(info->rtfClass) @@ -1670,6 +1725,7 @@ static LRESULT ME_StreamIn(ME_TextEditor *editor, DWORD format, EDITSTREAM *stre RTFSetDestinationCallback(&parser, rtfPict, ME_RTFReadPictGroup); RTFSetDestinationCallback(&parser, rtfObject, ME_RTFReadObjectGroup); RTFSetDestinationCallback(&parser, rtfParNumbering, ME_RTFReadParnumGroup); + RTFSetDestinationCallback(&parser, rtfParNumText, ME_RTFReadPntextGroup); if (!parser.editor->bEmulateVersion10) /* v4.1 */ { RTFSetDestinationCallback(&parser, rtfNoNestTables, RTFSkipGroup); diff --git a/dlls/riched20/reader.c b/dlls/riched20/reader.c index 95360d8b9d4..402335d4b68 100644 --- a/dlls/riched20/reader.c +++ b/dlls/riched20/reader.c @@ -150,6 +150,7 @@ RTFDestroy(RTF_Info *info) } RTFDestroyAttrs(info); free(info->cpOutputBuffer); + ME_DestroyString( info->pntext ); while (info->tableDef) { RTFTable *tableDef = info->tableDef; @@ -252,6 +253,7 @@ void RTFInit(RTF_Info *info) info->nestingLevel = 0; info->canInheritInTbl = FALSE; info->borderType = 0; + info->pntext = NULL; memset(&info->fmt, 0, sizeof(info->fmt)); info->fmt.cbSize = sizeof(info->fmt); @@ -2498,13 +2500,42 @@ static void SpecialChar (RTF_Info *info) case rtfPage: case rtfSect: case rtfPar: + { + ME_Paragraph *para; RTFFlushOutputBuffer(info); + if ((info->fmt.dwMask & PFM_NUMBERING) && !info->pntext) + info->fmt.wNumbering = 0; + + if (info->pntext && info->pntext->nLen && + info->pntext->szData[info->pntext->nLen - 1] == '\t') + { + info->pntext->szData[--info->pntext->nLen] = 0; + if ((info->fmt.dwMask & PFM_NUMBERINGTAB) && + info->fmt.wNumberingTab == 0) + info->fmt.wNumberingTab = lDefaultTab; + } + editor_set_selection_para_fmt( info->editor, &info->fmt ); + para = info->editor->pCursors[0].para; memset(&info->fmt, 0, sizeof(info->fmt)); info->fmt.cbSize = sizeof(info->fmt); RTFPutUnicodeChar (info, '\r'); if (info->editor->bEmulateVersion10) RTFPutUnicodeChar (info, '\n'); + + if (info->pntext) + { + if (info->pntext->nLen && para->fmt.wNumbering) + { + para_num_clear( ¶->para_num ); + para->para_num.text = info->pntext; + info->pntext = NULL; + para_mark_rewrap( info->editor, para ); + } + ME_DestroyString( info->pntext ); + info->pntext = NULL; + } break; + } case rtfNoBrkSpace: RTFPutUnicodeChar (info, 0x00A0); break; diff --git a/dlls/riched20/rtf.h b/dlls/riched20/rtf.h index f2d446ac5d3..69deb28ea0c 100644 --- a/dlls/riched20/rtf.h +++ b/dlls/riched20/rtf.h @@ -1175,6 +1175,7 @@ struct _RTF_Info { int borderType; /* value corresponds to the RTFBorder constants. */ PARAFORMAT2 fmt; /* Accumulated para fmt for current paragraph. */ + ME_String *pntext; /* Explicit paragraph number text from \pntext destination. */ }; -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10952
From: Rose Hellsing <rose@pinkro.se> --- dlls/riched20/tests/editor.c | 80 ++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/dlls/riched20/tests/editor.c b/dlls/riched20/tests/editor.c index 0a7b8ec6ce4..2d654641ab6 100644 --- a/dlls/riched20/tests/editor.c +++ b/dlls/riched20/tests/editor.c @@ -9093,6 +9093,86 @@ static void test_para_numbering(void) ok( cf.yHeight == 200, "got %ld\n", cf.yHeight ); DestroyWindow( edit ); + + /* \pn without \pntext should not produce a visible number: native + * richedit treats \pntext as the authoritative label, so its absence + * suppresses auto-numbering entirely. */ + edit = new_richeditW( NULL ); + { + const char *no_pntext = + "{\\rtf1{\\fonttbl{\\f0\\fswiss\\fprq2\\fcharset0 Arial;}}" + "\\pard{\\*\\pn\\pnlvlbody\\pnf0\\pnindent1000\\pnstart1\\pndec{\\pntxta.}}" + "\\f0 Alpha\\par " + "Beta\\par}"; + const WCHAR expect_no_pntext[] = {'A','l','p','h','a','\r','B','e','t','a',0}; + + es.dwCookie = (DWORD_PTR)&no_pntext; + es.dwError = 0; + es.pfnCallback = test_EM_STREAMIN_esCallback; + result = SendMessageA( edit, EM_STREAMIN, SF_RTF, (LPARAM)&es ); + ok( result == lstrlenW( expect_no_pntext ), "got %Id\n", result ); + + result = SendMessageW( edit, EM_GETTEXTEX, (WPARAM)&get_text, (LPARAM)buf ); + ok( result == lstrlenW( expect_no_pntext ), "got %Id\n", result ); + ok( !lstrcmpW( buf, expect_no_pntext ), "got %s\n", wine_dbgstr_w(buf) ); + } + DestroyWindow( edit ); + + /* \pntext containing \bullet: + * the bullet character is the label and must not appear in EM_GETTEXT. */ + edit = new_richeditW( NULL ); + { + const char *bullet_rtf = + "{\\rtf1{\\fonttbl{\\f0\\fswiss\\fprq2\\fcharset0 Arial;}" + "{\\f1\\fnil\\fcharset2 Symbol;}}" + "\\pard{\\pntext\\f1\\'b7\\tab}" + "{\\*\\pn\\pnlvlblt\\pnf1\\pnindent1000{\\pntxtb\\bullet}}" + "\\f0 Item one\\par" + "{\\pntext\\f1\\bullet\\tab}\\f0 Item two\\par}"; + const WCHAR expect_bullet[] = {'I','t','e','m',' ','o','n','e','\r', + 'I','t','e','m',' ','t','w','o',0}; + + es.dwCookie = (DWORD_PTR)&bullet_rtf; + es.dwError = 0; + es.pfnCallback = test_EM_STREAMIN_esCallback; + result = SendMessageA( edit, EM_STREAMIN, SF_RTF, (LPARAM)&es ); + ok( result == lstrlenW( expect_bullet ), "got %Id\n", result ); + + result = SendMessageW( edit, EM_GETTEXTEX, (WPARAM)&get_text, (LPARAM)buf ); + ok( result == lstrlenW( expect_bullet ), "got %Id\n", result ); + ok( !lstrcmpW( buf, expect_bullet ), "got %s\n", wine_dbgstr_w(buf) ); + + SendMessageW( edit, EM_SETSEL, 1, 1 ); + memset( &fmt, 0, sizeof(fmt) ); + fmt.cbSize = sizeof(fmt); + fmt.dwMask = PFM_ALL2; + SendMessageW( edit, EM_GETPARAFORMAT, 0, (LPARAM)&fmt ); + ok( fmt.wNumbering == PFN_BULLET, "got %d\n", fmt.wNumbering ); + } + DestroyWindow( edit ); + + /* The trailing \tab inside \pntext is the label/body separator and + * should not be present in the document text. */ + edit = new_richeditW( NULL ); + { + const char *tab_rtf = + "{\\rtf1{\\fonttbl{\\f0\\fswiss\\fprq2\\fcharset0 Arial;}}" + "\\pard{\\pntext\\f0 1.\\tab}" + "{\\*\\pn\\pnlvlbody\\pnf0\\pnindent1000\\pnstart1\\pndec{\\pntxta.}}" + "\\f0 Hello\\par}"; + const WCHAR expect_tab[] = {'H','e','l','l','o',0}; + + es.dwCookie = (DWORD_PTR)&tab_rtf; + es.dwError = 0; + es.pfnCallback = test_EM_STREAMIN_esCallback; + result = SendMessageA( edit, EM_STREAMIN, SF_RTF, (LPARAM)&es ); + ok( result == lstrlenW( expect_tab ), "got %Id\n", result ); + + result = SendMessageW( edit, EM_GETTEXTEX, (WPARAM)&get_text, (LPARAM)buf ); + ok( result == lstrlenW( expect_tab ), "got %Id\n", result ); + ok( !lstrcmpW( buf, expect_tab ), "got %s\n", wine_dbgstr_w(buf) ); + } + DestroyWindow( edit ); } static void fill_reobject_struct(REOBJECT *reobj, LONG cp, LPOLEOBJECT poleobj, -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10952
participants (2)
-
Rose Hellsing -
Rose Hellsing (@axtlos)