woahelp.pas 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595
  1. {
  2. $Id$
  3. This file is part of the Free Pascal Integrated Development Environment
  4. Copyright (c) 2000 by Berczi Gabor
  5. Borland OA .HLP reader objects and routines
  6. See the file COPYING.FPC, included in this distribution,
  7. for details about the copyright.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  11. **********************************************************************}
  12. {$R-}
  13. unit WOAHelp;
  14. interface
  15. uses Objects,WUtils,WHelp;
  16. const
  17. MinFormatVersion = $04; { was $34 }
  18. TP55FormatVersion = $04;
  19. TP70FormatVersion = $34;
  20. Signature = '$*$* &&&&$*$'#0;
  21. ncRawChar = $F;
  22. ncRepChar = $E;
  23. oa_rtFileHeader = Byte ($0);
  24. oa_rtContext = Byte ($1);
  25. oa_rtText = Byte ($2);
  26. oa_rtKeyWord = Byte ($3);
  27. oa_rtIndex = Byte ($4);
  28. oa_rtCompression = Byte ($5);
  29. oa_rtIndexTags = Byte ($6);
  30. ctNone = $00;
  31. ctNibble = $02;
  32. type
  33. FileStamp = array [0..32] of char; {+ null terminator + $1A }
  34. FileSignature = array [0..12] of char; {+ null terminator }
  35. THLPVersion = packed record
  36. FormatVersion : byte;
  37. TextVersion : byte;
  38. end;
  39. THLPRecordHeader = packed record
  40. RecType : byte; {TPRecType}
  41. RecLength : word;
  42. end;
  43. THLPContextPos = packed record
  44. LoW: word;
  45. HiB: byte;
  46. end;
  47. THLPContexts = packed record
  48. ContextCount : word;
  49. Contexts : array[0..0] of THLPContextPos;
  50. end;
  51. THLPFileHeader = packed record
  52. Options : word;
  53. MainIndexScreen : word;
  54. MaxScreenSize : word;
  55. Height : byte;
  56. Width : byte;
  57. LeftMargin : byte;
  58. end;
  59. THLPCompression = packed record
  60. CompType : byte;
  61. CharTable : array [0..13] of byte;
  62. end;
  63. THLPIndexDescriptor = packed record
  64. LengthCode : byte;
  65. UniqueChars : array [0..0] of byte;
  66. Context : word;
  67. end;
  68. THLPIndexTable = packed record
  69. IndexCount : word;
  70. Entries : record end;
  71. end;
  72. THLPKeywordDescriptor = packed record
  73. KwContext : word;
  74. end;
  75. THLPKeyWordRecord = packed record
  76. UpContext : word;
  77. DownContext : word;
  78. KeyWordCount : word;
  79. Keywords : array[0..0] of THLPKeywordDescriptor;
  80. end;
  81. THLPKeywordDescriptor55 = packed record
  82. PosY : byte;
  83. StartX : byte;
  84. EndX : byte;
  85. Dunno : array[0..1] of word;
  86. KwContext : word;
  87. end;
  88. THLPKeyWordRecord55 = packed record
  89. UpContext : word;
  90. DownContext : word;
  91. KeyWordCount : byte;
  92. Keywords : array[0..0] of THLPKeywordDescriptor55;
  93. end;
  94. POAHelpFile = ^TOAHelpFile;
  95. TOAHelpFile = object(THelpFile)
  96. Version : THLPVersion;
  97. Header : THLPFileHeader;
  98. Compression : THLPCompression;
  99. constructor Init(AFileName: string; AID: word);
  100. destructor Done; virtual;
  101. public
  102. function LoadIndex: boolean; virtual;
  103. function ReadTopic(T: PTopic): boolean; virtual;
  104. public { protected }
  105. F: PStream;
  106. TopicsRead : boolean;
  107. IndexTableRead : boolean;
  108. CompressionRead: boolean;
  109. IndexTagsRead : boolean;
  110. IndexTagsPos : longint;
  111. IndexTablePos : longint;
  112. function ReadHeader: boolean;
  113. function ReadTopics: boolean;
  114. function ReadIndexTable: boolean;
  115. function ReadCompression: boolean;
  116. function ReadIndexTags: boolean;
  117. function ReadRecord(var R: TRecord; ReadData: boolean): boolean;
  118. end;
  119. procedure RegisterHelpType;
  120. implementation
  121. constructor TOAHelpFile.Init(AFileName: string; AID: word);
  122. var OK: boolean;
  123. FS,L: longint;
  124. R: TRecord;
  125. begin
  126. if inherited Init(AID)=false then Fail;
  127. F:=New(PFastBufStream, Init(AFileName, stOpenRead, HelpStreamBufSize));
  128. OK:=F<>nil;
  129. if OK then OK:=(F^.Status=stOK);
  130. if OK then
  131. begin
  132. FS:=F^.GetSize;
  133. OK:=ReadHeader;
  134. end;
  135. while OK do
  136. begin
  137. L:=F^.GetPos;
  138. if (L>=FS) then Break;
  139. OK:=ReadRecord(R,false);
  140. if (OK=false) or (R.SClass=0) or (R.Size=0) then Break;
  141. case R.SClass of
  142. oa_rtContext : begin F^.Seek(L); OK:=ReadTopics; end;
  143. oa_rtText : {Skip};
  144. oa_rtKeyword : {Skip};
  145. oa_rtIndex : begin IndexTablePos:=L; {OK:=ReadIndexTable; }end;
  146. oa_rtCompression : begin F^.Seek(L); OK:=ReadCompression; end;
  147. oa_rtIndexTags : begin IndexTagsPos:=L; {OK:=ReadIndexTags; }end;
  148. else
  149. begin
  150. {$ifdef DEBUGMSG}
  151. ClearFormatParams;
  152. AddFormatParamInt(R.SClass);
  153. AddFormatParamInt(L);
  154. AddFormatParamInt(R.Size);
  155. ErrorBox('Uknown help record tag %x encountered, '+
  156. 'offset %x, size %d',@FormatParams);
  157. {$else}
  158. {Skip};
  159. {$endif}
  160. end;
  161. end;
  162. if OK then
  163. begin Inc(L, SizeOf(THLPRecordHeader)); Inc(L, R.Size); F^.Seek(L); OK:=(F^.Status=stOK); end
  164. end;
  165. OK:=OK and (TopicsRead=true);
  166. if OK=false then
  167. Begin
  168. Done;
  169. Fail;
  170. End;
  171. end;
  172. function TOAHelpFile.LoadIndex: boolean;
  173. begin
  174. LoadIndex:=ReadIndexTable;
  175. end;
  176. function TOAHelpFile.ReadHeader: boolean;
  177. var S: string;
  178. P: longint;
  179. R: TRecord;
  180. OK: boolean;
  181. begin
  182. F^.Seek(0);
  183. F^.Read(S[1],128); S[0]:=#255;
  184. OK:=(F^.Status=stOK); P:=Pos(Signature,S);
  185. OK:=OK and (P>0);
  186. if OK then
  187. begin
  188. F^.Seek(P+length(Signature)-1);
  189. F^.Read(Version,SizeOf(Version));
  190. OK:=(F^.Status=stOK) and (Version.FormatVersion>=MinFormatVersion);
  191. if OK then
  192. begin
  193. OK:=ReadRecord(R,true);
  194. OK:=OK and (R.SClass=oa_rtFileHeader) and (R.Size=SizeOf(Header));
  195. if OK then Move(R.Data^,Header,SizeOf(Header));
  196. {$ifdef ENDIAN_BIG}
  197. SwapWord(Header.Options);
  198. SwapWord(Header.MainIndexScreen);
  199. SwapWord(Header.MaxScreenSize);
  200. {$endif ENDIAN_BIG}
  201. DisposeRecord(R);
  202. end;
  203. end;
  204. ReadHeader:=OK;
  205. end;
  206. function TOAHelpFile.ReadTopics: boolean;
  207. var OK: boolean;
  208. R: TRecord;
  209. L,I: longint;
  210. function GetCtxPos(C: THLPContextPos): longint;
  211. begin
  212. {$ifdef ENDIAN_BIG}
  213. SwapWord(C.LoW);
  214. {$endif ENDIAN_BIG}
  215. GetCtxPos:=longint(C.HiB) shl 16 + C.LoW;
  216. end;
  217. begin
  218. OK:=ReadRecord(R, true);
  219. if OK then
  220. with THLPContexts(R.Data^) do
  221. begin
  222. {$ifdef ENDIAN_BIG}
  223. SwapWord(ContextCount);
  224. {$endif ENDIAN_BIG}
  225. for I:=1 to longint(ContextCount)-1 do
  226. begin
  227. if Topics^.Count=MaxCollectionSize then Break;
  228. L:=GetCtxPos(Contexts[I]);
  229. if (L and $800000)<>0 then L:=not L;
  230. if (L=-1) and (Header.MainIndexScreen>0) then
  231. L:=GetCtxPos(Contexts[Header.MainIndexScreen]);
  232. if (L>0) then
  233. AddTopic(I,L,'',nil,0);
  234. end;
  235. end;
  236. DisposeRecord(R);
  237. TopicsRead:=OK;
  238. ReadTopics:=OK;
  239. end;
  240. function TOAHelpFile.ReadIndexTable: boolean;
  241. var OK: boolean;
  242. R: TRecord;
  243. I: longint;
  244. LastTag,S: string;
  245. CurPtr: sw_word;
  246. HelpCtx: THelpCtx;
  247. LenCode,CopyCnt,AddLen: byte;
  248. type pword = ^word;
  249. begin
  250. if IndexTableRead then OK:=true else
  251. begin
  252. FillChar(R, SizeOf(R), 0);
  253. LastTag:=''; CurPtr:=0;
  254. OK:=(IndexTablePos<>0);
  255. if OK then begin F^.Seek(IndexTablePos); OK:=F^.Status=stOK; end;
  256. if OK then OK:=ReadRecord(R, true);
  257. if OK then
  258. with THLPIndexTable(R.Data^) do
  259. begin
  260. {$ifdef ENDIAN_BIG}
  261. SwapWord(IndexCount);
  262. {$endif ENDIAN_BIG}
  263. for I:=0 to IndexCount-1 do
  264. begin
  265. LenCode:=PByteArray(@Entries)^[CurPtr];
  266. AddLen:=LenCode and $1f; CopyCnt:=LenCode shr 5;
  267. S[0]:=chr(AddLen); Move(PByteArray(@Entries)^[CurPtr+1],S[1],AddLen);
  268. LastTag:=copy(LastTag,1,CopyCnt)+S;
  269. HelpCtx:=PWord(@PByteArray(@Entries)^[CurPtr+1+AddLen])^;
  270. AddIndexEntry(LastTag,HelpCtx);
  271. Inc(CurPtr,1+AddLen+2);
  272. end;
  273. end;
  274. DisposeRecord(R);
  275. IndexTableRead:=OK;
  276. end;
  277. ReadIndexTable:=OK;
  278. end;
  279. function TOAHelpFile.ReadCompression: boolean;
  280. var OK: boolean;
  281. R: TRecord;
  282. begin
  283. OK:=ReadRecord(R, true);
  284. OK:=OK and (R.Size=SizeOf(THLPCompression));
  285. if OK then Move(R.Data^,Compression,SizeOf(Compression));
  286. DisposeRecord(R);
  287. CompressionRead:=OK;
  288. ReadCompression:=OK;
  289. end;
  290. function TOAHelpFile.ReadIndexTags: boolean;
  291. var OK: boolean;
  292. begin
  293. OK:={ReadRecord(R, true)}true;
  294. IndexTagsRead:=OK;
  295. ReadIndexTags:=OK;
  296. end;
  297. function TOAHelpFile.ReadRecord(var R: TRecord; ReadData: boolean): boolean;
  298. var OK: boolean;
  299. H: THLPRecordHeader;
  300. begin
  301. FillChar(R, SizeOf(R), 0);
  302. F^.Read(H,SizeOf(H));
  303. {$ifdef ENDIAN_BIG}
  304. SwapWord(H.RecLength);
  305. {$endif ENDIAN_BIG}
  306. OK:=F^.Status=stOK;
  307. if OK then
  308. begin
  309. R.SClass:=H.RecType; R.Size:=H.RecLength;
  310. if (R.Size>0) and ReadData then
  311. begin
  312. GetMem(R.Data,R.Size);
  313. F^.Read(R.Data^,R.Size);
  314. OK:=F^.Status=stOK;
  315. end;
  316. if OK=false then DisposeRecord(R);
  317. end;
  318. ReadRecord:=OK;
  319. end;
  320. function TOAHelpFile.ReadTopic(T: PTopic): boolean;
  321. var SrcPtr,DestPtr,TopicSize: sw_word;
  322. NewR: TRecord;
  323. LinkPosCount: integer;
  324. LinkPos: array[1..50] of TRect;
  325. function IsLinkPosStart(X,Y: integer): boolean;
  326. var OK: boolean;
  327. I: integer;
  328. begin
  329. OK:=false;
  330. for I:=1 to LinkPosCount do
  331. with LinkPos[I] do
  332. if (A.X=X) and (A.Y=Y) then
  333. begin
  334. OK:=true;
  335. Break;
  336. end;
  337. IsLinkPosStart:=OK;
  338. end;
  339. function IsLinkPosEnd(X,Y: integer): boolean;
  340. var OK: boolean;
  341. I: integer;
  342. begin
  343. OK:=false;
  344. for I:=1 to LinkPosCount do
  345. with LinkPos[I] do
  346. if (B.X=X) and (B.Y=Y) then
  347. begin
  348. OK:=true;
  349. Break;
  350. end;
  351. IsLinkPosEnd:=OK;
  352. end;
  353. function ExtractTextRec(var R: TRecord): boolean;
  354. function GetNextNibble: byte;
  355. var B,N: byte;
  356. begin
  357. B:=PByteArray(R.Data)^[SrcPtr div 2];
  358. N:=( B and ($0f shl (4*(SrcPtr mod 2))) ) shr (4*(SrcPtr mod 2));
  359. Inc(SrcPtr);
  360. GetNextNibble:=N;
  361. end;
  362. procedure RealAddChar(C: char);
  363. begin
  364. if Assigned(NewR.Data) then
  365. PByteArray(NewR.Data)^[DestPtr]:=ord(C);
  366. Inc(DestPtr);
  367. end;
  368. var CurX,CurY: integer;
  369. InLink: boolean;
  370. procedure AddChar(C: char);
  371. begin
  372. if IsLinkPosStart(CurX+2,CurY) then
  373. begin
  374. RealAddChar(hscLink);
  375. InLink:=true;
  376. end
  377. else
  378. if (C=hscLineBreak) and (InLink) then
  379. begin
  380. RealAddChar(hscLink);
  381. InLink:=false;
  382. end;
  383. RealAddChar(C);
  384. if IsLinkPosEnd(CurX+2,CurY) then
  385. begin
  386. RealAddChar(hscLink);
  387. InLink:=false;
  388. end;
  389. if C<>hscLineBreak then
  390. Inc(CurX)
  391. else
  392. begin
  393. CurX:=0;
  394. Inc(CurY);
  395. end;
  396. end;
  397. var OK: boolean;
  398. C: char;
  399. P: pointer;
  400. function GetNextChar: char;
  401. var C: char;
  402. I,N,Cnt: byte;
  403. begin
  404. N:=GetNextNibble;
  405. case N of
  406. $00 : C:=#0;
  407. $01..$0D : C:=chr(Compression.CharTable[N]);
  408. ncRawChar : begin
  409. I:=GetNextNibble;
  410. C:=chr(I+GetNextNibble shl 4);
  411. end;
  412. ncRepChar : begin
  413. Cnt:=2+GetNextNibble;
  414. C:=GetNextChar{$ifdef FPC}(){$endif};
  415. for I:=1 to Cnt-1 do AddChar(C);
  416. end;
  417. end;
  418. GetNextChar:=C;
  419. end;
  420. begin
  421. OK:=Compression.CompType in[ctNone,ctNibble];
  422. if OK then
  423. case Compression.CompType of
  424. ctNone : ;
  425. ctNibble :
  426. begin
  427. CurX:=0; CurY:=0; InLink:=false;
  428. NewR.SClass:=0;
  429. NewR.Size:=0;
  430. NewR.Data:=nil;
  431. SrcPtr:=0; DestPtr:=0;
  432. while SrcPtr<(R.Size*2) do
  433. begin
  434. C:=GetNextChar;
  435. AddChar(C);
  436. end;
  437. if InLink then AddChar(hscLineBreak);
  438. TopicSize:=DestPtr;
  439. CurX:=0; CurY:=0; InLink:=false;
  440. NewR.SClass:=R.SClass;
  441. NewR.Size:=Min(MaxHelpTopicSize,TopicSize);
  442. GetMem(NewR.Data, NewR.Size);
  443. SrcPtr:=0; DestPtr:=0;
  444. while SrcPtr<(R.Size*2) do
  445. begin
  446. C:=GetNextChar;
  447. AddChar(C);
  448. end;
  449. if InLink then AddChar(hscLineBreak);
  450. DisposeRecord(R); R:=NewR;
  451. if (R.Size>DestPtr) then
  452. begin
  453. P:=R.Data; GetMem(R.Data,DestPtr);
  454. Move(P^,R.Data^,DestPtr); FreeMem(P,R.Size); R.Size:=DestPtr;
  455. end;
  456. end;
  457. else OK:=false;
  458. end;
  459. ExtractTextRec:=OK;
  460. end;
  461. var OK: boolean;
  462. TextR,KeyWR: TRecord;
  463. I: sw_word;
  464. begin
  465. OK:=T<>nil;
  466. if OK and (T^.Text=nil) then
  467. begin
  468. LinkPosCount:=0; FillChar(LinkPos,Sizeof(LinkPos),0);
  469. FillChar(TextR,SizeOf(TextR),0); FillChar(KeyWR,SizeOf(KeyWR),0);
  470. F^.Seek(T^.FileOfs); OK:=F^.Status=stOK;
  471. if OK then OK:=ReadRecord(TextR,true);
  472. OK:=OK and (TextR.SClass=oa_rtText);
  473. if OK then OK:=ReadRecord(KeyWR,true);
  474. OK:=OK and (KeyWR.SClass=oa_rtKeyword);
  475. if OK then
  476. begin
  477. case Version.FormatVersion of
  478. TP55FormatVersion :
  479. with THLPKeywordRecord55(KeyWR.Data^) do
  480. begin
  481. {$ifdef ENDIAN_BIG}
  482. SwapWord(UpContext);
  483. SwapWord(DownContext);
  484. {$endif ENDIAN_BIG}
  485. T^.LinkCount:=KeywordCount;
  486. GetMem(T^.Links,T^.LinkSize);
  487. if T^.LinkCount>0 then
  488. for I:=0 to T^.LinkCount-1 do
  489. with Keywords[I] do
  490. begin
  491. {$ifdef ENDIAN_BIG}
  492. SwapWord(KwContext);
  493. {$endif ENDIAN_BIG}
  494. T^.Links^[I].Context:=KwContext;
  495. T^.Links^[I].FileID:=ID;
  496. Inc(LinkPosCount);
  497. with LinkPos[LinkPosCount] do
  498. begin
  499. A.Y:=PosY-1; B.Y:=PosY-1;
  500. A.X:=StartX-1; B.X:=EndX-1;
  501. end;
  502. end;
  503. end;
  504. else
  505. with THLPKeywordRecord(KeyWR.Data^) do
  506. begin
  507. {$ifdef ENDIAN_BIG}
  508. SwapWord(KeywordCount);
  509. SwapWord(UpContext);
  510. SwapWord(DownContext);
  511. {$endif ENDIAN_BIG}
  512. T^.LinkCount:=KeywordCount;
  513. GetMem(T^.Links,T^.LinkSize);
  514. if KeywordCount>0 then
  515. for I:=0 to KeywordCount-1 do
  516. begin
  517. {$ifdef ENDIAN_BIG}
  518. SwapWord(Keywords[I].KwContext);
  519. {$endif ENDIAN_BIG}
  520. T^.Links^[I].Context:=Keywords[I].KwContext;
  521. T^.Links^[I].FileID:=ID;
  522. end;
  523. end;
  524. end;
  525. end;
  526. if OK then OK:=ExtractTextRec(TextR);
  527. if OK then
  528. if TextR.Size>0 then
  529. begin
  530. T^.Text:=TextR.Data; T^.TextSize:=TextR.Size;
  531. TextR.Data:=nil; TextR.Size:=0;
  532. end;
  533. DisposeRecord(TextR); DisposeRecord(KeyWR);
  534. end;
  535. ReadTopic:=OK;
  536. end;
  537. destructor TOAHelpFile.Done;
  538. begin
  539. if F<>nil then Dispose(F, Done);
  540. inherited Done;
  541. end;
  542. function CreateProc(const FileName,Param: string;Index : longint): PHelpFile; {$ifndef FPC}far;{$endif}
  543. begin
  544. CreateProc:=New(POAHelpFile, Init(FileName,Index));
  545. end;
  546. procedure RegisterHelpType;
  547. begin
  548. RegisterHelpFileType({$ifdef FPC}@{$endif}CreateProc);
  549. end;
  550. END.