markdown-it-imsize.mjs 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. // Adapted from markdown-it-imsize plugin by @tatsy
  2. // Original source https://github.com/tatsy/markdown-it-imsize/blob/master/lib/index.js
  3. function renderImSize (state, silent) {
  4. let attrs
  5. let code
  6. let label
  7. let pos
  8. let ref
  9. let res
  10. let title
  11. let width = ''
  12. let height = ''
  13. let token
  14. let tokens
  15. let start
  16. let href = ''
  17. const oldPos = state.pos
  18. const max = state.posMax
  19. if (state.src.charCodeAt(state.pos) !== 0x21/* ! */) { return false }
  20. if (state.src.charCodeAt(state.pos + 1) !== 0x5B/* [ */) { return false }
  21. const labelStart = state.pos + 2
  22. const labelEnd = state.md.helpers.parseLinkLabel(state, state.pos + 1, false)
  23. // parser failed to find ']', so it's not a valid link
  24. if (labelEnd < 0) { return false }
  25. pos = labelEnd + 1
  26. if (pos < max && state.src.charCodeAt(pos) === 0x28/* ( */) {
  27. //
  28. // Inline link
  29. //
  30. // [link]( <href> "title" )
  31. // ^^ skipping these spaces
  32. pos++
  33. for (; pos < max; pos++) {
  34. code = state.src.charCodeAt(pos)
  35. if (code !== 0x20 && code !== 0x0A) { break }
  36. }
  37. if (pos >= max) { return false }
  38. // [link]( <href> "title" )
  39. // ^^^^^^ parsing link destination
  40. start = pos
  41. res = state.md.helpers.parseLinkDestination(state.src, pos, state.posMax)
  42. if (res.ok) {
  43. href = state.md.normalizeLink(res.str)
  44. if (state.md.validateLink(href)) {
  45. pos = res.pos
  46. } else {
  47. href = ''
  48. }
  49. }
  50. // [link]( <href> "title" )
  51. // ^^ skipping these spaces
  52. start = pos
  53. for (; pos < max; pos++) {
  54. code = state.src.charCodeAt(pos)
  55. if (code !== 0x20 && code !== 0x0A) { break }
  56. }
  57. // [link]( <href> "title" )
  58. // ^^^^^^^ parsing link title
  59. res = state.md.helpers.parseLinkTitle(state.src, pos, state.posMax)
  60. if (pos < max && start !== pos && res.ok) {
  61. title = res.str
  62. pos = res.pos
  63. // [link]( <href> "title" )
  64. // ^^ skipping these spaces
  65. for (; pos < max; pos++) {
  66. code = state.src.charCodeAt(pos)
  67. if (code !== 0x20 && code !== 0x0A) { break }
  68. }
  69. } else {
  70. title = ''
  71. }
  72. // [link]( <href> "title" =WxH )
  73. // ^^^^ parsing image size
  74. if (pos - 1 >= 0) {
  75. code = state.src.charCodeAt(pos - 1)
  76. // there must be at least one white spaces
  77. // between previous field and the size
  78. if (code === 0x20) {
  79. res = parseImageSize(state.src, pos, state.posMax)
  80. if (res.ok) {
  81. width = res.width
  82. height = res.height
  83. pos = res.pos
  84. // [link]( <href> "title" =WxH )
  85. // ^^ skipping these spaces
  86. for (; pos < max; pos++) {
  87. code = state.src.charCodeAt(pos)
  88. if (code !== 0x20 && code !== 0x0A) { break }
  89. }
  90. }
  91. }
  92. }
  93. if (pos >= max || state.src.charCodeAt(pos) !== 0x29/* ) */) {
  94. state.pos = oldPos
  95. return false
  96. }
  97. pos++
  98. } else {
  99. //
  100. // Link reference
  101. //
  102. if (typeof state.env.references === 'undefined') { return false }
  103. // [foo] [bar]
  104. // ^^ optional whitespace (can include newlines)
  105. for (; pos < max; pos++) {
  106. code = state.src.charCodeAt(pos)
  107. if (code !== 0x20 && code !== 0x0A) { break }
  108. }
  109. if (pos < max && state.src.charCodeAt(pos) === 0x5B/* [ */) {
  110. start = pos + 1
  111. pos = state.md.helpers.parseLinkLabel(state, pos)
  112. if (pos >= 0) {
  113. label = state.src.slice(start, pos++)
  114. } else {
  115. pos = labelEnd + 1
  116. }
  117. } else {
  118. pos = labelEnd + 1
  119. }
  120. // covers label === '' and label === undefined
  121. // (collapsed reference link and shortcut reference link respectively)
  122. if (!label) { label = state.src.slice(labelStart, labelEnd) }
  123. ref = state.env.references[state.md.utils.normalizeReference(label)]
  124. if (!ref) {
  125. state.pos = oldPos
  126. return false
  127. }
  128. href = ref.href
  129. title = ref.title
  130. }
  131. //
  132. // We found the end of the link, and know for a fact it's a valid link;
  133. // so all that's left to do is to call tokenizer.
  134. //
  135. if (!silent) {
  136. state.pos = labelStart
  137. state.posMax = labelEnd
  138. const newState = new state.md.inline.State(
  139. state.src.slice(labelStart, labelEnd),
  140. state.md,
  141. state.env,
  142. tokens = []
  143. )
  144. newState.md.inline.tokenize(newState)
  145. token = state.push('image', 'img', 0)
  146. token.attrs = attrs = [['src', href],
  147. ['alt', '']]
  148. token.children = tokens
  149. if (title) {
  150. attrs.push(['title', title])
  151. }
  152. if (width !== '') {
  153. attrs.push(['width', width])
  154. }
  155. if (height !== '') {
  156. attrs.push(['height', height])
  157. }
  158. }
  159. state.pos = pos
  160. state.posMax = max
  161. return true
  162. }
  163. function parseNextNumber (str, pos, max) {
  164. let code
  165. const start = pos
  166. const result = {
  167. ok: false,
  168. pos,
  169. value: ''
  170. }
  171. code = str.charCodeAt(pos)
  172. while ((pos < max && (code >= 0x30 /* 0 */ && code <= 0x39 /* 9 */)) || code === 0x25 /* % */) {
  173. code = str.charCodeAt(++pos)
  174. }
  175. result.ok = true
  176. result.pos = pos
  177. result.value = str.slice(start, pos)
  178. return result
  179. }
  180. function parseImageSize (str, pos, max) {
  181. let code
  182. const result = {
  183. ok: false,
  184. pos: 0,
  185. width: '',
  186. height: ''
  187. }
  188. if (pos >= max) { return result }
  189. code = str.charCodeAt(pos)
  190. if (code !== 0x3d /* = */) { return result }
  191. pos++
  192. // size must follow = without any white spaces as follows
  193. // (1) =300x200
  194. // (2) =300x
  195. // (3) =x200
  196. code = str.charCodeAt(pos)
  197. if (code !== 0x78 /* x */ && (code < 0x30 || code > 0x39) /* [0-9] */) {
  198. return result
  199. }
  200. // parse width
  201. const resultW = parseNextNumber(str, pos, max)
  202. pos = resultW.pos
  203. // next charactor must be 'x'
  204. code = str.charCodeAt(pos)
  205. if (code !== 0x78 /* x */) { return result }
  206. pos++
  207. // parse height
  208. const resultH = parseNextNumber(str, pos, max)
  209. pos = resultH.pos
  210. result.width = resultW.value
  211. result.height = resultH.value
  212. result.pos = pos
  213. result.ok = true
  214. return result
  215. }
  216. export default (md) => {
  217. md.inline.ruler.before('emphasis', 'image', renderImSize)
  218. }