Printing with only pdf.js (without the viewer) - pdf.js

There are already two questions about this, but the specific question remains unanswered.
I have a multipage pdf document. I have successfully used pdf.js core to write a page to a canvas. Soon I'm sure I'll have prev / next paging functions so I can see each page. See my existing code at bottom. The reason I am not using the viewer is because my boss doesn't like it. He wants a totally different interface that is extremely minimal and simple, and I have successfully built that minus the full print, ala:
Please note that there is no url to a pdf document. I get the data from a api call that returns base64 data. My code loads the data from an array into the pdf.
My current print function seems like a hack, and I know it isn't how the pdf.js viewer does it. I want to print the pdf as the pdf.js viewer does; meaning, true to form. I'm not sure if converting each page to an image gets it done, but I remain open to suggestions.
The problems are: extra margin not in the original document, and headers / footers. Not to mention pdf documents actually can contain different page layouts and sizes for each page and throwing an image tag into a new window completely ignores that.
I did check the pdf.js examples on Github. None of them are print and they say that using the pdf.js without the viewer is not supported and we are on our own if we do that (is that right?). Anyway, nothing on Google that I could find.
var pdfDocument;
var canvas;
// load from base64 data
loadPdfDocument(file.Content);
function print() {
var doc = document.getElementById('document');
var html = doc.innerHTML;
if (canvas)
html = "<img src='" + canvas.toDataURL() + "'";
var win = window.open('', '', '');
win.document.write(html);
win.document.close();
win.focus();
win.print();
win.close();
};
function loadPdfDocument(base64Data) {
PDFJS.disableWorker = true;
var pdfData = base64ToUint8Array(base64Data);
PDFJS.getDocument(pdfData).then(function(pdf) {
canvas = document.getElementById('pdfPage');
pdfDocument = pdf;
loadPdfPage(1);
});
}
function base64ToUint8Array(base64) {
var raw = atob(base64); // convert base 64 string to raw string
var uint8Array = new Uint8Array(raw.length);
for (var i = 0; i < raw.length; i++) {
uint8Array[i] = raw.charCodeAt(i);
}
return uint8Array;
}
function loadPdfPage(pageIndex) {
pdfDocument.getPage(pageIndex).then(function(page) {
var scale = 1;
var viewport = page.getViewport(scale);
var context = canvas.getContext('2d');
canvas.height = viewport.height;
canvas.width = viewport.width;
page.render({
canvasContext: context,
viewport: viewport
});
});
}
<div id="document">
<canvas id="pdfPage" />
</div>

Related

Convert HTML to PDF Using OffScreenCanvas in React

OK so the question is self-explanatory.
Currently, I am doing the following
Using html2canvas npm package to convert my html to canvas.
Converting Canvas to image
canvas.toDataURL("image/png");
Using jsPDF to create a pdf using this image after some manipulations.
So basically all of this process is CPU intensive and leads to unresponsive page.
I'm trying to offload the process to a web worker and I'm unable to postMessage either HTML Node or Canvas Element to my worker thread.
Hence, trying to use OffScreenCanvas but I'm stuck about how to go on with this.
The first step can not be done in a Web-Worker. You need access to the DOM to be able to draw it, and Workers don't have access to the DOM.
The second step could be done on an OffscreenCanvas, html2canvas does accept a { canvas } parameter that you can also set to an OffscreenCanvas.
But once you get a context from an OffscreenCanvas you can't transfer it anymore, so you won't be able to pass that OffscreenCanvas to the Worker and you won't win anything from it since everything will still be done on the UI thread.
So the best we can do is to let html2canvas initialize an HTMLCanvasElement, draw on it and then convert it to an image in a Blob. Blobs can traverse realms without any cost of copying and the toBlob() method can have its compression part be done asynchronously.
The third step can be done in a Worker since this PR.
I don't know react so you will have to rewrite it, but here is a bare JS implementation:
script.js
const dpr = window.devicePixelRatio;
const worker = new Worker( "worker.js" );
worker.onmessage = makeDownloadLink;
worker.onerror = console.error;
html2canvas( target ).then( canvas => {
canvas.toBlob( (blob) => worker.postMessage( {
source: blob,
width: canvas.width / dpr, // retina?
height: canvas.height / dpr // retina?
} ) );
} );
worker.js
importScripts( "https://unpkg.com/jspdf#latest/dist/jspdf.umd.min.js" );
onmessage = ({ data }) => {
const { source, width, height } = data;
const reader = new FileReaderSync();
const data_url = reader.readAsDataURL( source );
const doc = new jspdf.jsPDF( { unit: "px", format: [ width, height ], orientation: width > height ? "l" : "p" });
doc.addImage( data_url, "PNG", 0, 0, width, height, { compression: "NONE" } );
postMessage( doc.output( "blob" ) );
};
And since StackSnippet's over-protected iframes do break html2canvas, here is an outsourced live example.

Konva load stage with images and background video

Hey guys so I have this konva js app that has your typical background video just like the demo or close to it. I am able to play the video background and add image objects on top of it etc. Now I want a way to save the stage in its current state. So I do state.stage.toJSON() and as expected it creates a serialized JSON object. Now here's where I am hung up. when I load the stage like so state.stage = Konva.Node.create(data.stage, "container"); the stage data gets loaded (it is the correct size and so forth) yet there is no background video, no images or anything how do I fix this? I don't even know if stage.toJSON is correct but the point is I need to save it, leave the page and load it back up at a future date.
state.backgroundVideo = new Konva.Image({
image: state.video,
draggable: false
});
state.video.addEventListener("loadedmetadata", function(e) {
state.backgroundVideo.width(state.width);
state.backgroundVideo.height(state.height);
});
state.anim = new Konva.Animation(function() {
// do nothing, animation just need to update the layer
}, state.layer);
state.layer.add(state.backgroundVideo);
state.layer.batchDraw();
const canvas = document.getElementsByTagName("canvas")[0];
state.canvas = canvas;
node.toJSON() doesnt serialize image or video objects from Konva.Image attributes.
To solve the issue you can save video or image src into custom attribute:
image.setAttr('source', imageURL);
A simple string will be serialized into JSON. Then after you created a node from the JSON Konva.Node.create(data.stage, "container");
You need to find such nodes and restore image or video manually
// "video-background" is a sample here
// you can define your own selector
const videoNode = stage.findOne('.video-background');
const source = videoNode.getAttr('source');
const video = document.createElement('video');
video.src = source;
videoNode.image(video);
For more information take a look here:
https://konvajs.org/docs/data_and_serialization/Complex_Load.html
https://konvajs.org/docs/data_and_serialization/Best_Practices.html
thanks #lavrton your comments really helped me figure it out. Rather than loading the stage directly however I just saved the data from the stage used the data to rebuild the stage in the exact same way. Heres the code I used to get the data to save.
Heres my code:
const groups = state.stage.find("Group");
const stageData = [];
for (let x = 0; x < groups.length; x++) {
let g = groups[x];
let i = g.findOne("Image").getAttrs();
let group = { x: g.getX(), y: g.getY() };
let image = { x: i.width, y: i.height };
let obj = { image, group, jsonId: g.getAttr("jsonId") };
stageData.push(obj);
}
console.log("stageData", stageData);

Programmatically scroll to section with PDFJS

I'd like to use PDFJS to load a PDF document, then allow programmatic scrolling to named sections within the document. I have the following:
<div id="pdfContainer" class="well well-sf-errors-review">
<canvas id="pdfCanvas"></canvas>
</div>
PDFJS.getDocument(url).then(
function(pdf) {
pdf.getPage(1).then(
function(page) {
var desiredWidth = document.getElementById('pdfContainer').clientWidth;
var viewport = page.getViewport(1);
var scale = desiredWidth / viewport.width;
var scaledViewport = page.getViewport(scale);
var canvas = document.getElementById('pdfCanvas');
var context = canvas.getContext('2d');
canvas.height = 600;
canvas.width = scaledViewport.width;
var renderContext = {
canvasContext: context,
viewport: scaledViewport
};
page.render(renderContext);
}
);
}
);
What I get from this is the first page of the 22-page document, with no scrolling (scrolling to the next page). I'd like to have continuous scrolling with the ability to jump to a named section. Any pointers would be appreciated as I don't find the website or the code as informative as I need to fully understand PDFJS. Thanks!

Copying selection as text/html to clipboard

I am working on a Firefox-addon that is supposed to copy snippets of an HTML document to clipboard using two flavours: text/unicode and text/html.
The code looks as follows:
function copySelection() {
var textUnicode = window.getSelection().toString();
var textHtml = window.getSelection();
var trans = Transferable(window);
trans.addDataFlavor("text/unicode");
trans.setTransferData("text/unicode", SupportsString(textUnicode), textUnicode.length * 2);
trans.addDataFlavor("text/html");
trans.setTransferData("text/html", textHtml, textHtml.length * 2); // *2 because it's unicode
Services.clipboard.setData(trans, null, Services.clipboard.kGlobalClipboard);
return true;
}
The problem is that I cannot paste the copied text OOWriter (formatted) or anywhere else (plain text). At the same time I can see with xclip, the text is copied to the cliboard, but I cannot paste it anywhere. Am I doing something wrong?
You make the false assumption that getSelection() returns a string with the html representation of the current selection.
But the line var textHtml = window.getSelection(); simply assigns the Selection object to textHtml.
A little more work is required.
Enumerate the selected ranges (the user might have done a multiple selection), clone each range, append the contents to a div, then the innerHTML property of that div is what you're looking for.
Keep in mind that you also have to take care of attributes with relative urls (src, href) and turn them to absolute.

Adding markers from a SQL db to OSM

I'm new to OpenStreetMap and I've been browsing the wiki and the net and I can't seem to find a tutorial anywhere but I've seen examples on the net.
Basically I want to generate my own OpenStreetmap and plot markers taking the latitude and longitude from a MySQL database and plotting them on my map. When the user clicks on a mark I'd like to have a popup. Basically I want this http://code.google.com/apis/maps/articles/phpsqlajax.html but for OpenStreetMap and not Google-maps.
Looks like they are using openLayer for map rendering. Here are some examples and api docs.
http://openlayers.org/dev/examples/
http://trac.osgeo.org/openlayers/wiki/Documentation
To accomplish this, you need to figure out the javascript for presenting markers on a "slippy map" interface.
OpenLayers is the name of a popular javascript library. It's free and open source. It's used to display a slippy map on the OpenStreetMap.org homepage, and various other sites around the web. It's often confused with OpenStreetMap itself, or people mistakenly get the impression that you must use OpenLayers if you want to embed OpenStreetMap maps on your site. Not true. There's lots of alternative javascript libraries for displaying a slippy map
...including the google maps API! You can set up a google maps display, but show OpenStreetMap "tile" images instead of google tiles. See Google Maps Example. This has the advantage of code compatibility, meaning you could follow through google maps tutorial you've linked there, but then drop in a cheeky bit of code to specify OpenStreetMap as the tile layer.
This has the disadvantage of showing an big evil google logo, and requiring an even more evil google maps API key, so all the cool kids are using OpenLayers.
There's various examples of using OpenLayers on the OpenStreetMap wiki. The "OpenLayers Dynamic POI" example shows the use of database for markers (although that example is a bit convoluted). Another popups example on my site
Hope that helps
// Sample code by August Li
// Modified by Tom Moore to work with SQL
var zoom, center, currentPopup, map, lyrMarkers;
var popupClass = OpenLayers.Class(OpenLayers.Popup.FramedCloud, {
"autoSize": true,
"minSize": new OpenLayers.Size(300, 50),
"maxSize": new OpenLayers.Size(500, 300),
"keepInMap": true
});
var bounds = new OpenLayers.Bounds();
var lat=36.287179515680556;
var lon=-96.69170379638672;
var zoom=11;
var lonLat = new OpenLayers.LonLat(lon, lat).transform(
new OpenLayers.Projection("EPSG:4326"), new OpenLayers.Projection("EPSG:900913"));
// begin addMarker function
// info1 I was going to use to add a tooltip. Haven't figured out
// how to do that in OpenLayers yet :( Someone who knows share that with us
// iconurl is the url to the png file that you want to use for the icon.
// you MUST call addMarker at least once to initialize the map
function addMarker(lat, lng, info, info1, iconurl) {
// First check to see if the map has been initialized. If not, do that now ...
if (map == null) {
var options = {
projection: new OpenLayers.Projection("EPSG:900913"),
displayProjection: new OpenLayers.Projection("EPSG:4326"),
units: "m",
numZoomLevels: 19,
maxResolution: 156543.0339,
maxExtent: new OpenLayers.Bounds(-20037508.34, -20037508.34, 20037508.34, 20037508.34)
};
map = new OpenLayers.Map("map", options);
map.addControl(new OpenLayers.Control.PanZoomBar());
var lyrOsm = new OpenLayers.Layer.OSM();
map.addLayer(lyrOsm);
lyrMarkers = new OpenLayers.Layer.Markers("Markers");
map.addLayer(lyrMarkers);
//add marker on given coordinates
map.setCenter(lonLat, zoom);
zoom = map.getZoom();
}
var iconSize = new OpenLayers.Size(36, 36);
var iconOffset = new OpenLayers.Pixel(-(iconSize.w / 2), -iconSize.h);
var icon = new OpenLayers.Icon(iconurl, iconSize, iconOffset);
var pt = new OpenLayers.LonLat(lng, lat).transform(
new OpenLayers.Projection("EPSG:4326"), map.getProjectionObject());
bounds.extend(pt);
var feature = new OpenLayers.Feature(lyrMarkers, pt);
feature.closeBox = true;
feature.popupClass = popupClass;
feature.data.popupContentHTML = info;
feature.data.overflow = "auto";
var marker = new OpenLayers.Marker(pt, icon.clone());
var markerClick = function(evt) {
if (currentPopup != null && currentPopup.visible()) {
currentPopup.hide();
}
if (this.popup == null) {
this.popup = this.createPopup(this.closeBox);
map.addPopup(this.popup);
this.popup.show();
} else {
this.popup.toggle();
}
currentPopup = this.popup;
OpenLayers.Event.stop(evt);
};
marker.events.register("mousedown", feature, markerClick);
lyrMarkers.addMarker(marker);
}
// end addMarker function
Best regards! I hope this helps someone who needs this to work...

Resources