6
6
import createDOMPurify from 'dompurify' ;
7
7
import html2canvas from 'html2canvas' ;
8
8
import jsPDF from 'jspdf' ;
9
+ import { createWorker } from 'tesseract.js' ;
9
10
import { v1 as uuidv1 } from 'uuid' ;
10
11
import { ReportSchemaType } from '../../../server/model' ;
11
12
import { uiSettingsService } from '../utils/settings_service' ;
@@ -57,15 +58,16 @@ const removeNonReportElements = (
57
58
reportSource : VISUAL_REPORT_TYPE
58
59
) => {
59
60
// remove buttons
60
- doc . querySelectorAll ( "button[class^='euiButton']:not(.visLegend__button)" ) . forEach ( ( e ) => e . remove ( ) ) ;
61
+ doc
62
+ . querySelectorAll ( "button[class^='euiButton']:not(.visLegend__button)" )
63
+ . forEach ( ( e ) => e . remove ( ) ) ;
61
64
// remove top navBar
62
65
doc . querySelectorAll ( "[class^='euiHeader']" ) . forEach ( ( e ) => e . remove ( ) ) ;
63
66
// remove visualization editor
64
67
if ( reportSource === VISUAL_REPORT_TYPE . visualization ) {
65
68
doc . querySelector ( '[data-test-subj="splitPanelResizer"]' ) ?. remove ( ) ;
66
69
doc . querySelector ( '.visEditor__collapsibleSidebar' ) ?. remove ( ) ;
67
70
}
68
- doc . body . style . paddingTop = '0px' ;
69
71
} ;
70
72
71
73
const addReportHeader = ( doc : Document , header : string ) => {
@@ -96,8 +98,10 @@ const addReportFooter = (doc: Document, footer: string) => {
96
98
97
99
const addReportStyle = ( doc : Document , style : string ) => {
98
100
const styleElement = document . createElement ( 'style' ) ;
101
+ styleElement . className = 'reportInjectedStyles' ;
99
102
styleElement . innerHTML = style ;
100
103
doc . getElementsByTagName ( 'head' ) [ 0 ] . appendChild ( styleElement ) ;
104
+ doc . body . style . paddingTop = '0px' ;
101
105
} ;
102
106
103
107
const computeHeight = ( height : number , header : string , footer : string ) => {
@@ -115,6 +119,7 @@ const computeHeight = (height: number, header: string, footer: string) => {
115
119
116
120
export const generateReport = async ( id : string , forceDelay = 15000 ) => {
117
121
const http = uiSettingsService . getHttpClient ( ) ;
122
+ const useForeignObjectRendering = uiSettingsService . get ( 'reporting:useFOR' ) ;
118
123
const DOMPurify = createDOMPurify ( window ) ;
119
124
120
125
const report = await http . get < ReportSchemaType > (
@@ -154,6 +159,26 @@ export const generateReport = async (id: string, forceDelay = 15000) => {
154
159
}
155
160
await timeout ( forceDelay ) ;
156
161
162
+ // Style changes onclone does not work with foreign object rendering enabled.
163
+ // Additionally increase span width to prevent text being truncated
164
+ if ( useForeignObjectRendering ) {
165
+ document
166
+ . querySelectorAll < HTMLSpanElement > ( 'span:not([data-html2canvas-ignore])' )
167
+ . forEach ( ( el ) => {
168
+ if ( ! el . closest ( '.globalFilterItem' ) )
169
+ el . style . width = el . offsetWidth + 30 + 'px' ;
170
+ } ) ;
171
+ document
172
+ . querySelectorAll < HTMLSpanElement > (
173
+ 'span.globalFilterItem:not([data-html2canvas-ignore])'
174
+ )
175
+ . forEach ( ( el ) => ( el . style . width = el . offsetWidth + 5 + 'px' ) ) ;
176
+ addReportHeader ( document , header ) ;
177
+ addReportFooter ( document , footer ) ;
178
+ addReportStyle ( document , reportingStyle ) ;
179
+ await timeout ( 1000 ) ;
180
+ }
181
+
157
182
const width = document . documentElement . scrollWidth ;
158
183
const height = computeHeight (
159
184
document . documentElement . scrollHeight ,
@@ -170,40 +195,87 @@ export const generateReport = async (id: string, forceDelay = 15000) => {
170
195
imageTimeout : 30000 ,
171
196
useCORS : true ,
172
197
removeContainer : false ,
198
+ allowTaint : true ,
199
+ foreignObjectRendering : useForeignObjectRendering ,
173
200
onclone : function ( documentClone ) {
174
201
removeNonReportElements ( documentClone , reportSource ) ;
175
- addReportHeader ( documentClone , header ) ;
176
- addReportFooter ( documentClone , footer ) ;
177
- addReportStyle ( documentClone , reportingStyle ) ;
202
+ if ( ! useForeignObjectRendering ) {
203
+ addReportHeader ( documentClone , header ) ;
204
+ addReportFooter ( documentClone , footer ) ;
205
+ addReportStyle ( documentClone , reportingStyle ) ;
206
+ }
178
207
} ,
179
- } ) . then ( function ( canvas ) {
180
- // TODO remove this and 'removeContainer: false' when https://github.com/niklasvh/html2canvas/pull/2949 is merged
181
- document
182
- . querySelectorAll < HTMLIFrameElement > ( '.html2canvas-container' )
183
- . forEach ( ( e ) => {
184
- const iframe = e . contentWindow ;
185
- if ( e ) {
186
- e . src = 'about:blank' ;
187
- if ( iframe ) {
188
- iframe . document . write ( '' ) ;
189
- iframe . document . clear ( ) ;
190
- iframe . close ( ) ;
208
+ } )
209
+ . then ( async function ( canvas ) {
210
+ // TODO remove this and 'removeContainer: false' when https://github.com/niklasvh/html2canvas/pull/2949 is merged
211
+ document
212
+ . querySelectorAll < HTMLIFrameElement > ( '.html2canvas-container' )
213
+ . forEach ( ( e ) => {
214
+ const iframe = e . contentWindow ;
215
+ if ( e ) {
216
+ e . src = 'about:blank' ;
217
+ if ( iframe ) {
218
+ iframe . document . write ( '' ) ;
219
+ iframe . document . clear ( ) ;
220
+ iframe . close ( ) ;
221
+ }
222
+ e . remove ( ) ;
191
223
}
192
- e . remove ( ) ;
193
- }
194
- } ) ;
224
+ } ) ;
195
225
196
- if ( format === 'png' ) {
197
- const link = document . createElement ( 'a' ) ;
198
- link . download = fileName ;
199
- link . href = canvas . toDataURL ( ) ;
200
- link . click ( ) ;
201
- } else {
202
- const orient = canvas . width > canvas . height ? 'landscape' : 'portrait' ;
203
- const pdf = new jsPDF ( orient , 'px' , [ canvas . width , canvas . height ] ) ;
204
- pdf . addImage ( canvas , 'JPEG' , 0 , 0 , canvas . width , canvas . height ) ;
205
- pdf . save ( fileName ) ;
206
- }
207
- return true ;
208
- } ) ;
226
+ if ( format === 'png' ) {
227
+ const link = document . createElement ( 'a' ) ;
228
+ link . download = fileName ;
229
+ link . href = canvas . toDataURL ( ) ;
230
+ link . click ( ) ;
231
+ } else if ( uiSettingsService . get ( 'reporting:useOcr' ) ) {
232
+ const worker = await createWorker ( {
233
+ workerPath : '../api/reporting/tesseract.js/dist/worker.min.js' ,
234
+ langPath : '../api/reporting/tesseract-lang-data' ,
235
+ corePath : '../api/reporting/tesseract.js-core/tesseract-core.wasm.js' ,
236
+ } ) ;
237
+ await worker . loadLanguage ( 'eng' ) ;
238
+ await worker . initialize ( 'eng' ) ;
239
+ const {
240
+ data : { text, pdf } ,
241
+ } = await worker
242
+ . recognize ( canvas . toDataURL ( ) , { pdfTitle : fileName } , { pdf : true } )
243
+ . catch ( ( e ) => console . error ( 'recognize' , e ) ) ;
244
+ await worker . terminate ( ) ;
245
+
246
+ const blob = new Blob ( [ new Uint8Array ( pdf ) ] , {
247
+ type : 'application/pdf' ,
248
+ } ) ;
249
+ const link = document . createElement ( 'a' ) ;
250
+ if ( link . download !== undefined ) {
251
+ const url = URL . createObjectURL ( blob ) ;
252
+ link . setAttribute ( 'href' , url ) ;
253
+ link . setAttribute ( 'download' , fileName ) ;
254
+ link . style . visibility = 'hidden' ;
255
+ document . body . appendChild ( link ) ;
256
+ link . click ( ) ;
257
+ document . body . removeChild ( link ) ;
258
+ }
259
+ } else {
260
+ const orient = canvas . width > canvas . height ? 'landscape' : 'portrait' ;
261
+ const pdf = new jsPDF ( orient , 'px' , [ canvas . width , canvas . height ] ) ;
262
+ pdf . addImage ( canvas , 'JPEG' , 0 , 0 , canvas . width , canvas . height ) ;
263
+ pdf . save ( fileName ) ;
264
+ }
265
+ return true ;
266
+ } )
267
+ . finally ( ( ) => {
268
+ if ( useForeignObjectRendering ) {
269
+ document
270
+ . querySelectorAll < HTMLSpanElement > (
271
+ 'span:not(.data-html2canvas-ignore)'
272
+ )
273
+ . forEach ( ( el ) => ( el . style . width = '' ) ) ;
274
+ document . querySelectorAll ( '.reportWrapper' ) . forEach ( ( e ) => e . remove ( ) ) ;
275
+ document
276
+ . querySelectorAll ( '.reportInjectedStyles' )
277
+ . forEach ( ( e ) => e . remove ( ) ) ;
278
+ document . body . style . paddingTop = '' ;
279
+ }
280
+ } ) ;
209
281
} ;
0 commit comments