#arrays #string #memory #byte #delphi-xe7
#массивы #строка #память #байт #delphi-xe7
Вопрос:
Для проекта мне нужно прочитать имя внутри файла шрифта TrueType (.ttf). Для этого я написал код, вдохновленный примером c . Вот код:
TWByteArray = array of Byte;
TWAnsiCharArray = array of AnsiChar;
...
//---------------------------------------------------------------------------
class function TWStringHelper.ByteToStr(const bytes: TWByteArray): string;
begin
SetLength(Result, Length(bytes));
if Length(Result) > 0 then
Move(bytes[0], Result[1], Length(bytes));
end;
//---------------------------------------------------------------------------
class function TWStringHelper.UniStrToByte(const str: UnicodeString): TWByteArray;
begin
SetLength(Result, Length(str) * SizeOf(WideChar));
if (Length(Result) > 0) then
Move(str[1], Result[0], Length(Result));
end;
//---------------------------------------------------------------------------
class function TWStringHelper.BytesToUniStr(const bytes: TWByteArray): UnicodeString;
begin
SetLength(Result, Length(bytes) div SizeOf(WideChar));
if Length(Result) > 0 then
Move(bytes[0], Result[1], Length(bytes));
end;
//---------------------------------------------------------------------------
...
//---------------------------------------------------------------------------
class function TWControlFont.SwapWord(value: Word): Word;
begin
Result := MakeWord(HiByte(value), LoByte(value));
end;
//---------------------------------------------------------------------------
class function TWControlFont.SwapLong(value: LongInt): LongInt;
begin
Result := MakeLong(SwapWord(HiWord(value)), SwapWord(LoWord(value)));
end;
//---------------------------------------------------------------------------
class function TWControlFont.GetFontNameFromFile(const fileName: UnicodeString): UnicodeString;
var
pFile: TFileStream;
offsetTable: ITTFOffsetTable;
dirTable: ITTFDirectoryTable;
nameHeader: ITTFNameTableHeader;
nameRecord: ITTFNameRecord;
nameBuffer: TWByteArray;//TWAnsiCharArray;
i: USHORT;
found: Boolean;
test2: string;
test3: UnicodeString;
test: Integer;
const name: array [0..3] of Byte = (Ord('n'), Ord('a'), Ord('m'), Ord('e'));
begin
// open font file
pFile := TFileStream.Create(fileName, fmOpenRead);
// succeeded?
if (not Assigned(pFile)) then
Exit;
try
pFile.Seek(0, soFromBeginning);
// read TTF offset table
if (pFile.Read(offsetTable, SizeOf(ITTFOffsetTable)) <> SizeOf(ITTFOffsetTable)) then
Exit;
offsetTable.m_NumOfTables := SwapWord(offsetTable.m_NumOfTables);
offsetTable.m_MajorVersion := SwapWord(offsetTable.m_MajorVersion);
offsetTable.m_MinorVersion := SwapWord(offsetTable.m_MinorVersion);
// is truetype font and version is 1.0?
if ((offsetTable.m_MajorVersion <> 1) or (offsetTable.m_MinorVersion <> 0)) then
Exit;
found := False;
// iterate through file tables
if (offsetTable.m_NumOfTables > 0) then
for i := 0 to offsetTable.m_NumOfTables - 1 do
begin
// read table
if (pFile.Read(dirTable, SizeOf(ITTFDirectoryTable)) <> SizeOf(ITTFDirectoryTable)) then
Exit;
// found name table?
if (CompareMem(@dirTable.m_Tag, @name, 4) = True) then
begin
found := True;
dirTable.m_Length := SwapLong(dirTable.m_Length);
dirTable.m_Offset := SwapLong(dirTable.m_Offset);
break;
end;
end;
// found name table?
if (not found) then
Exit;
// seek to name location
pFile.Position := dirTable.m_Offset;
// read name table header
if (pFile.Read(nameHeader, SizeOf(ITTFNameTableHeader)) <> SizeOf(ITTFNameTableHeader)) then
Exit;
nameHeader.m_NRCount := SwapWord(nameHeader.m_NRCount);
nameHeader.m_StorageOffset := SwapWord(nameHeader.m_StorageOffset);
// iterate through name records
if (nameHeader.m_NRCount > 0) then
for i := 0 to nameHeader.m_NRCount - 1 do
begin
// read name record
if (pFile.Read(nameRecord, SizeOf(ITTFNameRecord)) <> SizeOf(ITTFNameRecord)) then
Exit;
nameRecord.m_NameID := SwapWord(nameRecord.m_NameID);
// found font name?
if (nameRecord.m_NameID = 1) then
begin
// get font name length and offset
nameRecord.m_StringLength := SwapWord(nameRecord.m_StringLength);
nameRecord.m_StringOffset := SwapWord(nameRecord.m_StringOffset);
if (nameRecord.m_StringLength = 0) then
continue;
// calculate and seek to font name offset
pFile.Position := dirTable.m_Offset nameRecord.m_StringOffset nameHeader.m_StorageOffset;
try
SetLength(nameBuffer, nameRecord.m_StringLength 1);
//REM FillChar(nameBuffer[0], nameRecord.m_StringLength 1, $0);
// read font name from file
if (pFile.Read(nameBuffer[0], nameRecord.m_StringLength)
<> nameRecord.m_StringLength)
then
Exit;
nameBuffer[nameRecord.m_StringLength] := $0;
//OutputDebugString(PChar(nameBuffer));
//TWMemoryHelper.SwapBytes(nameBuffer[0], nameRecord.m_StringLength);
//OutputDebugString(PChar(nameBuffer));
//test := StringElementSize(RawByteString(@nameBuffer[0]));
//Result := TWStringHelper.BytesToUniStr(nameBuffer);
//Result := UnicodeString(AnsiString(TWStringHelper.ByteToStr(nameBuffer)));
//REM Result := UnicodeString(nameBuffer);
test2 := TWStringHelper.ByteToStr(nameBuffer);
OutputDebugStringA(PAnsiChar(test2));
test3 := UnicodeString(PAnsiChar(test2));
OutputDebugStringW(PWideChar(test3));
Result := test3;
OutputDebugStringW(PWideChar(test3));
finally
SetLength(nameBuffer, 0);
end;
break;
end;
end;
finally
pFile.Free;
end;
end;
//---------------------------------------------------------------------------
Этот код хорошо работает до финальной части функции GetFontNameFromFile(). Здесь все начинает усложняться. Действительно, я не могу правильно преобразовать массив байтов nameBuffer в строку.
Первая проблема, с которой я столкнулся, заключается в том, что буфер имен может быть простой строкой ASCII или строкой UTF16, в зависимости от файла (я пробовал использовать emoji.ttf, доступный в FireFox, который возвращает строку ASCII, и Tahoma.ttf из моей установки Win, который возвращает строку UTF16). Мне нужен способ определить это, и я не знаю, есть ли в VCL функция или класс для этого.
Вторая проблема заключается в самом преобразовании. Приведенный выше код работает более или менее, но я чувствую, что это неправильное решение. Когда я пытаюсь преобразовать в строку UnicodeString непосредственно из nameBuffer, я получаю несколько странных сбоев. Если я попытаюсь преобразовать nameBuffer в AnsiString, преобразование кажется успешным, однако преобразование, подобное UnicodeString(AnsiString(nameBuffer)), завершается неудачей.
И код, похоже, полон проблем с памятью. Поскольку я новичок в Delphi, меня не очень устраивает использование памяти. Например, я подозреваю несколько проблем с массивом байтов, когда я активирую
FillChar(nameBuffer[0], nameRecord.m_StringLength 1, $0);
строка.
Итак, кто-нибудь может проанализировать этот код и указать мне, что я делаю неправильно?
Заранее спасибо, с уважением
Комментарии:
1. И на каком языке вы работаете?
2. Delphi с RAD Studio XE7, извините
3. Здесь очень много ошибок, большинство из которых происходят из-за незнания языка. Нам будет сложно исправить их все.
4. Привет, Дэвид. Действительно, я начинаю с Delphi и понимаю, что качество приведенного выше кода, вероятно, очень низкое. Однако я опубликовал его целиком, чтобы дать контекст, чтобы узнать конкретную проблему, вот почему окончательное преобразование из массива байтов в строку Юникода плохо работает в конце функции. Это из-за ошибки преобразования? Или проблема с памятью? Однако я не ожидал, что ВЕСЬ код будет исправлен. Однако, если у вас есть желание и время показать мне хотя бы мои самые большие ошибки, я был бы благодарен. Я хочу учиться, и любая информация приветствуется. С уважением
5. Я использую firefox, но у меня нет никакого emoji.ttf в моей системе. В любом случае, возможно, вы неправильно диагностировали проблему, все строки имен в шрифте Windows являются unicode ( ссылка ).