unit MainFm;
(*
 * Copyright (c) 2015 - 2025 Dr. Yuriy Kotsarenko. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and limitations under the License.
 *)

// This example illustrates the following features:
//  * Drawing normal and curved text using different fonts, color gradients, outlines and effects.

interface

uses
  System.Types, System.Classes, Vcl.Graphics, Vcl.Forms, Vcl.Dialogs, Vcl.Controls, PXT.Types, PXT.Graphics, 
  PXT.Controls;

type
  TMainForm = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    FRenderingControl: TRenderingControl;
    FDisplaySize: TPoint2i;
    FDisplayScale: Single;

    FDevice: TDevice;
    FSwapChain: TSwapChain;
    FCanvas: TCanvas;
    FTextRenderer: TTextRenderer;
    FTextModeller: TTextModeller;

    FTextureCanvas: TTexture;
    FTextureScreen: TTexture;

    procedure RenderingControlResize(Sender: TObject);
    procedure RenderingControlPaint(Sender: TObject);
    procedure CreateRenderingControl;
  public
  end;

var
  MainForm: TMainForm;

implementation
{$R *.dfm}

uses
  System.SysUtils, System.Math, PXT.Headers, PXT.Graphics.Consts;

procedure TMainForm.FormCreate(Sender: TObject);
begin
  CreateRenderingControl;
  FDisplaySize := Point2i(ClientWidth, ClientHeight);
  FDisplayScale := PixelsPerInch / 96.0;
  try
    FDevice := TDevice.Create;
    FSwapChain := TSwapChain.Create(FDevice, FRenderingControl.WindowHandle, FDisplaySize);
    FCanvas := TCanvas.Create(FDevice);
    FTextRenderer := TTextRenderer.Create(FCanvas, Point2i(512, 512), TPixelFormat.LA8);
    FTextModeller := TTextModeller.Create(FDevice);

    FTextureCanvas := TTexture.Create(FDevice, TTextureParameters.Create(FDisplaySize.X, FDisplaySize.Y,
      0, TPixelFormat.RGBA8, TTextureType.Surface, TTextureAttribute.Drawable, 8));

    FTextureScreen := TTexture.Create(FDevice, TTextureParameters.Create(FDisplaySize.X, FDisplaySize.Y,
      0, TPixelFormat.RGBA8, TTextureType.Surface, TTextureAttribute.Drawable));
  except
    on E: EAfterwarpException do
    begin
      MessageDlg(E.Message, mtError, [mbOK], 0);
      Application.Terminate;
      Exit;
    end;
  end;
end;

procedure TMainForm.FormDestroy(Sender: TObject);
begin
  FTextRenderer.Free;
  FCanvas.Free;
  FSwapChain.Free;
  FDevice.Free;
end;

procedure TMainForm.RenderingControlResize(Sender: TObject);
begin
  if FSwapChain.Handle <> nil then
  begin
    FDisplaySize := Point2i(FRenderingControl.Width, FRenderingControl.Height);
    FSwapChain.Resize(FDisplaySize);

    FTextureScreen.Free;
    FTextureCanvas.Free;
    try
      FTextureCanvas := TTexture.Create(FDevice, TTextureParameters.Create(FDisplaySize.X, FDisplaySize.Y,
        0, TPixelFormat.RGBA8, TTextureType.Surface, TTextureAttribute.Drawable, 8));

      FTextureScreen := TTexture.Create(FDevice, TTextureParameters.Create(FDisplaySize.X, FDisplaySize.Y,
        0, TPixelFormat.RGBA8, TTextureType.Surface, TTextureAttribute.Drawable));
    except
      on E: EAfterwarpException do
      begin
        MessageDlg(E.Message, mtError, [mbOK], 0);
        Application.Terminate;
        Exit;
      end;
    end;
    FRenderingControl.Invalidate;
  end;
end;

procedure TMainForm.RenderingControlPaint(Sender: TObject);
var
  LFontParams: TFontParameters;
  LTexCoords: TQuad;
  LPosition: TPoint2f;
  LTechString: string;
  LModifiers: TTextRenderModifiers;
begin
  FTextureCanvas.BeginScene;
  try
    FDevice.Clear([TClearLayer.Color], FloatColor($FFE1E2E6));

    FCanvas.BeginScene;
    try
      // Draw dark background on bottom part of the form.
      FCanvas.Attributes := [];
      FCanvas.ContextState := TCanvasContextState.FlatScene;

      FCanvas.FillRect(FloatRect(0.0, FDisplaySize.Y * 0.5, FDisplaySize.X, FDisplaySize.Y * 0.5), $FF40444C,
        TBlendingEffect.Copy);

      LPosition := Point2f(FDisplaySize.X * 0.5, FDisplaySize.Y * 0.15);

      // For curved text, we have to use vector-based text renderer, which also required multisampled
      // render target to enable proper anti-aliasing.
      FTextModeller.SetFontParameters(TFontParameters.Create('Cambria', 16.0 * FDisplayScale,
        TFontWeight.Bold));

      FTextModeller.DrawCurved(FCanvas, LPosition, 'The framework also supports rendering curved text ' +
        'to better accomodate for existing content.', 1000.0, NAN, ColorPair($FF000040, $FF0040A0));

      // Prepare canvas for text output.
      FCanvas.ContextState := TCanvasContextState.FlatText;
      FCanvas.SamplerState := TCanvasSamplerState.Default;

      // Arial Semi-Bold 16px.
      LFontParams := TFontParameters.Create('Arial', 16.0 * FDisplayScale);
      LFontParams.Effect.BorderType := TFontBorder.Normal;
      LFontParams.Effect.ShadowOpacity := 1.0;
      FTextRenderer.FontParameters := LFontParams;

      FillChar(LModifiers, SizeOf(TTextRenderModifiers), 0);
      LModifiers.Interleave := 1.0;

      if FDisplayScale >= 2.0 then
        LModifiers.Interleave := 8.0; // Extend further on a 2x HiDPI screen for demo purposes.

      FTextRenderer.DrawCentered(LPosition, '欢迎使用演示不同文本字体和视觉效果的应用程序。',
        ColorPair($FF00E0FF, $FF0000FF), 1.0, True, @LModifiers);

      LPosition.Y := LPosition.Y + 32.0 * FDisplayScale;

      // Tahoma 14px.
      FTextRenderer.FontParameters := TFontParameters.Create('Tahoma', 14.0 * FDisplayScale);
      FTextRenderer.DrawCentered(LPosition, 'C''è il pieno supporto per le lingue straniere nel formato ' +
        'unicode UTF8.', ColorPair($FF008000, $FFFF0000));

      LPosition.Y := LPosition.Y + 32.0 * FDisplayScale;

      // Arial Regular 16px.
      LFontParams := TFontParameters.Create('Arial', 16.0 * FDisplayScale);
      LFontParams.Effect.BorderType := TFontBorder.Normal;
      LFontParams.Effect.ShadowOpacity := 1.0;
      FTextRenderer.FontParameters := LFontParams;

      FTextRenderer.DrawCentered(LPosition, 'Arial Regular 16px with Border, Shadow and Gradient.',
        ColorPair($FF00FF00, $FFFFFFFF));

      LPosition.Y := LPosition.Y + 32.0 * FDisplayScale;

      // Arial Regular 14px, Black.
      FTextRenderer.FontParameters := TFontParameters.Create('Arial', 14.0 * FDisplayScale);
      FTextRenderer.DrawCentered(LPosition, 'Arial Regular 14px without effects, black.', $FF000000);

      LPosition.Y := LPosition.Y + 28.0 * FDisplayScale;

      // Segoe UI SemiBold 16px, Inverted with Border and Shadow.
      LFontParams := TFontParameters.Create('Segoe UI', 16.0 * FDisplayScale, TFontWeight.SemiBold);
      LFontParams.Effect.BorderType := TFontBorder.SemiHeavy;
      LFontParams.Effect.BorderBrightness := 0.75;
      LFontParams.Effect.FillBrightness := 0.0;
      LFontParams.Effect.ShadowBrightness := 0.5;
      LFontParams.Effect.ShadowOpacity := 1.0;
      FTextRenderer.FontParameters := LFontParams;

      FTextRenderer.DrawCentered(LPosition, 'Segoe UI SemiBold 16px inverted with Border and Shadow.',
        $FFFFFFFF);

      LPosition.Y := LPosition.Y + 32.0 * FDisplayScale;

      // Consolas 12px.
      FTextRenderer.FontParameters := TFontParameters.Create('Consolas', 12.0 * FDisplayScale);
      FTextRenderer.DrawCentered(LPosition, 'Consolas (monofont) Regular 12px with Gradient.',
        ColorPair($FF0000FF, $FFA00000));

      LPosition.Y := LPosition.Y + 32.0 * FDisplayScale;

      // Verdana Bold 20px + Border + Shadow + Gradient
      LFontParams := TFontParameters.Create('Verdana', 20.0 * FDisplayScale, TFontWeight.Bold);
      LFontParams.Effect.BorderType := TFontBorder.Heavy;
      LFontParams.Effect.ShadowOpacity := 1.0;
      FTextRenderer.FontParameters := LFontParams;

      FTextRenderer.DrawAligned(LPosition, 'Verdana 20px aligned to left.', ColorPair($FF0000FF, $FFFF00FF),
        TTextAlignment.Start, TTextAlignment.Middle);

      // Cambria Bold Italic 18px + Border + Shadow + Gradient
      LFontParams := TFontParameters.Create('Cambria', 18.0 * FDisplayScale, TFontWeight.Bold,
        TFontStretch.Normal, TFontSlant.Italic);
      LFontParams.Effect.BorderType := TFontBorder.Normal;
      LFontParams.Effect.ShadowOpacity := 1.0;
      FTextRenderer.FontParameters := LFontParams;

      FTextRenderer.DrawAligned(LPosition, 'Cambria Italic 18px aligned to right.',
        ColorPair($FF0000FF, $FF00FFFF), TTextAlignment.Ending, TTextAlignment.Middle);

      FCanvas.Attributes := [TCanvasAttribute.Transform];
      FCanvas.Transform := TMatrix3x2f.Rotate(Pi * -0.5) * TMatrix3x2f.Translate(
        FDisplaySize.X - 8.0 * FDisplayScale, FDisplaySize.Y * 0.25);

      // Cambria UI 16px + Border + Shadow + Gradient, Vertical (with correct shadow)
      LFontParams := TFontParameters.Create('Cambria', 16.0 * FDisplayScale);
      LFontParams.Effect.BorderType := TFontBorder.Normal;
      LFontParams.Effect.ShadowOpacity := 1.0;
      LFontParams.Effect.ShadowDistance := Point2f(-2.0, 2.0);
      FTextRenderer.FontParameters := LFontParams;

      FTextRenderer.DrawAligned(TPoint2f.Zero, 'Vertical text is also possible.',
        ColorPair($FFFF0080, $FFFFFFFF), TTextAlignment.Middle, TTextAlignment.Ending);

      FCanvas.Transform := TMatrix3x2f.Rotate(Pi * 0.5) * TMatrix3x2f.Translate(
        8.0 * FDisplayScale, FDisplaySize.Y * 0.75);

      // Calibri UI 16px + Border + Shadow + Gradient, Vertical (with correct shadow)
      LFontParams := TFontParameters.Create('Cambria', 16.0 * FDisplayScale);
      LFontParams.Effect.BorderType := TFontBorder.Normal;
      LFontParams.Effect.ShadowOpacity := 1.0;
      LFontParams.Effect.ShadowDistance := Point2f(2.0, -2.0);
      FTextRenderer.FontParameters := LFontParams;

      FTextRenderer.DrawAligned(TPoint2f.Zero, 'Text in a different orientation.',
        ColorPair($FFA0A0A0, $FFFFFFFF), TTextAlignment.Middle, TTextAlignment.Ending);

      FCanvas.Attributes := [];
      LPosition := Point2f(FDisplaySize.X * 0.5, FDisplaySize.Y * 0.6);

      // Tahoma Regular 16px + Border + Shadow + Gradient
      LFontParams := TFontParameters.Create('Tahoma', 16.0 * FDisplayScale);
      LFontParams.Effect.BorderType := TFontBorder.Normal;
      LFontParams.Effect.ShadowOpacity := 1.0;
      FTextRenderer.FontParameters := LFontParams;

      FTextRenderer.DrawCentered(LPosition, 'このアプリケーションは、異なる言語で書かれたテキストを完全にサポートしています。',
        ColorPair($FFC65D7B, $FFE0FCBF));

      LPosition.Y := LPosition.Y + 40.0 * FDisplayScale;

      // Cambria SemiBold 18px with Border and Shadow.
      LFontParams := TFontParameters.Create('Cambria', 18.0 * FDisplayScale, TFontWeight.SemiBold);
      LFontParams.Effect.BorderType := TFontBorder.Normal;
      LFontParams.Effect.ShadowOpacity := 1.0;
      FTextRenderer.FontParameters := LFontParams;

      FTextRenderer.DrawCentered(LPosition, 'Some Cambria 18px sample text with Gradient, Border and Shadow.',
        ColorPair($FFE6CFFF, $FFE06E98));

      LPosition.Y := LPosition.Y + 32.0 * FDisplayScale;

      // Calibri 16px + Border + Shadow + Gradient.
      LFontParams := TFontParameters.Create('Calibri', 16.0 * FDisplayScale, TFontWeight.SemiBold);
      LFontParams.Effect.BorderType := TFontBorder.Normal;
      LFontParams.Effect.ShadowOpacity := 1.0;
      FTextRenderer.FontParameters := LFontParams;

      FTextRenderer.DrawCentered(LPosition, 'Calibri Regular 16px with Border, Shadow and Gradient.',
        ColorPair($FFFF0000, $FFFFE000));

      LPosition.Y := LPosition.Y + 32.0 * FDisplayScale;

      // Segoe UI SemiBold 16px, Inverted with Border and Shadow.
      LFontParams := TFontParameters.Create('Segoe UI', 16.0 * FDisplayScale, TFontWeight.SemiBold);
      LFontParams.Effect.BorderType := TFontBorder.Heavy;
      LFontParams.Effect.BorderBrightness := 1.0;
      LFontParams.Effect.FillBrightness := 0.0;
      LFontParams.Effect.ShadowBrightness := 0.75;
      LFontParams.Effect.ShadowOpacity := 1.0;
      FTextRenderer.FontParameters := LFontParams;

      FTextRenderer.DrawCentered(LPosition, 'Segoe UI SemiBold 16px inverted with Border and Shadow.',
        ColorPair($FFFFFFFF));

      LPosition.Y := LPosition.Y + 32.0 * FDisplayScale;

      // Arial 14px.
      FTextRenderer.FontParameters := TFontParameters.Create('Arial', 14.0 * FDisplayScale);
      FTextRenderer.DrawCentered(LPosition, 'Καλώς ορίσατε στον έλεγχο γραμματοσειρών του πλαισίου.',
        ColorPair($FF00FF00, $FFFFFFFF));

      // Set canvas state for vector-based text rendering.
      FCanvas.ContextState := TCanvasContextState.FlatScene;

      FTextModeller.SetFontParameters(TFontParameters.Create('Calibri', 16.0 * FDisplayScale,
        TFontWeight.SemiBold));

      FTextModeller.DrawCurved(FCanvas, LPosition, 'El framework es capaz de rasterizar texto en curva ' +
        'en cualquier orientación y con la transformación del canvas.', -1000.0, NAN,
        ColorPair($FF00E0FF, $FF0000FF));

      // Draw technology string.
      LFontParams := TFontParameters.Create('Segoe UI', 16.0 * FDisplayScale, TFontWeight.SemiBold);
      LFontParams.Effect.BorderType := TFontBorder.SemiHeavy;
      LFontParams.Effect.BorderBrightness := 1.0;
      LFontParams.Effect.FillBrightness := 0.0;
      LFontParams.Effect.ShadowBrightness := 0.75;
      LFontParams.Effect.ShadowOpacity := 1.0;
      FTextRenderer.FontParameters := LFontParams;

      if FDevice.Technology = TDeviceTechnology.Direct3D then
        LTechString := 'Direct3D (' + IntToStr(FDevice.TechVersion shr 8) + '.' +
          IntToStr((FDevice.TechVersion shr 4) and $0F) + '), features: '
      else
        LTechString := 'OpenGL (' + IntToStr(FDevice.TechVersion shr 8) + '.' +
          IntToStr((FDevice.TechVersion shr 4) and $0F) + '+), features: ';

      LTechString := LTechString + IntToStr(FDevice.TechFeatureVersion shr 8) + '.' +
        IntToStr((FDevice.TechFeatureVersion shr 4) and $0F);

      FTextRenderer.Draw(Point2f(8.0 * FDisplayScale, 4.0 * FDisplayScale), Utf8String(LTechString),
        ColorPair($A0FFFFFF));
    finally
      FCanvas.EndScene;
    end;
  finally
    FTextureCanvas.EndScene;
  end;
  // Resolve multisampled texture into a non-multisampled one so it can be rendered with the canvas.
  FTextureScreen.Copy(FTextureCanvas, 0, TPoint2i.Zero, 0, TIntRect.Zero);

  FSwapChain.BeginScene;
  try
    FDevice.Clear([TClearLayer.Color], FloatColor($FF404040));

    FCanvas.BeginScene;
    try
      FCanvas.ContextState := TCanvasContextState.FlatText;
      FCanvas.SamplerState := TCanvasSamplerState.Default;
      FCanvas.Attributes := [];

      LTexCoords := TQuad.Unity;

      if (FDevice.Technology = TDeviceTechnology.OpenGL) or
        (FDevice.Technology = TDeviceTechnology.OpenGL_ES) then
        LTexCoords := LTexCoords.Flip;

      FCanvas.Quad(FTextureScreen, Quad(0.0, 0.0, FDisplaySize.X, FDisplaySize.Y), LTexCoords,
        TColorRect.White, TBlendingEffect.Copy);
    finally
      FCanvas.EndScene;
    end;
  finally
    FSwapChain.EndScene;
  end;
end;

procedure TMainForm.CreateRenderingControl;
begin
  FRenderingControl := TRenderingControl.Create(nil);
  FRenderingControl.Parent := Self;
  FRenderingControl.Align := alClient;
  FRenderingControl.Width := 256;
  FRenderingControl.Height := 256;
  FRenderingControl.OnPaint := RenderingControlPaint;
  FRenderingControl.OnResize := RenderingControlResize;
end;

end.