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

Commit 79facd98 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Revert "Move PdfRenderer java and native code to...

Merge "Revert "Move PdfRenderer java and native code to packages/providers/MediaProvider"" into main
parents 8f456214 5b11e0b4
Loading
Loading
Loading
Loading
+18 −0
Original line number Diff line number Diff line
@@ -18053,6 +18053,24 @@ 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 {
+12 −20
Original line number Diff line number Diff line
@@ -25,9 +25,7 @@ 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;
@@ -39,12 +37,6 @@ 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;
@@ -87,7 +79,7 @@ public final class PdfEditor {
        }
        mInput = input;

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

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

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

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

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

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

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

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

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

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

    private void doClose() {
        if (mNativeDocument != 0) {
            synchronized (sPdfiumLock) {
            synchronized (PdfRenderer.sPdfiumLock) {
                nativeClose(mNativeDocument);
            }
            mNativeDocument = 0;
+502 −0
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);
}
+1 −0
Original line number Diff line number Diff line
@@ -428,6 +428,7 @@ cc_defaults {
                "jni/MovieImpl.cpp",
                "jni/pdf/PdfDocument.cpp",
                "jni/pdf/PdfEditor.cpp",
                "jni/pdf/PdfRenderer.cpp",
                "jni/pdf/PdfUtils.cpp",
            ],
            shared_libs: [
+2 −0
Original line number Diff line number Diff line
@@ -70,6 +70,7 @@ 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);
@@ -141,6 +142,7 @@ 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