![JAR search and dependency download from the Maven repository](/logo.png)
org.jpedal.render.BaseDisplay Maven / Gradle / Ivy
/*
* ===========================================
* Java Pdf Extraction Decoding Access Library
* ===========================================
*
* Project Info: http://www.idrsolutions.com
* Help section for developers at http://www.idrsolutions.com/support/
*
* (C) Copyright 1997-2017 IDRsolutions and Contributors.
*
* This file is part of JPedal/JPDF2HTML5
*
@LICENSE@
*
* ---------------
* BaseDisplay.java
* ---------------
*/
package org.jpedal.render;
import com.idrsolutions.pdf.color.blends.BlendMode;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.jpedal.color.PdfColor;
import org.jpedal.color.PdfPaint;
import org.jpedal.exception.PdfException;
import org.jpedal.fonts.PdfFont;
import org.jpedal.fonts.glyph.PdfGlyph;
import org.jpedal.io.ObjectStore;
import org.jpedal.objects.GraphicsState;
import org.jpedal.objects.PdfShape;
import org.jpedal.objects.raw.PdfDictionary;
import org.jpedal.parser.DecoderOptions;
import org.jpedal.render.utils.ShapeUtils;
import org.jpedal.utils.LogWriter;
import org.jpedal.utils.repositories.Vector_Int;
import org.jpedal.utils.repositories.Vector_Object;
import org.jpedal.utils.repositories.generic.Vector_Rectangle_Int;
public abstract class BaseDisplay implements DynamicVectorRenderer {
/**
* holds object type
*/
protected Vector_Int objectType;
/**
* default array size
*/
protected static final int defaultSize = 5000;
protected int type;
boolean isType3Font;
public int currentTokenNumber = -1;
/**
* set flag to show if we add a background
*/
protected boolean addBackground = true;
/**
* holds rectangular outline to test in redraw
*/
protected Vector_Rectangle_Int areas;
protected ObjectStore objectStoreRef;
protected int currentItem = -1;
//Used purely to keep track of rendering for colour change functionality
//must NEVER be made static
protected int itemToRender = -1;
//used to track end of PDF page in display
//must NEVER be made static
protected int endItem = -1;
Area lastClip;
boolean hasClips;
int blendMode = PdfDictionary.Normal;
/**
* shows if colours over-ridden for type3 font
*/
boolean colorsLocked;
Graphics2D g2;
//used by type3 fonts as identifier
String rawKey;
/**
* global colours if set
*/
PdfPaint fillCol, strokeCol;
public int rawPageNumber;
public static boolean invertHighlight;
boolean isPrinting;
org.jpedal.external.ImageHandler customImageHandler;
org.jpedal.external.ColorHandler customColorHandler;
double cropX, cropH;
float scaling = 1, lastScaling;
/**
* initial Q & D object to hold data
*/
protected Vector_Object pageObjects;
protected final Map imageIDtoName = new HashMap(10);
/**
* real size of pdf page
*/
int w, h;
/**
* background color
*/
protected Color backgroundColor = Color.WHITE;
protected static Color textColor;
protected static int colorThresholdToReplace = 255;
protected static boolean enhanceFractionalLines = true;
protected boolean changeLineArtAndText;
/**
* allow user to control
*/
public static RenderingHints userHints;
@Override
public void setG2(final Graphics2D g2) {
this.g2 = g2;
//If user hints has been defined use these values.
if (userHints != null) {
this.g2.setRenderingHints(userHints);
}
}
@Override
public void init(final int width, final int height, final Color backgroundColor) {
w = width;
h = height;
this.backgroundColor = backgroundColor;
}
protected void paintBackground(final Shape dirtyRegion) {
if (addBackground && g2 != null) {
g2.setColor(backgroundColor);
if (dirtyRegion == null) {
g2.fill(new Rectangle(0, 0, (int) (w * scaling), (int) (h * scaling)));
} else {
g2.fill(dirtyRegion);
}
}
}
protected static boolean checkColorThreshold(final int col) {
final int r = (col) & 0xFF;
final int g = (col >> 8) & 0xFF;
final int b = (col >> 16) & 0xFF;
return r <= colorThresholdToReplace && g <= colorThresholdToReplace && b <= colorThresholdToReplace;
}
void renderEmbeddedText(final int text_fill_type, final Object rawglyph, final int glyphType,
final AffineTransform glyphAT, final Rectangle textHighlight,
PdfPaint strokePaint, PdfPaint fillPaint,
final float strokeOpacity, final float fillOpacity, final int lineWidth) {
//ensure stroke only shows up
float strokeOnlyLine = 0;
if (text_fill_type == GraphicsState.STROKE && lineWidth >= 1.0) {
strokeOnlyLine = lineWidth;
}
//get glyph to draw
final PdfGlyph glyph = (PdfGlyph) rawglyph;
final AffineTransform at = g2.getTransform();
//and also as flat values so we can test below
final double[] affValues = new double[6];
at.getMatrix(affValues);
if (glyph != null) {
//set transform
g2.transform(glyphAT);
//type of draw operation to use
final Composite comp = g2.getComposite();
if ((text_fill_type & GraphicsState.FILL) == GraphicsState.FILL) {
//If we have an alt text color, its within threshold and not an additional item, use alt color
if (textColor != null && (itemToRender == -1 || (endItem == -1 || itemToRender <= endItem)) && checkColorThreshold(fillPaint.getRGB())) {
fillPaint = new PdfColor(textColor.getRed(), textColor.getGreen(), textColor.getBlue());
}
// fillPaint.setScaling(cropX, cropH, scaling, 0, 0);
fillPaint.setScaling(cropX, cropH, scaling, (float) glyphAT.getTranslateX(), (float) glyphAT.getTranslateY());
if (customColorHandler != null) {
customColorHandler.setPaint(g2, fillPaint, rawPageNumber, isPrinting);
} else if (DecoderOptions.Helper != null) {
DecoderOptions.Helper.setPaint(g2, fillPaint, rawPageNumber, isPrinting);
} else {
g2.setPaint(fillPaint);
}
renderComposite(fillOpacity);
if (textHighlight != null) {
if (invertHighlight) {
final Color color = g2.getColor();
g2.setColor(new Color(255 - color.getRed(), 255 - color.getGreen(), 255 - color.getBlue()));
} else if (DecoderOptions.backgroundColor != null) {
g2.setColor(DecoderOptions.backgroundColor);
}
}
//pass down color for drawing text
if (glyphType == DynamicVectorRenderer.TYPE3 && !glyph.ignoreColors()) {
glyph.setT3Colors(strokePaint, fillPaint, false);
}
glyph.render(GraphicsState.FILL, g2, scaling, false);
//reset opacity
g2.setComposite(comp);
}
if (text_fill_type == GraphicsState.STROKE) {
glyph.setStrokedOnly(true);
}
//creates shadow printing to Mac so added work around
if (DecoderOptions.isRunningOnMac && isPrinting && text_fill_type == GraphicsState.FILLSTROKE) {
} else if ((text_fill_type & GraphicsState.STROKE) == GraphicsState.STROKE) {
if (strokePaint != null) {
//If we have an alt text color, its within threshold and not an additional item, use alt color
if (textColor != null && (itemToRender == -1 || (endItem == -1 || itemToRender <= endItem)) && checkColorThreshold(strokePaint.getRGB())) {
strokePaint = new PdfColor(textColor.getRed(), textColor.getGreen(), textColor.getBlue());
}
strokePaint.setScaling(cropX, cropH, scaling, 0, 0);
}
if (customColorHandler != null) {
customColorHandler.setPaint(g2, strokePaint, rawPageNumber, isPrinting);
} else if (DecoderOptions.Helper != null) {
DecoderOptions.Helper.setPaint(g2, strokePaint, rawPageNumber, isPrinting);
} else {
g2.setPaint(strokePaint);
}
renderComposite(strokeOpacity);
if (textHighlight != null) {
if (invertHighlight) {
final Color color = g2.getColor();
g2.setColor(new Color(255 - color.getRed(), 255 - color.getGreen(), 255 - color.getBlue()));
} else if (DecoderOptions.backgroundColor != null) {
g2.setColor(DecoderOptions.backgroundColor);
}
}
try {
glyph.render(GraphicsState.STROKE, g2, strokeOnlyLine, false);
} catch (final Exception e) {
LogWriter.writeLog("Exception: " + e.getMessage());
}
//reset opacity
g2.setComposite(comp);
}
//restore transform
g2.setTransform(at);
}
}
void renderShape(final Shape defaultClip, final int fillType, PdfPaint strokeCol, PdfPaint fillCol,
final Stroke shapeStroke, Shape currentShape, final float strokeOpacity,
final float fillOpacity) {
boolean clipChanged = false;
final Shape clip = g2.getClip();
final Composite comp = g2.getComposite();
//check for 1 x 1 complex shape (after scaling) and replace with dot
if (type == DISPLAY_SCREEN && !isPrinting && currentShape.getBounds().getWidth() * scaling == 1 &&
currentShape.getBounds().getHeight() * scaling == 1 && ((BasicStroke) shapeStroke).getLineWidth() * scaling < 1) {
currentShape = new Rectangle(currentShape.getBounds().x, currentShape.getBounds().y, 1, 1);
}
//stroke and fill (do fill first so we don't overwrite Stroke)
if (fillType == GraphicsState.FILL || fillType == GraphicsState.FILLSTROKE) {
// Fill color is null if the shape is a pattern
if (fillCol != null) {
if ((fillCol.getRGB() != -1) &&
//If we have an alt text color, are changing line art as well, its within threshold and not an additional item, use alt color
(changeLineArtAndText && textColor != null && !fillCol.isPattern() && (itemToRender == -1 || (endItem == -1 || itemToRender <= endItem)) && checkColorThreshold(fillCol.getRGB()))) {
fillCol = new PdfColor(textColor.getRed(), textColor.getGreen(), textColor.getBlue());
}
fillCol.setScaling(cropX, cropH, scaling, 0, 0);
}
if (customColorHandler != null) {
customColorHandler.setPaint(g2, fillCol, rawPageNumber, isPrinting);
} else if (DecoderOptions.Helper != null) {
DecoderOptions.Helper.setPaint(g2, fillCol, rawPageNumber, isPrinting);
} else {
g2.setPaint(fillCol);
}
renderComposite(fillOpacity);
try {
//thin lines do not appear unless we use fillRect
final double iw = currentShape.getBounds2D().getWidth();
final double ih = currentShape.getBounds2D().getHeight();
if ((ih == 0d || iw == 0d) && ((BasicStroke) g2.getStroke()).getLineWidth() <= 1.0f) {
g2.fillRect(currentShape.getBounds().x, currentShape.getBounds().y, currentShape.getBounds().width, currentShape.getBounds().height);
} else {
g2.fill(currentShape);
}
} catch (final Exception e) {
LogWriter.writeLog("Exception " + e + " filling shape");
}
g2.setComposite(comp);
}
if ((fillType == GraphicsState.STROKE) || (fillType == GraphicsState.FILLSTROKE)) {
//set values for drawing the shape
final Stroke currentStroke = g2.getStroke();
//fix for using large width on point to draw line
if (currentShape.getBounds2D().getWidth() < 1.0f && ((BasicStroke) shapeStroke).getLineWidth() > 10) {
g2.setStroke(new BasicStroke(1));
} else {
//Adjust line width to 1 if less than 1
//ignore if using T3Display (such as ap image generation in html / svg conversion
if (enhanceFractionalLines && ((((BasicStroke) shapeStroke).getLineWidth() * scaling < 1) &&
!isPrinting && !(this instanceof T3Display))) {
g2.setStroke(new BasicStroke(1 / scaling, ((BasicStroke) shapeStroke).getEndCap(), ((BasicStroke) shapeStroke).getLineJoin(), ((BasicStroke) shapeStroke).getMiterLimit(), ((BasicStroke) shapeStroke).getDashArray(), ((BasicStroke) shapeStroke).getDashPhase()));
} else {
g2.setStroke(shapeStroke);
}
}
//If we have an alt text color, are changing line art, its within threshold and not an additional item, use alt color
if (changeLineArtAndText && textColor != null && !strokeCol.isPattern() && (itemToRender == -1 || (endItem == -1 || itemToRender <= endItem)) && checkColorThreshold(strokeCol.getRGB())) {
strokeCol = new PdfColor(textColor.getRed(), textColor.getGreen(), textColor.getBlue());
}
strokeCol.setScaling(cropX, cropH, scaling, 0, 0);
if (customColorHandler != null) {
customColorHandler.setPaint(g2, strokeCol, rawPageNumber, isPrinting);
} else if (DecoderOptions.Helper != null) {
DecoderOptions.Helper.setPaint(g2, strokeCol, rawPageNumber, isPrinting);
} else {
g2.setPaint(strokeCol);
}
renderComposite(strokeOpacity);
if (!isPrinting && clip != null && clip.getBounds2D().getWidth() % 1 > 0.65f && clip.getBounds2D().getHeight() % 1 > 0.1f) {
if (currentShape.getBounds().getWidth() == clip.getBounds().getWidth()) {
g2.setClip(ClipUtils.convertPDFClipToJavaClip(new Area(clip))); //use null or visible screen area
clipChanged = true;
}
}
//breaks printing so disabled there
if (!isPrinting && clip != null) {
final Double height = clip.getBounds2D().getHeight();
final Double width = clip.getBounds2D().getWidth();
if ((height < 1 && height > 0) || (width < 1 && width > 0)) {
g2.setClip(defaultClip); //use null or visible screen area
clipChanged = true;
}
}
g2.draw(currentShape);
g2.setStroke(currentStroke);
g2.setComposite(comp);
}
if (clipChanged) {
g2.setClip(clip);
}
}
void renderImage(final AffineTransform imageAf, BufferedImage image, final float alpha,
final GraphicsState currentGraphicsState, final float x, final float y) {
final boolean renderDirect = (currentGraphicsState != null);
if (image == null || g2 == null) {
return;
}
final AffineTransform before = g2.getTransform();
final Composite c = g2.getComposite();
renderComposite(alpha);
final AffineTransform upside_down;
float CTM[][] = new float[3][3];
if (currentGraphicsState != null) {
CTM = currentGraphicsState.CTM;
} else {
final double[] values = new double[6];
imageAf.getMatrix(values);
CTM[0][0] = (float) values[0];
CTM[0][1] = (float) values[1];
CTM[1][0] = (float) values[2];
CTM[1][1] = (float) values[3];
CTM[2][0] = x;
CTM[2][1] = y;
}
final int w = image.getWidth();
final int h = image.getHeight();
final double[] values = {CTM[0][0] / w, CTM[0][1] / w, -CTM[1][0] / h, -CTM[1][1] / h, 0, 0};
upside_down = new AffineTransform(values);
g2.translate(CTM[2][0] + CTM[1][0], CTM[2][1] + CTM[1][1]);
//allow user to over-ride
boolean useCustomRenderer = customImageHandler != null;
if (useCustomRenderer) {
useCustomRenderer = customImageHandler.drawImageOnscreen(image, 0, upside_down, null, g2, renderDirect, objectStoreRef, isPrinting);
//exit if done
if (useCustomRenderer) {
g2.setComposite(c);
return;
}
}
//hack to make bw
if (customColorHandler != null) {
final BufferedImage newImage = customColorHandler.processImage(image, rawPageNumber, isPrinting);
if (newImage != null) {
image = newImage;
}
} else if (DecoderOptions.Helper != null) {
final BufferedImage newImage = DecoderOptions.Helper.processImage(image, rawPageNumber, isPrinting);
if (newImage != null) {
image = newImage;
}
}
final Shape g2clip = g2.getClip();
boolean isClipReset = false;
//hack to fix clipping issues due to sub-pixels
if (g2clip != null) {
final double cy = g2.getClip().getBounds2D().getY();
final double ch = g2.getClip().getBounds2D().getHeight();
double diff = image.getHeight() - ch;
if (diff < 0) {
diff = -diff;
}
if (diff > 0 && diff < 1 && cy < 0 && image.getHeight() > 1 && image.getHeight() < 10) {
final boolean isSimpleOutline = ShapeUtils.isSimpleOutline(g2.getClip());
if (isSimpleOutline) {
final double cx = g2.getClip().getBounds2D().getX();
final double cw = g2.getClip().getBounds2D().getWidth();
g2.setClip(new Rectangle((int) cx, (int) cy, (int) cw, (int) ch));
isClipReset = false;
}
}
}
//Draw image as normal
g2.drawImage(image, upside_down, null);
if (isClipReset) {
g2.setClip(g2clip);
}
g2.setTransform(before);
g2.setComposite(c);
}
@Override
public void setScalingValues(final double cropX, final double cropH, final float scaling) {
this.cropX = cropX;
this.cropH = cropH;
this.scaling = scaling;
}
/**
* turn object into byte[] so we can move across
* this way should be much faster than the stadard Java serialise.
*
* NOT PART OF API and subject to change (DO NOT USE)
*
* @throws java.io.IOException
*/
@Override
public byte[] serializeToByteArray(final Set fontsAlreadyOnClient) throws IOException {
return new byte[0];
}
@Override
public void drawShape(final PdfShape pdfShape, final GraphicsState currentGraphicsState, final int cmd) {
// throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public void drawEmbeddedText(final float[][] Trm, final int fontSize, final PdfGlyph embeddedGlyph, final Object javaGlyph, final int type, final GraphicsState gs, final double[] at, final String glyf, final PdfFont currentFontData, final float glyfWidth) {
//throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public void paint(final Rectangle[] highlights, final AffineTransform viewScaling, final Rectangle userAnnot) {
// throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public int drawImage(final int pageNumber, final BufferedImage image, final GraphicsState currentGraphicsState, final boolean alreadyCached, final String name, final int previousUse) {
return -1;
}
@Override
public void drawAdditionalObjectsOverPage(final int[] type, final Color[] colors, final Object[] obj) throws PdfException {
}
@Override
public void drawText(final float[][] Trm, final String text, final GraphicsState currentGraphicsState, final float x, final float y, final Font javaFont) {
//throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public void setGraphicsState(final int fillType, final float value, final int BM) {
//throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public void drawTR(final int value) {
//throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public void drawClip(final GraphicsState currentGraphicsState, final Shape defaultClip, final boolean alwaysDraw) {
// throw new UnsupportedOperationException("Not supported yet.");
}
/**
* used by some custom version of DynamicVectorRenderer
*/
@Override
public void writeCustom(final int key, final Object value) {
switch (key) {
case CUSTOM_IMAGE_HANDLER:
this.customImageHandler = (org.jpedal.external.ImageHandler) value;
break;
case CUSTOM_COLOR_HANDLER:
this.customColorHandler = (org.jpedal.external.ColorHandler) value;
break;
case PAINT_BACKGROUND:
paintBackground((Shape) value);
break;
}
}
@Override
public void setValue(final int key, final int i) {
switch (key) {
case ALT_BACKGROUND_COLOR:
backgroundColor = new Color(i);
break;
case ALT_FOREGROUND_COLOR:
textColor = new Color(i);
break;
case FOREGROUND_INCLUDE_LINEART:
changeLineArtAndText = i > 0;
break;
case COLOR_REPLACEMENT_THRESHOLD:
colorThresholdToReplace = i;
break;
case ENHANCE_FRACTIONAL_LINES:
enhanceFractionalLines = i != 0;
break;
case TOKEN_NUMBER:
this.currentTokenNumber = i;
break;
}
}
@Override
public int getValue(final int key) {
switch (key) {
case TOKEN_NUMBER:
return currentTokenNumber;
//used by HTML to get font handing mode, etc
//this is the unused 'dummy' default implementation required for other modes as in Interface
default:
return -1;
}
}
/**
* allow user to read
*/
@Override
public boolean getBooleanValue(final int key) {
return false;
}
/**
* only used in HTML5 and SVG conversion
*
* @param fontObjID
* @param s
* @param potentialWidth
*/
@Override
public void saveAdvanceWidth(final int fontObjID, final String s, final int potentialWidth) {
}
/*save shape in array to draw*/
@Override
public void drawShape(final Object currentShape, final GraphicsState currentGraphicsState) {
System.out.println("drawShape in BaseDisplay Should never be called");
}
/*save shape in array to draw*/
@Override
public void eliminateHiddenText(final Shape currentShape, final GraphicsState gs, final int count, final boolean ignoreScaling) {
}
protected void renderComposite(final float alpha) {
if (blendMode == PdfDictionary.Normal || blendMode == PdfDictionary.Compatible) {
if (alpha != 1.0f) {
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));
}
} else { /// if (alpha != 1.0f){ - possible fix for 19888 to test
final Composite comp = new BlendMode(blendMode, alpha);
g2.setComposite(comp);
}
}
@Override
public boolean isHTMLorSVG() {
return false;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy