@graphty/graph-io 0.3.42 → 0.3.44

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