Version 5.2.6.1, tag libreoffice-5.2.6.1
[LibreOffice.git] / android / source / src / java / org / libreoffice / LOKitTileProvider.java
blobdd14621b3f027ac2a398944b50bda635b3f1f2ea
1 /* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 */
9 package org.libreoffice;
11 import android.graphics.Bitmap;
12 import android.graphics.PointF;
13 import android.util.Log;
14 import android.view.KeyEvent;
16 import org.libreoffice.kit.DirectBufferAllocator;
17 import org.libreoffice.kit.Document;
18 import org.libreoffice.kit.LibreOfficeKit;
19 import org.libreoffice.kit.Office;
20 import org.mozilla.gecko.gfx.BufferedCairoImage;
21 import org.mozilla.gecko.gfx.CairoImage;
22 import org.mozilla.gecko.gfx.GeckoLayerClient;
23 import org.mozilla.gecko.gfx.IntSize;
25 import java.nio.ByteBuffer;
27 /**
28 * LOKit implementation of TileProvider.
30 public class LOKitTileProvider implements TileProvider {
31 private static final String LOGTAG = LOKitTileProvider.class.getSimpleName();
32 private static int TILE_SIZE = 256;
33 private final GeckoLayerClient mLayerClient;
34 private final float mTileWidth;
35 private final float mTileHeight;
36 private final String mInputFile;
37 private Office mOffice;
38 private Document mDocument;
39 private boolean mIsReady = false;
41 private float mDPI;
42 private float mWidthTwip;
43 private float mHeightTwip;
45 private Document.MessageCallback mMessageCallback;
47 private long objectCreationTime = System.currentTimeMillis();
49 /**
50 * Initialize LOKit and load the document.
51 * @param layerClient - layerclient implementation
52 * @param messageCallback - callback for messages retrieved from LOKit
53 * @param input - input path of the document
55 public LOKitTileProvider(GeckoLayerClient layerClient, Document.MessageCallback messageCallback, String input) {
56 mLayerClient = layerClient;
57 mMessageCallback = messageCallback;
58 mDPI = LOKitShell.getDpi();
59 mTileWidth = pixelToTwip(TILE_SIZE, mDPI);
60 mTileHeight = pixelToTwip(TILE_SIZE, mDPI);
62 LibreOfficeKit.putenv("SAL_LOG=+WARN+INFO");
63 LibreOfficeKit.init(LibreOfficeMainActivity.mAppContext);
65 mOffice = new Office(LibreOfficeKit.getLibreOfficeKitHandle());
67 mInputFile = input;
69 Log.i(LOGTAG, "====> Loading file '" + input + "'");
70 mDocument = mOffice.documentLoad(input);
72 if (mDocument == null) {
73 Log.i(LOGTAG, "====> mOffice.documentLoad() returned null, trying to restart 'Office' and loading again");
74 mOffice.destroy();
75 Log.i(LOGTAG, "====> mOffice.destroy() done");
76 ByteBuffer handle = LibreOfficeKit.getLibreOfficeKitHandle();
77 Log.i(LOGTAG, "====> getLibreOfficeKitHandle() = " + handle);
78 mOffice = new Office(handle);
79 Log.i(LOGTAG, "====> new Office created");
80 mDocument = mOffice.documentLoad(input);
83 Log.i(LOGTAG, "====> mDocument = " + mDocument);
85 if (mDocument != null)
86 mDocument.initializeForRendering();
88 if (checkDocument()) {
89 postLoad();
90 mIsReady = true;
91 } else {
92 mIsReady = false;
96 /**
97 * Triggered after the document is loaded.
99 private void postLoad() {
100 mDocument.setMessageCallback(mMessageCallback);
102 int parts = mDocument.getParts();
103 Log.i(LOGTAG, "Document parts: " + parts);
105 LibreOfficeMainActivity.mAppContext.getDocumentPartView().clear();
107 // Writer documents always have one part, so hide the navigation drawer.
108 if (mDocument.getDocumentType() != Document.DOCTYPE_TEXT) {
109 for (int i = 0; i < parts; i++) {
110 String partName = mDocument.getPartName(i);
111 if (partName.isEmpty()) {
112 partName = getGenericPartName(i);
114 Log.i(LOGTAG, "Document part " + i + " name:'" + partName + "'");
116 mDocument.setPart(i);
117 resetDocumentSize();
118 final DocumentPartView partView = new DocumentPartView(i, partName);
119 LibreOfficeMainActivity.mAppContext.getDocumentPartView().add(partView);
121 } else {
122 LibreOfficeMainActivity.mAppContext.disableNavigationDrawer();
125 mDocument.setPart(0);
127 setupDocumentFonts();
129 LOKitShell.getMainHandler().post(new Runnable() {
130 @Override
131 public void run() {
132 LibreOfficeMainActivity.mAppContext.getDocumentPartViewListAdapter().notifyDataSetChanged();
137 private void setupDocumentFonts() {
138 String values = mDocument.getCommandValues(".uno:CharFontName");
139 if (values == null || values.isEmpty())
140 return;
142 LOKitShell.getFontController().parseJson(values);
143 LOKitShell.getFontController().setupFontViews();
146 private String getGenericPartName(int i) {
147 if (mDocument == null) {
148 return "";
150 switch (mDocument.getDocumentType()) {
151 case Document.DOCTYPE_DRAWING:
152 case Document.DOCTYPE_TEXT:
153 return "Page " + (i + 1);
154 case Document.DOCTYPE_SPREADSHEET:
155 return "Sheet " + (i + 1);
156 case Document.DOCTYPE_PRESENTATION:
157 return "Slide " + (i + 1);
158 case Document.DOCTYPE_OTHER:
159 default:
160 return "Part " + (i + 1);
164 public static float twipToPixel(float input, float dpi) {
165 return input / 1440.0f * dpi;
168 public static float pixelToTwip(float input, float dpi) {
169 return (input / dpi) * 1440.0f;
174 * @see TileProvider#getPartsCount()
176 @Override
177 public int getPartsCount() {
178 return mDocument.getParts();
182 * @see TileProvider#onSwipeLeft()
184 @Override
185 public void onSwipeLeft() {
186 if (mDocument.getDocumentType() == Document.DOCTYPE_PRESENTATION &&
187 getCurrentPartNumber() < getPartsCount()-1) {
188 LOKitShell.sendChangePartEvent(getCurrentPartNumber()+1);
193 * @see TileProvider#onSwipeRight()
195 @Override
196 public void onSwipeRight() {
197 if (mDocument.getDocumentType() == Document.DOCTYPE_PRESENTATION &&
198 getCurrentPartNumber() > 0) {
199 LOKitShell.sendChangePartEvent(getCurrentPartNumber()-1);
203 private boolean checkDocument() {
204 String error = null;
205 boolean ret;
207 if (mDocument == null || !mOffice.getError().isEmpty()) {
208 error = "Cannot open " + mInputFile + ": " + mOffice.getError();
209 ret = false;
210 } else {
211 ret = resetDocumentSize();
212 if (!ret) {
213 error = "Document returned an invalid size or the document is empty.";
217 if (!ret) {
218 final String message = error;
219 LOKitShell.getMainHandler().post(new Runnable() {
220 @Override
221 public void run() {
222 LibreOfficeMainActivity.mAppContext.showAlertDialog(message);
227 return ret;
230 private boolean resetDocumentSize() {
231 mWidthTwip = mDocument.getDocumentWidth();
232 mHeightTwip = mDocument.getDocumentHeight();
234 if (mWidthTwip == 0 || mHeightTwip == 0) {
235 Log.e(LOGTAG, "Document size zero - last error: " + mOffice.getError());
236 return false;
237 } else {
238 Log.i(LOGTAG, "Reset document size: " + mDocument.getDocumentWidth() + " x " + mDocument.getDocumentHeight());
241 return true;
245 * @see TileProvider#getPageWidth()
247 @Override
248 public int getPageWidth() {
249 return (int) twipToPixel(mWidthTwip, mDPI);
253 * @see TileProvider#getPageHeight()
255 @Override
256 public int getPageHeight() {
257 return (int) twipToPixel(mHeightTwip, mDPI);
261 * @see TileProvider#isReady()
263 @Override
264 public boolean isReady() {
265 return mIsReady;
269 * @see TileProvider#createTile(float, float, org.mozilla.gecko.gfx.IntSize, float)
271 @Override
272 public CairoImage createTile(float x, float y, IntSize tileSize, float zoom) {
273 ByteBuffer buffer = DirectBufferAllocator.guardedAllocate(tileSize.width * tileSize.height * 4);
274 if (buffer == null)
275 return null;
277 CairoImage image = new BufferedCairoImage(buffer, tileSize.width, tileSize.height, CairoImage.FORMAT_ARGB32);
278 rerenderTile(image, x, y, tileSize, zoom);
279 return image;
283 * @see TileProvider#rerenderTile(org.mozilla.gecko.gfx.CairoImage, float, float, org.mozilla.gecko.gfx.IntSize, float)
285 @Override
286 public void rerenderTile(CairoImage image, float x, float y, IntSize tileSize, float zoom) {
287 if (mDocument != null && image.getBuffer() != null) {
288 float twipX = pixelToTwip(x, mDPI) / zoom;
289 float twipY = pixelToTwip(y, mDPI) / zoom;
290 float twipWidth = mTileWidth / zoom;
291 float twipHeight = mTileHeight / zoom;
292 long start = System.currentTimeMillis() - objectCreationTime;
294 //Log.i(LOGTAG, "paintTile >> @" + start + " (" + tileSize.width + " " + tileSize.height + " " + (int) twipX + " " + (int) twipY + " " + (int) twipWidth + " " + (int) twipHeight + ")");
295 mDocument.paintTile(image.getBuffer(), tileSize.width, tileSize.height, (int) twipX, (int) twipY, (int) twipWidth, (int) twipHeight);
297 long stop = System.currentTimeMillis() - objectCreationTime;
298 //Log.i(LOGTAG, "paintTile << @" + stop + " elapsed: " + (stop - start));
299 } else {
300 if (mDocument == null) {
301 Log.e(LOGTAG, "Document is null!!");
307 * @see TileProvider#thumbnail(int)
309 @Override
310 public Bitmap thumbnail(int size) {
311 int widthPixel = getPageWidth();
312 int heightPixel = getPageHeight();
314 if (widthPixel > heightPixel) {
315 double ratio = heightPixel / (double) widthPixel;
316 widthPixel = size;
317 heightPixel = (int) (widthPixel * ratio);
318 } else {
319 double ratio = widthPixel / (double) heightPixel;
320 heightPixel = size;
321 widthPixel = (int) (heightPixel * ratio);
324 Log.w(LOGTAG, "Thumbnail size: " + getPageWidth() + " " + getPageHeight() + " " + widthPixel + " " + heightPixel);
326 ByteBuffer buffer = ByteBuffer.allocateDirect(widthPixel * heightPixel * 4);
327 if (mDocument != null)
328 mDocument.paintTile(buffer, widthPixel, heightPixel, 0, 0, (int) mWidthTwip, (int) mHeightTwip);
330 Bitmap bitmap = Bitmap.createBitmap(widthPixel, heightPixel, Bitmap.Config.ARGB_8888);
331 bitmap.copyPixelsFromBuffer(buffer);
332 if (bitmap == null) {
333 Log.w(LOGTAG, "Thumbnail not created!");
335 return bitmap;
339 * @see TileProvider#close()
341 @Override
342 public void close() {
343 Log.i(LOGTAG, "Document destroyed: " + mInputFile);
344 if (mDocument != null) {
345 mDocument.destroy();
346 mDocument = null;
351 * @see TileProvider#isTextDocument()
353 @Override
354 public boolean isTextDocument() {
355 return mDocument != null && mDocument.getDocumentType() == Document.DOCTYPE_TEXT;
359 * @see TileProvider#isSpreadsheet()
361 @Override
362 public boolean isSpreadsheet() {
363 return mDocument != null && mDocument.getDocumentType() == Document.DOCTYPE_SPREADSHEET;
367 * Returns the Unicode character generated by this event or 0.
369 private int getCharCode(KeyEvent keyEvent) {
370 switch (keyEvent.getKeyCode())
372 case KeyEvent.KEYCODE_DEL:
373 case KeyEvent.KEYCODE_ENTER:
374 return 0;
376 return keyEvent.getUnicodeChar();
380 * Returns the integer code representing the key of the event (non-zero for
381 * control keys).
383 private int getKeyCode(KeyEvent keyEvent) {
384 switch (keyEvent.getKeyCode()) {
385 case KeyEvent.KEYCODE_DEL:
386 return com.sun.star.awt.Key.BACKSPACE;
387 case KeyEvent.KEYCODE_ENTER:
388 return com.sun.star.awt.Key.RETURN;
390 return 0;
394 * @see TileProvider#sendKeyEvent(android.view.KeyEvent)
396 @Override
397 public void sendKeyEvent(KeyEvent keyEvent) {
398 if (keyEvent.getAction() == KeyEvent.ACTION_MULTIPLE) {
399 String keyString = keyEvent.getCharacters();
400 for (int i = 0; i < keyString.length(); i++) {
401 int codePoint = keyString.codePointAt(i);
402 mDocument.postKeyEvent(Document.KEY_EVENT_PRESS, codePoint, getKeyCode(keyEvent));
404 } else if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
405 mDocument.postKeyEvent(Document.KEY_EVENT_PRESS, getCharCode(keyEvent), getKeyCode(keyEvent));
406 } else if (keyEvent.getAction() == KeyEvent.ACTION_UP) {
407 mDocument.postKeyEvent(Document.KEY_EVENT_RELEASE, getCharCode(keyEvent), getKeyCode(keyEvent));
411 private void mouseButton(int type, PointF inDocument, int numberOfClicks, float zoomFactor) {
412 int x = (int) pixelToTwip(inDocument.x, mDPI);
413 int y = (int) pixelToTwip(inDocument.y, mDPI);
415 mDocument.setClientZoom(TILE_SIZE, TILE_SIZE, (int) (mTileWidth / zoomFactor), (int) (mTileHeight / zoomFactor));
416 mDocument.postMouseEvent(type, x, y, numberOfClicks, Document.MOUSE_BUTTON_LEFT, Document.KEYBOARD_MODIFIER_NONE);
420 * @see TileProvider#mouseButtonDown(android.graphics.PointF, int)
422 @Override
423 public void mouseButtonDown(PointF documentCoordinate, int numberOfClicks, float zoomFactor) {
424 mouseButton(Document.MOUSE_EVENT_BUTTON_DOWN, documentCoordinate, numberOfClicks, zoomFactor);
428 * @see TileProvider#mouseButtonUp(android.graphics.PointF, int)
430 @Override
431 public void mouseButtonUp(PointF documentCoordinate, int numberOfClicks, float zoomFactor) {
432 mouseButton(Document.MOUSE_EVENT_BUTTON_UP, documentCoordinate, numberOfClicks, zoomFactor);
436 * @param command UNO command string
437 * @param arguments Arguments to UNO command
439 @Override
440 public void postUnoCommand(String command, String arguments) {
441 mDocument.postUnoCommand(command, arguments);
444 private void setTextSelection(int type, PointF documentCoordinate) {
445 int x = (int) pixelToTwip(documentCoordinate.x, mDPI);
446 int y = (int) pixelToTwip(documentCoordinate.y, mDPI);
447 mDocument.setTextSelection(type, x, y);
451 * @see TileProvider#setTextSelectionStart(android.graphics.PointF)
453 @Override
454 public void setTextSelectionStart(PointF documentCoordinate) {
455 setTextSelection(Document.SET_TEXT_SELECTION_START, documentCoordinate);
459 * @see TileProvider#setTextSelectionEnd(android.graphics.PointF)
461 @Override
462 public void setTextSelectionEnd(PointF documentCoordinate) {
463 setTextSelection(Document.SET_TEXT_SELECTION_END, documentCoordinate);
467 * @see TileProvider#setTextSelectionReset(android.graphics.PointF)
469 @Override
470 public void setTextSelectionReset(PointF documentCoordinate) {
471 setTextSelection(Document.SET_TEXT_SELECTION_RESET, documentCoordinate);
475 * @see org.libreoffice.TileProvider#setGraphicSelectionStart(android.graphics.PointF)
477 @Override
478 public void setGraphicSelectionStart(PointF documentCoordinate) {
479 setGraphicSelection(Document.SET_GRAPHIC_SELECTION_START, documentCoordinate);
483 * @see org.libreoffice.TileProvider#setGraphicSelectionEnd(android.graphics.PointF)
485 @Override
486 public void setGraphicSelectionEnd(PointF documentCoordinate) {
487 setGraphicSelection(Document.SET_GRAPHIC_SELECTION_END, documentCoordinate);
490 private void setGraphicSelection(int type, PointF documentCoordinate) {
491 int x = (int) pixelToTwip(documentCoordinate.x, mDPI);
492 int y = (int) pixelToTwip(documentCoordinate.y, mDPI);
493 mDocument.setGraphicSelection(type, x, y);
496 @Override
497 protected void finalize() throws Throwable {
498 close();
499 super.finalize();
503 * @see TileProvider#changePart(int)
505 @Override
506 public void changePart(int partIndex) {
507 if (mDocument == null)
508 return;
510 mDocument.setPart(partIndex);
511 resetDocumentSize();
515 * @see TileProvider#getCurrentPartNumber()
517 @Override
518 public int getCurrentPartNumber() {
519 if (mDocument == null)
520 return 0;
522 return mDocument.getPart();
526 // vim:set shiftwidth=4 softtabstop=4 expandtab: