QPDFPageObjectHelper::placeFormXObject(
    QPDFObjectHandle fo, std::string name,
    QPDFObjectHandle::Rectangle rect,
    bool invert_transformations)
{
    // Calculate the transformation matrix that will place the given
    // form XObject fully inside the given rectangle, shrinking and
    // centering if needed.

    // When rendering a form XObject, the transformation in the
    // graphics state (cm) is applied first (of course -- when it is
    // applied, the PDF interpreter doesn't even know we're going to
    // be drawing a form XObject yet), and then the object's matrix
    // (M) is applied. The resulting matrix, when applied to the form
    // XObject's bounding box, will generate a new rectangle. We want
    // to create a transformation matrix that make the form XObject's
    // bounding box land in exactly the right spot.

    QPDFObjectHandle fdict = fo.getDict();
    QPDFObjectHandle bbox_obj = fdict.getKey("/BBox");
    if (! bbox_obj.isRectangle())
    {
        return "";
    }

    QPDFMatrix wmatrix;         // work matrix
    QPDFMatrix tmatrix;         // "to" matrix
    QPDFMatrix fmatrix;         // "from" matrix
    if (invert_transformations)
    {
        // tmatrix inverts scaling and rotation of the destination
        // page. Applying this matrix allows the overlaid form
        // XObject's to be absolute rather than relative to properties
        // of the destination page. tmatrix is part of the computed
        // transformation matrix.
        tmatrix = QPDFMatrix(getMatrixForTransformations(true));
        wmatrix.concat(tmatrix);
    }
    if (fdict.getKey("/Matrix").isMatrix())
    {
        // fmatrix is the transformation matrix that is applied to the
        // form XObject itself. We need this for calculations, but we
        // don't explicitly use it in the final result because the PDF
        // rendering system automatically applies this last before
        // drawing the form XObject.
        fmatrix = QPDFMatrix(fdict.getKey("/Matrix").getArrayAsMatrix());
        wmatrix.concat(fmatrix);
    }

    // The current wmatrix handles transformation from the form
    // xobject and, if requested, the destination page. Next, we have
    // to adjust this for scale and position.

    // Step 1: figure out what scale factor we need to make the form
    // XObject's bounding box fit within the destination rectangle.

    // Transform bounding box
    QPDFObjectHandle::Rectangle bbox = bbox_obj.getArrayAsRectangle();
    QPDFObjectHandle::Rectangle T = wmatrix.transformRectangle(bbox);

    // Calculate a scale factor, if needed. If the transformed
    // rectangle is too big, shrink it. Never expand it.
    if ((T.urx == T.llx) || (T.ury == T.lly))
    {
        // avoid division by zero
        return "";
    }
    double rect_w = rect.urx - rect.llx;
    double rect_h = rect.ury - rect.lly;
    double t_w = T.urx - T.llx;
    double t_h = T.ury - T.lly;
    double xscale = rect_w / t_w;
    double yscale = rect_h / t_h;
    double scale = (xscale < yscale ? xscale : yscale);
    if (scale > 1.0)
    {
        scale = 1.0;
    }

    // Step 2: figure out what translation is required to get the
    // rectangle to the right spot: centered within the destination.
    wmatrix = QPDFMatrix();
    wmatrix.scale(scale, scale);
    wmatrix.concat(tmatrix);
    wmatrix.concat(fmatrix);

    T = wmatrix.transformRectangle(bbox);
    double t_cx = (T.llx + T.urx) / 2.0;
    double t_cy = (T.lly + T.ury) / 2.0;
    double r_cx = (rect.llx + rect.urx) / 2.0;
    double r_cy = (rect.lly + rect.ury) / 2.0;
    double tx = r_cx - t_cx;
    double ty = r_cy - t_cy;

    // Now we can calculate the final matrix. The final matrix does
    // not include fmatrix because that is applied automatically by
    // the PDF interpreter.
    QPDFMatrix cm;
    cm.translate(tx, ty);
    cm.scale(scale, scale);
    cm.concat(tmatrix);
    return (
        "q\n" +
        cm.unparse() + " cm\n" +
        name + " Do\n" +
        "Q\n");
}