Development/Custom Shape Orientation Guide
TDF LibreOffice Document Liberation Project Community Blogs Weblate Nextcloud Redmine Ask LibreOffice Donate
This article is an orientation guide about custom shapes and is intended for developers. It points to relevant specifications, points to relevant parts of the code and tries to show relationships in regard to file formats.
Please use the “Discussion” page for your comments, questions and proposals. In case you find errors, simple correct them.
Introduction
What is “custom shape”?
custom shape is a technical term and does not mean user made shape, but custom shape is the generic term for the shapes that have an enhanced geometry. Strictly speaking, the spelling “custom shape”-shape should be used. However, this article uses the simpler spelling custom shape. These shapes are written as element <draw:custom-shape> in the file markup in ODF. These kind of shapes are identified by SdrObjKind::CustomShape and support the service com.sun.star.drawing.CustomShape.
Many of these shapes have handles and shape specific glue points. For all of these shapes and only for them the tool “Toggle Extrusion” is active in the UI. Looking at it allows to quickly distinguish a custom shape from other kind of shapes.
Starting with OOo2.0, custom shapes were implemented to work with shapes from binary MS Office format and related RTF and VML formats. Import and export filter for shapes defined in OOXML were added a few years later. Work on improving the filters is ongoing.
A custom shape is an object of SdrObjCustomShape class and belongs to the hierarchy of SdrObject class. In particular, many aspects of its text content are inherited from SdrTextObj class. Object methods of custom shapes, such as move and rotate, are located in the svdoashp.hxx/cxx files. Methods for preparing rendering are located in the customshapes folder in the svx module.
Custom shapes in a Writer document are special as they can have an attached text frame. The relevant class is SwTextBoxHelper. This text frame is mainly used for round-trip with OOXML. It has been introduced in 2014, read blog post. It still needs some improvements.
Specifications
When working on filters, you surely need the specifications. They are also useful for understanding the code, since identifiers often have a direct relationship to terms in the specifications. The “Relevant Specifications” section at the end of the article contains some links to them. References such as “[RTF page nn]” refer to this list.
API/UNO
Many properties of custom shapes are not represented by class members but by properties. They are used via services and interfaces. Most constants and properties for custom shapes are prefixed with EnhancedCustomShape. For to get a local copy install the SDK for your installed LibreOffice or build LibreOffice with configure option -–enable-odk which will put the SDK into folder /core/instdir/sdk. To take a look at a custom shape from an API point of view, use the “Development Tools”. Look at property “CustomShapeGeometry”, which is essential for custom shapes. It is a sequence of properties. They are explained in the “CustomShapeGeometry” and “Path” sections in alphabetical order.
Predefined Shapes
The MS binary file format and OOXML each define a set of predefined shapes. “Predefined” means that the geometry of the shapes is not contained in the file, but defined in the file format specification. ODF has no such concept; all shapes must be entirely defined in the file itself. The predefined shapes of MS binary file formats are described in “Appendix D: AutoShapes” in the specification [MS97]. The predefined shapes of OOXML are provided in separate xml-files [OOXML].
The translation of the OOXML preset shapes into our properties is located in CustomShapeProperties::initializePresetDataMap(). Since the result is independent of individual documents, it is located in the repository.
To translate AutoShapes of MS binar format to our properties, their geometry is described in structs of type mso_CustomShape.
struct mso_CustomShape
struct mso_CustomShape { SvxMSDffVertPair* pVertices; sal_uInt32 nVertices; sal_uInt16* pElements; sal_uInt32 nElements; SvxMSDffCalculationData* pCalculation; sal_uInt32 nCalculation; sal_Int32* pDefData; SvxMSDffTextRectangles* pTextRect; sal_uInt32 nTextRect; sal_Int32 nCoordWidth; sal_Int32 nCoordHeight; sal_Int32 nXRef; sal_Int32 nYRef; SvxMSDffVertPair* pGluePoints; sal_uInt32 nGluePoints; SvxMSDffHandle* pHandles; sal_uInt32 nHandles; };
The mso_CustomShape struct is defined in EnhancedCustomShapeGeometry.hxx and used in EnhancedCustomShapeGeometry.cxx to describe the preset shapes of MS binary format in a way, that they can be expressed as custom shapes. Translating such description to the syntax used in custom shapes is located at start of file EnhancedCustomShape2d.cxx. Method SdrObjCustomShape::MergeDefaultAttributes() sets the properties to the individual custom shape.
The structures in the EnhancedCustomShapeGeometry file use values that have flags incorporated, as well as special constants. Their meanings are explained later in the affected sections.
Shape definitions are not translated 1:1 from AutoShape definitions [MS97 Appendix D] in all cases. Custom shapes in LO are rendered using polygons. A fill is only possible for a closed polygon. However, the original definition may contain only a polyline. In such cases, the definition was adapted to the needs of our rendering engine. This was especially necessary for shapes rendered with lightened and darkened parts in MS Office. The description in MS binary format does not take into account such shading.
CustomShapeGeometry
AdjustmentValues
The corresponding attribute in ODF is draw:modifiers [ODF 19.196]. It is a space-separated list of numbers. The numbers can be used in formulas, point coordinates, and handle positions by using a modifier reference $0, $1,…. The other specifications use the term "adjustment value".
RTF has the array pAdjustHandles [RTF page 173] for this purpose.
The MS binary format has individual attributes adjustValue, adjust2Value, …, adjust10Value [MS97 page 30]. A reference to such a value is written as DFF_Prop_adjustValue, DFF_Prop_adjust2Value, … in structs of type mso_CustomShape in the file EnhancedCustomShapeGeometry.cxx.
OOXML and VML use the <avLst> element (List of Shape Adjust Values) [OOXML 20.1.9.5] with <gd> child elements (Shape Guide) [OOXML 20.1.9.11]. Such <gd> element has a name attribute and a fmla attribute. The values are referenced by using the value of the name attribute. Although formally arbitrary formulas are allowed, in practice constant numbers are used in case of adjustment values. Therefore translation to ODF is possible.
The AdjustmentValues property is a sequence of items of struct css.drawing.EnhanceCustomShapeAdjustmentValue. For ODF, a simple vector of numbers would suffice, but round-trip with OOXML additionally requires the name component. It is empty in case of ODF.
Equations
ODF
The Equations property is a sequence of strings. Each string holds an expression formed according to the formula language specified in ODF [ODF 19.171]. It is a complex language that allows nested expressions. The language provides the usual operators, expect ^, and functions abs, sqrt, sin, cos, tan, atan, min, max, atan2, and if. In addition it has the identifiers left, top , right, bottom (values of svg:viewBox), logwidth, logheight (values of svg:width and svg:height in hundredths of millimeters), hasfill, hasstroke, xstretch, ystretch and the identifier pi. The identifiers logwidth and logheight are often used when translating from OOXML, as they allow the current size of the shape to be used in equations instead of values from viewBox attribute.
The other file formats have a similar principle. However, their formula language has only a limited number of expression types [VML page 352] and [OOXML 20.1.9.11 and 20.1.10.56]. The translation of their formulas to ODF is implemented in LibreOffice, but the translation from ODF to their restricted format is not yet solved in LibreOffice (as of May 2022).
The corresponding element in ODF is <draw:equation> [ODF 10.2.5]. It has an attribute draw:name and an attribute draw:formula [ODF 19.171]. LibreOffice discards the names when opening an ODF file and uses the zero-based index of the equation in the sequence instead. For example, a reference to an equation with index 7 is then written as ?7. LibreOffice generates the names f0, f1, … and changes the references to ?f7, for example, when the ODF markup is written to the file.
MS binary format
The struct mso_CustomShape (used for the description of AutoShapes) contains the formulas in member pCalculation. This is an array of items of type struct SvxMSDffCalculationData. This struct has the members nFlags and nVal[3]. The member nFlags contains in its last byte coded which expression type has to be used. The member nVal[3] contains three parameters for the expression. Parameters not needed for an expression type are ignored.
Evaluating the expression type is located in EnhancedCustomShape2d::GetEquation() The expression type enumeration follows the expression type order in [VML page 352] and the enumeration “Shape Guide Formula” [MS97 page 171] or in [MS 2.2.58 SG].
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
sum | product | mid | abs | min | max | if | mod | atan2 | sin | cos | cosatan2 | sinatan2 | sqrt | sumangle | ellipse | tan |
The case 0 includes evaluation of the expression type val, which specifies a constant number.
Formulas can use references to other formulas that have a lower index and to adjustment values. The first byte of the member nFlags contains encoded which of the three parameters is not a simple number.
In a parameter from nVal[3] in a SvxMSDffCalculationData struct, a reference to a formula is written as 0x4nm. The 0x4 part is a flag evaluated in method EnhancedCustomShape2d::AppendEnhancedCustomShapeEquationParameter() to distinguish a reference to a formula from references to adjustment values and from other identifiers.
OOXML
The set of possible expression types for OOXML is listed in attribute fmls [OOXML 2.1.9.11]. Compared to MS binary format the types sumangle and ellipse have been removed, type pin (clamp) is added, type val for constant number is added and the type mid has been replaced by the more general expression type +/ (Add Divide). The standard defines 38 predefined constants and expressions that can be used as parameter [OOXML 20.1.10.56]. Such are hc for */ w 1.0 2.0 or ss for min w h, for example. These allow a shorter list of equations needed.
The translation in LibreOffice is located in static method convertToOOEquation in file customshapegeometry.cxx.
Extrusion
This is a sequence of properties. It serves as container for attributes from the 24 extrusion related attributes of the ODF element <draw:enhanced-geometry> [ODF 10.6.2]. The chapter “Custom Shapes in 3D” in the book “Custom Shape Tutorial” provides an overview of the meaning of these attributes.
There are similar attributes in MS binary format [MS 2.3.15 and 2.3.16] , RTF [RTF page 182] and VML [VML 19.2.2.11]. Only import from MS binary format works well.
OOXML has extrusion as part of its “3D” [OOXML 20.1.5]. Extrusion is not handled when exporting from ODF to OOXML, and in round-tripping from OOXML it is only preserved via the InteropGrabBag property, but not evaluated (as of May 2022).
LO uses the same rendering engine for extruded custom shapes as for 3D-scenes. The creation of a corresponding 3D-scene is done in the method EnhancedCustomShape3d::Create3DObject(), which is called by the method EnhancedCustomShapeEngine::render() .
Handles
The Handles property is a sequence of handles. The API reference has a handle therein as service EnhancedCustomShapeHandle.
The Handles property corresponds to array pAdjustHandles in RTF [RTF page 173] and MS binary format [MS 2.3.6.24][MS97 page 31] and to <handles> element in VML [VML 19.1.2.9]. The struct mso_CustomShape has this sequence of handles in its member pHandles. That is a sequence of items of type SvxMSDffHandle.
OOXML has the sequence of handles as element <ahLst> [OOXML 20.1.9.1] with child elements <ahPolar> [OOXML 20.1.9.2] and <ahXY> [OOXML 20.1.9.3].
ODF has no such container element but the elements for the individual handles are written directly after the elements for equations, which results in an order of the elements too. A handle is specified as <draw:handle> [ODF 10.6.3].
All specification know a “handle” but the details vary.
MS binary format
A handle is described by an ADJH struct in MS binary format [MS97 page 171][MS 2.2.57]. It consists of flags and attributes. The above mentioned struct SvxMSDffHandle reflects this; it has a component nFlags of type SvxMSDffHandleFlags and several components for attributes.
enum SvxMSDffHandleFlags | MS specification Identifier |
enum SvxMSDffHandleFlags | MS specification Identifier | ||
---|---|---|---|---|---|
0x0000 | NONE | 0x0080 | RANGE_X_MIN_IS_SPECIAL | fahxMin | |
0x0001 | MIRRORED_X | fahInverseX | 0x0100 | RANGE_X_MIN_IS_SPECIAL | fahxMax |
0x0002 | MIRRORED_Y | fahInverseY | 0x0200 | RANGE_Y_MIN_IS_SPECIAL | fahyMin |
0x0004 | SWITCHED | fahSwitchPosition | 0x0400 | RANGE_Y_MAX_IS_SPECIAL | fahyMax |
0x0008 | POLAR | fahPolar | 0x0800 | CENTER_X_IS_SPECIAL | fahxRange |
0x0010 | MAP | fahMap | 0x1000 | CENTER_Y_IS_SPECIAL | fahyRange |
0x0020 | RANGE | fahPin | 0x2000 | RADIUS_RANGE | fahPolarPin |
fahUnused |
The description of these flags in [MS] specfication is quite informative.
struct SvxMSDffHandle | MS specification Identifier |
Details |
---|---|---|
nPositionX | apX | 0:left, 1:right, 2:center (not used by AutoShapes), 0x…03-0x…82: reference to equation , 0x…100-0x…107:reference to adjustment value |
nPositionY | apY | 0:left, 1:right, 2:center (not used by AutoShapes), 0x…03-0x…82: reference to equation , 0x…100-0x…107:reference to adjustment value |
nCenterX | xRange | fahxRange ? reference to equation : direct value |
nCenterY | yRange | fahyRange ? reference to equation : direct value |
nRangeXMin | xMin | fahxMin ? reference to equation : direct value |
nRangeXMax | xMax | fahxMax ? reference to equation : direct value |
nRangeYMin | yMin | fahyMin ? reference to equation : direct value |
nRangeYMax | yMax | fahyMax ? reference to equation : direct value |
The components in struct mso_CustomShape have the same meaning as the attributes in [MS] specification. The method EnhancedCustomShape2d::SetEnhancedCustomShapeHandleParameter() converts these values into values of type EnhancedCustomShapeParameter, which has a Type component and a Value component. That is used by method lcl_ShapePropertiesFromDFF() in file svdoashp.cxx, which converts all handle data into the corresponding UNO properties.
OOXML, VML, RTF, ODF
RTF and VML use the same <h> element [VML 19.1.2.8] to specify a handle. Its attributes have counterparts in OOXML and ODF. OOXML has separate elements <ahPolar> [OOXML 20.1.9.2] for polar handles and ahXY [OOXML 20.1.9.3] for Cartesian handles. The attributes are nevertheless comparable. Service EnhancedCustomShapeHandle covers most of them. Therefore they are put together into one table to make the relationships visible.
EnhancedCustomShapeHandle | VML / RTF | OOXML | ODF | Comment |
---|---|---|---|---|
MirroredX | invx | _ | draw:handle-mirror-horizontal [ODF 19.176] | new = right - old |
MirroredY | invy | _ | draw:handle-mirror-vertical [ODF 19.177] | new = bottom - old |
Switched | switch | _ | draw:handle-switched [ODF 19.186] | Exchange coordinates of Cartesian handle, if shape is taller than wide. Our shape of “Rectange, Rounded” is an example where this property is used. |
_ | map | _ | _ | |
Position | position | pos element | draw:handle-position [ODF 19.179] | see details below |
Polar | polar | _ | draw:handle-polar [ODF 19.178] | Origin for polar coordinates |
RefX | _ | gdRefX | _ | OOXML: for ahXY element |
RefY | _ | gdRefY | _ | OOXML: for ahXY element |
RefAngle | _ | gdRefAng | _ | OOXML: for ahPolar element |
RefR | _ | gdRefAng | _ | OOXML: for ahPolar element |
RangeXMinimun | xrange min and max |
minX | draw:handle-range-x-minimum [ODF 19.183] |
OOXML: for ahXY element |
RangeXMaximum | see above | maxX | draw:handle-range-x-maximum [ODF 19.182] |
OOXML: for ahXY element |
RangeYMinimum | yrange min and max |
minY | draw:handle-range-y-minimum [ODF 19.185] |
OOXML: for ahXY element |
RangeYMaximum | see above | maxY | draw:handle-range-y-maximum [ODF 19.184] |
OOXML: for ahXY element |
RadiusRangeMinimum | radiusrange min and max |
minR | draw:handle-radius-range-minimum [ODF 19.181] |
OOXML: for ahPolar element |
RadiusRangeMaximum | see above | maxR | draw:handle-radius-range-maximum [ODF 19.180] |
OOXML: for ahPolar element |
_ | _ | minAng | _ | OOXML: for ahPolar element |
_ | _ | maxAng | _ | OOXML: for ahPolar element |
Comments to “Position”
The coordinates of a handle position need to directly or indirectly refer adjustment values. When the user changes the handle position, the application will determine the adjustment values so, that when using these values in the calculation of the coordinates of the position attribute. the position determined by the user is generated.
OOXML: The coordinates in the pos element are in unit EMU and not relative to the local coordinate system of the shape according to [OI29500 2.1.1310]. They are Cartesian coordinates even if the handle is a polar handle. It is yet not solved in general to calculate the needed adjustment values when a user moves a handle. But it already works for most of the predefined shapes. The calculation is located in method EnhancedCustomShape2d::SetHandleControllerPosition() and page “Improve handles of DrawingML shapes” has details about it.
ODF, VML/RTF: If the handle is set as polar handle, that is if draw:handle-polar attribute or Polar or polar flag, respectively, is set, then the position attribute contains polar coordinates (Radius,Angle) else Cartesian coordinates (x,y). Caution, the ODF specification text has erroneously order (Angle,Radius).
ODF (and UNO): In coordinates are not only numbers and references allowed but all identifiers as listed in section “Equations” as well, except for pi.
VML/RTF: Besides numbers and references to equations the identifiers center, topleft and bottomright are allowed for Cartesian handles.
MirroredX, MirroredY
The corresponding attributes in ODF are draw:mirror-horizontal [ODF 19.192] and draw:mirror-vertical [ODF 19.193].
When the user mirrors a custom shape, this does not change the defining points in the path of the shape, but mirroring is stored as property of the shape. This is different from, for example, polygons, where the reflection is directly applied to the coordinates of the path points. You will find a lot of code where this different handling of mirroring is addressed by a case distinction.
The mirroring is always done at the center point of the shape. Mirroring is handled by object methods.
The corresponding attributes in MS binary format and RTF are fFlipH and fFlipV, in VML and OOXML they are flipH and flipV.
Path
The Path property is actually a sequence of properties. They are discussed below in a separate section “Path”.
TextCameraZRotateAngle
OOXML provides full 3D transformation of shapes. Users often use its z-rotation to rotate a shape instead of using the 2D shape rotation. LibreOffice uses this property to track such rotation. ODF has nothing equivalent to this property, so the rotation is converted to 2D object rotation when in ODF. The property is not published.
TextPath
The TextPath property is a sequence of properties. It serves as container for attributes used to describe shapes called “FontWork” in the LibreOffice user interface. It corresponds to the service EnhancedCustomShapeTextPath in the API reference. Older MS Office versions called it “WordArt”, newer versions use the term “abc Transformation”. And you may also come across the term “text warp”.
The property IsFontwork and the properties starting with FontWork, which you might see when using the “Development Tools”, do not belong to custom shapes but to the "Formtext"-feature of curves and polygons.
OOXML has 40 predefined shapes in presetTextWarpDefinitons.xml file in the zip archive “Electronic inserts” belonging to Part 1. There exists no way to extend this set. In MS binary format these shape types are available too, integrated in the shape type's table used for all AutoShapes [MS97 page 57][MS 2.4.24 MSOSPT], similar for RTF with values 136 to 175 of ShapeType attribute [RTF page 190]. VML has no set of predefined shapes. It has a textpath element with several attributes which can be added to any shape element [VML 19.1.2.23] to get this artistic form of text data. Examples are shown in the specification. If Word detects or knows, that the shape geometry equals a predefined one, it writes the attribute o:spt with type index in the docx markup. If such attribute is missing LO imports the shape with errors (as of June 2022).
The struct mso_CustomShape defines only the geometry of the shape and has no information about a text path. The fact, that it is a “WordArt” shape, is given by its type. All properties belonging to TextPath are already included in the predefined shapes from the “Fontwork Gallery” or they are imported along with the shape when a document is opened that contains a “Fontwork”-shape.
The article “Remarks on Fontwork and TextWarp” contains more information about this special state of custom shapes. The chapter “Text Path” in the book “Custom Shape Tutorial” provides examples of “Fontwork”-shapes beyond the predefined ones.
LibreOffice Conference 2022 had a workshop "Creating 'Fontwork' geometries beyond the predefined shapes". Find the slides and additional material on https://events.documentfoundation.org/libreoffice-conference-2022/talk/WDU79Y/.
TextPreRotateAngle
This has been introduced for round-trip with OOXML Use and relationship to property TextRotateAngle is unclear. It needs improvements. The property is not published.
TextRotateAngle
The corresponding attribute in ODF is draw:text-rotate-angle [ODF 19.226] The text area of a custom shape can be rotated independent from the shape rotation. So it is possible to have an upright text in a rotated shape. The property is evaluated and correctly rendered but has no UI.
ToDo: Other file formats?
Type
The corresponding attribute in ODF is draw:type [ODF 19.229.3] with the default value non-primitive. LibreOffice uses this property in a very special way.
LibreOffice needs to know whether a shape was generated from a predefined shape on import for to be able to use this definition on export. LibreOffice tracks this in the Type property.
OOXML
The import of shapes in documents in OOXML format distinguishes three cases:
a) The shape markup has an element <custGeom>. Then Type="ooxml-non-primitive" is used. The file contains the complete shape definition.
b) The shape markup has an element <prstGeom> and no element <prstTxWarp>. Then it is a shape predefined in file presetShapeDefinitions.xml and the value of attribute prst of the <prstGeom> element is used with prefix “ooxml-”. The markup prst="noSmoking" results in value ooxml-noSmoking for the Type property, for example.
c) The shape markup has an element <prstGeom> with attribute prst="rect" and it has an element <prstTxWarp>. Then it is a shape predefined in file presetTextWarpDefinitions.xml. Those shapes are mapped to the corresponding shape type of MS binary format.
MS binary format
The import of shapes in documents in MS binary format uses the enum MSO_SPT in file msdffdef.hxx. The numbering is the same as defined in the specification [MS97 page 57]. These numbers are used with same meaning in attribute ShapeType in [RTF] too. This enum values are mapped to strings in pNameTypeTableArray in EnhancedCustomShapeTypeNames.cxx.
The value of property Type is used as parameter in method createCustomDefaults of interface XEnhancedCustomShapeDefaulter. That is implemented in SvxCustomShape::createCustomShapeDefaults. It covers shape types from MS binary format.
On export to OOXML the implementation tries to export the shape types from MS binary format to suitable OOXML shape types. That is located in ShapeExport::WriteCustomShape. If that is not possible a shape of kind <custGeom> which has the shape outline in its <path> elements is used. Improving the export filter is ongoing work.
OpenOffice.org
Last to mention are shapes with values structure col-nxxxxxxx of the Type attribute. We have two of such shapes in the UI, “Octagon Bevel” with Type="col-60da8460" and “Diamond Bevel” with Type="col-502ad400". Because ODF has no way to express to lighten or darken a part of a shape, OOo has introduces this special kind of value of the Type attribute. The value is decoded into member nColorData of an object of class EnhancedCustomShape2d. This member nColorData is used too to express lighten and darken for shaped types from MS binary format. The needed value is individually set for such shapes in the object ctor.
ViewBox
The corresponding attribute in ODF is svg:viewBox [ODF 19.574] and it has the same meaning as in SVG. It defines a local coordinate system for the coordinates used inside the enhanced geometry of the custom shape. The rectangle given by the svg:viewBox attribute is scaled to the world coordinate rectangle given by the svg:x, svg:y, svg:width and svg:height attributes. Using coordinates outside the rectangle given by the svg:viewBox attribute is possible, used in the shape “Rectangular Callout” for example.
The attribute svg:viewBox may be omitted in ODF. LibreOffice renders the shape then (not ODF conform) as if svg:viewBox="0 0 21600 21600" was set. That happens in ctor of EnhancedCustomShape2d via members nCoordWidthG and nCoordHeightG.
MS binary format and RTF use the individual attributes geoLeft, geoTop, geoRight and geoBottom to define a local coordinate system. The default values are 0, 0, 21600 and 21600. Using other values than 0 for attributes geoLeft and geoTop is neither implemented in MS Office nor in LibreOffice. The values from attributes geoRight and geoBottom are represented as members nCoordWidth and nCoordHeight in struct mso_CustomShape. All shape description in file EnhancedCustomShapeGeometry.cxx use the default values, only descriptions for shapes “can” and “heart” are different.
svg:viewBox="0 0 0 0"
OOXML has something similar in attributes w and h of the <path> element [OOXML 20.1.9.15]. OOXML can set these attributes individually for each <path> element. ODF can set attribute svg:viewBox only globally for the entire shape geometry.
On the other hand the attributes w and h may be omitted and so a viewBox does not exist. In that case the current width and height of the shape is used, which have unit EMU. But a missing viewBox is treated as svg:viewBox="0 0 21600 21600" in LibreOffice.
Therefore LibreOffice uses svg:viewBox="0 0 0 0" to indicate these situations. As of June 2022 it is set for all shapes on import from OOXML format, expect shapes “chartPlus”, “chartStar” and “chartX”. You find this in file oox-drawingml-cs-presets and in method CustomShapeProperties::pushToPropSet(). (I don’t know why the “ChartFoo” shapes use “0 0 4000000 400000” and whether these different values are actually used somewhere.)
The use of svg:viewBox="0 0 0 0" is problematic in regard to interoperability. As attribute in SVG it means, that the shape is not rendered, and thus MS Office will not render the shape, when it opens an ODF document with such a shape.
Path
The Path property in CustomShapeGeometry is a sequence of properties and serves as container. It has no direct corresponding attribute or element in any of the mentioned file format specifications. It corresponds to service EnhancedCustomShapePath in the API reference.
ConcentricGrandientFillAllowed
The corresponding attribute in ODF is draw:concentric-gradient-fill-allowed [ODF 19.124]. Concentric gradient fill is not implemented in LibreOffice, although the property is mentioned in the API reference.
ToDo: Other file formats?
Coordinates
There is no directly corresponding attribute in ODF. But the information in draw:enhanced-path attribute is divided into a sequence of the pure coordinates (Coordinates property ) and a sequence of commands (Segments property). For example draw:enhanced-path="M 0 10 Y 20 0 L 30 40 N Z" will become (not literally but in content) Coordinates=”0|10 20|0 30|40” and Segments=”M Y L N Z”.
Shapes in OOXML and VML too have no separation in coordinates and commands. But such separation exists in RTF and in MS binary format as arrays pSegmentInfo and pVertices.
The Coordinates property is a sequence of items of type css.drawing.EnhancedCustomShapeParameterPair. Such item has the components First and Second, not “X” and “Y”. The components First and Second themselves have type css.drawing.EnhancedCustomShapeParameter. That has the components Value and Type. Value can contain any number. Type is a constant from css.drawing.EnhancedCustomShapeParameterType. In the context of coordinates only the constants Normal = 0, Equation = 1 and Adjustment = 2 are allowed. So if the shape path needs the constant Right, an equation has to be introduced for that value. A reference to an equation is written with ? prefix in ODF file markup, to an adjustment value with $ prefix.
The mso_CustomShape struct (used for the description for predefined shape of MS binary format) has this sequence of coordinates in its member pVertices. A coordinate pair is a struct SvxMSDffVertPair with component nValA and nValB, both type sal_Int32. To be able to use a reference to an equation the flag MSO_I is used, which sets the first bit. A reference to an adjustment value is here not possible. Such reference needs to be used indirectly via equation.
ExtrusionAllowed
The corresponding attribute in ODF is draw:extrusion-allowed [ODF 19.149]. It is attribute f3DOK in MS binary format [MS97 page 31] and RTF [RTF page 184]. It is attribute extrusionok in VML [VML 19.1.2.14] and attribute extrusionOk in OOXML [OOXML 20.1.9.15]. Default value is true, for OOXML search for “extrusionOk” in [OI29599].
Lot of OOXML preset shapes have extrusionOk="false" in their path elements, “rightBrace”, smileyFace” or “verticalScoll” for example. The property is not evaluated in LibreOffice as of version 7.4. All custom shapes are able to extrude.
GluePoints
The corresponding attribute in ODF is draw:glue-points [ODF 19.175]. It is array pConnectionSites in RTF [RTF page 173] and MS binary format [MS97 page 31][MS 2.3.6.18]. It is attribute connectlocs in VML [VML 19.1.2.14]. In OOXML it is element cxnLst as child element of element custGeom and accordingly in the predefined OOXML shapes[OOXML 20.1.9.10].
The struct mso_CustomShape has the glue points in array pGluePoints, with items of type SvxMSDffVertPair. That is the same structure as used for property Coordinates.
OOXML has only one kind of glue points. ODF has the four default glue points at the middle of the edges of the maybe transformed snap rectangle, the object specific glue points defined in the path property of the enhanced-geometry (where this section is about) and glue points defined by the user according elements <draw:glue-point> [ODF 10.3.16].
In case of export from ODF to OOXML using a custGeom element, all glue points need to be exported. The method getGluePoints() of interface XGluePointsSupplier returns a read-only container which contains all glue points. OOXML uses world coordinates, ODF uses local coordinates. But export of glue points in this case is not yet implemented (as of June 2020).
GluePointLeavingDirections
The corresponding attribute in ODF is draw:glue-point-leaving-directions [ODF 19.173]. It is array pConnectionSitesDir in RTF [RTF page 173] and MS binary format [MS97 page 31][MS 2.3.6.20]. It is attribute connectangles of element <path> in VML [VML 19.1.2.14] and attribute ang of element cxn in OOXML [20.1.9.9].
This attribute is not implemented in LibreOffice although mentioned in the API reference.
GluePointType
The corresponding attribute in ODF is draw:glue-point-type [ODF 19.174] with values none, rectangle and segments. The values for the GluePointType property are given in EnhancedCustomShapeGluePointType as NONE, SEGMENTS, CUSTOM and RECT. That corresponds to attribute cxk in MS binary format and RTF [RTF page 186] and attribute connecttype in VML [VML 19.2.3.8].
Although the property is mentioned in the API reference, it is not really implemented for ODF documents but only rudimentary used in filters for MS binary format.
Segments
There is no directly corresponding attribute in ODF. But the information in attribute draw:enhanced-path is divided into a sequence of the pure coordinates (Coordinates property) and a sequence of commands (Segments property).
The Segments property is a sequence of items of type css.drawing.EnhancedCustomShapeSegment. That is a struct of components Command and Count. The component Command is one of the values listed in css.drawing.EnhancedCustomShapeCommand in the API reference. Values 1 to 16 have counterparts in ODF [ODF 19.145], the commands 17 to 21 are extensions by LibreOffice introduced for compatibility with OOXML. If one of the commands 17 to 21 occurs and extended ODF is used, the path is not only saved as element <draw:enhanced-path> without these commands but a second time as element<drawooo:enhanced-path> including these commands.
If the same command would follow several times in direct succession, this is combined into one item. The component Count tells how many sets of parameter values are needed, but the item has no information about the size of such set. The property Segments does not directly record how many parameters a command needs, but the program code needs to set it itself depending on the command.
The commands 1 to 16 are similar to those used by MS binary format, RTF and VML. OOXML has the commands as child elements of element path [OOXML 20.1.9.15]. It has only the commands arcTo [OOXML 20.1.9.4], close [OOXML 20.1.9.6], cubicBezTo [OOXML 21.1.9.7], lnTo [OOXML 20.1.9.13], moveTo [OOXML 20.1.9.14] and quadBezTo [OOXML 20.1.9.21].
The mso_CustomShape struct for the description for predefined shapes of MS binary format has this sequence of commands in its member pElements which is a sequence of items of type sal_Int16. Such item has in its first byte the command encoded and in its second byte information which is needed to calculate the above mentioned value for component Count (see table). The conversion of this description to Segments property is located in method lcl_ShapeSegmentFromBinary.
Command name in API | enum in API | first byte in pSegment | ODF | VML | OOXML | Number of parameters | second byte in pSegment → nCount | |
---|---|---|---|---|---|---|---|---|
UNKNOWN | 0 | 0xf8 | 0 | |||||
MOVETO | 1 | 0x40 | M | m | moveTo | 1 | Byte | |
LINETO | 2 | 0x00 | L | l | lnTo | 1 | Byte | |
CURVTO | 3 | 0x20 | C | c | cubicBezTo | 3 | Byte | |
CLOSESUBPATH | 4 | 0x60 | Z | x | close | 0 | 0 | |
ENDSUBPATH | 5 | 0x80 | N | e | end of <path> element instead | 0 | 0 | |
NOFILL | 6 | 0xaa | F | nf | attribute fill = "none" instead | 0 | 0 | |
NOSTROKE | 7 | 0xab | S | ns | attribute stroke="false" instead | 0 | 0 | |
ANGLEELLIPSETO | 8 | 0xa1 | T | ae | 6 | Byte/3 | ||
ANGLEELLIPSE | 9 | 0xa2 | U | al | 6 | Byte/3 | ||
ARCTO | 10 | 0xa3 | A | at | 8 | Byte/4 | ||
ARC | 11 | 0xa4 | B | ar | 8 | Byte/4 | ||
CLOCKWISEARCTO | 12 | 0xa5 | W | wa | 8 | Byte/4 | ||
CLOCKWISEARC | 13 | 0xa6 | V | va | 8 | Byte/4 | ||
ELLIPTICALQUADRANTX | 14 | 0xa7 | X | qx | 1 | Byte | ||
ELLIPTICALQUADRAnTY | 15 | 0xa8 | Y | qy | 1 | Byte | ||
QUADRICCURVETO | 16 | Q | qb | quadBezTo | 2 | |||
ARCANGLETO | 17 | G | arcTo | 4 | ||||
DARKEN | 18 | H | attribute fill="darken" instead | 0 | ||||
DARKENLESS | 19 | I | attribute fill="darkenless" instead | 0 | ||||
LIGHTEN | 20 | J | attribute fill="lighten" instead | 0 | ||||
LIGHTENLESS | 21 | K | attribute fill="lightenless" instead | 0 | ||||
not implemented relative moveto | t | |||||||
not implemented relative lineto | r | |||||||
not implemented relative curveto | v |
StretchX, StretchY
The corresponding attributes in ODF are draw:path-stretchpoint-x [ODF 19.204] and draw:path-stretchpoint-y [ODF 19.205]. MS binary format and RTF have these as attributes xLimo and yLimo [MS page 31], [RTF page 169]. VML has attribute limo of the <path> element [VML page 487]. The standard OOXML has nothing comparable. Authors of OOXML shapes have to use formulas to calculate the suitable point coordinates to get the same effect. Look at preset shape “flowChartAlternateProcess” to learn how it can be done in OOXML.
limo-stretch
The descriptions in [VML page 487] or in [MS97 page 31] are not helpful, but the newer version of the specification has useful illustrations of the intended behavior [MS 2.3.6.22 and 2.3.6.23]. The ODF specification describes clearer what these values mean. For attribute draw:path-stretchpoint-x [ODF 19.204] it is,
- “If specified, and if the x/y aspect ratio of the svg:viewBox 19.574 is lower than the x/y aspect ratio of the shape size then the whole path is stretched horizontally at draw:path-stretchpoint-x.
- Stretching is done by:
- 1) intersecting the path vertically at draw:path-stretchpoint-x.
- 2) moving the right path fragments to the right border of the drawing shape, using its original aspect ratio. The left path fragment remains unmodified at the left border of drawing the shape.“
LibreOffice
LibreOffice does not behave exactly as specified in ODF as it does not evaluate the number in the attribute but evaluates only whether the attribute exists or not. However, it preserves the values. And LibreOffice requires a square viewBox to produce a correct limo-stretch. The ODF specification considers non-square viewBox rectangles too, so here a fix is needed, see bug tdf#149468. Microsoft Office interprets the properties StretchX and StretchY in documents in ODF in case of a square viewBox the same way as LibreOffice.
To understand how LibreOffice uses the properties StretchX and StretchY, let’s calculate an example manually.
- Assume there is a shape with svg:viewBox="0 0 21600 21600" and svg:width="12cm" and svg:height="3cm". We want to get the world coordinates for a point on the right edge and for a point inside the shape, for example the center point. In the example we use constant values and only look at the x-coordinates. The behavior for StretchY is analogous.
- We call EnhancedCustomShape2d::GetPointAsB2DPoint() with
- true in bScale to get world coordinates.
- true in bReplaceGeoSize to indicate that properties StretchX and StretchY have to be considered in case they are set.
- EnhancedCustomShapeParameterType::NORMAL in rPair.First.Type and rPair.Second.Type because we use constant values.
- rPair.First.Value=21600 for a point on right edge or rPair.First.Value=10800 for center point; rPair.Second.Value does not matter here.
- true in bScale to get world coordinates.
- Method GetPointAsB2DPoint() calls EnhancedCustomShape2d::GetParameter(fValX, rPair.First, bReplaceGeoSize, false). The main purpose of that method is to resolve modifier references and function references to values.
- Case „StretchX is not set“
- In that case method GetParameter() simply returns a given constant value. So we get fValX=21600 for a point on the right edge or fValX=10800 for center point respectively. These values are then scaled with variable fScaleX.
- The variable fXScale is a member in class EnhancedCustomShape2d. It has been initialized with fXScale=aLogicRect.GetWidth/nCoordWidth in method EnhancedCustomShape2d::SetPathSize(). It contains the factor to be used to get world coordinates from local coordinates. In case of the example it is fXScale=12cm/21600. In reality, it is not „12cm“, but the value in hundredths of a millimeter. But I use the unit here to easily distinguish local coordinates and world coordinates.
- So the final return value of GetPointAsB2DPoint() is then fValX=21600*12cm/21600=12cm or fValX=10800*12cm/21600=6cm respectively.
- Summary: In case „StretchX is not set“, the relationship of the points to each other is preserved, but the entire geometry is scaled to the world size of the shape.
- Case „StretchX is set“
- Here we need to take a deeper look at method GetParameter(). The call from GetPointAsB2DPoint gives the parameters
- rRetValue = fValX,
- rParameter = {NORMAL,21600} or rParameter = {NORMAL,10800) respectively,
- bRepalceGeoWidth=true and
- bReplaceGeoHeight=false.
- rRetValue = fValX,
- The switch case NORMAL is used and because the TypeClass of 21600 or 10800 is not DOUBLE it goes to the else branch therein. There we find, that in case the value is at the right edge the return value is multiplied with variable fXRatio.
- The variable fXRatio is a member in class EnhancedCustomShape2d. It has been set in method EnhancedCustomShape2d::SetPathSize(). There it is set to fXRatio=1.0 for normal cases, but has a special handling in case nXRef!=0x8000000. That condition is the same as „StretchX is set“. In this special case it determines the ratio of width/height of the shape in world coordinates, in the example it sets fXRatio=12cm/3cm=4.0. But it uses that value only if fXRatio>1 (as of Jun 2022, see bug tdf#149468) and otherwise the normal value 1.0. The condition means, that a shape with a square viewBox rectangle is horizontally stretched.
- In that case it changes fXScale too; it becomes fXScale=12cm/21600 / 4.0 = 3cm/21600. So for a point on the right edge we have fXRatio=4.0 and fXScale=3cm/21600 and for all other points we have fXRatio=1.0 and fXScale=12cm/21600.
- Back to method GetParameter we get fRetValue=21600*4.0=86400.0 for the point on the edge and fRetValue=10800.0 for the center point.
- And back to method GetPointAsB2DPoint we get fValX=86400*3cm/21600=12cm for the point on the edge and fValX=10800*3cm/21600=1.5cm for the center point.
- Summary: In case „StretchX is set“, the world coordinates are so as if the shape is square with minimum of svg:Width and svg:Height as size. That is done by manipulating the scaling factor fXScale. Only for a point on the right edge we get the „original“ world coordinates, because the return value from GetParameter() was changed to compensate the manipulated scaling factor.
For to keep the ratio of points, but move them to the right side of the shape (as described in the ODF specification cited above), their coordinates need to be defined relative to the right edge of the shape and not to the left edge as it is usually done. In formulas not the constant number 21600 but the identifier right has to be used.
The corresponding members in mso_CustomShapes struct are nXRef and nYRef. The same identifiers, with same meaning are used as members of class EnhancedCustomShape2d. There they are initialized with value 0x80000000 in the ctor. That is interpreted as “property is not set”. The mso_CustomShapes structs in the descriptions of AutoShapes use the constant MIN_INT32 = std::numeric_limits<sal_Int32>::min() for the same purpose.
Find a presentation about the current implementation (as of Oct 2022) at https://events.documentfoundation.org/media/libreoffice-conference-2022/submissions/ZE9SYW/resources/Limo_Stretch_Talk_0ZXTAJj.odp.
SubViewSize
If a shape is rendered, that was imported from OOXML, the SubViewSize property is evaluated. It is a sequence of items of type css.awt.Size. For each sub-path in the draw:enhanced-path attribute the item sets the width and height of the used viewBox. Values for left and top are 0 in that case.
It is a proprietary attribute of LibreOffice. Therefore it can only be used in proprietary namespace in “extended” ODF, here written as drawooo:sub-view-size attribute. An overview of extensions to ODF by LibreOffice is in List of LibreOffice_ODF_Extensions.
TextFrames
The corresponding attribute in ODF is draw:text-areas [ODF 19.220]. It determines the rectangle into which the text content of the shape is written. It has one or two sets of four parameters. If a second set is given, it is only used for vertical writing directions. The parameters in the set mean position of left, top, right and bottom edge of the area. The positions may not only use constant values but references to modifiers or to equationa as well. If a definition for a text area is omitted, the entire area of the shape is used.
OOXML has such rectangle as element <rect> [OOXML 20.1.9.22] with attributes l, t, r and b. The values of these attributes may be guide references. There exists only one element for all writing directions. The import and export filter in module oox should handle it correctly, only a second text-area rectangle will be lost on export.
VML has such rectangle as textboxrect attribute of the element path. If MS Office opens a RTF or DOC document and saves it in DOCX format in “Compatible” mode, then it uses VML for a shape. When older version of LibreOffice open such DOCX document, they generate a path object; and that has no text-area rectangle at all. Current LO 7.4 generates a custom shape on opening, but does not interpret the textboxrect attribute.
MS binary format and RTL have such text areas as attribute pInscribe. The import and export filter for MS binary format have it as property DFF_Prop_textRectangles and it works well. Export is in file escherex.cxx, import in file msdffimp.cxx.
The corresponding member in mso_CustomShapes struct, which is used for the description of AutoShapes, is pTextRect. That is a sequence of items of type SvxMSDffTextRectangles, which is a struct with components nPairA and nPairB of type SVxMSDffVertPair. nPairA has the top-left vertex and nPairB the bottom-right vertex. SvxMSDffVertPair is a struct with component nValA for the x-coordinate and nValB for the y-coordinate. Because of this nesting of structs it is written with nested brackets, for example
const SvxMSDffTextRectangles mso_sptHexagonTextRect[] = { { { 3 MSO_I, 3 MSO_I }, { 4 MSO_I, 4 MSO_I } } };
Same as with Coordinates property a reference to an equation is possible with the flag MSO_I, a reference to a modifier is not possible.
The method SdrObjCustomShape::MergeDefaultAttributes converts such value correctly to the TextFrames property.
TextPathAllowed
The corresponding attribute in ODF is draw:text-path-allowed [ODF 19.222]. LibreOffice does not evaluate this attribute. It has no UI to allow the user to switch the text-path of an custom shape on or off. That corresponds to the fact, that the default value for this attribute is false. But nevertheless the value of attribute TextPath can be toggled via macro.
ToDo: Other file formats?
Relevant Specifications
- [MS]
- [MS-ODRAW] Office Drawing Binary File Format
- References in this article use the version 10.0 2022-05-17.
- https://docs.microsoft.com/en-us/openspecs/office_file_formats/ms-odraw/8560795e-7759-4745-838f-f7f2ef2f1872 [last visited 2022-05-28]
- [MS97]
- Microsoft Office Drawing 97-2007 Binary Format Specification
- Some information is easier to get from the old version, e.g. the geometry definition of the AutoShapes.
- https://download.microsoft.com/download/0/B/E/0BE8BDD7-E5E8-422A-ABFD-4342ED7AD886/OfficeDrawing97-2007BinaryFormatSpecification.pdf [last visited 2022-06-07]
- [RTF] Rich Text Format.
- The specification is no longer directly available from Microsoft. The article “Rich Text Format” on en.wikipedia.org lists two download links. The first one is a pdf-file with images, so not search-able. The second one is a better suitable pdf-file. References on this page use the version RTF 1.9.1.
- https://web.archive.org/web/20190708132914/http://www.kleinlercher.at/tools/Windows_Protocols/Word2007RTFSpec9.pdf [last visited 2022-05-27]
- [VML] Vector Markup Language.
- This file format is deprecated, but the markup is still used in docx-files in ‘compatibility mode’. VML is related to the MS binary format. The latest version of the specification is given in chapter 19 “VML Reference Material” in part 4 of the OOXML standard. Look for “ISO/IEC 29500-4:2016” on https://standards.iso.org/ittf/PubliclyAvailableStandards/index.html
- The version as of 2011 is in the archive of Microsoft https://docs.microsoft.com/en-us/windows/win32/vml/msdn-online-vector-markup-language-object-model-reference.
- The initial version can be found at https://www.w3.org/TR/NOTE-VML [last visited 2022-05-28]
- [OOXML] Office Open XML
- Part 1 is relevant here. Look for “ISO/IEC 29500-1:2016” on https://standards.iso.org/ittf/PubliclyAvailableStandards/index.html
- The zip archive “Electronic inserts” belonging to Part 1 contains files presetShapeDefinitions.xml and presertTextWarpDefinitions.xml, which have the markup equivalents for the preset shapes.
- [OI29500]
- Microsoft publishes “Office Implementation Information for ISO/IEC 29500 Standards Support” which documents errors in the standard and deviations from the standard in MS Office.
- https://docs.microsoft.com/en-us/openspecs/office_standards/ms-oi29500/1fd4a662-8623-49c0-82f0-18fa91b413b8 [last visited 2022-06-01]
- [ECMA]
- Office Open XML File Formats – Part 1 ECMA-376 Part 1 3rd Edition / June 2011
- This is a predecessor of the standard at ISO. It is the last edition, which has a list of parent and child elements. The current ISO version lists only attributes in the text. So it might be useful for searching element names.
- https://www.ecma-international.org/wp-content/uploads/ecma-376_third_edition_june_2011.zip [last visited 2022-05-28].
- [ODF] Open Document Format
- Open Document Format for Office Applications (OpenDocument) Version 1.3. Part 3: OpenDocument Schema. OASIS Standard 27 April 2021
- This is available in odt, html and pdf format.
- https://docs.oasis-open.org/office/OpenDocument/v1.3/os/part3-schema/ [last visited 2022-05-28].