html-pdf npm library gives different output on windows & ubuntu - node.js

I'm using https://www.npmjs.com/package/html-pdf library which is based on Phantom JS which internally uses webkit. I'm pasting the dummy HTML & JS code(keep these files in 1 folder) and also attaching the output screenshot.
The issue I'm facing is that on windows the PDF is generated with some extra space at top(empty space above red) which I can't get rid of.
Here is a forum(outdated) discussing similar issues, https://groups.google.com/forum/#!topic/phantomjs/YQIyxLWhmr0 .
input.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<div id="pageHeader" style="border-style: solid;border-width: 2px;color:red;">
header <br/> header <br/> header <br/> header
</div>
<div id="pageContent" style="border-style: solid;border-width: 2px;color:green;">
<div>
body <br/> body <br/> body
</div>
</div>
JS
(You require path, fs, handlebars, html-pdf npm packages)
var path = require('path');
var fs = require('fs');
var handlebars = require('handlebars');
var pdf = require('html-pdf');
saveHtml();
function saveHtml() {
fs.readFile('input.html', 'utf-8', {
flag: 'w'
}, function(error, source) {
handlebars.registerHelper('custom_title', function(title) {
return title;
})
var template = handlebars.compile(source);
var data = {};
var html = template(data);
var options = {
'format': 'A4',
'base': "file://",
/* You can give more options like height, width, border */
};
pdf.create(html, options).toFile('./output.pdf', function(err, res) {
if (err) {
console.log('err pdf');
return;
} else {
console.log('no err pdf');
return;
}
});
});
}
Output on ubuntu
Output on windows
Extra space at top(empty space above red) in Windows is the issue.
THINGS that didn't work
1. Adding
"border": {
"top": "0px" // or mm, cm, in
}
to options in JS file
https://www.npmjs.com/package/html-pdf#options
Giving fixed "height": "10.5in" & "width": "8in" in options in JS file
Making margin-top & padding-top to 0px/-50px to pageHeader div.
Overriding margin-top & padding of body to 0px/-20px in #media print in bootstrap.css
Giving fixed height to header
Any help will be greatly appreciated. Thanks.

You can manually set the CSS property to html tag. In my case I was having problems developing the template in Windows and deploying it to Linux (Heroku).
I put zoom: 0.7 in the label html and now both views look the same.
html{zoom: 0.7;}

I was able to get more consistent results by removing the ID's so that it treated everything as content rather than separate header and content areas.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<div style="border-style: solid;border-width: 2px;color:red;">
header
</div>
<div style="border-style: solid;border-width: 2px;color:green;">
<div>
body
</div>
</div>
</body>
</html>
If you need an ID for styling, use something other than pageHeader / pageFooter to avoid the special treatment associated with those IDs.

Have you tried using a normalize style sheet to compensate for cross platform differences?
https://necolas.github.io/normalize.css/

Related

WebdriverIO | how to show html content of wdio-timeline-reporter or wdio-html-reporter generated file with css in the email body sent by nodemailer

I am trying to send the html content of the file (report.html in the pic below) and this file opens up in browser perfectly as the reference of css works fine when opening it from the folder. But when I am trying to send this file as html body using nodemailer it just loses all its styling and in email I am just receiving a plain text email just like below.
Can someone tell me how to tell nodemailer that all the css are present of this file is present in the html-reports folder and it should appear in the email body same as it opens up in the browser.
If anyone using WDIO and sharing any such information over email then please share this info with me.
I am using latest version of the WDIO btw viz. V7.7 (cucumber framework)
Basically I want to achieve below use case -
After running the test html report comes of all the cucumber scenario pass/fail/skipped along with error trace
I want to share this info in email body and not as attachment and my email body should be properly styled
Thanks much in advance !
To achieve the results that you mentioned you have to use internal CSS, this is placed in the head section of your report.html, within a style element.
So where you have the link to your external CSS you want to replace that with the actual CSS that is in the report-styles.css and place it in a style element.
report-styles.css:
h1 {
color: blue;
}
p {
color: red;
}
report.html before:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="report-styles.css">
</head>
<body>
<div>Your report</div>
</body>
</html>
Replace the CSS link with the actual CSS in the file
report.html after:
<!DOCTYPE html>
<html>
<head>
<style>
h1 {
color: blue;
}
p {
color: red;
}
</style>
</head>
<body>
<div>Your report</div>
</body>
</html>
Then you can add the report.html to your email and all the styles will be there without having to reference that external file.
This will require some additional coding on your end after your report is completed and before you send your email.
You can use something like gulp to do the replacement for you or write your own code for the replacement.
gulp.js example:
var gulp = require('gulp');
var replace = require('gulp-replace');
var fs = require('fs');
function getCSSFilename(linkTag) {
var hrefValue = /href\=\"([A-Za-z0-9/._]*)\"/g;
var cssFilename = linkTag.match(hrefValue);
cssFilename = cssFilename[0].replace("href=\"", "").replace("\"", "");
return cssFilename;
}
gulp.task('inject-styles', function () {
return gulp.src("./report/report.html")
.pipe(replace(/<link rel="stylesheet" href="[^"]*"*>/g, function(linkTag) {
var style = fs.readFileSync(`.${getCSSFilename(linkTag)}`, 'utf8');
return '<style>\n' + style + '\t</style>';
}))
.pipe(gulp.dest('./report'));
});
Hope this is helpful enough to let you know how you achieve what you described.

Issue regarding converting of Arraybuffer of image to image and displaying it in html page

I have my js code as
app.get("/", function(req, res) {
var sql="SELECT image FROM images WHERE img_id=2";
connection.query(sql, function(err, result){
if(result.length <= 0)
message = "Profile not found!";
console.log(result);
res.render('image.ejs',{data:result});
});
});
and the image.ejs code is:
<html>
<head>
<title>Images</title>
</head>
<body>
<%= data %>
</body>
</html>
When I have runned it I just got the response like
[object Object]
but I need to my image to get displayed.will you guys help me out?
An ArrayBuffer/Buffer can't be a value for an img tag, what you can do is base64 encode the image, and display it
res.render('image.ejs',{ data: result[0].image.toString('base64') });
Now on your HTML
<html>
<head>
<title>Images</title>
</head>
<body>
<img src='data:image/png;base64,<%= data %>' />
</body>
</html>
If your images have different extensions, you can use: file-type package on the buffer and add the image type to the data passed to res.render
const imageBuffer = result[0].image;
const { mime } = await FileType.fromBuffer(imageBuffer)
res.render('image.ejs', { data: imageBuffer.toString('base64'), mime });
<img src='data:<%= mime %>;base64,<%= data %>' >

Simple SVG project cause error on Internet Explorer 11

I am learning svg and would like to compare displaying svg items on different browsers. My code works fine on firefox, chrome, edge, safari etc, but cannot work on ie11. Unfortunately application I develop needs to support ie11 so I need to force my code to work correctly.
Here is fiddle: https://jsbin.com/hemawaboqa/1/edit?html,js,output
HTML
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/#svgdotjs/svg.js"></script>
</head>
<body>
<div style="position:absolute;left:0px;top:0px;right:0px;bottom:0px;overflow:hidden;" id="svg-main-container">
<div style="position:absolute;left:0px;top:0px;bottom:0px;right:300px;border:1px solid #dadada;overflow:auto;" id="svg-canvas"></div>
</div>
</body>
</html>
JS
var draw = SVG().addTo('#svg-canvas').size(400, 400)
var rect = draw.rect(100, 100)
Why that code is not working on ie11?
I have created a sample using the SVG.js 3.0 version with your code, it will show the "Object doesn't support property or method 'from'" in IE11 browser, perhaps the issue is related to the svg.js version, and it is a plugin issue, you could feedback this issue to SVG.js forum.
Besides, I suggest you could refer to the following code, to use the old version of SVG.js:
<!DOCTYPE html>
<html lang=en-us>
<head>
<meta charset=utf-8>
<title>TEST</title>
</head>
<body>
<div style="position:absolute;left:0px;top:0px;right:0px;bottom:0px;overflow:hidden;" id="svg-main-container">
<div style="position:absolute;left:0px;top:0px;bottom:0px;right:300px;border:1px solid #dadada;overflow:auto;" id="drawing">
</div>
</div>
<script src=https://cdnjs.cloudflare.com/ajax/libs/svg.js/2.6.6/svg.min.js></script>
<script>
(function () {
'use strict';
// Add title as first child of SVG element:
var createTitle = function (svgObject, text) {
var fragment = document.createDocumentFragment();
var titleElement = document.createElement('TITLE');
fragment.appendChild(titleElement);
titleElement.innerHTML = text;
svgObject.node.insertAdjacentElement('afterbegin', titleElement);
};
SVG.extend(SVG.Doc, {
namespace: function () {
return this
.attr({xmlns: 'http://www.w3.org/2000/svg', version: '1.1'})
.attr('xmlns:xlink', SVG.xlink, SVG.xmlns);
}
});
var draw = new SVG('drawing').size(300, 300);
var rect = draw.rect(100, 100).attr({fill: '#f06'});
// Add title to SVG element
createTitle(draw, 'Rectangle');
}());
</script>
</body>
</html>
The result as below:
The library you are using has ECMA 6 elements that are not understood in IE.
If you need your project to work in IE, you will have to use another library or find out how to change it so it allows for older browsers (as suggested here: https://svgjs.dev/docs/3.0/compatibility/)

hbs rendered site with handlebars.js in the script - nodejs

I use hbs to render my pages with partials for navigation and footers.
router.get('/test', function (req, res) {
return res.render('test');
});
On one page I have template that uses mustache.js. This template doesn't work as it should as the {{}} seems to be picked up on the hbs render. Below is a basic example that illustrates the error. If I load this as a static page with express I get "Joe is a Web Developer", if I render it with hbs I get "is a".
Are there any work arounds that wont involve me changing how all my pages are rendered?
<!doctype html>
<html lang="en">
<head>
<title>Mustache.js Inline Method</title>
<script type="text/javascript" src="js/libs/mustache.js" ></script>
<script>
var view = {
name : "Joe",
occupation : "Web Developer"
};
function loadtemp(){
var output = Mustache.render("{{name}} is a {{occupation}}", view);
document.getElementById('person').innerHTML = output;
}
</script>
</head>
<body onload="loadtemp()" >
<p id="person"></p>
</body>
</html>
It was simple enough. I just had to escape the brackets with a \
So all it took was
\{{name}}

Google Translate widget - responsive

On my Web page I put translate widget when i resize browsers widged does not change size
I tried change css but i can change only css for Iframe
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<script src="http://code.jquery.com/ui/1.9.2/jquery-ui.js"></script>
<script type="text/javascript" src="script.js"></script>
<script type="text/javascript">
function googleTranslateElementInit() {
new google.translate.TranslateElement({
pageLanguage: 'en',
layout: google.translate.TranslateElement.InlineLayout.SIMPLE
}, 'google_translate_element');
}
</script>
<script type="text/javascript"
src="//translate.google.com/translate_a/element.js?cb=googleTranslateElementInit"></script>
</head>
<body>
<div id="google_translate_element"></div>
</body>
</html>
do you heve any solution?
Google Translate popup Layout - responsive fixed
<div id="google_translate_element" style="text-align: center;"></div>
<style>
.goog-te-banner-frame.skiptranslate {
display: none !important;
}
body {
top: 0px !important;
}
.goog-te-menu-frame {
max-width:100% !important;
}
.goog-te-menu2 {
max-width: 100% !important;
overflow-x: scroll !important;
box-sizing:border-box !important;
height:auto !important;
}
</style>
<script type="text/javascript">
function googleTranslateElementInit() {
new google.translate.TranslateElement({
pageLanguage: 'en',
autoDisplay: false,
layout: google.translate.TranslateElement.InlineLayout.SIMPLE
}, 'google_translate_element');
function changeGoogleStyles() {
if($('.goog-te-menu-frame').contents().find('.goog-te-menu2').length) {
$('.goog-te-menu-frame').contents().find('.goog-te-menu2').css({
'max-width':'100%',
'overflow-x':'auto',
'box-sizing':'border-box',
'height':'auto'
});
} else {
setTimeout(changeGoogleStyles, 50);
}
}
changeGoogleStyles();
}
</script>
<script type="text/javascript" src="//translate.google.com/translate_a/element.js?cb=googleTranslateElementInit"></script>
Not the solution to resizing issue but maybe helpful. You can change the default layout in the init function of the google translate selector.
Change in the line
layout: google.translate.TranslateElement.InlineLayout.SIMPLE to layout: google.translate.TranslateElement.InlineLayout.VERTICAL or layout: google.translate.TranslateElement.InlineLayout.HORIZONTAL.
These options will show the language choices in a vertical dropdown with also either the 'Made possible by Google Translate' label under or next to it.
You will not be able to adjust the layout of this widget using strictly CSS. The <a> elements containing links for all of the languages to choose from are laid out in <td> cells in rows. Therefore, they will not be laid out dynamically with resizing.
You can however, get around this by getting all the language links in the contained <iframe> and appending them to a <div> outside the <table>.
This should perform what you seek though may still require much CSS tweaking. Much of Google's UI elements are laid out manually with pixel dimensions and overridden attributes like overflow:hidden to avoid default (sometimes inconsistent) browser behavior. This solution may require a fair bit of [poking around the DOM][1] to determine where these adjustments are being done.
This should be executed in the top-most frame to access the <iframe> element and make changes to its CSS. Note that the selector is not a unique ID so it may return a different <iframe> than expected depending on the contents of your page.
var iframe = document.querySelector('.goog-te-menu-frame.skiptranslate');
if (iframe === null) {
console.error('Could not find iframe of language links');
} else {
// Force <iframe> visibility and auto-resizing
iframe.style.display = '';
iframe.style.height = '';
iframe.style.width = '99%!important';
This should be executed in the about:blank frame of the <iframe> to have access to the elements within.
// Get all the <a> elements
var anchors = document.querySelectorAll('a.goog-te-menu2-item');
anchors = Array.prototype.slice.call(language_anchors);
if (anchors.length < 1) {
console.error('Found no language links');
}
// Get the conatiner <div> that holds the table of links
var div = document.getElementById(':1.menuBody');
if (div === null) {
console.error('Could not find div containing table of language links');
} else {
// Remove width/height attributes to have <div> resize
div.style.height = '';
div.style.width = '';
// Iterate through all language links
anchors.forEach(function (a) {
// Set display to inline=block so its rendered like text
// This is what gets the elements onto a new line if they don't fit
a.style.display = 'inline-block';
// Append them directly to the <div>
div.appendChild(a);
});
// Remove the now empty <table> to keep things clean
div.removeChild(div.querySelector('table'));
}
This may break easily if Google changes their CSS class names or element IDs. Keep that in mind and happy rendering.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<script src="http://code.jquery.com/ui/1.9.2/jquery-ui.js"></script>
<script type="text/javascript" src="script.js"></script>
<script type="text/javascript">
function googleTranslateElementInit() {
new google.translate.TranslateElement({
pageLanguage: 'en',
layout: google.translate.TranslateElement.InlineLayout.HORIZONTAL
}, 'google_translate_element');
}
</script>
<script type="text/javascript"
src="//translate.google.com/translate_a/element.js?cb=googleTranslateElementInit"></script>
</head>
<body>
<div id="google_translate_element"></div>
</body>
</html>
YOU NEED TO CHANGE THE "SIMPLE" TO "HORIZONTAL"
You can put this in your css file for the theme that you're using. Tweak it to make it work for you. Hope that helps!
select.goog-te-combo{width:100%!important;}

Resources