unit textrender; {$mode objfpc}{$H+} interface uses Classes, SysUtils, graphics, math, LazUTF8; function renderFormattedText(canvas: TCanvas; rect: Trect; x,y: integer; const formattedtext: string): trect; implementation {$ifdef windows} uses windows; {$endif} {$ifdef DARWIN} uses macport,CocoaAll; {$endif} const altfonts: array [0..8] of string=('Consolas','Courier','Courier New','Fixedsys','Terminal','Arial','MS Sans Serif','Comic Sans MS','Wingdings'); var _8bitcolors:array[0..255] of dword; type TTextState=record bgcolor: tcolor; font: tfont; end; type TParsedValueInformation=record defined: boolean; value:integer; end; PParsedValueInformation=^TParsedValueInformation; procedure restoreOriginalState(canvas: TCanvas; originalState: TTextState); begin canvas.font.Assign(originalState.font); canvas.brush.color:=originalState.bgcolor; end; function readIntegerString(const formattedtext: string; var index: integer): string; begin result:=''; while index0 then b:=$5f+(b-1)*$28; g:=(i-16) mod 36; g:=g div 6; if g>0 then g:=$5f+(g-1)*$28; } end; for i:=232 to 255 do begin r:=8+10*(i-232); _8bitcolors[i]:=RGBToColor(r,r,r); end; end; procedure handleSGR(canvas: TCanvas; const originalState: TTextState; ParsedValueList: PParsedValueInformation; ParsedValueCount: integer); var operation: integer; t: Tcolor; r,g,b: byte; i: integer; begin i:=0; while ii) and ParsedValueList[i].defined then begin case ParsedValueList[i].value of 5: //8 bit begin inc(i); if (parsedvaluecount>i) and parsedValueList[i].defined then begin if inrange(parsedValueList[i].value,0,255) then begin initColorList; canvas.font.color:=_8bitcolors[parsedValueList[i].value]; end; end; end; 2: //24 bit begin inc(i); if (ParsedValueCount>i) and parsedValueList[i].defined then r:=ParsedValueList[i].Value else r:=0; inc(i); if (ParsedValueCount>i) and parsedValueList[i].defined then g:=ParsedValueList[i].Value else g:=0; inc(i); if (ParsedValueCount>i) and parsedValueList[i].defined then b:=ParsedValueList[i].Value else b:=0; canvas.font.color:=RGBToColor(r,g,b); end; end; end; end; 39: canvas.font.color:=originalstate.Font.color; 40..47: begin //set background color canvas.brush.color:=getAnsiColor(operation-40); end; 48: begin //set background color ex inc(i); if (parsedvaluecount>=2) and ParsedValueList[1].defined then begin case ParsedValueList[1].value of 5: //8 bit begin inc(i); if (parsedvaluecount>i) and parsedValueList[i].defined then begin if inrange(parsedValueList[i].value,0,255) then begin initColorList; canvas.brush.color:=_8bitcolors[parsedValueList[i].value]; end; end; end; 2: //24 bit begin inc(i); if (ParsedValueCount>i) and parsedValueList[i].defined then r:=ParsedValueList[i].Value else r:=0; inc(i); if (ParsedValueCount>i) and parsedValueList[i].defined then g:=ParsedValueList[i].Value else g:=0; inc(i); if (ParsedValueCount>i) and parsedValueList[i].defined then b:=ParsedValueList[i].Value else b:=0; canvas.brush.color:=RGBToColor(r,g,b); end; end; end; end; 49: canvas.brush.color:=originalstate.bgcolor; 90..97: begin canvas.font.color:=getAnsiColor(operation-90+8); end; 100..107: begin canvas.brush.color:=getAnsiColor(operation-100+8); end; end; inc(i); end; end; procedure handleCSISequence(canvas: TCanvas; rect: Trect; const formattedtext: string; const originalState: TTextState; var index: integer; var _x: integer; var _y: integer); function LineHeight: integer; begin result:=canvas.TextHeight('AFgGjJ'); end; function SpaceWidth: integer; begin result:=canvas.TextHeight(' '); end; var lastvalue: integer=0; valuelist: array of TParsedValueInformation; s: string; begin setlength(valuelist,0); while index=rect.Bottom-LineHeight then exit; if length(valuelist)=0 then exit; _y:=_y+LineHeight*valuelist[0].value; if _y>=rect.Bottom-LineHeight then _y:=rect.Bottom-LineHeight; exit; end; 'C': begin inc(index); if _x>=rect.right-spacewidth then exit; if length(valuelist)=0 then exit; _x:=_x+SpaceWidth*valuelist[0].value; if _x>=rect.right-SpaceWidth then _x:=rect.right-SpaceWidth; exit; end; 'D': begin inc(index); if _x=1) and valuelist[0].defined then _x:=rect.left+SpaceWidth*valuelist[0].value; if (length(valuelist)>=2) and valuelist[1].defined then _y:=rect.top+LineHeight*valuelist[1].value; end; 'm': //the actual use for this begin inc(index); handleSGR(canvas,originalState, @valuelist[0],length(valuelist)); exit; end; ';': begin inc(index); if index<>lastvalue+1 then //missing a value begin setlength(valuelist,length(valuelist)+1); valuelist[length(valuelist)-1].defined:=false; end; end; else begin inc(index); exit; //undefined end; end; end; end; procedure handleEscapeSequence(canvas: TCanvas; rect: Trect; const formattedtext: string; const originalState: TTextState; var index: integer; var _x: integer; var _y: integer); begin if index>=length(formattedtext) then exit; case formattedtext[index] of '[' : begin inc(index); handleCSISequence(canvas, rect, formattedtext, originalstate, index, _x, _y); end; 'c': begin inc(index); restoreOriginalState(canvas, originalState); end; end; end; function isChar(const s: string; index: integer; c: char): boolean; begin result:=false; if index>=length(s) then exit; result:=s[index]=c; end; function handleStyleSequence(canvas: TCanvas; const formattedtext: string; var index: integer): boolean; var i: integer; begin result:=false; if index>=length(formattedtext) then exit; i:=index; inc(i); case formattedtext[i] of 'b','B': begin if isChar(formattedtext, i+1,']') then begin canvas.Font.Style:=canvas.Font.Style+[fsBold]; index:=i+2; result:=true; end; end; 'u','U': begin if isChar(formattedtext, i+1,']') then begin canvas.Font.Style:=canvas.Font.Style+[fsUnderline]; index:=i+2; result:=true; end; end; 'i','I': begin if isChar(formattedtext, i+1,']') then begin canvas.Font.Style:=canvas.Font.Style+[fsItalic]; index:=i+2; result:=true; end; end; 's','S': begin if isChar(formattedtext, i+1,']') then begin canvas.Font.Style:=canvas.Font.Style+[fsStrikeOut]; index:=i+2; result:=true; end; end; '/': begin if (length(formattedtext)>=i+2) and (formattedtext[i+2]=']') then begin inc(i); case formattedtext[i] of 'b','B': begin canvas.Font.Style:=canvas.Font.Style-[fsBold]; index:=i+2; result:=true; end; 'u','U': begin canvas.Font.Style:=canvas.Font.Style-[fsUnderline]; index:=i+2; result:=true; end; 'i','I': begin canvas.Font.Style:=canvas.Font.Style-[fsItalic]; index:=i+2; result:=true; end; 's','S': begin canvas.Font.Style:=canvas.Font.Style-[fsStrikeOut]; index:=i+2; result:=true; end; end; end; end; end; end; function renderFormattedText(canvas: TCanvas; rect: Trect; x,y: integer; const formattedtext: string): trect; var i: integer; _x: integer; _y: integer; original: TTextState; lineheight: integer; w: integer; temprect: trect; maxx, maxy: integer; charlength: integer; c: string; procedure renderChar; begin //renderable character charlength:=UTF8CharacterLength(pchar(@formattedtext[i])); if charlength>1 then begin setlength(c,charlength); copymemory(@c[1], @formattedtext[i],charlength); w:=canvas.TextWidth(c); end else begin canvas.TextRect(rect,_x,_y,formattedtext[i]); w:=canvas.TextWidth(formattedtext[i]); end; if canvas.brush.Color<>original.bgcolor then begin temprect:=classes.rect(_x,_y,_x+w,_y+lineheight); if temprect.left>rect.right then begin inc(_x,w); maxx:=max(maxx, _x); inc(i); exit; end; if temprect.Right>rect.Right then temprect.right:=rect.right; if temprect.Bottom>rect.bottom then temprect.bottom:=rect.bottom; canvas.FillRect(temprect); end; if charlength>1 then canvas.TextRect(rect,_x,_y,c) else canvas.TextRect(rect,_x,_y,formattedtext[i]); inc(_x,w); maxx:=max(maxx, _x); inc(i,charlength); end; begin i:=1; original.font:=tfont.create; original.font.Assign(canvas.font); original.bgcolor:=canvas.brush.color; _x:=x; _y:=y; maxx:=_x; maxy:=_y; lineheight:=canvas.GetTextHeight('AFgGjJ'); while i<=length(formattedtext) do begin case formattedtext[i] of '[': begin //could be a secondary style thingy, check if handleStyleSequence(canvas,formattedtext, i)=false then renderchar; end; #27: //escape character begin inc(i); handleEscapeSequence(canvas, rect, formattedtext, original, i,_x,_y); maxx:=max(maxx, _x); maxy:=max(maxy, _y); end; #13: //return begin _x:=x; inc(_y, lineheight); maxy:=max(maxy, _y); inc(i); end; #10: inc(i); //ignore (linefeed) else renderChar; end; end; restoreOriginalState(canvas, original); original.font.free; result.left:=x; result.top:=y; result.right:=maxx; result.bottom:=maxy+lineheight; end; end.