source: branches/2.20_branch/NewView/HelpFile.pas@ 447

Last change on this file since 447 was 346, checked in by RBRi, 16 years ago

fix stupid type cast error

  • Property svn:eol-style set to native
File size: 29.9 KB
Line 
1Unit HelpFile;
2
3// NewView - a new OS/2 Help Viewer
4// Copyright 2003 Aaron Lawrence (aaronl at consultant dot com)
5// This software is released under the Gnu Public License - see readme.txt
6
7Interface
8
9// Encapsulates the basic reading of a help file's structure.
10
11uses
12 Classes,
13 BseDos,
14 Os2Def,
15 SysUtils,
16 Graphics,
17 IPFFileFormatUnit,
18 HelpTopic,
19 HelpBitmap,
20 ACLUtility,
21 SearchTable;
22
23type
24
25 TIndexEntry = class
26 private
27 name: String;
28 topic: TTopic;
29 flags: uint8;
30 public
31 CONSTRUCTOR Create(aName: String; aTopic: TTopic; aFlags: uint8);
32 DESTRUCTOR Destroy; override;
33
34 PROPERTY getTopic: TTopic read topic;
35 FUNCTION getLabel: String;
36 FUNCTION isGlobal: boolean;
37 FUNCTION getLevel: integer;
38 end;
39
40
41 TIndex = class
42 private
43 entries: TStringList;
44 // labels: TStringList;
45 public
46 CONSTRUCTOR Create;
47 DESTRUCTOR Destroy; override;
48
49 FUNCTION Count: longint;
50 FUNCTION GetLabels: TStringList;
51 FUNCTION GetTopic(aPos: longint): TTopic;
52 PROCEDURE Add(anIndexEntry: TIndexEntry);
53 end;
54
55
56 THelpFile = class
57 protected
58 _Filename : string;
59 _FileSize : longint;
60 _Handle: HFILE;
61
62 _pSlotData: pUInt16;
63 _SlotDataSize: longint;
64
65 _Title: string;
66
67 _Topics: TList; // of TTopics
68
69 _Dictionary: TList; // pointers to strings.
70
71 _Index: TIndex;
72
73 _SearchTable: TSearchTable;
74
75 _ReferencedFiles: TStringList;
76
77 _FontTable: TList;
78
79 _pHeader: TPHelpFileHeader;
80 _pExtendedHeader: TPExtendedHelpFileHeader;
81 _pContentsData: pointer;
82 _pResourceData: pointer;
83 _pSearchData: pointer;
84 _pHighlightWords: UInt32ArrayPointer;
85 _pSlotOffsets: Uint32ArrayPointer;
86 _pDictionaryData: pointer;
87 _pFontTableData: pointer;
88 _pTopicNameData: pointer;
89 _pTopicGlobalNamesData: pointer;
90
91 procedure InitMembers;
92 procedure Open;
93 procedure Close;
94 procedure ReadFileBlock( Var Dest: pointer;
95 const StartPosition: ULONG;
96 const Length: ULONG );
97
98 procedure ReadHeader;
99 procedure ReadContents;
100 procedure ReadDictionary;
101 procedure ReadSearchTable;
102
103 procedure ReadIndex;
104
105 procedure ReadReferencedFilesTable;
106 procedure ReadFontTableData;
107 procedure ParseFontTable;
108
109 function GetTopic( Index: longint ): TTopic;
110 function GetTopicCount: longint;
111
112 function GetDictionaryCount: longint;
113 function GetDictionaryWord( Index: longint ): string;
114 function GetDictionaryWordPtr( Index: longint ): pstring;
115
116 function GetHighlightWords: UInt32ArrayPointer;
117
118 function GetSearchTable: TSearchTable;
119
120 // Lookup global or local panel name list
121 function FindTopicByName( const Name: string;
122 Var pData: pointer;
123 Count: longint;
124 Offset: longint ): TTopic;
125
126 public
127 constructor Create( const aFileName: string );
128
129 destructor Destroy; override;
130
131 function GetIndex: TIndex;
132
133 property Title: string read _Title;
134 property Topics[ Index: longint ]: TTopic read GetTopic;
135 property TopicList: TList read _Topics;
136 property TopicCount: longint read GetTopicCount;
137 property Index: TIndex read GetIndex;
138 property Filename: string read _FileName;
139
140 property ReferencedFiles: TStringList read _ReferencedFiles;
141
142 procedure GetImages( ImageOffsets: TList;
143 Images: TImageList );
144
145 function GetImage( ImageOffset: longint ): THelpBitmap;
146
147 property DictionaryCount: longint read GetDictionaryCount;
148 property DictionaryWords[ Index: longint ]: string read GetDictionaryWord;
149 property DictionaryWordPtrs[ Index: longint ]: pstring read GetDictionaryWordPtr;
150
151 function IndexOfTopic( Topic: TTopic ): longint;
152
153 property SearchTable: TSearchTable read GetSearchTable;
154
155 function FindTopicByResourceID( ID: uint16 ): TTopic;
156
157 function FindTopicByLocalName( const Name: string ): TTopic;
158 function FindTopicByGlobalName( const Name: string ): TTopic;
159
160 function FindTopicByTitleStartsWith( const SearchText: string ): TTopic;
161 function FindTopicByTitleContains( const SearchText: string ): TTopic;
162
163 function FindTopicByIndexStartsWith( const SearchText: string ): TTopic;
164 function FindTopicByIndexContains( const SearchText: string ): TTopic;
165
166 procedure FindResourceIDsForTopic( Topic: TTopic;
167 ResourceIDs: TList );
168
169 property HighlightWords: UInt32ArrayPointer read GetHighlightWords;
170
171 property FileSize: longint read _FileSize;
172
173 procedure SetupFontSubstitutes( Substitutions: string );
174 public
175 NotesLoaded: boolean; // used externally
176
177 end;
178
179// Returns helpfile that the given topic is within
180Function TopicFile( Topic: TTopic ): THelpFile;
181
182function GetHelpFileTitle( const Filename: string ): string;
183
184Implementation
185
186uses
187 BseErr,
188 StringUtilsUnit,
189 CharUtilsUnit,
190 DebugUnit,
191 ACLFileIOUtility,
192 ACLLanguageUnit;
193
194// Load "missing" bitmap
195{$R Images}
196
197var
198 FileErrorNotFound: string;
199 FileErrorAccessDenied: string;
200 FileErrorInUse: string;
201 FileErrorInvalidHeader: string;
202
203
204 // -----------
205 // TIndexEntry
206 // -----------
207
208 CONSTRUCTOR TIndexEntry.Create(aName: String; aTopic: TTopic; aFlags: uint8);
209 begin
210 LogEvent(LogObjConstDest, 'TIndexEntry.Create');
211 name := aName;
212 topic := aTopic;
213 flags := aFlags;
214 end;
215
216
217 DESTRUCTOR TIndexEntry.Destroy;
218 begin
219 LogEvent(LogObjConstDest, 'TIndexEntry.Destroy');
220 topic := nil;
221 // inherited Destroy;
222 end;
223
224
225 FUNCTION TIndexEntry.getLabel: String;
226 begin
227 result := name;
228
229 // index level check (level 1 or 2)
230 if (getLevel) > 1 then
231 begin
232 result := '- ' + result;
233 end;
234
235 if isGlobal then
236 begin
237 result := result + ' (g)';
238 end;
239 end;
240
241
242 FUNCTION TIndexEntry.isGlobal: boolean;
243 begin
244 result := (flags and 64) > 0
245 end;
246
247
248 FUNCTION TIndexEntry.getLevel: integer;
249 begin
250 result := 1;
251
252 // index level check (level 1 or 2)
253 if (flags and 2 ) > 0 then
254 begin
255 result := 2;
256 end;
257 end;
258
259
260
261
262 // -----------
263 // TIndex
264 // -----------
265 CONSTRUCTOR TIndex.Create;
266 begin
267 inherited Create;
268
269 entries := TStringList.Create;
270 // labels := nil; // lazy
271 end;
272
273
274 DESTRUCTOR TIndex.Destroy;
275 var
276 i : longint;
277 tmpEntry : TIndexEntry;
278 begin
279 LogEvent(LogObjConstDest, 'TIndex.Destroy (size:' + IntToStr(entries.Count) + ')');
280
281 for i := 0 to entries.Count - 1 do
282 begin
283 tmpEntry := TIndexEntry(entries.Objects[i]);
284 if tmpEntry <> nil then
285 begin
286 tmpEntry.Destroy;
287 entries.Objects[i] := nil;
288 end;
289 end;
290 entries.Destroy;
291
292 inherited Destroy;
293 end;
294
295
296 FUNCTION TIndex.Count: longint;
297 begin
298 result := entries.Count;
299 end;
300
301
302 FUNCTION TIndex.GetLabels: TStringList;
303 begin
304 result := entries;
305 end;
306
307
308 FUNCTION TIndex.GetTopic(aPos: longint): TTopic;
309 begin
310 result := TIndexEntry(entries.Objects[aPos]).getTopic;
311 end;
312
313
314 PROCEDURE TIndex.add(anIndexEntry: TIndexEntry);
315 begin
316 // LogEvent(LogDebug, 'TIndex.add(' + aName + ', ' + anIndexEntry.ClassName);
317 entries.AddObject(anIndexEntry.getLabel, anIndexEntry);
318 end;
319
320
321
322
323Procedure OnLanguageEvent( Language: TLanguageFile;
324 const Apply: boolean );
325var
326 tmpPrefix : String;
327begin
328 tmpPrefix := 'HelpFile' + LANGUAGE_LABEL_DELIMITER;
329
330 Language.LL( Apply, FileErrorNotFound, tmpPrefix + 'FileErrorNotFound', 'File not found' );
331 Language.LL( Apply, FileErrorAccessDenied, tmpPrefix + 'FileErrorAccessDenied', 'Access denied' );
332 Language.LL( Apply, FileErrorInUse, tmpPrefix + 'FileErrorInUse', 'File in use by another program' );
333 Language.LL( Apply,
334 FileErrorInvalidHeader,
335 tmpPrefix + 'FileErrorInvalidHeader',
336 'File doesn''t appear to be an OS/2 Help document (header ID not correct)' );
337 Language.LL( Apply,
338 ErrorCorruptHelpFile,
339 tmpPrefix + 'ErrorCorruptHelpFile',
340 'File is corrupt' );
341end;
342
343Function TopicFile( Topic: TTopic ): THelpFile;
344Begin
345 Result := Topic.HelpFile as THelpFile;
346end;
347
348procedure THelpFile.InitMembers;
349begin
350 _SlotDataSize := 0;
351
352 _pHeader := nil;
353 _pExtendedHeader := nil;
354 _pContentsData := nil;
355 _pSlotOffsets := nil;
356 _pResourceData := nil;
357 _pSearchData := nil;
358 _pDictionaryData := nil;
359// _pIndexData := nil;
360 _pFontTableData := nil;
361
362 _pHighlightWords := nil;
363
364 _Dictionary := TList.Create;
365 _Topics := TList.Create;
366// _Index := TStringList.Create;
367 _ReferencedFiles := TStringList.Create;
368 _FontTable := TList.Create;
369
370 NotesLoaded := false;
371end;
372
373
374constructor THelpFile.Create(const aFileName: string);
375begin
376 LogEvent(LogObjConstDest, 'THelpFile.Create (file:' + aFileName + ')');
377 LogEvent(LogParse, 'Helpfile Load: ' + aFileName);
378
379 _FileName := aFileName;
380
381 InitMembers;
382
383 Open;
384
385 // we always need these basics:
386 try
387 ReadHeader;
388 ReadContents;
389 ReadDictionary;
390 ReadFontTableData;
391 ParseFontTable;
392 ReadReferencedFilesTable;
393 except
394 Close;
395 raise;
396 end;
397
398 // the rest is loaded on demand
399end;
400
401
402destructor THelpFile.Destroy;
403begin
404 LogEvent(LogObjConstDest, 'THelpFile.Destroy');
405 DeallocateMemory( _pHeader );
406 DeallocateMemory( _pExtendedHeader );
407 DeallocateMemory( _pContentsData );
408 DeallocateMemory( _pSlotOffsets );
409 DeallocateMemory( _pResourceData );
410 DeallocateMemory( _pSearchData );
411 DeallocateMemory( _pDictionaryData );
412// DeallocateMemory( _pIndexData );
413 DeallocateMemory( _pFontTableData );
414
415 DeallocateMemory( _pHighlightWords );
416
417 // index entries are pointing to topics
418 // so let us clean them first
419 if Assigned( _Index ) then
420 begin
421 _Index.Destroy;
422 end;
423
424 if Assigned( _Topics ) then
425 begin
426 DestroyListAndObjects( _Topics );
427 end;
428
429 _Dictionary.Free;
430 _SearchTable.Free;
431 _ReferencedFiles.Free;
432 _FontTable.Free;
433
434 DosClose( _Handle );
435end;
436
437procedure THelpFile.Open;
438var
439 OpenAction: ULong;
440 rc: APIRET;
441 szName: Cstring;
442 FileInfo: FILESTATUS3;
443begin
444 if not FileExists( _Filename ) then
445 raise EHelpFileException.Create( FileErrorNotFound );
446
447 szName := _FileName;
448 rc := DosOpen( szName,
449 _Handle,
450 OpenAction,
451 0, // file size - irrelevant, not creating,
452 0, // attributes - ''
453 OPEN_ACTION_OPEN_IF_EXISTS,
454 OPEN_SHARE_DENYNONE + OPEN_ACCESS_READONLY,
455 nil ); // no extended attributes
456 if rc<> 0 then
457 begin
458 case rc of
459 ERROR_FILE_NOT_FOUND: // crap, this doesn't actually occur!
460 raise EHelpFileException.Create( FileErrorNotFound );
461
462 ERROR_ACCESS_DENIED:
463 raise EHelpFileException.Create( FileErrorAccessDenied );
464
465 ERROR_SHARING_VIOLATION:
466 raise EHelpFileException.Create( FileErrorInUse );
467
468 else
469 raise EHelpFileException.Create( SysErrorMessage( rc ) );
470 end;
471 end;
472
473 DosQueryFileInfo( _Handle,
474 FIL_STANDARD,
475 FileInfo,
476 sizeof( FileInfo ) );
477 _FileSize := FileInfo.cbFile; // file size
478end;
479
480procedure THelpFile.Close;
481begin
482 if _Handle <> 0 then
483 DosClose( _Handle );
484 _Handle := 0;
485end;
486
487procedure THelpFile.ReadFileBlock( Var Dest: pointer;
488 const StartPosition: ULONG;
489 const Length: ULONG );
490begin
491 if not ACLFileIOUtility.ReadFileBlock( _Handle,
492 Dest,
493 StartPosition,
494 Length ) then
495 raise EHelpFileException.Create( ErrorCorruptHelpFile );
496end;
497
498// -------------------------------------------------------------------------
499
500procedure THelpFile.ReadHeader;
501begin
502 LogEvent(LogParse, 'Read header');
503
504 ReadFileBlock( _pHeader,
505 0,
506 sizeof( _pHeader^ ) );
507
508 if _pHeader^.ID <> INF_HEADER_ID then
509 begin
510 // not an OS/2 help file.
511 if _pHeader^.ID = $5f3f then
512 raise EWindowsHelpFormatException.Create( 'Win16' );
513
514 raise EHelpFileException.Create( FileErrorInvalidHeader );
515 end;
516
517 _Title := StrPas( _pHeader^.Title );
518
519 if _pHeader^.extstart > 0 then
520 begin
521 // read extended header
522 ReadFileBlock( _pExtendedHeader,
523 _pHeader^.extstart,
524 sizeof( _pExtendedHeader^ ) );
525 end;
526end;
527
528procedure THelpFile.ReadContents;
529var
530 Topic: TTopic;
531 EntryIndex: longint;
532 pEntry: pTTOCEntryStart;
533 pEnd: pTTOCEntryStart;
534begin
535 LogEvent(LogParse, 'Read contents');
536
537 if _pHeader^.ntoc = 0 then
538 exit; // explicit check required since ntoc is unsigned
539
540 // Presize the topics list to save reallocation time
541 _Topics.Capacity := _pHeader^.ntoc;
542
543 // read slots first so that Topics can refer to it.
544 ReadFileBlock( _pSlotOffsets,
545 _pHeader^.slotsstart,
546 _pHeader^.nslots * sizeof( uint32 ) );
547
548 ReadFileBlock( _pContentsData,
549 _pHeader^.tocstart,
550 _pHeader^.toclen );
551
552 pEntry := _pContentsData;
553 pEnd := _pContentsData + _pHeader^.toclen;
554
555 for EntryIndex := 0 to _pHeader^.ntoc - 1 do
556 begin
557 if pEntry >= pEnd then
558 // runs off end of data!
559 raise EHelpFileException.Create( ErrorCorruptHelpFile );
560
561 Topic := TTopic.Create( _Handle,
562 _pSlotOffsets,
563 _Dictionary,
564 pEntry,
565 _FontTable,
566 _ReferencedFiles );
567
568 Topic.HelpFile := Self;
569 Topic.Index := EntryIndex;
570
571 _Topics.Add( Topic );
572
573 inc( pEntry, pEntry ^. Length );
574 end;
575end;
576
577procedure THelpFile.ReadDictionary;
578var
579 i: longint;
580 Len: uint8;
581 p: pbyte;
582 pEnd: pbyte;
583begin
584 LogEvent(LogParse, 'Read dictionary');
585
586 if _pHeader^.ndict = 0 then
587 exit; // explicit check required since ndict is unsigned
588
589 ReadFileBlock( _pDictionaryData,
590 _pHeader^.dictstart,
591 _pHeader^.dictlen );
592
593 P := _pDictionaryData;
594 pEnd := _pDictionaryData + _pHeader^.dictlen;
595
596 // Presize the dictionary to save reallocation
597 _Dictionary.Capacity := _pHeader^.ndict;
598 for i := 0 to _pHeader^.ndict - 1 do
599 begin
600 // adjust length so we can use as a Pascal string
601 // (file uses length including length byte,
602 // Pascal string have length excluding length byte)
603 if p >= pEnd then
604 // ran off end of data
605 raise EHelpFileException.Create( ErrorCorruptHelpFile );
606
607 Len := p^ - 1;
608 p^ := Len;
609 _Dictionary.Add( P );
610 inc( P, Len + 1 );
611 end;
612end;
613
614
615function THelpFile.GetIndex: TIndex;
616begin
617 if _Index = nil then
618 begin
619 ReadIndex;
620 end;
621 Result := _Index;
622end;
623
624type
625 TIndexEntryHeader = record
626 TextLength: uint8;
627 Flags: uint8;
628 NumberOfRoots: uint8;
629 TOCIndex: uint16;
630 end;
631
632procedure THelpFile.ReadIndex;
633var
634 IndexIndex: longint; // I can't resist :-)
635 pEntryHeader: ^TIndexEntryHeader;
636 EntryText: string;
637 IndexTitleLen: longint;
638 p: pointer;
639 pEnd: pointer;
640 pIndexData: pointer;
641
642 tmpIndexEntry: TIndexEntry;
643begin
644 LogEvent(LogParse, 'Read index');
645
646 _Index := TIndex.Create;
647
648 if _pHeader^.nindex = 0 then
649 exit; // explicit check required since ndict is unsigned
650
651 pIndexData := nil;
652 ReadFileBlock( pIndexData,
653 _pHeader^.indexstart,
654 _pHeader^.indexlen );
655
656 P := pIndexData;
657 pEnd := pIndexData + _pHeader^.indexlen;
658
659 for IndexIndex := 0 to _pHeader^.nindex - 1 do
660 begin
661 if p >= pEnd then
662 // ran off end of data
663 raise EHelpFileException.Create( ErrorCorruptHelpFile );
664
665 pEntryHeader := p;
666 IndexTitleLen := pEntryHeader^.TextLength;
667 inc( p, sizeof( TIndexEntryHeader ) );
668
669 GetMemString( p, EntryText, IndexTitleLen );
670
671 if pEntryHeader^.TOCIndex < _Topics.Count then
672 begin
673 tmpIndexEntry := TIndexEntry.Create(EntryText, _Topics[pEntryHeader^.TOCIndex], pEntryHeader^.flags);
674 _Index.Add(tmpIndexEntry);
675 end
676 else
677// raise EHelpFileException.Create( 'Error reading help file index - out of range topic reference' );
678 ; // pass! something special
679
680 inc( p, IndexTitleLen
681 + pEntryHeader^.NumberOfRoots
682 * sizeof( uint32 ) ); // skip 'roots' for index search
683 end;
684
685 DeallocateMemory( pIndexData );
686end;
687
688function THelpFile.GetSearchTable: TSearchTable;
689begin
690 if _SearchTable = nil then
691 ReadSearchTable;
692 Result := _SearchTable;
693end;
694
695procedure THelpFile.ReadSearchTable;
696var
697 SearchTableOffset: longint;
698 SearchTableRecordLengthIs16Bit: boolean;
699begin
700 LogEvent(LogParse, 'Read search table');
701
702 if _pHeader^.SearchLen = 0 then
703 begin
704 LogEvent(LogParse, 'Read search table (len = 0');
705 exit;
706 end;
707
708 SearchTableOffset := _pHeader^.SearchStart and $7fffffff;
709 SearchTableRecordLengthIs16Bit := _pHeader^.SearchStart and $80000000 > 0;
710 ReadFileBlock( _pSearchData,
711 SearchTableOffset,
712 _pHeader^.SearchLen );
713
714 _SearchTable := TSearchTable.Create( _pSearchData,
715 SearchTableRecordLengthIs16Bit,
716 _Dictionary.Count,
717 _Topics.Count );
718end;
719
720function THelpFile.GetHighlightWords: UInt32ArrayPointer;
721begin
722 if _pHighlightWords = nil then
723 _pHighlightWords := AllocateMemory( _Dictionary.Count * sizeof( UInt32 ) );
724 Result := _pHighlightWords;
725end;
726
727function THelpFile.FindTopicByResourceID( ID: uint16 ): TTopic;
728var
729 i: longint;
730 pResourceIDs: UInt16ArrayPointer;
731 pTopicIndices: UInt16ArrayPointer;
732 FileResourceID: uint16;
733 TopicIndex: uint16;
734begin
735 Result := nil;
736
737 if _pHeader^.nres = 0 then
738 // since nres is unsigned
739 exit;
740
741 if _pResourceData = nil then
742 ReadFileBlock( _pResourceData,
743 _pHeader^.resstart,
744 _pHeader^.nres * sizeof( uint16 ) * 2 ); // list of IDs, list of topics
745
746 pResourceIDs := _pResourceData;
747 pTopicIndices := _pResourceData
748 + _pHeader^.nres * sizeof( uint16 );
749
750 for i := 0 to _pHeader^.nres - 1 do
751 begin
752 FileResourceID := pResourceIDs^[ i ];
753 if FileResourceID = ID then
754 begin
755 // found
756 TopicIndex := pTopicIndices^[ i ];
757 Result := _Topics[ TopicIndex ];
758 exit;
759 end;
760 end;
761end;
762
763// Look up a local "panel name" and return associated topic, if any.
764function THelpFile.FindTopicByLocalName( const Name: string ): TTopic;
765begin
766 Result := FindTopicByName( Name,
767 _pTopicNameData,
768 _pHeader^.nname,
769 _pHeader^.namestart );
770end;
771
772function THelpFile.FindTopicByGlobalName( const Name: string ): TTopic;
773begin
774 Result := nil;
775
776 if _pExtendedHeader = nil then
777 // no extended header - no global list to lookup
778 exit;
779
780 Result := FindTopicByName( Name,
781 _pTopicGlobalNamesData,
782 _pExtendedHeader ^. EntryInGNameTable,
783 _pExtendedHeader ^. HelpPanelGNameTblOffset );
784
785end;
786
787// The text of the names are stored in the (global) dictionary
788// with a table referencing them.
789// We could use a binary search here... but whatever...
790function THelpFile.FindTopicByName( const Name: string;
791 Var pData: pointer;
792 Count: longint;
793 Offset: longint ): TTopic;
794var
795 i: longint;
796 pNameTable: UInt16ArrayPointer;
797 pTopicIndices: UInt16ArrayPointer;
798 TopicIndex: uint16;
799
800 TopicNameWordIndex: uint16;
801 pTopicName: pstring;
802begin
803 Result := nil;
804
805 if Count = 0 then
806 // since it's unsigned
807 exit;
808
809 if pData = nil then
810 ReadFileBlock( pData,
811 Offset,
812 Count * sizeof( uint16 ) * 2 ); // list of name words, list of topics
813
814 // get pointers to the two parts of the table
815 pNameTable := pData;
816 pTopicIndices := pData
817 + Count * sizeof( uint16 );
818
819 for i := 0 to Count - 1 do
820 begin
821 TopicNameWordIndex := pNameTable[ i ];
822 pTopicName := DictionaryWordPtrs[ TopicNameWordIndex ];
823
824 if CompareText( pTopicName^, Name ) = 0 then
825 begin
826 // found
827 TopicIndex := pTopicIndices^[ i ];
828 Result := _Topics[ TopicIndex ];
829 exit;
830 end;
831 end;
832end;
833
834
835// TODO move to index class
836function THelpFile.FindTopicByIndexStartsWith( const SearchText: string ): TTopic;
837var
838 i: longint;
839 tmpLabel: String;
840begin
841 result := nil;
842 GetIndex; // make sure it's read
843
844 for i := 0 to _Index.Count - 1 do
845 begin
846 tmpLabel := _Index.GetLabels.ValuePtrs[i]^;
847 if StrStartsWithIgnoringCase(tmpLabel, SearchText) then
848 begin
849 // found
850 result := Index.getTopic(i);
851 exit;
852 end;
853 end;
854end;
855
856
857function THelpFile.FindTopicByIndexContains(const SearchText: string): TTopic;
858var
859 i: longint;
860 tmpLabel: String;
861begin
862 result := nil;
863 GetIndex; // make sure it's read
864
865 for i := 0 to _Index.Count - 1 do
866 begin
867 tmpLabel := _Index.GetLabels.ValuePtrs[i]^;
868 if CaseInsensitivePos(SearchText, tmpLabel) > 0 then
869 begin
870 // found
871 result := Index.getTopic(i);
872 exit;
873 end;
874 end;
875end;
876
877
878function THelpFile.FindTopicByTitleStartsWith( const SearchText: string ): TTopic;
879var
880 i: longint;
881 tmpTopic: TTopic;
882 tmpLevel : integer;
883 tmpMore : boolean;
884begin
885 result := nil;
886
887 tmpLevel := 0;
888 repeat
889 tmpMore := false;
890 inc(tmpLevel);
891 for i := 0 to _Topics.Count - 1 do
892 begin
893 tmpTopic := _Topics[i];
894 if tmpLevel = tmpTopic.ContentsLevel then
895 begin
896 if StrStartsWithIgnoringCase(tmpTopic.TitlePtr^, SearchText) then
897 begin
898 result := tmpTopic;
899 exit;
900 end;
901 end;
902 if tmpLevel < tmpTopic.ContentsLevel then
903 begin
904 tmpMore := True;
905 end;
906 end;
907 until NOT tmpMore;
908end;
909
910function THelpFile.FindTopicByTitleContains( const SearchText: string ): TTopic;
911var
912 i: longint;
913 tmpTopic: TTopic;
914 tmpLevel : integer;
915 tmpMore : boolean;
916begin
917 result := nil;
918
919 tmpLevel := 0;
920 repeat
921 tmpMore := false;
922 inc(tmpLevel);
923 for i := 0 to _Topics.Count - 1 do
924 begin
925 tmpTopic := _Topics[i];
926 if tmpLevel = tmpTopic.ContentsLevel then
927 begin
928 if CaseInsensitivePos( SearchText, tmpTopic.TitlePtr ^ ) > 0 then
929 begin
930 result := tmpTopic;
931 exit;
932 end;
933 end;
934 if tmpLevel < tmpTopic.ContentsLevel then
935 begin
936 tmpMore := True;
937 end;
938 end;
939 until NOT tmpMore;
940end;
941
942procedure THelpFile.FindResourceIDsForTopic( Topic: TTopic;
943 ResourceIDs: TList );
944var
945 i: longint;
946 pResourceIDs: UInt16ArrayPointer;
947 pTopicIndices: UInt16ArrayPointer;
948begin
949 ResourceIDs.Clear;
950
951 if _pHeader^.nres = 0 then
952 // since nres is unsigned
953 exit;
954
955 if _pResourceData = nil then
956 ReadFileBlock( _pResourceData,
957 _pHeader^.resstart,
958 _pHeader^.nres * sizeof( uint16 ) * 2 ); // list of IDs, list of topics
959
960 pResourceIDs := _pResourceData;
961 pTopicIndices := _pResourceData
962 + _pHeader^.nres * sizeof( uint16 );
963
964 for i := 0 to _pHeader^.nres - 1 do
965 begin
966 if pTopicIndices^[ i ] = Topic.Index then
967 begin
968 // found
969 ResourceIDs.Add( pointer( pResourceIDs^[ i ] ) );
970 end;
971 end;
972end;
973
974procedure THelpFile.ReadReferencedFilesTable;
975var
976 i: longint;
977 p: pointer;
978 pData: pointer;
979 DatabaseName: string;
980 pLength: pByte;
981begin
982 if _pExtendedHeader = nil then
983 // no extended header -> no referenced files table
984 exit;
985
986 if _pExtendedHeader ^.Numdatabase = 0 then
987 exit;
988
989 pData := nil; // please allocate...
990 ReadFileBlock( pData,
991 _pExtendedHeader^.DatabaseOffset,
992 _pExtendedHeader^.DatabaseSize );
993
994 p := pData;
995 for i := 0 to _pExtendedHeader^.Numdatabase - 1 do
996 begin
997 pLength := p; // length byte, including itself
998 GetMemString( p + 1, DatabaseName, ( pLength ^ ) - 1 );
999 _ReferencedFiles.Add( DatabaseName );
1000 inc( p, pLength ^ ); // skip to next entry
1001 end;
1002 DeallocateMemory( pData );
1003end;
1004
1005procedure THelpFile.ReadFontTableData;
1006begin
1007 if _pExtendedHeader = nil then
1008 // no extended header -> no font table
1009 exit;
1010
1011 if _pExtendedHeader^.NumFontEntry = 0 then
1012 exit;
1013
1014 ReadFileBlock( _pFontTableData,
1015 _pExtendedHeader^.FontTableOffset,
1016 _pExtendedHeader^.NumFontEntry * sizeof( THelpFontSpec ) );
1017end;
1018
1019procedure THelpFile.ParseFontTable;
1020var
1021 i: longint;
1022 p: pointer;
1023 pFontSpec: pTHelpFontSpec;
1024begin
1025 _FontTable.Clear;
1026
1027 p := _pFontTableData;
1028 if p = nil then
1029 exit; // no data
1030
1031 for i := 0 to _pExtendedHeader^.NumFontEntry - 1 do
1032 begin
1033 pFontSpec := p + i * sizeof( THelpFontSpec );
1034 _FontTable.Add( pFontSpec );
1035 end;
1036end;
1037
1038procedure THelpFile.GetImages( ImageOffsets: TList;
1039 Images: TImageList );
1040var
1041 ListIndex: longint;
1042 ImageOffset: longint;
1043 Bitmap: THelpBitmap;
1044begin
1045 Images.Clear;
1046
1047 for ListIndex := 0 to ImageOffsets.Count - 1 do
1048 begin
1049 ImageOffset := longint( ImageOffsets[ ListIndex ] );
1050 try
1051 Bitmap := THelpBitmap.CreateFromHelpFile( _Handle,
1052 _pHeader^.imgstart
1053 + ImageOffset );
1054 except
1055 on e: EHelpBitmapException do
1056{ raise EHelpFileException.Create( 'Error loading help bitmap at'
1057 + IntToStr( ImageOffset )
1058 + ': '
1059 + e.Message );}
1060 begin
1061 Bitmap := THelpBitmap.Create;
1062 Bitmap.LoadFromResourceName( 'MissingBitmap' );
1063 end;
1064 end;
1065
1066 Images.Add( Bitmap, nil );
1067 Bitmap.Destroy;
1068
1069 end;
1070end;
1071
1072function THelpFile.GetImage( ImageOffset: longint ): THelpBitmap;
1073begin
1074 try
1075 Result := THelpBitmap.CreateFromHelpFile( _Handle,
1076 _pHeader^.imgstart
1077 + ImageOffset );
1078 except
1079 on e: EHelpBitmapException do
1080{ raise EHelpFileException.Create( 'Error loading help bitmap at'
1081 + IntToStr( ImageOffset )
1082 + ': '
1083 + e.Message );}
1084 begin
1085 result := nil;
1086 end;
1087 end;
1088end;
1089
1090function THelpFile.GetTopic( Index: longint ): TTopic;
1091begin
1092 if ( Index < 0 )
1093 or ( Index > _Topics.Count - 1 ) then
1094 Result := nil
1095 else
1096 Result := _Topics[ Index ];
1097end;
1098
1099function THelpFile.GetTopicCount: longint;
1100begin
1101 Result := _Topics.Count;
1102end;
1103
1104function THelpFile.IndexOfTopic( Topic: TTopic ): longint;
1105begin
1106 Result := _Topics.IndexOf( Topic );
1107end;
1108
1109function THelpFile.GetDictionaryCount: longint;
1110begin
1111 Result := _Dictionary.Count;
1112end;
1113
1114function THelpFile.GetDictionaryWord( Index: longint ): string;
1115begin
1116 Result := pstring( _Dictionary[ Index ] )^;
1117end;
1118
1119function THelpFile.GetDictionaryWordPtr( Index: longint ): pstring;
1120begin
1121 Result := pstring( _Dictionary[ Index ] );
1122end;
1123
1124
1125// Looks for fonts that should be substitued to the
1126// users selected fixed font
1127// doesn't make a lot of sense for this to be here...
1128procedure THelpFile.SetupFontSubstitutes( Substitutions: string );
1129var
1130 Item: string;
1131 FontName: string;
1132 SpacePos: longint;
1133 W: longint;
1134 H: longint;
1135 i: longint;
1136 pFontSpec: pTHelpFontSpec;
1137 tmpSubstitutionItems : TStringList;
1138 tmpCounter : integer;
1139 tmpDimensionParts : TStringList;
1140begin
1141 ParseFontTable; // (re)load table from raw data
1142
1143 tmpSubstitutionItems := TStringList.Create;
1144 StrExtractStrings(tmpSubstitutionItems, Substitutions, [';'], #0);
1145
1146 for tmpCounter := 0 to tmpSubstitutionItems.Count - 1 do
1147 begin
1148 Item := tmpSubstitutionItems[tmpCounter];
1149 try
1150 if Item <> '' then
1151 begin
1152 // Look for space in xxxx WxH
1153
1154 SpacePos := LastPosOfChar(' ', Item);
1155 if SpacePos > 0 then
1156 begin
1157 // fontname comes before
1158 FontName := StrLeft( Item, SpacePos - 1 );
1159 Delete( Item, 1, SpacePos );
1160
1161 // width and height after, with an X between
1162 tmpDimensionParts := TStringList.Create;
1163 StrExtractStrings(tmpDimensionParts, Item, ['x'], #0);
1164 W := StrToInt(tmpDimensionParts[0]);
1165 H := StrToInt(tmpDimensionParts[1]);
1166 tmpDimensionParts.Destroy;
1167 if ( W > 0 ) and ( H > 0 ) then
1168 begin
1169 // Now look through the font table for matches
1170 for i := 0 to _FontTable.Count - 1 do
1171 begin
1172 pFontSpec := _FontTable[ i ];
1173 if StrPasWithLength( pFontSpec^.FaceName, sizeof( pFontSpec^.FaceName ) ) = FontName then
1174 begin
1175 // same face name...
1176 if ( W = pFontSpec ^. Height ) and ( H = pFontSpec ^. Width ) then
1177 begin
1178 // match
1179 _FontTable[ i ] := SubstituteFixedFont;
1180 end;
1181 end;
1182 end;
1183 end;
1184 end;
1185 end;
1186 except
1187 end;
1188 end;
1189
1190 tmpSubstitutionItems.Destroy;
1191end;
1192
1193
1194// -------------------------------------------------------------
1195// Get the title only from specific help file (if possible)
1196
1197function GetHelpFileTitle( const Filename: string ): string;
1198var
1199 OpenAction: ULong;
1200 rc: APIRET;
1201 szName: Cstring;
1202
1203 Header: THelpFileHeader;
1204 Handle: HFILE;
1205 Ext: string;
1206begin
1207 Ext := ExtractFileExt( Filename );
1208 Result := '';
1209
1210 if StrEqualIgnoringCase( Ext, '.inf' )
1211 or StrEqualIgnoringCase( Ext, '.hlp' ) then
1212 begin
1213 szName := Filename;
1214 rc := DosOpen( szName,
1215 Handle,
1216 OpenAction,
1217 0, // file size - irrelevant, not creating,
1218 0, // attributes - ''
1219 OPEN_ACTION_OPEN_IF_EXISTS,
1220 OPEN_SHARE_DENYNONE + OPEN_ACCESS_READONLY,
1221 nil ); // no extended attributes
1222 if rc = 0 then
1223 begin
1224 FillChar( Header, sizeof( Header ), 0 );
1225 if MyRead( Handle, Addr( Header ), sizeof( Header ) ) then
1226 if Header.ID = INF_HEADER_ID then
1227 Result := StrPas( Header.Title );
1228 DosClose( Handle );
1229 end;
1230 end;
1231end;
1232
1233Initialization
1234 RegisterProcForLanguages( OnLanguageEvent );
1235End.
1236
Note: See TracBrowser for help on using the repository browser.