11

I used the following snippet to insert an image into a Google Document:

// Source: http://stackoverflow.com/a/18859986/1536038
var doc = DocumentApp.openById('Google Drive Id');
var img = DriveApp.getFileById('Google Drive Id').getBlob(); 
doc.getBody().insertImage(0, img);

The result is an In line image:
enter image description here

I want, however, to have a Wrap text image, like so: enter image description here

Is that possible via Google Apps Script (on the fly)?

tehhowch
  • 9,645
  • 4
  • 24
  • 42
Jacob Jan Tuinstra
  • 1,197
  • 3
  • 19
  • 50
  • 1
    A [previous answer](http://stackoverflow.com/questions/17575863/how-do-i-format-text-i-am-copying-from-google-document-in-google-app-script/17577431#17577431) involved copying attributes of inline images when merging documents. I figured that technique would help here, but was surprised when I tried it. Each inline image is a child of a paragraph element. Once you change an inline image to "Wrap Text", it disappears as a child of the containing paragraph! That looks like a bug; I suspect that they've introduced a new Element type, but it's not yet supported in the API. – Mogsdad Nov 29 '13 at 20:32
  • @Mogsdad Is there perhaps a way to replace an image (that is set to wrap text)? – Jacob Jan Tuinstra Nov 29 '13 at 20:39
  • 1
    I don't see any method in the API that would do that - it would be nice to have something analogous to replaceText(); and it's further complicated by the fact that we can't get a handle on the object to invoke any methods anyway (see my previous comment). – Mogsdad Nov 29 '13 at 21:05
  • 1
    I remember having seen somewhere that inline image was the only supported mode but the problem is that I can't remember where and when I saw it... (I know, this kind of info is really useless :-) ) I searched the issue tracker without success and I'm afraid it was on the old Google group discussion group that has recently been wiped out :-( Let's hope someone from the GAS team will give a definitive answer to this. – Serge insas Nov 29 '13 at 21:22
  • 4
    Yes only inline images (issue 1529)http://stackoverflow.com/questions/11183518/adding-images-fixed-position-to-google-apps-documents-by-script – DavidF Dec 11 '13 at 07:02

1 Answers1

11

Issue 1529 has been fixed. As of December 2015, Google Apps Script can manipulate PositionedImage objects in Google Docs.


They behave a little differently than InlineImage elements, as they need to be anchored to a ListItem or Paragraph element, while InlineImages can be added only to Body, FooterSection, HeaderSection or TableCell elements.

A PositionedImage is an object anchored in an element, while an InlineImage is itself an element of a document. This implies that you cannot convert one type of image directly to the other. (When you switch an image from "Wrap text" to "Inline" using the UI, the PositionedImage is removed from its anchor paragraph, then inserted into the body of the document outside of that paragraph. You could emulate that via script if necessary.)

Insert a PositionedImage

Here's an example of a PositionedImage inserted by the following script:

Screenshot

// http://stackoverflow.com/a/20661113/1677912
function DemoPositionedImage() {
  // Get handle on active document
  var doc = DocumentApp.getActiveDocument();

  // Find desired image file
  var matchedFiles = DriveApp.getFilesByName('apple-touch-icon.png');
  if (matchedFiles.hasNext()) {
    // Get image object from file
    var image = matchedFiles.next().getBlob(); 

    // Add image as a PositionedImage.
    var positionedImage = doc.getBody().getParagraphs()[0].addPositionedImage(image);

    // Adjust layout, etc. here

    // Log the ID of the new image
    Logger.log( positionedImage.getId() );
  }
}

The log shows the ID of the new image, like this:

[15-12-11 20:35:03:706 EST] kix.9dwnzjfapdy8

Be careful - if you add multiple images to the same element (e.g. Paragraph), with default layout, the newest image will overlay existing ones. Therefore, it may look like you have a single image when there are actually a pile of them.

Retrieve existing PositionedImages

Since a PositionedImage is not an element of a document, it does not appear in the element hierarchy with elements like paragraphs, tables, or InlineImages, and cannot be found through the document methods getChild(), getNextSibling(), and so on. Likewise, there is no Body.getPositionedImages() to parallel Body.getImages().

Instead, you can get a PositionedImage using its unique ID, e.g. kix.9dwnzjfapdy8 from the earlier example.

var positionedImage = getPositionedImage(storedId);

Alternatively, you can get all the PositionedImage objects in a containing element as an array.

var positionedImages = getPositionedImages();
for (var i=0; i<positionedImages.length; i++) {
  Logger.log( positionedImages[i].getId() );
}

Retrieving all the PositionedImages in a document requires traversing all the possible anchor elements. The following utility does just that.

/**
 * Get a list of all PositionedImages in a document.
 * See stackoverflow.com/a/20661113/1677912.
 *
 * @param {String} docId         (optional) ID of document to scan
 *
 * @returns {PositionedImage[]}  Array of PositionedImages in document
 */
function getAllPositionedImages( docId ) {
  // Open document if given ID, otherwise use active document.
  if (docId) {
    var doc = DocumentApp.openById(docId);
  }
  else {
    doc = DocumentApp.getActiveDocument();
  }

  // Get handle on document's body
  var body = doc.getBody();

  // array to hold all images in document
  var allPositionedImages = [];

  var numElems = body.getNumChildren();

  for (var childIndex=0; childIndex<numElems; childIndex++) {
    var child = body.getChild(childIndex);
    switch ( child.getType() ) {
      case DocumentApp.ElementType.PARAGRAPH:
        var container = child.asParagraph();
        break;
      case DocumentApp.ElementType.LIST_ITEM:
        container = child.asListItem();
        break;

      default:
        // Skip elements that can't contain PositionedImages.
        continue;
    }
    // Collect images from current container
    var imagesHere = container.getPositionedImages();
    allPositionedImages = allPositionedImages.concat(imagesHere);        
  }
  return allPositionedImages;
}

Layout control

Most of the layout controls for PositionedImages are well described in the documentation:

The PositionedLayout enum used with the Layout methods is unique to PositionedImages. At the time of launch of PositionedImage support however, it was not included in editor autocompletion, and the documentation contained no examples of its use. Let's fill that gap.

Here's how you can set the layout of a PositionedImage so that it is wrapped by text:

positionedImage.setLayout( DocumentApp.PositionedLayout.WRAP_TEXT );

The following utility function gets the English equivalent of a PositionedLayout enum.

/**
 * Get the string representing the given PositionedLayout enum.
 * Ref: https://developers.google.com/apps-script/reference/document/positioned-layout
 *
 * See stackoverflow.com/a/20661113/1677912.
 *
 * @param {PositionedLayout} PositionedLayout  Enum value.
 *
 * @returns {String}         English text matching enum.
 */
function getLayoutString( PositionedLayout ) {
  var layout;
  switch ( PositionedLayout ) {
    case DocumentApp.PositionedLayout.ABOVE_TEXT:
      layout = "ABOVE_TEXT";
      break;
    case DocumentApp.PositionedLayout.BREAK_BOTH:
      layout = "BREAK_BOTH";
      break;
    case DocumentApp.PositionedLayout.BREAK_LEFT:
      layout = "BREAK_LEFT";
      break;
    case DocumentApp.PositionedLayout.BREAK_RIGHT:
      layout = "BREAK_RIGHT";
      break;
    case DocumentApp.PositionedLayout.WRAP_TEXT:
      layout = "WRAP_TEXT";
      break;
    default:
      layout = "";
      break;
  }
  return layout;
}

Note: This has been concurrently posted on my blog.

Mogsdad
  • 44,709
  • 21
  • 151
  • 275