Skip to content

Instantly share code, notes, and snippets.

@mindon
Created March 9, 2018 06:36
Show Gist options
  • Select an option

  • Save mindon/f6ba1924aa9c20c023024c6201892a4a to your computer and use it in GitHub Desktop.

Select an option

Save mindon/f6ba1924aa9c20c023024c6201892a4a to your computer and use it in GitHub Desktop.
Convert simple ai Illustrator file to svg format
// CONVERT simple .ai (adobe illustrator) file to svg format
// http://www.fileformat.info/format/ai/egff.htm
//
package main
import (
"fmt"
"io/ioutil"
"math"
"os"
"regexp"
"strconv"
"strings"
)
func main() {
fpath := "demo.ai"
if len(os.Args) > 1 {
fpath = os.Args[1]
}
body, err := ioutil.ReadFile(fpath)
if err != nil {
panic(err)
}
lines := strings.Split(string(body), "\r\n")
beginLine := "%%EndSetup"
beginSize := len(beginLine)
endSignal := "%%"
boundingBox := "%%BoundingBox: "
isBegin := false
state := AIState{}
viewSize := ""
translate := ""
result := ""
taskNames := []string{"move", "lineto", "curveto", "cmyk_color", "gray_color", "end_path"}
for _, line := range lines {
if !isBegin || len(line) == 0 {
if len(line) == beginSize && line == beginLine {
isBegin = true
} else if strings.HasPrefix(line, boundingBox) {
d := strings.Split(line[len(boundingBox):], " ")
if len(d) >= 4 {
x0, _ := strconv.ParseFloat(d[0], 64)
y0, _ := strconv.ParseFloat(d[1], 64)
x1, _ := strconv.ParseFloat(d[2], 64)
y1, _ := strconv.ParseFloat(d[3], 64)
translate = fmt.Sprintf(` transform="scale(1, -1) translate(%.3f,%.3f)"`, -x0, -y1)
viewSize = fmt.Sprintf(` width="%d" height="%d"`, int(x1-x0), int(y1-y0))
}
}
continue
}
if strings.HasPrefix(line, endSignal) {
break
}
for _, name := range taskNames {
task, ok := tasks[name]
if !ok {
continue
}
m := task.FindStringSubmatch(line)
if len(m) == 0 {
continue
}
if name == "move" {
state.Path += fmt.Sprintf(" M %s,%s", m[1], m[2])
x, _ := strconv.ParseFloat(m[1], 64)
y, _ := strconv.ParseFloat(m[2], 64)
state.Cpx = x
state.Cpy = y
} else if name == "lineto" {
state.Path += fmt.Sprintf(" L %s,%s", m[1], m[2])
x, _ := strconv.ParseFloat(m[1], 64)
y, _ := strconv.ParseFloat(m[2], 64)
state.Cpx = x
state.Cpy = y
} else if name == "curveto" {
d := []float64{}
for _, s := range m[1:] {
v, _ := strconv.ParseFloat(s, 64)
d = append(d, v)
}
state.Path += fmt.Sprintf(" C %f,%f %f,%f %f,%f ", d[0], d[1], d[2], d[3], d[4], d[5])
state.Cpx = d[len(d)-2]
state.Cpx = d[len(d)-1]
} else if name == "cmyk_color" {
_c, _ := strconv.ParseFloat(m[1], 64)
_y, _ := strconv.ParseFloat(m[2], 64)
_m, _ := strconv.ParseFloat(m[3], 64)
_k, _ := strconv.ParseFloat(m[4], 64)
cssHex := CMYK2HEX(_c, _y, _m, _k)
if m[5] == "K" {
state.StrokeColor = cssHex
} else {
state.FillColor = cssHex
}
} else if name == "gray_color" {
v, _ := strconv.ParseFloat(m[1], 64)
cssHex := CMYK2HEX(0, 0, 0, 1-v)
if m[2] == "G" {
state.StrokeColor = cssHex
} else {
state.FillColor = cssHex
}
} else if name == "end_path" {
_closed := false
_stroke := ""
_fill := ""
key := m[1]
if len(state.StrokeColor) == 0 {
state.StrokeColor = "#000000"
}
if len(state.FillColor) == 0 {
state.FillColor = "#ff0000"
}
if key == "S" || key == "B" {
_stroke = fmt.Sprintf("stroke:%s", state.StrokeColor)
} else if key == "s" || key == "b" {
_stroke = fmt.Sprintf("stroke:%s", state.StrokeColor)
_closed = true
}
if key == "F" || key == "B" {
_fill = fmt.Sprintf("fill:%s", state.FillColor)
} else if key == "f" || key == "b" {
_fill = fmt.Sprintf("fill:%s", state.StrokeColor)
_closed = true
}
if _closed {
state.Path += " z "
}
result += fmt.Sprintf(`<g style="%s;%s"><path d="%s" /></g>`+"\n", _stroke, _fill, state.Path)
state.Path = ""
} else {
fmt.Println("[IGNORE]", line)
}
break
}
}
result = `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"` + viewSize + ">\n <g" + translate + ">\n" + result + "\n </g>\n" + `</svg>`
output := strings.Replace(fpath, ".ai", ".svg", -1)
ioutil.WriteFile(output, []byte(result), os.ModeAppend)
fmt.Printf(`"%s" is converted as "%s"`+"\n", fpath, output)
}
func CMYK2HEX(c, m, y, k float64) string {
r := int(255.0 * math.Max(1-(k+c), 0))
g := int(255.0 * math.Max(1-(k+m), 0))
b := int(255.0 * math.Max(1-(k+y), 0))
return fmt.Sprintf("%02x%02x%02x", r, g, b)
}
type AIState struct {
FillColor string `json:"fill_color,omitempty"`
StrokeColor string `json:"stroke_color,omitempty"`
Path string `json:"path,omitempty"`
Cpx float64 `json:"cpx,omitempty"`
Cpy float64 `json:"cpy,omitempty"`
}
var tasks = map[string]*regexp.Regexp{
"move": regexp.MustCompile(`\s*(-?[\d\.]+) +(-?[\d\.]+) [mM]\s*$`),
"lineto": regexp.MustCompile(`^\s*(-?[\d\.]+) +(-?[\d\.]+) [lL]\s*$`),
"curveto": regexp.MustCompile(`^\s*(-?[\d\.]+) +(-?[\d\.]+) +(-?[\d\.]+) +(-?[\d\.]+) +(-?[\d\.]+) +(-?[\d\.]+) [cC]\s*$`),
"cmyk_color": regexp.MustCompile(`^\s*(-?[\d\.]+) +(-?[\d\.]+) +(-?[\d\.]+) +(-?[\d\.]+) +([kK])\s*$`),
"gray_color": regexp.MustCompile(`^\s*([\d\.]+) +([gG])\s*$`),
"end_path": regexp.MustCompile(`^\s*([nNBbfFsS]+)\s*$`),
}
@codingdudecom

Copy link
Copy Markdown

Hi, thanks for sharing this. Do you know if this works on newer versions of Illustrator? I'm getting an empty SVG, only the viewbox info seems to be corrrect.

The AI file header contains the text PDF1.5, does this mean that they've changed the format?

@mindon

mindon commented Oct 26, 2021

Copy link
Copy Markdown
Author

@codingdudecom it would be helpful if you provide a demo .ai file

@codingdudecom

Copy link
Copy Markdown

sure, here you go: https://file.io/4Zg1cqeQCRSm

@mindon

mindon commented Oct 27, 2021

Copy link
Copy Markdown
Author

sure, here you go: https://file.io/4Zg1cqeQCRSm

the .ai file you provided should be a pdf file indeed, this article provides some details on this
https://www.datalogics.com/blog/pdf-tips/illustrator-and-pdf-compatibility/

It's generated if saving with the "PDF compatible mode" option.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment