PdfSynctexController.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. import App from '../../../base'
  2. App.controller('PdfSynctexController', function ($scope, synctex, ide) {
  3. this.cursorPosition = null
  4. $scope.$watch(
  5. () => synctex.syncToPdfInFlight,
  6. value => ($scope.syncToPdfInFlight = value)
  7. )
  8. $scope.$watch(
  9. () => synctex.syncToCodeInFlight,
  10. value => ($scope.syncToCodeInFlight = value)
  11. )
  12. ide.$scope.$on('cursor:editor:update', (event, cursorPosition) => {
  13. this.cursorPosition = cursorPosition
  14. })
  15. $scope.syncToPdf = () => {
  16. if (this.cursorPosition == null) {
  17. return
  18. }
  19. synctex.syncToPdf(this.cursorPosition).then(highlights => {
  20. $scope.pdf.highlights = highlights
  21. })
  22. }
  23. ide.$scope.$on('cursor:editor:syncToPdf', $scope.syncToPdf)
  24. function syncToPosition(position, options) {
  25. synctex.syncToCode(position, options).then(data => {
  26. ide.editorManager.openDoc(data.doc, {
  27. gotoLine: data.line,
  28. })
  29. })
  30. }
  31. $scope.syncToCode = function () {
  32. syncToPosition($scope.pdf.position, {
  33. includeVisualOffset: true,
  34. fromPdfPosition: true,
  35. })
  36. }
  37. window.addEventListener('synctex:sync-to-position', event => {
  38. syncToPosition(event.detail, {
  39. fromPdfPosition: true,
  40. })
  41. })
  42. window.addEventListener('synctex:sync-to-entry', event => {
  43. const entry = event.detail
  44. const entity = ide.fileTreeManager.findEntityByPath(entry.file)
  45. if (entity && entity.type === 'doc') {
  46. ide.editorManager.openDoc(entity, {
  47. gotoLine: entry.line ?? undefined,
  48. gotoColumn: entry.column ?? undefined,
  49. })
  50. }
  51. })
  52. })
  53. App.factory('synctex', function (ide, $http, $q) {
  54. return {
  55. syncToPdfInFlight: false,
  56. syncToCodeInFlight: false,
  57. syncToPdf(cursorPosition) {
  58. const deferred = $q.defer()
  59. const docId = ide.editorManager.getCurrentDocId()
  60. if (docId == null) {
  61. deferred.reject()
  62. return deferred.promise
  63. }
  64. const doc = ide.fileTreeManager.findEntityById(docId)
  65. if (doc == null) {
  66. deferred.reject()
  67. return deferred.promise
  68. }
  69. let path = ide.fileTreeManager.getEntityPath(doc)
  70. if (path == null) {
  71. deferred.reject()
  72. return deferred.promise
  73. }
  74. // If the root file is folder/main.tex, then synctex sees the
  75. // path as folder/./main.tex
  76. const rootDocDirname = ide.fileTreeManager.getRootDocDirname()
  77. if (rootDocDirname != null && rootDocDirname !== '') {
  78. path = path.replace(RegExp(`^${rootDocDirname}`), `${rootDocDirname}/.`)
  79. }
  80. const { row, column } = cursorPosition
  81. this.syncToPdfInFlight = true
  82. $http({
  83. url: `/project/${ide.project_id}/sync/code`,
  84. method: 'GET',
  85. params: {
  86. file: path,
  87. line: row + 1,
  88. column,
  89. clsiserverid: ide.$scope.pdf.clsiServerId,
  90. },
  91. })
  92. .then(response => {
  93. this.syncToPdfInFlight = false
  94. const { data } = response
  95. return deferred.resolve(data.pdf || [])
  96. })
  97. .catch(response => {
  98. this.syncToPdfInFlight = false
  99. const error = response.data
  100. return deferred.reject(error)
  101. })
  102. return deferred.promise
  103. },
  104. syncToCode(position, options) {
  105. if (options == null) {
  106. options = {}
  107. }
  108. const deferred = $q.defer()
  109. if (position == null) {
  110. deferred.reject()
  111. return deferred.promise
  112. }
  113. // FIXME: this actually works better if it's halfway across the
  114. // page (or the visible part of the page). Synctex doesn't
  115. // always find the right place in the file when the point is at
  116. // the edge of the page, it sometimes returns the start of the
  117. // next paragraph instead.
  118. const h = position.offset.left
  119. // Compute the vertical position to pass to synctex, which
  120. // works with coordinates increasing from the top of the page
  121. // down. This matches the browser's DOM coordinate of the
  122. // click point, but the pdf position is measured from the
  123. // bottom of the page so we need to invert it.
  124. let v
  125. if (
  126. options.fromPdfPosition &&
  127. (position.pageSize != null ? position.pageSize.height : undefined) !=
  128. null
  129. ) {
  130. v = position.pageSize.height - position.offset.top || 0 // measure from pdf point (inverted)
  131. } else {
  132. v = position.offset.top || 0 // measure from html click position
  133. }
  134. // It's not clear exactly where we should sync to if it wasn't directly
  135. // clicked on, but a little bit down from the very top seems best.
  136. if (options.includeVisualOffset) {
  137. v += 72 // use the same value as in pdfViewer highlighting visual offset
  138. }
  139. this.syncToCodeInFlight = true
  140. $http({
  141. url: `/project/${ide.project_id}/sync/pdf`,
  142. method: 'GET',
  143. params: {
  144. page: position.page + 1,
  145. h: h.toFixed(2),
  146. v: v.toFixed(2),
  147. clsiserverid: ide.$scope.pdf.clsiServerId,
  148. },
  149. })
  150. .then(response => {
  151. this.syncToCodeInFlight = false
  152. const { data } = response
  153. if (
  154. data.code != null &&
  155. data.code.length > 0 &&
  156. data.code[0].file !== ''
  157. ) {
  158. const doc = ide.fileTreeManager.findEntityByPath(data.code[0].file)
  159. if (doc == null) {
  160. deferred.reject()
  161. }
  162. return deferred.resolve({ doc, line: data.code[0].line })
  163. } else if (data.code[0].file === '') {
  164. ide.$scope.sync_tex_error = true
  165. setTimeout(() => (ide.$scope.sync_tex_error = false), 4000)
  166. }
  167. })
  168. .catch(response => {
  169. this.syncToCodeInFlight = false
  170. const error = response.data
  171. return deferred.reject(error)
  172. })
  173. return deferred.promise
  174. },
  175. }
  176. })