|Figure 1: Command Popup window|
Thursday, May 13, 2021
Wednesday, March 24, 2021
DevTools implementation has been completed and this is the third and final part of the mini-series. The focus of this part is on the object inspector, but I have also improved or changed other DevTools parts, so first I will briefly mention those.
New menu position
Updates to the document model tree view
The object inspector tree view
|Figure 3: Top toolbar and tab bar in object inspector|
- "Interfaces" - the interfaces that the current object implements
- "Services" - the services that the current object supports
- "Properties" - the properties of the current object
- "Methods" - the combined methods that can be called on the current object
|Figure 4: Object inspector "Properties" tab|
- Properties accessible view XPropertySet.
- Properties defined as an attribute (marked "[attribute]" in IDL).
- Pseudo properties defined by a get and set method.
|Figure 5: "Properties" tab and the text view|
- "Method" - name of the method
- "Return type" - the return (simplified) type of the method
- "Parameters" - list of input parameters, where each one lists the direction ("in", "out" or "in/out"), the parameter name and the simplified type
- "Implementation Class" - class/interface where the method is implemented
Monday, March 1, 2021
Since my last blog post I've been continuing the work on DevTools and since then a lot of things have progressed. Point & click has been implemented and the object inspector view has been greatly improved to show current object’s properties and methods. In this part I will mainly talk about the point & click and a bit about the current state, and in the next blog I will extensively talk about the object inspector.
Point & click
|Figure 1: Current selection button|
The idea of this functionality is to provide a way to inspect selected objects in the document, for example an image or a shape. For this, I have implemented a selection change listener (sfx2/source/devtools/SelectionChangeHandler.hxx), whose purpose is to listen to the selection changes that happen in the document and store the latest selection object. It is started when the DevTools docking window is instantiated and shown. I have added a new toggle button “Current Selection” (see Figure 1) to the UI. When the button is selected, it automatically shows the current selected object (gathered with the selection change listener) in the object inspector.
|Figure 2: Current selected shape's properties shown in the object inspector|
In the example shown in Figure 2, we can see the shape is selected in the document and its properties are shown in the object inspector. If the "Current Selection" button wouldn't be toggled, then the document top-level object would be shown in the object inspector or the selected object in the DOM tree view.
While the "Current Selection" button is toggled, selecting any object in the DOM tree view (left-hand side tree view) has no effect, however if the current selected object is also present in the current DOM tree view, it will be selected. Note that if the object is not present in the tree, it won't be selected, because the DOM tree view will not force creation of on-demand object because of performance considerations.
|Figure 3: "Inspect Object" command in "Customize" dialog|
In addition to showing the selected object, I have added a UNO command named “Inspect Object” (.uno:InspectSelectedObject), which can be added to context menus for objects (See Figure 3). The purpose of this command is to provide a way to open the DevTools docking window and automatically show the current selected object. If a user regularly uses the object inspector, this may be a more convenient way for them to access DevTools. Note that by default the command isn't added to any context menu, this is up to the user. However, if there will be demand to add this to context menus, it can be easily added.
|Figure 4: "Inspect Object" context menu entry on a shape object|
The example in Figure 4 shows the context menu of a shape object, where the selected entry is the added "Inspect Object".
From the implementation standpoint, it was necessary to move the whole DevTools from svx to sfx2 module. This was mainly necessary to get .uno:InspectSelectedObject to work, because we need to signal to DevTools that we want to show the current selection and not the document root in the object inspector. Because the svx depends on sfx2 module, it is not possible to access svx from sfx2 (only the other way around).
Improvements to object inspector
Thursday, January 21, 2021
When developing macros and extensions in LibreOffice it is very useful to have an object inspector. With that tool you inspect the object tree and the methods, properties and interfaces of individual objects to understand its structure. There are quite some different object inspectors available for LibreOffice as extension. Probably the best known is called “XrayTool”, but there were also others like MRI, which was build for a similar purpose and various other more simple object inspectors (for example one is provided as an code example in ODK).
As a tool like this is very valuable it makes sense that it would be provided out-of-the-box by LibreOffice without the need for the user to get it separately. Also the user could even be unaware the such a tool exists. For this reasons The Document Foundation (TDF) put up a tender to create a built-in Xray like UNO object inspector, which was awarded to Collabora and we are now in the process of implementing it.
Thank you TDF and the donating users of LibreOffice to make the work on this tool possible.
The major gripe with Xray and other tools is that the user needs to go into the macro editor and run a the script with the inspecting object as the parameter.
For example to inspect the root UNO object:
or for example to inspect a specific (first) Calc sheet:
Document = ThisComponent
AllSheets = Document.getSheets()
MySheet = AllSheets.getByIndex(0)
|Figure 1: Watch window in the macro editor|
We can do much better than this. So the idea is to have a object inspector tool as a dockable bottom widget, very similar to “developer tools” that you can find in popular web browsers.
The left hand side of the tool will have a subset of the document model (DOM) presented as a tree and on the right hand side a object inspector view that will show the current UNO object’s properties, methods in a tree view that is taken from the Watch window in the LibreOffice macro editor (see Figure 1). When the user would select an UNO object in the DOM tree view (left-hand side), the object inspector (right-hand side) would show it.
An additional functionality that is present in developer tools in web browsers is the ability to point & click an object directly in the document, which can then be inspected in the developer tool. A similar functionality to this will be possible in LibreOffice, where you will be able to select an object in the document, that is then be shown in the object inspector (right-hand side).
The implementation has been divided into 4 parts:
- Introduce a new dockable window on the bottom of the UI.
- DOM tree view (left-hand side)
- Implement point&click functionality.
- Object inspector view (right-hand side)
Currently the 1. and 2. part have been implemented, so this blog post serves as a report of the work that has been done until now. For 3. and 4. part, a limited subset of the functionality has been already implemented, but it is not yet expected to work correctly and may change a lot in the future.
The dockable window has been added to all components of LibreOffice, which can be enabled in the menu (see Figure 2) under Help / Development tool (this location and the name can still change). The development tool code is all contained under svx/source/devtools/ where the docking window is implemented in DevelopmentToolDockingWindow.cxx.
DOM tree view (Left-hand)
The idea of the DOM tree view is to provide a useful subset of the DOM objects, that the user can quickly access and inspect. Without this, the users would have to traverse to the objects they are interested in inside the object inspector itself, which may not be as straight forward to the users that aren’t familiar with the DOM, and even if they are, the DOM subset still makes it easier.
The DOM tree view as currently implemented (see Figure 3), has a root “Document” element always available, which represents the root document UNO object, from where you can traverse to any other relevant UNO object in the DOM tree. Other available objects depend on the component.
In Writer the available objects are:
- Graphic Objects
- Embedded Objects (OLE)
- Style Families & Styles
- Shapes (per sheet)
- Charts (per sheet)
- Pivot Tables (per sheet)
- Style Families & Styles
In Impress and Draw:
- Pages / Slides
- Shapes (per page / slide)
- Master Slides
- Style Families & Styles
If there are other object(s) you would like to see in the DOM tree view, please let me know.
The main implementation file for the left-side DOM tree view is DocumentModelTreeHandler.cxx in the svx/source/devtools/ folder. For each node of the tree view, there is an object attached (subclass DocumentModelTreeEntry) to the node, which is responsible to fill the child nodes and provide the UNO object of the current node. The whole DOM tree view is populated on-demand, when the tree is expanded. This makes sure that we don’t take a lot of time inserting the whole object tree before-hand and have a more up-to-date view of the UNO objects.
All the code is present in the LibreOffice master repository. Please try out the development tools in a recent daily build. Any comments or suggestion are welcome.
Next part - point&click functionality
Partial support for the point&click has already been added. There is a selection listener added, which remembers what the selected object in the document is. This object is the available in the DOM tree view under the name “Current Selection”. If the user clicks on the node in the tree view, then the right-hand object inspector shows the UNO object.
To be continued..
Tuesday, October 13, 2020
In Draw, a PDF document can be opened using the PDFium library for rendering, where each page in the Draw document contains an rendered image from the PDF. This mode is useful for viewing PDFs and allows for the best fidelity. With viewing, there is also a need to review and comment and this is where PDF annotations come in as adding the support for the PDF annotations and to support a review based workflow has been one of my recent task at Collabora Productivity.
PDF supports a wide variety of annotations, but we don't support all of them in Draw. What we do support are comments, which are similar to pop-up note annotations in PDF, so the easiest is to add those first. To be able to use pop-up notes in Draw, we need to import them. This is done at import by using PDFium after we created the PDF graphic for rendering. In Draw, we then insert this as comments and so we get the basic support for manipulating with annotations, but how to save the changes? PDF export already supports saving comments as annotations, so this mostly already works (I needed to fix some bugs and add support for saving all needed properties).
|Figure 1. Pop-up Note annotation in PDF viewer (Evince) and Draw|
How the output compares between a PDF viewer and inside LibreOffice can be seen in Figure 1. All this is available in LibreOffice master and should be included in LibreOffice 7.1.
What about other annotations that are supported in PDF? The work to add those is ongoing and the recent success has been adding support to draw vector graphic annotations like polygon, ink (freehand), squares (rectangles) and circles (ellipses). So the idea is that instead of the usual marker for a comment, we draw a vector graphic that we read from the PDF annotation.
The first thing I needed to do is to extend the PDFium library, which didn't support reading all polygon vertices and ink strokes from the document. What also wasn't supported is reading the border information, which is needed for line widths.
Next thing is extending the import to read the geometry data, so I dded a special PDFAnnotationMarker class for that. Then the geometry data needs to be stored on the sd::Annotation class (implementation of XAnnotation, but I didn't extend XAnnotation at this point as it wasn't needed - yet) . Drawing is performed in AnnotationTag, where we create a new OverlayPolyPolygon object, that is responsible for creating the Primitive2D for the marker. What we get after this is done is shown in figure 2.
|Figure 2. Multiple annotations in PDF Viewer and Draw|
This work will shortly be merged to the LibreOffice master.
Wednesday, January 22, 2020
Thanks to the Dutch Standardisation Forum for financially sponsoring and Collabora Productivity in cooperation with Nou&Off for the work on implementing this specification into LibreOffice.
|Figure 1: PDF Export|
|Figure 2: Accessibility Check in menu|
The second part is an accessibility check functionality, which traverses the document structure and gather all possible accessibility issues. The accessibility check can be run manually through the menu under Tools - Accessibility Checker (see figure 2), or it will be triggered after PDF export dialog, if the PDF/UA support is enabled. The accessibility issues are presented in a dialog (see figure 3).
|Figure 3: Accessibility check dialog|
The checks that are (currently) implemented are:
- Check that the document title is set.
- Check that the document language is set, or that all styles that are in use, have the language set.
- Check all images, graphics, OLE objects for the alt (or title in some objects) text.
- Check tables don't include splits or merges, which aren't allowed by the specifications. The table should be
- Check for fake/manual numbering (not using integrated numbering). For example writing "1." "2." "3." at the beginning of the paragraphs.
- Check that hyperlink text is not a hyperlink itself - hyperlink should be described.
- Check for the contrast between text and the background. The algorithm is described in the WCAG specification.
- Check for blinking text, discouraged for the obvious reasons.
- Check for footnotes and endnotes, which should be avoided.
- Check for heading order. Order of the headings must increase incrementally with no skips (for example Heading 1 to Heading 3, skipping Heading 2).
- Check, if text conveys additional meaning with (direct) formatting.
The PDF/UA support will be included in LibreOffice 7.
Monday, April 23, 2018
Regarding the UNO API of XGraphicObject - what is left to do here is to properly clean up the uniqueID, as it is not possible to use it anymore for anything else as a uniqueID (used only in filters for the image names, if the name is not yet known).
Managing memory used by images
Previously the memory managing was done on the level of GraphicObjects, where a GraphicManager and GraphicCache (see figure 1) were responsible to create new instances from uniqueID and manage the memory usage that GraphicObject take. This is not possible anymore as we don't operate with uniqueIDs anymore, but always use Graphic and XGraphic objects (in UNO), so we need to manage the creation of Graphic object or more precisely - ImpGraphic (Graphic objects are just ref. counted objects of ImpGraphic).
Graphic swapping and swapping strategy
For the new code external swapping directed from SdrGrafObj and SwGrfNode was removed, so they can't influence when swapping will happen (maybe in the future they can provide hints when it is a good time to do swapping). There is now a global timer which triggers checking of all Graphic objects if any of them can be swapped out in case we exceed memory limit. Same code is triggered when a new object is created too. A object will be swapped out if it is not used for a certain amount of time. Each object tracks the timestamp when it was last used.
A swap-in happens if the object is swapped-out (obviously) and certain data is needed (under-laying bitmap, animation or metafile). This is checked at the same code-path where the timestamp updating happens.
The new swapping strategy is relatively simple - if a lot of memory is needed by graphic objects in a certain time, we let it use it and don't try to over-aggressively try to free it. In the past this cased swap-out and swap-in cycle that made the application completely unusable. In the future, external hints when a certain Graphic object can be swapped out may be added, so we can perform swapping more effectively. There are also several other ideas which will increase performance and reduce memory usage that can be implemented now with the new hierarchy where most all of the swapping is contained inside the Graphic itself, but all of this is currently out of the scope of this work.
Other changes to Graphic
The GraphicFilter didn't allow something like this, so I needed to add a new method, which doesn't actually load the image, but just gathers what kind of the image is loaded and its metadata (image size) and creates a GfxLink object that includes the (compressed) image data. The metadata is needed as we don't want to actually force a load when this basic information is requested. Actually we want to load the image as late as this is possible.
Another issue is also that we can have an external image (loaded from a file or even URL on the internet). The issue is similar to the lazy loading scenario, but it is different that a Graphic now must know the URL with which it was created and can be created completely empty (no loading of any kind). The reason for this is that loading is directed by the LinkManager, which is part of the document model. For security reasons the LinkManager can not allow that a Graphic is loaded so loading is directed by the LinkManager on demand (first usage). LinkManager also takes care of all URLs of various external resources. The user can look at those resources and change the URL of them or trigger an update. Changing URL and updating an object was previously done in SdrGrafObj and SwGrfNode, but now this is moved to the common code in Graphic object where SdrGrafObj and SwGrfNode only direct what to do. There are still rooms to improve things here, however not the scope of this work.