[PDF Viewer] Add support for view destination with 'FitB' fitting type
Table 151 in ISO 32000-1 standard lists multiple view destination parameters. Implement 'FitB' parameter, which displays the entire bounding box of a given page. This CL creates the following flow: 1. OpenPdfParamsParser parses the view parameters by using a GetPageBoundingBoxCallback. 2. GetPageBoundingBoxCallback uses PluginController to postMessageWithReply() to the PdfViewWebPlugin. 3. PdfViewWebPlugin utilizes a new API PDFiumPage::GetBoundingBox() to get the bounding box of a given page and returns it. 4. PDF Viewer handles the URL params and uses setFittingType() to zoom and move to the bounding box. Bug: 1414864 Change-Id: I32f34df226f3cc5f69724d10726b5032471af9d6 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4371251 Reviewed-by: Demetrios Papadopoulos <dpapad@chromium.org> Reviewed-by: Lei Zhang <thestig@chromium.org> Reviewed-by: Nigi <nigi@chromium.org> Commit-Queue: Andy Phan <andyphan@chromium.org> Cr-Commit-Position: refs/heads/main@{#1126928}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
2da08cdf91
commit
fef93ce0ca
chrome
browser
resources
test
pdf
@ -29,12 +29,13 @@ export interface DocumentMetadata {
|
||||
version: string;
|
||||
}
|
||||
|
||||
/** Enumeration of page fitting types. */
|
||||
/** Enumeration of page fitting types and bounding box fitting types. */
|
||||
export enum FittingType {
|
||||
NONE = 'none',
|
||||
FIT_TO_PAGE = 'fit-to-page',
|
||||
FIT_TO_WIDTH = 'fit-to-width',
|
||||
FIT_TO_HEIGHT = 'fit-to-height',
|
||||
FIT_TO_BOUNDING_BOX = 'fit-to-bounding-box',
|
||||
}
|
||||
|
||||
export interface NamedDestinationMessageData {
|
||||
@ -58,6 +59,13 @@ export interface Point {
|
||||
y: number;
|
||||
}
|
||||
|
||||
export interface Rect {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export type ExtendedKeyEvent = KeyboardEvent&{
|
||||
fromScriptingAPI?: boolean,
|
||||
fromPlugin?: boolean,
|
||||
|
@ -5,7 +5,7 @@
|
||||
import {assert} from 'chrome://resources/js/assert_ts.js';
|
||||
import {PromiseResolver} from 'chrome://resources/js/promise_resolver.js';
|
||||
|
||||
import {NamedDestinationMessageData, SaveRequestType} from './constants.js';
|
||||
import {NamedDestinationMessageData, Rect, SaveRequestType} from './constants.js';
|
||||
import {PdfPluginElement} from './internal_plugin.js';
|
||||
import {PinchPhase, Viewport} from './viewport.js';
|
||||
|
||||
@ -322,6 +322,13 @@ export class PluginController implements ContentController {
|
||||
this.postMessage_({type: 'loadPreviewPage', url: url, index: index});
|
||||
}
|
||||
|
||||
getPageBoundingBox(page: number): Promise<Rect> {
|
||||
return this.postMessageWithReply_({
|
||||
type: 'getPageBoundingBox',
|
||||
page,
|
||||
});
|
||||
}
|
||||
|
||||
getPasswordComplete(password: string) {
|
||||
this.postMessage_({type: 'getPasswordComplete', password: password});
|
||||
}
|
||||
|
@ -3,16 +3,18 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import {assert} from 'chrome://resources/js/assert_ts.js';
|
||||
import {FittingType, NamedDestinationMessageData, Point} from './constants.js';
|
||||
|
||||
import {FittingType, NamedDestinationMessageData, Point, Rect} from './constants.js';
|
||||
import {Size} from './viewport.js';
|
||||
|
||||
export interface OpenPdfParams {
|
||||
boundingBox?: Rect;
|
||||
page?: number;
|
||||
position?: Point;
|
||||
url?: string;
|
||||
zoom?: number;
|
||||
view?: FittingType;
|
||||
viewPosition?: number;
|
||||
position?: Point;
|
||||
page?: number;
|
||||
zoom?: number;
|
||||
}
|
||||
|
||||
export enum ViewMode {
|
||||
@ -29,18 +31,26 @@ export enum ViewMode {
|
||||
type GetNamedDestinationCallback = (name: string) =>
|
||||
Promise<NamedDestinationMessageData>;
|
||||
|
||||
type GetPageBoundingBoxCallback = (page: number) => Promise<Rect>;
|
||||
|
||||
// Parses the open pdf parameters passed in the url to set initial viewport
|
||||
// settings for opening the pdf.
|
||||
export class OpenPdfParamsParser {
|
||||
private getNamedDestinationCallback_: GetNamedDestinationCallback;
|
||||
private getPageBoundingBoxCallback_: GetPageBoundingBoxCallback;
|
||||
private viewportDimensions_?: Size;
|
||||
|
||||
/**
|
||||
* @param getNamedDestinationCallback Function called to fetch information for
|
||||
* a named destination.
|
||||
* @param getPageBoundingBoxCallback Function called to fetch information for
|
||||
* a page's bounding box.
|
||||
*/
|
||||
constructor(getNamedDestinationCallback: GetNamedDestinationCallback) {
|
||||
constructor(
|
||||
getNamedDestinationCallback: GetNamedDestinationCallback,
|
||||
getPageBoundingBoxCallback: GetPageBoundingBoxCallback) {
|
||||
this.getNamedDestinationCallback_ = getNamedDestinationCallback;
|
||||
this.getPageBoundingBoxCallback_ = getPageBoundingBoxCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -96,9 +106,12 @@ export class OpenPdfParamsParser {
|
||||
* Parse view parameter of open PDF parameters. The PDF should be opened at
|
||||
* the specified fitting type mode and position.
|
||||
* @param paramValue Params to parse.
|
||||
* @param pageNumber Page number for bounding box, if there is a fit bounding
|
||||
* box param.
|
||||
* @return Map with view parameters (view and viewPosition).
|
||||
*/
|
||||
private async parseViewParam_(paramValue: string): Promise<OpenPdfParams> {
|
||||
private async parseViewParam_(paramValue: string, pageNumber: number):
|
||||
Promise<OpenPdfParams> {
|
||||
const viewModeComponents = paramValue.toLowerCase().split(',');
|
||||
if (viewModeComponents.length === 0) {
|
||||
return {};
|
||||
@ -120,6 +133,11 @@ export class OpenPdfParamsParser {
|
||||
acceptsPositionParam = true;
|
||||
break;
|
||||
case ViewMode.FIT_B:
|
||||
params['view'] = FittingType.FIT_TO_BOUNDING_BOX;
|
||||
// pageNumber is 1-indexed, but PDF Viewer is 0-indexed.
|
||||
params['boundingBox'] =
|
||||
await this.getPageBoundingBoxCallback_(pageNumber - 1);
|
||||
break;
|
||||
case ViewMode.FIT_BH:
|
||||
case ViewMode.FIT_BV:
|
||||
// Not implemented yet, do nothing.
|
||||
@ -147,10 +165,12 @@ export class OpenPdfParamsParser {
|
||||
/**
|
||||
* Parse view parameters which come from nameddest.
|
||||
* @param paramValue Params to parse.
|
||||
* @param pageNumber Page number for bounding box, if there is a fit bounding
|
||||
* box param.
|
||||
* @return Map with view parameters.
|
||||
*/
|
||||
private async parseNameddestViewParam_(paramValue: string):
|
||||
Promise<OpenPdfParams> {
|
||||
private async parseNameddestViewParam_(
|
||||
paramValue: string, pageNumber: number): Promise<OpenPdfParams> {
|
||||
const viewModeComponents = paramValue.toLowerCase().split(',');
|
||||
const viewMode = viewModeComponents[0];
|
||||
const params: OpenPdfParams = {};
|
||||
@ -197,7 +217,7 @@ export class OpenPdfParamsParser {
|
||||
return params;
|
||||
}
|
||||
|
||||
return this.parseViewParam_(paramValue);
|
||||
return this.parseViewParam_(paramValue, pageNumber);
|
||||
}
|
||||
|
||||
/** Parse the parameters encoded in the fragment of a URL. */
|
||||
@ -269,16 +289,23 @@ export class OpenPdfParamsParser {
|
||||
|
||||
const urlParams = this.parseUrlParams_(url);
|
||||
|
||||
let pageNumber;
|
||||
if (urlParams.has('page')) {
|
||||
// |pageNumber| is 1-based, but goToPage() take a zero-based page index.
|
||||
const pageNumber = parseInt(urlParams.get('page')!, 10);
|
||||
pageNumber = parseInt(urlParams.get('page')!, 10);
|
||||
if (!Number.isNaN(pageNumber) && pageNumber > 0) {
|
||||
params['page'] = pageNumber - 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!pageNumber || pageNumber < 1) {
|
||||
pageNumber = 1;
|
||||
}
|
||||
|
||||
if (urlParams.has('view')) {
|
||||
Object.assign(params, await this.parseViewParam_(urlParams.get('view')!));
|
||||
Object.assign(
|
||||
params,
|
||||
await this.parseViewParam_(urlParams.get('view')!, pageNumber!));
|
||||
}
|
||||
|
||||
if (urlParams.has('zoom')) {
|
||||
@ -291,12 +318,14 @@ export class OpenPdfParamsParser {
|
||||
|
||||
if (data.pageNumber !== -1) {
|
||||
params.page = data.pageNumber;
|
||||
pageNumber = data.pageNumber;
|
||||
}
|
||||
|
||||
if (data.namedDestinationView) {
|
||||
Object.assign(
|
||||
params,
|
||||
await this.parseNameddestViewParam_(data.namedDestinationView));
|
||||
await this.parseNameddestViewParam_(
|
||||
data.namedDestinationView, pageNumber!));
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
@ -149,11 +149,6 @@ export abstract class PdfViewerBaseElement extends PolymerElement {
|
||||
|
||||
record(UserAction.DOCUMENT_OPENED);
|
||||
|
||||
// Parse open pdf parameters.
|
||||
this.paramsParser = new OpenPdfParamsParser(destination => {
|
||||
return PluginController.getInstance().getNamedDestination(destination);
|
||||
});
|
||||
|
||||
// Create the viewport.
|
||||
const defaultZoom =
|
||||
this.browserApi!.getZoomBehavior() === ZoomBehavior.MANAGE ?
|
||||
@ -190,6 +185,16 @@ export abstract class PdfViewerBaseElement extends PolymerElement {
|
||||
pluginController.isActive = true;
|
||||
this.currentController = pluginController;
|
||||
|
||||
// Parse open pdf parameters.
|
||||
const getNamedDestinationCallback = (destination: string) => {
|
||||
return PluginController.getInstance().getNamedDestination(destination);
|
||||
};
|
||||
const getPageBoundingBoxCallback = (page: number) => {
|
||||
return PluginController.getInstance().getPageBoundingBox(page);
|
||||
};
|
||||
this.paramsParser = new OpenPdfParamsParser(
|
||||
getNamedDestinationCallback, getPageBoundingBoxCallback);
|
||||
|
||||
this.tracker.add(
|
||||
pluginController.getEventTarget(),
|
||||
PluginControllerEventType.PLUGIN_MESSAGE,
|
||||
@ -432,14 +437,22 @@ export abstract class PdfViewerBaseElement extends PolymerElement {
|
||||
|
||||
if (params.position) {
|
||||
this.viewport_.goToPageAndXy(
|
||||
params.page ? params.page : 0, params.position.x, params.position.y);
|
||||
params.page || 0, params.position.x, params.position.y);
|
||||
} else if (params.page) {
|
||||
this.viewport_.goToPage(params.page);
|
||||
}
|
||||
|
||||
if (params.view) {
|
||||
this.isUserInitiatedEvent = false;
|
||||
this.viewport_.setFittingType(params.view);
|
||||
let fittingTypeParams;
|
||||
if (params.view === FittingType.FIT_TO_BOUNDING_BOX) {
|
||||
assert(params.boundingBox);
|
||||
fittingTypeParams = {
|
||||
page: params.page || 0,
|
||||
boundingBox: params.boundingBox,
|
||||
};
|
||||
}
|
||||
this.viewport_.setFittingType(params.view, fittingTypeParams);
|
||||
this.forceFit(params.view);
|
||||
if (params.viewPosition) {
|
||||
const zoomedPositionShift =
|
||||
|
@ -9,7 +9,7 @@ export {AnnotationTool} from './annotation_tool.js';
|
||||
// </if>
|
||||
export {Bookmark} from './bookmark_type.js';
|
||||
export {BrowserApi, ZoomBehavior} from './browser_api.js';
|
||||
export {FittingType, Point, SaveRequestType} from './constants.js';
|
||||
export {FittingType, Point, Rect, SaveRequestType} from './constants.js';
|
||||
export {PluginController} from './controller.js';
|
||||
export {ChangePageAndXyDetail, ChangePageDetail, ChangePageOrigin, ChangeZoomDetail, NavigateDetail, ViewerBookmarkElement} from './elements/viewer-bookmark.js';
|
||||
export {ViewerDocumentOutlineElement} from './elements/viewer-document-outline.js';
|
||||
|
@ -8,7 +8,7 @@ import {EventTracker} from 'chrome://resources/js/event_tracker.js';
|
||||
import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
|
||||
import {hasKeyModifiers, isRTL} from 'chrome://resources/js/util_ts.js';
|
||||
|
||||
import {ExtendedKeyEvent, FittingType, Point} from './constants.js';
|
||||
import {ExtendedKeyEvent, FittingType, Point, Rect} from './constants.js';
|
||||
import {Gesture, GestureDetector, PinchEventDetail} from './gesture_detector.js';
|
||||
import {PdfPluginElement} from './internal_plugin.js';
|
||||
import {SwipeDetector, SwipeDirection} from './swipe_detector.js';
|
||||
@ -40,6 +40,11 @@ export interface Size {
|
||||
height: number;
|
||||
}
|
||||
|
||||
interface FittingTypeParams {
|
||||
page: number;
|
||||
boundingBox: Rect;
|
||||
}
|
||||
|
||||
/** @return The area of the intersection of the rects */
|
||||
function getIntersectionArea(rect1: ViewportRect, rect2: ViewportRect): number {
|
||||
const left = Math.max(rect1.x, rect2.x);
|
||||
@ -604,6 +609,11 @@ export class Viewport implements ViewportInterface {
|
||||
* Save the current zoom and fitting type.
|
||||
*/
|
||||
saveZoomState() {
|
||||
// Fitting to bounding box does not need to be saved, so set the fitting
|
||||
// type to none.
|
||||
if (this.fittingType_ === FittingType.FIT_TO_BOUNDING_BOX) {
|
||||
this.setFittingType(FittingType.NONE);
|
||||
}
|
||||
this.savedZoom_ = this.internalZoom_;
|
||||
this.savedFittingType_ = this.fittingType_;
|
||||
}
|
||||
@ -901,7 +911,11 @@ export class Viewport implements ViewportInterface {
|
||||
return Math.max(zoom, 0);
|
||||
}
|
||||
|
||||
setFittingType(fittingType: FittingType) {
|
||||
/**
|
||||
* Set the fitting type and fit within the viewport accordingly.
|
||||
* @param params Params required for fitting to the bounding box.
|
||||
*/
|
||||
setFittingType(fittingType: FittingType, params?: FittingTypeParams) {
|
||||
switch (fittingType) {
|
||||
case FittingType.FIT_TO_PAGE:
|
||||
this.fitToPage();
|
||||
@ -912,6 +926,10 @@ export class Viewport implements ViewportInterface {
|
||||
case FittingType.FIT_TO_HEIGHT:
|
||||
this.fitToHeight();
|
||||
return;
|
||||
case FittingType.FIT_TO_BOUNDING_BOX:
|
||||
assert(params);
|
||||
this.fitToBoundingBox_(params.page, params.boundingBox);
|
||||
return;
|
||||
case FittingType.NONE:
|
||||
this.fittingType_ = fittingType;
|
||||
return;
|
||||
@ -1021,6 +1039,56 @@ export class Viewport implements ViewportInterface {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Zoom the viewport so that the bounding box of a page consumes the entire
|
||||
* viewport.
|
||||
* @param page The page to display.
|
||||
* @param boundingBox The bounding box to fit to.
|
||||
*/
|
||||
private fitToBoundingBox_(page: number, boundingBox: Rect) {
|
||||
// Ignore invalid bounding boxes, which can occur if the plugin fails to
|
||||
// give a valid box.
|
||||
if (!boundingBox.width || !boundingBox.height) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.fittingType_ = FittingType.FIT_TO_BOUNDING_BOX;
|
||||
|
||||
// Use the smallest zoom that fits the full bounding box on screen.
|
||||
const boundingBoxSize = {
|
||||
width: boundingBox.width,
|
||||
height: boundingBox.height,
|
||||
};
|
||||
|
||||
const zoomFitToWidth =
|
||||
this.computeFittingZoom_(boundingBoxSize, true, false);
|
||||
const zoomFitToHeight =
|
||||
this.computeFittingZoom_(boundingBoxSize, false, true);
|
||||
const newZoom = this.clampZoom_(Math.min(zoomFitToWidth, zoomFitToHeight));
|
||||
this.mightZoom_(() => {
|
||||
this.setZoomInternal_(newZoom);
|
||||
});
|
||||
|
||||
// Calculate the position.
|
||||
const pageInsetDimensions = this.getPageInsetDimensions(page);
|
||||
const viewportSize = this.size;
|
||||
const screenPosition: Point = {
|
||||
x: pageInsetDimensions.x + boundingBox.x,
|
||||
y: pageInsetDimensions.y + boundingBox.y,
|
||||
};
|
||||
// Center the bounding box in the dimension that isn't fully zoomed in.
|
||||
if (newZoom !== zoomFitToWidth) {
|
||||
screenPosition.x -=
|
||||
((viewportSize.width / newZoom) - boundingBox.width) / 2;
|
||||
}
|
||||
if (newZoom !== zoomFitToHeight) {
|
||||
screenPosition.y -=
|
||||
((viewportSize.height / newZoom) - boundingBox.height) / 2;
|
||||
}
|
||||
this.setPosition(
|
||||
{x: screenPosition.x * newZoom, y: screenPosition.y * newZoom});
|
||||
}
|
||||
|
||||
/** Zoom out to the next predefined zoom level. */
|
||||
zoomOut() {
|
||||
this.mightZoom_(() => {
|
||||
@ -1361,6 +1429,7 @@ export class Viewport implements ViewportInterface {
|
||||
*/
|
||||
handleNavigateToDestination(
|
||||
page: number, x: number|undefined, y: number|undefined, zoom: number) {
|
||||
// TODO(crbug.com/1430193): Handle view parameters and fitting types.
|
||||
if (zoom) {
|
||||
this.setZoom(zoom);
|
||||
}
|
||||
|
@ -106,10 +106,15 @@ async function doNavigationUrlTests(
|
||||
const viewport = getZoomableViewport(mockWindow, mockSizer, 0, 1);
|
||||
viewport.setViewportChangedCallback(mockViewportChangedCallback.callback);
|
||||
|
||||
const paramsParser = new OpenPdfParamsParser(function(_name) {
|
||||
const getNamedDestinationCallback = function(_name: string) {
|
||||
return Promise.resolve(
|
||||
{messageId: 'getNamedDestination_1', pageNumber: -1});
|
||||
});
|
||||
};
|
||||
const getPageBoundingBoxCallback = function(_page: number) {
|
||||
return Promise.resolve({x: -1, y: -1, width: -1, height: -1});
|
||||
};
|
||||
const paramsParser = new OpenPdfParamsParser(
|
||||
getNamedDestinationCallback, getPageBoundingBoxCallback);
|
||||
|
||||
const navigatorDelegate = new MockNavigatorDelegate();
|
||||
const navigator =
|
||||
@ -138,7 +143,7 @@ chrome.test.runTests([
|
||||
const viewport = getZoomableViewport(mockWindow, mockSizer, 0, 1);
|
||||
viewport.setViewportChangedCallback(mockCallback.callback);
|
||||
|
||||
const paramsParser = new OpenPdfParamsParser(function(destination) {
|
||||
const getNamedDestinationCallback = function(destination: string) {
|
||||
if (destination === 'US') {
|
||||
return Promise.resolve(
|
||||
{messageId: 'getNamedDestination_1', pageNumber: 0});
|
||||
@ -149,7 +154,12 @@ chrome.test.runTests([
|
||||
return Promise.resolve(
|
||||
{messageId: 'getNamedDestination_3', pageNumber: -1});
|
||||
}
|
||||
});
|
||||
};
|
||||
const getPageBoundingBoxCallback = function(_page: number) {
|
||||
return Promise.resolve({x: -1, y: -1, width: -1, height: -1});
|
||||
};
|
||||
const paramsParser = new OpenPdfParamsParser(
|
||||
getNamedDestinationCallback, getPageBoundingBoxCallback);
|
||||
const url = 'http://xyz.pdf';
|
||||
|
||||
const navigatorDelegate = new MockNavigatorDelegate();
|
||||
|
@ -7,6 +7,9 @@ import {FittingType, OpenPdfParamsParser, ViewMode} from 'chrome-extension://mhj
|
||||
const URL = 'http://xyz.pdf';
|
||||
|
||||
function getParamsParser(): OpenPdfParamsParser {
|
||||
const getPageBoundingBoxCallback = function(_page: number) {
|
||||
return Promise.resolve({x: 10, y: 15, width: 200, height: 300});
|
||||
};
|
||||
const paramsParser = new OpenPdfParamsParser(function(destination: string) {
|
||||
// Set the dummy viewport dimensions for calculating the zoom level for
|
||||
// view destination with 'FitR' type.
|
||||
@ -89,7 +92,7 @@ function getParamsParser(): OpenPdfParamsParser {
|
||||
}
|
||||
return Promise.resolve(
|
||||
{messageId: 'getNamedDestination_13', pageNumber: -1});
|
||||
});
|
||||
}, getPageBoundingBoxCallback);
|
||||
return paramsParser;
|
||||
}
|
||||
|
||||
@ -381,6 +384,21 @@ chrome.test.runTests([
|
||||
chrome.test.assertFalse(paramsParser.shouldShowSidenav(`${URL}`, true));
|
||||
chrome.test.assertTrue(paramsParser.shouldShowSidenav(`${URL}`, false));
|
||||
|
||||
chrome.test.succeed();
|
||||
},
|
||||
async function testParamsViewFitB() {
|
||||
const paramsParser = getParamsParser();
|
||||
|
||||
// Checking #view=FitB.
|
||||
const params =
|
||||
await paramsParser.getViewportFromUrlParams(`${URL}#view=FitB`);
|
||||
chrome.test.assertEq(FittingType.FIT_TO_BOUNDING_BOX, params.view);
|
||||
chrome.test.assertTrue(params.boundingBox !== undefined);
|
||||
chrome.test.assertEq(10, params.boundingBox.x);
|
||||
chrome.test.assertEq(15, params.boundingBox.y);
|
||||
chrome.test.assertEq(200, params.boundingBox.width);
|
||||
chrome.test.assertEq(300, params.boundingBox.height);
|
||||
|
||||
chrome.test.succeed();
|
||||
},
|
||||
]);
|
||||
|
@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import {FittingType, PAGE_SHADOW, SwipeDirection, Viewport} from 'chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai/pdf_viewer_wrapper.js';
|
||||
import {FittingType, PAGE_SHADOW, Point, Rect, SwipeDirection, Viewport} from 'chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai/pdf_viewer_wrapper.js';
|
||||
import {isMac} from 'chrome://resources/js/platform.js';
|
||||
|
||||
import {createMockUnseasonedPdfPluginForTest, getZoomableViewport, MockDocumentDimensions, MockElement, MockSizer, MockUnseasonedPdfPluginElement, MockViewportChangedCallback} from './test_util.js';
|
||||
@ -331,6 +331,14 @@ const tests = [
|
||||
viewport.setFittingType(FittingType.FIT_TO_HEIGHT);
|
||||
chrome.test.assertEq(FittingType.FIT_TO_HEIGHT, viewport.fittingType);
|
||||
|
||||
const documentDimensions = new MockDocumentDimensions();
|
||||
documentDimensions.addPage(0, 0);
|
||||
viewport.setDocumentDimensions(documentDimensions);
|
||||
|
||||
const params = {page: 0, boundingBox: {x: 0, y: 0, width: 1, height: 1}};
|
||||
viewport.setFittingType(FittingType.FIT_TO_BOUNDING_BOX, params);
|
||||
chrome.test.assertEq(FittingType.FIT_TO_BOUNDING_BOX, viewport.fittingType);
|
||||
|
||||
viewport.setFittingType(FittingType.NONE);
|
||||
chrome.test.assertEq(FittingType.NONE, viewport.fittingType);
|
||||
|
||||
@ -622,6 +630,71 @@ const tests = [
|
||||
chrome.test.succeed();
|
||||
},
|
||||
|
||||
function testFitToBoundingBox() {
|
||||
const mockWindow = new MockElement(100, 100, null);
|
||||
const mockSizer = new MockSizer();
|
||||
const mockCallback = new MockViewportChangedCallback();
|
||||
const viewport = getZoomableViewport(mockWindow, mockSizer, 0, 1);
|
||||
viewport.setViewportChangedCallback(mockCallback.callback);
|
||||
const documentDimensions = new MockDocumentDimensions();
|
||||
documentDimensions.addPage(50, 50);
|
||||
documentDimensions.addPage(50, 100);
|
||||
documentDimensions.addPage(100, 50);
|
||||
documentDimensions.addPage(100, 100);
|
||||
documentDimensions.addPage(200, 200);
|
||||
viewport.setDocumentDimensions(documentDimensions);
|
||||
|
||||
function assertPositionAndZoom(
|
||||
expectedPosition: Point, expectedZoom: number) {
|
||||
chrome.test.assertEq(
|
||||
FittingType.FIT_TO_BOUNDING_BOX, viewport.fittingType);
|
||||
chrome.test.assertTrue(mockCallback.wasCalled);
|
||||
chrome.test.assertEq(expectedPosition, viewport.position);
|
||||
chrome.test.assertEq(expectedZoom, viewport.getZoom());
|
||||
}
|
||||
|
||||
function testForVisibleBoundingBox(
|
||||
page: number, boundingBox: Rect, expectedX: number, expectedY: number,
|
||||
expectedZoom: number) {
|
||||
viewport.setZoom(0.1);
|
||||
mockCallback.reset();
|
||||
viewport.setFittingType(
|
||||
FittingType.FIT_TO_BOUNDING_BOX, {page, boundingBox});
|
||||
assertPositionAndZoom({x: expectedX, y: expectedY}, expectedZoom);
|
||||
}
|
||||
|
||||
// Bounding box is smaller than window size and square.
|
||||
let boundingBox: Rect = {x: 25, y: 25, width: 50, height: 50};
|
||||
testForVisibleBoundingBox(0, boundingBox, 60, 56, 2);
|
||||
testForVisibleBoundingBox(1, boundingBox, 60, 156, 2);
|
||||
testForVisibleBoundingBox(2, boundingBox, 60, 356, 2);
|
||||
testForVisibleBoundingBox(3, boundingBox, 60, 456, 2);
|
||||
testForVisibleBoundingBox(4, boundingBox, 60, 656, 2);
|
||||
|
||||
// Bounding box is smaller than window size with larger width.
|
||||
boundingBox = {x: 20, y: 25, width: 80, height: 50};
|
||||
testForVisibleBoundingBox(2, boundingBox, 31.25, 203.75, 1.25);
|
||||
testForVisibleBoundingBox(3, boundingBox, 31.25, 266.25, 1.25);
|
||||
testForVisibleBoundingBox(4, boundingBox, 31.25, 391.25, 1.25);
|
||||
|
||||
// Bounding box is smaller than window size with larger height.
|
||||
boundingBox = {x: 25, y: 20, width: 50, height: 80};
|
||||
testForVisibleBoundingBox(1, boundingBox, 18.75, 91.25, 1.25);
|
||||
testForVisibleBoundingBox(3, boundingBox, 18.75, 278.75, 1.25);
|
||||
testForVisibleBoundingBox(4, boundingBox, 18.75, 403.75, 1.25);
|
||||
|
||||
// Bounding box is the same size as window size.
|
||||
boundingBox = {x: 0, y: 0, width: 100, height: 100};
|
||||
testForVisibleBoundingBox(3, boundingBox, 5, 203, 1);
|
||||
testForVisibleBoundingBox(4, boundingBox, 5, 303, 1);
|
||||
|
||||
// Bounding box is larger than window size.
|
||||
boundingBox = {x: 10, y: 20, width: 150, height: 150};
|
||||
testForVisibleBoundingBox(
|
||||
4, boundingBox, 10, 215.33333333333331, 0.6666666666666666);
|
||||
|
||||
chrome.test.succeed();
|
||||
},
|
||||
async function testPinchZoomInWithGestureEvent() {
|
||||
const mockWindow = new MockElement(100, 100, null);
|
||||
const viewport = getZoomableViewport(mockWindow, new MockSizer(), 0, 1);
|
||||
@ -1694,6 +1767,12 @@ const tests = [
|
||||
chrome.test.assertEq(0, viewport.position.y);
|
||||
chrome.test.succeed();
|
||||
},
|
||||
|
||||
// TODO(crbug.com/1430193): Currently, fit types 'FIT_TO_PAGE',
|
||||
// 'FIT_TO_WIDTH', 'FIT_TO_HEIGHT', and 'FIT_TO_BOUNDING_BOX` do not correctly
|
||||
// navigate to a destination with the correct position and zoom level. Add
|
||||
// checks for position and zoom level for these fit types once fully
|
||||
// supported.
|
||||
];
|
||||
|
||||
chrome.test.runTests(tests);
|
||||
|
@ -373,6 +373,9 @@ class PDFEngine {
|
||||
// Returns a page's rect in screen coordinates, as well as its surrounding
|
||||
// border areas and bottom separator.
|
||||
virtual gfx::Rect GetPageScreenRect(int page_index) const = 0;
|
||||
// Return a page's bounding box rectangle, or an empty rectangle if
|
||||
// `page_index` is invalid.
|
||||
virtual gfx::RectF GetPageBoundingBox(int page_index) = 0;
|
||||
// Set color / grayscale rendering modes.
|
||||
virtual void SetGrayscale(bool grayscale) = 0;
|
||||
// Get the number of characters on a given page.
|
||||
|
@ -68,6 +68,7 @@
|
||||
#include "pdf/ui/file_name.h"
|
||||
#include "pdf/ui/thumbnail.h"
|
||||
#include "printing/metafile_skia.h"
|
||||
#include "printing/units.h"
|
||||
#include "services/network/public/mojom/referrer_policy.mojom-shared.h"
|
||||
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
|
||||
#include "third_party/blink/public/common/input/web_coalesced_input_event.h"
|
||||
@ -1295,6 +1296,8 @@ void PdfViewWebPlugin::OnMessage(const base::Value::Dict& message) {
|
||||
&PdfViewWebPlugin::HandleDisplayAnnotationsMessage},
|
||||
{"getNamedDestination",
|
||||
&PdfViewWebPlugin::HandleGetNamedDestinationMessage},
|
||||
{"getPageBoundingBox",
|
||||
&PdfViewWebPlugin::HandleGetPageBoundingBoxMessage},
|
||||
{"getPasswordComplete",
|
||||
&PdfViewWebPlugin::HandleGetPasswordCompleteMessage},
|
||||
{"getSelectedText", &PdfViewWebPlugin::HandleGetSelectedTextMessage},
|
||||
@ -1356,6 +1359,26 @@ void PdfViewWebPlugin::HandleGetNamedDestinationMessage(
|
||||
client_->PostMessage(std::move(reply));
|
||||
}
|
||||
|
||||
void PdfViewWebPlugin::HandleGetPageBoundingBoxMessage(
|
||||
const base::Value::Dict& message) {
|
||||
const int page_index = message.FindInt("page").value();
|
||||
base::Value::Dict reply =
|
||||
PrepareReplyMessage("getPageBoundingBoxReply", message);
|
||||
|
||||
gfx::RectF bounding_box = engine_->GetPageBoundingBox(page_index);
|
||||
gfx::Rect page_bounds = engine_->GetPageBoundsRect(page_index);
|
||||
|
||||
// Flip the origin from bottom-left to top-left.
|
||||
bounding_box.set_y(static_cast<float>(page_bounds.height()) -
|
||||
bounding_box.bottom());
|
||||
reply.Set("x", bounding_box.x());
|
||||
reply.Set("y", bounding_box.y());
|
||||
reply.Set("width", bounding_box.width());
|
||||
reply.Set("height", bounding_box.height());
|
||||
|
||||
client_->PostMessage(std::move(reply));
|
||||
}
|
||||
|
||||
void PdfViewWebPlugin::HandleGetPasswordCompleteMessage(
|
||||
const base::Value::Dict& message) {
|
||||
DCHECK(password_callback_);
|
||||
|
@ -461,6 +461,7 @@ class PdfViewWebPlugin final : public PDFEngine::Client,
|
||||
// Message handlers.
|
||||
void HandleDisplayAnnotationsMessage(const base::Value::Dict& message);
|
||||
void HandleGetNamedDestinationMessage(const base::Value::Dict& message);
|
||||
void HandleGetPageBoundingBoxMessage(const base::Value::Dict& message);
|
||||
void HandleGetPasswordCompleteMessage(const base::Value::Dict& message);
|
||||
void HandleGetSelectedTextMessage(const base::Value::Dict& message);
|
||||
void HandleGetThumbnailMessage(const base::Value::Dict& message);
|
||||
|
@ -3467,6 +3467,14 @@ gfx::Rect PDFiumEngine::GetScreenRect(const gfx::Rect& rect) const {
|
||||
return draw_utils::GetScreenRect(rect, position_, current_zoom_);
|
||||
}
|
||||
|
||||
gfx::RectF PDFiumEngine::GetPageBoundingBox(int page_index) {
|
||||
PDFiumPage* page = GetPage(page_index);
|
||||
if (!page) {
|
||||
return gfx::RectF();
|
||||
}
|
||||
return page->GetBoundingBox();
|
||||
}
|
||||
|
||||
void PDFiumEngine::Highlight(void* buffer,
|
||||
int stride,
|
||||
const gfx::Rect& rect,
|
||||
|
@ -37,6 +37,7 @@
|
||||
#include "third_party/skia/include/core/SkBitmap.h"
|
||||
#include "ui/gfx/geometry/point.h"
|
||||
#include "ui/gfx/geometry/rect.h"
|
||||
#include "ui/gfx/geometry/rect_f.h"
|
||||
#include "ui/gfx/geometry/size.h"
|
||||
#include "ui/gfx/geometry/vector2d.h"
|
||||
|
||||
@ -143,6 +144,7 @@ class PDFiumEngine : public PDFEngine,
|
||||
gfx::Rect GetPageBoundsRect(int index) override;
|
||||
gfx::Rect GetPageContentsRect(int index) override;
|
||||
gfx::Rect GetPageScreenRect(int page_index) const override;
|
||||
gfx::RectF GetPageBoundingBox(int page_index) override;
|
||||
void SetGrayscale(bool grayscale) override;
|
||||
int GetCharCount(int page_index) override;
|
||||
gfx::RectF GetCharBounds(int page_index, int char_index) override;
|
||||
|
@ -31,6 +31,8 @@
|
||||
#include "third_party/pdfium/public/cpp/fpdf_scopers.h"
|
||||
#include "third_party/pdfium/public/fpdf_annot.h"
|
||||
#include "third_party/pdfium/public/fpdf_catalog.h"
|
||||
#include "third_party/pdfium/public/fpdf_edit.h"
|
||||
#include "third_party/pdfium/public/fpdfview.h"
|
||||
#include "third_party/skia/include/core/SkImageInfo.h"
|
||||
#include "third_party/skia/include/core/SkPixmap.h"
|
||||
#include "ui/accessibility/accessibility_features.h"
|
||||
@ -57,6 +59,17 @@ constexpr float k180DegreesInRadians = base::kPiFloat;
|
||||
constexpr float k270DegreesInRadians = 3 * base::kPiFloat / 2;
|
||||
constexpr float k360DegreesInRadians = 2 * base::kPiFloat;
|
||||
|
||||
constexpr float kPointsToPixels = static_cast<float>(printing::kPixelsPerInch) /
|
||||
static_cast<float>(printing::kPointsPerInch);
|
||||
|
||||
// Page rotations in clockwise degrees.
|
||||
enum class Rotation {
|
||||
kRotate0 = 0,
|
||||
kRotate90 = 1,
|
||||
kRotate180 = 2,
|
||||
kRotate270 = 3,
|
||||
};
|
||||
|
||||
gfx::RectF FloatPageRectToPixelRect(FPDF_PAGE page, const gfx::RectF& input) {
|
||||
int output_width = FPDF_GetPageWidthF(page);
|
||||
int output_height = FPDF_GetPageHeightF(page);
|
||||
@ -279,6 +292,80 @@ bool AreTextStyleEqual(FPDF_TEXTPAGE text_page,
|
||||
char_style.is_bold == style.is_bold;
|
||||
}
|
||||
|
||||
// Returns the bounds with the smallest left, smallest bottom, largest right,
|
||||
// and largest top.
|
||||
FS_RECTF GetLargestBounds(const FS_RECTF& largest_bounds,
|
||||
const FS_RECTF& bounds) {
|
||||
return {std::min(largest_bounds.left, bounds.left),
|
||||
std::max(largest_bounds.top, bounds.top),
|
||||
std::max(largest_bounds.right, bounds.right),
|
||||
std::min(largest_bounds.bottom, bounds.bottom)};
|
||||
}
|
||||
|
||||
gfx::RectF GetRotatedRectF(Rotation rotation,
|
||||
gfx::SizeF page_size,
|
||||
const FS_RECTF& original_bounds) {
|
||||
FS_RECTF bounds;
|
||||
|
||||
// When the page is rotated 90 degrees or 270 degrees, the page width and
|
||||
// height are swapped. Swap it back for calculations.
|
||||
if (rotation == Rotation::kRotate90 || rotation == Rotation::kRotate270) {
|
||||
page_size.Transpose();
|
||||
}
|
||||
|
||||
switch (rotation) {
|
||||
case Rotation::kRotate0: {
|
||||
bounds = original_bounds;
|
||||
break;
|
||||
}
|
||||
case Rotation::kRotate90: {
|
||||
bounds.left = original_bounds.bottom;
|
||||
bounds.top = page_size.width() - original_bounds.left;
|
||||
bounds.right = original_bounds.top;
|
||||
bounds.bottom = page_size.width() - original_bounds.right;
|
||||
break;
|
||||
}
|
||||
case Rotation::kRotate180: {
|
||||
bounds.left = page_size.width() - original_bounds.right;
|
||||
bounds.top = page_size.height() - original_bounds.bottom;
|
||||
bounds.right = page_size.width() - original_bounds.left;
|
||||
bounds.bottom = page_size.height() - original_bounds.top;
|
||||
break;
|
||||
}
|
||||
case Rotation::kRotate270: {
|
||||
bounds.left = page_size.height() - original_bounds.top;
|
||||
bounds.top = original_bounds.right;
|
||||
bounds.right = page_size.height() - original_bounds.bottom;
|
||||
bounds.bottom = original_bounds.left;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return gfx::RectF(bounds.left, bounds.bottom, bounds.right - bounds.left,
|
||||
bounds.top - bounds.bottom);
|
||||
}
|
||||
|
||||
// Get the effective crop box. If empty or failed to calculate the effective
|
||||
// crop box, default to a `gfx::RectF` with dimensions page width by page
|
||||
// height.
|
||||
gfx::RectF GetEffectiveCropBox(FPDF_PAGE page,
|
||||
Rotation rotation,
|
||||
const gfx::SizeF& page_size) {
|
||||
gfx::RectF effective_crop_box;
|
||||
FS_RECTF effective_crop_bounds;
|
||||
if (FPDF_GetPageBoundingBox(page, &effective_crop_bounds)) {
|
||||
effective_crop_box =
|
||||
GetRotatedRectF(rotation, page_size, effective_crop_bounds);
|
||||
}
|
||||
|
||||
if (effective_crop_box.IsEmpty()) {
|
||||
effective_crop_box =
|
||||
gfx::RectF(0, 0, page_size.width(), page_size.height());
|
||||
}
|
||||
|
||||
return effective_crop_box;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
PDFiumPage::LinkTarget::LinkTarget() : page(-1) {}
|
||||
@ -562,6 +649,69 @@ gfx::RectF PDFiumPage::GetCroppedRect() {
|
||||
return FloatPageRectToPixelRect(page, rect);
|
||||
}
|
||||
|
||||
gfx::RectF PDFiumPage::GetBoundingBox() {
|
||||
FPDF_PAGE page = GetPage();
|
||||
if (!page) {
|
||||
return gfx::RectF();
|
||||
}
|
||||
|
||||
// Page width and height are already swapped based on page rotation.
|
||||
gfx::SizeF page_size(FPDF_GetPageWidthF(page), FPDF_GetPageHeightF(page));
|
||||
Rotation rotation = static_cast<Rotation>(FPDFPage_GetRotation(page));
|
||||
|
||||
// Start with bounds with the left and bottom values at the max possible
|
||||
// bounds and the right and top values at the min possible bounds. Bounds are
|
||||
// relative to the media box.
|
||||
FS_RECTF largest_bounds = {page_size.width(), 0, 0, page_size.height()};
|
||||
for (int i = 0; i < FPDFPage_CountObjects(page); ++i) {
|
||||
FPDF_PAGEOBJECT page_object = FPDFPage_GetObject(page, i);
|
||||
if (!page_object) {
|
||||
continue;
|
||||
}
|
||||
|
||||
FS_RECTF bounds;
|
||||
if (FPDFPageObj_GetBounds(page_object, &bounds.left, &bounds.bottom,
|
||||
&bounds.right, &bounds.top)) {
|
||||
largest_bounds = GetLargestBounds(largest_bounds, bounds);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < FPDFPage_GetAnnotCount(page); ++i) {
|
||||
ScopedFPDFAnnotation annotation(FPDFPage_GetAnnot(page, i));
|
||||
if (!annotation) {
|
||||
continue;
|
||||
}
|
||||
|
||||
FS_RECTF bounds;
|
||||
if (FPDFAnnot_GetRect(annotation.get(), &bounds)) {
|
||||
largest_bounds = GetLargestBounds(largest_bounds, bounds);
|
||||
}
|
||||
}
|
||||
|
||||
gfx::RectF bounding_box =
|
||||
GetRotatedRectF(rotation, page_size, largest_bounds);
|
||||
|
||||
gfx::RectF effective_crop_box =
|
||||
GetEffectiveCropBox(page, rotation, page_size);
|
||||
|
||||
// If the bounding box is empty, default to the effective crop box.
|
||||
if (bounding_box.IsEmpty()) {
|
||||
bounding_box = effective_crop_box;
|
||||
} else {
|
||||
// Some bounding boxes may be out-of-bounds of `effective_crop_box`. Clip to
|
||||
// be within `effective_crop_box`.
|
||||
bounding_box.Intersect(effective_crop_box);
|
||||
}
|
||||
|
||||
// Set the bounding box to be relative to the effective crop box.
|
||||
bounding_box.set_x(bounding_box.x() - effective_crop_box.x());
|
||||
bounding_box.set_y(bounding_box.y() - effective_crop_box.y());
|
||||
|
||||
// Scale to page pixels.
|
||||
bounding_box.Scale(kPointsToPixels);
|
||||
|
||||
return bounding_box;
|
||||
}
|
||||
|
||||
bool PDFiumPage::IsCharInPageBounds(int char_index,
|
||||
const gfx::RectF& page_bounds) {
|
||||
gfx::RectF char_bounds = GetCharBounds(char_index);
|
||||
|
@ -69,6 +69,12 @@ class PDFiumPage {
|
||||
// Get the bounds of the page with the crop box applied, in page pixels.
|
||||
gfx::RectF GetCroppedRect();
|
||||
|
||||
// Get the bounding box of the page in page pixels. The bounding box is the
|
||||
// largest rectangle containing all visible content in the effective crop box.
|
||||
// If the bounding box can't be calculated, returns the effective crop box.
|
||||
// The resulting bounding box is relative to the effective crop box.
|
||||
gfx::RectF GetBoundingBox();
|
||||
|
||||
// Returns if the character at `char_index` is within `page_bounds`.
|
||||
bool IsCharInPageBounds(int char_index, const gfx::RectF& page_bounds);
|
||||
|
||||
|
@ -137,6 +137,152 @@ TEST_P(PDFiumPageTest, IsCharInPageBounds) {
|
||||
EXPECT_FALSE(page.IsCharInPageBounds(29, page_bounds));
|
||||
}
|
||||
|
||||
TEST_P(PDFiumPageTest, GetBoundingBoxRotatedMultipage) {
|
||||
// Check getting bounding box for multiple rotated pages.
|
||||
TestClient client;
|
||||
std::unique_ptr<PDFiumEngine> engine =
|
||||
InitializeEngine(&client, FILE_PATH_LITERAL("rotated_multi_page.pdf"));
|
||||
ASSERT_TRUE(engine);
|
||||
ASSERT_EQ(4, engine->GetNumberOfPages());
|
||||
|
||||
// Rotation 0 degrees clockwise.
|
||||
{
|
||||
PDFiumPage& page = GetPDFiumPageForTest(*engine, 0);
|
||||
const gfx::RectF bounding_box = page.GetBoundingBox();
|
||||
EXPECT_FLOAT_EQ(0.0f, bounding_box.x());
|
||||
EXPECT_FLOAT_EQ(266.66669f, bounding_box.y());
|
||||
EXPECT_FLOAT_EQ(133.33334f, bounding_box.width());
|
||||
EXPECT_FLOAT_EQ(400.0f, bounding_box.height());
|
||||
}
|
||||
|
||||
// Rotation 90 degrees clockwise.
|
||||
{
|
||||
PDFiumPage& page = GetPDFiumPageForTest(*engine, 1);
|
||||
const gfx::RectF bounding_box = page.GetBoundingBox();
|
||||
EXPECT_FLOAT_EQ(266.66669f, bounding_box.x());
|
||||
EXPECT_FLOAT_EQ(666.66669f, bounding_box.y());
|
||||
EXPECT_FLOAT_EQ(400.0f, bounding_box.width());
|
||||
EXPECT_FLOAT_EQ(133.33334f, bounding_box.height());
|
||||
}
|
||||
// Rotation 180 degrees clockwise.
|
||||
{
|
||||
PDFiumPage& page = GetPDFiumPageForTest(*engine, 2);
|
||||
const gfx::RectF bounding_box = page.GetBoundingBox();
|
||||
EXPECT_FLOAT_EQ(666.66669f, bounding_box.x());
|
||||
EXPECT_FLOAT_EQ(933.33337f, bounding_box.y());
|
||||
EXPECT_FLOAT_EQ(133.33334f, bounding_box.width());
|
||||
EXPECT_FLOAT_EQ(400.0f, bounding_box.height());
|
||||
}
|
||||
// Rotation 270 degrees clockwise.
|
||||
{
|
||||
PDFiumPage& page = GetPDFiumPageForTest(*engine, 3);
|
||||
const gfx::RectF bounding_box = page.GetBoundingBox();
|
||||
EXPECT_FLOAT_EQ(933.33337f, bounding_box.x());
|
||||
EXPECT_FLOAT_EQ(0.0f, bounding_box.y());
|
||||
EXPECT_FLOAT_EQ(400.0f, bounding_box.width());
|
||||
EXPECT_FLOAT_EQ(133.33334f, bounding_box.height());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(PDFiumPageTest, GetBoundingBoxAnnotations) {
|
||||
// Check getting the bounding box for annotations.
|
||||
TestClient client;
|
||||
std::unique_ptr<PDFiumEngine> engine =
|
||||
InitializeEngine(&client, FILE_PATH_LITERAL("annots.pdf"));
|
||||
ASSERT_TRUE(engine);
|
||||
ASSERT_EQ(1, engine->GetNumberOfPages());
|
||||
|
||||
PDFiumPage& page = GetPDFiumPageForTest(*engine, 0);
|
||||
const gfx::RectF bounding_box = page.GetBoundingBox();
|
||||
EXPECT_FLOAT_EQ(92.0f, bounding_box.x());
|
||||
EXPECT_FLOAT_EQ(450.66669, bounding_box.y());
|
||||
EXPECT_FLOAT_EQ(201.33334f, bounding_box.width());
|
||||
EXPECT_FLOAT_EQ(469.33334f, bounding_box.height());
|
||||
}
|
||||
|
||||
TEST_P(PDFiumPageTest, GetBoundingBoxBlankPage) {
|
||||
// Check getting the bounding box for a blank page. The bounding box should be
|
||||
// the crop box scaled to page pixels.
|
||||
TestClient client;
|
||||
std::unique_ptr<PDFiumEngine> engine =
|
||||
InitializeEngine(&client, FILE_PATH_LITERAL("blank.pdf"));
|
||||
ASSERT_TRUE(engine);
|
||||
ASSERT_EQ(1, engine->GetNumberOfPages());
|
||||
|
||||
// The crop box is 200x200 in points.
|
||||
PDFiumPage& page = GetPDFiumPageForTest(*engine, 0);
|
||||
const gfx::RectF bounding_box = page.GetBoundingBox();
|
||||
EXPECT_FLOAT_EQ(0.0f, bounding_box.x());
|
||||
EXPECT_FLOAT_EQ(0.0f, bounding_box.y());
|
||||
EXPECT_FLOAT_EQ(266.66669f, bounding_box.width());
|
||||
EXPECT_FLOAT_EQ(266.66669f, bounding_box.height());
|
||||
}
|
||||
|
||||
TEST_P(PDFiumPageTest, GetBoundingBoxCropped) {
|
||||
// Check getting the bounding box for a page with a crop box different than
|
||||
// the media box.
|
||||
TestClient client;
|
||||
std::unique_ptr<PDFiumEngine> engine =
|
||||
InitializeEngine(&client, FILE_PATH_LITERAL("landscape_rectangles.pdf"));
|
||||
ASSERT_TRUE(engine);
|
||||
ASSERT_EQ(1, engine->GetNumberOfPages());
|
||||
|
||||
PDFiumPage& page = GetPDFiumPageForTest(*engine, 0);
|
||||
const gfx::RectF bounding_box = page.GetBoundingBox();
|
||||
EXPECT_FLOAT_EQ(0.0f, bounding_box.x());
|
||||
EXPECT_FLOAT_EQ(0.0f, bounding_box.y());
|
||||
EXPECT_FLOAT_EQ(800.0f, bounding_box.width());
|
||||
EXPECT_FLOAT_EQ(533.33337f, bounding_box.height());
|
||||
}
|
||||
|
||||
TEST_P(PDFiumPageTest, GetBoundingBoxRotatedMultipageCropped) {
|
||||
// Check getting the bounding box for a multiple rotated pages with a crop
|
||||
// box.
|
||||
TestClient client;
|
||||
std::unique_ptr<PDFiumEngine> engine = InitializeEngine(
|
||||
&client, FILE_PATH_LITERAL("rotated_multi_page_cropped.pdf"));
|
||||
ASSERT_TRUE(engine);
|
||||
ASSERT_EQ(4, engine->GetNumberOfPages());
|
||||
|
||||
// Rotation 0 degrees clockwise.
|
||||
{
|
||||
PDFiumPage& page = GetPDFiumPageForTest(*engine, 0);
|
||||
const gfx::RectF bounding_box = page.GetBoundingBox();
|
||||
EXPECT_FLOAT_EQ(0.0f, bounding_box.x());
|
||||
EXPECT_FLOAT_EQ(133.33334f, bounding_box.y());
|
||||
EXPECT_FLOAT_EQ(66.666672f, bounding_box.width());
|
||||
EXPECT_FLOAT_EQ(400.0f, bounding_box.height());
|
||||
}
|
||||
|
||||
// Rotation 90 degrees clockwise.
|
||||
{
|
||||
PDFiumPage& page = GetPDFiumPageForTest(*engine, 1);
|
||||
const gfx::RectF bounding_box = page.GetBoundingBox();
|
||||
EXPECT_FLOAT_EQ(133.33334f, bounding_box.x());
|
||||
EXPECT_FLOAT_EQ(400.0f, bounding_box.y());
|
||||
EXPECT_FLOAT_EQ(400.0f, bounding_box.width());
|
||||
EXPECT_FLOAT_EQ(66.666672f, bounding_box.height());
|
||||
}
|
||||
// Rotation 180 degrees clockwise.
|
||||
{
|
||||
PDFiumPage& page = GetPDFiumPageForTest(*engine, 2);
|
||||
const gfx::RectF bounding_box = page.GetBoundingBox();
|
||||
EXPECT_FLOAT_EQ(400.0f, bounding_box.x());
|
||||
EXPECT_FLOAT_EQ(133.33334f, bounding_box.y());
|
||||
EXPECT_FLOAT_EQ(66.666672f, bounding_box.width());
|
||||
EXPECT_FLOAT_EQ(400.0f, bounding_box.height());
|
||||
}
|
||||
// Rotation 270 degrees clockwise.
|
||||
{
|
||||
PDFiumPage& page = GetPDFiumPageForTest(*engine, 3);
|
||||
const gfx::RectF bounding_box = page.GetBoundingBox();
|
||||
EXPECT_FLOAT_EQ(133.33334f, bounding_box.x());
|
||||
EXPECT_FLOAT_EQ(0.0f, bounding_box.y());
|
||||
EXPECT_FLOAT_EQ(400.0f, bounding_box.width());
|
||||
EXPECT_FLOAT_EQ(66.666672f, bounding_box.height());
|
||||
}
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(All, PDFiumPageTest, testing::Bool());
|
||||
|
||||
class PDFiumPageLinkTest : public PDFiumTestBase {
|
||||
|
21
pdf/test/data/blank.in
Normal file
21
pdf/test/data/blank.in
Normal file
@ -0,0 +1,21 @@
|
||||
{{header}}
|
||||
{{object 1 0}} <<
|
||||
/Type /Catalog
|
||||
/Pages 2 0 R
|
||||
>>
|
||||
endobj
|
||||
{{object 2 0}} <<
|
||||
/Type /Pages
|
||||
/Count 1
|
||||
/MediaBox [0 0 200 200]
|
||||
/Kids [3 0 R]
|
||||
>>
|
||||
endobj
|
||||
{{object 3 0}} <<
|
||||
/Type /Page
|
||||
/Parent 2 0 R
|
||||
>>
|
||||
{{xref}}
|
||||
{{trailer}}
|
||||
{{startxref}}
|
||||
%%EOF
|
31
pdf/test/data/blank.pdf
Normal file
31
pdf/test/data/blank.pdf
Normal file
@ -0,0 +1,31 @@
|
||||
%PDF-1.7
|
||||
%<25><><EFBFBD><EFBFBD>
|
||||
1 0 obj <<
|
||||
/Type /Catalog
|
||||
/Pages 2 0 R
|
||||
>>
|
||||
endobj
|
||||
2 0 obj <<
|
||||
/Type /Pages
|
||||
/Count 1
|
||||
/MediaBox [0 0 200 200]
|
||||
/Kids [3 0 R]
|
||||
>>
|
||||
endobj
|
||||
3 0 obj <<
|
||||
/Type /Page
|
||||
/Parent 2 0 R
|
||||
>>
|
||||
xref
|
||||
0 4
|
||||
0000000000 65535 f
|
||||
0000000015 00000 n
|
||||
0000000068 00000 n
|
||||
0000000157 00000 n
|
||||
trailer <<
|
||||
/Root 1 0 R
|
||||
/Size 4
|
||||
>>
|
||||
startxref
|
||||
201
|
||||
%%EOF
|
54
pdf/test/data/rotated_multi_page.in
Normal file
54
pdf/test/data/rotated_multi_page.in
Normal file
@ -0,0 +1,54 @@
|
||||
{{header}}
|
||||
{{object 1 0}} <<
|
||||
/Type /Catalog
|
||||
/Pages 2 0 R
|
||||
>>
|
||||
endobj
|
||||
{{object 2 0}} <<
|
||||
/Type /Pages
|
||||
/Count 4
|
||||
/MediaBox [0 0 600 1200]
|
||||
/Kids [3 0 R 4 0 R 5 0 R 6 0 R]
|
||||
>>
|
||||
endobj
|
||||
{{object 3 0}} <<
|
||||
/Type /Page
|
||||
/Parent 2 0 R
|
||||
/Contents 7 0 R
|
||||
>>
|
||||
endobj
|
||||
{{object 4 0}} <<
|
||||
/Type /Page
|
||||
/Parent 2 0 R
|
||||
/Contents 7 0 R
|
||||
/Rotate 90
|
||||
>>
|
||||
endobj
|
||||
{{object 5 0}} <<
|
||||
/Type /Page
|
||||
/Parent 2 0 R
|
||||
/Contents 7 0 R
|
||||
/Rotate 180
|
||||
>>
|
||||
endobj
|
||||
{{object 6 0}} <<
|
||||
/Type /Page
|
||||
/Parent 2 0 R
|
||||
/Contents 7 0 R
|
||||
/Rotate 270
|
||||
>>
|
||||
endobj
|
||||
{{object 7 0}} <<
|
||||
{{streamlen}}
|
||||
>>
|
||||
stream
|
||||
q
|
||||
1 1 0 rg
|
||||
1 201 98 298 re B*
|
||||
Q
|
||||
endstream
|
||||
endobj
|
||||
{{xref}}
|
||||
{{trailer}}
|
||||
{{startxref}}
|
||||
%%EOF
|
68
pdf/test/data/rotated_multi_page.pdf
Normal file
68
pdf/test/data/rotated_multi_page.pdf
Normal file
@ -0,0 +1,68 @@
|
||||
%PDF-1.7
|
||||
%<25><><EFBFBD><EFBFBD>
|
||||
1 0 obj <<
|
||||
/Type /Catalog
|
||||
/Pages 2 0 R
|
||||
>>
|
||||
endobj
|
||||
2 0 obj <<
|
||||
/Type /Pages
|
||||
/Count 4
|
||||
/MediaBox [0 0 600 1200]
|
||||
/Kids [3 0 R 4 0 R 5 0 R 6 0 R]
|
||||
>>
|
||||
endobj
|
||||
3 0 obj <<
|
||||
/Type /Page
|
||||
/Parent 2 0 R
|
||||
/Contents 7 0 R
|
||||
>>
|
||||
endobj
|
||||
4 0 obj <<
|
||||
/Type /Page
|
||||
/Parent 2 0 R
|
||||
/Contents 7 0 R
|
||||
/Rotate 90
|
||||
>>
|
||||
endobj
|
||||
5 0 obj <<
|
||||
/Type /Page
|
||||
/Parent 2 0 R
|
||||
/Contents 7 0 R
|
||||
/Rotate 180
|
||||
>>
|
||||
endobj
|
||||
6 0 obj <<
|
||||
/Type /Page
|
||||
/Parent 2 0 R
|
||||
/Contents 7 0 R
|
||||
/Rotate 270
|
||||
>>
|
||||
endobj
|
||||
7 0 obj <<
|
||||
/Length 32
|
||||
>>
|
||||
stream
|
||||
q
|
||||
1 1 0 rg
|
||||
1 201 98 298 re B*
|
||||
Q
|
||||
endstream
|
||||
endobj
|
||||
xref
|
||||
0 8
|
||||
0000000000 65535 f
|
||||
0000000015 00000 n
|
||||
0000000068 00000 n
|
||||
0000000176 00000 n
|
||||
0000000245 00000 n
|
||||
0000000327 00000 n
|
||||
0000000410 00000 n
|
||||
0000000493 00000 n
|
||||
trailer <<
|
||||
/Root 1 0 R
|
||||
/Size 8
|
||||
>>
|
||||
startxref
|
||||
576
|
||||
%%EOF
|
55
pdf/test/data/rotated_multi_page_cropped.in
Normal file
55
pdf/test/data/rotated_multi_page_cropped.in
Normal file
@ -0,0 +1,55 @@
|
||||
{{header}}
|
||||
{{object 1 0}} <<
|
||||
/Type /Catalog
|
||||
/Pages 2 0 R
|
||||
>>
|
||||
endobj
|
||||
{{object 2 0}} <<
|
||||
/Type /Pages
|
||||
/Count 4
|
||||
/CropBox [50 100 400 600]
|
||||
/MediaBox [0 0 600 1200]
|
||||
/Kids [3 0 R 4 0 R 5 0 R 6 0 R]
|
||||
>>
|
||||
endobj
|
||||
{{object 3 0}} <<
|
||||
/Type /Page
|
||||
/Parent 2 0 R
|
||||
/Contents 7 0 R
|
||||
>>
|
||||
endobj
|
||||
{{object 4 0}} <<
|
||||
/Type /Page
|
||||
/Parent 2 0 R
|
||||
/Contents 7 0 R
|
||||
/Rotate 90
|
||||
>>
|
||||
endobj
|
||||
{{object 5 0}} <<
|
||||
/Type /Page
|
||||
/Parent 2 0 R
|
||||
/Contents 7 0 R
|
||||
/Rotate 180
|
||||
>>
|
||||
endobj
|
||||
{{object 6 0}} <<
|
||||
/Type /Page
|
||||
/Parent 2 0 R
|
||||
/Contents 7 0 R
|
||||
/Rotate 270
|
||||
>>
|
||||
endobj
|
||||
{{object 7 0}} <<
|
||||
{{streamlen}}
|
||||
>>
|
||||
stream
|
||||
q
|
||||
1 1 0 rg
|
||||
1 201 98 298 re B*
|
||||
Q
|
||||
endstream
|
||||
endobj
|
||||
{{xref}}
|
||||
{{trailer}}
|
||||
{{startxref}}
|
||||
%%EOF
|
69
pdf/test/data/rotated_multi_page_cropped.pdf
Normal file
69
pdf/test/data/rotated_multi_page_cropped.pdf
Normal file
@ -0,0 +1,69 @@
|
||||
%PDF-1.7
|
||||
%<25><><EFBFBD><EFBFBD>
|
||||
1 0 obj <<
|
||||
/Type /Catalog
|
||||
/Pages 2 0 R
|
||||
>>
|
||||
endobj
|
||||
2 0 obj <<
|
||||
/Type /Pages
|
||||
/Count 4
|
||||
/CropBox [50 100 400 600]
|
||||
/MediaBox [0 0 600 1200]
|
||||
/Kids [3 0 R 4 0 R 5 0 R 6 0 R]
|
||||
>>
|
||||
endobj
|
||||
3 0 obj <<
|
||||
/Type /Page
|
||||
/Parent 2 0 R
|
||||
/Contents 7 0 R
|
||||
>>
|
||||
endobj
|
||||
4 0 obj <<
|
||||
/Type /Page
|
||||
/Parent 2 0 R
|
||||
/Contents 7 0 R
|
||||
/Rotate 90
|
||||
>>
|
||||
endobj
|
||||
5 0 obj <<
|
||||
/Type /Page
|
||||
/Parent 2 0 R
|
||||
/Contents 7 0 R
|
||||
/Rotate 180
|
||||
>>
|
||||
endobj
|
||||
6 0 obj <<
|
||||
/Type /Page
|
||||
/Parent 2 0 R
|
||||
/Contents 7 0 R
|
||||
/Rotate 270
|
||||
>>
|
||||
endobj
|
||||
7 0 obj <<
|
||||
/Length 32
|
||||
>>
|
||||
stream
|
||||
q
|
||||
1 1 0 rg
|
||||
1 201 98 298 re B*
|
||||
Q
|
||||
endstream
|
||||
endobj
|
||||
xref
|
||||
0 8
|
||||
0000000000 65535 f
|
||||
0000000015 00000 n
|
||||
0000000068 00000 n
|
||||
0000000204 00000 n
|
||||
0000000273 00000 n
|
||||
0000000355 00000 n
|
||||
0000000438 00000 n
|
||||
0000000521 00000 n
|
||||
trailer <<
|
||||
/Root 1 0 R
|
||||
/Size 8
|
||||
>>
|
||||
startxref
|
||||
604
|
||||
%%EOF
|
Reference in New Issue
Block a user