Skip to content

Commit f1368d2

Browse files
author
Steve Canny
committed
doc: add DocumentPart.new_pic_inline()
1 parent e7553c2 commit f1368d2

File tree

5 files changed

+110
-1
lines changed

5 files changed

+110
-1
lines changed

docx/image/image.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,22 @@ def vert_dpi(self):
117117
"""
118118
return self._image_header.vert_dpi
119119

120+
def scaled_dimensions(self, width, height):
121+
"""
122+
Return a (cx, cy) 2-tuple representing the native dimensions of this
123+
image scaled by applying the following rules to *width* and *height*.
124+
If both *width* and *height* are specified, the return value is
125+
(*width*, *height*); no scaling is performed. If only one is
126+
specified, it is used to compute a scaling factor that is then
127+
applied to the unspecified dimension, preserving the aspect ratio of
128+
the image. If both *width* and *height* are |None|, the native
129+
dimensions are returned. The native dimensions are calculated using
130+
the dots-per-inch (dpi) value embedded in the image, defaulting to 72
131+
dpi if no value is specified, as is often the case. The returned
132+
values are both |Length| objects.
133+
"""
134+
raise NotImplementedError
135+
120136
@lazyproperty
121137
def sha1(self):
122138
"""

docx/oxml/shape.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,18 @@ def new(cls, cx, cy, shape_id, pic):
7474
inline.graphic.graphicData._insert_pic(pic)
7575
return inline
7676

77+
@classmethod
78+
def new_pic_inline(cls, shape_id, rId, filename, cx, cy):
79+
"""
80+
Return a new `wp:inline` element containing the `pic:pic` element
81+
specified by the argument values.
82+
"""
83+
pic_id = 0 # Word doesn't seem to use this, but does not omit it
84+
pic = CT_Picture.new(pic_id, filename, rId, cx, cy)
85+
inline = cls.new(cx, cy, shape_id, pic)
86+
inline.graphic.graphicData._insert_pic(pic)
87+
return inline
88+
7789
@classmethod
7890
def _inline_xml(cls):
7991
return (

docx/parts/document.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from .numbering import NumberingPart
1313
from ..opc.constants import RELATIONSHIP_TYPE as RT
1414
from ..opc.part import XmlPart
15+
from ..oxml.shape import CT_Inline
1516
from ..shape import InlineShapes
1617
from ..shared import lazyproperty
1718
from .styles import StylesPart
@@ -41,6 +42,16 @@ def document(self):
4142
"""
4243
return Document(self._element, self)
4344

45+
def get_or_add_image(self, image_descriptor):
46+
"""
47+
Return an (rId, image) 2-tuple for the image identified by
48+
*image_descriptor*. *image* is an |Image| instance providing access
49+
to the properties of the image, such as dimensions and image type.
50+
*rId* is the key for the relationship between this document part and
51+
the image part, reused if already present, newly created if not.
52+
"""
53+
raise NotImplementedError
54+
4455
def get_or_add_image_part(self, image_descriptor):
4556
"""
4657
Return an ``(image_part, rId)`` 2-tuple for the image identified by
@@ -86,7 +97,10 @@ def new_pic_inline(self, image_descriptor, width, height):
8697
specified by *image_descriptor* and scaled based on the values of
8798
*width* and *height*.
8899
"""
89-
raise NotImplementedError
100+
rId, image = self.get_or_add_image(image_descriptor)
101+
cx, cy = image.scaled_dimensions(width, height)
102+
shape_id, filename = self.next_id, image.filename
103+
return CT_Inline.new_pic_inline(shape_id, rId, filename, cx, cy)
90104

91105
@property
92106
def next_id(self):

tests/parts/test_document.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import pytest
1010

11+
from docx.image.image import Image
1112
from docx.opc.constants import RELATIONSHIP_TYPE as RT
1213
from docx.opc.coreprops import CoreProperties
1314
from docx.package import ImageParts, Package
@@ -21,6 +22,7 @@
2122

2223
from ..oxml.parts.unitdata.document import a_body, a_document
2324
from ..oxml.unitdata.text import a_p
25+
from ..unitutil.file import snippet_text
2426
from ..unitutil.mock import (
2527
instance_mock, class_mock, method_mock, property_mock
2628
)
@@ -81,6 +83,16 @@ def it_knows_the_next_available_xml_id(self, next_id_fixture):
8183
document, expected_id = next_id_fixture
8284
assert document.next_id == expected_id
8385

86+
def it_can_create_a_new_pic_inline(self, new_pic_fixture):
87+
document_part, path, width, height = new_pic_fixture[:4]
88+
image_, expected_xml = new_pic_fixture[4:]
89+
90+
inline = document_part.new_pic_inline(path, width, height)
91+
92+
document_part.get_or_add_image.assert_called_once_with(path)
93+
image_.scaled_dimensions.assert_called_once_with(width, height)
94+
assert inline.xml == expected_xml
95+
8496
def it_can_get_a_style_by_id(self, get_style_fixture):
8597
document_part, style_id, style_type, style_ = get_style_fixture
8698
style = document_part.get_style(style_id, style_type)
@@ -163,6 +175,19 @@ def next_id_fixture(self, request):
163175
document = DocumentPart(None, None, document_elm, None)
164176
return document, expected_id
165177

178+
@pytest.fixture
179+
def new_pic_fixture(self, image_, get_or_add_image_, next_id_prop_):
180+
document_part = DocumentPart(None, None, None, None)
181+
path, width, height, rId = 'foo/bar.png', 111, 222, 'rId42'
182+
expected_xml = snippet_text('inline')
183+
184+
get_or_add_image_.return_value = rId, image_
185+
image_.scaled_dimensions.return_value = 444, 888
186+
image_.filename = 'bar.png'
187+
next_id_prop_.return_value = 24
188+
189+
return document_part, path, width, height, image_, expected_xml
190+
166191
@pytest.fixture
167192
def nmprt_create_fixture(self, part_related_by_, relate_to_,
168193
NumberingPart_, numbering_part_):
@@ -222,6 +247,14 @@ def get_or_add_image_fixture(
222247
image_part_, rId_
223248
)
224249

250+
@pytest.fixture
251+
def get_or_add_image_(self, request):
252+
return method_mock(request, DocumentPart, 'get_or_add_image')
253+
254+
@pytest.fixture
255+
def image_(self, request):
256+
return instance_mock(request, Image)
257+
225258
@pytest.fixture
226259
def image_descriptor_(self, request):
227260
return instance_mock(request, str)
@@ -240,6 +273,10 @@ def image_parts_(self, request, image_part_):
240273
def InlineShapes_(self, request):
241274
return class_mock(request, 'docx.parts.document.InlineShapes')
242275

276+
@pytest.fixture
277+
def next_id_prop_(self, request):
278+
return property_mock(request, DocumentPart, 'next_id')
279+
243280
@pytest.fixture
244281
def NumberingPart_(self, request):
245282
return class_mock(request, 'docx.parts.document.NumberingPart')

tests/test_files/snippets/inline.txt

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<wp:inline xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
2+
<wp:extent cx="444" cy="888"/>
3+
<wp:docPr id="24" name="Picture 24"/>
4+
<wp:cNvGraphicFramePr>
5+
<a:graphicFrameLocks noChangeAspect="1"/>
6+
</wp:cNvGraphicFramePr>
7+
<a:graphic>
8+
<a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture">
9+
<pic:pic>
10+
<pic:nvPicPr>
11+
<pic:cNvPr id="0" name="bar.png"/>
12+
<pic:cNvPicPr/>
13+
</pic:nvPicPr>
14+
<pic:blipFill>
15+
<a:blip r:embed="rId42"/>
16+
<a:stretch>
17+
<a:fillRect/>
18+
</a:stretch>
19+
</pic:blipFill>
20+
<pic:spPr>
21+
<a:xfrm>
22+
<a:off x="0" y="0"/>
23+
<a:ext cx="444" cy="888"/>
24+
</a:xfrm>
25+
<a:prstGeom prst="rect"/>
26+
</pic:spPr>
27+
</pic:pic>
28+
</a:graphicData>
29+
</a:graphic>
30+
</wp:inline>

0 commit comments

Comments
 (0)