Aztec Native Editors Technical Overview

IMG_0024_framed

Introduction

Aztec is a new native rich-text & HTML editor with focus on performance, stability and flexibility. It’s available for both iOS and Android. The primary motivation behind the project was to provide a great editing experience for the mobile WordPress app but it’s being developed as a standalone open-source library, so other developers can use it and build something on top of it.

This post gets more into the technical features and implementations of each of the two flavors of Aztec. The architectures are both complex and this overview should help start the process of understanding how all the pieces work together.

Want to get more involved with testing Aztec? Feel like using Aztec in your own app and contributing back? Read more about Aztec and the release here.

User Features

There are a set of core features that Aztec is aiming to support with its initial release. Both versions of Aztec (iOS & Android) have used very different native implementations/libraries/techniques but the goal is to provide a similar user experience in your app.

Follow is a list of those features – we’ve noted features to be available (TBA) soon if they’re not ready yet.

  • Rich-text & raw HTML editing support with built-in synchronization
  • HTML formatting & color highlighting
  • Paragraph Indentation and alignment. (Android-TBA, iOS-TBA)
  • Ordered and Unordered Lists
  • Nested lists (iOS-TBA)
  • Blockquotes
  • Image support
  • Video support (Android-TBA)
  • Image alignment and text-wrapping (Android-TBA, iOS-TBA)
  • Pre tags. (iOS: renders but doesnt allow adding)
  • Font Styles: bold, italic, strikethrough, underline
  • Font Coloring (Android-TBA)
  • Header styles
  • Line breaks and paragraph breaks
  • Unknown HTML tag editing (iOS: renders but doesn’t allow editing the contents in Visual Mode)
  • Horizontal rule (HR)
  • Code blocks (iOS-TBA)
  • Beautiful HTML source editor (iOS: still not styled, or “beautiful”)
  • Emoji support
  • Spell-check
  • Voice dictation
  • Edit history (undo/redo) (iOS-TBA)
  • RTL language support (Android-TBA, iOS-TBA)

Technical Deep-Dive

iOS

Architecture

  • Aztec.TextView [ GitHub ]
    • Interacts with HTML using the new setHTML() and getHTML() methods.
    • Allows the client App to set the HTML contents, and not have to worry about the parsing.
    • Inherits from UITextView so:
      • It supports RTL text orientation, unicode (and emojis!), dictation.
      • Editing can be switched ON and OFF
      • Can be placed anywhere in your app.
  • Aztec.FormatBar [ GitHub ]
    • Native UI component inheriting from UIView
    • Aesthetically customizable through Aztec.FormatBarItem
    • Logically customizable through Aztec.FormatBarDelegate
  • Aztec.TextViewAttachmentImageProvider [ GitHub ]
    • Allows rendering custom NSTextAttachment subclasses.
    • Used in the example App for custom HTML-comment rendering.
  • Aztec.TextViewMediaDelegate [ GitHub ]
    • Aztec relies on your App’s own networking stack.
    • Does not have network library dependencies of its own.

Technologies

  • Written entirely in Swift 3.
  • Currently targeted at iOS 9+ but will most likely be 10+ when fully shipped.
  • Available through both CocoaPods and Carthage.
  • Currently includes 326 unit tests and 16 UI tests.

Third-Party Dependencies

Aztec iOS was developed to try to keep the dependencies count as low as possible. There are currently only two dependencies:

Challenges We’ve Faced

Synchronization of NSAttributedString and our DOM representation

One of the main goals behind Aztec was to provide a visual HTML editor that would maintain its HTML source intact as much as possible.

The reason is that HTML tends to be very sensible to changes when combined with custom CSS or even with javascript.  We recognized that it would be important to not lose any information between the initial input, and the final output after visually-editing it.

We wanted a file with a few localized edits to look almost the same as the original file.

Part of the problem with not losing information was that NSAttributedString uses a one-dimensional approach to styling, whereas the DOM tree uses a bidimensional approach.  In straight HTML there’s a parent-child relationship between tags (and text-styles) that simply does not exist in NSAttributedString.

Our experimental approach was to basically maintain an NSAttributedString as a sort quick visual cache to show on screen, and to map the edit operations to a parallel DOM structure.

Needless to say, this is a very difficult approach to maintain because there’s a big risk of de-synchronization between the cache and the DOM structure.

We’ve done a lot of work to ensure this is not a problem, and to some degree we’ve been successful, but we’re still suffering a bit from maintainability issues due to the complexity of the approach.

There are some upcoming changes that are aimed specifically at making the code easier to maintain and test.

Newlines

HTML has a lot of implicit newlines we need to deal with.  They exist at the end of block-level elements (block level means they’re elements that exist in their own line).

We call them “visual newlines” because they are only shown to the user in visual mode.

When editing regular content, you can simply edit all of the text nodes involved.  The problem with visual newlines is that editing them means more complicated actions need to take place.

Think about removing the trailing visual newline in a list.  It basically means extending the list to include what’s after it, up to the next paragraph break, and modifying the DOM structure to merge those elements.

We’ve done a lot of progress with handling these visual newlines.  Quite a good amount of effort has been put into making them work, but we think more effort will be needed for specific case we may not have fully considered.

What We’re Really Proud Of

We’re really proud of the work we’ve done to create a generic HTML editing component, that doesn’t feel slow or clunky.  There’s simply no comparison with how it feels when compared to other mobile editors.

The amount of time we’ve put into the architecture has paid off as well.  We still need to improve some sections of the code to make them cleaner and easier to maintain, but for the most part we feel pretty good about the quality of Aztec iOS, and most of the code is properly documented to make it easier to navigate.

The architecture was designed to be easy to write automated tests for.  We’ve been encouraging ourselves to try and write at least one unit test triggering each bug report we get, which has proved to be a key factor in avoiding regressions.

Android

Architecture

Aztec was inspired by a library called Knife, an open-source text editor. Aztec consists of several key components:

  • AztecText [ GitHub ]
    • Native UI view inheriting from EditText
    • The visual editor component
    • Styled text in a form of Spannable
    • Customizable styling with XML attributes
  • SourceViewEditText [ GitHub ]
    • Native UI view inheriting from EditText
    • The HTML editor component
    • Represents the styled text of AztecText in HTML
    • Customizable styling with XML attributes
  • AztecToolbar [ GitHub ]
    • Native UI view inheriting from FrameLayout
    • Used in conjunction with AztecText
    • Applies text styles in visual mode
  • AztecParser [ GitHub ]
    • Non-UI internal component
    • Translator between AztecText and SourceViewEditText
    • Converts styled text (Spannable) to HTML and back
  • ImageGetter [ GitHub ]
    • Interface for loading images implemented by an external module
    • Supplied by the Aztec library client
    • Not dependent on specific networking stack
    • Two sample loader modules available

All of Aztec’s features & capabilities are used and demonstrated in a sample Demo App, which is part of the repository.

Technologies

  • Written primarily in Kotlin
  • Developed using the latest release of Android Studio
  • Library minSdkVersion 14
  • Contains almost 350 unit tests

Third-Party Dependencies

Hurdles

  • Consistent and correct Spannable-to-HTML and HTML-to-Spannable parsing
  • List-editing (adding new items, deleting items, closing a list, etc.)
  • HTML element hierarchy & order preservation
  • Visual newlines around block elements
  • Inconsistencies of certain behavior across different Android API versions

Challenges We’ve Faced

One of the biggest challenges for us was avoiding regression errors by implementing new features & bug fixes. For example, things like list or quote editing involves a fair amount of character-level processing. It is key that the element and newline spans are applied to specific locations around certain characters. This system is sensitive to changes, so a small bug fix in one part of the editor often triggered a cascade and broke things in other parts. To help us overcome this we had to write numerous unit tests for every feature and edge case we could think of. It gave us confidence to see the tests passing after changing something and it would have been nearly impossible to progress without them.

Conclusion

We’ve come a long way since starting on the project in June of 2016.

Aztec has been integrated in the WordPress beta release and with every additional feature & bug fix it is becoming more usable in real world applications. We’ve designed the library to be simple to integrate and we’re continuing to work on the plug-in architecture so that it will be even more extensible and customizable in the future.

Help us test the Aztec native editors by using them in your own project and contributing back. Check out the original announcement post under “How should I report?” for more information on how to get involved.