Printing using ngx-extended-pdf-viewer on iOS and Mobile Safari or Chrome - ios

I have an Angular 7 app that is using ngx-extended-pdf-viewer to render a PDF that I get as a byte array from the web.api. I have no issues rendering the PDF or even printing it from any desktop application. ngx-extended-pdf-viewer as a print button built right in. However, when trying to print from Safari on an iPhone (iOS 12) it only prints a blank page with the url at the bottom. The actual PDF does not print. With Chrome on iOS it doesn't do anything that I can see. I am pretty new to Angular and actually to mobile web development, so perhaps lack of knowledge is getting me. The PDF viewer is in a mat-tab, so not sure if maybe that is causing some issues??
I have tried other packages, but they all seem to be based on the same pdf.js code from Mozilla. Also this is the only one I've found so far that has a print. I was thinking about perhaps trying pdf.js outside of an npm package, but so far have not found solid directions on getting this to work in Angular. I'm sure it will, but all directions I have found seem to omit details. Such as, put this code in your app. They just fail to say where in the app.
From the web.api:
[HttpPost("GetPdfBytes/{PdfId}")]
public ActionResult<byte[]> GetPdfBytesId([FromBody]int id)
{
string exactPath = string.Empty;
if (id == 1)
{
exactPath = Path.GetFullPath("pdf-test.pdf");
}
else if (id == 2)
{
exactPath = Path.GetFullPath("DPP.pdf");
}
else if (id == 3)
{
exactPath = Path.GetFullPath("Request.pdf");
}
else
{
exactPath = Path.GetFullPath("Emergency Issue.pdf");
}
byte[] bytes = System.IO.File.ReadAllBytes(exactPath);
return Ok(bytes);
}
The HTML:
<mat-tab label="PDF">
<ng-template matTabContent>
<ngx-extended-pdf-viewer *ngIf="visible[2]" id="pdf3" [src]="pdfSrc3" useBrowserLocale="true" delayFirstView="1000" showSidebarButton="false"
showOpenFileButton="false" >
</ngx-extended-pdf-viewer>
</ng-template>
</mat-tab>
TypeScript:
getPDFBytesId(id: string) {
this.getPDFFromServicePdfBytesId(Number(id)).subscribe(
(data: any) => {
this.pdfSrc3 = this.convertDataURIToBinary(data);
},
error => {
console.log(error);
}
);
}
// hits the web.api
getPDFFromServicePdfBytesId(id: number): Observable<any> {
const body = id;
return this.http.post<any>('http://localhost:5000/api/values/GetPdfBytes/' + id, body);
}
// converts what we got back to a Uint8Array which is used by the viewer
convertDataURIToBinary(dataURI: string) {
const raw = window.atob(dataURI);
const rawLength = raw.length;
const array = new Uint8Array(new ArrayBuffer(rawLength));
for (let i = 0; i < rawLength; i++) {
array[i] = raw.charCodeAt(i);
}
return array;
}

Related

Why is this coroutine is not serializing properly non iOS?

I thought it would be related to iOS/json and using if (string.IsNullOrEmpty(string))
but I switched that out and this coroutine is still not working. This works perfectly in the Unity Editor, but when build for iOS, this does not work. Could it be related to using null fields in the json itself? The null fields are important to the data structure of this project. For reference as to what the json does, each website is given an index from its placement in the hierarchy, and then the index number is matched with "Active Tab" object.
Example of json:
{
"tabList": [
"https://www.google.com/",
"https://www.usa.com/",
"https://www.nyc.com/",
""
],
"activeTab": 2
}
and the coroutine in question:
public IEnumerator LoadTabs()
{
jsonPath = Application.persistentDataPath + "/tabs.json";
if(File.Exists(jsonPath)){
string json = File.ReadAllText(jsonPath);
tabsObject = JsonUtility.FromJson<Tabs>(json);
//If tabs exit in JSON
if (tabsObject != null)
{
Debug.Log("Loading tabs from JSON File.");
// Populate new webviews for saved tabs
foreach(string tabURL in tabsObject.tabList){
if (string.IsNullOrEmpty(tabURL))
{
Debug.Log("null or empty");
browserControl.NewWebViewLoad(tabURL);
yield return new WaitForEndOfFrame();
}
else
{
Debug.Log("not null or empty");
browserControl.NewWebView(tabURL);
yield return new WaitForEndOfFrame();
}
}
//Wait until all tabs are populated
while (tabs[tabs.Count - 1].tabIndex == -1)
{
yield return null;
}
//Select active tab from tablist using JSON property.
// COULD CHANGE ACTIVE TAB FROM tabURL TO INDEX IN TABS
for (int i = 0; i < tabs.Count; i++){
if(tabs[i].tabIndex == tabsObject.activeTab )
{
SelectTab(tabs[i]);
break;
}
}
}
else {
// Create new tabs object if json exists but is empty.
tabsObject = new Tabs();
Debug.Log("No tabs saved opening a new webview.");
browserControl.NewWebViewLoad("");
}
} else {
//Create tab json if it doesnt exist.
File.WriteAllText(jsonPath,"");
tabsObject = new Tabs();
browserControl.NewWebViewLoad("");
Debug.Log("No tabs JSON File opening a new webview.");
}
allTabsLoaded = true;
tabCounter.text = tabs.Count.ToString();
}
Thank you!
After going through my code I took out all debugging lines that used the json data and I stopped getting the error. Look at your debugging if you are getting an error related to parsing json data with Unity.

SWT: Integrate clickable link into StyledText

With the help of this question I was able to figure out how I can display a link inside a StyledText widget in SwT. The color is correct and even the cursor changes shape when hovering over the link.
So far so good, but the link is not actually clickable. Although the cursor changes its shape, nothing happens if clicking on the link. Therefore I am asking how I can make clicking the link to actually open it in the browser.
I thought of using a MouseListener, tracking the click-location back to the respective text the click has been performed on and then deciding whether to open the link or not. However that seems way too complicated given that there already is some routine going on for changing the cursor accordingly. I believe that there is some easy way to do this (and assuring that the clicking-behavior is actually consistent to when the cursor changes its shape).
Does anyone have any suggestions?
Here's an MWE demonstrating what I have done so far:
public static void main(String[] args) throws MalformedURLException {
final URL testURL = new URL("https://stackoverflow.com/questions/1494337/can-html-style-links-be-added-to-swt-styledtext");
Display display = new Display();
Shell shell = new Shell(display);
shell.setLayout(new GridLayout(1, true));
StyledText sTextWidget = new StyledText(shell, SWT.READ_ONLY);
final String firstPart = "Some text before ";
String msg = firstPart + testURL.toString() + " some text after";
sTextWidget.setText(msg);
sTextWidget.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
StyleRange linkStyleRange = new StyleRange(firstPart.length(), testURL.toString().length(), null, null);
linkStyleRange.underline = true;
linkStyleRange.underlineStyle = SWT.UNDERLINE_LINK;
linkStyleRange.data = testURL.toString();
sTextWidget.setStyleRange(linkStyleRange);
shell.open();
while(!shell.isDisposed()) {
display.readAndDispatch();
}
}
Okay I was being a little too fast on posting this question... There's a snippet that deals with exactly this problem and it shows, that one indeed has to use an extra MouseListener in order to get things working.
The snippet can be found here and this is the relevant part setting up the listener:
styledText.addListener(SWT.MouseDown, event -> {
// It is up to the application to determine when and how a link should be activated.
// In this snippet links are activated on mouse down when the control key is held down
if ((event.stateMask & SWT.MOD1) != 0) {
int offset = styledText.getOffsetAtLocation(new Point (event.x, event.y));
if (offset != -1) {
StyleRange style1 = null;
try {
style1 = styledText.getStyleRangeAtOffset(offset);
} catch (IllegalArgumentException e) {
// no character under event.x, event.y
}
if (style1 != null && style1.underline && style1.underlineStyle == SWT.UNDERLINE_LINK) {
System.out.println("Click on a Link");
}
}
}
});

jsctypes finalizer cross domain issue

I've successfully used jsctypes in the past but the latest version of firefox (32) has started to give me an odd error message
here is what used to work-
javascript content sends a message to a javascript extension in chrome (the extension uses ctypes to call a special device allocator. it then returns a cdata.finalizer to the content
later when the content is garbage collected the finaizer gets called to release the special device allocation
while this used to work fine, I'm now getting an exception ctypes.CDataFinalizer
Not allowed to define cross-origin object as property on [Object] or [Array] XrayWrapper
searching on Google did not seem to find anything related.
on the extension I have this code( the func... things are access methods for c code)
Any suggestions?
self.addEventListener("allocArray", function (event) {
var info = event.detail.info;
try {
var cBytes = ctypes.int32_t(info.bytes);
var cArrayId = ctypes.uint32_t(0);
var err = funcAllocArray(cBytes, cArrayId.address());
if(err !== 0) {
info.rtnCode = err;
info.arrayId = -1;
info.error = "Error: " + (err === 2)? "out of memory": "allocation failed";
} else {
info.rtnCode = 0;
info.arrayId = ctypes.CDataFinalizer(cArrayId.value, funcReleaseArray);
}
}
catch(exception) {
info.rtnCode = -1;
info.arrayId = -1;
info.error = report(exception);
}
}, true, true);

window.opener not set in iOS Chrome

In one file, I have
go
In t2.html I have
<script>
document.write(window.opener);
</script>
On Safari on iOS, and on Chrome on the Mac and on pretty much every other browser, it prints out [object Window] like you'd expect.
On Chrome on iOS, I get null.
How do I get to the window that opened this window?
This code solves the problem you are talking about (specifically for issues with Chrome ios not liking "pop ups"), but in reference to Paypal Adaptive Payments where it opens a "pop up" and redirects to Paypal page for payment.
The key is that you have to:
Initiate the window.open directly from a button/link click
You must use the _blank as the window "name" (and not choose your own)
The main thing you want/need is:
var win;
//VERY IMPORTANT - You must use '_blank' and NOT name the window if you want it to work with chrome ios on iphone
//See this bug report from google explaining the issue: https://code.google.com/p/chromium/issues/detail?id=136610
win = window.open(paypalURL,'_blank');
//Initiate returnFromPayPal function if the pop up window is closed
if (win && win.closed) {
returnFromPayPal();
}
Here is the full code that you can follow (ignore anything that doesn't apply to what you are doing).
<div>
<?php $payUrl = 'https://www.paypal.com/webapps/adaptivepayment/flow/pay?expType=mini&paykey=' . $payKey ?>
<button onclick="loadPayPalPage('<?php echo $payUrl; ?>')" title="Pay online with PayPal">PayPal</button>
</div>
<script>
function loadPayPalPage(paypalURL)
{
var ua = navigator.userAgent;
var pollingInterval = 0;
var win;
// mobile device
if (ua.match(/iPhone|iPod|Android|Blackberry.*WebKit/i)) {
//VERY IMPORTANT - You must use '_blank' and NOT name the window if you want it to work with chrome ios on iphone
//See this bug report from google explaining the issue: https://code.google.com/p/chromium/issues/detail?id=136610
win = window.open(paypalURL,'_blank');
pollingInterval = setInterval(function() {
if (win && win.closed) {
clearInterval(pollingInterval);
returnFromPayPal();
}
} , 1000);
}
else
{
//Desktop device
var width = 400,
height = 550,
left,
top;
if (window.outerWidth) {
left = Math.round((window.outerWidth - width) / 2) + window.screenX;
top = Math.round((window.outerHeight - height) / 2) + window.screenY;
} else if (window.screen.width) {
left = Math.round((window.screen.width - width) / 2);
top = Math.round((window.screen.height - height) / 2);
}
//VERY IMPORTANT - You must use '_blank' and NOT name the window if you want it to work with chrome ios on iphone
//See this bug report from google explaining the issue: https://code.google.com/p/chromium/issues/detail?id=136610
win = window.open(paypalURL,'_blank','top=' + top + ', left=' + left + ', width=' + width + ', height=' + height + ', location=0, status=0, toolbar=0, menubar=0, resizable=0, scrollbars=1');
pollingInterval = setInterval(function() {
if (win && win.closed) {
clearInterval(pollingInterval);
returnFromPayPal();
}
} , 1000);
}
}
var returnFromPayPal = function()
{
location.replace("www.yourdomain.com/paypalStatusCheck.php");
// Here you would need to pass on the payKey to your server side handle (use session variable) to call the PaymentDetails API to make sure Payment has been successful
// based on the payment status- redirect to your success or cancel/failed page
}
</script>
This seems to be a bigger story. See Bugtracker here:
http://code.google.com/p/chromium/issues/detail?id=136610&q=window.opener&colspec=ID%20Pri%20Mstone%20ReleaseBlock%20OS%20Area%20Feature%20Status%20Owner%20Summary
But it seems, as if iframes could handle the parent-property, so maybe you could switch your app from using popups to using an overlay.
If you want to pass values from child to parent use the following code.
Add following code to parent page:
var hidden, state, visibilityChange;
if (typeof document.hidden !== "undefined") {
hidden = "hidden";
visibilityChange = "visibilitychange";
state = "visibilityState";
} else if (typeof document.mozHidden !== "undefined") {
hidden = "mozHidden";
visibilityChange = "mozvisibilitychange";
state = "mozVisibilityState";
} else if (typeof document.msHidden !== "undefined") {
hidden = "msHidden";
visibilityChange = "msvisibilitychange";
state = "msVisibilityState";
} else if (typeof document.webkitHidden !== "undefined") {
hidden = "webkitHidden";
visibilityChange = "webkitvisibilitychange";
state = "webkitVisibilityState";
}
// Add a listener that constantly changes the title
document.addEventListener(visibilityChange, function () {
if (localStorage.getItem("AccountName")) {
$("#txtGrower").val(localStorage.getItem("AccountName"));
}
if (localStorage.getItem("AccountID")) {
$("#hdnGrower").val(localStorage.getItem("AccountID"));
}
}, false);
Add following in child page (Any preferred event)
function CloseChildAndLoadValuesToParent() {
localStorage.setItem("AccountName", 'MyAccountName');
localStorage.setItem("AccountID", 'MyAccountID');
window.close();
}

PrettyPhoto annd youtube videos overlay

I have a webpage with prettyPhoto and a youtube video inside the web page.
With jquery I do:
$("#youtubevideo embed").attr("wmode", "opaque");
also tried $("#youtubevideo embed").attr("wmode", "transparent");
In firefox image is over the youtube video, but the corners of pretty photo are missing. Not really missing because if I scroll up ad down they are shown. But still they don't appear correctly.
In Chrome video is still on top of the images :( Is there a way to fix this? Thanks
after 2 days of searching the web for the answer i've found a pure JS function that fix it in all browsers!
there you go:
function fix_flash() {
// loop through every embed tag on the site
var embeds = document.getElementsByTagName('embed');
for (i = 0; i < embeds.length; i++) {
embed = embeds[i];
var new_embed;
// everything but Firefox & Konqueror
if (embed.outerHTML) {
var html = embed.outerHTML;
// replace an existing wmode parameter
if (html.match(/wmode\s*=\s*('|")[a-zA-Z]+('|")/i))
new_embed = html.replace(/wmode\s*=\s*('|")window('|")/i, "wmode='transparent'");
// add a new wmode parameter
else
new_embed = html.replace(/<embed\s/i, "<embed wmode='transparent' ");
// replace the old embed object with the fixed version
embed.insertAdjacentHTML('beforeBegin', new_embed);
embed.parentNode.removeChild(embed);
} else {
// cloneNode is buggy in some versions of Safari & Opera, but works fine in FF
new_embed = embed.cloneNode(true);
if (!new_embed.getAttribute('wmode') || new_embed.getAttribute('wmode').toLowerCase() == 'window')
new_embed.setAttribute('wmode', 'transparent');
embed.parentNode.replaceChild(new_embed, embed);
}
}
// loop through every object tag on the site
var objects = document.getElementsByTagName('object');
for (i = 0; i < objects.length; i++) {
object = objects[i];
var new_object;
// object is an IE specific tag so we can use outerHTML here
if (object.outerHTML) {
var html = object.outerHTML;
// replace an existing wmode parameter
if (html.match(/<param\s+name\s*=\s*('|")wmode('|")\s+value\s*=\s*('|")[a-zA-Z]+('|")\s*\/?\>/i))
new_object = html.replace(/<param\s+name\s*=\s*('|")wmode('|")\s+value\s*=\s*('|")window('|")\s*\/?\>/i, "<param name='wmode' value='transparent' />");
// add a new wmode parameter
else
new_object = html.replace(/<\/object\>/i, "<param name='wmode' value='transparent' />\n</object>");
// loop through each of the param tags
var children = object.childNodes;
for (j = 0; j < children.length; j++) {
try {
if (children[j] != null) {
var theName = children[j].getAttribute('name');
if (theName != null && theName.match(/flashvars/i)) {
new_object = new_object.replace(/<param\s+name\s*=\s*('|")flashvars('|")\s+value\s*=\s*('|")[^'"]*('|")\s*\/?\>/i, "<param name='flashvars' value='" + children[j].getAttribute('value') + "' />");
}
}
}
catch (err) {
}
}
// replace the old embed object with the fixed versiony
object.insertAdjacentHTML('beforeBegin', new_object);
object.parentNode.removeChild(object);
}
}
}
now you can just run in when the page loads with jQuery:
$(document).ready(function () {
fix_flash();
}
Also you can add ?wmode=transparent to each youtube link
So if you have code like:
<iframe src="http://www.youtube.com/embed/aoZbiS20HGI">
</iframe>
You need to change it to:
<iframe src="http://www.youtube.com/embed/aoZbiS20HGI?wmode=transparent">
</iframe>

Resources