mirror of
https://gitlab.com/freepascal.org/fpc/source.git
synced 2025-04-07 11:28:09 +02:00
578 lines
15 KiB
ObjectPascal
578 lines
15 KiB
ObjectPascal
{
|
|
wThis file is part of the Free Pascal Integrated Development Environment
|
|
Copyright (c) 2000 by Berczi Gabor
|
|
|
|
Borland OA .HLP reader objects and routines
|
|
|
|
See the file COPYING.FPC, included in this distribution,
|
|
for details about the copyright.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
|
|
**********************************************************************}
|
|
{$R-}
|
|
{$H-}
|
|
unit WOAHelp;
|
|
|
|
interface
|
|
|
|
uses Objects,WUtils,WHelp;
|
|
|
|
const
|
|
MinFormatVersion = $04; { was $34 }
|
|
|
|
TP55FormatVersion = $04;
|
|
TP70FormatVersion = $34;
|
|
|
|
Signature = '$*$* &&&&$*$'#0;
|
|
ncRawChar = $F;
|
|
ncRepChar = $E;
|
|
|
|
oa_rtFileHeader = Byte ($0);
|
|
oa_rtContext = Byte ($1);
|
|
oa_rtText = Byte ($2);
|
|
oa_rtKeyWord = Byte ($3);
|
|
oa_rtIndex = Byte ($4);
|
|
oa_rtCompression = Byte ($5);
|
|
oa_rtIndexTags = Byte ($6);
|
|
|
|
ctNone = $00;
|
|
ctNibble = $02;
|
|
|
|
type
|
|
FileStamp = array [0..32] of AnsiChar; {+ null terminator + $1A }
|
|
FileSignature = array [0..12] of AnsiChar; {+ null terminator }
|
|
|
|
THLPVersion = packed record
|
|
FormatVersion : byte;
|
|
TextVersion : byte;
|
|
end;
|
|
|
|
THLPRecordHeader = packed record
|
|
RecType : byte; {TPRecType}
|
|
RecLength : word;
|
|
end;
|
|
|
|
THLPContextPos = packed record
|
|
LoW: word;
|
|
HiB: byte;
|
|
end;
|
|
|
|
THLPContexts = packed record
|
|
ContextCount : word;
|
|
Contexts : array[0..0] of THLPContextPos;
|
|
end;
|
|
|
|
THLPFileHeader = packed record
|
|
Options : word;
|
|
MainIndexScreen : word;
|
|
MaxScreenSize : word;
|
|
Height : byte;
|
|
Width : byte;
|
|
LeftMargin : byte;
|
|
end;
|
|
|
|
THLPCompression = packed record
|
|
CompType : byte;
|
|
CharTable : array [0..13] of byte;
|
|
end;
|
|
|
|
THLPIndexDescriptor = packed record
|
|
LengthCode : byte;
|
|
UniqueChars : array [0..0] of byte;
|
|
Context : word;
|
|
end;
|
|
|
|
THLPIndexTable = packed record
|
|
IndexCount : word;
|
|
Entries : record end;
|
|
end;
|
|
|
|
THLPKeywordDescriptor = packed record
|
|
KwContext : word;
|
|
end;
|
|
|
|
THLPKeyWordRecord = packed record
|
|
UpContext : word;
|
|
DownContext : word;
|
|
KeyWordCount : word;
|
|
Keywords : array[0..0] of THLPKeywordDescriptor;
|
|
end;
|
|
|
|
THLPKeywordDescriptor55 = packed record
|
|
PosY : byte;
|
|
StartX : byte;
|
|
EndX : byte;
|
|
Dunno : array[0..1] of word;
|
|
KwContext : word;
|
|
end;
|
|
|
|
THLPKeyWordRecord55 = packed record
|
|
UpContext : word;
|
|
DownContext : word;
|
|
KeyWordCount : byte;
|
|
Keywords : array[0..0] of THLPKeywordDescriptor55;
|
|
end;
|
|
|
|
POAHelpFile = ^TOAHelpFile;
|
|
TOAHelpFile = object(THelpFile)
|
|
Version : THLPVersion;
|
|
Header : THLPFileHeader;
|
|
Compression : THLPCompression;
|
|
constructor Init(AFileName: string; AID: word);
|
|
destructor Done; virtual;
|
|
public
|
|
function LoadIndex: boolean; virtual;
|
|
function ReadTopic(T: PTopic): boolean; virtual;
|
|
public { protected }
|
|
F: PStream;
|
|
TopicsRead : boolean;
|
|
IndexTableRead : boolean;
|
|
CompressionRead: boolean;
|
|
IndexTagsRead : boolean;
|
|
IndexTagsPos : longint;
|
|
IndexTablePos : longint;
|
|
function ReadHeader: boolean;
|
|
function ReadTopics: boolean;
|
|
function ReadIndexTable: boolean;
|
|
function ReadCompression: boolean;
|
|
function ReadIndexTags: boolean;
|
|
function ReadRecord(var R: TRecord; ReadData: boolean): boolean;
|
|
end;
|
|
|
|
procedure RegisterHelpType;
|
|
|
|
implementation
|
|
|
|
|
|
constructor TOAHelpFile.Init(AFileName: string; AID: word);
|
|
var OK: boolean;
|
|
FS,L: longint;
|
|
R: TRecord;
|
|
begin
|
|
if inherited Init(AID)=false then Fail;
|
|
F:=New(PFastBufStream, Init(AFileName, stOpenRead, HelpStreamBufSize));
|
|
OK:=F<>nil;
|
|
if OK then OK:=(F^.Status=stOK);
|
|
if OK then
|
|
begin
|
|
FS:=F^.GetSize;
|
|
OK:=ReadHeader;
|
|
end;
|
|
while OK do
|
|
begin
|
|
L:=F^.GetPos;
|
|
if (L>=FS) then Break;
|
|
OK:=ReadRecord(R,false);
|
|
if (OK=false) or (R.SClass=0) or (R.Size=0) then Break;
|
|
case R.SClass of
|
|
oa_rtContext : begin F^.Seek(L); OK:=ReadTopics; end;
|
|
oa_rtText : {Skip};
|
|
oa_rtKeyword : {Skip};
|
|
oa_rtIndex : begin IndexTablePos:=L; {OK:=ReadIndexTable; }end;
|
|
oa_rtCompression : begin F^.Seek(L); OK:=ReadCompression; end;
|
|
oa_rtIndexTags : begin IndexTagsPos:=L; {OK:=ReadIndexTags; }end;
|
|
else
|
|
begin
|
|
{$ifdef DEBUGMSG}
|
|
ClearFormatParams;
|
|
AddFormatParamInt(R.SClass);
|
|
AddFormatParamInt(L);
|
|
AddFormatParamInt(R.Size);
|
|
ErrorBox('Uknown help record tag %x encountered, '+
|
|
'offset %x, size %d',@FormatParams);
|
|
{$else}
|
|
{Skip};
|
|
{$endif}
|
|
end;
|
|
end;
|
|
if OK then
|
|
begin Inc(L, SizeOf(THLPRecordHeader)); Inc(L, R.Size); F^.Seek(L); OK:=(F^.Status=stOK); end
|
|
end;
|
|
OK:=OK and (TopicsRead=true);
|
|
if OK=false then
|
|
Begin
|
|
Done;
|
|
Fail;
|
|
End;
|
|
end;
|
|
|
|
function TOAHelpFile.LoadIndex: boolean;
|
|
begin
|
|
LoadIndex:=ReadIndexTable;
|
|
end;
|
|
|
|
function TOAHelpFile.ReadHeader: boolean;
|
|
var S: string;
|
|
P: longint;
|
|
R: TRecord;
|
|
OK: boolean;
|
|
begin
|
|
F^.Seek(0);
|
|
F^.Read(S[1],128); S[0]:=#255;
|
|
OK:=(F^.Status=stOK); P:=Pos(Signature,S);
|
|
OK:=OK and (P>0);
|
|
if OK then
|
|
begin
|
|
F^.Seek(P+length(Signature)-1);
|
|
F^.Read(Version,SizeOf(Version));
|
|
OK:=(F^.Status=stOK) and (Version.FormatVersion>=MinFormatVersion);
|
|
if OK then
|
|
begin
|
|
OK:=ReadRecord(R,true);
|
|
OK:=OK and (R.SClass=oa_rtFileHeader) and (R.Size=SizeOf(Header));
|
|
if OK then Move(R.Data^,Header,SizeOf(Header));
|
|
Header.Options :=LEToN(Header.Options);
|
|
Header.MainIndexScreen:=LEToN(Header.MainIndexScreen);
|
|
Header.MaxScreenSize :=LEToN(Header.MaxScreenSize );
|
|
DisposeRecord(R);
|
|
end;
|
|
end;
|
|
ReadHeader:=OK;
|
|
end;
|
|
|
|
function TOAHelpFile.ReadTopics: boolean;
|
|
var OK: boolean;
|
|
R: TRecord;
|
|
L,I: longint;
|
|
function GetCtxPos(C: THLPContextPos): longint;
|
|
begin
|
|
c.LoW:=LEToN(Word(C.LoW));
|
|
GetCtxPos:=longint(C.HiB) shl 16 + C.LoW;
|
|
end;
|
|
begin
|
|
OK:=ReadRecord(R, true);
|
|
if OK then
|
|
with THLPContexts(R.Data^) do
|
|
begin
|
|
ContextCount:=LEToN(ContextCount);
|
|
for I:=1 to longint(ContextCount)-1 do
|
|
begin
|
|
if Topics^.Count=MaxCollectionSize then Break;
|
|
L:=GetCtxPos(Contexts[I]);
|
|
if (L and $800000)<>0 then L:=not L;
|
|
if (L=-1) and (Header.MainIndexScreen>0) then
|
|
L:=GetCtxPos(Contexts[Header.MainIndexScreen]);
|
|
if (L>0) then
|
|
AddTopic(I,L,'',nil,0);
|
|
end;
|
|
end;
|
|
DisposeRecord(R);
|
|
TopicsRead:=OK;
|
|
ReadTopics:=OK;
|
|
end;
|
|
|
|
function TOAHelpFile.ReadIndexTable: boolean;
|
|
var OK: boolean;
|
|
R: TRecord;
|
|
I: longint;
|
|
LastTag,S: string;
|
|
CurPtr: sw_word;
|
|
HelpCtx: THelpCtx;
|
|
LenCode,CopyCnt,AddLen: byte;
|
|
type pword = ^word;
|
|
begin
|
|
if IndexTableRead then OK:=true else
|
|
begin
|
|
FillChar(R, SizeOf(R), 0);
|
|
LastTag:=''; CurPtr:=0;
|
|
OK:=(IndexTablePos<>0);
|
|
if OK then begin F^.Seek(IndexTablePos); OK:=F^.Status=stOK; end;
|
|
if OK then OK:=ReadRecord(R, true);
|
|
if OK then
|
|
with THLPIndexTable(R.Data^) do
|
|
begin
|
|
IndexCount:=LEToN(IndexCount);
|
|
for I:=0 to IndexCount-1 do
|
|
begin
|
|
LenCode:=PByteArray(@Entries)^[CurPtr];
|
|
AddLen:=LenCode and $1f; CopyCnt:=LenCode shr 5;
|
|
S[0]:=chr(AddLen); Move(PByteArray(@Entries)^[CurPtr+1],S[1],AddLen);
|
|
LastTag:=copy(LastTag,1,CopyCnt)+S;
|
|
HelpCtx:=PWord(@PByteArray(@Entries)^[CurPtr+1+AddLen])^;
|
|
AddIndexEntry(LastTag,HelpCtx);
|
|
Inc(CurPtr,1+AddLen+2);
|
|
end;
|
|
end;
|
|
DisposeRecord(R);
|
|
IndexTableRead:=OK;
|
|
end;
|
|
ReadIndexTable:=OK;
|
|
end;
|
|
|
|
function TOAHelpFile.ReadCompression: boolean;
|
|
var OK: boolean;
|
|
R: TRecord;
|
|
begin
|
|
OK:=ReadRecord(R, true);
|
|
OK:=OK and (R.Size=SizeOf(THLPCompression));
|
|
if OK then Move(R.Data^,Compression,SizeOf(Compression));
|
|
DisposeRecord(R);
|
|
CompressionRead:=OK;
|
|
ReadCompression:=OK;
|
|
end;
|
|
|
|
function TOAHelpFile.ReadIndexTags: boolean;
|
|
var OK: boolean;
|
|
begin
|
|
OK:={ReadRecord(R, true)}true;
|
|
IndexTagsRead:=OK;
|
|
ReadIndexTags:=OK;
|
|
end;
|
|
|
|
function TOAHelpFile.ReadRecord(var R: TRecord; ReadData: boolean): boolean;
|
|
var OK: boolean;
|
|
H: THLPRecordHeader;
|
|
begin
|
|
FillChar(R, SizeOf(R), 0);
|
|
F^.Read(H,SizeOf(H));
|
|
H.RecLength:=LEToN(H.RecLength);
|
|
OK:=F^.Status=stOK;
|
|
if OK then
|
|
begin
|
|
R.SClass:=H.RecType; R.Size:=H.RecLength;
|
|
if (R.Size>0) and ReadData then
|
|
begin
|
|
GetMem(R.Data,R.Size);
|
|
F^.Read(R.Data^,R.Size);
|
|
OK:=F^.Status=stOK;
|
|
end;
|
|
if OK=false then DisposeRecord(R);
|
|
end;
|
|
ReadRecord:=OK;
|
|
end;
|
|
|
|
function TOAHelpFile.ReadTopic(T: PTopic): boolean;
|
|
var SrcPtr,DestPtr,TopicSize: sw_word;
|
|
NewR: TRecord;
|
|
LinkPosCount: integer;
|
|
LinkPos: array[1..50] of TRect;
|
|
function IsLinkPosStart(X,Y: integer): boolean;
|
|
var OK: boolean;
|
|
I: integer;
|
|
begin
|
|
OK:=false;
|
|
for I:=1 to LinkPosCount do
|
|
with LinkPos[I] do
|
|
if (A.X=X) and (A.Y=Y) then
|
|
begin
|
|
OK:=true;
|
|
Break;
|
|
end;
|
|
IsLinkPosStart:=OK;
|
|
end;
|
|
function IsLinkPosEnd(X,Y: integer): boolean;
|
|
var OK: boolean;
|
|
I: integer;
|
|
begin
|
|
OK:=false;
|
|
for I:=1 to LinkPosCount do
|
|
with LinkPos[I] do
|
|
if (B.X=X) and (B.Y=Y) then
|
|
begin
|
|
OK:=true;
|
|
Break;
|
|
end;
|
|
IsLinkPosEnd:=OK;
|
|
end;
|
|
function ExtractTextRec(var R: TRecord): boolean;
|
|
function GetNextNibble: byte;
|
|
var B,N: byte;
|
|
begin
|
|
B:=PByteArray(R.Data)^[SrcPtr div 2];
|
|
N:=( B and ($0f shl (4*(SrcPtr mod 2))) ) shr (4*(SrcPtr mod 2));
|
|
Inc(SrcPtr);
|
|
GetNextNibble:=N;
|
|
end;
|
|
procedure RealAddChar(C: AnsiChar);
|
|
begin
|
|
if Assigned(NewR.Data) then
|
|
PByteArray(NewR.Data)^[DestPtr]:=ord(C);
|
|
Inc(DestPtr);
|
|
end;
|
|
var CurX,CurY: integer;
|
|
InLink: boolean;
|
|
procedure AddChar(C: AnsiChar);
|
|
begin
|
|
if IsLinkPosStart(CurX+2,CurY) then
|
|
begin
|
|
RealAddChar(hscLink);
|
|
InLink:=true;
|
|
end
|
|
else
|
|
if (C=hscLineBreak) and (InLink) then
|
|
begin
|
|
RealAddChar(hscLink);
|
|
InLink:=false;
|
|
end;
|
|
RealAddChar(C);
|
|
if IsLinkPosEnd(CurX+2,CurY) then
|
|
begin
|
|
RealAddChar(hscLink);
|
|
InLink:=false;
|
|
end;
|
|
if C<>hscLineBreak then
|
|
Inc(CurX)
|
|
else
|
|
begin
|
|
CurX:=0;
|
|
Inc(CurY);
|
|
end;
|
|
end;
|
|
var OK: boolean;
|
|
C: AnsiChar;
|
|
P: pointer;
|
|
function GetNextChar: AnsiChar;
|
|
var C: AnsiChar;
|
|
I,N,Cnt: byte;
|
|
begin
|
|
N:=GetNextNibble;
|
|
case N of
|
|
$00 : C:=#0;
|
|
$01..$0D : C:=chr(Compression.CharTable[N]);
|
|
ncRawChar : begin
|
|
I:=GetNextNibble;
|
|
C:=chr(I+GetNextNibble shl 4);
|
|
end;
|
|
ncRepChar : begin
|
|
Cnt:=2+GetNextNibble;
|
|
C:=GetNextChar();
|
|
for I:=1 to Cnt-1 do AddChar(C);
|
|
end;
|
|
end;
|
|
GetNextChar:=C;
|
|
end;
|
|
begin
|
|
OK:=Compression.CompType in[ctNone,ctNibble];
|
|
if OK then
|
|
case Compression.CompType of
|
|
ctNone : ;
|
|
ctNibble :
|
|
begin
|
|
CurX:=0; CurY:=0; InLink:=false;
|
|
NewR.SClass:=0;
|
|
NewR.Size:=0;
|
|
NewR.Data:=nil;
|
|
SrcPtr:=0; DestPtr:=0;
|
|
while SrcPtr<(R.Size*2) do
|
|
begin
|
|
C:=GetNextChar;
|
|
AddChar(C);
|
|
end;
|
|
if InLink then AddChar(hscLineBreak);
|
|
TopicSize:=DestPtr;
|
|
|
|
CurX:=0; CurY:=0; InLink:=false;
|
|
NewR.SClass:=R.SClass;
|
|
NewR.Size:=Min(MaxHelpTopicSize,TopicSize);
|
|
GetMem(NewR.Data, NewR.Size);
|
|
SrcPtr:=0; DestPtr:=0;
|
|
while SrcPtr<(R.Size*2) do
|
|
begin
|
|
C:=GetNextChar;
|
|
AddChar(C);
|
|
end;
|
|
if InLink then AddChar(hscLineBreak);
|
|
DisposeRecord(R); R:=NewR;
|
|
if (R.Size>DestPtr) then
|
|
begin
|
|
P:=R.Data; GetMem(R.Data,DestPtr);
|
|
Move(P^,R.Data^,DestPtr); FreeMem(P,R.Size); R.Size:=DestPtr;
|
|
end;
|
|
end;
|
|
else OK:=false;
|
|
end;
|
|
ExtractTextRec:=OK;
|
|
end;
|
|
var OK: boolean;
|
|
TextR,KeyWR: TRecord;
|
|
I: sw_word;
|
|
begin
|
|
OK:=T<>nil;
|
|
if OK and (T^.Text=nil) then
|
|
begin
|
|
LinkPosCount:=0; FillChar(LinkPos,Sizeof(LinkPos),0);
|
|
FillChar(TextR,SizeOf(TextR),0); FillChar(KeyWR,SizeOf(KeyWR),0);
|
|
F^.Seek(T^.FileOfs); OK:=F^.Status=stOK;
|
|
if OK then OK:=ReadRecord(TextR,true);
|
|
OK:=OK and (TextR.SClass=oa_rtText);
|
|
if OK then OK:=ReadRecord(KeyWR,true);
|
|
OK:=OK and (KeyWR.SClass=oa_rtKeyword);
|
|
|
|
if OK then
|
|
begin
|
|
case Version.FormatVersion of
|
|
TP55FormatVersion :
|
|
with THLPKeywordRecord55(KeyWR.Data^) do
|
|
begin
|
|
UpContext:=LEToN(UpContext);
|
|
DownContext:=LEToN(DownContext);
|
|
T^.LinkCount:=KeywordCount;
|
|
GetMem(T^.Links,T^.LinkSize);
|
|
if T^.LinkCount>0 then
|
|
for I:=0 to T^.LinkCount-1 do
|
|
with Keywords[I] do
|
|
begin
|
|
KwContext:=LEToN(KwContext);
|
|
T^.Links^[I].Context:=KwContext;
|
|
T^.Links^[I].FileID:=ID;
|
|
Inc(LinkPosCount);
|
|
with LinkPos[LinkPosCount] do
|
|
begin
|
|
A.Y:=PosY-1; B.Y:=PosY-1;
|
|
A.X:=StartX-1; B.X:=EndX-1;
|
|
end;
|
|
end;
|
|
end;
|
|
else
|
|
with THLPKeywordRecord(KeyWR.Data^) do
|
|
begin
|
|
KeywordCount:=LEToN(KeywordCount);
|
|
UpContext:=LEToN(UpContext);
|
|
DownContext:=LEToN(DownContext);
|
|
T^.LinkCount:=KeywordCount;
|
|
GetMem(T^.Links,T^.LinkSize);
|
|
if KeywordCount>0 then
|
|
for I:=0 to KeywordCount-1 do
|
|
begin
|
|
Keywords[I].KwContext:=LEToN(Keywords[I].KwContext);
|
|
T^.Links^[I].Context:=Keywords[I].KwContext;
|
|
T^.Links^[I].FileID:=ID;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
if OK then OK:=ExtractTextRec(TextR);
|
|
if OK then
|
|
if TextR.Size>0 then
|
|
begin
|
|
T^.Text:=TextR.Data; T^.TextSize:=TextR.Size;
|
|
TextR.Data:=nil; TextR.Size:=0;
|
|
end;
|
|
|
|
DisposeRecord(TextR); DisposeRecord(KeyWR);
|
|
end;
|
|
ReadTopic:=OK;
|
|
end;
|
|
|
|
destructor TOAHelpFile.Done;
|
|
begin
|
|
if F<>nil then Dispose(F, Done);
|
|
inherited Done;
|
|
end;
|
|
|
|
function CreateProc(const FileName,Param: string;Index : longint): PHelpFile;
|
|
begin
|
|
CreateProc:=New(POAHelpFile, Init(FileName,Index));
|
|
end;
|
|
|
|
procedure RegisterHelpType;
|
|
begin
|
|
RegisterHelpFileType(@CreateProc);
|
|
end;
|
|
|
|
END.
|