2929except NameError :
3030 basestring = str
3131
32+ try :
33+ unicode # Py2
34+ except NameError :
35+ unicode = str
36+
3237try :
3338 file
3439except NameError :
4853 except Exception :
4954 IOBase = object
5055
56+ try :
57+ import gzip
58+ except ImportError :
59+ gzip = None
60+
5161# CairoSVG optional conversion
5262cairosvgsupport = False
53- svgwrite_valid_extensions = set (["SVG" ])
63+ svgwrite_valid_extensions = set (["SVG" , "SVGZ" ])
5464if upcean .support .cairosvgsupport :
5565 try :
5666 import cairosvg
5767 cairosvgsupport = True
58- svgwrite_valid_extensions = set (["SVG" , "PDF" , "PS" , "EPS" , "PNG" ])
68+ svgwrite_valid_extensions = set (["SVG" , "SVGZ" , " PDF" , "PS" , "EPS" , "PNG" ])
5969 except ImportError :
6070 cairosvgsupport = False
61- svgwrite_valid_extensions = set (["SVG" ])
71+ svgwrite_valid_extensions = set (["SVG" , "SVGZ" ])
6272
6373# regex/constants
6474_RE_URL = re .compile (r"^(ftp|ftps|sftp)://" , re .IGNORECASE )
65- _RE_EXT = re .compile (r"^\.(?P<ext>[A-Za-z ]+)$" )
66- _RE_NAME_EXT = re .compile (r"^(?P<name>.+):(?P<ext>[A-Za-z ]+)$" )
75+ _RE_EXT = re .compile (r"^\.(?P<ext>[A-Za-z0-9 ]+)$" )
76+ _RE_NAME_EXT = re .compile (r"^(?P<name>.+):(?P<ext>[A-Za-z0-9 ]+)$" )
6777
6878
6979def _is_file_like (x ):
@@ -103,6 +113,18 @@ def _svg_bytes_from_dwg(dwg):
103113 return s .encode ("utf-8" )
104114
105115
116+ def _gzip_bytes (data_bytes , compresslevel = 9 ):
117+ if gzip is None :
118+ raise ImportError ("gzip module not available" )
119+ out = BytesIO ()
120+ gz = gzip .GzipFile (filename = "" , mode = "wb" , fileobj = out , compresslevel = compresslevel )
121+ try :
122+ gz .write (data_bytes )
123+ finally :
124+ gz .close ()
125+ return out .getvalue ()
126+
127+
106128def _convert_svg_bytes (svg_bytes , fmt , out_fp ):
107129 """
108130 Convert SVG bytes to fmt using CairoSVG, writing into out_fp (file-like or filename).
@@ -130,13 +152,44 @@ def _convert_svg_bytes(svg_bytes, fmt, out_fp):
130152 pass
131153
132154
155+ def _write_bytes_to_target (data , target ):
156+ """
157+ Write bytes to:
158+ - file-like object
159+ - filename path
160+ """
161+ if _is_file_like (target ):
162+ target .write (data )
163+ return True
164+ f = open (target , "wb" )
165+ try :
166+ f .write (data )
167+ finally :
168+ f .close ()
169+ return True
170+
171+
172+ def _normalize_local_path (path , ext_upper ):
173+ """
174+ For local filesystem paths:
175+ - expand ~ and env vars
176+ - make absolute
177+ - append extension if missing (case-insensitive)
178+ """
179+ p = os .path .abspath (os .path .expandvars (os .path .expanduser (path )))
180+ ext_l = ext_upper .lower ()
181+ if ext_l and not p .lower ().endswith ("." + ext_l ):
182+ p = p + "." + ext_l
183+ return p
184+
185+
133186def get_save_filename (outfile ):
134187 """
135188 Determine (filename, EXT) where EXT is one of svgwrite_valid_extensions.
136189 Supports:
137190 - None/bool -> passthrough
138191 - file-like or "-" -> (outfile, "SVG")
139- - "name.ext" or "name:EXT" -> parsed
192+ - "name.ext" or "name:EXT" -> parsed (now includes svgz)
140193 - (name, ext) -> validated
141194 """
142195 if outfile is None or isinstance (outfile , bool ):
@@ -275,10 +328,7 @@ def embed_font(dwg, font_path, font_family):
275328 cache = set ()
276329 setattr (dwg , "_embedded_fonts" , cache )
277330
278- try :
279- fam = unicode (font_family ) # Py2
280- except Exception :
281- fam = str (font_family )
331+ fam = unicode (font_family )
282332
283333 key = (os .path .abspath (font_path ), fam )
284334 if key in cache :
@@ -344,7 +394,8 @@ def embed_font(dwg, font_path, font_family):
344394def save_to_file (inimage , outfile , outfileext , imgcomment = "barcode" ):
345395 """
346396 Save drawsvg.Drawing to:
347- - SVG directly, or
397+ - SVG directly
398+ - SVGZ (gzip-compressed SVG) <-- added
348399 - PNG/PDF/PS/EPS via CairoSVG (when available)
349400
350401 Behaviors preserved:
@@ -363,56 +414,78 @@ def save_to_file(inimage, outfile, outfileext, imgcomment="barcode"):
363414 # Decide destination
364415 if isinstance (outfile , basestring ) and _RE_URL .match (str (outfile )):
365416 upload_target = outfile
366- # Use BytesIO when converting (CairoSVG) else StringIO is fine
367- outfp = BytesIO () if (cairosvgsupport and fmt != "SVG" ) else StringIO ()
417+ # SVGZ and conversions are binary; SVG can be text but uploads are bytes anyway.
418+ outfp = BytesIO () if (fmt in ( "SVGZ" , "PNG" , "PDF" , "PS" , "EPS" ) or ( cairosvgsupport and fmt != "SVG" ) ) else StringIO ()
368419 elif outfile == "-" :
369420 return_to_var = True
370- outfp = BytesIO () if (cairosvgsupport and fmt != "SVG" ) else StringIO ()
421+ outfp = BytesIO () if (fmt in ( "SVGZ" , "PNG" , "PDF" , "PS" , "EPS" ) or ( cairosvgsupport and fmt != "SVG" ) ) else StringIO ()
371422 else :
372423 outfp = outfile # filename or file-like
373424
425+ # Normalize local filename strings (append extension if missing)
426+ if isinstance (outfp , basestring ) and not _RE_URL .match (str (outfp )) and outfp not in ("" , "-" ):
427+ outfp = _normalize_local_path (outfp , fmt )
428+
374429 # Write/convert
375- if cairosvgsupport and fmt in ("PNG" , "PDF" , "PS" , "EPS" , "SVG" ):
430+ if fmt == "SVGZ" :
431+ svg_bytes = _svg_bytes_from_dwg (dwg )
432+ gz_bytes = _gzip_bytes (svg_bytes )
433+ if isinstance (outfp , basestring ) and not outfp .lower ().endswith (".svgz" ):
434+ outfp = outfp + ".svgz"
435+ _write_bytes_to_target (gz_bytes , outfp )
436+
437+ elif cairosvgsupport and fmt in ("PNG" , "PDF" , "PS" , "EPS" , "SVG" ):
376438 _convert_svg_bytes (_svg_bytes_from_dwg (dwg ), fmt , outfp )
439+
377440 else :
378441 # Plain SVG
379442 svg_text = _drawing_to_svg_text (dwg )
380443
381444 if _is_file_like (outfp ):
382445 # If it's a binary buffer, write bytes
383446 if isinstance (outfp , BytesIO ):
384- outfp .write (svg_text . encode ( "utf-8" ) if not isinstance (svg_text , bytes ) else svg_text )
447+ outfp .write (svg_text if isinstance (svg_text , bytes ) else svg_text . encode ( "utf-8" ) )
385448 else :
386449 try :
387450 outfp .write (svg_text )
388451 except TypeError :
389452 outfp .write (svg_text .encode ("utf-8" ))
390453 else :
391454 # filename path
392- if hasattr (dwg , "save_svg" ) and fmt == "SVG" :
393- dwg .save_svg (outfp )
394- else :
395- # Py2: no encoding arg in open()
396- f = open (outfp , "wb" )
397- try :
398- b = svg_text if isinstance (svg_text , bytes ) else svg_text .encode ("utf-8" )
399- f .write (b )
400- finally :
401- f .close ()
455+ f = open (outfp , "wb" )
456+ try :
457+ b = svg_text if isinstance (svg_text , bytes ) else svg_text .encode ("utf-8" )
458+ f .write (b )
459+ finally :
460+ f .close ()
402461
403462 # Upload
404463 if upload_target :
464+ # always upload bytes
405465 if isinstance (outfp , BytesIO ):
406466 outfp .seek (0 )
407467 upload_file_to_internet_file (outfp , upload_target )
408468 outfp .close ()
409- else :
469+ elif _is_file_like ( outfp ) :
410470 outfp .seek (0 )
411- b = BytesIO (outfp .getvalue ().encode ("utf-8" ))
412- outfp .close ()
471+ data = outfp .read ()
472+ if not isinstance (data , bytes ):
473+ data = data .encode ("utf-8" )
474+ b = BytesIO ()
475+ b .write (data )
413476 b .seek (0 )
414477 upload_file_to_internet_file (b , upload_target )
415478 b .close ()
479+ try :
480+ outfp .close ()
481+ except Exception :
482+ pass
483+ else :
484+ f = open (outfp , "rb" )
485+ try :
486+ upload_file_to_internet_file (f , upload_target )
487+ finally :
488+ f .close ()
416489 return True
417490
418491 # "-" returns
0 commit comments