Source file: /~heha/vt/viewers/vtw.zip/SRC/VTW.PAS

{ $A+,B-,D+,F-,G+,I+,K+,L+,N-,P-,Q-,R-,S+,T-,V+,W-,X+,Y+}
{$A+,B-,D-,F-,G+,I-,K-,L-,N-,P-,Q-,R-,S-,T-,V-,W-,X+,Y-}
{$M $2000,$8000}	{32 Kilobyte Heap sollten reichen}
program VTW;

{$R VTW.RES}
{$D Videotext für Windows V.1.22 (01/97)}

{Künftige Zielstellung ist die Dreiteilung des Projekts in:
 * VT-Seiten-Beschaffung (mit DDE und Packerei)
 * VT-Seiten-Konvertierung (Text, Grafik und LinkList)
 * Präsentation unter Windows
 * Import / Exportfilter für verschiedene Dateiformate

Dateiformat .MGZ (russischer Videotext):
 * Binärformat mit zusammenhängenden Seiten zu je 24*40=960 Bytes
 * Bit 7 nirgends gesetzt, für 7-Bit-Binärkanäle geeignet
 * Keine dedizierte Seiteninformation verfügbar
 * Aufbau Kopfzeile:
   - 7 Bytes 15h (Kennung?)
   - 1 Byte Farbbyte für Kopfzeile (02h)
   - 23 Bytes Kopfzeile, Inhalt beliebig
   - 9 Bytes Magazin-Info: xxx-yy:zz
   mit xxx=Seitennummer, yy=Unterseitennummer (01, wenn erste oder nur eine),
   zz=maximale Unterseitennummer = Anzahl Unterseiten (01 wenn nur eine)
   xxx,yy und zz sind ASCII-Zeichen (hexadezimal), '-' und ':' literal
 * Die Art der Abspeicherung der Seiteninformation ist durchaus clever,
   da der Inserter dort die Uhrzeit (8 Zeichen) sowie ein konstantes
   Farbbyte (das erste der 9 Zeichen) einbaut für eine laufende Uhr
}

uses WinTypes, WinProcs, WinDos, OWindows, OMemory,
 {OStdDlgs,} ODialogs, Strings, {Formats,} Objects, Win31,
  CommDlg, ShellApi, LZExpand, WUtils {, WinExecE};
{WinExecE macht ernsthafte Probleme mit Windows NT, in meinem Fall
war kein Abbruch möglich, und der Bootsektor der Festplatte zerschossen!}

type char4=array[0..3]of char;
const
 VTW_Name:array[0..9]of char='Videotext'#0;
{ INDIZES=2048;}	{Keine Begrenzung durch dynamische Vewaltung}
 MAGIC=$e6e8d4d6;	{'VThf' mit gesetzten Bit7}
 INDEXPP=249;		{IndexeintrΣge pro fⁿhrender Index-Seite}
 MAGIC_HS=$f3e8d4d6;	{'VThs' mit gesetzten Bit7}
 INDEXPP_HS=255;	{im h#s-Format passen mehr EintrΣge 'rein}
const
 FONTW=8;		{Angaben zum verwendeten Videotext-Font}
 FONTH=10;
 SFontW:Integer=8;
 SFontH:Integer=10;

type
 Str40=String[40];
 VTLine=array[0..39]of byte;
 tIdx=record
  case integer of
  1: (Page,Subp:word);
  2: (L:LongInt);
 end;
 tHeader=record
  case integer of
  1: (Page,Subp,CBits:word; errors:byte);
  2: (Idx: tIdx);
 end;
 tSAATrail=record
  Page10,Page1,Subp1000,Subp100,Subp10,Subp1,Page100,C1,C2,PBLF:byte;
 end;
 tHSFTrail=record	{haftmann#software und FifiSoft Schwanz}
  Acqisitor:byte;	{Wer eingelesen hat}
  MaxSubp:byte;		{nur aus der Angabe xx/yy auf der Seite entnommen}
  MsDosTime:record	{Einlesezeitpunkt, zeitzonenabhΣngig}
   case integer of
   1: (L: LongInt);
   2: (Time,Date:word);
  end;
  CRC:word;		{Prⁿfsumme ⁿber diese Seite fⁿr Fehlerkorrektur}
  Mirror:word;		{Adresse einer Spiegelseite im XMS wΣhrend des Einlesens}
  UnixTime:LongInt;	{Einlesezeitpunkt, Universal Time}
 end;
 tPage=record
  case integer of
  0: (B: array[0..$3ff]of byte);
{  1: (C: array[0..$3ff]of char);}
  2: (L: array[0..24]of VTLine;
      S: tSAATrail;
      E: tHSFTrail);
  3: (H: tHeader);
  4: (TopLeft: array[0..6]of char);
 end;
 pIdxA=^tIdxA;
 tIdxA=array[0..16380]of tIdx;	{Nach oben offen - äh, durch 64KB begrenzt}

function LongMul(F1,F2:Integer):LongInt;
 inline($5A/$58/$F7/$EA);	{pop dx; pop ax; imul dx}
function LongDiv(Z:LongInt; N:Integer):Integer;
 inline($59/$58/$5A/$F7/$F9);	{pop cx; pop ax; pop dx; idiv cx}

{***********************}
{** Objektefreie Zone **}
{***********************}

{Programmstück Unsichtbare Kindfenster verwalten
 GetWindowWord(0) liefert Seitennummer
 GetWindowWord(2) liefert Unterseitennummer}
procedure Register;
{Registrierung der Fensterklasse}
 const
  wc: TWndClass=(
   style: CS_ParentDC;
   lpfnWndProc: @DefWindowProc;
   cbClsExtra: 0;
   cbWndExtra: 4;	{Platz für Seite/Unterseite}
   hInstance: 0;
   hIcon: 0;
   hCursor: 0;
   hbrBackground: 0;
   lpszMenuName: nil;
   lpszClassName: 'HiddenWnd');
 begin
  if HPrevInst=0 then begin
   wc.hInstance:=hInstance;
   wc.hCursor:=LoadCursor(hInstance,'HAND');
   if not RegisterClass(wc) then halt(255);
  end;
 end;

function CreateInvisible(hParent:HWnd; const R: TRect; N: LongInt):HWnd;
{Ein unsichtbares Fenster erzeugen, das Fensterhandle darf verworfen werden}
 var
  Wnd: HWnd;
 begin
  Wnd:=CreateWindow('HiddenWnd',nil,
    WS_Child or WS_Visible,
    R.left,R.top,R.right-R.left,R.bottom-R.top,
    hParent,0,hInstance,nil);
  SetWindowLong(Wnd,0,N);
  CreateInvisible:=Wnd;
 end;

function IsTheInvisible(Wnd:HWnd):boolean;
 var
  S: array[0..31]of Char;
 begin
  GetClassName(Wnd,S,sizeof(S));
  IsTheInvisible:=StrIComp(S,'HiddenWnd')=0;
 end;

procedure DestroyInvisibles(hParent:HWnd);
{alle unsichtbaren Kindfenster entfernen}
 var
  Wnd,Wnd2: HWnd;
 begin
  Wnd:=GetWindow(hParent,GW_Child);
  while Wnd<>0 do begin
   Wnd2:=GetWindow(Wnd,GW_hWndNext);
   if IsTheInvisible(Wnd) then
    DestroyWindow(Wnd);
   Wnd:=Wnd2;
  end;
 end;

function AppendNew(Wnd:HWnd; X,Y,W,H: Integer; P,S:Word):HWnd;
 var
  R:TRect;
 begin
  SetRect(R,X*SFontW,Y*SFontH+20,(X+W)*SFontW,(Y+H)*SFontH+20);
  AppendNew:=CreateInvisible(Wnd,R,MakeLong(P,S));
 end;

{Reaktion auf WM_ParentNotify}
{ WM_ParentNotify:
  if wParam=WM_LButtonDown then begin
   cWnd:=ChildWindowFromPoint(hWindow,TPoint(lParam));
   if IsTheInvisible(cWnd) then begin
    SelectPage(GetWindowWord(cWnd,0),GetWindowWord(cWnd,2),EQUALPAGE);
    ...
   end;
  end;}
{*******************}
{* Linklist-Objekt *}
{*******************}
(*
type
 pRI=^tRI;		{Linklist-Speicher"objekt"}
 tRI=record
  R:TRect;
  I:TIdx;
 end;

 pLinklist=^tLinklist;
 tLinklist=object(tCollection)
  procedure FreeItem(Item: pointer); virtual;
  procedure AppendNew(left,top,width,height:integer; Page,Subp: word);
 end;

procedure tLinklist.FreeItem(Item:pointer);
 begin
  Dispose(pRI(Item));	{Speicher freigeben}
 end;

procedure tLinklist.AppendNew(left,top,width,height:integer; Page,Subp: word);
 var RI:pRI;
 begin
  New(RI);		{Zeiger auf dyn. Variable beschaffen}
  RI^.R.left:=left;		{Dynamische Variable füllen}
  RI^.R.top:=top;		{Dynamische Variable füllen}
  RI^.R.right:=left+width;	{Dynamische Variable füllen}
  RI^.R.bottom:=top+height;	{Dynamische Variable füllen}
  RI^.I.Page:=Page;
  RI^.I.Subp:=Subp;
  Insert(RI);		{in die Kollektion stellen}
 end;
*)
{********************}
{* Videotext-Objekt *}
{********************}
{ctrl0	;Bit-Speicher
;0:	Rolling Header (bei Spezialaufgabe) aus/ein
;1:	Suche nach Pseudoseiten ein/aus
;2:	Trage auch die 3stelligen Zahlen in Prädiktortabelle ein ein/aus
;3:	[Teste Rolling Header NICHT auf Senderwechsel (RTL4-Problem) ein/aus]
;4:	Lösche NICHT die Seite, bei der eine Unterseite 0 und eine <>0 gefunden wurde
;5:	Quiet on/off (0=Piepser EIN!)
;6:	Indexdatei zum VT-File NICHT schreiben e/a
;7:	Fehler beim I²C-Bus (Standard-Fehlerroutine)
;8:	Die Semaphorendatei muß am Ende geschrieben (erneuert) werden
;	(Ist bei jedem Schreibzugriff auf CurVT..Req1 zu setzen!)
;9:	Garbage Collection Mode aktiv
;10:	Einlese-Mode aktiv
;11:	Die VT-Datei ist NOCH NICHT vollständig aktualisiert (wird von VTFile_Load_Init gesetzt)
;12:	Überlauf-Effekte bei den Indizes aufgetreten
;13:	DOS-ERROR bei der Semaphorendatei aufgetreten
;14:	DOS-ERROR bei der *.VT-Datei aufgetreten
;15:	Disk Full (Schreibfehler)
ctrl1
;0:	Zeitschoner-Bit (=1 wenn der Prädiktor garantiert nichts neues hat)
;1:	Ein Kritischer Fehler (Int24) ist aufgetreten}

{const		{Suchmodi, wenn angegebene Seite nicht gefunden:}
{ EQUALPAGE=1;	{Seite und Unterseite muß stimmen}
{ NEXTPAGE=2;	{Gehe zur nächsten Seite, minimale Unterseite}
{ PREVPAGE=3;	{Gehe zur vorherigen Seite, minimale Unterseite}
{ NEXTSUB=4;	{Gehe zur nächsten Unterseite mit Wrap-Around, Seite muß stimmen}
{ PREVSUB=5;	{Gehe zur vorherigen Unterseite mit Wrap-Around, Seite muß stimmen}
{ NEXTSUBPAGE=6;{Gehe zur nächsten Unterseite, wenn nicht dann zur nächsten Seite}
{ PREVSUBPAGE=7;{Gehe zur vorherigen Unterseite, wenn nicht dann zur vorherigen Seite}
{ ADDNEXTPAGE=10;{wie NEXTPAGE, jedoch nicht die angegebene Seite nehmen}
{ SUBPREVPAGE=11;{wie PREVPAGE, jedoch nicht die angegebene Seite nehmen}
{ ADDNEXTSUB=12;{wie NEXTSUB, jedoch nicht die angegebene Unterseite nehmen}
{ SUBPREVSUB=13;{wie PREVSUB, jedoch nicht die angegebene Unterseite nehmen}
{ ADDNEXTSUBPAGE=14;{wie NEXTSUBPAGE, jedoch nicht die angegebene Seite nehmen}
{ SUBPREVSUBPAGE=15;{wie PREVSUBPAGE, jedoch nicht die angegebene Seite nehmen}

type
 TSearchMode=(Default,EqualPage,NextPage,PrevPage,
  NextSub,PrevSub,NextSubPage,PrevSubPage,
  Unused,HomePage,AddNextPage,SubPrevPage,
  AddNextSub,SubPrevSub,AddNextSubPage,SubPrevSubPage);

type
 TVT=object
  VTFile: HFile;
  VREQFile: HFile;
  VREQ: record
   CurVT: array[0..8]of char;
   GarbFlg: byte;
   ctrl0: word;
   ctrl1: word;
   FuzzySender: byte;
   FuzzyQuality: byte;
   req: array[0..7]of TIdx;		{Request-Indizes}
  end;
  IndexCnt: integer;
  Index: pIdxA;
  Header: tHeader;
  LineInfo:array[0..24]of byte;		{enthält pro Zeile Infos wie:
 fQuiz: Zeile enthält QUIZ-Code ($18)
 fBlink: Zeile enthält BLINK-Code ($08)
 fDblH: Zeile enthält doppelt hohe Zeichen ($0D)
 fMix: Zeile enthält Code zur Transparentumschaltung ($0A)
 fPrevDblH: Vorhergehende Zeile enthält doppelt hohe Zeichen, Zeile ungültig
 fTwoFonts: Zeile enthält ESC ($1B)}
  Page: TPage;
  Alter: TDateTime;
  AlterStr: array[0..63] of Char;
{als String mit max. 2 Angaben, z.B. Stunden/Minuten, z.Z. in deutsch}
  QuellZeitzone, LokaleZeitzone: Integer;
{  LinkList: pLinklist;}
  Russisch: Boolean;
  PageSize: Integer;	{Länge einer Seite, z.B. 1024 oder 960}
  constructor Init;				{Page mit einer Startseite belegen}
  function LoadFile(FileName:PChar):integer;	{Rⁿckgabe: Fehlercode oder 0 wenn fehlerfrei}
  function ReadIndex:integer;			{Rⁿckgabe: dito}
{  function MkSortedIndex:integer;		{Rueckgabe void}
  procedure CloseFile;				{Datei schlie▀en, Index l÷schen, Startseite}
  function MkBitmap(dc:HDC;Flags,Land:word;const Bereich:TRect):integer;	{mittels Windows-Funktionen Bitmap kreieren}
  function MkText(Buf:PChar;Flags,Land:word;var Bereich:TRect):integer;	{zu Text machen}
{  function ProcessPage(var Buf; Flags,Font:word; const Bereich:TRect; )}
{  function IsHot:boolean;}
   {Testet, ob Datei gerade aktualisiert wird, und liest ggf. den Index neu ein}
{  function RequestPage(Desire:TIdx):boolean;}
   {Eintrag in die VREQxxx.VTD, wenn IsHot=TRUE. Subp=FFFF wenn irrelevant}
{  function AcknowledgePage(Desire:TIdx):boolean;}
   {Liest VREQxxx.VTD aus und testet das entspr. Bit}
{  function ChooseBroadcaster:boolean;}
   {fuer eine kuenftige VTGRAB-Version, bei der dies eingebaut ist}
  procedure IndexReAlloc(NewSize: Integer);	{hier: auf lokalem Heap}
  function MkLinkList(Wnd:HWnd;Flags:word):integer;	{eine Rechteckliste erzeugen}
  function PageContain(b:byte):boolean;		{Test ob Seite dieses Zeichen enthΣlt}
  function SelectPage(DP,DS:word;SearchMode:TSearchMode):word;
  function SelectLoadPage(var Desire:TIdx; SearchMode:TSearchMode):word;
  procedure SetupPage;
  {Auswahl einer gewⁿnschten Seite aus Datei. Ist die Seite nicht verfⁿgbar, wird je
  nach angegebenen Richtungen auf- oder abwΣrts gesucht. Wurde daraufhin eine gefunden, wird
  "Sel" entsprechend gesetzt, und die Rⁿckgabe ist 0, ansonsten -1. Bei DOS-Fehlern erscheint
  ebenfalls -1}
 end;


{Videotext-Seitennummer in "gewöhnliches Format" wandeln}
function UsualPage(Page:word):word; assembler;
 asm
  mov ax,Page
  and ax,7ffh		{Obere Bits löschen}
  cmp ax,100h
  jnc @@e
  add ax,800h		{000..0FF --> 800..8FF}
@@e:
 end;

{Videotext-Index in String wandeln}
function Idx2Str(Index:tIdx):PChar;	{Nicht reentrant!}
 const
  Puffer: array[0..9]of char='';	{Statische Variable, Vorbelegung Wurst}
 begin
  Index.Page:=UsualPage(Index.Page);
  Index.Subp:=Index.Subp and $3f7f;
  wvsprintf(Puffer,'%03X/%02X',Index);
  Idx2Str:=Puffer;
 end;

{Seite auswählen, liefert Index-Nummer oder FFFF bei Fehler}
function TVT.SelectPage(DP,DS:word; SearchMode:TSearchMode): word;
 var
  i,idx: word;		{Indizes}
  BB,OO: LongInt;	{BB:=Bester Abstand, OO:=Abstand, müßte "unsigned" sein!}
  f01: word;		{Entscheidet, ob GLEICHE Seite gefunden wird}
 begin

  OO:=$ffffffff;
  BB:=OO;
  idx:=$ffff;		{ungültigen Indexeintrag vormerken}
  DP:=DP and $7FF;
  DS:=DS and $3F7F;
  f01:=0;
  if SearchMode=HomePage then begin
   SearchMode:=NextSubPage;
   DP:=$100; DS:=0;
  end;
  if Byte(SearchMode) and 8 <>0 then begin
   Inc(f01);
   Byte(SearchMode):=Byte(SearchMode) and not 8;
  end;
  if IndexCnt>0 then
   for i:=0 to IndexCnt-1 do begin
    if Index^[i].Page < $1000 then begin	{Wenn Indexeintrag gültig}
     case SearchMode of
      NEXTPAGE: OO:=MakeLong(Index^[i].Subp and $3f7f,
		Index^[i].Page and $7ff -DP-f01);
			{Vorwärts, DesiredSub verwerfen}
      PREVPAGE: OO:=MakeLong(Index^[i].Subp and $3f7f,
		DP-Index^[i].Page and $7ff -f01);
			{Rückwärts & Vorwärts, DesiredSub verwerfen}
      NEXTSUB: if Index^[i].Page and $7ff=DP then	{Gleiche Seite?}
		OO:=LongInt(Index^[i].Subp) and $3f7f-DS-f01;	{Vorwärts}
      PREVSUB: if Index^[i].Page and $7ff=DP then	{Gleiche Seite?}
		OO:=LongInt(DS)-Index^[i].Subp and $3f7f-f01;	{Rückwärts}
      NEXTSUBPAGE: OO:=MakeLong(Index^[i].Subp and $3f7f,
		Index^[i].Page and $7ff)-
		MakeLong(DS,DP)-f01;
			{Vorwärts, Abstand als LongInt}
      PREVSUBPAGE: OO:=MakeLong(DS,DP)-
		MakeLong(Index^[i].Subp and $3f7f,
		Index^[i].Page and $7ff)-f01;
			{Rückwärts, Abstand als LongInt}
      EQUALPAGE: if (DP=Index^[i].Page and $7ff) and
		(DS=Index^[i].Subp and $3f7f) then
		OO:=0;		{Nur wenn gleich den Index setzen}
     end; {case}
     asm		{ich brauche hier "unsigned long", daher ASM}
      mov ax,word [OO]; mov dx,word [OO+2];
      sub ax,word [BB]; sbb dx,word [BB+2];
      jnc @@1;		{Springe NICHT wenn BB>OO war (vorzeichenlos mit CY)}
      mov ax,word [OO];	{if OO<BB then begin BB:=OO;}
      mov word [BB],ax;
      mov ax,word [OO+2];
      mov word [BB+2],ax;
      mov ax,[i];	{idx:=i; end;}
      mov [idx],ax;
@@1: end;
    end; {if}
   end; {for}
  SelectPage:=idx;
 end; {function}


function TVT.SelectLoadPage(var Desire: tIdx; SearchMode:TSearchMode): word;
 var
  i: word;			{Lade-Index}
 begin
  if Index=nil then begin
 {  memcpy(&Page,sPagedefault[country],1024);}
   SetupPage;			{kopieren und Header extrahieren}
   SelectLoadPage:=$ffff;		{"Fehler" melden}
   exit;
  end;
  if SearchMode=Default then	{Defaultwert?}
   SearchMode:=NextSub;		{Default-Suchmodus}
  i:=SelectPage(Desire.Page,Desire.Subp,SearchMode);
  if i<>$ffff then begin	{Suche erfolgreich}
   _llSeek(VTFile,LongMul(i,sizeof(Page)),0);
   if _lRead(VTFile,@Page,PageSize)=PageSize then begin
    SetupPage;
    Desire:=Header.Idx;		{Ergebnis rückschreiben}
    Move((Idx2Str(Desire))^,Page.Topleft[1],6);	{links oben hinpinseln}
   end else begin
    i:=$ffff;
   end;
  end;
  SelectLoadPage:=i;
 end;

constructor TVT.Init;
 var hRes:THandle;		{Handle auf Resource}
  STZ: PChar;
  EC: Integer;
 begin				{Startbild als Resource}
  hRes:=LoadResource(hInstance,
   FindResource(hInstance,MakeIntResource(49),RT_RCDATA));
  Move(LockResource(hRes)^,Page,sizeof(Page));	{Resource feststellen und absaugen}
  UnlockResource(hRes); FreeResource(hRes);	{Resource verwerfen}
  IndexCnt:=0;
  Index:=nil;
  Russisch:=false;
  PageSize:=sizeof(TPage);
  Header.Page:=$100; Header.Subp:=0; Header.CBits:=$4000;	{Seite 100 Sprache deutsch}
{  New(LinkList,Init(32,32));	{Linkliste gültig machen}
{Zeitzonen-Variable auswerten. Wenn nicht vorhanden, dann Mitteleuropa
 Aufbau der Variable z.B. TZ=MET-1DST:
 3 Zeichen Zeitzonen-Code (hier nicht ausgewertet)
 x Zeichen Zeitversatz in Stunden (eigentlich eine Floatpoint-Zahl)
 y Zeichen (Rest): Sommerzeit-Information
 Standardmäßig einzulesen mit sscanf(tz,"%3.3s%f%s",a,b,c)}
  QuellZeitzone:=-1;		{Versatz in Stunden}
  STZ:=GetEnvVar('TZ');
  if STZ<>nil then begin
   Val(STZ[3],LokaleZeitzone,EC);	{Erstes falsches Zeichen finden}
   if EC>3 then STZ[3+EC]:=#0;
   Val(STZ[3],LokaleZeitzone,EC);
   if EC<>0 then LokaleZeitzone:=QuellZeitzone;
					{Default: Keine Zeitdifferenz}
  end;
 end;

procedure TVT.IndexReAlloc(NewSize: Integer);	{hier: auf lokalem Heap}
 begin
  if NewSize>$2000 then RunError;		{Nie mehr als 8K Seiten}
  if Index<>nil then FreeMem(Index,IndexCnt*sizeof(TIdx));
  IndexCnt:=NewSize;
  GetMem(Index,IndexCnt*sizeof(TIdx));
 end;

function TVT.LoadFile(FileName:PChar):integer;
 var
  Path,Name: array[0..fsPathName]of Char;
  Ext: array[0..fsExtension]of Char;
  IdxFile: HFile;
  Result: integer;
 begin
  FileSplit(FileName,Path,Name,Ext);
  if StrLen(Name)=0 then begin
   LoadFile:=-1; exit; end;			{Fehler: Kein Dateiname}
  lStrCat(Path,Name);				{Path:=Path+Name}
  IndexCnt:=0;
  Russisch:=false;
  StrECopy(StrECopy(Name,Path),'.vti');
  IdxFile:=_lOpen(Name,0);	{Path+'.VTI'}		{Name dient als Zielpuffer}
  if IdxFile<>HFile_Error then begin
   IndexReAlloc(LongDiv(_llSeek(IdxFile,0,2),sizeof(TIdx)));
   _llSeek(IdxFile,0,0);		{setzt auch IndexCnt neu}
   _lRead(IdxFile,PChar(Index),IndexCnt*sizeof(TIdx));		{Indexdatei einlesen}
   _lClose(IdxFile);
  end else begin
   if lStrCmpi(Ext,'.mgz')=0 then Russisch:=true;
  end;
  if lStrLen(Ext)=0 then StrCopy(Ext,'.vt');	{Leere Extension als ".VT" annehmen}
  StrECopy(StrECopy(Name,Path),Ext);		{Path+Ext, Name dient als Zielpuffer}
  VTFile:=_lOpen(Name,0);
  Result:=Word(VTFile=HFile_Error);
  if Result<>0 then begin
   LoadFile:=Result; exit;
  end else
   if IndexCnt=0 then LoadFile:=ReadIndex else LoadFile:=0;
 end;

function TVT.ReadIndex:integer;		{Videotext-Index einlesen, alte und neue Form}
 var
  ph: LongInt;

 procedure readall;	{Klassisches Indexlesen, langsam}
  var
   i: integer;
  begin
   if IndexCnt>0 then			{n÷tig da IndexCnt ein WORD ist}
    for i:=0 to IndexCnt-1 do begin
     _llSeek(VTFile,LongMul(i,sizeof(TPage)),0);
     _lRead(VTFile,@(Index^[i]),sizeof(tidx));
    end;
  end; {readall}

 procedure ReadNewIndex(indexpp:word);	{Routine zum Einlesen eines New-Format-VT-Files}
			 {Die Kennung sei bereits vorher gecheckt! (schneller)}
  var
   i,i1: integer;
  begin
 {DateilΣnge:=Seitenzahl + (Seitenzahl+248) div INDEXPP, oder andersherum:}
 {Seitenzahl:=DateilΣnge - (DateilΣnge+249) div (INDEXPP+1)}
   i:=(IndexCnt+indexpp) div (indexpp+1);	{Reservierte Seiten am Beginn}
   for i1:=0 to i-1 do
    Longint(Index^[i1]):=MAGIC;			{Erste IndexeintrΣge sperren}
   while i<IndexCnt do begin
    _lRead(VTFile,@Page,sizeof(TPage));	{Eine Seite mit Indizes lesen}
    if ph>=0 then RunError;	{Bit15 mu▀ gesetzt sein, auch bei kⁿnftigen ─nderungen!}
    i1:=IndexCnt-i; if i1>indexpp then i1:=indexpp;	{Differenz, aber h÷chstens 255}
    Move(Page.B[4],Index^[i],sizeof(tIdx)*i1);	{Indizes kopieren}
    Inc(i,i1);		{gleich ein Ruck weiter}
   end {while};
  end;

 procedure ReadRussischIndex;
  var
   S: array[0..7] of Char;	{beinhaltet dann "?xxx-yy?"}
   I,EC: Integer;
  begin
   I:=0; while I<IndexCnt do begin
    _llSeek(VTFile,LongMul(I,PageSize)+30,0);
    _lRead(VTFile,@S[1],6);	{Lesen von "xxx-yy"}
    S[0]:='$'; S[4]:=#0; Val(S,Index^[I].page,EC);	{"$xxx0yy?"}
    S[4]:='$'; S[7]:=#0; Val(PChar(@S[4]),Index^[I].subp,EC);	{"$xxx$yy0"}
   end;
  end;

 begin {ReadIndex}
  PageSize:=sizeof(TPage);
  if Russisch then PageSize:=960;
  ReadIndex:=0;		{Annahme: fehlerfrei}
  IndexReAlloc(LongDiv(_llSeek(VTFile,0,2),PageSize));
  _llSeek(VTFile,0,0);
	{Anzahl der Seiten = Länge in Kilobytes}
  if IndexCnt>0 then begin	{Einen Bug ausbauen}
   if Russisch then ReadRussischIndex
   else begin
    _lRead(VTFile,@ph,sizeof(ph));	{Headerbytes einlesen}
    _llSeek(VTFile,0,0);		{Dateizeiger zurⁿcksetzen}
    if ph=MAGIC then ReadNewIndex(INDEXPP)
    else if ph=MAGIC_HS then ReadNewIndex(INDEXPP_HS)
    else readall;
   end;
  end;
 end; {TVT.ReadIndex}

procedure TVT.CloseFile;
 begin
  _lClose(VTFile);
{  Dispose(LinkList,Done);	{Objekt entfernen}
{  Linklist:=nil;		{Programmierfehler aufspüren}
 end;

{Die Flags beinhalten:
bit 0 Mit Quiz(1)
bit 1 Mit Blink(1)
bit 2 Mit Doppelter H÷he(1)
bit 3 Mit Transparent (MIX-Betrieb)(1)
bit 4 Invertierte Farben (schwarz->wei▀ usw)
bit 5 Einfarbig/Bunt(1)
bit 6 Debug-Flag (Anzeige der Schaltcodes)
bit 7 Rollende Unterseiten
bit 8 Doppelte Ausgabegroesze
bit 9 40 oder 41(1) Spalten
bit 10 24 oder 25(1) Zeilen
bit 11 Nur 1. Zeile (1)
bit 12 Berechnung in Pixel, bitte!
bit 12..15 Sprach (Font-) auswahl
	0 default (je nach Header)
	1 deutsch (usw.)
Topleft ist ein Ersatzstring fⁿr ganz links oben}
const
 fQuiz=1 shl 0; fBlink=1 shl 1; fDblH=1 shl 2; fMix=1 shl 3;
 fInvers=1 shl 4; fBunt=1 shl 5; fDebug=1 shl 6; fRoll=1 shl 7;
 fDblSize=1 shl 8; f41=1 shl 9; f25=1 shl 10; f1=1 shl 11; fFont=$F000;
 fPixels=1 shl 12;
 fGraf=1 shl 8; fHold=1 shl 9; fSep=1 shl 10; fShift=1 shl 11; fLatin=1 shl 12;
 fPrevDblH=1 shl 4;
 fTwoFonts=1 shl 5;

{ Hilfsfunktionen fⁿr MkBitmap }
 type tRGBWerte=array[0..8]of TColorRef;
const
 RGBWerte:tRGBWerte=($000000,$0000FF,$00FF00,$00FFFF,
				$FF0000,$FF00FF,$FFFF00,$FFFFFF,$808080);
 RGBInversWerte:tRGBWerte=($FFFFFF,$0000FF,$008000,$0060A0,
				$FF0000,$800060,$602000,$000000,$C0C0C0);
type
 pCharImg=^tCharImg;
 tCharImg=array[0..FONTH-1]of byte;		{Ein Zeichenbild}
 tCharImgTable=array[0..$1FF]of tCharImg;	{die Zeichenbildtabelle zum Type-Casten}
{procedure Tele8x10; external; {$L Tele8x10}	{der Font}

(*var GraphCharBuf:tCharImg;			{eine statische VARIABLE}
{procedure DoubleBuf(var src,dest;count:word;Attr:byte);	assembler;
 {hΣngt an jedes Byte das Attribut an, hier zum Ausrichten der Bitmapzeilen auf Wortgrenzen}
 asm	mov	cx,count
	jcxz	@@e
	mov	dx,ds
	cld
	mov	ah,Attr
	lds	si,src
	les	di,dest
@@l:	lodsb
	stosw
	loop	@@l
	mov	ds,dx
@@e:	end;

function GetCharImg(Code:integer):PCharImg;
 procedure GenChar8x10(Code:Integer; var Buf); assembler;
  asm	mov	ah,byte ptr Code
	les	di,Buf
	cld
	xor	al,al
	test	ah,1 shl 6
	jnz	@@sep		;{Separierte Grafik}
	call	@@22		;{Oberes Drittel}
	call	@@221
	call	@@2		;{Mittleres Drittel}
	call	@@221		;{Unteres Drittel}
	jmp	@@e

@@221:	shr	ah,2
@@22:	call	@@2
	call	@@2
@@2:	test	ah,1
	jz	@@2a
	or	al,$F0
@@2a:	test	ah,2
	jz	@@2b
	or	al,$F
@@2b:	stosb
	xor	al,al
	ret
@@111:
	shr	ah,2
@@11:	call	@@1
@@1:	test	ah,1
	jz	@@1a
	or	al,$60
@@1a:	test	ah,2
	jz	@@2b
	or	al,$6
	jmp	@@2b
@@sep:
	stosb
	call	@@11		;{Oberes Drittel}
	stosb
	call	@@111
	stosb			;{Mittleres Drittel}
	call	@@111
	stosb			;{Unteres Drittel}
@@e:	end;

 begin
  if Code<0 then begin
   GenChar8x10(Code,GraphCharBuf); GetCharImg:=@GraphCharBuf;
  end else if code<512 then
   GetCharImg:=@tCharImgTable((@Tele8x10)^)[Code]	{Dickes Typecasting}
  else begin
   MessageBox(0,'Code>=512!',nil,mb_ok);
   GetCharImg:=@tCharImgTable((@Tele8x10)^)[32]		{Dickes Typecasting}
  end;
 end;
*)

function memchr(const Buf; size:word; c:char):PChar; assembler;
 {Enthaltensein eines Bytes im Speicherbereich testen}
 asm	mov	cx,size
	cld
	mov	al,c
	les	di,Buf
	repne	scasb		{absuchen}
	jnz	@@1		{nicht gefunden: NIL}
	dec	di
	mov	ax,di
	mov	dx,es		{Gefunden: DX:AX:=ES:DI-1}
	jmp	@@e
@@1:	xor	ax,ax
	cwd			{DX:AX:=0:0}
@@e:	end;

function GetSlawicCase(Number:Integer):Integer;
{liefert Fall-Code für slawische Sprachen:
 0 für Singular (1 god, 21 god, 101 god usw.), Fall Sg. Nominativ
 1 für Plural "2..4" (2 goda, 23 goda, 154 goda), Fall Sg. Genitiv
 2 für Plural sonst (5 let, 13 let, 100 let), Fall Pl. Genitiv}
(* begin
  Number:=Abs(Number) mod 100;
	{nur Zehner- und Einerstellen sind von Interesse}
  if Number div 10 =1 then GetSlavicCase:=2	{immer 2 wenn Zehner=1}
  else case Number mod 10 do	{nur noch Einerstelle von Interesse}
   1: GetSlavicCase:=0;		{1: richtiger Singular}
   2..4: GetSlavicCase:=1;	{2..4}
   else GetSlavicCase:=2;	{5..0}
  end;
 end;*)

assembler; asm
	mov	ax,[Number]
	or	ax,ax
	jns	@@1
	neg	ax
@@1:	mov	dl,100
	div	dl		{AX/DL=AL, AH=Rest}
	xchg	al,ah
	cbw
	mov	dl,10
	div	dl
	dec	al
	jz	@@sc2
	xor	al,al
	dec	ah
	jz	@@sc0
	inc	al
	cmp	ah,4
	jc	@@sc1
@@sc2:	mov	al,2
@@sc0:
@@sc1:
	cbw
end;

function MkSgPl(Wort:PChar; Zahl: Integer; Ziel:PChar):PChar;
{Singular, Plural oder Plural II aus der Wortvorgabe formen.
 Enthält die Wortvorgabe ein &, wird der Plural slawisch ausgewertet, sonst
 deutsch/englisch. Beispiele für Wort-Templates:
 m#ouse$ice	für mouse (Maus) oder mice (Mäuse)
 #god$goda&let	für god (Jahr), goda oder let (Jahre)
 bab#y$ies	für baby und babies (englische Kleinkinder)
 Baby$s		für Baby und Babys (deutsche -"- nach der Rechtschreibreform)
  Das erste Sonderzeichen (#,$ oder &) markiert das Ende des (festen)
  Wortstamms, nach # befindet sich die Singular-Endung, nach $ die Plural-
  Endung, ist & enthalten, dann nach & die Plural2-Endung.
  Im Falle deutsch/englisch darf # weggelassen werden}
 var
  C,ScanChar: Char;
 begin
  ScanChar:=#0;	{Keine Token-Suche}
  MkSgPl:=Ziel;	{durchkoppeln}
  repeat
   C:=Wort^; Inc(Wort);
   case C of
    '#': if StrScan(Wort,'&')<>nil then begin
      case GetSlawicCase(Zahl) of
       1: ScanChar:='$';
       2: ScanChar:='&';
      end;
     end else begin
      if Zahl<>1 then ScanChar:='$'
     end;
    '$': if (ScanChar='$') or (Zahl<>1) then ScanChar:=#0 else ScanChar:='#';
    '&': if ScanChar='&' then ScanChar:=#0 else ScanChar:='#';
    #0: ;	{Nichts tun}
    else if ScanChar=#0 then begin	{Zeichen kopieren erlaubt...}
     Ziel^:=C; Inc(Ziel);
    end;
   end;
  until C=#0;
  Ziel^:=#0;
 end;

procedure TVT.SetupPage;
 var
  Line: Integer;
  CurTime,PageTime: TDateTime;
  SixInts: array[0..5]of Integer absolute CurTime;
 const Namen: array[0..5]of PChar=(
  'Jahr$e','Monat$e','Tag$e','Stunde$n','Minute$n','Sekunde$n');
 var
  Params: record	{für wvsprintf}
   I1: Integer; S1: PChar;
   I2: Integer; S2: PChar;
  end;
  S1,S2: array[0..31]of Char;
  Trash: Word;

 procedure SetBit(Mask:Byte);
  begin
   LineInfo[Line]:=LineInfo[Line] or Mask;
  end;

 procedure CheckLineBits(Line:Integer);
  var
   L: ^VTLine;		{Rechnungen reduzieren}
  begin
   LineInfo[Line]:=0;	{rücksetzen}
   L:=@Page.L[Line];
   if memchr(L^,40,#$18)<>nil then SetBit(fQuiz);
   if memchr(L^,40,#$08)<>nil then SetBit(fBlink);
   if memchr(L^,40,#$0A)<>nil then SetBit(fMix);
   if memchr(L^,40,#$1B)<>nil then SetBit(fTwoFonts);
   if memchr(L^,40,#$0D)<>nil then SetBit(fDblH);
  end;

 procedure CheckDblHChaining;
  var
   Line: Integer;
   PDblH: Boolean;
  begin
   PDblH:=false;
   for Line:=0 to 24 do begin
    if PDblH then begin
     SetBit(fPrevDblH);
     PDblH:=false;
{egal, ob die aktuelle Zeile auch doppelt hohe Zeichen enthielt!}
    end else begin
     if LineInfo[Line] and fDblH <>0 then PDblH:=true;
    end;
   end;
  end;

 begin
  if Russisch then begin
   Move(Page.L[0][30],S1[1],6);	{in einen Puffer kopieren}
   S1[0]:='$'; S1[4]:=#0; Val(S1,Header.Page,Trash);
   S1[4]:='$'; S1[7]:=#0; Val(PChar(@S1[4]),Header.Subp,Trash);
   Header.CBits:=0; Header.errors:=0;	{C-Bits usw. sind nicht verfügbar}
  end else begin
   Move(Page.H,Header,sizeof(tHeader));	{Header auslagern}
  end;
  Page.B[0]:=5;				{magenta (also lila)}
  FillChar(Page.B[1],6,' ');		{mit Spaces füllen}
  for Line:=0 to 24 do CheckLineBits(Line);
  CheckDblHChaining;
{Altersbestimmung}
  with CurTime do begin
   GetDate(Year,Month,Day,Trash);{DayOfWeek in den Skat drücken}
   GetTime(Hour,Min,Sec,Trash);	{Hundertstelsekunden weg}
   Inc(Hour,LokaleZeitzone);
  end;
  UnpackTime(Page.E.MsDosTime.L,PageTime);
  Inc(PageTime.Hour,QuellZeitzone);
  asm	{Differenz bilden, in Assembler immer noch am schönsten}
	mov	ax,[CurTime.Sec]
	sub	ax,[PageTime.Sec]
	jnc	@@1
	add	ax,60		{der Trick: CY bleibt 1 !!}
@@1:	mov	[CurTime.Sec],ax
	mov	ax,[CurTime.Min]
	sbb	ax,[PageTime.Min]
	jnc	@@2
	add	ax,60
@@2:	mov	[CurTime.Min],ax
	mov	ax,[CurTime.Hour]
	sbb	ax,[PageTime.Hour]
	cmp	ax,24
{hier Probleme umgehen, die durch negative Stundenangaben entstehen können,
 die duch das simple Addieren der Zeitzone s.o. auftreten können}
	cmc
	jnc	@@3
	add	ax,24
@@3:	mov	[CurTime.Hour],ax
	mov	ax,[CurTime.Day]
	sbb	ax,[PageTime.Day]
	cmp	ax,30
{hier Probleme umgehen, da 1. die Tage 1-basiert sind und 2. die Tage bis 31
 laufen können}
	cmc
	jnc	@@4
	add	ax,30		{im Mittel hat ein Monat 30 Tage}
@@4:	mov	[CurTime.Day],ax
	mov	ax,[CurTime.Month]
	sbb	ax,[PageTime.Month]
	cmp	ax,12
{hier Probleme wegen 1-basierter Monatsangabe umgehen (??)}
	cmc
	jnc	@@5
	add	ax,12
@@5:	mov	[CurTime.Month],ax
	mov	ax,[CurTime.Year]
	sbb	ax,[PageTime.Year]
	mov	[CurTime.Year],ax
  end;
{nun String formen}
  for Line:=0 to 4 do begin
   if SixInts[Line]<>0 then break;	{Erste Zahl ungleich Null auffinden}
  end;
  Params.I1:=SixInts[Line];	{Erste Zahl}
  MkSgPl(Namen[Line],Params.I1,S1);
  Params.S1:=S1;	{2. Parameter ist Sg/Pl-Flag}
  Inc(Line);
  Params.I2:=SixInts[Line];
  MkSgPl(Namen[Line],Params.I2,S2);
  Params.S2:=S2;
  wvsprintf(AlterStr,'%d %s %d %s',Params);
 end;

function TVT.PageContain(b:byte):boolean;		{Test ob Seite dieses Zeichen enthΣlt}
 begin
  PageContain:=memchr(Page,40*25,char(b))<>nil;
 end;


function CalculateWidth(Flags:word):word;
 var w:word;
 begin
  w:=40; if Flags and f41 <>0 then Inc(w);
  if Flags and fPixels <>0 then begin
   w:=w*FONTW;
   if Flags and fDblSize <>0 then w:=w*2;
  end;
  CalculateWidth:=w;
 end;

function CalculateHeight(Flags:word):word;
 var w:word;
 begin
  w:=24; if Flags and f25 <>0 then Inc(w);
  if Flags and f1 <>0 then w:=1;
  if Flags and fPixels <>0 then begin
   w:=w*FONTH;
   if Flags and fDblSize <>0 then w:=w*2;
  end;
  CalculateHeight:=w;
 end;

{C4 - Loeschen einer Seite
C5  - Schlagzeilen
C6  - Untertitel
C7  - Unterdruecken der Kopfzeile
C8  - Texterneuerung
C9  - Unterbrochene Sequenz
C10 - Unterdrueckung der Darstellung
C11 - Serielle Magazinfolge

C12 .. C14
000 - Englisch(0)
001 - Deutsch(4)
010 - Schweden/Finnisch(2)
011 - Italienisch(6)
100 - Belgisch/Franzoesisch(1)
101 - Portugisisch/Spanisch(5)
110 - dynamische Zeichenzuordnung(3)
111 - reserviert(7)
Ersetzkette  40 60 7B 23 24 ** ** ** 7C 7D 7E 5B 5C 5D 5E 5F }
const
 LandMap:array[0..7]of byte=(2,7,3,0,1,8,6,0);
 ErsetzKette:array[0..15]of char=
  #$40#$60#$7B#$23#$24#$40#$40#$40#$7C#$7D#$7E#$5B#$5C#$5D#$5E#$5F;
{zum Vertauschen der Reihenfolge in die im Menⁿ:
default(0), *deutsch(1), *englisch(2), *schwedisch(3), polnisch(4), tschechisch(5),
*italienisch(6), *franz÷sisch(7), *spanisch(8), islΣndisch(9),
ungarisch(10), tⁿrkisch(11), reserve(12), reserve(13): Umcodierung der Zeichen in Ersetzkette;
kyrillisch(14),arabisch(15): Sonderrolle;
Fonts mit * werden auch automatisch erkannt}

const Grafik=0;OEM=1;ANSI=2;U7Bit=3; UseGrafikFillchar=4;

{Für die Vergleichsroutine}
const AnsiUmcodTab:array[192..255]of char=
 'AAAAAAACEEEEIIIIDNOOOOO'#$d7+
 'OUUUUYPsaaaaaaaceeeeiiiidnooooo'#$f7'ouuuuypy';

{Für die Erzeugung von blankem Text aus der Videotextseite}
const AnsiTab:array[32..255]of char=
' !"#$%&''()*+,-./0123456789:;<=>?'+
#$a7'ABCDEFGHIJKLMNOPQRSTUVWXYZ'#$c4#$d6#$dc'^_'+
#$b0'abcdefghijklmnopqrstuvwxyz'#$e4#$f6#$fc#$df' '+
'@-'#$bc#$a3'$   '#$a6#$be#$ad#$ab#$bd#$bb'^#'+
#$c9#$e9#$e4'#'#$a4'   '#$f6#$e5#$fc#$c4#$d6#$c5#$dc'_'+
#$20#$20#$20#$20#$20#$20#$20'l'#$20#$20'SL'#$20#$20#$20#$20+
'c'#$e9#$e1'#'#$f9#$20#$20#$20#$e8#$fa'stz'#$ff#$ed'r'+
#$e9#$f9#$e0#$a3'$'#$e3#$f5#$b7#$f2#$e8#$ec#$b0#$e7#$bb'^#'+
#$e0#$e8#$e2#$e9#$ef#$c3#$d5#$c7#$f4#$fb#$e7#$eb#$ea#$f9#$ee'#'+
#$a1#$bf#$fc#$e7'$'#$aa#$ba#$d1#$f1#$e8#$e0#$e1#$e9#$ed#$f3#$fa+
#$c1#$c0#$c8#$cd#$cf#$d3#$d2#$da#$e6#$c6'dD'#$f8#$d8#$fe#$ee;


function Translate(c:byte;FlagReg,Land,DFlags:Word):integer;
 var
  ChChar:PChar;
  code:integer;
 begin
  code:=c;	{Standardfall}
  if FlagReg and (fBlink or fQuiz) <>0 then
   code:=32			{Leerzeichen ausgeben (Information verstecken)}
  else if FlagReg and fGraf <>0 then begin
   case code of
    32..63: code:=integer(c)-128-32;
    96..127: code:=integer(c)-128-96+32;	{Negative Codes fⁿr Grafikzeichen}
   end;
   if (code<0)
   and (FlagReg and fSep <>0)
   then code:=code+64;			{Separierte Grafik}
  end;
  if code>0 then begin		{Wenn kein Grafikzeichen geworden
   (Großbuchstaben) dann auch hier Landesregeln anwenden!}
   case Land of
    2..13: begin
      ChChar:=memchr(ErsetzKette,16,char(code));	{12 = reserve I, 13 = reserve II}
      if ChChar<>nil then
       Code:=integer(Land+6) shl 4 + (ChChar-ErsetzKette);	{Code im Bereich 80..13F}
     end;
    14..15:
     if FlagReg and fLatin =0 then begin
      if (code>=32) and (code<128) then
       {8-bit-Codes sowie Debuginfos nicht transferieren}
       Code:=integer(Land-14)*96+$140+(code-32);	{Kyrillisch, arabisch im Bereich 140..1FF}
     end else begin
      ChChar:=memchr(ErsetzKette,16,char(code));	{12 = reserve I, 13 = reserve II}
      if ChChar<>nil then
       Code:=integer(2+6) shl 4 + (ChChar-ErsetzKette);
	{Bei Lateinumschaltung gelten offenbar englische Codes}
     end;
   end; {case}
  end; {if}

  case DFlags and 3 of

  Grafik: Translate:=code;			{endlich ausgeben}

  OEM: begin
   if (code>=32) and (code<256) then begin
    code:=Integer(AnsiTab[code]);
    AnsiToOem(PChar(@code),PChar(@Code));	{Haarig, aber geht!?}
    Translate:=code;
   end else if DFlags and UseGrafikFillchar <>0 then
    TransLate:=$B1
   else
    Translate:=$20;
   end;

  ANSI: begin
   if (code>=32) and (code<256) then
   Translate:=Integer(AnsiTab[code])
   else if DFlags and UseGrafikFillchar <>0 then
    TransLate:=$7F
   else
    Translate:=$20;
   end;

  U7BIT: begin
    if (code>=32) and (code<=255) then begin
     code:=Integer(AnsiTab[code]);
     if (code>=192) and (code<=255) then
      code:=Integer(AnsiUmcodTab[code]);
    end;
    if (code<32) or (code>=127) then begin
     if DFlags and UseGrafikFillchar <>0 then
      TransLate:=Integer('X')
     else
      Translate:=$20;
    end;
   end;

  end;
 end;

function TVT.MkText(Buf:PChar;Flags,Land:word; var Bereich:TRect):integer;
{Bereich bezeichnet den darzustellenden Ausschnitt}
 var Lines: Integer;
  Cur: TPoint;
  HFarbe,VFarbe: byte;		{Farbattribute}
  FlagReg: word;		{Sonstige laufende Attribute}
  CurByte,OldByte: byte;	{um nicht jedesmal das Array adressieren zu mⁿssen}

 procedure WriteCharTranslate(c:byte);
  begin
   if (Bereich.left<=Cur.X) and (Cur.X<Bereich.right)
    {PtInRect(Bereich,Cur)} then begin	{später: Region!}
    Buf^:=Char(Translate(c,FlagReg,Land,ANSI));
    Inc(Buf);
   end;
  end;

 begin
{mit Schleifen loslegen}
  if Land=0 then Land:=LandMap[(Header.CBits shr 12) and 7];	{Wenn default(Auto-detect) dann aus den code-Bits}
  for Cur.Y:=Bereich.top to Bereich.bottom do
   if LineInfo[Cur.Y] and fPrevDblH =0 then begin
    HFarbe:=0; VFarbe:=7;	{Wei▀ auf Schwarz}
    FlagReg:=0;			{alle Flags aus}
    if (Flags and fMix <>0) or (Header.CBits and $60 <>0) then
     FlagReg:=fMix;		{in diesen beiden FΣllen mit MIXED starten}
    OldByte:=32;			{auch das vorhergehende Byte zu Space machen}
    for Cur.X:=0 to 39 do begin
     CurByte:=byte(Page.L[Cur.Y,Cur.X]);	{aus der Seite}
{Sofort wirksame Schalter}
     case CurByte of
      28:HFarbe:=0;		{Hintergrund schwarz}
      29:HFarbe:=VFarbe;		{Hintergrund=Vordergrund}
     end;
{Ein Zeichen ausgeben}
     if (CurByte<$20) and (Flags and fDebug =0) then begin
      if (FlagReg and (fHold or fGraf) = (fHold or fGraf)) and	{HoldGrafix und GrafikFlag}
      ((OldByte and not $40 >=$20) and (OldByte and not $40 <$40))
      then WriteCharTranslate(OldByte)
      else WriteCharTranslate(32);
     end else begin
      WriteCharTranslate(CurByte);
      OldByte:=CurByte;
     end;
{Nachher wirksame Schalter}
     if (CurByte<$20) then
      FlagReg:= FlagReg and not fQuiz;		{Secret aus}
     case CurByte of
      0..7: begin
	   FlagReg:= FlagReg and not fGraf;	{Grafikflag aus}
	   VFarbe:=CurByte; end;		{Farbe setzen}
      8:  if Flags and fBlink =0 then		{wenn "blinkende Zeichen AUS"}
	   FlagReg:= FlagReg or fBlink;		{Blinken ein}
      9:  FlagReg:= FlagReg and not fBlink;	{Blinken aus}
      10: if Flags and fMix <>0 then		{Wenn Mix-Betrieb EIN}
	   FlagReg:= FlagReg or fMix;		{InBox aus}
      11: FlagReg:= FlagReg and not fMix;	{InBox ein}
      12: FlagReg:= FlagReg and not fDblH;	{DoubleHeigth aus}
      13: {wenn nicht verboten}
	   FlagReg:= FlagReg or fDblH;		{DoubleHeigth ein}
      14: FlagReg:= FlagReg and not fShift;	{Shift aus (spΣter verwendet)}
      15: FlagReg:= FlagReg or fShift;		{Shift ein (spΣter verwendet)}
      16..23: begin
	   FlagReg:= FlagReg or fGraf;		{Grafikflag ein}
	   VFarbe:=CurByte and 7;		{Farbe setzen}
	  end;
      24: if Flags and fQuiz =0 then		{wenn "Quiz-Zeichen AUS"}
	   FlagReg:= FlagReg or fQuiz;		{Secret ein}
      25: FlagReg:= FlagReg and not fSep;	{separated aus}
      26: FlagReg:= FlagReg or fSep;		{separated ein}
      27: FlagReg:= FlagReg xor fLatin;		{Sprachumschaltung Lateinisch}
      30: FlagReg:= FlagReg or fHold;		{holdgrafix ein}
      31: FlagReg:= FlagReg and not fHold;	{holdgrafix aus}
     end; {case}
    end; {1 Zeile}
    if Flags and f41 <>0 then begin
     HFarbe:=0;			{Schwarz}
     FlagReg:=0;		{alle Flags aus}
     if (Flags and fMix <>0) or (Header.CBits and $60 <>0) then
      FlagReg:=fMix;		{in diesen beiden FΣllen mit MIXED das Space ausgeben}
     Cur.X:=40;
     WriteCharTranslate(32);	{noch ein Space dran}
    end;
    Buf^:=#13; Inc(Buf); Buf^:=#10; Inc(Buf);
   end; {else, KEIN END fⁿr FOR}
  Buf^:=#0;	{das Nullterminieren nicht vergessen!}
 end;


function TVT.MkBitmap(dc:HDC;Flags,Land:word;const Bereich:TRect):integer;
{Bereich bezeichnet den darzustellenden Ausschnitt}
 var Lines,Line,Col: byte;
  HFarbe,VFarbe: byte;		{Farbattribute}
  FlagReg: word;		{Sonstige laufende Attribute}
  LineContainsDblh: boolean;
  BitmapHandle,OldBitmapHandle: HBitmap;
  MemoryDC: HDC;		{ein Hilfs-GerΣtekontext}
  CurByte,OldByte: byte;	{um nicht jedesmal das Array adressieren zu mⁿssen}
  mode:LongInt;			{BitBlt-Mode}
  RGBWertePtr:^tRGBWerte;	{Auswahl der "Palette"}

 procedure WriteChar(Code:integer);
 {schreibt ein Zeichen in das Device Context}
  var
{   Bitbuf:array[0..FONTH-1]of word;}
   CharPos:TPoint;
  begin
{   DoubleBuf(GetCharImg(Code)^,Bitbuf,FONTH,0);		{die Null ist wichtig fⁿrs Ausfⁿllen}
{   SetBitmapBits(BitmapHandle,FONTH*2,@Bitbuf);		{Bits ⁿbernehmen lassen}
   Inc(Code,128);	{Bitmap hat vorn die Grafikzeichen}
   CharPos.X:=(Code mod 32) * fontw;
   CharPos.Y:=(Code div 32) * fonth;
   if Flags and fBunt <>0 then begin
    if (Flags and fDebug <>0)
    and (HFarbe=VFarbe)			{Schwarzer Adler auf schwarzem Grund?}
    then SetBkColor(dc,RGBWertePtr^[7-VFarbe])	{Komplementärfarbe!}
    else SetBkColor(dc,RGBWertePtr^[VFarbe]);	{1-Pixel werden auf BkColor gesetzt!!}
    if FlagReg and fMix <>0 then
     SetTextColor(dc,RGBWertePtr^[8])
    else SetTextColor(dc,RGBWertePtr^[HFarbe]);		{Farben am Ziel-DC einstellen}
   end;
   if FlagReg and fDblH <>0 then
    StretchBlt(dc,Col*FONTW,Line*FONTH,FONTW,FONTH*2,MemoryDC,
     CharPos.X,CharPos.Y,fontw,fonth,mode)
    {Zeichen in doppelter H÷he kopieren}
   else begin
    BitBlt(dc,Col*FONTW,Line*FONTH,FONTW,FONTH,MemoryDC,
     CharPos.X,CharPos.Y,mode);
    {Zeichen normal kopieren}
    if LineContainsDblH then
     BitBlt(dc,Col*FONTW,(Line+1)*FONTH,FONTW,FONTH,MemoryDC,
      0,0,mode);
    {Notgedrungenen Leeraum aus der kreierten Bitmap darunter kopieren}
   end;
  end; {WriteChar}

 procedure WriteCharTranslate(c:byte);
  var
   ChChar:PChar;
   code:integer;
  begin
   Code:=Translate(c,FlagReg,Land,Grafik); {Übersetzen in "Integer-Zeichen"}
   WriteChar(code);			{endlich ausgeben}
  end;

 function CanDblH:boolean;	{Doppelte Zeichenh÷he erlaubt?}
  begin
   CanDblH:=(Line<>0) and (Line<>Lines-1) and (Flags and fDblH <>0);
  end;

 begin
{Kontext erstellen}
  MemoryDC := CreateCompatibleDC(dc);		{Hilfsbitmap erstellen}
{  BitmapHandle:=CreateBitmap(FONTW*2,FONTH,1,1,nil);	{Ein leeres Bitmap}
  BitmapHandle:=LoadBitmap(HInstance,MakeIntResource(10));
  OldBitmapHandle:=SelectObject(MemoryDC, BitmapHandle);	{wie im Lehrbuch}
{Randwerte initialisieren}
  Lines:=CalculateHeight(Flags and not fPixels);
  Mode:=SRCCOPY;				{BitBlt-Modus festlegen}
  if (flags and fBunt =0) and (Flags and fInvers <>0) then Mode:=NOTSRCCOPY;
  RGBWertePtr:=@RGBWerte;
  if flags and fInvers <>0 then RGBWertePtr:=@RGBInversWerte;
{mit Schleifen loslegen}
  LineContainsDblH:=FALSE;
  if Land=0 then Land:=LandMap[(Header.CBits shr 12) and 7];	{Wenn default(Auto-detect) dann aus den code-Bits}
  for Line:=0 to Lines-1 do
   if LineContainsDblH then	{hier: vorhergehende Linie!}
    LineContainsDblH:=FALSE	{nichts weiter tun, dann nΣchste Zeile}
   else begin
    LineContainsDblH:=CanDblH and (memchr(Page.L[Line],40,#13)<>nil); {Stellen}
    HFarbe:=0; VFarbe:=7;	{Wei▀ auf Schwarz}
    FlagReg:=0;			{alle Flags aus}
    if (Flags and fMix <>0) or (Header.CBits and $60 <>0) then
     FlagReg:=fMix;		{in diesen beiden FΣllen mit MIXED starten}
    OldByte:=32;			{auch das vorhergehende Byte zu Space machen}
    for Col:=0 to 39 do begin
     CurByte:=byte(Page.L[Line,Col]);	{aus der Seite}
{Sofort wirksame Schalter}
     case CurByte of
      9:  FlagReg:= FlagReg and not fBlink;	{Blinken aus}
      12: FlagReg:= FlagReg and not fDblH;	{DoubleHeigth aus}
      24: if Flags and fQuiz =0 then		{wenn "Quiz-Zeichen AUS"}
	   FlagReg:= FlagReg or fQuiz;		{Secret ein}
      28:HFarbe:=0;		{Hintergrund schwarz}
      29:HFarbe:=VFarbe;		{Hintergrund=Vordergrund}
      30: FlagReg:= FlagReg or fHold;		{holdgrafix ein}
     end;
{Ein Zeichen ausgeben}
     if (CurByte<$20) and (Flags and fDebug =0) then begin
      if (FlagReg and (fHold or fGraf) = (fHold or fGraf)) and	{HoldGrafix und GrafikFlag}
      ((OldByte and not $40 >=$20) and (OldByte and not $40 <$40))
      then WriteCharTranslate(OldByte)
      else begin
       WriteCharTranslate(32);
       OldByte:=32;
      end;
     end else begin
      WriteCharTranslate(CurByte);
      OldByte:=CurByte;
     end;
{Nachher wirksame Schalter}
     if (CurByte<$20) and (CurByte<>24) then
      FlagReg:= FlagReg and not fQuiz;		{Secret aus}
     case CurByte of
      0..7: begin
	   FlagReg:= FlagReg and not fGraf;	{Grafikflag aus}
	   VFarbe:=CurByte; end;		{Farbe setzen}
      8:  if Flags and fBlink =0 then		{wenn "blinkende Zeichen AUS"}
	   FlagReg:= FlagReg or fBlink;		{Blinken ein}
      10: if Flags and fMix <>0 then		{Wenn Mix-Betrieb EIN}
	   FlagReg:= FlagReg or fMix;		{InBox aus}
      11: FlagReg:= FlagReg and not fMix;	{InBox ein}
      13: if CanDblH then			{wenn nicht verboten}
	   FlagReg:= FlagReg or fDblH;		{DoubleHeigth ein}
      14: FlagReg:= FlagReg and not fShift;	{Shift aus (spΣter verwendet)}
      15: FlagReg:= FlagReg or fShift;		{Shift ein (spΣter verwendet)}
      16..23: begin
	   FlagReg:= FlagReg or fGraf;		{Grafikflag ein}
	   VFarbe:=CurByte and 7;		{Farbe setzen}
	  end;
      25: FlagReg:= FlagReg and not fSep;	{separated aus}
      26: FlagReg:= FlagReg or fSep;		{separated ein}
      27: FlagReg:= FlagReg xor fLatin;		{Sprachumschaltung Lateinisch}
      31: FlagReg:= FlagReg and not fHold;	{holdgrafix aus}
     end; {case}
    end; {1 Zeile}
    if Flags and f41 <>0 then begin
     HFarbe:=0;			{Schwarz}
     FlagReg:=0;		{alle Flags aus}
     if (Flags and fMix <>0) or (Header.CBits and $60 <>0) then
      FlagReg:=fMix;		{in diesen beiden FΣllen mit MIXED das Space ausgeben}
     Col:=40;
     WriteCharTranslate(32);	{noch ein Space dran}
    end;
   end; {else, KEIN END fⁿr FOR}
{Kontext aufl÷sen}
  SelectObject(MemoryDC,OldBitmapHandle);
  DeleteDC(MemoryDC);
  DeleteObject(BitmapHandle);		{Alles lehrbuchmΣ▀ig}
 end;


function TVT.MkLinkList(Wnd:HWnd;Flags:word):integer;	{eine Rechteckliste erzeugen}
 var Lines,Line,Col: byte;
  FlagReg: word;		{Sonstige laufende Attribute}
  LineContainsDblh: boolean;
  CurByte: byte;	{um nicht jedesmal das Array adressieren zu mⁿssen}
  Ziffern,Winkel:byte; Seite:word;
  cWinkel:char;

 function CanDblH:boolean;	{Doppelte Zeichenh÷he erlaubt?}
  begin
   CanDblH:=(Line<>0) and (Line<>Lines-1) and (Flags and fDblH <>0);
  end;

 procedure TestZiffern;
  var i:word;
  begin
   if (Ziffern=3) and (Seite>=$100) and (Seite<=$899) then begin {Treffer}
    i:=SelectPage(Seite and $7FF, 0, NEXTSUB);
    if (i<>$FFFF) and (Index^[i].L<>Header.Idx.L) then begin
	{Wenn Seite tatsächlich gefunden und nicht die gerade angezeigte ist}
     AppendNew(Wnd,Col-Ziffern,Line,Ziffern,1+byte(FlagReg and fDblH <>0),
      Index^[i].Page, Index^[i].Subp);	{eintragen (Keine Telefonnummern verfolgen}
    end;
   end;
   Ziffern:=0;					{ZΣhler erneut scharfmachen}
   Seite:=0;
  end;

 procedure TestWinkel;
  var i:word; sm:TSearchMode;		{Gefundenen Index sowie Suchmodus}
  begin
   if (Winkel>=1) and (Winkel<=3) then begin	{Hier: Nur 1..3 Winkel zulassen}
    if cWinkel='>' then sm:=ADDNEXTSUBPAGE
    else sm:=SUBPREVSUBPAGE;
    i:=SelectPage(Header.Page,Header.Subp,sm);	{Neue Seite suchen}
    if i<>$FFFF then
     AppendNew(Wnd,Col-Winkel,Line,Winkel,1+byte(FlagReg and fDblH <>0),
      Index^[i].Page, Index^[i].Subp);
   end;
   Winkel:=0;					{ZΣhler erneut scharfmachen}
  end;

 begin
{  LinkList^.FreeAll;		{Liste leeren}
  DestroyInvisibles(Wnd);
  Lines:=CalculateHeight(Flags and not fPixels);
{mit Schleifen loslegen}
  LineContainsDblH:=FALSE;
  for Line:=0 to Lines-1 do
   if LineContainsDblH then	{hier: vorhergehende Linie!}
    LineContainsDblH:=FALSE	{nichts weiter tun, dann nΣchste Zeile}
   else begin
    FlagReg:=0; Ziffern:=0; Winkel:=0; Seite:=0; cWinkel:=' ';
    for Col:=0 to 39 do begin
     CurByte:=byte(Page.L[Line,Col]);	{aus der Seite}
     if (CurByte<$20) then
      FlagReg:= FlagReg and not fQuiz;		{Secret aus}
     case CurByte of
      0..7: FlagReg:= FlagReg and not fGraf;	{Grafikflag aus}
      12: FlagReg:= FlagReg and not fDblH;	{DoubleHeigth aus}
      13: if CanDblH then begin			{wenn nicht verboten}
	   FlagReg:= FlagReg or fDblH;		{DoubleHeigth ein}
	   LineContainsDblH:=TRUE;		{markieren, nΣchste Zeile auszulassen}
	  end;
      16..23: FlagReg:= FlagReg or fGraf;	{Grafikflag ein}
      24: if Flags and fQuiz =0 then		{wenn "Quiz-Zeichen AUS"}
	   FlagReg:= FlagReg or fQuiz;		{Secret ein}
     end;
     if FlagReg and (fGraf or fQuiz) <>0 then
      CurByte:=32;				{Leerzeichen annehmen}
     if (CurByte>=$30) and (CurByte <=$39) then begin {Ziffer?}
      Inc(Ziffern); Seite:=Seite shl 4 +(CurByte-$30);	{Seitennummer akkumulieren}
     end else TestZiffern;
     if Char(CurByte)<>cWinkel then TestWinkel;
     cWinkel:=Char(CurByte);
     case cWinkel of
     '>','<': Inc(Winkel)	{Winkel? (Zähler erhöhen)}
     end;
    end;{for}
    Col:=40;
    TestZiffern;				{41. Spalte}
    TestWinkel;
   end;{else}
 end;
{*****************************}
{* Ende Objektdefinition TVT *}
{*****************************}
var
 VT:tVT;			{Statisches Objekt}
const
 SDM:boolean=false;		{Self Destruction Mode Flag}
 SDM_EXE:boolean=false;
 SDM_Dir:boolean=false;
 SDM_WININI:boolean=true;
 SDM_REGDAT:boolean=true;	{Destruction Items}

function GetFileAttr(Name:PChar):Word; assembler;
 asm
  push ds
   lds dx,[Name]
   mov ax,4300h
   int 21h
  pop ds
  jc @@err
  xor ax,ax		{kein Dosenerror melden}
@@err:
  mov cx,-1
  mov [DosError],ax
  mov ax,cx
@@e:
 end;

type
 PSDMDlg=^TSDMDlg;
 TSDMDlg=object(TDialog)
  procedure SetupWindow; virtual;
  function CanClose:boolean; virtual;
 end;

procedure TSDMDlg.SetupWindow;
 var
  S,S1: array[byte]of char;
 begin
  GetArgStr(S,0,sizeof(S));	{Eigener Programmname}
  GetDlgItemText(HWindow,111,S1,sizeof(S1));
  wvsprintf(S1,S1,S[0]);		{Dateiname einsetzen}
  SetDlgItemText(HWindow,111,S1);
  if GetFileAttr(S) and faReadOnly <>0 then
   SendDlgItemMessage(HWindow,101,WM_Enable,0,0); {Grau schalten}
  CheckDlgButton(HWindow,103,1); {ankreuzen}
  CheckDlgButton(HWindow,104,1); {ankreuzen}
 end;

function TSDMDlg.CanClose:boolean;
 begin
  CanClose:=true;
  MessageBeep(MB_IconExclamation);
  SDM:=true;
  SDM_Exe:=Boolean(IsDlgButtonChecked(HWindow,101)); {angekreuzt?}
  SDM_Dir:=Boolean(IsDlgButtonChecked(HWindow,102)); {angekreuzt?}
  SDM_WinIni:=Boolean(IsDlgButtonChecked(HWindow,103)); {angekreuzt?}
  SDM_RegDat:=Boolean(IsDlgButtonChecked(HWindow,104)); {angekreuzt?}
  PostQuitMessage(0);
 end;

type
{****************************}
{* Objektdefinition TVTWApp *}
{****************************}
 TVTWApp = object(TApplication)
  constructor Init(AName:PChar);
  procedure InitMainWindow; virtual;
  procedure InitInstance; virtual;
  function ProcessAppMsg(var Message:TMsg):boolean; virtual;
  destructor done; virtual;
 end;

var
 VTWApp: TVTWApp;

constructor TVTWApp.Init;
 begin
  FileMode:=0;				{Nichts anbrennen lassen}
  VT.Init;				{Erst mal unser eigenes Objekt}
  inherited Init(AName);
 end;

procedure TVTWApp.InitInstance;
 begin
  inherited InitInstance;
  hAccTable:=LoadAccelerators(hInstance,'MAIN');
 end;


destructor TVTWApp.Done;
 var
  S,P:array[0..127]of char;
  ReOpenBuf: TOfStruct;
 begin
  inherited Done;
  if SDM then begin
   GetArgStr(S,0,sizeof(S));
   if SDM_Exe then begin
    OpenFile(S,ReOpenBuf,OF_Delete);
   end;
   if SDM_Dir then begin
    FileSplit(S,P,nil,nil);
    RemoveDir(P);
   end;
   if SDM_WinIni then begin
    WriteProfileString(VTW_Name,nil,nil)	{wegputzen}
   end;
   if SDM_RegDat then begin
    RegDeleteKey(HKEY_Classes_Root,'.VT');
    RegDeleteKey(HKEY_Classes_Root,'h#s Videotext Betrachter');
   end;
  end;
 end;

{*********************************}
{* Ende Objektdefinition TVTWApp *}
{*********************************}

function Contain(const R:TRect;P:TPoint):boolean;	{Punkt in Rechteck?}
 begin
  Contain:=(R.Left<=P.x) and (P.x<R.Right) and (R.Top<=P.y) and (P.y<R.Bottom);
 end;
{*********************************************************}
{* THistory, ein Objekt zur Verwaltung der History-Liste *}
{*********************************************************}
const
 HISTSIZE=100;				{das Objekt merkt sich maximal 100 EintrΣge}
type
 tHistory=object
  Hist:array[0..HISTSIZE-1]of tIdx;	{das "GedΣchtnis"}
  HistPtr,HistLen:word;			{2 Indizes}
  constructor Init;
  destructor Done;
  procedure Append(const That:tIdx);	{geht immer, notfalls wird das Array verschoben}
  function Fwd(var That:tIdx):boolean;	{Ergebnis=FALSE, wenn Schritt nicht m÷glich}
  function CanFwd:Boolean;
  function Back(var That:tIdx):boolean;
  function CanBack:Boolean;
 end;

constructor tHistory.Init;
 begin HistPtr:=0; HistLen:=0; end;

destructor tHistory.Done;
 begin end;

procedure tHistory.Append(const That:tIdx);	{geht immer, notfalls wird das Array verschoben}
 begin
  if HistPtr=HISTSIZE then begin
   Move(Hist[1],Hist[0],sizeof(tIdx)*(HISTSIZE-1));	{Jeden Index vorschieben}
   Dec(HistPtr);
  end;
  Hist[HistPtr]:=That;
  Inc(HistPtr);
  HistLen:=HistPtr;				{LΣnge kⁿrzen}
 end;

function tHistory.Fwd(var That:tIdx):boolean;	{Ergebnis=FALSE, wenn Schritt nicht m÷glich}
 begin
  Fwd:=CanFwd;
  if CanFwd then begin
   That:=Hist[HistPtr];
   Inc(HistPtr);
  end;
 end;

function tHistory.CanFwd:Boolean;
 begin CanFwd:=(HistPtr<HistLen); end;

function tHistory.Back(var That:tIdx):boolean;
 begin
  Back:=CanBack;
  if CanBack then begin
   Dec(HistPtr);
   That:=Hist[HistPtr-1];
  end;
 end;

function tHistory.CanBack:Boolean;
 begin CanBack:=(HistPtr>=2); end;

{Dialog "Neue Seite"}
{objektfrei}
function GotoDlgProc(hWindow:HWnd; Msg,wParam:Word; lParam: LongInt):
  Bool; export;
 var
  lPtr: PChar absolute lParam;	{Vereinfachung des lParam-Zugriffs}
  S: array[0..31]of Char;
  Pg: Word; ec: Integer;
 const
  IdxP: ^TIdx=nil;		{"Transfer-Zeiger"}
 begin
  GotoDlgProc:=false;
  case Msg of
   WM_InitDialog: begin
    IdxP:=Pointer(lParam);	{schlucken}
    Pg:=UsualPage(IdxP^.Page);
    wvsprintf(S,'%X',Pg);	{in Hexzahl konvertieren}
    SendDlgItemMessage(hWindow,101,CB_AddString,0,LongInt(@S));
    SendDlgItemMessage(hWindow,101,CB_SetCurSel,0,0);
    GotoDlgProc:=true;	{?? Immer diese Objekte !!}
   end;

   WM_Command: case wParam of
    IDOK: begin
{     Pg:=Word(SendDlgItemMessage(hWindow,101,CB_GetCurSel,0,0));
     SendDlgItemMessage(hWindow,101,CB_GetLBText,Pg,LongInt(@S[1]));}
     GetDlgItemText(hWindow,101,@S[1],sizeof(S)-1);
     S[0]:='$';		{Hexzahl erzwingen}
     Val(S,Pg,ec);
     if ec=0 then begin
      IdxP^.Page:=Pg and $7FF;
      IdxP^.SubP:=0;
      EndDialog(hWindow,1);
     end else begin
      MessageBox(hWindow,'Falsche Ziffern!',S,MB_OK or MB_IconExclamation);
     end;
    end;

    IDCancel: EndDialog(hWindow,0);

    101: begin
{hier Routinen zum Zeichnen der künftigen Owner-Draw-Listbox}
    end;

   end;

  end;
 end;

{Dialog "Ränder"}
type
 TMargin=record
  left,right: Integer;
  enabled, Strichkiller: Bool;
 end;
{objektfrei}
function MarginDlgProc(hWindow:HWnd; Msg,wParam:Word; lParam: LongInt):
  Bool; export;
 var
  TranslateOK: Bool;
  Margin: TMargin;	{Kopie auf Stack fürs Einsammeln der Werte}
 const
  MP: ^TMargin=nil;	{"Transfer-Zeiger"}
 begin
  MarginDlgProc:=false;
  case Msg of
   WM_InitDialog: begin
    MP:=Pointer(lParam);	{schlucken}
    SetDlgItemInt(hWindow,101,MP^.left,true);
    SetDlgItemInt(hWindow,102,MP^.right,true);
    CheckDlgButton(hWindow,103,Word(MP^.enabled));
    CheckDlgButton(hWindow,104,Word(MP^.StrichKiller));
    SendMessage(hWindow,WM_Command,103,MakeLong(0,BN_Clicked));
    MarginDlgProc:=true;	{?? Immer diese Objekte !!}
   end;

   WM_Command: case wParam of
    IDOK: begin
     Margin.left:=GetDlgItemInt(hWindow,101,@TranslateOK,true);
     if TranslateOK then begin
      Margin.right:=GetDlgItemInt(hWindow,102,@TranslateOK,true);
      if TranslateOK then begin
       if (0<Margin.left)
       and (Margin.left<=Margin.right)
       and (Margin.right<=40) then begin
	Margin.enabled:=Bool(IsDlgButtonChecked(hWindow,103));
	Margin.Strichkiller:=Bool(IsDlgButtonChecked(hWindow,104));
	MP^:=Margin;	{Transfer 'runter}
	EndDialog(hWindow,1);
	exit;
       end;
      end;
     end;
     MessageBox(hWindow,'Falsche Eingabe!',nil,MB_OK or MB_IconExclamation);
    end;

    IDCancel: EndDialog(hWindow,0);

    103: if HiWord(lParam)=BN_Clicked then begin
     Margin.enabled:=Bool(IsDlgButtonChecked(hWindow,103));
     EnableWindow(GetDlgItem(hWindow,101),Margin.enabled);
     EnableWindow(GetDlgItem(hWindow,102),Margin.enabled);
    end;
   end{case wParam};

  end{case Msg};
 end;

{Dialog "Info"}
{objektfrei}
function InfoDlgProc(hWindow:HWnd; Msg,wParam:Word; lParam: LongInt):
  Bool; export;
 const
  Inf1: array[4..11]of PChar=(
   'C4-L÷schen einer Seite','C5-Schlagzeilen',
   'C6-Untertitel','C7-Unterdrⁿcken der Kopfzeile',
   'C8-Texterneuerung','C9-Unterbrochene Sequenz',
   'C10-Unterdrⁿckung der Darstellung','C11-Serielle Magazinfolge');
  Inf2: array[0..7]of PChar=(
   'Englisch(0)','Belgisch/Franz÷sisch(4)',
   'Schwedisch/Finnisch(2)','dynamische Zeichenzuordnung(6)',
   'Deutsch(1)','Portugisisch/Spanisch(5)',
   'Italienisch(3)','reserviert(7)');
 var
  I: Integer;
 begin
  InfoDlgProc:=false;
  case Msg of
   WM_InitDialog: begin	{lParam enthält die C-Bits}
    SetDlgItemText(hWindow,101,Inf2[(lParam shr 12)and 7]);
    for I:=4 to 11 do
     if lParam and (1 shl I) <>0 then
      SendDlgItemMessage(hWindow,102,LB_AddString,0,LongInt(Inf1[I]));
    InfoDlgProc:=true;	{?? Immer diese Objekte !!}
   end;

   WM_Command: case wParam of
    IDOK: EndDialog(hWindow,1);

   end{case wParam};

  end{case Msg};
 end;

{Flags für momentan angezeigte doppelte Höhen usw. setzen,
 wichtig für eine korrekte Markierungsfunktion}
const
 fdDblH=$40;
 fdPrevDblH=$80;
procedure CheckVisibleDblHs(ShowDblH: Bool);
 var
  Line: Integer;
  PDblH: Boolean;
 begin
  PDblH:=false;
  for Line:=0 to 24 do begin
{Bits vorsorglich löschen}
   VT.LineInfo[Line]:=VT.LineInfo[Line] and not (fdPrevDblH or fdDblH);
   if ShowDblH then begin
    if PDblH then begin
     VT.LineInfo[Line]:=VT.LineInfo[Line] or fdPrevDblH;
     PDblH:=false;
{egal, ob die aktuelle Zeile auch doppelt hohe Zeichen enthielt!}
    end else if VT.LineInfo[Line] and fDblH <>0 then begin
     if Line<>24 then VT.LineInfo[Line]:=VT.LineInfo[Line] or fdDblH;
     PDblH:=true;
    end;
   end;
  end;
 end;

{ TVTWindow, a TWindow descendant }
const
 DOSShowCmd:Integer=SW_Hide;
var
 DirName: array[0..fsPathName] of Char;	{Aktuelles Verzeichnis}
 AFileName: array[byte] of Char;
const
 Unzipper: array[0..127]of Char='pkunzip -o -j';
type
 PVTWindow = ^TVTWindow;
 TVTWindow = object(TWindow)
  Bitmaps:array[1..2]of record
   h:HBitmap;
   valid:boolean;
  end;
  hCurArrow,hCurHand: hCursor;	{Unsere 2 Mauspfeile}
{    IconImageValid: Boolean;}
  sWidth,sHeight: Integer;	{40 oder 41, 24 oder 25}
  Mode: Longint;
  CurBitmapIdx:integer;	{Dieser schaltet hin und her (1 oder 2)}
  MenuFlags:word;		{hier sind die Flags wie QUIZ usw. drin}
  MenuFont:word;		{hier ist die Font-Nummer gespeichert}
  Current:tIdx;		{Aktuell angezeigte Seite/Unterseite}
  History:tHistory;		{GeschichtsObjekt}
{Suchen-Standard-Dialogbox}
  HFind: HWnd;
  FindReplace: TFindReplace;	{So 'ne Struktur fürs CommDlg}
  FindWhat: array[0..127]of char;	{Suchstring}
  IDFR: Word;			{ID von Find/Replace}
  SuchStart: TIdx;		{Index, bei der die Suche begann}
{Gepackter Videotext}
  TempFileName: PChar;	{Zum Löschen einer evtl. temporären Datei}
  SelectedRect:	TRect;	{der erste Punkt ist hierbei nicht unbedingt oben links}
  SelectedRegion: HRgn;	{So löst man das Markierungsproblem elegant!}
  Margin: TMargin;
  hStatic: HWnd;
  hGray: HWnd;
  hFwdBtn,hBackBtn: HWnd;
{  sfontw,sfonth:Integer;	{Bildschirm-Fontgrößen}
  constructor Init(ATitle: PChar);
  destructor Done; virtual;
{    function GetClassName : PChar; virtual;}
  procedure GetWindowClass(var WndClass: TWndClass); virtual;
  procedure DefWndProc(var Msg: TMessage); virtual;
  procedure Paint(PaintDC: HDC; var PaintInfo: TPaintStruct); virtual;
  procedure CMFileOpen(var Msg: TMessage); virtual cm_First+102;
  procedure CMFileSave(var Msg: TMessage); virtual cm_First+104;
  procedure CMSetUnzipper(var Msg: TMessage); virtual cm_First+2020;
  procedure CMQuit(var Msg: TMessage); virtual cm_First+108;
  procedure CMSDM(var Msg: TMessage); virtual cm_First+109;
{Oh, ich hasse Kapselklassen! Alles muss man 6* schreiben!}
  procedure CMNextPage(var Msg: TMessage); virtual cm_First+301;
  procedure CMPrevPage(var Msg: TMessage); virtual cm_First+302;
  procedure CMNextSubp(var Msg: TMessage); virtual cm_First+303;
  procedure CMPrevSubp(var Msg: TMessage); virtual cm_First+304;
  procedure CMHomePage(var Msg: TMessage); virtual cm_First+305;
  procedure CMCertainPage(var Msg: TMessage); virtual cm_First+306;
  procedure CMNext(var Msg: TMessage); virtual cm_First+307;
  procedure CMBack(var Msg: TMessage); virtual cm_First+308;
  procedure CMFwd(var Msg: TMessage); virtual cm_First+309;
  procedure IDNextPage(var Msg: TMessage); virtual ID_First+301;
  procedure IDPrevPage(var Msg: TMessage); virtual ID_First+302;
  procedure IDNext(var Msg: TMessage); virtual ID_First+303;
  procedure IDPrev(var Msg: TMessage); virtual ID_First+304;
  procedure IDBack(var Msg: TMessage); virtual ID_First+308;
  procedure IDFwd(var Msg: TMessage); virtual ID_First+309;
  procedure CMCopy(var Msg: TMessage); virtual cm_First+202;
  procedure CMInfo(var Msg: TMessage); virtual cm_First+205;
  procedure CMFind(var Msg: TMessage); virtual cm_First+203;
  procedure CMFindNext(var Msg: TMessage); virtual cm_First+204;
  procedure CMSetMargin(var Msg: TMessage); virtual cm_First+208;
  procedure Suche;		{Suchfunktion, benutzt FindReplace.Flags}

  procedure CMQuiz(var Msg:TMessage); virtual cm_First+401;
  procedure CMBlink(var Msg:TMessage); virtual cm_First+402;
  procedure CMDblH(var Msg:TMessage); virtual cm_First+403;
  procedure CMMix(var Msg:TMessage); virtual cm_First+404;
  procedure CMInvers(var Msg:TMessage); virtual cm_First+405;
  procedure CMS_W(var Msg:TMessage); virtual cm_First+406;
  procedure CMDebug(var Msg:TMessage); virtual cm_First+407;
  procedure CMRoll(var Msg:TMessage); virtual cm_First+408;
  procedure CMSetDblSize(var Msg:TMessage); virtual cm_First+409;
  procedure CMSet41(var Msg:TMessage); virtual cm_First+410;
  procedure CMSet25(var Msg:TMessage); virtual cm_First+411;

  procedure CMFontA(var Msg:TMessage); virtual cm_First+501;	{auto}
  procedure CMFontD(var Msg:TMessage); virtual cm_First+502;	{deutsch}
  procedure CMFontE(var Msg:TMessage); virtual cm_First+503;	{engl.}
  procedure CMFontW(var Msg:TMessage); virtual cm_First+504;	{schwed./finn.}
  procedure CMFontP(var Msg:TMessage); virtual cm_First+505;	{poln.}
  procedure CMFontT(var Msg:TMessage); virtual cm_First+506;	{tschech./slowak.}
  procedure CMFontI(var Msg:TMessage); virtual cm_First+507;	{ital.}
  procedure CMFontF(var Msg:TMessage); virtual cm_First+508;	{frz./belg.}
  procedure CMFontS(var Msg:TMessage); virtual cm_First+509;	{span./port.}
  procedure CMFontL(var Msg:TMessage); virtual cm_First+510;	{dΣn./islΣnd.}
  procedure CMFontU(var Msg:TMessage); virtual cm_First+511;	{ungar.}
  procedure CMFontR(var Msg:TMessage); virtual cm_First+512;	{tⁿrk.}
  procedure CMFontK(var Msg:TMessage); virtual cm_First+515;	{kyrill.}
  procedure CMFontB(var Msg:TMessage); virtual cm_First+516;	{arab.}

  procedure CMHelp(var Msg:TMessage); virtual cm_First+901;	{Hilfe}
  procedure CMAbout(var Msg:TMessage); virtual cm_First+999;	{About-Box}
  procedure FontFail;			{Fehlermeldung: Font nicht da}
  procedure WMGetMinMaxInfo(var Msg: TMessage); virtual wm_First + wm_GetMinMaxInfo;
  procedure WMSysCommand(var Msg: TMessage); virtual wm_First + wm_SysCommand;
  procedure WMMove(var Msg: TMessage); virtual WM_First + WM_Move;
  procedure WMSize(var Msg: TMessage); virtual WM_First + WM_Size;
{    procedure AdjustScroller;}
  function LoadVTFile(Name: PChar): Boolean;
{    function OpenDIB(var TheFile: File): Boolean;}
{    procedure GetBitmapData(var TheFile: File;
    BitsHandle: THandle; BitsByteSize: Longint);}
  procedure WMTimer(var Msg: TMessage); virtual wm_First+wm_Timer;
{  procedure WM_INITMENU(var Msg: TMessage); virtual wm_First+wm_INITMENU;}
  procedure StartBlink;
  procedure StopBlink;
  procedure RemoveAnySelection;
  procedure WMLButtonDown(var Msg: TMessage); virtual wm_First+WM_LBUTTONDOWN;
  procedure WMRButtonDown(var Msg: TMessage); virtual wm_First+WM_RBUTTONDOWN;
  procedure WMMouseMove(var Msg: TMessage); virtual WM_First+WM_MouseMove;
  procedure WMLButtonUp(var Msg: TMessage); virtual WM_First+WM_LButtonUp;
  procedure WMParentNotify(var Msg: TMessage); virtual WM_First+WM_ParentNotify;
  procedure SetupWindow; virtual;
  procedure WMUser88(var Msg: TMessage); virtual WM_First + WM_User+88;
{  procedure Destroy; virtual;}
  function ToggleCheck(TheFlag,MenuItem:word):boolean;	{liefert TRUE wenn Flag gesetzt wird}
  function FontCheck(NewFont:word):integer;		{liefert void}
  procedure UpdateBitmaps;
  procedure UpdateBitmap(BitmapNr:Byte);
      {fⁿllt die beiden Bitmaps mit der aktuellen Seite mit den aktuellen Flags}
  function MBox(StrTableNr:word;var Params;Stil:word):integer;
  procedure Resize;		{Fenstergroesze nach rechts unten aendern}
  procedure RemoveTemporaryFile;{Falls vorhanden temporäre Datei löschen}
  procedure CalcWindowSize(MFlags:Word; var P:TPoint);	{Fenstergröße (nicht nur Client) berechnen}
  procedure CalcSFontX;
 end;

procedure TVTWindow.CalcSFontX;
 begin
  sfontw:=FONTW; if MenuFlags and fDblSize <>0 then sfontw:=2*FONTW;
  sfonth:=FONTH; if MenuFlags and fDblSize <>0 then sfonth:=2*FONTH;
 end;

procedure TVTWindow.CMQuit(var Msg: TMessage);
 begin
  CloseWindow;
 end;

{procedure TVTWindow.WM_INITMENU(var Msg: TMessage);
 begin
  MessageBeep(MB_OK);
  CheckMenuItem(Msg.WParam,SC_MOVE,MF_BYCOMMAND);
 end;}

procedure TVTWindow.WMGetMinMaxInfo(var Msg: TMessage);
 begin
  with TMinMaxInfo(Pointer(Msg.LParam)^) do begin
   ptMaxPosition.X:=Attr.X;
   ptMaxPosition.Y:=Attr.Y;	{Linke obere Ecke bleibt}
   ptReserved.X:=Attr.X;
   ptReserved.Y:=Attr.Y;	{Linke obere Ecke bleibt}
   CalcWindowSize(MenuFlags or fDblSize,ptMaxSize);	{Größe wird hier vorgegeben}
  end;
 end;

procedure TVTWindow.WMMove(var Msg: TMessage);
 var R: TRect;
 begin
  if not IsIconic(HWindow) then begin
   GetWindowRect(HWindow,R);
   Attr.X:=R.Left;	{Attr's mitführen, auch wenn großgestellt!}
   Attr.Y:=R.Top;
  end {else inherited WMMove(Msg)};
 end;

procedure TVTWindow.WMSize(var Msg: TMessage);
 var R: TRect;
 begin
  inherited WMSize(Msg);
  if not IsIconic(HWindow) then begin
   GetClientRect(HWindow,R);
   MoveWindow(hStatic,0,R.bottom-20,
     CalculateWidth(MenuFlags or fPixels),20,true);
  end;
 end;

procedure TVTWindow.DefWndProc(var Msg: TMessage);
 begin
  if Msg.Message=IDFR then begin
{   MessageBeep(Word(-1));}
   if FindReplace.Flags and FR_DialogTerm <>0 then begin
    HFind:=0;
    FindReplace.Flags:=FindReplace.Flags and not FR_DialogTerm;
   end else begin
    if FindReplace.Flags and FR_FindNext =0 then begin
     Suchstart:=Current;
     MessageBeep(Word(-1));	{Beep wenn Neue Suche}
    end;
    FindReplace.Flags:=FindReplace.Flags and not FR_FindNext;
    Suche;
   end;
  end else inherited DefWndProc(Msg);
 end;

procedure TVTWindow.WMSysCommand(var Msg: TMessage);
 begin
  case (Msg.wParam and $FFF0) of
   SC_MAXIMIZE: begin
    if MenuFlags and fDblSize =0 then ToggleCheck(fDblSize,409);
   end;
   SC_RESTORE: begin
    if not IsIconic(HWindow) then
     if MenuFlags and fDblSize <>0 then ToggleCheck(fDblSize,409);
   end;
  end;
  inherited WMSysCommand(Msg);
 end;

procedure TVTWindow.CMSDM(var Msg: TMessage);
 begin
  VTWApp.ExecDialog(New(PSDMDlg,Init(@self,'SDM')));
 end;

procedure TVTWindow.CMHelp(var Msg:TMessage);
 begin
  MessageBox(hWindow,'Hilfe nicht verfügbar!','Pardon',MB_OK);
{  WinHelp(hWindow,'VTW.HLP',HELP_INDEX,0);}
 end;

procedure TVTWindow.CMNextPage(var Msg: TMessage);
 begin
  if VT.SelectLoadPage(Current,ADDNEXTPAGE)<>$FFFF then begin
   History.Append(Current);
   UpdateBitmaps;
  end;
 end;
procedure TVTWindow.CMPrevPage(var Msg: TMessage);
 begin
  if VT.SelectLoadPage(Current,SUBPREVPAGE)<>$FFFF then begin
   History.Append(Current);
   UpdateBitmaps;
  end;
 end;
procedure TVTWindow.CMNextSubp(var Msg: TMessage);
 begin
  if VT.SelectLoadPage(Current,ADDNEXTSUB)<>$FFFF then begin
   History.Append(Current);
   UpdateBitmaps;
  end;
 end;
procedure TVTWindow.CMPrevSubp(var Msg: TMessage);
 begin
  if VT.SelectLoadPage(Current,SUBPREVSUB)<>$FFFF then begin
   History.Append(Current);
   UpdateBitmaps;
  end;
 end;
procedure TVTWindow.CMHomePage(var Msg: TMessage);
 begin
  if VT.SelectLoadPage(Current,HOMEPAGE)<>$FFFF then begin
   History.Append(Current);
   UpdateBitmaps;
  end;
 end;
procedure TVTWindow.CMCertainPage(var Msg: TMessage);
 var
  TempIdx: TIdx;
 begin
  TempIdx:=Current;		{statische Variable; etwas russisch}
  if DialogBoxParam(hInstance,'GOTO',hWindow,@GotoDlgProc,LongInt(@TempIdx))
    =IDOK then begin
   if VT.SelectLoadPage(TempIdx,NEXTSUB)<>$FFFF then begin
    Current:=TempIdx;		{rückschreiben}
    History.Append(Current);
    UpdateBitmaps;
   end else begin
    MBox(3,TempIdx.Page,MB_OK or MB_IconExclamation)
   end;
  end;
 end;

procedure TVTWindow.CMNext(var Msg: TMessage);
 begin
   if VT.SelectLoadPage(Current,ADDNEXTSUBPAGE)<>$FFFF then begin
    History.Append(Current);
    UpdateBitmaps;
   end;
 end;

procedure TVTWindow.CMBack(var Msg: TMessage);
 begin
  if History.Back(Current) then begin
   if VT.SelectLoadPage(Current,EQUALPAGE)<>$FFFF then begin
    UpdateBitmaps
   end else begin
    {MBox}
   end;
  end;
 end;

procedure TVTWindow.IDNextPage(var Msg: TMessage);
 begin
  CMNextPage(Msg);
 end;

procedure TVTWindow.IDPrevPage(var Msg: TMessage);
 begin
  CMPrevPage(Msg);
 end;

procedure TVTWindow.IDNext(var Msg: TMessage);
 begin
  CMNext(Msg);
 end;

procedure TVTWindow.IDPrev(var Msg: TMessage);
 begin
  if VT.SelectLoadPage(Current,SUBPREVSUBPAGE)<>$FFFF then begin
   History.Append(Current);
   UpdateBitmaps;
  end;
 end;

procedure TVTWindow.IDBack(var Msg: TMessage);
 begin
  CMBack(Msg);
 end;

procedure TVTWindow.IDFwd(var Msg: TMessage);
 begin
  CMFwd(Msg);
 end;

procedure TVTWindow.WMRButtonDown(var Msg: TMessage);
 begin CMBack(Msg); end;

procedure TVTWindow.CMFwd(var Msg: TMessage);
 begin
  if History.Fwd(Current) then begin
   if VT.SelectLoadPage(Current,EQUALPAGE)<>$FFFF then begin
    UpdateBitmaps
   end else begin
    {MBox}
   end;
  end;
 end;

procedure TVTWindow.CMInfo(var Msg: TMessage);
 begin
  DialogBoxParam(hInstance,'INFO',hWindow,@InfoDlgProc,VT.Header.CBits);
 end;

procedure SortRect(var R:TRect); forward;

procedure TVTWindow.CMCopy(var Msg: TMessage);
 var
  Puffer: array[0..1200]of Char;
  HMem: THandle;
  OldBitmap, HBM: HBitmap;
  HilfDC, BMDC: HDC;
  P: PChar;
  R: TRect;
 begin
  CopyRect(R,SelectedRect); SortRect(R);
  VT.MkText(Puffer,MenuFlags or fBlink,MenuFont,R);
  HMem:=GlobalAlloc(GMEM_Share,StrLen(Puffer));
  P:=GlobalLock(HMem);
  StrCopy(P,Puffer);
  GlobalUnlock(HMem);

  HilfDC:=GetDC(HWindow);
  BMDC:=CreateCompatibleDC(HilfDC);
  HBM:=CreateCompatibleBitmap(HilfDC,41*FONTW,25*FONTH);
  ReleaseDC(HWindow,HilfDC);
  OldBitmap:=SelectObject(BMDC,HBM);
  VT.MkBitmap(BMDC,MenuFlags or fBlink,MenuFont,R);
  SelectObject(BMDC,OldBitmap);
  DeleteDC(BMDC);

  OpenClipboard(HWindow);
  EmptyClipboard;
  SetClipboardData(CF_Bitmap,HBM);
  SetClipboardData(CF_Text,HMem);
  CloseClipboard;
 end;

procedure TVTWindow.CMFind(var Msg: TMessage);
 begin
  if HFind=0 then
   HFind:=FindText(FindReplace)
  else ShowWindow(HFind,SW_Restore);
  Suchstart:=Current;	{Kopieren}
 end;

procedure TVTWindow.CMFindNext(var Msg: TMessage);
 begin
  Suche;
 end;

procedure TVTWindow.CMSetMargin(var Msg: TMessage);
 begin
  DialogBoxParam(hInstance,'MARGIN',hWindow,@MarginDlgProc,LongInt(@Margin));
 end;

function _Upcase:Char; assembler;
 asm	cmp	al,'a'
	jc	@@e
	cmp	al,'z'
	ja	@@e
	sub	al,20h
@@e:	end;

function StrIPos(S,SubS:PChar):PChar; assembler;
 asm	push	ds
	lds	si,[S]
	les	di,[SubS]
@@o:	mov	al,es:[di]
	or	al,al
	jz	@@em		{Substring leer: "Gefunden" melden}
	call	_UpCase
	mov	ah,al
	cld
@@l:	lodsb		{AnsiNext}
	or	al,al
	jz	@@e1		{String zu Ende: abbrechen mit NIL}
	call	_UpCase
	cmp	al,ah
	jne	@@l
	push	si
	push	di
@@l1:	inc	di	{AnsiNext}
	mov	al,es:[di]
	or	al,al
	jz	@@f		{Substring zu Ende: Suche geschafft!}
	call	_UpCase
	mov	ah,al
	lodsb
	call	_UpCase
	cmp	al,ah
	jz	@@l1	{Zeichen ungleich oder String zu Ende: hier abbrechen}
	pop	di
	pop	si
	jmp	@@o	{AH neuladen}
@@f:
	pop	di
	pop	si
	dec	si		{Gefunden, Zeiger 1 Zeichen zurück}
@@em:	mov	dx,ds
	mov	ax,si
	jmp	@@e
@@e1:	cbw
	cwd
@@e:	pop	ds
	end;

procedure TVTWindow.Suche;
 var
  OldCursor: HCursor;
  Puffer: array[0..1200]of Char;
  R: TRect;
  Richtung: TSearchMode;
  SP: PChar;
 label
  l1;
 begin
  OldCursor:=SetCursor(LoadCursor(0,IDC_WAIT));
{  MessageBox(HWindow,FindWhat,'Suche jetzt:',0);}
  SetRect(R,0,0,40,24);
  Richtung:=ADDNEXTSUBPAGE;
  if FindReplace.Flags and FR_Down <>0 then Richtung:=SUBPREVSUBPAGE;
  repeat
   if VT.SelectLoadPage(Current,Richtung)=$FFFF then break;
   VT.MkText(Puffer,MenuFlags or fBlink,MenuFont,R);
   if FindReplace.Flags and FR_MatchCase <>0
   then SP:=StrPos(Puffer,FindWhat)
   else SP:=StrIPos(Puffer,FindWhat);
   if SP<>nil then begin
    History.Append(Current);
    UpdateBitmaps;
    goto l1;
   end;
  until Suchstart.L=Current.L;
  MessageBox(HWindow,'Suchbegriff nicht gefunden!',
    'VTW',MB_OK or MB_IconAsterisk);
l1:
  SetCursor(OldCursor);
 end;


function TVTWindow.MBox(StrTableNr:word;var Params;Stil:word):integer;
 var tmp,tmp2:array[0..255]of char;
  p:PChar;
 begin
  p:=nil;			{Standard-Header}
  if LoadString(hInstance,StrTableNr,tmp,sizeof(tmp))>strlen(tmp) then
   p:=strend(tmp)+1;		{Zeiger hinter die Trenn-Null versetzen}
  wvsprintf(tmp2,tmp,Params);
  MBox:=MessageBox(HWindow,tmp2,p,Stil);
 end;

procedure TVTWindow.CMAbout;
 begin MBox(6,Mem[0:0],MB_OK OR MB_ICONEXCLAMATION); end;

procedure TVTWindow.StartBlink;
 begin
  if (MenuFlags and fBlink <>0) and VT.PageContain(8) then
   if SetTimer(HWindow,12,1500,nil)=0 then begin
    MBox(2,self,MB_OK);
   end;
 end;

procedure TVTWindow.StopBlink;
 begin
  KillTimer(HWindow,12);
  if CurBitmapIdx=2 then begin
   CurBitmapIdx:=1;
   InvalidateRect(HWindow,nil,FALSE);	{mit true wⁿrde es wei▀ aufflackern}
  end;
 end;

procedure TVTWindow.CMQuiz(var Msg:TMessage);
 begin
  ToggleCheck(fQuiz,401);
  if VT.PageContain(24) then UpdateBitmaps;	{Nur wenn Quiz-Zeichen enthalten}
 end;

procedure TVTWindow.CMBlink(var Msg:TMessage);
 begin
  if ToggleCheck(fBlink,402) then StartBlink
  else StopBlink;
 end;

procedure TVTWindow.CMDblH(var Msg:TMessage);
 begin
  ToggleCheck(fDblH,403);
  CheckVisibleDblHs(MenuFlags and fDblH <>0);
  if VT.PageContain(13) then UpdateBitmaps;	{Nur wenn Doppelte H÷he enthalten}
 end;

procedure TVTWindow.CMMix(var Msg:TMessage);
 begin
  ToggleCheck(fMix,404);
  if VT.Header.CBits and $60 =0 then UpdateBitmaps;
 end;

procedure TVTWindow.CMInvers(var Msg:TMessage);
 begin
  ToggleCheck(fInvers,405);
  UpdateBitmaps;
 end;

procedure TVTWindow.CMS_W(var Msg:TMessage);
 begin
  MenuFlags:=MenuFlags xor fBunt;
  ToggleCheck(fBunt,406);		{Hier ist's intern andersherum}
  MenuFlags:=MenuFlags xor fBunt;
  UpdateBitmaps;
 end;

procedure TVTWindow.CMDebug(var Msg:TMessage);
 begin
  ToggleCheck(fDebug,407);
  UpdateBitmaps;
 end;

procedure TVTWindow.CMRoll(var Msg:TMessage);
 begin
  ToggleCheck(fRoll,408);
  {UpdateBitmaps;}
 end;

function TVTWindow.ToggleCheck;
 var Check: word;
 begin
  if MenuFlags and TheFlag <>0 then Check:=MF_BYCOMMAND or mf_unchecked
  else Check:=mf_bycommand or mf_checked;
  MenuFlags:=MenuFlags xor TheFlag;
  CheckMenuItem(Attr.Menu,MenuItem,Check);
  ToggleCheck:=MenuFlags and TheFlag <>0;	{Rⁿckgabewert}
 end;

procedure TVTWindow.CMFontA(var Msg:TMessage);
 begin FontCheck(0); UpdateBitmaps; end;
procedure TVTWindow.CMFontD(var Msg:TMessage);
 begin FontCheck(1); UpdateBitmaps; end;
procedure TVTWindow.CMFontE(var Msg:TMessage);
 begin FontCheck(2); UpdateBitmaps; end;
procedure TVTWindow.CMFontW(var Msg:TMessage);
 begin FontCheck(3); UpdateBitmaps; end;
procedure TVTWindow.CMFontP(var Msg:TMessage);
 begin FontCheck(4); UpdateBitmaps; end;
procedure TVTWindow.CMFontT(var Msg:TMessage);
 begin FontCheck(5); UpdateBitmaps; end;
procedure TVTWindow.CMFontI(var Msg:TMessage);
 begin FontCheck(6); UpdateBitmaps; end;
procedure TVTWindow.CMFontF(var Msg:TMessage);
 begin FontCheck(7); UpdateBitmaps; end;
procedure TVTWindow.CMFontS(var Msg:TMessage);
 begin FontCheck(8); UpdateBitmaps; end;
procedure TVTWindow.CMFontL(var Msg:TMessage);
 begin FontCheck(9); UpdateBitmaps; end;
procedure TVTWindow.CMFontU(var Msg:TMessage);
 begin FontCheck(10); UpdateBitmaps; end;
procedure TVTWindow.CMFontR(var Msg:TMessage);
 begin FontCheck(11); UpdateBitmaps; FontFail; end;
procedure TVTWindow.CMFontK(var Msg:TMessage);
 begin FontCheck(14); UpdateBitmaps; end;
procedure TVTWindow.CMFontB(var Msg:TMessage);
 begin FontCheck(15); UpdateBitmaps; FontFail; end;

function TVTWindow.FontCheck;
 begin
  CheckMenuItem(Attr.Menu,501+MenuFont,MF_BYCOMMAND or MF_UNCHECKED);
  MenuFont:=NewFont;
  CheckMenuItem(Attr.Menu,501+MenuFont,MF_BYCOMMAND or MF_CHECKED);
 end;

procedure TVTWindow.FontFail;
 begin
  MBox(1,self,MB_OK);
 end;

procedure TVTWindow.UpdateBitmaps;
 begin
  Bitmaps[1].valid:=false;
  Bitmaps[2].valid:=false;
  CurBitmapIdx:=1;
  StopBlink;
  CheckVisibleDblHs(MenuFlags and fDblH <>0);
  EnableWindow(hBackBtn,History.CanBack);
  EnableWindow(hFwdBtn,History.CanFwd);
  VT.MkLinkList(hWindow,MenuFlags);		{Neue Linkliste erzeugen (dauert nicht lange??)}
  InvalidateRect(HWindow,nil,FALSE);	{mit true wⁿrde es wei▀ aufflackern}
  SetWindowText(hStatic,VT.AlterStr);
  StartBlink;
 end;

procedure TVTWindow.CalcWindowSize(MFlags:Word; var P: TPoint);
 var
  R: TRect;
 begin
  CalcSFontX;		{Neue Bildschirmfontgrößen berechnen}
  R.Left:=0; R.Top:=0;
  R.Right:=CalculateWidth(MFlags or fPixels);
  R.Bottom:=CalculateHeight(MFlags or fPixels);
  AdjustWindowRect(R,Attr.Style,TRUE);
  P.X:=R.Right-R.Left;
  P.Y:=R.Bottom-R.Top+20+20;	{Button- und Statuszeile}
 end;

procedure TVTWindow.Resize;		{Keine Übergabe der neuen Größen}
 var
  P: TPoint;
 begin
  CalcWindowSize(MenuFlags,P);
  MoveWindow(HWindow,Attr.X,Attr.Y,P.X,P.Y,TRUE);
	{mit Befehl zum Neuzeichnen}
 end;

procedure TVTWindow.CMSet25;
 begin
  if ToggleCheck(f25,411) then begin
   Bitmaps[1].valid:=false; Bitmaps[2].valid:=false;
  end;
  Resize;
 end;

procedure TVTWindow.CMSet41;
 begin
  if ToggleCheck(f41,410) then begin
   Bitmaps[1].valid:=false; Bitmaps[2].valid:=false;
  end;
  Resize;
 end;

procedure TVTWindow.CMSetDblsize;
 begin
  if IsZoomed(HWindow) then
   PostMessage(HWindow,WM_SYSCOMMAND,SC_RESTORE,0)
  else
   PostMessage(HWindow,WM_SYSCOMMAND,SC_ZOOM,0);
{  ToggleCheck(fDblSize,409);
  Resize;}
 end;

procedure TVTWindow.UpdateBitmap(BitmapNr:byte);
 var DC,MemDC:HDC;
  OldCursor:hCursor;
  OldBitmap:hBitmap;
  R: TRect;	{noch Dummy}
 begin
  DC := GetDC(HWindow);
  MemDC:= CreateCompatibleDC(DC);
  ReleaseDC(HWindow, DC);
  OldCursor := SetCursor(LoadCursor(0,IDC_WAIT));	{LoadCursor(HWindow,'CURSOR_1'))}
  OldBitmap:= SelectObject(MemDC, Bitmaps[BitmapNr].h);
  if BitmapNr=2 then
  VT.MkBitmap(MemDC,MenuFlags and not fBlink,MenuFont,R)
  else VT.MkBitmap(MemDC,MenuFlags or fBlink,MenuFont,R);
  SelectObject(MemDC,OldBitmap);
  DeleteDC(MemDC);
  SetCursor(OldCursor);
  Bitmaps[BitmapNr].valid:=TRUE;
 end;

procedure TVTWApp.InitMainWindow;
 begin
  MainWindow := New(PVTWindow, Init(VTW_Name));
 end;

constructor TVTWindow.Init(ATitle: PChar);
 var
  e,f,i,TheFlag:word;
  DCHandle: HDC;
  R:TRect;
 begin
  SDM:=false;
  CurBitmapIdx:=0;		{nichts anbrennen lassen!}
  inherited Init(nil, ATitle);
  MenuFlags:=fBunt;
  MenuFont:=0;			{Default Font}
  SelectedRegion:=0;		{nichts selektiert}
  TempFileName:=nil;		{Keine temporäre Datei zu löschen}
  HFind:=0;			{sonst gehen keine Hotkeys!!!}
  Margin.left:=2;
  Margin.right:=40;
  Margin.enabled:=false;	{Ränder festlegen}
  Margin.StrichKiller:=true;	{Strichkiller aktivieren}

  Attr.Menu := LoadMenu(HInstance,MAKEINTRESOURCE(49));
{  Attr.ExStyle:=WS_EX_Transparent;}
  e:=GetProfileInt(VTW_Name,'Flag',fDblH or fBlink or fBunt or f41) xor fBunt;
  f:=GetProfileInt(VTW_Name,'Font',0);
  TheFlag:=1;
  for i:=401 to 411 do begin
   if e and TheFlag <>0 then ToggleCheck(TheFlag,i);	{MenⁿhΣkchen setzen}
   TheFlag:=TheFlag shl 1;		{naechstes Flag}
  end;
  FontCheck(f);				{Font setzen wie letztens}
  GetProfileString(VTW_Name,'LDir','',DirName,sizeof(DirName));
					{NIL statt '' ist nicht erlaubt}
  with Attr do begin
   Style := Style and not ws_thickframe or CS_BYTEALIGNWINDOW;

   X:=Integer(GetProfileInt(VTW_Name,'OrgX',64));	{aus den WIN.INI EintrΣgen}
   Y:=Integer(GetProfileInt(VTW_Name,'OrgY',64));
   CalcWindowSize(MenuFlags and not fDblSize,TPoint(Pointer(@W)^));		{W und H füllen}
  end;
  GetProfileString(VTW_Name,'Unzipper',Unzipper,Unzipper,sizeof(Unzipper));
  if MenuFlags and fDblSize <>0 then CmdShow:=SW_ShowMaximized;
  CalcSFontX;		{Bildschirm-Fontgröße}

  DCHandle := CreateDC('Display', nil, nil, nil);
  Bitmaps[1].h:= CreateCompatibleBitmap(DCHandle,41*FONTW,25*FONTH);
  Bitmaps[2].h:= CreateCompatibleBitmap(DCHandle,41*FONTW,25*FONTH);
  if GetDeviceCaps(DCHandle, numColors) < 3 then Mode := notSrcCopy
  else Mode := srcCopy;			{Müßte auf Monochrom getestet werden!}
  DeleteDC(DCHandle);
 end;

function TVTWApp.ProcessAppMsg;
 begin
  if PVTWindow(MainWindow)^.HFind<>0 then
   ProcessAppMsg:=IsDialogMessage(PVTWindow(MainWindow)^.HFind,Message)
   {Klimmzug erforderlich, sonst gehen keine Tasten im Suchfenster}
  else ProcessAppMsg:=inherited ProcessAppMsg(Message);
 end;

procedure tVTWindow.SetupWindow;
 var
  Sysmenu:hMenu;
  R: TRect;
 begin
  inherited SetupWindow;
  hCurArrow:=LoadCursor(0,IDC_ARROW);
  hCurHand:=LoadCursor(hInstance,'HAND');
  Sysmenu:=GetSystemMenu(hWindow,FALSE);
{  if sysmenu=attr.menu then messagebeep(MB_OK);
  AppendMenu(Sysmenu,MF_BYCOMMAND or MF_STRING,$E000,'Hallo');
  CheckMenuItem(Sysmenu,SC_MOVE,MF_BYCOMMAND or MF_CHECKED);}
  DeleteMenu(Sysmenu,SC_SIZE,MF_BYCOMMAND);
  ModifyMenu(Sysmenu,SC_Zoom,MF_ByCommand,SC_Zoom,'Gro▀&bild');
{Die FindReplace-Struktur vorbelegen}
  FillChar(FindReplace,sizeof(FindReplace),#0);
  with FindReplace do begin
   lStructSize:=sizeof(FindReplace);
   hWndOwner:=HWindow;
{   Flags:=FR_Down or }
   FindWhat[0]:=#0;
   lpstrFindWhat:=FindWhat;		{String-Zeiger festsetzen}
   wFindWhatLen:=sizeof(FindWhat);	{Länge übermitteln}
  end;
  IDFR:=RegisterWindowMessage('commdlg_FindReplace');
{  DeleteMenu(Sysmenu,SC_ZOOM,MF_BYCOMMAND);}	{Ohne Menüpunkt geht's nicht?}
{  DrawMenuBar(hwindow);}
{  Attr.Menu:=Sysmenu;}
  StartBlink;
{  SetRect(R,0,0,20,20);
  CreateInvisible(HWindow,R,MakeLong($100,$100));}
  hBackBtn:=CreateWindow('BUTTON','Zurⁿck',BS_PushButton or WS_Child or WS_Visible or WS_Disabled,
    0,0,60,20,HWindow,308,HInstance,nil);
{  CreateWindow('BUTTON','S100',BS_PushButton or WS_Child or WS_Visible,
    40,0,40,20,HWindow,305,HInstance,nil);}
  CreateWindow('BUTTON','Seite-',BS_PushButton or WS_Child or WS_Visible,
    60,0,50,20,HWindow,302,HInstance,nil);
  CreateWindow('BUTTON','Unter-',BS_PushButton or WS_Child or WS_Visible,
    110,0,50,20,HWindow,304,HInstance,nil);
  CreateWindow('BUTTON','Unter+',BS_PushButton or WS_Child or WS_Visible,
    160,0,50,20,HWindow,303,HInstance,nil);
  CreateWindow('BUTTON','Seite+',BS_PushButton or WS_Child or WS_Visible,
    210,0,50,20,HWindow,301,HInstance,nil);
{  CreateWindow('BUTTON','Mag+',BS_PushButton or WS_Child or WS_Visible,
    250,0,40,20,HWindow,307,HInstance,nil);}
  hFwdBtn:=CreateWindow('BUTTON','VorwΣrts',BS_PushButton or WS_Child or WS_Visible or WS_Disabled,
    260,0,60,20,HWindow,309,HInstance,nil);
  hStatic:=CreateWindow('STATIC','Statuszeile',WS_Child or WS_Visible or SS_Center,
    0,CalculateHeight(MenuFlags or fPixels)+20,320,20,HWindow,709,HInstance,nil);
  hGray:=CreateWindow('STATIC','',WS_Child or WS_Visible or SS_GrayRect,
    320,0,660,20,HWindow,$FFFF,HInstance,nil);
  PostMessage(HWindow,WM_User+88,0,0);	{Fortfahren...}
 end;

procedure tVTWindow.WMUser88(var Msg: TMessage);
 begin
  if lStrLen(CmdLine)>0 then begin
   UpdateWindow(HWindow);		{Erst mal darstellen}
   if LoadVTFile(CmdLine)=false then	{Datei im voraus laden}
    MBox(5,CmdLine,mb_ok);
  end;
  UpdateBitmaps;
 end;

procedure tVTWindow.WMTimer(var Msg: TMessage);
 var NextTime:word;
 begin
  NextTime:=0;
  if CurBitmapIdx=1 then begin
   CurBitmapIdx:=2; NextTime:=500;	{1/2 Sekunde AUS}
  end else if CurBitmapIdx=2 then begin
   CurBitmapIdx:=1; NextTime:=1500;	{1 1/2 Sekunden EIN}
  end;
  if NextTime<>0 then begin
   if not IsIconic(HWindow) then	{Sonst flackert das Icon}
    InvalidateRect(HWindow,nil,FALSE);	{mit true wⁿrde es wei▀ aufflackern}
   SetTimer(HWindow,12,NextTime,nil);
  end;
 end;

{Funktionen zur Markierungsverwaltung, mit Rundung}
{Ursprung der Ergebnisparameter an der linken oberen Ecke unterhalb der
 Buttons}
procedure PointToRaster(var P:TPoint);
 begin
  P.X:=(P.X+sFontW div 2) div sFontW;
  P.Y:=(P.Y-20) div sFontH;
 end;

procedure SortRect(var R:TRect); assembler;
{sortiert die Punkte, daß die Zuordnungen stimmen}
 asm	les	bx,[R]
	mov	ax,es:TRect[bx].left
	cmp	ax,es:TRect[bx].right
	jl	@@1
	xchg	es:TRect[bx].right,ax
	mov	es:TRect[bx].left,ax
@@1:	mov	ax,es:TRect[bx].top
	cmp	ax,es:TRect[bx].bottom
	jl	@@2
	xchg	es:TRect[bx].bottom,ax
	mov	es:TRect[bx].top,ax
@@2:	end;

function CreateTRectRegion(var R:TRect; LeftMarg,RightMarg:Integer):HRgn;
{umschaltbar auf Markierungsform mit linkem und rechtem Rand,
 Bedingung dafür ist, ob Left<=Right gilt.
 Die Ränder sind "inklusive" gesehen, die Schreibrichtung hier VLNR.
 Die Bezüge auf LineInfo beziehen sich auf momentan _sichtbare_ Statii}
 var
  R2: TRect;
  Rgn1,Rgn2: HRgn;	{Hilfsregion zum Zusammensetzen}
  I,K: Integer;		{Hilfs-Int für Zeilenhöhe}
 label
  L1;
 begin
  CopyRect(R2,R);	{irgendein Heini hat CopyRect falsch deklariert}
  if (LeftMarg<=RightMarg) and (R2.top<>R2.bottom) then begin
{So umsortieren, daß der 1. Punkt oben und der 2. Punkt unten ist}
   if R2.top>R2.bottom then SetRect(R2,R2.right,R2.bottom,R2.left,R2.top);
{Koordinaten zur Erfassung doppelt hoher Buchstaben ausrichten}
   if VT.LineInfo[R2.top] and fdPrevDblH <>0 then Dec(R2.top);
   if VT.LineInfo[R2.bottom] and fdDblH <>0 then Inc(R2.bottom);
{Zeilenhöhen für 1. und letzte Zeile erfassen}
   I:=1; if VT.LineInfo[R2.top] and fdDblH <>0 then I:=2;
   K:=0; if VT.LineInfo[R2.bottom] and fdPrevDblH <>0 then K:=1;
{Test auf Anomalie (Mittelbereich hat negative Breite)}
   if R2.top+I>R2.bottom-K then begin
    SortRect(R2);	{richtig sortieren}
    goto L1;		{weiter wie ohne Ränder}
   end;
{Oberer Schwanz}
   if R2.left<=RightMarg then begin
    Rgn1:=CreateRectRgn(R2.left,R2.top,RightMarg+1,R2.top+I);
   end else Rgn1:=0;
   Inc(R2.top,I);	{weiter 'runter}
{Unterer Schwanz}
   if R2.right>LeftMarg then begin
    Rgn2:=CreateRectRgn(LeftMarg,R2.bottom-K,R2.right,R2.bottom+1);
   end else Rgn2:=0;
   Dec(R2.bottom,K);
{Kombinieren zu Rgn1}
   if Rgn2<>0 then begin
    if Rgn1=0 then Rgn1:=Rgn2
    else begin
     CombineRgn(Rgn1,Rgn1,Rgn2,RGN_XOR);
     DeleteObject(Rgn2);
    end;
   end;
{Mittelbereich}
   if R2.top<R2.bottom then begin
    Rgn2:=CreateRectRgn(LeftMarg,R2.top,RightMarg+1,R2.bottom);
   end else Rgn2:=0;
{Kombinieren zu Rgn1}
   if Rgn2<>0 then begin
    if Rgn1=0 then Rgn1:=Rgn2
    else begin
     CombineRgn(Rgn1,Rgn1,Rgn2,RGN_XOR);
     DeleteObject(Rgn2);
    end;
   end;
{Wert-Rückgabe}
   CreateTRectRegion:=Rgn1;
  end else begin
   SortRect(R2);
{Nun Rechteck auf (mindestens) Buchstabenhöhe aufblasen}
   if VT.LineInfo[R2.top] and fdPrevDblH <>0 then Dec(R2.top);
   if VT.LineInfo[R2.bottom] and fdDblH <>0 then Inc(R2.bottom);
L1:
   Inc(R2.bottom);
   CreateTRectRegion:=CreateRectRgnIndirect(R2);
  end;
 end;

procedure TVTWindow.RemoveAnySelection;
 var
  DC:HDC;
 begin
  if SelectedRegion<>0 then begin
   DC:=GetDC(HWindow);
   SetMapMode(DC,MM_AnIsotropic);
   SetViewportOrg(DC,0,20);
   SetViewportExt(DC,sFontW,sFontH);
   SetWindowExt(DC,1,1);	{in Buchstaben vorgehen!}
   InvertRgn(DC,SelectedRegion);
   DeleteObject(SelectedRegion);
   SelectedRegion:=0;		{Nichts mehr selektiert}
   ReleaseDC(HWindow,DC);
  end;
 end;

procedure TVTWindow.WMLButtonDown(var Msg: TMessage);
 var
  P1: TPoint absolute SelectedRect;
 begin
  RemoveAnySelection;	{Selektion weg vom Bildschirm}
{  P1.X:=Msg.LParamLo div FONTW;
  P1.Y:=Msg.LParamHi div FONTH;}
  P1:=MakePoint(Msg.LParam);
  PointToRaster(P1);
{if LineHidden then Dec(P1.Y)}
  SelectedRect.right:=SelectedRect.left;
  SelectedRect.bottom:=SelectedRect.top;
  SetCapture(HWindow);	{Mausereignisse GLOBAL abfangen}
  SetFocus(HWindow);	{vielleicht gehen dann die Hotkeys?}
 end;

procedure TVTWindow.WMParentNotify(var Msg: TMessage);
 begin
  case Msg.wParam of
   WM_LButtonDown, WM_RButtonDown, WM_MButtonDown:
    SendMessage(hWindow,Msg.wParam,0,Msg.lParam);
  end;
 end;

procedure TVTWindow.WMMouseMove(var Msg: TMessage);
 var
  Pn:TPoint;
  DC:HDC;
  hReg:HRgn;
  ml,mr: Integer;	{Ränder}
{ function TestProc(Item:pRI):boolean; far;
  begin
   TestProc:=Contain(Item^.R,Pn);
  end;}
 begin
  Pn:=MakePoint(Msg.LParam);
  PointToRaster(Pn);
  if Msg.WParam and MK_LBUTTON <>0 then begin
   if (SelectedRect.right<>Pn.X) or (SelectedRect.bottom<>Pn.Y) then begin
    ml:=Margin.left-1; mr:=Margin.right-1;
    if not Margin.enabled then ml:=255;
    SelectedRect.right:=Pn.X;
    SelectedRect.bottom:=Pn.Y;
    DC:=GetDC(HWindow);
    SetMapMode(DC,MM_AnIsotropic);
    SetViewportExt(DC,sfontw,sfonth);
    SetViewportOrg(DC,0,20);
    SetWindowExt(DC,1,1);
    if SelectedRegion<>0 then begin
     hReg:=CreateTRectRegion(SelectedRect,ml,mr);
     CombineRgn(SelectedRegion,SelectedRegion,hReg,RGN_XOR);
     InvertRgn(DC,SelectedRegion);	{die Differenzmenge "umklappen"}
     DeleteObject(SelectedRegion);	{alte Selektierung weg!}
     SelectedRegion:=hReg;		{neue Region setzen}
    end else begin
     SelectedRegion:=CreateTRectRegion(SelectedRect,ml,mr);
     InvertRgn(DC,SelectedRegion);
    end;
    ReleaseDC(HWindow,DC);
   end;
  end else begin		{Linke Taste nicht gedrückt}
{   if VT.LinkList^.FirstThat(@TestProc)<>nil then
{    SetCursor(hCurHand)			{Job erledigt, Rechteck gefunden}
{   else
{    SetCursor(hCurArrow);		{Kein Rechteck gefunden}
  end;
 end;

procedure TVTWindow.WMLButtonUp(var Msg: TMessage);
 var
  Punkt:TPoint;
  hChild: HWnd;
  L: LongInt absolute Current;
{  RI:pRI;}
  Result:Word;
  Fill,Page:word;		{ich hoffe so steht Result gleich richtig}
{ function TestProc(Item:pRI):boolean; far;
  begin
   TestProc:=Contain(Item^.R,Punkt);
  end;}
 begin
  ReleaseCapture;		{Globale Mausereignisse abschalten}
  if SelectedRegion=0 then begin
   Punkt:=MakePoint(Msg.LParam);
   hChild:=ChildWindowFromPoint(hWindow,Punkt);
   if (hChild<>0) and (hChild<>hWindow)
   and (hChild<>hStatic) and (hChild<>hGray) then begin
    L:=GetWindowLong(hChild,0);	{gespeichertes Ziel}
{
   RI:=pRI(VT.LinkList^.FirstThat(@TestProc));
   if RI<>nil then begin
    Current:=RI^.I;	{Index so übernehmen}
    if VT.SelectLoadPage(Current,EQUALPAGE)<>$FFFF then begin
     History.Append(Current);
     UpdateBitmaps;
    end else begin
     MBox(3,Current,MB_OK);	{Seite nicht gefunden}
    end;
{	MBox(4,Page,MB_OK)}
   end;
  end;
 end;

procedure TVTWindow.GetWindowClass;
 begin
  inherited GetWindowClass(WndClass);
  WndClass.hIcon := LoadIcon(HInstance,'MAIN');
  WndClass.hbrBackground:=0;
{  WndClass.hCursor := 0;	{Maus in Eigenregie verwalten}
 end;

destructor TVTWindow.Done;
 function WriteProfileInt(KeyName,Template:PChar;value:word):bool;
  var s:array[0..127]of char;
  begin
   wvsprintf(s,Template,value);		{je nach Wunsch}
   WriteProfileInt:=WriteProfileString(VTW_Name,KeyName,s);
  end;
 begin
  WinHelp(hWindow,'VTW.HLP',HELP_QUIT,0);
  RemoveTemporaryFile;
  WriteProfileInt('Flag','%#04X',MenuFlags);
  WriteProfileInt('Font','%u',MenuFont);
  WriteProfileString(VTW_Name,'LDir',DirName);	{Fehlermeldungen bool vergessen}
  WriteProfileInt('OrgX','%d',Word(Attr.X));
  WriteProfileInt('OrgY','%d',Word(Attr.Y));
  WriteProfileString(VTW_Name,'Unzipper',Unzipper);
  SendMessage(HWND_BroadCast,WM_WinIniChange,0,LongInt(@VTW_Name));
  StopBlink;
  DestroyCursor(hCurHand);
  if Bitmaps[1].h <> 0 then DeleteObject(Bitmaps[1].h);
  if Bitmaps[2].h <> 0 then DeleteObject(Bitmaps[2].h);
{  DeleteObject(IconizedBits);}
  inherited Done;
 end;

const
 ofn: TOpenFileName=(
  lStructSize:		sizeof(TOpenFileName);
  hWndOwner:		0;
  hInstance:		0;
  lpstrFilter:	'Videotext-Dateien'#0'*.vt;*.mgz'#0+
		'Komprimierter Videotext'#0'*.vt_;*.vtz;*.zip'#0+
		'Alle Dateien'#0'*.*'#0;
  lpstrCustomFilter:	nil;
  nMaxCustFilter:	0;
  nFilterIndex:		0;
  lpstrFile:		AFileName;
  nMaxFile:		sizeof(AFileName);
  lpstrFileTitle:	nil;
  nMaxFileTitle:	0;
  lpstrInitialDir:	DirName;
  lpstrTitle:		nil;
  Flags:	OFN_HideReadOnly or OFN_OverwritePrompt or OFN_FileMustExist;
  nFileOffset:		0;
  nFileExtension:	0;
  lpstrDefExt:		'.VT';
  lCustData:		0;
  lpfnHook:		nil;
  lpTemplateName:	nil);

procedure TVTWindow.CMFileOpen(var Msg: TMessage);
 var
  Filter: array[byte]of Char;
 begin
  ofn.hWndOwner:=HWindow;
  LoadString(HInstance,10,Filter,Sizeof(Filter));
  ofn.lpstrFilter:=Filter;
  AFileName[0]:=#0;
  if GetOpenFileName(ofn) then begin
   lStrCpyn(DirName,AFileName,ofn.nFileOffset-1);	{Pfad ohne "\" sichern}
   if LoadVTFile(AFileName) then begin
    UpdateBitmaps;
   end{ else MBox(5,AFileName,mb_ok)};
  end;
 end;

procedure TVTWindow.CMFileSave(var Msg: TMessage);
 var
  Filter,Title: array[0..255] of Char;
 begin
  ofn.hWndOwner:=HWindow;
  LoadString(HInstance,11,Filter,sizeof(Filter));
  ofn.lpstrFilter:=Filter;
{   nFilterIndex:=1;		{damit korrekte Nummer hier zurückgegeben wird}
  AFileName[0]:=#0;
  LoadString(HInstance,12,Title,sizeof(Title));
  ofn.lpstrTitle:=Title;
  if GetSaveFileName(ofn) then begin
   wvsprintf(Filter,'Nicht unterstⁿtzte Funktion %ld',ofn.nFilterIndex);
	{Als Titel Nummer anzeigen (tmp)}
   SetWindowText(hStatic,Filter);
  end;
  ofn.lpstrTitle:=nil;
 end;

function ShortFileName(TheName:PChar; MaxLen:word):PChar;
{verkürzt den Dateinamen im Puffer durch Einfügen von ... im Pfad}
 var
  Len:word;
  P:PChar;
 begin
  Len:=StrLen(TheName);
  if Len>MaxLen then begin
   P:=StrScan(TheName,'\');
   if P=nil then begin
    P:=StrScan(TheName,':');
    if P=nil then P:=TheName+2	{vorn 3 Zeichen}
   end;
   StrMove(StrECopy(P+1,'...'),P+Len-MaxLen+4,MaxLen-4);
  end;
  ShortFileName:=TheName;	{Pointer durchreichen}
 end;

function OpenZipFile(FileName:PChar; OpenMode:Integer; Wnd:HWnd;
  var fh:HFile; var TempFile:PChar):Integer;
{weitere Parameter: Unzip-Fenstertitel, Name oder Nummer der Auspack-Datei..}
{Rückgaben:
 0: alles OK, Datei ist unkomprimiert
 >0: alles OK, Datei war komprimiert (Nummer=Auspack-Routine EXP/ZIP/ARJ/...)
 <0: Fehler beim Öffnen oder Auspacken:
 -1: ...beim Öffnen der Ursprungsdatei
 -2: ...beim Auswerten der Dateiinformationen
 -3: ...beim Kreieren der .PIF-Datei
 -4: ...beim Starten des Entpackers
 -5: ...beim Öffnen der ausgepackten Datei}
{Mögliches Problem: Codeseiten - es fehlt AnsiToOem() und umgekehrt}
const
 ZipSign=$4B50;		{"PK"}
 ExpSign=$5A53;		{"SZ"}
 var
  f: HFile;
  w:Word;
  PFile,TFile,CmdLine: array[0..255]of Char;
  SP,EFile: PChar;
  APif:TPif;
 begin
  TempFile:=nil;
  f:=_lopen(FileName,OpenMode);
  OpenZipFile:=-1;		{Fehler beim ersten Öffnen}
  if f=HFILE_Error then exit;
  if _lread(f,PChar(@w),2)=2 then begin;
   case w of
    ExpSign: begin
     GetTempFileName(#0,'DKZ',0,TFile);	{Künftiger Ziel-Dateiname}
     _llseek(f,0,0);			{Quelldatei schon offen}
     fh:=_lcreat(TFile,0);		{Zieldatei}
     if fh=HFILE_Error then exit;
     if LZCopy(f,fh)<0 then exit;	{Auspacken}
     _lclose(f);
     OpenZipFile:=1;			{Auspacken mit EXPAND OK}
     TempFile:=StrNew(TFile);
     _llseek(fh,0,0);			{"Zurückspulen"}
     exit;
    end;
    ZipSign: begin
     OpenZipFile:=-2;
     GetTempFileName(#0,'DKZ',0,PFile);
     _ldelete(PFile);			{Windows ist schon mächtig komisch!}
     lStrCpy(GetFileNameExt(PFile),'.PIF');{PIF-Dateiname im TEMP-Vrz.}
     EFile:=GetFileNamePtr(PFile);	{Dateiname adressieren}
     lStrCpy(CmdLine,PFile);		{Erstes Element der Kommandozeile}
{Name der ersten Datei extrahieren und an TEMP-Pfad anhängen}
     _llseek(f,$1A,0);			{_llseek macht niemals Fehler!}
     if _lread(f,PChar(@w),2)<>2 then exit;{Name-Länge}
     if w>255 then exit;		{Notbremse!}
     _llseek(f,$1E,0);
     if _lread(f,TFile,w)<>w then exit;	{Name selbst}
     TFile[w]:=#0;			{nullterminieren}
     _lclose(f);			{fertig mit Auslesen}
{Kommandozeile für Unzipper zusammenstellen}
     OpenZipFile:=-3;
     lStrCat(lStrCat(CmdLine,' /C '),Unzipper);

     SP:=StrEnd(CmdLine);
     SP^:=' '; Inc(SP);			{trennt Optionen vom .ZIP-File}
     SP:=StrEnd(FileExpand(SP,FileName));{ZIP-Pfad+Dateiname+Ext}
     SP^:=' '; Inc(SP);
     lStrCpy(SP,TFile);			{Dateiname aus .ZIP-Datei}
{PIF-Datei kreieren}
     SP:=nil;
     if ((EFile-1)^='\') then begin
      SP:=EFile-1;			{Backslash wegpatchen,}
      if (SP-1)^<>':' then SP^:=#0;	{Pfad benötigt}
     end;
     CreatePif(MakeIntResource(26),nil,nil,PFile,APif);
     if SP<>nil then SP^:='\';		{Backslash wieder zurückpatchen}
     f:=_lcreat(PFile,0);		{PIF-Datei kreieren}
     if f=HFILE_Error then exit;
     if _lwrite(f,PChar(@APIF),sizeof(APIF))<>sizeof(APIF) then exit;
     _lclose(f);
{Auspacken starten}
     OpenZipFile:=-4;
     if ExecAndWait(CmdLine,DOSShowCmd,Wnd)>$4000 then exit;
     _ldelete(PFile);
{Ergebnisdatei öffnen und fürs Löschen vorbereiten}
     OpenZipFile:=-5;
     lStrCpy(EFile,TFile);		{Temp-Pfad plus ausgepackte Datei}
     f:=_lopen(PFile,OpenMode);
     if f=HFILE_Error then exit;
     OpenZipFile:=2;
     fh:=f;
     TempFile:=StrNew(PFile);		{auf dem Heap}
     exit;
    end;
   end{case};
  end;
  _llseek(f,0,0);
  fh:=f;
  OpenZipFile:=0;			{Nicht ausgepackt}
 end;

function CloseZipFile(f:HFile; TempFile:PChar):Integer;
 begin
  CloseZipFile:=_lclose(f);
  if TempFile<>nil then begin
   _ldelete(TempFile);
   StrDispose(TempFile);
  end;
 end;

function SetWaitCursor: HCursor;
 begin
  ShowCursor(true);
  SetWaitCursor:=SetCursor(LoadCursor(0,IDC_Wait));
 end;

procedure RemoveWaitCursor(OldCursor: HCursor);
 begin
  SetCursor(OldCursor);
  ShowCursor(false);
 end;

procedure TVTWindow.RemoveTemporaryFile;
 begin
  if TempFileName<>nil then begin
   _ldelete(TempFileName);
   StrDispose(TempFileName);
   TempFileName:=nil;		{Gelöscht markieren}
  end;
 end;

function TVTWindow.LoadVTFile(Name: PChar): Boolean;
 label
  LoadFail;
 var
  OldCursor:HCursor;
  CatBuf,PBuf: array[byte]of Char;	{Puffer "kurzer" Dateiname}
  SomeSP: array[0..1]of PChar;	{für WVSPRINTF}
  F: HFile;
  TempFile,LoadFile:PChar;
 begin
  OldCursor:=SetWaitCursor;
  SetWindowText(hStatic,'Lade bzw. packe Datei aus...');
  UpdateWindow(hStatic);
  LoadVTFile:=TRUE;
  if OpenZipFile(Name,0,HWindow,F,TempFile)<0 then goto LoadFail;
  _lclose(F);
  LoadFile:=Name;
  if TempFile<>nil then begin
   LoadFile:=TempFile;
  end;
  SetWindowText(hStatic,'Erstelle Index...');
  UpdateWindow(hStatic);
  if VT.LoadFile(LoadFile)<>0 then goto LoadFail;
  RemoveTemporaryFile;		{Alte Datei löschen!}
  TempFileName:=TempFile;	{Neu setzen}
  lStrCpy(CatBuf,Name);
  AnsiLower(ShortFileName(CatBuf,20));
  SomeSP[0]:=VTW_Name;
  SomeSP[1]:=CatBuf;
  wvsprintf(PBuf,'%s: %s',SomeSP);
	{AnsiLower macht nur "Name" zu Kleinbuchstaben!}
  SetWindowText(HWindow,PBuf);
  History.Init;
  VT.SelectLoadPage(Current,HOMEPAGE);
  SetWindowText(hStatic,VT.AlterStr);	{Alter der Seite anzeigen}
  History.Append(Current);
  RemoveWaitCursor(OldCursor);
  exit;
LoadFail:
  SetWindowText(hStatic,'Fehler beim ╓ffnen!');
  LoadVTFile := False;
  RemoveWaitCursor(OldCursor);
 end;

procedure TVTWindow.Paint(PaintDC: HDC; var PaintInfo: TPaintStruct);
 var
  dc:HDC;
  OldBitmap:HBitmap;
  R:TRect;
  BitmapDesc: TBitmap;
 begin
  if CurBitmapIdx>0 then begin
   with Bitmaps[CurBitmapIdx] do begin
    if valid=FALSE then begin
     UpdateBitmap(CurBitmapIdx);
    end;
    GetClientRect(HWindow,R);
    DC:=CreateCompatibleDC(PaintDC);	{Ein gleichartiges DC besorgen}
    OldBitmap:=SelectObject(DC,h);	{unser Bitmap dranhΣngen}
    GetObject(h,sizeof(BitmapDesc),@BitmapDesc);
    SetMapMode(PaintDC,MM_AnIsotropic);
    SetViewportOrg(PaintDC,0,20);
    SetViewportExt(PaintDC,sFontW,sFontH);
    SetWindowExt(PaintDC,FONTW,FONTH);	{in Buchstaben vorgehen!}
    BitBlt(PaintDC,0,0,41*FONTW,25*FONTH,DC,0,0,Mode);
    SetWindowExt(PaintDC,1,1);	{in Buchstaben vorgehen!}
    if SelectedRegion<>0 then InvertRgn(PaintDC,SelectedRegion);
    SelectObject(DC,OldBitmap);
    DeleteDC(DC);
   end;
  end;
 end;

function ZipDlgProc(hWindow:HWnd; Msg,wParam:Word; lParam: LongInt): Bool; export;
 begin
  ZipDlgProc:=false;
  case Msg of
   WM_InitDialog: begin
    SetDlgItemText(hWindow,101,Unzipper);
    ZipDlgProc:=true;	{?? Immer diese Objekte !!}
   end;

   WM_Command: case wParam of
    IDOK: begin
     GetDlgItemText(hWindow,101,Unzipper,sizeof(Unzipper));
     EndDialog(hWindow,1);
    end;

    IDCancel: EndDialog(hWindow,0);

{    IDHelp: WinHelp(hWindow,VTW_HELP,HELP_Context,2020);}

   end;
  end;
 end;


procedure TVTWindow.CMSetUnzipper(var Msg: TMessage);
 begin
  DialogBoxParam(Seg(HInstance),'ZIP',HWindow,@ZIPDlgProc,0);
 end;

function Param0: PChar; assembler;
 {liefert eigenen Programmnamen, besser als GetArgStr}
 asm	call	GetDosEnvironment
	mov	es,dx
	mov	di,ax
	xor	ax,ax
	cld
	jmp	@@2	;{falls ENV leer! - könnt ja sein...}
@@1:	scasb
	jnz	@@1
@@2:	scasb
	jnz	@@1
	inc	ax	;{ax=1}
	scasw
	jnz	@@err
	mov	dx,es
	mov	ax,di
	jmp	@@e
@@err:
	dec	ax
	cwd		;{NIL liefern}
@@e:
 end;

 {Neuen Menüpunkt "Prozeß abspalten" hinzufügen:
 CM_Spawn: WinExec(Param0,SW_Show);}

begin
  Register;
 VTWApp.Init(VTW_Name);
 VTWApp.Run;
 VTWApp.Done;
end.

Detected encoding: OEM (CP437)1
Wrong umlauts? - Assume file is ANSI (CP1252) encoded