/*
* Copyright 2006-2017 ICEsoft Technologies Canada Corp.
*
* Licensed 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.icepdf.core.pobjects.graphics.commands;
import org.icepdf.core.pobjects.Form;
import org.icepdf.core.pobjects.ImageUtility;
import org.icepdf.core.pobjects.Name;
import org.icepdf.core.pobjects.Page;
import org.icepdf.core.pobjects.graphics.*;
import org.icepdf.core.util.Defs;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.util.HashMap;
/**
* The FormDrawCmd when executed will draw an xForm's shapes to a raster and
* then paint the raster. This procedure is only executed if the xForm
* is part of transparency group that has a alpha value < 1.0f.
*
* @since 5.0
*/
public class FormDrawCmd extends AbstractDrawCmd {
private Form xForm;
private BufferedImage xFormBuffer;
private int x, y;
private static boolean disableXObjectSMask;
// Used to use Max_value but we have a few corner cases where the dimension is +-5 of Short.MAX_VALUE, but
// realistically we seldom have enough memory to load anything bigger then 8000px. 4k+ image are big!
public static int MAX_IMAGE_SIZE = 2000; // Short.MAX_VALUE
static {
// decide if large images will be scaled
disableXObjectSMask =
Defs.sysPropertyBoolean("org.icepdf.core.disableXObjectSMask",
false);
MAX_IMAGE_SIZE = Defs.sysPropertyInt("org.icepdf.core.maxSmaskImageSize", MAX_IMAGE_SIZE);
}
public FormDrawCmd(Form xForm) {
this.xForm = xForm;
}
@Override
public Shape paintOperand(Graphics2D g, Page parentPage, Shape currentShape,
Shape clip, AffineTransform base,
OptionalContentState optionalContentState,
boolean paintAlpha, PaintTimer paintTimer) {
if (optionalContentState.isVisible() && xFormBuffer == null) {
RenderingHints renderingHints = g.getRenderingHints();
Rectangle2D bBox = xForm.getBBox();
x = (int) bBox.getX();
y = (int) bBox.getY();
boolean hasMask = ((xForm.getGraphicsState().getExtGState() != null &&
xForm.getGraphicsState().getExtGState().getSMask() != null) ||
(xForm.getExtGState() != null && xForm.getExtGState().getSMask() != null));
boolean isExtendGraphicState = xForm.getGraphicsState().getExtGState() != null &&
xForm.getExtGState() != null;
boolean normalBM = false;
if (isExtendGraphicState && xForm.getExtGState().getBlendingMode() != null) {
normalBM = xForm.getExtGState().getBlendingMode().equals(new Name("Normal")) &&
xForm.getGraphicsState().getExtGState().getBlendingMode().equals(new Name("Normal")) &&
(xForm.getExtGState() != null &&
(!xForm.getExtGState().isAlphaAShape() || xForm.getExtGState().getOverprintMode() == 0));
}
SoftMask formSoftMask = null;
SoftMask softMask = null;
if (xForm.getGraphicsState().getExtGState().getSMask() != null) {
softMask = xForm.getGraphicsState().getExtGState().getSMask();
boolean isShading = softMask.getG().getResources().isShading();
if (isShading) {
isShading = checkForShaddingFill(softMask.getG());
softMask.getG().setShading(isShading);
}
if (!isShading) {
x = (int) softMask.getG().getBBox().getX();
y = (int) softMask.getG().getBBox().getY();
}
}
if (xForm.getExtGState().getSMask() != null) {
formSoftMask = xForm.getExtGState().getSMask();
boolean isShading = formSoftMask.getG().getResources().isShading();
if (isShading) {
isShading = checkForShaddingFill(formSoftMask.getG());
formSoftMask.getG().setShading(isShading);
}
if (!isShading) {
x = (int) formSoftMask.getG().getBBox().getX();
y = (int) formSoftMask.getG().getBBox().getY();
}
}
// check if we have the same xobject.
if (softMask != null && formSoftMask != null) {
if (softMask.getPObjectReference() != null && formSoftMask.getPObjectReference() != null &&
softMask.getPObjectReference().equals(formSoftMask.getPObjectReference())) {
softMask = null;
} else if (softMask.getG().getPObjectReference() != null &&
formSoftMask.getG().getPObjectReference() != null &&
softMask.getG().getPObjectReference().equals(formSoftMask.getG().getPObjectReference())) {
softMask = null;
}
}
// need to check if we really have a shading pattern, as the resources check can be false positive.
if (xForm.getResources().isShading()) {
boolean isFormShading = checkForShaddingFill(xForm);
xForm.setShading(isFormShading);
}
// create the form and we'll paint it at the very least
xFormBuffer = createBufferXObject(parentPage, xForm, null, renderingHints, normalBM);
if (!disableXObjectSMask && hasMask) {
// apply the mask and paint.
if (!xForm.isShading()) {
if (softMask != null && softMask.getS().equals(SoftMask.SOFT_MASK_TYPE_ALPHA)) {
logger.warning("Smask alpha example, currently not supported.");
} else if (softMask != null && softMask.getS().equals(SoftMask.SOFT_MASK_TYPE_LUMINOSITY)) {
xFormBuffer = applyMask(parentPage, xFormBuffer, softMask, formSoftMask, g.getRenderingHints());
}
} else if (softMask != null) {
// still not property aligning the form or mask space to correctly apply a shading pattern.
// experimental as it fixes some, breaks others, but regardless we don't support it well.
logger.warning("Smask pattern paint example, currently not supported.");
xFormBuffer.flush();
xFormBuffer = createBufferXObject(parentPage, softMask.getG(), null, renderingHints, true);
return currentShape;
}
// apply the form mask to current form content that has been rasterized to xFormBuffer
if (formSoftMask != null) {
BufferedImage formSMaskBuffer = applyMask(parentPage, xFormBuffer, formSoftMask, softMask,
g.getRenderingHints());
// compost all the images.
if (softMask != null) {
BufferedImage formBuffer = new ImageUtility().createTranslucentCompatibleImage(
xFormBuffer.getWidth(), xFormBuffer.getHeight());
Graphics2D g2d = (Graphics2D) formBuffer.getGraphics();
// java.util.List<Number> compRaw = formSoftMask.getBC();
// if (compRaw != null) {
// g2d.setColor(Color.BLACK);
// g2d.fillRect(0, 0, xFormBuffer.getWidth(), xFormBuffer.getHeight());
// }
g2d.drawImage(formSMaskBuffer, 0, 0, null);
// g2d.drawImage(xFormBuffer, 0, 0, null);
xFormBuffer.flush();
xFormBuffer = formBuffer;
} else {
xFormBuffer = formSMaskBuffer;
}
}
} else if (isExtendGraphicState) {
BufferedImage shape = createBufferXObject(parentPage, xForm, null, renderingHints, true);
xFormBuffer = new ImageUtility().applyExplicitOutline(xFormBuffer, shape);
}
// ImageUtility.displayImage(xFormBuffer, "final" + xForm.getGroup() + " " + xForm.getPObjectReference() +
// xFormBuffer.getHeight() + "x" + xFormBuffer.getHeight());
}
g.drawImage(xFormBuffer, null, x, y);
return currentShape;
}
private BufferedImage applyMask(Page parentPage, BufferedImage xFormBuffer, SoftMask softMask, SoftMask gsSoftMask,
RenderingHints renderingHints) {
if (softMask != null && softMask.getS().equals(SoftMask.SOFT_MASK_TYPE_ALPHA)) {
logger.warning("Smask alpha example, currently not supported.");
} else if (softMask != null && softMask.getS().equals(SoftMask.SOFT_MASK_TYPE_LUMINOSITY)) {
BufferedImage sMaskBuffer = createBufferXObject(parentPage, softMask.getG(), softMask, renderingHints, true);
// ImageUtility.displayImage(xFormBuffer, "base " + xForm.getPObjectReference() + " " + xFormBuffer.getHeight() + " x " + xFormBuffer.getHeight());
// ImageUtility.displayImage(sMaskBuffer, "smask " + softMask.getG().getPObjectReference() + " " + useLuminosity);
if (!(gsSoftMask != null)) {
xFormBuffer = new ImageUtility().applyExplicitSMask(xFormBuffer, sMaskBuffer);
} else {
// todo try and figure out how to apply an AIS=false alpha to an xobject.
// xFormBuffer = ImageUtility.applyExplicitLuminosity(xFormBuffer, sMaskBuffer);
xFormBuffer = new ImageUtility().applyExplicitOutline(xFormBuffer, sMaskBuffer);
}
// test for TR function
if (softMask.getTR() != null) {
logger.warning("Smask Transfer Function example, currently not supported.");
}
// todo need to look at matte too which is on the xobject.
}
// ImageUtility.displayImage(xFormBuffer, "final " + softMask.getG().getPObjectReference());
return xFormBuffer;
}
/**
* Paint the form content to a BufferedImage so that the forms content can be
* used to apply the sMask data. Further work is needed to fully support this
* section of transparency groups.
*
* @param parentPage parent page object
* @param xForm form being drawn to buffer.
* @param renderingHints graphic state rendering hinds of parent.
* @return buffered image of xObject content.
*/
private BufferedImage createBufferXObject(Page parentPage, Form xForm, SoftMask softMask,
RenderingHints renderingHints, boolean isMask) {
Rectangle2D bBox = xForm.getBBox();
int width = (int) bBox.getWidth();
int height = (int) bBox.getHeight();
// corner cases where some bBoxes don't have a dimension.
if (width == 0) {
width = 1;
} else if (width >= MAX_IMAGE_SIZE) {
width = xFormBuffer.getWidth();
}
if (height == 0) {
height = 1;
} else if (height >= MAX_IMAGE_SIZE) {
height = xFormBuffer.getHeight();
}
// create the new image to write too.
BufferedImage bi = ImageUtility.createTranslucentCompatibleImage(width, height);
Graphics2D canvas = bi.createGraphics();
if (!isMask && xForm.getExtGState() != null && xForm.getExtGState().getBlendingMode() != null
&& !new Name("Normal").equals(xForm.getExtGState().getBlendingMode())
) {
if (xForm.getGroup() != null) {
HashMap tmp = xForm.getGroup();
Object cs = xForm.getLibrary().getObject(tmp, new Name("CS"));
// looking for additive colour spaces, if so we paint an background.
if (cs == null || cs instanceof ICCBased || cs instanceof Name &&
(((Name) cs).equals(DeviceRGB.DEVICERGB_KEY)
|| ((Name) cs).equals(DeviceCMYK.DEVICECMYK_KEY))) {
canvas.setColor(Color.WHITE);
canvas.fillRect(0, 0, width, height);
}
}
}
// copy over the rendering hints
canvas.setRenderingHints(renderingHints);
// get shapes and paint them.
try {
Shapes xFormShapes = xForm.getShapes();
if (xFormShapes != null) {
xFormShapes.setPageParent(parentPage);
// translate the coordinate system as we'll paint the g
// graphic at the correctly location later.
if (!xForm.isShading()) {
canvas.translate(-(int) bBox.getX(), -(int) bBox.getY());
canvas.setClip(bBox);
xFormShapes.paint(canvas);
xFormShapes.setPageParent(null);
}
// basic support for gradient fills, still have a few corners cases to work on.
else {
for (DrawCmd cmd : xFormShapes.getShapes()) {
if (cmd instanceof ShapeDrawCmd && ((ShapeDrawCmd) cmd).getShape() == null) {
Rectangle2D bounds = bBox.getBounds2D();
((ShapeDrawCmd) cmd).setShape(bounds);
}
}
canvas.translate(-x, -y);
canvas.setClip(bBox.getBounds2D());
xFormShapes.paint(canvas);
xFormShapes.setPageParent(null);
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
logger.fine("Form draw thread interrupted.");
}
canvas.dispose();
return bi;
}
private boolean checkForShaddingFill(Form xform) {
boolean found = false;
for (DrawCmd cmd : xform.getShapes().getShapes()) {
if (cmd instanceof ShapeDrawCmd && ((ShapeDrawCmd) cmd).getShape() == null) {
found = true;
}
}
return found;
}
}