Skip to content

Commit 69c96ee

Browse files
committed
Make SvgConverter autotaggable.
DEVSIX-8940
1 parent e9f259b commit 69c96ee

File tree

9 files changed

+418
-12
lines changed

9 files changed

+418
-12
lines changed
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
This file is part of the iText (R) project.
3+
Copyright (c) 1998-2025 Apryse Group NV
4+
Authors: Apryse Software.
5+
6+
This program is offered under a commercial and under the AGPL license.
7+
For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
8+
9+
AGPL licensing:
10+
This program is free software: you can redistribute it and/or modify
11+
it under the terms of the GNU Affero General Public License as published by
12+
the Free Software Foundation, either version 3 of the License, or
13+
(at your option) any later version.
14+
15+
This program is distributed in the hope that it will be useful,
16+
but WITHOUT ANY WARRANTY; without even the implied warranty of
17+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18+
GNU Affero General Public License for more details.
19+
20+
You should have received a copy of the GNU Affero General Public License
21+
along with this program. If not, see <https://www.gnu.org/licenses/>.
22+
*/
23+
package com.itextpdf.commons.utils;
24+
25+
26+
import java.io.IOException;
27+
28+
/**
29+
* Functional interface which takes 0 parameters and returns nothing and can throw a checked exception.
30+
*/
31+
@FunctionalInterface
32+
public interface IOThrowingAction {
33+
/**
34+
* Execute action.
35+
*
36+
* @throws IOException any exception thrown by the encapsulated code
37+
*/
38+
void execute() throws IOException;
39+
}

commons/src/sharpenconfig/java/com/itextpdf/commons/SharpenConfigMapping.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ public void applyMappingConfiguration(MappingConfigurator configurator) {
124124
configurator.mapType("com.itextpdf.commons.utils.ThrowingAction", "System.Action");
125125
configurator.mapFunctionalInterfaceToDelegate("com.itextpdf.commons.utils.Action");
126126
configurator.mapFunctionalInterfaceToDelegate("com.itextpdf.commons.utils.ThrowingAction");
127+
configurator.mapFunctionalInterfaceToDelegate("com.itextpdf.commons.utils.IOThrowingAction");
127128
configurator.mapFunctionalInterfaceToDelegate("com.itextpdf.commons.utils.ThrowingSupplier");
128129
configurator.keepInternalProtected("com.itextpdf.commons.ecosystem.TestConfigurationEvent.doAction");
129130
configurator.addCustomUsingDeclaration("com.itextpdf.kernel.colors.DeviceRgb", Arrays.asList("iText.Commons.Utils"));

svg/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@
3333
<version>${project.version}</version>
3434
<scope>test</scope>
3535
</dependency>
36+
<dependency>
37+
<groupId>com.itextpdf</groupId>
38+
<artifactId>pdfua</artifactId>
39+
<version>${project.version}</version>
40+
<scope>test</scope>
41+
</dependency>
3642
</dependencies>
3743

3844
<build>

svg/src/main/java/com/itextpdf/svg/converter/SvgConverter.java

Lines changed: 74 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,17 @@ This file is part of the iText (R) project.
2323
package com.itextpdf.svg.converter;
2424

2525
import com.itextpdf.commons.utils.FileUtil;
26+
import com.itextpdf.commons.utils.IOThrowingAction;
27+
import com.itextpdf.kernel.exceptions.PdfException;
2628
import com.itextpdf.kernel.geom.PageSize;
2729
import com.itextpdf.kernel.geom.Rectangle;
2830
import com.itextpdf.kernel.pdf.PdfDocument;
2931
import com.itextpdf.kernel.pdf.PdfPage;
3032
import com.itextpdf.kernel.pdf.PdfWriter;
3133
import com.itextpdf.kernel.pdf.WriterProperties;
3234
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
35+
import com.itextpdf.kernel.pdf.tagging.StandardRoles;
36+
import com.itextpdf.kernel.pdf.tagutils.TagTreePointer;
3337
import com.itextpdf.kernel.pdf.xobject.PdfFormXObject;
3438
import com.itextpdf.layout.element.Image;
3539
import com.itextpdf.styledxmlparser.IXmlParser;
@@ -48,13 +52,13 @@ This file is part of the iText (R) project.
4852
import com.itextpdf.svg.renderers.SvgDrawContext;
4953
import com.itextpdf.svg.renderers.impl.PdfRootSvgNodeRenderer;
5054
import com.itextpdf.svg.utils.SvgCssUtils;
55+
import org.slf4j.Logger;
56+
import org.slf4j.LoggerFactory;
5157

5258
import java.io.File;
5359
import java.io.IOException;
5460
import java.io.InputStream;
5561
import java.io.OutputStream;
56-
import org.slf4j.Logger;
57-
import org.slf4j.LoggerFactory;
5862

5963
/**
6064
* This is the main container class for static methods that do high-level
@@ -64,11 +68,12 @@ This file is part of the iText (R) project.
6468
*/
6569
public final class SvgConverter {
6670

67-
private SvgConverter() {
68-
}
69-
71+
public static final String SVG_DEFAULT_ROLE = StandardRoles.FIGURE;
7072
private static final Logger LOGGER = LoggerFactory.getLogger(SvgConverter.class);
7173

74+
private SvgConverter() {
75+
//empty constructor
76+
}
7277

7378
private static void checkNull(Object o) {
7479
if (o == null) {
@@ -212,7 +217,13 @@ public static void drawOnPage(String content, PdfPage page) {
212217
*/
213218
public static void drawOnPage(String content, PdfPage page, float x, float y) {
214219
checkNull(page);
215-
drawOnCanvas(content, new PdfCanvas(page), x, y);
220+
PdfCanvas canvas = new PdfCanvas(page);
221+
try {
222+
withTaggingIfNeeded(page.getDocument(), canvas, page, null, () -> drawOnCanvas(content, canvas, x, y));
223+
} catch (IOException e) {
224+
// This can't happen in normal circumstances.
225+
throw new PdfException(e);
226+
}
216227
}
217228

218229

@@ -238,7 +249,14 @@ public static void drawOnPage(String content, PdfPage page, ISvgConverterPropert
238249
*/
239250
public static void drawOnPage(String content, PdfPage page, float x, float y, ISvgConverterProperties props) {
240251
checkNull(page);
241-
drawOnCanvas(content, new PdfCanvas(page), x, y, props);
252+
final PdfCanvas canvas = new PdfCanvas(page);
253+
try {
254+
withTaggingIfNeeded(page.getDocument(), canvas, page, props,
255+
() -> drawOnCanvas(content, canvas, x, y, props));
256+
} catch (IOException e) {
257+
// This can't happen in normal circumstances.
258+
throw new PdfException(e);
259+
}
242260
}
243261

244262
/**
@@ -263,7 +281,8 @@ public static void drawOnPage(InputStream stream, PdfPage page) throws IOExcepti
263281
*/
264282
public static void drawOnPage(InputStream stream, PdfPage page, float x, float y) throws IOException {
265283
checkNull(page);
266-
drawOnCanvas(stream, new PdfCanvas(page), x, y);
284+
PdfCanvas canvas = new PdfCanvas(page);
285+
withTaggingIfNeeded(page.getDocument(), canvas, page, null, () -> drawOnCanvas(stream, canvas, x, y));
267286
}
268287

269288
/**
@@ -288,12 +307,14 @@ public static void drawOnPage(InputStream stream, PdfPage page, ISvgConverterPro
288307
* @param props a container for extra properties that customize the behavior
289308
* @throws IOException when the Stream cannot be read correctly
290309
*/
291-
public static void drawOnPage(InputStream stream, PdfPage page, float x, float y, ISvgConverterProperties props) throws IOException {
310+
public static void drawOnPage(InputStream stream, PdfPage page, float x, float y, ISvgConverterProperties props)
311+
throws IOException {
292312
checkNull(page);
293313
if (props instanceof SvgConverterProperties && ((SvgConverterProperties) props).getCustomViewport() == null) {
294314
((SvgConverterProperties) props).setCustomViewport(page.getMediaBox());
295315
}
296-
drawOnCanvas(stream, new PdfCanvas(page), x, y, props);
316+
PdfCanvas canvas = new PdfCanvas(page);
317+
withTaggingIfNeeded(page.getDocument(), canvas, page, props, () -> drawOnCanvas(stream, canvas, x, y, props));
297318
}
298319

299320
/**
@@ -723,8 +744,18 @@ public static Image convertToImage(InputStream stream, PdfDocument document) thr
723744
* @return a {@link Image Image} containing the PDF instructions corresponding to the passed SVG content
724745
* @throws IOException when the Stream cannot be read correctly
725746
*/
726-
public static Image convertToImage(InputStream stream, PdfDocument document, ISvgConverterProperties props) throws IOException {
727-
return new Image(convertToXObject(stream, document, props));
747+
public static Image convertToImage(InputStream stream, PdfDocument document, ISvgConverterProperties props)
748+
throws IOException {
749+
Image image = new Image(convertToXObject(stream, document, props));
750+
if (props instanceof SvgConverterProperties) {
751+
SvgConverterProperties properties = (SvgConverterProperties) props;
752+
if (properties.getAccessibilityProperties().getAlternateDescription() != null) {
753+
image.getAccessibilityProperties()
754+
.setAlternateDescription(properties.getAccessibilityProperties().getAlternateDescription());
755+
}
756+
image.getAccessibilityProperties().setRole(properties.getAccessibilityProperties().getRole());
757+
}
758+
return image;
728759
}
729760

730761
/*
@@ -937,4 +968,35 @@ private static ResourceResolver createResourceResolver(final ISvgConverterProper
937968
}
938969
return new ResourceResolver(props.getBaseUri(), props.getResourceRetriever());
939970
}
971+
972+
973+
private static void withTaggingIfNeeded(PdfDocument document, PdfCanvas canvas, PdfPage page,
974+
ISvgConverterProperties props, IOThrowingAction function)
975+
throws IOException {
976+
final boolean isTagged = document.isTagged();
977+
if (isTagged) {
978+
SvgConverterProperties properties = null;
979+
if (props instanceof SvgConverterProperties) {
980+
properties = (SvgConverterProperties) props;
981+
}
982+
String role = SVG_DEFAULT_ROLE;
983+
if (properties != null) {
984+
role = properties.getAccessibilityProperties().getRole();
985+
}
986+
987+
TagTreePointer tagTreePointer = new TagTreePointer(document);
988+
tagTreePointer.addTag(role);
989+
tagTreePointer.setPageForTagging(page);
990+
if (properties != null && properties.getAccessibilityProperties().getAlternateDescription() != null) {
991+
tagTreePointer.getProperties()
992+
.setAlternateDescription(properties.getAccessibilityProperties().getAlternateDescription());
993+
}
994+
canvas.openTag(tagTreePointer.getTagReference());
995+
}
996+
function.execute();
997+
if (isTagged) {
998+
canvas.closeTag();
999+
}
1000+
}
1001+
9401002
}

svg/src/main/java/com/itextpdf/svg/processors/impl/SvgConverterProperties.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ This file is part of the iText (R) project.
2323
package com.itextpdf.svg.processors.impl;
2424

2525
import com.itextpdf.kernel.geom.Rectangle;
26+
import com.itextpdf.kernel.pdf.tagutils.AccessibilityProperties;
27+
import com.itextpdf.kernel.pdf.tagutils.DefaultAccessibilityProperties;
2628
import com.itextpdf.layout.font.FontProvider;
2729
import com.itextpdf.styledxmlparser.css.CssStyleSheet;
2830
import com.itextpdf.styledxmlparser.css.media.MediaDeviceDescription;
@@ -34,11 +36,14 @@ This file is part of the iText (R) project.
3436

3537
import java.nio.charset.StandardCharsets;
3638

39+
import static com.itextpdf.svg.converter.SvgConverter.SVG_DEFAULT_ROLE;
40+
3741
/**
3842
* Default and fallback implementation of {@link ISvgConverterProperties} for
3943
* {@link DefaultSvgProcessor}.
4044
*/
4145
public class SvgConverterProperties implements ISvgConverterProperties {
46+
4247
/** The media device description. */
4348
private MediaDeviceDescription mediaDeviceDescription;
4449

@@ -59,6 +64,9 @@ public class SvgConverterProperties implements ISvgConverterProperties {
5964

6065
private Rectangle customViewport = null;
6166

67+
private final AccessibilityProperties accessibilityProperties = new DefaultAccessibilityProperties(
68+
SVG_DEFAULT_ROLE);
69+
6270
/**
6371
* Creates a new {@link SvgConverterProperties} instance.
6472
* Instantiates its members, IResourceRetriever and ISvgNodeRendererFactory, to its default implementations.
@@ -80,6 +88,16 @@ public Rectangle getCustomViewport() {
8088
return customViewport;
8189
}
8290

91+
92+
/**
93+
* Gets the accessibility properties.
94+
*
95+
* @return the accessibility properties.
96+
*/
97+
public AccessibilityProperties getAccessibilityProperties() {
98+
return this.accessibilityProperties;
99+
}
100+
83101
/**
84102
* Sets the custom viewport of SVG.
85103
* <p>

0 commit comments

Comments
 (0)