Rounded Rectangles and Ellipses

Murray Sargent

In Word, PowerPoint, OneNote and RichEdit, you can enclose text in a rectangle by putting the text into a math “boxed formula” object. To try this out, type alt+= to enter a math zone and then \rect(a+b)<space>. This displays as as Image BoxedFormulaThe “\rect(a+b)” is the UnicodeMath representation of this boxed formula. In MathML, the boxed formula object is represented by the <menclose notation=”box”> element. The <menclose> documentation mentions other notations as well, notably “roundedbox” and “circle” (which is actually an ellipse). Up through Office 2013, only the “box” option, along with hiding sides and various crossbars, was implemented in Office apps.

Mobile versions of Excel needed custom rounded boxes with background color for cell tokens in the formula bar. Accordingly, the RichEdit boxed-formula implementation was enhanced to offer that feature along with the ellipse. The Direct2D line styles are supported, and the line and background colors can be specified by COLORREFs. This post describes the user interfaces (UI) and APIs for creating such objects in a RichEdit control. Examples include

Image RoundedBoxes

In Excel, it turned out that having a math zone in the formula bar complicated font binding. So RichEdit offers the same functionality in an Enclose object, which works in ordinary text.

User Interface (UI)

Let’s start with the UI entry in a math zone. To insert an empty rectangle object, you can use UnicodeMath. For this note that each math object is associated with its own Unicode character. The character for the square rectangle is U+25AD, ▭. In a math zone, the text \rect autocorrects to ▭. The characters for the rounded box and ellipse are U+25A2 (▢) and U+2B2D (⬭), respectively. In the RichEdit math autocorrect, you can type \rrect for ▢ and \ellipse for ⬭. But you can also just type the hex character code followed by alt+x to insert the character. Then put the text you want inside the parentheses and hit the Space bar to build it up into the object as illustrated above. The colors for the border and background are taken from the text and background colors in the character format for the opening character of the rectangle object. Currently the Enclose object can only be inserted via copy/paste or the ITextRange2::SetInlineObject() function described below.

APIs

To understand the APIs for inserting and manipulating a math object, here’s a quick summary of how such objects are represented in the RichEdit backing store. In OfficeMath built-up format, as distinguished from a linear format like UnicodeMath or LaTeX, mathematical objects like fraction and boxed formula are represented by a start delimiter, the first argument, an argument separator if the object has more than one argument, the second argument, etc., with the final argument terminated by the end delimiter. For example, the fraction 𝑎 over 𝑏 is represented in built-up format by {frac 𝑎|𝑏} where {frac is the start delimiter, | is the argument separator, and } is the end delimiter. Similarly, the subscript object 𝑎𝑏 is represented by {sub 𝑎|𝑏}. Here the start delimiter is the same character for all math objects and is the Unicode character U+FDD0 in RichEdit (Word uses a different character). The kind of object is specified by a rich-text object-name property associated with the start delimiter. So in plain text, the built-up forms of the fraction and subscript are identical if the fraction arguments are the same as their subscript counterparts. In the example here, a plain-text search for {frac 𝑎|𝑏} matches {sub 𝑎|𝑏} as well {frac 𝑎|𝑏}. A rich-text search can distinguish between the two.

To insert an empty math object into a RichEdit control, call ITextRange2::SetInlineObject(Type, Align, Char, Char1, Char2, Count, TeXStyle, cCol). In particular, to insert one of the enclosures in a math zone, call

ITextRange2::SetInlineObject(tomBoxedFormula, Align, Char, Char1, 0, 0, 0, 0),

where Char = ▭, ▢, or ⬭ produce a square rectangle, a rounded rectangle, or an ellipse, respectively. To insert such an enclosure in ordinary text, call

ITextRange2::SetInlineObject(tomEnclose, Align, Char, Char1, 0, 0, 0, 0)

To change the border and/or background colors, select the object start character and apply the formatting via EM_SETCHARFORMAT, EM_SETRANGEFORMAT, or ITextFont::SetForeColor() and SetBackColor().

The various border styles are determined by the Align argument. For the rectangle, the Align values are defined by any combination of OR’ing bits given by

tomBoxHideTop 1
tomBoxHideBottom 2
tomBoxHideLeft 4
tomBoxHideRight 8
tomBoxStrikeH 16
tomBoxStrikeV 32
tomBoxStrikeTLBR 64
tomBoxStrikeBLTR 128

Here the low eight bits of Align control which sides are hidden (if any) along with whether four possible strike-throughs are drawn. Since all eight bits were defined for these purposes before, they are persisted in most existing file formats along with the Char code. The UnicodeMath notation that defines the Align value 𝑛 for a string 𝑥 is ▭ (𝑛&𝑥).

For the rounded rectangle, the Align values are defined by

tomRoundedBoxDashStyleMask 0x07 D2D1_DASH_STYLE
tomRoundedBoxHideBorder 0x08
tomRoundedBoxCapStyleMask 0x30 D2D1_CAP_STYLE * 16
tomRoundedBoxNullRadius 0x40
tomRoundedBoxCompact 0x80

Here bits 0..2 of Align give the Direct2D D2D1_DASH_STYLE, and bits 4..5 give the D2D1_CAP_STYLE. For example, Align = D2D1_DASH_STYLE_CUSTOM (5) gives the “input 1” example above. The radius (in twips) of the rounded corners of a rounded rectangle is given by Char1. The Office MathML converters don’t support the rounded rectangle and ellipse yet, even though MathML does.