woahelp.pas 15 KB

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