1 /* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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/.
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
;
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;
42 private float mWidthTwip
;
43 private float mHeightTwip
;
45 private Document
.MessageCallback mMessageCallback
;
47 private long objectCreationTime
= System
.currentTimeMillis();
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());
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");
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()) {
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
);
118 final DocumentPartView partView
= new DocumentPartView(i
, partName
);
119 LibreOfficeMainActivity
.mAppContext
.getDocumentPartView().add(partView
);
122 LibreOfficeMainActivity
.mAppContext
.disableNavigationDrawer();
125 mDocument
.setPart(0);
127 setupDocumentFonts();
129 LOKitShell
.getMainHandler().post(new Runnable() {
132 LibreOfficeMainActivity
.mAppContext
.getDocumentPartViewListAdapter().notifyDataSetChanged();
137 private void setupDocumentFonts() {
138 String values
= mDocument
.getCommandValues(".uno:CharFontName");
139 if (values
== null || values
.isEmpty())
142 LOKitShell
.getFontController().parseJson(values
);
143 LOKitShell
.getFontController().setupFontViews();
146 private String
getGenericPartName(int i
) {
147 if (mDocument
== null) {
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
:
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()
177 public int getPartsCount() {
178 return mDocument
.getParts();
182 * @see TileProvider#onSwipeLeft()
185 public void onSwipeLeft() {
186 if (mDocument
.getDocumentType() == Document
.DOCTYPE_PRESENTATION
&&
187 getCurrentPartNumber() < getPartsCount()-1) {
188 LOKitShell
.sendChangePartEvent(getCurrentPartNumber()+1);
193 * @see TileProvider#onSwipeRight()
196 public void onSwipeRight() {
197 if (mDocument
.getDocumentType() == Document
.DOCTYPE_PRESENTATION
&&
198 getCurrentPartNumber() > 0) {
199 LOKitShell
.sendChangePartEvent(getCurrentPartNumber()-1);
203 private boolean checkDocument() {
207 if (mDocument
== null || !mOffice
.getError().isEmpty()) {
208 error
= "Cannot open " + mInputFile
+ ": " + mOffice
.getError();
211 ret
= resetDocumentSize();
213 error
= "Document returned an invalid size or the document is empty.";
218 final String message
= error
;
219 LOKitShell
.getMainHandler().post(new Runnable() {
222 LibreOfficeMainActivity
.mAppContext
.showAlertDialog(message
);
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());
238 Log
.i(LOGTAG
, "Reset document size: " + mDocument
.getDocumentWidth() + " x " + mDocument
.getDocumentHeight());
245 * @see TileProvider#getPageWidth()
248 public int getPageWidth() {
249 return (int) twipToPixel(mWidthTwip
, mDPI
);
253 * @see TileProvider#getPageHeight()
256 public int getPageHeight() {
257 return (int) twipToPixel(mHeightTwip
, mDPI
);
261 * @see TileProvider#isReady()
264 public boolean isReady() {
269 * @see TileProvider#createTile(float, float, org.mozilla.gecko.gfx.IntSize, float)
272 public CairoImage
createTile(float x
, float y
, IntSize tileSize
, float zoom
) {
273 ByteBuffer buffer
= DirectBufferAllocator
.guardedAllocate(tileSize
.width
* tileSize
.height
* 4);
277 CairoImage image
= new BufferedCairoImage(buffer
, tileSize
.width
, tileSize
.height
, CairoImage
.FORMAT_ARGB32
);
278 rerenderTile(image
, x
, y
, tileSize
, zoom
);
283 * @see TileProvider#rerenderTile(org.mozilla.gecko.gfx.CairoImage, float, float, org.mozilla.gecko.gfx.IntSize, float)
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));
300 if (mDocument
== null) {
301 Log
.e(LOGTAG
, "Document is null!!");
307 * @see TileProvider#thumbnail(int)
310 public Bitmap
thumbnail(int size
) {
311 int widthPixel
= getPageWidth();
312 int heightPixel
= getPageHeight();
314 if (widthPixel
> heightPixel
) {
315 double ratio
= heightPixel
/ (double) widthPixel
;
317 heightPixel
= (int) (widthPixel
* ratio
);
319 double ratio
= widthPixel
/ (double) heightPixel
;
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!");
339 * @see TileProvider#close()
342 public void close() {
343 Log
.i(LOGTAG
, "Document destroyed: " + mInputFile
);
344 if (mDocument
!= null) {
351 * @see TileProvider#isTextDocument()
354 public boolean isTextDocument() {
355 return mDocument
!= null && mDocument
.getDocumentType() == Document
.DOCTYPE_TEXT
;
359 * @see TileProvider#isSpreadsheet()
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
:
376 return keyEvent
.getUnicodeChar();
380 * Returns the integer code representing the key of the event (non-zero for
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
;
394 * @see TileProvider#sendKeyEvent(android.view.KeyEvent)
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)
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)
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
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)
454 public void setTextSelectionStart(PointF documentCoordinate
) {
455 setTextSelection(Document
.SET_TEXT_SELECTION_START
, documentCoordinate
);
459 * @see TileProvider#setTextSelectionEnd(android.graphics.PointF)
462 public void setTextSelectionEnd(PointF documentCoordinate
) {
463 setTextSelection(Document
.SET_TEXT_SELECTION_END
, documentCoordinate
);
467 * @see TileProvider#setTextSelectionReset(android.graphics.PointF)
470 public void setTextSelectionReset(PointF documentCoordinate
) {
471 setTextSelection(Document
.SET_TEXT_SELECTION_RESET
, documentCoordinate
);
475 * @see org.libreoffice.TileProvider#setGraphicSelectionStart(android.graphics.PointF)
478 public void setGraphicSelectionStart(PointF documentCoordinate
) {
479 setGraphicSelection(Document
.SET_GRAPHIC_SELECTION_START
, documentCoordinate
);
483 * @see org.libreoffice.TileProvider#setGraphicSelectionEnd(android.graphics.PointF)
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
);
497 protected void finalize() throws Throwable
{
503 * @see TileProvider#changePart(int)
506 public void changePart(int partIndex
) {
507 if (mDocument
== null)
510 mDocument
.setPart(partIndex
);
515 * @see TileProvider#getCurrentPartNumber()
518 public int getCurrentPartNumber() {
519 if (mDocument
== null)
522 return mDocument
.getPart();
526 // vim:set shiftwidth=4 softtabstop=4 expandtab: