unit TextImages;

interface

uses windows,SysUtils,Graphics,StrUtils,Contnrs,classes,ABitmaplight,types,light;

type


 TDirInf = record
  firstDir,exeDir,currentFile,fileName:string;
  mode:byte;
 end;

 TTextInf=class(TObject)
 public
  x,y,lineHeight:Integer;
  newline,antialias:boolean;
  font:TFont;
  constructor create();
 end;

 TFontInf=class(TObject)
  style:TFontStyles;
  height,color:Integer;
  name:string;
 end;

 TTagText=class(TObject)
 public
  rText,lText,mText,bufferText:string;
  stack:TStack;
  fontinf:TFontInf;
  class function getInteger(s:string;def:integer=0):Integer;
  class function getColor(s:string;def:integer=-1):TColor;
  class function covertColor(const color:Dword):TColor;
  class function serachFileName(var inf:TDirInf):string;
  class function getIntArray(const text:string;len:byte;ng:Integer=-1):TIntegerDynArray;
  procedure changeFontForFontTag(var font:TFont;const tag:string);
  function setText(const s:string;mode:shortInt):shortInt;overload;
  procedure clear();
  constructor create();
  destructor destroy();override;
  function getTextInf(inf:TTextInf;const tag:string):TTextInf;
 end;

 TTextImage=class(TObject)
 public
  function loadText(fileName:string;var bitmap:TBitmap):boolean;
 end;

 function max(const x,y:Integer):Integer;
 function min(const x,y:Integer):Integer;

const
 ttNone=0;
 ttOpen=1;
 ttClose=2;

implementation

function max(const x,y:Integer):Integer;
begin
 if(x>y)then result:=x
 else result:=y;
end;

function min(const x,y:Integer):Integer;
begin
 if(x<y)then result:=x
 else result:=y;
end;

{TTextInf}
constructor TTextInf.create();
begin
 inherited;
 antialias:=false;
// font.Charset:=DEFAULT_CHARSET;
end;

{TTagText}
constructor TTagText.create();
begin
 inherited;
 stack:=TStack.Create();
 bufferText:='';
end;

destructor TTagText.destroy();
begin
 //if (fontinf<>nil) then fontinf.Free();
 stack.Free();
 inherited;
end;

(*ファイルフォルダのセット*)
class function TTagText.serachFileName(var inf:TDirInf):string;
const
 CURRENT=1;
 SINARIO=4;
 TABLE=2;
 WAVE=5;
 SKIN=3;
 PARENT=6;
 EXE=7;
 //MID=7;
begin
   case inf.mode of
    CURRENT:
    begin
     result:=concat(ExtractFileDir(inf.currentFile),'\',inf.FileName);
    end;
    SINARIO:result:=inf.firstDir+'\'+inf.fileName;
    TABLE:result:=inf.exeDir+'\data\table\'+inf.fileName;
    WAVE:result:=inf.exeDir+'\data\wave\'+inf.fileName;
    SKIN:result:=inf.exeDir+'\Scheme\'+inf.fileName;
    PARENT:result:=ExtractFileDir(ExtractFileDir(inf.currentFile))+'\'+inf.fileName;
    EXE:result:=inf.exeDir+'\'+inf.fileName;
 //   MID:result:=inf.exeDir+'\data\midi\'+inf.fileName;
    else
     result:=inf.fileName;//呼び出し失敗
    end;
end;

(*ｈｔｍｌ式カラーをdelphi式に変更*)
class function TTagText.covertColor(const color:Dword):TColor;
begin
 result:=((color and $ff0000) shr 16)   //青
         or (color and $00ff00)           //緑
         or ((color and $0000ff) shl 16);//赤
end;

(*ｈｔｍｌ式カラーの文字をdelphi式の色に変更*)
class function TTagText.getColor(s:string;def:integer=-1):TColor;
var
 s2:string;
begin

 result:=getInteger(s,-1);
 if(result<>-1)then
  result:=covertColor(result)
 else
 begin
  s2:=trim(AnsiDeQuotedStr((AnsiLowerCase(trim(s))),'"'));
  if(s2='red')then
   result:=clred
  else if(s2='blue')then
   result:=clblue
  else if(s2='yellow')then
   result:=clyellow
  else if(s2='green')then
   result:=clgreen
  else if(s2='white')then
   result:=clwhite
  else if(s2='maroon')then
   result:=clMaroon
  else if(s2='purple')then
   result:=clpurple
  else if(s2='olive')then
   result:=clOlive
  else if(s2='aqua')then
   result:=claqua
  else if(s2='navy')then
   result:=clNavy
  else if(s2='teal')then
   result:=clTeal
  else if(s2='fuchsia')then
   result:=clFuchsia
  else if(s2='white')then
   result:=clWhite
  else if(s2='black')then
   result:=clBlack
  else if(s2='silver')then
   result:=$c0c0c0
  else if(s2='gray')then
   result:=clGray
  else
   result:=def;
 end;
  
end;

(*文字を数値化*)
class function TTagText.getInteger(s:string;def:integer=0):Integer;
var
 i,i1,i2,n:Integer;
 isHex,isNum:boolean;
 key:string;
begin

 i1:=0;
 i2:=0;
 n:=1;
 isHex:=false;
 isNum:=false;
 key:='';


 for i:=1 to Length(s) do
 begin
  i1:=i;
  case s[i] of
   '0'..'9':
   begin
    i2:=1;
    isNum:=true;
    break;
   end;
   '$':
   begin
    isHex:=true;
    break;
   end;
   '#':
   begin
    s[i]:='$';
    isHex:=true;
    break;
   end;
   '-':
   begin
    n:=-1;
   end;
   '+':
   begin
    n:=1;
   end;
  end;
 end;

 if(isHex)then
 begin
  i2:=1;
  for i:=i1+1 to Length(s) do
   case s[i] of
    '0'..'9','a'..'f','A'..'F':
    begin
     i2:=i2+1;
     isNum:=true;
    end;
   else
    break;
   end;
 end
 else
 for i:=i1+1 to Length(s) do
  case s[i] of
   '0'..'9':i2:=i2+1;
  else
   break;
  end;

 if(isNum)then
 begin
  key:=copy(s,i1,i2);
  try
   result:=StrToInt(key)*n;
  except
   result:=def;
  end;
 end
 else result:=def;


end;



(*カンマ区切り文字を数字の配列にする*)
class function TTagText.getIntArray(const text:string;len:byte;ng:Integer=-1):TIntegerDynArray;
var
 left,right:string;
 pos,i:Integer;
 inside:boolean;
const
 QUOTE=',';
begin
 inside:=true;

 if(len>0)then
 begin
  setLength(result,len);
  right:=text;
  
  for i:=0 to len-1 do
  begin
   if(inside)then
   begin
    pos:=Ansipos(QUOTE,right);

    if(pos>0)then
    begin
     left:=copy(right,1,pos-1);
     result[i]:=getInteger(left,ng);
     right:=copy(right,pos+1,length(right)-pos);
     
    end
    else
    begin
     result[i]:=TTagText.getInteger(right,ng);
     inside:=false;
    end;
   end
   else result[i]:=ng;
  end;

 end;
end;

(*情報のリセット*)
procedure TTagText.clear();
begin
 self.rText:='';
 self.lText:='';
 self.mText:='';
 self.bufferText:='';
end;

(*現在のフォント情報を保存し、タグテキストから新たなフォント情報を返す*)
function TTagText.getTextInf(inf:TTextInf;const tag:string):TTextInf;
var
 s:string;
 newInf:TFontInf;
begin

 if(tag<>'')then
 begin

  s:=trim(AnsiLowercase(tag));

  if(AnsiStartsText('font',s))then
  begin

   fontinf:=TFontInf.Create();
   fontinf.color:=inf.font.Color;
   fontinf.height:=inf.font.Height;
   fontinf.name:=inf.font.Name;
   stack.Push(fontinf);

   changeFontForFontTag(inf.font,copy(tag,length('font')+1,length(s)));
  end
  else if(s='/font')then
  begin
   if(stack.Count>0)then
    newinf:=TFontInf(stack.pop());
    if(newInf<>nil)then
    begin
     inf.font.Color:=newinf.color;
     inf.font.Name:=newinf.name;
     inf.font.Height:=newinf.height;
     newinf.Free();
    end;
  end
  else if(s='b')then
   inf.font.Style:=inf.font.Style+[fsBold]
  else if(s='/b')then
   inf.font.Style:=inf.font.Style-[fsBold]
  else if(s='i')then
   inf.font.Style:=inf.font.Style+[fsItalic]
  else if(s='/i')then
   inf.font.Style:=inf.font.Style-[fsItalic]
  else if(s='u')then
   inf.font.Style:=inf.font.Style+[fsUnderline]
  else if(s='/u')then
   inf.font.Style:=inf.font.Style-[fsUnderline]
  else if(s='s')then
   inf.font.Style:=inf.font.Style+[fsStrikeOut]
  else if(s='/s')then
   inf.font.Style:=inf.font.Style-[fsStrikeOut]
 { else if(s='tt')then
   inf.font.Pitch:=fpFixed
  else if(s='/tt')then
   inf.font.Pitch:=fpDefault }
  else if(s='br')or(s='br /')then
  begin
   inf.newline:=true;
  end
  else if AnsiStartsText('lineheight=',s)then
  inf.lineHeight:=getInteger
  (copy(s,length('lineheight=')+1,length(s)),inf.lineHeight)
  else if AnsiStartsText('shiftx=',s)then
  inf.x:=inf.x+getInteger
  (copy(s,length('shiftx=')+1,length(s)),0)
  else if AnsiStartsText('shifty=',s)then
  inf.y:=inf.y+getInteger
  (copy(s,length('shifty=')+1,length(s)),0)
 end;

 result:=inf;

end;

(*fontタグを用いてフォントを修飾*)
procedure TTagText.changeFontForFontTag(var font:TFont;const tag:string);
var
 s1,s2:string;
 n1,n2,i,state,v:integer;
const
 DEF=0;
 EQUAL=1;
 QUOTE=2;
begin

 n1:=1;
 n2:=0;
 state:=DEF;

 for i:=1 to Length(tag) do
 begin
  case state of
   DEF:
    if(tag[i]='=')then
    begin
     state:=EQUAL;
     s1:=AnsiLowerCase(trim(copy(tag,n1,i-n1)));
     n2:=i+1;
    end;
   EQUAL:
    if(tag[i]='"')then
    begin
     state:=QUOTE;
    end;
   QUOTE:
    if(tag[i]='"')then
    begin
     state:=DEF;
     s2:=copy(tag,n2+1,i-n2-1);
     n1:=i+1;
     if('pixels'=s1)then
      font.Height:=getInteger(s2,font.Height)
     else if('color'=s1)then
     begin
      v:=TTagText.getColor(s2);
      if(v<>-1)then
       font.color:=v;
     end
     else if('face'=s1)then
     begin
      font.Name:=s2;
     end;
   end;
  end;
 end;

end;

function TTagText.setText(const s:string;mode:shortInt):shortInt;
var
 i1,i2:Integer;
begin


 lText:=s;
 rText:='';
 mText:='';

 if(mode=ttOpen)then
 begin
  i2:=ansipos('>',s);
  if(i2>0)then
  begin
   bufferText:=bufferText+copy(s,1,i2);
   mText:=trim(copy(bufferText,2,length(bufferText)-2));
   rText:=copy(s,i2+1,length(s));
   bufferText:='';
   lText:='';
   result:=ttClose;
  end
  else
  begin
   bufferText:=bufferText+s;
   lText:='';
   result:=ttOpen;
  end;
 end

 else                    
 begin
  i1:=ansipos('<',s);
  if(i1>0)then
  begin
   mText:=copy(s,i1,length(s));
   lText:=copy(s,0,i1-1);
   i2:=ansipos('>',mText);
   if(i2>0)then
   begin
    result:=ttClose;
    rText:=copy(mText,i2+1,length(mText));
    mText:=trim(copy(mText,2,i2-2));
   end
   else
   begin
    result:=ttOpen;
    bufferText:=bufferText+mText;
    rText:='';
    mText:='';
   end;
  end
  else
   result:=ttNone;
 end;

end;

{TTextImage}

(*テキストファイルを読み込んでビットマップ形式にする*)
function TTextImage.loadText(fileName:string;var bitmap:TBitmap):boolean;
var
 sList:TStringList;
 i,n,v,w,h,widest,highest:Integer;
 check:shortInt;
 s:string;
 tagText:TTagText;
 tInf:TTextInf;
 pos:TPoint;
 oldBs:TBrushStyle;
 fonttranceparent,autoLine:boolean;
const
 padding=1;
begin
 autoLine:=true;

 sList:=TStringList.Create();
 tInf:=TTextInf.Create();
 tagText:=TTagtext.create();
        
 (*キャンバスの設定*)
  if(bitmap=nil)then
  begin
   bitmap:=TBitmap.Create();
   bitmap.HandleType:=bmDIB;
   bitmap.PixelFormat:=pf32bit;
  end;

  bitmap.Width:=632;
  bitmap.Height:=420;
  bitmap.Canvas.Font.Color:=clWhite;
  bitmap.Canvas.Font.Height:=-12;
  bitmap.Canvas.Brush.Color:=clBlack;
  bitmap.Canvas.Font.Charset:=DEFAULT_CHARSET;
  bitmap.Canvas.Font.Name:='ＭＳ Ｐゴシック';
  tInf.x:=0;
  tInf.y:=padding;               //テキストの高さ
  tInf.lineHeight:=100;
  tInf.font:=bitmap.Canvas.Font;
  tInf.newline:=false;
  fonttranceparent:=false;
  w:=0;
  h:=0;
  widest:=0;
  highest:=0;
  oldBs:=bitmap.Canvas.Brush.Style;
  tinf.font.Pitch:=fpDefault;

 try
  (*テキスト書き込み*)
  sList.LoadFromFile(fileName);
  n:=0;
  s:=trim(sList.Strings[n]);
  (*テキストの設定*)
  if(s='[jptx:init]')then
  begin
   n:=1;
   for  i:=n  to sList.Count-1 do
   begin

    sList.Strings[i]:=trim(sList.Strings[i]);


    if not(AnsiStartsText(';',sList.Strings[i]))then

    if(sList.Strings[i]='[jptx:begin]')then
    begin
     break;
    end

    else if(AnsiStartsText('fontpixels=',sList.Strings[i]))then
    begin
     s:=copy(sList.Strings[i],length('fontpixels=')+1,length(sList.Strings[i]));
     bitmap.Canvas.Font.Height:=TTagText.getInteger(s,bitmap.Canvas.Font.Height);
    end

    else if(AnsiStartsText('fontcolor=',sList.Strings[i]))then
    begin
     s:=copy(sList.Strings[i],length('fontcolor=')+1,length(sList.Strings[i]));
     v:=TTagText.getColor(s);
     if(v<>-1)then
      bitmap.Canvas.Font.Color:=v;
    end

    else if(AnsiStartsText('backcolor=',sList.Strings[i]))then
    begin
     s:=copy(sList.Strings[i],length('backcolor=')+1,length(sList.Strings[i]));
     v:=TTagText.getColor(s);
     if(v<>-1)then
      bitmap.Canvas.Brush.Color:=v;
    end

    else if(AnsiStartsText('backwidth=',sList.Strings[i]))then
    begin
     s:=copy(sList.Strings[i],length('backwidth=')+1,length(sList.Strings[i]));
     w:=TTagText.getInteger(s,0);
     if(w>0)then bitmap.Width:=w;
    end

    else if(AnsiStartsText('backheight=',sList.Strings[i]))then
    begin
     s:=copy(sList.Strings[i],length('backheight=')+1,length(sList.Strings[i]));
     h:=TTagText.getInteger(s,0);
     if(h>0)then bitmap.Height:=h;
    end

    else if(AnsiStartsText('lineheight=',sList.Strings[i]))then
    begin
     s:=copy(sList.Strings[i],length('lineheight')+1,length(sList.Strings[i]));
     tInf.lineHeight:=TTagText.getInteger(s,tInf.lineHeight);
    end

    else if(AnsiStartsText('fontface=',sList.Strings[i]))then
    begin
     s:=copy(sList.Strings[i],length('fontface=')+1,length(sList.Strings[i]));
     bitmap.Canvas.Font.Name:=s;
    end

    else if(AnsiStartsText('autoline=',sList.Strings[i]))then
    begin
     s:=copy(sList.Strings[i],length('autoline=')+1,length(sList.Strings[i]));
     autoLine:=(TTagText.getInteger(s,1)>0);
    end

    else if(AnsiStartsText('antialias=',sList.Strings[i]))then
    begin
     s:=copy(sList.Strings[i],length('antialias=')+1,length(sList.Strings[i]));
     tinf.antialias:=(TTagText.getInteger(s,0)>0);
    end

    else if(AnsiStartsText('fonttransparent=',sList.Strings[i]))then
    begin
     s:=copy(sList.Strings[i],length('fonttransparent=')+1,length(sList.Strings[i]));
     if(TTagText.getInteger(s,0)<>0)then
     begin
      fonttranceparent:=true;
     end
     else
     begin
      fonttranceparent:=false;
     end;
    end;
   end;

   n:=i+1;
  end;

  light.FillBMP(bitmap,bitmap.Canvas.Brush.Color,false);
//  bitmap.Canvas.FillRect(Rect(0,0,bitmap.Width,bitmap.Height));
  if(fonttranceparent)then
  begin
   bitmap.Canvas.Pen.Color:=bitmap.Canvas.Font.Color;
   bitmap.Canvas.PenPos:=Point(0,0);
   bitmap.Canvas.LineTo(bitmap.Width,0);
  end;

  (*実際の文字の入力*)


  bitmap.Canvas.Brush.Style:=bsClear;
  tInf.x:=0;
  check:=ttNone;

  for  i:=n  to sList.Count-1 do
  begin

   if(trim(sList.Strings[i])='[jptx:end]')then
   begin
    break;
   end;

   if(autoline)then tInf.x:=0;

   check:=tagText.setText(sList.Strings[i],check);
   if(check=ttClose)then
   begin
    repeat

     (*タグの無い部分の描画*)
     if(tagText.lText<>'')then
     begin
      if(tinf.antialias)then
       pos:=SuperTextOut(bitmap,tInf.x,tInf.y,bitmap.Canvas.Font,tagText.lText)
      else
      begin
       bitmap.Canvas.TextOut(tInf.x,tInf.y,tagText.lText);
       pos:=bitmap.Canvas.PenPos;
      end;

      tInf.x:=Pos.X;
      widest:=max(widest,tInf.x);
      highest:=max(highest,pos.y+bitmap.Canvas.Font.Height);
     end;
     tInf:=tagText.getTextInf(tInf,tagText.mText); //タグ情報の読み込み
     bitmap.Canvas.Font:=tInf.font;


     (*強制改行*)
     if(tInf.newline)then
     begin
      tInf.newline:=false;
      tagText.lText:='';
      tInf.x:=0;
      inc(tInf.y,Round(bitmap.Canvas.Font.Height*tinf.lineHeight/100));
     end;

     tagText.mText:='';
     check:=tagText.setText(tagText.rText,check);

    until(check<>ttClose);

    if(tagText.lText<>'')then
    begin
     if(tinf.antialias)then
       pos:=SuperTextOut(bitmap,tInf.x,tInf.y,bitmap.Canvas.Font,tagText.lText)
     else
     begin
      bitmap.Canvas.TextOut(tInf.x,tInf.y,tagText.lText);
      pos:=bitmap.Canvas.PenPos;
     end;

     tInf.x:=Pos.X;
     widest:=max(widest,tinf.x);
     highest:=max(highest,pos.y+bitmap.Canvas.Font.Height);
    end;

   end
   else
   begin

    if(tagText.lText<>'')then
    begin
     if(tinf.antialias)then
       pos:=SuperTextOut(bitmap,tInf.x,tInf.y,bitmap.Canvas.Font,tagText.lText)
     else
     begin
      bitmap.Canvas.TextOut(tInf.x,tInf.y,tagText.lText);
      pos:=bitmap.Canvas.PenPos;
     end;

    tInf.x:=pos.x;
    widest:=max(widest,tInf.x);
    highest:=max(highest,pos.y+bitmap.Canvas.Font.Height);
   end;

    if(autoline)then tInf.x:=1;

   end;

   if(tInf.x>0)and(autoline)then
   begin
    tInf.x:=0;
    inc(tInf.y,Round(bitmap.Canvas.Font.Height*tinf.lineHeight/100));
  //  inc(tInf.y,+bitmap.Canvas.Font.Height+tinf.lineHeight);
   end;

  end;

  inc(tInf.y,bitmap.Canvas.Font.Height+tinf.lineHeight);

  (*背景にサイズ設定が無い場合自動的に設定*)
  if(w<1)then
  begin
   widest:=min(widest,632);
   bitmap.Width:=widest;
  end;
  if(h<1)then
  begin
   highest:=min(highest,420);
   bitmap.Height:=highest;
  end;

  tagText.Free();
  sList.Free();
  tInf.Free();
  result:=true;
  bitmap.Canvas.Brush.Style:=oldBs;
 except
  bitmap.Canvas.Brush.Style:=oldBs;
  tagText.Free();
  sList.Free();
  tInf.Free();
  windows.MessageBox(0,'','テキスト描画のエラー',MB_OK);
  result:=false;
 end;

end;

end.


