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 {
|
||||
if math.IsInf(l, 1) {
|
||||
return "100%"
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// 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, " ")
|
||||
|
||||
// link to group
|
||||
group.Paths = []*Path{&svgPath}
|
||||
func (gc *GraphicContext) SetDPI(dpi int) {
|
||||
gc.DPI = dpi
|
||||
gc.recalc()
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// link to group
|
||||
group.Texts = []*Text{&svgText}
|
||||
left, _, right, _ := gc.GetStringBounds(text)
|
||||
return right - left
|
||||
func (gc *GraphicContext) GetDPI() int {
|
||||
return gc.DPI
|
||||
}
|
||||
|
||||
// Creates new group from current context
|
||||
// append 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)
|
||||
|
||||
// link
|
||||
gc.svg.Groups = append(gc.svg.Groups, &group)
|
||||
|
||||
return &group
|
||||
// SetFont sets the font used to draw text.
|
||||
func (gc *GraphicContext) SetFont(font *truetype.Font) {
|
||||
gc.Current.Font = font
|
||||
}
|
||||
|
||||
// Embed svg font definition to svg tree itself
|
||||
func (gc *GraphicContext) embedSvgFont(text string) {
|
||||
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 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}
|
||||
// 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()
|
||||
}
|
||||
|
||||
// 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?
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
////////////////////
|
||||
// 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) {
|
||||
font, err := gc.FontCache.Load(gc.Current.FontData)
|
||||
if err != nil {
|
||||
|
@ -333,39 +396,6 @@ func (gc *GraphicContext) recalc() {
|
|||
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)
|
||||
|
||||
|
@ -374,8 +404,3 @@ func (gc *GraphicContext) GetFontName() string {
|
|||
fontData := gc.Current.FontData
|
||||
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"`
|
||||
Xmlns string `xml:"xmlns,attr"`
|
||||
Fonts []*Font `xml:"defs>font"`
|
||||
Masks []*Mask `xml:"defs>mask"`
|
||||
Groups []*Group `xml:"g"`
|
||||
fontMode FontMode
|
||||
FontMode FontMode `xml:"-"`
|
||||
FillStroke
|
||||
}
|
||||
|
||||
|
@ -32,7 +33,7 @@ func NewSvg() *Svg {
|
|||
return &Svg{
|
||||
Xmlns: "http://www.w3.org/2000/svg",
|
||||
FillStroke: FillStroke{Fill: "none", Stroke: "none"},
|
||||
fontMode: SvgFontMode,
|
||||
FontMode: SvgFontMode,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -43,6 +44,7 @@ type Group struct {
|
|||
Paths []*Path `xml:"path"`
|
||||
Texts []*Text `xml:"text"`
|
||||
Image *Image `xml:"image"`
|
||||
Mask string `xml:"mask,attr,omitempty"`
|
||||
}
|
||||
|
||||
type Path struct {
|
||||
|
@ -60,11 +62,41 @@ type Text struct {
|
|||
}
|
||||
|
||||
type Image struct {
|
||||
Href string `xml:"href,attr"`
|
||||
X int `xml:"x,attr,omitempty"`
|
||||
Y int `xml:"y,attr,omitempty"`
|
||||
Width int `xml:"width,attr"`
|
||||
Height int `xml:"height,attr"`
|
||||
Position
|
||||
Dimension
|
||||
Href string `xml:"href,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 */
|
||||
|
@ -109,6 +141,11 @@ type Position struct {
|
|||
Y float64 `xml:"y,attr,omitempty"`
|
||||
}
|
||||
|
||||
type Dimension struct {
|
||||
Width string `xml:"width,attr"`
|
||||
Height string `xml:"height,attr"`
|
||||
}
|
||||
|
||||
type FillStroke struct {
|
||||
Fill string `xml:"fill,attr,omitempty"`
|
||||
FillRule string `xml:"fill-rule,attr,omitempty"`
|
||||
|
|
Loading…
Reference in a new issue