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

Last change on this file since 801 was 801, checked in by jts21, 10 years ago

Make JfxBrowser use only JavaFX Nodes, and make WebParser get images with webView.snapshot() instead of blitting the parent Swing component onto an Image

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