diff --git a/BUILDING.md b/BUILDING.md
index 5e10b57f5d..4f36cb0b7a 100644
--- a/BUILDING.md
+++ b/BUILDING.md
@@ -42,6 +42,33 @@ $ mvn clean install \
> >(tee mvn.log) 2> >(tee mvn-error.log >&2)
```
+iText is backwards compatible in minor releases. To ensure that code changes conform to this requirement we use japicmp.
+Todo verify this execute following commands:
+
+```bash
+$ mvn clean install
+$ mvn verify --activate-profiles qa \
+ -Dcheckstyle.skip=true \
+ -Ddependency-check.skip=true \
+ -Dpmd.skip=true \
+ -Dspotbugs.skip=true \
+ -Dmaven.main.skip=true \
+ -Dmaven.test.skip=true \
+ -Djapicmp.breakBuildOnModifications=true \
+ -Djapicmp.breakBuildOnBinaryIncompatibleModifications=true \
+ -Djapicmp.breakBuildOnSourceIncompatibleModifications=true
+```
+
+If you add new public methods or classes those should be documented.
+To verify this you can execute the following commands:
+
+```bash
+$ mvn clean install
+$ mvn javadoc:javadoc | grep -E "(: warning:)|(: error:)"
+```
+
+
+
[1]: https://maven.apache.org/
[2]: https://www.ghostscript.com/
diff --git a/barcodes/pom.xml b/barcodes/pom.xml
index b4c161cd01..b977ab8ce1 100644
--- a/barcodes/pom.xml
+++ b/barcodes/pom.xml
@@ -1,14 +1,18 @@
4.0.0
+
com.itextpdfroot
- 8.0.3
+ 8.0.4
+
barcodes
+
iText - barcodeshttps://itextpdf.com/
+
com.itextpdf
diff --git a/bouncy-castle-adapter/pom.xml b/bouncy-castle-adapter/pom.xml
index b1b9921f02..9ea5625678 100644
--- a/bouncy-castle-adapter/pom.xml
+++ b/bouncy-castle-adapter/pom.xml
@@ -1,12 +1,15 @@
4.0.0
+
com.itextpdfroot
- 8.0.3
+ 8.0.4
+
bouncy-castle-adapter
+
iText - Bouncy Castle Adapterhttps://itextpdf.com/
@@ -14,7 +17,7 @@
**/***/*
-
+
org.bouncycastle
@@ -32,6 +35,7 @@
${project.version}
+
diff --git a/bouncy-castle-adapter/src/main/java/com/itextpdf/bouncycastle/BouncyCastleFactory.java b/bouncy-castle-adapter/src/main/java/com/itextpdf/bouncycastle/BouncyCastleFactory.java
index 7c7a2ba216..1f7d93fb27 100644
--- a/bouncy-castle-adapter/src/main/java/com/itextpdf/bouncycastle/BouncyCastleFactory.java
+++ b/bouncy-castle-adapter/src/main/java/com/itextpdf/bouncycastle/BouncyCastleFactory.java
@@ -81,8 +81,10 @@ This file is part of the iText (R) project.
import com.itextpdf.bouncycastle.asn1.x509.ExtensionsBC;
import com.itextpdf.bouncycastle.asn1.x509.GeneralNameBC;
import com.itextpdf.bouncycastle.asn1.x509.GeneralNamesBC;
+import com.itextpdf.bouncycastle.asn1.x509.IssuingDistributionPointBC;
import com.itextpdf.bouncycastle.asn1.x509.KeyPurposeIdBC;
import com.itextpdf.bouncycastle.asn1.x509.KeyUsageBC;
+import com.itextpdf.bouncycastle.asn1.x509.ReasonFlagsBC;
import com.itextpdf.bouncycastle.asn1.x509.SubjectPublicKeyInfoBC;
import com.itextpdf.bouncycastle.asn1.x509.TBSCertificateBC;
import com.itextpdf.bouncycastle.asn1.x509.TimeBC;
@@ -186,8 +188,10 @@ This file is part of the iText (R) project.
import com.itextpdf.commons.bouncycastle.asn1.x509.IExtensions;
import com.itextpdf.commons.bouncycastle.asn1.x509.IGeneralName;
import com.itextpdf.commons.bouncycastle.asn1.x509.IGeneralNames;
+import com.itextpdf.commons.bouncycastle.asn1.x509.IIssuingDistributionPoint;
import com.itextpdf.commons.bouncycastle.asn1.x509.IKeyPurposeId;
import com.itextpdf.commons.bouncycastle.asn1.x509.IKeyUsage;
+import com.itextpdf.commons.bouncycastle.asn1.x509.IReasonFlags;
import com.itextpdf.commons.bouncycastle.asn1.x509.ISubjectPublicKeyInfo;
import com.itextpdf.commons.bouncycastle.asn1.x509.ITBSCertificate;
import com.itextpdf.commons.bouncycastle.asn1.x509.ITime;
@@ -282,10 +286,14 @@ This file is part of the iText (R) project.
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.CRLDistPoint;
+import org.bouncycastle.asn1.x509.DistributionPointName;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.Extensions;
import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.asn1.x509.IssuingDistributionPoint;
+import org.bouncycastle.asn1.x509.KeyPurposeId;
import org.bouncycastle.asn1.x509.KeyUsage;
+import org.bouncycastle.asn1.x509.ReasonFlags;
import org.bouncycastle.asn1.x509.TBSCertificate;
import org.bouncycastle.asn1.x509.Time;
import org.bouncycastle.cert.jcajce.JcaCertStore;
@@ -1196,6 +1204,38 @@ public ICRLDistPoint createCRLDistPoint(Object object) {
((ASN1EncodableBC) object).getEncodable() : object));
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public IIssuingDistributionPoint createIssuingDistributionPoint(Object point) {
+ return new IssuingDistributionPointBC(IssuingDistributionPoint.getInstance(point instanceof ASN1EncodableBC ?
+ ((ASN1EncodableBC) point).getEncodable() : point));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public IIssuingDistributionPoint createIssuingDistributionPoint(IDistributionPointName distributionPoint,
+ boolean onlyContainsUserCerts,
+ boolean onlyContainsCACerts,
+ IReasonFlags onlySomeReasons, boolean indirectCRL,
+ boolean onlyContainsAttributeCerts) {
+ return new IssuingDistributionPointBC(new IssuingDistributionPoint(distributionPoint == null ? null :
+ ((DistributionPointNameBC) distributionPoint).getDistributionPointName(), onlyContainsUserCerts,
+ onlyContainsCACerts, onlySomeReasons == null ? null :
+ ((ReasonFlagsBC) onlySomeReasons).getReasonFlags(), indirectCRL, onlyContainsAttributeCerts));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public IReasonFlags createReasonFlags(int reasons) {
+ return new ReasonFlagsBC(new ReasonFlags(reasons));
+ }
+
/**
* {@inheritDoc}
*/
@@ -1204,6 +1244,14 @@ public IDistributionPointName createDistributionPointName() {
return DistributionPointNameBC.getInstance();
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public IDistributionPointName createDistributionPointName(IGeneralNames generalNames) {
+ return new DistributionPointNameBC(new DistributionPointName(((GeneralNamesBC)generalNames).getGeneralNames()));
+ }
+
/**
* {@inheritDoc}
*/
@@ -1558,6 +1606,14 @@ public IBasicConstraints createBasicConstraints(boolean b) {
return new BasicConstraintsBC(new BasicConstraints(b));
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public IBasicConstraints createBasicConstraints(int pathLength) {
+ return new BasicConstraintsBC(new BasicConstraints(pathLength));
+ }
+
/**
* {@inheritDoc}
*/
@@ -1582,6 +1638,15 @@ public IKeyPurposeId createKeyPurposeId() {
return KeyPurposeIdBC.getInstance();
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public IKeyPurposeId createKeyPurposeId(IASN1ObjectIdentifier objectIdentifier) {
+ return new KeyPurposeIdBC(KeyPurposeId.getInstance(
+ ((ASN1ObjectIdentifierBC) objectIdentifier).getASN1ObjectIdentifier()));
+ }
+
/**
* {@inheritDoc}
*/
@@ -1590,6 +1655,14 @@ public IExtendedKeyUsage createExtendedKeyUsage(IKeyPurposeId purposeId) {
return new ExtendedKeyUsageBC(purposeId);
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public IExtendedKeyUsage createExtendedKeyUsage(IKeyPurposeId[] purposeIds) {
+ return new ExtendedKeyUsageBC(purposeIds);
+ }
+
/**
* {@inheritDoc}
*/
@@ -1697,6 +1770,14 @@ public ITime createTime(Date date) {
return new TimeBC(new Time(date));
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ITime createEndDate(X509Certificate certificate) {
+ return createTime(certificate.getNotAfter());
+ }
+
/**
* {@inheritDoc}
*/
diff --git a/bouncy-castle-adapter/src/main/java/com/itextpdf/bouncycastle/asn1/ASN1BitStringBC.java b/bouncy-castle-adapter/src/main/java/com/itextpdf/bouncycastle/asn1/ASN1BitStringBC.java
index 130a60e541..6b1f199daa 100644
--- a/bouncy-castle-adapter/src/main/java/com/itextpdf/bouncycastle/asn1/ASN1BitStringBC.java
+++ b/bouncy-castle-adapter/src/main/java/com/itextpdf/bouncycastle/asn1/ASN1BitStringBC.java
@@ -55,4 +55,12 @@ public ASN1BitString getASN1BitString() {
public String getString() {
return getASN1BitString().getString();
}
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int intValue() {
+ return getASN1BitString().intValue();
+ }
}
diff --git a/bouncy-castle-adapter/src/main/java/com/itextpdf/bouncycastle/asn1/x509/CRLReasonBC.java b/bouncy-castle-adapter/src/main/java/com/itextpdf/bouncycastle/asn1/x509/CRLReasonBC.java
index f3de220292..3dc7a89384 100644
--- a/bouncy-castle-adapter/src/main/java/com/itextpdf/bouncycastle/asn1/x509/CRLReasonBC.java
+++ b/bouncy-castle-adapter/src/main/java/com/itextpdf/bouncycastle/asn1/x509/CRLReasonBC.java
@@ -34,6 +34,7 @@ public class CRLReasonBC extends ASN1EncodableBC implements ICRLReason {
private static final CRLReasonBC INSTANCE = new CRLReasonBC(null);
private static final int KEY_COMPROMISE = CRLReason.keyCompromise;
+ private static final int REMOVE_FROM_CRL = CRLReason.removeFromCRL;
/**
* Creates new wrapper instance for {@link CRLReason}.
@@ -69,4 +70,12 @@ public CRLReason getCRLReason() {
public int getKeyCompromise() {
return KEY_COMPROMISE;
}
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int getRemoveFromCRL() {
+ return REMOVE_FROM_CRL;
+ }
}
diff --git a/bouncy-castle-adapter/src/main/java/com/itextpdf/bouncycastle/asn1/x509/DistributionPointBC.java b/bouncy-castle-adapter/src/main/java/com/itextpdf/bouncycastle/asn1/x509/DistributionPointBC.java
index e701f05b6c..55328f4971 100644
--- a/bouncy-castle-adapter/src/main/java/com/itextpdf/bouncycastle/asn1/x509/DistributionPointBC.java
+++ b/bouncy-castle-adapter/src/main/java/com/itextpdf/bouncycastle/asn1/x509/DistributionPointBC.java
@@ -26,6 +26,8 @@ This file is part of the iText (R) project.
import com.itextpdf.commons.bouncycastle.asn1.x509.IDistributionPoint;
import com.itextpdf.commons.bouncycastle.asn1.x509.IDistributionPointName;
+import com.itextpdf.commons.bouncycastle.asn1.x509.IGeneralNames;
+import com.itextpdf.commons.bouncycastle.asn1.x509.IReasonFlags;
import org.bouncycastle.asn1.x509.DistributionPoint;
/**
@@ -57,4 +59,20 @@ public DistributionPoint getPoint() {
public IDistributionPointName getDistributionPoint() {
return new DistributionPointNameBC(getPoint().getDistributionPoint());
}
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public IGeneralNames getCRLIssuer() {
+ return new GeneralNamesBC(getPoint().getCRLIssuer());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public IReasonFlags getReasons() {
+ return new ReasonFlagsBC(getPoint().getReasons());
+ }
}
diff --git a/bouncy-castle-adapter/src/main/java/com/itextpdf/bouncycastle/asn1/x509/ExtendedKeyUsageBC.java b/bouncy-castle-adapter/src/main/java/com/itextpdf/bouncycastle/asn1/x509/ExtendedKeyUsageBC.java
index 50589c37d8..5b9c4d9946 100644
--- a/bouncy-castle-adapter/src/main/java/com/itextpdf/bouncycastle/asn1/x509/ExtendedKeyUsageBC.java
+++ b/bouncy-castle-adapter/src/main/java/com/itextpdf/bouncycastle/asn1/x509/ExtendedKeyUsageBC.java
@@ -27,6 +27,7 @@ This file is part of the iText (R) project.
import com.itextpdf.commons.bouncycastle.asn1.x509.IKeyPurposeId;
import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
+import org.bouncycastle.asn1.x509.KeyPurposeId;
/**
* Wrapper class for {@link ExtendedKeyUsage}.
@@ -50,6 +51,15 @@ public ExtendedKeyUsageBC(IKeyPurposeId purposeId) {
super(new ExtendedKeyUsage(((KeyPurposeIdBC) purposeId).getKeyPurposeId()));
}
+ /**
+ * Creates new wrapper instance for {@link ExtendedKeyUsage}.
+ *
+ * @param purposeIds KeyPurposeId wrappers array
+ */
+ public ExtendedKeyUsageBC(IKeyPurposeId[] purposeIds) {
+ super(new ExtendedKeyUsage(unwrapPurposeIds(purposeIds)));
+ }
+
/**
* Gets actual org.bouncycastle object being wrapped.
*
@@ -58,4 +68,12 @@ public ExtendedKeyUsageBC(IKeyPurposeId purposeId) {
public ExtendedKeyUsage getExtendedKeyUsage() {
return (ExtendedKeyUsage) getEncodable();
}
+
+ private static KeyPurposeId[] unwrapPurposeIds(IKeyPurposeId[] purposeIds) {
+ KeyPurposeId[] purposeIdsUnwrapped = new KeyPurposeId[purposeIds.length];
+ for (int i = 0; i < purposeIds.length; ++i) {
+ purposeIdsUnwrapped[i] = ((KeyPurposeIdBC) purposeIds[i]).getKeyPurposeId();
+ }
+ return purposeIdsUnwrapped;
+ }
}
diff --git a/bouncy-castle-adapter/src/main/java/com/itextpdf/bouncycastle/asn1/x509/ExtensionBC.java b/bouncy-castle-adapter/src/main/java/com/itextpdf/bouncycastle/asn1/x509/ExtensionBC.java
index d73b24cf5e..22e5dc8a53 100644
--- a/bouncy-castle-adapter/src/main/java/com/itextpdf/bouncycastle/asn1/x509/ExtensionBC.java
+++ b/bouncy-castle-adapter/src/main/java/com/itextpdf/bouncycastle/asn1/x509/ExtensionBC.java
@@ -35,9 +35,12 @@ This file is part of the iText (R) project.
public class ExtensionBC extends ASN1EncodableBC implements IExtension {
private static final ExtensionBC INSTANCE = new ExtensionBC(null);
- private static final ASN1ObjectIdentifierBC C_RL_DISTRIBUTION_POINTS =
+ private static final ASN1ObjectIdentifierBC CRL_DISTRIBUTION_POINTS =
new ASN1ObjectIdentifierBC(Extension.cRLDistributionPoints);
+ private static final ASN1ObjectIdentifierBC ISSUING_DISTRIBUTION_POINT =
+ new ASN1ObjectIdentifierBC(Extension.issuingDistributionPoint);
+
private static final ASN1ObjectIdentifierBC AUTHORITY_INFO_ACCESS =
new ASN1ObjectIdentifierBC(Extension.authorityInfoAccess);
@@ -88,7 +91,15 @@ public Extension getExtension() {
*/
@Override
public IASN1ObjectIdentifier getCRlDistributionPoints() {
- return C_RL_DISTRIBUTION_POINTS;
+ return CRL_DISTRIBUTION_POINTS;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public IASN1ObjectIdentifier getIssuingDistributionPoint() {
+ return ISSUING_DISTRIBUTION_POINT;
}
/**
diff --git a/bouncy-castle-adapter/src/main/java/com/itextpdf/bouncycastle/asn1/x509/IssuingDistributionPointBC.java b/bouncy-castle-adapter/src/main/java/com/itextpdf/bouncycastle/asn1/x509/IssuingDistributionPointBC.java
new file mode 100644
index 0000000000..23b84fafa8
--- /dev/null
+++ b/bouncy-castle-adapter/src/main/java/com/itextpdf/bouncycastle/asn1/x509/IssuingDistributionPointBC.java
@@ -0,0 +1,112 @@
+/*
+ This file is part of the iText (R) project.
+ Copyright (c) 1998-2024 Apryse Group NV
+ Authors: Apryse Software.
+
+ This program is offered under a commercial and under the AGPL license.
+ For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
+
+ AGPL licensing:
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+ */
+package com.itextpdf.bouncycastle.asn1.x509;
+
+import com.itextpdf.bouncycastle.asn1.ASN1EncodableBC;
+import com.itextpdf.commons.bouncycastle.asn1.x509.IDistributionPointName;
+import com.itextpdf.commons.bouncycastle.asn1.x509.IIssuingDistributionPoint;
+import com.itextpdf.commons.bouncycastle.asn1.x509.IReasonFlags;
+import org.bouncycastle.asn1.x509.IssuingDistributionPoint;
+
+/**
+ * Wrapper class for {@link IssuingDistributionPoint}.
+ */
+public class IssuingDistributionPointBC extends ASN1EncodableBC implements IIssuingDistributionPoint {
+ /**
+ * Creates new wrapper instance for {@link IssuingDistributionPoint}.
+ *
+ * @param issuingDistPoint {@link IssuingDistributionPoint} to be wrapped
+ */
+ public IssuingDistributionPointBC(IssuingDistributionPoint issuingDistPoint) {
+ super(issuingDistPoint);
+ }
+
+ /**
+ * Gets actual org.bouncycastle object being wrapped.
+ *
+ * @return wrapped {@link IssuingDistributionPoint}.
+ */
+ public IssuingDistributionPoint getIssuingDistributionPoint() {
+ return (IssuingDistributionPoint) getEncodable();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return {@inheritDoc}
+ */
+ @Override
+ public IDistributionPointName getDistributionPoint() {
+ return new DistributionPointNameBC(getIssuingDistributionPoint().getDistributionPoint());
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return {@inheritDoc}
+ */
+ @Override
+ public boolean onlyContainsUserCerts() {
+ return getIssuingDistributionPoint().onlyContainsUserCerts();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return {@inheritDoc}
+ */
+ @Override
+ public boolean onlyContainsCACerts() {
+ return getIssuingDistributionPoint().onlyContainsCACerts();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return {@inheritDoc}
+ */
+ @Override
+ public boolean isIndirectCRL() {
+ return getIssuingDistributionPoint().isIndirectCRL();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return {@inheritDoc}
+ */
+ @Override
+ public boolean onlyContainsAttributeCerts() {
+ return getIssuingDistributionPoint().onlyContainsAttributeCerts();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return {@inheritDoc}
+ */
+ @Override
+ public IReasonFlags getOnlySomeReasons() {
+ return new ReasonFlagsBC(getIssuingDistributionPoint().getOnlySomeReasons());
+ }
+}
diff --git a/bouncy-castle-adapter/src/main/java/com/itextpdf/bouncycastle/asn1/x509/ReasonFlagsBC.java b/bouncy-castle-adapter/src/main/java/com/itextpdf/bouncycastle/asn1/x509/ReasonFlagsBC.java
new file mode 100644
index 0000000000..4e3bc364e7
--- /dev/null
+++ b/bouncy-castle-adapter/src/main/java/com/itextpdf/bouncycastle/asn1/x509/ReasonFlagsBC.java
@@ -0,0 +1,50 @@
+/*
+ This file is part of the iText (R) project.
+ Copyright (c) 1998-2024 Apryse Group NV
+ Authors: Apryse Software.
+
+ This program is offered under a commercial and under the AGPL license.
+ For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
+
+ AGPL licensing:
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+ */
+package com.itextpdf.bouncycastle.asn1.x509;
+
+import com.itextpdf.bouncycastle.asn1.ASN1BitStringBC;
+import com.itextpdf.commons.bouncycastle.asn1.x509.IReasonFlags;
+import org.bouncycastle.asn1.x509.ReasonFlags;
+
+/**
+ * Wrapper class for {@link ReasonFlags}.
+ */
+public class ReasonFlagsBC extends ASN1BitStringBC implements IReasonFlags {
+ /**
+ * Creates new wrapper instance for {@link ReasonFlags}.
+ *
+ * @param reasonFlags {@link ReasonFlags} to be wrapped
+ */
+ public ReasonFlagsBC(ReasonFlags reasonFlags) {
+ super(reasonFlags);
+ }
+
+ /**
+ * Gets actual org.bouncycastle object being wrapped.
+ *
+ * @return wrapped {@link ReasonFlags}.
+ */
+ public ReasonFlags getReasonFlags() {
+ return (ReasonFlags) getEncodable();
+ }
+}
diff --git a/bouncy-castle-adapter/src/main/java/com/itextpdf/bouncycastle/cert/X509v2CRLBuilderBC.java b/bouncy-castle-adapter/src/main/java/com/itextpdf/bouncycastle/cert/X509v2CRLBuilderBC.java
index d2a7135bbd..9876766314 100644
--- a/bouncy-castle-adapter/src/main/java/com/itextpdf/bouncycastle/cert/X509v2CRLBuilderBC.java
+++ b/bouncy-castle-adapter/src/main/java/com/itextpdf/bouncycastle/cert/X509v2CRLBuilderBC.java
@@ -22,16 +22,22 @@ This file is part of the iText (R) project.
*/
package com.itextpdf.bouncycastle.cert;
+import com.itextpdf.bouncycastle.asn1.ASN1EncodableBC;
+import com.itextpdf.bouncycastle.asn1.ASN1ObjectIdentifierBC;
import com.itextpdf.bouncycastle.asn1.x500.X500NameBC;
import com.itextpdf.bouncycastle.operator.ContentSignerBC;
+import com.itextpdf.commons.bouncycastle.asn1.IASN1Encodable;
+import com.itextpdf.commons.bouncycastle.asn1.IASN1ObjectIdentifier;
import com.itextpdf.commons.bouncycastle.asn1.x500.IX500Name;
import com.itextpdf.commons.bouncycastle.cert.IX509CRLHolder;
import com.itextpdf.commons.bouncycastle.cert.IX509v2CRLBuilder;
import com.itextpdf.commons.bouncycastle.operator.IContentSigner;
+import java.io.IOException;
import java.math.BigInteger;
import java.util.Date;
import java.util.Objects;
+
import org.bouncycastle.cert.X509v2CRLBuilder;
/**
@@ -77,6 +83,17 @@ public IX509v2CRLBuilder addCRLEntry(BigInteger bigInteger, Date date, int i) {
return this;
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public IX509v2CRLBuilder addExtension(IASN1ObjectIdentifier objectIdentifier, boolean isCritical,
+ IASN1Encodable extension) throws IOException {
+ builder.addExtension(((ASN1ObjectIdentifierBC) objectIdentifier).getASN1ObjectIdentifier(), isCritical,
+ ((ASN1EncodableBC) extension).getEncodable());
+ return this;
+ }
+
/**
* {@inheritDoc}
*/
diff --git a/bouncy-castle-adapter/src/main/java/com/itextpdf/bouncycastle/cert/ocsp/BasicOCSPRespBC.java b/bouncy-castle-adapter/src/main/java/com/itextpdf/bouncycastle/cert/ocsp/BasicOCSPRespBC.java
index a098dbd72e..4a8ebe0c45 100644
--- a/bouncy-castle-adapter/src/main/java/com/itextpdf/bouncycastle/cert/ocsp/BasicOCSPRespBC.java
+++ b/bouncy-castle-adapter/src/main/java/com/itextpdf/bouncycastle/cert/ocsp/BasicOCSPRespBC.java
@@ -93,6 +93,9 @@ public boolean isSignatureValid(IContentVerifierProvider provider) throws OCSPEx
@Override
public IX509CertificateHolder[] getCerts() {
X509CertificateHolder[] certs = basicOCSPResp.getCerts();
+ if (certs == null) {
+ return new IX509CertificateHolder[0];
+ }
IX509CertificateHolder[] certsBC = new IX509CertificateHolder[certs.length];
for (int i = 0; i < certs.length; i++) {
certsBC[i] = new X509CertificateHolderBC(certs[i]);
diff --git a/bouncy-castle-adapter/src/main/java/com/itextpdf/bouncycastle/cert/ocsp/RevokedStatusBC.java b/bouncy-castle-adapter/src/main/java/com/itextpdf/bouncycastle/cert/ocsp/RevokedStatusBC.java
index 17abe8b23e..b8432d8267 100644
--- a/bouncy-castle-adapter/src/main/java/com/itextpdf/bouncycastle/cert/ocsp/RevokedStatusBC.java
+++ b/bouncy-castle-adapter/src/main/java/com/itextpdf/bouncycastle/cert/ocsp/RevokedStatusBC.java
@@ -26,6 +26,8 @@ This file is part of the iText (R) project.
import org.bouncycastle.cert.ocsp.RevokedStatus;
+import java.util.Date;
+
/**
* Wrapper class for {@link RevokedStatus}.
*/
@@ -47,4 +49,14 @@ public RevokedStatusBC(RevokedStatus certificateStatus) {
public RevokedStatus getRevokedStatus() {
return (RevokedStatus) super.getCertificateStatus();
}
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return {@inheritDoc}
+ */
+ @Override
+ public Date getRevocationTime() {
+ return getRevokedStatus().getRevocationTime();
+ }
}
diff --git a/bouncy-castle-connector/pom.xml b/bouncy-castle-connector/pom.xml
index 462c465670..3685fbdb8f 100644
--- a/bouncy-castle-connector/pom.xml
+++ b/bouncy-castle-connector/pom.xml
@@ -1,26 +1,28 @@
4.0.0
+
com.itextpdfroot
- 8.0.3
+ 8.0.4
+
bouncy-castle-connector
+
iText - Bouncy Castle Connectorhttps://itextpdf.com/
-
+
**/***/*
-
+
com.itextpdfbouncy-castle-adapter${project.version}
- trueorg.bouncycastle
@@ -31,12 +33,12 @@
bcprov-jdk18on
+ truecom.itextpdfbouncy-castle-fips-adapter${project.version}
- trueorg.bouncycastle
@@ -47,8 +49,10 @@
bc-fips
+ true
+
diff --git a/bouncy-castle-connector/src/main/java/com/itextpdf/bouncycastleconnector/BouncyCastleDefaultFactory.java b/bouncy-castle-connector/src/main/java/com/itextpdf/bouncycastleconnector/BouncyCastleDefaultFactory.java
index f15eecf184..58f32f6475 100644
--- a/bouncy-castle-connector/src/main/java/com/itextpdf/bouncycastleconnector/BouncyCastleDefaultFactory.java
+++ b/bouncy-castle-connector/src/main/java/com/itextpdf/bouncycastleconnector/BouncyCastleDefaultFactory.java
@@ -84,8 +84,10 @@ This file is part of the iText (R) project.
import com.itextpdf.commons.bouncycastle.asn1.x509.IExtensions;
import com.itextpdf.commons.bouncycastle.asn1.x509.IGeneralName;
import com.itextpdf.commons.bouncycastle.asn1.x509.IGeneralNames;
+import com.itextpdf.commons.bouncycastle.asn1.x509.IIssuingDistributionPoint;
import com.itextpdf.commons.bouncycastle.asn1.x509.IKeyPurposeId;
import com.itextpdf.commons.bouncycastle.asn1.x509.IKeyUsage;
+import com.itextpdf.commons.bouncycastle.asn1.x509.IReasonFlags;
import com.itextpdf.commons.bouncycastle.asn1.x509.ISubjectPublicKeyInfo;
import com.itextpdf.commons.bouncycastle.asn1.x509.ITBSCertificate;
import com.itextpdf.commons.bouncycastle.asn1.x509.ITime;
@@ -168,7 +170,7 @@ public String getDigestAlgorithmOid(String name) {
public String getAlgorithmName(String oid) {
throw new UnsupportedOperationException(BouncyCastleLogMessageConstant.BOUNCY_CASTLE_DEPENDENCY_MUST_PRESENT);
}
-
+
@Override
public IASN1ObjectIdentifier createASN1ObjectIdentifier(IASN1Encodable encodable) {
throw new UnsupportedOperationException(BouncyCastleLogMessageConstant.BOUNCY_CASTLE_DEPENDENCY_MUST_PRESENT);
@@ -454,7 +456,7 @@ public String getProviderName() {
public IJceKeyTransEnvelopedRecipient createJceKeyTransEnvelopedRecipient(PrivateKey privateKey) {
throw new UnsupportedOperationException(BouncyCastleLogMessageConstant.BOUNCY_CASTLE_DEPENDENCY_MUST_PRESENT);
}
-
+
@Override
public IJceKeyAgreeEnvelopedRecipient createJceKeyAgreeEnvelopedRecipient(PrivateKey privateKey) {
throw new UnsupportedOperationException(BouncyCastleLogMessageConstant.BOUNCY_CASTLE_DEPENDENCY_MUST_PRESENT);
@@ -624,11 +626,33 @@ public ICRLDistPoint createCRLDistPoint(Object object) {
throw new UnsupportedOperationException(BouncyCastleLogMessageConstant.BOUNCY_CASTLE_DEPENDENCY_MUST_PRESENT);
}
+ @Override
+ public IIssuingDistributionPoint createIssuingDistributionPoint(Object point) {
+ throw new UnsupportedOperationException(BouncyCastleLogMessageConstant.BOUNCY_CASTLE_DEPENDENCY_MUST_PRESENT);
+ }
+
+ @Override
+ public IIssuingDistributionPoint createIssuingDistributionPoint(IDistributionPointName distributionPoint,
+ boolean onlyContainsUserCerts, boolean onlyContainsCACerts,IReasonFlags onlySomeReasons,
+ boolean indirectCRL, boolean onlyContainsAttrCerts) {
+ throw new UnsupportedOperationException(BouncyCastleLogMessageConstant.BOUNCY_CASTLE_DEPENDENCY_MUST_PRESENT);
+ }
+
+ @Override
+ public IReasonFlags createReasonFlags(int reasons) {
+ throw new UnsupportedOperationException(BouncyCastleLogMessageConstant.BOUNCY_CASTLE_DEPENDENCY_MUST_PRESENT);
+ }
+
@Override
public IDistributionPointName createDistributionPointName() {
throw new UnsupportedOperationException(BouncyCastleLogMessageConstant.BOUNCY_CASTLE_DEPENDENCY_MUST_PRESENT);
}
+ @Override
+ public IDistributionPointName createDistributionPointName(IGeneralNames generalNames) {
+ throw new UnsupportedOperationException(BouncyCastleLogMessageConstant.BOUNCY_CASTLE_DEPENDENCY_MUST_PRESENT);
+ }
+
@Override
public IGeneralNames createGeneralNames(IASN1Encodable encodable) {
throw new UnsupportedOperationException(BouncyCastleLogMessageConstant.BOUNCY_CASTLE_DEPENDENCY_MUST_PRESENT);
@@ -828,6 +852,11 @@ public IBasicConstraints createBasicConstraints(boolean b) {
throw new UnsupportedOperationException(BouncyCastleLogMessageConstant.BOUNCY_CASTLE_DEPENDENCY_MUST_PRESENT);
}
+ @Override
+ public IBasicConstraints createBasicConstraints(int pathLength) {
+ throw new UnsupportedOperationException(BouncyCastleLogMessageConstant.BOUNCY_CASTLE_DEPENDENCY_MUST_PRESENT);
+ }
+
@Override
public IKeyUsage createKeyUsage() {
throw new UnsupportedOperationException(BouncyCastleLogMessageConstant.BOUNCY_CASTLE_DEPENDENCY_MUST_PRESENT);
@@ -843,11 +872,21 @@ public IKeyPurposeId createKeyPurposeId() {
throw new UnsupportedOperationException(BouncyCastleLogMessageConstant.BOUNCY_CASTLE_DEPENDENCY_MUST_PRESENT);
}
+ @Override
+ public IKeyPurposeId createKeyPurposeId(IASN1ObjectIdentifier objectIdentifier) {
+ throw new UnsupportedOperationException(BouncyCastleLogMessageConstant.BOUNCY_CASTLE_DEPENDENCY_MUST_PRESENT);
+ }
+
@Override
public IExtendedKeyUsage createExtendedKeyUsage(IKeyPurposeId purposeId) {
throw new UnsupportedOperationException(BouncyCastleLogMessageConstant.BOUNCY_CASTLE_DEPENDENCY_MUST_PRESENT);
}
+ @Override
+ public IExtendedKeyUsage createExtendedKeyUsage(IKeyPurposeId[] purposeIds) {
+ throw new UnsupportedOperationException(BouncyCastleLogMessageConstant.BOUNCY_CASTLE_DEPENDENCY_MUST_PRESENT);
+ }
+
@Override
public IX509ExtensionUtils createX509ExtensionUtils(IDigestCalculator digestCalculator) {
throw new UnsupportedOperationException(BouncyCastleLogMessageConstant.BOUNCY_CASTLE_DEPENDENCY_MUST_PRESENT);
@@ -908,6 +947,11 @@ public ITime createTime(Date date) {
throw new UnsupportedOperationException(BouncyCastleLogMessageConstant.BOUNCY_CASTLE_DEPENDENCY_MUST_PRESENT);
}
+ @Override
+ public ITime createEndDate(X509Certificate certificate) {
+ throw new UnsupportedOperationException(BouncyCastleLogMessageConstant.BOUNCY_CASTLE_DEPENDENCY_MUST_PRESENT);
+ }
+
@Override
public boolean isNullExtension(IExtension extNonce) {
throw new UnsupportedOperationException(BouncyCastleLogMessageConstant.BOUNCY_CASTLE_DEPENDENCY_MUST_PRESENT);
diff --git a/bouncy-castle-fips-adapter/pom.xml b/bouncy-castle-fips-adapter/pom.xml
index e8d4af6991..f457c2d3b7 100644
--- a/bouncy-castle-fips-adapter/pom.xml
+++ b/bouncy-castle-fips-adapter/pom.xml
@@ -1,12 +1,15 @@
4.0.0
+
com.itextpdfroot
- 8.0.3
+ 8.0.4
+
bouncy-castle-fips-adapter
+
iText - Bouncy Castle FIPS Adapterhttps://itextpdf.com/
@@ -34,6 +37,7 @@
${project.version}
+
diff --git a/bouncy-castle-fips-adapter/src/main/java/com/itextpdf/bouncycastlefips/BouncyCastleFipsFactory.java b/bouncy-castle-fips-adapter/src/main/java/com/itextpdf/bouncycastlefips/BouncyCastleFipsFactory.java
index 4b09970f9e..c8a4d51228 100644
--- a/bouncy-castle-fips-adapter/src/main/java/com/itextpdf/bouncycastlefips/BouncyCastleFipsFactory.java
+++ b/bouncy-castle-fips-adapter/src/main/java/com/itextpdf/bouncycastlefips/BouncyCastleFipsFactory.java
@@ -81,8 +81,10 @@ This file is part of the iText (R) project.
import com.itextpdf.bouncycastlefips.asn1.x509.ExtensionsBCFips;
import com.itextpdf.bouncycastlefips.asn1.x509.GeneralNameBCFips;
import com.itextpdf.bouncycastlefips.asn1.x509.GeneralNamesBCFips;
+import com.itextpdf.bouncycastlefips.asn1.x509.IssuingDistributionPointBCFips;
import com.itextpdf.bouncycastlefips.asn1.x509.KeyPurposeIdBCFips;
import com.itextpdf.bouncycastlefips.asn1.x509.KeyUsageBCFips;
+import com.itextpdf.bouncycastlefips.asn1.x509.ReasonFlagsBCFips;
import com.itextpdf.bouncycastlefips.asn1.x509.SubjectPublicKeyInfoBCFips;
import com.itextpdf.bouncycastlefips.asn1.x509.TBSCertificateBCFips;
import com.itextpdf.bouncycastlefips.asn1.x509.TimeBCFips;
@@ -187,8 +189,10 @@ This file is part of the iText (R) project.
import com.itextpdf.commons.bouncycastle.asn1.x509.IExtensions;
import com.itextpdf.commons.bouncycastle.asn1.x509.IGeneralName;
import com.itextpdf.commons.bouncycastle.asn1.x509.IGeneralNames;
+import com.itextpdf.commons.bouncycastle.asn1.x509.IIssuingDistributionPoint;
import com.itextpdf.commons.bouncycastle.asn1.x509.IKeyPurposeId;
import com.itextpdf.commons.bouncycastle.asn1.x509.IKeyUsage;
+import com.itextpdf.commons.bouncycastle.asn1.x509.IReasonFlags;
import com.itextpdf.commons.bouncycastle.asn1.x509.ISubjectPublicKeyInfo;
import com.itextpdf.commons.bouncycastle.asn1.x509.ITBSCertificate;
import com.itextpdf.commons.bouncycastle.asn1.x509.ITime;
@@ -286,10 +290,14 @@ This file is part of the iText (R) project.
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.CRLDistPoint;
+import org.bouncycastle.asn1.x509.DistributionPointName;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.Extensions;
import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.asn1.x509.IssuingDistributionPoint;
+import org.bouncycastle.asn1.x509.KeyPurposeId;
import org.bouncycastle.asn1.x509.KeyUsage;
+import org.bouncycastle.asn1.x509.ReasonFlags;
import org.bouncycastle.asn1.x509.TBSCertificate;
import org.bouncycastle.asn1.x509.Time;
import org.bouncycastle.cert.jcajce.JcaCertStore;
@@ -1206,6 +1214,38 @@ public ICRLDistPoint createCRLDistPoint(Object object) {
((ASN1EncodableBCFips) object).getEncodable() : object));
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public IIssuingDistributionPoint createIssuingDistributionPoint(Object point) {
+ return new IssuingDistributionPointBCFips(IssuingDistributionPoint.getInstance(
+ point instanceof ASN1EncodableBCFips ? ((ASN1EncodableBCFips) point).getEncodable() : point));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public IIssuingDistributionPoint createIssuingDistributionPoint(IDistributionPointName distributionPoint,
+ boolean onlyContainsUserCerts,
+ boolean onlyContainsCACerts,
+ IReasonFlags onlySomeReasons, boolean indirectCRL,
+ boolean onlyContainsAttributeCerts) {
+ return new IssuingDistributionPointBCFips(new IssuingDistributionPoint(distributionPoint == null ? null :
+ ((DistributionPointNameBCFips) distributionPoint).getDistributionPointName(), onlyContainsUserCerts,
+ onlyContainsCACerts, onlySomeReasons == null ? null :
+ ((ReasonFlagsBCFips) onlySomeReasons).getReasonFlags(), indirectCRL, onlyContainsAttributeCerts));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public IReasonFlags createReasonFlags(int reasons) {
+ return new ReasonFlagsBCFips(new ReasonFlags(reasons));
+ }
+
/**
* {@inheritDoc}
*/
@@ -1214,6 +1254,15 @@ public IDistributionPointName createDistributionPointName() {
return DistributionPointNameBCFips.getInstance();
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public IDistributionPointName createDistributionPointName(IGeneralNames generalNames) {
+ return new DistributionPointNameBCFips(
+ new DistributionPointName(((GeneralNamesBCFips)generalNames).getGeneralNames()));
+ }
+
/**
* {@inheritDoc}
*/
@@ -1567,6 +1616,14 @@ public IBasicConstraints createBasicConstraints(boolean b) {
return new BasicConstraintsBCFips(new BasicConstraints(b));
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public IBasicConstraints createBasicConstraints(int pathLength) {
+ return new BasicConstraintsBCFips(new BasicConstraints(pathLength));
+ }
+
/**
* {@inheritDoc}
*/
@@ -1591,6 +1648,15 @@ public IKeyPurposeId createKeyPurposeId() {
return KeyPurposeIdBCFips.getInstance();
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public IKeyPurposeId createKeyPurposeId(IASN1ObjectIdentifier objectIdentifier) {
+ return new KeyPurposeIdBCFips(KeyPurposeId.getInstance(
+ ((ASN1ObjectIdentifierBCFips) objectIdentifier).getASN1ObjectIdentifier()));
+ }
+
/**
* {@inheritDoc}
*/
@@ -1599,6 +1665,14 @@ public IExtendedKeyUsage createExtendedKeyUsage(IKeyPurposeId purposeId) {
return new ExtendedKeyUsageBCFips(purposeId);
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public IExtendedKeyUsage createExtendedKeyUsage(IKeyPurposeId[] purposeIds) {
+ return new ExtendedKeyUsageBCFips(purposeIds);
+ }
+
/**
* {@inheritDoc}
*/
@@ -1706,6 +1780,14 @@ public ITime createTime(Date date) {
return new TimeBCFips(new Time(date));
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ITime createEndDate(X509Certificate certificate) {
+ return createTime(certificate.getNotAfter());
+ }
+
/**
* {@inheritDoc}
*/
diff --git a/bouncy-castle-fips-adapter/src/main/java/com/itextpdf/bouncycastlefips/asn1/ASN1BitStringBCFips.java b/bouncy-castle-fips-adapter/src/main/java/com/itextpdf/bouncycastlefips/asn1/ASN1BitStringBCFips.java
index 04d980af86..cd561bf498 100644
--- a/bouncy-castle-fips-adapter/src/main/java/com/itextpdf/bouncycastlefips/asn1/ASN1BitStringBCFips.java
+++ b/bouncy-castle-fips-adapter/src/main/java/com/itextpdf/bouncycastlefips/asn1/ASN1BitStringBCFips.java
@@ -55,4 +55,12 @@ public ASN1BitString getASN1BitString() {
public String getString() {
return getASN1BitString().getString();
}
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int intValue() {
+ return getASN1BitString().intValue();
+ }
}
diff --git a/bouncy-castle-fips-adapter/src/main/java/com/itextpdf/bouncycastlefips/asn1/x509/CRLReasonBCFips.java b/bouncy-castle-fips-adapter/src/main/java/com/itextpdf/bouncycastlefips/asn1/x509/CRLReasonBCFips.java
index bbdef7961a..f139430d1e 100644
--- a/bouncy-castle-fips-adapter/src/main/java/com/itextpdf/bouncycastlefips/asn1/x509/CRLReasonBCFips.java
+++ b/bouncy-castle-fips-adapter/src/main/java/com/itextpdf/bouncycastlefips/asn1/x509/CRLReasonBCFips.java
@@ -34,6 +34,7 @@ public class CRLReasonBCFips extends ASN1EncodableBCFips implements ICRLReason {
private static final CRLReasonBCFips INSTANCE = new CRLReasonBCFips(null);
private static final int KEY_COMPROMISE = CRLReason.keyCompromise;
+ private static final int REMOVE_FROM_CRL = CRLReason.removeFromCRL;
/**
* Creates new wrapper instance for {@link CRLReason}.
@@ -69,4 +70,12 @@ public CRLReason getCRLReason() {
public int getKeyCompromise() {
return KEY_COMPROMISE;
}
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int getRemoveFromCRL() {
+ return REMOVE_FROM_CRL;
+ }
}
diff --git a/bouncy-castle-fips-adapter/src/main/java/com/itextpdf/bouncycastlefips/asn1/x509/DistributionPointBCFips.java b/bouncy-castle-fips-adapter/src/main/java/com/itextpdf/bouncycastlefips/asn1/x509/DistributionPointBCFips.java
index df7c86c0d6..86a4f8180c 100644
--- a/bouncy-castle-fips-adapter/src/main/java/com/itextpdf/bouncycastlefips/asn1/x509/DistributionPointBCFips.java
+++ b/bouncy-castle-fips-adapter/src/main/java/com/itextpdf/bouncycastlefips/asn1/x509/DistributionPointBCFips.java
@@ -26,6 +26,8 @@ This file is part of the iText (R) project.
import com.itextpdf.commons.bouncycastle.asn1.x509.IDistributionPoint;
import com.itextpdf.commons.bouncycastle.asn1.x509.IDistributionPointName;
+import com.itextpdf.commons.bouncycastle.asn1.x509.IGeneralNames;
+import com.itextpdf.commons.bouncycastle.asn1.x509.IReasonFlags;
import org.bouncycastle.asn1.x509.DistributionPoint;
/**
@@ -57,4 +59,20 @@ public DistributionPoint getPoint() {
public IDistributionPointName getDistributionPoint() {
return new DistributionPointNameBCFips(getPoint().getDistributionPoint());
}
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public IGeneralNames getCRLIssuer() {
+ return new GeneralNamesBCFips(getPoint().getCRLIssuer());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public IReasonFlags getReasons() {
+ return new ReasonFlagsBCFips(getPoint().getReasons());
+ }
}
diff --git a/bouncy-castle-fips-adapter/src/main/java/com/itextpdf/bouncycastlefips/asn1/x509/ExtendedKeyUsageBCFips.java b/bouncy-castle-fips-adapter/src/main/java/com/itextpdf/bouncycastlefips/asn1/x509/ExtendedKeyUsageBCFips.java
index d775ef0d38..bf6017a474 100644
--- a/bouncy-castle-fips-adapter/src/main/java/com/itextpdf/bouncycastlefips/asn1/x509/ExtendedKeyUsageBCFips.java
+++ b/bouncy-castle-fips-adapter/src/main/java/com/itextpdf/bouncycastlefips/asn1/x509/ExtendedKeyUsageBCFips.java
@@ -27,6 +27,7 @@ This file is part of the iText (R) project.
import com.itextpdf.commons.bouncycastle.asn1.x509.IKeyPurposeId;
import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
+import org.bouncycastle.asn1.x509.KeyPurposeId;
/**
* Wrapper class for {@link ExtendedKeyUsage}.
@@ -50,6 +51,15 @@ public ExtendedKeyUsageBCFips(IKeyPurposeId purposeId) {
super(new ExtendedKeyUsage(((KeyPurposeIdBCFips) purposeId).getKeyPurposeId()));
}
+ /**
+ * Creates new wrapper instance for {@link ExtendedKeyUsage}.
+ *
+ * @param purposeIds KeyPurposeId wrappers array
+ */
+ public ExtendedKeyUsageBCFips(IKeyPurposeId[] purposeIds) {
+ super(new ExtendedKeyUsage(unwrapPurposeIds(purposeIds)));
+ }
+
/**
* Gets actual org.bouncycastle object being wrapped.
*
@@ -58,4 +68,12 @@ public ExtendedKeyUsageBCFips(IKeyPurposeId purposeId) {
public ExtendedKeyUsage getExtendedKeyUsage() {
return (ExtendedKeyUsage) getEncodable();
}
+
+ private static KeyPurposeId[] unwrapPurposeIds(IKeyPurposeId[] purposeIds) {
+ KeyPurposeId[] purposeIdsUnwrapped = new KeyPurposeId[purposeIds.length];
+ for (int i = 0; i < purposeIds.length; ++i) {
+ purposeIdsUnwrapped[i] = ((KeyPurposeIdBCFips) purposeIds[i]).getKeyPurposeId();
+ }
+ return purposeIdsUnwrapped;
+ }
}
diff --git a/bouncy-castle-fips-adapter/src/main/java/com/itextpdf/bouncycastlefips/asn1/x509/ExtensionBCFips.java b/bouncy-castle-fips-adapter/src/main/java/com/itextpdf/bouncycastlefips/asn1/x509/ExtensionBCFips.java
index caa78537f6..acf1e2a940 100644
--- a/bouncy-castle-fips-adapter/src/main/java/com/itextpdf/bouncycastlefips/asn1/x509/ExtensionBCFips.java
+++ b/bouncy-castle-fips-adapter/src/main/java/com/itextpdf/bouncycastlefips/asn1/x509/ExtensionBCFips.java
@@ -35,9 +35,12 @@ This file is part of the iText (R) project.
public class ExtensionBCFips extends ASN1EncodableBCFips implements IExtension {
private static final ExtensionBCFips INSTANCE = new ExtensionBCFips(null);
- private static final ASN1ObjectIdentifierBCFips C_RL_DISTRIBUTION_POINTS =
+ private static final ASN1ObjectIdentifierBCFips CRL_DISTRIBUTION_POINTS =
new ASN1ObjectIdentifierBCFips(Extension.cRLDistributionPoints);
+ private static final ASN1ObjectIdentifierBCFips ISSUING_DISTRIBUTION_POINT =
+ new ASN1ObjectIdentifierBCFips(Extension.issuingDistributionPoint);
+
private static final ASN1ObjectIdentifierBCFips AUTHORITY_INFO_ACCESS =
new ASN1ObjectIdentifierBCFips(Extension.authorityInfoAccess);
@@ -88,7 +91,15 @@ public Extension getExtension() {
*/
@Override
public IASN1ObjectIdentifier getCRlDistributionPoints() {
- return C_RL_DISTRIBUTION_POINTS;
+ return CRL_DISTRIBUTION_POINTS;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public IASN1ObjectIdentifier getIssuingDistributionPoint() {
+ return ISSUING_DISTRIBUTION_POINT;
}
/**
diff --git a/bouncy-castle-fips-adapter/src/main/java/com/itextpdf/bouncycastlefips/asn1/x509/IssuingDistributionPointBCFips.java b/bouncy-castle-fips-adapter/src/main/java/com/itextpdf/bouncycastlefips/asn1/x509/IssuingDistributionPointBCFips.java
new file mode 100644
index 0000000000..a2f14c778d
--- /dev/null
+++ b/bouncy-castle-fips-adapter/src/main/java/com/itextpdf/bouncycastlefips/asn1/x509/IssuingDistributionPointBCFips.java
@@ -0,0 +1,112 @@
+/*
+ This file is part of the iText (R) project.
+ Copyright (c) 1998-2024 Apryse Group NV
+ Authors: Apryse Software.
+
+ This program is offered under a commercial and under the AGPL license.
+ For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
+
+ AGPL licensing:
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+ */
+package com.itextpdf.bouncycastlefips.asn1.x509;
+
+import com.itextpdf.bouncycastlefips.asn1.ASN1EncodableBCFips;
+import com.itextpdf.commons.bouncycastle.asn1.x509.IDistributionPointName;
+import com.itextpdf.commons.bouncycastle.asn1.x509.IIssuingDistributionPoint;
+import com.itextpdf.commons.bouncycastle.asn1.x509.IReasonFlags;
+import org.bouncycastle.asn1.x509.IssuingDistributionPoint;
+
+/**
+ * Wrapper class for {@link IssuingDistributionPoint}.
+ */
+public class IssuingDistributionPointBCFips extends ASN1EncodableBCFips implements IIssuingDistributionPoint {
+ /**
+ * Creates new wrapper instance for {@link IssuingDistributionPoint}.
+ *
+ * @param issuingDistPoint {@link IssuingDistributionPoint} to be wrapped
+ */
+ public IssuingDistributionPointBCFips(IssuingDistributionPoint issuingDistPoint) {
+ super(issuingDistPoint);
+ }
+
+ /**
+ * Gets actual org.bouncycastle object being wrapped.
+ *
+ * @return wrapped {@link IssuingDistributionPoint}.
+ */
+ public IssuingDistributionPoint getIssuingDistributionPoint() {
+ return (IssuingDistributionPoint) getEncodable();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return {@inheritDoc}
+ */
+ @Override
+ public IDistributionPointName getDistributionPoint() {
+ return new DistributionPointNameBCFips(getIssuingDistributionPoint().getDistributionPoint());
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return {@inheritDoc}
+ */
+ @Override
+ public boolean onlyContainsUserCerts() {
+ return getIssuingDistributionPoint().onlyContainsUserCerts();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return {@inheritDoc}
+ */
+ @Override
+ public boolean onlyContainsCACerts() {
+ return getIssuingDistributionPoint().onlyContainsCACerts();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return {@inheritDoc}
+ */
+ @Override
+ public boolean isIndirectCRL() {
+ return getIssuingDistributionPoint().isIndirectCRL();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return {@inheritDoc}
+ */
+ @Override
+ public boolean onlyContainsAttributeCerts() {
+ return getIssuingDistributionPoint().onlyContainsAttributeCerts();
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return {@inheritDoc}
+ */
+ @Override
+ public IReasonFlags getOnlySomeReasons() {
+ return new ReasonFlagsBCFips(getIssuingDistributionPoint().getOnlySomeReasons());
+ }
+}
diff --git a/bouncy-castle-fips-adapter/src/main/java/com/itextpdf/bouncycastlefips/asn1/x509/ReasonFlagsBCFips.java b/bouncy-castle-fips-adapter/src/main/java/com/itextpdf/bouncycastlefips/asn1/x509/ReasonFlagsBCFips.java
new file mode 100644
index 0000000000..b1cb89240d
--- /dev/null
+++ b/bouncy-castle-fips-adapter/src/main/java/com/itextpdf/bouncycastlefips/asn1/x509/ReasonFlagsBCFips.java
@@ -0,0 +1,50 @@
+/*
+ This file is part of the iText (R) project.
+ Copyright (c) 1998-2024 Apryse Group NV
+ Authors: Apryse Software.
+
+ This program is offered under a commercial and under the AGPL license.
+ For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
+
+ AGPL licensing:
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+ */
+package com.itextpdf.bouncycastlefips.asn1.x509;
+
+import com.itextpdf.bouncycastlefips.asn1.ASN1BitStringBCFips;
+import com.itextpdf.commons.bouncycastle.asn1.x509.IReasonFlags;
+import org.bouncycastle.asn1.x509.ReasonFlags;
+
+/**
+ * Wrapper class for {@link ReasonFlags}.
+ */
+public class ReasonFlagsBCFips extends ASN1BitStringBCFips implements IReasonFlags {
+ /**
+ * Creates new wrapper instance for {@link ReasonFlags}.
+ *
+ * @param reasonFlags {@link ReasonFlags} to be wrapped
+ */
+ public ReasonFlagsBCFips(ReasonFlags reasonFlags) {
+ super(reasonFlags);
+ }
+
+ /**
+ * Gets actual org.bouncycastle object being wrapped.
+ *
+ * @return wrapped {@link ReasonFlags}.
+ */
+ public ReasonFlags getReasonFlags() {
+ return (ReasonFlags) getEncodable();
+ }
+}
diff --git a/bouncy-castle-fips-adapter/src/main/java/com/itextpdf/bouncycastlefips/cert/X509v2CRLBuilderBCFips.java b/bouncy-castle-fips-adapter/src/main/java/com/itextpdf/bouncycastlefips/cert/X509v2CRLBuilderBCFips.java
index 88b0899924..17180d5f6c 100644
--- a/bouncy-castle-fips-adapter/src/main/java/com/itextpdf/bouncycastlefips/cert/X509v2CRLBuilderBCFips.java
+++ b/bouncy-castle-fips-adapter/src/main/java/com/itextpdf/bouncycastlefips/cert/X509v2CRLBuilderBCFips.java
@@ -22,13 +22,18 @@ This file is part of the iText (R) project.
*/
package com.itextpdf.bouncycastlefips.cert;
+import com.itextpdf.bouncycastlefips.asn1.ASN1EncodableBCFips;
+import com.itextpdf.bouncycastlefips.asn1.ASN1ObjectIdentifierBCFips;
import com.itextpdf.bouncycastlefips.asn1.x500.X500NameBCFips;
import com.itextpdf.bouncycastlefips.operator.ContentSignerBCFips;
+import com.itextpdf.commons.bouncycastle.asn1.IASN1Encodable;
+import com.itextpdf.commons.bouncycastle.asn1.IASN1ObjectIdentifier;
import com.itextpdf.commons.bouncycastle.asn1.x500.IX500Name;
import com.itextpdf.commons.bouncycastle.cert.IX509CRLHolder;
import com.itextpdf.commons.bouncycastle.cert.IX509v2CRLBuilder;
import com.itextpdf.commons.bouncycastle.operator.IContentSigner;
+import java.io.IOException;
import java.math.BigInteger;
import java.util.Date;
import java.util.Objects;
@@ -77,6 +82,17 @@ public IX509v2CRLBuilder addCRLEntry(BigInteger bigInteger, Date date, int i) {
return this;
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public IX509v2CRLBuilder addExtension(IASN1ObjectIdentifier objectIdentifier, boolean isCritical,
+ IASN1Encodable extension) throws IOException {
+ builder.addExtension(((ASN1ObjectIdentifierBCFips) objectIdentifier).getASN1ObjectIdentifier(), isCritical,
+ ((ASN1EncodableBCFips) extension).getEncodable());
+ return this;
+ }
+
/**
* {@inheritDoc}
*/
diff --git a/bouncy-castle-fips-adapter/src/main/java/com/itextpdf/bouncycastlefips/cert/ocsp/BasicOCSPRespBCFips.java b/bouncy-castle-fips-adapter/src/main/java/com/itextpdf/bouncycastlefips/cert/ocsp/BasicOCSPRespBCFips.java
index 3771a16fc0..779768a627 100644
--- a/bouncy-castle-fips-adapter/src/main/java/com/itextpdf/bouncycastlefips/cert/ocsp/BasicOCSPRespBCFips.java
+++ b/bouncy-castle-fips-adapter/src/main/java/com/itextpdf/bouncycastlefips/cert/ocsp/BasicOCSPRespBCFips.java
@@ -92,6 +92,9 @@ public boolean isSignatureValid(IContentVerifierProvider provider) throws OCSPEx
@Override
public IX509CertificateHolder[] getCerts() {
X509CertificateHolder[] certs = basicOCSPResp.getCerts();
+ if (certs == null) {
+ return new IX509CertificateHolder[0];
+ }
IX509CertificateHolder[] certsBCFips = new IX509CertificateHolder[certs.length];
for (int i = 0; i < certs.length; i++) {
certsBCFips[i] = new X509CertificateHolderBCFips(certs[i]);
diff --git a/bouncy-castle-fips-adapter/src/main/java/com/itextpdf/bouncycastlefips/cert/ocsp/RevokedStatusBCFips.java b/bouncy-castle-fips-adapter/src/main/java/com/itextpdf/bouncycastlefips/cert/ocsp/RevokedStatusBCFips.java
index 7ad1999343..0b895eef06 100644
--- a/bouncy-castle-fips-adapter/src/main/java/com/itextpdf/bouncycastlefips/cert/ocsp/RevokedStatusBCFips.java
+++ b/bouncy-castle-fips-adapter/src/main/java/com/itextpdf/bouncycastlefips/cert/ocsp/RevokedStatusBCFips.java
@@ -26,6 +26,8 @@ This file is part of the iText (R) project.
import org.bouncycastle.cert.ocsp.RevokedStatus;
+import java.util.Date;
+
/**
* Wrapper class for {@link RevokedStatus}.
*/
@@ -47,4 +49,14 @@ public RevokedStatusBCFips(RevokedStatus certificateStatus) {
public RevokedStatus getRevokedStatus() {
return (RevokedStatus) super.getCertificateStatus();
}
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return {@inheritDoc}
+ */
+ @Override
+ public Date getRevocationTime() {
+ return getRevokedStatus().getRevocationTime();
+ }
}
diff --git a/commons/pom.xml b/commons/pom.xml
index 7c94e88eb8..a4541ae757 100644
--- a/commons/pom.xml
+++ b/commons/pom.xml
@@ -1,12 +1,15 @@
4.0.0
+
com.itextpdfroot
- 8.0.3
+ 8.0.4
+
commons
+
iText - commonshttps://itextpdf.com/
diff --git a/commons/src/main/java/com/itextpdf/commons/actions/ProductNameConstant.java b/commons/src/main/java/com/itextpdf/commons/actions/ProductNameConstant.java
index 9a9153aedd..a0524fe1a9 100644
--- a/commons/src/main/java/com/itextpdf/commons/actions/ProductNameConstant.java
+++ b/commons/src/main/java/com/itextpdf/commons/actions/ProductNameConstant.java
@@ -35,6 +35,10 @@ public final class ProductNameConstant {
* itext-core constant.
*/
public static final String ITEXT_CORE = "itext-core";
+ /**
+ * itext-core sign module constant.
+ */
+ public static final String ITEXT_CORE_SIGN = "itext-core-sign";
/**
* pdfhtml constant.
*/
diff --git a/commons/src/main/java/com/itextpdf/commons/actions/contexts/AbstractContextManagerConfigurationEvent.java b/commons/src/main/java/com/itextpdf/commons/actions/contexts/AbstractContextManagerConfigurationEvent.java
index 0ba5b0ae47..44d22d201d 100644
--- a/commons/src/main/java/com/itextpdf/commons/actions/contexts/AbstractContextManagerConfigurationEvent.java
+++ b/commons/src/main/java/com/itextpdf/commons/actions/contexts/AbstractContextManagerConfigurationEvent.java
@@ -50,7 +50,7 @@ protected void registerGenericContext(Collection namespaces, Collection<
/**
* Unregisters certain namespaces.
*
- * @param namespaces the namespaces to be unregisted
+ * @param namespaces the namespaces to be unregistered
*/
protected void unregisterContext(Collection namespaces) {
ContextManager.getInstance().unregisterContext(namespaces);
diff --git a/commons/src/main/java/com/itextpdf/commons/actions/contexts/ContextManager.java b/commons/src/main/java/com/itextpdf/commons/actions/contexts/ContextManager.java
index 0ec20c817d..b4e17fd08f 100644
--- a/commons/src/main/java/com/itextpdf/commons/actions/contexts/ContextManager.java
+++ b/commons/src/main/java/com/itextpdf/commons/actions/contexts/ContextManager.java
@@ -44,6 +44,9 @@ public class ContextManager {
local.registerGenericContext(NamespaceConstant.ITEXT_CORE_NAMESPACES,
Collections.singleton(ProductNameConstant.ITEXT_CORE));
+ local.registerGenericContext(Collections.singleton(NamespaceConstant.CORE_SIGN),
+ Collections.singleton(ProductNameConstant.ITEXT_CORE_SIGN));
+
local.registerGenericContext(Collections.singletonList(NamespaceConstant.PDF_HTML),
Collections.singleton(ProductNameConstant.PDF_HTML));
diff --git a/commons/src/main/java/com/itextpdf/commons/actions/data/CommonsProductData.java b/commons/src/main/java/com/itextpdf/commons/actions/data/CommonsProductData.java
index 23258855a1..a81e81974a 100644
--- a/commons/src/main/java/com/itextpdf/commons/actions/data/CommonsProductData.java
+++ b/commons/src/main/java/com/itextpdf/commons/actions/data/CommonsProductData.java
@@ -28,7 +28,7 @@ This file is part of the iText (R) project.
public final class CommonsProductData {
static final String COMMONS_PUBLIC_PRODUCT_NAME = "Commons";
static final String COMMONS_PRODUCT_NAME = "commons";
- static final String COMMONS_VERSION = "8.0.3";
+ static final String COMMONS_VERSION = "8.0.4";
static final String MINIMAL_COMPATIBLE_LICENSEKEY_VERSION = "4.1.0";
static final int COMMONS_COPYRIGHT_SINCE = 2000;
static final int COMMONS_COPYRIGHT_TO = 2024;
diff --git a/commons/src/main/java/com/itextpdf/commons/bouncycastle/IBouncyCastleFactory.java b/commons/src/main/java/com/itextpdf/commons/bouncycastle/IBouncyCastleFactory.java
index 903b62969c..6c6f7f60d2 100644
--- a/commons/src/main/java/com/itextpdf/commons/bouncycastle/IBouncyCastleFactory.java
+++ b/commons/src/main/java/com/itextpdf/commons/bouncycastle/IBouncyCastleFactory.java
@@ -81,8 +81,10 @@ This file is part of the iText (R) project.
import com.itextpdf.commons.bouncycastle.asn1.x509.IExtensions;
import com.itextpdf.commons.bouncycastle.asn1.x509.IGeneralName;
import com.itextpdf.commons.bouncycastle.asn1.x509.IGeneralNames;
+import com.itextpdf.commons.bouncycastle.asn1.x509.IIssuingDistributionPoint;
import com.itextpdf.commons.bouncycastle.asn1.x509.IKeyPurposeId;
import com.itextpdf.commons.bouncycastle.asn1.x509.IKeyUsage;
+import com.itextpdf.commons.bouncycastle.asn1.x509.IReasonFlags;
import com.itextpdf.commons.bouncycastle.asn1.x509.ISubjectPublicKeyInfo;
import com.itextpdf.commons.bouncycastle.asn1.x509.ITBSCertificate;
import com.itextpdf.commons.bouncycastle.asn1.x509.ITime;
@@ -988,13 +990,59 @@ IJcaX509CertificateHolder createJcaX509CertificateHolder(X509Certificate certifi
*/
ICRLDistPoint createCRLDistPoint(Object object);
+ /**
+ * Create Issuing Distribution Point wrapper from {@link Object}.
+ *
+ * @param point {@link Object} to create Issuing Distribution Point wrapper from
+ *
+ * @return created Issuing Distribution Point wrapper.
+ */
+ IIssuingDistributionPoint createIssuingDistributionPoint(Object point);
+
+ /**
+ * Create Issuing Distribution Point wrapper with specified values.
+ *
+ * @param distributionPoint one of names from the corresponding distributionPoint from the cRLDistributionPoints
+ * extension of every certificate that is within the scope of this CRL
+ * @param onlyContainsUserCerts true if the scope of the CRL only includes end entity public key certificates
+ * @param onlyContainsCACerts true if the scope of the CRL only includes CA certificates
+ * @param onlySomeReasons reason codes associated with a distribution point
+ * @param indirectCRL true if CRL includes certificates issued by authorities other than the CRL issuer,
+ * false if the scope of the CRL only includes certificates issued by the CRL issuer
+ * @param onlyContainsAttrCerts true if the scope of the CRL only includes attribute certificates
+ *
+ * @return created Issuing Distribution Point wrapper.
+ */
+ IIssuingDistributionPoint createIssuingDistributionPoint(IDistributionPointName distributionPoint,
+ boolean onlyContainsUserCerts, boolean onlyContainsCACerts,
+ IReasonFlags onlySomeReasons, boolean indirectCRL,
+ boolean onlyContainsAttrCerts);
+
+ /**
+ * Creates the wrapper for ReasonFlags.
+ *
+ * @param reasons the bitwise OR of the Key Reason flags giving the allowed uses for the key
+ *
+ * @return created ReasonFlags wrapper.
+ */
+ IReasonFlags createReasonFlags(int reasons);
+
/**
* Create distribution point name wrapper without parameters.
*
- * @return created distribution point name wrapper
+ * @return created distribution point name wrapper.
*/
IDistributionPointName createDistributionPointName();
+ /**
+ * Create distribution point name wrapper by passing general names.
+ *
+ * @param generalNames general names to create distribution point name from
+ *
+ * @return created distribution point name wrapper.
+ */
+ IDistributionPointName createDistributionPointName(IGeneralNames generalNames);
+
/**
* Cast ASN1 Encodable wrapper to general names wrapper.
*
@@ -1380,6 +1428,15 @@ IJcaX509v3CertificateBuilder createJcaX509v3CertificateBuilder(X509Certificate s
*/
IBasicConstraints createBasicConstraints(boolean b);
+ /**
+ * Create basic constraints wrapper from {@code int} value.
+ *
+ * @param pathLength {@code int} flag to create basic constraints wrapper from
+ *
+ * @return created basic constraints wrapper
+ */
+ IBasicConstraints createBasicConstraints(int pathLength);
+
/**
* Create key usage wrapper without parameters.
*
@@ -1403,6 +1460,15 @@ IJcaX509v3CertificateBuilder createJcaX509v3CertificateBuilder(X509Certificate s
*/
IKeyPurposeId createKeyPurposeId();
+ /**
+ * Create key purpose id wrapper from {@link IASN1ObjectIdentifier}.
+ *
+ * @param objectIdentifier {@link IASN1ObjectIdentifier} to create key purpose id wrapper from
+ *
+ * @return created key purpose id wrapper
+ */
+ IKeyPurposeId createKeyPurposeId(IASN1ObjectIdentifier objectIdentifier);
+
/**
* Create extended key usage wrapper from key purpose id wrapper.
*
@@ -1412,6 +1478,15 @@ IJcaX509v3CertificateBuilder createJcaX509v3CertificateBuilder(X509Certificate s
*/
IExtendedKeyUsage createExtendedKeyUsage(IKeyPurposeId purposeId);
+ /**
+ * Create extended key usage wrapper from key purpose id wrappers array.
+ *
+ * @param purposeIds {@link IKeyPurposeId} array to create extended key usage wrapper from
+ *
+ * @return created extended key usage wrapper
+ */
+ IExtendedKeyUsage createExtendedKeyUsage(IKeyPurposeId[] purposeIds);
+
/**
* Create X509 Extension utils wrapper from digest calculator wrapper.
*
@@ -1513,6 +1588,15 @@ IJcaX509v3CertificateBuilder createJcaX509v3CertificateBuilder(X509Certificate s
*/
ITime createTime(Date date);
+ /**
+ * Create time wrapper from the end date of the certificate.
+ *
+ * @param certificate {@link X509Certificate} to get end date to create time wrapper from
+ *
+ * @return created time wrapper
+ */
+ ITime createEndDate(X509Certificate certificate);
+
/**
* Checks if provided extension wrapper wraps {@code null}.
*
diff --git a/commons/src/main/java/com/itextpdf/commons/bouncycastle/asn1/IASN1BitString.java b/commons/src/main/java/com/itextpdf/commons/bouncycastle/asn1/IASN1BitString.java
index 889a4240b6..92d18b1e89 100644
--- a/commons/src/main/java/com/itextpdf/commons/bouncycastle/asn1/IASN1BitString.java
+++ b/commons/src/main/java/com/itextpdf/commons/bouncycastle/asn1/IASN1BitString.java
@@ -27,4 +27,10 @@ This file is part of the iText (R) project.
* to switch between bouncy-castle and bouncy-castle FIPS implementations.
*/
public interface IASN1BitString extends IASN1Primitive, IASN1String {
+ /**
+ * Calls actual {@code intValue} method for the wrapped ASN1BitString object.
+ *
+ * @return int value of the wrapped ASN1BitString.
+ */
+ int intValue();
}
diff --git a/commons/src/main/java/com/itextpdf/commons/bouncycastle/asn1/x509/ICRLReason.java b/commons/src/main/java/com/itextpdf/commons/bouncycastle/asn1/x509/ICRLReason.java
index e67795875a..48019b93bc 100644
--- a/commons/src/main/java/com/itextpdf/commons/bouncycastle/asn1/x509/ICRLReason.java
+++ b/commons/src/main/java/com/itextpdf/commons/bouncycastle/asn1/x509/ICRLReason.java
@@ -35,4 +35,11 @@ public interface ICRLReason extends IASN1Encodable {
* @return CRLReason.keyCompromise value.
*/
int getKeyCompromise();
+
+ /**
+ * Gets {@code removeFromCRL} constant for the wrapped CRLReason.
+ *
+ * @return CRLReason.removeFromCRL value.
+ */
+ int getRemoveFromCRL();
}
diff --git a/commons/src/main/java/com/itextpdf/commons/bouncycastle/asn1/x509/IDistributionPoint.java b/commons/src/main/java/com/itextpdf/commons/bouncycastle/asn1/x509/IDistributionPoint.java
index 89debd066b..549e56d1cc 100644
--- a/commons/src/main/java/com/itextpdf/commons/bouncycastle/asn1/x509/IDistributionPoint.java
+++ b/commons/src/main/java/com/itextpdf/commons/bouncycastle/asn1/x509/IDistributionPoint.java
@@ -35,4 +35,18 @@ public interface IDistributionPoint extends IASN1Encodable {
* @return {@link IDistributionPointName} wrapped distribution point.
*/
IDistributionPointName getDistributionPoint();
+
+ /**
+ * Calls actual {@code getCRLIssuer} method for the wrapped DistributionPoint object.
+ *
+ * @return {@link IGeneralNames} wrapped CRL issuer.
+ */
+ IGeneralNames getCRLIssuer();
+
+ /**
+ * Calls actual {@code getReasons} method for the wrapped DistributionPoint object.
+ *
+ * @return {@link IReasonFlags} wrapped reason codes.
+ */
+ IReasonFlags getReasons();
}
diff --git a/commons/src/main/java/com/itextpdf/commons/bouncycastle/asn1/x509/IExtension.java b/commons/src/main/java/com/itextpdf/commons/bouncycastle/asn1/x509/IExtension.java
index 45a1836e46..e0a15c8fd2 100644
--- a/commons/src/main/java/com/itextpdf/commons/bouncycastle/asn1/x509/IExtension.java
+++ b/commons/src/main/java/com/itextpdf/commons/bouncycastle/asn1/x509/IExtension.java
@@ -37,6 +37,13 @@ public interface IExtension extends IASN1Encodable {
*/
IASN1ObjectIdentifier getCRlDistributionPoints();
+ /**
+ * Gets {@code issuingDistributionPoint} constant for the wrapped Extension.
+ *
+ * @return Extension.issuingDistributionPoint wrapper.
+ */
+ IASN1ObjectIdentifier getIssuingDistributionPoint();
+
/**
* Gets {@code authorityInfoAccess} constant for the wrapped Extension.
*
diff --git a/commons/src/main/java/com/itextpdf/commons/bouncycastle/asn1/x509/IIssuingDistributionPoint.java b/commons/src/main/java/com/itextpdf/commons/bouncycastle/asn1/x509/IIssuingDistributionPoint.java
new file mode 100644
index 0000000000..9230976e32
--- /dev/null
+++ b/commons/src/main/java/com/itextpdf/commons/bouncycastle/asn1/x509/IIssuingDistributionPoint.java
@@ -0,0 +1,73 @@
+/*
+ This file is part of the iText (R) project.
+ Copyright (c) 1998-2024 Apryse Group NV
+ Authors: Apryse Software.
+
+ This program is offered under a commercial and under the AGPL license.
+ For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
+
+ AGPL licensing:
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+ */
+package com.itextpdf.commons.bouncycastle.asn1.x509;
+
+import com.itextpdf.commons.bouncycastle.asn1.IASN1Encodable;
+
+/**
+ * This interface represents the wrapper for IssuingDistributionPoint that provides the ability
+ * to switch between bouncy-castle and bouncy-castle FIPS implementations.
+ */
+public interface IIssuingDistributionPoint extends IASN1Encodable {
+ /**
+ * Calls actual {@code getDistributionPoint} method for the wrapped IssuingDistributionPoint object.
+ *
+ * @return {@link IDistributionPointName} wrapped distribution point name.
+ */
+ IDistributionPointName getDistributionPoint();
+
+ /**
+ * Calls actual {@code onlyContainsUserCerts} method for the wrapped IssuingDistributionPoint object.
+ *
+ * @return true if onlyContainsUserCerts was set, false otherwise.
+ */
+ boolean onlyContainsUserCerts();
+
+ /**
+ * Calls actual {@code onlyContainsCACerts} method for the wrapped IssuingDistributionPoint object.
+ *
+ * @return true if onlyContainsCACerts was set, false otherwise.
+ */
+ boolean onlyContainsCACerts();
+
+ /**
+ * Calls actual {@code isIndirectCRL} method for the wrapped IssuingDistributionPoint object.
+ *
+ * @return boolean value identifying if CRL is indirect.
+ */
+ boolean isIndirectCRL();
+
+ /**
+ * Calls actual {@code onlyContainsAttributeCerts} method for the wrapped IssuingDistributionPoint object.
+ *
+ * @return true if onlyContainsAttributeCerts was set, false otherwise.
+ */
+ boolean onlyContainsAttributeCerts();
+
+ /**
+ * Calls actual {@code getOnlySomeReasons} method for the wrapped IssuingDistributionPoint object.
+ *
+ * @return {@link IReasonFlags} wrapped reason flags.
+ */
+ IReasonFlags getOnlySomeReasons();
+}
diff --git a/commons/src/main/java/com/itextpdf/commons/bouncycastle/asn1/x509/IReasonFlags.java b/commons/src/main/java/com/itextpdf/commons/bouncycastle/asn1/x509/IReasonFlags.java
new file mode 100644
index 0000000000..be1e69a6fe
--- /dev/null
+++ b/commons/src/main/java/com/itextpdf/commons/bouncycastle/asn1/x509/IReasonFlags.java
@@ -0,0 +1,32 @@
+/*
+ This file is part of the iText (R) project.
+ Copyright (c) 1998-2024 Apryse Group NV
+ Authors: Apryse Software.
+
+ This program is offered under a commercial and under the AGPL license.
+ For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
+
+ AGPL licensing:
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+ */
+package com.itextpdf.commons.bouncycastle.asn1.x509;
+
+import com.itextpdf.commons.bouncycastle.asn1.IASN1BitString;
+
+/**
+ * This interface represents the wrapper for ReasonFlags that provides the ability
+ * to switch between bouncy-castle and bouncy-castle FIPS implementations.
+ */
+public interface IReasonFlags extends IASN1BitString {
+}
diff --git a/commons/src/main/java/com/itextpdf/commons/bouncycastle/cert/IX509v2CRLBuilder.java b/commons/src/main/java/com/itextpdf/commons/bouncycastle/cert/IX509v2CRLBuilder.java
index 5112345f24..6b0f919b1c 100644
--- a/commons/src/main/java/com/itextpdf/commons/bouncycastle/cert/IX509v2CRLBuilder.java
+++ b/commons/src/main/java/com/itextpdf/commons/bouncycastle/cert/IX509v2CRLBuilder.java
@@ -22,8 +22,11 @@ This file is part of the iText (R) project.
*/
package com.itextpdf.commons.bouncycastle.cert;
+import com.itextpdf.commons.bouncycastle.asn1.IASN1Encodable;
+import com.itextpdf.commons.bouncycastle.asn1.IASN1ObjectIdentifier;
import com.itextpdf.commons.bouncycastle.operator.IContentSigner;
+import java.io.IOException;
import java.math.BigInteger;
import java.util.Date;
@@ -43,6 +46,20 @@ public interface IX509v2CRLBuilder {
*/
IX509v2CRLBuilder addCRLEntry(BigInteger bigInteger, Date date, int i);
+ /**
+ * Calls actual {@code addExtension} method for the wrapped X509v2CRLBuilder object.
+ *
+ * @param objectIdentifier extension object identifier
+ * @param isCritical specifies if extension is critical or not
+ * @param extension encoded extension value
+ *
+ * @return {@link IX509v2CRLBuilder} the current wrapper object.
+ *
+ * @throws IOException if an I/O error occurs.
+ */
+ IX509v2CRLBuilder addExtension(IASN1ObjectIdentifier objectIdentifier, boolean isCritical,
+ IASN1Encodable extension) throws IOException;
+
/**
* Calls actual {@code setNextUpdate} method for the wrapped X509v2CRLBuilder object.
*
diff --git a/commons/src/main/java/com/itextpdf/commons/bouncycastle/cert/ocsp/IRevokedStatus.java b/commons/src/main/java/com/itextpdf/commons/bouncycastle/cert/ocsp/IRevokedStatus.java
index 7e09e8a3fc..cc3943de9f 100644
--- a/commons/src/main/java/com/itextpdf/commons/bouncycastle/cert/ocsp/IRevokedStatus.java
+++ b/commons/src/main/java/com/itextpdf/commons/bouncycastle/cert/ocsp/IRevokedStatus.java
@@ -22,9 +22,17 @@ This file is part of the iText (R) project.
*/
package com.itextpdf.commons.bouncycastle.cert.ocsp;
+import java.util.Date;
+
/**
* This interface represents the wrapper for RevokedStatus that provides the ability
* to switch between bouncy-castle and bouncy-castle FIPS implementations.
*/
public interface IRevokedStatus extends ICertificateStatus {
+ /**
+ * Calls actual {@code getRevocationTime} method for the wrapped RevokedStatus object.
+ *
+ * @return certificate revocation time.
+ */
+ Date getRevocationTime();
}
diff --git a/font-asian/pom.xml b/font-asian/pom.xml
index 0eb7dc99c7..a538019ac1 100644
--- a/font-asian/pom.xml
+++ b/font-asian/pom.xml
@@ -1,12 +1,15 @@
4.0.0
+
com.itextpdfroot
- 8.0.3
+ 8.0.4
+
font-asian
+
iText - Asian fontsiText Asian fonts for use in conjunction with iText, a free Java-PDF libraryhttps://itextpdf.com/
@@ -15,9 +18,11 @@
Various licenses (see individual files)
+
true
+
diff --git a/forms/pom.xml b/forms/pom.xml
index 5d4f6ee98f..918b96d09f 100644
--- a/forms/pom.xml
+++ b/forms/pom.xml
@@ -1,15 +1,18 @@
4.0.0
+
com.itextpdfroot
- 8.0.3
+ 8.0.4
+
forms
+
iText - formshttps://itextpdf.com/
-
+
com.itextpdf
@@ -34,6 +37,18 @@
test
+
+
+
+
+ src/main/resources
+
+ **/*.json
+
+
+
+
+
bouncy-castle-test
diff --git a/forms/src/main/java/com/itextpdf/forms/FormDefaultAccessibilityProperties.java b/forms/src/main/java/com/itextpdf/forms/FormDefaultAccessibilityProperties.java
new file mode 100644
index 0000000000..0d8c79f71e
--- /dev/null
+++ b/forms/src/main/java/com/itextpdf/forms/FormDefaultAccessibilityProperties.java
@@ -0,0 +1,121 @@
+/*
+ This file is part of the iText (R) project.
+ Copyright (c) 1998-2024 Apryse Group NV
+ Authors: Apryse Software.
+
+ This program is offered under a commercial and under the AGPL license.
+ For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
+
+ AGPL licensing:
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+ */
+package com.itextpdf.forms;
+
+import com.itextpdf.commons.utils.MessageFormatUtil;
+import com.itextpdf.forms.exceptions.FormsExceptionMessageConstant;
+import com.itextpdf.forms.form.FormProperty;
+import com.itextpdf.kernel.exceptions.PdfException;
+import com.itextpdf.kernel.pdf.tagging.PdfStructureAttributes;
+import com.itextpdf.kernel.pdf.tagging.StandardRoles;
+import com.itextpdf.kernel.pdf.tagutils.DefaultAccessibilityProperties;
+import com.itextpdf.layout.IPropertyContainer;
+
+/**
+ * The {@link FormDefaultAccessibilityProperties} class is used to create a specific forms related instance of the
+ * {@link DefaultAccessibilityProperties} class.
+ */
+public class FormDefaultAccessibilityProperties extends DefaultAccessibilityProperties {
+ /**
+ * Represents the role: radio.
+ */
+ public static final String FORM_FIELD_RADIO = "rb";
+ /**
+ * Represents the role: Checkbox.
+ */
+ public static final String FORM_FIELD_CHECK = "cb";
+ /**
+ * Represents the role: PushButton.
+ */
+ public static final String FORM_FIELD_PUSH_BUTTON = "pb";
+ /**
+ * Represents the role: ListBox.
+ */
+ public static final String FORM_FIELD_LIST_BOX = "lb";
+ /**
+ * Represents the role: Text. This can be passwords, text areas, etc.
+ */
+ public static final String FORM_FIELD_TEXT = "tv";
+ private static final String ROLE_NAME = "Role";
+ private static final String OWNER_PRINT_FIELD_NAME = "PrintField";
+
+ private static final String ATTRIBUTE_CHECKED = "Checked";
+ private static final String ATTRIBUTE_ON = "on";
+ private static final String ATTRIBUTE_OFF = "off";
+
+ private static final String[] ALLOWED_VALUES = new String[] {
+ FORM_FIELD_TEXT,
+ FORM_FIELD_RADIO,
+ FORM_FIELD_CHECK,
+ FORM_FIELD_LIST_BOX,
+ FORM_FIELD_PUSH_BUTTON
+ };
+
+ /**
+ * Instantiates a new {@link FormDefaultAccessibilityProperties } instance based on structure element role.
+ *
+ * @param formFieldType the type of the formField
+ */
+ public FormDefaultAccessibilityProperties(String formFieldType) {
+ super(StandardRoles.FORM);
+ checkIfFormFieldTypeIsAllowed(formFieldType);
+ PdfStructureAttributes attrs = new PdfStructureAttributes(OWNER_PRINT_FIELD_NAME);
+ attrs.addEnumAttribute(ROLE_NAME, formFieldType);
+ super.addAttributes(attrs);
+
+ if (FORM_FIELD_RADIO.equals(formFieldType) || FORM_FIELD_CHECK.equals(formFieldType)) {
+ PdfStructureAttributes checkedState = new PdfStructureAttributes(OWNER_PRINT_FIELD_NAME);
+ checkedState.addEnumAttribute(ATTRIBUTE_CHECKED, ATTRIBUTE_OFF);
+ super.addAttributes(checkedState);
+ }
+ }
+
+ /**
+ * Updates the checked value of the form field based on the {@link FormProperty#FORM_FIELD_CHECKED} property.
+ * If no such property is found, the checked value is set to "off".
+ *
+ * @param element The element which contains a {@link FormProperty#FORM_FIELD_CHECKED} property.
+ */
+ public void updateCheckedValue(IPropertyContainer element) {
+ for (PdfStructureAttributes pdfStructureAttributes : getAttributesList()) {
+ if (pdfStructureAttributes.getAttributeAsEnum(ATTRIBUTE_CHECKED) != null) {
+ String checkedValue =
+ Boolean.TRUE.equals(element.getProperty(FormProperty.FORM_FIELD_CHECKED))
+ ? ATTRIBUTE_ON : ATTRIBUTE_OFF;
+ pdfStructureAttributes.addEnumAttribute(ATTRIBUTE_CHECKED, checkedValue);
+ }
+ }
+ }
+
+ private static void checkIfFormFieldTypeIsAllowed(String formFieldType) {
+ for (String allowedValue : ALLOWED_VALUES) {
+ if (allowedValue.equals(formFieldType)) {
+ return;
+ }
+ }
+ String allowedValues = String.join(", ", ALLOWED_VALUES);
+ String message = MessageFormatUtil.format(FormsExceptionMessageConstant.ROLE_NAME_INVALID_FOR_FORM,
+ formFieldType, allowedValues);
+ throw new PdfException(message);
+ }
+}
diff --git a/forms/src/main/java/com/itextpdf/forms/PdfAcroForm.java b/forms/src/main/java/com/itextpdf/forms/PdfAcroForm.java
index 53f5da836e..240eda83bd 100644
--- a/forms/src/main/java/com/itextpdf/forms/PdfAcroForm.java
+++ b/forms/src/main/java/com/itextpdf/forms/PdfAcroForm.java
@@ -39,6 +39,7 @@ This file is part of the iText (R) project.
import com.itextpdf.kernel.geom.AffineTransform;
import com.itextpdf.kernel.geom.Point;
import com.itextpdf.kernel.geom.Rectangle;
+import com.itextpdf.kernel.pdf.IsoKey;
import com.itextpdf.kernel.pdf.PdfArray;
import com.itextpdf.kernel.pdf.PdfBoolean;
import com.itextpdf.kernel.pdf.PdfDictionary;
@@ -258,7 +259,6 @@ public void addField(PdfFormField field, PdfPage page, boolean throwExceptionOnE
return;
}
}
-
PdfFormFieldMergeUtil.mergeKidsWithSameNames(field, throwExceptionOnError);
// PdfPageFormCopier expects that we replace existed field by a new one in case they have the same names.
diff --git a/forms/src/main/java/com/itextpdf/forms/PdfSigFieldLock.java b/forms/src/main/java/com/itextpdf/forms/PdfSigFieldLock.java
index 54a6d0f1de..035e632e3e 100644
--- a/forms/src/main/java/com/itextpdf/forms/PdfSigFieldLock.java
+++ b/forms/src/main/java/com/itextpdf/forms/PdfSigFieldLock.java
@@ -45,8 +45,9 @@ public PdfSigFieldLock() {
/**
* Creates an instance of {@link PdfSigFieldLock}.
- * @param dict The dictionary whose entries should be added to
- * the signature field lock dictionary.
+ *
+ * @param dict the dictionary whose entries should be added to
+ * the signature field lock dictionary
*/
public PdfSigFieldLock(PdfDictionary dict) {
super(dict);
@@ -57,8 +58,10 @@ public PdfSigFieldLock(PdfDictionary dict) {
* Sets the permissions granted for the document when the corresponding signature
* field is signed. See {@link PdfSigFieldLock.LockPermissions}
* for getting more info.
- * @param permissions The permissions granted for the document.
- * @return This {@link PdfSigFieldLock} object.
+ *
+ * @param permissions the permissions granted for the document
+ *
+ * @return this {@link PdfSigFieldLock} object.
*/
public PdfSigFieldLock setDocumentPermissions(LockPermissions permissions) {
getPdfObject().put(PdfName.P, getLockPermission(permissions));
@@ -67,10 +70,12 @@ public PdfSigFieldLock setDocumentPermissions(LockPermissions permissions) {
/**
* Sets signature lock for specific fields in the document.
- * @param action Indicates the set of fields that should be locked after the actual
- * signing of the corresponding signature takes place.
- * @param fields Names indicating the fields.
- * @return This {@link PdfSigFieldLock} object.
+ *
+ * @param action indicates the set of fields that should be locked after the actual
+ * signing of the corresponding signature takes place
+ * @param fields names indicating the fields
+ *
+ * @return this {@link PdfSigFieldLock} object.
*/
public PdfSigFieldLock setFieldLock(LockAction action, String... fields) {
PdfArray fieldsArray = new PdfArray();
@@ -82,6 +87,13 @@ public PdfSigFieldLock setFieldLock(LockAction action, String... fields) {
return this;
}
+ /**
+ * Returns the specified action of a signature field lock as {@link PdfName} value.
+ *
+ * @param action the action as {@link LockAction}
+ *
+ * @return the specified action of a signature field lock as {@link PdfName}.
+ */
public static PdfName getLockActionValue(LockAction action) {
switch (action) {
case ALL:
@@ -95,6 +107,12 @@ public static PdfName getLockActionValue(LockAction action) {
}
}
+ /**
+ * Returns the specified level of access permissions granted for the document as {@link PdfNumber} value.
+ *
+ * @param permissions the level of access permissions as {@link LockPermissions}
+ * @return the specified level of access permissions as {@link PdfNumber}.
+ */
public static PdfNumber getLockPermission(LockPermissions permissions) {
switch (permissions) {
case NO_CHANGES_ALLOWED:
diff --git a/forms/src/main/java/com/itextpdf/forms/exceptions/AttributeNotFoundException.java b/forms/src/main/java/com/itextpdf/forms/exceptions/AttributeNotFoundException.java
index dd26dc4a53..eea0ba8693 100644
--- a/forms/src/main/java/com/itextpdf/forms/exceptions/AttributeNotFoundException.java
+++ b/forms/src/main/java/com/itextpdf/forms/exceptions/AttributeNotFoundException.java
@@ -24,10 +24,15 @@ This file is part of the iText (R) project.
import com.itextpdf.commons.exceptions.ITextException;
+/**
+ * This class represents iText exception that should be thrown when the attribute with given name
+ * is not found in the object attributes list.
+ */
public class AttributeNotFoundException extends ITextException {
/**
* The exception thrown when the attribute with given name is not found in the object attributes list.
+ *
* @param attribute the name of missing attribute.
*/
public AttributeNotFoundException(String attribute) {
diff --git a/forms/src/main/java/com/itextpdf/forms/exceptions/FormsExceptionMessageConstant.java b/forms/src/main/java/com/itextpdf/forms/exceptions/FormsExceptionMessageConstant.java
index 72b621cdfb..dfe2060dfa 100644
--- a/forms/src/main/java/com/itextpdf/forms/exceptions/FormsExceptionMessageConstant.java
+++ b/forms/src/main/java/com/itextpdf/forms/exceptions/FormsExceptionMessageConstant.java
@@ -32,11 +32,11 @@ public final class FormsExceptionMessageConstant {
public static final String FIELD_FLATTENING_IS_NOT_SUPPORTED_IN_APPEND_MODE = "Field flattening is not supported "
+ "in append mode.";
-
+
public static final String INNER_ARRAY_SHALL_HAVE_TWO_ELEMENTS = "Inner arrays shall have exactly two elements";
public static final String OPTION_ELEMENT_MUST_BE_STRING_OR_ARRAY = "Option element must be a string or an array";
-
+
public static final String PAGE_ALREADY_FLUSHED_USE_ADD_FIELD_APPEARANCE_TO_PAGE_METHOD_BEFORE_PAGE_FLUSHING = ""
+ "The page has been already flushed. Use PdfAcroForm#addFieldAppearanceToPage() method before page "
+ "flushing.";
@@ -56,6 +56,9 @@ public final class FormsExceptionMessageConstant {
public static final String SEPARATOR_SHOULD_BE_A_VALID_VALUE = "Separator should be a valid value. Values that are "
+ "not allowed are null,empty string, or . ";
public static final String FIELD_NAME_ALREADY_EXISTS_IN_FORM = "Field name {0} already exists in the form.";
+ public static final String ROLE_NAME_INVALID_FOR_FORM = "Invalid formfield type: {0}, only following values are "
+ + "allowed {1}.";
+ ;
private FormsExceptionMessageConstant() {
diff --git a/forms/src/main/java/com/itextpdf/forms/exceptions/XfdfException.java b/forms/src/main/java/com/itextpdf/forms/exceptions/XfdfException.java
index 212fea160f..5b42637ef3 100644
--- a/forms/src/main/java/com/itextpdf/forms/exceptions/XfdfException.java
+++ b/forms/src/main/java/com/itextpdf/forms/exceptions/XfdfException.java
@@ -24,16 +24,29 @@ This file is part of the iText (R) project.
import com.itextpdf.commons.exceptions.ITextException;
+/**
+ * This class represents iText exception that should be thrown when some errors occur while working with
+ * XFDF objects (XFDF file is XML-based Acrobat Forms Data Format).
+ */
public class XfdfException extends ITextException {
+ /**
+ * The exception thrown when some errors occur while working with XFDF objects.
+ *
+ * @param message exception message.
+ */
public XfdfException(String message) {
super(message);
}
- /** Message in case one tries to add attribute with null name or value */
+ /**
+ * Message in case one tries to add attribute with null name or value.
+ */
public static final String ATTRIBUTE_NAME_OR_VALUE_MISSING = "Attribute name or value are missing";
- /** Message in case one tries to add annotation without indicating the page it belongs to*/
+ /**
+ * Message in case one tries to add annotation without indicating the page it belongs to.
+ */
public static final String PAGE_IS_MISSING = "Required Page attribute is missing.";
}
diff --git a/forms/src/main/java/com/itextpdf/forms/fields/AbstractPdfFormField.java b/forms/src/main/java/com/itextpdf/forms/fields/AbstractPdfFormField.java
index cd3a175987..16243fa745 100644
--- a/forms/src/main/java/com/itextpdf/forms/fields/AbstractPdfFormField.java
+++ b/forms/src/main/java/com/itextpdf/forms/fields/AbstractPdfFormField.java
@@ -31,6 +31,7 @@ This file is part of the iText (R) project.
import com.itextpdf.kernel.colors.DeviceGray;
import com.itextpdf.kernel.colors.DeviceRgb;
import com.itextpdf.kernel.font.PdfFont;
+import com.itextpdf.kernel.pdf.IConformanceLevel;
import com.itextpdf.kernel.pdf.PdfAConformanceLevel;
import com.itextpdf.kernel.pdf.PdfDictionary;
import com.itextpdf.kernel.pdf.PdfDocument;
@@ -82,8 +83,15 @@ public abstract class AbstractPdfFormField extends PdfObjectWrapper> {
/**
* Conformance level of the form field.
*/
- private PdfAConformanceLevel conformanceLevel = null;
+ private IConformanceLevel conformanceLevel = null;
/**
* Creates builder for {@link PdfFormField} creation.
@@ -54,6 +55,9 @@ public abstract class FormFieldBuilder> {
protected FormFieldBuilder(PdfDocument document, String formFieldName) {
this.document = document;
this.formFieldName = formFieldName;
+ if (document != null) {
+ this.conformanceLevel = document.getConformanceLevel();
+ }
}
/**
@@ -78,8 +82,25 @@ public String getFormFieldName() {
* Gets conformance level for form field creation.
*
* @return instance of {@link PdfAConformanceLevel} to be used for form field creation
+ * @deprecated since 8.0.4 will return {@link IConformanceLevel} in next major release
*/
+ @Deprecated
public PdfAConformanceLevel getConformanceLevel() {
+ if (conformanceLevel instanceof PdfAConformanceLevel){
+ return (PdfAConformanceLevel) conformanceLevel;
+ }
+ return null;
+ }
+
+ /**
+ * Gets conformance level for form field creation.
+ *
+ * @return instance of {@link IConformanceLevel} to be used for form field creation
+ *
+ * @deprecated since 8.0.4 will be renamed to getConformanceLevel()
+ */
+ @Deprecated
+ public IConformanceLevel getGenericConformanceLevel() {
return conformanceLevel;
}
@@ -88,12 +109,28 @@ public PdfAConformanceLevel getConformanceLevel() {
*
* @param conformanceLevel instance of {@link PdfAConformanceLevel} to be used for form field creation
* @return this builder
+ *
+ * @deprecated since 8.0.4 conformance level param will change to {@link IConformanceLevel}
*/
+ @Deprecated
public T setConformanceLevel(PdfAConformanceLevel conformanceLevel) {
this.conformanceLevel = conformanceLevel;
return getThis();
}
+ /**
+ * Sets conformance level for form field creation.
+ *
+ * @param conformanceLevel Instance of {@link IConformanceLevel} to be used for form field creation.
+ * @return This builder.
+ * @deprecated since 8.0.4 will be renamed to setConformanceLevel
+ */
+ @Deprecated
+ public T setGenericConformanceLevel(IConformanceLevel conformanceLevel) {
+ this.conformanceLevel = conformanceLevel;
+ return getThis();
+ }
+
/**
* Returns this builder object. Required for superclass methods.
*
diff --git a/forms/src/main/java/com/itextpdf/forms/fields/NonTerminalFormFieldBuilder.java b/forms/src/main/java/com/itextpdf/forms/fields/NonTerminalFormFieldBuilder.java
index e0901edf10..25303efd71 100644
--- a/forms/src/main/java/com/itextpdf/forms/fields/NonTerminalFormFieldBuilder.java
+++ b/forms/src/main/java/com/itextpdf/forms/fields/NonTerminalFormFieldBuilder.java
@@ -46,7 +46,7 @@ public NonTerminalFormFieldBuilder(PdfDocument document, String formFieldName) {
*/
public PdfFormField createNonTerminalFormField() {
PdfFormField field = PdfFormCreator.createFormField(getDocument());
- field.pdfAConformanceLevel = getConformanceLevel();
+ field.pdfConformanceLevel = getGenericConformanceLevel();
field.setFieldName(getFormFieldName());
return field;
}
diff --git a/forms/src/main/java/com/itextpdf/forms/fields/PdfFormAnnotation.java b/forms/src/main/java/com/itextpdf/forms/fields/PdfFormAnnotation.java
index cdb885fca5..33cb3d419b 100644
--- a/forms/src/main/java/com/itextpdf/forms/fields/PdfFormAnnotation.java
+++ b/forms/src/main/java/com/itextpdf/forms/fields/PdfFormAnnotation.java
@@ -163,7 +163,7 @@ public static PdfFormAnnotation makeFormAnnotation(PdfObject pdfObject, PdfDocum
if (document != null && document.getReader() != null
&& document.getReader().getPdfAConformanceLevel() != null) {
- field.pdfAConformanceLevel = document.getReader().getPdfAConformanceLevel();
+ field.pdfConformanceLevel = document.getReader().getPdfAConformanceLevel();
}
return field;
@@ -1007,7 +1007,7 @@ protected void drawCheckBoxAndSaveAppearance(String onStateName) {
final Canvas canvasOff = new Canvas(xObjectOff, getDocument());
setMetaInfoToCanvas(canvasOff);
canvasOff.add(formFieldElement);
- if (getPdfAConformanceLevel() == null) {
+ if (getPdfConformanceLevel() == null) {
xObjectOff.getResources().addFont(getDocument(), getFont());
}
normalAppearance.put(new PdfName(OFF_STATE_VALUE), xObjectOff.getPdfObject());
@@ -1227,7 +1227,7 @@ private void createCheckBox() {
formFieldElement.setProperty(Property.FONT_SIZE, UnitValue.createPointValue(getFontSize()));
setModelElementProperties(getRect(getPdfObject()));
- ((CheckBox) formFieldElement).setPdfAConformanceLevel(getPdfAConformanceLevel());
+ ((CheckBox) formFieldElement).setPdfConformanceLevel(getPdfConformanceLevel());
((CheckBox) formFieldElement).setCheckBoxType(parent.checkType.getValue());
}
diff --git a/forms/src/main/java/com/itextpdf/forms/fields/PdfFormAnnotationUtil.java b/forms/src/main/java/com/itextpdf/forms/fields/PdfFormAnnotationUtil.java
index 94e69acb9a..0e51480368 100644
--- a/forms/src/main/java/com/itextpdf/forms/fields/PdfFormAnnotationUtil.java
+++ b/forms/src/main/java/com/itextpdf/forms/fields/PdfFormAnnotationUtil.java
@@ -100,7 +100,6 @@ public static void addWidgetAnnotationToPage(PdfPage page, PdfAnnotation annotat
boolean tagged = document.isTagged();
if (tagged) {
tagPointer = document.getTagStructureContext().getAutoTaggingPointer();
- //TODO DEVSIX-4117 PrintField attributes
if (!StandardRoles.FORM.equals(tagPointer.getRole())) {
tagPointer.addTag(StandardRoles.FORM);
}
diff --git a/forms/src/main/java/com/itextpdf/forms/fields/PdfFormField.java b/forms/src/main/java/com/itextpdf/forms/fields/PdfFormField.java
index f11282f74d..9c15b510c0 100644
--- a/forms/src/main/java/com/itextpdf/forms/fields/PdfFormField.java
+++ b/forms/src/main/java/com/itextpdf/forms/fields/PdfFormField.java
@@ -257,7 +257,7 @@ public static PdfFormField makeFormField(PdfObject pdfObject, PdfDocument docume
if (document != null && document.getReader() != null &&
document.getReader().getPdfAConformanceLevel() != null) {
- field.pdfAConformanceLevel = document.getReader().getPdfAConformanceLevel();
+ field.pdfConformanceLevel = document.getReader().getPdfAConformanceLevel();
}
return field;
@@ -1068,7 +1068,7 @@ public PdfFormField setCheckType(CheckBoxType checkType) {
checkType = CheckBoxType.CROSS;
}
this.checkType = new NullableContainer<>(checkType);
- if (getPdfAConformanceLevel() != null) {
+ if (getPdfConformanceLevel() != null) {
return this;
}
try {
diff --git a/forms/src/main/java/com/itextpdf/forms/fields/PushButtonFormFieldBuilder.java b/forms/src/main/java/com/itextpdf/forms/fields/PushButtonFormFieldBuilder.java
index 70305a8edd..fb72358d8c 100644
--- a/forms/src/main/java/com/itextpdf/forms/fields/PushButtonFormFieldBuilder.java
+++ b/forms/src/main/java/com/itextpdf/forms/fields/PushButtonFormFieldBuilder.java
@@ -81,7 +81,7 @@ public PdfButtonFormField createPushButton() {
} else {
annotation = new PdfWidgetAnnotation(getWidgetRectangle());
field = PdfFormCreator.createButtonFormField(annotation, getDocument());
- if (null != getConformanceLevel()) {
+ if (null != getGenericConformanceLevel()) {
annotation.setFlag(PdfAnnotation.PRINT);
}
}
@@ -89,7 +89,7 @@ public PdfButtonFormField createPushButton() {
if (this.getFont() != null) {
field.setFont(this.getFont());
}
- field.pdfAConformanceLevel = getConformanceLevel();
+ field.pdfConformanceLevel = getGenericConformanceLevel();
field.setPushButton(true);
field.setFieldName(getFormFieldName());
field.text = caption;
diff --git a/forms/src/main/java/com/itextpdf/forms/fields/RadioFormFieldBuilder.java b/forms/src/main/java/com/itextpdf/forms/fields/RadioFormFieldBuilder.java
index b005af6a23..7c518f83cf 100644
--- a/forms/src/main/java/com/itextpdf/forms/fields/RadioFormFieldBuilder.java
+++ b/forms/src/main/java/com/itextpdf/forms/fields/RadioFormFieldBuilder.java
@@ -54,7 +54,7 @@ public RadioFormFieldBuilder(PdfDocument document, String radioGroupFormFieldNam
public PdfButtonFormField createRadioGroup() {
PdfButtonFormField radioGroup = PdfFormCreator.createButtonFormField(getDocument());
radioGroup.disableFieldRegeneration();
- radioGroup.pdfAConformanceLevel = getConformanceLevel();
+ radioGroup.pdfConformanceLevel = getGenericConformanceLevel();
radioGroup.setFieldName(getFormFieldName());
radioGroup.setFieldFlags(PdfButtonFormField.FF_RADIO);
radioGroup.enableFieldRegeneration();
@@ -84,12 +84,12 @@ public PdfFormAnnotation createRadioButton(String appearanceName, Rectangle rect
final PdfName appearancePdfName = new PdfName(appearanceName);
final PdfWidgetAnnotation annotation = new PdfWidgetAnnotation(widgetRectangle);
annotation.setAppearanceState(appearancePdfName);
- if (getConformanceLevel() != null) {
+ if (getGenericConformanceLevel() != null) {
annotation.setFlag(PdfAnnotation.PRINT);
}
PdfFormAnnotation radio = PdfFormCreator.createFormAnnotation(annotation, getDocument());
setPageToField(radio);
- radio.pdfAConformanceLevel = getConformanceLevel();
+ radio.pdfConformanceLevel = getGenericConformanceLevel();
return radio;
}
diff --git a/forms/src/main/java/com/itextpdf/forms/fields/SignatureFormFieldBuilder.java b/forms/src/main/java/com/itextpdf/forms/fields/SignatureFormFieldBuilder.java
index ee58d79984..d469a0f7ba 100644
--- a/forms/src/main/java/com/itextpdf/forms/fields/SignatureFormFieldBuilder.java
+++ b/forms/src/main/java/com/itextpdf/forms/fields/SignatureFormFieldBuilder.java
@@ -52,7 +52,7 @@ public PdfSignatureFormField createSignature() {
signatureFormField = PdfFormCreator.createSignatureFormField(getDocument());
} else {
PdfWidgetAnnotation annotation = new PdfWidgetAnnotation(getWidgetRectangle());
- if (getConformanceLevel() != null) {
+ if (getGenericConformanceLevel() != null) {
annotation.setFlag(PdfAnnotation.PRINT);
}
signatureFormField = PdfFormCreator.createSignatureFormField(annotation, getDocument());
@@ -63,7 +63,7 @@ public PdfSignatureFormField createSignature() {
if (getFont() != null) {
signatureFormField.font = getFont();
}
- signatureFormField.pdfAConformanceLevel = getConformanceLevel();
+ signatureFormField.pdfConformanceLevel = getGenericConformanceLevel();
signatureFormField.setFieldName(getFormFieldName());
return signatureFormField;
}
diff --git a/forms/src/main/java/com/itextpdf/forms/fields/TextFormFieldBuilder.java b/forms/src/main/java/com/itextpdf/forms/fields/TextFormFieldBuilder.java
index 468efa4aaa..19c5d9cc23 100644
--- a/forms/src/main/java/com/itextpdf/forms/fields/TextFormFieldBuilder.java
+++ b/forms/src/main/java/com/itextpdf/forms/fields/TextFormFieldBuilder.java
@@ -58,7 +58,7 @@ private PdfTextFormField createText(boolean multiline) {
field = PdfFormCreator.createTextFormField(getDocument());
} else {
PdfWidgetAnnotation annotation = new PdfWidgetAnnotation(getWidgetRectangle());
- if (null != getConformanceLevel()) {
+ if (null != getGenericConformanceLevel()) {
annotation.setFlag(PdfAnnotation.PRINT);
}
field = PdfFormCreator.createTextFormField(annotation, getDocument());
@@ -68,7 +68,7 @@ private PdfTextFormField createText(boolean multiline) {
field.setFont(getFont());
}
field.disableFieldRegeneration();
- field.pdfAConformanceLevel = getConformanceLevel();
+ field.pdfConformanceLevel = getGenericConformanceLevel();
field.setMultiline(multiline);
field.setFieldName(getFormFieldName());
field.setValue(TEXT_FORM_FIELD_DEFAULT_VALUE);
diff --git a/forms/src/main/java/com/itextpdf/forms/fields/properties/SignedAppearanceText.java b/forms/src/main/java/com/itextpdf/forms/fields/properties/SignedAppearanceText.java
index ffc8481634..cc1e4c9242 100644
--- a/forms/src/main/java/com/itextpdf/forms/fields/properties/SignedAppearanceText.java
+++ b/forms/src/main/java/com/itextpdf/forms/fields/properties/SignedAppearanceText.java
@@ -33,12 +33,12 @@ public class SignedAppearanceText {
/**
* The reason for signing.
*/
- private String reason = "Reason: ";
+ private String reason = "";
/**
* Holds value of property location.
*/
- private String location = "Location: ";
+ private String location = "";
/**
* The name of the signer from the certificate.
@@ -79,7 +79,7 @@ public String getReasonLine() {
* @return this same {@link SignedAppearanceText} instance.
*/
public SignedAppearanceText setReasonLine(String reason) {
- this.reason = reason;
+ this.reason = reason.trim();
return this;
}
@@ -104,7 +104,7 @@ public String getLocationLine() {
* @return this same {@link SignedAppearanceText} instance.
*/
public SignedAppearanceText setLocationLine(String location) {
- this.location = location;
+ this.location = location.trim();
return this;
}
@@ -119,7 +119,7 @@ public SignedAppearanceText setLocationLine(String location) {
* @return this same {@link SignedAppearanceText} instance.
*/
public SignedAppearanceText setSignedBy(String signedBy) {
- this.signedBy = signedBy;
+ this.signedBy = signedBy.trim();
return this;
}
@@ -163,16 +163,16 @@ public SignedAppearanceText setSignDate(java.util.Calendar signDate) {
*/
public String generateDescriptionText() {
final StringBuilder buf = new StringBuilder();
- if (!signedBy.isEmpty()) {
+ if (signedBy != null && !signedBy.isEmpty()) {
buf.append("Digitally signed by ").append(signedBy);
}
if (isSignDateSet) {
buf.append('\n').append("Date: ").append(DateTimeUtil.dateToString(signDate));
}
- if (reason != null) {
+ if (reason != null && !reason.isEmpty()) {
buf.append('\n').append(reason);
}
- if (location != null) {
+ if (location != null && !location.isEmpty()) {
buf.append('\n').append(location);
}
return buf.toString();
diff --git a/forms/src/main/java/com/itextpdf/forms/form/FormProperty.java b/forms/src/main/java/com/itextpdf/forms/form/FormProperty.java
index 6d21cff508..498cff7609 100644
--- a/forms/src/main/java/com/itextpdf/forms/form/FormProperty.java
+++ b/forms/src/main/java/com/itextpdf/forms/form/FormProperty.java
@@ -61,6 +61,7 @@ public final class FormProperty {
public static final int FORM_FIELD_LABEL = PROPERTY_START + 10;
/** The Constant FORM_ACCESSIBILITY_LANGUAGE. */
+ @Deprecated()
public static final int FORM_ACCESSIBILITY_LANGUAGE = PROPERTY_START + 11;
/** The Constant FORM_FIELD_RADIO_GROUP_NAME. */
diff --git a/forms/src/main/java/com/itextpdf/forms/form/element/AbstractSelectField.java b/forms/src/main/java/com/itextpdf/forms/form/element/AbstractSelectField.java
index 5ab5c29ef9..a7bcd38674 100644
--- a/forms/src/main/java/com/itextpdf/forms/form/element/AbstractSelectField.java
+++ b/forms/src/main/java/com/itextpdf/forms/form/element/AbstractSelectField.java
@@ -37,6 +37,11 @@ public abstract class AbstractSelectField extends FormField
protected List options = new ArrayList<>();
+ /**
+ * Instantiates a new {@link AbstractSelectField} instance.
+ *
+ * @param id the id of the field
+ */
protected AbstractSelectField(String id) {
super(id);
}
@@ -44,7 +49,7 @@ protected AbstractSelectField(String id) {
/**
* Add a container with options. This might be a container for options group.
*
- * @param optionElement a container with options.
+ * @param optionElement a container with options
*
* @deprecated starting from 8.0.1.
*/
@@ -57,7 +62,7 @@ public void addOption(IBlockElement optionElement) {
/**
* Add an option to the element.
*
- * @param option a {@link SelectFieldItem}.
+ * @param option a {@link SelectFieldItem}
*/
public void addOption(SelectFieldItem option) {
options.add(option);
@@ -66,8 +71,8 @@ public void addOption(SelectFieldItem option) {
/**
* Add an option to the element.
*
- * @param option a {@link SelectFieldItem}.
- * @param selected {@code true} is the option if selected, {@code false} otherwise.
+ * @param option a {@link SelectFieldItem}
+ * @param selected {@code true} is the option if selected, {@code false} otherwise
*/
public void addOption(SelectFieldItem option, boolean selected) {
option.getElement().setProperty(FormProperty.FORM_FIELD_SELECTED, selected);
@@ -105,7 +110,7 @@ public boolean hasOptions() {
/**
* Get an option {@link SelectFieldItem} by its string value.
*
- * @param value string value to find an option by.
+ * @param value string value to find an option by
*
* @return a {@link SelectFieldItem}.
*/
diff --git a/forms/src/main/java/com/itextpdf/forms/form/element/Button.java b/forms/src/main/java/com/itextpdf/forms/form/element/Button.java
index 1395c9cc58..937b99f35a 100644
--- a/forms/src/main/java/com/itextpdf/forms/form/element/Button.java
+++ b/forms/src/main/java/com/itextpdf/forms/form/element/Button.java
@@ -22,9 +22,11 @@ This file is part of the iText (R) project.
*/
package com.itextpdf.forms.form.element;
+import com.itextpdf.forms.FormDefaultAccessibilityProperties;
import com.itextpdf.forms.form.renderer.ButtonRenderer;
import com.itextpdf.kernel.colors.Color;
import com.itextpdf.kernel.colors.ColorConstants;
+import com.itextpdf.kernel.pdf.tagutils.AccessibilityProperties;
import com.itextpdf.layout.element.BlockElement;
import com.itextpdf.layout.element.IBlockElement;
import com.itextpdf.layout.element.Image;
@@ -152,6 +154,19 @@ public T1 getDefaultProperty(int property) {
return super.getDefaultProperty(property);
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public AccessibilityProperties getAccessibilityProperties() {
+ if (tagProperties == null){
+ tagProperties = new FormDefaultAccessibilityProperties(
+ FormDefaultAccessibilityProperties.FORM_FIELD_PUSH_BUTTON);
+ }
+ return tagProperties;
+ }
+
+
/**
* {@inheritDoc}
*
diff --git a/forms/src/main/java/com/itextpdf/forms/form/element/CheckBox.java b/forms/src/main/java/com/itextpdf/forms/form/element/CheckBox.java
index b207966b87..56fc402939 100644
--- a/forms/src/main/java/com/itextpdf/forms/form/element/CheckBox.java
+++ b/forms/src/main/java/com/itextpdf/forms/form/element/CheckBox.java
@@ -23,11 +23,15 @@ This file is part of the iText (R) project.
package com.itextpdf.forms.form.element;
import com.itextpdf.commons.utils.MessageFormatUtil;
+import com.itextpdf.forms.FormDefaultAccessibilityProperties;
import com.itextpdf.forms.fields.properties.CheckBoxType;
import com.itextpdf.forms.form.FormProperty;
import com.itextpdf.forms.form.renderer.CheckBoxRenderer;
import com.itextpdf.forms.logs.FormsLogMessageConstants;
+import com.itextpdf.kernel.pdf.IConformanceLevel;
import com.itextpdf.kernel.pdf.PdfAConformanceLevel;
+import com.itextpdf.kernel.pdf.tagging.PdfStructureAttributes;
+import com.itextpdf.kernel.pdf.tagutils.AccessibilityProperties;
import com.itextpdf.layout.properties.BoxSizingPropertyValue;
import com.itextpdf.layout.properties.Property;
import com.itextpdf.layout.properties.UnitValue;
@@ -70,16 +74,30 @@ public CheckBox setChecked(boolean checked) {
/**
* Sets the PDF/A conformance level for the checkbox.
+ * This method is deprecated use setPdfConformanceLevel.
+ * @param conformanceLevel The PDF/A conformance level to set.
*
- * @param conformanceLevel the PDF/A conformance level to set
- *
- * @return this checkbox instance
+ * @return This checkbox instance.
*/
+ @Deprecated()
public CheckBox setPdfAConformanceLevel(PdfAConformanceLevel conformanceLevel) {
setProperty(FormProperty.FORM_CONFORMANCE_LEVEL, conformanceLevel);
return this;
}
+ /**
+ * Sets the conformance level for the checkbox.
+ *
+ * @param conformanceLevel The PDF/A conformance level to set.
+ *
+ * @return tThis checkbox instance.
+ */
+ public CheckBox setPdfConformanceLevel(IConformanceLevel conformanceLevel) {
+ setProperty(FormProperty.FORM_CONFORMANCE_LEVEL, conformanceLevel);
+ return this;
+ }
+
+
/**
* Sets the icon of the checkbox.
@@ -117,6 +135,21 @@ public CheckBox setSize(float size) {
return this;
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public AccessibilityProperties getAccessibilityProperties() {
+ if (tagProperties == null){
+ tagProperties = new FormDefaultAccessibilityProperties(FormDefaultAccessibilityProperties.FORM_FIELD_CHECK);
+ }
+ if (tagProperties instanceof FormDefaultAccessibilityProperties){
+ ((FormDefaultAccessibilityProperties)tagProperties).updateCheckedValue(this);
+ }
+
+ return tagProperties;
+ }
+
/* (non-Javadoc)
* @see com.itextpdf.layout.element.AbstractElement#makeNewRenderer()
*/
diff --git a/forms/src/main/java/com/itextpdf/forms/form/element/ComboBoxField.java b/forms/src/main/java/com/itextpdf/forms/form/element/ComboBoxField.java
index a957cb0d74..a520665ea4 100644
--- a/forms/src/main/java/com/itextpdf/forms/form/element/ComboBoxField.java
+++ b/forms/src/main/java/com/itextpdf/forms/form/element/ComboBoxField.java
@@ -23,10 +23,12 @@ This file is part of the iText (R) project.
package com.itextpdf.forms.form.element;
import com.itextpdf.commons.utils.MessageFormatUtil;
+import com.itextpdf.forms.FormDefaultAccessibilityProperties;
import com.itextpdf.forms.exceptions.FormsExceptionMessageConstant;
import com.itextpdf.forms.form.FormProperty;
import com.itextpdf.forms.form.renderer.SelectFieldComboBoxRenderer;
import com.itextpdf.forms.logs.FormsLogMessageConstants;
+import com.itextpdf.kernel.pdf.tagutils.AccessibilityProperties;
import com.itextpdf.layout.renderer.IRenderer;
import org.slf4j.Logger;
@@ -156,6 +158,18 @@ public SelectFieldItem getSelectedOption() {
return null;
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public AccessibilityProperties getAccessibilityProperties() {
+ if (tagProperties == null){
+ tagProperties = new FormDefaultAccessibilityProperties(
+ FormDefaultAccessibilityProperties.FORM_FIELD_LIST_BOX);
+ }
+ return tagProperties;
+ }
+
@Override
protected IRenderer makeNewRenderer() {
return new SelectFieldComboBoxRenderer(this);
diff --git a/forms/src/main/java/com/itextpdf/forms/form/element/FormField.java b/forms/src/main/java/com/itextpdf/forms/form/element/FormField.java
index b4f3ca5548..f8dab3914c 100644
--- a/forms/src/main/java/com/itextpdf/forms/form/element/FormField.java
+++ b/forms/src/main/java/com/itextpdf/forms/form/element/FormField.java
@@ -23,20 +23,27 @@ This file is part of the iText (R) project.
package com.itextpdf.forms.form.element;
import com.itextpdf.forms.form.FormProperty;
+import com.itextpdf.kernel.pdf.tagutils.DefaultAccessibilityProperties;
import com.itextpdf.layout.element.AbstractElement;
import com.itextpdf.layout.properties.Property;
import com.itextpdf.layout.properties.UnitValue;
+import com.itextpdf.layout.tagging.IAccessibleElement;
/**
* Implementation of the {@link AbstractElement} class for form fields.
*
* @param the generic type of the form field (e.g. input field, button, text area)
*/
-public abstract class FormField extends AbstractElement implements IFormField {
+public abstract class FormField extends AbstractElement implements IFormField,
+ IAccessibleElement {
/** The id. */
private final String id;
+
+ /** The tag properties. */
+ protected DefaultAccessibilityProperties tagProperties;
+
/**
* Instantiates a new {@link FormField} instance.
*
diff --git a/forms/src/main/java/com/itextpdf/forms/form/element/InputField.java b/forms/src/main/java/com/itextpdf/forms/form/element/InputField.java
index b5509f29ac..7b96c3ee2c 100644
--- a/forms/src/main/java/com/itextpdf/forms/form/element/InputField.java
+++ b/forms/src/main/java/com/itextpdf/forms/form/element/InputField.java
@@ -22,9 +22,11 @@ This file is part of the iText (R) project.
*/
package com.itextpdf.forms.form.element;
+import com.itextpdf.forms.FormDefaultAccessibilityProperties;
import com.itextpdf.forms.exceptions.FormsExceptionMessageConstant;
-import com.itextpdf.forms.form.renderer.InputFieldRenderer;
import com.itextpdf.forms.form.FormProperty;
+import com.itextpdf.forms.form.renderer.InputFieldRenderer;
+import com.itextpdf.kernel.pdf.tagutils.AccessibilityProperties;
import com.itextpdf.layout.element.Paragraph;
import com.itextpdf.layout.properties.BoxSizingPropertyValue;
import com.itextpdf.layout.properties.Property;
@@ -97,8 +99,8 @@ public void setPlaceholder(Paragraph placeholder) {
}
/* (non-Javadoc)
- * @see FormField#getDefaultProperty(int)
- */
+ * @see FormField#getDefaultProperty(int)
+ */
@Override
public T1 getDefaultProperty(int property) {
switch (property) {
@@ -111,11 +113,21 @@ public T1 getDefaultProperty(int property) {
}
}
+ /**
+ * Get rotation.
+ *
+ * @return rotation value.
+ */
+ public int getRotation() {
+ return this.rotation;
+ }
+
/**
* Set rotation of the input field.
*
* @param rotation new rotation value, counterclockwise. Must be a multiple of 90 degrees.
- * It has sense only in interactive mode, see {@link FormField#setInteractive}.
+ * It has sense only in interactive mode, see {@link FormField#setInteractive}.
+ *
* @return the edited {@link InputField}.
*/
public InputField setRotation(int rotation) {
@@ -128,12 +140,15 @@ public InputField setRotation(int rotation) {
}
/**
- * Get rotation.
- *
- * @return rotation value.
+ * {@inheritDoc}
*/
- public int getRotation() {
- return this.rotation;
+ @Override
+ public AccessibilityProperties getAccessibilityProperties() {
+ if (tagProperties == null) {
+ tagProperties = new FormDefaultAccessibilityProperties(FormDefaultAccessibilityProperties.FORM_FIELD_TEXT);
+ }
+
+ return tagProperties;
}
/* (non-Javadoc)
diff --git a/forms/src/main/java/com/itextpdf/forms/form/element/ListBoxField.java b/forms/src/main/java/com/itextpdf/forms/form/element/ListBoxField.java
index 5fc7738c65..3d5c95f779 100644
--- a/forms/src/main/java/com/itextpdf/forms/form/element/ListBoxField.java
+++ b/forms/src/main/java/com/itextpdf/forms/form/element/ListBoxField.java
@@ -22,10 +22,10 @@ This file is part of the iText (R) project.
*/
package com.itextpdf.forms.form.element;
+import com.itextpdf.forms.FormDefaultAccessibilityProperties;
import com.itextpdf.forms.form.FormProperty;
import com.itextpdf.forms.form.renderer.SelectFieldListBoxRenderer;
-import com.itextpdf.kernel.pdf.navigation.PdfDestination;
-import com.itextpdf.layout.element.IBlockElement;
+import com.itextpdf.kernel.pdf.tagutils.AccessibilityProperties;
import com.itextpdf.layout.properties.BoxSizingPropertyValue;
import com.itextpdf.layout.properties.OverflowPropertyValue;
import com.itextpdf.layout.properties.Property;
@@ -34,7 +34,6 @@ This file is part of the iText (R) project.
import java.util.ArrayList;
import java.util.List;
-import java.util.function.Consumer;
/**
* A field that represents a control for selecting one or several of the provided options.
@@ -120,6 +119,18 @@ public List getSelectedStrings() {
return selectedStrings;
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public AccessibilityProperties getAccessibilityProperties() {
+ if (tagProperties == null) {
+ tagProperties = new FormDefaultAccessibilityProperties(
+ FormDefaultAccessibilityProperties.FORM_FIELD_LIST_BOX);
+ }
+ return tagProperties;
+ }
+
@Override
protected IRenderer makeNewRenderer() {
return new SelectFieldListBoxRenderer(this);
diff --git a/forms/src/main/java/com/itextpdf/forms/form/element/Radio.java b/forms/src/main/java/com/itextpdf/forms/form/element/Radio.java
index 79b44ad591..8ace905106 100644
--- a/forms/src/main/java/com/itextpdf/forms/form/element/Radio.java
+++ b/forms/src/main/java/com/itextpdf/forms/form/element/Radio.java
@@ -22,8 +22,10 @@ This file is part of the iText (R) project.
*/
package com.itextpdf.forms.form.element;
+import com.itextpdf.forms.FormDefaultAccessibilityProperties;
import com.itextpdf.forms.form.FormProperty;
import com.itextpdf.forms.form.renderer.RadioRenderer;
+import com.itextpdf.kernel.pdf.tagutils.AccessibilityProperties;
import com.itextpdf.layout.properties.BorderRadius;
import com.itextpdf.layout.properties.BoxSizingPropertyValue;
import com.itextpdf.layout.properties.Property;
@@ -88,6 +90,20 @@ public T1 getProperty(int property) {
return super.getProperty(property);
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public AccessibilityProperties getAccessibilityProperties() {
+ if (tagProperties == null){
+ tagProperties = new FormDefaultAccessibilityProperties(FormDefaultAccessibilityProperties.FORM_FIELD_RADIO);
+ }
+ if (tagProperties instanceof FormDefaultAccessibilityProperties){
+ ((FormDefaultAccessibilityProperties)tagProperties).updateCheckedValue(this);
+ }
+ return tagProperties;
+ }
+
/* (non-Javadoc)
* @see com.itextpdf.layout.element.AbstractElement#makeNewRenderer()
*/
diff --git a/forms/src/main/java/com/itextpdf/forms/form/element/SignatureFieldAppearance.java b/forms/src/main/java/com/itextpdf/forms/form/element/SignatureFieldAppearance.java
index 9e579462b2..866007a372 100644
--- a/forms/src/main/java/com/itextpdf/forms/form/element/SignatureFieldAppearance.java
+++ b/forms/src/main/java/com/itextpdf/forms/form/element/SignatureFieldAppearance.java
@@ -22,9 +22,11 @@ This file is part of the iText (R) project.
*/
package com.itextpdf.forms.form.element;
+import com.itextpdf.forms.FormDefaultAccessibilityProperties;
import com.itextpdf.forms.fields.properties.SignedAppearanceText;
import com.itextpdf.forms.form.renderer.SignatureAppearanceRenderer;
import com.itextpdf.io.image.ImageData;
+import com.itextpdf.kernel.pdf.tagutils.AccessibilityProperties;
import com.itextpdf.layout.element.Div;
import com.itextpdf.layout.element.IElement;
import com.itextpdf.layout.element.Image;
@@ -234,6 +236,17 @@ public String getId() {
return idWithDots == null? super.getId() : idWithDots;
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public AccessibilityProperties getAccessibilityProperties() {
+ if (tagProperties == null){
+ tagProperties = new FormDefaultAccessibilityProperties(FormDefaultAccessibilityProperties.FORM_FIELD_TEXT);
+ }
+ return tagProperties;
+ }
+
/**
* {@inheritDoc}
*
diff --git a/forms/src/main/java/com/itextpdf/forms/form/element/TextArea.java b/forms/src/main/java/com/itextpdf/forms/form/element/TextArea.java
index 1c2bb45637..908d8a9649 100644
--- a/forms/src/main/java/com/itextpdf/forms/form/element/TextArea.java
+++ b/forms/src/main/java/com/itextpdf/forms/form/element/TextArea.java
@@ -22,8 +22,10 @@ This file is part of the iText (R) project.
*/
package com.itextpdf.forms.form.element;
-import com.itextpdf.forms.form.renderer.TextAreaRenderer;
+import com.itextpdf.forms.FormDefaultAccessibilityProperties;
import com.itextpdf.forms.form.FormProperty;
+import com.itextpdf.forms.form.renderer.TextAreaRenderer;
+import com.itextpdf.kernel.pdf.tagutils.AccessibilityProperties;
import com.itextpdf.layout.element.Paragraph;
import com.itextpdf.layout.properties.BoxSizingPropertyValue;
import com.itextpdf.layout.properties.Leading;
@@ -106,6 +108,17 @@ public void setPlaceholder(Paragraph placeholder) {
this.placeholder = placeholder;
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public AccessibilityProperties getAccessibilityProperties() {
+ if (this.tagProperties == null){
+ tagProperties = new FormDefaultAccessibilityProperties(FormDefaultAccessibilityProperties.FORM_FIELD_TEXT);
+ }
+ return tagProperties;
+ }
+
/* (non-Javadoc)
* @see com.itextpdf.layout.element.AbstractElement#makeNewRenderer()
*/
diff --git a/forms/src/main/java/com/itextpdf/forms/form/renderer/AbstractFormFieldRenderer.java b/forms/src/main/java/com/itextpdf/forms/form/renderer/AbstractFormFieldRenderer.java
index 0c957cbe1f..822ad7c979 100644
--- a/forms/src/main/java/com/itextpdf/forms/form/renderer/AbstractFormFieldRenderer.java
+++ b/forms/src/main/java/com/itextpdf/forms/form/renderer/AbstractFormFieldRenderer.java
@@ -22,12 +22,15 @@ This file is part of the iText (R) project.
*/
package com.itextpdf.forms.form.renderer;
+import com.itextpdf.forms.fields.PdfFormField;
import com.itextpdf.forms.form.FormProperty;
import com.itextpdf.forms.form.element.IFormField;
import com.itextpdf.forms.logs.FormsLogMessageConstants;
import com.itextpdf.kernel.geom.Rectangle;
+import com.itextpdf.kernel.pdf.IConformanceLevel;
import com.itextpdf.kernel.pdf.PdfAConformanceLevel;
import com.itextpdf.kernel.pdf.PdfDocument;
+import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
import com.itextpdf.kernel.pdf.tagging.StandardRoles;
import com.itextpdf.kernel.pdf.tagutils.AccessibilityProperties;
import com.itextpdf.kernel.pdf.tagutils.TagTreePointer;
@@ -73,7 +76,7 @@ public abstract class AbstractFormFieldRenderer extends BlockRenderer {
/**
* Checks if form fields need to be flattened.
*
- * @return true, if fields need to be flattened
+ * @return true, if fields need to be flattened.
*/
public boolean isFlatten() {
if (parent != null) {
@@ -96,7 +99,7 @@ public boolean isFlatten() {
/**
* Gets the default value of the form field.
*
- * @return the default value of the form field
+ * @return the default value of the form field.
*/
public String getDefaultValue() {
String defaultValue = this.getProperty(FormProperty.FORM_FIELD_VALUE);
@@ -183,6 +186,19 @@ public void draw(DrawContext drawContext) {
}
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public MinMaxWidth getMinMaxWidth() {
+ childRenderers.clear();
+ flatRenderer = null;
+ IRenderer renderer = createFlatRenderer();
+ addChild(renderer);
+ MinMaxWidth minMaxWidth = super.getMinMaxWidth();
+ return minMaxWidth;
+ }
+
/**
* {@inheritDoc}
*/
@@ -191,29 +207,38 @@ public void drawChildren(DrawContext drawContext) {
drawContext.getCanvas().saveState();
boolean flatten = isFlatten();
if (flatten) {
- drawContext.getCanvas().rectangle(applyBorderBox(occupiedArea.getBBox(), false)).clip().endPath();
+ PdfCanvas canvas = drawContext.getCanvas();
+ canvas.rectangle(applyBorderBox(occupiedArea.getBBox(), false)).clip().endPath();
flatRenderer.draw(drawContext);
} else {
applyAcroField(drawContext);
+ writeAcroFormFieldLangAttribute(drawContext.getDocument());
}
drawContext.getCanvas().restoreState();
}
/**
- * {@inheritDoc}
+ * Applies the accessibility properties to the form field.
+ *
+ * @param formField The form field to which the accessibility properties should be applied.
+ * @param pdfDocument The document to which the form field belongs.
*/
- @Override
- public MinMaxWidth getMinMaxWidth() {
- childRenderers.clear();
- flatRenderer = null;
- IRenderer renderer = createFlatRenderer();
- addChild(renderer);
- MinMaxWidth minMaxWidth = super.getMinMaxWidth();
- return minMaxWidth;
+ protected void applyAccessibilityProperties(PdfFormField formField, PdfDocument pdfDocument) {
+ if (!pdfDocument.isTagged()) {
+ return;
+ }
+ final AccessibilityProperties properties = ((IAccessibleElement) this.modelElement)
+ .getAccessibilityProperties();
+ final String alternativeDescription = properties.getAlternateDescription();
+ if (alternativeDescription != null && !alternativeDescription.isEmpty()) {
+ formField.setAlternativeName(alternativeDescription);
+ }
}
+
/**
* Adjusts the field layout.
+ *
* @param layoutContext layout context
*/
protected abstract void adjustFieldLayout(LayoutContext layoutContext);
@@ -221,7 +246,7 @@ public MinMaxWidth getMinMaxWidth() {
/**
* Creates the flat renderer instance.
*
- * @return the renderer instance
+ * @return the renderer instance.
*/
protected abstract IRenderer createFlatRenderer();
@@ -235,7 +260,7 @@ public MinMaxWidth getMinMaxWidth() {
/**
* Gets the model id.
*
- * @return the model id
+ * @return the model id.
*/
protected String getModelId() {
return ((IFormField) getModelElement()).getId();
@@ -246,7 +271,8 @@ protected String getModelId() {
*
* @param availableWidth the available width
* @param availableHeight the available height
- * @return true, if the renderer fits
+ *
+ * @return true, if the renderer fits.
*/
protected boolean isRendererFit(float availableWidth, float availableHeight) {
if (occupiedArea == null) {
@@ -261,22 +287,48 @@ protected boolean isRendererFit(float availableWidth, float availableHeight) {
/**
* Gets the accessibility language.
*
- * @return the accessibility language
+ * @return the accessibility language.
+ * @deprecated use {@link IAccessibleElement#getAccessibilityProperties()} instead
*/
+ @Deprecated()
protected String getLang() {
- return this.getProperty(FormProperty.FORM_ACCESSIBILITY_LANGUAGE);
+ String language = null;
+ if (this.getModelElement() instanceof IAccessibleElement) {
+ language = ((IAccessibleElement) this.getModelElement()).getAccessibilityProperties().getLanguage();
+ }
+ if (language == null) {
+ language = this.getProperty(FormProperty.FORM_ACCESSIBILITY_LANGUAGE);
+ }
+ return language;
}
-
/**
* Gets the conformance level. If the conformance level is not set, the conformance level of the document is used.
*
* @param document the document
*
* @return the conformance level or null if the conformance level is not set.
+ *
+ * @deprecated since 8.0.4 will return {@link IConformanceLevel}
*/
+ @Deprecated
protected PdfAConformanceLevel getConformanceLevel(PdfDocument document) {
- final PdfAConformanceLevel conformanceLevel = this.getProperty(
+ return PdfAConformanceLevel.getPDFAConformance(this.getProperty(
+ FormProperty.FORM_CONFORMANCE_LEVEL), document);
+ }
+
+ /**
+ * Gets the conformance level. If the conformance level is not set, the conformance level of the document is used.
+ *
+ * @param document the document
+ *
+ * @return the conformance level or null if the conformance level is not set.
+ *
+ * @deprecated since 8.0.4 will be renamed to getConformanceLevel()
+ */
+ @Deprecated
+ protected IConformanceLevel getGenericConformanceLevel(PdfDocument document) {
+ final IConformanceLevel conformanceLevel = this.getProperty(
FormProperty.FORM_CONFORMANCE_LEVEL);
if (conformanceLevel != null) {
return conformanceLevel;
@@ -284,21 +336,23 @@ protected PdfAConformanceLevel getConformanceLevel(PdfDocument document) {
if (document == null) {
return null;
}
- if (document.getConformanceLevel() instanceof PdfAConformanceLevel) {
- return (PdfAConformanceLevel) document.getConformanceLevel();
- }
- return null;
+ return document.getConformanceLevel();
}
/**
* Determines, whether the layout is based in the renderer itself or flat renderer.
- *
- * @return {@code true} if layout is based on flat renderer, false otherwise
+ *
+ * @return {@code true} if layout is based on flat renderer, false otherwise.
*/
protected boolean isLayoutBasedOnFlatRenderer() {
return true;
}
+ /**
+ * Sets the form accessibility language identifier of the form element in case the document is tagged.
+ *
+ * @param pdfDoc the document which contains form field
+ */
protected void writeAcroFormFieldLangAttribute(PdfDocument pdfDoc) {
if (pdfDoc.isTagged()) {
TagTreePointer formParentPointer = pdfDoc.getTagStructureContext().getAutoTaggingPointer();
@@ -313,51 +367,16 @@ protected void writeAcroFormFieldLangAttribute(PdfDocument pdfDoc) {
}
}
- /**
- * Deletes all margin properties. Used in {@code applyAcroField} to not apply margins twice as we already use area
- * with margins applied (margins shouldn't be an interactive part of the field, i.e. included into its occupied
- * area).
- *
- * @return the map of deleted margins
- */
- Map deleteMargins() {
-
- final Map margins = new HashMap<>();
- margins.put(Property.MARGIN_TOP, this.modelElement.getOwnProperty(Property.MARGIN_TOP));
- margins.put(Property.MARGIN_BOTTOM, this.modelElement.getOwnProperty(Property.MARGIN_BOTTOM));
- margins.put(Property.MARGIN_LEFT, this.modelElement.getOwnProperty(Property.MARGIN_LEFT));
- margins.put(Property.MARGIN_RIGHT, this.modelElement.getOwnProperty(Property.MARGIN_RIGHT));
-
- modelElement.deleteOwnProperty(Property.MARGIN_RIGHT);
- modelElement.deleteOwnProperty(Property.MARGIN_LEFT);
- modelElement.deleteOwnProperty(Property.MARGIN_TOP);
- modelElement.deleteOwnProperty(Property.MARGIN_BOTTOM);
- return margins;
- }
-
- /**
- * Applies the properties to the model element.
- *
- * @param properties the properties to apply
- */
- void applyProperties(Map properties) {
- for (Entry integerObjectEntry : properties.entrySet()) {
- if (integerObjectEntry.getValue() != null) {
- modelElement.setProperty(integerObjectEntry.getKey(), integerObjectEntry.getValue());
- } else {
- modelElement.deleteOwnProperty(integerObjectEntry.getKey());
- }
- }
- }
private void processLangAttribute() {
- IPropertyContainer propertyContainer = flatRenderer.getModelElement();
- String lang = getLang();
- if (propertyContainer instanceof IAccessibleElement && lang != null) {
- AccessibilityProperties properties = ((IAccessibleElement) propertyContainer).getAccessibilityProperties();
- if (properties.getLanguage() == null) {
- properties.setLanguage(lang);
- }
- }
+ IPropertyContainer propertyContainer = flatRenderer.getModelElement();
+ String lang = getLang();
+ if (propertyContainer instanceof IAccessibleElement && lang != null) {
+ AccessibilityProperties properties = ((IAccessibleElement) propertyContainer)
+ .getAccessibilityProperties();
+ if (properties.getLanguage() == null) {
+ properties.setLanguage(lang);
+ }
+ }
}
}
diff --git a/forms/src/main/java/com/itextpdf/forms/form/renderer/AbstractSelectFieldRenderer.java b/forms/src/main/java/com/itextpdf/forms/form/renderer/AbstractSelectFieldRenderer.java
index b251786e1f..2dc3b80719 100644
--- a/forms/src/main/java/com/itextpdf/forms/form/renderer/AbstractSelectFieldRenderer.java
+++ b/forms/src/main/java/com/itextpdf/forms/form/renderer/AbstractSelectFieldRenderer.java
@@ -23,14 +23,17 @@ This file is part of the iText (R) project.
package com.itextpdf.forms.form.renderer;
import com.itextpdf.forms.fields.ChoiceFormFieldBuilder;
+import com.itextpdf.forms.fields.PdfFormField;
import com.itextpdf.forms.form.FormProperty;
import com.itextpdf.forms.form.element.AbstractSelectField;
import com.itextpdf.forms.form.element.IFormField;
import com.itextpdf.forms.form.element.SelectFieldItem;
import com.itextpdf.kernel.geom.Rectangle;
+import com.itextpdf.kernel.pdf.IConformanceLevel;
import com.itextpdf.kernel.pdf.PdfAConformanceLevel;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.tagging.StandardRoles;
+import com.itextpdf.kernel.pdf.tagutils.AccessibilityProperties;
import com.itextpdf.kernel.pdf.tagutils.TagTreePointer;
import com.itextpdf.layout.layout.LayoutArea;
import com.itextpdf.layout.layout.LayoutContext;
@@ -40,6 +43,7 @@ This file is part of the iText (R) project.
import com.itextpdf.layout.renderer.BlockRenderer;
import com.itextpdf.layout.renderer.DrawContext;
import com.itextpdf.layout.renderer.IRenderer;
+import com.itextpdf.layout.tagging.IAccessibleElement;
import java.util.ArrayList;
import java.util.List;
@@ -59,6 +63,9 @@ protected AbstractSelectFieldRenderer(AbstractSelectField modelElement) {
addChild(createFlatRenderer());
}
+ /**
+ * {@inheritDoc}
+ */
@Override
public LayoutResult layout(LayoutContext layoutContext) {
// Resolve width here in case it's relative, while parent width is still intact.
@@ -77,7 +84,10 @@ public LayoutResult layout(LayoutContext layoutContext) {
final boolean isForcedPlacement = Boolean.TRUE.equals(getPropertyAsBoolean(Property.FORCED_PLACEMENT));
LayoutResult layoutResult = super.layout(new LayoutContext(area, layoutContext.getMarginsCollapseInfo(),
layoutContext.getFloatRendererAreas(), layoutContext.isClippedHeight()));
-
+ if (isForcedPlacement){
+ // Restore the Property.FORCED_PLACEMENT value as it was before super.layout
+ setProperty(Property.FORCED_PLACEMENT, true);
+ }
if (layoutResult.getStatus() != LayoutResult.FULL) {
if (isForcedPlacement) {
layoutResult = makeLayoutResultFull(layoutContext.getArea(), layoutResult);
@@ -124,24 +134,40 @@ public void draw(DrawContext drawContext) {
}
}
+ /**
+ * {@inheritDoc}
+ */
@Override
public void drawChildren(DrawContext drawContext) {
if (isFlatten()) {
super.drawChildren(drawContext);
} else {
applyAcroField(drawContext);
+ writeAcroFormFieldLangAttribute(drawContext.getDocument());
}
}
/**
* Gets the accessibility language.
*
- * @return the accessibility language
+ * @return the accessibility language.
*/
protected String getLang() {
- return this.getProperty(FormProperty.FORM_ACCESSIBILITY_LANGUAGE);
+ String language = null;
+ if (this.getModelElement() instanceof IAccessibleElement) {
+ language = ((IAccessibleElement) this.getModelElement()).getAccessibilityProperties().getLanguage();
+ }
+ if (language == null){
+ language = this.getProperty(FormProperty.FORM_ACCESSIBILITY_LANGUAGE);
+ }
+ return language;
}
+ /**
+ * Sets the form accessibility language identifier of the form element in case the document is tagged.
+ *
+ * @param pdfDoc the document which contains form field.
+ */
protected void writeAcroFormFieldLangAttribute(PdfDocument pdfDoc) {
if (pdfDoc.isTagged()) {
TagTreePointer formParentPointer = pdfDoc.getTagStructureContext().getAutoTaggingPointer();
@@ -156,14 +182,42 @@ protected void writeAcroFormFieldLangAttribute(PdfDocument pdfDoc) {
}
}
+ /**
+ * Applies the accessibility properties to the form field.
+ *
+ * @param formField The form field to which the accessibility properties should be applied.
+ * @param pdfDocument The document to which the form field belongs.
+ */
+ protected void applyAccessibilityProperties(PdfFormField formField, PdfDocument pdfDocument) {
+ if (!pdfDocument.isTagged()) {
+ return;
+ }
+ final AccessibilityProperties properties = ((IAccessibleElement) this.modelElement)
+ .getAccessibilityProperties();
+ final String alternativeDescription = properties.getAlternateDescription();
+ if (alternativeDescription != null && !alternativeDescription.isEmpty()) {
+ formField.setAlternativeName(alternativeDescription);
+ }
+ }
+
+ /**
+ * Creates the flat renderer instance.
+ *
+ * @return {@link IRenderer} instance.
+ */
protected abstract IRenderer createFlatRenderer();
+ /**
+ * Applies the AcroField widget.
+ *
+ * @param drawContext the draw context
+ */
protected abstract void applyAcroField(DrawContext drawContext);
/**
* Checks if form fields need to be flattened.
*
- * @return true, if fields need to be flattened
+ * @return true, if fields need to be flattened.
*/
protected boolean isFlatten() {
return (boolean) getPropertyAsBoolean(FormProperty.FORM_FIELD_FLATTEN);
@@ -172,7 +226,7 @@ protected boolean isFlatten() {
/**
* Gets the model id.
*
- * @return the model id
+ * @return the model id.
*/
protected String getModelId() {
return ((IFormField) getModelElement()).getId();
@@ -182,8 +236,8 @@ protected String getModelId() {
* Retrieve the options from select field (can be combo box or list box field) and set them
* to the form field builder.
*
- * @param builder {@link ChoiceFormFieldBuilder} to set options to.
- * @param field {@link AbstractSelectField} to retrieve the options from.
+ * @param builder {@link ChoiceFormFieldBuilder} to set options to
+ * @param field {@link AbstractSelectField} to retrieve the options from
*/
protected void setupBuilderValues(ChoiceFormFieldBuilder builder, AbstractSelectField field) {
List options = field.getItems();
@@ -215,6 +269,15 @@ protected void setupBuilderValues(ChoiceFormFieldBuilder builder, AbstractSelect
}
}
+ /**
+ * Returns final height of the select field.
+ *
+ * @param availableHeight available height of the layout area
+ * @param actualHeight actual occupied height of the select field
+ * @param isClippedHeight indicates whether the layout area's height is clipped or not
+ *
+ * @return final height of the select field.
+ */
protected float getFinalSelectFieldHeight(float availableHeight, float actualHeight, boolean isClippedHeight) {
boolean isForcedPlacement = Boolean.TRUE.equals(getPropertyAsBoolean(Property.FORCED_PLACEMENT));
if (!isClippedHeight && actualHeight > availableHeight) {
@@ -232,9 +295,26 @@ protected float getFinalSelectFieldHeight(float availableHeight, float actualHei
* @param document the document
*
* @return the conformance level or null if the conformance level is not set.
+ * @deprecated since 8.0.4 will be return {@link IConformanceLevel}
*/
+ @Deprecated
protected PdfAConformanceLevel getConformanceLevel(PdfDocument document) {
- final PdfAConformanceLevel conformanceLevel = this.getProperty(
+ return PdfAConformanceLevel.getPDFAConformance(this.getProperty(
+ FormProperty.FORM_CONFORMANCE_LEVEL),document);
+ }
+
+ /**
+ * Gets the conformance level. If the conformance level is not set, the conformance level of the document is used.
+ *
+ * @param document the document
+ *
+ * @return the conformance level or null if the conformance level is not set.
+ *
+ * @deprecated since 8.0.4 will be renamed to getConformanceLevel()
+ */
+ @Deprecated
+ protected IConformanceLevel getGenericConformanceLevel(PdfDocument document) {
+ final IConformanceLevel conformanceLevel = this.getProperty(
FormProperty.FORM_CONFORMANCE_LEVEL);
if (conformanceLevel != null) {
return conformanceLevel;
@@ -242,12 +322,17 @@ protected PdfAConformanceLevel getConformanceLevel(PdfDocument document) {
if (document == null) {
return null;
}
- if (document.getConformanceLevel() instanceof PdfAConformanceLevel) {
- return (PdfAConformanceLevel) document.getConformanceLevel();
- }
- return null;
+ return document.getConformanceLevel();
}
+
+ /**
+ * Gets options that are marked as selected from the select field options subtree.
+ *
+ * @param optionsSubTree options subtree to get selected options
+ *
+ * @return selected options list.
+ */
protected List getOptionsMarkedSelected(IRenderer optionsSubTree) {
List selectedOptions = new ArrayList<>();
for (IRenderer option : optionsSubTree.getChildRenderers()) {
diff --git a/forms/src/main/java/com/itextpdf/forms/form/renderer/ButtonRenderer.java b/forms/src/main/java/com/itextpdf/forms/form/renderer/ButtonRenderer.java
index 9d10c4ec81..3076d79964 100644
--- a/forms/src/main/java/com/itextpdf/forms/form/renderer/ButtonRenderer.java
+++ b/forms/src/main/java/com/itextpdf/forms/form/renderer/ButtonRenderer.java
@@ -30,6 +30,7 @@ This file is part of the iText (R) project.
import com.itextpdf.forms.form.FormProperty;
import com.itextpdf.forms.form.element.Button;
import com.itextpdf.forms.logs.FormsLogMessageConstants;
+import com.itextpdf.forms.util.FormFieldRendererUtil;
import com.itextpdf.io.logs.IoLogMessageConstant;
import com.itextpdf.kernel.colors.Color;
import com.itextpdf.kernel.geom.Rectangle;
@@ -251,7 +252,7 @@ protected void applyAcroField(DrawContext drawContext) {
PdfDocument doc = drawContext.getDocument();
Rectangle area = getOccupiedArea().getBBox().clone();
applyMargins(area, false);
- final Map margins = deleteMargins();
+ final Map properties = FormFieldRendererUtil.removeProperties(modelElement);
PdfPage page = doc.getPage(occupiedArea.getPageNumber());
Background background = this.getProperty(Property.BACKGROUND);
@@ -271,12 +272,13 @@ protected void applyAcroField(DrawContext drawContext) {
modelElement.setProperty(Property.RENDERING_MODE, this.getProperty(Property.RENDERING_MODE));
final PdfButtonFormField button = new PushButtonFormFieldBuilder(doc, name).setWidgetRectangle(area)
.setFont(font)
- .setConformanceLevel(getConformanceLevel(doc))
+ .setGenericConformanceLevel(getGenericConformanceLevel(doc))
.createPushButton();
button.disableFieldRegeneration();
button.setFontSize(fontSizeValue);
button.getFirstFormAnnotation().setBackgroundColor(backgroundColor);
applyDefaultFieldProperties(button);
+ applyAccessibilityProperties(button,doc);
button.getFirstFormAnnotation().setFormFieldElement((Button) modelElement);
button.enableFieldRegeneration();
PdfAcroForm forms = PdfFormCreator.getAcroForm(doc, true);
@@ -284,8 +286,7 @@ protected void applyAcroField(DrawContext drawContext) {
// with the same names (and add all the widgets as kids to that merged field), so we can add it anyway.
forms.addField(button, page);
- writeAcroFormFieldLangAttribute(doc);
- applyProperties(margins);
+ FormFieldRendererUtil.reapplyProperties(modelElement, properties);
}
/**
diff --git a/forms/src/main/java/com/itextpdf/forms/form/renderer/CheckBoxRenderer.java b/forms/src/main/java/com/itextpdf/forms/form/renderer/CheckBoxRenderer.java
index 3b0dd34a86..05381253ea 100644
--- a/forms/src/main/java/com/itextpdf/forms/form/renderer/CheckBoxRenderer.java
+++ b/forms/src/main/java/com/itextpdf/forms/form/renderer/CheckBoxRenderer.java
@@ -34,10 +34,14 @@ This file is part of the iText (R) project.
import com.itextpdf.forms.form.renderer.checkboximpl.PdfACheckBoxRenderingStrategy;
import com.itextpdf.forms.form.renderer.checkboximpl.PdfCheckBoxRenderingStrategy;
import com.itextpdf.forms.util.BorderStyleUtil;
+import com.itextpdf.forms.util.FormFieldRendererUtil;
import com.itextpdf.kernel.geom.Rectangle;
+import com.itextpdf.kernel.pdf.IConformanceLevel;
import com.itextpdf.kernel.pdf.PdfAConformanceLevel;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfPage;
+import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
+import com.itextpdf.kernel.pdf.tagutils.TagTreePointer;
import com.itextpdf.layout.borders.Border;
import com.itextpdf.layout.element.Paragraph;
import com.itextpdf.layout.layout.LayoutContext;
@@ -103,9 +107,13 @@ public RenderingMode getRenderingMode() {
* Returns whether or not the checkbox is in PDF/A mode.
*
* @return true if the checkbox is in PDF/A mode, false otherwise
+ *
+ * @deprecated since 8.0.4 will be removed
*/
+ @Deprecated
public boolean isPdfA() {
- return this.getProperty(FormProperty.FORM_CONFORMANCE_LEVEL) != null;
+ IConformanceLevel conformanceLevel = this.getProperty(FormProperty.FORM_CONFORMANCE_LEVEL);
+ return conformanceLevel instanceof PdfAConformanceLevel;
}
/**
@@ -129,9 +137,11 @@ public CheckBoxType getCheckBoxType() {
public ICheckBoxRenderingStrategy createCheckBoxRenderStrategy() {
// html rendering is PDFA compliant this means we don't have to check if its PDFA.
ICheckBoxRenderingStrategy renderingStrategy;
+ boolean isConformantPdfDocument =
+ this.getProperty(FormProperty.FORM_CONFORMANCE_LEVEL) != null;
if (getRenderingMode() == RenderingMode.HTML_MODE) {
renderingStrategy = new HtmlCheckBoxRenderingStrategy();
- } else if (getRenderingMode() == RenderingMode.DEFAULT_LAYOUT_MODE && isPdfA()) {
+ } else if (getRenderingMode() == RenderingMode.DEFAULT_LAYOUT_MODE && isConformantPdfDocument) {
renderingStrategy = new PdfACheckBoxRenderingStrategy();
} else {
renderingStrategy = new PdfCheckBoxRenderingStrategy();
@@ -183,6 +193,24 @@ protected void adjustFieldLayout(LayoutContext layoutContext) {
// We don't need any layout adjustments
}
+ /**
+ * Applies given paddings to the given rectangle.
+ *
+ * Checkboxes don't support setting of paddings as they are always centered.
+ * So that this method returns the rectangle as is.
+ *
+ * @param rect a rectangle paddings will be applied on.
+ * @param paddings the paddings to be applied on the given rectangle
+ * @param reverse indicates whether paddings will be applied
+ * inside (in case of false) or outside (in case of true) the rectangle.
+ *
+ * @return The rectangle NOT modified by the paddings.
+ */
+ @Override
+ protected Rectangle applyPaddings(Rectangle rect, UnitValue[] paddings, boolean reverse) {
+ return rect;
+ }
+
/**
* Creates a flat renderer for the checkbox.
*
@@ -233,10 +261,10 @@ protected void applyAcroField(DrawContext drawContext) {
final PdfDocument doc = drawContext.getDocument();
final Rectangle area = flatRenderer.getOccupiedArea().getBBox().clone();
- final Map margins = deleteMargins();
+ final Map properties = FormFieldRendererUtil.removeProperties(this.modelElement);
final PdfPage page = doc.getPage(occupiedArea.getPageNumber());
final CheckBoxFormFieldBuilder builder = new CheckBoxFormFieldBuilder(doc, name).setWidgetRectangle(area)
- .setConformanceLevel(this.getProperty(FormProperty.FORM_CONFORMANCE_LEVEL));
+ .setGenericConformanceLevel(this.getProperty(FormProperty.FORM_CONFORMANCE_LEVEL));
if (this.hasProperty(FormProperty.FORM_CHECKBOX_TYPE)) {
builder.setCheckType((CheckBoxType) this.getProperty(FormProperty.FORM_CHECKBOX_TYPE));
@@ -252,12 +280,13 @@ protected void applyAcroField(DrawContext drawContext) {
if (!isBoxChecked()) {
checkBox.setValue(PdfFormAnnotation.OFF_STATE_VALUE);
}
+
+ applyAccessibilityProperties(checkBox, doc);
checkBox.getFirstFormAnnotation().setFormFieldElement((CheckBox) modelElement);
checkBox.enableFieldRegeneration();
PdfFormCreator.getAcroForm(doc, true).addField(checkBox, page);
- writeAcroFormFieldLangAttribute(doc);
- applyProperties(margins);
+ FormFieldRendererUtil.reapplyProperties(modelElement, properties);
}
/**
@@ -288,7 +317,17 @@ public FlatParagraphRenderer(Paragraph modelElement) {
@Override
public void drawChildren(DrawContext drawContext) {
final Rectangle rectangle = this.getInnerAreaBBox().clone();
+ PdfCanvas canvas = drawContext.getCanvas();
+ boolean isTaggingEnabled = drawContext.isTaggingEnabled();
+ if (isTaggingEnabled) {
+ TagTreePointer tp = drawContext.getDocument().getTagStructureContext().getAutoTaggingPointer();
+ canvas.openTag(tp.getTagReference());
+ }
createCheckBoxRenderStrategy().drawCheckBoxContent(drawContext, CheckBoxRenderer.this, rectangle);
+ if (isTaggingEnabled) {
+ canvas.closeTag();
+ }
+
}
}
diff --git a/forms/src/main/java/com/itextpdf/forms/form/renderer/InputFieldRenderer.java b/forms/src/main/java/com/itextpdf/forms/form/renderer/InputFieldRenderer.java
index c4109aa04a..89a4ca4bb5 100644
--- a/forms/src/main/java/com/itextpdf/forms/form/renderer/InputFieldRenderer.java
+++ b/forms/src/main/java/com/itextpdf/forms/form/renderer/InputFieldRenderer.java
@@ -29,6 +29,7 @@ This file is part of the iText (R) project.
import com.itextpdf.forms.form.FormProperty;
import com.itextpdf.forms.form.element.InputField;
import com.itextpdf.forms.logs.FormsLogMessageConstants;
+import com.itextpdf.forms.util.FormFieldRendererUtil;
import com.itextpdf.io.logs.IoLogMessageConstant;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.PdfDocument;
@@ -163,7 +164,7 @@ protected void applyAcroField(DrawContext drawContext) {
final PdfDocument doc = drawContext.getDocument();
final Rectangle area = this.getOccupiedArea().getBBox().clone();
applyMargins(area, false);
- final Map margins = deleteMargins();
+ final Map properties = FormFieldRendererUtil.removeProperties(this.modelElement);
final PdfPage page = doc.getPage(occupiedArea.getPageNumber());
final float fontSizeValue = fontSize.getValue();
@@ -177,7 +178,7 @@ protected void applyAcroField(DrawContext drawContext) {
modelElement.setProperty(Property.BOX_SIZING, BoxSizingPropertyValue.BORDER_BOX);
final PdfFormField inputField = new TextFormFieldBuilder(doc, name).setWidgetRectangle(area)
.setFont(font)
- .setConformanceLevel(getConformanceLevel(doc))
+ .setGenericConformanceLevel(getGenericConformanceLevel(doc))
.createText();
inputField.disableFieldRegeneration();
inputField.setValue(value);
@@ -192,12 +193,12 @@ protected void applyAcroField(DrawContext drawContext) {
inputField.getFirstFormAnnotation().setRotation(rotation);
}
applyDefaultFieldProperties(inputField);
+ applyAccessibilityProperties(inputField,doc);
inputField.getFirstFormAnnotation().setFormFieldElement((InputField) modelElement);
inputField.enableFieldRegeneration();
PdfFormCreator.getAcroForm(doc, true).addField(inputField, page);
- writeAcroFormFieldLangAttribute(doc);
- applyProperties(margins);
+ FormFieldRendererUtil.reapplyProperties(modelElement, properties);
}
/**
diff --git a/forms/src/main/java/com/itextpdf/forms/form/renderer/RadioRenderer.java b/forms/src/main/java/com/itextpdf/forms/form/renderer/RadioRenderer.java
index 9b5f363804..dd7dab313d 100644
--- a/forms/src/main/java/com/itextpdf/forms/form/renderer/RadioRenderer.java
+++ b/forms/src/main/java/com/itextpdf/forms/form/renderer/RadioRenderer.java
@@ -32,13 +32,16 @@ This file is part of the iText (R) project.
import com.itextpdf.forms.form.element.Radio;
import com.itextpdf.forms.util.BorderStyleUtil;
import com.itextpdf.forms.util.DrawingUtil;
+import com.itextpdf.forms.util.FormFieldRendererUtil;
import com.itextpdf.kernel.colors.Color;
import com.itextpdf.kernel.colors.ColorConstants;
import com.itextpdf.kernel.exceptions.PdfException;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfPage;
+import com.itextpdf.kernel.pdf.canvas.CanvasArtifact;
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
+import com.itextpdf.kernel.pdf.tagutils.TagTreePointer;
import com.itextpdf.layout.borders.Border;
import com.itextpdf.layout.element.Paragraph;
import com.itextpdf.layout.layout.LayoutContext;
@@ -171,7 +174,7 @@ protected void applyAcroField(DrawContext drawContext) {
PdfDocument doc = drawContext.getDocument();
PdfAcroForm form = PdfFormCreator.getAcroForm(doc, true);
Rectangle area = flatRenderer.getOccupiedArea().getBBox().clone();
- final Map margins = deleteMargins();
+ final Map properties = FormFieldRendererUtil.removeProperties(this.modelElement);
PdfPage page = doc.getPage(occupiedArea.getPageNumber());
String groupName = this.getProperty(FormProperty.FORM_FIELD_RADIO_GROUP_NAME);
@@ -182,7 +185,7 @@ protected void applyAcroField(DrawContext drawContext) {
PdfButtonFormField radioGroup = (PdfButtonFormField) form.getField(groupName);
if (null == radioGroup) {
radioGroup = new RadioFormFieldBuilder(doc, groupName)
- .setConformanceLevel(getConformanceLevel(doc))
+ .setGenericConformanceLevel(getGenericConformanceLevel(doc))
.createRadioGroup();
radioGroup.disableFieldRegeneration();
radioGroup.setValue(PdfFormAnnotation.OFF_STATE_VALUE);
@@ -194,7 +197,7 @@ protected void applyAcroField(DrawContext drawContext) {
}
PdfFormAnnotation radio = new RadioFormFieldBuilder(doc, null)
- .setConformanceLevel(getConformanceLevel(doc))
+ .setGenericConformanceLevel(getGenericConformanceLevel(doc))
.createRadioButton(getModelId(), area);
radio.disableFieldRegeneration();
@@ -208,10 +211,9 @@ protected void applyAcroField(DrawContext drawContext) {
radioGroup.addKid(radio);
radioGroup.enableFieldRegeneration();
+ applyAccessibilityProperties(radioGroup, doc);
form.addField(radioGroup, page);
-
- writeAcroFormFieldLangAttribute(doc);
- applyProperties(margins);
+ FormFieldRendererUtil.reapplyProperties(this.modelElement, properties);
}
/**
@@ -240,6 +242,11 @@ public void drawChildren(DrawContext drawContext) {
}
PdfCanvas canvas = drawContext.getCanvas();
+ boolean isTaggingEnabled = drawContext.isTaggingEnabled();
+ if (isTaggingEnabled) {
+ TagTreePointer tp = drawContext.getDocument().getTagStructureContext().getAutoTaggingPointer();
+ canvas.openTag(tp.getTagReference());
+ }
Rectangle rectangle = getOccupiedArea().getBBox().clone();
Border border = this.getProperty(Property.BORDER);
if (border != null) {
@@ -252,6 +259,9 @@ public void drawChildren(DrawContext drawContext) {
DrawingUtil.drawCircle(
canvas, rectangle.getLeft() + radius, rectangle.getBottom() + radius, radius / 2);
canvas.restoreState();
+ if (isTaggingEnabled) {
+ canvas.closeTag();
+ }
}
/**
@@ -278,11 +288,19 @@ public void drawBorder(DrawContext drawContext) {
final float cx = rectangle.getX() + rectangle.getWidth() / 2;
final float cy = rectangle.getY() + rectangle.getHeight() / 2;
final float r = (Math.min(rectangle.getWidth(), rectangle.getHeight()) + borderWidth) / 2;
- drawContext.getCanvas()
- .setStrokeColor(border.getColor())
+ final boolean isTaggingEnabled = drawContext.isTaggingEnabled();
+ final PdfCanvas canvas = drawContext.getCanvas();
+
+ if (isTaggingEnabled){
+ canvas.openTag(new CanvasArtifact());
+ }
+ canvas.setStrokeColor(border.getColor())
.setLineWidth(borderWidth)
.circle(cx, cy, r)
.stroke();
+ if (isTaggingEnabled){
+ canvas.closeTag();
+ }
}
}
@@ -313,10 +331,18 @@ public void drawBackground(DrawContext drawContext) {
final float cx = rectangle.getX() + rectangle.getWidth() / 2;
final float cy = rectangle.getY() + rectangle.getHeight() / 2;
final float r = (Math.min(rectangle.getWidth(), rectangle.getHeight()) + borderWidth) / 2;
- drawContext.getCanvas()
- .setFillColor(backgroundColor)
+ final boolean isTaggingEnabled = drawContext.isTaggingEnabled();
+ final PdfCanvas canvas = drawContext.getCanvas();
+
+ if (isTaggingEnabled){
+ canvas.openTag(new CanvasArtifact());
+ }
+ canvas.setFillColor(backgroundColor)
.circle(cx, cy, r)
.fill();
+ if (isTaggingEnabled){
+ canvas.closeTag();
+ }
}
}
}
diff --git a/forms/src/main/java/com/itextpdf/forms/form/renderer/SelectFieldComboBoxRenderer.java b/forms/src/main/java/com/itextpdf/forms/form/renderer/SelectFieldComboBoxRenderer.java
index dadb18622b..3c03dda2c8 100644
--- a/forms/src/main/java/com/itextpdf/forms/form/renderer/SelectFieldComboBoxRenderer.java
+++ b/forms/src/main/java/com/itextpdf/forms/form/renderer/SelectFieldComboBoxRenderer.java
@@ -31,6 +31,7 @@ This file is part of the iText (R) project.
import com.itextpdf.forms.form.element.ComboBoxField;
import com.itextpdf.forms.form.element.SelectFieldItem;
import com.itextpdf.forms.util.BorderStyleUtil;
+import com.itextpdf.forms.util.FormFieldRendererUtil;
import com.itextpdf.io.logs.IoLogMessageConstant;
import com.itextpdf.kernel.font.PdfFont;
import com.itextpdf.kernel.geom.Rectangle;
@@ -56,6 +57,7 @@ This file is part of the iText (R) project.
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -115,14 +117,17 @@ protected void applyAcroField(DrawContext drawContext) {
final ChoiceFormFieldBuilder builder = new ChoiceFormFieldBuilder(doc, name).setWidgetRectangle(area)
.setFont(font)
- .setConformanceLevel(getConformanceLevel(doc));
+ .setGenericConformanceLevel(getGenericConformanceLevel(doc));
+
+ applyMargins(area, false);
+ final Map properties = FormFieldRendererUtil.removeProperties(this.modelElement);
modelElement.setProperty(Property.FONT_PROVIDER, this.getProperty(Property.FONT_PROVIDER));
modelElement.setProperty(Property.RENDERING_MODE, this.getProperty(Property.RENDERING_MODE));
setupBuilderValues(builder, comboBoxFieldModelElement);
final PdfChoiceFormField comboBoxField = builder.createComboBox();
comboBoxField.disableFieldRegeneration();
-
+ applyAccessibilityProperties(comboBoxField, doc);
final Background background = this.modelElement.getProperty(Property.BACKGROUND);
if (background != null) {
comboBoxField.getFirstFormAnnotation().setBackgroundColor(background.getColor());
@@ -154,7 +159,7 @@ protected void applyAcroField(DrawContext drawContext) {
comboBoxField.enableFieldRegeneration();
PdfFormCreator.getAcroForm(doc, true).addField(comboBoxField, page);
- writeAcroFormFieldLangAttribute(doc);
+ FormFieldRendererUtil.reapplyProperties(this.modelElement, properties);
}
diff --git a/forms/src/main/java/com/itextpdf/forms/form/renderer/SelectFieldListBoxRenderer.java b/forms/src/main/java/com/itextpdf/forms/form/renderer/SelectFieldListBoxRenderer.java
index a3e6e12100..4a3e36cd72 100644
--- a/forms/src/main/java/com/itextpdf/forms/form/renderer/SelectFieldListBoxRenderer.java
+++ b/forms/src/main/java/com/itextpdf/forms/form/renderer/SelectFieldListBoxRenderer.java
@@ -30,6 +30,7 @@ This file is part of the iText (R) project.
import com.itextpdf.forms.form.element.AbstractSelectField;
import com.itextpdf.forms.form.element.ListBoxField;
import com.itextpdf.forms.util.BorderStyleUtil;
+import com.itextpdf.forms.util.FormFieldRendererUtil;
import com.itextpdf.io.logs.IoLogMessageConstant;
import com.itextpdf.kernel.colors.ColorConstants;
import com.itextpdf.kernel.colors.DeviceRgb;
@@ -57,6 +58,7 @@ This file is part of the iText (R) project.
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -193,6 +195,8 @@ protected void applyAcroField(DrawContext drawContext) {
final Rectangle area = this.getOccupiedArea().getBBox().clone();
final PdfPage page = doc.getPage(occupiedArea.getPageNumber());
+ applyMargins(area, false);
+ final Map properties = FormFieldRendererUtil.removeProperties(this.modelElement);
// Some properties are set to the HtmlDocumentRenderer, which is root renderer for this ButtonRenderer, but
// in forms logic root renderer is CanvasRenderer, and these properties will have default values. So
// we get them from renderer and set these properties to model element, which will be passed to forms logic.
@@ -202,13 +206,13 @@ protected void applyAcroField(DrawContext drawContext) {
ListBoxField lbModelElement = (ListBoxField) modelElement;
List selectedOptions = lbModelElement.getSelectedStrings();
ChoiceFormFieldBuilder builder = new ChoiceFormFieldBuilder(doc, getModelId())
- .setConformanceLevel(getConformanceLevel(doc))
+ .setGenericConformanceLevel(getGenericConformanceLevel(doc))
.setFont(font)
.setWidgetRectangle(area);
setupBuilderValues(builder, lbModelElement);
PdfChoiceFormField choiceField = builder.createList();
choiceField.disableFieldRegeneration();
-
+ applyAccessibilityProperties(choiceField,drawContext.getDocument());
choiceField.setFontSize(fontSize.getValue());
choiceField.setMultiSelect(isMultiple());
choiceField.setListSelected(selectedOptions.toArray(new String[selectedOptions.size()]));
@@ -229,8 +233,8 @@ protected void applyAcroField(DrawContext drawContext) {
choiceField.getFirstFormAnnotation().setFormFieldElement(lbModelElement);
choiceField.enableFieldRegeneration();
PdfFormCreator.getAcroForm(doc, true).addField(choiceField, page);
+ FormFieldRendererUtil.reapplyProperties(this.modelElement, properties);
- writeAcroFormFieldLangAttribute(doc);
}
private float getCalculatedHeight(IRenderer flatRenderer) {
diff --git a/forms/src/main/java/com/itextpdf/forms/form/renderer/SignatureAppearanceRenderer.java b/forms/src/main/java/com/itextpdf/forms/form/renderer/SignatureAppearanceRenderer.java
index 1508d4b251..611c9bf73b 100644
--- a/forms/src/main/java/com/itextpdf/forms/form/renderer/SignatureAppearanceRenderer.java
+++ b/forms/src/main/java/com/itextpdf/forms/form/renderer/SignatureAppearanceRenderer.java
@@ -29,6 +29,7 @@ This file is part of the iText (R) project.
import com.itextpdf.forms.fields.PdfSignatureFormField;
import com.itextpdf.forms.fields.SignatureFormFieldBuilder;
import com.itextpdf.forms.form.element.SignatureFieldAppearance;
+import com.itextpdf.forms.util.FormFieldRendererUtil;
import com.itextpdf.io.logs.IoLogMessageConstant;
import com.itextpdf.kernel.colors.Color;
import com.itextpdf.kernel.geom.Rectangle;
@@ -53,6 +54,7 @@ This file is part of the iText (R) project.
import com.itextpdf.layout.renderer.ParagraphRenderer;
import java.util.List;
+import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -128,6 +130,8 @@ protected void adjustFieldLayout(LayoutContext layoutContext) {
Rectangle bBox = getOccupiedArea().getBBox().clone();
applyPaddings(bBox, false);
applyBorderBox(bBox, false);
+ applyMargins(bBox, false);
+
if (bBox.getY() < 0) {
bBox.setHeight(bBox.getY() + bBox.getHeight());
bBox.setY(0);
@@ -194,7 +198,6 @@ protected void adjustFieldLayout(LayoutContext layoutContext) {
default:
return;
}
-
adjustChildrenLayout(renderingMode, signatureRect, descriptionRect, layoutContext.getArea().getPageNumber());
}
@@ -236,7 +239,7 @@ protected void applyAcroField(DrawContext drawContext) {
PdfDocument doc = drawContext.getDocument();
Rectangle area = getOccupiedArea().getBBox().clone();
applyMargins(area, false);
- deleteMargins();
+ Map properties = FormFieldRendererUtil.removeProperties(this.modelElement);
PdfPage page = doc.getPage(occupiedArea.getPageNumber());
Background background = this.getProperty(Property.BACKGROUND);
@@ -254,19 +257,20 @@ protected void applyAcroField(DrawContext drawContext) {
modelElement.setProperty(Property.FONT_PROVIDER, this.getProperty(Property.FONT_PROVIDER));
modelElement.setProperty(Property.RENDERING_MODE, this.getProperty(Property.RENDERING_MODE));
final PdfSignatureFormField sigField = new SignatureFormFieldBuilder(doc, name).setWidgetRectangle(area)
- .setConformanceLevel(getConformanceLevel(doc))
+ .setGenericConformanceLevel(getGenericConformanceLevel(doc))
.setFont(font)
.createSignature();
sigField.disableFieldRegeneration();
sigField.setFontSize(fontSizeValue);
sigField.getFirstFormAnnotation().setBackgroundColor(backgroundColor);
applyDefaultFieldProperties(sigField);
+ applyAccessibilityProperties(sigField,doc);
sigField.getFirstFormAnnotation().setFormFieldElement((SignatureFieldAppearance) modelElement);
sigField.enableFieldRegeneration();
PdfAcroForm forms = PdfFormCreator.getAcroForm(doc, true);
forms.addField(sigField, page);
+ FormFieldRendererUtil.reapplyProperties(modelElement, properties);
- writeAcroFormFieldLangAttribute(doc);
}
private void adjustChildrenLayout(RenderingMode renderingMode,
diff --git a/forms/src/main/java/com/itextpdf/forms/form/renderer/TextAreaRenderer.java b/forms/src/main/java/com/itextpdf/forms/form/renderer/TextAreaRenderer.java
index e5a5bd22f0..86b48d5520 100644
--- a/forms/src/main/java/com/itextpdf/forms/form/renderer/TextAreaRenderer.java
+++ b/forms/src/main/java/com/itextpdf/forms/form/renderer/TextAreaRenderer.java
@@ -30,6 +30,7 @@ This file is part of the iText (R) project.
import com.itextpdf.forms.form.FormProperty;
import com.itextpdf.forms.form.element.TextArea;
import com.itextpdf.forms.logs.FormsLogMessageConstants;
+import com.itextpdf.forms.util.FormFieldRendererUtil;
import com.itextpdf.io.logs.IoLogMessageConstant;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.PdfDocument;
@@ -193,7 +194,7 @@ protected void applyAcroField(DrawContext drawContext) {
PdfDocument doc = drawContext.getDocument();
Rectangle area = getOccupiedArea().getBBox().clone();
applyMargins(area, false);
- final Map margins = deleteMargins();
+ final Map properties = FormFieldRendererUtil.removeProperties(modelElement);
PdfPage page = doc.getPage(occupiedArea.getPageNumber());
final float fontSizeValue = fontSize.getValue();
final PdfString defaultValue = new PdfString(getDefaultValue());
@@ -202,7 +203,7 @@ protected void applyAcroField(DrawContext drawContext) {
// That's why we got rid of several properties we set by default during TextArea instance creation.
modelElement.setProperty(Property.BOX_SIZING, BoxSizingPropertyValue.BORDER_BOX);
final PdfFormField inputField = new TextFormFieldBuilder(doc, name).setWidgetRectangle(area)
- .setConformanceLevel(getConformanceLevel(doc))
+ .setGenericConformanceLevel(getGenericConformanceLevel(doc))
.setFont(font)
.createMultilineText();
inputField.disableFieldRegeneration();
@@ -212,10 +213,10 @@ protected void applyAcroField(DrawContext drawContext) {
applyDefaultFieldProperties(inputField);
inputField.getFirstFormAnnotation().setFormFieldElement((TextArea) modelElement);
inputField.enableFieldRegeneration();
+ applyAccessibilityProperties(inputField, doc);
PdfFormCreator.getAcroForm(doc, true).addField(inputField, page);
- writeAcroFormFieldLangAttribute(doc);
- applyProperties(margins);
+ FormFieldRendererUtil.reapplyProperties(modelElement, properties);
}
/**
diff --git a/forms/src/main/java/com/itextpdf/forms/form/renderer/checkboximpl/HtmlCheckBoxRenderingStrategy.java b/forms/src/main/java/com/itextpdf/forms/form/renderer/checkboximpl/HtmlCheckBoxRenderingStrategy.java
index 93643c2cfa..5c52ffe76a 100644
--- a/forms/src/main/java/com/itextpdf/forms/form/renderer/checkboximpl/HtmlCheckBoxRenderingStrategy.java
+++ b/forms/src/main/java/com/itextpdf/forms/form/renderer/checkboximpl/HtmlCheckBoxRenderingStrategy.java
@@ -34,6 +34,9 @@ This file is part of the iText (R) project.
*/
public final class HtmlCheckBoxRenderingStrategy implements ICheckBoxRenderingStrategy {
+ /**
+ * Creates a new {@link HtmlCheckBoxRenderingStrategy} instance.
+ */
public HtmlCheckBoxRenderingStrategy() {
// empty constructor
}
diff --git a/forms/src/main/java/com/itextpdf/forms/util/FormFieldRendererUtil.java b/forms/src/main/java/com/itextpdf/forms/util/FormFieldRendererUtil.java
new file mode 100644
index 0000000000..0c45c6e961
--- /dev/null
+++ b/forms/src/main/java/com/itextpdf/forms/util/FormFieldRendererUtil.java
@@ -0,0 +1,82 @@
+/*
+ This file is part of the iText (R) project.
+ Copyright (c) 1998-2024 Apryse Group NV
+ Authors: Apryse Software.
+
+ This program is offered under a commercial and under the AGPL license.
+ For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
+
+ AGPL licensing:
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+ */
+package com.itextpdf.forms.util;
+
+import com.itextpdf.layout.IPropertyContainer;
+import com.itextpdf.layout.properties.Property;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * Utility class for centralized logic related to form field rendering.
+ */
+public final class FormFieldRendererUtil {
+
+ //These properties are related to the outer box of the element.
+ private static final int[] PROPERTIES_THAT_IMPACT_LAYOUT = new int[] {
+ Property.MARGIN_TOP, Property.MARGIN_BOTTOM, Property.MARGIN_LEFT, Property.MARGIN_RIGHT,
+ Property.WIDTH, Property.BOTTOM, Property.LEFT, Property.POSITION
+ };
+
+ /**
+ * Creates a new instance of {@link FormFieldRendererUtil}.
+ */
+ private FormFieldRendererUtil() {
+ // empty constructor
+ }
+
+ /**
+ * Removes properties that impact the lay outing of interactive form fields.
+ *
+ * @param modelElement The model element to remove the properties from.
+ *
+ * @return A map containing the removed properties.
+ */
+ public static Map removeProperties(IPropertyContainer modelElement) {
+ final Map properties = new HashMap<>(PROPERTIES_THAT_IMPACT_LAYOUT.length);
+ for (int i : PROPERTIES_THAT_IMPACT_LAYOUT) {
+ properties.put(i, modelElement.
-Â QWERTY
+Â QWERTY
-Â QWERTZ
+Â QWERTZ
Chapter 2 Basics
@@ -2073,7 +2073,7 @@ Chapter 2 Basics
25
-Â If no Wi-Fi networks are available or you choose not to join any, iPhone connects to
+Â If no Wi-Fi networks are available or you choose not to join any, iPhone connects to
the Internet over a cellular data network ( or ). You cannot access the iTunes Wi-
Fi Music Store over a cellular network.
@@ -2377,7 +2377,7 @@ You can double-click the Home button to quickly go to Favorites. See “Home
Call someone you’ve recently called or who has recently called you
- Tap Recents and choose a person or number. Calls you’ve missed are shown in red.
+ Tap Recents and choose a person or number. Calls you’ve missed are shown in red.
Tap Missed to see only missed calls.
Dial a call
@@ -2520,7 +2520,7 @@ Chapter 3 Phone
Decline a call
- Do one of the following:
+ Do one of the following:
 Press the Sleep/Wake button twice quickly.
Sleep/Wake
button
-Â Press and hold the mic button on the headset for about two seconds, then let go.
+Â Press and hold the mic button on the headset for about two seconds, then let go.
When you let go, two low beeps confirm that the call was declined.
-Â Tap Decline. Decline appears only when you’re using iPhone.
+Â Tap Decline. Decline appears only when you’re using iPhone.
@@ -2564,11 +2564,11 @@ Tap Add Call and make a second call.
Respond to an incoming call
- Do one of the following:
+ Do one of the following:
-Â To ignore the call and send it to voicemail, tap Ignore.
+Â To ignore the call and send it to voicemail, tap Ignore.
-Â To hold the first call and answer the new call, tap Hold Call + Answer.
+Â To hold the first call and answer the new call, tap Hold Call + Answer.
Chapter 3 Phone
@@ -2938,9 +2938,9 @@ If you’ve set up iTunes to sync contacts automatically, iTunes keeps your
to-date—whether you make changes on your computer or on iPhone. iTunes supports
syncing with the following applications on your computer.
-Â On a Mac: Mac OS X Address Book, Microsoft Entourage, and Yahoo! Address Book
+Â On a Mac: Mac OS X Address Book, Microsoft Entourage, and Yahoo! Address Book
-Â On a PC: Yahoo! Address Book, Windows Address Book (Outlook Express), or
+Â On a PC: Yahoo! Address Book, Windows Address Book (Outlook Express), or
Microsoft Outlook 2003 or 2007
@@ -3135,7 +3135,7 @@ From the Home screen choose Settings > Sounds > Ringtone, then choose a ri
Assign a ringtone to a contact
- From Phone, tap Contacts and choose a contact. Tap Ringtone and choose a ringtone.
+ From Phone, tap Contacts and choose a contact. Tap Ringtone and choose a ringtone.
When that person calls, you hear that ringtone.
@@ -3210,7 +3210,7 @@ headset, preventing connections with other headsets.
Pair iPhone with the iPhone Bluetooth Headset
- Connect the iPhone Dual Dock to your computer, then place iPhone and the headset in
+ Connect the iPhone Dual Dock to your computer, then place iPhone and the headset in
the dock.
Route calls through iPhone
-m
+m
Answer a call by tapping the iPhone touchscreen.
@@ -3363,10 +3363,10 @@ Bluetooth device.
m
- Turn off Bluetooth. From the Home screen choose Settings > General > Bluetooth and
+ Turn off Bluetooth. From the Home screen choose Settings > General > Bluetooth and
drag the switch to Off.
-m Turn off the headset or car kit, or move out of range. You must be within about 30 feet
+m Turn off the headset or car kit, or move out of range. You must be within about 30 feet
of a Bluetooth device for it to be connected to iPhone.
To turn off the iPhone Bluetooth Headset, press and hold the button until you hear the
@@ -3392,7 +3392,7 @@ Until you pair the device with iPhone again, iPhone doesn’t route calls th
Turning Bluetooth on iPhone On or Off
- From the Home screen choose Settings > General > Bluetooth, then turn Bluetooth on
+ From the Home screen choose Settings > General > Bluetooth, then turn Bluetooth on
or off.
@@ -3435,9 +3435,9 @@ is turned off.
For information about making international calls, including rates and other charges
that may apply, contact your carrier or go to your carrier’s website.
Make a call from outside your home country
- Contact your carrier to enable your iPhone account for international roaming.
+ Contact your carrier to enable your iPhone account for international roaming.
Set iPhone to add the correct prefix when dialing from another country
- From the Home screen choose Settings > Phone, then turn International Assist on or
+ From the Home screen choose Settings > Phone, then turn International Assist on or
1
off. By default, International Assist is on.
@@ -3539,7 +3539,7 @@ apply.
Free accounts are also available online:
-Â
+Â
/Linkwww.mail.yahoo.com
@@ -3549,11 +3549,11 @@ transferred to iPhone as soon as it is received by the mail server.
To receive push email, you must first set up a Yahoo! account on iPhone.
-Â
+Â
/Linkwww.google.com/mail
-Â
+Â
/Linkwww.aol.com
@@ -3589,16 +3589,16 @@ email address, and password. After that, you’re done.
Otherwise, click Other, select a server type—IMAP, POP, or Exchange—and enter your
account information:
-Â Your email address
+Â Your email address
-Â The email server type (IMAP, POP, or Exchange)
+Â The email server type (IMAP, POP, or Exchange)
-Â The Internet host name for your incoming mail server (which may look like
+Â The Internet host name for your incoming mail server (which may look like
“mail.example.com”)
-Â The Internet host name for your outgoing mail server (which may look like
+Â The Internet host name for your outgoing mail server (which may look like
“smtp.example.com”)
@@ -3609,7 +3609,7 @@ Chapter 4 Mail
48
-Â Your user name and password for incoming and outgoing servers (you may not need
+Â Your user name and password for incoming and outgoing servers (you may not need
to enter a user name and password for an outgoing server)
Note: Exchange email accounts must be configured for IMAP in order to work with
@@ -3774,7 +3774,7 @@ Chapter 4 Mail
See all the recipients of a message
-m Open the message and tap Details.
+m Open the message and tap Details.
Tap a name or email address to see the recipient’s contact information. Then tap a
phone number, email address, or text message to contact the person. Tap Hide to hide
the recipients.
@@ -3786,7 +3786,7 @@ email address and tap Create New Contact or “Add to Existing Contact.̶
Mark a message as unread
- Open the message and tap “Mark as Unread.”
+ Open the message and tap “Mark as Unread.”
A blue dot
-Â To delete a bookmark or folder, tap next to the bookmark or folder, then tap
+Â To delete a bookmark or folder, tap next to the bookmark or folder, then tap
Delete.
-Â To reposition a bookmark or folder, drag next to the item you want to move.
+Â To reposition a bookmark or folder, drag next to the item you want to move.
-Â To edit the name or address of a bookmark or folder, or to put it in a different folder,
+Â To edit the name or address of a bookmark or folder, or to put it in a different folder,
tap the bookmark or folder.
When you finish, tap Done.
@@ -4429,7 +4429,7 @@ onscreen, then disappear automatically after you finish using them.
Additional Controls
- From the Now Playing screen tap the album cover.
+ From the Now Playing screen tap the album cover.
The repeat and shuffle controls and the scrubber bar appear. You can see time elapsed,
time remaining, and the song number. The song’s lyrics appear also, if you’ve added
them to the song in iTunes.
@@ -4591,7 +4591,7 @@ rated songs.
Rate a song
- Drag your thumb across the ratings bar to give the song zero to five stars.
+ Drag your thumb across the ratings bar to give the song zero to five stars.
Making Playlists Directly on iPhone
@@ -5013,7 +5013,7 @@ Chapter 7 Applications
Follow a link in a message
-m Tap the link.
+m Tap the link.
Tap a web address to open a webpage in Safari, a phone number to make a call, an
email address to open a preaddressed email in Mail, or a street address to see a map in
Maps. To return to your text messages, press the Home button and tap Text.
@@ -5085,7 +5085,7 @@ Adding and Editing Calendar Events Directly on iPhone
Add an event
-
+
You can enter any of the following:
@@ -5110,7 +5110,7 @@ iPhone may not sound your alert at the correct local time. To manually set iPhon
/Linkthe correct time, see page 96.
-Â Notes
+Â Notes
Set iPhone to make a sound when you get a calendar alert
@@ -5124,7 +5124,7 @@ Tap the event and tap Edit.
Delete an event
- Tap the event, tap Edit, then scroll down and tap Delete Event.
+ Tap the event, tap Edit, then scroll down and tap Delete Event.
Viewing Your Calendar
@@ -5149,7 +5149,7 @@ Chapter 7 Applications
72
-Â Month view: Days with events show a dot below the date. Tap a day to see its events
+Â Month view: Days with events show a dot below the date. Tap a day to see its events
in a list below the calendar. Tap or to see the previous or next month.
@@ -5164,7 +5164,7 @@ Tap Today.
See the details of an event
- Tap the event.
+ Tap the event.
Set iPhone to adjust event times for a selected time zone
@@ -5201,9 +5201,9 @@ If you’ve set iTunes to sync photos automatically, iTunes copies or update
library (or selected albums) from your computer to iPhone whenever you connect
iPhone to your computer. iTunes can sync your photos from the following applications:
-Â On a Mac: iPhoto 4.0.3 or later
+Â On a Mac: iPhoto 4.0.3 or later
-Â On a PC: Adobe Photoshop Album 2.0 or later or Adobe Photoshop Elements 3.0
+Â On a PC: Adobe Photoshop Album 2.0 or later or Adobe Photoshop Elements 3.0
or later
For information about syncing iPhone with photos and other information on your
@@ -5224,7 +5224,7 @@ contacts, and upload them to your computer.
Take a picture
-
+
Tap Camera, then aim iPhone and tap .
@@ -5237,9 +5237,9 @@ Import photos from iPhone to your computer
Connect iPhone to your computer. Then:
-Â On a Mac: In iPhoto, click Import. iPhoto should open automatically.
+Â On a Mac: In iPhoto, click Import. iPhoto should open automatically.
-Â On a PC: Follow the instructions that came with your camera or photo application.
+Â On a PC: Follow the instructions that came with your camera or photo application.
Viewing Photos
@@ -5272,17 +5272,17 @@ Tap a picture in the Camera Roll album, then tap .
Delete a picture
-m
+m
View photos from your computer
-m From the Home screen choose Photos.
+m From the Home screen choose Photos.
 Tap Photo Library to see all your photos.
 Tap any photo album, or Camera Roll to see pictures you’ve taken with iPhone.
See a photo at full screen
-m Tap a thumbnail photo to see it at full screen. Tap the full screen photo to hide
+m Tap a thumbnail photo to see it at full screen. Tap the full screen photo to hide
the controls.
See the next or previous photo
-m
+m
Flick left or right. Or tap the screen to show the controls, then tap or .
@@ -5322,11 +5322,11 @@ format, expands to fit the screen.
Zoom in on part of a photo
- Double-tap the part you want to zoom in on. Double-tap again to zoom out.
+ Double-tap the part you want to zoom in on. Double-tap again to zoom out.
Zoom in or out
- Pinch to zoom in or out.
+ Pinch to zoom in or out.
2 To set:
-Â The length of time each slide is shown, tap Play Each Slide For and choose a time.
+Â The length of time each slide is shown, tap Play Each Slide For and choose a time.
-Â Transition effects when moving from photo to photo, tap Transition and choose a
+Â Transition effects when moving from photo to photo, tap Transition and choose a
transition type.
-Â Whether slideshows repeat, turn Repeat on or off.
+Â Whether slideshows repeat, turn Repeat on or off.
-Â Whether photos are shown in random order, turn Shuffle on or off.
+Â Whether photos are shown in random order, turn Shuffle on or off.
Play music during a slideshow
@@ -5433,12 +5433,12 @@ that person has enabled email contributions.
To send photos to a Web Gallery, you need to do the following:
-Â Set up your .Mac mail account on iPhone
+Â Set up your .Mac mail account on iPhone
-Â Publish an iPhoto ‘08 album to a .Mac Web Gallery
+Â Publish an iPhoto ‘08 album to a .Mac Web Gallery
-Â Select “Allow photo uploading by email” in the Publish Settings pane of iPhoto ‘08
+Â Select “Allow photo uploading by email” in the Publish Settings pane of iPhoto ‘08
Chapter 7 Applications
@@ -5814,7 +5814,7 @@ To quickly move the pin to the area currently displayed, tap , then tap Replace
Zoom in to a part of a map
-m
+m
Pinch the map with two fingers. Or double-tap the part you want to zoom in on.
Double-tap again to zoom in even closer.
@@ -5872,7 +5872,7 @@ Tap in the search field, then tap Bookmarks or Recents.
Add a location to your contacts list
-
+
Find a location, tap the pin that points to it, tap next to the name or description,
@@ -5990,7 +5990,7 @@ Type things like:
Contact a business or get directions
-
+
Tap the pin that marks a business, then tap next to the name.
@@ -6003,12 +6003,12 @@ Tap the pin that marks a business, then tap next to the name.
From there, you can do the following:
-Â Depending on what information is stored for that business, you can tap a phone
+Â Depending on what information is stored for that business, you can tap a phone
number to call, email address to email, or web address to visit a website.
-Â For directions, tap Directions To Here or Directions From Here.
+Â For directions, tap Directions To Here or Directions From Here.
-Â To add the business to your contacts list, scroll down and tap Create New Contact or
+Â To add the business to your contacts list, scroll down and tap Create New Contact or
“Add to Existing Contact.”
@@ -6138,11 +6138,11 @@ Set an alarm
Tap Alarm and tap , then adjust any of the following settings:
-Â To set the alarm to repeat on certain days, tap Repeat and choose the days.
+Â To set the alarm to repeat on certain days, tap Repeat and choose the days.
-Â To choose the ringtone that sounds when the alarm goes off, tap Sound.
+Â To choose the ringtone that sounds when the alarm goes off, tap Sound.
-Â To set whether the alarm gives you the option to hit snooze, turn Snooze on or off.
+Â To set whether the alarm gives you the option to hit snooze, turn Snooze on or off.
If Snooze is on and you tap Snooze when the alarm sounds, the alarm stops and then
sounds again in ten minutes.
@@ -6387,7 +6387,7 @@ the iTunes Wi-Fi Music Store.
Turn Wi-Fi on or off
- Choose Wi-Fi and turn Wi-Fi on or off.
+ Choose Wi-Fi and turn Wi-Fi on or off.
Join a Wi-Fi network
@@ -6586,26 +6586,26 @@ About
Choose General > About to get information about iPhone, including:
-Â Name of your phone network
+Â Name of your phone network
-Â Number of songs, videos, and photos
+Â Number of songs, videos, and photos
-Â Total storage capacity
+Â Total storage capacity
-Â Space available
+Â Space available
-Â Software version
+Â Software version
-Â Serial and model numbers
+Â Serial and model numbers
-Â Wi-Fi and Bluetooth addresses
+Â Wi-Fi and Bluetooth addresses
-Â IMEI (International Mobile Equipment Identity) and ICCID (Integrated Circuit Card
+Â IMEI (International Mobile Equipment Identity) and ICCID (Integrated Circuit Card
Identifier, or Smart Card) numbers
-Â Modem firmware version of the cellular transmitter
+Â Modem firmware version of the cellular transmitter
-Â Legal information
+Â Legal information
@@ -6671,7 +6671,7 @@ for your region.
Set the language for iPhone
- Choose General > International > Language, choose the language you want to use,
+ Choose General > International > Language, choose the language you want to use,
and tap Done.
Turn international keyboards on or off
@@ -6717,7 +6717,7 @@ music or on a call.
Set the amount of time before iPhone locks
- Choose General > Auto-Lock and choose a time.
+ Choose General > Auto-Lock and choose a time.
@@ -6732,7 +6732,7 @@ you to enter the passcode to unlock it.
Turn passcode lock off
- Choose General > Passcode Lock and tap Turn Passcode Off, then enter your passcode.
+ Choose General > Passcode Lock and tap Turn Passcode Off, then enter your passcode.
Change the passcode
@@ -6803,7 +6803,7 @@ when you’re in an area not covered by your carrier’s network. For ex
traveling, you can turn off Data Roaming to avoid potential roaming charges. By
default, Data Roaming is turned off.
- Choose General > Network and turn Data Roaming on or off.
+ Choose General > Network and turn Data Roaming on or off.
@@ -6816,7 +6816,7 @@ information.
Turn Bluetooth on or off
- Choose General > Network and turn Bluetooth on or off.
+ Choose General > Network and turn Bluetooth on or off.
@@ -6936,7 +6936,7 @@ Adjust advanced settings
Choose Mail > Accounts, choose an account, then do one of the following:
-Â To set whether drafts, sent messages, and deleted messages are stored on iPhone or
+Â To set whether drafts, sent messages, and deleted messages are stored on iPhone or
remotely on your email server (IMAP accounts only), tap Advanced and choose Drafts
Mailbox, Sent Mailbox, or Deleted Mailbox.
@@ -6944,14 +6944,14 @@ If you store messages on iPhone, you can see them even when iPhone isn’t <
connected to the Internet.
-Â To set when deleted messages are removed permanently from iPhone, tap Advanced and
+Â To set when deleted messages are removed permanently from iPhone, tap Advanced and
tap Remove, then choose a time: Never, or after one day, one week, or one month.
-Â To adjust email server settings, tap Host Name, User Name, or Password under
+Â To adjust email server settings, tap Host Name, User Name, or Password under
Incoming Mail Server or Outgoing Mail Server. Ask your network administrator or
Internet service provider for the correct settings.
-Â To adjust SSL and password settings, tap Advanced. Ask your network administrator or
+Â To adjust SSL and password settings, tap Advanced. Ask your network administrator or
Internet service provider for the correct settings.
Delete an email account from iPhone
@@ -6970,7 +6970,7 @@ when you don’t have Mail open.
Set whether iPhone checks for new messages automatically
- Choose Mail > Auto-Check, then tap Manual,“Every 15 minutes,”“Every 30 minutes,” or
+ Choose Mail > Auto-Check, then tap Manual,“Every 15 minutes,”“Every 30 minutes,” or
“Every hour.”
If you have a Yahoo! email account, email is instantly transferred to iPhone as it arrives
@@ -6978,7 +6978,7 @@ at the Yahoo! server.
Set whether iPhone plays an alert sound when you have new email
- Choose Sound, then turn New Mail on or off.
+ Choose Sound, then turn New Mail on or off.
Set the number of messages shown on iPhone
@@ -6999,19 +6999,19 @@ Chapter 8 Settings
Set a minimum font size for messages
- Choose Mail > Minimum Font Size, then choose Small, Medium, Large, Extra Large, or
+ Choose Mail > Minimum Font Size, then choose Small, Medium, Large, Extra Large, or
Giant.
Setting whether iPhone shows To and Cc labels in message lists
- Choose Mail, then turn Show To/Cc Label on or off.
+ Choose Mail, then turn Show To/Cc Label on or off.
To Cc
If Show To/Cc Label is on, or next to each message in a list indicates whether
the message was sent directly to you or you were Cc’ed.
Setting iPhone to confirm that you want to delete a message
- Choose Mail and turn Ask Before Deleting on or off.
+ Choose Mail and turn Ask Before Deleting on or off.
tapping Delete.
@@ -7021,18 +7021,18 @@ Settings for Sending Email
Set an alert to sound when you successfully send a message
- Choose Sound, then turn Sent Mail on or off.
+ Choose Sound, then turn Sent Mail on or off.
Set whether iPhone sends you a copy of every message you send
- Choose Mail, then turn Always Bcc Myself on or off.
+ Choose Mail, then turn Always Bcc Myself on or off.
Add a signature to your messages
You can set iPhone to add a signature—your favorite quote, or your name, title, and
phone number, for example—that appears in every message you send.
- Choose Mail > Signature, then type a signature.
+ Choose Mail > Signature, then type a signature.
Set the default email account
@@ -7043,7 +7043,7 @@ sending a photo from Photos or tapping a business’ email address in Maps,
message is sent from your default email account.
- Choose Mail > Default Account, then choose an account.
+ Choose Mail > Default Account, then choose an account.
If Ask Before Deleting is on, to delete a message you must tap , then confirm by
@@ -7061,7 +7061,7 @@ Setting How Contacts Are Displayed
Set how contacts are sorted and displayed
- Choose Phone, then do one of the following:
+ Choose Phone, then do one of the following:
 To sort alphabetically by first or last name, tap Sort Order.
 To display first name first or last name first, tap Display Order.
@@ -7240,21 +7240,21 @@ Change security settings
Choose Safari, then do one of the following:
-Â To enable or disable JavaScript, turn JavaScript on or off.
+Â To enable or disable JavaScript, turn JavaScript on or off.
JavaScript lets web programmers control elements of the page—for example, a page
that uses JavaScript might display the current date and time or cause a linked page
to appear in a new pop-up page.
-Â To enable or disable plug-ins, turn Plug-ins on or off. Plug-ins allow Safari to play some
+Â To enable or disable plug-ins, turn Plug-ins on or off. Plug-ins allow Safari to play some
types of audio and video files and to display Microsoft Word files and Microsoft Excel
documents.
-Â To block or allow pop-ups, turn Block Pop-ups on or off. Blocking pop-ups stops only
+Â To block or allow pop-ups, turn Block Pop-ups on or off. Blocking pop-ups stops only
pop-ups that appear when you close a page or open a page by typing its address.
It doesn’t block pop-ups that open when you click a link.
-Â To set whether Safari accepts cookies, tap Accept Cookies and choose Never,
+Â To set whether Safari accepts cookies, tap Accept Cookies and choose Never,
“From visited,” or Always.
@@ -7265,11 +7265,11 @@ based on information you may have provided.
Some pages won’t work correctly unless iPhone is set to accept cookies.
-Â To clear the history of webpages you’ve visited, tap Clear History.
+Â To clear the history of webpages you’ve visited, tap Clear History.
-Â To clear all cookies from Safari, tap Clear Cookies.
+Â To clear all cookies from Safari, tap Clear Cookies.
-Â To clear the browser cache, tap Clear Cache.
+Â To clear the browser cache, tap Clear Cache.
The browser cache stores the content of pages so the pages open faster the next
time you visit them. If a page you open isn’t showing new content, clearing the
@@ -7453,10 +7453,10 @@ Restore or transfer settings
Do one of the following:
- Connect a new iPhone to the same computer you used with your other iPhone,
+ Connect a new iPhone to the same computer you used with your other iPhone,
open iTunes, and follow the onscreen instructions.
- Reset the information on iPhone. In Settings, choose General > Reset, then choose
+ Reset the information on iPhone. In Settings, choose General > Reset, then choose
“Reset All Settings,”“Erase All Content and Settings,” or “Reset Network Settings.”Then
connect iPhone to your computer, open iTunes, and follow the onscreen instructions.
When you reset network settings, your list of previously used networks and your VPN
@@ -7756,14 +7756,14 @@ or
If iPhone doesn’t appear in iTunes or you can’t sync iPhone
-Â The iPhone battery might need to be recharged. For information about charging
+Â The iPhone battery might need to be recharged. For information about charging
/LinkiPhone, see “Charging the Battery” on page 27.
-Â Disconnect other USB devices from your computer and connect iPhone to a different
+Â Disconnect other USB devices from your computer and connect iPhone to a different
USB 2.0 port on your computer (not on your keyboard).
-Â Turn iPhone off and back on again. Press and hold the Sleep/Wake button on top of
+Â Turn iPhone off and back on again. Press and hold the Sleep/Wake button on top of
iPhone for a few seconds until a red slider appears, then drag the slider. Then press
and hold the Sleep/Wake button until the Apple logo appears. Tap Unlock and enter
your SIM’s PIN if the SIM is locked.
@@ -7773,7 +7773,7 @@ Note: If you enter the PIN incorrectly three times, you may need to contact your
carrier for a Personal Unlocking Key (PUK) to enable your SIM card again.
-Â Restart your computer and reconnect iPhone to your computer.
+Â Restart your computer and reconnect iPhone to your computer.
/Link
@@ -7795,7 +7795,7 @@ Appendix A Tips and Troubleshooting
115
-Â Download and install (or reinstall) the latest version of iTunes from
+Â Download and install (or reinstall) the latest version of iTunes from
www.apple.com/
@@ -7809,17 +7809,17 @@ the web
are no bars, or if it says “No service,” try moving to a different location. If you’re
indoors, try going outdoors or moving closer to a window.
-Â Check to make sure you’re in an area with network coverage. Go to your carrier’s
+Â Check to make sure you’re in an area with network coverage. Go to your carrier’s
website to see network coverage areas.
-Â Make sure airplane mode isn’t on. From the Home screen choose Settings, then turn
+Â Make sure airplane mode isn’t on. From the Home screen choose Settings, then turn
airplane mode off. If that doesn’t work, turn airplane mode on, wait 15 seconds, then
turn airplane mode off again.
-Â Make sure to include an area code for every phone number in your contacts list that
+Â Make sure to include an area code for every phone number in your contacts list that
you use to send or receive text messages.
-Â Turn iPhone off and back on again. Press and hold the Sleep/Wake button on top of
+Â Turn iPhone off and back on again. Press and hold the Sleep/Wake button on top of
iPhone for a few seconds until a red slider appears, then drag the slider. Then press
and hold the Sleep/Wake button until the Apple logo appears. Tap Unlock and enter
your SIM’s PIN if the SIM is locked.
@@ -7827,10 +7827,10 @@ your SIM’s PIN if the SIM is locked.
Note: If you enter the PIN incorrectly three times, you may need to contact your
carrier for a Personal Unlocking Key (PUK) to enable your SIM card again.
-Â There may be a problem with your wireless service. Call your carrier or go to your
+Â There may be a problem with your wireless service. Call your carrier or go to your
carrier’s website.
-Â
+Â
Restore the iPhone software. See “Updating and Restoring iPhone Software” on
@@ -7840,19 +7840,19 @@ page 120.
If iPhone won’t turn on, or if the display freezes or doesn’t respond
-Â Press and hold the Home
+Â Press and hold the Home
button below the screen for at least six seconds, until
the application you were using quits.
-Â If that doesn’t work, turn iPhone off and turn it on again. Press and hold the Sleep/
+Â If that doesn’t work, turn iPhone off and turn it on again. Press and hold the Sleep/
Wake button on top of iPhone for a few seconds until a red slider appears, and then
drag the slider. Then press and hold the Sleep/Wake button until the Apple logo
appears.
-Â If that doesn’t work, reset iPhone. Press and hold both the Sleep/Wake button and
+Â If that doesn’t work, reset iPhone. Press and hold both the Sleep/Wake button and
the Home
If iPhone continues to freeze or not respond after you reset it
-Â Reset iPhone settings. From the Home screen choose Settings > General > Reset >
+Â Reset iPhone settings. From the Home screen choose Settings > General > Reset >
Reset All Settings. All your preferences are reset, but no data or media is deleted.
-Â If that doesn’t work, erase all content on iPhone. From the Home screen choose
+Â If that doesn’t work, erase all content on iPhone. From the Home screen choose
Settings > General > Reset > “Erase All Content and Settings.” All your preferences
and other data are removed from iPhone.
-Â
+Â
If that doesn’t work, restore the iPhone software. See “Updating and Restoring
@@ -8137,10 +8137,10 @@ You can use iTunes to update or restore iPhone software. You should always updat
iPhone to use the latest software. You can also restore the software, which returns
iPhone to its original state.
-Â If you update, the iPhone software is updated but your settings and songs are not
+Â If you update, the iPhone software is updated but your settings and songs are not
affected.
-Â If you restore, all data is erased from iPhone, including songs, videos, contacts, photos,
+Â If you restore, all data is erased from iPhone, including songs, videos, contacts, photos,
calendar information, and any other data. All iPhone settings are restored to their
original state.
diff --git a/layout/pom.xml b/layout/pom.xml
index b904c9a018..39b1683b83 100644
--- a/layout/pom.xml
+++ b/layout/pom.xml
@@ -1,14 +1,18 @@
4.0.0
+
com.itextpdfroot
- 8.0.3
+ 8.0.4
+
layout
+
iText - layouthttps://itextpdf.com/
+
com.itextpdf
@@ -34,4 +38,15 @@
test
+
+
+
+
+ src/main/resources
+
+ **/*.json
+
+
+
+
\ No newline at end of file
diff --git a/layout/src/main/java/com/itextpdf/layout/Canvas.java b/layout/src/main/java/com/itextpdf/layout/Canvas.java
index 1125349fc1..7f4804c56c 100644
--- a/layout/src/main/java/com/itextpdf/layout/Canvas.java
+++ b/layout/src/main/java/com/itextpdf/layout/Canvas.java
@@ -162,6 +162,7 @@ public void enableAutoTagging(PdfPage page) {
logger.error(IoLogMessageConstant.PASSED_PAGE_SHALL_BE_ON_WHICH_CANVAS_WILL_BE_RENDERED);
}
this.page = page;
+ this.pdfCanvas.setDrawingOnPage(this.isAutoTaggingEnabled());
}
/**
diff --git a/layout/src/main/java/com/itextpdf/layout/RootElement.java b/layout/src/main/java/com/itextpdf/layout/RootElement.java
index 5ca1b3cec2..c3c4b5d63e 100644
--- a/layout/src/main/java/com/itextpdf/layout/RootElement.java
+++ b/layout/src/main/java/com/itextpdf/layout/RootElement.java
@@ -44,9 +44,9 @@ This file is part of the iText (R) project.
import com.itextpdf.layout.properties.VerticalAlignment;
import com.itextpdf.layout.renderer.IRenderer;
import com.itextpdf.layout.renderer.RootRenderer;
-import com.itextpdf.layout.tagging.LayoutTaggingHelper;
import com.itextpdf.layout.splitting.DefaultSplitCharacters;
import com.itextpdf.layout.splitting.ISplitCharacters;
+import com.itextpdf.layout.tagging.LayoutTaggingHelper;
import java.io.Closeable;
import java.io.IOException;
@@ -365,7 +365,7 @@ private static void traverseAndCallIso(PdfDocument pdfDocument, IRenderer render
if (renderer == null) {
return;
}
- pdfDocument.checkIsoConformance(renderer.getModelElement(), IsoKey.LAYOUT);
+ pdfDocument.checkIsoConformance(renderer, IsoKey.LAYOUT);
List renderers = renderer.getChildRenderers();
if (renderers == null) {
return;
diff --git a/layout/src/main/java/com/itextpdf/layout/exceptions/LayoutExceptionMessageConstant.java b/layout/src/main/java/com/itextpdf/layout/exceptions/LayoutExceptionMessageConstant.java
index 08e2074bc0..da1fc1e5b5 100644
--- a/layout/src/main/java/com/itextpdf/layout/exceptions/LayoutExceptionMessageConstant.java
+++ b/layout/src/main/java/com/itextpdf/layout/exceptions/LayoutExceptionMessageConstant.java
@@ -54,6 +54,7 @@ public final class LayoutExceptionMessageConstant {
public static final String INVALID_COLUMN_PROPERTIES =
"Invalid column-count/column-width/column-gap properties, they're absent or have negative value";
public static final String INVALID_FONT_PROPERTY_VALUE = "Invalid FONT property value type.";
+ public static final String TAGGING_HINTKEY_SHOULD_HAVE_ACCES = "TaggingHintKey should have accessibility properties" ;
private LayoutExceptionMessageConstant(){}
}
diff --git a/layout/src/main/java/com/itextpdf/layout/font/ComplexFontSelectorStrategy.java b/layout/src/main/java/com/itextpdf/layout/font/ComplexFontSelectorStrategy.java
index b1ff31969c..8419b2a13e 100644
--- a/layout/src/main/java/com/itextpdf/layout/font/ComplexFontSelectorStrategy.java
+++ b/layout/src/main/java/com/itextpdf/layout/font/ComplexFontSelectorStrategy.java
@@ -25,6 +25,7 @@ This file is part of the iText (R) project.
import com.itextpdf.io.font.otf.Glyph;
import com.itextpdf.io.util.TextUtil;
import com.itextpdf.kernel.font.PdfFont;
+import com.itextpdf.layout.font.selectorstrategy.FirstMatchFontSelectorStrategy;
import java.util.ArrayList;
import java.util.List;
@@ -33,7 +34,9 @@ This file is part of the iText (R) project.
* Complex FontSelectorStrategy split text based on {@link java.lang.Character.UnicodeScript}.
* If unicode script changes, a new font will be found.
* If there is no suitable font, only one notdef glyph from {@link FontSelector#bestMatch()} will be added.
+ * @deprecated was replaced by {@link FirstMatchFontSelectorStrategy}.
*/
+@Deprecated
public class ComplexFontSelectorStrategy extends FontSelectorStrategy {
private PdfFont font;
diff --git a/layout/src/main/java/com/itextpdf/layout/font/FontProvider.java b/layout/src/main/java/com/itextpdf/layout/font/FontProvider.java
index 4ac00f8c17..b70b3a65f2 100644
--- a/layout/src/main/java/com/itextpdf/layout/font/FontProvider.java
+++ b/layout/src/main/java/com/itextpdf/layout/font/FontProvider.java
@@ -22,19 +22,22 @@ This file is part of the iText (R) project.
*/
package com.itextpdf.layout.font;
+import com.itextpdf.commons.utils.FileUtil;
import com.itextpdf.io.font.FontCache;
import com.itextpdf.io.font.FontProgram;
import com.itextpdf.io.font.FontProgramFactory;
import com.itextpdf.io.font.PdfEncodings;
import com.itextpdf.io.font.Type1Font;
import com.itextpdf.io.font.constants.StandardFonts;
-import com.itextpdf.commons.utils.FileUtil;
import com.itextpdf.kernel.exceptions.PdfException;
import com.itextpdf.kernel.font.PdfFont;
import com.itextpdf.kernel.font.PdfFontFactory;
import com.itextpdf.kernel.font.PdfFontFactory.EmbeddingStrategy;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.layout.exceptions.LayoutExceptionMessageConstant;
+import com.itextpdf.layout.font.selectorstrategy.FirstMatchFontSelectorStrategy.FirstMathFontSelectorStrategyFactory;
+import com.itextpdf.layout.font.selectorstrategy.IFontSelectorStrategy;
+import com.itextpdf.layout.font.selectorstrategy.IFontSelectorStrategyFactory;
import java.io.IOException;
import java.util.ArrayList;
@@ -53,7 +56,7 @@ This file is part of the iText (R) project.
*
* It is allowed to use only one {@link FontProvider} per document. If additional fonts per element needed,
* another instance of {@link FontSet} can be used. For more details see {@link com.itextpdf.layout.properties.Property#FONT_SET},
- * {@link #getPdfFont(FontInfo, FontSet)}, {@link #getStrategy(String, List, FontCharacteristics, FontSet)}.
+ * {@link #getPdfFont(FontInfo, FontSet)}, {@link #createFontSelectorStrategy(List, FontCharacteristics, FontSet)}.
*
* Note, FontProvider does not close created {@link FontProgram}s, because of possible conflicts with {@link FontCache}.
*/
@@ -69,6 +72,8 @@ public class FontProvider {
protected final String defaultFontFamily;
protected final Map pdfFonts;
+ private IFontSelectorStrategyFactory fontSelectorStrategyFactory;
+
/**
* Creates a new instance of FontProvider.
*
@@ -105,6 +110,7 @@ public FontProvider(FontSet fontSet, String defaultFontFamily) {
pdfFonts = new HashMap<>();
fontSelectorCache = new FontSelectorCache(this.fontSet);
this.defaultFontFamily = defaultFontFamily;
+ this.fontSelectorStrategyFactory = new FirstMathFontSelectorStrategyFactory();
}
/**
@@ -364,7 +370,9 @@ public boolean getDefaultEmbeddingFlag() {
* font selector strategy instance and will not be otherwise preserved in font provider.
*
* @return {@link FontSelectorStrategy} instance.
+ * @deprecated use {@link #createFontSelectorStrategy(List, FontCharacteristics, FontSet)}
*/
+ @Deprecated
public FontSelectorStrategy getStrategy(String text, List fontFamilies, FontCharacteristics fc, FontSet additionalFonts) {
return new ComplexFontSelectorStrategy(text, getFontSelector(fontFamilies, fc, additionalFonts), this, additionalFonts);
}
@@ -379,7 +387,9 @@ public FontSelectorStrategy getStrategy(String text, List fontFamilies,
* @param fc instance of {@link FontCharacteristics} to create {@link FontSelector} for sequences of glyphs.
*
* @return {@link FontSelectorStrategy} instance.
+ * @deprecated use {@link #createFontSelectorStrategy(List, FontCharacteristics, FontSet)}
*/
+ @Deprecated
public FontSelectorStrategy getStrategy(String text, List fontFamilies, FontCharacteristics fc) {
return getStrategy(text, fontFamilies, fc, null);
}
@@ -393,11 +403,44 @@ public FontSelectorStrategy getStrategy(String text, List fontFamilies,
* @param fontFamilies target font families to create {@link FontSelector} for sequences of glyphs.
*
* @return {@link FontSelectorStrategy} instance.
+ * @deprecated use {@link #createFontSelectorStrategy(List, FontCharacteristics, FontSet)}
*/
+ @Deprecated
public FontSelectorStrategy getStrategy(String text, List fontFamilies) {
return getStrategy(text, fontFamilies, null);
}
+ /**
+ * Sets factory which will be used in {@link #createFontSelectorStrategy(List, FontCharacteristics, FontSet)}
+ * method.
+ *
+ * @param factory the factory which will be used to create font selector strategies
+ */
+ public void setFontSelectorStrategyFactory(IFontSelectorStrategyFactory factory) {
+ this.fontSelectorStrategyFactory = factory;
+ }
+
+ /**
+ * Creates the {@link IFontSelectorStrategy} to split text into sequences of glyphs, already tied
+ * to the fonts which contain them. The fonts can be taken from the added fonts to the font provider and
+ * are chosen based on font-families list and desired font characteristics.
+ *
+ * @param fontFamilies target font families to create {@link FontSelector} for sequences of glyphs.
+ * @param fc instance of {@link FontCharacteristics} to create {@link FontSelector} for sequences of glyphs.
+ * @param additionalFonts set which provides fonts additionally to the fonts added to font provider.
+ * Combined set of font provider fonts and additional fonts is used when choosing
+ * a single font for a sequence of glyphs. Additional fonts will only be used for the given
+ * font selector strategy instance and will not be otherwise preserved in font provider.
+ *
+ * @return {@link IFontSelectorStrategy} instance
+ */
+ public IFontSelectorStrategy createFontSelectorStrategy(List fontFamilies,
+ FontCharacteristics fc, FontSet additionalFonts) {
+
+ final FontSelector fontSelector = getFontSelector(fontFamilies, fc, additionalFonts);
+ return fontSelectorStrategyFactory.createFontSelectorStrategy(this, fontSelector, additionalFonts);
+ }
+
/**
* Create {@link FontSelector} or get from cache.
*
diff --git a/layout/src/main/java/com/itextpdf/layout/font/FontSelectorStrategy.java b/layout/src/main/java/com/itextpdf/layout/font/FontSelectorStrategy.java
index 00f07de09a..795e84f5a7 100644
--- a/layout/src/main/java/com/itextpdf/layout/font/FontSelectorStrategy.java
+++ b/layout/src/main/java/com/itextpdf/layout/font/FontSelectorStrategy.java
@@ -24,13 +24,16 @@ This file is part of the iText (R) project.
import com.itextpdf.io.font.otf.Glyph;
import com.itextpdf.kernel.font.PdfFont;
+import com.itextpdf.layout.font.selectorstrategy.IFontSelectorStrategy;
import java.util.List;
/**
* {@link FontSelectorStrategy} is responsible for splitting text into sub texts with one particular font.
* {@link #nextGlyphs()} will create next sub text and set current font.
+ * @deprecated replaced by {@link IFontSelectorStrategy}.
*/
+@Deprecated
public abstract class FontSelectorStrategy {
protected String text;
diff --git a/layout/src/main/java/com/itextpdf/layout/font/selectorstrategy/AbstractFontSelectorStrategy.java b/layout/src/main/java/com/itextpdf/layout/font/selectorstrategy/AbstractFontSelectorStrategy.java
new file mode 100644
index 0000000000..610d6417ba
--- /dev/null
+++ b/layout/src/main/java/com/itextpdf/layout/font/selectorstrategy/AbstractFontSelectorStrategy.java
@@ -0,0 +1,234 @@
+/*
+ This file is part of the iText (R) project.
+ Copyright (c) 1998-2024 Apryse Group NV
+ Authors: Apryse Software.
+
+ This program is offered under a commercial and under the AGPL license.
+ For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
+
+ AGPL licensing:
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+ */
+package com.itextpdf.layout.font.selectorstrategy;
+
+import com.itextpdf.commons.datastructures.Tuple2;
+import com.itextpdf.io.font.otf.Glyph;
+import com.itextpdf.io.font.otf.GlyphLine;
+import com.itextpdf.io.util.TextUtil;
+import com.itextpdf.kernel.font.PdfFont;
+import com.itextpdf.layout.font.FontInfo;
+import com.itextpdf.layout.font.FontProvider;
+import com.itextpdf.layout.font.FontSelector;
+import com.itextpdf.layout.font.FontSet;
+import com.itextpdf.layout.renderer.TextPreprocessingUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The class defines complex implementation of {@link IFontSelectorStrategy} which based on the following algorithm:
+ * 1. Find first significant symbol (not whitespace or special).
+ * 2. Find font which matches symbol according to passed {@link FontSelector}.
+ * 3. Try to append as many symbols as possible using the current font.
+ * 4. If symbol is not matched to the current font, go to step 1.
+ *
+ * Algorithm takes care of the case when there is no matched font for symbol or when diacritic
+ * from another font is used (previous symbol will be processed by diacritic's font).
+ */
+public abstract class AbstractFontSelectorStrategy implements IFontSelectorStrategy {
+ private final FontProvider fontProvider;
+ private final FontSet additionalFonts;
+ private final FontSelector fontSelector;
+
+ /**
+ * Creates a new instance of {@link AbstractFontSelectorStrategy}.
+ *
+ * @param fontProvider the font provider
+ * @param fontSelector the font selector
+ * @param additionalFonts the set of fonts to be used additionally to the fonts added to font provider.
+ */
+ public AbstractFontSelectorStrategy(FontProvider fontProvider, FontSelector fontSelector,
+ FontSet additionalFonts) {
+
+ this.fontProvider = fontProvider;
+ this.additionalFonts = additionalFonts;
+ this.fontSelector = fontSelector;
+ }
+
+ /**
+ * If it is necessary to provide a check that the best font for passed symbol equals to the current font.
+ * Result of checking is used to split text into parts in case if inequality.
+ *
+ * @return {@code true} if check is needed, otherwise {@code false}
+ */
+ protected abstract boolean isCurrentFontCheckRequired();
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public List> getGlyphLines(String text) {
+ List> result = new ArrayList<>();
+ int index = 0;
+ int indexDiacritic = -1;
+
+ while (index < text.length()) {
+ // Find the best font for first significant symbol
+ PdfFont currentFont = null;
+ int indexSignificant = nextSignificantIndex(index, text);
+ if (indexSignificant < text.length()) {
+ int codePoint = extractCodePoint(text, indexDiacritic == -1 ? indexSignificant : indexDiacritic);
+ currentFont = matchFont(codePoint, fontSelector, fontProvider, additionalFonts);
+ }
+
+ List resolvedGlyphs = new ArrayList<>();
+ // Try to append as many symbols as possible to the current font
+ if (currentFont != null) {
+ Character.UnicodeScript firstScript = null;
+ int to = indexSignificant;
+ boolean breakRequested = false;
+ for (int i = indexSignificant; i < text.length(); i++) {
+ int codePoint = extractCodePoint(text, i);
+ if (codePoint > 0xFFFF) {
+ i++;
+ }
+ if (isCurrentFontCheckRequired() && (i != indexDiacritic - 1)) {
+ if (currentFont != matchFont(codePoint, fontSelector, fontProvider, additionalFonts)) {
+ breakRequested = true;
+ }
+ }
+
+ if (i > indexDiacritic) {
+ if (TextUtil.isDiacritic(codePoint)) {
+ final PdfFont diacriticFont = matchFont(codePoint, fontSelector, fontProvider, additionalFonts);
+ // Diacritic font must contain previous symbol, if not, don't
+ // enable special logic for diacritic and process it as usual symbol
+ boolean isPreviousMatchFont =
+ i == 0 || diacriticFont == null || diacriticFont.containsGlyph(extractCodePoint(text, i - 1));
+ // If diacritic font equals to the current font or null, don't
+ // enable special logic for diacritic and process it as usual symbol
+ if (diacriticFont != null && diacriticFont != currentFont && isPreviousMatchFont) {
+ // If it's the first diacritic in a row, we want to break to try to find a better font for
+ // the previous letter during the next iteration
+ if (indexDiacritic != i - 1) {
+ breakRequested = true;
+ }
+ indexDiacritic = i;
+ if (breakRequested) {
+ to = i - 2;
+ }
+ }
+ } else {
+ indexDiacritic = -1;
+ }
+ }
+
+ Character.UnicodeScript currScript = Character.UnicodeScript.of(codePoint);
+ if (isSignificantUnicodeScript(currScript)) {
+ if (firstScript == null) {
+ firstScript = currScript;
+ } else if (firstScript != currScript) {
+ breakRequested = true;
+ }
+ }
+
+ if (breakRequested) {
+ break;
+ }
+
+ to = i;
+ }
+
+ if (to < index) {
+ continue;
+ }
+
+ int numOfAppendedGlyphs = currentFont.appendGlyphs(text, index, to, resolvedGlyphs);
+ index += numOfAppendedGlyphs;
+ }
+ // If no symbols were appended, try to append any symbols
+ if (resolvedGlyphs.isEmpty()) {
+ currentFont = getPdfFont(fontSelector.bestMatch(), fontProvider, additionalFonts);
+ if (index != indexSignificant) {
+ index += currentFont.appendGlyphs(text, index, indexSignificant - 1, resolvedGlyphs);
+ }
+ while (index <= indexSignificant && index < text.length()) {
+ index += currentFont.appendAnyGlyph(text, index, resolvedGlyphs);
+ }
+ }
+
+ GlyphLine tempGlyphLine = new GlyphLine(resolvedGlyphs);
+ GlyphLine finalGlyphLine = TextPreprocessingUtil.replaceSpecialWhitespaceGlyphs(tempGlyphLine, currentFont);
+ result.add(new Tuple2<>(finalGlyphLine, currentFont));
+ }
+
+ return result;
+ }
+
+ /**
+ * Finds the best font which matches passed symbol.
+ *
+ * @param codePoint the symbol to match
+ * @param fontSelector the font selector
+ * @param fontProvider the font provider
+ * @param additionalFonts the addition fonts
+ * @return font which matches the symbol
+ */
+ protected PdfFont matchFont(int codePoint, FontSelector fontSelector, FontProvider fontProvider, FontSet additionalFonts) {
+ PdfFont matchedFont = null;
+ for (FontInfo fontInfo : fontSelector.getFonts()) {
+ if (fontInfo.getFontUnicodeRange().contains(codePoint)) {
+ PdfFont temptFont = getPdfFont(fontInfo, fontProvider, additionalFonts);
+ Glyph glyph = temptFont.getGlyph(codePoint);
+ if (null != glyph && 0 != glyph.getCode()) {
+ matchedFont = temptFont;
+ break;
+ }
+ }
+ }
+ return matchedFont;
+ }
+
+ private static int nextSignificantIndex(int startIndex, String text) {
+ int nextValidChar = startIndex;
+ for (; nextValidChar < text.length(); nextValidChar++) {
+ if (!TextUtil.isWhitespaceOrNonPrintable(text.charAt(nextValidChar))) {
+ break;
+ }
+ }
+ return nextValidChar;
+ }
+
+ private static boolean isSignificantUnicodeScript(Character.UnicodeScript unicodeScript) {
+ // Character.UnicodeScript.UNKNOWN will be handled as significant unicode script
+ return unicodeScript != Character.UnicodeScript.COMMON && unicodeScript != Character.UnicodeScript.INHERITED;
+ }
+
+ private static int extractCodePoint(String text, int idx) {
+ return TextUtil.isSurrogatePair(text, idx) ? TextUtil.convertToUtf32(text, idx) : (int) text.charAt(idx);
+ }
+
+ /**
+ * Utility method to create PdfFont.
+ *
+ * @param fontInfo instance of FontInfo
+ *
+ * @return cached or just created PdfFont on success, otherwise null
+ *
+ * @see FontProvider#getPdfFont(FontInfo, FontSet)
+ */
+ private static PdfFont getPdfFont(FontInfo fontInfo, FontProvider fontProvider, FontSet additionalFonts) {
+ return fontProvider.getPdfFont(fontInfo, additionalFonts);
+ }
+}
diff --git a/layout/src/main/java/com/itextpdf/layout/font/selectorstrategy/BestMatchFontSelectorStrategy.java b/layout/src/main/java/com/itextpdf/layout/font/selectorstrategy/BestMatchFontSelectorStrategy.java
new file mode 100644
index 0000000000..eee25ce9d1
--- /dev/null
+++ b/layout/src/main/java/com/itextpdf/layout/font/selectorstrategy/BestMatchFontSelectorStrategy.java
@@ -0,0 +1,67 @@
+/*
+ This file is part of the iText (R) project.
+ Copyright (c) 1998-2024 Apryse Group NV
+ Authors: Apryse Software.
+
+ This program is offered under a commercial and under the AGPL license.
+ For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
+
+ AGPL licensing:
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+ */
+package com.itextpdf.layout.font.selectorstrategy;
+
+import com.itextpdf.layout.font.FontProvider;
+import com.itextpdf.layout.font.FontSelector;
+import com.itextpdf.layout.font.FontSet;
+
+/**
+ * The class implements strategy where the best font for each symbol is used if possible.
+ */
+public class BestMatchFontSelectorStrategy extends AbstractFontSelectorStrategy {
+
+ /**
+ * Creates a new instance of {@link BestMatchFontSelectorStrategy}.
+ *
+ * @param fontProvider the font provider
+ * @param fontSelector the font selector
+ * @param additionalFonts the set of fonts to be used additionally to the fonts added to font provider.
+ */
+ public BestMatchFontSelectorStrategy(FontProvider fontProvider, FontSelector fontSelector,
+ FontSet additionalFonts) {
+ super(fontProvider, fontSelector, additionalFonts);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected boolean isCurrentFontCheckRequired() {
+ return true;
+ }
+
+ /**
+ * The factory for {@link BestMatchFontSelectorStrategy}.
+ */
+ public static final class BestMatchFontSelectorStrategyFactory implements IFontSelectorStrategyFactory {
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public IFontSelectorStrategy createFontSelectorStrategy(FontProvider fontProvider, FontSelector fontSelector,
+ FontSet additionalFonts) {
+ return new BestMatchFontSelectorStrategy(fontProvider, fontSelector, additionalFonts);
+ }
+ }
+}
diff --git a/layout/src/main/java/com/itextpdf/layout/font/selectorstrategy/FirstMatchFontSelectorStrategy.java b/layout/src/main/java/com/itextpdf/layout/font/selectorstrategy/FirstMatchFontSelectorStrategy.java
new file mode 100644
index 0000000000..18e6e9fbe6
--- /dev/null
+++ b/layout/src/main/java/com/itextpdf/layout/font/selectorstrategy/FirstMatchFontSelectorStrategy.java
@@ -0,0 +1,67 @@
+/*
+ This file is part of the iText (R) project.
+ Copyright (c) 1998-2024 Apryse Group NV
+ Authors: Apryse Software.
+
+ This program is offered under a commercial and under the AGPL license.
+ For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
+
+ AGPL licensing:
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+ */
+package com.itextpdf.layout.font.selectorstrategy;
+
+import com.itextpdf.layout.font.FontProvider;
+import com.itextpdf.layout.font.FontSelector;
+import com.itextpdf.layout.font.FontSet;
+
+/**
+ * The class implements strategy where the first matched font is used to render as many glyphs as possible.
+ */
+public class FirstMatchFontSelectorStrategy extends AbstractFontSelectorStrategy {
+
+ /**
+ * Creates a new instance of {@link FirstMatchFontSelectorStrategy}.
+ *
+ * @param fontProvider the font provider
+ * @param fontSelector the font selector
+ * @param additionalFonts the set of fonts to be used additionally to the fonts added to font provider.
+ */
+ public FirstMatchFontSelectorStrategy(FontProvider fontProvider, FontSelector fontSelector,
+ FontSet additionalFonts) {
+ super(fontProvider, fontSelector, additionalFonts);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected boolean isCurrentFontCheckRequired() {
+ return false;
+ }
+
+ /**
+ * The factory for {@link FirstMatchFontSelectorStrategy}.
+ */
+ public static final class FirstMathFontSelectorStrategyFactory implements IFontSelectorStrategyFactory {
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public IFontSelectorStrategy createFontSelectorStrategy(FontProvider fontProvider, FontSelector fontSelector,
+ FontSet additionalFonts) {
+ return new FirstMatchFontSelectorStrategy(fontProvider, fontSelector, additionalFonts);
+ }
+ }
+}
diff --git a/layout/src/main/java/com/itextpdf/layout/font/selectorstrategy/IFontSelectorStrategy.java b/layout/src/main/java/com/itextpdf/layout/font/selectorstrategy/IFontSelectorStrategy.java
new file mode 100644
index 0000000000..07f4fe5f3e
--- /dev/null
+++ b/layout/src/main/java/com/itextpdf/layout/font/selectorstrategy/IFontSelectorStrategy.java
@@ -0,0 +1,42 @@
+/*
+ This file is part of the iText (R) project.
+ Copyright (c) 1998-2024 Apryse Group NV
+ Authors: Apryse Software.
+
+ This program is offered under a commercial and under the AGPL license.
+ For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
+
+ AGPL licensing:
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+ */
+package com.itextpdf.layout.font.selectorstrategy;
+
+import com.itextpdf.commons.datastructures.Tuple2;
+import com.itextpdf.io.font.otf.GlyphLine;
+import com.itextpdf.kernel.font.PdfFont;
+
+import java.util.List;
+
+/**
+ * The font selector strategy is responsible for splitting text into parts with one particular font.
+ */
+public interface IFontSelectorStrategy {
+ /**
+ * Converts text into glyphs with the best matching font.
+ *
+ * @param text the text to split
+ * @return the glyphs with the matching font attached
+ */
+ List> getGlyphLines(String text);
+}
diff --git a/layout/src/main/java/com/itextpdf/layout/font/selectorstrategy/IFontSelectorStrategyFactory.java b/layout/src/main/java/com/itextpdf/layout/font/selectorstrategy/IFontSelectorStrategyFactory.java
new file mode 100644
index 0000000000..9fb1bdae48
--- /dev/null
+++ b/layout/src/main/java/com/itextpdf/layout/font/selectorstrategy/IFontSelectorStrategyFactory.java
@@ -0,0 +1,43 @@
+/*
+ This file is part of the iText (R) project.
+ Copyright (c) 1998-2024 Apryse Group NV
+ Authors: Apryse Software.
+
+ This program is offered under a commercial and under the AGPL license.
+ For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
+
+ AGPL licensing:
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+ */
+package com.itextpdf.layout.font.selectorstrategy;
+
+import com.itextpdf.layout.font.FontProvider;
+import com.itextpdf.layout.font.FontSelector;
+import com.itextpdf.layout.font.FontSet;
+
+/**
+ * The factory for {@link IFontSelectorStrategy}.
+ */
+public interface IFontSelectorStrategyFactory {
+ /**
+ * Creates a new instance of {@link IFontSelectorStrategy}.
+ *
+ * @param fontProvider the font provider
+ * @param fontSelector the font selector
+ * @param additionalFonts the set of fonts to be used additionally to the fonts added to font provider.
+ *
+ * @return new instance of {@link IFontSelectorStrategy}
+ */
+ IFontSelectorStrategy createFontSelectorStrategy(FontProvider fontProvider, FontSelector fontSelector, FontSet additionalFonts);
+}
diff --git a/layout/src/main/java/com/itextpdf/layout/renderer/AbstractRenderer.java b/layout/src/main/java/com/itextpdf/layout/renderer/AbstractRenderer.java
index 277e1b375d..6ccff6e980 100644
--- a/layout/src/main/java/com/itextpdf/layout/renderer/AbstractRenderer.java
+++ b/layout/src/main/java/com/itextpdf/layout/renderer/AbstractRenderer.java
@@ -1822,7 +1822,7 @@ protected UnitValue[] getPaddings() {
}
/**
- * Applies given paddings on the given rectangle
+ * Applies given paddings to the given rectangle.
*
* @param rect a rectangle paddings will be applied on.
* @param paddings the paddings to be applied on the given rectangle
diff --git a/layout/src/main/java/com/itextpdf/layout/renderer/CollapsedTableBorders.java b/layout/src/main/java/com/itextpdf/layout/renderer/CollapsedTableBorders.java
index d6faedde1c..c2ceec7095 100644
--- a/layout/src/main/java/com/itextpdf/layout/renderer/CollapsedTableBorders.java
+++ b/layout/src/main/java/com/itextpdf/layout/renderer/CollapsedTableBorders.java
@@ -262,15 +262,10 @@ protected void buildBordersArrays(CellRenderer cell, int row, int col, int[] row
} while (j > 0 && rows.size() != nextCellRow &&
(j + (int) rows.get(nextCellRow)[j].getPropertyAsInteger(Property.COLSPAN) != col ||
- (int) nextCellRow - rows.get((int) nextCellRow)[j].getPropertyAsInteger(Property.ROWSPAN) + 1 + rowspansToDeduct[j] != row));
+ (int) nextCellRow - rows.get((int) nextCellRow)[j].getPropertyAsInteger(Property.ROWSPAN) + 1 != row));
// process only valid cells which hasn't been processed yet
if (j >= 0 && nextCellRow != rows.size() && nextCellRow > row) {
CellRenderer nextCell = rows.get(nextCellRow)[j];
- nextCell.setProperty(Property.ROWSPAN, ((int) nextCell.getPropertyAsInteger(Property.ROWSPAN)) - rowspansToDeduct[j]);
- int nextCellColspan = (int) nextCell.getPropertyAsInteger(Property.COLSPAN);
- for (int i = j; i < j + nextCellColspan; i++) {
- rowspansToDeduct[i] = 0;
- }
buildBordersArrays(nextCell, nextCellRow, true);
}
@@ -301,11 +296,6 @@ protected void buildBordersArrays(CellRenderer cell, int row, int col, int[] row
}
if (nextCellRow != rows.size()) {
CellRenderer nextCell = rows.get(nextCellRow)[col + currCellColspan];
- nextCell.setProperty(Property.ROWSPAN, ((int) nextCell.getPropertyAsInteger(Property.ROWSPAN)) - rowspansToDeduct[col + currCellColspan]);
- int nextCellColspan = (int) nextCell.getPropertyAsInteger(Property.COLSPAN);
- for (int i = col + currCellColspan; i < col + currCellColspan + nextCellColspan; i++) {
- rowspansToDeduct[i] = 0;
- }
buildBordersArrays(nextCell, nextCellRow, true);
}
}
diff --git a/layout/src/main/java/com/itextpdf/layout/renderer/TableBorders.java b/layout/src/main/java/com/itextpdf/layout/renderer/TableBorders.java
index c2ca1489b6..a6e3c725aa 100644
--- a/layout/src/main/java/com/itextpdf/layout/renderer/TableBorders.java
+++ b/layout/src/main/java/com/itextpdf/layout/renderer/TableBorders.java
@@ -155,6 +155,10 @@ public TableBorders(List rows, int numberOfColumns, Border[] tab
protected abstract float getCellVerticalAddition(float[] indents);
// endregion
+ /**
+ * @deprecated Remove rowspansToDeduct parameter which is not used anymore.
+ */
+ @Deprecated
protected abstract void buildBordersArrays(CellRenderer cell, int row, int col, int[] rowspansToDeduct);
protected abstract TableBorders updateBordersOnNewPage(boolean isOriginalNonSplitRenderer, boolean isFooterOrHeader, TableRenderer currentRenderer, TableRenderer headerRenderer, TableRenderer footerRenderer);
@@ -162,7 +166,6 @@ public TableBorders(List rows, int numberOfColumns, Border[] tab
protected TableBorders processAllBordersAndEmptyRows() {
CellRenderer[] currentRow;
- int[] rowspansToDeduct = new int[numberOfColumns];
int numOfRowsToRemove = 0;
if (!rows.isEmpty()) {
for (int row = startRow - largeTableIndexOffset; row <= finishRow - largeTableIndexOffset; row++) {
@@ -170,26 +173,19 @@ protected TableBorders processAllBordersAndEmptyRows() {
boolean hasCells = false;
for (int col = 0; col < numberOfColumns; col++) {
if (null != currentRow[col]) {
- int colspan = (int) currentRow[col].getPropertyAsInteger(Property.COLSPAN);
- if (rowspansToDeduct[col] > 0) {
- int rowspan = (int) currentRow[col].getPropertyAsInteger(Property.ROWSPAN) - rowspansToDeduct[col];
- if (rowspan < 1) {
- Logger logger = LoggerFactory.getLogger(TableRenderer.class);
- logger.warn(IoLogMessageConstant.UNEXPECTED_BEHAVIOUR_DURING_TABLE_ROW_COLLAPSING);
- rowspan = 1;
- }
- currentRow[col].setProperty(Property.ROWSPAN, rowspan);
- if (0 != numOfRowsToRemove) {
- removeRows(row - numOfRowsToRemove, numOfRowsToRemove);
- row -= numOfRowsToRemove;
- numOfRowsToRemove = 0;
- }
+ if (0 != numOfRowsToRemove) {
+ // Decrease rowspans if necessary
+ updateRowspanForNextNonEmptyCellInEachColumn(numOfRowsToRemove, row);
+
+ // Remove empty rows
+ removeRows(row - numOfRowsToRemove, numOfRowsToRemove);
+ row -= numOfRowsToRemove;
+ numOfRowsToRemove = 0;
}
- buildBordersArrays(currentRow[col], row, col, rowspansToDeduct);
+
+ buildBordersArrays(currentRow[col], row, col, null);
hasCells = true;
- for (int i = 0; i < colspan; i++) {
- rowspansToDeduct[col + i] = 0;
- }
+ int colspan = (int) currentRow[col].getPropertyAsInteger(Property.COLSPAN);
col += colspan - 1;
} else {
if (horizontalBorders.get(row).size() <= col) {
@@ -197,19 +193,17 @@ protected TableBorders processAllBordersAndEmptyRows() {
}
}
}
+
if (!hasCells) {
if (row == rows.size() - 1) {
- removeRows(row - rowspansToDeduct[0], rowspansToDeduct[0]);
+ removeRows(row - numOfRowsToRemove, numOfRowsToRemove);
// delete current row
- rows.remove(row - rowspansToDeduct[0]);
+ rows.remove(row - numOfRowsToRemove);
setFinishRow(finishRow - 1);
Logger logger = LoggerFactory.getLogger(TableRenderer.class);
logger.warn(IoLogMessageConstant.LAST_ROW_IS_NOT_COMPLETE);
} else {
- for (int i = 0; i < numberOfColumns; i++) {
- rowspansToDeduct[i]++;
- }
numOfRowsToRemove++;
}
}
@@ -221,17 +215,6 @@ protected TableBorders processAllBordersAndEmptyRows() {
return this;
}
- private void removeRows(int startRow, int numOfRows) {
- for (int row = startRow; row < startRow + numOfRows; row++) {
- rows.remove(startRow);
- horizontalBorders.remove(startRow + 1);
- for (int j = 0; j <= numberOfColumns; j++) {
- verticalBorders.get(j).remove(startRow + 1);
- }
- }
- setFinishRow(finishRow - numOfRows);
- }
-
// region init
protected TableBorders initializeBorders() {
List tempBorders;
@@ -415,4 +398,62 @@ public float[] getCellBorderIndents(int row, int col, int rowspan, int colspan)
return indents;
}
// endregion
+
+ private void removeRows(int startRow, int numOfRows) {
+ for (int row = startRow; row < startRow + numOfRows; row++) {
+ rows.remove(startRow);
+ horizontalBorders.remove(startRow + 1);
+ for (int j = 0; j <= numberOfColumns; j++) {
+ verticalBorders.get(j).remove(startRow + 1);
+ }
+ }
+ setFinishRow(finishRow - numOfRows);
+ }
+
+ private void updateRowspanForNextNonEmptyCellInEachColumn(int numOfRowsToRemove, int row) {
+ // We go by columns in a current row which is not empty. For each column we look for
+ // a non-empty cell going up by rows (going down in a table). For each such cell we
+ // collect data to be able to analyze its rowspan.
+
+ // Iterate by columns
+ int c = 0;
+ while (c < numberOfColumns) {
+ int r = row;
+ CellRenderer[] cr = null;
+ // Look for non-empty cell in a column
+ while (r < rows.size() && (cr == null || cr[c] == null)) {
+ cr = rows.get(r);
+ ++r;
+ }
+
+ // Found a cell
+ if (cr != null && cr[c] != null) {
+ CellRenderer cell = cr[c];
+ final int origRowspan = (int) cell.getPropertyAsInteger(Property.ROWSPAN);
+ int spansToRestore = 0;
+ // Here we analyze whether current cell's rowspan touches a non-empty row before
+ // numOfRowsToRemove. If it doesn't touch it we will need to 'restore' a few
+ // rowspans which is a difference between the current (non-empty) row and the row
+ // where we found non-empty cell for this column
+ if (row - numOfRowsToRemove < r - origRowspan) {
+ spansToRestore = r - row - 1;
+ }
+
+ int rowspan = origRowspan;
+ rowspan = rowspan - numOfRowsToRemove;
+ if (rowspan < 1) {
+ rowspan = 1;
+ }
+ rowspan += spansToRestore;
+ rowspan = Math.min(rowspan, origRowspan);
+
+ cell.setProperty(Property.ROWSPAN, rowspan);
+
+ final int colspan = (int) cell.getPropertyAsInteger(Property.COLSPAN);
+ c += colspan;
+ } else {
+ ++c;
+ }
+ }
+ }
}
diff --git a/layout/src/main/java/com/itextpdf/layout/renderer/TableRenderer.java b/layout/src/main/java/com/itextpdf/layout/renderer/TableRenderer.java
index d3d847fe3c..781850e4b9 100644
--- a/layout/src/main/java/com/itextpdf/layout/renderer/TableRenderer.java
+++ b/layout/src/main/java/com/itextpdf/layout/renderer/TableRenderer.java
@@ -908,13 +908,13 @@ public LayoutResult layout(LayoutContext layoutContext) {
? this
: firstCauseOfNothing);
} else {
- int status = ((occupiedArea.getBBox().getHeight()
- - (null == footerRenderer ? 0 : footerRenderer.getOccupiedArea().getBBox().getHeight())
- - (null == headerRenderer ? 0 : headerRenderer.getOccupiedArea().getBBox().getHeight() - headerRenderer.bordersHandler.getMaxBottomWidth())
- == 0)
- && (isAndWasComplete || isFirstOnThePage))
- ? LayoutResult.NOTHING
- : LayoutResult.PARTIAL;
+ float footerHeight = null == footerRenderer ? 0 : footerRenderer.getOccupiedArea().getBBox().getHeight();
+ float headerHeight = null == headerRenderer ? 0 : headerRenderer.getOccupiedArea().getBBox().getHeight()
+ - headerRenderer.bordersHandler.getMaxBottomWidth();
+ float captionHeight = null == captionRenderer ? 0 : captionRenderer.getOccupiedArea().getBBox().getHeight();
+ float heightDiff = occupiedArea.getBBox().getHeight() - footerHeight - headerHeight - captionHeight;
+ int status = Float.compare(0,heightDiff) == 0 && (isAndWasComplete || isFirstOnThePage) ?
+ LayoutResult.NOTHING : LayoutResult.PARTIAL;
if ((status == LayoutResult.NOTHING && Boolean.TRUE.equals(getPropertyAsBoolean(Property.FORCED_PLACEMENT)))
|| wasHeightClipped) {
if (wasHeightClipped) {
diff --git a/layout/src/main/java/com/itextpdf/layout/renderer/TextPreprocessingUtil.java b/layout/src/main/java/com/itextpdf/layout/renderer/TextPreprocessingUtil.java
index 105ce977a6..8542957489 100644
--- a/layout/src/main/java/com/itextpdf/layout/renderer/TextPreprocessingUtil.java
+++ b/layout/src/main/java/com/itextpdf/layout/renderer/TextPreprocessingUtil.java
@@ -53,7 +53,8 @@ public static GlyphLine replaceSpecialWhitespaceGlyphs(GlyphLine line, PdfFont f
final Integer xAdvance = calculateXAdvancement(spaceWidth, isMonospaceFont, glyph);
final boolean isSpecialWhitespaceGlyph = xAdvance != null;
if (isSpecialWhitespaceGlyph) {
- Glyph newGlyph = new Glyph(space, glyph.getUnicode());
+ Glyph newGlyph = new Glyph(space);
+ newGlyph.setChars(glyph.getChars());
assert xAdvance <= Short.MAX_VALUE && xAdvance >= Short.MIN_VALUE;
newGlyph.setXAdvance((short) (int) xAdvance);
line.set(i, newGlyph);
diff --git a/layout/src/main/java/com/itextpdf/layout/renderer/TextRenderer.java b/layout/src/main/java/com/itextpdf/layout/renderer/TextRenderer.java
index f83423ec1b..89ae4b24b4 100644
--- a/layout/src/main/java/com/itextpdf/layout/renderer/TextRenderer.java
+++ b/layout/src/main/java/com/itextpdf/layout/renderer/TextRenderer.java
@@ -24,6 +24,7 @@ This file is part of the iText (R) project.
import com.itextpdf.commons.actions.contexts.IMetaInfo;
import com.itextpdf.commons.actions.sequence.SequenceId;
+import com.itextpdf.commons.datastructures.Tuple2;
import com.itextpdf.commons.utils.MessageFormatUtil;
import com.itextpdf.io.font.FontMetrics;
import com.itextpdf.io.font.FontProgram;
@@ -48,8 +49,8 @@ This file is part of the iText (R) project.
import com.itextpdf.layout.exceptions.LayoutExceptionMessageConstant;
import com.itextpdf.layout.font.FontCharacteristics;
import com.itextpdf.layout.font.FontProvider;
-import com.itextpdf.layout.font.FontSelectorStrategy;
import com.itextpdf.layout.font.FontSet;
+import com.itextpdf.layout.font.selectorstrategy.IFontSelectorStrategy;
import com.itextpdf.layout.hyphenation.Hyphenation;
import com.itextpdf.layout.hyphenation.HyphenationConfig;
import com.itextpdf.layout.layout.LayoutArea;
@@ -983,19 +984,19 @@ public void draw(DrawContext drawContext) {
canvas.endText().restoreState();
endElementOpacityApplying(drawContext);
+ if (isTagged) {
+ canvas.closeTag();
+ }
+
Object underlines = this.getProperty(Property.UNDERLINE);
if (underlines instanceof List) {
for (Object underline : (List) underlines) {
if (underline instanceof Underline) {
- drawSingleUnderline((Underline) underline, fontColor, canvas, fontSize.getValue(), italicSimulation ? ITALIC_ANGLE : 0);
+ drawAndTagSingleUnderline(drawContext.isTaggingEnabled(), (Underline) underline, fontColor, canvas, fontSize.getValue(), italicSimulation ? ITALIC_ANGLE : 0);
}
}
} else if (underlines instanceof Underline) {
- drawSingleUnderline((Underline) underlines, fontColor, canvas, fontSize.getValue(), italicSimulation ? ITALIC_ANGLE : 0);
- }
-
- if (isTagged) {
- canvas.closeTag();
+ drawAndTagSingleUnderline(drawContext.isTaggingEnabled(), (Underline) underlines, fontColor, canvas, fontSize.getValue(), italicSimulation ? ITALIC_ANGLE : 0);
}
}
@@ -1526,17 +1527,15 @@ protected boolean resolveFonts(List addTo) {
throw new IllegalStateException(
LayoutExceptionMessageConstant.FONT_PROVIDER_NOT_SET_FONT_FAMILY_NOT_RESOLVED);
}
- FontCharacteristics fc = createFontCharacteristics();
- FontSelectorStrategy strategy = provider.getStrategy(strToBeConverted, Arrays.asList((String[])font), fc, fontSet);
// process empty renderers because they can have borders or paddings with background to be drawn
if (null == strToBeConverted || strToBeConverted.isEmpty()) {
addTo.add(this);
} else {
- while (!strategy.endOfText()) {
- GlyphLine nextGlyphs = new GlyphLine(strategy.nextGlyphs());
- PdfFont currentFont = strategy.getCurrentFont();
- GlyphLine newGlyphs = TextPreprocessingUtil.replaceSpecialWhitespaceGlyphs(nextGlyphs, currentFont);
- TextRenderer textRenderer = createCopy(newGlyphs, currentFont);
+ FontCharacteristics fc = createFontCharacteristics();
+ IFontSelectorStrategy strategy = provider.createFontSelectorStrategy(Arrays.asList((String[])font), fc, fontSet);
+ List> subTextWithFont = strategy.getGlyphLines(strToBeConverted);
+ for (Tuple2 subText : subTextWithFont) {
+ TextRenderer textRenderer = createCopy(subText.getFirst(), subText.getSecond());
addTo.add(textRenderer);
}
}
@@ -1621,18 +1620,11 @@ static boolean codePointIsOfSpecialScript(int codePoint) {
@Override
PdfFont resolveFirstPdfFont(String[] font, FontProvider provider, FontCharacteristics fc, FontSet additionalFonts) {
- FontSelectorStrategy strategy = provider.getStrategy(strToBeConverted, Arrays.asList(font), fc, additionalFonts);
- List resolvedGlyphs;
- PdfFont currentFont;
- //try to find first font that can render at least one glyph.
- while (!strategy.endOfText()) {
- resolvedGlyphs = strategy.nextGlyphs();
- currentFont = strategy.getCurrentFont();
- for (Glyph glyph : resolvedGlyphs) {
- if (currentFont.containsGlyph(glyph.getUnicode())) {
- return currentFont;
- }
- }
+ IFontSelectorStrategy strategy = provider.createFontSelectorStrategy(Arrays.asList(font), fc, additionalFonts);
+ // Try to find first font that can render at least one glyph.
+ final List> glyphLines = strategy.getGlyphLines(strToBeConverted);
+ if (!glyphLines.isEmpty()) {
+ return glyphLines.get(0).getSecond();
}
return super.resolveFirstPdfFont(font, provider, fc, additionalFonts);
}
@@ -1666,6 +1658,18 @@ boolean[] isStartsWithSplitCharWhiteSpaceAndEndsWithSplitChar(ISplitCharacters s
}
}
+ private void drawAndTagSingleUnderline(boolean isTagged, Underline underline,
+ TransparentColor fontStrokeColor, PdfCanvas canvas,
+ float fontSize, float italicAngleTan) {
+ if (isTagged) {
+ canvas.openTag(new CanvasArtifact());
+ }
+ drawSingleUnderline(underline, fontStrokeColor, canvas, fontSize, italicAngleTan);
+ if (isTagged) {
+ canvas.closeTag();
+ }
+ }
+
private float getCharWidth(Glyph g, float fontSize, Float hScale, Float characterSpacing, Float wordSpacing) {
if (hScale == null)
hScale = 1f;
diff --git a/layout/src/main/java/com/itextpdf/layout/tagging/LayoutTaggingHelper.java b/layout/src/main/java/com/itextpdf/layout/tagging/LayoutTaggingHelper.java
index ebb72fc5a6..e83bb1765d 100644
--- a/layout/src/main/java/com/itextpdf/layout/tagging/LayoutTaggingHelper.java
+++ b/layout/src/main/java/com/itextpdf/layout/tagging/LayoutTaggingHelper.java
@@ -55,21 +55,21 @@ This file is part of the iText (R) project.
* tree for layout element (with keeping right order for tags).
*/
public class LayoutTaggingHelper {
- private TagStructureContext context;
- private PdfDocument document;
- private boolean immediateFlush;
+ private final TagStructureContext context;
+ private final PdfDocument document;
+ private final boolean immediateFlush;
// kidsHints and parentHints fields represent tree of TaggingHintKey, where parentHints
// stores a parent for the key, and kidsHints stores kids for key.
- private Map> kidsHints;
- private Map parentHints;
+ private final Map> kidsHints;
+ private final Map parentHints;
- private Map autoTaggingPointerSavedPosition;
+ private final Map autoTaggingPointerSavedPosition;
- private Map> taggingRules;
+ private final Map> taggingRules;
// dummiesForPreExistingTags is used to process TaggingDummyElement
- private Map dummiesForPreExistingTags;
+ private final Map dummiesForPreExistingTags;
private final int RETVAL_NO_PARENT = -1;
private final int RETVAL_PARENT_AND_KID_FINISHED = -2;
@@ -334,6 +334,7 @@ public void releaseAllHints() {
public boolean createTag(IRenderer renderer, TagTreePointer tagPointer) {
TaggingHintKey hintKey = getHintKey(renderer);
+
boolean noHint = hintKey == null;
if (noHint) {
hintKey = getOrCreateHintKey(renderer, false);
@@ -584,6 +585,7 @@ private boolean createSingleTag(TaggingHintKey hintKey, TagTreePointer tagPointe
}
tagPointer.addTag(ind, modelElement.getAccessibilityProperties());
+ hintKey.setTagPointer(new TagTreePointer(tagPointer));
if (hintKey.getOverriddenRole() != null) {
tagPointer.setRole(hintKey.getOverriddenRole());
}
@@ -763,6 +765,7 @@ private void registerRules(PdfVersion pdfVersion) {
registerSingleRule(StandardRoles.TABLE, tableRule);
registerSingleRule(StandardRoles.TFOOT, tableRule);
registerSingleRule(StandardRoles.THEAD, tableRule);
+ registerSingleRule(StandardRoles.TH, new THTaggingRule());
if (pdfVersion.compareTo(PdfVersion.PDF_1_5) < 0 ) {
TableTaggingPriorToOneFiveVersionRule priorToOneFiveRule = new TableTaggingPriorToOneFiveVersionRule();
registerSingleRule(StandardRoles.TABLE, priorToOneFiveRule);
diff --git a/layout/src/main/java/com/itextpdf/layout/tagging/THTaggingRule.java b/layout/src/main/java/com/itextpdf/layout/tagging/THTaggingRule.java
new file mode 100644
index 0000000000..612b2cbbf4
--- /dev/null
+++ b/layout/src/main/java/com/itextpdf/layout/tagging/THTaggingRule.java
@@ -0,0 +1,86 @@
+/*
+ This file is part of the iText (R) project.
+ Copyright (c) 1998-2024 Apryse Group NV
+ Authors: Apryse Software.
+
+ This program is offered under a commercial and under the AGPL license.
+ For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
+
+ AGPL licensing:
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+ */
+package com.itextpdf.layout.tagging;
+
+import com.itextpdf.kernel.pdf.PdfName;
+import com.itextpdf.kernel.pdf.tagging.PdfStructureAttributes;
+import com.itextpdf.kernel.pdf.tagging.StandardRoles;
+import com.itextpdf.kernel.pdf.tagutils.AccessibilityProperties;
+import com.itextpdf.layout.exceptions.LayoutExceptionMessageConstant;
+
+import java.util.List;
+
+/**
+ * Used to automatically add scope attribute to TH cells.
+ *
+ * This behavior is enabled by default. In the future, we maybe want to expand this with a heuristic
+ * which determines the scope based on the position of all the TH cells in the table.
+ *
+ * If the scope attribute is already present, it will not be modified.
+ * If the scope attribute is not present, it will be added with the value "Column".
+ * If the scope attribute is present with the value "None", it will be removed.
+ */
+class THTaggingRule implements ITaggingRule {
+
+
+ /**
+ * Creates a new {@link THTaggingRule} instance.
+ */
+ THTaggingRule() {
+ super();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean onTagFinish(LayoutTaggingHelper taggingHelper, TaggingHintKey taggingHintKey) {
+ if (taggingHintKey.getAccessibilityProperties() == null) {
+ throw new IllegalArgumentException(LayoutExceptionMessageConstant.TAGGING_HINTKEY_SHOULD_HAVE_ACCES);
+ }
+ final List attributesList = taggingHintKey.getAccessibilityProperties().getAttributesList();
+
+ for (PdfStructureAttributes attributes : attributesList) {
+ final PdfName scopeValue = attributes.getPdfObject().getAsName(PdfName.Scope);
+ // the scope None is used to build complicated tables where TD cells don't refer to
+ // the TH cell in the TD cells column or row
+ if (scopeValue != null && !PdfName.None.equals(scopeValue)) {
+ return true;
+ }
+ if (PdfName.None.equals(scopeValue)) {
+ attributes.removeAttribute(PdfName.Scope.getValue());
+ return true;
+ }
+ }
+ if (taggingHintKey.getTagPointer() == null) {
+ return true;
+ }
+
+ final AccessibilityProperties properties = taggingHintKey.getAccessibilityProperties();
+ final PdfStructureAttributes atr = new PdfStructureAttributes(StandardRoles.TABLE);
+ atr.addEnumAttribute(PdfName.Scope.getValue(), PdfName.Column.getValue());
+ properties.addAttributes(atr);
+ taggingHintKey.getTagPointer().applyProperties(properties);
+ return true;
+ }
+}
diff --git a/layout/src/main/java/com/itextpdf/layout/tagging/TableTaggingRule.java b/layout/src/main/java/com/itextpdf/layout/tagging/TableTaggingRule.java
index 38946ffec7..f7b1ea9310 100644
--- a/layout/src/main/java/com/itextpdf/layout/tagging/TableTaggingRule.java
+++ b/layout/src/main/java/com/itextpdf/layout/tagging/TableTaggingRule.java
@@ -44,55 +44,45 @@ public boolean onTagFinish(LayoutTaggingHelper taggingHelper, TaggingHintKey tab
List tableCellTagsUnindexed = new ArrayList<>();
List nonCellKids = new ArrayList<>();
for (TaggingHintKey kidKey : kidKeys) {
- if (StandardRoles.TD.equals(kidKey.getAccessibleElement().getAccessibilityProperties().getRole())
- || StandardRoles.TH.equals(kidKey.getAccessibleElement().getAccessibilityProperties().getRole())) {
- if (kidKey.getAccessibleElement() instanceof Cell) {
- Cell cell = (Cell) kidKey.getAccessibleElement();
- int rowInd = cell.getRow();
- int colInd = cell.getCol();
- TreeMap rowTags = tableTags.get(rowInd);
- if (rowTags == null) {
- rowTags = new TreeMap<>();
- tableTags.put(rowInd, rowTags);
- }
- rowTags.put(colInd, kidKey);
- } else {
- tableCellTagsUnindexed.add(kidKey);
+ final String kidRole = getKidRole(kidKey,taggingHelper);
+ final boolean isCell = StandardRoles.TD.equals(kidRole) || StandardRoles.TH.equals(kidRole);
+ if (isCell && kidKey.getAccessibleElement() instanceof Cell) {
+ final Cell cell = (Cell) kidKey.getAccessibleElement();
+ final int rowInd = cell.getRow();
+ final int colInd = cell.getCol();
+ TreeMap rowTags = tableTags.get(rowInd);
+ if (rowTags == null) {
+ rowTags = new TreeMap<>();
+ tableTags.put(rowInd, rowTags);
}
-
+ rowTags.put(colInd, kidKey);
+ } else if (isCell) {
+ tableCellTagsUnindexed.add(kidKey);
} else {
nonCellKids.add(kidKey);
}
}
- boolean createTBody = true;
- if (tableHintKey.getAccessibleElement() instanceof Table) {
- Table modelElement = (Table) tableHintKey.getAccessibleElement();
- createTBody = modelElement.getHeader() != null && !modelElement.isSkipFirstHeader()
- || modelElement.getFooter() != null && !modelElement.isSkipLastFooter();
- }
- TaggingDummyElement tbodyTag = null;
- tbodyTag = new TaggingDummyElement(createTBody ? StandardRoles.TBODY : null);
-
+ TaggingDummyElement tbodyTag = getTbodyTag(tableHintKey);
for (TaggingHintKey nonCellKid : nonCellKids) {
- String kidRole = nonCellKid.getAccessibleElement().getAccessibilityProperties().getRole();
- if (!StandardRoles.THEAD.equals(kidRole) && !StandardRoles.TFOOT.equals(kidRole) && !StandardRoles.CAPTION.equals(kidRole)) {
+ String kidRole = getKidRole(nonCellKid,taggingHelper);
+ if (!StandardRoles.THEAD.equals(kidRole) && !StandardRoles.TFOOT.equals(kidRole)
+ && !StandardRoles.CAPTION.equals(kidRole)) {
// In usual cases it isn't expected that this for loop will work, but it is possible to
// create custom tag hierarchy by specifying role, and put any child to tableHintKey
taggingHelper.moveKidHint(nonCellKid, tableHintKey);
}
}
for (TaggingHintKey nonCellKid : nonCellKids) {
- String kidRole = nonCellKid.getAccessibleElement().getAccessibilityProperties().getRole();
- if (StandardRoles.THEAD.equals(kidRole)) {
+ if (StandardRoles.THEAD.equals(getKidRole(nonCellKid,taggingHelper))) {
taggingHelper.moveKidHint(nonCellKid, tableHintKey);
}
}
- taggingHelper.addKidsHint(tableHintKey, Collections.singletonList(LayoutTaggingHelper.getOrCreateHintKey(tbodyTag)), -1);
+ taggingHelper.addKidsHint(tableHintKey,
+ Collections.singletonList(LayoutTaggingHelper.getOrCreateHintKey(tbodyTag)), -1);
for (TaggingHintKey nonCellKid : nonCellKids) {
- String kidRole = nonCellKid.getAccessibleElement().getAccessibilityProperties().getRole();
- if (StandardRoles.TFOOT.equals(kidRole)) {
+ if (StandardRoles.TFOOT.equals(getKidRole(nonCellKid,taggingHelper))) {
taggingHelper.moveKidHint(nonCellKid, tableHintKey);
}
}
@@ -112,8 +102,7 @@ public boolean onTagFinish(LayoutTaggingHelper taggingHelper, TaggingHintKey tab
}
for (TaggingHintKey nonCellKid : nonCellKids) {
- String kidRole = nonCellKid.getAccessibleElement().getAccessibilityProperties().getRole();
- if (StandardRoles.CAPTION.equals(kidRole)) {
+ if (StandardRoles.CAPTION.equals(getKidRole(nonCellKid,taggingHelper))) {
moveCaption(taggingHelper, nonCellKid, tableHintKey);
}
}
@@ -121,7 +110,34 @@ public boolean onTagFinish(LayoutTaggingHelper taggingHelper, TaggingHintKey tab
return true;
}
- private static void moveCaption(LayoutTaggingHelper taggingHelper, TaggingHintKey caption, TaggingHintKey tableHintKey) {
+ private static String getKidRole(TaggingHintKey kidKey, LayoutTaggingHelper helper) {
+ return helper
+ .getPdfDocument()
+ .getTagStructureContext()
+ .resolveMappingToStandardOrDomainSpecificRole(kidKey.getAccessibilityProperties().getRole(),null)
+ .getRole();
+ }
+
+ /**
+ * Creates a dummy element with {@link StandardRoles#TBODY} role if needed.
+ * Otherwise, returns a dummy element with a null role.
+ *
+ * @param tableHintKey the hint key of the table.
+ *
+ * @return a dummy element with {@link StandardRoles#TBODY} role if needed.
+ */
+ private static TaggingDummyElement getTbodyTag(TaggingHintKey tableHintKey) {
+ boolean createTBody = true;
+ if (tableHintKey.getAccessibleElement() instanceof Table) {
+ Table modelElement = (Table) tableHintKey.getAccessibleElement();
+ createTBody = modelElement.getHeader() != null && !modelElement.isSkipFirstHeader()
+ || modelElement.getFooter() != null && !modelElement.isSkipLastFooter();
+ }
+ return new TaggingDummyElement(createTBody ? StandardRoles.TBODY : null);
+ }
+
+ private static void moveCaption(LayoutTaggingHelper taggingHelper, TaggingHintKey caption,
+ TaggingHintKey tableHintKey) {
if (!(tableHintKey.getAccessibleElement() instanceof Table)) {
return;
}
@@ -142,4 +158,5 @@ private static void moveCaption(LayoutTaggingHelper taggingHelper, TaggingHintKe
taggingHelper.moveKidHint(caption, tableHintKey);
}
}
+
}
diff --git a/layout/src/main/java/com/itextpdf/layout/tagging/TaggingHintKey.java b/layout/src/main/java/com/itextpdf/layout/tagging/TaggingHintKey.java
index 3949ba8c6f..24a472bf77 100644
--- a/layout/src/main/java/com/itextpdf/layout/tagging/TaggingHintKey.java
+++ b/layout/src/main/java/com/itextpdf/layout/tagging/TaggingHintKey.java
@@ -22,6 +22,8 @@ This file is part of the iText (R) project.
*/
package com.itextpdf.layout.tagging;
+import com.itextpdf.kernel.pdf.tagutils.AccessibilityProperties;
+import com.itextpdf.kernel.pdf.tagutils.TagTreePointer;
import com.itextpdf.layout.element.IElement;
import com.itextpdf.layout.renderer.IRenderer;
import com.itextpdf.layout.renderer.RootRenderer;
@@ -36,6 +38,7 @@ public final class TaggingHintKey {
private boolean isFinished;
private String overriddenRole;
private boolean elementBasedFinishingOnly;
+ private TagTreePointer tagPointer;
/**
* Instantiate a new {@link TaggingHintKey} instance.
@@ -57,6 +60,31 @@ public IAccessibleElement getAccessibleElement() {
return elem;
}
+
+ /**
+ * Gets the TagTreePointer.
+ *
+ * @return the {@link TagTreePointer} or null if there is no associated one yet.
+ */
+ public TagTreePointer getTagPointer() {
+ return tagPointer;
+ }
+
+ /**
+ * Sets the TagTreePointer.
+ *
+ * @param tag the TagTreePointer to set.
+ */
+ public void setTagPointer(TagTreePointer tag) {
+ this.tagPointer = tag;
+ }
+
+ AccessibilityProperties getAccessibilityProperties() {
+ if (elem == null){
+ return null;
+ }
+ return elem.getAccessibilityProperties();
+ }
/**
* Retrieve hint key finished flag.
*
diff --git a/layout/src/main/resources/META-INF/native-image/resource-config.json b/layout/src/main/resources/META-INF/native-image/resource-config.json
new file mode 100644
index 0000000000..2ea7c49ea2
--- /dev/null
+++ b/layout/src/main/resources/META-INF/native-image/resource-config.json
@@ -0,0 +1,349 @@
+{
+ "resources":{
+ "includes":[{
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/external/classes.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/af.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/as.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/bg.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/bn.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/ca.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/cop.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/cs.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/cy.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/da.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/de.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/de_1901.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/de_CH.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/de_DR.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/el.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/el_Polyton.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/en.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/en_GB.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/en_US.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/eo.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/es.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/et.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/eu.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/fi.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/fr.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/ga.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/gl.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/grc.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/gu.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/hi.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/hr.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/hsb.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/hu.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/hy.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/ia.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/id.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/is.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/it.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/kmr.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/kn.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/la.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/lo.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/lt.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/lv.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/ml.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/mn.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/mr.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/nb.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/nl.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/nn.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/no.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/or.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/pa.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/pl.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/pt.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/ro.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/ru.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/sa.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/sk.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/sl.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/sr_Cyrl.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/sr_Latn.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/sv.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/ta.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/te.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/tk.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/tr.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/uk.xml\\E"
+ }, {
+ "condition": {
+ "typeReachable": "com.itextpdf.layout.hyphenation.Hyphenator"
+ },
+ "pattern":"\\Qcom/itextpdf/hyph/zh_Latn.xml\\E"
+ }]}
+}
diff --git a/layout/src/test/java/com/itextpdf/layout/CanvasTest.java b/layout/src/test/java/com/itextpdf/layout/CanvasTest.java
index 0f273699e2..95eab4717d 100644
--- a/layout/src/test/java/com/itextpdf/layout/CanvasTest.java
+++ b/layout/src/test/java/com/itextpdf/layout/CanvasTest.java
@@ -33,6 +33,8 @@ This file is part of the iText (R) project.
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfPage;
+import com.itextpdf.kernel.pdf.PdfResources;
+import com.itextpdf.kernel.pdf.PdfStream;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.kernel.pdf.action.PdfAction;
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
@@ -376,4 +378,43 @@ public void addImageElemMethodLinkingTest() {
Assert.assertTrue(events.get(1) instanceof TestProductEvent);
}
}
+
+ @Test
+ public void drawingOnPageReuseCanvas() {
+ try (PdfDocument pdfDocument = new PdfDocument(new PdfWriter(new ByteArrayOutputStream()))) {
+ ExposedPdfCanvas canvas = new ExposedPdfCanvas(pdfDocument.addNewPage());
+ Assert.assertTrue(canvas.getDrawingOnPage());
+ try (Canvas canvas1 = new Canvas(canvas, new Rectangle(200, 200, 200, 200))) {
+ Assert.assertTrue(((ExposedPdfCanvas) canvas1.pdfCanvas).getDrawingOnPage());
+ }
+ }
+ }
+
+ @Test
+ public void notDrawingOnPageReuseCanvas() {
+ try (PdfDocument pdfDocument = new PdfDocument(new PdfWriter(new ByteArrayOutputStream()))) {
+ PdfStream stream = new PdfStream();
+ ExposedPdfCanvas canvas = new ExposedPdfCanvas(stream, new PdfResources(), pdfDocument);
+ Assert.assertFalse(canvas.getDrawingOnPage());
+ try (Canvas canvas1 = new Canvas(canvas, new Rectangle(200, 200, 200, 200))) {
+ Assert.assertFalse(((ExposedPdfCanvas) canvas1.pdfCanvas).getDrawingOnPage());
+ }
+ }
+ }
+
+ static class ExposedPdfCanvas extends PdfCanvas{
+
+ public ExposedPdfCanvas(PdfStream contentStream, PdfResources resources, PdfDocument document) {
+ super(contentStream, resources, document);
+ }
+
+ public ExposedPdfCanvas(PdfPage page) {
+ super(page);
+ }
+
+
+ public boolean getDrawingOnPage(){
+ return this.drawingOnPage;
+ }
+ }
}
diff --git a/layout/src/test/java/com/itextpdf/layout/FontSelectorTest.java b/layout/src/test/java/com/itextpdf/layout/FontSelectorTest.java
index 80daf6df1f..f8724c3fba 100644
--- a/layout/src/test/java/com/itextpdf/layout/FontSelectorTest.java
+++ b/layout/src/test/java/com/itextpdf/layout/FontSelectorTest.java
@@ -39,13 +39,10 @@ This file is part of the iText (R) project.
import com.itextpdf.layout.font.FontSelector;
import com.itextpdf.layout.font.FontSet;
import com.itextpdf.layout.font.RangeBuilder;
+import com.itextpdf.layout.font.selectorstrategy.BestMatchFontSelectorStrategy.BestMatchFontSelectorStrategyFactory;
import com.itextpdf.layout.properties.Property;
import com.itextpdf.test.ExtendedITextTest;
import com.itextpdf.test.annotations.type.IntegrationTest;
-import org.junit.Assert;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.junit.experimental.categories.Category;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -58,6 +55,10 @@ This file is part of the iText (R) project.
import java.util.List;
import java.util.Map;
import java.util.Set;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
@Category(IntegrationTest.class)
public class FontSelectorTest extends ExtendedITextTest {
@@ -468,12 +469,12 @@ public void cyrillicAndLatinWithUnicodeRange() throws Exception {
}
@Test
- // TODO update cmp after fix DEVSIX-2052
public void notSignificantCharacterOfTheFontWithUnicodeRange() throws Exception {
String outFileName = destinationFolder + "notSignificantCharacterOfTheFontWithUnicodeRange.pdf";
String cmpFileName = sourceFolder + "cmp_notSignificantCharacterOfTheFontWithUnicodeRange.pdf";
FontProvider sel = new FontProvider();
+ sel.setFontSelectorStrategyFactory(new BestMatchFontSelectorStrategyFactory());
Assert.assertTrue(sel.getFontSet().addFont(fontsFolder + "NotoSansCJKjp-Bold.otf", null, "FontAlias", new RangeBuilder(117, 117).create())); // just 'u' letter
Assert.assertTrue(sel.getFontSet().addFont(fontsFolder + "FreeSans.ttf", null, "FontAlias", new RangeBuilder(106, 113).create()));// 'j', 'm' and 'p' are in that interval
@@ -491,15 +492,18 @@ public void notSignificantCharacterOfTheFontWithUnicodeRange() throws Exception
}
@Test
- // TODO update cmp after fix DEVSIX-2052
public void checkThreeFontsInOneLineWithUnicodeRange() throws Exception {
String outFileName = destinationFolder + "checkThreeFontsInOneLineWithUnicodeRange.pdf";
String cmpFileName = sourceFolder + "cmp_checkThreeFontsInOneLineWithUnicodeRange.pdf";
FontProvider sel = new FontProvider();
- Assert.assertTrue(sel.getFontSet().addFont(fontsFolder + "NotoSansCJKjp-Bold.otf", null, "FontAlias", new RangeBuilder(97, 99).create())); // 'a', 'b' and 'c' are in that interval
- Assert.assertTrue(sel.getFontSet().addFont(fontsFolder + "FreeSans.ttf", null, "FontAlias", new RangeBuilder(100, 102).create()));// 'd', 'e' and 'f' are in that interval
- Assert.assertTrue(sel.getFontSet().addFont(fontsFolder + "Puritan2.otf", null, "FontAlias", new RangeBuilder(120, 122).create()));// 'x', 'y' and 'z' are in that interval
+ sel.setFontSelectorStrategyFactory(new BestMatchFontSelectorStrategyFactory());
+ // 'a', 'b' and 'c' are in that interval
+ Assert.assertTrue(sel.getFontSet().addFont(fontsFolder + "NotoSansCJKjp-Bold.otf", null, "FontAlias", new RangeBuilder(97, 99).create()));
+ // 'd', 'e' and 'f' are in that interval
+ Assert.assertTrue(sel.getFontSet().addFont(fontsFolder + "FreeSans.ttf", null, "FontAlias", new RangeBuilder(100, 102).create()));
+ // 'x', 'y' and 'z' are in that interval
+ Assert.assertTrue(sel.getFontSet().addFont(fontsFolder + "Puritan2.otf", null, "FontAlias", new RangeBuilder(120, 122).create()));
PdfDocument pdfDoc = new PdfDocument(new PdfWriter(outFileName));
Document doc = new Document(pdfDoc);
diff --git a/layout/src/test/java/com/itextpdf/layout/HyphenateTest.java b/layout/src/test/java/com/itextpdf/layout/HyphenateTest.java
index 6b8b7c4ab3..40047832f1 100644
--- a/layout/src/test/java/com/itextpdf/layout/HyphenateTest.java
+++ b/layout/src/test/java/com/itextpdf/layout/HyphenateTest.java
@@ -27,190 +27,130 @@ This file is part of the iText (R) project.
import com.itextpdf.layout.hyphenation.HyphenationConfig;
import com.itextpdf.test.ExtendedITextTest;
import com.itextpdf.test.annotations.type.IntegrationTest;
-import org.junit.Assert;
-import org.junit.Test;
-import org.junit.experimental.categories.Category;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+@RunWith(Parameterized.class)
@Category(IntegrationTest.class)
public class HyphenateTest extends ExtendedITextTest {
- private List params = Arrays.asList(
- new TestParams("af"),
-
- //নমস্কাৰ
- new TestParams("as", "\u09A8\u09AE\u09B8\u09CD\u0995\u09BE\u09F0"),
-
- //Здравей
- new TestParams("bg", "\u0417\u0434\u0440\u0430\u0432\u0435\u0439"),
-
- //আলাইকুম
- new TestParams("bn", "\u0986\u09B2\u09BE\u0987\u0995\u09C1\u09AE"),
- new TestParams("ca", "Benvinguts"),
-
- //ⲘⲉⲧⲢⲉⲙ̀ⲛⲭⲏⲙⲓ
- new TestParams("cop", "\u2C98\u2C89\u2CA7\u2CA2\u2C89\u2C99\u0300\u2C9B\u2CAD\u2C8F\u2C99\u2C93"),
- new TestParams("cs"),
- new TestParams("cy"),
- new TestParams("da"),
- new TestParams("de"),
- new TestParams("de_DE","14\u00a0Tagen 14\u00a0Tagen 14\u00a0Tagen "),
- new TestParams("de_DE","14\u20110Tagen 14\u2011Tagen 14\u20110Tagen "),
- new TestParams("de_1901"),
- new TestParams("de_CH"),
- new TestParams("de_DR"),
-
- //καλημέρα
- new TestParams("el", "\u03BA\u03B1\u03BB\u03B7\u03BC\u03AD\u03C1\u03B1"),
-
- //καλημέρα
- new TestParams("el_Polyton", "\u03BA\u03B1\u03BB\u03B7\u03BC\u03AD\u03C1\u03B1"),
- new TestParams("en"),
- new TestParams("en_GB"),
- new TestParams("en_US"),
- new TestParams("eo"),
- new TestParams("es", "gracias"),
- new TestParams("et", "Vabandust"),
- new TestParams("eu", "euskara"),
-
- //Näkemiin
- new TestParams("fi", "N\u00E4kemiin"),
- new TestParams("fr"),
- new TestParams("ga"),
- new TestParams("gl"),
-
- //καλημέρα
- new TestParams("grc", "\u03BA\u03B1\u03BB\u03B7\u03BC\u03AD\u03C1\u03B1"),
-
- //ગુજરાતી
- new TestParams("gu", "\u0A97\u0AC1\u0A9C\u0AB0\u0ABE\u0AA4\u0AC0"),
-
- //सुप्रभातम्
- new TestParams("hi", "\u0938\u0941\u092A\u094D\u0930\u092D\u093E\u0924\u092E\u094D"),
- new TestParams("hr"),
- new TestParams("hsb"),
- new TestParams("hu", "sziasztok"),
-
- //շնորհակալություն
- new TestParams("hy", "\u0577\u0576\u0578\u0580\u0570\u0561\u056F\u0561\u056C\u0578\u0582\u0569\u0575\u0578\u0582\u0576"),
- new TestParams("ia"),
- new TestParams("id"),
- new TestParams("is"),
- new TestParams("it"),
- new TestParams("kmr"),
-
- //ಕನ್ನಡ
- new TestParams("kn", "\u0C95\u0CA8\u0CCD\u0CA8\u0CA1"),
- new TestParams("la"),
-
- //ຍິນດີຕ້ອນຮັບ
- new TestParams("lo", "\u0E8D\u0EB4\u0E99\u0E94\u0EB5\u0E95\u0EC9\u0EAD\u0E99\u0EAE\u0EB1\u0E9A"),
- new TestParams("lt", "Labanakt"),
- new TestParams("lv", "Labvakar"),
-
- //സ്വാഗതം
- new TestParams("ml", "\u0D38\u0D4D\u0D35\u0D3E\u0D17\u0D24\u0D02"),
-
- //Өршөөгөөрэй
- new TestParams("mn", "\u04E8\u0440\u0448\u04E9\u04E9\u0433\u04E9\u04E9\u0440\u044D\u0439"),
-
- //नमस्कार
- new TestParams("mr", "\u0928\u092E\u0938\u094D\u0915\u093E\u0930"),
- new TestParams("nb"),
- new TestParams("nl"),
- new TestParams("nn"),
- new TestParams("no"),
-
- //ନମସ୍କାର
- new TestParams("or", "\u0B28\u0B2E\u0B38\u0B4D\u0B15\u0B3E\u0B30"),
-
- //ਨਮਸਕਾਰ
- new TestParams("pa", "\u0A28\u0A2E\u0A38\u0A15\u0A3E\u0A30"),
- new TestParams("pl"),
- new TestParams("pt"),
- new TestParams("ro"),
-
- //здравствуй
- new TestParams("ru", "\u0437\u0434\u0440\u0430\u0432\u0441\u0442\u0432\u0443\u0439"),
- new TestParams("sa"),
- new TestParams("sk"),
- new TestParams("sl"),
-
- //Добродошли
- new TestParams("sr_Cyrl", "\u0414\u043E\u0431\u0440\u043E\u0434\u043E\u0448\u043B\u0438"),
- new TestParams("sr_Latn"),
-
- //Välkommen
- new TestParams("sv", "V\u00E4lkommen"),
-
- //வாருங்கள்
- new TestParams("ta", "\u0BB5\u0BBE\u0BB0\u0BC1\u0B99\u0BCD\u0B95\u0BB3\u0BCD"),
-
- //సుస్వాగతం
- new TestParams("te", "\u0C38\u0C41\u0C38\u0C4D\u0C35\u0C3E\u0C17\u0C24\u0C02"),
- new TestParams("tk"),
- new TestParams("tr", "Merhaba"),
-
- //здравствуй
- new TestParams("uk", "\u0437\u0434\u0440\u0430\u0432\u0441\u0442\u0432\u0443\u0439"),
- new TestParams("zh_Latn")
- );
-
- private List errors = new ArrayList<>();
-
- @Test
- public void runTest() {
- // This test can't be sped up because it uses of a lot of input data
- for (TestParams param : params) {
- tryHyphenate(param.lang, param.testWorld, param.shouldPass);
- }
- Assert.assertTrue(buildReport(), errors.isEmpty());
- }
-
- private void tryHyphenate(String lang, String testWorld, boolean shouldPass) {
- String[] parts = lang.split("_");
- lang = parts[0];
- String country = (parts.length == 2) ? parts[1] : null;
- HyphenationConfig config = new HyphenationConfig(lang, country, 3, 3);
- Hyphenation result = config.hyphenate(testWorld);
- if ( (result == null) == shouldPass) {
- errors.add(MessageFormatUtil.format("\nLanguage: {0}, error on hyphenate({1}), shouldPass: {2}", lang, testWorld, shouldPass));
- }
- }
-
- private String buildReport() {
- StringBuilder builder = new StringBuilder();
- builder.append("There are ").append(errors.size()).append(" errors.");
- for (String message : errors) {
- builder.append(message);
- }
- return builder.toString();
- }
-
- private static class TestParams {
- String lang;
- String testWorld;
- boolean shouldPass;
-
- public TestParams(String lang, String testWorld, boolean shouldPass) {
- this.lang = lang;
- this.testWorld = testWorld;
- this.shouldPass = shouldPass;
- }
-
- public TestParams(String lang, String testWorld) {
- this(lang, testWorld, true);
- }
-
- public TestParams(String lang, boolean shouldPass) {
- this(lang, "country", shouldPass);
- }
-
- public TestParams(String lang) {
- this(lang, "country", true);
- }
- }
+ private final String lang;
+ private final String testWord;
+
+ private List errors = new ArrayList<>();
+
+ public HyphenateTest(String testName, String lang, String testWord) {
+ this.lang = lang;
+ this.testWord = testWord;
+ }
+
+ @Parameterized.Parameters(name = "{0}")
+ public static Iterable hyphenationProperties() {
+ return Arrays.asList(new Object[][]{
+ {"African", "af", "country"},
+ {"Assamese", "as", "\u09A8\u09AE\u09B8\u09CD\u0995\u09BE\u09F0"},
+ {"Bulgarian", "bg", "\u0417\u0434\u0440\u0430\u0432\u0435\u0439"},
+ {"Bengali", "bn", "\u0986\u09B2\u09BE\u0987\u0995\u09C1\u09AE"},
+ {"Catalan", "ca", "Benvinguts"},
+ {"Coptic", "cop", "\u2C98\u2C89\u2CA7\u2CA2\u2C89\u2C99\u0300\u2C9B\u2CAD\u2C8F\u2C99\u2C93"},
+ {"Czech", "cs", "country"},
+ {"Welsh", "cy", "country"},
+ {"Danish", "da", "country"},
+ {"German", "de", "country"},
+ {"German Belgium", "de_DE","14\u00a0Tagen 14\u00a0Tagen 14\u00a0Tagen "},
+ {"German Germany", "de_DE","14\u20110Tagen 14\u2011Tagen 14\u20110Tagen "},
+ {"German Traditional", "de_1901", "country"},
+ {"Swiss German", "de_CH", "country"},
+ {"New German orthography", "de_DR", "country"},
+ {"Modern Greek", "el", "\u03BA\u03B1\u03BB\u03B7\u03BC\u03AD\u03C1\u03B1"},
+ {"Greek Polytonic", "el_Polyton", "\u03BA\u03B1\u03BB\u03B7\u03BC\u03AD\u03C1\u03B1"},
+ {"English", "en", "country"},
+ {"English Great Britain", "en_GB", "country"},
+ {"English United States", "en_US", "country"},
+ {"Esperanto", "eo", "country"},
+ {"Spanish", "es", "gracias"},
+ {"Estonian", "et", "Vabandust"},
+ {"Basque", "eu", "euskara"},
+ {"Finnish", "fi", "N\u00E4kemiin"},
+ {"French", "fr", "country"},
+ {"Irish", "ga", "country"},
+ {"Galician", "gl", "country"},
+ {"Ancient Greek", "grc", "\u03BA\u03B1\u03BB\u03B7\u03BC\u03AD\u03C1\u03B1"},
+ {"Gujarati", "gu", "\u0A97\u0AC1\u0A9C\u0AB0\u0ABE\u0AA4\u0AC0"},
+ {"Hindi", "hi", "\u0938\u0941\u092A\u094D\u0930\u092D\u093E\u0924\u092E\u094D"},
+ {"Croatian", "hr", "country"},
+ {"Upper Sorbian", "hsb", "country"},
+ {"Hungarian", "hu", "sziasztok"},
+ {"Armenian", "hy", "\u0577\u0576\u0578\u0580\u0570\u0561\u056F\u0561\u056C\u0578\u0582\u0569\u0575\u0578\u0582\u0576"},
+ {"Interlingua", "ia", "country"},
+ {"Indonesian", "id", "country"},
+ {"Icelandic", "is", "country"},
+ {"Italian", "it", "country"},
+ {"Kurmanji", "kmr", "country"},
+ {"Kannada", "kn", "\u0C95\u0CA8\u0CCD\u0CA8\u0CA1"},
+ {"Latin", "la", "country"},
+ {"Lao", "lo", "\u0E8D\u0EB4\u0E99\u0E94\u0EB5\u0E95\u0EC9\u0EAD\u0E99\u0EAE\u0EB1\u0E9A"},
+ {"Lithuanian", "lt", "Labanakt"},
+ {"Latvian", "lv", "Labvakar"},
+ {"Malayalam", "ml", "\u0D38\u0D4D\u0D35\u0D3E\u0D17\u0D24\u0D02"},
+ {"Mongolian", "mn", "\u04E8\u0440\u0448\u04E9\u04E9\u0433\u04E9\u04E9\u0440\u044D\u0439"},
+ {"Marathi", "mr", "\u0928\u092E\u0938\u094D\u0915\u093E\u0930"},
+ {"Norwegian Bokmål", "nb", "country"},
+ {"Dutch; Flemish", "nl", "country"},
+ {"Norwegian Nynorsk", "nn", "country"},
+ {"Norwegian", "no", "country"},
+ {"Oriya", "or", "\u0B28\u0B2E\u0B38\u0B4D\u0B15\u0B3E\u0B30"},
+ {"Panjabi; Punjabi", "pa", "\u0A28\u0A2E\u0A38\u0A15\u0A3E\u0A30"},
+ {"Polish", "pl", "country"},
+ {"Portuguese", "pt", "country"},
+ {"Romanian; Moldavian; Moldovan", "ro", "country"},
+ {"Russian", "ru", "\u0437\u0434\u0440\u0430\u0432\u0441\u0442\u0432\u0443\u0439"},
+ {"Sanskrit", "sa", "country"},
+ {"Slovak", "sk", "country"},
+ {"Slovenian", "sl", "country"},
+ {"Serbian Cyrillic", "sr_Cyrl", "\u0414\u043E\u0431\u0440\u043E\u0434\u043E\u0448\u043B\u0438"},
+ {"Serbian Latin", "sr_Latn", "country"},
+ {"Swedish", "sv", "V\u00E4lkommen"},
+ {"Tamil", "ta", "\u0BB5\u0BBE\u0BB0\u0BC1\u0B99\u0BCD\u0B95\u0BB3\u0BCD"},
+ {"Telugu", "te", "\u0C38\u0C41\u0C38\u0C4D\u0C35\u0C3E\u0C17\u0C24\u0C02"},
+ {"Turkmen", "tk", "country"},
+ {"Turkish", "tr", "Merhaba"},
+ {"Ukrainian", "uk", "\u0437\u0434\u0440\u0430\u0432\u0441\u0442\u0432\u0443\u0439"},
+ {"Chinese Latin", "zh_Latn", "country"}
+ });
+ }
+
+ @Test
+ public void runTest() {
+ errors.clear();
+ tryHyphenate(lang, testWord);
+ Assert.assertTrue(buildReport(), errors.isEmpty());
+ }
+
+ private void tryHyphenate(String lang, String testWorld) {
+ String[] parts = lang.split("_");
+ lang = parts[0];
+ String country = (parts.length == 2) ? parts[1] : null;
+ HyphenationConfig config = new HyphenationConfig(lang, country, 3, 3);
+ Hyphenation result = config.hyphenate(testWorld);
+ if (result == null) {
+ errors.add(MessageFormatUtil.format("\nLanguage: {0}, error on hyphenate({1})", lang, testWorld));
+ }
+ }
+
+ private String buildReport() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("There are ").append(errors.size()).append(" errors.");
+ for (String message : errors) {
+ builder.append(message);
+ }
+ return builder.toString();
+ }
}
diff --git a/layout/src/test/java/com/itextpdf/layout/LayoutTaggingTest.java b/layout/src/test/java/com/itextpdf/layout/LayoutTaggingTest.java
index b7774b0bcb..c0cdbb0117 100644
--- a/layout/src/test/java/com/itextpdf/layout/LayoutTaggingTest.java
+++ b/layout/src/test/java/com/itextpdf/layout/LayoutTaggingTest.java
@@ -33,6 +33,7 @@ This file is part of the iText (R) project.
import com.itextpdf.kernel.font.PdfFontFactory;
import com.itextpdf.kernel.geom.PageSize;
import com.itextpdf.kernel.geom.Rectangle;
+import com.itextpdf.kernel.logs.KernelLogMessageConstant;
import com.itextpdf.kernel.pdf.CompressionConstants;
import com.itextpdf.kernel.pdf.PdfArray;
import com.itextpdf.kernel.pdf.PdfDictionary;
@@ -1019,8 +1020,9 @@ public void notAsciiCharTest() throws IOException, InterruptedException, SAXExce
}
@Test
- //TODO update cmp-file after DEVSIX-3351 fixed
- @LogMessages(messages = {@LogMessage(messageTemplate = IoLogMessageConstant.XOBJECT_HAS_NO_STRUCT_PARENTS)})
+ @LogMessages(messages = {
+ @LogMessage(messageTemplate = KernelLogMessageConstant.XOBJECT_STRUCT_PARENT_INDEX_MISSED_AND_RECREATED)
+ })
public void checkParentTreeIfFormXObjectTaggedTest() throws IOException, InterruptedException {
String outFileName = destinationFolder + "checkParentTreeIfFormXObjectTaggedTest.pdf";
String cmpPdf = sourceFolder + "cmp_checkParentTreeIfFormXObjectTaggedTest.pdf";
@@ -1153,6 +1155,105 @@ public void unexpectedTableHintChildTest()
compareResult(outFile, "cmp_" + outFile);
}
+ @Test
+ public void tableAppendsScopeToCell()
+ throws IOException, ParserConfigurationException, InterruptedException, SAXException {
+ String outFile = "tableAppendsScopeToCell.pdf";
+ PdfDocument pdfDocument = new PdfDocument(new PdfWriter(destinationFolder + outFile));
+ pdfDocument.setTagged();
+ Document document = new Document(pdfDocument);
+
+ Table table = new Table(UnitValue.createPercentArray(2)).useAllAvailableWidth();
+ Cell cell = new Cell().add(new Paragraph("Header 1"));
+ cell.getAccessibilityProperties().setRole(StandardRoles.TH);
+ table.addHeaderCell(cell);
+
+ Cell cell2 = new Cell().add(new Paragraph("Header 2"));
+ cell2.getAccessibilityProperties().setRole(StandardRoles.TH);
+ table.addHeaderCell(cell2);
+
+ Cell cell3 = new Cell().add(new Paragraph("Data 1"));
+ table.addCell(cell3);
+
+ Cell cell4 = new Cell().add(new Paragraph("Data 2"));
+ table.addCell(cell4);
+
+ document.add(table);
+ document.close();
+ compareResult(outFile, "cmp_" + outFile);
+ }
+
+ @Test
+ public void tableAppendsScopeNoneToCell() throws Exception {
+ String outFile = "tableAppendsScopeNoneToCell.pdf";
+ PdfDocument pdfDocument = new PdfDocument(new PdfWriter(destinationFolder + outFile));
+ pdfDocument.setTagged();
+ Document document = new Document(pdfDocument);
+
+ Table table = new Table(UnitValue.createPercentArray(2)).useAllAvailableWidth();
+ Cell cell = new Cell().add(new Paragraph("Header 1"));
+ cell.getAccessibilityProperties().setRole(StandardRoles.TH);
+ cell.getAccessibilityProperties().addAttributes(new PdfStructureAttributes("Table")
+ .addEnumAttribute("Scope", "None"));
+ table.addHeaderCell(cell);
+
+ Cell cell2 = new Cell().add(new Paragraph("Header 2"));
+ cell2.getAccessibilityProperties().setRole(StandardRoles.TH);
+ cell2.getAccessibilityProperties().addAttributes(new PdfStructureAttributes("Table")
+ .addEnumAttribute("Scope", "None"));
+ table.addHeaderCell(cell2);
+
+ Cell cell3 = new Cell().add(new Paragraph("Data 1"));
+ table.addCell(cell3);
+
+ Cell cell4 = new Cell().add(new Paragraph("Data 2"));
+ table.addCell(cell4);
+
+ document.add(table);
+ document.close();
+ compareResult(outFile, "cmp_" + outFile);
+ }
+
+ @Test
+ public void tableAddsScopeRegardlessOfHeaderId() throws Exception {
+ String outFile = "tableAddsScopeRegardlessOfHeaderId.pdf";
+ PdfDocument pdfDocument = new PdfDocument(new PdfWriter(destinationFolder + outFile));
+ pdfDocument.setTagged();
+ Document document = new Document(pdfDocument);
+
+ Table table = new Table(UnitValue.createPercentArray(3)).useAllAvailableWidth();
+ Cell hCell = new Cell().add(new Paragraph("Header 1"));
+ hCell.getAccessibilityProperties().setRole(StandardRoles.TH)
+ .getAttributesList()
+ .add(new PdfStructureAttributes("Table")
+ .addEnumAttribute("Scope", "Both"));
+ table.addHeaderCell(hCell);
+
+ Cell hCell2 = new Cell().add(new Paragraph("Header 2"));
+ hCell2.getAccessibilityProperties().setRole(StandardRoles.TH);
+ hCell2.getAccessibilityProperties().setStructureElementIdString("ID_header");
+ table.addHeaderCell(hCell2);
+
+ Cell hCell3 = new Cell().add(new Paragraph("Header 2"));
+ hCell3.getAccessibilityProperties().setRole(StandardRoles.TH);
+ hCell3.getAccessibilityProperties().getAttributesList()
+ .add(new PdfStructureAttributes("Table")
+ .addEnumAttribute("Scope", "Row"));
+ table.addHeaderCell(hCell3);
+
+ Cell cell3 = new Cell().add(new Paragraph("Data 1"));
+ table.addCell(cell3);
+
+ Cell cell4 = new Cell().add(new Paragraph("Data 2"));
+ table.addCell(cell4);
+
+ table.addCell(new Cell().add(new Paragraph("Data 3")));
+
+ document.add(table);
+ document.close();
+ compareResult(outFile, "cmp_" + outFile);
+ }
+
private Paragraph createParagraph1() throws IOException {
PdfFont font = PdfFontFactory.createFont(StandardFonts.HELVETICA_BOLD);
Paragraph p = new Paragraph().add("text chunk. ").add("explicitly added separate text chunk");
diff --git a/layout/src/test/java/com/itextpdf/layout/PdfUA2AnnotationsTest.java b/layout/src/test/java/com/itextpdf/layout/PdfUA2AnnotationsTest.java
index 3ae49dbb7b..3e6b3ca76c 100644
--- a/layout/src/test/java/com/itextpdf/layout/PdfUA2AnnotationsTest.java
+++ b/layout/src/test/java/com/itextpdf/layout/PdfUA2AnnotationsTest.java
@@ -61,7 +61,6 @@ This file is part of the iText (R) project.
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
import com.itextpdf.kernel.pdf.filespec.PdfFileSpec;
import com.itextpdf.kernel.pdf.tagging.StandardRoles;
-import com.itextpdf.kernel.pdf.tagutils.TagTreePointer;
import com.itextpdf.kernel.pdf.xobject.PdfFormXObject;
import com.itextpdf.kernel.utils.CompareTool;
import com.itextpdf.kernel.xmp.XMPException;
@@ -192,6 +191,7 @@ public void pdfUA2RubberStampAnnotationsTest() throws IOException, XMPException,
stamp.setStampName(PdfName.Approved);
stamp.setContents("stamp contents");
stamp.getPdfObject().put(PdfName.Type, PdfName.Annot);
+ pdfDocument.getTagStructureContext().getAutoTaggingPointer().addTag(StandardRoles.SECT);
pdfPage.addAnnotation(stamp);
pdfPage.flush();
}
diff --git a/layout/src/test/java/com/itextpdf/layout/TableTest.java b/layout/src/test/java/com/itextpdf/layout/TableTest.java
index e90b53bbd9..67877297d8 100644
--- a/layout/src/test/java/com/itextpdf/layout/TableTest.java
+++ b/layout/src/test/java/com/itextpdf/layout/TableTest.java
@@ -22,6 +22,7 @@ This file is part of the iText (R) project.
*/
package com.itextpdf.layout;
+import com.itextpdf.commons.utils.PlaceHolderTextUtil;
import com.itextpdf.io.image.ImageDataFactory;
import com.itextpdf.io.logs.IoLogMessageConstant;
import com.itextpdf.io.util.UrlUtil;
@@ -2047,9 +2048,6 @@ public void tableWithEmptyLastRowTest() throws IOException, InterruptedException
}
@Test
- @LogMessages(messages = {
- @LogMessage(messageTemplate = IoLogMessageConstant.UNEXPECTED_BEHAVIOUR_DURING_TABLE_ROW_COLLAPSING)
- })
public void tableWithEmptyRowsBetweenFullRowsTest() throws IOException, InterruptedException {
String testName = "tableWithEmptyRowsBetweenFullRowsTest.pdf";
String outFileName = destinationFolder + testName;
@@ -2072,7 +2070,6 @@ public void tableWithEmptyRowsBetweenFullRowsTest() throws IOException, Interrup
@Test
@LogMessages(messages = {
- @LogMessage(messageTemplate = IoLogMessageConstant.UNEXPECTED_BEHAVIOUR_DURING_TABLE_ROW_COLLAPSING, count = 2),
@LogMessage(messageTemplate = IoLogMessageConstant.LAST_ROW_IS_NOT_COMPLETE)
})
public void tableWithEmptyRowAfterJustOneCellTest() throws IOException, InterruptedException {
@@ -2098,7 +2095,6 @@ public void tableWithEmptyRowAfterJustOneCellTest() throws IOException, Interrup
@Test
@LogMessages(messages = {
- @LogMessage(messageTemplate = IoLogMessageConstant.UNEXPECTED_BEHAVIOUR_DURING_TABLE_ROW_COLLAPSING, count = 39),
@LogMessage(messageTemplate = IoLogMessageConstant.LAST_ROW_IS_NOT_COMPLETE)
})
public void tableWithAlternatingRowsTest() throws IOException, InterruptedException {
@@ -2145,9 +2141,30 @@ public void coloredTableWithColoredCellsTest() throws IOException, InterruptedEx
}
@Test
- @LogMessages(messages = {
- @LogMessage(messageTemplate = IoLogMessageConstant.UNEXPECTED_BEHAVIOUR_DURING_TABLE_ROW_COLLAPSING, count = 2)
- })
+ public void tableWithEmptyRowsAndSpansTest() throws IOException, InterruptedException {
+ String testName = "tableWithEmptyRowsAndSpansTest.pdf";
+ String outFileName = destinationFolder + testName;
+ String cmpFileName = sourceFolder + "cmp_" + testName;
+
+ PdfDocument pdfDoc = new PdfDocument(new PdfWriter(outFileName));
+ Document doc = new Document(pdfDoc);
+
+ Table table = new Table(UnitValue.createPercentArray(new float[]{30, 30, 30}));
+ table.addCell(new Cell().add(new Paragraph("Hello")));
+ table.addCell(new Cell().add(new Paragraph("Lovely")));
+ table.addCell(new Cell().add(new Paragraph("World")));
+ startSeveralEmptyRows(table);
+ table.addCell(new Cell(2, 2).add(new Paragraph("Hello")));
+ table.addCell(new Cell().add(new Paragraph("Lovely")));
+ table.addCell(new Cell().add(new Paragraph("World")));
+
+ doc.add(table);
+
+ doc.close();
+ Assert.assertNull(new CompareTool().compareByContent(outFileName, cmpFileName, destinationFolder, testName + "_diff"));
+ }
+
+ @Test
public void tableWithEmptyRowsAndSeparatedBordersTest() throws IOException, InterruptedException {
String testName = "tableWithEmptyRowsAndSeparatedBordersTest.pdf";
String outFileName = destinationFolder + testName;
@@ -2170,10 +2187,6 @@ public void tableWithEmptyRowsAndSeparatedBordersTest() throws IOException, Inte
}
@Test
- // TODO DEVSIX-6020:Border-collapsing doesn't work in case startNewRow has been called
- @LogMessages(messages = {
- @LogMessage(messageTemplate = IoLogMessageConstant.UNEXPECTED_BEHAVIOUR_DURING_TABLE_ROW_COLLAPSING)
- })
public void tableWithCollapsedBordersTest() throws IOException, InterruptedException {
String testName = "tableWithCollapsedBordersTest.pdf";
String outFileName = destinationFolder + testName;
@@ -3245,7 +3258,7 @@ public void inheritHeaderPropsWhileMinMaxWidthCalculationsTest() throws IOExcept
// of the table will be calculated by the header.
table.addHeaderCell(new Cell().add(new Paragraph("Hello")));
table.addCell(new Cell().add(new Paragraph("He")));
-
+
// If this property is not inherited while calculating min/max widths,
// then while layouting header will request more space than the layout box's width
table.getHeader().setBold();
@@ -3575,6 +3588,90 @@ public void negativeLayoutAreaTest() throws IOException, InterruptedException {
Assert.assertNull(new CompareTool().compareByContent(outFileName, cmpFileName, destinationFolder, testName + "_diff"));
}
+ @Test
+ @LogMessages(messages = {
+ @LogMessage(messageTemplate = LayoutLogMessageConstant.ELEMENT_DOES_NOT_FIT_AREA, logLevel = LogLevelConstants.WARN),
+ })
+ public void keepTogetherCaptionAndHugeCellTest() throws IOException, InterruptedException {
+ String fileName = "keepTogetherCaptionAndHugeCell.pdf";
+
+ PdfDocument pdfDocument = new PdfDocument(CompareTool.createTestPdfWriter(destinationFolder + fileName));
+ Document document = new Document(pdfDocument, PageSize.A4);
+
+ Table table = new Table(1)
+ .setCaption(new Div().add(new Paragraph("hello world")));
+
+ Cell dataCell = new Cell()
+ .setKeepTogether(true)
+ .add(new Paragraph(PlaceHolderTextUtil
+ .getPlaceHolderText(PlaceHolderTextUtil.PlaceHolderTextBy.WORDS, 600)));
+
+ table.addCell(dataCell);
+ document.add(table);
+
+ document.close();
+
+ Assert.assertNull(new CompareTool().compareByContent(destinationFolder + fileName,
+ sourceFolder + "cmp_" + fileName, destinationFolder));
+ }
+
+ @Test
+ @LogMessages(messages = {
+ @LogMessage(messageTemplate = LayoutLogMessageConstant.ELEMENT_DOES_NOT_FIT_AREA, logLevel = LogLevelConstants.WARN),
+ })
+ public void keepTogetherCaptionDoesntFitPageTest() throws IOException, InterruptedException {
+ String fileName = "keepTogetherCaptionDoesntFitPage.pdf";
+
+ PdfDocument pdfDocument = new PdfDocument(CompareTool.createTestPdfWriter(destinationFolder + fileName));
+ Document document = new Document(pdfDocument, PageSize.A4);
+
+ document.add(new Paragraph(PlaceHolderTextUtil
+ .getPlaceHolderText(PlaceHolderTextUtil.PlaceHolderTextBy.WORDS, 580)));
+
+ Table table = new Table(1)
+ .setCaption(new Div().add(new Paragraph("hello world")));
+
+ Cell dataCell = new Cell()
+ .setKeepTogether(true)
+ .add(new Paragraph(PlaceHolderTextUtil
+ .getPlaceHolderText(PlaceHolderTextUtil.PlaceHolderTextBy.WORDS, 600)));
+
+ table.addCell(dataCell);
+ document.add(table);
+
+ document.close();
+
+ Assert.assertNull(new CompareTool().compareByContent(destinationFolder + fileName,
+ sourceFolder + "cmp_" + fileName, destinationFolder));
+ }
+
+ @Test
+ @LogMessages(messages = {
+ @LogMessage(messageTemplate = LayoutLogMessageConstant.ELEMENT_DOES_NOT_FIT_AREA, logLevel = LogLevelConstants.WARN),
+ })
+ public void keepTogetherCaptionAndSplitCellTest() throws IOException, InterruptedException {
+ String fileName = "keepTogetherCaptionAndSplitCell.pdf";
+ PdfDocument pdfDocument = new PdfDocument(CompareTool.createTestPdfWriter(destinationFolder + fileName));
+ Document document = new Document(pdfDocument, PageSize.A4);
+
+ Table table = new Table(1)
+ .setCaption(new Div().add(new Paragraph("hello world").setFontSize(40)),CaptionSide.BOTTOM);
+
+
+ Cell dataCell = new Cell()
+ .setKeepTogether(true)
+ .add(new Paragraph(PlaceHolderTextUtil
+ .getPlaceHolderText(PlaceHolderTextUtil.PlaceHolderTextBy.WORDS, 540)));
+
+ table.addCell(dataCell);
+ document.add(table);
+
+ document.close();
+
+ Assert.assertNull(new CompareTool().compareByContent(destinationFolder + fileName,
+ sourceFolder + "cmp_" + fileName, destinationFolder));
+ }
+
private static class RotatedDocumentRenderer extends DocumentRenderer {
private final PdfDocument pdfDoc;
diff --git a/layout/src/test/java/com/itextpdf/layout/font/FontSelectorLayoutTest.java b/layout/src/test/java/com/itextpdf/layout/font/FontSelectorLayoutTest.java
index 909615a0e2..10ba59954b 100644
--- a/layout/src/test/java/com/itextpdf/layout/font/FontSelectorLayoutTest.java
+++ b/layout/src/test/java/com/itextpdf/layout/font/FontSelectorLayoutTest.java
@@ -30,22 +30,23 @@ This file is part of the iText (R) project.
import com.itextpdf.kernel.utils.CompareTool;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.element.Paragraph;
+import com.itextpdf.layout.font.selectorstrategy.BestMatchFontSelectorStrategy.BestMatchFontSelectorStrategyFactory;
import com.itextpdf.test.AssertUtil;
import com.itextpdf.test.ExtendedITextTest;
import com.itextpdf.test.annotations.type.IntegrationTest;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.experimental.categories.Category;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-
@Category(IntegrationTest.class)
public class FontSelectorLayoutTest extends ExtendedITextTest {
- public static final String sourceFolder = "./src/test/resources/com/itextpdf/layout/NonBreakingHyphenTest/";
- public static final String destinationFolder = "./target/test/com/itextpdf/layout/NonBreakingHyphenTest/";
- public static final String fontsFolder = "./src/test/resources/com/itextpdf/layout/fonts/";
+ private static final String sourceFolder = "./src/test/resources/com/itextpdf/layout/NonBreakingHyphenTest/";
+ private static final String destinationFolder = "./target/test/com/itextpdf/layout/NonBreakingHyphenTest/";
+ private static final String fontsFolder = "./src/test/resources/com/itextpdf/layout/fonts/";
@BeforeClass
public static void beforeClass() {
@@ -53,7 +54,6 @@ public static void beforeClass() {
}
@Test
- //TODO: update after fix of DEVSIX-2052
public void nonBreakingHyphenDifferentFonts() throws IOException, InterruptedException {
String outFileName = destinationFolder + "nonBreakingHyphenDifferentFonts.pdf";
String cmpFileName = sourceFolder + "cmp_nonBreakingHyphenDifferentFonts.pdf";
@@ -61,6 +61,7 @@ public void nonBreakingHyphenDifferentFonts() throws IOException, InterruptedExc
Document document = new Document(new PdfDocument(new PdfWriter(outFileName)));
FontProvider sel = new FontProvider();
+ sel.setFontSelectorStrategyFactory(new BestMatchFontSelectorStrategyFactory());
sel.getFontSet().addFont(StandardFonts.TIMES_ROMAN);
sel.getFontSet().addFont(StandardFonts.COURIER);
sel.getFontSet().addFont(fontsFolder + "Puritan2.otf", PdfEncodings.IDENTITY_H, "Puritan2");
diff --git a/layout/src/test/java/com/itextpdf/layout/font/selectorstrategy/BestMatchFontSelectorStrategyTest.java b/layout/src/test/java/com/itextpdf/layout/font/selectorstrategy/BestMatchFontSelectorStrategyTest.java
new file mode 100644
index 0000000000..a89dbcfe7e
--- /dev/null
+++ b/layout/src/test/java/com/itextpdf/layout/font/selectorstrategy/BestMatchFontSelectorStrategyTest.java
@@ -0,0 +1,146 @@
+/*
+ This file is part of the iText (R) project.
+ Copyright (c) 1998-2024 Apryse Group NV
+ Authors: Apryse Software.
+
+ This program is offered under a commercial and under the AGPL license.
+ For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
+
+ AGPL licensing:
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+ */
+package com.itextpdf.layout.font.selectorstrategy;
+
+import com.itextpdf.commons.datastructures.Tuple2;
+import com.itextpdf.io.font.otf.GlyphLine;
+import com.itextpdf.kernel.font.PdfFont;
+import com.itextpdf.layout.font.selectorstrategy.BestMatchFontSelectorStrategy.BestMatchFontSelectorStrategyFactory;
+import com.itextpdf.test.ExtendedITextTest;
+import com.itextpdf.test.annotations.type.UnitTest;
+
+import java.util.List;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+@Category(UnitTest.class)
+public class BestMatchFontSelectorStrategyTest extends ExtendedITextTest {
+ @Test
+ public void twoDiacriticsInRowTest() {
+ IFontSelectorStrategy strategy = FontSelectorTestsUtil.createStrategyWithFreeSansAndTNR(new BestMatchFontSelectorStrategyFactory());
+
+ final List> result = strategy.getGlyphLines(
+ "L with accent: \u004f\u0301\u0302 abc");
+ Assert.assertEquals(3, result.size());
+ Assert.assertEquals("L with accent: ", result.get(0).getFirst().toString());
+ Assert.assertEquals("\u004f\u0301\u0302", result.get(1).getFirst().toString());
+ Assert.assertEquals(" abc", result.get(2).getFirst().toString());
+ // Diacritics and symbol were separated, but the font is the same
+ Assert.assertEquals(result.get(0).getSecond(), result.get(2).getSecond());
+ }
+
+ @Test
+ public void oneDiacriticTest() {
+ IFontSelectorStrategy strategy = FontSelectorTestsUtil.createStrategyWithFreeSansAndTNR(new BestMatchFontSelectorStrategyFactory());
+
+ final List> result = strategy.getGlyphLines(
+ "L with accent: \u004f\u0302 abc");
+ Assert.assertEquals(3, result.size());
+ Assert.assertEquals("L with accent: ", result.get(0).getFirst().toString());
+ Assert.assertEquals("\u004f\u0302", result.get(1).getFirst().toString());
+ Assert.assertEquals(" abc", result.get(2).getFirst().toString());
+ Assert.assertNotEquals(result.get(0).getSecond(), result.get(1).getSecond());
+ }
+
+ @Test
+ public void oneDiacriticWithUnsupportedFontTest() {
+ IFontSelectorStrategy strategy = FontSelectorTestsUtil.createStrategyWithTNR(new BestMatchFontSelectorStrategyFactory());
+
+ final List> result = strategy.getGlyphLines(
+ "L with accent: \u004f\u0302 abc");
+ Assert.assertEquals(3, result.size());
+ Assert.assertEquals("L with accent: \u004f", result.get(0).getFirst().toString());
+ Assert.assertEquals("", result.get(1).getFirst().toString());
+ Assert.assertEquals(" abc", result.get(2).getFirst().toString());
+ Assert.assertEquals(result.get(0).getSecond(), result.get(2).getSecond());
+ Assert.assertEquals(result.get(0).getSecond(), result.get(1).getSecond());
+ }
+
+ @Test
+ public void diacriticFontDoesnotContainPreviousSymbolTest() {
+ IFontSelectorStrategy strategy = FontSelectorTestsUtil.createStrategyWithNotoSans(new BestMatchFontSelectorStrategyFactory());
+
+ final List> result = strategy.getGlyphLines(
+ "Ми\u0301ръ (mírə)");
+ Assert.assertEquals(6, result.size());
+ Assert.assertEquals("Ми", result.get(0).getFirst().toString());
+ Assert.assertEquals("\u0301", result.get(1).getFirst().toString());
+ Assert.assertEquals("ръ", result.get(2).getFirst().toString());
+ Assert.assertEquals(" (mír", result.get(3).getFirst().toString());
+ Assert.assertEquals("ə", result.get(4).getFirst().toString());
+ Assert.assertEquals(")", result.get(5).getFirst().toString());
+ Assert.assertEquals(result.get(0).getSecond(), result.get(2).getSecond());
+ Assert.assertEquals(result.get(2).getSecond(), result.get(3).getSecond());
+ }
+
+
+ @Test
+ public void oneDiacriticWithOneSupportedFontTest() {
+ IFontSelectorStrategy strategy = FontSelectorTestsUtil.createStrategyWithFreeSans(new BestMatchFontSelectorStrategyFactory());
+
+ final List> result = strategy.getGlyphLines(
+ "L with accent: \u004f\u0302 abc");
+ Assert.assertEquals(1, result.size());
+ Assert.assertEquals("L with accent: \u004f\u0302 abc", result.get(0).getFirst().toString());
+ }
+
+ @Test
+ public void surrogatePairsTest() {
+ IFontSelectorStrategy strategy = FontSelectorTestsUtil.createStrategyWithOldItalic(new BestMatchFontSelectorStrategyFactory());
+
+ // this text contains three successive surrogate pairs
+ final List> result = strategy.getGlyphLines(
+ "text \uD800\uDF10\uD800\uDF00\uD800\uDF11 text");
+ Assert.assertEquals(3, result.size());
+ Assert.assertEquals("text", result.get(0).getFirst().toString());
+ Assert.assertEquals(" \uD800\uDF10\uD800\uDF00\uD800\uDF11 ", result.get(1).getFirst().toString());
+ Assert.assertEquals("text", result.get(2).getFirst().toString());
+ Assert.assertEquals(result.get(0).getSecond(), result.get(2).getSecond());
+ }
+
+ @Test
+ public void simpleThreeFontTest() {
+ IFontSelectorStrategy strategy = FontSelectorTestsUtil.createStrategyWithLimitedThreeFonts(new BestMatchFontSelectorStrategyFactory());
+
+ final List> result = strategy.getGlyphLines("abcdefxyz");
+ Assert.assertEquals(3, result.size());
+ Assert.assertEquals("abc", result.get(0).getFirst().toString());
+ Assert.assertEquals("def", result.get(1).getFirst().toString());
+ Assert.assertEquals("xyz", result.get(2).getFirst().toString());
+ }
+
+ @Test
+ public void threeFontWithSpacesTest() {
+ IFontSelectorStrategy strategy = FontSelectorTestsUtil.createStrategyWithLimitedThreeFonts(new BestMatchFontSelectorStrategyFactory());
+
+ final List> result = strategy.getGlyphLines(" axadefa ");
+ Assert.assertEquals(6, result.size());
+ Assert.assertEquals(" a", result.get(0).getFirst().toString());
+ Assert.assertEquals("x", result.get(1).getFirst().toString());
+ Assert.assertEquals("a", result.get(2).getFirst().toString());
+ Assert.assertEquals("def", result.get(3).getFirst().toString());
+ Assert.assertEquals("a", result.get(4).getFirst().toString());
+ Assert.assertEquals(" ", result.get(5).getFirst().toString());
+ }
+}
diff --git a/layout/src/test/java/com/itextpdf/layout/font/selectorstrategy/FirstMatchFontSelectorStrategyTest.java b/layout/src/test/java/com/itextpdf/layout/font/selectorstrategy/FirstMatchFontSelectorStrategyTest.java
new file mode 100644
index 0000000000..6a29545b41
--- /dev/null
+++ b/layout/src/test/java/com/itextpdf/layout/font/selectorstrategy/FirstMatchFontSelectorStrategyTest.java
@@ -0,0 +1,134 @@
+/*
+ This file is part of the iText (R) project.
+ Copyright (c) 1998-2024 Apryse Group NV
+ Authors: Apryse Software.
+
+ This program is offered under a commercial and under the AGPL license.
+ For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
+
+ AGPL licensing:
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+ */
+package com.itextpdf.layout.font.selectorstrategy;
+
+import com.itextpdf.commons.datastructures.Tuple2;
+import com.itextpdf.io.font.otf.GlyphLine;
+import com.itextpdf.kernel.font.PdfFont;
+import com.itextpdf.layout.font.selectorstrategy.FirstMatchFontSelectorStrategy.FirstMathFontSelectorStrategyFactory;
+import com.itextpdf.test.ExtendedITextTest;
+import com.itextpdf.test.annotations.type.UnitTest;
+
+import java.util.List;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+@Category(UnitTest.class)
+public class FirstMatchFontSelectorStrategyTest extends ExtendedITextTest {
+ @Test
+ public void twoDiacriticsInRowTest() {
+ IFontSelectorStrategy strategy = FontSelectorTestsUtil.createStrategyWithFreeSansAndTNR(new FirstMathFontSelectorStrategyFactory());
+
+ final List> result = strategy.getGlyphLines(
+ "L with accent: \u004f\u0301\u0302 abc");
+ Assert.assertEquals(2, result.size());
+ Assert.assertEquals("L with accent: ", result.get(0).getFirst().toString());
+ Assert.assertEquals("\u004f\u0301\u0302 abc", result.get(1).getFirst().toString());
+ }
+
+ @Test
+ public void oneDiacriticTest() {
+ IFontSelectorStrategy strategy = FontSelectorTestsUtil.createStrategyWithFreeSansAndTNR(new FirstMathFontSelectorStrategyFactory());
+
+ final List> result = strategy.getGlyphLines(
+ "L with accent: \u004f\u0302 abc");
+ Assert.assertEquals(2, result.size());
+ Assert.assertEquals("L with accent: ", result.get(0).getFirst().toString());
+ Assert.assertEquals("\u004f\u0302 abc", result.get(1).getFirst().toString());
+ Assert.assertNotEquals(result.get(0).getSecond(), result.get(1).getSecond());
+ }
+
+ @Test
+ public void diacriticFontDoesnotContainPreviousSymbolTest() {
+ IFontSelectorStrategy strategy = FontSelectorTestsUtil.createStrategyWithNotoSans(new FirstMathFontSelectorStrategyFactory());
+
+ final List> result = strategy.getGlyphLines(
+ "Ми\u0301ръ (mírə)");
+ Assert.assertEquals(6, result.size());
+ Assert.assertEquals("Ми", result.get(0).getFirst().toString());
+ Assert.assertEquals("\u0301", result.get(1).getFirst().toString());
+ Assert.assertEquals("ръ (", result.get(2).getFirst().toString());
+ Assert.assertEquals("mír", result.get(3).getFirst().toString());
+ Assert.assertEquals("ə", result.get(4).getFirst().toString());
+ Assert.assertEquals(")", result.get(5).getFirst().toString());
+ Assert.assertEquals(result.get(0).getSecond(), result.get(2).getSecond());
+ Assert.assertEquals(result.get(2).getSecond(), result.get(3).getSecond());
+ }
+
+ @Test
+ public void oneDiacriticWithUnsupportedFontTest() {
+ IFontSelectorStrategy strategy = FontSelectorTestsUtil.createStrategyWithTNR(new FirstMathFontSelectorStrategyFactory());
+
+ final List> result = strategy.getGlyphLines(
+ "L with accent: \u004f\u0302 abc");
+ Assert.assertEquals(3, result.size());
+ Assert.assertEquals("L with accent: \u004f", result.get(0).getFirst().toString());
+ Assert.assertEquals("", result.get(1).getFirst().toString());
+ Assert.assertEquals(" abc", result.get(2).getFirst().toString());
+ Assert.assertEquals(result.get(0).getSecond(), result.get(2).getSecond());
+ Assert.assertEquals(result.get(0).getSecond(), result.get(1).getSecond());
+ }
+
+ @Test
+ public void oneDiacriticWithOneSupportedFontTest() {
+ IFontSelectorStrategy strategy = FontSelectorTestsUtil.createStrategyWithFreeSans(new FirstMathFontSelectorStrategyFactory());
+
+ final List> result = strategy.getGlyphLines(
+ "L with accent: \u004f\u0302 abc");
+ Assert.assertEquals(1, result.size());
+ Assert.assertEquals("L with accent: \u004f\u0302 abc", result.get(0).getFirst().toString());
+ }
+
+ @Test
+ public void surrogatePairsTest() {
+ IFontSelectorStrategy strategy = FontSelectorTestsUtil.createStrategyWithOldItalic(new FirstMathFontSelectorStrategyFactory());
+
+ // this text contains three successive surrogate pairs
+ final List> result = strategy.getGlyphLines(
+ "text \uD800\uDF10\uD800\uDF00\uD800\uDF11 text");
+ Assert.assertEquals(3, result.size());
+ Assert.assertEquals("text ", result.get(0).getFirst().toString());
+ Assert.assertEquals("\uD800\uDF10\uD800\uDF00\uD800\uDF11 ", result.get(1).getFirst().toString());
+ Assert.assertEquals("text", result.get(2).getFirst().toString());
+ Assert.assertEquals(result.get(0).getSecond(), result.get(2).getSecond());
+ }
+
+ @Test
+ public void simpleThreeFontTest() {
+ IFontSelectorStrategy strategy = FontSelectorTestsUtil.createStrategyWithLimitedThreeFonts(new FirstMathFontSelectorStrategyFactory());
+
+ final List> result = strategy.getGlyphLines("abcdefxyz");
+ Assert.assertEquals(1, result.size());
+ Assert.assertEquals("abcdefxyz", result.get(0).getFirst().toString());
+ }
+
+ @Test
+ public void threeFontWithSpacesTest() {
+ IFontSelectorStrategy strategy = FontSelectorTestsUtil.createStrategyWithLimitedThreeFonts(new FirstMathFontSelectorStrategyFactory());
+
+ final List> result = strategy.getGlyphLines(" axadefa ");
+ Assert.assertEquals(1, result.size());
+ Assert.assertEquals(" axadefa ", result.get(0).getFirst().toString());
+ }
+}
diff --git a/layout/src/test/java/com/itextpdf/layout/font/selectorstrategy/FontSelectorTestsUtil.java b/layout/src/test/java/com/itextpdf/layout/font/selectorstrategy/FontSelectorTestsUtil.java
new file mode 100644
index 0000000000..bab17ea68d
--- /dev/null
+++ b/layout/src/test/java/com/itextpdf/layout/font/selectorstrategy/FontSelectorTestsUtil.java
@@ -0,0 +1,108 @@
+/*
+ This file is part of the iText (R) project.
+ Copyright (c) 1998-2024 Apryse Group NV
+ Authors: Apryse Software.
+
+ This program is offered under a commercial and under the AGPL license.
+ For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
+
+ AGPL licensing:
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+ */
+package com.itextpdf.layout.font.selectorstrategy;
+
+import com.itextpdf.io.font.constants.StandardFontFamilies;
+import com.itextpdf.io.font.constants.StandardFonts;
+import com.itextpdf.layout.font.FontCharacteristics;
+import com.itextpdf.layout.font.FontProvider;
+import com.itextpdf.layout.font.FontSet;
+import com.itextpdf.layout.font.RangeBuilder;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class FontSelectorTestsUtil {
+ private static final String FONTS_FOLDER = "./src/test/resources/com/itextpdf/layout/fonts/";
+
+ public static IFontSelectorStrategy createStrategyWithFreeSansAndTNR(IFontSelectorStrategyFactory factory) {
+ FontSet fs = new FontSet();
+ fs.addFont(StandardFonts.TIMES_ROMAN);
+ fs.addFont(FONTS_FOLDER + "FreeSans.ttf");
+ final FontProvider fontProvider = new FontProvider(fs);
+ fontProvider.setFontSelectorStrategyFactory(factory);
+ List fontFamilies = new ArrayList<>();
+ fontFamilies.add("random");
+ return fontProvider.createFontSelectorStrategy(fontFamilies, new FontCharacteristics(), null);
+ }
+
+ public static IFontSelectorStrategy createStrategyWithNotoSans(IFontSelectorStrategyFactory factory) {
+ FontSet fs = new FontSet();
+ fs.addFont(FONTS_FOLDER + "NotoKufiArabic-Regular.ttf");
+ fs.addFont(FONTS_FOLDER + "NotoSansCJKjp-Regular.otf");
+ fs.addFont(FONTS_FOLDER + "NotoSansCherokee-Regular.ttf");
+ final FontProvider fontProvider = new FontProvider(fs, StandardFontFamilies.TIMES);
+ fontProvider.setFontSelectorStrategyFactory(factory);
+ List fontFamilies = new ArrayList<>();
+ fontFamilies.add("random");
+ return fontProvider.createFontSelectorStrategy(fontFamilies, new FontCharacteristics(), null);
+ }
+
+ public static IFontSelectorStrategy createStrategyWithTNR(IFontSelectorStrategyFactory factory) {
+ FontSet fs = new FontSet();
+ fs.addFont(StandardFonts.TIMES_ROMAN);
+ final FontProvider fontProvider = new FontProvider(fs);
+ fontProvider.setFontSelectorStrategyFactory(factory);
+ List fontFamilies = new ArrayList<>();
+ fontFamilies.add("random");
+ return fontProvider.createFontSelectorStrategy(fontFamilies, new FontCharacteristics(), null);
+ }
+
+ public static IFontSelectorStrategy createStrategyWithFreeSans(IFontSelectorStrategyFactory factory) {
+ FontSet fs = new FontSet();
+ fs.addFont(FONTS_FOLDER + "FreeSans.ttf");
+ final FontProvider fontProvider = new FontProvider(fs);
+ fontProvider.setFontSelectorStrategyFactory(factory);
+ List fontFamilies = new ArrayList<>();
+ fontFamilies.add("random");
+ return fontProvider.createFontSelectorStrategy(fontFamilies, new FontCharacteristics(), null);
+ }
+
+ public static IFontSelectorStrategy createStrategyWithLimitedThreeFonts(IFontSelectorStrategyFactory factory) {
+ final FontProvider fontProvider = new FontProvider();
+
+ // 'a', 'b' and 'c' are in that interval
+ fontProvider.getFontSet().addFont(FONTS_FOLDER + "NotoSansCJKjp-Bold.otf", null, "FontAlias", new RangeBuilder(97, 99).create());
+ // 'd', 'e' and 'f' are in that interval
+ fontProvider.getFontSet().addFont(FONTS_FOLDER + "FreeSans.ttf", null, "FontAlias", new RangeBuilder(100, 102).create());
+ // 'x', 'y' and 'z' are in that interval
+ fontProvider.getFontSet().addFont(FONTS_FOLDER + "Puritan2.otf", null, "FontAlias", new RangeBuilder(120, 122).create());
+
+ fontProvider.setFontSelectorStrategyFactory(factory);
+ List fontFamilies = new ArrayList<>();
+ fontFamilies.add("random");
+ return fontProvider.createFontSelectorStrategy(fontFamilies, new FontCharacteristics(), null);
+ }
+
+ public static IFontSelectorStrategy createStrategyWithOldItalic(IFontSelectorStrategyFactory factory) {
+ final FontProvider fontProvider = new FontProvider();
+
+ fontProvider.getFontSet().addFont(FONTS_FOLDER + "NotoSansOldItalic-Regular.ttf", null, "FontAlias");
+ fontProvider.getFontSet().addFont(FONTS_FOLDER + "FreeSans.ttf", null, "FontAlias");
+
+ fontProvider.setFontSelectorStrategyFactory(factory);
+ List fontFamilies = new ArrayList<>();
+ fontFamilies.add("random");
+ return fontProvider.createFontSelectorStrategy(fontFamilies, new FontCharacteristics(), null);
+ }
+}
diff --git a/layout/src/test/java/com/itextpdf/layout/renderer/TextPreprocessingUtilTest.java b/layout/src/test/java/com/itextpdf/layout/renderer/TextPreprocessingUtilTest.java
index 89127fb9aa..c1065806b6 100644
--- a/layout/src/test/java/com/itextpdf/layout/renderer/TextPreprocessingUtilTest.java
+++ b/layout/src/test/java/com/itextpdf/layout/renderer/TextPreprocessingUtilTest.java
@@ -24,6 +24,7 @@ This file is part of the iText (R) project.
import com.itextpdf.io.font.otf.Glyph;
import com.itextpdf.io.font.otf.GlyphLine;
+import com.itextpdf.io.util.TextUtil;
import com.itextpdf.kernel.font.PdfFont;
import com.itextpdf.kernel.font.PdfFontFactory;
import com.itextpdf.test.ExtendedITextTest;
@@ -86,6 +87,9 @@ private void specialWhitespaceGlyphTest(int unicode) {
Glyph glyph = glyphLine.get(0);
Glyph space = pdfFont.getGlyph('\u0020');
- Assert.assertTrue(space.getCode() == glyph.getCode() && space.getWidth() == glyph.getWidth());
+ Assert.assertEquals(space.getCode(), glyph.getCode());
+ Assert.assertEquals(space.getWidth(), glyph.getWidth());
+ Assert.assertEquals(space.getUnicode(), glyph.getUnicode());
+ Assert.assertArrayEquals(TextUtil.convertFromUtf32(unicode), glyph.getChars());
}
}
diff --git a/layout/src/test/resources/com/itextpdf/layout/CanvasTest/cmp_canvasWithPageEnableTaggingTest01.pdf b/layout/src/test/resources/com/itextpdf/layout/CanvasTest/cmp_canvasWithPageEnableTaggingTest01.pdf
index 6502b796cd..5466863a98 100644
Binary files a/layout/src/test/resources/com/itextpdf/layout/CanvasTest/cmp_canvasWithPageEnableTaggingTest01.pdf and b/layout/src/test/resources/com/itextpdf/layout/CanvasTest/cmp_canvasWithPageEnableTaggingTest01.pdf differ
diff --git a/layout/src/test/resources/com/itextpdf/layout/FontSelectorTest/cmp_checkThreeFontsInOneLineWithUnicodeRange.pdf b/layout/src/test/resources/com/itextpdf/layout/FontSelectorTest/cmp_checkThreeFontsInOneLineWithUnicodeRange.pdf
index b04d1c9f2f..c436675d64 100644
Binary files a/layout/src/test/resources/com/itextpdf/layout/FontSelectorTest/cmp_checkThreeFontsInOneLineWithUnicodeRange.pdf and b/layout/src/test/resources/com/itextpdf/layout/FontSelectorTest/cmp_checkThreeFontsInOneLineWithUnicodeRange.pdf differ
diff --git a/layout/src/test/resources/com/itextpdf/layout/FontSelectorTest/cmp_notSignificantCharacterOfTheFontWithUnicodeRange.pdf b/layout/src/test/resources/com/itextpdf/layout/FontSelectorTest/cmp_notSignificantCharacterOfTheFontWithUnicodeRange.pdf
index 980431bbc9..4db94acb0f 100644
Binary files a/layout/src/test/resources/com/itextpdf/layout/FontSelectorTest/cmp_notSignificantCharacterOfTheFontWithUnicodeRange.pdf and b/layout/src/test/resources/com/itextpdf/layout/FontSelectorTest/cmp_notSignificantCharacterOfTheFontWithUnicodeRange.pdf differ
diff --git a/layout/src/test/resources/com/itextpdf/layout/LayoutTaggingPdf2Test/cmp_docWithSectInPdf2.pdf b/layout/src/test/resources/com/itextpdf/layout/LayoutTaggingPdf2Test/cmp_docWithSectInPdf2.pdf
index 5502c694d3..2ec2e61487 100644
Binary files a/layout/src/test/resources/com/itextpdf/layout/LayoutTaggingPdf2Test/cmp_docWithSectInPdf2.pdf and b/layout/src/test/resources/com/itextpdf/layout/LayoutTaggingPdf2Test/cmp_docWithSectInPdf2.pdf differ
diff --git a/layout/src/test/resources/com/itextpdf/layout/LayoutTaggingTest/cmp_checkParentTreeIfFormXObjectTaggedTest.pdf b/layout/src/test/resources/com/itextpdf/layout/LayoutTaggingTest/cmp_checkParentTreeIfFormXObjectTaggedTest.pdf
index 03cbfcf08c..eb19e3a6a1 100644
Binary files a/layout/src/test/resources/com/itextpdf/layout/LayoutTaggingTest/cmp_checkParentTreeIfFormXObjectTaggedTest.pdf and b/layout/src/test/resources/com/itextpdf/layout/LayoutTaggingTest/cmp_checkParentTreeIfFormXObjectTaggedTest.pdf differ
diff --git a/layout/src/test/resources/com/itextpdf/layout/LayoutTaggingTest/cmp_createTaggedVersionOneDotFourTest01.pdf b/layout/src/test/resources/com/itextpdf/layout/LayoutTaggingTest/cmp_createTaggedVersionOneDotFourTest01.pdf
index 5ac32dd02f..c1eb64a2e0 100644
Binary files a/layout/src/test/resources/com/itextpdf/layout/LayoutTaggingTest/cmp_createTaggedVersionOneDotFourTest01.pdf and b/layout/src/test/resources/com/itextpdf/layout/LayoutTaggingTest/cmp_createTaggedVersionOneDotFourTest01.pdf differ
diff --git a/layout/src/test/resources/com/itextpdf/layout/LayoutTaggingTest/cmp_linkTest01.pdf b/layout/src/test/resources/com/itextpdf/layout/LayoutTaggingTest/cmp_linkTest01.pdf
index 57a19fcf18..b4283165e3 100644
Binary files a/layout/src/test/resources/com/itextpdf/layout/LayoutTaggingTest/cmp_linkTest01.pdf and b/layout/src/test/resources/com/itextpdf/layout/LayoutTaggingTest/cmp_linkTest01.pdf differ
diff --git a/layout/src/test/resources/com/itextpdf/layout/LayoutTaggingTest/cmp_tableAddsScopeRegardlessOfHeaderId.pdf b/layout/src/test/resources/com/itextpdf/layout/LayoutTaggingTest/cmp_tableAddsScopeRegardlessOfHeaderId.pdf
new file mode 100644
index 0000000000..4bf6ac829c
Binary files /dev/null and b/layout/src/test/resources/com/itextpdf/layout/LayoutTaggingTest/cmp_tableAddsScopeRegardlessOfHeaderId.pdf differ
diff --git a/layout/src/test/resources/com/itextpdf/layout/LayoutTaggingTest/cmp_tableAppendsScopeNoneToCell.pdf b/layout/src/test/resources/com/itextpdf/layout/LayoutTaggingTest/cmp_tableAppendsScopeNoneToCell.pdf
new file mode 100644
index 0000000000..f76bfd344f
Binary files /dev/null and b/layout/src/test/resources/com/itextpdf/layout/LayoutTaggingTest/cmp_tableAppendsScopeNoneToCell.pdf differ
diff --git a/layout/src/test/resources/com/itextpdf/layout/LayoutTaggingTest/cmp_tableAppendsScopeToCell.pdf b/layout/src/test/resources/com/itextpdf/layout/LayoutTaggingTest/cmp_tableAppendsScopeToCell.pdf
new file mode 100644
index 0000000000..8f3355f3eb
Binary files /dev/null and b/layout/src/test/resources/com/itextpdf/layout/LayoutTaggingTest/cmp_tableAppendsScopeToCell.pdf differ
diff --git a/layout/src/test/resources/com/itextpdf/layout/LayoutTaggingTest/cmp_tableTest03.pdf b/layout/src/test/resources/com/itextpdf/layout/LayoutTaggingTest/cmp_tableTest03.pdf
index 0383faf185..0c1ea4fbf6 100644
Binary files a/layout/src/test/resources/com/itextpdf/layout/LayoutTaggingTest/cmp_tableTest03.pdf and b/layout/src/test/resources/com/itextpdf/layout/LayoutTaggingTest/cmp_tableTest03.pdf differ
diff --git a/layout/src/test/resources/com/itextpdf/layout/NonBreakingHyphenTest/cmp_nonBreakingHyphenDifferentFonts.pdf b/layout/src/test/resources/com/itextpdf/layout/NonBreakingHyphenTest/cmp_nonBreakingHyphenDifferentFonts.pdf
index b02e9bc0d7..986fe643f9 100644
Binary files a/layout/src/test/resources/com/itextpdf/layout/NonBreakingHyphenTest/cmp_nonBreakingHyphenDifferentFonts.pdf and b/layout/src/test/resources/com/itextpdf/layout/NonBreakingHyphenTest/cmp_nonBreakingHyphenDifferentFonts.pdf differ
diff --git a/layout/src/test/resources/com/itextpdf/layout/PdfUA2AnnotationsTest/cmp_pdfuaRubberstampAnnotationTest.pdf b/layout/src/test/resources/com/itextpdf/layout/PdfUA2AnnotationsTest/cmp_pdfuaRubberstampAnnotationTest.pdf
index c02dbb4942..488bf5575d 100644
Binary files a/layout/src/test/resources/com/itextpdf/layout/PdfUA2AnnotationsTest/cmp_pdfuaRubberstampAnnotationTest.pdf and b/layout/src/test/resources/com/itextpdf/layout/PdfUA2AnnotationsTest/cmp_pdfuaRubberstampAnnotationTest.pdf differ
diff --git a/layout/src/test/resources/com/itextpdf/layout/TableTest/cmp_keepTogetherCaptionAndHugeCell.pdf b/layout/src/test/resources/com/itextpdf/layout/TableTest/cmp_keepTogetherCaptionAndHugeCell.pdf
new file mode 100644
index 0000000000..400b0035c8
Binary files /dev/null and b/layout/src/test/resources/com/itextpdf/layout/TableTest/cmp_keepTogetherCaptionAndHugeCell.pdf differ
diff --git a/layout/src/test/resources/com/itextpdf/layout/TableTest/cmp_keepTogetherCaptionAndSplitCell.pdf b/layout/src/test/resources/com/itextpdf/layout/TableTest/cmp_keepTogetherCaptionAndSplitCell.pdf
new file mode 100644
index 0000000000..c232c8c123
Binary files /dev/null and b/layout/src/test/resources/com/itextpdf/layout/TableTest/cmp_keepTogetherCaptionAndSplitCell.pdf differ
diff --git a/layout/src/test/resources/com/itextpdf/layout/TableTest/cmp_keepTogetherCaptionDoesntFitPage.pdf b/layout/src/test/resources/com/itextpdf/layout/TableTest/cmp_keepTogetherCaptionDoesntFitPage.pdf
new file mode 100644
index 0000000000..b4f2d728be
Binary files /dev/null and b/layout/src/test/resources/com/itextpdf/layout/TableTest/cmp_keepTogetherCaptionDoesntFitPage.pdf differ
diff --git a/layout/src/test/resources/com/itextpdf/layout/TableTest/cmp_tableWithAlternatingRowsTest.pdf b/layout/src/test/resources/com/itextpdf/layout/TableTest/cmp_tableWithAlternatingRowsTest.pdf
index 452bb1c533..e3ea693bb9 100644
Binary files a/layout/src/test/resources/com/itextpdf/layout/TableTest/cmp_tableWithAlternatingRowsTest.pdf and b/layout/src/test/resources/com/itextpdf/layout/TableTest/cmp_tableWithAlternatingRowsTest.pdf differ
diff --git a/layout/src/test/resources/com/itextpdf/layout/TableTest/cmp_tableWithCollapsedBordersTest.pdf b/layout/src/test/resources/com/itextpdf/layout/TableTest/cmp_tableWithCollapsedBordersTest.pdf
index 4c4e684a0a..1cde41cd88 100644
Binary files a/layout/src/test/resources/com/itextpdf/layout/TableTest/cmp_tableWithCollapsedBordersTest.pdf and b/layout/src/test/resources/com/itextpdf/layout/TableTest/cmp_tableWithCollapsedBordersTest.pdf differ
diff --git a/layout/src/test/resources/com/itextpdf/layout/TableTest/cmp_tableWithEmptyRowAfterJustOneCellTest.pdf b/layout/src/test/resources/com/itextpdf/layout/TableTest/cmp_tableWithEmptyRowAfterJustOneCellTest.pdf
index 602d5bf1db..bf5ee52c55 100644
Binary files a/layout/src/test/resources/com/itextpdf/layout/TableTest/cmp_tableWithEmptyRowAfterJustOneCellTest.pdf and b/layout/src/test/resources/com/itextpdf/layout/TableTest/cmp_tableWithEmptyRowAfterJustOneCellTest.pdf differ
diff --git a/layout/src/test/resources/com/itextpdf/layout/TableTest/cmp_tableWithEmptyRowsAndSpansTest.pdf b/layout/src/test/resources/com/itextpdf/layout/TableTest/cmp_tableWithEmptyRowsAndSpansTest.pdf
new file mode 100644
index 0000000000..b0fbec2a39
Binary files /dev/null and b/layout/src/test/resources/com/itextpdf/layout/TableTest/cmp_tableWithEmptyRowsAndSpansTest.pdf differ
diff --git a/layout/src/test/resources/com/itextpdf/layout/TableTest/cmp_tableWithEmptyRowsBetweenFullRowsTest.pdf b/layout/src/test/resources/com/itextpdf/layout/TableTest/cmp_tableWithEmptyRowsBetweenFullRowsTest.pdf
index d17b88bf62..aad3d0b530 100644
Binary files a/layout/src/test/resources/com/itextpdf/layout/TableTest/cmp_tableWithEmptyRowsBetweenFullRowsTest.pdf and b/layout/src/test/resources/com/itextpdf/layout/TableTest/cmp_tableWithEmptyRowsBetweenFullRowsTest.pdf differ
diff --git a/layout/src/test/resources/com/itextpdf/layout/TabsTest/cmp_fillParagraphWithTabsDifferently.pdf b/layout/src/test/resources/com/itextpdf/layout/TabsTest/cmp_fillParagraphWithTabsDifferently.pdf
index 85dae06409..c276099de3 100644
Binary files a/layout/src/test/resources/com/itextpdf/layout/TabsTest/cmp_fillParagraphWithTabsDifferently.pdf and b/layout/src/test/resources/com/itextpdf/layout/TabsTest/cmp_fillParagraphWithTabsDifferently.pdf differ
diff --git a/layout/src/test/resources/com/itextpdf/layout/fonts/NOTICE.txt b/layout/src/test/resources/com/itextpdf/layout/fonts/NOTICE.txt
index 938af5af9d..e776c6f23a 100644
--- a/layout/src/test/resources/com/itextpdf/layout/fonts/NOTICE.txt
+++ b/layout/src/test/resources/com/itextpdf/layout/fonts/NOTICE.txt
@@ -10,6 +10,10 @@ Please notice that the following fonts are used with the mentioned below license
* NotoSans-Regular - SIL Open Font License v1.1
* NotoSansThai-Regular - SIL Open Font License v1.1
* Puritan2 - SIL Open Font License v1.1
+* NotoSansOldItalic-Regular.ttf - SIL Open Font License v1.1. Based on commit 20bc5918912503bc1537a407a694738c33c048aa (07.31.2020) from "https://github.com/googlefonts/noto-fonts"
+* NotoSansCJKjp-Regular.otf - SIL Open Font License v1.1
+* NotoSansCherokee-Regular.ttf - SIL Open Font License v1.1
+* NotoKufiArabic-Regular.ttf - SIL Open Font License v1.1
diff --git a/layout/src/test/resources/com/itextpdf/layout/fonts/NotoKufiArabic-Regular.ttf b/layout/src/test/resources/com/itextpdf/layout/fonts/NotoKufiArabic-Regular.ttf
new file mode 100644
index 0000000000..6686b1ef54
Binary files /dev/null and b/layout/src/test/resources/com/itextpdf/layout/fonts/NotoKufiArabic-Regular.ttf differ
diff --git a/layout/src/test/resources/com/itextpdf/layout/fonts/NotoSansCJKjp-Regular.otf b/layout/src/test/resources/com/itextpdf/layout/fonts/NotoSansCJKjp-Regular.otf
new file mode 100644
index 0000000000..296fbebd86
Binary files /dev/null and b/layout/src/test/resources/com/itextpdf/layout/fonts/NotoSansCJKjp-Regular.otf differ
diff --git a/layout/src/test/resources/com/itextpdf/layout/fonts/NotoSansCherokee-Regular.ttf b/layout/src/test/resources/com/itextpdf/layout/fonts/NotoSansCherokee-Regular.ttf
new file mode 100644
index 0000000000..749fad1e81
Binary files /dev/null and b/layout/src/test/resources/com/itextpdf/layout/fonts/NotoSansCherokee-Regular.ttf differ
diff --git a/layout/src/test/resources/com/itextpdf/layout/fonts/NotoSansOldItalic-Regular.ttf b/layout/src/test/resources/com/itextpdf/layout/fonts/NotoSansOldItalic-Regular.ttf
new file mode 100644
index 0000000000..83ff12fe2d
Binary files /dev/null and b/layout/src/test/resources/com/itextpdf/layout/fonts/NotoSansOldItalic-Regular.ttf differ
diff --git a/native-image-test/pom.xml b/native-image-test/pom.xml
new file mode 100644
index 0000000000..ee86f9ea56
--- /dev/null
+++ b/native-image-test/pom.xml
@@ -0,0 +1,136 @@
+
+
+ 4.0.0
+
+
+ com.itextpdf
+ root
+ 8.0.4
+
+
+ native-image-test
+
+ native-image-test
+ Native image tests.
+ https://itextpdf.com/
+
+
+ ${project.parent.version}
+ 0.10.1
+ 5.10.2
+ 1.10.2
+
+
+
+
+
+
+
+ com.itextpdf
+ font-asian
+ ${itext.version}
+
+
+ com.itextpdf
+ bouncy-castle-adapter
+ ${itext.version}
+
+
+ com.itextpdf
+ forms
+ ${itext.version}
+
+
+ com.itextpdf
+ hyph
+ ${itext.version}
+
+
+ com.itextpdf
+ io
+ ${itext.version}
+
+
+ com.itextpdf
+ kernel
+ ${itext.version}
+
+
+ com.itextpdf
+ layout
+ ${itext.version}
+
+
+ com.itextpdf
+ sign
+ ${itext.version}
+
+
+ com.itextpdf
+ svg
+ ${itext.version}
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter
+ ${junit.jupiter.version}
+ test
+
+
+ org.junit.platform
+ junit-platform-launcher
+ ${junit.platform.version}
+ test
+
+
+
+
+
+ native
+
+
+
+ maven-surefire-plugin
+
+ false
+
+
+
+ org.graalvm.buildtools
+ native-maven-plugin
+ ${native.maven.plugin.version}
+ true
+
+
+ test-native
+
+ test
+
+ test
+
+
+
+
+
+ --initialize-at-build-time=org.junit.validator.PublicClassValidator
+ -H:ResourceConfigurationFiles=${basedir}/src/test/resources/resource-config.json
+
+ -H:-CheckToolchain
+
+
+ false
+
+
+ false
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/native-image-test/src/test/java/com/itextpdf/nativeimage/BouncyCastleTest.java b/native-image-test/src/test/java/com/itextpdf/nativeimage/BouncyCastleTest.java
new file mode 100644
index 0000000000..3b27f30c71
--- /dev/null
+++ b/native-image-test/src/test/java/com/itextpdf/nativeimage/BouncyCastleTest.java
@@ -0,0 +1,77 @@
+/*
+ This file is part of the iText (R) project.
+ Copyright (c) 1998-2024 Apryse Group NV
+ Authors: Apryse Software.
+
+ This program is offered under a commercial and under the AGPL license.
+ For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
+
+ AGPL licensing:
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+ */
+package com.itextpdf.nativeimage;
+
+import com.itextpdf.kernel.pdf.PdfDictionary;
+import com.itextpdf.kernel.pdf.PdfDocument;
+import com.itextpdf.kernel.pdf.PdfName;
+import com.itextpdf.kernel.pdf.PdfReader;
+import com.itextpdf.kernel.pdf.ReaderProperties;
+import com.itextpdf.signatures.PdfPKCS7;
+import com.itextpdf.signatures.SignatureUtil;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.DisabledInNativeImage;
+
+class BouncyCastleTest {
+ private static final String SOURCE_FOLDER = "com/itextpdf/nativeimage/BouncyCastleTest/";
+
+ @Test
+ void readEncryptedDocument() throws IOException {
+ PdfReader reader = new PdfReader(SOURCE_FOLDER + "encrypted.pdf",
+ new ReaderProperties().setPassword("123".getBytes()));
+ PdfDocument pdfDocument = new PdfDocument(reader);
+
+ PdfDictionary form = pdfDocument.getCatalog().getPdfObject().getAsDictionary(PdfName.AcroForm);
+
+ PdfDictionary field = form.getAsArray(PdfName.Fields).getAsDictionary(0);
+
+ Assertions.assertEquals("ch", field.getAsString(PdfName.T).toUnicodeString());
+ Assertions.assertEquals("SomeStringValueInDictionary",
+ field.getAsDictionary(new PdfName("TestDic")).getAsString(new PdfName("TestString")).toUnicodeString());
+ Assertions.assertEquals("SomeStringValueInArray",
+ field.getAsArray(new PdfName("TestArray")).getAsString(0).toUnicodeString());
+
+ pdfDocument.close();
+ }
+
+ // By some reason it fails in native mode (works on java) on build agent so here we disable
+ @DisabledInNativeImage
+ @Test
+ void readSignature() throws IOException, GeneralSecurityException {
+ String filePath = SOURCE_FOLDER + "isa.pdf";
+ String signatureName = "Signature1";
+
+ PdfDocument document = new PdfDocument(new PdfReader(filePath));
+ SignatureUtil sigUtil = new SignatureUtil(document);
+ PdfPKCS7 pdfPKCS7 = sigUtil.readSignatureData(signatureName);
+
+ Assertions.assertTrue(pdfPKCS7.verifySignatureIntegrityAndAuthenticity());
+ Assertions.assertFalse(sigUtil.signatureCoversWholeDocument(signatureName));
+
+ document.close();
+ }
+}
diff --git a/native-image-test/src/test/java/com/itextpdf/nativeimage/FormsTest.java b/native-image-test/src/test/java/com/itextpdf/nativeimage/FormsTest.java
new file mode 100644
index 0000000000..e6c59333b3
--- /dev/null
+++ b/native-image-test/src/test/java/com/itextpdf/nativeimage/FormsTest.java
@@ -0,0 +1,59 @@
+/*
+ This file is part of the iText (R) project.
+ Copyright (c) 1998-2024 Apryse Group NV
+ Authors: Apryse Software.
+
+ This program is offered under a commercial and under the AGPL license.
+ For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
+
+ AGPL licensing:
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+ */
+package com.itextpdf.nativeimage;
+
+import com.itextpdf.forms.PdfAcroForm;
+import com.itextpdf.forms.fields.CheckBoxFormFieldBuilder;
+import com.itextpdf.forms.fields.PdfButtonFormField;
+import com.itextpdf.forms.fields.merging.AlwaysThrowExceptionStrategy;
+import com.itextpdf.forms.fields.merging.MergeFieldsStrategy;
+import com.itextpdf.forms.fields.merging.OnDuplicateFormFieldNameStrategy;
+import com.itextpdf.io.source.ByteArrayOutputStream;
+import com.itextpdf.kernel.exceptions.PdfException;
+import com.itextpdf.kernel.pdf.PdfDocument;
+import com.itextpdf.kernel.pdf.PdfWriter;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+class FormsTest {
+ @Test
+ void defaultStrategy() {
+ PdfDocument pdfDocument = new PdfDocument(new PdfWriter(new ByteArrayOutputStream()));
+ OnDuplicateFormFieldNameStrategy strategy = pdfDocument.getDiContainer()
+ .getInstance(OnDuplicateFormFieldNameStrategy.class);
+ Assertions.assertEquals(MergeFieldsStrategy.class, strategy.getClass());
+ }
+
+ @Test
+ void alwaysThrowStrategy() {
+ PdfDocument pdfDocument = new PdfDocument(new PdfWriter(new ByteArrayOutputStream()));
+ PdfAcroForm form = PdfAcroForm.getAcroForm(pdfDocument, true, new AlwaysThrowExceptionStrategy());
+ PdfButtonFormField field1 = new CheckBoxFormFieldBuilder(pdfDocument, "test").createCheckBox();
+ form.addField(field1);
+ PdfButtonFormField field2 = new CheckBoxFormFieldBuilder(pdfDocument, "test").createCheckBox();
+
+ Exception exception = Assertions.assertThrows(PdfException.class, () -> form.addField(field2));
+ Assertions.assertEquals("Field name test already exists in the form.", exception.getMessage());
+ }
+}
diff --git a/native-image-test/src/test/java/com/itextpdf/nativeimage/IoTest.java b/native-image-test/src/test/java/com/itextpdf/nativeimage/IoTest.java
new file mode 100644
index 0000000000..e98791b6ab
--- /dev/null
+++ b/native-image-test/src/test/java/com/itextpdf/nativeimage/IoTest.java
@@ -0,0 +1,301 @@
+/*
+ This file is part of the iText (R) project.
+ Copyright (c) 1998-2024 Apryse Group NV
+ Authors: Apryse Software.
+
+ This program is offered under a commercial and under the AGPL license.
+ For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
+
+ AGPL licensing:
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+ */
+package com.itextpdf.nativeimage;
+
+import com.itextpdf.io.codec.brotli.dec.Dictionary;
+import com.itextpdf.io.font.AdobeGlyphList;
+import com.itextpdf.io.font.CjkResourceLoader;
+import com.itextpdf.io.font.FontProgram;
+import com.itextpdf.io.font.FontProgramFactory;
+import com.itextpdf.io.font.Type1Font;
+import com.itextpdf.io.font.cmap.AbstractCMap;
+import com.itextpdf.io.font.constants.StandardFonts;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Map;
+import org.junit.jupiter.api.Test;
+
+import org.junit.jupiter.api.Assertions;
+
+class IoTest {
+ @Test
+ void adobeGlyphList() {
+ Assertions.assertEquals(97, AdobeGlyphList.nameToUnicode("a"));
+ }
+
+ @Test
+ void standardFonts() throws IOException {
+ checkType1Font(StandardFonts.COURIER);
+ checkType1Font(StandardFonts.COURIER_BOLD);
+ checkType1Font(StandardFonts.COURIER_BOLDOBLIQUE);
+ checkType1Font(StandardFonts.COURIER_OBLIQUE);
+
+ checkType1Font(StandardFonts.HELVETICA);
+ checkType1Font(StandardFonts.HELVETICA_BOLD);
+ checkType1Font(StandardFonts.HELVETICA_BOLDOBLIQUE);
+ checkType1Font(StandardFonts.HELVETICA_OBLIQUE);
+
+ checkType1Font(StandardFonts.SYMBOL);
+
+ checkType1Font(StandardFonts.TIMES_BOLD);
+ checkType1Font(StandardFonts.TIMES_BOLDITALIC);
+ checkType1Font(StandardFonts.TIMES_ITALIC);
+ checkType1Font(StandardFonts.TIMES_ROMAN);
+
+ checkType1Font(StandardFonts.ZAPFDINGBATS);
+ }
+
+ @Test
+ void fontAsianCmaps() {
+ checkFontAsianCmap("78-EUC-H", "Japan1");
+ checkFontAsianCmap("78-EUC-V", "Japan1");
+ checkFontAsianCmap("78-H", "Japan1");
+ checkFontAsianCmap("78-RKSJ-H", "Japan1");
+ checkFontAsianCmap("78-RKSJ-V", "Japan1");
+ checkFontAsianCmap("78-V", "Japan1");
+ checkFontAsianCmap("78ms-RKSJ-H", "Japan1");
+ checkFontAsianCmap("78ms-RKSJ-V", "Japan1");
+ checkFontAsianCmap("83pv-RKSJ-H", "Japan1");
+ checkFontAsianCmap("90ms-RKSJ-H", "Japan1");
+ checkFontAsianCmap("90ms-RKSJ-V", "Japan1");
+ checkFontAsianCmap("90msp-RKSJ-H", "Japan1");
+ checkFontAsianCmap("90msp-RKSJ-V", "Japan1");
+ checkFontAsianCmap("90pv-RKSJ-H", "Japan1");
+ checkFontAsianCmap("90pv-RKSJ-V", "Japan1");
+ checkFontAsianCmap("Add-H", "Japan1");
+ checkFontAsianCmap("Add-RKSJ-H", "Japan1");
+ checkFontAsianCmap("Add-RKSJ-V", "Japan1");
+ checkFontAsianCmap("Add-V", "Japan1");
+
+ checkFontAsianCmap("Adobe-CNS1-0", "CNS1");
+ checkFontAsianCmap("Adobe-CNS1-1", "CNS1");
+ checkFontAsianCmap("Adobe-CNS1-2", "CNS1");
+ checkFontAsianCmap("Adobe-CNS1-3", "CNS1");
+ checkFontAsianCmap("Adobe-CNS1-4", "CNS1");
+ checkFontAsianCmap("Adobe-CNS1-5", "CNS1");
+ checkFontAsianCmap("Adobe-CNS1-6", "CNS1");
+ checkFontAsianCmap("Adobe-CNS1-7", "CNS1");
+
+ checkFontAsianCmap("Adobe-GB1-0", "GB1");
+ checkFontAsianCmap("Adobe-GB1-1", "GB1");
+ checkFontAsianCmap("Adobe-GB1-2", "GB1");
+ checkFontAsianCmap("Adobe-GB1-3", "GB1");
+ checkFontAsianCmap("Adobe-GB1-4", "GB1");
+ checkFontAsianCmap("Adobe-GB1-5", "GB1");
+
+ checkFontAsianCmap("Adobe-Japan1-0", "Japan1");
+ checkFontAsianCmap("Adobe-Japan1-1", "Japan1");
+ checkFontAsianCmap("Adobe-Japan1-2", "Japan1");
+ checkFontAsianCmap("Adobe-Japan1-3", "Japan1");
+ checkFontAsianCmap("Adobe-Japan1-4", "Japan1");
+ checkFontAsianCmap("Adobe-Japan1-5", "Japan1");
+ checkFontAsianCmap("Adobe-Japan1-6", "Japan1");
+ checkFontAsianCmap("Adobe-Japan1-7", "Japan1");
+
+ checkFontAsianCmap("Adobe-Korea1-0", "Korea1");
+ checkFontAsianCmap("Adobe-Korea1-1", "Korea1");
+ checkFontAsianCmap("Adobe-Korea1-2", "Korea1");
+
+ checkFontAsianCmap("Adobe-KR-0", "KR");
+ checkFontAsianCmap("Adobe-KR-1", "KR");
+ checkFontAsianCmap("Adobe-KR-2", "KR");
+ checkFontAsianCmap("Adobe-KR-3", "KR");
+ checkFontAsianCmap("Adobe-KR-4", "KR");
+ checkFontAsianCmap("Adobe-KR-5", "KR");
+ checkFontAsianCmap("Adobe-KR-6", "KR");
+ checkFontAsianCmap("Adobe-KR-7", "KR");
+ checkFontAsianCmap("Adobe-KR-8", "KR");
+ checkFontAsianCmap("Adobe-KR-9", "KR");
+
+ checkFontAsianCmap("B5-H", "CNS1");
+ checkFontAsianCmap("B5-V", "CNS1");
+ checkFontAsianCmap("B5pc-H", "CNS1");
+ checkFontAsianCmap("B5pc-V", "CNS1");
+ checkFontAsianCmap("CNS1-H", "CNS1");
+ checkFontAsianCmap("CNS1-V", "CNS1");
+ checkFontAsianCmap("CNS2-H", "CNS1");
+ checkFontAsianCmap("CNS2-V", "CNS1");
+ checkFontAsianCmap("CNS-EUC-H", "CNS1");
+ checkFontAsianCmap("CNS-EUC-V", "CNS1");
+ checkFontAsianCmap("ETen-B5-H", "CNS1");
+ checkFontAsianCmap("ETen-B5-V", "CNS1");
+ checkFontAsianCmap("ETenms-B5-H", "CNS1");
+ checkFontAsianCmap("ETenms-B5-V", "CNS1");
+ checkFontAsianCmap("ETHK-B5-H", "CNS1");
+ checkFontAsianCmap("ETHK-B5-V", "CNS1");
+
+ checkFontAsianCmap("EUC-H", "Japan1");
+ checkFontAsianCmap("EUC-V", "Japan1");
+ checkFontAsianCmap("Ext-H", "Japan1");
+ checkFontAsianCmap("Ext-RKSJ-H", "Japan1");
+ checkFontAsianCmap("Ext-RKSJ-V", "Japan1");
+ checkFontAsianCmap("Ext-V", "Japan1");
+
+ checkFontAsianCmap("GB-EUC-H", "GB1");
+ checkFontAsianCmap("GB-EUC-V", "GB1");
+ checkFontAsianCmap("GBK2K-H", "GB1");
+ checkFontAsianCmap("GBK2K-V", "GB1");
+ checkFontAsianCmap("GBK-EUC-H", "GB1");
+ checkFontAsianCmap("GBK-EUC-V", "GB1");
+ checkFontAsianCmap("GBKp-EUC-H", "GB1");
+ checkFontAsianCmap("GBKp-EUC-V", "GB1");
+ checkFontAsianCmap("GBpc-EUC-H", "GB1");
+ checkFontAsianCmap("GBpc-EUC-V", "GB1");
+ checkFontAsianCmap("GBT-EUC-H", "GB1");
+ checkFontAsianCmap("GBT-EUC-V", "GB1");
+ checkFontAsianCmap("GBT-H", "GB1");
+ checkFontAsianCmap("GBT-V", "GB1");
+ checkFontAsianCmap("GBTpc-EUC-H", "GB1");
+ checkFontAsianCmap("GBTpc-EUC-V", "GB1");
+
+ checkFontAsianCmap("H", "Japan1");
+ checkFontAsianCmap("Hankaku", "Japan1");
+ checkFontAsianCmap("Hiragana", "Japan1");
+
+ checkFontAsianCmap("HKdla-B5-H", "CNS1");
+ checkFontAsianCmap("HKdla-B5-V", "CNS1");
+ checkFontAsianCmap("HKdlb-B5-H", "CNS1");
+ checkFontAsianCmap("HKdlb-B5-V", "CNS1");
+ checkFontAsianCmap("HKgccs-B5-H", "CNS1");
+ checkFontAsianCmap("HKgccs-B5-V", "CNS1");
+ checkFontAsianCmap("HKm314-B5-H", "CNS1");
+ checkFontAsianCmap("HKm314-B5-V", "CNS1");
+ checkFontAsianCmap("HKm471-B5-H", "CNS1");
+ checkFontAsianCmap("HKm471-B5-V", "CNS1");
+ checkFontAsianCmap("HKscs-B5-H", "CNS1");
+ checkFontAsianCmap("HKscs-B5-V", "CNS1");
+
+ checkFontAsianCmap("Identity-H", "Identity");
+ checkFontAsianCmap("Identity-V", "Identity");
+
+ checkFontAsianCmap("Katakana", "Japan1");
+
+ checkFontAsianCmap("KSC-EUC-H", "Korea1");
+ checkFontAsianCmap("KSC-EUC-V", "Korea1");
+ checkFontAsianCmap("KSC-H", "Korea1");
+ checkFontAsianCmap("KSC-Johab-H", "Korea1");
+ checkFontAsianCmap("KSC-Johab-V", "Korea1");
+ checkFontAsianCmap("KSC-V", "Korea1");
+ checkFontAsianCmap("KSCms-UHC-H", "Korea1");
+ checkFontAsianCmap("KSCms-UHC-HW-H", "Korea1");
+ checkFontAsianCmap("KSCms-UHC-HW-V", "Korea1");
+ checkFontAsianCmap("KSCms-UHC-V", "Korea1");
+ checkFontAsianCmap("KSCpc-EUC-H", "Korea1");
+ checkFontAsianCmap("KSCpc-EUC-V", "Korea1");
+
+ checkFontAsianCmap("NWP-H", "Japan1");
+ checkFontAsianCmap("NWP-V", "Japan1");
+ checkFontAsianCmap("RKSJ-H", "Japan1");
+ checkFontAsianCmap("RKSJ-V", "Japan1");
+ checkFontAsianCmap("Roman", "Japan1");
+
+ checkFontAsianCmap("UniAKR-UTF8-H", "KR");
+ checkFontAsianCmap("UniAKR-UTF16-H", "KR");
+ checkFontAsianCmap("UniAKR-UTF32-H", "KR");
+
+ checkFontAsianCmap("UniCNS-UCS2-H", "CNS1");
+ checkFontAsianCmap("UniCNS-UCS2-V", "CNS1");
+ checkFontAsianCmap("UniCNS-UTF8-H", "CNS1");
+ checkFontAsianCmap("UniCNS-UTF8-V", "CNS1");
+ checkFontAsianCmap("UniCNS-UTF16-H", "CNS1");
+ checkFontAsianCmap("UniCNS-UTF16-V", "CNS1");
+ checkFontAsianCmap("UniCNS-UTF32-H", "CNS1");
+ checkFontAsianCmap("UniCNS-UTF32-V", "CNS1");
+
+ checkFontAsianCmap("UniGB-UCS2-H", "GB1");
+ checkFontAsianCmap("UniGB-UCS2-V", "GB1");
+ checkFontAsianCmap("UniGB-UTF8-H", "GB1");
+ checkFontAsianCmap("UniGB-UTF8-V", "GB1");
+ checkFontAsianCmap("UniGB-UTF16-H", "GB1");
+ checkFontAsianCmap("UniGB-UTF16-V", "GB1");
+ checkFontAsianCmap("UniGB-UTF32-H", "GB1");
+ checkFontAsianCmap("UniGB-UTF32-V", "GB1");
+
+ checkFontAsianCmap("UniJIS2004-UTF8-H", "Japan1");
+ checkFontAsianCmap("UniJIS2004-UTF8-V", "Japan1");
+ checkFontAsianCmap("UniJIS2004-UTF16-H", "Japan1");
+ checkFontAsianCmap("UniJIS2004-UTF16-V", "Japan1");
+ checkFontAsianCmap("UniJIS2004-UTF32-H", "Japan1");
+ checkFontAsianCmap("UniJIS2004-UTF32-V", "Japan1");
+ checkFontAsianCmap("UniJIS-UCS2-H", "Japan1");
+ checkFontAsianCmap("UniJIS-UCS2-HW-H", "Japan1");
+ checkFontAsianCmap("UniJIS-UCS2-HW-V", "Japan1");
+ checkFontAsianCmap("UniJIS-UCS2-V", "Japan1");
+ checkFontAsianCmap("UniJIS-UTF8-H", "Japan1");
+ checkFontAsianCmap("UniJIS-UTF8-V", "Japan1");
+ checkFontAsianCmap("UniJIS-UTF16-H", "Japan1");
+ checkFontAsianCmap("UniJIS-UTF16-V", "Japan1");
+ checkFontAsianCmap("UniJIS-UTF32-H", "Japan1");
+ checkFontAsianCmap("UniJIS-UTF32-V", "Japan1");
+ checkFontAsianCmap("UniJISPro-UCS2-HW-V", "Japan1");
+ checkFontAsianCmap("UniJISPro-UCS2-V", "Japan1");
+ checkFontAsianCmap("UniJISPro-UTF8-V", "Japan1");
+ checkFontAsianCmap("UniJISX0213-UTF32-H", "Japan1");
+ checkFontAsianCmap("UniJISX0213-UTF32-V", "Japan1");
+ checkFontAsianCmap("UniJISX02132004-UTF32-H", "Japan1");
+ checkFontAsianCmap("UniJISX02132004-UTF32-V", "Japan1");
+
+ checkFontAsianCmap("UniKS-UCS2-H", "Korea1");
+ checkFontAsianCmap("UniKS-UCS2-V", "Korea1");
+ checkFontAsianCmap("UniKS-UTF8-H", "Korea1");
+ checkFontAsianCmap("UniKS-UTF8-V", "Korea1");
+ checkFontAsianCmap("UniKS-UTF16-H", "Korea1");
+ checkFontAsianCmap("UniKS-UTF16-V", "Korea1");
+ checkFontAsianCmap("UniKS-UTF32-H", "Korea1");
+ checkFontAsianCmap("UniKS-UTF32-V", "Korea1");
+
+ checkFontAsianCmap("V", "Japan1");
+ checkFontAsianCmap("WP-Symbol", "Japan1");
+
+ checkFontAsianCmap("toUnicode/Adobe-CNS1-UCS2", "Adobe_CNS1_UCS2");
+ checkFontAsianCmap("toUnicode/Adobe-GB1-UCS2", "Adobe_GB1_UCS2");
+ checkFontAsianCmap("toUnicode/Adobe-Japan1-UCS2", "Adobe_Japan1_UCS2");
+ checkFontAsianCmap("toUnicode/Adobe-Korea1-UCS2", "Adobe_Korea1_UCS2");
+ checkFontAsianCmap("toUnicode/Adobe-KR-UCS2", "Adobe_KR_UCS2");
+ }
+
+ @Test
+ void predefinedCidFonts() {
+ Map> cidFonts = CjkResourceLoader.getAllPredefinedCidFonts();
+ Assertions.assertEquals(11, cidFonts.size());
+ }
+
+ @Test
+ void brotliData() {
+ ByteBuffer buf = Dictionary.getData();
+ Assertions.assertEquals(122784, buf.remaining());
+ }
+
+ private void checkFontAsianCmap(String cmapName, String ordering) {
+ AbstractCMap cmap = CjkResourceLoader.getUni2CidCmap(cmapName);
+ Assertions.assertEquals(cmapName.substring(cmapName.lastIndexOf("/") + 1), cmap.getName());
+ Assertions.assertEquals(ordering, cmap.getOrdering());
+ }
+
+ private void checkType1Font(String fontName) throws IOException {
+ FontProgram font = FontProgramFactory.createFont(fontName, null, false);
+ Assertions.assertInstanceOf(Type1Font.class, font);
+ Assertions.assertEquals(fontName, font.getFontNames().getFontName());
+ }
+}
diff --git a/native-image-test/src/test/java/com/itextpdf/nativeimage/KernelTest.java b/native-image-test/src/test/java/com/itextpdf/nativeimage/KernelTest.java
new file mode 100644
index 0000000000..711d12b831
--- /dev/null
+++ b/native-image-test/src/test/java/com/itextpdf/nativeimage/KernelTest.java
@@ -0,0 +1,35 @@
+/*
+ This file is part of the iText (R) project.
+ Copyright (c) 1998-2024 Apryse Group NV
+ Authors: Apryse Software.
+
+ This program is offered under a commercial and under the AGPL license.
+ For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
+
+ AGPL licensing:
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+ */
+package com.itextpdf.nativeimage;
+
+import com.itextpdf.kernel.pdf.PdfName;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+class KernelTest {
+ @Test
+ void staticPdfNames() {
+ Assertions.assertTrue(PdfName.staticNames.size() > 800);
+ }
+}
diff --git a/native-image-test/src/test/java/com/itextpdf/nativeimage/LayoutTest.java b/native-image-test/src/test/java/com/itextpdf/nativeimage/LayoutTest.java
new file mode 100644
index 0000000000..e171d0e5ec
--- /dev/null
+++ b/native-image-test/src/test/java/com/itextpdf/nativeimage/LayoutTest.java
@@ -0,0 +1,442 @@
+/*
+ This file is part of the iText (R) project.
+ Copyright (c) 1998-2024 Apryse Group NV
+ Authors: Apryse Software.
+
+ This program is offered under a commercial and under the AGPL license.
+ For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
+
+ AGPL licensing:
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+ */
+package com.itextpdf.nativeimage;
+
+import com.itextpdf.layout.hyphenation.Hyphenation;
+import com.itextpdf.layout.hyphenation.HyphenationConfig;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+class LayoutTest {
+
+ @Test
+ void afHyphenation() {
+ Hyphenation hyph = getHyphenation("af", "Country");
+ Assertions.assertArrayEquals(new int[]{4}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void asHyphenation() {
+ Hyphenation hyph = getHyphenation("as", "\u09A8\u09AE\u09B8\u09CD\u0995\u09BE\u09F0");
+ Assertions.assertArrayEquals(new int[]{2}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void bgHyphenation() {
+ Hyphenation hyph = getHyphenation("bg", "\u0417\u0434\u0440\u0430\u0432\u0435\u0439");
+ Assertions.assertArrayEquals(new int[]{4}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void bnHyphenation() {
+ Hyphenation hyph = getHyphenation("bn", "\u0986\u09B2\u09BE\u0987\u0995\u09C1\u09AE");
+ Assertions.assertArrayEquals(new int[]{}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void caHyphenation() {
+ Hyphenation hyph = getHyphenation("ca", "Benvinguts");
+ Assertions.assertArrayEquals(new int[]{3, 6}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void copHyphenation() {
+ Hyphenation hyph = getHyphenation("cop", "\u2C98\u2C89\u2CA7\u2CA2\u2C89\u2C99\u0300\u2C9B\u2CAD\u2C8F\u2C99\u2C93");
+ Assertions.assertArrayEquals(new int[]{2, 10}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void csHyphenation() {
+ Hyphenation hyph = getHyphenation("cs", "country");
+ Assertions.assertArrayEquals(new int[]{4, 5}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void cyHyphenation() {
+ Hyphenation hyph = getHyphenation("cy", "country");
+ Assertions.assertArrayEquals(new int[]{4, 5}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void daHyphenation() {
+ Hyphenation hyph = getHyphenation("da", "country");
+ Assertions.assertArrayEquals(new int[]{2, 4}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void deHyphenation() {
+ Hyphenation hyph = getHyphenation("de", "Tage");
+ Assertions.assertArrayEquals(new int[]{2}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void de1901Hyphenation() {
+ Hyphenation hyph = getHyphenation("de_1901", "Tage");
+ Assertions.assertArrayEquals(new int[]{2}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void deCHHyphenation() {
+ Hyphenation hyph = getHyphenation("de_CH", "Tage");
+ Assertions.assertArrayEquals(new int[]{2}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void deDRHyphenation() {
+ Hyphenation hyph = getHyphenation("de_DR", "Tage");
+ Assertions.assertArrayEquals(new int[]{2}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void elHyphenation() {
+ Hyphenation hyph = getHyphenation("el", "\u03BA\u03B1\u03BB\u03B7\u03BC\u03AD\u03C1\u03B1");
+ Assertions.assertArrayEquals(new int[]{2, 4}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void elPolytonHyphenation() {
+ Hyphenation hyph = getHyphenation("el_Polyton", "\u03BA\u03B1\u03BB\u03B7\u03BC\u03AD\u03C1\u03B1");
+ Assertions.assertArrayEquals(new int[]{2, 4, 6}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void enHyphenation() {
+ Hyphenation hyph = getHyphenation("en", "country");
+ Assertions.assertArrayEquals(new int[]{4}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void enGBHyphenation() {
+ Hyphenation hyph = getHyphenation("en_GB", "country");
+ Assertions.assertArrayEquals(new int[]{4}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void enUSHyphenation() {
+ Hyphenation hyph = getHyphenation("en_US", "country");
+ Assertions.assertArrayEquals(new int[]{4}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void eoHyphenation() {
+ Hyphenation hyph = getHyphenation("eo", "country");
+ Assertions.assertArrayEquals(new int[]{2, 4}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void esHyphenation() {
+ Hyphenation hyph = getHyphenation("es", "gracias");
+ Assertions.assertArrayEquals(new int[]{3}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void etHyphenation() {
+ Hyphenation hyph = getHyphenation("et", "Vabandust");
+ Assertions.assertArrayEquals(new int[]{2, 5}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void euHyphenation() {
+ Hyphenation hyph = getHyphenation("eu", "euskara");
+ Assertions.assertArrayEquals(new int[]{3, 5}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void fiHyphenation() {
+ Hyphenation hyph = getHyphenation("fi", "N\u00E4kemiin");
+ Assertions.assertArrayEquals(new int[]{2, 4}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void frHyphenation() {
+ Hyphenation hyph = getHyphenation("fr", "country");
+ Assertions.assertArrayEquals(new int[]{4}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void gaHyphenation() {
+ Hyphenation hyph = getHyphenation("ga", "country");
+ Assertions.assertArrayEquals(new int[]{4}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void glHyphenation() {
+ Hyphenation hyph = getHyphenation("gl", "country");
+ Assertions.assertArrayEquals(new int[]{2, 4}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void grcHyphenation() {
+ Hyphenation hyph = getHyphenation("grc", "\u03BA\u03B1\u03BB\u03B7\u03BC\u03AD\u03C1\u03B1");
+ Assertions.assertArrayEquals(new int[]{2, 4, 6}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void guHyphenation() {
+ Hyphenation hyph = getHyphenation("hi", "\u0938\u0941\u092A\u094D\u0930\u092D\u093E\u0924\u092E\u094D");
+ Assertions.assertArrayEquals(new int[]{5}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void hrHyphenation() {
+ Hyphenation hyph = getHyphenation("hr", "country");
+ Assertions.assertArrayEquals(new int[]{2, 4}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void hsbHyphenation() {
+ Hyphenation hyph = getHyphenation("hsb", "country");
+ Assertions.assertArrayEquals(new int[]{4}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void huHyphenation() {
+ Hyphenation hyph = getHyphenation("hu", "sziasztok");
+ Assertions.assertArrayEquals(new int[]{3, 6}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void hyHyphenation() {
+ Hyphenation hyph = getHyphenation("hy", "\u0577\u0576\u0578\u0580\u0570\u0561\u056F\u0561\u056C\u0578\u0582\u0569\u0575\u0578\u0582\u0576");
+ Assertions.assertArrayEquals(new int[]{6, 8}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void iaHyphenation() {
+ Hyphenation hyph = getHyphenation("ia", "country");
+ Assertions.assertArrayEquals(new int[]{3, 4}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void idHyphenation() {
+ Hyphenation hyph = getHyphenation("id", "country");
+ Assertions.assertArrayEquals(new int[]{2, 4}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void isHyphenation() {
+ Hyphenation hyph = getHyphenation("is", "country");
+ Assertions.assertArrayEquals(new int[]{4}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void itHyphenation() {
+ Hyphenation hyph = getHyphenation("it", "country");
+ Assertions.assertArrayEquals(new int[]{4}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void kmrHyphenation() {
+ Hyphenation hyph = getHyphenation("kmr", "country");
+ Assertions.assertArrayEquals(new int[]{4}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void knHyphenation() {
+ Hyphenation hyph = getHyphenation("kn", "\u0C95\u0CA8\u0CCD\u0CA8\u0CA1");
+ Assertions.assertArrayEquals(new int[]{}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void laHyphenation() {
+ Hyphenation hyph = getHyphenation("la", "country");
+ Assertions.assertArrayEquals(new int[]{4}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void loHyphenation() {
+ Hyphenation hyph = getHyphenation("lo", "\u0E8D\u0EB4\u0E99\u0E94\u0EB5\u0E95\u0EC9\u0EAD\u0E99\u0EAE\u0EB1\u0E9A");
+ Assertions.assertArrayEquals(new int[]{3}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void ltHyphenation() {
+ Hyphenation hyph = getHyphenation("lt", "Labanakt");
+ Assertions.assertArrayEquals(new int[]{2, 4}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void lvHyphenation() {
+ Hyphenation hyph = getHyphenation("lv", "Labvakar");
+ Assertions.assertArrayEquals(new int[]{3, 5}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void mlHyphenation() {
+ Hyphenation hyph = getHyphenation("ml", "\u0D38\u0D4D\u0D35\u0D3E\u0D17\u0D24\u0D02");
+ Assertions.assertArrayEquals(new int[]{}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void mnHyphenation() {
+ Hyphenation hyph = getHyphenation("mn", "\u04E8\u0440\u0448\u04E9\u04E9\u0433\u04E9\u04E9\u0440\u044D\u0439");
+ Assertions.assertArrayEquals(new int[]{2, 5, 8}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void mrHyphenation() {
+ Hyphenation hyph = getHyphenation("mr", "\u0928\u092E\u0938\u094D\u0915\u093E\u0930");
+ Assertions.assertArrayEquals(new int[]{2}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void nbHyphenation() {
+ Hyphenation hyph = getHyphenation("nb", "country");
+ Assertions.assertArrayEquals(new int[]{4}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void nlHyphenation() {
+ Hyphenation hyph = getHyphenation("nl", "country");
+ Assertions.assertArrayEquals(new int[]{4}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void nnHyphenation() {
+ Hyphenation hyph = getHyphenation("nn", "country");
+ Assertions.assertArrayEquals(new int[]{4}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void noHyphenation() {
+ Hyphenation hyph = getHyphenation("no", "country");
+ Assertions.assertArrayEquals(new int[]{4}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void orHyphenation() {
+ Hyphenation hyph = getHyphenation("or", "\u0B28\u0B2E\u0B38\u0B4D\u0B15\u0B3E\u0B30");
+ Assertions.assertArrayEquals(new int[]{2}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void paHyphenation() {
+ Hyphenation hyph = getHyphenation("pa", "\u0A28\u0A2E\u0A38\u0A15\u0A3E\u0A30");
+ Assertions.assertArrayEquals(new int[]{2}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void plHyphenation() {
+ Hyphenation hyph = getHyphenation("pl", "country");
+ Assertions.assertArrayEquals(new int[]{2, 4}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void ptHyphenation() {
+ Hyphenation hyph = getHyphenation("pt", "country");
+ Assertions.assertArrayEquals(new int[]{2, 4}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void roHyphenation() {
+ Hyphenation hyph = getHyphenation("ro", "country");
+ Assertions.assertArrayEquals(new int[]{2, 4}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void ruHyphenation() {
+ Hyphenation hyph = getHyphenation("ru", "\u0437\u0434\u0440\u0430\u0432\u0441\u0442\u0432\u0443\u0439");
+ Assertions.assertArrayEquals(new int[]{5}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void saHyphenation() {
+ Hyphenation hyph = getHyphenation("sa", "country");
+ Assertions.assertArrayEquals(new int[]{2, 3}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void skHyphenation() {
+ Hyphenation hyph = getHyphenation("sk", "country");
+ Assertions.assertArrayEquals(new int[]{4}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void slHyphenation() {
+ Hyphenation hyph = getHyphenation("sl", "country");
+ Assertions.assertArrayEquals(new int[]{2, 4}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void srCyrlHyphenation() {
+ Hyphenation hyph = getHyphenation("sr_Cyrl", "\u0414\u043E\u0431\u0440\u043E\u0434\u043E\u0448\u043B\u0438");
+ Assertions.assertArrayEquals(new int[]{2, 5, 7}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void srLatnHyphenation() {
+ Hyphenation hyph = getHyphenation("sr_Latn", "country");
+ Assertions.assertArrayEquals(new int[]{2, 4}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void svHyphenation() {
+ Hyphenation hyph = getHyphenation("sv", "V\u00E4lkommen");
+ Assertions.assertArrayEquals(new int[]{3, 6}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void taHyphenation() {
+ Hyphenation hyph = getHyphenation("ta", "\u0BB5\u0BBE\u0BB0\u0BC1\u0B99\u0BCD\u0B95\u0BB3\u0BCD");
+ Assertions.assertArrayEquals(new int[]{}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void teHyphenation() {
+ Hyphenation hyph = getHyphenation("te", "\u0C38\u0C41\u0C38\u0C4D\u0C35\u0C3E\u0C17\u0C24\u0C02");
+ Assertions.assertArrayEquals(new int[]{}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void tkHyphenation() {
+ Hyphenation hyph = getHyphenation("tk", "country");
+ Assertions.assertArrayEquals(new int[]{4, 5}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void trHyphenation() {
+ Hyphenation hyph = getHyphenation("tr", "Merhaba");
+ Assertions.assertArrayEquals(new int[]{3, 5}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void ukHyphenation() {
+ Hyphenation hyph = getHyphenation("uk", "\u0437\u0434\u0440\u0430\u0432\u0441\u0442\u0432\u0443\u0439");
+ Assertions.assertArrayEquals(new int[]{5}, hyph.getHyphenationPoints());
+ }
+
+ @Test
+ void zhLatnHyphenation() {
+ Hyphenation hyph = getHyphenation("zh_Latn", "country");
+ Assertions.assertArrayEquals(new int[]{4}, hyph.getHyphenationPoints());
+ }
+
+ private Hyphenation getHyphenation(String lang, String word) {
+ String[] parts = lang.split("_");
+ lang = parts[0];
+ String country = (parts.length == 2) ? parts[1] : null;
+ HyphenationConfig config = new HyphenationConfig(lang, country, 2, 2);
+ return config.hyphenate(word);
+ }
+}
diff --git a/native-image-test/src/test/java/com/itextpdf/nativeimage/SvgTest.java b/native-image-test/src/test/java/com/itextpdf/nativeimage/SvgTest.java
new file mode 100644
index 0000000000..0a73ba01ae
--- /dev/null
+++ b/native-image-test/src/test/java/com/itextpdf/nativeimage/SvgTest.java
@@ -0,0 +1,62 @@
+/*
+ This file is part of the iText (R) project.
+ Copyright (c) 1998-2024 Apryse Group NV
+ Authors: Apryse Software.
+
+ This program is offered under a commercial and under the AGPL license.
+ For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
+
+ AGPL licensing:
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+ */
+package com.itextpdf.nativeimage;
+
+import com.itextpdf.styledxmlparser.css.ICssResolver;
+import com.itextpdf.styledxmlparser.jsoup.nodes.Element;
+import com.itextpdf.styledxmlparser.jsoup.parser.Tag;
+import com.itextpdf.styledxmlparser.node.INode;
+import com.itextpdf.styledxmlparser.node.impl.jsoup.node.JsoupElementNode;
+import com.itextpdf.svg.SvgConstants;
+import com.itextpdf.svg.css.SvgCssContext;
+import com.itextpdf.svg.css.impl.SvgStyleResolver;
+import com.itextpdf.svg.processors.impl.SvgConverterProperties;
+import com.itextpdf.svg.processors.impl.SvgProcessorContext;
+
+import java.util.Map;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+class SvgTest {
+
+ @Test
+ void defaultCss() {
+ ICssResolver styleResolver = new SvgStyleResolver(new SvgProcessorContext(new SvgConverterProperties()));
+ Element svg = new Element(Tag.valueOf("svg"), "");
+ INode svgNode = new JsoupElementNode(svg);
+ Map resolvedStyles = styleResolver.resolveStyles(svgNode, new SvgCssContext());
+
+ Assertions.assertEquals("1", resolvedStyles.get(SvgConstants.Attributes.STROKE_OPACITY));
+ Assertions.assertEquals("1px", resolvedStyles.get(SvgConstants.Attributes.STROKE_WIDTH));
+ Assertions.assertEquals(SvgConstants.Values.NONE, resolvedStyles.get(SvgConstants.Attributes.STROKE));
+ Assertions.assertEquals(SvgConstants.Values.BUTT, resolvedStyles.get(SvgConstants.Attributes.STROKE_LINECAP));
+ Assertions.assertEquals("0", resolvedStyles.get(SvgConstants.Attributes.STROKE_DASHOFFSET));
+ Assertions.assertEquals(SvgConstants.Values.NONE, resolvedStyles.get(SvgConstants.Attributes.STROKE_DASHARRAY));
+ Assertions.assertEquals("4", resolvedStyles.get(SvgConstants.Attributes.STROKE_MITERLIMIT));
+ Assertions.assertEquals("black", resolvedStyles.get(SvgConstants.Attributes.FILL));
+ Assertions.assertEquals(SvgConstants.Values.FILL_RULE_NONZERO, resolvedStyles.get(SvgConstants.Attributes.FILL_RULE));
+ Assertions.assertEquals("1", resolvedStyles.get(SvgConstants.Attributes.FILL_OPACITY));
+ Assertions.assertEquals("helvetica", resolvedStyles.get(SvgConstants.Attributes.FONT_FAMILY));
+ Assertions.assertEquals("9pt", resolvedStyles.get(SvgConstants.Attributes.FONT_SIZE));
+ }
+}
diff --git a/native-image-test/src/test/resources/com/itextpdf/nativeimage/BouncyCastleTest/encrypted.pdf b/native-image-test/src/test/resources/com/itextpdf/nativeimage/BouncyCastleTest/encrypted.pdf
new file mode 100644
index 0000000000..ed878c39b4
Binary files /dev/null and b/native-image-test/src/test/resources/com/itextpdf/nativeimage/BouncyCastleTest/encrypted.pdf differ
diff --git a/native-image-test/src/test/resources/com/itextpdf/nativeimage/BouncyCastleTest/isa.pdf b/native-image-test/src/test/resources/com/itextpdf/nativeimage/BouncyCastleTest/isa.pdf
new file mode 100644
index 0000000000..fe9593ae5e
Binary files /dev/null and b/native-image-test/src/test/resources/com/itextpdf/nativeimage/BouncyCastleTest/isa.pdf differ
diff --git a/native-image-test/src/test/resources/resource-config.json b/native-image-test/src/test/resources/resource-config.json
new file mode 100644
index 0000000000..c964742e66
--- /dev/null
+++ b/native-image-test/src/test/resources/resource-config.json
@@ -0,0 +1,8 @@
+{
+ "resources":{
+ "includes":[{
+ "pattern":"\\Qcom/itextpdf/nativeimage/BouncyCastleTest/encrypted.pdf\\E"
+ }, {
+ "pattern":"\\Qcom/itextpdf/nativeimage/BouncyCastleTest/isa.pdf\\E"
+ }]}
+}
diff --git a/pdfa/pom.xml b/pdfa/pom.xml
index e9c0725007..f82ea8ae52 100644
--- a/pdfa/pom.xml
+++ b/pdfa/pom.xml
@@ -1,14 +1,18 @@
4.0.0
+
com.itextpdfroot
- 8.0.3
+ 8.0.4
+
pdfa
+
iText - pdfahttps://itextpdf.com/
+
com.itextpdf
diff --git a/pdfa/src/main/java/com/itextpdf/pdfa/PdfAXMPUtil.java b/pdfa/src/main/java/com/itextpdf/pdfa/PdfAXMPUtil.java
index f897972c9a..8f6b6e4789 100644
--- a/pdfa/src/main/java/com/itextpdf/pdfa/PdfAXMPUtil.java
+++ b/pdfa/src/main/java/com/itextpdf/pdfa/PdfAXMPUtil.java
@@ -38,7 +38,7 @@ public class PdfAXMPUtil {
" \n" +
" \n" +
" \n" +
- " http://www.aiim.org/pdfua/ns/id/\n" +
+ " \n" +
" pdfuaid\n" +
" PDF/UA identification schema\n" +
" \n" +
diff --git a/pdfa/src/main/java/com/itextpdf/pdfa/checker/PdfA1Checker.java b/pdfa/src/main/java/com/itextpdf/pdfa/checker/PdfA1Checker.java
index 94e5bdc898..2603f7ccdb 100644
--- a/pdfa/src/main/java/com/itextpdf/pdfa/checker/PdfA1Checker.java
+++ b/pdfa/src/main/java/com/itextpdf/pdfa/checker/PdfA1Checker.java
@@ -28,7 +28,6 @@ This file is part of the iText (R) project.
import com.itextpdf.io.source.PdfTokenizer;
import com.itextpdf.io.source.RandomAccessFileOrArray;
import com.itextpdf.io.source.RandomAccessSourceFactory;
-import com.itextpdf.io.util.TextUtil;
import com.itextpdf.kernel.colors.Color;
import com.itextpdf.kernel.colors.PatternColor;
import com.itextpdf.kernel.exceptions.PdfException;
@@ -53,6 +52,7 @@ This file is part of the iText (R) project.
import com.itextpdf.kernel.pdf.colorspace.PdfDeviceCs;
import com.itextpdf.kernel.pdf.colorspace.PdfPattern;
import com.itextpdf.kernel.pdf.colorspace.PdfSpecialCs;
+import com.itextpdf.kernel.utils.checkers.FontCheckUtil;
import com.itextpdf.pdfa.exceptions.PdfAConformanceException;
import com.itextpdf.pdfa.exceptions.PdfaExceptionMessageConstant;
import com.itextpdf.pdfa.logs.PdfAConformanceLogMessageConstant;
@@ -365,19 +365,10 @@ public void checkSignatureType(boolean isCAdES) {
*/
@Override
public void checkText(String text, PdfFont font) {
- for (int i = 0; i < text.length(); ++i) {
- int ch;
- if (TextUtil.isSurrogatePair(text, i)) {
- ch = TextUtil.convertToUtf32(text, i);
- i++;
- } else {
- ch = text.charAt(i);
- }
-
- if (!font.containsGlyph(ch)) {
- throw new PdfAConformanceException(
- PdfaExceptionMessageConstant.EMBEDDED_FONTS_SHALL_DEFINE_ALL_REFERENCED_GLYPHS);
- }
+ int index = FontCheckUtil.checkGlyphsOfText(text, font, new ACharacterChecker());
+ if (index != -1) {
+ throw new PdfAConformanceException(
+ PdfaExceptionMessageConstant.EMBEDDED_FONTS_SHALL_DEFINE_ALL_REFERENCED_GLYPHS);
}
}
@@ -832,4 +823,11 @@ private int getMaxArrayCapacity() {
private int getMaxDictionaryCapacity() {
return 4095;
}
+
+ private static final class ACharacterChecker implements FontCheckUtil.CharacterChecker {
+ @Override
+ public boolean check(int ch, PdfFont font) {
+ return !font.containsGlyph(ch);
+ }
+ }
}
diff --git a/pdfa/src/test/java/com/itextpdf/pdfa/PdfA1bCheckfieldAppearanceTest.java b/pdfa/src/test/java/com/itextpdf/pdfa/PdfA1bCheckfieldAppearanceTest.java
index 0001e40d2b..1248cfe387 100644
--- a/pdfa/src/test/java/com/itextpdf/pdfa/PdfA1bCheckfieldAppearanceTest.java
+++ b/pdfa/src/test/java/com/itextpdf/pdfa/PdfA1bCheckfieldAppearanceTest.java
@@ -69,7 +69,7 @@ public void pdfA1bCheckFieldOffAppearanceTest() throws IOException, InterruptedE
doc.addNewPage();
PdfAcroForm form = PdfFormCreator.getAcroForm(doc, true);
PdfFormField chk = new CheckBoxFormFieldBuilder(doc, "name").setWidgetRectangle(new Rectangle(100, 500, 50, 50))
- .setCheckType(CheckBoxType.CHECK).setConformanceLevel(PdfAConformanceLevel.PDF_A_1B)
+ .setCheckType(CheckBoxType.CHECK).setGenericConformanceLevel(PdfAConformanceLevel.PDF_A_1B)
.createCheckBox().setValue("Off");
chk.getFirstFormAnnotation().setBorderColor(ColorConstants.BLACK);
chk.getFirstFormAnnotation().setBorderWidth(1);
@@ -92,7 +92,7 @@ public void pdfA1bCheckFieldOnAppearanceTest() throws IOException, InterruptedEx
doc.addNewPage();
PdfAcroForm form = PdfFormCreator.getAcroForm(doc, true);
PdfFormField chk = new CheckBoxFormFieldBuilder(doc, "name").setWidgetRectangle(new Rectangle(100, 500, 50, 50))
- .setCheckType(CheckBoxType.CHECK).setConformanceLevel(PdfAConformanceLevel.PDF_A_1B)
+ .setCheckType(CheckBoxType.CHECK).setGenericConformanceLevel(PdfAConformanceLevel.PDF_A_1B)
.createCheckBox().setValue("On");
chk.getFirstFormAnnotation().setBorderColor(ColorConstants.BLACK);
chk.getFirstFormAnnotation().setBorderWidth(1);
diff --git a/pdfa/src/test/java/com/itextpdf/pdfa/PdfACheckfieldTest.java b/pdfa/src/test/java/com/itextpdf/pdfa/PdfACheckfieldTest.java
index 77ebf36cb2..9be9904f7b 100644
--- a/pdfa/src/test/java/com/itextpdf/pdfa/PdfACheckfieldTest.java
+++ b/pdfa/src/test/java/com/itextpdf/pdfa/PdfACheckfieldTest.java
@@ -72,7 +72,7 @@ public void pdfA1aCheckFieldOffAppearanceTest() throws IOException, InterruptedE
doc.addNewPage();
PdfAcroForm form = PdfFormCreator.getAcroForm(doc, true);
PdfFormField chk = new CheckBoxFormFieldBuilder(doc, "name").setWidgetRectangle(new Rectangle(100, 500, 50, 50))
- .setCheckType(CheckBoxType.CHECK).setConformanceLevel(PdfAConformanceLevel.PDF_A_1A)
+ .setCheckType(CheckBoxType.CHECK).setGenericConformanceLevel(PdfAConformanceLevel.PDF_A_1A)
.createCheckBox().setValue("Off");
chk.getFirstFormAnnotation().setBorderColor(ColorConstants.BLACK);
chk.getFirstFormAnnotation().setBorderWidth(1);
@@ -97,7 +97,7 @@ public void pdfA1aCheckFieldOnAppearanceTest() throws IOException, InterruptedEx
doc.addNewPage();
PdfAcroForm form = PdfFormCreator.getAcroForm(doc, true);
PdfFormField chk = new CheckBoxFormFieldBuilder(doc, "name").setWidgetRectangle(new Rectangle(100, 500, 50, 50))
- .setCheckType(CheckBoxType.CHECK).setConformanceLevel(PdfAConformanceLevel.PDF_A_1A)
+ .setCheckType(CheckBoxType.CHECK).setGenericConformanceLevel(PdfAConformanceLevel.PDF_A_1A)
.createCheckBox().setValue("On");
chk.getFirstFormAnnotation().setBorderColor(ColorConstants.BLACK);
chk.getFirstFormAnnotation().setBorderWidth(1);
@@ -120,7 +120,7 @@ public void pdfA1bCheckFieldOffAppearanceTest() throws IOException, InterruptedE
doc.addNewPage();
PdfAcroForm form = PdfFormCreator.getAcroForm(doc, true);
PdfFormField chk = new CheckBoxFormFieldBuilder(doc, "name").setWidgetRectangle(new Rectangle(100, 500, 50, 50))
- .setCheckType(CheckBoxType.CHECK).setConformanceLevel(PdfAConformanceLevel.PDF_A_1B)
+ .setCheckType(CheckBoxType.CHECK).setGenericConformanceLevel(PdfAConformanceLevel.PDF_A_1B)
.createCheckBox().setValue("Off");
chk.getFirstFormAnnotation().setBorderColor(ColorConstants.BLACK);
chk.getFirstFormAnnotation().setBorderWidth(1);
@@ -143,7 +143,7 @@ public void pdfA1bCheckFieldOnAppearanceTest() throws IOException, InterruptedEx
doc.addNewPage();
PdfAcroForm form = PdfFormCreator.getAcroForm(doc, true);
PdfFormField chk = new CheckBoxFormFieldBuilder(doc, "name").setWidgetRectangle(new Rectangle(100, 500, 50, 50))
- .setCheckType(CheckBoxType.CHECK).setConformanceLevel(PdfAConformanceLevel.PDF_A_1B)
+ .setCheckType(CheckBoxType.CHECK).setGenericConformanceLevel(PdfAConformanceLevel.PDF_A_1B)
.createCheckBox().setValue("On");
chk.getFirstFormAnnotation().setBorderColor(ColorConstants.BLACK);
chk.getFirstFormAnnotation().setBorderWidth(1);
@@ -168,7 +168,7 @@ public void pdfA2aCheckFieldOffAppearanceTest() throws IOException, InterruptedE
doc.addNewPage();
PdfAcroForm form = PdfFormCreator.getAcroForm(doc, true);
PdfFormField chk = new CheckBoxFormFieldBuilder(doc, "name").setWidgetRectangle(new Rectangle(100, 500, 50, 50))
- .setCheckType(CheckBoxType.CHECK).setConformanceLevel(PdfAConformanceLevel.PDF_A_2A)
+ .setCheckType(CheckBoxType.CHECK).setGenericConformanceLevel(PdfAConformanceLevel.PDF_A_2A)
.createCheckBox().setValue("Off");
chk.getFirstFormAnnotation().setBorderColor(ColorConstants.BLACK);
chk.getFirstFormAnnotation().setBorderWidth(1);
@@ -193,7 +193,7 @@ public void pdfA2aCheckFieldOnAppearanceTest() throws IOException, InterruptedEx
doc.addNewPage();
PdfAcroForm form = PdfFormCreator.getAcroForm(doc, true);
PdfFormField chk = new CheckBoxFormFieldBuilder(doc, "name").setWidgetRectangle(new Rectangle(100, 500, 50, 50))
- .setCheckType(CheckBoxType.CHECK).setConformanceLevel(PdfAConformanceLevel.PDF_A_2A)
+ .setCheckType(CheckBoxType.CHECK).setGenericConformanceLevel(PdfAConformanceLevel.PDF_A_2A)
.createCheckBox().setValue("On");
chk.getFirstFormAnnotation().setBorderColor(ColorConstants.BLACK);
chk.getFirstFormAnnotation().setBorderWidth(1);
@@ -216,7 +216,7 @@ public void pdfA2bCheckFieldOffAppearanceTest() throws IOException, InterruptedE
doc.addNewPage();
PdfAcroForm form = PdfFormCreator.getAcroForm(doc, true);
PdfFormField chk = new CheckBoxFormFieldBuilder(doc, "name").setWidgetRectangle(new Rectangle(100, 500, 50, 50))
- .setCheckType(CheckBoxType.CHECK).setConformanceLevel(PdfAConformanceLevel.PDF_A_2B)
+ .setCheckType(CheckBoxType.CHECK).setGenericConformanceLevel(PdfAConformanceLevel.PDF_A_2B)
.createCheckBox().setValue("Off");
chk.getFirstFormAnnotation().setBorderColor(ColorConstants.BLACK);
chk.getFirstFormAnnotation().setBorderWidth(1);
@@ -239,7 +239,7 @@ public void pdfA2bCheckFieldOnAppearanceTest() throws IOException, InterruptedEx
doc.addNewPage();
PdfAcroForm form = PdfFormCreator.getAcroForm(doc, true);
PdfFormField chk = new CheckBoxFormFieldBuilder(doc, "name").setWidgetRectangle(new Rectangle(100, 500, 50, 50))
- .setCheckType(CheckBoxType.CHECK).setConformanceLevel(PdfAConformanceLevel.PDF_A_2B)
+ .setCheckType(CheckBoxType.CHECK).setGenericConformanceLevel(PdfAConformanceLevel.PDF_A_2B)
.createCheckBox().setValue("On");
chk.getFirstFormAnnotation().setBorderColor(ColorConstants.BLACK);
chk.getFirstFormAnnotation().setBorderWidth(1);
@@ -262,7 +262,7 @@ public void pdfA2uCheckFieldOffAppearanceTest() throws IOException, InterruptedE
doc.addNewPage();
PdfAcroForm form = PdfFormCreator.getAcroForm(doc, true);
PdfFormField chk = new CheckBoxFormFieldBuilder(doc, "name").setWidgetRectangle(new Rectangle(100, 500, 50, 50))
- .setCheckType(CheckBoxType.CHECK).setConformanceLevel(PdfAConformanceLevel.PDF_A_2U)
+ .setCheckType(CheckBoxType.CHECK).setGenericConformanceLevel(PdfAConformanceLevel.PDF_A_2U)
.createCheckBox().setValue("Off");
chk.getFirstFormAnnotation().setBorderColor(ColorConstants.BLACK);
chk.getFirstFormAnnotation().setBorderWidth(1);
@@ -285,7 +285,7 @@ public void pdfA2uCheckFieldOnAppearanceTest() throws IOException, InterruptedEx
doc.addNewPage();
PdfAcroForm form = PdfFormCreator.getAcroForm(doc, true);
PdfFormField chk = new CheckBoxFormFieldBuilder(doc, "name").setWidgetRectangle(new Rectangle(100, 500, 50, 50))
- .setCheckType(CheckBoxType.CHECK).setConformanceLevel(PdfAConformanceLevel.PDF_A_2U)
+ .setCheckType(CheckBoxType.CHECK).setGenericConformanceLevel(PdfAConformanceLevel.PDF_A_2U)
.createCheckBox().setValue("On");
chk.getFirstFormAnnotation().setBorderColor(ColorConstants.BLACK);
chk.getFirstFormAnnotation().setBorderWidth(1);
@@ -310,7 +310,7 @@ public void pdfA3aCheckFieldOffAppearanceTest() throws IOException, InterruptedE
doc.addNewPage();
PdfAcroForm form = PdfFormCreator.getAcroForm(doc, true);
PdfFormField chk = new CheckBoxFormFieldBuilder(doc, "name").setWidgetRectangle(new Rectangle(100, 500, 50, 50))
- .setCheckType(CheckBoxType.CHECK).setConformanceLevel(PdfAConformanceLevel.PDF_A_3A)
+ .setCheckType(CheckBoxType.CHECK).setGenericConformanceLevel(PdfAConformanceLevel.PDF_A_3A)
.createCheckBox().setValue("Off");
chk.getFirstFormAnnotation().setBorderColor(ColorConstants.BLACK);
chk.getFirstFormAnnotation().setBorderWidth(1);
@@ -335,7 +335,7 @@ public void pdfA3aCheckFieldOnAppearanceTest() throws IOException, InterruptedEx
doc.addNewPage();
PdfAcroForm form = PdfFormCreator.getAcroForm(doc, true);
PdfFormField chk = new CheckBoxFormFieldBuilder(doc, "name").setWidgetRectangle(new Rectangle(100, 500, 50, 50))
- .setCheckType(CheckBoxType.CHECK).setConformanceLevel(PdfAConformanceLevel.PDF_A_3A)
+ .setCheckType(CheckBoxType.CHECK).setGenericConformanceLevel(PdfAConformanceLevel.PDF_A_3A)
.createCheckBox().setValue("On");
chk.getFirstFormAnnotation().setBorderColor(ColorConstants.BLACK);
chk.getFirstFormAnnotation().setBorderWidth(1);
@@ -358,7 +358,7 @@ public void pdfA3bCheckFieldOffAppearanceTest() throws IOException, InterruptedE
doc.addNewPage();
PdfAcroForm form = PdfFormCreator.getAcroForm(doc, true);
PdfFormField chk = new CheckBoxFormFieldBuilder(doc, "name").setWidgetRectangle(new Rectangle(100, 500, 50, 50))
- .setCheckType(CheckBoxType.CHECK).setConformanceLevel(PdfAConformanceLevel.PDF_A_3B)
+ .setCheckType(CheckBoxType.CHECK).setGenericConformanceLevel(PdfAConformanceLevel.PDF_A_3B)
.createCheckBox().setValue("Off");
chk.getFirstFormAnnotation().setBorderColor(ColorConstants.BLACK);
chk.getFirstFormAnnotation().setBorderWidth(1);
@@ -381,7 +381,7 @@ public void pdfA3bCheckFieldOnAppearanceTest() throws IOException, InterruptedEx
doc.addNewPage();
PdfAcroForm form = PdfFormCreator.getAcroForm(doc, true);
PdfFormField chk = new CheckBoxFormFieldBuilder(doc, "name").setWidgetRectangle(new Rectangle(100, 500, 50, 50))
- .setCheckType(CheckBoxType.CHECK).setConformanceLevel(PdfAConformanceLevel.PDF_A_3B)
+ .setCheckType(CheckBoxType.CHECK).setGenericConformanceLevel(PdfAConformanceLevel.PDF_A_3B)
.createCheckBox().setValue("On");
chk.getFirstFormAnnotation().setBorderColor(ColorConstants.BLACK);
chk.getFirstFormAnnotation().setBorderWidth(1);
@@ -404,7 +404,7 @@ public void pdfA3uCheckFieldOffAppearanceTest() throws IOException, InterruptedE
doc.addNewPage();
PdfAcroForm form = PdfFormCreator.getAcroForm(doc, true);
PdfFormField chk = new CheckBoxFormFieldBuilder(doc, "name").setWidgetRectangle(new Rectangle(100, 500, 50, 50))
- .setCheckType(CheckBoxType.CHECK).setConformanceLevel(PdfAConformanceLevel.PDF_A_3U)
+ .setCheckType(CheckBoxType.CHECK).setGenericConformanceLevel(PdfAConformanceLevel.PDF_A_3U)
.createCheckBox().setValue("Off");
chk.getFirstFormAnnotation().setBorderColor(ColorConstants.BLACK);
chk.getFirstFormAnnotation().setBorderWidth(1);
@@ -427,7 +427,7 @@ public void pdfA3uCheckFieldOnAppearanceTest() throws IOException, InterruptedEx
doc.addNewPage();
PdfAcroForm form = PdfFormCreator.getAcroForm(doc, true);
PdfFormField chk = new CheckBoxFormFieldBuilder(doc, "name").setWidgetRectangle(new Rectangle(100, 500, 50, 50))
- .setCheckType(CheckBoxType.CHECK).setConformanceLevel(PdfAConformanceLevel.PDF_A_3U)
+ .setCheckType(CheckBoxType.CHECK).setGenericConformanceLevel(PdfAConformanceLevel.PDF_A_3U)
.createCheckBox().setValue("On");
chk.getFirstFormAnnotation().setBorderColor(ColorConstants.BLACK);
chk.getFirstFormAnnotation().setBorderWidth(1);
diff --git a/pdfa/src/test/java/com/itextpdf/pdfa/PdfAFormFieldTest.java b/pdfa/src/test/java/com/itextpdf/pdfa/PdfAFormFieldTest.java
index 0eeac140a8..290a2a2570 100644
--- a/pdfa/src/test/java/com/itextpdf/pdfa/PdfAFormFieldTest.java
+++ b/pdfa/src/test/java/com/itextpdf/pdfa/PdfAFormFieldTest.java
@@ -128,7 +128,7 @@ public void pdfAButtonFieldTest() throws Exception {
SOURCE_FOLDER + "FreeSans.ttf", EmbeddingStrategy.PREFER_EMBEDDED);
PdfButtonFormField group = new RadioFormFieldBuilder(pdf, "group")
- .setConformanceLevel(PdfAConformanceLevel.PDF_A_1B).createRadioGroup();
+ .setGenericConformanceLevel(PdfAConformanceLevel.PDF_A_1B).createRadioGroup();
group.setValue("");
group.setReadOnly(true);
@@ -184,9 +184,9 @@ public void pdfA1DocWithPdfA1ButtonFieldTest() throws IOException, InterruptedEx
PdfAcroForm form = PdfFormCreator.getAcroForm(pdfDoc, true);
PdfFormField emptyField = new NonTerminalFormFieldBuilder(pdfDoc, "empty")
- .setConformanceLevel(conformanceLevel).createNonTerminalFormField();
+ .setGenericConformanceLevel(conformanceLevel).createNonTerminalFormField();
emptyField.addKid(new PushButtonFormFieldBuilder(pdfDoc, "button")
- .setWidgetRectangle(new Rectangle(36, 756, 20, 20)).setConformanceLevel(conformanceLevel)
+ .setWidgetRectangle(new Rectangle(36, 756, 20, 20)).setGenericConformanceLevel(conformanceLevel)
.createPushButton().setFieldFlags(PdfAnnotation.PRINT)
.setFieldName("button").setValue("hello"));
form.addField(emptyField);
@@ -211,7 +211,7 @@ public void pdfA1DocWithPdfA1CheckBoxFieldTest() throws IOException, Interrupted
PdfAcroForm form = PdfFormCreator.getAcroForm(pdfDoc, true);
form.addField(new CheckBoxFormFieldBuilder(pdfDoc, "checkBox").setWidgetRectangle(new Rectangle(36, 726, 20, 20))
- .setCheckType(CheckBoxType.STAR).setConformanceLevel(conformanceLevel)
+ .setCheckType(CheckBoxType.STAR).setGenericConformanceLevel(conformanceLevel)
.createCheckBox().setValue("1"));
pdfDoc.close();
@@ -238,7 +238,7 @@ public void pdfA1DocWithPdfA1ChoiceFieldTest() throws IOException, InterruptedEx
options.add(new PdfString("Name"));
options.add(new PdfString("Surname"));
PdfFormField choiceFormField = new ChoiceFormFieldBuilder(pdfDoc, "choice").setWidgetRectangle(new Rectangle(36, 696, 100, 70))
- .setOptions(options).setConformanceLevel(conformanceLevel)
+ .setOptions(options).setGenericConformanceLevel(conformanceLevel)
.createList().setValue("1", true);
choiceFormField.setFont(fontFreeSans);
form.addField(choiceFormField);
@@ -268,7 +268,7 @@ public void pdfA1DocWithPdfA1ComboBoxFieldTest() throws IOException, Interrupted
PdfAcroForm form = PdfFormCreator.getAcroForm(pdfDoc, true);
PdfFormField choiceFormField = new ChoiceFormFieldBuilder(pdfDoc, "combo")
.setWidgetRectangle(new Rectangle(156, 616, 70, 70)).setOptions(new String[]{"用", "规", "表"})
- .setConformanceLevel(conformanceLevel).createComboBox()
+ .setGenericConformanceLevel(conformanceLevel).createComboBox()
.setValue("用");
choiceFormField.setFont(fontCJK);
form.addField(choiceFormField);
@@ -298,7 +298,7 @@ public void pdfA1DocWithPdfA1ListFieldTest() throws IOException, InterruptedExce
PdfChoiceFormField f = new ChoiceFormFieldBuilder(pdfDoc, "list")
.setWidgetRectangle(new Rectangle(86, 556, 50, 200)).setOptions(new String[]{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"})
- .setConformanceLevel(conformanceLevel).createList();
+ .setGenericConformanceLevel(conformanceLevel).createList();
f.setValue("9").setFont(fontFreeSans);
f.setValue("4");
f.setTopIndex(2);
@@ -328,7 +328,7 @@ public void pdfA1DocWithPdfA1PushButtonFieldTest() throws IOException, Interrupt
PdfAcroForm form = PdfFormCreator.getAcroForm(pdfDoc, true);
PdfFormField pushButtonFormField = new PushButtonFormFieldBuilder(pdfDoc, "push button").setWidgetRectangle(new Rectangle(36, 526, 100, 20))
- .setCaption("Push").setConformanceLevel(conformanceLevel)
+ .setCaption("Push").setGenericConformanceLevel(conformanceLevel)
.createPushButton();
pushButtonFormField.setFont(fontFreeSans).setFontSize(12);
form.addField(pushButtonFormField);
@@ -354,8 +354,8 @@ public void pdfA1DocWithPdfA1RadioButtonFieldTest() throws IOException, Interrup
PdfAcroForm form = PdfFormCreator.getAcroForm(pdfDoc, true);
String pdfFormFieldName = "radio group";
- RadioFormFieldBuilder builder = new RadioFormFieldBuilder(pdfDoc, pdfFormFieldName).setConformanceLevel(conformanceLevel);
- PdfButtonFormField radioGroup = builder.setConformanceLevel(conformanceLevel)
+ RadioFormFieldBuilder builder = new RadioFormFieldBuilder(pdfDoc, pdfFormFieldName).setGenericConformanceLevel(conformanceLevel);
+ PdfButtonFormField radioGroup = builder.setGenericConformanceLevel(conformanceLevel)
.createRadioGroup();
radioGroup.setValue("");
PdfFormAnnotation radio1 = builder
@@ -395,7 +395,7 @@ public void pdfA1DocWithPdfA1TextFieldTest() throws IOException, InterruptedExce
PdfAcroForm form = PdfFormCreator.getAcroForm(pdfDoc, true);
PdfFormField textFormField = new TextFormFieldBuilder(pdfDoc, "text").setWidgetRectangle(new Rectangle(36, 466, 90, 20))
- .setConformanceLevel(conformanceLevel).createText().setValue("textField").setValue("iText");
+ .setGenericConformanceLevel(conformanceLevel).createText().setValue("textField").setValue("iText");
textFormField.setFont(fontFreeSans).setFontSize(12);
form.addField(textFormField);
pdfDoc.close();
@@ -422,7 +422,7 @@ public void pdfA1DocWithPdfA1SignatureFieldTest() throws IOException, Interrupte
PdfAcroForm form = PdfFormCreator.getAcroForm(pdfDoc, true);
PdfFormField signFormField = new SignatureFormFieldBuilder(pdfDoc, "signature")
- .setConformanceLevel(conformanceLevel).createSignature();
+ .setGenericConformanceLevel(conformanceLevel).createSignature();
signFormField.setFont(fontFreeSans).setFontSize(20);
form.addField(signFormField);
@@ -449,7 +449,7 @@ public void mergePdfADocWithFormTest() throws IOException, InterruptedException
PdfAcroForm form = PdfFormCreator.getAcroForm(pdfDoc, true);
PdfFormField field = new TextFormFieldBuilder(pdfDoc, "text").setWidgetRectangle(new Rectangle(150, 100, 100, 20))
- .setConformanceLevel(PdfAConformanceLevel.PDF_A_1B).createText()
+ .setGenericConformanceLevel(PdfAConformanceLevel.PDF_A_1B).createText()
.setValue("textField").setFieldName("text");
field.setFont(font).setFontSize(10);
field.getFirstFormAnnotation().setPage(1);
@@ -696,7 +696,7 @@ public void pdfASignatureFieldWithTextAndFontTest() throws IOException, Interrup
PdfSignatureFormField signatureFormField = signatureFormFieldBuilder.setWidgetRectangle(
new Rectangle(200, 200, 40, 40))
.setFont(fontFreeSans)
- .setConformanceLevel(PdfAConformanceLevel.PDF_A_4)
+ .setGenericConformanceLevel(PdfAConformanceLevel.PDF_A_4)
.createSignature();
signatureFormField.getFirstFormAnnotation().setFormFieldElement(signatureFieldAppearance2);
form.addField(signatureFormField);
@@ -814,7 +814,7 @@ public void draw(DrawContext context) {
PdfDocument pdf = context.getDocument();
PdfAcroForm form = PdfFormCreator.getAcroForm(pdf, true);
PdfFormAnnotation chk = new RadioFormFieldBuilder(pdf, "")
- .setConformanceLevel(PdfAConformanceLevel.PDF_A_1B).createRadioButton(_value, bbox);
+ .setGenericConformanceLevel(PdfAConformanceLevel.PDF_A_1B).createRadioButton(_value, bbox);
_group.addKid(chk);
chk.setPage(pageNumber);
diff --git a/pdfa/src/test/java/com/itextpdf/pdfa/PdfAPushbuttonfieldTest.java b/pdfa/src/test/java/com/itextpdf/pdfa/PdfAPushbuttonfieldTest.java
index 3bc2f7c7df..b5289735a3 100644
--- a/pdfa/src/test/java/com/itextpdf/pdfa/PdfAPushbuttonfieldTest.java
+++ b/pdfa/src/test/java/com/itextpdf/pdfa/PdfAPushbuttonfieldTest.java
@@ -79,7 +79,7 @@ public void pdfA1bButtonAppearanceTest() throws IOException, InterruptedExceptio
PdfFont font = PdfFontFactory.createFont(sourceFolder + "FreeSans.ttf",
"WinAnsi", EmbeddingStrategy.FORCE_EMBEDDED);
PdfFormField button = new PushButtonFormFieldBuilder(doc, "push button").setWidgetRectangle(rect)
- .setCaption("push").setConformanceLevel(PdfAConformanceLevel.PDF_A_1B)
+ .setCaption("push").setGenericConformanceLevel(PdfAConformanceLevel.PDF_A_1B)
.createPushButton();
button.setFont(font).setFontSize(12);
form.addField(button);
@@ -110,7 +110,7 @@ public void pdfA1bButtonAppearanceRegenerateTest() throws IOException, Interrupt
PdfFont font = PdfFontFactory.createFont(sourceFolder + "FreeSans.ttf",
"WinAnsi", EmbeddingStrategy.FORCE_EMBEDDED);
PdfFormField button = new PushButtonFormFieldBuilder(doc, "push button").setWidgetRectangle(rect)
- .setCaption("push").setConformanceLevel(PdfAConformanceLevel.PDF_A_1B)
+ .setCaption("push").setGenericConformanceLevel(PdfAConformanceLevel.PDF_A_1B)
.createPushButton();
button.setFont(font).setFontSize(12);
button.regenerateField();
@@ -141,7 +141,7 @@ public void pdfA1bButtonAppearanceSetValueTest() throws IOException, Interrupted
PdfFont font = PdfFontFactory.createFont(sourceFolder + "FreeSans.ttf",
"WinAnsi", EmbeddingStrategy.FORCE_EMBEDDED);
PdfFormField button = new PushButtonFormFieldBuilder(doc, "push button").setWidgetRectangle(rect)
- .setCaption("push").setConformanceLevel(PdfAConformanceLevel.PDF_A_1B)
+ .setCaption("push").setGenericConformanceLevel(PdfAConformanceLevel.PDF_A_1B)
.createPushButton();
button.setFont(font).setFontSize(12);
button.setValue("button");
diff --git a/pdfa/src/test/java/com/itextpdf/pdfa/PdfAXmpTest.java b/pdfa/src/test/java/com/itextpdf/pdfa/PdfAXmpTest.java
index 9d940217d9..a3f3ff61aa 100644
--- a/pdfa/src/test/java/com/itextpdf/pdfa/PdfAXmpTest.java
+++ b/pdfa/src/test/java/com/itextpdf/pdfa/PdfAXmpTest.java
@@ -22,11 +22,15 @@ This file is part of the iText (R) project.
*/
package com.itextpdf.pdfa;
+import com.itextpdf.io.source.ByteArrayOutputStream;
import com.itextpdf.kernel.pdf.PdfAConformanceLevel;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfOutputIntent;
import com.itextpdf.kernel.pdf.PdfReader;
+import com.itextpdf.kernel.pdf.PdfString;
+import com.itextpdf.kernel.pdf.PdfViewerPreferences;
import com.itextpdf.kernel.pdf.PdfWriter;
+import com.itextpdf.kernel.pdf.WriterProperties;
import com.itextpdf.kernel.utils.CompareTool;
import com.itextpdf.kernel.xmp.XMPConst;
import com.itextpdf.kernel.xmp.XMPException;
@@ -36,9 +40,15 @@ This file is part of the iText (R) project.
import com.itextpdf.kernel.xmp.options.SerializeOptions;
import com.itextpdf.test.ExtendedITextTest;
import com.itextpdf.test.annotations.type.IntegrationTest;
+
+import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -127,6 +137,57 @@ public void saveAndReadDocumentWithCanonicalXmpMetadata() throws IOException, XM
}
}
+ @Test
+ public void testPdfUAExtensionMetadata() throws IOException {
+
+ String outFile = destinationFolder + "testPdfUAExtensionMetadata.pdf";
+ String cmpFile = cmpFolder + "cmp_testPdfUAExtensionMetadata.pdf";
+
+ try (FileOutputStream fos = new FileOutputStream(outFile)) {
+ generatePdfAWithUA(fos);
+ }
+
+ CompareTool ct = new CompareTool();
+ Assert.assertNull(ct.compareXmp(outFile, cmpFile, true));
+
+ }
+
+ @Test
+ public void testPdfUAIdSchemaNameSpaceUriIsNotText() throws IOException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ generatePdfAWithUA(baos);
+
+ // check whether the pdfuaid NS URI was properly encoded as a URI with rdf:resource
+ PdfDocument readDoc = new PdfDocument(new PdfReader(new ByteArrayInputStream(baos.toByteArray())));
+ String xmpString = new String(readDoc.getXmpMetadata(), StandardCharsets.UTF_8);
+ Assert.assertTrue(
+ "Did not find expected namespaceURI definition",
+ xmpString.contains("")
+ );
+
+ }
+
+ private void generatePdfAWithUA(OutputStream os) throws IOException {
+ WriterProperties wp = new WriterProperties().addUAXmpMetadata();
+ try (PdfWriter w = new PdfWriter(os, wp)) {
+ PdfOutputIntent outputIntent;
+ try (InputStream is = new FileInputStream(sourceFolder + "sRGB Color Space Profile.icm")) {
+ outputIntent = new PdfOutputIntent(
+ "Custom", "",
+ "http://www.color.org",
+ "sRGB IEC61966-2.1",
+ is
+ );
+ }
+ PdfDocument pdfDoc = new PdfADocument(w, PdfAConformanceLevel.PDF_A_2A, outputIntent).setTagged();
+ pdfDoc.getDocumentInfo().setTitle("Test document");
+ pdfDoc.getCatalog().setViewerPreferences(new PdfViewerPreferences().setDisplayDocTitle(true));
+ pdfDoc.getCatalog().setLang(new PdfString("en"));
+ pdfDoc.addNewPage();
+ pdfDoc.close();
+ }
+ }
+
private int count(byte[] array, byte b) {
int counter = 0;
for (byte each : array) {
diff --git a/pdfa/src/test/resources/com/itextpdf/pdfa/cmp/PdfAFormFieldTest/cmp_pdfASignatureFieldTestWithText.pdf b/pdfa/src/test/resources/com/itextpdf/pdfa/cmp/PdfAFormFieldTest/cmp_pdfASignatureFieldTestWithText.pdf
index 9fe7cc532e..e5814f7dd5 100644
Binary files a/pdfa/src/test/resources/com/itextpdf/pdfa/cmp/PdfAFormFieldTest/cmp_pdfASignatureFieldTestWithText.pdf and b/pdfa/src/test/resources/com/itextpdf/pdfa/cmp/PdfAFormFieldTest/cmp_pdfASignatureFieldTestWithText.pdf differ
diff --git a/pdfa/src/test/resources/com/itextpdf/pdfa/cmp/PdfAXmpTest/cmp_testPdfUAExtensionMetadata.pdf b/pdfa/src/test/resources/com/itextpdf/pdfa/cmp/PdfAXmpTest/cmp_testPdfUAExtensionMetadata.pdf
new file mode 100644
index 0000000000..16d382cb6e
Binary files /dev/null and b/pdfa/src/test/resources/com/itextpdf/pdfa/cmp/PdfAXmpTest/cmp_testPdfUAExtensionMetadata.pdf differ
diff --git a/pdftest/pom.xml b/pdftest/pom.xml
index 603cffdf07..ac73f8eac5 100644
--- a/pdftest/pom.xml
+++ b/pdftest/pom.xml
@@ -1,28 +1,33 @@
4.0.0
+
com.itextpdfroot
- 8.0.3
+ 8.0.4
+
pdftest
+
iText - pdftesthttps://itextpdf.com/
+
true1.25.125
-
-
-
- true
-
- vera-dev
- Vera development
- https://artifactory.openpreservation.org/artifactory/vera-dev
-
-
+
+
+
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ 2.16.0
+
+
+ org.verapdf
@@ -40,6 +45,18 @@
${junit.version}
+
+
+
+
+ true
+
+ vera-dev
+ Vera development
+ https://artifactory.openpreservation.org/artifactory/vera-dev
+
+
+
diff --git a/pdftest/src/test/java/com/itextpdf/test/VeraPdfLoggerValidationTest.java b/pdftest/src/test/java/com/itextpdf/test/VeraPdfLoggerValidationTest.java
index 283beae427..5e5bc9b8d0 100644
--- a/pdftest/src/test/java/com/itextpdf/test/VeraPdfLoggerValidationTest.java
+++ b/pdftest/src/test/java/com/itextpdf/test/VeraPdfLoggerValidationTest.java
@@ -45,10 +45,29 @@ public static void beforeClass() {
}
@Test
- public void checkValidatorLogsTest() throws IOException {
+ public void checkValidatorLogsNoOutputTest() throws IOException {
+ String source = "pdfA2b_checkValidatorLogsTest.pdf";
+ String target = "checkValidatorLogsNoOutputTest.pdf";
+ FileUtil.copy(SOURCE_FOLDER + source, DESTINATION_FOLDER + target);
+ Assert.assertNull(new VeraPdfValidator().validate(DESTINATION_FOLDER + target)); // Android-Conversion-Skip-Line (TODO DEVSIX-7377 introduce pdf\a validation on Android)
+ }
- String fileNameWithWarnings = "cmp_pdfA2b_checkValidatorLogsTest_with_warnings.pdf";
- String fileNameWithoutWarnings = "cmp_pdfA2b_checkValidatorLogsTest.pdf";
+ @Test
+ public void checkValidatorLogsWithWarningTest() throws IOException {
+ String source = "pdfA2b_checkValidatorLogsTest_with_warnings.pdf";
+ String target = "checkValidatorLogsWitWarningTest.pdf";
+ FileUtil.copy(SOURCE_FOLDER + source, DESTINATION_FOLDER + target);
+ String expectedWarningsForFileWithWarnings = "The following warnings and errors were logged during validation:\n"
+ + "WARNING: Invalid embedded cff font. Charset range exceeds number of glyphs\n"
+ + "WARNING: Missing OutputConditionIdentifier in an output intent dictionary\n"
+ + "WARNING: The Top DICT does not begin with ROS operator";
+ Assert.assertEquals(expectedWarningsForFileWithWarnings, new VeraPdfValidator().validate(DESTINATION_FOLDER + target)); // Android-Conversion-Skip-Line (TODO DEVSIX-7377 introduce pdf\a validation on Android)
+ }
+
+ @Test
+ public void checkValidatorLogsCleanupTest() throws IOException {
+ String fileNameWithWarnings = "pdfA2b_checkValidatorLogsTest_with_warnings.pdf";
+ String fileNameWithoutWarnings = "pdfA2b_checkValidatorLogsTest.pdf";
FileUtil.copy(SOURCE_FOLDER + fileNameWithWarnings, DESTINATION_FOLDER + fileNameWithWarnings);
FileUtil.copy(SOURCE_FOLDER + fileNameWithoutWarnings, DESTINATION_FOLDER + fileNameWithoutWarnings);
@@ -61,4 +80,15 @@ public void checkValidatorLogsTest() throws IOException {
//We check that the logs are empty after the first check
Assert.assertNull(new VeraPdfValidator().validate(DESTINATION_FOLDER + fileNameWithoutWarnings)); // Android-Conversion-Skip-Line (TODO DEVSIX-7377 introduce pdf\a validation on Android)
}
+
+ @Test
+ public void checkValidatorLogsForFileContainingErrorsTest() throws IOException {
+ String source = "pdfA2b_checkValidatorLogsTest_with_errors.pdf";
+ String target = "checkValidatorLogsForFileContainingErrorsTest.pdf";
+ FileUtil.copy(SOURCE_FOLDER + source, DESTINATION_FOLDER + target);
+
+ String expectedResponseForErrors = "VeraPDF verification failed. See verification results: file:";
+ String result = new VeraPdfValidator().validate(DESTINATION_FOLDER + target); // Android-Conversion-Skip-Line (TODO DEVSIX-7377 introduce pdf\a validation on Android));
+ Assert.assertTrue(result.startsWith(expectedResponseForErrors)); // Android-Conversion-Skip-Line (TODO DEVSIX-7377 introduce pdf\a validation on Android)
+ }
}
diff --git a/pdftest/src/test/resources/com/itextpdf/pdftest/cmp/VeraPdfLoggerValidationTest/cmp_pdfA2b_checkValidatorLogsTest.pdf b/pdftest/src/test/resources/com/itextpdf/pdftest/cmp/VeraPdfLoggerValidationTest/pdfA2b_checkValidatorLogsTest.pdf
similarity index 100%
rename from pdftest/src/test/resources/com/itextpdf/pdftest/cmp/VeraPdfLoggerValidationTest/cmp_pdfA2b_checkValidatorLogsTest.pdf
rename to pdftest/src/test/resources/com/itextpdf/pdftest/cmp/VeraPdfLoggerValidationTest/pdfA2b_checkValidatorLogsTest.pdf
diff --git a/pdftest/src/test/resources/com/itextpdf/pdftest/cmp/VeraPdfLoggerValidationTest/pdfA2b_checkValidatorLogsTest_with_errors.pdf b/pdftest/src/test/resources/com/itextpdf/pdftest/cmp/VeraPdfLoggerValidationTest/pdfA2b_checkValidatorLogsTest_with_errors.pdf
new file mode 100644
index 0000000000..edc4e915fd
Binary files /dev/null and b/pdftest/src/test/resources/com/itextpdf/pdftest/cmp/VeraPdfLoggerValidationTest/pdfA2b_checkValidatorLogsTest_with_errors.pdf differ
diff --git a/pdftest/src/test/resources/com/itextpdf/pdftest/cmp/VeraPdfLoggerValidationTest/cmp_pdfA2b_checkValidatorLogsTest_with_warnings.pdf b/pdftest/src/test/resources/com/itextpdf/pdftest/cmp/VeraPdfLoggerValidationTest/pdfA2b_checkValidatorLogsTest_with_warnings.pdf
similarity index 100%
rename from pdftest/src/test/resources/com/itextpdf/pdftest/cmp/VeraPdfLoggerValidationTest/cmp_pdfA2b_checkValidatorLogsTest_with_warnings.pdf
rename to pdftest/src/test/resources/com/itextpdf/pdftest/cmp/VeraPdfLoggerValidationTest/pdfA2b_checkValidatorLogsTest_with_warnings.pdf
diff --git a/pdfua/pom.xml b/pdfua/pom.xml
index 60a4fbb04d..94463ace7f 100644
--- a/pdfua/pom.xml
+++ b/pdfua/pom.xml
@@ -1,12 +1,15 @@
4.0.0
+
com.itextpdfroot
- 8.0.3
+ 8.0.4
+
pdfua
+
iText - pdfuahttps://itextpdf.com/
@@ -21,12 +24,29 @@
layout${project.version}
+
+ com.itextpdf
+ forms
+ ${project.version}
+ com.itextpdfpdftest${project.version}test
+
+ com.itextpdf
+ bouncy-castle-adapter
+ ${project.version}
+ test
+
+
+ com.itextpdf
+ font-asian
+ ${project.version}
+ test
+
\ No newline at end of file
diff --git a/pdfua/src/main/java/com/itextpdf/pdfua/PdfUAConfig.java b/pdfua/src/main/java/com/itextpdf/pdfua/PdfUAConfig.java
new file mode 100644
index 0000000000..6abe7ffba4
--- /dev/null
+++ b/pdfua/src/main/java/com/itextpdf/pdfua/PdfUAConfig.java
@@ -0,0 +1,76 @@
+/*
+ This file is part of the iText (R) project.
+ Copyright (c) 1998-2024 Apryse Group NV
+ Authors: Apryse Software.
+
+ This program is offered under a commercial and under the AGPL license.
+ For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
+
+ AGPL licensing:
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+ */
+package com.itextpdf.pdfua;
+
+import com.itextpdf.kernel.pdf.PdfUAConformanceLevel;
+
+/**
+ * Class that holds the configuration for the PDF/UA document.
+ */
+public class PdfUAConfig {
+
+ private final PdfUAConformanceLevel conformanceLevel;
+ private final String title;
+ private final String language;
+
+ /**
+ * Creates a new PdfUAConfig instance.
+ *
+ * @param conformanceLevel The conformance level of the PDF/UA document.
+ * @param title The title of the PDF/UA document.
+ * @param language The language of the PDF/UA document.
+ */
+ public PdfUAConfig(PdfUAConformanceLevel conformanceLevel, String title, String language) {
+ this.conformanceLevel = conformanceLevel;
+ this.title = title;
+ this.language = language;
+ }
+
+ /**
+ * Gets the conformance level.
+ *
+ * @return The {@link PdfUAConformanceLevel}.
+ */
+ public PdfUAConformanceLevel getConformanceLevel() {
+ return conformanceLevel;
+ }
+
+ /**
+ * Gets the title.
+ *
+ * @return The title.
+ */
+ public String getTitle() {
+ return title;
+ }
+
+ /**
+ * Gets the language.
+ *
+ * @return The language.
+ */
+ public String getLanguage() {
+ return language;
+ }
+
+}
diff --git a/pdfua/src/main/java/com/itextpdf/pdfua/PdfUADocument.java b/pdfua/src/main/java/com/itextpdf/pdfua/PdfUADocument.java
new file mode 100644
index 0000000000..bb6c2f3770
--- /dev/null
+++ b/pdfua/src/main/java/com/itextpdf/pdfua/PdfUADocument.java
@@ -0,0 +1,163 @@
+/*
+ This file is part of the iText (R) project.
+ Copyright (c) 1998-2024 Apryse Group NV
+ Authors: Apryse Software.
+
+ This program is offered under a commercial and under the AGPL license.
+ For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
+
+ AGPL licensing:
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+ */
+package com.itextpdf.pdfua;
+
+import com.itextpdf.kernel.pdf.DocumentProperties;
+import com.itextpdf.kernel.pdf.IConformanceLevel;
+import com.itextpdf.kernel.pdf.IPdfPageFactory;
+import com.itextpdf.kernel.pdf.PdfDocument;
+import com.itextpdf.kernel.pdf.PdfDocumentInfo;
+import com.itextpdf.kernel.pdf.PdfReader;
+import com.itextpdf.kernel.pdf.PdfString;
+import com.itextpdf.kernel.pdf.PdfViewerPreferences;
+import com.itextpdf.kernel.pdf.PdfWriter;
+import com.itextpdf.kernel.pdf.StampingProperties;
+import com.itextpdf.kernel.utils.ValidationContainer;
+import com.itextpdf.pdfua.checkers.PdfUA1Checker;
+import com.itextpdf.pdfua.exceptions.PdfUALogMessageConstants;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Creates a Pdf/UA document.
+ * This class is an extension of PdfDocument and adds the necessary configuration for PDF/UA conformance.
+ * It will add necessary validation to guide the user to create a PDF/UA compliant document.
+ */
+public class PdfUADocument extends PdfDocument {
+
+ private static final IPdfPageFactory pdfPageFactory = new PdfUAPageFactory();
+ private static final Logger LOGGER = LoggerFactory.getLogger(PdfUADocument.class);
+ private PdfUAConfig config;
+ private boolean warnedOnPageFlush = false;
+
+ /**
+ * Creates a PdfUADocument instance.
+ *
+ * @param writer The writer to write the PDF document.
+ * @param config The configuration for the PDF/UA document.
+ */
+ public PdfUADocument(PdfWriter writer, PdfUAConfig config) {
+ this(writer, new DocumentProperties(), config);
+ }
+
+ /**
+ * Creates a PdfUADocument instance.
+ *
+ * @param writer The writer to write the PDF document.
+ * @param properties The properties for the PDF document.
+ * @param config The configuration for the PDF/UA document.
+ */
+ public PdfUADocument(PdfWriter writer, DocumentProperties properties, PdfUAConfig config) {
+ super(configureWriterProperties(writer), properties);
+ setupUAConfiguration(config);
+ }
+
+ /**
+ * Creates a PdfUADocument instance.
+ *
+ * @param reader The reader to read the PDF document.
+ * @param writer The writer to write the PDF document.
+ * @param config The configuration for the PDF/UA document.
+ */
+ public PdfUADocument(PdfReader reader, PdfWriter writer, PdfUAConfig config) {
+ super(reader, configureWriterProperties(writer));
+ setupUAConfiguration(config);
+ }
+
+ /**
+ * Creates a PdfUADocument instance.
+ *
+ * @param reader The reader to read the PDF document.
+ * @param writer The writer to write the PDF document.
+ * @param properties The properties for the PDF document.
+ * @param config The configuration for the PDF/UA document.
+ */
+ public PdfUADocument(PdfReader reader, PdfWriter writer, StampingProperties properties, PdfUAConfig config) {
+ super(reader, configureWriterProperties(writer), properties);
+ setupUAConfiguration(config);
+ }
+
+ /**
+ * {inheritDoc}
+ */
+ @Override
+ public IConformanceLevel getConformanceLevel() {
+ return config.getConformanceLevel();
+ }
+
+ /**
+ * @return The PageFactory for the PDF/UA document.
+ */
+ @Override
+ protected IPdfPageFactory getPageFactory() {
+ return pdfPageFactory;
+ }
+
+ /**
+ * Returns if the document is in the closing state.
+ *
+ * @return true if the document is closing, false otherwise.
+ */
+ boolean isClosing(){
+ return this.isClosing;
+ }
+
+ /**
+ * Warns the user that the page is being flushed.
+ * Will only warn once.
+ */
+ void warnOnPageFlush() {
+ if (!warnedOnPageFlush) {
+ LOGGER.warn(PdfUALogMessageConstants.PAGE_FLUSHING_DISABLED);
+ warnedOnPageFlush = true;
+ }
+ }
+
+ /**
+ * Disables the warning for page flushing.
+ */
+ public void disablePageFlushingWarning() {
+ warnedOnPageFlush = true;
+ }
+
+ private void setupUAConfiguration(PdfUAConfig config) {
+ //basic configuration
+ this.config = config;
+ this.setTagged();
+ this.getCatalog().setViewerPreferences(new PdfViewerPreferences().setDisplayDocTitle(true));
+ this.getCatalog().setLang(new PdfString(config.getLanguage()));
+ final PdfDocumentInfo info = this.getDocumentInfo();
+ info.setTitle(config.getTitle());
+ //validation
+ final ValidationContainer validationContainer = new ValidationContainer();
+ validationContainer.addChecker(new PdfUA1Checker(this));
+ this.getDiContainer().register(ValidationContainer.class, validationContainer);
+ }
+
+ private static PdfWriter configureWriterProperties(PdfWriter writer) {
+ writer.getProperties().addUAXmpMetadata();
+ return writer;
+ }
+
+}
diff --git a/pdfua/src/main/java/com/itextpdf/pdfua/PdfUAPage.java b/pdfua/src/main/java/com/itextpdf/pdfua/PdfUAPage.java
new file mode 100644
index 0000000000..415b46074c
--- /dev/null
+++ b/pdfua/src/main/java/com/itextpdf/pdfua/PdfUAPage.java
@@ -0,0 +1,48 @@
+/*
+ This file is part of the iText (R) project.
+ Copyright (c) 1998-2024 Apryse Group NV
+ Authors: Apryse Software.
+
+ This program is offered under a commercial and under the AGPL license.
+ For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
+
+ AGPL licensing:
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+ */
+package com.itextpdf.pdfua;
+
+import com.itextpdf.kernel.geom.PageSize;
+import com.itextpdf.kernel.pdf.PdfDictionary;
+import com.itextpdf.kernel.pdf.PdfDocument;
+import com.itextpdf.kernel.pdf.PdfPage;
+
+class PdfUAPage extends PdfPage {
+ protected PdfUAPage(PdfDictionary pdfObject) {
+ super(pdfObject);
+ }
+
+ protected PdfUAPage(PdfDocument pdfDocument, PageSize pageSize) {
+ super(pdfDocument, pageSize);
+ }
+
+ @Override
+ public void flush(boolean flushResourcesContentStreams) {
+ final PdfDocument document = getDocument();
+ if (((PdfUADocument) document).isClosing()) {
+ super.flush(flushResourcesContentStreams);
+ return;
+ }
+ ((PdfUADocument) document).warnOnPageFlush();
+ }
+}
diff --git a/pdfua/src/main/java/com/itextpdf/pdfua/PdfUAPageFactory.java b/pdfua/src/main/java/com/itextpdf/pdfua/PdfUAPageFactory.java
new file mode 100644
index 0000000000..66117687e6
--- /dev/null
+++ b/pdfua/src/main/java/com/itextpdf/pdfua/PdfUAPageFactory.java
@@ -0,0 +1,58 @@
+/*
+ This file is part of the iText (R) project.
+ Copyright (c) 1998-2024 Apryse Group NV
+ Authors: Apryse Software.
+
+ This program is offered under a commercial and under the AGPL license.
+ For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
+
+ AGPL licensing:
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+ */
+package com.itextpdf.pdfua;
+
+import com.itextpdf.kernel.geom.PageSize;
+import com.itextpdf.kernel.pdf.IPdfPageFactory;
+import com.itextpdf.kernel.pdf.PdfDictionary;
+import com.itextpdf.kernel.pdf.PdfDocument;
+import com.itextpdf.kernel.pdf.PdfPage;
+
+class PdfUAPageFactory implements IPdfPageFactory {
+
+
+ public PdfUAPageFactory() {
+ //empty constructor
+ }
+
+ /**
+ * @param pdfObject the {@link PdfDictionary} object on which the {@link PdfPage} will be based
+ *
+ * @return The pdf page.
+ */
+ @Override
+ public PdfPage createPdfPage(PdfDictionary pdfObject) {
+ return new PdfUAPage(pdfObject);
+ }
+
+ /**
+ * @param pdfDocument {@link PdfDocument} to add page
+ * @param pageSize {@link PageSize} of the created page
+ *
+ * @return The Pdf page.
+ */
+ @Override
+ public PdfPage createPdfPage(PdfDocument pdfDocument, PageSize pageSize) {
+ return new PdfUAPage(pdfDocument, pageSize);
+ }
+}
diff --git a/pdfua/src/main/java/com/itextpdf/pdfua/checkers/PdfUA1Checker.java b/pdfua/src/main/java/com/itextpdf/pdfua/checkers/PdfUA1Checker.java
index 36148147d8..3c9bc36626 100644
--- a/pdfua/src/main/java/com/itextpdf/pdfua/checkers/PdfUA1Checker.java
+++ b/pdfua/src/main/java/com/itextpdf/pdfua/checkers/PdfUA1Checker.java
@@ -25,15 +25,20 @@ This file is part of the iText (R) project.
import com.itextpdf.commons.datastructures.Tuple2;
import com.itextpdf.commons.utils.MessageFormatUtil;
import com.itextpdf.kernel.font.PdfFont;
+import com.itextpdf.kernel.pdf.EncryptionConstants;
import com.itextpdf.kernel.pdf.IsoKey;
+import com.itextpdf.kernel.pdf.PdfArray;
import com.itextpdf.kernel.pdf.PdfBoolean;
import com.itextpdf.kernel.pdf.PdfCatalog;
import com.itextpdf.kernel.pdf.PdfDictionary;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfName;
+import com.itextpdf.kernel.pdf.PdfNumber;
import com.itextpdf.kernel.pdf.PdfObject;
import com.itextpdf.kernel.pdf.PdfResources;
import com.itextpdf.kernel.pdf.PdfStream;
+import com.itextpdf.kernel.pdf.PdfString;
+import com.itextpdf.kernel.pdf.PdfVersion;
import com.itextpdf.kernel.pdf.tagging.PdfMcr;
import com.itextpdf.kernel.pdf.tagging.PdfNamespace;
import com.itextpdf.kernel.pdf.tagging.PdfStructTreeRoot;
@@ -41,11 +46,24 @@ This file is part of the iText (R) project.
import com.itextpdf.kernel.pdf.tagutils.IRoleMappingResolver;
import com.itextpdf.kernel.pdf.tagutils.TagStructureContext;
import com.itextpdf.kernel.pdf.tagutils.TagTreeIterator;
-import com.itextpdf.kernel.pdf.tagutils.TagTreePointer;
import com.itextpdf.kernel.utils.IValidationChecker;
import com.itextpdf.kernel.utils.ValidationContext;
+import com.itextpdf.kernel.utils.checkers.FontCheckUtil;
+import com.itextpdf.kernel.xmp.XMPConst;
+import com.itextpdf.kernel.xmp.XMPException;
+import com.itextpdf.kernel.xmp.XMPMeta;
+import com.itextpdf.kernel.xmp.XMPMetaFactory;
+import com.itextpdf.pdfua.checkers.utils.AnnotationCheckUtil;
+import com.itextpdf.pdfua.checkers.utils.BCP47Validator;
+import com.itextpdf.pdfua.checkers.utils.FormCheckUtil;
+import com.itextpdf.pdfua.checkers.utils.FormulaCheckUtil;
import com.itextpdf.pdfua.checkers.utils.GraphicsCheckUtil;
import com.itextpdf.pdfua.checkers.utils.LayoutCheckUtil;
+import com.itextpdf.pdfua.checkers.utils.NoteCheckUtil;
+import com.itextpdf.pdfua.checkers.utils.PdfUAValidationContext;
+import com.itextpdf.pdfua.checkers.utils.XfaCheckUtil;
+import com.itextpdf.pdfua.checkers.utils.headings.HeadingsChecker;
+import com.itextpdf.pdfua.checkers.utils.tables.TableCheckUtil;
import com.itextpdf.pdfua.exceptions.PdfUAConformanceException;
import com.itextpdf.pdfua.exceptions.PdfUAExceptionMessageConstants;
@@ -66,6 +84,10 @@ public class PdfUA1Checker implements IValidationChecker {
private final TagStructureContext tagStructureContext;
+ private final HeadingsChecker headingsChecker;
+
+ private final PdfUAValidationContext context;
+
/**
* Creates PdfUA1Checker instance with PDF document which will be validated against PDF/UA-1 standard.
*
@@ -74,6 +96,8 @@ public class PdfUA1Checker implements IValidationChecker {
public PdfUA1Checker(PdfDocument pdfDocument) {
this.pdfDocument = pdfDocument;
this.tagStructureContext = new TagStructureContext(pdfDocument);
+ this.context = new PdfUAValidationContext(pdfDocument);
+ this.headingsChecker = new HeadingsChecker(context);
}
/**
@@ -84,6 +108,7 @@ public void validateDocument(ValidationContext validationContext) {
checkCatalog(validationContext.getPdfDocument().getCatalog());
checkStructureTreeRoot(validationContext.getPdfDocument().getStructTreeRoot());
checkFonts(validationContext.getFonts());
+ XfaCheckUtil.check(validationContext.getPdfDocument());
}
/**
@@ -93,7 +118,8 @@ public void validateDocument(ValidationContext validationContext) {
public void validateObject(Object obj, IsoKey key, PdfResources resources, PdfStream contentStream, Object extra) {
switch (key) {
case LAYOUT:
- LayoutCheckUtil.checkLayoutElements(obj);
+ new LayoutCheckUtil(context).checkRenderer(obj);
+ headingsChecker.checkLayoutElement(obj);
break;
case CANVAS_WRITING_CONTENT:
checkOnWritingCanvasToContent(obj);
@@ -101,9 +127,83 @@ public void validateObject(Object obj, IsoKey key, PdfResources resources, PdfSt
case CANVAS_BEGIN_MARKED_CONTENT:
checkOnOpeningBeginMarkedContent(obj, extra);
break;
+ case FONT:
+ checkText((String) obj, (PdfFont) extra);
+ break;
+ case DUPLICATE_ID_ENTRY:
+ throw new PdfUAConformanceException(MessageFormatUtil.format(
+ PdfUAExceptionMessageConstants.NON_UNIQUE_ID_ENTRY_IN_STRUCT_TREE_ROOT, obj));
+ case PDF_OBJECT:
+ checkPdfObject((PdfObject) obj);
+ break;
+ case CRYPTO:
+ checkCrypto((PdfDictionary) obj);
+ break;
+ }
+ }
+
+ /**
+ * Verify the conformity of the file specification dictionary.
+ *
+ * @param fileSpec the {@link PdfDictionary} containing file specification to be checked
+ */
+ protected void checkFileSpec(PdfDictionary fileSpec) {
+ if (fileSpec.containsKey(PdfName.EF)) {
+ if (!fileSpec.containsKey(PdfName.F) || !fileSpec.containsKey(PdfName.UF)) {
+ throw new PdfUAConformanceException(PdfUAExceptionMessageConstants.FILE_SPECIFICATION_DICTIONARY_SHALL_CONTAIN_F_KEY_AND_UF_KEY);
+ }
+ }
+ }
+
+
+ private void checkText(String str, PdfFont font) {
+ int index = FontCheckUtil.checkGlyphsOfText(str, font, new UaCharacterChecker());
+
+ if (index != -1) {
+ throw new PdfUAConformanceException(MessageFormatUtil.format(
+ PdfUAExceptionMessageConstants.GLYPH_IS_NOT_DEFINED_OR_WITHOUT_UNICODE, str.charAt(index)));
+ }
+ }
+
+ protected void checkMetadata(PdfCatalog catalog) {
+ if (catalog.getDocument().getPdfVersion().compareTo(PdfVersion.PDF_1_7) > 0) {
+ throw new PdfUAConformanceException(PdfUAExceptionMessageConstants.INVALID_PDF_VERSION);
+ }
+
+ PdfObject pdfMetadata = catalog.getPdfObject().get(PdfName.Metadata);
+ if (pdfMetadata == null || !pdfMetadata.isStream()) {
+ throw new PdfUAConformanceException(PdfUAExceptionMessageConstants.DOCUMENT_SHALL_CONTAIN_XMP_METADATA_STREAM);
+ }
+ byte[] metaBytes = ((PdfStream) pdfMetadata).getBytes();
+
+ try {
+ XMPMeta metadata = XMPMetaFactory.parseFromBuffer(metaBytes);
+ Integer part = metadata.getPropertyInteger(XMPConst.NS_PDFUA_ID, XMPConst.PART);
+ if (!Integer.valueOf(1).equals(part)) {
+ throw new PdfUAConformanceException(PdfUAExceptionMessageConstants.METADATA_SHALL_CONTAIN_UA_VERSION_IDENTIFIER);
+ }
+ if (metadata.getProperty(XMPConst.NS_DC, XMPConst.TITLE) == null) {
+ throw new PdfUAConformanceException(PdfUAExceptionMessageConstants.METADATA_SHALL_CONTAIN_DC_TITLE_ENTRY);
+ }
+ } catch (XMPException e) {
+ throw new PdfUAConformanceException(PdfUAExceptionMessageConstants.DOCUMENT_SHALL_CONTAIN_XMP_METADATA_STREAM, e);
}
}
+ private void checkViewerPreferences(PdfCatalog catalog) {
+ PdfDictionary viewerPreferences = catalog.getPdfObject().getAsDictionary(PdfName.ViewerPreferences);
+ if (viewerPreferences == null) {
+ throw new PdfUAConformanceException(PdfUAExceptionMessageConstants.MISSING_VIEWER_PREFERENCES);
+ }
+ PdfObject displayDocTitle = viewerPreferences.get(PdfName.DisplayDocTitle);
+ if (!(displayDocTitle instanceof PdfBoolean)) {
+ throw new PdfUAConformanceException(PdfUAExceptionMessageConstants.MISSING_VIEWER_PREFERENCES);
+ }
+ if (PdfBoolean.FALSE.equals(displayDocTitle)) {
+ throw new PdfUAConformanceException(PdfUAExceptionMessageConstants.VIEWER_PREFERENCES_IS_FALSE);
+ }
+
+ }
private void checkOnWritingCanvasToContent(Object data) {
Stack> tagStack = getTagStack(data);
@@ -199,14 +299,22 @@ private void checkCatalog(PdfCatalog catalog) {
throw new PdfUAConformanceException(
PdfUAExceptionMessageConstants.METADATA_SHALL_BE_PRESENT_IN_THE_CATALOG_DICTIONARY);
}
+ if (!(catalogDict.get(PdfName.Lang) instanceof PdfString) || !BCP47Validator.validate(catalogDict.get(PdfName.Lang).toString())) {
+ throw new PdfUAConformanceException(
+ PdfUAExceptionMessageConstants.DOCUMENT_SHALL_CONTAIN_VALID_LANG_ENTRY);
+ }
PdfDictionary markInfo = catalogDict.getAsDictionary(PdfName.MarkInfo);
if (markInfo != null && markInfo.containsKey(PdfName.Suspects)) {
PdfBoolean markInfoSuspects = markInfo.getAsBoolean(PdfName.Suspects);
if (markInfoSuspects != null && markInfoSuspects.getValue()) {
throw new PdfUAConformanceException(
- PdfUAExceptionMessageConstants.SUSPECTS_ENTRY_IN_MARK_INFO_DICTIONARY_SHALL_NOT_HAVE_A_VALUE_OF_TRUE);
+ PdfUAExceptionMessageConstants.
+ SUSPECTS_ENTRY_IN_MARK_INFO_DICTIONARY_SHALL_NOT_HAVE_A_VALUE_OF_TRUE);
}
}
+ checkViewerPreferences(catalog);
+ checkMetadata(catalog);
+ checkOCProperties(catalogDict.getAsDictionary(PdfName.OCProperties));
}
private void checkStructureTreeRoot(PdfStructTreeRoot structTreeRoot) {
@@ -222,10 +330,49 @@ private void checkStructureTreeRoot(PdfStructTreeRoot structTreeRoot) {
}
TagTreeIterator tagTreeIterator = new TagTreeIterator(structTreeRoot);
- tagTreeIterator.addHandler(GraphicsCheckUtil.createFigureTagHandler());
+ tagTreeIterator.addHandler(new GraphicsCheckUtil.GraphicsHandler(context));
+ tagTreeIterator.addHandler(new FormulaCheckUtil.FormulaTagHandler(context));
+ tagTreeIterator.addHandler(new NoteCheckUtil.NoteTagHandler(context));
+ tagTreeIterator.addHandler(new HeadingsChecker.HeadingHandler(context));
+ tagTreeIterator.addHandler(new TableCheckUtil.TableHandler(context));
+ tagTreeIterator.addHandler(new AnnotationCheckUtil.AnnotationHandler(context));
+ tagTreeIterator.addHandler(new FormCheckUtil.FormTagHandler(context));
tagTreeIterator.traverse();
+ }
+ private void checkOCProperties(PdfDictionary ocProperties) {
+ if (ocProperties == null) {
+ return;
+ }
+ if (!(ocProperties.get(PdfName.Configs) instanceof PdfArray)) {
+ throw new PdfUAConformanceException(PdfUAExceptionMessageConstants.OCG_PROPERTIES_CONFIG_SHALL_BE_AN_ARRAY);
+ }
+ PdfArray configs = ocProperties.getAsArray(PdfName.Configs);
+ if (configs != null && !configs.isEmpty()) {
+ PdfDictionary d = ocProperties.getAsDictionary(PdfName.D);
+ checkOCGNameAndASKey(d);
+ for (PdfObject config : configs) {
+ checkOCGNameAndASKey((PdfDictionary) config);
+ }
+ PdfArray ocgsArray = ocProperties.getAsArray(PdfName.OCGs);
+ if (ocgsArray != null) {
+ for (PdfObject ocg : ocgsArray) {
+ checkOCGNameAndASKey((PdfDictionary) ocg);
+ }
+ }
+ }
+ }
+ private void checkOCGNameAndASKey(PdfDictionary dict) {
+ if (dict == null) {
+ return;
+ }
+ if (dict.get(PdfName.AS) != null) {
+ throw new PdfUAConformanceException(PdfUAExceptionMessageConstants.OCG_SHALL_NOT_CONTAIN_AS_ENTRY);
+ }
+ if (!(dict.get(PdfName.Name) instanceof PdfString) || (((PdfString)dict.get(PdfName.Name)).toString().isEmpty())) {
+ throw new PdfUAConformanceException(PdfUAExceptionMessageConstants.NAME_ENTRY_IS_MISSING_OR_EMPTY_IN_OCG);
+ }
}
private void checkFonts(Collection fontsInDocument) {
@@ -243,4 +390,45 @@ private void checkFonts(Collection fontsInDocument) {
));
}
}
+
+ private void checkCrypto(PdfDictionary encryptionDictionary) {
+ if (encryptionDictionary != null) {
+ if (!(encryptionDictionary.get(PdfName.P) instanceof PdfNumber)) {
+ throw new PdfUAConformanceException(
+ PdfUAExceptionMessageConstants.P_VALUE_IS_ABSENT_IN_ENCRYPTION_DICTIONARY);
+ }
+ int permissions = ((PdfNumber) encryptionDictionary.get(PdfName.P)).intValue();
+ if ((EncryptionConstants.ALLOW_SCREENREADERS & permissions) == 0) {
+ throw new PdfUAConformanceException(
+ PdfUAExceptionMessageConstants.TENTH_BIT_OF_P_VALUE_IN_ENCRYPTION_SHOULD_BE_NON_ZERO);
+ }
+ }
+ }
+
+ /**
+ * This method checks the requirements that must be fulfilled by a COS
+ * object in a PDF/UA document.
+ *
+ * @param obj the COS object that must be checked
+ */
+ private void checkPdfObject(PdfObject obj) {
+ if (obj.getType() == PdfObject.DICTIONARY) {
+ PdfDictionary dict = (PdfDictionary) obj;
+ PdfName type = dict.getAsName(PdfName.Type);
+ if (PdfName.Filespec.equals(type)) {
+ checkFileSpec(dict);
+ }
+ }
+ }
+
+ private static final class UaCharacterChecker implements FontCheckUtil.CharacterChecker {
+ @Override
+ public boolean check(int ch, PdfFont font) {
+ if (font.containsGlyph(ch)) {
+ return !font.getGlyph(ch).hasValidUnicode();
+ } else {
+ return true;
+ }
+ }
+ }
}
diff --git a/pdfua/src/main/java/com/itextpdf/pdfua/checkers/utils/ActionCheckUtil.java b/pdfua/src/main/java/com/itextpdf/pdfua/checkers/utils/ActionCheckUtil.java
new file mode 100644
index 0000000000..6e96014bd5
--- /dev/null
+++ b/pdfua/src/main/java/com/itextpdf/pdfua/checkers/utils/ActionCheckUtil.java
@@ -0,0 +1,61 @@
+/*
+ This file is part of the iText (R) project.
+ Copyright (c) 1998-2024 Apryse Group NV
+ Authors: Apryse Software.
+
+ This program is offered under a commercial and under the AGPL license.
+ For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
+
+ AGPL licensing:
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+ */
+package com.itextpdf.pdfua.checkers.utils;
+
+import com.itextpdf.kernel.pdf.PdfDictionary;
+import com.itextpdf.kernel.pdf.PdfName;
+import com.itextpdf.pdfua.exceptions.PdfUAConformanceException;
+import com.itextpdf.pdfua.exceptions.PdfUAExceptionMessageConstants;
+
+/**
+ * Class that provides methods for checking PDF/UA compliance of actions.
+ */
+public class ActionCheckUtil {
+ private ActionCheckUtil() {
+ // Empty constructor.
+ }
+
+ /**
+ * Check PDF/UA compliance of an action
+ *
+ * @param action action to check
+ */
+ public static void checkAction(PdfDictionary action) {
+ if (action == null) {
+ return;
+ }
+ PdfName s = action.getAsName(PdfName.S);
+ PdfDictionary rendition = action.getAsDictionary(PdfName.R);
+ if (PdfName.Rendition.equals(s) && rendition != null) {
+ checkRenditionMedia(rendition.getAsDictionary(PdfName.BE) != null ? rendition.getAsDictionary(PdfName.BE).getAsDictionary(PdfName.C) : null);
+ checkRenditionMedia(rendition.getAsDictionary(PdfName.MH) != null ? rendition.getAsDictionary(PdfName.MH).getAsDictionary(PdfName.C) : null);
+ checkRenditionMedia(rendition.getAsDictionary(PdfName.C));
+ }
+ }
+
+ private static void checkRenditionMedia(PdfDictionary mediaClipDict) {
+ if (mediaClipDict != null && (mediaClipDict.get(PdfName.CT) == null || mediaClipDict.get(PdfName.Alt) == null)) {
+ throw new PdfUAConformanceException(PdfUAExceptionMessageConstants.CT_OR_ALT_ENTRY_IS_MISSING_IN_MEDIA_CLIP);
+ }
+ }
+}
diff --git a/pdfua/src/main/java/com/itextpdf/pdfua/checkers/utils/AnnotationCheckUtil.java b/pdfua/src/main/java/com/itextpdf/pdfua/checkers/utils/AnnotationCheckUtil.java
new file mode 100644
index 0000000000..0128fba36f
--- /dev/null
+++ b/pdfua/src/main/java/com/itextpdf/pdfua/checkers/utils/AnnotationCheckUtil.java
@@ -0,0 +1,162 @@
+/*
+ This file is part of the iText (R) project.
+ Copyright (c) 1998-2024 Apryse Group NV
+ Authors: Apryse Software.
+
+ This program is offered under a commercial and under the AGPL license.
+ For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
+
+ AGPL licensing:
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+ */
+package com.itextpdf.pdfua.checkers.utils;
+
+import com.itextpdf.commons.utils.MessageFormatUtil;
+import com.itextpdf.kernel.exceptions.PdfException;
+import com.itextpdf.kernel.pdf.PdfArray;
+import com.itextpdf.kernel.pdf.PdfDictionary;
+import com.itextpdf.kernel.pdf.PdfName;
+import com.itextpdf.kernel.pdf.PdfObject;
+import com.itextpdf.kernel.pdf.annot.PdfAnnotation;
+import com.itextpdf.kernel.pdf.tagging.IStructureNode;
+import com.itextpdf.kernel.pdf.tagging.PdfObjRef;
+import com.itextpdf.kernel.pdf.tagging.PdfStructElem;
+import com.itextpdf.pdfua.exceptions.PdfUAConformanceException;
+import com.itextpdf.pdfua.exceptions.PdfUAExceptionMessageConstants;
+
+/**
+ * Class that provides methods for checking PDF/UA compliance of annotations.
+ */
+public final class AnnotationCheckUtil {
+ private AnnotationCheckUtil() {
+ // Empty constructor.
+ }
+
+ /**
+ * Is annotation visible: {@code true} if hidden flag isn't
+ * set and annotation intersects CropBox (default value is MediaBox).
+ *
+ * @param annotDict annotation to check
+ *
+ * @return {@code true} if annotation should be checked, otherwise {@code false}
+ */
+ public static boolean isAnnotationVisible(PdfDictionary annotDict) {
+ if (annotDict.getAsNumber(PdfName.F) != null) {
+ int flags = annotDict.getAsNumber(PdfName.F).intValue();
+ if ((flags & PdfAnnotation.HIDDEN) != 0) {
+ return false;
+ }
+ }
+ if (annotDict.getAsDictionary(PdfName.P) != null) {
+ PdfDictionary page = annotDict.getAsDictionary(PdfName.P);
+ PdfArray pageBox = page.getAsArray(PdfName.CropBox) == null ? page.getAsArray(PdfName.MediaBox) : page.getAsArray(PdfName.CropBox);
+ if (pageBox != null && annotDict.getAsArray(PdfName.Rect) != null) {
+ PdfArray annotBox = annotDict.getAsArray(PdfName.Rect);
+ try {
+ if (pageBox.toRectangle().getIntersection(annotBox.toRectangle()) == null) {
+ return false;
+ }
+ } catch (PdfException ignore) {
+ // ignore
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Helper class that checks the conformance of annotations while iterating the tag tree structure.
+ */
+ public static class AnnotationHandler extends ContextAwareTagTreeIteratorHandler {
+
+ /**
+ * Creates a new instance of the {@link AnnotationCheckUtil.AnnotationHandler}.
+ *
+ * @param context The validation context.
+ */
+ public AnnotationHandler(PdfUAValidationContext context) {
+ super(context);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void nextElement(IStructureNode elem) {
+ if (!(elem instanceof PdfObjRef)) {
+ return;
+ }
+ PdfObjRef objRef = (PdfObjRef) elem;
+ PdfDictionary annotObj = objRef.getReferencedObject();
+ if (annotObj == null) {
+ return;
+ }
+
+ if (annotObj.getAsDictionary(PdfName.P) != null) {
+ PdfDictionary pageDict = annotObj.getAsDictionary(PdfName.P);
+ if (!PdfName.S.equals(pageDict.getAsName(PdfName.Tabs))) {
+ throw new PdfUAConformanceException(PdfUAExceptionMessageConstants.PAGE_WITH_ANNOT_DOES_NOT_HAVE_TABS_WITH_S);
+ }
+ }
+
+ PdfName subtype = annotObj.getAsName(PdfName.Subtype);
+
+ if (!isAnnotationVisible(annotObj) || PdfName.Popup.equals(subtype)) {
+ return;
+ }
+
+ if (PdfName.PrinterMark.equals(subtype)) {
+ throw new PdfUAConformanceException(PdfUAExceptionMessageConstants.PRINTER_MARK_IS_NOT_PERMITTED);
+ }
+
+ if (PdfName.TrapNet.equals(subtype)) {
+ throw new PdfUAConformanceException(PdfUAExceptionMessageConstants.ANNOT_TRAP_NET_IS_NOT_PERMITTED);
+ }
+
+ if (!PdfName.Widget.equals(subtype) && !(annotObj.containsKey(PdfName.Contents)
+ || annotObj.containsKey(PdfName.Alt))) {
+ throw new PdfUAConformanceException(MessageFormatUtil.format(
+ PdfUAExceptionMessageConstants.ANNOTATION_OF_TYPE_0_SHOULD_HAVE_CONTENTS_OR_ALT_KEY, subtype.getValue()));
+ }
+
+ if (PdfName.Link.equals(subtype)) {
+ PdfStructElem parentLink = context.getElementIfRoleMatches(PdfName.Link, objRef.getParent());
+ if (parentLink == null) {
+ throw new PdfUAConformanceException(
+ PdfUAExceptionMessageConstants.LINK_ANNOT_IS_NOT_NESTED_WITHIN_LINK);
+ }
+ if (!annotObj.containsKey(PdfName.Contents)) {
+ throw new PdfUAConformanceException(PdfUAExceptionMessageConstants.LINK_ANNOTATION_SHOULD_HAVE_CONTENTS_KEY);
+ }
+ }
+
+ if (PdfName.Screen.equals(subtype)) {
+ PdfDictionary action = annotObj.getAsDictionary(PdfName.A);
+ PdfDictionary additionalActions = annotObj.getAsDictionary(PdfName.AA);
+ ActionCheckUtil.checkAction(action);
+ checkAAEntry(additionalActions);
+ }
+ }
+
+ private static void checkAAEntry(PdfDictionary additionalActions) {
+ if (additionalActions != null) {
+ for (PdfObject val : additionalActions.values()) {
+ if (val instanceof PdfDictionary) {
+ ActionCheckUtil.checkAction((PdfDictionary) val);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/pdfua/src/main/java/com/itextpdf/pdfua/checkers/utils/BCP47Validator.java b/pdfua/src/main/java/com/itextpdf/pdfua/checkers/utils/BCP47Validator.java
new file mode 100644
index 0000000000..84847f1846
--- /dev/null
+++ b/pdfua/src/main/java/com/itextpdf/pdfua/checkers/utils/BCP47Validator.java
@@ -0,0 +1,59 @@
+/*
+ This file is part of the iText (R) project.
+ Copyright (c) 1998-2024 Apryse Group NV
+ Authors: Apryse Software.
+
+ This program is offered under a commercial and under the AGPL license.
+ For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
+
+ AGPL licensing:
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+ */
+package com.itextpdf.pdfua.checkers.utils;
+
+import java.util.regex.Pattern;
+
+/**
+ * This class is a validator for IETF BCP 47 language tag (RFC 5646)
+ */
+public class BCP47Validator {
+ private static String regular = "(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang)";
+ private static String irregular = "(en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)";
+ private static String grandfathered = "(?" + irregular + "|" + regular + ")";
+ private static String privateUse = "(?x(-[A-Za-z0-9]{1,8})+)";
+ private static String singleton = "[0-9A-WY-Za-wy-z]";
+ private static String extension = "(?" + singleton + "(-[A-Za-z0-9]{2,8})+)";
+ private static String variant = "(?[A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3})";
+ private static String region = "(?[A-Za-z]{2}|[0-9]{3})";
+ private static String script = "(?