|
| 1 | +# Introduction |
| 2 | + |
| 3 | +Aztec provides a UI component to display and edit HTML content in a performant way. It does not implement its text layout functionality. Instead, it builds on top of Apple's [UITextView](https://developer.apple.com/documentation/uikit/uitextview) provided by the UIKit framework. |
| 4 | + |
| 5 | +The library is composed of two main systems: |
| 6 | + - [TextView](https://github.com/wordpress-mobile/AztecEditor-iOS/blob/develop/Aztec/Classes/TextKit/TextView.swift) a user interface component that allows the presentation and editing of the HTML |
| 7 | + - HTML converters that transform raw HTML to [NSAttributedString](https://developer.apple.com/documentation/foundation/nsattributedstring) and vice-versa |
| 8 | + |
| 9 | +You can use each of the components separately, but they are all composed under the TextView class to provide a single element to display and edit HTML. |
| 10 | + |
| 11 | +# TextView |
| 12 | + |
| 13 | +[TextView](https://github.com/wordpress-mobile/AztecEditor-iOS/blob/develop/Aztec/Classes/TextKit/TextView.swift) is the core class of the system. It's a `UITextView` subclass that implements custom subclasses of `NSTextStorage`, `NSLayoutManager`, and `NSAttributeString` attributes to allow HTML editing. |
| 14 | + |
| 15 | +There are two main tasks that this class handles: |
| 16 | + |
| 17 | + - Maintenance of the custom `NSAttributedString` attributes and attachments as the user edits the content to represent a valid HTML string. |
| 18 | + - Presentation of the custom attributes, like lists and quotes |
| 19 | + |
| 20 | +The TextView and [TextStorage](https://github.com/wordpress-mobile/AztecEditor-iOS/blob/develop/Aztec/Classes/TextKit/TextStorage.swift) classes are responsible for the maintenance of the `NSAttributedString`. |
| 21 | + |
| 22 | +They handle user manipulation of formatting (strong, emphasis, lists, blockquotes), embeds (images, videos, separators), and the user interaction with them (line breaks, composition). |
| 23 | + |
| 24 | +The formatting is implemented by the `toggle(formatter, atRange)` method in combination with AttributeFormatter objects. We split the formatter objects into the following categories: |
| 25 | + - [AttributeFormatter](https://github.com/wordpress-mobile/AztecEditor-iOS/blob/develop/Aztec/Classes/Formatters/Base/AttributeFormatter.swift) that handle characters attributes like bold, italic, |
| 26 | + - [ParagraphAttributedFormatter](https://github.com/wordpress-mobile/AztecEditor-iOS/blob/develop/Aztec/Classes/Formatters/Base/ParagraphAttributeFormatter.swift), that control paragraph attributes like lists, blockquotes, headings, etc. |
| 27 | + |
| 28 | +For embeds, like images, videos, separators, tables, etc we use subclasses of the [NSTextAttachment](https://developer.apple.com/documentation/uikit/nstextattachment) object: |
| 29 | + - [MediaAttachment](https://github.com/wordpress-mobile/AztecEditor-iOS/blob/develop/Aztec/Classes/TextKit/MediaAttachment.swift), |
| 30 | + - [RenderableAttachment](https://github.com/wordpress-mobile/AztecEditor-iOS/blob/develop/Aztec/Classes/TextKit/RenderableAttachment.swift) |
| 31 | + |
| 32 | +The `replace(at:NSRange, with: NSTextAttachment)` handles the insertion of embeds with multiple helpers methods to handle each specific type of embed. |
| 33 | + |
| 34 | +The attachment objects work by delegating their presentation using the following protocols: |
| 35 | + - [TextViewAttachmentDelegate](https://github.com/wordpress-mobile/AztecEditor-iOS/blob/develop/Aztec/Classes/TextKit/TextView.swift#L7): for media elements where the data can be be downloaded asynchronously from the web. For example `img` and `video` elements. |
| 36 | + - [TextViewAttachmentImageProvider](https://github.com/wordpress-mobile/AztecEditor-iOS/blob/develop/Aztec/Classes/TextKit/TextView.swift#L75): for HTML elements, that have a static presentation like `HR`, HTML comments, `More`, `NextPage`. |
| 37 | + |
| 38 | +Another essential task is interaction with user input and handling new lines, deleting list items, and indents around: |
| 39 | + - Lists |
| 40 | + - Quote |
| 41 | + - Paragraphs |
| 42 | + |
| 43 | +The following "ensure..." methods handle all these scenarios: |
| 44 | + - ['ensureRemovalOfParagraphAttributesWhenPressingEnterInAnEmptyParagraph'](https://github.com/wordpress-mobile/AztecEditor-iOS/blob/develop/Aztec/Classes/TextKit/TextView.swift#L1945) |
| 45 | + - [ensureInsertionOfEndOfLine](https://github.com/wordpress-mobile/AztecEditor-iOS/blob/develop/Aztec/Classes/TextKit/TextView.swift#L697) |
| 46 | + - [evaluateRemovalOfSingleLineParagraphAttributesAfterSelectionChange](https://github.com/wordpress-mobile/AztecEditor-iOS/blob/develop/Aztec/Classes/TextKit/TextView.swift#L1884) |
| 47 | + |
| 48 | +These methods check if you are adding or removing new paragraphs around lists or quotes and update the attributes accordingly. |
| 49 | + |
| 50 | +# Presentation of the custom attributes |
| 51 | + |
| 52 | +The [LayoutManager](https://github.com/wordpress-mobile/AztecEditor-iOS/blob/develop/Aztec/Classes/TextKit/LayoutManager.swift) class handles the presentation for the custom attributes that the standard [NSLayoutManager](https://developer.apple.com/documentation/uikit/nslayoutmanager) is unable to do: |
| 53 | + |
| 54 | + - Backgrounds and vertical bars for Quotes: `drawBlockquotes` |
| 55 | + - Backgrounds for Pre: `drawHTMLPre` |
| 56 | + - List bullets: `drawLists` |
| 57 | + |
| 58 | +The layout manager uses the extra information provided by the [ParagraphStyle](https://github.com/wordpress-mobile/AztecEditor-iOS/blob/develop/Aztec/Classes/TextKit/ParagraphStyle.swift) class to be able to render the correct background and bullets for each element. |
| 59 | + |
| 60 | +# HTML Converter |
| 61 | + |
| 62 | +The [HTMLConverter](https://github.com/wordpress-mobile/AztecEditor-iOS/blob/develop/Aztec/Classes/NSAttributedString/Conversions/HTMLConverter.swift) class handles all the conversation between HTML and NSAttributedString. The main entry points are: |
| 63 | + - [attributedString(from:html, defaultAttributes)](https://github.com/wordpress-mobile/AztecEditor-iOS/blob/develop/Aztec/Classes/NSAttributedString/Conversions/HTMLConverter.swift#L58), converts from HTML to a NSAttributedString object |
| 64 | + - [html(from:NSAttributedString, pretify)](https://github.com/wordpress-mobile/AztecEditor-iOS/blob/develop/Aztec/Classes/NSAttributedString/Conversions/HTMLConverter.swift#L110) converts from NSAttributedString to HTML |
| 65 | + |
| 66 | +# Converting from HTML to NSAttributedString |
| 67 | + |
| 68 | +Two classes are responsible for the conversion: |
| 69 | + - [HTMLParser](https://github.com/wordpress-mobile/AztecEditor-iOS/blob/develop/Aztec/Classes/Libxml2/Converters/In/HTMLParser.swift) transforms from HTML text to an in-memory XML tree. |
| 70 | + - [AttributedStringSerializer](https://github.com/wordpress-mobile/AztecEditor-iOS/blob/develop/Aztec/Classes/NSAttributedString/Conversions/AttributedStringSerializer.swift) converts from the in-memory tree to NSAttributedString. |
| 71 | + |
| 72 | +<img src="resources/html_to_nsattributedstring.png"> |
| 73 | + |
| 74 | +The `HTMLParser` class uses the libXML2 library to read the raw HTML. It then transforms the document returned to a more developer-friendly DOM node tree. |
| 75 | +The DOM node tree implementation has classes representing elements, text, CSS attributes, CSS values, and comments. |
| 76 | +`AttributedStringSerializer` then converts this DOM tree to an NSAttributedString. |
| 77 | + |
| 78 | +The specialized `NSAttributedString` has custom attributes that add extra information about the HTML elements that the string contains: |
| 79 | + - [HTMLRepresentation](https://github.com/wordpress-mobile/AztecEditor-iOS/blob/develop/Aztec/Classes/NSAttributedString/Attributes/HTMLRepresentation.swift) stores the original HTML elements and attributes that were in the initial HTML |
| 80 | + - [ParagraphStyle](https://github.com/wordpress-mobile/AztecEditor-iOS/blob/develop/Aztec/Classes/TextKit/ParagraphStyle.swift) is a subclass on [NSParagraphMutableStyle](https://github.com/wordpress-mobile/AztecEditor-iOS/blob/develop/Aztec/Classes/TextKit/ParagraphStyle.swift), that allows representation of a hierarchy of elements, for example, nested lists, blockquote inside lists, and others. |
| 81 | + |
| 82 | +Specialized [Converter](https://github.com/wordpress-mobile/AztecEditor-iOS/blob/develop/Aztec/Classes/Converters/ElementsToAttributedString/Implementations/GenericElementConverter.swift) classes transform from HTML Elements to NSAttributedString text and attributes, for example: |
| 83 | + - [ImageElementConverter](https://github.com/wordpress-mobile/AztecEditor-iOS/blob/develop/Aztec/Classes/Converters/ElementsToAttributedString/Implementations/ImageElementConverter.swift) converts `img `elements to a custom attachment, |
| 84 | + - [GenericElementConverter](https://github.com/wordpress-mobile/AztecEditor-iOS/blob/develop/Aztec/Classes/Converters/ElementsToAttributedString/Implementations/GenericElementConverter.swift) transform `strong` elements to a font attribute |
| 85 | + - [LIElementConverter](https://github.com/wordpress-mobile/AztecEditor-iOS/blob/develop/Aztec/Classes/Converters/ElementsToAttributedString/Implementations/GenericElementConverter.swift) transform `li` elements from a list to `ParagrahStyle` |
| 86 | + |
| 87 | +# Converting NSAttributedString to HTML |
| 88 | + |
| 89 | +In a mirror process from the HTML to NSAttributed conversion, we have the following classes: |
| 90 | + - [AttributedStringParser](https://github.com/wordpress-mobile/AztecEditor-iOS/blob/develop/Aztec/Classes/NSAttributedString/Conversions/AttributedStringParser.swift) converts from the `NSAttributedString` to an in-memory tree |
| 91 | + - [HTMLSerializer](https://github.com/wordpress-mobile/AztecEditor-iOS/blob/develop/Aztec/Classes/Libxml2/Converters/Out/HTMLSerializer.swift) transforms from the in-memory tree to an HTML string. |
| 92 | + |
| 93 | +<img src="resources/nsattributedstring_to_html.png"> |
| 94 | + |
| 95 | +The `AttributedStringParser` transforms the `NSAttributedString` to a DOM tree. On a first pass, it iterates paragraph by paragraph and uses [StringAttributesConverters](https://github.com/wordpress-mobile/AztecEditor-iOS/blob/develop/Aztec/Classes/Converters/ElementsToAttributedString/Implementations/GenericElementConverter.swift) and [AttachmentConverters](https://github.com/wordpress-mobile/AztecEditor-iOS/blob/develop/Aztec/Classes/Converters/ElementsToAttributedString/Implementations/GenericElementConverter.swift) to convert from `NSStringAttributes` and `NSAttachments` to the DOM. |
| 96 | +Then it goes through the DOM tree and merges nodes to create a simplified version of the tree. |
| 97 | + |
| 98 | +We then use the `HTMLSerializer` to convert the DOM Tree to an HTML string. |
| 99 | + |
| 100 | + |
| 101 | + |
0 commit comments