1 | package pro.verron.officestamper.core; | |
2 | ||
3 | import jakarta.xml.bind.JAXBElement; | |
4 | import org.docx4j.dml.Graphic; | |
5 | import org.docx4j.dml.picture.Pic; | |
6 | import org.docx4j.dml.wordprocessingDrawing.Inline; | |
7 | import org.docx4j.openpackaging.exceptions.Docx4JException; | |
8 | import org.docx4j.openpackaging.packages.WordprocessingMLPackage; | |
9 | import org.docx4j.wml.Drawing; | |
10 | import org.docx4j.wml.R; | |
11 | import pro.verron.officestamper.api.OfficeStamperException; | |
12 | ||
13 | import java.io.IOException; | |
14 | import java.io.InputStream; | |
15 | import java.util.Arrays; | |
16 | ||
17 | /** | |
18 | * Extracts images from a docx document. | |
19 | * | |
20 | * @author Joseph Verron | |
21 | * @version ${version} | |
22 | * @since 1.4.7 | |
23 | */ | |
24 | public class DocxImageExtractor { | |
25 | ||
26 | private final WordprocessingMLPackage wordprocessingMLPackage; | |
27 | ||
28 | /** | |
29 | * Creates a new image extractor for the given docx document. | |
30 | * | |
31 | * @param wordprocessingMLPackage the docx document to extract images from. | |
32 | */ | |
33 | public DocxImageExtractor(WordprocessingMLPackage wordprocessingMLPackage) { | |
34 | this.wordprocessingMLPackage = wordprocessingMLPackage; | |
35 | } | |
36 | ||
37 | /** | |
38 | * Extract an image bytes from an embedded image run. | |
39 | * | |
40 | * @param run run containing the embedded drawing. | |
41 | */ | |
42 | byte[] getRunDrawingData(R run) { | |
43 | String imageRelId = getPic(run).getBlipFill() | |
44 | .getBlip() | |
45 | .getEmbed(); | |
46 | String imageRelPartName = getImageRelPartName(imageRelId); | |
47 | long size = getImageSize(imageRelPartName); | |
48 | InputStream stream = getImageStream(imageRelPartName); | |
49 |
1
1. getRunDrawingData : replaced return value with null for pro/verron/officestamper/core/DocxImageExtractor::getRunDrawingData → KILLED |
return streamToByteArray(size, stream); |
50 | } | |
51 | ||
52 | private static Pic getPic(R run) { | |
53 |
1
1. getPic : negated conditional → KILLED |
for (Object runContent : run.getContent()) { |
54 |
1
1. getPic : negated conditional → KILLED |
if (!(runContent instanceof JAXBElement<?> runElement)) break; |
55 |
1
1. getPic : negated conditional → KILLED |
if (!(runElement.getValue() instanceof Drawing drawing)) break; |
56 | Graphic graphic = getInlineGraphic(drawing); | |
57 |
1
1. getPic : replaced return value with null for pro/verron/officestamper/core/DocxImageExtractor::getPic → KILLED |
return graphic.getGraphicData() |
58 | .getPic(); | |
59 | } | |
60 | throw new OfficeStamperException("Run drawing not found !"); | |
61 | } | |
62 | ||
63 | private String getImageRelPartName(String imageRelId) { | |
64 | // TODO: find a better way to find image rel part name in source part store | |
65 |
1
1. getImageRelPartName : replaced return value with "" for pro/verron/officestamper/core/DocxImageExtractor::getImageRelPartName → KILLED |
return wordprocessingMLPackage.getMainDocumentPart() |
66 | .getRelationshipsPart() | |
67 | .getPart(imageRelId) | |
68 | .getPartName() | |
69 | .getName() | |
70 | .substring(1); | |
71 | } | |
72 | ||
73 | private long getImageSize(String imageRelPartName) { | |
74 | try { | |
75 |
1
1. getImageSize : replaced long return with 0 for pro/verron/officestamper/core/DocxImageExtractor::getImageSize → KILLED |
return wordprocessingMLPackage.getSourcePartStore() |
76 | .getPartSize(imageRelPartName); | |
77 | } catch (Docx4JException e) { | |
78 | throw new OfficeStamperException(e); | |
79 | } | |
80 | } | |
81 | ||
82 | private InputStream getImageStream(String imageRelPartName) { | |
83 | try { | |
84 |
1
1. getImageStream : replaced return value with null for pro/verron/officestamper/core/DocxImageExtractor::getImageStream → KILLED |
return wordprocessingMLPackage.getSourcePartStore() |
85 | .loadPart(imageRelPartName); | |
86 | } catch (Docx4JException e) { | |
87 | throw new OfficeStamperException(e); | |
88 | } | |
89 | } | |
90 | ||
91 | /** | |
92 | * Converts an InputStream to a byte array. | |
93 | * | |
94 | * @param size expected size of the byte array. | |
95 | * @param is input stream to read data from. | |
96 | * | |
97 | * @return the data from the input stream. | |
98 | */ | |
99 | private static byte[] streamToByteArray(long size, InputStream is) { | |
100 |
2
1. streamToByteArray : changed conditional boundary → SURVIVED 2. streamToByteArray : negated conditional → KILLED |
if (size > Integer.MAX_VALUE) throw new OfficeStamperException("Image size exceeds maximum allowed (2GB)"); |
101 | ||
102 | int intSize = (int) size; | |
103 | byte[] data = new byte[intSize]; | |
104 | int numRead = tryRead(is, data); | |
105 |
1
1. streamToByteArray : replaced return value with null for pro/verron/officestamper/core/DocxImageExtractor::streamToByteArray → KILLED |
return Arrays.copyOfRange(data, 0, numRead); |
106 | } | |
107 | ||
108 | /** | |
109 | * Extract an inline graphic from a drawing. | |
110 | * | |
111 | * @param drawing the drawing containing the graphic. | |
112 | */ | |
113 | private static Graphic getInlineGraphic(Drawing drawing) { | |
114 | var anchorOrInline = drawing.getAnchorOrInline(); | |
115 |
1
1. getInlineGraphic : negated conditional → KILLED |
if (anchorOrInline.isEmpty()) throw new OfficeStamperException("Anchor or Inline is empty !"); |
116 |
2
1. getInlineGraphic : negated conditional → KILLED 2. getInlineGraphic : replaced return value with null for pro/verron/officestamper/core/DocxImageExtractor::getInlineGraphic → KILLED |
if (anchorOrInline.getFirst() instanceof Inline inline) return inline.getGraphic(); |
117 | throw new OfficeStamperException("Don't know how to process anchor !"); | |
118 | } | |
119 | ||
120 | private static int tryRead(InputStream is, byte[] data) { | |
121 | try { | |
122 |
1
1. tryRead : replaced int return with 0 for pro/verron/officestamper/core/DocxImageExtractor::tryRead → KILLED |
return is.read(data); |
123 | } catch (IOException e) { | |
124 | throw new OfficeStamperException(e); | |
125 | } | |
126 | } | |
127 | ||
128 | /** | |
129 | * Extract the name of the image from an embedded image run. | |
130 | * | |
131 | * @param run run containing the embedded drawing. | |
132 | * | |
133 | * @return a {@link String} object | |
134 | */ | |
135 | public String getRunDrawingFilename(R run) { | |
136 |
1
1. getRunDrawingFilename : replaced return value with "" for pro/verron/officestamper/core/DocxImageExtractor::getRunDrawingFilename → NO_COVERAGE |
return getPic(run).getNvPicPr() |
137 | .getCNvPr() | |
138 | .getName(); | |
139 | } | |
140 | ||
141 | /** | |
142 | * Extract the content type of the image from an embedded image run. | |
143 | * | |
144 | * @param run run containing the embedded drawing. | |
145 | * | |
146 | * @return a {@link String} object | |
147 | */ | |
148 | public String getRunDrawingAltText(R run) { | |
149 |
1
1. getRunDrawingAltText : replaced return value with "" for pro/verron/officestamper/core/DocxImageExtractor::getRunDrawingAltText → NO_COVERAGE |
return getPic(run).getNvPicPr() |
150 | .getCNvPr() | |
151 | .getDescr(); | |
152 | } | |
153 | ||
154 | /** | |
155 | * Extract the width of the image from an embedded image run. | |
156 | * | |
157 | * @param run run containing the embedded drawing. | |
158 | * | |
159 | * @return a {@link Integer} object | |
160 | */ | |
161 | public Integer getRunDrawingMaxWidth(R run) { | |
162 |
1
1. getRunDrawingMaxWidth : replaced Integer return value with 0 for pro/verron/officestamper/core/DocxImageExtractor::getRunDrawingMaxWidth → SURVIVED |
return (int) getPic(run).getSpPr() |
163 | .getXfrm() | |
164 | .getExt() | |
165 | .getCx(); | |
166 | } | |
167 | } | |
Mutations | ||
49 |
1.1 |
|
53 |
1.1 |
|
54 |
1.1 |
|
55 |
1.1 |
|
57 |
1.1 |
|
65 |
1.1 |
|
75 |
1.1 |
|
84 |
1.1 |
|
100 |
1.1 2.2 |
|
105 |
1.1 |
|
115 |
1.1 |
|
116 |
1.1 2.2 |
|
122 |
1.1 |
|
136 |
1.1 |
|
149 |
1.1 |
|
162 |
1.1 |