Главная »Статьи »AutoCAD и Delphi » |
В данной статье будет рассмотрен формат файла SHX-файлов, его чтение и экспорт в текстовый формат SHP. Исключение составляет формат больших шрифтов (BigFont) — такие шрифты не рассматриваются. Формат файла SHXОписание формата файла SHX найти не удалось, поэтому были скомпилированы несколько тестовых файлов, которые затем проанализированы HEX-редактором. Вначале расположен заголовок, который оканчивается последовательностью их 3-х байт: 0x0D, 0x0A, 0x1A. Затем:
ЧтениеОбъявим тип файла:
type
TShxFontKind = (fkShpFont, fkUniFont, fkBigFont, fkUnknown);
Объявим тип записи номера формы и количества байт: type TShxShapeInfo = packed record ShapeNumber: Word; GlyphBytes: Word; end; Создаем определение класса формы: TShxShape = class(TObject) private FFont: TShxFont; FNumber: Word; FBytesCount: Word; FName: String; FGlyphHighIndex: Word; protected FGlyph: array of Byte; // массив байт описания формы function GetGlyph(Index: Integer): Byte; procedure SetGlyph(Index: Integer; const Value: Byte); procedure SetGlyphLength(const Value: Word); function GetGlyphLength: Word; public constructor Create(AFont: TShxFont; ANumber, ABytesCount: Word); destructor Destroy; override; property Number: Word read FNumber write FNumber; property BytesCount: Word read FBytesCount write FBytesCount; property Name: String read FName write FName; property Glyph[Index: Integer]: Byte read GetGlyph write SetGlyph; property GlyphLength: Word read GetGlyphLength write SetGlyphLength; end; Создаем определение класса шрифта: TShxFont = class(TObjectList) private FFontFileName: String; // полный путь к файлу (устан-ся при успешном LoadFromFile) FFontName: String; // = имя формы 0 (имя шрифта вместе с копирайтами) FFontKind: TShxFontKind; // тип шрифта FIsFont: Boolean; // флаг "это шрифт": True если шрифт, False если файл форм FAbove: Byte; // высота строчной буквы над базовой линией FBelow: Byte; // размер подстрочного элемента под базовой линией FModes: Byte; // режимы ориентации шрифта FUniEncoding: Byte; // кодировка Unicode FUniType: Byte; // тип лицензии FMaxStrLen: Byte; // макс. длина строки определения формы (для экспорта в SHP) protected function GetShape(Index: Integer): TShxShape; procedure SetShape(Index: Integer; ASample: TShxShape); procedure SetMaxStrLen(const Value: Byte); procedure SetDefault; procedure LoadShape(const FileHandle: THandle; const ShapeNum: Word); public constructor Create; function Add(AShape: TShxShape): Integer; procedure Insert(Index: Integer; ASample: TShxShape); function LoadFromFile(const AFileName: String): Boolean; function GetShapeIndex(ShapeNumber: Word): Integer; property Shapes[Index: Integer]: TShxShape read GetShape write SetShape; default; property FontFileName: String read FFontFileName; property FontName: String read FFontName; property FontKind: TShxFontKind read FFontKind; property IsFont: Boolean read FIsFont; property Above: Byte read FAbove; property Below: Byte read FBelow; property Modes: Byte read FModes; property UniEncoding: Byte read FUniEncoding; property UniType: Byte read FUniType; property MaxStrLen: Byte read FMaxStrLen write SetMaxStrLen; end; Реализация большинства методов этих классов тривиальна и пояснений не требует. Рассмотрим непосредственно чтение shx-файла: function TShxFont.LoadFromFile(const AFileName: String): Boolean; const SHX_FILE_SIGNATURE_SIZE: Word = 32; // размер, в который влезает сигнатура файла SHX_SHAPES_NUM_SHAPES_OFS: Word = $1C; // смещение слова кол. форм от начала файла SHX_UNIFONT_NUM_SHAPES_OFS: Word = $19; SHX_SHAPES_SHAPE0_GLYPH_LENGTH: Word = $03; // длина инфо о шрифте в форме 0 SHX_UNIFNT_SHAPE0_GLYPH_LENGTH: Word = $05; var FileHandle: THandle; NumShapesOfs: Integer; i, NumShapes, Shape0GlyphLen: Word; ShapeInfo: TShxShapeInfo; Buffer: PAnsiChar; S: AnsiString; begin Result:= False; Clear; FileHandle:= FileOpen(AFileName, fmOpenRead); try FileSeek(FileHandle, 0, FILE_BEGIN); // Определение типа шрифта - проверка сигнатуры Buffer:= AllocMem(SHX_FILE_SIGNATURE_SIZE + 1); try FileRead(FileHandle, Buffer^, SHX_FILE_SIGNATURE_SIZE); S:= AnsiString(Buffer); finally FreeMem(Buffer); end; FFontKind:= GetFontKind(S); if FFontKind = fkUnknown then raise Exception.CreateFmt('Error Font Kind [%d]!', [Integer(FFontKind)]); if FFontKind = fkBigFont then raise Exception.Create('BigFont is not support!'); // Количество форм if FFontKind = fkShpFont then NumShapesOfs:= SHX_SHAPES_NUM_SHAPES_OFS else NumShapesOfs:= SHX_UNIFONT_NUM_SHAPES_OFS; FileSeek(FileHandle, NumShapesOfs, FILE_BEGIN); FileRead(FileHandle, NumShapes, SizeOf(NumShapes)); // Для SHAPE-шрифта читаем пары: [Номер формы][Количество байт (всего, вкл. имя)] if FFontKind = fkShpFont then begin for i:= 0 to NumShapes - 1 do begin FileRead(FileHandle, ShapeInfo, SizeOf(TShxShapeInfo)); Add(TShxShape.Create(Self, ShapeInfo.ShapeNumber, ShapeInfo.GlyphBytes)); end; end; // Имена и байты форм for i:= 0 to NumShapes - 1 do begin case FFontKind of fkShpFont: LoadShape(FileHandle, i); fkUniFont: begin FileRead(FileHandle, ShapeInfo, SizeOf(TShxShapeInfo)); Add(TShxShape.Create(Self, ShapeInfo.ShapeNumber, ShapeInfo.GlyphBytes)); LoadShape(FileHandle, i); end; end; end; if Count < 1 then raise Exception.CreateFmt('Error Reading Shapes [%d]!', [Count]); // Присвоение информации по шрифту if Shapes[0].Number = 0 then // это файл шрифта begin FIsFont:= True; // Проверка длины глифа для формы 0 if FFontKind = fkShpFont then Shape0GlyphLen:= SHX_SHAPES_SHAPE0_GLYPH_LENGTH else Shape0GlyphLen:= SHX_UNIFNT_SHAPE0_GLYPH_LENGTH; if Shapes[0].GlyphLength < Shape0GlyphLen then raise Exception.CreateFmt('Error Shape 0 Corrupt [%d, %d]!', [Shapes[0].GlyphLength, Shape0GlyphLen]); // Общая часть для fkShpFont и для fkUniFont FAbove:= Shapes[0].Glyph[0]; FBelow:= Shapes[0].Glyph[1]; FModes:= Shapes[0].Glyph[2]; // Только для fkUniFont if FFontKind = fkUniFont then begin FUniEncoding:= Shapes[0].Glyph[3]; FUniType:= Shapes[0].Glyph[4]; end; if Shapes[0].Name <> '' then FFontName:= Shapes[0].Name else FFontName:= ChangeFileExt(FFontFileName, ''); // Считали все данные из формы 0, теперь удалим ее Delete(0); end else // это файл форм SetDefault; FFontFileName:= AFileName; Result:= True; finally FileClose(FileHandle); end; end; Сначала читаем из файла 32 байта и определяем тип файла (функция GetFontKind). Далее читаем количество форм. Затем:
После чтения данных из файла разбираемся с формой 0. Если ее нет, то это файл форм, а не шрифта — устанавливаем дефолтные значения для полей (метод SetDefault). Если форма 0 есть, то устанавливаем значения полей из нее. В заключение удаляем нулевую форму, т.к. она больше не требуется. Реализация функции GetFontKind: function GetFontKind(AFileHeader: AnsiString): TShxFontKind; const SHX_SHAPES10: AnsiString = 'AutoCAD-86 shapes 1.0'; SHX_SHAPES11: AnsiString = 'AutoCAD-86 shapes 1.1'; SHX_UNIFONT10: AnsiString = 'AutoCAD-86 unifont 1.0'; SHX_BIGFONT10: AnsiString = 'AutoCAD-86 bigfont 1.0'; begin if (Pos(SHX_SHAPES10, AFileHeader) <> 0) or (Pos(SHX_SHAPES11, AFileHeader) <> 0) then Result:= fkShpFont else if (Pos(SHX_UNIFONT10, AFileHeader) <> 0) then Result:= fkUniFont else if (Pos(SHX_BIGFONT10, AFileHeader) <> 0) then Result:= fkBigFont else Result:= fkUnknown; end; Реализация метода LoadShape: type THackedShxShape = class(TShxShape); procedure TShxFont.LoadShape(const FileHandle: THandle; const ShapeNum: Word); var GlyphLen, GlyphOfs: Word; Buffer: PAnsiChar; S: AnsiString; begin Buffer:= System.AllocMem(Shapes[ShapeNum].BytesCount + 1); try FileRead(FileHandle, Buffer^, Shapes[ShapeNum].BytesCount); S:= AnsiString(Buffer); Shapes[ShapeNum].Name:= String(S); // Байты формы GlyphOfs:= Length(Shapes[ShapeNum].Name) + 1; GlyphLen:= Shapes[ShapeNum].BytesCount - GlyphOfs; Shapes[ShapeNum].GlyphLength:= GlyphLen; System.Move(Buffer[GlyphOfs], THackedShxShape(Shapes[ShapeNum]).FGlyph[0], GlyphLen); finally FreeMem(Buffer); end; end; В представленных в статье реализациях опущено большинство проверок, чтобы сделать код более простым для понимания.
С чтением закончили, теперь переходим к экспорту в SHP-файл. Экспорт в SHP-файлДобавим в класс TShxShape следующий метод, который возвращает описание формы в SHP формате: function TShxShape.GetGlyphDefinition(AMaxStrLen: Byte): String; var i: Integer; n, MaxLen: Byte; begin MaxLen:= EnsureRange(AMaxStrLen, MAX_STR_LEN_MIN, MAX_STR_LEN_MAX); Result:= Format('*%d,%d', [FNumber, GlyphLength]); if FName = '' then Result:= Result + #13#10 else Result:= Format('%s,%s'#13#10, [Result, FName]); n:= 0; FGlyphHighIndex:= High(FGlyph); i:= Low(FGlyph); while i <= FGlyphHighIndex do begin case FGlyph[i] of $0: Result:= Format('%s0', [Result]); // конец описания формы $1: Result:= Format('%s%d,', [Result, FGlyph[i]]); // опускание пера $2: Result:= Format('%s%d,', [Result, FGlyph[i]]); // поднятие пера $3: begin // деление длин векторов на следующий байт Result:= Format('%s%d,%d,', [Result, FGlyph[i], FGlyph[i + 1]]); Inc(i); Inc(n); end; $4: begin // умножение длин векторов на следующий байт Result:= Format('%s%d,%d,', [Result, FGlyph[i], FGlyph[i + 1]]); Inc(i); Inc(n); end; // занесение текущей позиции в стек $5: Result:= Format('%s%d,', [Result, FGlyph[i]]); // восстановление текущей позиции из стека $6: Result:= Format('%s%d,', [Result, FGlyph[i]]); $7: begin // отрисовка субформы, номер которой определяется следующим байтом if FFont.FFontKind = fkUniFont then // (для UNIFONT - двумя) begin Result:= Format('%s%d,0%.4x,', [Result, FGlyph[i], (FGlyph[i + 1] shl 8) + FGlyph[i + 2]]); Inc(i, 2); Inc(n, 2); end else begin Result:= Format('%s%d,%d,', [Result, FGlyph[i], FGlyph[i + 1]]); Inc(i); Inc(n); end; end; $8: begin // смещение по осям X-Y, заданное следующими двумя байтами Result:= Format('%s%d,(%d,%d),', [Result, FGlyph[i], Shortint(FGlyph[i + 1]), Shortint(FGlyph[i + 2])]); Inc(i, 2); Inc(n, 2); end; $9: begin // ряд из нескольких смещений по осям X-Y, оканчивающийся на (0,0) Result:= Format('%s%d,', [Result, FGlyph[i]]); Inc(n); Inc(i, 2); // сейчас указывает на dY while True do begin Result:= Format('%s(%d,%d),', [Result, Shortint(FGlyph[i - 1]), Shortint(FGlyph[i])]); Inc(n, 2); if n > MaxLen then begin Result:= Result + #13#10; n:= 0; end; if (FGlyph[i - 1] = 0) and (FGlyph[i] = 0) then // [(dX,dY) = (0,0)]? Break; Inc(i, 2); end; end; $A: begin // октантная дуга, заданная следующими двумя байтами Result:= Format('%s%d,(%d,%s),', [Result, FGlyph[i], FGlyph[i + 1], EncodeOctant(FGlyph[i + 2])]); Inc(i, 2); Inc(n, 2); end; $B: begin // дробная дуга, заданная следующими 5 байтами Result:= Format('%s%d,(%d,%d,%d,%d,%s),', [Result, FGlyph[i], FGlyph[i + 1], FGlyph[i + 2], FGlyph[i + 3], FGlyph[i + 4], EncodeOctant(FGlyph[i + 5])]); Inc(i, 5); Inc(n, 5); end; $C: begin // дуга, заданная смещением X-Y и прогибом Result:= Format('%s%d,(%d,%d,%d),', [Result, FGlyph[i], Shortint(FGlyph[i + 1]), Shortint(FGlyph[i + 2]), Shortint(FGlyph[i + 3])]); Inc(i, 3); Inc(n, 3); end; $D: begin // несколько дуг, заданных смещением X-Y и прогибом Result:= Format('%s%d,', [Result, FGlyph[i]]); Inc(i, 3); // сейчас указывает на Bulge while True do begin if (FGlyph[i - 2] = 0) and (FGlyph[i - 1] = 0) then // [(dX,dY) = (0,0)]? begin Result:= Format('%s(0,0),', [Result]); Dec(i); Break; end else begin Result:= Format('%s(%d,%d,%d),', [Result, Shortint(FGlyph[i - 2]), Shortint(FGlyph[i - 1]), Shortint(FGlyph[i])]); if n > MaxLen then begin Result:= Result + #13#10; n:= 0; end; end; Inc(i, 3); Inc(n, 3); end; end; // обработка следующей команды только для вертикального текста $E: Result:= Format('%s%d,', [Result, FGlyph[i]]); else // длина вектора и код направления Result:= Format('%s0%.2x,', [Result, FGlyph[i]]); end; Inc(n); if n > MaxLen then begin Result:= Result + #13#10; n:= 0; end; Inc(i); end; end; Реализация функции EncodeOctant: function EncodeOctant(DSC: Byte): String; var S, C: Byte; CW: Boolean; tmp: Shortint; b: Byte; begin tmp:= Shortint(DSC); CW:= ((tmp and $80) <> 0); b:= Byte(tmp); S:= (b and $70) shr 4; C:= b and $07; if CW then Result:= Format('-0%.1x%.1x', [S, C]) else Result:= Format('0%.1x%.1x', [S, C]); end; Добавим метод экспорта в класс TShxFont: procedure TShxFont.ExportToShpFile(const AFileName: String); var Lst: TStringList; i: Integer; begin if Count < 1 then Exit; Lst:= TStringList.Create; try if FFontKind = fkShpFont then begin Lst.Add(Format('*0,4,%s', [FFontName])); Lst.Add(Format('%d,%d,%d,0', [FAbove, FBelow, FModes])); end else // fkUniFont begin Lst.Add(Format('*UNIFONT,6,%s', [FFontName])); Lst.Add(Format('%d,%d,%d,%d,%d,0', [FAbove, FBelow, FModes, FUniEncoding, FUniType])); end; for i:= 0 to Count - 1 do Lst.Add(Shapes[i].GetGlyphDefinition(FMaxStrLen)); Lst.Add(''); Lst.SaveToFile(AFileName); finally Lst.Free; end; end; В прилагаемом примере построено приложение, реализующее описанные классы. В следующей части рассмотрим отрисовку символов. К статье прилагаются файлы: Внимание! Запрещается воспроизведение
данной статьи или ее части без согласования с автором. Если вы желаете разместить
эту статью на своем сайте или издать в печатном виде, свяжитесь с автором. |