AbstractPdfShading.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.shading;

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.PdfObjectWrapper;
import com.itextpdf.kernel.pdf.PdfStream;
import com.itextpdf.kernel.pdf.colorspace.PdfColorSpace;
import com.itextpdf.kernel.pdf.colorspace.PdfSpecialCs.Pattern;
import com.itextpdf.kernel.pdf.function.IPdfFunction;

/**
 * The PdfShading class that represents the Shading Dictionary PDF object.
 */
public abstract class AbstractPdfShading extends PdfObjectWrapper<PdfDictionary> {

    /**
     * Creates the {@link AbstractPdfShading} object from the existing {@link PdfDictionary} with corresponding type.
     *
     * @param shadingDictionary {@link PdfDictionary} from which the {@link AbstractPdfShading} object will be created
     *
     * @return Created {@link AbstractPdfShading} object
     */
    public static AbstractPdfShading makeShading(PdfDictionary shadingDictionary) {
        if (!shadingDictionary.containsKey(PdfName.ShadingType)) {
            throw new PdfException(KernelExceptionMessageConstant.SHADING_TYPE_NOT_FOUND);
        }
        if (!shadingDictionary.containsKey(PdfName.ColorSpace)) {
            throw new PdfException(KernelExceptionMessageConstant.COLOR_SPACE_NOT_FOUND);
        }

        AbstractPdfShading shading;
        switch (shadingDictionary.getAsNumber(PdfName.ShadingType).intValue()) {
            case ShadingType.FUNCTION_BASED:
                shading = new PdfFunctionBasedShading(shadingDictionary);
                break;
            case ShadingType.AXIAL:
                shading = new PdfAxialShading(shadingDictionary);
                break;
            case ShadingType.RADIAL:
                shading = new PdfRadialShading(shadingDictionary);
                break;
            case ShadingType.FREE_FORM_GOURAUD_SHADED_TRIANGLE_MESH:
                if (!shadingDictionary.isStream()) {
                    throw new PdfException(KernelExceptionMessageConstant.UNEXPECTED_SHADING_TYPE);
                }
                shading = new PdfFreeFormGouraudShadedTriangleShading((PdfStream) shadingDictionary);
                break;
            case ShadingType.LATTICE_FORM_GOURAUD_SHADED_TRIANGLE_MESH:
                if (!shadingDictionary.isStream()) {
                    throw new PdfException(KernelExceptionMessageConstant.UNEXPECTED_SHADING_TYPE);
                }
                shading = new PdfLatticeFormGouraudShadedTriangleShading((PdfStream) shadingDictionary);
                break;
            case ShadingType.COONS_PATCH_MESH:
                if (!shadingDictionary.isStream()) {
                    throw new PdfException(KernelExceptionMessageConstant.UNEXPECTED_SHADING_TYPE);
                }
                shading = new PdfCoonsPatchShading((PdfStream) shadingDictionary);
                break;
            case ShadingType.TENSOR_PRODUCT_PATCH_MESH:
                if (!shadingDictionary.isStream()) {
                    throw new PdfException(KernelExceptionMessageConstant.UNEXPECTED_SHADING_TYPE);
                }
                shading = new PdfTensorProductPatchShading((PdfStream) shadingDictionary);
                break;
            default:
                throw new PdfException(KernelExceptionMessageConstant.UNEXPECTED_SHADING_TYPE);
        }
        return shading;
    }

    /**
     * Creates the {@link AbstractPdfShading} object from the existing {@link PdfDictionary}.
     *
     * @param pdfObject {@link PdfDictionary} from which the {@link AbstractPdfShading} object will be created
     */
    protected AbstractPdfShading(PdfDictionary pdfObject) {
        super(pdfObject);
    }

    /**
     * Creates the {@link AbstractPdfShading} object from the existing {@link PdfDictionary},
     * using provided type and colorspace.
     *
     * @param pdfObject {@link PdfDictionary} from which the {@link AbstractPdfShading} object will be created
     * @param type type with which this {@link AbstractPdfShading} object will be created
     * @param colorSpace {@link PdfColorSpace} with which this {@link AbstractPdfShading} object will be created
     */
    protected AbstractPdfShading(PdfDictionary pdfObject, int type, PdfColorSpace colorSpace) {
        super(pdfObject);
        getPdfObject().put(PdfName.ShadingType, new PdfNumber(type));
        if (colorSpace instanceof Pattern) {
            throw new IllegalArgumentException("colorSpace");
        }
        getPdfObject().put(PdfName.ColorSpace, colorSpace.getPdfObject());
    }

    /**
     * Gets the shading type.
     *
     * @return int value of {@link PdfName#ShadingType}
     */
    public int getShadingType() {
        return (int) getPdfObject().getAsInt(PdfName.ShadingType);
    }

    /**
     * Gets the color space in which colour values shall be expressed.
     *
     * @return {@link PdfObject} Color space
     */
    public PdfObject getColorSpace() {
        return getPdfObject().get(PdfName.ColorSpace);
    }

    /**
     * Gets the function PdfObject that represents color transitions
     * across the shading geometry.
     *
     * @return {@link PdfObject} Function
     */
    public PdfObject getFunction() {
        return getPdfObject().get(PdfName.Function);
    }

    /**
     * Sets the function that represents color transitions
     * across the shading geometry as one object.
     *
     * @param function The {@link IPdfFunction} to set
     */
    public final void setFunction(IPdfFunction function) {
        getPdfObject().put(PdfName.Function, function.getAsPdfObject());
        setModified();
    }

    /**
     * Sets the function object that represents color transitions
     * across the shading geometry as an array of functions.
     *
     * @param functions The array of {@link IPdfFunction} to be set
     */
    public final void setFunction(IPdfFunction[] functions) {
        PdfArray arr = new PdfArray();
        for (IPdfFunction func : functions) {
            arr.add(func.getAsPdfObject());
        }
        getPdfObject().put(PdfName.Function, arr);
        setModified();
    }

    /**
     * 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 final void flush() {
        super.flush();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected boolean isWrappedObjectMustBeIndirect() {
        return true;
    }
}