Динамический массив байтов в строку

#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 ( ссылка ).