svg.py 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. import lxml.etree as etree
  2. import lxml.builder as builder
  3. from io import BytesIO
  4. import log
  5. restrictedElems = [ "a"
  6. , "color-profile"
  7. , "font"
  8. , "icccolor"
  9. , "switch"
  10. , "script"
  11. , "text"
  12. , "view"
  13. ]
  14. restrictedAttrs = [ "contentStyleType"
  15. , "color-profile"
  16. ]
  17. unenforcedElems = [ "animateTransform"
  18. , "cursor"
  19. , "filter"
  20. , "marker"
  21. , "mask"
  22. , "pattern"
  23. , "style"
  24. # SVG Filter elements
  25. , "feBlend"
  26. , "feColorMatrix"
  27. , "feComponentTransfer"
  28. , "feComposite"
  29. , "feConvolveMatrix"
  30. , "feDiffuseLighting"
  31. , "feDisplacementMap"
  32. , "feDistantLight"
  33. , "feDropShadow"
  34. , "feGaussianBlur"
  35. , "feImage"
  36. , "feMerge"
  37. , "feMergeNode"
  38. , "feMorphology"
  39. , "feOffset"
  40. , "fePointLight"
  41. , "feSpecularLighting"
  42. , "feSpotLight"
  43. , "feTile"
  44. , "feTurbulence"
  45. ]
  46. unenforcedAttrs = [ "cursor"
  47. #, "style" # style is compensated for in forc, so it's currently commented out.
  48. , "zoomAndPan"
  49. # SVG event attributes
  50. , "onbegin"
  51. , "onend"
  52. , "onrepeat"
  53. , "onabort"
  54. , "onerror"
  55. , "onresize"
  56. , "onscroll"
  57. , "onunload"
  58. , "oncopy"
  59. , "oncut"
  60. , "onpaste"
  61. , "oncancel"
  62. , "oncanplay"
  63. , "oncanplaythrough"
  64. , "onchange"
  65. , "onclick"
  66. , "onclose"
  67. , "oncuechange"
  68. , "ondblclick"
  69. , "ondrag"
  70. , "ondragend"
  71. , "ondragenter"
  72. , "ondragexit"
  73. , "ondragleave"
  74. , "ondragover"
  75. , "ondragstart"
  76. , "ondrop"
  77. , "ondurationchange"
  78. , "onemptied"
  79. , "onended"
  80. , "onerror"
  81. , "onfocus"
  82. , "oninput"
  83. , "oninvalid"
  84. , "onkeydown"
  85. , "onkeypress"
  86. , "onkeyup"
  87. , "onload"
  88. , "onloadeddata"
  89. , "onloadedmetadata"
  90. , "onloadstart"
  91. , "onmousedown"
  92. , "onmouseenter"
  93. , "onmouseleave"
  94. , "onmousemove"
  95. , "onmouseout"
  96. , "onmouseover"
  97. , "onmouseup"
  98. , "onmousewheel"
  99. , "onpause"
  100. , "onplay"
  101. , "onplaying"
  102. , "onprogress"
  103. , "onratechange"
  104. , "onreset"
  105. , "onresize"
  106. , "onscroll"
  107. , "onseeked"
  108. , "onseeking"
  109. , "onselect"
  110. , "onshow"
  111. , "onstalled"
  112. , "onsubmit"
  113. , "onsuspend"
  114. , "ontimeupdate"
  115. , "ontoggle"
  116. , "onvolumechange"
  117. , "onwaiting"
  118. , "onactivate"
  119. , "onfocusin"
  120. , "onfocusout"
  121. ]
  122. xmlns = '{http://www.w3.org/2000/svg}'
  123. xlinkNS = '{http://www.w3.org/1999/xlink}'
  124. def isSVGValid(svgImage, ignoreUnenforcedContents=False):
  125. """
  126. Evaluates if a glyphs' SVG file is compliant with the SVGinOT standard.
  127. This checks for most things.
  128. Checks that currently don't exist:
  129. restricted (explicitly forbidden) contents:
  130. - relative units (em, ex, etc.)
  131. - rgba() colors
  132. - CSS2 color values in styles
  133. 'unenforced' (not explicitly forbidden but not explicitly compatible either) contents:
  134. - XML entities
  135. """
  136. svgEmbeddedImages = svgImage.findall("//" + xmlns + "image")
  137. # Stuff relating to the root tag
  138. # --------------------------------------------------------------------
  139. # There must be an xmlns and it must be set to 'http://www.w3.org/2000/svg'.
  140. if None in svgImage.getroot().nsmap:
  141. if svgImage.getroot().nsmap[None] != 'http://www.w3.org/2000/svg':
  142. raise ValueError(f"This SVG image has a root namespace that is '{svgImage.getroot().nsmap[None]}'. It needs to be set to 'http://www.w3.org/2000/svg'.")
  143. else:
  144. raise ValueError(f"This SVG image doesn't have a root namespace. It needs one, and it needs to be set to 'http://www.w3.org/2000/svg'.")
  145. # If there is an xlink namespace, it must be set to "http://www.w3.org/1999/xlink".
  146. if "xlink" in svgImage.getroot().nsmap:
  147. if svgImage.getroot().nsmap["xlink"] != "http://www.w3.org/1999/xlink":
  148. raise ValueError(f"This SVG image has an xlink namespace that is '{svgImage.getroot().nsmap['xlink']}'. It needs to be set to 'http://www.w3.org/1999/xlink'.")
  149. # SVG version must either be "1.1" or unmarked.
  150. if "version" in svgImage.getroot().attrib:
  151. svgImageVersion = svgImage.getroot().attrib["version"]
  152. if not svgImageVersion == "1.1":
  153. raise ValueError(f"The version of This SVG image is set to '{svgImageVersion}'. It needs to either be set to 1.1 or removed entirely.")
  154. # Restricted contents
  155. # --------------------------------------------------------------------
  156. # These are explicitly not in the spec and should be disallowed under all circumstances.
  157. # elements
  158. for elem in restrictedElems:
  159. if svgImage.find('//' + xmlns + elem) is not None:
  160. raise ValueError(f"This SVG image has a '{elem}' element. These are not compatible in SVGinOT fonts.")
  161. # attributes
  162. for attr in restrictedAttrs:
  163. if svgImage.find(f"//*[@{attr}]") is not None:
  164. raise ValueError(f"This SVG image has a '{attr}' attribute. These are not compatible in SVGinOT fonts.")
  165. # image elements that contain SVGs
  166. if svgEmbeddedImages:
  167. for i in svgEmbeddedImages:
  168. href = i.attrib[xlinkNS + 'href']
  169. if href:
  170. if href.endswith('.svg'):
  171. raise ValueError(f"This SVG image has an 'image' attribute that links to an SVG file. These are not compatible in SVGinOT fonts.")
  172. # XSL processing instructions exist in the file
  173. if "xsl" in svgImage.getroot().nsmap:
  174. raise ValueError(f"This SVG image contains XSL. This is not compatible in SVGinOT fonts.")
  175. # not included:
  176. # - relative units (em, ex, etc.)
  177. # - rgba() colors
  178. # - CSS2 color values in styles
  179. # Unenforced Contents
  180. # --------------------------------------------------------------------
  181. # These are not enforced in the spec and are
  182. # not guaranteed to work.
  183. nuscMsg = "If you don't want forc to make an error when it detects this, use the --nusc build flag."
  184. if not ignoreUnenforcedContents:
  185. # elements
  186. for elem in unenforcedElems:
  187. if svgImage.find('//' + xmlns + elem) is not None:
  188. raise ValueError(f"This SVG image has a '{elem}' element. Compatibility with this is not mandatory in SVGinOT fonts so it is not recommended. {nuscMsg}")
  189. # attributes
  190. for attr in unenforcedAttrs:
  191. if svgImage.find(f"//*[@{attr}]") is not None:
  192. raise ValueError(f"This SVG image has a '{attr}' attribute. Compatibility with this is not mandatory in SVGinOT fonts so it is not recommended. {nuscMsg}")
  193. # image elements that don't contain JPEGs or PNGs
  194. acceptedImageExtensions = ['.png', '.jpg', '.jpeg', '.jpe', '.jif', '.jfif', '.jfi']
  195. if svgEmbeddedImages:
  196. for i in svgEmbeddedImages:
  197. href = i.attrib[xlinkNS + 'href']
  198. if href:
  199. count = 0
  200. for ext in acceptedImageExtensions:
  201. if not href.endswith(ext):
  202. count += 1
  203. if count == len(acceptedImageExtensions):
  204. raise ValueError(f"This SVG image has one or more image attribute(s) that links to a file that is not a JPEG or PNG image. Compatibility with any image type other than PNG or JPEG is not mandatory in SVGinOT fonts so it is not recommended. {nuscMsg}")
  205. # there should be no SVG child elements.
  206. if svgImage.find("//{*}svg") is not None:
  207. raise ValueError(f"This SVG image has a child svg attribute. Compatibility with this is not mandatory in SVGinOT fonts so it is not recommended. {nuscMsg}")
  208. # not included:
  209. # - XML entities