Browse Source

+ html helpviewer

peter 26 years ago
parent
commit
cb9c161602
7 changed files with 1609 additions and 173 deletions
  1. 25 8
      ide/text/fphelp.pas
  2. 6 21
      ide/text/fpide.pas
  3. 24 26
      ide/text/fpmopts.inc
  4. 167 88
      ide/text/whelp.pas
  5. 117 30
      ide/text/whlpview.pas
  6. 685 0
      ide/text/whtml.pas
  7. 585 0
      ide/text/whtmlhlp.pas

+ 25 - 8
ide/text/fphelp.pas

@@ -18,7 +18,7 @@ unit FPHelp;
 interface
 interface
 
 
 uses
 uses
-  Drivers,HelpCtx,WHlpView,
+  Drivers,HelpCtx,WHlpView,WHTML,
 {$ifdef EDITORS}
 {$ifdef EDITORS}
   Editors,
   Editors,
 {$else}
 {$else}
@@ -56,12 +56,12 @@ const
 implementation
 implementation
 
 
 uses Objects,Views,App,MsgBox,
 uses Objects,Views,App,MsgBox,
-     WHelp,
+     WHelp,WHTMLHlp,
      FPConst,FPVars,FPUtils;
      FPConst,FPVars,FPUtils;
 
 
 const
 const
     MaxStatusLevel = 10;
     MaxStatusLevel = 10;
-    
+
 var StatusStack : array[0..MaxStatusLevel] of string[MaxViewWidth];
 var StatusStack : array[0..MaxStatusLevel] of string[MaxViewWidth];
 
 
 const
 const
@@ -142,7 +142,7 @@ begin
 
 
     hcDebugMenu     : S:='';
     hcDebugMenu     : S:='';
     hcToggleBreakpoint : S:='Toggles Breakpoint';
     hcToggleBreakpoint : S:='Toggles Breakpoint';
-    
+
     hcToolsMenu     : S:='User installed tools';
     hcToolsMenu     : S:='User installed tools';
     hcCalculator    : S:='Show calculator';
     hcCalculator    : S:='Show calculator';
     hcGrep          : S:='Run grep';
     hcGrep          : S:='Run grep';
@@ -199,18 +199,32 @@ begin
 end;
 end;
 
 
 procedure InitHelpSystem;
 procedure InitHelpSystem;
-procedure AddFile(HelpFile: string);
+procedure AddOAFile(HelpFile: string);
+begin
+  {$IFDEF DEBUG}SetStatus(strLoadingHelp+' ('+SmartPath(HelpFile)+')');{$ENDIF}
+  HelpFacility^.AddOAHelpFile(HelpFile);
+  {$IFDEF DEBUG}SetStatus(strLoadingHelp);{$ENDIF}
+end;
+procedure AddHTMLFile(TOCEntry,HelpFile: string);
 begin
 begin
   {$IFDEF DEBUG}SetStatus(strLoadingHelp+' ('+SmartPath(HelpFile)+')');{$ENDIF}
   {$IFDEF DEBUG}SetStatus(strLoadingHelp+' ('+SmartPath(HelpFile)+')');{$ENDIF}
-  HelpFacility^.AddHelpFile(HelpFile);
+  HelpFacility^.AddHTMLHelpFile(HelpFile, TOCEntry);
   {$IFDEF DEBUG}SetStatus(strLoadingHelp);{$ENDIF}
   {$IFDEF DEBUG}SetStatus(strLoadingHelp);{$ENDIF}
 end;
 end;
 var I: integer;
 var I: integer;
+    S: string;
 begin
 begin
   New(HelpFacility, Init);
   New(HelpFacility, Init);
   PushStatus(strLoadingHelp);
   PushStatus(strLoadingHelp);
+{  AddHTMLFile('User''s guide','C:\FP\USER\USER.HTM');}
   for I:=0 to HelpFiles^.Count-1 do
   for I:=0 to HelpFiles^.Count-1 do
-    AddFile(HelpFiles^.At(I)^);
+    begin
+      S:=HelpFiles^.At(I)^;
+      if copy(UpcaseStr(ExtOf(S)),1,4)='.HTM' then
+        AddHTMLFile(S,S)
+      else
+        AddOAFile(S);
+    end;
   PopStatus;
   PopStatus;
 end;
 end;
 
 
@@ -341,7 +355,10 @@ end;
 END.
 END.
 {
 {
   $Log$
   $Log$
-  Revision 1.5  1999-02-04 12:23:44  pierre
+  Revision 1.6  1999-02-08 10:37:43  peter
+    + html helpviewer
+
+  Revision 1.5  1999/02/04 12:23:44  pierre
     + cmResetDebugger and cmGrep
     + cmResetDebugger and cmGrep
     * Avoid StatusStack overflow
     * Avoid StatusStack overflow
 
 

+ 6 - 21
ide/text/fpide.pas

@@ -31,7 +31,7 @@ type
       function    OpenSearch(FileName: string) : boolean;
       function    OpenSearch(FileName: string) : boolean;
       procedure   HandleEvent(var Event: TEvent); virtual;
       procedure   HandleEvent(var Event: TEvent); virtual;
       function    GetPalette: PPalette; virtual;
       function    GetPalette: PPalette; virtual;
-      procedure   DosShell; {virtual;}
+      procedure   DosShell; virtual;
       destructor  Done; virtual;
       destructor  Done; virtual;
     public
     public
       procedure ShowUserScreen;
       procedure ShowUserScreen;
@@ -182,10 +182,10 @@ begin
       nil))))))))))),
       nil))))))))))),
     NewSubMenu('~R~un',hcRunMenu, NewMenu(
     NewSubMenu('~R~un',hcRunMenu, NewMenu(
       NewItem('~R~un','Ctrl+F9', kbCtrlF9, cmRun, hcRun,
       NewItem('~R~un','Ctrl+F9', kbCtrlF9, cmRun, hcRun,
-      NewItem('~S~tep Over','F8', kbF8, cmStepOver, hcRun,
-      NewItem('~T~race Into','F7', kbF7, cmTraceInto, hcRun,
+      NewItem('~S~tep over','F8', kbF8, cmStepOver, hcRun,
+      NewItem('~T~race into','F7', kbF7, cmTraceInto, hcRun,
       NewItem('P~a~rameters...','', kbNoKey, cmParameters, hcParameters,
       NewItem('P~a~rameters...','', kbNoKey, cmParameters, hcParameters,
-      NewItem('Reset ~P~rog','Ctrl+F2', kbCtrlF2, cmResetDebugger, hcResetDebugger,
+      NewItem('~P~rogram reset','Ctrl+F2', kbCtrlF2, cmResetDebugger, hcResetDebugger,
       nil)))))),
       nil)))))),
     NewSubMenu('~C~ompile',hcCompileMenu, NewMenu(
     NewSubMenu('~C~ompile',hcCompileMenu, NewMenu(
       NewItem('~C~ompile','Alt+F9', kbAltF9, cmCompile, hcCompile,
       NewItem('~C~ompile','Alt+F9', kbAltF9, cmCompile, hcCompile,
@@ -636,8 +636,6 @@ end;
 
 
 destructor TIDEApp.Done;
 destructor TIDEApp.Done;
 begin
 begin
-  { Close debugging session if active }
-  DoResetDebugger;
   inherited Done;
   inherited Done;
   DoneHelpSystem;
   DoneHelpSystem;
   DoneTemplates;
   DoneTemplates;
@@ -646,21 +644,8 @@ end;
 END.
 END.
 {
 {
   $Log$
   $Log$
-  Revision 1.10  1999-02-05 13:51:40  peter
-    * unit name of FPSwitches -> FPSwitch which is easier to use
-    * some fixes for tp7 compiling
-
-  Revision 1.9  1999/02/05 13:03:54  pierre
-   * DosShell is not virtual in app !
-
-  Revision 1.8  1999/02/05 12:11:54  pierre
-    + SourceDir that stores directories for sources that the
-      compiler should not know about
-      Automatically asked for addition when a new file that
-      needed filedialog to be found is in an unknown directory
-      Stored and retrieved from INIFile
-    + Breakpoints conditions added to INIFile
-    * Breakpoints insterted and removed at debin and end of debug session
+  Revision 1.11  1999-02-08 10:37:44  peter
+    + html helpviewer
 
 
   Revision 1.7  1999/02/04 13:32:03  pierre
   Revision 1.7  1999/02/04 13:32:03  pierre
     * Several things added (I cannot commit them independently !)
     * Several things added (I cannot commit them independently !)

+ 24 - 26
ide/text/fpmopts.inc

@@ -394,22 +394,8 @@ var R,R2: TRect;
     Count,I : integer;
     Count,I : integer;
 const
 const
   LW = 25;
   LW = 25;
-   procedure Add(i : integer;Data,Name : string);
-     begin
-       R2.Copy(R);
-       R2.A.X:=LW;
-       New(IL[i], Init(R2, 255));
-       IL[i]^.Data^:=Data;
-       D^.Insert(IL[i]);
-       R2.Copy(R);
-       R2.B.X:=LW;
-       D^.Insert(New(PLabel, Init(R2, Name, IL[i])));
-       R.Move(0,2);
-     end;
-
 begin
 begin
-  { add SourceDirs }
-  Count:=DirectorySwitches^.ItemCount+1;
+  Count:=DirectorySwitches^.ItemCount;
   R.Assign(0,0,64,2+Count*2);
   R.Assign(0,0,64,2+Count*2);
   New(D, Init(R, 'Directories'));
   New(D, Init(R, 'Directories'));
   with D^ do
   with D^ do
@@ -418,17 +404,25 @@ begin
     R.Grow(-2,-2);
     R.Grow(-2,-2);
     Dec(R.B.X);
     Dec(R.B.X);
     R.B.Y:=R.A.Y+1;
     R.B.Y:=R.A.Y+1;
-    Add(Count-1,SourceDirs,'Path to ~S~ources');
-    for i:=Count-2 downto 0 do
-      Add(i,DirectorySwitches^.GetStringItem(i),DirectorySwitches^.ItemName(i));
+    for i:=Count-1 downto 0 do
+     begin
+       R2.Copy(R);
+       R2.A.X:=LW;
+       New(IL[i], Init(R2, 255));
+       IL[i]^.Data^:=DirectorySwitches^.GetStringItem(i);
+       Insert(IL[i]);
+       R2.Copy(R);
+       R2.B.X:=LW;
+       Insert(New(PLabel, Init(R2, DirectorySwitches^.ItemName(i), IL[i])));
+       R.Move(0,2);
+     end;
   end;
   end;
   InsertButtons(D);
   InsertButtons(D);
   IL[Count-1]^.Select;
   IL[Count-1]^.Select;
   if Desktop^.ExecView(D)=cmOK then
   if Desktop^.ExecView(D)=cmOK then
    begin
    begin
-     for i:=Count-2 downto 0 do
+     for i:=Count-1 downto 0 do
       DirectorySwitches^.SetStringItem(i,IL[i]^.Data^);
       DirectorySwitches^.SetStringItem(i,IL[i]^.Data^);
-     SourceDirs:=IL[Count-1]^.Data^;
    end;
    end;
   Dispose(D, Done);
   Dispose(D, Done);
 end;
 end;
@@ -443,7 +437,7 @@ var D: PCenterDialog;
     R,R2,R3: TRect;
     R,R2,R3: TRect;
     CB: PCheckBoxes;
     CB: PCheckBoxes;
     IL: PIntegerLine;
     IL: PIntegerLine;
-    ExtIL: PInputLine;
+    ExtIL,TabExtIL: PInputLine;
     TabSize: Integer;
     TabSize: Integer;
     EFlags: Longint;
     EFlags: Longint;
 begin
 begin
@@ -494,11 +488,11 @@ begin
 
 
     R.Move(0,(R.B.Y-R.A.Y)+1); R.B.Y:=R.A.Y+2;
     R.Move(0,(R.B.Y-R.A.Y)+1); R.B.Y:=R.A.Y+2;
     R2.Copy(R); Inc(R2.A.Y);
     R2.Copy(R); Inc(R2.A.Y);
-    New(ExtIL, Init(R2, 128));
-    ExtIL^.SetData(TabsPattern);
-    Insert(ExtIL);
+    New(TabExtIL, Init(R2, 128));
+    TabExtIL^.SetData(TabsPattern);
+    Insert(TabExtIL);
     R2.Move(0,-1);
     R2.Move(0,-1);
-    Insert(New(PLabel, Init(R2, 'File ~p~atterns needing tabs', ExtIL)));
+    Insert(New(PLabel, Init(R2, 'File ~p~atterns needing tabs', TabExtIL)));
   end;
   end;
   InsertButtons(D);
   InsertButtons(D);
   CB^.Select;
   CB^.Select;
@@ -516,6 +510,7 @@ begin
          Editor^.SetFlags(EFlags);
          Editor^.SetFlags(EFlags);
        end;
        end;
     ExtIL^.GetData(HighlightExts);
     ExtIL^.GetData(HighlightExts);
+    TabExtIL^.GetData(TabsPattern);
   end;
   end;
   Dispose(D, Done);
   Dispose(D, Done);
 end;
 end;
@@ -705,7 +700,10 @@ end;
 
 
 {
 {
   $Log$
   $Log$
-  Revision 1.10  1999-02-06 00:07:47  florian
+  Revision 1.11  1999-02-08 10:37:45  peter
+    + html helpviewer
+
+  Revision 1.10  1999/02/06 00:07:47  florian
     * speed/size optimization is now a radio button
     * speed/size optimization is now a radio button
 
 
   Revision 1.9  1999/02/05 12:11:58  pierre
   Revision 1.9  1999/02/05 12:11:58  pierre

+ 167 - 88
ide/text/whelp.pas

@@ -38,10 +38,19 @@ const
       ctNone         = $00;
       ctNone         = $00;
       ctNibble       = $02;
       ctNibble       = $02;
 
 
+      hscLineBreak   = #0;
+      hscLink        = #2;
+      hscLineStart   = #3;
+      hscCode        = #5;
+      hscCenter      = #10;
+      hscRight       = #11;
+
 type
 type
       FileStamp      = array [0..32] of char; {+ null terminator + $1A }
       FileStamp      = array [0..32] of char; {+ null terminator + $1A }
       FileSignature  = array [0..12] of char; {+ null terminator }
       FileSignature  = array [0..12] of char; {+ null terminator }
 
 
+      THelpCtx = longint;
+
       THLPVersion = packed record
       THLPVersion = packed record
         FormatVersion : byte;
         FormatVersion : byte;
         TextVersion   : byte;
         TextVersion   : byte;
@@ -107,25 +116,25 @@ type
       PIndexEntry = ^TIndexEntry;
       PIndexEntry = ^TIndexEntry;
       TIndexEntry = packed record
       TIndexEntry = packed record
         Tag        : PString;
         Tag        : PString;
-        HelpCtx    : word;
+        HelpCtx    : THelpCtx;
         FileID     : word;
         FileID     : word;
       end;
       end;
 
 
       PKeywordDescriptor = ^TKeywordDescriptor;
       PKeywordDescriptor = ^TKeywordDescriptor;
       TKeywordDescriptor = packed record
       TKeywordDescriptor = packed record
         FileID     : word;
         FileID     : word;
-        Context    : word;
+        Context    : THelpCtx;
       end;
       end;
 
 
       PKeywordDescriptors = ^TKeywordDescriptors;
       PKeywordDescriptors = ^TKeywordDescriptors;
-      TKeywordDescriptors = array[0..16382] of TKeywordDescriptor;
+      TKeywordDescriptors = array[0..10900] of TKeywordDescriptor;
 
 
       PTopic = ^TTopic;
       PTopic = ^TTopic;
       TTopic = record
       TTopic = record
-        HelpCtx       : word;
+        HelpCtx       : THelpCtx;
         FileOfs       : longint;
         FileOfs       : longint;
         TextSize      : word;
         TextSize      : word;
-        Text          : pointer;
+        Text          : PByteArray;
         LinkCount     : word;
         LinkCount     : word;
         LinkSize      : word;
         LinkSize      : word;
         Links         : PKeywordDescriptors;
         Links         : PKeywordDescriptors;
@@ -143,7 +152,7 @@ type
       TTopicCollection = object(TCollection)
       TTopicCollection = object(TCollection)
         function   At(Index: Integer): PTopic;
         function   At(Index: Integer): PTopic;
         procedure  FreeItem(Item: Pointer); virtual;
         procedure  FreeItem(Item: Pointer); virtual;
-        function   SearchTopic(AHelpCtx: word): PTopic;
+        function   SearchTopic(AHelpCtx: THelpCtx): PTopic;
       end;
       end;
 
 
       PIndexEntryCollection = ^TIndexEntryCollection;
       PIndexEntryCollection = ^TIndexEntryCollection;
@@ -155,15 +164,30 @@ type
 
 
       PHelpFile = ^THelpFile;
       PHelpFile = ^THelpFile;
       THelpFile = object(TObject)
       THelpFile = object(TObject)
+        ID           : word;
+        Topics       : PTopicCollection;
+        IndexEntries : PIndexEntryCollection;
+        constructor Init(AID: word);
+        function    LoadTopic(HelpCtx: THelpCtx): PTopic; virtual;
+        destructor  Done; virtual;
+      public
+        function    LoadIndex: boolean; virtual;
+        function    SearchTopic(HelpCtx: THelpCtx): PTopic; virtual;
+        function    ReadTopic(T: PTopic): boolean; virtual;
+      private
+        procedure MaintainTopicCache;
+      end;
+
+      POAHelpFile = ^TOAHelpFile;
+      TOAHelpFile = object(THelpFile)
         Version      : THLPVersion;
         Version      : THLPVersion;
         Header       : THLPFileHeader;
         Header       : THLPFileHeader;
         Compression  : THLPCompression;
         Compression  : THLPCompression;
-        Topics       : PTopicCollection;
-        IndexEntries : PIndexEntryCollection;
-        ID           : word;
         constructor Init(AFileName: string; AID: word);
         constructor Init(AFileName: string; AID: word);
-        function    LoadTopic(HelpCtx: word): PTopic; virtual;
         destructor  Done; virtual;
         destructor  Done; virtual;
+      public
+        function    LoadIndex: boolean; virtual;
+        function    ReadTopic(T: PTopic): boolean; virtual;
       private
       private
         F: PBufStream;
         F: PBufStream;
         TopicsRead     : boolean;
         TopicsRead     : boolean;
@@ -178,8 +202,6 @@ type
         function  ReadCompression: boolean;
         function  ReadCompression: boolean;
         function  ReadIndexTags: boolean;
         function  ReadIndexTags: boolean;
         function  ReadRecord(var R: TRecord; ReadData: boolean): boolean;
         function  ReadRecord(var R: TRecord; ReadData: boolean): boolean;
-        function  ReadTopic(T: PTopic): boolean;
-        procedure MaintainTopicCache;
       end;
       end;
 
 
       PHelpFileCollection = PCollection;
       PHelpFileCollection = PCollection;
@@ -189,16 +211,18 @@ type
         HelpFiles: PHelpFileCollection;
         HelpFiles: PHelpFileCollection;
         IndexTabSize: integer;
         IndexTabSize: integer;
         constructor Init;
         constructor Init;
-        function    AddHelpFile(FileName: string): boolean; virtual;
-        function    LoadTopic(SourceFileID: word; Context: word): PTopic; virtual;
-        function    TopicSearch(Keyword: string; var FileID, Context: word): boolean; virtual;
+        function    AddOAHelpFile(FileName: string): boolean;
+        function    AddHTMLHelpFile(FileName, TOCEntry: string): boolean;
+        function    LoadTopic(SourceFileID: word; Context: THelpCtx): PTopic; virtual;
+        function    TopicSearch(Keyword: string; var FileID: word; Context: THelpCtx): boolean; virtual;
         function    BuildIndexTopic: PTopic; virtual;
         function    BuildIndexTopic: PTopic; virtual;
         destructor  Done; virtual;
         destructor  Done; virtual;
       private
       private
         LastID: word;
         LastID: word;
         function  SearchFile(ID: byte): PHelpFile;
         function  SearchFile(ID: byte): PHelpFile;
-        function  SearchTopicInHelpFile(F: PHelpFile; Context: word): PTopic;
-        function  SearchTopicOwner(SourceFileID: word; Context: word): PHelpFile;
+        function  SearchTopicInHelpFile(F: PHelpFile; Context: THelpCtx): PTopic;
+        function  SearchTopicOwner(SourceFileID: word; Context: THelpCtx): PHelpFile;
+        function  AddFile(H: PHelpFile): boolean;
       end;
       end;
 
 
 const TopicCacheSize    : integer = 10;
 const TopicCacheSize    : integer = 10;
@@ -206,12 +230,18 @@ const TopicCacheSize    : integer = 10;
       HelpFacility      : PHelpFacility = nil;
       HelpFacility      : PHelpFacility = nil;
       MaxHelpTopicSize  : word = 65520;
       MaxHelpTopicSize  : word = 65520;
 
 
+function  NewTopic(FileID: byte; HelpCtx: THelpCtx; Pos: longint): PTopic;
 procedure DisposeTopic(P: PTopic);
 procedure DisposeTopic(P: PTopic);
 
 
+function  NewIndexEntry(Tag: string; FileID: word; HelpCtx: THelpCtx): PIndexEntry;
+procedure DisposeIndexEntry(P: PIndexEntry);
+
 implementation
 implementation
 
 
 uses
 uses
-  drivers;
+  Dos,
+  WHTMLHlp,
+  Drivers;
 
 
 type
 type
      PByteArray = ^TByteArray;
      PByteArray = ^TByteArray;
@@ -247,7 +277,7 @@ begin
   FillChar(R, SizeOf(R), 0);
   FillChar(R, SizeOf(R), 0);
 end;
 end;
 
 
-function NewTopic(FileID: byte; HelpCtx: word; Pos: longint): PTopic;
+function NewTopic(FileID: byte; HelpCtx: THelpCtx; Pos: longint): PTopic;
 var P: PTopic;
 var P: PTopic;
 begin
 begin
   New(P); FillChar(P^,SizeOf(P^), 0);
   New(P); FillChar(P^,SizeOf(P^), 0);
@@ -280,7 +310,7 @@ begin
   CloneTopic:=NT;
   CloneTopic:=NT;
 end;
 end;
 
 
-function NewIndexEntry(Tag: string; FileID, HelpCtx: word): PIndexEntry;
+function NewIndexEntry(Tag: string; FileID: word; HelpCtx: THelpCtx): PIndexEntry;
 var P: PIndexEntry;
 var P: PIndexEntry;
 begin
 begin
   New(P); FillChar(P^,SizeOf(P^), 0);
   New(P); FillChar(P^,SizeOf(P^), 0);
@@ -317,7 +347,7 @@ begin
   if Item<>nil then DisposeTopic(Item);
   if Item<>nil then DisposeTopic(Item);
 end;
 end;
 
 
-function TTopicCollection.SearchTopic(AHelpCtx: word): PTopic;
+function TTopicCollection.SearchTopic(AHelpCtx: THelpCtx): PTopic;
 function Match(P: PTopic): boolean;{$ifndef FPC}far;{$endif}
 function Match(P: PTopic): boolean;{$ifndef FPC}far;{$endif}
 begin Match:=(P^.HelpCtx=AHelpCtx); end;
 begin Match:=(P^.HelpCtx=AHelpCtx); end;
 begin
 begin
@@ -347,15 +377,82 @@ begin
   Compare:=R;
   Compare:=R;
 end;
 end;
 
 
-constructor THelpFile.Init(AFileName: string; AID: word);
-var OK: boolean;
-    FS,L: longint;
-    R: TRecord;
+constructor THelpFile.Init(AID: word);
 begin
 begin
   inherited Init;
   inherited Init;
   ID:=AID;
   ID:=AID;
   New(Topics, Init(500,500));
   New(Topics, Init(500,500));
   New(IndexEntries, Init(200,100));
   New(IndexEntries, Init(200,100));
+end;
+
+function THelpFile.LoadTopic(HelpCtx: THelpCtx): PTopic;
+var T: PTopic;
+begin
+  T:=SearchTopic(HelpCtx);
+  if (T<>nil) then
+     if T^.Text=nil then
+     begin
+       MaintainTopicCache;
+       if ReadTopic(T)=false then T:=nil;
+       if (T<>nil) and (T^.Text=nil) then T:=nil;
+     end;
+  if T<>nil then
+     begin T^.LastAccess:=GetDosTicks; T:=CloneTopic(T); end;
+  LoadTopic:=T;
+end;
+
+function THelpFile.LoadIndex: boolean;
+begin
+  Abstract;
+end;
+
+function THelpFile.SearchTopic(HelpCtx: THelpCtx): PTopic;
+var T: PTopic;
+begin
+  T:=Topics^.SearchTopic(HelpCtx);
+  SearchTopic:=T;
+end;
+
+function THelpFile.ReadTopic(T: PTopic): boolean;
+begin
+  Abstract;
+end;
+
+procedure THelpFile.MaintainTopicCache;
+var Count: integer;
+    MinP: PTopic;
+    MinLRU: longint;
+procedure CountThem(P: PTopic); {$ifndef FPC}far;{$endif}
+begin if (P^.Text<>nil) or (P^.Links<>nil) then Inc(Count); end;
+procedure SearchLRU(P: PTopic); {$ifndef FPC}far;{$endif}
+begin if P^.LastAccess<MinLRU then begin MinLRU:=P^.LastAccess; MinP:=P; end; end;
+var P: PTopic;
+begin
+  Count:=0; Topics^.ForEach(@CountThem);
+  if (Count>=TopicCacheSize) then
+  begin
+    MinLRU:=MaxLongint; P:=nil; Topics^.ForEach(@SearchLRU);
+    if P<>nil then
+    begin
+      FreeMem(P^.Text,P^.TextSize); P^.TextSize:=0; P^.Text:=nil;
+      FreeMem(P^.Links,P^.LinkSize); P^.LinkSize:=0; P^.LinkCount:=0; P^.Links:=nil;
+    end;
+  end;
+end;
+
+destructor THelpFile.Done;
+begin
+  if Topics<>nil then Dispose(Topics, Done);
+  if IndexEntries<>nil then Dispose(IndexEntries, Done);
+  inherited Done;
+end;
+
+constructor TOAHelpFile.Init(AFileName: string; AID: word);
+var OK: boolean;
+    FS,L: longint;
+    R: TRecord;
+begin
+  inherited Init(AID);
   New(F, Init(AFileName, stOpenRead, HelpStreamBufSize));
   New(F, Init(AFileName, stOpenRead, HelpStreamBufSize));
   OK:=F<>nil;
   OK:=F<>nil;
   if OK then OK:=(F^.Status=stOK);
   if OK then OK:=(F^.Status=stOK);
@@ -382,7 +479,12 @@ begin
   if OK=false then Fail;
   if OK=false then Fail;
 end;
 end;
 
 
-function THelpFile.ReadHeader: boolean;
+function TOAHelpFile.LoadIndex: boolean;
+begin
+  LoadIndex:=ReadIndexTable;
+end;
+
+function TOAHelpFile.ReadHeader: boolean;
 var S: string;
 var S: string;
     P: longint;
     P: longint;
     R: TRecord;
     R: TRecord;
@@ -405,7 +507,7 @@ begin
   ReadHeader:=OK;
   ReadHeader:=OK;
 end;
 end;
 
 
-function THelpFile.ReadTopics: boolean;
+function TOAHelpFile.ReadTopics: boolean;
 var OK: boolean;
 var OK: boolean;
     R: TRecord;
     R: TRecord;
     L,I: longint;
     L,I: longint;
@@ -432,7 +534,7 @@ begin
   ReadTopics:=OK;
   ReadTopics:=OK;
 end;
 end;
 
 
-function THelpFile.ReadIndexTable: boolean;
+function TOAHelpFile.ReadIndexTable: boolean;
 var OK: boolean;
 var OK: boolean;
     R: TRecord;
     R: TRecord;
     I: longint;
     I: longint;
@@ -464,7 +566,7 @@ begin
   ReadIndexTable:=OK;
   ReadIndexTable:=OK;
 end;
 end;
 
 
-function THelpFile.ReadCompression: boolean;
+function TOAHelpFile.ReadCompression: boolean;
 var OK: boolean;
 var OK: boolean;
     R: TRecord;
     R: TRecord;
 begin
 begin
@@ -476,7 +578,7 @@ begin
   ReadCompression:=OK;
   ReadCompression:=OK;
 end;
 end;
 
 
-function THelpFile.ReadIndexTags: boolean;
+function TOAHelpFile.ReadIndexTags: boolean;
 var OK: boolean;
 var OK: boolean;
 begin
 begin
   OK:={ReadRecord(R, true)}true;
   OK:={ReadRecord(R, true)}true;
@@ -484,7 +586,7 @@ begin
   ReadIndexTags:=OK;
   ReadIndexTags:=OK;
 end;
 end;
 
 
-function THelpFile.ReadRecord(var R: TRecord; ReadData: boolean): boolean;
+function TOAHelpFile.ReadRecord(var R: TRecord; ReadData: boolean): boolean;
 var OK: boolean;
 var OK: boolean;
     H: THLPRecordHeader;
     H: THLPRecordHeader;
 begin
 begin
@@ -505,7 +607,7 @@ begin
   ReadRecord:=OK;
   ReadRecord:=OK;
 end;
 end;
 
 
-function THelpFile.ReadTopic(T: PTopic): boolean;
+function TOAHelpFile.ReadTopic(T: PTopic): boolean;
 var SrcPtr,DestPtr: word;
 var SrcPtr,DestPtr: word;
     NewR: TRecord;
     NewR: TRecord;
 function ExtractTextRec(var R: TRecord): boolean;
 function ExtractTextRec(var R: TRecord): boolean;
@@ -610,48 +712,8 @@ begin
   ReadTopic:=OK;
   ReadTopic:=OK;
 end;
 end;
 
 
-function THelpFile.LoadTopic(HelpCtx: word): PTopic;
-var T: PTopic;
-begin
-  T:=Topics^.SearchTopic(HelpCtx);
-  if (T<>nil) then
-     if T^.Text=nil then
-     begin
-       MaintainTopicCache;
-       if ReadTopic(T)=false then T:=nil;
-       if (T<>nil) and (T^.Text=nil) then T:=nil;
-     end;
-  if T<>nil then
-     begin T^.LastAccess:=GetDosTicks; T:=CloneTopic(T); end;
-  LoadTopic:=T;
-end;
-
-procedure THelpFile.MaintainTopicCache;
-var Count: integer;
-    MinP: PTopic;
-    MinLRU: longint;
-procedure CountThem(P: PTopic); {$ifndef FPC}far;{$endif}
-begin if (P^.Text<>nil) or (P^.Links<>nil) then Inc(Count); end;
-procedure SearchLRU(P: PTopic); {$ifndef FPC}far;{$endif}
-begin if P^.LastAccess<MinLRU then begin MinLRU:=P^.LastAccess; MinP:=P; end; end;
-var P: PTopic;
-begin
-  Count:=0; Topics^.ForEach(@CountThem);
-  if (Count>=TopicCacheSize) then
-  begin
-    MinLRU:=MaxLongint; P:=nil; Topics^.ForEach(@SearchLRU);
-    if P<>nil then
-    begin
-      FreeMem(P^.Text,P^.TextSize); P^.TextSize:=0; P^.Text:=nil;
-      FreeMem(P^.Links,P^.LinkSize); P^.LinkSize:=0; P^.LinkCount:=0; P^.Links:=nil;
-    end;
-  end;
-end;
-
-destructor THelpFile.Done;
+destructor TOAHelpFile.Done;
 begin
 begin
-  if Topics<>nil then Dispose(Topics, Done);
-  if IndexEntries<>nil then Dispose(IndexEntries, Done);
   if F<>nil then Dispose(F, Done);
   if F<>nil then Dispose(F, Done);
   inherited Done;
   inherited Done;
 end;
 end;
@@ -663,18 +725,32 @@ begin
   IndexTabSize:=40;
   IndexTabSize:=40;
 end;
 end;
 
 
-function THelpFacility.AddHelpFile(FileName: string): boolean;
+
+function THelpFacility.AddOAHelpFile(FileName: string): boolean;
+var H: PHelpFile;
+begin
+  H:=New(POAHelpFile, Init(FileName, LastID+1));
+  AddOAHelpFile:=AddFile(H);
+end;
+
+function THelpFacility.AddHTMLHelpFile(FileName, TOCEntry: string): boolean;
 var H: PHelpFile;
 var H: PHelpFile;
 begin
 begin
-  New(H, Init(FileName, LastID+1));
+  H:=New(PHTMLHelpFile, Init(FileName, LastID+1, TOCEntry));
+  AddHTMLHelpFile:=AddFile(H);;
+end;
+
+function THelpFacility.AddFile(H: PHelpFile): boolean;
+begin
   if H<>nil then
   if H<>nil then
-     begin
-       HelpFiles^.Insert(H); Inc(LastID);
-     end;
-  AddHelpFile:=true;
+    begin
+      HelpFiles^.Insert(H);
+      Inc(LastID);
+    end;
+  AddFile:=H<>nil;
 end;
 end;
 
 
-function THelpFacility.SearchTopicOwner(SourceFileID: word; Context: word): PHelpFile;
+function THelpFacility.SearchTopicOwner(SourceFileID: word; Context: THelpCtx): PHelpFile;
 var P: PTopic;
 var P: PTopic;
     HelpFile: PHelpFile;
     HelpFile: PHelpFile;
 function Search(F: PHelpFile): boolean; {$ifndef FPC}far;{$endif}
 function Search(F: PHelpFile): boolean; {$ifndef FPC}far;{$endif}
@@ -694,7 +770,7 @@ begin
   SearchTopicOwner:=HelpFile;
   SearchTopicOwner:=HelpFile;
 end;
 end;
 
 
-function THelpFacility.LoadTopic(SourceFileID: word; Context: word): PTopic;
+function THelpFacility.LoadTopic(SourceFileID: word; Context: THelpCtx): PTopic;
 var P: PTopic;
 var P: PTopic;
     H: PHelpFile;
     H: PHelpFile;
 begin
 begin
@@ -708,7 +784,7 @@ begin
   LoadTopic:=P;
   LoadTopic:=P;
 end;
 end;
 
 
-function THelpFacility.TopicSearch(Keyword: string; var FileID, Context: word): boolean;
+function THelpFacility.TopicSearch(Keyword: string; var FileID: word; Context: THelpCtx): boolean;
 function ScanHelpFile(H: PHelpFile): boolean; {$ifndef FPC}far;{$endif}
 function ScanHelpFile(H: PHelpFile): boolean; {$ifndef FPC}far;{$endif}
 function Search(P: PIndexEntry): boolean; {$ifndef FPC}far;{$endif}
 function Search(P: PIndexEntry): boolean; {$ifndef FPC}far;{$endif}
 begin
 begin
@@ -716,7 +792,7 @@ begin
 end;
 end;
 var P: PIndexEntry;
 var P: PIndexEntry;
 begin
 begin
-  H^.ReadIndexTable;
+  H^.LoadIndex;
   P:=H^.IndexEntries^.FirstThat(@Search);
   P:=H^.IndexEntries^.FirstThat(@Search);
   if P<>nil then begin FileID:=H^.ID; Context:=P^.HelpCtx; end;
   if P<>nil then begin FileID:=H^.ID; Context:=P^.HelpCtx; end;
   ScanHelpFile:=P<>nil;
   ScanHelpFile:=P<>nil;
@@ -737,7 +813,7 @@ begin
   InsertKeywords:=Keywords^.Count>=MaxCollectionSize;
   InsertKeywords:=Keywords^.Count>=MaxCollectionSize;
 end;
 end;
 begin
 begin
-  H^.ReadIndexTable;
+  H^.LoadIndex;
   if Keywords^.Count<MaxCollectionSize then
   if Keywords^.Count<MaxCollectionSize then
   H^.IndexEntries^.FirstThat(@InsertKeywords);
   H^.IndexEntries^.FirstThat(@InsertKeywords);
 end;
 end;
@@ -757,7 +833,7 @@ begin
   for I:=0 to Lines^.Count-1 do
   for I:=0 to Lines^.Count-1 do
   begin
   begin
     S:=Lines^.At(I)^;
     S:=Lines^.At(I)^;
-    Size:=length(S)+1; S[Size]:=#0;
+    Size:=length(S)+1; S[Size]:=hscLineBreak;
     Move(S[1],PByteArray(T^.Text)^[CurPtr],Size);
     Move(S[1],PByteArray(T^.Text)^[CurPtr],Size);
     Inc(CurPtr,Size);
     Inc(CurPtr,Size);
     if CurPtr>=T^.TextSize then Break;
     if CurPtr>=T^.TextSize then Break;
@@ -837,11 +913,11 @@ begin
   SearchFile:=HelpFiles^.FirstThat(@Match);
   SearchFile:=HelpFiles^.FirstThat(@Match);
 end;
 end;
 
 
-function THelpFacility.SearchTopicInHelpFile(F: PHelpFile; Context: word): PTopic;
+function THelpFacility.SearchTopicInHelpFile(F: PHelpFile; Context: THelpCtx): PTopic;
 var P: PTopic;
 var P: PTopic;
 begin
 begin
   if F=nil then P:=nil else
   if F=nil then P:=nil else
-  P:=F^.Topics^.SearchTopic(Context);
+  P:=F^.SearchTopic(Context);
   SearchTopicInHelpFile:=P;
   SearchTopicInHelpFile:=P;
 end;
 end;
 
 
@@ -854,7 +930,10 @@ end;
 END.
 END.
 {
 {
   $Log$
   $Log$
-  Revision 1.2  1998-12-28 15:47:56  peter
+  Revision 1.3  1999-02-08 10:37:46  peter
+    + html helpviewer
+
+  Revision 1.2  1998/12/28 15:47:56  peter
     + Added user screen support, display & window
     + Added user screen support, display & window
     + Implemented Editor,Mouse Options dialog
     + Implemented Editor,Mouse Options dialog
     + Added location of .INI and .CFG file
     + Added location of .INI and .CFG file

+ 117 - 30
ide/text/whlpview.pas

@@ -42,7 +42,8 @@ type
       PHelpLink = ^THelpLink;
       PHelpLink = ^THelpLink;
       THelpLink = record
       THelpLink = record
         Bounds   : TRect;
         Bounds   : TRect;
-        Context  : word;
+        FileID   : longint;
+        Context  : THelpCtx;
       end;
       end;
 
 
       PHelpColorArea = ^THelpColorArea;
       PHelpColorArea = ^THelpColorArea;
@@ -93,7 +94,8 @@ type
         function    GetLineText(Line: integer): string; virtual;
         function    GetLineText(Line: integer): string; virtual;
         function    GetLinkCount: integer; virtual;
         function    GetLinkCount: integer; virtual;
         procedure   GetLinkBounds(Index: integer; var R: TRect); virtual;
         procedure   GetLinkBounds(Index: integer; var R: TRect); virtual;
-        function    GetLinkContext(Index: integer): word; virtual;
+        function    GetLinkFileID(Index: integer): word; virtual;
+        function    GetLinkContext(Index: integer): THelpCtx; virtual;
         function    GetColorAreaCount: integer; virtual;
         function    GetColorAreaCount: integer; virtual;
         procedure   GetColorAreaBounds(Index: integer; var R: TRect); virtual;
         procedure   GetColorAreaBounds(Index: integer; var R: TRect); virtual;
         function    GetColorAreaColor(Index: integer): word; virtual;
         function    GetColorAreaColor(Index: integer): word; virtual;
@@ -105,7 +107,7 @@ type
       end;
       end;
 
 
       THelpHistoryEntry = record
       THelpHistoryEntry = record
-        Context_     : word;
+        Context_     : THelpCtx;
         Delta_       : TPoint;
         Delta_       : TPoint;
         CurPos_      : TPoint;
         CurPos_      : TPoint;
         CurLink_     : integer;
         CurLink_     : integer;
@@ -126,14 +128,15 @@ type
         function    GetLineText(Line: integer): string; virtual;
         function    GetLineText(Line: integer): string; virtual;
         function    GetLinkCount: integer; virtual;
         function    GetLinkCount: integer; virtual;
         procedure   GetLinkBounds(Index: integer; var R: TRect); virtual;
         procedure   GetLinkBounds(Index: integer; var R: TRect); virtual;
-        function    GetLinkContext(Index: integer): word; virtual;
+        function    GetLinkFileID(Index: integer): word; virtual;
+        function    GetLinkContext(Index: integer): THelpCtx; virtual;
         function    GetLinkText(Index: integer): string; virtual;
         function    GetLinkText(Index: integer): string; virtual;
         function    GetColorAreaCount: integer; virtual;
         function    GetColorAreaCount: integer; virtual;
         procedure   GetColorAreaBounds(Index: integer; var R: TRect); virtual;
         procedure   GetColorAreaBounds(Index: integer; var R: TRect); virtual;
         function    GetColorAreaColor(Index: integer): word; virtual;
         function    GetColorAreaColor(Index: integer): word; virtual;
         procedure   SelectNextLink(ANext: boolean); virtual;
         procedure   SelectNextLink(ANext: boolean); virtual;
         procedure   SwitchToIndex; virtual;
         procedure   SwitchToIndex; virtual;
-        procedure   SwitchToTopic(SourceFileID: word; Context: word); virtual;
+        procedure   SwitchToTopic(SourceFileID: word; Context: THelpCtx); virtual;
         procedure   SetTopic(Topic: PTopic); virtual;
         procedure   SetTopic(Topic: PTopic); virtual;
         procedure   SetCurLink(Link: integer); virtual;
         procedure   SetCurLink(Link: integer); virtual;
         procedure   SelectLink(Index: integer); virtual;
         procedure   SelectLink(Index: integer); virtual;
@@ -151,7 +154,7 @@ type
         IndexTopic : PTopic;
         IndexTopic : PTopic;
         IndexHelpTopic: PHelpTopic;
         IndexHelpTopic: PHelpTopic;
         function    LinkContainsPoint(var R: TRect; var P: TPoint): boolean;
         function    LinkContainsPoint(var R: TRect; var P: TPoint): boolean;
-        procedure   ISwitchToTopic(SourceFileID: word; Context: word; RecordInHistory: boolean);
+        procedure   ISwitchToTopic(SourceFileID: word; Context: THelpCtx; RecordInHistory: boolean);
         procedure   ISwitchToTopicPtr(P: PTopic; RecordInHistory: boolean);
         procedure   ISwitchToTopicPtr(P: PTopic; RecordInHistory: boolean);
         procedure   BuildTopicWordList;
         procedure   BuildTopicWordList;
       end;
       end;
@@ -165,10 +168,10 @@ type
       THelpWindow = object(TWindow)
       THelpWindow = object(TWindow)
         HelpView: PHelpViewer;
         HelpView: PHelpViewer;
         HideOnClose: boolean;
         HideOnClose: boolean;
-        constructor Init(var Bounds: TRect; ATitle: TTitleStr; ASourceFileID: word; AContext: word; ANumber: Integer);
+        constructor Init(var Bounds: TRect; ATitle: TTitleStr; ASourceFileID: word; AContext: THelpCtx; ANumber: Integer);
         procedure   InitFrame; virtual;
         procedure   InitFrame; virtual;
         procedure   ShowIndex; virtual;
         procedure   ShowIndex; virtual;
-        procedure   ShowTopic(SourceFileID: word; Context: word); virtual;
+        procedure   ShowTopic(SourceFileID: word; Context: THelpCtx); virtual;
         procedure   HandleEvent(var Event: TEvent); virtual;
         procedure   HandleEvent(var Event: TEvent); virtual;
         procedure   Close; virtual;
         procedure   Close; virtual;
         function    GetPalette: PPalette; virtual; { needs to be overriden }
         function    GetPalette: PPalette; virtual; { needs to be overriden }
@@ -203,10 +206,11 @@ begin
   UpcaseStr:=S;
   UpcaseStr:=S;
 end;
 end;
 
 
-function NewLink(Topic: word; StartP, EndP: TPoint): PHelpLink;
+function NewLink(FileID: longint; Topic: THelpCtx; StartP, EndP: TPoint): PHelpLink;
 var P: PHelpLink;
 var P: PHelpLink;
 begin
 begin
   New(P); FillChar(P^, SizeOf(P^), 0);
   New(P); FillChar(P^, SizeOf(P^), 0);
+  P^.FileID:=FileID;
   P^.Context:=Topic; P^.Bounds.A:=StartP; P^.Bounds.B:=EndP;
   P^.Context:=Topic; P^.Bounds.A:=StartP; P^.Bounds.B:=EndP;
   NewLink:=P;
   NewLink:=P;
 end;
 end;
@@ -339,20 +343,50 @@ var TextPos,LinkNo: word;
     LinkStart,LinkEnd,ColorAreaStart,ColorAreaEnd: TPoint;
     LinkStart,LinkEnd,ColorAreaStart,ColorAreaEnd: TPoint;
     CurPos: TPoint;
     CurPos: TPoint;
     ZeroLevel: integer;
     ZeroLevel: integer;
+    LineStart,NextLineStart: integer;
+    LineAlign : (laLeft,laCenter,laRight);
+    FirstLink,LastLink: integer;
 procedure ClearLine;
 procedure ClearLine;
 begin
 begin
   Line:='';
   Line:='';
 end;
 end;
 procedure AddWord(TheWord: string); forward;
 procedure AddWord(TheWord: string); forward;
 procedure NextLine;
 procedure NextLine;
+var P: sw_integer;
+    I,Delta: integer;
 begin
 begin
   Line:=CharStr(' ',Margin)+Line;
   Line:=CharStr(' ',Margin)+Line;
+  repeat
+    P:=Pos(#255,Line);
+    if P>0 then Line[P]:=#32;
+  until P=0;
   while copy(Line,length(Line),1)=' ' do Delete(Line,length(Line),1);
   while copy(Line,length(Line),1)=' ' do Delete(Line,length(Line),1);
+  Delta:=0;
+  if Line<>'' then
+  case LineAlign of
+    laLeft    : ;
+    laCenter  : if Margin+length(Line)+Margin<Width then
+                  begin
+                    Delta:=(Width-(Margin+length(Line)+Margin)) div 2;
+                    Line:=CharStr(' ',Delta)+Line;
+                  end;
+    laRight   : if Margin+length(Line)+Margin<Width then
+                  begin
+                    Delta:=Width-(Margin+length(Line)+Margin);
+                    Line:=CharStr(' ',Delta)+Line;
+                  end;
+  end;
+  if (Delta>0) and (FirstLink<>LastLink) then
+  for I:=FirstLink to LastLink-1 do
+    with PHelpLink(Links^.At(I))^ do
+      Bounds.Move(Delta,0);
   if Line='' then Line:=' ';
   if Line='' then Line:=' ';
   Lines^.Insert(NewStr(Line));
   Lines^.Insert(NewStr(Line));
   ClearLine;
   ClearLine;
-  CurPos.X:=Margin; Inc(CurPos.Y);
+  LineStart:=NextLineStart;
+  CurPos.X:=Margin+LineStart; Line:=CharStr(#255,LineStart); Inc(CurPos.Y);
   if InLink then LinkStart:=CurPos;
   if InLink then LinkStart:=CurPos;
+  FirstLink:=LastLink;
 end;
 end;
 procedure FlushLine;
 procedure FlushLine;
 var W: string;
 var W: string;
@@ -363,7 +397,9 @@ end;
 procedure AddWord(TheWord: string);
 procedure AddWord(TheWord: string);
 var W: string;
 var W: string;
 begin
 begin
-  W:=TheWord; while copy(W,length(W),1)=' ' do Delete(W,length(W),1);
+  W:=TheWord;
+  while (length(W)>0) and (W[length(W)] in [' ',#255]) do
+     Delete(W,length(W),1);
   if (copy(Line+TheWord,1,1)<>' ') then
   if (copy(Line+TheWord,1,1)<>' ') then
     if (Line<>'') and (Margin+length(Line)+length(W)+Margin>Width) then
     if (Line<>'') and (Margin+length(Line)+length(W)+Margin>Width) then
        NextLine;
        NextLine;
@@ -373,36 +409,63 @@ end;
 procedure CheckZeroLevel;
 procedure CheckZeroLevel;
 begin
 begin
   if ZeroLevel<>0 then
   if ZeroLevel<>0 then
-     begin if CurWord<>'' then AddWord(CurWord+' '); CurWord:=''; ZeroLevel:=0; end;
+     begin
+       if CurWord<>'' then AddWord(CurWord+' ');
+       CurWord:='';
+       ZeroLevel:=0;
+     end;
 end;
 end;
+var Diff: integer;
 begin
 begin
   Lines^.FreeAll; Links^.FreeAll;
   Lines^.FreeAll; Links^.FreeAll;
   if Topic=nil then Lines^.Insert(NewStr('No help available for this topic.')) else
   if Topic=nil then Lines^.Insert(NewStr('No help available for this topic.')) else
   begin
   begin
-    TextPos:=0; ClearLine; CurWord:=''; CurPos.X:=Margin; CurPos.Y:=0; LinkNo:=0;
+    LineStart:=0; NextLineStart:=0;
+    TextPos:=0; ClearLine; CurWord:=''; Line:='';
+    CurPos.X:=Margin+LineStart; CurPos.Y:=0; LinkNo:=0;
     InLink:=false; InColorArea:=false; ZeroLevel:=0;
     InLink:=false; InColorArea:=false; ZeroLevel:=0;
+    LineAlign:=laLeft;
+    FirstLink:=0; LastLink:=0;
     while (TextPos<Topic^.TextSize) do
     while (TextPos<Topic^.TextSize) do
     begin
     begin
       C:=chr(PByteArray(Topic^.Text)^[TextPos]);
       C:=chr(PByteArray(Topic^.Text)^[TextPos]);
       case C of
       case C of
-        #0 : {if ZeroLevel=0 then ZeroLevel:=1 else
+        hscLineBreak :
+            {if ZeroLevel=0 then ZeroLevel:=1 else
                 begin FlushLine; FlushLine; ZeroLevel:=0; end;}
                 begin FlushLine; FlushLine; ZeroLevel:=0; end;}
              if InLink then CurWord:=CurWord+' ' else
              if InLink then CurWord:=CurWord+' ' else
-                FlushLine;
+               begin
+                 NextLineStart:=0;
+                 FlushLine;
+                 LineStart:=0;
+                 LineAlign:=laLeft;
+               end;
         #1 : Break;
         #1 : Break;
-        #2 : begin
+        hscLink :
+             begin
                CheckZeroLevel;
                CheckZeroLevel;
                if InLink=false then
                if InLink=false then
                   begin LinkStart:=CurPos; InLink:=true; end else
                   begin LinkStart:=CurPos; InLink:=true; end else
                 begin
                 begin
                   if CurWord<>'' then AddWord(CurWord); CurWord:='';
                   if CurWord<>'' then AddWord(CurWord); CurWord:='';
                   LinkEnd:=CurPos; Dec(LinkEnd.X);
                   LinkEnd:=CurPos; Dec(LinkEnd.X);
-                  Links^.Insert(NewLink(Topic^.Links^[LinkNo].Context,LinkStart,LinkEnd));
-                  Inc(LinkNo);
+                  if Topic^.Links<>nil then
+                    begin
+                      Inc(LastLink);
+                      Links^.Insert(NewLink(Topic^.Links^[LinkNo].FileID,
+                        Topic^.Links^[LinkNo].Context,LinkStart,LinkEnd));
+                      Inc(LinkNo);
+                    end;
                   InLink:=false;
                   InLink:=false;
                 end;
                 end;
               end;
               end;
-        #5 : begin
+        hscLineStart :
+             begin
+               NextLineStart:=length(Line)+length(CurWord);
+{               LineStart:=LineStart+(NextLineStart-LineStart);}
+             end;
+        hscCode :
+             begin
                if InColorArea=false then
                if InColorArea=false then
                   ColorAreaStart:=CurPos else
                   ColorAreaStart:=CurPos else
                 begin
                 begin
@@ -412,6 +475,10 @@ begin
                 end;
                 end;
                InColorArea:=not InColorArea;
                InColorArea:=not InColorArea;
              end;
              end;
+        hscCenter :
+             LineAlign:=laCenter;
+        hscRight  :
+             LineAlign:=laCenter;
         #32: if InLink then CurWord:=CurWord+C else
         #32: if InLink then CurWord:=CurWord+C else
                 begin CheckZeroLevel; AddWord(CurWord+C); CurWord:=''; end;
                 begin CheckZeroLevel; AddWord(CurWord+C); CurWord:=''; end;
       else begin CheckZeroLevel; CurWord:=CurWord+C; end;
       else begin CheckZeroLevel; CurWord:=CurWord+C; end;
@@ -447,7 +514,14 @@ begin
   R:=P^.Bounds;
   R:=P^.Bounds;
 end;
 end;
 
 
-function THelpTopic.GetLinkContext(Index: integer): word;
+function THelpTopic.GetLinkFileID(Index: integer): word;
+var P: PHelpLink;
+begin
+  P:=Links^.At(Index);
+  GetLinkFileID:=P^.FileID;
+end;
+
+function THelpTopic.GetLinkContext(Index: integer): THelpCtx;
 var P: PHelpLink;
 var P: PHelpLink;
 begin
 begin
   P:=Links^.At(Index);
   P:=Links^.At(Index);
@@ -566,7 +640,12 @@ begin
   HelpTopic^.GetLinkBounds(Index,R);
   HelpTopic^.GetLinkBounds(Index,R);
 end;
 end;
 
 
-function THelpViewer.GetLinkContext(Index: integer): word;
+function THelpViewer.GetLinkFileID(Index: integer): word;
+begin
+  GetLinkFileID:=HelpTopic^.GetLinkFileID(Index);
+end;
+
+function THelpViewer.GetLinkContext(Index: integer): THelpCtx;
 begin
 begin
   GetLinkContext:=HelpTopic^.GetLinkContext(Index);
   GetLinkContext:=HelpTopic^.GetLinkContext(Index);
 end;
 end;
@@ -648,12 +727,12 @@ begin
   ISwitchToTopicPtr(IndexTopic,true);
   ISwitchToTopicPtr(IndexTopic,true);
 end;
 end;
 
 
-procedure THelpViewer.SwitchToTopic(SourceFileID: word; Context: word);
+procedure THelpViewer.SwitchToTopic(SourceFileID: word; Context: THelpCtx);
 begin
 begin
   ISwitchToTopic(SourceFileID,Context,true);
   ISwitchToTopic(SourceFileID,Context,true);
 end;
 end;
 
 
-procedure THelpViewer.ISwitchToTopic(SourceFileID: word; Context: word; RecordInHistory: boolean);
+procedure THelpViewer.ISwitchToTopic(SourceFileID: word; Context: THelpCtx; RecordInHistory: boolean);
 var P: PTopic;
 var P: PTopic;
 begin
 begin
   if HelpFacility=nil then P:=nil else
   if HelpFacility=nil then P:=nil else
@@ -763,8 +842,12 @@ begin
   begin
   begin
     while (Index=-1) and (I<WordList^.Count) do
     while (Index=-1) and (I<WordList^.Count) do
       begin
       begin
-        W:=UpcaseStr(Trim(WordList^.At(I)^.KWord^));
-        if copy(W,1,length(S))=S then Index:=I else
+        P:=WordList^.At(I);
+        if P^.KWord<>nil then
+          begin
+            W:=UpcaseStr(Trim(P^.KWord^));
+            if copy(W,1,length(S))=S then Index:=I;
+          end;
 {        if W>S then Break else}
 {        if W>S then Break else}
         Inc(I);
         Inc(I);
       end;
       end;
@@ -795,12 +878,12 @@ end;
 
 
 procedure THelpViewer.SelectLink(Index: integer);
 procedure THelpViewer.SelectLink(Index: integer);
 var ID: word;
 var ID: word;
-    Ctx: word;
+    Ctx: THelpCtx;
 begin
 begin
   if Index=-1 then Exit;
   if Index=-1 then Exit;
   if HelpTopic=nil then begin ID:=0; Ctx:=0; end else
   if HelpTopic=nil then begin ID:=0; Ctx:=0; end else
      begin
      begin
-       ID:=HelpTopic^.Topic^.FileID;
+       ID:=GetLinkFileID(Index);
        Ctx:=GetLinkContext(Index);
        Ctx:=GetLinkContext(Index);
      end;
      end;
   SwitchToTopic(ID,Ctx);
   SwitchToTopic(ID,Ctx);
@@ -885,6 +968,7 @@ begin
     if Y<GetLineCount then
     if Y<GetLineCount then
     begin
     begin
       S:=copy(GetLineText(Y),Delta.X+1,255);
       S:=copy(GetLineText(Y),Delta.X+1,255);
+      S:=copy(S,1,MaxViewWidth);
       MoveStr(B,S,NormalColor);
       MoveStr(B,S,NormalColor);
 
 
       for I:=LastColorAreaDrawn to GetColorAreaCount-1 do
       for I:=LastColorAreaDrawn to GetColorAreaCount-1 do
@@ -972,7 +1056,7 @@ begin
   GetPalette:=@P;
   GetPalette:=@P;
 end;
 end;
 
 
-constructor THelpWindow.Init(var Bounds: TRect; ATitle: TTitleStr; ASourceFileID: word; AContext: word; ANumber: Integer);
+constructor THelpWindow.Init(var Bounds: TRect; ATitle: TTitleStr; ASourceFileID: word; AContext: THelpCtx; ANumber: Integer);
 var R: TRect;
 var R: TRect;
     VSB,HSB: PScrollBar;
     VSB,HSB: PScrollBar;
 begin
 begin
@@ -1001,7 +1085,7 @@ begin
   HelpView^.SwitchToIndex;
   HelpView^.SwitchToIndex;
 end;
 end;
 
 
-procedure THelpWindow.ShowTopic(SourceFileID: word; Context: word);
+procedure THelpWindow.ShowTopic(SourceFileID: word; Context: THelpCtx);
 begin
 begin
   HelpView^.SwitchToTopic(SourceFileID, Context);
   HelpView^.SwitchToTopic(SourceFileID, Context);
 end;
 end;
@@ -1033,7 +1117,10 @@ end;
 END.
 END.
 {
 {
   $Log$
   $Log$
-  Revision 1.3  1999-01-21 11:54:32  peter
+  Revision 1.4  1999-02-08 10:37:47  peter
+    + html helpviewer
+
+  Revision 1.3  1999/01/21 11:54:32  peter
     + tools menu
     + tools menu
     + speedsearch in symbolbrowser
     + speedsearch in symbolbrowser
     * working run command
     * working run command

+ 685 - 0
ide/text/whtml.pas

@@ -0,0 +1,685 @@
+unit WHTML;
+
+interface
+
+{$ifndef FPC}
+  {$define TPUNIXLF}
+{$endif}
+
+uses Objects;
+
+type
+    PTextFile = ^TTextFile;
+    TTextFile = object(TObject)
+      function GetLine(Idx: sw_integer; var S: string): boolean; virtual;
+    end;
+
+    PDOSTextFile = ^TDOSTextFile;
+    TDOSTextFile = object(TTextFile)
+      constructor Init(AFileName: string);
+      function    GetLine(Idx: sw_integer; var S: string): boolean; virtual;
+      destructor  Done; virtual;
+    private
+      Lines : PUnsortedStrCollection;
+    end;
+
+    PSGMLParser = ^TSGMLParser;
+    TSGMLParser = object(TObject)
+      constructor Init;
+      function    Process(HTMLFile: PTextFile): boolean; virtual;
+      function    ProcessLine(LineText: string): boolean; virtual;
+      destructor  Done; virtual;
+    public
+      Line,LinePos: sw_integer;
+      procedure   DocSoftBreak; virtual;
+      procedure   DocAddTextChar(C: char); virtual;
+      procedure   DocAddText(S: string); virtual;
+      procedure   DocProcessTag(Tag: string); virtual;
+      procedure   DocProcessComment(Comment: string); virtual;
+      function    DocDecodeNamedEntity(Name: string; var Entity: string): boolean; virtual;
+    private
+      CurTag: string;
+      InTag,InComment,InString: boolean;
+    end;
+
+    PHTMLParser = ^THTMLParser;
+    THTMLParser = object(TSGMLParser)
+      procedure   DocSoftBreak; virtual;
+      procedure   DocAddTextChar(C: char); virtual;
+      procedure   DocProcessTag(Tag: string); virtual;
+      function    DocGetTagParam(Name: string; var Value: string): boolean; virtual;
+      procedure   DocProcessComment(Comment: string); virtual;
+      function    DocDecodeNamedEntity(Name: string; var E: string): boolean; virtual;
+    public
+      TagName,TagParams: string;
+      procedure   DocUnknownTag; virtual;
+      procedure   DocTYPE; virtual;
+      procedure   DocHTML(Entered: boolean); virtual;
+      procedure   DocHEAD(Entered: boolean); virtual;
+      procedure   DocMETA; virtual;
+      procedure   DocTITLE(Entered: boolean); virtual;
+      procedure   DocBODY(Entered: boolean); virtual;
+      procedure   DocAnchor(Entered: boolean); virtual;
+      procedure   DocHeading(Level: integer; Entered: boolean); virtual;
+      procedure   DocParagraph(Entered: boolean); virtual;
+      procedure   DocBreak; virtual;
+      procedure   DocImage; virtual;
+      procedure   DocBold(Entered: boolean); virtual;
+      procedure   DocCite(Entered: boolean); virtual;
+      procedure   DocCode(Entered: boolean); virtual;
+      procedure   DocEmphasized(Entered: boolean); virtual;
+      procedure   DocItalic(Entered: boolean); virtual;
+      procedure   DocKbd(Entered: boolean); virtual;
+      procedure   DocPreformatted(Entered: boolean); virtual;
+      procedure   DocSample(Entered: boolean); virtual;
+      procedure   DocStrong(Entered: boolean); virtual;
+      procedure   DocTeleType(Entered: boolean); virtual;
+      procedure   DocVariable(Entered: boolean); virtual;
+      procedure   DocList(Entered: boolean); virtual;
+      procedure   DocOrderedList(Entered: boolean); virtual;
+      procedure   DocListItem; virtual;
+      procedure   DocDefList(Entered: boolean); virtual;
+      procedure   DocDefTerm; virtual;
+      procedure   DocDefExp; virtual;
+      procedure   DocHorizontalRuler; virtual;
+    end;
+
+implementation
+
+function UpcaseStr(S: string): string;
+var I: Longint;
+begin
+  for I:=1 to length(S) do
+      S[I]:=Upcase(S[I]);
+  UpcaseStr:=S;
+end;
+
+function LowCase(C: char): char;
+begin
+  if ('A'<=C) and (C<='Z') then C:=chr(ord(C)+32);
+  LowCase:=C;
+end;
+
+function LowcaseStr(S: string): string;
+var I: Longint;
+begin
+  for I:=1 to length(S) do
+      S[I]:=Lowcase(S[I]);
+  LowcaseStr:=S;
+end;
+
+function LTrim(S: string): string;
+begin
+  while copy(S,1,1)=' ' do Delete(S,1,1);
+  LTrim:=S;
+end;
+
+function RTrim(S: string): string;
+begin
+  while copy(S,length(S),1)=' ' do Delete(S,length(S),1);
+  RTrim:=S;
+end;
+
+function Trim(S: string): string;
+begin
+  Trim:=RTrim(LTrim(S));
+end;
+
+function TTextFile.GetLine(Idx: sw_integer; var S: string): boolean;
+begin
+  Abstract;
+end;
+
+constructor TDOSTextFile.Init(AFileName: string);
+{$ifdef TPUNIXLF}
+  procedure readln(var t:text;var s:string);
+  var
+    c : char;
+    i : longint;
+  begin
+    c:=#0;
+    i:=0;
+    while (not eof(t)) and (c<>#10) and (i<255) do
+     begin
+       read(t,c);
+       if (i<255) and (c<>#10) then
+   begin
+     inc(i);
+     s[i]:=c;
+   end;
+     end;
+    if (i>0) and (s[i]=#13) then
+       dec(i);
+    s[0]:=chr(i);
+   end;
+{$endif}
+var f: text;
+    S: string;
+begin
+  inherited Init;
+{$I-}
+  Assign(f,AFileName);
+  Reset(f);
+  if IOResult<>0 then Fail;
+  New(Lines, Init(500,2000));
+  while (Eof(f)=false) and (IOResult=0) do
+    begin
+      readln(f,S);
+      Lines^.Insert(NewStr(S));
+    end;
+  Close(f);
+{$I+}
+end;
+
+function TDOSTextFile.GetLine(Idx: sw_integer; var S: string): boolean;
+var OK: boolean;
+    PS: PString;
+begin
+  OK:=(Lines<>nil) and (Idx<Lines^.Count);
+  if OK then
+    begin
+      PS:=Lines^.At(Idx);
+      if PS=nil then S:='' else S:=PS^;
+    end;
+  GetLine:=OK;
+end;
+
+destructor TDOSTextFile.Done;
+begin
+  inherited Done;
+  if Lines<>nil then Dispose(Lines, Done); Lines:=nil;
+end;
+
+constructor TSGMLParser.Init;
+begin
+  inherited Init;
+end;
+
+function TSGMLParser.Process(HTMLFile: PTextFile): boolean;
+var S: string;
+    OK,LineOK: boolean;
+begin
+  if HTMLFile=nil then Exit;
+  InTag:=false; InComment:=false; InString:=false; CurTag:='';
+  Line:=0; OK:=true;
+  repeat
+    LineOK:=HTMLFile^.GetLine(Line,S);
+    if LineOK then
+      begin
+        OK:=ProcessLine(S);
+        Inc(Line);
+      end;
+  until (LineOK=false) or (OK=false);
+  Process:=OK;
+end;
+
+function TSGMLParser.ProcessLine(LineText: string): boolean;
+var OK: boolean;
+    C: char;
+    NewInString: boolean;
+    OldInComment: boolean;
+    WasThereAnyText: boolean;
+    Pos2: integer;
+    Name,Entity: string;
+    LiteralCode: boolean;
+    LiteralStart,LiteralEnd: integer;
+begin
+  WasThereAnyText:=false;
+  OK:=true; LinePos:=1;
+  LiteralStart:=0; LiteralEnd:=0;
+  while (LinePos<=length(LineText)) and OK do
+    begin
+      LiteralCode:=false;
+      NewInString:=InString; OldInComment:=InComment;
+      C:=LineText[LinePos];
+
+      LiteralCode:=(LiteralStart<=LinePos) and (LinePos<=LiteralEnd);
+
+      if (LiteralCode=false) and (C='&') then
+        begin
+          LiteralStart:=0; LiteralEnd:=0;
+          Name:=''; Pos2:=LinePos+1;
+          while (Pos2<=length(LineText)) and (LineText[Pos2]<>';') do
+            begin
+              Name:=Name+LineText[Pos2];
+              Inc(Pos2);
+            end;
+          Inc(Pos2);
+          if DocDecodeNamedEntity(Name,Entity) then
+            begin
+              LineText:=copy(LineText,1,LinePos-1)+Entity+copy(LineText,Pos2,255);
+              LiteralStart:=LinePos; LiteralEnd:=LiteralStart+length(Entity)-1;
+              C:=LineText[LinePos];
+            end;
+        end;
+
+      LiteralCode:=(LiteralStart<=LinePos) and (LinePos<=LiteralEnd);
+
+      if (LiteralCode=false) and (C='"') and (InTag=true) and (InString=false) then
+        NewInString:=true;
+      if (LiteralCode=false) and (C='<') and (InTag=false) then
+        InTag:=true;
+
+      if InTag then CurTag:=CurTag+C else
+        begin
+          DocAddTextChar(C);
+          WasThereAnyText:=true;
+        end;
+      if (LiteralCode=false) and InTag and (InString=false) and (CurTag='<!--') then
+        InComment:=true;
+      if (LiteralCode=false) and InTag and InComment and (InString=false) and (length(CurTag)>=3) and
+         (copy(CurTag,length(CurTag)-2,3)='-->') then
+           InComment:=false;
+
+      if (LiteralCode=false) and (C='"') and (InTag=true) and (InString=true) then
+        NewInString:=false;
+      if (LiteralCode=false) and (C='>') and (InTag=true) then
+        begin
+          InTag:=false;
+          if OldInComment then
+            DocProcessComment(CurTag)
+          else
+            DocProcessTag(CurTag);
+          CurTag:='';
+        end;
+
+      InString:=NewInString;
+      Inc(LinePos);
+
+    end;
+  if WasThereAnyText then DocSoftBreak;
+end;
+
+procedure TSGMLParser.DocSoftBreak;
+begin
+  Abstract;
+end;
+
+procedure TSGMLParser.DocAddTextChar(C: char);
+begin
+  Abstract;
+end;
+
+procedure TSGMLParser.DocAddText(S: string);
+var I: sw_integer;
+begin
+  for I:=1 to length(S) do
+    DocAddTextChar(S[I]);
+end;
+
+function TSGMLParser.DocDecodeNamedEntity(Name: string; var Entity: string): boolean;
+begin
+  DocDecodeNamedEntity:=false;
+end;
+
+procedure TSGMLParser.DocProcessTag(Tag: string);
+begin
+  Abstract;
+end;
+
+procedure TSGMLParser.DocProcessComment(Comment: string);
+begin
+  Abstract;
+end;
+
+destructor TSGMLParser.Done;
+begin
+  inherited Done;
+end;
+
+procedure THTMLParser.DocSoftBreak;
+begin
+end;
+
+procedure THTMLParser.DocAddTextChar(C: char);
+begin
+end;
+
+function THTMLParser.DocDecodeNamedEntity(Name: string; var E: string): boolean;
+var Found: boolean;
+    Code: integer;
+    CC: integer;
+begin
+  Found:=true; Code:=-1;
+  Name:=LowCaseStr(Name);
+  if copy(Name,1,1)='#' then
+    begin
+      Val(copy(Name,2,255),Code,CC);
+      if CC<>0 then Code:=-1;
+    end;
+  if               (Name='lt')     then E:='<'   else { less-than sign                }
+  if               (Name='gt')     then E:='>'   else { greater-than sign              }
+  if               (Name='amp')    then E:='&'   else { ampersand                     }
+  if               (Name='quot')   then E:='"'   else { double quote sign             }
+  if (Code=160) or (Name='nbsp')   then E:=#255  else { no-break space                }
+  if (Code=161) or (Name='iexcl')  then E:='­'   else { inverted excalamation mark    }
+  if (Code=162) or (Name='cent')   then E:='›'   else { cent sign                     }
+  if (Code=163) or (Name='pound')  then E:='œ'   else { pound sterling sign           }
+  if (Code=164) or (Name='curren') then E:='$'   else { general currency sign         }
+  if (Code=165) or (Name='yen')    then E:='�'   else { yen sign                      }
+  if (Code=166) or (Name='brvbar') then E:='|'   else { broken vertical bar           }
+(*  if (Code=167) or (Name='sect')   then E:=#255  else { section sign                  }*)
+(*  if (Code=168) or (Name='uml')    then E:=#255  else { umlaut  (dieresis)            }*)
+  if (Code=169) or (Name='copy')   then E:='(C)' else { copyright sign                }
+(*  if (Code=170) or (Name='ordf')   then E:=#255  else { ordinal indicator, feminine   }*)
+  if (Code=171) or (Name='laquo')  then E:='"'   else { angle quotation mark -left    }
+  if (Code=172) or (Name='not')    then E:='!'   else { not sign                      }
+  if (Code=173) or (Name='shy')    then E:='-'   else { soft hypen                    }
+  if (Code=174) or (Name='reg')    then E:='(R)' else { registered sign               }
+(*  if (Code=175) or (Name='macr')   then E:='?'   else { macron                        }*)
+  if (Code=176) or (Name='deg')    then E:='ø'   else { degree sign                   }
+  if (Code=177) or (Name='plusmn') then E:='ñ'   else { plus-or-minus sign            }
+  if (Code=178) or (Name='sup2')   then E:='ý'   else { superscript 2                 }
+  if (Code=179) or (Name='sup3')   then E:='^3'  else { superscript 3                 }
+  if (Code=180) or (Name='acute')  then E:=''''  else { acute accent                  }
+  if (Code=181) or (Name='micro')  then E:='æ'   else { micro sign                    }
+(*  if (Code=182) or (Name='para')   then E:='?'   else { paragraph sign                }*)
+  if (Code=183) or (Name='middot') then E:='ù'   else { middle dot                    }
+(*  if (Code=184) or (Name='cedil')  then E:='?'   else { cedilla                       }*)
+  if (Code=185) or (Name='sup1')   then E:='^1'  else { superscript 1                 }
+(*  if (Code=186) or (Name='ordm')   then E:='?'   else { ordinal indicator, masculine  }*)
+  if (Code=187) or (Name='raquo')  then E:='"'   else { angle quoatation mark -right  }
+  if (Code=188) or (Name='frac14') then E:='¬'   else { fraction one-quarter          }
+  if (Code=189) or (Name='frac12') then E:='«'   else { fraction one-half             }
+  if (Code=190) or (Name='frac34') then E:='3/4' else { fraction three-quarters       }
+  if (Code=191) or (Name='iquest') then E:='¨'   else { inverted question mark        }
+  if (Code=192) or (Name='Agrave') then E:='A'   else { capital A, grave accent       }
+  if (Code=193) or (Name='Aacute') then E:='A'   else { capital A, acute accent       }
+  if (Code=194) or (Name='Acirc')  then E:='A'   else { capital A, circumflex accent  }
+  if (Code=195) or (Name='Atilde') then E:='A'   else { capital A, tilde accent       }
+  if (Code=196) or (Name='Auml')   then E:='Ž'   else { capital A, dieresis or umlaut }
+  if (Code=197) or (Name='Aring')  then E:='�'   else { capital A, ring               }
+  if (Code=198) or (Name='AElig')  then E:='AE'  else { capital AE diphthong          }
+(*  if (Code=199) or (Name='Ccedil') then E:='?'   else { capital C, cedilla            }*)
+  if (Code=200) or (Name='Egrave') then E:='�'   else { capital E, grave accent       }
+  if (Code=201) or (Name='Eacute') then E:='�'   else { capital E, acute accent       }
+  if (Code=202) or (Name='Ecirc')  then E:='E'   else { capital E, circumflex accent  }
+  if (Code=203) or (Name='Euml')   then E:='E'   else { capital E, dieresis or umlaut }
+  if (Code=204) or (Name='Igrave') then E:='I'   else { capital I, grave accent       }
+  if (Code=205) or (Name='Iacute') then E:='I'   else { capital I, acute accent       }
+  if (Code=206) or (Name='Icirc')  then E:='I'   else { capital I, circumflex accent  }
+  if (Code=207) or (Name='Iuml')   then E:='I'   else { capital I, dieresis or umlaut }
+(*  if (Code=208) or (Name='ETH')    then E:='?'   else { capital Eth, Icelandic        }*)
+  if (Code=209) or (Name='Ntidle') then E:='¥'   else { capital N, tilde              }
+  if (Code=210) or (Name='Ograve') then E:='O'   else { capital O, grave accent       }
+  if (Code=211) or (Name='Oacute') then E:='O'   else { capital O, acute accent       }
+  if (Code=212) or (Name='Ocirc')  then E:='O'   else { capital O, circumflex accent  }
+  if (Code=213) or (Name='Otilde') then E:='O'   else { capital O, tilde              }
+  if (Code=214) or (Name='Ouml')   then E:='™'   else { capital O, dieresis or umlaut }
+  if (Code=215) or (Name='times')  then E:='*'   else { multiply sign                 }
+  if (Code=216) or (Name='Oslash') then E:='O'   else { capital O, slash              }
+  if (Code=217) or (Name='Ugrave') then E:='U'   else { capital U, grave accent       }
+  if (Code=218) or (Name='Uacute') then E:='U'   else { capital U, acute accent       }
+  if (Code=219) or (Name='Ucirc')  then E:='U'   else { capital U, circumflex accent  }
+  if (Code=220) or (Name='Uuml')   then E:='š'   else { capital U, dieresis or umlaut }
+  if (Code=221) or (Name='Yacute') then E:='Y'   else { capital Y, acute accent       }
+(*  if (Code=222) or (Name='THORN')  then E:='?'   else { capital THORN, Icelandic      }*)
+  if (Code=223) or (Name='szlig')  then E:='á'   else { small sharp S, German         }
+  if (Code=224) or (Name='agrave') then E:='…'   else { small a, grave accent         }
+  if (Code=225) or (Name='aacute') then E:=' '   else { small a, acute accent         }
+  if (Code=226) or (Name='acirc')  then E:='ƒ'   else { small a, circumflex accent    }
+  if (Code=227) or (Name='atilde') then E:='ƒ'   else { small a, tilde                }
+  if (Code=228) or (Name='auml')   then E:='„'   else { small a, dieresis or umlaut   }
+  if (Code=229) or (Name='aring')  then E:='†'   else { small a, ring                 }
+  if (Code=230) or (Name='aelig')  then E:='ae'  else { small ae, diphthong           }
+(*  if (Code=231) or (Name='ccedil') then E:='?'   else { small c, cedilla              }*)
+  if (Code=232) or (Name='egrave') then E:='Š'   else { small e, grave accent         }
+  if (Code=233) or (Name='eacute') then E:='‚'   else { small e, acute accent         }
+  if (Code=234) or (Name='ecirc')  then E:='ˆ'   else { small e, circumflex accent    }
+  if (Code=235) or (Name='euml')   then E:='‰'   else { small e, dieresis or umlaut   }
+  if (Code=236) or (Name='igrave') then E:='�'   else { small i, grave accent         }
+  if (Code=237) or (Name='iacute') then E:='¡'   else { small i, acute accent         }
+  if (Code=238) or (Name='icirc')  then E:='Œ'   else { small i, circumflex accent    }
+  if (Code=239) or (Name='iuml')   then E:='‹'   else { small i, dieresis or umlaut   }
+(*  if (Code=240) or (Name='eth')    then E:='?'   else { small eth, Icelandic          }*)
+  if (Code=241) or (Name='ntilde') then E:='¤'   else { small n, tilde                }
+  if (Code=242) or (Name='ograve') then E:='•'   else { small o, grave accent         }
+  if (Code=243) or (Name='oacute') then E:='¢'   else { small o, acute accent         }
+  if (Code=244) or (Name='ocirc')  then E:='“'   else { small o, circumflex accent    }
+  if (Code=245) or (Name='otilde') then E:='“'   else { small o, tilde                }
+  if (Code=246) or (Name='ouml')   then E:='”'   else { small o, dieresis or umlaut   }
+  if (Code=247) or (Name='divide') then E:='/'   else { divide sign                   }
+  if (Code=248) or (Name='oslash') then E:='"'   else { small o, slash                }
+  if (Code=249) or (Name='ugrave') then E:='—'   else { small u, grave accent         }
+  if (Code=250) or (Name='uacute') then E:='£'   else { small u, acute accent         }
+  if (Code=251) or (Name='ucirc')  then E:='–'   else { small u, circumflex accent    }
+  if (Code=252) or (Name='uuml')   then E:='�'   else { small u, dieresis or umlaut   }
+  if (Code=253) or (Name='yacute') then E:='y'   else { small y, acute accent         }
+(*  if (Code=254) or (Name='thorn')  then E:='?'   else { small thorn, Icelandic        }*)
+  if (Code=255) or (Name='yuml')   then E:='y'   else { small y, dieresis or umlaut   }
+  Found:=false;
+  DocDecodeNamedEntity:=Found;
+end;
+
+procedure THTMLParser.DocProcessTag(Tag: string);
+var UTagName,ETagName: string[30];
+    P: byte;
+    NotEndTag: boolean;
+begin
+  if copy(Tag,1,1)='<' then Delete(Tag,1,1);
+  if copy(Tag,length(Tag),1)='>' then Delete(Tag,length(Tag),1);
+  Tag:=Trim(Tag);
+  P:=Pos(' ',Tag); if P=0 then P:=length(Tag)+1;
+  TagName:=copy(Tag,1,P-1); TagParams:=copy(Tag,P+1,255);
+  UTagName:=UpcaseStr(TagName);
+  NotEndTag:=copy(TagName,1,1)<>'/';
+  if NotEndTag then ETagName:=UTagName else ETagName:=copy(UTagName,2,255);
+
+  if (UTagName='!DOCTYPE') then DocTYPE else
+  { Section tags }
+  if (ETagName='HTML') then DocHTML(NotEndTag) else
+  if (ETagName='HEAD') then DocHEAD(NotEndTag) else
+  if (ETagName='TITLE') then DocTITLE(NotEndTag) else
+  if (ETagName='BODY') then DocBODY(NotEndTag) else
+  { Anchor tags }
+  if (ETagName='A') then DocAnchor(NotEndTag) else
+  { Direct formatting directives }
+  if (ETagName='H1') then DocHeading(1,NotEndTag) else
+  if (ETagName='H2') then DocHeading(2,NotEndTag) else
+  if (ETagName='H3') then DocHeading(3,NotEndTag) else
+  if (ETagName='H4') then DocHeading(4,NotEndTag) else
+  if (ETagName='H5') then DocHeading(5,NotEndTag) else
+  if (ETagName='H6') then DocHeading(6,NotEndTag) else
+  if (ETagName='P') then DocParagraph(NotEndTag) else
+  if (ETagName='BR') then DocBreak else
+  if (ETagName='B') then DocBold(NotEndTag) else
+  if (ETagName='CITE') then DocCite(NotEndTag) else
+  if (ETagName='CODE') then DocCode(NotEndTag) else
+  if (ETagName='EM') then DocEmphasized(NotEndTag) else
+  if (ETagName='I') then DocItalic(NotEndTag) else
+  if (ETagName='KBD') then DocKbd(NotEndTag) else
+  if (ETagName='PRE') then DocPreformatted(NotEndTag) else
+  if (ETagName='SAMP') then DocSample(NotEndTag) else
+  if (ETagName='STRONG') then DocStrong(NotEndTag) else
+  if (ETagName='TT') then DocTeleType(NotEndTag) else
+  if (ETagName='VAR') then DocVariable(NotEndTag) else
+  { Unordered & ordered lists }
+  if (ETagName='UL') then DocList(NotEndTag) else
+  if (ETagName='OL') then DocOrderedList(NotEndTag) else
+  if (UTagName='LI') then DocListItem else
+  { Definition list }
+  if (ETagName='DL') then DocDefList(NotEndTag) else
+  if (UTagName='DT') then DocDefTerm else
+  if (UTagName='DD') then DocDefExp else
+  { Misc. tags }
+  if (UTagName='META') then DocMETA else
+  if (UTagName='IMG') then DocImage else
+  if (UTagName='HR') then DocHorizontalRuler else
+  DocUnknownTag;
+end;
+
+function THTMLParser.DocGetTagParam(Name: string; var Value: string): boolean;
+var Found: boolean;
+    S: string;
+    ParamName,ParamValue: string;
+    InStr: boolean;
+    I: sw_integer;
+begin
+  Found:=false; Name:=UpcaseStr(Name);
+  S:=TagParams;
+  repeat
+    InStr:=false;
+    ParamName:=''; ParamValue:='';
+    S:=Trim(S); I:=1;
+    while (I<=length(S)) and (S[I]<>'=') do
+      begin
+        ParamName:=ParamName+S[I];
+        Inc(I);
+      end;
+    ParamName:=Trim(ParamName);
+    if S[I]='=' then
+    begin
+      Inc(I); InStr:=false;
+      while (I<=length(S)) and (S[I]=' ') do
+        Inc(I);
+      if (I<=length(S)) and (S[I]='"') then
+        begin
+          InStr:=true;
+          Inc(I);
+        end;
+      while (I<=length(S)) and ((InStr=true) or (S[I]<>' ')) do
+        begin
+          if S[I]='"' then
+            begin
+              InStr:=not InStr;
+              if InStr=false then Break;
+            end
+          else
+            ParamValue:=ParamValue+S[I];
+          Inc(I);
+        end;
+    end;
+    Found:=(Name=UpcaseStr(ParamName));
+    if Found then Value:=ParamValue;
+    Delete(S,1,I);
+  until Found or (S='');
+  DocGetTagParam:=Found;
+end;
+
+procedure THTMLParser.DocProcessComment(Comment: string);
+begin
+end;
+
+procedure THTMLParser.DocUnknownTag;
+begin
+end;
+
+procedure THTMLParser.DocTYPE;
+begin
+end;
+
+procedure THTMLParser.DocHTML(Entered: boolean);
+begin
+end;
+
+procedure THTMLParser.DocHEAD(Entered: boolean);
+begin
+end;
+
+procedure THTMLParser.DocMETA;
+begin
+end;
+
+procedure THTMLParser.DocTITLE(Entered: boolean);
+begin
+end;
+
+procedure THTMLParser.DocBODY(Entered: boolean);
+begin
+end;
+
+procedure THTMLParser.DocAnchor(Entered: boolean);
+begin
+end;
+
+procedure THTMLParser.DocHeading(Level: integer; Entered: boolean);
+begin
+end;
+
+procedure THTMLParser.DocParagraph(Entered: boolean);
+begin
+end;
+
+procedure THTMLParser.DocBreak;
+begin
+end;
+
+procedure THTMLParser.DocImage;
+begin
+end;
+
+procedure THTMLParser.DocBold(Entered: boolean);
+begin
+end;
+
+procedure THTMLParser.DocCite(Entered: boolean);
+begin
+end;
+
+procedure THTMLParser.DocCode(Entered: boolean);
+begin
+end;
+
+procedure THTMLParser.DocEmphasized(Entered: boolean);
+begin
+end;
+
+procedure THTMLParser.DocItalic(Entered: boolean);
+begin
+end;
+
+procedure THTMLParser.DocKbd(Entered: boolean);
+begin
+end;
+
+procedure THTMLParser.DocPreformatted(Entered: boolean);
+begin
+end;
+
+procedure THTMLParser.DocSample(Entered: boolean);
+begin
+end;
+
+procedure THTMLParser.DocStrong(Entered: boolean);
+begin
+end;
+
+procedure THTMLParser.DocTeleType(Entered: boolean);
+begin
+end;
+
+procedure THTMLParser.DocVariable(Entered: boolean);
+begin
+end;
+
+procedure THTMLParser.DocList(Entered: boolean);
+begin
+end;
+
+procedure THTMLParser.DocOrderedList(Entered: boolean);
+begin
+end;
+
+procedure THTMLParser.DocListItem;
+begin
+end;
+
+procedure THTMLParser.DocDefList(Entered: boolean);
+begin
+end;
+
+procedure THTMLParser.DocDefTerm;
+begin
+end;
+
+procedure THTMLParser.DocDefExp;
+begin
+end;
+
+procedure THTMLParser.DocHorizontalRuler;
+begin
+end;
+
+
+
+END.

+ 585 - 0
ide/text/whtmlhlp.pas

@@ -0,0 +1,585 @@
+unit WHTMLHlp;
+
+interface
+
+uses Objects,WHTML,WHelp;
+
+const
+     ListIndent = 2;
+     DefIndent  = 4;
+
+     MaxTopicLinks = 100;
+
+type
+    PTopicLinkCollection = ^TTopicLinkCollection;
+    TTopicLinkCollection = object(TStringCollection)
+      procedure Insert(Item: Pointer); virtual;
+      function  At(Index: sw_Integer): PString;
+      function  AddItem(Item: string): integer;
+    end;
+
+    TParagraphAlign = (paLeft,paCenter,paRight);
+
+    PHTMLTopicRenderer = ^THTMLTopicRenderer;
+    THTMLTopicRenderer = object(THTMLParser)
+      function  BuildTopic(P: PTopic; HTMLFile: PTextFile; ATopicLinks: PTopicLinkCollection): boolean;
+    public
+      procedure DocAddTextChar(C: char); virtual;
+      procedure DocSoftBreak; virtual;
+      procedure DocTYPE; virtual;
+      procedure DocHTML(Entered: boolean); virtual;
+      procedure DocHEAD(Entered: boolean); virtual;
+      procedure DocMETA; virtual;
+      procedure DocTITLE(Entered: boolean); virtual;
+      procedure DocBODY(Entered: boolean); virtual;
+      procedure DocAnchor(Entered: boolean); virtual;
+      procedure DocHeading(Level: integer; Entered: boolean); virtual;
+      procedure DocParagraph(Entered: boolean); virtual;
+      procedure DocBreak; virtual;
+      procedure DocImage; virtual;
+      procedure DocBold(Entered: boolean); virtual;
+      procedure DocCite(Entered: boolean); virtual;
+      procedure DocCode(Entered: boolean); virtual;
+      procedure DocEmphasized(Entered: boolean); virtual;
+      procedure DocItalic(Entered: boolean); virtual;
+      procedure DocKbd(Entered: boolean); virtual;
+      procedure DocPreformatted(Entered: boolean); virtual;
+      procedure DocSample(Entered: boolean); virtual;
+      procedure DocStrong(Entered: boolean); virtual;
+      procedure DocTeleType(Entered: boolean); virtual;
+      procedure DocVariable(Entered: boolean); virtual;
+      procedure DocList(Entered: boolean); virtual;
+      procedure DocOrderedList(Entered: boolean); virtual;
+      procedure DocListItem; virtual;
+      procedure DocDefList(Entered: boolean); virtual;
+      procedure DocDefTerm; virtual;
+      procedure DocDefExp; virtual;
+      procedure DocHorizontalRuler; virtual;
+    private
+      Topic: PTopic;
+      TopicLinks: PTopicLinkCollection;
+      TextPtr: word;
+      InTitle: boolean;
+      InBody: boolean;
+      InAnchor: boolean;
+      InParagraph: boolean;
+      InPreformatted: boolean;
+      TopicTitle: string;
+      Indent: integer;
+      AnyCharsInLine: boolean;
+      CurHeadLevel: integer;
+      PAlign: TParagraphAlign;
+      LinkIndexes: array[0..MaxTopicLinks] of sw_integer;
+      LinkPtr: sw_integer;
+{      Anchor: TAnchor;}
+      procedure AddText(S: string);
+      procedure AddChar(C: char);
+    end;
+
+    PHTMLHelpFile = ^THTMLHelpFile;
+    THTMLHelpFile = object(THelpFile)
+      constructor Init(AFileName: string; AID: word; ATOCEntry: string);
+      destructor  Done; virtual;
+    public
+      function    LoadIndex: boolean; virtual;
+      function    SearchTopic(HelpCtx: THelpCtx): PTopic; virtual;
+      function    ReadTopic(T: PTopic): boolean; virtual;
+    private
+      Renderer: PHTMLTopicRenderer;
+      FileName: string;
+      CurFileName: string;
+      TOCEntry: string;
+      TopicLinks: PTopicLinkCollection;
+    end;
+
+implementation
+
+uses Dos;
+
+const
+{$ifdef LINUX}
+  dirsep = '/';
+{$else}
+  dirsep = '\';
+{$endif}
+
+function FormatPath(Path: string): string;
+var P: sw_integer;
+begin
+  repeat
+    if DirSep='/' then P:=Pos('\',Path)
+                  else P:=Pos('/',Path);
+    if P>0 then Path[P]:=DirSep;
+  until P=0;
+  FormatPath:=Path;
+end;
+
+function UpcaseStr(S: string): string;
+var I: integer;
+begin
+  for I:=1 to length(S) do
+      S[I]:=Upcase(S[I]);
+  UpcaseStr:=S;
+end;
+
+function EncodeHTMLCtx(FileID: integer; LinkNo: word): longint;
+var Ctx: longint;
+begin
+  Ctx:=(longint(FileID) shl 16)+LinkNo;
+  EncodeHTMLCtx:=Ctx;
+end;
+
+procedure DecodeHTMLCtx(Ctx: longint; var FileID: word; var LinkNo: word);
+begin
+  if (Ctx shr 16)=0 then
+    begin
+      FileID:=$ffff; LinkNo:=0;
+    end
+  else
+    begin
+      FileID:=Ctx shr 16; LinkNo:=Ctx and $ffff;
+    end;
+end;
+
+function CharStr(C: char; Count: byte): string;
+var S: string;
+begin
+  S[0]:=chr(Count);
+  if Count>0 then FillChar(S[1],Count,C);
+  CharStr:=S;
+end;
+
+procedure TTopicLinkCollection.Insert(Item: Pointer);
+begin
+  AtInsert(Count,Item);
+end;
+
+function TTopicLinkCollection.At(Index: Integer): PString;
+begin
+  At:=inherited At(Index);
+end;
+
+function TTopicLinkCollection.AddItem(Item: string): integer;
+var Idx: integer;
+begin
+  if Item='' then Idx:=-1 else
+  if Search(@Item,Idx)=false then
+    begin
+      AtInsert(Count,NewStr(Item));
+      Idx:=Count-1;
+    end;
+  AddItem:=Idx;
+end;
+
+procedure THTMLTopicRenderer.DocAddTextChar(C: char);
+begin
+  if InTitle then TopicTitle:=TopicTitle+C else
+  if InBody then
+    begin
+      if (C<>#32) or (AnyCharsInLine=true) then AddChar(C);
+    end;
+end;
+
+procedure THTMLTopicRenderer.DocSoftBreak;
+begin
+  if InPreformatted then DocBreak else
+  if AnyCharsInLine then AddChar(' ');
+end;
+
+procedure THTMLTopicRenderer.DocTYPE;
+begin
+end;
+
+procedure THTMLTopicRenderer.DocHTML(Entered: boolean);
+begin
+end;
+
+procedure THTMLTopicRenderer.DocHEAD(Entered: boolean);
+begin
+end;
+
+procedure THTMLTopicRenderer.DocMETA;
+begin
+end;
+
+procedure THTMLTopicRenderer.DocTITLE(Entered: boolean);
+begin
+  if Entered then
+    begin
+      TopicTitle:='';
+    end
+  else
+    begin
+      { render topic title here }
+      if TopicTitle<>'' then
+        begin
+          AddText('  '+TopicTitle+' Ü'); DocBreak;
+          AddText(' '+CharStr('ß',length(TopicTitle)+3)); DocBreak;
+        end;
+    end;
+  InTitle:=Entered;
+end;
+
+procedure THTMLTopicRenderer.DocBODY(Entered: boolean);
+begin
+  InBody:=Entered;
+end;
+
+procedure THTMLTopicRenderer.DocAnchor(Entered: boolean);
+var HRef: string;
+begin
+  if Entered and InAnchor then DocAnchor(false);
+  if Entered then
+    begin
+      if DocGetTagParam('HREF',HRef)=false then HRef:='';
+      if (HRef<>'') and (copy(HRef,1,1)<>'#') then
+        begin
+          InAnchor:=true;
+          AddChar(hscLink);
+          LinkIndexes[LinkPtr]:=TopicLinks^.AddItem(HRef);
+          Inc(LinkPtr);
+        end;
+    end
+  else
+    begin
+      if InAnchor=true then AddChar(hscLink);
+      InAnchor:=false;
+    end;
+end;
+
+procedure DecodeAlign(Align: string; var PAlign: TParagraphAlign);
+begin
+  Align:=UpcaseStr(Align);
+  if Align='LEFT' then PAlign:=paLeft else
+  if Align='CENTER' then PAlign:=paCenter else
+  if Align='RIGHT' then PAlign:=paRight;
+end;
+
+procedure THTMLTopicRenderer.DocHeading(Level: integer; Entered: boolean);
+var Align: string;
+begin
+  if Entered then
+    begin
+      DocBreak;
+      CurHeadLevel:=Level;
+      PAlign:=paLeft;
+      if DocGetTagParam('ALIGN',Align) then
+        DecodeAlign(Align,PAlign);
+    end
+  else
+    begin
+{      if LastChar<>hscLineBreak then AddText(hscLineBreak);}
+      CurHeadLevel:=0;
+      DocBreak;
+    end;
+end;
+
+procedure THTMLTopicRenderer.DocParagraph(Entered: boolean);
+var Align: string;
+begin
+ { if Entered and InParagraph then}
+  if Entered and InParagraph then DocParagraph(false);
+  if Entered then
+    begin
+      if AnyCharsInLine then DocBreak;
+      if DocGetTagParam('ALIGN',Align) then
+        DecodeAlign(Align,PAlign);
+    end
+  else
+    begin
+{      if AnyCharsInLine then }DocBreak;
+      PAlign:=paLeft;
+    end;
+  InParagraph:=Entered;
+end;
+
+procedure THTMLTopicRenderer.DocBreak;
+begin
+  if (CurHeadLevel=1) or (PAlign=paCenter) then
+    AddChar(hscCenter);
+  if (PAlign=paRight) then
+    AddChar(hscRight);
+  AddChar(hscLineBreak);
+  if Indent>0 then
+  AddText(CharStr(#255,Indent)+hscLineStart);
+  AnyCharsInLine:=false;
+end;
+
+procedure THTMLTopicRenderer.DocImage;
+var Alt: string;
+begin
+  if DocGetTagParam('ALT',Alt)=false then Alt:='IMG';
+  if Alt<>'' then
+    begin
+      AddText('['+Alt+']');
+    end;
+end;
+
+procedure THTMLTopicRenderer.DocBold(Entered: boolean);
+begin
+end;
+
+procedure THTMLTopicRenderer.DocCite(Entered: boolean);
+begin
+end;
+
+procedure THTMLTopicRenderer.DocCode(Entered: boolean);
+begin
+end;
+
+procedure THTMLTopicRenderer.DocEmphasized(Entered: boolean);
+begin
+end;
+
+procedure THTMLTopicRenderer.DocItalic(Entered: boolean);
+begin
+end;
+
+procedure THTMLTopicRenderer.DocKbd(Entered: boolean);
+begin
+end;
+
+procedure THTMLTopicRenderer.DocPreformatted(Entered: boolean);
+begin
+  if AnyCharsInLine then DocBreak;
+  DocBreak;
+  InPreformatted:=Entered;
+end;
+
+procedure THTMLTopicRenderer.DocSample(Entered: boolean);
+begin
+end;
+
+procedure THTMLTopicRenderer.DocStrong(Entered: boolean);
+begin
+end;
+
+procedure THTMLTopicRenderer.DocTeleType(Entered: boolean);
+begin
+end;
+
+procedure THTMLTopicRenderer.DocVariable(Entered: boolean);
+begin
+end;
+
+procedure THTMLTopicRenderer.DocList(Entered: boolean);
+begin
+  if Entered then
+    begin
+      Inc(Indent,ListIndent);
+      DocBreak;
+    end
+  else
+    begin
+      Dec(Indent,ListIndent);
+      if AnyCharsInLine then DocBreak;
+    end;
+end;
+
+procedure THTMLTopicRenderer.DocOrderedList(Entered: boolean);
+begin
+  DocList(Entered);
+end;
+
+procedure THTMLTopicRenderer.DocListItem;
+begin
+  if AnyCharsInLine then
+    DocBreak;
+  AddText('þ'+hscLineStart);
+end;
+
+procedure THTMLTopicRenderer.DocDefList(Entered: boolean);
+begin
+  if Entered then
+    begin
+{      if LastChar<>hscLineBreak then DocBreak;}
+    end
+  else
+    begin
+      if AnyCharsInLine then DocBreak;
+    end;
+end;
+
+procedure THTMLTopicRenderer.DocDefTerm;
+begin
+  DocBreak;
+end;
+
+procedure THTMLTopicRenderer.DocDefExp;
+begin
+  Inc(Indent,DefIndent);
+  DocBreak;
+  Dec(Indent,DefIndent);
+end;
+
+procedure THTMLTopicRenderer.DocHorizontalRuler;
+var OAlign: TParagraphAlign;
+begin
+  OAlign:=PAlign;
+  if AnyCharsInLine then DocBreak;
+  PAlign:=paCenter;
+  DocAddText(' '+CharStr('Ä',60)+' ');
+  DocBreak;
+  PAlign:=OAlign;
+end;
+
+procedure THTMLTopicRenderer.AddChar(C: char);
+begin
+  if Topic=nil then Exit;
+  Topic^.Text^[TextPtr]:=ord(C);
+  Inc(TextPtr);
+  if (C>#15) and (C<>' ') then
+    AnyCharsInLine:=true;
+end;
+
+procedure THTMLTopicRenderer.AddText(S: string);
+var I: sw_integer;
+begin
+  for I:=1 to length(S) do
+    AddChar(S[I]);
+end;
+
+function THTMLTopicRenderer.BuildTopic(P: PTopic; HTMLFile: PTextFile; ATopicLinks: PTopicLinkCollection): boolean;
+var OK: boolean;
+    TP: pointer;
+    I: sw_integer;
+begin
+  Topic:=P; TopicLinks:=ATopicLinks;
+  OK:=Assigned(Topic) and Assigned(HTMLFile) and Assigned(TopicLinks);
+  if OK then
+    begin
+      if (Topic^.TextSize<>0) and Assigned(Topic^.Text) then
+        begin
+          FreeMem(Topic^.Text,Topic^.TextSize);
+          Topic^.TextSize:=0; Topic^.Text:=nil;
+        end;
+      Topic^.TextSize:=MaxHelpTopicSize;
+      GetMem(Topic^.Text,Topic^.TextSize);
+
+      TopicTitle:='';
+      InTitle:=false; InBody:=false; InAnchor:=false;
+      InParagraph:=false; InPreformatted:=false;
+      Indent:=0; CurHeadLevel:=0;
+      PAlign:=paLeft;
+      TextPtr:=0; LinkPtr:=0;
+      AnyCharsInLine:=false;
+      OK:=Process(HTMLFile);
+
+      if OK then
+        begin
+          { --- topic links --- }
+          if (Topic^.Links<>nil) and (Topic^.LinkSize>0) then
+            begin
+              FreeMem(Topic^.Links,Topic^.LinkSize);
+              Topic^.Links:=nil; Topic^.LinkSize:=0; Topic^.LinkCount:=0;
+            end;
+          Topic^.LinkCount:=TopicLinks^.Count;
+          Topic^.LinkSize:=SizeOf(Topic^.Links^[0])*Topic^.LinkCount;
+          GetMem(Topic^.Links,Topic^.LinkSize);
+          for I:=0 to Topic^.LinkCount-1 do
+            begin
+              Topic^.Links^[I].FileID:=Topic^.FileID;
+              Topic^.Links^[I].Context:=EncodeHTMLCtx(Topic^.FileID,LinkIndexes[I]+1);
+            end;
+          { --- topic text --- }
+          GetMem(TP,TextPtr);
+          Move(Topic^.Text^,TP^,TextPtr);
+          FreeMem(Topic^.Text,Topic^.TextSize);
+          Topic^.Text:=TP; Topic^.TextSize:=TextPtr;
+        end
+      else
+        begin
+          DisposeTopic(Topic);
+          Topic:=nil;
+        end;
+    end;
+  BuildTopic:=OK;
+end;
+
+constructor THTMLHelpFile.Init(AFileName: string; AID: word; ATOCEntry: string);
+begin
+  inherited Init(AID);
+  FileName:=AFileName; TOCEntry:=ATOCEntry;
+  if FileName='' then Fail;
+  New(Renderer, Init);
+  New(TopicLinks, Init(50,500));
+end;
+
+function THTMLHelpFile.LoadIndex: boolean;
+begin
+  IndexEntries^.Insert(NewIndexEntry(TOCEntry,ID,0));
+  LoadIndex:=true;
+end;
+
+function THTMLHelpFile.SearchTopic(HelpCtx: THelpCtx): PTopic;
+function MatchCtx(P: PTopic): boolean; {$ifndef FPC}far;{$endif}
+begin
+  MatchCtx:=P^.HelpCtx=HelpCtx;
+end;
+var FileID,LinkNo: word;
+    P: PTopic;
+begin
+  DecodeHTMLCtx(HelpCtx,FileID,LinkNo);
+  if (HelpCtx<>0) and (FileID<>ID) then P:=nil else
+  if (FileID=ID) and (LinkNo>TopicLinks^.Count) then P:=nil else
+    begin
+      P:=Topics^.FirstThat(@MatchCtx);
+      if P=nil then
+        begin
+          P:=NewTopic(ID,HelpCtx,0);
+          Topics^.Insert(P);
+        end;
+    end;
+  SearchTopic:=P;
+end;
+
+function CompletePath(const Base: string; InComplete: string): string;
+var Drv,BDrv: string[40]; D,BD: DirStr; N,BN: NameStr; E,BE: ExtStr;
+    P: sw_integer;
+begin
+  FSplit(InComplete,D,N,E);
+  P:=Pos(':',D); if P=0 then Drv:='' else begin Drv:=copy(D,1,P); Delete(D,1,P); end;
+  FSplit(Base,BD,BN,BE);
+  P:=Pos(':',BD); if P=0 then BDrv:='' else begin BDrv:=copy(BD,1,P); Delete(BD,1,P); end;
+  if copy(D,1,1)<>'\' then
+    InComplete:=BD+D+N+E;
+  if Drv='' then
+    InComplete:=BDrv+InComplete;
+  InComplete:=FExpand(InComplete);
+  CompletePath:=InComplete;
+end;
+
+function THTMLHelpFile.ReadTopic(T: PTopic): boolean;
+var OK: boolean;
+    HTMLFile: PDOSTextFile;
+    Name: string;
+    Link: string;
+    P: sw_integer;
+begin
+  OK:=T<>nil;
+  if OK then
+    begin
+      if T^.HelpCtx=0 then Name:=FileName else
+        begin
+          Link:=TopicLinks^.At(T^.HelpCtx-1)^;
+          Link:=FormatPath(Link);
+          P:=Pos('#',Link); if P>0 then Delete(Link,P,255);
+          if CurFileName='' then Name:=Link else
+          Name:=CompletePath(CurFileName,Link);
+        end;
+      New(HTMLFile, Init(Name));
+      OK:=Renderer^.BuildTopic(T,HTMLFile,TopicLinks);
+      if OK then CurFileName:=Name;
+      if HTMLFile<>nil then Dispose(HTMLFile, Done);
+    end;
+  ReadTopic:=OK;
+end;
+
+destructor THTMLHelpFile.Done;
+begin
+  inherited Done;
+  if Renderer<>nil then Dispose(Renderer, Done);
+  if TopicLinks<>nil then Dispose(TopicLinks, Done);
+end;
+
+END.