1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 | import { Matcher, UpcastWriter } from 'ckeditor5/src/engine';
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 | export function replaceImagesSourceWithBase64( documentFragment, rtfData ) {
|
22 | if ( !documentFragment.childCount ) {
|
23 | return;
|
24 | }
|
25 |
|
26 | const upcastWriter = new UpcastWriter();
|
27 | const shapesIds = findAllShapesIds( documentFragment, upcastWriter );
|
28 |
|
29 | removeAllImgElementsRepresentingShapes( shapesIds, documentFragment, upcastWriter );
|
30 | removeAllShapeElements( documentFragment, upcastWriter );
|
31 |
|
32 | const images = findAllImageElementsWithLocalSource( documentFragment, upcastWriter );
|
33 |
|
34 | if ( images.length ) {
|
35 | replaceImagesFileSourceWithInlineRepresentation( images, extractImageDataFromRtf( rtfData ), upcastWriter );
|
36 | }
|
37 | }
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 | export function _convertHexToBase64( hexString ) {
|
47 | return btoa( hexString.match( /\w{2}/g ).map( char => {
|
48 | return String.fromCharCode( parseInt( char, 16 ) );
|
49 | } ).join( '' ) );
|
50 | }
|
51 |
|
52 |
|
53 |
|
54 |
|
55 |
|
56 |
|
57 |
|
58 |
|
59 | function findAllShapesIds( documentFragment, writer ) {
|
60 | const range = writer.createRangeIn( documentFragment );
|
61 |
|
62 | const shapeElementsMatcher = new Matcher( {
|
63 | name: /v:(.+)/
|
64 | } );
|
65 |
|
66 | const shapesIds = [];
|
67 |
|
68 | for ( const value of range ) {
|
69 | if ( value.type != 'elementStart' ) {
|
70 | continue;
|
71 | }
|
72 |
|
73 | const el = value.item;
|
74 | const prevSiblingName = el.previousSibling && el.previousSibling.name || null;
|
75 |
|
76 |
|
77 | if ( shapeElementsMatcher.match( el ) && el.getAttribute( 'o:gfxdata' ) && prevSiblingName !== 'v:shapetype' ) {
|
78 | shapesIds.push( value.item.getAttribute( 'id' ) );
|
79 | }
|
80 | }
|
81 |
|
82 | return shapesIds;
|
83 | }
|
84 |
|
85 |
|
86 |
|
87 |
|
88 |
|
89 |
|
90 | function removeAllImgElementsRepresentingShapes( shapesIds, documentFragment, writer ) {
|
91 | const range = writer.createRangeIn( documentFragment );
|
92 |
|
93 | const imageElementsMatcher = new Matcher( {
|
94 | name: 'img'
|
95 | } );
|
96 |
|
97 | const imgs = [];
|
98 |
|
99 | for ( const value of range ) {
|
100 | if ( imageElementsMatcher.match( value.item ) ) {
|
101 | const el = value.item;
|
102 | const shapes = el.getAttribute( 'v:shapes' ) ? el.getAttribute( 'v:shapes' ).split( ' ' ) : [];
|
103 |
|
104 | if ( shapes.length && shapes.every( shape => shapesIds.indexOf( shape ) > -1 ) ) {
|
105 | imgs.push( el );
|
106 |
|
107 | } else if ( !el.getAttribute( 'src' ) ) {
|
108 | imgs.push( el );
|
109 | }
|
110 | }
|
111 | }
|
112 |
|
113 | for ( const img of imgs ) {
|
114 | writer.remove( img );
|
115 | }
|
116 | }
|
117 |
|
118 |
|
119 |
|
120 |
|
121 |
|
122 | function removeAllShapeElements( documentFragment, writer ) {
|
123 | const range = writer.createRangeIn( documentFragment );
|
124 |
|
125 | const shapeElementsMatcher = new Matcher( {
|
126 | name: /v:(.+)/
|
127 | } );
|
128 |
|
129 | const shapes = [];
|
130 |
|
131 | for ( const value of range ) {
|
132 | if ( value.type == 'elementStart' && shapeElementsMatcher.match( value.item ) ) {
|
133 | shapes.push( value.item );
|
134 | }
|
135 | }
|
136 |
|
137 | for ( const shape of shapes ) {
|
138 | writer.remove( shape );
|
139 | }
|
140 | }
|
141 |
|
142 |
|
143 |
|
144 |
|
145 |
|
146 |
|
147 |
|
148 |
|
149 | function findAllImageElementsWithLocalSource( documentFragment, writer ) {
|
150 | const range = writer.createRangeIn( documentFragment );
|
151 |
|
152 | const imageElementsMatcher = new Matcher( {
|
153 | name: 'img'
|
154 | } );
|
155 |
|
156 | const imgs = [];
|
157 |
|
158 | for ( const value of range ) {
|
159 | if ( imageElementsMatcher.match( value.item ) ) {
|
160 | if ( value.item.getAttribute( 'src' ).startsWith( 'file://' ) ) {
|
161 | imgs.push( value.item );
|
162 | }
|
163 | }
|
164 | }
|
165 |
|
166 | return imgs;
|
167 | }
|
168 |
|
169 |
|
170 |
|
171 |
|
172 |
|
173 |
|
174 |
|
175 |
|
176 | function extractImageDataFromRtf( rtfData ) {
|
177 | if ( !rtfData ) {
|
178 | return [];
|
179 | }
|
180 |
|
181 | const regexPictureHeader = /{\\pict[\s\S]+?\\bliptag-?\d+(\\blipupi-?\d+)?({\\\*\\blipuid\s?[\da-fA-F]+)?[\s}]*?/;
|
182 | const regexPicture = new RegExp( '(?:(' + regexPictureHeader.source + '))([\\da-fA-F\\s]+)\\}', 'g' );
|
183 | const images = rtfData.match( regexPicture );
|
184 | const result = [];
|
185 |
|
186 | if ( images ) {
|
187 | for ( const image of images ) {
|
188 | let imageType = false;
|
189 |
|
190 | if ( image.includes( '\\pngblip' ) ) {
|
191 | imageType = 'image/png';
|
192 | } else if ( image.includes( '\\jpegblip' ) ) {
|
193 | imageType = 'image/jpeg';
|
194 | }
|
195 |
|
196 | if ( imageType ) {
|
197 | result.push( {
|
198 | hex: image.replace( regexPictureHeader, '' ).replace( /[^\da-fA-F]/g, '' ),
|
199 | type: imageType
|
200 | } );
|
201 | }
|
202 | }
|
203 | }
|
204 |
|
205 | return result;
|
206 | }
|
207 |
|
208 |
|
209 |
|
210 |
|
211 |
|
212 |
|
213 |
|
214 | function replaceImagesFileSourceWithInlineRepresentation( imageElements, imagesHexSources, writer ) {
|
215 |
|
216 | if ( imageElements.length === imagesHexSources.length ) {
|
217 | for ( let i = 0; i < imageElements.length; i++ ) {
|
218 | const newSrc = `data:${ imagesHexSources[ i ].type };base64,${ _convertHexToBase64( imagesHexSources[ i ].hex ) }`;
|
219 | writer.setAttribute( 'src', newSrc, imageElements[ i ] );
|
220 | }
|
221 | }
|
222 | }
|