org.sejda.sambox.pdmodel.graphics.state.PDExtendedGraphicsState Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of sambox Show documentation
Show all versions of sambox Show documentation
An Apache PDFBox fork intended to be used as PDF processor for Sejda and PDFsam
related projects
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sejda.sambox.pdmodel.graphics.state;
import static java.util.Objects.nonNull;
import static java.util.Optional.ofNullable;
import java.io.IOException;
import org.sejda.sambox.cos.COSArray;
import org.sejda.sambox.cos.COSBase;
import org.sejda.sambox.cos.COSDictionary;
import org.sejda.sambox.cos.COSFloat;
import org.sejda.sambox.cos.COSName;
import org.sejda.sambox.cos.COSNumber;
import org.sejda.sambox.cos.COSObjectable;
import org.sejda.sambox.pdmodel.graphics.PDFontSetting;
import org.sejda.sambox.pdmodel.graphics.PDLineDashPattern;
import org.sejda.sambox.pdmodel.graphics.blend.BlendMode;
/**
* An extended graphics state dictionary.
*
* @author Ben Litchfield
*/
public class PDExtendedGraphicsState implements COSObjectable
{
private final COSDictionary dict;
/**
* Default constructor, creates blank graphics state.
*/
public PDExtendedGraphicsState()
{
dict = new COSDictionary();
dict.setItem(COSName.TYPE, COSName.EXT_G_STATE);
}
/**
* Create a graphics state from an existing dictionary.
*
* @param dictionary The existing graphics state.
*/
public PDExtendedGraphicsState(COSDictionary dictionary)
{
dict = dictionary;
}
/**
* This will implement the gs operator.
*
* @param gs The state to copy this dictionaries values into.
*
* @throws IOException If there is an error copying font information.
*/
public void copyIntoGraphicsState(PDGraphicsState gs) throws IOException
{
for (COSName key : dict.keySet())
{
if (key.equals(COSName.LW))
{
gs.setLineWidth(defaultIfNull(getLineWidth(), 1));
}
else if (key.equals(COSName.LC))
{
gs.setLineCap(getLineCapStyle());
}
else if (key.equals(COSName.LJ))
{
gs.setLineJoin(getLineJoinStyle());
}
else if (key.equals(COSName.ML))
{
gs.setMiterLimit(defaultIfNull(getMiterLimit(), 10));
}
else if (key.equals(COSName.D))
{
gs.setLineDashPattern(getLineDashPattern());
}
else if (key.equals(COSName.RI))
{
gs.setRenderingIntent(getRenderingIntent());
}
else if (key.equals(COSName.OPM))
{
gs.setOverprintMode(defaultIfNull(getOverprintMode(), 0));
}
else if (key.equals(COSName.FONT))
{
PDFontSetting setting = getFontSetting();
if (setting != null)
{
gs.getTextState().setFont(setting.getFont());
gs.getTextState().setFontSize(setting.getFontSize());
}
}
else if (key.equals(COSName.FL))
{
gs.setFlatness(defaultIfNull(getFlatnessTolerance(), 1.0f));
}
else if (key.equals(COSName.SM))
{
gs.setSmoothness(defaultIfNull(getSmoothnessTolerance(), 0));
}
else if (key.equals(COSName.SA))
{
gs.setStrokeAdjustment(getAutomaticStrokeAdjustment());
}
else if (key.equals(COSName.CA))
{
gs.setAlphaConstant(defaultIfNull(getStrokingAlphaConstant(), 1.0f));
}
else if (key.equals(COSName.CA_NS))
{
gs.setNonStrokeAlphaConstants(defaultIfNull(getNonStrokingAlphaConstant(), 1.0f));
}
else if (key.equals(COSName.AIS))
{
gs.setAlphaSource(getAlphaSourceFlag());
}
else if (key.equals(COSName.TK))
{
gs.getTextState().setKnockoutFlag(getTextKnockoutFlag());
}
else if (key.equals(COSName.SMASK))
{
PDSoftMask softmask = getSoftMask();
if (softmask != null)
{
// Softmask must know the CTM at the time the ExtGState is activated. Read
// https://bugs.ghostscript.com/show_bug.cgi?id=691157#c7 for a good explanation.
softmask.setInitialTransformationMatrix(
gs.getCurrentTransformationMatrix().clone());
}
gs.setSoftMask(softmask);
}
else if (key.equals(COSName.BM))
{
gs.setBlendMode(getBlendMode());
}
else if (key.equals(COSName.TR))
{
if (dict.containsKey(COSName.TR2))
{
// "If both TR and TR2 are present in the same graphics state parameter dictionary,
// TR2 shall take precedence."
continue;
}
gs.setTransfer(getTransfer());
}
else if (key.equals(COSName.TR2))
{
gs.setTransfer(getTransfer2());
}
}
}
private float defaultIfNull(Float val, float fallback)
{
return nonNull(val) ? val : fallback;
}
/**
* This will get the underlying dictionary that this class acts on.
*
* @return The underlying dictionary for this class.
*/
@Override
public COSDictionary getCOSObject()
{
return dict;
}
/**
* This will get the line width. This will return null if there is no line width
*
* @return null or the LW value of the dictionary.
*/
public Float getLineWidth()
{
return getFloatItem(COSName.LW);
}
/**
* This will set the line width.
*
* @param width The line width for the object.
*/
public void setLineWidth(Float width)
{
setFloatItem(COSName.LW, width);
}
/**
* This will get the line cap style.
*
* @return null or the LC value of the dictionary.
*/
public int getLineCapStyle()
{
return dict.getInt(COSName.LC);
}
/**
* This will set the line cap style for the graphics state.
*
* @param style The new line cap style to set.
*/
public void setLineCapStyle(int style)
{
dict.setInt(COSName.LC, style);
}
/**
* This will get the line join style.
*
* @return null or the LJ value in the dictionary.
*/
public int getLineJoinStyle()
{
return dict.getInt(COSName.LJ);
}
/**
* This will set the line join style.
*
* @param style The new line join style.
*/
public void setLineJoinStyle(int style)
{
dict.setInt(COSName.LJ, style);
}
/**
* This will get the miter limit.
*
* @return null or the ML value in the dictionary.
*/
public Float getMiterLimit()
{
return getFloatItem(COSName.ML);
}
/**
* This will set the miter limit for the graphics state.
*
* @param miterLimit The new miter limit value
*/
public void setMiterLimit(Float miterLimit)
{
setFloatItem(COSName.ML, miterLimit);
}
/**
* This will get the dash pattern.
*
* @return null or the D value in the dictionary.
*/
public PDLineDashPattern getLineDashPattern()
{
COSArray dp = dict.getDictionaryObject(COSName.D, COSArray.class);
if (nonNull(dp) && dp.size() == 2)
{
COSBase dashArray = dp.getObject(0);
COSBase phase = dp.getObject(1);
if (dashArray instanceof COSArray && phase instanceof COSNumber)
{
return new PDLineDashPattern((COSArray) dashArray, ((COSNumber) phase).intValue());
}
}
return null;
}
/**
* This will set the dash pattern for the graphics state.
*
* @param dashPattern The dash pattern
*/
public void setLineDashPattern(PDLineDashPattern dashPattern)
{
dict.setItem(COSName.D, dashPattern.getCOSObject());
}
/**
* This will get the rendering intent.
*
* @return null or the RI value in the dictionary.
*/
public RenderingIntent getRenderingIntent()
{
String ri = dict.getNameAsString("RI");
if (ri != null)
{
return RenderingIntent.fromString(ri);
}
return null;
}
/**
* This will set the rendering intent for the graphics state.
*
* @param ri The new rendering intent
*/
public void setRenderingIntent(String ri)
{
dict.setName("RI", ri);
}
/**
* This will get the overprint control.
*
* @return The overprint control or null if one has not been set.
*/
public boolean getStrokingOverprintControl()
{
return dict.getBoolean(COSName.OP, false);
}
/**
* This will set the overprint control(OP).
*
* @param op The overprint control.
*/
public void setStrokingOverprintControl(boolean op)
{
dict.setBoolean(COSName.OP, op);
}
/**
* This will get the overprint control for non stroking operations. If this value is null then the regular overprint
* control value will be returned.
*
* @return The overprint control or null if one has not been set.
*/
public boolean getNonStrokingOverprintControl()
{
return dict.getBoolean(COSName.OP_NS, getStrokingOverprintControl());
}
/**
* This will set the overprint control(OP).
*
* @param op The overprint control.
*/
public void setNonStrokingOverprintControl(boolean op)
{
dict.setBoolean(COSName.OP_NS, op);
}
/**
* This will get the overprint control mode.
*
* @return The overprint control mode or null if one has not been set.
*/
public Float getOverprintMode()
{
return getFloatItem(COSName.OPM);
}
/**
* This will set the overprint mode(OPM).
*
* @param overprintMode The overprint mode
*/
public void setOverprintMode(Float overprintMode)
{
if (overprintMode == null)
{
dict.removeItem(COSName.OPM);
}
else
{
dict.setInt(COSName.OPM, overprintMode.intValue());
}
}
/**
* This will get the font setting of the graphics state.
*
* @return The font setting.
*/
public PDFontSetting getFontSetting()
{
PDFontSetting setting = null;
COSBase base = dict.getDictionaryObject(COSName.FONT);
if (base instanceof COSArray font)
{
setting = new PDFontSetting(font);
}
return setting;
}
/**
* This will set the font setting for this graphics state.
*
* @param fs The new font setting.
*/
public void setFontSetting(PDFontSetting fs)
{
dict.setItem(COSName.FONT, fs);
}
/**
* This will get the flatness tolerance.
*
* @return The flatness tolerance or null if one has not been set.
*/
public Float getFlatnessTolerance()
{
return getFloatItem(COSName.FL);
}
/**
* This will get the flatness tolerance.
*
* @param flatness The new flatness tolerance
*/
public void setFlatnessTolerance(Float flatness)
{
setFloatItem(COSName.FL, flatness);
}
/**
* This will get the smothness tolerance.
*
* @return The smothness tolerance or null if one has not been set.
*/
public Float getSmoothnessTolerance()
{
return getFloatItem(COSName.SM);
}
/**
* This will get the smoothness tolerance.
*
* @param smoothness The new smoothness tolerance
*/
public void setSmoothnessTolerance(Float smoothness)
{
setFloatItem(COSName.SM, smoothness);
}
/**
* This will get the automatic stroke adjustment flag.
*
* @return The automatic stroke adjustment flag or null if one has not been set.
*/
public boolean getAutomaticStrokeAdjustment()
{
return dict.getBoolean(COSName.SA, false);
}
/**
* This will get the automatic stroke adjustment flag.
*
* @param sa The new automatic stroke adjustment flag.
*/
public void setAutomaticStrokeAdjustment(boolean sa)
{
dict.setBoolean(COSName.SA, sa);
}
/**
* This will get the stroking alpha constant.
*
* @return The stroking alpha constant or null if one has not been set.
*/
public Float getStrokingAlphaConstant()
{
return getFloatItem(COSName.CA);
}
/**
* This will get the stroking alpha constant.
*
* @param alpha The new stroking alpha constant.
*/
public void setStrokingAlphaConstant(Float alpha)
{
setFloatItem(COSName.CA, alpha);
}
/**
* This will get the non stroking alpha constant.
*
* @return The non stroking alpha constant or null if one has not been set.
*/
public Float getNonStrokingAlphaConstant()
{
return getFloatItem(COSName.CA_NS);
}
/**
* This will get the non stroking alpha constant.
*
* @param alpha The new non stroking alpha constant.
*/
public void setNonStrokingAlphaConstant(Float alpha)
{
setFloatItem(COSName.CA_NS, alpha);
}
/**
* This will get the alpha source flag (“alpha is shape”), that specifies whether the current soft mask and alpha
* constant shall be interpreted as shape values (true) or opacity values (false).
*
* @return The alpha source flag.
*/
public boolean getAlphaSourceFlag()
{
return dict.getBoolean(COSName.AIS, false);
}
/**
* This will get the alpha source flag (“alpha is shape”), that specifies whether the current soft mask and alpha
* constant shall be interpreted as shape values (true) or opacity values (false).
*
* @param alpha The alpha source flag.
*/
public void setAlphaSourceFlag(boolean alpha)
{
dict.setBoolean(COSName.AIS, alpha);
}
/**
* Returns the blending mode stored in the COS dictionary
*
* @return the blending mode
*/
public BlendMode getBlendMode()
{
return BlendMode.getInstance(dict.getDictionaryObject(COSName.BM));
}
/**
* Set the blending mode.
*
* @param bm
*/
public void setBlendMode(BlendMode bm)
{
dict.setItem(COSName.BM, BlendMode.getCOSName(bm));
}
/**
* Returns the soft mask stored in the COS dictionaryO
*
* @return the soft mask
*/
public PDSoftMask getSoftMask()
{
if (!dict.containsKey(COSName.SMASK))
{
return null;
}
return PDSoftMask.create(dict.getDictionaryObject(COSName.SMASK));
}
/**
*
* /** This will get the text knockout flag.
*
* @return The text knockout flag.
*/
public boolean getTextKnockoutFlag()
{
return dict.getBoolean(COSName.TK, true);
}
/**
* This will get the text knockout flag.
*
* @param tk The text knockout flag.
*/
public void setTextKnockoutFlag(boolean tk)
{
dict.setBoolean(COSName.TK, tk);
}
/**
* This will get a float item from the dictionary.
*
* @param key The key to the item.
*
* @return The value for that item.
*/
private Float getFloatItem(COSName key)
{
return ofNullable(dict.getDictionaryObject(key, COSNumber.class)).map(COSNumber::floatValue)
.orElse(null);
}
/**
* This will set a float object.
*
* @param key The key to the data that we are setting.
* @param value The value that we are setting.
*/
private void setFloatItem(COSName key, Float value)
{
if (value == null)
{
dict.removeItem(key);
}
else
{
dict.setItem(key, new COSFloat(value));
}
}
/**
* This will get the transfer function of the /TR dictionary.
*
* @return The transfer function. According to the PDF specification, this is either a single function (which
* applies to all process colorants) or an array of four functions (which apply to the process colorants
* individually). The name Identity may be used to represent the identity function.
*/
public COSBase getTransfer()
{
COSBase base = dict.getDictionaryObject(COSName.TR);
if (base instanceof COSArray && ((COSArray) base).size() != 4)
{
return null;
}
return base;
}
/**
* This will set the transfer function of the /TR dictionary.
*
* @param transfer The transfer function. According to the PDF specification, this is either a single function
* (which applies to all process colorants) or an array of four functions (which apply to the process colorants
* individually). The name Identity may be used to represent the identity function.
*/
public void setTransfer(COSBase transfer)
{
dict.setItem(COSName.TR, transfer);
}
/**
* This will get the transfer function of the /TR2 dictionary.
*
* @return The transfer function. According to the PDF specification, this is either a single function (which
* applies to all process colorants) or an array of four functions (which apply to the process colorants
* individually). The name Identity may be used to represent the identity function, and the name Default denotes the
* transfer function that was in effect at the start of the page.
*/
public COSBase getTransfer2()
{
COSBase base = dict.getDictionaryObject(COSName.TR2);
if (base instanceof COSArray && ((COSArray) base).size() != 4)
{
return null;
}
return base;
}
/**
* This will set the transfer function of the /TR2 dictionary.
*
* @param transfer2 The transfer function. According to the PDF specification, this is either a single function
* (which applies to all process colorants) or an array of four functions (which apply to the process colorants
* individually). The name Identity may be used to represent the identity function, and the name Default denotes the
* transfer function that was in effect at the start of the page.
*/
public void setTransfer2(COSBase transfer2)
{
dict.setItem(COSName.TR2, transfer2);
}
}