Page 1 of 2

Saving RVF to a stream with PNG Images

Posted: Mon Jun 25, 2012 10:59 pm
by DavidCase
I am reviewing the tRichView control with a view to adding it to my standard development toolkit. So far - very impressed. I do have one issue that may be me doing something really stupid but I would appreciate some help.

I started with the Ribbon Demo as does most of what we need for a simple bulk mailer application so we can send newslettrers to our customers. I added the capability to save the TRichView edited content to a stream and then save the stream into a BLOB field in a remote database on our server many miles away.

A user can then look at the available templates on the remote DB and download their choice into TRichView - works like a dream. Until I decided to include images (.png) in the RVF data - and that is where things went wrong.

If I use the same client machine then the images are saved to the application folder so reloading to this machine displays the image fine. However, when I send the RichView content to the mail body (using the Indy Demo you provide) the image does not get sent with the mail - just the dreaded little red X. :?

I suspect that I am not saving the actual image to the remote DB (just a link). Sorry - my first play with an editor (I usually write encryption libraries) so I am stumped.

Thanks in advance for any help.

david.

Posted: Tue Jun 26, 2012 6:03 am
by Sergey Tkachenko
Please send this email (using Indy) to richviewgmailcom

Mail Sent...

Posted: Wed Jun 27, 2012 10:31 am
by DavidCase
Hi,

I have sent you the output from the bulk mail test we are doing. Yu will see that the image has not been included in the mail.

Posted: Wed Jun 27, 2012 12:09 pm
by Sergey Tkachenko
Yes, I can see the image is not included in the email. The email just includes the code <img ... src="51.png">.

Why may it happen? I can imagine two reasons

1) This image was marked as external image by specifying its rvespImageFileName<>'', and HTML was saved with rvsoUseItemImageFileNames in Options.
For such images, their filename is written in <img src>, the image itself is not saved.

or

2) You forgot to assign OnSaveImage2 event

More Details...

Posted: Wed Jun 27, 2012 3:28 pm
by DavidCase
I suspect I may not be describing the problem I have properly - perhaps this will help.

I know this is a long topic but I am totally new to TRichView and have spent a long time looking through the forums but the problem is that I probably would not know if I found the answer because I dont know if I have even the basic code right. I would truly appreciate any help that helps me to resolve this image issue as everything else seems to work well - even if I have coded it like a dummy :-)

I am using RAD Studio 2009, Windows 7.


On our remote database server I have created a DBISAM 4 database that stores the following

"EMail Templates"
"EMail Recipient Groups"
"Email Recipients"

These tables are on the remote server because we have users in various offices and we always need to make sure they are using the right template and that these templates will be consistent.

Within the "EMail Templates" table we have the following fields
TEMPLATE_ID AUTOINC
TEMPLATE_NAME STRING
TEMPLATE_BODY BLOB

Our users use the TRichView editor (based on your Ribbon Demo) to create an Email Template. This template can include tables, lists and images in addition to the WYSIWYG edited text.

When the user completes editing a template they can save the template to the remote database "Email Templates" field. This is the code used to save the template.

procedure TfrmMain.DoSaveToTemplateDBExecute(Sender: TObject);
var
ms : TDBISAMBLOBStream;
begin
// make sure the remote DB is connected
if DBConnected then begin
try
// create the template save form and tidy up the input fields
with TfrmSaveTemplate.Create( nil ) do begin
ebName.Text := '';
ebDescription.Text := '';
ebCreatedBy.Text := '';
ebDateCreated.Text := DateToStr( Date );
btnLogin.Enabled := FALSE;
btnCancel.Enabled := TRUE;
// display the template save form modally and wait for response
ShowModal;
if ModalResult = mrOK then begin
// make sure the template does not exist
if not DT_TEMPLATES.FindKey( [ebName.Text] ) then begin
// add new record
DT_TEMPLATES.Append;
DT_TEMPLATES.FieldByName( 'NAME' ).AsString := ebName.Text;
DT_TEMPLATES.FieldByName( 'DESCRIPTION' ).AsString := ebDescription.Text;
DT_TEMPLATES.FieldByName( 'CREATED_BY' ).AsString := ebCreatedBy.Text;
DT_TEMPLATES.FieldByName( 'DATE_CREATED' ).AsDateTime := StrToDate( ebDateCreated.Text );
// create the stream - TDBISAMBLOBStream is fully compatible with streams - just easier to use with DBISAM
ms := TDBISAMBLOBStream.Create( TBlobField( DT_TEMPLATES.FieldByName( 'BODY' ) ), bmWrite );
// use SaveRVFToStream to save the template to the BLOB Field.
RichViewEdit1.SaveRVFToStream( ms, FALSE );
// free the stream - must do this before the Post
ms.Free;
// Post The Changes
DT_TEMPLATES.Post;
ShowMessage( 'Template Has Been Saved' );
end;
end;
end;
finally
frmSaveTemplate.Free;
end;
end;
end;

NOTE:
If the user has placed images in the editor they should be saved in the remote BLOB Field (written in the RVF to the BLOB field) but this does not seem to be happening so I have probebly missed something out of the above procedure. The images are instead written to the application folder which is not really what we need because these folders are going to get quite big.


When a user wants to send an email to a number of recipients the idea is to download an EMail Template from the remote server, make any changes and then just send the mail to the listed users...

Here is the loading code...

// load template from remote DB
procedure TfrmMain.actionLoadFromTemplateExecute(Sender: TObject);
var
fname : string;
begin
if DBConnected then begin
try
// create template load form (cbName combo box is filled with available template names)
with TfrmLoadTemplate.Create( nil ) do begin
btnLoad.Enabled := TRUE;
btnCancel.Enabled := TRUE;
ShowModal;
if ModalResult = mrOK then begin
if DT_TEMPLATES.FindKey( [cbName.Text] ) then begin
// we save to a file because saving to a stream and then opening the stream in TRichView didnt seem to work.
fname := format( '%s\%s.rvf', [FTemplateFolder,cbName.Text] );
TBlobField( DT_TEMPLATES.FieldByName( 'BODY' ) ).SaveToFile( fname );
RichViewEdit1.LoadRVF( fname );
RichViewedit1.Format;
// everything loaded fine but any images did not unless the image exists in the
// application folder - than the image displays OK
end;
end;
end;
finally
frmSaveTemplate.Free;
end;
end;
end;


Sending as EMail is as follows...

procedure TfrmMain.actionSendMailExecute(Sender: TObject);
var
MaxCount : Integer;
SendDelta : Integer;
SendCount : Integer;
PauseDelta : Integer;
begin

// No attachments allowed so we do not need to build attachments list
{
if not PrepareAttachments then begin
exit;
end;
}

// we do need the stringlist though to check later
Attachments := TStringList.Create;

{
btnSend.Enabled := False;
btnSend.Caption := 'Wait...';
}

DT_USERS.First;

SendCount := DT_USERS.RecordCount;
StatusBar1.Panels[1].Text := IntToStr( SendCount );
StatusBar1.Panels[3].Progress.Max := SendCount;
StatusBar1.Panels[3].Progress.Position := 0;
Application.ProcessMessages;

SendDelta := 0;
MaxCount := DT_MAILHOST.FieldByName( 'MAX_MAIL_COUNT' ).AsInteger;

DT_USERS.First;
while not DT_USERS.eof do begin

{ Basic Headers }
IdMessage1.Clear;
IdMessage1.From.Address := ebSenderMailAddress.Text;
IdMessage1.From.Name := ebMailFrom.Text;

{ check if the user has opted in }
if DT_USERS.FieldByName( 'USER_MAILOK' ).AsBoolean = TRUE then begin

with IdMessage1.Recipients.Add do begin
Address := DT_USERS.FieldByName( 'USER_EMAIL' ).AsString;
Name := DT_USERS.FieldByName( 'USER_NAME' ).AsString;
end;

IdMessage1.Subject := ebSubject.Text;

with IdMessage1.ReplyTo.Add do begin
Address := 'noreply@brixx.com';
Name := 'No Reply';
end;

HTMLImages := THTMLImagesCollection.Create;
try
BuildEmail;
finally
HTMLImages.Free;
end;

IdSMTP1.MailAgent := 'Brixx Bulk Mailer';
IdSMTP1.Host := ebMailHost.Text;
IdSMTP1.Port := StrToIntDef( ebMailPort.Text, 25);
IdSMTP1.Username := ebSenderMailAddress.Text;
IdSMTP1.Password := ebMailPassword.Text;

IdSMTP1.Connect;
try
IdSMTP1.Send(IdMessage1);
finally
IdSMTP1.Disconnect;
StatusBar1.Panels[3].Progress.Position := StatusBar1.Panels[3].Progress.Position + 1;
inc( SendDelta );
Application.ProcessMessages;
if SendDelta = MaxCount then begin
// insert delay here
end;

end;

end;

DT_USERS.Next;

end;
ShowMessage('Done');
end;


//
// Build the Email
// based on the demo code from TRichView forums
procedure TfrmMain.BuildEmail;
var
Stream : TMemoryStream;
ws : String;
s : TRVRawByteString;
i : Integer;
txtpart, htmpart, txthtmpart, txthtmlimgpart : TIdText;
imgpart : TIdAttachmentMemory;
begin

// saving text
TxtPart := TIdText.Create( IdMessage1.MessageParts );
Stream := TMemoryStream.Create;

RichViewEdit1.SaveTextToStreamW('', Stream, 80, False, False);
Stream.Position := 0;
SetLength(ws, Stream.Size div 2);
if Stream.Size <> 0 then begin
Stream.ReadBuffer(Pointer(ws)^, Stream.Size);
end;
Stream.Free;
txtpart.Body.Text := ws;
txtpart.ContentType := 'text/plain';
txtpart.ContentTransfer := 'quoted-printable';
txtpart.CharSet := 'utf-8';


// saving HTML
//
// QUESTION : Is there a way to insert the current recipient name in to the HTML message somewhere?
//
htmpart := TIdText.Create(IdMessage1.MessageParts);
Stream := TMemoryStream.Create;
RichViewEdit1.SaveHTMLToStreamEx(Stream, '', ebSubject.Text, '', '', '', '', [rvsoUseCheckpointsNames, rvsoUTF8]);
Stream.Position := 0;
SetLength(s, Stream.Size);
if Stream.Size<>0 then begin
Stream.ReadBuffer(Pointer(s)^, Stream.Size);
end;
Stream.Free;
htmpart.Body.Text := UTF8ToUnicodeString(s);
htmpart.ContentType := 'text/html';
htmpart.ContentTransfer := 'quoted-printable';
htmpart.CharSet := 'utf-8';

// combining text and HTML
IdMessage1.ContentType := 'multipart/alternative';
if (Attachments.Count=0) and (HTMLImages.Count=0) then begin
exit;
// should we actually exit here - not sure but we seem to be because no image is in the template.
end;

txthtmpart := TIdText.Create(IdMessage1.MessageParts);
txthtmpart.ContentType := 'multipart/alternative';
txthtmpart.Index := 0;
txtpart.ParentPart := txthtmpart.Index;
htmpart.ParentPart := txthtmpart.Index;

// images
for i := 0 to HTMLImages.Count-1 do begin
HTMLImages.Stream.Position := 0;
imgpart := TIdAttachmentMemory.Create(IdMessage1.MessageParts, HTMLImages.Stream);
imgpart.ContentType := HTMLImages.ContentType;
imgpart.ContentID := '<'+HTMLImages.Name+'>';
imgpart.ContentDisposition := 'inline';
end;


// combining images and text+html
IdMessage1.ContentType := 'multipart/related; type="multipart/alternative"';
if Attachments.Count=0 then begin
//exit;
end;

if HTMLImages.Count>0 then begin
txthtmlimgpart := TIdText.Create(IdMessage1.MessageParts);
txthtmlimgpart.ContentType := 'multipart/related; type="multipart/alternative"';
txthtmlimgpart.Index := 0;

txthtmpart.ParentPart := txthtmlimgpart.Index;
for i := IdMessage1.MessageParts.Count-1-HTMLImages.Count to IdMessage1.MessageParts.Count-1 do begin
IdMessage1.MessageParts.ParentPart := txthtmlimgpart.Index;
end;

txtpart.ParentPart := txthtmpart.Index;
htmpart.ParentPart := txthtmpart.Index;
end;

// files - none required so skip this...
{
IdMessage1.ContentType := 'multipart/mixed';
for i := 0 to Attachments.Count-1 do begin
TIdAttachmentFile.Create(IdMessage1.MessageParts, Attachments);
end;
}
end;

Posted: Thu Jun 28, 2012 12:38 pm
by Sergey Tkachenko
You can see this code uses HTMLImages collection.
This collection is filled in OnSaveImage2 event (TfrmMain.RichViewEdit1SaveImage2 in this demo).

Cannot Understand

Posted: Thu Jun 28, 2012 1:33 pm
by DavidCase
Hi Sergey,

Clearly I am far too dumb to use your product - Please accept my apologies but I do not understand your reply. Let me ask some simple questions...

Is there an example of saving editor content and images to an RVF stream so the images are not written to the local app folder but are saved with the RVF stream?

Is there an example of loading the editor from an RVF Stream containing content and images so the images are displayed as expected.

Is there an example of converting RVF to HTML for email purposes that includes both the content and images. You told me that the SaveImage2
event does this so is there an example of this I can look at please as the one in the demo code is empty.

I have tried to work my way through the demos but this has not worked.

I understand you getting annoyed at my questions but this is completely new to me and I have never had to do anything like this before so please understand I am just trying to get this right.

Regards and apologies
David.

Posted: Thu Jun 28, 2012 2:30 pm
by Sergey Tkachenko
My work to answering questions, so feel free to ask, and you do not need to apologize.

RVF
When saving document to RVF file/stream, images are stored inside this file/size (unless you exclude rvfoSavePicturesBody from RVFOptions).
The only necessary step is registering graphic classes that may be used in this document. There is no need to register TBitmap, TIcon, TMetafile, TJpegImage, but other classes must be registered:

Code: Select all

RegisterClasses([TGifImage, TPngImage]);
RegisterClasses must be called one time before the first loading.

HTML
HTML contains only references to images, so images must be stored elsewhere. By default, TRichViewEdit saves images in files.
MIME email can contain both HTML and its images. But a special code is required to save images inside email body instead of file.
As I can see, your code is based on http://www.trichview.com/support/files/mime.zip demo, subfolder Delphi\Indy.
This demo contains TfrmMain.RichViewEdit1SaveImage2 procedure.
This procedure must be copied to your form and assigned to OnSaveImage2 event of RichViewEdit1:

Code: Select all

procedure TfrmMain.RichViewEdit1SaveImage2(Sender: TCustomRichView;
  Graphic: TGraphic; SaveFormat: TRVSaveFormat; const Path,
  ImagePrefix: String; var ImageSaveNo: Integer; var Location: String;
  var DoDefault: Boolean);
var gr: TGraphic;
    bmp: TBitmap;
begin
  if SaveFormat<>rvsfHTML then
    exit;
  if not (Graphic is TJPEGImage) and not RV_IsHTMLGraphicFormat(Graphic) then begin
    bmp := TBitmap.Create;
    try
      bmp.Assign(Graphic);
    except
      bmp.Width := Graphic.Width;
      bmp.Height := Graphic.Height;
      bmp.Canvas.Draw(0,0, Graphic);
    end;
    gr := TJPEGImage.Create;
    gr.Assign(bmp);
    bmp.Free;
    end
  else
    gr := Graphic;

  Location := Format('image%d.%s', [ImageSaveNo, GraphicExtension(TGraphicClass(gr.ClassType))]);
  inc(ImageSaveNo);
  with HTMLImages.Add as THTMLImageItem do
  begin
    gr.SaveToStream(Stream);
    Name := Location;
    ContentType := GetImageContentType(gr);
  end;
  Location := 'cid:'+Location;
  DoDefault := False;
  if gr<>Graphic then
    gr.Free;
end;

Great - Nearly There (I think)

Posted: Thu Jun 28, 2012 4:08 pm
by DavidCase
I copied the code over to the SaveImage2 event and the image is now being saved as an attachment - is that the best I can hope for.

What I really wanted was to see the mail with the image in the correct place ( I place the image in a table column and add the descriptive text in the column to the right of the image.)

Posted: Fri Jun 29, 2012 7:46 am
by Sergey Tkachenko
Please send this email (where the image is displayed as an attachment) to me

Email Sent

Posted: Fri Jun 29, 2012 12:37 pm
by DavidCase
Hi Sergey - Mail sent as requested...

Posted: Sat Jun 30, 2012 2:38 pm
by Sergey Tkachenko
I received it... The main problem - this is not a PNG image. I do not know why it may happen, may be data are corrupted.
Please create a simple project reproducing this problem, I'll try to find what's wrong.

Project

Posted: Fri Jul 06, 2012 12:39 pm
by DavidCase
Hi Sergey,

I will try to create a simple project and send to you.

I have tried jpeg files and these fail as well...

Can I send you the code I use by email (it is not very long - about 90 lines)

Cheers
David.

Posted: Fri Jul 06, 2012 6:56 pm
by Sergey Tkachenko
If possible, please send a project that I can compile.
To richviewgmailcom.

Posted: Tue Jul 24, 2012 1:14 pm
by DavidCase
Hi Sergey,

Project emailed to you today...

Thanks
david.