Merge branch 'master' into link-layers
This commit is contained in:
commit
a02896c6da
10 changed files with 88 additions and 28 deletions
|
|
@ -2,4 +2,9 @@
|
||||||
|
|
||||||
#### Improvements 🧹
|
#### Improvements 🧹
|
||||||
|
|
||||||
|
- PDF exports now support external links on shapes [#891](https://github.com/terrastruct/d2/issues/891)
|
||||||
|
|
||||||
#### Bugfixes ⛑️
|
#### Bugfixes ⛑️
|
||||||
|
|
||||||
|
- Fixes a regression where PNG backgrounds could be cut off in the appendix. [#941](https://github.com/terrastruct/d2/pull/941)
|
||||||
|
- Fixes zooming not working in watch mode. [#944](https://github.com/terrastruct/d2/pull/944)
|
||||||
|
|
|
||||||
2
ci/sub
2
ci/sub
|
|
@ -1 +1 @@
|
||||||
Subproject commit 5198280010adc30aabb611579579916abfe20a45
|
Subproject commit 690bc39e545cae76314fa32effe343a088e2a52e
|
||||||
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -488,7 +489,16 @@ func renderPDF(ctx context.Context, ms *xmain.State, plugin d2plugin.Plugin, ske
|
||||||
return svg, err
|
return svg, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = pdf.AddPDFPage(pngImg, currBoardPath, themeID, rootFill)
|
viewboxSlice := appendix.FindViewboxSlice(svg)
|
||||||
|
viewboxX, err := strconv.ParseFloat(viewboxSlice[0], 64)
|
||||||
|
if err != nil {
|
||||||
|
return svg, err
|
||||||
|
}
|
||||||
|
viewboxY, err := strconv.ParseFloat(viewboxSlice[1], 64)
|
||||||
|
if err != nil {
|
||||||
|
return svg, err
|
||||||
|
}
|
||||||
|
err = pdf.AddPDFPage(pngImg, currBoardPath, themeID, rootFill, diagram.Shapes, pad, viewboxX, viewboxY)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return svg, err
|
return svg, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,3 @@
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#d2-svg-container > svg {
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#d2-err {
|
#d2-err {
|
||||||
/* This style was copied from Chrome's svg parser error style. */
|
/* This style was copied from Chrome's svg parser error style. */
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,8 @@ function init(reconnectDelay) {
|
||||||
const ws = new WebSocket(
|
const ws = new WebSocket(
|
||||||
`ws://${window.location.host}${window.location.pathname}watch`
|
`ws://${window.location.host}${window.location.pathname}watch`
|
||||||
);
|
);
|
||||||
|
let isInit = true;
|
||||||
|
let ratio;
|
||||||
ws.onopen = () => {
|
ws.onopen = () => {
|
||||||
reconnectDelay = 1000;
|
reconnectDelay = 1000;
|
||||||
console.info("watch websocket opened");
|
console.info("watch websocket opened");
|
||||||
|
|
@ -31,6 +33,29 @@ function init(reconnectDelay) {
|
||||||
// setting innerHTML to only the actual svg innards. However then you also need to parse
|
// setting innerHTML to only the actual svg innards. However then you also need to parse
|
||||||
// out the width, height and viewbox out of the top level SVG tag and update those manually.
|
// out the width, height and viewbox out of the top level SVG tag and update those manually.
|
||||||
d2SVG.innerHTML = msg.svg;
|
d2SVG.innerHTML = msg.svg;
|
||||||
|
|
||||||
|
const svgEl = d2SVG.querySelector("#d2-svg");
|
||||||
|
// just use inner SVG in watch mode
|
||||||
|
svgEl.parentElement.replaceWith(svgEl);
|
||||||
|
let width = parseInt(svgEl.getAttribute("width"), 10);
|
||||||
|
let height = parseInt(svgEl.getAttribute("height"), 10);
|
||||||
|
if (isInit) {
|
||||||
|
if (width > height) {
|
||||||
|
if (width > window.innerWidth) {
|
||||||
|
ratio = window.innerWidth / width;
|
||||||
|
}
|
||||||
|
} else if (height > window.innerHeight) {
|
||||||
|
ratio = window.innerHeight / height;
|
||||||
|
}
|
||||||
|
// Scale svg fit to zoom
|
||||||
|
isInit = false;
|
||||||
|
}
|
||||||
|
if (ratio) {
|
||||||
|
// body padding is 8px
|
||||||
|
svgEl.setAttribute("width", width * ratio - 16);
|
||||||
|
svgEl.setAttribute("height", height * ratio - 16);
|
||||||
|
}
|
||||||
|
|
||||||
d2ErrDiv.style.display = "none";
|
d2ErrDiv.style.display = "none";
|
||||||
}
|
}
|
||||||
if (msg.err) {
|
if (msg.err) {
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,14 @@ const (
|
||||||
var viewboxRegex = regexp.MustCompile(`viewBox=\"([0-9\- ]+)\"`)
|
var viewboxRegex = regexp.MustCompile(`viewBox=\"([0-9\- ]+)\"`)
|
||||||
var widthRegex = regexp.MustCompile(`width=\"([.0-9]+)\"`)
|
var widthRegex = regexp.MustCompile(`width=\"([.0-9]+)\"`)
|
||||||
var heightRegex = regexp.MustCompile(`height=\"([.0-9]+)\"`)
|
var heightRegex = regexp.MustCompile(`height=\"([.0-9]+)\"`)
|
||||||
|
var svgRegex = regexp.MustCompile(`<svg(.*?)>`)
|
||||||
|
|
||||||
|
func FindViewboxSlice(svg []byte) []string {
|
||||||
|
viewboxMatches := viewboxRegex.FindAllStringSubmatch(string(svg), 2)
|
||||||
|
viewboxMatch := viewboxMatches[1]
|
||||||
|
viewboxRaw := viewboxMatch[1]
|
||||||
|
return strings.Split(viewboxRaw, " ")
|
||||||
|
}
|
||||||
|
|
||||||
func Append(diagram *d2target.Diagram, ruler *textmeasure.Ruler, in []byte) []byte {
|
func Append(diagram *d2target.Diagram, ruler *textmeasure.Ruler, in []byte) []byte {
|
||||||
svg := string(in)
|
svg := string(in)
|
||||||
|
|
@ -91,14 +99,22 @@ func Append(diagram *d2target.Diagram, ruler *textmeasure.Ruler, in []byte) []by
|
||||||
newOuterViewbox := fmt.Sprintf(`viewBox="0 0 %d %d"`, viewboxWidth, viewboxHeight)
|
newOuterViewbox := fmt.Sprintf(`viewBox="0 0 %d %d"`, viewboxWidth, viewboxHeight)
|
||||||
newViewbox := fmt.Sprintf(`viewBox="%s %s %s %s"`, viewboxSlice[0], viewboxSlice[1], strconv.Itoa(viewboxWidth), strconv.Itoa(viewboxHeight))
|
newViewbox := fmt.Sprintf(`viewBox="%s %s %s %s"`, viewboxSlice[0], viewboxSlice[1], strconv.Itoa(viewboxWidth), strconv.Itoa(viewboxHeight))
|
||||||
|
|
||||||
widthMatches := widthRegex.FindAllStringSubmatch(svg, 2)
|
dimensionsToUpdate := 2
|
||||||
heightMatches := heightRegex.FindAllStringSubmatch(svg, 2)
|
outerSVG := svgRegex.FindString(svg)
|
||||||
|
// if outer svg has dimensions set we also need to update it
|
||||||
|
if widthRegex.FindString(outerSVG) != "" {
|
||||||
|
dimensionsToUpdate++
|
||||||
|
}
|
||||||
|
|
||||||
|
// update 1st 3 matches of width and height 1st is outer svg (if dimensions are set), 2nd inner svg, 3rd is background color rect
|
||||||
|
widthMatches := widthRegex.FindAllStringSubmatch(svg, dimensionsToUpdate)
|
||||||
|
heightMatches := heightRegex.FindAllStringSubmatch(svg, dimensionsToUpdate)
|
||||||
newWidth := fmt.Sprintf(`width="%s"`, strconv.Itoa(viewboxWidth))
|
newWidth := fmt.Sprintf(`width="%s"`, strconv.Itoa(viewboxWidth))
|
||||||
newHeight := fmt.Sprintf(`height="%s"`, strconv.Itoa(viewboxHeight))
|
newHeight := fmt.Sprintf(`height="%s"`, strconv.Itoa(viewboxHeight))
|
||||||
|
|
||||||
svg = strings.Replace(svg, viewboxMatches[0][0], newOuterViewbox, 1)
|
svg = strings.Replace(svg, viewboxMatches[0][0], newOuterViewbox, 1)
|
||||||
svg = strings.Replace(svg, viewboxMatch[0], newViewbox, 1)
|
svg = strings.Replace(svg, viewboxMatch[0], newViewbox, 1)
|
||||||
for i := 0; i < 2; i++ {
|
for i := 0; i < dimensionsToUpdate; i++ {
|
||||||
svg = strings.Replace(svg, widthMatches[i][0], newWidth, 1)
|
svg = strings.Replace(svg, widthMatches[i][0], newWidth, 1)
|
||||||
svg = strings.Replace(svg, heightMatches[i][0], newHeight, 1)
|
svg = strings.Replace(svg, heightMatches[i][0], newHeight, 1)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2
go.mod
generated
2
go.mod
generated
|
|
@ -23,7 +23,7 @@ require (
|
||||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2
|
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2
|
||||||
gonum.org/v1/plot v0.12.0
|
gonum.org/v1/plot v0.12.0
|
||||||
nhooyr.io/websocket v1.8.7
|
nhooyr.io/websocket v1.8.7
|
||||||
oss.terrastruct.com/util-go v0.0.0-20230228050345-d1fed4d6be62
|
oss.terrastruct.com/util-go v0.0.0-20230301015829-35b30391c74d
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|
|
||||||
4
go.sum
generated
4
go.sum
generated
|
|
@ -277,6 +277,6 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh
|
||||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g=
|
nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g=
|
||||||
nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
|
nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
|
||||||
oss.terrastruct.com/util-go v0.0.0-20230228050345-d1fed4d6be62 h1:XQZNMkHQr2q1eJIcHcgja29X04oDG8SqqdICCgxe5Bk=
|
oss.terrastruct.com/util-go v0.0.0-20230301015829-35b30391c74d h1:+1Bp2bYA7bieedJuqbiwOLhnMs6GQQLB4sNX7BcDbSQ=
|
||||||
oss.terrastruct.com/util-go v0.0.0-20230228050345-d1fed4d6be62/go.mod h1:Fwy72FDIOOM4K8F96ScXkxHHppR1CPfUyo9+x9c1PBU=
|
oss.terrastruct.com/util-go v0.0.0-20230301015829-35b30391c74d/go.mod h1:Fwy72FDIOOM4K8F96ScXkxHHppR1CPfUyo9+x9c1PBU=
|
||||||
rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4=
|
rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4=
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"github.com/jung-kurt/gofpdf"
|
"github.com/jung-kurt/gofpdf"
|
||||||
|
|
||||||
"oss.terrastruct.com/d2/d2renderers/d2fonts"
|
"oss.terrastruct.com/d2/d2renderers/d2fonts"
|
||||||
|
"oss.terrastruct.com/d2/d2target"
|
||||||
"oss.terrastruct.com/d2/d2themes"
|
"oss.terrastruct.com/d2/d2themes"
|
||||||
"oss.terrastruct.com/d2/d2themes/d2themescatalog"
|
"oss.terrastruct.com/d2/d2themes/d2themescatalog"
|
||||||
"oss.terrastruct.com/d2/lib/color"
|
"oss.terrastruct.com/d2/lib/color"
|
||||||
|
|
@ -19,13 +20,13 @@ type GoFPDF struct {
|
||||||
|
|
||||||
func Init() *GoFPDF {
|
func Init() *GoFPDF {
|
||||||
newGofPDF := gofpdf.NewCustom(&gofpdf.InitType{
|
newGofPDF := gofpdf.NewCustom(&gofpdf.InitType{
|
||||||
UnitStr: "in",
|
UnitStr: "pt",
|
||||||
})
|
})
|
||||||
|
|
||||||
newGofPDF.AddUTF8FontFromBytes("source", "", d2fonts.FontFaces[d2fonts.SourceSansPro.Font(0, d2fonts.FONT_STYLE_REGULAR)])
|
newGofPDF.AddUTF8FontFromBytes("source", "", d2fonts.FontFaces[d2fonts.SourceSansPro.Font(0, d2fonts.FONT_STYLE_REGULAR)])
|
||||||
newGofPDF.AddUTF8FontFromBytes("source", "B", d2fonts.FontFaces[d2fonts.SourceSansPro.Font(0, d2fonts.FONT_STYLE_BOLD)])
|
newGofPDF.AddUTF8FontFromBytes("source", "B", d2fonts.FontFaces[d2fonts.SourceSansPro.Font(0, d2fonts.FONT_STYLE_BOLD)])
|
||||||
newGofPDF.SetAutoPageBreak(false, 0)
|
newGofPDF.SetAutoPageBreak(false, 0)
|
||||||
newGofPDF.SetLineWidth(0.05)
|
newGofPDF.SetLineWidth(2)
|
||||||
newGofPDF.SetMargins(0, 0, 0)
|
newGofPDF.SetMargins(0, 0, 0)
|
||||||
|
|
||||||
fpdf := GoFPDF{
|
fpdf := GoFPDF{
|
||||||
|
|
@ -57,7 +58,7 @@ func (g *GoFPDF) GetFillRGB(themeID int64, fill string) (color.RGB, error) {
|
||||||
return color.Hex2RGB(fill)
|
return color.Hex2RGB(fill)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GoFPDF) AddPDFPage(png []byte, boardPath []string, themeID int64, fill string) error {
|
func (g *GoFPDF) AddPDFPage(png []byte, boardPath []string, themeID int64, fill string, shapes []d2target.Shape, pad int64, viewboxX, viewboxY float64) error {
|
||||||
var opt gofpdf.ImageOptions
|
var opt gofpdf.ImageOptions
|
||||||
opt.ImageType = "png"
|
opt.ImageType = "png"
|
||||||
imageInfo := g.pdf.RegisterImageOptionsReader(strings.Join(boardPath, "/"), opt, bytes.NewReader(png))
|
imageInfo := g.pdf.RegisterImageOptionsReader(strings.Join(boardPath, "/"), opt, bytes.NewReader(png))
|
||||||
|
|
@ -73,10 +74,10 @@ func (g *GoFPDF) AddPDFPage(png []byte, boardPath []string, themeID int64, fill
|
||||||
|
|
||||||
g.pdf.SetFont("source", "B", 14)
|
g.pdf.SetFont("source", "B", 14)
|
||||||
pathString := strings.Join(boardPath, " / ")
|
pathString := strings.Join(boardPath, " / ")
|
||||||
headerMargin := 0.3
|
headerMargin := 28.0
|
||||||
headerWidth := g.pdf.GetStringWidth(pathString) + 2*headerMargin
|
headerWidth := g.pdf.GetStringWidth(pathString) + 2*headerMargin
|
||||||
|
|
||||||
minPageDimension := 6.0
|
minPageDimension := 576.0
|
||||||
pageWidth = math.Max(math.Max(minPageDimension, imageWidth), headerWidth)
|
pageWidth = math.Max(math.Max(minPageDimension, imageWidth), headerWidth)
|
||||||
pageHeight = math.Max(minPageDimension, imageHeight)
|
pageHeight = math.Max(minPageDimension, imageHeight)
|
||||||
|
|
||||||
|
|
@ -86,7 +87,7 @@ func (g *GoFPDF) AddPDFPage(png []byte, boardPath []string, themeID int64, fill
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add page
|
// Add page
|
||||||
headerHeight := 0.75
|
headerHeight := 72.0
|
||||||
g.pdf.AddPageFormat("", gofpdf.SizeType{Wd: pageWidth, Ht: pageHeight + headerHeight})
|
g.pdf.AddPageFormat("", gofpdf.SizeType{Wd: pageWidth, Ht: pageHeight + headerHeight})
|
||||||
|
|
||||||
// Draw header
|
// Draw header
|
||||||
|
|
@ -117,17 +118,30 @@ func (g *GoFPDF) AddPDFPage(png []byte, boardPath []string, themeID int64, fill
|
||||||
g.pdf.CellFormat(pageWidth-prefixWidth-headerMargin, headerHeight, boardName, "", 0, "", false, 0, "")
|
g.pdf.CellFormat(pageWidth-prefixWidth-headerMargin, headerHeight, boardName, "", 0, "", false, 0, "")
|
||||||
|
|
||||||
// Draw image
|
// Draw image
|
||||||
g.pdf.ImageOptions(strings.Join(boardPath, "/"), (pageWidth-imageWidth)/2, headerHeight+(pageHeight-imageHeight)/2, imageWidth, imageHeight, false, opt, 0, "")
|
imageX := (pageWidth - imageWidth) / 2
|
||||||
|
imageY := headerHeight + (pageHeight-imageHeight)/2
|
||||||
|
g.pdf.ImageOptions(strings.Join(boardPath, "/"), imageX, imageY, imageWidth, imageHeight, false, opt, 0, "")
|
||||||
|
|
||||||
|
// Draw external links
|
||||||
|
for _, shape := range shapes {
|
||||||
|
if shape.Link != "" {
|
||||||
|
linkX := imageX + float64(shape.Pos.X) - viewboxX - float64(shape.StrokeWidth)
|
||||||
|
linkY := imageY + float64(shape.Pos.Y) - viewboxY - float64(shape.StrokeWidth)
|
||||||
|
linkWidth := float64(shape.Width) + float64(shape.StrokeWidth*2)
|
||||||
|
linkHeight := float64(shape.Height) + float64(shape.StrokeWidth*2)
|
||||||
|
g.pdf.LinkString(linkX, linkY, linkWidth, linkHeight, shape.Link)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Draw header/img seperator
|
// Draw header/img seperator
|
||||||
g.pdf.SetXY(headerMargin, headerHeight)
|
g.pdf.SetXY(headerMargin, headerHeight)
|
||||||
g.pdf.SetLineWidth(0.01)
|
g.pdf.SetLineWidth(1)
|
||||||
if fillRGB.IsLight() {
|
if fillRGB.IsLight() {
|
||||||
g.pdf.SetDrawColor(10, 15, 37) // steel-900
|
g.pdf.SetDrawColor(10, 15, 37) // steel-900
|
||||||
} else {
|
} else {
|
||||||
g.pdf.SetDrawColor(255, 255, 255)
|
g.pdf.SetDrawColor(255, 255, 255)
|
||||||
}
|
}
|
||||||
g.pdf.CellFormat(pageWidth-(headerMargin*2), 0.01, "", "T", 0, "", false, 0, "")
|
g.pdf.CellFormat(pageWidth-(headerMargin*2), 1, "", "T", 0, "", false, 0, "")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package version
|
package version
|
||||||
|
|
||||||
// Pre-built binaries will have version set correctly during build time.
|
// Pre-built binaries will have version set correctly during build time.
|
||||||
var Version = "v0.2.1-HEAD"
|
var Version = "v0.2.2-HEAD"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue