I'm trying to create a Google Docs add-on in which someone:
Selects an image
Clicks a menu item
A dialog is displayed, showing the image (on a canvas) with a couple tools
Canvas is modified using tools
Canvas data is saved and replaces the original image
Meta data for the image is saved, so it can be re-edited from the original.
I know how to get the image selection (from the GS code) and trigger the menu item and dialog. I also know how to do all of my custom code things.
I need to know:
How to get the original image URL (or extract it as a base64 string) that I can put in to a canvas
Replace the image and save it in the document.
Save metadata on a per-image basis so it can be re-edited.
Examples would be awesome, though links to documentation would also be great. I've found a lot of things, but nothing concrete on how to extract the data as anything but a blob.
(This answer is a work-in-progress. Starting an answer to put the bits as I figure them out. If someone else helps me figure out the missing bits, I'll accept theirs instead of this one).
How to get the original image URL (or extract it as a base64 string)
As far as I can tell, there isn't a way to get the default URL. I was however able to get the base64 string. It's slightly convoluted, but works.
Code.gs
// Gets an InlineImage in some way. I'm using the currently selected image,
// but that's irrelevant to the code sample.
// #return {InlineImage}
function getImage() {
// gets the InlineImage element somehow
}
// Gets the actual data URI.
// Note: this function uses image/png only. You can change this by changing
// it in the two places, or using a variable. Just be sure the two spots
// match.
// #return {string}
function getDataUri() {
return 'data:image/png;base64,' + Utilities.base64Encode(getImage().getAs('image/png').getBytes());
}
MyDialogJavaScript.html
$(function () {
google.script.run
.withSuccessHandler(function (data) { console.log(data); })
.withFailureHandler(function (err) { console.log('failure: ' + err); })
.getDataUri();
});
An important note: you must SandboxMode.IFRAME when creating the dialog or else you'll get something like:
Rejecting <img>.setAttribute('src', blahblahblah
This is apparently due to a limitation in the Caja compiler normally used. See the answer here for more info: Using base64-encoded images with HtmlService in Apps Script
Related
I'm using EPUB.js and Vue to render an Epub. I want to display the cover images of several epub books so users can click one to then see the whole book.
There's no documentation on how to do this, but there are several methods that indicate that this should be possible.
First off, there's Book.coverUrl() method.
Note that I'm setting an img src property equal to bookCoverSrc in the Vue template. Setting this.bookCoverSrc will automatically update the src of the img tag and cause an image to display (if the src is valid / resolves).
this.book = new Epub(this.epubUrl, {});
this.book.ready.then(() => {
this.book.coverUrl().then((url) => {
this.bookCoverSrc = url;
});
})
The above doesn't work. url is undefined.
Weirdly, there appears to be a cover property directly on book. So, I try:
this.book = new Epub(this.epubUrl, {});
this.book.ready.then(() => {
this.coverSrc = this.book.cover;
});
this.book.cover resolves to OEBPS/#public#vhost#g#gutenberg#html#files#49010#49010-h#images#cover.jpg, so at least locally when I set it to a src results in a request to http://localhost:8080/OEBPS/#public#vhost#g#gutenberg#html#files#49010#49010-h#images#cover.jpg, which 200s but returns no content. Probably a quirk of webpack-dev-server to 200 on that, but if I page through sources in Chrome dev tools I also don't see any indicate that such a URL should resolve.
So, docs not helping. I googled and found this github question from 2015. Their code is like
$("#cover").attr("src", Book.store.urlCache[Book.cover]);
Interesting, nothing in the docks about Book.store.urlCache. As expected, urlCache is undefined, though book.store exists. I don't see anything on there that can help me display a cover image though.
Using epub.js, how can I display a cover image of an Epub file? Note that simply rendering the first "page" of the Epub file (which is usually the cover image) doesn't solve my problem, as I'd like to list a couple epub files' cover images.
Note also that I believe the epub files I'm using do have cover images. The files are Aesop's Fables and Irish Wonders.
EDIT: It's possible I need to use Book.load on the url provided by book.cover first. I did so and tried to console.log it, but it's a massive blog of weirdly encoded text that looks something like:
����
So I think it's an image straight up, and I need to find a way to get that onto the Document somehow?
EDIT2: that big blobby blob is type: string, and I can't atob() or btoa() it.
EDIT3: Just fetching the url provided by this.book.cover returns my index.html, default behavior for webpack-dev-server when it doesn't know what else to do.
EDIT4: Below is the code for book.coverUrl from epub.js
key: "coverUrl",
value: function coverUrl() {
var _this9 = this;
var retrieved = this.loaded.cover.then(function (url) {
if (_this9.archived) {
// return this.archive.createUrl(this.cover);
return _this9.resources.get(_this9.cover);
} else {
return _this9.cover;
}
});
return retrieved;
}
If I use this.archive.createUrl(this.cover) instead of this.resources.get, I actually get a functional URL, that looks like blob:http://localhost:8080/9a3447b7-5cc8-4cfd-8608-d963910cb5f5. I'll try getting that out into src and see what happens.
The reason this was happening to me was because the functioning line of code in the coverUrl function was commented out in the source library epub.js, and a non-functioning line of code was written instead.
So, I had to copy down the entire library, uncomment the good code and delete the bad. Now the function works as it should.
To do so, clone down the entire epub.js project. Copy over the dependencies in that project's package.json to your own. Then, take the src, lib, and libs folders and copy them somewhere into your project. Find a way to disable eslint for the location you put these folders into because the project uses TAB characters for spacing which caused my terminal to hang due to ESLINT exploding.
npm install so you have your and epub.js dependencies in your node_modules.
Open book.js. Uncomment line 661 which looks like
return this.archive.createUrl(this.cover);
and comment out line 662 which looks like
// return this.resources.get(this.cover);
Now you can display an image by setting an img tag's src attribute to the URL returned by book.coverUrl().
this.book = new Epub(this.epubUrl, {});
this.book.ready.then(() => {
this.book.coverUrl().then((url) => {
this.bookCoverSrc = url;
});
})
I am using the exif.js from here.
I have currently extracted all the data from my img with EXIF.getData(), and have displayed it in my as a side to the picture.
But now I want to display not all the data, but to specify what exactly (camera model, date when the picture was taken, if there are any Tags to the picture, etc).
My pictures are I JSON file and I have added Tags to all of them.
I am not sure how to approach it. Should I use EXIF.getData() and proceed somehow from there? I am extracting the data on IMG click in a modal dialog.
function clickImage(){
let images = $(".box img");
images.click(function() {
modalContent.attr('src', $(this).attr('src'));
modalView.css('display', 'block');
googleMap.css('display','none');
EXIF.getData(this, function() {
result.text(EXIF.pretty(this))
}
}
Currently, I gave all the date in result.
You should use exifr instead of exif-js because it has been unmaintained for two years and still has breaking bugs in it (n is undefined).
With exifr you can either parse everything
exifr.parse('./myimage.jpg').then(output => {
console.log('Camera:', output.Make, output.Model))
})
or just a few tags
let output = await exifr.parse(file, ['ISO', 'Orientation', 'LensModel'])
I'm trying to automatically insert an image from a Google drive into a Google form with the help of a Google script using the addImage function, which has recently stopped working and leads to the error message 'Invalid data update formular'.
I'm using Google forms to allow users to enter text from images.
Some sort of manual image recognition.
The images are stored on the googel drive in a directory.
A Google script inserts them into a form and adds several questions with short answers, multiple choice questions and page breaks.
So until recently everything worked, but now the addImage function of the form returns the error message
Invalid data update form.
I have tried to do do the following:
created a new form with a new script to test addImageItem function (source below)
tested that all other functions to add items to the form except addImageItem (like addMultipleChoiceItem, addPageBreakItem, addCheckboxItem, addTextItem) still work
add image manualy to see if that still work
use other formats as image source (JPG and PNG)
use different files on the google drive as image source
use UrlFetchApp.fetch function as image source (like in the documentation example)
All attempts with addImageItem unsuccessful.
The functions addMultipleChoiceItem, addPageBreakItem, addCheckboxItem, addTextItem return the desired result by adding Items to the form.
Also adding image by hand using the edit form interface stil works.
The images we update are about 60kB in size, about 800x600 px large.
The answers to this thread from 2012 which looks similar do not help. The error messages in the question are different. I also tried to use the getBlob() and getThumbnail() functions, as mentioned in the answers there, but they also lead to the error 'Invalid data update form'.
function myFunction() {
var form = FormApp.openById("drive_file_id_of_form");
var img = DriveApp.getFileById("drive_file_id_of_image");
var imgFormItem = form.addImageItem();
imgFormItem.setImage(img);
//var img = UrlFetchApp.fetch('https://www.google.com/images/srpr/logo4w.png');
//var blob = img.getThumbnail();
//var blob = img.getBlob();
//form.addImageItem().setImage(blob);
}
According to the google documentation, addImage should return a newly created ImageItem as a return value but lead to the error on line 4:
Invalid data for updating the form. (line4, file "Code")
Can someone please help me find out what is invalid in the form data or in the images I try to add and how to avoid this problem?
Edit: found a fresh bug tracker id for this topic on google issue tracker
We have an InDesign Server service that changes text, images, colours etc in documents according to user input. When done, it outputs either an INDD package (zipped directory) or a PDF.
One of our clients requires all output to be in CMYK, according to their own .icc colour profile. I can get IDS to attach the colour profile to the file no problem, like this:
// Set CMYK profile
if(inputs['cmyk_profile'] !== undefined && inputs['cmyk_profile']) {
app.colorSettings.cmykPolicy = ColorSettingsPolicy.PRESERVE_EMBEDDED_PROFILES;
document.cmykPolicy = ColorSettingsPolicy.PRESERVE_EMBEDDED_PROFILES;
document.cmykProfile = inputs['cmyk_profile'];
//document.printPreferences.colorOutput = ColorOutputModes.COMPOSITE_CMYK;
}
I confess, though, that I am a bit of a newbie and the PRESERVE_EMBEDDED_PROFILES lines are only a vague guess of what I think should be happening. I also cannot get the last, commented-out, line to work because it throws the error: "Invalid parameter". According to the docs, this instance variable is "not valid when a device-independent PPD is specified" although I don't see why not.
My main problem is that the user-inputted images and colours can be in RGB format, and they need to be converted to CMYK, in that specific colour profile. (I have been told that simply adding the profile to the file isn't enough - I have to convert the assets as well)
I am also trying to enforce CMYK when converting to PDF too, like this:
// Set CMYK if document has it
if(document.cmykPolicy)
{
app.pdfExportPreferences.pdfDestinationProfile = PDFProfileSelector.USE_DOCUMENT;
app.pdfExportPreferences.pdfColorSpace = PDFColorSpace.CMYK;
}
else
{
app.pdfExportPreferences.pdfColorSpace = PDFColorSpace.UNCHANGED_COLOR_SPACE;
}
// export pdf...
document.exportFile(ExportFormat.pdfType, myFile, preset);
But I have been told that the resulting PDF file is also RGB.
Is there any relatively painless way of doing this?
I am going to implement a dynamic legend using JavaScript in Adobe Acrobat.
The document contains a lot of layers. Every layer has an own legend. The origin idea is to implement the legend so, that it contains the images in a dialog box for the visible layers.
I can only hide/show the layers by setting state to false or true (this.getOCGs()[i].state = false;) on document-level.
Question 1: Can I extract data from layer somehow for legend establishing? I think no, as we only have these function on layers: getIntent(), setIntent() and setAction(). Right? Therefore I decided to arrange it so, that all needed icons for every layer are saved in a folder with corresponding names. JavaScript should import the icons and I build the a dialog window with icons of visible Layers and place a text(description for this icon).
I tried all possibilities of image import described here: http://pubhelper.blogspot.com.au/2012/07/astuces-toolbar-icons-et-javascript.html. I got only one way (Convert the icons as hexadecimal strings). This way isn't good, as it is too much work to create with an other tool a hexadecimal string from a images and place it into a javascript code.
Unfortunately, I cannot import image using other methods:(. Since the security settings in Adobe are changed after version 7 or so, it is not possible to use functions like app.newDoc, app.openDoc, even app.getPath On document-level. I decided to implement the import on a folder level using trusted functions like this:
Variant 1:
var importImg = app.trustedFunction(function() {
app.beginPriv();
var myDoc = app.newDoc({
nWidth: 20,
nHeight: 20
});
var img = myDoc.importIcon("icon", "/icon.png", 0);
app.endPriv();
return img; });
var oIcon = importImg();
The settings in Preferences->JavaScript-> JavaScript Security are disabled (Enable menu item JS execution privileges, enable global object security policy)
NotAllowedError: Security settings prevent access to this property or method.
App.newDoc:109:Folder-Level:User:acrobat.js
Variant 2:
var importImg = app.trustedFunction(function() {
var appPath = var phPath = app.getPath({
cCategory: "user",
cFolder: "javascript"
});
try {
app.beginPriv();
var doc = app.openDoc({
cPath: phPath + "/icon.png",
bHidden: true
});
app.endPriv();
} catch (e) {
console.println("Could not open icon file: " + e);
return;
}
var oIcon = util.iconStreamFromIcon(doc.getIcon("icon"));
return oIcon;});
var oIcon = importImg();
Could not open icon file: NotAllowedError: Security settings prevent access to this property or method.
At least it allows the execution of all these functions like app.newDoc, but in the second variant it says, wrong range of content or so. Maybe is here the pdf from an image created false? I just took the image and printed it into a pdf.
I tried all these possibilities with .jpg, .png, .pdf. with different sizes(big images and 20x20), It doesn't work.
Could somebody help me, as I spent a lot of time with trying different possibilities. It would be actually better to implement the main goal described above on document level, is it possible?
Thank you and kind regards,
Alex
Do you have the Console fully activated in Acrobat? If not, do so and look for error messages you get.
The first variant does not work, because myDoc is not defined (unless you have done that before you call the importImg function).
If you want to import the image into the newly created file, you will have to make a reference to the Document Object you create with newDoc(). Actually, that would make the link to myDoc, as in
var myDoc = app.newDoc(1,1)
(are you sure you want to create a document measuring 1x1 pt?)
The next issue with the first variant is a bug in Acrobat, which discards "floating" Icon Objects when saving the document; you'd have to attach the Icon Object to a field to keep it; this field can be hidden, or even on a hidden Template page in the document.