From: Mark R. <mr...@st...> - 2001-12-14 08:23:50
|
This document describes proposed changes to the Sketch 0.7 infrastructure to provide support for complex text objects. Comments should be sent to the sketch-devel mailing list. Mark ----------------------------------------------------------------------- NOTATION Unless otherwise indicated, the following abbreviations of class names are assumed: editor Sketch.Editor.doceditor.EditorWithSelection canvas Sketch.UI.canvas.SketchCanvas view canvas (subclass of view) object any graphics or text primitive textobject Sketch.Graphics.textbox.TextBox texteditor Sketch.Editors.textboxtool.TextBoxEditor texttool Sketch.Editors.texttool.TextTool document Sketch.Graphics.document.EditDocument MOTIVATION Editing textobjects within the canvas' current redraw mechanism is problematic, since each change to the object (e.g. inserting a character) requires that the textobject and all overlapping objects be redrawn. This can be slow even for a simple document on a fast computer, resulting in annoying flicker and poor responsiveness to user input. Flicker can in principle be solved by double buffering the entire redraw operation (which is done automatically if libart is used), but the redraw time lag still presents a serious problem. BACKGROUND When the user edits a textobject (e.g. by pressing a key to insert a character), the command ultimately arrives at the texteditor class associated with the object. Upon successful completion (defined by returning a non-null undo object), the document issues a REDRAW signal with the clipping region set to the bounding rectangle of the object. When the canvas receives this signal, it takes charge of redrawing the window within the clipping region. The region is cleared and all objects overlapping the region are drawn in order from the bottom to the top of the object stack. This means that if the textobject is partially behind other objects, it remains occluded during editing. PROPOSED SOLUTION The main idea is to pull the textobject being edited out of the normal stacking order and put it 'on top'. This task would be tricky if taken literally, and even so the REDRAW signal would still cause other objects beneath the textobject to be redrawn after each change. Assuming the textobject is already 'on top', and that we already have saved a copy of the window after all other objects of the document have been drawn, it is very easy and fast to do double buffered drawing of the textobject without redrawing any other objects. To accomplish this, we need (a) to avoid drawing the textobject during a normal document redraw, (b) to save a copy of the window, and (c) to bypass the normal REDRAW signal and allow the editor to redraw the textobject and any tool-specific graphical elements (e.g. a box around the textobject during editing). These points are accomplished as follows: (a) PreRedraw and PostRedraw methods are added to the editor, tool, and texteditor classes. The editor's PreRedraw method is called by the canvas immediately before the view is redrawn. Similarly, the editor's PostRedraw is called after the redraw is complete. The editor in turn calls the current tool's PreRedraw and PostRedraw methods, respectively. In the tool base class these methods are defined to do nothing by default. The texttool overrides these and passes the calls along to its texteditor instance, if it exists (i.e. the editor is active). The texteditor uses these calls in order to pull the edited textobject to the top and do redraws during editing. In the texteditor class, PreRedraw simply tells the textobject to ignore the next redraw operation. This works fine when using gtkdevice, but needs extra help for libart since the libartdevice handles the drawing not the object. In fact, libart needs extra support anyway for complex text objects, which will be described later. Suffice it to say that the libart code simply checks with the textobject to see if it should not be drawn. (b) Immediately after redrawing the document, but before calling PostRedraw, the canvas saves a copy of the screen using new code added to the GCDevice class. This 'back-buffer' is maintained at all times and may have some interesting uses outside of the current discussion. (c) When the texteditor completes a change, it informs the document to issue the EDIT_REDRAW signal rather than the REDRAW signal. The canvas, in turn, calls editor.PostRedraw rather than the usual full redrawing method. The texteditor's PostRedraw method initiates a double-buffer using the 'back-buffer' as the base image, asks the text object to redraw itself, adds additional graphical elements such as a bounding rectangle, and finally copies the double-buffer to the screen. Simple, no? The result is nearly instantaneous response with no flickering for textobjects containing a few hundred characters on an 800 MHz system. The main delay for long textobjects comes from retypesetting the text after each change, not redrawing. LIBART AND MULTISTYLE TEXT The current libartdevice treats text like any other curve and applies a single style when rendering. To support multi-style text objects, a new object flag (is_CompoundText) is added to the Protocols class to distinguish textobjects supporting multiple styles. Objects with this flag set are drawn using draw_compound_text() rather than draw_curve(). This routine first checks whether the textobject should not be drawn in order to bring it 'to the top' for editing (see (a) above). When drawing, it renders the text in blocks of uniformly styled characters (the blocks and associated styles are maintained by the textobject). IDEAS AND ISSUES This implementation evolved incrementally while I worked to integrate the multi-line/style text code I wrote for Sketch 0.6 into the 0.7 code base. While I like the overall approach, some of the details could likely be improved. For instance, PostRedraw is used both at the end of a full redraw and in response to an EDIT_REDRAW signal. These distinct functions should probably be handled by different methods. It is not clear how useful these changes will be for non-text objects. In principle they may be useful whenever editing objects that require more than simple inversion (XOR) drawing to indicate the current state. The new 'back-buffer' feature could be used to enhance some of the current inversion drawing such as handles. One idea, taken from Dia, is to draw handles in color with transparency, using the back-buffer for alpha-blending and erasing. CURRENT STATE All the code described here exists, integrates with the current CVS version of Sketch, and works in conjunction with a new multi-line, multi-style text object. The text object and supporting infrastructure changes have been publicly released for testing purposes, and can be downloaded as patches against the Sketch CVS source from http://sketch.sourceforge.net/files/src/patches/0.7. To provide a sense for the magnitude of the infrastructure revisions, the number of changed or added lines in the most significant modules is approximately: Sketch.UI.gtkdevice : 90 (double-buffering code) Sketch.UI.libartdevice : 70 (draw_compound_text method) Sketch.UI.view : 7 Sketch.Graphics.document : 6 Sketch.Editor.doceditor : 4 Sketch.Editor.tools : 4 |