PdfSynctexController.js 5.2 KB

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