]> granicus.if.org Git - imagemagick/blobdiff - coders/svg.c
...
[imagemagick] / coders / svg.c
index 6bf786ce9cac8f2603065a23b14955cc968260c8..60a55c32a25776fc0f5f51adc37b032253ae4633 100644 (file)
@@ -24,7 +24,7 @@
 %  You may not use this file except in compliance with the License.  You may  %
 %  obtain a copy of the License at                                            %
 %                                                                             %
-%    https://www.imagemagick.org/script/license.php                           %
+%    https://imagemagick.org/script/license.php                               %
 %                                                                             %
 %  Unless required by applicable law or agreed to in writing, software        %
 %  distributed under the License is distributed on an "AS IS" BASIS,          %
 #endif
 #endif
 \f
+/*
+  Define declarations.
+*/
+#define DefaultSVGDensity  96.0
+\f
 /*
   Typedef declarations.
 */
@@ -224,7 +229,11 @@ static MagickBooleanType IsSVG(const unsigned char *magick,const size_t length)
 {
   if (length < 4)
     return(MagickFalse);
-  if (LocaleNCompare((const char *) magick,"?xml",4) == 0)
+  if (LocaleNCompare((const char *) magick+1,"svg",3) == 0)
+    return(MagickTrue);
+  if (length < 5)
+    return(MagickFalse);
+  if (LocaleNCompare((const char *) magick+1,"?xml",4) == 0)
     return(MagickTrue);
   return(MagickFalse);
 }
@@ -328,19 +337,19 @@ static double GetUserSpaceCoordinateValue(const SVGInfo *svg_info,int type,
     }
   GetNextToken(p,&p,MagickPathExtent,token);
   if (LocaleNCompare(token,"cm",2) == 0)
-    return(96.0*svg_info->scale[0]/2.54*value);
+    return(DefaultSVGDensity*svg_info->scale[0]/2.54*value);
   if (LocaleNCompare(token,"em",2) == 0)
     return(svg_info->pointsize*value);
   if (LocaleNCompare(token,"ex",2) == 0)
     return(svg_info->pointsize*value/2.0);
   if (LocaleNCompare(token,"in",2) == 0)
-    return(96.0*svg_info->scale[0]*value);
+    return(DefaultSVGDensity*svg_info->scale[0]*value);
   if (LocaleNCompare(token,"mm",2) == 0)
-    return(96.0*svg_info->scale[0]/25.4*value);
+    return(DefaultSVGDensity*svg_info->scale[0]/25.4*value);
   if (LocaleNCompare(token,"pc",2) == 0)
-    return(96.0*svg_info->scale[0]/6.0*value);
+    return(DefaultSVGDensity*svg_info->scale[0]/6.0*value);
   if (LocaleNCompare(token,"pt",2) == 0)
-    return(1.25*svg_info->scale[0]*value);
+    return(svg_info->scale[0]*value);
   if (LocaleNCompare(token,"px",2) == 0)
     return(value);
   return(value);
@@ -549,6 +558,66 @@ static void SVGElementDeclaration(void *context,const xmlChar *name,int type,
         name,(xmlElementTypeVal) type,content);
 }
 
+static void SVGStripString(const MagickBooleanType trim,char *message)
+{
+  register char
+    *p,
+    *q;
+
+  size_t
+    length;
+
+  assert(message != (char *) NULL);
+  if (*message == '\0')
+    return;
+  /*
+    Remove comment.
+  */
+  q=message;
+  for (p=message; *p != '\0'; p++)
+  {
+    if ((*p == '/') && (*(p+1) == '*'))
+      {
+        for ( ; *p != '\0'; p++)
+          if ((*p == '*') && (*(p+1) == '/'))
+            {
+              p+=2;
+              break;
+            }
+        if (*p == '\0')
+          break;
+      }
+    *q++=(*p);
+  }
+  *q='\0';
+  if (trim != MagickFalse)
+    {
+      /*
+        Remove whitespace.
+      */
+      length=strlen(message);
+      p=message;
+      while (isspace((int) ((unsigned char) *p)) != 0)
+        p++;
+      if ((*p == '\'') || (*p == '"'))
+        p++;
+      q=message+length-1;
+      while ((isspace((int) ((unsigned char) *q)) != 0) && (q > p))
+        q--;
+      if (q > p)
+        if ((*q == '\'') || (*q == '"'))
+          q--;
+      (void) memmove(message,p,(size_t) (q-p+1));
+      message[q-p+1]='\0';
+    }
+  /*
+    Convert newlines to a space.
+  */
+  for (p=message; *p != '\0'; p++)
+    if (*p == '\n')
+      *p=' ';
+}
+
 static char **SVGKeyValuePairs(void *context,const int key_sentinel,
   const int value_sentinel,const char *text,size_t *number_tokens)
 {
@@ -602,13 +671,13 @@ static char **SVGKeyValuePairs(void *context,const int key_sentinel,
       }
     tokens[i]=AcquireString(p);
     (void) CopyMagickString(tokens[i],p,(size_t) (q-p+1));
-    StripString(tokens[i]);
+    SVGStripString(MagickTrue,tokens[i]);
     i++;
     p=q+1;
   }
   tokens[i]=AcquireString(p);
   (void) CopyMagickString(tokens[i],p,(size_t) (q-p+1));
-  StripString(tokens[i++]);
+  SVGStripString(MagickTrue,tokens[i++]);
   tokens[i]=(char *) NULL;
   *number_tokens=(size_t) i;
   return(tokens);
@@ -678,6 +747,8 @@ static void SVGProcessStyleElement(void *context,const xmlChar *name,
     (void) FormatLocaleFile(svg_info->file,"font-size %g\n",
       svg_info->pointsize);
   }
+  color=AcquireString("none");
+  units=AcquireString("userSpaceOnUse");
   for (i=0; i < (ssize_t) (number_tokens-1); i+=2)
   {
     keyword=(char *) tokens[i];
@@ -756,6 +827,26 @@ static void SVGProcessStyleElement(void *context,const xmlChar *name,
               value);
             break;
           }
+        if (LocaleCompare(keyword,"font") == 0)
+          {
+            char
+              family[MagickPathExtent],
+              size[MagickPathExtent],
+              style[MagickPathExtent];
+
+            if (sscanf(value,"%2048s %2048s %2048s",style,size,family) != 3)
+              break;
+            if (GetUserSpaceCoordinateValue(svg_info,0,style) == 0)
+              (void) FormatLocaleFile(svg_info->file,"font-style \"%s\"\n",
+                style);
+            else
+              if (sscanf(value,"%2048s %2048s",size,family) != 2)
+                break;
+            (void) FormatLocaleFile(svg_info->file,"font-size \"%s\"\n",size);
+            (void) FormatLocaleFile(svg_info->file,"font-family \"%s\"\n",
+              family);
+            break;
+          }
         if (LocaleCompare(keyword,"font-family") == 0)
           {
             (void) FormatLocaleFile(svg_info->file,"font-family \"%s\"\n",
@@ -788,6 +879,37 @@ static void SVGProcessStyleElement(void *context,const xmlChar *name,
           }
         break;
       }
+      case 'K':
+      case 'k':
+      {
+        if (LocaleCompare(keyword,"kerning") == 0)
+          {
+            (void) FormatLocaleFile(svg_info->file,"kerning \"%s\"\n",value);
+            break;
+          }
+        break;
+      }
+      case 'L':
+      case 'l':
+      {
+        if (LocaleCompare(keyword,"letter-spacing") == 0)
+          {
+            (void) FormatLocaleFile(svg_info->file,"letter-spacing \"%s\"\n",
+              value);
+            break;
+          }
+        break;
+      }
+      case 'M':
+      case 'm':
+      {
+        if (LocaleCompare(keyword,"mask") == 0)
+          {
+            (void) FormatLocaleFile(svg_info->file,"mask \"%s\"\n",value);
+            break;
+          }
+        break;
+      }
       case 'O':
       case 'o':
       {
@@ -912,6 +1034,10 @@ static void SVGProcessStyleElement(void *context,const xmlChar *name,
         break;
     }
   }
+  if (units != (char *) NULL)
+    units=DestroyString(units);
+  if (color != (char *) NULL)
+    color=DestroyString(color);
   for (i=0; tokens[i] != (char *) NULL; i++)
     tokens[i]=DestroyString(tokens[i]);
   tokens=(char **) RelinquishMagickMemory(tokens);
@@ -1390,6 +1516,11 @@ static void SVGStartElement(void *context,const xmlChar *name,
       if (LocaleCompare((const char *) name,"text") == 0)
         {
           PushGraphicContext(id);
+          (void) FormatLocaleFile(svg_info->file,"class \"text\"\n");
+          (void) FormatLocaleFile(svg_info->file,"translate %g,%g\n",
+            svg_info->bounds.x,svg_info->bounds.y);
+          svg_info->center.x=svg_info->bounds.x;
+          svg_info->center.y=svg_info->bounds.y;
           svg_info->bounds.x=0.0;
           svg_info->bounds.y=0.0;
           svg_info->bounds.width=0.0;
@@ -1400,12 +1531,6 @@ static void SVGStartElement(void *context,const xmlChar *name,
         {
           if (*svg_info->text != '\0')
             {
-              DrawInfo
-                *draw_info;
-
-              TypeMetric
-                metrics;
-
               char
                 *text;
 
@@ -1414,14 +1539,6 @@ static void SVGStartElement(void *context,const xmlChar *name,
                 svg_info->bounds.x-svg_info->center.x,svg_info->bounds.y-
                 svg_info->center.y,text);
               text=DestroyString(text);
-              draw_info=CloneDrawInfo(svg_info->image_info,(DrawInfo *) NULL);
-              draw_info->pointsize=svg_info->pointsize;
-              draw_info->text=AcquireString(svg_info->text);
-              (void) ConcatenateString(&draw_info->text," ");
-              (void) GetTypeMetrics(svg_info->image,draw_info,
-                &metrics,svg_info->exception);
-              svg_info->bounds.x+=metrics.width;
-              draw_info=DestroyDrawInfo(draw_info);
               *svg_info->text='\0';
             }
           PushGraphicContext(id);
@@ -1532,13 +1649,24 @@ static void SVGStartElement(void *context,const xmlChar *name,
             }
           if (LocaleCompare(keyword,"dx") == 0)
             {
-              svg_info->bounds.x+=GetUserSpaceCoordinateValue(svg_info,1,value);
+              double
+                dx;
+
+              dx=GetUserSpaceCoordinateValue(svg_info,1,value);
+              svg_info->bounds.x+=dx;
+              if (LocaleCompare((char *) name,"text") == 0)
+                (void) FormatLocaleFile(svg_info->file,"translate %g,0.0\n",dx);
               break;
             }
           if (LocaleCompare(keyword,"dy") == 0)
             {
-              svg_info->bounds.y+=
-                GetUserSpaceCoordinateValue(svg_info,-1,value);
+              double
+                dy;
+
+              dy=GetUserSpaceCoordinateValue(svg_info,-1,value);
+              svg_info->bounds.y+=dy;
+              if (LocaleCompare((char *) name,"text") == 0)
+                (void) FormatLocaleFile(svg_info->file,"translate 0.0,%g\n",dy);
               break;
             }
           break;
@@ -1799,6 +1927,28 @@ static void SVGStartElement(void *context,const xmlChar *name,
             }
           break;
         }
+        case 'K':
+        case 'k':
+        {
+          if (LocaleCompare(keyword,"kerning") == 0)
+            {
+              (void) FormatLocaleFile(svg_info->file,"kerning \"%s\"\n",
+                value);
+              break;
+            }
+          break;
+        }
+        case 'L':
+        case 'l':
+        {
+          if (LocaleCompare(keyword,"letter-spacing") == 0)
+            {
+              (void) FormatLocaleFile(svg_info->file,"letter-spacing \"%s\"\n",
+                value);
+              break;
+            }
+          break;
+        }
         case 'M':
         case 'm':
         {
@@ -2070,6 +2220,10 @@ static void SVGStartElement(void *context,const xmlChar *name,
                         p=(const char *) value;
                         GetNextToken(p,&p,MagickPathExtent,token);
                         angle=StringToDouble(value,(char **) NULL);
+                        affine.sx=cos(DegreesToRadians(fmod(angle,360.0)));
+                        affine.rx=sin(DegreesToRadians(fmod(angle,360.0)));
+                        affine.ry=(-sin(DegreesToRadians(fmod(angle,360.0))));
+                        affine.sy=cos(DegreesToRadians(fmod(angle,360.0)));
                         GetNextToken(p,&p,MagickPathExtent,token);
                         if (*token == ',')
                           GetNextToken(p,&p,MagickPathExtent,token);
@@ -2078,14 +2232,14 @@ static void SVGStartElement(void *context,const xmlChar *name,
                         if (*token == ',')
                           GetNextToken(p,&p,MagickPathExtent,token);
                         y=StringToDouble(token,&next_token);
-                        affine.sx=cos(DegreesToRadians(fmod(angle,360.0)));
-                        affine.rx=sin(DegreesToRadians(fmod(angle,360.0)));
-                        affine.ry=(-sin(DegreesToRadians(fmod(angle,360.0))));
-                        affine.sy=cos(DegreesToRadians(fmod(angle,360.0)));
-                        affine.tx=0.0;
-                        affine.ty=0.0;
-                        svg_info->center.x=x;
-                        svg_info->center.y=y;
+                        affine.tx=svg_info->bounds.x+x*
+                          cos(DegreesToRadians(fmod(angle,360.0)))+y*
+                          sin(DegreesToRadians(fmod(angle,360.0)));
+                        affine.ty=svg_info->bounds.y-x*
+                          sin(DegreesToRadians(fmod(angle,360.0)))+y*
+                          cos(DegreesToRadians(fmod(angle,360.0)));
+                        affine.tx-=x/2.0;
+                        affine.ty-=y/2.0;
                         break;
                       }
                     break;
@@ -2137,7 +2291,7 @@ static void SVGStartElement(void *context,const xmlChar *name,
                               (*p == ','))
                             break;
                         affine.tx=GetUserSpaceCoordinateValue(svg_info,1,value);
-                        affine.ty=affine.tx;
+                        affine.ty=0;
                         if (*p != '\0')
                           affine.ty=GetUserSpaceCoordinateValue(svg_info,-1,
                             p+1);
@@ -2310,7 +2464,8 @@ static void SVGStartElement(void *context,const xmlChar *name,
         }
     }
   (void) LogMagickEvent(CoderEvent,GetMagickModule(),"  )");
-  units=DestroyString(units);
+  if (units != (char *) NULL)
+    units=DestroyString(units);
   if (color != (char *) NULL)
     color=DestroyString(color);
 }
@@ -2341,6 +2496,7 @@ static void SVGEndElement(void *context,const xmlChar *name)
     {
       if (LocaleCompare((const char *) name,"circle") == 0)
         {
+          (void) FormatLocaleFile(svg_info->file,"class \"circle\"\n");
           (void) FormatLocaleFile(svg_info->file,"circle %g,%g %g,%g\n",
             svg_info->element.cx,svg_info->element.cy,svg_info->element.cx,
             svg_info->element.cy+svg_info->element.minor);
@@ -2390,6 +2546,7 @@ static void SVGEndElement(void *context,const xmlChar *name)
           double
             angle;
 
+          (void) FormatLocaleFile(svg_info->file,"class \"ellipse\"\n");
           angle=svg_info->element.angle;
           (void) FormatLocaleFile(svg_info->file,"ellipse %g,%g %g,%g 0,360\n",
             svg_info->element.cx,svg_info->element.cy,
@@ -2439,6 +2596,7 @@ static void SVGEndElement(void *context,const xmlChar *name)
     {
       if (LocaleCompare((const char *) name,"line") == 0)
         {
+          (void) FormatLocaleFile(svg_info->file,"class \"line\"\n");
           (void) FormatLocaleFile(svg_info->file,"line %g,%g %g,%g\n",
             svg_info->segment.x1,svg_info->segment.y1,svg_info->segment.x2,
             svg_info->segment.y2);
@@ -2472,6 +2630,7 @@ static void SVGEndElement(void *context,const xmlChar *name)
         }
       if (LocaleCompare((const char *) name,"path") == 0)
         {
+          (void) FormatLocaleFile(svg_info->file,"class \"path\"\n");
           (void) FormatLocaleFile(svg_info->file,"path \"%s\"\n",
             svg_info->vertices);
           (void) FormatLocaleFile(svg_info->file,"pop graphic-context\n");
@@ -2479,6 +2638,7 @@ static void SVGEndElement(void *context,const xmlChar *name)
         }
       if (LocaleCompare((const char *) name,"polygon") == 0)
         {
+          (void) FormatLocaleFile(svg_info->file,"class \"polygon\"\n");
           (void) FormatLocaleFile(svg_info->file,"polygon %s\n",
             svg_info->vertices);
           (void) FormatLocaleFile(svg_info->file,"pop graphic-context\n");
@@ -2486,6 +2646,7 @@ static void SVGEndElement(void *context,const xmlChar *name)
         }
       if (LocaleCompare((const char *) name,"polyline") == 0)
         {
+          (void) FormatLocaleFile(svg_info->file,"class \"polyline\"\n");
           (void) FormatLocaleFile(svg_info->file,"polyline %s\n",
             svg_info->vertices);
           (void) FormatLocaleFile(svg_info->file,"pop graphic-context\n");
@@ -2505,10 +2666,16 @@ static void SVGEndElement(void *context,const xmlChar *name)
         {
           if ((svg_info->radius.x == 0.0) && (svg_info->radius.y == 0.0))
             {
-              (void) FormatLocaleFile(svg_info->file,"rectangle %g,%g %g,%g\n",
-                svg_info->bounds.x,svg_info->bounds.y,
-                svg_info->bounds.x+svg_info->bounds.width,
-                svg_info->bounds.y+svg_info->bounds.height);
+              (void) FormatLocaleFile(svg_info->file,"class \"rect\"\n");
+              if ((fabs(svg_info->bounds.width-1.0) < MagickEpsilon) &&
+                  (fabs(svg_info->bounds.height-1.0) < MagickEpsilon))
+                (void) FormatLocaleFile(svg_info->file,"point %g,%g\n",
+                  svg_info->bounds.x,svg_info->bounds.y);
+              else
+                (void) FormatLocaleFile(svg_info->file,
+                  "rectangle %g,%g %g,%g\n",svg_info->bounds.x,
+                  svg_info->bounds.y,svg_info->bounds.x+svg_info->bounds.width,
+                  svg_info->bounds.y+svg_info->bounds.height);
               (void) FormatLocaleFile(svg_info->file,"pop graphic-context\n");
               break;
             }
@@ -2562,7 +2729,7 @@ static void SVGEndElement(void *context,const xmlChar *name)
             keyword=(char *) tokens[j];
             value=(char *) tokens[j+1];
             (void) FormatLocaleFile(svg_info->file,"push class \"%s\"\n",
-              keyword);
+              *keyword == '.' ? keyword+1 : keyword);
             SVGProcessStyleElement(context,name,value);
             (void) FormatLocaleFile(svg_info->file,"pop class\n");
           }
@@ -2591,11 +2758,13 @@ static void SVGEndElement(void *context,const xmlChar *name)
               char
                 *text;
 
+              SVGStripString(MagickTrue,svg_info->text);
               text=EscapeString(svg_info->text,'\'');
-              (void) FormatLocaleFile(svg_info->file,"text %g,%g \"%s\"\n",
-                svg_info->bounds.x,svg_info->bounds.y,text);
+              (void) FormatLocaleFile(svg_info->file,"text 0,0 \"%s\"\n",text);
               text=DestroyString(text);
               *svg_info->text='\0';
+              svg_info->center.x=0.0;
+              svg_info->center.y=0.0;
             }
           (void) FormatLocaleFile(svg_info->file,"pop graphic-context\n");
           break;
@@ -2604,27 +2773,15 @@ static void SVGEndElement(void *context,const xmlChar *name)
         {
           if (*svg_info->text != '\0')
             {
-              DrawInfo
-                *draw_info;
-
-              TypeMetric
-                metrics;
-
               char
                 *text;
 
+              (void) FormatLocaleFile(svg_info->file,"class \"tspan\"\n");
               text=EscapeString(svg_info->text,'\'');
               (void) FormatLocaleFile(svg_info->file,"text %g,%g \"%s\"\n",
-                svg_info->bounds.x,svg_info->bounds.y,text);
+                svg_info->bounds.x-svg_info->center.x,svg_info->bounds.y-
+                svg_info->center.y,text);
               text=DestroyString(text);
-              draw_info=CloneDrawInfo(svg_info->image_info,(DrawInfo *) NULL);
-              draw_info->pointsize=svg_info->pointsize;
-              draw_info->text=AcquireString(svg_info->text);
-              (void) ConcatenateString(&draw_info->text," ");
-              (void) GetTypeMetrics(svg_info->image,draw_info,&metrics,
-                svg_info->exception);
-              svg_info->bounds.x+=metrics.width;
-              draw_info=DestroyDrawInfo(draw_info);
               *svg_info->text='\0';
             }
           (void) FormatLocaleFile(svg_info->file,"pop graphic-context\n");
@@ -2691,7 +2848,7 @@ static void SVGCharacters(void *context,const xmlChar *c,int length)
   for (i=0; i < (ssize_t) length; i++)
     *p++=c[i];
   *p='\0';
-  StripString(text);
+  SVGStripString(MagickFalse,text);
   if (svg_info->text == (char *) NULL)
     svg_info->text=text;
   else
@@ -2771,6 +2928,9 @@ static void SVGComment(void *context,const xmlChar *value)
   (void) ConcatenateString(&svg_info->comment,(const char *) value);
 }
 
+static void SVGWarning(void *,const char *,...)
+  magick_attribute((__format__ (__printf__,2,3)));
+
 static void SVGWarning(void *context,const char *format,...)
 {
   char
@@ -2803,6 +2963,9 @@ static void SVGWarning(void *context,const char *format,...)
   va_end(operands);
 }
 
+static void SVGError(void *,const char *,...)
+  magick_attribute((__format__ (__printf__,2,3)));
+
 static void SVGError(void *context,const char *format,...)
 {
   char
@@ -2938,7 +3101,6 @@ static void SVGExternalSubset(void *context,const xmlChar *name,
 static char
   SVGDensityGeometry[] = "96.0x96.0";
 
-
 static Image *ReadSVGImage(const ImageInfo *image_info,ExceptionInfo *exception)
 {
   char
@@ -3177,8 +3339,10 @@ static Image *ReadSVGImage(const ImageInfo *image_info,ExceptionInfo *exception)
               (ssize_t *) NULL,&image->columns,&image->rows);
             if ((image->columns != 0) || (image->rows != 0))
               {
-                image->resolution.x=96.0*image->columns/dimension_info.width;
-                image->resolution.y=96.0*image->rows/dimension_info.height;
+                image->resolution.x=DefaultSVGDensity*image->columns/
+                  dimension_info.width;
+                image->resolution.y=DefaultSVGDensity*image->rows/
+                  dimension_info.height;
                 if (fabs(image->resolution.x) < MagickEpsilon)
                   image->resolution.x=image->resolution.y;
                 else
@@ -3192,8 +3356,10 @@ static Image *ReadSVGImage(const ImageInfo *image_info,ExceptionInfo *exception)
           }
         if (apply_density != MagickFalse)
           {
-            image->columns=image->resolution.x*dimension_info.width/96.0;
-            image->rows=image->resolution.y*dimension_info.height/96.0;
+            image->columns=image->resolution.x*dimension_info.width/
+              DefaultSVGDensity;
+            image->rows=image->resolution.y*dimension_info.height/
+              DefaultSVGDensity;
           }
         else
           {
@@ -3208,22 +3374,24 @@ static Image *ReadSVGImage(const ImageInfo *image_info,ExceptionInfo *exception)
         image->rows=gdk_pixbuf_get_height(pixel_buffer);
 #endif
         image->alpha_trait=BlendPixelTrait;
-        status=SetImageExtent(image,image->columns,image->rows,exception);
-        if (status == MagickFalse)
-          {
-#if !defined(MAGICKCORE_CAIRO_DELEGATE)
-            g_object_unref(G_OBJECT(pixel_buffer));
-#endif
-            g_object_unref(svg_handle);
-            ThrowReaderException(MissingDelegateError,
-              "NoDecodeDelegateForThisImageFormat");
-          }
         if (image_info->ping == MagickFalse)
           {
 #if defined(MAGICKCORE_CAIRO_DELEGATE)
             size_t
               stride;
+#endif
 
+            status=SetImageExtent(image,image->columns,image->rows,exception);
+            if (status == MagickFalse)
+              {
+#if !defined(MAGICKCORE_CAIRO_DELEGATE)
+                g_object_unref(G_OBJECT(pixel_buffer));
+#endif
+                g_object_unref(svg_handle);
+                ThrowReaderException(MissingDelegateError,
+                  "NoDecodeDelegateForThisImageFormat");
+              }
+#if defined(MAGICKCORE_CAIRO_DELEGATE)
             stride=4*image->columns;
 #if defined(MAGICKCORE_PANGOCAIRO_DELEGATE)
             stride=(size_t) cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32,
@@ -3258,8 +3426,8 @@ static Image *ReadSVGImage(const ImageInfo *image_info,ExceptionInfo *exception)
             cairo_paint(cairo_image);
             cairo_set_operator(cairo_image,CAIRO_OPERATOR_OVER);
             if (apply_density != MagickFalse)
-              cairo_scale(cairo_image,image->resolution.x/96.0,
-                image->resolution.y/96.0);
+              cairo_scale(cairo_image,image->resolution.x/DefaultSVGDensity,
+                image->resolution.y/DefaultSVGDensity);
             rsvg_handle_render_cairo(svg_handle,cairo_image);
             cairo_destroy(cairo_image);
             cairo_surface_destroy(cairo_surface);
@@ -3437,8 +3605,6 @@ static Image *ReadSVGImage(const ImageInfo *image_info,ExceptionInfo *exception)
       image=(Image *) NULL;
       read_info=CloneImageInfo(image_info);
       SetImageInfoBlob(read_info,(void *) NULL,0);
-      if (read_info->density != (char *) NULL)
-        read_info->density=DestroyString(read_info->density);
       (void) FormatLocaleString(read_info->filename,MagickPathExtent,"mvg:%s",
         filename);
       image=ReadImage(read_info,exception);
@@ -4215,9 +4381,29 @@ static MagickBooleanType WriteSVGImage(const ImageInfo *image_info,Image *image,
         status=MagickFalse;
         break;
       }
+      case 'k':
+      case 'K':
+      {
+        if (LocaleCompare("kerning",keyword) == 0)
+          {
+            GetNextToken(q,&q,extent,token);
+            (void) FormatLocaleString(message,MagickPathExtent,"kerning:%s;",
+              token);
+            (void) WriteBlobString(image,message);
+          }
+        break;
+      }
       case 'l':
       case 'L':
       {
+        if (LocaleCompare("letter-spacing",keyword) == 0)
+          {
+            GetNextToken(q,&q,extent,token);
+            (void) FormatLocaleString(message,MagickPathExtent,
+              "letter-spacing:%s;",token);
+            (void) WriteBlobString(image,message);
+            break;
+          }
         if (LocaleCompare("line",keyword) == 0)
           {
             primitive_type=LinePrimitive;