Text along a path (GNU Emacs)


SVG 2 specifications allows flowing text along a curve via textPath element. This opens up possibilities for cool text effects e.g. Formula Editor in GNU Emacs.

GNU Emacs uses librsvg for SVG rendering on GNU/Linux. This fix for librsvg was  available in 2014 [4]. But this wasn't applied for some reason. Librsvg has moved to Rust since then. The code below is pre-Rust version of librsvg.


<?xml version="1.0" encoding="UTF-8"?>
<svg height="300" width="800" xmlns="http://www.w3.org/2000/svg"
    <path id="my_path1" d="M 50 100 Q 25 10 180 100 T 350 100 T 520 100 T 690 100" fill="transparent" />
        <textPath xlink:href ="#my_path1" font-size="34"> Text along a path looks awesome!!


Text wrapping

Text wrapping using inline-size attribute (new implementation). This allows for pixel-precise fonts. In other words, instead of restrictions based on points (=1/72 of an inch) for font-size, one can specify pixel-size. How do you verify? In this case (monospace font), it's simple: 200px / 25px = 8 chars !


<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg"
     width="100" height="300">

  <path d="M 0,25 H 120 M 0,225 H 120 M 62.5,25 V 2" stroke="red"/>
  <text x="62.5" y="25" inline-size="200"
    style="font: 25px IPAMincho; inline-size: 200px; writing-mode: vertical-rl;">



Text inside a shape

Text layout inside a shape using shape-inside attribute (new implementation). The algorithm ensures that even with a superscript the padding is maintained.

The text is justified, i.e., the whitespace due to wrapping is distributed across the line. This example probably looks better with a center aligned text. However, we are simply reusing the SVG example from the specifications.

Justified text
Center aligned text


<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg"
     width="300" height="300" viewBox="0 0 300 300">
  <circle id="circle" cx="150" cy="150" r="125" fill="none" stroke="black"/>
  <text style="shape-inside: url(#circle);
           shape-padding: 25px;
           font: 18px DejaVu;
           text-align: justified;
           line-height: 110%;">This is a
  sample of wrapped text in SVG 2! There should
  be 25 pixel padding around the text. The text is
  justified on both sides. It looks good!</text>


The algorithm works by finding the intersection of current line with the curve while tracing the curve from the start point to the end point. An up-crossing followed by a down-crossing constitutes a segment. Looking at the image, you'll see that this reverses in the lower half. Hence it's important that the curve definition is continuous.

It's important to note that librsvg only uses point, line and curve primitives from Cairo for drawing shapes. Hence a rectangle is a combination of four line segments while a circle is a combination of four arcs. For the above algorithm to work, it is imperative that the segments follow one another.

For padding a regular shape, simple scaling works well. i.e. scale the shape to a smaller size which accommodates the padding and then fill that shape with text. However, this approach doesn't work for irregular shapes.

Text in a shape - Broken lines

Extending the algorithm in the previous section, text can also be fitted into shapes where it splits the line into separate sections (upto 4 for an M shape). The only requirement is that the shape be defined as a continuous curve.



<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg"
     width="400" height="300" viewBox="0 0 400 300">

  <path id="pw" fill="none" stroke="black"
    d="M50,50 h100 L200,150 L250,50 h100
       L250,250 h-100 Z"/>

  <text style="shape-inside: url(#pw);
           shape-padding: 20px;
           font: 12px DejaVu;
           text-align: justified;
           line-height: 100%;">This is a
  sample of wrapped text in SVG 2! There should
  be 25 pixel padding around the text. The text is
  justified on both sides. It looks good!</text>


CSS Parsing

libcroco is the CSS parsing engine behind the C version of librsvg. Some useful code snippets for the same.

    char *str = "body { font: 20px \"Noto Naskh Arabic\", serif; inline-size: 200px; direction: rtl;}";

    CRStatement *stmt = cr_statement_parse_from_buf (str, CR_UTF_8);
    // CRSelector *selectors;
    CRDeclaration *declarations;
    cr_statement_ruleset_get_declarations (stmt, &declarations);
    printf("\n%d %s", cr_declaration_nr_props (declarations),
           cr_declaration_to_string (declarations, 2));
    for (int i = 0; i < cr_declaration_nr_props (declarations); i += 1) {
        CRDeclaration *decl = cr_declaration_get_from_list (declarations, i);
        const char *property = cr_string_peek_raw_str (decl->property);
        const char *value = cr_term_to_string (decl->value);
        printf ("%s: %s important: %d\n", property, value, decl->important);







Popular posts from this blog

GNU Emacs as a Comic Book Reader

Data Visualization with GNU Emacs

Mozilla Readability in GNU Emacs