Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,14 @@ def html = gmd.gmdToHtml(text, [name: "Per"])
gmd.htmlToPdf(html, new File("pdfFile.pdf"))
```

GMD supports the [Matrix](https://github.com/Alipsa/matrix) library directly, i.e. Matrix, Chart and MatrixXChart
types can be used with the `out` PrintWriter object without needing to convert them into markdown first.
GMD supports the [Matrix](https://github.com/Alipsa/matrix) library directly. `Matrix`,
`se.alipsa.matrix.pict.Chart`, `se.alipsa.matrix.charm.Chart`, `se.alipsa.matrix.gg.GgChart`, and
`MatrixXChart` types can be used with the `out` PrintWriter object without needing to convert them
into markdown first. Pict, Charm, and GG charts accept `width`, `height`, optional alt text, and
optional HTML attributes (for example, `out.println(chart, 640, 480, 'Sales chart', [class: 'chart'])`).
They also accept alt text and attributes without explicit dimensions. Alt-only GG printing honors
the dimensions configured on the chart; explicit GG dimensions are restored after rendering.
MatrixXChart accepts optional alt text and HTML attributes.
Here is an example:
````
# Employees
Expand Down
8 changes: 8 additions & 0 deletions gmd-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,14 @@
<groupId>se.alipsa.matrix</groupId>
<artifactId>matrix-pict</artifactId>
</dependency>
<dependency>
<groupId>se.alipsa.matrix</groupId>
<artifactId>matrix-charts</artifactId>
</dependency>
<dependency>
<groupId>se.alipsa.matrix</groupId>
<artifactId>matrix-ggplot</artifactId>
</dependency>
<dependency>
<groupId>se.alipsa.matrix</groupId>
<artifactId>matrix-xchart</artifactId>
Expand Down
83 changes: 78 additions & 5 deletions gmd-core/src/main/groovy/se/alipsa/gmd/core/Printer.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@ package se.alipsa.gmd.core
import org.jsoup.nodes.Entities
import se.alipsa.groovy.svg.Svg
import se.alipsa.matrix.chartexport.ChartToSvg
import se.alipsa.matrix.charm.Chart as CharmChart
import se.alipsa.matrix.gg.GgChart
import se.alipsa.matrix.pict.CharmBridge;
import se.alipsa.matrix.pict.Chart;
import se.alipsa.matrix.core.Matrix
import se.alipsa.matrix.xchart.abstractions.MatrixXChart;

class Printer extends PrintWriter {

private static final int DEFAULT_CHART_WIDTH = 800
private static final int DEFAULT_CHART_HEIGHT = 600

Printer() {
super(new StringWriter());
}
Expand Down Expand Up @@ -51,19 +56,39 @@ class Printer extends PrintWriter {


private static String chartToMd(Chart x, double width, double height, String alt, Map<String, String> attributes) {
Svg svg = CharmBridge.renderSvg(x, width as int, height as int)
svgToMd(CharmBridge.renderSvg(x, width as int, height as int), alt, attributes)
}

private static String svgToMd(Svg svg, String alt, Map<String, String> attributes) {
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
ChartToSvg.export(svg, os)
String imgContent = Base64.getEncoder().encodeToString(os.toByteArray())
return imgToHtml("data:image/svg+xml;base64,${imgContent}", alt, attributes)
imgToHtml("data:image/svg+xml;base64,${imgContent}", alt, attributes)
}
}

private static String chartToMd(MatrixXChart x, String alt, Map<String, String> attributes) {
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
x.exportSvg(os)
String imgContent = Base64.getEncoder().encodeToString(os.toByteArray())
return imgToHtml("data:image/svg+xml;base64,${imgContent}", alt, attributes)
imgToHtml("data:image/svg+xml;base64,${imgContent}", alt, attributes)
}
}

private static String chartToMd(CharmChart x, double width, double height, String alt, Map<String, String> attributes) {
svgToMd(x.render(width as int, height as int), alt, attributes)
}

private static String chartToMd(GgChart x, double width, double height, String alt, Map<String, String> attributes) {
int originalWidth = x.width
int originalHeight = x.height
try {
x.width = width as int
x.height = height as int
svgToMd(x.render(), alt, attributes)
} finally {
x.width = originalWidth
x.height = originalHeight
}
}

Expand Down Expand Up @@ -104,14 +129,62 @@ class Printer extends PrintWriter {
print(isAtLineStart() ? terminateHtmlBlock(html) : html)
}

void print(Chart x, double width = 800, double height = 600, String alt = '', Map<String, String> attributes = [:]) {
void print(Chart x, double width = DEFAULT_CHART_WIDTH, double height = DEFAULT_CHART_HEIGHT, String alt = '', Map<String, String> attributes = [:]) {
printChart(chartToMd(x, width, height, alt, attributes))
}

void println(Chart x, double width = DEFAULT_CHART_WIDTH, double height = DEFAULT_CHART_HEIGHT, String alt = '', Map<String, String> attributes = [:]) {
println(chartToMd(x, width, height, alt, attributes))
}

void print(CharmChart x, double width = DEFAULT_CHART_WIDTH, double height = DEFAULT_CHART_HEIGHT, String alt = '', Map<String, String> attributes = [:]) {
printChart(chartToMd(x, width, height, alt, attributes))
}

void println(CharmChart x, double width = DEFAULT_CHART_WIDTH, double height = DEFAULT_CHART_HEIGHT, String alt = '', Map<String, String> attributes = [:]) {
println(chartToMd(x, width, height, alt, attributes))
}

void print(CharmChart x, String alt, Map<String, String> attributes = [:]) {
printChart(chartToMd(x, DEFAULT_CHART_WIDTH, DEFAULT_CHART_HEIGHT, alt, attributes))
}

void println(CharmChart x, String alt, Map<String, String> attributes = [:]) {
println(chartToMd(x, DEFAULT_CHART_WIDTH, DEFAULT_CHART_HEIGHT, alt, attributes))
}

void print(Chart x, String alt, Map<String, String> attributes = [:]) {
printChart(chartToMd(x, DEFAULT_CHART_WIDTH, DEFAULT_CHART_HEIGHT, alt, attributes))
}

void println(Chart x, String alt, Map<String, String> attributes = [:]) {
println(chartToMd(x, DEFAULT_CHART_WIDTH, DEFAULT_CHART_HEIGHT, alt, attributes))
}

/**
* Renders a GgChart at the supplied dimensions. The chart's original
* dimensions are restored before this method returns.
*/
void print(GgChart x, double width = DEFAULT_CHART_WIDTH, double height = DEFAULT_CHART_HEIGHT, String alt = '', Map<String, String> attributes = [:]) {
printChart(chartToMd(x, width, height, alt, attributes))
}

void println(Chart x, double width = 800, double height = 600, String alt = '', Map<String, String> attributes = [:]) {
/**
* Renders a GgChart at the supplied dimensions. The chart's original
* dimensions are restored before this method returns.
*/
void println(GgChart x, double width = DEFAULT_CHART_WIDTH, double height = DEFAULT_CHART_HEIGHT, String alt = '', Map<String, String> attributes = [:]) {
println(chartToMd(x, width, height, alt, attributes))
}

void print(GgChart x, String alt, Map<String, String> attributes = [:]) {
printChart(svgToMd(x.render(), alt, attributes))
}

void println(GgChart x, String alt, Map<String, String> attributes = [:]) {
println(svgToMd(x.render(), alt, attributes))
}

void print(MatrixXChart x, String alt = '', Map<String, String> attributes = [:]) {
printChart(chartToMd(x, alt, attributes))
}
Expand Down
66 changes: 64 additions & 2 deletions gmd-core/src/test/groovy/test/alipsa/groovy/gmd/GmdTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -547,13 +547,75 @@ def empData = Matrix.builder().data(
.build()
BarChart chart = BarChart.createVertical("Salaries", empData, "emp_name", ChartType.BASIC, "salary")
out.println(chart)
out.println(chart, 'Pict chart')
```
'''
Gmd gmd = new Gmd()
String md = gmd.gmdToMd(text)
assertTrue(md.contains('# Employees'))
String image = md.split("data:image/svg\\+xml;base64,", 2)[1].split('"', 2)[0]
assertTrue(new String(Base64.decoder.decode(image), StandardCharsets.UTF_8).contains('<svg'), 'Chart should be SVG-backed')
assertTrue(md.contains('<img alt="Pict chart" src="data:image/svg+xml;base64,'), md)
List<String> svgs = decodeSvgImages(md, 'Chart should be SVG-backed')
assertTrue(svgs[0].contains('width="800"') && svgs[0].contains('height="600"'), svgs[0].take(200))
assertTrue(svgs[1].contains('width="800"') && svgs[1].contains('height="600"'), svgs[1].take(200))
}

@Test
void testCharmChart() {
def text = '''
```{groovy echo=false}
import static se.alipsa.matrix.charm.Charts.plot

def chart = plot([x: [1, 2], y: [3, 4]]) {
mapping(x: 'x', y: 'y')
layers { geomPoint() }
}.build()
out.println(chart, 640, 480, 'Sized Charm chart', [class: 'chart'])
out.println(chart, 'Charm chart', [class: 'chart'])
```
'''

String md = new Gmd().gmdToMd(text)

assertTrue(md.contains('<img alt="Charm chart" src="data:image/svg+xml;base64,'), md)
assertTrue(md.contains('class="chart"'), md)
List<String> svgs = decodeSvgImages(md, 'Charm chart should be SVG-backed')
assertTrue(svgs[0].contains('width="640"') && svgs[0].contains('height="480"'), svgs[0].take(200))
assertTrue(svgs[1].contains('width="800"') && svgs[1].contains('height="600"'), svgs[1].take(200))
}

@Test
void testGgChart() {
def text = '''
```{groovy echo=false}
import static se.alipsa.matrix.gg.GgPlot.*
import se.alipsa.matrix.core.Matrix

def data = Matrix.builder().data(x: [1, 2], y: [3, 4]).types(int, int).build()
def chart = ggplot(data, aes(x: 'x', y: 'y')) + geom_point()
chart.width = 1200
chart.height = 900
out.println(chart, 640, 480, 'GG chart', [class: 'chart'])
out.println(chart, 'GG default chart')
```
'''

String md = new Gmd().gmdToMd(text)

assertTrue(md.contains('<img alt="GG chart" src="data:image/svg+xml;base64,'), md)
assertTrue(md.contains('class="chart"'), md)
List<String> svgs = decodeSvgImages(md, 'GG chart should be SVG-backed')
assertTrue(svgs[0].contains('width="640"') && svgs[0].contains('height="480"'), svgs[0].take(200))
assertTrue(svgs[1].contains('width="1200"') && svgs[1].contains('height="900"'), svgs[1].take(200))
}

private static List<String> decodeSvgImages(String markdown, String message) {
String marker = 'data:image/svg+xml;base64,'
assertTrue(markdown.contains(marker), "$message — no image in: $markdown")
List<String> svgs = markdown.split(java.util.regex.Pattern.quote(marker)).drop(1).collect { image ->
new String(Base64.decoder.decode(image.split('"', 2)[0]), StandardCharsets.UTF_8)
}
svgs.each { svg -> assertTrue(svg.contains('<svg'), message) }
return svgs
}

private static String chartDocument(String output) {
Expand Down
35 changes: 0 additions & 35 deletions req/v3.2.0-improvements.md

This file was deleted.

Loading