mPDF image to cover page with crop and no distortion - html

I've tried every conceivable combination of getting something that seems so simple to work.
I need to place an image on a page, A4 (210mm (h) X 297mm (w)) and have that image 100% height, i.e. 297mm, then stretch the width proportionally to that and just crop the overflow (i.e. hidden in css overflow). I've tried every combination I can think of with $mpdf->Image(), or like I've done everywhere else in the PDF file, use pure HTML and CSS. E.g.
<img src="path(to/file.jpg" />
or
<div style="background: url("path(to/file.jpg") center center no-repeat;"></div>
again, with all possible CSS configurations I can think of.
Is it not possible to stretch and image to fit the entire pages height while maintaining the aspect ratio and crop the image on the sides?
I've seen background-image-resize on MPDF full page background, but again, nothing.
How can I get an image to be 100% of the page's height (I don't care if I have to define the height (i.e. 297mm) or if it's a percentage) have the image scale proportionally to that and crop any excess of the image on the sides.
I'm sure I've just missed something obvious here. I can't see what I'm doing wrong (I think I'm stuck in a loop in my head now).
An example of what shows up fine (as expected) if I do an echo and view in the browser
$html = '<div style="
background: url('.$imageSource.') center center no-repeat;
position: absolute;
overflow: hidden;
height: 297mm;
width: 100%;
background-size: cover;
top: 0;
left: 0;
"></div>';
However, doing the same with $pdf->WriteHTML( $html, 2 ) and then $pdf->Output() the image is 297mm in height, but the width is distorted (i.e. it doesn't stretch proportionally to the height).
Here is the resulting PDF (with a placeholder image)
This is what I'm trying to achieve
So the resulting PDF gets "squeezed". Instead of extending the width proportionally to the height (297mm).
PS. Sorry for the lack of actual tested code. But there are just so many different combinations I've tried that I can't reproduce all of them.
PPS. Using latest version of mPDF. Grabbed from GitHub three days ago.

Since I had a lot of problems getting the logic, CSS and math right I thought I'd share my final working solution here with anyone else struggling with this.
Start with the function
function image( $containerWidth, $containerHeight, $imageSource, $top, $left, $border ){
// Get image width
$imageWidth = getimagesize( $imageSource )[0];
// Get image height
$imageHeight = getimagesize( $imageSource )[1];
// Get image aspect ratio
$imageRatio = $imageWidth / $imageHeight;
// Get container aspect ratio
$containerRatio = $containerWidth / $containerHeight;
// Decide if image should increase in height or width
if( $imageRatio > $containerRatio ){
$width = ( ( $imageRatio / $containerRatio ) * 100 );
}else{
$width = ( ( $containerRatio / $imageRatio ) * 100 );
}
if( $border ){
// $border array: 0 = thicknes in points, 1 = type (solid, dotted etc), 2 = color
$border = 'border: '.$border[0].'pt '.$border[1].' '.$border[2].';';
}
return '<div style="position: absolute; top: '.$top.'mm; left: '.$left.'mm; width: '.$containerWidth.'mm; height: '.$containerHeight.'mm; overflow:hidden; margin:0;'.$border.'"><img src="'.$imageSource.'" style="width: '.$width.'%; margin: 0 -'.( ( $width - 100 ) / 2 ).'%;" /></div>';
}
Then for a centered full page image
$image1 = image( 210, 297, $imageSource[0], 0, 0, null );
Or two images on top of each other, one with a white border
$image2 = image( 105, 148, $imageSource[1], 15, 15, null );
$image3 = image( 105, 148, $imageSource[2], 133, 90, [ 10, 'solid', 'white' ] );
Output
// mPDF options array
$pdfOptions = array(
'mode' => 'utf-8',
'format' => 'A4',
'img_dpi' => 300,
'dpi' => 300,
);
// Declare $pdf and set options
$pdf = new \Mpdf\Mpdf( $pdfOptions );
// Stylesheets
$style = file_get_contents( __DIR__ . '/path/to/stylesheet.css');
$pdf->WriteHTML( $style, 1 );
$pdf->WriteHTML( $image1, 2 );
$pdf->AddPage();
$pdf->WriteHTML( $image2, 2 );
$pdf->WriteHTML( $image3, 2 );
$pdf->Output();

Related

How to modify "downcute" CSS from rmdformats, to have author as in "readthedown"

My understanding of CSS is less than basic. I have a vague idea of what some elements do, and I manage to customize for example their colour or font size. But I really don't understand how to achieve the following.
I like very much the downcute CSS style from the rmdformats collection (here's an example).
But I prefer the position of author & date in the bottom-left corner, under the TOC, as in the readthedown CSS (here's an example).
I tried to achieve this by adding a custom CSS, copying in it some elements (for example, "#postamble") of readthedown.css that seem to achieve this. But it doesn't work – it's just beyond me.
Is what I'd like to do possible to achieve in a simple way, by adding a custom CSS on top of the downcute css, for example? I'd appreciate any suggestions, mechanical instructions, or explanations of how elements like "#postamble" or ".Content p.authors,
.Content p.date" work.
(The HTML page is created from these templates in R with knitr)
You asked about this quite a while ago, but I've only found your question today. I've got two options for you. In the first one, it just moves the author over to the bottom of the sidebar. In the second option, you get the contrasting background (along with the author's name moved).
You'll declare the author in the YAML of the downcute RMD, like you would normally. Then add this javascript chunk anywhere in your RMD. If you try to run a JS chunk inline, it won't do anything. You have to knit for it to do its thing. In this example, the author's name is literally downcute.
Option 1
```{r listenOrElse,results="asis",engine="js",echo=F}
setTimeout(function(){
au = document.querySelector("p.authors");
ah = au.clientHeight; // get height of author element
sb = document.querySelector("div.Sidebar");
sh = sb.clientHeight; // get height of sidebar
anh = sh - ah; // calculate the top position of moved author element
$(au).appendTo(sb); // move the author
$(au).css({top: anh + 'px', left: '5px', position: 'absolute'}); // set placement
}, 400); // in case of delayed build
```
Option 2
```{r OrElse,results="asis",engine="js",echo=F}
// alternative with contrast background
setTimeout(function(){
au = document.querySelector("p.authors");
ah = au.clientHeight; // get height of author element
sb = document.querySelector("div.Sidebar");
sh = sb.clientHeight; // get height of sidebar
dnh = sh - (ah*2); // calculate the top position of moved author element
dv = document.createElement("div");
$(dv).appendTo(sb);
$(dv).css({top: dnh + 'px', left: 0, width: '100%',
height: ah * 2 + 'px',
position: 'absolute', display: 'block',
background: '#7B7B7B'});
$(au).appendTo(dv); // move the author
$(au).css({top: ah * .5 + 'px', left: '5px', position: 'absolute',
color: '#F7F7F7', fontWeight: 'bold'}); // set placement
}, 400); // in case of delayed build
```

Add border to each page when printing without any border breaks

I have a project, which I will need to print a multi page exam.
I want to have border on each page, but at the bottom and at the top, when page breaks, the border will break too!
SCREENSHOT OF AN EXAMPLE
I am using #media print to style the print page
Code Structure of the page is:
header
main-content{
--- div for each question
}
each question has a border bottom, and the main-content has a full border
So any Idea on who can I achieve this goal?
Note: I am aware of the break-after or break-before properties, and they're no use in this case
Not sure why you would want that space below a page on a print. When you print a page, it is going to look really bad if you have a grey border at the bottom of each printed page. It would make sense to put the border on screen and when you print remove the borders.
I Would say you can just make the bottom border thicker.
#media print{
.main-content{
border-bottom 5px solid grey;
}
}
Either that, or make the background color grey like on the picture and add margin below the main-content div to space the pages from each other.
Keep in mind that Background color and border color does not always show up on print. It depends on the browser and the printer settings.
Found a Solution:
I used javascript to add questions into custom pages.
then I printed those pages.
$(document).ready(function () {
var pageIndex = 1;
var $questions = $(".question");
var $header = $("#mainHeader");
var $printPage = $("#toPrint");
var page = "<div class='page'>";
var pageHeight = 0;
const firstPageHeight = 1350; //Pixels
const otherPagesHeight = 1800; //Pixels
function resetPage() {
page+= "</div>";
$printPage.append(page);
page = "<div class='page'>";
pageIndex++;//Go to the next Page
pageHeight = 0; //Reset the page
}
function addQuestion(that) {
page += "<div class='question'>" + $(that).html() + "</div>";
}
$printPage.append("<div id='printHeader'>" + $header.html() + "</div>" ); //Adding the header
$.each($questions, function () {
//First page with header
var currentPageHeight = pageIndex == 1 ? firstPageHeight : otherPagesHeight;
if ($(this).height() + pageHeight < currentPageHeight){
addQuestion(this);
pageHeight += $(this).height();
}else{
//This page does not have the space for this question, so we move to the next page
//But first we need to close the previous page
resetPage();
pageHeight += $(this).height();
addQuestion(this);
}
//The page is full, we finish it
if (pageHeight >= currentPageHeight)
resetPage();
});
$(".q-space").show();
});
Hope this helps someone...

HTML table scale to fit

I have a KPI dashboard with a lot of small charts. One type of chart is in fact a HTML table. It is displayed in a DIV.
<div style="width:400px; height:250px;overflow:hidden">
<table>
<tr><th>Col1</th><th>Col2</th></tr>
<tr><td>Row1</td><td>Row2</td></tr>
</table>
<div>
Currently, I hide the overflow. I would like to make the table 'fit' the div.
How can I make this table to fit/scale down to the DIV if it would become too big to diplay? Ideally, the text would also shrink.
This CSS will make your table have the same height/width as the container you are using. Borders/background are only added for visualising what happens.
Shrinking the text will however be far more challenging. There is probably no way without using javascript to achieve that. And even if you did, content might end up being unreadable because of a too small font-size.
I managed to come up with some javascript/jquery code to change the font-size until the table fits the div or the font-size reaches 5px (= unreadable). Of coarse you will need to edit some of it yourself (because it would apply on all tables if you don't change the selectors to id's)
[JSFiddle]
table{
width: 100%;
background-color: red;
}
th, td{
width: 50%;
border: blue solid 1px;
}
Jquery / Javascript
$(document).ready(function () {
var HeightDiv = $("div").height();
var HeightTable = $("table").height();
if (HeightTable > HeightDiv) {
var FontSizeTable = parseInt($("table").css("font-size"), 10);
while (HeightTable > HeightDiv && FontSizeTable > 5) {
FontSizeTable--;
$("table").css("font-size", FontSizeTable);
HeightTable = $("table").height();
}
}
});
Here is what I use currently, it is embedded in a project (see for example the classes), but feel free to use it as inspiration.
scaleTable = function (markupId) {
//This hacky stuff is used because the table is invisible in IE.
function realWidth(obj){
var clone = obj.clone();
clone.css("visibility","hidden");
$('body').append(clone);
var width = clone.outerWidth();
clone.remove();
return width;
}
function realHeight(obj){
var clone = obj.clone();
clone.css("visibility","hidden");
$('body').append(clone);
var height = clone.outerHeight();
clone.remove();
return height;
}
var table = $("#"+markupId+" table:first-of-type");
var tablecontainer = $("#"+markupId).parents( ".scalabletablecontainer" );
var scalex = tablecontainer.innerWidth() / realWidth(table);
var scaley = tablecontainer.innerHeight() / realHeight(table);
var scale = Math.min(scalex, scaley);
if (scale<1.0) {
var fontsize = 12.0 * scale;
var padding = 5.0 * scale;
$("#"+markupId+" table tbody").css("font-size", fontsize + "px");
$("#"+markupId+" table tbody TD").css("padding",padding + "px");
$("#"+markupId+" table TH").css("padding",padding + "px");
}
};
Get table and div dimensions as shown in the previous comments. Then apply css
transfrom:scale(factorX, factorY)
to the table.

How can I use IMagick to resize text based on character count and image size

I'm currently trying to create a meme website site, a problem I'm having is I can't get the code to work which should shrink the text when there's a large amount of characters.
Can anyone point me in the direction or does anyone know why my text is not getting smaller when then wordcount increase.
As always thankyou for your help- much appreciated.
<?php
$color = new ImagickPixel('#ffffff');
$strokeSize = "2 : 2";
$text = "This is a load of text that should change in size when long.";
$fontsize ="55";
$draw = new ImagickDraw();
$outputImage = new Imagick('_img/user_memes/large/user_1/chicken.jpg');
// Get the width and height of the image
$imgSize = $outputImage ->getImageGeometry();
$imgWidth = $imgSize['width'];
$imgHeight = $imgSize['height'];
$draw->setFillColor('#fff');
$draw->setFont( "fonts/impact.ttf" );
$draw->setGravity(Imagick::GRAVITY_SOUTH);
$draw->setStrokeColor('#000');
$draw->setStrokeWidth(1);
$draw->setStrokeAntialias(true);
$draw->setTextAntialias(true);
// Set the desired width of the text to 90% of the image width
$textDesiredWidth = intval( $imgWidth * 0.9 );
$textDesiredHeight = intval( $imgHeight * 0.9 );
var_dump($outputImage->queryFontMetrics($draw, ''.$text.''));
// Create an array for the textwidth and textheight
$textProperties = array( 'textWidth' => 1 );
// Increase the fontsize until we have reached our desired width
while ( $textProperties['textWidth'] <= $textDesiredWidth ) {
$draw->setFontSize( $fontsize );
$textProperties = $outputImage->queryFontMetrics( $draw, $text );
$fontSize++;
}
echo "---" . $textProperties['textWidth'];
$outputImage->annotateImage($draw, 0, 5, 0, ''.$text.'');
$outputImage->setFormat('png');
$outputImage->writeimage('_img/user_memes/large/user_1/spork_thumbnail.png');
?>
<br><br>
<img src="_img/user_memes/large/user_1/spork_thumbnail.png" alt="some_text">

Set the content of PDF to center

I am generating dynamic multicell with string. Like A, B, C etc. I want to center those multicell on the page. I need to make the $pdf->SetMargins() dynamic, which means it will automatically center itself when the content is loaded. This is a code I tried but something is wrong.
$pdfWidth = 210;
col = 9; // This is dynamic so it can be any value from 5-20;
$mar = (($pdfWidth + ($col * 8)) /2)/2;
$pdf->SetMargins($mar,0,0);
I'm not sure why you're dividing by 2 twice. If you take the total width of the page, minus the content and divide that by two you have your desired margin already. Also, don't forget to set the override parameter in SetMargins() to 'true'.
$pdfWidth = 210;
$col = 9;
$mar = (($pdfWidth - ($col*8)) /2); //Only one division by 2 is required
$pdf->SetMargins($mar,0,0, true); //the 'true' is necessary or it won't override the default margins
//VERY IMPORTANT that you set all the above BEFORE adding the page!
$pdf->AddPage();
//Content of page
Now any MultiCell with cell width of 8, as you provided in the example, should be perfectly centered on the page.