diff --git a/renderers/gio/gio.go.off b/renderers/gio/gio.go.off new file mode 100644 index 0000000..5878f69 --- /dev/null +++ b/renderers/gio/gio.go.off @@ -0,0 +1,170 @@ +package gio + +import ( + "image" + "image/color" + + "gioui.org/f32" + "gioui.org/layout" + "gioui.org/op" + "gioui.org/op/clip" + "gioui.org/op/paint" + "github.com/tdewolff/canvas" +) + +type Gio struct { + ops *op.Ops + width, height float64 + xScale, yScale float64 + dimensions layout.Dimensions +} + +// New returns a Gio renderer of fixed size. +func New(gtx layout.Context, width, height float64) *Gio { + dimensions := layout.Dimensions{Size: image.Point{int(width + 0.5), int(height + 0.5)}} + return &Gio{ + ops: gtx.Ops, + width: width, + height: height, + xScale: 1.0, + yScale: 1.0, + dimensions: dimensions, + } +} + +// NewContain returns a Gio renderer that fills the constraints either horizontally or vertically, whichever is met first. +func NewContain(gtx layout.Context, width, height float64) *Gio { + xScale := float64(gtx.Constraints.Max.X-gtx.Constraints.Min.X) / width + yScale := float64(gtx.Constraints.Max.Y-gtx.Constraints.Min.Y) / height + if yScale < xScale { + xScale = yScale + } else { + yScale = xScale + } + + dimensions := layout.Dimensions{Size: image.Point{int(width*xScale + 0.5), int(height*yScale + 0.5)}} + return &Gio{ + ops: gtx.Ops, + width: width, + height: height, + xScale: xScale, + yScale: yScale, + dimensions: dimensions, + } +} + +// NewStretch returns a Gio renderer that stretches the view to fit the constraints. +func NewStretch(gtx layout.Context, width, height float64) *Gio { + xScale := float64(gtx.Constraints.Max.X-gtx.Constraints.Min.X) / width + yScale := float64(gtx.Constraints.Max.Y-gtx.Constraints.Min.Y) / height + + dimensions := layout.Dimensions{Size: image.Point{int(width*xScale + 0.5), int(height*yScale + 0.5)}} + return &Gio{ + ops: gtx.Ops, + width: width, + height: height, + xScale: xScale, + yScale: yScale, + dimensions: dimensions, + } +} + +// Dimensions returns the dimensions for Gio. +func (r *Gio) Dimensions() layout.Dimensions { + return r.dimensions +} + +// Size returns the size of the canvas in millimeters. +func (r *Gio) Size() (float64, float64) { + return r.width, r.height +} + +func (r *Gio) point(p canvas.Point) f32.Point { + return f32.Point{float32(r.xScale * p.X), float32(r.yScale * (r.height - p.Y))} +} + +func (r *Gio) renderPath(path *canvas.Path, fill canvas.Paint) { + path = path.ReplaceArcs() + + p := clip.Path{} + p.Begin(r.ops) + for scanner := path.Scanner(); scanner.Scan(); { + switch scanner.Cmd() { + case canvas.MoveToCmd: + p.MoveTo(r.point(scanner.End())) + case canvas.LineToCmd: + p.LineTo(r.point(scanner.End())) + case canvas.QuadToCmd: + p.QuadTo(r.point(scanner.CP1()), r.point(scanner.End())) + case canvas.CubeToCmd: + p.CubeTo(r.point(scanner.CP1()), r.point(scanner.CP2()), r.point(scanner.End())) + case canvas.ArcToCmd: + // TODO: ArcTo + p.LineTo(r.point(scanner.End())) + case canvas.CloseCmd: + p.Close() + } + } + + shape := clip.Outline{p.End()} + defer shape.Op().Push(r.ops).Pop() + + if fill.IsColor() { + paint.Fill(r.ops, toNRGBA(fill.Color)) + } else if fill.IsGradient() { + if g, ok := fill.Gradient.(*canvas.LinearGradient); ok && len(g.Stops) == 2 { + linearGradient := paint.LinearGradientOp{} + linearGradient.Stop1 = r.point(g.Start) + linearGradient.Stop2 = r.point(g.End) + linearGradient.Color1 = toNRGBA(g.Stops[0].Color) + linearGradient.Color2 = toNRGBA(g.Stops[1].Color) + linearGradient.Add(r.ops) + paint.PaintOp{}.Add(r.ops) + } + } +} + +// RenderPath renders a path to the canvas using a style and a transformation matrix. +func (r *Gio) RenderPath(path *canvas.Path, style canvas.Style, m canvas.Matrix) { + if style.HasFill() { + r.renderPath(path.Copy().Transform(m), style.Fill) + } + + if style.HasStroke() { + if style.IsDashed() { + dashOffset, dashes := canvas.ScaleDash(style.StrokeWidth, style.DashOffset, style.Dashes) + path = path.Dash(dashOffset, dashes...) + } + path = path.Stroke(style.StrokeWidth, style.StrokeCapper, style.StrokeJoiner, canvas.Tolerance) + r.renderPath(path.Transform(m), style.Stroke) + } +} + +// RenderText renders a text object to the canvas using a transformation matrix. +func (r *Gio) RenderText(text *canvas.Text, m canvas.Matrix) { + text.RenderAsPath(r, m, 0.0) +} + +// RenderImage renders an image to the canvas using a transformation matrix. +func (r *Gio) RenderImage(img image.Image, m canvas.Matrix) { + paint.NewImageOp(img).Add(r.ops) + m = canvas.Identity.Scale(r.xScale, r.yScale).Mul(m) + m = m.Translate(0.0, float64(img.Bounds().Max.Y)) + trans := op.Affine(f32.NewAffine2D( + float32(m[0][0]), -float32(m[0][1]), float32(m[0][2]), + -float32(m[1][0]), float32(m[1][1]), float32(r.yScale*r.height-m[1][2]), + )).Push(r.ops) + paint.PaintOp{}.Add(r.ops) + trans.Pop() +} + +func toNRGBA(col color.Color) color.NRGBA { + r, g, b, a := col.RGBA() + if a == 0 { + return color.NRGBA{} + } + r = (r * 0xffff) / a + g = (g * 0xffff) / a + b = (b * 0xffff) / a + return color.NRGBA{R: uint8(r >> 8), G: uint8(g >> 8), B: uint8(b >> 8), A: uint8(a >> 8)} +} diff --git a/renderers/pdf/writer.go b/renderers/pdf/writer.go index e97e2b2..defa6f6 100644 --- a/renderers/pdf/writer.go +++ b/renderers/pdf/writer.go @@ -450,13 +450,29 @@ end`, bfRangeCount, bfRange.String(), bfCharCount, bfChar.String()) toUnicodeRef := w.writeObject(toUnicodeStream) // write font program - fontfileRef := w.writeObject(pdfStream{ - dict: pdfDict{ - "Subtype": pdfName("OpenType"), - "Filter": pdfFilterFlate, - }, - stream: fontProgram, - }) + var cidSubtype string + var fontfileKey pdfName + var fontfileRef pdfRef + if font.SFNT.IsTrueType { + cidSubtype = "CIDFontType2" + fontfileKey = "FontFile2" + fontfileRef = w.writeObject(pdfStream{ + dict: pdfDict{ + "Filter": pdfFilterFlate, + }, + stream: fontProgram, + }) + } else if font.SFNT.IsCFF { + cidSubtype = "CIDFontType0" + fontfileKey = "FontFile3" + fontfileRef = w.writeObject(pdfStream{ + dict: pdfDict{ + "Subtype": pdfName("OpenType"), + "Filter": pdfFilterFlate, + }, + stream: fontProgram, + }) + } // get name and CID subtype name := font.Name() @@ -473,13 +489,6 @@ end`, bfRangeCount, bfRange.String(), bfCharCount, bfChar.String()) encoding = "Identity-V" } - cidSubtype := "" - if font.SFNT.IsTrueType { - cidSubtype = "CIDFontType2" - } else if font.SFNT.IsCFF { - cidSubtype = "CIDFontType0" - } - // in order to support more than 256 characters, we need to use a CIDFont dictionary which must be inside a Type0 font. Character codes in the stream are glyph IDs, however for subsetted fonts they are the _old_ glyph IDs, which is why we need the CIDToGIDMap dict := pdfDict{ "Type": pdfName("Font"), @@ -488,12 +497,12 @@ end`, bfRangeCount, bfRange.String(), bfCharCount, bfChar.String()) "Encoding": pdfName(encoding), // map character codes in the stream to CID with identity encoding, we additionally map CID to GID in the descendant font when subsetting, otherwise that is also identity "ToUnicode": toUnicodeRef, "DescendantFonts": pdfArray{pdfDict{ - "Type": pdfName("Font"), - "Subtype": pdfName(cidSubtype), - "BaseFont": pdfName(baseFont), - "DW": DW, - "W": W, - "CIDToGIDMap": pdfName("Identity"), + "Type": pdfName("Font"), + "Subtype": pdfName(cidSubtype), + "BaseFont": pdfName(baseFont), + "DW": DW, + "W": W, + //"CIDToGIDMap": pdfName("Identity"), "CIDSystemInfo": pdfDict{ "Registry": "Adobe", "Ordering": "Identity", @@ -514,7 +523,7 @@ end`, bfRangeCount, bfRange.String(), bfCharCount, bfChar.String()) "Descent": -int(f * float64(font.SFNT.Hhea.Descender)), "CapHeight": int(f * float64(font.SFNT.OS2.SCapHeight)), "StemV": 80, // taken from Inkscape, should be calculated somehow, maybe use: 10+220*(usWeightClass-50)/900 - "FontFile3": fontfileRef, + fontfileKey: fontfileRef, }, }}, } diff --git a/renderers/renderers.go b/renderers/renderers.go index d23c103..df5d1be 100644 --- a/renderers/renderers.go +++ b/renderers/renderers.go @@ -210,9 +210,9 @@ func SVGZ(opts ...interface{}) canvas.Writer { } } if options == nil { - options := svg.DefaultOptions + defaultOptions := svg.DefaultOptions + options = &defaultOptions options.Compression = flate.DefaultCompression - opts = append(opts, &options) } else if options.Compression < -2 || options.Compression == 0 || 9 < options.Compression { options.Compression = flate.DefaultCompression }