Forum How do I...?

resolve image paths within an inline svg

whittaker007
Hi, I'm attempting to get a little tricky with dynamic content by manipulating svg images. We need to load bitmap images dynamically and transform them by adding borders and rotations through svg. The general idea is that we could load an svg file which contains a link to a bitmap image, and apply transformations and decorations to the image. By loading a different svg container we should be able to apply different themes to any given image.

To start with we created some simple test svg images which link to an external image using Adobe Illustrator or InDesign. The svg images and the bitmap image they linked to were copied to our test web server and I used PHP to feed Prince a constructed document which contained the svg images as content as img links:
...
<img src="img/container1.svg" />
<img src="img/container2.svg" />
...
This worked flawlessly and was very encouraging. The next step was to try and alter the svg images by replacing items in the svg text such as the url of the linked image, and border colours. To do this, we have to first load the svg file as text content, manipulate it, and write it out to the page as svg content rather than as a link to an external svg file:
...
$svg = file_get_contents( $svgURL );
$svg = str_replace( $oldImgRef, $newImgRef, $svg );
$svg = str_replace( $oldColour, $newColour, $svg );
print $svg;
...
Now the svg content renders, and the colours are changed as expected, but the external bitmap image is not loaded. By commenting out the str_replace commands so that no transformation is being done to the svg other than reading it in and printing it inline also fails to display the original linked image.

So I figured it must be a pathing problem, possibly because the images are in a subfolder from the page being rendered. I have tried relative urls, site absolute urls, machine absolute urls and setting Prince's baseURL to the location of the PHP contents:

$prince -> setBaseURL( 'http://localhost/test/' );

But none of these seem to work. Is it something to do with the way svg links to images using xlink:href? Here is an example of what the link looks like in the svg source:

<image overflow="visible" enable-background="new" width="443" height="293" xlink:href="test.png" transform="matrix(0.9409 0 0 0.9409 12.7158 9.6282)"></image>

and I am replacing "test.png" with "test2.png", "img/test2.png", "/test/img/test2.png" or "/Users/scott/Sites/test/img/test2.png"

Any ideas?

Thanks,

Scott
mikeday
Is this a public web page that I can take a look at to see what is going on? Is Prince producing any error messages in the output log about failing to load the specified image file?
whittaker007
No, this is just on my local machine at the moment. But I seem to have fixed it - under two out of three scenarios anyway. The three scenarios I have been testing are output to screen, output to file and direct (passthrough).

It turns out that the cause of the problem was the line:

$prince -> setHTML( true );

Setting it to false rendered the image in the svg when the PDF is output in passthrough mode. Maybe this is a similar issue that I discovered when rendering to the screen that the web browser needs to have the content type set to xhtml in the header, like so:

header("Content-Type: application/xhtml+xml; charset=utf-8");

otherwise the browser attempts to render it as HTML and ignores the SVG content. So that's two out of three. I still have a problem outputting directly to file using convert_string_to_file. My setup for all three scenarios is the same:
$prince = new Prince( $bin );
$prince -> setHTML( false );
$prince -> setLog( $log );

// render the print content to the output buffer and capture as string
ob_start();
include $in;
$out = ob_get_clean();

// now render it
switch ( $renderMode ) {
	case 'screen':
		print $out;
	break;

	case 'direct':
		header('Content-type: application/pdf');
		header("Content-Disposition: attachment; filename=out.pdf");
		$prince -> convert_string_to_passthru( $out );
	break;
	
	case 'file':
		$messages = Array();
		$prince -> convert_string_to_file( $in, $out, $messages );
		include 'header_web.php';
		var_dump( $messages );
		print '<![CDATA['.$out.']]>';
		include 'footer.php';
	break;
}


So when it uses 'file' output, the parsed document has been captured into the $out string variable, then fed into prince using convert_string_to_file and capturing any messages into an array. Finally it renders an HTML page which prints out the errors captured in $messages, including the $out document source wrapped in CDATA to prevent it from being rendered on the page.

The contents of $out look fine, and they do render correctly in both of the outher output modes. However the logfile is not created, and I get the following output on screen:
array
  0 => 
    array
      0 => string '-c: line 18: syntax error near unexpected token `('' (length=51)
  1 => 
    array
      0 => string '-c: line 18: `<image overflow="visible" enable-background="new    " width="443" height="293" xlink:href="img/zebrula.png"  transform="matrix(0.9987 0 0 0.9987 -0.4551 -0.468)">
'' (length=178)

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:svg="http://www.w3.org/2000/svg"
      xmlns:xlink="http://www.w3.org/1999/xlink"
		xml:lang="en-nz" lang="en-nz">
	<head>
		<title>Prince printable content</title>
		<link rel="stylesheet" type="text/css" media="print" href="css/print.css" />
	</head>

	<body>
		<div id="container"><h2>Dynamic svg modification</h2><h3>Your selections:</h3><p><strong>Frame colour:</strong> #FF0000</p><p><strong>Image URL:</strong> img/zebrula.png</p><p><strong>Template style:</strong> fullframe-pic</p>
<!-- Generator: Adobe Illustrator 13.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 14948)  -->

<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
	 width="444.09px" height="294.062px" viewBox="-0.5 -0.5 444.09 294.062" enable-background="new -0.5 -0.5 444.09 294.062"
	 xml:space="preserve">
<rect y="-0.472" fill="#FFFFFF" stroke="#FF0000" width="440.566" height="293.062"/>
<image overflow="visible" enable-background="new    " width="443" height="293" xlink:href="img/zebrula.png"  transform="matrix(0.9987 0 0 0.9987 -0.4551 -0.468)">
</image>
</svg>
		</div>
	</body>
</html>
mikeday
I don't quite understand this line:
$prince -> convert_string_to_file( $in, $out, $messages ); 

Perhaps $out should be passed as the first argument, which is the document string, and the second argument should be the filename of the PDF to create?
whittaker007
Oh dear, you're absolutely right - the code had been rearranged a couple of times and $out got reassigned from a filename to the processed document text. I've replaced the vars with some more appropriate names and it all works great now, thanks!