A few days ago I got back from the cheerful Danish city of Aarhus. I was there to participate and present at the LibreOffice Conference of this year.
My talk was one of the ten ones for the GSoC panel where students would discuss their work from the Summer (for more info about mine you can read it here on this blog).
Besides the GSoC panel (and, of course, a ton of coffee) the conference offers a great variety of talks on any the most disparate aspects of LibreOffice.
A miriad contributors explained what was wrong and got fixed during the last year, features that used to be dreams and now are not anymore, future dreams and issues to work on soon.
Other talks were less development-oriented: from LibreItalia showed how the Italian army and several Italian towns' administrations are switching to LibreOffice to a presentation on the importance and the state of vertical writing features for Japanese LibreOffice users (and not only).
The talks are a great opportunity to learn about the general architecture of the (humongous) project and its recent changes. However, the best part of the conference is the hanging out. From sharing streams of coffee during breaks and beers at nights to a bunch of coding at the HackFest.
Looking forward to be there next year.
I made a screencast of the current prototype.
You can watch it here on Youtube.
Some of the major current limitations:
Lack of support for Undo/Redo
basic cursor motion when editing presents issues
(e.g. cursor anooyingly repositions itself at beginning of box while editing -
this is why, in the video above, I use pasting instead of inserting single characters).
advanced cursor motion among boxes at appropriate user input
(e.g. when cursor gets past end of text in a box it could reappear in the next one)
In my last post I described what I thought were necessary properties of an implementation of text chaining. The following seems to be a natural consequence of those observations: given a chain of boxes, we need that editing in each of them is global to the whole text in the chain. We do not only need that changes in one single text box propagate through the other boxes, but also that any type of selection (and most other user actions) is extended to the text in the whole chain. For example, if the user presses Ctrl-a in any of the boxes in the chain we expect to see the text in all the boxes to be selected (instead of a "local" behavior that selects only the portions in the box in focus).
A Timeline
With this perspective in mind, here is an attempt for a plan of attack:
Week 1 (from May 25 to My 29): Experimenting. My goal is to have a pointer that moves seamlessly though boxes as if it were just the same box.
Weeks 2 & 3 (from June 1 to June 12):Make boxes share the whole text (paragraph-wise). We should have that behavior as selection should be global to boxes in the chain (adding and removing text? Maybe, that might be more complicated though). Also, we expect to have this with boxes having chunks of the global text that are whole paragraphs (since this seems simpler than more fine grained breaks).
Week 4 (from June 15 to June 19): Integrate overflow check and automatic transfer of text (paragraph-wise). We want editing in one box to propagate in real time in the others. Since overflow detection code from last year seems quite solid it might be feasibe to almost use it as it is. Breaks are still at level of paragraphs at this stage.
Weeks 5 & 6 (from June 22 to July 3): Make breaks occur at word level instead of paragraph level.
The week starting July 5 can then be used for cleaning up the code to integrate it in master andfix other problems that can show on the way. If this schedule holds, this leaves several weeks to work on hyphenation in the last part of this GSoC.
In this post I will describe some ideas on what we expect from chained text in Draw giving a high-level, pseudo code specification of it.
See my last post here for an overview of the problem.
It was all wrong
In my last post I described some of the necessary changes we wanted to get in the project this year. One of the main problems was that now text is moved only after the user exits from editing mode and the current implementation is only sensitive to edits occurring in the first box in the chain (if you delete content in a box in the middle of a chain, nothing is going to happen in the others).
In that post, however, I don't describe precisely enough what we want from a future implementation.
The expected behavior
The behavior we expect from chaining text in Draw is the same we have in Writer already implemented now: when user edits text (adding text, removing text, changing formatting and font size or whatever) in any of the boxes in the chain the implementation should:
restructure the whole text in the chain, i.e. redistribute appropriately the text among the boxes (I will call this process "spreading");
position the editing pointer at the right updated spot (e.g. if the user pasted some text overflowing in a next box the pointer should be in the latter at the end of the pasted text).
A high-level functional view of spreading
Currently I have no clue of how the mechanism above should be implemented in editeng, svx and the other modules responsible for text dynamics in LO Draw. If we wanted to idealize the process and LO internals, I would now like to propose the "only" two components we may need to achieve the result above. Notice that, for the time being, we want to understand the issue of what before even touching the problem of how, so I will make little or no consideration of efficiency or compatibility with existing architecture whatsoever. The description is in some kind of statically typed pseudolanguage, none of the data types I use refer to real data types in LO code.
The Spreader
Suppose the we have some text T0 already distributed, err... spread, among boxes in a logical chain of text boxes [b_1, b_2..., b_n]. Suppose now that the user edits it (e.g. pasting some content in the middle of it) and we get a new text T through the chain; consider T as the concatenation of the content of all the boxes. Assume that text objects T0 and T contain also information about formatting of the text, the box space it requires for fitting, paragraph breaks, etc... At the same time assume that each box object b_i contains info on their respective sizes.
Vector<Texts> spreadText(TextBoxChain Bxs, WholeText T)
{
/*
Given in input chain of boxes Bxs = [b_1, b_2,...b_n] and text T
should return a vector of texts [t_1, t_2,... t_n] such that
1) t_1, t_2 ...t_n give T if "concatenated" together
2) each t_i fits inside box b_i
*/
}
I hope the intuition behind the function spreadText is clear enough. The function that spreads the text does what you expect from it: assigns portions of the text to some box so that all fit within the box (let us assume that there is enough space for the whole content for now). Ideally, such a function could be used in the middle of the formatting loop somwehere in ImpEditEngine:
... // new editing has occurred yielding text T
newTexts = spreadText(Bxs, T)
// Replace T with newTexts
...
It is an open question how plausible it is the code we'll write will actually look like that.
Moving the pointer
After spreading the text among the boxes we need to figure out where the editing pointer is now located. For example, suppose the pointer is at the bottom of the first box - say the tenth word in the paragraph - and the user now copies and paste two more words inside it making the text overflow. From a global perspective, the pointer is at the end of the pasted text (after the twelveth word) but where we want the pointer now is in the second box at the end of the first (or second, depending on where overflow occurs) box. Thus we need to be able to map from a "global" position to a "local" position in a specific box and one function like the one below can come handy. (Note for the attentive reader: as far as I remember, in editeng positions are not measured in "words", so take that just for sake of example)
Pair<TextBox, TextPos> global2localPos(TextBoxChain Bxs, WholeText T, TextPos globPos)
{
/*
Return a pair (b_j, locPos) such that if
[t_1, t_2,... t_n] = spreadText([b_1, b_2,...b_n], T)
then globPos in T corresponds to locPos in t_j.
*/
}
One building block from the other
It seems intuitive to me that the spreading function as described above can be implemented (inefficiently) using global2localPos:
Vector<Texts> spreadText(TextBoxChain Bxs, WholeText T)
{
foreach word w in T:
posOfWrd = getStartPos(w,T)
(bx4w, _) = global2localPos(Bxs, T, posOfWrd) // Haskell-like pattern syntax (we ignore _)
// Assign w to bx4w
}
So are all problems solved?
Obviously not. There are still a million questions to be answered (and as many to be asked), among which:
what would "WholeText" and "Text" look like in editeng? We need to work at the level of words but many classes in editeng might work at the level of paragraphs.
as a related question to the previous one: Can we break and recombine paragraph objects easily?
Do we have all the information on sizes of text and boxes readily accessible somewhere in the code?
In this post I describe part of my project for GSoC 2014: automatic flow of text from one box to another in Draw/Impress. I outline some of the current features and limitations and describe future developments.
An Overview of Dynamic Text Flow
Imagine having a text box containing too much content for its size. In some documents it is not possible or convenient to solve the problem resizing the box or the font. In these situations we may want the overflowing text to move to some other area in the document, such as another text box logically linked to the overflowing one.
To have a concrete idea of the kind of behavior we expect, look at the following screenshots from one of the latest versions of the prototype (written during GSoC 2014).
When the user enters too much text for a single box this is automatically distributed among the others until it fits (or until there are no more boxes to fill). Notice that (currently) the text is redistribited paragraph-wise: each box receives as many paragraphs as it can contain - in this case one per box. Also notice that the rightmost box in the second screenshot is filled because the second and third paragraphs, once moved from the leftmost box, do not fit completely in the central text box; thus chaining occurs recursively.
A more desirable behavior: beyond the tiranny of paragraphs and "editing mode"
The screenshots above give an idea of what the current prototype does, let us now look at some of possible improvements. There are two major flaws in the current implementation:
text cannot be broken at arbitrary locations, only at the end of paragraphs;
user has to stop editing to see results.
We would like that text could be automatically moved anywhere between two words, not only between paragraphs. Also we would like such "automatic transfer" to occur seamlessly in real time (Writer, for example, implements such behavior), not only after editing is done. This is a serious problem both because it reduces the WYIWYG-ness for the user (who has to stop and check her results every once in a while) and because it complicates the design of the feature increasing the number of questions to be answered; all these questions hardly have a sensible answer from a UX point of view: should the whole text appear only in the first box (as it is the case now)? If yes, then shouldn't the next logical boxes be special and should not be editable?
The main goal of my GSoC 2015 project is to achieve the two properties above. As a starting point on deciding how to build them, I will now get back to the current implementation describing what it does under the hood.
Current prototype: a skeleton
The following is a overall scheme of how the current implementation works. The reader will notice how the general idea is extremely simple.
When user is editing: keep track of whether (and at which paragraph) the text exceeds the box size.
When user is done with editing: when creating "non-edit-mode" text of the box, if overflows occurred:
"cut" the overflowing paragraphs from text in current box;
set the overflowing paragraphs as text in the next logical box and recursively call checks on overflows.
Conclusions
The current prototype for text chaining should be modified to handle between-words breaks and real-time modifications. Going in this direction will require abandoning a good part of code written last year (or at least the approach outlined above).
Next steps:
finding out how to test for overflow by words only; currently complete paragraphs are used to test for overflowing content.
finding out how to possibly modify content of text boxes in real time; current code depends completely on the user exiting editing mode.