You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

spipdf_fonctions.php 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455
  1. <?php
  2. /**
  3. * Génération d'article SPIP au format PDF.
  4. *
  5. * @package spiPDF
  6. * @author Yves Tannier [grafactory.net]
  7. * @copyright 2010-2017 Yves Tannier
  8. *
  9. * @link https://contrib.spip.net/3719
  10. * @link https://zone.spip.org/trac/spip-zone/browser/_plugins_/spipdf
  11. * @link http://www.grafactory.net/
  12. *
  13. * @license GPL Gnu Public Licence
  14. *
  15. * @version 1.2.0
  16. */
  17. if (!defined('_ECRIRE_INC_VERSION')){
  18. return;
  19. }
  20. // Options pour les marges des PDF, valables seulement pour la librairie mPDF
  21. // définissez vos options par défaut directement dans votre mes_options.php
  22. if (!defined('_SPIPDF_FORMAT')){
  23. define('_SPIPDF_FORMAT', 'A4');
  24. }
  25. if (!defined('_SPIPDF_MARGIN_TOP')){
  26. define('_SPIPDF_MARGIN_TOP', 20);
  27. }
  28. if (!defined('_SPIPDF_MARGIN_RIGHT')){
  29. define('_SPIPDF_MARGIN_RIGHT', 20);
  30. }
  31. if (!defined('_SPIPDF_MARGIN_BOTTOM')){
  32. define('_SPIPDF_MARGIN_BOTTOM', 15);
  33. }
  34. if (!defined('_SPIPDF_MARGIN_LEFT')){
  35. define('_SPIPDF_MARGIN_LEFT', 15);
  36. }
  37. if (!defined('_SPIPDF_MARGIN_HEADER')){
  38. define('_SPIPDF_MARGIN_HEADER', 2);
  39. }
  40. if (!defined('_SPIPDF_MARGIN_FOOTER')){
  41. define('_SPIPDF_MARGIN_FOOTER', 2);
  42. }
  43. // Charset (qui peut être défini dans un fichier d'options
  44. if (!defined('SPIPDF_CHARSET')){
  45. define('SPIPDF_CHARSET', 'UTF-8');
  46. //define('SPIPDF_CHARSET', 'ISO-8859-15');
  47. }
  48. // utilisé pour le constructeur de HTML2PDF
  49. if (SPIPDF_CHARSET=='UTF-8'){
  50. define('SPIPDF_UNICODE', true);
  51. } else {
  52. define('SPIPDF_UNICODE', false);
  53. }
  54. // pour les function unicode2charset
  55. include_spip('inc/charsets');
  56. // repris dans le plugin article_pdf => a modifier
  57. // https://zone.spip.org/trac/spip-zone/browser/_plugins_/article_pdf
  58. function spipdf_first_clean($texte){
  59. // supprimer les remarques HTML (du Couteau Suisse ?)
  60. $texte = preg_replace(',<!-- .* -->,msU', '', $texte);
  61. $trans = array();
  62. $trans["<br />\n"] = '<BR>'; // Pour éviter que le \n ne se tranforme en espace dans les <DIV class=spip_code> (TT, tag SPIP : code)
  63. // gestion d'un encodage latin1
  64. if (SPIPDF_CHARSET=='ISO-8859-15' || SPIPDF_CHARSET=='iso-8859-15'){
  65. $trans['&#176;'] = '°';
  66. $trans['&#339;'] = 'oe';
  67. $trans['&#8211;'] = '-';
  68. $trans['&#8216;'] = '\'';
  69. $trans['&#8217;'] = '\'';
  70. $trans['&#8220;'] = '"';
  71. $trans['&#8221;'] = '"';
  72. $trans['&#8230;'] = '...';
  73. $trans['&#8364;'] = 'Euros';
  74. $trans['&ucirc;'] = 'û';
  75. $trans['->'] = '-»';
  76. $trans['<-'] = '«-';
  77. $trans['&mdash;'] = '-';
  78. $trans['&deg;'] = '°';
  79. $trans['œ'] = 'oe';
  80. $trans['Œ'] = 'OE';
  81. $trans['…'] = '...';
  82. $trans['&euro;'] = 'Euros';
  83. $trans['€'] = 'Euros';
  84. $trans['&copy;'] = '©';
  85. }
  86. // pas d'insécable
  87. $trans['&nbsp;'] = ' ';
  88. // certains titles font paniquer l'analyse
  89. // TODO : a peaufiner car ils sont necessaires pour les signets
  90. // <bookmark title="Compatibilité" level="0" ></bookmark>
  91. // http://wiki.spipu.net/doku.php?id=html2pdf:fr:v4:bookmark
  92. //$texte = preg_replace(',title=".*",msU', 'title=""', $texte);
  93. $texte = strtr($texte, $trans);
  94. if (SPIPDF_CHARSET=='UTF-8'){
  95. $texte = charset2unicode($texte);
  96. } else {
  97. $texte = unicode2charset(charset2unicode($texte), SPIPDF_CHARSET); // Repasser tout dans le charset demandé
  98. }
  99. // Décoder les codes HTML dans le charset final
  100. $texte = html_entity_decode($texte, ENT_NOQUOTES, SPIPDF_CHARSET);
  101. return $texte;
  102. }
  103. //function spipdf_remplaceSpan($matches) { return str_replace('img', 'img style="padding:5px;" style="float:'.$matches[1].'"', $matches[2]); }
  104. function spipdf_remplaceSpan_wfloat($matches){
  105. return str_replace('img', 'img style="padding:5px;" class="pdf_img_float_' . $matches[1] . '"', $matches[2]);
  106. }
  107. function spipdf_remplaceSpan($matches){
  108. return str_replace('img', 'img style="padding:5px;" align="' . $matches[1] . '"', $matches[2]);
  109. }
  110. function spipdf_remplaceSpanCenter($matches){
  111. return $matches[1];
  112. }
  113. //function spipdf_remplaceDt($matches) { return str_replace('img', 'img style="padding:5px;" style="float:'.$matches[1].'"', $matches[2]); }
  114. function spipdf_remplaceDt_wfloat($matches){
  115. return str_replace('img', 'img style="padding:5px;" class="pdf_img_float_' . $matches[1] . '"', $matches[2]);
  116. }
  117. function spipdf_remplaceDt($matches){
  118. return str_replace('img', 'img style="padding:5px;" align="' . $matches[1] . '"', $matches[2]);
  119. }
  120. function spipdf_remplaceIdParName($matches){
  121. return str_replace('id=\'', 'name=\'', $matches[0]);
  122. }
  123. function spipdf_remplaceFloatPuce($matches){
  124. return str_replace('style=\'', 'style=\'float:left;', $matches[0]);
  125. }
  126. function spipdf_remplaceDtCenter($matches){
  127. return $matches[1];
  128. }
  129. function spipdf_remplaceCaption($matches){
  130. $table = '<table style="border:none;"' . $matches[1] . '<tr><td style="text-align: center;border:none;">' . $matches[2] . '</td></tr>';
  131. $table .= '<tr><td style="border:none;">';
  132. $table .= '<table' . $matches[1] . $matches[3] . '</table>';
  133. $table .= '</td></tr></table>';
  134. return $table;
  135. }
  136. function spipdf_nettoyer_html($html, $params_pdf = array()){
  137. // supprimer les spans autour des images
  138. $patterns_float = '/<span class=\'spip_document_[0-9]+ spip_documents.*>(.*)<\/span>/iUms';
  139. $html = preg_replace_callback($patterns_float, 'spipdf_remplaceSpanCenter', $html);
  140. // supprimer les spans autour des images et récupérer le placement
  141. $patterns_float = '/<span class=\'spip_document_[0-9]+ spip_documents.*float:(.*);.*>(.*)<\/span>/iUms';
  142. $html = preg_replace_callback($patterns_float, !empty($params_pdf['float']) ? 'spipdf_remplaceSpan' : 'spipdf_remplaceSpan_wfloat', $html);
  143. // supprimer les dl autour des images et récupérer le placement
  144. $patterns_float = '/<dl class=\'spip_document_[0-9]+ spip_documents.*float:(.*);.*<dt>(.*)<\/dt>.*<\/dl>/iUms';
  145. $html = preg_replace_callback($patterns_float, !empty($params_pdf['float']) ? 'spipdf_remplaceDt' : 'spipdf_remplaceDt_wfloat', $html);
  146. // replacer id par name pour les notes
  147. $patterns_note = '/<a[^>]*href[^>]*class=\'spip_note\'[^>]*>/iUms';
  148. $html = preg_replace_callback($patterns_note, 'spipdf_remplaceIdParName', $html);
  149. // float sur les puces graphiques
  150. $patterns_puce = '/<img[^>]*class=[\'"]puce[\'"] alt=[\'"]-[\'"][^>]*>/iUms';
  151. $html = preg_replace($patterns_puce, '-', $html);
  152. // supprimer les dl autour des images centrer
  153. $patterns_float = '/<dl class=\'spip_document_[0-9]+ spip_documents.*<dt>(.*)<\/dt>.*<\/dl>/iUms';
  154. $html = preg_replace_callback($patterns_float, 'spipdf_remplaceDtCenter', $html);
  155. // remplacer les captions
  156. if (!empty($params_pdf['caption'])){
  157. $patterns_caption = '/<table(.*)<caption>(.*)<\/caption>(.*)<\/table>/iUms';
  158. $html = preg_replace_callback($patterns_caption, 'spipdf_remplaceCaption', $html);
  159. }
  160. // tableaux centré
  161. $html = preg_replace('/<table/iUms', '<table align="center"', $html);
  162. // balise cadre
  163. $patterns_cadre = '/<textarea[^>]*class=[\'"]spip_cadre[\'"] [^>]*>(.*)<\/textarea>/iUms';
  164. $html = preg_replace($patterns_cadre, '<div class="spip_cadre">$2</div>', $html);
  165. // gestion des caractères spéciaux et de charset
  166. $html = spipdf_first_clean($html);
  167. return $html;
  168. }
  169. // traiter la balise page
  170. function traite_balise_page($html){
  171. // on teste la balise page
  172. if (preg_match('/<page\s(.*)>/iUms', $html, $matches)){
  173. // on crée un tableau avec (beurk) Global pour accèder aux valeurs de pages
  174. if (!empty($matches[1])){
  175. $balise_page = $matches[1];
  176. $pattern = '/(.*)="(.*)"/iUms';
  177. getBalise($matches);
  178. $balise_page = preg_replace_callback($pattern, 'getBalise', $balise_page);
  179. // supprimer <page> et </page>
  180. $html = preg_replace('/<\/?page\s(.*)>/iUms', '', $html);
  181. $html = preg_replace('/<\/page>/iUms', '', $html);
  182. return $html;
  183. }
  184. } else {
  185. return $html;
  186. }
  187. }
  188. //On sort cette fonction de la fonction traite_balise_page
  189. function getBalise($matches){
  190. $matches = array_pad($matches, 3, null);
  191. $matches[2] = str_replace('mm', '', $matches[2]);
  192. $GLOBALS['valeurs_page'][trim($matches[1])] = trim($matches[2]);
  193. }
  194. /**
  195. * traitement principal. avec ce pipeline, le PDF est mis en cache et recalculé "normalement"
  196. *
  197. *
  198. * @param string $html
  199. * Le contenu HTML à transformer en PDF
  200. * @param string/bool $file
  201. * Nom du fichier vers lequel enregistrer (uniquement fonctionnel avec mpdf pour l'instant)
  202. * @return string
  203. * Contenu binaire du PDF généré
  204. */
  205. function spipdf_html2pdf($html, $file = false){
  206. // les librairies possibles
  207. $possible_librairies = array(
  208. 'mpdf' => array( // gére le float d'image mais pas les captions de tableau
  209. 'float' => true,
  210. 'caption' => true,
  211. 'traite_balise_page' => true,
  212. ),
  213. 'html2pdf' => array( // ne gére pas le float d'image et les captions
  214. 'float' => false,
  215. 'caption' => true,
  216. ),
  217. 'dompdf' => array( // domPDF beta 0.6 EXPERIMENTAL
  218. 'float' => false,
  219. 'caption' => true,
  220. 'traite_balise_page' => true,
  221. ),
  222. );
  223. // choix de la classe de génération via la balise <page lib>
  224. if (preg_match('/\<page\s*.lib_pdf=["|\'](.*)["|\']/iUms', $html, $match_librairie)
  225. && !empty($match_librairie[1])
  226. && array_key_exists(strtolower($match_librairie[1]), $possible_librairies)
  227. ){
  228. $librairie_pdf = strtolower($match_librairie[1]);
  229. } else {
  230. $librairie_pdf = 'mpdf';
  231. }
  232. // tester si la librairie dans un sous-dossier de lib/ à la racine du spip ou dans le dossier squelettes/ ou dans un plugin
  233. $dir_librairie_pdf = find_in_path("lib/$librairie_pdf/");
  234. if (!$dir_librairie_pdf){
  235. die('Impossible de trouver la librairie de génération de PDF ' . $librairie_pdf . '. vérifiez que vous l\'avez bien téléchargée et installée dans lib/ ou squelettes/lib/');
  236. }
  237. // nettoyer le HTML et gérer les placements d'image en fonction de la librairie utilisée
  238. $html = spipdf_nettoyer_html($html, $possible_librairies[$librairie_pdf]);
  239. // Debug = voir le html sans génération de PDF
  240. if (isset($_GET['debug_spipdf'])){
  241. echo $html;
  242. exit;
  243. }
  244. // du A4 par defaut
  245. $format_page = _SPIPDF_FORMAT;
  246. // traiter la balise page pour les librairies qui ne la comprennent pas
  247. if (!empty($possible_librairies[$librairie_pdf]['traite_balise_page'])){
  248. $html = traite_balise_page($html);
  249. // dans balise_page, on ne récupère que quelques possibilité dont le format
  250. if (!empty($GLOBALS['valeurs_page'])){
  251. if (!empty($GLOBALS['valeurs_page']['format'])){
  252. $format_page = $GLOBALS['valeurs_page']['format'];
  253. }
  254. if (!empty($GLOBALS['valeurs_page']['backtop'])){
  255. $backtop = $GLOBALS['valeurs_page']['backtop'];
  256. } else {
  257. $backtop = _SPIPDF_MARGIN_TOP;
  258. }
  259. if (!empty($GLOBALS['valeurs_page']['backbottom'])){
  260. $backbottom = $GLOBALS['valeurs_page']['backbottom'];
  261. } else {
  262. $backbottom = _SPIPDF_MARGIN_BOTTOM;
  263. }
  264. if (!empty($GLOBALS['valeurs_page']['backleft'])){
  265. $backleft = $GLOBALS['valeurs_page']['backleft'];
  266. } else {
  267. $backleft = _SPIPDF_MARGIN_LEFT;
  268. }
  269. if (!empty($GLOBALS['valeurs_page']['backright'])){
  270. $backright = $GLOBALS['valeurs_page']['backright'];
  271. } else {
  272. $backright = _SPIPDF_MARGIN_RIGHT;
  273. }
  274. if (!empty($GLOBALS['valeurs_page']['margin_header'])){
  275. $margin_header = $GLOBALS['valeurs_page']['margin_header'];
  276. } else {
  277. $margin_header = _SPIPDF_MARGIN_HEADER;
  278. }
  279. if (!empty($GLOBALS['valeurs_page']['margin_footer'])){
  280. $margin_footer = $GLOBALS['valeurs_page']['margin_footer'];
  281. } else {
  282. $margin_footer = _SPIPDF_MARGIN_FOOTER;
  283. }
  284. }
  285. }
  286. if ($librairie_pdf=='mpdf'){ // la librairie mPDF
  287. // si il y a des options dans la balise page
  288. // http://mpdf1.com/manual/index.php?tid=307
  289. // le chemin relatif vers mPDF
  290. if (!defined('_MPDF_PATH')) {
  291. define('_MPDF_PATH', $dir_librairie_pdf);
  292. }
  293. // les fichiers tmp dans le tmp/ de spip
  294. if (!defined('_MPDF_TEMP_PATH')) {
  295. define("_MPDF_TEMP_PATH", sous_repertoire(_DIR_TMP, 'mpdf'));
  296. }
  297. if (!defined('_MPDF_TTFONTDATAPATH')) {
  298. define('_MPDF_TTFONTDATAPATH', sous_repertoire(_DIR_CACHE, 'ttfontdata'));
  299. }
  300. include_once _MPDF_PATH . 'mpdf.php';
  301. // la classe mPDF
  302. $mpdf = new mPDF(SPIPDF_CHARSET, $format_page, 0, '', $backleft, $backright, $backtop, $backbottom, $margin_header, $margin_footer);
  303. $mpdf->WriteHTML($html);
  304. /**
  305. * Si un nom de fichier est fourni, on enregistre le fichier,
  306. * sinon envoyer le code binaire du PDF dans le flux
  307. */
  308. $html = $mpdf->Output($file, $file ? 'F' : 'S');
  309. $echap_special_pdf_chars = true;
  310. } elseif ($librairie_pdf=='dompdf') { // la librairie dompdf beta 0.6 // EXPERIMENTAL
  311. // le chemin relatif vers mPDF
  312. require_once $dir_librairie_pdf . 'dompdf_config.inc.php';
  313. $dompdf = new DOMPDF();
  314. $dompdf->load_html($html, SPIPDF_CHARSET);
  315. $dompdf->set_paper($format_page);
  316. $dompdf->render();
  317. $html = $dompdf->output();
  318. if ($file) {
  319. include_spip('inc/flock');
  320. ecrire_fichier($file, $html);
  321. }
  322. // envoyer le code binaire du PDF dans le flux
  323. $echap_special_pdf_chars = true;
  324. } else { // la librairie HTML2PDF par défaut
  325. // appel de la classe HTML2pdf
  326. require_once $dir_librairie_pdf . 'html2pdf.class.php';
  327. try {
  328. if ($flux['args']['contexte']['lang'] == '')
  329. $lang = 'fr';
  330. else
  331. $lang = $flux['args']['contexte']['lang'];
  332. // les paramétres d'orientation et de format son écrasé par ceux défini dans la balise <page> du squelette
  333. $html2pdf = new HTML2PDF('P', $format_page, $lang, SPIPDF_UNICODE, SPIPDF_CHARSET);
  334. // mode debug de HTML2PDF
  335. if (defined('SPIPDF_DEBUG_HTML2PDF')){
  336. $html2pdf->setModeDebug();
  337. }
  338. // police différente selon unicode ou latin
  339. if (SPIPDF_UNICODE){
  340. $police_caractere = 'FreeSans';
  341. } else {
  342. $police_caractere = 'Arial';
  343. }
  344. $html2pdf->setDefaultFont($police_caractere);
  345. $html2pdf->writeHTML($html);
  346. $html = $html2pdf->Output($file, $file ? 'F' : 'S'); // envoyer le code binaire du PDF dans le flux
  347. $echap_special_pdf_chars = true;
  348. } catch (HTML2PDF_exception $e) {
  349. echo $e;
  350. }
  351. }
  352. // On échappe les suites de caractères <? pour éviter des erreurs d'évaluation PHP (seront remis en place avec affichage_final)
  353. // l'erreur d'évaluation est liée à la directive short_open_tag=On dans la configuration de PHP
  354. if (!empty($echap_special_pdf_chars)
  355. and strpos($html, '<' . '?')!==false
  356. ){
  357. $html = str_replace('<' . '?', "<\2\2?", $html);
  358. }
  359. return $html;
  360. }
  361. /**
  362. * On rétablit les <? du code PDF si necessaire
  363. * on n'agit que sur les pages non html.
  364. *
  365. * @param string $texte
  366. *
  367. * @return string
  368. */
  369. function spipdf_affichage_final($texte){
  370. if ($GLOBALS['html']==false
  371. and strpos($texte, "<\2\2?")!==false
  372. ){
  373. $texte = str_replace("<\2\2?", '<' . '?', $texte);
  374. }
  375. return $texte;
  376. }
  377. /**
  378. * Ne pas permettre d'aller chercher un fond en sous-repertoire dans spipdf.html.
  379. *
  380. * @param $fond
  381. *
  382. * @return mixed
  383. */
  384. function spipdf_securise_fond($fond){
  385. $fond = str_replace('/', '_', $fond);
  386. $fond = str_replace('\\', '_', $fond);
  387. return $fond;
  388. }