Type3Font.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.font;

import com.itextpdf.io.font.FontNames;
import com.itextpdf.io.font.FontProgram;
import com.itextpdf.io.font.constants.FontDescriptorFlags;
import com.itextpdf.io.font.constants.FontStretches;
import com.itextpdf.io.font.constants.FontWeights;
import com.itextpdf.io.font.otf.Glyph;

import java.util.HashMap;
import java.util.Map;

/**
 * FontProgram class for Type 3 font. Contains map of {@link Type3Glyph}.
 * Type3Glyphs belong to a particular pdf document.
 * Note, an instance of Type3Font can not be reused for multiple pdf documents.
 */
public class Type3Font extends FontProgram {

    private final Map<Integer, Type3Glyph> type3Glyphs = new HashMap<>();
    /**
     * Stores glyphs without associated unicode.
     */
    private final Map<Integer, Type3Glyph> type3GlyphsWithoutUnicode = new HashMap<>();
    private boolean colorized = false;
    private int flags = 0;

    /**
     * Creates a Type 3 font program.
     *
     * @param colorized defines whether the glyph color is specified in the glyph descriptions in the font.
     */
    Type3Font(boolean colorized) {
        this.colorized = colorized;
        this.fontNames = new FontNames();
        getFontMetrics().setBbox(0, 0, 0, 0);
    }

    /**
     * Returns a glyph by unicode.
     *
     * @param unicode glyph unicode
     *
     * @return {@link Type3Glyph} glyph, or {@code null} if this font does not contain glyph for the unicode
     */
    public Type3Glyph getType3Glyph(int unicode) {
        return type3Glyphs.get(unicode);
    }

    /**
     * Returns a glyph by its code. These glyphs may not have unicode.
     *
     * @param code glyph code
     *
     * @return {@link Type3Glyph} glyph, or {@code null} if this font does not contain glyph for the code
     */
    public Type3Glyph getType3GlyphByCode(int code) {
        Type3Glyph glyph = type3GlyphsWithoutUnicode.get(code);
        if (glyph == null && codeToGlyph.get(code) != null) {
            glyph = type3Glyphs.get(codeToGlyph.get(code).getUnicode());
        }
        return glyph;
    }

    @Override
    public int getPdfFontFlags() {
        return flags;
    }

    @Override
    public boolean isFontSpecific() {
        return false;
    }

    public boolean isColorized() {
        return colorized;
    }

    @Override
    public int getKerning(Glyph glyph1, Glyph glyph2) {
        return 0;
    }


    /**
     * Returns number of glyphs for this font.
     * Its also count glyphs without unicode.
     * See {@link #type3GlyphsWithoutUnicode}.
     *
     * @return {@code int} number off all glyphs
     */
    public int getNumberOfGlyphs() {
        return type3Glyphs.size() + type3GlyphsWithoutUnicode.size();
    }

    /**
     * Sets the PostScript name of the font.
     * <p>
     * If full name is null, it will be set as well.
     *
     * @param fontName the PostScript name of the font, shall not be null or empty.
     */
    @Override
    protected void setFontName(String fontName) {
        // This dummy override allows PdfType3Font to use setter from different module.
        super.setFontName(fontName);
    }

    /**
     * Sets a preferred font family name.
     *
     * @param fontFamily a preferred font family name.
     */
    @Override
    protected void setFontFamily(String fontFamily) {
        // This dummy override allows PdfType3Font to use setter from different module.
        super.setFontFamily(fontFamily);
    }

    /**
     * Sets font weight.
     *
     * @param fontWeight integer form 100 to 900. See {@link FontWeights}.
     */
    @Override
    protected void setFontWeight(int fontWeight) {
        // This dummy override allows PdfType3Font to use setter from different module.
        super.setFontWeight(fontWeight);
    }

    /**
     * Sets font width in css notation (font-stretch property)
     *
     * @param fontWidth {@link FontStretches}.
     */
    @Override
    protected void setFontStretch(String fontWidth) {
        // This dummy override allows PdfType3Font to use setter from different module.
        super.setFontStretch(fontWidth);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void setCapHeight(int capHeight) {
        // This dummy override allows PdfType3Font to use setter from different module.
        super.setCapHeight(capHeight);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void setItalicAngle(int italicAngle) {
        // This dummy override allows PdfType3Font to use setter from different module.
        super.setItalicAngle(italicAngle);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void setTypoAscender(int ascender) {
        // This dummy override allows PdfType3Font to use setter from different module.
        super.setTypoAscender(ascender);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void setTypoDescender(int descender) {
        // This dummy override allows PdfType3Font to use setter from different module.
        super.setTypoDescender(descender);
    }

    /**
     * Sets Font descriptor flags.
     * @see FontDescriptorFlags
     *
     * @param flags {@link FontDescriptorFlags}.
     */
    void setPdfFontFlags(int flags) {
        this.flags = flags;
    }

    void addGlyph(int code, int unicode, int width, int[] bbox, Type3Glyph type3Glyph) {
        if (codeToGlyph.containsKey(code)) {
            removeGlyphFromMappings(code);
        }
        Glyph glyph = new Glyph(code, width, unicode, bbox);
        codeToGlyph.put(code, glyph);
        if (unicode < 0) {
            type3GlyphsWithoutUnicode.put(code, type3Glyph);
        } else {
            unicodeToGlyph.put(unicode, glyph);
            type3Glyphs.put(unicode, type3Glyph);
        }
        recalculateAverageWidth();
    }

    private void removeGlyphFromMappings(int glyphCode) {
        Glyph removed = codeToGlyph.remove(glyphCode);
        if (removed == null) {
            return;
        }
        int unicode = removed.getUnicode();
        if (unicode < 0) {
            type3GlyphsWithoutUnicode.remove(glyphCode);
        } else {
            unicodeToGlyph.remove(unicode);
            type3Glyphs.remove(unicode);
        }
    }

    private void recalculateAverageWidth() {
        int widthSum = 0;
        int glyphsNumber = codeToGlyph.size();
        for (Glyph glyph : codeToGlyph.values()) {
            if (glyph.getWidth() == 0) {
                glyphsNumber--;
                continue;
            }
            widthSum += glyph.getWidth();
        }
        avgWidth = glyphsNumber == 0 ? 0 : widthSum / glyphsNumber;
    }
}