CSS Text Line Spacing Exposed!

Want evenly spaced lines of text like when writing on the lined paper we all used as kids? Should be easy. Turns out with CSS it is not. This post will show why. It is the result of too much time reading specs and making tests as I worked on Inkscape’s multi-line text.

The first thing to understand is that CSS text works by filling line boxes with glyphs and then stacking the boxes, much as is done in printing with movable type.

Four lines of movable type placed in a composing stick over a box of movable type.

Movable type placed in a composing stick. The image has been flipped horizontally so the glyphs are legible. (Modified from photo by Willi Heidelbach [CC BY 2.5], via Wikimedia Commons)

A line of CSS text is composed of a series of glyphs. It corresponds to a row of movable type where each glyph represents (mostly) a piece of type. The CSS ‘font-size’ property corresponds to the height of the type. A CSS line box contains a line of CSS text plus any leading (extra space) above and below the line.

Four lines of text mimicking the above figure.

The same four lines of text as in the previous figure. The CSS line boxes are shown by red rectangles. The line boxes are stacked without any leading between the lines.

The lines in the above figure are set tight, without any spacing between the lines. This makes the text hard to read. It is normal in typesetting to add a bit of leading between lines to give the lines a small amount of separation. This can be done with CSS through the ‘line-height’ property. A typical value of the ‘line-height’ property would be ‘1.2’ which means in the simplest terms to make the distance between the baselines of the text be 1.2 times the font size. CSS dictates that the extra space be split, half above the line, half below the line. The following example uses a ‘line-height’ value of 1.5 (to make the figure clearer).

Same four lines of text as in above figure but with leading added between lines.

The same four lines of text as in the previous figure but with leading added by a ‘line-height’ value of 1.5. The distance between the baselines (light-blue lines) is 1.5 times the font size. (Line boxes without leading are shown in green, line boxes with leading in red.)

Unlike with physical type faces, lines can be moved closer together than the height of the glyph boxes by using a ‘line-height’ value less than one. Normally you would not want to do this.

Same four lines of text as in above figure but with negative leading.

The same four lines of text as in the previous figure but with negative leading generated by a ‘line-height’ value of 0.8.

When only one font is used (same family and size), the distance between the baselines is consistent and easy to predict. But with multiple fonts it becomes a bit of a challenge. To understand the inner workings of ‘line-height’ we first need to get back to basics.

Glyphs are designed inside an em box. The ‘font-size’ property scales the em box so when rendered the height of the em box matches the font size. For most scripts, the em box is divided into two parts by a baseline. The ascent measures the distance between the baseline and the top of the box while the descent measures the distance between the baseline and the bottom of the box.

Diagram of 'em' box showing ascent and descent.

The coordinate system for defining glyphs is based on the “em box” (blue square). The origin of the coordinate system for Latin based glyphs is at the baseline on the left side of the box. The baseline divides the em box into two parts.

The distinction between ‘ascent’ and descent’ is important as the height of the CSS line box is calculated by finding independently the maximum ascent and the maximum descent of all the glyphs in a line of text and then adding the two values. The ratio between ascent and descent is a font design issue and will be different for different font families. Mixing font families may then lead to a line box height greater than that for a single font family.

Two 'M' glyphs from different fonts aligned to their alphabetic baseline.

Two ‘M’ glyphs with the same font size but from different font families (DejaVu Sans and Scheherazade). Their glyph boxes (blue rectangles) have the same height (equal to the em box or font size) but the boxes are shifted vertically so that their baselines are aligned. The resulting line box (dashed red rectangle), assuming a ‘line-height’ value of ‘1’, has a height that is greater than if just one font was used.

Keeping the same font family but mixing different sizes can also give results that are a bit unexpected.

Two text blocks.

Left: Text with a font size of 25 pixels and with a ‘line-height’ value of ‘2’. Right: Same as left but font size of 50px for middle line. Notice how the line boxes (red dashed rectangles) are lined up on a grid but that the baselines (light-blue lines) on the right are not; the middle right line’s baseline is off the grid.

So far, we’ve discussed only ‘line-height’ values that are unitless. Both absolute (‘px’, ‘pt’, etc. ) and relative (‘em’, ‘ex’, ‘%’) units are also allowed. The “computed” value of a unitless value is the unitless value while the “computed” value of a value with units is the “absolute” value. The actual value “used” for determining line box height is for a unitless value, the computed value multiplied by the font size, while for the values with units it is the “absolute value” For example, assuming a font size of 24px:

‘line-height:1.5′
computed value: 1.5, used value: 36px;
‘line-height: 36px’
computed and used values: 36px;
‘line-height: 150%’
computed and used values: 36px;
‘line-height: 1.5em’
computed and used values: 36px.

The importance of this is that it is the computed value of ‘line-height’ that is inherited by child elements. This gives different results for values with units compared to those without as seen in the following figure:

Two text blocks.

Left: Text with a font size of 25 pixels and with a ‘line-height’ value of ‘2’. Right: Same as left but ‘line-height’ value of ‘2em’. With the unitless ‘line-height’ value, the child element (second line, span with larger font) inherits the value ‘2’. As the larger font has a size of 50px, the “used” value for ‘line-height’ is 100px (2 times 50px) thus the line box is 100px tall. With the ‘line-height’ value of ‘2em’, the computed value is 50px. This is inherited by the child element which is then used in calculating the line box height. CodePen.

The astute observer will notice that in the above example the line box height of the middle line on the right is not 50 pixels as one might naturally expect. It is actually a bit larger. Why? Recall that the line box height is calculated from the maximum ascent and maximum descent of all the glyphs. One small detail was left out. CSS dictates that an imaginary zero width glyph called the “strut” be included in the calculation. This strut represents a glyph in the containing block’s initial font and with the block’s initial font size and line height. This throws everything out of alignment as shown in the figure below.

'A' and 'D' glyphs aligned to a common baseline with the 'D' having twice the font size as the 'A'.

Let the ‘A’ represents the strut. The glyph boxes for the ‘A’ and ‘D’ without considering line height are shown by blue rectangles. The glyph boxes with line height taken into account are shown by red-dashed rectangles. For the ‘D’, the glyph boxes with and without taking into account the line height are the same. Note that both the ‘A’ and ‘D’ boxes with line height factored in have the same height (2em relative to the containing block font size). The two boxes are aligned using the ‘alphabetic’ baseline. This results in the ‘A’ glyph box (with effect of line height) extending down past the bottom of the ‘D’ glyph box. The resulting line box (solid-pink rectangle) height is thus greater than either of the glyph box heights. The extra height is shown by the light gray rectangle.

So how can one keep line boxes on a regular grid? The solution is to rely on the strut! The way to do this is to make sure that the ascents and descents all child elements are smaller than the containing block strut’s ascent and descent values. One can do this most easily by setting ‘line-height’ to zero in child elements.

Two blocks of text, both showing evenly spaced lines. The third line on the right has text with a font size twice the rest of the lines.

Text with evenly spaced lines. Left: All text with the same font size. Right: The third line has text with double the font size but with a ‘line-height’ value of ‘0’. This ensures that the strut controls the spacing between lines. CodePen.

As one can see, positioning text on a regular grid can be done through a bit of effort. Does it have to be so difficult? There maybe an easier solution on the horizon. The CSS working group is working on a “Line Grid” specification that may make this trivial.

SVG Working Group Editor’s Meeting Report — London — 2016

First, let me thank all the people that donated to Inkscape’s SVG Standards Work fund as well as to the Inkscape general fund that made my attendance possible.

The subset of the SVG working group met in London after the LGM meeting to get down to the nitty gritty of getting the SVG 2 specification ready to move to the “Candidate Recommendation” (CR) stage. Three of the core group members (Nikos, Amelia, and myself) were joined some of the days by three other group members who do not normally participate in the weekly teleconferences. This was a great chance to get some new eyes looking at the spec.

Most of the time was spent in reading the specification together. We managed to get through about half the chapters including the most problematic ones. When we found problems we either made changes on the fly if possible or filed issues if not. We recently switched to Github to keep track of issues which seems to be working well. You can see outstanding issues at our issue tracker. (Feel completely free to add your own comments!)

Minutes of the meetings can be found at:

As this was a meeting focused on getting the spec out the door, our discussions were pretty mundane. Nevertheless, let me give you a flavor of the kinds of things we addressed. It was brought up in the issue tracker that the specification is unclear on how text should be rendered if it follows a <textPath> element. It never occurred to me (and probably to most people) that you could have in an SVG file the following:

<text x="50" y="150">Before<textPath xlink:href="#path">On Path</textPath>After</text>
]]>

For an implementer, it is fairly straight forward to figure out where to position the “Before” (use the ‘x’ and ‘y’ attributes) and the “On Path” (use the path) but where should the “After” be rendered? Firefox won’t render it at all. Chrome will render the “After” starting at the end position of the ‘h’ in “On Path”. After some discussion we decided that the only really logical place to render the “After” was at the end of the path. This is the only point that is well defined (the ‘h’ can move around depending on the font used to render the text).

Defining a fill area using <div&gt and floats.

How the above text element is rendered according to the decision of the group at the London meeting. The starting point for text after the <textPath> element is at the end of the path (red dot).

We will have another editor’s meeting in June in Amsterdam where hopefully we’ll finish the editting so we can move the spec to CR. We’ll then need to turn our attention to writing tests. Please consider making a donation to support my travel to this meeting at the link at the start of the post! Thanks.

SVG Working Group Meeting Report — Sydney — 2016

The SVG Working Group had a four day face-to-face meeting in Sydney this month. Like last year, the first day was a joint meeting with the CSS Working Group. I would like to thank all the people that donated to Inkscape’s SVG Standards Work fund as well as to the Inkscape general fund that made my attendance possible.

Joint CSS and SVG Meeting

Minutes

CSS Stroke and Fill Specification

The CSS working group would like to allow the use of the SVG ‘stroke’ and ‘fill’ properties on CSS text as well as other places in CSS (e.g. box border). They’ve created a prototype document that basically copies the SVG stroke and fill text adding the necessary parts to get it to work with CSS text. This document has been temporary called Text Decoration 4 (the title will certainly be changed). They’ve proposed converting the ‘stroke’ and ‘fill’ properties to short-hands. (A short-hand property allows setting multiple CSS properties at the same time.) They also would like to see the ‘stroke-alignment’ property implemented (this property allows one to stroke only the inside or only the outside of a shape). I pointed out the difficulty in actually defining how ‘stroke-alignment’ would work. The SVG WG moved some of the advance stroking properties out of the SVG 2 specification into an SVG Stroke module to avoid holding up the SVG 2 specification. (See my blog entry on this as well as the issues in the SVG Stroke module.) Other issues discussed were how glyphs are painted (‘paint-order’, ‘text-shadow’), multiple strokes/fills, dashing, and ‘text-decoration-fill/stroke’.

Text Issues

Next we covered a whole slew of text issues I raised dealing with flowed text in SVG.

Strategy for using CSS in SVG for wrapping

The first issue was to agree on how SVG and CSS are related. I presented my strategy: that HTML/CSS and SVG have there own methods to define an area to fill called the wrapping area. Once this area is defined, one uses CSS rules to fill it. Here is how one defines the wrapping area in both HTML/CSS and SVG:

The CSS/HTML code:
    <style>
      .wrapper { shape-inside: xxx; ... }
      .float-left { shape-outside: yyy; ... }
      .float-right { shape-outside: zzz; ... }
    </style>
    <div id="wrapper">
      <div id="float-left"/>
      <div id="float-right"/>
      <p>
	Some text.
      </p>
    </div>
The result:
Defining a fill area using <div&gt and floats.

Wrapped text in HTML. One starts with a wrapper <div>. The ‘shape-inside’ property on this <div> reduces the wrapping area to the circle. Two floats <div>s are defined, one on the left (green rectangle) and right (red rectangle). The area that the floats exclude is reduced by the half-ellipses defined by the ‘shape-outside’ property. The final wrapping area is the light blue shape.

The CSS/SVG code:
    <style>
      .my_text { shape-inside: xxx; shape-outside: yyy, zzz; ... }
    </style>
    <text id="my_text">Some text.</text>
The result:
Defining a fill area in SVG.

Wrapped text in SVG 2. One starts with a element. The ‘shape-inside’ property on this element defines the wrapping area. The ‘shape-outside’ property reduces the wrapping area. The final wrapping area is the light blue shape.

It was pointed out at a discussion on Day 2, that the use of ‘shape-outside’ in SVG was not consistent with the CSS model. The ‘shape-outside’ property defines the effective shape of an element as seen by other elements. We agreed to change ‘shape-outside’ to an SVG only property ‘shape-subtract’.

How is the first line placed in a wrapped shape?

When the top of a wrapping area is not horizontal, how do you decide where the first line of text is placed?
Different strategies for locating the position of the first line in pyramid shape.

Alternative solutions for where to start layout of the first line, from top to bottom: First place a chunk of text fits: No restrictions, Restricted to multiples of ‘line-height’, Restricted to multiples of half ‘line-height’.

We were informed that with CSS floats, the line is moved down until the first text chunk fits. To be consistent with CSS, we should follow the same rule. A future CSS Line Grid specification may allow one to control line position.

Overflow text

What should happen when the shape isn’t big enough for all the text? This is mostly an issue for browsers where the user can specify a larger font (i.e. for accessibility reasons). CSS has an ‘overflow’ property that selects either clipping or scrolling. Neither of these is a great solution for SVG. The tentative solution in the CSS Shapes 2 specification of extending the <div> below the wrapping area doesn’t work for SVG. I proposed that there should be a means to expose the overflowed text, such as displaying it on hovering over the ellipses at the end of the displayed text. There was some interest in this. For the moment, the best solution is probably to explicitly not define what should happen, leaving it to a future time to specify the behavior. In reflecting on this after the meeting, I think one strategy is to suggest that authors provide an overflow region by adding an additional shape to the value of the ‘shape-inside’ property.

How does text wrap in a shape with a doughnut hole or other feature that breaks a line into parts?

Since SVG can have arbitrary shapes, it is possible to create shapes that break a single text line into parts. How should these shapes be filled?
A shape a bit like an 'H' showing text be laid out on both sides of the central break.

An example of a shape that breaks lines into parts.

The ‘wrap-flow’ property does not apply here as that dictates how text is flowed around floats. A new ‘wrap-inside’ property has been proposed. For the moment, however, it was agreed that text should flow as shown in the above figure. This would be the default value of any new property.

Flowing into multiple shapes

The aborted SVG 1.2 specification allowed text to be flowed sequentially into multiple shapes. This is something that Inkscape implemented and I would like to see this ability preserved. The proposed CSS methods for doing this don’t work for SVG. I proposed giving the ‘shape-inside’ property a list of shapes. It was agreed that this would be an acceptable solution for SVG. (And it can provide a place for over-flowed text.)

How is the first glyph positioned on a line?

When dealing with rectangles, it is straight forward to find the position of the first glyph but with arbitrary shapes it is more difficult. I asked what was the correct CSS method. I was told that one considers the glyph box extended upwards/downwards as dictated by using the height of the entire line box. (For example, if one has a ‘line-height’ value of 2 with a 16px font, the line box has a height of 32px.) It’s not clear where this is specified in CSS.

Baseline issues

We next switch from text wrapping to baseline issues. Text is normally laid out along a baseline which can be different depending on the script. For example, western scripts normally use an Alphabetic baseline while Indic scripts use a Hanging baseline.
Three different scripts showing their baselines.

Example baselines (red lines) in three different scripts. From left to right: alphabetic, hanging, ideographic. The EM box is shown in blue for the ideographic script.

The proposed CSS definition of the ‘dominant-baseline’ property differs from the SVG 1.1 definition in that several of the SVG 1.1 values are missing. We discussed the missing values. Some of the missing values will be added to the CSS definition. There is one fundamental difference between CSS and SVG 1.1: With SVG 1.1 (and XSL 1.1), the baseline table does not change automatically when the font size is change. One must explicitly reset the table by setting the ‘dominant-baseline’ property to the ‘reset-size’ value. The proposed CSS definition will automatically reset the table on change in font size. I’m not sure this is a necessarily good change (it’s definitely not backwards compatible) but then this is probably such a small corner case that it doesn’t really matter.

Figure from the XSL 1.1 specification showing the default behavior upon ‘font-size’ change. The baseline table does not change.

The CSS ‘auto’ value has one small problem for SVG. With vertical text, if the value of ‘text-orientation’ is ‘sideways’, the alphabetic baseline is used. SVG 1.1 always uses the central baseline for vertical text. The CSS specification will be fixed to be compatible with SVG 1.1.

We also discussed default values for the various baselines. In principle, one gets the values of the baselines from the font but most fonts don’t have baseline tables; for interoperability we need defaults. It was decided that the CSS group would investigate this more and come up with a recommended set of default values.

Filters

I brought up a couple of issues dealing with SVG/CSS Filter Effects module. The first was the status of the document. Progress towards finishing this document seems to have stopped. Two of the three editors are no longer very active. The third editor was present and said he would push this through.

I was also interested in the next level as I have a filter primitive I would like to see added. It turns out that Level 2 has already been started. Not much is in there now. Apple, however, has a bunch of filter primitives they would like to add.

Next we covered the issue of artifacts created by using eight bit per channel colors (8 bpc) in the filter chain. The specification as written doesn’t directly specify that 8 bpc color should be used but a couple examples do assume this. I proposed that they be converted to use the range 0 to 1 so that one can use floats to describe color channels. This would solve the problem. Dean Jackson from Apple will investigate the possibility of requiring that floats be used rather than ints in the filter chain.

A blob showing artifacts due to the use of only an 8 bit bump map as input to a lighting filter primitive.

An example of the artifacts one gets due to using only 8 bit alpha channel bump map as the input into a lighting filter primitive.

Gradient Banding

An unplanned topic… how to get rid of banding in gradients. Dithering is a well known technique. Can dithering be added to CSS gradients? There seems to be support for this idea. The syntax and technique needs to be specified.

SVG Meeting — Day 1

Minutes

Path Stroking: Conformance

I brought up some time ago the inconstancy in how paths are stroked with the half the stroke width is greater than the radius of curvature (see my blog post). I suggested that maybe we define the proper way to stroke a path. Another SVG Working Group member took an action to research this topic more. He consulted members of the PDF standards group as well as hardware vendors. After he presented his findings we agreed that being more specific at this time wasn’t really a viable option as the way paths are stroked is such a fundamental property of the underlying graphics libraries that are used to render SVG.

Fallback for the ‘arc’s Line Join

It has been noted that the currently specified fallback for the ‘arcs’ line join of a ‘miter’ join is less than idea. I presented a number of alternative options to the working group. They agreed to a change in the fallback to one of two possible fallbacks:

Fallback options: Blue: increasing the radius of the inner arc to meet the outer arc. Red: increasing the radius of the inner arc while decreasing the radius of the outer arc until the two meet.

I added to Inkscape trunk’s ‘Join type’ LPE the different possible fallbacks for people to test. A full discussion can be found at here. If you have an opinion, let me know.

Path morphing

With the possible demise of SMIL animations, I asked what was the status of turning the path data attribute into a property so that it can be animated using CSS/Web Animations. The response was that it hasn’t been forgotten and that Chrome will soon have an implementation. (SMIL usage shot up dramatically after YouTube started to use SMIL to animate the Play/Pause button.) CSS path animation will be based on SVG 1.1’s path animation. A future version might include more flexible rules for path interpolation (at the moment, animated paths must have the same number and type of path commands).

SVG Meeting — Day 2

Minutes (Some minutes missing due to operator error…. The meeting crossed midnight GMT which confuses the minute making bot.)

Presentation Attributes

SVG has the idea of presentation attributes. These are attributes that can also be set using CSS. Recently, we’ve promoted quite a few geometric attributes to be presentation attributes to allow setting them with CSS (it does seem a bit strange to “style” the position of a rectangle… but that is what we’ve enabled). As we add new properties, should these also be turned into presentation attributes? It is a bit of a maintenance burden to ensure all new properties are also presentation attributes, especially as we adopt a plethora of new text properties. We have already decided to require CSS so there is not necessarily a need for new presentation attributes. HTML has already deprecated/removed presentation attributes in favor of CSS. After a bit of discussion, we have decided to follow HTML’s lead.

Other Topics

Topics covered included how SVG is integrated into HTML, coordinate precision in generated content, and aligning dashes to corners. As mentioned earlier, we decided to create a ‘shape-subtract’ property rather than misuse the ‘shape-outside’ property. Most of the afternoon was spent with specification editing.

SVG Meeting — Day 3

This was a specification editing day. (It is extremely useful to have the working group present when editing as any issues that arise can be immediately dealt with.)

SVG Mesh Gradients, Heat Maps, and a Plea

Introduction

Mesh gradients are great for creating life-like illustrations. Long asked for, they were one of the first things we added to the SVG 2 specification. I added mesh support to Inkscape (behind a compiler flag) for testing. There was one problem that was immediately apparent: as mesh gradients use bilinear interpolation between corner colors, there were non-smooth color transitions between the mesh patches leading to unwanted visual artifacts. See my blog post from a few years ago for more details about this problem.

A little bit of investigation showed that Adobe Illustrator and CoralDRAW use some sort of smoothing algorithm to get rid of the artifacts. We asked Adobe if they would give us the algorithm but they replied no. I did a little research and found that using bicubic interpolation would be a good solution. After adding a trial implementation in Inkscape and demonstrating it at last year’s Sydney SVG working group meeting, I got the group’s approval to add this to the SVG 2 specification.

Heat Maps

The discussion in the Wikipedia of bicubic interpolation has some nice heat map illustrations showing the effects of bilinear vs. bicubic interpolation. I thought it would be interesting to duplicate these illustrations using SVG. Here is how I did it.

The first step is to create a mesh. It is rather easy to do this in Inkscape with the mesh tool. I created a raw mesh with a 3×3 array of patches to match the one in the Wikipedia article. The tricky part is then to set each patch corner to a gray level based on the data. I estimated the data values from looking at the color chart in the Wikipedia illustrations. I then converted each gray level into an appropriate RGB hex color value. Here is the result with bilinear interpolation:

Heat map using a mesh gradient without smoothing.

A mesh gradient with a 3×3 array of patches using bilinear interpolation (i.e. without smoothing). The gray levels at the patch corners represent the input data.

One can clearly see the visual artifacts at the mesh boundaries (enhanced by Mach Banding). Switching to bicubic interpolation produces a smoother mesh as seen next:

Heat map using a mesh gradient without smoothing.

A mesh gradient using bicubic interpolation (i.e. with smoothing). The gray levels of the patch corners represent the input data.

The next step is to transform the gray levels into a color scale. For this I turned to SVG filters. I used a filter consisting of a single Component Transfer filter primitive. I created a table to map gray level to color by inverting the estimation I used to convert the colors into gray values. Here are the results for both bilinear and bicubic interpolation:

Heat map using a mesh gradient without smoothing.

A mesh gradient using bilinear interpolation (i.e. without smoothing) with a simple SVG filter to convert gray levels to color values.

Heat map using a mesh gradient without smoothing.

A mesh gradient using bicubic interpolation (i.e. with smoothing) with a simple SVG filter to convert gray levels to color values.

The match between these images (generated with Inkscape) and those in the Wikipedia article are pretty good.

Plea

Convincing the SVG working group to add meshes to SVG and then to adding the auto-smoothing option would not of been possible without attending the SVG working group meetings in person. It is much easier to lobby for things when one can provide live demonstrations. At these meetings I’ve been able to get auto-flowed text, text on a shape, font-features support, hatch fills, the arcs and clipped miter line joins, etc. into the SVG 2 specification. The Inkscape board has been allocating funds for my travel (thanks Inkscape donors!). To enable travel to future working group meetings, a designated fund for SVG specification work has been set up. The SVG working group holds about three face-to-face meetings each year (in addition to weekly teleconferences). If you are interested in supporting SVG development from an end-user perspective, please consider donating. More information on the work I’ve done as well a donations link can be found at the Inkscape website on the SVG Standards Work page. (Donations are tax deductible in the US.)

An Inkscape SVG Filter Tutorial — Part 2

Part 1 introduced SVG filter primitives and demonstrated the creation of a Fabric filter effect. Part 2 shows various ways to colorize the fabric. It ends with an example of using the techniques learned here to draw part of a bag of coffee beans.

Dying the Fabric

Our fabric at this point is white. We can give it color a variety of ways. We could have started off with a colorized pattern but that would not allow us to change the color so easily. And as this is a tutorial on using filters, lets look at ways the color can be changed utilizing filter primitives.

Coloring with the Flood, Blend, and Composite Primitives

We can use the Flood filter primitive to create a sheet of solid color and then use the Blend filter primitive to combine it with the fabric. The resulting image bleeds into the background. We’ll use the Composite filter primitive to auto-clip the background.

The Flood Filter Primitive

Add the Flood filter primitive to the filter chain by selecting Flood and clicking on the Add Effect button. The fabric will turn a solid black. Like the Turbulence filter primitive, the Flood filter primitive takes no inputs but simply fills the filter region with a solid color Black is the default flood color. You can change the color by clicking on the color sample next to Flood Color: in the dialog. Change the color however you wish. Leave the Opacity at one.

The Blend Filter Primitive.

Next add the Blend filter primitive. The drawing will be unchanged. Connect the Blend input to the last Displacement Map. The fabric should appear on top of the flood fill. This is expected as the default blending mode is Normal which simply draws the second image over the first. Use the drop-down menu to change the Mode to Multiply. This results in the lighter areas of the fabric taking on the flood color.

The output of the filter chain after blending.

Try experimenting with the other blending modes.

The Composite Filter Primitive

The flood fill leaks into the background. This can be removed by clipping the image to fabric area using the Composite filter primitive. Add the Composite filter primitive to the filter chain. The resulting image is again unchanged. Connect the second input to the composite filter to the last Displacement Map filter primitive. Still the image remains unchanged. Now change the Operator type to In. This dictates that the image should be clipped to the area that is “In” the image created by the second Displacement Map filter primitive.

Filter Dialog image.

The Filter Effect dialog after adding and adjusting the Flood, Blend, and Composite filter primitives.

The output of the filter after compositing.

Coloring the Fabric with the Component Transfer Filter Primitive

The Component Transfer filter primitive maps, pixel by pixel, the colors from an input image to different colors in an output image. Each “component” (Red, Green, Blue, and Alpha) is mapped independently. The method for mapping is determined by the Type; each Type has its own attributes. We’ll use the Linear and Identity mappings.

Identity
The output component has the same value as the input component.
Linear
The output component is equal to: intercept + input × slope. This is identical to the Identity type if the intercept is zero and the slope is one.

Replace the Flood Fill, Blend, and Composite filter primitives in the above filter chain by the Composite Transfer filter primitive. (To delete a filter primitive, right-click on the filter primitive name and select Delete in the menu that appears.) The just removed three-primitive filter chain mapped black to black and white to the flood color. We can duplicate this by setting the Red, Green, and Blue component transfer types to Linear (keeping the Alpha component type set to Identity). The condition that black maps to black requires that the Intercept values all be set to zero. The condition that white maps to the flood color dictates the slopes. The RGB values for the flood color used above are 205, 185, 107 on a scale where 255 is the maximum value. These values translate to 0.80, 0.73, 0.42 on a scale where the maximum value is one. Since an input value of 1.0 for the red component must result in a value of 0.80 we can see that these values are the required slopes.

Graph of input vs. output for the red, green, and blue channels.

Graph of the transfer functions.

Filter Dialog image.

The Filter Effect dialog after adding and adjusting the Component Transfer filter primitive.

The output of the filter after adding and adjusting the Component Transfer filter primitive.

Now suppose we want the fabric to be more subtle. We can change the mapping so that for each component, zero is mapped to half the maximum value. In this case we have the following values (RGB): Intercepts: 0.40, 0.36, 0.21 and Slopes: 0.40, 0.37, 0.21. See the following figure:

Graph of input vs. output for the red, green, and blue channels.

Graph of the transfer functions where the darkest value is half the lightest value.

Filter Dialog image.

The Filter Effect dialog after adding and adjusting the Component Transfer filter primitive.

The output of the filter after adjusting the Component Transfer filter primitive so the darkest areas have half the component values of the lightest.

Coloring the Fabric with the Color Matrix Filter Primitive

This filter primitive, unlike the Component Transfer, can intermix the color components. It does not, however, have the fine control over the transfer curves like in the Component Transfer filter primitive. There are several Types in this filter primitive. The Saturate, Hue Rotate, and Luminous to Alpha types are shortcuts for the more generic Matrix type. We need to use the Matrix type to match the results of the previous filters.

First replace the Component Transfer filter primitive by the Color Matrix filter primitive. After adding the new primitive, the fabric may disappear; that is a bug in Inkscape. Click on the matrix in the Filter Dialog and the fabric should reappear. The initial matrix is the Identity matrix (consisting of ones on the diagonal) which does not change the image.

The rows in the matrix control the output of, from top to bottom, the Red, Green, Blue, and Alpha channels. The columns correspond to the input, again in the same Red, Green, Blue, and Alpha order. The last column allows one to enter a constant offset for the row. For example, one can make a green object red by changing the top row to “0 1 0 0 0″ which means that the Red channel output is 0×R + 1×G + 0×B + 0×A + 0, where R, G, B, and A are the input values for the Red, Green, Blue, and Alpha channels respectively (on a scale of zero to one).

To change the values in the matrix, click first on a row of numbers to select the row and then click on a numeric entry in the row. The following figures show the values needed to match the fabric samples above.

Filter Dialog image.

The Filter Effect dialog after adding and adjusting the Color Matrix filter primitive to match the first (high contrast) fabric sample above.

Filter Dialog image.

The Filter Effect dialog after adding and adjusting the Color Matrix filter primitive to match the second (lower contrast) fabric sample above.

Coloring the Fabric Using the Fill Color and the Tile Filter Primitive

In an ideal world, a fabric filter would just take as input the color of an object and use that to blend with a pattern. SVG filters do have the ability to do this. One would read in a pattern tile using the Image filter primitive and then tile the pattern using the Tile filter primitive. But the Tile filter primitive is the one filter primitive that Inkscape hasn’t implemented. While more convenient, this method would still lack the fine control over color that the above methods have.

The output of a filter using the Tile primitive. The two rectangles differ only in Fill color. Renders correctly in Chrome, incorrectly in Firefox and Inkscape.

Putting it All Together

Let’s do something with the fabric! We could stencil some text on the fabric to make it look like part of a bag of coffee beans. The best way to do this is to break the filter up into two separate filters. The first will distort the weave (using the first Turbulence and Displacement Map pair and color the fabric while the second will add a gentle wave to both the fabric and text (using the second Turbulence and Displacement Map pair). The text is given its own filter to take away the sharp edges and to also give it a bit of irregularity independent of the weave. The text could be blended on top of the fabric by giving it an opacity of less than one. A better effect can be achieved, however, by using the new mix-blend-mode property. Inkscape can render this property but does not yet have a GUI to set it. Firefox supports this property and Chrome should soon (if it doesn’t already). I’ve used the mix-blend-mode value of multiply by adding the property to the text style attribute with the XML editor. The fabric and text are then grouped together before applying the “wave” filter to the group.

Part of a bag of coffee beans. Three filters are used. The first to distort the weave and give color to the fabric, the second to slightly blur and distort the text, and the third to take the blended together fabric and text and give them both a gentle wave.

Note, it is possible to put the text in the “defs” section and use the Image filter primitive to import the text into a filter so that the blending can be done with the Blend filter primitive. This isn’t easy to do in Inkscape and Firefox seems to have problems rendering it.

I hope you enjoyed this tutorial. Please leave comments and questions!


A section of a bag of coffee beans.

A PNG image just for Google+ which doesn’t support SVG images.

An Inkscape SVG Filter Tutorial — Part 1

Part 1 introduces SVG filter primitives and demonstrates the creation of a Fabric filter effect. Part 2 shows various ways to colorize the fabric.

Introduction

SVG filters allow bitmap-type manipulations inside a vector format. Scalability is preserved by pushing the bitmap processing to the SVG renderer at the point when the final screen resolution is known. SVG filters are very powerful, so powerful in fact that they have been moved out of SVG and into a separate CSS specification so that they can also be applied to HTML content. This power comes with a price: SVG filters can be difficult to construct. For example, a simple drop shadow filter consists of three connected filter primitives as shown in this SVG code:
<filter id="DropShadow">
  <feOffset in="SourceAlpha" dx="2" dy="2" result="offset"/>  ❶
  <feGaussianBlur in="offset" stdDeviation="2" result="blur"/>  ❷
  <feBlend in="SourceGraphic" in2="blur" mode="normal"/>  ❸
 </filter>
  1. Offset filter primtive: Create an image using the text alpha (SourceAlpha) and shift it down and right two pixels. Results in shifted black text.
  2. Gaussian Blur filter primitive: Blur result of previous step (“offset”).
  3. Blend filter primtive: Render the original image (SourceGraphic) over the result of the previous step (“blur”).
Some sample text!

A drop shadow applied to text.

Inkscape contains a Filter Dialog that can be used to construct filters. Here is the dialog showing the above drop-shadow filter effect:

Filter dialog showing the three filter primitives and how they are connected.

The Inkscape Filter Dialog showing a drop-shadow filter effect. The dialog shows the filter primitives and how their inputs (left-pointing triangles) are connected (black lines). It also contains controls for setting the various filter primitive attributes.

There can be more than one way to construct the same filter effect. For example, the order of the offset and blur primitives can be swapped without changing the result:

Some more text!

An alternative drop-shadow filter applied to text.

Inkscape contains over 200 canned filters effects, many of which have adjustable parameters. But sometimes none of them will do exactly what you want. In that case you can construct your own filter effect. It’s not as hard as it first seems once you understand some of the basic filter primitives.

A Fabric Filter

This tutorial creates a basic filter that can be applied to a pattern to create realistic fabric. It will introduce several very useful filter primitives that are fundamental to most of Inkscape’s canned filter effects.

Creating a Pattern

To begin with, we need a pattern that is the basis of the weave of the fabric. I’ve constructed a simple pattern consisting of four rectangles, two for the horizontal threads and two for the vertical threads. I’ve applied a linear gradient to give them a 3D look. One can certainly do better but as the pattern tile is quite small, one need not go overboard. Once you have drawn all the pattern parts, select them and then use Objects->Pattern to convert to a pattern. The new pattern will then be available in the Pattern drop-down menu that appears when the Pattern icon is highlighted on the Fill tab of the Fill and Stroke dialog.

The pattern consisting of four rectangles with linear gradients simulating a small section of the fabric weave.

The pattern (shown scaled up).

Next, apply the fabric pattern to the an object to create simple fabric.

The basic weave pattern applied to a large rectangle.

The pattern applied to a large rectangle.

Adding Blur

The pattern looks like a brick wall. It’s too harsh for fabric. We can soften the edges by applying a little blur. This is done through the Gaussian Blur filter primitive. Open the Filter Editor dialog (Filters->Filter Editor). Click on the New button to create a new, empty filter. A new filter with the name “filter1″ should be created. You can double click on the name to give the filter a custom name. Apply the filter to the fabric piece by selecting the piece and then checking the box next to the filter name. Your piece of fabric will disappear; don’t worry. We need to add a filter primitive to get it to show back up. To add a blur filter primitive select Gaussian Blur in the drop-down menu next to Add Effect and then clicking the Add Effect button. The fabric should now be visible with the blur effect applied. You can change the amount of blur by using the slider next to Standard Deviation; a value of 0.5 seems to be about right.

Filter Dialog image.

The Filter Effect dialog after applying a small amount of blur.

Note how the input to the Gaussian Blur primitive (triangle next to “Gaussian Blur”) is linked (under Connections) to the Source Graphic.
The basic weave pattern applied to a large rectangle.

A small amount of blur applied to the fabric.

Distorting the Threads

The pattern is still too rigid. The threads in real fabric are not so regular looking. We need to add some random distortions. To do so, we’ll link up two different filter primitives. The first filter primitive, Turbulence, will generate random noise. This noise will be used as an input to a Displacement Map filter primitive where pixels are shifted based on the value of the input.

The Turbulence Filter Primitive

Add a Turbulence filter primitive to the filter chain by selecting Turbulence from the drop-down menu next to Add Effect button, the click on the button. You should see a rectangle region filled with small random dots. There are a couple of things to note: The first is that the rectangle will be bigger than you initial object. This is normal. The filter region is enlarged by 10% on each side and the Turbulence filter fills this region. This is done on purpose as some filter primitives draw outside the object (e.g. the Gaussian Blur and Offset primitives). You can set the boundary of the filter region under the Filter General Settings tab. The default 10% works for most filters. You don’t want the region to be too large as it effects the time to render the filter. The second thing to note is that the Turbulence filter primitive has no inputs despite what is shown in the Filter Editor dialog.

There are a number of parameters to control the generation of the noise:

Type
There are two values: Turbulence and Fractal Noise. The difference between the two is somewhat technical so I won’t go into it here. (See the Turbulence Filter Primitive section in my guide book.)
Base Frequency
This parameter controls the granularity of the noise. The value roughly corresponds to the inverse of the length in pixels of the fluctuations. (Note that the default value of ‘0’ is a special case and doesn’t follow this rule.)
Octaves
The number of octaves used in creating the turbulence. For each additional octave, a new contribution is added to the turbulence with the frequency doubled and the contribution halved compared to the proceeding octave. It is usually not useful to use a value above three or four.
Seed
The seed for the pseudo-random number generator used to create the turbulence. Normally one doesn’t need to change this value.

One can guess that variations in the threads are about on the order of the distance between adjacent threads. For the pattern used here, the vertical threads are 6 pixels apart. This gives a base frequency of about 0.17 (i.e. 1/6). The value of Type should be changed to Fractal Noise. (Both Type values give good visual results but the Turbulence value leads to a shift of the image down and to the right for technical reasons.) Here is the resulting dialog:

Filter Dialog image.

The Filter Effect dialog after adding the Turbulence filter primitive.

And here is the resulting image:

The output of the first turbulence filter primitive.

The output of the filter chain which is at this point the output of the Turbulence filter primitive.

The Displacement Map Filter Primitive

Now we need to add the Displacement Map filter primitive which will take both the output of the Gaussian Blur and the Turbulence filter primitives as inputs. Select Dispacement Map from the drop-down menu and then click on the Add Effect button. Note that both inputs to the Dispacement Map filter primitive are set to the last filter primitive in the filter chain. We’ll need to drag the top one to the Gaussian Blur filter primitive. (Start the drag in the little triangle at the right of the filter primitive in the list.) Again, the image doesn’t change. We’ll need to make one more change but first here are the parameters for the Displacement Map filter primitive:

Scale
The scale factor is used to determine how far pixels should be shifted. The magnitude of the shift is the value of the displacement map (on a scale of 0 to 1) multiplied by this value.
X displacment
Determines which component (red, green, blue, alpha) should be used from the input map to control the x displacement.
Y displacement
Determines which component (red, green, blue, alpha) should be used from the input map to control the y displacement.

For our purpose, any values of X displacement and Y displacement are equally valid as all channels contain the same type of pseudo-random noise. To actually see a shift, one must set a non-zero scale factor. A value of about six seems to give a good effect.

Filter Dialog image.

The Filter Effect dialog after adding and adjusting the Displacement Map filter primitive.

And here is the resulting image:

The output of the first turbulence filter primitive.

The output of the filter chain after adding and adjusting the Displacement Map filter primitive.

Distorting the Fabric

Fabric rarely lies flat unless stretched and even then it is hard to make the threads lie straight and parallel. We can add a random wave to the fabric by adding another Turbulence and Displacement Map pair, but this time using a lower Base Frequency. Repeat the instructions above to add the two filter primitives but this time connect the top input to the Displacement Map to the previous Displacement Map. Set the Base Frequency to a value of 0.01. Set the Type to Fractal Noise. Set the Scale to ten.

Filter Dialog image.

The Filter Effect dialog after adding and adjusting the second Turbulence and Displacement Map filter primitives.

And here is the resulting image:

The final fabric image.

The output of the filter chain after distorting the fabric.

Of course, the pattern and filter can be applied to an arbitrary shape:

The pattern and filter applied to a blob.

The pattern and filter applied to a cloth patch.

Conclusion

We have constructed a basic Fabric filter but there is plenty of room for improvement. In the next part we’ll look at ways to add color to the fabric.


A section of a bag of coffee beans.

A PNG image just for Google+ which doesn’t support SVG images.

Font Features Land in Inkscape Trunk

I’ve just landed basic font features support in the development version of Inkscape. What are font features and why should you be excited? (And maybe why should you not be too excited.)
The letter combination 'st' shown without a ligature and with a 'historical' ligature.

Font Features

Font features support allows one to enable (or disable) the OpenType tables within a given font, allowing you to select alternative glyphs for rendering text.
A series of examples showing the same text with and without applying various OpenType tables.

A sample of font features in action. The font is Linux Biolinum which has reasonable OpenType tables. Try the SVG (with WOFF).

The new CSS Fonts Module Level 3 adds a variety of CSS properties for defining which OpenType tables to enable/disable (as well as having nice examples of each property’s use — this is one of the more readable W3C specifications). Inkscape trunk supports the ‘font-variants-liguatures’, ‘font-variant-caps’, ‘font-variant-numeric’, ‘font-variant-position’, and ‘font-feature-settings’ properties. The properties can be set under the Variants tab in the Text and Font dialog.
The 'Variants' Tab in the 'Text and Fonts' dialog showing a series of buttons to select which font features are enabled.

The Variants tab in the Text and Font dialog.

Why you shouldn’t be too excited

Being able to enable various font features within a font is quite exciting but there are quite a few caveats at the moment:
  • One must use a trunk build of Inkscape linked with the latest unstable version of Pango (1.37.1 or greater).
  • Font feature support in fonts is usually minimal and often buggy. It’s hard to know what OpenType tables are available in which fonts.
  • Browser support is sparse. Firefox has rather good support. Chrome support seems limited to ligatures. UPDATE: As of Chrome 52, Chrome supports font features.
  • Correct display of alternative glyphs requires that the same font as used in content creation is used for rendering. On the Web the best way to do this is to use WOFF but Inkscape has no support for using User fonts (this is a future goal of Inkscape but will require considerable work).

Thanks

I would like to thank: Behdad Esfahbod, maintainer of Pango for adding the code to Pango to make accessing the OpenType tables possible. Thanks as well to Matthias Clasen and Akira Togoh who are the source of the patch to Pango. Thanks also to all the people that supported the Inkscape Hackfest in Toronto where I was able to meet and discuss Pango issues with Behdad in person and also where the idea of adding font feature support to Inkscape germinated.

Google taking the SMIL out of SVG.

Google has recently announced their intention to drop SMIL support in Blink, the rendering engine for Chrome. SMIL is a way to animate SVG’s in a declarative way. Google’s argument is that SMIL animation has not become hugely popular and that Web Animations will provide the same functionality. As a result of this announcement, the SVG working group decided to move SMIL from SVG 2 and into its own specification. One could say that SMIL is on life support at the moment. SMIL’s lack of use is most likely due to its lack of support in IE. Microsoft has declared they will not implement SMIL in IE but they have hinted in the past that they are open to a native JS implementation built on top of Web Animations. So why would losing SMIL be a great loss?
  1. SMIL declarative animations are easier to write compared to JavaScript or CSS/Web Animations.
  2. SMIL animations are in general more performant.
  3. With SMIL animations one can independently animate different attributes and properties.
  4. JavaScript is not allowed to run inside SVGs in many situations due to security issues so it is not a viable alternative in many cases.
  5. Web Animations don’t replace all the functionality of SMIL. For example, one cannot animate attributes including paths. In particular you won’t be able to do this:
Morphing Batman logos.

A variety of Batman logos, animated with SMIL.

Ironically, YouTube is planning on using SMIL to animate buttons. As usual, if you are reading this in a blog aggregator and the images don’t display correctly, try viewing on my blog website. Aggregators don’t play well with SVG. (For more on animating paths, see my blog post on path animations.) You can read about Google’s intention and the debate that is going at the chromium.org Google group. If you use SMIL or plan to, let Google know that it is important to you.
A figure just to have a nice image in Google+ (which doesn’t do SVG… another reason to frown):
Frown face.

Paths: Stroking and Offsetting

Path stroking and offsetting are two intertwined topics; stroking is often implemented by path offsetting. This post explores some of the problems encountered with these path operations.

Stroking: It’s not as easy as it looks.

What could be easier that stroking a path? It’s a fundamental concept in all graphics libraries. You construct a path: in PostScript:
newpath
100 100 moveto
150 100 lineto
10 setlinewidth
stroke
in SVG:
<path d="M 100,100 150,100" stroke-width="10"/>
and voila, you have a horizontal path, 50 pixels long, that is 10 pixels wide. Hmm, if only it were that easy. It turns out that stroking an arbitrary path can be quite complicated. Different graphics libraries can give quite different results.
A simple Bezier path segment with high curvature at one end.

A Bezier path segment with high curvature at the end. Web browsers differ on the rendering. (SVG)

Firefox's rendering of the circle. It appears solid. Chome's rendering of the circle. It appears like a donut.

Rendering of above path: Firefox (left/top), Chrome (right/bottom). (PNG)

There are two different ways to stroke a path. The first method is to pass a line segment of length ‘stroke-width’, centered on and perpendicular to the path, from one end to the other. Any pixels the line crosses are part of the stroke. This seems to be what Firefox does. (An equivalent method is to pass a circle of diameter ‘stroke-width’ centered on the path and then clip the semi-circles at the ends.) The second method is to construct two paths, offset by half the ‘stroke-width’ on each side of the original path and then fill the area between the two paths. This seems to be what Chrome does.
A simple Bezier path segment with high curvature at one end.

A Bezier path segment with high curvature at the end. Stroke constructed by offsetting path. Red: original path, blue: offset paths. (SVG)

Rendering engines appear to fall into one of these two camps:
Sweep a line:
Firefox, Adobe Reader
Offset paths:
Chrome, Inkscape (Cairo), Opera (Presto), Evince, Batik, rsvg
The difference can be also be seen in circular paths.
Two circular paths with strokes of different widths.

Two same size circular paths with different stroke widths. When one-half the stroke width exceeds the circle radius (right circle), web browsers differ in their rendering. (SVG)

Firefox's rendering of the circle. It appears solid. Chome's rendering of the circle. It appears like a doughnut.

Rendering of a circular path when one-half the stroke width is greater than the radius in: Firefox (left/top), Chrome (right/bottom). (PNG)

When using the Offset paths method, an inner path is always created. As the direction of this path is the same regardless of the stroke width, one cannot differentiate between the case where the stroke width is less than one-half the radius and the case where it is not. This can be seen in the animation below:
Two circular paths with strokes of different widths. The drawing of the stroke is animated.

Stroking the path. The arrows indicate the direction of the offset paths. If the drawing is not animated, view the image by itself. (SVG)

Interestingly, some renderers draw a filled circle when one-half the ‘stroke-width’ is greater than the radius for an SVG <circle> (i.e. not a circular <path>) while others still draw a doughnut. However, for the SVG <rect> element, the rectangles are always drawn filled if the ‘stroke-width’ is greater than either the ‘width’ or ‘height’ (at least in the renderers I tested). So what does the SVG specification say about how to stroke a path? Nothing…! One can look to PostScript and PDF on which SVG is partially based for a hint on what it should say. The PostScript and PDF specifications say the same thing. From the PDF 1.7 reference:

The S operator paints a line along the current path. The stroked line follows each straight or curved segment in the path, centered on the segment with sides parallel to it. Each of the path’s subpaths is treated separately…

This seems to indicate that the sweeping the line technique is what is expected and indeed, Adobe’s own product, Adobe Reader, appears to do just that.

Stroke Alignment

Designers often want more control over how a stroke is positioned: only on the inside, only on the outside, or some arbitrary ratio of the two. The new SVG ‘stroke-alignment‘ property offers this control. For a closed path, it is relatively easy to figure out how this property should behave:
A figure eight path showing various methods for offsetting.

Top: the original path. Middle: left: stroke inside; right: stroke outside. Bottom: left: stroke to left; right: stroke to right.

For an open path, it is not quite so easy. What is inside, what is outside? One can define the terms by looking at what is filled: inside is in the fill, outside is not in the fill. With this definition, a single straight line segment would render nothing for an ‘inside’ stroke and a stroke on both sides for an ‘outside’ stroke. The SVG specification has a slightly different definition for ‘outside’ (see figure). For an open path it may make more sense to talk about left/right rather than inside/outside.
A figure eight path showing various methods for offsetting.

Top to bottom: Default stroke. Fill area (in gray). Inside (according to SVG specification?). Outside (implemented here by masking). Inside (another interpretation). Outside (according to SVG specification?). Stroke on left (round end cap in pink).

Handling line joins is fairly straight forward. End caps, at least ’round’ ones, are another matter. Does one draw half an end cap? Or does the radius of the end cap match the width of the (shifted) stroke?
Left: straight lines, right: curved lines.

Round end caps. Top to bottom: Default stroke. Stroke alignment ‘outside’, end-cap radius doubled. Stroke alignment ‘outside’, end-cap radius same as normal.

The ‘stroke-alignment’ property was recently removed from the SVG 2 specification draft and moved into a separate SVG Strokes module, partly due to the difficulty in specifying exactly how it should behave. The ‘stroke-alignment’ ‘inside’/’outside’ values can be simulated via other methods. The new ‘paint-order‘ property allows one to paint the stroke before the fill and thus simulating stroking only the outside of the path (this only works for opaque fill). A mask can also be used to simulate stroking the outside of path. A clip path can be used to simulate stroking the inside of a path.

Offset Paths

We’ve seen that offsetting a path can be used for constructing strokes. What about offsetting a path for the purpose of creating a new path? This is quite useful in mapping. For example you might want to show multiple bus routes going along a road with different offsets for each route. More stylistically, you could produce the shadowing seen around land masses in older, hand-drawn maps.
Section of map showing lines ringing a group of islands.

An excerpt from a submarine cable map showing the use of offset paths to shade around land masses. Note also the use of inside strokes to define country boundaries.

Offsetting paths is in practice extremely tricky! Here are a few of the problems:
  1. Offsets of Bezier segments are not Beziers; in fact they are 10th-order polynomials. In practice, one can do a pretty good job of estimating the offset by breaking up a Bezier path into smaller segments.
  2. Offset paths can have loops at cusps.
  3. Offset paths may require breaking apart left and right offset paths and recombining to form outset and inset paths. It can be difficult to get this right.
Entire scientific papers are written on this topic.[1] Here is a simple example path with offsets both inside and outside:
Path with a series of offsets.

Left: insets, right: outsets. Red path is original.

In this case, the outsets correspond to the outer edge of a stroked path with appropriate width when the ‘stroke-linejoin’ type is ’round’. The insets correspond to the inner edge of such strokes. Taking a closer look at the offset paths shows a number of cusp loops:
Complex path with offsets.

The same original path as in the above figure. Left: the light blue region is created by stroking the original path. As can be seen it matches the corresponding outset (blue) and inset (green) paths. Right: The raw offset paths used to construct the visible outset and inset paths. In this case, the outset path is constructed from the raw outset path (blue) and the inset path is constructed from the raw inset path (green). Cusp loops and overlaps have been removed.

Determining what is outset or inset becomes more difficult as a path loops back on itself. Both the outset and inset paths can consist of parts of both the right-offset and left-offset paths as shown below:
A path that loops back on itself three times.

Left: The left-offset path (blue) and the right-offset path (green), relative to the path’s direction (clock-wise). Right: The resulting outset path (blue) and inset path (green).

Here’s an example where Inkscape’s Linked Offset function gets it wrong:
A circular path segment on top of a figure eight segment.

The resulting outset path (blue) and inset path (green) as found by Inkscape’s Linked Offset function.

The previous examples assumed that the line joins for outside joins are rounded. It would be desirable to be able to specify the type of join to use. This can maintain the feel of the original path.
A triangle path with 's' shaped sides with various offsets.

Left: Outset path with three different types of joins: ‘bevel’, ’round’, and ‘arcs’. Right: Outset paths with various offsets and with the ‘arcs’ line join. Note: the ‘arcs’ line join fails for the outer most path as the generated arcs do not intersect; this results in falling back to a ‘miter’ line join.

Allowing more freedom to define stroke position and being able to offset strokes are highly desirable features for designers, but as this post shows, they are not so simple to implement. Before we can add such features to SVG, we need to define robust algorithms for generating proper offset paths.

References

  1. An offset algorithm for polyline curves Xu-Zheng Liu, Jun-Hai Yong, Guo-Qin Zheng, Jia-Guang Sun.
An image for the sole purpose of having a good PNG image to show in Google+ which doesn’t support SVG images, bad Google+. Complex path with offsets.

SVG Working Group Meeting Report — Sydney

The SVG Working Group had a four day face-to-face meeting in Sydney this month. The first day was a joint meeting with the CSS Working Group. I would like to thank the Inkscape board for allocating the funds for this trip as well as all the Inkscape donors that made it possible. This was an expensive trip as I was traveling from Paris and Sydney is an expensive city… but I think it was well worth it as the SVG WG (and CSS WG, where appropriate) approved all of my proposals and worked through all of the issues I raised. Unfortunately, due to the high cost of this trip, I have exhausted the budgeted funding from Inkscape for SVG WG travel this year and will probably miss the two other planned meetings, one in Sweden in June and one in Japan in October. We target the Sweden meeting for moving the SVG 2 specification from Working Draft to Candidate Recommendation so it would be especially good to be there. If anyone has ideas for alternative funding, please let me know. Highlights: A summary of selected topics, grouped by day, follows:

Joint CSS and SVG Meeting

Minutes
  • SVG sizing in HTML.

    We spent some time discussing how SVG should be sized in HTML. For corner cases, the browsers disagree on how large an SVG should be displayed. There is going to be a lot work required to get this nailed down.

  • CSS Filter Effects:

    We spent a lot of time going through and resolving the remaining issues in the CSS Filter Effects specification. (This is basically SVG 1.1 filters repackaged for use by HTML with some extra syntax sugar coating.) We then agreed to publish the specification as a Candidate Recommendation.

  • CSS Blending:

    We discussed publishing the CSS Blending specification as a Recommendation, the final step in creating a specification. I raised a point that most of the tests assumed HTML content. It was requested that more SVG specific test be created. (Part of the requirement for Recommendation status is that there be a test suite and that two independently developed renderers pass each test in the suite.)

  • SVG in OpenType, Color Palettes:

    The new OpenType specification allows for multi-colored SVG glyphs. It would be nice to set those colors through CSS. We discussed several methods for doing so and decided on one method. It will be added to the CSS Fonts Level 4 specification.

  • Text Rendering:

    The ‘text-rendering‘ property gives renderers a hint on what speed/precision trade-offs should be made. It was pointed out that the layout of text flowed into a box will change as one zooms in and out on a page in Firefox due to font-hinting, font-size rounding, etc. The Google docs people would like to prevent this. It was decided that the ‘geometricPrecision’ value should require that font-metrics and text-measurement be independent of device resolution and zoom level. (Note: this property is defined in SVG but both Firefox and Chrome support it on HTML content.)

  • Text Properties:

    Text in SVG 2 relies heavily on CSS specifications that are in various states of readiness. I asked the CSS/SVG groups what is the policy for referencing these specs. In particular, SVG 2 needs to reference the CSS Shapes Level 2 specification in order to implement text wrapping inside of SVG shapes. The CSS WG agreed to publish CSS Shapes Level 2 as a Working Draft so we can reference it. We also discussed various technical issues in defining how text wraps around excluded areas and in flowing text into more than one shape.

SVG Day 1

Minutes
  • CamelCase Names

    The SVG WG decided some time ago to avoid new CamelCase names like ‘LinearGradient’ which cause problems with integration in HTML (HTML is case insensitive and CamelCase SVG names must be added by hand to HTML parsers). We went through the list of new CamelCase names in SVG 2 and decided which ones could be changed, weighing arguments for consistency against the desire to not introduce new CamelCase names. It was decided that <meshGradient> should be changed to <mesh>. This was mostly motivated by the ability to use a mesh as a standalone entity (and not only as a paint server). Other changes include: <hatchPath> to <hatchpath>, <solidColor> to <solidcolor>, …

  • Requiring <foreignObject> HTML to be rendered.

    There was a proposal to require any HTML content in a <foreignObject> element to be rendered. I pointed out that not all SVG renderers are HTML renderers (Inkscape as an example). It was decided to have separate conformance classes, one requiring HTML content to be rendered and one not.

  • Requiring Style Sheets Support:

    It was decided to require style sheet support. We discussed what kind of style sheets to require. We decided to require basic style sheet support at the CSS 1 or CSS 2.1 level (that part of the discussion was not minuted).

  • Open Issues:

    We spent considerable time going through the specification chapter by chapter looking at open issues that would block publishing the specification as a Candidate Recommendation. This was a long multi-day process.

SVG Day 2

Minutes

Note: Day 2 and Day 3 minutes are merged.

  • Superpaths:

    Superpaths is the name for the ability to reuse path segment data. This is useful, for example, to define the boundary between two shapes just once, reusing the path segment for both shapes. SVG renderers might be able to exploit this information to provide better anti-aliasing between two shapes knowing they share a common border. The SVG WG endorses this proposal but it probably won’t be ready in time for SVG 2. Instead, it will be developed in a separate Path enhancement module.

  • Line-Join: Miter Clipped:

    It was proposed on the SVG mailing list that there be a new behavior for the miter ‘line-join’ value in regards to the ‘miter-limit’ property. At the moment, if a miter produces a line cap that extends farther than the ‘miter-limit’ value then the miter type is changed to bevel. This causes abrupt jumps when the angle between the joined lines changes such that the miter length crosses over the ‘miter-limit’ value (see demo). A better solution is to clip the line join at the ‘miter-limit’. This is done by some rendering libraries including the one used on Windows. We decided to create a new value for ‘line-join’ with this behavior.

  • Auto-Path Closing:

    The ‘z’ path command closes paths by drawing a line segment to the first point in the path. This is fine if the path is made up of straight lines but becomes problematic if the path is made up of curves. For example, it can cause rendering problems for markers as there will be an extra line segment between the start and end of the path. If the last point is exactly on top of the first point, one can remove this closing line segment but this isn’t always possible, especially if one is using the relative path commands with rounding errors. A more detailed discussion can be found here. We decided to allow a ‘z’ command to fill in missing point data using the first point in the path. For example in: d=”m 100,125 c 0,-75 100,-75 100,0 c 0,75 -100,75 z” the missing point of the second Bezier curve is filled in by the first point in the path.

  • Text on a Shape:

    An Inkscape developer has been working on putting text on a shape by converting shapes to paths while storing the original shape in the <defs> section. It would be much easier if SVG just allowed text on a shape. I proposed that we include this in SVG 2. This is actually quite easy to specify as we have already defined how shapes are converted to paths (needed by markers on shapes and putting dash patterns on shapes). A couple minor points needed to be decided: Do we allow negative path offsets? (Yes) How do we decide which side of a path the text should be put? (A new attribute) The SVG WG approved adding text on a shape to SVG 2.

  • Marker knockouts, mid-markers, etc:

    A number of new marker features still need some work. To facilitate finishing SVG 2 we decided to move them to a separate specification. There is some hesitation to do so as there is fear that once removed from the main SVG specification they will be forgotten about. This will be a trial of how well separating parts of SVG 2 into separates specifications works. The marker knockout feature, very useful for arrowheads is one feature moved into the new specification. On day 3 we approved publishing the new Markers Level 1 specification as a First Public Working Draft.

  • Text properties:

    With our new reliance on CSS for text layout, just what CSS properties should SVG 2 support? We don’t want to necessarily list them all in the SVG 2 specification as the list could change as CSS adds new properties. We decided that we should support all paragraph level properties (‘text-indent’, ‘text-justification’, etc.). We’ll ask the CSS working group to create a definition for CSS paragraph properties that we can then reference.

  • Text ‘dx’, ‘dy’, and ‘rotate’ attributes:

    SVG 1.1 has the properties ‘dx’, ‘dy’, and ‘rotate’ attributes that allow individual glyphs to be shifted and rotated. While not difficult to support on auto-wrapped text (they would be applied after CSS text layout), we decided that they weren’t really needed. They can still be used on SVG 1.1 style text (which is still part of SVG 2).

SVG Day 3

Minutes

Note: Day 3 minutes are at end of Day 2 minutes.

  • Stroking Enhancements:

    As part of trying to push SVG 2 quickly, we decided to move some of the stroking enhancements that still need work into a separate specification. This includes better dashing algorithms (such as controlling dash position at intersections) and variable width strokes. We agreed to the publication of SVG Strokes as a First Public Working Draft.

  • Smoothing in Mesh Gradients:

    Coons-Patch mesh gradients have one problem: the color profile at the boundary between patches is not always smooth. This leads to visible artifacts which are enhanced by Mach Banding. I’ve discussed this in more detail here. I proposed to the SVG WG that we include the option of auto-smoothing meshes using monotonic-bicubic interpolation. (There is an experimental implementation in Inkscape trunk which I demonstrated to the group.) The SVG WG accepted my proposal.

  • Motion Path:

    SVG has the ability to animate a graphical object along a path. This ability is desired for HTML. The SVG and CSS working groups have produced a new specification, Motion Path Module Level 1, for this purpose. We agreed to publish the specification as a First Public Working Draft.