Skip to content

Commit c5b921e

Browse files
author
Steve Canny
committed
shp: add InlineShape.width, height getters
Along the way: * extract tests/parts/test_shape.py
1 parent 0bed6a8 commit c5b921e

File tree

8 files changed

+237
-162
lines changed

8 files changed

+237
-162
lines changed

docs/dev/analysis/features/shapes-inline-size.rst

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,6 @@ inline with, however its dimensions can be specified. For some shape types,
1111
both the contained shape and the shape container specify a width and height.
1212

1313

14-
Acceptance tests
15-
----------------
16-
17-
Feature: Query and change dimensions of inline shape
18-
In order to adjust the display size of an inline shape
19-
As a python-docx developer
20-
I need to query and change the width and height of an inline shape
21-
22-
Scenario: Query inline shape dimensions
23-
Given an inline shape of known dimensions
24-
Then the dimensions of the inline shape match the known values
25-
26-
Scenario: Change inline shape dimensions
27-
Given an inline shape of known dimensions
28-
When I change the dimensions of the inline shape
29-
Then the dimensions of the inline shape match the new values
30-
31-
3214
Unit tests
3315
----------
3416

docx/oxml/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,14 @@ class ValidationError(Exception):
2323

2424
from docx.oxml.shape import (
2525
CT_Blip, CT_BlipFillProperties, CT_GraphicalObject,
26-
CT_GraphicalObjectData, CT_Inline, CT_Picture
26+
CT_GraphicalObjectData, CT_Inline, CT_Picture, CT_PositiveSize2D
2727
)
2828
register_custom_element_class('a:blip', CT_Blip)
2929
register_custom_element_class('a:graphic', CT_GraphicalObject)
3030
register_custom_element_class('a:graphicData', CT_GraphicalObjectData)
3131
register_custom_element_class('pic:blipFill', CT_BlipFillProperties)
3232
register_custom_element_class('pic:pic', CT_Picture)
33+
register_custom_element_class('wp:extent', CT_PositiveSize2D)
3334
register_custom_element_class('wp:inline', CT_Inline)
3435

3536
from docx.oxml.parts.document import CT_Body, CT_Document

docx/oxml/shape.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from docx.oxml.shared import (
88
nsmap, nspfxmap, OxmlBaseElement, OxmlElement, qn
99
)
10+
from docx.shared import Emu
1011

1112

1213
class CT_Blip(OxmlBaseElement):
@@ -84,6 +85,10 @@ class CT_Inline(OxmlBaseElement):
8485
"""
8586
``<w:inline>`` element, container for an inline shape.
8687
"""
88+
@property
89+
def extent(self):
90+
return self.find(qn('wp:extent'))
91+
8792
@property
8893
def graphic(self):
8994
return self.find(qn('a:graphic'))
@@ -183,12 +188,24 @@ class CT_PositiveSize2D(OxmlBaseElement):
183188
Used for ``<wp:extent>`` element, and perhaps others later. Specifies the
184189
size of a DrawingML drawing.
185190
"""
191+
@property
192+
def cx(self):
193+
cx_str = self.get('cx')
194+
cx = int(cx_str)
195+
return Emu(cx)
196+
197+
@property
198+
def cy(self):
199+
cy_str = self.get('cy')
200+
cy = int(cy_str)
201+
return Emu(cy)
202+
186203
@classmethod
187204
def new(cls, nsptagname_str, cx, cy):
188-
elt = OxmlElement(nsptagname_str)
189-
elt.set('cx', str(cx))
190-
elt.set('cy', str(cy))
191-
return elt
205+
elm = OxmlElement(nsptagname_str)
206+
elm.set('cx', str(cx))
207+
elm.set('cy', str(cy))
208+
return elm
192209

193210

194211
class CT_PresetGeometry2D(OxmlBaseElement):

docx/shape.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,20 @@ def __init__(self, inline):
2323
super(InlineShape, self).__init__()
2424
self._inline = inline
2525

26+
@property
27+
def height(self):
28+
"""
29+
Return the display height of this inline shape as an |Emu| instance.
30+
Length instances such as this behave like an int, but also have
31+
built-in conversion properties, e.g.::
32+
33+
\>>> inline_shape.height
34+
914400
35+
\>>> inline_shape.height.inches
36+
1.0
37+
"""
38+
return self._inline.extent.cy
39+
2640
@classmethod
2741
def new_picture(cls, r, image_part, rId, shape_id):
2842
"""
@@ -41,6 +55,10 @@ def new_picture(cls, r, image_part, rId, shape_id):
4155

4256
@property
4357
def type(self):
58+
"""
59+
Return the member of ``docx.enum.shape.WD_INLINE_SHAPE`` denoting the
60+
inline shape type, e.g. ``LINKED_PICTURE``.
61+
"""
4462
graphicData = self._inline.graphic.graphicData
4563
uri = graphicData.uri
4664
if uri == nsmap['pic']:
@@ -53,3 +71,10 @@ def type(self):
5371
if uri == nsmap['dgm']:
5472
return WD_INLINE_SHAPE.SMART_ART
5573
return WD_INLINE_SHAPE.NOT_IMPLEMENTED
74+
75+
@property
76+
def width(self):
77+
"""
78+
Return the display width of this inline shape as an |Emu| instance.
79+
"""
80+
return self._inline.extent.cx

features/shp-inline-shape-size.feature

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ Feature: Query and change dimensions of inline shape
33
As a python-docx developer
44
I need to query and change the width and height of an inline shape
55

6-
@wip
76
Scenario: Query inline shape dimensions
87
Given an inline shape of known dimensions
98
Then the dimensions of the inline shape match the known values

features/steps/shape.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from docx import Document
1414
from docx.enum.shape import WD_INLINE_SHAPE
1515
from docx.parts.document import InlineShape, InlineShapes
16+
from docx.shared import Inches
1617

1718
from .helpers import test_docx, test_file_path
1819

@@ -71,6 +72,13 @@ def when_add_inline_picture_to_document(context):
7172
)
7273

7374

75+
@when('I change the dimensions of the inline shape')
76+
def when_change_dimensions_of_inline_shape(context):
77+
inline_shape = context.inline_shape
78+
inline_shape.width = Inches(1)
79+
inline_shape.height = Inches(0.5)
80+
81+
7482
# then =====================================================
7583

7684
@then('I can access each inline shape by index')
@@ -115,9 +123,16 @@ def then_inline_shape_type_is_shape_type(context, shape_type):
115123

116124
@then('the dimensions of the inline shape match the known values')
117125
def then_dimensions_of_inline_shape_match_known_values(context):
126+
inline_shape = context.inline_shape
127+
assert inline_shape.width == 1778000, 'got %s' % inline_shape.width
128+
assert inline_shape.height == 711200, 'got %s' % inline_shape.height
129+
130+
131+
@then('the dimensions of the inline shape match the new values')
132+
def then_dimensions_of_inline_shape_match_new_values(context):
118133
inline_shape = context.inline_shape
119134
assert inline_shape.width == 914400, 'got %s' % inline_shape.width
120-
assert inline_shape.height == 914400, 'got %s' % inline_shape.height
135+
assert inline_shape.height == 457200, 'got %s' % inline_shape.height
121136

122137

123138
@then('the document contains the inline picture')

tests/parts/test_document.py

Lines changed: 1 addition & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,10 @@
1010

1111
from mock import Mock
1212

13-
from docx.enum.shape import WD_INLINE_SHAPE
1413
from docx.opc.constants import CONTENT_TYPE as CT, RELATIONSHIP_TYPE as RT
1514
from docx.opc.package import PartFactory
1615
from docx.opc.packuri import PackURI
1716
from docx.oxml.parts.document import CT_Body, CT_Document
18-
from docx.oxml.shared import nsmap
1917
from docx.oxml.text import CT_R
2018
from docx.package import ImageParts, Package
2119
from docx.parts.document import _Body, DocumentPart, InlineShapes
@@ -24,11 +22,7 @@
2422
from docx.table import Table
2523
from docx.text import Paragraph
2624

27-
from ..oxml.unitdata.dml import (
28-
a_blip, a_blipFill, a_cNvPr, a_cNvPicPr, a_docPr, a_drawing, a_fillRect,
29-
a_graphic, a_graphicData, a_pic, a_prstGeom, a_stretch, an_ext,
30-
an_extent, an_inline, an_nvPicPr, an_off, an_spPr, an_xfrm
31-
)
25+
from ..oxml.unitdata.dml import a_drawing, an_inline
3226
from ..oxml.parts.unitdata.document import a_body, a_document
3327
from ..oxml.unitdata.table import (
3428
a_gridCol, a_tbl, a_tblGrid, a_tblPr, a_tc, a_tr
@@ -393,136 +387,6 @@ def _tr_bldr(self, cols):
393387
return tr_bldr
394388

395389

396-
class DescribeInlineShape(object):
397-
398-
def it_knows_what_type_of_shape_it_is(self, shape_type_fixture):
399-
inline_shape, inline_shape_type = shape_type_fixture
400-
assert inline_shape.type == inline_shape_type
401-
402-
def it_can_contruct_a_new_inline_picture_shape(
403-
self, new_picture_fixture):
404-
inline_shape, r, image_part_, rId, shape_id, expected_inline_xml = (
405-
new_picture_fixture
406-
)
407-
picture = inline_shape.new_picture(r, image_part_, rId, shape_id)
408-
assert picture._inline.xml == expected_inline_xml
409-
assert r[0][0] is picture._inline
410-
411-
# fixtures -------------------------------------------------------
412-
413-
@pytest.fixture
414-
def image_params(self):
415-
filename = 'foobar.garf'
416-
rId = 'rId42'
417-
cx, cy = 914422, 223344
418-
return filename, rId, cx, cy
419-
420-
@pytest.fixture
421-
def image_part_(self, request, image_params):
422-
filename, rId, cx, cy = image_params
423-
image_part_ = instance_mock(request, ImagePart)
424-
image_part_.default_cx = cx
425-
image_part_.default_cy = cy
426-
image_part_.filename = filename
427-
return image_part_
428-
429-
@pytest.fixture
430-
def new_picture_fixture(self, request, image_part_, image_params):
431-
filename, rId, cx, cy = image_params
432-
inline_shape = InlineShape(None)
433-
r = an_r().with_nsdecls().element
434-
shape_id = 7
435-
name = 'Picture %d' % shape_id
436-
uri = nsmap['pic']
437-
expected_inline = (
438-
an_inline().with_nsdecls('r', 'wp', 'w').with_child(
439-
an_extent().with_cx(cx).with_cy(cy)).with_child(
440-
a_docPr().with_id(shape_id).with_name(name)).with_child(
441-
a_graphic().with_nsdecls().with_child(
442-
a_graphicData().with_uri(uri).with_child(
443-
self._pic_bldr(filename, rId, cx, cy))))
444-
).element
445-
expected_inline_xml = expected_inline.xml
446-
return (
447-
inline_shape, r, image_part_, rId, shape_id, expected_inline_xml
448-
)
449-
450-
@pytest.fixture(params=[
451-
'embed pic', 'link pic', 'link+embed pic', 'chart', 'smart art',
452-
'not implemented'
453-
])
454-
def shape_type_fixture(self, request):
455-
if request.param == 'embed pic':
456-
inline = self._inline_with_picture(embed=True)
457-
shape_type = WD_INLINE_SHAPE.PICTURE
458-
459-
elif request.param == 'link pic':
460-
inline = self._inline_with_picture(link=True)
461-
shape_type = WD_INLINE_SHAPE.LINKED_PICTURE
462-
463-
elif request.param == 'link+embed pic':
464-
inline = self._inline_with_picture(embed=True, link=True)
465-
shape_type = WD_INLINE_SHAPE.LINKED_PICTURE
466-
467-
elif request.param == 'chart':
468-
inline = self._inline_with_uri(nsmap['c'])
469-
shape_type = WD_INLINE_SHAPE.CHART
470-
471-
elif request.param == 'smart art':
472-
inline = self._inline_with_uri(nsmap['dgm'])
473-
shape_type = WD_INLINE_SHAPE.SMART_ART
474-
475-
elif request.param == 'not implemented':
476-
inline = self._inline_with_uri('foobar')
477-
shape_type = WD_INLINE_SHAPE.NOT_IMPLEMENTED
478-
479-
return InlineShape(inline), shape_type
480-
481-
def _inline_with_picture(self, embed=False, link=False):
482-
picture_ns = nsmap['pic']
483-
484-
blip_bldr = a_blip()
485-
if embed:
486-
blip_bldr.with_embed('rId1')
487-
if link:
488-
blip_bldr.with_link('rId2')
489-
490-
inline = (
491-
an_inline().with_nsdecls('wp', 'r').with_child(
492-
a_graphic().with_nsdecls().with_child(
493-
a_graphicData().with_uri(picture_ns).with_child(
494-
a_pic().with_nsdecls().with_child(
495-
a_blipFill().with_child(
496-
blip_bldr)))))
497-
).element
498-
return inline
499-
500-
def _inline_with_uri(self, uri):
501-
inline = (
502-
an_inline().with_nsdecls('wp').with_child(
503-
a_graphic().with_nsdecls().with_child(
504-
a_graphicData().with_uri(uri)))
505-
).element
506-
return inline
507-
508-
def _pic_bldr(self, name, rId, cx, cy):
509-
return (
510-
a_pic().with_nsdecls().with_child(
511-
an_nvPicPr().with_child(
512-
a_cNvPr().with_id(0).with_name(name)).with_child(
513-
a_cNvPicPr())).with_child(
514-
a_blipFill().with_child(
515-
a_blip().with_embed(rId)).with_child(
516-
a_stretch().with_child(
517-
a_fillRect()))).with_child(
518-
an_spPr().with_child(
519-
an_xfrm().with_child(
520-
an_off().with_x(0).with_y(0)).with_child(
521-
an_ext().with_cx(cx).with_cy(cy))).with_child(
522-
a_prstGeom().with_prst('rect')))
523-
)
524-
525-
526390
class DescribeInlineShapes(object):
527391

528392
def it_knows_how_many_inline_shapes_it_contains(

0 commit comments

Comments
 (0)