How to replace a graphic with given file in InDesign via script? - javascript

I need to replace some images in an InDesign document with a given file. This happens using the InDesign server, but scripting is almost the same as with regular InDesign, except no user interaction is possible.
What I have is a InDesign Document, the ID of an Rectangle containing some image and the Path to a new image that should replace the image.
The image should be replaced, but the settings like FitOptions etc. should stay the same. Also, the new file shall be embedded in the InDesign Document. There is already some code that sort of works:
function changeImages(doc) {
var arrayLength = changeImage.length;
for (var i = 0; i < arrayLength; i++) {
var fr = doc.textFrames.itemByID(1 * changeImage[i].id);
if (!fr)
continue;
var file = File(imagePath + changeImage[i].file);
fr.place(file);
fr.fit (FitOptions.CONTENT_TO_FRAME);
fr.fit (FitOptions.PROPORTIONALLY);
fr.fit (FitOptions.CENTER_CONTENT);
}
}
This doesn't seem right. Why is it using doc.textFrames when the object is a rectangle? I am actually confused this even works.
Also it just sets some FitOptions, but I want to keep the existing.
I am very new to InDesign scripting, so I am lost here. I am reading the docs and other resources, but I am confused. e.g why is there doc.textFrames.itemByID but nothing like that for other Frames? Do I have to iterate doc.allPageItems and compare ids?

itemByID is a method available for all pageItems and both textFrames and rectangles are subclass of pageItem. So you have access to this method from both, and it'll give the same result. You should be able to use doc.rectangles.itemByID as well. See: http://www.indesignjs.de/extendscriptAPI/indesign11/#Rectangles.html#d1e201999__d1e202138
But you are right that the description is a bit confusing, it says:
Returns the TextFrame with the specified ID.
which is obviously not the case. If you already have the IDs you want to target, you could use doc.pageItems.itemByID, which is maybe less confusing, since basically you're looking for pageItems when using itemByID.
As for fitting options, they are a property of your rectangle object, so placing a new image shouldn't change the fitting options. If you want to keep the same, simply remove the calls to fit(). See in property list of Rectangle, frameFittingOptions: http://www.indesignjs.de/extendscriptAPI/indesign11/#Rectangle.html

Josef,
I've had the same problem with InDesign CS4 with keeping the original FitOptions. I was never able to figure out how to get the settings currently being used in InDesign CS4.
To get around the problem what I did was to set the value in the Fitting on Empty Frame in the Frame Fitting Options in the InDesign document.
Then in code I used that setting, something like this:
changeImages (app.activeDocument);
function changeImages(doc)
{
with(doc)
{
var rec = doc.rectangles.itemByID(207);
var file = new File("c:\\new_image.png");
rec.place(file);
rec.fit(rec.frameFittingOptions.fittingOnEmptyFrame);
}
}

Related

How to get loaded image property in phaser.js?

I have a problem for getting properties of loaded image in Phaser.js.
Now I resolve it by accessing private variable (a suck method I known...):
var image = game.textures.get("imageA")
console.log("width",image.frames.__BASE.width);
Does anyone has a better solution to get these properties?
Thanks a lot.
Depending on your UseCase, you could use the getSourceImage or get method, of the Texture object.
It is abit longer, also but works (if you need/want the html element ):
var image = game.textures.get("imageA");
console.info(image.getSourceImage().width);
here the link to the documentation
Or you could use the get function of the texture (if you need/want the phaser frame):
var image = game.textures.get("imageA");
console.info(image.get().width);
here the link to the documentation
The parameter for get and getSourceImage are optional, but you could enter a name/index of a frame, if you need a specific frame.

Loop through an SVG file in Javascript

I'm trying to go through a rather basic SVG document with four <rects>, each with a unique ID. I want to add those elements to an array.
This is what I have...
// event listner to make sure the page has loaded before running the scrupt
window.addEventListener("load", function(){
// gets an SVG object from the inline HTML
var svgObject = document.getElementById('rectTestSvg').contentDocument;
var elementList = [];
for(var i = 0; i < svgObject.numElements; i++){
if(svgObject[i].id('*_rect') === true)
{
elementList.push(svgObject.getElementAt(i));
}
}
console.log(elementList);
});
It doesn't work past getting the svgObject, but hopefully it at least helps illustrate the idea.
Any help anyone could throw my way would be really appreciated
In HTML5, SVG elements and their content are "just DOM elements", so queryselect the elements directly, and then form an array directly off of the result:
var myRects = Array.from(svgObject.querySelectorAll("rect"));
Done.
And the reason you want Array.from is because query selections are NodeList objects - while they're static (in this particular case, but most definitely not always so read the function documentation for functions you use!) they do not have any of the array functions that makes working with lists actually easy (map, filter, etc) so we turn it into an Array for writing normal code.
Do note that if your SVG object lives inside an iframe, you're almost guaranteed to not "just have access" to it, in which case you'll need to make sure the SVG document loads its own script that can talk to the parent page through a window.postMessage() channel (or websocket, but that's overkill).

Replace text using an array of placeholder : replacement pairs in JavaScript

This is a simple problem (I am new to JavaScript and have a limited knowledge of the syntax and using arrays etc.), so I am sure someone more knowledgeable will be able to advise the simplest solution fairly easily!
I would like to replace a number of text placeholders in an existing Google Doc template with variable text inputs, which I ultimately plan to populate from one or more external sources via APIs (such as a form).
function replaceAllPlaceholders() {
var body = DocumentApp.getActiveDocument().getBody(); //defines the range within which to replace text
body.replaceText('placeholder1', 'replacement1');
body.replaceText('placeholder2', 'replacement2');
body.replaceText('placeholder3', 'replacement3');
// ...
body.replaceText('placeholder98', 'replacement98');
body.replaceText('placeholder99', 'replacement99'); }
Rather than repeat the replaceText( function for each replacement as I have done above, how can I instead layout the information out as an array of placeholder:replacement pairs, and then loop through each?
// for example something like this (pseudo):
//
// var obj = {
// 'placeholder1': 'replacement1' // I would like to keep open the option to retrieve this array from an external source instead
// 'placeholder2': 'replacement2'
// 'placeholder3': 'replacement3' };
//
// body.replaceText(*all placeholders*,*all replacements*);
I imagine this would allow greater flexibility in editing the set of placeholders and or replacements going forward, either directly within Google Apps Script or by replacing the whole array to one retrieved from an external source (as well as reducing the code required). The problem is I have not been able to figure out the correct method to do this. Any suggestions?
Alternatively, is there a better way to achieve my goal?
I am open to all recommendations!
Try this
var placeholders = [
['placeholder1', 'replacement1'],
['placeholder2', 'replacement2'],
['placeholder3', 'replacement3']
];
placeholders.forEach(function(pair) {
body.replaceText(pair[0], pair[1]);
});

PDF.js returns text contents of the whole Document as each Page's textContent

I'm building a client-side app that uses PDF.js to parse the contents of a selected PDF file, and I'm running into a strange issue.
Everything seems to be working great. The code successfully loads the PDF.js PDF object, which then loops through the Pages of the document, and then gets the textContent for each Page.
After I let the code below run, and inspect the data in browser tools, I'm noticing that each Page's textContent object contains the text of the entire document, not ONLY the text from the related Page.
Has anybody experienced this before?
I pulled (and modified) most of the code I'm using from PDF.js posts here, and it's pretty straight-forward and seems to perform exactly as expected, aside from this issue:
testLoop: function (event) {
var file = event.target.files[0];
var fileReader = new FileReader();
fileReader.readAsArrayBuffer(file);
fileReader.onload = function () {
var typedArray = new Uint8Array(this.result);
PDFJS.getDocument(typedArray).then(function (pdf) {
for(var i = 1; i <= pdf.numPages; i++) {
pdf.getPage(i).then(function (page) {
page.getTextContent().then(function (textContent) {
console.log(textContent);
});
});
}
});
}
},
Additionally, the size of the returned textContent objects are slightly different for each Page, even though all of the objects share a common last object - the last bit of text for the whole document.
Here is an image of my inspector to illustrate that the objects are all very similarly sized.
Through manual inspection of the objects in the inspector shown, I can see that the data from, Page #1, for example, should really only consist of about ~140 array items, so why does the object for that page contain ~700 or so? And why the variation?
It looks like the issue here is the formatting of the PDF document I'm trying to parse. The PDF contains government records in a tabular format, which apparently was not composed according to modern PDF standards.
I've tested the script with different PDF files (which I know are properly composed), and the Page textContent objects returned are correctly split based on the content of the Pages.
In case anyone else runs into this issue in the future, there are at least two possible ways to handle the problem, as far as I have imagined so far:
Somehow reformat the malformed PDF to use updated standards, then process it. I don't know how to do this, nor am I sure it's realistic.
Select the largest of the returned Page textContent objects (since they all contain more or less the full text of the document) and do your operations on that textContent object.

How do I get my widget not to crash if there is no value in a xml node?

I'm getting an xml file and want to get data from it.
The source of the xml doesn't really matter but what I;ve got to get a certain field is:
tracks = xmlDoc.getElementsByTagName("track");
variable = tracks.item(i).childNodes.item(4).childNodes.item(0).nodeValue;
Now this works like a charm, EXCEPT when there is no value in the node. So if the structure is like this:
<xml>
<one>
<two>nodeValue</two>
</one>
<one>
<two></two>
</one>
</xml>
the widget will crash on the second 'one' node, because there is no value in the 'two' node. The console says:
TypeError: tracks.item(i).childNodes.item(4).childNodes.item(0) has no properties
Any ideas on how to get the widget to just see empty as an empty string (null, empty, or ""), instead of crashing? I'm guessing something along the lines of data, getValue(), text, or something else.
using
var track= xmlDoc.getElementsByTagName('track')[0];
var info= track.getElementsByTagName('artist')[0];
var value= info.firstChild? info.firstChild.data : '';
doesn't work and returns "TypeError: track has no properties". That's from the second line where artist is called.
Test that the ‘two’ node has a child node before accessing its data.
childNodes.item(i) (or the JavaScript simple form childNodes[i]) should generally be avoided, it's a bit fragile relying on whitespace text nodes being in the exact expected place.
I'd do something like:
var tracks= xmlDoc.getElementsByTagName('track')[0];
var track= tracks.getElementsByTagName('one')[0];
var info= track.getElementsByTagName('two')[0];
var value= info.firstChild? info.firstChild.data : '';
(If you don't know the tagnames of ‘one’ and ‘two’ in advance, you could always use ‘getElementsByTagName('*')’ to get all elements, as long as you don't need to support IE5, where this doesn't work.)
An alternative to the last line is to use a method to read all the text inside the node, including any of its child nodes. This doesn't matter if the node only ever contains at most one Text node, but can be useful if the tree can get denormalised or contain EntityReferences or nested elements. Historically one had to write a recurse method to get this information, but these days most browsers support the DOM Level 3 textContent property and/or IE's innerText extension:
var value= info.textContent!==undefined? info.textContent : info.innerText;
without a dtd that allows a one element to contain an empty two element, you will have to parse and fiddle the text of your xml to get a document out of it.
Empty elements are like null values in databases- put in something, a "Nothing" or "0" value, a non breaking space, anything at all- or don't include the two element.
Maybe it could be an attribute of one, instead of an element in its own right.
Attributes can have empty strings for values. Better than phantom elements .
Yahoo! Widgets does not implement all basic javascript functions needed to be able to use browser-code in a widget.
instead of using:
tracks = xmlDoc.getElementsByTagName("track");
variable = tracks.item(i).childNodes.item(4).childNodes.item(0).nodeValue;
to get values it's better to use Xpath with a direct conversion to string. When a string is empty in Yahoo! Widgets it doesn't give any faults, but returns the 'empty'. innerText and textContent (the basic javascript way in browsers, used alongside things like getElementsByTagName) are not fully (or not at all) implemented in the Yahoo! Widgets Engine and make it run slower and quite awfully react to xmlNodes and childNodes. an easy way however to traverse an xml Document structure is using 'evaluate' to get everything you need (including lists of nodes) from the xml.
After finding this out, my solution was to make everything a lot easier and less sensitive to faults and errors. Also I chose to put the objects in an array to make working with them easier.
var entries = xmlDoc.evaluate("lfm/recenttracks/track");
var length = entries.length;
for(var i = 0; i < length; i++) {
var entry = entries.item(i);
var obj = {
artist: entry.evaluate("string(artist)"),
name: entry.evaluate("string(name)"),
url: entry.evaluate("string(url)"),
image: entry.evaluate("string(image[#size='medium'])")
};
posts[i] = obj;
}

Categories