/**
* ===========================================
* Java Pdf Extraction Decoding Access Library
* ===========================================
*
* Project Info: http://www.jpedal.org
*
* (C) Copyright 2007, IDRsolutions and Contributors.
*
* This file is part of JPedal
*
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* ---------------
* OutputDisplay.java
* ---------------
* (C) Copyright 2007, by IDRsolutions and Contributors.
*
*
* --------------------------
*/
package org.jpedal.render.output;
import org.jpedal.color.ColorSpaces;
import org.jpedal.fonts.PdfFont;
import org.jpedal.fonts.StandardFonts;
import org.jpedal.fonts.glyph.PdfGlyph;
import org.jpedal.io.ObjectStore;
import org.jpedal.objects.GraphicsState;
import org.jpedal.objects.PdfPageData;
import org.jpedal.objects.TextState;
import org.jpedal.parser.Cmd;
import org.jpedal.render.BaseDisplay;
import org.jpedal.render.DynamicVectorRenderer;
import org.jpedal.utils.Matrix;
import org.jpedal.utils.repositories.Vector_Rectangle;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.HashMap;
import java.util.Map;
public class OutputDisplay extends BaseDisplay{
protected int[] pageoffsets;
//offset in single page mode
protected int newTotalHeight = 0;
protected int pageGap=50;
String lastGlyf="";
final public static int TEXT_AS_TEXT =-1;
final public static int TEXT_ON_CANVAS =1;
protected String clip=null;
//provides common static functions
static protected org.jpedal.render.output.OutputHelper Helper=null;
FontMapper fontMapper=null;
String lastFontUsed="";
protected boolean includeClip=false;
protected int rasterizeText=-1;
protected Map embeddedFontsByFontID=new HashMap();
/**track if JS for glyf already inserted*/
private Map glyfsRasterized=new HashMap();
final public static int MaxNumberOfDecimalPlaces=0;
final public static int FontMode=1;
final public static int PercentageScaling=2;
//final public static int SpacingPercentage=3;
final public static int IncludeJSFontResizingCode=4;
final public static int ExcludeMetadata=6;
final public static int EmbedImageAsBase64Stream =7;
final public static int AddNavBar =8;
final public static int SingleFileOutput =9;
final public static int UseCharSpacing =10;
final public static int UseWordSpacing =11;
final public static int UseFontResizing =12;
final public static int HasJavascript =13;
final public static int ConvertSpacesTonbsp =14;
final public static int EncloseContentInDiv =15;
final public static int IncludeClip =16;
final public static int UseImagesOnNavBar =17;
final public static int RasterizeText =18;
final public static int CustomSinglePageHeader = 19;
final public static int CustomSingleFileName = 20;
//hold a default value set via JVM flag for font mapping
protected static int defaultMode= FontMapper.DEFAULT_ON_UNMAPPED;
protected int fontMode=FontMapper.DEFAULT_ON_UNMAPPED;
//only output to 1 page
protected boolean isSingleFileOutput =false;
//give user option to embed image inside HTML5
protected boolean embedImageAsBase64=false;
private boolean groupGlyphsInTJ=true;
//allow us to position every glyf on its own and not try to merge into strings*/
protected boolean writeEveryGlyf=false;
//control whether CSS inlined in HTML file or in own css file
public boolean inlineCSS=true;
/** debug flags */
static final public boolean debugForms = false;
final private static boolean DISABLE_IMAGES = false;
final private static boolean DISABLE_SHAPE = false;
final private static boolean DISABLE_TEXT = false;
protected final static boolean DEBUG_TEXT_AREA = false;
protected final static boolean DEBUG_DRAW_PAGE_BORDER = false;
public final static int TOFILE = 0;
public final static int TOP_SECTION = 1;
public final static int SCRIPT = 2;
public final static int FORM = 3;
public final static int CSS = 4;
public final static int IMAGE = 5;
public final static int TEXT = 6;
public final static int KEEP_GLYFS_SEPARATE = 7;
public final static int SET_ENCODING_USED = 8;
public final static int JSIMAGESPECIAL = 9;
public final static int SAVE_EMBEDDED_FONT = 10;
public final static int PAGEDATA = 11;
public final static int IMAGE_CONTROLLER = 12;
public final static int JAVAFXMLMETHODS = 13;
public final static int FONT_AS_SHAPE = 14;
protected OutputImageController imageController=null;
protected BufferedWriter output =null;
protected StringBuffer script = new StringBuffer(10000);
protected StringBuffer fonts_as_shapes = new StringBuffer(10000);
protected StringBuffer form = new StringBuffer(10000);
protected StringBuffer testDivs = new StringBuffer(10000);
protected StringBuffer images = new StringBuffer(10000);
protected StringBuffer css = new StringBuffer(10000);
protected StringBuffer topSection = new StringBuffer(10000);
protected StringBuffer javaFXMLMethods = new StringBuffer(10000);
/**allow user to control scaling of images*/
protected boolean userControlledImageScaling=false;
/**current text element number if using Divs. Used as link to CSS*/
protected int textID=1;
protected int shadeId = 0;
/**number of decimal places on numbers*/
protected int dpCount=0;
public String rootDir=null, fileName=null;
protected int dx;
protected int dy;
protected int dr;
protected boolean excludeMetadata=false;
private boolean convertSpacesTonbsp=false;
/**include a user nav tollbar in output*/
protected boolean addNavBar=false,useImagesOnNavBar=false;
/** Controls blocks of text so they can be joined up*/
protected TextBlock currentTextBlock;
protected TextBlock previousTextBlock;
protected Rectangle cropBox;
protected Point2D midPoint;
/** Used to reduce canvas color javascript calls between text and shapes*/
protected int currentColor = 0;
/**control for encodings Java/CSS*/
protected String[] encodingType=new String[]{"UTF-8","utf-8"};
protected static final int JAVA_TYPE=0;
protected static final int OUTPUT_TYPE =1;
public static final int FORM_TAG = 0;
protected float scaling = 1.0f; //Scale to large and images may be lost due to lack of memory.
//amount of font change needed
protected int fontChangeNeeded=-1;
//protected float spacingNeeded=1.0f; //amount of space needed
float w;
protected float h; //override int super.w and super.h for better accuracy. @Shit
protected String[] tag={"<form>"};
//flag to say if JS has been added to allow images to work for checkboxes and radio buttons.
protected boolean jsImagesAdded = false;
protected String pageNumberAsString=null;
protected PdfPageData pageData;
private int currentTokenNumber=-1,lastTokenNumber=496; //496 is impossible value
protected boolean includeJSFontResizingCode =true;
protected static final boolean debugSigg = false;
protected int fontSize;
protected float[][] Trm;
protected float[][] ctm;
protected String imageName,id;
protected int iw,ih;
protected double[] coords;
//Change header name in single page output
protected String customSinglePageHeaderName = System.getProperty("org.jpedal.pdf2html.customSinglePageHeader");
//Change file name in single page output
protected String customSingleFileOutputName = System.getProperty("org.jpedal.pdf2html.customSingleFileName");
//used to eliminate duplicate glyfs
protected float[][] lastTrm;
public OutputDisplay(int pageNumber, Point2D midPoint, Rectangle cropBox, boolean addBackground, int defaultSize, ObjectStore newObjectRef) {
super();
//setup FontMode with any value passed in via JVM or default
fontMode=defaultMode;
type = DynamicVectorRenderer.CREATE_HTML;
this.pageNumber = pageNumber;
this.objectStoreRef = newObjectRef;
this.addBackground = addBackground;
this.cropBox = cropBox;
this.midPoint = midPoint;
//setupArrays(defaultSize);
areas = new Vector_Rectangle(defaultSize);
}
//allow user to control various values
public void setValue(int key,int value){
switch(key){
case MaxNumberOfDecimalPlaces:
this.dpCount=value;
break;
case FontMode:
//<start-std>
/**
//<end-std>
if(value==org.jpedal.examples.html.HTMLFontMapper.EMBED_ALL || value==org.jpedal.examples.html.HTMLFontMapper.EMBED_ALL_EXCEPT_BASE_FAMILIES){
break;
}
/**/
this.fontMode=value;
break;
case PercentageScaling:
this.scaling=value/100f;
//adjust for single page
newTotalHeight = (int)(((cropBox.height*scaling) + pageGap) * (pageNumber-1)) ; //How far down the page the content of next page starts in single file output
break;
case RasterizeText:
this.rasterizeText=value;
break;
case UseFontResizing:
this.fontChangeNeeded=value;
break;
//case SpacingPercentage:
// this.spacingNeeded=value/100f;
// break;
}
}
//allow user to control various values
public void setBooleanValue(int key,boolean value){
switch(key){
case AddNavBar:
this.addNavBar=value;
break;
case ConvertSpacesTonbsp:
convertSpacesTonbsp=value;
break;
case ExcludeMetadata:
excludeMetadata=value;
break;
case IncludeClip:
includeClip=value;
break;
case UseImagesOnNavBar:
this.useImagesOnNavBar=value;
break;
}
}
//allow user to control various values
public int getValue(int key){
int value=-1;
switch(key){
case FontMode:
value=fontMode;
break;
case RasterizeText:
value=rasterizeText;
break;
}
return value;
}
/**
* allow user to set own value for certain tags
* Throws RuntimeException
* @param type
* @param value
*/
public void setTag(int type,String value){
// switch (type) {
//
// case FORM_TAG:
// tag[FORM_TAG]=value;
// break;
//
// default:
throw new RuntimeException("Unknown tag value "+type);
// }
}
/*setup renderer*/
public void init(int width, int height, int rawRotation, Color backgroundColor) {
if(rawRotation==90 || rawRotation==270){
h = width * scaling;
w = height * scaling;
}else{
w = width * scaling;
h = height * scaling;
}
this.pageRotation = rawRotation;
this.backgroundColor = backgroundColor;
shadeId = 0;
currentTextBlock = new TextBlock();
previousTextBlock = new TextBlock();
}
/**
* Add output to correct area so we can assemble later.
* Can also be used for any specific code features (ie setting a value)
*/
public synchronized void writeCustom(int section, Object str) {
switch(section){
case PAGEDATA:
this.pageData=(PdfPageData)str;
//any offset
dx=pageData.getCropBoxX(pageNumber);
dy=pageData.getCropBoxY(pageNumber);
dr=pageData.getRotation(pageNumber);
if(isSingleFileOutput && pageData.getPageCount() != 1){
int pageCount=pageData.getPageCount()+1;
//work out cumulative page heights so far for offset (ie not this page but all previous)
pageoffsets=new int[pageCount];
for(int ii=2;ii<pageCount;ii++){ //so if page 4 we get page 1,2,3
pageoffsets[ii]=(int) (pageoffsets[ii-1]+(pageData.getCropBoxHeight(pageNumber)*scaling)+pageGap);
}
}
break;
case IMAGE_CONTROLLER:
this.imageController=(OutputImageController)str;
this.userControlledImageScaling=imageController!=null;
break;
default:
throw new RuntimeException("Option "+section+" not recognised");
}
}
public synchronized void flagDecodingFinished(){
if(output !=null){
completeOutput();
}
}
// save image in array to draw
public int drawImage(int pageNumber, BufferedImage image, GraphicsState gs, boolean alreadyCached, String name, int optionsApplied, int previousUse) {
//flush any cached text before we write out
flushText();
//show if image is upside down
boolean needsflipping=false;
//figure out co-ords
float x = (gs.CTM[2][0] * scaling);
float y = (gs.CTM[2][1] * scaling);
iw = (int) (gs.CTM[0][0] * scaling);
//value can also be set this way but we need to adjust the x coordinate
if(iw==0){
iw= (int) (gs.CTM[1][0] * scaling);
if(iw<0)
iw=-iw;
}
ih = (int) (gs.CTM[1][1] * scaling);
//again value can be set this way
if(ih==0){
ih= (int) (gs.CTM[0][1] * scaling);
}
//Reset with to positive if negative
if(iw<0)
iw = iw*-1;
if(iw < 1)
iw = 1;
if(ih == 0) {
ih = 1;
}
//Account for negative widths (ie ficha_acceso_a_ofimatica_e-portafirma.pdf)
if(ih < 1) {
y += ih;
ih = Math.abs(ih);
}
//add negative width value to of the x coord problem_document2.pdf page 3
if(gs.CTM[0][0] < 0){
x=x-iw;
}
//adjust in this case so in correct place
if(gs.CTM[0][0]==0 && gs.CTM[1][1]==0 && gs.CTM[0][1]>0 && gs.CTM[1][0]<0){
x=x-iw;
needsflipping=true;
}
//factor in non-zero offset for cropBox
//for the moment lets limit it to just this case of page cropping/4.pdf
if(gs.CTM[0][0]==0 && gs.CTM[1][1]==0 && gs.CTM[0][1]>0 && gs.CTM[1][0]>0){
x=x-(pageData.getCropBoxX(pageNumber)/2);
y=y-pageData.getCropBoxY(pageNumber);
}
Graphics2D g2savedImage=null;
//scale image
BufferedImage savedImage=image;
Image img=null;
if(!userControlledImageScaling){ //default scaling option
//we use image type if no clip but use transparent if clipped
int imageMode=image.getType();
//see if clip and apply if needed
Rectangle clip=null;
Area clipShape = gs.getClippingShape();
if(clipShape!=null) {
//scale if needed
if(scaling!=1)
clipShape.transform(AffineTransform.getScaleInstance(scaling, scaling));
clip=clipShape.getBounds();
}
boolean applyClip=false;
if(clip!=null && (clip.x>x || clip.y>y || clip.getMaxX()<(x+iw) || clip.getMaxY()<(y+ih))){
applyClip=true;
imageMode=BufferedImage.TYPE_INT_ARGB;
}
img = image.getScaledInstance(iw, ih, BufferedImage.SCALE_SMOOTH);
savedImage = new BufferedImage(iw, ih, imageMode);
g2savedImage = (Graphics2D) savedImage.getGraphics();
if(applyClip){ //add the clip onto image
AffineTransform before=g2savedImage.getTransform();
//apply transforms to invert
//we built the clip in Java2d (which has origin top left, and image in PDF coords)
//so we need to flip it.
AffineTransform invert=new AffineTransform();
invert.scale(1, -1);
invert.translate(0, -ih);
g2savedImage.transform(invert);
g2savedImage.transform(AffineTransform.getTranslateInstance(-x, -y));
//set the actual clip
g2savedImage.setClip(gs.getClippingShape());
//restore
g2savedImage.setTransform(before);
}
//add transform to flip if needed
if(needsflipping){
AffineTransform aff=new AffineTransform();
aff.scale(scaling, -scaling);
aff.translate(0, -ih);
g2savedImage.setTransform(aff);
}
}
// Covert PDF coords (factor out scaling in calc)
coords =new double[]{x/scaling,y/scaling};
correctCoords(coords);
//add back in scaling
coords[0]=coords[0]*scaling;
coords[1]=coords[1]*scaling;
//subtract image height as y co-ordinate inverted
coords[1] -= ih;
//we need to factor in scaling as cropBox not scaled and co-ords are!
Rectangle rect = new Rectangle((int) (coords[0]/scaling),(int)(coords[1]/scaling),(int)(iw/scaling),(int)(ih/scaling));
if(cropBox.intersects(rect)) {
if(!userControlledImageScaling){ //default values
g2savedImage.drawImage(img,0, 0, null);
/**
* rotate if needed
* (done separately at moment to keep it simple and separate)
*/
if(pageRotation==90 || pageRotation==270){
savedImage = rotateImage(savedImage);
}
}
/**
* store image
*/
image=savedImage; //assign so we will use if passed out
id=name+'_'+pageNumber;
if(!embedImageAsBase64){ //usual operation
//make sure image Dir exists
String imageDir;
if(type == DynamicVectorRenderer.CREATE_JAVAFX)
imageDir= "/img/pix/" + pageNumberAsString+ '/';
else
imageDir= fileName+"/img/" + pageNumberAsString+ '/';
File imgDir=new File(rootDir +imageDir);
if(!imgDir.exists())
imgDir.mkdirs();
imageName= imageDir+name+".png";
try{
ImageIO.write(savedImage, "PNG", new File(rootDir + imageName));
}catch(Exception e){
e.printStackTrace();
}
}
return -2;
}else
return -1;
}
protected BufferedImage rotateImage(BufferedImage savedImage) {
int w = savedImage.getWidth();
int h = savedImage.getHeight();
BufferedImage bi = new BufferedImage(h,w,savedImage.getType());
Graphics2D g2 = bi.createGraphics();
g2.rotate(Math.toRadians(pageRotation), w/2, h/2);
int diff=(w-h)/2;
g2.drawImage(savedImage,diff,diff,null);
savedImage=bi;
return savedImage;
}
/**
* trim if needed
* @param i
* @return
* * (note duplicate in OutputShape)
*/
protected String setPrecision(double i) {
String value=String.valueOf(i);
int ptr=value.indexOf(".");
int len=value.length();
int decimals=len-ptr-1;
if(ptr>0 && decimals> this.dpCount){
if(dpCount==0)
value=value.substring(0,ptr+dpCount);
else
value=value.substring(0,ptr+dpCount+1);
}
return value;
}
/**
* trim if needed
* @param i
* @return
* * (note duplicate in OutputShape)
*/
protected static String setPrecision(double i, int dpCount) {
String value=String.valueOf(i);
int ptr=value.indexOf(".");
int len=value.length();
int decimals=len-ptr-1;
if(ptr>0 && decimals> dpCount){
if(dpCount==0)
value=value.substring(0,ptr+dpCount);
else
value=value.substring(0,ptr+dpCount+1);
}
return value;
}
/*save clip in array to draw*/
public void drawClip(GraphicsState currentGraphicsState, Shape defaultClip, boolean canBeCached) {
//RenderUtils.renderClip(currentGraphicsState.getClippingShape(), null, defaultClip, g2);
}
//For debugging text
// static int textShown = 0;
// static int amount = 7;
public void drawEmbeddedText(float[][] Trm, int fontSize, PdfGlyph embeddedGlyph,
Object javaGlyph, int type, GraphicsState gs,
AffineTransform at, String glyf, PdfFont currentFontData, float glyfWidth){
//90 broken general/siggietest broken and cropping/02-07.pdf
/**
* factor in page rotation to Trm (not on canvas)
*/
if(rasterizeText!= TEXT_ON_CANVAS){
switch(this.pageRotation){
case 90:
{
float x=Trm[2][0],y=Trm[2][1];
Trm=Matrix.multiply(Trm,new float[][]{{0,-1,0},{1,0,0},{0,0,1}});
Trm[2][0]=x;
Trm[2][1]=y;
//alter trm to include
//System.out.println("after="+(int)Trm[0][0]+" "+(int)Trm[1][0]+" "+(int)Trm[0][1]+" "+(int)Trm[1][1]+" "+Trm[2][0]+" "+Trm[2][1]);
}
break;
case 180:
{
float x=Trm[2][0],y=Trm[2][1];
Trm=Matrix.multiply(Trm,new float[][]{{1,0,0},{0,-1,0},{0,0,1}});
Trm[2][0]=x;
Trm[2][1]=y;
//alter trm to include
//System.out.println("after="+(int)Trm[0][0]+" "+(int)Trm[1][0]+" "+(int)Trm[0][1]+" "+(int)Trm[1][1]+" "+Trm[2][0]+" "+Trm[2][1]);
}
break;
case 270:
{
float x=Trm[2][0],y=Trm[2][1];
Trm=Matrix.multiply(Trm,new float[][]{{0,1,0},{-1,0,0},{0,0,1}});
Trm[2][0]=(pageData.getMediaBoxHeight(pageNumber)+(pageData.getCropBoxHeight(pageNumber)-pageData.getCropBoxY(pageNumber)))-y;
Trm[2][1]=x+pageData.getCropBoxX(pageNumber);
//alter trm to include
//System.out.println("after="+(int)Trm[0][0]+" "+(int)Trm[1][0]+" "+(int)Trm[0][1]+" "+(int)Trm[1][1]+" "+Trm[2][0]+" "+Trm[2][1]);
break;
}
}
}
//if -100 we get value - allows user to override
if(glyfWidth==-100){
glyfWidth=currentFontData.getWidth(-1);
}
/**
* optimisations to choose best fontsize for different cases of FontSize, Tc and Tw
*/
int altFontSize=-1,fontCondition=-1;
TextState currentTextState=gs.getTextState();
if(currentTextState!=null){
float rawFontSize=Trm[0][0];
//if(Math.abs(rawFontSize)<1)
// rawFontSize=(int)Trm[0][1];
float tc=currentTextState.getCharacterSpacing();
float tw=currentTextState.getWordSpacing();
//do not use rounded up fontSize - we generate value again
float diff=(rawFontSize-(int)rawFontSize);
// System.out.println(fontSize+" "+rawFontSize+" "+Trm[0][0]+" "+(rawFontSize-(int)rawFontSize)+" "+tc+" "+tw+" "+glyf);
if(rawFontSize>1){
//case 1 font sixe 8.5 or above and negative Tc
if(fontSize==9 && diff==0.5f && tc>-0.2 && tw==0 && fontSize!=(int)rawFontSize){
altFontSize=(int)rawFontSize;
//identify type
fontCondition=1;
// System.out.println(fontSize+" "+rawFontSize+" "+Trm[0][0]+" "+(rawFontSize-(int)rawFontSize)+" "+tc+" "+tw);
}else if(fontSize==8 && rawFontSize>8 && tc>0 && diff<0.3f && tw==0){
altFontSize=fontSize+1;
//System.out.println("XX "+fontSize+" "+rawFontSize+" "+Trm[0][0]+" "+(rawFontSize-(int)rawFontSize)+" "+tc+" "+tw);
fontCondition=2;
}
}
}
//ignore blocks of multiple spaces
if(currentTokenNumber==lastTokenNumber && glyf.equals(" ") && lastGlyf.equals(" ")){
flushText();
return;
}
//trap any non-standard glyfs
if(OutputDisplay.Helper!=null && glyf.length()>3 && !StandardFonts.isValidGlyphName(glyf)){
glyf= OutputDisplay.Helper.mapNonstandardGlyfName(glyf,currentFontData);
}
//if(!isObjectVisible(new Rectangle((int)Trm[2][0], (int)Trm[2][1],fontSize,fontSize),gs.getClippingShape()))
// return;
// if(textShown == amount)
// return;
//
// textShown++;
//save raw value as we need this for test later
this.Trm = Trm;
this.ctm=gs.CTM;
//code assumes Trm is square - if not alter font size
if(Trm[0][0]!=Trm[1][1] && Trm[1][0]==0 && Trm[0][1]==0){
fontSize= (int) Trm[0][0];
}
this.fontSize = fontSize;
//if its not visible, ignore it
Area clip=gs.getClippingShape();
if(clip!=null && !clip.getBounds().contains(new Point((int)Trm[2][0]+1,(int) Trm[2][1]+1)) && !clip.getBounds().contains(new Point((int)Trm[2][0]+fontSize/2,(int) Trm[2][1]+fontSize/2)))
return;
//<start-std>
if(rasterizeText!= TEXT_AS_TEXT){ //option to convert to shapes
if(glyf.length()>0)
rasterizeTextAsShape(Trm, fontSize, embeddedGlyph, gs, currentFontData, glyf, at);
return;
}
//<end-std>
//Ignore empty or crappy characters
if(glyf.length()==0 || TextBlock.ignoreGlyf(glyf)) {
return;
}
//code assumes Trm is square - if not alter font size
if(Trm[0][0]!=Trm[1][1] && Trm[1][0]==0 && Trm[0][1]==0){
fontSize= (int) Trm[0][0];
}
//text size sometimes negative as a flag so ensure always positive
if(fontSize<0)
fontSize=-fontSize;
float charWidth = fontSize * glyfWidth;
//get font name and convert if needed for output and changed
if(!currentFontData.getBaseFontName().equals(lastFontUsed)){
fontMapper= getFontMapper(currentFontData);
lastFontUsed=currentFontData.getBaseFontName();
//save font id so we can see how many fonts mapped onto this name
String value= (String) embeddedFontsByFontID.get(lastFontUsed);
if(value==null)
embeddedFontsByFontID.put(lastFontUsed,"browser");
else if(!value.contains("browser"))
embeddedFontsByFontID.put(lastFontUsed,value+ ',' +"browser");
}
int textFillType = gs.getTextRenderType();
int color = textFillType == GraphicsState.STROKE ? gs.getStrokeColor().getRGB(): gs.getNonstrokeColor().getRGB();
double[] coords = {Trm[2][0], Trm[2][1]};
correctCoords(coords);
//reject duplicate text used to create text bold by creating slighlty offset second character
if(lastTrm!=null &&
Trm[0][0]==lastTrm[0][0] && Trm[0][1]==lastTrm[0][1] && Trm[1][0]==lastTrm[1][0] && Trm[1][1]==lastTrm[1][1]
&& glyf.equals(lastGlyf)){
//work out absolute diffs
float xDiff= Math.abs(Trm[2][0]-lastTrm[2][0]);
float yDiff= Math.abs(Trm[2][1]-lastTrm[2][1]);
//if does not look like new char, ignore
float fontDiffAllowed=1;
if(xDiff<fontDiffAllowed && yDiff<fontDiffAllowed){
return;
}
}
float x=(float) coords[0];
float y=(float) coords[1];
float rotX=x;
float rotY=y;
//needed for this case to ensure text runs across page
if(this.pageRotation==90){
rotX=Trm[2][1];
rotY=Trm[2][0];
}
//Append new glyf to text block if we can otherwise flush it
if(writeEveryGlyf || !currentTextBlock.isSameFont(fontSize, fontMapper, Trm, color)
|| !currentTextBlock.appendText(glyf, charWidth, rotX, rotY , groupGlyphsInTJ, (!groupGlyphsInTJ || currentTokenNumber!=lastTokenNumber),x,y)) {
flushText();
//Set up new block, if it just a space disregard it.
if(!glyf.equals(" ")) {
float spaceWidth = fontSize * currentFontData.getCurrentFontSpaceWidth();
currentTextBlock = new TextBlock(glyf, fontSize, fontMapper, Trm, x, y,
charWidth, color, spaceWidth, cropBox, Trm, ctm, altFontSize,fontCondition,rotX,rotY);
if(convertSpacesTonbsp)
currentTextBlock.convertSpacesTonbsp(true);
if(currentTextBlock.getRotationAngle() == 0) {
currentTextBlock.adjustY(-fontSize);
}
}else {
currentTextBlock = new TextBlock();
}
}
//update incase changed
lastTokenNumber=currentTokenNumber;
lastGlyf=glyf;
lastTrm=Trm;
}
//<start-std>
private void rasterizeTextAsShape(float[][] Trm, int fontSize, PdfGlyph embeddedGlyph, GraphicsState gs, PdfFont currentFontData, String glyf, AffineTransform at) {
/**
* convert text to shape and draw shape instead
* Generally text at 1000x1000 matrix so we scale down by 1000 and then up by fontsize
*/
if(embeddedGlyph!=null && embeddedGlyph.getShape()!=null && !glyf.equals(" ") ){
GraphicsState TextGs=gs;//new GraphicsState();
//name we will store draw code under as routine
String JSRoutineName;
//check all chars letters or numbers and use int value if invalid
boolean isInvalid=false;
for(int aa=0;aa<glyf.length();aa++){
if(!Character.isLetterOrDigit(glyf.charAt(aa))){
isInvalid=true;
}
//exit if wrong
if(isInvalid)
break;
}
if(isInvalid)
JSRoutineName=currentFontData.getFontID()+Integer.toHexString((int)glyf.charAt(0));
else
JSRoutineName=currentFontData.getFontID()+glyf;
//ensure starts with letter
if(!Character.isLetter(JSRoutineName.charAt(0)))
JSRoutineName= 's' +JSRoutineName;
//flag to show if generated
String cacheKey= currentFontData.getBaseFontName() + '.' + JSRoutineName;
//see if we have already decoded glyph and use that data to reduce filesize
boolean isAlreadyDecoded=glyfsRasterized.containsKey(cacheKey);
//get the glyph as textGlyf shape (which we already have to render)
Area textGlyf = (Area) embeddedGlyph.getShape().clone();
//adjust GS to work correctly
TextGs.setClippingShape(null);
TextGs.setFillType(gs.getTextRenderType());
/**
* adjust scaling to factor in font size
*/
float[] aff={Trm[0][0],Trm[0][1],Trm[1][0],Trm[1][1]};
float d=1000f; //default if not scaled
float[] BBox=currentFontData.getFontBounds();
//System.out.println(glyf+" "+textGlyf.getBounds()+" "+BBox[0]+" "+BBox[1]+" "+BBox[2]+" "+BBox[3]+" "+fontSize);
//allow for rescaled TT
if(textGlyf.getBounds().height>2000){
//fails!!
//textGlyf.transform(AffineTransform.getScaleInstance(0.01f,0.01f));
//in theory does the same thing and works but larger files
d=d*100;
}
//apply font scaling scaling
for(int aa=0;aa<4;aa++)
aff[aa]=aff[aa]/d;
//do the actual rendering
writeCustom(SCRIPT, "pdf.save();");
double[] coords = {Trm[2][0], Trm[2][1]};
correctCoords(coords);
/**
* adjustments for Type1 fonts
*/
double dx=0,dy=0;
if(currentFontData.getFontType()==StandardFonts.TYPE1){
//fixes bg_holiday
double totY=BBox[3]-BBox[1];
if(totY>1000)
dy=fontSize+(totY-1000)/100;
//good example is general/1.pdf
double totX=BBox[2]-BBox[0];
if(totX>1000)
dx=fontSize-(fontSize*((totX-1000)/1000));
}
//adjust co-ords to allow for scaling
int tx,ty;
switch(pageRotation){
case 90:
ty= (int) ((int) (coords[0]*1.33f)+dx);
tx=(int)(((cropBox.height-coords[1])*1.33f)-dy);
break;
/**
180 and 270 also need an upside down
case 180:
ty= (int) ((int) (coords[0]*1.33f)+dx);
tx=(int)(((coords[1])*1.33f)-dy);
break;
case 270:
ty= (int) ((int) (coords[0]*1.33f)+dx);
tx=(int)(((coords[1])*1.33f)-dy);
break;
/**/
default:
tx= (int) ((int) (coords[0]*1.33f)+dx);
ty=(int)(((coords[1])*1.33f)-dy);
}
//place scaled shape of text on page
if(Trm[0][1]==0 && Trm[1][0]==0){ //simple version, no scaling
writeCustom(SCRIPT, "pdf.translate(" +tx + ',' +ty+");");
writeCustom(SCRIPT, "pdf.scale("+aff[0]+" , "+aff[3]+");");
}else{ //full monty! Takes more space so not used unless needed
writeCustom(SCRIPT, "pdf.transform("+(aff[0])+" , "+(-aff[1])+", "+(-aff[2])+" , "+(aff[3])+", "+tx+" , "+ty+");");
}
//call to draw text
writeCustom(SCRIPT, JSRoutineName+"(pdf);");
//writeCustom(SCRIPT, JSRoutineName+"a(pdf);");
//writeCustom(SCRIPT, JSRoutineName+"b(pdf);");
//write out colour
int fillType = gs.getFillType();
if(fillType==GraphicsState.FILL || fillType==GraphicsState.FILLSTROKE) {
writeCustom(SCRIPT, "pdf.fillStyle = '" + OutputShape.rgbToCSSColor(gs.nonstrokeColorSpace.getColor().getRGB()) + "';");
writeCustom(SCRIPT, "pdf.fill();");
}
if(fillType==GraphicsState.STROKE || fillType==GraphicsState.STROKE) {
writeCustom(SCRIPT, "pdf.strokeStyle = '" + OutputShape.rgbToCSSColor(gs.strokeColorSpace.getColor().getRGB()) + "';");
writeCustom(SCRIPT, "pdf.stroke();");
}
//generate the JS ONCE for each glyf
if(!isAlreadyDecoded){
//marks debug code to put Trm box and bounds on screen
//drawNonPatternedShape(new Rectangle((int)0,(int)0,(int)(aff[0]),(int)aff[3]), TextGs, Cmd.Tj,JSRoutineName+"a");
//drawNonPatternedShape(textGlyf.getBounds(), TextGs, Cmd.Tj,JSRoutineName+"b");
drawNonPatternedShape(textGlyf, TextGs, Cmd.Tj,JSRoutineName);
glyfsRasterized.put(cacheKey,"x"); //flag it as now in file
}
writeCustom(SCRIPT, "pdf.restore();");
}
}
//<end-std>
protected FontMapper getFontMapper(PdfFont currentFontData) {
return null;
}
/**
* Write out text buffer in correct format
*/
protected void flushText() {}
/*save shape in array to draw*/
public void drawShape(Shape currentShape, GraphicsState gs, int cmd) {
//fix for missing lines on /sample_pdfs_html/general/mmv6.pdf
if(currentShape.getBounds().height<1 && (gs.getLineWidth()*gs.CTM[0][0]>0.5f))
currentShape=new Rectangle(currentShape.getBounds().x,currentShape.getBounds().y,currentShape.getBounds().width,1);
this.ctm = gs.CTM;
if(!isObjectVisible(currentShape.getBounds(),gs.getClippingShape()))
return;
//flush any cached text before we write out
flushText();
//turn pattern into an image
if(gs.getNonstrokeColor().isPattern() || gs.nonstrokeColorSpace.getID()== ColorSpaces.Pattern) { //complex stuff
drawPatternedShape(currentShape, gs);
}
else { //standard shapes
drawNonPatternedShape(currentShape, gs, cmd,null);
}
}
protected void drawNonPatternedShape(Shape currentShape, GraphicsState gs, int cmd, String name) {}
protected void drawPatternedShape(Shape currentShape, GraphicsState gs) {}
private boolean isObjectVisible(Rectangle bounds, Area clip) {
boolean fixShape=true;
if(fixShape && dx==0 && dy==0 && dr==0){
//get any Clip (only rectangle outline)
Rectangle clipBox=null;
//if(dx!=0 || dy!=0) //factor in offset on crop
// bounds.translate(-dx, -dy);
if(clip!=null)
clipBox=clip.getBounds();
else
clipBox=null;
//additional tests to allow for being on edge
if(cropBox!=null && !cropBox.intersects(bounds) && Math.abs(cropBox.getMaxX()-bounds.getMaxX())>1 ||
( clipBox!=null && !clipBox.intersects(bounds)&& Math.abs(clipBox.getMaxX()-bounds.getMaxX())>1)){
return false;
}
}
return true;
}
/*add XForm object*/
final public void drawXForm(DynamicVectorRenderer dvr, GraphicsState gs) {
//flush any cached text before we write out
flushText();
//renderXForm(dvr, gs.getStrokeAlpha());
}
/**
* add footer and other material to complete
*/
protected void completeOutput() {}
public void setOutputDir(String outputDir,String outputFilename, String pageNumberAsString) {
rootDir=outputDir;
fileName=outputFilename;
this.pageNumberAsString=pageNumberAsString;
}
/**
* Coverts an array of numbers to a String for JavaScript parameters.
*
* @param coords Numbers to change
* @param count Use up to count doubles from coords array
* @return String Bracketed stringified version of coords
*/
protected String coordsToStringParam(double[] coords, int count)
{
String result = "";
for(int i = 0; i<count; i++) {
if(i!=0) {
result += ",";
}
result += setPrecision(coords[i]);
}
return result;
}
/**
* Converts coords from Pdf system to java.
*/
protected void correctCoords(double[] coords)
{
coords[0] = coords[0] - midPoint.getX();
coords[0] += cropBox.width / 2;
coords[1] = coords[1] - midPoint.getY();
coords[1] = 0 - coords[1];
coords[1] += cropBox.height / 2;
}
/**
* Formats an int to CSS rgb(r,g,b) string
*
*/
protected static String rgbToColor(int raw)
{
int r = (raw>>16) & 255;
int g = (raw>>8) & 255;
int b = raw & 255;
return "rgb(" + r + ',' + g + ',' + b + ')';
}
/**
* Draws boxes around where the text should be.
*/
protected void drawTextArea() {}
/**
* Draw a debug area around border of page.
*/
protected void drawPageBorder() {}
/**
* allow tracking of specific commands
**/
public void flagCommand(int commandID, int tokenNumber){
switch(commandID){
case Cmd.BT:
//reset to will be rest for text
// lastR=-1;
// lastG=-1;
// lastB=-1;
break;
case Cmd.Tj:
this.currentTokenNumber=tokenNumber;
break;
}
}
protected String replaceTokenValues(String customSingleFileName, int type) {
//Strip the file name from the root dir
String nameOfPDF = rootDir.substring(0, rootDir.length()-1); //strip off last / or \
//find end (which could be after \ or /)
int pt = nameOfPDF.lastIndexOf("\\");
int fowardSlash = nameOfPDF.lastIndexOf("/");
if(fowardSlash>pt)
pt=fowardSlash;
//and remove path from filename
nameOfPDF = nameOfPDF.substring(pt+1);
//all tokens start $ so ignore if not present
if(customSingleFileName!=null && customSingleFileName.contains("$")){
//possible replacement values
String fileName = nameOfPDF;
String pageNumber = pageNumberAsString;
String pageCount = String.valueOf(pageData.getPageCount());
//do the replacements
customSingleFileName=customSingleFileName.replace("$fileName$", fileName);
//prevent "$currentPageNumber$" being used in filename as several files gets produced instead of one
if(type==OutputDisplay.CustomSingleFileName && customSingleFileName.contains("$currentPageNumber$"))
throw new RuntimeException("$currentPageNumber$ - is an in valid token for file name. Use $fileName$ to get the file name or $totalPageNumber$ to get the total amount of pages ");
customSingleFileName=customSingleFileName.replace("$currentPageNumber$", pageNumber);
customSingleFileName=customSingleFileName.replace("$totalPageNumber$", pageCount);
}
return customSingleFileName;
}
public boolean isScalingControlledByUser(){
return userControlledImageScaling;
}
}