Java tutorial
/* * PDFRenderer.java * Copyright (c) 2015 Zbynek Cervinka * * PDFRenderer 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 3 of the License, or * (at your option) any later version. * * PDFRenderer 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 PDFRenderer. If not, see <http://www.gnu.org/licenses/>. * * Created on 10.5.2015, 22:29:10 by Zbynek Cervinka */ package org.fit.cssbox.render; import java.awt.Color; import java.awt.Rectangle; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.net.URI; import java.util.Vector; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.pdmodel.common.PDRectangle; import org.apache.pdfbox.pdmodel.font.PDFont; import org.apache.pdfbox.pdmodel.font.PDType0Font; import org.apache.pdfbox.pdmodel.font.PDType1Font; import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory; import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; import org.fit.cssbox.layout.BackgroundImage; import org.fit.cssbox.layout.Box; import org.fit.cssbox.layout.ElementBox; import org.fit.cssbox.layout.LengthSet; import org.fit.cssbox.layout.ReplacedBox; import org.fit.cssbox.layout.ReplacedContent; import org.fit.cssbox.layout.ReplacedImage; import org.fit.cssbox.layout.TextBox; import org.fit.cssbox.layout.VisualContext; import cz.vutbr.web.css.CSSProperty; import cz.vutbr.web.css.NodeData; import cz.vutbr.web.css.TermColor; /** * A renderer that produces an PDF output using PDFBox library * * @author Zbynek Cervinka */ public class PDFRenderer implements BoxRenderer { private float resCoef, rootHeight; private FontDB fontDB; // PDFBox variables private PDDocument doc = null; private PDPage page = null; private PDPageContentStream content = null; private PDRectangle pageFormat = null; // page help variables private int pageCount; private float pageEnd; // TREE and LIST variables private Node rootNodeOfTree, recentNodeInTree, rootNodeOfList, recentNodeInList; private Vector<Node> nodesWithoutParent = new Vector<Node>(16); // break/avoid tables private Vector<float[]> breakTable = new Vector<float[]>(2); private Vector<float[]> avoidTable = new Vector<float[]>(2); // font variables private Vector<fontTableRecord> fontTable = new Vector<fontTableRecord>(2); // try to replace unicode characters private boolean replaceUnicode = false; private class fontTableRecord { public String fontName; public Boolean isBold; public Boolean isItalic; public PDFont loadedFont; fontTableRecord(String fontName, Boolean isBold, Boolean isItalic, PDFont loadedFont) { this.fontName = fontName; this.isBold = isBold; this.isItalic = isItalic; this.loadedFont = loadedFont; } }; // other variables private OutputStream pathToSave; private float outputTopPadding; private float outputBottomPadding; /** * Constructor * * initialize the variables */ public PDFRenderer(int rootWidth, int rootHeight, OutputStream out, String pageFormat) { this.rootHeight = rootHeight; this.pathToSave = out; this.pageCount = 0; switch (pageFormat) { case "A0": this.pageFormat = PDRectangle.A0; break; case "A1": this.pageFormat = PDRectangle.A1; break; case "A2": this.pageFormat = PDRectangle.A2; break; case "A3": this.pageFormat = PDRectangle.A3; break; case "A4": this.pageFormat = PDRectangle.A4; break; case "A5": this.pageFormat = PDRectangle.A5; break; case "A6": this.pageFormat = PDRectangle.A6; break; case "LETTER": this.pageFormat = PDRectangle.LETTER; break; default: this.pageFormat = PDRectangle.A4; break; } initSettings(rootWidth); } public PDFRenderer(int rootWidth, int rootHeight, OutputStream out, PDRectangle pageFormat) { this.rootHeight = rootHeight; this.pathToSave = out; this.pageCount = 0; this.pageFormat = pageFormat; initSettings(rootWidth); } private void initSettings(int rootWidth) { // calculate resize coefficient resCoef = this.pageFormat.getWidth() / rootWidth; // count the recent number of pages pageCount = (int) Math.ceil(rootHeight * resCoef / this.pageFormat.getHeight()); // sets the top and bottom paddings for the output page outputTopPadding = this.pageFormat.getHeight() / 100; outputBottomPadding = this.pageFormat.getHeight() / 100; fontDB = new FontDB(); } @Override public void startElementContents(ElementBox elem) { } @Override public void finishElementContents(ElementBox elem) { } /** * Creates 2 new Nodes with reference to elem inside * - one goes to LIST * - second goes to TREE */ @Override public void renderElementBackground(ElementBox elem) { // elem has no parent object - new Node will be root if (elem.getParent() == null) { // TREE rootNodeOfTree = new Node(null, elem, null, null, null); recentNodeInTree = rootNodeOfTree; // LIST rootNodeOfList = new Node(null, elem, null, null, rootNodeOfTree); recentNodeInList = rootNodeOfList; } // add new Node with reference to elem inside to TREE and to LIST to right place else { // TREE Node targetNode = findNodeToInsert(elem.getParent().getOrder(), elem.getOrder()); if (targetNode == null) { Node tmpNode = new Node(null, elem, null, null, null); tmpNode.setParentIDOfNoninsertedNode(elem.getParent().getOrder()); nodesWithoutParent.add(tmpNode); } else { recentNodeInTree = targetNode.insertNewNode(elem, null, null, null); } // LIST recentNodeInList = recentNodeInList.insertNewNode(elem, null, null, recentNodeInTree); } } /** * Creates 2 Nodes for LIST and TREE with the same content - TEXT to insert * Inserts object to right place */ @Override public void renderTextContent(TextBox text) { // elem has no parent object - new Node will be root if (text.getParent() == null) { rootNodeOfTree = new Node(null, null, text, null, null); recentNodeInTree = rootNodeOfTree; rootNodeOfList = new Node(null, null, text, null, rootNodeOfTree); recentNodeInList = rootNodeOfList; } // add new Node with reference to elem inside to TREE and to LIST to right place else { // TREE Node targetNode = findNodeToInsert(text.getParent().getOrder(), text.getOrder()); if (targetNode == null) { Node tmpNode = new Node(null, null, text, null, null); tmpNode.setParentIDOfNoninsertedNode(text.getParent().getOrder()); nodesWithoutParent.add(tmpNode); } else { recentNodeInTree = targetNode.insertNewNode(null, text, null, null); } // LIST recentNodeInList = recentNodeInList.insertNewNode(null, text, null, recentNodeInTree); } } /** * Creates 2 Nodes for LIST and TREE with the same content - BOX to insert * Inserts object to right place */ @Override public void renderReplacedContent(ReplacedBox box) { Box convertedBox = (Box) box; // elem has no parent object - new Node will be root if (convertedBox.getParent() == null) { rootNodeOfTree = new Node(null, null, null, box, null); recentNodeInTree = rootNodeOfTree; rootNodeOfList = new Node(null, null, null, box, rootNodeOfTree); recentNodeInList = rootNodeOfList; } // add new Node with reference to elem inside to TREE and to LIST to right place else { // TREE Node targetNode = findNodeToInsert(convertedBox.getParent().getOrder(), convertedBox.getOrder()); if (targetNode == null) { Node tmpNode = new Node(null, null, null, box, null); tmpNode.setParentIDOfNoninsertedNode(convertedBox.getParent().getOrder()); nodesWithoutParent.add(tmpNode); } else { recentNodeInTree = targetNode.insertNewNode(null, null, box, null); } // LIST recentNodeInList = recentNodeInList.insertNewNode(null, null, box, recentNodeInTree); } } /** * Processing the LIST and TREE data structures and writes data to OUTPUT */ @Override public void close() { // FINISH STEP B - process the nodesWithoutParent table and insert nodes to TREE, if possible tryToInsertNotInsertedNodes(); // STEP C - creates breakTable and avoidTable tables from data structure // and modifies them to contain only records that are not causing conflicts // or unacceptable page break appearance createAndProcessBreakAndAvoidTables(); // STEP D - makes paging in the TREE data structure according to data // in breakTable, avoidTable and the ends determined by the size of document page makePaging(); // STEP E - transforms all data from LIST data structure to Apache PDFBox format // and using Apache PDFBox functions creates PDF document containing transformed data makePDF(); } ////////////////////////////////////////////////////////////////////// // FUNCTIONS FULFILLING THE B - E STEP ////////////////////////////////////////////////////////////////////// /** * FINISH STEP B - process the nodesWithoutParent table and insert nodes to TREE, if possible */ private void tryToInsertNotInsertedNodes() { // repeats until the table is empty while (nodesWithoutParent.size() > 0) { int sizeBefore = nodesWithoutParent.size(); // goes through table and tries to find at least one record to add to TREE for (int i = 0; i < nodesWithoutParent.size(); i++) { Node findMyParent = nodesWithoutParent.get(i); Node nodeToInsert = findNodeToInsert(findMyParent.getParentIDOfNoninsertedNode(), findMyParent.getID()); // inserts the node, if parent node found in the tree if (nodeToInsert != null) { nodeToInsert.insertNewNode(findMyParent); nodesWithoutParent.remove(i); } } // if non of the records can not bee added to the TREE, it breaks the cycle if (sizeBefore == nodesWithoutParent.size()) break; } } /** * STEP C - creates breakTable and avoidTable tables from data structure * and modifies them to contain only records that are not causing conflicts * or unacceptable page break appearance */ private void createAndProcessBreakAndAvoidTables() { // creates and inserts records into breakTable and avoidTable // according to CSS property of elements in TREE createBreakAvoidTables(); // deletes all items bigger than argument*pageFormat.getHeight() in avoidTable deleteAvoidsBiggerThan(0.8f); // merges all records containing overlapping intervals, respects maximum size of interval mergeAvoids(0.8f); } /** * STEP D - makes paging in the TREE data structure according to data * in breakTable, avoidTable and the ends determined by the size of document page */ private void makePaging() { pageEnd = pageFormat.getHeight(); while (breakTable.size() > 0 || pageEnd < rootHeight * resCoef) { // continues breaking until the breakTable is not empty // or the end of page is below the content limit if (breakTable.size() == 0 || pageEnd < breakTable.get(0)[0]) { // searches avoidTable for interval on the boundary between 2 pages boolean nalezeno = false; for (int i = 0; i < avoidTable.size(); i++) { if (avoidTable.get(i)[0] < pageEnd && avoidTable.get(i)[1] > pageEnd) { makeBreakAt(avoidTable.get(i)[2]); // sets new end of page according to height of the page in PDF document this.pageEnd += this.pageFormat.getHeight(); nalezeno = true; } } // not founded in avoidTable -> break normal if (!nalezeno) { makeBreakAt(pageEnd); // sets new end of page according to height of the page in PDF document this.pageEnd += this.pageFormat.getHeight(); } } // EOP is inside the interval in first record of breakTable else if (pageEnd > breakTable.get(0)[0] && pageEnd < breakTable.get(0)[1]) { if (breakTable.get(0)[2] > pageEnd) { makeBreakAt(pageEnd); // sets new end of page according to height of the page in PDF document this.pageEnd += this.pageFormat.getHeight(); } else makeBreakAt(breakTable.get(0)[2]); breakTable.remove(0); } // EOP is after the interval in first record of breakTable else { makeBreakAt(breakTable.get(0)[2]); breakTable.remove(0); } } } /** * STEP E - transforms all data from LIST data structure to Apache PDFBox format * and using Apache PDFBox functions creates PDF document containing transformed data */ private void makePDF() { // creates PDF document with first blank page createDocPDFBox(); // inserts all needed blank pages to PDF document insertNPagesPDFBox(pageCount); // transforms all data from LIST data structure to Apache PDFBox format // and writes it do PDF document writeAllElementsToPDF(); // saves current document saveDocPDFBox(); } ///////////////////////////////////////////////////////////////////// // FUNCTIONS FOR WORKING WITH LIST AND TREE DATA STRUCTURE ///////////////////////////////////////////////////////////////////// /** * Finds the parent node to insert actual node in TREE * @return the Node */ private Node findNodeToInsert(int parentID, int myID) { // there is 2x ID=0 at the root of TREE - if my parents ID is zero and I am not, // I have to insert to the second node with ID=0 if (myID != 0 && parentID == 0) return rootNodeOfTree.getAllChildren().firstElement(); // wanted node "to insert" is recent node if (recentNodeInTree.getID() == parentID) return recentNodeInTree; // wanted node "to insert" is parent node of recent node if (recentNodeInTree.getParentNode() != null && recentNodeInTree.getParentNode().getID() == parentID) return recentNodeInTree.getParentNode(); // goes through whole tree Vector<Node> queueOpen = new Vector<Node>(16); queueOpen.add(rootNodeOfTree); while (queueOpen.size() > 0) { if (queueOpen.firstElement().getID() == parentID) return queueOpen.firstElement(); Vector<Node> children = queueOpen.firstElement().getAllChildren(); if (children != null) queueOpen.addAll(children); queueOpen.remove(0); } return null; } ///////////////////////////////////////////////////////////////////// // FUNCTIONS FOR WORKING WITH BREAKTABLE AND AVOIDTABLE ///////////////////////////////////////////////////////////////////// /** * Goes throw TREE and inserts items into breakTable and into avoidTable */ private void createBreakAvoidTables() { Vector<Node> queueOpen = new Vector<Node>(16); queueOpen.add(rootNodeOfTree); // goes through TREE while (queueOpen.size() > 0) { Node recNodeToInvestigate = queueOpen.firstElement(); queueOpen.remove(0); if (recNodeToInvestigate.isElem()) { // gets CSS property for further classification NodeData style = recNodeToInvestigate.getElem().getStyle(); CSSProperty.PageBreak pgbefore = style.getProperty("page-break-before"); CSSProperty.PageBreak pgafter = style.getProperty("page-break-after"); CSSProperty.PageBreakInside pginside = style.getProperty("page-break-inside"); // element contains page-break-before: always; CSS property if (pgbefore != null && pgbefore == CSSProperty.PageBreak.ALWAYS) { // creates empty record float[] tableRec = new float[4]; // finds start of the interval Node temp = getElementAbove(recNodeToInvestigate); if (temp == null) tableRec[0] = recNodeToInvestigate.getParentNode().getElemY() * resCoef + recNodeToInvestigate.getParentNode().getPlusOffset(); else tableRec[0] = getLastBottom(temp) * resCoef; // finds ends of the interval tableRec[1] = getFirstTop(recNodeToInvestigate) * resCoef; // finds the break place tableRec[2] = recNodeToInvestigate.getElem().getAbsoluteContentY() * resCoef; // inserts into breakTable insertIntoTable(tableRec, breakTable); } // element contains page-break-after: always; CSS property if (pgafter != null && pgafter == CSSProperty.PageBreak.ALWAYS) { // creates empty record float[] tableRec = new float[4]; // finds start of the interval tableRec[0] = getLastBottom(recNodeToInvestigate) * resCoef; // finds ends of the interval Node temp = getElementBelow(recNodeToInvestigate); if (temp != null) { tableRec[1] = getFirstTop(temp) * resCoef; } else { tableRec[1] = recNodeToInvestigate.getElemY() * resCoef + recNodeToInvestigate.getElemHeight() * resCoef; } // finds the break place tableRec[2] = recNodeToInvestigate.getElem().getAbsoluteContentY() * resCoef + recNodeToInvestigate.getElem().getHeight() * resCoef; // inserts into breakTable insertIntoTable(tableRec, breakTable); } // element contains page-break-before: avoid; CSS property if (pgbefore != null && pgbefore == CSSProperty.PageBreak.AVOID) { // creates empty record float[] tableRec = new float[4]; // finds start of the interval Node temp = getElementAbove(recNodeToInvestigate); if (temp != null) { tableRec[0] = getLastBottom(temp) * resCoef; } else { tableRec[0] = recNodeToInvestigate.getElemY() * resCoef; } // finds ends of the interval tableRec[1] = getFirstTop(recNodeToInvestigate) * resCoef; // finds the break place tableRec[2] = tableRec[0] - 1; // inserts into avoidTable insertIntoTable(tableRec, avoidTable); } // element contains page-break-after: avoid; CSS property if (pgafter != null && pgafter == CSSProperty.PageBreak.AVOID) { // creates empty record float[] tableRec = new float[4]; // finds start of the interval tableRec[0] = getLastBottom(recNodeToInvestigate) * resCoef; // finds ends of the interval Node temp = getElementBelow(recNodeToInvestigate); if (temp != null) { tableRec[1] = getFirstTop(temp) * resCoef; } else { tableRec[1] = recNodeToInvestigate.getElemY() * resCoef + recNodeToInvestigate.getElemHeight() * resCoef; } // finds the break place tableRec[2] = tableRec[0] - 1; // inserts into avoidTable insertIntoTable(tableRec, avoidTable); } // element contains page-break-inside: avoid; CSS property if (pginside != null && pginside == CSSProperty.PageBreakInside.AVOID) { // creates empty record float[] tableRec = new float[4]; // finds start of the interval tableRec[0] = recNodeToInvestigate.getElem().getAbsoluteContentY() * resCoef - 1; // finds ends of the interval tableRec[1] = tableRec[0] + recNodeToInvestigate.getElem().getHeight() * resCoef + 1; // finds the break place tableRec[2] = tableRec[0] - 1; // inserts into avoidTable insertIntoTable(tableRec, avoidTable); } } // adds all children to the end of queueOpen if (recNodeToInvestigate.getAllChildren() != null) { queueOpen.addAll(recNodeToInvestigate.getAllChildren()); } } } /** * Inserts record into breakTable or into avoidTable */ private void insertIntoTable(float[] tableRec, Vector<float[]> table) { boolean inserted = false; for (int i = 0; i < table.size(); i++) { if (tableRec[0] < table.get(i)[0]) { table.add(i, tableRec); inserted = true; break; } } if (!inserted) table.add(tableRec); } /** * Deletes items in Avoid table that are higher than "biggerThan" of the page height */ private void deleteAvoidsBiggerThan(float biggerThan) { for (int i = 0; i < avoidTable.size(); i++) { if (avoidTable.get(i)[1] - avoidTable.get(i)[0] > biggerThan * pageFormat.getHeight()) avoidTable.remove(i); } } /** * Merges avoid interval that are overlapping */ private void mergeAvoids(float biggerThan) { // goes through table for (int i = 1; i < avoidTable.size(); i++) { // tests if intervals in records are overlapping if (avoidTable.get(i - 1)[1] > avoidTable.get(i)[0]) { // tests size of interval if it is not larger than allowed if (avoidTable.get(i)[1] - avoidTable.get(i - 1)[0] > biggerThan * pageFormat.getHeight()) { avoidTable.remove(i); i--; } // merges overlapping records else { if (avoidTable.get(i - 1)[1] < avoidTable.get(i)[1]) avoidTable.get(i - 1)[1] = avoidTable.get(i)[1]; avoidTable.remove(i); i--; } } } } /** * Updates all tables by moving all break/avoid lines */ private void updateTables(float moveBy) { // moves all records in breakTable for (int i = 0; i < breakTable.size(); i++) { breakTable.get(i)[0] += moveBy; breakTable.get(i)[1] += moveBy; breakTable.get(i)[2] += moveBy; } // moves all records in avoidTable for (int i = 0; i < avoidTable.size(); i++) { avoidTable.get(i)[0] += moveBy; avoidTable.get(i)[1] += moveBy; avoidTable.get(i)[2] += moveBy; } } ///////////////////////////////////////////////////////////////////// // FUNCTIONS FOR MAKING PAGING ///////////////////////////////////////////////////////////////////// /** * Finds the element above element * @return the Node */ private Node getElementAbove(Node recentNode) { if (recentNode == null) return null; Node nParent = recentNode.getParentNode(); if (nParent == null) return null; Vector<Node> nChildren = nParent.getAllChildren(); if (nChildren == null) return null; Node nodeX = null; // goes through whole TREE while (nChildren.size() > 0) { Node temp = nChildren.firstElement(); nChildren.remove(0); // if recent child's ID is equal to original nod's ID - continue if (recentNode.getID() == temp.getID()) continue; // if the child is not above - continue if (temp.getElemY() * resCoef + temp.getPlusOffset() + temp.getElemHeight() * resCoef + temp.getPlusHeight() > recentNode.getElemY() * resCoef + recentNode.getPlusOffset()) continue; if (nodeX == null) nodeX = temp; else if (nodeX.getElemY() * resCoef + nodeX.getPlusOffset() + nodeX.getElemHeight() * resCoef + nodeX.getPlusHeight() <= temp.getElemY() * resCoef + temp.getPlusOffset() + temp.getElemHeight() * resCoef + temp.getPlusHeight()) { nodeX = temp; } } return nodeX; } /** * Finds the element below element * @return the Node */ private Node getElementBelow(Node recentNode) { if (recentNode == null) return null; // gets Vector of all parents children (including the node itself) Node nParent = recentNode.getParentNode(); if (nParent == null) return null; Vector<Node> nChildren = nParent.getAllChildren(); if (nChildren == null) return null; Node wantedNode = null; // goes through all children and search for node below the node given while (nChildren.size() > 0) { // gets first element from Vector Node temp = nChildren.firstElement(); nChildren.remove(0); // continues if recent node is the same as the original node if (recentNode.getID() == temp.getID()) continue; // new candidate is not under recent node if (temp.getElemY() * resCoef + temp.getPlusOffset() < recentNode.getElemY() * resCoef + recentNode.getElemHeight() * resCoef + recentNode.getPlusHeight() + recentNode.getPlusOffset()) { continue; } // wantedNode gets new reference if it has not one yet or the old node // contains element with lower position then new candidate if (wantedNode == null) wantedNode = temp; else if (wantedNode.getElemY() * resCoef + wantedNode.getPlusOffset() >= temp.getElemY() * resCoef + temp.getPlusOffset()) { wantedNode = temp; } } return wantedNode; } /** * Finds the top of first child element in Node * @return the resized distance from top of the document or -1 for not null argument */ private float getFirstTop(Node recentNode) { if (recentNode == null) return -1; Vector<Node> nChildren = recentNode.getAllChildren(); if (nChildren == null) return recentNode.getElemY() * resCoef + recentNode.getPlusOffset(); float vysledekNeelem = Float.MAX_VALUE; float vysledekElem = Float.MAX_VALUE; // goes through subTREE and searches for first not-ElementBox element // - in case it doesn't contain any not-ElementBox element, it would pick first ElementBox element Vector<Node> subTree = nChildren; while (subTree.size() > 0) { Node aktualni = subTree.firstElement(); subTree.remove(0); Vector<Node> subChildren = aktualni.getAllChildren(); if (subChildren != null) subTree.addAll(subChildren); if (aktualni.isElem()) { if (aktualni.getElemY() * resCoef + aktualni.getPlusOffset() < vysledekElem) { vysledekElem = aktualni.getElemY() * resCoef + aktualni.getPlusOffset(); } } else { if (aktualni.getElemY() * resCoef + aktualni.getPlusOffset() < vysledekNeelem) { vysledekNeelem = aktualni.getElemY() * resCoef + aktualni.getPlusOffset(); } } } if (vysledekNeelem != Float.MAX_VALUE) return vysledekNeelem; if (vysledekElem != Float.MAX_VALUE) return vysledekElem; return -2; } /** * Finds the bottom of last child element in Node * @return the resized distance from top of the document */ private float getLastBottom(Node recentNode) { if (recentNode == null) return -1; Vector<Node> nChildren = recentNode.getAllChildren(); if (nChildren == null) return recentNode.getElemY() * resCoef + recentNode.getElemHeight() * resCoef + recentNode.getPlusOffset() + recentNode.getPlusHeight(); float vysledekNeelem = -Float.MAX_VALUE; float vysledekElem = -Float.MAX_VALUE; // goes through subTREE and searches for last not-ElementBox element // - in case it doesn't contain any not-ElementBox element, it would pick last ElementBox element Vector<Node> subTree = nChildren; while (subTree.size() > 0) { Node aktualni = subTree.firstElement(); subTree.remove(0); Vector<Node> subChildren = aktualni.getAllChildren(); if (subChildren != null) subTree.addAll(subChildren); if (aktualni.isElem()) { if (aktualni.getElemY() * resCoef + aktualni.getElemHeight() * resCoef + aktualni.getPlusOffset() + aktualni.getPlusHeight() > vysledekElem) vysledekElem = aktualni.getElemY() * resCoef + aktualni.getElemHeight() * resCoef + aktualni.getPlusOffset() + aktualni.getPlusHeight(); } else { if (aktualni.getElemY() * resCoef + aktualni.getElemHeight() * resCoef + aktualni.getPlusOffset() + aktualni.getPlusHeight() > vysledekNeelem) vysledekNeelem = aktualni.getElemY() * resCoef + aktualni.getElemHeight() * resCoef + aktualni.getPlusOffset() + aktualni.getPlusHeight(); } } if (vysledekNeelem != -Float.MAX_VALUE) return vysledekNeelem; if (vysledekElem != -Float.MAX_VALUE) return vysledekElem; return -2; } /** * Makes end of page by moving elements in TREE according to line1 */ private void makeBreakAt(float line1) { if (line1 > rootHeight * resCoef || line1 < 0) return; float spaceBetweenLines = 0; line1 -= outputBottomPadding; // goes through TREE end finds set of all non-ElementbBox elements which are crossed by the line1 // - picks one element from this set, which has the lowest distance from the top of the page Vector<Node> myOpen = new Vector<Node>(2); myOpen.add(rootNodeOfTree); float line2 = line1; while (myOpen.size() > 0) { Node myRecentNode = myOpen.firstElement(); myOpen.remove(0); Vector<Node> myChildren = myRecentNode.getAllChildren(); if (myChildren != null) { myOpen.addAll(myChildren); } float startOfTheElement = myRecentNode.getElemY() * resCoef + myRecentNode.getPlusOffset(); float endOfTheElement = startOfTheElement + myRecentNode.getElemHeight() * resCoef + myRecentNode.getPlusHeight(); // sets the line2 variable to match the top of the element from set // which has the lowest distance from the top of the document if (!myRecentNode.isElem()) { if (startOfTheElement < line1 && endOfTheElement > line1) { if (startOfTheElement < line2) { line2 = startOfTheElement; } } } } // counts line3 Vector<Node> myOpen2 = new Vector<Node>(2); myOpen2.add(rootNodeOfTree); float line3 = line2; while (myOpen2.size() > 0) { Node myRecentNode2 = myOpen2.firstElement(); myOpen2.remove(0); Vector<Node> myChildren2 = myRecentNode2.getAllChildren(); if (myChildren2 != null) { myOpen2.addAll(myChildren2); } float startOfTheElement = myRecentNode2.getElemY() * resCoef + myRecentNode2.getPlusOffset(); float endOfTheElement = startOfTheElement + myRecentNode2.getElemHeight() * resCoef + myRecentNode2.getPlusHeight(); // counts the line3 if (!myRecentNode2.isElem()) { if (startOfTheElement < line2 && endOfTheElement > line2) { if (startOfTheElement < line3) { line3 = startOfTheElement; } } } } // counts distance between lines spaceBetweenLines = (float) (pageFormat.getHeight() * Math.ceil((line1 - 1) / pageFormat.getHeight()) - line3); // goes through TREE and increases height or moves element Vector<Node> myOpen3 = new Vector<Node>(2); myOpen3.add(rootNodeOfTree); while (myOpen3.size() > 0) { Node myRecentNode = myOpen3.firstElement(); myOpen3.remove(0); Vector<Node> myChildren = myRecentNode.getAllChildren(); if (myChildren != null) { myOpen3.addAll(myChildren); } // counts start and end of the element float startOfTheElement = myRecentNode.getElemY() * resCoef + myRecentNode.getPlusOffset(); float endOfTheElement = startOfTheElement + myRecentNode.getElemHeight() * resCoef + myRecentNode.getPlusHeight() - 10 * resCoef; // whole element if above the line2 - nothing happens if (endOfTheElement <= line2) { } // increases the height of element which: // - is ElementBox // - is crossed by the line2 // - has got at least 2 children else if (myRecentNode.isElem() && myRecentNode.getElemY() * resCoef + myRecentNode.getPlusOffset() < line2 && myRecentNode.getElemY() * resCoef + myRecentNode.getPlusOffset() + myRecentNode.getElemHeight() * resCoef + myRecentNode.getPlusHeight() >= line2 && myRecentNode.getAllChildren() != null) { myRecentNode.addPlusHeight(outputTopPadding + spaceBetweenLines + outputBottomPadding); } // moves element in one of following cases: // - element is completely below the line2 // - element is crossing line2 and is not ElementBox else { myRecentNode.addPlusOffset(outputTopPadding + spaceBetweenLines + outputBottomPadding); } } // updates height of the original document this.rootHeight += (outputTopPadding + spaceBetweenLines + outputBottomPadding) / resCoef; // updates values in all records in avoidTable and breakTable updateTables(outputTopPadding + spaceBetweenLines + outputBottomPadding); // update count the number of pages this.pageCount = (int) Math.ceil(rootHeight * resCoef / pageFormat.getHeight()); } //////////////////////////////////////////////////////////////////////// // INSERTING TO PDF // // - gets data describing each element from data structures and calls // appropriate function to transform and write data to PDF document //////////////////////////////////////////////////////////////////////// /** * Writing elements to pages in PDF */ private void writeAllElementsToPDF() { // goes through all pages in PDF and inserts to all elements to current page for (int i = 0; i < pageCount; i++) { changeRecentPageToPDFBox(i); Vector<Node> elementsToWriteToPDF = new Vector<Node>(2); elementsToWriteToPDF.add(rootNodeOfList); while (elementsToWriteToPDF.size() > 0) { // get first element from Vector Node recentNode = elementsToWriteToPDF.firstElement(); elementsToWriteToPDF.remove(0); // get all children of recentNode and add to elementsToWriteToPDF Vector<Node> allChildren = recentNode.getAllChildren(); if (allChildren != null) elementsToWriteToPDF.addAll(allChildren); // inserts elem data to PDF if (recentNode.isElem()) { ElementBox elem = recentNode.getElem(); // draws colored background drawBgToElem(elem, i, recentNode.getTreeEq().getPlusOffset(), recentNode.getTreeEq().getPlusHeight()); // draws background image if (elem.getBackgroundImages() != null && elem.getBackgroundImages().size() > 0) { insertBgImg(elem, i, recentNode.getTreeEq().getPlusOffset(), recentNode.getTreeEq().getPlusHeight()); } // draws border drawBorder(elem, i, recentNode.getTreeEq().getPlusOffset(), recentNode.getTreeEq().getPlusHeight()); } // inserts text to PDF if (recentNode.isText()) { // draws the text if it is not overlapping the parent element more then 60 % // on the right side Node parent = recentNode.getTreeEq().getParentNode().getParentNode(); float parentRightEndOfElement = (parent.getElemX() + parent.getElemWidth()) * resCoef; float recentRightEndOfElement = (recentNode.getElemX() + recentNode.getElemWidth()) * resCoef; float widthRecentElem = recentNode.getElemWidth() * resCoef; if (parentRightEndOfElement - recentRightEndOfElement > -widthRecentElem * 0.6) { TextBox text = recentNode.getText(); if (text.isEmpty() || !text.isVisible() || !text.isDeclaredVisible() || !text.isDisplayed()) continue; insertText(text, i, recentNode.getTreeEq().getPlusOffset(), recentNode.getTreeEq().getPlusHeight()); } } // inserts box data to PDF if (recentNode.isBox()) { ReplacedBox box = recentNode.getBox(); insertImg(box, i, recentNode.getTreeEq().getPlusOffset(), recentNode.getTreeEq().getPlusHeight()); } } } } /** * Draws image gained from <img> tag to OUTPUT */ private void insertImg(ReplacedBox box, int i, float plusOffset, float plusHeight) { ReplacedContent cont = box.getContentObj(); if (cont != null) { if (cont instanceof ReplacedImage) { BufferedImage img = ((ReplacedImage) cont).getBufferedImage(); float pageStart = i * pageFormat.getHeight(); float pageEnd = (i + 1) * pageFormat.getHeight(); Rectangle cb = ((Box) box).getAbsoluteContentBounds(); if (img != null && cb.y * resCoef < pageEnd && (cb.y + img.getHeight()) * resCoef + plusHeight + plusOffset > pageStart) { // calculates resized coordinates in CSSBox form float startX = cb.x * resCoef; float startY = (cb.y * resCoef + plusOffset + plusHeight) - i * pageFormat.getHeight(); float width = (float) cb.getWidth() * resCoef; float height = (float) cb.getHeight() * resCoef + plusHeight; // inserts image insertImagePDFBox(img, startX, startY, width, height); } } } } /** * Draws element background image to OUTPUT */ private void insertBgImg(ElementBox elem, int i, float plusOffset, float plusHeight) { for (BackgroundImage bimg : elem.getBackgroundImages()) { BufferedImage img = bimg.getBufferedImage(); float pageStart = i * pageFormat.getHeight(); float pageEnd = (i + 1) * pageFormat.getHeight(); if (img != null && elem.getAbsoluteContentY() * resCoef + plusOffset < pageEnd && (elem.getAbsoluteContentY() + img.getHeight()) * resCoef + plusOffset + plusHeight > pageStart) { // calculates resized coordinates in CSSBox form float startX = (elem.getAbsoluteContentX() - elem.getPadding().left) * resCoef; float startY = (elem.getAbsoluteContentY() - elem.getPadding().top) * resCoef + plusOffset - i * pageFormat.getHeight(); float width = img.getWidth() * resCoef; float height = img.getHeight() * resCoef; // correction of long backgrounds if (height > 5 * plusHeight) height += plusHeight; // inserts image insertImagePDFBox(img, startX, startY, width, height); } } } /** * Draws border to OUTPUT * * @returns 0 for inserted OK, -1 for exception occurs and 1 for border out of page */ private int drawBorder(ElementBox elem, int i, float plusOffset, float plusHeight) { final LengthSet border = elem.getBorder(); if (border.top > 0 || border.right > 0 || border.bottom > 0 || border.right > 0) { // counts the distance between top of the document and the start/end of the page final float pageStart = i * pageFormat.getHeight(); final float pageEnd = (i + 1) * pageFormat.getHeight(); // checks if border is not completely out of page if (elem.getAbsoluteContentY() * resCoef + plusOffset > pageEnd || (elem.getAbsoluteContentY() + plusOffset + elem.getContentHeight()) * resCoef + plusHeight + plusOffset < pageStart) return 1; // calculates resized X,Y coordinates in CSSBox form final float border_x = elem.getAbsoluteContentX() * resCoef; final float border_y = pageFormat.getHeight() - (elem.getAbsoluteContentY() * resCoef + plusOffset) + i * pageFormat.getHeight() - elem.getContentHeight() * resCoef - plusHeight; // calculates the padding for each side final float paddingTop = elem.getPadding().top * resCoef; final float paddingRight = elem.getPadding().right * resCoef; final float paddingBottom = elem.getPadding().bottom * resCoef; final float paddingLeft = elem.getPadding().left * resCoef; // calculates the border size for each side final float borderTopSize = border.top * resCoef; final float borderRightSize = border.right * resCoef; final float borderBottomSize = border.bottom * resCoef; final float borderLeftSize = border.left * resCoef; // calculate the element size final float elemWidth = elem.getContentWidth() * resCoef; final float elemHeight = elem.getContentHeight() * resCoef + plusHeight; float bX, bY, bWidth, bHeight; try { // left border if (borderLeftSize > 0) { bX = border_x - borderLeftSize - paddingLeft; bY = border_y - borderBottomSize - paddingBottom; bWidth = borderLeftSize; bHeight = elemHeight + borderTopSize + borderBottomSize + paddingTop + paddingBottom; drawRectanglePDFBox(borderLeftSize, getBorderColor(elem, "left"), bX, bY, bWidth, bHeight); } // right border if (borderRightSize > 0) { bX = border_x + elemWidth + paddingRight; bY = border_y - borderBottomSize - paddingBottom; bWidth = borderRightSize; bHeight = elemHeight + borderTopSize + borderBottomSize + paddingTop + paddingBottom; drawRectanglePDFBox(borderRightSize, getBorderColor(elem, "right"), bX, bY, bWidth, bHeight); } // top border if (borderTopSize > 0) { bX = border_x - borderLeftSize - paddingLeft; bY = border_y + elemHeight + paddingTop; bWidth = elemWidth + borderLeftSize + borderRightSize + paddingLeft + paddingRight; bHeight = borderTopSize; drawRectanglePDFBox(borderTopSize, getBorderColor(elem, "top"), bX, bY, bWidth, bHeight); } // bottom border if (borderBottomSize > 0) { bX = border_x - borderLeftSize - paddingLeft; bY = border_y - borderBottomSize - paddingBottom; bWidth = elemWidth + borderLeftSize + borderRightSize + paddingLeft + paddingRight; bHeight = borderBottomSize; drawRectanglePDFBox(borderBottomSize, getBorderColor(elem, "bottom"), bX, bY, bWidth, bHeight); } } catch (Exception e) { e.printStackTrace(); return -1; } } return 0; } /** * Draws colored background to OUTPUT * * @returns 0 for inserted OK, -1 for exception occurs and 1 for element completely out of page */ private int drawBgToElem(ElementBox elem, int i, float plusOffset, float plusHeight) { // checks if any color available if (elem.getBgcolor() == null) return 0; // for root element the background color will be painted to background of whole page if (elem.getParent() == null) return drawBgToWholePagePDFBox(elem.getBgcolor()); // calculates the start and the end of current page float pageStart = i * pageFormat.getHeight(); float pageEnd = (i + 1) * pageFormat.getHeight(); // checks if the element if completely out of page if (elem.getAbsoluteContentY() * resCoef + plusOffset > pageEnd || (elem.getAbsoluteContentY() + elem.getContentHeight()) * resCoef + plusOffset + plusHeight < pageStart) return 1; // calculates the padding float paddingTop = elem.getPadding().top * resCoef; float paddingRight = elem.getPadding().right * resCoef; float paddingBottom = elem.getPadding().bottom * resCoef; float paddingLeft = elem.getPadding().left * resCoef; try { float border_x = elem.getAbsoluteContentX() * resCoef - paddingLeft; float border_y = pageFormat.getHeight() - (elem.getAbsoluteContentY() * resCoef + plusOffset) + i * pageFormat.getHeight() - elem.getContentHeight() * resCoef - plusHeight - paddingBottom; drawRectanglePDFBox(0, elem.getBgcolor(), border_x, border_y, (elem.getContentWidth()) * resCoef + paddingLeft + paddingRight, elem.getContentHeight() * resCoef + paddingTop + paddingBottom + plusHeight); } catch (Exception e) { e.printStackTrace(); return -1; } return 0; } /** * Draws text to OUTPUT * * @returns 0 for inserted ok, -1 for exception occures and 1 for text out of page */ private int insertText(TextBox text, int i, float plusOffset, float plusHeight) { // counts the distance between top of the document and the start/end of the page float pageStart = i * pageFormat.getHeight(); float pageEnd = (i + 1) * pageFormat.getHeight(); // checks if the whole text is out of the page if (text.getAbsoluteContentY() * resCoef + plusOffset > pageEnd || (text.getAbsoluteContentY() + text.getHeight()) * resCoef + plusOffset < pageStart) return 1; // gets data describing the text VisualContext ctx = text.getVisualContext(); float fontSize = ctx.getFont().getSize() * resCoef; boolean isBold = ctx.getFont().isBold(); boolean isItalic = ctx.getFont().isItalic(); boolean isUnderlined = ctx.getTextDecorationString().equals("underline"); String fontFamily = ctx.getFont().getFamily(); Color color = ctx.getColor(); // if font is not in fontTable we load it PDFont font = null; for (int iter = 0; iter < fontTable.size(); iter++) { if (fontTable.get(iter).fontName.equalsIgnoreCase(fontFamily) && fontTable.get(iter).isItalic == isItalic && fontTable.get(iter).isBold == isBold) font = fontTable.get(iter).loadedFont; } if (font == null) { font = setFont(fontFamily, isItalic, isBold); fontTable.add(new fontTableRecord(fontFamily, isBold, isItalic, font)); } //font.setFontEncoding(new PdfDocEncoding()); //TODO is this useful? String textToInsert = filterUnicode(text.getText()); try { content.setNonStrokingColor(color); float leading = 2f * fontSize; // counts the resized coordinates in CSSBox form float startX = text.getAbsoluteContentX() * resCoef; float startY = (text.getAbsoluteContentY() * resCoef + plusOffset) % pageFormat.getHeight(); // writes to PDF writeTextPDFBox(startX, startY, textToInsert, font, fontSize, isUnderlined, isBold, leading); } catch (Exception e) { e.printStackTrace(); return -1; } return 0; } ///////////////////////////////////////////////////////////////////////// // USES APACHE PDFBOX FUNCTIONS TO CREATE PDF DOCUMENT // // - all parameters are in CSSBox form (y coordinate is distance from top) // - transforms data to Apache PDFBox form and creates PDF document using // Apache PDFBox functions ///////////////////////////////////////////////////////////////////////// /** * Saves the PDF document to disk using PDFBox */ private int saveDocPDFBox() { try { content.close(); doc.save(pathToSave); doc.close(); } catch (Exception e) { e.printStackTrace(); return -1; } return 0; } /** * Creates document witch first page in it using PDFBox */ private int createDocPDFBox() { try { doc = new PDDocument(); page = new PDPage(pageFormat); doc.addPage(page); content = new PDPageContentStream(doc, page); } catch (Exception e) { e.printStackTrace(); return -1; } return 0; } /** * Inserts N pages to PDF document using PDFBox */ private int insertNPagesPDFBox(int pageCount) { for (int i = 1; i < pageCount; i++) { page = new PDPage(pageFormat); doc.addPage(page); } return 0; } /** * Changes recent page using PDFBox */ private int changeRecentPageToPDFBox(int i) { page = (PDPage) doc.getDocumentCatalog().getPages().get(i); try { content.close(); content = new PDPageContentStream(doc, page, true, true); } catch (IOException e) { e.printStackTrace(); return -1; } return 0; } /** * Inserts background to whole recent PDF page using PDFBox */ private int drawBgToWholePagePDFBox(Color bgColor) { try { content.setNonStrokingColor(bgColor); content.addRect(0, 0, pageFormat.getWidth(), pageFormat.getHeight()); content.fill(); } catch (IOException e) { e.printStackTrace(); return -1; } return 0; } /** * Inserts rectangle to recent PDF page using PDFBox */ private int drawRectanglePDFBox(float lineWidth, Color bgColor, float x, float y, float width, float height) { if (bgColor == null) return 1; try { content.setLineWidth(lineWidth); content.setNonStrokingColor(bgColor); content.addRect(x, y, width, height); content.fill(); } catch (IOException e) { e.printStackTrace(); return -1; } return 0; } /** * Inserts image to recent PDF page using PDFBox */ private int insertImagePDFBox(BufferedImage img, float x, float y, float width, float height) { // transform X,Y coordinates to Apache PDFBox format y = pageFormat.getHeight() - height - y; try { //PDXObjectImage ximage = new PDPixelMap(doc, img); //content.drawXObject(ximage, x, y, width, height); PDImageXObject ximage = LosslessFactory.createFromImage(doc, img); content.drawImage(ximage, x, y, width, height); } catch (IOException e) { e.printStackTrace(); return -1; } return 0; } /** * Writes String to recent PDF page using PDFBox */ private int writeTextPDFBox(float x, float y, String textToInsert, PDFont font, float fontSize, boolean isUnderlined, boolean isBold, float leading) { // transform X,Y coordinates to Apache PDFBox format y = pageFormat.getHeight() - y - leading * resCoef; try { content.beginText(); content.setFont(font, fontSize); content.newLineAtOffset(x, y); try { content.showText(textToInsert); } catch (IllegalArgumentException e) { // NOTE: seems to happen for embedded icon fonts like glyphicons and fa, add space so there is some text otherwise PDFBox throws IllegalStateException: subset is empty; these work with SVGRenderer content.showText(" "); System.err.println("Error: " + e.getMessage()); } content.endText(); // underlines text if text is set underlined if (isUnderlined) { content.setLineWidth(1); float strokeWidth = font.getStringWidth(textToInsert) / 1000 * fontSize; float lineHeightCalibration = 1f; float yOffset = fontSize / 6.4f; if (isBold) { lineHeightCalibration = 1.5f; yOffset = fontSize / 5.7f; } content.addRect(x, y - yOffset, strokeWidth, resCoef * lineHeightCalibration); content.fill(); } } catch (IOException e) { e.printStackTrace(); return -1; } return 0; } /** * Creates object describing font * @return the font object */ private PDFont setFont(String fontFamily, boolean isItalic, boolean isBold) { PDFont font = loadTTF(fontFamily, isItalic, isBold); //try some fallbacks when not found if (font == null) font = tryTTFFallback(fontFamily, isItalic, isBold); if (font == null) font = tryBuiltinFallback(fontFamily, isItalic, isBold); return font; } private PDFont tryBuiltinFallback(String fontFamily, boolean isItalic, boolean isBold) { PDFont font; fontFamily = fontFamily.toLowerCase(); switch (fontFamily) { case "courier": case "courier new": case "lucida console": if (isBold && isItalic) { font = PDType1Font.COURIER_BOLD_OBLIQUE; } else if (isBold) { font = PDType1Font.COURIER_BOLD; } else if (isItalic) { font = PDType1Font.COURIER_OBLIQUE; } else { font = PDType1Font.COURIER; } break; case "times": case "garamond": case "georgia": case "times new roman": case "serif": if (isBold && isItalic) { font = PDType1Font.TIMES_BOLD_ITALIC; } else if (isBold) { font = PDType1Font.TIMES_BOLD; } else if (isItalic) { font = PDType1Font.TIMES_ITALIC; } else { font = PDType1Font.TIMES_ROMAN; } break; default: if (isBold && isItalic) { font = PDType1Font.HELVETICA_BOLD_OBLIQUE; } else if (isBold) { font = PDType1Font.HELVETICA_BOLD; } else if (isItalic) { font = PDType1Font.HELVETICA_OBLIQUE; } else { font = PDType1Font.HELVETICA; } break; } return font; } private PDFont tryTTFFallback(String fontFamily, boolean isItalic, boolean isBold) { fontFamily = fontFamily.toLowerCase(); switch (fontFamily) { case "courier": case "courier new": case "lucida console": case "monotype": return loadTTFAlternatives(new String[] { "freemono", "DejaVuSansMono" }, isItalic, isBold); case "times": case "garamond": case "georgia": case "times new roman": case "serif": return loadTTFAlternatives(new String[] { "freeserif" }, isItalic, isBold); default: return loadTTFAlternatives(new String[] { "freesans" }, isItalic, isBold); } } /** * Tries to load a font from the system database. * @param fontFamily * @param isItalic * @param isBold * @return the font or {@code null} when not found */ private PDFont loadTTF(String fontFamily, boolean isItalic, boolean isBold) { PDFont font = null; try { URI uri = fontDB.findFontURI(fontFamily, isBold, isItalic); if (uri != null) font = PDType0Font.load(doc, new File(uri)); } catch (IOException e) { font = null; } return font; } private PDFont loadTTFAlternatives(String[] fontFamilies, boolean isItalic, boolean isBold) { for (String fontFamily : fontFamilies) { PDFont font = loadTTF(fontFamily, isItalic, isBold); if (font != null) return font; } return null; } ///////////////////////////////////////////////////////////////////// // OTHER FUNCTIONS ///////////////////////////////////////////////////////////////////// /** * Returns color of border */ private Color getBorderColor(ElementBox elem, String side) { Color clr = null; // gets the color value from CSS property CSSProperty.BorderColor bclr = elem.getStyle().getProperty("border-" + side + "-color"); TermColor tclr = elem.getStyle().getValue(TermColor.class, "border-" + side + "-color"); CSSProperty.BorderStyle bst = elem.getStyle().getProperty("border-" + side + "-style"); if (bst != CSSProperty.BorderStyle.HIDDEN && bclr != CSSProperty.BorderColor.TRANSPARENT) { if (tclr != null) clr = tclr.getValue(); if (clr == null) { clr = elem.getVisualContext().getColor(); if (clr == null) clr = Color.BLACK; } } else { clr = elem.getBgcolor(); } return clr; } private String filterUnicode(String text) { //replace nbsp with spaces text = text.replace('\u00A0', ' '); text = text.replace('\u00AD', '-'); if (replaceUnicode) { // removes diacritics text = text.replace("\u010f", "d"); text = text.replace("\u010e", "D"); text = text.replace("\u011b", "e"); text = text.replace("\u011a", "E"); text = text.replace("\u0161", "s"); text = text.replace("\u0160", "S"); text = text.replace("\u010d", "c"); text = text.replace("\u010c", "C"); text = text.replace("\u0159", "r"); text = text.replace("\u0158", "R"); text = text.replace("\u017e", "z"); text = text.replace("\u017d", "Z"); text = text.replace("\u016F", "u"); text = text.replace("\u016E", "U"); text = text.replace("\u0148", "n"); text = text.replace("\u0147", "N"); text = text.replace("\u0165", "t"); text = text.replace("\u0164", "T"); text = text.replace("\u2013", "\u002D"); // replaces apostrophe with equivalent text = text.replace("\u2018", "\'"); text = text.replace("\u2019", "\'"); text = text.replace("\u201a", "\'"); text = text.replace("\u201b", "\'"); // replaces quote-marks with equivalent text = text.replace("\u201c", "\""); text = text.replace("\u201d", "\""); text = text.replace("\u201e", "\""); text = text.replace("\u201f", "\""); // replaces three-dots-mark with three dots text = text.replace("\u2026", "..."); // replaces vertical line with equivalent text = text.replace("\u2502", "\u007C"); // replaces em dash with dash text = text.replace("\u2014", "-"); // less then, more than text = text.replace("\u2039", "<"); text = text.replace("\u203A", ">"); // replaces bullet point with alternative text = text.replace("\u2022", "\u00B7"); // removes slovak diacritics text = text.replace("\u013E", "l"); text = text.replace("\u013D", "L"); text = text.replace("\u013A", "l"); text = text.replace("\u0139", "L"); text = text.replace("\u0151", "o"); text = text.replace("\u0150", "O"); text = text.replace("\u0155", "r"); text = text.replace("\u0154", "R"); text = text.replace("\u0171", "u"); text = text.replace("\u0170", "U"); } return text; } }