Skip to content

Implementing Rich Text in Drawy

Sunday, 2 August 2026  |  Abdelhadi Wael

for the past few months i have been working on implementing rich text in drawy as part of google summer of code 2026.

Overview on the old implementation:

The old text logic depended on a manual implementation, almost everything was built from scratch using a string. Placing the cursor, caret movement, selection operations, line and word boundary detection, editing operations were all implemented from scratch.

I found implementing all of this impressive, but while it worked for plain text editing, this code was hard to maintain / extend, and it would have been nearly impossible to add the features that were added.

Phase 1: Refactoring
Goal: Rewrite without breaking anything

I replaced the string with QTextDocument , all the things that were written manually before was gone and now editing operations are handled by QTextCursor.

OperationHow it was doneHow it is done now
Placing cursorBinary search over the line, measuring substring widths with QFontMetrics to find which character the click landed on.documentLayout()->hitTest()
Caret movementManual index calculation increment/decrement; up/down walked to the previous/next \ncursor.movePosition(Left/Right/Up/Down/WordLeft/WordRight)
SelectionTwo indices, with selection rectangles rebuilt line-by-line using QFontMetricsQTextCursor anchor/position, drawn via QAbstractTextDocumentLayout::Selection
Word/line boundary detectionManual scan for separator characters or \ncursor.select(WordUnderCursor / LineUnderCursor)

Phase 2: Rich Text

1. adding IME support
An Input Method Editor (IME) is how the OS handles input for languages that can't be typed with a single key press per character without it user can't type with Arabic, Japanese, Chinese, dead keys, etc The OS sends this as a QInputMethodEvent, which Drawy previously didn't handle. IME now works correctly here is demonstration of how it works with CJK input:

https://youtu.be/2W2R9udyKKU?si=BLwUmLpcg_E9XE9T

2. Properties:
user now have much more control over properties i added support for choosing font family, font style, text alignment, list format.

per range formatting is also now implemented meaning user can set different colors, size, font, style to different parts of the text.

3. Word Wrapping:

When the text box is resized, text now wraps to fit within its bounds.

https://www.youtube.com/watch?v=AqGJd5Bf6sw

4. undo / redo:

text history is managed by two stacks: drawy global stack, and QTextDocument's internal one. Only one is active at a time. While the user is editing text, the global stack is disabled and QTextDocument's internal stack takes over, handling text edits and property changes, etc. Once the user exits edit mode, the internal stack is disabled again, any changes go through Drawy's global stack instead, stored as HTML.

5. Spell checking

In my proposal, I didn't plan much time for spell checking, I thought that I'd just wire Sonnet in and be done with it. I later found out that Sonnet's ready-made integrations are built for QTextEdit and QML, so I had to write my own spell-checking pipeline instead.

  1. Each block (paragraph) is passed to Sonnet::gusser to guess its language and determine which dictionary to use.

  2. Text is split into words using QTextBoundaryFinder.

  3. Sonnet::Speller checks whether each word is misspelled.

  4. Misspelled words are highlighted using QSyntaxHighlighter.

added new menu to settings using Sonnet::ConfigWidget to allow customizing spell checking:

Note: Everything mentioned in this blog is on the gsoc2026 branch currently and will be merged once GSoC is completed.

Gratitude:
I am very grateful to my mentor Laurent Montel for his time and effort guiding me throughout this project.