Image.java

1
package pro.verron.officestamper.preset;
2
3
import org.docx4j.wml.R;
4
import org.jspecify.annotations.Nullable;
5
import pro.verron.officestamper.api.DocxPart;
6
import pro.verron.officestamper.api.OfficeStamperException;
7
import pro.verron.officestamper.utils.wml.WmlFactory;
8
9
import java.io.ByteArrayInputStream;
10
import java.io.IOException;
11
import java.io.InputStream;
12
13
import static pro.verron.officestamper.utils.openpackaging.OpenpackagingFactory.newImgPart;
14
import static pro.verron.officestamper.utils.openpackaging.OpenpackagingUtils.findImgPart;
15
16
/// This class describes an image, which will be inserted into a document.
17
///
18
/// @author Joseph Verron
19
/// @author Romster
20
/// @version ${version}
21
/// @since 1.0.0
22
public final class Image {
23
    private final InputStream source;
24
    private final @Nullable Integer maxWidth;
25
    private final String filenameHint;
26
    private final String altText;
27
    private byte @Nullable [] bytes = null;
28
29
    /// Constructor for Image.
30
    ///
31
    /// @param source - content of the image as InputStream
32
    ///
33
    /// @throws IOException if any.
34
    public Image(InputStream source)
35
            throws IOException {
36
        this(source, null);
37
    }
38
39
    /// Constructor for Image.
40
    ///
41
    /// @param source content of the image as InputStream
42
    /// @param maxWidth max width of the image in twip
43
    public Image(InputStream source, @Nullable Integer maxWidth) {
44
        this(source, maxWidth, "dummyFileName", "dummyAltText");
45
    }
46
47
    /// Constructor for Image.
48
    ///
49
    /// @param source content of the image as InputStream
50
    /// @param maxWidth max width of the image in twip
51
    /// @param filenameHint filename hint for the image.
52
    /// @param altText alternative text for the image.
53
    public Image(InputStream source, @Nullable Integer maxWidth, String filenameHint, String altText) {
54
        this.source = source;
55
        this.maxWidth = maxWidth;
56
        this.filenameHint = filenameHint;
57
        this.altText = altText;
58
    }
59
60
    /// Constructor for Image.
61
    ///
62
    /// @param imageBytes - content of the image as an array of the bytes
63
    public Image(byte[] imageBytes) {
64
        this(new ByteArrayInputStream(imageBytes), null);
65
    }
66
67
    /// Constructor for Image.
68
    ///
69
    /// @param imageBytes - content of the image as an array of the bytes
70
    /// @param maxWidth   - max width of the image in twip
71
    public Image(byte[] imageBytes, @Nullable Integer maxWidth) {
72
        this(imageBytes, maxWidth, "dummyFileName", "dummyAltText");
73
    }
74
75
    /// Constructor for Image.
76
    ///
77
    /// @param imageBytes content of the image as an array of the bytes
78
    /// @param maxWidth max width of the image in twip
79
    /// @param filenameHint filename hint for the image.
80
    /// @param altText alternative text for the image.
81
    public Image(byte[] imageBytes, @Nullable Integer maxWidth, String filenameHint, String altText) {
82
        var inputStream = new ByteArrayInputStream(imageBytes);
83
        this(inputStream, maxWidth, filenameHint, altText);
84
    }
85
86
    /// Creates a new run with the provided image and associated metadata.
87
    ///
88
    /// TODO: adding the same image twice will put the image twice into the docx-zip file.
89
    ///  We should make the second addition of the same image a reference instead.
90
    ///
91
    /// @param part The document part where the image will be inserted.
92
    ///
93
    /// @return The created run containing the image.
94
    ///
95
    /// @throws OfficeStamperException If there is an error creating the image part
96
    public R newRun(DocxPart part) {
97
        var mlPackage = part.document();
98
        try {
99
            var parts = part.part();
100
            var document = part.document();
101
            var documentModel = document.getDocumentModel();
102
            var sections = documentModel.getSections();
103
            var lastSection = sections.getLast();
104
            var pageDimension = lastSection.getPageDimensions();
105 1 1. lambda$newRun$0 : replaced return value with null for pro/verron/officestamper/preset/Image::lambda$newRun$0 → KILLED
            var imgPart = findImgPart(mlPackage, parts, bytes()).orElseGet(() -> newImgPart(mlPackage, parts, bytes()));
106
            var relationship = imgPart.relationship();
107
            var imgFormat = imgPart.format();
108
            var dimension = imgFormat.dimension();
109
            var format = imgFormat.name();
110 1 1. newRun : negated conditional → KILLED
            var scale = WmlFactory.computeScale(pageDimension, maxWidth == null ? -1 : maxWidth, dimension);
111 1 1. newRun : negated conditional → SURVIVED
            var inline = format.equals("svg")
112
                    ? WmlFactory.newSVGInline(relationship, filenameHint, altText, scale)
113
                    : WmlFactory.newImgInline(relationship, filenameHint, altText, scale);
114
            var drawing = WmlFactory.newDrawing(inline);
115 1 1. newRun : replaced return value with null for pro/verron/officestamper/preset/Image::newRun → KILLED
            return WmlFactory.newRun(drawing);
116
        } catch (Exception e) {
117
            throw new OfficeStamperException("Failed to create an ImagePart", e);
118
        }
119
    }
120
121
    private synchronized byte[] bytes() {
122 1 1. bytes : negated conditional → KILLED
        if (bytes == null) try (InputStream source = this.source) {
123
            bytes = source.readAllBytes();
124
        } catch (IOException e) {
125
            throw new OfficeStamperException("Failed to cache the image bytes", e);
126
        }
127 1 1. bytes : replaced return value with null for pro/verron/officestamper/preset/Image::bytes → KILLED
        return bytes;
128
    }
129
130
}

Mutations

105

1.1
Location : lambda$newRun$0
Killed by : pro.verron.officestamper.test.ImageTests.[engine:junit-jupiter]/[class:pro.verron.officestamper.test.ImageTests]/[test-template:gifReplacementInGlobalParagraphsTestWithMaxWidth(pro.verron.officestamper.test.utils.ContextFactory)]/[test-template-invocation:#2]
replaced return value with null for pro/verron/officestamper/preset/Image::lambda$newRun$0 → KILLED

110

1.1
Location : newRun
Killed by : pro.verron.officestamper.test.ImageTests.[engine:junit-jupiter]/[class:pro.verron.officestamper.test.ImageTests]/[test-template:gifReplacementInGlobalParagraphsTestWithMaxWidth(pro.verron.officestamper.test.utils.ContextFactory)]/[test-template-invocation:#2]
negated conditional → KILLED

111

1.1
Location : newRun
Killed by : none
negated conditional → SURVIVED
Covering tests

115

1.1
Location : newRun
Killed by : pro.verron.officestamper.test.ImageTests.[engine:junit-jupiter]/[class:pro.verron.officestamper.test.ImageTests]/[test-template:gifReplacementInGlobalParagraphsTestWithMaxWidth(pro.verron.officestamper.test.utils.ContextFactory)]/[test-template-invocation:#2]
replaced return value with null for pro/verron/officestamper/preset/Image::newRun → KILLED

122

1.1
Location : bytes
Killed by : pro.verron.officestamper.test.ImageTests.[engine:junit-jupiter]/[class:pro.verron.officestamper.test.ImageTests]/[test-template:gifReplacementInGlobalParagraphsTestWithMaxWidth(pro.verron.officestamper.test.utils.ContextFactory)]/[test-template-invocation:#2]
negated conditional → KILLED

127

1.1
Location : bytes
Killed by : pro.verron.officestamper.test.ImageTests.[engine:junit-jupiter]/[class:pro.verron.officestamper.test.ImageTests]/[test-template:gifReplacementInGlobalParagraphsTestWithMaxWidth(pro.verron.officestamper.test.utils.ContextFactory)]/[test-template-invocation:#2]
replaced return value with null for pro/verron/officestamper/preset/Image::bytes → KILLED

Active mutators

Tests examined


Report generated by PIT 1.23.1 support