unit IpHtmlUtils;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Graphics, Forms, 
  IpHtmlTypes;
  
function ColorFromString(S: String): TColor;
function TryColorFromString(S: String; out AColor: TColor; out AErrMsg: String): Boolean;

function GetAlignmentForStr(S: string; ADefault: TIpHtmlAlign = haDefault): TIpHtmlAlign;

function AnsiToEscape(const S: string): string;
function EscapeToAnsi(const S: string): string;
function NoBreakToSpace(const S: string): string;

function FindFontName(const AFontList: string): string;

function MaxI2(const I1, I2: Integer) : Integer;
function MinI2(const I1, I2: Integer) : Integer;


implementation

uses
  Translations, LazUTF8,
  IpConst,IpUtils;

const
  htmlNamedColors: array[0..140] of record
    s: string;
    c: TColor;
  end = ( // alphabetically ordered
    (s:'ALICEBLUE'; c:$FFF8F0),
    (s:'ANTIQUEWHITE'; c:$D7EBFA),
    (s:'AQUA'; c: $FFFF00),
    (s:'AQUAMARINE'; c:$D4FF7F),
    (s:'AZURE'; c:$FFFFF0),
    (s:'BEIGE'; c:$DCF5F5),
    (s:'BISQUE'; c:$C4E4FF),
    (s:'BLACK'; c:clBlack),
    (s:'BLANCHEDALMOND'; c:$CDEBFF),
    (s:'BLUE'; c:$FF0000),
    (s:'BLUEVIOLET'; c:$E22B8A),
    (s:'BROWN'; c:$2A2AA5),
    (s:'BURLYWOOD'; c:$87B8DE),
    (s:'CADETBLUE'; c:$A09E5F),
    (s:'CHARTREUSE'; c:$00FF7F),
    (s:'CHOCOLATE'; c:$1E69D2),
    (s:'CORAL'; c:$507FFF),
    (s:'CORNFLOWERBLUE'; c:$ED9564),
    (s:'CORNSILK'; c:$DCF8FF),
    (s:'CRIMSON'; c:$3C14DC),
    (s:'CYAN'; c: $FFFF00),
    (s:'DARKBLUE'; c:$8B0000),
    (s:'DARKCYAN'; c:$8B8B00),
    (s:'DARKGOLDENROD'; c:$0B86B8),
    (s:'DARKGRAY'; c:$A9A9A9),
    (s:'DARKGREEN'; c:$006400),
    (s:'DARKKHAKI'; c:$6BB7BD),
    (s:'DARKMAGENTA'; c:$8B008B),
    (s:'DARKOLIVEGREEN'; c:$2F6B55),
    (s:'DARKORANGE'; c:$008CFF),
    (s:'DARKORCHID'; c:$CC3299),
    (s:'DARKRED'; c:$00008B),
    (s:'DARKSALMON'; c:$7A96E9),
    (s:'DARKSEAGREEN'; c:$8FBC8F),
    (s:'DARKSLATEBLUE' ; c:$8B3D48),
    (s:'DARKSLATEGRAY'; c:$4F4F2F),
    (s:'DARKTURQUOISE'; c:$D1CE00),
    (s:'DARKVIOLET'; c:$D30094),
    (s:'DARKYELLOW'; c:$008080),
    (s:'DEEPPINK'; c:$9314FF),
    (s:'DEEPSKYBLUE'; c:$FFBF00),
    (s:'DIMGRAY'; c:$696969),
    (s:'DODGERBLUE'; c:$FF901E),
    (s:'FIREBRICK'; c:$2222B2),
    (s:'FLORALWHITE'; c:$F0FAFF),
    (s:'FORESTGREEN'; c:$228B22),
    (s:'FUCHSIA'; c:$FF00FF),
    (s:'GAINSBORO'; c:$DCDCDC),
    (s:'GHOSTWHITE'; c:$FFF8F8),
    (s:'GOLD'; c:$00D7FF),
    (s:'GOLDENROD'; c:$20A5DA),
    (s:'GRAY'; c:$808080),
    (s:'GREEN'; c:$008000),
    (s:'GREENYELLOW'; c:$2FFFAD),
    (s:'HONEYDEW'; c:$F0FFF0),
    (s:'HOTPINK'; c:$B469FF),
    (s:'INDIANRED'; c:$5C5CCD),
    (s:'INDIGO'; c:$82004B),
    (s:'IVORY'; c:$F0FFFF),
    (s:'KHAKI'; c:$8CE6F0),
    (s:'LAVENDER'; c:$FAE6E6),
    (s:'LAVENDERBLUSH'; c:$F5F0FF),
    (s:'LAWNGREEN'; c:$00FC7C),
    (s:'LEMONCHIFFON'; c:$CDFAFF),
    (s:'LIGHTBLUE'; c:$E6D8AD),
    (s:'LIGHTCORAL'; c:$8080F0),
    (s:'LIGHTCYAN'; c:$FFFFE0),
    (s:'LIGHTGOLDENRODYELLOW'; c:$D2FAFA),
    (s:'LIGHTGREEN'; c:$90EE90),
    (s:'LIGHTGREY'; c:$D3D3D3),
    (s:'LIGHTPINK'; c:$C1B6FF),
    (s:'LIGHTSALMON'; c:$7AA0FF),
    (s:'LIGHTSEAGREEN'; c:$AAB220),
    (s:'LIGHTSKYBLUE'; c:$FACE87),
    (s:'LIGHTSLATEGRAY'; c:$998877),
    (s:'LIGHTSTEELBLUE'; c:$DEC4B0),
    (s:'LIGHTYELLOW'; c:$E0FFFF),
    (s:'LIME'; c:$00FF00),
    (s:'LIMEGREEN'; c:$32CD32),
    (s:'LINEN'; c:$E6F0FA),
    (s:'MAGENTA'; c:$FF00FF),
    (s:'MAROON'; c:$000080),
    (s:'MEDIUMAQUAMARINE'; c:$AACD66),
    (s:'MEDIUMBLUE'; c:$CD0000),
    (s:'MEDIUMORCHID'; c:$D355BA),
    (s:'MEDIUMPURPLE'; c:$DB7093),
    (s:'MEDIUMSEAGREEN'; c:$71B33C),
    (s:'MEDIUMSLATEBLUE'; c:$EE687B),
    (s:'MEDIUMSPRINGGREEN'; c:$9AFA00),
    (s:'MEDIUMTURQUOISE'; c:$CCD148),
    (s:'MEDIUMVIOLETRED'; c:$8515C7),
    (s:'MIDNIGHTBLUE'; c:$701919),
    (s:'MINTCREAM'; c:$FAFFF5),
    (s:'MISTYROSE'; c:$E1E4FF),
    (s:'MOCCASIN'; c:$B5E4FF),
    (s:'NAVAJOWHITE'; c:$ADDEFF),
    (s:'NAVY'; c:$800000),
    (s:'OLDLACE'; c:$E6F5FD),
    (s:'OLIVE'; c:$008080),
    (s:'OLIVEDRAB'; c:$238E6B),
    (s:'ORANGE'; c:$00A5FF),
    (s:'ORANGERED'; c:$0045FF),
    (s:'ORCHID'; c:$D670DA),
    (s:'PALEGOLDENROD'; c:$AAE8EE),
    (s:'PALEGREEN'; c:$98FB98),
    (s:'PALETURQUOISE'; c:$EEEEAF),
    (s:'PALEVIOLETRED'; c:$9370DB),
    (s:'PAPAYAWHIP'; c:$D5EFFF),
    (s:'PEACHPUFF'; c:$B9DAFF),
    (s:'PERU'; c:$3F85CD),
    (s:'PINK'; c:$CBC0FF),
    (s:'PLUM'; c:$DDA0DD),
    (s:'POWDERBLUE'; c:$E6E0B0),
    (s:'PURPLE'; c:$800080),
    (s:'RED'; c:$0000FF),
    (s:'ROSYBROWN'; c:$8F8FBC),
    (s:'ROYALBLUE'; c:$901604),
    (s:'SADDLEBROWN'; c:$13458B),
    (s:'SALMON'; c:$7280FA),
    (s:'SANDYBROWN'; c:$60A4F4),
    (s:'SEAGREEN'; c:$578B2E),
    (s:'SEASHELL'; c:$EEF5FF),
    (s:'SIENNA'; c:$2D52A0),
    (s:'SILVER'; c:$C0C0C0),
    (s:'SKYBLUE'; c:$EBCE87),
    (s:'SLATEBLUE'; c:$CD5A6A),
    (s:'SLATEGRAY'; c:$908070),
    (s:'SNOW'; c:$FAFAFF),
    (s:'SPRINGGREEN'; c:$7FFF00),
    (s:'STEELBLUE'; c:$B48246),
    (s:'TAN'; c:$8CB4D2),
    (s:'TEAL'; c:$808000),
    (s:'THISTLE'; c:$D8BFD8),
    (s:'TOMATO'; c:$4763FF),
    (s:'TURQUOISE'; c:$D0E040),
    (s:'VIOLET'; c:$EE82EE),
    (s:'WHEAT'; c:$B3DEF5),
    (s:'WHITE'; c:$FFFFFF),
    (s:'WHITESMOKE'; c:$F5F5F5),
    (s:'YELLOW'; c:$00FFFF),
    (s:'YELLOWGREEN'; c:$32CD9A)
  );

function BinSearchNamedColor(const AColorStr: string; var AColor: TColor): boolean;
var
  First: Integer;
  Last: Integer;
  Pivot: Integer;
begin
  First := Low(htmlNamedColors); //Sets the first item of the range
  Last := High(htmlNamedColors); //Sets the last item of the range
  Result := False; //Initializes the Found flag (Not found yet)

  //If First > Last then the searched item doesn't exist
  //If the item is found the loop will stop
  while (First <= Last) {and (not Result)} do
  begin
    //Gets the middle of the selected range
    Pivot := (First + Last) div 2;
    //Compares the String in the middle with the searched one
    if htmlNamedColors[Pivot].s = AColorStr then
    begin
      Result  := True;
      AColor := htmlNamedColors[Pivot].c;
      exit;
    end
    //If the Item in the middle has a bigger value than
    //the searched item, then select the first half
    else if htmlNamedColors[Pivot].s > AColorStr then
      Last := Pivot - 1
        //else select the second half
    else
      First := Pivot + 1;
  end;
end;

function TryColorFromString(S: String; out AColor: TColor; out AErrMsg: String): Boolean;

  function ByteValue(HighNibble, LowNibble: Char): Byte;
  var
    S: String[3] = '$xx';
    Err: Integer;
  begin
    S[2] := HighNibble;
    S[3] := LowNibble;
    val(S, Result, Err);
    if Err <> 0 then Result := 255;
  end;

var
  R, G, B: Integer;
begin
  Result := false;
  
  AColor := clNone;
  if S = '' then
  begin
    Result := true;
    Exit;
  end;
  
  S := UpperCase(S);
  if S[1] = '#' then
  begin
    if not (Length(S) in [4, 7]) then
    begin
      AErrMsg := SHtmlInvColor + S;
      Result := false;
    end else
    begin
      if Length(S) = 7 then
      begin
        R := ByteValue(S[2], S[3]);
        G := ByteValue(S[4], S[5]);
        B := ByteValue(S[6], S[7]);
      end else
      begin
        R := ByteValue(S[2], S[2]);
        G := ByteValue(S[3], S[3]);
        B := ByteValue(S[4], S[4]);
      end;
      AColor := RGBToColor(R, G, B);
      Result := true;
    end;
  end else
  if BinSearchNamedColor(S, AColor) then 
  begin
    Result := true;
    exit;
  end else
  if Length(S) = 6 then
    try
      R := ByteValue(S[1], S[2]);
      G := ByteValue(S[3], S[4]);
      B := ByteValue(S[5], S[6]);
      AColor := RGBToColor(R, G, B);
      Result := true;
    except
      AErrMsg := SHtmlInvColor + S;
    end;
end;

function ColorFromString(S: String): TColor;
var
  msg: String;
begin
  if not TryColorFromString(S, Result, msg) then
    Result := clNone;
end;

function GetAlignmentForStr(S: string;
  ADefault: TIpHtmlAlign = haDefault): TIpHtmlAlign;
begin
  S := UpperCase(S);
  if Length(S) = 0 then
  begin
    Result := ADefault;
    exit;
  end;
  
  case S[1] of
    'C','M': 
      if S = 'CHAR' then 
        Result := haChar
      else if (S = 'CENTER') or (S = 'MIDDLE') then
        Result := haCenter;
    'J': 
      if S = 'JUSTIFY' then Result := haJustify;
    'L': 
      if (S = 'LEFT') then Result := haLeft;
    'R': 
      if S = 'RIGHT' then Result := haRight;
    else 
      Result := ADefault;
  end;
end;

const
  CodeCount = 126;
  
  {Sorted by Size where size is Length(Name).
  Make sure you respect this when adding new items}
  Codes: array[0..Pred(CodeCount)] of record
    Size: Integer;
    Name: String;
    Value: String;
    ValueUtf8: String; //UTF8 DiBo33
  end = (
    (Size: 2; Name: 'gt';   Value: '>'; ValueUtf8: #$3E),
    (Size: 2; Name: 'lt';   Value: '<'; ValueUtf8: #$3C),
    (Size: 3; Name: 'amp';  Value: '&'; ValueUtf8: #$26),
    (Size: 3; Name: 'deg';  Value: #176; ValueUtf8: #$C2#$B0),
    (Size: 3; Name: 'ETH';  Value: #208; ValueUtf8: #$C3#$90),
    (Size: 3; Name: 'eth';  Value: #240; ValueUtf8: #$C3#$B0),
    (Size: 3; Name: 'not';  Value: #172; ValueUtf8: #$C2#$AC),
    (Size: 3; Name: 'reg';  Value: #174; ValueUtf8: #$C2#$AE),
    (Size: 3; Name: 'shy';  Value: ShyChar; ValueUtf8: ShyChar),
    (Size: 3; Name: 'uml';  Value: #168; ValueUtf8: #$C2#$A8),
    (Size: 3; Name: 'yen';  Value: #165; ValueUtf8: #$C2#$A5),
    (Size: 4; Name: 'Auml'; Value: #196; ValueUtf8: #$C3#$84),
    (Size: 4; Name: 'auml'; Value: #228; ValueUtf8: #$C3#$A4),
    (Size: 4; Name: 'bull'; Value: #149; ValueUtf8: #$E2#$80#$A2),
    (Size: 4; Name: 'cent'; Value: #162; ValueUtf8: #$C2#$A2),
    (Size: 4; Name: 'circ'; Value: '^';  ValueUtf8: #$5E),
    (Size: 4; Name: 'copy'; Value: #169; ValueUtf8: #$C2#$A9),
    (Size: 4; Name: 'Euml'; Value: #203; ValueUtf8: #$C3#$8B),
    (Size: 4; Name: 'euml'; Value: #235; ValueUtf8: #$C3#$AB),
    (Size: 4; Name: 'euro'; Value: #128; ValueUtf8: #$E2#$82#$AC),
    (Size: 4; Name: 'fnof'; Value: #131; ValueUtf8: #$C6#$92),
    (Size: 4; Name: 'Iuml'; Value: #207; ValueUtf8: #$C3#$8F),
    (Size: 4; Name: 'iuml'; Value: #239; ValueUtf8: #$C3#$AF),
    (Size: 4; Name: 'macr'; Value: #175; ValueUtf8: #$C2#$AF),
    (Size: 4; Name: 'nbsp'; Value: NbspChar; ValueUtf8: NbspChar),
    (Size: 4; Name: 'ordf'; Value: #170; ValueUtf8: #$C2#$AA),
    (Size: 4; Name: 'ordm'; Value: #186; ValueUtf8: #$C2#$BA),
    (Size: 4; Name: 'Ouml'; Value: #214; ValueUtf8: #$C3#$96),
    (Size: 4; Name: 'ouml'; Value: #246; ValueUtf8: #$C3#$B6),
    (Size: 4; Name: 'para'; Value: #182; ValueUtf8: #$C2#$B6),
    (Size: 4; Name: 'quot'; Value: '"';  ValueUtf8: #$22),
    (Size: 4; Name: 'sect'; Value: #167; ValueUtf8: #$C2#$A7),
    (Size: 4; Name: 'sup1'; Value: #185; ValueUtf8: #$C2#$B9),
    (Size: 4; Name: 'sup2'; Value: #178; ValueUtf8: #$C2#$B2),
    (Size: 4; Name: 'sup3'; Value: #179; ValueUtf8: #$C2#$B3),
    (Size: 4; Name: 'Uuml'; Value: #220; ValueUtf8: #$C3#$9C),
    (Size: 4; Name: 'uuml'; Value: #252; ValueUtf8: #$C3#$BC),
    (Size: 4; Name: 'Yuml'; Value: #159; ValueUtf8: #$C5#$B8),
    (Size: 4; Name: 'yuml'; Value: #255; ValueUtf8: #$C3#$BF),
    (Size: 5; Name: 'Acirc'; Value: #194; ValueUtf8: #$C3#$82),
    (Size: 5; Name: 'acirc'; Value: #226; ValueUtf8: #$C3#$A2),
    (Size: 5; Name: 'acute'; Value: #180; ValueUtf8: #$C2#$B4),
    (Size: 5; Name: 'AElig'; Value: #198; ValueUtf8: #$C3#$86),
    (Size: 5; Name: 'aelig'; Value: #230; ValueUtf8: #$C3#$A6),
    (Size: 5; Name: 'Aring'; Value: #197; ValueUtf8: #$C3#$85),
    (Size: 5; Name: 'aring'; Value: #229; ValueUtf8: #$C3#$A5),
    (Size: 5; Name: 'cedil'; Value: #184; ValueUtf8: #$C2#$B8),
    (Size: 5; Name: 'Ecirc'; Value: #202; ValueUtf8: #$C3#$8A),
    (Size: 5; Name: 'ecirc'; Value: #234; ValueUtf8: #$C3#$AA),
    (Size: 5; Name: 'frasl'; Value: '/';  ValueUtf8: #$2F),
    (Size: 5; Name: 'Icirc'; Value: #206; ValueUtf8: #$C3#$8E),
    (Size: 5; Name: 'icirc'; Value: #238; ValueUtf8: #$C3#$AE),
    (Size: 5; Name: 'iexcl'; Value: #161; ValueUtf8: #$C2#$A1),
    (Size: 5; Name: 'laquo'; Value: #171; ValueUtf8: #$C2#$AB),
    (Size: 5; Name: 'ldquo'; Value: #147; ValueUtf8: #$E2#$80#$9C),
    (Size: 5; Name: 'lsquo'; Value: #145; ValueUtf8: #$E2#$80#$98),
    (Size: 5; Name: 'mdash'; Value: #151; ValueUtf8: #$E2#$80#$94),
    (Size: 5; Name: 'micro'; Value: #181; ValueUtf8: #$C2#$B5),
    (Size: 5; Name: 'minus'; Value: '-';  ValueUtf8: #$2D),
    (Size: 5; Name: 'ndash'; Value: #150; ValueUtf8: #$E2#$80#$93),
    (Size: 5; Name: 'Ocirc'; Value: #212; ValueUtf8: #$C3#$94),
    (Size: 5; Name: 'ocirc'; Value: #244; ValueUtf8: #$C3#$B4),
    (Size: 5; Name: 'OElig'; Value: #140; ValueUtf8: #$C5#$92),
    (Size: 5; Name: 'oelig'; Value: #156; ValueUtf8: #$C5#$93),
    (Size: 5; Name: 'pound'; Value: #163; ValueUtf8: #$C2#$A3),
    (Size: 5; Name: 'raquo'; Value: #187; ValueUtf8: #$C2#$BB),
    (Size: 5; Name: 'rdquo'; Value: #148; ValueUtf8: #$E2#$80#$9D),
    (Size: 5; Name: 'rsquo'; Value: #146; ValueUtf8: #$E2#$80#$99),
    (Size: 5; Name: 'szlig'; Value: #223; ValueUtf8: #$C3#$9F),
    (Size: 5; Name: 'THORN'; Value: #222; ValueUtf8: #$C3#$9E),
    (Size: 5; Name: 'thorn'; Value: #254; ValueUtf8: #$C3#$BE),
    (Size: 5; Name: 'tilde'; Value: '~';  ValueUtf8: #$7E),
    (Size: 5; Name: 'times'; Value: #215; ValueUtf8: #$C3#$97),
    (Size: 5; Name: 'trade'; Value: #153; ValueUtf8: #$E2#$84#$A2),
    (Size: 5; Name: 'Ucirc'; Value: #219; ValueUtf8: #$C3#$9B),
    (Size: 5; Name: 'ucirc'; Value: #251; ValueUtf8: #$C3#$BB),
    (Size: 6; Name: 'Aacute'; Value: #193; ValueUtf8: #$C3#$81),
    (Size: 6; Name: 'aacute'; Value: #225; ValueUtf8: #$C3#$A1),
    (Size: 6; Name: 'Agrave'; Value: #192; ValueUtf8: #$C3#$80),
    (Size: 6; Name: 'agrave'; Value: #224; ValueUtf8: #$C3#$A0),
    (Size: 6; Name: 'Atilde'; Value: #195; ValueUtf8: #$C3#$83),
    (Size: 6; Name: 'atilde'; Value: #227; ValueUtf8: #$C3#$A3),
    (Size: 6; Name: 'brvbar'; Value: #166; ValueUtf8: #$C2#$A6),
    (Size: 6; Name: 'Ccedil'; Value: #199; ValueUtf8: #$C3#$87),
    (Size: 6; Name: 'ccedil'; Value: #231; ValueUtf8: #$C3#$A7),
    (Size: 6; Name: 'curren'; Value: #164; ValueUtf8: #$C2#$A4),
    (Size: 6; Name: 'dagger'; Value: #134; ValueUtf8: #$E2#$80#$A0),
    (Size: 6; Name: 'Dagger'; Value: #135; ValueUtf8: #$E2#$80#$A1),
    (Size: 6; Name: 'divide'; Value: #247; ValueUtf8: #$C3#$B7),
    (Size: 6; Name: 'Eacute'; Value: #201; ValueUtf8: #$C3#$89),
    (Size: 6; Name: 'eacute'; Value: #233; ValueUtf8: #$C3#$A9),
    (Size: 6; Name: 'Egrave'; Value: #200; ValueUtf8: #$C3#$88),
    (Size: 6; Name: 'egrave'; Value: #232; ValueUtf8: #$C3#$A8),
    (Size: 6; Name: 'frac12'; Value: #189; ValueUtf8: #$C2#$BD),
    (Size: 6; Name: 'frac14'; Value: #188; ValueUtf8: #$C2#$BC),
    (Size: 6; Name: 'frac34'; Value: #190; ValueUtf8: #$C2#$BE),
    (Size: 6; Name: 'hellip'; Value: #133; ValueUtf8: #$E2#$80#$A6),
    (Size: 6; Name: 'Iacute'; Value: #205; ValueUtf8: #$C3#$8D),
    (Size: 6; Name: 'iacute'; Value: #237; ValueUtf8: #$C3#$AD),
    (Size: 6; Name: 'Igrave'; Value: #204; ValueUtf8: #$C3#$8C),
    (Size: 6; Name: 'igrave'; Value: #236; ValueUtf8: #$C3#$AC),
    (Size: 6; Name: 'iquest'; Value: #191; ValueUtf8: #$C2#$BF),
    (Size: 6; Name: 'lsaquo'; Value: #139; ValueUtf8: #$E2#$80#$B9),
    (Size: 6; Name: 'middot'; Value: #183; ValueUtf8: #$C2#$B7),
    (Size: 6; Name: 'Ntilde'; Value: #209; ValueUtf8: #$C3#$91),
    (Size: 6; Name: 'ntilde'; Value: #241; ValueUtf8: #$C3#$B1),
    (Size: 6; Name: 'Oacute'; Value: #211; ValueUtf8: #$C3#$93),
    (Size: 6; Name: 'oacute'; Value: #243; ValueUtf8: #$C3#$B3),
    (Size: 6; Name: 'Ograve'; Value: #210; ValueUtf8: #$C3#$92),
    (Size: 6; Name: 'ograve'; Value: #242; ValueUtf8: #$C3#$B2),
    (Size: 6; Name: 'Oslash'; Value: #216; ValueUtf8: #$C3#$98),
    (Size: 6; Name: 'oslash'; Value: #248; ValueUtf8: #$C3#$B8),
    (Size: 6; Name: 'Otilde'; Value: #213; ValueUtf8: #$C3#$95),
    (Size: 6; Name: 'otilde'; Value: #245; ValueUtf8: #$C3#$B5),
    (Size: 6; Name: 'permil'; Value: #137; ValueUtf8: #$E2#$80#$B0),
    (Size: 6; Name: 'plusmn'; Value: #177; ValueUtf8: #$C2#$B1),
    (Size: 6; Name: 'rsaquo'; Value: #155; ValueUtf8: #$E2#$80#$BA),
    (Size: 6; Name: 'Scaron'; Value: #138; ValueUtf8: #$C5#$A0),
    (Size: 6; Name: 'scaron'; Value: #154; ValueUtf8: #$C5#$A1),
    (Size: 6; Name: 'Uacute'; Value: #218; ValueUtf8: #$C3#$9A),
    (Size: 6; Name: 'uacute'; Value: #250; ValueUtf8: #$C3#$BA),
    (Size: 6; Name: 'Ugrave'; Value: #217; ValueUtf8: #$C3#$99),
    (Size: 6; Name: 'ugrave'; Value: #249; ValueUtf8: #$C3#$B9),
    (Size: 6; Name: 'Yacute'; Value: #221; ValueUtf8: #$C3#$9D),
    (Size: 6; Name: 'yacute'; Value: #253; ValueUtf8: #$C3#$BD),
    (Size: 6; Name: 'xxxxxx'; Value: NAnchorChar; ValueUtf8: NAnchorChar)
  );
  
function ParseConstant(const S: string; OnUtf8: boolean = false): string;
var
  Error: Integer;
  Index1: Integer;
  Index2: Integer;
  Size1: Integer;
  Found: Boolean;
begin {'Complete boolean eval' must be off}
  Result := ' ';
  Size1 := Length(S);
  if Size1 = 0 then Exit;
  if (S[1] in ['$', '0'..'9']) then
  begin
    Val(S, Index1, Error);
    if (Error = 0) then
    begin
      if not OnUTF8 and (Index1 >= 32) and (Index1 <= 255) then
        Result := Chr(Index1)
      else
      begin
        Result := UnicodeToUTF8(Index1);
        if Result = NbspUTF8 then Result := NbspChar;
      end;
    end;
  end else
  begin
    Index1 := 0;
    repeat
      if Size1 = Codes[Index1].Size then
      begin
        Found := True;
        Index2 := 1;
        while Index2 <= Size1 do
        begin
          if S[Index2] <> Codes[Index1].Name[Index2] then
          begin
            Found := False;
            Break;
          end;
          Inc(Index2);
        end;
        if Found then
        begin
          if OnUtf8 then 
            Result := Codes[Index1].ValueUTF8
          else 
            Result := Codes[Index1].Value;
          Break;
        end;
      end;
      Inc(Index1);
    until (Index1 >= CodeCount) or (Codes[Index1].Size > Size1);
  end;
end;

{- returns the string with & escapes expanded}
procedure ExpandEscapes(var S: string);
var
  i, j : Integer;
  Co : string;
  Ch : AnsiChar;
  St : string;
begin
  i := Length(S);
  while i > 0 do begin
    if S[i] = '&' then begin
      j := i;
      while (j < length(S)) and not (S[j] in [';', ' ']) do
        Inc(j);
      Co := copy(S, i + 1, j - i - 1);
      if Co <> '' then begin
        if Co[1] = '#' then begin
          Delete(Co, 1, 1);
          if UpCase(Co[1]) = 'X' then begin
            Delete(Co, 1, 1);
            Insert('$', Co, 1);
          end;
        end;
        Delete(S, i, j - i + 1);
        if SystemCharSetIsUTF8 then begin
          St := ParseConstant(Co, true);
          Insert(St, S, i)
        end else begin
          Ch := ParseConstant(Co)[1];
          Insert(Ch, S, i);
        end;
      end;
    end;
    Dec(i);
  end;
end;

function EscapeToAnsi(const S: string): string;
var
  P : Integer;
begin
  Result := S;
  P := CharPos('&', S);
  if P <> 0 then
    ExpandEscapes(Result);
end;

{ Returns the string with & escapes}
function AnsiToEscape(const S: string): string;
var
  i : Integer;
  procedure replaceCharBy(newStr: string);
  begin
    Result[i] := '&';
    Insert(newStr, Result, i + 1);
  end;

begin
  Result := S;
  i := length(Result);
  while i > 0 do begin
    case Result[i] of
    ShyChar : replaceCharBy('shy;');
    NbspChar : replaceCharBy('nbsp;');
    '"' : replaceCharBy('quot;');
    '&' : replaceCharBy('amp;');
    '<' : replaceCharBy('lt;');
    '>' : replaceCharBy('gt;');
    end;
    Dec(i);
  end;
end;

function NoBreakToSpace(const S: string): string;
var
  P, n : Integer;
begin
  SetLength(Result, Length(S));
  n := 0;
  P := 1;
  while P <= Length(S) do
  begin
    inc(n);
    if S[P] = NbspChar then
      Result[n] := ' '
    else if (P < Length(S)) and (S[P] = NbspUtf8[1]) and (S[P+1] = NbspUtf8[2]) then
    begin
      Result[n] := ' ';
      inc(P);
    end else
      Result[n] := S[P];
    inc(P);
  end;
  SetLength(Result, n);
end;


var // Remember previous in/out values for function FindFontName.
  PrevFontList1, PrevResult1: string;
  PrevFontList2, PrevResult2: string;

function FindFontName(const AFontList: string): string;

  function CheckFonts(ATestFontList: array of String): String;
  var
    i: Integer;
  begin
    for i:=0 to High(ATestFontList) do begin
      Result := ATestFontList[i];
      if Screen.Fonts.IndexOf(Result) > -1 then
        exit;
    end;
    Result := '';
  end;

var
  L: TStringList;
  S: String;
  i: Integer;
begin
  // Optimize 2 previous calls having the same parameter value.
  if AFontList = PrevFontList1 then
    Exit(PrevResult1)
  else
  if AFontList = PrevFontList2 then
    Exit(PrevResult2);

  Result := '';
  L := TStringList.Create;
  try
    L.CommaText := AFontList;
    for i:=0 to L.Count-1 do begin
      S := L[i];
      if Screen.Fonts.IndexOf(S) > -1 then
        Result := S
      else
      if SameText(S, 'sans-serif') then
        Result := Checkfonts(['Arial', 'Helvetica', 'Liberation Sans'])
      else
      if SameText(S, 'serif') then
        Result := CheckFonts(['Times', 'Times New Roman', 'Liberation Serif'])
      else
      if SameText(S, 'monospace') then
        Result := CheckFonts(['Courier New', 'Courier', 'Liberation Mono']);
      if Result <> '' then
        Break;
    end;
    if Result = '' then  // Unknown or CheckFonts returned ''.
      Result := Screen.MenuFont.Name;
  finally
    L.Free;
    PrevFontList2 := PrevFontList1;
    PrevResult2 := PrevResult1;
    PrevFontList1 := AFontList;
    PrevResult1 := Result;
  end;
end;

function MaxI2(const I1, I2: Integer) : Integer;
begin
  Result := I1;
  if I2 > I1 then
    Result := I2;
end;

function MinI2(const I1, I2: Integer) : Integer;
begin
  Result := I1;
  if I2 < I1 then
    Result := I2;
end;


end.

