Skip to content
Snippets Groups Projects
Commit 9e927d1e authored by Tom Robinson's avatar Tom Robinson
Browse files

Java HTML->PNG rendering experiments

parent 134a4653
Branches
Tags
No related merge requests found
......@@ -41,6 +41,8 @@
com.sun.jmx/jmxri]]
[medley "0.7.0"] ; lightweight lib of useful functions
[mysql/mysql-connector-java "5.1.37"] ; MySQL JDBC driver
[net.sf.cssbox/cssbox "4.10"]
[org.xhtmlrenderer/flying-saucer-core "9.0.8"]
[org.liquibase/liquibase-core "3.4.1"] ; migration management (Java lib)
[org.slf4j/slf4j-log4j12 "1.7.12"]
[org.yaml/snakeyaml "1.16"] ; YAML parser (required by liquibase)
......
......@@ -64,4 +64,82 @@
(let [data (:data (driver/dataset-query (:dataset_query card) {:executed_by *current-user-id*}))]
{:status 200 :body (html [:html [:body {:style "margin: 0;"} (p/render-pulse-card card data)]])})))
(def ^:private card-width 400)
(defn parse-dom
[stream]
(let [dbf (javax.xml.parsers.DocumentBuilderFactory/newInstance)]
(.setNamespaceAware dbf true)
(.setFeature dbf "http://apache.org/xml/features/nonvalidating/load-external-dtd" false)
(.parse (.newDocumentBuilder dbf) stream)))
; ported from https://stackoverflow.com/questions/17061682/java-html-rendering-engine
(defn render-to-png-swing
[html os]
(let [image (-> (java.awt.GraphicsEnvironment/getLocalGraphicsEnvironment)
.getDefaultScreenDevice
.getDefaultConfiguration
(.createCompatibleImage card-width 600))
graphics (.createGraphics image)
jep (new javax.swing.JEditorPane "text/html" html)]
(.setSize jep card-width 600)
(.print jep graphics)
(javax.imageio.ImageIO/write image "png" os)))
; ported from https://github.com/radkovo/CSSBox/blob/cssbox-4.10/src/main/java/org/fit/cssbox/demo/ImageRenderer.java
(defn render-to-png-cssbox
[html, os]
(let [is (new java.io.ByteArrayInputStream (.getBytes html java.nio.charset.StandardCharsets/UTF_8))
docSource (new org.fit.cssbox.io.StreamDocumentSource is nil "text/html")
parser (new org.fit.cssbox.io.DefaultDOMSource docSource)
doc (-> parser .parse)
media (new cz.vutbr.web.css.MediaSpec "screen")
windowSize (new java.awt.Dimension card-width 600)]
(.setDimensions media (.width windowSize) (.height windowSize))
(.setDeviceDimensions media (.width windowSize) (.height windowSize))
(let [da (new org.fit.cssbox.css.DOMAnalyzer doc (.getURL docSource))]
(.setMediaSpec da media)
(.attributesToStyles da)
(.addStyleSheet da nil (org.fit.cssbox.css.CSSNorm/stdStyleSheet) org.fit.cssbox.css.DOMAnalyzer$Origin/AGENT)
(.addStyleSheet da nil (org.fit.cssbox.css.CSSNorm/userStyleSheet) org.fit.cssbox.css.DOMAnalyzer$Origin/AGENT)
(.addStyleSheet da nil (org.fit.cssbox.css.CSSNorm/formsStyleSheet) org.fit.cssbox.css.DOMAnalyzer$Origin/AGENT)
(.getStyleSheets da)
(let [contentCanvas (new org.fit.cssbox.layout.BrowserCanvas (.getRoot da) da (.getURL docSource))]
(-> contentCanvas (.setAutoMediaUpdate false))
(-> contentCanvas .getConfig (.setClipViewport false))
(-> contentCanvas .getConfig (.setLoadImages true))
(-> contentCanvas .getConfig (.setLoadBackgroundImages true))
(-> contentCanvas (.createLayout windowSize))
(javax.imageio.ImageIO/write (.getImage contentCanvas) "png" os)))))
; ported from http://grepcode.com/file/repo1.maven.org/maven2/org.xhtmlrenderer/core-renderer/R8pre2/org/xhtmlrenderer/simple/Graphics2DRenderer.java
(defn render-to-png-flying-saucer
[html os]
(let [is (new java.io.ByteArrayInputStream (.getBytes html java.nio.charset.StandardCharsets/UTF_8))
dom (parse-dom is)
renderer (new org.xhtmlrenderer.simple.Graphics2DRenderer)
rect (new java.awt.Dimension card-width 600)
buff (new java.awt.image.BufferedImage (.getWidth rect) (.getHeight rect) java.awt.image.BufferedImage/TYPE_INT_ARGB)
g (.getGraphics buff)]
(.setDocument renderer dom nil)
(.layout renderer g rect)
(.dispose g)
(let [rect (.getMinimumSize renderer)
buff (new java.awt.image.BufferedImage (.getWidth rect) (.getHeight rect) java.awt.image.BufferedImage/TYPE_INT_ARGB)
g (.getGraphics buff)]
(.render renderer g)
(.dispose g)
(javax.imageio.ImageIO/write buff "png" os))))
(defendpoint GET "/preview_card_png/:id"
"Get PNG rendering of a `Card` with ID."
[id]
(let [card (Card id)]
(read-check Database (:database (:dataset_query card)))
(let [data (:data (driver/dataset-query (:dataset_query card) {:executed_by *current-user-id*}))
html (html [:html [:body {:style "margin: 0; background-color: white;"} (p/render-pulse-card card data)]])
os (new java.io.ByteArrayOutputStream)]
(render-to-png-cssbox html os)
{:status 200 :headers {"Content-Type" "image/png"} :body (new java.io.ByteArrayInputStream (.toByteArray os)) })))
(define-routes)
......@@ -21,16 +21,16 @@
:recipients? true
:fields [{:name "recipients"
:displayName "Send to"
:multi: true
:type: "email"
:multi true
:type "email"
:placeholder "Enter email address these questions should be sent to"
:required true}]}
:slack {:displayName "Slack"
:recipients? false
:fields [{:name "channel"
:displayName "Send to"
:multi: false
:type: "select"
:multi false
:type "select"
:options ["#general", "#random", "#ios"]
:required true}]}})
......
(ns metabase.pulse
(:require [hiccup.core :refer [html]]
[clojure.pprint :refer [cl-format]]))
[clojure.pprint :refer [cl-format]]
[clojure.string :refer [upper-case]]
(metabase.models [setting :refer [defsetting]])))
(def ^:private section-style "font-family: Lato, \"Helvetica Neue\", Helvetica, sans-serif;")
(def ^:private header-style "font-size: 16px; font-weight: 700; color: rgb(57,67,64); margin-bottom: 8px;")
(def ^:private scalar-style "font-size: 24pt; font-weight: 400; color: rgb(45,134,212); padding-top: 12px;")
(def ^:private bar-th-style "font-size: 10px; font-weight: 400; color: rgb(57,67,64); text-transform: uppercase; border-bottom: 4px solid rgb(248, 248, 248); padding-bottom: 10px;")
(def ^:private bar-td-style "font-size: 16px; font-weight: 400; text-align: left; padding-right: 1em; padding-top: 8px;")
;;; ## CONFIG
(defsetting slack-token "Slack API bearer token obtained from https://api.slack.com/web#authentication")
(def ^:private font-style "font-family: Lato, \"Helvetica Neue\", Helvetica, Arial, sans-serif;")
(def ^:private section-style font-style)
(def ^:private header-style (str font-style "font-size: 16px; font-weight: 700; color: rgb(57,67,64); margin-bottom: 8px;"))
(def ^:private scalar-style (str font-style "font-size: 32px; font-weight: 400; color: rgb(45,134,212); padding-top: 12px;"))
(def ^:private bar-th-style (str font-style "font-size: 10px; font-weight: 400; color: rgb(57,67,64); border-bottom: 4px solid rgb(248, 248, 248); padding-bottom: 10px;"))
(def ^:private bar-td-style (str font-style "font-size: 16px; font-weight: 400; text-align: left; padding-right: 1em; padding-top: 8px;"))
(defn format-number
[n]
......@@ -16,9 +24,9 @@
[index row max-value]
[:tr {:style (if (odd? index) "color: rgb(189,193,191);" "color: rgb(124,131,129);")}
[:td {:style bar-td-style} (first row)]
[:td {:style (str bar-td-style " font-weight: 700;")} (format-number (second row))]
[:td {:style bar-td-style}
[:div {:style (str "background-color: rgb(135, 93, 175); height: 20px; width: " (float (* 100 (/ (second row) max-value))) "%")} " "]]])
[:td {:style (str bar-td-style "font-weight: 700;")} (format-number (second row))]
[:td {:style (str bar-td-style "width: 99%;")}
[:div {:style (str "background-color: rgb(135, 93, 175); height: 20px; width: " (float (* 100 (/ (second row) max-value))) "%")} " "]]])
(defn render-bar-chart
[data]
......@@ -27,11 +35,11 @@
[:table {:style "border-collapse: collapse;"}
[:thead
[:tr
[:th {:style (str bar-td-style " " bar-th-style " min-width: 60px;")}
(:display_name (first cols))]
[:th {:style (str bar-td-style " " bar-th-style " min-width: 60px;")}
(:display_name (second cols))]
[:th {:style (str bar-td-style " " bar-th-style " width: 99%;")}]]]
[:th {:style (str bar-td-style bar-th-style "min-width: 60px;")}
(-> cols first :display_name upper-case)]
[:th {:style (str bar-td-style bar-th-style "min-width: 60px;")}
(-> cols second :display_name upper-case)]
[:th {:style (str bar-td-style bar-th-style "width: 99%;")}]]]
[:tbody
(map-indexed #(render-bar-chart-row %1 %2 max-value) rows)]]))
......@@ -40,8 +48,8 @@
[:div {:style scalar-style} (-> data :rows first first format-number)])
(defn render-pulse-card
[card, data]
[:section {:style (str section-style "margin: 1em;")}
[card data]
[:div {:style (str section-style "margin: 16px;")}
[:div {:style header-style} (:name card)]
(cond
(and (= (count (:cols data)) 1) (= (count (:rows data)) 1)) (render-scalar data)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment