Node, phantomjs & converting html to pdf: element dimensions ignored? - html

I am wanting to generate a PDF from an HTML document, where the HTML document has elements specified with dimensions in cm. The idea is that an element or image is specified to fit in the page as specified. The issue is I can't get the dimensions to be respected.
This is the test HTML I am working with:
<html>
<head>
<title>test case</title>
</head>
<body>
<div style="width: 21cm; height: 27cm; border: solid 1px black;">This should be within page bounds!?</div>
</body>
</html>
Trying with the NodeJS module 'phantomjs-pdf' (0.1.2), I used the following code:
var pdf = require('phantomjs-pdf');
var options = {
"html" : "./test-data/document.html",
};
pdf.convert(options, function(result) {
/* Using a buffer and callback */
result.toBuffer(function(returnedBuffer) {});
/* Using a readable stream */
var stream = result.toStream();
/* Using the temp file path */
var tmpPath = result.getTmpPath();
/* Using the file writer and callback */
result.toFile("out.pdf", function() {});
});
The output document is 21cm x 29.71.cm (US Letter).
I have tried another package 'html-pdf', which also seems to to be PhantomJS based and get the same behaviour, suggesting the issue may be PhantomJS related?
Any suggestions?
I am open to using an alternative package for going from HTML to PDF, on the condition the element dimensions are respected. This is because I will be using an image as the background (PNG or SVG, depending on support), which represents an expected document template and then will be putting HTML elements with a given section of the page, based on an absolute position within an element.
I am using NodeJS 6.7.0, MacOS X 10.12 and I have PhantomJS 2.0.0 installed via MacPorts.

I gave up on PhantomJS after discovering https://github.com/fraserxu/electron-pdf. So far it has exceeded expecations. Because Electron runs Chromium the printToPDF functionality works pretty much as you'd expect. You can npm install electron-pdf and use the CLI to evaluate it for your app. Since you want Letter size add -p Letter and maybe set the margins to none.
In either case, I found that the (browser) window size in pixels needed to match the PDF size. For Letter this would be (8.5" * 96) by (11" * 96) 96 is the standard pixel per inch ratio for HTML. If you want to work in cm use that ratio.

Related

TinyMCE - automatically insert additional attributes when copying/pasting content?

Is there a way to configure TinyMCE to automatically insert additional attribues when copying and pasting content into a textarea?
In my case, I have a textarea that I copy/paste content with text and images. When images are inserted, I would like to automatically mark the img tags as having a specific CSS class (for ensuring they are fluid).
I'm using Django TinyMCE is that makes any difference. Has anyone succeeded in achieving this sort of behavior?
The TinyMCE Paste plugin has the ability for you to pre or post process the content during the paste process.
I would recommend using the postprocess API as this allows the Paste plugin to do its cleanup first.
https://www.tinymce.com/docs/plugins/paste/#paste_postprocess
For example you could do something like this in your TinyMCE init (not that this is what you want to do I just had this example handy from a project):
paste_postprocess: function(editor, fragment) {
var allElements = fragment.node.getElementsByTagName("td");
for (i = 0; i < allElements.length; ++i) {
console.log('initial font family: ', allElements[i].style.fontFamily);
var st = allElements[i].style;
stCleaned = st.fontFamily.replace("sans-serif", "").replace("Calibri", "Arial");
st.fontFamily = stCleaned; // Indirectly
}
}
...then each time the Paste plugin gets run your code will get run after it and you can manipulate the pasted content as you see fit.

How to implement a rich text editor using Polymer?

I tried to integrate TinyMCE with Polymer but it doesn't work due to the Shadow DOM.
Also, I tried to follow this instructions from the bellow link, but still doesn't work either.
How do I wrap tinyMCE inside a polymer element?
You can use polymer-quill which is based on popular quilljs WYSIWYG editor.
example:
<style is="custom-style">
polymer-quill.full {
--polymer-quill-editor-max-height: 300px;
--polymer-quill-editor-min-height: 100px;
}
</style>
<h2>Full Toolbar, Show Results, Max Height (300px), Min Height (100px), Save as Deltas, Save every 1 second</h2>
<polymer-quill content='{"ops":[{"insert":"Hello World! - Store as Delta"},{"attributes":{"header":2},"insert":"\n"}]}'
class='full'
store-as="delta"
save-interval="1000"
toolbar-type="full"
show-results>
</polymer-quill>
<h2>Standard Toolbar, Hide Results, Default height (100px), Save as HTML, Save every 2 seconds</h2>
<polymer-quill content="<h2>Hello World! - Store as HTML</h2>" store-as="html"></polymer-quill>
go to polymer-quill github for more info

SVG Stack not working in Chrome (webkit)

I have been exploring using SVG's for the latest website I've been building - bit behind the times so trying to catch up. I initially setup my file similar to the way I would do a normal sprite. Although this worked, it does seem a little clumsy when you want to take advantage of resizing the vector and then trying to find the new background position in the document!
After doing some research I came across the idea of stacking it via layers - which makes a heap of sense. After getting all excited and successfully doing this I then came across a few posts saying this isn't support in all browsers - typical.
https://code.google.com/p/chromium/issues/detail?id=128055#c6
Here is a great tutorial for stacking SVG images in a single file as well as some work arounds for browsers that don't support it: http://hofmannsven.com/2013/laboratory/svg-stacking/
Although this works fine, is there an alternative to save writing all this extra code and fallbacks?
After thinking about this a little I decided I could take advantage of the Apache server and see if I could simply inject what I needed into the document. The end result? Works perfect in all browsers :)
To start with I added some code in my .htaccess file to capture all .svg requests
RewriteRule ^(.*)\.svg$ /{path-to-file}/svg.php [L]
Then I wrote a few lines to deal with the target layer and inject that into the file
(UPDATE) Added new variable called target-fill to allow for dynamically changing the fill colour of a shape if required
<?php
// Set the SVG header
header('Content-Type: image/svg+xml');
$queryString = Array();
if(isset($_SERVER['QUERY_STRING'])) $queryString = explode('&', $_SERVER['QUERY_STRING']);
// Get target from the query string
$target = $queryString[0];
// Get a fill alternative if available and valid
if(isset($queryString[1]) && hexdec($queryString[1]) !== false) {
$targetFill = '#' . $queryString[1];
} else {
$targetFill = '';
}
// Validate the target - this is your ID in the SVG file
$validTargets = Array('Camera', 'Layer_1');
if(!in_array($target, $validTargets)) $target = false;
// Get contents of the file - tweak this depending on where you have saved this file to relative to the root of your website
$filename = '../..' . $_SERVER['REDIRECT_URL'];
// Get the contents of the file
$contents = file_get_contents($filename);
// Replace the target with the valid target above
// - doing it this way rather than echoing the target in the SVG file as it seemed like a security risk
if($target) $contents = str_replace('g:target', 'g#' . $target, $contents);
// Replace the fill colour if available
$contents = str_replace('target-fill', $targetFill, $contents);
// Output the amended SVG file
echo $contents;
Included near the top of the SVG is the stacking code to hide we don't want displayed and to turn on what we do
<defs>
<style>
svg g { display: none }
svg g:target, svg g:target g { display: inline }
svg g:target * { fill: target-fill; }
</style>
</defs>
And that is it. So now instead of calling your SVG file like (also works as a background image):
<img src="images/svg-file.svg#Camera">
You would do it like this
<img src="images/svg-file.svg?Camera">
The advantage of doing it this way is you can now also do some further checks based on the user agent to return an alternative file altogether if SVG isn't supported.
(UPDATE) You can now also express a second parameter to change the fill colour if required. Use it like this:
<img src="images/svg-file.svg?Camera&cc0000">
Hope this helps someone else out there.

cloneNode issue with HTML5 nodes in IE

I am trying to clone an HTML node using cloneNode() method of browser's DOM API and even using Jquery clone() function. The API works perfectly fine with HTML tags, However i am facing some issues while using it with HTML5 tags like time e.g.
The issue is that following <time> tag content <time class="storydate">April 7, 2010</time> gets converted to: <:time class=storydate awpUniq="912">April 7, 2010. Although IE renders the original time node correctly then why such issue with the clone API.
And this issue isn't observed in FF/ chrome. Please give some clue how to avoid this
Is this of any help? From the HTML5 Shiv issue list:
http://code.google.com/p/html5shiv/issues/detail?id=28
Links to
http://pastie.org/935834#49
solution seems to be:
// Issue: <HTML5_elements> become <:HTML5_elements> when element is cloneNode'd
// Solution: use an alternate cloneNode function, the default is broken and should not be used in IE anyway (for example: it should not clone events)
// Example of HTML5-safe element cloning
function html5_cloneNode(element) {
var div = html5_createElement('div'); // create a HTML5-safe element
div.innerHTML = element.outerHTML; // set HTML5-safe element's innerHTML as input element's outerHTML
return div.firstChild; // return HTML5-safe element's first child, which is an outerHTML clone of the input element
} // critique: function could be written more cross-browser friendly?

How does one print iframes?

I built a printable version of my data using multiple iframes to display line item details. However, if any of my iframe content is larger than a page they get cut off when printing (they display fine on the screen). All of my iframes point back to the same domain and I'm using the browser's File->Print function to do my printing. I don't think it's a browser specific bug as I get the same results in IE & FF.
What do I need to do to print an HTML document containing multiple iframes?
I don't believe there's an easy way of doing this. If they're all on the same domain, why are you using IFRAMEs? Why not put the data directly in the page? If you're looking for scrolling, a div with height: ###px; overflow: auto; would allow it without having to use IFRAMEs, and a CSS print stylesheet would allow you to take the overflow/height CSS off when the user hits print.
I found an answer here. While less than ideal, it'll allow me to print the master record first and then optionally print each line item as a seperate print job by printing each iframe individually.
There are different answers to this problem depending on the browser engine. To solve these issues in a simple cross-browser way I created https://github.com/noderaider/print.
I offer two npm packages:
print-utils - Generic JavaScript library
react-focus - React iframe component
Usage without React
Install via npm:
npm i -S print-utils
HTML:
<iframe id="print-content" src="/frame.html"></iframe>
JavaScript (ES2015)
import { usePrintFrame } from 'print-utils'
/** Start cross browser print frame */
const disposePrintFrame = usePrintFrame(document.getElementById('print-frame'))
/** Stop using print frame */
disposePrintFrame()
Usage with React
npm i -S react-focus
Usage:
import React, { Component } from 'react'
import reactFocus from 'react-focus'
/** Create Focus Component */
const Focus = reactFocus(React)
/**
* Use the component within your application just like an iframe
* it will automatically be printable across all major browsers (IE10+)
*/
export default class App extends Component {
render() {
return (
<div>
<div>
<h2>Welcome to react-focus</h2>
</div>
<div>
<Focus src={`?d=${Date.now()}`} />
</div>
</div>
)
}
}
Try the following (just a guess) at the bottom of your CSS:
#media print {
iframe {
overflow: visible;
}
}