PdfSpecialCs.java
/*
This file is part of the iText (R) project.
Copyright (c) 1998-2025 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 <https://www.gnu.org/licenses/>.
*/
package com.itextpdf.kernel.pdf.colorspace;
import com.itextpdf.kernel.exceptions.KernelExceptionMessageConstant;
import com.itextpdf.kernel.exceptions.PdfException;
import com.itextpdf.kernel.pdf.PdfArray;
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.PdfString;
import com.itextpdf.kernel.pdf.function.IPdfFunction;
import com.itextpdf.kernel.pdf.function.PdfFunctionFactory;
import java.util.Arrays;
import java.util.List;
public abstract class PdfSpecialCs extends PdfColorSpace {
protected PdfSpecialCs(PdfArray pdfObject) {
super(pdfObject);
}
/**
* To manually flush a {@code PdfObject} behind this wrapper, you have to ensure
* that this object is added to the document, i.e. it has an indirect reference.
* Basically this means that before flushing you need to explicitly call {@link #makeIndirect(PdfDocument)}.
* For example: wrapperInstance.makeIndirect(document).flush();
* Note that not every wrapper require this, only those that have such warning in documentation.
*/
@Override
public void flush() {
super.flush();
}
@Override
protected boolean isWrappedObjectMustBeIndirect() {
return true;
}
public static class Indexed extends PdfSpecialCs {
public Indexed(PdfArray pdfObject) {
super(pdfObject);
}
public Indexed(PdfObject base, int hival, PdfString lookup) {
this(getIndexedCsArray(base, hival, lookup));
}
@Override
public int getNumberOfComponents() {
return 1;
}
public PdfColorSpace getBaseCs() {
return makeColorSpace(((PdfArray)getPdfObject()).get(1));
}
private static PdfArray getIndexedCsArray(PdfObject base, int hival, PdfString lookup) {
PdfArray indexed = new PdfArray();
indexed.add(PdfName.Indexed);
indexed.add(base);
indexed.add(new PdfNumber(hival));
indexed.add(lookup.setHexWriting(true));
return indexed;
}
}
public static class Separation extends PdfSpecialCs {
public Separation(PdfArray pdfObject) {
super(pdfObject);
}
public Separation(PdfName name, PdfObject alternateSpace, PdfObject tintTransform) {
this(getSeparationCsArray(name, alternateSpace, tintTransform));
}
/**
* Creates a new separation color space.
*
* @param name The name for the separation color
* @param alternateSpace The alternate colorspace
* @param tintTransform The function how the transform colors in the separation color space
* to the alternate color space
*/
public Separation(String name, PdfColorSpace alternateSpace, IPdfFunction tintTransform) {
this(new PdfName(name), alternateSpace.getPdfObject(),
tintTransform.getAsPdfObject());
if (!tintTransform.checkCompatibilityWithColorSpace(alternateSpace)) {
throw new PdfException(
KernelExceptionMessageConstant.FUNCTION_IS_NOT_COMPATIBLE_WITH_COLOR_SPACE, this);
}
}
@Override
public int getNumberOfComponents() {
return 1;
}
public PdfColorSpace getBaseCs() {
return makeColorSpace(((PdfArray)getPdfObject()).get(2));
}
public PdfName getName() {
return ((PdfArray)getPdfObject()).getAsName(1);
}
/**
* Gets the function to calulate a separation color value to an alternative colorspace.
*
* @return a {@link IPdfFunction} to perform the calculation
*/
public IPdfFunction getTintTransformation() {
return PdfFunctionFactory.create(((PdfArray)getPdfObject()).get(3));
}
private static PdfArray getSeparationCsArray(PdfName name, PdfObject alternateSpace, PdfObject tintTransform) {
PdfArray separation = new PdfArray();
separation.add(PdfName.Separation);
separation.add(name);
separation.add(alternateSpace);
separation.add(tintTransform);
return separation;
}
}
public static class DeviceN extends PdfSpecialCs {
protected int numOfComponents = 0;
public DeviceN(PdfArray pdfObject) {
super(pdfObject);
numOfComponents = pdfObject.getAsArray(1).size();
}
public DeviceN(PdfArray names, PdfObject alternateSpace, PdfObject tintTransform) {
this(getDeviceNCsArray(names, alternateSpace, tintTransform));
}
/**
* Creates a new DiviceN colorspace.
*
* @param names the names of the components
* @param alternateSpace the alternate colorspace
* @param tintTransform the function to transform colors to the alternate colorspace
*/
public DeviceN(List<String> names, PdfColorSpace alternateSpace, IPdfFunction tintTransform) {
this(new PdfArray(names, true), alternateSpace.getPdfObject(),
tintTransform.getAsPdfObject());
if (tintTransform.getInputSize() != numOfComponents ||
tintTransform.getOutputSize() != alternateSpace.getNumberOfComponents()) {
throw new PdfException(
KernelExceptionMessageConstant.FUNCTION_IS_NOT_COMPATIBLE_WITH_COLOR_SPACE, this);
}
}
@Override
public int getNumberOfComponents() {
return numOfComponents;
}
public PdfColorSpace getBaseCs() {
return makeColorSpace(((PdfArray)getPdfObject()).get(2));
}
public PdfArray getNames() {
return ((PdfArray)getPdfObject()).getAsArray(1);
}
protected static PdfArray getDeviceNCsArray(PdfArray names, PdfObject alternateSpace, PdfObject tintTransform) {
PdfArray deviceN = new PdfArray();
deviceN.add(PdfName.DeviceN);
deviceN.add(names);
deviceN.add(alternateSpace);
deviceN.add(tintTransform);
return deviceN;
}
}
public static class NChannel extends DeviceN {
public NChannel(PdfArray pdfObject) {
super(pdfObject);
}
public NChannel(PdfArray names, PdfObject alternateSpace, PdfObject tintTransform, PdfDictionary attributes) {
this(getNChannelCsArray(names, alternateSpace, tintTransform, attributes));
}
/**
* Creates a new NChannel colorspace.
*
* @param names the names for the components
* @param alternateSpace the alternative colorspace
* @param tintTransform the function to transform colors to the alternate color space
* @param attributes NChannel specific attributes
*/
public NChannel(List<String> names, PdfColorSpace alternateSpace, IPdfFunction tintTransform,
PdfDictionary attributes) {
this(new PdfArray(names, true), alternateSpace.getPdfObject(),
tintTransform.getAsPdfObject(), attributes);
if (tintTransform.getInputSize() != 1 ||
tintTransform.getOutputSize() != alternateSpace.getNumberOfComponents()) {
throw new PdfException(
KernelExceptionMessageConstant.FUNCTION_IS_NOT_COMPATIBLE_WITH_COLOR_SPACE, this);
}
}
protected static PdfArray getNChannelCsArray(PdfArray names, PdfObject alternateSpace, PdfObject tintTransform,
PdfDictionary attributes) {
PdfArray nChannel = getDeviceNCsArray(names, alternateSpace, tintTransform);
nChannel.add(attributes);
return nChannel;
}
}
public static class Pattern extends PdfColorSpace {
public Pattern() {
super(PdfName.Pattern);
}
protected Pattern(PdfObject pdfObj) {
super(pdfObj);
}
@Override
public int getNumberOfComponents() {
return 0;
}
@Override
protected boolean isWrappedObjectMustBeIndirect() {
return false;
}
}
public static class UncoloredTilingPattern extends Pattern {
public UncoloredTilingPattern(PdfArray pdfObject) {
super(pdfObject);
}
public UncoloredTilingPattern(PdfColorSpace underlyingColorSpace) {
super(new PdfArray(Arrays.asList(PdfName.Pattern, underlyingColorSpace.getPdfObject())));
}
/**
* To manually flush a {@code PdfObject} behind this wrapper, you have to ensure
* that this object is added to the document, i.e. it has an indirect reference.
* Basically this means that before flushing you need to explicitly call {@link #makeIndirect(PdfDocument)}.
* For example: wrapperInstance.makeIndirect(document).flush();
* Note that not every wrapper require this, only those that have such warning in documentation.
*/
@Override
public void flush() {
super.flush();
}
public PdfColorSpace getUnderlyingColorSpace() {
return PdfColorSpace.makeColorSpace(((PdfArray) getPdfObject()).get(1));
}
@Override
public int getNumberOfComponents() {
return PdfColorSpace.makeColorSpace(((PdfArray) getPdfObject()).get(1)).getNumberOfComponents();
}
@Override
protected boolean isWrappedObjectMustBeIndirect() {
return true;
}
}
}