View Issue Details

IDProjectCategoryView StatusLast Update
0000752Far ManagerViewerpublic2011-03-21 23:19
Reportertheultramage Assigned Toalexy  
PrioritynormalSeverityminorReproducibilityhave not tried
Status closedResolutionfixed 
Fixed in Version3.0 
Summary0000752: hex view on utf8 files shows each byte as 0x00
DescriptionWhen viewing an utf8 file, and switching to hex view (F4), all data shows as 00.
Is this the best way to handle this sort of situation?

Example:
0000000000: 00 00 00 00 00 00 │ ?áéíóú

When using shift+F8 to change codepage to ANSI, it displays corectly.
When using F8 to switch between ANSI/OEM, it switches to ascii display and works too.
Additional InformationTo reproduce:
1. create new file with encoding set to UTF-8
2. write something and save
3. view the new file in hex mode (F3, then F4)
TagsNo tags attached.
Build1925

Relationships

has duplicate 0000826 closed Wrong hex values in viewer for text with Hebrew characters 
has duplicate 0000854 closed Hex view shows wrong hex values for the UTF-8 encoded files 

Activities

alexmitin

2009-05-06 05:46

reporter   bugnote:0003118

Перенесено из 0000854

> В общем тут есть такое (цитата):
> "А зачем в Viewer::ShowHex() вызывается вот это:
> WideCharToMultiByte(VM.CodePage, 0, (const wchar_t*)&Ch,1, &NewCh,1," ",NULL);
> после чего печатается 16-ричный код NewCh (который для файлв в utf8
> может принимать любое значение, зависящее от положения луны, погодных
> условий и пр.)"
>
>возможно это и лечение.
>"возможно" - потому, что пока не пойму на что оно может повлиять.
>
> alexmitin: закомменть эту строку и попытайся "исследовать"
> на что оно повлияет :-)

Там проблема глубже. Текущий код viewer расчитан на представление текста в разных кодировках. Но он не очень хорошо расчитан на представление hex вида для кодировок с переменным числом байт/символ.

Сейчас код viewer читает данные из файла, исходя из кодировки заданной пользователем, и конвертирует их в Unicode (UTF-16) - смотри функцию Viewer::vread() - для дальнейшего показа на консоли. Для показа просто текста этого вполне достаточно.

При переключении в hex режим - код в Viewer::ShowHex() пытается конвертировать данные обратно из Unicode в заданную кодировку, и только потом показать байт в виде hex значения.

Тут несколько проблем:

1) Двойное преобразование может привести к потере информации.

2) Не проверяется результат функции WideCharToMultiByte. В случае неудачи значение NewCh не изменяется, а так как это значение и не проинициализировано - то оно содержит просто мусор.

3) Код не расчитан, что один символ может занимать разное число байт - как раз случай UTF-8. Оригинальная спецификация допускала от 1 до 6 байт на символ
(Смотри http://en.wikipedia.org/wiki/UTF-8 ) Позже RFC3629 ограничил допустимый диапазон: каждый символ может занимать от 1 до 4 байт.

Проблема хорошо видна на значениях offset в левой части. Каждая hex строка показываает 16 байт. А вот offset в случае UTF-8 кодировки не соответствует количеству байт. Скорее он показывает количество символов. Но тогда теряется смысл hex режима для UTF-8.
-----------------

Для hex вида нужно читать байты из файла без изменений, показывать оригинальные значения каждого байта, а в правой части показывать текст в выбранной кодировке. Тут есть проблема, что вывод нужно форматировать с учетом позиции символа и его размера в байтах. Вполне может оказаться, что байты символа могут оказаться на разных строках hex вывода. Думаю правильнее показывать символ на позиции первого байта, а остальные места заполнять пробелами или предопределенным символом (это можно сделать конфигурируемым через регистр).

Все это требует значительного изменения кода viewer.

2useven10

2011-03-21 21:26

developer  

viewer.diff (6,101 bytes)   
Index: viewer.cpp
===================================================================
--- viewer.cpp	(revision 5864)
+++ viewer.cpp	(working copy)
@@ -74,6 +74,15 @@
 
 static int ViewerID=0;
 
+static int utf8_to_WideChar(const char *s, int nc, wchar_t *w1,wchar_t *w2, int wlen, int &tail);
+//
+//#define REPLACE_CHAR 0xFEFF // Zero Width No-Break Space
+#define REPLACE_CHAR 0xFFFD  // Replacement
+//
+//#define CONTINUE_CHAR 0x00BB // Right-Pointing Double Angle Quotation Mark
+#define CONTINUE_CHAR 0x203A // Single Right-Pointing Angle Quotation Mark
+//#define CONTINUE_CHAR 0x2026 // Horizontal Ellipse
+
 Viewer::Viewer(bool bQuickView, UINT aCodePage):
 	ViOpt(Opt.ViOpt),
 	Reader(ViewFile),
@@ -350,13 +359,6 @@
 			CodePageChangedByUser=TRUE;
 		}
 
-		// BUGBUG
-		// ���� ��� �������� �������� hex � UTF8, ��� �� ��������.
-		if (VM.Hex && VM.CodePage==CP_UTF8)
-		{
-			VM.CodePage=Opt.ViOpt.AnsiCodePageAsDefault?GetACP():GetOEMCP();
-		}
-
 		if (!IsUnicodeOrUtfCodePage(VM.CodePage))
 		{
 			ViewFile.SetPointer(0, nullptr, FILE_BEGIN);
@@ -710,6 +712,74 @@
 					wcscat(OutStr, BorderLine);
 			}
 		}
+
+      else if ( CP_UTF8 == VM.CodePage )
+      {
+         __int64 fpos = vtell();
+         unsigned char line[16+3];
+         DWORD nr = 0;
+         Reader.Read(line, (DWORD)sizeof(line), &nr);
+         if (nr > 16)
+            Reader.Unread(nr-16);
+
+         if (!nr)
+            wcscpy(OutStr,L"");
+         else
+         {
+            int out_len = (int)wcslen(OutStr);
+            int border_len = (int)wcslen(BorderLine);
+            if (SelectSize)
+            {
+               if (SelectPos >= fpos && SelectPos < fpos+16)
+               {
+                  int off = (int)(SelectPos - fpos);
+                  bSelStartFound = true;
+                  SelStart = out_len + 3*off + (off < 8 ? 0 : border_len); 
+                  SelSize = SelectSize;
+               }
+               __int64 selectEnd = SelectPos + SelectSize - 1;
+               if (selectEnd >= fpos && selectEnd < fpos+16)
+               {
+                  int off = (int)(selectEnd - fpos);
+                  bSelEndFound = true;
+                  SelEnd = out_len + 3*off + (off < 8 ? 0 : border_len) + 1; 
+                  SelSize = SelectSize;
+               }
+            }
+            for (X=0; X<16; X++)
+            {
+               int off = out_len + 3*X + (X < 8 ? 0 : border_len);
+               if (X == 8)
+                  wcscpy(OutStr+off-border_len, BorderLine);
+               if (X < (int)nr)
+                  _snwprintf(OutStr+off, ARRAYSIZE(OutStr)-off, L"%02X ", (int)line[X]);
+               else
+                  wcscpy(OutStr+off, L"   ");
+               TextStr[X] = L' ';
+            }
+
+            wchar_t w1[16], w2[16];
+            int tail, nw, ib=0, iw=0;
+            nw = utf8_to_WideChar((char *)line, (int)nr, w1, w2, 16, tail);
+            bool first = true;
+            while (ib < 16 && iw < nw)
+            {
+               if (first && w1[iw] == REPLACE_CHAR && w2[iw] == L'?')
+                  TextStr[ib++] = CONTINUE_CHAR; // ��� ����� �� �� ������ ��������� �� '����' utf-8
+               else                              // �� ������� ��-�� ����� ��� �� ��� ���� �� �����...
+               {
+                  first = false;
+                  TextStr[ib++] = w1[iw] ? w1[iw] : L' ';
+               }
+               int clen = WideCharToMultiByte(CP_UTF8, 0, w2+iw, 1, NULL,0, NULL,NULL);
+               while (--clen > 0 && ib < 16)
+                  TextStr[ib++] = CONTINUE_CHAR; 
+               ++iw;
+            }
+            TextPos = 16;
+         }
+      }
+
 		else
 		{
 			for (X=0; X<16; X++)
@@ -782,14 +852,7 @@
 
 		wcscat(OutStr,L" ");
 		wcscat(OutStr,TextStr);
-#if 0
 
-		for (size_t I=0; I < wcslen(OutStr); ++I)
-			if (OutStr[I] == (wchar_t)0xFFFF)
-				OutStr[I]=L'?';
-
-#endif
-
 		if (StrLength(OutStr)>HexLeftPos)
 		{
 			FS<<fmt::LeftAlign()<<fmt::Width(ObjWidth)<<fmt::Precision(ObjWidth)<<OutStr+static_cast<size_t>(HexLeftPos);
@@ -1393,13 +1456,6 @@
 		{
 			UINT nCodePage = SelectCodePage(VM.CodePage, true, true);
 
-			// BUGBUG
-			// ���� ��� �������� �������� hex � UTF8, ��� �� ��������.
-			if (VM.Hex && nCodePage==CP_UTF8)
-			{
-				return TRUE;
-			}
-
 			if (nCodePage!=(UINT)-1)
 			{
 				CodePageChangedByUser=TRUE;
@@ -2726,8 +2782,6 @@
 
 static int utf8_to_WideChar(const char *s, int nc, wchar_t *w1,wchar_t *w2, int wlen, int &tail )
 {
-#define REPLACEMENT 0xFFFD // L'?'
-
 	if ( wlen <= 0 )
 	{
 		tail = nc;
@@ -2754,7 +2808,7 @@
 				else
 				{
 					tail = 0;
-					w1[0] = REPLACEMENT;
+					w1[0] = REPLACE_CHAR;
 					if (w2)
 						w2[0] = L'?';
 					nw = 1;
@@ -2762,7 +2816,7 @@
 				return nw;
 			}
 			c2 = ((const unsigned char *)s)[ic];
-			if ( 0x80 != (c2 & 0xC0)        // illegal 2-st byte
+			if ( 0x80 != (c2 & 0xC0)        // illegal 2-nd byte
 				|| (0xE0 == c1 && c2 <= 0x9F) // illegal 3-byte start (overlaps with 2-byte)
 				|| (0xF0 == c1 && c2 <= 0x8F) // illegal 4-byte start (overlaps with 3-byte)
 				|| (0xF4 == c1 && c2 >= 0x90) // illegal 4-byte (out of unicode range)
@@ -2811,7 +2865,7 @@
 						if (nw >= wlen)
 						{
 							--nw;
-							wc = REPLACEMENT;
+							wc = REPLACE_CHAR;
 						}
 					}
 				}
@@ -2826,7 +2880,7 @@
 		}
 		else
 		{
-			w1[nw] = REPLACEMENT;
+			w1[nw] = REPLACE_CHAR;
 			if (w2)
 				w2[nw] = L'?';
 		}
@@ -3482,14 +3536,6 @@
 
 int Viewer::ProcessHexMode(int newMode, bool isRedraw)
 {
-	// BUGBUG
-	// �� �� ���, ���� �� ����� ���������� ��������� hex-�������� � UTF8 - ����� ������� � OEM.
-	// ��� ����� ��� �� ��������, � ��������� �������� ��� �����������.
-	if (VM.CodePage==CP_UTF8 && newMode)
-	{
-		VM.CodePage=Opt.ViOpt.AnsiCodePageAsDefault?GetACP():GetOEMCP();
-	}
-
 	int oldHex=VM.Hex;
 	VM.Hex=newMode&1;
 
viewer.diff (6,101 bytes)   

2useven10

2011-03-21 21:27

developer  

hex-view-far3.PNG (63,530 bytes)   
hex-view-far3.PNG (63,530 bytes)   

2useven10

2011-03-21 21:28

developer   bugnote:0006540

предлагается вариант hex-view для utf-8

alexy

2011-03-21 21:37

administrator   bugnote:0006541

вроде не плохо. Тока может придумать что то чтоб этот "continue char" как то цветом помечать?

З.Ы. Почем wcscpy(OutStr,L""); а не *OutStr=0;?

2useven10

2011-03-21 21:41

developer   bugnote:0006543

Last edited: 2011-03-21 21:43

копипаст из кода для однобайтных кодировок (ниже)...
И тогда уж... = L'\0';
насчет цвета - никогда не делал.
если кто-нибудь сделает, у меня никаких возражений нет :)

alexy

2011-03-21 22:06

administrator   bugnote:0006546

1925

2useven10

2011-03-21 22:29

developer   bugnote:0006547

заметил одну фичу: в far2 при переключении по F4 выделение найденного сохраняется, а в far3 теряется. Наверное правильнее было-бы сохранить поведение far2. Или в far3 'правильное' поведение?

alexy

2011-03-21 23:19

administrator   bugnote:0006548

Fixed in 1925.

А про выделение это уже для другого тикета тема.

Issue History

Date Modified Username Field Change
2009-02-23 10:58 theultramage New Issue
2009-04-15 07:30 alexy Relationship added has duplicate 0000826
2009-05-05 09:11 vskirdin Relationship added has duplicate 0000854
2009-05-06 05:46 alexmitin Note Added: 0003118
2011-03-21 21:26 2useven10 File Added: viewer.diff
2011-03-21 21:27 2useven10 File Added: hex-view-far3.PNG
2011-03-21 21:28 2useven10 Note Added: 0006540
2011-03-21 21:37 alexy Note Added: 0006541
2011-03-21 21:41 2useven10 Note Added: 0006543
2011-03-21 21:43 2useven10 Note Edited: 0006543
2011-03-21 21:51 alexy Assigned To => alexy
2011-03-21 21:51 alexy Status new => assigned
2011-03-21 22:06 alexy Note Added: 0006546
2011-03-21 22:06 alexy Status assigned => feedback
2011-03-21 22:29 2useven10 Note Added: 0006547
2011-03-21 23:19 alexy Build => 1925
2011-03-21 23:19 alexy Note Added: 0006548
2011-03-21 23:19 alexy Status feedback => closed
2011-03-21 23:19 alexy Resolution open => fixed
2011-03-21 23:19 alexy Fixed in Version => 3.0