Coder Social home page Coder Social logo

victor's Introduction

Victor

Victor - Ruby SVG Image Builder

Gem Version Build Status Maintainability


Victor is a direct Ruby-to-SVG builder. All method calls are converted directly to SVG elements.

Demo


Install

$ gem install victor

Or with bundler:

gem 'victor'

Examples

require 'victor'

svg = Victor::SVG.new width: 140, height: 100, style: { background: '#ddd' }

svg.build do 
  rect x: 10, y: 10, width: 120, height: 80, rx: 10, fill: '#666'
  
  circle cx: 50, cy: 50, r: 30, fill: 'yellow'
  circle cx: 58, cy: 32, r: 4, fill: 'black'
  polygon points: %w[45,50 80,30 80,70], fill: '#666'

  3.times do |i|
    x = 80 + i*18
    circle cx: x, cy: 50, r: 4, fill: 'yellow'
  end
end

svg.save 'pacman'

Output:

pacman

See the examples folder for several ruby scripts and their SVG output.

Usage

Initialize your SVG image:

require 'victor'
svg = Victor::SVG.new

Any option you provide to SVG.new will be added as an attribute to the main <svg> element. By default, height and width are set to 100%.

svg = Victor::SVG.new width: '100%', height: '100%'
# same as just Victor::SVG.new

svg = Victor::SVG.new width: '100%', height: '100%', viewBox: "0 0 200 100"

As an alternative, you can set the root SVG attributes using the setup method:

require 'victor'
svg = Victor::SVG.new
svg.setup width: 200, height: 150

Victor uses a single method (element) to generate all SVG elements:

svg.element :rect, x: 2, y: 2, width: 200, height: 200
# => <rect x="2" y="2" width="200" height="200"/>

But you can omit it. Calls to any other method, will be delegated to the element method, so normal usage looks more like this:

svg.rect x: 2, y: 2, width: 200, height: 200
# => <rect x="2" y="2" width="200" height="200"/>

In other words, these are the same:

svg.element :anything, option: 'value'
svg.anything option: 'value'

You can use the build method, to generate the SVG with a block

svg.build do 
  rect x: 0, y: 0, width: 100, height: 100, fill: '#ccc'
  rect x: 20, y: 20, width: 60, height: 60, fill: '#f99'
end

If the value of an attribute is a hash, it will be converted to a style-compatible string:

svg.rect x: 0, y: 0, width: 100, height: 100, style: { stroke: '#ccc', fill: 'red' }
# => <rect x=0 y=0 width=100 height=100 style="stroke:#ccc; fill:red"/>

If the value of an attribute is an array, it will be converted to a space delimited string:

svg.path d: ['M', 150, 0, 'L', 75, 200, 'L', 225, 200, 'Z']
# => <path d="M 150 0 L 75 200 L 225 200 Z"/>

For SVG elements that have an inner content - such as text - simply pass it as the first argument:

svg.text "Victor", x: 40, y: 50
# => <text x="40" y="50">Victor</text>

You can also nest elements with blocks:

svg.build do
  g font_size: 30, font_family: 'arial', fill: 'white' do
    text "Scalable Victor Graphics", x: 40, y: 50
  end
end
# => <g font-size="30" font-family="arial" fill="white">
#      <text x="40" y="50">Scalable Victor Graphics</text>
#    </g>

Underscores in attribute names are converted to dashes:

svg.text "Victor", x: 40, y: 50, font_family: 'arial', font_weight: 'bold', font_size: 40
# => <text x="40" y="50" font-family="arial" font-weight="bold" font-size="40">
#      Victor
#    </text>

Features

Composite SVG

Victor also supports the ability to combine several smaller SVG objects into one using the << operator or the #append method.

This operator expects to receive any object that responds to #to_s (can be another SVG object).

require 'victor'
include Victor

# Create a reusable SVG object
frame = SVG.new
frame.rect x: 0, y: 0, width: 100, height: 100, fill: '#336'
frame.rect x: 10, y: 10, width: 80, height: 80, fill: '#fff'

# ... and another
troll = SVG.new
troll.circle cx: 50, cy: 60, r: 24, fill: 'yellow'
troll.polygon points: %w[24,50 50,14 76,54], fill: 'red'

# Combine objects into a single image
svg = SVG.new viewBox: '0 0 100 100'
svg << frame
svg << troll

# ... and save it
svg.save 'framed-troll'

Output:

troll

These two calls are identical:

svg << other
svg.append other

To make this common use case a little easier to use, you can use a block when instantiating a new SVG object:

troll = SVG.new do
  circle cx: 50, cy: 60, r: 24, fill: 'yellow'
end

Which is the same as:

troll = SVG.new
troll.build do
  circle cx: 50, cy: 60, r: 24, fill: 'yellow'
end

Another approach to a more modular SVG composition, would be to subclass Victor::SVG.

See the composite svg example or the subclassing example for more details.

Saving the Output

Generate the full SVG to a string with render:

result = svg.render

Or, save it to a file with save:

svg.save 'filename'
# the '.svg' extension is optional

SVG Templates

The :default SVG template is designed to be a full XML document (i.e., a standalone SVG image). If you wish to use the output as an SVG element inside HTML, you can change the SVG template:

svg = Victor::SVG.new template: :html 
# accepts :html, :minimal, :default or a filename

You can also point it to any other template file:

svg = Victor::SVG.new template: 'path/to/template.svg'

See the templates folder for an understanding of how templates are structured.

Templates can also be provided when rendering or saving the output:

svg.save 'filename', template: :minimal
svg.render template: :minimal

CSS

CSS gets a special treatment in Victor::SVG, with these objectives in mind:

  • Hide implementation details (such as the need for a CDATA marker)
  • Provide a DSL-like syntax for CSS rules

The Victor::SVG objects has a css property, which can contain either a Hash or a String:

svg = Victor::SVG.new

svg.css = css_hash_or_string
# or without the equal sign:
svg.css css_hash_or_string

svg.build do
  # ...
end

This flexibility allows you to apply CSS in multiple ways. Below are some examples.

Assigning CSS rules using the hash syntax

svg = Victor::SVG.new

svg.build do 
  css['.main'] = {
    stroke: "green", 
    stroke_width: 2,
    fill: "yellow"
  }

  circle cx: 35, cy: 35, r: 20, class: 'main'
end

Assigning a full hash to the CSS property

svg.css = {
  '.bar': {
    fill: '#666',
    stroke: '#fff',
    stroke_width: 1
  },
  '.negative': {
    fill: '#f66'
  },
  '.positive': {
    fill: '#6f6'
  }
}

Underscore characters will be converted to dashes (stroke_width becomes stroke-width).

Importing CSS from an external file

svg.css = File.read 'styles.css'

CSS @import directives

If you need to add CSS statements , like @import, use the following syntax:

css['@import'] = [
  "url('https://fonts.googleapis.com/css?family=Audiowide')",
  "url('https://fonts.googleapis.com/css?family=Pacifico')"
]

This is achieved thanks to the fact that when Victor encounters an array in the CSS hash, it will prefix each of the array elements with the hash key, so the above will result in two @import url(...) rows.

See the css example, css string example, or the custom fonts example.

Tagless Elements

Using underscore (_) as the element name will simply add the value to the generated SVG, without any surrounding element. This is designed to allow generating something like this:

<text>
  You are
  <tspan font-weight="bold">not</tspan>
  a banana
</text>

using this Ruby code:

svg.build do 
  text do
    _ 'You are'
    tspan 'not', font_weight: "bold"
    _ 'a banana'
  end
end

See the tagless elements example.

XML Encoding

Plain text values are encoded automatically:

svg.build do
  text "Ben & Jerry's"
end
# <text>Ben &amp; Jerry's</text>

If you need to use the raw, unencoded string, add ! to the element's name:

svg.build do
  text! "Ben & Jerry's"
end
# <text>Ben & Jerry's</text>

See the xml encoding example.

XML Newlines

By default, the generated SVGs will have a newline glue between the elements. You can change this (for example, to an empty string) if the default newlines are not appropriate for your use case.

svg = Victor::SVG.new glue: ''

The glue can also be provided when rendering or saving the output:

svg.save 'filename', glue: ''
svg.render glue: ''

DSL Syntax

Victor also supports a DSL-like syntax. To use it, simply require 'victor/script'.

Once required, you have access to:

  • svg - returns an instance of Victor::SVG
  • All the methods that are available on the SVG object, are included at the root level.

For example:

require 'victor/script'

setup width: 100, height: 100

build do
  circle cx: 50, cy: 50, r: 30, fill: "yellow"
end

puts render
save 'output.svg'

See the dsl example.

Related Projects

A command line utility that allows converting Ruby to SVG as well as SVG to Victor Ruby scripts.

A Victor playground that works in the browser.

A Ruby gem that uses Victor to generate SVG charts

Minichart

A Ruby gem that uses Victor to generate consistent random icon images, similar to GitHub's identicon.

Icodi

Contributing / Support

If you experience any issue, have a question or a suggestion, or if you wish to contribute, feel free to open an issue.


victor's People

Contributors

danbernier avatar dannyben avatar depfu[bot] avatar kuboon avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

victor's Issues

Convert to other image formats

Hi,
It may not have much to do with the gem, but some good idea to be able to have other formats besides svg, such as jpg or png.
BTW: Thank you very much for the gem

Wrap JSON data in a CDATA element

I'm trying to insert JSON data wrapped in a CDATA tag. Is there a way to do this? Or do I need to implement something like how you render CSS styles?

I've got the following, but the brackets get replaced with HTML entities, regardless if I escape them or not.

svg = Victor::SVG.new template: "app/assets/images/badges/template.svg"
svg.element "openbadges:assertion", verify: "url_here" do
  svg._ "<![CDATA[{ key: 'data' }]]>"
end
svg.render glue: ""

I'm trying to get to this output:

<openbadges:assertion verify="url_here">
<![CDATA[{ key: 'data' }]]>
</openbadges:assertion>

How to add a .png image from my PC to the svg canvas?

Hello.

I want to display my .png image on the svg canvas with victor.
While I can display an image from the internet, I can't display any image from my PC.

I'm asking about the svg <image> tag.
In html I can do:

<body>

<svg width="200" height="200">
  <image href="/home/user/Pictures/Screenshot.png", height="200" width="200"/>
</svg>

</body>

And it will display my png image on the svg canvas.

In victor, I tried to do this in a few different ways, but the only code that displayed anything was:

require 'victor'

svg = Victor::SVG.new width: 1000, height: 1000, style: { background: '#ddd' }

svg.build do 

  image href: "file:///home/user/Pictures/Screenshot.png", height: 200, width: 200   
  # if you will replace "file:///home/user/Pictures/Screenshot.png", with an image address from the internet, it works
  # "/home/user/Pictures/Screenshot.png" won't display anything at all

end

svg.save 'image'

Unfortunately the image displayed was not my image, but this error:
image

Sorry, if this is some sort of a simple mistake but I'm sure that both the path and the image are correct.

Allow changing templates

Need to allow users to choose another template.

  • Consider allowing several built in template, like :standalone and :html
  • Allow choosing any arbitrary template file

Allow hash/array attributes in the main SVG node

To allow things like the style property below, to be set as a hash, and the viewbox, to be set as an array

<svg xmlns="http://www.w3.org/2000/svg" version="1.1" 
     style="width: 300px; height: 300px;" viewBox="0 0 300 300">

generate single elements to append on svg

I am missing the functionality to create single elements like <g> or <rect> and then append them to a svg.

This would be helpful because i want to encapsulate the generation of parts of my svg and afterwards glue them together.

Am I missing something or is this not possible and when not, may some functionality be included?

Combine existing SVG files

Hi there, I have a use case that I'm not sure how to address with this gem.
I need to conbine 2 SVG files:

  • An SVG logo
  • An SVG QR code generated by this gem

I've checked victor-cli a little bit and I think it can help with me with this problem, just not sure how.
Is there a tool or method I'm missing that can accomplish that?

Thanks!

XML escaping

This gem looks really great for a simple SVG builder, but I noticed it isn't doing XML escaping:

$ ruby -rvictor -e 'puts Victor::SVG.new.build { text { _ "&" } }'
<text>
&
</text>

The & should be escaped to &amp; to be valid XML. Any plans to add this functionality?

collision

Is possible to add small function?
I have 2 shapes or shape and text.
I need information this shape is collision with other?

meybe other function add put other shape on right , distance 3cm or 3 px.
please look at agg library

Select custom fonts

Let's say I want to load a custom font. I define this in the CSS/Style. How/where are these fonts loaded from?

Conversion utility

Hi,

I have started using this gem to generate SVGs for CNC milling and one of the use cases I have is using an existing SVG as a starting point for the generation of the desired SVG. I created a utility that takes an SVG as input and converts it to a victor script. Is this something you think could be included in the gem?

Script is below:

require "nokogiri"
require "stringio"
require "byebug"

input = ARGF
doc = Nokogiri::XML(input)

svg = doc.children.last
raise "not an SVG" if svg.name != "svg"

RAW_NODES = ["text", "style"]

def get_attrs(node)
  node.attributes.values.reduce({}) do |acc, attr|
    name = attr.name
    value = attr.value
    key = attr.respond_to?(:prefix) ? "#{attr.prefix}:#{name}" : name
    acc[key] = value
    acc
  end
end

def process_node(node, out)
  children = node.children
  unless children.empty?
    out.puts "do"
    process_nodes(children, out)
    out.puts "end"
  end
end

def process_nodes(nodes, out)
  nodes.each do |node|
    attrs = get_attrs(node)
    output = true
    params = attrs.empty? ? "" : "(#{attrs.inspect})"
    name = node.name
    if RAW_NODES.include?(name)
      inner_text = node.text
      output = inner_text.strip.length > 0
      params = " #{inner_text.inspect}"
    end
    if output
      out.write "#{node.name}#{params} "
      process_node(node, out) unless RAW_NODES.include?(name)
      out.puts
    end
  end
end

result = StringIO.new
result.puts 'require "victor"'
result.puts
result.write "svg = Victor::SVG.new( "
result.write get_attrs(svg).inspect
result.puts ")"
result.puts "svg.build do"
process_nodes(svg.children, result)
result.puts "end"
result.puts 'svg.save "convert_test"'
result.rewind
str = result.read

puts str.gsub("=>", ":")

Usage is: ruby convert.rb [svg_filename]. To get improved formatting I pipe it through the rufo formatter.

Support for nested SVGs

Hello!
I can't find any documentation for nesting SVG. When you compose svgs with the append operator (<<) to a SVG, the svg tag is removed from the new svg object.

The goal is to have something like this:
<svg id='main'> ... <svg id='nested1'>...</svg> <svg id='nested2'>...</svg> </svg>

Can be done with the actual gem, or it should be implemented?

Is it possible to nest tspan elements inside text elements?

I could not find any examples that showed how I would do this (and forgive me if it is obvious, I am coming back to Ruby after many years away, as I contemplate using this gem to help me port the basic features of the bytefield LaTeX package to something that can be used inside Asciidoctor). One thing I need to be able to do is center a span of text in which elements have different styles, which seems possible to do in SVG by nesting tspan objects inside text objects, but I can’t see how I would emit such a structure with Victor.

Variable scope issues

It seems that when using victor within a class that class variables are not known anymore.

require "victor"

@test =1

svg = Victor::SVG.new(width: '100%', height: '100%', viewBox: '-10 -10 200 250')
puts @test.inspect
svg.build do
   puts @test.inspect
      g(transform: "matrix(1 0 0 -1 0 240") do
      puts @test.inspect
   end
end

The output is

1
nil
nil 

Only instance variables are known e.g. if I would define @@test instead of @test. This seems a rather unusual behavior for a gem. Any comments?

The VERSION constant is set at the global level

Hi! We recently started using your gem to render SVGs, but I saw some weird test breakage in our app after adding it. Looking into the issue, I found that we had a test in our app that expects the VERSION constant at the top level to not be set. Including this gem causes ::VERSION to be set as Victor::VERSION.

I'm pretty sure this is because of the "include Victor" line in lib/victor.rb. Removing that would mean using Victor::SVG, Victor::CSS, and Victor::Attributes at the top level as well. But, you probably wouldn't have to change any code within the gem, and if someone else had an SVG, CSS, or Attributes class at the top level there wouldn't be any conflicts.

tspan elements always rendered with space inbetween

Let’s say I want to create styled text like this:

expected

Sample program:

require 'victor'

svg = Victor::SVG.new
svg.build do
  text font_size: 20, y: 20, font_family: 'Verdana, sans-serif' do
    tspan 'im', font_weight: "bold"
    tspan 'plicit vs. '
    tspan 'ex', font_weight: "bold"
    tspan 'plicit'
  end
end
svg.save("test.svg")

The output of this program, however, gets rendered like this:

actual

The apparent cause is that the generated XML has each tspan element on a separate line:

<text font-size="20" y="20" font-family="Verdana, sans-serif">
<tspan font-weight="bold">
im
</tspan>
<tspan>
plicit vs. 
</tspan>
<tspan font-weight="bold">
ex
</tspan>
<tspan>
plicit
</tspan>
</text>

If I manually edit the XML to look like this, the text gets rendered as expected:

<text font-size="20" y="20" font-family="Verdana, sans-serif">
<tspan font-weight="bold">im</tspan><tspan>plicit vs. </tspan><tspan font-weight="bold">ex</tspan><tspan>plicit</tspan>
</text>

This is the same behaviour know from HTML (i.e. white space, including line breaks, is significant in elements that may contain text nodes).

The fix would obviously be easy: Just render all tspan elements in a single line. However, this would not be fully backwards compatible – for example the rendered SVG from #35 (comment) would have no more spaces between the words. I’m not really sure what to do about that.

Should the template argument be moved to #render and #save?

The template argument in the initializer seems a little out of place. We only care about it when we render and save. Options to consider:

  • Keep it as is
  • Move it to be an optional argument of #render and #save, and;
    • Remove it from the initializer, or;
    • Keep it in the initializer as well, to set the effective default, should the user choose to do so.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.