@graphty/graph-io 0.3.43 → 0.3.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (674) hide show
  1. package/dist/chunks/approximations-DJMHdDvv.js +40 -0
  2. package/dist/chunks/approximations-DJMHdDvv.js.map +1 -0
  3. package/dist/chunks/block-CPCHkX42.js +2777 -0
  4. package/dist/chunks/block-CPCHkX42.js.map +1 -0
  5. package/dist/chunks/{choose-DvpbdRuz.js → choose--jM0yYCo.js} +3 -2
  6. package/dist/chunks/{choose-DvpbdRuz.js.map → choose--jM0yYCo.js.map} +1 -1
  7. package/dist/chunks/{constants-fT3E8JYY.js → constants-BKwqelYD.js} +6 -2
  8. package/dist/chunks/constants-BKwqelYD.js.map +1 -0
  9. package/dist/chunks/convert-BcQ8_jMo.js +59 -0
  10. package/dist/chunks/convert-BcQ8_jMo.js.map +1 -0
  11. package/dist/chunks/emit-DZy6JgdA.js +3456 -0
  12. package/dist/chunks/emit-DZy6JgdA.js.map +1 -0
  13. package/dist/chunks/{escape-B62ZGM_a.js → escape-CgzxiYPg.js} +44 -21
  14. package/dist/chunks/escape-CgzxiYPg.js.map +1 -0
  15. package/dist/chunks/{export-rKLeGQMo.js → export-BvqAXCKh.js} +196 -792
  16. package/dist/chunks/export-BvqAXCKh.js.map +1 -0
  17. package/dist/chunks/{exporter-yLxKBvLE.js → exporter--Mk9gXCJ.js} +4 -3
  18. package/dist/chunks/{exporter-yLxKBvLE.js.map → exporter--Mk9gXCJ.js.map} +1 -1
  19. package/dist/chunks/{exporter-RVtXVetw.js → exporter-C-9cgZKa.js} +1805 -287
  20. package/dist/chunks/exporter-C-9cgZKa.js.map +1 -0
  21. package/dist/chunks/{exporter-8hLNemFk.js → exporter-DFqJhBnj.js} +133 -74
  22. package/dist/chunks/exporter-DFqJhBnj.js.map +1 -0
  23. package/dist/chunks/{exporter-BWwEuOPT.js → exporter-DteVeJki.js} +22 -12
  24. package/dist/chunks/exporter-DteVeJki.js.map +1 -0
  25. package/dist/chunks/format-BpQBx8MZ.js +676 -0
  26. package/dist/chunks/format-BpQBx8MZ.js.map +1 -0
  27. package/dist/chunks/generic-BGO71QBv.js +1347 -0
  28. package/dist/chunks/generic-BGO71QBv.js.map +1 -0
  29. package/dist/chunks/{importer-C98dpSH4.js → importer-B-9Rc1zO.js} +141 -82
  30. package/dist/chunks/importer-B-9Rc1zO.js.map +1 -0
  31. package/dist/chunks/{importer-D794XUTe.js → importer-BVVR740x.js} +31 -8
  32. package/dist/chunks/importer-BVVR740x.js.map +1 -0
  33. package/dist/chunks/{importer-WrGRZb0N.js → importer-C0AjA3Ax.js} +449 -102
  34. package/dist/chunks/importer-C0AjA3Ax.js.map +1 -0
  35. package/dist/chunks/{importer-BbNb7eNg.js → importer-C7pvJe1L.js} +1398 -129
  36. package/dist/chunks/importer-C7pvJe1L.js.map +1 -0
  37. package/dist/chunks/{importer-xZ-3suY1.js → importer-ClwoVx3O.js} +791 -135
  38. package/dist/chunks/importer-ClwoVx3O.js.map +1 -0
  39. package/dist/chunks/{importer-Ht7LNg8L.js → importer-D0sC_6bO.js} +116 -296
  40. package/dist/chunks/importer-D0sC_6bO.js.map +1 -0
  41. package/dist/chunks/{importer-Dz7tI8Sa.js → importer-EHQk1jbM.js} +617 -115
  42. package/dist/chunks/importer-EHQk1jbM.js.map +1 -0
  43. package/dist/chunks/{json-elements-DKSqpNW1.js → json-elements-DJZRZ6jd.js} +123 -32
  44. package/dist/chunks/json-elements-DJZRZ6jd.js.map +1 -0
  45. package/dist/chunks/lexicon-WMsNddyU.js +509 -0
  46. package/dist/chunks/lexicon-WMsNddyU.js.map +1 -0
  47. package/dist/chunks/{ontology-export-CtRXjg_i.js → ontology-export-G4MqRdux.js} +3 -2
  48. package/dist/chunks/{ontology-export-CtRXjg_i.js.map → ontology-export-G4MqRdux.js.map} +1 -1
  49. package/dist/chunks/{records-Cu4bxYOm.js → records-CiaWZrFH.js} +2 -2
  50. package/dist/chunks/{records-Cu4bxYOm.js.map → records-CiaWZrFH.js.map} +1 -1
  51. package/dist/chunks/styles-DvoFBKCK.js +690 -0
  52. package/dist/chunks/styles-DvoFBKCK.js.map +1 -0
  53. package/dist/chunks/styles-yed-Cz9rLBCP.js +842 -0
  54. package/dist/chunks/styles-yed-Cz9rLBCP.js.map +1 -0
  55. package/dist/chunks/styles-zNTmz5R7.js +2102 -0
  56. package/dist/chunks/styles-zNTmz5R7.js.map +1 -0
  57. package/dist/chunks/{text-DHOJXnED.js → text-CzJNFOS9.js} +2 -2
  58. package/dist/chunks/{text-DHOJXnED.js.map → text-CzJNFOS9.js.map} +1 -1
  59. package/dist/chunks/units-XMZFIFe1.js +19 -0
  60. package/dist/chunks/units-XMZFIFe1.js.map +1 -0
  61. package/dist/chunks/writer-gQ8kx99H.js +448 -0
  62. package/dist/chunks/writer-gQ8kx99H.js.map +1 -0
  63. package/dist/csv.js +5 -4
  64. package/dist/csv.js.map +1 -1
  65. package/dist/cx.js +1 -1
  66. package/dist/cx2.js +2 -2
  67. package/dist/cys.js +1 -1
  68. package/dist/dot.js +5 -2
  69. package/dist/dot.js.map +1 -1
  70. package/dist/gexf.js +292 -412
  71. package/dist/gexf.js.map +1 -1
  72. package/dist/gml.js +927 -74
  73. package/dist/gml.js.map +1 -1
  74. package/dist/graph-io.js +351 -236
  75. package/dist/graph-io.js.map +1 -1
  76. package/dist/graphml.js +4 -2
  77. package/dist/graphml.js.map +1 -1
  78. package/dist/json.js +1 -1
  79. package/dist/neo4j.js +4 -3
  80. package/dist/neo4j.js.map +1 -1
  81. package/dist/obo.js +1 -1
  82. package/dist/pajek.js +4 -2
  83. package/dist/positions.d.ts +1 -0
  84. package/dist/positions.js +427 -0
  85. package/dist/positions.js.map +1 -0
  86. package/dist/src/common/codes.d.ts +6 -0
  87. package/dist/src/common/codes.d.ts.map +1 -1
  88. package/dist/src/common/codes.js +6 -0
  89. package/dist/src/common/codes.js.map +1 -1
  90. package/dist/src/common/escape.d.ts.map +1 -1
  91. package/dist/src/common/escape.js +4 -2
  92. package/dist/src/common/escape.js.map +1 -1
  93. package/dist/src/common/export.d.ts +10 -0
  94. package/dist/src/common/export.d.ts.map +1 -1
  95. package/dist/src/common/export.js +123 -100
  96. package/dist/src/common/export.js.map +1 -1
  97. package/dist/src/common/input.d.ts.map +1 -1
  98. package/dist/src/common/input.js +8 -1
  99. package/dist/src/common/input.js.map +1 -1
  100. package/dist/src/common/json-elements.d.ts +12 -9
  101. package/dist/src/common/json-elements.d.ts.map +1 -1
  102. package/dist/src/common/json-elements.js +160 -37
  103. package/dist/src/common/json-elements.js.map +1 -1
  104. package/dist/src/common/notes.d.ts +42 -0
  105. package/dist/src/common/notes.d.ts.map +1 -0
  106. package/dist/src/common/notes.js +72 -0
  107. package/dist/src/common/notes.js.map +1 -0
  108. package/dist/src/common/report.d.ts +18 -0
  109. package/dist/src/common/report.d.ts.map +1 -1
  110. package/dist/src/common/report.js +36 -1
  111. package/dist/src/common/report.js.map +1 -1
  112. package/dist/src/common/units.d.ts +24 -0
  113. package/dist/src/common/units.d.ts.map +1 -0
  114. package/dist/src/common/units.js +26 -0
  115. package/dist/src/common/units.js.map +1 -0
  116. package/dist/src/common/xml.d.ts +18 -0
  117. package/dist/src/common/xml.d.ts.map +1 -1
  118. package/dist/src/common/xml.js +42 -5
  119. package/dist/src/common/xml.js.map +1 -1
  120. package/dist/src/formats/cx/exporter.d.ts +6 -0
  121. package/dist/src/formats/cx/exporter.d.ts.map +1 -1
  122. package/dist/src/formats/cx/exporter.js +71 -8
  123. package/dist/src/formats/cx/exporter.js.map +1 -1
  124. package/dist/src/formats/cx/importer.d.ts +5 -2
  125. package/dist/src/formats/cx/importer.d.ts.map +1 -1
  126. package/dist/src/formats/cx/importer.js +102 -72
  127. package/dist/src/formats/cx/importer.js.map +1 -1
  128. package/dist/src/formats/cx/styles.d.ts +102 -0
  129. package/dist/src/formats/cx/styles.d.ts.map +1 -0
  130. package/dist/src/formats/cx/styles.js +225 -0
  131. package/dist/src/formats/cx/styles.js.map +1 -0
  132. package/dist/src/formats/cx2/exporter.d.ts +5 -0
  133. package/dist/src/formats/cx2/exporter.d.ts.map +1 -1
  134. package/dist/src/formats/cx2/exporter.js +181 -71
  135. package/dist/src/formats/cx2/exporter.js.map +1 -1
  136. package/dist/src/formats/cx2/importer.d.ts +4 -1
  137. package/dist/src/formats/cx2/importer.d.ts.map +1 -1
  138. package/dist/src/formats/cx2/importer.js +19 -1
  139. package/dist/src/formats/cx2/importer.js.map +1 -1
  140. package/dist/src/formats/cx2/styles.d.ts +18 -0
  141. package/dist/src/formats/cx2/styles.d.ts.map +1 -0
  142. package/dist/src/formats/cx2/styles.js +24 -0
  143. package/dist/src/formats/cx2/styles.js.map +1 -0
  144. package/dist/src/formats/cys/constants.d.ts +2 -0
  145. package/dist/src/formats/cys/constants.d.ts.map +1 -1
  146. package/dist/src/formats/cys/constants.js +2 -0
  147. package/dist/src/formats/cys/constants.js.map +1 -1
  148. package/dist/src/formats/cys/exporter.d.ts +6 -2
  149. package/dist/src/formats/cys/exporter.d.ts.map +1 -1
  150. package/dist/src/formats/cys/exporter.js +215 -53
  151. package/dist/src/formats/cys/exporter.js.map +1 -1
  152. package/dist/src/formats/cys/importer.d.ts +2 -1
  153. package/dist/src/formats/cys/importer.d.ts.map +1 -1
  154. package/dist/src/formats/cys/importer.js +119 -6
  155. package/dist/src/formats/cys/importer.js.map +1 -1
  156. package/dist/src/formats/cys/session.d.ts +2 -0
  157. package/dist/src/formats/cys/session.d.ts.map +1 -1
  158. package/dist/src/formats/cys/session.js +5 -0
  159. package/dist/src/formats/cys/session.js.map +1 -1
  160. package/dist/src/formats/cys/styles.d.ts +38 -0
  161. package/dist/src/formats/cys/styles.d.ts.map +1 -0
  162. package/dist/src/formats/cys/styles.js +213 -0
  163. package/dist/src/formats/cys/styles.js.map +1 -0
  164. package/dist/src/formats/cys/tables.d.ts +8 -3
  165. package/dist/src/formats/cys/tables.d.ts.map +1 -1
  166. package/dist/src/formats/cys/tables.js +88 -34
  167. package/dist/src/formats/cys/tables.js.map +1 -1
  168. package/dist/src/formats/dot/exporter.d.ts +2 -0
  169. package/dist/src/formats/dot/exporter.d.ts.map +1 -1
  170. package/dist/src/formats/dot/exporter.js +195 -81
  171. package/dist/src/formats/dot/exporter.js.map +1 -1
  172. package/dist/src/formats/dot/importer.d.ts.map +1 -1
  173. package/dist/src/formats/dot/importer.js +100 -20
  174. package/dist/src/formats/dot/importer.js.map +1 -1
  175. package/dist/src/formats/dot/index.d.ts +1 -0
  176. package/dist/src/formats/dot/index.d.ts.map +1 -1
  177. package/dist/src/formats/dot/index.js +1 -0
  178. package/dist/src/formats/dot/index.js.map +1 -1
  179. package/dist/src/formats/dot/positions.d.ts +14 -0
  180. package/dist/src/formats/dot/positions.d.ts.map +1 -0
  181. package/dist/src/formats/dot/positions.js +70 -0
  182. package/dist/src/formats/dot/positions.js.map +1 -0
  183. package/dist/src/formats/dot/styles.d.ts +82 -0
  184. package/dist/src/formats/dot/styles.d.ts.map +1 -0
  185. package/dist/src/formats/dot/styles.js +1496 -0
  186. package/dist/src/formats/dot/styles.js.map +1 -0
  187. package/dist/src/formats/gexf/exporter.d.ts.map +1 -1
  188. package/dist/src/formats/gexf/exporter.js +20 -5
  189. package/dist/src/formats/gexf/exporter.js.map +1 -1
  190. package/dist/src/formats/gexf/importer.d.ts +8 -1
  191. package/dist/src/formats/gexf/importer.d.ts.map +1 -1
  192. package/dist/src/formats/gexf/importer.js +251 -131
  193. package/dist/src/formats/gexf/importer.js.map +1 -1
  194. package/dist/src/formats/gexf/index.d.ts +8 -0
  195. package/dist/src/formats/gexf/index.d.ts.map +1 -1
  196. package/dist/src/formats/gexf/index.js +10 -2
  197. package/dist/src/formats/gexf/index.js.map +1 -1
  198. package/dist/src/formats/gexf/schema.d.ts +5 -0
  199. package/dist/src/formats/gexf/schema.d.ts.map +1 -1
  200. package/dist/src/formats/gexf/schema.js +5 -0
  201. package/dist/src/formats/gexf/schema.js.map +1 -1
  202. package/dist/src/formats/gexf/styles.d.ts +74 -0
  203. package/dist/src/formats/gexf/styles.d.ts.map +1 -0
  204. package/dist/src/formats/gexf/styles.js +604 -0
  205. package/dist/src/formats/gexf/styles.js.map +1 -0
  206. package/dist/src/formats/gml/exporter.d.ts +1 -5
  207. package/dist/src/formats/gml/exporter.d.ts.map +1 -1
  208. package/dist/src/formats/gml/exporter.js +19 -8
  209. package/dist/src/formats/gml/exporter.js.map +1 -1
  210. package/dist/src/formats/gml/importer.d.ts.map +1 -1
  211. package/dist/src/formats/gml/importer.js +4 -0
  212. package/dist/src/formats/gml/importer.js.map +1 -1
  213. package/dist/src/formats/gml/index.d.ts +2 -1
  214. package/dist/src/formats/gml/index.d.ts.map +1 -1
  215. package/dist/src/formats/gml/index.js +2 -1
  216. package/dist/src/formats/gml/index.js.map +1 -1
  217. package/dist/src/formats/gml/positions.d.ts +14 -0
  218. package/dist/src/formats/gml/positions.d.ts.map +1 -0
  219. package/dist/src/formats/gml/positions.js +46 -0
  220. package/dist/src/formats/gml/positions.js.map +1 -0
  221. package/dist/src/formats/gml/styles.d.ts +59 -0
  222. package/dist/src/formats/gml/styles.d.ts.map +1 -0
  223. package/dist/src/formats/gml/styles.js +1056 -0
  224. package/dist/src/formats/gml/styles.js.map +1 -0
  225. package/dist/src/formats/gml/syntax.d.ts +11 -0
  226. package/dist/src/formats/gml/syntax.d.ts.map +1 -1
  227. package/dist/src/formats/gml/syntax.js +151 -62
  228. package/dist/src/formats/gml/syntax.js.map +1 -1
  229. package/dist/src/formats/graphml/constants.d.ts +4 -0
  230. package/dist/src/formats/graphml/constants.d.ts.map +1 -1
  231. package/dist/src/formats/graphml/constants.js +5 -1
  232. package/dist/src/formats/graphml/constants.js.map +1 -1
  233. package/dist/src/formats/graphml/exporter.d.ts.map +1 -1
  234. package/dist/src/formats/graphml/exporter.js +133 -65
  235. package/dist/src/formats/graphml/exporter.js.map +1 -1
  236. package/dist/src/formats/graphml/importer.d.ts +4 -1
  237. package/dist/src/formats/graphml/importer.d.ts.map +1 -1
  238. package/dist/src/formats/graphml/importer.js +100 -44
  239. package/dist/src/formats/graphml/importer.js.map +1 -1
  240. package/dist/src/formats/graphml/index.d.ts +1 -0
  241. package/dist/src/formats/graphml/index.d.ts.map +1 -1
  242. package/dist/src/formats/graphml/index.js +1 -0
  243. package/dist/src/formats/graphml/index.js.map +1 -1
  244. package/dist/src/formats/graphml/styles-plain.d.ts +19 -0
  245. package/dist/src/formats/graphml/styles-plain.d.ts.map +1 -0
  246. package/dist/src/formats/graphml/styles-plain.js +187 -0
  247. package/dist/src/formats/graphml/styles-plain.js.map +1 -0
  248. package/dist/src/formats/graphml/styles-yed.d.ts +179 -0
  249. package/dist/src/formats/graphml/styles-yed.d.ts.map +1 -0
  250. package/dist/src/formats/graphml/styles-yed.js +1086 -0
  251. package/dist/src/formats/graphml/styles-yed.js.map +1 -0
  252. package/dist/src/formats/graphml/styles-yfiles3.d.ts +19 -0
  253. package/dist/src/formats/graphml/styles-yfiles3.d.ts.map +1 -0
  254. package/dist/src/formats/graphml/styles-yfiles3.js +508 -0
  255. package/dist/src/formats/graphml/styles-yfiles3.js.map +1 -0
  256. package/dist/src/formats/graphml/tree.d.ts.map +1 -1
  257. package/dist/src/formats/graphml/tree.js +34 -19
  258. package/dist/src/formats/graphml/tree.js.map +1 -1
  259. package/dist/src/formats/graphml/yfiles.d.ts +16 -2
  260. package/dist/src/formats/graphml/yfiles.d.ts.map +1 -1
  261. package/dist/src/formats/graphml/yfiles.js +83 -12
  262. package/dist/src/formats/graphml/yfiles.js.map +1 -1
  263. package/dist/src/formats/json/exporter.d.ts +5 -0
  264. package/dist/src/formats/json/exporter.d.ts.map +1 -1
  265. package/dist/src/formats/json/exporter.js +68 -25
  266. package/dist/src/formats/json/exporter.js.map +1 -1
  267. package/dist/src/formats/json/importer.d.ts +22 -3
  268. package/dist/src/formats/json/importer.d.ts.map +1 -1
  269. package/dist/src/formats/json/importer.js +439 -264
  270. package/dist/src/formats/json/importer.js.map +1 -1
  271. package/dist/src/formats/json/positions.d.ts +16 -0
  272. package/dist/src/formats/json/positions.d.ts.map +1 -0
  273. package/dist/src/formats/json/positions.js +44 -0
  274. package/dist/src/formats/json/positions.js.map +1 -0
  275. package/dist/src/formats/json/styles.d.ts +102 -0
  276. package/dist/src/formats/json/styles.d.ts.map +1 -0
  277. package/dist/src/formats/json/styles.js +1753 -0
  278. package/dist/src/formats/json/styles.js.map +1 -0
  279. package/dist/src/formats/neo4j/exporter.d.ts.map +1 -1
  280. package/dist/src/formats/neo4j/exporter.js +2 -1
  281. package/dist/src/formats/neo4j/exporter.js.map +1 -1
  282. package/dist/src/formats/pajek/exporter.d.ts +2 -0
  283. package/dist/src/formats/pajek/exporter.d.ts.map +1 -1
  284. package/dist/src/formats/pajek/exporter.js +142 -76
  285. package/dist/src/formats/pajek/exporter.js.map +1 -1
  286. package/dist/src/formats/pajek/importer.d.ts +4 -0
  287. package/dist/src/formats/pajek/importer.d.ts.map +1 -1
  288. package/dist/src/formats/pajek/importer.js +60 -6
  289. package/dist/src/formats/pajek/importer.js.map +1 -1
  290. package/dist/src/formats/pajek/index.d.ts +2 -0
  291. package/dist/src/formats/pajek/index.d.ts.map +1 -1
  292. package/dist/src/formats/pajek/index.js +2 -0
  293. package/dist/src/formats/pajek/index.js.map +1 -1
  294. package/dist/src/formats/pajek/styles.d.ts +50 -0
  295. package/dist/src/formats/pajek/styles.d.ts.map +1 -0
  296. package/dist/src/formats/pajek/styles.js +569 -0
  297. package/dist/src/formats/pajek/styles.js.map +1 -0
  298. package/dist/src/formats/pajek/syntax.d.ts +6 -0
  299. package/dist/src/formats/pajek/syntax.d.ts.map +1 -1
  300. package/dist/src/formats/pajek/syntax.js +6 -0
  301. package/dist/src/formats/pajek/syntax.js.map +1 -1
  302. package/dist/src/formats/xgmml/columns.d.ts.map +1 -1
  303. package/dist/src/formats/xgmml/columns.js +34 -9
  304. package/dist/src/formats/xgmml/columns.js.map +1 -1
  305. package/dist/src/formats/xgmml/constants.d.ts +2 -0
  306. package/dist/src/formats/xgmml/constants.d.ts.map +1 -1
  307. package/dist/src/formats/xgmml/constants.js +3 -1
  308. package/dist/src/formats/xgmml/constants.js.map +1 -1
  309. package/dist/src/formats/xgmml/document.d.ts +12 -8
  310. package/dist/src/formats/xgmml/document.d.ts.map +1 -1
  311. package/dist/src/formats/xgmml/document.js +56 -13
  312. package/dist/src/formats/xgmml/document.js.map +1 -1
  313. package/dist/src/formats/xgmml/emit.d.ts.map +1 -1
  314. package/dist/src/formats/xgmml/emit.js +12 -6
  315. package/dist/src/formats/xgmml/emit.js.map +1 -1
  316. package/dist/src/formats/xgmml/exporter.d.ts +3 -1
  317. package/dist/src/formats/xgmml/exporter.d.ts.map +1 -1
  318. package/dist/src/formats/xgmml/exporter.js +22 -9
  319. package/dist/src/formats/xgmml/exporter.js.map +1 -1
  320. package/dist/src/formats/xgmml/importer.d.ts +2 -1
  321. package/dist/src/formats/xgmml/importer.d.ts.map +1 -1
  322. package/dist/src/formats/xgmml/importer.js +31 -24
  323. package/dist/src/formats/xgmml/importer.js.map +1 -1
  324. package/dist/src/formats/xgmml/styles.d.ts +104 -0
  325. package/dist/src/formats/xgmml/styles.d.ts.map +1 -0
  326. package/dist/src/formats/xgmml/styles.js +310 -0
  327. package/dist/src/formats/xgmml/styles.js.map +1 -0
  328. package/dist/src/index.d.ts +9 -7
  329. package/dist/src/index.d.ts.map +1 -1
  330. package/dist/src/index.js +7 -7
  331. package/dist/src/index.js.map +1 -1
  332. package/dist/src/positions/codes.d.ts +31 -0
  333. package/dist/src/positions/codes.d.ts.map +1 -0
  334. package/dist/src/positions/codes.js +26 -0
  335. package/dist/src/positions/codes.js.map +1 -0
  336. package/dist/src/positions/convert.d.ts +58 -0
  337. package/dist/src/positions/convert.d.ts.map +1 -0
  338. package/dist/src/positions/convert.js +107 -0
  339. package/dist/src/positions/convert.js.map +1 -0
  340. package/dist/src/positions/extract.d.ts +27 -0
  341. package/dist/src/positions/extract.d.ts.map +1 -0
  342. package/dist/src/positions/extract.js +526 -0
  343. package/dist/src/positions/extract.js.map +1 -0
  344. package/dist/src/positions/index.d.ts +10 -0
  345. package/dist/src/positions/index.d.ts.map +1 -0
  346. package/dist/src/positions/index.js +9 -0
  347. package/dist/src/positions/index.js.map +1 -0
  348. package/dist/src/positions/types.d.ts +208 -0
  349. package/dist/src/positions/types.d.ts.map +1 -0
  350. package/dist/src/positions/types.js +10 -0
  351. package/dist/src/positions/types.js.map +1 -0
  352. package/dist/src/registry.d.ts +30 -0
  353. package/dist/src/registry.d.ts.map +1 -1
  354. package/dist/src/registry.js +130 -9
  355. package/dist/src/registry.js.map +1 -1
  356. package/dist/src/styles/annotations.d.ts +132 -0
  357. package/dist/src/styles/annotations.d.ts.map +1 -0
  358. package/dist/src/styles/annotations.js +320 -0
  359. package/dist/src/styles/annotations.js.map +1 -0
  360. package/dist/src/styles/budget.d.ts +45 -0
  361. package/dist/src/styles/budget.d.ts.map +1 -0
  362. package/dist/src/styles/budget.js +58 -0
  363. package/dist/src/styles/budget.js.map +1 -0
  364. package/dist/src/styles/canon/bends.d.ts +32 -0
  365. package/dist/src/styles/canon/bends.d.ts.map +1 -0
  366. package/dist/src/styles/canon/bends.js +74 -0
  367. package/dist/src/styles/canon/bends.js.map +1 -0
  368. package/dist/src/styles/canon/color-tables.d.ts +22 -0
  369. package/dist/src/styles/canon/color-tables.d.ts.map +1 -0
  370. package/dist/src/styles/canon/color-tables.js +369 -0
  371. package/dist/src/styles/canon/color-tables.js.map +1 -0
  372. package/dist/src/styles/canon/color.d.ts +66 -0
  373. package/dist/src/styles/canon/color.d.ts.map +1 -0
  374. package/dist/src/styles/canon/color.js +548 -0
  375. package/dist/src/styles/canon/color.js.map +1 -0
  376. package/dist/src/styles/canon/enums.d.ts +25 -0
  377. package/dist/src/styles/canon/enums.d.ts.map +1 -0
  378. package/dist/src/styles/canon/enums.js +187 -0
  379. package/dist/src/styles/canon/enums.js.map +1 -0
  380. package/dist/src/styles/canon/graphviz.d.ts +48 -0
  381. package/dist/src/styles/canon/graphviz.d.ts.map +1 -0
  382. package/dist/src/styles/canon/graphviz.js +160 -0
  383. package/dist/src/styles/canon/graphviz.js.map +1 -0
  384. package/dist/src/styles/canon/index.d.ts +13 -0
  385. package/dist/src/styles/canon/index.d.ts.map +1 -0
  386. package/dist/src/styles/canon/index.js +13 -0
  387. package/dist/src/styles/canon/index.js.map +1 -0
  388. package/dist/src/styles/canon/number.d.ts +59 -0
  389. package/dist/src/styles/canon/number.d.ts.map +1 -0
  390. package/dist/src/styles/canon/number.js +142 -0
  391. package/dist/src/styles/canon/number.js.map +1 -0
  392. package/dist/src/styles/canon/site.d.ts +51 -0
  393. package/dist/src/styles/canon/site.d.ts.map +1 -0
  394. package/dist/src/styles/canon/site.js +63 -0
  395. package/dist/src/styles/canon/site.js.map +1 -0
  396. package/dist/src/styles/canon/text.d.ts +61 -0
  397. package/dist/src/styles/canon/text.d.ts.map +1 -0
  398. package/dist/src/styles/canon/text.js +199 -0
  399. package/dist/src/styles/canon/text.js.map +1 -0
  400. package/dist/src/styles/codes.d.ts +95 -0
  401. package/dist/src/styles/codes.d.ts.map +1 -0
  402. package/dist/src/styles/codes.js +97 -0
  403. package/dist/src/styles/codes.js.map +1 -0
  404. package/dist/src/styles/cyjs/cascade.d.ts +134 -0
  405. package/dist/src/styles/cyjs/cascade.d.ts.map +1 -0
  406. package/dist/src/styles/cyjs/cascade.js +2252 -0
  407. package/dist/src/styles/cyjs/cascade.js.map +1 -0
  408. package/dist/src/styles/cyjs/defaults-table.d.ts +25 -0
  409. package/dist/src/styles/cyjs/defaults-table.d.ts.map +1 -0
  410. package/dist/src/styles/cyjs/defaults-table.js +76 -0
  411. package/dist/src/styles/cyjs/defaults-table.js.map +1 -0
  412. package/dist/src/styles/cyjs/emit.d.ts +82 -0
  413. package/dist/src/styles/cyjs/emit.d.ts.map +1 -0
  414. package/dist/src/styles/cyjs/emit.js +791 -0
  415. package/dist/src/styles/cyjs/emit.js.map +1 -0
  416. package/dist/src/styles/cyjs/index.d.ts +13 -0
  417. package/dist/src/styles/cyjs/index.d.ts.map +1 -0
  418. package/dist/src/styles/cyjs/index.js +13 -0
  419. package/dist/src/styles/cyjs/index.js.map +1 -0
  420. package/dist/src/styles/cyjs/live.d.ts +46 -0
  421. package/dist/src/styles/cyjs/live.d.ts.map +1 -0
  422. package/dist/src/styles/cyjs/live.js +108 -0
  423. package/dist/src/styles/cyjs/live.js.map +1 -0
  424. package/dist/src/styles/cyjs/mappers.d.ts +147 -0
  425. package/dist/src/styles/cyjs/mappers.d.ts.map +1 -0
  426. package/dist/src/styles/cyjs/mappers.js +744 -0
  427. package/dist/src/styles/cyjs/mappers.js.map +1 -0
  428. package/dist/src/styles/cyjs/selector.d.ts +113 -0
  429. package/dist/src/styles/cyjs/selector.d.ts.map +1 -0
  430. package/dist/src/styles/cyjs/selector.js +611 -0
  431. package/dist/src/styles/cyjs/selector.js.map +1 -0
  432. package/dist/src/styles/cyjs/stylesheet-text.d.ts +37 -0
  433. package/dist/src/styles/cyjs/stylesheet-text.d.ts.map +1 -0
  434. package/dist/src/styles/cyjs/stylesheet-text.js +181 -0
  435. package/dist/src/styles/cyjs/stylesheet-text.js.map +1 -0
  436. package/dist/src/styles/cytoscape/emit.d.ts +125 -0
  437. package/dist/src/styles/cytoscape/emit.d.ts.map +1 -0
  438. package/dist/src/styles/cytoscape/emit.js +1025 -0
  439. package/dist/src/styles/cytoscape/emit.js.map +1 -0
  440. package/dist/src/styles/cytoscape/index.d.ts +10 -0
  441. package/dist/src/styles/cytoscape/index.d.ts.map +1 -0
  442. package/dist/src/styles/cytoscape/index.js +10 -0
  443. package/dist/src/styles/cytoscape/index.js.map +1 -0
  444. package/dist/src/styles/cytoscape/lexicon.d.ts +49 -0
  445. package/dist/src/styles/cytoscape/lexicon.d.ts.map +1 -0
  446. package/dist/src/styles/cytoscape/lexicon.js +605 -0
  447. package/dist/src/styles/cytoscape/lexicon.js.map +1 -0
  448. package/dist/src/styles/cytoscape/mappings.d.ts +171 -0
  449. package/dist/src/styles/cytoscape/mappings.d.ts.map +1 -0
  450. package/dist/src/styles/cytoscape/mappings.js +460 -0
  451. package/dist/src/styles/cytoscape/mappings.js.map +1 -0
  452. package/dist/src/styles/cytoscape/resolve.d.ts +137 -0
  453. package/dist/src/styles/cytoscape/resolve.d.ts.map +1 -0
  454. package/dist/src/styles/cytoscape/resolve.js +381 -0
  455. package/dist/src/styles/cytoscape/resolve.js.map +1 -0
  456. package/dist/src/styles/cytoscape/translate.d.ts +50 -0
  457. package/dist/src/styles/cytoscape/translate.d.ts.map +1 -0
  458. package/dist/src/styles/cytoscape/translate.js +156 -0
  459. package/dist/src/styles/cytoscape/translate.js.map +1 -0
  460. package/dist/src/styles/defaults.d.ts +90 -0
  461. package/dist/src/styles/defaults.d.ts.map +1 -0
  462. package/dist/src/styles/defaults.js +414 -0
  463. package/dist/src/styles/defaults.js.map +1 -0
  464. package/dist/src/styles/index.d.ts +15 -0
  465. package/dist/src/styles/index.d.ts.map +1 -0
  466. package/dist/src/styles/index.js +14 -0
  467. package/dist/src/styles/index.js.map +1 -0
  468. package/dist/src/styles/internal.d.ts +17 -0
  469. package/dist/src/styles/internal.d.ts.map +1 -0
  470. package/dist/src/styles/internal.js +16 -0
  471. package/dist/src/styles/internal.js.map +1 -0
  472. package/dist/src/styles/io/approximations.d.ts +24 -0
  473. package/dist/src/styles/io/approximations.d.ts.map +1 -0
  474. package/dist/src/styles/io/approximations.js +51 -0
  475. package/dist/src/styles/io/approximations.js.map +1 -0
  476. package/dist/src/styles/io/block.d.ts +166 -0
  477. package/dist/src/styles/io/block.d.ts.map +1 -0
  478. package/dist/src/styles/io/block.js +930 -0
  479. package/dist/src/styles/io/block.js.map +1 -0
  480. package/dist/src/styles/io/generic.d.ts +88 -0
  481. package/dist/src/styles/io/generic.d.ts.map +1 -0
  482. package/dist/src/styles/io/generic.js +516 -0
  483. package/dist/src/styles/io/generic.js.map +1 -0
  484. package/dist/src/styles/io/index.d.ts +8 -0
  485. package/dist/src/styles/io/index.d.ts.map +1 -0
  486. package/dist/src/styles/io/index.js +7 -0
  487. package/dist/src/styles/io/index.js.map +1 -0
  488. package/dist/src/styles/io/writer.d.ts +89 -0
  489. package/dist/src/styles/io/writer.d.ts.map +1 -0
  490. package/dist/src/styles/io/writer.js +626 -0
  491. package/dist/src/styles/io/writer.js.map +1 -0
  492. package/dist/src/styles/notes.d.ts +47 -0
  493. package/dist/src/styles/notes.d.ts.map +1 -0
  494. package/dist/src/styles/notes.js +140 -0
  495. package/dist/src/styles/notes.js.map +1 -0
  496. package/dist/src/styles/plan/classes.d.ts +31 -0
  497. package/dist/src/styles/plan/classes.d.ts.map +1 -0
  498. package/dist/src/styles/plan/classes.js +139 -0
  499. package/dist/src/styles/plan/classes.js.map +1 -0
  500. package/dist/src/styles/plan/continuous.d.ts +32 -0
  501. package/dist/src/styles/plan/continuous.d.ts.map +1 -0
  502. package/dist/src/styles/plan/continuous.js +165 -0
  503. package/dist/src/styles/plan/continuous.js.map +1 -0
  504. package/dist/src/styles/plan/discrete.d.ts +39 -0
  505. package/dist/src/styles/plan/discrete.d.ts.map +1 -0
  506. package/dist/src/styles/plan/discrete.js +127 -0
  507. package/dist/src/styles/plan/discrete.js.map +1 -0
  508. package/dist/src/styles/plan/families.d.ts +31 -0
  509. package/dist/src/styles/plan/families.d.ts.map +1 -0
  510. package/dist/src/styles/plan/families.js +95 -0
  511. package/dist/src/styles/plan/families.js.map +1 -0
  512. package/dist/src/styles/plan/features.d.ts +58 -0
  513. package/dist/src/styles/plan/features.d.ts.map +1 -0
  514. package/dist/src/styles/plan/features.js +185 -0
  515. package/dist/src/styles/plan/features.js.map +1 -0
  516. package/dist/src/styles/plan/index.d.ts +7 -0
  517. package/dist/src/styles/plan/index.d.ts.map +1 -0
  518. package/dist/src/styles/plan/index.js +7 -0
  519. package/dist/src/styles/plan/index.js.map +1 -0
  520. package/dist/src/styles/plan/plan.d.ts +71 -0
  521. package/dist/src/styles/plan/plan.d.ts.map +1 -0
  522. package/dist/src/styles/plan/plan.js +893 -0
  523. package/dist/src/styles/plan/plan.js.map +1 -0
  524. package/dist/src/styles/plan/resolve.d.ts +137 -0
  525. package/dist/src/styles/plan/resolve.d.ts.map +1 -0
  526. package/dist/src/styles/plan/resolve.js +587 -0
  527. package/dist/src/styles/plan/resolve.js.map +1 -0
  528. package/dist/src/styles/types.d.ts +702 -0
  529. package/dist/src/styles/types.d.ts.map +1 -0
  530. package/dist/src/styles/types.js +10 -0
  531. package/dist/src/styles/types.js.map +1 -0
  532. package/dist/src/styles/vocabulary.d.ts +41 -0
  533. package/dist/src/styles/vocabulary.d.ts.map +1 -0
  534. package/dist/src/styles/vocabulary.js +140 -0
  535. package/dist/src/styles/vocabulary.js.map +1 -0
  536. package/dist/src/types.d.ts +64 -1
  537. package/dist/src/types.d.ts.map +1 -1
  538. package/dist/src/types.js.map +1 -1
  539. package/dist/styles.d.ts +1 -0
  540. package/dist/styles.js +322 -0
  541. package/dist/styles.js.map +1 -0
  542. package/dist/xgmml.js +5 -4
  543. package/dist/xgmml.js.map +1 -1
  544. package/package.json +13 -2
  545. package/src/common/codes.ts +7 -0
  546. package/src/common/escape.ts +5 -2
  547. package/src/common/export.ts +179 -140
  548. package/src/common/input.ts +8 -4
  549. package/src/common/json-elements.ts +169 -38
  550. package/src/common/notes.ts +94 -0
  551. package/src/common/report.ts +41 -1
  552. package/src/common/units.ts +42 -0
  553. package/src/common/xml.ts +45 -5
  554. package/src/formats/cx/exporter.ts +95 -11
  555. package/src/formats/cx/importer.ts +126 -90
  556. package/src/formats/cx/styles.ts +304 -0
  557. package/src/formats/cx2/exporter.ts +234 -77
  558. package/src/formats/cx2/importer.ts +19 -1
  559. package/src/formats/cx2/styles.ts +29 -0
  560. package/src/formats/cys/constants.ts +3 -0
  561. package/src/formats/cys/exporter.ts +246 -60
  562. package/src/formats/cys/importer.ts +125 -9
  563. package/src/formats/cys/session.ts +6 -0
  564. package/src/formats/cys/styles.ts +254 -0
  565. package/src/formats/cys/tables.ts +117 -48
  566. package/src/formats/dot/exporter.ts +262 -126
  567. package/src/formats/dot/importer.ts +111 -20
  568. package/src/formats/dot/index.ts +1 -0
  569. package/src/formats/dot/positions.ts +77 -0
  570. package/src/formats/dot/styles.ts +1755 -0
  571. package/src/formats/gexf/exporter.ts +27 -11
  572. package/src/formats/gexf/importer.ts +308 -158
  573. package/src/formats/gexf/index.ts +10 -0
  574. package/src/formats/gexf/schema.ts +5 -0
  575. package/src/formats/gexf/styles.ts +719 -0
  576. package/src/formats/gml/exporter.ts +33 -14
  577. package/src/formats/gml/importer.ts +4 -0
  578. package/src/formats/gml/index.ts +2 -1
  579. package/src/formats/gml/positions.ts +52 -0
  580. package/src/formats/gml/styles.ts +1248 -0
  581. package/src/formats/gml/syntax.ts +173 -62
  582. package/src/formats/graphml/constants.ts +5 -0
  583. package/src/formats/graphml/exporter.ts +163 -71
  584. package/src/formats/graphml/importer.ts +119 -45
  585. package/src/formats/graphml/index.ts +1 -0
  586. package/src/formats/graphml/styles-plain.ts +236 -0
  587. package/src/formats/graphml/styles-yed.ts +1221 -0
  588. package/src/formats/graphml/styles-yfiles3.ts +573 -0
  589. package/src/formats/graphml/tree.ts +36 -20
  590. package/src/formats/graphml/yfiles.ts +89 -13
  591. package/src/formats/json/exporter.ts +76 -26
  592. package/src/formats/json/importer.ts +541 -322
  593. package/src/formats/json/positions.ts +50 -0
  594. package/src/formats/json/styles.ts +2082 -0
  595. package/src/formats/neo4j/exporter.ts +2 -1
  596. package/src/formats/pajek/exporter.ts +195 -106
  597. package/src/formats/pajek/importer.ts +78 -9
  598. package/src/formats/pajek/index.ts +2 -0
  599. package/src/formats/pajek/styles.ts +685 -0
  600. package/src/formats/pajek/syntax.ts +7 -0
  601. package/src/formats/xgmml/columns.ts +39 -9
  602. package/src/formats/xgmml/constants.ts +3 -0
  603. package/src/formats/xgmml/document.ts +54 -12
  604. package/src/formats/xgmml/emit.ts +11 -6
  605. package/src/formats/xgmml/exporter.ts +32 -13
  606. package/src/formats/xgmml/importer.ts +43 -29
  607. package/src/formats/xgmml/styles.ts +400 -0
  608. package/src/index.ts +16 -0
  609. package/src/positions/codes.ts +32 -0
  610. package/src/positions/convert.ts +123 -0
  611. package/src/positions/extract.ts +619 -0
  612. package/src/positions/index.ts +23 -0
  613. package/src/positions/types.ts +221 -0
  614. package/src/registry.ts +179 -8
  615. package/src/styles/annotations.ts +478 -0
  616. package/src/styles/budget.ts +60 -0
  617. package/src/styles/canon/bends.ts +84 -0
  618. package/src/styles/canon/color-tables.ts +386 -0
  619. package/src/styles/canon/color.ts +624 -0
  620. package/src/styles/canon/enums.ts +211 -0
  621. package/src/styles/canon/graphviz.ts +189 -0
  622. package/src/styles/canon/index.ts +13 -0
  623. package/src/styles/canon/number.ts +152 -0
  624. package/src/styles/canon/site.ts +87 -0
  625. package/src/styles/canon/text.ts +221 -0
  626. package/src/styles/codes.ts +104 -0
  627. package/src/styles/cyjs/cascade.ts +2670 -0
  628. package/src/styles/cyjs/defaults-table.ts +82 -0
  629. package/src/styles/cyjs/emit.ts +952 -0
  630. package/src/styles/cyjs/index.ts +37 -0
  631. package/src/styles/cyjs/live.ts +147 -0
  632. package/src/styles/cyjs/mappers.ts +879 -0
  633. package/src/styles/cyjs/selector.ts +778 -0
  634. package/src/styles/cyjs/stylesheet-text.ts +214 -0
  635. package/src/styles/cytoscape/emit.ts +1264 -0
  636. package/src/styles/cytoscape/index.ts +40 -0
  637. package/src/styles/cytoscape/lexicon.ts +658 -0
  638. package/src/styles/cytoscape/mappings.ts +603 -0
  639. package/src/styles/cytoscape/resolve.ts +521 -0
  640. package/src/styles/cytoscape/translate.ts +237 -0
  641. package/src/styles/defaults.ts +504 -0
  642. package/src/styles/index.ts +57 -0
  643. package/src/styles/internal.ts +17 -0
  644. package/src/styles/io/approximations.ts +47 -0
  645. package/src/styles/io/block.ts +1138 -0
  646. package/src/styles/io/generic.ts +687 -0
  647. package/src/styles/io/index.ts +18 -0
  648. package/src/styles/io/writer.ts +780 -0
  649. package/src/styles/notes.ts +165 -0
  650. package/src/styles/plan/classes.ts +166 -0
  651. package/src/styles/plan/continuous.ts +218 -0
  652. package/src/styles/plan/discrete.ts +165 -0
  653. package/src/styles/plan/families.ts +115 -0
  654. package/src/styles/plan/features.ts +234 -0
  655. package/src/styles/plan/index.ts +7 -0
  656. package/src/styles/plan/plan.ts +1129 -0
  657. package/src/styles/plan/resolve.ts +685 -0
  658. package/src/styles/types.ts +814 -0
  659. package/src/styles/vocabulary.ts +172 -0
  660. package/src/types.ts +76 -1
  661. package/dist/chunks/constants-fT3E8JYY.js.map +0 -1
  662. package/dist/chunks/escape-B62ZGM_a.js.map +0 -1
  663. package/dist/chunks/export-rKLeGQMo.js.map +0 -1
  664. package/dist/chunks/exporter-8hLNemFk.js.map +0 -1
  665. package/dist/chunks/exporter-BWwEuOPT.js.map +0 -1
  666. package/dist/chunks/exporter-RVtXVetw.js.map +0 -1
  667. package/dist/chunks/importer-BbNb7eNg.js.map +0 -1
  668. package/dist/chunks/importer-C98dpSH4.js.map +0 -1
  669. package/dist/chunks/importer-D794XUTe.js.map +0 -1
  670. package/dist/chunks/importer-Dz7tI8Sa.js.map +0 -1
  671. package/dist/chunks/importer-Ht7LNg8L.js.map +0 -1
  672. package/dist/chunks/importer-WrGRZb0N.js.map +0 -1
  673. package/dist/chunks/importer-xZ-3suY1.js.map +0 -1
  674. package/dist/chunks/json-elements-DKSqpNW1.js.map +0 -1
@@ -0,0 +1,1496 @@
1
+ /**
2
+ * DOT styles: every node's, cluster's and edge's attributes read the way Graphviz draws them, with Graphviz's
3
+ * defaults beneath, and a graph's styles written back as Graphviz attributes.
4
+ *
5
+ * Reading follows Graphviz's own rules, measured against `dot` 2.43:
6
+ *
7
+ * - Labels: a node with no `label` shows its name (`\N`), as one tool-default rule over a style column holding each
8
+ * node's name. Escapes are applied in Graphviz's two passes; an HTML label shows its text, a record label its
9
+ * fields, and a `point` draws no label.
10
+ * - Fills: `point` is always filled; `filled` and `radial` fill any shape, `striped` only boxes, `wedged` only
11
+ * ellipses; an empty `fillcolor` falls back to `color`, then light gray.
12
+ * - Colors through Graphviz's color rules: a `colorscheme` applies to its own object only, an unknown color is black,
13
+ * `none` and `invis` are transparent, and a color list is read as Graphviz draws it for that use.
14
+ * - Outlines: `plain`, `plaintext` and `none` draw none. `setlinewidth(n)` is a pen width, `bold` a width of 2.
15
+ * - Sizes: exact with `fixedsize`, an empty label or a `point`; otherwise minimums Graphviz grows to fit the label,
16
+ * carried and noted as `W_STYLE_SIZE_MINIMUM`.
17
+ * - Arrows per `dir`, `arrowhead` and `arrowtail`, with the graph's kind deciding the default: a target arrow on
18
+ * directed edges, as one tool-default rule.
19
+ * - A cluster's container node holds the cluster's attributes: its fill (`bgcolor`, or the fill when `filled`) and
20
+ * outline (`pencolor`, else `color`) are the cluster's, it is drawn as a rectangle, and it has no label by default.
21
+ * Cluster membership is offered to the planner, so clusters whose nodes share a fill become one rule.
22
+ *
23
+ * Writing: Graphviz attributes from what graphty draws (`fillcolor` with `style=filled`, `width` and `height` with
24
+ * `fixedsize=true`, `penwidth`, `style`, arrows with `dir`, font attributes, graph `bgcolor`) replace every style
25
+ * attribute the reader reads. The `graphty.styles` block is the quoted graph attribute `"graphty.styles"`, and the
26
+ * per-element values its rules read are quoted `"graphty.style.<target>.<key>"` attributes.
27
+ */
28
+ import { isWritableDotText } from "../../common/escape.js";
29
+ import { inferTextDtype } from "../../common/text.js";
30
+ import { canonColor, colorOpacity } from "../../styles/canon/color.js";
31
+ import { canonEnum } from "../../styles/canon/enums.js";
32
+ import { canonNumber, DOT_POINTS_PER_INCH, num6 } from "../../styles/canon/number.js";
33
+ import { MAX_STYLE_VALUE_LENGTH, noteAt } from "../../styles/canon/site.js";
34
+ import { dotLabelText, htmlLabelText, recordLabelText } from "../../styles/canon/text.js";
35
+ import { STYLE_ISSUE } from "../../styles/codes.js";
36
+ import { toolDefaults } from "../../styles/defaults.js";
37
+ import { StyleApproximations } from "../../styles/io/approximations.js";
38
+ import { blockStyles, parseStyleBlock, STYLE_BLOCK_NAME, styleBlockLosses, writeStyleBlock, } from "../../styles/io/block.js";
39
+ import { sharedInExportCall } from "../../styles/io/writer.js";
40
+ import { StyleNotes } from "../../styles/notes.js";
41
+ import { styleFingerprint } from "../../styles/plan/features.js";
42
+ import { plainCell, resolveStyles } from "../../styles/plan/resolve.js";
43
+ import { valueColumns } from "../graphml/styles-yed.js";
44
+ import { CLUSTER_COLUMN, LABEL_ATTRIBUTE } from "./names.js";
45
+ /**
46
+ * The graph attribute holding the `graphty.styles` block of a DOT file graphty wrote.
47
+ * @category Built-in formats
48
+ */
49
+ export { STYLE_BLOCK_NAME as DOT_STYLE_BLOCK_ATTRIBUTE } from "../../styles/io/block.js";
50
+ /**
51
+ * What the DOT writer holds natively: Graphviz's node, cluster and edge attributes, and the graph's `bgcolor`. The
52
+ * `graphty.styles` block and the per-element values its rules read are quoted attributes.
53
+ * @category Built-in formats
54
+ */
55
+ export const DOT_STYLE_CAPABILITIES = Object.freeze({
56
+ node: Object.freeze([
57
+ "fill",
58
+ "fillOpacity",
59
+ "borderColor",
60
+ "borderWidth",
61
+ "borderStyle",
62
+ "shape",
63
+ "width",
64
+ "height",
65
+ "label",
66
+ "labelColor",
67
+ "labelSize",
68
+ "labelFont",
69
+ "tooltip",
70
+ "visible",
71
+ ]),
72
+ edge: Object.freeze([
73
+ "color",
74
+ "opacity",
75
+ "width",
76
+ "lineStyle",
77
+ "sourceArrow",
78
+ "targetArrow",
79
+ "arrowSize",
80
+ "label",
81
+ "sourceLabel",
82
+ "targetLabel",
83
+ "labelColor",
84
+ "labelSize",
85
+ "labelFont",
86
+ "tooltip",
87
+ "visible",
88
+ ]),
89
+ graph: Object.freeze(["background"]),
90
+ block: "native",
91
+ });
92
+ /** The style column holding the name each node shows when it has no label (Graphviz's `\N`). */
93
+ const NAME_KEY = "name";
94
+ /** The style column marking directed edges, which Graphviz draws with an arrow at the target. */
95
+ const DIRECTED_KEY = "directed";
96
+ /** The style column marking a cluster's container node. */
97
+ const CLUSTER_KEY = "cluster";
98
+ /** Every node and cluster attribute the reader reads; the writer replaces them all. */
99
+ const NODE_ATTRIBUTES = [
100
+ "colorscheme",
101
+ "color",
102
+ "fillcolor",
103
+ "bgcolor",
104
+ "pencolor",
105
+ "style",
106
+ "shape",
107
+ "width",
108
+ "height",
109
+ "fixedsize",
110
+ "regular",
111
+ "penwidth",
112
+ "fontname",
113
+ "fontsize",
114
+ "fontcolor",
115
+ "tooltip",
116
+ ];
117
+ /** Every edge attribute the reader reads; the writer replaces them all. */
118
+ const EDGE_ATTRIBUTES = [
119
+ "colorscheme",
120
+ "color",
121
+ "style",
122
+ "penwidth",
123
+ "fontname",
124
+ "fontsize",
125
+ "fontcolor",
126
+ "tooltip",
127
+ "edgetooltip",
128
+ "arrowhead",
129
+ "arrowtail",
130
+ "dir",
131
+ "arrowsize",
132
+ "headlabel",
133
+ "taillabel",
134
+ ];
135
+ /** Attributes the reader notes as not carried. */
136
+ const NOTED_ATTRIBUTES = ["peripheries", "xlabel"];
137
+ const CIRCLE_LIKE = new Set(["circle", "doublecircle", "mcircle", "square", "msquare"]);
138
+ const BOX_LIKE = new Set(["box", "rect", "rectangle", "square"]);
139
+ const ELLIPSE_LIKE = new Set(["", "ellipse", "oval", "circle"]);
140
+ const NO_OUTLINE = new Set(["plain", "plaintext", "none"]);
141
+ const RECORD = new Set(["record", "mrecord"]);
142
+ const TRUE_TEXTS = new Set(["true", "yes", "shape"]);
143
+ /** A point's size when the file gives none: 0.05 inch. */
144
+ const POINT_SIZE = 0.05;
145
+ /** The size in inches Graphviz gives a circle, a square or a `regular` node that states none. */
146
+ const REGULAR_SIZE = 0.5;
147
+ /**
148
+ * Whether a text is an HTML string (balanced angle brackets around the whole text), which graph-io keeps with its
149
+ * brackets and DOT writes bare as `<...>`.
150
+ * @param text - the text
151
+ * @returns true for an HTML string
152
+ */
153
+ export function isHtmlString(text) {
154
+ if (text.length < 2 || !text.startsWith("<") || !text.endsWith(">")) {
155
+ return false;
156
+ }
157
+ let depth = 0;
158
+ for (let i = 0; i < text.length; i++) {
159
+ const c = text.codePointAt(i);
160
+ if (c === 0x3c) {
161
+ depth++;
162
+ }
163
+ else if (c === 0x3e) {
164
+ depth--;
165
+ if (depth === 0 && i !== text.length - 1) {
166
+ return false;
167
+ }
168
+ }
169
+ }
170
+ return depth === 0;
171
+ }
172
+ /**
173
+ * The value of an attribute cell as text, or null when it is unset.
174
+ * @param table - the node, edge or graph table
175
+ * @param name - the attribute
176
+ * @param i - the row
177
+ * @returns the text
178
+ */
179
+ function cellText(table, name, i) {
180
+ if (!table.has(name)) {
181
+ return null;
182
+ }
183
+ const v = plainCell(table.value(name, i));
184
+ if (typeof v === "string") {
185
+ return v;
186
+ }
187
+ return typeof v === "number" || typeof v === "boolean" ? String(v) : null;
188
+ }
189
+ /**
190
+ * A text, or null when it is empty.
191
+ * @param v - the text
192
+ * @returns the text
193
+ */
194
+ function nonEmpty(v) {
195
+ return v === null || v === "" ? null : v;
196
+ }
197
+ /**
198
+ * The tokens of a `style` value: split on commas, lowercased, blanks before a token skipped (blanks after it make it
199
+ * a token Graphviz does not know, so they are kept).
200
+ * @param raw - the value
201
+ * @param at - where it was read
202
+ * @returns the tokens
203
+ */
204
+ function styleTokens(raw, at) {
205
+ if (raw === null) {
206
+ return [];
207
+ }
208
+ if (raw.length > MAX_STYLE_VALUE_LENGTH) {
209
+ noteAt(at, STYLE_ISSUE.VALUE_TOO_LONG, `${String(raw.length)} characters`);
210
+ return [];
211
+ }
212
+ return raw
213
+ .toLowerCase()
214
+ .split(",")
215
+ .map((t) => t.replace(/^[ \t\r\n]+/, ""))
216
+ .filter((t) => t !== "");
217
+ }
218
+ /**
219
+ * The pen width a style's `setlinewidth(n)` gives, or null.
220
+ * @param tokens - the style's tokens
221
+ * @returns the width's text
222
+ */
223
+ function setLineWidth(tokens) {
224
+ for (const t of tokens) {
225
+ if (t.startsWith("setlinewidth(") && t.endsWith(")")) {
226
+ return t.slice("setlinewidth(".length, -1).trim();
227
+ }
228
+ }
229
+ return null;
230
+ }
231
+ /**
232
+ * Whether an attribute is one of Graphviz's true spellings (`true`, `yes`, a non-zero number; `fixedsize` also
233
+ * takes `shape`).
234
+ * @param raw - the value
235
+ * @returns true when set
236
+ */
237
+ function isTrue(raw) {
238
+ if (raw === null) {
239
+ return false;
240
+ }
241
+ const t = raw.trim().toLowerCase();
242
+ return TRUE_TEXTS.has(t) || (/^-?\d+$/.test(t) && Number(t) !== 0);
243
+ }
244
+ /**
245
+ * The text a label draws: an HTML label's text (also for a quoted label equal to an HTML label's content elsewhere
246
+ * in the file, since Graphviz 2.43 marks HTML on a pooled string), a record's fields, the escapes applied.
247
+ * @param raw - the label as the file gives it
248
+ * @param names - what the escapes stand for
249
+ * @param html - the file's HTML strings
250
+ * @param record - whether the node is a record
251
+ * @param at - where it was read
252
+ * @returns the text
253
+ */
254
+ function labelText(raw, names, html, record, at) {
255
+ if (isHtmlString(raw)) {
256
+ return htmlLabelText(raw, at);
257
+ }
258
+ if (html.has(`<${raw}>`)) {
259
+ return htmlLabelText(`<${raw}>`, at);
260
+ }
261
+ return dotLabelText(record ? recordLabelText(raw, at) : raw, names, at);
262
+ }
263
+ /**
264
+ * Reads a color into a property and its alpha into the property's opacity.
265
+ * @param raw - the color
266
+ * @param scheme - the object's color scheme
267
+ * @param use - how Graphviz draws it
268
+ * @param property - the color property
269
+ * @param opacity - its opacity property, or null when the alpha is dropped
270
+ * @param put - receives the values
271
+ * @param at - the sites
272
+ * @returns the alpha, or null when the color is absent
273
+ */
274
+ function readColor(raw, scheme, use, property, opacity, put, at) {
275
+ if (raw === null) {
276
+ return null;
277
+ }
278
+ const c = canonColor(raw, "graphviz", at(property), { scheme, use });
279
+ if (c === null) {
280
+ return null;
281
+ }
282
+ put(property, c.color);
283
+ if (opacity !== null) {
284
+ put(opacity, c.alpha);
285
+ }
286
+ else if (c.alpha > 0 || property !== "borderColor") {
287
+ // a transparent outline is no outline (the caller sets its width to 0); any other alpha is dropped, noted
288
+ colorOpacity(c, null, at(property), false);
289
+ }
290
+ return c.alpha;
291
+ }
292
+ /**
293
+ * A length in points as the shortest inches (at least 6 significant digits) that read back as the same length.
294
+ * @param points - the length
295
+ * @returns the inches
296
+ */
297
+ function inchesOf(points) {
298
+ const want = num6(points);
299
+ for (let digits = 6; digits < 17; digits++) {
300
+ const inches = Number((points / DOT_POINTS_PER_INCH).toPrecision(digits));
301
+ if (num6(inches * DOT_POINTS_PER_INCH) === want) {
302
+ return inches + 0;
303
+ }
304
+ }
305
+ return points / DOT_POINTS_PER_INCH;
306
+ }
307
+ /**
308
+ * A size attribute in inches, or null with a note when it is not a non-negative number.
309
+ * @param raw - the value
310
+ * @param at - where it was read
311
+ * @returns the inches
312
+ */
313
+ function inches(raw, at) {
314
+ if (raw === null) {
315
+ return null;
316
+ }
317
+ const v = raw.trim() === "" ? Number.NaN : Number(raw);
318
+ if (!Number.isFinite(v) || v < 0) {
319
+ noteAt(at, STYLE_ISSUE.BAD_VALUE, raw.slice(0, MAX_STYLE_VALUE_LENGTH));
320
+ return null;
321
+ }
322
+ return v;
323
+ }
324
+ /**
325
+ * Reads the attributes every element shares: font, tooltip, visibility.
326
+ * @param get - the element's attributes
327
+ * @param tokens - its style tokens
328
+ * @param scheme - its color scheme
329
+ * @param names - what the tooltip's escapes stand for
330
+ * @param put - receives the values
331
+ * @param at - the sites
332
+ */
333
+ function readCommon(get, tokens, scheme, names, put, at) {
334
+ if (tokens.includes("invis")) {
335
+ put("visible", false);
336
+ }
337
+ readColor(nonEmpty(get("fontcolor")), scheme, "line", "labelColor", null, put, at);
338
+ const size = get("fontsize");
339
+ put("labelSize", size === null ? null : canonNumber(size, at("labelSize")));
340
+ const font = get("fontname");
341
+ if (font !== null && font.length <= MAX_STYLE_VALUE_LENGTH) {
342
+ put("labelFont", font);
343
+ }
344
+ const tip = get("tooltip") ?? get("edgetooltip");
345
+ if (tip !== null) {
346
+ put("tooltip", dotLabelText(tip, names, at("tooltip")));
347
+ }
348
+ }
349
+ /**
350
+ * Reads one node, or one cluster's container, the way Graphviz draws it.
351
+ * @param get - its attributes
352
+ * @param cx - its name and kind
353
+ * @param put - receives the values
354
+ * @param at - the sites
355
+ */
356
+ function readNode(get, cx, put, at) {
357
+ const tokens = styleTokens(get("style"), at("visible"));
358
+ const shapeRaw = nonEmpty(get("shape"));
359
+ const shape = (shapeRaw ?? "").toLowerCase();
360
+ const n = {
361
+ get,
362
+ cx,
363
+ put,
364
+ at,
365
+ tokens,
366
+ shapeRaw,
367
+ shape,
368
+ point: !cx.cluster && shape === "point",
369
+ scheme: get("colorscheme"),
370
+ color: nonEmpty(get("color")),
371
+ };
372
+ // in a node's label \E stands for nothing (measured with dot 2.43); \T and \H stand for their letters
373
+ const names = cx.cluster ? { graph: cx.name } : { node: cx.name, graph: cx.graph, edge: "" };
374
+ readCommon(get, tokens, n.scheme, names, put, at);
375
+ if (cx.cluster) {
376
+ readClusterFill(n);
377
+ }
378
+ else {
379
+ readNodeFill(n);
380
+ }
381
+ readNodeOutline(n);
382
+ readNodeShape(n);
383
+ // label: a node shows its name unless it has a label (the tool-default rule); a cluster and a point show none
384
+ const label = n.point ? null : get(LABEL_ATTRIBUTE);
385
+ if (label !== null) {
386
+ put("label", labelText(label, names, cx.html, RECORD.has(shape), at("label")));
387
+ }
388
+ if (!cx.cluster) {
389
+ readSize(get, shape, n.point, label ?? (n.point ? null : cx.name), put, at);
390
+ }
391
+ if (Number(get("peripheries")) > 1 || nonEmpty(get("xlabel")) !== null) {
392
+ noteAt(at("label"), STYLE_ISSUE.NOT_CARRIED, nonEmpty(get("xlabel")) === null ? "peripheries" : "xlabel");
393
+ }
394
+ }
395
+ /**
396
+ * Reads a cluster's fill: its fill color when filled, else its background.
397
+ * @param n - the cluster
398
+ */
399
+ function readClusterFill(n) {
400
+ const { get, tokens } = n;
401
+ const filled = tokens.includes("filled") || tokens.includes("radial") || tokens.includes("striped");
402
+ const bg = nonEmpty(get("bgcolor"));
403
+ const raw = filled ? (nonEmpty(get("fillcolor")) ?? n.color ?? bg ?? "lightgrey") : bg;
404
+ readColor(raw, n.scheme, "fill", "fill", "fillOpacity", n.put, n.at);
405
+ }
406
+ /**
407
+ * Reads a node's fill: a point is always filled; a striped fill fills a box and a wedged one an ellipse only.
408
+ * @param n - the node
409
+ */
410
+ function readNodeFill(n) {
411
+ const { shape, point } = n;
412
+ const fillcolor = nonEmpty(n.get("fillcolor"));
413
+ const filled = point ||
414
+ n.tokens.some((t) => t === "filled" ||
415
+ t === "radial" ||
416
+ (t === "striped" && BOX_LIKE.has(shape)) ||
417
+ (t === "wedged" && ELLIPSE_LIKE.has(shape)));
418
+ if (filled) {
419
+ const raw = fillcolor ?? n.color ?? (point ? "black" : "lightgrey");
420
+ readColor(raw, n.scheme, "fill", "fill", "fillOpacity", n.put, n.at);
421
+ }
422
+ else if (fillcolor !== null) {
423
+ noteAt(n.at("fill"), STYLE_ISSUE.UNFILLED, fillcolor.slice(0, MAX_STYLE_VALUE_LENGTH));
424
+ }
425
+ }
426
+ /**
427
+ * Reads a node's or cluster's outline: its color, width and dashes.
428
+ * @param n - the node
429
+ */
430
+ function readNodeOutline(n) {
431
+ const { get, cx, put, at, tokens } = n;
432
+ if (!cx.cluster && NO_OUTLINE.has(n.shape)) {
433
+ put("borderWidth", 0);
434
+ }
435
+ else {
436
+ const outline = cx.cluster ? (nonEmpty(get("pencolor")) ?? n.color) : n.color;
437
+ const alpha = readColor(outline, n.scheme, "line", "borderColor", null, put, at);
438
+ const width = nonEmpty(get("penwidth")) ?? setLineWidth(tokens);
439
+ if (alpha === 0) {
440
+ put("borderWidth", 0);
441
+ }
442
+ else if (width !== null) {
443
+ put("borderWidth", penWidth(width, get("penwidth") !== null, at("borderWidth")));
444
+ }
445
+ else if (tokens.includes("bold")) {
446
+ put("borderWidth", 2);
447
+ }
448
+ }
449
+ if (tokens.includes("dashed")) {
450
+ put("borderStyle", "dashed");
451
+ }
452
+ else if (tokens.includes("dotted")) {
453
+ put("borderStyle", "dotted");
454
+ }
455
+ }
456
+ /**
457
+ * Reads a node's or cluster's shape: a cluster is a rectangle, rounded by the `rounded` style as a box is.
458
+ * @param n - the node
459
+ */
460
+ function readNodeShape(n) {
461
+ const rounded = n.tokens.includes("rounded");
462
+ if (n.cx.cluster) {
463
+ n.put("shape", rounded ? "round-rectangle" : "rectangle");
464
+ }
465
+ else if (n.shapeRaw !== null) {
466
+ n.put("shape", rounded && BOX_LIKE.has(n.shape)
467
+ ? "round-rectangle"
468
+ : canonEnum("shape", n.shapeRaw, "graphviz", n.at("shape")));
469
+ }
470
+ }
471
+ /**
472
+ * A pen width as Graphviz draws it: a negative `penwidth` is raised to 0, its minimum, and noted.
473
+ * @param raw - the `penwidth` or `setlinewidth` value
474
+ * @param attribute - whether it is the `penwidth` attribute
475
+ * @param site - where it was read
476
+ * @returns the width, or null when it does not parse
477
+ */
478
+ function penWidth(raw, attribute, site) {
479
+ const value = Number(raw.trim());
480
+ if (attribute && Number.isFinite(value) && value < 0) {
481
+ noteAt(site, STYLE_ISSUE.BAD_VALUE, raw.slice(0, MAX_STYLE_VALUE_LENGTH));
482
+ return 0;
483
+ }
484
+ return canonNumber(raw, site);
485
+ }
486
+ /**
487
+ * Reads a node's width and height: exact where Graphviz draws them exactly (`fixedsize`, an empty label that is not
488
+ * a record's, a point), otherwise minimums it grows to fit the label, noted.
489
+ * @param get - the node's attributes
490
+ * @param shape - its shape, lowercase
491
+ * @param point - whether it is a point
492
+ * @param label - the label it shows, or null
493
+ * @param put - receives the values
494
+ * @param at - the sites
495
+ */
496
+ function readSize(get, shape, point, label, put, at) {
497
+ let w = inches(get("width"), at("width"));
498
+ let h = inches(get("height"), at("height"));
499
+ const same = point || CIRCLE_LIKE.has(shape) || isTrue(get("regular"));
500
+ if (shape === "plain") {
501
+ // plain is plaintext with its width and height forced to 0: exactly the size of its label
502
+ w = h = 0;
503
+ }
504
+ else if (w === null && h === null) {
505
+ if (point) {
506
+ put("width", num6(POINT_SIZE * DOT_POINTS_PER_INCH));
507
+ put("height", num6(POINT_SIZE * DOT_POINTS_PER_INCH));
508
+ return;
509
+ }
510
+ if (!same) {
511
+ return;
512
+ }
513
+ // a regular shape takes the smaller of the default width and height (0.5 inch) for both
514
+ w = h = REGULAR_SIZE;
515
+ }
516
+ let ww = w;
517
+ let hh = h;
518
+ if (point) {
519
+ ww = hh = Math.min(w ?? Infinity, h ?? Infinity);
520
+ }
521
+ else if (same) {
522
+ ww = hh = Math.max(w ?? 0, h ?? 0);
523
+ }
524
+ const exact = point || isTrue(get("fixedsize")) || (label === "" && !RECORD.has(shape));
525
+ if (!exact) {
526
+ noteAt(at("width"), STYLE_ISSUE.SIZE_MINIMUM, "grows to fit its label");
527
+ }
528
+ put("width", ww === null ? null : canonNumber(ww, at("width"), DOT_POINTS_PER_INCH));
529
+ put("height", hh === null ? null : canonNumber(hh, at("height"), DOT_POINTS_PER_INCH));
530
+ }
531
+ /**
532
+ * Reads one edge the way Graphviz draws it.
533
+ * @param get - its attributes
534
+ * @param cx - its names and direction
535
+ * @param put - receives the values
536
+ * @param at - the sites
537
+ */
538
+ function readEdge(get, cx, put, at) {
539
+ const tokens = styleTokens(get("style"), at("visible"));
540
+ const scheme = get("colorscheme");
541
+ readCommon(get, tokens, scheme, cx.names, put, at);
542
+ readColor(nonEmpty(get("color")), scheme, "edge", "color", "opacity", put, at);
543
+ readEdgeLine(get, tokens, put, at);
544
+ readEdgeArrows(get, cx, put, at);
545
+ for (const [attribute, property] of [
546
+ [LABEL_ATTRIBUTE, "label"],
547
+ ["headlabel", "targetLabel"],
548
+ ["taillabel", "sourceLabel"],
549
+ ]) {
550
+ const raw = get(attribute);
551
+ if (raw !== null) {
552
+ put(property, labelText(raw, cx.names, cx.html, false, at(property)));
553
+ }
554
+ }
555
+ if (nonEmpty(get("xlabel")) !== null) {
556
+ noteAt(at("label"), STYLE_ISSUE.NOT_CARRIED, "xlabel");
557
+ }
558
+ }
559
+ /**
560
+ * Reads an edge's line: its width and dashes.
561
+ * @param get - its attributes
562
+ * @param tokens - its style tokens
563
+ * @param put - receives the values
564
+ * @param at - the sites
565
+ */
566
+ function readEdgeLine(get, tokens, put, at) {
567
+ const width = nonEmpty(get("penwidth")) ?? setLineWidth(tokens);
568
+ if (width !== null) {
569
+ put("width", penWidth(width, get("penwidth") !== null, at("width")));
570
+ }
571
+ else if (tokens.includes("bold")) {
572
+ put("width", 2);
573
+ }
574
+ if (tokens.includes("dashed")) {
575
+ put("lineStyle", "dashed");
576
+ }
577
+ else if (tokens.includes("dotted")) {
578
+ put("lineStyle", "dotted");
579
+ }
580
+ else if (tokens.includes("solid") || tokens.includes("bold")) {
581
+ put("lineStyle", "solid");
582
+ }
583
+ else if (tokens.includes("tapered")) {
584
+ put("lineStyle", "other:tapered");
585
+ }
586
+ }
587
+ /**
588
+ * Reads an edge's arrows and their size.
589
+ * @param get - its attributes
590
+ * @param cx - its names and direction
591
+ * @param put - receives the values
592
+ * @param at - the sites
593
+ */
594
+ function readEdgeArrows(get, cx, put, at) {
595
+ const dirRaw = nonEmpty(get("dir"));
596
+ const head = nonEmpty(get("arrowhead"));
597
+ const tail = nonEmpty(get("arrowtail"));
598
+ // with none of these the tool-default rule draws the arrow of the graph's kind
599
+ if (dirRaw !== null || head !== null || tail !== null) {
600
+ const dir = (dirRaw ?? (cx.directed ? "forward" : "none")).trim().toLowerCase();
601
+ const forward = dir === "forward" || dir === "both";
602
+ const back = dir === "back" || dir === "both";
603
+ put("targetArrow", forward ? canonEnum("arrow", head ?? "normal", "graphviz", at("targetArrow")) : "none");
604
+ put("sourceArrow", back ? canonEnum("arrow", tail ?? "normal", "graphviz", at("sourceArrow")) : "none");
605
+ }
606
+ const size = get("arrowsize");
607
+ if (size !== null) {
608
+ put("arrowSize", canonNumber(size, at("arrowSize")));
609
+ }
610
+ }
611
+ /**
612
+ * The HTML strings of a file: its HTML node names and labels, for Graphviz's shared string pool.
613
+ * @param snapshot - the graph
614
+ * @returns the strings, with their brackets
615
+ */
616
+ function htmlStrings(snapshot) {
617
+ const out = new Set();
618
+ for (let i = 0; i < snapshot.nodeCount; i++) {
619
+ const id = snapshot.ids.idOf(i);
620
+ if (typeof id === "string" && isHtmlString(id)) {
621
+ out.add(id);
622
+ }
623
+ }
624
+ for (const [table, n] of [
625
+ [snapshot.nodes, snapshot.nodeCount],
626
+ [snapshot.edges, snapshot.edgeCount],
627
+ ]) {
628
+ for (const name of [LABEL_ATTRIBUTE, "headlabel", "taillabel"].filter((c) => table.has(c))) {
629
+ addHtmlCells(table, name, n, out);
630
+ }
631
+ }
632
+ return out;
633
+ }
634
+ /**
635
+ * Adds the HTML strings of one label column.
636
+ * @param table - the node or edge table
637
+ * @param name - the column
638
+ * @param n - its rows
639
+ * @param out - receives the strings
640
+ */
641
+ function addHtmlCells(table, name, n, out) {
642
+ for (let i = 0; i < n; i++) {
643
+ const v = cellText(table, name, i);
644
+ if (v !== null && isHtmlString(v)) {
645
+ out.add(v);
646
+ }
647
+ }
648
+ }
649
+ /**
650
+ * Which edges are directed: every edge of a directed graph, or as its direction column says.
651
+ * @param snapshot - the graph
652
+ * @returns per edge
653
+ */
654
+ function directedEdges(snapshot) {
655
+ const out = new Array(snapshot.edgeCount).fill(snapshot.directed);
656
+ const col = snapshot.directed ? snapshot.edges.byRole("directed") : null;
657
+ if (col !== null) {
658
+ for (let e = 0; e < snapshot.edgeCount; e++) {
659
+ const v = plainCell(col.value(e));
660
+ if (typeof v === "boolean") {
661
+ out[e] = v;
662
+ }
663
+ }
664
+ }
665
+ return out;
666
+ }
667
+ /**
668
+ * Whether a node is a cluster's container.
669
+ * @param snapshot - the graph
670
+ * @param i - the node
671
+ * @returns true for a container
672
+ */
673
+ function isCluster(snapshot, i) {
674
+ return snapshot.nodes.has(CLUSTER_COLUMN) && snapshot.nodes.value(CLUSTER_COLUMN, i) === true;
675
+ }
676
+ /**
677
+ * Per element, what its labels' escapes and defaults depend on.
678
+ * @param snapshot - the graph
679
+ * @param written - the direction every edge is written with when the graph mixes both, or null to read it per edge
680
+ * @returns the contexts
681
+ */
682
+ function contexts(snapshot, written = null) {
683
+ const html = htmlStrings(snapshot);
684
+ const graph = snapshot.meta.name ?? "";
685
+ const own = directedEdges(snapshot);
686
+ // a graph mixing both directions is written with one, every edge then drawn as that one
687
+ const directed = written === null || own.every((d) => d === snapshot.directed) ? own : own.map(() => written);
688
+ const name = (i) => String(snapshot.ids.idOf(i));
689
+ return {
690
+ node: (i) => ({ name: name(i), graph, cluster: isCluster(snapshot, i), html }),
691
+ edge: (e) => {
692
+ const tail = name(snapshot.edgeSource(e));
693
+ const head = name(snapshot.edgeTarget(e));
694
+ return {
695
+ names: { graph, tail, head, edge: `${tail}${directed[e] ? "->" : "--"}${head}` },
696
+ directed: directed[e],
697
+ html,
698
+ };
699
+ },
700
+ };
701
+ }
702
+ /**
703
+ * The label a node shows when it has none of its own: its name, escapes applied (an HTML name shows its text); null
704
+ * for a cluster's container and a point.
705
+ * @param cx - the node
706
+ * @param shape - its shape, lowercase
707
+ * @param notes - where an HTML name is noted
708
+ * @returns the label, or null
709
+ */
710
+ function defaultLabel(cx, shape, notes) {
711
+ if (cx.cluster || shape === "point") {
712
+ return null;
713
+ }
714
+ const at = { notes, target: "node", property: "label" };
715
+ return isHtmlString(cx.name)
716
+ ? htmlLabelText(cx.name, at)
717
+ : dotLabelText(String.raw `\N`, { node: cx.name, graph: cx.graph }, at);
718
+ }
719
+ /**
720
+ * The columns of a table that hold styles: Graphviz's style attributes and graphty's per-element style values.
721
+ * @param table - the node or edge table
722
+ * @param attributes - the style attributes of the table
723
+ * @returns the column names
724
+ */
725
+ function styleColumns(table, attributes) {
726
+ const own = new Set([...attributes, ...NOTED_ATTRIBUTES]);
727
+ return [...table.names()].filter((c) => own.has(c) || c.startsWith("graphty.style."));
728
+ }
729
+ /**
730
+ * The styles of a DOT file, read the way Graphviz draws it, with Graphviz's defaults beneath: an unfilled black
731
+ * ellipse 54 x 36 labeled with its name, black edges with a target arrow in a directed graph. A file graphty wrote
732
+ * gives back its `graphty.styles` block.
733
+ * @param snapshot - the graph the import built
734
+ * @param context - the work budget
735
+ * @returns the styles
736
+ */
737
+ export function dotStyles(snapshot, context) {
738
+ const notes = new StyleNotes();
739
+ const cx = contexts(snapshot);
740
+ const node = valueColumns(snapshot.nodeCount);
741
+ const nameColumn = new Array(snapshot.nodeCount).fill(null);
742
+ const clusterColumn = new Array(snapshot.nodeCount).fill(null);
743
+ let named = 0;
744
+ let clusters = 0;
745
+ for (let i = 0; i < snapshot.nodeCount; i++) {
746
+ context.budget.spend("node", NODE_ATTRIBUTES.length);
747
+ const get = (name) => cellText(snapshot.nodes, name, i);
748
+ const ncx = cx.node(i);
749
+ const at = (property) => ({ notes, target: "node", property, element: i, budget: context.budget });
750
+ readNode(get, ncx, (p, v) => {
751
+ node.put(i, p, v);
752
+ }, at);
753
+ const shown = defaultLabel(ncx, (get("shape") ?? "").toLowerCase(), notes);
754
+ if (shown !== null) {
755
+ nameColumn[i] = shown;
756
+ named++;
757
+ }
758
+ if (ncx.cluster) {
759
+ clusterColumn[i] = true;
760
+ clusters++;
761
+ }
762
+ }
763
+ const edge = valueColumns(snapshot.edgeCount);
764
+ for (let e = 0; e < snapshot.edgeCount; e++) {
765
+ context.budget.spend("edge", EDGE_ATTRIBUTES.length);
766
+ const get = (name) => cellText(snapshot.edges, name, e);
767
+ const at = (property) => ({ notes, target: "edge", property, element: e, budget: context.budget });
768
+ readEdge(get, cx.edge(e), (p, v) => {
769
+ edge.put(e, p, v);
770
+ }, at);
771
+ }
772
+ const graph = {};
773
+ const bg = nonEmpty(cellText(snapshot.graph, "bgcolor", 0));
774
+ if (snapshot.graph.has("bgcolor") && bg !== null) {
775
+ readColor(bg, cellText(snapshot.graph, "colorscheme", 0), "fill", "background", null, (p, v) => {
776
+ if (v !== null) {
777
+ graph[p] = v;
778
+ }
779
+ }, (property) => ({ notes, target: "graph", property }));
780
+ }
781
+ const blockText = cellText(snapshot.graph, STYLE_BLOCK_NAME, 0);
782
+ if (blockText !== null) {
783
+ exactFromBlock(snapshot, blockText, { node: node.values, edge: edge.values }, cx);
784
+ }
785
+ // Graphviz's defaults that are rules: each node shows its name, and a directed edge has an arrow at its target
786
+ const nodeRules = [];
787
+ const nodeColumns = new Map();
788
+ if (named > 0) {
789
+ nodeColumns.set(NAME_KEY, nameColumn);
790
+ nodeRules.push({
791
+ id: "node.tool.name-label",
792
+ target: "node",
793
+ role: "passthrough",
794
+ origin: "tool",
795
+ where: { has: { style: NAME_KEY } },
796
+ map: { label: { kind: "passthrough", by: { style: NAME_KEY } } },
797
+ facts: { elements: named, reason: "NODE_NAME" },
798
+ });
799
+ }
800
+ const features = clusters > 0 ? [{ ref: { style: CLUSTER_KEY }, values: clusterColumn }] : [];
801
+ const edgeRules = [];
802
+ const edgeColumns = new Map();
803
+ const directed = directedEdges(snapshot);
804
+ if (directed.some(Boolean)) {
805
+ edgeColumns.set(DIRECTED_KEY, directed.map((d) => (d ? true : null)));
806
+ edgeRules.push({
807
+ id: "edge.tool.arrow",
808
+ target: "edge",
809
+ role: "group",
810
+ origin: "tool",
811
+ where: { has: { style: DIRECTED_KEY } },
812
+ set: { targetArrow: "triangle" },
813
+ });
814
+ }
815
+ return {
816
+ canonical: true,
817
+ dialect: "graphviz",
818
+ tool: "graphviz",
819
+ units: "px",
820
+ node: { values: node.values, rules: nodeRules, columns: nodeColumns, features },
821
+ edge: { values: edge.values, rules: edgeRules, columns: edgeColumns },
822
+ graph,
823
+ notes: notes.list(),
824
+ readFrom: {
825
+ node: styleColumns(snapshot.nodes, NODE_ATTRIBUTES),
826
+ edge: styleColumns(snapshot.edges, EDGE_ATTRIBUTES),
827
+ },
828
+ };
829
+ }
830
+ /** Neutral shapes by the Graphviz shape that draws them exactly. */
831
+ const SHAPES = new Map([
832
+ ["ellipse", "ellipse"],
833
+ ["rectangle", "box"],
834
+ ["round-rectangle", "box"],
835
+ ["triangle", "triangle"],
836
+ ["diamond", "diamond"],
837
+ ["pentagon", "pentagon"],
838
+ ["hexagon", "hexagon"],
839
+ ["octagon", "octagon"],
840
+ ["parallelogram", "parallelogram"],
841
+ ["trapezoid", "trapezium"],
842
+ ["star", "star"],
843
+ ["none", "none"],
844
+ ]);
845
+ /** Neutral arrows by the Graphviz arrow that draws them. */
846
+ const ARROWS = new Map([
847
+ ["none", "none"],
848
+ ["triangle", "normal"],
849
+ ["triangle-open", "onormal"],
850
+ ["vee", "vee"],
851
+ ["tee", "tee"],
852
+ ["diamond", "diamond"],
853
+ ["diamond-open", "odiamond"],
854
+ ["circle", "dot"],
855
+ ["circle-open", "odot"],
856
+ ["square", "box"],
857
+ ["crow", "crow"],
858
+ ["half", "lnormal"],
859
+ ["inverted", "inv"],
860
+ ]);
861
+ /** Line and border styles Graphviz draws exactly; the others are written as the nearest. */
862
+ const STROKES = new Map([
863
+ ["dashed", "dashed"],
864
+ ["dotted", "dotted"],
865
+ ]);
866
+ /**
867
+ * The attribute values Graphviz draws when an attribute is absent, from the tool-defaults table: the writer leaves
868
+ * them out, so a file states what differs from Graphviz's drawing (and a size stays one Graphviz may grow).
869
+ * @param target - nodes or edges
870
+ * @returns the default text of each attribute
871
+ */
872
+ function graphvizDefaults(target) {
873
+ const t = toolDefaults("graphviz")?.[target];
874
+ const out = new Map();
875
+ const add = (attribute, property, scale = 1) => {
876
+ const v = t[property]?.value;
877
+ if (typeof v === "number") {
878
+ out.set(attribute, num6(v / scale));
879
+ }
880
+ else if (typeof v === "string") {
881
+ out.set(attribute, v);
882
+ }
883
+ };
884
+ add("fontcolor", "labelColor");
885
+ add("fontsize", "labelSize");
886
+ add("fontname", "labelFont");
887
+ if (target === "node") {
888
+ add("color", "borderColor");
889
+ add("pencolor", "borderColor");
890
+ add("penwidth", "borderWidth");
891
+ add("shape", "shape");
892
+ add("width", "width", DOT_POINTS_PER_INCH);
893
+ add("height", "height", DOT_POINTS_PER_INCH);
894
+ }
895
+ else {
896
+ add("color", "color");
897
+ add("penwidth", "width");
898
+ add("arrowsize", "arrowSize");
899
+ }
900
+ return out;
901
+ }
902
+ const DEFAULT_ATTRIBUTES = {
903
+ node: graphvizDefaults("node"),
904
+ edge: graphvizDefaults("edge"),
905
+ };
906
+ /**
907
+ * Leaves out every attribute whose value is the one Graphviz draws without it.
908
+ * @param out - the attributes, changed in place
909
+ * @param target - nodes or edges
910
+ * @returns the attributes
911
+ */
912
+ function withoutDefaults(out, target) {
913
+ for (const [attribute, value] of DEFAULT_ATTRIBUTES[target]) {
914
+ if (out.get(attribute) === value) {
915
+ out.delete(attribute);
916
+ }
917
+ }
918
+ if (!out.has("width") && !out.has("height")) {
919
+ out.delete("fixedsize");
920
+ }
921
+ return out;
922
+ }
923
+ /**
924
+ * A color as Graphviz writes it: `#rrggbb`, or `#rrggbbaa` with an opacity below 1.
925
+ * @param color - the canonical color
926
+ * @param opacity - the opacity, or null
927
+ * @returns the text
928
+ */
929
+ function gvColor(color, opacity) {
930
+ if (typeof opacity !== "number" || opacity >= 1) {
931
+ return color;
932
+ }
933
+ const a = Math.max(0, Math.min(255, Math.round(opacity * 255)));
934
+ return color + a.toString(16).padStart(2, "0");
935
+ }
936
+ /**
937
+ * A label's text as a DOT label: backslashes and line breaks escaped, so Graphviz's escape passes give the text back.
938
+ * @param text - the text
939
+ * @returns the label
940
+ */
941
+ function escapeLabel(text) {
942
+ return text.replaceAll("\\", String.raw `\\`).replaceAll("\n", String.raw `\n`);
943
+ }
944
+ /**
945
+ * A shape, line or arrow value in Graphviz's spelling.
946
+ * @param table - the exact spellings
947
+ * @param value - the neutral value
948
+ * @param nearest - the spelling written when there is no exact one
949
+ * @param property - the property, for the approximation note
950
+ * @param approximate - records approximations
951
+ * @returns the spelling
952
+ */
953
+ function spelled(table, value, nearest, property, approximate) {
954
+ const exact = table.get(value);
955
+ if (exact !== undefined) {
956
+ return exact;
957
+ }
958
+ if (value.startsWith("other:")) {
959
+ return value.slice("other:".length);
960
+ }
961
+ approximate(property, value);
962
+ return nearest;
963
+ }
964
+ /**
965
+ * The font, tooltip and visibility attributes every element shares.
966
+ * @param get - the element's values
967
+ * @param out - the attributes, added to
968
+ * @param tokens - the style tokens, added to
969
+ */
970
+ function writeCommon(get, out, tokens) {
971
+ const color = get("labelColor");
972
+ if (typeof color === "string") {
973
+ out.set("fontcolor", color);
974
+ }
975
+ const size = get("labelSize");
976
+ if (typeof size === "number") {
977
+ out.set("fontsize", size);
978
+ }
979
+ const font = get("labelFont");
980
+ if (typeof font === "string") {
981
+ out.set("fontname", font);
982
+ }
983
+ const tip = get("tooltip");
984
+ if (typeof tip === "string") {
985
+ out.set("tooltip", escapeLabel(tip));
986
+ }
987
+ if (get("visible") === false) {
988
+ tokens.push("invis");
989
+ }
990
+ }
991
+ /**
992
+ * A node's or cluster's Graphviz attributes from its values.
993
+ * @param get - the node's values
994
+ * @param cx - the node's name and kind
995
+ * @param approximate - records approximations
996
+ * @param notes - where an HTML name is noted
997
+ * @returns the attributes
998
+ */
999
+ function nodeAttributes(get, cx, approximate, notes) {
1000
+ const out = new Map();
1001
+ const tokens = [];
1002
+ writeCommon(get, out, tokens);
1003
+ writeNodeOutline(get, cx, approximate, out, tokens);
1004
+ const gvShape = writeNodeShape(get, cx, approximate, out, tokens);
1005
+ if (!cx.cluster) {
1006
+ writeNodeSize(get, out);
1007
+ }
1008
+ // the label is written where it differs from what Graphviz shows without one (the name), "" for none
1009
+ const label = get("label");
1010
+ const shown = defaultLabel(cx, gvShape, notes);
1011
+ if (typeof label === "string" && label !== shown) {
1012
+ out.set(LABEL_ATTRIBUTE, escapeLabel(label));
1013
+ }
1014
+ else if (label === null && shown !== null) {
1015
+ out.set(LABEL_ATTRIBUTE, "");
1016
+ }
1017
+ if (tokens.length > 0) {
1018
+ out.set("style", tokens.join(","));
1019
+ }
1020
+ return withoutDefaults(out, "node");
1021
+ }
1022
+ /**
1023
+ * A node's or cluster's fill and outline attributes.
1024
+ * @param get - the node's values
1025
+ * @param cx - the node's name and kind
1026
+ * @param approximate - records approximations
1027
+ * @param out - the attributes, added to
1028
+ * @param tokens - the style tokens, added to
1029
+ */
1030
+ function writeNodeOutline(get, cx, approximate, out, tokens) {
1031
+ const fill = get("fill");
1032
+ const fillOpacity = get("fillOpacity");
1033
+ if (typeof fill === "string" && fillOpacity !== 0) {
1034
+ tokens.push("filled");
1035
+ out.set("fillcolor", gvColor(fill, fillOpacity));
1036
+ }
1037
+ const border = get("borderColor");
1038
+ if (typeof border === "string") {
1039
+ out.set(cx.cluster ? "pencolor" : "color", border);
1040
+ }
1041
+ const borderWidth = get("borderWidth");
1042
+ if (typeof borderWidth === "number") {
1043
+ out.set("penwidth", borderWidth);
1044
+ }
1045
+ const borderStyle = get("borderStyle");
1046
+ if (typeof borderStyle === "string" && borderStyle !== "solid") {
1047
+ const nearest = borderStyle.includes("dash") ? "dashed" : "solid";
1048
+ tokens.push(spelled(STROKES, borderStyle, nearest, "borderStyle", approximate));
1049
+ }
1050
+ }
1051
+ /**
1052
+ * A node's shape attribute; a cluster is always a rectangle, rounded or not, and any other shape is approximated.
1053
+ * @param get - the node's values
1054
+ * @param cx - the node's name and kind
1055
+ * @param approximate - records approximations
1056
+ * @param out - the attributes, added to
1057
+ * @param tokens - the style tokens, added to
1058
+ * @returns the Graphviz shape written, "" for none
1059
+ */
1060
+ function writeNodeShape(get, cx, approximate, out, tokens) {
1061
+ const shape = get("shape");
1062
+ if (typeof shape !== "string") {
1063
+ return "";
1064
+ }
1065
+ if (shape === "round-rectangle") {
1066
+ tokens.push("rounded");
1067
+ }
1068
+ if (cx.cluster) {
1069
+ if (shape !== "rectangle" && shape !== "round-rectangle") {
1070
+ approximate("shape", shape);
1071
+ }
1072
+ return "";
1073
+ }
1074
+ const gvShape = spelled(SHAPES, shape, shape === "vee" ? "invtriangle" : "box", "shape", approximate);
1075
+ out.set("shape", gvShape);
1076
+ return gvShape;
1077
+ }
1078
+ /**
1079
+ * A node's width and height, fixed so Graphviz does not grow them to fit the label.
1080
+ * @param get - the node's values
1081
+ * @param out - the attributes, added to
1082
+ */
1083
+ function writeNodeSize(get, out) {
1084
+ const width = get("width");
1085
+ const height = get("height");
1086
+ if (typeof width === "number" || typeof height === "number") {
1087
+ out.set("fixedsize", "true");
1088
+ }
1089
+ if (typeof width === "number") {
1090
+ out.set("width", inchesOf(width));
1091
+ }
1092
+ if (typeof height === "number") {
1093
+ out.set("height", inchesOf(height));
1094
+ }
1095
+ }
1096
+ /**
1097
+ * An edge's Graphviz attributes from its values.
1098
+ * @param get - the edge's values
1099
+ * @param directed - whether the edge is directed (its default arrows)
1100
+ * @param approximate - records approximations
1101
+ * @returns the attributes
1102
+ */
1103
+ function edgeAttributes(get, directed, approximate) {
1104
+ const out = new Map();
1105
+ const tokens = [];
1106
+ writeCommon(get, out, tokens);
1107
+ const color = get("color");
1108
+ if (typeof color === "string") {
1109
+ out.set("color", gvColor(color, get("opacity")));
1110
+ }
1111
+ const width = get("width");
1112
+ if (typeof width === "number") {
1113
+ out.set("penwidth", width);
1114
+ }
1115
+ const line = get("lineStyle");
1116
+ if (typeof line === "string" && line !== "solid") {
1117
+ tokens.push(spelled(STROKES, line, line.includes("dash") ? "dashed" : "solid", "lineStyle", approximate));
1118
+ }
1119
+ writeEdgeArrows(get, directed, approximate, out);
1120
+ const arrowSize = get("arrowSize");
1121
+ if (typeof arrowSize === "number") {
1122
+ out.set("arrowsize", arrowSize);
1123
+ }
1124
+ for (const [property, attribute] of [
1125
+ ["label", LABEL_ATTRIBUTE],
1126
+ ["targetLabel", "headlabel"],
1127
+ ["sourceLabel", "taillabel"],
1128
+ ]) {
1129
+ const v = get(property);
1130
+ if (typeof v === "string") {
1131
+ out.set(attribute, escapeLabel(v));
1132
+ }
1133
+ }
1134
+ if (tokens.length > 0) {
1135
+ out.set("style", tokens.join(","));
1136
+ }
1137
+ return withoutDefaults(out, "edge");
1138
+ }
1139
+ /**
1140
+ * An edge's arrow attributes, written where they differ from the graph's own: a target arrow on a directed edge.
1141
+ * @param get - the edge's values
1142
+ * @param directed - whether the edge is directed
1143
+ * @param approximate - records approximations
1144
+ * @param out - the attributes, added to
1145
+ */
1146
+ function writeEdgeArrows(get, directed, approximate, out) {
1147
+ const source = get("sourceArrow");
1148
+ const target = get("targetArrow");
1149
+ const tail = typeof source === "string" && source !== "none";
1150
+ const head = typeof target === "string" && target !== "none";
1151
+ const plain = directed ? head && !tail && target === "triangle" : !head && !tail;
1152
+ if (plain || (source === null && target === null)) {
1153
+ return;
1154
+ }
1155
+ let dir = "none";
1156
+ if (head && tail) {
1157
+ dir = "both";
1158
+ }
1159
+ else if (head) {
1160
+ dir = "forward";
1161
+ }
1162
+ else if (tail) {
1163
+ dir = "back";
1164
+ }
1165
+ out.set("dir", dir);
1166
+ if (head) {
1167
+ out.set("arrowhead", spelled(ARROWS, target, "normal", "targetArrow", approximate));
1168
+ }
1169
+ if (tail) {
1170
+ out.set("arrowtail", spelled(ARROWS, source, "normal", "sourceArrow", approximate));
1171
+ }
1172
+ }
1173
+ /**
1174
+ * Each element's values of the properties the writer writes, as a getter per element.
1175
+ * @param picture - each element's value of a property
1176
+ * @param target - nodes or edges
1177
+ * @returns the getter
1178
+ */
1179
+ function getter(picture, target) {
1180
+ const columns = new Map();
1181
+ for (const p of DOT_STYLE_CAPABILITIES[target]) {
1182
+ columns.set(p, picture(target, p));
1183
+ }
1184
+ return (i) => (property) => columns.get(property)?.[i] ?? null;
1185
+ }
1186
+ /**
1187
+ * For a file graphty wrote: where a value read from the attributes is what the writer writes for the block's own
1188
+ * value (a hexagon written as a hexagon, a pentagon-like vee as an inverted triangle, an opacity rounded to 255 steps,
1189
+ * a size rounded in inches), the block's exact value is the truth, so a re-import gives back the same rules; a value
1190
+ * another program changed since stays the truth.
1191
+ * @param snapshot - the graph
1192
+ * @param text - the block's text
1193
+ * @param read - the values read, changed in place
1194
+ * @param read.node - the node values
1195
+ * @param read.edge - the edge values
1196
+ * @param cx - the elements' contexts
1197
+ */
1198
+ function exactFromBlock(snapshot, text, read, cx) {
1199
+ const parsed = parseStyleBlock(text, styleFingerprint(snapshot));
1200
+ const ex = parsed.ok ? blockStyles(parsed.block, snapshot, "dot") : null;
1201
+ if (ex === null) {
1202
+ return;
1203
+ }
1204
+ const notes = new StyleNotes();
1205
+ for (const target of ["node", "edge"]) {
1206
+ const n = target === "node" ? snapshot.nodeCount : snapshot.edgeCount;
1207
+ const get = getter((t, p) => resolveStyles(ex, t, p, snapshot), target);
1208
+ const values = read[target];
1209
+ for (let i = 0; i < n; i++) {
1210
+ const want = get(i);
1211
+ const back = writtenAndReadBack(target, i, want, cx, notes);
1212
+ for (const property of DOT_STYLE_CAPABILITIES[target]) {
1213
+ const native = values[property]?.[i] ?? null;
1214
+ const w = want(property);
1215
+ if (native !== w && native === (back.get(property) ?? null)) {
1216
+ values[property] ?? (values[property] = new Array(n).fill(null));
1217
+ values[property][i] = w;
1218
+ }
1219
+ }
1220
+ }
1221
+ }
1222
+ }
1223
+ /**
1224
+ * An element's values written as the writer writes them and read back as the reader reads them.
1225
+ * @param target - nodes or edges
1226
+ * @param i - the element
1227
+ * @param want - its values
1228
+ * @param cx - the elements' contexts
1229
+ * @param notes - a scratch for the notes the round trip makes
1230
+ * @returns the values read back, by property
1231
+ */
1232
+ function writtenAndReadBack(target, i, want, cx, notes) {
1233
+ const ignore = () => undefined;
1234
+ const back = new Map();
1235
+ const put = (property, value) => {
1236
+ if (value !== null) {
1237
+ back.set(property, value);
1238
+ }
1239
+ };
1240
+ const at = (property) => ({ notes, target, property });
1241
+ if (target === "node") {
1242
+ const ncx = cx.node(i);
1243
+ const attrs = nodeAttributes(want, ncx, ignore, notes);
1244
+ readNode((name) => textOf(attrs.get(name)), ncx, put, at);
1245
+ }
1246
+ else {
1247
+ const ecx = cx.edge(i);
1248
+ const attrs = edgeAttributes(want, ecx.directed, ignore);
1249
+ readEdge((name) => textOf(attrs.get(name)), ecx, put, at);
1250
+ }
1251
+ return back;
1252
+ }
1253
+ /**
1254
+ * A written value as the text the importer would read back.
1255
+ * @param v - the value
1256
+ * @returns its text, or null
1257
+ */
1258
+ function textOf(v) {
1259
+ return v === undefined ? null : String(v);
1260
+ }
1261
+ /**
1262
+ * Whether a style column written as a DOT attribute reads back as the same values: DOT values are untyped and the
1263
+ * importer infers a column's type from its text, so a text column whose every value looks like a number does not.
1264
+ * @param col - the values
1265
+ * @returns true when it reads back unchanged
1266
+ */
1267
+ function readsBack(col) {
1268
+ const vals = col.filter((v) => v !== null);
1269
+ if (vals.every((v) => typeof v === "number")) {
1270
+ return vals.every((v) => Number.isFinite(v));
1271
+ }
1272
+ if (vals.every((v) => typeof v === "boolean")) {
1273
+ return true;
1274
+ }
1275
+ if (vals.every((v) => typeof v === "string")) {
1276
+ return vals.some((v) => inferTextDtype(v) === "string") && vals.every((v) => isWritableDotText(v));
1277
+ }
1278
+ return false;
1279
+ }
1280
+ /**
1281
+ * A `graphty.styles` block's JSON with every quote and backslash inside its strings written as a `\u` escape, so the
1282
+ * text holds no backslash before a quote, which a DOT quoted string cannot carry; it parses to the same value.
1283
+ * @param json - the block
1284
+ * @returns the block
1285
+ */
1286
+ function dotSafeJson(json) {
1287
+ return json.replaceAll(/\\(["\\])/g, (_m, c) => (c === '"' ? String.raw `\u0022` : String.raw `\u005c`));
1288
+ }
1289
+ /**
1290
+ * Works out a DOT export with styles: every node's, cluster's and edge's Graphviz attributes from what graphty draws,
1291
+ * in place of every style attribute it had; the graph's `bgcolor`; the `graphty.styles` block as the graph attribute
1292
+ * `"graphty.styles"` and the per-element values its rules read as `"graphty.style.<target>.<key>"` attributes. A
1293
+ * style column whose values would not read back as written (text that looks like numbers) goes inside the block
1294
+ * instead, with all the others.
1295
+ * @param snapshot - the graph handed to the exporter
1296
+ * @param input - the styles, or undefined when none are written
1297
+ * @param onMixedDirection - the exporter's option: the direction a graph mixing both is written with
1298
+ * @returns the styled export, or null without styles
1299
+ */
1300
+ export function dotStyledExport(snapshot, input, onMixedDirection) {
1301
+ if (input === undefined) {
1302
+ return null;
1303
+ }
1304
+ // the registry's check before writing and the write of one export call share the work
1305
+ return sharedInExportCall(input, [snapshot, onMixedDirection], () => styledExport(snapshot, input, onMixedDirection));
1306
+ }
1307
+ /**
1308
+ * Works out a DOT export with styles: dotStyledExport() without the sharing.
1309
+ * @param snapshot - the graph handed to the exporter
1310
+ * @param input - the styles
1311
+ * @param onMixedDirection - the exporter's option
1312
+ * @returns the styled export
1313
+ */
1314
+ function styledExport(snapshot, input, onMixedDirection) {
1315
+ const approximated = new StyleApproximations();
1316
+ const replaced = [];
1317
+ const out = snapshot.withColumns();
1318
+ const written = onMixedDirection === "directed" || onMixedDirection === "undirected";
1319
+ const cx = contexts(snapshot, written ? onMixedDirection === "directed" : null);
1320
+ const notes = new StyleNotes();
1321
+ for (const target of ["node", "edge"]) {
1322
+ const table = target === "node" ? out.nodes : out.edges;
1323
+ const read = dataRead(input.rules.styles.rules, target);
1324
+ const approximate = (property, value) => {
1325
+ approximated.add(target, property, value);
1326
+ };
1327
+ const columns = attributeColumns(snapshot, input, target, { cx, approximate, notes, read });
1328
+ replaced.push(...removeStyleAttributes(table, target, columns, read));
1329
+ setAttributeColumns(table, columns);
1330
+ }
1331
+ const { background } = input.rules.styles.graph;
1332
+ if (out.graph.has("bgcolor")) {
1333
+ out.graph.remove("bgcolor");
1334
+ }
1335
+ if (typeof background === "string") {
1336
+ out.graph.set("bgcolor", [background], { dtype: "string" });
1337
+ }
1338
+ const inline = [...input.tags.node.values(), ...input.tags.edge.values()].some((col) => !readsBack(col));
1339
+ if (!inline) {
1340
+ setTagColumns(out, input);
1341
+ }
1342
+ const block = inline ? writeStyleBlock(input.rules, { inlineColumns: true, snapshot }) : input.block;
1343
+ out.graph.set(STYLE_BLOCK_NAME, [dotSafeJson(block)], { dtype: "string" });
1344
+ const losses = approximated.notes("Graphviz");
1345
+ if (inline) {
1346
+ losses.push(...styleBlockLosses(block, input.rules.styles.rules.length, "size"));
1347
+ }
1348
+ return { snapshot: out, notes: [...replaced, ...losses] };
1349
+ }
1350
+ /**
1351
+ * Every element's Graphviz attributes of a target, as columns.
1352
+ * @param snapshot - the graph handed to the exporter
1353
+ * @param input - the styles
1354
+ * @param target - nodes or edges
1355
+ * @param w - the contexts, the recorders and the columns the rules read
1356
+ * @returns the attribute columns, by name
1357
+ */
1358
+ function attributeColumns(snapshot, input, target, w) {
1359
+ const n = target === "node" ? snapshot.nodeCount : snapshot.edgeCount;
1360
+ const get = getter((t, p) => input.picture(t, p), target);
1361
+ const columns = new Map();
1362
+ for (let i = 0; i < n; i++) {
1363
+ const attrs = target === "node"
1364
+ ? nodeAttributes(get(i), w.cx.node(i), w.approximate, w.notes)
1365
+ : edgeAttributes(get(i), w.cx.edge(i).directed, w.approximate);
1366
+ // no label is written label="" (Graphviz would show the name), which a rule reading the label column
1367
+ // reads back as the empty label
1368
+ if (w.read.has(LABEL_ATTRIBUTE) && attrs.get(LABEL_ATTRIBUTE) === "" && get(i)("label") === null) {
1369
+ w.approximate("label", "");
1370
+ }
1371
+ for (const [name, value] of attrs) {
1372
+ let col = columns.get(name);
1373
+ if (col === undefined) {
1374
+ col = new Array(n).fill(null);
1375
+ columns.set(name, col);
1376
+ }
1377
+ col[i] = value;
1378
+ }
1379
+ }
1380
+ return columns;
1381
+ }
1382
+ /**
1383
+ * Removes every column Graphviz reads as how to draw: the picture writes them, so a data column of the same name (a
1384
+ * `label` attribute graphty does not draw) would draw something graphty does not. One a rule reads, whose values the
1385
+ * picture does not write, is lost to the rule, and said so.
1386
+ * @param table - the node or edge table, changed in place
1387
+ * @param target - nodes or edges
1388
+ * @param columns - the attributes the picture writes
1389
+ * @param read - the data columns the rules read
1390
+ * @returns the notes of the columns lost to a rule
1391
+ */
1392
+ function removeStyleAttributes(table, target, columns, read) {
1393
+ const attributes = target === "node" ? NODE_ATTRIBUTES : EDGE_ATTRIBUTES;
1394
+ const replaced = [];
1395
+ for (const name of table.names()) {
1396
+ if (!attributes.includes(name) && name !== LABEL_ATTRIBUTE && !name.startsWith("graphty.style.")) {
1397
+ continue;
1398
+ }
1399
+ const differs = read.has(name) ? replacedRows(table, name, columns.get(name)) : 0;
1400
+ if (differs > 0) {
1401
+ replaced.push(Object.freeze({
1402
+ code: DOT_ATTRIBUTE_CLASH_CODE,
1403
+ message: `${target} column "${name}" is not written: the ${name} attribute holds what graphty draws (${String(differs)} ${target}${differs === 1 ? "" : "s"} differ)`,
1404
+ column: name,
1405
+ count: differs,
1406
+ }));
1407
+ }
1408
+ table.remove(name);
1409
+ }
1410
+ return replaced;
1411
+ }
1412
+ /**
1413
+ * Sets the attribute columns the picture writes: the label with its role, numbers as f64, the rest as text.
1414
+ * @param table - the node or edge table, changed in place
1415
+ * @param columns - the attributes
1416
+ */
1417
+ function setAttributeColumns(table, columns) {
1418
+ for (const [name, col] of columns) {
1419
+ if (name === LABEL_ATTRIBUTE) {
1420
+ table.set(name, col, { dtype: "string", role: "label" }, { replaceRole: true });
1421
+ }
1422
+ else if (col.every((v) => v === null || typeof v === "number")) {
1423
+ table.set(name, col, { dtype: "f64" });
1424
+ }
1425
+ else {
1426
+ table.set(name, col.map((v) => (v === null ? null : String(v))), { dtype: "string" });
1427
+ }
1428
+ }
1429
+ }
1430
+ /**
1431
+ * Sets the per-element values the rules read as `"graphty.style.<target>.<key>"` attributes.
1432
+ * @param out - the graph, changed in place
1433
+ * @param input - the styles
1434
+ */
1435
+ function setTagColumns(out, input) {
1436
+ for (const target of ["node", "edge"]) {
1437
+ const table = target === "node" ? out.nodes : out.edges;
1438
+ for (const [name, col] of input.tags[target]) {
1439
+ const kinds = new Set(col.filter((v) => v !== null).map((v) => typeof v));
1440
+ let dtype = "string";
1441
+ if (kinds.has("number")) {
1442
+ dtype = "f64";
1443
+ }
1444
+ else if (kinds.has("boolean")) {
1445
+ dtype = "bool";
1446
+ }
1447
+ table.set(name, col, { dtype });
1448
+ }
1449
+ }
1450
+ }
1451
+ /**
1452
+ * The data columns the rules of a target read.
1453
+ * @param rules - the rules
1454
+ * @param target - nodes or edges
1455
+ * @returns the column names
1456
+ */
1457
+ function dataRead(rules, target) {
1458
+ const out = new Set();
1459
+ for (const { ext: _ext, ...rule } of rules.filter((r) => r.target === target)) {
1460
+ JSON.stringify(rule, (_k, v) => {
1461
+ if (typeof v === "object" && v !== null && !Array.isArray(v) && Object.keys(v).length === 1) {
1462
+ const { data } = v;
1463
+ if (typeof data === "string") {
1464
+ out.add(data);
1465
+ }
1466
+ }
1467
+ return v;
1468
+ });
1469
+ }
1470
+ return out;
1471
+ }
1472
+ /**
1473
+ * A data column named like an attribute the export writes is not written: the exporter's `DOT_LOSS.ATTRIBUTE_CLASH`.
1474
+ */
1475
+ export const DOT_ATTRIBUTE_CLASH_CODE = "W_DOT_ATTRIBUTE_CLASH";
1476
+ /**
1477
+ * How many rows of a data column the attribute written in its place does not hold as it is.
1478
+ * @param table - the table
1479
+ * @param name - the column
1480
+ * @param written - the attribute's values, or undefined when none is written
1481
+ * @returns the rows that differ
1482
+ */
1483
+ function replacedRows(table, name, written) {
1484
+ const column = table.get(name);
1485
+ let n = 0;
1486
+ for (let i = 0; column !== null && i < column.length; i++) {
1487
+ const cell = column.isSet(i) ? plainCell(column.value(i)) : null;
1488
+ const w = written?.[i] ?? null;
1489
+ const text = typeof cell === "string" ? cell : JSON.stringify(cell);
1490
+ if (cell !== null && (w === null || String(w) !== text)) {
1491
+ n++;
1492
+ }
1493
+ }
1494
+ return n;
1495
+ }
1496
+ //# sourceMappingURL=styles.js.map