Skip to content
Next Next commit
Add a rudimentary anchor type shape
  • Loading branch information
jtrain committed Apr 28, 2017
commit b8d115b30c7fd8828c17d57c70cdee5a934b4d10
232 changes: 232 additions & 0 deletions docs/dev/analysis/features/shapes/shapes-anchor.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
Anchor shape
============

Word allows a graphical object to be placed into a document as a floating
object. A floating shape appears as a ``<w:drawing>`` element as a child of
a ``<w:r>`` element and has a ``<w:anchor>`` child.


Candidate protocol -- anchor shape access
-----------------------------------------

The following interactive session illustrates the protocol for accessing an
anchor shape::

>>> shapes = document.body.anchor_shapes
>>> shape = shapes[0]
>>> assert shape.type == MSO_SHAPE_TYPE.PICTURE


Resources
---------

* `Document Members (Word) on MSDN`_
* `Shape Members (Word) on MSDN`_

.. _Document Members (Word) on MSDN:
http://msdn.microsoft.com/en-us/library/office/ff840898.aspx

.. _Shape Members (Word) on MSDN:
http://msdn.microsoft.com/en-us/library/office/ff195191.aspx


MS API
------

The Shapes and InlineShapes properties on Document hold references to things
like pictures in the MS API.

* Height and Width
* Borders
* Shadow
* Hyperlink
* PictureFormat (providing brightness, color, crop, transparency, contrast)
* ScaleHeight and ScaleWidth
* HasChart
* HasSmartArt
* Type (Chart, LockedCanvas, Picture, SmartArt, etc.)


Spec references
---------------

* 17.3.3.9 drawing (DrawingML Object)
* 20.4.2.3 anchor (Anchor DrawingML Object)
* 20.4.2.7 extent (Drawing Object Size)


Minimal XML
-----------

.. highlight:: xml

This XML represents my best guess of the minimal inline shape container that
Word will load::

<w:r>
<w:drawing>
<wp:anchor>
<wp:simplePos x="0" y="0" />
<wp:positionH relativeFrom="margin">
<wp:align>right</wp:align>
</wp:positionH>
<wp:positionV relativeFrom="margin">
<wp:align>center</wp:align>
</wp:positionV>
<wp:wrapSquare wrapText="bothSides"/>
<wp:extent cx="914400" cy="914400"/>
<wp:docPr id="1" name="Picture 1"/>
<a:graphic xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
<a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture">

<!-- might not have to put anything here for a start -->

</a:graphicData>
</a:graphic>
</wp:inline>
</w:drawing>
</w:r>


Specimen XML
------------

.. highlight:: xml

A ``CT_Drawing`` (``<w:drawing>``) element can appear in a run, as a peer of,
for example, a ``<w:t>`` element. This element contains a DrawingML object.
WordprocessingML drawings are discussed in section 20.4 of the ISO/IEC spec.

This XML represents an inline shape inserted inline on a paragraph by itself.
The particulars of the graphical object itself are redacted::

<w:p>
<w:r>
<w:rPr/>
<w:noProof/>
</w:rPr>
<w:drawing>
<wp:anchor distT="0" distB="0" distL="0" distR="0" behindDoc="0" locked="0" allowOverlap="1" simplePos="0" wp14:anchorId="1BDE1558" wp14:editId="31E593BB">
<wp:extent cx="859536" cy="343814"/>
<wp:effectExtent l="0" t="0" r="4445" b="12065"/>
<wp:docPr id="1" name="Picture 1"/>
<wp:cNvGraphicFramePr>
<a:graphicFrameLocks xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" noChangeAspect="1"/>
</wp:cNvGraphicFramePr>
<a:graphic xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
<a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture">

<!-- graphical object, such as pic:pic, goes here -->

</a:graphicData>
</a:graphic>
</wp:anchor>
</w:drawing>
</w:r>
</w:p>


Schema definitions
------------------

.. highlight:: xml

::

<xsd:complexType name="CT_Drawing">
<xsd:choice minOccurs="1" maxOccurs="unbounded">
<xsd:element ref="wp:anchor" minOccurs="0"/>
<xsd:element ref="wp:inline" minOccurs="0"/>
</xsd:choice>
</xsd:complexType>

<xsd:complexType name="CT_Anchor">
<xsd:sequence>
<xsd:element name="simplePos" type="a:CT_Point2D"/>
<xsd:element name="positionH" type="CT_PosH"/>
<xsd:element name="positionV" type="CT_PosV"/>
<xsd:element name="extent" type="a:CT_PositiveSize2D"/>
<xsd:element name="effectExtent" type="CT_EffectExtent" minOccurs="0"/>
<xsd:group ref="EG_WrapType"/>
<xsd:element name="docPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
<xsd:element name="cNvGraphicFramePr" type="a:CT_NonVisualGraphicFrameProperties"
minOccurs="0" maxOccurs="1"/>
<xsd:element ref="a:graphic" minOccurs="1" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute name="distT" type="ST_WrapDistance" use="optional"/>
<xsd:attribute name="distB" type="ST_WrapDistance" use="optional"/>
<xsd:attribute name="distL" type="ST_WrapDistance" use="optional"/>
<xsd:attribute name="distR" type="ST_WrapDistance" use="optional"/>
<xsd:attribute name="simplePos" type="xsd:boolean"/>
<xsd:attribute name="relativeHeight" type="xsd:unsignedInt" use="required"/>
<xsd:attribute name="behindDoc" type="xsd:boolean" use="required"/>
<xsd:attribute name="locked" type="xsd:boolean" use="required"/>
<xsd:attribute name="layoutInCell" type="xsd:boolean" use="required"/>
<xsd:attribute name="hidden" type="xsd:boolean" use="optional"/>
<xsd:attribute name="allowOverlap" type="xsd:boolean" use="required"/>
</xsd:complexType>

<xsd:complexType name="CT_PosV">
<xsd:sequence>
<xsd:choice minOccurs="1" maxOccurs="1">
<xsd:element name="align" type="ST_AlignV" minOccurs="1" maxOccurs="1"/>
<xsd:element name="posOffset" type="ST_PositionOffset" minOccurs="1" maxOccurs="1"/>
</xsd:choice>
</xsd:sequence>
<xsd:attribute name="relativeFrom" type="ST_RelFromV" use="required"/>
</xsd:complexType>

<xsd:complexType name="CT_PosH">
<xsd:sequence>
<xsd:choice minOccurs="1" maxOccurs="1">
<xsd:element name="align" type="ST_AlignH" minOccurs="1" maxOccurs="1"/>
<xsd:element name="posOffset" type="ST_PositionOffset" minOccurs="1" maxOccurs="1"/>
</xsd:choice>
</xsd:sequence>
<xsd:attribute name="relativeFrom" type="ST_RelFromH" use="required"/>
</xsd:complexType>

<xsd:complexType name="CT_PositiveSize2D">
<xsd:attribute name="cx" type="ST_PositiveCoordinate" use="required"/>
<xsd:attribute name="cy" type="ST_PositiveCoordinate" use="required"/>
</xsd:complexType>

<xsd:complexType name="CT_EffectExtent">
<xsd:attribute name="l" type="a:ST_Coordinate" use="required"/>
<xsd:attribute name="t" type="a:ST_Coordinate" use="required"/>
<xsd:attribute name="r" type="a:ST_Coordinate" use="required"/>
<xsd:attribute name="b" type="a:ST_Coordinate" use="required"/>
</xsd:complexType>

<xsd:complexType name="CT_NonVisualDrawingProps">
<xsd:sequence>
<xsd:element name="hlinkClick" type="CT_Hyperlink" minOccurs="0"/>
<xsd:element name="hlinkHover" type="CT_Hyperlink" minOccurs="0"/>
<xsd:element name="extLst" type="CT_OfficeArtExtensionList" minOccurs="0"/>
</xsd:sequence>
<xsd:attribute name="id" type="ST_DrawingElementId" use="required"/>
<xsd:attribute name="name" type="xsd:string" use="required"/>
<xsd:attribute name="descr" type="xsd:string" default=""/>
<xsd:attribute name="hidden" type="xsd:boolean" default="false"/>
<xsd:attribute name="title" type="xsd:string" default=""/>
</xsd:complexType>

<xsd:complexType name="CT_NonVisualGraphicFrameProperties">
<xsd:sequence>
<xsd:element name="graphicFrameLocks" type="CT_GraphicalObjectFrameLocking" minOccurs="0"/>
<xsd:element name="extLst" type="CT_OfficeArtExtensionList" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>

<xsd:complexType name="CT_GraphicalObject">
<xsd:sequence>
<xsd:element name="graphicData" type="CT_GraphicalObjectData"/>
</xsd:sequence>
</xsd:complexType>

<xsd:complexType name="CT_GraphicalObjectData">
<xsd:sequence>
<xsd:any minOccurs="0" maxOccurs="unbounded" processContents="strict"/>
</xsd:sequence>
<xsd:attribute name="uri" type="xsd:token" use="required"/>
</xsd:complexType>
16 changes: 16 additions & 0 deletions docx/enum/shape.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,20 @@ class WD_INLINE_SHAPE_TYPE(object):
SMART_ART = 15
NOT_IMPLEMENTED = -6


WD_INLINE_SHAPE = WD_INLINE_SHAPE_TYPE


class WD_ANCHOR_SHAPE_TYPE(object):
"""
Corresponds to WdInlineShapeType enumeration
http://msdn.microsoft.com/en-us/library/office/ff192587.aspx
"""
CHART = 12
LINKED_PICTURE = 4
PICTURE = 3
SMART_ART = 15
NOT_IMPLEMENTED = -6


WD_ANCHOR_SHAPE = WD_ANCHOR_SHAPE_TYPE
3 changes: 2 additions & 1 deletion docx/oxml/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ def OxmlElement(nsptag_str, attrs=None, nsdecls=None):
register_element_cls('w:type', CT_SectType)

from .shape import (
CT_Blip, CT_BlipFillProperties, CT_GraphicalObject,
CT_Anchor, CT_Blip, CT_BlipFillProperties, CT_GraphicalObject,
CT_GraphicalObjectData, CT_Inline, CT_NonVisualDrawingProps, CT_Picture,
CT_PictureNonVisual, CT_Point2D, CT_PositiveSize2D, CT_ShapeProperties,
CT_Transform2D
Expand All @@ -112,6 +112,7 @@ def OxmlElement(nsptag_str, attrs=None, nsdecls=None):
register_element_cls('wp:docPr', CT_NonVisualDrawingProps)
register_element_cls('wp:extent', CT_PositiveSize2D)
register_element_cls('wp:inline', CT_Inline)
register_element_cls('wp:anchor', CT_Anchor)

from .styles import CT_LatentStyles, CT_LsdException, CT_Style, CT_Styles
register_element_cls('w:basedOn', CT_String)
Expand Down
23 changes: 23 additions & 0 deletions docx/oxml/shape.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,29 @@ def _inline_xml(cls):
)


class CT_Anchor(CT_Inline):
"""
``<w:anchor>`` element, container for a floating shape.
"""
wrapSquare = ZeroOrOne('wp:wrapSquare')

@classmethod
def _inline_xml(cls):
return (
'<wp:anchor %s>\n'
' <wp:extent cx="914400" cy="914400"/>\n'
' <wp:wordSquare wrapText="bothSides" />\n'
' <wp:docPr id="666" name="unnamed"/>\n'
' <wp:cNvGraphicFramePr>\n'
' <a:graphicFrameLocks noChangeAspect="1"/>\n'
' </wp:cNvGraphicFramePr>\n'
' <a:graphic>\n'
' <a:graphicData uri="URI not set"/>\n'
' </a:graphic>\n'
'</wp:anchor>' % nsdecls('wp', 'a', 'pic', 'r')
)


class CT_NonVisualDrawingProps(BaseOxmlElement):
"""
Used for ``<wp:docPr>`` element, and perhaps others. Specifies the id and
Expand Down
11 changes: 9 additions & 2 deletions docx/parts/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from .numbering import NumberingPart
from ..opc.constants import RELATIONSHIP_TYPE as RT
from ..opc.part import XmlPart
from ..oxml.shape import CT_Inline
from ..oxml.shape import CT_Anchor, CT_Inline
from ..shape import InlineShapes
from ..shared import lazyproperty
from .settings import SettingsPart
Expand Down Expand Up @@ -89,10 +89,17 @@ def new_pic_inline(self, image_descriptor, width, height):
specified by *image_descriptor* and scaled based on the values of
*width* and *height*.
"""
return self.new_pic(image_descriptor, width, height, inline=True)

def new_pic(self, image_descriptor, width, height, inline=True):
rId, image = self.get_or_add_image(image_descriptor)
cx, cy = image.scaled_dimensions(width, height)
shape_id, filename = self.next_id, image.filename
return CT_Inline.new_pic_inline(shape_id, rId, filename, cx, cy)
if inline:
ShapeType = CT_Inline
else:
ShapeType = CT_Anchor
return ShapeType.new_pic_inline(shape_id, rId, filename, cx, cy)

@property
def next_id(self):
Expand Down
29 changes: 28 additions & 1 deletion docx/shape.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
absolute_import, division, print_function, unicode_literals
)

from .enum.shape import WD_INLINE_SHAPE
from .enum.shape import WD_INLINE_SHAPE, WD_ANCHOR_SHAPE
from .oxml.ns import nsmap
from .shared import Parented

Expand Down Expand Up @@ -101,3 +101,30 @@ def width(self):
def width(self, cx):
self._inline.extent.cx = cx
self._inline.graphic.graphicData.pic.spPr.cx = cx


class AnchorShape(InlineShape):
"""
Proxy for an ``<wp:anchor>`` element, representing the container for a
positioned graphical element.
"""

@property
def type(self):
"""
The type of this anchored shape as a member of
``docx.enum.shape.WD_INLINE_SHAPE``, e.g. ``LINKED_PICTURE``.
Read-only.
"""
graphicData = self._inline.graphic.graphicData
uri = graphicData.uri
if uri == nsmap['pic']:
blip = graphicData.pic.blipFill.blip
if blip.link is not None:
return WD_ANCHOR_SHAPE.LINKED_PICTURE
return WD_ANCHOR_SHAPE.PICTURE
if uri == nsmap['c']:
return WD_ANCHOR_SHAPE.CHART
if uri == nsmap['dgm']:
return WD_ANCHOR_SHAPE.SMART_ART
return WD_ANCHOR_SHAPE.NOT_IMPLEMENTED
10 changes: 10 additions & 0 deletions tests/oxml/unitdata/dml.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@
from ...unitdata import BaseBuilder


class CT_AnchorBuilder(BaseBuilder):
__tag__ = 'wp:anchor'
__nspfxs__ = ('wp',)
__attrs__ = ('distT', 'distB', 'distL', 'distR')


class CT_BlipBuilder(BaseBuilder):
__tag__ = 'a:blip'
__nspfxs__ = ('a',)
Expand Down Expand Up @@ -195,6 +201,10 @@ def an_inline():
return CT_InlineBuilder()


def an_anchor():
return CT_AnchorBuilder()


def an_nvPicPr():
return CT_PictureNonVisualBuilder()

Expand Down
Loading