program RichTextConsole; {$APPTYPE CONSOLE} // Atlas Info: store rich text in a blob field of a FireDAC memory table with // TAtlasDBRichEdit. Loads RTF, formats a selection, commits it to the field, // posts, cancels an unwanted change and rejects bytes that are not RTF. // Every step verifies its own result and the program exits with code 0. uses System.SysUtils, System.Classes, System.Variants, System.IOUtils, Data.DB, FireDAC.Comp.Client, Vcl.Forms, Vcl.Controls, Vcl.Graphics, Atlas.DBControls, Atlas.RichEdit; var Checks: Integer = 0; procedure Check(Condition: Boolean; const Description: string); begin if not Condition then raise Exception.Create('FAILED: ' + Description); Inc(Checks); Writeln(' ok ', Description); end; function RTF(const Body: string): TBytes; begin Result := TEncoding.ASCII.GetBytes('{\rtf1\ansi ' + Body + '}'); end; function CreateNotes(Owner: TComponent): TFDMemTable; begin Result := TFDMemTable.Create(Owner); Result.FieldDefs.Add('ID', ftInteger); Result.FieldDefs.Add('Title', ftWideString, 60); Result.FieldDefs.Add('Notes', ftBlob); Result.CreateDataSet; Result.AppendRecord([1, 'Acorn Design', Null]); Result.AppendRecord([2, 'Harbor Services', Null]); Result.First; end; procedure Run; var Form: TForm; Notes: TFDMemTable; Source: TDataSource; Rich: TAtlasDBRichEdit; Viewer: TAtlasRichEdit; Saved: TBytes; FileName: string; Rejected: Boolean; begin // The rich edit controls need a parent window; the form is never shown. Form := TForm.CreateNew(nil); try Notes := CreateNotes(Form); Source := TDataSource.Create(Form); Source.DataSet := Notes; Rich := TAtlasDBRichEdit.Create(Form); Rich.Parent := Form; Rich.DataSource := Source; Rich.DataField := 'Notes'; // A plain Atlas VCL rich editor reads the stored bytes back for checking. Viewer := TAtlasRichEdit.Create(Form); Viewer.Parent := Form; Writeln('1. Load RTF into the current record'); Rich.LoadRTF(RTF('\b Original notes\b0 for Acorn Design')); Check(Notes.State = dsEdit, 'LoadRTF puts the record into edit state and does not post'); Notes.Post; Saved := Rich.SaveRTF; Check((Length(Saved) > 5) and (TEncoding.ASCII.GetString(Saved, 0, 5) = '{\rtf'), 'SaveRTF returns RTF bytes'); Viewer.LoadRTF(Saved); Check(Pos('Original notes', Viewer.Text) > 0, 'the text survives the round trip through the blob field'); Writeln('2. Format a selection and commit it'); Rich.SelStart := 0; Rich.SelLength := 8; Rich.FormatSelection('Segoe UI', 18, [fsItalic], clRed); Check(Notes.State = dsEdit, 'FormatSelection starts editing the record'); Rich.CommitToField; Notes.Post; Viewer.LoadRTF(Rich.SaveRTF); Viewer.SelStart := 0; Viewer.SelLength := 8; Check((fsItalic in Viewer.SelAttributes.Style) and (Viewer.SelAttributes.Size = 18), 'the formatting is stored in the field'); Writeln('3. Cancel an unwanted change'); Rich.LoadRTF(RTF('Temporary notes')); Check(Notes.State = dsEdit, 'the replacement is pending'); Notes.Cancel; Viewer.LoadRTF(Rich.SaveRTF); Check(Pos('Original notes', Viewer.Text) > 0, 'Cancel restores the stored notes'); Writeln('4. Reject bytes that are not RTF'); Rejected := False; try Rich.LoadRTF(TEncoding.ASCII.GetBytes('plain text is not RTF')); except on EArgumentException do Rejected := True; end; Check(Rejected and (Notes.State = dsBrowse), 'invalid data raises and leaves the record in browse state'); Writeln('5. Each record keeps its own notes'); Notes.Next; Rich.LoadRTF(RTF('Notes for Harbor Services')); Notes.Post; Notes.First; Viewer.LoadRTF(Rich.SaveRTF); Check(Pos('Original notes', Viewer.Text) > 0, 'record 1 still shows its own notes'); Notes.Next; Viewer.LoadRTF(Rich.SaveRTF); Check(Pos('Harbor Services', Viewer.Text) > 0, 'record 2 shows the notes written to it'); FileName := TPath.Combine(GetCurrentDir, 'notes.rtf'); TFile.WriteAllBytes(FileName, Rich.SaveRTF); Writeln(' wrote ', FileName); finally Form.Free; end; end; begin try Application.Initialize; Run; Writeln('PASS: ', Checks, ' checks'); except on E: Exception do begin Writeln(E.ClassName, ': ', E.Message); Halt(1); end; end; end.