All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.itextpdf.forms.form.renderer.AbstractSelectFieldRenderer Maven / Gradle / Ivy

/*
    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.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;
import com.itextpdf.layout.layout.LayoutResult;
import com.itextpdf.layout.properties.Property;
import com.itextpdf.layout.properties.UnitValue;
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;

/**
 * Abstract {@link BlockRenderer} for select form fields.
 */
public abstract class AbstractSelectFieldRenderer extends BlockRenderer {

    /**
     * Creates a new {@link AbstractSelectFieldRenderer} instance.
     *
     * @param modelElement the model element
     */
    protected AbstractSelectFieldRenderer(AbstractSelectField modelElement) {
        super(modelElement);
        addChild(createFlatRenderer());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public LayoutResult layout(LayoutContext layoutContext) {
        // Resolve width here in case it's relative, while parent width is still intact.
        // If it's inline-block context, relative width is already resolved.
        Float width = retrieveWidth(layoutContext.getArea().getBBox().getWidth());
        if (width != null) {
            updateWidth(UnitValue.createPointValue((float) width));
        }

        float childrenMaxWidth = getMinMaxWidth().getMaxWidth();

        LayoutArea area = layoutContext.getArea().clone();
        area.getBBox().moveDown(INF - area.getBBox().getHeight()).setHeight(INF).setWidth(childrenMaxWidth + EPS);
        // A workaround for the issue that super.layout clears Property.FORCED_PLACEMENT,
        // but we need it later in this function
        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);
            } else {
                return new LayoutResult(LayoutResult.NOTHING, null, null, this, this);
            }
        }

        float availableHeight = layoutContext.getArea().getBBox().getHeight();
        boolean isClippedHeight = layoutContext.isClippedHeight();

        Rectangle dummy = new Rectangle(0, 0);
        applyMargins(dummy, true);
        applyBorderBox(dummy, true);
        applyPaddings(dummy, true);
        float additionalHeight = dummy.getHeight();

        availableHeight -= additionalHeight;
        availableHeight = Math.max(availableHeight, 0);
        float actualHeight = getOccupiedArea().getBBox().getHeight() - additionalHeight;

        float finalSelectFieldHeight = getFinalSelectFieldHeight(availableHeight, actualHeight, isClippedHeight);
        if (finalSelectFieldHeight < 0) {
            return new LayoutResult(LayoutResult.NOTHING, null, null, this, this);
        }

        float delta = finalSelectFieldHeight - actualHeight;
        if (Math.abs(delta) > EPS) {
            getOccupiedArea().getBBox().increaseHeight(delta).moveDown(delta);
        }

        return layoutResult;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void draw(DrawContext drawContext) {
        if (isFlatten()) {
            super.draw(drawContext);
        } else {
            drawChildren(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.
     */
    protected String getLang() {
        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();
            List kidsRoles = formParentPointer.getKidsRoles();
            int lastFormIndex = kidsRoles.lastIndexOf(StandardRoles.FORM);
            TagTreePointer formPointer = formParentPointer.moveToKid(lastFormIndex);

            if (getLang() != null) {
                formPointer.getProperties().setLanguage(getLang());
            }
            formParentPointer.moveToParent();
        }
    }

    /**
     * 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.
     */
    protected boolean isFlatten() {
        return (boolean) getPropertyAsBoolean(FormProperty.FORM_FIELD_FLATTEN);
    }

    /**
     * Gets the model id.
     *
     * @return the model id.
     */
    protected String getModelId() {
        return ((IFormField) getModelElement()).getId();
    }

    /**
     * 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
     */
    protected void setupBuilderValues(ChoiceFormFieldBuilder builder, AbstractSelectField field) {
        List options = field.getItems();
        if (options.isEmpty()) {
            builder.setOptions(new String[0]);
            return;
        }

        final boolean supportExportValueAndDisplayValue = field.hasExportAndDisplayValues();
        // If one element has export value and display value, then all elements must have export value and display value
        if (supportExportValueAndDisplayValue) {
            String[][] exportValuesAndDisplayValues = new String[options.size()][];
            for (int i = 0; i < options.size(); i++) {
                SelectFieldItem option = options.get(i);
                String[] exportValues = new String[2];
                exportValues[0] = option.getExportValue();
                exportValues[1] = option.getDisplayValue();
                exportValuesAndDisplayValues[i] = exportValues;
            }
            builder.setOptions(exportValuesAndDisplayValues);
        } else {
            // In normal case we just use display values as this will correctly give the one value that we need
            String[] displayValues = new String[options.size()];
            for (int i = 0; i < options.size(); i++) {
                SelectFieldItem option = options.get(i);
                displayValues[i] = option.getDisplayValue();
            }
            builder.setOptions(displayValues);
        }
    }

    /**
     * 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) {
            if (isForcedPlacement) {
                return availableHeight;
            }
            return -1;
        }
        return actualHeight;
    }

    /**
     * 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 return {@link IConformanceLevel}
     */
    @Deprecated
    protected PdfAConformanceLevel getConformanceLevel(PdfDocument document) {
        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;
        }
        if (document == null) {
            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()) {
            if (isOptionRenderer(option)) {
                if (Boolean.TRUE.equals(option.getProperty(FormProperty.FORM_FIELD_SELECTED))) {
                    selectedOptions.add(option);
                }
            } else {
                List subSelectedOptions = getOptionsMarkedSelected(option);
                selectedOptions.addAll(subSelectedOptions);
            }
        }
        return selectedOptions;
    }

    static boolean isOptGroupRenderer(IRenderer renderer) {
        return renderer.hasProperty(FormProperty.FORM_FIELD_LABEL) &&
                !renderer.hasProperty(FormProperty.FORM_FIELD_SELECTED);
    }

    static boolean isOptionRenderer(IRenderer child) {
        return child.hasProperty(FormProperty.FORM_FIELD_SELECTED);
    }

    private LayoutResult makeLayoutResultFull(LayoutArea layoutArea, LayoutResult layoutResult) {
        IRenderer splitRenderer = layoutResult.getSplitRenderer() == null ? this : layoutResult.getSplitRenderer();
        if (occupiedArea == null) {
            occupiedArea = new LayoutArea(layoutArea.getPageNumber(),
                    new Rectangle(layoutArea.getBBox().getLeft(), layoutArea.getBBox().getTop(), 0, 0));
        }
        layoutResult = new LayoutResult(LayoutResult.FULL, occupiedArea, splitRenderer, null);
        return layoutResult;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy