Page 1 of 1

srve + embedded hyperlinks not rendering to PDF

Posted: Sun Feb 03, 2008 1:15 am
by toolwiz
From the general lack of replies to my questions about hyperlinks here, I get the feeling that nobody else is working with them.


If you have a document open in SRVE that contains embedded hyperlinks, it does not appear that it will create them when rendering to PDF (I'm using llionPDF). Since rendering to PDF is analogous to printing on a sheet of paper, embedded hyperlinks don't make much sense. Yet, it is common to have embedded hyperlinks inside of PDF files. And in particular, you can create PDF files from Word and OpenOffice that contain embedded hyperlinks.

Explicit hyperlinks work fine with SRVE, but that's only because llionPDF will create them automatically itself.

This is example of embedded hyperlink: click here to open google This is where the text says one thing, and the associated hyperlink is totally different.

In the MakePDF demo, btnSavePDFClick calls MakePageMetafile, which calls srve.DrawPage, which eventually calls srve.PaintPage, which there calls PlayEnhMetaFile() a few times.

And that, as we say, is all she wrote.

What I can't figure out is where individual items are being written to the metafile. The only text being written in PaintPage is the page number. Perhaps it's this line:

FRichViewEdit.RVData.PaintTo(MetafileCanvas, RVRect, bStripTop, bStripBottom, True, Printing);


In the MakePDF demo, if I open an RTF file that contains embedded links (created in Word), and save as PDF, the words show up as blue letters with underscores, but they're not hyperlinks. There's nothing behind them.

And actually, in the MakePDF demo, they're not "live" either.

If I search through srve for "url", I find this:

property OnURLNeeded : TRVURLNeededEvent read FURLNeeded write FURLNeeded;

which is more or less "inherited" from RV/RVE (to reflect it).

But there's no code within SRVE that looks at it other than an assignment to copy it from an RVE to an embedded RVE.


So ... the question at hand is: what's the best way to render embedded hyperlinks that exist in an RVE/SRVE to a PDF file?

Right off-hand, I'd think to modify DrawMarker above to call that FURLNeeded handler, passing item.ID as the second param.

But, if you get back a URL, what would it do? Again, in the context given, it does not know it's writing output for a PDF file. It's just rendering a metafile.

Any suggestions?

-David

Posted: Sun Feb 03, 2008 6:11 pm
by Sergey Tkachenko
In PDF, you can define some rectangular areas as hyperlinks (LLPDFLib: pdf.Page.SetUrl method). When exporting to PDF using TRVReportHelper, you can use TRVReportHelper.OnDrawHyperlink event.
Unfortunately, this feature is not implemented in ScaleRichView yet (will be implemented in one of next updates)

Posted: Sun Feb 03, 2008 7:09 pm
by toolwiz
Well, I need to figure out how to do it now.

Any suggestions?

I purchased licenses for SRV and llPDFlib specifically to make an app that will let me do this, among other things. It was never clear that it was not possible until after I got the source and started digging around in it.

Generating embedded links in PDF documents is handled perfectly fine by just about every PDF tool on the market.

It seems that what's needed is to identify items that have Tags associated with them that are strings that look like URLs. Then save off a list of objects that contain that Tag value, the position of the item on the page, and the page#. Then before the EndDoc command is given, run through that list and call the SetURL method for each one.

I just need to figure out where in the code it's iterating through those items. The place I found above seems to be iterating through LINES rather than ITEMS. But I can't figure out where the LINES are generated.

There are places where it does iterate through ITEMS, but they seem to be doing stuff related to graphics.

Or ... perhaps I can make a completely independent pass over all of the ITEMS and look for the Tags as above. The placement of items on the pages in the document don't change, right? But it would sure be a whole lot easier to just record a list of items with Tags at the point where they're rendered to the page.

I also want to do this for images -- the ability to attach URLs to images in the PDF file. So solving both of these together would be better than trying to solve them separately.

I'd appreciate any suggestions you might have. I have something that's finished except for this, and the client is getting upset with the delay.

Thanks
-David

Posted: Tue Feb 05, 2008 10:50 pm
by toolwiz
Well, I got this solved. I added a couple of classes to the DLines unit, modified the interfaces on a bunch of Draw/Paint methods, and made a few mods in CRVFData and SclRView. Now you can pass a TRVTaggedDLIItemsList instance at the end of the argument list to:

TSRichViewEdit.DrawPage ->
TSRichViewEdit.DrawPageEx ->
TSRichViewEdit.PaintPage ->
RVData.PaintTo

which adds each item with a tag onto the list.

The actual object that's added and the list are:

Code: Select all

  TRVTaggedItemLoc = class
  private
    FRect: TRect;
    FDrawLineInfo: TRVDrawLineInfo;
  public
    property DrawLineInfo : TRVDrawLineInfo read FDrawLineInfo write FDrawLineInfo;
    property Rect : TRect read FRect write FRect;
    procedure SetPos( r : TRect );
  end;

  TRVTaggedDLIItemsList = class( TObjectList ) // of TRVTaggedItemLoc's
  private
    function GetItem(i: integer): TRVTaggedItemLoc;
    procedure SetItem(i: integer; const Value: TRVTaggedItemLoc);
    function ContainsItem( itemno : integer ) : integer;
  public
    constructor Create;
    function Add( item : TRVTaggedItemLoc ): Integer;
    function AddNewDLI( dli : TRVDrawLineInfo ) : TRVTaggedItemLoc;
    property Items[ i : integer ] : TRVTaggedItemLoc read GetItem write SetItem; default;
  end;

------------------

procedure TRVTaggedItemLoc.SetPos( r : TRect );
begin
    FRect.Top := r.Top;
    FRect.Left := r.Left;
    FRect.Right := r.Right + r.Left;
    FRect.Bottom := r.Bottom + r.Top;
end;

-----------------------

This is added as a local var in the PaintTo function near line #3474 of CRVData.pas:

    last_DrawText_pos : TRect;

This is added at the bottom of DrawText around line #3553:

      last_DrawText_pos := Rect( X, Y, W, dli.Height );

This is added around line #3963 and following each call to DrawText:

      if ((StartNo>i) or (EndNo<i)) and
         not (rvstCompletelySelected in State) then begin {not selected}
        DrawText(dli.Left, dli.Top, dli.Width, 1, i,
          RV_ReturnProcessedString(DrawItems.GetString(i,Items),
            RVStyle.TextStyles[No], IsDrawItemLastOnWrappedLine(i),
            ShowSpecialCharacters, True),
          True, True, False,True);
        AddToTIL( dli );  // ** DS **
        end

-----------------------
Here's the AddToTIL proc:

  procedure AddToTIL( di : TRVDrawLineInfo );
  var ti : TRVTaggedItemLoc;
  begin
    if (til <> nil) then begin
        if (PChar(GetItemTag(di.ItemNo)) <> nil) then begin
            ti := til.AddNewDLI( di );      
            ti.SetPos( last_DrawText_pos );
        end;
    end;
  end;

-----------------------

Finally, the loop in the MakePDF demo that calls MakePageMetafile looks like this:

var  tagged_items : TRVTaggedDLIItemsList;
 . . .
    tagged_items := TRVTaggedDLIItemsList.Create;  // ***
    Screen.Cursor := crHourGlass;
    try
        Application.Hint := 'Formatting...';
        for i := 0 to SRichViewEdit1.PageCount-1 do begin
            if i>0 then begin
                pdf.NewPage;
                pdf.Page[i].Size := pdf.Page[0].Size;
                pdf.Page[i].Orientation := pdf.Page[0].Orientation;
            end;
            Metafile := MakePageMetafile(i+1,
                                    pdf.Page[i].Width, pdf.Page[i].Height,
                                    tagged_items );  // ***
            pdf.Canvas.Draw(0, 0, Metafile);
            // vvvvvvvvvvvvvvvvvv
            for j := 0 to tagged_items.Count-1 do begin
                til := tagged_items[j];
                str := PChar( SRichViewEdit1.RichViewEdit.GetItemTag( til.DrawLineInfo.ItemNo ) );
                if (URLKind(str) <> urlNone) then
                    pdf.Page[i].SetUrl( til.Rect, str );
            end;
            tagged_items.Clear;
            // ^^^^^^^^^^^^^^^^^^^
            Metafile.Free;
            Application.Hint := Format('Collecting PDF pages (%d of %d)... ', [i+1, SRichViewEdit1.PageCount]);
        end;
        Application.Hint := 'Writing PDF file...';
        pdf.EndDoc;
    finally
        tagged_items.Free;  // ***
        Screen.Cursor := crDefault;
        Application.Hint := '';
    end;

-David