Skip to content

Instantly share code, notes, and snippets.

@cbrew
Last active April 7, 2016 09:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cbrew/6248510 to your computer and use it in GitHub Desktop.
Save cbrew/6248510 to your computer and use it in GitHub Desktop.
IPython notebook showing how to measure SVG text using Javascript and Python.
{
"metadata": {
"name": ""
},
"nbformat": 3,
"nbformat_minor": 0,
"worksheets": [
{
"cells": [
{
"cell_type": "heading",
"level": 1,
"metadata": {},
"source": [
"Measuring text in IPython using the Javascript interface."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This notebook is an experiment in using the IPython notebook to do precise layout. For better or worse, SVG's facilities for text handling are general enough to handle modern variable-width fonts. This means that it is tricky to work out how wide text is when displayed. The solution is to make use of IPython's Javascript interface, which gives everything that is needed, in moderately simple form.\n",
"\n",
"The width calculations do not apply exactly to other display media, but the before and after SVGs look good when opened in Safari or Chrome. Neither Inkscape nor the Unix convert tool do the same as the web browsers. \n",
"\n",
"Create a simple piece of SVG with text and a rectangle that is only a rough estimate of the right size to fit tightly around the text."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"from IPython.display import display,SVG,Javascript"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 1
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"def create_svg(text, name1=\"mySVG\", tname='myText', rname='myRect'):\n",
" \"\"\"\n",
" Create an SVG and assign names to the components.\n",
" \"\"\"\n",
" svg = SVG(\"\"\"<svg version=\"1.1\" x=\"0\" y=\"0\" width=\"400\" height=\"200\"\n",
"\n",
" baseProfile=\"full\" \n",
" xmlns=\"http://www.w3.org/2000/svg\" id=\"%s\"> \n",
"\n",
" <text id=\"%s\" name=\"textName\" x=\"10\" y=\"125\" font-size=\"18\" \n",
" style=\"font-family: impact, georgia, times, serif; font-weight: normal; font-style: normal\"\n",
" text-anchor=\"start\" fill=\"blue\">%s</text>\n",
" <rect id=\"%s\" x=\"10\" y=\"107\" stroke=\"grey\"\n",
" fill=\"None\" height=\"18\" width=\"%d\"></rect>\n",
" </svg>\"\"\" % (name1,tname,text,rname,9*len(text)))\n",
" return svg\n",
"\n",
"create_svg('Exercise caution when entering the building')"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 2,
"svg": [
"<svg baseProfile=\"full\" height=\"200\" id=\"mySVG\" version=\"1.1\" width=\"400\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\"> \n",
"\n",
" <text fill=\"blue\" font-size=\"18\" id=\"myText\" name=\"textName\" style=\"font-family: impact, georgia, times, serif; font-weight: normal; font-style: normal\" text-anchor=\"start\" x=\"10\" y=\"125\">Exercise caution when entering the building</text>\n",
" <rect fill=\"None\" height=\"18\" id=\"myRect\" stroke=\"grey\" width=\"387\" x=\"10\" y=\"107\"/>\n",
" </svg>"
],
"text": [
"<IPython.core.display.SVG at 0x116ba5f90>"
]
}
],
"prompt_number": 2
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The next cell finds the text and its surrounding rectangle, then adjusts the height and width of the rectangle to match those of the text that it is supposed to contain. For now, the names of the text and the rectangle are hard-wired. \n"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"%%javascript \n",
"var mytext = document.getElementById('myText');\n",
"var bb1 = mytext.getBBox();\n",
"var myrect = document.getElementById('myRect');\n",
"var bb2 = myrect.getBBox();\n",
"myrect.setAttribute('width',bb1.width);\n",
"myrect.setAttribute('height',bb1.height);"
],
"language": "python",
"metadata": {},
"outputs": [
{
"javascript": [
"var mytext = document.getElementById('myText');\n",
"var bb1 = mytext.getBBox();\n",
"var myrect = document.getElementById('myRect');\n",
"var bb2 = myrect.getBBox();\n",
"myrect.setAttribute('width',bb1.width);\n",
"myrect.setAttribute('height',bb1.height);"
],
"metadata": {},
"output_type": "display_data",
"text": [
"<IPython.core.display.Javascript at 0x116ba5a90>"
]
}
],
"prompt_number": 3
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The next cell uses IPython's kernel interface to create a string from the SVG element tree, then assign it to a Python variable. XMLSerializer seems to work smoothly for Safari. It is not certain that the API for the IPython kernel will persist into later versions, since the team are working hard on Javascript interaction.\n",
"\n",
"The two cells after the one with the Javascript cell magic verify that display works, and that the raw svg is as expected."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"%%javascript\n",
"var kernel = IPython.notebook.kernel;\n",
"var mySVG = document.getElementById('mySVG');\n",
"var s = new XMLSerializer()\n",
"var pp = s.serializeToString(mySVG);\n",
"var command = 'svgs = \"\"\"' + pp + '\"\"\"'\n",
"kernel.execute(command);"
],
"language": "python",
"metadata": {},
"outputs": [
{
"javascript": [
"var kernel = IPython.notebook.kernel;\n",
"var mySVG = document.getElementById('mySVG');\n",
"var s = new XMLSerializer()\n",
"var pp = s.serializeToString(mySVG);\n",
"var command = 'svgs = \"\"\"' + pp + '\"\"\"'\n",
"kernel.execute(command);"
],
"metadata": {},
"output_type": "display_data",
"text": [
"<IPython.core.display.Javascript at 0x116ba5b90>"
]
}
],
"prompt_number": 4
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"SVG(data=svgs)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 5,
"svg": [
"<svg baseProfile=\"full\" height=\"200\" id=\"mySVG\" version=\"1.1\" width=\"400\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\"> \n",
"\n",
" <text fill=\"blue\" font-size=\"18\" id=\"myText\" name=\"textName\" style=\"font-family: impact, georgia, times, serif; font-weight: normal; font-style: normal\" text-anchor=\"start\" x=\"10\" y=\"125\">Exercise caution when entering the building</text>\n",
" <rect fill=\"None\" height=\"23\" id=\"myRect\" stroke=\"grey\" width=\"314\" x=\"10\" y=\"107\"/>\n",
" </svg>"
],
"text": [
"<IPython.core.display.SVG at 0x116ba5a90>"
]
}
],
"prompt_number": 5
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"print svgs"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"<svg baseProfile=\"full\" height=\"200\" id=\"mySVG\" version=\"1.1\" width=\"400\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\"> \n",
"\n",
" <text fill=\"blue\" font-size=\"18\" id=\"myText\" name=\"textName\" style=\"font-family: impact, georgia, times, serif; font-weight: normal; font-style: normal\" text-anchor=\"start\" x=\"10\" y=\"125\">Exercise caution when entering the building</text>\n",
" <rect fill=\"None\" height=\"23\" id=\"myRect\" stroke=\"grey\" width=\"314\" x=\"10\" y=\"107\"></rect>\n",
" </svg>\n"
]
}
],
"prompt_number": 6
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"# output the saved SVG to a file.\n",
"outf = open('sample.svg','w')\n",
"outf.write(svgs);\n",
"outf.close()"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 7
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now try the same thing, but using a different interface to execute the Javascript, which makes it possible to dynamically build the string that is executed by Javascript. This allows flexibility in naming the rectangle and the text box."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"def move_rectangle(tname, rname):\n",
" '''\n",
" Change the height and width of the surrounding rectangle\n",
" to match those of the text box.\n",
" '''\n",
" return Javascript('''\n",
" var mytext = document.getElementById(\"%s\");\n",
" var bb1 = mytext.getBBox();\n",
" var myrect = document.getElementById(\"%s\");\n",
" var bb2 = myrect.getBBox();\n",
" myrect.setAttribute('width',bb1.width);\n",
" myrect.setAttribute('height',bb1.height);\n",
" ''' % (tname,rname))"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 8
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"If you run the next cell using IPython 1.0 on Safari, it will show the rectangle only loosely around the text"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"svg = create_svg('An infinite number of screaming monkeys.')\n",
"display(svg)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "display_data",
"svg": [
"<svg baseProfile=\"full\" height=\"200\" id=\"mySVG\" version=\"1.1\" width=\"400\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\"> \n",
"\n",
" <text fill=\"blue\" font-size=\"18\" id=\"myText\" name=\"textName\" style=\"font-family: impact, georgia, times, serif; font-weight: normal; font-style: normal\" text-anchor=\"start\" x=\"10\" y=\"125\">An infinite number of screaming monkeys.</text>\n",
" <rect fill=\"None\" height=\"18\" id=\"myRect\" stroke=\"grey\" width=\"360\" x=\"10\" y=\"107\"/>\n",
" </svg>"
],
"text": [
"<IPython.core.display.SVG at 0x116ba5b10>"
]
}
],
"prompt_number": 9
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"svg = create_svg('An infinite number of screaming monkeys.',name1='bazonka',tname='xya',rname='abd')\n",
"display(svg)\n",
"move_rectangle('xya','abd')"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "display_data",
"svg": [
"<svg baseProfile=\"full\" height=\"200\" id=\"bazonka\" version=\"1.1\" width=\"400\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\"> \n",
"\n",
" <text fill=\"blue\" font-size=\"18\" id=\"xya\" name=\"textName\" style=\"font-family: impact, georgia, times, serif; font-weight: normal; font-style: normal\" text-anchor=\"start\" x=\"10\" y=\"125\">An infinite number of screaming monkeys.</text>\n",
" <rect fill=\"None\" height=\"18\" id=\"abd\" stroke=\"grey\" width=\"360\" x=\"10\" y=\"107\"/>\n",
" </svg>"
],
"text": [
"<IPython.core.display.SVG at 0x116ba5ed0>"
]
},
{
"javascript": [
"\n",
" var mytext = document.getElementById(\"xya\");\n",
" var bb1 = mytext.getBBox();\n",
" var myrect = document.getElementById(\"abd\");\n",
" var bb2 = myrect.getBBox();\n",
" myrect.setAttribute('width',bb1.width);\n",
" myrect.setAttribute('height',bb1.height);\n",
" "
],
"metadata": {},
"output_type": "pyout",
"prompt_number": 10,
"text": [
"<IPython.core.display.Javascript at 0x116ba5b10>"
]
}
],
"prompt_number": 10
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Save the original svg data, which has not changed."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"print svg.data"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"<svg baseProfile=\"full\" height=\"200\" id=\"bazonka\" version=\"1.1\" width=\"400\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\"> \n",
"\n",
" <text fill=\"blue\" font-size=\"18\" id=\"xya\" name=\"textName\" style=\"font-family: impact, georgia, times, serif; font-weight: normal; font-style: normal\" text-anchor=\"start\" x=\"10\" y=\"125\">An infinite number of screaming monkeys.</text>\n",
" <rect fill=\"None\" height=\"18\" id=\"abd\" stroke=\"grey\" width=\"360\" x=\"10\" y=\"107\"/>\n",
" </svg>\n"
]
}
],
"prompt_number": 11
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"# output the saved SVG to a file.\n",
"outf = open('before.svg','w')\n",
"outf.write(svg.data);\n",
"outf.close()"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 12
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Recover the current state of the transformed svg, and save the result to a file."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"def recover_svg(name):\n",
" \"\"\"\n",
" obtain the string value of an SVG with a particular ID.\n",
" \"\"\"\n",
" display(Javascript('''\n",
" var kernel = IPython.notebook.kernel;\n",
" var mySVG = document.getElementById('%s');\n",
" var s = new XMLSerializer()\n",
" var pp = s.serializeToString(mySVG);\n",
" var command = 'svgs = \"\"\"' + pp + '\"\"\"'\n",
" kernel.execute(command);''' % (name,)))\n",
" return svgs\n",
"\n",
"\n",
"\n",
"svgs = recover_svg('bazonka')"
],
"language": "python",
"metadata": {},
"outputs": [
{
"javascript": [
"\n",
" var kernel = IPython.notebook.kernel;\n",
" var mySVG = document.getElementById('bazonka');\n",
" var s = new XMLSerializer()\n",
" var pp = s.serializeToString(mySVG);\n",
" var command = 'svgs = \"\"\"' + pp + '\"\"\"'\n",
" kernel.execute(command);"
],
"metadata": {},
"output_type": "display_data",
"text": [
"<IPython.core.display.Javascript at 0x116ba5d10>"
]
}
],
"prompt_number": 13
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"\n",
"# output the saved SVG to a file.\n",
"outf = open('after.svg','w')\n",
"outf.write(svgs);\n",
"outf.close()"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 14
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"print svgs"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"<svg baseProfile=\"full\" height=\"200\" id=\"bazonka\" version=\"1.1\" width=\"400\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\"> \n",
"\n",
" <text fill=\"blue\" font-size=\"18\" id=\"xya\" name=\"textName\" style=\"font-family: impact, georgia, times, serif; font-weight: normal; font-style: normal\" text-anchor=\"start\" x=\"10\" y=\"125\">An infinite number of screaming monkeys.</text>\n",
" <rect fill=\"None\" height=\"23\" id=\"abd\" stroke=\"grey\" width=\"302\" x=\"10\" y=\"107\"></rect>\n",
" </svg>\n"
]
}
],
"prompt_number": 15
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"SVG(svgs)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 16,
"svg": [
"<svg baseProfile=\"full\" height=\"200\" id=\"bazonka\" version=\"1.1\" width=\"400\" x=\"0\" xmlns=\"http://www.w3.org/2000/svg\" y=\"0\"> \n",
"\n",
" <text fill=\"blue\" font-size=\"18\" id=\"xya\" name=\"textName\" style=\"font-family: impact, georgia, times, serif; font-weight: normal; font-style: normal\" text-anchor=\"start\" x=\"10\" y=\"125\">An infinite number of screaming monkeys.</text>\n",
" <rect fill=\"None\" height=\"23\" id=\"abd\" stroke=\"grey\" width=\"302\" x=\"10\" y=\"107\"/>\n",
" </svg>"
],
"text": [
"<IPython.core.display.SVG at 0x116ba5910>"
]
}
],
"prompt_number": 16
}
],
"metadata": {}
}
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment