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.itextpdf root - 8.0.3 + 8.0.4 + barcodes + iText - barcodes https://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.itextpdf root - 8.0.3 + 8.0.4 + bouncy-castle-adapter + iText - Bouncy Castle Adapter https://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.itextpdf root - 8.0.3 + 8.0.4 + bouncy-castle-connector + iText - Bouncy Castle Connector https://itextpdf.com/ - + **/* **/* - + com.itextpdf bouncy-castle-adapter ${project.version} - true org.bouncycastle @@ -31,12 +33,12 @@ bcprov-jdk18on + true com.itextpdf bouncy-castle-fips-adapter ${project.version} - true org.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.itextpdf root - 8.0.3 + 8.0.4 + bouncy-castle-fips-adapter + iText - Bouncy Castle FIPS Adapter https://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.itextpdf root - 8.0.3 + 8.0.4 + commons + iText - commons https://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.itextpdf root - 8.0.3 + 8.0.4 + font-asian + iText - Asian fonts iText Asian fonts for use in conjunction with iText, a free Java-PDF library https://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.itextpdf root - 8.0.3 + 8.0.4 + forms + iText - forms https://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.getOwnProperty(i)); + modelElement.deleteOwnProperty(i); + } + + return properties; + } + + /** + * Reapplies the properties {@link IPropertyContainer}. + * + * @param modelElement The model element to reapply the properties to. + * @param properties The properties to reapply. + */ + public static void reapplyProperties(IPropertyContainer modelElement, Map properties) { + for (Entry integerObjectEntry : properties.entrySet()) { + if (integerObjectEntry.getValue() != null) { + modelElement.setProperty(integerObjectEntry.getKey(), integerObjectEntry.getValue()); + } else { + modelElement.deleteOwnProperty(integerObjectEntry.getKey()); + } + } + } +} diff --git a/forms/src/main/java/com/itextpdf/forms/util/RegisterDefaultDiContainer.java b/forms/src/main/java/com/itextpdf/forms/util/RegisterDefaultDiContainer.java index 043a10bd5f..aadbddcf4f 100644 --- a/forms/src/main/java/com/itextpdf/forms/util/RegisterDefaultDiContainer.java +++ b/forms/src/main/java/com/itextpdf/forms/util/RegisterDefaultDiContainer.java @@ -26,8 +26,14 @@ This file is part of the iText (R) project. import com.itextpdf.forms.fields.merging.MergeFieldsStrategy; import com.itextpdf.forms.fields.merging.OnDuplicateFormFieldNameStrategy; +/** + * Registers a default instance for a dependency injection container. + */ public class RegisterDefaultDiContainer { + /** + * Creates an instance of {@link RegisterDefaultDiContainer}. + */ public RegisterDefaultDiContainer() { // Empty constructor but should be public as we need it for automatic class loading // sharp diff --git a/forms/src/main/java/com/itextpdf/forms/xfa/XfaForm.java b/forms/src/main/java/com/itextpdf/forms/xfa/XfaForm.java index 04c5e4ef9b..5f16ceac2a 100644 --- a/forms/src/main/java/com/itextpdf/forms/xfa/XfaForm.java +++ b/forms/src/main/java/com/itextpdf/forms/xfa/XfaForm.java @@ -358,6 +358,30 @@ public static String getNodeText(Node n) { return n == null ? "" : getNodeText(n, ""); } + /** + * Gets all the text contained in the child nodes of the node under the provided path. + * + * @param path path to the node to extract text in the format "some.path.to.node" + * + * @return text found under the provided path or {@code null} if node or text wasn't found + */ + public String getNodeTextByPath(String path) { + if (!xfaPresent) { + return null; + } + Xml2SomDatasets nodeSom = new Xml2SomDatasets(domDocument); + AcroFieldsSearch nodeFieldsSom = new AcroFieldsSearch(nodeSom.getName2Node().keySet()); + + String foundPath = nodeFieldsSom.inverseSearchGlobal(Xml2Som.splitParts(path)); + + if (foundPath != null) { + Node resultNode = nodeSom.getName2Node().get(foundPath); + return XfaForm.getNodeText(resultNode); + } + + return null; + } + /** * Sets the text of this node. All the child's node are deleted and a new * child text node is created. diff --git a/forms/src/main/java/com/itextpdf/forms/xfdf/ActionObject.java b/forms/src/main/java/com/itextpdf/forms/xfdf/ActionObject.java index 57ae592a5f..abe3b444b1 100644 --- a/forms/src/main/java/com/itextpdf/forms/xfdf/ActionObject.java +++ b/forms/src/main/java/com/itextpdf/forms/xfdf/ActionObject.java @@ -33,7 +33,6 @@ This file is part of the iText (R) project. */ public class ActionObject { - /** * Type of inner action element. Possible values: {@link PdfName#URI}, {@link PdfName#Launch}, {@link PdfName#GoTo}, * {@link PdfName#GoToR}, {@link PdfName#Named}. @@ -74,16 +73,21 @@ public class ActionObject { */ private DestObject destination; + /** + * Creates an instance of {@link ActionObject}. + * + * @param type type of inner action element. Possible values: {@link PdfName#URI}, {@link PdfName#Launch}, + * {@link PdfName#GoTo}, {@link PdfName#GoToR}, {@link PdfName#Named} + */ public ActionObject(PdfName type) { this.type = type; } - /** * Returns the type of inner action element. Possible values: {@link PdfName#URI}, {@link PdfName#Launch}, * {@link PdfName#GoTo}, {@link PdfName#GoToR}, {@link PdfName#Named}. * - * @return {@link PdfName} type of inner action element + * @return {@link PdfName} type of inner action element. */ public PdfName getType() { return type; @@ -94,7 +98,8 @@ public PdfName getType() { * {@link PdfName#GoTo}, {@link PdfName#GoToR}, {@link PdfName#Named}. * * @param type {@link PdfName} type of inner action object - * @return current {@link ActionObject} + * + * @return current {@link ActionObject}. */ public ActionObject setType(PdfName type) { this.type = type; @@ -105,7 +110,7 @@ public ActionObject setType(PdfName type) { * Gets the string value of URI elements. Corresponds to Name, required attribute of URI element. * For more details see paragraph 6.5.30 in Xfdf specification. * - * @return {@link PdfString} value of URI element + * @return {@link PdfString} value of URI element. */ public PdfString getUri() { return uri; @@ -116,7 +121,8 @@ public PdfString getUri() { * For more details see paragraph 6.5.30 in Xfdf specification. * * @param uri {@link PdfString} value to be set to URI element - * @return current {@link ActionObject} + * + * @return current {@link ActionObject}. */ public ActionObject setUri(PdfString uri) { this.uri = uri; @@ -126,7 +132,7 @@ public ActionObject setUri(PdfString uri) { /** * Gets IsMap, optional attribute of URI element. For more details see paragraph 6.5.30 in Xfdf specification. * - * @return boolean indicating if URI element is a map + * @return boolean indicating if URI element is a map. */ public boolean isMap() { return isMap; @@ -136,7 +142,8 @@ public boolean isMap() { * Sets IsMap, optional attribute of URI element. For more details see paragraph 6.5.30 in Xfdf specification. * * @param map boolean indicating if URI element is a map - * @return current {@link ActionObject} + * + * @return current {@link ActionObject}. */ public ActionObject setMap(boolean map) { isMap = map; @@ -144,19 +151,22 @@ public ActionObject setMap(boolean map) { } /** - * Gets the value of Name, required attribute of Named element. For more details see paragraph 6.5.24 in Xfdf specification. + * Gets the value of Name, required attribute of Named element. + * For more details see paragraph 6.5.24 in Xfdf specification. * - * @return {@link PdfName} value of Name attribute of a named action element + * @return {@link PdfName} value of Name attribute of a named action element. */ public PdfName getNameAction() { return nameAction; } /** - * Sets the value of Name, required attribute of Named element. For more details see paragraph 6.5.24 in Xfdf specification. + * Sets the value of Name, required attribute of Named element. + * For more details see paragraph 6.5.24 in Xfdf specification. * * @param nameAction {@link PdfName} value to be set to Name attribute of a named action element - * @return current {@link ActionObject} + * + * @return current {@link ActionObject}. */ public ActionObject setNameAction(PdfName nameAction) { this.nameAction = nameAction; @@ -168,7 +178,7 @@ public ActionObject setNameAction(PdfName nameAction) { * Corresponds to F key in go-to action or launch dictionaries. * For more details see paragraphs 6.5.11, 6.5.23 in Xfdf specification. * - * @return {@link String} value of OriginalName attribute of current action object + * @return {@link String} value of OriginalName attribute of current action object. */ public String getFileOriginalName() { return fileOriginalName; @@ -180,7 +190,8 @@ public String getFileOriginalName() { * For more details see paragraphs 6.5.11, 6.5.23 in Xfdf specification. * * @param fileOriginalName {@link String} value of OriginalName attribute of action object - * @return current {@link ActionObject} + * + * @return current {@link ActionObject}. */ public ActionObject setFileOriginalName(String fileOriginalName) { this.fileOriginalName = fileOriginalName; @@ -188,19 +199,22 @@ public ActionObject setFileOriginalName(String fileOriginalName) { } /** - * Gets the boolean value of NewWindow, optional attribute of Launch element. For more details see paragraph 6.5.23 in Xfdf specification. + * Gets the boolean value of NewWindow, optional attribute of Launch element. + * For more details see paragraph 6.5.23 in Xfdf specification. * - * @return boolean indicating if current Launch action element should be opened in a new window + * @return boolean indicating if current Launch action element should be opened in a new window. */ public boolean isNewWindow() { return isNewWindow; } /** - * Sets the boolean value of NewWindow, optional attribute of Launch element. For more details see paragraph 6.5.23 in Xfdf specification. + * Sets the boolean value of NewWindow, optional attribute of Launch element. + * For more details see paragraph 6.5.23 in Xfdf specification. * * @param newWindow boolean indicating if current Launch action element should be opened in a new window - * @return current {@link ActionObject} + * + * @return current {@link ActionObject}. */ public ActionObject setNewWindow(boolean newWindow) { isNewWindow = newWindow; @@ -212,7 +226,7 @@ public ActionObject setNewWindow(boolean newWindow) { * Corresponds to Dest key in link annotation dictionary. * For more details see paragraph 6.5.10 in Xfdf specification. * - * @return {@link DestObject} destination attribute of current action element + * @return {@link DestObject} destination attribute of current action element. */ public DestObject getDestination() { return destination; @@ -224,7 +238,8 @@ public DestObject getDestination() { * For more details see paragraph 6.5.10 in Xfdf specification. * * @param destination {@link DestObject} destination attribute of the action element - * @return current {@link ActionObject} + * + * @return current {@link ActionObject}. */ public ActionObject setDestination(DestObject destination) { this.destination = destination; diff --git a/forms/src/main/java/com/itextpdf/forms/xfdf/AnnotObject.java b/forms/src/main/java/com/itextpdf/forms/xfdf/AnnotObject.java index 8656b18bb6..5a8d94342c 100644 --- a/forms/src/main/java/com/itextpdf/forms/xfdf/AnnotObject.java +++ b/forms/src/main/java/com/itextpdf/forms/xfdf/AnnotObject.java @@ -36,7 +36,8 @@ This file is part of the iText (R) project. import java.util.List; /** - * Represents annotation, a child element of annots tag in Xfdf document structure. For more details see part 6.4 in Xfdf specification. + * Represents annotation, a child element of annots tag in Xfdf document structure. + * For more details see part 6.4 in Xfdf specification. */ public class AnnotObject { @@ -139,6 +140,9 @@ public class AnnotObject { */ private PdfIndirectReference ref; + /** + * Creates an instance of {@link AnnotObject}. + */ public AnnotObject() { this.attributes = new ArrayList<>(); } @@ -150,7 +154,7 @@ public AnnotObject() { * {@link XfdfConstants#SOUND}, {@link XfdfConstants#SQUARE}, {@link XfdfConstants#SQUIGGLY}, * {@link XfdfConstants#STAMP}, {@link XfdfConstants#STRIKEOUT}, {@link XfdfConstants#TEXT}, {@link XfdfConstants#UNDERLINE}. * - * @return {@link String} value of the type of annotation + * @return {@link String} value of the type of annotation. */ public String getName() { return name; @@ -164,7 +168,8 @@ public String getName() { * {@link XfdfConstants#STAMP}, {@link XfdfConstants#STRIKEOUT}, {@link XfdfConstants#TEXT}, {@link XfdfConstants#UNDERLINE}. * * @param name {@link String} value of the type of annotation - * @return {@link AnnotObject annotation object} with set name + * + * @return {@link AnnotObject annotation object} with set name. */ public AnnotObject setName(String name) { this.name = name; @@ -174,7 +179,7 @@ public AnnotObject setName(String name) { /** * Gets a list of all attributes of the annotation. * - * @return {@link List list} containing all {@link AttributeObject attribute objects} of the annotation + * @return {@link List list} containing all {@link AttributeObject attribute objects} of the annotation. */ public List getAttributes() { return attributes; @@ -182,7 +187,9 @@ public List getAttributes() { /** * Finds the attribute by name in attributes list. - * @param name The name of the attribute to look for. + * + * @param name the name of the attribute to look for + * * @return {@link AttributeObject} with the given name, or null, if no object with this name was found. */ public AttributeObject getAttribute(String name) { @@ -196,8 +203,11 @@ public AttributeObject getAttribute(String name) { /** * Finds the attribute by name in attributes list and return its string value. - * @param name The name of the attribute to look for. - * @return the value of the {@link AttributeObject} with the given name, or null, if no object with this name was found. + * + * @param name the name of the attribute to look for + * + * @return the value of the {@link AttributeObject} with the given name, + * or null, if no object with this name was found. */ public String getAttributeValue(String name) { for (AttributeObject attr : attributes) { @@ -211,7 +221,7 @@ public String getAttributeValue(String name) { /** * Gets the popup annotation, an inner element of the annotation element. * - * @return {@link AnnotObject} representing the inner popup annotation + * @return {@link AnnotObject} representing the inner popup annotation. */ public AnnotObject getPopup() { return popup; @@ -221,7 +231,8 @@ public AnnotObject getPopup() { * Sets the popup annotation, an inner element of the annotation element. * * @param popup {@link AnnotObject annotation object} representing inner popup annotation - * @return current {@link AnnotObject annotation object} + * + * @return current {@link AnnotObject annotation object}. */ public AnnotObject setPopup(AnnotObject popup) { this.popup = popup; @@ -231,7 +242,7 @@ public AnnotObject setPopup(AnnotObject popup) { /** * Gets the boolean, indicating if annotation has an inner popup element. * - * @return true if annotation has an inner popup element, false otherwise + * @return true if annotation has an inner popup element, false otherwise. */ public boolean isHasPopup() { return hasPopup; @@ -241,7 +252,8 @@ public boolean isHasPopup() { * Sets the boolean, indicating if annotation has inner popup element. * * @param hasPopup a boolean indicating if annotation has inner popup element - * @return current {@link AnnotObject annotation object} + * + * @return current {@link AnnotObject annotation object}. */ public AnnotObject setHasPopup(boolean hasPopup) { this.hasPopup = hasPopup; @@ -256,7 +268,7 @@ public AnnotObject setHasPopup(boolean hasPopup) { * Content model: a string or a rich text string. * For more details see paragraph 6.5.4 in Xfdf document specification. * - * @return {@link PdfString} value of inner contents element of current annotation object + * @return {@link PdfString} value of inner contents element of current annotation object. */ public PdfString getContents() { return contents; @@ -266,7 +278,8 @@ public PdfString getContents() { * Sets the string value of contents tag in Xfdf document structure. * * @param contents {@link PdfString string} value of inner contents element - * @return current {@link AnnotObject annotation object} + * + * @return current {@link AnnotObject annotation object}. */ public AnnotObject setContents(PdfString contents) { this.contents = contents; @@ -281,7 +294,7 @@ public AnnotObject setContents(PdfString contents) { * Content model: text string. * For more details see paragraph 6.5.5 in Xfdf document specification. * - * @return {@link PdfString} value of inner contents-richtext element of current annotation object + * @return {@link PdfString} value of inner contents-richtext element of current annotation object. */ public PdfString getContentsRichText() { return contentsRichText; @@ -291,7 +304,8 @@ public PdfString getContentsRichText() { * Sets the string value of contents-richtext tag in xfdf document structure. * * @param contentsRichRext {@link PdfString rich text string} value of inner contents-richtext element - * @return current {@link AnnotObject annotation object} + * + * @return current {@link AnnotObject annotation object}. */ public AnnotObject setContentsRichText(PdfString contentsRichRext) { this.contentsRichText = contentsRichRext; @@ -302,7 +316,7 @@ public AnnotObject setContentsRichText(PdfString contentsRichRext) { * Gets Action element, a child of OnActivation element of the link annotation. * Corresponds to the A key in the link annotation dictionary. * - * @return inner {@link ActionObject action object} of annotation object + * @return inner {@link ActionObject action object} of annotation object. */ public ActionObject getAction() { return action; @@ -313,7 +327,8 @@ public ActionObject getAction() { * Corresponds to the A key in the link annotation dictionary. * * @param action {@link ActionObject action object}, an inner element of annotation object - * @return current {@link AnnotObject annotation object} + * + * @return current {@link AnnotObject annotation object}. */ public AnnotObject setAction(ActionObject action) { this.action = action; @@ -322,6 +337,7 @@ public AnnotObject setAction(ActionObject action) { /** * Adds new {@link AttributeObject} to the list of annotation attributes. + * * @param attr attribute to be added. */ public void addAttribute(AttributeObject attr) { @@ -347,9 +363,10 @@ void addAttribute(String name, Rectangle value) { /** * Adds new attribute by given name and value. If required attribute is present, value of the attribute can't be null. - * @param name {@link String} attribute name + * + * @param name {@link String} attribute name * @param valueObject {@link PdfObject} attribute value - * @param required boolean indicating if the attribute is required + * @param required boolean indicating if the attribute is required */ void addAttribute(String name, PdfObject valueObject, boolean required) { if (valueObject == null) { @@ -360,13 +377,13 @@ void addAttribute(String name, PdfObject valueObject, boolean required) { } String valueString = null; if (valueObject.getType() == PdfObject.BOOLEAN) { - valueString = ((PdfBoolean)(valueObject)).getValue() ? "yes" : "no"; + valueString = ((PdfBoolean) (valueObject)).getValue() ? "yes" : "no"; } else if (valueObject.getType() == PdfObject.NAME) { - valueString = ((PdfName)(valueObject)).getValue(); + valueString = ((PdfName) (valueObject)).getValue(); } else if (valueObject.getType() == PdfObject.NUMBER) { - valueString = XfdfObjectUtils.convertFloatToString((float)((PdfNumber)(valueObject)).getValue()); + valueString = XfdfObjectUtils.convertFloatToString((float) ((PdfNumber) (valueObject)).getValue()); } else if (valueObject.getType() == PdfObject.STRING) { - valueString = ((PdfString)(valueObject)).getValue(); + valueString = ((PdfString) (valueObject)).getValue(); } attributes.add(new AttributeObject(name, valueString)); @@ -398,7 +415,8 @@ public DestObject getDestination() { * Corresponds to the Dest key in link annotation dictionary. * * @param destination {@link DestObject destination object}, an inner element of annotation object - * @return current {@link AnnotObject annotation object} + * + * @return current {@link AnnotObject annotation object}. */ public AnnotObject setDestination(DestObject destination) { this.destination = destination; @@ -411,7 +429,7 @@ public AnnotObject setDestination(DestObject destination) { * Content model: Base64 encoded string. * For more details see paragraph 6.5.1 in Xfdf document specification. * - * @return {@link String} value of inner appearance element + * @return {@link String} value of inner appearance element. */ public String getAppearance() { return appearance; @@ -423,7 +441,8 @@ public String getAppearance() { * Content model: Base64 encoded string. * * @param appearance {@link String} value of inner appearance element of annotation object - * @return current {@link AnnotObject annotation object} + * + * @return current {@link AnnotObject annotation object}. */ public AnnotObject setAppearance(String appearance) { this.appearance = appearance; @@ -436,7 +455,7 @@ public AnnotObject setAppearance(String appearance) { * Content model: text string. * For more details see paragraph 6.5.7 in Xfdf document specification. * - * @return {@link String} value of inner deafultappearance element + * @return {@link String} value of inner default appearance element. */ public String getDefaultAppearance() { return defaultAppearance; @@ -448,7 +467,8 @@ public String getDefaultAppearance() { * Content model: text string. * * @param defaultAppearance {@link String} value of inner defaultappearance element of annotation object - * @return current {@link AnnotObject annotation object} + * + * @return current {@link AnnotObject annotation object}. */ public AnnotObject setDefaultAppearance(String defaultAppearance) { this.defaultAppearance = defaultAppearance; @@ -461,7 +481,7 @@ public AnnotObject setDefaultAppearance(String defaultAppearance) { * Content model : a text string. * For more details see paragraph 6.5.9 in Xfdf document specification. * - * @return {@link String} value of inner defaultstyle element + * @return {@link String} value of inner defaultstyle element. */ public String getDefaultStyle() { return defaultStyle; @@ -473,7 +493,8 @@ public String getDefaultStyle() { * Content model : a text string. * * @param defaultStyle {@link String} value of inner defaultstyle element of annotation object - * @return current {@link AnnotObject annotation object} + * + * @return current {@link AnnotObject annotation object}. */ public AnnotObject setDefaultStyle(String defaultStyle) { this.defaultStyle = defaultStyle; @@ -485,7 +506,7 @@ public AnnotObject setDefaultStyle(String defaultStyle) { * Corresponds to the Border key in the common annotation dictionary. * For more details see paragraph 6.5.3 in Xfdf document specification. * - * @return inner {@link BorderStyleAltObject BorderStyleAlt object} + * @return inner {@link BorderStyleAltObject BorderStyleAlt object}. */ public BorderStyleAltObject getBorderStyleAlt() { return borderStyleAlt; @@ -496,7 +517,8 @@ public BorderStyleAltObject getBorderStyleAlt() { * Corresponds to the Border key in the common annotation dictionary. * * @param borderStyleAlt inner {@link BorderStyleAltObject BorderStyleAlt object} - * @return current {@link AnnotObject annotation object} + * + * @return current {@link AnnotObject annotation object}. */ public AnnotObject setBorderStyleAlt(BorderStyleAltObject borderStyleAlt) { this.borderStyleAlt = borderStyleAlt; @@ -508,7 +530,7 @@ public AnnotObject setBorderStyleAlt(BorderStyleAltObject borderStyleAlt) { * Corresponds to the Vertices key in the polygon or polyline annotation dictionary. * For more details see paragraph 6.5.31 in Xfdf document specification. * - * @return {@link String} value of inner vertices element + * @return {@link String} value of inner vertices element. */ public String getVertices() { return vertices; @@ -519,7 +541,8 @@ public String getVertices() { * Corresponds to the Vertices key in the polygon or polyline annotation dictionary. * * @param vertices {@link String} value of inner vertices element - * @return current {@link AnnotObject annotation object} + * + * @return current {@link AnnotObject annotation object}. */ public AnnotObject setVertices(String vertices) { this.vertices = vertices; @@ -528,6 +551,7 @@ public AnnotObject setVertices(String vertices) { /** * Gets the reference to the source {@link PdfAnnotation}. Used for attaching popups in case of reading data from pdf file. + * * @return an {@link PdfIndirectReference} of the source annotation object. */ public PdfIndirectReference getRef() { @@ -536,7 +560,9 @@ public PdfIndirectReference getRef() { /** * Sets the reference to the source {@link PdfAnnotation}. Used for attaching popups in case of reading data from pdf file. - * @param ref {@link PdfIndirectReference} of the source annotation object. + * + * @param ref {@link PdfIndirectReference} of the source annotation object + * * @return this {@link AnnotObject} instance. */ public AnnotObject setRef(PdfIndirectReference ref) { diff --git a/forms/src/main/java/com/itextpdf/forms/xfdf/DestObject.java b/forms/src/main/java/com/itextpdf/forms/xfdf/DestObject.java index 5a133d8db1..8f871c0ec7 100644 --- a/forms/src/main/java/com/itextpdf/forms/xfdf/DestObject.java +++ b/forms/src/main/java/com/itextpdf/forms/xfdf/DestObject.java @@ -102,8 +102,11 @@ public class DestObject { */ private FitObject fitBV; + /** + * Creates an instance of {@link DestObject}. + */ public DestObject() { - //create empty DestObject + // Create an empty DestObject. } /** @@ -121,7 +124,8 @@ public String getName() { * Sets the Name attribute of Named element, a child of Dest element. * Allows a destination to be referred to indirectly by means of a name object or a byte string. * - * @param name string value of the Name attribute. + * @param name string value of the Name attribute + * * @return this {@link DestObject} instance. */ public DestObject setName(String name) { @@ -146,7 +150,8 @@ public FitObject getXyz() { * Corresponds to the XYZ key in the destination syntax. * Required attributes: Page, Left, Bottom, Right, Top. * - * @param xyz a {@link FitObject} that represents XYZ of Dest element. + * @param xyz a {@link FitObject} that represents XYZ of Dest element + * * @return this {@link DestObject} instance. */ public DestObject setXyz(FitObject xyz) { @@ -171,7 +176,8 @@ public FitObject getFit() { * Corresponds to the Fit key in the destination syntax. * Required attributes: Page. * - * @param fit a {@link FitObject} that represents Fit of Dest element. + * @param fit a {@link FitObject} that represents Fit of Dest element + * * @return this {@link DestObject} instance. */ public DestObject setFit(FitObject fit) { @@ -196,7 +202,8 @@ public FitObject getFitH() { * Corresponds to the FitH key in the destination syntax. * Required attributes: Page, Top. * - * @param fitH a {@link FitObject} that represents FitH of Dest element. + * @param fitH a {@link FitObject} that represents FitH of Dest element + * * @return this {@link DestObject} instance. */ public DestObject setFitH(FitObject fitH) { @@ -221,7 +228,8 @@ public FitObject getFitV() { * Corresponds to the FitV key in the destination syntax. * Required attributes: Page, Left. * - * @param fitV a {@link FitObject} that represents FitV of Dest element. + * @param fitV a {@link FitObject} that represents FitV of Dest element + * * @return this {@link DestObject} instance. */ public DestObject setFitV(FitObject fitV) { @@ -246,7 +254,8 @@ public FitObject getFitR() { * Corresponds to the FitR key in the destination syntax. * Required attributes: Page, Left, Bottom, Right, Top. * - * @param fitR a {@link FitObject} that represents FitR of Dest element. + * @param fitR a {@link FitObject} that represents FitR of Dest element + * * @return this {@link DestObject} instance. */ public DestObject setFitR(FitObject fitR) { @@ -272,7 +281,8 @@ public FitObject getFitB() { * Required attributes: Page. * For more details see paragraph 6.5.14 in XFDF document specification. * - * @param fitB a {@link FitObject} that represents FitB of Dest element. + * @param fitB a {@link FitObject} that represents FitB of Dest element + * * @return this {@link DestObject} instance. */ public DestObject setFitB(FitObject fitB) { @@ -297,7 +307,8 @@ public FitObject getFitBH() { * Corresponds to the FitBH key in the destination syntax. * Required attributes: Page, Top. * - * @param fitBH a {@link FitObject} that represents FitBH of Dest element. + * @param fitBH a {@link FitObject} that represents FitBH of Dest element + * * @return this {@link DestObject} instance. */ public DestObject setFitBH(FitObject fitBH) { @@ -322,7 +333,8 @@ public FitObject getFitBV() { * Corresponds to the FitBV key in the destination syntax. * Required attributes: Page, Left. * - * @param fitBV a {@link FitObject} that represents FitBV of Dest element. + * @param fitBV a {@link FitObject} that represents FitBV of Dest element + * * @return this {@link DestObject} instance. */ public DestObject setFitBV(FitObject fitBV) { diff --git a/forms/src/main/java/com/itextpdf/forms/xfdf/FObject.java b/forms/src/main/java/com/itextpdf/forms/xfdf/FObject.java index 69415daa88..a6a7ee9db1 100644 --- a/forms/src/main/java/com/itextpdf/forms/xfdf/FObject.java +++ b/forms/src/main/java/com/itextpdf/forms/xfdf/FObject.java @@ -37,6 +37,11 @@ public class FObject { */ private String href; + /** + * Creates an instance of {@link FObject}. + * + * @param href the name of the source or target file + */ public FObject(String href) { this.href = href; } @@ -44,7 +49,7 @@ public FObject(String href) { /** * Gets the name of the source or target file. * - * @return the name of the source or target file + * @return the name of the source or target file. */ public String getHref() { return href; @@ -54,7 +59,8 @@ public String getHref() { * Sets the name of the source or target file. * * @param href the name of the source or target file - * @return current {@link FObject f object} + * + * @return current {@link FObject f object}. */ public FObject setHref(String href) { this.href = href; diff --git a/forms/src/main/java/com/itextpdf/forms/xfdf/FieldObject.java b/forms/src/main/java/com/itextpdf/forms/xfdf/FieldObject.java index 3bf3c550eb..f8fd7b45e2 100644 --- a/forms/src/main/java/com/itextpdf/forms/xfdf/FieldObject.java +++ b/forms/src/main/java/com/itextpdf/forms/xfdf/FieldObject.java @@ -22,7 +22,6 @@ This file is part of the iText (R) project. */ package com.itextpdf.forms.xfdf; - /** * Represents the field element, a child of the fields and field elements. * The field element corresponds to a form field. @@ -69,13 +68,23 @@ public class FieldObject { */ private FieldObject parent; + /** + * Creates an instance of {@link FieldObject}. + */ public FieldObject() { } + /** + * Creates an instance of {@link FieldObject}. + * + * @param name the name attribute of the field element + * @param value the field's value + * @param containsRichText indicates if a value-richtext element is present inside the field + */ public FieldObject(String name, String value, boolean containsRichText) { this.name = name; this.containsRichText = containsRichText; - if(containsRichText) { + if (containsRichText) { this.richTextValue = value; } else { this.value = value; @@ -88,7 +97,7 @@ public FieldObject(String name, String value, boolean containsRichText) { * In a hierarchical form field, the name is the partial field name. * For more details see paragraph 6.3.2.2 in XFDF document specification. * - * @return {@link String} value of field name attribute + * @return {@link String} value of field name attribute. */ public String getName() { return name; @@ -111,7 +120,7 @@ public void setName(String name) { * Corresponds to the V key in the FDF field dictionary. * For more details see paragraph 6.3.3 in XFDF document specification. * - * @return {@link String} representation of inner value element of the field + * @return {@link String} representation of inner value element of the field. */ public String getValue() { return value; @@ -135,7 +144,7 @@ public void setValue(String value) { * Content model: text strign or rich text string. * For more details see paragraph 6.3.4 in XFDF document specification. * - * @return {@link String} representation of inner value-richtext element of the field + * @return {@link String} representation of inner value-richtext element of the field. */ public String getRichTextValue() { return richTextValue; @@ -156,7 +165,7 @@ public void setRichTextValue(String richTextValue) { /** * Gets a boolean indicating if a value-richtext element is present inside the field. * - * @return true if a value-richtext element is present inside the field, false otherwise + * @return true if a value-richtext element is present inside the field, false otherwise. */ public boolean isContainsRichText() { return containsRichText; @@ -174,7 +183,7 @@ public void setContainsRichText(boolean containsRichText) { /** * Gets a parent field of current field. * - * @return parent {@link FieldObject field object} of the current field + * @return parent {@link FieldObject field object} of the current field. */ public FieldObject getParent() { return parent; diff --git a/forms/src/main/java/com/itextpdf/forms/xfdf/FieldsObject.java b/forms/src/main/java/com/itextpdf/forms/xfdf/FieldsObject.java index 1425faab98..40087a8f3f 100644 --- a/forms/src/main/java/com/itextpdf/forms/xfdf/FieldsObject.java +++ b/forms/src/main/java/com/itextpdf/forms/xfdf/FieldsObject.java @@ -38,6 +38,9 @@ public class FieldsObject { */ private List fieldList; + /** + * Creates an instance of {@link FieldsObject}. + */ public FieldsObject() { this.fieldList = new ArrayList<>(); } @@ -53,7 +56,9 @@ public List getFieldList() { /** * Adds a new field to the list. - * @param field FieldObject containing the info about the form field. + * + * @param field FieldObject containing the info about the form field + * * @return current {@link FieldsObject fields object} */ public FieldsObject addField(FieldObject field) { diff --git a/forms/src/main/java/com/itextpdf/forms/xfdf/FitObject.java b/forms/src/main/java/com/itextpdf/forms/xfdf/FitObject.java index 75b3f6343d..56783b4e30 100644 --- a/forms/src/main/java/com/itextpdf/forms/xfdf/FitObject.java +++ b/forms/src/main/java/com/itextpdf/forms/xfdf/FitObject.java @@ -37,7 +37,7 @@ public class FitObject { * Represents the page displayed by current Fit element. * Attribute of Fit, FitB, FitBH, FitBV, FitH, FitR, FitV, XYZ elements. */ - private PdfObject page; + private final PdfObject page; /** * Vertical coordinate positioned at the top edge of the window. @@ -65,8 +65,13 @@ public class FitObject { */ private float zoom; + /** + * Creates an instance of {@link FitObject}. + * + * @param page the page displayed by current Fit element + */ public FitObject(PdfObject page) { - if(page == null) { + if (page == null) { throw new XfdfException(XfdfException.PAGE_IS_MISSING); } this.page = page; @@ -76,7 +81,7 @@ public FitObject(PdfObject page) { * Gets the PdfObject representing the page displayed by current Fit element. * Attribute of Fit, FitB, FitBH, FitBV, FitH, FitR, FitV, XYZ elements. * - * @return {@link PdfObject page} of the current Fit element + * @return {@link PdfObject page} of the current Fit element. */ public PdfObject getPage() { return page; @@ -85,7 +90,7 @@ public PdfObject getPage() { /** * Gets a float vertical coordinate positioned at the top edge of the window. * - * @return top vertical coordinate + * @return top vertical coordinate. */ public float getTop() { return top; @@ -93,8 +98,10 @@ public float getTop() { /** * Sets a float vertical coordinate positioned at the top edge of the window. + * * @param top vertical coordinate value - * @return current {@link FitObject fit object} + * + * @return current {@link FitObject fit object}. */ public FitObject setTop(float top) { this.top = top; @@ -104,7 +111,7 @@ public FitObject setTop(float top) { /** * Gets a float horizontal coordinate positioned at the left edge of the window. * - * @return left horizontal coordinate + * @return left horizontal coordinate. */ public float getLeft() { return left; @@ -112,8 +119,10 @@ public float getLeft() { /** * Sets a float horizontal coordinate positioned at the left edge of the window. + * * @param left horizontal coordinate value - * @return current {@link FitObject fit object} + * + * @return current {@link FitObject fit object}. */ public FitObject setLeft(float left) { this.left = left; @@ -123,7 +132,7 @@ public FitObject setLeft(float left) { /** * Gets a float vertical coordinate positioned at the bottom edge of the window. * - * @return bottom vertical coordinate + * @return bottom vertical coordinate. */ public float getBottom() { return bottom; @@ -133,7 +142,8 @@ public float getBottom() { * Sets a float vertical coordinate positioned at the bottom edge of the window. * * @param bottom vertical coordinate value - * @return current {@link FitObject fit object} + * + * @return current {@link FitObject fit object}. */ public FitObject setBottom(float bottom) { this.bottom = bottom; @@ -143,7 +153,7 @@ public FitObject setBottom(float bottom) { /** * Gets a float horizontal coordinate positioned at the right edge of the window. * - * @return right horizontal coordinate + * @return right horizontal coordinate. */ public float getRight() { return right; @@ -153,7 +163,8 @@ public float getRight() { * Sets a float horizontal coordinate positioned at the right edge of the window. * * @param right horizontal coordinate - * @return current {@link FitObject fit object} + * + * @return current {@link FitObject fit object}. */ public FitObject setRight(float right) { this.right = right; @@ -164,7 +175,7 @@ public FitObject setRight(float right) { * Gets a float representing the zoom ratio. * Attribute of XYZ object. * - * @return zoom ratio value + * @return zoom ratio value. */ public float getZoom() { return zoom; @@ -175,7 +186,8 @@ public float getZoom() { * Attribute of XYZ object. * * @param zoom ratio value - * @return current {@link FitObject fit object} + * + * @return current {@link FitObject fit object}. */ public FitObject setZoom(float zoom) { this.zoom = zoom; diff --git a/forms/src/main/java/com/itextpdf/forms/xfdf/IdsObject.java b/forms/src/main/java/com/itextpdf/forms/xfdf/IdsObject.java index fa5db83cb8..b7042ac5ec 100644 --- a/forms/src/main/java/com/itextpdf/forms/xfdf/IdsObject.java +++ b/forms/src/main/java/com/itextpdf/forms/xfdf/IdsObject.java @@ -51,6 +51,9 @@ public class IdsObject { */ private String modified; + /** + * Creates an instance of {@link IdsObject}. + */ public IdsObject() { } @@ -60,7 +63,7 @@ public IdsObject() { * This value does not change when the file is incrementally updated. * The value shall be a hexadecimal number. * - * @return the permanent identifier value + * @return the permanent identifier value. */ public String getOriginal() { return original; @@ -74,7 +77,8 @@ public String getOriginal() { * A common value for this is an MD5 checksum. * * @param original the permanent identifier value - * @return current {@link IdsObject ids object} + * + * @return current {@link IdsObject ids object}. */ public IdsObject setOriginal(String original) { this.original = original; @@ -88,7 +92,7 @@ public IdsObject setOriginal(String original) { * on the file's contents at the time it was last updated. * The value shall be a hexadecimal number. * - * @return the unique identifier value + * @return the unique identifier value. */ public String getModified() { return modified; @@ -103,7 +107,8 @@ public String getModified() { * A common value for this is an MD5 checksum. * * @param modified the unique identifier value - * @return current {@link IdsObject ids object} + * + * @return current {@link IdsObject ids object}. */ public IdsObject setModified(String modified) { this.modified = modified; diff --git a/forms/src/main/java/com/itextpdf/forms/xfdf/XfdfObjectFactory.java b/forms/src/main/java/com/itextpdf/forms/xfdf/XfdfObjectFactory.java index f6312d0076..4bb1c3667e 100644 --- a/forms/src/main/java/com/itextpdf/forms/xfdf/XfdfObjectFactory.java +++ b/forms/src/main/java/com/itextpdf/forms/xfdf/XfdfObjectFactory.java @@ -61,15 +61,19 @@ This file is part of the iText (R) project. import java.util.List; import java.util.StringTokenizer; +/** + * A factory for creating {@link XfdfObject} objects. + */ public class XfdfObjectFactory { private static final Logger logger = LoggerFactory.getLogger(XfdfObjectFactory.class); /** * Extracts data from pdf document acroform and annotations into XfdfObject. - * * - * @param document Pdf document for data extraction. - * @param filename The name od pdf document for data extraction. + * + * @param document Pdf document for data extraction + * @param filename The name od pdf document for data extraction + * * @return XfdfObject containing data from pdf forms and annotations. */ public XfdfObject createXfdfObject(PdfDocument document, String filename) { @@ -83,13 +87,13 @@ public XfdfObject createXfdfObject(PdfDocument document, String filename) { String delims = "."; StringTokenizer st = new StringTokenizer(fieldName, delims); List nameParts = new ArrayList<>(); - while(st.hasMoreTokens()) { + while (st.hasMoreTokens()) { nameParts.add(st.nextToken()); } String name = nameParts.get(nameParts.size() - 1); String value = form.getField(fieldName).getValueAsString(); FieldObject childField = new FieldObject(name, value, false); - if(nameParts.size() > 1) { + if (nameParts.size() > 1) { FieldObject parentField = new FieldObject(); parentField.setName(nameParts.get(nameParts.size() - 2)); childField.setParent(parentField); @@ -117,15 +121,17 @@ public XfdfObject createXfdfObject(PdfDocument document, String filename) { } /** - * Extracts data from input stream into XfdfObject. Typically input stream is based on .xfdf file + * Extracts data from input stream into XfdfObject. Typically input stream is based on .xfdf file. + * + * @param xfdfInputStream the input stream containing xml-styled xfdf data * - * @param xfdfInputStream The input stream containing xml-styled xfdf data. * @return XfdfObject containing original xfdf data. + * * @throws ParserConfigurationException if a XfdfObject cannot be created which satisfies the configuration * requested. * @throws SAXException if any parse errors occurs. */ - public XfdfObject createXfdfObject(InputStream xfdfInputStream) throws ParserConfigurationException, SAXException { + public XfdfObject createXfdfObject(InputStream xfdfInputStream) throws ParserConfigurationException, SAXException { XfdfObject xfdfObject = new XfdfObject(); Document document = XfdfFileUtils.createXfdfDocumentFromStream(xfdfInputStream); @@ -198,18 +204,18 @@ private void visitChildNodes(NodeList nList, XfdfObject xfdfObject) { } private static boolean isAnnotSupported(String nodeName) { - return XfdfConstants.TEXT.equalsIgnoreCase(nodeName) || - XfdfConstants.HIGHLIGHT.equalsIgnoreCase(nodeName) || - XfdfConstants.UNDERLINE.equalsIgnoreCase(nodeName) || - XfdfConstants.STRIKEOUT.equalsIgnoreCase(nodeName) || - XfdfConstants.SQUIGGLY.equalsIgnoreCase(nodeName) || - XfdfConstants.CIRCLE.equalsIgnoreCase(nodeName) || - XfdfConstants.SQUARE.equalsIgnoreCase(nodeName) || - XfdfConstants.POLYLINE.equalsIgnoreCase(nodeName) || - XfdfConstants.POLYGON.equalsIgnoreCase(nodeName) || - XfdfConstants.STAMP.equalsIgnoreCase(nodeName) || + return XfdfConstants.TEXT.equalsIgnoreCase(nodeName) || + XfdfConstants.HIGHLIGHT.equalsIgnoreCase(nodeName) || + XfdfConstants.UNDERLINE.equalsIgnoreCase(nodeName) || + XfdfConstants.STRIKEOUT.equalsIgnoreCase(nodeName) || + XfdfConstants.SQUIGGLY.equalsIgnoreCase(nodeName) || + XfdfConstants.CIRCLE.equalsIgnoreCase(nodeName) || + XfdfConstants.SQUARE.equalsIgnoreCase(nodeName) || + XfdfConstants.POLYLINE.equalsIgnoreCase(nodeName) || + XfdfConstants.POLYGON.equalsIgnoreCase(nodeName) || + XfdfConstants.STAMP.equalsIgnoreCase(nodeName) || // XfdfConstants.FREETEXT.equalsIgnoreCase(nodeName) || - XfdfConstants.LINE.equalsIgnoreCase(nodeName); + XfdfConstants.LINE.equalsIgnoreCase(nodeName); } private void readAnnotsList(Node node, AnnotsObject annotsObject) { @@ -220,7 +226,7 @@ private void readAnnotsList(Node node, AnnotsObject annotsObject) { if (currentNode.getNodeType() == Node.ELEMENT_NODE && isAnnotationSubtype(currentNode.getNodeName()) && isAnnotSupported(currentNode.getNodeName())) { - visitAnnotationNode(currentNode, annotsObject); + visitAnnotationNode(currentNode, annotsObject); } } } @@ -348,7 +354,8 @@ private void addAnnotObjectAttribute(AnnotObject annotObject, Node attributeNode case XfdfConstants.INTENSITY: annotObject.addAttribute(new AttributeObject(attributeName, attributeNode.getNodeValue())); break; - default: logger.warn(IoLogMessageConstant.XFDF_UNSUPPORTED_ANNOTATION_ATTRIBUTE); + default: + logger.warn(IoLogMessageConstant.XFDF_UNSUPPORTED_ANNOTATION_ATTRIBUTE); break; } } @@ -443,18 +450,18 @@ private List readXfdfRootAttributes(Element root) { } private static void addPopup(PdfAnnotation pdfAnnot, AnnotsObject annots, int pageNumber) { - if(((PdfPopupAnnotation)pdfAnnot).getParentObject() != null) { - PdfAnnotation parentAnnotation = ((PdfPopupAnnotation)pdfAnnot).getParent(); + if (((PdfPopupAnnotation) pdfAnnot).getParentObject() != null) { + PdfAnnotation parentAnnotation = ((PdfPopupAnnotation) pdfAnnot).getParent(); PdfIndirectReference parentRef = parentAnnotation.getPdfObject().getIndirectReference(); boolean hasParentAnnot = false; - for(AnnotObject annot: annots.getAnnotsList()) { - if(parentRef.equals(annot.getRef())) { + for (AnnotObject annot : annots.getAnnotsList()) { + if (parentRef.equals(annot.getRef())) { hasParentAnnot = true; annot.setHasPopup(true); annot.setPopup(createXfdfAnnotation(pdfAnnot, pageNumber)); } } - if(!hasParentAnnot) { + if (!hasParentAnnot) { AnnotObject parentAnnot = new AnnotObject(); parentAnnot.setRef(parentRef); parentAnnot.addFdfAttributes(pageNumber); @@ -469,13 +476,13 @@ private static void addPopup(PdfAnnotation pdfAnnot, AnnotsObject annots, int pa private static void addAnnotation(PdfAnnotation pdfAnnot, AnnotsObject annots, int pageNumber) { boolean hasCorrecpondingAnnotObject = false; - for(AnnotObject annot : annots.getAnnotsList()) { - if(pdfAnnot.getPdfObject().getIndirectReference().equals(annot.getRef())) { + for (AnnotObject annot : annots.getAnnotsList()) { + if (pdfAnnot.getPdfObject().getIndirectReference().equals(annot.getRef())) { hasCorrecpondingAnnotObject = true; updateXfdfAnnotation(annot, pdfAnnot, pageNumber); } } - if(!hasCorrecpondingAnnotObject) { + if (!hasCorrecpondingAnnotObject) { annots.addAnnot(createXfdfAnnotation(pdfAnnot, pageNumber)); } } @@ -487,10 +494,10 @@ private static void addAnnotations(PdfDocument pdfDoc, XfdfObject resultXfdf) { PdfPage page = pdfDoc.getPage(i); List pdfAnnots = page.getAnnotations(); for (PdfAnnotation pdfAnnot : pdfAnnots) { - if(pdfAnnot.getSubtype() == PdfName.Popup) { + if (pdfAnnot.getSubtype() == PdfName.Popup) { addPopup(pdfAnnot, annots, i); } else { - addAnnotation(pdfAnnot, annots, i); + addAnnotation(pdfAnnot, annots, i); } } } @@ -498,7 +505,7 @@ private static void addAnnotations(PdfDocument pdfDoc, XfdfObject resultXfdf) { } private static void updateXfdfAnnotation(AnnotObject annotObject, PdfAnnotation pdfAnnotation, int pageNumber) { - //TODO DEVSIX-4132 implement update, refactor createXfdfAnnotation() method to accommodate the change + // TODO DEVSIX-4132 implement update, refactor createXfdfAnnotation() method to accommodate the change. } private static void addCommonAnnotationAttributes(AnnotObject annot, PdfAnnotation pdfAnnotation) { @@ -509,7 +516,7 @@ private static void addCommonAnnotationAttributes(AnnotObject annot, PdfAnnotati } annot.addAttribute(XfdfConstants.DATE, pdfAnnotation.getDate()); String flagsString = XfdfObjectUtils.convertFlagsToString(pdfAnnotation); - if(flagsString != null) { + if (flagsString != null) { annot.addAttribute(new AttributeObject(XfdfConstants.FLAGS, flagsString)); } @@ -526,7 +533,7 @@ private static void addMarkupAnnotationAttributes(AnnotObject annot, PdfMarkupAn } private static void addBorderStyleAttributes(AnnotObject annotObject, PdfNumber width, - PdfArray dashes, PdfName style) { + PdfArray dashes, PdfName style) { annotObject.addAttribute(XfdfConstants.WIDTH, width); annotObject.addAttribute(XfdfConstants.DASHES, XfdfObjectUtils.convertDashesFromArray(dashes)); annotObject.addAttribute(XfdfConstants.STYLE, XfdfObjectUtils.getStyleFullValue(style)); @@ -596,7 +603,7 @@ private static void createCircleAnnotation(PdfAnnotation pdfAnnotation, AnnotObj annot.addAttribute(new AttributeObject(XfdfConstants.INTERIOR_COLOR, XfdfObjectUtils.convertColorToString(pdfCircleAnnotation.getInteriorColor().getColorValue()))); } - if(pdfCircleAnnotation.getRectangleDifferences() != null) { + if (pdfCircleAnnotation.getRectangleDifferences() != null) { annot.addAttribute(new AttributeObject(XfdfConstants.FRINGE, XfdfObjectUtils.convertFringeToString( pdfCircleAnnotation.getRectangleDifferences().toFloatArray()))); } @@ -627,7 +634,7 @@ private static void createSquareAnnotation(PdfAnnotation pdfAnnotation, AnnotObj if (pdfSquareAnnotation.getInteriorColor() != null && pdfSquareAnnotation.getInteriorColor().getColorValue() != null) { annot.addAttribute(new AttributeObject(XfdfConstants.INTERIOR_COLOR, XfdfObjectUtils.convertColorToString(pdfSquareAnnotation.getInteriorColor().getColorValue()))); } - if(pdfSquareAnnotation.getRectangleDifferences() != null) { + if (pdfSquareAnnotation.getRectangleDifferences() != null) { annot.addAttribute(new AttributeObject(XfdfConstants.FRINGE, XfdfObjectUtils.convertFringeToString( pdfSquareAnnotation.getRectangleDifferences().toFloatArray()))); } @@ -765,28 +772,28 @@ private static void createLinkAnnotation(PdfAnnotation pdfAnnotation, AnnotObjec //in iText pdfLinkAnnotation doesn't have a popup sub-element PdfDictionary action = pdfLinkAnnotation.getAction(); - if(pdfLinkAnnotation.getAction() != null) { + if (pdfLinkAnnotation.getAction() != null) { PdfName type = action.getAsName(PdfName.S); ActionObject actionObject = new ActionObject(type); - if(PdfName.URI.equals(type)) { + if (PdfName.URI.equals(type)) { actionObject.setUri(action.getAsString(PdfName.URI)); - if(action.get(PdfName.IsMap) != null) { + if (action.get(PdfName.IsMap) != null) { actionObject.setMap((boolean) action.getAsBool(PdfName.IsMap)); } } annot.setAction(actionObject); } - PdfArray dest = (PdfArray) pdfLinkAnnotation.getDestinationObject(); + PdfArray dest = (PdfArray) pdfLinkAnnotation.getDestinationObject(); if (dest != null) { createDestElement(dest, annot); } PdfArray border = pdfLinkAnnotation.getBorder(); - if(border != null) { + if (border != null) { BorderStyleAltObject borderStyleAltObject = new BorderStyleAltObject(border.getAsNumber(0).floatValue(), - border.getAsNumber(1).floatValue(),border.getAsNumber(2).floatValue()); + border.getAsNumber(1).floatValue(), border.getAsNumber(2).floatValue()); annot.setBorderStyleAlt(borderStyleAltObject); } } @@ -794,38 +801,38 @@ private static void createLinkAnnotation(PdfAnnotation pdfAnnotation, AnnotObjec private static void createDestElement(PdfArray dest, AnnotObject annot) { DestObject destObject = new DestObject(); PdfName type = dest.getAsName(1); - if(PdfName.XYZ.equals(type)) { + if (PdfName.XYZ.equals(type)) { FitObject xyz = new FitObject(dest.get(0)); xyz.setLeft(dest.getAsNumber(2).floatValue()) .setTop(dest.getAsNumber(3).floatValue()) .setZoom(dest.getAsNumber(4).floatValue()); destObject.setXyz(xyz); - } else if(PdfName.Fit.equals(type)) { + } else if (PdfName.Fit.equals(type)) { FitObject fit = new FitObject(dest.get(0)); destObject.setFit(fit); - } else if(PdfName.FitB.equals(type)) { + } else if (PdfName.FitB.equals(type)) { FitObject fitB = new FitObject(dest.get(0)); destObject.setFitB(fitB); - } else if(PdfName.FitR.equals(type)) { + } else if (PdfName.FitR.equals(type)) { FitObject fitR = new FitObject(dest.get(0)); fitR.setLeft(dest.getAsNumber(2).floatValue()); fitR.setBottom(dest.getAsNumber(3).floatValue()); fitR.setRight(dest.getAsNumber(4).floatValue()); fitR.setTop(dest.getAsNumber(5).floatValue()); destObject.setFitR(fitR); - } else if(PdfName.FitH.equals(type)) { + } else if (PdfName.FitH.equals(type)) { FitObject fitH = new FitObject(dest.get(0)); fitH.setTop(dest.getAsNumber(2).floatValue()); destObject.setFitH(fitH); - } else if(PdfName.FitBH.equals(type)) { + } else if (PdfName.FitBH.equals(type)) { FitObject fitBH = new FitObject(dest.get(0)); fitBH.setTop(dest.getAsNumber(2).floatValue()); destObject.setFitBH(fitBH); - } else if(PdfName.FitBV.equals(type)) { + } else if (PdfName.FitBV.equals(type)) { FitObject fitBV = new FitObject(dest.get(0)); fitBV.setLeft(dest.getAsNumber(2).floatValue()); destObject.setFitBV(fitBV); - } else if(PdfName.FitV.equals(type)) { + } else if (PdfName.FitV.equals(type)) { FitObject fitV = new FitObject(dest.get(0)); fitV.setLeft(dest.getAsNumber(2).floatValue()); destObject.setFitV(fitV); @@ -898,13 +905,13 @@ private static AnnotObject createXfdfAnnotation(PdfAnnotation pdfAnnotation, int createCircleAnnotation(pdfAnnotation, annot, pageNumber); } if (pdfAnnotation instanceof PdfSquareAnnotation) { - createSquareAnnotation(pdfAnnotation, annot, pageNumber); + createSquareAnnotation(pdfAnnotation, annot, pageNumber); } if (pdfAnnotation instanceof PdfStampAnnotation) { - createStampAnnotation(pdfAnnotation, annot, pageNumber); + createStampAnnotation(pdfAnnotation, annot, pageNumber); } if (pdfAnnotation instanceof PdfFreeTextAnnotation) { - createFreeTextAnnotation(pdfAnnotation, annot); + createFreeTextAnnotation(pdfAnnotation, annot); } if (pdfAnnotation instanceof PdfLineAnnotation) { createLineAnnotation(pdfAnnotation, annot, pageNumber); @@ -916,7 +923,7 @@ private static AnnotObject createXfdfAnnotation(PdfAnnotation pdfAnnotation, int createLinkAnnotation(pdfAnnotation, annot); } - if (isSupportedAnnotation(pdfAnnotation)){ + if (isSupportedAnnotation(pdfAnnotation)) { addCommonAnnotationAttributes(annot, pdfAnnotation); if (pdfAnnotation instanceof PdfMarkupAnnotation) { addMarkupAnnotationAttributes(annot, (PdfMarkupAnnotation) pdfAnnotation); @@ -937,7 +944,7 @@ private static AnnotObject convertPdfPopupToAnnotObject(PdfPopupAnnotation pdfPo } private static boolean isSupportedAnnotation(PdfAnnotation pdfAnnotation) { - return pdfAnnotation instanceof PdfTextMarkupAnnotation || + return pdfAnnotation instanceof PdfTextMarkupAnnotation || pdfAnnotation instanceof PdfTextAnnotation || pdfAnnotation instanceof PdfCircleAnnotation || pdfAnnotation instanceof PdfSquareAnnotation || diff --git a/forms/src/main/resources/META-INF/native-image/reflect-config.json b/forms/src/main/resources/META-INF/native-image/reflect-config.json new file mode 100644 index 0000000000..4590e4bd5e --- /dev/null +++ b/forms/src/main/resources/META-INF/native-image/reflect-config.json @@ -0,0 +1,6 @@ +[{ + "condition": { + "typeReachable": "com.itextpdf.forms.fields.merging.OnDuplicateFormFieldNameStrategy" + }, + "name":"com.itextpdf.forms.util.RegisterDefaultDiContainer" +}] diff --git a/forms/src/test/java/com/itextpdf/forms/PdfChoiceFieldTest.java b/forms/src/test/java/com/itextpdf/forms/PdfChoiceFieldTest.java index 5e142077ec..494a6e09ed 100644 --- a/forms/src/test/java/com/itextpdf/forms/PdfChoiceFieldTest.java +++ b/forms/src/test/java/com/itextpdf/forms/PdfChoiceFieldTest.java @@ -78,7 +78,7 @@ public void choiceFieldsWithUnicodeTest() throws IOException, InterruptedExcepti // 规 PdfFormField field = new ChoiceFormFieldBuilder(pdfDoc, "combo1") .setWidgetRectangle(new Rectangle(36, 666, 40, 80)).setOptions(new String[]{"\u89c4", "\u89c9"}) - .setConformanceLevel(null).createComboBox() + .setGenericConformanceLevel(null).createComboBox() .setValue("\u89c4"); field.setFont(font); field.getFirstFormAnnotation().setBorderColor(ColorConstants.BLACK); @@ -87,7 +87,7 @@ public void choiceFieldsWithUnicodeTest() throws IOException, InterruptedExcepti // 觉 field = new ChoiceFormFieldBuilder(pdfDoc, "combo2") .setWidgetRectangle(new Rectangle(136, 666, 40, 80)).setOptions(new String[]{"\u89c4", "\u89c9"}) - .setConformanceLevel(null).createComboBox(); + .setGenericConformanceLevel(null).createComboBox(); field.setValue("\u89c4").setFont(font); field.setValue("\u89c9"); field.getFirstFormAnnotation().setBorderColor(ColorConstants.BLACK); @@ -96,7 +96,7 @@ public void choiceFieldsWithUnicodeTest() throws IOException, InterruptedExcepti // 规 field = new ChoiceFormFieldBuilder(pdfDoc, "list1") .setWidgetRectangle(new Rectangle(236, 666, 50, 80)).setOptions(new String[]{"\u89c4", "\u89c9"}) - .setConformanceLevel(null).createList() + .setGenericConformanceLevel(null).createList() .setValue("\u89c4"); field.setFont(font); field.getFirstFormAnnotation().setBorderColor(ColorConstants.BLACK); @@ -105,7 +105,7 @@ public void choiceFieldsWithUnicodeTest() throws IOException, InterruptedExcepti // 觉 field = new ChoiceFormFieldBuilder(pdfDoc, "list2") .setWidgetRectangle(new Rectangle(336, 666, 50, 80)).setOptions(new String[]{"\u89c4", "\u89c9"}) - .setConformanceLevel(null).createList(); + .setGenericConformanceLevel(null).createList(); field.setValue("\u89c4").setFont(font); field.setValue("\u89c9"); field.getFirstFormAnnotation().setBorderColor(ColorConstants.BLACK); @@ -182,7 +182,7 @@ public void multiSelectByValueTest() throws IOException, InterruptedException { PdfAcroForm form = PdfFormCreator.getAcroForm(document, true); PdfChoiceFormField choice = (PdfChoiceFormField) new ChoiceFormFieldBuilder(document, "choice") .setWidgetRectangle(new Rectangle(336, 666, 50, 80)).setOptions(new String[]{"one", "two", "three", "four"}) - .setConformanceLevel(null).createList() + .setGenericConformanceLevel(null).createList() .setValue("two").setFont(null); choice.getFirstFormAnnotation().setBorderColor(ColorConstants.BLACK); choice.setMultiSelect(true); diff --git a/forms/src/test/java/com/itextpdf/forms/fields/CheckBoxFormFieldBuilderTest.java b/forms/src/test/java/com/itextpdf/forms/fields/CheckBoxFormFieldBuilderTest.java index a78919fc11..f61d5ba2a1 100644 --- a/forms/src/test/java/com/itextpdf/forms/fields/CheckBoxFormFieldBuilderTest.java +++ b/forms/src/test/java/com/itextpdf/forms/fields/CheckBoxFormFieldBuilderTest.java @@ -84,7 +84,7 @@ public void createCheckBoxWithoutWidgetTest() { @Test public void createCheckBoxWithConformanceLevelTest() { PdfButtonFormField checkBoxFormField = new CheckBoxFormFieldBuilder(DUMMY_DOCUMENT, DUMMY_NAME) - .setWidgetRectangle(DUMMY_RECTANGLE).setConformanceLevel(PdfAConformanceLevel.PDF_A_1A) + .setWidgetRectangle(DUMMY_RECTANGLE).setGenericConformanceLevel(PdfAConformanceLevel.PDF_A_1A) .createCheckBox(); compareCheckBoxes(checkBoxFormField, true); diff --git a/forms/src/test/java/com/itextpdf/forms/fields/ChoiceFormFieldBuilderTest.java b/forms/src/test/java/com/itextpdf/forms/fields/ChoiceFormFieldBuilderTest.java index 5801fb32b2..b3392c96ac 100644 --- a/forms/src/test/java/com/itextpdf/forms/fields/ChoiceFormFieldBuilderTest.java +++ b/forms/src/test/java/com/itextpdf/forms/fields/ChoiceFormFieldBuilderTest.java @@ -129,7 +129,7 @@ public void createComboBoxWithoutWidgetTest() { @Test public void createComboBoxWithConformanceLevelTest() { PdfChoiceFormField choiceFormField = new ChoiceFormFieldBuilder(DUMMY_DOCUMENT, DUMMY_NAME) - .setWidgetRectangle(DUMMY_RECTANGLE).setConformanceLevel(PdfAConformanceLevel.PDF_A_1A) + .setWidgetRectangle(DUMMY_RECTANGLE).setGenericConformanceLevel(PdfAConformanceLevel.PDF_A_1A) .createComboBox(); compareChoices(new PdfDictionary(), choiceFormField, true); @@ -179,7 +179,7 @@ public void createListWithoutWidgetTest() { public void createListWithConformanceLevelTest() { PdfChoiceFormField choiceFormField = new ChoiceFormFieldBuilder(DUMMY_DOCUMENT, DUMMY_NAME) .setWidgetRectangle(DUMMY_RECTANGLE) - .setConformanceLevel(PdfAConformanceLevel.PDF_A_1A).createList(); + .setGenericConformanceLevel(PdfAConformanceLevel.PDF_A_1A).createList(); PdfDictionary expectedDictionary = new PdfDictionary(); expectedDictionary.put(PdfName.Ff, new PdfNumber(0)); diff --git a/forms/src/test/java/com/itextpdf/forms/fields/FormFieldBuilderTest.java b/forms/src/test/java/com/itextpdf/forms/fields/FormFieldBuilderTest.java index 492c2917f1..42dbd6344b 100644 --- a/forms/src/test/java/com/itextpdf/forms/fields/FormFieldBuilderTest.java +++ b/forms/src/test/java/com/itextpdf/forms/fields/FormFieldBuilderTest.java @@ -24,6 +24,7 @@ This file is part of the iText (R) project. import com.itextpdf.kernel.pdf.PdfAConformanceLevel; import com.itextpdf.kernel.pdf.PdfDocument; +import com.itextpdf.kernel.pdf.PdfUAConformanceLevel; import com.itextpdf.kernel.pdf.PdfWriter; import com.itextpdf.test.ExtendedITextTest; import com.itextpdf.test.annotations.type.UnitTest; @@ -46,14 +47,29 @@ public void constructorTest() { Assert.assertSame(DUMMY_NAME, builder.getFormFieldName()); } + @Test public void getSetConformanceLevelTest() { TestBuilder builder = new TestBuilder(DUMMY_DOCUMENT, DUMMY_NAME); - builder.setConformanceLevel(PdfAConformanceLevel.PDF_A_1A); + builder.setGenericConformanceLevel(PdfAConformanceLevel.PDF_A_1A); + Assert.assertSame(PdfAConformanceLevel.PDF_A_1A, builder.getGenericConformanceLevel()); + } + @Test + public void getSetConformanceLevelDepreceatedTest() { + TestBuilder builder = new TestBuilder(DUMMY_DOCUMENT, DUMMY_NAME); + builder.setConformanceLevel(PdfAConformanceLevel.PDF_A_1A); Assert.assertSame(PdfAConformanceLevel.PDF_A_1A, builder.getConformanceLevel()); } + + @Test + public void getSetConformanceLevelDifferentTest() { + TestBuilder builder = new TestBuilder(DUMMY_DOCUMENT, DUMMY_NAME); + builder.setGenericConformanceLevel(PdfUAConformanceLevel.PDFUA_1); + Assert.assertNull(builder.getConformanceLevel()); + } + private static class TestBuilder extends FormFieldBuilder { protected TestBuilder(PdfDocument document, String formFieldName) { diff --git a/forms/src/test/java/com/itextpdf/forms/fields/PushButtonFormFieldBuilderTest.java b/forms/src/test/java/com/itextpdf/forms/fields/PushButtonFormFieldBuilderTest.java index b075169002..60a0d028bf 100644 --- a/forms/src/test/java/com/itextpdf/forms/fields/PushButtonFormFieldBuilderTest.java +++ b/forms/src/test/java/com/itextpdf/forms/fields/PushButtonFormFieldBuilderTest.java @@ -85,7 +85,7 @@ public void createPushButtonWithoutWidgetTest() { @Test public void createPushButtonWithConformanceLevelTest() { PdfButtonFormField pushButtonFormField = new PushButtonFormFieldBuilder(DUMMY_DOCUMENT, DUMMY_NAME) - .setWidgetRectangle(DUMMY_RECTANGLE).setConformanceLevel(PdfAConformanceLevel.PDF_A_1A) + .setWidgetRectangle(DUMMY_RECTANGLE).setGenericConformanceLevel(PdfAConformanceLevel.PDF_A_1A) .createPushButton(); comparePushButtons(pushButtonFormField, true); diff --git a/forms/src/test/java/com/itextpdf/forms/fields/RadioFormFieldBuilderTest.java b/forms/src/test/java/com/itextpdf/forms/fields/RadioFormFieldBuilderTest.java index 8cab10b1db..81faec4909 100644 --- a/forms/src/test/java/com/itextpdf/forms/fields/RadioFormFieldBuilderTest.java +++ b/forms/src/test/java/com/itextpdf/forms/fields/RadioFormFieldBuilderTest.java @@ -175,7 +175,7 @@ public void createRadioButtonWithConformanceLevelTest() { RadioFormFieldBuilder builder = new RadioFormFieldBuilder(DUMMY_DOCUMENT, DUMMY_NAME); PdfButtonFormField radioGroup = builder.createRadioGroup(); PdfFormAnnotation radioAnnotation = builder - .setConformanceLevel(PdfAConformanceLevel.PDF_A_1A) + .setGenericConformanceLevel(PdfAConformanceLevel.PDF_A_1A) .createRadioButton(DUMMY_APPEARANCE_NAME, DUMMY_RECTANGLE); compareRadioButtons(radioAnnotation, radioGroup, false); } @@ -186,7 +186,7 @@ public void createRadioButtonWithConformanceLevelAddedToGroupTest() { RadioFormFieldBuilder builder = new RadioFormFieldBuilder(DUMMY_DOCUMENT, DUMMY_NAME); PdfButtonFormField radioGroup = builder.createRadioGroup(); PdfFormAnnotation radioAnnotation = builder - .setConformanceLevel(PdfAConformanceLevel.PDF_A_1A) + .setGenericConformanceLevel(PdfAConformanceLevel.PDF_A_1A) .createRadioButton(DUMMY_APPEARANCE_NAME, DUMMY_RECTANGLE); radioGroup.addKid(radioAnnotation); compareRadioButtons(radioAnnotation, radioGroup, true); @@ -273,7 +273,7 @@ private static void compareRadioButtons(PdfFormAnnotation radioButtonFormField, radioButtonFormField.getPdfObject().getAsDictionary(PdfName.AP)); } } - if (radioButtonFormField.pdfAConformanceLevel != null) { + if (radioButtonFormField.pdfConformanceLevel != null) { putIfAbsent(expectedDictionary, PdfName.F, new PdfNumber(PdfAnnotation.PRINT)); } diff --git a/forms/src/test/java/com/itextpdf/forms/fields/SignatureFormFieldBuilderTest.java b/forms/src/test/java/com/itextpdf/forms/fields/SignatureFormFieldBuilderTest.java index 8d1ffb7b25..76f6c524cb 100644 --- a/forms/src/test/java/com/itextpdf/forms/fields/SignatureFormFieldBuilderTest.java +++ b/forms/src/test/java/com/itextpdf/forms/fields/SignatureFormFieldBuilderTest.java @@ -76,7 +76,7 @@ public void createSignatureWithoutWidgetTest() { @Test public void createSignatureWithConformanceLevelTest() { PdfSignatureFormField signatureFormField = new SignatureFormFieldBuilder(DUMMY_DOCUMENT, DUMMY_NAME) - .setWidgetRectangle(DUMMY_RECTANGLE).setConformanceLevel(PdfAConformanceLevel.PDF_A_1A) + .setWidgetRectangle(DUMMY_RECTANGLE).setGenericConformanceLevel(PdfAConformanceLevel.PDF_A_1A) .createSignature(); compareSignatures(signatureFormField, true); diff --git a/forms/src/test/java/com/itextpdf/forms/fields/TextFormFieldBuilderTest.java b/forms/src/test/java/com/itextpdf/forms/fields/TextFormFieldBuilderTest.java index 78f6516953..9a4b3b28ae 100644 --- a/forms/src/test/java/com/itextpdf/forms/fields/TextFormFieldBuilderTest.java +++ b/forms/src/test/java/com/itextpdf/forms/fields/TextFormFieldBuilderTest.java @@ -81,7 +81,7 @@ public void createTextWithoutWidgetTest() { @Test public void createTextWithConformanceLevelTest() { PdfTextFormField textFormField = new TextFormFieldBuilder(DUMMY_DOCUMENT, DUMMY_NAME) - .setWidgetRectangle(DUMMY_RECTANGLE).setConformanceLevel(PdfAConformanceLevel.PDF_A_1A).createText(); + .setWidgetRectangle(DUMMY_RECTANGLE).setGenericConformanceLevel(PdfAConformanceLevel.PDF_A_1A).createText(); PdfDictionary expectedDictionary = new PdfDictionary(); expectedDictionary.put(PdfName.Ff, new PdfNumber(0)); @@ -113,7 +113,7 @@ public void createMultilineTextWithoutWidgetTest() { @Test public void createMultilineTextWithConformanceLevelTest() { PdfTextFormField textFormField = new TextFormFieldBuilder(DUMMY_DOCUMENT, DUMMY_NAME) - .setWidgetRectangle(DUMMY_RECTANGLE).setConformanceLevel(PdfAConformanceLevel.PDF_A_1A) + .setWidgetRectangle(DUMMY_RECTANGLE).setGenericConformanceLevel(PdfAConformanceLevel.PDF_A_1A) .createMultilineText(); PdfDictionary expectedDictionary = new PdfDictionary(); diff --git a/forms/src/test/java/com/itextpdf/forms/form/element/AccessibleElementTest.java b/forms/src/test/java/com/itextpdf/forms/form/element/AccessibleElementTest.java new file mode 100644 index 0000000000..254af5403b --- /dev/null +++ b/forms/src/test/java/com/itextpdf/forms/form/element/AccessibleElementTest.java @@ -0,0 +1,211 @@ +/* + 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.form.element; + +import com.itextpdf.forms.form.FormProperty; +import com.itextpdf.io.source.ByteArrayOutputStream; +import com.itextpdf.kernel.pdf.PdfDictionary; +import com.itextpdf.kernel.pdf.PdfDocument; +import com.itextpdf.kernel.pdf.PdfName; +import com.itextpdf.kernel.pdf.PdfWriter; +import com.itextpdf.kernel.pdf.tagging.IStructureNode; +import com.itextpdf.kernel.pdf.tagging.PdfStructElem; +import com.itextpdf.kernel.pdf.tagging.PdfStructTreeRoot; +import com.itextpdf.layout.Document; +import com.itextpdf.layout.element.IBlockElement; +import com.itextpdf.layout.tagging.IAccessibleElement; +import com.itextpdf.test.ExtendedITextTest; +import com.itextpdf.test.annotations.type.IntegrationTest; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.function.Supplier; +import org.junit.Assert; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + + +@Category(IntegrationTest.class) +@RunWith(Parameterized.class) +public class AccessibleElementTest extends ExtendedITextTest { + private final TestContainer testContainer; + + public AccessibleElementTest(TestContainer index) { + this.testContainer = index; + } + + + @Parameterized.Parameters(name = "{0}") + public static Collection getDataTestFixtureData() { + int amountOfEntries = 8; + List data = new ArrayList<>(); + for (int i = 0; i < amountOfEntries; i++) { + data.add(new Object[]{new TestContainer(i)}); + } + return data; + } + + private Supplier getDataToTest(int index){ + switch (index){ + case 0: + return () -> new InputField("inputField"); + case 1: + return () -> new TextArea("textArea"); + case 2: + return () -> new Radio("radioButton", "group"); + case 3: + ComboBoxField field = new ComboBoxField("comboBox"); + field.addOption(new SelectFieldItem("option1")); + field.addOption(new SelectFieldItem("option2")); + return () -> field; + case 4: + ListBoxField field2 = new ListBoxField("listBox", 4, false); + field2.addOption(new SelectFieldItem("option1")); + field2.addOption(new SelectFieldItem("option2")); + return () -> field2; + case 5: + return () -> new SignatureFieldAppearance("signatureField"); + case 6: + return () -> new Button("button"); + case 7: + return () -> new CheckBox("checkBox"); + default: + throw new IllegalArgumentException("Invalid index"); + } + } + + @Test + public void testInteractive() { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PdfDocument pdfDocument = new PdfDocument(new PdfWriter(baos)); + pdfDocument.setTagged(); + Document document = new Document(pdfDocument); + + IFormField element = getDataToTest(testContainer.index).get(); + element.setInteractive(true); + IAccessibleElement accessibleElement = (IAccessibleElement) element; + accessibleElement.getAccessibilityProperties().setLanguage("en"); + + document.add((IBlockElement) element); + + PdfStructTreeRoot root = pdfDocument.getStructTreeRoot(); + IStructureNode documentStruct = root.getKids().get(0); + IStructureNode kid = documentStruct.getKids().get(0); + PdfStructElem elem = (PdfStructElem) kid; + PdfDictionary obj = elem.getPdfObject(); + Assert.assertEquals(PdfName.Form, elem.getRole()); + Assert.assertTrue(obj.containsKey(PdfName.Lang)); + Assert.assertEquals("en", obj.getAsString(PdfName.Lang).getValue()); + document.close(); + pdfDocument.close(); + } + + @Test + public void testNonInteractive() { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PdfDocument pdfDocument = new PdfDocument(new PdfWriter(baos)); + pdfDocument.setTagged(); + Document document = new Document(pdfDocument); + + IFormField element = getDataToTest(testContainer.index).get(); + IAccessibleElement accessibleElement = (IAccessibleElement) element; + accessibleElement.getAccessibilityProperties().setLanguage("en"); + + document.add((IBlockElement) element); + + PdfStructTreeRoot root = pdfDocument.getStructTreeRoot(); + IStructureNode documentStruct = root.getKids().get(0); + IStructureNode kid = documentStruct.getKids().get(0); + PdfStructElem elem = (PdfStructElem) kid; + PdfDictionary obj = elem.getPdfObject(); + Assert.assertEquals(PdfName.Form, elem.getRole()); + Assert.assertTrue(obj.containsKey(PdfName.Lang)); + Assert.assertEquals("en", obj.getAsString(PdfName.Lang).getValue()); + document.close(); + pdfDocument.close(); + } + + @Test + public void testInteractiveProperty() { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PdfDocument pdfDocument = new PdfDocument(new PdfWriter(baos)); + pdfDocument.setTagged(); + Document document = new Document(pdfDocument); + + IFormField element = getDataToTest(testContainer.index).get(); + + element.setProperty(FormProperty.FORM_ACCESSIBILITY_LANGUAGE, "en"); + IFormField formField = (IFormField) element; + formField.setInteractive(true); + + document.add((IBlockElement) element); + + PdfStructTreeRoot root = pdfDocument.getStructTreeRoot(); + IStructureNode documentStruct = root.getKids().get(0); + IStructureNode kid = documentStruct.getKids().get(0); + PdfStructElem elem = (PdfStructElem) kid; + PdfDictionary obj = elem.getPdfObject(); + Assert.assertEquals(PdfName.Form, elem.getRole()); + Assert.assertTrue(obj.containsKey(PdfName.Lang)); + Assert.assertEquals("en", obj.getAsString(PdfName.Lang).getValue()); + document.close(); + pdfDocument.close(); + } + + public static class TestContainer { + + public final int index; + + public TestContainer(int index) { + this.index = index; + } + + @Override + public String toString() { + switch (index){ + case 0: + return "InputField"; + case 1: + return "TextArea"; + case 2: + return "Radio"; + case 3: + return "ComboBox"; + case 4: + return "ListBox"; + case 5: + return "SignatureField"; + case 6: + return "Button"; + case 7: + return "CheckBox"; + default: + throw new IllegalArgumentException("Invalid index"); + } + } + } +} + diff --git a/forms/src/test/java/com/itextpdf/forms/form/element/CheckBoxTest.java b/forms/src/test/java/com/itextpdf/forms/form/element/CheckBoxTest.java index 346e378314..0ea25c1cd5 100644 --- a/forms/src/test/java/com/itextpdf/forms/form/element/CheckBoxTest.java +++ b/forms/src/test/java/com/itextpdf/forms/form/element/CheckBoxTest.java @@ -585,6 +585,20 @@ private void generateCheckBoxesForAllRenderingModes(Document document, Consumer< } + @Test + public void basicCheckBoxTagged() throws IOException, InterruptedException { + String outPdf = DESTINATION_FOLDER + "basicCheckboxTagged.pdf"; + String cmpPdf = SOURCE_FOLDER + "cmp_basicCheckboxTagged.pdf"; + try (Document document = new Document(new PdfDocument(new PdfWriter(outPdf)))) { + document.getPdfDocument().setTagged(); + generateCheckBoxes(document, checkBox -> { + }); + generateCheckBoxes(document, checkBox -> { + }); + } + Assert.assertNull(new CompareTool().compareByContent(outPdf, cmpPdf, DESTINATION_FOLDER)); + } + private List generateCheckBoxes(Document document, Consumer alterFunction) { List checkBoxList = new ArrayList<>(); diff --git a/forms/src/test/java/com/itextpdf/forms/form/element/ComboBoxFieldTest.java b/forms/src/test/java/com/itextpdf/forms/form/element/ComboBoxFieldTest.java index e2cd279767..80610a7fa8 100644 --- a/forms/src/test/java/com/itextpdf/forms/form/element/ComboBoxFieldTest.java +++ b/forms/src/test/java/com/itextpdf/forms/form/element/ComboBoxFieldTest.java @@ -319,6 +319,8 @@ public void comboBoxFieldWithLangTest() throws IOException, InterruptedException flattenComboBoxField.addOption(new SelectFieldItem("option 1")); flattenComboBoxField.addOption(new SelectFieldItem("option 2")); flattenComboBoxField.setSelected("option 1"); + + //TODO DEVSIX-8205 Use setLanguage method from AccessibilityProperties flattenComboBoxField.setProperty(FormProperty.FORM_ACCESSIBILITY_LANGUAGE, "random_lang"); document.add(flattenComboBoxField); } @@ -543,5 +545,43 @@ public void addingOptionWithNullExportValueTest() { () -> comboBoxField.addOption(new SelectFieldItem("option 1", (String) null))); } + @Test + public void basicComboBoxFieldTaggedTest() throws IOException, InterruptedException { + String outPdf = DESTINATION_FOLDER + "basicComboBoxFieldTagged.pdf"; + String cmpPdf = SOURCE_FOLDER + "cmp_basicComboBoxFieldTagged.pdf"; + + try (Document document = new Document(new PdfDocument(new PdfWriter(outPdf)))) { + document.getPdfDocument().setTagged(); + ComboBoxField formComboBoxField = new ComboBoxField("form combo box field"); + formComboBoxField.setInteractive(true); + formComboBoxField.addOption(new SelectFieldItem("option 1")); + formComboBoxField.addOption(new SelectFieldItem("option 2")); + document.add(formComboBoxField); + + ComboBoxField flattenComboBoxField = new ComboBoxField("flatten combo box field"); + flattenComboBoxField.setInteractive(false); + flattenComboBoxField.addOption(new SelectFieldItem("option 1")); + flattenComboBoxField.addOption(new SelectFieldItem("option 2")); + document.add(flattenComboBoxField); + + ComboBoxField formComboBoxFieldSelected = new ComboBoxField("form combo box field selected"); + formComboBoxFieldSelected.setInteractive(true); + formComboBoxFieldSelected.addOption(new SelectFieldItem("option 1")); + formComboBoxFieldSelected.addOption(new SelectFieldItem("option 2")); + formComboBoxFieldSelected.setSelected("option 1"); + document.add(formComboBoxFieldSelected); + + ComboBoxField flattenComboBoxFieldSelected = new ComboBoxField("flatten combo box field selected"); + flattenComboBoxFieldSelected.setInteractive(false); + flattenComboBoxFieldSelected.addOption(new SelectFieldItem("option 1")); + flattenComboBoxFieldSelected.addOption(new SelectFieldItem("option 2")); + flattenComboBoxFieldSelected.setSelected("option 1"); + document.add(flattenComboBoxFieldSelected); + + } + + Assert.assertNull(new CompareTool().compareByContent(outPdf, cmpPdf, DESTINATION_FOLDER)); + } + } diff --git a/forms/src/test/java/com/itextpdf/forms/form/element/FixedPositionTest.java b/forms/src/test/java/com/itextpdf/forms/form/element/FixedPositionTest.java new file mode 100644 index 0000000000..d9b7bcbf69 --- /dev/null +++ b/forms/src/test/java/com/itextpdf/forms/form/element/FixedPositionTest.java @@ -0,0 +1,444 @@ +/* + 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.form.element; + + +import com.itextpdf.forms.fields.properties.SignedAppearanceText; +import com.itextpdf.io.image.ImageData; +import com.itextpdf.io.image.ImageDataFactory; +import com.itextpdf.io.logs.IoLogMessageConstant; +import com.itextpdf.kernel.colors.ColorConstants; +import com.itextpdf.kernel.geom.PageSize; +import com.itextpdf.kernel.pdf.PdfDocument; +import com.itextpdf.kernel.pdf.PdfWriter; +import com.itextpdf.kernel.utils.CompareTool; +import com.itextpdf.layout.Document; +import com.itextpdf.layout.ElementPropertyContainer; +import com.itextpdf.layout.IPropertyContainer; +import com.itextpdf.layout.borders.SolidBorder; +import com.itextpdf.layout.element.AreaBreak; +import com.itextpdf.layout.element.Div; +import com.itextpdf.layout.element.Paragraph; +import com.itextpdf.layout.properties.AreaBreakType; +import com.itextpdf.layout.properties.Property; +import com.itextpdf.layout.properties.UnitValue; +import com.itextpdf.test.ExtendedITextTest; +import com.itextpdf.test.annotations.LogMessage; +import com.itextpdf.test.annotations.LogMessages; +import com.itextpdf.test.annotations.type.IntegrationTest; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.experimental.categories.Category; + + +@Category(IntegrationTest.class) +public class FixedPositionTest extends ExtendedITextTest { + + public static final String SOURCE_FOLDER = + "./src/test/resources/com/itextpdf/forms/form/element/FixedPositionTest/"; + public static final String DESTINATION_FOLDER = + "./target/test/com/itextpdf/forms/form/element/FixedPositionTest/"; + public static final String IMG_FOLDER = + "./src/test/resources/com/itextpdf/forms/form/element/SignatureFieldAppearanceTest/"; + + @BeforeClass + public static void setUp() { + createDestinationFolder(DESTINATION_FOLDER); + } + + + @Test + public void nonInteractive() throws IOException, InterruptedException { + final String outputFileName = DESTINATION_FOLDER + "ni_setFixedPosition.pdf"; + final String cmpFileName = SOURCE_FOLDER + "cmp_ni_setFixedPosition.pdf"; + try (PdfDocument pdfDocument = new PdfDocument(new PdfWriter(outputFileName))) { + Document document = new Document(pdfDocument); + + int left = 100; + int bottom = 700; + int width = 150; + + for (Supplier iFormFieldSupplier : getDataToTest()) { + IFormField field = iFormFieldSupplier.get(); + new DummyContainer() + .applyFixedPosition(left, bottom, width) + .applyToElement(field); + + bottom -= 75; + document.add(field); + } + + document.close(); + } + Assert.assertNull(new CompareTool().compareByContent(outputFileName, cmpFileName, DESTINATION_FOLDER, "diff")); + } + + @Test + public void nonInteractiveOnSpecificPage() throws IOException, InterruptedException { + final String outputFileName = DESTINATION_FOLDER + "ni_setFixedPositionOnPage.pdf"; + final String cmpFileName = SOURCE_FOLDER + "cmp_ni_setFixedPositionOnPage.pdf"; + try (PdfDocument pdfDocument = new PdfDocument(new PdfWriter(outputFileName))) { + Document document = new Document(pdfDocument); + + int left = 100; + int bottom = 700; + int width = 150; + int page = 1; + for (Supplier iFormFieldSupplier : getDataToTest()) { + IFormField field = iFormFieldSupplier.get(); + new DummyContainer() + .applyFixedPosition(++page, left, bottom, width) + .applyToElement(field); + + document.add(field); + } + + document.close(); + } + Assert.assertNull(new CompareTool().compareByContent(outputFileName, cmpFileName, DESTINATION_FOLDER, + "diff")); + } + + @Test + @LogMessages(messages = @LogMessage(messageTemplate = IoLogMessageConstant.CLIP_ELEMENT)) + public void interactive() throws IOException, InterruptedException { + final String outputFileName = DESTINATION_FOLDER + "interactive_fixed_pos.pdf"; + final String cmpFileName = SOURCE_FOLDER + "cmp_interactive_fixed_pos.pdf"; + try (PdfDocument pdfDocument = new PdfDocument(new PdfWriter(outputFileName))) { + Document document = new Document(pdfDocument); + int left = 100; + int bottom = 700; + int width = 150; + for (Supplier iFormFieldSupplier : getDataToTest()) { + IFormField field = iFormFieldSupplier.get(); + field.setInteractive(true); + new DummyContainer() + .applyFixedPosition(left, bottom, width) + .applyToElement(field); + document.add(field); + bottom -= 75; + } + + document.close(); + } + Assert.assertNull(new CompareTool().compareByContent(outputFileName, cmpFileName, DESTINATION_FOLDER, + "diff")); + } + + @Test + @LogMessages(messages = @LogMessage(messageTemplate = IoLogMessageConstant.CLIP_ELEMENT)) + public void interactiveOnPage() throws IOException, InterruptedException { + final String outputFileName = DESTINATION_FOLDER + "interactive_fixed_pos_on_page.pdf"; + final String cmpFileName = SOURCE_FOLDER + "cmp_interactive_fixed_pos_on_page.pdf"; + try (PdfDocument pdfDocument = new PdfDocument(new PdfWriter(outputFileName))) { + Document document = new Document(pdfDocument); + int left = 100; + int bottom = 700; + int width = 150; + int page = 1; + for (Supplier iFormFieldSupplier : getDataToTest()) { + IFormField field = iFormFieldSupplier.get(); + field.setInteractive(true); + + new DummyContainer() + .applyFixedPosition(++page, left, bottom, width) + .applyToElement(field); + + document.add(field); + } + + document.close(); + } + Assert.assertNull(new CompareTool().compareByContent(outputFileName, cmpFileName, DESTINATION_FOLDER, + "diff")); + } + + @Test + @LogMessages(messages = @LogMessage(messageTemplate = IoLogMessageConstant.CLIP_ELEMENT)) + public void interactiveWidthOutOfBounds() throws IOException, InterruptedException { + final String outputFileName = DESTINATION_FOLDER + "interactiveOutOfBounds.pdf"; + final String cmpFileName = SOURCE_FOLDER + "cmp_interactiveOutOfBounds.pdf"; + try (PdfDocument pdfDocument = new PdfDocument(new PdfWriter(outputFileName))) { + Document document = new Document(pdfDocument); + int left = 100; + int bottom = 10000; + int width = 100; + for (Supplier iFormFieldSupplier : getDataToTest()) { + IFormField field = iFormFieldSupplier.get(); + field.setInteractive(true); + new DummyContainer() + .applyFixedPosition(left, bottom, width) + .applyToElement(field); + document.add(field); + bottom -= 75; + } + document.close(); + } + Assert.assertNull(new CompareTool().compareByContent(outputFileName, cmpFileName, DESTINATION_FOLDER, + "diff")); + } + + @Test + @LogMessages(messages = @LogMessage(messageTemplate = IoLogMessageConstant.CLIP_ELEMENT, count = 2)) + public void interactiveMarginLeft() throws IOException, InterruptedException { + final String outputFileName = DESTINATION_FOLDER + "interactiveMarginLeft.pdf"; + final String cmpFileName = SOURCE_FOLDER + "cmp_interactiveMarginLeft.pdf"; + try (PdfDocument pdfDocument = new PdfDocument(new PdfWriter(outputFileName))) { + Document document = new Document(pdfDocument); + for (Supplier iFormFieldSupplier : getDataToTest()) { + IFormField field = iFormFieldSupplier.get(); + + new DummyContainer().applyToElement(field); + UnitValue marginUV = UnitValue.createPointValue(200); + field.setProperty(Property.MARGIN_LEFT, marginUV); + document.add(field); + } + document.add(new AreaBreak(AreaBreakType.NEXT_PAGE)); + for (Supplier iFormFieldSupplier : getDataToTest()) { + IFormField field = iFormFieldSupplier.get(); + field.setInteractive(true); + new DummyContainer().applyToElement(field); + + UnitValue marginUV = UnitValue.createPointValue(200); + field.setProperty(Property.MARGIN_LEFT, marginUV); + + document.add(field); + } + document.close(); + } + Assert.assertNull(new CompareTool().compareByContent(outputFileName, cmpFileName, DESTINATION_FOLDER, + "diff")); + } + + @Test + @LogMessages(messages = @LogMessage(messageTemplate = IoLogMessageConstant.CLIP_ELEMENT, count = 2)) + public void interactiveMarginTop() throws IOException, InterruptedException { + final String outputFileName = DESTINATION_FOLDER + "marginTop.pdf"; + final String cmpFileName = SOURCE_FOLDER + "cmp_marginTop.pdf"; + try (PdfDocument pdfDocument = new PdfDocument(new PdfWriter(outputFileName))) { + Document document = new Document(pdfDocument, PageSize.A4, false); + + for (Supplier iFormFieldSupplier : getDataToTest()) { + IFormField field = iFormFieldSupplier.get(); + new DummyContainer().applyToElement(field); + UnitValue marginUV = UnitValue.createPointValue(50); + field.setProperty(Property.MARGIN_TOP, marginUV); + document.add(field); + } + document.add(new AreaBreak(AreaBreakType.NEXT_PAGE)); + for (Supplier iFormFieldSupplier : getDataToTest()) { + IFormField field = iFormFieldSupplier.get(); + field.setInteractive(true); + new DummyContainer().applyToElement(field); + + UnitValue marginUV = UnitValue.createPointValue(50); + field.setProperty(Property.MARGIN_TOP, marginUV); + + document.add(field); + } + document.close(); + } + Assert.assertNull(new CompareTool().compareByContent(outputFileName, cmpFileName, DESTINATION_FOLDER, + "diff")); + } + + @Test + @LogMessages(messages = @LogMessage(messageTemplate = IoLogMessageConstant.CLIP_ELEMENT, count = 2)) + public void width() throws IOException, InterruptedException { + final String outputFileName = DESTINATION_FOLDER + "width.pdf"; + final String cmpFileName = SOURCE_FOLDER + "cmp_width.pdf"; + try (PdfDocument pdfDocument = new PdfDocument(new PdfWriter(outputFileName))) { + Document document = new Document(pdfDocument, PageSize.A4, false); + + for (Supplier iFormFieldSupplier : getDataToTest()) { + IFormField field = iFormFieldSupplier.get(); + new DummyContainer().applyToElement(field); + field.setProperty(Property.WIDTH, UnitValue.createPointValue(400)); + document.add(field); + } + document.add(new AreaBreak(AreaBreakType.NEXT_PAGE)); + for (Supplier iFormFieldSupplier : getDataToTest()) { + IFormField field = iFormFieldSupplier.get(); + field.setInteractive(true); + + new DummyContainer().applyToElement(field); + field.setProperty(Property.WIDTH, UnitValue.createPointValue(400)); + document.add(field); + } + document.close(); + } + Assert.assertNull(new CompareTool().compareByContent(outputFileName, cmpFileName, DESTINATION_FOLDER, + "diff")); + } + + @Test + @LogMessages(messages = @LogMessage(messageTemplate = IoLogMessageConstant.CLIP_ELEMENT, count = 2)) + public void padding() throws IOException, InterruptedException { + final String outputFileName = DESTINATION_FOLDER + "padding.pdf"; + final String cmpFileName = SOURCE_FOLDER + "cmp_padding.pdf"; + try (PdfDocument pdfDocument = new PdfDocument(new PdfWriter(outputFileName))) { + Document document = new Document(pdfDocument, PageSize.A4, false); + + for (Supplier iFormFieldSupplier : getDataToTest()) { + IFormField field = iFormFieldSupplier.get(); + new DummyContainer().applyToElement(field); + field.setProperty(Property.PADDING_LEFT, UnitValue.createPointValue(50)); + document.add(field); + } + document.add(new AreaBreak(AreaBreakType.NEXT_PAGE)); + for (Supplier iFormFieldSupplier : getDataToTest()) { + IFormField field = iFormFieldSupplier.get(); + field.setInteractive(true); + new DummyContainer().applyToElement(field); + field.setProperty(Property.LEFT, UnitValue.createPointValue(20)); + document.add(field); + } + document.close(); + } + Assert.assertNull(new CompareTool().compareByContent(outputFileName, cmpFileName, DESTINATION_FOLDER, + "diff")); + } + + private static List> getDataToTest() throws MalformedURLException { + List> data = new ArrayList<>(); + data.add(() -> { + InputField inputField = new InputField("inputField"); + inputField.setValue("value some text"); + return inputField; + }); + + data.add(() -> { + TextArea textArea = new TextArea("textArea"); + textArea.setValue("value some text\nsome more text"); + return textArea; + }); + + data.add(() -> { + Radio radio = new Radio("radioButton", "group"); + radio.setChecked(true); + return radio; + }); + + data.add(() -> { + ComboBoxField field = new ComboBoxField("comboBox"); + field.addOption(new SelectFieldItem("option1")); + field.addOption(new SelectFieldItem("option2")); + field.setSelected("option1"); + return field; + }); + + data.add(() -> { + ListBoxField field2 = new ListBoxField("listBox", 4, false); + field2.addOption(new SelectFieldItem("option1")); + field2.addOption(new SelectFieldItem("option2")); + field2.setValue("option1"); + return field2; + }); + + data.add(() -> { + SignatureFieldAppearance app = new SignatureFieldAppearance("signatureField1"); + app.setContent(new SignedAppearanceText().setSignedBy("signer\nname").setLocationLine("location") + .setReasonLine("reason")); + return app; + }); + + ImageData image = ImageDataFactory.create(IMG_FOLDER + "1.png"); + data.add(() -> { + SignatureFieldAppearance app = new SignatureFieldAppearance("signatureField2"); + app.setContent(image); + return app; + }); + + data.add(() -> { + SignatureFieldAppearance app = new SignatureFieldAppearance("signatureField3"); + app.setContent("signature with image and description test\n" + + "signature with image and description test\nsignature with image and description test", image); + return app; + }); + + data.add(() -> { + SignatureFieldAppearance app = new SignatureFieldAppearance("signatureField4"); + app.setContent("signer", new SignedAppearanceText().setSignedBy("signer").setLocationLine("location") + .setReasonLine("reason")); + return app; + }); + + data.add(() -> { + SignatureFieldAppearance app = new SignatureFieldAppearance("signatureField5"); + app.setContent(new Div().add(new Paragraph("signature with div element test\n" + + "signature with div element test\nsignature with div element test"))); + return app; + }); + + data.add(() -> { + Button button = new Button("button"); + button.setValue("Click me"); + return button; + }); + + data.add(() -> { + CheckBox cb = new CheckBox("checkBox"); + cb.setSize(20); + cb.setChecked(true); + return cb; + }); + return data; + } + + public static class DummyContainer extends ElementPropertyContainer { + + public DummyContainer(){ + setBackgroundColor(ColorConstants.RED); + setBorder(new SolidBorder(ColorConstants.BLUE, 2)); + } + + public DummyContainer applyFixedPosition(int left, int bottom, int width) { + setFixedPosition(left, bottom, width); + return this; + } + + public DummyContainer applyFixedPosition(int pageNumber, int left, int bottom, int width) { + setFixedPosition(pageNumber, left, bottom, width); + return this; + } + + + public void applyToElement(IPropertyContainer propertyContainer) { + for (Integer i : this.properties.keySet()) { + Object value = this.properties.get((int)i); + propertyContainer.setProperty((int) i, value); + } + + } + } + + +} + diff --git a/forms/src/test/java/com/itextpdf/forms/form/element/InputFieldTest.java b/forms/src/test/java/com/itextpdf/forms/form/element/InputFieldTest.java index ea0f9eb85e..dd2c63d13c 100644 --- a/forms/src/test/java/com/itextpdf/forms/form/element/InputFieldTest.java +++ b/forms/src/test/java/com/itextpdf/forms/form/element/InputFieldTest.java @@ -133,6 +133,7 @@ public void inputFieldWithLangTest() throws IOException, InterruptedException { InputField flattenInputField = new InputField("input field with lang"); flattenInputField.setProperty(FormProperty.FORM_FIELD_FLATTEN, false); flattenInputField.setProperty(FormProperty.FORM_FIELD_VALUE, "input field with lang"); + flattenInputField.setProperty(FormProperty.FORM_ACCESSIBILITY_LANGUAGE, "random_lang"); flattenInputField.setProperty(Property.BORDER, new SolidBorder(2f)); document.add(flattenInputField); @@ -151,6 +152,7 @@ public void inputFieldWithNullLangTest() throws IOException, InterruptedExceptio InputField flattenInputField = new InputField("input field with null lang"); flattenInputField.setProperty(FormProperty.FORM_FIELD_FLATTEN, false); flattenInputField.setProperty(FormProperty.FORM_FIELD_VALUE, "input field with null lang"); + flattenInputField.setProperty(FormProperty.FORM_ACCESSIBILITY_LANGUAGE, null); flattenInputField.setProperty(Property.BORDER, new SolidBorder(2f)); document.add(flattenInputField); diff --git a/forms/src/test/java/com/itextpdf/forms/form/element/ListBoxFieldTest.java b/forms/src/test/java/com/itextpdf/forms/form/element/ListBoxFieldTest.java index bb1a7591b8..6421d300f8 100644 --- a/forms/src/test/java/com/itextpdf/forms/form/element/ListBoxFieldTest.java +++ b/forms/src/test/java/com/itextpdf/forms/form/element/ListBoxFieldTest.java @@ -44,6 +44,7 @@ This file is part of the iText (R) project. import com.itextpdf.layout.borders.SolidBorder; import com.itextpdf.layout.element.Div; import com.itextpdf.layout.element.Paragraph; +import com.itextpdf.layout.logs.LayoutLogMessageConstant; import com.itextpdf.layout.properties.Property; import com.itextpdf.layout.properties.TextAlignment; import com.itextpdf.layout.properties.UnitValue; @@ -501,6 +502,40 @@ public void invalidOptionsTest() throws IOException, InterruptedException { Assert.assertNull(new CompareTool().compareByContent(outPdf, cmpPdf, DESTINATION_FOLDER)); } + @Test + @LogMessages(messages = @LogMessage(messageTemplate = LayoutLogMessageConstant.ELEMENT_DOES_NOT_FIT_AREA, count = 1)) + public void listBoxIsBiggerThanPage() throws IOException, InterruptedException { + String outPdf = DESTINATION_FOLDER + "listBoxIsBiggerThenPage.pdf"; + String cmpPdf = SOURCE_FOLDER + "cmp_listBoxIsBiggerThenPage.pdf"; + Document document = new Document(new PdfDocument(new PdfWriter(outPdf))) ; + ListBoxField list = (ListBoxField) new ListBoxField("name", 200, false).setInteractive(true); + list.setBackgroundColor(ColorConstants.RED); + list.addOption("value1"); + list.addOption("value2"); + document.add(new Paragraph("s\no\nm\ne\nl\no\nn\ng\nt\ne\nx\nt\n")); + document.add(list); + document.close(); + + Assert.assertNull(new CompareTool().compareByContent(outPdf, cmpPdf, DESTINATION_FOLDER)); + } + + @Test + @LogMessages(messages = @LogMessage(messageTemplate = LayoutLogMessageConstant.ELEMENT_DOES_NOT_FIT_AREA, count = 1)) + public void listBoxIsBiggerThanPageNonI() throws IOException, InterruptedException { + String outPdf = DESTINATION_FOLDER + "listBoxIsBiggerThenPageNonI.pdf"; + String cmpPdf = SOURCE_FOLDER + "cmp_listBoxIsBiggerThenPageNonI.pdf"; + Document document = new Document(new PdfDocument(new PdfWriter(outPdf))) ; + ListBoxField list = (ListBoxField) new ListBoxField("name", 200, false); + list.setBackgroundColor(ColorConstants.RED); + list.addOption("value1"); + list.addOption("value2"); + document.add(new Paragraph("s\no\nm\ne\nl\no\nn\ng\nt\ne\nx\nt\n")); + document.add(list); + document.close(); + + Assert.assertNull(new CompareTool().compareByContent(outPdf, cmpPdf, DESTINATION_FOLDER)); + } + @Test public void invalidOptionsExceptionTest() throws IOException, InterruptedException { try (PdfDocument doc = new PdfDocument(new PdfWriter(new ByteArrayOutputStream()))) { diff --git a/forms/src/test/java/com/itextpdf/forms/form/element/RadioTest.java b/forms/src/test/java/com/itextpdf/forms/form/element/RadioTest.java index d319ca22fe..be4d9daaa2 100644 --- a/forms/src/test/java/com/itextpdf/forms/form/element/RadioTest.java +++ b/forms/src/test/java/com/itextpdf/forms/form/element/RadioTest.java @@ -95,6 +95,31 @@ public void basicRadioTest() throws IOException, InterruptedException { Assert.assertNull(new CompareTool().compareByContent(outPdf, cmpPdf, DESTINATION_FOLDER)); } + @Test + public void basicRadioTaggedTest() throws IOException, InterruptedException { + String outPdf = DESTINATION_FOLDER + "basicRadioTagged.pdf"; + String cmpPdf = SOURCE_FOLDER + "cmp_basicRadioTagged.pdf"; + + try (Document document = new Document(new PdfDocument(new PdfWriter(outPdf)))) { + document.getPdfDocument().setTagged(); + Radio formRadio1 = createRadioButton("form radio button 1", "form radio group", null, null, true, false); + document.add(formRadio1); + + Radio formRadio2 = createRadioButton("form radio button 2", "form radio group", null, null, false, false); + document.add(formRadio2); + + Radio flattenRadio1 = createRadioButton("flatten radio button 1", "flatten radio group", null, null, true, + true); + document.add(flattenRadio1); + + Radio flattenRadio2 = createRadioButton("flatten radio button 2", "flatten radio group", null, null, false, + true); + document.add(flattenRadio2); + } + + Assert.assertNull(new CompareTool().compareByContent(outPdf, cmpPdf, DESTINATION_FOLDER)); + } + @Test public void emptyNameTest() { try (Document document = new Document(new PdfDocument(new PdfWriter(new ByteArrayOutputStream())))) { diff --git a/forms/src/test/java/com/itextpdf/forms/form/element/SignatureFieldAppearanceTest.java b/forms/src/test/java/com/itextpdf/forms/form/element/SignatureFieldAppearanceTest.java index 80f2609064..b293bd904a 100644 --- a/forms/src/test/java/com/itextpdf/forms/form/element/SignatureFieldAppearanceTest.java +++ b/forms/src/test/java/com/itextpdf/forms/form/element/SignatureFieldAppearanceTest.java @@ -39,6 +39,7 @@ This file is part of the iText (R) project. 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.PdfWriter; import com.itextpdf.kernel.pdf.annot.PdfAnnotation; import com.itextpdf.kernel.utils.CompareTool; @@ -422,9 +423,7 @@ public PdfFont getDefaultFont() { sigField.setInteractive(true); sigField.setBorder(new SolidBorder(ColorConstants.GREEN, 1)); - Exception e = Assert.assertThrows(IllegalStateException.class, () -> { - document.add(sigField); - }); + Exception e = Assert.assertThrows(IllegalStateException.class, () -> document.add(sigField)); Assert.assertEquals(LayoutExceptionMessageConstant.INVALID_FONT_PROPERTY_VALUE, e.getMessage()); } @@ -506,4 +505,17 @@ public void customModeTest() throws IOException, InterruptedException { Assert.assertNull(new CompareTool().compareByContent(outPdf, cmpPdf, DESTINATION_FOLDER)); } + + @Test + public void flattenEmptySignatureTest() throws IOException, InterruptedException { + String srcPdf = SOURCE_FOLDER + "emptySignature.pdf"; + String outPdf = DESTINATION_FOLDER + "flattenEmptySignature.pdf"; + String cmpPdf = SOURCE_FOLDER + "cmp_flattenEmptySignature.pdf"; + + try (PdfDocument document = new PdfDocument(new PdfReader(srcPdf), new PdfWriter(outPdf))) { + PdfAcroForm acroForm = PdfFormCreator.getAcroForm(document, false); + acroForm.flattenFields(); + } + Assert.assertNull(new CompareTool().compareVisually(outPdf, cmpPdf, DESTINATION_FOLDER, "diff_")); + } } diff --git a/forms/src/test/java/com/itextpdf/forms/form/element/TextAreaTest.java b/forms/src/test/java/com/itextpdf/forms/form/element/TextAreaTest.java index 46d1c3d437..5cf4d0a20f 100644 --- a/forms/src/test/java/com/itextpdf/forms/form/element/TextAreaTest.java +++ b/forms/src/test/java/com/itextpdf/forms/form/element/TextAreaTest.java @@ -267,7 +267,7 @@ public void textAreaWith0FontSizeWithoutHeightTest() throws IOException, Interru try (Document document = new Document(new PdfDocument(new PdfWriter(outPdf)))) { TextArea textArea = new TextArea("text area"); textArea.setInteractive(true); - textArea.setProperty(FormProperty.FORM_FIELD_VALUE, "Font\n size \nof this\nText Area will not " + textArea.setProperty(FormProperty.FORM_FIELD_VALUE, "Font\n size \nof this\nText Area will not " + "\nbe approximated\nbased on the content\nbecause height is not set"); textArea.setProperty(Property.BORDER, new SolidBorder(1f)); textArea.setFontSize(0); @@ -275,7 +275,7 @@ public void textAreaWith0FontSizeWithoutHeightTest() throws IOException, Interru TextArea flattenTextArea = new TextArea("text area"); flattenTextArea.setInteractive(false); - flattenTextArea.setProperty(FormProperty.FORM_FIELD_VALUE, "Font\n size \nof this\nText Area will not " + flattenTextArea.setProperty(FormProperty.FORM_FIELD_VALUE, "Font\n size \nof this\nText Area will not " + "\nbe approximated\nbased on the content\nbecause height is not set"); flattenTextArea.setProperty(Property.BORDER, new SolidBorder(1f)); flattenTextArea.setFontSize(0); @@ -337,7 +337,8 @@ public void textAreaWithCustomBorderTest() throws IOException, InterruptedExcept document.add(textArea); TextArea flattenedTextArea = new TextArea("flattened text area"); - flattenedTextArea.setValue("text area with custom border\nBorder shall be orange, 10 points wide and dashed"); + flattenedTextArea.setValue( + "text area with custom border\nBorder shall be orange, 10 points wide and dashed"); flattenedTextArea.setInteractive(false); flattenedTextArea.setBorder(new DashedBorder(ColorConstants.ORANGE, 10)); document.add(flattenedTextArea); diff --git a/forms/src/test/java/com/itextpdf/forms/form/renderer/InputFieldRendererTest.java b/forms/src/test/java/com/itextpdf/forms/form/renderer/InputFieldRendererTest.java index e8dd716920..7f1908b640 100644 --- a/forms/src/test/java/com/itextpdf/forms/form/renderer/InputFieldRendererTest.java +++ b/forms/src/test/java/com/itextpdf/forms/form/renderer/InputFieldRendererTest.java @@ -101,14 +101,14 @@ public void setMinMaxWidthBasedOnFixedWidthWithoutAbsoluteWidthOnElementTest() { @Test public void pdfAConformanceLevelTest() { InputFieldRenderer inputFieldRenderer = new InputFieldRenderer(new InputField("")); - Assert.assertNull(inputFieldRenderer.getConformanceLevel(null)); + Assert.assertNull(inputFieldRenderer.getGenericConformanceLevel(null)); } @Test public void pdfAConformanceLevelWithDocumentTest() { PdfDocument pdfDocument = new PdfDocument(new PdfWriter(new ByteArrayOutputStream())); InputFieldRenderer inputFieldRenderer = new InputFieldRenderer(new InputField("")); - Assert.assertNull(inputFieldRenderer.getConformanceLevel(pdfDocument)); + Assert.assertNull(inputFieldRenderer.getGenericConformanceLevel(pdfDocument)); } @Test @@ -116,7 +116,7 @@ public void pdfAConformanceLevelWithConformanceLevelTest() { PdfDocument pdfDocument = new PdfDocument(new PdfWriter(new ByteArrayOutputStream())); InputFieldRenderer inputFieldRenderer = new InputFieldRenderer(new InputField("")); inputFieldRenderer.setProperty(FormProperty.FORM_CONFORMANCE_LEVEL, PdfAConformanceLevel.PDF_A_1B); - Assert.assertEquals(PdfAConformanceLevel.PDF_A_1B, inputFieldRenderer.getConformanceLevel(pdfDocument)); + Assert.assertEquals(PdfAConformanceLevel.PDF_A_1B, inputFieldRenderer.getGenericConformanceLevel(pdfDocument)); } @Test diff --git a/forms/src/test/java/com/itextpdf/forms/form/renderer/SelectFieldListBoxRendererTest.java b/forms/src/test/java/com/itextpdf/forms/form/renderer/SelectFieldListBoxRendererTest.java index 5d7d1f1087..31c08a6c2b 100644 --- a/forms/src/test/java/com/itextpdf/forms/form/renderer/SelectFieldListBoxRendererTest.java +++ b/forms/src/test/java/com/itextpdf/forms/form/renderer/SelectFieldListBoxRendererTest.java @@ -60,14 +60,14 @@ public void allowLastYLineRecursiveExtractionTest() { @Test public void pdfAConformanceLevelTest() { SelectFieldListBoxRenderer renderer = new SelectFieldListBoxRenderer(new ListBoxField("", 1, false)); - Assert.assertNull(renderer.getConformanceLevel(null)); + Assert.assertNull(renderer.getGenericConformanceLevel(null)); } @Test public void pdfAConformanceLevelWithDocumentTest() { PdfDocument pdfDocument = new PdfDocument(new PdfWriter(new ByteArrayOutputStream())); SelectFieldListBoxRenderer renderer = new SelectFieldListBoxRenderer(new ListBoxField("", 1, false)); - Assert.assertNull(renderer.getConformanceLevel(pdfDocument)); + Assert.assertNull(renderer.getGenericConformanceLevel(pdfDocument)); } @Test @@ -75,7 +75,7 @@ public void pdfAConformanceLevelWithConformanceLevelTest() { PdfDocument pdfDocument = new PdfDocument(new PdfWriter(new ByteArrayOutputStream())); SelectFieldListBoxRenderer renderer = new SelectFieldListBoxRenderer(new ListBoxField("", 1, false)); renderer.setProperty(FormProperty.FORM_CONFORMANCE_LEVEL, PdfAConformanceLevel.PDF_A_1B); - Assert.assertEquals(PdfAConformanceLevel.PDF_A_1B, renderer.getConformanceLevel(pdfDocument)); + Assert.assertEquals(PdfAConformanceLevel.PDF_A_1B, renderer.getGenericConformanceLevel(pdfDocument)); } private static class CustomSelectFieldListBoxRenderer extends SelectFieldListBoxRenderer { diff --git a/forms/src/test/java/com/itextpdf/forms/xfa/XFAFormTest.java b/forms/src/test/java/com/itextpdf/forms/xfa/XFAFormTest.java index 99ae3b1b8e..ca8b343330 100644 --- a/forms/src/test/java/com/itextpdf/forms/xfa/XFAFormTest.java +++ b/forms/src/test/java/com/itextpdf/forms/xfa/XFAFormTest.java @@ -34,7 +34,6 @@ This file is part of the iText (R) project. import java.io.FileInputStream; import java.io.IOException; - import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; @@ -134,4 +133,14 @@ public void extractXFADataTest() throws IOException { Assert.assertNotNull(node); Assert.assertEquals("Number1", node.getNodeName()); } + + @Test + public void extractNodeTextByPathText() throws IOException { + String inFileName = sourceFolder + "TextField1.pdf"; + try (PdfDocument pdfDocument = new PdfDocument(new PdfReader(inFileName))) { + XfaForm xfaForm = new XfaForm(pdfDocument); + Assert.assertEquals("Test", xfaForm.getNodeTextByPath("xdp.datasets.data.form1")); + Assert.assertNull(xfaForm.getNodeTextByPath("xdp.datasets.noElement")); + } + } } diff --git a/forms/src/test/resources/com/itextpdf/forms/form/element/CheckBoxTest/cmp_basicCheckboxTagged.pdf b/forms/src/test/resources/com/itextpdf/forms/form/element/CheckBoxTest/cmp_basicCheckboxTagged.pdf new file mode 100644 index 0000000000..dff63c6315 Binary files /dev/null and b/forms/src/test/resources/com/itextpdf/forms/form/element/CheckBoxTest/cmp_basicCheckboxTagged.pdf differ diff --git a/forms/src/test/resources/com/itextpdf/forms/form/element/ComboBoxFieldTest/cmp_basicComboBoxFieldTagged.pdf b/forms/src/test/resources/com/itextpdf/forms/form/element/ComboBoxFieldTest/cmp_basicComboBoxFieldTagged.pdf new file mode 100644 index 0000000000..92472e0216 Binary files /dev/null and b/forms/src/test/resources/com/itextpdf/forms/form/element/ComboBoxFieldTest/cmp_basicComboBoxFieldTagged.pdf differ diff --git a/forms/src/test/resources/com/itextpdf/forms/form/element/FixedPositionTest/cmp_interactiveMarginLeft.pdf b/forms/src/test/resources/com/itextpdf/forms/form/element/FixedPositionTest/cmp_interactiveMarginLeft.pdf new file mode 100644 index 0000000000..1043f597a2 Binary files /dev/null and b/forms/src/test/resources/com/itextpdf/forms/form/element/FixedPositionTest/cmp_interactiveMarginLeft.pdf differ diff --git a/forms/src/test/resources/com/itextpdf/forms/form/element/FixedPositionTest/cmp_interactiveOutOfBounds.pdf b/forms/src/test/resources/com/itextpdf/forms/form/element/FixedPositionTest/cmp_interactiveOutOfBounds.pdf new file mode 100644 index 0000000000..5c64793466 Binary files /dev/null and b/forms/src/test/resources/com/itextpdf/forms/form/element/FixedPositionTest/cmp_interactiveOutOfBounds.pdf differ diff --git a/forms/src/test/resources/com/itextpdf/forms/form/element/FixedPositionTest/cmp_interactive_fixed_pos.pdf b/forms/src/test/resources/com/itextpdf/forms/form/element/FixedPositionTest/cmp_interactive_fixed_pos.pdf new file mode 100644 index 0000000000..4f8403e20b Binary files /dev/null and b/forms/src/test/resources/com/itextpdf/forms/form/element/FixedPositionTest/cmp_interactive_fixed_pos.pdf differ diff --git a/forms/src/test/resources/com/itextpdf/forms/form/element/FixedPositionTest/cmp_interactive_fixed_pos_on_page.pdf b/forms/src/test/resources/com/itextpdf/forms/form/element/FixedPositionTest/cmp_interactive_fixed_pos_on_page.pdf new file mode 100644 index 0000000000..9242021f48 Binary files /dev/null and b/forms/src/test/resources/com/itextpdf/forms/form/element/FixedPositionTest/cmp_interactive_fixed_pos_on_page.pdf differ diff --git a/forms/src/test/resources/com/itextpdf/forms/form/element/FixedPositionTest/cmp_marginTop.pdf b/forms/src/test/resources/com/itextpdf/forms/form/element/FixedPositionTest/cmp_marginTop.pdf new file mode 100644 index 0000000000..957d17a614 Binary files /dev/null and b/forms/src/test/resources/com/itextpdf/forms/form/element/FixedPositionTest/cmp_marginTop.pdf differ diff --git a/forms/src/test/resources/com/itextpdf/forms/form/element/FixedPositionTest/cmp_ni_setFixedPosition.pdf b/forms/src/test/resources/com/itextpdf/forms/form/element/FixedPositionTest/cmp_ni_setFixedPosition.pdf new file mode 100644 index 0000000000..2f5d2f4faa Binary files /dev/null and b/forms/src/test/resources/com/itextpdf/forms/form/element/FixedPositionTest/cmp_ni_setFixedPosition.pdf differ diff --git a/forms/src/test/resources/com/itextpdf/forms/form/element/FixedPositionTest/cmp_ni_setFixedPositionOnPage.pdf b/forms/src/test/resources/com/itextpdf/forms/form/element/FixedPositionTest/cmp_ni_setFixedPositionOnPage.pdf new file mode 100644 index 0000000000..83efcf98e1 Binary files /dev/null and b/forms/src/test/resources/com/itextpdf/forms/form/element/FixedPositionTest/cmp_ni_setFixedPositionOnPage.pdf differ diff --git a/forms/src/test/resources/com/itextpdf/forms/form/element/FixedPositionTest/cmp_padding.pdf b/forms/src/test/resources/com/itextpdf/forms/form/element/FixedPositionTest/cmp_padding.pdf new file mode 100644 index 0000000000..ef9f7264a6 Binary files /dev/null and b/forms/src/test/resources/com/itextpdf/forms/form/element/FixedPositionTest/cmp_padding.pdf differ diff --git a/forms/src/test/resources/com/itextpdf/forms/form/element/FixedPositionTest/cmp_width.pdf b/forms/src/test/resources/com/itextpdf/forms/form/element/FixedPositionTest/cmp_width.pdf new file mode 100644 index 0000000000..4d98cb8462 Binary files /dev/null and b/forms/src/test/resources/com/itextpdf/forms/form/element/FixedPositionTest/cmp_width.pdf differ diff --git a/forms/src/test/resources/com/itextpdf/forms/form/element/ListBoxFieldTest/cmp_listBoxFieldWithLang.pdf b/forms/src/test/resources/com/itextpdf/forms/form/element/ListBoxFieldTest/cmp_listBoxFieldWithLang.pdf index 9231fd0b3d..4d83d96e23 100644 Binary files a/forms/src/test/resources/com/itextpdf/forms/form/element/ListBoxFieldTest/cmp_listBoxFieldWithLang.pdf and b/forms/src/test/resources/com/itextpdf/forms/form/element/ListBoxFieldTest/cmp_listBoxFieldWithLang.pdf differ diff --git a/forms/src/test/resources/com/itextpdf/forms/form/element/ListBoxFieldTest/cmp_listBoxIsBiggerThenPage.pdf b/forms/src/test/resources/com/itextpdf/forms/form/element/ListBoxFieldTest/cmp_listBoxIsBiggerThenPage.pdf new file mode 100644 index 0000000000..ecc6b8396a Binary files /dev/null and b/forms/src/test/resources/com/itextpdf/forms/form/element/ListBoxFieldTest/cmp_listBoxIsBiggerThenPage.pdf differ diff --git a/forms/src/test/resources/com/itextpdf/forms/form/element/ListBoxFieldTest/cmp_listBoxIsBiggerThenPageNonI.pdf b/forms/src/test/resources/com/itextpdf/forms/form/element/ListBoxFieldTest/cmp_listBoxIsBiggerThenPageNonI.pdf new file mode 100644 index 0000000000..9252d087de Binary files /dev/null and b/forms/src/test/resources/com/itextpdf/forms/form/element/ListBoxFieldTest/cmp_listBoxIsBiggerThenPageNonI.pdf differ diff --git a/forms/src/test/resources/com/itextpdf/forms/form/element/RadioTest/cmp_basicRadioTagged.pdf b/forms/src/test/resources/com/itextpdf/forms/form/element/RadioTest/cmp_basicRadioTagged.pdf new file mode 100644 index 0000000000..a3590cccda Binary files /dev/null and b/forms/src/test/resources/com/itextpdf/forms/form/element/RadioTest/cmp_basicRadioTagged.pdf differ diff --git a/forms/src/test/resources/com/itextpdf/forms/form/element/SignatureFieldAppearanceTest/cmp_flattenEmptySignature.pdf b/forms/src/test/resources/com/itextpdf/forms/form/element/SignatureFieldAppearanceTest/cmp_flattenEmptySignature.pdf new file mode 100644 index 0000000000..2c8795731c Binary files /dev/null and b/forms/src/test/resources/com/itextpdf/forms/form/element/SignatureFieldAppearanceTest/cmp_flattenEmptySignature.pdf differ diff --git a/forms/src/test/resources/com/itextpdf/forms/form/element/SignatureFieldAppearanceTest/emptySignature.pdf b/forms/src/test/resources/com/itextpdf/forms/form/element/SignatureFieldAppearanceTest/emptySignature.pdf new file mode 100644 index 0000000000..314b592557 Binary files /dev/null and b/forms/src/test/resources/com/itextpdf/forms/form/element/SignatureFieldAppearanceTest/emptySignature.pdf differ diff --git a/hyph/pom.xml b/hyph/pom.xml index a027fd5795..a2b7ab5e38 100644 --- a/hyph/pom.xml +++ b/hyph/pom.xml @@ -1,12 +1,15 @@ 4.0.0 + com.itextpdf root - 8.0.3 + 8.0.4 + hyph + iText - hyph XML files that can be used for hyphenation https://itextpdf.com/ @@ -15,9 +18,11 @@ Various licenses (see individual files) + true + diff --git a/io/pom.xml b/io/pom.xml index 23be807e1f..adc2d64100 100644 --- a/io/pom.xml +++ b/io/pom.xml @@ -1,14 +1,18 @@ 4.0.0 + com.itextpdf root - 8.0.3 + 8.0.4 + io + iText - io https://itextpdf.com/ + com.itextpdf @@ -28,6 +32,7 @@ test + @@ -38,6 +43,7 @@ **/*.html **/*.txt **/*.properties + **/*.json diff --git a/io/src/main/java/com/itextpdf/io/exceptions/IoExceptionMessageConstant.java b/io/src/main/java/com/itextpdf/io/exceptions/IoExceptionMessageConstant.java index 5fe0ef012d..7603dae2bd 100644 --- a/io/src/main/java/com/itextpdf/io/exceptions/IoExceptionMessageConstant.java +++ b/io/src/main/java/com/itextpdf/io/exceptions/IoExceptionMessageConstant.java @@ -140,6 +140,7 @@ public final class IoExceptionMessageConstant { public static final String PAGE_NUMBER_MUST_BE_GT_EQ_1 = "Page number must be >= 1."; public static final String PDF_HEADER_NOT_FOUND = "PDF header not found."; public static final String PDF_STARTXREF_NOT_FOUND = "PDF startxref not found."; + public static final String PDF_EOF_NOT_FOUND = "PDF \"%%EOF\" marker is not found."; public static final String PHOTOMETRIC_IS_NOT_SUPPORTED = "Photometric {0} is not supported."; public static final String PLANAR_IMAGES_ARE_NOT_SUPPORTED = "Planar images are not supported."; public static final String PNG_IMAGE_EXCEPTION = "PNG image exception."; diff --git a/io/src/main/java/com/itextpdf/io/font/Type1Font.java b/io/src/main/java/com/itextpdf/io/font/Type1Font.java index 81d7501cee..8839e6f434 100644 --- a/io/src/main/java/com/itextpdf/io/font/Type1Font.java +++ b/io/src/main/java/com/itextpdf/io/font/Type1Font.java @@ -81,6 +81,26 @@ protected Type1Font(String baseFont) { getFontNames().setFontName(baseFont); } + /** + * Fills missing character codes in {@code codeToGlyph} map. + * + * @param fontEncoding to be used to map unicode values to character codes. + */ + public void initializeGlyphs(FontEncoding fontEncoding) { + for (int i = 0; i < 256; i++) { + final int unicode = fontEncoding.getUnicode(i); + // Original unicodeToGlyph will be the source of widths here + Glyph fontGlyph = unicodeToGlyph.get(unicode); + if (fontGlyph == null) { + continue; + } + + Glyph glyph = new Glyph(i, fontGlyph.getWidth(), unicode, fontGlyph.getChars(), false); + codeToGlyph.put(i, glyph); + unicodeToGlyph.put(glyph.getUnicode(), glyph); + } + } + public boolean isBuiltInFont() { return fontParser != null && fontParser.isBuiltInFont(); } diff --git a/io/src/main/java/com/itextpdf/io/logs/IoLogMessageConstant.java b/io/src/main/java/com/itextpdf/io/logs/IoLogMessageConstant.java index a60f37ebbd..e440ca92e9 100644 --- a/io/src/main/java/com/itextpdf/io/logs/IoLogMessageConstant.java +++ b/io/src/main/java/com/itextpdf/io/logs/IoLogMessageConstant.java @@ -476,6 +476,8 @@ public final class IoLogMessageConstant { public static final String XFDF_UNSUPPORTED_ANNOTATION_ATTRIBUTE = "Xfdf unsupported attribute type"; + @Deprecated + // replaced by com.itextpdf.kernel.logs.KernelLogMessageConstant#XOBJECT_STRUCT_PARENT_INDEX_MISSED_AND_RECREATED public static final String XOBJECT_HAS_NO_STRUCT_PARENTS = "XObject has no StructParents entry in its stream, no " + "entry in ParentTree will be created for the corresponding structure elements"; diff --git a/io/src/main/java/com/itextpdf/io/source/PdfTokenizer.java b/io/src/main/java/com/itextpdf/io/source/PdfTokenizer.java index 3e10003418..360589ca66 100644 --- a/io/src/main/java/com/itextpdf/io/source/PdfTokenizer.java +++ b/io/src/main/java/com/itextpdf/io/source/PdfTokenizer.java @@ -241,6 +241,28 @@ public long getStartxref() throws java.io.IOException { throw new IOException(IoExceptionMessageConstant.PDF_STARTXREF_NOT_FOUND, this); } + /** + * Gets next %%EOF marker in current PDF file. + * + * @return next %%EOF marker position + * + * @throws java.io.IOException in case of input-output related exceptions during PDF document reading + */ + public long getNextEof() throws java.io.IOException { + int arrLength = 128; + String str; + do { + long currentPosition = file.getPosition(); + str = readString(arrLength); + long eofPosition = str.indexOf("%%EOF"); + if (eofPosition >= 0) { + // 6 stands for '%%EOF' length + 1 + return currentPosition + eofPosition + 6; + } + } while (!str.isEmpty()); + throw new IOException(IoExceptionMessageConstant.PDF_EOF_NOT_FOUND, this); + } + public void nextValidToken() throws java.io.IOException { int level = 0; byte[] n1 = null; diff --git a/io/src/main/java/com/itextpdf/io/util/TextUtil.java b/io/src/main/java/com/itextpdf/io/util/TextUtil.java index 306f30c0c7..202aa9dcb1 100644 --- a/io/src/main/java/com/itextpdf/io/util/TextUtil.java +++ b/io/src/main/java/com/itextpdf/io/util/TextUtil.java @@ -38,6 +38,17 @@ public final class TextUtil { private TextUtil() { } + /** + * Checks if the passed code point corresponds to diacritic. + * + * @param codePoint the code point to check + * + * @return {@code true} if passed code point is diacritic, {@code false} otherwise + */ + public static boolean isDiacritic(int codePoint) { + return codePoint > 0x0300 && codePoint <= 0x036F; + } + /** * Check if the value of a character belongs to a certain interval * that indicates it's the higher part of a surrogate pair. @@ -71,7 +82,7 @@ public static char lowSurrogate(int codePoint) { /** * Checks if two subsequent characters in a String are - * are the higher and the lower character in a surrogate + * the higher and the lower character in a surrogate * pair (and therefore eligible for conversion to a UTF 32 character). * * @param text the String with the high and low surrogate characters @@ -86,7 +97,7 @@ && isSurrogateHigh(text.charAt(idx)) /** * Checks if two subsequent characters in a character array are - * are the higher and the lower character in a surrogate + * the higher and the lower character in a surrogate * pair (and therefore eligible for conversion to a UTF 32 character). * * @param text the character array with the high and low surrogate characters diff --git a/io/src/main/resources/META-INF/native-image/reflect-config.json b/io/src/main/resources/META-INF/native-image/reflect-config.json new file mode 100644 index 0000000000..0418967ee9 --- /dev/null +++ b/io/src/main/resources/META-INF/native-image/reflect-config.json @@ -0,0 +1,6 @@ +[{ + "condition": { + "typeReachable": "com.itextpdf.io.codec.brotli.dec.Dictionary" + }, + "name":"com.itextpdf.io.codec.brotli.dec.DictionaryData" +}] diff --git a/io/src/main/resources/META-INF/native-image/resource-config.json b/io/src/main/resources/META-INF/native-image/resource-config.json new file mode 100644 index 0000000000..c0e10028ad --- /dev/null +++ b/io/src/main/resources/META-INF/native-image/resource-config.json @@ -0,0 +1,475 @@ +{ + "resources":{ + "includes":[{ + "condition": { + "typeReachable": "com.itextpdf.io.font.AdobeGlyphList" + }, + "pattern":"\\Qcom/itextpdf/io/font/AdobeGlyphList.txt\\E" + }, { + "condition": { + "typeReachable": "com.itextpdf.io.font.Type1Font" + }, + "pattern":"\\Qcom/itextpdf/io/font/afm/Courier.afm\\E" + }, { + "condition": { + "typeReachable": "com.itextpdf.io.font.Type1Font" + }, + "pattern":"\\Qcom/itextpdf/io/font/afm/Courier-Bold.afm\\E" + }, { + "condition": { + "typeReachable": "com.itextpdf.io.font.Type1Font" + }, + "pattern":"\\Qcom/itextpdf/io/font/afm/Courier-BoldOblique.afm\\E" + }, { + "condition": { + "typeReachable": "com.itextpdf.io.font.Type1Font" + }, + "pattern":"\\Qcom/itextpdf/io/font/afm/Courier-Oblique.afm\\E" + }, { + "condition": { + "typeReachable": "com.itextpdf.io.font.Type1Font" + }, + "pattern":"\\Qcom/itextpdf/io/font/afm/Helvetica.afm\\E" + }, { + "condition": { + "typeReachable": "com.itextpdf.io.font.Type1Font" + }, + "pattern":"\\Qcom/itextpdf/io/font/afm/Helvetica-Bold.afm\\E" + }, { + "condition": { + "typeReachable": "com.itextpdf.io.font.Type1Font" + }, + "pattern":"\\Qcom/itextpdf/io/font/afm/Helvetica-BoldOblique.afm\\E" + }, { + "condition": { + "typeReachable": "com.itextpdf.io.font.Type1Font" + }, + "pattern":"\\Qcom/itextpdf/io/font/afm/Helvetica-Oblique.afm\\E" + }, { + "condition": { + "typeReachable": "com.itextpdf.io.font.Type1Font" + }, + "pattern":"\\Qcom/itextpdf/io/font/afm/Symbol.afm\\E" + }, { + "condition": { + "typeReachable": "com.itextpdf.io.font.Type1Font" + }, + "pattern":"\\Qcom/itextpdf/io/font/afm/Times-Bold.afm\\E" + }, { + "condition": { + "typeReachable": "com.itextpdf.io.font.Type1Font" + }, + "pattern":"\\Qcom/itextpdf/io/font/afm/Times-BoldItalic.afm\\E" + }, { + "condition": { + "typeReachable": "com.itextpdf.io.font.Type1Font" + }, + "pattern":"\\Qcom/itextpdf/io/font/afm/Times-Italic.afm\\E" + }, { + "condition": { + "typeReachable": "com.itextpdf.io.font.Type1Font" + }, + "pattern":"\\Qcom/itextpdf/io/font/afm/Times-Roman.afm\\E" + }, { + "condition": { + "typeReachable": "com.itextpdf.io.font.Type1Font" + }, + "pattern":"\\Qcom/itextpdf/io/font/afm/ZapfDingbats.afm\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/78-EUC-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/78-EUC-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/78-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/78-RKSJ-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/78-RKSJ-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/78-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/78ms-RKSJ-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/78ms-RKSJ-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/83pv-RKSJ-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/90ms-RKSJ-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/90ms-RKSJ-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/90msp-RKSJ-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/90msp-RKSJ-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/90pv-RKSJ-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/90pv-RKSJ-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/Add-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/Add-RKSJ-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/Add-RKSJ-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/Add-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/Adobe-CNS1-0\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/Adobe-CNS1-1\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/Adobe-CNS1-2\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/Adobe-CNS1-3\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/Adobe-CNS1-4\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/Adobe-CNS1-5\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/Adobe-CNS1-6\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/Adobe-CNS1-7\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/Adobe-GB1-0\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/Adobe-GB1-1\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/Adobe-GB1-2\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/Adobe-GB1-3\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/Adobe-GB1-4\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/Adobe-GB1-5\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/Adobe-Japan1-0\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/Adobe-Japan1-1\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/Adobe-Japan1-2\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/Adobe-Japan1-3\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/Adobe-Japan1-4\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/Adobe-Japan1-5\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/Adobe-Japan1-6\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/Adobe-Japan1-7\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/Adobe-Korea1-0\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/Adobe-Korea1-1\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/Adobe-Korea1-2\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/Adobe-KR-0\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/Adobe-KR-1\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/Adobe-KR-2\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/Adobe-KR-3\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/Adobe-KR-4\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/Adobe-KR-5\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/Adobe-KR-6\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/Adobe-KR-7\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/Adobe-KR-8\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/Adobe-KR-9\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/B5-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/B5-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/B5pc-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/B5pc-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/CNS1-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/CNS1-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/CNS2-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/CNS2-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/CNS-EUC-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/CNS-EUC-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/ETen-B5-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/ETen-B5-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/ETenms-B5-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/ETenms-B5-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/ETHK-B5-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/ETHK-B5-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/EUC-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/EUC-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/Ext-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/Ext-RKSJ-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/Ext-RKSJ-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/Ext-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/GB-EUC-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/GB-EUC-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/GB-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/GB-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/GBK2K-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/GBK2K-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/GBK-EUC-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/GBK-EUC-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/GBKp-EUC-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/GBKp-EUC-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/GBpc-EUC-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/GBpc-EUC-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/GBT-EUC-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/GBT-EUC-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/GBT-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/GBT-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/GBTpc-EUC-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/GBTpc-EUC-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/Hankaku\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/Hiragana\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/HKdla-B5-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/HKdla-B5-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/HKdlb-B5-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/HKdlb-B5-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/HKgccs-B5-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/HKgccs-B5-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/HKm314-B5-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/HKm314-B5-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/HKm471-B5-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/HKm471-B5-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/HKscs-B5-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/HKscs-B5-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/Identity-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/Identity-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/Katakana\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/KSC-EUC-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/KSC-EUC-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/KSC-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/KSC-Johab-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/KSC-Johab-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/KSC-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/KSCms-UHC-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/KSCms-UHC-HW-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/KSCms-UHC-HW-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/KSCms-UHC-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/KSCpc-EUC-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/KSCpc-EUC-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/NWP-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/NWP-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/RKSJ-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/RKSJ-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/Roman\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/UniAKR-UTF8-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/UniAKR-UTF16-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/UniAKR-UTF32-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/UniCNS-UCS2-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/UniCNS-UCS2-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/UniCNS-UTF8-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/UniCNS-UTF8-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/UniCNS-UTF16-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/UniCNS-UTF16-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/UniCNS-UTF32-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/UniCNS-UTF32-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/UniGB-UCS2-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/UniGB-UCS2-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/UniGB-UTF8-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/UniGB-UTF8-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/UniGB-UTF16-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/UniGB-UTF16-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/UniGB-UTF32-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/UniGB-UTF32-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/UniJIS2004-UTF8-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/UniJIS2004-UTF8-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/UniJIS2004-UTF16-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/UniJIS2004-UTF16-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/UniJIS2004-UTF32-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/UniJIS2004-UTF32-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/UniJIS-UCS2-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/UniJIS-UCS2-HW-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/UniJIS-UCS2-HW-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/UniJIS-UCS2-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/UniJIS-UTF8-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/UniJIS-UTF8-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/UniJIS-UTF16-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/UniJIS-UTF16-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/UniJIS-UTF32-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/UniJIS-UTF32-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/UniJISPro-UCS2-HW-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/UniJISPro-UCS2-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/UniJISPro-UTF8-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/UniJISX0213-UTF32-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/UniJISX0213-UTF32-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/UniJISX02132004-UTF32-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/UniJISX02132004-UTF32-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/UniKS-UCS2-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/UniKS-UCS2-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/UniKS-UTF8-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/UniKS-UTF8-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/UniKS-UTF16-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/UniKS-UTF16-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/UniKS-UTF32-H\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/UniKS-UTF32-V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/V\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/WP-Symbol\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/toUnicode/Adobe-CNS1-UCS2\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/toUnicode/Adobe-GB1-UCS2\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/toUnicode/Adobe-Japan1-UCS2\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/toUnicode/Adobe-Korea1-UCS2\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/toUnicode/Adobe-KR-UCS2\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/cjk_registry.properties\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/HeiseiKakuGo-W5.properties\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/HeiseiMin-W3.properties\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/HYGoThic-Medium.properties\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/HYSMyeongJo-Medium.properties\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/HYSMyeongJoStd-Medium.properties\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/KozMinPro-Regular.properties\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/MHei-Medium.properties\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/MSung-Light.properties\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/MSungStd-Light.properties\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/STSong-Light.properties\\E" + }, { + "pattern":"\\Qcom/itextpdf/io/font/cmap/STSongStd-Light.properties\\E" + }]} +} diff --git a/io/src/test/java/com/itextpdf/io/font/Type1FontTest.java b/io/src/test/java/com/itextpdf/io/font/Type1FontTest.java new file mode 100644 index 0000000000..9d72270a39 --- /dev/null +++ b/io/src/test/java/com/itextpdf/io/font/Type1FontTest.java @@ -0,0 +1,46 @@ +/* + 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.io.font; + +import com.itextpdf.test.ExtendedITextTest; +import com.itextpdf.test.annotations.type.UnitTest; + +import java.io.IOException; +import org.junit.Assert; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +@Category(UnitTest.class) +public class Type1FontTest extends ExtendedITextTest { + + @Test + public void fillUsingEncodingTest() throws IOException { + FontEncoding fontEncoding = FontEncoding.createFontEncoding("WinAnsiEncoding"); + Type1Font type1StdFont = (Type1Font) FontProgramFactory.createFont("Helvetica", true); + Assert.assertEquals(149, type1StdFont.codeToGlyph.size()); + type1StdFont.initializeGlyphs(fontEncoding); + Assert.assertEquals(217, type1StdFont.codeToGlyph.size()); + Assert.assertEquals(0x2013, type1StdFont.codeToGlyph.get(150).getUnicode()); + Assert.assertArrayEquals(new char[]{(char)0x2013}, type1StdFont.codeToGlyph.get(150).getChars()); + } +} diff --git a/io/src/test/java/com/itextpdf/io/source/PdfTokenizerTest.java b/io/src/test/java/com/itextpdf/io/source/PdfTokenizerTest.java index ab33544cae..91cb7ddab3 100644 --- a/io/src/test/java/com/itextpdf/io/source/PdfTokenizerTest.java +++ b/io/src/test/java/com/itextpdf/io/source/PdfTokenizerTest.java @@ -204,6 +204,47 @@ public void readFullyThenReadStringTest() throws IOException { Assert.assertEquals("15", tok.readString(data.length())); } + @Test + public void getNextEofShortTextTest() throws IOException { + String data = "some text to test \ngetting end of\n file logic%%EOF"; + + RandomAccessSourceFactory factory = new RandomAccessSourceFactory(); + try (PdfTokenizer tok = new PdfTokenizer(new RandomAccessFileOrArray( + factory.createSource(data.getBytes(StandardCharsets.ISO_8859_1))))) { + long eofPosition = tok.getNextEof(); + Assert.assertEquals(data.length() + 1, eofPosition); + } + } + + @Test + public void getNextEofLongTextTest() throws IOException { + String data = "some text to test \ngetting end of\n file logic"; + StringBuilder stringBuilder = new StringBuilder(); + for (int i = 0; i < 20; ++i) { + stringBuilder.append(data); + } + stringBuilder.append("%%EOF"); + + RandomAccessSourceFactory factory = new RandomAccessSourceFactory(); + try (PdfTokenizer tok = new PdfTokenizer(new RandomAccessFileOrArray( + factory.createSource(stringBuilder.toString().getBytes(StandardCharsets.ISO_8859_1))))) { + long eofPosition = tok.getNextEof(); + Assert.assertEquals(data.length() * 20 + 6, eofPosition); + } + } + + @Test + public void getNextEofSeveralEofTest() throws IOException { + String data = "some text %%EOFto test \nget%%EOFting end of\n fil%%EOFe logic%%EOF"; + + RandomAccessSourceFactory factory = new RandomAccessSourceFactory(); + try (PdfTokenizer tok = new PdfTokenizer(new RandomAccessFileOrArray( + factory.createSource(data.getBytes(StandardCharsets.ISO_8859_1))))) { + long eofPosition = tok.getNextEof(); + Assert.assertEquals(data.indexOf("%%EOF") + 6, eofPosition); + } + } + @Test public void getDecodedStringContentTest() throws IOException { String data = "/Name1 15"; diff --git a/io/src/test/java/com/itextpdf/io/util/TextUtilTest.java b/io/src/test/java/com/itextpdf/io/util/TextUtilTest.java index bfb484c292..b6f9524482 100644 --- a/io/src/test/java/com/itextpdf/io/util/TextUtilTest.java +++ b/io/src/test/java/com/itextpdf/io/util/TextUtilTest.java @@ -112,6 +112,12 @@ public void isMarkNegativeTest() { Assert.assertFalse(TextUtil.isMark(glyph)); } + @Test + public void isDiacriticTest() { + Assert.assertTrue(TextUtil.isDiacritic("\u0303".charAt(0))); + Assert.assertFalse(TextUtil.isDiacritic("\u006b".charAt(0))); + } + private void helper(boolean expected, int currentCRPosition, Glyph...glyphs) { GlyphLine glyphLine = new GlyphLine(Arrays.asList(glyphs)); Assert.assertTrue(expected == TextUtil.isCarriageReturnFollowedByLineFeed(glyphLine, currentCRPosition)); diff --git a/itext7core/pom.xml b/itext7core/pom.xml index 01a0c26454..629ac34fa4 100644 --- a/itext7core/pom.xml +++ b/itext7core/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.itextpdf itext7-core - 8.0.3 + 8.0.4 pom iText 7 Core A Free Java-PDF library diff --git a/itextcore/pom.xml b/itextcore/pom.xml index d7a7186205..5bfa3242f2 100644 --- a/itextcore/pom.xml +++ b/itextcore/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.itextpdf itext-core - 8.0.3 + 8.0.4 pom iText Core A Free Java-PDF library diff --git a/kernel/pom.xml b/kernel/pom.xml index 5975bffaad..4f5ad03a9d 100644 --- a/kernel/pom.xml +++ b/kernel/pom.xml @@ -1,17 +1,22 @@ 4.0.0 + com.itextpdf root - 8.0.3 + 8.0.4 + kernel + iText - kernel https://itextpdf.com/ + **/com/itextpdf/kernel/xmp/** + com.itextpdf @@ -36,6 +41,18 @@ test + + + + + src/main/resources + + **/*.json + + + + + bouncy-castle-test diff --git a/kernel/src/main/java/com/itextpdf/kernel/actions/data/ITextCoreProductData.java b/kernel/src/main/java/com/itextpdf/kernel/actions/data/ITextCoreProductData.java index 2124d67f34..6c7663756a 100644 --- a/kernel/src/main/java/com/itextpdf/kernel/actions/data/ITextCoreProductData.java +++ b/kernel/src/main/java/com/itextpdf/kernel/actions/data/ITextCoreProductData.java @@ -30,7 +30,7 @@ This file is part of the iText (R) project. */ public final class ITextCoreProductData { private static final String CORE_PUBLIC_PRODUCT_NAME = "Core"; - private static final String CORE_VERSION = "8.0.3"; + private static final String CORE_VERSION = "8.0.4"; private static final int CORE_COPYRIGHT_SINCE = 2000; private static final int CORE_COPYRIGHT_TO = 2024; diff --git a/kernel/src/main/java/com/itextpdf/kernel/colors/CalGray.java b/kernel/src/main/java/com/itextpdf/kernel/colors/CalGray.java index 03f53da9b5..774be95c61 100644 --- a/kernel/src/main/java/com/itextpdf/kernel/colors/CalGray.java +++ b/kernel/src/main/java/com/itextpdf/kernel/colors/CalGray.java @@ -24,21 +24,49 @@ This file is part of the iText (R) project. import com.itextpdf.kernel.pdf.colorspace.PdfCieBasedCs; +/** + * Representation of a CalGray color space. + */ public class CalGray extends Color { + /** + * Creates a new CalGray color space using the given {@link PdfCieBasedCs} color space. + * + * @param cs Color space + */ public CalGray(PdfCieBasedCs.CalGray cs) { this(cs, 0f); } + /** + * Creates a new CalGray color space using the given {@link PdfCieBasedCs} color space and color value. + * + * @param cs Color space + * @param value Gray color value + */ public CalGray(PdfCieBasedCs.CalGray cs, float value) { super(cs, new float[]{value}); } + /** + * Creates a new CalGray color space using the given white point and color value. + * + * @param whitePoint Color values for defining the white point + * @param value Gray color value + */ public CalGray(float[] whitePoint, float value) { super(new PdfCieBasedCs.CalGray(whitePoint), new float[]{value}); } + /** + * Creates a new CalGray color space using the given white point, black point, gamma and color value. + * + * @param whitePoint Color values for defining the white point + * @param blackPoint Color values for defining the black point + * @param gamma Gamma correction + * @param value Gray color value + */ public CalGray(float[] whitePoint, float[] blackPoint, float gamma, float value) { this(new PdfCieBasedCs.CalGray(whitePoint, blackPoint, gamma), value); } diff --git a/kernel/src/main/java/com/itextpdf/kernel/colors/CalRgb.java b/kernel/src/main/java/com/itextpdf/kernel/colors/CalRgb.java index 1f1ddf5e06..5a7613afac 100644 --- a/kernel/src/main/java/com/itextpdf/kernel/colors/CalRgb.java +++ b/kernel/src/main/java/com/itextpdf/kernel/colors/CalRgb.java @@ -24,21 +24,50 @@ This file is part of the iText (R) project. import com.itextpdf.kernel.pdf.colorspace.PdfCieBasedCs; +/** + * Representation of a CalRgb color space. + */ public class CalRgb extends Color { + /** + * Creates a new CalRgb color using the given {@link PdfCieBasedCs} color space. + * + * @param cs Color space + */ public CalRgb(PdfCieBasedCs.CalRgb cs) { this(cs, new float[cs.getNumberOfComponents()]); } + /** + * Creates a new CalRgb color using the given {@link PdfCieBasedCs} color space and RGB color values. + * + * @param cs Color space + * @param value RGB color values + */ public CalRgb(PdfCieBasedCs.CalRgb cs, float[] value) { super(cs, value); } + /** + * Creates a new CalRgb color using the given white point and RGB color values. + * + * @param whitePoint Color values for defining the white point + * @param value RGB color values + */ public CalRgb(float[] whitePoint, float[] value) { super(new PdfCieBasedCs.CalRgb(whitePoint), value); } + /** + * Creates a new CalRgb color using the given white point, black point, gamma, matrix and RGB color values. + * + * @param whitePoint Color values for defining the white point + * @param blackPoint Color values for defining the black point + * @param gamma Gamma correction + * @param matrix Matrix correction + * @param value RGB color value + */ public CalRgb(float[] whitePoint, float[] blackPoint, float[] gamma, float[] matrix, float[] value) { this(new PdfCieBasedCs.CalRgb(whitePoint, blackPoint, gamma, matrix), value); } diff --git a/kernel/src/main/java/com/itextpdf/kernel/colors/DeviceN.java b/kernel/src/main/java/com/itextpdf/kernel/colors/DeviceN.java index 5b9e16e074..061d5dda54 100644 --- a/kernel/src/main/java/com/itextpdf/kernel/colors/DeviceN.java +++ b/kernel/src/main/java/com/itextpdf/kernel/colors/DeviceN.java @@ -29,13 +29,27 @@ This file is part of the iText (R) project. import java.util.Arrays; import java.util.List; +/** + * Representation of a DeviceN color space. + */ public class DeviceN extends Color { + /** + * Creates a DeviceN color using the given {@link PdfSpecialCs} color space. + * + * @param cs Color space + */ public DeviceN(PdfSpecialCs.DeviceN cs) { this(cs, getDefaultColorants(cs.getNumberOfComponents())); } + /** + * Creates a DeviceN color using the given {@link PdfSpecialCs} color space and color values. + * + * @param cs Color space + * @param value Color component values + */ public DeviceN(PdfSpecialCs.DeviceN cs, float[] value) { super(cs, value); } @@ -43,10 +57,10 @@ public DeviceN(PdfSpecialCs.DeviceN cs, float[] value) { /** * Creates a color in a new DeviceN color space. * - * @param names the names oif the components - * @param alternateCs the alternate color space + * @param names the names oif the components + * @param alternateCs the alternate color space * @param tintTransform the function to transform color to the alternate color space - * @param value the values for the components of this color + * @param value the values for the components of this color */ public DeviceN(List names, PdfColorSpace alternateCs, IPdfFunction tintTransform, float[] value) { this(new PdfSpecialCs.DeviceN(names, alternateCs, tintTransform), value); diff --git a/kernel/src/main/java/com/itextpdf/kernel/colors/IccBased.java b/kernel/src/main/java/com/itextpdf/kernel/colors/IccBased.java index bb297045fb..49a7f7ac34 100644 --- a/kernel/src/main/java/com/itextpdf/kernel/colors/IccBased.java +++ b/kernel/src/main/java/com/itextpdf/kernel/colors/IccBased.java @@ -28,13 +28,27 @@ This file is part of the iText (R) project. import java.io.InputStream; +/** + * Representation on an ICC Based color space. + */ public class IccBased extends Color { + /** + * Creates an ICC Based color using the given {@link PdfCieBasedCs} color space. + * + * @param cs Color space + */ public IccBased(PdfCieBasedCs.IccBased cs) { this(cs, new float[cs.getNumberOfComponents()]); } + /** + * Creates an ICC Based color using the given {@link PdfCieBasedCs} color space and color values. + * + * @param cs Color space + * @param value Color values + */ public IccBased(PdfCieBasedCs.IccBased cs, float[] value) { super(cs, value); } @@ -61,6 +75,13 @@ public IccBased(InputStream iccStream, float[] value) { this(new PdfCieBasedCs.IccBased(iccStream), value); } + /** + * Creates an ICC Based color using the given ICC profile stream, range and color values. + * + * @param iccStream ICC profile stream. User is responsible for closing the stream. + * @param range Range for color + * @param value Color values + */ public IccBased(InputStream iccStream, float[] range, float[] value) { this(new PdfCieBasedCs.IccBased(iccStream, range), value); if (getNumberOfComponents() * 2 != range.length) diff --git a/kernel/src/main/java/com/itextpdf/kernel/colors/Indexed.java b/kernel/src/main/java/com/itextpdf/kernel/colors/Indexed.java index c8e0ea71a3..bd8a0fdc73 100644 --- a/kernel/src/main/java/com/itextpdf/kernel/colors/Indexed.java +++ b/kernel/src/main/java/com/itextpdf/kernel/colors/Indexed.java @@ -24,13 +24,26 @@ This file is part of the iText (R) project. import com.itextpdf.kernel.pdf.colorspace.PdfColorSpace; +/** + * Representation of an indexed color space. + */ public class Indexed extends Color { - + /** + * Creates an indexed color using the given {@link PdfColorSpace}. + * + * @param colorSpace Object containing the most common properties of color spaces + */ public Indexed(PdfColorSpace colorSpace) { this(colorSpace, 0); } + /** + * Creates an indexed color using the given {@link PdfColorSpace} and color values. + * + * @param colorSpace Object containing the most common properties of color spaces + * @param colorValue Color values + */ public Indexed(PdfColorSpace colorSpace, int colorValue) { super(colorSpace, new float[] {colorValue}); } diff --git a/kernel/src/main/java/com/itextpdf/kernel/colors/Lab.java b/kernel/src/main/java/com/itextpdf/kernel/colors/Lab.java index 77957c9561..58c22df66d 100644 --- a/kernel/src/main/java/com/itextpdf/kernel/colors/Lab.java +++ b/kernel/src/main/java/com/itextpdf/kernel/colors/Lab.java @@ -24,21 +24,49 @@ This file is part of the iText (R) project. import com.itextpdf.kernel.pdf.colorspace.PdfCieBasedCs; +/** + * Representation of a lab color space. + */ public class Lab extends Color { + /** + * Creates a lab color using the given {@link PdfCieBasedCs} color space. + * + * @param cs Color space + */ public Lab(PdfCieBasedCs.Lab cs) { this(cs, new float[cs.getNumberOfComponents()]); } + /** + * Creates a lab color using the given {@link PdfCieBasedCs} color space and color values. + * + * @param cs Color space + * @param value Color values + */ public Lab(PdfCieBasedCs.Lab cs, float[] value) { super(cs, value); } + /** + * Creates a lab color using the given white point and color values. + * + * @param whitePoint Color values for defining the white point + * @param value Color values + */ public Lab(float[] whitePoint, float[] value) { super(new PdfCieBasedCs.Lab(whitePoint), value); } + /** + * Creates a lab color using the given white point, black point and color values. + * + * @param whitePoint Color values for defining the white point + * @param blackPoint Color values for defining the black point + * @param range Range for color + * @param value Color values + */ public Lab(float[] whitePoint, float[] blackPoint, float[] range, float[] value) { this(new PdfCieBasedCs.Lab(whitePoint, blackPoint, range), value); } diff --git a/kernel/src/main/java/com/itextpdf/kernel/colors/PatternColor.java b/kernel/src/main/java/com/itextpdf/kernel/colors/PatternColor.java index bc7b7f1cd4..bae07405b4 100644 --- a/kernel/src/main/java/com/itextpdf/kernel/colors/PatternColor.java +++ b/kernel/src/main/java/com/itextpdf/kernel/colors/PatternColor.java @@ -26,31 +26,64 @@ This file is part of the iText (R) project. import com.itextpdf.kernel.pdf.colorspace.PdfPattern; import com.itextpdf.kernel.pdf.colorspace.PdfSpecialCs; +/** + * Representation of a Pattern Color. + */ public class PatternColor extends Color { private PdfPattern pattern; // The underlying color for uncolored patterns. Will be null for colored ones. private Color underlyingColor; + /** + * Creates a pattern color using the given color pattern object. + * + * @param coloredPattern Color space that uses pattern objects + */ public PatternColor(PdfPattern coloredPattern) { super(new PdfSpecialCs.Pattern(), null); this.pattern = coloredPattern; } + /** + * Creates a pattern color using the given uncolored pattern object and color. + * + * @param uncoloredPattern Tiling pattern object of the color space + * @param color Color object + */ public PatternColor(PdfPattern.Tiling uncoloredPattern, Color color) { this(uncoloredPattern, color.getColorSpace(), color.getColorValue()); } + /** + * Creates a pattern color using the given uncolored pattern object, an underlying color space and color values. + * + * @param uncoloredPattern Tiling pattern object of the color space + * @param underlyingCS Underlying color space object + * @param colorValue Color values + */ public PatternColor(PdfPattern.Tiling uncoloredPattern, PdfColorSpace underlyingCS, float[] colorValue) { this(uncoloredPattern, new PdfSpecialCs.UncoloredTilingPattern(ensureNotPatternCs(underlyingCS)), colorValue); } + /** + * Creates a pattern color using the given uncolored pattern object, uncolored tiling pattern and color values. + * + * @param uncoloredPattern Tiling pattern object of the color space + * @param uncoloredTilingCS Tiling pattern color space + * @param colorValue Color values + */ public PatternColor(PdfPattern.Tiling uncoloredPattern, PdfSpecialCs.UncoloredTilingPattern uncoloredTilingCS, float[] colorValue) { super(uncoloredTilingCS, colorValue); this.pattern = uncoloredPattern; this.underlyingColor = makeColor(uncoloredTilingCS.getUnderlyingColorSpace(), colorValue); } + /** + * Returns the pattern of the color space. + * + * @return PdfPattern object + */ public PdfPattern getPattern() { return pattern; } diff --git a/kernel/src/main/java/com/itextpdf/kernel/colors/Separation.java b/kernel/src/main/java/com/itextpdf/kernel/colors/Separation.java index 0f2aac3462..56d3492127 100644 --- a/kernel/src/main/java/com/itextpdf/kernel/colors/Separation.java +++ b/kernel/src/main/java/com/itextpdf/kernel/colors/Separation.java @@ -26,13 +26,27 @@ This file is part of the iText (R) project. import com.itextpdf.kernel.pdf.colorspace.PdfSpecialCs; import com.itextpdf.kernel.pdf.function.IPdfFunction; +/** + * Representation of a separation color space. + */ public class Separation extends Color { + /** + * Creates a separation color using the given {@link PdfSpecialCs} color space. + * + * @param cs Color space + */ public Separation(PdfSpecialCs.Separation cs) { this(cs, 1f); } + /** + * Creates a separation color using the given {@link PdfSpecialCs} color space and color value. + * + * @param cs Color space + * @param value Color value + */ public Separation(PdfSpecialCs.Separation cs, float value) { super(cs, new float[]{value}); } @@ -40,10 +54,10 @@ public Separation(PdfSpecialCs.Separation cs, float value) { /** * Creates a color in a new separation color space. * - * @param name the name for the separation color - * @param alternateCs the alternative color space - * @param tintTransform the function to transform color to the alternate colorspace - * @param value the color value + * @param name the name for the separation color + * @param alternateCs the alternative color space + * @param tintTransform the function to transform color to the alternate color space + * @param value the color value */ public Separation(String name, PdfColorSpace alternateCs, IPdfFunction tintTransform, float value) { this(new PdfSpecialCs.Separation(name, alternateCs, tintTransform), value); diff --git a/kernel/src/main/java/com/itextpdf/kernel/exceptions/KernelExceptionMessageConstant.java b/kernel/src/main/java/com/itextpdf/kernel/exceptions/KernelExceptionMessageConstant.java index 31478d924d..d68ed00fef 100644 --- a/kernel/src/main/java/com/itextpdf/kernel/exceptions/KernelExceptionMessageConstant.java +++ b/kernel/src/main/java/com/itextpdf/kernel/exceptions/KernelExceptionMessageConstant.java @@ -321,6 +321,8 @@ public final class KernelExceptionMessageConstant { public static final String SHADING_TYPE_NOT_FOUND = "Shading type not found."; public static final String STDCF_NOT_FOUND_ENCRYPTION = "/StdCF not found (encryption)"; public static final String STREAM_SHALL_END_WITH_ENDSTREAM = "Stream shall end with endstream keyword."; + @Deprecated + // Replaced with log message public static final String STRUCT_PARENT_INDEX_NOT_FOUND_IN_TAGGED_OBJECT = "StructParent index not found in " + "tagged object."; public static final String STRUCTURE_ELEMENT_IN_STRUCTURE_DESTINATION_SHALL_BE_AN_INDIRECT_OBJECT = "Structure " diff --git a/kernel/src/main/java/com/itextpdf/kernel/font/DocType1Font.java b/kernel/src/main/java/com/itextpdf/kernel/font/DocType1Font.java index 3c9981b067..e78b73a0ac 100644 --- a/kernel/src/main/java/com/itextpdf/kernel/font/DocType1Font.java +++ b/kernel/src/main/java/com/itextpdf/kernel/font/DocType1Font.java @@ -57,6 +57,7 @@ static Type1Font createFontProgram(PdfDictionary fontDictionary, FontEncoding fo if (!fontDictionary.containsKey(PdfName.FontDescriptor)) { final Type1Font type1StdFont = getType1Font(baseFont); if (type1StdFont != null) { + type1StdFont.initializeGlyphs(fontEncoding); return type1StdFont; } } @@ -64,11 +65,12 @@ static Type1Font createFontProgram(PdfDictionary fontDictionary, FontEncoding fo PdfDictionary fontDesc = fontDictionary.getAsDictionary(PdfName.FontDescriptor); fontProgram.subtype = fontDesc != null ? fontDesc.getAsName(PdfName.Subtype) : null; fillFontDescriptor(fontProgram, fontDesc); - processWidth(fontDictionary,fontEncoding,toUnicode,fontProgram); + initializeGlyphs(fontDictionary, fontEncoding, toUnicode, fontProgram); + return fontProgram; } - static void processWidth(PdfDictionary fontDictionary, FontEncoding fontEncoding, + static void initializeGlyphs(PdfDictionary fontDictionary, FontEncoding fontEncoding, CMapToUnicode toUnicode, DocType1Font fontProgram) { PdfNumber firstCharNumber = fontDictionary.getAsNumber(PdfName.FirstChar); int firstChar = firstCharNumber != null ? Math.max(firstCharNumber.intValue(), 0) : 0; @@ -109,7 +111,7 @@ static Type1Font getType1Font(String baseFont) { try { //if there are no font modifiers, cached font could be used, //otherwise a new instance should be created. - return (Type1Font) FontProgramFactory.createFont(baseFont, true); + return (Type1Font) FontProgramFactory.createFont(baseFont, false); } catch (Exception e ) { return null; } diff --git a/kernel/src/main/java/com/itextpdf/kernel/font/PdfSimpleFont.java b/kernel/src/main/java/com/itextpdf/kernel/font/PdfSimpleFont.java index fbcd7340f8..2ac49fcfac 100644 --- a/kernel/src/main/java/com/itextpdf/kernel/font/PdfSimpleFont.java +++ b/kernel/src/main/java/com/itextpdf/kernel/font/PdfSimpleFont.java @@ -286,24 +286,21 @@ public boolean appendDecodedCodesToGlyphsList(List list, PdfString charac byte[] contentBytes = characterCodes.getValueBytes(); for (byte b : contentBytes) { int code = b & 0xff; - Glyph glyph = null; - CMapToUnicode toUnicodeCMap = getToUnicode(); - if (toUnicodeCMap != null && toUnicodeCMap.lookup(code) != null - && (glyph = getFontProgram().getGlyphByCode(code)) != null) { - if (!Arrays.equals(toUnicodeCMap.lookup(code), glyph.getChars())) { + Glyph glyph = getFontProgram().getGlyphByCode(code); + final int uni = enc.getUnicode(code); + if (glyph == null && uni > -1) { + glyph = getGlyph(uni); + } + + if (glyph != null) { + char[] chars; + CMapToUnicode toUnicodeCMap = getToUnicode(); + if (toUnicodeCMap != null && (chars = toUnicodeCMap.lookup(code)) != null + && !Arrays.equals(chars, glyph.getChars())) { // Copy the glyph because the original one may be reused (e.g. standard Helvetica font program) glyph = new Glyph(glyph); - glyph.setChars(toUnicodeCMap.lookup(code)); + glyph.setChars(chars); } - } else { - int uni = enc.getUnicode(code); - if (uni > -1) { - glyph = getGlyph(uni); - } else if (enc.getBaseEncoding() == null) { - glyph = getFontProgram().getGlyphByCode(code); - } - } - if (glyph != null) { list.add(glyph); } else { Logger logger = LoggerFactory.getLogger(this.getClass()); diff --git a/kernel/src/main/java/com/itextpdf/kernel/font/PdfType0Font.java b/kernel/src/main/java/com/itextpdf/kernel/font/PdfType0Font.java index d4d69fe744..70d058966f 100644 --- a/kernel/src/main/java/com/itextpdf/kernel/font/PdfType0Font.java +++ b/kernel/src/main/java/com/itextpdf/kernel/font/PdfType0Font.java @@ -262,7 +262,7 @@ public Glyph getGlyph(int unicode) { // TODO DEVSIX-7568 handle unicode value with cmap and use only glyphByCode Glyph glyph = getFontProgram().getGlyph(unicode); if (glyph == null && (glyph = notdefGlyphs.get(unicode)) == null) { - // Handle special layout characters like sfthyphen (00AD). + // Handle special layout characters like softhyphen (00AD). // This glyphs will be skipped while converting to bytes Glyph notdef = getFontProgram().getGlyphByCode(0); if (notdef != null) { diff --git a/kernel/src/main/java/com/itextpdf/kernel/font/PdfType1Font.java b/kernel/src/main/java/com/itextpdf/kernel/font/PdfType1Font.java index 62de5c0a6e..05f874f677 100644 --- a/kernel/src/main/java/com/itextpdf/kernel/font/PdfType1Font.java +++ b/kernel/src/main/java/com/itextpdf/kernel/font/PdfType1Font.java @@ -54,9 +54,6 @@ public class PdfType1Font extends PdfSimpleFont { PdfType1Font(PdfDictionary fontDictionary) { super(fontDictionary); newFont = false; - // if there is no FontDescriptor, it is most likely one of the Standard Font with StandardEncoding as base encoding. - // unused variable. - // boolean fillStandardEncoding = !fontDictionary.containsKey(PdfName.FontDescriptor); fontEncoding = DocFontEncoding.createDocFontEncoding(fontDictionary.get(PdfName.Encoding), toUnicode); fontProgram = DocType1Font.createFontProgram(fontDictionary, fontEncoding, toUnicode); diff --git a/kernel/src/main/java/com/itextpdf/kernel/logs/KernelLogMessageConstant.java b/kernel/src/main/java/com/itextpdf/kernel/logs/KernelLogMessageConstant.java index ce81016a87..698f9c3e46 100644 --- a/kernel/src/main/java/com/itextpdf/kernel/logs/KernelLogMessageConstant.java +++ b/kernel/src/main/java/com/itextpdf/kernel/logs/KernelLogMessageConstant.java @@ -49,7 +49,6 @@ public final class KernelLogMessageConstant { "Full compression mode was requested to be switched off in append mode but the original document has " + "cross-reference stream, not cross-reference table. Falling back to cross-reference stream in " + "appended document and switching full compression on"; - public static final String JPXDECODE_FILTER_DECODING = "JPXDecode filter decoding into the bit map is not supported. The stream data would be left in JPEG2000 " + "format"; @@ -90,6 +89,15 @@ public final class KernelLogMessageConstant { public static final String FORMFIELD_ANNOTATION_WILL_NOT_BE_FLATTENED = "Form field annotation flattening is not " + "supported. Use the PdfAcroForm#flattenFields() method instead."; + public static final String INVALID_DDICTIONARY_FIELD_VALUE = "The default configuration dictionary field {0}" + + " has a value of {1}, which is not the required value for this field. The field will not be processed."; + + public static final String STRUCT_PARENT_INDEX_MISSED_AND_RECREATED = + "StructParent index not found in tagged object, so index is recreated."; + + public static final String XOBJECT_STRUCT_PARENT_INDEX_MISSED_AND_RECREATED = + "XObject has no StructParents index in its stream, so index is recreated"; + private KernelLogMessageConstant() { //Private constructor will prevent the instantiation of this class directly } diff --git a/kernel/src/main/java/com/itextpdf/kernel/pdf/DocumentProperties.java b/kernel/src/main/java/com/itextpdf/kernel/pdf/DocumentProperties.java index 61463754db..4ad138cb7a 100644 --- a/kernel/src/main/java/com/itextpdf/kernel/pdf/DocumentProperties.java +++ b/kernel/src/main/java/com/itextpdf/kernel/pdf/DocumentProperties.java @@ -58,4 +58,13 @@ public DocumentProperties setEventCountingMetaInfo(IMetaInfo metaInfo) { this.metaInfo = metaInfo; return this; } + + /** + * Checks if the document event counting meta info was already set. + * + * @return true if the document event counting meta info is set, false otherwise. + */ + public boolean isEventCountingMetaInfoSet() { + return this.metaInfo != null; + } } diff --git a/kernel/src/main/java/com/itextpdf/kernel/pdf/DocumentRevision.java b/kernel/src/main/java/com/itextpdf/kernel/pdf/DocumentRevision.java new file mode 100644 index 0000000000..f28b09aa83 --- /dev/null +++ b/kernel/src/main/java/com/itextpdf/kernel/pdf/DocumentRevision.java @@ -0,0 +1,63 @@ +/* + 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.kernel.pdf; + +import java.util.Set; + +/** + * Class which stores information about single PDF document revision. + */ +public class DocumentRevision { + private final long eofOffset; + private final Set modifiedObjects; + + /** + * Creates {@link DocumentRevision} from end-of-file byte position and a set of indirect references which were + * modified in this document revision. + * + * @param eofOffset end-of-file byte position + * @param modifiedObjects {@link Set} of {@link PdfIndirectReference} objects which were modified + */ + public DocumentRevision(long eofOffset, Set modifiedObjects) { + this.eofOffset = eofOffset; + this.modifiedObjects = modifiedObjects; + } + + /** + * Gets end-of-file byte position. + * + * @return end-of-file byte position + */ + public long getEofOffset() { + return eofOffset; + } + + /** + * Gets objects which were modified in this document revision. + * + * @return {@link Set} of {@link PdfIndirectReference} objects which were modified + */ + public Set getModifiedObjects() { + return modifiedObjects; + } +} diff --git a/kernel/src/main/java/com/itextpdf/kernel/pdf/GenericNameTree.java b/kernel/src/main/java/com/itextpdf/kernel/pdf/GenericNameTree.java index 632a5386ae..eebd5fd10e 100644 --- a/kernel/src/main/java/com/itextpdf/kernel/pdf/GenericNameTree.java +++ b/kernel/src/main/java/com/itextpdf/kernel/pdf/GenericNameTree.java @@ -30,6 +30,8 @@ This file is part of the iText (R) project. import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; +import java.util.function.Consumer; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -62,17 +64,7 @@ protected GenericNameTree(PdfDocument pdfDoc) { * @param value object to add */ public void addEntry(PdfString key, PdfObject value) { - final PdfObject existingVal = items.get(key); - if (existingVal != null) { - final PdfIndirectReference valueRef = value.getIndirectReference(); - if (valueRef != null && valueRef.equals(existingVal.getIndirectReference())) { - return; - } else { - LOGGER.warn(MessageFormatUtil.format(IoLogMessageConstant.NAME_ALREADY_EXISTS_IN_THE_NAME_TREE, key)); - } - } - modified = true; - items.put(key, value); + addEntry(key, value, null); } /** @@ -162,6 +154,30 @@ public PdfDictionary buildTree() { return reduceTree(names, leaves, leaves.length, NODE_SIZE * NODE_SIZE); } + /** + * Add an entry to the name tree. + * + * @param key key of the entry + * @param value object to add + * @param onErrorAction action to perform if such entry exists + */ + protected void addEntry(PdfString key, PdfObject value, Consumer onErrorAction) { + final PdfObject existingVal = items.get(key); + if (existingVal != null) { + final PdfIndirectReference valueRef = value.getIndirectReference(); + if (valueRef != null && valueRef.equals(existingVal.getIndirectReference())) { + return; + } else { + LOGGER.warn(MessageFormatUtil.format(IoLogMessageConstant.NAME_ALREADY_EXISTS_IN_THE_NAME_TREE, key)); + if (onErrorAction != null) { + onErrorAction.accept(pdfDoc); + } + } + } + modified = true; + items.put(key, value); + } + protected final void setItems(LinkedHashMap items) { this.items = items; } diff --git a/kernel/src/main/java/com/itextpdf/kernel/pdf/IsoKey.java b/kernel/src/main/java/com/itextpdf/kernel/pdf/IsoKey.java index ac08950edd..c1b4c58a00 100644 --- a/kernel/src/main/java/com/itextpdf/kernel/pdf/IsoKey.java +++ b/kernel/src/main/java/com/itextpdf/kernel/pdf/IsoKey.java @@ -45,5 +45,6 @@ public enum IsoKey { // PDF/UA Enums CANVAS_BEGIN_MARKED_CONTENT, CANVAS_WRITING_CONTENT, - LAYOUT + LAYOUT, + DUPLICATE_ID_ENTRY } diff --git a/kernel/src/main/java/com/itextpdf/kernel/pdf/OcgPropertiesCopier.java b/kernel/src/main/java/com/itextpdf/kernel/pdf/OcgPropertiesCopier.java index 51e65053ba..fdd41f8552 100644 --- a/kernel/src/main/java/com/itextpdf/kernel/pdf/OcgPropertiesCopier.java +++ b/kernel/src/main/java/com/itextpdf/kernel/pdf/OcgPropertiesCopier.java @@ -25,7 +25,9 @@ This file is part of the iText (R) project. import com.itextpdf.io.logs.IoLogMessageConstant; import com.itextpdf.io.font.PdfEncodings; import com.itextpdf.commons.utils.MessageFormatUtil; +import com.itextpdf.kernel.logs.KernelLogMessageConstant; import com.itextpdf.kernel.pdf.annot.PdfAnnotation; +import com.itextpdf.kernel.pdf.layer.PdfOCProperties; import java.util.ArrayList; import java.util.HashSet; @@ -258,16 +260,17 @@ private static void copyDDictionary(Set fromOcgsToCopy, Pd final PdfDictionary toDDict = toOcProperties.getAsDictionary(PdfName.D); - // The Name field is not copied because it will be given when flushing the PdfOCProperties + OcgPropertiesCopier.copyDStringField(PdfName.Name, fromDDict, toDDict); // Delete the Creator field because the D dictionary are changing toDDict.remove(PdfName.Creator); - // The BaseState field is not copied because for dictionary D BaseState should have the value ON, which is the default + OcgPropertiesCopier.copyDNameField(PdfName.BaseState, fromDDict, toDDict); OcgPropertiesCopier.copyDArrayField(PdfName.ON, fromOcgsToCopy, fromDDict, toDDict, toDocument); OcgPropertiesCopier.copyDArrayField(PdfName.OFF, fromOcgsToCopy, fromDDict, toDDict, toDocument); - // The Intent field is not copied because for dictionary D Intent should have the value View, which is the default + OcgPropertiesCopier.copyDNameField(PdfName.Intent, fromDDict, toDDict); // The AS field is not copied because it will be given when flushing the PdfOCProperties OcgPropertiesCopier.copyDArrayField(PdfName.Order, fromOcgsToCopy, fromDDict, toDDict, toDocument); - // The ListModel field is not copied because it only affects the visual presentation of the layers + // The ListMode field is copied, but it only affects the visual presentation of the layers + OcgPropertiesCopier.copyDNameField(PdfName.ListMode, fromDDict, toDDict); OcgPropertiesCopier.copyDArrayField(PdfName.RBGroups, fromOcgsToCopy, fromDDict, toDDict, toDocument); OcgPropertiesCopier.copyDArrayField(PdfName.Locked, fromOcgsToCopy, fromDDict, toDDict, toDocument); } @@ -280,6 +283,38 @@ private static void attemptToAddObjectToArray(Set fromOcgs } } + private static void copyDNameField(PdfName fieldToCopy, PdfDictionary fromDict, PdfDictionary toDict) { + final PdfName fromName = fromDict.getAsName(fieldToCopy); + if (fromName == null || toDict.getAsName(fieldToCopy) != null) { + return; + } + + if (PdfOCProperties.checkDDictonaryFieldValue(fieldToCopy, fromName)) { + toDict.put(fieldToCopy, fromName); + } else { + Logger logger = LoggerFactory.getLogger(OcgPropertiesCopier.class); + String warnText = MessageFormatUtil.format(KernelLogMessageConstant.INVALID_DDICTIONARY_FIELD_VALUE, + fieldToCopy, fromName); + logger.warn(warnText); + } + } + + private static void copyDStringField(PdfName fieldToCopy,PdfDictionary fromDict, PdfDictionary toDict){ + PdfString fromString = fromDict.getAsString(fieldToCopy); + if (fromString == null || toDict.getAsString(fieldToCopy) != null) { + return; + } + + if (PdfOCProperties.checkDDictonaryFieldValue(fieldToCopy, fromString)) { + toDict.put(fieldToCopy,fromString); + } else { + Logger logger = LoggerFactory.getLogger(OcgPropertiesCopier.class); + String warnText = MessageFormatUtil.format(KernelLogMessageConstant.INVALID_DDICTIONARY_FIELD_VALUE, + fieldToCopy, fromString); + logger.warn(warnText); + } + } + private static void copyDArrayField(PdfName fieldToCopy, Set fromOcgsToCopy, PdfDictionary fromDict, PdfDictionary toDict, PdfDocument toDocument) { if (fromDict.getAsArray(fieldToCopy) == null) { diff --git a/kernel/src/main/java/com/itextpdf/kernel/pdf/PdfAConformanceLevel.java b/kernel/src/main/java/com/itextpdf/kernel/pdf/PdfAConformanceLevel.java index 653992382e..4952621819 100644 --- a/kernel/src/main/java/com/itextpdf/kernel/pdf/PdfAConformanceLevel.java +++ b/kernel/src/main/java/com/itextpdf/kernel/pdf/PdfAConformanceLevel.java @@ -118,4 +118,26 @@ public static PdfAConformanceLevel getConformanceLevel(XMPMeta meta) { conformanceXmpProperty == null ? null : conformanceXmpProperty.getValue()); } } + + /** + * Gets the PdfA conformance level. + * @param possibleConformance the possible candidate for {@link PdfAConformanceLevel} + * @param document the document + * @return the conformance level or null if it's not PDFA + * + * @deprecated since 8.0.4 Will be removed in next major release + */ + @Deprecated + public static PdfAConformanceLevel getPDFAConformance(IConformanceLevel possibleConformance, PdfDocument document){ + if (possibleConformance instanceof PdfAConformanceLevel) { + return (PdfAConformanceLevel) possibleConformance; + } + if (document == null) { + return null; + } + if (document.getConformanceLevel() instanceof PdfAConformanceLevel) { + return (PdfAConformanceLevel) document.getConformanceLevel(); + } + return null; + } } diff --git a/kernel/src/main/java/com/itextpdf/kernel/pdf/PdfCatalog.java b/kernel/src/main/java/com/itextpdf/kernel/pdf/PdfCatalog.java index 05e7fc52e4..a5325ccfa1 100644 --- a/kernel/src/main/java/com/itextpdf/kernel/pdf/PdfCatalog.java +++ b/kernel/src/main/java/com/itextpdf/kernel/pdf/PdfCatalog.java @@ -80,6 +80,9 @@ public class PdfCatalog extends PdfObjectWrapper { // If this flag is false all outline operations will be ignored private boolean outlineMode; + private boolean ocgCopied = false; + + /** * Create {@link PdfCatalog} dictionary. * @@ -454,9 +457,12 @@ public PdfCatalog remove(PdfName key) { * @return boolean indicating if the dictionary needs to be reconstructed */ protected boolean isOCPropertiesMayHaveChanged() { - return ocProperties != null; + return ocProperties != null || ocgCopied; } + void setOcgCopied(boolean ocgCopied) { + this.ocgCopied = ocgCopied; + } PdfPagesTree getPageTree() { return pageTree; } diff --git a/kernel/src/main/java/com/itextpdf/kernel/pdf/PdfDocument.java b/kernel/src/main/java/com/itextpdf/kernel/pdf/PdfDocument.java index 960e1e04eb..2f8dcbafcb 100644 --- a/kernel/src/main/java/com/itextpdf/kernel/pdf/PdfDocument.java +++ b/kernel/src/main/java/com/itextpdf/kernel/pdf/PdfDocument.java @@ -121,6 +121,7 @@ public class PdfDocument implements IEventDispatcher, Closeable { * Not null if document opened either in writing or stamping mode. */ protected PdfWriter writer = null; + /** * PdfReader associated with the document. * Not null if document is opened either in reading or stamping mode. @@ -185,7 +186,6 @@ public class PdfDocument implements IEventDispatcher, Closeable { private final DIContainer diContainer = new DIContainer(); - /** * Open PDF document in reading mode. * @@ -1325,6 +1325,7 @@ public List copyPagesTo(List pagesToCopy, PdfDocument toDocume // Copying OCGs should go after copying LinkAnnotations if (getCatalog() != null && getCatalog().getPdfObject().getAsDictionary(PdfName.OCProperties) != null) { OcgPropertiesCopier.copyOCGProperties(this, toDocument, page2page); + toDocument.getCatalog().setOcgCopied(true); } // It's important to copy tag structure after link annotations were copied, because object content items in tag @@ -2459,7 +2460,8 @@ private void removeUnusedWidgetsFromFields(PdfPage page) { } private void resolveDestinations(PdfDocument toDocument, Map page2page) { - for (final DestinationMutationInfo mutation : pendingDestinationMutations) { + for (int i = 0; i < pendingDestinationMutations.size(); ++i) { + PdfDocument.DestinationMutationInfo mutation = pendingDestinationMutations.get(i); PdfDestination copiedDest = null; copiedDest = getCatalog().copyDestination(mutation.getOriginalDestination().getPdfObject(), page2page, toDocument); diff --git a/kernel/src/main/java/com/itextpdf/kernel/pdf/PdfName.java b/kernel/src/main/java/com/itextpdf/kernel/pdf/PdfName.java index bf7d902905..bc92ee4ee8 100644 --- a/kernel/src/main/java/com/itextpdf/kernel/pdf/PdfName.java +++ b/kernel/src/main/java/com/itextpdf/kernel/pdf/PdfName.java @@ -537,6 +537,7 @@ public class PdfName extends PdfPrimitiveObject implements Comparable { public static final PdfName ML = createDirectName("ML"); public static final PdfName MMType1 = createDirectName("MMType1"); public static final PdfName MN = createDirectName("ML"); + public static final PdfName MH = createDirectName("MH"); public static final PdfName ModDate = createDirectName("ModDate"); public static final PdfName Movie = createDirectName("Movie"); public static final PdfName MR = createDirectName("MR"); diff --git a/kernel/src/main/java/com/itextpdf/kernel/pdf/PdfPage.java b/kernel/src/main/java/com/itextpdf/kernel/pdf/PdfPage.java index 5233dc2769..de06e8c78a 100644 --- a/kernel/src/main/java/com/itextpdf/kernel/pdf/PdfPage.java +++ b/kernel/src/main/java/com/itextpdf/kernel/pdf/PdfPage.java @@ -31,7 +31,10 @@ This file is part of the iText (R) project. import com.itextpdf.kernel.geom.Rectangle; import com.itextpdf.kernel.pdf.action.PdfAction; import com.itextpdf.kernel.pdf.annot.PdfAnnotation; +import com.itextpdf.kernel.pdf.annot.PdfLinkAnnotation; import com.itextpdf.kernel.pdf.annot.PdfMarkupAnnotation; +import com.itextpdf.kernel.pdf.annot.PdfPrinterMarkAnnotation; +import com.itextpdf.kernel.pdf.annot.PdfWidgetAnnotation; import com.itextpdf.kernel.pdf.filespec.PdfFileSpec; import com.itextpdf.kernel.pdf.tagging.PdfStructTreeRoot; import com.itextpdf.kernel.pdf.tagging.StandardRoles; @@ -50,7 +53,6 @@ This file is part of the iText (R) project. import java.util.ArrayList; import java.util.Arrays; import java.util.List; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -858,9 +860,18 @@ public PdfPage addAnnotation(int index, PdfAnnotation annotation, boolean tagAnn if (getDocument().isTagged()) { if (tagAnnotation) { TagTreePointer tagPointer = getDocument().getTagStructureContext().getAutoTaggingPointer(); - if (annotation instanceof PdfMarkupAnnotation && StandardRoles.DOCUMENT.equals(tagPointer.getRole()) + if (!StandardRoles.ANNOT.equals(tagPointer.getRole()) + // "Annot" tag was added starting from PDF 1.5 && PdfVersion.PDF_1_4.compareTo(getDocument().getPdfVersion()) < 0) { - tagPointer.addTag(StandardRoles.ANNOT); + + if (PdfVersion.PDF_2_0.compareTo(getDocument().getPdfVersion()) > 0) { + if (!(annotation instanceof PdfWidgetAnnotation) && !(annotation instanceof PdfLinkAnnotation) + && !(annotation instanceof PdfPrinterMarkAnnotation)) { + tagPointer.addTag(StandardRoles.ANNOT); + } + } else if (annotation instanceof PdfMarkupAnnotation) { + tagPointer.addTag(StandardRoles.ANNOT); + } } PdfPage prevPage = tagPointer.getCurrentPage(); tagPointer.setPageForTagging(this).addAnnotationTag(annotation); diff --git a/kernel/src/main/java/com/itextpdf/kernel/pdf/PdfReader.java b/kernel/src/main/java/com/itextpdf/kernel/pdf/PdfReader.java index 0d82f4f7db..909ccaf7e7 100644 --- a/kernel/src/main/java/com/itextpdf/kernel/pdf/PdfReader.java +++ b/kernel/src/main/java/com/itextpdf/kernel/pdf/PdfReader.java @@ -46,6 +46,7 @@ This file is part of the iText (R) project. import java.io.ByteArrayInputStream; import java.io.Closeable; +import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; @@ -86,6 +87,8 @@ public class PdfReader implements Closeable { private XMPMeta xmpMeta; + private XrefProcessor xrefProcessor = new XrefProcessor(); + protected PdfTokenizer tokens; protected PdfEncryption decrypt; @@ -182,6 +185,17 @@ public PdfReader(String filename) throws IOException { } + /** + * Reads and parses a PDF document. + * + * @param file the file of the document + * @param properties properties of the created reader + * @throws IOException on error + */ + public PdfReader(File file, ReaderProperties properties) throws IOException { + this(file.getAbsolutePath(), properties); + } + PdfReader(IRandomAccessSource byteSource, ReaderProperties properties, boolean closeStream) throws IOException { this.properties = properties; this.tokens = getOffsetTokeniser(byteSource, closeStream); @@ -1000,8 +1014,8 @@ protected void readXref() throws IOException { return; } } catch (XrefCycledReferencesException - | MemoryLimitsAwareException - | InvalidXRefPrevException exceptionWhileReadingXrefStream) { + | MemoryLimitsAwareException + | InvalidXRefPrevException exceptionWhileReadingXrefStream) { throw exceptionWhileReadingXrefStream; } catch (Exception ignored) { // Do nothing. @@ -1122,6 +1136,7 @@ protected PdfDictionary readXrefSection() throws IOException { } } } + processXref(xref); PdfDictionary trailer = (PdfDictionary) readObject(false); PdfObject xrs = trailer.get(PdfName.XRefStm); if (xrs != null && xrs.getType() == PdfObject.NUMBER) { @@ -1250,6 +1265,7 @@ protected boolean readXrefStream(long ptr) throws IOException { ++start; } } + processXref(xref); ptr = prev; if (alreadyVisitedXrefStreams.contains(ptr)) { throw new XrefCycledReferencesException( @@ -1390,6 +1406,10 @@ boolean isMemorySavingMode() { return memorySavingMode; } + void setXrefProcessor(XrefProcessor xrefProcessor) { + this.xrefProcessor = xrefProcessor; + } + private void processArrayReadError() { final String error = MessageFormatUtil.format(KernelExceptionMessageConstant.UNEXPECTED_TOKEN, new String(tokens.getByteContent(), StandardCharsets.UTF_8)); @@ -1565,6 +1585,15 @@ private static PdfTokenizer getOffsetTokeniser(IRandomAccessSource byteSource, b return tok; } + private void processXref(PdfXrefTable xrefTable) throws IOException { + long currentPosition = tokens.getPosition(); + try { + xrefProcessor.processXref(xrefTable, tokens); + } finally { + tokens.seek(currentPosition); + } + } + protected static class ReusableRandomAccessSource implements IRandomAccessSource { private ByteBuffer buffer; @@ -1639,4 +1668,21 @@ public boolean isStricter(StrictnessLevel compareWith) { return compareWith == null || this.levelValue > compareWith.levelValue; } } + + /** + * Class containing a callback which is called on every xref table reading. + */ + static class XrefProcessor { + /** + * Process xref table. + * + * @param xrefTable {@link PdfXrefTable} to be processed + * @param tokenizer {@link PdfTokenizer} to be processed + * + * @throws IOException in case of input-output related exceptions during PDF document reading + */ + void processXref(PdfXrefTable xrefTable, PdfTokenizer tokenizer) throws IOException { + // Do nothing. + } + } } diff --git a/kernel/src/main/java/com/itextpdf/kernel/pdf/PdfRevisionsReader.java b/kernel/src/main/java/com/itextpdf/kernel/pdf/PdfRevisionsReader.java new file mode 100644 index 0000000000..b9c12a7c8a --- /dev/null +++ b/kernel/src/main/java/com/itextpdf/kernel/pdf/PdfRevisionsReader.java @@ -0,0 +1,103 @@ +/* + 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.kernel.pdf; + +import com.itextpdf.io.source.PdfTokenizer; +import com.itextpdf.io.source.RASInputStream; +import com.itextpdf.io.source.RandomAccessFileOrArray; +import com.itextpdf.io.source.WindowRandomAccessSource; +import com.itextpdf.kernel.pdf.PdfReader.XrefProcessor; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Class to retrieve important information about PDF document revisions. + */ +public class PdfRevisionsReader { + private final PdfReader reader; + private List documentRevisions = null; + + /** + * Creates {@link PdfRevisionsReader} class. + * + * @param reader {@link PdfReader} instance from which revisions to be collected + */ + public PdfRevisionsReader(PdfReader reader) { + this.reader = reader; + } + + /** + * Gets information about PDF document revisions. + * + * @return {@link List} of {@link DocumentRevision} objects + * + * @throws IOException in case of input-output related exceptions during PDF document reading + */ + public List getAllRevisions() throws IOException { + if (documentRevisions == null) { + RandomAccessFileOrArray raf = reader.getSafeFile(); + WindowRandomAccessSource source = new WindowRandomAccessSource( + raf.createSourceView(), 0, raf.length()); + + try (InputStream inputStream = new RASInputStream(source); + PdfReader newReader = new PdfReader(inputStream); + PdfDocument newDocument = new PdfDocument(newReader)) { + newDocument.getXref().unmarkReadingCompleted(); + newDocument.getXref().clearAllReferences(); + RevisionsXrefProcessor xrefProcessor = new RevisionsXrefProcessor(); + newReader.setXrefProcessor(xrefProcessor); + newReader.readXref(); + documentRevisions = xrefProcessor.getDocumentRevisions(); + } + Collections.reverse(documentRevisions); + } + return documentRevisions; + } + + static class RevisionsXrefProcessor extends XrefProcessor { + private final List documentRevisions = new ArrayList<>(); + + @Override + void processXref(PdfXrefTable xrefTable, PdfTokenizer tokenizer) throws IOException { + Set modifiedObjects = new HashSet<>(); + for (int i = 0; i < xrefTable.size(); ++i) { + if (xrefTable.get(i) != null) { + modifiedObjects.add(xrefTable.get(i)); + } + } + long eofOffset = tokenizer.getNextEof(); + documentRevisions.add(new DocumentRevision(eofOffset, modifiedObjects)); + xrefTable.clearAllReferences(); + } + + List getDocumentRevisions() { + return documentRevisions; + } + } +} diff --git a/kernel/src/main/java/com/itextpdf/kernel/pdf/PdfWriter.java b/kernel/src/main/java/com/itextpdf/kernel/pdf/PdfWriter.java index ef881a1cc0..ad3100167a 100644 --- a/kernel/src/main/java/com/itextpdf/kernel/pdf/PdfWriter.java +++ b/kernel/src/main/java/com/itextpdf/kernel/pdf/PdfWriter.java @@ -146,6 +146,15 @@ public PdfWriter setCompressionLevel(int compressionLevel) { return this; } + /** + * Gets the writer properties. + * + * @return The {@link WriterProperties} of the current PdfWriter. + */ + public WriterProperties getProperties() { + return properties; + } + /** * Sets the smart mode. *
@@ -220,9 +229,10 @@ protected void flushObject(PdfObject pdfObject, boolean canBeInObjStm) { /** * Copies a PdfObject either stand alone or as part of the PdfDocument passed as documentTo. * - * @param obj object to copy - * @param documentTo optional target document + * @param obj object to copy + * @param documentTo optional target document * @param allowDuplicating allow that some objects will become duplicated by this action + * * @return the copies object */ protected PdfObject copyObject(PdfObject obj, PdfDocument documentTo, boolean allowDuplicating) { @@ -232,11 +242,12 @@ protected PdfObject copyObject(PdfObject obj, PdfDocument documentTo, boolean al /** * Copies a PdfObject either stand alone or as part of the PdfDocument passed as documentTo. * - * @param obj object to copy - * @param documentTo optional target document + * @param obj object to copy + * @param documentTo optional target document * @param allowDuplicating allow that some objects will become duplicated by this action - * @param copyFilter {@link ICopyFilter} a filter to apply while copying arrays and dictionaries - * * Use {@link NullCopyFilter} for no filtering + * @param copyFilter {@link ICopyFilter} a filter to apply while copying arrays and dictionaries + * * Use {@link NullCopyFilter} for no filtering + * * @return the copies object */ protected PdfObject copyObject(PdfObject obj, PdfDocument documentTo, boolean allowDuplicating, diff --git a/kernel/src/main/java/com/itextpdf/kernel/pdf/PdfXrefTable.java b/kernel/src/main/java/com/itextpdf/kernel/pdf/PdfXrefTable.java index 41c98cca6f..7a0423a203 100644 --- a/kernel/src/main/java/com/itextpdf/kernel/pdf/PdfXrefTable.java +++ b/kernel/src/main/java/com/itextpdf/kernel/pdf/PdfXrefTable.java @@ -428,6 +428,14 @@ void markReadingCompleted() { readingCompleted = true; } + /** + * Change the state of the cross-reference table to unmark that reading of the document + * was completed. + */ + void unmarkReadingCompleted() { + readingCompleted = false; + } + /** * Check if reading of the document was completed. * @@ -506,7 +514,7 @@ PdfIndirectReference createNewIndirectReference(PdfDocument document) { } /** - * Clear the state of the cross-reference table. + * Clear the state of the cross-reference table without free references removal. */ void clear() { for (int i = 1; i <= count; i++) { @@ -518,6 +526,16 @@ void clear() { count = 1; } + /** + * Clear the state of the cross-reference table including free references. + */ + void clearAllReferences() { + for (int i = 1; i <= count; i++) { + xref[i] = null; + } + count = 1; + } + private List createSections(PdfDocument document, boolean dropObjectsFromObjectStream) { List sections = new ArrayList<>(); int first = 0; diff --git a/kernel/src/main/java/com/itextpdf/kernel/pdf/canvas/PdfCanvas.java b/kernel/src/main/java/com/itextpdf/kernel/pdf/canvas/PdfCanvas.java index b6a41c3b48..56390a53a9 100644 --- a/kernel/src/main/java/com/itextpdf/kernel/pdf/canvas/PdfCanvas.java +++ b/kernel/src/main/java/com/itextpdf/kernel/pdf/canvas/PdfCanvas.java @@ -192,6 +192,7 @@ public class PdfCanvas { protected List layerDepth; private Stack> tagStructureStack = new Stack<>(); + protected boolean drawingOnPage = false; /** * Creates PdfCanvas from content stream of page, form XObject, pattern etc. @@ -236,6 +237,7 @@ public PdfCanvas(PdfPage page, boolean wrapOldContent) { applyRotation(page); page.setPageRotationInverseMatrixWritten(); } + this.drawingOnPage = true; } /** @@ -721,7 +723,7 @@ public PdfCanvas showText(GlyphLine text) { public PdfCanvas showText(GlyphLine text, Iterator iterator) { checkDefaultDeviceGrayBlackColor(getColorKeyForText()); document.checkIsoConformance(currentGs, IsoKey.FONT_GLYPHS, null, contentStream); - document.checkIsoConformance(tagStructureStack, IsoKey.CANVAS_WRITING_CONTENT); + this.checkIsoConformanceWritingOnContent(); PdfFont font; if ((font = currentGs.getFont()) == null) { throw new PdfException( @@ -845,6 +847,15 @@ public PdfCanvas showText(GlyphLine text, Iterator iter return this; } + /** + * Sets whether we are currently drawing on a page. + * + * @param drawingOnPage {@code true} if we are currently drawing on page {@code false} if not + */ + public void setDrawingOnPage(boolean drawingOnPage) { + this.drawingOnPage = drawingOnPage; + } + /** * Finds horizontal distance between the start of the `from` glyph and end of `to` glyph. * Glyphs with placement are ignored. @@ -898,7 +909,7 @@ private float getWordSpacingAddition(Glyph glyph) { public PdfCanvas showText(PdfArray textArray) { checkDefaultDeviceGrayBlackColor(getColorKeyForText()); document.checkIsoConformance(currentGs, IsoKey.FONT_GLYPHS, null, contentStream); - document.checkIsoConformance(tagStructureStack, IsoKey.CANVAS_WRITING_CONTENT); + this.checkIsoConformanceWritingOnContent(); if (currentGs.getFont() == null) { throw new PdfException( KernelExceptionMessageConstant.FONT_AND_SIZE_MUST_BE_SET_BEFORE_WRITING_ANY_TEXT, currentGs); @@ -951,7 +962,6 @@ public PdfCanvas moveTo(double x, double y) { * @return current canvas. */ public PdfCanvas lineTo(double x, double y) { - document.checkIsoConformance(tagStructureStack, IsoKey.CANVAS_WRITING_CONTENT); contentStream.getOutputStream() .writeDouble(x) .writeSpace() @@ -972,7 +982,6 @@ public PdfCanvas lineTo(double x, double y) { * @return current canvas. */ public PdfCanvas curveTo(double x1, double y1, double x2, double y2, double x3, double y3) { - document.checkIsoConformance(tagStructureStack, IsoKey.CANVAS_WRITING_CONTENT); contentStream.getOutputStream() .writeDouble(x1) .writeSpace() @@ -1000,7 +1009,6 @@ public PdfCanvas curveTo(double x1, double y1, double x2, double y2, double x3, * @return current canvas. */ public PdfCanvas curveTo(double x2, double y2, double x3, double y3) { - document.checkIsoConformance(tagStructureStack, IsoKey.CANVAS_WRITING_CONTENT); contentStream.getOutputStream() .writeDouble(x2) .writeSpace() @@ -1023,7 +1031,6 @@ public PdfCanvas curveTo(double x2, double y2, double x3, double y3) { * @return current canvas. */ public PdfCanvas curveFromTo(double x1, double y1, double x3, double y3) { - document.checkIsoConformance(tagStructureStack, IsoKey.CANVAS_WRITING_CONTENT); contentStream.getOutputStream() .writeDouble(x1) .writeSpace() @@ -1181,7 +1188,6 @@ public static List bezierArc(double x1, double y1, double x2, double y * @return current canvas. */ public PdfCanvas rectangle(double x, double y, double width, double height) { - document.checkIsoConformance(tagStructureStack, IsoKey.CANVAS_WRITING_CONTENT); contentStream.getOutputStream().writeDouble(x). writeSpace(). writeDouble(y). @@ -1285,6 +1291,7 @@ public PdfCanvas closePath() { * @return current canvas. */ public PdfCanvas closePathEoFillStroke() { + checkIsoConformanceWritingOnContent(); checkDefaultDeviceGrayBlackColor(CheckColorMode.FILL_AND_STROKE); contentStream.getOutputStream().writeBytes(bStar); @@ -1353,6 +1360,7 @@ public PdfCanvas eoClip() { * @return current canvas. */ public PdfCanvas closePathStroke() { + checkIsoConformanceWritingOnContent(); contentStream.getOutputStream().writeBytes(s); return this; } @@ -1363,6 +1371,7 @@ public PdfCanvas closePathStroke() { * @return current canvas. */ public PdfCanvas fill() { + checkIsoConformanceWritingOnContent(); checkDefaultDeviceGrayBlackColor(CheckColorMode.FILL); contentStream.getOutputStream().writeBytes(f); @@ -1375,6 +1384,7 @@ public PdfCanvas fill() { * @return current canvas. */ public PdfCanvas fillStroke() { + checkIsoConformanceWritingOnContent(); checkDefaultDeviceGrayBlackColor(CheckColorMode.FILL_AND_STROKE); contentStream.getOutputStream().writeBytes(B); @@ -1387,6 +1397,7 @@ public PdfCanvas fillStroke() { * @return current canvas. */ public PdfCanvas eoFill() { + checkIsoConformanceWritingOnContent(); checkDefaultDeviceGrayBlackColor(CheckColorMode.FILL); contentStream.getOutputStream().writeBytes(fStar); @@ -1399,6 +1410,7 @@ public PdfCanvas eoFill() { * @return current canvas. */ public PdfCanvas eoFillStroke() { + checkIsoConformanceWritingOnContent(); checkDefaultDeviceGrayBlackColor(CheckColorMode.FILL_AND_STROKE); contentStream.getOutputStream().writeBytes(BStar); @@ -2132,7 +2144,9 @@ public PdfCanvas beginMarkedContent(PdfName tag, PdfDictionary properties) { out.write(resources.addProperties(properties)).writeSpace().writeBytes(BDC); } final Tuple2 tuple2 = new Tuple2<>(tag, properties); - document.checkIsoConformance(tagStructureStack, IsoKey.CANVAS_BEGIN_MARKED_CONTENT, null, null, tuple2); + if (this.drawingOnPage){ + document.checkIsoConformance(tagStructureStack, IsoKey.CANVAS_BEGIN_MARKED_CONTENT, null, null, tuple2); + } tagStructureStack.push(tuple2); return this; } @@ -2406,13 +2420,18 @@ private void showTextInt(String text) { throw new PdfException( KernelExceptionMessageConstant.FONT_AND_SIZE_MUST_BE_SET_BEFORE_WRITING_ANY_TEXT, currentGs); } - - document.checkIsoConformance(tagStructureStack, IsoKey.CANVAS_WRITING_CONTENT); + this.checkIsoConformanceWritingOnContent(); document.checkIsoConformance(text, IsoKey.FONT, null, null, currentGs.getFont()); currentGs.getFont().writeText(text, contentStream.getOutputStream()); } + private void checkIsoConformanceWritingOnContent(){ + if (this.drawingOnPage){ + document.checkIsoConformance(tagStructureStack, IsoKey.CANVAS_WRITING_CONTENT); + } + } + private void addToPropertiesAndBeginLayer(IPdfOCG layer) { PdfName name = resources.addProperties(layer.getPdfObject()); contentStream.getOutputStream().write(PdfName.OC).writeSpace() @@ -2463,7 +2482,6 @@ private void applyRotation(PdfPage page) { private PdfCanvas drawArc(double x1, double y1, double x2, double y2, double startAng, double extent, boolean continuous) { - document.checkIsoConformance(tagStructureStack, IsoKey.CANVAS_WRITING_CONTENT); List ar = bezierArc(x1, y1, x2, y2, startAng, extent); if (ar.isEmpty()) { return this; diff --git a/kernel/src/main/java/com/itextpdf/kernel/pdf/layer/PdfOCProperties.java b/kernel/src/main/java/com/itextpdf/kernel/pdf/layer/PdfOCProperties.java index 330e84c1d5..c62dfcdbde 100644 --- a/kernel/src/main/java/com/itextpdf/kernel/pdf/layer/PdfOCProperties.java +++ b/kernel/src/main/java/com/itextpdf/kernel/pdf/layer/PdfOCProperties.java @@ -22,15 +22,13 @@ This file is part of the iText (R) project. */ package com.itextpdf.kernel.pdf.layer; +import com.itextpdf.commons.utils.MessageFormatUtil; import com.itextpdf.io.font.PdfEncodings; -import com.itextpdf.kernel.pdf.PdfArray; -import com.itextpdf.kernel.pdf.PdfDictionary; -import com.itextpdf.kernel.pdf.PdfDocument; -import com.itextpdf.kernel.pdf.PdfIndirectReference; -import com.itextpdf.kernel.pdf.PdfName; -import com.itextpdf.kernel.pdf.PdfObject; -import com.itextpdf.kernel.pdf.PdfObjectWrapper; -import com.itextpdf.kernel.pdf.PdfString; +import com.itextpdf.kernel.logs.KernelLogMessageConstant; +import com.itextpdf.kernel.pdf.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -133,21 +131,23 @@ public PdfObject fillDictionary(boolean removeNonDocumentOcgs) { } getPdfObject().put(PdfName.OCGs, gr); - // Save radio groups. - PdfArray rbGroups = null; - PdfDictionary d = getPdfObject().getAsDictionary(PdfName.D); - if (d != null) { - rbGroups = d.getAsArray(PdfName.RBGroups); - } + PdfDictionary filledDDictionary = new PdfDictionary(); - d = new PdfDictionary(); - if (rbGroups != null) { - d.put(PdfName.RBGroups, rbGroups); + // Save radio groups,Name,BaseState,Intent,ListMode + PdfDictionary dDictionary = getPdfObject().getAsDictionary(PdfName.D); + if (dDictionary != null) { + PdfOCProperties.copyDDictionaryField(PdfName.RBGroups, dDictionary, filledDDictionary); + PdfOCProperties.copyDDictionaryField(PdfName.Name, dDictionary, filledDDictionary); + PdfOCProperties.copyDDictionaryField(PdfName.BaseState, dDictionary, filledDDictionary); + PdfOCProperties.copyDDictionaryField(PdfName.Intent, dDictionary, filledDDictionary); + PdfOCProperties.copyDDictionaryField(PdfName.ListMode, dDictionary, filledDDictionary); } - d.put(PdfName.Name, new PdfString(createUniqueName(), PdfEncodings.UNICODE_BIG)); - getPdfObject().put(PdfName.D, d); + if (filledDDictionary.get(PdfName.Name) == null) { + filledDDictionary.put(PdfName.Name, new PdfString(createUniqueName(), PdfEncodings.UNICODE_BIG)); + } + getPdfObject().put(PdfName.D, filledDDictionary); List docOrder = new ArrayList<>(layers); for (int i = 0; i < docOrder.size(); i++) { @@ -163,7 +163,7 @@ public PdfObject fillDictionary(boolean removeNonDocumentOcgs) { PdfLayer layer = (PdfLayer) element; getOCGOrder(order, layer); } - d.put(PdfName.Order, order); + filledDDictionary.put(PdfName.Order, order); PdfArray off = new PdfArray(); for (Object element : layers) { @@ -171,22 +171,20 @@ public PdfObject fillDictionary(boolean removeNonDocumentOcgs) { if (layer.getTitle() == null && !layer.isOn()) off.add(layer.getIndirectReference()); } - if (off.size() > 0) - d.put(PdfName.OFF, off); - else - d.remove(PdfName.OFF); + + if (off.size() > 0) { + filledDDictionary.put(PdfName.OFF, off); + } PdfArray locked = new PdfArray(); for (PdfLayer layer : layers) { if (layer.getTitle() == null && layer.isLocked()) locked.add(layer.getIndirectReference()); } - if (locked.size() > 0) - d.put(PdfName.Locked, locked); - else - d.remove(PdfName.Locked); + if (locked.size() > 0) { + filledDDictionary.put(PdfName.Locked, locked); + } - d.remove(PdfName.AS); addASEvent(PdfName.View, PdfName.Zoom); addASEvent(PdfName.View, PdfName.View); addASEvent(PdfName.Print, PdfName.Print); @@ -199,6 +197,26 @@ public PdfObject fillDictionary(boolean removeNonDocumentOcgs) { return getPdfObject(); } + /** + * Checks if optional content group default configuration dictionary field value matches + * the required value for this field, if one exists. + * + * @param field default configuration dictionary field. + * @param value value of that field. + * + * @return boolean indicating if field meets requirement. + */ + public static boolean checkDDictonaryFieldValue(PdfName field, PdfObject value) { + // dictionary D BaseState should have the value ON + if (PdfName.BaseState.equals(field) && !PdfName.ON.equals(value)) { + return false; + //for dictionary D Intent should have the value View + } else if (PdfName.Intent.equals(field) && !PdfName.View.equals(value)) { + return false; + } + return true; + } + @Override public void flush() { fillDictionary(); @@ -263,6 +281,20 @@ private static void getOCGOrder(PdfArray order, PdfLayer layer) { order.add(kids); } + private static void copyDDictionaryField(PdfName fieldToAdd, PdfDictionary fromDictionary, PdfDictionary toDictionary) { + PdfObject value = fromDictionary.get(fieldToAdd); + if(value != null) { + if (PdfOCProperties.checkDDictonaryFieldValue(fieldToAdd, value)) { + toDictionary.put(fieldToAdd, value); + } else { + Logger logger = LoggerFactory.getLogger(PdfOCProperties.class); + String warnText = MessageFormatUtil.format(KernelLogMessageConstant.INVALID_DDICTIONARY_FIELD_VALUE, + fieldToAdd, value); + logger.warn(warnText); + } + } + } + private void removeNotRegisteredOcgs() { final PdfDictionary dDict = getPdfObject().getAsDictionary(PdfName.D); diff --git a/kernel/src/main/java/com/itextpdf/kernel/pdf/tagging/McrCheckUtil.java b/kernel/src/main/java/com/itextpdf/kernel/pdf/tagging/McrCheckUtil.java new file mode 100644 index 0000000000..1d54b7a6ae --- /dev/null +++ b/kernel/src/main/java/com/itextpdf/kernel/pdf/tagging/McrCheckUtil.java @@ -0,0 +1,87 @@ +/* + 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.kernel.pdf.tagging; + +import com.itextpdf.kernel.pdf.PdfDictionary; +import com.itextpdf.kernel.pdf.tagutils.ITagTreeIteratorHandler; +import com.itextpdf.kernel.pdf.tagutils.TagTreeIterator; + +/** + * Class that provides methods for searching mcr in tag tree. + */ +public final class McrCheckUtil { + + /** + * Creates a new {@link McrCheckUtil} instance. + */ + private McrCheckUtil() { + // Empty constructor + } + + /** + * Checks if tag structure of TR element contains any mcr. + * + * @param elementTR PdfDictionary of TR element. + * + * @return true if mcr found. + */ + public static boolean isTrContainsMcr(PdfDictionary elementTR) { + TagTreeIterator tagTreeIterator = new TagTreeIterator(new PdfStructElem(elementTR)); + McrCheckUtil.McrTagHandler handler = new McrCheckUtil.McrTagHandler(); + tagTreeIterator.addHandler(handler); + tagTreeIterator.traverse(); + return handler.tagTreeHaveMcr(); + } + + /** + * Search for mcr elements in the TagTree. + */ + private static class McrTagHandler implements ITagTreeIteratorHandler { + + private boolean haveMcr = false; + + /** + * Method returns if tag tree has mcr in it. + */ + public boolean tagTreeHaveMcr() { + return haveMcr; + } + + /** + * Creates a new {@link McrTagHandler} instance. + */ + public McrTagHandler() { + //empty constructor + } + + /** + * {@inheritDoc} + */ + @Override + public void nextElement(IStructureNode elem) { + if ((elem instanceof PdfMcr)) { + haveMcr = true; + } + } + } +} diff --git a/kernel/src/main/java/com/itextpdf/kernel/pdf/tagging/ParentTreeHandler.java b/kernel/src/main/java/com/itextpdf/kernel/pdf/tagging/ParentTreeHandler.java index ce8794375d..9b07837886 100644 --- a/kernel/src/main/java/com/itextpdf/kernel/pdf/tagging/ParentTreeHandler.java +++ b/kernel/src/main/java/com/itextpdf/kernel/pdf/tagging/ParentTreeHandler.java @@ -23,8 +23,9 @@ This file is part of the iText (R) project. package com.itextpdf.kernel.pdf.tagging; import com.itextpdf.io.logs.IoLogMessageConstant; -import com.itextpdf.kernel.exceptions.PdfException; import com.itextpdf.kernel.exceptions.KernelExceptionMessageConstant; +import com.itextpdf.kernel.exceptions.PdfException; +import com.itextpdf.kernel.logs.KernelLogMessageConstant; import com.itextpdf.kernel.pdf.IsoKey; import com.itextpdf.kernel.pdf.PdfArray; import com.itextpdf.kernel.pdf.PdfDictionary; @@ -55,6 +56,7 @@ This file is part of the iText (R) project. * for specified page, by MCID or by struct parent index. */ class ParentTreeHandler { + private static final Logger LOGGER = LoggerFactory.getLogger(ParentTreeHandler.class); private PdfStructTreeRoot structTreeRoot; @@ -70,6 +72,8 @@ class ParentTreeHandler { private Map xObjectToStructParentsInd; + private int maxStructParentIndex = -1; + /** * Init ParentTreeHandler. On init the parent tree is read and stored in this instance. */ @@ -132,7 +136,6 @@ public void savePageStructParentIndexIfNeeded(PdfPage page) { if (page.isFlushed() || pageToPageMcrs.get(indRef) == null) { return; } - // TODO checking for XObject-related mcrs is here to keep up the same behaviour that should be fixed in the scope of DEVSIX-3351 boolean hasNonObjRefMcr = pageToPageMcrs.get(indRef).getPageContentStreamsMcrs().size() > 0 || pageToPageMcrs.get(indRef).getPageResourceXObjects().size() > 0; @@ -152,8 +155,7 @@ public void registerMcr(PdfMcr mcr) { private void registerMcr(PdfMcr mcr, boolean registeringOnInit) { PdfIndirectReference mcrPageIndRef = mcr.getPageIndirectReference(); if (mcrPageIndRef == null || (!(mcr instanceof PdfObjRef) && mcr.getMcid() < 0)) { - Logger logger = LoggerFactory.getLogger(ParentTreeHandler.class); - logger.error(IoLogMessageConstant.ENCOUNTERED_INVALID_MCR); + LOGGER.error(IoLogMessageConstant.ENCOUNTERED_INVALID_MCR); return; } PageMcrsContainer pageMcrs = pageToPageMcrs.get(mcrPageIndRef); @@ -180,15 +182,17 @@ private void registerMcr(PdfMcr mcr, boolean registeringOnInit) { Integer structParent = xObjectStream.getAsInt(PdfName.StructParents); if (structParent != null) { xObjectToStructParentsInd.put(stmIndRef, structParent); + if (registeringOnInit) { + xObjectStream.release(); + } } else { - // TODO DEVSIX-3351 an error is thrown here because right now no /StructParents will be created. - Logger logger = LoggerFactory.getLogger(ParentTreeHandler.class); - logger.error(IoLogMessageConstant.XOBJECT_HAS_NO_STRUCT_PARENTS); + maxStructParentIndex++; + xObjectToStructParentsInd.put(stmIndRef, maxStructParentIndex); + xObjectStream.put(PdfName.StructParents, new PdfNumber(maxStructParentIndex)); + structTreeRoot.getPdfObject().put(PdfName.ParentTreeNextKey, new PdfNumber(maxStructParentIndex + 1)); + LOGGER.warn(KernelLogMessageConstant.XOBJECT_STRUCT_PARENT_INDEX_MISSED_AND_RECREATED); } pageMcrs.putXObjectMcr(stmIndRef, mcr); - if (registeringOnInit) { - xObjectStream.release(); - } } else if (mcr instanceof PdfObjRef) { PdfDictionary obj = ((PdfDictionary) mcr.getPdfObject()).getAsDictionary(PdfName.Obj); if (obj == null || obj.isFlushed()) { @@ -199,7 +203,11 @@ private void registerMcr(PdfMcr mcr, boolean registeringOnInit) { if (n != null) { pageMcrs.putObjectReferenceMcr(n.intValue(), mcr); } else { - throw new PdfException(KernelExceptionMessageConstant.STRUCT_PARENT_INDEX_NOT_FOUND_IN_TAGGED_OBJECT); + maxStructParentIndex++; + pageMcrs.putObjectReferenceMcr(maxStructParentIndex, mcr); + obj.put(PdfName.StructParent, new PdfNumber(maxStructParentIndex)); + structTreeRoot.getPdfObject().put(PdfName.ParentTreeNextKey, new PdfNumber(maxStructParentIndex + 1)); + LOGGER.warn(KernelLogMessageConstant.STRUCT_PARENT_INDEX_MISSED_AND_RECREATED); } } else { pageMcrs.putPageContentStreamMcr(mcr.getMcid(), mcr); @@ -263,7 +271,6 @@ private void registerAllMcrs() { Map parentTreeEntries = new PdfNumTree(structTreeRoot.getDocument().getCatalog(), PdfName.ParentTree).getNumbers(); Set mcrParents = new LinkedHashSet<>(); - int maxStructParentIndex = -1; for (Map.Entry entry : parentTreeEntries.entrySet()) { if (entry.getKey() > maxStructParentIndex) { maxStructParentIndex = (int) entry.getKey(); diff --git a/kernel/src/main/java/com/itextpdf/kernel/pdf/tagging/PdfStructIdTree.java b/kernel/src/main/java/com/itextpdf/kernel/pdf/tagging/PdfStructIdTree.java index 0e89118348..1444bfa5b7 100644 --- a/kernel/src/main/java/com/itextpdf/kernel/pdf/tagging/PdfStructIdTree.java +++ b/kernel/src/main/java/com/itextpdf/kernel/pdf/tagging/PdfStructIdTree.java @@ -22,7 +22,10 @@ This file is part of the iText (R) project. */ package com.itextpdf.kernel.pdf.tagging; +import com.itextpdf.commons.utils.MessageFormatUtil; +import com.itextpdf.io.logs.IoLogMessageConstant; import com.itextpdf.kernel.pdf.GenericNameTree; +import com.itextpdf.kernel.pdf.IsoKey; import com.itextpdf.kernel.pdf.PdfDictionary; import com.itextpdf.kernel.pdf.PdfDocument; import com.itextpdf.kernel.pdf.PdfIndirectReference; @@ -78,4 +81,9 @@ public PdfStructElem getStructElemById(PdfString id) { public PdfStructElem getStructElemById(byte[] id) { return this.getStructElemById(new PdfString(id)); } + + @Override + public void addEntry(PdfString key, PdfObject value) { + super.addEntry(key, value, pdfDoc -> pdfDoc.checkIsoConformance(key, IsoKey.DUPLICATE_ID_ENTRY)); + } } diff --git a/kernel/src/main/java/com/itextpdf/kernel/pdf/tagging/StructureTreeCopier.java b/kernel/src/main/java/com/itextpdf/kernel/pdf/tagging/StructureTreeCopier.java index 3a39dbba8a..1cbed4fc1d 100644 --- a/kernel/src/main/java/com/itextpdf/kernel/pdf/tagging/StructureTreeCopier.java +++ b/kernel/src/main/java/com/itextpdf/kernel/pdf/tagging/StructureTreeCopier.java @@ -244,11 +244,13 @@ private static void copyTo(PdfDocument destDocument, int insertBeforePage, Map

page2page keys and values represent pages from {@code destDocument}. */ - private static void copyTo(PdfDocument destDocument, Map page2page, PdfDocument callingDocument, boolean copyFromDestDocument) { + private static void copyTo(PdfDocument destDocument, Map page2page, PdfDocument callingDocument + , boolean copyFromDestDocument) { copyTo(destDocument, page2page, callingDocument, copyFromDestDocument, -1); } - private static void copyTo(PdfDocument destDocument, Map page2page, PdfDocument callingDocument, boolean copyFromDestDocument, int insertIndex) { + private static void copyTo(PdfDocument destDocument, Map page2page, PdfDocument callingDocument + , boolean copyFromDestDocument, int insertIndex) { CopyStructureResult copiedStructure = copyStructure(destDocument, page2page, callingDocument, copyFromDestDocument); PdfStructTreeRoot destStructTreeRoot = destDocument.getStructTreeRoot(); destStructTreeRoot.makeIndirect(destDocument); @@ -283,7 +285,8 @@ private static void copyTo(PdfDocument destDocument, Map page2 } } - private static CopyStructureResult copyStructure(PdfDocument destDocument, Map page2page, PdfDocument callingDocument, boolean copyFromDestDocument) { + private static CopyStructureResult copyStructure(PdfDocument destDocument, Map page2page + , PdfDocument callingDocument, boolean copyFromDestDocument) { PdfDocument fromDocument = copyFromDestDocument ? destDocument : callingDocument; Map topsToFirstDestPage = new HashMap<>(); Set objectsToCopy = new HashSet<>(); @@ -380,16 +383,23 @@ private static PdfDictionary copyObject(PdfDictionary source, PdfDictionary dest } PdfObject k = source.get(PdfName.K); + PdfDictionary lastCopiedTrPage = null; if (k != null) { if (k.isArray()) { PdfArray kArr = (PdfArray) k; PdfArray newArr = new PdfArray(); for (int i = 0; i < kArr.size(); i++) { - PdfObject copiedKid = copyObjectKid(kArr.get(i), copied, destPage, parentChangePg, copyingParams); + PdfObject copiedKid = copyObjectKid(kArr.get(i), copied, destPage, parentChangePg, copyingParams + , lastCopiedTrPage); if (copiedKid != null) { newArr.add(copiedKid); + if (copiedKid instanceof PdfDictionary + && PdfName.TR.equals(((PdfDictionary) copiedKid).getAsName(PdfName.S))) { + lastCopiedTrPage = destPage; + } } } + if (!newArr.isEmpty()) { if (newArr.size() == 1) { copied.put(PdfName.K, newArr.get(0)); @@ -398,7 +408,8 @@ private static PdfDictionary copyObject(PdfDictionary source, PdfDictionary dest } } } else { - PdfObject copiedKid = copyObjectKid(k, copied, destPage, parentChangePg, copyingParams); + PdfObject copiedKid = copyObjectKid(k, copied, destPage, parentChangePg, copyingParams + , lastCopiedTrPage); if (copiedKid != null) { copied.put(PdfName.K, copiedKid); } @@ -407,7 +418,9 @@ private static PdfDictionary copyObject(PdfDictionary source, PdfDictionary dest return copied; } - private static PdfObject copyObjectKid(PdfObject kid, PdfDictionary copiedParent, PdfDictionary destPage, boolean parentChangePg, StructElemCopyingParams copyingParams) { + private static PdfObject copyObjectKid(PdfObject kid, PdfDictionary copiedParent, PdfDictionary destPage, + boolean parentChangePg, StructElemCopyingParams copyingParams, + PdfDictionary lastCopiedTrPage) { if (kid.isNumber()) { if (!parentChangePg) { copyingParams.getToDocument().getStructTreeRoot().getParentTreeHandler() @@ -416,11 +429,27 @@ private static PdfObject copyObjectKid(PdfObject kid, PdfDictionary copiedParent } } else if (kid.isDictionary()) { PdfDictionary kidAsDict = (PdfDictionary) kid; - // if element is TD and its parent is TR which was copied, then we copy it in any case + //if element is TD and its parent is TR which was copied, then we copy it in any case if (copyingParams.getObjectsToCopy().contains(kidAsDict) || shouldTableElementBeCopied(kidAsDict, copiedParent)) { + //if TR element is not connected to any page, + //it should be copied to the same page as the last copied TR which connected to page + PdfDictionary destination = destPage; + if (PdfName.TR.equals(kidAsDict.getAsName(PdfName.S)) + && !copyingParams.getObjectsToCopy().contains(kidAsDict)) { + if (McrCheckUtil.isTrContainsMcr(kidAsDict)){ + return null; + } + + if (lastCopiedTrPage == null) { + return null; + } else { + destination = lastCopiedTrPage; + } + } boolean hasParent = kidAsDict.containsKey(PdfName.P); - PdfDictionary copiedKid = copyObject(kidAsDict, destPage, parentChangePg, copyingParams); + PdfDictionary copiedKid = copyObject(kidAsDict, destination, parentChangePg, copyingParams); + if (hasParent) { copiedKid.put(PdfName.P, copiedParent); } else { @@ -446,8 +475,9 @@ private static PdfObject copyObjectKid(PdfObject kid, PdfDictionary copiedParent } static boolean shouldTableElementBeCopied(PdfDictionary obj, PdfDictionary parent) { - return (PdfName.TD.equals(obj.get(PdfName.S)) || PdfName.TH.equals(obj.get(PdfName.S))) - && PdfName.TR.equals(parent.get(PdfName.S)); + PdfName role = obj.getAsName(PdfName.S); + return ((PdfName.TD.equals(role) || PdfName.TH.equals(role)) && PdfName.TR.equals(parent.get(PdfName.S))) + || PdfName.TR.equals(role); } private static PdfDictionary copyNamespaceDict(PdfDictionary srcNsDict, StructElemCopyingParams copyingParams) { diff --git a/kernel/src/main/java/com/itextpdf/kernel/pdf/tagutils/ITagTreeIteratorHandler.java b/kernel/src/main/java/com/itextpdf/kernel/pdf/tagutils/ITagTreeIteratorHandler.java index e407defeea..f341617613 100644 --- a/kernel/src/main/java/com/itextpdf/kernel/pdf/tagutils/ITagTreeIteratorHandler.java +++ b/kernel/src/main/java/com/itextpdf/kernel/pdf/tagutils/ITagTreeIteratorHandler.java @@ -36,4 +36,5 @@ public interface ITagTreeIteratorHandler { * @param elem the next element */ void nextElement(IStructureNode elem); + } diff --git a/kernel/src/main/java/com/itextpdf/kernel/pdf/tagutils/RootTagNormalizer.java b/kernel/src/main/java/com/itextpdf/kernel/pdf/tagutils/RootTagNormalizer.java index 8b58a3f670..aefb7cc2bb 100644 --- a/kernel/src/main/java/com/itextpdf/kernel/pdf/tagutils/RootTagNormalizer.java +++ b/kernel/src/main/java/com/itextpdf/kernel/pdf/tagutils/RootTagNormalizer.java @@ -147,10 +147,7 @@ private void wrapAllKidsInTag(PdfStructElem parent, PdfName wrapTagRole, PdfName } private void removeOldRoot(PdfStructElem oldRoot) { - TagTreePointer tagPointer = new TagTreePointer(document); - tagPointer - .setCurrentStructElem(oldRoot) - .removeTag(); + new TagTreePointer(oldRoot, document).removeTag(); } private void logCreatedRootTagHasMappingIssue(PdfNamespace rootTagOriginalNs, IRoleMappingResolver mapping) { diff --git a/kernel/src/main/java/com/itextpdf/kernel/pdf/tagutils/TagTreeIterator.java b/kernel/src/main/java/com/itextpdf/kernel/pdf/tagutils/TagTreeIterator.java index d140c2a328..ed092d3c35 100644 --- a/kernel/src/main/java/com/itextpdf/kernel/pdf/tagutils/TagTreeIterator.java +++ b/kernel/src/main/java/com/itextpdf/kernel/pdf/tagutils/TagTreeIterator.java @@ -81,6 +81,9 @@ public void traverse() { } private static void traverse(IStructureNode elem, Set handlerList) { + if (elem == null) { + return; + } for (ITagTreeIteratorHandler handler : handlerList) { handler.nextElement(elem); } diff --git a/kernel/src/main/java/com/itextpdf/kernel/pdf/tagutils/TagTreePointer.java b/kernel/src/main/java/com/itextpdf/kernel/pdf/tagutils/TagTreePointer.java index 7d31bb29fc..718807f987 100644 --- a/kernel/src/main/java/com/itextpdf/kernel/pdf/tagutils/TagTreePointer.java +++ b/kernel/src/main/java/com/itextpdf/kernel/pdf/tagutils/TagTreePointer.java @@ -730,6 +730,15 @@ PdfStructElem getCurrentStructElem() { return currentStructElem; } + /** + * Applies properties to the current tag. + *

+ * @param properties the properties to be applied to the current tag. + */ + public void applyProperties(AccessibilityProperties properties) { + AccessibilityPropertiesToStructElem.apply(properties, getCurrentStructElem()); + } + private int getNextNewKidPosition() { int nextPos = nextNewKidIndex; nextNewKidIndex = -1; diff --git a/kernel/src/main/java/com/itextpdf/kernel/utils/CompareTool.java b/kernel/src/main/java/com/itextpdf/kernel/utils/CompareTool.java index f8db121d46..c1026c363a 100644 --- a/kernel/src/main/java/com/itextpdf/kernel/utils/CompareTool.java +++ b/kernel/src/main/java/com/itextpdf/kernel/utils/CompareTool.java @@ -179,7 +179,7 @@ public static PdfWriter createTestPdfWriter(String filename) throws FileNotFound * be created, or cannot be opened for any other reason. */ public static PdfWriter createTestPdfWriter(String filename, WriterProperties properties) throws FileNotFoundException { - return new MemoryFirstPdfWriter(filename, properties); + return new MemoryFirstPdfWriter(filename, properties); // Android-Conversion-Replace return new PdfWriter(filename, properties); } /** diff --git a/kernel/src/main/java/com/itextpdf/kernel/utils/ValidationContainer.java b/kernel/src/main/java/com/itextpdf/kernel/utils/ValidationContainer.java index 3723fb649f..4b134a1972 100644 --- a/kernel/src/main/java/com/itextpdf/kernel/utils/ValidationContainer.java +++ b/kernel/src/main/java/com/itextpdf/kernel/utils/ValidationContainer.java @@ -98,4 +98,5 @@ public void addChecker(IValidationChecker checker) { public boolean containsChecker(IValidationChecker checker) { return validationCheckers.contains(checker); } + } diff --git a/kernel/src/main/java/com/itextpdf/kernel/utils/checkers/FontCheckUtil.java b/kernel/src/main/java/com/itextpdf/kernel/utils/checkers/FontCheckUtil.java new file mode 100644 index 0000000000..bfe067896b --- /dev/null +++ b/kernel/src/main/java/com/itextpdf/kernel/utils/checkers/FontCheckUtil.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.kernel.utils.checkers; + +import com.itextpdf.io.util.TextUtil; +import com.itextpdf.kernel.font.PdfFont; + +/** + * Utility class that contains common checks used in both the PDFA and PDFUA module for fonts. + */ +public final class FontCheckUtil { + + private FontCheckUtil(){ + // Empty constructor + } + + /** + * Checks the text by the passed checker and the font. + * + * @param text the text to check + * @param font the font to check + * @param checker the checker which checks the text according to the font + * + * @return {@code -1} if no character passes the check, or index of the first symbol which passes the check + */ + public static int checkGlyphsOfText(String text, PdfFont font, CharacterChecker checker) { + 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 (checker.check(ch, font)) { + return i; + } + } + return -1; + } + + /** + * Character checker which performs check of passed symbol against the font. + */ + public static interface CharacterChecker { + /** + * Checks passed symbol against the font + * + * @param ch character to check + * @param font font to check + * + * @return {@code true} if check passes, otherwise {@code false} + */ + boolean check(int ch, PdfFont font); + } +} diff --git a/kernel/src/main/java/com/itextpdf/kernel/xmp/XMPConst.java b/kernel/src/main/java/com/itextpdf/kernel/xmp/XMPConst.java index 3be053a61d..9a1f859fd5 100644 --- a/kernel/src/main/java/com/itextpdf/kernel/xmp/XMPConst.java +++ b/kernel/src/main/java/com/itextpdf/kernel/xmp/XMPConst.java @@ -212,6 +212,11 @@ public interface XMPConst */ String PART = "part"; + /** + * XMP meta title + */ + String TITLE = "title"; + /** * ISO 19005 revision */ diff --git a/kernel/src/main/resources/META-INF/native-image/reflect-config.json b/kernel/src/main/resources/META-INF/native-image/reflect-config.json new file mode 100644 index 0000000000..2621b74cf5 --- /dev/null +++ b/kernel/src/main/resources/META-INF/native-image/reflect-config.json @@ -0,0 +1,7 @@ +[{ + "condition": { + "typeReachable": "com.itextpdf.kernel.pdf.PdfName" + }, + "name": "com.itextpdf.kernel.pdf.PdfName", + "allDeclaredFields":true +}] diff --git a/kernel/src/test/java/com/itextpdf/kernel/pdf/DocumentPropertiesUnitTest.java b/kernel/src/test/java/com/itextpdf/kernel/pdf/DocumentPropertiesUnitTest.java new file mode 100644 index 0000000000..5abec5124b --- /dev/null +++ b/kernel/src/test/java/com/itextpdf/kernel/pdf/DocumentPropertiesUnitTest.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.kernel.pdf; + +import com.itextpdf.commons.actions.contexts.IMetaInfo; +import com.itextpdf.test.ExtendedITextTest; +import com.itextpdf.test.annotations.type.UnitTest; +import org.junit.Assert; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +@Category(UnitTest.class) +public class DocumentPropertiesUnitTest extends ExtendedITextTest { + + @Test + public void setEventCountingMetaInfoTest() { + DocumentProperties documentProperties = new DocumentProperties(); + documentProperties.setEventCountingMetaInfo(new TestMetaInfo()); + Assert.assertTrue(documentProperties.isEventCountingMetaInfoSet()); + } + + @Test + public void metaInfoIsNotSetTest() { + DocumentProperties documentProperties = new DocumentProperties(); + Assert.assertFalse(documentProperties.isEventCountingMetaInfoSet()); + } + + private static class TestMetaInfo implements IMetaInfo { + } +} diff --git a/kernel/src/test/java/com/itextpdf/kernel/pdf/OcgPropertiesCopierTest.java b/kernel/src/test/java/com/itextpdf/kernel/pdf/OcgPropertiesCopierTest.java index 1d830699df..642d769393 100644 --- a/kernel/src/test/java/com/itextpdf/kernel/pdf/OcgPropertiesCopierTest.java +++ b/kernel/src/test/java/com/itextpdf/kernel/pdf/OcgPropertiesCopierTest.java @@ -638,9 +638,9 @@ public void copyDFieldsToEmptyDocumentTest() throws IOException { Assert.assertNull(dDict.getAsArray(PdfName.Creator)); - Assert.assertEquals("OCConfigName0", dDict.getAsString(PdfName.Name).toUnicodeString()); + Assert.assertEquals("Name", dDict.getAsString(PdfName.Name).toUnicodeString()); - Assert.assertNull(dDict.getAsName(PdfName.BaseState)); + Assert.assertEquals(PdfName.ON, dDict.getAsName(PdfName.BaseState)); PdfArray asArray = dDict.getAsArray(PdfName.AS); Assert.assertEquals(1, asArray.size()); @@ -648,9 +648,9 @@ public void copyDFieldsToEmptyDocumentTest() throws IOException { Assert.assertEquals(PdfName.Print, asArray.getAsDictionary(0).getAsArray(PdfName.Category).getAsName(0)); Assert.assertEquals("noPrint1", asArray.getAsDictionary(0).getAsArray(PdfName.OCGs).getAsDictionary(0).getAsString(PdfName.Name).toUnicodeString()); - Assert.assertNull(dDict.getAsName(PdfName.Intent)); + Assert.assertEquals(PdfName.View, dDict.getAsName(PdfName.Intent)); - Assert.assertNull(dDict.getAsName(PdfName.ListMode)); + Assert.assertEquals(PdfName.VisiblePages, dDict.getAsName(PdfName.ListMode)); } @Test @@ -764,9 +764,9 @@ public void copyDFieldsToDocumentWithDDictTest() throws IOException { Assert.assertNull(dDict.getAsArray(PdfName.Creator)); - Assert.assertEquals("OCConfigName0", dDict.getAsString(PdfName.Name).toUnicodeString()); + Assert.assertEquals("Name", dDict.getAsString(PdfName.Name).toUnicodeString()); - Assert.assertNull(dDict.getAsName(PdfName.BaseState)); + Assert.assertEquals(PdfName.ON, dDict.getAsName(PdfName.BaseState)); PdfArray asArray = dDict.getAsArray(PdfName.AS); Assert.assertEquals(1, asArray.size()); @@ -776,9 +776,9 @@ public void copyDFieldsToDocumentWithDDictTest() throws IOException { Assert.assertEquals("noPrint1", asArray.getAsDictionary(0).getAsArray(PdfName.OCGs).getAsDictionary(0).getAsString(PdfName.Name).toUnicodeString()); Assert.assertEquals("from_noPrint1", asArray.getAsDictionary(0).getAsArray(PdfName.OCGs).getAsDictionary(1).getAsString(PdfName.Name).toUnicodeString()); - Assert.assertNull(dDict.getAsName(PdfName.Intent)); + Assert.assertEquals(PdfName.View, dDict.getAsName(PdfName.Intent)); - Assert.assertNull(dDict.getAsName(PdfName.ListMode)); + Assert.assertEquals(PdfName.VisiblePages, dDict.getAsName(PdfName.ListMode)); } // Copy OCGs from different locations (OCMDs, annotations, content streams, xObjects) test block @@ -1113,12 +1113,13 @@ private static byte[] getDocumentWithAllDFields() throws IOException { pdfResource.makeIndirect(fromDocument); PdfOCProperties ocProperties = fromDocument.getCatalog().getOCProperties(true); + PdfDictionary dDictionary = ocProperties.getPdfObject().getAsDictionary(PdfName.D); // Creator (will be not copied) - ocProperties.getPdfObject().put(PdfName.Creator, new PdfString("CreatorName", PdfEncodings.UNICODE_BIG)); + dDictionary.put(PdfName.Creator, new PdfString("CreatorName", PdfEncodings.UNICODE_BIG)); // Name (will be automatically changed) - ocProperties.getPdfObject().put(PdfName.Name, new PdfString("Name", PdfEncodings.UNICODE_BIG)); + dDictionary.put(PdfName.Name, new PdfString("Name", PdfEncodings.UNICODE_BIG)); // BaseState (will be not copied) - ocProperties.getPdfObject().put(PdfName.BaseState, PdfName.OFF); + dDictionary.put(PdfName.BaseState, PdfName.ON); // AS (will be automatically changed) PdfArray asArray = new PdfArray(); PdfDictionary dict = new PdfDictionary(); @@ -1130,15 +1131,15 @@ private static byte[] getDocumentWithAllDFields() throws IOException { ocgs.add(locked1.getPdfObject()); dict.put(PdfName.OCGs, ocgs); asArray.add(dict); - ocProperties.getPdfObject().put(PdfName.AS, asArray); + dDictionary.put(PdfName.AS, asArray); PdfLayer noPrint1 = new PdfLayer("noPrint1", fromDocument); pdfResource.addProperties(noPrint1.getPdfObject()); noPrint1.setPrint("Print", false); // Intent (will be not copied) - ocProperties.getPdfObject().put(PdfName.Intent, PdfName.Design); + dDictionary.put(PdfName.Intent, PdfName.View); // ListMode (will be not copied) - ocProperties.getPdfObject().put(PdfName.ListMode, PdfName.VisiblePages); + dDictionary.put(PdfName.ListMode, PdfName.VisiblePages); } fromDocBytes = outputStream.toByteArray(); } diff --git a/kernel/src/test/java/com/itextpdf/kernel/pdf/ParentTreeTest.java b/kernel/src/test/java/com/itextpdf/kernel/pdf/ParentTreeTest.java index d8ad0ff9a9..420bbf3c2d 100644 --- a/kernel/src/test/java/com/itextpdf/kernel/pdf/ParentTreeTest.java +++ b/kernel/src/test/java/com/itextpdf/kernel/pdf/ParentTreeTest.java @@ -23,8 +23,10 @@ This file is part of the iText (R) project. package com.itextpdf.kernel.pdf; import com.itextpdf.io.font.constants.StandardFonts; +import com.itextpdf.io.logs.IoLogMessageConstant; import com.itextpdf.kernel.font.PdfFontFactory; import com.itextpdf.kernel.geom.Rectangle; +import com.itextpdf.kernel.logs.KernelLogMessageConstant; import com.itextpdf.kernel.pdf.annot.PdfLinkAnnotation; import com.itextpdf.kernel.pdf.canvas.CanvasTag; import com.itextpdf.kernel.pdf.canvas.PdfCanvas; @@ -33,17 +35,19 @@ This file is part of the iText (R) project. import com.itextpdf.kernel.pdf.tagging.PdfStructElem; import com.itextpdf.kernel.utils.CompareTool; import com.itextpdf.kernel.utils.CompareTool.CompareResult; +import com.itextpdf.test.AssertUtil; import com.itextpdf.test.ExtendedITextTest; +import com.itextpdf.test.annotations.LogMessage; +import com.itextpdf.test.annotations.LogMessages; import com.itextpdf.test.annotations.type.IntegrationTest; +import java.io.ByteArrayOutputStream; +import java.io.IOException; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.experimental.categories.Category; - -import java.io.IOException; - import static org.junit.Assert.assertTrue; @Category(IntegrationTest.class) @@ -350,6 +354,47 @@ public void test06() throws IOException { assertTrue(checkParentTree(outFile, cmpFile)); } + @Test + @LogMessages(messages = { + @LogMessage(messageTemplate = KernelLogMessageConstant.STRUCT_PARENT_INDEX_MISSED_AND_RECREATED, count = 4) + }) + public void allObjRefDontHaveStructParentTest() throws IOException, InterruptedException { + String pdf = sourceFolder + "allObjRefDontHaveStructParent.pdf"; + String outPdf = destinationFolder + "allObjRefDontHaveStructParent.pdf"; + String cmpPdf = sourceFolder + "cmp_allObjRefDontHaveStructParent.pdf"; + + PdfDocument taggedPdf = new PdfDocument(new PdfReader(pdf), new PdfWriter(outPdf)); + taggedPdf.close(); + Assert.assertNull(new CompareTool().compareByContent(outPdf, cmpPdf, destinationFolder, "diff")); + } + + @Test + @LogMessages(messages = { + @LogMessage(messageTemplate = KernelLogMessageConstant.XOBJECT_STRUCT_PARENT_INDEX_MISSED_AND_RECREATED) + }) + public void xObjDoesntHaveStructParentTest() throws IOException, InterruptedException { + String pdf = sourceFolder + "xObjDoesntHaveStructParentTest.pdf"; + String outPdf = destinationFolder + "xObjDoesntHaveStructParentTest.pdf"; + String cmpPdf = sourceFolder + "cmp_xObjDoesntHaveStructParentTest.pdf"; + + PdfDocument taggedPdf = new PdfDocument(new PdfReader(pdf), new PdfWriter(outPdf)); + taggedPdf.close(); + Assert.assertNull(new CompareTool().compareByContent(outPdf, cmpPdf, destinationFolder, "diff")); + } + + @Test + @LogMessages(messages = { + @LogMessage(messageTemplate = IoLogMessageConstant.CREATED_ROOT_TAG_HAS_MAPPING) + }) + public void copyPageWithMultipleDocumentTagsTest() throws IOException { + PdfDocument pdfDoc = new PdfDocument( + new PdfReader(sourceFolder + "pdfWithMultipleDocumentTags.pdf"), + new PdfWriter(new ByteArrayOutputStream())); + + AssertUtil.doesNotThrow(() -> pdfDoc.getTagStructureContext().normalizeDocumentRootTag()); + } + + private boolean checkParentTree(String outFileName, String cmpFileName) throws IOException { PdfReader outReader = CompareTool.createOutputReader(outFileName); PdfDocument outDocument = new PdfDocument(outReader); diff --git a/kernel/src/test/java/com/itextpdf/kernel/pdf/PdfAConformanceLevelTest.java b/kernel/src/test/java/com/itextpdf/kernel/pdf/PdfAConformanceLevelTest.java index 0c236945cc..b99f9cb012 100644 --- a/kernel/src/test/java/com/itextpdf/kernel/pdf/PdfAConformanceLevelTest.java +++ b/kernel/src/test/java/com/itextpdf/kernel/pdf/PdfAConformanceLevelTest.java @@ -22,6 +22,7 @@ This file is part of the iText (R) project. */ package com.itextpdf.kernel.pdf; +import com.itextpdf.io.source.ByteArrayOutputStream; import com.itextpdf.kernel.xmp.XMPConst; import com.itextpdf.kernel.xmp.XMPException; import com.itextpdf.kernel.xmp.XMPMeta; @@ -58,4 +59,31 @@ public void getXmpConformanceBTest() throws XMPException { PdfAConformanceLevel level = PdfAConformanceLevel.getConformanceLevel(meta); Assert.assertEquals(PdfAConformanceLevel.PDF_A_2B, level); } + + @Test + public void getPdfAConformanceLevel01Test() { + Assert.assertNull(PdfAConformanceLevel.getPDFAConformance(null, null)); + } + + @Test + public void getPdfAConformanceLevel02Test() { + Assert.assertEquals(PdfAConformanceLevel.PDF_A_1A, + PdfAConformanceLevel.getPDFAConformance(PdfAConformanceLevel.PDF_A_1A, null)); + } + + + @Test + public void getPdfAConformanceLevel03Test() { + try(PdfDocument pdfDocument = new PdfDocument(new PdfWriter(new ByteArrayOutputStream()))){ + Assert.assertEquals(PdfAConformanceLevel.PDF_A_1A, + PdfAConformanceLevel.getPDFAConformance(PdfAConformanceLevel.PDF_A_1A, pdfDocument)); + } + } + + @Test + public void getPdfAConformanceLevel04Test() { + try(PdfDocument pdfDocument = new PdfDocument(new PdfWriter(new ByteArrayOutputStream()))){ + Assert.assertNull(PdfAConformanceLevel.getPDFAConformance(null, pdfDocument)); + } + } } diff --git a/kernel/src/test/java/com/itextpdf/kernel/pdf/PdfCopyTest.java b/kernel/src/test/java/com/itextpdf/kernel/pdf/PdfCopyTest.java index 3ef9e7e6ad..42a5ab8a91 100644 --- a/kernel/src/test/java/com/itextpdf/kernel/pdf/PdfCopyTest.java +++ b/kernel/src/test/java/com/itextpdf/kernel/pdf/PdfCopyTest.java @@ -60,7 +60,7 @@ public static void beforeClass() { public static void afterClass() { CompareTool.cleanup(destinationFolder); } - + @Test @LogMessages(messages = { @LogMessage(messageTemplate = IoLogMessageConstant.SOURCE_DOCUMENT_HAS_ACROFORM_DICTIONARY), @@ -383,6 +383,22 @@ public void copyPagesLinkAnnotationTest() throws IOException, InterruptedExcepti Assert.assertNull(new CompareTool().compareByContent(outFileName, cmpFileName, destinationFolder)); } + @Test + public void copyDocWithFullDDictionary() throws IOException, InterruptedException { + String outFileName = destinationFolder + "copyDocWithDDictionary.pdf"; + String cmpFileName = sourceFolder + "cmp_copyDocWithDDictionary.pdf"; + + PdfDocument inPdf = new PdfDocument(new PdfReader(sourceFolder + "DocWithDDictionary.pdf")); + PdfDocument outPdf = new PdfDocument(new PdfWriter(outFileName)); + + inPdf.copyPagesTo(1, 1, outPdf); + + inPdf.close(); + outPdf.close(); + + Assert.assertNull(new CompareTool().compareByContent(outFileName, cmpFileName, destinationFolder)); + } + private List getPdfAnnotations(PdfDocument pdfDoc) { int number = pdfDoc.getNumberOfPages(); ArrayList annotations = new ArrayList<>(); diff --git a/kernel/src/test/java/com/itextpdf/kernel/pdf/PdfPagesTest.java b/kernel/src/test/java/com/itextpdf/kernel/pdf/PdfPagesTest.java index e481a7de89..68d623bed7 100644 --- a/kernel/src/test/java/com/itextpdf/kernel/pdf/PdfPagesTest.java +++ b/kernel/src/test/java/com/itextpdf/kernel/pdf/PdfPagesTest.java @@ -181,6 +181,7 @@ public void randomObjectPagesTest() throws IOException { } @Test + // Android-Conversion-Ignore-Test (TODO DEVSIX-8114 Fix randomNumberPagesTest test) public void randomNumberPagesTest() throws IOException { String filename = "randomNumberPagesTest.pdf"; int pageCount = 1000; diff --git a/kernel/src/test/java/com/itextpdf/kernel/pdf/PdfReaderTest.java b/kernel/src/test/java/com/itextpdf/kernel/pdf/PdfReaderTest.java index 91ac8fb39c..67ca4830a4 100644 --- a/kernel/src/test/java/com/itextpdf/kernel/pdf/PdfReaderTest.java +++ b/kernel/src/test/java/com/itextpdf/kernel/pdf/PdfReaderTest.java @@ -59,6 +59,7 @@ This file is part of the iText (R) project. import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; @@ -82,6 +83,8 @@ public class PdfReaderTest extends ExtendedITextTest { static final String creator = "iText 6"; static final String title = "Empty iText 6 Document"; + static final byte[] USER_PASSWORD = "Hello".getBytes(StandardCharsets.ISO_8859_1); + @BeforeClass public static void beforeClass() { createDestinationFolder(DESTINATION_FOLDER); @@ -2758,6 +2761,61 @@ public void invalidXrefTableRebuildsCorrectlyWhenTrailerIsBeforeObjects() throws } } + @Test + public void newPdfReaderConstructorTest() throws IOException { + String filename = SOURCE_FOLDER + "simpleDoc.pdf"; + + PdfReader reader = new PdfReader(new File(filename), new ReaderProperties()); + PdfDocument pdfDoc = new PdfDocument(reader); + Assert.assertEquals(author, pdfDoc.getDocumentInfo().getAuthor()); + Assert.assertEquals(creator, pdfDoc.getDocumentInfo().getCreator()); + Assert.assertEquals(title, pdfDoc.getDocumentInfo().getTitle()); + PdfObject object = pdfDoc.getPdfObject(1); + Assert.assertEquals(PdfObject.DICTIONARY, object.getType()); + Assert.assertTrue(objectTypeEqualTo(object, PdfName.Catalog)); + + object = pdfDoc.getPdfObject(2); + Assert.assertEquals(PdfObject.DICTIONARY, object.getType()); + Assert.assertTrue(objectTypeEqualTo(object, PdfName.Pages)); + + object = pdfDoc.getPdfObject(3); + Assert.assertEquals(PdfObject.DICTIONARY, object.getType()); + + object = pdfDoc.getPdfObject(4); + Assert.assertEquals(PdfObject.DICTIONARY, object.getType()); + Assert.assertTrue(objectTypeEqualTo(object, PdfName.Page)); + + Assert.assertEquals(PdfObject.STREAM, pdfDoc.getPdfObject(5).getType()); + } + + @Test + public void newPdfReaderConstructorPropertiesTest() throws IOException { + String fileName = SOURCE_FOLDER + "simpleDocWithPassword.pdf"; + PdfReader reader = new PdfReader(new File(fileName),new ReaderProperties() + .setPassword(USER_PASSWORD)); + + PdfDocument pdfDoc = new PdfDocument(reader); + Assert.assertEquals(author, pdfDoc.getDocumentInfo().getAuthor()); + Assert.assertEquals(creator, pdfDoc.getDocumentInfo().getCreator()); + Assert.assertEquals(title, pdfDoc.getDocumentInfo().getTitle()); + PdfObject object = pdfDoc.getPdfObject(1); + Assert.assertEquals(PdfObject.DICTIONARY, object.getType()); + Assert.assertTrue(objectTypeEqualTo(object, PdfName.Catalog)); + + object = pdfDoc.getPdfObject(2); + Assert.assertEquals(PdfObject.DICTIONARY, object.getType()); + Assert.assertTrue(objectTypeEqualTo(object, PdfName.Pages)); + + object = pdfDoc.getPdfObject(3); + Assert.assertEquals(PdfObject.DICTIONARY, object.getType()); + + object = pdfDoc.getPdfObject(4); + Assert.assertEquals(PdfObject.DICTIONARY, object.getType()); + Assert.assertTrue(objectTypeEqualTo(object, PdfName.Page)); + + Assert.assertEquals(PdfObject.STREAM, pdfDoc.getPdfObject(5).getType()); + } + private static PdfDictionary getTestPdfDictionary() { HashMap tmpMap = new HashMap(); tmpMap.put(new PdfName("b"), new PdfName("c")); diff --git a/kernel/src/test/java/com/itextpdf/kernel/pdf/PdfRevisionsReaderTest.java b/kernel/src/test/java/com/itextpdf/kernel/pdf/PdfRevisionsReaderTest.java new file mode 100644 index 0000000000..c191a85c51 --- /dev/null +++ b/kernel/src/test/java/com/itextpdf/kernel/pdf/PdfRevisionsReaderTest.java @@ -0,0 +1,226 @@ +/* + 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.kernel.pdf; + +import com.itextpdf.test.ExtendedITextTest; +import com.itextpdf.test.annotations.type.UnitTest; + +import java.io.IOException; +import java.util.List; +import org.junit.Assert; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +@Category(UnitTest.class) +public class PdfRevisionsReaderTest extends ExtendedITextTest { + private static final String SOURCE_FOLDER = "./src/test/resources/com/itextpdf/kernel/pdf/PdfRevisionsReaderTest/"; + + @Test + public void singleRevisionDocumentTest() throws IOException { + String filename = SOURCE_FOLDER + "singleRevisionDocument.pdf"; + + try (PdfReader reader = new PdfReader(filename)) { + PdfRevisionsReader revisionsReader = new PdfRevisionsReader(reader); + List documentRevisions = revisionsReader.getAllRevisions(); + + Assert.assertEquals(1, documentRevisions.size()); + + DocumentRevision firstRevision = documentRevisions.get(0); + assertResultingRevision(firstRevision, 1, 2, 3, 4, 5, 6); + Assert.assertEquals(929, firstRevision.getEofOffset()); + } + } + + @Test + public void singleRevisionWithXrefStreamTest() throws IOException { + String filename = SOURCE_FOLDER + "singleRevisionWithXrefStream.pdf"; + + try (PdfReader reader = new PdfReader(filename)) { + PdfRevisionsReader revisionsReader = new PdfRevisionsReader(reader); + List documentRevisions = revisionsReader.getAllRevisions(); + + Assert.assertEquals(1, documentRevisions.size()); + + DocumentRevision firstRevision = documentRevisions.get(0); + assertResultingRevision(firstRevision, 1, 2, 3, 4, 5, 6, 7, 8); + Assert.assertEquals(1085, firstRevision.getEofOffset()); + } + } + + @Test + public void multipleRevisionsDocument() throws IOException { + String filename = SOURCE_FOLDER + "multipleRevisionsDocument.pdf"; + + try (PdfReader reader = new PdfReader(filename)) { + PdfRevisionsReader revisionsReader = new PdfRevisionsReader(reader); + List documentRevisions = revisionsReader.getAllRevisions(); + + Assert.assertEquals(3, documentRevisions.size()); + + DocumentRevision firstRevision = documentRevisions.get(0); + assertResultingRevision(firstRevision, 1, 2, 3, 4, 5, 6); + Assert.assertEquals(929, firstRevision.getEofOffset()); + + DocumentRevision secondRevision = documentRevisions.get(1); + assertResultingRevision(secondRevision, 1, 3, 4, 7, 8, 9, 10, 11, 12, 13, 14, 15); + Assert.assertEquals(28119, secondRevision.getEofOffset()); + + DocumentRevision thirdRevision = documentRevisions.get(2); + assertResultingRevision(thirdRevision, 1, 3, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28); + Assert.assertEquals(36207, thirdRevision.getEofOffset()); + } + } + + @Test + public void freeReferencesDocument() throws IOException { + String filename = SOURCE_FOLDER + "freeReferencesDocument.pdf"; + + try (PdfReader reader = new PdfReader(filename)) { + PdfRevisionsReader revisionsReader = new PdfRevisionsReader(reader); + List documentRevisions = revisionsReader.getAllRevisions(); + + Assert.assertEquals(5, documentRevisions.size()); + + DocumentRevision firstRevision = documentRevisions.get(0); + assertResultingRevision(firstRevision, 1, 2, 3, 4, 5, 6); + Assert.assertEquals(929, firstRevision.getEofOffset()); + + DocumentRevision secondRevision = documentRevisions.get(1); + assertResultingRevision(secondRevision, 1, 3, 4, 7, 8, 9, 10, 11, 12, 13, 14, 15); + Assert.assertEquals(28119, secondRevision.getEofOffset()); + + DocumentRevision thirdRevision = documentRevisions.get(2); + assertResultingRevision(thirdRevision, 1, 3, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28); + Assert.assertEquals(36207, thirdRevision.getEofOffset()); + + DocumentRevision fourthRevision = documentRevisions.get(3); + assertResultingRevision(fourthRevision, new int[] {1, 3, 23, 24}, new int[] {0, 0, 1, 1}); + Assert.assertEquals(37006, fourthRevision.getEofOffset()); + + DocumentRevision fifthRevision = documentRevisions.get(4); + assertResultingRevision(fifthRevision, new int[] {1, 3, 19, 20, 21, 22, 23, 25}, + new int[] {0, 0, 1, 1, 1, 1, 1, 1}); + Assert.assertEquals(38094, fifthRevision.getEofOffset()); + } + } + + @Test + public void multipleRevisionsWithXrefStreamTest() throws IOException { + String filename = SOURCE_FOLDER + "multipleRevisionsWithXrefStream.pdf"; + + try (PdfReader reader = new PdfReader(filename)) { + PdfRevisionsReader revisionsReader = new PdfRevisionsReader(reader); + List documentRevisions = revisionsReader.getAllRevisions(); + + Assert.assertEquals(3, documentRevisions.size()); + + DocumentRevision firstRevision = documentRevisions.get(0); + assertResultingRevision(firstRevision, 1, 2, 3, 4, 5, 6, 7, 8); + Assert.assertEquals(1085, firstRevision.getEofOffset()); + + DocumentRevision secondRevision = documentRevisions.get(1); + assertResultingRevision(secondRevision, 1, 3, 4, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19); + Assert.assertEquals(28137, secondRevision.getEofOffset()); + + DocumentRevision thirdRevision = documentRevisions.get(2); + assertResultingRevision(thirdRevision, 1, 3, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34); + Assert.assertEquals(36059, thirdRevision.getEofOffset()); + } + } + + @Test + public void freeReferencesWithXrefStream() throws IOException { + String filename = SOURCE_FOLDER + "freeReferencesWithXrefStream.pdf"; + + try (PdfReader reader = new PdfReader(filename)) { + PdfRevisionsReader revisionsReader = new PdfRevisionsReader(reader); + List documentRevisions = revisionsReader.getAllRevisions(); + + Assert.assertEquals(5, documentRevisions.size()); + + DocumentRevision firstRevision = documentRevisions.get(0); + assertResultingRevision(firstRevision, 1, 2, 3, 4, 5, 6, 7, 8); + Assert.assertEquals(1085, firstRevision.getEofOffset()); + + DocumentRevision secondRevision = documentRevisions.get(1); + assertResultingRevision(secondRevision, 1, 3, 4, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19); + Assert.assertEquals(28137, secondRevision.getEofOffset()); + + DocumentRevision thirdRevision = documentRevisions.get(2); + assertResultingRevision(thirdRevision, 1, 3, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34); + Assert.assertEquals(36059, thirdRevision.getEofOffset()); + + DocumentRevision fourthRevision = documentRevisions.get(3); + assertResultingRevision(fourthRevision, new int[] {1, 3, 27, 28, 35}, new int[] {0, 0, 1, 1, 0}); + Assert.assertEquals(36975, fourthRevision.getEofOffset()); + + DocumentRevision fifthRevision = documentRevisions.get(4); + assertResultingRevision(fifthRevision, new int[] {1, 3, 23, 24, 25, 26, 27, 29, 36}, + new int[] {0, 0, 1, 1, 1, 1, 1, 1, 0}); + Assert.assertEquals(38111, fifthRevision.getEofOffset()); + } + } + + @Test + public void documentWithStreamAndTableXref() throws IOException { + String filename = SOURCE_FOLDER + "documentWithStreamAndTableXref.pdf"; + + try (PdfReader reader = new PdfReader(filename)) { + PdfRevisionsReader revisionsReader = new PdfRevisionsReader(reader); + List documentRevisions = revisionsReader.getAllRevisions(); + + Assert.assertEquals(3, documentRevisions.size()); + + DocumentRevision thirdRevision = revisionsReader.getAllRevisions().get(0); + // xref was broken in this revision and fixed in the next one + assertResultingRevision(thirdRevision, new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9}, + new int[] {0, 0, 0, 0, 0, 0, 65535, 0, 0}); + Assert.assertEquals(1381, thirdRevision.getEofOffset()); + + DocumentRevision secondRevision = revisionsReader.getAllRevisions().get(1); + assertResultingRevision(secondRevision, 1, 2, 3, 4, 5, 6, 7, 8); + Assert.assertEquals(1550, secondRevision.getEofOffset()); + + DocumentRevision firstRevision = revisionsReader.getAllRevisions().get(2); + assertResultingRevision(firstRevision); + Assert.assertEquals(1550, firstRevision.getEofOffset()); + } + } + + private void assertResultingRevision(DocumentRevision documentRevision, int... objNumbers) { + assertResultingRevision(documentRevision, objNumbers, new int[objNumbers.length]); + } + + private void assertResultingRevision(DocumentRevision documentRevision, int[] objNumbers, int[] objGens) { + Assert.assertEquals(objNumbers.length, objGens.length); + Assert.assertEquals(objNumbers.length + 1, documentRevision.getModifiedObjects().size()); + for (int i = 0; i < objNumbers.length; ++i) { + int objNumber = objNumbers[i]; + int objGen = objGens[i]; + Assert.assertTrue(documentRevision.getModifiedObjects().stream().anyMatch( + reference -> reference.getObjNumber() == objNumber && reference.getGenNumber() == objGen)); + } + Assert.assertTrue(documentRevision.getModifiedObjects().stream().anyMatch( + reference -> reference.getObjNumber() == 0 && reference.getGenNumber() == 65535 && reference.isFree())); + } +} diff --git a/kernel/src/test/java/com/itextpdf/kernel/pdf/annot/AddMiscTypesAnnotationsTest.java b/kernel/src/test/java/com/itextpdf/kernel/pdf/annot/AddMiscTypesAnnotationsTest.java index 46fca522a3..ab283d487e 100644 --- a/kernel/src/test/java/com/itextpdf/kernel/pdf/annot/AddMiscTypesAnnotationsTest.java +++ b/kernel/src/test/java/com/itextpdf/kernel/pdf/annot/AddMiscTypesAnnotationsTest.java @@ -22,8 +22,8 @@ This file is part of the iText (R) project. */ package com.itextpdf.kernel.pdf.annot; -import com.itextpdf.io.logs.IoLogMessageConstant; import com.itextpdf.io.font.constants.StandardFonts; +import com.itextpdf.io.logs.IoLogMessageConstant; import com.itextpdf.kernel.colors.ColorConstants; import com.itextpdf.kernel.colors.DeviceCmyk; import com.itextpdf.kernel.colors.DeviceGray; @@ -41,8 +41,10 @@ This file is part of the iText (R) project. import com.itextpdf.kernel.pdf.PdfReader; import com.itextpdf.kernel.pdf.PdfStream; import com.itextpdf.kernel.pdf.PdfString; +import com.itextpdf.kernel.pdf.PdfVersion; import com.itextpdf.kernel.pdf.PdfWriter; import com.itextpdf.kernel.pdf.StampingProperties; +import com.itextpdf.kernel.pdf.WriterProperties; import com.itextpdf.kernel.pdf.action.PdfAction; import com.itextpdf.kernel.pdf.action.PdfTarget; import com.itextpdf.kernel.pdf.annot.da.AnnotationDefaultAppearance; @@ -60,39 +62,38 @@ This file is part of the iText (R) project. import com.itextpdf.test.annotations.LogMessages; import com.itextpdf.test.annotations.type.IntegrationTest; -import org.junit.AfterClass; -import org.junit.Assert; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.experimental.categories.Category; - import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io.IOException; import java.util.List; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.experimental.categories.Category; @Category(IntegrationTest.class) public class AddMiscTypesAnnotationsTest extends ExtendedITextTest { - public static final String sourceFolder = + public static final String SOURCE_FOLDER = "./src/test/resources/com/itextpdf/kernel/pdf/annot/AddMiscTypesAnnotationsTest/"; - public static final String destinationFolder = + public static final String DESTINATION_FOLDER = "./target/test/com/itextpdf/kernel/pdf/annot/AddMiscTypesAnnotationsTest/"; @BeforeClass public static void beforeClass() { - createDestinationFolder(destinationFolder); + createDestinationFolder(DESTINATION_FOLDER); } @AfterClass public static void afterClass() { - CompareTool.cleanup(destinationFolder); + CompareTool.cleanup(DESTINATION_FOLDER); } @Test public void addTextAnnotation01() throws Exception { - PdfDocument document = new PdfDocument(CompareTool.createTestPdfWriter(destinationFolder + "textAnnotation01.pdf")); + PdfDocument document = new PdfDocument(CompareTool.createTestPdfWriter(DESTINATION_FOLDER + "textAnnotation01.pdf")); PdfPage page = document.addNewPage(); @@ -108,12 +109,33 @@ public void addTextAnnotation01() throws Exception { document.close(); - Assert.assertNull(new CompareTool().compareByContent(destinationFolder + "textAnnotation01.pdf", sourceFolder + "cmp_textAnnotation01.pdf", destinationFolder, "diff_")); + Assert.assertNull(new CompareTool().compareByContent(DESTINATION_FOLDER + "textAnnotation01.pdf", SOURCE_FOLDER + + "cmp_textAnnotation01.pdf", + DESTINATION_FOLDER, "diff_")); + } + + @Test + public void addTextAnnotInTagged14PdfTest() throws Exception { + String outPdf = DESTINATION_FOLDER + "addTextAnnotInTagged14PdfTest.pdf"; + String cmpPdf = SOURCE_FOLDER + "cmp_addTextAnnotInTagged14PdfTest.pdf"; + + try (PdfDocument pdfDoc = new PdfDocument(CompareTool.createTestPdfWriter( + outPdf, new WriterProperties().setPdfVersion(PdfVersion.PDF_1_4)))) { + pdfDoc.setTagged(); + + PdfPage page = pdfDoc.addNewPage(); + + PdfTextAnnotation annot = new PdfTextAnnotation(new Rectangle(100, 600, 50, 40)); + annot.setText(new PdfString("Text Annotation 01")).setContents(new PdfString("Some contents...")); + page.addAnnotation(annot); + } + + Assert.assertNull(new CompareTool().compareByContent(outPdf, cmpPdf, DESTINATION_FOLDER, "diff_")); } @Test public void caretTest() throws IOException, InterruptedException { - String filename = destinationFolder + "caretAnnotation.pdf"; + String filename = DESTINATION_FOLDER + "caretAnnotation.pdf"; PdfDocument pdfDoc = new PdfDocument(CompareTool.createTestPdfWriter(filename)); @@ -153,7 +175,8 @@ public void caretTest() throws IOException, InterruptedException { pdfDoc.close(); CompareTool compareTool = new CompareTool(); - String errorMessage = compareTool.compareByContent(filename, sourceFolder + "cmp_CaretAnnotation.pdf", destinationFolder, "diff_"); + String errorMessage = compareTool.compareByContent(filename, SOURCE_FOLDER + "cmp_CaretAnnotation.pdf", + DESTINATION_FOLDER, "diff_"); if (errorMessage != null) { Assert.fail(errorMessage); } @@ -161,7 +184,7 @@ public void caretTest() throws IOException, InterruptedException { @Test public void addFreeTextAnnotation01() throws Exception { - PdfDocument document = new PdfDocument(CompareTool.createTestPdfWriter(destinationFolder + "freeTextAnnotation01.pdf")); + PdfDocument document = new PdfDocument(CompareTool.createTestPdfWriter(DESTINATION_FOLDER + "freeTextAnnotation01.pdf")); PdfPage page = document.addNewPage(); @@ -177,12 +200,14 @@ public void addFreeTextAnnotation01() throws Exception { document.close(); - Assert.assertNull(new CompareTool().compareByContent(destinationFolder + "freeTextAnnotation01.pdf", sourceFolder + "cmp_freeTextAnnotation01.pdf", destinationFolder, "diff_")); + Assert.assertNull(new CompareTool().compareByContent(DESTINATION_FOLDER + "freeTextAnnotation01.pdf", SOURCE_FOLDER + + "cmp_freeTextAnnotation01.pdf", + DESTINATION_FOLDER, "diff_")); } @Test public void addSquareAndCircleAnnotations01() throws Exception { - PdfDocument document = new PdfDocument(CompareTool.createTestPdfWriter(destinationFolder + "squareAndCircleAnnotations01.pdf")); + PdfDocument document = new PdfDocument(CompareTool.createTestPdfWriter(DESTINATION_FOLDER + "squareAndCircleAnnotations01.pdf")); PdfPage page = document.addNewPage(); @@ -196,12 +221,14 @@ public void addSquareAndCircleAnnotations01() throws Exception { document.close(); - Assert.assertNull(new CompareTool().compareByContent(destinationFolder + "squareAndCircleAnnotations01.pdf", sourceFolder + "cmp_squareAndCircleAnnotations01.pdf", destinationFolder, "diff_")); + Assert.assertNull(new CompareTool().compareByContent(DESTINATION_FOLDER + "squareAndCircleAnnotations01.pdf", SOURCE_FOLDER + + "cmp_squareAndCircleAnnotations01.pdf", + DESTINATION_FOLDER, "diff_")); } @Test public void fileAttachmentTest() throws IOException, InterruptedException { - String filename = destinationFolder + "fileAttachmentAnnotation.pdf"; + String filename = DESTINATION_FOLDER + "fileAttachmentAnnotation.pdf"; PdfWriter writer = CompareTool.createTestPdfWriter(filename); writer.setCompressionLevel(CompressionConstants.NO_COMPRESSION); @@ -209,7 +236,7 @@ public void fileAttachmentTest() throws IOException, InterruptedException { PdfPage page1 = pdfDoc.addNewPage(); - PdfFileSpec spec = PdfFileSpec.createEmbeddedFileSpec(pdfDoc, sourceFolder + "sample.wav", null, "sample.wav", null, null); + PdfFileSpec spec = PdfFileSpec.createEmbeddedFileSpec(pdfDoc, SOURCE_FOLDER + "sample.wav", null, "sample.wav", null, null); PdfFileAttachmentAnnotation fileAttach = new PdfFileAttachmentAnnotation(new Rectangle(100, 100), spec); fileAttach.setIconName(PdfName.Paperclip); @@ -219,7 +246,8 @@ public void fileAttachmentTest() throws IOException, InterruptedException { pdfDoc.close(); CompareTool compareTool = new CompareTool(); - String errorMessage = compareTool.compareByContent(filename, sourceFolder + "cmp_fileAttachmentAnnotation.pdf", destinationFolder, "diff_"); + String errorMessage = compareTool.compareByContent(filename, SOURCE_FOLDER + "cmp_fileAttachmentAnnotation.pdf", + DESTINATION_FOLDER, "diff_"); if (errorMessage != null) { Assert.fail(errorMessage); } @@ -227,10 +255,10 @@ public void fileAttachmentTest() throws IOException, InterruptedException { @Test public void fileAttachmentTargetTest() throws IOException, InterruptedException { - String filename = destinationFolder + "fileAttachmentTargetTest.pdf"; + String filename = DESTINATION_FOLDER + "fileAttachmentTargetTest.pdf"; PdfDocument pdfDoc = new PdfDocument(CompareTool.createTestPdfWriter(filename)); - PdfFileSpec spec = PdfFileSpec.createEmbeddedFileSpec(pdfDoc, sourceFolder + "sample.pdf", null, "embedded_doc.pdf", null, null); + PdfFileSpec spec = PdfFileSpec.createEmbeddedFileSpec(pdfDoc, SOURCE_FOLDER + "sample.pdf", null, "embedded_doc.pdf", null, null); PdfFileAttachmentAnnotation fileAttachmentAnnotation = new PdfFileAttachmentAnnotation(new Rectangle(300, 500, 50, 50), spec); fileAttachmentAnnotation.setName(new PdfString("FileAttachmentAnnotation1")); pdfDoc.addNewPage(); @@ -259,7 +287,8 @@ public void fileAttachmentTargetTest() throws IOException, InterruptedException pdfDoc.close(); CompareTool compareTool = new CompareTool(); - String errorMessage = compareTool.compareByContent(filename, sourceFolder + "cmp_fileAttachmentTargetTest.pdf", destinationFolder, "diff_"); + String errorMessage = compareTool.compareByContent(filename, SOURCE_FOLDER + "cmp_fileAttachmentTargetTest.pdf", + DESTINATION_FOLDER, "diff_"); if (errorMessage != null) { Assert.fail(errorMessage); } @@ -270,7 +299,7 @@ public void fileAttachmentTargetTest() throws IOException, InterruptedException public void noFileAttachmentTargetTest() throws IOException, InterruptedException { String fileName = "noFileAttachmentTargetTest.pdf"; - PdfDocument pdfDoc = new PdfDocument(CompareTool.createTestPdfWriter(destinationFolder + fileName)); + PdfDocument pdfDoc = new PdfDocument(CompareTool.createTestPdfWriter(DESTINATION_FOLDER + fileName)); pdfDoc.addNewPage(); PdfLinkAnnotation linkAnnotation = new PdfLinkAnnotation(new Rectangle(400, 500, 50, 50)); @@ -279,8 +308,8 @@ public void noFileAttachmentTargetTest() throws IOException, InterruptedExceptio pdfDoc.close(); CompareTool compareTool = new CompareTool(); - String errorMessage = compareTool.compareByContent(destinationFolder + fileName, - sourceFolder + "cmp_" + fileName, destinationFolder, "diff_"); + String errorMessage = compareTool.compareByContent(DESTINATION_FOLDER + fileName, + SOURCE_FOLDER + "cmp_" + fileName, DESTINATION_FOLDER, "diff_"); if (errorMessage != null) { Assert.fail(errorMessage); } @@ -291,7 +320,7 @@ public void noFileAttachmentTargetTest() throws IOException, InterruptedExceptio */ @Test public void fileAttachmentAppendModeTest() throws IOException, InterruptedException { - String fileName = destinationFolder + "fileAttachmentAppendModeTest.pdf"; + String fileName = DESTINATION_FOLDER + "fileAttachmentAppendModeTest.pdf"; ByteArrayOutputStream baos = new ByteArrayOutputStream(); PdfDocument inputDoc = new PdfDocument(new PdfWriter(baos)); PdfPage page1 = inputDoc.addNewPage(); @@ -312,13 +341,14 @@ public void fileAttachmentAppendModeTest() throws IOException, InterruptedExcept finalDoc.addFileAttachment("some_test", spec); finalDoc.close(); - Assert.assertNull(new CompareTool().compareByContent(fileName, sourceFolder + "cmp_fileAttachmentAppendModeTest.pdf", destinationFolder, "diff_")); + Assert.assertNull(new CompareTool().compareByContent(fileName, SOURCE_FOLDER + "cmp_fileAttachmentAppendModeTest.pdf", + DESTINATION_FOLDER, "diff_")); } @Test public void rubberStampTest() throws IOException, InterruptedException { - String filename = destinationFolder + "rubberStampAnnotation01.pdf"; + String filename = DESTINATION_FOLDER + "rubberStampAnnotation01.pdf"; PdfDocument pdfDoc = new PdfDocument(CompareTool.createTestPdfWriter(filename)); @@ -370,7 +400,8 @@ public void rubberStampTest() throws IOException, InterruptedException { pdfDoc.close(); CompareTool compareTool = new CompareTool(); - String errorMessage = compareTool.compareByContent(filename, sourceFolder + "cmp_rubberStampAnnotation01.pdf", destinationFolder, "diff_"); + String errorMessage = compareTool.compareByContent(filename, SOURCE_FOLDER + "cmp_rubberStampAnnotation01.pdf", + DESTINATION_FOLDER, "diff_"); if (errorMessage != null) { Assert.fail(errorMessage); } @@ -378,7 +409,7 @@ public void rubberStampTest() throws IOException, InterruptedException { @Test public void rubberStampWrongStampTest() throws IOException, InterruptedException { - String filename = destinationFolder + "rubberStampAnnotation02.pdf"; + String filename = DESTINATION_FOLDER + "rubberStampAnnotation02.pdf"; PdfDocument pdfDoc = new PdfDocument(CompareTool.createTestPdfWriter(filename)); @@ -393,7 +424,8 @@ public void rubberStampWrongStampTest() throws IOException, InterruptedException pdfDoc.close(); CompareTool compareTool = new CompareTool(); - String errorMessage = compareTool.compareByContent(filename, sourceFolder + "cmp_rubberStampAnnotation02.pdf", destinationFolder, "diff_"); + String errorMessage = compareTool.compareByContent(filename, SOURCE_FOLDER + "cmp_rubberStampAnnotation02.pdf", + DESTINATION_FOLDER, "diff_"); if (errorMessage != null) { Assert.assertNull(errorMessage); } @@ -401,7 +433,7 @@ public void rubberStampWrongStampTest() throws IOException, InterruptedException @Test public void inkTest() throws IOException, InterruptedException { - String filename = destinationFolder + "inkAnnotation01.pdf"; + String filename = DESTINATION_FOLDER + "inkAnnotation01.pdf"; PdfDocument pdfDoc = new PdfDocument(CompareTool.createTestPdfWriter(filename)); @@ -428,7 +460,8 @@ public void inkTest() throws IOException, InterruptedException { pdfDoc.close(); CompareTool compareTool = new CompareTool(); - String errorMessage = compareTool.compareByContent(filename, sourceFolder + "cmp_inkAnnotation01.pdf", destinationFolder, "diff_"); + String errorMessage = compareTool.compareByContent(filename, SOURCE_FOLDER + "cmp_inkAnnotation01.pdf", + DESTINATION_FOLDER, "diff_"); if (errorMessage != null) { Assert.assertNull(errorMessage); } @@ -436,7 +469,7 @@ public void inkTest() throws IOException, InterruptedException { @Test public void printerMarkText() throws IOException, InterruptedException { - String filename = destinationFolder + "printerMarkAnnotation01.pdf"; + String filename = DESTINATION_FOLDER + "printerMarkAnnotation01.pdf"; PdfDocument pdfDoc = new PdfDocument(CompareTool.createTestPdfWriter(filename)); PdfPage page1 = pdfDoc.addNewPage(); @@ -468,7 +501,8 @@ public void printerMarkText() throws IOException, InterruptedException { pdfDoc.close(); CompareTool compareTool = new CompareTool(); - String errorMessage = compareTool.compareByContent(filename, sourceFolder + "cmp_printerMarkAnnotation01.pdf", destinationFolder, "diff_"); + String errorMessage = compareTool.compareByContent(filename, SOURCE_FOLDER + "cmp_printerMarkAnnotation01.pdf", + DESTINATION_FOLDER, "diff_"); if (errorMessage != null) { Assert.fail(errorMessage); } @@ -476,7 +510,7 @@ public void printerMarkText() throws IOException, InterruptedException { @Test public void trapNetworkText() throws IOException, InterruptedException { - String filename = destinationFolder + "trapNetworkAnnotation01.pdf"; + String filename = DESTINATION_FOLDER + "trapNetworkAnnotation01.pdf"; PdfDocument pdfDoc = new PdfDocument(CompareTool.createTestPdfWriter(filename)); @@ -511,7 +545,8 @@ public void trapNetworkText() throws IOException, InterruptedException { pdfDoc.close(); CompareTool compareTool = new CompareTool(); - String errorMessage = compareTool.compareByContent(filename, sourceFolder + "cmp_trapNetworkAnnotation01.pdf", destinationFolder, "diff_"); + String errorMessage = compareTool.compareByContent(filename, SOURCE_FOLDER + "cmp_trapNetworkAnnotation01.pdf", + DESTINATION_FOLDER, "diff_"); if (errorMessage != null) { Assert.fail(errorMessage); } @@ -519,7 +554,7 @@ public void trapNetworkText() throws IOException, InterruptedException { @Test public void waterMarkTest() throws IOException, InterruptedException { - String filename = destinationFolder + "watermarkAnnotation01.pdf"; + String filename = DESTINATION_FOLDER + "watermarkAnnotation01.pdf"; PdfDocument pdfDoc = new PdfDocument(CompareTool.createTestPdfWriter(filename)); @@ -555,7 +590,8 @@ public void waterMarkTest() throws IOException, InterruptedException { pdfDoc.close(); CompareTool compareTool = new CompareTool(); - String errorMessage = compareTool.compareByContent(filename, sourceFolder + "cmp_watermarkAnnotation01.pdf", destinationFolder, "diff_"); + String errorMessage = compareTool.compareByContent(filename, SOURCE_FOLDER + "cmp_watermarkAnnotation01.pdf", + DESTINATION_FOLDER, "diff_"); if (errorMessage != null) { Assert.fail(errorMessage); } @@ -563,7 +599,7 @@ public void waterMarkTest() throws IOException, InterruptedException { @Test public void redactionTest() throws IOException, InterruptedException { - String filename = destinationFolder + "redactionAnnotation01.pdf"; + String filename = DESTINATION_FOLDER + "redactionAnnotation01.pdf"; PdfDocument pdfDoc = new PdfDocument(CompareTool.createTestPdfWriter(filename)); @@ -623,7 +659,8 @@ public void redactionTest() throws IOException, InterruptedException { pdfDoc.close(); CompareTool compareTool = new CompareTool(); - String errorMessage = compareTool.compareByContent(filename, sourceFolder + "cmp_redactionAnnotation01.pdf", destinationFolder, "diff_"); + String errorMessage = compareTool.compareByContent(filename, SOURCE_FOLDER + "cmp_redactionAnnotation01.pdf", + DESTINATION_FOLDER, "diff_"); if (errorMessage != null) { Assert.fail(errorMessage); } @@ -632,9 +669,9 @@ public void redactionTest() throws IOException, InterruptedException { @Test public void defaultAppearanceTest() throws IOException, InterruptedException { String name = "defaultAppearance"; - String inPath = sourceFolder + "in_" + name + ".pdf"; - String outPath = destinationFolder + name + ".pdf"; - String cmpPath = sourceFolder + "cmp_" + name + ".pdf"; + String inPath = SOURCE_FOLDER + "in_" + name + ".pdf"; + String outPath = DESTINATION_FOLDER + name + ".pdf"; + String cmpPath = SOURCE_FOLDER + "cmp_" + name + ".pdf"; String diff = "diff_" + name + "_"; PdfDocument pdfDoc = new PdfDocument(new PdfReader(inPath), CompareTool.createTestPdfWriter(outPath)); @@ -692,12 +729,12 @@ public void defaultAppearanceTest() throws IOException, InterruptedException { ); pdfDoc.close(); - Assert.assertNull(new CompareTool().compareByContent(outPath, cmpPath, destinationFolder, diff)); + Assert.assertNull(new CompareTool().compareByContent(outPath, cmpPath, DESTINATION_FOLDER, diff)); } @Test public void make3dAnnotationTest() throws IOException { - String filename = sourceFolder + "3d_annotation.pdf"; + String filename = SOURCE_FOLDER + "3d_annotation.pdf"; PdfDocument pdfDoc = new PdfDocument(new PdfReader(filename)); @@ -710,10 +747,10 @@ public void make3dAnnotationTest() throws IOException { @Test public void add3dAnnotationTest() throws IOException, InterruptedException { - PdfDocument pdfDoc = new PdfDocument(CompareTool.createTestPdfWriter(destinationFolder + "add3DAnnotation01.pdf")); + PdfDocument pdfDoc = new PdfDocument(CompareTool.createTestPdfWriter(DESTINATION_FOLDER + "add3DAnnotation01.pdf")); Rectangle rect = new Rectangle(100, 400, 400, 400); - PdfStream stream3D = new PdfStream(pdfDoc, new FileInputStream(sourceFolder + "teapot.u3d")); + PdfStream stream3D = new PdfStream(pdfDoc, new FileInputStream(SOURCE_FOLDER + "teapot.u3d")); stream3D.put(PdfName.Type, new PdfName("3D")); stream3D.put(PdfName.Subtype, new PdfName("U3D")); stream3D.setCompressionLevel(CompressionConstants.UNDEFINED_COMPRESSION); @@ -734,8 +771,8 @@ public void add3dAnnotationTest() throws IOException, InterruptedException { pdfDoc.addNewPage().addAnnotation(annot); pdfDoc.close(); - Assert.assertNull(new CompareTool().compareByContent(destinationFolder + "add3DAnnotation01.pdf", - sourceFolder + "cmp_add3DAnnotation01.pdf", destinationFolder, "diff_")); + Assert.assertNull(new CompareTool().compareByContent(DESTINATION_FOLDER + "add3DAnnotation01.pdf", + SOURCE_FOLDER + "cmp_add3DAnnotation01.pdf", DESTINATION_FOLDER, "diff_")); } diff --git a/kernel/src/test/java/com/itextpdf/kernel/pdf/canvas/parser/HighlightItemsTest.java b/kernel/src/test/java/com/itextpdf/kernel/pdf/canvas/parser/HighlightItemsTest.java index c1d99655ae..f11e4529d7 100644 --- a/kernel/src/test/java/com/itextpdf/kernel/pdf/canvas/parser/HighlightItemsTest.java +++ b/kernel/src/test/java/com/itextpdf/kernel/pdf/canvas/parser/HighlightItemsTest.java @@ -45,7 +45,6 @@ This file is part of the iText (R) project. import org.junit.AfterClass; import org.junit.Assert; -import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -240,11 +239,20 @@ public void trueTypeCIDFontWithDWWithoutProperWidthGlyphTest() throws IOExceptio } @Test - //TODO: DEVSIX-4784 (incorrect displaying of highlights) - public void invalidHighlightTest() throws IOException, InterruptedException { - String input = sourceFolder + "invalidHighlight.pdf"; - String output = outputPath + "invalidHighlightOutput.pdf"; - String cmp = sourceFolder + "cmp_invalidHighlight.pdf"; + public void doubleMappingSimpleFontTest() throws IOException, InterruptedException { + String input = sourceFolder + "doubleMappingSimpleFont.pdf"; + String output = outputPath + "doubleMappingSimpleFont.pdf"; + String cmp = sourceFolder + "cmp_doubleMappingSimpleFont.pdf"; + PdfWriter writer = CompareTool.createTestPdfWriter(output); + parseAndHighlight(input, writer, false); + Assert.assertNull(new CompareTool().compareByContent(output, cmp, outputPath)); + } + + @Test + public void doubleMappingSimpleFontTest2() throws IOException, InterruptedException { + String input = sourceFolder + "doubleMappingSimpleFont2.pdf"; + String output = outputPath + "doubleMappingSimpleFont2.pdf"; + String cmp = sourceFolder + "cmp_doubleMappingSimpleFont2.pdf"; PdfWriter writer = CompareTool.createTestPdfWriter(output); parseAndHighlight(input, writer, true); Assert.assertNull(new CompareTool().compareByContent(output, cmp, outputPath)); diff --git a/kernel/src/test/java/com/itextpdf/kernel/pdf/layer/PdfLayerTest.java b/kernel/src/test/java/com/itextpdf/kernel/pdf/layer/PdfLayerTest.java index 492a47d878..7369976592 100644 --- a/kernel/src/test/java/com/itextpdf/kernel/pdf/layer/PdfLayerTest.java +++ b/kernel/src/test/java/com/itextpdf/kernel/pdf/layer/PdfLayerTest.java @@ -409,7 +409,7 @@ public void testInStamperMode1() throws IOException, InterruptedException { PdfDocument pdfDoc = new PdfDocument(new PdfReader(sourceFolder + "input_layered.pdf"), CompareTool.createTestPdfWriter(destinationFolder + "output_copy_layered.pdf")); pdfDoc.close(); - Assert.assertNull(new CompareTool().compareByContent(destinationFolder + "output_copy_layered.pdf", sourceFolder + "input_layered.pdf", destinationFolder, "diff")); + Assert.assertNull(new CompareTool().compareByContent(destinationFolder + "output_copy_layered.pdf", sourceFolder + "cmp_output_copy_layered.pdf", destinationFolder, "diff")); } @Test diff --git a/kernel/src/test/java/com/itextpdf/kernel/pdf/tagging/StructureTreeCopierUnitTest.java b/kernel/src/test/java/com/itextpdf/kernel/pdf/tagging/StructureTreeCopierUnitTest.java index dfdd2d9b40..6c111356f8 100644 --- a/kernel/src/test/java/com/itextpdf/kernel/pdf/tagging/StructureTreeCopierUnitTest.java +++ b/kernel/src/test/java/com/itextpdf/kernel/pdf/tagging/StructureTreeCopierUnitTest.java @@ -76,7 +76,7 @@ public void shouldTableElementBeCopiedTrTdTest() { PdfDictionary obj = new PdfDictionary(tr); PdfDictionary parent = new PdfDictionary(td); - Assert.assertFalse(StructureTreeCopier.shouldTableElementBeCopied(obj, parent)); + Assert.assertTrue(StructureTreeCopier.shouldTableElementBeCopied(obj, parent)); } @Test @@ -84,7 +84,7 @@ public void shouldTableElementBeCopiedTrTrTest() { PdfDictionary obj = new PdfDictionary(tr); PdfDictionary parent = new PdfDictionary(tr); - Assert.assertFalse(StructureTreeCopier.shouldTableElementBeCopied(obj, parent)); + Assert.assertTrue(StructureTreeCopier.shouldTableElementBeCopied(obj, parent)); } @Test diff --git a/kernel/src/test/java/com/itextpdf/kernel/utils/PdfMergerTest.java b/kernel/src/test/java/com/itextpdf/kernel/utils/PdfMergerTest.java index 8e38816017..8c840df4f0 100644 --- a/kernel/src/test/java/com/itextpdf/kernel/utils/PdfMergerTest.java +++ b/kernel/src/test/java/com/itextpdf/kernel/utils/PdfMergerTest.java @@ -35,7 +35,6 @@ This file is part of the iText (R) project. import com.itextpdf.test.annotations.type.IntegrationTest; import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -124,6 +123,23 @@ public void mergeDocumentWithCycleRefInAcroFormTest() throws IOException, Interr destinationFolder, "diff_")); } + @Test + public void mergeDocumentWithLinkAnnotationTest() throws IOException, InterruptedException { + String filename = sourceFolder + "documentWithLinkAnnotation.pdf"; + String resultFile = destinationFolder + "mergedDocumentWithLinkAnnotation.pdf"; + + PdfReader reader = new PdfReader(filename); + + PdfWriter writer1 = CompareTool.createTestPdfWriter(resultFile); + PdfDocument pdfDoc = new PdfDocument(reader); + PdfDocument result = new PdfDocument(writer1); + PdfMerger merger = new PdfMerger(result).setCloseSourceDocuments(true); + + merger.merge(pdfDoc, 1, 1).close(); + + Assert.assertNull(new CompareTool().compareByContent(resultFile, sourceFolder + "cmp_mergedDocumentWithLinkAnnotation.pdf", destinationFolder, "diff_")); + } + @Test public void mergeDocumentTest02() throws IOException, InterruptedException { String filename = sourceFolder + "doc1.pdf"; @@ -259,11 +275,78 @@ public void tdInsideTdTableTest() throws ParserConfigurationException, SAXExcept } @Test - // TODO DEVSIX-5974 Empty tr isn't copied. public void emptyTrTableTest() throws ParserConfigurationException, SAXException, IOException, InterruptedException { mergeAndCompareTagStructures("emptyTrTable.pdf", 1, 1); } + @Test + public void splitEmptyTrTableFirstPageTest() throws ParserConfigurationException, SAXException, IOException, InterruptedException { + mergeAndCompareTagStructures("splitTableWithEmptyTrFirstPage.pdf", 1, 1); + } + + @Test + public void splitEmptyTrTableSecondPageTest() throws ParserConfigurationException, SAXException, IOException, InterruptedException { + mergeAndCompareTagStructures("splitTableWithEmptyTrSecondPage.pdf", 2, 2); + } + + @Test + public void splitEmptyTrTableFullTest() throws ParserConfigurationException, SAXException, IOException, InterruptedException { + mergeAndCompareTagStructures("splitTableWithEmptyTrFull.pdf", 1, 2); + } + + @Test + public void emptyFirstTrTableTest() throws ParserConfigurationException, SAXException, IOException, InterruptedException { + mergeAndCompareTagStructures("emptyFirstTrTable.pdf", 1, 1); + } + + @Test + public void emptyLastTrTableTest() throws ParserConfigurationException, SAXException, IOException, InterruptedException { + mergeAndCompareTagStructures("emptyLastTrTable.pdf", 1, 1); + } + + @Test + public void emptyTwoAdjacentTrTableTest() throws ParserConfigurationException, SAXException, IOException, InterruptedException { + mergeAndCompareTagStructures("emptyTwoAdjacentTrTable.pdf", 1, 1); + } + + @Test + public void emptyAllTrTableTest() throws ParserConfigurationException, SAXException, IOException, InterruptedException { + mergeAndCompareTagStructures("emptyAllTrTable.pdf", 1, 1); + } + + @Test + public void emptySingleTrTableTest() throws ParserConfigurationException, SAXException, IOException, InterruptedException { + mergeAndCompareTagStructures("emptySingleTrTable.pdf", 1, 1); + } + + @Test + public void splitAndMergeEmptyTrTableTest() throws ParserConfigurationException, SAXException, IOException, InterruptedException { + String sourceFilename = sourceFolder + "splitTableWithEmptyTrFull.pdf"; + String firstPageFilename = destinationFolder + "firstPageDoc.pdf"; + String secondPageFilename = destinationFolder + "secondPageDoc.pdf"; + String resultFilename = destinationFolder + "splitAndMergeEmptyTrTable.pdf"; + String cmpFilename = sourceFolder + "cmp_splitAndMergeEmptyTrTable.pdf"; + + PdfDocument sourceDoc = new PdfDocument(new PdfReader(sourceFilename)); + + PdfDocument firstPageDoc = new PdfDocument(new PdfWriter(firstPageFilename)); + PdfMerger mergerFirstPage = new PdfMerger(firstPageDoc); + mergerFirstPage.merge(sourceDoc, 1, 1); + mergerFirstPage.close(); + + PdfDocument secondPageDoc = new PdfDocument(new PdfWriter(secondPageFilename)); + PdfMerger mergerSecondPage = new PdfMerger(secondPageDoc); + mergerSecondPage.merge(sourceDoc, 2, 2); + mergerSecondPage.close(); + + List sources = new ArrayList(); + sources.add(new File(firstPageFilename)); + sources.add(new File(secondPageFilename)); + mergePdfs(sources, resultFilename, new PdfMergerProperties(), false); + + Assert.assertNull(new CompareTool().compareTagStructures(resultFilename, cmpFilename)); + } + @Test @LogMessages(messages = {@LogMessage(messageTemplate = IoLogMessageConstant.NAME_ALREADY_EXISTS_IN_THE_NAME_TREE, count = 2)}) public void mergeOutlinesNamedDestinations() throws IOException, InterruptedException { @@ -550,43 +633,52 @@ public void MergeWithSameNamedOcgOcmdDTest() throws IOException, InterruptedExce @Test @LogMessages(messages = { - @LogMessage(messageTemplate = IoLogMessageConstant.TAG_STRUCTURE_INIT_FAILED) + @LogMessage(messageTemplate = KernelLogMessageConstant.STRUCT_PARENT_INDEX_MISSED_AND_RECREATED) }) - public void mergePdfWithMissingStructElemBeginningOfTreeTest() throws IOException { - //TODO change assertion after DEVSIX-7478 is fixed - Assert.assertNull(mergeSinglePdfAndGetResultingStructTreeRoot("structParentMissingFirstElement.pdf")); + public void mergePdfWithMissingStructElemBeginningOfTreeTest() throws IOException, InterruptedException { + String name = "structParentMissingFirstElement.pdf"; + Assert.assertNotNull(mergeSinglePdfAndGetResultingStructTreeRoot(name)); + Assert.assertNull(new CompareTool().compareByContent( + destinationFolder + name, + sourceFolder + "cmp_" + name, destinationFolder)); } @Test @LogMessages(messages = { - @LogMessage(messageTemplate = IoLogMessageConstant.TAG_STRUCTURE_INIT_FAILED), - @LogMessage(messageTemplate = IoLogMessageConstant.SOURCE_DOCUMENT_HAS_ACROFORM_DICTIONARY) + @LogMessage(messageTemplate = IoLogMessageConstant.SOURCE_DOCUMENT_HAS_ACROFORM_DICTIONARY), + @LogMessage(messageTemplate = KernelLogMessageConstant.STRUCT_PARENT_INDEX_MISSED_AND_RECREATED) }) - public void mergePdfWithMissingStructElemEndOfTreeTest() throws IOException { - //TODO change assertion after DEVSIX-7478 is fixed - Assert.assertNull( - mergeSinglePdfAndGetResultingStructTreeRoot("structParentMissingLastElement.pdf")); + public void mergePdfWithMissingStructElemEndOfTreeTest() throws IOException, InterruptedException { + String name = "structParentMissingLastElement.pdf"; + Assert.assertNotNull(mergeSinglePdfAndGetResultingStructTreeRoot(name)); + Assert.assertNull(new CompareTool().compareByContent( + destinationFolder + name, + sourceFolder + "cmp_" + name, destinationFolder)); } @Test @LogMessages(messages = { - @LogMessage(messageTemplate = IoLogMessageConstant.TAG_STRUCTURE_INIT_FAILED), - @LogMessage(messageTemplate = IoLogMessageConstant.SOURCE_DOCUMENT_HAS_ACROFORM_DICTIONARY) + @LogMessage(messageTemplate = IoLogMessageConstant.SOURCE_DOCUMENT_HAS_ACROFORM_DICTIONARY), + @LogMessage(messageTemplate = KernelLogMessageConstant.STRUCT_PARENT_INDEX_MISSED_AND_RECREATED, count = 4) }) - public void mergePdfAllObjectsMissingStructParentTest() throws IOException { - //TODO change assertion after DEVSIX-7478 is fixed - Assert.assertNull(mergeSinglePdfAndGetResultingStructTreeRoot( - "allObjectsHaveStructParent.pdf")); + public void mergePdfAllObjectsMissingStructParentTest() throws IOException, InterruptedException { + String name = "allObjectsHaveStructParent.pdf"; + Assert.assertNotNull(mergeSinglePdfAndGetResultingStructTreeRoot(name)); + Assert.assertNull(new CompareTool().compareByContent( + destinationFolder + name, + sourceFolder + "cmp_" + name, destinationFolder)); } @Test @LogMessages(messages = { - @LogMessage(messageTemplate = IoLogMessageConstant.TAG_STRUCTURE_INIT_FAILED) + @LogMessage(messageTemplate = KernelLogMessageConstant.STRUCT_PARENT_INDEX_MISSED_AND_RECREATED, count = 2) }) - public void mergePdfChildObjectsOfSameStructElemMissingStructParentTest() throws IOException { - //TODO change assertion after DEVSIX-7478 is fixed - Assert.assertNull(mergeSinglePdfAndGetResultingStructTreeRoot( - "SameStructElemNoParent.pdf")); + public void mergePdfChildObjectsOfSameStructElemMissingStructParentTest() throws IOException, InterruptedException { + String name = "SameStructElemNoParent.pdf"; + Assert.assertNotNull(mergeSinglePdfAndGetResultingStructTreeRoot(name)); + Assert.assertNull(new CompareTool().compareByContent( + destinationFolder + name, + sourceFolder + "cmp_" + name, destinationFolder)); } @Test diff --git a/kernel/src/test/java/com/itextpdf/kernel/utils/checkers/FontCheckUtilTest.java b/kernel/src/test/java/com/itextpdf/kernel/utils/checkers/FontCheckUtilTest.java new file mode 100644 index 0000000000..4c81db4df5 --- /dev/null +++ b/kernel/src/test/java/com/itextpdf/kernel/utils/checkers/FontCheckUtilTest.java @@ -0,0 +1,103 @@ +/* + 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.kernel.utils.checkers; + +import com.itextpdf.io.font.FontEncoding; +import com.itextpdf.io.font.FontProgramFactory; +import com.itextpdf.io.font.constants.StandardFonts; +import com.itextpdf.kernel.font.PdfFont; +import com.itextpdf.kernel.font.PdfFontFactory; +import com.itextpdf.kernel.font.PdfFontFactory.EmbeddingStrategy; +import com.itextpdf.kernel.utils.checkers.FontCheckUtil.CharacterChecker; +import com.itextpdf.test.ExtendedITextTest; +import com.itextpdf.test.annotations.type.UnitTest; + +import java.io.IOException; +import org.junit.Assert; +import org.junit.Test; +import org.junit.experimental.categories.Category; + + +@Category(UnitTest.class) +public class FontCheckUtilTest extends ExtendedITextTest { + private static final String FONTS_FOLDER = "./src/test/resources/com/itextpdf/kernel/pdf/fonts/"; + + @Test + public void checkFontAvailable() throws IOException { + PdfFont font = PdfFontFactory.createFont(StandardFonts.HELVETICA); + Assert.assertEquals(-1, FontCheckUtil.checkGlyphsOfText("123", font, new CharacterChecker() { + @Override + public boolean check(int ch, PdfFont fontToCheck) { + return !fontToCheck.containsGlyph(ch); + } + })); + } + + + @Test + public void checkFontNotAvailable() throws IOException { + PdfFont font = PdfFontFactory.createFont(StandardFonts.HELVETICA); + Assert.assertEquals(2, FontCheckUtil.checkGlyphsOfText("hi⫊", font, new CharacterChecker() { + @Override + public boolean check(int ch, PdfFont fontToCheck) { + return !fontToCheck.containsGlyph(ch); + } + })); + } + + @Test + public void checkUnicodeMappingNotAvailable() throws IOException { + PdfFont font = PdfFontFactory.createFont( + FontProgramFactory.createType1Font(FONTS_FOLDER + "cmr10.afm", FONTS_FOLDER + "cmr10.pfb"), + FontEncoding.FONT_SPECIFIC, EmbeddingStrategy.FORCE_EMBEDDED); + int index = FontCheckUtil.checkGlyphsOfText("h i", font, new CharacterChecker() { + @Override + public boolean check(int ch, PdfFont fontToCheck) { + if (fontToCheck.containsGlyph(ch)) { + return !fontToCheck.getGlyph(ch).hasValidUnicode(); + } else { + return true; + } + } + }); + Assert.assertEquals(1, index); + } + + @Test + public void checkUnicodeMappingAvailable() throws IOException { + PdfFont font = PdfFontFactory.createFont( + FontProgramFactory.createType1Font(FONTS_FOLDER + "cmr10.afm", FONTS_FOLDER + "cmr10.pfb"), + FontEncoding.FONT_SPECIFIC, EmbeddingStrategy.FORCE_EMBEDDED); + int index = FontCheckUtil.checkGlyphsOfText("hi", font, new CharacterChecker() { + @Override + public boolean check(int ch, PdfFont fontToCheck) { + if (fontToCheck.containsGlyph(ch)) { + return !fontToCheck.getGlyph(ch).hasValidUnicode(); + } else { + return true; + } + } + }); + Assert.assertEquals(-1, index); + } +} \ No newline at end of file diff --git a/kernel/src/test/java/com/itextpdf/kernel/xmp/impl/XMPNodeTest.java b/kernel/src/test/java/com/itextpdf/kernel/xmp/impl/XMPNodeTest.java new file mode 100644 index 0000000000..c5f1f1ae47 --- /dev/null +++ b/kernel/src/test/java/com/itextpdf/kernel/xmp/impl/XMPNodeTest.java @@ -0,0 +1,44 @@ +/* + 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.kernel.xmp.impl; + +import com.itextpdf.kernel.xmp.XMPException; +import com.itextpdf.kernel.xmp.options.PropertyOptions; +import com.itextpdf.test.AssertUtil; +import com.itextpdf.test.ExtendedITextTest; +import com.itextpdf.test.annotations.type.UnitTest; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +@Category(UnitTest.class) +public class XMPNodeTest extends ExtendedITextTest { + @Test + public void test() throws XMPException { + XMPNode node = new XMPNode("rdf:RDF", "idk", new PropertyOptions()); + node.addChild(new XMPNode("rdf:Description", "idk", new PropertyOptions())); + for (Object object : node.getUnmodifiableChildren()) { + AssertUtil.doesNotThrow(() -> + node.addChild(new XMPNode("xmp:Authors", "itext", new PropertyOptions().setArrayAlternate(true)))); + } + } +} diff --git a/kernel/src/test/resources/com/itextpdf/kernel/pdf/ParentTreeTest/allObjRefDontHaveStructParent.pdf b/kernel/src/test/resources/com/itextpdf/kernel/pdf/ParentTreeTest/allObjRefDontHaveStructParent.pdf new file mode 100644 index 0000000000..61f550406e Binary files /dev/null and b/kernel/src/test/resources/com/itextpdf/kernel/pdf/ParentTreeTest/allObjRefDontHaveStructParent.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/pdf/ParentTreeTest/cmp_allObjRefDontHaveStructParent.pdf b/kernel/src/test/resources/com/itextpdf/kernel/pdf/ParentTreeTest/cmp_allObjRefDontHaveStructParent.pdf new file mode 100644 index 0000000000..377a90e91e Binary files /dev/null and b/kernel/src/test/resources/com/itextpdf/kernel/pdf/ParentTreeTest/cmp_allObjRefDontHaveStructParent.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/pdf/ParentTreeTest/cmp_xObjDoesntHaveStructParentTest.pdf b/kernel/src/test/resources/com/itextpdf/kernel/pdf/ParentTreeTest/cmp_xObjDoesntHaveStructParentTest.pdf new file mode 100644 index 0000000000..89ea390113 Binary files /dev/null and b/kernel/src/test/resources/com/itextpdf/kernel/pdf/ParentTreeTest/cmp_xObjDoesntHaveStructParentTest.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/pdf/ParentTreeTest/pdfWithMultipleDocumentTags.pdf b/kernel/src/test/resources/com/itextpdf/kernel/pdf/ParentTreeTest/pdfWithMultipleDocumentTags.pdf new file mode 100644 index 0000000000..029b27931f Binary files /dev/null and b/kernel/src/test/resources/com/itextpdf/kernel/pdf/ParentTreeTest/pdfWithMultipleDocumentTags.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/pdf/ParentTreeTest/xObjDoesntHaveStructParentTest.pdf b/kernel/src/test/resources/com/itextpdf/kernel/pdf/ParentTreeTest/xObjDoesntHaveStructParentTest.pdf new file mode 100644 index 0000000000..c4f448cc46 Binary files /dev/null and b/kernel/src/test/resources/com/itextpdf/kernel/pdf/ParentTreeTest/xObjDoesntHaveStructParentTest.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/pdf/PdfCopyTest/DocWithDDictionary.pdf b/kernel/src/test/resources/com/itextpdf/kernel/pdf/PdfCopyTest/DocWithDDictionary.pdf new file mode 100644 index 0000000000..fca8cebb4a Binary files /dev/null and b/kernel/src/test/resources/com/itextpdf/kernel/pdf/PdfCopyTest/DocWithDDictionary.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/pdf/PdfCopyTest/cmp_copyDocWithDDictionary.pdf b/kernel/src/test/resources/com/itextpdf/kernel/pdf/PdfCopyTest/cmp_copyDocWithDDictionary.pdf new file mode 100644 index 0000000000..87f7225636 Binary files /dev/null and b/kernel/src/test/resources/com/itextpdf/kernel/pdf/PdfCopyTest/cmp_copyDocWithDDictionary.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/pdf/PdfOutlineTest/cmp_outlineTypeNull.pdf b/kernel/src/test/resources/com/itextpdf/kernel/pdf/PdfOutlineTest/cmp_outlineTypeNull.pdf index 6d607161b8..01ea9dc7e8 100644 Binary files a/kernel/src/test/resources/com/itextpdf/kernel/pdf/PdfOutlineTest/cmp_outlineTypeNull.pdf and b/kernel/src/test/resources/com/itextpdf/kernel/pdf/PdfOutlineTest/cmp_outlineTypeNull.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/pdf/PdfReaderTest/simpleDoc.pdf b/kernel/src/test/resources/com/itextpdf/kernel/pdf/PdfReaderTest/simpleDoc.pdf new file mode 100644 index 0000000000..fe812fd105 Binary files /dev/null and b/kernel/src/test/resources/com/itextpdf/kernel/pdf/PdfReaderTest/simpleDoc.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/pdf/PdfReaderTest/simpleDocWithPassword.pdf b/kernel/src/test/resources/com/itextpdf/kernel/pdf/PdfReaderTest/simpleDocWithPassword.pdf new file mode 100644 index 0000000000..e0c3a0d53f Binary files /dev/null and b/kernel/src/test/resources/com/itextpdf/kernel/pdf/PdfReaderTest/simpleDocWithPassword.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/pdf/PdfRevisionsReaderTest/documentWithStreamAndTableXref.pdf b/kernel/src/test/resources/com/itextpdf/kernel/pdf/PdfRevisionsReaderTest/documentWithStreamAndTableXref.pdf new file mode 100644 index 0000000000..6bb3a9aed1 Binary files /dev/null and b/kernel/src/test/resources/com/itextpdf/kernel/pdf/PdfRevisionsReaderTest/documentWithStreamAndTableXref.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/pdf/PdfRevisionsReaderTest/freeReferencesDocument.pdf b/kernel/src/test/resources/com/itextpdf/kernel/pdf/PdfRevisionsReaderTest/freeReferencesDocument.pdf new file mode 100644 index 0000000000..5326515bab Binary files /dev/null and b/kernel/src/test/resources/com/itextpdf/kernel/pdf/PdfRevisionsReaderTest/freeReferencesDocument.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/pdf/PdfRevisionsReaderTest/freeReferencesWithXrefStream.pdf b/kernel/src/test/resources/com/itextpdf/kernel/pdf/PdfRevisionsReaderTest/freeReferencesWithXrefStream.pdf new file mode 100644 index 0000000000..12685a44ee Binary files /dev/null and b/kernel/src/test/resources/com/itextpdf/kernel/pdf/PdfRevisionsReaderTest/freeReferencesWithXrefStream.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/pdf/PdfRevisionsReaderTest/multipleRevisionsDocument.pdf b/kernel/src/test/resources/com/itextpdf/kernel/pdf/PdfRevisionsReaderTest/multipleRevisionsDocument.pdf new file mode 100644 index 0000000000..d2a53e0848 Binary files /dev/null and b/kernel/src/test/resources/com/itextpdf/kernel/pdf/PdfRevisionsReaderTest/multipleRevisionsDocument.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/pdf/PdfRevisionsReaderTest/multipleRevisionsWithXrefStream.pdf b/kernel/src/test/resources/com/itextpdf/kernel/pdf/PdfRevisionsReaderTest/multipleRevisionsWithXrefStream.pdf new file mode 100644 index 0000000000..0aa09214b4 Binary files /dev/null and b/kernel/src/test/resources/com/itextpdf/kernel/pdf/PdfRevisionsReaderTest/multipleRevisionsWithXrefStream.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/pdf/PdfRevisionsReaderTest/singleRevisionDocument.pdf b/kernel/src/test/resources/com/itextpdf/kernel/pdf/PdfRevisionsReaderTest/singleRevisionDocument.pdf new file mode 100644 index 0000000000..7e6f851581 Binary files /dev/null and b/kernel/src/test/resources/com/itextpdf/kernel/pdf/PdfRevisionsReaderTest/singleRevisionDocument.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/pdf/PdfRevisionsReaderTest/singleRevisionWithXrefStream.pdf b/kernel/src/test/resources/com/itextpdf/kernel/pdf/PdfRevisionsReaderTest/singleRevisionWithXrefStream.pdf new file mode 100644 index 0000000000..74fefc9ec9 Binary files /dev/null and b/kernel/src/test/resources/com/itextpdf/kernel/pdf/PdfRevisionsReaderTest/singleRevisionWithXrefStream.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/pdf/PdfStructElemTest/cmp_structTreeCopyingTest02.pdf b/kernel/src/test/resources/com/itextpdf/kernel/pdf/PdfStructElemTest/cmp_structTreeCopyingTest02.pdf index 1d7f51ad73..d1e9502925 100644 Binary files a/kernel/src/test/resources/com/itextpdf/kernel/pdf/PdfStructElemTest/cmp_structTreeCopyingTest02.pdf and b/kernel/src/test/resources/com/itextpdf/kernel/pdf/PdfStructElemTest/cmp_structTreeCopyingTest02.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/pdf/PdfStructElemTest/cmp_structTreeCopyingTest04.pdf b/kernel/src/test/resources/com/itextpdf/kernel/pdf/PdfStructElemTest/cmp_structTreeCopyingTest04.pdf index 81fc95e570..1d787ddc8e 100644 Binary files a/kernel/src/test/resources/com/itextpdf/kernel/pdf/PdfStructElemTest/cmp_structTreeCopyingTest04.pdf and b/kernel/src/test/resources/com/itextpdf/kernel/pdf/PdfStructElemTest/cmp_structTreeCopyingTest04.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/pdf/PdfStructElemTest/cmp_structTreeCopyingTest06.pdf b/kernel/src/test/resources/com/itextpdf/kernel/pdf/PdfStructElemTest/cmp_structTreeCopyingTest06.pdf index 1cdd6c22d2..9ab49b2b49 100644 Binary files a/kernel/src/test/resources/com/itextpdf/kernel/pdf/PdfStructElemTest/cmp_structTreeCopyingTest06.pdf and b/kernel/src/test/resources/com/itextpdf/kernel/pdf/PdfStructElemTest/cmp_structTreeCopyingTest06.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/pdf/annot/AddMiscTypesAnnotationsTest/cmp_addTextAnnotInTagged14PdfTest.pdf b/kernel/src/test/resources/com/itextpdf/kernel/pdf/annot/AddMiscTypesAnnotationsTest/cmp_addTextAnnotInTagged14PdfTest.pdf new file mode 100644 index 0000000000..bde4b8a6c5 Binary files /dev/null and b/kernel/src/test/resources/com/itextpdf/kernel/pdf/annot/AddMiscTypesAnnotationsTest/cmp_addTextAnnotInTagged14PdfTest.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/pdf/canvas/parser/HighlightItemsTest/cmp_doubleMappingSimpleFont.pdf b/kernel/src/test/resources/com/itextpdf/kernel/pdf/canvas/parser/HighlightItemsTest/cmp_doubleMappingSimpleFont.pdf new file mode 100644 index 0000000000..bfd165d6ac Binary files /dev/null and b/kernel/src/test/resources/com/itextpdf/kernel/pdf/canvas/parser/HighlightItemsTest/cmp_doubleMappingSimpleFont.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/pdf/canvas/parser/HighlightItemsTest/cmp_invalidHighlight.pdf b/kernel/src/test/resources/com/itextpdf/kernel/pdf/canvas/parser/HighlightItemsTest/cmp_doubleMappingSimpleFont2.pdf similarity index 57% rename from kernel/src/test/resources/com/itextpdf/kernel/pdf/canvas/parser/HighlightItemsTest/cmp_invalidHighlight.pdf rename to kernel/src/test/resources/com/itextpdf/kernel/pdf/canvas/parser/HighlightItemsTest/cmp_doubleMappingSimpleFont2.pdf index 81242a6ac8..9f198a8fd5 100644 Binary files a/kernel/src/test/resources/com/itextpdf/kernel/pdf/canvas/parser/HighlightItemsTest/cmp_invalidHighlight.pdf and b/kernel/src/test/resources/com/itextpdf/kernel/pdf/canvas/parser/HighlightItemsTest/cmp_doubleMappingSimpleFont2.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/pdf/canvas/parser/HighlightItemsTest/doubleMappingSimpleFont.pdf b/kernel/src/test/resources/com/itextpdf/kernel/pdf/canvas/parser/HighlightItemsTest/doubleMappingSimpleFont.pdf new file mode 100644 index 0000000000..ac89da0309 Binary files /dev/null and b/kernel/src/test/resources/com/itextpdf/kernel/pdf/canvas/parser/HighlightItemsTest/doubleMappingSimpleFont.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/pdf/canvas/parser/HighlightItemsTest/invalidHighlight.pdf b/kernel/src/test/resources/com/itextpdf/kernel/pdf/canvas/parser/HighlightItemsTest/doubleMappingSimpleFont2.pdf similarity index 100% rename from kernel/src/test/resources/com/itextpdf/kernel/pdf/canvas/parser/HighlightItemsTest/invalidHighlight.pdf rename to kernel/src/test/resources/com/itextpdf/kernel/pdf/canvas/parser/HighlightItemsTest/doubleMappingSimpleFont2.pdf diff --git a/kernel/src/test/resources/com/itextpdf/kernel/pdf/layer/PdfLayerTest/cmp_output_copy_layered.pdf b/kernel/src/test/resources/com/itextpdf/kernel/pdf/layer/PdfLayerTest/cmp_output_copy_layered.pdf new file mode 100644 index 0000000000..d957af711a Binary files /dev/null and b/kernel/src/test/resources/com/itextpdf/kernel/pdf/layer/PdfLayerTest/cmp_output_copy_layered.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/pdf/layer/PdfLayerTest/ocpConfigs.pdf b/kernel/src/test/resources/com/itextpdf/kernel/pdf/layer/PdfLayerTest/ocpConfigs.pdf index 5d68f5028c..0f217c612e 100644 Binary files a/kernel/src/test/resources/com/itextpdf/kernel/pdf/layer/PdfLayerTest/ocpConfigs.pdf and b/kernel/src/test/resources/com/itextpdf/kernel/pdf/layer/PdfLayerTest/ocpConfigs.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_MergeWithSameNamedOCG.pdf b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_MergeWithSameNamedOCG.pdf index 652264cdc9..db714d7a22 100644 Binary files a/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_MergeWithSameNamedOCG.pdf and b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_MergeWithSameNamedOCG.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_SameStructElemNoParent.pdf b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_SameStructElemNoParent.pdf new file mode 100644 index 0000000000..9de615e2b0 Binary files /dev/null and b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_SameStructElemNoParent.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_allObjectsHaveStructParent.pdf b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_allObjectsHaveStructParent.pdf new file mode 100644 index 0000000000..ce6593c77f Binary files /dev/null and b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_allObjectsHaveStructParent.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_emptyAllTrTable.pdf b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_emptyAllTrTable.pdf new file mode 100644 index 0000000000..157f0f247e Binary files /dev/null and b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_emptyAllTrTable.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_emptyFirstTrTable.pdf b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_emptyFirstTrTable.pdf new file mode 100644 index 0000000000..c8774cecc7 Binary files /dev/null and b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_emptyFirstTrTable.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_emptyLastTrTable.pdf b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_emptyLastTrTable.pdf new file mode 100644 index 0000000000..0ebd1e8865 Binary files /dev/null and b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_emptyLastTrTable.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_emptyRowWithTags.pdf b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_emptyRowWithTags.pdf index 9da1545ba8..ca399021d7 100644 Binary files a/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_emptyRowWithTags.pdf and b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_emptyRowWithTags.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_emptySingleTrTable.pdf b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_emptySingleTrTable.pdf new file mode 100644 index 0000000000..d6be2eaf09 Binary files /dev/null and b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_emptySingleTrTable.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_emptyTrTable.pdf b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_emptyTrTable.pdf index 69190ea2a4..cd045760fe 100644 Binary files a/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_emptyTrTable.pdf and b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_emptyTrTable.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_emptyTwoAdjacentTrTable.pdf b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_emptyTwoAdjacentTrTable.pdf new file mode 100644 index 0000000000..3cd723b698 Binary files /dev/null and b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_emptyTwoAdjacentTrTable.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_mergePdfWithComplexOCGTest.pdf b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_mergePdfWithComplexOCGTest.pdf index fc84730472..3ee417dfb2 100644 Binary files a/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_mergePdfWithComplexOCGTest.pdf and b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_mergePdfWithComplexOCGTest.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_mergePdfWithComplexOCGTwiceTest.pdf b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_mergePdfWithComplexOCGTwiceTest.pdf index 949e9be430..54713f1af3 100644 Binary files a/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_mergePdfWithComplexOCGTwiceTest.pdf and b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_mergePdfWithComplexOCGTwiceTest.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_mergePdfWithOCGTest.pdf b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_mergePdfWithOCGTest.pdf index 4820634f85..1d2e7eff6f 100644 Binary files a/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_mergePdfWithOCGTest.pdf and b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_mergePdfWithOCGTest.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_mergeTwoPagePdfWithComplexOCGTest.pdf b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_mergeTwoPagePdfWithComplexOCGTest.pdf index be203568cf..b5371972c0 100644 Binary files a/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_mergeTwoPagePdfWithComplexOCGTest.pdf and b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_mergeTwoPagePdfWithComplexOCGTest.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_mergeWithSameNamedOCMD.pdf b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_mergeWithSameNamedOCMD.pdf index 2fee600a7c..d20100d15d 100644 Binary files a/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_mergeWithSameNamedOCMD.pdf and b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_mergeWithSameNamedOCMD.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_mergedDocumentWithLinkAnnotation.pdf b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_mergedDocumentWithLinkAnnotation.pdf new file mode 100644 index 0000000000..1f05cdfd4c Binary files /dev/null and b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_mergedDocumentWithLinkAnnotation.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_splitAndMergeEmptyTrTable.pdf b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_splitAndMergeEmptyTrTable.pdf new file mode 100644 index 0000000000..98fc085e27 Binary files /dev/null and b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_splitAndMergeEmptyTrTable.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_splitTableWithEmptyTrFirstPage.pdf b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_splitTableWithEmptyTrFirstPage.pdf new file mode 100644 index 0000000000..d66a8c3649 Binary files /dev/null and b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_splitTableWithEmptyTrFirstPage.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_splitTableWithEmptyTrFull.pdf b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_splitTableWithEmptyTrFull.pdf new file mode 100644 index 0000000000..36e33896a9 Binary files /dev/null and b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_splitTableWithEmptyTrFull.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_splitTableWithEmptyTrSecondPage.pdf b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_splitTableWithEmptyTrSecondPage.pdf new file mode 100644 index 0000000000..f7c7a86fa7 Binary files /dev/null and b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_splitTableWithEmptyTrSecondPage.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_structParentMissingFirstElement.pdf b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_structParentMissingFirstElement.pdf new file mode 100644 index 0000000000..4738327b22 Binary files /dev/null and b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_structParentMissingFirstElement.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_structParentMissingLastElement.pdf b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_structParentMissingLastElement.pdf new file mode 100644 index 0000000000..0c494f3285 Binary files /dev/null and b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_structParentMissingLastElement.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_trInsideTdTable.pdf b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_trInsideTdTable.pdf index 8076713366..7cf6397725 100644 Binary files a/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_trInsideTdTable.pdf and b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/cmp_trInsideTdTable.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/documentWithLinkAnnotation.pdf b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/documentWithLinkAnnotation.pdf new file mode 100644 index 0000000000..7360c282e1 Binary files /dev/null and b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/documentWithLinkAnnotation.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/emptyAllTrTable.pdf b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/emptyAllTrTable.pdf new file mode 100644 index 0000000000..ea173fe638 Binary files /dev/null and b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/emptyAllTrTable.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/emptyFirstTrTable.pdf b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/emptyFirstTrTable.pdf new file mode 100644 index 0000000000..9c85a8a867 Binary files /dev/null and b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/emptyFirstTrTable.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/emptyLastTrTable.pdf b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/emptyLastTrTable.pdf new file mode 100644 index 0000000000..f9de009a12 Binary files /dev/null and b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/emptyLastTrTable.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/emptySingleTrTable.pdf b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/emptySingleTrTable.pdf new file mode 100644 index 0000000000..9bbc871cd7 Binary files /dev/null and b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/emptySingleTrTable.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/emptyTwoAdjacentTrTable.pdf b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/emptyTwoAdjacentTrTable.pdf new file mode 100644 index 0000000000..93b6051d66 Binary files /dev/null and b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/emptyTwoAdjacentTrTable.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/splitTableWithEmptyTrFirstPage.pdf b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/splitTableWithEmptyTrFirstPage.pdf new file mode 100644 index 0000000000..a70c2cd801 Binary files /dev/null and b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/splitTableWithEmptyTrFirstPage.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/splitTableWithEmptyTrFull.pdf b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/splitTableWithEmptyTrFull.pdf new file mode 100644 index 0000000000..a70c2cd801 Binary files /dev/null and b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/splitTableWithEmptyTrFull.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/splitTableWithEmptyTrSecondPage.pdf b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/splitTableWithEmptyTrSecondPage.pdf new file mode 100644 index 0000000000..a70c2cd801 Binary files /dev/null and b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/splitTableWithEmptyTrSecondPage.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfSplitterTest/cmp/cmp_splitDocument1_1.pdf b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfSplitterTest/cmp/cmp_splitDocument1_1.pdf index 7abfcaccea..5626de76b0 100644 Binary files a/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfSplitterTest/cmp/cmp_splitDocument1_1.pdf and b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfSplitterTest/cmp/cmp_splitDocument1_1.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfSplitterTest/cmp/cmp_splitDocument2_1.pdf b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfSplitterTest/cmp/cmp_splitDocument2_1.pdf index 00fab5e358..5c69bdc4bd 100644 Binary files a/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfSplitterTest/cmp/cmp_splitDocument2_1.pdf and b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfSplitterTest/cmp/cmp_splitDocument2_1.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfSplitterTest/cmp/cmp_splitDocument3_1.pdf b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfSplitterTest/cmp/cmp_splitDocument3_1.pdf index cea62b9880..697ab2aaa0 100644 Binary files a/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfSplitterTest/cmp/cmp_splitDocument3_1.pdf and b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfSplitterTest/cmp/cmp_splitDocument3_1.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfSplitterTest/cmp/cmp_splitDocument4_1.pdf b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfSplitterTest/cmp/cmp_splitDocument4_1.pdf index e22013c4ff..afccdb90e1 100644 Binary files a/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfSplitterTest/cmp/cmp_splitDocument4_1.pdf and b/kernel/src/test/resources/com/itextpdf/kernel/utils/PdfSplitterTest/cmp/cmp_splitDocument4_1.pdf differ diff --git a/kernel/src/test/resources/com/itextpdf/kernel/utils/TaggedPdfReaderToolTest/cmpXml01.xml b/kernel/src/test/resources/com/itextpdf/kernel/utils/TaggedPdfReaderToolTest/cmpXml01.xml index 9c3b1c78c2..831d4812fa 100644 --- a/kernel/src/test/resources/com/itextpdf/kernel/utils/TaggedPdfReaderToolTest/cmpXml01.xml +++ b/kernel/src/test/resources/com/itextpdf/kernel/utils/TaggedPdfReaderToolTest/cmpXml01.xml @@ -1773,12 +1773,12 @@ that letter. Drag your finger along the index to scroll quickly through the list :end description.]]> Index

-m Tap an item in the list to choose it. Depending on the list, tapping an item can do +m Tap an item in the list to choose it. Depending on the list, tapping an item can do different things—for example, it may open a new list, play a song, open an email, or show someone’s contact information so you can call that person. -m The back button in the upper-left corner shows the name of the previous list. Tap it to +m The back button in the upper-left corner shows the name of the previous list. Tap it to go back. -m When viewing photos, web pages, email, or maps, you can zoom in and out. Pinch your +m When viewing photos, web pages, email, or maps, you can zoom in and out. Pinch your fingers together or apart. For photos and web pages, you can double-tap (tap twice quickly) to zoom in, then double-tap again to zoom out. For maps, double-tap to zoom in and tap once with two fingers to zoom out.

@@ -1798,9 +1798,9 @@ mistyped words.

iPhone provides keyboards in multiple languages, and supports the following keyboard formats:

-Â 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

@@ -4152,12 +4152,12 @@ Edit. Then do one of the following: Â To make a new folder, tap New Folder.

-Â 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

@@ -7867,14 +7867,14 @@ Appendix A Tips and Troubleshooting

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.itextpdf root - 8.0.3 + 8.0.4 + layout + iText - layout https://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.itextpdf root - 8.0.3 + 8.0.4 + pdfa + iText - pdfa https://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.itextpdf root - 8.0.3 + 8.0.4 + pdftest + iText - pdftest https://itextpdf.com/ + true 1.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.itextpdf root - 8.0.3 + 8.0.4 + pdfua + iText - pdfua https://itextpdf.com/ @@ -21,12 +24,29 @@ layout ${project.version} + + com.itextpdf + forms + ${project.version} + com.itextpdf pdftest ${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 = "(?