Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d84522ce authored by Dillon Simpson's avatar Dillon Simpson
Browse files

Move PdfRenderer java and native code to packages/providers/MediaProvider

PdfEditor will be mainlined post-V due to API review bandwidth constraints.

Test: manual testing against printspooler usage of this API, CTS
Bug: 319339682
Change-Id: I65fa29c0268f9fa9294807e8ba3fb68f7a7519dd
parent 6971e714
Loading
Loading
Loading
Loading
+0 −18
Original line number Diff line number Diff line
@@ -17922,24 +17922,6 @@ package android.graphics.pdf {
    method public android.graphics.pdf.PdfDocument.PageInfo.Builder setContentRect(android.graphics.Rect);
  }
  public final class PdfRenderer implements java.lang.AutoCloseable {
    ctor public PdfRenderer(@NonNull android.os.ParcelFileDescriptor) throws java.io.IOException;
    method public void close();
    method public int getPageCount();
    method public android.graphics.pdf.PdfRenderer.Page openPage(int);
    method public boolean shouldScaleForPrinting();
  }
  public final class PdfRenderer.Page implements java.lang.AutoCloseable {
    method public void close();
    method public int getHeight();
    method public int getIndex();
    method public int getWidth();
    method public void render(@NonNull android.graphics.Bitmap, @Nullable android.graphics.Rect, @Nullable android.graphics.Matrix, int);
    field public static final int RENDER_MODE_FOR_DISPLAY = 1; // 0x1
    field public static final int RENDER_MODE_FOR_PRINT = 2; // 0x2
  }
}
package android.graphics.text {
+20 −12
Original line number Diff line number Diff line
@@ -25,7 +25,9 @@ import android.os.ParcelFileDescriptor;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;

import dalvik.system.CloseGuard;

import libcore.io.IoUtils;

import java.io.IOException;
@@ -37,6 +39,12 @@ import java.io.IOException;
 */
public final class PdfEditor {

    /**
     * Any call the native pdfium code has to be single threaded as the library does not support
     * parallel use.
     */
    private static final Object sPdfiumLock = new Object();

    private final CloseGuard mCloseGuard = CloseGuard.get();

    private long mNativeDocument;
@@ -79,7 +87,7 @@ public final class PdfEditor {
        }
        mInput = input;

        synchronized (PdfRenderer.sPdfiumLock) {
        synchronized (sPdfiumLock) {
            mNativeDocument = nativeOpen(mInput.getFd(), size);
            try {
                mPageCount = nativeGetPageCount(mNativeDocument);
@@ -112,7 +120,7 @@ public final class PdfEditor {
        throwIfClosed();
        throwIfPageNotInDocument(pageIndex);

        synchronized (PdfRenderer.sPdfiumLock) {
        synchronized (sPdfiumLock) {
            mPageCount = nativeRemovePage(mNativeDocument, pageIndex);
        }
    }
@@ -138,12 +146,12 @@ public final class PdfEditor {
            Point size = new Point();
            getPageSize(pageIndex, size);

            synchronized (PdfRenderer.sPdfiumLock) {
            synchronized (sPdfiumLock) {
                nativeSetTransformAndClip(mNativeDocument, pageIndex, transform.ni(),
                        0, 0, size.x, size.y);
            }
        } else {
            synchronized (PdfRenderer.sPdfiumLock) {
            synchronized (sPdfiumLock) {
                nativeSetTransformAndClip(mNativeDocument, pageIndex, transform.ni(),
                        clip.left, clip.top, clip.right, clip.bottom);
            }
@@ -161,7 +169,7 @@ public final class PdfEditor {
        throwIfOutSizeNull(outSize);
        throwIfPageNotInDocument(pageIndex);

        synchronized (PdfRenderer.sPdfiumLock) {
        synchronized (sPdfiumLock) {
            nativeGetPageSize(mNativeDocument, pageIndex, outSize);
        }
    }
@@ -177,7 +185,7 @@ public final class PdfEditor {
        throwIfOutMediaBoxNull(outMediaBox);
        throwIfPageNotInDocument(pageIndex);

        synchronized (PdfRenderer.sPdfiumLock) {
        synchronized (sPdfiumLock) {
            return nativeGetPageMediaBox(mNativeDocument, pageIndex, outMediaBox);
        }
    }
@@ -193,7 +201,7 @@ public final class PdfEditor {
        throwIfMediaBoxNull(mediaBox);
        throwIfPageNotInDocument(pageIndex);

        synchronized (PdfRenderer.sPdfiumLock) {
        synchronized (sPdfiumLock) {
            nativeSetPageMediaBox(mNativeDocument, pageIndex, mediaBox);
        }
    }
@@ -209,7 +217,7 @@ public final class PdfEditor {
        throwIfOutCropBoxNull(outCropBox);
        throwIfPageNotInDocument(pageIndex);

        synchronized (PdfRenderer.sPdfiumLock) {
        synchronized (sPdfiumLock) {
            return nativeGetPageCropBox(mNativeDocument, pageIndex, outCropBox);
        }
    }
@@ -225,7 +233,7 @@ public final class PdfEditor {
        throwIfCropBoxNull(cropBox);
        throwIfPageNotInDocument(pageIndex);

        synchronized (PdfRenderer.sPdfiumLock) {
        synchronized (sPdfiumLock) {
            nativeSetPageCropBox(mNativeDocument, pageIndex, cropBox);
        }
    }
@@ -238,7 +246,7 @@ public final class PdfEditor {
    public boolean shouldScaleForPrinting() {
        throwIfClosed();

        synchronized (PdfRenderer.sPdfiumLock) {
        synchronized (sPdfiumLock) {
            return nativeScaleForPrinting(mNativeDocument);
        }
    }
@@ -255,7 +263,7 @@ public final class PdfEditor {
        try {
            throwIfClosed();

            synchronized (PdfRenderer.sPdfiumLock) {
            synchronized (sPdfiumLock) {
                nativeWrite(mNativeDocument, output.getFd());
            }
        } finally {
@@ -287,7 +295,7 @@ public final class PdfEditor {

    private void doClose() {
        if (mNativeDocument != 0) {
            synchronized (PdfRenderer.sPdfiumLock) {
            synchronized (sPdfiumLock) {
                nativeClose(mNativeDocument);
            }
            mNativeDocument = 0;
+0 −502
Original line number Diff line number Diff line
/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.graphics.pdf;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;

import com.android.internal.util.Preconditions;

import dalvik.system.CloseGuard;

import libcore.io.IoUtils;

import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * <p>
 * This class enables rendering a PDF document. This class is not thread safe.
 * </p>
 * <p>
 * If you want to render a PDF, you create a renderer and for every page you want
 * to render, you open the page, render it, and close the page. After you are done
 * with rendering, you close the renderer. After the renderer is closed it should not
 * be used anymore. Note that the pages are rendered one by one, i.e. you can have
 * only a single page opened at any given time.
 * </p>
 * <p>
 * A typical use of the APIs to render a PDF looks like this:
 * </p>
 * <pre>
 * // create a new renderer
 * PdfRenderer renderer = new PdfRenderer(getSeekableFileDescriptor());
 *
 * // let us just render all pages
 * final int pageCount = renderer.getPageCount();
 * for (int i = 0; i < pageCount; i++) {
 *     Page page = renderer.openPage(i);
 *
 *     // say we render for showing on the screen
 *     page.render(mBitmap, null, null, Page.RENDER_MODE_FOR_DISPLAY);
 *
 *     // do stuff with the bitmap
 *
 *     // close the page
 *     page.close();
 * }
 *
 * // close the renderer
 * renderer.close();
 * </pre>
 *
 * <h3>Print preview and print output</h3>
 * <p>
 * If you are using this class to rasterize a PDF for printing or show a print
 * preview, it is recommended that you respect the following contract in order
 * to provide a consistent user experience when seeing a preview and printing,
 * i.e. the user sees a preview that is the same as the printout.
 * </p>
 * <ul>
 * <li>
 * Respect the property whether the document would like to be scaled for printing
 * as per {@link #shouldScaleForPrinting()}.
 * </li>
 * <li>
 * When scaling a document for printing the aspect ratio should be preserved.
 * </li>
 * <li>
 * Do not inset the content with any margins from the {@link android.print.PrintAttributes}
 * as the application is responsible to render it such that the margins are respected.
 * </li>
 * <li>
 * If document page size is greater than the printed media size the content should
 * be anchored to the upper left corner of the page for left-to-right locales and
 * top right corner for right-to-left locales.
 * </li>
 * </ul>
 *
 * @see #close()
 */
public final class PdfRenderer implements AutoCloseable {
    /**
     * Any call the native pdfium code has to be single threaded as the library does not support
     * parallel use.
     */
    final static Object sPdfiumLock = new Object();

    private final CloseGuard mCloseGuard = CloseGuard.get();

    private final Point mTempPoint = new Point();

    private long mNativeDocument;

    private final int mPageCount;

    private ParcelFileDescriptor mInput;

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    private Page mCurrentPage;

    /** @hide */
    @IntDef({
        Page.RENDER_MODE_FOR_DISPLAY,
        Page.RENDER_MODE_FOR_PRINT
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface RenderMode {}

    /**
     * Creates a new instance.
     * <p>
     * <strong>Note:</strong> The provided file descriptor must be <strong>seekable</strong>,
     * i.e. its data being randomly accessed, e.g. pointing to a file.
     * </p>
     * <p>
     * <strong>Note:</strong> This class takes ownership of the passed in file descriptor
     * and is responsible for closing it when the renderer is closed.
     * </p>
     * <p>
     * If the file is from an untrusted source it is recommended to run the renderer in a separate,
     * isolated process with minimal permissions to limit the impact of security exploits.
     * </p>
     *
     * @param input Seekable file descriptor to read from.
     *
     * @throws java.io.IOException If an error occurs while reading the file.
     * @throws java.lang.SecurityException If the file requires a password or
     *         the security scheme is not supported.
     */
    public PdfRenderer(@NonNull ParcelFileDescriptor input) throws IOException {
        if (input == null) {
            throw new NullPointerException("input cannot be null");
        }

        final long size;
        try {
            Os.lseek(input.getFileDescriptor(), 0, OsConstants.SEEK_SET);
            size = Os.fstat(input.getFileDescriptor()).st_size;
        } catch (ErrnoException ee) {
            throw new IllegalArgumentException("file descriptor not seekable");
        }
        mInput = input;

        synchronized (sPdfiumLock) {
            mNativeDocument = nativeCreate(mInput.getFd(), size);
            try {
                mPageCount = nativeGetPageCount(mNativeDocument);
            } catch (Throwable t) {
                nativeClose(mNativeDocument);
                mNativeDocument = 0;
                throw t;
            }
        }

        mCloseGuard.open("close");
    }

    /**
     * Closes this renderer. You should not use this instance
     * after this method is called.
     */
    public void close() {
        throwIfClosed();
        throwIfPageOpened();
        doClose();
    }

    /**
     * Gets the number of pages in the document.
     *
     * @return The page count.
     */
    public int getPageCount() {
        throwIfClosed();
        return mPageCount;
    }

    /**
     * Gets whether the document prefers to be scaled for printing.
     * You should take this info account if the document is rendered
     * for printing and the target media size differs from the page
     * size.
     *
     * @return If to scale the document.
     */
    public boolean shouldScaleForPrinting() {
        throwIfClosed();

        synchronized (sPdfiumLock) {
            return nativeScaleForPrinting(mNativeDocument);
        }
    }

    /**
     * Opens a page for rendering.
     *
     * @param index The page index.
     * @return A page that can be rendered.
     *
     * @see android.graphics.pdf.PdfRenderer.Page#close() PdfRenderer.Page.close()
     */
    public Page openPage(int index) {
        throwIfClosed();
        throwIfPageOpened();
        throwIfPageNotInDocument(index);
        mCurrentPage = new Page(index);
        return mCurrentPage;
    }

    @Override
    protected void finalize() throws Throwable {
        try {
            if (mCloseGuard != null) {
                mCloseGuard.warnIfOpen();
            }

            doClose();
        } finally {
            super.finalize();
        }
    }

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    private void doClose() {
        if (mCurrentPage != null) {
            mCurrentPage.close();
            mCurrentPage = null;
        }

        if (mNativeDocument != 0) {
            synchronized (sPdfiumLock) {
                nativeClose(mNativeDocument);
            }
            mNativeDocument = 0;
        }

        if (mInput != null) {
            IoUtils.closeQuietly(mInput);
            mInput = null;
        }
        mCloseGuard.close();
    }

    private void throwIfClosed() {
        if (mInput == null) {
            throw new IllegalStateException("Already closed");
        }
    }

    private void throwIfPageOpened() {
        if (mCurrentPage != null) {
            throw new IllegalStateException("Current page not closed");
        }
    }

    private void throwIfPageNotInDocument(int pageIndex) {
        if (pageIndex < 0 || pageIndex >= mPageCount) {
            throw new IllegalArgumentException("Invalid page index");
        }
    }

    /**
     * This class represents a PDF document page for rendering.
     */
    public final class Page implements AutoCloseable {

        private final CloseGuard mCloseGuard = CloseGuard.get();

        /**
         * Mode to render the content for display on a screen.
         */
        public static final int RENDER_MODE_FOR_DISPLAY = 1;

        /**
         * Mode to render the content for printing.
         */
        public static final int RENDER_MODE_FOR_PRINT = 2;

        private final int mIndex;
        private final int mWidth;
        private final int mHeight;

        private long mNativePage;

        private Page(int index) {
            Point size = mTempPoint;
            synchronized (sPdfiumLock) {
                mNativePage = nativeOpenPageAndGetSize(mNativeDocument, index, size);
            }
            mIndex = index;
            mWidth = size.x;
            mHeight = size.y;
            mCloseGuard.open("close");
        }

        /**
         * Gets the page index.
         *
         * @return The index.
         */
        public int getIndex() {
            return  mIndex;
        }

        /**
         * Gets the page width in points (1/72").
         *
         * @return The width in points.
         */
        public int getWidth() {
            return mWidth;
        }

        /**
         * Gets the page height in points (1/72").
         *
         * @return The height in points.
         */
        public int getHeight() {
            return mHeight;
        }

        /**
         * Renders a page to a bitmap.
         * <p>
         * You may optionally specify a rectangular clip in the bitmap bounds. No rendering
         * outside the clip will be performed, hence it is your responsibility to initialize
         * the bitmap outside the clip.
         * </p>
         * <p>
         * You may optionally specify a matrix to transform the content from page coordinates
         * which are in points (1/72") to bitmap coordinates which are in pixels. If this
         * matrix is not provided this method will apply a transformation that will fit the
         * whole page to the destination clip if provided or the destination bitmap if no
         * clip is provided.
         * </p>
         * <p>
         * The clip and transformation are useful for implementing tile rendering where the
         * destination bitmap contains a portion of the image, for example when zooming.
         * Another useful application is for printing where the size of the bitmap holding
         * the page is too large and a client can render the page in stripes.
         * </p>
         * <p>
         * <strong>Note: </strong> The destination bitmap format must be
         * {@link Config#ARGB_8888 ARGB}.
         * </p>
         * <p>
         * <strong>Note: </strong> The optional transformation matrix must be affine as per
         * {@link android.graphics.Matrix#isAffine() Matrix.isAffine()}. Hence, you can specify
         * rotation, scaling, translation but not a perspective transformation.
         * </p>
         *
         * @param destination Destination bitmap to which to render.
         * @param destClip Optional clip in the bitmap bounds.
         * @param transform Optional transformation to apply when rendering.
         * @param renderMode The render mode.
         *
         * @see #RENDER_MODE_FOR_DISPLAY
         * @see #RENDER_MODE_FOR_PRINT
         */
        public void render(@NonNull Bitmap destination, @Nullable Rect destClip,
                           @Nullable Matrix transform, @RenderMode int renderMode) {
            if (mNativePage == 0) {
                throw new NullPointerException();
            }

            destination = Preconditions.checkNotNull(destination, "bitmap null");

            if (destination.getConfig() != Config.ARGB_8888) {
                throw new IllegalArgumentException("Unsupported pixel format");
            }

            if (destClip != null) {
                if (destClip.left < 0 || destClip.top < 0
                        || destClip.right > destination.getWidth()
                        || destClip.bottom > destination.getHeight()) {
                    throw new IllegalArgumentException("destBounds not in destination");
                }
            }

            if (transform != null && !transform.isAffine()) {
                 throw new IllegalArgumentException("transform not affine");
            }

            if (renderMode != RENDER_MODE_FOR_PRINT && renderMode != RENDER_MODE_FOR_DISPLAY) {
                throw new IllegalArgumentException("Unsupported render mode");
            }

            if (renderMode == RENDER_MODE_FOR_PRINT && renderMode == RENDER_MODE_FOR_DISPLAY) {
                throw new IllegalArgumentException("Only single render mode supported");
            }

            final int contentLeft = (destClip != null) ? destClip.left : 0;
            final int contentTop = (destClip != null) ? destClip.top : 0;
            final int contentRight = (destClip != null) ? destClip.right
                    : destination.getWidth();
            final int contentBottom = (destClip != null) ? destClip.bottom
                    : destination.getHeight();

            // If transform is not set, stretch page to whole clipped area
            if (transform == null) {
                int clipWidth = contentRight - contentLeft;
                int clipHeight = contentBottom - contentTop;

                transform = new Matrix();
                transform.postScale((float)clipWidth / getWidth(),
                        (float)clipHeight / getHeight());
                transform.postTranslate(contentLeft, contentTop);
            }

            // FIXME: This code is planned to be outside the UI rendering module, so it should not
            // be able to access native instances from Bitmap, Matrix, etc.
            final long transformPtr = transform.ni();

            synchronized (sPdfiumLock) {
                nativeRenderPage(mNativeDocument, mNativePage, destination.getNativeInstance(),
                        contentLeft, contentTop, contentRight, contentBottom, transformPtr,
                        renderMode);
            }
        }

        /**
         * Closes this page.
         *
         * @see android.graphics.pdf.PdfRenderer#openPage(int)
         */
        @Override
        public void close() {
            throwIfClosed();
            doClose();
        }

        @Override
        protected void finalize() throws Throwable {
            try {
                if (mCloseGuard != null) {
                    mCloseGuard.warnIfOpen();
                }

                doClose();
            } finally {
                super.finalize();
            }
        }

        private void doClose() {
            if (mNativePage != 0) {
                synchronized (sPdfiumLock) {
                    nativeClosePage(mNativePage);
                }
                mNativePage = 0;
            }

            mCloseGuard.close();
            mCurrentPage = null;
        }

        private void throwIfClosed() {
            if (mNativePage == 0) {
                throw new IllegalStateException("Already closed");
            }
        }
    }

    private static native long nativeCreate(int fd, long size);
    private static native void nativeClose(long documentPtr);
    private static native int nativeGetPageCount(long documentPtr);
    private static native boolean nativeScaleForPrinting(long documentPtr);
    private static native void nativeRenderPage(long documentPtr, long pagePtr, long bitmapHandle,
            int clipLeft, int clipTop, int clipRight, int clipBottom, long transformPtr,
            int renderMode);
    private static native long nativeOpenPageAndGetSize(long documentPtr, int pageIndex,
            Point outSize);
    private static native void nativeClosePage(long pagePtr);
}
+0 −1
Original line number Diff line number Diff line
@@ -427,7 +427,6 @@ cc_defaults {
                "jni/MovieImpl.cpp",
                "jni/pdf/PdfDocument.cpp",
                "jni/pdf/PdfEditor.cpp",
                "jni/pdf/PdfRenderer.cpp",
                "jni/pdf/PdfUtils.cpp",
            ],
            shared_libs: [
+0 −2
Original line number Diff line number Diff line
@@ -70,7 +70,6 @@ extern int register_android_graphics_fonts_Font(JNIEnv* env);
extern int register_android_graphics_fonts_FontFamily(JNIEnv* env);
extern int register_android_graphics_pdf_PdfDocument(JNIEnv* env);
extern int register_android_graphics_pdf_PdfEditor(JNIEnv* env);
extern int register_android_graphics_pdf_PdfRenderer(JNIEnv* env);
extern int register_android_graphics_text_MeasuredText(JNIEnv* env);
extern int register_android_graphics_text_LineBreaker(JNIEnv *env);
extern int register_android_graphics_text_TextShaper(JNIEnv *env);
@@ -142,7 +141,6 @@ extern int register_android_graphics_HardwareBufferRenderer(JNIEnv* env);
            REG_JNI(register_android_graphics_fonts_FontFamily),
            REG_JNI(register_android_graphics_pdf_PdfDocument),
            REG_JNI(register_android_graphics_pdf_PdfEditor),
            REG_JNI(register_android_graphics_pdf_PdfRenderer),
            REG_JNI(register_android_graphics_text_MeasuredText),
            REG_JNI(register_android_graphics_text_LineBreaker),
            REG_JNI(register_android_graphics_text_TextShaper),
Loading