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

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

Links partially working in converted web pages

File size: 30.4 KB
Line 
1package org.expeditee.io;
2
3import java.awt.Color;
4import java.awt.Font;
5import java.awt.image.BufferedImage;
6import java.io.File;
7import java.io.IOException;
8import java.net.HttpURLConnection;
9import java.net.MalformedURLException;
10import java.net.URL;
11import java.util.ArrayList;
12import java.util.Arrays;
13import java.util.regex.Matcher;
14import java.util.regex.Pattern;
15
16import javax.imageio.ImageIO;
17
18import org.expeditee.gui.Frame;
19import org.expeditee.gui.FrameIO;
20import org.expeditee.gui.FrameUtils;
21import org.expeditee.gui.MessageBay;
22import org.expeditee.gui.MessageBay.Progress;
23import org.expeditee.items.ItemUtils;
24import org.expeditee.items.Justification;
25import org.expeditee.items.Picture;
26import org.expeditee.items.Text;
27import org.expeditee.reflection.JavaFX;
28import org.w3c.dom.Element;
29import org.w3c.dom.Node;
30import org.w3c.dom.html.HTMLBodyElement;
31
32/**
33 * Methods to convert webpages to Expeditee frames
34 *
35 * @author ngw8
36 * @author jts21
37 */
38public class WebParser {
39
40
41 /**
42 * Loads a webpage and renders it as Expeditee frame(s)
43 *
44 * @param URL
45 * Page to load
46 * @param frame
47 * The Expeditee frame to output the converted page to
48 */
49 public static void parseURL(final String URL, final Frame frame) {
50 try {
51 JavaFX.PlatformRunLater.invoke(null, new Runnable() {
52 @Override
53 public void run() {
54 try {
55 Object webEngine = JavaFX.WebEngineConstructor.newInstance(URL);
56 loadPage(webEngine, frame);
57 } catch (Exception e) {
58 e.printStackTrace();
59 }
60 }
61 });
62 } catch (Exception e) {
63 e.printStackTrace();
64 }
65 }
66
67 protected static void loadPage(final Object webEngine, final Frame frame) throws Exception {
68 JavaFX.ReadOnlyObjectPropertyAddListener.invoke(JavaFX.WorkerStateProperty.invoke(JavaFX.WebEngineGetLoadWorker
69 .invoke(webEngine)), java.lang.reflect.Proxy.newProxyInstance(
70 JavaFX.ChangeListener.getClassLoader(), new java.lang.Class[] { JavaFX.ChangeListener },
71 new java.lang.reflect.InvocationHandler() {
72 @Override
73 public Object invoke(Object proxy, java.lang.reflect.Method method, Object[] args)
74 throws java.lang.Throwable {
75 String method_name = method.getName();
76 // Class<?>[] classes = method.getParameterTypes();
77 // public void changed(ObservableValue ov, State oldState, State newState)
78 if (method_name.equals("changed")) {
79 // changed takes 3 args
80 if (args == null || args.length != 3) {
81 return null;
82 }
83 // args[0] is the ObservableValue
84 // args[2] is the new State
85 if (args[2].getClass() == JavaFX.State) {
86 int id = JavaFX.StateConstants.indexOf(args[2]);
87 switch (id) {
88 case 0: // READY
89 // MessageBay.displayMessage("WebEngine ready");
90 break;
91 case 1: // SCHEDULED
92 // MessageBay.displayMessage("Scheduled page load");
93 break;
94 case 2: // RUNNING
95 System.out.println("Loading page!");
96 // MessageBay.displayMessage("WebEngine running");
97 break;
98 case 3: // SUCCEEDED
99 // MessageBay.displayMessage("Finished loading page");
100 System.out.println("Parsing page!");
101 JavaFX.WebEngineExecuteScript.invoke(webEngine, "window.resizeTo(800, 800)");
102 parsePage(webEngine, frame);
103 System.out.println("Parsed page!");
104 break;
105 case 4: // CANCELLED
106 MessageBay.displayMessage("Cancelled loading page");
107 break;
108 case 5: // FAILED
109 MessageBay.displayMessage("Failed to load page");
110 break;
111 }
112 }
113 System.out.println("\n");
114 }
115 return null;
116 }
117 }));
118 }
119
120 /**
121 * Converts a loaded page to Expeditee frame(s)
122 *
123 * @param webEngine
124 * The JavaFX WebEngine in which the page to be converted is loaded
125 * @param frame
126 * The Expeditee frame to output the converted page to
127 */
128 public static void parsePage(final Object webEngine, final Frame frame) {
129 try {
130 JavaFX.PlatformRunLater.invoke(null, new Runnable() {
131 @Override
132 public void run() {
133 try {
134 Progress progressBar = MessageBay.displayProgress("Converting web page");
135
136 HTMLBodyElement doc = (HTMLBodyElement) JavaFX.WebEngineExecuteScript.invoke(webEngine, "document.body");
137
138 Object window = JavaFX.WebEngineExecuteScript.invoke(webEngine, "window");
139
140 JavaFX.WebEngineExecuteScript.invoke(webEngine, ""
141 + "var css = 'a * { outline: 0.1px outset rgba(9,9,9,0.001); }';" // TODO
142 + "var head = document.head;"
143 + "var style = document.createElement('style');"
144 + "style.id = 'expediteeparser';"
145
146 + "style.appendChild(document.createTextNode(css));"
147
148 + "head.appendChild(style);"
149 );
150
151 frame.setBackgroundColor(rgbStringToColor((String) JavaFX.JSObjectCall.invoke(JavaFX.JSObjectCall.invoke(window, "getComputedStyle", new Object[] { doc }), "getPropertyValue",
152 new Object[] { "background-color" })));
153
154 // Functions to be used later in JavaScript
155 JavaFX.WebEngineExecuteScript.invoke(webEngine, ""
156 + "function addToSpan(text) {"
157 + " span = document.createElement('wordSpan');"
158 + " span.textContent = text;"
159 + " par.insertBefore(span, refNode);"
160 + " if (prevSpan !== null && span.getBoundingClientRect().top > prevSpan.getBoundingClientRect().top) {"
161 + " span.textContent = '\\n' + span.textContent;"
162 + " if ( prevPrevSpan !== null && prevPrevSpan.getBoundingClientRect().left == prevSpan.getBoundingClientRect().left) {"
163 + " prevPrevSpan.textContent = prevPrevSpan.textContent + prevSpan.textContent;"
164 + " par.removeChild(prevSpan);"
165 + " } else {"
166 + " prevPrevSpan = prevSpan;"
167 + " }"
168 + " prevSpan = span;"
169 + " } else if ( prevSpan !== null) {"
170 + " prevSpan.textContent = prevSpan.textContent + span.textContent;"
171 + " par.removeChild(span);"
172 + " } else {"
173 + " prevSpan = span;"
174 + " }"
175 + "}"
176 );
177
178 // Getting an array of all HTML elements in the page
179 Object contentElements = JavaFX.WebEngineExecuteScript.invoke(webEngine, "document.querySelectorAll('body *');");
180 int contentElementsLength = (Integer) JavaFX.JSObjectGetMember.invoke(contentElements, "length");
181
182 for (int i = 0; i < contentElementsLength; i++) {
183 // Getting the current HTML element, then making it accessible in JavaScript
184 Element currentElement = (Element) JavaFX.JSObjectGetSlot.invoke(contentElements, i);
185 JavaFX.JSObjectSetMember.invoke(window, "para", currentElement);
186
187 JavaFX.WebEngineExecuteScript.invoke(webEngine, "para.style.wordBreak = 'normal';");
188
189 // Creating a TreeWalker that is used to loop over all the TextNodes within the current element
190 JavaFX.WebEngineExecuteScript.invoke(webEngine, "var walker = document.createTreeWalker(para, NodeFilter.SHOW_TEXT, null, false);");
191
192 // Using Javascript to get an array of all the text nodes in the current element. Have to loop through twice (once
193 // to build the array and once actually going through the array, otherwise when the textnode is removed from the
194 // document items end up being skipped)
195 Object textNodes = JavaFX.WebEngineExecuteScript.invoke(webEngine, ""
196 + "function getTextNodes(rootNode){"
197 + "var node;"
198 + "var textNodes=[];"
199 + "var walk = document.createTreeWalker(rootNode, NodeFilter.SHOW_TEXT);"
200 + "while(node=walk.nextNode()) {"
201 + "if((node.textContent.trim().length > 0)) { "
202 + "textNodes.push(node);"
203 + "}"
204 + "}"
205 + "return textNodes;"
206 + "}; "
207 + "getTextNodes(para)");
208
209 int nodesLength = (Integer) JavaFX.JSObjectGetMember.invoke(textNodes, "length");
210
211 // Looping through all the text nodes in the current paragraph
212 for (int j = 0; j < nodesLength; j++) {
213 Node currentNode = (Node) JavaFX.JSObjectGetSlot.invoke(textNodes, j);
214
215 // Making the current node accessible in JavaScript
216 JavaFX.JSObjectSetMember.invoke(window, "textNode", currentNode);
217
218 JavaFX.WebEngineExecuteScript.invoke(webEngine, ""
219 + "var span = null;"
220 + "var prevSpan = null;"
221 + "var prevPrevSpan = null;"
222 );
223
224 // Splitting the text node's content into individual words
225 String textContent = ((String) JavaFX.WebEngineExecuteScript.invoke(webEngine, "textNode.textContent")).replaceAll("\\n|\\r", "").replaceAll("\\s+", " ");
226 String[] words = splitIntoWords(textContent);
227
228 JavaFX.WebEngineExecuteScript.invoke(webEngine, ""
229 + "var refNode = textNode.nextSibling;"
230 + "var par = textNode.parentElement;"
231 + "textNode.parentElement.removeChild(textNode)");
232
233 // Adding each word back to the page
234 for (int k = 0; k < words.length; k++) {
235 Object currentWord = words[k];
236 JavaFX.JSObjectCall.invoke(window, "addToSpan", new Object[] { currentWord });
237 }
238
239 JavaFX.WebEngineExecuteScript.invoke(webEngine, ""
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
247 progressBar.set((100 * (i + 1)) / contentElementsLength);
248 }
249
250 // Finding all links within the page, then setting the href attribute of all their descendants to be the same
251 // link/URL.
252 // This is needed because there is no apparent and efficient way to check if an element is a child of a link when
253 // running through the document when added each element to Expeditee
254 JavaFX.WebEngineExecuteScript.invoke(webEngine, ""
255 + "var anchors = document.getElementsByTagName('a');"
256 + ""
257 + "for (var i = 0; i < anchors.length; i++) {"
258 + "var currentAnchor = anchors.item(i);"
259 + "var anchorDescendants = currentAnchor.querySelectorAll('*');"
260 + "for (var j = 0; j < anchorDescendants.length; j++) {"
261 + "anchorDescendants.item(j).href = currentAnchor.href;"
262 + "}"
263 + "}"
264 );
265
266 // Creating a TreeWalker that is used to loop over all the nodes within the document
267 JavaFX.WebEngineExecuteScript.invoke(webEngine, "var walker = document.createTreeWalker(document.body, NodeFilter.SHOW_ALL);");
268
269 Node currentNode;
270
271 // Looping through all the nodes in the document
272 while ((currentNode = (Node) JavaFX.WebEngineExecuteScript.invoke(webEngine, "walker.nextNode()")) != null) {
273
274 if (currentNode.getNodeType() == Node.TEXT_NODE || currentNode.getNodeType() == Node.ELEMENT_NODE) {
275
276 Object style;
277 Object bounds;
278
279 if (currentNode.getNodeType() == Node.TEXT_NODE) {
280 // CSS style for the element
281 style = JavaFX.JSObjectCall.invoke(window, "getComputedStyle", new Object[] { currentNode.getParentNode() });
282
283 // Getting a rectangle that represents the area and position of the element
284 bounds = JavaFX.JSObjectCall.invoke(currentNode.getParentNode(), "getBoundingClientRect", new Object[] {});
285 } else {
286 style = JavaFX.JSObjectCall.invoke(window, "getComputedStyle", new Object[] { currentNode });
287
288 bounds = JavaFX.JSObjectCall.invoke(currentNode, "getBoundingClientRect", new Object[] {});
289 }
290
291 // Bounding rectangle position is relative to the current view, so scroll position must be added to x/y
292 // TODO: This doesn't check if an element or any of its parent elements have position:fixed set - the only
293 // way to check seems to be to walking through the element's parents until the document root is reached
294 float x = Float.valueOf(JavaFX.JSObjectGetMember.invoke(bounds, "left").toString())
295 + Float.valueOf(JavaFX.WebEngineExecuteScript.invoke(webEngine, "window.pageXOffset").toString());
296 float y = Float.valueOf(JavaFX.JSObjectGetMember.invoke(bounds, "top").toString())
297 + Float.valueOf(JavaFX.WebEngineExecuteScript.invoke(webEngine, "window.pageYOffset").toString());
298
299 float width = Float.valueOf(JavaFX.JSObjectGetMember.invoke(bounds, "width").toString());
300 float height = Float.valueOf(JavaFX.JSObjectGetMember.invoke(bounds, "height").toString());
301
302 // Checking if the element is actually visible on the page
303 if (WebParser.elementVisible(x, y, width, height, style)) {
304
305 // Filtering the node type, starting with text nodes
306 if (currentNode.getNodeType() == Node.TEXT_NODE) {
307 String fontSize = ((String) JavaFX.JSObjectCall.invoke(style, "getPropertyValue", new Object[] { "font-size" }));
308
309 // Trimming off the units (always px) from the font size
310 fontSize = fontSize.substring(0, fontSize.length() - 2);
311
312 // Always returns in format "rgb(x,x,x)" or "rgba(x,x,x,x)"
313 String color = (String) JavaFX.JSObjectCall.invoke(style, "getPropertyValue", new Object[] { "color" });
314
315 // Always returns in format "rgb(x,x,x)" or "rgba(x,x,x,x)"
316 String bgColor = (String) JavaFX.JSObjectCall.invoke(style, "getPropertyValue", new Object[] { "background-color" });
317
318 String align = (String) JavaFX.JSObjectCall.invoke(style, "getPropertyValue", new Object[] { "text-align" });
319
320 // Returns comma-separated list of typefaces
321 String typeface = (String) JavaFX.JSObjectCall.invoke(style, "getPropertyValue", new Object[] { "font-family" });
322
323 String[] typefaces = typeface.split(", |,");
324
325 String weight = (String) JavaFX.JSObjectCall.invoke(style, "getPropertyValue", new Object[] { "font-weight" });
326
327 String fontStyle = (String) JavaFX.JSObjectCall.invoke(style, "getPropertyValue", new Object[] { "font-style" });
328
329 // Returns "normal" or a value in pixels (e.g. "10px")
330 String letterSpacing = (String) JavaFX.JSObjectCall.invoke(style, "getPropertyValue", new Object[] { "letter-spacing" });
331
332 // Returns a value in pixels (e.g. "10px")
333 String lineHeight = (String) JavaFX.JSObjectCall.invoke(style, "getPropertyValue", new Object[] { "line-height" });
334
335 String textTransform = (String) JavaFX.JSObjectCall.invoke(style, "getPropertyValue", new Object[] { "text-transform" });
336
337 String linkUrl = (String) JavaFX.JSObjectGetMember.invoke(currentNode.getParentNode(), "href");
338
339 Boolean fontFound = false;
340 Font font = new Font(null);
341
342 // Looping through all font-families listed in the element's CSS until one that is installed is
343 // found, or the end of the list is reached, in which case the default font is used
344 for (int j = 0; j < typefaces.length && !fontFound; j++) {
345 if (typefaces[j].toLowerCase().equals("sans-serif")) {
346 typefaces[j] = "Arial";
347 } else if (typefaces[j].toLowerCase().equals("serif")) {
348 typefaces[j] = "Times New Roman";
349 }
350
351 // Regex will remove any inverted commas surrounding multi-word typeface names
352 font = new Font(typefaces[j].replaceAll("^'|'$", ""), Font.PLAIN, 12);
353
354 // If the font isn't found, Java just uses Font.DIALOG, so this check checks whether the font was found
355 if (!(font.getFamily().toLowerCase().equals(Font.DIALOG.toLowerCase()))) {
356 fontFound = true;
357 }
358 }
359
360 if (font.getFamily().toLowerCase().equals(Font.DIALOG.toLowerCase())) {
361 font = new Font("Times New Roman", Font.PLAIN, 12);
362 }
363
364 String fontStyleComplete = "";
365
366 int weightInt = 0;
367
368 try {
369 weightInt = Integer.parseInt(weight);
370 } catch (NumberFormatException nfe) {
371 // Use default value as set above
372 }
373
374 // checking if font is bold - i.e. 'bold', 'bolder' or weight over 500
375 if (weight.toLowerCase().startsWith("bold") || weightInt > 500) {
376 fontStyleComplete = fontStyleComplete.concat("bold");
377 }
378
379 if (fontStyle.toLowerCase().equals("italic") || fontStyle.toLowerCase().equals("oblique")) {
380 fontStyleComplete = fontStyleComplete.concat("italic");
381 }
382
383 float fontSizeFloat = 12;
384
385 try {
386 fontSizeFloat = Float.valueOf(fontSize);
387 } catch (NumberFormatException nfe) {
388 // Use default value as set above
389 }
390
391 float letterSpacingFloat = -0.008f;
392
393 try {
394 letterSpacingFloat = (Integer.parseInt(letterSpacing.substring(0, letterSpacing.length() - 2)) / (fontSizeFloat));
395 } catch (NumberFormatException nfe) {
396 // Use default value as set above
397 }
398
399 float lineHeightInt = -1;
400
401 try {
402 lineHeightInt = (Float.parseFloat(lineHeight.substring(0, lineHeight.length() - 2)));
403 } catch (NumberFormatException nfe) {
404 // Use default value as set above
405 }
406
407 Text t;
408
409 String textContent = currentNode.getTextContent().replaceAll("[^\\S\\n]+", " ");
410 textContent = textContent.replaceAll("^(\\s)(\\n|\\r)", "");
411
412 if (textTransform.equals("uppercase")) {
413 textContent = textContent.toUpperCase();
414 } else if (textTransform.equals("lowercase")) {
415 textContent = textContent.toUpperCase();
416 }
417
418 t = frame.addText(Math.round(x), Math.round(y), textContent, null);
419
420 t.setColor(rgbStringToColor(color));
421 t.setBackgroundColor(rgbStringToColor(bgColor));
422 t.setFont(font);
423 t.setSize(fontSizeFloat);
424 t.setFontStyle(fontStyleComplete);
425 t.setLetterSpacing(letterSpacingFloat);
426
427 // Removing any spacing between lines allowing t.getLineHeight() to be used to get the actual height
428 // of just the characters (i.e. distance from ascenders to descenders)
429 t.setSpacing(0);
430
431 t.setSpacing(lineHeightInt - t.getLineHeight());
432
433 if (align.equals("left")) {
434 t.setJustification(Justification.left);
435 } else if (align.equals("right")) {
436 t.setJustification(Justification.right);
437 } else if (align.equals("center")) {
438 t.setJustification(Justification.center);
439 } else if (align.equals("justify")) {
440 t.setJustification(Justification.full);
441 }
442
443 // Font size is added to the item width to give a little breathing room
444 t.setWidth(Math.round(width + (t.getSize())));
445
446 if (!linkUrl.equals("undefined")) {
447 t.setAction("gotourl " + linkUrl);
448 t.setActionMark(false);
449 }
450
451 } else if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
452
453 // background image, returns in format "url(protocol://absolute/path/to/img.extension)" for images,
454 // may also return gradients, data, etc. (not handled yet). Only need to add bg image on
455 // 'ELEMENT_NODE' (and not 'TEXT_NODE' otherwise there would be double-ups
456 String bgImage = (String) JavaFX.JSObjectCall.invoke(style, "getPropertyValue", new Object[] { "background-image" });
457
458 String linkUrl = (String) JavaFX.JSObjectGetMember.invoke(currentNode, "href");
459
460 if (bgImage.startsWith("url(")) {
461
462 bgImage = bgImage.substring(4, bgImage.length() - 1);
463
464 String bgSize = ((String) JavaFX.JSObjectCall.invoke(style, "getPropertyValue", new Object[] { "background-size" })).toLowerCase();
465 String bgRepeat = ((String) JavaFX.JSObjectCall.invoke(style, "getPropertyValue", new Object[] { "background-repeat" })).toLowerCase();
466
467 // Returns "[x]px [y]px", "[x]% [y]%", "[x]px [y]%" or "[x]% [y]px"
468 String bgPosition = ((String) JavaFX.JSObjectCall.invoke(style, "getPropertyValue", new Object[] { "background-position" })).toLowerCase();
469
470 String[] bgOffsetCoords = bgPosition.split(" ");
471
472 int bgOffsetX = 0, bgOffsetY = 0;
473
474 float originXPercent = 0, originYPercent = 0;
475
476 int cropStartX, cropStartY, cropEndX, cropEndY;
477
478 // Converting the x and y offset values to integers (and from % to px if needed)
479 if (bgOffsetCoords[0].endsWith("%")) {
480 bgOffsetX = (int) ((Integer.valueOf(bgOffsetCoords[0].substring(0, bgOffsetCoords[0].length() - 1)) / 100.0) * width);
481 originXPercent = (Integer.valueOf(bgOffsetCoords[0].substring(0, bgOffsetCoords[0].length() - 1))) / 100f;
482 } else if (bgOffsetCoords[0].endsWith("px")) {
483 bgOffsetX = (int) (Integer.valueOf(bgOffsetCoords[0].substring(0, bgOffsetCoords[0].length() - 2)));
484 }
485
486 if (bgOffsetCoords[1].endsWith("%")) {
487 bgOffsetY = (int) ((Integer.valueOf(bgOffsetCoords[1].substring(0, bgOffsetCoords[1].length() - 1)) / 100.0) * height);
488 originYPercent = (Integer.valueOf(bgOffsetCoords[1].substring(0, bgOffsetCoords[1].length() - 1))) / 100f;
489 } else if (bgOffsetCoords[1].endsWith("px")) {
490 bgOffsetY = (int) (Integer.valueOf(bgOffsetCoords[1].substring(0, bgOffsetCoords[1].length() - 2)));
491 }
492
493 // Converting from an offset to crop coords
494 cropStartX = -1 * bgOffsetX;
495 cropEndX = (int) (cropStartX + width);
496
497 cropStartY = -1 * bgOffsetY;
498 cropEndY = (int) (cropStartY + height);
499
500 int bgWidth = -1;
501
502 if (bgSize.equals("cover")) {
503 bgWidth = (int) width;
504 } else if (bgSize.equals("contain")) {
505 // TODO: actually compute the appropriate width
506 bgWidth = (int) width;
507 } else if (bgSize.equals("auto")) {
508 bgWidth = -1;
509 } else {
510 bgSize = bgSize.split(" ")[0];
511
512 if (bgSize.endsWith("%")) {
513 bgWidth = (int) ((Integer.parseInt(bgSize.replaceAll("\\D", "")) / 100.0) * width);
514 } else if (bgSize.endsWith("px")) {
515 bgWidth = Integer.parseInt(bgSize.replaceAll("\\D", ""));
516 }
517 }
518
519 try {
520 WebParser.addImageFromUrl(bgImage, linkUrl, frame, x, y, bgWidth, cropStartX, cropStartY, cropEndX, cropEndY, bgRepeat, originXPercent, originYPercent);
521 } catch (MalformedURLException mue) {
522 // probably a 'data:' url, not supported yet
523 mue.printStackTrace();
524 }
525 }
526
527 String imgSrc;
528
529 if (currentNode.getNodeName().toLowerCase().equals("img") && (imgSrc = JavaFX.JSObjectGetMember.invoke(currentNode, "src").toString()) != null) {
530 try {
531 WebParser.addImageFromUrl(imgSrc, linkUrl, frame, x, y, (int) width, null, null, null, null, null, 0, 0);
532 } catch (MalformedURLException mue) {
533 // probably a 'data:' url, not supported yet
534 mue.printStackTrace();
535 }
536 }
537 }
538 }
539 }
540 }
541
542 } catch (Exception e) {
543 e.printStackTrace();
544 }
545 System.out.println("Parsed frame");
546 FrameUtils.Parse(frame);
547 frame.setChanged(true);
548 FrameIO.SaveFrame(frame);
549 }
550 });
551 } catch (Exception e) {
552 e.printStackTrace();
553 }
554 }
555
556 /**
557 * @param rgbString
558 * string in the format <i>rgb(x,x,x)</i> or <i>rgba(x,x,x,x)</i>
559 * @return A Color object that should match the rgb string passed int. Returns null if alpha is 0
560 */
561 private static Color rgbStringToColor(String rgbString) {
562 // Splitting the string into 'rgb' and 'x, x, x'
563 String[] tmpStrings = rgbString.split("\\(|\\)");
564
565 // Splitting up the RGB(A) components into an array
566 tmpStrings = tmpStrings[1].split(",");
567
568 int[] components = new int[4];
569 Arrays.fill(components, 255);
570
571 for (int i = 0; i < tmpStrings.length; i++) {
572 Float d = Float.parseFloat(tmpStrings[i].trim());
573
574 components[i] = Math.round(d);
575 }
576
577 if (components[3] > 0) {
578 return new Color(components[0], components[1], components[2], components[3]);
579 } else {
580 return null;
581 }
582 }
583
584 private static boolean elementVisible(float x, float y, float width, float height, Object style) {
585 try {
586 if (width <= 0 || height <= 0 || x + width <= 0 || y + height <= 0 || ((String) JavaFX.JSObjectCall.invoke(style, "getPropertyValue", new Object[] { "visibility" })).equals("hidden")
587 || ((String) JavaFX.JSObjectCall.invoke(style, "getPropertyValue", new Object[] { "display" })).equals("none")) {
588 return false;
589 } else {
590 return true;
591 }
592 } catch (Exception e) {
593 e.printStackTrace();
594 return false;
595 }
596 }
597
598 /**
599 * @param imgSrc
600 * URL of the image to add
601 * @param frame
602 * Frame to add the image to
603 * @param x
604 * X-coordinate at which the image should be placed on the frame
605 * @param y
606 * Y-coordinate at which the image should be placed on the frame
607 * @param width
608 * Width of the image once added to the frame. Negative 1 (-1) will cause the actual width of the image file to be used
609 *
610 * @param cropStartX
611 * X-coordinate at which to start crop, or null for no crop
612 * @param cropStartY
613 * Y-coordinate at which to start crop, or null for no crop
614 * @param cropEndX
615 * X-coordinate at which to end the crop, or null for no crop
616 * @param cropEndY
617 * Y-coordinate at which to end the crop, or null for no crop
618 *
619 * @param repeat
620 * String determining how the image should be tiled/repeated. Valid strings are: <i>no-repeat</i>, <i>repeat-x</i>, or
621 * <i>repeat-y</i>. All other values (including null) will cause the image to repeat in both directions
622 *
623 * @param originXPercent
624 * Percentage into the image to use as the x coordinate of the image's origin point
625 * @param originYPercent
626 * Percentage into the image to use as the y coordinate of the image's origin point
627 *
628 * @throws MalformedURLException
629 * @throws IOException
630 */
631 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,
632 float originXPercent, float originYPercent)
633 throws MalformedURLException,
634 IOException {
635
636 URL imgUrl = new URL(imgSrc);
637
638 HttpURLConnection connection = (HttpURLConnection) (imgUrl.openConnection());
639
640 // Spoofing a widely accepted User Agent, since some sites refuse to serve non-webbrowser clients
641 connection.setRequestProperty("User-Agent", "Mozilla/5.0");
642
643 BufferedImage img = ImageIO.read(connection.getInputStream());
644
645 int hashcode = Arrays.hashCode(img.getData().getPixels(0, 0, img.getWidth(), img.getHeight(), (int[]) null));
646 File out = new File(FrameIO.IMAGES_PATH + Integer.toHexString(hashcode) + ".png");
647 out.mkdirs();
648 ImageIO.write(img, "png", out);
649
650 if (cropEndX == null || cropStartX == null || cropEndY == null || cropStartY == null) {
651 cropStartX = 0;
652 cropStartY = 0;
653 cropEndX = img.getWidth();
654 cropEndY = img.getHeight();
655 } else if (cropStartX < 0) {
656 cropEndX = cropEndX - cropStartX;
657 x = x + Math.abs(cropStartX);
658 cropStartX = 0;
659 }
660
661 if (cropStartY < 0) {
662 cropEndY = cropEndY - cropStartY;
663 y = y + Math.abs(cropStartY);
664 cropStartY = 0;
665 }
666
667 if (width < 0) {
668 width = img.getWidth();
669 }
670
671 if (repeat != null) {
672 if (repeat.equals("no-repeat")) {
673 int tmpCropEndY = (int) (cropStartY + ((float) width / img.getWidth()) * img.getHeight());
674 int tmpCropEndX = cropStartX + width;
675
676 cropEndX = (cropEndX < tmpCropEndX) ? cropEndX : tmpCropEndX;
677 cropEndY = (cropEndY < tmpCropEndY) ? cropEndY : tmpCropEndY;
678 } else if (repeat.equals("repeat-x")) {
679 int tmpCropEndY = (int) (cropStartY + ((float) width / img.getWidth()) * img.getHeight());
680 cropEndY = (cropEndY < tmpCropEndY) ? cropEndY : tmpCropEndY;
681 } else if (repeat.equals("repeat-y")) {
682 int tmpCropEndX = cropStartX + width;
683 cropEndX = (cropEndX < tmpCropEndX) ? cropEndX : tmpCropEndX;
684 }
685 }
686
687 if (originXPercent > 0) {
688 int actualWidth = cropEndX - cropStartX;
689
690 int originXPixels = Math.round(originXPercent * actualWidth);
691
692 x = x - originXPixels;
693
694 cropStartX = (int) (cropStartX + (width - actualWidth) * originXPercent);
695 cropEndX = (int) (cropEndX + (width - actualWidth) * originXPercent);
696 }
697
698 if (originYPercent > 0) {
699 int height = (int) ((img.getHeight() / (float) img.getWidth()) * width);
700 int actualHeight = (cropEndY - cropStartY);
701 int originYPixels = Math.round(originYPercent * actualHeight);
702
703 y = y - originYPixels;
704
705 cropStartY = (int) (cropStartY + (height - actualHeight) * originYPercent);
706 cropEndY = (int) (cropEndY + (height - actualHeight) * originYPercent);
707 }
708
709 Text text = new Text("@i: " + out.getName() + " " + width);
710 text.setPosition(x, y);
711
712 Picture pic = ItemUtils.CreatePicture(text, frame);
713
714 float invScale = 1 / pic.getScale();
715
716 pic.setCrop((int)(cropStartX * invScale), (int)(cropStartY * invScale), (int)(cropEndX * invScale), (int)(cropEndY * invScale));
717
718 if (linkUrl != null && !linkUrl.equals("undefined")) {
719 pic.setAction("goto " + linkUrl);
720 pic.setActionMark(false);
721 }
722
723 frame.addItem(pic);
724 pic.anchor();
725 pic.getSource().anchor();
726 }
727
728 private static String[] splitIntoWords(String toSplit) {
729 ArrayList<String> words = new ArrayList<String>();
730 Pattern regex = Pattern.compile("\\s+");
731 Matcher matcher = regex.matcher(toSplit);
732
733 // The index at which the previous word ended
734 int prevEndIndex = 0;
735
736 String prev = null;
737
738 while (matcher.find()) {
739 String w = toSplit.substring(prevEndIndex, matcher.start());
740
741 if (prev != null) {
742 words.add(prev + " ");
743 }
744
745 prev = w;
746 prevEndIndex = matcher.end();
747 }
748
749 // Adding the final two words
750 if (prev != null) {
751 words.add(prev + " ");
752 }
753
754 words.add(toSplit.substring(prevEndIndex));
755
756 return words.toArray(new String[words.size()]);
757 }
758}
Note: See TracBrowser for help on using the repository browser.