Skip to content

Commit c66e4ed

Browse files
rtibblesbotclaude
andcommitted
feat: resize QTI images at publish to their authored size
- Sized <img> (integer width and height) ship a resized copy per size in the QTI package; width/height stay in the XML. - Rewrites go per element, so one image at two sizes ships two files. - Derived Perseus markdown carries ` =WxH`, so publish resizes and fills the images map. - Legacy items keep the size on their QTI <img>. Refs #6189 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent 107b8fa commit c66e4ed

7 files changed

Lines changed: 400 additions & 56 deletions

File tree

‎contentcuration/contentcuration/tests/utils/qti/test_html_to_markdown.py‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,19 @@ def test_image_gets_content_storage_prefix():
4141
assert expected in result
4242

4343

44+
def test_image_without_a_pixel_size_pair_carries_no_suffix():
45+
expected = "![d]({})".format(exercises.CONTENT_STORAGE_FORMAT.format("abc123.png"))
46+
for size in (
47+
"",
48+
'width="200"',
49+
'height="150"',
50+
'width="50%" height="150"',
51+
'width="0" height="150"',
52+
):
53+
result = _markdown_from_html(f'<p><img alt="d" src="abc123.png" {size}/></p>')
54+
assert result == expected, size
55+
56+
4457
def test_interaction_is_dropped_from_prompt():
4558
fragment = "<p>Fill <qti-text-entry-interaction/> in</p>"
4659
assert html_to_markdown(_elements(fragment)) == "Fill in"
@@ -172,6 +185,9 @@ def test_table():
172185
"inline."
173186
),
174187
"![alt text]({})".format(exercises.CONTENT_STORAGE_FORMAT.format("abc123.png")),
188+
"![sized]({} =200x150)".format(
189+
exercises.CONTENT_STORAGE_FORMAT.format("abc123.png")
190+
),
175191
"> A blockquote paragraph.",
176192
"- First bullet\n- Second bullet\n - Nested bullet\n- Third bullet",
177193
"1. First numbered\n2. Second numbered",

‎contentcuration/contentcuration/tests/utils/qti/test_media.py‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from contentcuration.tests.utils.qti.test_validation import VALID_CHOICE_ITEM
22
from contentcuration.utils.assessment.qti.media import get_qti_media_references
33
from contentcuration.utils.assessment.qti.media import rewrite_qti_media_paths
4+
from contentcuration.utils.assessment.qti.media import rewrite_qti_sized_image_paths
45
from contentcuration.utils.assessment.qti.media import set_qti_item_language
56
from contentcuration.utils.assessment.qti.validation import validate_qti_item
67

@@ -85,6 +86,38 @@ def test_rewrite_ignores_values_not_in_mapping():
8586
)
8687

8788

89+
def _path_for_size(filename, width, height):
90+
return f"images/{width}x{height}-{filename}"
91+
92+
93+
def test_sized_rewrite_points_each_sized_img_at_its_size():
94+
xml = (
95+
f'<item>\n <img alt="a > b" src="{CHECKSUM_A}.png" width="200" height="150"/>'
96+
f"<img height='75' src='{CHECKSUM_A}.png' width='100' />"
97+
f'<img src="{CHECKSUM_B}.png" width="200" height="150"></img></item>'
98+
)
99+
assert rewrite_qti_sized_image_paths(xml, _path_for_size) == (
100+
f'<item>\n <img alt="a > b" src="images/200x150-{CHECKSUM_A}.png" width="200" height="150"/>'
101+
f"<img height='75' src='images/100x75-{CHECKSUM_A}.png' width='100' />"
102+
f'<img src="images/200x150-{CHECKSUM_B}.png" width="200" height="150"></img></item>'
103+
)
104+
105+
106+
def test_sized_rewrite_leaves_imgs_without_a_pixel_size_or_checksum_src():
107+
xml = (
108+
f'<item><img src="{CHECKSUM_A}.png"/>'
109+
f'<img src="{CHECKSUM_A}.png" width="200"/>'
110+
f'<img src="{CHECKSUM_A}.png" width="50%" height="150"/>'
111+
f'<img srcset="{CHECKSUM_A}.png 1x" width="200" height="150"/>'
112+
f'<img permanentSrc="{CHECKSUM_A}.png" width="200" height="150"/>'
113+
f'<img data-src="{CHECKSUM_A}.png" width="200" height="150"/>'
114+
f'<img alt=\'src="{CHECKSUM_A}.png"\' width="200" height="150"/>'
115+
'<img src="https://example.com/x.png" width="200" height="150"/>'
116+
f'<object data="{CHECKSUM_A}.png" width="200" height="150"></object></item>'
117+
)
118+
assert rewrite_qti_sized_image_paths(xml, _path_for_size) == xml
119+
120+
88121
ITEM_WITHOUT_LANGUAGE = (
89122
'<qti-assessment-item xmlns="http://www.imsglobal.org/xsd/imsqtiasi_v3p0" '
90123
'identifier="i" title="t" adaptive="false" time-dependent="false">'

0 commit comments

Comments
 (0)