Implement ClearRect in svg context
This commit is contained in:
parent
1588b49f0d
commit
7419075cb6
3 changed files with 228 additions and 163 deletions
|
@ -26,6 +26,9 @@ func toSvgRGBA(c color.Color) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func toSvgLength(l float64) string {
|
func toSvgLength(l float64) string {
|
||||||
|
if math.IsInf(l, 1) {
|
||||||
|
return "100%"
|
||||||
|
}
|
||||||
return optiSprintf("%f", l)
|
return optiSprintf("%f", l)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
337
draw2dsvg/gc.go
337
draw2dsvg/gc.go
|
@ -102,132 +102,52 @@ func (gc *GraphicContext) Restore() {
|
||||||
// TODO use common transformation group for multiple elements
|
// TODO use common transformation group for multiple elements
|
||||||
}
|
}
|
||||||
|
|
||||||
// private funcitons
|
func (gc *GraphicContext) SetDPI(dpi int) {
|
||||||
|
gc.DPI = dpi
|
||||||
func (gc *GraphicContext) drawPaths(drawType drawType, paths ...*draw2d.Path) {
|
gc.recalc()
|
||||||
// create elements
|
|
||||||
svgPath := Path{}
|
|
||||||
group := gc.newGroup(drawType)
|
|
||||||
|
|
||||||
// set attrs to path element
|
|
||||||
paths = append(paths, gc.Current.Path)
|
|
||||||
svgPathsDesc := make([]string, len(paths))
|
|
||||||
// multiple pathes has to be joined to single svg path description
|
|
||||||
// because fill-rule wont work for whole group as excepted
|
|
||||||
for i, path := range paths {
|
|
||||||
svgPathsDesc[i] = toSvgPathDesc(path)
|
|
||||||
}
|
|
||||||
svgPath.Desc = strings.Join(svgPathsDesc, " ")
|
|
||||||
|
|
||||||
// link to group
|
|
||||||
group.Paths = []*Path{&svgPath}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gc *GraphicContext) drawString(text string, drawType drawType, x, y float64) float64 {
|
func (gc *GraphicContext) GetDPI() int {
|
||||||
// create elements
|
return gc.DPI
|
||||||
svgText := Text{}
|
|
||||||
group := gc.newGroup(drawType)
|
|
||||||
|
|
||||||
// set attrs to text element
|
|
||||||
svgText.Text = text
|
|
||||||
svgText.FontSize = gc.Current.FontSize
|
|
||||||
svgText.X = x
|
|
||||||
svgText.Y = y
|
|
||||||
svgText.FontFamily = gc.Current.FontData.Name
|
|
||||||
|
|
||||||
if gc.svg.fontMode == SvgFontMode {
|
|
||||||
gc.embedSvgFont(text)
|
|
||||||
}
|
|
||||||
|
|
||||||
// link to group
|
|
||||||
group.Texts = []*Text{&svgText}
|
|
||||||
left, _, right, _ := gc.GetStringBounds(text)
|
|
||||||
return right - left
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates new group from current context
|
// SetFont sets the font used to draw text.
|
||||||
// append it to svg and return
|
func (gc *GraphicContext) SetFont(font *truetype.Font) {
|
||||||
func (gc *GraphicContext) newGroup(drawType drawType) *Group {
|
gc.Current.Font = font
|
||||||
group := Group{}
|
|
||||||
// set attrs to group
|
|
||||||
if drawType&stroked == stroked {
|
|
||||||
group.Stroke = toSvgRGBA(gc.Current.StrokeColor)
|
|
||||||
group.StrokeWidth = toSvgLength(gc.Current.LineWidth)
|
|
||||||
group.StrokeLinecap = gc.Current.Cap.String()
|
|
||||||
group.StrokeLinejoin = gc.Current.Join.String()
|
|
||||||
if len(gc.Current.Dash) > 0 {
|
|
||||||
group.StrokeDasharray = toSvgArray(gc.Current.Dash)
|
|
||||||
group.StrokeDashoffset = toSvgLength(gc.Current.DashOffset)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if drawType&filled == filled {
|
|
||||||
group.Fill = toSvgRGBA(gc.Current.FillColor)
|
|
||||||
group.FillRule = toSvgFillRule(gc.Current.FillRule)
|
|
||||||
}
|
|
||||||
|
|
||||||
group.Transform = toSvgTransform(gc.Current.Tr)
|
|
||||||
|
|
||||||
// link
|
|
||||||
gc.svg.Groups = append(gc.svg.Groups, &group)
|
|
||||||
|
|
||||||
return &group
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Embed svg font definition to svg tree itself
|
// SetFontSize sets the font size in points (as in “a 12 point font”).
|
||||||
func (gc *GraphicContext) embedSvgFont(text string) {
|
func (gc *GraphicContext) SetFontSize(fontSize float64) {
|
||||||
fontName := gc.Current.FontData.Name
|
gc.Current.FontSize = fontSize
|
||||||
gc.loadCurrentFont()
|
gc.recalc()
|
||||||
|
|
||||||
// find or create font Element
|
|
||||||
svgFont := (*Font)(nil)
|
|
||||||
for _, font := range gc.svg.Fonts {
|
|
||||||
if font.Name == fontName {
|
|
||||||
svgFont = font
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if svgFont == nil {
|
|
||||||
// create new
|
|
||||||
svgFont = &Font{}
|
|
||||||
// and link
|
|
||||||
gc.svg.Fonts = append(gc.svg.Fonts, svgFont)
|
|
||||||
}
|
|
||||||
|
|
||||||
// fill with glyphs
|
|
||||||
|
|
||||||
gc.Save()
|
|
||||||
defer gc.Restore()
|
|
||||||
gc.SetFontSize(2048)
|
|
||||||
defer gc.SetDPI(gc.GetDPI())
|
|
||||||
gc.SetDPI(92)
|
|
||||||
filling:
|
|
||||||
for _, rune := range text {
|
|
||||||
for _, g := range svgFont.Glyphs {
|
|
||||||
if g.Rune == Rune(rune) {
|
|
||||||
continue filling
|
|
||||||
}
|
|
||||||
}
|
|
||||||
glyph := gc.glyphCache.Fetch(gc, gc.GetFontName(), rune)
|
|
||||||
// glyphCache.Load indirectly calls CreateStringPath for single rune string
|
|
||||||
|
|
||||||
glypPath := glyph.Path.VerticalFlip() // svg font glyphs have oposite y axe
|
|
||||||
svgFont.Glyphs = append(svgFont.Glyphs, &Glyph{
|
|
||||||
Rune: Rune(rune),
|
|
||||||
Desc: toSvgPathDesc(glypPath),
|
|
||||||
HorizAdvX: glyph.Width,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// set attrs
|
|
||||||
svgFont.Id = "font-" + strconv.Itoa(len(gc.svg.Fonts))
|
|
||||||
svgFont.Name = fontName
|
|
||||||
|
|
||||||
// TODO use css @font-face with id instead of this
|
|
||||||
svgFont.Face = &Face{Family: fontName, Units: 2048, HorizAdvX: 2048}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE following functions copied from dwra2d{img|gl}
|
// DrawImage draws the raster image in the current canvas
|
||||||
|
func (gc *GraphicContext) DrawImage(image image.Image) {
|
||||||
|
bounds := image.Bounds()
|
||||||
|
|
||||||
|
svgImage := &Image{Href: imageToSvgHref(image)}
|
||||||
|
svgImage.X = float64(bounds.Min.X)
|
||||||
|
svgImage.Y = float64(bounds.Min.Y)
|
||||||
|
svgImage.Width = toSvgLength(float64(bounds.Max.X - bounds.Min.X))
|
||||||
|
svgImage.Height = toSvgLength(float64(bounds.Max.Y - bounds.Min.Y))
|
||||||
|
gc.newGroup(0).Image = svgImage
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearRect fills the specified rectangle with a default transparent color
|
||||||
|
func (gc *GraphicContext) ClearRect(x1, y1, x2, y2 int) {
|
||||||
|
mask := gc.newMask(x1, y1, x2-x1, y2-y1)
|
||||||
|
|
||||||
|
newGroup := &Group{
|
||||||
|
Groups: gc.svg.Groups,
|
||||||
|
Mask: "url(#" + mask.Id + ")",
|
||||||
|
}
|
||||||
|
|
||||||
|
// replace groups with new masked group
|
||||||
|
gc.svg.Groups = []*Group{newGroup}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE following two functions and soe other further below copied from dwra2d{img|gl}
|
||||||
// TODO move them all to common draw2dbase?
|
// TODO move them all to common draw2dbase?
|
||||||
|
|
||||||
// CreateStringPath creates a path from the string s at x, y, and returns the string width.
|
// CreateStringPath creates a path from the string s at x, y, and returns the string width.
|
||||||
|
@ -303,6 +223,149 @@ func (gc *GraphicContext) GetStringBounds(s string) (left, top, right, bottom fl
|
||||||
return left, top, right, bottom
|
return left, top, right, bottom
|
||||||
}
|
}
|
||||||
|
|
||||||
|
////////////////////
|
||||||
|
// private funcitons
|
||||||
|
|
||||||
|
func (gc *GraphicContext) drawPaths(drawType drawType, paths ...*draw2d.Path) {
|
||||||
|
// create elements
|
||||||
|
svgPath := Path{}
|
||||||
|
group := gc.newGroup(drawType)
|
||||||
|
|
||||||
|
// set attrs to path element
|
||||||
|
paths = append(paths, gc.Current.Path)
|
||||||
|
svgPathsDesc := make([]string, len(paths))
|
||||||
|
// multiple pathes has to be joined to single svg path description
|
||||||
|
// because fill-rule wont work for whole group as excepted
|
||||||
|
for i, path := range paths {
|
||||||
|
svgPathsDesc[i] = toSvgPathDesc(path)
|
||||||
|
}
|
||||||
|
svgPath.Desc = strings.Join(svgPathsDesc, " ")
|
||||||
|
|
||||||
|
// attach to group
|
||||||
|
group.Paths = []*Path{&svgPath}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add text element to svg and returns its expected width
|
||||||
|
func (gc *GraphicContext) drawString(text string, drawType drawType, x, y float64) float64 {
|
||||||
|
// create elements
|
||||||
|
svgText := Text{}
|
||||||
|
group := gc.newGroup(drawType)
|
||||||
|
|
||||||
|
// set attrs to text element
|
||||||
|
svgText.Text = text
|
||||||
|
svgText.FontSize = gc.Current.FontSize
|
||||||
|
svgText.X = x
|
||||||
|
svgText.Y = y
|
||||||
|
svgText.FontFamily = gc.Current.FontData.Name
|
||||||
|
|
||||||
|
if gc.svg.FontMode == SvgFontMode {
|
||||||
|
gc.embedSvgFont(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
// attach to group
|
||||||
|
group.Texts = []*Text{&svgText}
|
||||||
|
left, _, right, _ := gc.GetStringBounds(text)
|
||||||
|
return right - left
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates new group from current context
|
||||||
|
// attach it to svg and return
|
||||||
|
func (gc *GraphicContext) newGroup(drawType drawType) *Group {
|
||||||
|
group := Group{}
|
||||||
|
// set attrs to group
|
||||||
|
if drawType&stroked == stroked {
|
||||||
|
group.Stroke = toSvgRGBA(gc.Current.StrokeColor)
|
||||||
|
group.StrokeWidth = toSvgLength(gc.Current.LineWidth)
|
||||||
|
group.StrokeLinecap = gc.Current.Cap.String()
|
||||||
|
group.StrokeLinejoin = gc.Current.Join.String()
|
||||||
|
if len(gc.Current.Dash) > 0 {
|
||||||
|
group.StrokeDasharray = toSvgArray(gc.Current.Dash)
|
||||||
|
group.StrokeDashoffset = toSvgLength(gc.Current.DashOffset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if drawType&filled == filled {
|
||||||
|
group.Fill = toSvgRGBA(gc.Current.FillColor)
|
||||||
|
group.FillRule = toSvgFillRule(gc.Current.FillRule)
|
||||||
|
}
|
||||||
|
|
||||||
|
group.Transform = toSvgTransform(gc.Current.Tr)
|
||||||
|
|
||||||
|
// attach
|
||||||
|
gc.svg.Groups = append(gc.svg.Groups, &group)
|
||||||
|
|
||||||
|
return &group
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates new mask attached to svg
|
||||||
|
func (gc *GraphicContext) newMask(x, y, width, height int) *Mask {
|
||||||
|
mask := &Mask{}
|
||||||
|
mask.X = float64(x)
|
||||||
|
mask.Y = float64(y)
|
||||||
|
mask.Width = toSvgLength(float64(width))
|
||||||
|
mask.Height = toSvgLength(float64(height))
|
||||||
|
|
||||||
|
// attach mask
|
||||||
|
gc.svg.Masks = append(gc.svg.Masks, mask)
|
||||||
|
mask.Id = "mask-" + strconv.Itoa(len(gc.svg.Masks))
|
||||||
|
return mask
|
||||||
|
}
|
||||||
|
|
||||||
|
// Embed svg font definition to svg tree itself
|
||||||
|
// Or update existing if already exists for curent font data
|
||||||
|
func (gc *GraphicContext) embedSvgFont(text string) *Font {
|
||||||
|
fontName := gc.Current.FontData.Name
|
||||||
|
gc.loadCurrentFont()
|
||||||
|
|
||||||
|
// find or create font Element
|
||||||
|
svgFont := (*Font)(nil)
|
||||||
|
for _, font := range gc.svg.Fonts {
|
||||||
|
if font.Name == fontName {
|
||||||
|
svgFont = font
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if svgFont == nil {
|
||||||
|
// create new
|
||||||
|
svgFont = &Font{}
|
||||||
|
// and attach
|
||||||
|
gc.svg.Fonts = append(gc.svg.Fonts, svgFont)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fill with glyphs
|
||||||
|
|
||||||
|
gc.Save()
|
||||||
|
defer gc.Restore()
|
||||||
|
gc.SetFontSize(2048)
|
||||||
|
defer gc.SetDPI(gc.GetDPI())
|
||||||
|
gc.SetDPI(92)
|
||||||
|
filling:
|
||||||
|
for _, rune := range text {
|
||||||
|
for _, g := range svgFont.Glyphs {
|
||||||
|
if g.Rune == Rune(rune) {
|
||||||
|
continue filling
|
||||||
|
}
|
||||||
|
}
|
||||||
|
glyph := gc.glyphCache.Fetch(gc, gc.GetFontName(), rune)
|
||||||
|
// glyphCache.Load indirectly calls CreateStringPath for single rune string
|
||||||
|
|
||||||
|
glypPath := glyph.Path.VerticalFlip() // svg font glyphs have oposite y axe
|
||||||
|
svgFont.Glyphs = append(svgFont.Glyphs, &Glyph{
|
||||||
|
Rune: Rune(rune),
|
||||||
|
Desc: toSvgPathDesc(glypPath),
|
||||||
|
HorizAdvX: glyph.Width,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// set attrs
|
||||||
|
svgFont.Id = "font-" + strconv.Itoa(len(gc.svg.Fonts))
|
||||||
|
svgFont.Name = fontName
|
||||||
|
|
||||||
|
// TODO use css @font-face with id instead of this
|
||||||
|
svgFont.Face = &Face{Family: fontName, Units: 2048, HorizAdvX: 2048}
|
||||||
|
return svgFont
|
||||||
|
}
|
||||||
|
|
||||||
func (gc *GraphicContext) loadCurrentFont() (*truetype.Font, error) {
|
func (gc *GraphicContext) loadCurrentFont() (*truetype.Font, error) {
|
||||||
font, err := gc.FontCache.Load(gc.Current.FontData)
|
font, err := gc.FontCache.Load(gc.Current.FontData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -333,39 +396,6 @@ func (gc *GraphicContext) recalc() {
|
||||||
gc.Current.Scale = gc.Current.FontSize * float64(gc.DPI) * (64.0 / 72.0)
|
gc.Current.Scale = gc.Current.FontSize * float64(gc.DPI) * (64.0 / 72.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gc *GraphicContext) SetDPI(dpi int) {
|
|
||||||
gc.DPI = dpi
|
|
||||||
gc.recalc()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gc *GraphicContext) GetDPI() int {
|
|
||||||
return gc.DPI
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetFont sets the font used to draw text.
|
|
||||||
func (gc *GraphicContext) SetFont(font *truetype.Font) {
|
|
||||||
gc.Current.Font = font
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetFontSize sets the font size in points (as in “a 12 point font”).
|
|
||||||
func (gc *GraphicContext) SetFontSize(fontSize float64) {
|
|
||||||
gc.Current.FontSize = fontSize
|
|
||||||
gc.recalc()
|
|
||||||
}
|
|
||||||
|
|
||||||
// DrawImage draws the raster image in the current canvas
|
|
||||||
func (gc *GraphicContext) DrawImage(image image.Image) {
|
|
||||||
bounds := image.Bounds()
|
|
||||||
|
|
||||||
gc.newGroup(0).Image = &Image{
|
|
||||||
Href: imageToSvgHref(image),
|
|
||||||
X: bounds.Min.X,
|
|
||||||
Y: bounds.Min.Y,
|
|
||||||
Width: bounds.Max.X - bounds.Min.X,
|
|
||||||
Height: bounds.Max.Y - bounds.Min.Y,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///////////////////////////////////////
|
///////////////////////////////////////
|
||||||
// TODO implement following methods (or remove if not neccesary)
|
// TODO implement following methods (or remove if not neccesary)
|
||||||
|
|
||||||
|
@ -374,8 +404,3 @@ func (gc *GraphicContext) GetFontName() string {
|
||||||
fontData := gc.Current.FontData
|
fontData := gc.Current.FontData
|
||||||
return fmt.Sprintf("%s:%d:%d:%d", fontData.Name, fontData.Family, fontData.Style, gc.Current.FontSize)
|
return fmt.Sprintf("%s:%d:%d:%d", fontData.Name, fontData.Family, fontData.Style, gc.Current.FontSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClearRect fills the specified rectangle with a default transparent color
|
|
||||||
func (gc *GraphicContext) ClearRect(x1, y1, x2, y2 int) {
|
|
||||||
// panic("not implemented")
|
|
||||||
}
|
|
||||||
|
|
|
@ -23,8 +23,9 @@ type Svg struct {
|
||||||
XMLName xml.Name `xml:"svg"`
|
XMLName xml.Name `xml:"svg"`
|
||||||
Xmlns string `xml:"xmlns,attr"`
|
Xmlns string `xml:"xmlns,attr"`
|
||||||
Fonts []*Font `xml:"defs>font"`
|
Fonts []*Font `xml:"defs>font"`
|
||||||
|
Masks []*Mask `xml:"defs>mask"`
|
||||||
Groups []*Group `xml:"g"`
|
Groups []*Group `xml:"g"`
|
||||||
fontMode FontMode
|
FontMode FontMode `xml:"-"`
|
||||||
FillStroke
|
FillStroke
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,7 +33,7 @@ func NewSvg() *Svg {
|
||||||
return &Svg{
|
return &Svg{
|
||||||
Xmlns: "http://www.w3.org/2000/svg",
|
Xmlns: "http://www.w3.org/2000/svg",
|
||||||
FillStroke: FillStroke{Fill: "none", Stroke: "none"},
|
FillStroke: FillStroke{Fill: "none", Stroke: "none"},
|
||||||
fontMode: SvgFontMode,
|
FontMode: SvgFontMode,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,6 +44,7 @@ type Group struct {
|
||||||
Paths []*Path `xml:"path"`
|
Paths []*Path `xml:"path"`
|
||||||
Texts []*Text `xml:"text"`
|
Texts []*Text `xml:"text"`
|
||||||
Image *Image `xml:"image"`
|
Image *Image `xml:"image"`
|
||||||
|
Mask string `xml:"mask,attr,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Path struct {
|
type Path struct {
|
||||||
|
@ -60,11 +62,41 @@ type Text struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Image struct {
|
type Image struct {
|
||||||
Href string `xml:"href,attr"`
|
Position
|
||||||
X int `xml:"x,attr,omitempty"`
|
Dimension
|
||||||
Y int `xml:"y,attr,omitempty"`
|
Href string `xml:"href,attr"`
|
||||||
Width int `xml:"width,attr"`
|
}
|
||||||
Height int `xml:"height,attr"`
|
|
||||||
|
type Mask struct {
|
||||||
|
Identity
|
||||||
|
Position
|
||||||
|
Dimension
|
||||||
|
}
|
||||||
|
|
||||||
|
type Rect struct {
|
||||||
|
Position
|
||||||
|
Dimension
|
||||||
|
FillStroke
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Mask) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
||||||
|
bigRect := Rect{}
|
||||||
|
bigRect.X, bigRect.Y = 0, 0
|
||||||
|
bigRect.Width, bigRect.Height = "100%", "100%"
|
||||||
|
bigRect.Fill = "#fff"
|
||||||
|
rect := Rect{}
|
||||||
|
rect.X, rect.Y = m.X, m.Y
|
||||||
|
rect.Width, rect.Height = m.Width, m.Height
|
||||||
|
rect.Fill = "#000"
|
||||||
|
|
||||||
|
return e.EncodeElement(struct {
|
||||||
|
XMLName xml.Name `xml:"mask"`
|
||||||
|
Rects [2]Rect `xml:"rect"`
|
||||||
|
Id string `xml:"id,attr"`
|
||||||
|
}{
|
||||||
|
Rects: [2]Rect{bigRect, rect},
|
||||||
|
Id: m.Id,
|
||||||
|
}, start)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* font related elements */
|
/* font related elements */
|
||||||
|
@ -109,6 +141,11 @@ type Position struct {
|
||||||
Y float64 `xml:"y,attr,omitempty"`
|
Y float64 `xml:"y,attr,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Dimension struct {
|
||||||
|
Width string `xml:"width,attr"`
|
||||||
|
Height string `xml:"height,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
type FillStroke struct {
|
type FillStroke struct {
|
||||||
Fill string `xml:"fill,attr,omitempty"`
|
Fill string `xml:"fill,attr,omitempty"`
|
||||||
FillRule string `xml:"fill-rule,attr,omitempty"`
|
FillRule string `xml:"fill-rule,attr,omitempty"`
|
||||||
|
|
Loading…
Reference in a new issue