Unit HelpFile; // NewView - a new OS/2 Help Viewer // Copyright 2003 Aaron Lawrence (aaronl at consultant dot com) // This software is released under the Gnu Public License - see readme.txt Interface // Encapsulates the basic reading of a help file's structure. uses Classes, BseDos, Os2Def, SysUtils, Graphics, IPFFileFormatUnit, HelpTopic, HelpBitmap, ACLUtility, SearchTable; type TIndexEntry = class private name: String; topic: TTopic; flags: uint8; public CONSTRUCTOR Create(aName: String; aTopic: TTopic; aFlags: uint8); DESTRUCTOR Destroy; override; PROPERTY getTopic: TTopic read topic; FUNCTION getLabel: String; FUNCTION isGlobal: boolean; FUNCTION getLevel: integer; end; TIndex = class private entries: TStringList; // labels: TStringList; public CONSTRUCTOR Create; DESTRUCTOR Destroy; override; FUNCTION Count: longint; FUNCTION GetLabels: TStringList; FUNCTION GetTopic(aPos: longint): TTopic; PROCEDURE Add(anIndexEntry: TIndexEntry); end; THelpFile = class protected _Filename : string; _FileSize : longint; _Handle: HFILE; _pSlotData: pUInt16; _SlotDataSize: longint; _Title: string; _Topics: TList; // of TTopics _Dictionary: TList; // pointers to strings. _Index: TIndex; _SearchTable: TSearchTable; _ReferencedFiles: TStringList; _FontTable: TList; _pHeader: TPHelpFileHeader; _pExtendedHeader: TPExtendedHelpFileHeader; _pContentsData: pointer; _pResourceData: pointer; _pSearchData: pointer; _pHighlightWords: UInt32ArrayPointer; _pSlotOffsets: Uint32ArrayPointer; _pDictionaryData: pointer; _pFontTableData: pointer; _pTopicNameData: pointer; _pTopicGlobalNamesData: pointer; procedure InitMembers; procedure Open; procedure Close; procedure ReadFileBlock( Var Dest: pointer; const StartPosition: ULONG; const Length: ULONG ); procedure ReadHeader; procedure ReadContents; procedure ReadDictionary; procedure ReadSearchTable; procedure ReadIndex; procedure ReadReferencedFilesTable; procedure ReadFontTableData; procedure ParseFontTable; function GetTopic( Index: longint ): TTopic; function GetTopicCount: longint; function GetDictionaryCount: longint; function GetDictionaryWord( Index: longint ): string; function GetDictionaryWordPtr( Index: longint ): pstring; function GetHighlightWords: UInt32ArrayPointer; function GetSearchTable: TSearchTable; // Lookup global or local panel name list function FindTopicByName( const Name: string; Var pData: pointer; Count: longint; Offset: longint ): TTopic; public constructor Create( const aFileName: string ); destructor Destroy; override; function GetIndex: TIndex; property Title: string read _Title; property Topics[ Index: longint ]: TTopic read GetTopic; property TopicList: TList read _Topics; property TopicCount: longint read GetTopicCount; property Index: TIndex read GetIndex; property Filename: string read _FileName; property ReferencedFiles: TStringList read _ReferencedFiles; procedure GetImages( ImageOffsets: TList; Images: TImageList ); function GetImage( ImageOffset: longint ): THelpBitmap; property DictionaryCount: longint read GetDictionaryCount; property DictionaryWords[ Index: longint ]: string read GetDictionaryWord; property DictionaryWordPtrs[ Index: longint ]: pstring read GetDictionaryWordPtr; function IndexOfTopic( Topic: TTopic ): longint; property SearchTable: TSearchTable read GetSearchTable; function FindTopicByResourceID( ID: uint16 ): TTopic; function FindTopicByLocalName( const Name: string ): TTopic; function FindTopicByGlobalName( const Name: string ): TTopic; function FindTopicByTitleStartsWith( const SearchText: string ): TTopic; function FindTopicByTitleContains( const SearchText: string ): TTopic; function FindTopicByIndexStartsWith( const SearchText: string ): TTopic; function FindTopicByIndexContains( const SearchText: string ): TTopic; procedure FindResourceIDsForTopic( Topic: TTopic; ResourceIDs: TList ); property HighlightWords: UInt32ArrayPointer read GetHighlightWords; property FileSize: longint read _FileSize; procedure SetupFontSubstitutes( Substitutions: string ); public NotesLoaded: boolean; // used externally end; // Returns helpfile that the given topic is within Function TopicFile( Topic: TTopic ): THelpFile; function GetHelpFileTitle( const Filename: string ): string; Implementation uses BseErr, StringUtilsUnit, CharUtilsUnit, DebugUnit, ACLFileIOUtility, ACLLanguageUnit; // Load "missing" bitmap {$R Images} var FileErrorNotFound: string; FileErrorAccessDenied: string; FileErrorInUse: string; FileErrorInvalidHeader: string; // ----------- // TIndexEntry // ----------- CONSTRUCTOR TIndexEntry.Create(aName: String; aTopic: TTopic; aFlags: uint8); begin LogEvent(LogObjConstDest, 'TIndexEntry.Create'); name := aName; topic := aTopic; flags := aFlags; end; DESTRUCTOR TIndexEntry.Destroy; begin LogEvent(LogObjConstDest, 'TIndexEntry.Destroy'); topic := nil; // inherited Destroy; end; FUNCTION TIndexEntry.getLabel: String; begin result := name; // index level check (level 1 or 2) if (getLevel) > 1 then begin result := '- ' + result; end; if isGlobal then begin result := result + ' (g)'; end; end; FUNCTION TIndexEntry.isGlobal: boolean; begin result := (flags and 64) > 0 end; FUNCTION TIndexEntry.getLevel: integer; begin result := 1; // index level check (level 1 or 2) if (flags and 2 ) > 0 then begin result := 2; end; end; // ----------- // TIndex // ----------- CONSTRUCTOR TIndex.Create; begin inherited Create; entries := TStringList.Create; // labels := nil; // lazy end; DESTRUCTOR TIndex.Destroy; var i : longint; tmpEntry : TIndexEntry; begin LogEvent(LogObjConstDest, 'TIndex.Destroy (size:' + IntToStr(entries.Count) + ')'); for i := 0 to entries.Count - 1 do begin tmpEntry := TIndexEntry(entries.Objects[i]); if tmpEntry <> nil then begin tmpEntry.Destroy; entries.Objects[i] := nil; end; end; entries.Destroy; inherited Destroy; end; FUNCTION TIndex.Count: longint; begin result := entries.Count; end; FUNCTION TIndex.GetLabels: TStringList; begin result := entries; end; FUNCTION TIndex.GetTopic(aPos: longint): TTopic; begin result := TIndexEntry(entries.Objects[aPos]).getTopic; end; PROCEDURE TIndex.add(anIndexEntry: TIndexEntry); begin // LogEvent(LogDebug, 'TIndex.add(' + aName + ', ' + anIndexEntry.ClassName); entries.AddObject(anIndexEntry.getLabel, anIndexEntry); end; Procedure OnLanguageEvent( Language: TLanguageFile; const Apply: boolean ); var tmpPrefix : String; begin tmpPrefix := 'HelpFile' + LANGUAGE_LABEL_DELIMITER; Language.LL( Apply, FileErrorNotFound, tmpPrefix + 'FileErrorNotFound', 'File not found' ); Language.LL( Apply, FileErrorAccessDenied, tmpPrefix + 'FileErrorAccessDenied', 'Access denied' ); Language.LL( Apply, FileErrorInUse, tmpPrefix + 'FileErrorInUse', 'File in use by another program' ); Language.LL( Apply, FileErrorInvalidHeader, tmpPrefix + 'FileErrorInvalidHeader', 'File doesn''t appear to be an OS/2 Help document (header ID not correct)' ); Language.LL( Apply, ErrorCorruptHelpFile, tmpPrefix + 'ErrorCorruptHelpFile', 'File is corrupt' ); end; Function TopicFile( Topic: TTopic ): THelpFile; Begin Result := Topic.HelpFile as THelpFile; end; procedure THelpFile.InitMembers; begin _SlotDataSize := 0; _pHeader := nil; _pExtendedHeader := nil; _pContentsData := nil; _pSlotOffsets := nil; _pResourceData := nil; _pSearchData := nil; _pDictionaryData := nil; // _pIndexData := nil; _pFontTableData := nil; _pHighlightWords := nil; _Dictionary := TList.Create; _Topics := TList.Create; // _Index := TStringList.Create; _ReferencedFiles := TStringList.Create; _FontTable := TList.Create; NotesLoaded := false; end; constructor THelpFile.Create(const aFileName: string); begin LogEvent(LogObjConstDest, 'THelpFile.Create (file:' + aFileName + ')'); LogEvent(LogParse, 'Helpfile Load: ' + aFileName); _FileName := aFileName; InitMembers; Open; // we always need these basics: try ReadHeader; ReadContents; ReadDictionary; ReadFontTableData; ParseFontTable; ReadReferencedFilesTable; except Close; raise; end; // the rest is loaded on demand end; destructor THelpFile.Destroy; begin LogEvent(LogObjConstDest, 'THelpFile.Destroy'); DeallocateMemory( _pHeader ); DeallocateMemory( _pExtendedHeader ); DeallocateMemory( _pContentsData ); DeallocateMemory( _pSlotOffsets ); DeallocateMemory( _pResourceData ); DeallocateMemory( _pSearchData ); DeallocateMemory( _pDictionaryData ); // DeallocateMemory( _pIndexData ); DeallocateMemory( _pFontTableData ); DeallocateMemory( _pHighlightWords ); // index entries are pointing to topics // so let us clean them first if Assigned( _Index ) then begin _Index.Destroy; end; if Assigned( _Topics ) then begin DestroyListAndObjects( _Topics ); end; _Dictionary.Free; _SearchTable.Free; _ReferencedFiles.Free; _FontTable.Free; DosClose( _Handle ); end; procedure THelpFile.Open; var OpenAction: ULong; rc: APIRET; szName: Cstring; FileInfo: FILESTATUS3; begin if not FileExists( _Filename ) then raise EHelpFileException.Create( FileErrorNotFound ); szName := _FileName; rc := DosOpen( szName, _Handle, OpenAction, 0, // file size - irrelevant, not creating, 0, // attributes - '' OPEN_ACTION_OPEN_IF_EXISTS, OPEN_SHARE_DENYNONE + OPEN_ACCESS_READONLY, nil ); // no extended attributes if rc<> 0 then begin case rc of ERROR_FILE_NOT_FOUND: // crap, this doesn't actually occur! raise EHelpFileException.Create( FileErrorNotFound ); ERROR_ACCESS_DENIED: raise EHelpFileException.Create( FileErrorAccessDenied ); ERROR_SHARING_VIOLATION: raise EHelpFileException.Create( FileErrorInUse ); else raise EHelpFileException.Create( SysErrorMessage( rc ) ); end; end; DosQueryFileInfo( _Handle, FIL_STANDARD, FileInfo, sizeof( FileInfo ) ); _FileSize := FileInfo.cbFile; // file size end; procedure THelpFile.Close; begin if _Handle <> 0 then DosClose( _Handle ); _Handle := 0; end; procedure THelpFile.ReadFileBlock( Var Dest: pointer; const StartPosition: ULONG; const Length: ULONG ); begin if not ACLFileIOUtility.ReadFileBlock( _Handle, Dest, StartPosition, Length ) then raise EHelpFileException.Create( ErrorCorruptHelpFile ); end; // ------------------------------------------------------------------------- procedure THelpFile.ReadHeader; begin LogEvent(LogParse, 'Read header'); ReadFileBlock( _pHeader, 0, sizeof( _pHeader^ ) ); if _pHeader^.ID <> INF_HEADER_ID then begin // not an OS/2 help file. if _pHeader^.ID = $5f3f then raise EWindowsHelpFormatException.Create( 'Win16' ); raise EHelpFileException.Create( FileErrorInvalidHeader ); end; _Title := StrPas( _pHeader^.Title ); if _pHeader^.extstart > 0 then begin // read extended header ReadFileBlock( _pExtendedHeader, _pHeader^.extstart, sizeof( _pExtendedHeader^ ) ); end; end; procedure THelpFile.ReadContents; var Topic: TTopic; EntryIndex: longint; pEntry: pTTOCEntryStart; pEnd: pTTOCEntryStart; begin LogEvent(LogParse, 'Read contents'); if _pHeader^.ntoc = 0 then exit; // explicit check required since ntoc is unsigned // Presize the topics list to save reallocation time _Topics.Capacity := _pHeader^.ntoc; // read slots first so that Topics can refer to it. ReadFileBlock( _pSlotOffsets, _pHeader^.slotsstart, _pHeader^.nslots * sizeof( uint32 ) ); ReadFileBlock( _pContentsData, _pHeader^.tocstart, _pHeader^.toclen ); pEntry := _pContentsData; pEnd := _pContentsData + _pHeader^.toclen; for EntryIndex := 0 to _pHeader^.ntoc - 1 do begin if pEntry >= pEnd then // runs off end of data! raise EHelpFileException.Create( ErrorCorruptHelpFile ); Topic := TTopic.Create( _Handle, _pSlotOffsets, _Dictionary, pEntry, _FontTable, _ReferencedFiles ); Topic.HelpFile := Self; Topic.Index := EntryIndex; _Topics.Add( Topic ); inc( pEntry, pEntry ^. Length ); end; end; procedure THelpFile.ReadDictionary; var i: longint; Len: uint8; p: pbyte; pEnd: pbyte; begin LogEvent(LogParse, 'Read dictionary'); if _pHeader^.ndict = 0 then exit; // explicit check required since ndict is unsigned ReadFileBlock( _pDictionaryData, _pHeader^.dictstart, _pHeader^.dictlen ); P := _pDictionaryData; pEnd := _pDictionaryData + _pHeader^.dictlen; // Presize the dictionary to save reallocation _Dictionary.Capacity := _pHeader^.ndict; for i := 0 to _pHeader^.ndict - 1 do begin // adjust length so we can use as a Pascal string // (file uses length including length byte, // Pascal string have length excluding length byte) if p >= pEnd then // ran off end of data raise EHelpFileException.Create( ErrorCorruptHelpFile ); Len := p^ - 1; p^ := Len; _Dictionary.Add( P ); inc( P, Len + 1 ); end; end; function THelpFile.GetIndex: TIndex; begin if _Index = nil then begin ReadIndex; end; Result := _Index; end; type TIndexEntryHeader = record TextLength: uint8; Flags: uint8; NumberOfRoots: uint8; TOCIndex: uint16; end; procedure THelpFile.ReadIndex; var IndexIndex: longint; // I can't resist :-) pEntryHeader: ^TIndexEntryHeader; EntryText: string; IndexTitleLen: longint; p: pointer; pEnd: pointer; pIndexData: pointer; tmpIndexEntry: TIndexEntry; begin LogEvent(LogParse, 'Read index'); _Index := TIndex.Create; if _pHeader^.nindex = 0 then exit; // explicit check required since ndict is unsigned pIndexData := nil; ReadFileBlock( pIndexData, _pHeader^.indexstart, _pHeader^.indexlen ); P := pIndexData; pEnd := pIndexData + _pHeader^.indexlen; for IndexIndex := 0 to _pHeader^.nindex - 1 do begin if p >= pEnd then // ran off end of data raise EHelpFileException.Create( ErrorCorruptHelpFile ); pEntryHeader := p; IndexTitleLen := pEntryHeader^.TextLength; inc( p, sizeof( TIndexEntryHeader ) ); GetMemString( p, EntryText, IndexTitleLen ); if pEntryHeader^.TOCIndex < _Topics.Count then begin tmpIndexEntry := TIndexEntry.Create(EntryText, _Topics[pEntryHeader^.TOCIndex], pEntryHeader^.flags); _Index.Add(tmpIndexEntry); end else // raise EHelpFileException.Create( 'Error reading help file index - out of range topic reference' ); ; // pass! something special inc( p, IndexTitleLen + pEntryHeader^.NumberOfRoots * sizeof( uint32 ) ); // skip 'roots' for index search end; DeallocateMemory( pIndexData ); end; function THelpFile.GetSearchTable: TSearchTable; begin if _SearchTable = nil then ReadSearchTable; Result := _SearchTable; end; procedure THelpFile.ReadSearchTable; var SearchTableOffset: longint; SearchTableRecordLengthIs16Bit: boolean; begin LogEvent(LogParse, 'Read search table'); if _pHeader^.SearchLen = 0 then begin LogEvent(LogParse, 'Read search table (len = 0'); exit; end; SearchTableOffset := _pHeader^.SearchStart and $7fffffff; SearchTableRecordLengthIs16Bit := _pHeader^.SearchStart and $80000000 > 0; ReadFileBlock( _pSearchData, SearchTableOffset, _pHeader^.SearchLen ); _SearchTable := TSearchTable.Create( _pSearchData, SearchTableRecordLengthIs16Bit, _Dictionary.Count, _Topics.Count ); end; function THelpFile.GetHighlightWords: UInt32ArrayPointer; begin if _pHighlightWords = nil then _pHighlightWords := AllocateMemory( _Dictionary.Count * sizeof( UInt32 ) ); Result := _pHighlightWords; end; function THelpFile.FindTopicByResourceID( ID: uint16 ): TTopic; var i: longint; pResourceIDs: UInt16ArrayPointer; pTopicIndices: UInt16ArrayPointer; FileResourceID: uint16; TopicIndex: uint16; begin Result := nil; if _pHeader^.nres = 0 then // since nres is unsigned exit; if _pResourceData = nil then ReadFileBlock( _pResourceData, _pHeader^.resstart, _pHeader^.nres * sizeof( uint16 ) * 2 ); // list of IDs, list of topics pResourceIDs := _pResourceData; pTopicIndices := _pResourceData + _pHeader^.nres * sizeof( uint16 ); for i := 0 to _pHeader^.nres - 1 do begin FileResourceID := pResourceIDs^[ i ]; if FileResourceID = ID then begin // found TopicIndex := pTopicIndices^[ i ]; Result := _Topics[ TopicIndex ]; exit; end; end; end; // Look up a local "panel name" and return associated topic, if any. function THelpFile.FindTopicByLocalName( const Name: string ): TTopic; begin Result := FindTopicByName( Name, _pTopicNameData, _pHeader^.nname, _pHeader^.namestart ); end; function THelpFile.FindTopicByGlobalName( const Name: string ): TTopic; begin Result := nil; if _pExtendedHeader = nil then // no extended header - no global list to lookup exit; Result := FindTopicByName( Name, _pTopicGlobalNamesData, _pExtendedHeader ^. EntryInGNameTable, _pExtendedHeader ^. HelpPanelGNameTblOffset ); end; // The text of the names are stored in the (global) dictionary // with a table referencing them. // We could use a binary search here... but whatever... function THelpFile.FindTopicByName( const Name: string; Var pData: pointer; Count: longint; Offset: longint ): TTopic; var i: longint; pNameTable: UInt16ArrayPointer; pTopicIndices: UInt16ArrayPointer; TopicIndex: uint16; TopicNameWordIndex: uint16; pTopicName: pstring; begin Result := nil; if Count = 0 then // since it's unsigned exit; if pData = nil then ReadFileBlock( pData, Offset, Count * sizeof( uint16 ) * 2 ); // list of name words, list of topics // get pointers to the two parts of the table pNameTable := pData; pTopicIndices := pData + Count * sizeof( uint16 ); for i := 0 to Count - 1 do begin TopicNameWordIndex := pNameTable[ i ]; pTopicName := DictionaryWordPtrs[ TopicNameWordIndex ]; if CompareText( pTopicName^, Name ) = 0 then begin // found TopicIndex := pTopicIndices^[ i ]; Result := _Topics[ TopicIndex ]; exit; end; end; end; // TODO move to index class function THelpFile.FindTopicByIndexStartsWith( const SearchText: string ): TTopic; var i: longint; tmpLabel: String; begin result := nil; GetIndex; // make sure it's read for i := 0 to _Index.Count - 1 do begin tmpLabel := _Index.GetLabels.ValuePtrs[i]^; if StrStartsWithIgnoringCase(tmpLabel, SearchText) then begin // found result := Index.getTopic(i); exit; end; end; end; function THelpFile.FindTopicByIndexContains(const SearchText: string): TTopic; var i: longint; tmpLabel: String; begin result := nil; GetIndex; // make sure it's read for i := 0 to _Index.Count - 1 do begin tmpLabel := _Index.GetLabels.ValuePtrs[i]^; if CaseInsensitivePos(SearchText, tmpLabel) > 0 then begin // found result := Index.getTopic(i); exit; end; end; end; function THelpFile.FindTopicByTitleStartsWith( const SearchText: string ): TTopic; var i: longint; tmpTopic: TTopic; tmpLevel : integer; tmpMore : boolean; begin result := nil; tmpLevel := 0; repeat tmpMore := false; inc(tmpLevel); for i := 0 to _Topics.Count - 1 do begin tmpTopic := _Topics[i]; if tmpLevel = tmpTopic.ContentsLevel then begin if StrStartsWithIgnoringCase(tmpTopic.TitlePtr^, SearchText) then begin result := tmpTopic; exit; end; end; if tmpLevel < tmpTopic.ContentsLevel then begin tmpMore := True; end; end; until NOT tmpMore; end; function THelpFile.FindTopicByTitleContains( const SearchText: string ): TTopic; var i: longint; tmpTopic: TTopic; tmpLevel : integer; tmpMore : boolean; begin result := nil; tmpLevel := 0; repeat tmpMore := false; inc(tmpLevel); for i := 0 to _Topics.Count - 1 do begin tmpTopic := _Topics[i]; if tmpLevel = tmpTopic.ContentsLevel then begin if CaseInsensitivePos( SearchText, tmpTopic.TitlePtr ^ ) > 0 then begin result := tmpTopic; exit; end; end; if tmpLevel < tmpTopic.ContentsLevel then begin tmpMore := True; end; end; until NOT tmpMore; end; procedure THelpFile.FindResourceIDsForTopic( Topic: TTopic; ResourceIDs: TList ); var i: longint; pResourceIDs: UInt16ArrayPointer; pTopicIndices: UInt16ArrayPointer; begin ResourceIDs.Clear; if _pHeader^.nres = 0 then // since nres is unsigned exit; if _pResourceData = nil then ReadFileBlock( _pResourceData, _pHeader^.resstart, _pHeader^.nres * sizeof( uint16 ) * 2 ); // list of IDs, list of topics pResourceIDs := _pResourceData; pTopicIndices := _pResourceData + _pHeader^.nres * sizeof( uint16 ); for i := 0 to _pHeader^.nres - 1 do begin if pTopicIndices^[ i ] = Topic.Index then begin // found ResourceIDs.Add( pointer( pResourceIDs^[ i ] ) ); end; end; end; procedure THelpFile.ReadReferencedFilesTable; var i: longint; p: pointer; pData: pointer; DatabaseName: string; pLength: pByte; begin if _pExtendedHeader = nil then // no extended header -> no referenced files table exit; if _pExtendedHeader ^.Numdatabase = 0 then exit; pData := nil; // please allocate... ReadFileBlock( pData, _pExtendedHeader^.DatabaseOffset, _pExtendedHeader^.DatabaseSize ); p := pData; for i := 0 to _pExtendedHeader^.Numdatabase - 1 do begin pLength := p; // length byte, including itself GetMemString( p + 1, DatabaseName, ( pLength ^ ) - 1 ); _ReferencedFiles.Add( DatabaseName ); inc( p, pLength ^ ); // skip to next entry end; DeallocateMemory( pData ); end; procedure THelpFile.ReadFontTableData; begin if _pExtendedHeader = nil then // no extended header -> no font table exit; if _pExtendedHeader^.NumFontEntry = 0 then exit; ReadFileBlock( _pFontTableData, _pExtendedHeader^.FontTableOffset, _pExtendedHeader^.NumFontEntry * sizeof( THelpFontSpec ) ); end; procedure THelpFile.ParseFontTable; var i: longint; p: pointer; pFontSpec: pTHelpFontSpec; begin _FontTable.Clear; p := _pFontTableData; if p = nil then exit; // no data for i := 0 to _pExtendedHeader^.NumFontEntry - 1 do begin pFontSpec := p + i * sizeof( THelpFontSpec ); _FontTable.Add( pFontSpec ); end; end; procedure THelpFile.GetImages( ImageOffsets: TList; Images: TImageList ); var ListIndex: longint; ImageOffset: longint; Bitmap: THelpBitmap; begin Images.Clear; for ListIndex := 0 to ImageOffsets.Count - 1 do begin ImageOffset := longint( ImageOffsets[ ListIndex ] ); try Bitmap := THelpBitmap.CreateFromHelpFile( _Handle, _pHeader^.imgstart + ImageOffset ); except on e: EHelpBitmapException do { raise EHelpFileException.Create( 'Error loading help bitmap at' + IntToStr( ImageOffset ) + ': ' + e.Message );} begin Bitmap := THelpBitmap.Create; Bitmap.LoadFromResourceName( 'MissingBitmap' ); end; end; Images.Add( Bitmap, nil ); Bitmap.Destroy; end; end; function THelpFile.GetImage( ImageOffset: longint ): THelpBitmap; begin try Result := THelpBitmap.CreateFromHelpFile( _Handle, _pHeader^.imgstart + ImageOffset ); except on e: EHelpBitmapException do { raise EHelpFileException.Create( 'Error loading help bitmap at' + IntToStr( ImageOffset ) + ': ' + e.Message );} begin result := nil; end; end; end; function THelpFile.GetTopic( Index: longint ): TTopic; begin if ( Index < 0 ) or ( Index > _Topics.Count - 1 ) then Result := nil else Result := _Topics[ Index ]; end; function THelpFile.GetTopicCount: longint; begin Result := _Topics.Count; end; function THelpFile.IndexOfTopic( Topic: TTopic ): longint; begin Result := _Topics.IndexOf( Topic ); end; function THelpFile.GetDictionaryCount: longint; begin Result := _Dictionary.Count; end; function THelpFile.GetDictionaryWord( Index: longint ): string; begin Result := pstring( _Dictionary[ Index ] )^; end; function THelpFile.GetDictionaryWordPtr( Index: longint ): pstring; begin Result := pstring( _Dictionary[ Index ] ); end; // Looks for fonts that should be substitued to the // users selected fixed font // doesn't make a lot of sense for this to be here... procedure THelpFile.SetupFontSubstitutes( Substitutions: string ); var Item: string; FontName: string; SpacePos: longint; W: longint; H: longint; i: longint; pFontSpec: pTHelpFontSpec; tmpSubstitutionItems : TStringList; tmpCounter : integer; tmpDimensionParts : TStringList; begin ParseFontTable; // (re)load table from raw data tmpSubstitutionItems := TStringList.Create; StrExtractStrings(tmpSubstitutionItems, Substitutions, [';'], #0); for tmpCounter := 0 to tmpSubstitutionItems.Count - 1 do begin Item := tmpSubstitutionItems[tmpCounter]; try if Item <> '' then begin // Look for space in xxxx WxH SpacePos := LastPosOfChar(' ', Item); if SpacePos > 0 then begin // fontname comes before FontName := StrLeft( Item, SpacePos - 1 ); Delete( Item, 1, SpacePos ); // width and height after, with an X between tmpDimensionParts := TStringList.Create; StrExtractStrings(tmpDimensionParts, Item, ['x'], #0); W := StrToInt(tmpDimensionParts[0]); H := StrToInt(tmpDimensionParts[1]); tmpDimensionParts.Destroy; if ( W > 0 ) and ( H > 0 ) then begin // Now look through the font table for matches for i := 0 to _FontTable.Count - 1 do begin pFontSpec := _FontTable[ i ]; if StrPasWithLength( pFontSpec^.FaceName, sizeof( pFontSpec^.FaceName ) ) = FontName then begin // same face name... if ( W = pFontSpec ^. Height ) and ( H = pFontSpec ^. Width ) then begin // match _FontTable[ i ] := SubstituteFixedFont; end; end; end; end; end; end; except end; end; tmpSubstitutionItems.Destroy; end; // ------------------------------------------------------------- // Get the title only from specific help file (if possible) function GetHelpFileTitle( const Filename: string ): string; var OpenAction: ULong; rc: APIRET; szName: Cstring; Header: THelpFileHeader; Handle: HFILE; Ext: string; begin Ext := ExtractFileExt( Filename ); Result := ''; if StrEqualIgnoringCase( Ext, '.inf' ) or StrEqualIgnoringCase( Ext, '.hlp' ) then begin szName := Filename; rc := DosOpen( szName, Handle, OpenAction, 0, // file size - irrelevant, not creating, 0, // attributes - '' OPEN_ACTION_OPEN_IF_EXISTS, OPEN_SHARE_DENYNONE + OPEN_ACCESS_READONLY, nil ); // no extended attributes if rc = 0 then begin FillChar( Header, sizeof( Header ), 0 ); if MyRead( Handle, Addr( Header ), sizeof( Header ) ) then if Header.ID = INF_HEADER_ID then Result := StrPas( Header.Title ); DosClose( Handle ); end; end; end; Initialization RegisterProcForLanguages( OnLanguageEvent ); End.