Skip to content
This repository has been archived by the owner on Jul 25, 2023. It is now read-only.

Commit

Permalink
Support rendering to SVG in addition to PNG
Browse files Browse the repository at this point in the history
As per #200.
  • Loading branch information
Avi Flax committed Oct 2, 2019
1 parent affec08 commit 5d2d75e
Show file tree
Hide file tree
Showing 11 changed files with 442 additions and 177 deletions.
3 changes: 2 additions & 1 deletion CHANGES.md
Expand Up @@ -5,7 +5,8 @@ that are relevant only to those working on the framework itself.

Date | Description | PR | Release
-- | -- | -- | --
TBD | Remove Node renderer and CLI option `--tmp-renderer` | TBD | TBD
TBD | Render diagrams as SVG and/or PNG | TBD | TBD
2019-09-17 | Remove Node renderer and CLI option `--tmp-renderer` | [#205](https://github.com/FundingCircle/fc4-framework/pull/205) | [2019.09.17-6da6ef8](https://github.com/FundingCircle/fc4-framework/releases/tag/release_2019.09.17-6da6ef8)
2019-09-10 | Make the Clojure renderer the default | [#197](https://github.com/FundingCircle/fc4-framework/pull/197) | [2019.09.10-c987903](https://github.com/FundingCircle/fc4-framework/releases/tag/release_2019.09.10-c987903)
2019-08-05 | Change recommended installation method to Homebrew | [#188](https://github.com/FundingCircle/fc4-framework/pull/188) | [2019.09.10-df9ca2d](https://github.com/FundingCircle/fc4-framework/releases/tag/release_2019.09.10-df9ca2d)
2019-07-26 | New CLI option `--tmp-renderer` | [#178](https://github.com/FundingCircle/fc4-framework/pull/178) | [2019-07-26_1982](https://github.com/FundingCircle/fc4-framework/releases/tag/release_2019-07-26_1982)
Expand Down
43 changes: 34 additions & 9 deletions docs/tool/index.md
Expand Up @@ -42,13 +42,15 @@ Improves the layout of [Structurizr Express][structurizr-express] diagrams:

### Rendering

Given [Structurizr Express][structurizr-express] diagram YAML files, creates PNG image files that
Given [Structurizr Express][structurizr-express] diagram YAML files, creates image files that
contain the visualization of the diagram.

* The resulting image file is created in the same directory as the YAML file, with the same base
filename and the `png` extension
* e.g. `docs/spline_reticulator_01_context.yaml` yields `docs/spline_reticulator_01_context.png`
* If the image file already exists it will be overwritten
* The resulting image files are created in the same directory as the YAML file, with the same base
filename and the appropriate extensions
* E.G. `docs/spline_reticulator_01_context.yaml` may yield
`docs/spline_reticulator_01_context.png` and/or `docs/spline_reticulator_01_context.html`
* The default output format is PNG but users may specify which format(s) should be rendered
* If an image file already exists it will be overwritten


## Setup
Expand Down Expand Up @@ -138,8 +140,8 @@ Basic usage: `fc4 OPTIONS PATH [PATH...]`
* When processing, the tool overwrites files in place:
* If the [formatting](#formatting) or [snapping](#snapping) features are specified, the YAML file
will be overwritten in place
* If the [rendering](#rendering) feature is specified, the PNG file, if it already exists, will
be overwritten in place
* If the [rendering](#rendering) feature is specified, the image files, if they already exist,
will be overwritten in place

When invoked with the `-w | --watch` option, instead of immediately processing the diagrams and
exiting, the tool will start up in a persistent mode, watching the YAML files and processing them
Expand Down Expand Up @@ -168,14 +170,37 @@ Reformats each specified Structurizr Express YAML file as described [above](#for
Renders each specified Structurizr Express YAML file as described [above](#rendering).

* The resulting image files are created in the same directory as their corresponding YAML files,
with the same base filename and the `png` extension
with the same base filename and, by default, the `png` extension
* e.g. `docs/spline_reticulator_01_context.yaml` yields `docs/spline_reticulator_01_context.png`
* If the image file already exists it will be overwritten
* Output formats may be specified via `-o | --output-formats`
* If an image file already exists it will be overwritten

##### `-s | --snap`

If specified, elements in diagrams will be [snapped](#snapping) to a virtual grid.

#### Output Formats

##### `-o FORMATS | --output-formats FORMATS`

Specifies the output format(s) for rendering diagrams.

* Allowed only when `-r | --render` is specified
* Value must be a character-delimited list of output formats
* The formats allowed are `png` and `svg`
* The delimiters allowed are `+` (plus sign) and `,` (comma)
* If not specified, the default is `png`
* SVG diagrams are written to files with the filename extension `.html`

Here are some examples of valid ways to use this option:

* `-o png`
* `-o svg`
* `-o png+svg`
* `-o svg,png`
* `--output-formats=svg+png`
* `--output-formats png,svg`

#### Watching

##### `-w | --watch`
Expand Down
3 changes: 2 additions & 1 deletion tool/deps.edn
Expand Up @@ -49,7 +49,8 @@
:extra-deps {org.clojure/test.check {:mvn/version "0.10.0"}
eftest {:mvn/version "0.5.8"}
orchestra {:mvn/version "2018.12.06-2"}
image-resizer {:mvn/version "0.1.10"}}
image-resizer {:mvn/version "0.1.10"}
info.debatty/java-string-similarity {:mvn/version "1.2.1"}}
; It’s crucial to ensure that the JVM’s default character encoding is
; UTF-8 because the renderer outputs UTF-8 encoded text to its stderr,
; which the main program (the JVM program) then needs to read correctly.
Expand Down
Expand Up @@ -3,13 +3,13 @@
[clj-chrome-devtools.impl.connection :refer [connect connection? make-ws-client]]
[clojure.java.io :refer [file]]
[clojure.spec.alpha :as s]
[clojure.string :as str :refer [blank? ends-with? includes? join split starts-with? trim]]
[clojure.string :as str :refer [blank? ends-with? includes? join starts-with?]]
[cognitect.anomalies :as anom]
[fc4.image-utils :refer [bytes->buffered-image buffered-image->bytes png-data-uri->bytes width height]]
[fc4.integrations.structurizr.express.spec] ;; for side effects
[fc4.io.util :refer [debug? debug]]
[fc4.rendering :as r :refer [Renderer]]
[fc4.util :refer [fault namespaces qualify-keys with-timeout]]
[fc4.util :refer [fault with-timeout]]
[fc4.yaml :as yaml])
(:import [java.awt Color Font Image RenderingHints]
[java.awt.image BufferedImage]))
Expand Down Expand Up @@ -151,30 +151,7 @@
:ret (s/or :success nil?
:failure ::anom/anomaly))

(defn- dlog [prefix it]
(debug prefix (subs it 0 1000))
it)

(defn- extract-diagram
"Returns, as a bytearray, a PNG image of the current diagram. set-yaml-and-update-diagram must
have already been called."
[automation]
(debug "Extracting diagram...")
(->> "structurizr.scripting.exportCurrentDiagramToPNG({crop: false});"
(a/evaluate automation)
(dlog "Result of exportCurrentDiagramToPNG:")
(png-data-uri->bytes)))

(defn- extract-key
"Returns, as a bytearray, a PNG image of the current diagram’s key. set-yaml-and-update-diagram
must have already been called."
[automation]
(debug "Extracting key...")
(->> "structurizr.scripting.exportCurrentDiagramKeyToPNG();"
(a/evaluate automation)
(png-data-uri->bytes)))

(defn- conjoin
(defn- conjoin-png
[diagram-image key-image]
(debug "Conjoining diagram and key...")
; There are a few casts to int below; they’re to avoid reflection.
Expand Down Expand Up @@ -207,20 +184,47 @@
(.drawString "Key" (int (+ kx key-title-y-offset)) (int (+ ky key-title-x-offset))))
(buffered-image->bytes ci)))

(defn- extract-diagram-png
"Returns a PNG image of the current diagram. set-yaml-and-update-diagram must have already been
called."
[automation]
(debug "Extracting diagram as PNG...")
(let [main (png-data-uri->bytes
(a/evaluate automation "structurizr.scripting.exportCurrentDiagramToPNG();"))
key (png-data-uri->bytes
(a/evaluate automation "structurizr.scripting.exportCurrentDiagramKeyToPNG();"))]
#:fc4.rendering.png{:main main
:key key
:conjoined (conjoin-png main key)}))

(defn- extract-diagram-svg
"Returns an SVG image of the current diagram. set-yaml-and-update-diagram must have already been
called."
[automation]
(debug "Extracting diagram as SVG...")
(let [main (a/evaluate automation "structurizr.scripting.exportCurrentDiagramToSVG();")
key (a/evaluate automation "structurizr.scripting.exportCurrentDiagramKeyToSVG();")]
#:fc4.rendering.svg{:main main
:key key
:conjoined (str main "\n\n" key "\n")}))

(defn- do-render
"Renders a Structurizr Express diagram as a PNG file, returning a PNG bytearray on success. Not
entirely pure; communicates with a child process to perform the rendering."
[diagram-yaml automation {:keys [structurizr-express-url timeout-ms]}]
;; Protect developers from themselves
{:pre [(not (ends-with? diagram-yaml ".yaml")) (not (ends-with? diagram-yaml ".yml"))]}
"Renders a Structurizr Express diagram as PNG and/or SVG images. Not entirely pure; communicates
with a child process to perform the rendering."
[diagram-yaml automation {:keys [structurizr-express-url timeout-ms output-formats] :as opts}]
{:pre [(not (ends-with? diagram-yaml ".yaml"))
(not (ends-with? diagram-yaml ".yml"))
(seq output-formats)]}
(debug "Rendering with options:" opts)
(try
(with-timeout timeout-ms
(or (load-structurizr-express automation structurizr-express-url)
(set-yaml-and-update-diagram automation (prep-yaml diagram-yaml))
(let [diagram-image (extract-diagram automation)
key-image (extract-key automation)
final-image (conjoin diagram-image key-image)]
{::r/png-bytes final-image})))
{::r/images (merge {}
(when (contains? output-formats :png)
{::r/png (extract-diagram-png automation)})
(when (contains? output-formats :svg)
{::r/svg (extract-diagram-svg automation)}))}))
(catch Exception e
(fault (str e)))))

Expand All @@ -237,6 +241,7 @@
(defrecord ChromiumRenderer [browser conn automation opts]
Renderer
(render [renderer diagram-yaml] (do-render diagram-yaml automation opts))
(render [renderer diagram-yaml options] (do-render diagram-yaml automation (merge opts options)))

java.io.Closeable
(close [renderer] (.destroy (:browser renderer))))
Expand All @@ -246,7 +251,8 @@
:timeout-ms 30000
:headless true
:debug-port 9222
:debug-conn-timeout-ms 5000})
:debug-conn-timeout-ms 5000
:output-formats #{:png}})

(def ws-client-opts
"Options for make-ws-client."
Expand Down
31 changes: 25 additions & 6 deletions tool/src/main/fc4/io/cli/main.clj
Expand Up @@ -3,11 +3,12 @@
(:gen-class)
(:require [clj-yaml.core :refer [parse-string]]
[clojure.pprint :refer [pprint]]
[clojure.string :as str :refer [join lower-case]]
[clojure.set :refer [subset?]]
[clojure.string :as str :refer [join lower-case split trim]]
[clojure.tools.cli :refer [parse-opts]]
[fc4.io.cli.util :as cu :refer [beep exit fail]]
[fc4.io.render :refer [render-diagram-file]]
[fc4.io.util :refer [debug? print-now read-text-file]]
[fc4.io.util :refer [debug debug? print-now read-text-file]]
[fc4.io.watch :as watch]
[fc4.io.yaml :refer [validate]]
[fc4.integrations.structurizr.express.chromium-renderer :as cr]
Expand All @@ -21,8 +22,19 @@
[["-f" "--format" (str "Rewrites diagram YAML files, reformatting the YAML to improve readability"
" and diffability.")]
["-s" "--snap" "Rewrites diagram YAML files, snapping elements to a grid and aligning elements."]
["-r" "--render" (str "Creates PNG image files that contain the visualization of each diagram as"
" specified in the YAML files.")]
["-r" "--render"
(str "Creates image files that contain the visualization of each diagram as specified in the"
" YAML files. The format(s) can be specified via -o/--output-formats.")]
["-o" "--output-formats FORMAT"
(str "Specifies the output format(s) for rendering diagrams. Allowed only when -r/--render is"
" specified. Value is a character-delimited list of output formats. Supported formats are"
" 'png' and 'svg'; supported delimiters are '+' (plus sign) and ',' (comma). If not"
" specified, the default is 'png'. NB: svg output is written to files with the extension"
" '.html'.")
:parse-fn #(->> (split % #"[\+,]")
(map (comp keyword lower-case trim))
(set))
:validate [#(subset? % #{:png :svg}) "Supported formats are 'png' and 'svg'."]]
["-w" "--watch" (str "Watches the diagrams in/under the specified paths and processes them (as"
" per the options above) when they change.")]
["-h" "--help" "Prints the synopsis and a list of the most commonly used commands and exits. Other options are ignored."]
Expand All @@ -35,6 +47,8 @@
"render" "fc4 -r"})

(def defaults
;; TODO: make to-closest configurable via CLI options. I’ve found cases wherein a coarser-grained
;; grid would be very helpful.
{:snap {:to-closest 100
:min-margin 50}})

Expand Down Expand Up @@ -65,7 +79,7 @@
NB: exit and fail usually call System/exit but their normal behaviors can be overridden by
changing the contents of the atoms in exit-on-exit? and exit-on-fail?"
[{:keys [arguments summary errors]
{:keys [format snap render help]} :options}]
{:keys [format snap render help output-formats]} :options}]
(let [; Normalize the first arg so we can check whether it’s a legacy subcommand.
first-arg (some-> arguments first lower-case)]
(cond help
Expand All @@ -83,6 +97,10 @@
(fail (usage-message summary (str "NB: At least one of -f, -s, or -r (or their"
" full-length equivalents) MUST be specified")))

(and output-formats (not render))
(fail (usage-message (str "-o/--output-formats is allowed only when -r/--render is"
" specified")))

(empty? arguments)
(fail (usage-message summary "NB: At least one path MUST be specified")))))

Expand Down Expand Up @@ -119,7 +137,8 @@
(spit file-path))))))

(when render
(with-msg "rendering" render-diagram-file file-path renderer))
(debug "calling render-diagram-file for" file-path "with options" options)
(with-msg "rendering" render-diagram-file file-path renderer options))

(catch Exception e
(when watch (beep)) ; good chance the user’s terminal is in the background
Expand Down

0 comments on commit 5d2d75e

Please sign in to comment.