source: trunk/src/org/expeditee/io/WebParser.java@ 767

Last change on this file since 767 was 767, checked in by ngw8, 10 years ago

Updates to new webparser/converter - now gives a tidier, more usable conversion. A lot of the code is copied from the old parser, so still need to go through and make it more efficient (and tidier) for the new approach.

File size: 48.1 KB
Line 
1package org.expeditee.io;
2
3import java.awt.Color;
4import java.awt.Font;
5import java.awt.Graphics;
6import java.awt.image.BufferedImage;
7import java.io.File;
8import java.io.IOException;
9import java.lang.reflect.InvocationTargetException;
10import java.net.HttpURLConnection;
11import java.net.MalformedURLException;
12import java.net.URL;
13import java.text.SimpleDateFormat;
14import java.util.Arrays;
15
16/*
17 * JavaFX is not on the default java classpath until Java 8 (but is still included with Java 7), so your IDE will probably complain that the imports below can't be resolved.
18 * In Eclipse hitting'Proceed' when told 'Errors exist in project' should allow you to run Expeditee without any issues (although the JFX Browser widget will not display),
19 * or you can just exclude JfxBrowser, WebParser and JfxbrowserActions from the build path.
20 *
21 * If you are using Ant to build/run, 'ant build' will try to build with JavaFX jar added to the classpath.
22 * If this fails, 'ant build-nojfx' will build with the JfxBrowser, WebParser and JfxbrowserActions excluded from the build path.
23 */
24
25
26import java.util.Date;
27
28import javafx.animation.AnimationTimer;
29import javafx.application.Platform;
30import javafx.beans.value.ChangeListener;
31import javafx.beans.value.ObservableValue;
32import javafx.concurrent.Worker.State;
33import javafx.scene.transform.Rotate;
34import javafx.scene.web.WebEngine;
35
36import javax.imageio.ImageIO;
37import javax.swing.JComponent;
38
39import netscape.javascript.JSObject;
40
41import org.expeditee.gui.DisplayIO;
42import org.expeditee.gui.Frame;
43import org.expeditee.gui.FrameCreator;
44import org.expeditee.gui.FrameGraphics;
45import org.expeditee.gui.FrameIO;
46import org.expeditee.gui.FrameUtils;
47import org.expeditee.gui.MessageBay;
48import org.expeditee.gui.MessageBay.Progress;
49import org.expeditee.importer.FrameDNDTransferHandler;
50import org.expeditee.items.ItemUtils;
51import org.expeditee.items.Justification;
52import org.expeditee.items.Picture;
53import org.expeditee.items.Text;
54import org.w3c.dom.Node;
55import org.w3c.dom.html.HTMLBodyElement;
56
57import com.sun.org.apache.bcel.internal.generic.NEW;
58
59/**
60 * Methods to convert webpages to Expeditee frames
61 *
62 * @author ngw8
63 * @author jts21
64 */
65public class WebParser {
66
67
68 /**
69 * Loads a webpage and renders it as Expeditee frame(s)
70 *
71 * @param URL
72 * Page to load
73 * @param frame
74 * The Expeditee frame to output the converted page to
75 */
76 public static void parseURL(final String URL, final Frame frame) {
77 try {
78 Platform.runLater(new Runnable() {
79 @Override
80 public void run() {
81 try {
82 WebEngine webEngine = new WebEngine(URL);
83 loadPage(webEngine, frame);
84 } catch (Exception e) {
85 e.printStackTrace();
86 }
87 }
88 });
89 } catch (Exception e) {
90 e.printStackTrace();
91 }
92 }
93
94 protected static void loadPage(final WebEngine webEngine, final Frame frame) throws Exception {
95 webEngine.getLoadWorker().stateProperty().addListener(new ChangeListener<State>() {
96
97 @Override
98 public void changed(ObservableValue<? extends State> ov, State oldState, State newState) {
99
100 switch (newState) {
101 case READY: // READY
102 // MessageBay.displayMessage("WebEngine ready");
103 break;
104 case SCHEDULED: // SCHEDULED
105 // MessageBay.displayMessage("Scheduled page load");
106 break;
107 case RUNNING: // RUNNING
108 System.out.println("Loading page!");
109 // MessageBay.displayMessage("WebEngine running");
110 break;
111 case SUCCEEDED: // SUCCEEDED
112 // MessageBay.displayMessage("Finished loading page");
113 System.out.println("Parsing page!");
114 webEngine.executeScript("window.resizeTo(800, 800);"
115 + "document.body.style.width = '1000px'");
116 parsePage(webEngine, frame);
117 System.out.println("Parsed page!");
118 break;
119 case CANCELLED: // CANCELLED
120 MessageBay.displayMessage("Cancelled loading page");
121 break;
122 case FAILED: // FAILED
123 MessageBay.displayMessage("Failed to load page");
124 break;
125 }
126 }
127 });
128 }
129
130 /**
131 * Converts a loaded page to Expeditee frame(s)
132 *
133 * @param webEngine
134 * The JavaFX WebEngine in which the page to be converted is loaded
135 * @param frame
136 * The Expeditee frame to output the converted page to
137 */
138 public static void parsePage(final WebEngine webEngine, final Frame frame) {
139 try {
140 Platform.runLater(new Runnable() {
141 @Override
142 public void run() {
143 try {
144 Progress progressBar = MessageBay.displayProgress("Converting web page");
145
146 Node doc = (Node) webEngine.executeScript("document.body");
147
148 JSObject window = (JSObject) webEngine.executeScript("window");
149
150 frame.setBackgroundColor(rgbStringToColor((String) ((JSObject) (window.call("getComputedStyle", new Object[] { doc }))).call("getPropertyValue",
151 new Object[] { "background-color" })));
152
153 // Functions to be used later in JavaScript
154 webEngine.executeScript(""
155 + "function addToSpan(text) {"
156 + " span = document.createElement('wordSpan');"
157 + " span.textContent = text;"
158 + " par.insertBefore(span, refNode);"
159 // Checking if the current word is on a new line (i.e. lower than the previous word)
160 + " if (prevSpan !== null && span.getBoundingClientRect().top > prevSpan.getBoundingClientRect().top) {"
161 // If it is, prepend a new line character to it. The new line characters doesn't affect the rendered HTML
162 + " span.textContent = '\\n' + span.textContent;"
163
164 // Checking if the previous word is horizontally aligned with the one before it.
165 // If it is, merge the text of the two spans
166 + " if ( prevPrevSpan !== null && prevPrevSpan.getBoundingClientRect().left == prevSpan.getBoundingClientRect().left) {"
167 + " prevPrevSpan.textContent = prevPrevSpan.textContent + prevSpan.textContent;"
168 + " par.removeChild(prevSpan);"
169 + " } else {"
170 + " prevPrevSpan = prevSpan;"
171 + " }"
172 + " prevSpan = span;"
173 + " } else if ( prevSpan !== null) {"
174 // Word is on the same line as the previous one, so merge the second into the span of the first
175 + " prevSpan.textContent = prevSpan.textContent + span.textContent;"
176 + " par.removeChild(span);"
177 + " } else {"
178 + " prevSpan = span;"
179 + " }"
180 + "}"
181
182 + "function splitIntoWords(toSplit) {"
183 + " var words = [];"
184 + " var pattern = /\\s+/g;"
185 + " var words = toSplit.split(pattern);"
186 + ""
187 + " for (var i = 0; i < words.length - 1; i++) {"
188 + " words[i] = words[i] + ' ';"
189 + " }"
190 + " return words;"
191 + "}"
192 );
193
194 // Using Javascript to get an array of all the text nodes in the document so they can be wrapped in spans. Have to
195 // loop through twice (once to build the array and once actually going through the array, otherwise when the
196 // textnode is removed from the document items end up being skipped)
197 JSObject textNodes = (JSObject) webEngine.executeScript(""
198 + "function getTextNodes(rootNode){"
199 + "var node;"
200 + "var textNodes=[];"
201 + "var walk = document.createTreeWalker(rootNode, NodeFilter.SHOW_TEXT);"
202 + "while(node=walk.nextNode()) {"
203 + "if((node.textContent.trim().length > 0)) { "
204 + "textNodes.push(node);"
205 + "}"
206 + "}"
207 + "return textNodes;"
208 + "}; "
209 + "getTextNodes(document.body)"
210 );
211
212 int nodesLength = (Integer) textNodes.getMember("length");
213
214 // Looping through all the text nodes in the document
215 for (int j = 0; j < nodesLength; j++) {
216 Node currentNode = (Node) textNodes.getSlot(j);
217
218 // Making the current node accessible in JavaScript
219 window.setMember("currentNode", currentNode);
220
221 webEngine.executeScript(""
222 + "var span = null, prevSpan = null, prevPrevSpan = null;"
223
224 // Removing repeated whitespace from the text node's content then splitting it into individual words
225 + "var textContent = currentNode.textContent.replace(/\\n|\\r/g, '').replace(/\\s+/g, ' ');"
226 + "var words = splitIntoWords(textContent);"
227
228 + "var refNode = currentNode.nextSibling;"
229 + "var par = currentNode.parentElement;"
230 + "currentNode.parentElement.removeChild(currentNode);"
231
232 + "for (var i = 0; i < words.length; i++) {"
233 + " addToSpan(words[i]);"
234 + "}"
235
236 + "if (prevPrevSpan !== null && prevPrevSpan.getBoundingClientRect().left == prevSpan.getBoundingClientRect().left) {"
237 + " prevPrevSpan.textContent = prevPrevSpan.textContent + prevSpan.textContent;"
238 + " par.removeChild(prevSpan);"
239 + "}"
240 );
241
242 // Will never reach 100% here, as the processing is not quite finished - progress is set to 100% at the end of
243 // the addPageToFrame loop below
244 progressBar.set((100 * (j)) / nodesLength);
245 }
246
247 // Finding all links within the page, then setting the href attribute of all their descendants to be the same
248 // link/URL.
249 // This is needed because there is no apparent and efficient way to check if an element is a child of a link when
250 // running through the document when added each element to Expeditee
251 webEngine.executeScript(""
252 + "var anchors = document.getElementsByTagName('a');"
253 + ""
254 + "for (var i = 0; i < anchors.length; i++) {"
255 + "var currentAnchor = anchors.item(i);"
256 + "var anchorDescendants = currentAnchor.querySelectorAll('*');"
257 + "for (var j = 0; j < anchorDescendants.length; j++) {"
258 + "anchorDescendants.item(j).href = currentAnchor.href;"
259 + "}"
260 + "}"
261 );
262
263 WebParser.addPageToFrame(doc, window, webEngine, frame);
264
265 progressBar.set(100);
266
267 } catch (Exception e) {
268 e.printStackTrace();
269 }
270 System.out.println("Parsed frame");
271 FrameUtils.Parse(frame);
272 frame.setChanged(true);
273 FrameIO.SaveFrame(frame);
274 }
275 });
276 } catch (Exception e) {
277 e.printStackTrace();
278 }
279 }
280
281 /**
282 * Converts a loaded page to Expeditee frame(s)
283 *
284 * @param webEngine
285 * The JavaFX WebEngine in which the page to be converted is loaded
286 * @param frame
287 * The Expeditee frame to output the converted page to
288 */
289 public static void parsePageSimple(final WebEngine webEngine, final Object webView, final JComponent jfxPanel, final Frame frame) {
290 try {
291 final Object notifier = new Object();
292 final MutableBool bottomReached = new MutableBool(false);
293
294 String pageTitle = webEngine.getTitle();
295
296 String framesetName = FrameIO.ConvertToValidFramesetName((new SimpleDateFormat("yy-MM-dd-HH-mm-ss").format(new Date())) + pageTitle);
297
298 final Progress progressBar = MessageBay.displayProgress("Converting web page");
299
300 final Frame frameset = FrameIO.CreateNewFrameset(framesetName);
301
302 frameset.setTitle(pageTitle);
303 frameset.getTitleItem().setSize(14);
304
305 Text link = new Text(DisplayIO.getCurrentFrame().getNextItemID(), webEngine.getTitle());
306
307 link.setPosition(100, 100);
308
309 DisplayIO.getCurrentFrame().addItem(link);
310
311 link.setLink(framesetName + "1");
312
313 // Timer that fires every time JFX is redrawn. After a few redraws, the handle method of this takes a screenshot of the page,
314 // adds it to the frame, then adds the text on top
315 AnimationTimer timer = new AnimationTimer() {
316
317 int frameCount = 0;
318 Frame frameToAddTo = frameset;
319
320 @Override
321 public void handle(long arg0) {
322 System.out.println(frameCount);
323 // Must wait 2 frames before taking a snapshot of the webview, otherwise JavaFX won't have redrawn
324 if (frameCount++ > 3) {
325 frameCount = 0;
326 this.stop();
327
328 BufferedImage image = new BufferedImage(jfxPanel.getWidth(), jfxPanel.getHeight(), BufferedImage.TYPE_INT_ARGB);
329
330 Graphics graphics = image.createGraphics();
331
332 // Drawing the JfxPanel (containing the webview) to the image
333 jfxPanel.paint(graphics);
334
335 try {
336 frameToAddTo = FrameIO.CreateFrame(frameToAddTo.getFramesetName(), null, null);
337
338 int hashcode = Arrays.hashCode(image.getData().getPixels(0, 0, image.getWidth(), image.getHeight(), (int[]) null));
339
340 File out = new File(FrameIO.IMAGES_PATH + "webpage-" + Integer.toHexString(hashcode) + ".png");
341 out.mkdirs();
342 ImageIO.write(image, "png", out);
343
344 // Adding the image
345 frameToAddTo.addText(0, 0, "@i: " + out.getName(), null);
346
347 Text thumb = frameset.addText(100, 100, "@i: " + out.getName() + " " + 200, null);
348 thumb.setLink(frameToAddTo.getName());
349
350 // Button to go to the next page
351 Text nextButton = (Text) FrameCreator.createButton("Next", null, null, 10F, 10F);
352 nextButton.setID(frameToAddTo.getNextItemID());
353 nextButton.addAction("next");
354 frameToAddTo.addItem(nextButton);
355
356 FrameIO.SaveFrame(frameToAddTo);
357 FrameIO.SaveFrame(frameset);
358
359 System.out.println("Screenshot taken and added " + frameToAddTo.getName());
360
361 } catch (IOException e) {
362 e.printStackTrace();
363 }
364
365 graphics.dispose();
366 image.flush();
367
368 try {
369 Platform.runLater(new Runnable() {
370 @Override
371 public void run() {
372 try {
373 HTMLBodyElement doc = (HTMLBodyElement) webEngine.executeScript("document.body");
374
375 JSObject window = (JSObject) webEngine.executeScript("window");
376
377 int visibleWidth = (int) webEngine.executeScript("window.innerWidth");
378 int visibleHeight = (int) webEngine.executeScript("window.innerHeight");
379
380 System.out.println("Adding text " + frameToAddTo.getName());
381 WebParser.addTextToFrame(doc, visibleWidth, visibleHeight, window, webEngine, frameToAddTo);
382 FrameIO.SaveFrame(frameToAddTo);
383 System.out.println("Added text " + frameToAddTo.getName());
384 } catch (Exception ex) {
385 ex.printStackTrace();
386 }
387
388 synchronized (notifier) {
389 notifier.notify();
390 }
391 }
392 });
393 } catch (Exception ex) {
394 ex.printStackTrace();
395 }
396 }
397 }
398 };
399
400 Platform.runLater(new Runnable() {
401 @Override
402 public void run() {
403 try {
404 webEngine.executeScript(""
405 // Initializing the counter used when scrolling the page
406 + "var scrollCounter = 0;"
407
408 // Setting all text to be hidden
409 + "var css = document.createElement('style');"
410 + "css.type = 'text/css';"
411 + "var style = 'WordSpan { visibility: hidden }';"
412 + "css.appendChild(document.createTextNode(style));"
413 + "document.getElementsByTagName('head')[0].appendChild(css);");
414
415 HTMLBodyElement doc = (HTMLBodyElement) webEngine.executeScript("document.body");
416
417 JSObject window = (JSObject) webEngine.executeScript("window");
418
419 frame.setBackgroundColor(rgbStringToColor((String) ((JSObject) (window.call("getComputedStyle", new Object[] { doc }))).call("getPropertyValue",
420 new Object[] { "background-color" })));
421
422 // Functions to be used later in JavaScript
423 webEngine.executeScript(""
424 + "function addToSpan(text) {"
425 + " span = document.createElement('wordSpan');"
426 + " span.textContent = text;"
427 + " par.insertBefore(span, refNode);"
428 + " if (prevSpan !== null && span.getBoundingClientRect().top > prevSpan.getBoundingClientRect().top) {"
429 + " span.textContent = '\\n' + span.textContent;"
430 + " if ( prevPrevSpan !== null && prevPrevSpan.getBoundingClientRect().left == prevSpan.getBoundingClientRect().left) {"
431 + " prevPrevSpan.textContent = prevPrevSpan.textContent + prevSpan.textContent;"
432 + " par.removeChild(prevSpan);"
433 + " } else {"
434 + " prevPrevSpan = prevSpan;"
435 + " }"
436 + " prevSpan = span;"
437 + " } else if ( prevSpan !== null) {"
438 + " prevSpan.textContent = prevSpan.textContent + span.textContent;"
439 + " par.removeChild(span);"
440 + " } else {"
441 + " prevSpan = span;"
442 + " }"
443 + "}"
444
445 + "function splitIntoWords(toSplit) {"
446 + " var words = [];"
447 + " var pattern = /\\s+/g;"
448 + " var words = toSplit.split(pattern);"
449 + ""
450 + " for (var i = 0; i < words.length - 1; i++) {"
451 + " words[i] = words[i] + ' ';"
452 + " }"
453 + " return words;"
454 + "}"
455 );
456
457 // Using Javascript to get an array of all the text nodes in the document so they can be wrapped in spans. Have to
458 // loop through twice (once to build the array and once actually going through the array, otherwise when the
459 // textnode is removed from the document items end up being skipped)
460 JSObject textNodes = (JSObject) webEngine.executeScript(""
461 + "function getTextNodes(rootNode){"
462 + "var node;"
463 + "var textNodes=[];"
464 + "var walk = document.createTreeWalker(rootNode, NodeFilter.SHOW_TEXT);"
465 + "while(node=walk.nextNode()) {"
466 + "if((node.textContent.trim().length > 0)) { "
467 + "textNodes.push(node);"
468 + "}"
469 + "}"
470 + "return textNodes;"
471 + "}; "
472 + "getTextNodes(document.body)"
473 );
474
475 int nodesLength = (Integer) textNodes.getMember("length");
476
477 // Looping through all the text nodes in the document
478 for (int j = 0; j < nodesLength; j++) {
479 Node currentNode = (Node) textNodes.getSlot(j);
480
481 // Making the current node accessible in JavaScript
482 window.setMember("currentNode", currentNode);
483
484 webEngine.executeScript(""
485 + "var span = null, prevSpan = null, prevPrevSpan = null;"
486
487 // Removing repeated whitespace from the text node's content then splitting it into individual words
488 + "var textContent = currentNode.textContent.replace(/\\n|\\r/g, '').replace(/\\s+/g, ' ');"
489 + "var words = splitIntoWords(textContent);"
490
491 + "var refNode = currentNode.nextSibling;"
492 + "var par = currentNode.parentElement;"
493 + "currentNode.parentElement.removeChild(currentNode);"
494
495 + "for (var i = 0; i < words.length; i++) {"
496 + " addToSpan(words[i]);"
497 + "}"
498
499 + "if (prevPrevSpan !== null && prevPrevSpan.getBoundingClientRect().left == prevSpan.getBoundingClientRect().left) {"
500 + " prevPrevSpan.textContent = prevPrevSpan.textContent + prevSpan.textContent;"
501 + " par.removeChild(prevSpan);"
502 + "}"
503 );
504
505 // Will never reach 100% here, as the processing is not quite finished - progress is set to 100% at the end of
506 // the addPageToFrame loop below
507 progressBar.set((100 * (j)) / nodesLength);
508 }
509
510 // Finding all links within the page, then setting the href attribute of all their descendants to be the same
511 // link/URL.
512 // This is needed because there is no apparent and efficient way to check if an element is a child of a link when
513 // running through the document when added each element to Expeditee
514 webEngine.executeScript(""
515 + "var anchors = document.getElementsByTagName('a');"
516 + ""
517 + "for (var i = 0; i < anchors.length; i++) {"
518 + "var currentAnchor = anchors.item(i);"
519 + "var anchorDescendants = currentAnchor.querySelectorAll('*');"
520 + "for (var j = 0; j < anchorDescendants.length; j++) {"
521 + "anchorDescendants.item(j).href = currentAnchor.href;"
522 + "}"
523 + "}"
524 );
525
526 } catch (Exception ex) {
527 ex.printStackTrace();
528 }
529
530 synchronized (notifier) {
531 notifier.notify();
532 }
533 }
534 });
535
536 synchronized (notifier) {
537 try {
538 // Waiting for the JavaFX thread to finish
539 notifier.wait();
540 } catch (InterruptedException e) {
541 // TODO Auto-generated catch block
542 e.printStackTrace();
543 }
544 }
545
546 while (!bottomReached.getValue()) {
547 Platform.runLater(new Runnable() {
548 @Override
549 public void run() {
550 try {
551 // Scrolling down the page
552 webEngine.executeScript(""
553 + "window.scrollTo(0, scrollCounter * 0.9 * window.innerHeight);"
554 + "scrollCounter = scrollCounter+1;");
555
556 System.out.println("Scrolling");
557
558 bottomReached.setValue((Boolean) webEngine.executeScript("(window.pageYOffset + window.innerHeight >= document.documentElement.scrollHeight)"));
559
560 synchronized (notifier) {
561 notifier.notify();
562 }
563
564 } catch (Exception e) {
565 e.printStackTrace();
566 }
567 }
568 });
569
570 synchronized (notifier) {
571 try {
572 // Waiting for the JavaFX thread to finish
573 notifier.wait();
574 } catch (InterruptedException e) {
575 // TODO Auto-generated catch block
576 e.printStackTrace();
577 }
578 }
579
580 timer.start();
581
582 synchronized (notifier) {
583 try {
584 // Waiting for the timer thread to finish before looping again
585 notifier.wait();
586 } catch (InterruptedException e) {
587 // TODO Auto-generated catch block
588 e.printStackTrace();
589 }
590 }
591
592 }
593
594 } catch (Exception ex) {
595 ex.printStackTrace();
596 }
597
598
599
600 }
601
602 /**
603 * @param rgbString
604 * string in the format <i>rgb(x,x,x)</i> or <i>rgba(x,x,x,x)</i>
605 * @return A Color object that should match the rgb string passed int. Returns null if alpha is 0
606 */
607 private static Color rgbStringToColor(String rgbString) {
608
609 if (rgbString == null) {
610 return null;
611 }
612
613 // Splitting the string into 'rgb' and 'x, x, x'
614 String[] tmpStrings = rgbString.split("\\(|\\)");
615
616 // Splitting up the RGB(A) components into an array
617 tmpStrings = tmpStrings[1].split(",");
618
619 int[] components = new int[4];
620 Arrays.fill(components, 255);
621
622 for (int i = 0; i < tmpStrings.length; i++) {
623 Float d = Float.parseFloat(tmpStrings[i].trim());
624
625 components[i] = Math.round(d);
626 }
627
628 if (components[3] > 0) {
629 return new Color(components[0], components[1], components[2], components[3]);
630 } else {
631 return null;
632 }
633 }
634
635 /**
636 * @param rootElement
637 * Element that will be converted (including all sub-elements)
638 * @param backgroundColor
639 * String to be used as the background color of this element when added. In the format "rgb(x,x,x)" or "rgba(x,x,x,x)"
640 * @param window
641 * 'window' from Javascript
642 * @param webEngine
643 * Web engine that the page is loaded in
644 * @param frame
645 * Expeditee frame to add the converted page to
646 * @throws IllegalArgumentException
647 * @throws IllegalAccessException
648 */
649 private static void addPageToFrame(Node rootElement, JSObject window, WebEngine webEngine, Frame frame) throws InvocationTargetException, IllegalAccessException,
650 IllegalArgumentException {
651
652 Node currentNode = rootElement;
653
654 if (currentNode.getNodeType() == Node.TEXT_NODE || currentNode.getNodeType() == Node.ELEMENT_NODE) {
655
656 JSObject style;
657 JSObject bounds;
658
659 if (currentNode.getNodeType() == Node.TEXT_NODE) {
660 // CSS style for the element
661 style = (JSObject) window.call("getComputedStyle", new Object[] { currentNode.getParentNode() });
662
663 // Getting a rectangle that represents the area and position of the element
664 bounds = (JSObject) ((JSObject) currentNode.getParentNode()).call("getBoundingClientRect", new Object[] {});
665 } else {
666 style = (JSObject) window.call("getComputedStyle", new Object[] { currentNode });
667
668 bounds = (JSObject) ((JSObject) currentNode).call("getBoundingClientRect", new Object[] {});
669 }
670
671 // Bounding rectangle position is relative to the current view, so scroll position must be added to x/y
672 // TODO: This doesn't check if an element or any of its parent elements have position:fixed set - the only
673 // way to check seems to be to walking through the element's parents until the document root is reached
674 float x = Float.valueOf(bounds.getMember("left").toString()) + Float.valueOf(webEngine.executeScript("window.pageXOffset").toString());
675 float y = Float.valueOf(bounds.getMember("top").toString()) + Float.valueOf(webEngine.executeScript("window.pageYOffset").toString());
676
677 float width = Float.valueOf(bounds.getMember("width").toString());
678 float height = Float.valueOf(bounds.getMember("height").toString());
679
680 // Checking if the element is actually visible on the page
681 if (WebParser.elementVisible(x, y, width, height, style)) {
682
683 // Filtering the node type, starting with text nodes
684 if (currentNode.getNodeType() == Node.TEXT_NODE) {
685
686 String fontSize = ((String) style.call("getPropertyValue", new Object[] { "font-size" }));
687
688 // Trimming off the units (always px) from the font size
689 fontSize = fontSize.substring(0, fontSize.length() - 2);
690
691 // Always returns in format "rgb(x,x,x)" or "rgba(x,x,x,x)"
692 String color = (String) style.call("getPropertyValue", new Object[] { "color" });
693
694 // Always returns in format "rgb(x,x,x)" or "rgba(x,x,x,x)"
695 String bgColorString = (String) style.call("getPropertyValue", new Object[] { "background-color" });
696
697 String align = (String) style.call("getPropertyValue", new Object[] { "text-align" });
698
699 // Returns comma-separated list of typefaces
700 String typeface = (String) style.call("getPropertyValue", new Object[] { "font-family" });
701
702 String[] typefaces = typeface.split(", |,");
703
704 String weight = (String) style.call("getPropertyValue", new Object[] { "font-weight" });
705
706 String fontStyle = (String) style.call("getPropertyValue", new Object[] { "font-style" });
707
708 // Returns "normal" or a value in pixels (e.g. "10px")
709 String letterSpacing = (String) style.call("getPropertyValue", new Object[] { "letter-spacing" });
710
711 // Returns a value in pixels (e.g. "10px")
712 String lineHeight = (String) style.call("getPropertyValue", new Object[] { "line-height" });
713
714 String textTransform = (String) style.call("getPropertyValue", new Object[] { "text-transform" });
715
716 String linkUrl = (String) ((JSObject) currentNode.getParentNode()).getMember("href");
717
718 Boolean fontFound = false;
719 Font font = new Font(null);
720
721 // Looping through all font-families listed in the element's CSS until one that is installed is
722 // found, or the end of the list is reached, in which case the default font is used
723 for (int j = 0; j < typefaces.length && !fontFound; j++) {
724 if (typefaces[j].toLowerCase().equals("sans-serif")) {
725 typefaces[j] = "Arial Unicode MS";
726 } else if (typefaces[j].toLowerCase().equals("serif")) {
727 typefaces[j] = "Times New Roman";
728 } else if ((typefaces[j].toLowerCase().equals("arial"))) {
729 // Have to use Arial Unicode, otherwise unicode characters display incorrectly
730 typefaces[j] = "Arial Unicode MS";
731 }
732
733 // Regex will remove any inverted commas surrounding multi-word typeface names
734 font = new Font(typefaces[j].replaceAll("^'|'$", ""), Font.PLAIN, 12);
735
736 // If the font isn't found, Java just uses Font.DIALOG, so this check checks whether the font was found
737 if (!(font.getFamily().toLowerCase().equals(Font.DIALOG.toLowerCase()))) {
738 fontFound = true;
739 }
740 }
741
742 if (font.getFamily().toLowerCase().equals(Font.DIALOG.toLowerCase())) {
743 font = new Font("Times New Roman", Font.PLAIN, 12);
744 }
745
746 String fontStyleComplete = "";
747
748 int weightInt = 0;
749
750 try {
751 weightInt = Integer.parseInt(weight);
752 } catch (NumberFormatException nfe) {
753 // Use default value as set above
754 }
755
756 // checking if font is bold - i.e. 'bold', 'bolder' or weight over 500
757 if (weight.toLowerCase().startsWith("bold") || weightInt > 500) {
758 fontStyleComplete = fontStyleComplete.concat("bold");
759 }
760
761 if (fontStyle.toLowerCase().equals("italic") || fontStyle.toLowerCase().equals("oblique")) {
762 fontStyleComplete = fontStyleComplete.concat("italic");
763 }
764
765 float fontSizeFloat = 12;
766
767 try {
768 fontSizeFloat = Float.valueOf(fontSize);
769 } catch (NumberFormatException nfe) {
770 // Use default value as set above
771 }
772
773 float letterSpacingFloat = -0.008f;
774
775 try {
776 letterSpacingFloat = (Integer.parseInt(letterSpacing.substring(0, letterSpacing.length() - 2)) / (fontSizeFloat));
777 } catch (NumberFormatException nfe) {
778 // Use default value as set above
779 }
780
781 float lineHeightInt = -1;
782
783 try {
784 lineHeightInt = (Float.parseFloat(lineHeight.substring(0, lineHeight.length() - 2)));
785 } catch (NumberFormatException nfe) {
786 // Use default value as set above
787 }
788
789 Text t;
790
791 String textContent = currentNode.getTextContent().replaceAll("[^\\S\\n]+", " ");
792 textContent = textContent.replaceAll("^(\\s)(\\n|\\r)", "");
793
794 if (textTransform.equals("uppercase")) {
795 textContent = textContent.toUpperCase();
796 } else if (textTransform.equals("lowercase")) {
797 textContent = textContent.toUpperCase();
798 }
799
800 // Adding the text to the frame. Expeditee text seems to be positioned relative to the baseline of the first line, so
801 // the font size has to be added to the y-position
802 t = frame.addText(Math.round(x), Math.round(y + fontSizeFloat), textContent, null);
803
804 t.setColor(rgbStringToColor(color));
805 t.setBackgroundColor(rgbStringToColor(bgColorString));
806 t.setFont(font);
807 t.setSize(fontSizeFloat);
808 t.setFontStyle(fontStyleComplete);
809 t.setLetterSpacing(letterSpacingFloat);
810
811 // Removing any spacing between lines allowing t.getLineHeight() to be used to get the actual height
812 // of just the characters (i.e. distance from ascenders to descenders)
813 t.setSpacing(0);
814
815 t.setSpacing(lineHeightInt - t.getLineHeight());
816
817 if (align.equals("left")) {
818 t.setJustification(Justification.left);
819 } else if (align.equals("right")) {
820 t.setJustification(Justification.right);
821 } else if (align.equals("center")) {
822 t.setJustification(Justification.center);
823 } else if (align.equals("justify")) {
824 t.setJustification(Justification.full);
825 }
826
827 // Font size is added to the item width to give a little breathing room
828 t.setWidth(Math.round(width + (t.getSize())));
829
830 if (!linkUrl.equals("undefined")) {
831 t.setAction("gotourl " + linkUrl);
832 t.setActionMark(false);
833 }
834
835 } else if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
836
837 // Always returns in format "rgb(x,x,x)" or "rgba(x,x,x,x)"
838 String bgColorString = (String) style.call("getPropertyValue", new Object[] { "background-color" });
839
840 Color bgColor = rgbStringToColor(bgColorString);
841
842 // If the element has a background color then add it (to Expeditee) as a rectangle with that background color
843 if (bgColor != null) {
844 System.out.println("bg");
845 frame.addRectangle(Math.round(x), Math.round(y), Math.round(width), Math.round(height), 0, null, bgColor);
846 }
847
848 // background image, returns in format "url(protocol://absolute/path/to/img.extension)" for images,
849 // may also return gradients, data, etc. (not handled yet). Only need to add bg image on
850 // 'ELEMENT_NODE' (and not 'TEXT_NODE' otherwise there would be double-ups
851 String bgImage = (String) style.call("getPropertyValue", new Object[] { "background-image" });
852
853 String linkUrl = (String) ((JSObject) currentNode).getMember("href");
854
855 if (bgImage.startsWith("url(")) {
856 bgImage = bgImage.substring(4, bgImage.length() - 1);
857
858 String bgSize = ((String) style.call("getPropertyValue", new Object[] { "background-size" })).toLowerCase();
859 String bgRepeat = ((String) style.call("getPropertyValue", new Object[] { "background-repeat" })).toLowerCase();
860
861 // Returns "[x]px [y]px", "[x]% [y]%", "[x]px [y]%" or "[x]% [y]px"
862 String bgPosition = ((String) style.call("getPropertyValue", new Object[] { "background-position" })).toLowerCase();
863
864 String[] bgOffsetCoords = bgPosition.split(" ");
865
866 int bgOffsetX = 0, bgOffsetY = 0;
867
868 float originXPercent = 0, originYPercent = 0;
869
870 int cropStartX, cropStartY, cropEndX, cropEndY;
871
872 // Converting the x and y offset values to integers (and from % to px if needed)
873 if (bgOffsetCoords[0].endsWith("%")) {
874 bgOffsetX = (int) ((Integer.valueOf(bgOffsetCoords[0].substring(0, bgOffsetCoords[0].length() - 1)) / 100.0) * width);
875 originXPercent = (Integer.valueOf(bgOffsetCoords[0].substring(0, bgOffsetCoords[0].length() - 1))) / 100f;
876 } else if (bgOffsetCoords[0].endsWith("px")) {
877 bgOffsetX = (int) (Integer.valueOf(bgOffsetCoords[0].substring(0, bgOffsetCoords[0].length() - 2)));
878 }
879
880 if (bgOffsetCoords[1].endsWith("%")) {
881 bgOffsetY = (int) ((Integer.valueOf(bgOffsetCoords[1].substring(0, bgOffsetCoords[1].length() - 1)) / 100.0) * height);
882 originYPercent = (Integer.valueOf(bgOffsetCoords[1].substring(0, bgOffsetCoords[1].length() - 1))) / 100f;
883 } else if (bgOffsetCoords[1].endsWith("px")) {
884 bgOffsetY = (int) (Integer.valueOf(bgOffsetCoords[1].substring(0, bgOffsetCoords[1].length() - 2)));
885 }
886
887 // Converting from an offset to crop coords
888 cropStartX = -1 * bgOffsetX;
889 cropEndX = (int) (cropStartX + width);
890
891 cropStartY = -1 * bgOffsetY;
892 cropEndY = (int) (cropStartY + height);
893
894 int bgWidth = -1;
895
896 if (bgSize.equals("cover")) {
897 bgWidth = (int) width;
898 } else if (bgSize.equals("contain")) {
899 // TODO: actually compute the appropriate width
900 bgWidth = (int) width;
901 } else if (bgSize.equals("auto")) {
902 bgWidth = -1;
903 } else {
904 bgSize = bgSize.split(" ")[0];
905
906 if (bgSize.endsWith("%")) {
907 bgWidth = (int) ((Integer.parseInt(bgSize.replaceAll("\\D", "")) / 100.0) * width);
908 } else if (bgSize.endsWith("px")) {
909 bgWidth = Integer.parseInt(bgSize.replaceAll("\\D", ""));
910 }
911 }
912
913 try {
914 WebParser.addImageFromUrl(bgImage, linkUrl, frame, x, y, bgWidth, cropStartX, cropStartY, cropEndX, cropEndY, bgRepeat, originXPercent, originYPercent);
915 } catch (MalformedURLException mue) {
916 // probably a 'data:' url, not supported yet
917 mue.printStackTrace();
918 } catch (IOException e) {
919 // TODO Auto-generated catch block
920 e.printStackTrace();
921 }
922 }
923
924 String imgSrc;
925
926 if (currentNode.getNodeName().toLowerCase().equals("img") && (imgSrc = ((JSObject) currentNode).getMember("src").toString()) != null) {
927 try {
928 WebParser.addImageFromUrl(imgSrc, linkUrl, frame, x, y, (int) width, null, null, null, null, null, 0, 0);
929 } catch (MalformedURLException mue) {
930 // probably a 'data:' url, not supported yet
931 mue.printStackTrace();
932 } catch (IOException e) {
933 // TODO Auto-generated catch block
934 e.printStackTrace();
935 }
936 }
937 }
938 }
939
940 Node childNode = currentNode.getFirstChild();
941
942 while (childNode != null) {
943 addPageToFrame(childNode, window, webEngine, frame);
944 childNode = childNode.getNextSibling();
945 }
946 }
947 }
948
949 private static boolean elementVisible(float x, float y, float width, float height, JSObject style) {
950 if (width <= 0 || height <= 0 || x + width <= 0 || y + height <= 0 || ((String) style.call("getPropertyValue", new Object[] { "visibility" })).equals("hidden")
951 || ((String) style.call("getPropertyValue", new Object[] { "display" })).equals("none")) {
952 return false;
953 } else {
954 return true;
955 }
956 }
957
958 /**
959 * @param imgSrc
960 * URL of the image to add
961 * @param linkUrl
962 * Absolute URL that the image should link to when clicked
963 * @param frame
964 * Frame to add the image to
965 * @param x
966 * X-coordinate at which the image should be placed on the frame
967 * @param y
968 * Y-coordinate at which the image should be placed on the frame
969 * @param width
970 * Width of the image once added to the frame. Negative 1 (-1) will cause the actual width of the image file to be used
971 *
972 * @param cropStartX
973 * X-coordinate at which to start crop, or null for no crop
974 * @param cropStartY
975 * Y-coordinate at which to start crop, or null for no crop
976 * @param cropEndX
977 * X-coordinate at which to end the crop, or null for no crop
978 * @param cropEndY
979 * Y-coordinate at which to end the crop, or null for no crop
980 *
981 * @param repeat
982 * String determining how the image should be tiled/repeated. Valid strings are: <i>no-repeat</i>, <i>repeat-x</i>, or
983 * <i>repeat-y</i>. All other values (including null) will cause the image to repeat in both directions
984 *
985 * @param originXPercent
986 * Percentage into the image to use as the x coordinate of the image's origin point
987 * @param originYPercent
988 * Percentage into the image to use as the y coordinate of the image's origin point
989 *
990 * @throws MalformedURLException
991 * @throws IOException
992 */
993 private static void addImageFromUrl(String imgSrc, String linkUrl, final Frame frame, float x, float y, int width, Integer cropStartX, Integer cropStartY, Integer cropEndX, Integer cropEndY, String repeat,
994 float originXPercent, float originYPercent)
995 throws MalformedURLException,
996 IOException {
997
998 URL imgUrl = new URL(imgSrc);
999
1000 HttpURLConnection connection = (HttpURLConnection) (imgUrl.openConnection());
1001
1002 // Spoofing a widely accepted User Agent, since some sites refuse to serve non-webbrowser clients
1003 connection.setRequestProperty("User-Agent", "Mozilla/5.0");
1004
1005 BufferedImage img = ImageIO.read(connection.getInputStream());
1006
1007 int hashcode = Arrays.hashCode(img.getData().getPixels(0, 0, img.getWidth(), img.getHeight(), (int[]) null));
1008 File out = new File(FrameIO.IMAGES_PATH + Integer.toHexString(hashcode) + ".png");
1009 out.mkdirs();
1010 ImageIO.write(img, "png", out);
1011
1012 if (cropEndX == null || cropStartX == null || cropEndY == null || cropStartY == null) {
1013 cropStartX = 0;
1014 cropStartY = 0;
1015 cropEndX = img.getWidth();
1016 cropEndY = img.getHeight();
1017 } else if (cropStartX < 0) {
1018 cropEndX = cropEndX - cropStartX;
1019 x = x + Math.abs(cropStartX);
1020 cropStartX = 0;
1021 }
1022
1023 if (cropStartY < 0) {
1024 cropEndY = cropEndY - cropStartY;
1025 y = y + Math.abs(cropStartY);
1026 cropStartY = 0;
1027 }
1028
1029 if (width < 0) {
1030 width = img.getWidth();
1031 }
1032
1033 if (repeat != null) {
1034 if (repeat.equals("no-repeat")) {
1035 int tmpCropEndY = (int) (cropStartY + ((float) width / img.getWidth()) * img.getHeight());
1036 int tmpCropEndX = cropStartX + width;
1037
1038 cropEndX = (cropEndX < tmpCropEndX) ? cropEndX : tmpCropEndX;
1039 cropEndY = (cropEndY < tmpCropEndY) ? cropEndY : tmpCropEndY;
1040 } else if (repeat.equals("repeat-x")) {
1041 int tmpCropEndY = (int) (cropStartY + ((float) width / img.getWidth()) * img.getHeight());
1042 cropEndY = (cropEndY < tmpCropEndY) ? cropEndY : tmpCropEndY;
1043 } else if (repeat.equals("repeat-y")) {
1044 int tmpCropEndX = cropStartX + width;
1045 cropEndX = (cropEndX < tmpCropEndX) ? cropEndX : tmpCropEndX;
1046 }
1047 }
1048
1049 if (originXPercent > 0) {
1050 int actualWidth = cropEndX - cropStartX;
1051
1052 int originXPixels = Math.round(originXPercent * actualWidth);
1053
1054 x = x - originXPixels;
1055
1056 cropStartX = (int) (cropStartX + (width - actualWidth) * originXPercent);
1057 cropEndX = (int) (cropEndX + (width - actualWidth) * originXPercent);
1058 }
1059
1060 if (originYPercent > 0) {
1061 int height = (int) ((img.getHeight() / (float) img.getWidth()) * width);
1062 int actualHeight = (cropEndY - cropStartY);
1063 int originYPixels = Math.round(originYPercent * actualHeight);
1064
1065 y = y - originYPixels;
1066
1067 cropStartY = (int) (cropStartY + (height - actualHeight) * originYPercent);
1068 cropEndY = (int) (cropEndY + (height - actualHeight) * originYPercent);
1069 }
1070
1071 Text text = new Text("@i: " + out.getName() + " " + width);
1072 text.setPosition(x, y);
1073
1074 Picture pic = ItemUtils.CreatePicture(text, frame);
1075
1076 float invScale = 1 / pic.getScale();
1077
1078 pic.setCrop((int)(cropStartX * invScale), (int)(cropStartY * invScale), (int)(cropEndX * invScale), (int)(cropEndY * invScale));
1079
1080 if (linkUrl != null && !linkUrl.equals("undefined")) {
1081 pic.setAction("goto " + linkUrl);
1082 pic.setActionMark(false);
1083 }
1084
1085 frame.addItem(pic);
1086 pic.anchor();
1087 pic.getSource().anchor();
1088 }
1089
1090 /**
1091 * @param rootElement
1092 * Element that will be converted (including all sub-elements)
1093 * @param backgroundColor
1094 * String to be used as the background color of this element when added. In the format "rgb(x,x,x)" or "rgba(x,x,x,x)"
1095 * @param window
1096 * 'window' from Javascript
1097 * @param webEngine
1098 * Web engine that the page is loaded in
1099 * @param frame
1100 * Expeditee frame to add the converted page to
1101 * @throws IllegalArgumentException
1102 * @throws IllegalAccessException
1103 */
1104 private static void addTextToFrame(Node rootElement, int visibleWidth, int visibleHeight, JSObject window, WebEngine webEngine, Frame frame) throws InvocationTargetException,
1105 IllegalAccessException, IllegalArgumentException {
1106
1107 Node currentNode = rootElement;
1108
1109 if (currentNode.getNodeType() == Node.TEXT_NODE) {
1110
1111 JSObject style;
1112 JSObject bounds;
1113
1114
1115 // CSS style for the element
1116 style = (JSObject) window.call("getComputedStyle", new Object[] { currentNode.getParentNode() });
1117
1118 // Getting a rectangle that represents the area and position of the element
1119 bounds = (JSObject) ((JSObject) currentNode.getParentNode()).call("getBoundingClientRect", new Object[] {});
1120
1121
1122 // Bounding rectangle position is relative to the current view, so scroll position must be added to x/y
1123 // TODO: This doesn't check if an element or any of its parent elements have position:fixed set - the only
1124 // way to check seems to be to walking through the element's parents until the document root is reached
1125 float x = Float.valueOf(bounds.getMember("left").toString());
1126 float y = Float.valueOf(bounds.getMember("top").toString());
1127
1128 float width = Float.valueOf(bounds.getMember("width").toString());
1129 float height = Float.valueOf(bounds.getMember("height").toString());
1130
1131 // Checking if the element is actually visible on the page
1132 if (width > 0 && height > 0 && x + width > 0 && y + height > 0 && x <= visibleWidth && y <= visibleHeight
1133 && !(((String) style.call("getPropertyValue", new Object[] { "display" })).equals("none"))) {
1134
1135 String fontSize = ((String) style.call("getPropertyValue", new Object[] { "font-size" }));
1136
1137 // Trimming off the units (always px) from the font size
1138 fontSize = fontSize.substring(0, fontSize.length() - 2);
1139
1140 // Always returns in format "rgb(x,x,x)" or "rgba(x,x,x,x)"
1141 String color = (String) style.call("getPropertyValue", new Object[] { "color" });
1142
1143 // Always returns in format "rgb(x,x,x)" or "rgba(x,x,x,x)"
1144 String bgColorString = (String) style.call("getPropertyValue", new Object[] { "background-color" });
1145
1146 String align = (String) style.call("getPropertyValue", new Object[] { "text-align" });
1147
1148 // Returns comma-separated list of typefaces
1149 String typeface = (String) style.call("getPropertyValue", new Object[] { "font-family" });
1150
1151 String[] typefaces = typeface.split(", |,");
1152
1153 String weight = (String) style.call("getPropertyValue", new Object[] { "font-weight" });
1154
1155 String fontStyle = (String) style.call("getPropertyValue", new Object[] { "font-style" });
1156
1157 // Returns "normal" or a value in pixels (e.g. "10px")
1158 String letterSpacing = (String) style.call("getPropertyValue", new Object[] { "letter-spacing" });
1159
1160 // Returns a value in pixels (e.g. "10px")
1161 String lineHeight = (String) style.call("getPropertyValue", new Object[] { "line-height" });
1162
1163 String textTransform = (String) style.call("getPropertyValue", new Object[] { "text-transform" });
1164
1165 String linkUrl = (String) ((JSObject) currentNode.getParentNode()).getMember("href");
1166
1167 Boolean fontFound = false;
1168 Font font = new Font(null);
1169
1170 // Looping through all font-families listed in the element's CSS until one that is installed is
1171 // found, or the end of the list is reached, in which case the default font is used
1172 for (int j = 0; j < typefaces.length && !fontFound; j++) {
1173 if (typefaces[j].toLowerCase().equals("sans-serif")) {
1174 typefaces[j] = "Arial Unicode MS";
1175 } else if (typefaces[j].toLowerCase().equals("serif")) {
1176 typefaces[j] = "Times New Roman";
1177 } else if ((typefaces[j].toLowerCase().equals("arial"))) {
1178 // Have to use Arial Unicode, otherwise unicode characters display incorrectly
1179 typefaces[j] = "Arial Unicode MS";
1180 }
1181
1182 // Regex will remove any inverted commas surrounding multi-word typeface names
1183 font = new Font(typefaces[j].replaceAll("^'|'$", ""), Font.PLAIN, 12);
1184
1185 // If the font isn't found, Java just uses Font.DIALOG, so this check checks whether the font was found
1186 if (!(font.getFamily().toLowerCase().equals(Font.DIALOG.toLowerCase()))) {
1187 fontFound = true;
1188 }
1189 }
1190
1191 if (font.getFamily().toLowerCase().equals(Font.DIALOG.toLowerCase())) {
1192 font = new Font("Times New Roman", Font.PLAIN, 12);
1193 }
1194
1195 String fontStyleComplete = "";
1196
1197 int weightInt = 0;
1198
1199 try {
1200 weightInt = Integer.parseInt(weight);
1201 } catch (NumberFormatException nfe) {
1202 // Use default value as set above
1203 }
1204
1205 // checking if font is bold - i.e. 'bold', 'bolder' or weight over 500
1206 if (weight.toLowerCase().startsWith("bold") || weightInt > 500) {
1207 fontStyleComplete = fontStyleComplete.concat("bold");
1208 }
1209
1210 if (fontStyle.toLowerCase().equals("italic") || fontStyle.toLowerCase().equals("oblique")) {
1211 fontStyleComplete = fontStyleComplete.concat("italic");
1212 }
1213
1214 float fontSizeFloat = 12;
1215
1216 try {
1217 fontSizeFloat = Float.valueOf(fontSize);
1218 } catch (NumberFormatException nfe) {
1219 // Use default value as set above
1220 }
1221
1222 float letterSpacingFloat = -0.008f;
1223
1224 try {
1225 letterSpacingFloat = (Integer.parseInt(letterSpacing.substring(0, letterSpacing.length() - 2)) / (fontSizeFloat));
1226 } catch (NumberFormatException nfe) {
1227 // Use default value as set above
1228 }
1229
1230 float lineHeightInt = -1;
1231
1232 try {
1233 lineHeightInt = (Float.parseFloat(lineHeight.substring(0, lineHeight.length() - 2)));
1234 } catch (NumberFormatException nfe) {
1235 // Use default value as set above
1236 }
1237
1238 Text t;
1239
1240 String textContent = currentNode.getTextContent().replaceAll("[^\\S\\n]+", " ");
1241 textContent = textContent.replaceAll("^(\\s)(\\n|\\r)", "");
1242
1243 if (textTransform.equals("uppercase")) {
1244 textContent = textContent.toUpperCase();
1245 } else if (textTransform.equals("lowercase")) {
1246 textContent = textContent.toUpperCase();
1247 }
1248
1249 // Adding the text to the frame. Expeditee text seems to be positioned relative to the baseline of the first line, so
1250 // the font size has to be added to the y-position
1251 t = frame.addText(Math.round(x), Math.round(y + fontSizeFloat), textContent, null);
1252
1253 t.setColor(rgbStringToColor(color));
1254 t.setBackgroundColor(rgbStringToColor(bgColorString));
1255 t.setFont(font);
1256 t.setSize(fontSizeFloat);
1257 t.setFontStyle(fontStyleComplete);
1258 t.setLetterSpacing(letterSpacingFloat);
1259
1260 // Removing any spacing between lines allowing t.getLineHeight() to be used to get the actual height
1261 // of just the characters (i.e. distance from ascenders to descenders)
1262 t.setSpacing(0);
1263
1264 t.setSpacing(lineHeightInt - t.getLineHeight());
1265
1266 if (align.equals("left")) {
1267 t.setJustification(Justification.left);
1268 } else if (align.equals("right")) {
1269 t.setJustification(Justification.right);
1270 } else if (align.equals("center")) {
1271 t.setJustification(Justification.center);
1272 } else if (align.equals("justify")) {
1273 t.setJustification(Justification.full);
1274 }
1275
1276 // Font size is added to the item width to give a little breathing room
1277 t.setWidth(Math.round(width + (t.getSize())));
1278
1279 if (!linkUrl.equals("undefined")) {
1280 t.setAction("gotourl " + linkUrl);
1281 t.setActionMark(false);
1282 }
1283 }
1284
1285 } else if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
1286 Node childNode = currentNode.getFirstChild();
1287
1288 while (childNode != null) {
1289 addTextToFrame(childNode, visibleWidth, visibleHeight, window, webEngine, frame);
1290 childNode = childNode.getNextSibling();
1291 }
1292 }
1293 }
1294
1295 private static class MutableBool {
1296 private boolean value;
1297
1298 public MutableBool(boolean value) {
1299 this.value = value;
1300 }
1301
1302 public boolean getValue() {
1303 return value;
1304 }
1305
1306 public void setValue(boolean value) {
1307 this.value = value;
1308 }
1309 }
1310}
Note: See TracBrowser for help on using the repository browser.