{ $Id$ This 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-} 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 char; {+ null terminator + $1A } FileSignature = array [0..12] of char; {+ 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)); {$ifdef ENDIAN_BIG} SwapWord(Header.Options); SwapWord(Header.MainIndexScreen); SwapWord(Header.MaxScreenSize); {$endif ENDIAN_BIG} 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 {$ifdef ENDIAN_BIG} SwapWord(C.LoW); {$endif ENDIAN_BIG} GetCtxPos:=longint(C.HiB) shl 16 + C.LoW; end; begin OK:=ReadRecord(R, true); if OK then with THLPContexts(R.Data^) do begin {$ifdef ENDIAN_BIG} SwapWord(ContextCount); {$endif ENDIAN_BIG} 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 {$ifdef ENDIAN_BIG} SwapWord(IndexCount); {$endif ENDIAN_BIG} 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)); {$ifdef ENDIAN_BIG} SwapWord(H.RecLength); {$endif ENDIAN_BIG} 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: char); begin if Assigned(NewR.Data) then PByteArray(NewR.Data)^[DestPtr]:=ord(C); Inc(DestPtr); end; var CurX,CurY: integer; InLink: boolean; procedure AddChar(C: char); 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: char; P: pointer; function GetNextChar: char; var C: char; 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{$ifdef FPC}(){$endif}; 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 {$ifdef ENDIAN_BIG} SwapWord(UpContext); SwapWord(DownContext); {$endif ENDIAN_BIG} 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 {$ifdef ENDIAN_BIG} SwapWord(KwContext); {$endif ENDIAN_BIG} 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 {$ifdef ENDIAN_BIG} SwapWord(KeywordCount); SwapWord(UpContext); SwapWord(DownContext); {$endif ENDIAN_BIG} T^.LinkCount:=KeywordCount; GetMem(T^.Links,T^.LinkSize); if KeywordCount>0 then for I:=0 to KeywordCount-1 do begin {$ifdef ENDIAN_BIG} SwapWord(Keywords[I].KwContext); {$endif ENDIAN_BIG} 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; {$ifndef FPC}far;{$endif} begin CreateProc:=New(POAHelpFile, Init(FileName,Index)); end; procedure RegisterHelpType; begin RegisterHelpFileType({$ifdef FPC}@{$endif}CreateProc); end; END.